roby 0.8.0 → 3.0.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 (644) hide show
  1. checksums.yaml +7 -0
  2. data/.deep-cover.rb +3 -0
  3. data/.gitattributes +1 -0
  4. data/.gitignore +24 -0
  5. data/.simplecov +10 -0
  6. data/.travis.yml +17 -0
  7. data/.yardopts +4 -0
  8. data/Gemfile +15 -0
  9. data/README.md +11 -0
  10. data/Rakefile +47 -177
  11. data/benchmark/{alloc_misc.rb → attic/alloc_misc.rb} +2 -2
  12. data/benchmark/{discovery_latency.rb → attic/discovery_latency.rb} +19 -19
  13. data/benchmark/{garbage_collection.rb → attic/garbage_collection.rb} +9 -9
  14. data/benchmark/{genom.rb → attic/genom.rb} +0 -0
  15. data/benchmark/attic/transactions.rb +62 -0
  16. data/benchmark/plan_basic_operations.rb +28 -0
  17. data/benchmark/relations/graph.rb +63 -0
  18. data/benchmark/ruby/identity.rb +18 -0
  19. data/benchmark/ruby/set_intersect_vs_hash_merge.rb +39 -0
  20. data/benchmark/ruby/yield_vs_block.rb +35 -0
  21. data/benchmark/run +5 -0
  22. data/benchmark/synthetic_plan_modifications_with_transactions.rb +79 -0
  23. data/benchmark/transactions.rb +99 -51
  24. data/bin/roby +38 -197
  25. data/bin/roby-display +14 -0
  26. data/bin/roby-log +3 -176
  27. data/doc/guide/{src → attic}/abstraction/achieve_with.page +1 -1
  28. data/doc/guide/{src → attic}/abstraction/forwarding.page +1 -1
  29. data/doc/guide/{src → attic}/abstraction/hierarchy.page +1 -1
  30. data/doc/guide/{src → attic}/abstraction/index.page +1 -1
  31. data/doc/guide/{src → attic}/abstraction/task_models.page +1 -1
  32. data/doc/guide/{overview.rdoc → attic/cycle/api_overview.rdoc} +6 -1
  33. data/doc/guide/{src → attic}/cycle/cycle-overview.png +0 -0
  34. data/doc/guide/{src → attic}/cycle/cycle-overview.svg +0 -0
  35. data/doc/guide/attic/cycle/error_handling.page +98 -0
  36. data/doc/guide/{src → attic}/cycle/error_instantaneous_repair.png +0 -0
  37. data/doc/guide/{src → attic}/cycle/error_instantaneous_repair.svg +0 -0
  38. data/doc/guide/{src/cycle/error_handling.page → attic/cycle/error_sources.page} +46 -89
  39. data/doc/guide/{src → attic}/cycle/garbage_collection.page +1 -1
  40. data/doc/guide/{src → attic}/cycle/index.page +1 -1
  41. data/doc/guide/{src → attic}/cycle/propagation.page +11 -1
  42. data/doc/guide/{src → attic}/cycle/propagation_diamond.png +0 -0
  43. data/doc/guide/{src → attic}/cycle/propagation_diamond.svg +0 -0
  44. data/doc/guide/attic/plans/building_plans.page +89 -0
  45. data/doc/guide/attic/plans/code.page +192 -0
  46. data/doc/guide/{src/basics → attic/plans}/events.page +3 -4
  47. data/doc/guide/attic/plans/index.page +7 -0
  48. data/doc/guide/{plan_modifications.rdoc → attic/plans/plan_modifications.rdoc} +5 -3
  49. data/doc/guide/{src/basics → attic/plans}/plan_objects.page +2 -1
  50. data/doc/guide/attic/plans/querying_plans.page +5 -0
  51. data/doc/guide/{src/basics → attic/plans}/tasks.page +20 -20
  52. data/doc/guide/config.yaml +7 -4
  53. data/doc/guide/ext/extended_menu.rb +29 -0
  54. data/doc/guide/ext/init.rb +6 -0
  55. data/doc/guide/ext/rdoc_links.rb +7 -6
  56. data/doc/guide/src/advanced_concepts/history.page +5 -0
  57. data/doc/guide/src/advanced_concepts/index.page +11 -0
  58. data/doc/guide/src/advanced_concepts/recognizing_patterns.page +83 -0
  59. data/doc/guide/src/advanced_concepts/scheduling.page +87 -0
  60. data/doc/guide/src/advanced_concepts/transactions.page +5 -0
  61. data/doc/guide/src/advanced_concepts/unreachability.page +42 -0
  62. data/doc/guide/src/base.template +96 -0
  63. data/doc/guide/src/basics_shell_header.txt +5 -7
  64. data/doc/guide/src/building/action_coordination.page +96 -0
  65. data/doc/guide/src/building/actions.page +124 -0
  66. data/doc/guide/src/building/file_layout.page +71 -0
  67. data/doc/guide/src/building/index.page +50 -0
  68. data/doc/guide/src/building/patterns.page +86 -0
  69. data/doc/guide/src/building/patterns_forwarding.png +0 -0
  70. data/doc/guide/src/building/patterns_forwarding.svg +277 -0
  71. data/doc/guide/src/building/runtime.page +95 -0
  72. data/doc/guide/src/building/task_models.page +94 -0
  73. data/doc/guide/src/building/tasks.page +284 -0
  74. data/doc/guide/src/concepts/error_handling.page +100 -0
  75. data/doc/guide/src/concepts/exception_propagation.png +0 -0
  76. data/doc/guide/src/concepts/exception_propagation.svg +445 -0
  77. data/doc/guide/src/concepts/execution.page +85 -0
  78. data/doc/guide/src/concepts/execution.png +0 -0
  79. data/doc/guide/src/concepts/execution.svg +573 -0
  80. data/doc/guide/src/concepts/execution_cycle.png +0 -0
  81. data/doc/guide/src/concepts/garbage_collection.page +57 -0
  82. data/doc/guide/src/concepts/index.page +27 -0
  83. data/doc/guide/src/concepts/plans.page +101 -0
  84. data/doc/guide/src/concepts/policy.page +31 -0
  85. data/doc/guide/src/concepts/reactor.page +61 -0
  86. data/doc/guide/src/concepts/simple_plan_example.png +0 -0
  87. data/doc/guide/src/concepts/simple_plan_example.svg +376 -0
  88. data/doc/guide/src/default.template +9 -74
  89. data/doc/guide/src/event_relations/forward.page +71 -0
  90. data/doc/guide/src/event_relations/index.page +12 -0
  91. data/doc/guide/src/event_relations/scheduling_constraints.page +43 -0
  92. data/doc/guide/src/event_relations/signal.page +55 -0
  93. data/doc/guide/src/event_relations/temporal_constraints.page +77 -0
  94. data/doc/guide/src/htmldoc.metainfo +21 -8
  95. data/doc/guide/src/index.page +8 -3
  96. data/doc/guide/src/{introduction/install.page → installation/index.page} +37 -25
  97. data/doc/guide/src/installation/publications.page +14 -0
  98. data/doc/guide/src/{introduction → installation}/videos.page +14 -7
  99. data/doc/guide/src/interacting/index.page +16 -0
  100. data/doc/guide/src/interacting/run.page +33 -0
  101. data/doc/guide/src/interacting/shell.page +95 -0
  102. data/doc/guide/src/plugins/creating_plugins.page +72 -0
  103. data/doc/guide/src/plugins/index.page +27 -5
  104. data/doc/guide/src/plugins/{fault_tolerance.page → standard_plugins/fault_tolerance.page} +2 -2
  105. data/doc/guide/src/plugins/standard_plugins/index.page +11 -0
  106. data/doc/guide/src/plugins/{subsystems.page → standard_plugins/subsystems.page} +2 -2
  107. data/doc/guide/src/style_screen.css +687 -0
  108. data/doc/guide/src/task_relations/dependency.page +107 -0
  109. data/doc/guide/src/task_relations/executed_by.page +77 -0
  110. data/doc/guide/src/task_relations/index.page +12 -0
  111. data/doc/guide/src/task_relations/new_relations.page +119 -0
  112. data/doc/guide/src/task_relations/planned_by.page +46 -0
  113. data/doc/guide/src/tutorial/app.page +117 -0
  114. data/doc/guide/src/{basics → tutorial}/code_examples.page +6 -5
  115. data/doc/guide/src/{basics → tutorial}/dry.page +15 -15
  116. data/doc/guide/src/{basics → tutorial}/errors.page +43 -68
  117. data/doc/guide/src/tutorial/events.page +195 -0
  118. data/doc/guide/src/{basics → tutorial}/hierarchy.page +53 -52
  119. data/doc/guide/src/tutorial/index.page +13 -0
  120. data/doc/guide/src/tutorial/log_replay/goForward_1.png +0 -0
  121. data/doc/guide/src/tutorial/log_replay/goForward_2.png +0 -0
  122. data/doc/guide/src/tutorial/log_replay/goForward_3.png +0 -0
  123. data/doc/guide/src/{basics → tutorial}/log_replay/goForward_4.png +0 -0
  124. data/doc/guide/src/tutorial/log_replay/goForward_5.png +0 -0
  125. data/doc/guide/src/{basics → tutorial}/log_replay/hierarchy_error_1.png +0 -0
  126. data/doc/guide/src/{basics → tutorial}/log_replay/hierarchy_error_2.png +0 -0
  127. data/doc/guide/src/{basics → tutorial}/log_replay/hierarchy_error_3.png +0 -0
  128. data/doc/guide/src/tutorial/log_replay/moveto_code_error.png +0 -0
  129. data/doc/guide/src/{basics → tutorial}/log_replay/plan_repair_1.png +0 -0
  130. data/doc/guide/src/{basics → tutorial}/log_replay/plan_repair_2.png +0 -0
  131. data/doc/guide/src/{basics → tutorial}/log_replay/plan_repair_3.png +0 -0
  132. data/doc/guide/src/tutorial/log_replay/plan_repair_4.png +0 -0
  133. data/doc/guide/src/tutorial/log_replay/roby_log_main_window.png +0 -0
  134. data/doc/guide/src/{basics → tutorial}/log_replay/roby_log_relation_window.png +0 -0
  135. data/doc/guide/src/{basics → tutorial}/log_replay/roby_replay_event_representation.png +0 -0
  136. data/doc/guide/src/tutorial/relations_display.page +153 -0
  137. data/doc/guide/src/{basics → tutorial}/roby_cycle_overview.png +0 -0
  138. data/doc/guide/src/tutorial/shell.page +121 -0
  139. data/doc/guide/src/{basics → tutorial}/summary.page +1 -1
  140. data/doc/guide/src/tutorial/tasks.page +374 -0
  141. data/lib/roby.rb +102 -47
  142. data/lib/roby/actions.rb +17 -0
  143. data/lib/roby/actions/action.rb +80 -0
  144. data/lib/roby/actions/interface.rb +45 -0
  145. data/lib/roby/actions/library.rb +23 -0
  146. data/lib/roby/actions/models/action.rb +224 -0
  147. data/lib/roby/actions/models/coordination_action.rb +58 -0
  148. data/lib/roby/actions/models/interface.rb +22 -0
  149. data/lib/roby/actions/models/interface_base.rb +294 -0
  150. data/lib/roby/actions/models/library.rb +12 -0
  151. data/lib/roby/actions/models/method_action.rb +90 -0
  152. data/lib/roby/actions/task.rb +114 -0
  153. data/lib/roby/and_generator.rb +125 -0
  154. data/lib/roby/app.rb +2795 -829
  155. data/lib/roby/app/autotest_console_reporter.rb +138 -0
  156. data/lib/roby/app/base.rb +21 -0
  157. data/lib/roby/app/cucumber.rb +2 -0
  158. data/lib/roby/app/cucumber/controller.rb +439 -0
  159. data/lib/roby/app/cucumber/helpers.rb +280 -0
  160. data/lib/roby/app/cucumber/world.rb +32 -0
  161. data/lib/roby/app/debug.rb +136 -0
  162. data/lib/roby/app/gen.rb +2 -0
  163. data/lib/roby/app/rake.rb +178 -38
  164. data/lib/roby/app/robot_config.rb +9 -0
  165. data/lib/roby/app/robot_names.rb +115 -0
  166. data/lib/roby/app/run.rb +3 -2
  167. data/lib/roby/app/scripts.rb +72 -0
  168. data/lib/roby/app/scripts/autotest.rb +173 -0
  169. data/lib/roby/app/scripts/display.rb +2 -0
  170. data/lib/roby/app/scripts/restart.rb +52 -0
  171. data/lib/roby/app/scripts/results.rb +17 -8
  172. data/lib/roby/app/scripts/run.rb +155 -24
  173. data/lib/roby/app/scripts/shell.rb +147 -62
  174. data/lib/roby/app/scripts/test.rb +107 -22
  175. data/lib/roby/app/test_reporter.rb +74 -0
  176. data/lib/roby/app/test_server.rb +159 -0
  177. data/lib/roby/app/vagrant.rb +47 -0
  178. data/lib/roby/backports.rb +16 -0
  179. data/lib/roby/cli/display.rb +190 -0
  180. data/lib/roby/cli/exceptions.rb +17 -0
  181. data/lib/roby/cli/gen/actions/class.rb +5 -0
  182. data/lib/roby/cli/gen/actions/test.rb +6 -0
  183. data/lib/roby/cli/gen/app/.yardopts +6 -0
  184. data/lib/roby/cli/gen/app/README.md +28 -0
  185. data/lib/roby/cli/gen/app/Rakefile +15 -0
  186. data/{app → lib/roby/cli/gen/app}/config/app.yml +29 -39
  187. data/lib/roby/cli/gen/app/models/.gitattributes +1 -0
  188. data/{app → lib/roby/cli/gen/app/scripts}/controllers/.gitattributes +0 -0
  189. data/{app/data/.gitattributes → lib/roby/cli/gen/app/test/.gitignore} +0 -0
  190. data/lib/roby/cli/gen/class/class.rb +6 -0
  191. data/lib/roby/cli/gen/class/test.rb +7 -0
  192. data/lib/roby/cli/gen/helpers.rb +203 -0
  193. data/lib/roby/cli/gen/module/module.rb +5 -0
  194. data/lib/roby/cli/gen/module/test.rb +6 -0
  195. data/lib/roby/cli/gen/roby_app/config/init.rb +17 -0
  196. data/lib/roby/cli/gen/roby_app/config/robots/robot.rb +40 -0
  197. data/lib/roby/cli/gen/task/class.rb +44 -0
  198. data/lib/roby/cli/gen/task/test.rb +6 -0
  199. data/lib/roby/cli/gen_main.rb +120 -0
  200. data/lib/roby/cli/log.rb +276 -0
  201. data/lib/roby/cli/log/flamegraph.html +499 -0
  202. data/lib/roby/cli/log/flamegraph_renderer.rb +88 -0
  203. data/lib/roby/cli/main.rb +153 -0
  204. data/lib/roby/coordination.rb +60 -0
  205. data/lib/roby/coordination/action_script.rb +25 -0
  206. data/lib/roby/coordination/action_state_machine.rb +125 -0
  207. data/lib/roby/coordination/actions.rb +106 -0
  208. data/lib/roby/coordination/base.rb +145 -0
  209. data/lib/roby/coordination/calculus.rb +40 -0
  210. data/lib/roby/coordination/child.rb +28 -0
  211. data/lib/roby/coordination/event.rb +29 -0
  212. data/lib/roby/coordination/fault_handler.rb +25 -0
  213. data/lib/roby/coordination/fault_handling_task.rb +13 -0
  214. data/lib/roby/coordination/fault_response_table.rb +110 -0
  215. data/lib/roby/coordination/models/action_script.rb +64 -0
  216. data/lib/roby/coordination/models/action_state_machine.rb +224 -0
  217. data/lib/roby/coordination/models/actions.rb +191 -0
  218. data/lib/roby/coordination/models/arguments.rb +55 -0
  219. data/lib/roby/coordination/models/base.rb +176 -0
  220. data/lib/roby/coordination/models/capture.rb +86 -0
  221. data/lib/roby/coordination/models/child.rb +35 -0
  222. data/lib/roby/coordination/models/event.rb +41 -0
  223. data/lib/roby/coordination/models/exceptions.rb +42 -0
  224. data/lib/roby/coordination/models/fault_handler.rb +219 -0
  225. data/lib/roby/coordination/models/fault_response_table.rb +77 -0
  226. data/lib/roby/coordination/models/root.rb +22 -0
  227. data/lib/roby/coordination/models/script.rb +283 -0
  228. data/lib/roby/coordination/models/task.rb +184 -0
  229. data/lib/roby/coordination/models/task_from_action.rb +50 -0
  230. data/lib/roby/coordination/models/task_from_as_plan.rb +33 -0
  231. data/lib/roby/coordination/models/task_from_instanciation_object.rb +31 -0
  232. data/lib/roby/coordination/models/task_from_variable.rb +27 -0
  233. data/lib/roby/coordination/models/task_with_dependencies.rb +48 -0
  234. data/lib/roby/coordination/models/variable.rb +32 -0
  235. data/lib/roby/coordination/script.rb +200 -0
  236. data/lib/roby/coordination/script_instruction.rb +12 -0
  237. data/lib/roby/coordination/task.rb +45 -0
  238. data/lib/roby/coordination/task_base.rb +69 -0
  239. data/lib/roby/coordination/task_script.rb +293 -0
  240. data/lib/roby/coordination/task_state_machine.rb +308 -0
  241. data/lib/roby/decision_control.rb +33 -21
  242. data/lib/roby/distributed_object.rb +76 -0
  243. data/lib/roby/droby.rb +17 -0
  244. data/lib/roby/droby/droby_id.rb +6 -0
  245. data/lib/roby/droby/enable.rb +153 -0
  246. data/lib/roby/droby/event_logger.rb +189 -0
  247. data/lib/roby/droby/event_logging.rb +57 -0
  248. data/lib/roby/droby/exceptions.rb +14 -0
  249. data/lib/roby/droby/identifiable.rb +22 -0
  250. data/lib/roby/droby/logfile.rb +141 -0
  251. data/lib/roby/droby/logfile/client.rb +176 -0
  252. data/lib/roby/droby/logfile/file_format.md +97 -0
  253. data/lib/roby/droby/logfile/index.rb +117 -0
  254. data/lib/roby/droby/logfile/reader.rb +139 -0
  255. data/lib/roby/droby/logfile/server.rb +199 -0
  256. data/lib/roby/droby/logfile/writer.rb +114 -0
  257. data/lib/roby/droby/marshal.rb +264 -0
  258. data/lib/roby/droby/marshallable.rb +12 -0
  259. data/lib/roby/droby/null_event_logger.rb +25 -0
  260. data/lib/roby/droby/object_manager.rb +205 -0
  261. data/lib/roby/droby/peer_id.rb +6 -0
  262. data/lib/roby/droby/plan_rebuilder.rb +373 -0
  263. data/lib/roby/droby/rebuilt_plan.rb +160 -0
  264. data/lib/roby/droby/remote_droby_id.rb +6 -0
  265. data/lib/roby/droby/timepoints.rb +205 -0
  266. data/lib/roby/droby/timepoints_ctf.metadata.erb +101 -0
  267. data/lib/roby/droby/timepoints_ctf.rb +125 -0
  268. data/lib/roby/droby/v5.rb +14 -0
  269. data/lib/roby/droby/v5/builtin.rb +120 -0
  270. data/lib/roby/droby/v5/droby_class.rb +45 -0
  271. data/lib/roby/droby/v5/droby_constant.rb +81 -0
  272. data/lib/roby/droby/v5/droby_dump.rb +1026 -0
  273. data/lib/roby/droby/v5/droby_id.rb +44 -0
  274. data/lib/roby/droby/v5/droby_model.rb +82 -0
  275. data/lib/roby/droby/v5/peer_id.rb +10 -0
  276. data/lib/roby/droby/v5/remote_droby_id.rb +42 -0
  277. data/lib/roby/event.rb +79 -957
  278. data/lib/roby/event_constraints.rb +835 -0
  279. data/lib/roby/event_generator.rb +1047 -0
  280. data/lib/roby/event_structure/causal_link.rb +6 -0
  281. data/lib/roby/event_structure/forwarding.rb +6 -0
  282. data/lib/roby/event_structure/precedence.rb +7 -0
  283. data/lib/roby/event_structure/signal.rb +8 -0
  284. data/lib/roby/event_structure/temporal_constraints.rb +640 -0
  285. data/lib/roby/exceptions.rb +446 -152
  286. data/lib/roby/executable_plan.rb +549 -0
  287. data/lib/roby/execution_engine.rb +1997 -950
  288. data/lib/roby/filter_generator.rb +26 -0
  289. data/lib/roby/gui/chronicle_view.rb +225 -0
  290. data/lib/roby/gui/chronicle_widget.rb +925 -0
  291. data/lib/roby/gui/dot_id.rb +11 -0
  292. data/lib/roby/gui/exception_view.rb +44 -0
  293. data/lib/roby/gui/log_display.rb +273 -0
  294. data/lib/roby/gui/model_views.rb +2 -0
  295. data/lib/roby/gui/model_views/action_interface.rb +53 -0
  296. data/lib/roby/gui/model_views/task.rb +47 -0
  297. data/lib/roby/gui/model_views/task.rhtml +41 -0
  298. data/lib/roby/gui/object_info_view.rb +89 -0
  299. data/lib/roby/gui/plan_dot_layout.rb +427 -0
  300. data/lib/roby/gui/plan_rebuilder_widget.rb +357 -0
  301. data/lib/roby/gui/qt4_toMSecsSinceEpoch.rb +8 -0
  302. data/lib/roby/gui/relations_view.rb +278 -0
  303. data/lib/roby/gui/relations_view/relations.ui +139 -0
  304. data/lib/roby/gui/relations_view/relations_canvas.rb +1088 -0
  305. data/lib/roby/gui/relations_view/relations_config.rb +292 -0
  306. data/lib/roby/gui/relations_view/relations_view.ui +53 -0
  307. data/lib/roby/gui/scheduler_view.css +24 -0
  308. data/lib/roby/gui/scheduler_view.rb +46 -0
  309. data/lib/roby/gui/scheduler_view.rhtml +53 -0
  310. data/lib/roby/gui/stepping.rb +93 -0
  311. data/lib/roby/gui/stepping.ui +181 -0
  312. data/lib/roby/gui/styles.rb +81 -0
  313. data/lib/roby/gui/task_display_configuration.rb +42 -0
  314. data/lib/roby/gui/task_state_at.rb +38 -0
  315. data/lib/roby/hooks.rb +26 -0
  316. data/lib/roby/interface.rb +136 -469
  317. data/lib/roby/interface/async.rb +20 -0
  318. data/lib/roby/interface/async/action_monitor.rb +188 -0
  319. data/lib/roby/interface/async/interface.rb +498 -0
  320. data/lib/roby/interface/async/job_monitor.rb +213 -0
  321. data/lib/roby/interface/async/log.rb +238 -0
  322. data/lib/roby/interface/async/new_job_listener.rb +79 -0
  323. data/lib/roby/interface/async/ui_connector.rb +183 -0
  324. data/lib/roby/interface/client.rb +553 -0
  325. data/lib/roby/interface/command.rb +24 -0
  326. data/lib/roby/interface/command_argument.rb +16 -0
  327. data/lib/roby/interface/command_library.rb +92 -0
  328. data/lib/roby/interface/droby_channel.rb +174 -0
  329. data/lib/roby/interface/exceptions.rb +22 -0
  330. data/lib/roby/interface/interface.rb +655 -0
  331. data/lib/roby/interface/job.rb +47 -0
  332. data/lib/roby/interface/rest.rb +10 -0
  333. data/lib/roby/interface/rest/api.rb +29 -0
  334. data/lib/roby/interface/rest/helpers.rb +24 -0
  335. data/lib/roby/interface/rest/server.rb +212 -0
  336. data/lib/roby/interface/server.rb +154 -0
  337. data/lib/roby/interface/shell_client.rb +468 -0
  338. data/lib/roby/interface/shell_subcommand.rb +24 -0
  339. data/lib/roby/interface/subcommand_client.rb +35 -0
  340. data/lib/roby/interface/tcp.rb +168 -0
  341. data/lib/roby/models/arguments.rb +112 -0
  342. data/lib/roby/models/plan_object.rb +83 -0
  343. data/lib/roby/models/task.rb +835 -0
  344. data/lib/roby/models/task_event.rb +62 -0
  345. data/lib/roby/models/task_service.rb +78 -0
  346. data/lib/roby/or_generator.rb +88 -0
  347. data/lib/roby/plan.rb +1751 -864
  348. data/lib/roby/plan_object.rb +611 -0
  349. data/lib/roby/plan_service.rb +200 -0
  350. data/lib/roby/promise.rb +332 -0
  351. data/lib/roby/queries.rb +23 -0
  352. data/lib/roby/queries/and_matcher.rb +32 -0
  353. data/lib/roby/queries/any.rb +27 -0
  354. data/lib/roby/queries/code_error_matcher.rb +58 -0
  355. data/lib/roby/queries/event_generator_matcher.rb +9 -0
  356. data/lib/roby/queries/execution_exception_matcher.rb +165 -0
  357. data/lib/roby/queries/index.rb +165 -0
  358. data/lib/roby/queries/localized_error_matcher.rb +149 -0
  359. data/lib/roby/queries/matcher_base.rb +107 -0
  360. data/lib/roby/queries/none.rb +27 -0
  361. data/lib/roby/queries/not_matcher.rb +30 -0
  362. data/lib/roby/queries/op_matcher.rb +8 -0
  363. data/lib/roby/queries/or_matcher.rb +30 -0
  364. data/lib/roby/queries/plan_object_matcher.rb +363 -0
  365. data/lib/roby/queries/query.rb +188 -0
  366. data/lib/roby/queries/task_event_generator_matcher.rb +86 -0
  367. data/lib/roby/queries/task_matcher.rb +344 -0
  368. data/lib/roby/relations.rb +42 -678
  369. data/lib/roby/relations/bidirectional_directed_adjacency_graph.rb +492 -0
  370. data/lib/roby/relations/directed_relation_support.rb +268 -0
  371. data/lib/roby/relations/event_relation_graph.rb +19 -0
  372. data/lib/roby/relations/fork_merge_visitor.rb +154 -0
  373. data/lib/roby/relations/graph.rb +533 -0
  374. data/lib/roby/relations/models/directed_relation_support.rb +11 -0
  375. data/lib/roby/relations/models/graph.rb +75 -0
  376. data/lib/roby/relations/models/task_relation_graph.rb +18 -0
  377. data/lib/roby/relations/space.rb +380 -0
  378. data/lib/roby/relations/task_relation_graph.rb +20 -0
  379. data/lib/roby/robot.rb +85 -38
  380. data/lib/roby/schedulers/basic.rb +155 -25
  381. data/lib/roby/schedulers/null.rb +20 -0
  382. data/lib/roby/schedulers/reporting.rb +31 -0
  383. data/lib/roby/schedulers/state.rb +129 -0
  384. data/lib/roby/schedulers/temporal.rb +91 -0
  385. data/lib/roby/singletons.rb +87 -0
  386. data/lib/roby/standalone.rb +4 -2
  387. data/lib/roby/standard_errors.rb +405 -82
  388. data/lib/roby/state.rb +6 -3
  389. data/lib/roby/state/conf_model.rb +5 -0
  390. data/lib/roby/state/events.rb +181 -95
  391. data/lib/roby/state/goal_model.rb +77 -0
  392. data/lib/roby/state/open_struct.rb +591 -0
  393. data/lib/roby/state/open_struct_model.rb +68 -0
  394. data/lib/roby/state/pos.rb +45 -45
  395. data/lib/roby/state/shapes.rb +11 -11
  396. data/lib/roby/state/state_model.rb +303 -0
  397. data/lib/roby/state/task.rb +43 -0
  398. data/lib/roby/support.rb +88 -148
  399. data/lib/roby/task.rb +1361 -1750
  400. data/lib/roby/task_arguments.rb +428 -0
  401. data/lib/roby/task_event.rb +127 -0
  402. data/lib/roby/task_event_generator.rb +337 -0
  403. data/lib/roby/task_service.rb +6 -0
  404. data/lib/roby/task_structure/conflicts.rb +104 -0
  405. data/lib/roby/task_structure/dependency.rb +932 -0
  406. data/lib/roby/task_structure/error_handling.rb +118 -0
  407. data/lib/roby/task_structure/executed_by.rb +234 -0
  408. data/lib/roby/task_structure/planned_by.rb +90 -0
  409. data/lib/roby/tasks/aggregator.rb +37 -0
  410. data/lib/roby/tasks/external_process.rb +275 -0
  411. data/lib/roby/tasks/group.rb +27 -0
  412. data/lib/roby/tasks/null.rb +19 -0
  413. data/lib/roby/tasks/parallel.rb +43 -0
  414. data/lib/roby/tasks/sequence.rb +88 -0
  415. data/lib/roby/tasks/simple.rb +21 -0
  416. data/lib/roby/{thread_task.rb → tasks/thread.rb} +50 -24
  417. data/lib/roby/tasks/timeout.rb +17 -0
  418. data/lib/roby/tasks/virtual.rb +55 -0
  419. data/lib/roby/template_plan.rb +7 -0
  420. data/lib/roby/test/aruba_minitest.rb +74 -0
  421. data/lib/roby/test/assertion.rb +16 -0
  422. data/lib/roby/test/assertions.rb +490 -0
  423. data/lib/roby/test/common.rb +368 -591
  424. data/lib/roby/test/dsl.rb +149 -0
  425. data/lib/roby/test/error.rb +18 -0
  426. data/lib/roby/test/event_reporter.rb +83 -0
  427. data/lib/roby/test/execution_expectations.rb +1134 -0
  428. data/lib/roby/test/expect_execution.rb +151 -0
  429. data/lib/roby/test/minitest_helpers.rb +166 -0
  430. data/lib/roby/test/roby_app_helpers.rb +200 -0
  431. data/lib/roby/test/run_planners.rb +155 -0
  432. data/lib/roby/test/self.rb +112 -0
  433. data/lib/roby/test/spec.rb +198 -0
  434. data/lib/roby/test/tasks/empty_task.rb +4 -4
  435. data/lib/roby/test/tasks/goto.rb +28 -27
  436. data/lib/roby/test/teardown_plans.rb +100 -0
  437. data/lib/roby/test/testcase.rb +239 -307
  438. data/lib/roby/test/tools.rb +159 -155
  439. data/lib/roby/test/validate_state_machine.rb +75 -0
  440. data/lib/roby/transaction.rb +1125 -0
  441. data/lib/roby/transaction/event_generator_proxy.rb +63 -0
  442. data/lib/roby/transaction/plan_object_proxy.rb +99 -0
  443. data/lib/roby/transaction/plan_service_proxy.rb +43 -0
  444. data/lib/roby/transaction/proxying.rb +120 -0
  445. data/lib/roby/transaction/task_event_generator_proxy.rb +19 -0
  446. data/lib/roby/transaction/task_proxy.rb +135 -0
  447. data/lib/roby/until_generator.rb +30 -0
  448. data/lib/roby/version.rb +5 -0
  449. data/lib/roby/yard.rb +169 -0
  450. data/lib/yard-roby.rb +1 -0
  451. data/manifest.xml +32 -6
  452. data/roby.gemspec +59 -0
  453. metadata +788 -587
  454. data/Manifest.txt +0 -321
  455. data/NOTES +0 -4
  456. data/README.txt +0 -166
  457. data/TODO.txt +0 -146
  458. data/app/README.txt +0 -24
  459. data/app/Rakefile +0 -8
  460. data/app/config/ROBOT.rb +0 -5
  461. data/app/config/init.rb +0 -33
  462. data/app/config/roby.yml +0 -3
  463. data/app/controllers/ROBOT.rb +0 -2
  464. data/app/planners/ROBOT/main.rb +0 -6
  465. data/app/planners/main.rb +0 -5
  466. data/app/scripts/distributed +0 -3
  467. data/app/scripts/generate/bookmarks +0 -3
  468. data/app/scripts/replay +0 -3
  469. data/app/scripts/results +0 -3
  470. data/app/scripts/run +0 -3
  471. data/app/scripts/server +0 -3
  472. data/app/scripts/shell +0 -3
  473. data/app/scripts/test +0 -3
  474. data/app/tasks/.gitattributes +0 -0
  475. data/app/tasks/ROBOT/.gitattributes +0 -0
  476. data/bin/roby-shell +0 -25
  477. data/doc/guide/src/basics/app.page +0 -139
  478. data/doc/guide/src/basics/index.page +0 -11
  479. data/doc/guide/src/basics/log_replay/goForward_1.png +0 -0
  480. data/doc/guide/src/basics/log_replay/goForward_2.png +0 -0
  481. data/doc/guide/src/basics/log_replay/goForward_3.png +0 -0
  482. data/doc/guide/src/basics/log_replay/goForward_5.png +0 -0
  483. data/doc/guide/src/basics/log_replay/plan_repair_4.png +0 -0
  484. data/doc/guide/src/basics/log_replay/roby_log_main_window.png +0 -0
  485. data/doc/guide/src/basics/relations_display.page +0 -203
  486. data/doc/guide/src/basics/shell.page +0 -102
  487. data/doc/guide/src/default.css +0 -319
  488. data/doc/guide/src/introduction/index.page +0 -29
  489. data/doc/guide/src/introduction/publications.page +0 -14
  490. data/doc/guide/src/relations/dependency.page +0 -89
  491. data/doc/guide/src/relations/index.page +0 -12
  492. data/ext/droby/dump.cc +0 -175
  493. data/ext/droby/extconf.rb +0 -3
  494. data/ext/graph/algorithm.cc +0 -746
  495. data/ext/graph/extconf.rb +0 -7
  496. data/ext/graph/graph.cc +0 -575
  497. data/ext/graph/graph.hh +0 -183
  498. data/ext/graph/iterator_sequence.hh +0 -102
  499. data/ext/graph/undirected_dfs.hh +0 -226
  500. data/ext/graph/undirected_graph.hh +0 -421
  501. data/lib/roby/app/scripts/generate/bookmarks.rb +0 -162
  502. data/lib/roby/app/scripts/replay.rb +0 -31
  503. data/lib/roby/app/scripts/server.rb +0 -18
  504. data/lib/roby/basic_object.rb +0 -151
  505. data/lib/roby/config.rb +0 -14
  506. data/lib/roby/distributed.rb +0 -36
  507. data/lib/roby/distributed/base.rb +0 -448
  508. data/lib/roby/distributed/communication.rb +0 -875
  509. data/lib/roby/distributed/connection_space.rb +0 -616
  510. data/lib/roby/distributed/distributed_object.rb +0 -206
  511. data/lib/roby/distributed/drb.rb +0 -62
  512. data/lib/roby/distributed/notifications.rb +0 -531
  513. data/lib/roby/distributed/peer.rb +0 -555
  514. data/lib/roby/distributed/protocol.rb +0 -529
  515. data/lib/roby/distributed/proxy.rb +0 -343
  516. data/lib/roby/distributed/subscription.rb +0 -311
  517. data/lib/roby/distributed/transaction.rb +0 -498
  518. data/lib/roby/external_process_task.rb +0 -225
  519. data/lib/roby/graph.rb +0 -160
  520. data/lib/roby/log.rb +0 -3
  521. data/lib/roby/log/chronicle.rb +0 -303
  522. data/lib/roby/log/console.rb +0 -74
  523. data/lib/roby/log/data_stream.rb +0 -275
  524. data/lib/roby/log/dot.rb +0 -279
  525. data/lib/roby/log/event_stream.rb +0 -161
  526. data/lib/roby/log/file.rb +0 -396
  527. data/lib/roby/log/gui/basic_display.ui +0 -83
  528. data/lib/roby/log/gui/basic_display_ui.rb +0 -89
  529. data/lib/roby/log/gui/chronicle.rb +0 -26
  530. data/lib/roby/log/gui/chronicle_view.rb +0 -40
  531. data/lib/roby/log/gui/chronicle_view.ui +0 -70
  532. data/lib/roby/log/gui/chronicle_view_ui.rb +0 -90
  533. data/lib/roby/log/gui/data_displays.rb +0 -171
  534. data/lib/roby/log/gui/data_displays.ui +0 -155
  535. data/lib/roby/log/gui/data_displays_ui.rb +0 -146
  536. data/lib/roby/log/gui/notifications.rb +0 -26
  537. data/lib/roby/log/gui/relations.rb +0 -269
  538. data/lib/roby/log/gui/relations.ui +0 -123
  539. data/lib/roby/log/gui/relations_ui.rb +0 -120
  540. data/lib/roby/log/gui/relations_view.rb +0 -185
  541. data/lib/roby/log/gui/relations_view.ui +0 -149
  542. data/lib/roby/log/gui/relations_view_ui.rb +0 -144
  543. data/lib/roby/log/gui/replay.rb +0 -366
  544. data/lib/roby/log/gui/replay_controls.rb +0 -206
  545. data/lib/roby/log/gui/replay_controls.ui +0 -282
  546. data/lib/roby/log/gui/replay_controls_ui.rb +0 -249
  547. data/lib/roby/log/gui/runtime.rb +0 -130
  548. data/lib/roby/log/hooks.rb +0 -186
  549. data/lib/roby/log/logger.rb +0 -203
  550. data/lib/roby/log/notifications.rb +0 -244
  551. data/lib/roby/log/plan_rebuilder.rb +0 -468
  552. data/lib/roby/log/relations.rb +0 -1084
  553. data/lib/roby/log/server.rb +0 -547
  554. data/lib/roby/log/sqlite.rb +0 -47
  555. data/lib/roby/log/timings.rb +0 -233
  556. data/lib/roby/plan-object.rb +0 -371
  557. data/lib/roby/planning.rb +0 -13
  558. data/lib/roby/planning/loops.rb +0 -309
  559. data/lib/roby/planning/model.rb +0 -1012
  560. data/lib/roby/planning/task.rb +0 -180
  561. data/lib/roby/query.rb +0 -655
  562. data/lib/roby/relations/conflicts.rb +0 -67
  563. data/lib/roby/relations/dependency.rb +0 -358
  564. data/lib/roby/relations/ensured.rb +0 -19
  565. data/lib/roby/relations/error_handling.rb +0 -22
  566. data/lib/roby/relations/events.rb +0 -7
  567. data/lib/roby/relations/executed_by.rb +0 -208
  568. data/lib/roby/relations/influence.rb +0 -10
  569. data/lib/roby/relations/planned_by.rb +0 -63
  570. data/lib/roby/state/information.rb +0 -55
  571. data/lib/roby/state/state.rb +0 -367
  572. data/lib/roby/task-operations.rb +0 -186
  573. data/lib/roby/task_index.rb +0 -80
  574. data/lib/roby/test/distributed.rb +0 -230
  575. data/lib/roby/test/tasks/simple_task.rb +0 -23
  576. data/lib/roby/transactions.rb +0 -507
  577. data/lib/roby/transactions/proxy.rb +0 -325
  578. data/plugins/fault_injection/History.txt +0 -4
  579. data/plugins/fault_injection/README.txt +0 -34
  580. data/plugins/fault_injection/Rakefile +0 -12
  581. data/plugins/fault_injection/TODO.txt +0 -0
  582. data/plugins/fault_injection/app.rb +0 -52
  583. data/plugins/fault_injection/fault_injection.rb +0 -89
  584. data/plugins/fault_injection/test/test_fault_injection.rb +0 -78
  585. data/plugins/subsystems/README.txt +0 -37
  586. data/plugins/subsystems/Rakefile +0 -13
  587. data/plugins/subsystems/app.rb +0 -182
  588. data/plugins/subsystems/test/app/README +0 -24
  589. data/plugins/subsystems/test/app/Rakefile +0 -8
  590. data/plugins/subsystems/test/app/config/app.yml +0 -71
  591. data/plugins/subsystems/test/app/config/init.rb +0 -12
  592. data/plugins/subsystems/test/app/config/roby.yml +0 -3
  593. data/plugins/subsystems/test/app/planners/main.rb +0 -20
  594. data/plugins/subsystems/test/app/scripts/distributed +0 -3
  595. data/plugins/subsystems/test/app/scripts/replay +0 -3
  596. data/plugins/subsystems/test/app/scripts/results +0 -3
  597. data/plugins/subsystems/test/app/scripts/run +0 -3
  598. data/plugins/subsystems/test/app/scripts/server +0 -3
  599. data/plugins/subsystems/test/app/scripts/shell +0 -3
  600. data/plugins/subsystems/test/app/scripts/test +0 -3
  601. data/plugins/subsystems/test/app/tasks/services.rb +0 -15
  602. data/plugins/subsystems/test/test_subsystems.rb +0 -78
  603. data/test/distributed/test_communication.rb +0 -195
  604. data/test/distributed/test_connection.rb +0 -284
  605. data/test/distributed/test_execution.rb +0 -378
  606. data/test/distributed/test_mixed_plan.rb +0 -341
  607. data/test/distributed/test_plan_notifications.rb +0 -238
  608. data/test/distributed/test_protocol.rb +0 -525
  609. data/test/distributed/test_query.rb +0 -106
  610. data/test/distributed/test_remote_plan.rb +0 -491
  611. data/test/distributed/test_transaction.rb +0 -466
  612. data/test/mockups/external_process +0 -28
  613. data/test/mockups/tasks.rb +0 -27
  614. data/test/planning/test_loops.rb +0 -432
  615. data/test/planning/test_model.rb +0 -427
  616. data/test/planning/test_task.rb +0 -126
  617. data/test/relations/test_conflicts.rb +0 -42
  618. data/test/relations/test_dependency.rb +0 -324
  619. data/test/relations/test_ensured.rb +0 -38
  620. data/test/relations/test_executed_by.rb +0 -224
  621. data/test/relations/test_planned_by.rb +0 -56
  622. data/test/suite_core.rb +0 -29
  623. data/test/suite_distributed.rb +0 -10
  624. data/test/suite_planning.rb +0 -4
  625. data/test/suite_relations.rb +0 -8
  626. data/test/tasks/test_external_process.rb +0 -126
  627. data/test/tasks/test_thread_task.rb +0 -70
  628. data/test/test_bgl.rb +0 -528
  629. data/test/test_event.rb +0 -969
  630. data/test/test_exceptions.rb +0 -591
  631. data/test/test_execution_engine.rb +0 -987
  632. data/test/test_gui.rb +0 -20
  633. data/test/test_interface.rb +0 -43
  634. data/test/test_log.rb +0 -125
  635. data/test/test_log_server.rb +0 -133
  636. data/test/test_plan.rb +0 -418
  637. data/test/test_query.rb +0 -424
  638. data/test/test_relations.rb +0 -260
  639. data/test/test_state.rb +0 -432
  640. data/test/test_support.rb +0 -16
  641. data/test/test_task.rb +0 -1181
  642. data/test/test_testcase.rb +0 -138
  643. data/test/test_transactions.rb +0 -610
  644. data/test/test_transactions_proxy.rb +0 -216
