roby 0.7.3 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) hide show
  1. data/History.txt +7 -5
  2. data/Manifest.txt +91 -16
  3. data/README.txt +24 -24
  4. data/Rakefile +92 -64
  5. data/app/config/app.yml +42 -43
  6. data/app/config/init.rb +26 -0
  7. data/benchmark/alloc_misc.rb +123 -0
  8. data/benchmark/discovery_latency.rb +67 -0
  9. data/benchmark/garbage_collection.rb +48 -0
  10. data/benchmark/genom.rb +31 -0
  11. data/benchmark/transactions.rb +62 -0
  12. data/bin/roby +1 -1
  13. data/bin/roby-log +16 -6
  14. data/doc/guide/.gitignore +2 -0
  15. data/doc/guide/config.yaml +34 -0
  16. data/doc/guide/ext/init.rb +14 -0
  17. data/doc/guide/ext/previous_next.rb +40 -0
  18. data/doc/guide/ext/rdoc_links.rb +33 -0
  19. data/doc/guide/index.rdoc +16 -0
  20. data/doc/guide/overview.rdoc +62 -0
  21. data/doc/guide/plan_modifications.rdoc +67 -0
  22. data/doc/guide/src/abstraction/achieve_with.page +8 -0
  23. data/doc/guide/src/abstraction/forwarding.page +8 -0
  24. data/doc/guide/src/abstraction/hierarchy.page +19 -0
  25. data/doc/guide/src/abstraction/index.page +28 -0
  26. data/doc/guide/src/abstraction/task_models.page +13 -0
  27. data/doc/guide/src/basics.template +6 -0
  28. data/doc/guide/src/basics/app.page +139 -0
  29. data/doc/guide/src/basics/code_examples.page +33 -0
  30. data/doc/guide/src/basics/dry.page +69 -0
  31. data/doc/guide/src/basics/errors.page +443 -0
  32. data/doc/guide/src/basics/events.page +179 -0
  33. data/doc/guide/src/basics/hierarchy.page +275 -0
  34. data/doc/guide/src/basics/index.page +11 -0
  35. data/doc/guide/src/basics/log_replay/goForward_1.png +0 -0
  36. data/doc/guide/src/basics/log_replay/goForward_2.png +0 -0
  37. data/doc/guide/src/basics/log_replay/goForward_3.png +0 -0
  38. data/doc/guide/src/basics/log_replay/goForward_4.png +0 -0
  39. data/doc/guide/src/basics/log_replay/goForward_5.png +0 -0
  40. data/doc/guide/src/basics/log_replay/hierarchy_error_1.png +0 -0
  41. data/doc/guide/src/basics/log_replay/hierarchy_error_2.png +0 -0
  42. data/doc/guide/src/basics/log_replay/hierarchy_error_3.png +0 -0
  43. data/doc/guide/src/basics/log_replay/plan_repair_1.png +0 -0
  44. data/doc/guide/src/basics/log_replay/plan_repair_2.png +0 -0
  45. data/doc/guide/src/basics/log_replay/plan_repair_3.png +0 -0
  46. data/doc/guide/src/basics/log_replay/plan_repair_4.png +0 -0
  47. data/doc/guide/src/basics/log_replay/roby_log_main_window.png +0 -0
  48. data/doc/guide/src/basics/log_replay/roby_log_relation_window.png +0 -0
  49. data/doc/guide/src/basics/log_replay/roby_replay_event_representation.png +0 -0
  50. data/doc/guide/src/basics/plan_objects.page +71 -0
  51. data/doc/guide/src/basics/relations_display.page +203 -0
  52. data/doc/guide/src/basics/roby_cycle_overview.png +0 -0
  53. data/doc/guide/src/basics/shell.page +102 -0
  54. data/doc/guide/src/basics/summary.page +32 -0
  55. data/doc/guide/src/basics/tasks.page +357 -0
  56. data/doc/guide/src/basics_shell_header.txt +16 -0
  57. data/doc/guide/src/cycle/cycle-overview.png +0 -0
  58. data/doc/guide/src/cycle/cycle-overview.svg +208 -0
  59. data/doc/guide/src/cycle/error_handling.page +168 -0
  60. data/doc/guide/src/cycle/error_instantaneous_repair.png +0 -0
  61. data/doc/guide/src/cycle/error_instantaneous_repair.svg +1224 -0
  62. data/doc/guide/src/cycle/garbage_collection.page +10 -0
  63. data/doc/guide/src/cycle/index.page +23 -0
  64. data/doc/guide/src/cycle/propagation.page +154 -0
  65. data/doc/guide/src/cycle/propagation_diamond.png +0 -0
  66. data/doc/guide/src/cycle/propagation_diamond.svg +1279 -0
  67. data/doc/guide/src/default.css +319 -0
  68. data/doc/guide/src/default.template +74 -0
  69. data/doc/guide/src/htmldoc.metainfo +20 -0
  70. data/doc/guide/src/htmldoc.virtual +18 -0
  71. data/doc/guide/src/images/bodybg.png +0 -0
  72. data/doc/guide/src/images/contbg.png +0 -0
  73. data/doc/guide/src/images/footerbg.png +0 -0
  74. data/doc/guide/src/images/gradient1.png +0 -0
  75. data/doc/guide/src/images/gradient2.png +0 -0
  76. data/doc/guide/src/index.page +7 -0
  77. data/doc/guide/src/introduction/index.page +29 -0
  78. data/doc/guide/src/introduction/install.page +133 -0
  79. data/doc/{papers.rdoc → guide/src/introduction/publications.page} +5 -2
  80. data/doc/{videos.rdoc → guide/src/introduction/videos.page} +4 -2
  81. data/doc/guide/src/plugins/fault_tolerance.page +44 -0
  82. data/doc/guide/src/plugins/index.page +11 -0
  83. data/doc/guide/src/plugins/subsystems.page +45 -0
  84. data/doc/guide/src/relations/dependency.page +89 -0
  85. data/doc/guide/src/relations/index.page +12 -0
  86. data/doc/misc/update_github +24 -0
  87. data/doc/tutorials/02-GoForward.rdoc +3 -3
  88. data/ext/graph/graph.cc +46 -0
  89. data/lib/roby.rb +57 -22
  90. data/lib/roby/app.rb +132 -112
  91. data/lib/roby/app/plugins/rake.rb +21 -0
  92. data/lib/roby/app/rake.rb +0 -7
  93. data/lib/roby/app/run.rb +1 -1
  94. data/lib/roby/app/scripts/distributed.rb +1 -2
  95. data/lib/roby/app/scripts/generate/bookmarks.rb +1 -1
  96. data/lib/roby/app/scripts/results.rb +2 -1
  97. data/lib/roby/app/scripts/run.rb +6 -2
  98. data/lib/roby/app/scripts/shell.rb +11 -11
  99. data/lib/roby/config.rb +1 -1
  100. data/lib/roby/decision_control.rb +62 -3
  101. data/lib/roby/distributed.rb +4 -0
  102. data/lib/roby/distributed/base.rb +8 -0
  103. data/lib/roby/distributed/communication.rb +12 -8
  104. data/lib/roby/distributed/connection_space.rb +61 -44
  105. data/lib/roby/distributed/distributed_object.rb +1 -1
  106. data/lib/roby/distributed/notifications.rb +22 -30
  107. data/lib/roby/distributed/peer.rb +13 -8
  108. data/lib/roby/distributed/proxy.rb +5 -5
  109. data/lib/roby/distributed/subscription.rb +4 -4
  110. data/lib/roby/distributed/transaction.rb +3 -3
  111. data/lib/roby/event.rb +176 -110
  112. data/lib/roby/exceptions.rb +12 -4
  113. data/lib/roby/execution_engine.rb +1604 -0
  114. data/lib/roby/external_process_task.rb +225 -0
  115. data/lib/roby/graph.rb +0 -6
  116. data/lib/roby/interface.rb +221 -137
  117. data/lib/roby/log/console.rb +5 -3
  118. data/lib/roby/log/data_stream.rb +94 -16
  119. data/lib/roby/log/dot.rb +8 -8
  120. data/lib/roby/log/event_stream.rb +13 -3
  121. data/lib/roby/log/file.rb +43 -18
  122. data/lib/roby/log/gui/basic_display_ui.rb +89 -0
  123. data/lib/roby/log/gui/chronicle_view_ui.rb +90 -0
  124. data/lib/roby/log/gui/data_displays.rb +4 -5
  125. data/lib/roby/log/gui/data_displays_ui.rb +146 -0
  126. data/lib/roby/log/gui/relations.rb +18 -18
  127. data/lib/roby/log/gui/relations_ui.rb +120 -0
  128. data/lib/roby/log/gui/relations_view_ui.rb +144 -0
  129. data/lib/roby/log/gui/replay.rb +41 -13
  130. data/lib/roby/log/gui/replay_controls.rb +3 -0
  131. data/lib/roby/log/gui/replay_controls.ui +133 -110
  132. data/lib/roby/log/gui/replay_controls_ui.rb +249 -0
  133. data/lib/roby/log/hooks.rb +19 -18
  134. data/lib/roby/log/logger.rb +7 -6
  135. data/lib/roby/log/notifications.rb +4 -4
  136. data/lib/roby/log/plan_rebuilder.rb +20 -22
  137. data/lib/roby/log/relations.rb +44 -16
  138. data/lib/roby/log/server.rb +1 -4
  139. data/lib/roby/log/timings.rb +88 -19
  140. data/lib/roby/plan-object.rb +135 -11
  141. data/lib/roby/plan.rb +408 -224
  142. data/lib/roby/planning/loops.rb +32 -25
  143. data/lib/roby/planning/model.rb +157 -51
  144. data/lib/roby/planning/task.rb +47 -20
  145. data/lib/roby/query.rb +128 -92
  146. data/lib/roby/relations.rb +254 -136
  147. data/lib/roby/relations/conflicts.rb +6 -9
  148. data/lib/roby/relations/dependency.rb +358 -0
  149. data/lib/roby/relations/ensured.rb +0 -1
  150. data/lib/roby/relations/error_handling.rb +0 -1
  151. data/lib/roby/relations/events.rb +0 -2
  152. data/lib/roby/relations/executed_by.rb +26 -11
  153. data/lib/roby/relations/planned_by.rb +14 -14
  154. data/lib/roby/robot.rb +46 -0
  155. data/lib/roby/schedulers/basic.rb +34 -0
  156. data/lib/roby/standalone.rb +4 -0
  157. data/lib/roby/standard_errors.rb +21 -15
  158. data/lib/roby/state/events.rb +5 -4
  159. data/lib/roby/support.rb +107 -6
  160. data/lib/roby/task-operations.rb +23 -19
  161. data/lib/roby/task.rb +522 -148
  162. data/lib/roby/task_index.rb +80 -0
  163. data/lib/roby/test/common.rb +283 -44
  164. data/lib/roby/test/distributed.rb +53 -37
  165. data/lib/roby/test/testcase.rb +9 -204
  166. data/lib/roby/test/tools.rb +3 -3
  167. data/lib/roby/transactions.rb +154 -111
  168. data/lib/roby/transactions/proxy.rb +40 -7
  169. data/manifest.xml +20 -0
  170. data/plugins/fault_injection/README.txt +0 -3
  171. data/plugins/fault_injection/Rakefile +2 -8
  172. data/plugins/fault_injection/app.rb +1 -1
  173. data/plugins/fault_injection/fault_injection.rb +3 -3
  174. data/plugins/fault_injection/test/test_fault_injection.rb +19 -25
  175. data/plugins/subsystems/README.txt +0 -3
  176. data/plugins/subsystems/Rakefile +2 -7
  177. data/plugins/subsystems/app.rb +27 -16
  178. data/plugins/subsystems/test/app/config/init.rb +3 -0
  179. data/plugins/subsystems/test/app/planners/main.rb +1 -1
  180. data/plugins/subsystems/test/app/tasks/services.rb +1 -1
  181. data/plugins/subsystems/test/test_subsystems.rb +23 -16
  182. data/test/distributed/test_communication.rb +32 -15
  183. data/test/distributed/test_connection.rb +28 -26
  184. data/test/distributed/test_execution.rb +59 -54
  185. data/test/distributed/test_mixed_plan.rb +34 -34
  186. data/test/distributed/test_plan_notifications.rb +26 -26
  187. data/test/distributed/test_protocol.rb +57 -48
  188. data/test/distributed/test_query.rb +11 -7
  189. data/test/distributed/test_remote_plan.rb +71 -71
  190. data/test/distributed/test_transaction.rb +50 -47
  191. data/test/mockups/external_process +28 -0
  192. data/test/planning/test_loops.rb +163 -119
  193. data/test/planning/test_model.rb +3 -3
  194. data/test/planning/test_task.rb +27 -7
  195. data/test/relations/test_conflicts.rb +3 -3
  196. data/test/relations/test_dependency.rb +324 -0
  197. data/test/relations/test_ensured.rb +2 -2
  198. data/test/relations/test_executed_by.rb +94 -19
  199. data/test/relations/test_planned_by.rb +11 -9
  200. data/test/suite_core.rb +6 -3
  201. data/test/suite_distributed.rb +1 -0
  202. data/test/suite_planning.rb +1 -0
  203. data/test/suite_relations.rb +2 -2
  204. data/test/tasks/test_external_process.rb +126 -0
  205. data/test/{test_thread_task.rb → tasks/test_thread_task.rb} +17 -20
  206. data/test/test_bgl.rb +21 -1
  207. data/test/test_event.rb +229 -155
  208. data/test/test_exceptions.rb +79 -80
  209. data/test/test_execution_engine.rb +987 -0
  210. data/test/test_gui.rb +1 -1
  211. data/test/test_interface.rb +11 -5
  212. data/test/test_log.rb +18 -7
  213. data/test/test_log_server.rb +1 -0
  214. data/test/test_plan.rb +229 -395
  215. data/test/test_query.rb +193 -35
  216. data/test/test_relations.rb +88 -8
  217. data/test/test_state.rb +55 -37
  218. data/test/test_support.rb +1 -1
  219. data/test/test_task.rb +371 -218
  220. data/test/test_testcase.rb +32 -16
  221. data/test/test_transactions.rb +211 -170
  222. data/test/test_transactions_proxy.rb +37 -19
  223. metadata +169 -71
  224. data/.gitignore +0 -29
  225. data/doc/styles/allison.css +0 -314
  226. data/doc/styles/allison.js +0 -316
  227. data/doc/styles/allison.rb +0 -276
  228. data/doc/styles/jamis.rb +0 -593
  229. data/lib/roby/control.rb +0 -746
  230. data/lib/roby/executives/simple.rb +0 -30
  231. data/lib/roby/propagation.rb +0 -562
  232. data/lib/roby/relations/hierarchy.rb +0 -239
  233. data/lib/roby/transactions/updates.rb +0 -139
  234. data/test/relations/test_hierarchy.rb +0 -158
  235. data/test/test_control.rb +0 -399
  236. data/test/test_propagation.rb +0 -210
