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,17 +1,14 @@
1
- require 'roby/plan-object'
2
- require 'roby/exceptions'
3
- require 'roby/event'
4
- require 'utilrb/module/attr_predicate'
5
-
6
1
  module Roby
7
2
  class TaskModelTag < Module
8
3
  module ClassExtension
9
4
  # Returns the list of static arguments required by this task model
10
5
  def arguments(*new_arguments)
11
6
  new_arguments.each do |arg_name|
7
+ arg_name = arg_name.to_sym
12
8
  argument_set << arg_name.to_sym
13
9
  unless method_defined?(arg_name)
14
10
  define_method(arg_name) { arguments[arg_name] }
11
+ define_method("#{arg_name}=") { |value| arguments[arg_name] = value }
15
12
  end
16
13
  end
17
14
 
@@ -19,6 +16,27 @@ module Roby
19
16
  end
20
17
  # Declares a set of arguments required by this task model
21
18
  def argument(*args); arguments(*args) end
19
+ # The part of +arguments+ that is meaningful for this task model
20
+ def meaningful_arguments(arguments)
21
+ self_arguments = self.arguments.to_set
22
+ arguments.to_hash.delete_if do |key, _|
23
+ !self_arguments.include?(key)
24
+ end
25
+ end
26
+
27
+ # Checks if this model fullfills everything in +models+
28
+ def fullfills?(models)
29
+ if !models.respond_to?(:each)
30
+ models = [models]
31
+ end
32
+
33
+ for tag in models
34
+ if !has_ancestor?(tag)
35
+ return false
36
+ end
37
+ end
38
+ true
39
+ end
22
40
  end
23
41
  include TaskModelTag::ClassExtension
24
42
 
@@ -46,6 +64,8 @@ module Roby
46
64
  # The task which fired this event
47
65
  attr_reader :task
48
66
 
67
+ def model; self.class end
68
+
49
69
  def initialize(task, generator, propagation_id, context, time = Time.now)
50
70
  @task = task
51
71
  @terminal_flag = generator.terminal_flag
@@ -88,7 +108,7 @@ module Roby
88
108
  # responds to #call
89
109
  def self.controlable?; respond_to?(:call) end
90
110
  # If the event is controlable
91
- def controlable?; self.class.controlable? end
111
+ def controlable?; model.controlable? end
92
112
  class << self
93
113
  # Called by Task.update_terminal_flag to update the flag
94
114
  attr_writer :terminal
@@ -104,7 +124,7 @@ module Roby
104
124
  # The event symbol
105
125
  def self.symbol; @symbol end
106
126
  # The event symbol
107
- def symbol; self.class.symbol end
127
+ def symbol; model.symbol end
108
128
  end
109
129
 
110
130
  # A task event model bound to a particular task instance
@@ -140,9 +160,22 @@ module Roby
140
160
  # by task.plan=. It is redefined here for performance reasons.
141
161
  attr_accessor :plan
142
162
 
143
- # Fire the event
163
+ # Check that the event can be emitted
164
+ def emitting(context)
165
+ task.emitting_event(self, context)
166
+ super if defined? super
167
+ end
168
+
144
169
  def fire(event)
145
170
  task.fire_event(event)
171
+ super if defined? super
172
+ end
173
+
174
+ def emit_failed(*reason)
175
+ if symbol == :start
176
+ task.failed_to_start = true
177
+ task.plan.task_index.set_state(task, :failed?)
178
+ end
146
179
  super
147
180
  end
148
181
 
@@ -153,13 +186,13 @@ module Roby
153
186
  super if defined? super
154
187
  if task.finished? && !terminal?
155
188
  raise CommandFailed.new(nil, self),
156
- "#{symbol}!(#{context})) called by #{Propagation.sources} but the task has finished. Task has been terminated by #{task.event(:stop).history.first.sources}."
189
+ "#{symbol}!(#{context}) called by #{plan.engine.propagation_sources.to_a} but the task has finished. Task has been terminated by #{task.event(:stop).history.first.sources}."
157
190
  elsif task.pending? && symbol != :start
158
191
  raise CommandFailed.new(nil, self),
159
- "#{symbol}!(#{context})) called by #{Propagation.sources} but the task is not running"
192
+ "#{symbol}!(#{context}) called by #{plan.engine.propagation_sources.to_a} but the task has never been started"
160
193
  elsif task.running? && symbol == :start
161
194
  raise CommandFailed.new(nil, self),
162
- "#{symbol}!(#{context})) called by #{Propagation.sources} but the task is already running. Task has been started by #{task.event(:start).history.first.sources}."
195
+ "#{symbol}!(#{context}) called by #{plan.engine.propagation_sources.to_a} but the task is already running. Task has been started by #{task.event(:start).history.first.sources}."
163
196
  end
164
197
  end
165
198
 
@@ -219,7 +252,7 @@ module Roby
219
252
  task.update_terminal_flag
220
253
  end
221
254
  end
222
- def new(context); event_model.new(task, self, Propagation.propagation_id, context) end
255
+ def new(context); event_model.new(task, self, plan.engine.propagation_id, context) end
223
256
 
224
257
  def to_s
225
258
  "#{task}/#{symbol}"
@@ -238,8 +271,8 @@ module Roby
238
271
  end
239
272
 
240
273
  if child_task
241
- unless task.realized_by?(child_task)
242
- task.realized_by child_task,
274
+ unless task.depends_on?(child_task, false)
275
+ task.depends_on child_task,
243
276
  :success => [child_event.symbol],
244
277
  :remove_when_done => true
245
278
  end
@@ -249,6 +282,13 @@ module Roby
249
282
  end
250
283
  end
251
284
 
285
+ # Refines exceptions that may be thrown by #call_without_propagation
286
+ def call_without_propagation(context)
287
+ super
288
+ rescue EventNotExecutable => e
289
+ refine_exception(e)
290
+ end
291
+
252
292
  # Checks that the event can be called. Raises various exception
253
293
  # when it is not the case.
254
294
  def check_call_validity
@@ -268,23 +308,29 @@ module Roby
268
308
 
269
309
  def refine_exception (e)
270
310
  if task.partially_instanciated?