@@ -0,0 +1,43 @@
1
+ module Roby
2
+ class Task
3
+ def self.state
4
+ if !@state
5
+ if superclass.respond_to?(:state)
6
+ supermodel = superclass.state
7
+ end
8
+ @state = StateModel.new(supermodel)
9
+ end
10
+ @state
11
+ end
12
+
13
+ def state
14
+ @state ||= StateSpace.new(self.model.state)
15
+ end
16
+
17
+ def resolve_state_sources
18
+ model.state.resolve_data_sources(self, state)
19
+ end
20
+
21
+ def self.goal
22
+ if !@goal
23
+ if superclass.respond_to?(:goal)
24
+ supermodel = superclass.goal
25
+ end
26
+ @goal = GoalModel.new(self.state, supermodel)
27
+ end
28
+ @goal
29
+ end
30
+
31
+ def goal
32
+ @goal ||= GoalSpace.new(self.model.goal)
33
+ end
34
+
35
+ def resolve_goals
36
+ if !fully_instanciated?
37
+ raise ArgumentError, "cannot resolve goals on a task that is not fully instanciated"
38
+ end
39
+ self.model.goal.resolve_goals(self, self.goal)
40
+ end
41
+ end
42
+ end
43
+
@@ -1,75 +1,95 @@
1
- require 'roby/config'
1
+ require 'thread'
2
2
  require 'facets/string/camelcase'
