class Roby::PlanObject

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

Attributes

addition_time[RW]

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

executable[W]

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

execution_engine[R]

The underlying execution engine if {#plan} is executable

finalization_handlers[R]

@return [Array<InstanceHandler>] set of finalization handlers defined

on this task instance

@see when_finalized

finalization_time[RW]

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

model[R]

This object's model

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

plan[R]

The plan this object belongs to

promise_executor[R]

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

removed_at[RW]

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.

Public Class Methods

new(plan: TemplatePlan.new) click to toggle source
Calls superclass method
# 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

Public Instance Methods

apply_relation_changes(object, changes) click to toggle source

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.

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

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

# File lib/roby/plan_object.rb, line 197
def as_plan; self end
can_finalize?() click to toggle source

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

# File lib/roby/plan_object.rb, line 39
def can_finalize?
    true
end
commit_transaction() click to toggle source

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

# File lib/roby/plan_object.rb, line 388
def commit_transaction
end
concrete_model() click to toggle source

The non-specialized model for self

It is always self.class

# File lib/roby/plan_object.rb, line 16
def concrete_model; self.class end
connection_space() click to toggle source
# File lib/roby/plan_object.rb, line 25
def connection_space
    if plan
        plan.connection_space
    end
end
each_finalization_handler() { |block| ... } click to toggle source

Enumerates the finalization handlers that should be applied in finalized!

@yieldparam [#call] block the handler's block @return [void]

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

Iterates on all the children of this root object

# File lib/roby/plan_object.rb, line 454
def each_plan_child; self end
engine() click to toggle source

@deprecated use {#execution_engine} instead

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

If this object is executable

# File lib/roby/plan_object.rb, line 341
def executable?
    @executable || (@executable.nil? && !garbage? && plan && plan.executable?)
end
finalized!(timestamp = nil) click to toggle source

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 {PlanObject#removed_at}. It is false by default, as it is pretty expensive.

@param [Time,nil] timestamp the time at which it got finalized. It is stored in

{#finalization_time}

@return [void]

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

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

# File lib/roby/plan_object.rb, line 158
def finalized?; !!removed_at end
forget_peer(peer) click to toggle source

Called when all links to peer should be removed.

Calls superclass method
# 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) click to toggle source

@return [Boolean] true if this object provides all the given models

# File lib/roby/plan_object.rb, line 604
def fullfills?(models)
    Array(models).all? do |m|
        self.model <= m
    end
end
garbage!() click to toggle source

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 garbage?

# File lib/roby/plan_object.rb, line 361
def garbage!
    @garbage = true
end
initialize_copy(other) click to toggle source
Calls superclass method
# 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) { || ... } click to toggle source

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

# 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_relation(enumeration_method, false[, arg1, arg2]) do |self_t, related_t| click to toggle source
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.

# 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
plan=(new_plan) click to toggle source

Sets the new plan. Since it is forbidden to re-use a plan object that has been removed from a plan, it raises ArgumentError if it is the case

# File lib/roby/plan_object.rb, line 163
def plan=(new_plan)
    if removed_at
        if PlanObject.debug_finalization_place?
            raise ArgumentError, "#{self} has been removed from plan, cannot add it back\n" +
                "Removed at\n  #{removed_at.join("\n  ")}"
        else
            raise ArgumentError, "#{self} has been removed from plan, cannot add it back. Set PlanObject.debug_finalization_place to true to get the backtrace of where (in the code) the object got finalized"
        end
    end
    @addition_time = Time.now
    @plan = new_plan
    @local_owner_id = plan.droby_id
    if new_plan && new_plan.executable?
        @execution_engine = new_plan.execution_engine
        @promise_executor = Concurrent::SerializedExecutionDelegator.
            new(@execution_engine.thread_pool)
    else
        @execution_engine = nil
        @promise_executor = nil
    end
end
promise(description: " click to toggle source

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

@param [String] description a textual description of the promise's

role (used for debugging and timing)

@return [Promise]

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

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

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

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

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

True if this object is useful for one of our peers

Calls superclass method
# File lib/roby/plan_object.rb, line 398
def remotely_useful?; (plan && plan.remotely_useful?) || super end
replace_by(object) click to toggle source

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

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

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

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

Return the root plan object for this object.

# File lib/roby/plan_object.rb, line 450
def root_object; self end
root_object?() click to toggle source

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

# File lib/roby/plan_object.rb, line 452
def root_object?; root_object == self end
subscribed?() click to toggle source

True if we are explicitely subscribed to this object

Calls superclass method
# 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?() click to toggle source

True if this object is a transaction proxy, false otherwise

# File lib/roby/plan_object.rb, line 150
def transaction_proxy?; false end
transaction_stack() click to toggle source

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

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

True if we should send updates about this object to peer

Calls superclass method
# File lib/roby/plan_object.rb, line 394
def update_on?(peer); (plan && plan.update_on?(peer)) || super end
updated_by?(peer) click to toggle source

True if we receive updates for this object from peer

Calls superclass method
# File lib/roby/plan_object.rb, line 396
def updated_by?(peer); (plan && plan.updated_by?(peer)) || super end
when_finalized(options = Hash.new, &block) click to toggle source

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

@option options [:copy,:drop] :on_replace (: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

@yieldparam [Roby::Task] task the task that got finalized. It might be

different than the one on which the handler got installed because of
replacements

@return [void]

# 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