class Roby::PlanObject
Base class for all objects which are included in a plan.
Attributes
The time at which this plan object has been added into its first plan
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
The underlying execution engine if {#plan} is executable
@return [Array<InstanceHandler>] set of finalization handlers defined
on this task instance
@see when_finalized
The time at which this plan object has been finalized (i.e. removed from plan), or nil if it has not been (yet)
This object's model
This is usually self.class, unless {#specialize} has been called in which case it is this object's singleton class
The plan this object belongs to
A thread pool that ensures that any work queued using {#promise} is serialized
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
# 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
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
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
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
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
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
# File lib/roby/plan_object.rb, line 25 def connection_space if plan plan.connection_space end end
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
# 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
# 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
Iterates on all the children of this root object
# File lib/roby/plan_object.rb, line 454 def each_plan_child; self end
@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
If this object is executable
# File lib/roby/plan_object.rb, line 341 def executable? @executable || (@executable.nil? && !garbage? && plan && plan.executable?) end
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
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
Called when all links to peer
should be removed.
# 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
@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
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
# 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
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
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
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
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
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
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
True if this object is useful for one of our peers
# File lib/roby/plan_object.rb, line 398 def remotely_useful?; (plan && plan.remotely_useful?) || super end
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
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
Return the root plan object for this object.
# File lib/roby/plan_object.rb, line 450 def root_object; self end
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
True if we are explicitely subscribed to this object
# 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
True if this object is a transaction proxy, false otherwise
# File lib/roby/plan_object.rb, line 150 def transaction_proxy?; false end
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
True if we should send updates about this object to peer
# File lib/roby/plan_object.rb, line 394 def update_on?(peer); (plan && plan.update_on?(peer)) || super end
True if we receive updates for this object from peer
# File lib/roby/plan_object.rb, line 396 def updated_by?(peer); (plan && plan.updated_by?(peer)) || super end
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