module Roby::Models::Task

Public Class Methods

model_relation(name) click to toggle source
# File lib/roby/models/task.rb, line 285
def self.model_relation(name)
    model_attribute_list(name)
end

Public Instance Methods

abstract() click to toggle source

Declare that this task model defines abstract tasks. Abstract tasks can be used to represent an action, without specifically representing how this action should be done.

Instances of abstract task models are not executable, i.e. they cannot be started.

@see abstract? executable?

# File lib/roby/models/task.rb, line 461
def abstract
    @abstract = true
end
all_models() click to toggle source

@deprecated

Use each_submodel instead

# File lib/roby/models/task.rb, line 221
def all_models
    submodels
end
as_plan(arguments = Hash.new) click to toggle source

Default implementation of the as_plan method

The as_plan method is used to use task models as representation of abstract actions. For instance, if an as_plan method is available on a particular MoveTo task model, one can do

root.depends_on(MoveTo)

This default implementation looks for planning methods declared in the main Roby application planners that return the required task type or one of its subclasses. If one is found, it is using it to generate the action. Otherwise, it falls back to returning a new instance of this task model, unless the model is abstract in which case it raises ArgumentError.

It can be used with

class TaskModel < Roby::Task
end

root = Roby::Task.new
child = root.depends_on(TaskModel)

If arguments need to be given, the with_arguments method should be used:

root = Roby::Task.new
child = root.depends_on(TaskModel.with_arguments(id: 200))
# File lib/roby/models/task.rb, line 208
def as_plan(arguments = Hash.new)
    Roby.app.prepare_action(self, **arguments).first
rescue Application::ActionResolutionError
    if abstract?
        raise Application::ActionResolutionError, "#{self} is abstract and no planning method exists that returns it"
    else
        new(arguments)
    end
end
can_merge?(target_model) click to toggle source
# File lib/roby/models/task.rb, line 826
def can_merge?(target_model)
    fullfills?(target_model)
end
clear_model() click to toggle source

Clears all definitions saved in this model. This is to be used by the reloading code

Calls superclass method
# File lib/roby/models/task.rb, line 227
def clear_model
    class_eval do
        # Remove event models
        events.each_key do |ev_symbol|
            remove_const ev_symbol.to_s.camelcase(:upper)
        end

        [@events, @signal_sets, @forwarding_sets, @causal_link_sets,
            @arguments, @handler_sets, @precondition_sets].each do |set|
            set.clear if set
        end
    end
    super
end
compute_terminal_events(events) click to toggle source
# File lib/roby/models/task.rb, line 147
def compute_terminal_events(events)
    success_events, failure_events, terminal_events =
        [events[:success]].to_set, 
        [events[:failed]].to_set,
        [events[:stop], events[:success], events[:failed]].to_set

    event_set = events.values.to_set
    discover_terminal_events(event_set, terminal_events, success_events, events[:success])
    discover_terminal_events(event_set, terminal_events, failure_events, events[:failed])
    discover_terminal_events(event_set, terminal_events, nil, events[:stop])

    events.each_value do |ev|
        if ev.event_model.terminal?
            if !success_events.include?(ev) && !failure_events.include?(ev)
                terminal_events << ev 
            end
        end
    end

    return terminal_events, success_events, failure_events
end
define_command_method(event_name, block) click to toggle source

@api private

Define the method that will be used as command for the given event

@param [Symbol] event_name the event name

# File lib/roby/models/task.rb, line 565
def define_command_method(event_name, block)
    check_arity(block, 1, strict: true)
    define_method("event_command_#{event_name}", &block)
    method = instance_method("event_command_#{event_name}")
    lambda do |dst_task, *event_context| 
        method.bind(dst_task).call(*event_context) 
    end
end
define_event_methods(event_name) click to toggle source

@api private

Define support methods for a task event

@param [Symbol] event_name the event name

# File lib/roby/models/task.rb, line 579
def define_event_methods(event_name)
    event_name = event_name.to_sym
    if !method_defined?("#{event_name}_event")
        define_method("#{event_name}_event") do
            @bound_events[event_name] || event(event_name)
        end
    end
    if !method_defined?("#{event_name}?")
        define_method("#{event_name}?") do
            (@bound_events[event_name] || event(event_name)).emitted?
        end
    end
    if !method_defined?("#{event_name}!")
        define_method("#{event_name}!") do |*context| 
            (@bound_events[event_name] || event(event_name)).call(*context)
        end
    end
    if !respond_to?("#{event_name}_event")
        singleton_class.class_eval do
            define_method("#{event_name}_event") do
                find_event_model(event_name)
            end
        end
    end