3
3
  require 'facets/string/snakecase'
4
+ require 'facets/string/modulize'
4
5
  require 'facets/kernel/constant'
5
- require 'utilrb/enumerable'
6
6
  require 'utilrb/time/to_hms'
7
- require 'utilrb/module/cached_enum'
7
+ require 'utilrb/module/define_or_reuse'
8
8
  require 'utilrb/logger'
9
- require 'utilrb/gc/force'
10
- require 'utilrb/hash/to_sym_keys'
11
- require 'utilrb/array/to_s'
12
- require 'utilrb/hash/to_s'
13
- require 'utilrb/set/to_s'
9
+ require 'utilrb/marshal/load_with_missing_constants'
14
10
 
15
11
  class IO
16
12
  def ask(question, default, output_io = STDOUT)
17
- output_io.print question
18
- output_io.flush
19
- loop do
20
- answer = readline.chomp.downcase
21
- if answer.empty?
22
- return default
23
- elsif answer == 'y'
24
- return true
25
- elsif answer == 'n'
26
- return false
27
- else
28
- output_io.print "\nInvalid answer, try again: "
29
- output_io.flush
30
- end
31
- end
13
+ output_io.print question
14
+ output_io.flush
15
+ loop do
16
+ answer = readline.chomp.downcase
17
+ if answer.empty?
18
+ return default
19
+ elsif answer == 'y'
20
+ return true
21
+ elsif answer == 'n'
22
+ return false
23
+ else
24
+ output_io.print "\nInvalid answer, try again: "
25
+ output_io.flush
26
+ end
27
+ end
32
28
  end
33
29
  end
34
30
 
35
- module Enumerable
36
- def empty?
37
- for i in self
38
- return false
39
- end
40
- true
31
+ class Module
32
+ def each_fullfilled_model
33
+ return enum_for(__method__) if !block_given?
34
+ yield self
41
35
  end
42
36
  end
43
37
 
44
- class Module
45
- # :call-seq
46
- # define_under(name, value) -> value
47
- # define_under(name) { ... } -> value
48
- #
49
- # Defines a new constant under a given module
50
- # In the first form, the method gets its value from its argument.
51
- # In the second case, it calls the provided block
52
- def define_under(name, value = nil)
53
- if old = constants.find { |cn| cn == name.to_s }
54
- return const_get(old)
55
- else
56
- const_set(name, (value || yield))
38
+ class Object
39
+ def inspect
40
+ guard = (Thread.current[:ROBY_SUPPORT_INSPECT_RECURSION_GUARD] ||= Hash.new)
41
+ guard.compare_by_identity
42
+ if guard.has_key?(self)
43
+ return "..."
44
+ else
45
+ begin
46
+ guard[self] = self
47
+ to_s
48
+ ensure
49
+ guard.delete(self)
50
+ end
57
51
  end
58
52
  end
59
53
  end
60
54
 
55
+ class Set
56
+ def inspect
57
+ to_s
58
+ end
59
+
60
+ if !method_defined?(:intersect?)
61
+ def intersect?(set)
62
+ set.is_a?(Set) or raise ArgumentError, "value must be a set"
63
+ if size < set.size
64
+ any? { |o| set.include?(o) }
65
+ else
66
+ set.any? { |o| include?(o) }
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ module Enumerable
73
+ def empty?
74
+ for i in self
75
+ return false
76
+ end
77
+ true
78
+ end
79
+ end
80
+
61
81
  class Thread
62
82
  def send_to(object, name, *args, &prc)
63
- if Thread.current == self
64
- object.send(name, *args, &prc)
65
- else
66
- @msg_queue ||= Queue.new
67
- @msg_queue << [ object, name, args, prc ]
68
- end
83
+ if Thread.current == self
84
+ object.send(name, *args, &prc)
85
+ else
86
+ @msg_queue ||= Queue.new
87
+ @msg_queue << [ object, name, args, prc ]
88
+ end
69
89
  end
70
90
  def process_events
71
91
  @msg_queue ||= Queue.new
72
- loop do
92
+ loop do
73
93
  object, name, args, block = *@msg_queue.deq(true)
74
94
  object.send(name, *args, &block)
75
95
  end
@@ -78,116 +98,36 @@ def process_events
78
98
  end
79
99
 
80
100
  module Roby
81
- @logger = Logger.new(STDERR)
82
- @logger.level = Logger::WARN
83
- @logger.progname = "Roby"
84
- @logger.formatter = lambda { |severity, time, progname, msg| "#{time.to_hms} (#{progname}) #{msg}\n" }
85
-
86
- extend Logger::Hierarchy
87
- extend Logger::Forward
88
-
89
- class Pool < Queue
90
- def initialize(klass)
91
- @klass = klass
92
- super()
93
- end
94
-
95
- def pop
96
- value = super(true) rescue nil
97
- value || @klass.new
98
- end
99
- end
100
-
101
- @mutexes = Pool.new(Mutex)
102
- @condition_variables = Pool.new(ConditionVariable)
103
- class << self
104
- # A pool of mutexes (as a Queue)
105
- attr_reader :mutexes
106
- # A pool of condition variables (as a Queue)
107
- attr_reader :condition_variables
108
- end
109
-
110
- # call-seq:
111
- # condition_variable => cv
112
- # condition_variable(true) => cv, mutex
113
- # condition_variable { |cv| ... } => value returned by the block
114
- # condition_variable(true) { |cv, mutex| ... } => value returned by the block
115
- #
116
- # Get a condition variable object from the Roby.condition_variables
117
- # pool and, if mutex is not true, a Mutex object
118
- #
119
- # If a block is given, the two objects are yield and returned into the
120
- # pool after the block has returned. In that case, the method returns
121
- # the value returned by the block
122
- def self.condition_variable(mutex = false)
123
- cv = condition_variables.pop
124
-
125
- if block_given?
126
- begin
127
- if mutex
128
- mt = mutexes.pop
129
- yield(cv, mt)
130
- else
131
- yield(cv)
132
- end
133
-
134
- ensure
135
- return_condition_variable(cv, mt)
136
- end
101
+ def self.format_time(time, format = 'hms')
102
+ if format == 'sec'
103
+ time.to_f.to_s
104
+ elsif format == 'hms'
105
+ "#{time.strftime('%H:%M:%S.%3N')}"
137
106
  else
138
- if mutex
139
- return cv, mutexes.pop
140
- else
141
- return cv
142
- end
143
- end
144
- end
145
-
146
- # Returns a ConditionVariable and optionally a Mutex into the
147
- # Roby.condition_variables and Roby.mutexes pools
148
- def self.return_condition_variable(cv, mutex = nil)
149
- condition_variables.push cv
150
- if mutex
151
- mutexes.push mutex
107
+ "#{time.strftime(format)}"
152
108
  end
153
- nil
154
109
  end
155
110
 
156
- @global_lock = Mutex.new
157
- class << self
158
- # This Mutex object is locked during the event propagation loop, and
159
- # unlock while this loop is sleeping. It is used to wait for the
160
- # availability of the main plan.
161
- attr_reader :global_lock
162
- end
163
-
164
- def self.taken_global_lock?; Thread.current[:global_lock_taken] end
165
-
166
- # Implements a recursive behaviour on Roby.mutex
167
- def self.synchronize
168
- if Thread.current[:global_lock_taken]
169
- yield
170
- else
171
- global_lock.lock
172
- begin
173
- Thread.current[:global_lock_taken] = true
174
- yield
175
- ensure
176
- Thread.current[:global_lock_taken] = false
177
- global_lock.unlock
178
- end
179
- end
180
- end
111
+ extend Logger::Root('Roby', Logger::WARN) { |severity, time, progname, msg| "#{Roby.format_time(time)} (#{progname}) #{msg}\n" }
181
112
 
182
113
  class << self
183
114
  attr_accessor :enable_deprecation_warnings
115
+ attr_accessor :deprecation_warnings_are_errors
184
116
  end
185
117
  @enable_deprecation_warnings = true
118
+ @deprecation_warnings_are_errors = (ENV['ROBY_ALL_DEPRECATIONS_ARE_ERRORS'] == '1')
186
119
 
187
- def self.warn_deprecated(msg)
188
- if enable_deprecation_warnings
189
- Roby.warn "Deprecation Warning: #{msg} at #{caller[1]}"
120
+ def self.warn_deprecated(msg, caller_depth = 1)
121
+ if deprecation_warnings_are_errors
122
+ error_deprecated(msg, caller_depth)
123
+ elsif enable_deprecation_warnings
124
+ Roby.warn "Deprecation Warning: #{msg} at #{caller[1, caller_depth].join("\n")}"
190
125
  end
191
126
  end
127
+
128
+ def self.error_deprecated(msg, caller_depth = 1)
129
+ Roby.fatal "Deprecation Error: #{msg} at #{caller[1, caller_depth].join("\n")}"
130
+ raise NotImplementedError
131
+ end
192
132
  end
193
133
 
@@ -1,390 +1,5 @@
1
1
  module Roby
2
- class TaskModelTag < Module
3
- module ClassExtension
4
- # Returns the list of static arguments required by this task model
5
- def arguments(*new_arguments)
6
- new_arguments.each do |arg_name|
7
- arg_name = arg_name.to_sym
8
- argument_set << arg_name.to_sym
9
- unless method_defined?(arg_name)
10
- define_method(arg_name) { arguments[arg_name] }
11
- define_method("#{arg_name}=") { |value| arguments[arg_name] = value }
12
- end
13
- end
14
-
15
- @argument_enumerator ||= enum_for(:each_argument_set)
16
- end
17
- # Declares a set of arguments required by this task model
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
40
- end
41
- include TaskModelTag::ClassExtension
42
-
43
- def initialize(&block)
44
- super do
45
- inherited_enumerable("argument_set", "argument_set") { ValueSet.new }
46
- unless const_defined? :ClassExtension
47
- const_set(:ClassExtension, Module.new)
48
- end
49
-
50
- self::ClassExtension.include TaskModelTag::ClassExtension
51
- end
52
- class_eval(&block) if block_given?
53
- end
54
-
55
- def clear_model
56
- @argument_set.clear if @argument_set
57
- end
58
- end
59
-
60
- # Base class for task events
61
- # When events are emitted, then the created object is
62
- # an instance of a class derived from this one
63
- class TaskEvent < Event
64
- # The task which fired this event
65
- attr_reader :task
66
-
67
- def model; self.class end
68
-
69
- def initialize(task, generator, propagation_id, context, time = Time.now)
70
- @task = task
71
- @terminal_flag = generator.terminal_flag
72
- super(generator, propagation_id, context, time)
73
- end
74
-
75
- # Returns the set of events from the task that are the cause of this
76
- # event
77
- def task_sources
78
- result = ValueSet.new
79
- event_sources = sources
80
- for ev in event_sources
81
- gen = ev.generator
82
- if gen.respond_to?(:task) && gen.task == task
83
- result.merge ev.task_sources
84
- end
85
- end
86
- if result.empty?
87
- result << self
88
- end
89
-
90
- result
91
- end
92
-
93
- def to_s
94
- "#{generator.to_s}@#{propagation_id} [#{time.to_hms}]: #{context}"
95
- end
96
-
97
- def pretty_print(pp)
98
- pp.text "at [#{time.to_hms}/#{propagation_id}] in the "
99
- generator.pretty_print(pp)
100
- pp.breakable
101
- pp.group(2) do
102
- pp.seplist(context || []) { |v| v.pretty_print pp }
103
- end
104
- end
105
-
106
- # If the event model defines a controlable event
107
- # By default, an event is controlable if the model
108
- # responds to #call
109
- def self.controlable?; respond_to?(:call) end
110
- # If the event is controlable
111
- def controlable?; model.controlable? end
112
- class << self
113
- # Called by Task.update_terminal_flag to update the flag
114
- attr_writer :terminal
115
- end
116
- # If the event model defines a terminal event
117
- def self.terminal?; @terminal end
118
- # If this event is terminal
119
- def success?; @terminal_flag == :success end
120
- # If this event is terminal
121
- def failure?; @terminal_flag == :failure end
122
- # If this event is terminal
123
- def terminal?; @terminal_flag end
124
- # The event symbol
125
- def self.symbol; @symbol end
126
- # The event symbol
127
- def symbol; model.symbol end
128
- end
129
-
130
- # A task event model bound to a particular task instance
131
- # The Task/TaskEvent/TaskEventGenerator relationship is
132
- # comparable to the Class/UnboundMethod/Method one:
133
- # * a Task object is a model for a task, a Class in a model for an object
134
- # * a TaskEvent object is a model for an event instance (the instance being unspecified),
135
- # an UnboundMethod is a model for an instance method
136
- # * a TaskEventGenerator object represents a particular event model
137
- # *bound* to a particular task instance, a Method object represents a particular method
138
- # bound to a particular object
139
- class TaskEventGenerator < EventGenerator
140
- # The task we are part of
141
- attr_reader :task
142
- # The event symbol (its name as a Symbol object)
143
- attr_reader :symbol
144
- # The event class
145
- attr_reader :event_model
146
- def initialize(task, model)
147
- @task, @event_model = task, model
148
- @symbol = model.symbol
149
- super(model.respond_to?(:call))
150
- end
151
-
152
- def default_command(context)
153
- event_model.call(task, context)
154
- end
155
-
156
- # See PlanObject::child_plan_object.
157
- child_plan_object :task
158
-
159
- # The event plan. It is the same as task.plan and is actually updated
160
- # by task.plan=. It is redefined here for performance reasons.
161
- attr_accessor :plan
162
-
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
-
169
- def fire(event)
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
179
- super
180
- end
181
-
182
- # See EventGenerator#calling
183
- #
184
- # In TaskEventGenerator, this hook checks that the task is running
185
- def calling(context)
186
- super if defined? super
187
- if task.finished? && !terminal?
188
- raise CommandFailed.new(nil, self),
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}."
190
- elsif task.pending? && symbol != :start
191
- raise CommandFailed.new(nil, self),
192
- "#{symbol}!(#{context}) called by #{plan.engine.propagation_sources.to_a} but the task has never been started"
193
- elsif task.running? && symbol == :start
194
- raise CommandFailed.new(nil, self),
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}."
196
- end
197
- end
198
-
199
- # See EventGenerator#fired
200
- #
201
- # In TaskEventGenerator, this hook calls the unreachable handlers added
202
- # by EventGenerator#if_unreachable when the task has finished, not
203
- # before
204
- def fired(event)
205
- super if defined? super
206
-
207
- if symbol == :stop
208
- task.each_event { |ev| ev.unreachable!(task.terminal_event) }
209
- end
210
- end
211
-
212
- def related_tasks(result = nil)
213
- tasks = super
214
- tasks.delete(task)
215
- tasks
216
- end
217
-
218
- def each_handler
219
- super
220
-
221
- if self_owned?
222
- task.each_handler(event_model.symbol) { |o| yield(o) }
223
- end
224
- end
225
- def each_precondition
226
- super
227
- task.each_precondition(event_model.symbol) { |o| yield(o) }
228
- end
229
-
230
- def controlable?; event_model.controlable? end
231
- attr_accessor :terminal_flag
232
- def terminal?; !!@terminal_flag end
233
- def success?; @terminal_flag == :success end
234
- def failure?; @terminal_flag == :failure end
235
- def added_child_object(child, relations, info)
236
- super if defined? super
237
-
238
- if relations.include?(EventStructure::CausalLink) &&
239
- child.respond_to?(:task) && child.task == task &&
240
- child.terminal_flag != terminal_flag
241
-
242
- task.update_terminal_flag
243
- end
244
- end
245
- def removed_child_object(child, relations)
246
- super if defined? super
247
-
248
- if relations.include?(EventStructure::CausalLink) &&
249
- child.respond_to?(:task) && child.task == task &&
250
- terminal_flag
251
-
252
- task.update_terminal_flag
253
- end
254
- end
255
- def new(context); event_model.new(task, self, plan.engine.propagation_id, context) end
256
-
257
- def to_s
258
- "#{task}/#{symbol}"
259
- end
260
- def inspect
261
- "#{task.inspect}/#{symbol}: #{history.to_s}"
262
- end
263
- def pretty_print(pp)
264
- pp.text "#{symbol} event of #{task.class}:0x#{task.address.to_s(16)}"
265
- end
266
-
267
- def achieve_with(obj)
268
- child_task, child_event = case obj
269
- when Roby::Task: [obj, obj.event(:success)]
270
- when Roby::TaskEventGenerator: [obj.task, obj]
271
- end
272
-
273
- if child_task
274
- unless task.depends_on?(child_task, false)
275
- task.depends_on child_task,
276
- :success => [child_event.symbol],
277
- :remove_when_done => true
278
- end
279
- super(child_event)
280
- else
281
- super(obj)
282
- end
283
- end
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
-
292
- # Checks that the event can be called. Raises various exception
293
- # when it is not the case.
294
- def check_call_validity
295
- super
296
- rescue EventNotExecutable => e
297
- refine_exception(e)
298
- end
299
-
300
- # Checks that the event can be emitted. Raises various exception
301
- # when it is not the case.
302
- def check_emission_validity
303
- super
304
- rescue EventNotExecutable => e
305
- refine_exception(e)
306
- end
307
-
308
-
309
- def refine_exception (e)
310
- if task.partially_instanciated?
311
- raise EventNotExecutable.new(self), "#{symbol}! called on #{task} which is partially instanciated\n" +
312
- "The following arguments were not set: \n" +
313
- task.list_unset_arguments.map {|n| "\t#{n}"}.join("\n")+"\n"
314
- #
315
- elsif !plan
316
- raise EventNotExecutable.new(self), "#{symbol}! called on #{task} but the task is in no plan"
317
- elsif !plan.executable?
318
- raise EventNotExecutable.new(self), "#{symbol}! called on #{task} but the plan is not executable"
319
- elsif task.abstract?
320
- raise EventNotExecutable.new(self), "#{symbol}! called on #{task} but the task is abstract"
321
- else
322
- raise EventNotExecutable.new(self), "#{symbol}! called on #{task} which is not executable: #{e.message}"
323
- end
324
- end
325
-
326
- end
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.
334
- class TaskArguments < Hash
335
- private :delete, :delete_if
336
-
337
- attr_reader :task
338
- def initialize(task)
339
- @task = task
340
- super()
341
- end
342
-
343
- def writable?(key)
344
- !(has_key?(key) && task.model.arguments.include?(key))
345
- end
346
-
347
- def dup; self.to_hash end
348
- def to_hash
349
- inject({}) { |h, (k, v)| h[k] = v ; h }
350
- end
351
-
352
- alias :update! :[]=
353
- def []=(key, value)
354
- key = key.to_sym if key.respond_to?(:to_str)
355
- if writable?(key)
356
- if !task.read_write?
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}"
358
- end
359
-
360
- updating
361
- super
362
- updated
363
- else
364
- raise ArgumentError, "cannot override task arguments"
365
- end
366
- end
367
- def updating; super if defined? super end
368
- def updated; super if defined? super end
369
-
370
- def [](key)
371
- key = key.to_sym if key.respond_to?(:to_str)
372
- super(key)
373
- end
374
-
375
- alias :do_merge! :merge!
376
- def merge!(hash)
377
- super do |key, old, new|
378
- if old == new then old
379
- elsif writable?(key) then new
380
- else
381
- raise ArgumentError, "cannot override task argument #{key}: trying to replace #{old} by #{new}"
382
- end
383
- end
384
- end
385
- end
386
-
387
- # In a plan, Task objects represent the activities of the robot.
2
+ # In a plan, Task objects represent the system's activities.
388
3
  #
