Class: Roby::PlanObject

Inherits:
DistributedObject show all
Extended by:
Models::PlanObject
Includes:
DRoby::Identifiable, GUI::GraphvizPlanObject, GUI::RelationsCanvasPlanObject, Relations::DirectedRelationSupport
Defined in:
lib/roby/plan_object.rb,
lib/roby.rb,
lib/roby/droby/enable.rb

Overview

Base class for all objects which are included in a plan.

Direct Known Subclasses

EventGenerator, Task

Defined Under Namespace

Classes: InstanceHandler

Instance Attribute Summary collapse

Attributes included from Transaction::Proxying::Cache

#transaction_forwarder_module, #transaction_proxy_module

Attributes included from Relations::DirectedRelationSupport

#relation_graphs

Attributes inherited from DistributedObject

#local_owner_id, #owners

Instance Method Summary collapse

Methods included from Models::PlanObject

child_plan_object, finalization_handler, match

Methods included from DRoby::Identifiable

#droby_id

Methods included from GUI::RelationsCanvasPlanObject

#display, #display_create, #display_events, #display_name, #display_parent

Methods included from GUI::GraphvizPlanObject

#apply_layout, #dot_label, #to_dot

Methods included from Relations::DirectedRelationSupport

#[], #[]=, #add_parent_object, #child_object?, #child_objects, #clear_vertex, #each_child_object, #each_in_neighbour, #each_out_neighbour, #each_parent_object, #each_relation, #each_relation_graph, #each_relation_sorted, #each_root_relation_graph, #enum_child_objects, #enum_parent_objects, #enum_relations, #leaf?, #parent_object?, #parent_objects, #related_object?, #related_objects, #relation_graph_for, #relations, #remove_child_object, #remove_children, #remove_parent_object, #remove_parents, #remove_relations, #root?, #sorted_relations

Methods inherited from DistributedObject

#add_owner, #clear_owners, #owned_by?, #remove_owner

Constructor Details

#initialize(plan: TemplatePlan.new) ⇒ PlanObject

Returns a new instance of PlanObject.



118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/roby/plan_object.rb', line 118

def initialize(plan: TemplatePlan.new)
    super()

    @plan = plan
    self.plan = @plan

    @removed_at = nil
    @executable = nil
    @garbage = false
    @finalization_handlers = Array.new
    @model = self.class
end

Instance Attribute Details

#addition_timeObject

The time at which this plan object has been added into its first plan



597
598
599
# File 'lib/roby/plan_object.rb', line 597

def addition_time
  @addition_time
end

#executable=(value) ⇒ Object (writeonly)

A three-state flag with the following values:

nil

the object is executable if its plan is

true

the object is executable

false

the object is not executable



338
339
340
# File 'lib/roby/plan_object.rb', line 338

def executable=(value)
  @executable = value
end

#execution_engineObject (readonly)

The underlying execution engine if #plan is executable



19
20
21
# File 'lib/roby/plan_object.rb', line 19

def execution_engine
  @execution_engine
end

#finalization_handlersArray<InstanceHandler> (readonly)

Returns set of finalization handlers defined on this task instance.

Returns:

  • (Array<InstanceHandler>)

    set of finalization handlers defined on this task instance

See Also:



530
531
532
# File 'lib/roby/plan_object.rb', line 530

def finalization_handlers
  @finalization_handlers
end

#finalization_timeObject

The time at which this plan object has been finalized (i.e. removed from plan), or nil if it has not been (yet)



601
602
603
# File 'lib/roby/plan_object.rb', line 601

def finalization_time
  @finalization_time
end

#modelObject (readonly)

This object’s model

This is usually self.class, unless #specialize has been called in which case it is this object’s singleton class



11
12
13
# File 'lib/roby/plan_object.rb', line 11

def model
  @model
end

#planObject

The plan this object belongs to



141
142
143
# File 'lib/roby/plan_object.rb', line 141

def plan
  @plan
end

#promise_executorObject (readonly)

A thread pool that ensures that any work queued using #promise is serialized