end
discover_terminal_events(events, terminal_set, set, root) click to toggle source
# File lib/roby/models/task.rb, line 127
def discover_terminal_events(events, terminal_set, set, root)
    stack = [root]
    while !stack.empty?
        vertex = stack.shift
        for relation in [EventStructure::Signal, EventStructure::Forwarding]
            for parent in vertex.parent_objects(relation)
                if !events.include?(parent)
                    next
                elsif parent[vertex, relation]
                    next
                elsif !terminal_set.include?(parent)
                    terminal_set  << parent
                    set   << parent if set
                    stack << parent
                end
            end
        end
    end
end
enum_events() click to toggle source
# File lib/roby/models/task.rb, line 640
def enum_events # :nodoc
    Roby.warn_deprecated "#enum_events is deprecated, use #each_event without a block instead"
    each_event
end
event(event_name, options = Hash.new, &block) click to toggle source

Defines a new event on this task.

@param [Symbol] event_name the event name @param [Hash] options an option hash @option options [Boolean] :controllable if true, the event is

controllable and will use the default command of emitting directly
in the command

@option options [Boolean] :terminal if true, the event is marked as

terminal, i.e. it will terminate the task upon emission. Giving this
flag is required to redeclare an existing terminal event in a
subclass. Otherwise, it is determined automatically by checking
whether the event is forwarded to :stop

@option options [Class] :model the base class used to create the

model for this event. This class is going to be used to generate the
event. Defaults to TaskEvent.

When a task event (for instance start) is emitted, a Roby::TaskEvent object is created to describe the information related to this emission (time, sources, context information, …). Task.event defines a specific event model MyTask::MyEvent for each task event with name :my_event. This specific model is by default a subclass of Roby::TaskEvent, but it is possible to override that by using the model option.

# File lib/roby/models/task.rb, line 524
def event(event_name, options = Hash.new, &block)
    event_name = event_name.to_sym

    options = validate_options options,
        controlable: nil, command: nil, terminal: nil,
        model: find_event_model(event_name) || Roby::TaskEvent

    if options.has_key?(:controlable)
        options[:command] = options[:controlable]
    elsif !options.has_key?(:command) && block
        options[:command] = define_command_method(event_name, block)
    end
    validate_event_definition_request(event_name, options)

    # Define the event class
    new_event = options[:model].new_submodel task_model: self,
        terminal: options[:terminal],
        symbol: event_name, command: options[:command]
    new_event.permanent_model = self.permanent_model?

    setup_terminal_handler = false
    old_model = find_event_model(event_name)
    if new_event.symbol != :stop && options[:terminal] && (!old_model || !old_model.terminal?)
        setup_terminal_handler = true
    end

    events[new_event.symbol] = new_event
    if setup_terminal_handler
        forward(new_event => :stop)
    end
    const_set(event_name.to_s.camelcase(:upper), new_event)

    define_event_methods(event_name)
    new_event
end
find_event_model(name) click to toggle source

Find the event class for event, or nil if event is not an event name for this model

# File lib/roby/models/task.rb, line 652
def find_event_model(name)
    find_event(name.to_sym)
end
Also aliased as: has_event?
forward(mappings) click to toggle source

Establish model-level forwarding between events of that task. These relations will be established on all the instances of this task model (and its subclasses).

Forwarding is used to cause the target event to be emitted when the source event is.

@param [Hash<Symbol,Array<Symbol>>,Hash<Symbol,Symbol>] mappings the source-to-target mappings @example

# A task that is stopped as soon as it is started
class MyTask < Roby::Task
  forward start: :stop
end

@see Task#forward @see EventGenerator#forward. @see Roby::EventStructure::Forward the forwarding relation.

# File lib/roby/models/task.rb, line 378
def forward(mappings)
    mappings.each do |from, to|
        from    = event_model(from).symbol
        targets = Array[*to].map { |ev| event_model(ev).symbol }

        if event_model(from).terminal?
            non_terminal = targets.find_all { |name| !event_model(name).terminal? }
            if !non_terminal.empty?
                raise ArgumentError, "trying to establish a forwarding relation from the terminal event #{from} to the non-terminal event(s) #{targets}"
            end
        end

        forwarding_sets[from].merge targets
    end
    update_terminal_flag
end
from(object) click to toggle source

Helper method to define delayed arguments from related objects

@example propagate an argument from a parent task

argument :target, default: from(:parent).target
# File lib/roby/models/task.rb, line 401
def from(object)
    if object.kind_of?(Symbol)
        Roby.from(nil).send(object)
    else
        Roby.from(object)
    end
end
from_state(state_object = State) click to toggle source

Helper method to define delayed arguments from the State object

@example get an argument from the State object

argument :initial_pose, default: from_state.pose
# File lib/roby/models/task.rb, line 413
def from_state(state_object = State)
    Roby.from_state(state_object)
