Class: Roby::PlanningLoop

Inherits:
Task show all
Defined in:
lib/roby/planning/loops.rb

Overview

This class unrolls a loop in the plan. It maintains lookahead patterns developped at all times by calling an external planner, and manages them. This documentation will start by describing the general behaviour of this task, and then we will detail different specific modes of operation.

Behaviour description

The task unrolls the loop by generating /patterns/, which are a combination of a task representing the operation to be done during one pass of the loop, and a planning task which will generate the subplan for this operation. These patterns are developped as children of either the PlanningLoop task itself, or its planned_task if there is one.

During the execution of this suite of patterns, the following constraints are always met:

  • the planning task of a pattern is started after the one of the previous pattern has finished.

  • a pattern is started after the previous one has finished.

The #start! command do not starts the loop per-se. It only makes the first lookahead patterns to be developped. You have to call #loop_start! once to start the generated patterns themselves.

Periodic and nonperiodic loops

On the one hand, if the :period option of #initialize is non-nil, it is expected to be a floating-point value representing a time in seconds. In that case, the loop is periodic and each pattern in the loop is started at the given periodic rate, triggered by the #periodic_trigger event. Note that the ‘zero-period’ case is a special situation where the loop runs as fast as possible.

On the other hand, if :period is nil, the loop is nonperiodic, and each pattern must be explicitely started by calling #loop_start!. Finally, #loop_start! can also be called to bypass the period value (i.e. to start a pattern earlier than expected). Repetitive calls to #loop_start! will make the loop develop and start at most one pattern.

Zero lookahead

When the loop lookahead is nonzero, patterns are planend ahead-of-time: they are planned as soon as possible. In some cases, it is non desirable, for instance because some information is available only at a later time.

For these situations, one can use a zero lookahead. In that case, the patterns are not pre-planned, but instead the planning task is started only when the pattern itself should have been started: either when the period timeouts, or when #loop_start! is explicitely called.

TODO: make figures.

Constant Summary

Constants included from Log::TaskHooks

Log::TaskHooks::HOOKS

Constants included from Log::BasicObjectHooks

Log::BasicObjectHooks::HOOKS

Instance Attribute Summary collapse

Attributes inherited from PlanObject

#executable, #plan, #removed_at

Attributes inherited from BasicObject

#distribute

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Task

#_dump, _load, #droby_dump, improves, #improves?, improves?, match, needs, #needs?, needs?

Methods included from TaskStructure::ModelConflicts

#conflicts_with, #conflicts_with?

Methods included from Distributed::DRobyTaskModel::Dump

#droby_dump

Methods included from Distributed::DRobyModel::Dump

#droby_dump

Methods included from Distributed::TaskNotifications

#updated_data

Methods included from TaskOperations

#+, #|

Methods included from Log::TaskHooks

#added_child_object, #removed_child_object

Methods inherited from PlanObject

#add_child_object, #apply_relation_changes, child_plan_object, #each_plan_child, #executable?, #finalized?, #forget_peer, #read_write?, #remotely_useful?, #removing_child_object, #replace_by, #replace_subplan_by, #root_object, #root_object?, #subscribed?, #update_on?, #updated_by?

Methods included from Distributed::RelationModificationHooks

#added_child_object, #removed_child_object

Methods included from Transactions::PlanObjectUpdates

#adding_child_object, #removing_child_object

Methods included from DirectedRelationSupport

#add_child_object, #add_parent_object, #check_is_relation, #related_objects, #relations, #remove_child_object, #remove_children, #remove_parent_object, #remove_parents, #remove_relations

Methods inherited from BasicObject

#add_sibling_for, #distribute?, distribute?, #finalized?, #forget_peer, #has_sibling_on?, #initialize_copy, local_only, #read_write?, #remotely_useful?, #remove_sibling_for, #self_owned?, #sibling_of, #sibling_on, #subscribe, #subscribed?, #update_on?, #updated?, #updated_by?, #updated_peers

Methods included from Log::BasicObjectHooks

#added_owner, #removed_owner

Constructor Details

#initialize(options) ⇒ PlanningLoop

Returns a new instance of PlanningLoop.



