roby 0.7.3 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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