@@ -1,33 +1,46 @@
1
- require 'roby/event'
2
- require 'roby/task'
3
- require 'roby/relations'
4
- require 'roby/basic_object'
5
-
6
1
  module Roby
7
- # A plan object is a collection of tasks and events. In plans, tasks can be
8
- # +missions+ (#missions, #mission?), which means that they are the final
9
- # goals of the system. A task is +useful+ if it helps into the realization
10
- # of a mission (it is linked to a mission by #hierarchy_relation or one
11
- # of the #service_relations), and is not useful otherwise. #garbage_collect
12
- # removes the tasks that are not useful.
2
+ # A plan object manages a collection of tasks and events.
3
+ #
4
+ # == Adding and removing objects from plans
5
+ # The #add, #add_mission and #add_permanent calls allow to add objects in
6
+ # plans. The #remove_object removes the same objects from the plan. Note
7
+ # that you should never remove objects yourself: a GC mechanism will do
8
+ # that properly for you, taking into account the consequences of the object
9
+ # removal.
13
10
  #
14
- # The following event hooks are defined:
15
- # * #inserted
16
- # * #discarded
17
- # * #discovered_tasks
18
- # * #discovered_events
19
- # * #replaced
20
- # * #added_transaction
21
- # * #removed_transaction
22
- # * #garbage
23
- # * #finalized_task
24
- # * #finalized_event
11
+ # To reduce the complexity of object management, a garbage collection
12
+ # mechanism is in place during the plan execution, stopping and removing
13
+ # tasks that are not useful anymore for the system's goals. This garbage
14
+ # collection mechanism runs at the end of the execution cycle. Once an
15
+ # object is not active (i.e. for a task, once it is stopped), the object is
16
+ # /finalized/ and either the #finalized_task or the #finalized_event hook is
17
+ # called.
18
+ #
19
+ # Two special kinds of objects exist in plans:
20
+ # * the +missions+ (#missions, #mission?, #add_mission and #unmark_mission) are the
21
+ # final goals of the system. A task is +useful+ if it helps into the
22
+ # Realization of a mission (it is the child of a mission through one of the
23
+ # task relations).
24
+ # * the +permanent+ objects (#add_permanent, #unmark_permanent, #permanent?, #permanent_tasks and
25
+ # #permanent_events) are plan objects that are not affected by the plan's
26
+ # garbage collection mechanism. As for missions, task that are useful to
27
+ # permanent tasks are also
25
28
  #