389
4
  # === Task models
390
5
  #
@@ -415,7 +30,7 @@ def merge!(hash)
415
30
  # end
416
31
  #
417
32
  # event :other_event do |context|
418
- # engine.once { emit :other_event }
33
+ # execution_engine.once { emit :other_event }
419
34
  # end
420
35
  # end
421
36
  #
@@ -425,7 +40,7 @@ def merge!(hash)
425
40
  #
426
41
  # === Task relations
427
42
  #
428
- # Task relations are defined in the TaskStructure RelationSpace instance.
43
+ # Task relations are defined in the TaskStructure Relations::Space instance.
429
44
  # See TaskStructure documentation for the list of special methods defined
430
45
  # by the various graphs, and the TaskStructure namespace for the name and
431
46
  # purpose of the various relation graphs themselves.
@@ -467,713 +82,636 @@ def merge!(hash)
467
82
  # execution) cannot become non-terminal. Nonetheless, a non-terminal
468
83
  # event can become terminal.
469
84
  #
85
+ # @!macro InstanceHandlerOptions
86
+ # @option options [:copy,:drop] :on_replace defines the behaviour
87
+ # when this object gets replaced in the plan. If :copy is used,
88
+ # the handler is added to the replacing task and is also kept
89
+ # in the original task. If :drop, it is not copied (but is
90
+ # kept).
470
91
  class Task < PlanObject
471
- unless defined? RootTaskTag
472
- RootTaskTag = TaskModelTag.new
473
- include RootTaskTag
474
- end
475
-
476
- # Clears all definitions saved in this model. This is to be used by the
477
- # reloading code
478
- def self.clear_model
479
- class_eval do
480
- # Remove event models
481
- events.each_key do |ev_symbol|
482
- remove_const ev_symbol.to_s.camelcase(true)
483
- end
484
-
485
- [@events, @signal_sets, @forwarding_sets, @causal_link_sets,
486
- @argument_set, @handler_sets, @precondition_sets].each do |set|
487
- set.clear if set
488
- end
489
- end
490
- end
491
-
492
- # Declares an attribute set which follows the task models inheritance
493
- # hierarchy. Define the corresponding enumeration methods as well.
494
- #
495
- # For instance,
496
- # model_attribute_list 'signal'
497
- #
498
- # defines the model-level signals, which can be accessed through
499
- # .each_signal(model)
500
- # .signals(model)
501
- # #each_signal(model)
502
- #
503
- def self.model_attribute_list(name) # :nodoc:
504
- inherited_enumerable("#{name}_set", "#{name}_sets", :map => true) { Hash.new { |h, k| h[k] = ValueSet.new } }
505
- class_eval <<-EOD
506
- def self.each_#{name}(model)
507
- for obj in #{name}s(model)
508
- yield(obj)
509
- end
510
- self
511
- end
512
- def self.#{name}s(model)
513
- result = ValueSet.new
514
- each_#{name}_set(model, false) do |set|
515
- result.merge set
516
- end
517
- result
518
- end
519
- def each_#{name}(model); self.model.each_#{name}(model) { |o| yield(o) } end
520
- EOD
521
- end
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.
92
+ extend Models::Task
93
+ provides TaskService
539
94
 
540
- ##
541
- # :method: each_signal
542
- # :call-seq:
543
- # task.each_signal(event_model) do |target_event_model|
544
- # end
95
+ # The task arguments
545
96
  #
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]
97
+ # @return [TaskArguments]
98
+ attr_reader :arguments
99
+
100
+ # The accumulated history of this task
557
101
  #
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
102
+ # This is the list of events that this task ever emitted, sorted by
103
+ # emission time (oldest first)
565
104
  #
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.
105
+ # @return [Array<Event>]
106
+ attr_reader :history
107
+
108
+ # The part of {#arguments} that is meaningful for this task model. I.e.
109
+ # it returns the set of elements in {#arguments} that are listed in the
110
+ # task model
111
+ def meaningful_arguments(task_model = self.model)
112
+ task_model.meaningful_arguments(arguments)
113
+ end
569
114
 
570
- ##
571
- # :method: each_forwarding
572
- # :call-seq:
573
- # task.each_forwarding(event_model) do |target_event_model|
574
- # end
115
+ # @api private
575
116
  #
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
-
117
+ # Evaluate delayed arguments, and replace in {#arguments} the ones that
118
+ # currently have a value
119
+ def freeze_delayed_arguments
120
+ if !arguments.static?
121
+ result = Hash.new
122
+ arguments.each do |key, value|
123
+ if TaskArguments.delayed_argument?(value)
124
+ catch(:no_value) do
125
+ result[key] = value.evaluate_delayed_argument(self)
126
+ end
127
+ end
128
+ end
129
+ assign_arguments(**result)
130
+ end
131
+ end
582
132
 
583
- ##
584
- # :singleton-method: causal_links
585
- # :call-seq:
586
- # task_model.causal_links(event_model) => [target_event_models]
133
+ # The task name
587
134
  #
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
135
+ # @return [String]
136
+ def name
137
+ return @name if @name
138
+ name = "#{model.name || self.class.name}:0x#{address.to_s(16)}"
139
+ if !frozen?
140
+ @name = name
141
+ end
142
+ name
143
+ end
144
+
145
+ # Whether the task is a mission for its owners.
595
146
  #
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.
147
+ # If you want to know if it a mission for the local system, use
148
+ # Plan#mission_task?. In non-distributed Roby, the two are identical
149
+ attr_predicate :mission?, true
150
+
151
+ def inspect
152
+ state = if pending? then 'pending'
153
+ elsif failed_to_start? then 'failed to start'
154
+ elsif starting? then 'starting'
155
+ elsif running? then 'running'
156
+ elsif finishing? then 'finishing'
157
+ else 'finished'
158
+ end
159
+ "#<#{to_s} executable=#{executable?} state=#{state} plan=#{plan.to_s}>"
160
+ end
599
161
 
600
- ##
601
- # :method: each_causal_link
602
- # :call-seq:
603
- # task.each_causal_link(event_model) do |target_event_model|
604
- # end
162
+ # @api private
605
163
  #
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]
164
+ # Helper to assign multiple argument values at once
617
165
  #
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
166
+ # It differs from calling assign_argument in a loop in two ways:
167
+ #
168
+ # - it is common for subclasses to define a high-level argument that is,
169
+ # in the end, propagated to lower-level arguments. This method handles
170
+ # the fact that, when doing this, one will get parallel assignment of
171
+ # the high-level and low-level values during e.g. log replay which would
172
+ # fail in assign_arguments since arguments are single-assignation
625
173
  #
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.
174
+ # - assignation is all-or-nothing
175
+ def assign_arguments(**arguments)
176
+ initial_arguments = @arguments
177
+ initial_set_arguments = initial_arguments.assigned_arguments
178
+ current_arguments = initial_set_arguments.dup
629
179
 
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
-
640
- model_attribute_list('signal')
641
- model_attribute_list('forwarding')
642
- model_attribute_list('causal_link')
643
- model_attribute_list('handler')
644
- model_attribute_list('precondition')
645
-
646
- # The task arguments as symbol => value associative container
647
- attr_reader :arguments
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
652
- def meaningful_arguments(task_model = self.model)
653
- arguments.slice(*task_model.arguments)
654
- end
655
-
656
- # The task name
657
- def name
658
- @name ||= "#{model.name || self.class.name}:0x#{address.to_s(16)}"
659
- end
660
-
661
- # This predicate is true if this task is a mission for its owners. If
662
- # you want to know if it a mission for the local pDB, use Plan#mission?
663
- attr_predicate :mission?, true
664
-
665
- def inspect
666
- state = if pending? then 'pending'
667
- elsif failed_to_start? then 'failed to start'
668
- elsif starting? then 'starting'
669
- elsif running? then 'running'
670
- elsif finishing? then 'finishing'
671
- else 'finished'
672
- end
673
- "#<#{to_s} executable=#{executable?} state=#{state} plan=#{plan.to_s}>"
674
- end
675
-
676
- # Builds a task object using this task model
677
- #
678
- # The task object can be configured by a given block. After the
679
- # block is called, two things are checked:
680
- # * the task shall have a +start+ event
681
- # * the task shall have at least one terminal event. If no +stop+ event
682
- # is defined, then all terminal events are aliased to +stop+
683
- def initialize(arguments = Hash.new) #:yields: task_object
684
- super() if defined? super
685
-
686
- @arguments = TaskArguments.new(self)
180
+ # First assign normal values
687
181
  arguments.each do |key, value|
182
+ @arguments = TaskArguments.new(self)
183
+ @arguments.merge!(initial_set_arguments)
184
+ assign_argument(key, value)
185
+ current_arguments.merge!(@arguments) do |k, v1, v2|
186
+ if v1 != v2
187
+ raise ArgumentError, "trying to override #{k}=#{v1} to #{v2}"
188
+ end
189
+ v1
190
+ end
191
+ end
192
+ initial_arguments.merge!(current_arguments)
193
+
194
+ ensure
195
+ @arguments = initial_arguments
196
+ end
197
+
198
+ # @api private
199
+ #
200
+ # Sets one of this task's arguments
201
+ def assign_argument(key, value)
202
+ key = key.to_sym
203
+ if TaskArguments.delayed_argument?(value)
204
+ @arguments[key] = value
205
+ else
688
206
  if self.respond_to?("#{key}=")
689
207
  self.send("#{key}=", value)
690
- else
208
+ end
209
+ if @arguments.writable?(key, value)
210
+ # The accessor did not write the argument. That's alright
691
211
  @arguments[key] = value
692
212
  end
693
213
  end
214
+ end
694
215
 
695
- @model = self.class
216
+
217
+ # Create a new task object
218
+ #
219
+ # @param [Plan] plan the plan this task should be added two. The default
220
+ # is to add it to its own TemplatePlan object
221
+ # @param [Hash<Symbol,Object>] arguments assignation to task arguments
222
+ def initialize(plan: TemplatePlan.new, **arguments)
223
+ @bound_events = Hash.new
224
+ super(plan: plan)
225
+
226
+ @model = self.class
227
+ @abstract = @model.abstract?
228
+
229
+ @failed_to_start = false
230
+ @pending = true
231
+ @started = false
232
+ @running = false
233
+ @starting = false
234
+ @finished = false
235
+ @finishing = false
236
+ @success = nil
237
+ @reusable = true
238
+ @history = Array.new
239
+ @coordination_objects = Array.new
240
+
241
+ @arguments = TaskArguments.new(self)
242
+ assign_arguments(**arguments)
243
+ # Now assign default values for the arguments that have not yet been
244
+ # set
245
+ model.arguments.each do |argname|
246
+ next if @arguments.has_key?(argname)
247
+
248
+ has_default, default = model.default_argument(argname)
249
+ if has_default
250
+ assign_argument(argname, default)
251
+ end
252
+ end
696
253
 
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.
702
- initialize_events
703
- end
254
+ @poll_handlers = []
255
+ @execute_handlers = []
704
256
 
257
+ initialize_events
258
+ plan.register_task(self)
259
+ template = self.model.template
705
260
 
706
- # Lists all arguments, that are set to be needed via the :argument
707
- # syntax but are not set.
261
+ mappings = Hash.new
262
+ template.events_by_name.each do |name, template_event|
263
+ mappings[template_event] = bound_events[name]
264
+ end
265
+ template.copy_relation_graphs_to(plan, mappings)
266
+ apply_terminal_flags(
267
+ template.terminal_events.map(&mappings.method(:[])),
268
+ template.success_events.map(&mappings.method(:[])),
269
+ template.failure_events.map(&mappings.method(:[])))
270
+ @terminal_flag_invalid = false
271
+
272
+ if self.model.state_machine
273
+ @state_machine = TaskStateMachine.new(self.model.state_machine)
274
+ end
275
+ end
276
+
277
+ # Retrieve the current state of the task
708
278
  #
709
- # This is needed for debugging purposes.
710
- def list_unset_arguments # :nodoc:
711
- ret = Array.new
712
- model.arguments.each { |name|
713
- if !arguments.has_key?(name) then
714
- ret << name
715
- end }
716
- ret
279
+ # Can be one of the core states: pending, failed_to_start, starting,
280
+ # started, running, finishing, succeeded or failed
281
+ #
282
+ # If the task has a state machine defined with
283
+ # {TaskStateHelper#refine_running_state}, the state
284
+ # machine's current state will be returned in place of :running
285
+ #
286
+ # @return [Symbol]
287
+ def current_state
288
+ # Started and not finished
289
+ if running?
290
+ if respond_to?("state_machine")
291
+ # state_machine.status # => String
292
+ # state_machine.status_name # => Symbol
293
+ return state_machine.status_name
294
+ else
295
+ return :running
296
+ end
297
+ end
298
+
299
+ # True, when task has never been started
300
+ if pending?
301
+ return :pending
302
+ elsif failed_to_start?
303
+ return :failed_to_start
304
+ elsif starting?
305
+ return :starting
306
+ # True, when terminal event is pending
307
+ elsif finishing?
308
+ return :finishing
309
+ # Terminated with success or failure
310
+ elsif success?
311
+ return :succeeded
312
+ elsif failed?
313
+ return :failed
314
+ end
315
+ end
316
+
317
+ # Test if that current state corresponds to the provided state (symbol)
318
+ #
319
+ # @param [Symbol] state
320
+ # @return [Boolean]
321
+ def current_state?(state)
322
+ return state == current_state.to_sym
717
323
  end
718
324
 
719
325
  # Helper methods which creates all the necessary TaskEventGenerator
720
326
  # objects and stores them in the #bound_events map
721
- def initialize_events # :nodoc:
722
- @instantiated_model_events = false
723
-
724
- # Create all event generators
725
- bound_events = Hash.new
726
- model.each_event do |ev_symbol, ev_model|
727
- bound_events[ev_symbol.to_sym] = TaskEventGenerator.new(self, ev_model)
728
- end
729
- @bound_events = bound_events
327
+ def initialize_events # :nodoc:
328
+ # Create all event generators
329
+ bound_events = Hash.new
330
+ model.each_event do |ev_symbol, ev_model|
331
+ ev = TaskEventGenerator.new(self, ev_model)
332
+ ev.plan = plan
333
+ bound_events[ev_symbol.to_sym] = ev
334
+ end
335
+ @bound_events = bound_events
730
336
  end
337
+ private :initialize_events
731
338
 
732
- def model; self.class end
339
+ # (see PlanObject#promise)
340
+ #
341
+ # @raise [PromiseInFinishedTask] if attempting to create a promise on a
342
+ # task that is either finished, or failed to start
343
+ def promise(description: "#{self}.promise", executor: promise_executor, &block)
344
+ if failed_to_start?
345
+ raise PromiseInFinishedTask, "attempting to create a promise on #{self} that has failed to start"
346
+ elsif finished?
347
+ raise PromiseInFinishedTask, "attempting to create a promise on #{self} that is finished"
348
+ end
349
+ super
350
+ end
733
351
 
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
352
+ # Returns for how many seconds this task is running. Returns nil if
353
+ # the task is not running.
354
+ def lifetime
355
+ if running?
356
+ Time.now - start_time
357
+ elsif finished?
358
+ end_time - start_time
359
+ end
360
+ end
741
361
 
742
362
  # Returns when this task has been started
743
363
  def start_time
744
- if running?
745
- history.first.time
364
+ if ev = start_event.last
365
+ ev.time
746
366
  end
747
367
  end
748
368
 
369
+ # Returns when this task has finished
370
+ def end_time
371
+ if ev = stop_event.last
372
+ ev.time
373
+ end
374
+ end
375
+
376
+ # The last event emitted by this task
377
+ #
378
+ # @return [TaskEvent,nil]
379
+ def last_event
380
+ history.last
381
+ end
382
+
749
383
  def create_fresh_copy
