module Roby::TaskStructure::Dependency::Extension
Public Instance Methods
Returns the child whose role is role_name
If validate
is true (the default), raises ArgumentError if there is none. Otherwise, returns nil. This argument is meant only to avoid the costly operation of raising an exception in cases it is expected that the role may not exist.
# File lib/roby/task_structure/dependency.rb, line 387 def child_from_role(role_name, validate = true) if !validate Roby.warn_deprecated "#child_from_role(name, false) has been replaced by #find_child_from_role" end child = find_child_from_role(role_name) if !child && validate known_children = Hash.new each_out_neighbour_merged(Dependency, intrusive: false) do |myself, child| myself[child, Dependency][:roles].each do |role| known_children[role] = child end end raise Roby::NoSuchChild.new(self, role_name, known_children), "#{self} has no child with the role '#{role_name}'" end child end
The set of child objects in the Dependency
relation
# File lib/roby/task_structure/dependency.rb, line 295 def children; child_objects(Dependency) end
True if obj
is a parent of this object in the hierarchy relation (obj
is realized by self
)
# File lib/roby/task_structure/dependency.rb, line 278 def depended_upon_by?(obj); parent_object?(obj, Dependency) end
Adds task
as a child of self
in the Dependency
relation. The following options are allowed:
- success
-
the list of success events. The default is [:success]
- failure
-
the list of failing events. The default is [:failed]
- model
-
a
[task_model, arguments]
pair which defines the task model the parent is expecting. The default value is to get these parameters fromtask
The success
set describes the events of the child task that are required by the parent task. More specifically, the child task remains useful for the parent task as long as none of these events are emitted. By default, it is the success
event. Of course, an error condition is encountered when all events of success
become unreachable. In addition, the relation is removed if the remove_when_done
flag is set to true (false by default).
The failure
set describes the events of the child task which are an error condition from the parent task point of view.
In both error cases, a ChildFailedError
exception is raised.
# File lib/roby/task_structure/dependency.rb, line 499 def depends_on(task, options = {}) if task.respond_to?(:as_plan) task = task.as_plan end if task == self raise ArgumentError, "cannot add a dependency of a task to itself" end options = Dependency.validate_options options, model: [task.provided_models, task.meaningful_arguments], success: :success.to_unbound_task_predicate, failure: false.to_unbound_task_predicate, remove_when_done: true, consider_in_pending: true, roles: nil, role: nil # We accept # # model # [model1, model2] # [model1, arguments] # [[model1, model2], arguments] if !options[:model].respond_to?(:to_ary) options[:model] = [Array(options[:model]), Hash.new] elsif options[:model].size == 2 if !options[:model].first.respond_to?(:to_ary) if options[:model].last.kind_of?(Hash) options[:model] = [Array(options[:model].first), options[:model].last] else options[:model] = [options[:model], Hash.new] end end elsif !options[:model].first.respond_to?(:to_ary) options[:model] = [Array(options[:model]), Hash.new] end roles = options[:roles] || Set.new if role = options.delete(:role) roles << role.to_str end roles = roles.map { |r| r.to_str } options[:roles] = roles.to_set if options[:success].nil? options[:success] = [] end options[:success] = Array[*options[:success]]. map { |predicate| predicate.to_unbound_task_predicate }. inject(&:or) if options[:failure].nil? options[:failure] = [] end options[:failure] = Array[*options[:failure]]. map { |predicate| predicate.to_unbound_task_predicate }. inject(&:or) #options[:success] ||= false.to_unbound_task_predicate #options[:failure] ||= false.to_unbound_task_predicate # Validate failure and success event names if options[:success] not_there = options[:success].required_events. find_all { |name| !task.has_event?(name) } if !not_there.empty? raise ArgumentError, "#{task} does not have the following events: #{not_there.join(", ")}" end end if options[:failure] not_there = options[:failure].required_events. find_all { |name| !task.has_event?(name) } if !not_there.empty? raise ArgumentError, "#{task} does not have the following events: #{not_there.join(", ")}" end end # There is no positive events in success. Behind the scenes, it # actually means that the task does not have to start (since nothing # in :success would become unreachable) # # Add !:start in failure if !options[:success] not_started = :start.to_unbound_task_predicate.never if options[:failure] options[:failure] = not_started.or(options[:failure]) else options[:failure] = not_started end end required_model, required_args = *options[:model] if !required_args.respond_to?(:to_hash) raise ArgumentError, "argument specification must be a hash, got #{required_args} (#{required_args.class})" elsif !task.fullfills?(required_model, required_args) raise ArgumentError, "task #{task} does not fullfill the provided model #{options[:model]}" end # Check if there is already a dependency link. If it is the case, # merge the options. Otherwise, just add. add_child(task, options) task end
True if obj
is a child of this object in the hierarchy relation. If recursive
is true, take into account the whole subgraph. Otherwise, only direct children are checked.
# File lib/roby/task_structure/dependency.rb, line 283 def depends_on?(obj, recursive: false) if recursive relation_graph_for(Dependency). depth_first_visit(obj) { |v| return true if v == obj } return false else child_object?(obj, Dependency) end end
Enumerates the models that are fullfilled by this task
@return [Array<Model<Task>,TaskService>] @see provided_models
# File lib/roby/task_structure/dependency.rb, line 747 def each_fullfilled_model(&block) fullfilled_model[0].each(&block) end
Enumerates all the roles this task has
# File lib/roby/task_structure/dependency.rb, line 316 def each_role(&block) if !block_given? return enum_for(:each_role, &block) end each_parent_object(Dependency) do |parent| yield(parent, parent.roles_of(self)) end end
Returns an explicitly set {#fullfilled_model}
@return [nil,Object] either nil if no explicit model has been set, or
the model in the same format as expected by {#fullfilled_model=} (which is different than the value returned by {#fullfilled_model})
# File lib/roby/task_structure/dependency.rb, line 717 def explicit_fullfilled_model if explicit = @fullfilled_model explicit elsif explicit = self.model.explicit_fullfilled_model tasks, tags = explicit.partition { |m| m <= Roby::Task } [tasks.first || Roby::Task, tags, Hash.new] end end
True if fullfilled_model
has been set on this task or on this task's model
@return [Boolean]
# File lib/roby/task_structure/dependency.rb, line 708 def explicit_fullfilled_model? !!explicit_fullfilled_model end
Returns the child whose role is role_name
@return [nil,Task] the task if a dependency with the given role is
found, and nil otherwise
# File lib/roby/task_structure/dependency.rb, line 367 def find_child_from_role(role_name) each_out_neighbour_merged(Dependency, intrusive: false) do |myself, child| roles = myself[child, Dependency][:roles] if roles.include?(role_name) if plan return plan[child] else return child end end end nil end
# File lib/roby/task_structure/dependency.rb, line 770 def find_through_method_missing(m, args) MetaRuby::DSLs.find_through_method_missing( self, m, args, '_child' => :find_child_from_role) end
Return the set of this task children for which the :start event has no parent in CausalLinks
# File lib/roby/task_structure/dependency.rb, line 620 def first_children result = Set.new causal_link_graph = plan.event_relation_graph_for(EventStructure::CausalLink) relation_graph_for(Dependency).depth_first_visit(self) do |task| next if task == self if task != self && causal_link_graph.root?(task.start_event) result << task end end result end
The list of models and arguments that this task fullfilles
If there is a task model in the list of models, it is always the first element of the model set
@return [(Array<Model<Task>,Model<TaskService>>,{String=>Object}]
Beware that, for historical reasons, this is not the same format than {#fullfilled_model=}
# File lib/roby/task_structure/dependency.rb, line 679 def fullfilled_model if current_model = explicit_fullfilled_model has_value = true else current_model = [Roby::Task, [], {}] end each_in_neighbour_merged(Dependency, intrusive: false) do |myself, parent| has_value = true required_models, required_arguments = parent[myself, Dependency][:model] current_model = Dependency.merge_fullfilled_model(current_model, required_models, required_arguments) end if !has_value model = self.model.fullfilled_model.find_all { |m| m <= Roby::Task }.min [[model], self.meaningful_arguments] else model, tags, arguments = *current_model tags = tags.dup tags.unshift model [tags, arguments] end end
Sets a base model specification that must be met by this task
In normal operations, the fullfilled model returned by fullfilled_model
is computed from the dependency relations in which self
is a child.
However, this fails in case self
is a root task in the dependency relation. Moreover, it might be handy to over-constrain the model computed through the dependency relation.
In both cases, a model can be specified explicitely by setting the fullfilled_model
attribute. The value has to be
[task_model, [tag1, tag2, ...], task_arguments]
For instance, a completely non-constrained model would be
[Roby::Task, [], {}]
This parameter can be set model-wide by using fullfilled_model=
on the class object
# File lib/roby/task_structure/dependency.rb, line 654 def fullfilled_model=(model) if !model[0].kind_of?(Class) raise ArgumentError, "expected a task model as first element, got #{model[0]}" end if !model[1].respond_to?(:to_ary) raise ArgumentError, "expected an array as second element, got #{model[1]}" elsif !model[1].all? { |t| t.kind_of?(Roby::Models::TaskServiceModel) } raise ArgumentError, "expected an array of model tags as second element, got #{model[1]}" end if !model[2].respond_to?(:to_hash) raise ArgumentError, "expected a hash as third element, got #{model[2]}" end @fullfilled_model = model end
# File lib/roby/task_structure/dependency.rb, line 329 def has_role?(role_name) !!find_child_from_role(role_name) end
# File lib/roby/task_structure/dependency.rb, line 766 def has_through_method_missing?(m) MetaRuby::DSLs.has_through_method_missing?( self, m, '_child' => :has_role?) end
Returns the single parent task for this task
If there is more than one parent or no parent at all, raise an exception
# File lib/roby/task_structure/dependency.rb, line 299 def parent_task parents = each_parent_task.to_a if parents.size > 1 raise ArgumentError, "#{self} has #{parents.size} parents (#{parents.map(&:to_s).join(", ")}. A single parent was expected" elsif parents.empty? raise ArgumentError, "#{self} has no parents. A single parent was expected" end parents.first end
The set of parent objects in the Dependency
relation
# File lib/roby/task_structure/dependency.rb, line 293 def parents; parent_objects(Dependency) end
Returns the set of models this task is providing by itself
It differs from fullfilled_model
because it is not considering the models that are required because of the dependency relation
@return [Array<Models::Task,TaskService>] @see fullfilled_model
# File lib/roby/task_structure/dependency.rb, line 733 def provided_models if model = explicit_fullfilled_model [model[0]] + model[1] else models = self.model.fullfilled_model task_class = models.find { |m| m.kind_of?(Class) } [task_class] + models.find_all { |m| !task_class.has_ancestor?(m) } end end
# File lib/roby/task_structure/dependency.rb, line 604 def remove_dependency(task_or_role) if task_or_role.respond_to?(:to_str) remove_child(child_from_role(task_or_role)) else remove_child(task_or_role) end end
Remove all children that have successfully finished
# File lib/roby/task_structure/dependency.rb, line 752 def remove_finished_children # We call #to_a to get a copy of children, since we will remove # children in the block. Note that we can't use #delete_if here # since #children is a relation enumerator (not the relation list # itself) children = each_child.to_a for child in children child, info = child if info[:success].evaluate(child) remove_child(child) end end end
Remove a given role this task's child
@param [Task] child the child task @param [Array<String>] roles the roles that should be removed @param [Boolean] remove_child_when_empty if true (the default), the
child will be removed from this task's children if the set of roles is empty
@raise [ArgumentError] if the child does not have the expected role @return [Boolean] true if the child is still a child of this task
after the call, and false otherwise
# File lib/roby/task_structure/dependency.rb, line 343 def remove_roles(child, *roles, remove_child_when_empty: true) dependency_info = self[child, Dependency].dup child_roles = dependency_info[:roles].dup roles.each do |r| if !child_roles.include?(r) raise ArgumentError, "#{r} is not a role of #{child} with respect to #{self}" end child_roles.delete(r) end if child_roles.empty? && remove_child_when_empty remove_child(child) false else dependency_info[:roles] = child_roles self[child, Dependency] = dependency_info true end end
Returns a task in the dependency hierarchy of this task by following the roles. path
is an array of role names, and the method will follow the trail until the desired task
Raises ArgumentError if the child does not exist
See role_path to get a role path for a specific task
# File lib/roby/task_structure/dependency.rb, line 412 def resolve_role_path(*path) if path.size == 1 && path[0].respond_to?(:to_ary) path = path[0] end # Special case for ease of use in algorithms if path.empty? return self end up_until_now = [] path.inject(self) do |task, role| up_until_now << role if !(next_task = task.find_child_from_role(role)) raise ArgumentError, "the child #{up_until_now.join(".")} of #{task} does not exist" end next_task end end
Returns a set role paths that lead to task
when starting from self
A role path is an array of roles that lead to task
when starting by self
.
I.e. if ['role1', 'role2', 'role3'] is a role path from self
to +task, it means that
task1 = self.child_from_role('role1') task2 = task1.child_from_role('role2') task = task2.child_from_role('role3')
The method returns a set of role paths, as there may be multiple paths leading from self
to task
See resolve_role_path
to get a task from its role path
# File lib/roby/task_structure/dependency.rb, line 447 def role_paths(task, validate = true) if task == self return [] end result = [] task.each_role do |parent, roles| if parent == self new_paths = roles.map { |r| [r] } elsif heads = role_paths(parent, false) heads.each do |h| roles.each do |t| result << (h.dup << t) end end end if new_paths result.concat(new_paths) end end if result.empty? if validate raise ArgumentError, "#{task} can not be reached from #{self}" end return end result end
# File lib/roby/task_structure/dependency.rb, line 325 def roles each_role.map { |_, roles| roles.to_a }.flatten.to_set end
Returns the set of roles that child
has
# File lib/roby/task_structure/dependency.rb, line 310 def roles_of(child) info = self[child, Dependency] info[:roles] end