26
29
  class Plan < BasicObject
27
30
  extend Logger::Hierarchy
28
31
  extend Logger::Forward
29
32
 
30
- # The task index for this plan
33
+ # The ExecutionEngine object which handles this plan. The role of this
34
+ # object is to provide the event propagation, error propagation and
35
+ # garbage collection mechanisms for the execution.
36
+ attr_accessor :engine
37
+ # The DecisionControl object which is associated with this plan. This
38
+ # object's role is to handle the conflicts that can occur during event
39
+ # propagation.
40
+ def control; engine.control end
41
+
42
+ # The task index for this plan. This is a TaskIndex object which allows
43
+ # efficient resolving of queries.
31
44
  attr_reader :task_index
32
45
 
33
46
  # The list of tasks that are included in this plan
@@ -35,13 +48,16 @@ module Roby
35
48
  # The set of events that are defined by #known_tasks
36
49
  attr_reader :task_events
37
50
  # The list of the robot's missions. Do not change that set directly, use
38
- # #insert and #discard instead.
51
+ # #add_mission and #remove_mission instead.
39
52
  attr_reader :missions
53
+ # The list of tasks that are kept outside GC. Do not change that set
54
+ # directly, use #permanent and #auto instead.
55
+ attr_reader :permanent_tasks
40
56
  # The list of events that are not included in a task