end
fullfills?(models) click to toggle source
# File lib/roby/models/task.rb, line 812
def fullfills?(models)
    if models.respond_to?(:each)
        models = models.to_a
    else models = [models]
    end

    models.each do |m|
        m.each_fullfilled_model do |test_m|
            return false if !has_ancestor?(test_m)
        end
    end
    return true
end
has_event?(name)

Checks if name is a name for an event of this task

Alias for: find_event_model
instantiate_event_relations(template) click to toggle source
# File lib/roby/models/task.rb, line 58
def instantiate_event_relations(template)
    events = template.events_by_name

    all_signals.each do |generator, signalled_events|
        next if signalled_events.empty?
        generator = events[generator]

        for signalled in signalled_events
            signalled = events[signalled]
            generator.signals signalled
        end
    end

    all_forwardings.each do |generator, signalled_events|
        next if signalled_events.empty?
        generator = events[generator]

        for signalled in signalled_events
            signalled = events[signalled]
            generator.forward_to signalled
        end
    end

    all_causal_links.each do |generator, signalled_events|
        next if signalled_events.empty?
        generator = events[generator]

        for signalled in signalled_events
            signalled = events[signalled]
            generator.add_causal_link signalled
        end
    end

    # Add a link from internal_event to stop if stop is controllable
    if events[:stop].controlable?
        events[:internal_error].signals events[:stop]
    end

    terminal_events, success_events, failure_events =
        compute_terminal_events(events)

    template.terminal_events = terminal_events
    template.success_events   = success_events
    template.failure_events  = failure_events
    start_event = events[:start]

    # WARN: the start event CAN be terminal: it can be a signal from
    # :start to a terminal event
    #
    # Create the precedence relations between 'normal' events and the terminal events
    root_terminal_events = terminal_events.find_all do |ev|
        (ev != start_event) && ev.root?(Roby::EventStructure::Precedence)
    end

    events.each_value do |ev|
        next if ev == start_event
        if !terminal_events.include?(ev)
            if ev.root?(Roby::EventStructure::Precedence)
                start_event.add_precedence(ev)
            end
            if ev.leaf?(Roby::EventStructure::Precedence)
                for terminal in root_terminal_events
                    ev.add_precedence(terminal)
                end
            end
        end
    end
end
interruptible() click to toggle source

