roby 0.8.0 → 3.0.0

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