class Roby::Relations::Space

A relation space is a module which handles a list of relations (Relations::Graph instances) and applies them to a set of classes. For instance, the TaskStructure relation space is defined by

TaskStructure = Space(Task)

See the files in roby/relations to see example definitions of new relations

Use Space#relation allow to define a new relation in a given space. For instance, one can either do

TaskStructure.relation :NewRelation

or

module TaskStructure
    relation :NewRelation
end

This relation can then be referenced by TaskStructure::NewRelation

Attributes

applied[R]

The set of classes on which the relations have been applied

default_graph_class[RW]

The default graph class to be used for new relations. Defaults to Relations::Graph

relations[R]

The set of relations included in this relation space

Public Class Methods

new_relation_graph_mapping() click to toggle source
# File lib/roby/relations/space.rb, line 40
def self.new_relation_graph_mapping
    Hash.new do |h, k|
        if k
            if k.kind_of?(Class)
                known_relations = h.each_key.find_all { |rel| rel.kind_of?(Class) }
                raise ArgumentError, "#{k} is not a known relation (known relations are #{known_relations.map { |o| "#{o.name}" }.join(", ")})"
            elsif known_graph = h.fetch(k.class, nil)
                raise ArgumentError, "it seems that you're trying to use the relation API to access a graph that is not part of this object's current plan. Given graph was #{k.object_id}, and the current graph for #{k.class} is #{known_graph.object_id}"
            else
                raise ArgumentError, "graph object #{known_graph} is not a known relation graph"
            end
        end
    end
end

Public Instance Methods

add_edge(parent, child, info) click to toggle source
Calls superclass method
# File lib/roby/relations/space.rb, line 335
def add_edge(parent, child, info)
    super
    parent.instance_variable_set single_child_accessor, child
end
add_relation(rel) click to toggle source
# File lib/roby/relations/space.rb, line 367
def add_relation(rel)
    relations << rel
    Relations.add_relation(rel)
end
apply_on(klass) click to toggle source

This relation applies on klass. It mainly means that a relation defined on this Space will define the relation-access methods and include its support module (if any) in klass. Note that the {DirectedRelationSupport} module is automatically included in klass as well.

# File lib/roby/relations/space.rb, line 81
def apply_on(klass)
    klass.include DirectedRelationSupport
    klass.relation_spaces << self
    each_relation do |graph|
        klass.include graph::Extension
    end
    applied << klass

    while klass
        if klass.respond_to?(:all_relation_spaces)
            klass.all_relation_spaces << self
        end
        klass = if klass.respond_to?(:supermodel) then klass.supermodel
                end
    end
end
each_relation() { |rel| ... } click to toggle source

Yields the relations that are defined on this space

# File lib/roby/relations/space.rb, line 99
def each_relation
    return enum_for(__method__) if !block_given?
    relations.each do |rel|
        yield(rel)
    end
end
each_root_relation() { |rel| ... } click to toggle source

Yields the root relations that are defined on this space. A relation is a root relation when it has no parent relation (i.e. it is the subset of no other relations).

# File lib/roby/relations/space.rb, line 109
def each_root_relation
    return enum_for(__method__) if !block_given?
    relations.each do |rel|
        yield(rel) if !rel.parent
    end
end
instanciate(observer: nil) click to toggle source

Instanciate this space's relation graphs

It instanciates a graph per relation defined on self, and sets their subset/superset relationships accordingly

@param observer a graph observer object @return [Hash<Models<Graph>,Graph>]

# File lib/roby/relations/space.rb, line 62
def instanciate(observer: nil)
    graphs = self.class.new_relation_graph_mapping
    relations.each do |rel|
        g = rel.new(observer: observer)
        graphs[g] = graphs[rel] = g
    end
    relations.each do |rel|
        rel.subsets.each do |subset_rel|
            graphs[rel].superset_of(graphs[subset_rel])
        end
    end
    graphs
end
relation(relation_name, child_name: relation_name.to_s.snakecase, const_name: relation_name, parent_name: nil, graph: default_graph_class, single_child: false, distribute: true, dag: true, weak: false, strong: false, copy_on_replace: false, noinfo: false, subsets: Set.new, **submodel_options) click to toggle source

Defines a relation in this relation space. This defines a relation graph, and various iteration methods on the vertices. If a block is given, it defines a set of functions which should additionally be defined on the vertex objects.

