module Roby::TaskStructure::Dependency::Extension

Public Instance Methods

child_from_role(role_name, validate = true) click to toggle source

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
children() click to toggle source

The set of child objects in the Dependency relation

# File lib/roby/task_structure/dependency.rb, line 295
def children; child_objects(Dependency) end
depended_upon_by?(obj) click to toggle source

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
depends_on(task, options = {}) click to toggle source

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 from task

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
depends_on?(obj, recursive: false) click to toggle source

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
each_fullfilled_model(&block) click to toggle source

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
each_role() { |parent, roles_of| ... } click to toggle source

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
explicit_fullfilled_model() click to toggle source

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
explicit_fullfilled_model?() click to toggle source

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
find_child_from_role(role_name) click to toggle source

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
find_through_method_missing(m, args) click to toggle source
# 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
first_children() click to toggle source

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
fullfilled_model() click to toggle source

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
fullfilled_model=(model) click to toggle source

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
has_role?(role_name) click to toggle source
# File lib/roby/task_structure/dependency.rb, line 329
def has_role?(role_name)
    !!find_child_from_role(role_name)
end
has_through_method_missing?(m) click to toggle source
# 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
parent_task() click to toggle source

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
parents() click to toggle source

The set of parent objects in the Dependency relation

# File lib/roby/task_structure/dependency.rb, line 293
def parents; parent_objects(Dependency) end
provided_models() click to toggle source

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
remove_dependency(task_or_role) click to toggle source
# 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_finished_children() click to toggle source

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_roles(child, *roles, remove_child_when_empty: true) click to toggle source

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
resolve_role_path(*path) click to toggle source

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
role_paths(task, validate = true) click to toggle source

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
roles() click to toggle source
# File lib/roby/task_structure/dependency.rb, line 325
def roles
    each_role.map { |_, roles| roles.to_a }.flatten.to_set
end
roles_of(child) click to toggle source

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