Class: Roby::PlanObject
- Inherits:
-
DistributedObject
- Object
- DistributedObject
- Roby::PlanObject
- 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
Defined Under Namespace
Classes: InstanceHandler
Instance Attribute Summary collapse
-
#addition_time ⇒ Object
The time at which this plan object has been added into its first plan.
-
#executable ⇒ 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.
-
#execution_engine ⇒ Object
readonly
The underlying execution engine if #plan is executable.
-
#finalization_handlers ⇒ Array<InstanceHandler>
readonly
Set of finalization handlers defined on this task instance.
-
#finalization_time ⇒ Object
The time at which this plan object has been finalized (i.e. removed from plan), or nil if it has not been (yet).
-
#model ⇒ Object
readonly
This object’s model.
-
#plan ⇒ Object
The plan this object belongs to.
-
#promise_executor ⇒ Object
readonly
A thread pool that ensures that any work queued using #promise is serialized.
-
#removed_at ⇒ Object
The place where this object has been removed from its plan.
Attributes included from Transaction::Proxying::Cache
#transaction_forwarder_module, #transaction_proxy_module
Attributes included from Relations::DirectedRelationSupport
Attributes inherited from DistributedObject
Instance Method Summary collapse
-
#add_child_object(child, type, info = nil) ⇒ Object
Synchronizes the plan of this object from the one of its peer.
-
#apply_relation_changes(object, changes) ⇒ Object
Transfers a set of relations from this plan object to
object
. -
#as_plan ⇒ Object
Used in plan management as a way to extract a plan object from any object.
-
#can_finalize? ⇒ Boolean
Whether this task can be finalized.
-
#commit_transaction ⇒ Object
Method called to apply modifications needed to commit this object into the underlying plan.
-
#concrete_model ⇒ Object
The non-specialized model for self.
- #connection_space ⇒ Object
-
#each_finalization_handler {|block| ... } ⇒ void
Enumerates the finalization handlers that should be applied in finalized!.
- #each_in_neighbour_merged(relation, intrusive: nil, &block) ⇒ Object
- #each_out_neighbour_merged(relation, intrusive: nil, &block) ⇒ Object
-
#each_plan_child ⇒ Object
Iterates on all the children of this root object.
-
#engine ⇒ Object
deprecated
Deprecated.
use #execution_engine instead
-
#executable? ⇒ Boolean
If this object is executable.
-
#finalized!(timestamp = nil) ⇒ void
Called when a particular object has been removed from its plan.
-
#finalized? ⇒ Boolean
True if this object has been included in a plan, but has been removed from it since.
-
#forget_peer(peer) ⇒ Object
Called when all links to
peer
should be removed. -
#fullfills?(models) ⇒ Boolean
True if this object provides all the given models.
-
#garbage! ⇒ Object
Mark this task as garbage.
-
#garbage? ⇒ Object
Whether this task has been marked as garbage by the garbage collection process.
-
#initialize(plan: TemplatePlan.new) ⇒ PlanObject
constructor
A new instance of PlanObject.
- #initialize_copy(other) ⇒ Object
-
#initialize_replacement(object) ⇒ Object
Called by #replace_by and #replace_subplan_by to do object-specific initialization of
object
whenobject
is used to replaceself
in a plan. -
#merged_relations(enumerator, intrusive, &block) ⇒ 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.
-
#promise(description: "#{self}.promise", executor: promise_executor, &block) ⇒ Promise
Create a promise that is serialized with all promises created for this object.
-
#read_write? ⇒ Boolean
True if this object can be modified by the local plan manager.
-
#real_object ⇒ Object
If
self
is a transaction proxy, returns the underlying plan object, regardless of how many transactions there is on the stack. -
#remotely_useful? ⇒ Boolean
True if this object is useful for one of our peers.
-
#replace_by(object) ⇒ Object
Replaces
self
byobject
in all graphsself
is part of. -
#replace_subplan_by(object) ⇒ Object
Replaces, in the plan, the subplan generated by this plan object by the one generated by
object
. -
#root_object ⇒ Object
Return the root plan object for this object.
-
#root_object? ⇒ Boolean
True if this object is a root object in the plan.
-
#subscribed? ⇒ Boolean
True if we are explicitely subscribed to this object.
-
#transaction_proxy? ⇒ Boolean
True if this object is a transaction proxy, false otherwise.
-
#transaction_stack ⇒ Object
Returns the stack of transactions/plans this object is part of, starting with self.plan.
-
#update_on?(peer) ⇒ Boolean
True if we should send updates about this object to
peer
. -
#updated_by?(peer) ⇒ Boolean
True if we receive updates for this object from
peer
. -
#when_finalized(options = {}) {|task| ... } ⇒ void
Called when the task gets finalized, i.e.
Methods included from Models::PlanObject
child_plan_object, finalization_handler, match
Methods included from DRoby::Identifiable
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.
123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'lib/roby/plan_object.rb', line 123 def initialize(plan: TemplatePlan.new) super() @plan = plan self.plan = @plan @removed_at = nil @executable = nil @garbage = false @finalization_handlers = [] @model = self.class end |
Instance Attribute Details
#addition_time ⇒ Object
The time at which this plan object has been added into its first plan
630 631 632 |
# File 'lib/roby/plan_object.rb', line 630 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
355 356 357 |
# File 'lib/roby/plan_object.rb', line 355 def executable=(value) @executable = value end |
#execution_engine ⇒ Object (readonly)
The underlying execution engine if #plan is executable
23 24 25 |
# File 'lib/roby/plan_object.rb', line 23 def execution_engine @execution_engine end |
#finalization_handlers ⇒ Array<InstanceHandler> (readonly)
Returns set of finalization handlers defined on this task instance.
563 564 565 |
# File 'lib/roby/plan_object.rb', line 563 def finalization_handlers @finalization_handlers end |
#finalization_time ⇒ Object
The time at which this plan object has been finalized (i.e. removed from plan), or nil if it has not been (yet)
634 635 636 |
# File 'lib/roby/plan_object.rb', line 634 def finalization_time @finalization_time end |
#model ⇒ Object (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
13 14 15 |
# File 'lib/roby/plan_object.rb', line 13 def model @model end |
#plan ⇒ Object
The plan this object belongs to
146 147 148 |
# File 'lib/roby/plan_object.rb', line 146 def plan @plan end |
#promise_executor ⇒ Object (readonly)
A thread pool that ensures that any work queued using #promise is serialized
27 28 29 |
# File 'lib/roby/plan_object.rb', line 27 def promise_executor @promise_executor end |
#removed_at ⇒ Object
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.
161 162 163 |
# File 'lib/roby/plan_object.rb', line 161 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
460 461 462 463 464 465 466 467 468 469 470 471 |
# File 'lib/roby/plan_object.rb', line 460 def add_child_object(child, type, info = nil) # :nodoc: if child.plan != plan root_object.synchronize_plan(child.root_object) unless 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.
497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 |
# File 'lib/roby/plan_object.rb', line 497 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 # See comment above about keeping the two loops separate changes.each_slice(3) do |rel, parents, children| # rubocop:disable Style/CombinableLoops 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 ⇒ Object
Used in plan management as a way to extract a plan object from any object
210 211 212 |
# File 'lib/roby/plan_object.rb', line 210 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
41 42 43 |
# File 'lib/roby/plan_object.rb', line 41 def can_finalize? true end |
#commit_transaction ⇒ Object
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
405 |
# File 'lib/roby/plan_object.rb', line 405 def commit_transaction; end |
#concrete_model ⇒ Object
The non-specialized model for self
It is always self.class
18 19 20 |
# File 'lib/roby/plan_object.rb', line 18 def concrete_model self.class end |
#connection_space ⇒ Object
29 30 31 |
# File 'lib/roby/plan_object.rb', line 29 def connection_space plan&.connection_space end |
#each_finalization_handler {|block| ... } ⇒ void
This method returns an undefined value.
Enumerates the finalization handlers that should be applied in finalized!
570 571 572 573 574 575 576 577 |
# File 'lib/roby/plan_object.rb', line 570 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
237 238 239 240 241 242 243 244 245 |
# File 'lib/roby/plan_object.rb', line 237 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
247 248 249 250 251 252 253 254 255 |
# File 'lib/roby/plan_object.rb', line 247 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 ⇒ Object
Iterates on all the children of this root object
484 485 486 |
# File 'lib/roby/plan_object.rb', line 484 def each_plan_child self end |
#engine ⇒ Object
use #execution_engine instead
149 150 151 152 |
# File 'lib/roby/plan_object.rb', line 149 def engine Roby.warn_deprecated "PlanObject#engine is deprecated, use #execution_engine instead" execution_engine end |
#executable? ⇒ Boolean
If this object is executable
358 359 360 |
# File 'lib/roby/plan_object.rb', line 358 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.
589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 |
# File 'lib/roby/plan_object.rb', line 589 def finalized!( = 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 = || 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
165 166 167 |
# File 'lib/roby/plan_object.rb', line 165 def finalized? !!removed_at end |
#forget_peer(peer) ⇒ Object
Called when all links to peer
should be removed.
448 449 450 451 452 453 454 455 456 457 |
# File 'lib/roby/plan_object.rb', line 448 def forget_peer(peer) unless 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.
637 638 639 640 641 |
# File 'lib/roby/plan_object.rb', line 637 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
378 379 380 |
# File 'lib/roby/plan_object.rb', line 378 def garbage! @garbage = true end |
#garbage? ⇒ Object
Whether this task has been marked as garbage by the garbage collection process
368 |
# File 'lib/roby/plan_object.rb', line 368 attr_predicate :garbage? |
#initialize_copy(other) ⇒ Object
136 137 138 139 140 141 142 143 |
# File 'lib/roby/plan_object.rb', line 136 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
542 543 544 545 546 547 548 549 |
# File 'lib/roby/plan_object.rb', line 542 def initialize_replacement(object) finalization_handlers.each do |handler| if handler.copy_on_replace? object ||= yield object.when_finalized(handler., &handler.block) end end end |
#merged_relations(enumerator, intrusive, &block) ⇒ 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
.
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 |
# File 'lib/roby/plan_object.rb', line 297 def merged_relations(enumerator, intrusive, &block) return enum_for(__method__, enumerator, intrusive) unless block_given? plan_chain = self.transaction_stack object = self.real_object enumerator = enumerator.to_proc pending = [] 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 = [] enumerator.call(object) do || next if next_plan[, create: false] if !intrusive yield(object, ) else new_objects << 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, &block) for in pending yield(self.plan[]) end else enumerator.call(self) do || yield(self, ) 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
204 205 206 |
# File 'lib/roby/plan_object.rb', line 204 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
552 553 554 555 556 557 558 |
# File 'lib/roby/plan_object.rb', line 552 def read_write? if self_owned? true elsif plan.self_owned? owners.all? { |p| plan.owned_by?(p) } end end |
#real_object ⇒ Object
If self
is a transaction proxy, returns the underlying plan object, regardless of how many transactions there is on the stack. Otherwise, return self.
217 218 219 220 221 222 223 |
# File 'lib/roby/plan_object.rb', line 217 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
420 421 422 |
# File 'lib/roby/plan_object.rb', line 420 def remotely_useful? plan&.remotely_useful? || super end |
#replace_by(object) ⇒ Object
Replaces self
by object
in all graphs self
is part of.
533 534 535 |
# File 'lib/roby/plan_object.rb', line 533 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.
528 529 530 |
# File 'lib/roby/plan_object.rb', line 528 def replace_subplan_by(object) raise NotImplementedError, "#{self.class} did not reimplement #replace_subplan_by" end |
#root_object ⇒ Object
Return the root plan object for this object.
474 475 476 |
# File 'lib/roby/plan_object.rb', line 474 def root_object self end |
#root_object? ⇒ Boolean
True if this object is a root object in the plan.
479 480 481 |
# File 'lib/roby/plan_object.rb', line 479 def root_object? root_object == self end |
#subscribed? ⇒ Boolean
True if we are explicitely subscribed to this object
383 384 385 386 387 388 389 390 391 |
# File 'lib/roby/plan_object.rb', line 383 def subscribed? if root_object? plan&.subscribed? || (!self_owned? && owners.any?(&:subscribed_plan?)) || super else root_object.subscribed? end end |
#transaction_proxy? ⇒ Boolean
True if this object is a transaction proxy, false otherwise
155 156 157 |
# File 'lib/roby/plan_object.rb', line 155 def transaction_proxy? false end |
#transaction_stack ⇒ Object
Returns the stack of transactions/plans this object is part of, starting with self.plan.
227 228 229 230 231 232 233 234 235 |
# File 'lib/roby/plan_object.rb', line 227 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
410 411 412 |
# File 'lib/roby/plan_object.rb', line 410 def update_on?(peer) plan&.update_on?(peer) || super end |
#updated_by?(peer) ⇒ Boolean
True if we receive updates for this object from peer
415 416 417 |
# File 'lib/roby/plan_object.rb', line 415 def updated_by?(peer) plan&.updated_by?(peer) || super end |
#when_finalized(options = {}) {|task| ... } ⇒ void
This method returns an undefined value.
Called when the task gets finalized, i.e. removed from the main plan
619 620 621 622 623 |
# File 'lib/roby/plan_object.rb', line 619 def when_finalized( = {}, &block) = InstanceHandler. check_arity(block, 1) finalization_handlers << InstanceHandler.new(block, ([:on_replace] == :copy)) end |