41
57
  attr_reader :free_events
42
- # The list of tasks that are kept outside GC. Do not change that set
58
+ # The list of events that are kept outside GC. Do not change that set
43
59
  # directly, use #permanent and #auto instead.
44
- attr_reader :keepalive
60
+ attr_reader :permanent_events
45
61
 
46
62
  # A map of event => task repairs. Whenever an exception is found,
47
63
  # exception propagation checks that no repair is defined for that
@@ -70,9 +86,17 @@ module Roby
70
86
  end
71
87
  end
72
88
 
89
+ # The set of relations available for this plan
90
+ attr_reader :relations
91
+
92
+ # The propagation engine for this object. It is either nil (if no
93
+ # propagation engine is available) or self.
94
+ attr_reader :propagation_engine
95
+
73
96
  def initialize
74
97
  @missions = ValueSet.new
75
- @keepalive = ValueSet.new
98
+ @permanent_tasks = ValueSet.new
99
+ @permanent_events = ValueSet.new
76
100
  @known_tasks = ValueSet.new
77
101
  @free_events = ValueSet.new
78
102
  @task_events = ValueSet.new
@@ -80,16 +104,34 @@ module Roby
80
104
  @gc_quarantine = ValueSet.new
81
105
  @transactions = ValueSet.new
82
106
  @repairs = Hash.new
107
+ @exception_handlers = Array.new
108
+
109
+ @relations = TaskStructure.relations + EventStructure.relations
110
+ @structure_checks = relations.
111
+ map { |r| r.method(:check_structure) if r.respond_to?(:check_structure) }.
112
+ compact
83
113
 
84
114
  @task_index = Roby::TaskIndex.new
85
115
 
86
116
  super() if defined? super
87
117
  end
88
118
 
89
- def inspect
119
+ def inspect # :nodoc:
90
120
  "#<#{to_s}: missions=#{missions.to_s} tasks=#{known_tasks.to_s} events=#{free_events.to_s} transactions=#{transactions.to_s}>"
91
121
  end
92
122
 
123
+ # Calls the given block in the execution thread of this plan's engine.
124
+ # If there is no engine attached to this plan, yields immediately
125
+ #
126
+ # See ExecutionEngine#execute
127
+ def execute(&block)
128
+ if engine
129
+ engine.execute(&block)
130
+ else
131
+ yield
132
+ end
133
+ end
134
+
93
135
  # call-seq:
94
136
  # plan.partition_event_task(objects) => events, tasks
95
137
  #
@@ -119,51 +161,138 @@ module Roby
119
161
  ret
120
162
  end
121
163
 
122
- # Inserts a new mission in the plan. Its child tree is automatically
123
- # inserted too. Returns the plan
124
- def insert(task)
164
+ # Returns the set of stacked transaction, starting at +self+
165
+ def transaction_stack
166
+ plan_chain = [self]
167
+ while plan_chain.last.respond_to?(:plan)
168
+ plan_chain << plan_chain.last.plan
169
+ end
170
+ plan_chain
171
+ end
172
+
173
+ # Inserts a new mission in the plan.
174
+ #
175
+ # In the plan manager, missions are the tasks which constitute the
176
+ # robot's goal. This is the base for two things:
177
+ # * if a mission fails, the MissionFailedError is raised
178
+ # * the mission and all the tasks and events which are useful for it,
179
+ # are not removed automatically by the garbage collection mechanism.
180
+ # A task or event is <b>useful</b> if it is part of the child subgraph
181
+ # of the mission, i.e. if there is a path in the relation graphs where
182
+ # the mission is the source and the task is the target.
183
+ def add_mission(task)
125
184
  return if @missions.include?(task)
126
185
 
127
186
  @missions << task
128
- discover(task)
187
+ add(task)
129
188
  task.mission = true if task.self_owned?
130
- inserted(task)
189
+ added_mission(task)
131
190
  self
132
191
  end
192
+ def insert(task) # :nodoc:
193
+ Roby.warn_deprecated "#insert has been replaced by #add_mission"
194
+ add_mission(task)
195
+ end
133
196
  # Hook called when +tasks+ have been inserted in this plan
134
- def inserted(tasks); super if defined? super end
135
-
136
- # Forbid the GC to take out +task+
137
- def permanent(task)
138
- @keepalive << task
139
- discover(task)
140
- end
141
-
142
- # Make GC finalize +task+ if it is not useful anymore
143
- def auto(task); @keepalive.delete(task) end
144
-
145
- def edit
146
- if block_given?
147
- Roby::Control.synchronize do
148
- yield
149
- end
150
- end
151
- end
197
+ def added_mission(tasks)
198
+ super if defined? super
199
+ if respond_to?(:inserted)
200
+ Roby.warn_deprecated "the #inserted hook has been replaced by #added_mission"
201
+ inserted(tasks)
202
+ end
203
+ end
204
+ # Checks if +task+ is a mission of this plan
205
+ def mission?(task); @missions.include?(task) end
152
206
 
153
- def permanent?(task); @keepalive.include?(task) end
207
+ def remove_mission(task) # :nodoc:
208
+ Roby.warn_deprecated "#remove_mission renamed #unmark_mission"
209
+ unmark_mission(task)
210
+ end
154
211
 
155
212
  # Removes the task in +tasks+ from the list of missions
156
- def discard(task)
213
+ def unmark_mission(task)
157
214
  @missions.delete(task)
158
- discover(task)
215
+ add(task)
159
216
  task.mission = false if task.self_owned?
160
217
 
161
- discarded(task)
218
+ unmarked_mission(task)
162
219
  self
163
220
  end
164
221
  # Hook called when +tasks+ have been discarded from this plan