23
24
25
# File 'lib/roby/plan_object.rb', line 23

def promise_executor
  @promise_executor
end

#removed_atObject

The place where this object has been removed from its plan. Once an object is removed from its plan, it cannot be added back again.



154
155
156
# File 'lib/roby/plan_object.rb', line 154

def removed_at
  @removed_at
end

Instance Method Details

#add_child_object(child, type, info = nil) ⇒ Object

Synchronizes the plan of this object from the one of its peer



436
437
438
439
440
441
442
443
444
445
446
447
# File 'lib/roby/plan_object.rb', line 436

def add_child_object(child, type, info = nil) # :nodoc:
    if child.plan != plan
        root_object.synchronize_plan(child.root_object)
        if !type.kind_of?(Class)
            # If given a graph, we need to re-resolve it as #plan might
            # have changed
            type = relation_graphs.fetch(type.class)
        end
    end

    super
end

#apply_relation_changes(object, changes) ⇒ Object

Transfers a set of relations from this plan object to object. changes is formatted as a sequence of relation, parents, children slices, where parents and children are sets of objects.

For each of these slices, the method removes the parent->self and self->child edges in the given relation, and then adds the corresponding parent->object and object->child edges.



465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
# File 'lib/roby/plan_object.rb', line 465

def apply_relation_changes(object, changes)
    # The operation is done in two parts to avoid problems with
    # creating cycles in the graph: first we remove the old edges, then
    # we add the new ones.
    changes.each_slice(3) do |rel, parents, children|
        next if rel.copy_on_replace?

        parents.each_slice(2) do |parent, info|
            parent.remove_child_object(self, rel)
        end
        children.each_slice(2) do |child, info|
            remove_child_object(child, rel)
        end
    end

    changes.each_slice(3) do |rel, parents, children|
        parents.each_slice(2) do |parent, info|
            parent.add_child_object(object, rel, info)
        end
        children.each_slice(2) do |child, info|
            object.add_child_object(child, rel, info)
        end
    end
end

#as_planObject

Used in plan management as a way to extract a plan object from any object



197
# File 'lib/roby/plan_object.rb', line 197

def as_plan; self end

#can_finalize?Boolean

Whether this task can be finalized

Unlike plan-level structure, a task that is marked as keepalive will be processed by garbage collection (e.g. stopping it). However, it will not be finalized (removed from the plan). The garbage collection will clear its relations instead of finalizing it

The default returns true

Returns:

  • (Boolean)


39
40
41
# File 'lib/roby/plan_object.rb', line 39

def can_finalize?
    true
end

#commit_transactionObject

Method called to apply modifications needed to commit this object into the underlying plan

For instance, Task will make sure that argument objects that are transaction proxies are unwrapped

Note that the method should only deal with modifications that are internal to the task itself. Modifications of the relation graphs are handled by Transaction itself in Transaction#apply_modifications_to_plan

The default implementation does nothing



388
389
# File 'lib/roby/plan_object.rb', line 388

def commit_transaction
end

#concrete_modelObject

The non-specialized model for self

It is always self.class



16
# File 'lib/roby/plan_object.rb', line 16

def concrete_model; self.class end

#connection_spaceObject



25
26
27
28
29
# File 'lib/roby/plan_object.rb', line 25

def connection_space
    if plan
        plan.connection_space
    end
end

#each_finalization_handler {|block| ... } ⇒ void

This method returns an undefined value.

Enumerates the finalization handlers that should be applied in finalized!

