class Roby::Task
In a plan, Task
objects represent the system's activities.
Task
models¶ ↑
A task model is mainly described by:
a set of named arguments, which are required to parametrize the task instance. The argument list is described using Task.argument and arguments are either set at object creation by passing an argument hash to Task.new
, or by calling Task#argument explicitely.
a set of events, which are situations describing the task progression. The base Roby::Task
model defines the start
,success
,failed
and stop
events. Events can be defined on the models by using Task.event
:
class MyTask < Roby::Task event :intermediate_event end
defines a non-controllable event, i.e. an event which can be emitted, but cannot be triggered explicitely by the system. Controllable events are defined by associating a block of code with the event, this block being responsible for making the event emitted either in the future or just now. For instance,
class MyTask < Roby::Task event :intermediate_event do |context| emit :intermediate_event end event :other_event do |context| execution_engine.once { emit :other_event } end end
define two controllable event. In the first case, the event is immediately emitted, and in the second case it will be emitted at the beginning of the next execution cycle.
Task
relations¶ ↑
Task
relations are defined in the TaskStructure
Relations::Space
instance. See TaskStructure
documentation for the list of special methods defined by the various graphs, and the TaskStructure
namespace for the name and purpose of the various relation graphs themselves.
Executability¶ ↑
By default, a task is not executable, which means that no event command can be called and no event can be emitted. A task becomes executable either because Task#executable=
has explicitely been called or because it has been inserted in a Plan
object. Note that forcing executability with executable=
is only useful for testing. When the Roby
controller manages a real systems, the executability property enforces the constraint that a task cannot be executed outside of the plan supervision.
Finally, it is possible to describe abstract task models: tasks which do represent an action, but for which the means to perform that action are still unknown. This is done by calling Task.abstract in the task definition:
class AbstTask < Roby::Task abstract end
An instance of an abstract model cannot be executed, even if it is included in a plan.
Inheritance rules¶ ↑
On task models, a submodel can inherit from a parent model if the actions described by the parent model are also performed by the child model. For instance, a Goto(x, y)
model could be subclassed into a Goto::ByFoot(x, y)
model.
The following constraints apply when subclassing a task model:
-
a task subclass has at least the same events than the parent class
-
changes to event attributes are limited. The rules are:
-
a controlable event must remain controlable. Nonetheless, a non-controlable event can become a controlable one
-
a terminal event (i.e. a terminal event which ends the task execution) cannot become non-terminal. Nonetheless, a non-terminal event can become terminal.
-
@!macro InstanceHandlerOptions
@option options [:copy,:drop] :on_replace defines the behaviour when this object gets replaced in the plan. If :copy is used, the handler is added to the replacing task and is also kept in the original task. If :drop, it is not copied (but is kept).
Attributes
The task arguments
@return [TaskArguments]
List of EventGenerator
objects bound to this task
The internal data for this task
@see data= updated_data
updated_data_event
@api private
The set of instance-level execute blocks
@return [Array<InstanceHandler>]
The time at which the task failed to start
The event that caused this task to fail. This is equivalent to taking the first emitted element of
task.event(:failed).last.task_sources
It is only much more efficient
The reason for which this task failed.
It can either be an event or a LocalizedError
object.
If it is an event, it is the most specialized event whose emission has been forwarded to :failed
If it is a LocalizedError
object, it is the exception that caused the task failure.
The accumulated history of this task
This is the list of events that this task ever emitted, sorted by emission time (oldest first)
@return [Array<Event>]
@api private
The set of instance-level poll blocks
@return [Array<InstanceHandler>]
The most specialized event that caused this task to end
Public Class Methods
# File lib/roby/coordination/task_script.rb, line 251 def self.create_script(*task, &block) script_model = Coordination::TaskScript.new_submodel(root: self) script = script_model.new(*task) if block_given? script.parse(&block) end script end
# File lib/roby/state/task.rb, line 21 def self.goal if !@goal if superclass.respond_to?(:goal) supermodel = superclass.goal end @goal = GoalModel.new(self.state, supermodel) end @goal end
Create a new task object
@param [Plan] plan the plan this task should be added two. The default
is to add it to its own TemplatePlan object
@param [Hash<Symbol,Object>] arguments assignation to task arguments
Roby::PlanObject::new
# File lib/roby/task.rb, line 222 def initialize(plan: TemplatePlan.new, **arguments) @bound_events = Hash.new super(plan: plan) @model = self.class @abstract = @model.abstract? @failed_to_start = false @pending = true @started = false @running = false @starting = false @finished = false @finishing = false @success = nil @reusable = true @history = Array.new @coordination_objects = Array.new @arguments = TaskArguments.new(self) assign_arguments(**arguments) # Now assign default values for the arguments that have not yet been # set model.arguments.each do |argname| next if @arguments.has_key?(argname) has_default, default = model.default_argument(argname) if has_default assign_argument(argname, default) end end @poll_handlers = [] @execute_handlers = [] initialize_events plan.register_task(self) template = self.model.template mappings = Hash.new template.events_by_name.each do |name, template_event| mappings[template_event] = bound_events[name] end template.copy_relation_graphs_to(plan, mappings) apply_terminal_flags( template.terminal_events.map(&mappings.method(:[])), template.success_events.map(&mappings.method(:[])), template.failure_events.map(&mappings.method(:[]))) @terminal_flag_invalid = false if self.model.state_machine @state_machine = TaskStateMachine.new(self.model.state_machine) end end
Adds a script that is going to be executed for every instance of this task model
# File lib/roby/coordination/task_script.rb, line 262 def self.script(&block) s = create_script(&block) scripts << s s end
# File lib/roby/state/task.rb, line 3 def self.state if !@state if superclass.respond_to?(:state) supermodel = superclass.state end @state = StateModel.new(supermodel) end @state end
Public Instance Methods
Creates a sequence where self
will be started first, and task
is started if self
finished successfully. The returned value is an instance of Sequence.
Note that this operator always creates a new Sequence object, so
a + b + c + d
will create 3 Sequence instances. If more than two tasks should be organized in a sequence, one should instead use Sequence#<<:
Sequence.new << a << b << c << d
# File lib/roby/task.rb, line 1554 def +(task) # !!!! + is NOT commutative if task.null? self elsif self.null? task else Tasks::Sequence.new << self << task end end
@api private
Validates that both self and the child object are owned by the local instance
Roby::Relations::DirectedRelationSupport#add_child_object
# File lib/roby/task.rb, line 1247 def add_child_object(child, type, info) unless read_write? && child.read_write? raise OwnershipError, "cannot add a relation between tasks we don't own. #{self} by #{owners.to_a} and #{child} is owned by #{child.owners.to_a}" end super end
@api private
Declare that a coordination object is attached to this task
@param [Coordination::Base] object
# File lib/roby/task.rb, line 1617 def add_coordination_object(object) @coordination_objects.push(object) end
@api private
# File lib/roby/task.rb, line 1355 def apply_replacement_operations(edges) edges.each do |g, removed_parent, removed_child, added_parent, added_child| info = g.edge_info(removed_parent, removed_child) g.add_relation(plan[added_parent], plan[added_child], info) end edges.each do |g, removed_parent, removed_child, added_parent, added_child| if !g.copy_on_replace? g.remove_relation(plan[removed_parent], plan[removed_child]) end end end
# File lib/roby/task.rb, line 636 def apply_terminal_flags(terminal_events, success_events, failure_events) for ev in bound_events.each_value ev.terminal_flag = nil if terminal_events.include?(ev) if success_events.include?(ev) ev.terminal_flag = :success elsif failure_events.include?(ev) ev.terminal_flag = :failure else ev.terminal_flag = true end end end end
# File lib/roby/task.rb, line 1486 def as_plan self end
Returns an object that will allow to track this task's role in the plan regardless of replacements
The returning object will point to the replacing object when self is replaced by something. In effect, it points to the task's role in the plan instead of to the actual task itself.
@return [PlanService]
# File lib/roby/task.rb, line 1482 def as_service @service ||= (plan.find_plan_service(self) || PlanService.new(self)) end
@api private
Sets one of this task's arguments
# File lib/roby/task.rb, line 201 def assign_argument(key, value) key = key.to_sym if TaskArguments.delayed_argument?(value) @arguments[key] = value else if self.respond_to?("#{key}=") self.send("#{key}=", value) end if @arguments.writable?(key, value) # The accessor did not write the argument. That's alright @arguments[key] = value end end end
@api private
Helper to assign multiple argument values at once
It differs from calling assign_argument
in a loop in two ways:
-
it is common for subclasses to define a high-level argument that is, in the end, propagated to lower-level arguments. This method handles the fact that, when doing this, one will get parallel assignment of the high-level and low-level values during e.g. log replay which would fail in
assign_arguments
since arguments are single-assignation -
assignation is all-or-nothing
# File lib/roby/task.rb, line 175 def assign_arguments(**arguments) initial_arguments = @arguments initial_set_arguments = initial_arguments.assigned_arguments current_arguments = initial_set_arguments.dup # First assign normal values arguments.each do |key, value| @arguments = TaskArguments.new(self) @arguments.merge!(initial_set_arguments) assign_argument(key, value) current_arguments.merge!(@arguments) do |k, v1, v2| if v1 != v2 raise ArgumentError, "trying to override #{k}=#{v1} to #{v2}" end v1 end end initial_arguments.merge!(current_arguments) ensure @arguments = initial_arguments end
Tests if a task could be merged within self
Unlike a replacement, a merge implies that self is modified to match both its current role and the target's role. Roby
has no built-in merge logic (no merge method). This method is a helper for Roby
extensions that implement such a scheme, to check for attributes common to all tasks that would forbid a merge
# File lib/roby/task.rb, line 1187 def can_merge?(target) if defined?(super) && !super return end if finished? || target.finished? return false end if !model.can_merge?(target.model) return false end target.arguments.each_assigned_argument do |key, val| if arguments.set?(key) && arguments[key] != val return false end end true end
True if self can be used to replace target
# File lib/roby/task.rb, line 1176 def can_replace?(target) fullfills?(*target.fullfilled_model) end
Clear relations events of this task have with events outside the task
# File lib/roby/task.rb, line 574 def clear_events_external_relations(remove_strong: true) removed = false task_events = bound_events.values each_event do |event| for rel in event.sorted_relations graph = plan.event_relation_graph_for(rel) next if !remove_strong && graph.strong? to_remove = Array.new graph.each_in_neighbour(event) do |neighbour| if !task_events.include?(neighbour) to_remove << neighbour << event end end graph.each_out_neighbour(event) do |neighbour| if !task_events.include?(neighbour) to_remove << event << neighbour end end to_remove.each_slice(2) do |from, to| graph.remove_edge(from, to) end removed ||= !to_remove.empty? end end removed end
Remove all relations in which self
or its event are involved
@param [Boolean] remove_internal if true, remove in-task relations between
events
@param [Boolean] remove_strong if true, remove strong relations as well
Roby::Relations::DirectedRelationSupport#clear_relations
# File lib/roby/task.rb, line 607 def clear_relations(remove_internal: false, remove_strong: true) modified_plan = false if remove_internal each_event do |ev| if ev.clear_relations(remove_strong: remove_strong) modified_plan = true end end else modified_plan = clear_events_external_relations(remove_strong: remove_strong) end super(remove_strong: remove_strong) || modified_plan end
@api private
This method is called during the commit process to apply changes stored in a proxy
Roby::PlanObject#commit_transaction
# File lib/roby/task.rb, line 1259 def commit_transaction super arguments.dup.each do |key, value| if value.respond_to?(:transaction_proxy?) && value.transaction_proxy? arguments.update!(key, value.__getobj__) end end end
Checks if task
is in the same execution state than self
Returns true if they are either both running or both pending
# File lib/roby/task.rb, line 1008 def compatible_state?(task) finished? || !(running? ^ task.running?) end
@api private
# File lib/roby/task.rb, line 1388 def compute_object_replacement_operation(object) edges = [] plan.each_task_relation_graph do |g| next if g.strong? g.each_in_neighbour(self) do |parent| if parent != object edges << [g, parent, self, parent, object] end end g.each_out_neighbour(self) do |child| if object != child edges << [g, self, child, object, child] end end end plan.each_event_relation_graph do |g| next if g.strong? each_event do |event| object_event = nil g.each_in_neighbour(event) do |parent| if !parent.respond_to?(:task) || (parent.task != self && parent.task != object) object_event ||= object.event(event.symbol) edges << [g, parent, event, parent, object_event] end end g.each_out_neighbour(event) do |child| if !child.respond_to?(:task) || (child.task != self && child.task != object) object_event ||= object.event(event.symbol) edges << [g, event, child, object_event, child] end end end end edges end
@api private
# File lib/roby/task.rb, line 1279 def compute_subplan_replacement_operation(object) edges, edges_candidates = [], [] subplan_tasks = Set[self, object] parent_tasks = Set.new plan.each_task_relation_graph do |g| next if g.strong? rel = g.class each_in_neighbour_merged(rel, intrusive: true) do |parent| parent_tasks << parent edges << [g, parent, self, parent, object] end object.each_in_neighbour_merged(rel, intrusive: true) do |parent| parent_tasks << parent end if g.weak? each_out_neighbour_merged(rel, intrusive: true) do |child| edges_candidates << [child, [g, self, child, object, child]] end else object.each_out_neighbour_merged(rel, intrusive: true) do |child| subplan_tasks << child end each_out_neighbour_merged(rel, intrusive: true) do |child| subplan_tasks << child end end end plan.each_event_relation_graph do |g| next if g.strong? rel = g.class model.each_event do |_, event| event = plan.each_object_in_transaction_stack(self). find { |_, o| o.find_event(event.symbol) }. last.event(event.symbol) object_event = plan.each_object_in_transaction_stack(object). find { |_, o| o.find_event(event.symbol) }. last.event(event.symbol) event.each_in_neighbour_merged(rel, intrusive: false) do |_, parent| if parent.respond_to?(:task) edges_candidates << [plan[parent.task], [g, parent, event, parent, object_event]] end end event.each_out_neighbour_merged(rel, intrusive: false) do |_, child| if child.respond_to?(:task) edges_candidates << [plan[child.task], [g, event, child, object_event, child]] end end end end edges_candidates.each do |reference_task, op| if subplan_tasks.include?(reference_task) next elsif parent_tasks.include?(reference_task) edges << op elsif plan.in_useful_subplan?(self, reference_task) || plan.in_useful_subplan?(object, reference_task) subplan_tasks << reference_task else edges << op end end edges = edges.map do |g, removed_parent, removed_child, added_parent, added_child| [g, plan[removed_parent], plan[removed_child], plan[added_parent], plan[added_child]] end edges end
# File lib/roby/task.rb, line 383 def create_fresh_copy model.new(arguments.dup) end
@api private
# File lib/roby/task.rb, line 1594 def create_transaction_proxy(transaction) transaction.create_and_register_proxy_task(self) end
Retrieve the current state of the task
Can be one of the core states: pending, failed_to_start, starting, started, running, finishing, succeeded or failed
If the task has a state machine defined with {TaskStateHelper#refine_running_state}, the state machine's current state will be returned in place of :running
@return [Symbol]
# File lib/roby/task.rb, line 287 def current_state # Started and not finished if running? if respond_to?("state_machine") # state_machine.status # => String # state_machine.status_name # => Symbol return state_machine.status_name else return :running end end # True, when task has never been started if pending? return :pending elsif failed_to_start? return :failed_to_start elsif starting? return :starting # True, when terminal event is pending elsif finishing? return :finishing # Terminated with success or failure elsif success? return :succeeded elsif failed? return :failed end end
Test
if that current state corresponds to the provided state (symbol)
@param [Symbol] state @return [Boolean]
# File lib/roby/task.rb, line 321 def current_state?(state) return state == current_state.to_sym end
Sets the internal data value for this task. This calls the {#updated_data} hook, and emits {#updated_data_event} if the task is running.
# File lib/roby/task.rb, line 994 def data=(value) @data = value updated_data emit :updated_data if running? end
Call to force the value of {#reusable?} to false @return [void]
# File lib/roby/task.rb, line 522 def do_not_reuse @reusable = false end
Enumerate the coordination objects currently attached to this task
@yieldparam [Coordination::Base] object
# File lib/roby/task.rb, line 1608 def each_coordination_object(&block) @coordination_objects.each(&block) end
Iterates on all the events defined for this task
@param [Boolean] only_wrapped For consistency with transaction
proxies. Should not be used in user code.
@yield [generator] @yieldparam [TaskEventGenerator] generator the generators that are
tied to this task
@return self
# File lib/roby/task.rb, line 856 def each_event(only_wrapped = true) return enum_for(__method__, only_wrapped) if !block_given? for ev in bound_events.each_value yield(ev) end self end
Lists all exception handlers attached to this task
# File lib/roby/task.rb, line 1239 def each_exception_handler(&iterator) model.each_exception_handler(&iterator) end
@deprecated use {TaskEventGenerator#emit} instead (e.g. task.start_event.emit)
# File lib/roby/task.rb, line 786 def emit(event_model, *context) Roby.warn_deprecated "Roby::Task#emit(event_name) is deprecated, use EventGenerator#emit (e.g. task.start_event.emit or task.event(:start).emit)" event(event_model).emit(*context) self end
Returns when this task has finished
# File lib/roby/task.rb, line 370 def end_time if ev = stop_event.last ev.time end end
@api private
Helper for {#execute} and {#poll} that ensures that the {#do_poll} is called by the execution engine
# File lib/roby/task.rb, line 1069 def ensure_poll_handler_called if !transaction_proxy? && running? @poll_handler_id ||= execution_engine.add_propagation_handler(description: "poll block for #{self}", type: :external_events, &method(:do_poll)) end end
Returns the event generator by its name or model
@param [Symbol] name the event name @return [TaskEventGenerator,nil] @raise [ArgumentError] if the event does not exist
# File lib/roby/task.rb, line 775 def event(event_model) if event = find_event(event_model) event else raise ArgumentError, "cannot find #{event_model} in the set of bound events in #{self}. Known events are #{bound_events}." end end
(see Models::Task#event_model)
# File lib/roby/task.rb, line 877 def event_model(model) self.model.event_model(model) end
Set
the executable flag. executable cannot be set to false
if the task is running, and cannot be set to true on a finished task.
# File lib/roby/task.rb, line 456 def executable=(flag) return if flag == @executable return unless self_owned? if flag && !pending? raise ModelViolation, "cannot set the executable flag of #{self} since it is not pending" elsif !flag && running? raise ModelViolation, "cannot unset the executable flag of #{self} since it is running" end super end
True if this task is executable. A task is not executable if it is abstract or partially instanciated.
@see abstract? partially_instanciated?
Roby::PlanObject#executable?
# File lib/roby/task.rb, line 444 def executable? if @executable == true true elsif @executable.nil? (!abstract? && !partially_instanciated? && super) end end
Add a block that is going to be executed once, either at the next cycle if the task is already running, or when the task is started
@macro InstanceHandlerOptions @return [void]
# File lib/roby/task.rb, line 1031 def execute(options = Hash.new, &block) default_on_replace = if abstract? then :copy else :drop end options = InstanceHandler.validate_options(options, on_replace: default_on_replace) check_arity(block, 1) @execute_handlers << InstanceHandler.new(block, (options[:on_replace] == :copy)) ensure_poll_handler_called end
# File lib/roby/task.rb, line 565 def failed_to_start!(reason, time = Time.now) mark_failed_to_start(reason, time) each_event do |ev| ev.unreachable!(reason) end execution_engine.log(:task_failed_to_start, self, reason) end
# File lib/roby/task.rb, line 548 def failed_to_start?; @failed_to_start end
Returns the event generator by its name
@param [Symbol] name the event name @return [TaskEventGenerator,nil]
# File lib/roby/task.rb, line 765 def find_event(name) bound_events[name] || bound_events[event_model(name).symbol] end
Hook called by TaskEventGenerator#fired
when one of this task's events has been fired.
# File lib/roby/task.rb, line 694 def fired_event(event) history << event update_task_status(event) end
“Simply” mark this task as terminated. This is meant to be used on quarantined tasks in tests.
Do not use this unless you really know what you are doing
# File lib/roby/task.rb, line 1212 def forcefully_terminate update_task_status(event(:stop).new([])) end
@deprecated use {TaskEventGenerator#forward_to} instead (e.g. task.start_event.forward_to other_task.stop_event)
# File lib/roby/task.rb, line 824 def forward_to(event_model, to, *to_task_events) Roby.warn_deprecated "Task#forward_to is deprecated, use EventGenerator#forward_to instead (e.g. #{event_model}_event.forward_to other_event)" generator = event(event_model) if Hash === to_task_events.last delay = to_task_events.pop end to_events = case when Task to_task_events.map { |ev| to.event(ev) } when EventGenerator then [to] else raise ArgumentError, "expected Task or EventGenerator, got #{to}(#{to.class}: #{to.class.ancestors})" end to_events.each do |ev| generator.forward_to ev, delay end end
@api private
Evaluate delayed arguments, and replace in {#arguments} the ones that currently have a value
# File lib/roby/task.rb, line 119 def freeze_delayed_arguments if !arguments.static? result = Hash.new arguments.each do |key, value| if TaskArguments.delayed_argument?(value) catch(:no_value) do result[key] = value.evaluate_delayed_argument(self) end end end assign_arguments(**result) end end
Whether this task instance provides a set of models and arguments
The fullfills? predicate checks if this task can be used to fullfill the need of the given model and arguments The default is to check if
* the needed task model is an ancestor of this task * the task * +args+ is included in the task arguments
# File lib/roby/task.rb, line 1149 def fullfills?(models, args = nil) if models.kind_of?(Roby::Task) args ||= models.meaningful_arguments models = models.model end if !model.fullfills?(models) return false end if args args.each do |key, name| if self.arguments[key] != name return false end end end true end
True if all arguments defined by Task.argument on the task model are either explicitely set or have a default value.
# File lib/roby/task.rb, line 486 def fully_instanciated? if arguments.static? @fully_instanciated ||= list_unset_arguments.empty? else list_unset_arguments.empty? end end
Roby::PlanObject#garbage!
# File lib/roby/task.rb, line 531 def garbage! bound_events.each_value(&:garbage!) super end
# File lib/roby/state/task.rb, line 31 def goal @goal ||= GoalSpace.new(self.model.goal) end
@api private
Handles the given exception.
In addition to the exception handlers provided by {ExceptionHandlingObject}, it checks for repair tasks (as defined by TaskStructure::ErrorHandling
)
@param [ExecutionException] e
Roby::ExceptionHandlingObject#handle_exception
# File lib/roby/task.rb, line 1227 def handle_exception(e) return if !plan tasks = find_all_matching_repair_tasks(e) return super if tasks.empty? if !tasks.any? { |t| t.running? } tasks.first.start! end true end
True if this model requires an argument named key and that argument is set
# File lib/roby/task.rb, line 1171 def has_argument?(key) self.arguments.set?(key) end
True if this task has an event of the required model. The event model can either be a event class or an event name.
# File lib/roby/task.rb, line 500 def has_event?(event_model) bound_events.has_key?(event_model) end
@api private
Roby::PlanObject#initialize_replacement
# File lib/roby/task.rb, line 1451 def initialize_replacement(task) super execute_handlers.each do |handler| if handler.copy_on_replace? task.execute(handler.as_options, &handler.block) end end poll_handlers.each do |handler| if handler.copy_on_replace? task.poll(handler.as_options, &handler.block) end end end
# File lib/roby/task.rb, line 151 def inspect state = if pending? then 'pending' elsif failed_to_start? then 'failed to start' elsif starting? then 'starting' elsif running? then 'running' elsif finishing? then 'finishing' else 'finished' end "#<#{to_s} executable=#{executable?} state=#{state} plan=#{plan.to_s}>" end
Returns true if this task's stop event is controlable
# File lib/roby/task.rb, line 453 def interruptible?; stop_event.controlable? end
# File lib/roby/task.rb, line 622 def invalidate_terminal_flag; @terminal_flag_invalid = true end
# File lib/roby/task.rb, line 621 def invalidated_terminal_flag?; !!@terminal_flag_invalid end
The last event emitted by this task
@return [TaskEvent,nil]
# File lib/roby/task.rb, line 379 def last_event history.last end
Returns for how many seconds this task is running. Returns nil if the task is not running.
# File lib/roby/task.rb, line 354 def lifetime if running? Time.now - start_time elsif finished? end_time - start_time end end
# File lib/roby/task.rb, line 550 def mark_failed_to_start(reason, time) if failed_to_start? return elsif !pending? && !starting? raise Roby::InternalError, "#{self} is neither pending nor starting, cannot mark as failed_to_start!" end @failed_to_start = true @failed_to_start_time = time @failure_reason = reason @pending = false @starting = false @failed = true plan.task_index.set_state(self, :failed?) end
Return a task match object that matches self
@return [Queries::TaskMatcher]
# File lib/roby/task.rb, line 1601 def match self.class.match.with_instance(self) end
The part of {#arguments} that is meaningful for this task model. I.e. it returns the set of elements in {#arguments} that are listed in the task model
# File lib/roby/task.rb, line 111 def meaningful_arguments(task_model = self.model) task_model.meaningful_arguments(arguments) end
The task name
@return [String]
# File lib/roby/task.rb, line 136 def name return @name if @name name = "#{model.name || self.class.name}:0x#{address.to_s(16)}" if !frozen? @name = name end name end
True if this task is a null task. See NullTask.
# File lib/roby/task.rb, line 918 def null?; false end
@deprecated use {TaskEventGenerator#on} on the event object, e.g.
task.start_event.on { |event| ... }
# File lib/roby/task.rb, line 795 def on(event_model, options = Hash.new, &user_handler) Roby.warn_deprecated "Task#on is deprecated, use EventGenerator#on instead (e.g. #{event_model}_event.signals other_event)" event(event_model).on(options, &user_handler) self end
True if at least one argument required by the task model is not set. See Task.argument.
# File lib/roby/task.rb, line 496 def partially_instanciated?; !fully_instanciated? end
Adds a new poll block on this instance
@macro InstanceHandlerOptions @yieldparam [Roby::Task] task the task on which the poll block is
executed. It might be different than the one on which it has been added because of replacements.
@return [Object] an ID that can be used in {#remove_poll_handler}
# File lib/roby/task.rb, line 1047 def poll(options = Hash.new, &block) default_on_replace = if abstract? then :copy else :drop end options = InstanceHandler.validate_options(options, on_replace: default_on_replace) check_arity(block, 1) @poll_handlers << (handler = InstanceHandler.new(block, (options[:on_replace] == :copy))) ensure_poll_handler_called handler end
(see PlanObject#promise
)
@raise [PromiseInFinishedTask] if attempting to create a promise on a
task that is either finished, or failed to start
Roby::PlanObject#promise
# File lib/roby/task.rb, line 343 def promise(description: "#{self}.promise", executor: promise_executor, &block) if failed_to_start? raise PromiseInFinishedTask, "attempting to create a promise on #{self} that has failed to start" elsif finished? raise PromiseInFinishedTask, "attempting to create a promise on #{self} that is finished" end super end
Mark the task as quarantined
Once set it cannot be unset
# File lib/roby/task.rb, line 544 def quarantined! @quarantined = true end
@api private
Declare that a coordination object is no longer attached to this task
@param [Coordination::Base] object
# File lib/roby/task.rb, line 1626 def remove_coordination_object(object) @coordination_objects.delete(object) end
Remove a poll handler from this instance
@param [Object] handler the ID returned by {#poll} @return [void]
# File lib/roby/task.rb, line 1061 def remove_poll_handler(handler) @poll_handlers.delete(handler) end
Replaces self by object
It replaces self by object in all relations self
is part of, and do the same for the task's event generators.
@see replace_subplan_by
# File lib/roby/task.rb, line 1433 def replace_by(object) event_mappings = Hash.new event_resolver = ->(e) { object.event(e.symbol) } each_event do |ev| event_mappings[ev] = [nil, event_resolver] end object.each_event do |ev| event_mappings[ev] = nil end plan.replace_subplan(Hash[self => object, object => nil], event_mappings) initialize_replacement(object) each_event do |event| event.initialize_replacement(nil) { object.event(event.symbol) } end end
Replaces self's subplan by another subplan
Replaces the subplan generated by self 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 {DirectedRelationSupport}.
Relations
to free events are not copied during replacement
@see replace_by
# File lib/roby/task.rb, line 1377 def replace_subplan_by(object) edges = compute_subplan_replacement_operation(object) apply_replacement_operations(edges) initialize_replacement(object) each_event do |event| event.initialize_replacement(object.event(event.symbol)) end end
# File lib/roby/state/task.rb, line 35 def resolve_goals if !fully_instanciated? raise ArgumentError, "cannot resolve goals on a task that is not fully instanciated" end self.model.goal.resolve_goals(self, self.goal) end
# File lib/roby/state/task.rb, line 17 def resolve_state_sources model.state.resolve_data_sources(self, state) end
Create a new task of the same model and with the same arguments than this one. Insert this task in the plan and make it replace the fresh one.
See Plan#respawn
# File lib/roby/task.rb, line 1274 def respawn plan.respawn(self) end
True if this task can be reused by some other parts in the plan
# File lib/roby/task.rb, line 527 def reusable? plan && @reusable && !quarantined? && !garbage? && !failed_to_start? && !finished? && !finishing? end
True if this task is currently running (i.e. is has already started, and is not finished)
# File lib/roby/task.rb, line 511 def running?; started? && !finished? end
Adds a task script that is going to be executed while this task instance runs.
# File lib/roby/coordination/task_script.rb, line 277 def script(options = Hash.new, &block) execute do |task| script = model.create_script(task, &block) script.prepare script.step end model.create_script(self, &block) end
@deprecated use {TaskEventGenerator#signal} instead (e.g. task.start_event.signal other_task.stop_event)
# File lib/roby/task.rb, line 802 def signals(event_model, to, *to_task_events) Roby.warn_deprecated "Task#signals is deprecated, use EventGenerator#signal instead (e.g. #{event_model}_event.signals other_event)" generator = event(event_model) if Hash === to_task_events.last delay = to_task_events.pop end to_events = case to when Task to_task_events.map { |ev_model| to.event(ev_model) } when EventGenerator then [to] else raise ArgumentError, "expected Task or EventGenerator, got #{to}(#{to.class}: #{to.class.ancestors})" end to_events.each do |event| generator.signals event, delay end self end
@deprecated this has no equivalent. It really has never seen proper support
# File lib/roby/task.rb, line 1468 def simulate simulation_task = self.model.simulation_model.new(arguments.to_hash) plan.force_replace(self, simulation_task) simulation_task end
Returns when this task has been started
# File lib/roby/task.rb, line 363 def start_time if ev = start_event.last ev.time end end
# File lib/roby/state/task.rb, line 13 def state @state ||= StateSpace.new(self.model.state) end
Returns this task's set of terminal events.
A terminal event is an event whose emission announces the end of the task. In most case, it is an event which is forwarded directly on indirectly to stop
.
@return [Array<TaskEventGenerator>]
# File lib/roby/task.rb, line 872 def terminal_events bound_events.each_value.find_all { |ev| ev.terminal? } end
# File lib/roby/task.rb, line 1589 def to_execution_exception ExecutionException.new(LocalizedError.new(self)) end
Converts this object into a task object
# File lib/roby/task.rb, line 920 def to_task; self end
# File lib/roby/coordination/task_script.rb, line 286 def transition! poll_transition_event.emit end
Declares that this fault response table should be made active when this task starts, and deactivated when it ends
# File lib/roby/task.rb, line 1129 def use_fault_response_table(table_model, arguments = Hash.new) arguments = table_model.validate_arguments(arguments) table = nil execute do |task| table = task.plan.use_fault_response_table(table_model, arguments) end stop_event.on do |event| plan.remove_fault_response_table(table) end end
Register a hook that is called when this task is finalized (removed from its plan)
@macro InstanceHandlerOptions
Roby::PlanObject#when_finalized
# File lib/roby/task.rb, line 1493 def when_finalized(options = Hash.new, &block) default = if abstract? then :copy else :drop end options, remaining = InstanceHandler.filter_options options, on_replace: default super(options.merge(remaining), &block) end
Creates a parallel aggregation between self
and task
. Both tasks are started at the same time, and the returned instance finishes when both tasks are finished. The returned value is an instance of Parallel.
Note that this operator always creates a new Parallel object, so
a | b | c | d
will create three instances of Parallel. If more than two tasks should be organized that way, one should instead use Parallel#<<:
Parallel.new << a << b << c << d
# File lib/roby/task.rb, line 1579 def |(task) if self.null? task elsif task.null? self else Tasks::Parallel.new << self << task end end
Private Instance Methods
# File lib/roby/task.rb, line 1499 def internal_error_handler(exception) if !exception.originates_from?(self) return pass_exception end gen = exception.generator error = exception.exception if (gen == start_event) && !gen.emitted? if !failed_to_start? failed_to_start!(error) end elsif !running? pass_exception elsif (!gen || !gen.terminal?) && !internal_error_event.emitted? internal_error_event.emit(error) if stop_event.pending? || !stop_event.controlable? # In this case, we can't "just" stop the task. We have # to inject +error+ in the exception handling and kill # everything that depends on it. add_error(TaskEmergencyTermination.new(self, error, false)) end else if execution_engine.display_exceptions? # No nice way to isolate this error through the task # interface, as we can't emergency stop it. Quarantine it # and inject it in the normal exception propagation # mechanisms. execution_engine.fatal "putting #{self} in quarantine: #{self} failed to emit" execution_engine.fatal "the error is:" Roby.log_exception_with_backtrace(error, execution_engine, :fatal) end plan.quarantine_task(self) add_error(TaskEmergencyTermination.new(self, error, true)) end end