The valid options are:

child_name

define a each_#{child_name} method to iterate on the vertex children. Uses the relation name by default (a Child relation would define a each_child method)

parent_name

define a each_#{parent_name} method to iterate on the parent vertices. If none is given, no method is defined.

subsets

a list of subgraphs. See Relations::Graph#superset_of [empty set by default]

noinfo

wether the relation embeds some additional information. If false, the child iterator method (each_#{child_name}) will yield (child, info) instead of only child [false by default]

graph

the relation graph class [Relations::Graph by default]

distribute

if true, the relation can be seen by remote peers [true by default]

single_child

if the relations accepts only one child per vertex. If this option is set, defines a #{child_name} method which returns the only child (or nil if there is no child at all) [false by default]

dag

if true, {CycleFoundError} will be raised if a new vertex would create a cycle in this relation [true by default]

weak

marks that this relation might be broken by the plan manager if needs be. This is currently only used in the garbage collection phase to decide in which order to GC the tasks. I.e. if a cycle is found, the weak relations will be broken to resolve it.

strong

marks that the tasks that are linked by this relation should not be torn apart. This is for instance used in the replacement operation, which will never “move” a relation from the original task to the replaced one.

For instance,

relation :Children

defines an instance of Relations::Graph which is a DAG, defining the following methods on its vertices:

each_children { |v, info| ... } => graph
find_children { |v, info| ... } => object or nil
add_children(v, info = nil) => graph
remove_children(v) => graph

and

relation :Children, child_name: :child

would define

each_child { |v, info| ... } => graph
find_child { |v, info| ... } => object or nil
add_child(v, info = nil) => graph
remove_child(v) => graph
  • the {DirectedRelationSupport} module gets included in the vertex classes at the construction of the Space instance. See apply_on.

  • the :noinfo option would then remove the 'info' parameter to the various blocks.

  • if :single_child is set to true, then an additional method is defined:

    child => object or nil
  • and finally if the following is used

    relation :Children, child_name: :child, parent_name: :parent
    

    then the following method is additionally defined

    each_parent { |v| ... }

Finally, if a block is given, it gets included in the target class (i.e. for a TaskStructure relation, Roby::Task)

# File lib/roby/relations/space.rb, line 189
            def relation(relation_name,
                            child_name:  relation_name.to_s.snakecase,
                            const_name:  relation_name,
                            parent_name: nil,
                            graph:       default_graph_class,
                            single_child: false,

                            distribute:  true,
                            dag:         true,
                            weak:        false,
                            strong:      false,
                            copy_on_replace: false,
                            noinfo:      false,
                            subsets:     Set.new,
                            **submodel_options)

                if block_given?
                    raise ArgumentError, "calling relation with a block is not supported anymore. Reopen #{const_name}::Extension after the relation call to add helper methods"
                elsif strong && weak
                    raise ArgumentError, "a relation cannot be both strong and weak"
                end

                # Check if this relation is already defined. If it is the case, reuse it.
                # This is needed mostly by the reloading code
                graph_class = define_or_reuse(const_name) do
                    klass = graph.new_submodel(
                        distribute: distribute, dag: dag, weak: weak, strong: strong,
                        copy_on_replace: copy_on_replace, noinfo: noinfo, subsets: subsets,
                        child_name: child_name, **submodel_options)
                    synthetized_methods = Module.new do
                        define_method("__r_#{relation_name}__") { self.relation_graphs[klass] }
                    end
                    extension = Module.new
                    class_extension = Module.new
                    klass.const_set("SynthetizedMethods", synthetized_methods)
                    klass.const_set("Extension", extension)
                    klass.const_set("ModelExtension", class_extension)
                    extension.const_set("ClassExtension", class_extension)
                    klass
                end
                subsets.each do |subset_rel|
                    graph_class.superset_of(subset_rel)
                end
                synthetized_methods = graph_class::SynthetizedMethods
                extension = graph_class::Extension
                applied.each do |klass|
                    klass.include synthetized_methods
                    klass.include extension
                end

                if parent_name
                    synthetized_methods.class_eval <<-EOD,  __FILE__, __LINE__ + 1
                    def each_#{parent_name}(&iterator)
                        return enum_for(__method__) if !iterator
                        self.each_parent_object(__r_#{relation_name}__, &iterator)
                    end
                    EOD
                end

                if noinfo
                    synthetized_methods.class_eval <<-EOD,  __FILE__, __LINE__ + 1
                    def each_#{child_name}(&iterator)
                        return enum_for(__method__) if !iterator
                        each_child_object(__r_#{relation_name}__, &iterator)
                    end
                    def find_#{child_name}(&block)
                        each_child_object(__r_#{relation_name}__).find(&block)
                    end
                    EOD
                else
                    synthetized_methods.class_eval <<-EOD,  __FILE__, __LINE__ + 1
                    def enum_#{child_name}
                        Roby.warn_deprecated "enum_#{child_name} is deprecated, use each_#{child_name} instead"
                        each_#{child_name}
                    end
                    def each_#{child_name}(with_info = true)
                        return enum_for(__method__, with_info) if !block_given?
                        if with_info
                            each_child_object(__r_#{relation_name}__) do |child|
                                yield(child, self[child, __r_#{relation_name}__])
                            end
                        else
                            each_child_object(__r_#{relation_name}__, &Proc.new)
                        end
                    end
                    def find_#{child_name}(with_info = true)
                        if with_info
                            each_child_object(__r_#{relation_name}__) do |child|
                                return child if yield(child, self[child, __r_#{relation_name}__])
                            end
                        else
                            each_child_object(__r_#{relation_name}__).find(&Proc.new)
                        end
                        nil
                    end
                    EOD
                end

                synthetized_methods.class_eval <<-EOD,  __FILE__, __LINE__ + 1
                def add_#{child_name}(to, info = nil)
                    add_child_object(to, __r_#{relation_name}__, info)
                    self
                end
                def remove_#{child_name}(to)
                    remove_child_object(to, __r_#{relation_name}__)
                    self
                end

                def adding_#{child_name}_parent(parent, info)
                end
                def added_#{child_name}_parent(parent, info)
                end
                def removing_#{child_name}_parent(parent)
                end
                def removed_#{child_name}_parent(parent)
                end
                def updating_#{child_name}_parent(parent, info)
                end
                def updated_#{child_name}_parent(parent, info)
                end

                def adding_#{child_name}(child, info)
                end
                def added_#{child_name}(child, info)
                end
                def removing_#{child_name}(child)
                end
                def removed_#{child_name}(child)
                end
                def updating_#{child_name}(child, info)
                end
                def updated_#{child_name}(child, info)
                end
                EOD

                if single_child
                    synthetized_methods.class_eval do
                        define_method child_name do
                            if task = instance_variable_get("@#{child_name}")
                                plan[task]
                            end
                        end
                    end
                    graph_class.class_eval do
                        attr_reader :single_child_accessor

                        def add_edge(parent, child, info)
                            super
                            parent.instance_variable_set single_child_accessor, child
                        end

                        def update_single_child_accessor(object, expected_object)
                            current_object = object.instance_variable_get single_child_accessor
                            if current_object == expected_object
                                object.instance_variable_set single_child_accessor,
                                    each_out_neighbour(object).first
                            end
                        end

                        def remove_edge(parent, child)
                            super
                            update_single_child_accessor(parent, child)
                        end

                        def remove_vertex(object)
                            parents = in_neighbours(object)
                            super
                            parents.each do |parent|
                                update_single_child_accessor(parent, object)
                            end
                        end
                    end
                end

                add_relation(graph_class)
                graph_class
            end
remove_edge(parent, child) click to toggle source
Calls superclass method
# File lib/roby/relations/space.rb, line 348
def remove_edge(parent, child)
    super
    update_single_child_accessor(parent, child)
end
remove_relation(rel) click to toggle source

Remove rel from the set of relations managed in this space

# File lib/roby/relations/space.rb, line 373
def remove_relation(rel)
    relations.delete(rel)
    Relations.remove_relation(rel)
end
remove_vertex(object) click to toggle source
Calls superclass method
# File lib/roby/relations/space.rb, line 353
def remove_vertex(object)
    parents = in_neighbours(object)
    super
    parents.each do |parent|
        update_single_child_accessor(parent, object)
    end
end
update_single_child_accessor(object, expected_object) click to toggle source
# File lib/roby/relations/space.rb, line 340
def update_single_child_accessor(object, expected_object)
    current_object = object.instance_variable_get single_child_accessor
    if current_object == expected_object
        object.instance_variable_set single_child_accessor,
            each_out_neighbour(object).first
    end
end