165
- def discarded(tasks); super if defined? super end
222
+ def unmarked_mission(task)
223
+ super if defined? super
224
+ if respond_to?(:removed_mission)
225
+ Roby.warn_deprecated "the #removed_mission hook has been replaced by #unmarked_mission"
226
+ removed_mission(task)
227
+ end
228
+ if respond_to?(:discarded)
229
+ Roby.warn_deprecated "the #discarded hook has been replaced by #unmarked_mission"
230
+ discarded(task)
231
+ end
232
+ end
233
+ def discard(task) # :nodoc:
234
+ Roby.warn_deprecated "#discard has been replaced by #unmark_mission"
235
+ unmark_mission(task)
236
+ end
237
+
238
+ # Adds +object+ in the list of permanent tasks. Permanent tasks are
239
+ # tasks that are not to be subject to the plan's garbage collection
240
+ # mechanism (i.e. they will not be removed even though they are not
241
+ # directly linked to a mission).
242
+ #
243
+ # #object is at the same time added in the plan, meaning that all the
244
+ # tasks and events related to it are added in the plan as well. See
245
+ # #add.
246
+ #
247
+ # Unlike missions, the failure of a permanent task does not constitute
248
+ # an error.
249
+ #
250
+ # See also #unmark_permanent and #permanent?
251
+ def add_permanent(object)
252
+ if object.kind_of?(Task)
253
+ @permanent_tasks << object
254
+ else
255
+ @permanent_events << object
256
+ end
257
+ add(object)
258
+ self
259
+ end
260
+
261
+ def permanent(object) # :nodoc:
262
+ Roby.warn_deprecated "#permanent has been replaced by #add_permanent"
263
+ add_permanent(object)
264
+ end
265
+
266
+ # Removes +object+ from the list of permanent objects. Permanent objects
267
+ # are protected from the plan's garbage collection. This does not remove
268
+ # the task/event itself from the plan.
269
+ #
270
+ # See also #add_permanent and #permanent?
271
+ def unmark_permanent(object)
272
+ @permanent_tasks.delete(object)
273
+ @permanent_events.delete(object)
274
+ end
275
+
276
+ def auto(obj) # :nodoc:
277
+ Roby.warn_deprecated "#auto has been replaced by #unmark_permanent"
278
+ unmark_permanent(obj)
279
+ end
280
+
281
+ # True if +obj+ is neither a permanent task nor a permanent object.
282
+ #
283
+ # See also #add_permanent and #unmark_permanent
284
+ def permanent?(obj); @permanent_tasks.include?(obj) || @permanent_events.include?(obj) end
166
285
 
286
+ def edit
287
+ if block_given?
288
+ Roby.synchronize do
289
+ yield
290
+ end
291
+ end
292
+ end
293
+
294
+ # True if this plan owns the given object, i.e. if all the owners of the
295
+ # object are also owners of the plan.
167
296
  def owns?(object)
168
297
  (object.owners - owners).empty?
169
298
  end
@@ -175,43 +304,55 @@ module Roby
175
304
  @free_events.each { |e| e.clear_relations }
176
305
  @free_events.clear
177
306
  @missions.clear
178
- @keepalive.clear
307
+ @permanent_tasks.clear
308
+ @permanent_events.clear
179
309
  end
180
310
 
181
- def handle_replace(from, to)
311
+ def handle_replace(from, to) # :nodoc:
182
312
  return if from == to
183
313
 
184
314
  # Check that +to+ is valid in all hierarchy relations where +from+ is a child
185
315
  if !to.fullfills?(*from.fullfilled_model)
186
- raise InvalidReplace.new(from, to, "to does not fullfills #{from.fullfilled_model}")
316
+ raise InvalidReplace.new(from, to), "task #{to} does not fullfill #{from.fullfilled_model}"
187
317
  end
188
318
 
189
319
  # Check that +to+ is in the same execution state than +from+
190
320
  if !to.compatible_state?(from)
191
- raise InvalidReplace.new(from, to, "state. #{from.running?}, #{to.running?}")
321
+ raise InvalidReplace.new(from, to), "cannot replace #{from} by #{to} as their state is incompatible: from is #{from.running?} and to is #{to.running?}"
192
322
  end
193
323
 
194
324
  # Swap the subplans of +from+ and +to+
195
325
  yield(from, to)
196
326
 
197
- replaced(from, to)
198
327
  if mission?(from)
199
- discard(from)
200
- insert(to)
328
+ unmark_mission(from)
329
+ add_mission(to)
201
330
  elsif permanent?(from)
202
- auto(from)
203
- permanent(to)
331
+ unmark_permanent(from)
332
+ add_permanent(to)
204
333
  else
205
- discover(to)
334
+ add(to)
206
335
  end
336
+ replaced(from, to)
207
337
  end
208
338
 
339
+ # Replace the task +from+ by +to+ in all relations +from+ is part of
340
+ # (including events).
341
+ #
342
+ # See also #replace
209
343
  def replace_task(from, to)
210
344
  handle_replace(from, to) do
211
345
  from.replace_by(to)
212
346
  end
213
347
  end
214
348
 
349
+ # Replace +from+ by +to+ in the plan, in all relations in which +from+
350
+ # and its events are /children/. It therefore replaces the subplan
351
+ # generated by +from+ (i.e. +from+ and all the tasks/events that can be
352
+ # reached by following the task and event relations) by the subplan
353
+ # generated by +to+.
354
+ #
355
+ # See also #replace_task
215
356
  def replace(from, to)
216
357
  handle_replace(from, to) do
217
358
  from.replace_subplan_by(to)
@@ -225,12 +366,24 @@ module Roby
225
366
  # plain Plan objects and false for transcations
226
367
  def executable?; true end
227
368
 
369
+ def discover(objects) # :nodoc:
370
+ Roby.warn_deprecated "#discover has been replaced by #add"
371
+ add(objects)
372
+ end
373
+
228
374
  # call-seq:
229
- # plan.discover([t1, t2, ...]) => plan
375
+ # plan.add(task) => plan
376
+ # plan.add(event) => plan
377
+ # plan.add([task, event, task2, ...]) => plan
378
+ # plan.add([t1, t2, ...]) => plan
230
379
  #
231
- # Updates Plan#known_tasks with either the child tree of the tasks in
232
- # +objects+
233
- def discover(objects)
380
+ # Adds the subplan of the given tasks and events into the plan.
381
+ #
382
+ # That means that it adds the listed tasks/events and the task/events
383
+ # that are reachable through any relations). The #added_events and
384
+ # #added_tasks hooks are called for the objects that were not in
385
+ # the plan.
386
+ def add(objects)
234
387
  event_seeds, tasks = partition_event_task(objects)