104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/roby/planning/loops.rb', line 104

def initialize(options)
    task_arguments, planning_options = PlanningLoop.filter_options(options)
    task_arguments[:method_options].merge!(planning_options)
    super(task_arguments)

           if period && period > 0
               @periodic_trigger = State.on_delta :t => period
               periodic_trigger.disable
               periodic_trigger.on event(:loop_start)
           end
           
    @patterns = []
           @pattern_id = 0
end

Instance Attribute Details

#patternsObject (readonly)

An array of [planning_task, user_command]. The last element is the first arrived



58
59
60
# File 'lib/roby/planning/loops.rb', line 58

def patterns
  @patterns
end

#periodic_triggerObject (readonly)

If this loop is periodic of nonzero period, the state event which represents that period.



102
103
104
# File 'lib/roby/planning/loops.rb', line 102

def periodic_trigger
  @periodic_trigger
end

Class Method Details

.filter_options(options) ⇒ Object

Filters the options in options, splitting between the options that are specific to the planning task and those that are to be forwarded to the planner itself



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/roby/planning/loops.rb', line 79

def self.filter_options(options) # :nodoc:
    task_arguments, planning_options = Kernel.filter_options options, 
  :period => nil,
  :lookahead => 1,
  :planner_model => nil,
  :planned_model => Roby::Task,
  :method_name => nil,
  :method_options => {},
  :planning_owners => nil

    if !task_arguments[:method_name]
  raise ArgumentError, "required argument :method_name missing"
    elsif !task_arguments[:planner_model]
  raise ArgumentError, "required argument :planner_model missing"
    elsif task_arguments[:lookahead] < 0
  raise ArgumentError, "lookahead must be positive"
    end
    task_arguments[:period] ||= nil
    [task_arguments, planning_options]
end

Instance Method Details

#append_pattern(*context) ⇒ Object

Appends a new unplanned pattern after all the patterns already developped

context is forwarded to the planned task



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/roby/planning/loops.rb', line 136

def append_pattern(*context)
    # Create the new pattern
    task_arguments = arguments.slice(:planner_model, :planned_model, :method_name)
    task_arguments[:method_options] = method_options.dup
    task_arguments[:method_options][:pattern_id] = @pattern_id
    @pattern_id += 1

    planning = PlanningTask.new(task_arguments)
    planned  = planning.planned_task
    planned.forward(:start,   self, :loop_start)
    planned.forward(:success, self, :loop_success)
    planned.forward(:stop,    self, :loop_end)
    main_task.realized_by planned
    
    # Schedule it. We start the new pattern when these three conditions are met:
    # * it has been planned (planning has finished)
    # * the previous one (if any) has finished
           # * the period (if any) has expired or an external event required
           #   the explicit start of the pattern (call done to user_command,
           #   for instance through a call to #loop_start!)
           #
           # The +precondition+ event represents a situation where the new pattern
           # *can* be started, while +command+ is the situation asking for the
           # pattern to start.
    precondition = planning.event(:success)
    user_command = EventGenerator.new(true)
           command      = user_command

    if last_planning = last_planning_task
  last_planned = last_planning.planned_task

               if !last_planned.finished?
      precondition &= last_planned.event(:stop)
  end

               if period && !periodic_trigger
                   command |= planned.event(:success)
               end

  if last_planning.finished?
      planning.start!(*context) 
  else
      last_planning.event(:success).filter(*context).on(planning.event(:start))
  end
    end
           command &= precondition

    patterns.unshift([planning, user_command])
    command.on(planned.event(:start))
    planning
end

#last_planning_taskObject

The PlanningTask object for the last pattern



127
128
129
130
131
# File 'lib/roby/planning/loops.rb', line 127

def last_planning_task
    if pattern = patterns.first
  pattern.first
    end
end

#main_taskObject

The task on which the children are added



120
# File 'lib/roby/planning/loops.rb', line 120

def main_task; planned_task || self end

#planned_taskObject

:nodoc:



122
123
124
# File 'lib/roby/planning/loops.rb', line 122

def planned_task # :nodoc:
    planned_tasks.find { true } 
end