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:

@!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

arguments[R]

The task arguments

@return [TaskArguments]

bound_events[R]

List of EventGenerator objects bound to this task

data[R]

The internal data for this task

@see data= updated_data updated_data_event

execute_handlers[R]

@api private

The set of instance-level execute blocks

@return [Array<InstanceHandler>]

failed_to_start_time[R]

The time at which the task failed to start

failure_event[R]

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

failure_reason[R]

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.

history[R]

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>]

poll_handlers[R]

@api private

The set of instance-level poll blocks

@return [Array<InstanceHandler>]

state_machine[R]
terminal_event[R]

The most specialized event that caused this task to end

Public Class Methods

create_script(*task, &block) click to toggle source
# 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
goal() click to toggle source
# 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
new(plan: TemplatePlan.new, **arguments) click to toggle source

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

Calls superclass method 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
script(&block) click to toggle source

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

+(task) click to toggle source

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
add_child_object(child, type, info) click to toggle source

@api private

Validates that both self and the child object are owned by the local instance

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

@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
apply_replacement_operations(edges) click to toggle source

@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
apply_terminal_flags(terminal_events, success_events, failure_events) click to toggle source
# 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
as_plan() click to toggle source
# File lib/roby/task.rb, line 1486
def as_plan
    self
end
as_service() click to toggle source

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
assign_argument(key, value) click to toggle source

@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
assign_arguments(**arguments) click to toggle source

@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
can_merge?(target) click to toggle source

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

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

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_events_external_relations(remove_strong: true) click to toggle source

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
clear_relations(remove_internal: false, remove_strong: true) click to toggle source

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

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

@api private

This method is called during the commit process to apply changes stored in a proxy

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

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
compute_object_replacement_operation(object) click to toggle source

@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
compute_subplan_replacement_operation(object) click to toggle source

@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
create_fresh_copy() click to toggle source
# File lib/roby/task.rb, line 383
def create_fresh_copy
    model.new(arguments.dup)
end
create_transaction_proxy(transaction) click to toggle source

@api private

# File lib/roby/task.rb, line 1594
def create_transaction_proxy(transaction)
    transaction.create_and_register_proxy_task(self)
end
current_state() click to toggle source

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

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
data=(value) click to toggle source

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

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
each_coordination_object(&block) click to toggle source

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
each_event(only_wrapped = true) { |ev| ... } click to toggle source

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
Also aliased as: each_plan_child
each_exception_handler(&iterator) click to toggle source

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
each_plan_child(only_wrapped = true)
Alias for: each_event
emit(event_model, *context) click to toggle source

@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
end_time() click to toggle source

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

@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
event(event_model) click to toggle source

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
event_model(model) click to toggle source

(see Models::Task#event_model)

# File lib/roby/task.rb, line 877
def event_model(model)
    self.model.event_model(model)
end
executable=(flag) click to toggle source

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.

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

True if this task is executable. A task is not executable if it is abstract or partially instanciated.

@see abstract? partially_instanciated?

Calls superclass method 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
execute(options = Hash.new, &block) click to toggle source

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
failed_to_start!(reason, time = Time.now) click to toggle source
# 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
failed_to_start?() click to toggle source
# File lib/roby/task.rb, line 548
def failed_to_start?; @failed_to_start end
find_event(name) click to toggle source

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
fired_event(event) click to toggle source

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

“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
forward_to(event_model, to, *to_task_events) click to toggle source

@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
freeze_delayed_arguments() click to toggle source

@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
fullfills?(models, args = nil) click to toggle source

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

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
garbage!() click to toggle source
Calls superclass method Roby::PlanObject#garbage!
# File lib/roby/task.rb, line 531
def garbage!
    bound_events.each_value(&:garbage!)
    super
end
goal() click to toggle source
# File lib/roby/state/task.rb, line 31
def goal
    @goal ||= GoalSpace.new(self.model.goal)
end
handle_exception(e) click to toggle source

@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

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

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

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
initialize_replacement(task) click to toggle source

@api private

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

Returns true if this task's stop event is controlable

# File lib/roby/task.rb, line 453
def interruptible?; stop_event.controlable? end
invalidate_terminal_flag() click to toggle source
# File lib/roby/task.rb, line 622
def invalidate_terminal_flag; @terminal_flag_invalid = true end
invalidated_terminal_flag?() click to toggle source
# File lib/roby/task.rb, line 621
def invalidated_terminal_flag?; !!@terminal_flag_invalid end
last_event() click to toggle source

The last event emitted by this task

@return [TaskEvent,nil]

# File lib/roby/task.rb, line 379
def last_event
    history.last
end
lifetime() click to toggle source

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
mark_failed_to_start(reason, time) click to toggle source
# 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
match() click to toggle source

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
meaningful_arguments(task_model = self.model) click to toggle source

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

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

True if this task is a null task. See NullTask.

# File lib/roby/task.rb, line 918
def null?; false end
on(event_model, options = Hash.new, &user_handler) click to toggle source

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

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
poll(options = Hash.new, &block) click to toggle source

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
promise(description: " click to toggle source

(see PlanObject#promise)

@raise [PromiseInFinishedTask] if attempting to create a promise on a

task that is either finished, or failed to start
Calls superclass method 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
quarantined!() click to toggle source

Mark the task as quarantined

Once set it cannot be unset

# File lib/roby/task.rb, line 544
def quarantined!
    @quarantined = true
end
remove_coordination_object(object) click to toggle source

@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_poll_handler(handler) click to toggle source

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
replace_by(object) click to toggle source

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
replace_subplan_by(object) click to toggle source

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
resolve_goals() click to toggle source
# 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
resolve_state_sources() click to toggle source
# File lib/roby/state/task.rb, line 17
def resolve_state_sources
    model.state.resolve_data_sources(self, state)
end
respawn() click to toggle source

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

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

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
script(options = Hash.new, &block) click to toggle source

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
signals(event_model, to, *to_task_events) click to toggle source

@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
simulate() click to toggle source

@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
start_time() click to toggle source

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
state() click to toggle source
# File lib/roby/state/task.rb, line 13
def state
    @state ||= StateSpace.new(self.model.state)
end
terminal_events() click to toggle source

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
to_execution_exception() click to toggle source
# File lib/roby/task.rb, line 1589
def to_execution_exception
    ExecutionException.new(LocalizedError.new(self))
end
to_task() click to toggle source

Converts this object into a task object

# File lib/roby/task.rb, line 920
def to_task; self end
transition!() click to toggle source
# File lib/roby/coordination/task_script.rb, line 286
def transition!
    poll_transition_event.emit
end
updated_data() click to toggle source

This hook is called whenever the internal data of this task is updated. See data, data= and the updated_data event

# File lib/roby/task.rb, line 1002
def updated_data
end
use_fault_response_table(table_model, arguments = Hash.new) click to toggle source

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
when_finalized(options = Hash.new, &block) click to toggle source

Register a hook that is called when this task is finalized (removed from its plan)

@macro InstanceHandlerOptions

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

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

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