235
388
  event_seeds = (event_seeds || ValueSet.new).to_value_set
236
389
 
@@ -239,7 +392,7 @@ module Roby
239
392
  new_tasks = useful_task_component(nil, tasks, tasks)
240
393
  unless new_tasks.empty?
241
394
  old_task_events = task_events.dup
242
- new_tasks = discover_task_set(new_tasks)
395
+ new_tasks = add_task_set(new_tasks)
243
396
  event_seeds.merge(task_events - old_task_events)
244
397
  end
245
398
  end
@@ -257,17 +410,17 @@ module Roby
257
410
  end
258
411
  end
259
412
 
260
- discover_event_set(events - task_events - free_events)
413
+ add_event_set(events - task_events - free_events)
261
414
  end
262
415
 
263
416
  self
264
417
  end
265
418
 
266
- # Add +events+ to the set of known events and call discovered_events
419
+ # Add +events+ to the set of known events and call added_events
267
420
  # for the new events
268
421
  #
269
- # This is for internal use, use #discover instead
270
- def discover_event_set(events)
422
+ # This is for internal use, use #add instead
423
+ def add_event_set(events)
271
424
  events = events.difference(free_events)
272
425
  events.delete_if do |e|
273
426
  if !e.root_object?
@@ -280,17 +433,17 @@ module Roby
280
433
 
281
434
  unless events.empty?
282
435
  free_events.merge(events)
283
- discovered_events(events)
436
+ added_events(events)
284
437
  end
285
438
 
286
439
  events
287
440
  end
288
441
 
289
- # Add +tasks+ to the set of known tasks and call discovered_tasks for
442
+ # Add +tasks+ to the set of known tasks and call added_tasks for
290
443
  # the new tasks
291
444
  #
292
- # This is for internal use, use #discover instead
293
- def discover_task_set(tasks)
445
+ # This is for internal use, use #add instead
446
+ def add_task_set(tasks)
294
447
  tasks = tasks.difference(known_tasks)
295
448
  for t in tasks
296
449
  t.plan = self
@@ -298,7 +451,7 @@ module Roby
298
451
  task_index.add t
299
452
  end
300
453
  known_tasks.merge tasks
301
- discovered_tasks(tasks)
454
+ added_tasks(tasks)
302
455
 
303
456
  for t in tasks
304
457
  t.instantiate_model_event_relations
@@ -306,18 +459,45 @@ module Roby
306
459
  tasks
307
460
  end
308
461
 
309
- # DEPRECATED. Use #discovered_tasks instead
310
- def discovered(tasks); super if defined? super end
311
- # Hook called when new tasks have been discovered in this plan
312
- def discovered_tasks(tasks)
313
- discovered(tasks)
314
- super if defined? super
315
- end
462
+ def added_tasks(tasks)
463
+ if respond_to?(:discovered)
464
+ Roby.warn_deprecated "the #discovered hook is deprecated, use #added_tasks instead"
465
+ discovered(tasks)
466
+ end
467
+ if respond_to?(:discovered_tasks)
468
+ Roby.warn_deprecated "the #discovered_tasks hook is deprecated, use #added_tasks instead"
469
+ discovered_tasks(tasks)
470
+ end
471
+
472
+ if engine
473
+ engine.event_ordering.clear
474
+ end
475
+ super if defined? super
476
+ end
477
+
316
478
  # Hook called when new events have been discovered in this plan
317
- def discovered_events(events)
479
+ def added_events(events)
480
+ if engine
481
+ engine.event_ordering.clear
482
+ end
483
+
484
+ if respond_to?(:discovered_events)
485
+ Roby.warn_deprecated "the #discovered_events hook has been replaced by #added_events"
486
+ discovered_events(events)
487
+ end
318
488
  super if defined? super
319
489
  end
320
490
 
491
+ # Creates a new transaction and yields it. Ensures that the transaction
492
+ # is discarded if the block returns without having committed it.
493
+ def in_transaction
494
+ yield(trsc = Transaction.new(self))
495
+
496
+ ensure
497
+ if trsc && !trsc.finalized?
498
+ trsc.discard_transaction
499
+ end
500
+ end
321
501
  # Hook called when a new transaction has been built on top of this plan
322
502
  def added_transaction(trsc); super if defined? super end
323
503
  # Removes the transaction +trsc+ from the list of known transactions
@@ -334,7 +514,7 @@ module Roby
334
514
  def useful_task_component(complete_set, useful_set, seeds)
335
515
  old_useful_set = useful_set.dup
336
516
  for rel in TaskStructure.relations
337
- next unless rel.root_relation?
517
+ next if !rel.root_relation?
338
518
  for subgraph in rel.generated_subgraphs(seeds, false)
339
519
  useful_set.merge(subgraph)
340
520
  end
@@ -356,17 +536,17 @@ module Roby
356
536
  # Remove all missions that are finished
357
537
  for finished_mission in (@missions & task_index.by_state[:finished?])
358
538
  if !task_index.repaired_tasks.include?(finished_mission)
359
- discard(finished_mission)
539
+ unmark_mission(finished_mission)
360
540
  end
361
541
  end
362
- for finished_permanent in (@keepalive & task_index.by_state[:finished?])
542
+ for finished_permanent in (@permanent_tasks & task_index.by_state[:finished?])
363
543
  if !task_index.repaired_tasks.include?(finished_permanent)
364
- auto(finished_permanent)
544
+ unmark_permanent(finished_permanent)
365
545
  end
366
546
  end
367
547
 
368
548
  # Create the set of tasks which must be kept as-is
369
- seeds = @missions | @keepalive
549
+ seeds = @missions | @permanent_tasks
370
550
  for trsc in transactions
371
551
  seeds.merge trsc.proxy_objects.keys.to_value_set
372
552
  end
@@ -446,7 +626,7 @@ module Roby
446
626
  # 'useful' when they are chained to a task.
447
627
  def useful_events
448
628
  return ValueSet.new if free_events.empty?
449
- (free_events & useful_event_component(ValueSet.new))
629
+ (free_events & useful_event_component(permanent_events.dup))
450
630
  end