750
- model.new(arguments.dup)
751
- end
752
-
753
- def initialize_copy(old) # :nodoc:
754
- super
755
-
756
- @name = nil
757
- @history = old.history.dup
758
-
759
- @arguments = TaskArguments.new(self)
760
- arguments.do_merge! old.arguments
761
- arguments.instance_variable_set(:@task, self)
762
-
763
- initialize_events
764
- plan.add(self)
765
- end
766
-
767
- def instantiate_model_event_relations
768
- return if @instantiated_model_events
769
- # Add the model-level signals to this instance
770
- @instantiated_model_events = true
771
-
772
- left_border = bound_events.values.to_value_set
773
- right_border = bound_events.values.to_value_set
774
-
775
- model.each_signal_set do |generator, signalled_events|
776
- next if signalled_events.empty?
777
- generator = bound_events[generator]
778
- right_border.delete(generator)
779
-
780
- for signalled in signalled_events
781
- signalled = bound_events[signalled]
782
- generator.signals signalled
783
- left_border.delete(signalled)
784
- end
785
- end
786
-
787
-
788
- model.each_forwarding_set do |generator, signalled_events|
789
- next if signalled_events.empty?
790
- generator = bound_events[generator]
791
- right_border.delete(generator)
792
-
793
- for signalled in signalled_events
794
- signalled = bound_events[signalled]
795
- generator.forward_to signalled
796
- left_border.delete(signalled)
797
- end
798
- end
799
-
800
- model.each_causal_link_set do |generator, signalled_events|
801
- next if signalled_events.empty?
802
- generator = bound_events[generator]
803
- right_border.delete(generator)
804
-
805
- for signalled in signalled_events
806
- signalled = bound_events[signalled]
807
- generator.add_causal_link signalled
808
- left_border.delete(signalled)
809
- end
810
- end
811
-
812
- update_terminal_flag
813
-
814
- # WARNING: this works only because:
815
- # * there is always at least updated_data as an intermediate event
816
- # * there is always one terminal event which is not stop
817
- start_event = bound_events[:start]
818
- stop_event = bound_events[:stop]
819
- left_border.delete(start_event)
820
- right_border.delete(start_event)
821
- left_border.delete(stop_event)
822
- right_border.delete(stop_event)
823
-
824
- for generator in left_border
825
- start_event.add_precedence(generator) unless generator.terminal?
826
- end
827
-
828
- # WARN: the start event CAN be terminal: it can be a signal from
829
- # :start to a terminal event
830
- #
831
- # Create the precedence relations between 'normal' events and the terminal events
832
- for terminal in left_border
833
- next unless terminal.terminal?
834
- for generator in right_border
835
- unless generator.terminal?
836
- generator.add_precedence(terminal)
837
- end
838
- end
839
- end
840
- end
841
-
842
- def plan=(new_plan) # :nodoc:
843
- if plan != new_plan
844
- if plan && plan.include?(self)
845
- raise ModelViolation.new, "still included in #{plan}, cannot change the plan"
846
- elsif self_owned? && running?
847
- raise ModelViolation.new, "cannot change the plan of a running task"
848
- end
849
- end
850
-
851
- super
852
-
853
- for _, ev in bound_events
854
- ev.plan = plan
855
- end
856
- end
857
-
858
- class << self
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?
875
- def abstract
876
- @abstract = true
877
- end
878
-
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.
887
- def terminates
888
- event :failed, :command => true, :terminal => true
889
- interruptible
890
- end
891
-
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.
897
- def interruptible
898
- if !has_event?(:failed) || !event_model(:failed).controlable?
899
- raise ArgumentError, "failed is not controlable"
900
- end
901
- event(:stop) do |context|
902
- if starting?
903
- on :start, self, :stop
904
- return
905
- end
906
- failed!(context)
907
- end
908
- end
909
-
910
- def setup_poll_method(block) # :nodoc:
911
- define_method(:poll) do |plan|
912
- return unless self_owned?
913
- begin
914
- poll_handler
915
- rescue Exception => e
916
- emit :failed, e
917
- end
918
- end
919
-
920
- define_method(:poll_handler, &block)
921
- end
922
-
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.
934
- def poll(&block)
935
- if !block_given?
936
- raise "no block given"
937
- end
938
-
939
- setup_poll_method(block)
940
-
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)) }
943
- end
944
- end
945
-
946
- # Roby::Task is an abstract model. See Task::abstract
947
- abstract
948
-
949
- # Returns true if this task is from an abstract model. If it is the
950
- # case, the task is not executable.
384
+ model.new(arguments.dup)
385
+ end
386
+
387
+ def initialize_copy(old) # :nodoc:
388
+ super
389
+
390
+ @name = nil
391
+
392
+ @arguments = TaskArguments.new(self)
393
+ arguments.force_merge! old.arguments
394
+ arguments.instance_variable_set(:@task, self)
395
+
396
+ @instantiated_model_events = false
397
+
398
+ # Create all event generators
399
+ @bound_events = Hash.new
400
+ @execute_handlers = old.execute_handlers.dup
401
+ @poll_handlers = old.poll_handlers.dup
402
+ if m = old.instance_variable_get(:@fullfilled_model)
403
+ @fullfilled_model = m.dup
404
+ end
405
+ end
406
+
407
+ def plan=(new_plan) # :nodoc:
408
+ super
409
+
410
+ @relation_graphs =
411
+ if plan then plan.task_relation_graphs
412
+ end
413
+ for ev in bound_events.each_value
414
+ ev.plan = plan
415
+ end
416
+ end
417
+
418
+ # Roby::Task is an abstract model. See Models::Task#abstract
419
+ abstract
420
+
421
+ ## :method:abstract?
951
422
  #
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
423
+ # If true, this instance is marked as abstract, i.e. as a placeholder
424
+ # for future actions.
425
+ #
426
+ # By default, it takes the value of its model, i.e. through
427
+ # model.abstract, set by calling abstract in a task model definition as
428
+ # in
429
+ #
430
+ # class MyModel < Roby::Task
431
+ # abstract
432
+ # end
433
+ #
434
+ # It can also be overriden on a per instance basis with
435
+ #
436
+ # task.abstract = <value>
437
+ #
438
+ attr_predicate :abstract?, true
439
+
440
+ # True if this task is executable. A task is not executable if it is
955
441
  # abstract or partially instanciated.
956
442
  #
957
- # See #abstract? and #partially_instanciated?
958
- def executable?; !abstract? && !partially_instanciated? && super end
959
- # Returns true if this task's stop event is controlable
960
- def interruptible?; event(:stop).controlable? end
961
- # Set the executable flag. executable cannot be set to +false+ if the
962
- # task is running, and cannot be set to true on a finished task.
963
- def executable=(flag)
964
- return if flag == @executable
965
- return unless self_owned?
966
- if flag && !pending?
967
- raise ModelViolation, "cannot set the executable flag on a task which is not pending"
968
- elsif !flag && running?
969
- raise ModelViolation, "cannot unset the executable flag on a task which is running"
970
- end
971
- super
972
- end
973
-
974
- # True if all arguments defined by Task.argument on the task model are set.
975
- def fully_instanciated?
976
- @fully_instanciated ||= model.arguments.all? { |name| arguments.has_key?(name) }
977
- end
443
+ # @see abstract? partially_instanciated?
444
+ def executable?
445
+ if @executable == true
446
+ true
447
+ elsif @executable.nil?
448
+ (!abstract? && !partially_instanciated? && super)
449
+ end
450
+ end
451
+
452
+ # Returns true if this task's stop event is controlable
453
+ def interruptible?; stop_event.controlable? end
454
+ # Set the executable flag. executable cannot be set to +false+ if the
455
+ # task is running, and cannot be set to true on a finished task.
456
+ def executable=(flag)
457
+ return if flag == @executable
458
+ return unless self_owned?
459
+ if flag && !pending?
460
+ raise ModelViolation, "cannot set the executable flag of #{self} since it is not pending"
461
+ elsif !flag && running?
462
+ raise ModelViolation, "cannot unset the executable flag of #{self} since it is running"
463
+ end
464
+ super
465
+ end
466
+
467
+ # Lists all arguments, that are set to be needed via the :argument
468
+ # syntax but are not set.
469
+ #
470
+ # This is needed for debugging purposes.
471
+ def list_unset_arguments # :nodoc:
472
+ actual_arguments =
473
+ if arguments.static?
474
+ arguments
475
+ else
476
+ arguments.evaluate_delayed_arguments
477
+ end
478
+
479
+ model.arguments.find_all do |name|
480
+ !actual_arguments.has_key?(name)
481
+ end
482
+ end
483
+
484
+ # True if all arguments defined by Task.argument on the task model are
485
+ # either explicitely set or have a default value.
486
+ def fully_instanciated?
487
+ if arguments.static?
488
+ @fully_instanciated ||= list_unset_arguments.empty?
489
+ else
490
+ list_unset_arguments.empty?
491
+ end
492
+ end
493
+
978
494
  # True if at least one argument required by the task model is not set.
979
495
  # See Task.argument.
980
- def partially_instanciated?; !fully_instanciated? end
496
+ def partially_instanciated?; !fully_instanciated? end
981
497
 
982
498
  # True if this task has an event of the required model. The event model
983
499
  # can either be a event class or an event name.
984
500
  def has_event?(event_model)
985
- bound_events.has_key?(event_model) ||
986
- model.has_event?(event_model)
987
- end
501
+ bound_events.has_key?(event_model)
502
+ end
988
503
 
989
504
  # True if this task is starting, i.e. if its start event is pending
990
505
  # (has been called, but is not emitted yet)
991
- def starting?; event(:start).pending? end
992
- # True if this task has never been started
993
- def pending?; !failed_to_start? && !starting? && !started? end
506
+ attr_predicate :starting?, true
507
+ # True if this task can be started
508
+ attr_predicate :pending?, true
994
509
  # True if this task is currently running (i.e. is has already started,
995
510
  # and is not finished)
996
511
  def running?; started? && !finished? end
512
+
513
+ attr_predicate :started?, true
514
+ attr_predicate :finished?, true
515
+ attr_predicate :failed?, true
516
+ attr_predicate :success?, true
997
517
  # True if the task is finishing, i.e. if a terminal event is pending.
998
- def finishing?
999
- if running?
1000
- each_event { |ev| return true if ev.terminal? && ev.pending? }
1001
- end
1002
- false
1003
- end
518
+ attr_predicate :finishing?, true
1004
519
 
1005
- attr_predicate :started?, true
1006
- attr_predicate :finished?, true
1007
- attr_predicate :success?, true
1008
- attr_predicate :failed_to_start?, true
520
+ # Call to force the value of {#reusable?} to false
521
+ # @return [void]
522
+ def do_not_reuse
523
+ @reusable = false
524
+ end
525
+
526
+ # True if this task can be reused by some other parts in the plan
527
+ def reusable?
528
+ plan && @reusable && !quarantined? && !garbage? && !failed_to_start? && !finished? && !finishing?
529
+ end
1009
530
 
1010
- # True if the +failed+ event of this task has been fired
1011
- def failed?; failed_to_start? || (finished? && @success == false) end
531
+ def garbage!
532
+ bound_events.each_value(&:garbage!)
533
+ super
534
+ end
1012
535
 
1013
- # call-seq:
1014
- # task.clear_relations => task
536
+ # @!method quarantined?
1015
537
  #
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
1026
- end
1027
-
1028
- # Update the terminal flag for the event models that are defined in
1029
- # this task model. The event is terminal if model-level signals (set up
1030
- # by Task::on) lead to the emission of the +stop+ event
1031
- def self.update_terminal_flag # :nodoc:
1032
- events = enum_events.map { |name, _| name }
1033
- terminal_events = [:stop]
1034
- events.delete(:stop)
1035
-
1036
- loop do
1037
- old_size = terminal_events.size
1038
- events.delete_if do |ev|
1039
- if signals(ev).any? { |sig_ev| terminal_events.include?(sig_ev) } ||
1040
- forwardings(ev).any? { |sig_ev| terminal_events.include?(sig_ev) }
1041
- terminal_events << ev
1042
- true
1043
- end
1044
- end
1045
- break if old_size == terminal_events.size
1046
- end
1047
-
1048
- terminal_events.each do |sym|
1049
- if ev = self.events[sym]
1050
- ev.terminal = true
1051
- else
1052
- ev = superclass.event_model(sym)
1053
- unless ev.terminal?
1054
- event sym, :model => ev, :terminal => true,
1055
- :command => (ev.method(:call) rescue nil)
1056
- end
1057
- end
1058
- end
1059
- end
1060
-
1061
- # Updates the terminal flag for all events in the task. An event is
1062
- # terminal if the +stop+ event of the task will be called because this
1063
- # event is.
1064
- def update_terminal_flag # :nodoc:
1065
- return unless @instantiated_model_events
1066
-
1067
- for _, ev in bound_events
1068
- ev.terminal_flag = nil
1069
- end
1070
-
1071
- success_events, failure_events, terminal_events =
1072
- [event(:success)].to_value_set,
1073
- [event(:failed)].to_value_set,
1074
- [event(:stop), event(:success), event(:failed)].to_value_set
1075
-
1076
- loop do
1077
- old_size = terminal_events.size
1078
- for _, ev in bound_events
1079
- for relation in [EventStructure::Signal, EventStructure::Forwarding]
1080
- for target in ev.child_objects(relation)
1081
- next if !target.respond_to?(:task) || target.task != self
1082
- next if ev[target, relation]
1083
-
1084
- if success_events.include?(target)
1085
- success_events << ev
1086
- terminal_events << ev
1087
- break
1088
- elsif failure_events.include?(target)
1089
- failure_events << ev
1090
- terminal_events << ev
1091
- break
1092
- elsif terminal_events.include?(target)
1093
- terminal_events << ev
1094
- end
538
+ # Whether this task has been quarantined
539
+ attr_predicate :quarantined?
540
+
541
+ # Mark the task as quarantined
542
+ #
543
+ # Once set it cannot be unset
544
+ def quarantined!
545
+ @quarantined = true
546
+ end
547
+
548
+ def failed_to_start?; @failed_to_start end
549
+
550
+ def mark_failed_to_start(reason, time)
551
+ if failed_to_start?
552
+ return
553
+ elsif !pending? && !starting?
554
+ raise Roby::InternalError, "#{self} is neither pending nor starting, cannot mark as failed_to_start!"
555
+ end
556
+ @failed_to_start = true
557
+ @failed_to_start_time = time
558
+ @failure_reason = reason
559
+ @pending = false
560
+ @starting = false
561
+ @failed = true
562
+ plan.task_index.set_state(self, :failed?)
563
+ end
564
+
565
+ def failed_to_start!(reason, time = Time.now)
566
+ mark_failed_to_start(reason, time)
567
+ each_event do |ev|
568
+ ev.unreachable!(reason)
569
+ end
570
+ execution_engine.log(:task_failed_to_start, self, reason)
571
+ end
572
+
573
+ # Clear relations events of this task have with events outside the task
574
+ def clear_events_external_relations(remove_strong: true)
575
+ removed = false
576
+ task_events = bound_events.values
577
+ each_event do |event|
578
+ for rel in event.sorted_relations
579
+ graph = plan.event_relation_graph_for(rel)
580
+ next if !remove_strong && graph.strong?
581
+
582
+ to_remove = Array.new
583
+ graph.each_in_neighbour(event) do |neighbour|
584
+ if !task_events.include?(neighbour)
585
+ to_remove << neighbour << event
586
+ end
587
+ end
588
+ graph.each_out_neighbour(event) do |neighbour|
589
+ if !task_events.include?(neighbour)
590
+ to_remove << event << neighbour
1095
591
  end
1096
592
  end
593
+ to_remove.each_slice(2) do |from, to|
594
+ graph.remove_edge(from, to)
595
+ end
596
+ removed ||= !to_remove.empty?
597
+ end
598
+ end
599
+ removed
600
+ end
601
+
602
+ # Remove all relations in which +self+ or its event are involved
603
+ #
604
+ # @param [Boolean] remove_internal if true, remove in-task relations between
605
+ # events
606
+ # @param [Boolean] remove_strong if true, remove strong relations as well
607
+ def clear_relations(remove_internal: false, remove_strong: true)
608
+ modified_plan = false
609
+ if remove_internal
610
+ each_event do |ev|
611
+ if ev.clear_relations(remove_strong: remove_strong)
612
+ modified_plan = true
613
+ end
614
+ end
615
+ else
616
+ modified_plan = clear_events_external_relations(remove_strong: remove_strong)
617
+ end
618
+ super(remove_strong: remove_strong) || modified_plan
619
+ end
1097
620
 
1098
- success_events.include?(ev) || failure_events.include?(ev) || terminal_events.include?(ev)
1099
- end
1100
- break if old_size == terminal_events.size
1101
- end
1102
-
1103
- for ev in success_events
1104
- ev.terminal_flag = :success if ev.respond_to?(:task) && ev.task == self
1105
- end
1106
- for ev in failure_events
1107
- ev.terminal_flag = :failure if ev.respond_to?(:task) && ev.task == self
1108
- end
1109
- for ev in (terminal_events - success_events - failure_events)
1110
- ev.terminal_flag = true if ev.respond_to?(:task) && ev.task == self
1111
- end
1112
- end
621
+ def invalidated_terminal_flag?; !!@terminal_flag_invalid end
622
+ def invalidate_terminal_flag; @terminal_flag_invalid = true end
623
+
624
+ # Updates the terminal flag for all events in the task. An event is
625
+ # terminal if the +stop+ event of the task will be called because this
626
+ # event is.
627
+ def update_terminal_flag # :nodoc:
628
+ return if !invalidated_terminal_flag?
629
+ terminal_events, success_events, failure_events =
630
+ self.model.compute_terminal_events(bound_events)
631
+ apply_terminal_flags(terminal_events, success_events, failure_events)
632
+ @terminal_flag_invalid = false
633
+ return terminal_events, success_events, failure_events
634
+ end
635
+
636
+ def apply_terminal_flags(terminal_events, success_events, failure_events)
637
+ for ev in bound_events.each_value
638
+ ev.terminal_flag = nil
639
+ if terminal_events.include?(ev)
640
+ if success_events.include?(ev)
641
+ ev.terminal_flag = :success
642
+ elsif failure_events.include?(ev)
643
+ ev.terminal_flag = :failure
644
+ else
645
+ ev.terminal_flag = true
646
+ end
647
+ end
648
+ end
649
+ end
1113
650
 
1114
651
  # Returns a list of Event objects, for all events that have been fired
1115
652
  # by this task. The list is sorted by emission times.
1116
- def history
1117
- history = []
1118
- each_event do |event|
1119
- history += event.history
1120
- end
1121
-
1122
- history.sort_by { |ev| ev.time }
1123
- end
653
+ attr_reader :history
1124
654
 
1125
655
  # Returns the set of tasks directly related to this task, either because
1126
656
  # of task relations or because of task events that are related to other
1127
657
  # task events
1128
- def related_tasks(result = nil)
1129
- result = related_objects(nil, result)
1130
- each_event do |ev|
1131
- ev.related_tasks(result)
1132
- end
1133
-
1134
- result
1135
- end
658
+ def related_tasks(result = Set.new)
659
+ result = related_objects(nil, result)
660
+ each_event do |ev|
661
+ ev.related_tasks(result)
662
+ end
663
+
664
+ result
665
+ end
1136
666
 
1137
667
  # Returns the set of events directly related to this task
1138
- def related_events(result = nil)
1139
- each_event do |ev|
1140
- result = ev.related_events(result)
1141
- end
1142
-
1143
- result.reject { |ev| ev.respond_to?(:task) && ev.task == self }.
1144
- to_value_set
1145
- end
668
+ def related_events(result = Set.new)
669
+ each_event do |ev|
670
+ ev.related_events(result)
671
+ end
672
+
673
+ result.reject { |ev| ev.respond_to?(:task) && ev.task == self }.
674
+ to_set
675
+ end
1146
676
 
1147
677
  # This method is called by TaskEventGenerator#fire just before the event handlers
1148
678
  # and commands are called