271
- raise EventNotExecutable.new(self), "#{name}! called on #{task} which is partially instanciated\n" +
311
+ raise EventNotExecutable.new(self), "#{symbol}! called on #{task} which is partially instanciated\n" +
272
312
  "The following arguments were not set: \n" +
273
313
  task.list_unset_arguments.map {|n| "\t#{n}"}.join("\n")+"\n"
274
314
  #
275
315
  elsif !plan
276
- raise EventNotExecutable.new(self), "#{name}! called on #{task} but the task is in no plan"
316
+ raise EventNotExecutable.new(self), "#{symbol}! called on #{task} but the task is in no plan"
277
317
  elsif !plan.executable?
278
- raise EventNotExecutable.new(self), "#{name}! called on #{task} but the plan is not executable"
318
+ raise EventNotExecutable.new(self), "#{symbol}! called on #{task} but the plan is not executable"
279
319
  elsif task.abstract?
280
- raise EventNotExecutable.new(self), "#{name}! called on #{task} but the task is abstract"
320
+ raise EventNotExecutable.new(self), "#{symbol}! called on #{task} but the task is abstract"
281
321
  else
282
- raise EventNotExecutable.new(self), "#{name}! called on #{task} which is not executable: #{e.message}"
322
+ raise EventNotExecutable.new(self), "#{symbol}! called on #{task} which is not executable: #{e.message}"
283
323
  end
284
324
  end
285
325
 
286
326
  end
287
327
 
328
+ # Class that handles task arguments. They are handled specially as the
329
+ # arguments cannot be overwritten and can not be changed by a task that is
330
+ # not owned.
331
+ #
332
+ # Moreover, two hooks #updating and #updated allow to hook into the argument
333
+ # update system.
288
334
  class TaskArguments < Hash
289
335
  private :delete, :delete_if
290
336
 
@@ -305,6 +351,7 @@ module Roby
305
351
 
306
352
  alias :update! :[]=
307
353
  def []=(key, value)
354
+ key = key.to_sym if key.respond_to?(:to_str)
308
355
  if writable?(key)
309
356
  if !task.read_write?
310
357
  raise OwnershipError, "cannot change the argument set of a task which is not owned #{task} is owned by #{task.owners} and #{task.plan} by #{task.plan.owners}"
@@ -320,6 +367,11 @@ module Roby
320
367
  def updating; super if defined? super end
321
368
  def updated; super if defined? super end
322
369
 
370
+ def [](key)
371
+ key = key.to_sym if key.respond_to?(:to_str)
372
+ super(key)
373
+ end
374
+
323
375
  alias :do_merge! :merge!
324
376
  def merge!(hash)
325
377
  super do |key, old, new|
@@ -363,7 +415,7 @@ module Roby
363
415
  # end
364
416
  #
365
417
  # event :other_event do |context|
366
- # Roby.once { emit :other_event }
418
+ # engine.once { emit :other_event }
367
419
  # end
368
420
  # end
369
421
  #
@@ -371,6 +423,13 @@ module Roby
371
423
  # immediately emitted, and in the second case it will be emitted at the
372
424
  # beginning of the next execution cycle.
373
425
  #
426
+ # === Task relations
427
+ #
428
+ # Task relations are defined in the TaskStructure RelationSpace instance.
429
+ # See TaskStructure documentation for the list of special methods defined
430
+ # by the various graphs, and the TaskStructure namespace for the name and
431
+ # purpose of the various relation graphs themselves.
432
+ #
374
433
  # === Executability
375
434
  #
376
435
  # By default, a task is not executable, which means that no event command
@@ -420,7 +479,7 @@ module Roby
420
479
  class_eval do
421
480
  # Remove event models
422
481
  events.each_key do |ev_symbol|
423
- remove_const ev_symbol.to_s.camelize
482
+ remove_const ev_symbol.to_s.camelcase(true)
424
483
  end
425
484
 