451
631
 
452
632
  # The set of events that can be removed from the plan
@@ -462,38 +642,36 @@ module Roby
462
642
 
463
643
  # Checks if +task+ is included in this plan
464
644
  def include?(object); @known_tasks.include?(object) || @free_events.include?(object) end
465
- # Checks if +task+ is a mission of this plan
466
- def mission?(task); @missions.include?(task) end
467
645
  # Count of tasks in this plan
468
646
  def size; @known_tasks.size end
469
647
  # Returns true if there is no task in this plan
470
648
  def empty?; @known_tasks.empty? end
471
649
  # Iterates on all tasks
472
650
  def each_task; @known_tasks.each { |t| yield(t) } end
473
-
474
- # Install a plan repair for +failure_point+ with +task+. If +task+ is
475
- # pending, it is started.
651
+
652
+ # Install a plan repair for +failure_point+ with +task+. A plan repair
653
+ # is a task which, during its lifetime, is supposed to fix the problem
654
+ # encountered at +failure_point+
655
+ #
656
+ # +failure_point+ is an Event object which represents the event causing
657
+ # the problem.
476
658
  #
477
659
  # See also #repairs and #remove_repair
478
660
  def add_repair(failure_point, task)
479
661
  if !failure_point.kind_of?(Event)
480
662
  raise TypeError, "failure point #{failure_point} should be an event"
481
663
  elsif task.plan && task.plan != self
482
- raise ArgumentError, "wrong plan: #{task} is in #{task.plan}, not #{self}"
664
+ raise ArgumentError, "wrong plan: #{task} is in #{task.plan}, not #{plan}"
483
665
  elsif repairs.has_key?(failure_point)
484
666
  raise ArgumentError, "there is already a plan repair defined for #{failure_point}: #{repairs[failure_point]}"
485
667
  elsif !task.plan
486
- discover(task)
668
+ add(task)
487
669
  end
488
670
 
489
671
  repairs[failure_point] = task
490
672
  if failure_point.generator.respond_to?(:task)
491
673
  task_index.repaired_tasks << failure_point.generator.task
492
674
  end
493
-
494
- if task.pending?
495
- Roby.once { task.start! }
496
- end
497
675
  end
498
676
 
499
677
  # Removes +task+ from the set of active plan repairs.
@@ -544,11 +722,13 @@ module Roby
544
722
  # Otherwise, raises ArgumentError.
545
723
  #
546
724
  # This method is provided for consistency with Transaction#[]
547
- def [](object)
548
- if object.plan != self
549
- raise ArgumentError, "#{object} is not from #{plan}"
550
- elsif !object.plan
551
- discover(object)
725
+ def [](object, create = true)
726
+ if !object.plan && !object.finalized?
727
+ add(object)
728
+ elsif object.finalized? && create
729
+ raise ArgumentError, "#{object} is has been finalized, and can't be reused"
730
+ elsif object.plan != self
731
+ raise ArgumentError, "#{object} is not from #{self}"
552
732
  end
553
733
  object
554
734
  end
@@ -561,109 +741,13 @@ module Roby
561
741
  end
562
742
  end
563
743
 
564
- # Kills and removes all unneeded tasks
565
- def garbage_collect(force_on = nil)
566
- if force_on && !force_on.empty?
567
- force_gc.merge(force_on.to_value_set)
568
- end
569
-
570
- # The set of tasks for which we queued stop! at this cycle
571
- # #finishing? is false until the next event propagation cycle
572
- finishing = ValueSet.new
573
- did_something = true
574
- while did_something
575
- did_something = false
576
-
577
- tasks = unneeded_tasks | force_gc
578
- local_tasks = self.local_tasks & tasks
579
- remote_tasks = tasks - local_tasks
580
-
581
- # Remote tasks are simply removed, regardless of other concerns
582
- for t in remote_tasks
583
- Plan.debug { "GC: removing the remote task #{t}" }
584
- remove_object(t)
585
- end
586
-
587
- break if local_tasks.empty?
588
-
589
- if local_tasks.all? { |t| t.pending? || t.finished? }
590
- local_tasks.each do |t|
591
- Plan.debug { "GC: #{t} is not running, removed" }
592
- garbage(t)
593
- remove_object(t)
594
- end
595
- break
596
- end
597
-
598
- # Mark all root local_tasks as garbage
599
- roots = nil
600
- 2.times do |i|
601
- roots = local_tasks.find_all do |t|
602
- if t.root?
603
- garbage(t)
604
- true
605
- else
606
- Plan.debug { "GC: ignoring #{t}, it is not root" }
607
- false
608
- end
609
- end
610
-
611
- break if i == 1 || !roots.empty?
612
-
613
- # There is a cycle somewhere. Try to break it by removing
614
- # weak relations within elements of local_tasks
615
- Plan.debug "cycle found, removing weak relations"
616
-
617
- local_tasks.each do |t|
618
- next if t.root?
619
- t.each_graph do |rel|
620
- rel.remove(t) if rel.weak?
621
- end
622
- end
623
- end
624
-
625
- (roots.to_value_set - finishing - gc_quarantine).each do |local_task|
626
- if local_task.pending?
627
- Plan.info "GC: removing pending task #{local_task}"
628
- remove_object(local_task)
629
- did_something = true
630
- elsif local_task.starting?
631
- # wait for task to be started before killing it
632
- Plan.debug { "GC: #{local_task} is starting" }
633
- elsif local_task.finished?
634
- Plan.debug { "GC: #{local_task} is not running, removed" }
635
- remove_object(local_task)
636
- did_something = true
637
- elsif !local_task.finishing?
638
- if local_task.event(:stop).controlable?
639
- Plan.debug { "GC: queueing #{local_task}/stop" }
640
- if !local_task.respond_to?(:stop!)
641
- Plan.fatal "something fishy: #{local_task}/stop is controlable but there is no #stop! method"
642
- gc_quarantine << local_task
643
- else
644
- finishing << local_task
645
- Roby::Control.once do
646
- Plan.debug { "GC: stopping #{local_task}" }
647
- local_task.stop!(nil)
648
- end
649
- end
650
- else
651
- Plan.warn "GC: ignored #{local_task}, it cannot be stopped"
652
- gc_quarantine << local_task
653
- end
654
- elsif local_task.finishing?
655
- Plan.debug { "GC: waiting for #{local_task} to finish" }
656
- else
657
- Plan.warn "GC: ignored #{local_task}"
658
- end
659
- end
660
- end
661
-
662
- unneeded_events.each do |event|
663
- remove_object(event)
664
- end
665
- end
744
+ def discard_modifications(object)
745
+ remove_object(object)
746
+ end
666
747
 