1149
- def emitting_event(event, context) # :nodoc:
1150
- if !executable?
1151
- raise TaskNotExecutable.new(self), "trying to emit #{symbol} on #{self} but #{self} is not executable"
1152
- end
1153
-
679
+ def check_emission_validity(event) # :nodoc:
1154
680
  if finished? && !event.terminal?
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}."
681
+ EmissionRejected.new(event).
682
+ exception("#{self}.emit(#{event.symbol}) called by #{execution_engine.propagation_sources.to_a} but the task has finished. Task has been terminated by #{stop_event.last.sources.to_a}.")
1157
683
  elsif pending? && event.symbol != :start
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"
684
+ EmissionRejected.new(event).
685
+ exception("#{self}.emit(#{event.symbol}) called by #{execution_engine.propagation_sources.to_a} but the task has never been started")
1160
686
  elsif running? && event.symbol == :start
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}."
687
+ EmissionRejected.new(event).
688
+ exception("#{self}.emit(#{event.symbol}) called by #{execution_engine.propagation_sources.to_a} but the task is already running. Task has been started by #{start_event.last.sources.to_a}.")
1163
689
  end
1164
-
1165
- super if defined? super
1166
690
  end
1167
691
 
1168
692
  # 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)
1172
- super if defined? super
693
+ # has been fired.
694
+ def fired_event(event)
695
+ history << event
696
+ update_task_status(event)
1173
697
  end
698
+
699
+ # The most specialized event that caused this task to end
700
+ attr_reader :terminal_event
1174
701
 
1175
- # The event which has finished the task (if there is one)
1176
- attr_reader :terminal_event
702
+ # The reason for which this task failed.
703
+ #
704
+ # It can either be an event or a LocalizedError object.
705
+ #
706
+ # If it is an event, it is the most specialized event whose emission
707
+ # has been forwarded to :failed
708
+ #
709
+ # If it is a LocalizedError object, it is the exception that caused the
710
+ # task failure.
711
+ attr_reader :failure_reason
712
+
713
+ # The time at which the task failed to start
714
+ attr_reader :failed_to_start_time
1177
715
 
1178
716
  # The event that caused this task to fail. This is equivalent to taking
1179
717
  # the first emitted element of
@@ -1181,846 +719,919 @@ def fire_event(event)
1181
719
  #
1182
720
  # It is only much more efficient
1183
721
  attr_reader :failure_event
1184
-
1185
- # Call to update the task status because of +event+
1186
- def update_task_status(event) # :nodoc:
1187
- if event.success?
1188
- plan.task_index.set_state(self, :success?)
1189
- self.success = true
1190
- self.finished = true
1191
- @terminal_event ||= event
1192
- elsif event.failure?
1193
- plan.task_index.set_state(self, :failed?)
1194
- self.success = false
1195
- self.finished = true
1196
- @terminal_event ||= event
722
+
723
+ # Call to update the task status because of +event+
724
+ def update_task_status(event) # :nodoc:
725
+ if event.symbol == :start
726
+ plan.task_index.set_state(self, :running?)
727
+ @starting = false
728
+ @pending = false
729
+ @started = true
730
+ @running = true
731
+ @executable = true
732
+ end
733
+
734
+ if event.success?
735
+ plan.task_index.add_state(self, :success?)
736
+ @success = true
737
+ elsif event.failure?
738
+ plan.task_index.add_state(self, :failed?)
739
+ @failed = true
740
+ @failure_reason ||= event
1197
741
  @failure_event ||= event
1198
- elsif event.terminal? && !finished?
1199
- plan.task_index.set_state(self, :finished?)
1200
- self.finished = true
1201
- @terminal_event ||= event
1202
- end
1203
-
1204
- if event.symbol == :start
1205
- plan.task_index.set_state(self, :running?)
1206
- self.started = true
1207
- end
1208
- end
742
+ end
743
+
744
+ if event.terminal?
745
+ @terminal_event ||= event
746
+ end
747
+
748
+ if event.symbol == :stop
749
+ plan.task_index.remove_state(self, :running?)
750
+ plan.task_index.add_state(self, :finished?)
751
+ @running = false
752
+ @finishing = false
753
+ @finished = true
754
+ @executable = false
755
+ end
756
+ end
1209
757
 
1210
- # List of EventGenerator objects bound to this task
758
+ # List of EventGenerator objects bound to this task
1211
759
  attr_reader :bound_events
1212
760
 
1213
- # call-seq:
1214
- # emit(event_model, *context) => self
761
+ # Returns the event generator by its name
1215
762
  #
1216
- # Emits +event_model+ in the given +context+. Event handlers are fired.
1217
- # This is equivalent to
1218
- # event(event_model).emit(*context)
763
+ # @param [Symbol] name the event name
764
+ # @return [TaskEventGenerator,nil]
765
+ def find_event(name)
766
+ bound_events[name] ||
767
+ bound_events[event_model(name).symbol]
768
+ end
769
+
770
+ # Returns the event generator by its name or model
1219
771
  #
772
+ # @param [Symbol] name the event name
773
+ # @return [TaskEventGenerator,nil]
774
+ # @raise [ArgumentError] if the event does not exist
775
+ def event(event_model)
776
+ if event = find_event(event_model)
777
+ event
778
+ else
779
+ raise ArgumentError, "cannot find #{event_model} in the set of bound events in #{self}. Known events are #{bound_events}."
780
+ end
781
+ end
782
+
783
+ # @!group Deprecated Event API
784
+
785
+ # @deprecated use {TaskEventGenerator#emit} instead (e.g. task.start_event.emit)
1220
786
  def emit(event_model, *context)
787
+ Roby.warn_deprecated "Roby::Task#emit(event_name) is deprecated, use EventGenerator#emit (e.g. task.start_event.emit or task.event(:start).emit)"
1221
788
  event(event_model).emit(*context)
1222
789
  self
1223
790
  end
1224
791
 
1225
- # Returns the TaskEventGenerator which describes the required event
1226
- # model. +event_model+ can either be an event name or an Event class.
1227
- def event(event_model)
1228
- unless event = bound_events[event_model]
1229
- event_model = self.event_model(event_model)
1230
- unless event = bound_events[event_model.symbol]
1231
- raise "cannot find #{event_model.symbol.inspect} in the set of bound events in #{self}. Known events are #{bound_events}."
1232
- end
1233
- end
1234
- event
1235
- end
1236
-
1237
- # call-seq:
1238
- # on(event) { |event| ... }
1239
- #
1240
- # Defines an event handler for the given event.
1241
- def on(event_model, to = nil, *to_task_events, &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)
1245
- raise ArgumentError, "you must provide either a task or an event handler (got nil for both)"
1246
- end
1247
792
 
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
793
+ # @deprecated use {TaskEventGenerator#on} on the event object, e.g.
794
+ # task.start_event.on { |event| ... }
795
+ def on(event_model, options = Hash.new, &user_handler)
796
+ Roby.warn_deprecated "Task#on is deprecated, use EventGenerator#on instead (e.g. #{event_model}_event.signals other_event)"
797
+ event(event_model).on(options, &user_handler)
1255
798
  self
1256
799
  end
1257
800
 
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
801
+ # @deprecated use {TaskEventGenerator#signal} instead (e.g. task.start_event.signal other_task.stop_event)
1273
802
  def signals(event_model, to, *to_task_events)
803
+ Roby.warn_deprecated "Task#signals is deprecated, use EventGenerator#signal instead (e.g. #{event_model}_event.signals other_event)"
804
+
1274
805
  generator = event(event_model)
1275
806
  if Hash === to_task_events.last
1276
807
  delay = to_task_events.pop
1277
808
  end
1278
- to_events = case to
1279
- when Task
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"
1282
- [to.event(generator.symbol)]
1283
- else
1284
- to_task_events.map { |ev_model| to.event(ev_model) }
1285
- end
1286
- when EventGenerator: [to]
1287
- else []
1288
- end
1289
-
1290
- to_events.push delay if delay
1291
- generator.signals(*to_events)
809
+ to_events = case to
810
+ when Task
811
+ to_task_events.map { |ev_model| to.event(ev_model) }
812
+ when EventGenerator then [to]
813
+ else
814
+ raise ArgumentError, "expected Task or EventGenerator, got #{to}(#{to.class}: #{to.class.ancestors})"
815
+ end
816
+
817
+ to_events.each do |event|
818
+ generator.signals event, delay
819
+ end
1292
820
  self
1293
821
  end
1294
822
 
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"
823
+ # @deprecated use {TaskEventGenerator#forward_to} instead (e.g. task.start_event.forward_to other_task.stop_event)
824
+ def forward_to(event_model, to, *to_task_events)
825
+ Roby.warn_deprecated "Task#forward_to is deprecated, use EventGenerator#forward_to instead (e.g. #{event_model}_event.forward_to other_event)"
826
+
827
+ generator = event(event_model)
828
+ if Hash === to_task_events.last
829
+ delay = to_task_events.pop
1299
830
  end
831
+ to_events = case
832
+ when Task
833
+ to_task_events.map { |ev| to.event(ev) }
834
+ when EventGenerator then [to]
835
+ else
836
+ raise ArgumentError, "expected Task or EventGenerator, got #{to}(#{to.class}: #{to.class.ancestors})"
837
+ end
1300
838
 
1301
- forward_to(name, to, *to_task_events)
839
+ to_events.each do |ev|
840
+ generator.forward_to ev, delay
841
+ end
1302
842
  end
1303
843
 
1304
- # call-seq:
1305
- # forward_to source_event, dest_task, ev1, ev2, ev3, ...
1306
- # forward_to source_event, dest_task, ev1, ev2, ev3, delay_options
844
+ # @!endgroup Deprecated Event API
845
+
846
+ include MetaRuby::DSLs::FindThroughMethodMissing
847
+
848
+ # Iterates on all the events defined for this task
1307
849
  #
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,
850
+ # @param [Boolean] only_wrapped For consistency with transaction
851
+ # proxies. Should not be used in user code.
852
+ # @yield [generator]
853
+ # @yieldparam [TaskEventGenerator] generator the generators that are
854
+ # tied to this task
855
+ # @return self
856
+ def each_event(only_wrapped = true)
857
+ return enum_for(__method__, only_wrapped) if !block_given?
858
+ for ev in bound_events.each_value
859
+ yield(ev)
860
+ end
861
+ self
862
+ end
863
+ alias :each_plan_child :each_event
864
+
865
+ # Returns this task's set of terminal events.
1311
866
  #
1312
- # To call an event whenever other events are emitted, use the Signal
1313
- # relation. See Task#signals, Task.signal and EventGenerator#signals.
867
+ # A terminal event is an event whose emission announces the end of the
868
+ # task. In most case, it is an event which is forwarded directly on
869
+ # indirectly to +stop+.
1314
870
  #
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)
1320
- generator = event(name)
1321
- if Hash === to_task_events.last
1322
- delay = to_task_events.pop
871
+ # @return [Array<TaskEventGenerator>]
872
+ def terminal_events
873
+ bound_events.each_value.find_all { |ev| ev.terminal? }
874
+ end
875
+
876
+ # (see Models::Task#event_model)
877
+ def event_model(model)
878
+ self.model.event_model(model)
879
+ end
880
+
881
+ def to_s # :nodoc:
882
+ s = "#{name}<id:#{droby_id.id}>(#{arguments})"
883
+ id = owners.map do |owner|
884
+ next if plan && (owner == plan.local_owner)
885
+ sibling = remote_siblings[owner]
886
+ "#{sibling ? Object.address_from_id(sibling.ref).to_s(16) : 'nil'}@#{owner.remote_name}"
1323
887
  end
888
+ unless id.empty?
889
+ s << "[" << id.join(",") << "]"
890
+ end
891
+ s
892
+ end
1324
893
 
1325
- to_events = if to.respond_to?(:event)
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"
1328
- [to.event(generator.symbol)]
1329
- else
1330
- to_task_events.map { |ev| to.event(ev) }
1331
- end
1332
- elsif to.kind_of?(EventGenerator)
1333
- [to]
1334
- else
1335
- raise ArgumentError, "expected Task or EventGenerator, got #{to}(#{to.class}: #{to.class.ancestors})"
1336
- end
1337
-
1338
- to_events.each do |ev|
1339
- generator.forward_to ev, delay
1340
- end
1341
- end
1342
-
1343
- # :stopdoc:
1344
- attr_accessor :calling_event
1345
-
1346
- def method_missing(name, *args, &block) # :nodoc:
1347
- if calling_event && calling_event.respond_to?(name)
1348
- calling_event.send(name, *args, &block)
1349
- else
1350
- super
1351
- end
1352
- rescue NameError => e
1353
- raise e, e.message, caller(1)
1354
- rescue NoMethodError => e
1355
- raise e, e.message, caller(1)
1356
- end
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
-
1365
- @@event_command_id = 0
1366
- def self.allocate_event_command_id # :nodoc:
1367
- @@event_command_id += 1
1368
- end
1369
- # :startdoc:
1370
-
1371
- # call-seq:
1372
- # self.event(name, options = nil) { ... } => event class or nil
1373
- #
1374
- # Define a new event in this task.
1375
- #
1376
- # <b>Available options</b>
1377
- #
1378
- # <tt>command</tt>::
1379
- # either true, false or an event command for the new event. In that
1380
- # latter case, the command is an object which must respond to
1381
- # #to_proc. If it is true, a default handler is defined which simply
1382
- # emits the event. If a block is given, it is used as the event
1383
- # command.
1384
- #
1385
- # <tt>terminal</tt>::
1386
- # set to true if this event is a terminal event, i.e. if its emission
1387
- # means that the task has finished.
1388
- #
1389
- # <tt>model</tt>::
1390
- # base class for the event model (see "Event models" below). The default is the
1391
- # TaskEvent class
1392
- #
1393
- # <b>Event models</b>
1394
- #
1395
- # When a task event (for instance +start+) is emitted, a Roby::Event
1396
- # object is created to describe the information related to this
1397
- # emission (time, sources, context information, ...). Task.event
1398
- # defines a specific event model MyTask::MyEvent for each task event
1399
- # with name :my_event. This specific model is by default a subclass of
1400
- # Roby::TaskEvent, but it is possible to override that by using the +model+
1401
- # option.
1402
- def self.event(ev, options = Hash.new, &block)
1403
- options = validate_options(options, :command => nil, :terminal => nil, :model => TaskEvent)
1404
-
1405
- ev_s = ev.to_s
1406
- ev = ev.to_sym
1407
-
1408
- if !options.has_key?(:command)
1409
- if block
1410
- define_method("event_command_#{ev_s}", &block)
1411
- method = instance_method("event_command_#{ev_s}")
1412
- end
1413
-
1414
- if method
1415
- check_arity(method, 1)
1416
- options[:command] = lambda do |dst_task, *event_context|
1417
- begin
1418
- dst_task.calling_event = dst_task.event(ev)
1419
- method.bind(dst_task).call(*event_context)
1420
- ensure
1421
- dst_task.calling_event = nil
1422
- end
1423
- end
1424
- end
1425
- end
1426
- validate_event_definition_request(ev, options)
1427
-
1428
- command_handler = options[:command] if options[:command].respond_to?(:call)
1429
-
1430
- # Define the event class
1431
- task_klass = self
1432
- new_event = Class.new(options[:model]) do
1433
- @terminal = options[:terminal]
1434
- @symbol = ev
1435
- @command_handler = command_handler
1436
-
1437
- define_method(:name) { "#{task.name}::#{ev_s.camelcase(true)}" }
1438
- singleton_class.class_eval do
1439
- attr_reader :command_handler
1440
- define_method(:name) { "#{task_klass.name}::#{ev_s.camelcase(true)}" }
1441
- def to_s; name end
894
+ def pretty_print(pp, with_owners = true) # :nodoc:
895
+ pp.text "#{model.name}:0x#{self.address.to_s(16)}"
896
+ if with_owners
897
+ pp.nest(2) do
898
+ pp.breakable
899
+ pp.text "owners: "
900
+ pp.nest(2) do
901
+ pp.seplist(owners) { |r| pp.text r.to_s }
902
+ end
1442
903
  end
1443
904
  end
1444
-
1445
- setup_terminal_handler = false
1446
- old_model = find_event_model(ev)
1447
- if new_event.symbol != :stop && options[:terminal] && (!old_model || !old_model.terminal?)
1448
- setup_terminal_handler = true
1449
- end
1450
-
1451
- events[new_event.symbol] = new_event
1452
- if setup_terminal_handler
1453
- forward(new_event => :stop)
1454
- end
1455
- const_set(ev_s.camelcase(true), new_event)
1456
-
1457
- if options[:command]
1458
- # check that the supplied command handler can take two arguments
1459
- check_arity(command_handler, 2) if command_handler
1460
-
1461
- # define #call on the event model
1462
- new_event.singleton_class.class_eval do
1463
- if command_handler
1464
- define_method(:call, &command_handler)
1465
- else
1466
- def call(task, context) # :nodoc:
1467
- task.emit(symbol, *context)
1468
- end
1469
- end
905
+ pp.nest(2) do
906
+ pp.breakable
907
+ pp.text "arguments: "
908
+ if !arguments.empty?
909
+ pp.nest(2) do
910
+ pp.breakable
911
+ arguments.pretty_print(pp)
912
+ end
1470
913
  end
914
+ end
915
+ end
916
+
917
+ # True if this task is a null task. See NullTask.
918
+ def null?; false end
919
+ # Converts this object into a task object
920
+ def to_task; self end
921
+
922
+ # Event emitted when the task is started
923
+ #
924
+ # It is controlable by default, its command simply emitting the start
925
+ # event
926
+ event :start, command: true
1471
927
 
1472
- # define an instance method which calls the event command
1473
- define_method("#{ev_s}!") do |*context|
1474
- generator = event(ev)
1475
- generator.call(*context)
1476
- end
928
+ # Event emitted when the task has stopped
929
+ #
930
+ # It is not controlable by default. If the task can be stopped without
931
+ # any specific action, call {Models::Task#terminates} on the task model. If it
932
+ # needs specific actions, define a controlable failed event and call
933
+ # {Models::Task#interruptible}
934
+ #
935
+ # @example task with simple termination
936
+ # class MyTask < Roby::Task
937
+ # terminates
938
+ # end
939
+ #
940
+ # @example task with complex termination
941
+ # class Mytask < Roby::Task
942
+ # event :failed do
943
+ # # Terminate the underlying process
944
+ # end
945
+ # interruptible
946
+ # end
947
+ event :stop
948
+
949
+ # Event emitted when the task has successfully finished
950
+ #
951
+ # It is obviously forwarded to {#stop_event}
952
+ event :success, terminal: true
953
+
954
+ # Event emitted when the task has finished without performing its duty
955
+ #
956
+ # It is obviously forwarded to {#stop_event}
957
+ event :failed, terminal: true
958
+
959
+ # Event emitted when the task's underlying
960
+ # {TaskStructure::ExecutionAgent#execution_agent} finished
961
+ # while the task was running
962
+ #
963
+ # It is obviously forwarded to {#failed_event}
964
+ event :aborted
965
+ forward aborted: :failed
966
+
967
+ # Event emitted when a task internal code block ({Models::Task#on} handler,
968
+ # {Models::Task#poll} block) raised an exception
969
+ #
970
+ # It signals {#stop_event} if {#stop_event} is controlable
971
+ event :internal_error
972
+
973
+ class InternalError
974
+ # Mark the InternalError event as a failure event, even if it is not
975
+ # forwarded to the stop event at the model level
976
+ def failure?
977
+ true
1477
978
  end
979
+ end
1478
980
 
