Class: Roby::Relations::Space

Inherits:
Module show all
Defined in:
lib/roby/relations/space.rb

Overview

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

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Module

#action_library, #droby_dump, #droby_marshallable?, #each_fullfilled_model, #private_model?, #proxy_for

Methods included from Models::TaskServiceDefinitionDSL

#task_service

Constructor Details

#initializeSpace

:nodoc:



35
36
37
38
39
40
# File 'lib/roby/relations/space.rb', line 35

def initialize # :nodoc:
    @relations = []
    @applied   = []
    @default_graph_class = Relations::Graph
    super
end

Instance Attribute Details

#appliedObject (readonly)

The set of classes on which the relations have been applied



30
31
32
# File 'lib/roby/relations/space.rb', line 30

def applied
  @applied
end

#default_graph_classObject

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



33
34
35
# File 'lib/roby/relations/space.rb', line 33

def default_graph_class
  @default_graph_class
end

#relationsObject (readonly)

The set of relations included in this relation space



28
29
30
# File 'lib/roby/relations/space.rb', line 28

def relations
  @relations
end

Class Method Details

.new_relation_graph_mappingObject



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/roby/relations/space.rb', line 42

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) }
                                   .map { |o| o.name.to_s }.join(", ")
                raise ArgumentError,
                      "#{k} is not a known relation (known relations "\
                      "are #{known_relations})"
            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

Instance Method Details

#add_relation(rel) ⇒ Object



379
380
381
382
# File 'lib/roby/relations/space.rb', line 379

def add_relation(rel)
    relations << rel
    Relations.add_relation(rel)
end

#apply_on(klass) ⇒ Object

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.



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/roby/relations/space.rb', line 93

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(&block) ⇒ Object

Yields the relations that are defined on this space



111
112
113
114
115
# File 'lib/roby/relations/space.rb', line 111

def each_relation(&block)
    return enum_for(__method__) unless block_given?

    relations.each(&block)
end

#each_root_relationObject

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).



120
121
122
123
124
125
126
# File 'lib/roby/relations/space.rb', line 120

def each_root_relation
    return enum_for(__method__) unless block_given?

    relations.each do |rel|
        yield(rel) unless rel.parent
    end
end

#instanciate(observer: nil) ⇒ Hash<Models<Graph>,Graph>

Instanciate this space’s relation graphs

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

Parameters:

  • observer (defaults to: nil)

    a graph observer object

Returns:



74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/roby/relations/space.rb', line 74

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| # rubocop:disable Style/CombinableLoops
        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) ⇒ Object

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)



201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
# File 'lib/roby/relations/space.rb', line 201

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 <<-DEF, __FILE__, __LINE__ + 1
        def each_#{parent_name}(&iterator)
            return enum_for(__method__) if !iterator
            self.each_parent_object(__r_#{relation_name}__, &iterator)
        end
        DEF
    end

    if noinfo
        synthetized_methods.class_eval <<-DEF, __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
        DEF
    else
        synthetized_methods.class_eval <<-DEF, __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, &block)
            return enum_for(__method__, with_info) unless block
            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}__, &block)
            end
        end
        def find_#{child_name}(with_info = true, &block)
            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(&block)
            end
            nil
        end
        DEF
    end

    synthetized_methods.class_eval <<-DEF, __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
    DEF

    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_relation(rel) ⇒ Object

Remove rel from the set of relations managed in this space



385
386
387
388
# File 'lib/roby/relations/space.rb', line 385

def remove_relation(rel)
    relations.delete(rel)
    Relations.remove_relation(rel)
end