Declare that tasks of this model can be interrupted by calling the command of {Roby::Task#failed_event}

@raise [ArgumentError] if {Roby::Task#failed_event} is not controlable.

# File lib/roby/models/task.rb, line 434
def interruptible
    if !has_event?(:failed) || !event_model(:failed).controlable?
        raise ArgumentError, "failed is not controlable"
    end

    event(:stop) do |context| 
        if starting?
            start_event.signals stop_event
            return
        end
        failed!(context)
    end
end
invalidate_template() click to toggle source
# File lib/roby/models/task.rb, line 40
def invalidate_template
    @template = nil
end
match(*args) click to toggle source

Returns a TaskMatcher object that matches this task model

# File lib/roby/models/task.rb, line 796
def match(*args)
    matcher = Queries::TaskMatcher.new
    if args.empty? && self != Task
        matcher.which_fullfills(self)
    else
        matcher.which_fullfills(*args)
    end
    matcher
end
on(*event_names, &user_handler) click to toggle source

Adds an event handler for the given event model. The block is going to be called whenever some events are emitted.

Unlike a block given to {EventGenerator#on}, the block is evaluated in the context of the task instance.

@param [Array<Symbol>] event_names the name of the events on which

to install the handler

@yieldparam [Object] context the arguments passed to {Roby::Task#emit}

when the event was emitted
# File lib/roby/models/task.rb, line 703
def on(*event_names, &user_handler)
    if !user_handler
        raise ArgumentError, "#on called without a block"
    end

    check_arity(user_handler, 1, strict: true)
    event_names.each do |from|
        from = event_model(from).symbol
        if user_handler 
            method_name = "event_handler_#{from}_#{Object.address_from_id(user_handler.object_id).to_s(16)}"
            define_method(method_name, &user_handler)

            handler = lambda { |event| event.task.send(method_name, event) }
            handler_sets[from] << EventGenerator::EventHandler.new(handler, false, false)
        end
    end
end
on_exception(matcher, &handler) click to toggle source

Defines an exception handler.

When propagating exceptions, {ExecutionException} goes up in the task hierarchy and calls matching handlers on the tasks it finds, and on their planning task. The first matching handler is called, and the exception propagation assumes that it handled the exception (i.e. won't look for new handlers) unless it calls {Roby::Task#pass_exception}

@param [#to_execution_exception_matcher] matcher object for

exceptions. Subclasses of {LocalizedError} have it (matching the
exception class) as well as {Task} (matches exception origin).
See {Roby::Queries} for more advanced exception matchers.

@yieldparam [ExecutionException] exception the exception that is

being handled

@example install a handler for a TaskModelViolation exception

on_exception(TaskModelViolation, ...) do |task, exception_object|
    if cannot_handle
        task.pass_exception # send to the next handler
    end
    do_handle
end
# File lib/roby/models/task.rb, line 775
def on_exception(matcher, &handler)
    check_arity(handler, 1, strict: true)
    matcher = matcher.to_execution_exception_matcher
    id = (@@exception_handler_id += 1)
    define_method("exception_handler_#{id}", &handler)
    exception_handlers.unshift [matcher, instance_method("exception_handler_#{id}")]
end
poll(&block) click to toggle source

Declares that the given block should be called at each execution cycle, when the task is running. Use it that way:

class MyTask < Roby::Task
  poll do
    ... do something ...
  end
end

If the given polling block raises an exception, the task will be terminated by emitting its failed event.

# File lib/roby/models/task.rb, line 743
def poll(&block)
    if !block_given?
        raise ArgumentError, "no block given"
    end

    define_method(:poll_handler, &block)
end
precondition(event, reason, &block) click to toggle source
# File lib/roby/models/task.rb, line 722
def precondition(event, reason, &block)
    event = event_model(event)
    precondition_sets[event.symbol] << [reason, block]
end
provided_services() click to toggle source

Returns the lists of tags this model fullfills.

# File lib/roby/models/task.rb, line 728
def provided_services
    ancestors.find_all { |m| m.kind_of?(Models::TaskServiceModel) }
end
query(*args) click to toggle source
# File lib/roby/models/task.rb, line 785
def query(*args)
    q = Queries::Query.new
    if args.empty? && self != Task
        q.which_fullfills(self)
    else
        q.which_fullfills(*args)
    end
    q
end
signal(mappings) click to toggle source

Establish model-level signals between events of that task. These signals will be established on all the instances of this task model (and its subclasses).

Signals cause the target event(s) command to be called when the source event is emitted.

@param [Hash<Symbol,Array<Symbol>>,Hash<Symbol,Symbol>] mappings the source-to-target mappings @raise [ArgumentError] if the target event is not controlable,

i.e. not have a command

@example when establishing multiple relations from the same source use name-to-arrays

signal start: [:one, :two]
# File lib/roby/models/task.rb, line 320
def signal(mappings)
    mappings.each do |from, to|
        from    = event_model(from)
        targets = Array[*to].map { |ev| event_model(ev) }

        if from.terminal?
            non_terminal = targets.find_all { |ev| !ev.terminal? }
            if !non_terminal.empty?
                raise ArgumentError, "trying to establish a signal from the terminal event #{from} to the non-terminal events #{non_terminal}"
            end
        end
        non_controlable = targets.find_all { |ev| !ev.controlable? }
        if !non_controlable.empty?
            raise ArgumentError, "trying to signal #{non_controlable.join(" ")} which is/are not controlable"
        end

        signal_sets[from.symbol].merge targets.map { |ev| ev.symbol }
    end
    update_terminal_flag
end
template() click to toggle source

The plan that is used to instantiate this task model

# File lib/roby/models/task.rb, line 45
def template
    return @template if @template

    template = Template.new
    each_event do |event_name, event_model|
        template.add(event = TemplateEventGenerator.new(event_model.controlable?, event_model, plan: template))
        template.events_by_name[event_name] = event
    end

    instantiate_event_relations(template)
    @template = template
end
terminal_events() click to toggle source

Get the list of terminal events for this task model

# File lib/roby/models/task.rb, line 646
def terminal_events
    each_event.find_all { |_, e| e.terminal? }.
        map { |_, e| e }
end
terminates() click to toggle source

Declare that tasks of this model can finish by simply emitting stop, i.e. with no specific action.

@example

class MyTask < Roby::Task
  terminates
end
# File lib/roby/models/task.rb, line 425
def terminates
    event :failed, command: true, terminal: true
    interruptible
end
to_coordination_task(task_model) click to toggle source
# File lib/roby/models/task.rb, line 830
def to_coordination_task(task_model)
    Roby::Coordination::Models::TaskFromAsPlan.new(self, self)
end
to_execution_exception_matcher() click to toggle source

@return [Queries::ExecutionExceptionMatcher] an exception match

object that matches exceptions originating from this task
# File lib/roby/models/task.rb, line 808
def to_execution_exception_matcher
    Queries::ExecutionExceptionMatcher.new.with_origin(self)
end
with_arguments(arguments = Hash.new) click to toggle source

If this class model has an 'as_plan', this specifies what arguments should be passed to as_plan

# File lib/roby/models/task.rb, line 171
def with_arguments(arguments = Hash.new)
    if respond_to?(:as_plan)
        AsPlanProxy.new(self, arguments)
    else
        raise NoMethodError, "#with_arguments is invalid on #self, as #self does not have an #as_plan method"
    end
end