1479
- if !method_defined?("#{ev_s}_event")
1480
- define_method("#{ev_s}_event") do
1481
- event(ev)
1482
- end
981
+ on :internal_error do |error|
982
+ if error.context
983
+ @failure_reason = error.context.first
984
+ end
985
+ end
986
+
987
+ # The internal data for this task
988
+ #
989
+ # @see data= updated_data updated_data_event
990
+ attr_reader :data
991
+
992
+ # Sets the internal data value for this task. This calls the
993
+ # {#updated_data} hook, and emits {#updated_data_event} if the task is running.
994
+ def data=(value)
995
+ @data = value
996
+ updated_data
997
+ emit :updated_data if running?
998
+ end
999
+
1000
+ # This hook is called whenever the internal data of this task is
1001
+ # updated. See #data, #data= and the +updated_data+ event
1002
+ def updated_data
1003
+ end
1004
+ event :updated_data, command: false
1005
+
1006
+ # Checks if +task+ is in the same execution state than +self+
1007
+ # Returns true if they are either both running or both pending
1008
+ def compatible_state?(task)
1009
+ finished? || !(running? ^ task.running?)
1010
+ end
1011
+
1012
+ # @api private
1013
+ #
1014
+ # The set of instance-level execute blocks
1015
+ #
1016
+ # @return [Array<InstanceHandler>]
1017
+ attr_reader :execute_handlers
1018
+
1019
+ # @api private
1020
+ #
1021
+ # The set of instance-level poll blocks
1022
+ #
1023
+ # @return [Array<InstanceHandler>]
1024
+ attr_reader :poll_handlers
1025
+
1026
+ # Add a block that is going to be executed once, either at the next
1027
+ # cycle if the task is already running, or when the task is started
1028
+ #
1029
+ # @macro InstanceHandlerOptions
1030
+ # @return [void]
1031
+ def execute(options = Hash.new, &block)
1032
+ default_on_replace = if abstract? then :copy else :drop end
1033
+ options = InstanceHandler.validate_options(options, on_replace: default_on_replace)
1034
+
1035
+ check_arity(block, 1)
1036
+ @execute_handlers << InstanceHandler.new(block, (options[:on_replace] == :copy))
1037
+ ensure_poll_handler_called
1038
+ end
1039
+
1040
+ # Adds a new poll block on this instance
1041
+ #
1042
+ # @macro InstanceHandlerOptions
1043
+ # @yieldparam [Roby::Task] task the task on which the poll block is
1044
+ # executed. It might be different than the one on which it has been
1045
+ # added because of replacements.
1046
+ # @return [Object] an ID that can be used in {#remove_poll_handler}
1047
+ def poll(options = Hash.new, &block)
1048
+ default_on_replace = if abstract? then :copy else :drop end
1049
+ options = InstanceHandler.validate_options(options, on_replace: default_on_replace)
1050
+
1051
+ check_arity(block, 1)
1052
+ @poll_handlers << (handler = InstanceHandler.new(block, (options[:on_replace] == :copy)))
1053
+ ensure_poll_handler_called
1054
+ handler
1055
+ end
1056
+
1057
+ # Remove a poll handler from this instance
1058
+ #
1059
+ # @param [Object] handler the ID returned by {#poll}
1060
+ # @return [void]
1061
+ def remove_poll_handler(handler)
1062
+ @poll_handlers.delete(handler)
1063
+ end
1064
+
1065
+ # @api private
1066
+ #
1067
+ # Helper for {#execute} and {#poll} that ensures that the {#do_poll} is
1068
+ # called by the execution engine
1069
+ def ensure_poll_handler_called
1070
+ if !transaction_proxy? && running?
1071
+ @poll_handler_id ||= execution_engine.add_propagation_handler(description: "poll block for #{self}", type: :external_events, &method(:do_poll))
1483
1072
  end
1484
- if !method_defined?("#{ev_s}?")
1485
- define_method("#{ev_s}?") do
1486
- event(ev).happened?
1073
+ end
1074
+
1075
+ # @api private
1076
+ #
1077
+ # Internal method used to register the poll blocks in the engine
1078
+ # execution cycle
1079
+ def do_poll(plan) # :nodoc:
1080
+ return unless self_owned?
1081
+ # Don't call if we are terminating
1082
+ return if finished?
1083
+ # Don't call if we already had an error in the poll block
1084
+ return if event(:internal_error).emitted?
1085
+
1086
+ begin
1087
+ while execute_block = @execute_handlers.pop
1088
+ execute_block.block.call(self)
1089
+ end
1090
+
1091
+ if respond_to?(:poll_handler)
1092
+ poll_handler
1093
+ end
1094
+
1095
+ if machine = state_machine
1096
+ machine.do_poll(self)
1487
1097
  end
1098
+
1099
+ @poll_handlers.each do |poll_block|
1100
+ poll_block.block.call(self)
1101
+ end
1102
+ rescue LocalizedError => e
1103
+ execution_engine.add_error(e)
1104
+ rescue Exception => e
1105
+ execution_engine.add_error(CodeError.new(e, self))
1488
1106
  end
1107
+ end
1108
+
1109
+ on :start do |ev|
1110
+ engine = execution_engine
1489
1111
 
1490
- new_event
1112
+ # Register poll:
1113
+ # - single class poll_handler add be class method Task#poll
1114
+ # - additional instance poll_handler added by instance method poll
1115
+ # - polling as defined in state of the state_machine, i.e. substates of running
1116
+ if respond_to?(:poll_handler) || !poll_handlers.empty? || state_machine
1117
+ @poll_handler_id = engine.add_propagation_handler(description: "poll block of #{self}", type: :external_events, &method(:do_poll))
1118
+ end
1119
+ end
1120
+
1121
+ on :stop do |ev|
1122
+ if @poll_handler_id
1123
+ execution_engine.remove_propagation_handler(@poll_handler_id)
1124
+ end
1491
1125
  end
1492
1126
 
1493
- def self.validate_event_definition_request(ev, options) #:nodoc:
1494
- if options[:command] && options[:command] != true && !options[:command].respond_to?(:call)
1495
- raise ArgumentError, "Allowed values for :command option: true, false, nil and an object responding to #call. Got #{options[:command]}"
1127
+ # Declares that this fault response table should be made active when
1128
+ # this task starts, and deactivated when it ends
1129
+ def use_fault_response_table(table_model, arguments = Hash.new)
1130
+ arguments = table_model.validate_arguments(arguments)
1131
+
1132
+ table = nil
1133
+ execute do |task|
1134
+ table = task.plan.use_fault_response_table(table_model, arguments)
1135
+ end
1136
+ stop_event.on do |event|
1137
+ plan.remove_fault_response_table(table)
1496
1138
  end
1139
+ end
1497
1140
 
1498
- if ev.to_sym == :stop
1499
- if options.has_key?(:terminal) && !options[:terminal]
1500
- raise ArgumentError, "the 'stop' event cannot be non-terminal"
1141
+ # Whether this task instance provides a set of models and arguments
1142
+ #
1143
+ # The fullfills? predicate checks if this task can be used
1144
+ # to fullfill the need of the given model and arguments
1145
+ # The default is to check if
1146
+ # * the needed task model is an ancestor of this task
1147
+ # * the task
1148
+ # * +args+ is included in the task arguments
1149
+ def fullfills?(models, args = nil)
1150
+ if models.kind_of?(Roby::Task)
1151
+ args ||= models.meaningful_arguments
1152
+ models = models.model
1153
+ end
1154
+ if !model.fullfills?(models)
1155
+ return false
1156
+ end
1157
+
1158
+ if args
1159
+ args.each do |key, name|
1160
+ if self.arguments[key] != name
1161
+ return false
1162
+ end
1501
1163
  end
1502
- options[:terminal] = true
1503
1164
  end
1504
1165
 
1505
- # Check for inheritance rules
1506
- if events.include?(ev)
1507
- raise ArgumentError, "event #{ev} already defined"
1508
- elsif old_event = find_event_model(ev)
1509
- if old_event.terminal? && !options[:terminal]
1510
- raise ArgumentError, "trying to override a terminal event into a non-terminal one", caller(2)
1511
- elsif old_event.controlable? && !options[:command]
1512
- raise ArgumentError, "trying to override a controlable event into a non-controlable one", caller(2)
1166
+ true
1167
+ end
1168
+
1169
+ # True if this model requires an argument named key and that argument is
1170
+ # set
1171
+ def has_argument?(key)
1172
+ self.arguments.set?(key)
1173
+ end
1174
+
1175
+ # True if self can be used to replace target
1176
+ def can_replace?(target)
1177
+ fullfills?(*target.fullfilled_model)
1178
+ end
1179
+
1180
+ # Tests if a task could be merged within self
1181
+ #
1182
+ # Unlike a replacement, a merge implies that self is modified to match
1183
+ # both its current role and the target's role. Roby has no built-in
1184
+ # merge logic (no merge method). This method is a helper for Roby
1185
+ # extensions that implement such a scheme, to check for attributes
1186
+ # common to all tasks that would forbid a merge
1187
+ def can_merge?(target)
1188
+ if defined?(super) && !super
1189
+ return
1190
+ end
1191
+
1192
+ if finished? || target.finished?
1193
+ return false
1194
+ end
1195
+
1196
+ if !model.can_merge?(target.model)
1197
+ return false
1198
+ end
1199
+
1200
+ target.arguments.each_assigned_argument do |key, val|
1201
+ if arguments.set?(key) && arguments[key] != val
1202
+ return false
1513
1203
  end
1514
1204
  end
1205
+ true
1515
1206
  end
1516
1207
 
1517
- # Events defined by the task model
1518
- inherited_enumerable(:event, :events, :map => true) { Hash.new }
1208
+ # "Simply" mark this task as terminated. This is meant to be used on
1209
+ # quarantined tasks in tests.
1210
+ #
1211
+ # Do not use this unless you really know what you are doing
1212
+ def forcefully_terminate
1213
+ update_task_status(event(:stop).new([]))
1214
+ end
1519
1215
 
1520
- def self.enum_events # :nodoc
1521
- @__enum_events__ ||= enum_for(:each_event)
1522
- end
1216
+ include ExceptionHandlingObject
1523
1217
 
1524
- # call-seq:
1525
- # task.each_event { |event_object| ... } => task
1218
+ # @api private
1526
1219
  #
1527
- # Iterates on all the events defined for this task
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
1532
- for _, ev in bound_events
1533
- yield(ev)
1534
- end
1535
- self
1220
+ # Handles the given exception.
1221
+ #
1222
+ # In addition to the exception handlers provided by
1223
+ # {ExceptionHandlingObject}, it checks for repair tasks (as defined by
1224
+ # TaskStructure::ErrorHandling)
1225
+ #
1226
+ # @param [ExecutionException] e
1227
+ def handle_exception(e)
1228
+ return if !plan
1229
+
1230
+ tasks = find_all_matching_repair_tasks(e)
1231
+ return super if tasks.empty?
1232
+ if !tasks.any? { |t| t.running? }
1233
+ tasks.first.start!
1234
+ end
1235
+ true
1536
1236
  end
1537
- alias :each_plan_child :each_event
1538
-
1539
- # Returns the set of terminal events this task has. A terminal event is
1540
- # an event whose emission announces the end of the task. In most case,
1541
- # it is an event which is forwarded directly on indirectly to +stop+.
1542
- def terminal_events
1543
- bound_events.values.find_all { |ev| ev.terminal? }
1544
- end
1545
-
1546
- # Get the list of terminal events for this task model
1547
- def self.terminal_events
1548
- enum_events.find_all { |_, e| e.terminal? }.
1549
- map { |_, e| e }
1550
- end
1551
-
1552
- # Get the event model for +event+
1553
- def event_model(model); self.model.event_model(model) end
1554
-
1555
- # Find the event class for +event+, or nil if +event+ is not an event name for this model
1556
- def self.find_event_model(name)
1557
- name = name.to_sym
1558
- each_event { |sym, e| return e if sym == name }
1559
- nil
1560
- end
1561
-
1562
- # Checks that all events in +events+ are valid events for this task.
1563
- # The requested events can be either an event name (symbol or string)
1564
- # or an event class
1565
- #
1566
- # Returns the corresponding array of event classes
1567
- def self.event_model(model_def) #:nodoc:
1568
- if model_def.respond_to?(:to_sym)
1569
- ev_model = find_event_model(model_def.to_sym)
1570
- unless ev_model
1571
- all_events = enum_events.map { |name, _| name }
1572
- raise ArgumentError, "#{model_def} is not an event of #{name}: #{all_events}" unless ev_model
1573
- end
1574
- elsif model_def.respond_to?(:has_ancestor?) && model_def.has_ancestor?(TaskEvent)
1575
- # Check that model_def is an event class for us
1576
- ev_model = find_event_model(model_def.symbol)
1577
- if !ev_model
1578
- raise ArgumentError, "no #{model_def.symbol} event in #{name}"
1579
- elsif ev_model != model_def
1580
- raise ArgumentError, "the event model #{model_def} is not a model for #{name} (found #{ev_model} with the same name)"
1581
- end
1582
- else
1583
- raise ArgumentError, "wanted either a symbol or an event class, got #{model_def}"
1584
- end
1585
-
1586
- ev_model
1587
- end
1588
-
1589
- class << self
1590
- # Checks if _name_ is a name for an event of this task
1591
- alias :has_event? :find_event_model
1592
-
1593
- private :validate_event_definition_request
1237
+
1238
+ # Lists all exception handlers attached to this task
1239
+ def each_exception_handler(&iterator)
1240
+ model.each_exception_handler(&iterator)
1594
1241
  end
1595
-
1596
- # call-seq:
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
1242
 
1618
- signal_sets[from.symbol].merge targets.map { |ev| ev.symbol }.to_value_set
1243
+ # @api private
1244
+ #
1245
+ # Validates that both self and the child object are owned by the local
1246
+ # instance
1247
+ def add_child_object(child, type, info)
1248
+ unless read_write? && child.read_write?
1249
+ raise OwnershipError, "cannot add a relation between tasks we don't own. #{self} by #{owners.to_a} and #{child} is owned by #{child.owners.to_a}"
1619
1250
  end
1620
- update_terminal_flag
1251
+
1252
+ super
1621
1253
  end
1622
1254
 
1623
- # call-seq:
1624
- # on(event_name) { |event| ... }
1255
+ # @api private
1625
1256
  #
1626
- # Adds an event handler for the given event model. When the event is fired,
1627
- # all events given in argument will be called. If they are controlable,
1628
- # then the command is called. If not, they are just fired
1629
- def self.on(mappings, &user_handler)
1630
- if user_handler
1631
- check_arity(user_handler, 1)
1257
+ # This method is called during the commit process to apply changes
1258
+ # stored in a proxy
1259
+ def commit_transaction
1260
+ super
1261
+
1262
+ arguments.dup.each do |key, value|
1263
+ if value.respond_to?(:transaction_proxy?) && value.transaction_proxy?
1264
+ arguments.update!(key, value.__getobj__)
1265
+ end
1266
+ end
1267
+ end
1268
+
1269
+ # Create a new task of the same model and with the same arguments
1270
+ # than this one. Insert this task in the plan and make it replace
1271
+ # the fresh one.
1272
+ #
1273
+ # See Plan#respawn
1274
+ def respawn
1275
+ plan.respawn(self)
1276
+ end
1277
+
1278
+ # @api private
1279
+ def compute_subplan_replacement_operation(object)
1280
+ edges, edges_candidates = [], []
1281
+ subplan_tasks = Set[self, object]
1282
+ parent_tasks = Set.new
1283
+ plan.each_task_relation_graph do |g|
1284
+ next if g.strong?
1285
+ rel = g.class
1286
+
1287
+ each_in_neighbour_merged(rel, intrusive: true) do |parent|
1288
+ parent_tasks << parent
1289
+ edges << [g, parent, self, parent, object]
1290
+ end
1291
+ object.each_in_neighbour_merged(rel, intrusive: true) do |parent|
1292
+ parent_tasks << parent
1293
+ end
1294
+
1295
+ if g.weak?
1296
+ each_out_neighbour_merged(rel, intrusive: true) do |child|
1297
+ edges_candidates << [child, [g, self, child, object, child]]
1298
+ end
1299
+ else
1300
+ object.each_out_neighbour_merged(rel, intrusive: true) do |child|
1301
+ subplan_tasks << child
1302
+ end
1303
+ each_out_neighbour_merged(rel, intrusive: true) do |child|
1304
+ subplan_tasks << child
1305
+ end
1306
+ end
1632
1307
  end
1633
1308
 
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)
1309
+ plan.each_event_relation_graph do |g|
1310
+ next if g.strong?
1311
+ rel = g.class
1312
+
1313
+ model.each_event do |_, event|
1314
+ event = plan.each_object_in_transaction_stack(self).
1315
+ find { |_, o| o.find_event(event.symbol) }.
1316
+ last.event(event.symbol)
1317
+ object_event = plan.each_object_in_transaction_stack(object).
1318
+ find { |_, o| o.find_event(event.symbol) }.
1319
+ last.event(event.symbol)
1320
+
1321
+ event.each_in_neighbour_merged(rel, intrusive: false) do |_, parent|
1322
+ if parent.respond_to?(:task)
1323
+ edges_candidates <<
1324
+ [plan[parent.task], [g, parent, event, parent, object_event]]
1325
+ end
1326
+ end
1327
+ event.each_out_neighbour_merged(rel, intrusive: false) do |_, child|
1328
+ if child.respond_to?(:task)
1329
+ edges_candidates <<
1330
+ [plan[child.task], [g, event, child, object_event, child]]
1331
+ end
1332
+ end
1333
+ end
1334
+ end
1335
+
1336
+ edges_candidates.each do |reference_task, op|
1337
+ if subplan_tasks.include?(reference_task)
1338
+ next
1339
+ elsif parent_tasks.include?(reference_task)
1340
+ edges << op
1341
+ elsif plan.in_useful_subplan?(self, reference_task) || plan.in_useful_subplan?(object, reference_task)
1342
+ subplan_tasks << reference_task
1343
+ else
1344
+ edges << op
1345
+ end
1637
1346
  end
1638
1347
 
1639
- mappings = [*mappings].zip([]) unless Hash === mappings
1640
- mappings.each do |from, _|
1641
- from = event_model(from).symbol
1642
- if user_handler
1643
- method_name = "event_handler_#{from}_#{Object.address_from_id(user_handler.object_id).to_s(16)}"
1644
- define_method(method_name, &user_handler)
1645
- handler_sets[from] << lambda { |event| event.task.send(method_name, event) }
1646
- end
1348
+ edges = edges.map do |g, removed_parent, removed_child, added_parent, added_child|
1349
+ [g, plan[removed_parent], plan[removed_child], plan[added_parent], plan[added_child]]
1647
1350
  end
1351
+ edges
1648
1352
  end
1649
1353
 
1650
- # call-seq:
1651
- # causal_link(:from => :to)
1652
- #
1653
- # Declares a causal link between two events in the task. See
1654
- # EventStructure::CausalLink for a description of the causal link
1655
- # relation.
1656
- def self.causal_link(mappings)
1657
- mappings.each do |from, to|
1658
- from = event_model(from).symbol
1659
- causal_link_sets[from].merge Array[*to].map { |ev| event_model(ev).symbol }.to_value_set
1354
+ # @api private
1355
+ def apply_replacement_operations(edges)
1356
+ edges.each do |g, removed_parent, removed_child, added_parent, added_child|
1357
+ info = g.edge_info(removed_parent, removed_child)
1358
+ g.add_relation(plan[added_parent], plan[added_child], info)
1660
1359
  end
1661
- update_terminal_flag
1662
- end
1360
+ edges.each do |g, removed_parent, removed_child, added_parent, added_child|
1361
+ if !g.copy_on_replace?
1362
+ g.remove_relation(plan[removed_parent], plan[removed_child])
1363
+ end
1364
+ end
1365
+ end
1663
1366
 
1664
- # call-seq:
1665
- # forward :from => :to
1367
+ # Replaces self's subplan by another subplan
1368
+ #
1369
+ # Replaces the subplan generated by self by the one generated by object.
1370
+ # In practice, it means that we transfer all parent edges whose target
1371
+ # is self from the receiver to object. It calls the various add/remove
1372
+ # hooks defined in {DirectedRelationSupport}.
1666
1373
  #
1667
- # Defines a forwarding relation between two events of the same task
1668
- # instance. See EventStructure::Forward for a description of the
1669
- # forwarding relation.
1374
+ # Relations to free events are not copied during replacement
1670
1375
  #