Yield Parameters:

  • block (#call)

    the handler’s block



537
538
539
540
541
542
543
544
# File 'lib/roby/plan_object.rb', line 537

def each_finalization_handler(&block)
    finalization_handlers.each do |handler|
        yield(handler.block)
    end
    self.class.each_finalization_handler do |model_handler|
        model_handler.bind(self).call(&block)
    end
end

#each_in_neighbour_merged(relation, intrusive: nil, &block) ⇒ Object



222
223
224
225
226
227
228
229
# File 'lib/roby/plan_object.rb', line 222

def each_in_neighbour_merged(relation, intrusive: nil, &block)
    if intrusive.nil?
        raise ArgumentError, "you must give a true or false to the intrusive flag"
    end
    merged_relations(
        proc { |o, &b| o.each_in_neighbour(relation, &b) },
        intrusive, &block)
end

#each_out_neighbour_merged(relation, intrusive: nil, &block) ⇒ Object



231
232
233
234
235
236
237
238
# File 'lib/roby/plan_object.rb', line 231

def each_out_neighbour_merged(relation, intrusive: nil, &block)
    if intrusive.nil?
        raise ArgumentError, "you must give a true or false to the intrusive flag"
    end
    merged_relations(
        proc { |o, &b| o.each_out_neighbour(relation, &b) },
        intrusive, &block)
end

#each_plan_childObject

Iterates on all the children of this root object



454
# File 'lib/roby/plan_object.rb', line 454

def each_plan_child; self end

#engineObject

Deprecated.

use #execution_engine instead



144
145
146
147
# File 'lib/roby/plan_object.rb', line 144

def engine
    Roby.warn_deprecated "PlanObject#engine is deprecated, use #execution_engine instead"
    execution_engine
end

#executable?Boolean

If this object is executable

Returns:

  • (Boolean)


341
342
343
# File 'lib/roby/plan_object.rb', line 341

def executable?
    @executable || (@executable.nil? && !garbage? && plan && plan.executable?)
end

#finalized!(timestamp = nil) ⇒ void

This method returns an undefined value.

Called when a particular object has been removed from its plan

If PlanObject.debug_finalization_place? is true (set with PlanObject.debug_finalization_place=, the backtrace in this call is stored in #removed_at. It is false by default, as it is pretty expensive.

Parameters:

  • timestamp (Time, nil) (defaults to: nil)

    the time at which it got finalized. It is stored in #finalization_time



556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
# File 'lib/roby/plan_object.rb', line 556

def finalized!(timestamp = nil)
    if self.plan.executable?
        # call finalization handlers
        each_finalization_handler do |handler|
            handler.call(self)
        end
    end

    if root_object?
        self.plan = nil
        if PlanObject.debug_finalization_place?
            self.removed_at = caller
        else
            self.removed_at = []
        end
        self.finalization_time = timestamp || Time.now
        self.finalized = true
    end
end

#finalized?Boolean

True if this object has been included in a plan, but has been removed from it since

Returns:

  • (Boolean)


158
# File 'lib/roby/plan_object.rb', line 158

def finalized?; !!removed_at end

#forget_peer(peer) ⇒ Object

Called when all links to peer should be removed.



424
425
426
427
428
429
430
431
432
433
# File 'lib/roby/plan_object.rb', line 424

def forget_peer(peer)
    if !root_object?
        raise ArgumentError, "#{self} is not root"
    end

    each_plan_child do |child|
        child.forget_peer(peer)
    end
    super
end

#fullfills?(models) ⇒ Boolean

Returns true if this object provides all the given models.

Returns:

  • (Boolean)

    true if this object provides all the given models



604
605
606
607
608
# File 'lib/roby/plan_object.rb', line 604

def fullfills?(models)
    Array(models).all? do |m|
        self.model <= m
    end
end

#garbage!Object

Mark this task as garbage

Garbage tasks cannot be involved in new relations, and cannot be executed

Once set, this flag cannot be unset

See Also:



361
362
363
# File 'lib/roby/plan_object.rb', line 361

def garbage!
    @garbage = true
end

#garbage?Object

Whether this task has been marked as garbage by the garbage collection process

See Also:



351
# File 'lib/roby/plan_object.rb', line 351

attr_predicate :garbage?

#initialize_copy(other) ⇒ Object



131
132
133
134
135
136
137
138
# File 'lib/roby/plan_object.rb', line 131

def initialize_copy(other)
    # Consider whether subclasses initialized the plan before calling us
    # ... in which case we handle it specially and propagate it
    super
    @plan = nil
    @relation_graphs = nil
    @finalization_handlers = other.finalization_handlers.dup
end

#initialize_replacement(object) ⇒ Object

Called by #replace_by and #replace_subplan_by to do object-specific initialization of object when object is used to replace self in a plan

The default implementation does nothing



509
510
511
512
513
514
515
516
# File 'lib/roby/plan_object.rb', line 509

def initialize_replacement(object)
    finalization_handlers.each do |handler|
        if handler.copy_on_replace?
            object ||= yield
            object.when_finalized(handler.as_options, &handler.block)
        end
    end
end

#merged_relations(enumerator, intrusive) ⇒ Object

call-seq:

merged_relation(enumeration_method, false[, arg1, arg2]) do |self_t, related_t|
end
merged_relation(enumeration_method, true[, arg1, arg2]) do |related_t|
end

It is assumed that enumeration_method is the name of a method on self that will yield an object related to self.

This method computes the same set of related objects, but does so while merging all the changes that underlying transactions may have applied. I.e. it is equivalent to calling enumeration_method on the plan that would be the result of the application of the whole transaction stack

If instrusive is false, the edges are yielded at the level they appear. I.e. both self and the related object are given, and

self_t, related_t

may be part of a parent plan of self.plan. I.e.

self_t is either self itself, or the task that self represents in a parent plan / transaction.

If instrusive is true, the related objects are recursively added to all transactions in the transaction stack, and are given at the end. I.e. only the related object is yield, and it is guaranteed to be included in self.plan.

For instance,

merged_relations(:each_child, false) do |parent, child|
   ...
end

yields the children of self according to the modifications that the transactions apply, but may do so in the transaction’s parent plans.

merged_relations(:each_child, true) do |child|
   ...
end

Will yield the same set of tasks, but included in self.plan.



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
# File 'lib/roby/plan_object.rb', line 280

def merged_relations(enumerator, intrusive)
    return enum_for(__method__, enumerator, intrusive) if !block_given?

    plan_chain = self.transaction_stack
    object     = self.real_object
    enumerator = enumerator.to_proc

    pending = Array.new
    while plan_chain.size > 1
        plan      = plan_chain.pop
        next_plan = plan_chain.last

        # Objects that are in +plan+ but not in +next_plan+ are
        # automatically added, as +next_plan+ is not able to change
        # them. Those that are included in +next_plan+ are handled
        # later.
        new_objects = Array.new
        enumerator.call(object) do |related_object|
            next if next_plan[related_object, create: false]

            if !intrusive
                yield(object, related_object)
            else
                new_objects   << related_object
            end
        end

        # Here, pending contains objects from the previous plan (i.e. in
        # plan.plan). Proxy them in +plan+.
        #
        # It is important to do that *after* we enumerated the relations
        # that exist in +plan+ (above), as it reduces the number of
        # relations at each level.
        pending.map! { |t| plan[t] }
        # And add the new objects that we just discovered
        pending.concat(new_objects)

        if next_plan
            object = next_plan[object]
        end
    end

    if intrusive
        enumerator.call(self, &Proc.new)
        for related_object in pending
            yield(self.plan[related_object])
        end
    else
        enumerator.call(self) do |related_object|
            yield(self, related_object)
        end
    end
end

#promise(description: "#{self}.promise", executor: promise_executor, &block) ⇒ Promise

Create a promise that is serialized with all promises created for this object

Parameters:

  • description (String) (defaults to: "#{self}.promise")

    a textual description of the promise’s role (used for debugging and timing)

Returns:



191
192
193
# File 'lib/roby/plan_object.rb', line 191

def promise(description: "#{self}.promise", executor: promise_executor, &block)
    execution_engine.promise(description: description, executor: executor, &block)
end

#read_write?Boolean

True if this object can be modified by the local plan manager

Returns:

  • (Boolean)


519
520
521
522
523
524
525
# File 'lib/roby/plan_object.rb', line 519

def read_write?
    if self_owned?
        true
    elsif plan.self_owned?
        owners.all? { |p| plan.owned_by?(p) }
    end
end

#real_objectObject

If self is a transaction proxy, returns the underlying plan object, regardless of how many transactions there is on the stack. Otherwise, return self.



202
203
204
205
206
207
208
# File 'lib/roby/plan_object.rb', line 202

def real_object
    result = self
    while result.respond_to?(:__getobj__)
        result = result.__getobj__
    end
    result
end

#remotely_useful?Boolean

True if this object is useful for one of our peers

Returns:

  • (Boolean)


398
# File 'lib/roby/plan_object.rb', line 398

def remotely_useful?; (plan && plan.remotely_useful?) || super end

#replace_by(object) ⇒ Object

Replaces self by object in all graphs self is part of.

Raises:

  • (NotImplementedError)


500
501
502
# File 'lib/roby/plan_object.rb', line 500

def replace_by(object)
    raise NotImplementedError, "#{self.class} did not reimplement #replace_by"
end

#replace_subplan_by(object) ⇒ Object

Replaces, in the plan, the subplan generated by this plan object by the one generated by object. In practice, it means that we transfer all parent edges whose target is self from the receiver to object. It calls the various add/remove hooks defined in Relations::DirectedRelationSupport.

Raises:

  • (NotImplementedError)


495
496
497
# File 'lib/roby/plan_object.rb', line 495

def replace_subplan_by(object)
    raise NotImplementedError, "#{self.class} did not reimplement #replace_subplan_by"
end

#root_objectObject

Return the root plan object for this object.



450
# File 'lib/roby/plan_object.rb', line 450

def root_object; self end

#root_object?Boolean

True if this object is a root object in the plan.

Returns:

  • (Boolean)


452
# File 'lib/roby/plan_object.rb', line 452

def root_object?; root_object == self end

#subscribed?Boolean

True if we are explicitely subscribed to this object

Returns:

  • (Boolean)


366
367
368
369
370
371
372
373
374
# File 'lib/roby/plan_object.rb', line 366

def subscribed?
    if root_object?
        (plan && plan.subscribed?) ||
            (!self_owned? && owners.any? { |peer| peer.subscribed_plan? }) ||
            super
    else
        root_object.subscribed?
    end
end

#transaction_proxy?Boolean

True if this object is a transaction proxy, false otherwise

Returns:

  • (Boolean)


150
# File 'lib/roby/plan_object.rb', line 150

def transaction_proxy?; false end

#transaction_stackObject

Returns the stack of transactions/plans this object is part of, starting with self.plan.



212
213
214
215
216
217
218
219
220
# File 'lib/roby/plan_object.rb', line 212

def transaction_stack
    result = [plan]
    obj    = self
    while obj.respond_to?(:__getobj__)
        obj = obj.__getobj__
        result << obj.plan
    end
    result
end

#update_on?(peer) ⇒ Boolean

True if we should send updates about this object to peer

Returns:

  • (Boolean)


394
# File 'lib/roby/plan_object.rb', line 394

def update_on?(peer); (plan && plan.update_on?(peer)) || super end

#updated_by?(peer) ⇒ Boolean

True if we receive updates for this object from peer

Returns:

  • (Boolean)


396
# File 'lib/roby/plan_object.rb', line 396

def updated_by?(peer); (plan && plan.updated_by?(peer)) || super end

#when_finalized(options = Hash.new) {|task| ... } ⇒ void

This method returns an undefined value.

Called when the task gets finalized, i.e. removed from the main plan

Parameters:

  • options (Hash) (defaults to: Hash.new)

    a customizable set of options

Options Hash (options):

  • :on_replace (:copy, :drop) — default: :drop

    behaviour to be followed when the task gets replaced. If :drop, the handler is not passed, if :copy it is installed on the new task as well as kept on the old one

Yield Parameters:

  • task (Roby::Task)

    the task that got finalized. It might be different than the one on which the handler got installed because of replacements



586
587
588
589
590
# File 'lib/roby/plan_object.rb', line 586

def when_finalized(options = Hash.new, &block)
    options = InstanceHandler.validate_options options
    check_arity(block, 1)
    finalization_handlers << InstanceHandler.new(block, (options[:on_replace] == :copy))
end