426
485
  [@events, @signal_sets, @forwarding_sets, @causal_link_sets,
@@ -461,6 +520,123 @@ module Roby
461
520
  EOD
462
521
  end
463
522
 
523
+ ##
524
+ # :singleton-method: signals
525
+ # :call-seq:
526
+ # task_model.signals(event_model) => [target_event_models]
527
+ #
528
+ # Returns the set of model-level signal targets for the given event.
529
+
530
+ ##
531
+ # :singleton-method: each_signal
532
+ # :call-seq:
533
+ # task_model.each_signal(event_model) do |target_event_model|
534
+ # end
535
+ #
536
+ # Enumerates the set of model-level causal links that are defined for
537
+ # the given event. It enumerates all the ones defined on this model
538
+ # (using Task::signal) and also on its parent classes.
539
+
540
+ ##
541
+ # :method: each_signal
542
+ # :call-seq:
543
+ # task.each_signal(event_model) do |target_event_model|
544
+ # end
545
+ #
546
+ # Enumerates the set of model-level causal links that are defined for
547
+ # the given event. It enumerates all the ones defined on this task's model
548
+ # (using Task::signal) and also on its parent classes.
549
+
550
+
551
+
552
+
553
+ ##
554
+ # :singleton-method: forwardings
555
+ # :call-seq:
556
+ # task_model.forwardings(event_model) => [target_event_models]
557
+ #
558
+ # Returns the set of model-level forwarding targets for the given event.
559
+
560
+ ##
561
+ # :singleton-method: each_forwarding
562
+ # :call-seq:
563
+ # task_model.each_forwarding(event_model) do |target_event_model|
564
+ # end
565
+ #
566
+ # Enumerates the set of model-level causal links that are defined for
567
+ # the given event. It enumerates all the ones defined on this model
568
+ # (using Task::forward) and also on its parent classes.
569
+
570
+ ##
571
+ # :method: each_forwarding
572
+ # :call-seq:
573
+ # task.each_forwarding(event_model) do |target_event_model|
574
+ # end
575
+ #
576
+ # Enumerates the set of model-level causal links that are defined for
577
+ # the given event. It enumerates all the ones defined on this task's
578
+ # model (using Task::forward) and also on its parent classes.
579
+
580
+
581
+
582
+
583
+ ##
584
+ # :singleton-method: causal_links
585
+ # :call-seq:
586
+ # task_model.causal_links(event_model) => [target_event_models]
587
+ #
588
+ # Returns the set of model-level causal_link targets for the given event.
589
+
590
+ ##
591
+ # :singleton-method: each_causal_link
592
+ # :call-seq:
593
+ # task_model.each_causal_link(event_model) do |target_event_model|
594
+ # end
595
+ #
596
+ # Enumerates the set of model-level causal links that are defined for
597
+ # the given event. It enumerates all the ones defined on this model
598
+ # (using Task::causal_link) and also on its parent classes.
599
+
600
+ ##
601
+ # :method: each_causal_link
602
+ # :call-seq:
603
+ # task.each_causal_link(event_model) do |target_event_model|
604
+ # end
605
+ #
606
+ # Enumerates the set of model-level causal links that are defined for
607
+ # the given event. It enumerates all the ones defined on this task's model
608
+ # (using Task::causal_link) and also on its parent classes.
609
+
610
+
611
+
612
+
613
+ ##
614
+ # :singleton-method: handlers
615
+ # :call-seq:
616
+ # task_model.handlers(event_model) => [target_event_models]
617
+ #
618
+ # Returns the set of model-level event handlers for the given event.
619
+
620
+ ##
621
+ # :singleton-method: each_handler
622
+ # :call-seq:
623
+ # task_model.each_handler(event_model) do |target_event_model|
624
+ # end
625
+ #
626
+ # Enumerates the set of model-level event handlers that are defined for
627
+ # the given event. It enumerates all handlers defined on the instance's
628
+ # task model and its parent classes.
629
+
630
+ ##
631
+ # :method: each_handler
632
+ # :call-seq:
633
+ # task.each_handler(event_model) do |target_event_model|
634
+ # end
635
+ #
636
+ # Enumerates the set of model-level event handlers that are defined for
637
+ # the given event. It enumerates all handlers defined on the instance's
638
+ # task model and its parent classes.
639
+
464
640
  model_attribute_list('signal')
465
641
  model_attribute_list('forwarding')
466
642
  model_attribute_list('causal_link')
@@ -469,13 +645,17 @@ module Roby
469
645
 
470
646
  # The task arguments as symbol => value associative container
471
647
  attr_reader :arguments
472
- # The part of +arguments+ that is meaningful for this task model
648
+
649
+ # The part of +arguments+ that is meaningful for this task model. I.e.
650
+ # it returns the set of elements in the +arguments+ property that define
651
+ # arguments listed in the task model
473
652
  def meaningful_arguments(task_model = self.model)
474
653
  arguments.slice(*task_model.arguments)
475
654
  end
655
+
476
656
  # The task name
477
657
  def name
478
- @name ||= "#{model.name || self.class.name}#{arguments.to_s}:0x#{address.to_s(16)}"
658
+ @name ||= "#{model.name || self.class.name}:0x#{address.to_s(16)}"
479
659
  end
480
660
 
481
661
  # This predicate is true if this task is a mission for its owners. If
@@ -484,6 +664,7 @@ module Roby
484
664
 
485
665
  def inspect
486
666
  state = if pending? then 'pending'
667
+ elsif failed_to_start? then 'failed to start'
487
668
  elsif starting? then 'starting'
488
669
  elsif running? then 'running'
489
670
  elsif finishing? then 'finishing'
@@ -499,23 +680,34 @@ module Roby
499
680
  # * the task shall have a +start+ event
500
681
  # * the task shall have at least one terminal event. If no +stop+ event
501
682
  # is defined, then all terminal events are aliased to +stop+
502
- def initialize(arguments = nil) #:yields: task_object
683
+ def initialize(arguments = Hash.new) #:yields: task_object
503
684
  super() if defined? super
504
685
 
505
686
  @arguments = TaskArguments.new(self)
506
- @arguments.merge!(arguments) if arguments
687
+ arguments.each do |key, value|
688
+ if self.respond_to?("#{key}=")
689
+ self.send("#{key}=", value)
690
+ else
691
+ @arguments[key] = value
692
+ end
693
+ end
507
694
 
508
695
  @model = self.class
509
696
 
510
697
  yield(self) if block_given?
698
+ # Create the EventGenerator instances that represent this task's
699
+ # events. Note that the event relations are instanciated by
700
+ # Plan#discover when this task is included in a plan, thus avoiding
701
+ # filling up the relation graphs with unused relations.
511
702
  initialize_events
512
703
  end
513
704
 
514
705
 
515
706
  # Lists all arguments, that are set to be needed via the :argument
516
707
  # syntax but are not set.
708
+ #
517
709
  # This is needed for debugging purposes.
518
- def list_unset_arguments
710
+ def list_unset_arguments # :nodoc:
519
711
  ret = Array.new
520
712
  model.arguments.each { |name|
521
713
  if !arguments.has_key?(name) then
@@ -523,7 +715,6 @@ module Roby
523
715
  end }
524
716
  ret
525
717
  end
526
-
527
718
 
528
719
  # Helper methods which creates all the necessary TaskEventGenerator
529
720
  # objects and stores them in the #bound_events map
@@ -540,6 +731,25 @@ module Roby
540
731
 
541
732
  def model; self.class end
542
733
 
734
+ # Returns for how many seconds this task is running. Returns nil if
735
+ # the task is not running.
736
+ def lifetime
737
+ if running?
738
+ Time.now - history.first.time
739
+ end
740
+ end
741
+
742
+ # Returns when this task has been started
743
+ def start_time
744
+ if running?
745
+ history.first.time
746
+ end
747
+ end
748
+
749
+ def create_fresh_copy
750
+ model.new(arguments.dup)
751
+ end
752
+
543
753
  def initialize_copy(old) # :nodoc:
544
754
  super
545
755
 
@@ -551,7 +761,7 @@ module Roby
551
761
  arguments.instance_variable_set(:@task, self)
552
762
 
553
763
  initialize_events
554
- plan.discover(self)
764
+ plan.add(self)
555
765
  end
556
766
 
557
767
  def instantiate_model_event_relations
@@ -569,7 +779,7 @@ module Roby
569
779
 
570
780
  for signalled in signalled_events
571
781
  signalled = bound_events[signalled]
572
- generator.signal signalled
782
+ generator.signals signalled
573
783
  left_border.delete(signalled)
574
784
  end
575
785
  end
@@ -582,7 +792,7 @@ module Roby
582
792
 
583
793
  for signalled in signalled_events
584
794
  signalled = bound_events[signalled]
585
- generator.forward signalled
795
+ generator.forward_to signalled
586
796
  left_border.delete(signalled)
587
797
  end
588
798
  end
@@ -646,29 +856,44 @@ module Roby
646
856
  end
647
857
 
648
858
  class << self
649
- # If this task is an abstract task
650
- # Abstract tasks are not executable. This attribute is
651
- # not inherited in the task hierarchy
652
- attr_reader :abstract
653
- alias :abstract? :abstract
654
-
655
- # Mark this task model as an abstract model
859
+ ##
860
+ # :singleton-method: abstract?
861
+ #
862
+ # True if this task is an abstract task.
863
+ #
864
+ # See Task::abstract() for more information.
865
+ attr_predicate :abstract
866
+
867
+ # Declare that this task model defines abstract tasks. Abstract
868
+ # tasks can be used to represent an action, without specifically
869
+ # representing how this action should be done.
870
+ #
871
+ # Instances of abstract task models are not executable, i.e. they
872
+ # cannot be started.
873
+ #
874
+ # See also #abstract? and #executable?
656
875
  def abstract
657
876
  @abstract = true
658
877
  end
659
878
 
660
- # Declare that nothing special is required to stop this task.
661
- # This makes +failed+ and +stop+ controlable events, and
662
- # makes the interruption sequence be stop! => calls failed! =>
663
- # emits +failed+ => emits +stop+.
879
+ # Declare that tasks of this model can finish by simply emitting
880
+ # +stop+. Use it this way:
881
+ #
882
+ # class MyTask < Roby::Task
883
+ # terminates
884
+ # end
885
+ #
886
+ # It adds a +stop!+ command that emits the +failed+ event.
664
887
  def terminates
665
888
  event :failed, :command => true, :terminal => true
666
889
  interruptible
667
890
  end
668
891
 
669
- # Sets up a command for +stop+ in the case where +failed+ is also
670
- # controllable, if the command of +failed+ should be used to stop
671
- # the task.
892
+ # Declare that tasks of this model can be interrupted. It does so by
893
+ # defining a command for +stop+, which in effect calls the command
894
+ # for +failed+.
895
+ #
896
+ # Raises ArgumentError if failed is not controlable.
672
897
  def interruptible
673
898
  if !has_event?(:failed) || !event_model(:failed).controlable?
674
899
  raise ArgumentError, "failed is not controlable"
@@ -683,7 +908,7 @@ module Roby
683
908
  end
684
909
 
685
910
  def setup_poll_method(block) # :nodoc:
686
- define_method(:poll) do
911
+ define_method(:poll) do |plan|
687
912
  return unless self_owned?
688
913
  begin
689
914
  poll_handler
@@ -695,9 +920,17 @@ module Roby
695
920
  define_method(:poll_handler, &block)
696
921
  end
697
922
 
698
- # Defines a block which will be called at each execution cycle for
699
- # each running task of this model. The block is called in the
700
- # instance context of the target task (i.e. using instance_eval)
923
+ # Declares that the given block should be called at each execution
924
+ # cycle, when the task is running. Use it that way:
925
+ #
926
+ # class MyTask < Roby::Task
927
+ # poll do
928
+ # ... do something ...
929
+ # end
930
+ # end
931
+ #
932
+ # If the given polling block raises an exception, the task will be
933
+ # terminated by emitting its +failed+ event.
701
934
  def poll(&block)
702
935
  if !block_given?
703
936
  raise "no block given"
@@ -705,8 +938,8 @@ module Roby
705
938
 
706
939
  setup_poll_method(block)
707
940
 
708
- on(:start) { Control.event_processing << method(:poll) }
709
- on(:stop) { Control.event_processing.delete(method(:poll)) }
941
+ on(:start) { |ev| ev.task.plan.engine.propagation_handlers << method(:poll) }
942
+ on(:stop) { |ev| ev.task.plan.engine.propagation_handlers.delete(method(:poll)) }
710
943
  end
711
944
  end
712
945
 
@@ -715,8 +948,13 @@ module Roby
715
948
 
716
949
  # Returns true if this task is from an abstract model. If it is the
717
950
  # case, the task is not executable.
718
- def abstract?; self.class.abstract? end
719
- # Check if this task is executable
951
+ #
952
+ # See Task::abstract for more details.
953
+ def abstract?; model.abstract? end
954
+ # True if this task is executable. A task is not executable if it is
955
+ # abstract or partially instanciated.
956
+ #
957
+ # See #abstract? and #partially_instanciated?
720
958
  def executable?; !abstract? && !partially_instanciated? && super end
721
959
  # Returns true if this task's stop event is controlable
722
960
  def interruptible?; event(:stop).controlable? end
@@ -745,14 +983,14 @@ module Roby
745
983
  # can either be a event class or an event name.
746
984
  def has_event?(event_model)
747
985
  bound_events.has_key?(event_model) ||
748
- self.class.has_event?(event_model)
986
+ model.has_event?(event_model)
749
987
  end
750
988
 
751
989
  # True if this task is starting, i.e. if its start event is pending
752
990
  # (has been called, but is not emitted yet)
753
991
  def starting?; event(:start).pending? end
754
992
  # True if this task has never been started
755
- def pending?; !starting? && !started? end
993
+ def pending?; !failed_to_start? && !starting? && !started? end
756
994
  # True if this task is currently running (i.e. is has already started,
757
995
  # and is not finished)
758
996
  def running?; started? && !finished? end
@@ -767,14 +1005,24 @@ module Roby
767
1005
  attr_predicate :started?, true
768
1006
  attr_predicate :finished?, true
769
1007
  attr_predicate :success?, true
1008
+ attr_predicate :failed_to_start?, true
770
1009
 
771
1010
  # True if the +failed+ event of this task has been fired
772
- def failed?; finished? && @success == false end
1011
+ def failed?; failed_to_start? || (finished? && @success == false) end
773
1012
 
774
- # Remove all relations in which +self+ or its event are involved
775
- def clear_relations
776
- each_event { |ev| ev.clear_relations }
777
- super
1013
+ # call-seq:
1014
+ # task.clear_relations => task
1015
+ #
1016
+ # Remove all relations in which +self+ or its event are involved
1017
+ #--
1018
+ # The including_events flag is here for the benefit of
1019
+ # Transactions::Proxy::Task only
1020
+ def clear_relations(including_events = true)
1021
+ if including_events
1022
+ each_event { |ev| ev.clear_relations }
1023
+ end
1024
+ super()
1025
+ self
778
1026
  end
779
1027
 
780
1028
  # Update the terminal flag for the event models that are defined in
@@ -863,8 +1111,8 @@ module Roby
863
1111
  end
864
1112
  end
865
1113
 
866
- # Returns a sorted list of Event objects, for all events that have been
867
- # fired by this task
1114
+ # Returns a list of Event objects, for all events that have been fired
1115
+ # by this task. The list is sorted by emission times.
868
1116
  def history
869
1117
  history = []
870
1118
  each_event do |event|
@@ -874,9 +1122,9 @@ module Roby
874
1122
  history.sort_by { |ev| ev.time }
875
1123
  end
876
1124
 
877
- # Returns the set of tasks directly related to this task, either
878
- # because of task relations or because of task events that are related
879
- # to other task events
1125
+ # Returns the set of tasks directly related to this task, either because
1126
+ # of task relations or because of task events that are related to other
1127
+ # task events
880
1128
  def related_tasks(result = nil)
881
1129
  result = related_objects(nil, result)
882
1130
  each_event do |ev|
@@ -898,29 +1146,41 @@ module Roby
898
1146
 
899
1147
  # This method is called by TaskEventGenerator#fire just before the event handlers
900
1148
  # and commands are called
901
- def fire_event(event) # :nodoc:
1149
+ def emitting_event(event, context) # :nodoc:
902
1150
  if !executable?
903
- raise TaskNotExecutable.new(self), "trying to fire #{event.generator.symbol} on #{self} but #{self} is not executable"
1151
+ raise TaskNotExecutable.new(self), "trying to emit #{symbol} on #{self} but #{self} is not executable"
904
1152
  end
905
1153
 
906
1154
  if finished? && !event.terminal?
907
- raise EmissionFailed.new(nil, self),
908
- "emit(#{event.symbol}: #{event.model}[#{event.context}]) called @#{event.propagation_id} by #{Propagation.sources} but the task has finished. Task has been terminated by #{event(:stop).history.first.sources}."
1155
+ raise EmissionFailed.new(nil, self),
1156
+ "emit(#{event.symbol}, #{context}) called by #{plan.engine.propagation_sources.to_a} but the task has finished. Task has been terminated by #{event(:stop).history.first.sources}."
909
1157
  elsif pending? && event.symbol != :start
910
- raise EmissionFailed.new(nil, self),
911
- "emit(#{event.symbol}: #{event.model}[#{event.context}]) called @#{event.propagation_id} by #{Propagation.sources} but the task is not running"
1158
+ raise EmissionFailed.new(nil, self),
1159
+ "emit(#{event.symbol}, #{context}) called by #{plan.engine.propagation_sources.to_a} but the task has never been started"
912
1160
  elsif running? && event.symbol == :start
913
- raise EmissionFailed.new(nil, self),
914
- "emit(#{event.symbol}: #{event.model}[#{event.context}]) called @#{event.propagation_id} by #{Propagation.sources} but the task is already running. Task has been started by #{event(:start).history.first.sources}."
1161
+ raise EmissionFailed.new(nil, self),
1162
+ "emit(#{event.symbol}, #{context}) called by #{plan.engine.propagation_sources.to_a} but the task is already running. Task has been started by #{event(:start).history.first.sources}."
915
1163
  end
916
1164
 
917
- update_task_status(event)
1165
+ super if defined? super
1166
+ end
918
1167
 
1168
+ # Hook called by TaskEventGenerator#fired when one of this task's events
1169
+ # is fired.
1170
+ def fire_event(event)
1171
+ update_task_status(event)
919
1172
  super if defined? super
920
1173
  end
921
1174
 
922
1175
  # The event which has finished the task (if there is one)
923
1176
  attr_reader :terminal_event
1177
+
1178
+ # The event that caused this task to fail. This is equivalent to taking
1179
+ # the first emitted element of
1180
+ # task.event(:failed).last.task_sources
1181
+ #
1182
+ # It is only much more efficient
1183
+ attr_reader :failure_event
924
1184
 
925
1185
  # Call to update the task status because of +event+
926
1186
  def update_task_status(event) # :nodoc:
@@ -934,6 +1194,7 @@ module Roby
934
1194
  self.success = false
935
1195
  self.finished = true
936
1196
  @terminal_event ||= event
1197
+ @failure_event ||= event
937
1198
  elsif event.terminal? && !finished?
938
1199
  plan.task_index.set_state(self, :finished?)
939
1200
  self.finished = true
@@ -974,22 +1235,42 @@ module Roby
974
1235
  end
975
1236
 
976
1237
  # call-seq:
977
- # on(event, task[, event1, event2, ...])
978
1238
  # on(event) { |event| ... }
979
- # on(event[, task, event1, event2, ...]) { |event| ... }
980
- # on(event, task[, event1, event2, ...], delay)
981
- #
982
- # Adds a signal from the given event to the specified targets, and/or
983
- # defines an event handler. Note that <tt>on(event, task)</tt> is
984
- # equivalent to <tt>on(event, task, event)</tt>
985
1239
  #
986
- # +delay+, if given, specifies that the signal must be postponed for as
987
- # much time as specified. See EventGenerator#signal for valid values.
1240
+ # Defines an event handler for the given event.
988
1241
  def on(event_model, to = nil, *to_task_events, &user_handler)
989
- unless to || user_handler
1242
+ if to
1243
+ Roby.warn_deprecated "on(event_name, task, target_events) has been replaced by #signals"
1244
+ elsif !(to || user_handler)
990
1245
  raise ArgumentError, "you must provide either a task or an event handler (got nil for both)"
991
1246
  end
992
1247
 
1248
+ if to
1249
+ signals(event_model, to, *to_task_events)
1250
+ end
1251
+ if user_handler
1252
+ generator = event(event_model)
1253
+ generator.on(&user_handler)
1254
+ end
1255
+ self
1256
+ end
1257
+
1258
+ # call-seq:
1259
+ # signals source_event, dest_task, ev1, ev2, ev3, ...
1260
+ # signals source_event, dest_task, ev1, ev2, ev3, delay_options
1261
+ #
1262
+ # Creates a signal from +source_event+, which is an event name of
1263
+ # +self+, to the listed events of +dest_task+. The destination events
1264
+ # will be called when the source event is emitted.
1265
+ #
1266
+ # To simply emit target events (i.e. not calling the event's commands),
1267
+ # use #forwards
1268
+ #
1269
+ # Optionally, a delay can be added to the signal. +delay_options+ can be
1270
+ # either:
1271
+ # :delay => relative_delay_in_seconds
1272
+ # :at => absolute_time
1273
+ def signals(event_model, to, *to_task_events)
993
1274
  generator = event(event_model)
994
1275
  if Hash === to_task_events.last
995
1276
  delay = to_task_events.pop
@@ -997,6 +1278,7 @@ module Roby
997
1278
  to_events = case to
998
1279
  when Task
999
1280
  if to_task_events.empty?
1281
+ Roby.warn_deprecated "signals(event_name, target_task) is deprecated. You must now always specify the target event name"
1000
1282
  [to.event(generator.symbol)]
1001
1283
  else
1002
1284
  to_task_events.map { |ev_model| to.event(ev_model) }
@@ -1006,27 +1288,35 @@ module Roby
1006
1288
  end
1007
1289
 
1008
1290
  to_events.push delay if delay
1009
- generator.on(*to_events, &user_handler)
1291
+ generator.signals(*to_events)
1010
1292
  self
1011
1293
  end
1012
1294
 
1295
+ def forward(name, to, *to_task_events)
1296
+ Roby.warn_deprecated "Task#forward has been renamed into Task#forward_to"
1297
+ if to_task_events.empty?
1298
+ Roby.warn_deprecated "the Task#forward(event_name, target_task) form is deprecated. Use Task#forward_to and specify the target event name"
1299
+ end
1300
+
1301
+ forward_to(name, to, *to_task_events)
1302
+ end
1303
+
1013
1304
  # call-seq:
1014
- # forward source_event, dest_task, ev1, ev2, ev3, ...
1015
- # forward source_event, dest_task, ev1, ev2, ev3, delay_options
1305
+ # forward_to source_event, dest_task, ev1, ev2, ev3, ...
1306
+ # forward_to source_event, dest_task, ev1, ev2, ev3, delay_options
1016
1307
  #
1017
- # Fowards +name+ to the events in +to_task_events+ on task +to+. The
1018
- # target events will be emitted as soon as the +name+ event is emitted
1019
- # on the receiving task, without calling any command.
1308
+ # Fowards the +source_event+, which is the name of an event of +self+,
1309
+ # to the listed events in +dest_task+. The target events will be emitted
1310
+ # as soon as the source event is emitted,
1020
1311
  #
1021
1312
  # To call an event whenever other events are emitted, use the Signal
1022
- # relation. See Task#on, Task.on and EventGenerator#on. As for Task#on,
1023
- # <tt>forward(:start, task)</tt> is a shortcut to <tt>forward(:start,
1024
- # task, :start)</tt>.
1313
+ # relation. See Task#signals, Task.signal and EventGenerator#signals.
1025
1314
  #
1026
- # If a +delay_options+ hash is provided, the forwarding is not performed
1027
- # immediately, but with a given delay. See EventGenerator#forward for
1028
- # the delay specification.
1029
- def forward(name, to, *to_task_events)
1315
+ # Optionally, a delay can be added to the signal. +delay_options+ can be
1316
+ # either:
1317
+ # :delay => relative_delay_in_seconds
1318
+ # :at => absolute_time
1319
+ def forward_to(name, to, *to_task_events)
1030
1320
  generator = event(name)
1031
1321
  if Hash === to_task_events.last
1032
1322
  delay = to_task_events.pop
@@ -1034,10 +1324,11 @@ module Roby
1034
1324
 
1035
1325
  to_events = if to.respond_to?(:event)
1036
1326
  if to_task_events.empty?
1327
+ Roby.warn_deprecated "forward_to(event_name, target_task) is deprecated. You must now always specify the target event name"
1037
1328
  [to.event(generator.symbol)]
1038
- else
1039
- to_task_events.map { |ev| to.event(ev) }
1040
- end
1329
+ else
1330
+ to_task_events.map { |ev| to.event(ev) }
1331
+ end
1041
1332
  elsif to.kind_of?(EventGenerator)
1042
1333
  [to]
1043
1334
  else
@@ -1045,31 +1336,44 @@ module Roby
1045
1336
  end
1046
1337
 
1047
1338
  to_events.each do |ev|
1048
- generator.forward ev, delay
1339
+ generator.forward_to ev, delay
1049
1340
  end
1050
1341
  end
1051
1342
 
1343
+ # :stopdoc:
1052
1344
  attr_accessor :calling_event
1345
+
1053
1346
  def method_missing(name, *args, &block) # :nodoc:
1054
1347
  if calling_event && calling_event.respond_to?(name)
1055
1348
  calling_event.send(name, *args, &block)
1056
1349
  else
1057
1350
  super
1058
1351
  end
1059
- rescue
1060
- raise $!, $!.message, $!.backtrace[1..-1]
1352
+ rescue NameError => e
1353
+ raise e, e.message, caller(1)
1354
+ rescue NoMethodError => e
1355
+ raise e, e.message, caller(1)
1061
1356
  end
1062
1357
 
1358
+ # Declares that this task model provides the given interface. +model+
1359
+ # must be an instance of TaskModelTag
1360
+ def self.provides(model)
1361
+ include model
1362
+ end
1363
+
1364
+
1063
1365
  @@event_command_id = 0
1064
1366
  def self.allocate_event_command_id # :nodoc:
1065
1367
  @@event_command_id += 1
1066
1368
  end
1369
+ # :startdoc:
1370
+
1067
1371
  # call-seq:
1068
- # self.event(name, options = nil) { ... } -> event class or nil
1372
+ # self.event(name, options = nil) { ... } => event class or nil
1069
1373
  #
1070
1374
  # Define a new event in this task.
1071
1375
  #
1072
- # ==== Available options
1376
+ # <b>Available options</b>
1073
1377
  #
1074
1378
  # <tt>command</tt>::
1075
1379
  # either true, false or an event command for the new event. In that
@@ -1086,7 +1390,7 @@ module Roby
1086
1390
  # base class for the event model (see "Event models" below). The default is the
1087
1391
  # TaskEvent class
1088
1392
  #
1089
- # ==== Event models
1393
+ # <b>Event models</b>
1090
1394
  #
1091
1395
  # When a task event (for instance +start+) is emitted, a Roby::Event
1092
1396
  # object is created to describe the information related to this
@@ -1130,10 +1434,10 @@ module Roby
1130
1434
  @symbol = ev
1131
1435
  @command_handler = command_handler
1132
1436
 
1133
- define_method(:name) { "#{task.name}::#{ev_s.camelize}" }
1437
+ define_method(:name) { "#{task.name}::#{ev_s.camelcase(true)}" }
1134
1438
  singleton_class.class_eval do
1135
1439
  attr_reader :command_handler
1136
- define_method(:name) { "#{task_klass.name}::#{ev_s.camelize}" }
1440
+ define_method(:name) { "#{task_klass.name}::#{ev_s.camelcase(true)}" }
1137
1441
  def to_s; name end
1138
1442
  end
1139
1443
  end
@@ -1148,7 +1452,7 @@ module Roby
1148
1452
  if setup_terminal_handler
1149
1453
  forward(new_event => :stop)
1150
1454
  end
1151
- const_set(ev_s.camelize, new_event)
1455
+ const_set(ev_s.camelcase(true), new_event)
1152
1456
 
1153
1457
  if options[:command]
1154
1458
  # check that the supplied command handler can take two arguments
@@ -1172,6 +1476,17 @@ module Roby
1172
1476
  end
1173
1477
  end
1174
1478
 
1479
+ if !method_defined?("#{ev_s}_event")
1480
+ define_method("#{ev_s}_event") do
1481
+ event(ev)
1482
+ end
1483
+ end
1484
+ if !method_defined?("#{ev_s}?")
1485
+ define_method("#{ev_s}?") do
1486
+ event(ev).happened?
1487
+ end
1488
+ end
1489
+
1175
1490
  new_event
1176
1491
  end
1177
1492
 
@@ -1201,15 +1516,23 @@ module Roby
1201
1516
 
1202
1517
  # Events defined by the task model
1203
1518
  inherited_enumerable(:event, :events, :map => true) { Hash.new }
1204
- def self.enum_events
1519
+
1520
+ def self.enum_events # :nodoc
1205
1521
  @__enum_events__ ||= enum_for(:each_event)
1206
1522
  end
1207
1523
 
1524
+ # call-seq:
1525
+ # task.each_event { |event_object| ... } => task
1526
+ #
1208
1527
  # Iterates on all the events defined for this task
1209
- def each_event # :yield:bound_event
1528
+ #--
1529
+ # The +only_wrapped+ flag is here for consistency with transaction
1530
+ # proxies, and should probably not be used in user code.
1531
+ def each_event(only_wrapped = true) # :yield:bound_event
1210
1532
  for _, ev in bound_events
1211
1533
  yield(ev)
1212
1534
  end
1535
+ self
1213
1536
  end
1214
1537
  alias :each_plan_child :each_event
1215
1538
 
@@ -1271,8 +1594,34 @@ module Roby
1271
1594
  end
1272
1595
 
1273
1596
  # call-seq:
1274
- # on(event_model) { |event| ... }
1275
- # on(event_model => ev1, ev2 => [ ev3, ev4 ]) { |event| ... }
1597
+ # signal(name1 => name2, name3 => [name4, name5])
1598
+ #
1599
+ # Establish model-level signals between events of that task. These
1600
+ # signals will be established on all the instances of this task model
1601
+ # (and its subclasses).
1602
+ def self.signal(mappings)
1603
+ mappings.each do |from, to|
1604
+ from = event_model(from)
1605
+ targets = Array[*to].map { |ev| event_model(ev) }
1606
+
1607
+ if from.terminal?
1608
+ non_terminal = targets.find_all { |ev| !ev.terminal? }
1609
+ if !non_terminal.empty?
1610
+ raise ArgumentError, "trying to establish a forwarding relation from the terminal event #{from} to the non-terminal events #{non_terminal}"
1611
+ end
1612
+ end
1613
+ non_controlable = targets.find_all { |ev| !ev.controlable? }
1614
+ if !non_controlable.empty?
1615
+ raise ArgumentError, "trying to signal #{non_controlable.join(" ")} which is/are not controlable"
1616
+ end
1617
+
1618
+ signal_sets[from.symbol].merge targets.map { |ev| ev.symbol }.to_value_set
1619
+ end
1620
+ update_terminal_flag
1621
+ end
1622
+
1623
+ # call-seq:
1624
+ # on(event_name) { |event| ... }
1276
1625
  #
1277
1626
  # Adds an event handler for the given event model. When the event is fired,
1278
1627
  # all events given in argument will be called. If they are controlable,
@@ -1282,21 +1631,14 @@ module Roby
1282
1631
  check_arity(user_handler, 1)
1283
1632
  end
1284
1633
 
1634
+ if mappings.kind_of?(Hash)
1635
+ Roby.warn_deprecated "the on(event => event) form of Task.on is deprecated. Use #signal to establish signals"
1636
+ signal(mappings)
1637
+ end
1638
+
1285
1639
  mappings = [*mappings].zip([]) unless Hash === mappings
1286
- mappings.each do |from, to|
1640
+ mappings.each do |from, _|
1287
1641
  from = event_model(from).symbol
1288
- to = if to
1289
- Array[*to].map do |ev|
1290
- model = event_model(ev)
1291
- raise ArgumentError, "trying to signal #{ev} which is not controlable" unless model.controlable?
1292
- model.symbol
1293
- end
1294
- else; []
1295
- end
1296
-
1297
- signal_sets[from].merge to.to_value_set
1298
- update_terminal_flag
1299
-
1300
1642
  if user_handler
1301
1643
  method_name = "event_handler_#{from}_#{Object.address_from_id(user_handler.object_id).to_s(16)}"
1302
1644
  define_method(method_name, &user_handler)
@@ -1329,8 +1671,17 @@ module Roby
1329
1671
  # See also Task#forward and EventGenerator#forward.
1330
1672
  def self.forward(mappings)
1331
1673
  mappings.each do |from, to|
1332
- from = event_model(from).symbol
1333
- forwarding_sets[from].merge Array[*to].map { |ev| event_model(ev).symbol }.to_value_set
1674
+ from = event_model(from).symbol
1675
+ targets = Array[*to].map { |ev| event_model(ev).symbol }
1676
+
1677
+ if event_model(from).terminal?
1678
+ non_terminal = targets.find_all { |name| !event_model(name).terminal? }
1679
+ if !non_terminal.empty?
1680
+ raise ArgumentError, "trying to establish a forwarding relation from the terminal event #{from} to the non-terminal event(s) #{targets}"
1681
+ end
1682
+ end
1683
+
1684
+ forwarding_sets[from].merge targets.to_value_set
1334
1685
  end
1335
1686
  update_terminal_flag
1336
1687
  end
@@ -1341,7 +1692,7 @@ module Roby
1341
1692
  end
1342
1693
 
1343
1694
  def to_s # :nodoc:
1344
- s = name.dup
1695
+ s = name.dup + arguments.to_s
1345
1696
  id = owners.map do |owner|
1346
1697
  next if owner == Roby::Distributed
1347
1698
  sibling = remote_siblings[owner]
@@ -1353,17 +1704,22 @@ module Roby
1353
1704
  s
1354
1705
  end
1355
1706
 
1356
- def pretty_print(pp) # :nodoc:
1357
- pp.text "#{self.class.name}:0x#{self.address.to_s(16)}"
1358
- pp.breakable
1359
- pp.nest(2) do
1360
- pp.text " owners: "
1361
- pp.seplist(owners) { |r| pp.text r.to_s }
1707
+ def pretty_print(pp, with_owners = true) # :nodoc:
1708
+ pp.text "#{model.name}:0x#{self.address.to_s(16)}"
1709
+ if with_owners
1710
+ pp.breakable
1711
+ pp.nest(2) do
1712
+ pp.text " owners: "
1713
+ pp.seplist(owners) { |r| pp.text r.to_s }
1714
+ pp.breakable
1362
1715
 
1363
- pp.breakable
1364
- pp.text "arguments: "
1365
- arguments.pretty_print(pp)
1366
- end
1716
+ pp.text "arguments: "
1717
+ arguments.pretty_print(pp)
1718
+ end
1719
+ else
1720
+ pp.text " "
1721
+ arguments.pretty_print(pp)
1722
+ end
1367
1723
  end
1368
1724
 
1369
1725
  # True if this task is a null task. See NullTask.
@@ -1417,7 +1773,7 @@ module Roby
1417
1773
  def fullfills?(models, args = {})
1418
1774
  if models.kind_of?(Task)
1419
1775
  klass, args =
1420
- models.class,
1776
+ models.model,
1421
1777
  models.meaningful_arguments
1422
1778
  models = [klass]
1423
1779
  else
@@ -1429,7 +1785,7 @@ module Roby
1429
1785
 
1430
1786
  # Check the arguments that are required by the model
1431
1787
  for tag in models
1432
- unless self_model.has_ancestor?(tag)
1788
+ if !self_model.has_ancestor?(tag)
1433
1789
  return false
1434
1790
  end
1435
1791
 
@@ -1449,6 +1805,25 @@ module Roby
1449
1805
  true
1450
1806
  end
1451
1807
 
1808
+ # True if +self+ can be used to replace +target+
1809
+ def can_replace?(target)
1810
+ fullfills?(*target.fullfilled_model)
1811
+ end
1812
+
1813
+ def can_merge?(target)
1814
+ target_model = target.fullfilled_model
1815
+ if !fullfills?(target_model.first)
1816
+ return false
1817
+ end
1818
+
1819
+ target_model.last.each do |key, val|
1820
+ if arguments.has_key?(key) && arguments[key] != val
1821
+ return false
1822
+ end
1823
+ end
1824
+ true
1825
+ end
1826
+
1452
1827
  include ExceptionHandlingObject
1453
1828
  inherited_enumerable('exception_handler', 'exception_handlers') { Array.new }
1454
1829
 
@@ -1457,9 +1832,10 @@ module Roby
1457
1832
 
1458
1833
  @@exception_handler_id = 0
1459
1834
 
1460
- # call-seq:
1461
- # on_exception(TaskModelViolation, ...) { |task, exception_object| ... }
1462
- #
1835
+ ##
1836
+ # :call-seq:
1837
+ # on_exception(exception_class, ...) { |task, exception_object| ... }
1838
+ #
1463
1839
  # Defines an exception handler. matcher === exception_object is used to
1464
1840
  # determine if the handler should be called when +exception_object+ has
1465
1841
  # been fired. The first matching handler is called. Call #pass_exception to pass
@@ -1576,8 +1952,8 @@ module Roby
1576
1952
  singleton_class.class_eval do
1577
1953
  setup_poll_method(block)
1578
1954
  end
1579
- on(:start) { Control.event_processing << method(:poll) }
1580
- on(:stop) { Control.event_processing.delete(method(:poll)) }
1955
+ on(:start) { |ev| @poll_handler_id = plan.engine.add_propagation_handler(method(:poll)) }
1956
+ on(:stop) { |ev| plan.engine.remove_propagation_handler(@poll_handler_id) }
1581
1957
  end
1582
1958
  end
1583
1959
 
@@ -1617,7 +1993,7 @@ module Roby
1617
1993
  start_event.call
1618
1994
  end
1619
1995
  on :start do
1620
- success_event.forward_once event(:success)
1996
+ success_event.forward_to_once event(:success)
1621
1997
  success_event.if_unreachable(true) do
1622
1998
  emit :failed if executable?
1623
1999
  end
@@ -1648,5 +2024,3 @@ module Roby
1648
2024
 
1649
2025
  end
1650
2026
 
1651
- require 'roby/task-operations'
1652
-