1671
- # See also Task#forward and EventGenerator#forward.
1672
- def self.forward(mappings)
1673
- mappings.each do |from, to|
1674
- from = event_model(from).symbol
1675
- targets = Array[*to].map { |ev| event_model(ev).symbol }
1376
+ # @see replace_by
1377
+ def replace_subplan_by(object)
1378
+ edges = compute_subplan_replacement_operation(object)
1379
+ apply_replacement_operations(edges)
1380
+
1381
+ initialize_replacement(object)
1382
+ each_event do |event|
1383
+ event.initialize_replacement(object.event(event.symbol))
1384
+ end
1385
+ end
1676
1386
 
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}"
1387
+ # @api private
1388
+ def compute_object_replacement_operation(object)
1389
+ edges = []
1390
+ plan.each_task_relation_graph do |g|
1391
+ next if g.strong?
1392
+
1393
+ g.each_in_neighbour(self) do |parent|
1394
+ if parent != object
1395
+ edges << [g, parent, self, parent, object]
1396
+ end
1397
+ end
1398
+ g.each_out_neighbour(self) do |child|
1399
+ if object != child
1400
+ edges << [g, self, child, object, child]
1681
1401
  end
1682
1402
  end
1403
+ end
1683
1404
 
1684
- forwarding_sets[from].merge targets.to_value_set
1405
+ plan.each_event_relation_graph do |g|
1406
+ next if g.strong?
1407
+
1408
+ each_event do |event|
1409
+ object_event = nil
1410
+ g.each_in_neighbour(event) do |parent|
1411
+ if !parent.respond_to?(:task) || (parent.task != self && parent.task != object)
1412
+ object_event ||= object.event(event.symbol)
1413
+ edges << [g, parent, event, parent, object_event]
1414
+ end
1415
+ end
1416
+ g.each_out_neighbour(event) do |child|
1417
+ if !child.respond_to?(:task) || (child.task != self && child.task != object)
1418
+ object_event ||= object.event(event.symbol)
1419
+ edges << [g, event, child, object_event, child]
1420
+ end
1421
+ end
1422
+ end
1685
1423
  end
1686
- update_terminal_flag
1687
- end
1424
+ edges
1425
+ end
1688
1426
 
1689
- def self.precondition(event, reason, &block)
1690
- event = event_model(event)
1691
- precondition_sets[event.symbol] << [reason, block]
1692
- end
1427
+ # Replaces self by object
1428
+ #
1429
+ # It replaces self by object in all relations +self+ is part of, and do
1430
+ # the same for the task's event generators.
1431
+ #
1432
+ # @see replace_subplan_by
1433
+ def replace_by(object)
1434
+ event_mappings = Hash.new
1435
+ event_resolver = ->(e) { object.event(e.symbol) }
1436
+ each_event do |ev|
1437
+ event_mappings[ev] = [nil, event_resolver]
1438
+ end
1439
+ object.each_event do |ev|
1440
+ event_mappings[ev] = nil
1441
+ end
1442
+ plan.replace_subplan(Hash[self => object, object => nil], event_mappings)
1693
1443
 
1694
- def to_s # :nodoc:
1695
- s = name.dup + arguments.to_s
1696
- id = owners.map do |owner|
1697
- next if owner == Roby::Distributed
1698
- sibling = remote_siblings[owner]
1699
- "#{sibling ? Object.address_from_id(sibling.ref).to_s(16) : 'nil'}@#{owner.remote_name}"
1700
- end
1701
- unless id.empty?
1702
- s << "[" << id.join(",") << "]"
1703
- end
1704
- s
1705
- end
1706
-
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
1444
+ initialize_replacement(object)
1445
+ each_event do |event|
1446
+ event.initialize_replacement(nil) { object.event(event.symbol) }
1447
+ end
1448
+ end
1449
+
1450
+ # @api private
1451
+ def initialize_replacement(task)
1452
+ super
1715
1453
 
1716
- pp.text "arguments: "
1717
- arguments.pretty_print(pp)
1454
+ execute_handlers.each do |handler|
1455
+ if handler.copy_on_replace?
1456
+ task.execute(handler.as_options, &handler.block)
1718
1457
  end
1719
- else
1720
- pp.text " "
1721
- arguments.pretty_print(pp)
1722
1458
  end
1723
- end
1724
1459
 
1725
- # True if this task is a null task. See NullTask.
1726
- def null?; false end
1727
- # Converts this object into a task object
1728
- def to_task; self end
1729
-
1730
- event :start, :command => true
1460
+ poll_handlers.each do |handler|
1461
+ if handler.copy_on_replace?
1462
+ task.poll(handler.as_options, &handler.block)
1463
+ end
1464
+ end
1465
+ end
1731
1466
 
1732
- # Define :stop before any other terminal event
1733
- event :stop
1734
- event :success, :terminal => true
1735
- event :failed, :terminal => true
1467
+ # @deprecated this has no equivalent. It really has never seen proper support
1468
+ def simulate
1469
+ simulation_task = self.model.simulation_model.new(arguments.to_hash)
1470
+ plan.force_replace(self, simulation_task)
1471
+ simulation_task
1472
+ end
1736
1473
 
1737
- event :aborted
1738
- forward :aborted => :failed
1474
+ # Returns an object that will allow to track this task's role in the
1475
+ # plan regardless of replacements
1476
+ #
1477
+ # The returning object will point to the replacing object when self is
1478
+ # replaced by something. In effect, it points to the task's role in the
1479
+ # plan instead of to the actual task itself.
1480
+ #
1481
+ # @return [PlanService]
1482
+ def as_service
1483
+ @service ||= (plan.find_plan_service(self) || PlanService.new(self))
1484
+ end
1739
1485
 
1740
- # The internal data for this task
1741
- attr_reader :data
1742
- # Sets the internal data value for this task. This calls the
1743
- # #updated_data hook, and emits +updated_data+ if the task is running.
1744
- def data=(value)
1745
- @data = value
1746
- updated_data
1747
- emit :updated_data if running?
1748
- end
1749
- # This hook is called whenever the internal data of this task is
1750
- # updated. See #data, #data= and the +updated_data+ event
1751
- def updated_data
1752
- super if defined? super
1753
- end
1754
- event :updated_data, :command => false
1755
-
1756
- # Checks if +task+ is in the same execution state than +self+
1757
- # Returns true if they are either both running or both pending
1758
- def compatible_state?(task)
1759
- finished? || !(running? ^ task.running?)
1760
- end
1761
-
1762
- # Returns the lists of tags this model fullfills.
1763
- def self.tags
1764
- ancestors.find_all { |m| m.instance_of?(TaskModelTag) }
1765
- end
1766
-
1767
- # The fullfills? predicate checks if this task can be used
1768
- # to fullfill the need of the given +model+ and +arguments+
1769
- # The default is to check if
1770
- # * the needed task model is an ancestor of this task
1771
- # * the task
1772
- # * +args+ is included in the task arguments
1773
- def fullfills?(models, args = {})
1774
- if models.kind_of?(Task)
1775
- klass, args =
1776
- models.model,
1777
- models.meaningful_arguments
1778
- models = [klass]
1779
- else
1780
- models = [*models]
1781
- end
1782
- self_model = self.model
1783
- self_args = self.arguments
1784
- args = args.dup
1785
-
1786
- # Check the arguments that are required by the model
1787
- for tag in models
1788
- if !self_model.has_ancestor?(tag)
1789
- return false
1790
- end
1791
-
1792
- unless args.empty?
1793
- for arg_name in tag.arguments
1794
- if user_arg = args.delete(arg_name)
1795
- return false unless user_arg == self_args[arg_name]
1796
- end
1797
- break if args.empty?
1798
- end
1799
- end
1800
- end
1801
-
1802
- if !args.empty?
1803
- raise ArgumentError, "the arguments '#{args.keys.join(", ")}' are unknown to the tags #{models.join(", ")}"
1804
- end
1805
- true
1806
- end
1807
-
1808
- # True if +self+ can be used to replace +target+
1809
- def can_replace?(target)
1810
- fullfills?(*target.fullfilled_model)
1486
+ def as_plan
1487
+ self
1811
1488
  end
1812
1489
 
1813
- def can_merge?(target)
1814
- target_model = target.fullfilled_model
1815
- if !fullfills?(target_model.first)
1816
- return false
1817
- end
1490
+ # Register a hook that is called when this task is finalized (removed from its plan)
1491
+ #
1492
+ # @macro InstanceHandlerOptions
1493
+ def when_finalized(options = Hash.new, &block)
1494
+ default = if abstract? then :copy else :drop end
1495
+ options, remaining = InstanceHandler.filter_options options, on_replace: default
1496
+ super(options.merge(remaining), &block)
1497
+ end
1818
1498
 
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
1499
+ def internal_error_handler(exception)
1500
+ if !exception.originates_from?(self)
1501
+ return pass_exception
1502
+ end
1826
1503
 
1827
- include ExceptionHandlingObject
1828
- inherited_enumerable('exception_handler', 'exception_handlers') { Array.new }
1504
+ gen = exception.generator
1505
+ error = exception.exception
1506
+ if (gen == start_event) && !gen.emitted?
1507
+ if !failed_to_start?
1508
+ failed_to_start!(error)
1509
+ end
1510
+ elsif !running?
1511
+ pass_exception
1512
+ elsif (!gen || !gen.terminal?) && !internal_error_event.emitted?
1513
+ internal_error_event.emit(error)
1514
+ if stop_event.pending? || !stop_event.controlable?
1515
+ # In this case, we can't "just" stop the task. We have
1516
+ # to inject +error+ in the exception handling and kill
1517
+ # everything that depends on it.
1518
+ add_error(TaskEmergencyTermination.new(self, error, false))
1519
+ end
1520
+ else
1521
+ if execution_engine.display_exceptions?
1522
+ # No nice way to isolate this error through the task
1523
+ # interface, as we can't emergency stop it. Quarantine it
1524
+ # and inject it in the normal exception propagation
1525
+ # mechanisms.
1526
+ execution_engine.fatal "putting #{self} in quarantine: #{self} failed to emit"
1527
+ execution_engine.fatal "the error is:"
1528
+ Roby.log_exception_with_backtrace(error, execution_engine, :fatal)
1529
+ end
1829
1530
 
1830
- # Lists all exception handlers attached to this task
1831
- def each_exception_handler(&iterator); model.each_exception_handler(&iterator) end
1832
-
1833
- @@exception_handler_id = 0
1834
-
1835
- ##
1836
- # :call-seq:
1837
- # on_exception(exception_class, ...) { |task, exception_object| ... }
1838
- #
1839
- # Defines an exception handler. matcher === exception_object is used to
1840
- # determine if the handler should be called when +exception_object+ has
1841
- # been fired. The first matching handler is called. Call #pass_exception to pass
1842
- # the exception to previous handlers
1843
- #
1844
- # on_exception(TaskModelViolation, ...) do |task, exception_object|
1845
- # if cannot_handle
1846
- # task.pass_exception # send to the next handler
1847
- # end
1848
- # do_handle
1849
- # end
1850
- def self.on_exception(*matchers, &handler)
1851
- check_arity(handler, 1)
1852
- id = (@@exception_handler_id += 1)
1853
- define_method("exception_handler_#{id}", &handler)
1854
- exception_handlers.unshift [matchers, instance_method("exception_handler_#{id}")]
1855
- end
1856
-
1857
- # We can't add relations on objects we don't own
1858
- def add_child_object(child, type, info)
1859
- unless read_write? && child.read_write?
1860
- raise OwnershipError, "cannot add a relation between tasks we don't own. #{self} by #{owners.to_a} and #{child} is owned by #{child.owners.to_a}"
1861
- end
1862
-
1863
- super
1864
- end
1865
-
1866
- # This method is called during the commit process to
1867
- def commit_transaction
1868
- super if defined? super
1869
-
1870
- arguments.dup.each do |key, value|
1871
- if value.kind_of?(Roby::Transactions::Proxy)
1872
- arguments.update!(key, value.__getobj__)
1873
- end
1874
- end
1875
- end
1531
+ plan.quarantine_task(self)
1532
+ add_error(TaskEmergencyTermination.new(self, error, true))
1533
+ end
1534
+ end
1535
+ private :internal_error_handler
1876
1536
 
1877
- # Create a new task of the same model and with the same arguments
1878
- # than this one. Insert this task in the plan and make it replace
1879
- # the fresh one.
1537
+ on_exception(Roby::CodeError) do |exception|
1538
+ internal_error_handler(exception)
1539
+ end
1540
+
1541
+ # Creates a sequence where +self+ will be started first, and +task+ is
1542
+ # started if +self+ finished successfully. The returned value is an
1543
+ # instance of Sequence.
1880
1544
  #
1881
- # See Plan#respawn
1882
- def respawn
1883
- plan.respawn(self)
1884
- end
1885
-
1886
- # Replaces, in the plan, the subplan generated by this plan object by
1887
- # the one generated by +object+. In practice, it means that we transfer
1888
- # all parent edges whose target is +self+ from the receiver to
1889
- # +object+. It calls the various add/remove hooks defined in
1890
- # DirectedRelationSupport.
1891
- def replace_subplan_by(object)
1892
- super
1893
-
1894
- # Compute the set of tasks that are in our subtree and not in
1895
- # object's *after* the replacement
1896
- own_subtree = ValueSet.new
1897
- TaskStructure.each_root_relation do |rel|
1898
- own_subtree.merge generated_subgraph(rel)
1899
- own_subtree -= object.generated_subgraph(rel)
1900
- end
1901
-
1902
- changes = []
1903
- each_event do |event|
1904
- next unless object.has_event?(event.symbol)
1905
- changes.clear
1906
-
1907
- event.each_relation do |rel|
1908
- parents = []
1909
- event.each_parent_object(rel) do |parent|
1910
- if !parent.respond_to?(:task) || !own_subtree.include?(parent.task)
1911
- parents << parent << parent[event, rel]
1912
- end
1913
- end
1914
- children = []
1915
- event.each_child_object(rel) do |child|
1916
- if !child.respond_to?(:task) || !own_subtree.include?(child.task)
1917
- children << child << event[child, rel]
1918
- end
1919
- end
1920
- changes << rel << parents << children
1921
- end
1922
-
1923
- event.apply_relation_changes(object.event(event.symbol), changes)
1924
- end
1925
- end
1926
-
1927
- # Replaces +self+ by +object+ in all relations +self+ is part of, and
1928
- # do the same for the task's event generators.
1929
- def replace_by(object)
1930
- each_event do |event|
1931
- event_name = event.symbol
1932
- if object.has_event?(event_name)
1933
- event.replace_by object.event(event_name)
1934
- end
1935
- end
1936
- super
1937
- end
1938
-
1939
- # Define a polling block for this task. The polling block is called at
1940
- # each execution cycle while the task is running (i.e. in-between the
1941
- # emission of +start+ and the emission of +stop+.
1942
- #
1943
- # Raises ArgumentError if the task is already running.
1944
- #
1945
- # See also Task::poll
1946
- def poll(&block)
1947
- if !pending?
1948
- raise ArgumentError, "cannot set a polling block when the task is not pending"
1949
- end
1950
-
1951
-
1952
- singleton_class.class_eval do
1953
- setup_poll_method(block)
1954
- end
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) }
1957
- end
1958
- end
1545
+ # Note that this operator always creates a new Sequence object, so
1546
+ #
1547
+ # a + b + c + d
1548
+ #
1549
+ # will create 3 Sequence instances. If more than two tasks should be
1550
+ # organized in a sequence, one should instead use Sequence#<<:
1551
+ #
1552
+ # Sequence.new << a << b << c << d
1553
+ #
1554
+ def +(task)
1555
+ # !!!! + is NOT commutative
1556
+ if task.null?
1557
+ self
1558
+ elsif self.null?
1559
+ task
1560
+ else
1561
+ Tasks::Sequence.new << self << task
1562
+ end
1563
+ end
1959
1564
 
1960
- # A special task model which does nothing and emits +success+
1961
- # as soon as it is started.
1962
- class NullTask < Task
1963
- event :start, :command => true
1964
- event :stop
1965
- forward :start => :success
1565
+ # Creates a parallel aggregation between +self+ and +task+. Both tasks
1566
+ # are started at the same time, and the returned instance finishes when
1567
+ # both tasks are finished. The returned value is an instance of
1568
+ # Parallel.
1569
+ #
1570
+ # Note that this operator always creates a new Parallel object, so
1571
+ #
1572
+ # a | b | c | d
1573
+ #
1574
+ # will create three instances of Parallel. If more than two tasks should
1575
+ # be organized that way, one should instead use Parallel#<<:
1576
+ #
1577
+ # Parallel.new << a << b << c << d
1578
+ #
1579
+ def |(task)
1580
+ if self.null?
1581
+ task
1582
+ elsif task.null?
1583
+ self
1584
+ else
1585
+ Tasks::Parallel.new << self << task
1586
+ end
1587
+ end
1966
1588
 
1967
- # Always true. See Task#null?
1968
- def null?; true end
1969
- end
1589
+ def to_execution_exception
1590
+ ExecutionException.new(LocalizedError.new(self))
1591
+ end
1970
1592
 
1971
- # A virtual task is a task representation for a combination of two events.
1972
- # This allows to combine two unrelated events, one being the +start+ event
1973
- # of the virtual task and the other its success event.
1974
- #
1975
- # The task fails if the success event becomes unreachable.
1976
- #
1977
- # See VirtualTask.create
1978
- class VirtualTask < Task
1979
- # The start event
1980
- attr_reader :start_event
1981
- # The success event
1982
- attr_accessor :success_event
1983
- # Set the start event
1984
- def start_event=(ev)
1985
- if !ev.controlable?
1986
- raise ArgumentError, "the start event of a virtual task must be controlable"
1987
- end
1988
- @start_event = ev
1989
- end
1990
-
1991
- event :start do
1992
- event(:start).achieve_with(start_event)
1993
- start_event.call
1994
- end
1995
- on :start do
1996
- success_event.forward_to_once event(:success)
1997
- success_event.if_unreachable(true) do
1998
- emit :failed if executable?
1999
- end
2000
- end
2001
-
2002
- terminates
2003
-
2004
- # Creates a new VirtualTask with the given start and success events
2005
- def self.create(start, success)
2006
- task = VirtualTask.new
2007
- task.start_event = start
2008
- task.success_event = success
2009
-
2010
- if start.respond_to?(:task)
2011
- task.realized_by start.task
2012
- end
2013
- if success.respond_to?(:task)
2014
- task.realized_by success.task
2015
- end
2016
-
2017
- task
2018
- end
1593
+ # @api private
1594
+ def create_transaction_proxy(transaction)
1595
+ transaction.create_and_register_proxy_task(self)
1596
+ end
1597
+
1598
+ # Return a task match object that matches self
1599
+ #
1600
+ # @return [Queries::TaskMatcher]
1601
+ def match
1602
+ self.class.match.with_instance(self)
1603
+ end
1604
+
1605
+ # Enumerate the coordination objects currently attached to this task
1606
+ #
1607
+ # @yieldparam [Coordination::Base] object
1608
+ def each_coordination_object(&block)
1609
+ @coordination_objects.each(&block)
1610
+ end
1611
+
1612
+ # @api private
1613
+ #
1614
+ # Declare that a coordination object is attached to this task
1615
+ #
1616
+ # @param [Coordination::Base] object
1617
+ def add_coordination_object(object)
1618
+ @coordination_objects.push(object)
1619
+ end
1620
+
1621
+ # @api private
1622
+ #
1623
+ # Declare that a coordination object is no longer attached to this task
1624
+ #
1625
+ # @param [Coordination::Base] object
1626
+ def remove_coordination_object(object)
1627
+ @coordination_objects.delete(object)
1628
+ end
2019
1629
  end
2020
1630
 
1631
+
2021
1632
  unless defined? TaskStructure
2022
- TaskStructure = RelationSpace(Task)
1633
+ TaskStructure = RelationSpace(Task)
1634
+ TaskStructure.default_graph_class = Relations::TaskRelationGraph
2023
1635
  end
2024
-
2025
1636
  end
2026
1637