748
+ # Remove +object+ from this plan. You usually don't have to do that
749
+ # manually. Object removal is handled by the plan's garbage collection
750
+ # mechanism.
667
751
  def remove_object(object)
668
752
  if !object.root_object?
669
753
  raise ArgumentError, "cannot remove #{object} which is a non-root object"
@@ -687,8 +771,12 @@ module Roby
687
771
 
688
772
  @free_events.delete(object)
689
773
  @missions.delete(object)
774
+ if object.respond_to? :mission=
775
+ object.mission = false
776
+ end
690
777
  @known_tasks.delete(object)
691
- @keepalive.delete(object)
778
+ @permanent_tasks.delete(object)
779
+ @permanent_events.delete(object)
692
780
  force_gc.delete(object)
693
781
 
694
782
  object.plan = nil
@@ -714,11 +802,6 @@ module Roby
714
802
  self
715
803
  end
716
804
 
717
- # Backward compatibility
718
- def remove_task(t) # :nodoc:
719
- remove_object(t)
720
- end
721
-
722
805
  # Hook called when +task+ is marked as garbage. It will be garbage
723
806
  # collected as soon as possible
724
807
  def garbage(task)
@@ -741,22 +824,123 @@ module Roby
741
824
  def finalized(task) # :nodoc:
742
825
  super if defined? super
743
826
  end
827
+
744
828
  # Hook called when +task+ has been removed from this plan
745
829
  def finalized_task(task)
830
+ finalized_transaction_object(task) { |trsc, proxy| trsc.finalized_plan_task(proxy) }
746
831
  super if defined? super
747
832
  finalized(task)
748
833
  end
834
+
749
835
  # Hook called when +event+ has been removed from this plan
750
- def finalized_event(event); super if defined? super end
836
+ def finalized_event(event)
837
+ if engine && executable?
838
+ engine.finalized_event(event)
839
+ end
840
+ finalized_transaction_object(event) { |trsc, proxy| trsc.finalized_plan_event(proxy) }
841
+ super if defined? super
842
+ end
843
+
844
+ # Generic filter which checks if +object+ is included in one of the
845
+ # transactions of this plan. If it is the case, it yields the
846
+ # transaction and the associated proxy
847
+ def finalized_transaction_object(object)
848
+ return unless object.root_object?
849
+ for trsc in transactions
850
+ next unless trsc.proxying?
851
+
852
+ if proxy = trsc.wrap(object, false)
853
+ yield(trsc, proxy)
854
+ end
855
+ end
856
+ end
857
+
858
+ # Replace +task+ with a fresh copy of itself.
859
+ #
860
+ # The new task takes the place of the old one in the plan: any relation
861
+ # that was going to/from +task+ or one of its events is removed, and the
862
+ # corresponding one is created, but this time involving the newly
863
+ # created task.
864
+ def recreate(task)
865
+ new_task = task.create_fresh_copy
866
+ replace_task(task, new_task)
867
+ new_task
868
+ end
751
869
 
752
- # Replace +task+ with a fresh copy of itself
870
+ # Replace +task+ with a fresh copy of itself and start it.
871
+ #
872
+ # See #recreate for details about the new task.
753
873
  def respawn(task)
754
- new_task = task.class.new(task.arguments.dup)
874
+ new = recreate(task)
875
+ engine.once { new.start!(nil) }
876
+ new
877
+ end
878
+
879
+ # The set of blocks that should be called to check the structure of the
880
+ # plan. See also Plan.structure_checks.
881
+ attr_reader :structure_checks
882
+
883
+ @structure_checks = Array.new
884
+ class << self
885
+ # A set of structure checking procedures that must be performed on all plans
886
+ attr_reader :structure_checks
887
+ end
888
+
889
+ # Get all missions that have failed
890
+ def self.check_failed_missions(plan)
891
+ result = []
892
+ for task in plan.missions
893
+ result << MissionFailedError.new(task) if task.failed?
894
+ end
895
+ result
896
+ end
897
+ structure_checks << method(:check_failed_missions)
898
+
899
+ # Perform the structure checking step by calling the procs registered
900
+ # in #structure_checks and Plan.structure_checks. These procs are
901
+ # supposed to return a collection of exception objects, or nil if no
902
+ # error has been found
903
+ def check_structure
904
+ # Do structure checking and gather the raised exceptions
905
+ exceptions = {}
906
+ for prc in (Plan.structure_checks + structure_checks)
907
+ begin
908
+ new_exceptions = prc.call(self)
909
+ rescue Exception => e
910
+ if engine
911
+ engine.add_framework_error(e, 'structure checking')
912
+ else
913
+ raise
914
+ end
915
+ end
916
+ next unless new_exceptions
755
917
 
756
- replace_task(task, new_task)
757
- Control.once { new_task.start!(nil) }
758
- new_task
918
+ [*new_exceptions].each do |e, tasks|
919
+ e = ExecutionEngine.to_execution_exception(e)
920
+ exceptions[e] = tasks
921
+ end
922
+ end
923
+ exceptions
759
924
  end
925
+
926
+
927
+ include Roby::ExceptionHandlingObject
928
+
929
+ attr_reader :exception_handlers
930
+ def each_exception_handler(&iterator); exception_handlers.each(&iterator) end
931
+ def on_exception(*matchers, &handler)
932
+ check_arity(handler, 2)
933
+ exception_handlers.unshift [matchers, handler]
934
+ end
935
+ end
936
+
937
+ class << self
938
+ # Returns the main plan
939
+ attr_reader :plan
760
940
  end
941
+
942
+ # Defines a global exception handler on the main plan.
943
+ # See also Plan#on_exception
944
+ def self.on_exception(*matchers, &handler); Roby.plan.on_exception(*matchers, &handler) end
761
945
  end
762
946