roby 0.8.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (644) hide show
  1. checksums.yaml +7 -0
  2. data/.deep-cover.rb +3 -0
  3. data/.gitattributes +1 -0
  4. data/.gitignore +24 -0
  5. data/.simplecov +10 -0
  6. data/.travis.yml +17 -0
  7. data/.yardopts +4 -0
  8. data/Gemfile +15 -0
  9. data/README.md +11 -0
  10. data/Rakefile +47 -177
  11. data/benchmark/{alloc_misc.rb → attic/alloc_misc.rb} +2 -2
  12. data/benchmark/{discovery_latency.rb → attic/discovery_latency.rb} +19 -19
  13. data/benchmark/{garbage_collection.rb → attic/garbage_collection.rb} +9 -9
  14. data/benchmark/{genom.rb → attic/genom.rb} +0 -0
  15. data/benchmark/attic/transactions.rb +62 -0
  16. data/benchmark/plan_basic_operations.rb +28 -0
  17. data/benchmark/relations/graph.rb +63 -0
  18. data/benchmark/ruby/identity.rb +18 -0
  19. data/benchmark/ruby/set_intersect_vs_hash_merge.rb +39 -0
  20. data/benchmark/ruby/yield_vs_block.rb +35 -0
  21. data/benchmark/run +5 -0
  22. data/benchmark/synthetic_plan_modifications_with_transactions.rb +79 -0
  23. data/benchmark/transactions.rb +99 -51
  24. data/bin/roby +38 -197
  25. data/bin/roby-display +14 -0
  26. data/bin/roby-log +3 -176
  27. data/doc/guide/{src → attic}/abstraction/achieve_with.page +1 -1
  28. data/doc/guide/{src → attic}/abstraction/forwarding.page +1 -1
  29. data/doc/guide/{src → attic}/abstraction/hierarchy.page +1 -1
  30. data/doc/guide/{src → attic}/abstraction/index.page +1 -1
  31. data/doc/guide/{src → attic}/abstraction/task_models.page +1 -1
  32. data/doc/guide/{overview.rdoc → attic/cycle/api_overview.rdoc} +6 -1
  33. data/doc/guide/{src → attic}/cycle/cycle-overview.png +0 -0
  34. data/doc/guide/{src → attic}/cycle/cycle-overview.svg +0 -0
  35. data/doc/guide/attic/cycle/error_handling.page +98 -0
  36. data/doc/guide/{src → attic}/cycle/error_instantaneous_repair.png +0 -0
  37. data/doc/guide/{src → attic}/cycle/error_instantaneous_repair.svg +0 -0
  38. data/doc/guide/{src/cycle/error_handling.page → attic/cycle/error_sources.page} +46 -89
  39. data/doc/guide/{src → attic}/cycle/garbage_collection.page +1 -1
  40. data/doc/guide/{src → attic}/cycle/index.page +1 -1
  41. data/doc/guide/{src → attic}/cycle/propagation.page +11 -1
  42. data/doc/guide/{src → attic}/cycle/propagation_diamond.png +0 -0
  43. data/doc/guide/{src → attic}/cycle/propagation_diamond.svg +0 -0
  44. data/doc/guide/attic/plans/building_plans.page +89 -0
  45. data/doc/guide/attic/plans/code.page +192 -0
  46. data/doc/guide/{src/basics → attic/plans}/events.page +3 -4
  47. data/doc/guide/attic/plans/index.page +7 -0
  48. data/doc/guide/{plan_modifications.rdoc → attic/plans/plan_modifications.rdoc} +5 -3
  49. data/doc/guide/{src/basics → attic/plans}/plan_objects.page +2 -1
  50. data/doc/guide/attic/plans/querying_plans.page +5 -0
  51. data/doc/guide/{src/basics → attic/plans}/tasks.page +20 -20
  52. data/doc/guide/config.yaml +7 -4
  53. data/doc/guide/ext/extended_menu.rb +29 -0
  54. data/doc/guide/ext/init.rb +6 -0
  55. data/doc/guide/ext/rdoc_links.rb +7 -6
  56. data/doc/guide/src/advanced_concepts/history.page +5 -0
  57. data/doc/guide/src/advanced_concepts/index.page +11 -0
  58. data/doc/guide/src/advanced_concepts/recognizing_patterns.page +83 -0
  59. data/doc/guide/src/advanced_concepts/scheduling.page +87 -0
  60. data/doc/guide/src/advanced_concepts/transactions.page +5 -0
  61. data/doc/guide/src/advanced_concepts/unreachability.page +42 -0
  62. data/doc/guide/src/base.template +96 -0
  63. data/doc/guide/src/basics_shell_header.txt +5 -7
  64. data/doc/guide/src/building/action_coordination.page +96 -0
  65. data/doc/guide/src/building/actions.page +124 -0
  66. data/doc/guide/src/building/file_layout.page +71 -0
  67. data/doc/guide/src/building/index.page +50 -0
  68. data/doc/guide/src/building/patterns.page +86 -0
  69. data/doc/guide/src/building/patterns_forwarding.png +0 -0
  70. data/doc/guide/src/building/patterns_forwarding.svg +277 -0
  71. data/doc/guide/src/building/runtime.page +95 -0
  72. data/doc/guide/src/building/task_models.page +94 -0
  73. data/doc/guide/src/building/tasks.page +284 -0
  74. data/doc/guide/src/concepts/error_handling.page +100 -0
  75. data/doc/guide/src/concepts/exception_propagation.png +0 -0
  76. data/doc/guide/src/concepts/exception_propagation.svg +445 -0
  77. data/doc/guide/src/concepts/execution.page +85 -0
  78. data/doc/guide/src/concepts/execution.png +0 -0
  79. data/doc/guide/src/concepts/execution.svg +573 -0
  80. data/doc/guide/src/concepts/execution_cycle.png +0 -0
  81. data/doc/guide/src/concepts/garbage_collection.page +57 -0
  82. data/doc/guide/src/concepts/index.page +27 -0
  83. data/doc/guide/src/concepts/plans.page +101 -0
  84. data/doc/guide/src/concepts/policy.page +31 -0
  85. data/doc/guide/src/concepts/reactor.page +61 -0
  86. data/doc/guide/src/concepts/simple_plan_example.png +0 -0
  87. data/doc/guide/src/concepts/simple_plan_example.svg +376 -0
  88. data/doc/guide/src/default.template +9 -74
  89. data/doc/guide/src/event_relations/forward.page +71 -0
  90. data/doc/guide/src/event_relations/index.page +12 -0
  91. data/doc/guide/src/event_relations/scheduling_constraints.page +43 -0
  92. data/doc/guide/src/event_relations/signal.page +55 -0
  93. data/doc/guide/src/event_relations/temporal_constraints.page +77 -0
  94. data/doc/guide/src/htmldoc.metainfo +21 -8
  95. data/doc/guide/src/index.page +8 -3
  96. data/doc/guide/src/{introduction/install.page → installation/index.page} +37 -25
  97. data/doc/guide/src/installation/publications.page +14 -0
  98. data/doc/guide/src/{introduction → installation}/videos.page +14 -7
  99. data/doc/guide/src/interacting/index.page +16 -0
  100. data/doc/guide/src/interacting/run.page +33 -0
  101. data/doc/guide/src/interacting/shell.page +95 -0
  102. data/doc/guide/src/plugins/creating_plugins.page +72 -0
  103. data/doc/guide/src/plugins/index.page +27 -5
  104. data/doc/guide/src/plugins/{fault_tolerance.page → standard_plugins/fault_tolerance.page} +2 -2
  105. data/doc/guide/src/plugins/standard_plugins/index.page +11 -0
  106. data/doc/guide/src/plugins/{subsystems.page → standard_plugins/subsystems.page} +2 -2
  107. data/doc/guide/src/style_screen.css +687 -0
  108. data/doc/guide/src/task_relations/dependency.page +107 -0
  109. data/doc/guide/src/task_relations/executed_by.page +77 -0
  110. data/doc/guide/src/task_relations/index.page +12 -0
  111. data/doc/guide/src/task_relations/new_relations.page +119 -0
  112. data/doc/guide/src/task_relations/planned_by.page +46 -0
  113. data/doc/guide/src/tutorial/app.page +117 -0
  114. data/doc/guide/src/{basics → tutorial}/code_examples.page +6 -5
  115. data/doc/guide/src/{basics → tutorial}/dry.page +15 -15
  116. data/doc/guide/src/{basics → tutorial}/errors.page +43 -68
  117. data/doc/guide/src/tutorial/events.page +195 -0
  118. data/doc/guide/src/{basics → tutorial}/hierarchy.page +53 -52
  119. data/doc/guide/src/tutorial/index.page +13 -0
  120. data/doc/guide/src/tutorial/log_replay/goForward_1.png +0 -0
  121. data/doc/guide/src/tutorial/log_replay/goForward_2.png +0 -0
  122. data/doc/guide/src/tutorial/log_replay/goForward_3.png +0 -0
  123. data/doc/guide/src/{basics → tutorial}/log_replay/goForward_4.png +0 -0
  124. data/doc/guide/src/tutorial/log_replay/goForward_5.png +0 -0
  125. data/doc/guide/src/{basics → tutorial}/log_replay/hierarchy_error_1.png +0 -0
  126. data/doc/guide/src/{basics → tutorial}/log_replay/hierarchy_error_2.png +0 -0
  127. data/doc/guide/src/{basics → tutorial}/log_replay/hierarchy_error_3.png +0 -0
  128. data/doc/guide/src/tutorial/log_replay/moveto_code_error.png +0 -0
  129. data/doc/guide/src/{basics → tutorial}/log_replay/plan_repair_1.png +0 -0
  130. data/doc/guide/src/{basics → tutorial}/log_replay/plan_repair_2.png +0 -0
  131. data/doc/guide/src/{basics → tutorial}/log_replay/plan_repair_3.png +0 -0
  132. data/doc/guide/src/tutorial/log_replay/plan_repair_4.png +0 -0
  133. data/doc/guide/src/tutorial/log_replay/roby_log_main_window.png +0 -0
  134. data/doc/guide/src/{basics → tutorial}/log_replay/roby_log_relation_window.png +0 -0
  135. data/doc/guide/src/{basics → tutorial}/log_replay/roby_replay_event_representation.png +0 -0
  136. data/doc/guide/src/tutorial/relations_display.page +153 -0
  137. data/doc/guide/src/{basics → tutorial}/roby_cycle_overview.png +0 -0
  138. data/doc/guide/src/tutorial/shell.page +121 -0
  139. data/doc/guide/src/{basics → tutorial}/summary.page +1 -1
  140. data/doc/guide/src/tutorial/tasks.page +374 -0
  141. data/lib/roby.rb +102 -47
  142. data/lib/roby/actions.rb +17 -0
  143. data/lib/roby/actions/action.rb +80 -0
  144. data/lib/roby/actions/interface.rb +45 -0
  145. data/lib/roby/actions/library.rb +23 -0
  146. data/lib/roby/actions/models/action.rb +224 -0
  147. data/lib/roby/actions/models/coordination_action.rb +58 -0
  148. data/lib/roby/actions/models/interface.rb +22 -0
  149. data/lib/roby/actions/models/interface_base.rb +294 -0
  150. data/lib/roby/actions/models/library.rb +12 -0
  151. data/lib/roby/actions/models/method_action.rb +90 -0
  152. data/lib/roby/actions/task.rb +114 -0
  153. data/lib/roby/and_generator.rb +125 -0
  154. data/lib/roby/app.rb +2795 -829
  155. data/lib/roby/app/autotest_console_reporter.rb +138 -0
  156. data/lib/roby/app/base.rb +21 -0
  157. data/lib/roby/app/cucumber.rb +2 -0
  158. data/lib/roby/app/cucumber/controller.rb +439 -0
  159. data/lib/roby/app/cucumber/helpers.rb +280 -0
  160. data/lib/roby/app/cucumber/world.rb +32 -0
  161. data/lib/roby/app/debug.rb +136 -0
  162. data/lib/roby/app/gen.rb +2 -0
  163. data/lib/roby/app/rake.rb +178 -38
  164. data/lib/roby/app/robot_config.rb +9 -0
  165. data/lib/roby/app/robot_names.rb +115 -0
  166. data/lib/roby/app/run.rb +3 -2
  167. data/lib/roby/app/scripts.rb +72 -0
  168. data/lib/roby/app/scripts/autotest.rb +173 -0
  169. data/lib/roby/app/scripts/display.rb +2 -0
  170. data/lib/roby/app/scripts/restart.rb +52 -0
  171. data/lib/roby/app/scripts/results.rb +17 -8
  172. data/lib/roby/app/scripts/run.rb +155 -24
  173. data/lib/roby/app/scripts/shell.rb +147 -62
  174. data/lib/roby/app/scripts/test.rb +107 -22
  175. data/lib/roby/app/test_reporter.rb +74 -0
  176. data/lib/roby/app/test_server.rb +159 -0
  177. data/lib/roby/app/vagrant.rb +47 -0
  178. data/lib/roby/backports.rb +16 -0
  179. data/lib/roby/cli/display.rb +190 -0
  180. data/lib/roby/cli/exceptions.rb +17 -0
  181. data/lib/roby/cli/gen/actions/class.rb +5 -0
  182. data/lib/roby/cli/gen/actions/test.rb +6 -0
  183. data/lib/roby/cli/gen/app/.yardopts +6 -0
  184. data/lib/roby/cli/gen/app/README.md +28 -0
  185. data/lib/roby/cli/gen/app/Rakefile +15 -0
  186. data/{app → lib/roby/cli/gen/app}/config/app.yml +29 -39
  187. data/lib/roby/cli/gen/app/models/.gitattributes +1 -0
  188. data/{app → lib/roby/cli/gen/app/scripts}/controllers/.gitattributes +0 -0
  189. data/{app/data/.gitattributes → lib/roby/cli/gen/app/test/.gitignore} +0 -0
  190. data/lib/roby/cli/gen/class/class.rb +6 -0
  191. data/lib/roby/cli/gen/class/test.rb +7 -0
  192. data/lib/roby/cli/gen/helpers.rb +203 -0
  193. data/lib/roby/cli/gen/module/module.rb +5 -0
  194. data/lib/roby/cli/gen/module/test.rb +6 -0
  195. data/lib/roby/cli/gen/roby_app/config/init.rb +17 -0
  196. data/lib/roby/cli/gen/roby_app/config/robots/robot.rb +40 -0
  197. data/lib/roby/cli/gen/task/class.rb +44 -0
  198. data/lib/roby/cli/gen/task/test.rb +6 -0
  199. data/lib/roby/cli/gen_main.rb +120 -0
  200. data/lib/roby/cli/log.rb +276 -0
  201. data/lib/roby/cli/log/flamegraph.html +499 -0
  202. data/lib/roby/cli/log/flamegraph_renderer.rb +88 -0
  203. data/lib/roby/cli/main.rb +153 -0
  204. data/lib/roby/coordination.rb +60 -0
  205. data/lib/roby/coordination/action_script.rb +25 -0
  206. data/lib/roby/coordination/action_state_machine.rb +125 -0
  207. data/lib/roby/coordination/actions.rb +106 -0
  208. data/lib/roby/coordination/base.rb +145 -0
  209. data/lib/roby/coordination/calculus.rb +40 -0
  210. data/lib/roby/coordination/child.rb +28 -0
  211. data/lib/roby/coordination/event.rb +29 -0
  212. data/lib/roby/coordination/fault_handler.rb +25 -0
  213. data/lib/roby/coordination/fault_handling_task.rb +13 -0
  214. data/lib/roby/coordination/fault_response_table.rb +110 -0
  215. data/lib/roby/coordination/models/action_script.rb +64 -0
  216. data/lib/roby/coordination/models/action_state_machine.rb +224 -0
  217. data/lib/roby/coordination/models/actions.rb +191 -0
  218. data/lib/roby/coordination/models/arguments.rb +55 -0
  219. data/lib/roby/coordination/models/base.rb +176 -0
  220. data/lib/roby/coordination/models/capture.rb +86 -0
  221. data/lib/roby/coordination/models/child.rb +35 -0
  222. data/lib/roby/coordination/models/event.rb +41 -0
  223. data/lib/roby/coordination/models/exceptions.rb +42 -0
  224. data/lib/roby/coordination/models/fault_handler.rb +219 -0
  225. data/lib/roby/coordination/models/fault_response_table.rb +77 -0
  226. data/lib/roby/coordination/models/root.rb +22 -0
  227. data/lib/roby/coordination/models/script.rb +283 -0
  228. data/lib/roby/coordination/models/task.rb +184 -0
  229. data/lib/roby/coordination/models/task_from_action.rb +50 -0
  230. data/lib/roby/coordination/models/task_from_as_plan.rb +33 -0
  231. data/lib/roby/coordination/models/task_from_instanciation_object.rb +31 -0
  232. data/lib/roby/coordination/models/task_from_variable.rb +27 -0
  233. data/lib/roby/coordination/models/task_with_dependencies.rb +48 -0
  234. data/lib/roby/coordination/models/variable.rb +32 -0
  235. data/lib/roby/coordination/script.rb +200 -0
  236. data/lib/roby/coordination/script_instruction.rb +12 -0
  237. data/lib/roby/coordination/task.rb +45 -0
  238. data/lib/roby/coordination/task_base.rb +69 -0
  239. data/lib/roby/coordination/task_script.rb +293 -0
  240. data/lib/roby/coordination/task_state_machine.rb +308 -0
  241. data/lib/roby/decision_control.rb +33 -21
  242. data/lib/roby/distributed_object.rb +76 -0
  243. data/lib/roby/droby.rb +17 -0
  244. data/lib/roby/droby/droby_id.rb +6 -0
  245. data/lib/roby/droby/enable.rb +153 -0
  246. data/lib/roby/droby/event_logger.rb +189 -0
  247. data/lib/roby/droby/event_logging.rb +57 -0
  248. data/lib/roby/droby/exceptions.rb +14 -0
  249. data/lib/roby/droby/identifiable.rb +22 -0
  250. data/lib/roby/droby/logfile.rb +141 -0
  251. data/lib/roby/droby/logfile/client.rb +176 -0
  252. data/lib/roby/droby/logfile/file_format.md +97 -0
  253. data/lib/roby/droby/logfile/index.rb +117 -0
  254. data/lib/roby/droby/logfile/reader.rb +139 -0
  255. data/lib/roby/droby/logfile/server.rb +199 -0
  256. data/lib/roby/droby/logfile/writer.rb +114 -0
  257. data/lib/roby/droby/marshal.rb +264 -0
  258. data/lib/roby/droby/marshallable.rb +12 -0
  259. data/lib/roby/droby/null_event_logger.rb +25 -0
  260. data/lib/roby/droby/object_manager.rb +205 -0
  261. data/lib/roby/droby/peer_id.rb +6 -0
  262. data/lib/roby/droby/plan_rebuilder.rb +373 -0
  263. data/lib/roby/droby/rebuilt_plan.rb +160 -0
  264. data/lib/roby/droby/remote_droby_id.rb +6 -0
  265. data/lib/roby/droby/timepoints.rb +205 -0
  266. data/lib/roby/droby/timepoints_ctf.metadata.erb +101 -0
  267. data/lib/roby/droby/timepoints_ctf.rb +125 -0
  268. data/lib/roby/droby/v5.rb +14 -0
  269. data/lib/roby/droby/v5/builtin.rb +120 -0
  270. data/lib/roby/droby/v5/droby_class.rb +45 -0
  271. data/lib/roby/droby/v5/droby_constant.rb +81 -0
  272. data/lib/roby/droby/v5/droby_dump.rb +1026 -0
  273. data/lib/roby/droby/v5/droby_id.rb +44 -0
  274. data/lib/roby/droby/v5/droby_model.rb +82 -0
  275. data/lib/roby/droby/v5/peer_id.rb +10 -0
  276. data/lib/roby/droby/v5/remote_droby_id.rb +42 -0
  277. data/lib/roby/event.rb +79 -957
  278. data/lib/roby/event_constraints.rb +835 -0
  279. data/lib/roby/event_generator.rb +1047 -0
  280. data/lib/roby/event_structure/causal_link.rb +6 -0
  281. data/lib/roby/event_structure/forwarding.rb +6 -0
  282. data/lib/roby/event_structure/precedence.rb +7 -0
  283. data/lib/roby/event_structure/signal.rb +8 -0
  284. data/lib/roby/event_structure/temporal_constraints.rb +640 -0
  285. data/lib/roby/exceptions.rb +446 -152
  286. data/lib/roby/executable_plan.rb +549 -0
  287. data/lib/roby/execution_engine.rb +1997 -950
  288. data/lib/roby/filter_generator.rb +26 -0
  289. data/lib/roby/gui/chronicle_view.rb +225 -0
  290. data/lib/roby/gui/chronicle_widget.rb +925 -0
  291. data/lib/roby/gui/dot_id.rb +11 -0
  292. data/lib/roby/gui/exception_view.rb +44 -0
  293. data/lib/roby/gui/log_display.rb +273 -0
  294. data/lib/roby/gui/model_views.rb +2 -0
  295. data/lib/roby/gui/model_views/action_interface.rb +53 -0
  296. data/lib/roby/gui/model_views/task.rb +47 -0
  297. data/lib/roby/gui/model_views/task.rhtml +41 -0
  298. data/lib/roby/gui/object_info_view.rb +89 -0
  299. data/lib/roby/gui/plan_dot_layout.rb +427 -0
  300. data/lib/roby/gui/plan_rebuilder_widget.rb +357 -0
  301. data/lib/roby/gui/qt4_toMSecsSinceEpoch.rb +8 -0
  302. data/lib/roby/gui/relations_view.rb +278 -0
  303. data/lib/roby/gui/relations_view/relations.ui +139 -0
  304. data/lib/roby/gui/relations_view/relations_canvas.rb +1088 -0
  305. data/lib/roby/gui/relations_view/relations_config.rb +292 -0
  306. data/lib/roby/gui/relations_view/relations_view.ui +53 -0
  307. data/lib/roby/gui/scheduler_view.css +24 -0
  308. data/lib/roby/gui/scheduler_view.rb +46 -0
  309. data/lib/roby/gui/scheduler_view.rhtml +53 -0
  310. data/lib/roby/gui/stepping.rb +93 -0
  311. data/lib/roby/gui/stepping.ui +181 -0
  312. data/lib/roby/gui/styles.rb +81 -0
  313. data/lib/roby/gui/task_display_configuration.rb +42 -0
  314. data/lib/roby/gui/task_state_at.rb +38 -0
  315. data/lib/roby/hooks.rb +26 -0
  316. data/lib/roby/interface.rb +136 -469
  317. data/lib/roby/interface/async.rb +20 -0
  318. data/lib/roby/interface/async/action_monitor.rb +188 -0
  319. data/lib/roby/interface/async/interface.rb +498 -0
  320. data/lib/roby/interface/async/job_monitor.rb +213 -0
  321. data/lib/roby/interface/async/log.rb +238 -0
  322. data/lib/roby/interface/async/new_job_listener.rb +79 -0
  323. data/lib/roby/interface/async/ui_connector.rb +183 -0
  324. data/lib/roby/interface/client.rb +553 -0
  325. data/lib/roby/interface/command.rb +24 -0
  326. data/lib/roby/interface/command_argument.rb +16 -0
  327. data/lib/roby/interface/command_library.rb +92 -0
  328. data/lib/roby/interface/droby_channel.rb +174 -0
  329. data/lib/roby/interface/exceptions.rb +22 -0
  330. data/lib/roby/interface/interface.rb +655 -0
  331. data/lib/roby/interface/job.rb +47 -0
  332. data/lib/roby/interface/rest.rb +10 -0
  333. data/lib/roby/interface/rest/api.rb +29 -0
  334. data/lib/roby/interface/rest/helpers.rb +24 -0
  335. data/lib/roby/interface/rest/server.rb +212 -0
  336. data/lib/roby/interface/server.rb +154 -0
  337. data/lib/roby/interface/shell_client.rb +468 -0
  338. data/lib/roby/interface/shell_subcommand.rb +24 -0
  339. data/lib/roby/interface/subcommand_client.rb +35 -0
  340. data/lib/roby/interface/tcp.rb +168 -0
  341. data/lib/roby/models/arguments.rb +112 -0
  342. data/lib/roby/models/plan_object.rb +83 -0
  343. data/lib/roby/models/task.rb +835 -0
  344. data/lib/roby/models/task_event.rb +62 -0
  345. data/lib/roby/models/task_service.rb +78 -0
  346. data/lib/roby/or_generator.rb +88 -0
  347. data/lib/roby/plan.rb +1751 -864
  348. data/lib/roby/plan_object.rb +611 -0
  349. data/lib/roby/plan_service.rb +200 -0
  350. data/lib/roby/promise.rb +332 -0
  351. data/lib/roby/queries.rb +23 -0
  352. data/lib/roby/queries/and_matcher.rb +32 -0
  353. data/lib/roby/queries/any.rb +27 -0
  354. data/lib/roby/queries/code_error_matcher.rb +58 -0
  355. data/lib/roby/queries/event_generator_matcher.rb +9 -0
  356. data/lib/roby/queries/execution_exception_matcher.rb +165 -0
  357. data/lib/roby/queries/index.rb +165 -0
  358. data/lib/roby/queries/localized_error_matcher.rb +149 -0
  359. data/lib/roby/queries/matcher_base.rb +107 -0
  360. data/lib/roby/queries/none.rb +27 -0
  361. data/lib/roby/queries/not_matcher.rb +30 -0
  362. data/lib/roby/queries/op_matcher.rb +8 -0
  363. data/lib/roby/queries/or_matcher.rb +30 -0
  364. data/lib/roby/queries/plan_object_matcher.rb +363 -0
  365. data/lib/roby/queries/query.rb +188 -0
  366. data/lib/roby/queries/task_event_generator_matcher.rb +86 -0
  367. data/lib/roby/queries/task_matcher.rb +344 -0
  368. data/lib/roby/relations.rb +42 -678
  369. data/lib/roby/relations/bidirectional_directed_adjacency_graph.rb +492 -0
  370. data/lib/roby/relations/directed_relation_support.rb +268 -0
  371. data/lib/roby/relations/event_relation_graph.rb +19 -0
  372. data/lib/roby/relations/fork_merge_visitor.rb +154 -0
  373. data/lib/roby/relations/graph.rb +533 -0
  374. data/lib/roby/relations/models/directed_relation_support.rb +11 -0
  375. data/lib/roby/relations/models/graph.rb +75 -0
  376. data/lib/roby/relations/models/task_relation_graph.rb +18 -0
  377. data/lib/roby/relations/space.rb +380 -0
  378. data/lib/roby/relations/task_relation_graph.rb +20 -0
  379. data/lib/roby/robot.rb +85 -38
  380. data/lib/roby/schedulers/basic.rb +155 -25
  381. data/lib/roby/schedulers/null.rb +20 -0
  382. data/lib/roby/schedulers/reporting.rb +31 -0
  383. data/lib/roby/schedulers/state.rb +129 -0
  384. data/lib/roby/schedulers/temporal.rb +91 -0
  385. data/lib/roby/singletons.rb +87 -0
  386. data/lib/roby/standalone.rb +4 -2
  387. data/lib/roby/standard_errors.rb +405 -82
  388. data/lib/roby/state.rb +6 -3
  389. data/lib/roby/state/conf_model.rb +5 -0
  390. data/lib/roby/state/events.rb +181 -95
  391. data/lib/roby/state/goal_model.rb +77 -0
  392. data/lib/roby/state/open_struct.rb +591 -0
  393. data/lib/roby/state/open_struct_model.rb +68 -0
  394. data/lib/roby/state/pos.rb +45 -45
  395. data/lib/roby/state/shapes.rb +11 -11
  396. data/lib/roby/state/state_model.rb +303 -0
  397. data/lib/roby/state/task.rb +43 -0
  398. data/lib/roby/support.rb +88 -148
  399. data/lib/roby/task.rb +1361 -1750
  400. data/lib/roby/task_arguments.rb +428 -0
  401. data/lib/roby/task_event.rb +127 -0
  402. data/lib/roby/task_event_generator.rb +337 -0
  403. data/lib/roby/task_service.rb +6 -0
  404. data/lib/roby/task_structure/conflicts.rb +104 -0
  405. data/lib/roby/task_structure/dependency.rb +932 -0
  406. data/lib/roby/task_structure/error_handling.rb +118 -0
  407. data/lib/roby/task_structure/executed_by.rb +234 -0
  408. data/lib/roby/task_structure/planned_by.rb +90 -0
  409. data/lib/roby/tasks/aggregator.rb +37 -0
  410. data/lib/roby/tasks/external_process.rb +275 -0
  411. data/lib/roby/tasks/group.rb +27 -0
  412. data/lib/roby/tasks/null.rb +19 -0
  413. data/lib/roby/tasks/parallel.rb +43 -0
  414. data/lib/roby/tasks/sequence.rb +88 -0
  415. data/lib/roby/tasks/simple.rb +21 -0
  416. data/lib/roby/{thread_task.rb → tasks/thread.rb} +50 -24
  417. data/lib/roby/tasks/timeout.rb +17 -0
  418. data/lib/roby/tasks/virtual.rb +55 -0
  419. data/lib/roby/template_plan.rb +7 -0
  420. data/lib/roby/test/aruba_minitest.rb +74 -0
  421. data/lib/roby/test/assertion.rb +16 -0
  422. data/lib/roby/test/assertions.rb +490 -0
  423. data/lib/roby/test/common.rb +368 -591
  424. data/lib/roby/test/dsl.rb +149 -0
  425. data/lib/roby/test/error.rb +18 -0
  426. data/lib/roby/test/event_reporter.rb +83 -0
  427. data/lib/roby/test/execution_expectations.rb +1134 -0
  428. data/lib/roby/test/expect_execution.rb +151 -0
  429. data/lib/roby/test/minitest_helpers.rb +166 -0
  430. data/lib/roby/test/roby_app_helpers.rb +200 -0
  431. data/lib/roby/test/run_planners.rb +155 -0
  432. data/lib/roby/test/self.rb +112 -0
  433. data/lib/roby/test/spec.rb +198 -0
  434. data/lib/roby/test/tasks/empty_task.rb +4 -4
  435. data/lib/roby/test/tasks/goto.rb +28 -27
  436. data/lib/roby/test/teardown_plans.rb +100 -0
  437. data/lib/roby/test/testcase.rb +239 -307
  438. data/lib/roby/test/tools.rb +159 -155
  439. data/lib/roby/test/validate_state_machine.rb +75 -0
  440. data/lib/roby/transaction.rb +1125 -0
  441. data/lib/roby/transaction/event_generator_proxy.rb +63 -0
  442. data/lib/roby/transaction/plan_object_proxy.rb +99 -0
  443. data/lib/roby/transaction/plan_service_proxy.rb +43 -0
  444. data/lib/roby/transaction/proxying.rb +120 -0
  445. data/lib/roby/transaction/task_event_generator_proxy.rb +19 -0
  446. data/lib/roby/transaction/task_proxy.rb +135 -0
  447. data/lib/roby/until_generator.rb +30 -0
  448. data/lib/roby/version.rb +5 -0
  449. data/lib/roby/yard.rb +169 -0
  450. data/lib/yard-roby.rb +1 -0
  451. data/manifest.xml +32 -6
  452. data/roby.gemspec +59 -0
  453. metadata +788 -587
  454. data/Manifest.txt +0 -321
  455. data/NOTES +0 -4
  456. data/README.txt +0 -166
  457. data/TODO.txt +0 -146
  458. data/app/README.txt +0 -24
  459. data/app/Rakefile +0 -8
  460. data/app/config/ROBOT.rb +0 -5
  461. data/app/config/init.rb +0 -33
  462. data/app/config/roby.yml +0 -3
  463. data/app/controllers/ROBOT.rb +0 -2
  464. data/app/planners/ROBOT/main.rb +0 -6
  465. data/app/planners/main.rb +0 -5
  466. data/app/scripts/distributed +0 -3
  467. data/app/scripts/generate/bookmarks +0 -3
  468. data/app/scripts/replay +0 -3
  469. data/app/scripts/results +0 -3
  470. data/app/scripts/run +0 -3
  471. data/app/scripts/server +0 -3
  472. data/app/scripts/shell +0 -3
  473. data/app/scripts/test +0 -3
  474. data/app/tasks/.gitattributes +0 -0
  475. data/app/tasks/ROBOT/.gitattributes +0 -0
  476. data/bin/roby-shell +0 -25
  477. data/doc/guide/src/basics/app.page +0 -139
  478. data/doc/guide/src/basics/index.page +0 -11
  479. data/doc/guide/src/basics/log_replay/goForward_1.png +0 -0
  480. data/doc/guide/src/basics/log_replay/goForward_2.png +0 -0
  481. data/doc/guide/src/basics/log_replay/goForward_3.png +0 -0
  482. data/doc/guide/src/basics/log_replay/goForward_5.png +0 -0
  483. data/doc/guide/src/basics/log_replay/plan_repair_4.png +0 -0
  484. data/doc/guide/src/basics/log_replay/roby_log_main_window.png +0 -0
  485. data/doc/guide/src/basics/relations_display.page +0 -203
  486. data/doc/guide/src/basics/shell.page +0 -102
  487. data/doc/guide/src/default.css +0 -319
  488. data/doc/guide/src/introduction/index.page +0 -29
  489. data/doc/guide/src/introduction/publications.page +0 -14
  490. data/doc/guide/src/relations/dependency.page +0 -89
  491. data/doc/guide/src/relations/index.page +0 -12
  492. data/ext/droby/dump.cc +0 -175
  493. data/ext/droby/extconf.rb +0 -3
  494. data/ext/graph/algorithm.cc +0 -746
  495. data/ext/graph/extconf.rb +0 -7
  496. data/ext/graph/graph.cc +0 -575
  497. data/ext/graph/graph.hh +0 -183
  498. data/ext/graph/iterator_sequence.hh +0 -102
  499. data/ext/graph/undirected_dfs.hh +0 -226
  500. data/ext/graph/undirected_graph.hh +0 -421
  501. data/lib/roby/app/scripts/generate/bookmarks.rb +0 -162
  502. data/lib/roby/app/scripts/replay.rb +0 -31
  503. data/lib/roby/app/scripts/server.rb +0 -18
  504. data/lib/roby/basic_object.rb +0 -151
  505. data/lib/roby/config.rb +0 -14
  506. data/lib/roby/distributed.rb +0 -36
  507. data/lib/roby/distributed/base.rb +0 -448
  508. data/lib/roby/distributed/communication.rb +0 -875
  509. data/lib/roby/distributed/connection_space.rb +0 -616
  510. data/lib/roby/distributed/distributed_object.rb +0 -206
  511. data/lib/roby/distributed/drb.rb +0 -62
  512. data/lib/roby/distributed/notifications.rb +0 -531
  513. data/lib/roby/distributed/peer.rb +0 -555
  514. data/lib/roby/distributed/protocol.rb +0 -529
  515. data/lib/roby/distributed/proxy.rb +0 -343
  516. data/lib/roby/distributed/subscription.rb +0 -311
  517. data/lib/roby/distributed/transaction.rb +0 -498
  518. data/lib/roby/external_process_task.rb +0 -225
  519. data/lib/roby/graph.rb +0 -160
  520. data/lib/roby/log.rb +0 -3
  521. data/lib/roby/log/chronicle.rb +0 -303
  522. data/lib/roby/log/console.rb +0 -74
  523. data/lib/roby/log/data_stream.rb +0 -275
  524. data/lib/roby/log/dot.rb +0 -279
  525. data/lib/roby/log/event_stream.rb +0 -161
  526. data/lib/roby/log/file.rb +0 -396
  527. data/lib/roby/log/gui/basic_display.ui +0 -83
  528. data/lib/roby/log/gui/basic_display_ui.rb +0 -89
  529. data/lib/roby/log/gui/chronicle.rb +0 -26
  530. data/lib/roby/log/gui/chronicle_view.rb +0 -40
  531. data/lib/roby/log/gui/chronicle_view.ui +0 -70
  532. data/lib/roby/log/gui/chronicle_view_ui.rb +0 -90
  533. data/lib/roby/log/gui/data_displays.rb +0 -171
  534. data/lib/roby/log/gui/data_displays.ui +0 -155
  535. data/lib/roby/log/gui/data_displays_ui.rb +0 -146
  536. data/lib/roby/log/gui/notifications.rb +0 -26
  537. data/lib/roby/log/gui/relations.rb +0 -269
  538. data/lib/roby/log/gui/relations.ui +0 -123
  539. data/lib/roby/log/gui/relations_ui.rb +0 -120
  540. data/lib/roby/log/gui/relations_view.rb +0 -185
  541. data/lib/roby/log/gui/relations_view.ui +0 -149
  542. data/lib/roby/log/gui/relations_view_ui.rb +0 -144
  543. data/lib/roby/log/gui/replay.rb +0 -366
  544. data/lib/roby/log/gui/replay_controls.rb +0 -206
  545. data/lib/roby/log/gui/replay_controls.ui +0 -282
  546. data/lib/roby/log/gui/replay_controls_ui.rb +0 -249
  547. data/lib/roby/log/gui/runtime.rb +0 -130
  548. data/lib/roby/log/hooks.rb +0 -186
  549. data/lib/roby/log/logger.rb +0 -203
  550. data/lib/roby/log/notifications.rb +0 -244
  551. data/lib/roby/log/plan_rebuilder.rb +0 -468
  552. data/lib/roby/log/relations.rb +0 -1084
  553. data/lib/roby/log/server.rb +0 -547
  554. data/lib/roby/log/sqlite.rb +0 -47
  555. data/lib/roby/log/timings.rb +0 -233
  556. data/lib/roby/plan-object.rb +0 -371
  557. data/lib/roby/planning.rb +0 -13
  558. data/lib/roby/planning/loops.rb +0 -309
  559. data/lib/roby/planning/model.rb +0 -1012
  560. data/lib/roby/planning/task.rb +0 -180
  561. data/lib/roby/query.rb +0 -655
  562. data/lib/roby/relations/conflicts.rb +0 -67
  563. data/lib/roby/relations/dependency.rb +0 -358
  564. data/lib/roby/relations/ensured.rb +0 -19
  565. data/lib/roby/relations/error_handling.rb +0 -22
  566. data/lib/roby/relations/events.rb +0 -7
  567. data/lib/roby/relations/executed_by.rb +0 -208
  568. data/lib/roby/relations/influence.rb +0 -10
  569. data/lib/roby/relations/planned_by.rb +0 -63
  570. data/lib/roby/state/information.rb +0 -55
  571. data/lib/roby/state/state.rb +0 -367
  572. data/lib/roby/task-operations.rb +0 -186
  573. data/lib/roby/task_index.rb +0 -80
  574. data/lib/roby/test/distributed.rb +0 -230
  575. data/lib/roby/test/tasks/simple_task.rb +0 -23
  576. data/lib/roby/transactions.rb +0 -507
  577. data/lib/roby/transactions/proxy.rb +0 -325
  578. data/plugins/fault_injection/History.txt +0 -4
  579. data/plugins/fault_injection/README.txt +0 -34
  580. data/plugins/fault_injection/Rakefile +0 -12
  581. data/plugins/fault_injection/TODO.txt +0 -0
  582. data/plugins/fault_injection/app.rb +0 -52
  583. data/plugins/fault_injection/fault_injection.rb +0 -89
  584. data/plugins/fault_injection/test/test_fault_injection.rb +0 -78
  585. data/plugins/subsystems/README.txt +0 -37
  586. data/plugins/subsystems/Rakefile +0 -13
  587. data/plugins/subsystems/app.rb +0 -182
  588. data/plugins/subsystems/test/app/README +0 -24
  589. data/plugins/subsystems/test/app/Rakefile +0 -8
  590. data/plugins/subsystems/test/app/config/app.yml +0 -71
  591. data/plugins/subsystems/test/app/config/init.rb +0 -12
  592. data/plugins/subsystems/test/app/config/roby.yml +0 -3
  593. data/plugins/subsystems/test/app/planners/main.rb +0 -20
  594. data/plugins/subsystems/test/app/scripts/distributed +0 -3
  595. data/plugins/subsystems/test/app/scripts/replay +0 -3
  596. data/plugins/subsystems/test/app/scripts/results +0 -3
  597. data/plugins/subsystems/test/app/scripts/run +0 -3
  598. data/plugins/subsystems/test/app/scripts/server +0 -3
  599. data/plugins/subsystems/test/app/scripts/shell +0 -3
  600. data/plugins/subsystems/test/app/scripts/test +0 -3
  601. data/plugins/subsystems/test/app/tasks/services.rb +0 -15
  602. data/plugins/subsystems/test/test_subsystems.rb +0 -78
  603. data/test/distributed/test_communication.rb +0 -195
  604. data/test/distributed/test_connection.rb +0 -284
  605. data/test/distributed/test_execution.rb +0 -378
  606. data/test/distributed/test_mixed_plan.rb +0 -341
  607. data/test/distributed/test_plan_notifications.rb +0 -238
  608. data/test/distributed/test_protocol.rb +0 -525
  609. data/test/distributed/test_query.rb +0 -106
  610. data/test/distributed/test_remote_plan.rb +0 -491
  611. data/test/distributed/test_transaction.rb +0 -466
  612. data/test/mockups/external_process +0 -28
  613. data/test/mockups/tasks.rb +0 -27
  614. data/test/planning/test_loops.rb +0 -432
  615. data/test/planning/test_model.rb +0 -427
  616. data/test/planning/test_task.rb +0 -126
  617. data/test/relations/test_conflicts.rb +0 -42
  618. data/test/relations/test_dependency.rb +0 -324
  619. data/test/relations/test_ensured.rb +0 -38
  620. data/test/relations/test_executed_by.rb +0 -224
  621. data/test/relations/test_planned_by.rb +0 -56
  622. data/test/suite_core.rb +0 -29
  623. data/test/suite_distributed.rb +0 -10
  624. data/test/suite_planning.rb +0 -4
  625. data/test/suite_relations.rb +0 -8
  626. data/test/tasks/test_external_process.rb +0 -126
  627. data/test/tasks/test_thread_task.rb +0 -70
  628. data/test/test_bgl.rb +0 -528
  629. data/test/test_event.rb +0 -969
  630. data/test/test_exceptions.rb +0 -591
  631. data/test/test_execution_engine.rb +0 -987
  632. data/test/test_gui.rb +0 -20
  633. data/test/test_interface.rb +0 -43
  634. data/test/test_log.rb +0 -125
  635. data/test/test_log_server.rb +0 -133
  636. data/test/test_plan.rb +0 -418
  637. data/test/test_query.rb +0 -424
  638. data/test/test_relations.rb +0 -260
  639. data/test/test_state.rb +0 -432
  640. data/test/test_support.rb +0 -16
  641. data/test/test_task.rb +0 -1181
  642. data/test/test_testcase.rb +0 -138
  643. data/test/test_transactions.rb +0 -610
  644. data/test/test_transactions_proxy.rb +0 -216
@@ -0,0 +1,337 @@
1
+ module Roby
2
+ # Specialization of EventGenerator to represent task events
3
+ #
4
+ # It gives access to the task-specific information (associated task, event
5
+ # name, ...)
6
+ class TaskEventGenerator < EventGenerator
7
+ # The task we are part of
8
+ attr_reader :task
9
+ # The event symbol (its name as a Symbol object)
10
+ attr_reader :symbol
11
+ # Changes the underlying model
12
+ def override_model(model)
13
+ @event_model = model
14
+ end
15
+
16
+ def initialize(task, model)
17
+ super(model.respond_to?(:call), plan: task.plan)
18
+ @task, @event_model = task, model
19
+ @symbol = model.symbol
20
+ end
21
+
22
+ # The default command if the event is created with controlable: true.
23
+ # It emits the event on the task.
24
+ def default_command(context)
25
+ event_model.call(task, context)
26
+ end
27
+
28
+ def command=(block)
29
+ event_model.singleton_class.class_eval do
30
+ define_method(:call, &block)
31
+ end
32
+ end
33
+
34
+ # See PlanObject::child_plan_object.
35
+ child_plan_object :task
36
+
37
+ # The event plan. It is the same as task.plan and is actually updated
38
+ # by task.plan=. It is redefined here for performance reasons.
39
+ attr_reader :plan
40
+
41
+ # The event plan. It is the same as task.plan and is actually updated
42
+ # by task.plan=. It is redefined here for performance reasons.
43
+ def plan=(plan)
44
+ @plan = plan
45
+ @relation_graphs =
46
+ if plan then plan.event_relation_graphs
47
+ end
48
+ @execution_engine =
49
+ if plan && plan.executable? then plan.execution_engine
50
+ end
51
+ end
52
+
53
+ def pending(sources)
54
+ super
55
+ if symbol == :start
56
+ task.freeze_delayed_arguments
57
+ plan.task_index.set_state(task, :starting?) if plan && !plan.template?
58
+ task.pending = false
59
+ task.starting = true
60
+ end
61
+ end
62
+
63
+ def clear_pending
64
+ if @pending && symbol == :start
65
+ if !emitted? && !task.failed_to_start?
66
+ plan.task_index.set_state(task, :pending?) if plan && !plan.template?
67
+ task.pending = true
68
+ task.starting = false
69
+ end
70
+ end
71
+ super
72
+ end
73
+
74
+ def called(context)
75
+ super
76
+ if terminal? && pending?
77
+ task.finishing = true
78
+ end
79
+ end
80
+
81
+ def fire(event)
82
+ super
83
+ if symbol == :start
84
+ task.do_poll(plan)
85
+ elsif symbol == :stop
86
+ task.each_event do |ev|
87
+ ev.unreachable!(task.terminal_event)
88
+ end
89
+ end
90
+ end
91
+
92
+ # Actually emits the event. This should not be used directly.
93
+ #
94
+ # It forwards the call to Task#fire
95
+ def fired(event) # :nodoc:
96
+ super
97
+ task.fired_event(event)
98
+ end
99
+
100
+ # See EventGenerator#related_tasks
101
+ def related_tasks(result = nil) # :nodoc:
102
+ tasks = super
103
+ tasks.delete(task)
104
+ tasks
105
+ end
106
+
107
+ # See EventGenerator#each_handler
108
+ def each_handler # :nodoc:
109
+ if self_owned?
110
+ task.model.each_handler(event_model.symbol) { |o| yield(o) }
111
+ end
112
+
113
+ super
114
+ end
115
+
116
+ # See EventGenerator#each_precondition
117
+ def each_precondition # :nodoc:
118
+ task.model.each_precondition(event_model.symbol) { |o| yield(o) }
119
+ super
120
+ end
121
+
122
+ # See EventGenerator#controlable?
123
+ def controlable? # :nodoc:
124
+ event_model.controlable?
125
+ end
126
+
127
+ # Cached value for #terminal?
128
+ attr_writer :terminal_flag # :nodoc:
129
+
130
+ # Returns the value for #terminal_flag, updating it if needed
131
+ def terminal_flag # :nodoc:
132
+ if task.invalidated_terminal_flag?
133
+ task.update_terminal_flag
134
+ end
135
+ return @terminal_flag
136
+ end
137
+
138
+ # True if this event is either forwarded to or signals the task's :stop event
139
+ def terminal?; !!terminal_flag end
140
+ # True if this event is either forwarded to or signals the task's :success event
141
+ def success?; terminal_flag == :success end
142
+ # True if this event is either forwarded to or signals the task's :failed event
143
+ def failure?; terminal_flag == :failure end
144
+
145
+ # @api private
146
+ #
147
+ # Helper for the signal and forward relation hooks that invalidates the
148
+ # events terminal flags when the event structure is changed
149
+ def invalidate_task_terminal_flag_if_needed(child)
150
+ if child.respond_to?(:task) && child.task == task
151
+ task.invalidate_terminal_flag
152
+ end
153
+ end
154
+
155
+ # Invalidates the task's terminal flag when the Forwarding and/or the
156
+ # Signal relation gets modified.
157
+ def added_signal(child, info) # :nodoc:
158
+ super
159
+ invalidate_task_terminal_flag_if_needed(child)
160
+ end
161
+
162
+ # Invalidates the task's terminal flag when the Forwarding and/or the
163
+ # Signal relation gets modified.
164
+ def added_forwarding(child, info) # :nodoc:
165
+ super
166
+ invalidate_task_terminal_flag_if_needed(child)
167
+ end
168
+
169
+ # Invalidates the task's terminal flag when the Forwarding and/or the
170
+ # Signal relation gets modified.
171
+ def removed_signal(child)
172
+ super
173
+ invalidate_task_terminal_flag_if_needed(child)
174
+ end
175
+
176
+ def removed_forwarding(child)
177
+ super
178
+ invalidate_task_terminal_flag_if_needed(child)
179
+ end
180
+
181
+ # See EventGenerator#new
182
+ def new(context, propagation_id = nil, time = nil) # :nodoc:
183
+ event_model.new(task, self, propagation_id || execution_engine.propagation_id, context, time || Time.now)
184
+ end
185
+
186
+ def to_s # :nodoc:
187
+ "#{task}/#{symbol}"
188
+ end
189
+ def inspect # :nodoc:
190
+ "#{task.inspect}/#{symbol}: #{history.to_s}"
191
+ end
192
+ def pretty_print(pp) # :nodoc:
193
+ pp.text "#{symbol} event of #{task.class}:0x#{task.address.to_s(16)}"
194
+ end
195
+
196
+ # See EventGenerator#achieve_with
197
+ def achieve_with(obj) # :nodoc:
198
+ child_task, child_event = case obj
199
+ when Roby::Task then [obj, obj.event(:success)]
200
+ when Roby::TaskEventGenerator then [obj.task, obj]
201
+ end
202
+
203
+ if child_task
204
+ unless task.depends_on?(child_task)
205
+ task.depends_on child_task,
206
+ success: [child_event.symbol],
207
+ remove_when_done: true
208
+ end
209
+ super(child_event)
210
+ else
211
+ super(obj)
212
+ end
213
+ end
214
+
215
+ def emit_failed(error = nil, message = nil)
216
+ exception = super
217
+ if symbol == :start
218
+ if !task.failed_to_start?
219
+ task.failed_to_start!(exception)
220
+ end
221
+ end
222
+ end
223
+
224
+ # Checks that the event can be called. Raises various exception
225
+ # when it is not the case.
226
+ def check_call_validity # :nodoc:
227
+ if error = super
228
+ if !error.kind_of?(UnreachableEvent)
229
+ return refine_call_exception(error)
230
+ end
231
+ end
232
+
233
+ if task.failed_to_start?
234
+ CommandRejected.new(self).
235
+ exception("#{symbol}! called by #{execution_engine.propagation_sources.to_a} but the task has failed to start: #{task.failure_reason}")
236
+ elsif task.event(:stop).emitted?
237
+ CommandRejected.new(self).
238
+ exception("#{symbol}! called by #{execution_engine.propagation_sources.to_a} but the task has finished. Task has been terminated by #{task.event(:stop).history.first.sources.to_a}.")
239
+ elsif task.finished? && !terminal?
240
+ CommandRejected.new(self).
241
+ exception("#{symbol}! called by #{execution_engine.propagation_sources.to_a} but the task has finished. Task has been terminated by #{task.event(:stop).history.first.sources.to_a}.")
242
+ elsif task.pending? && symbol != :start
243
+ CommandRejected.new(self).
244
+ exception("#{symbol}! called by #{execution_engine.propagation_sources.to_a} but the task has never been started")
245
+ elsif task.running? && symbol == :start
246
+ CommandRejected.new(self).
247
+ exception("#{symbol}! called by #{execution_engine.propagation_sources.to_a} but the task is already running. Task has been started by #{task.event(:start).history.first.sources.to_a}.")
248
+ else error
249
+ end
250
+ end
251
+
252
+ def check_call_validity_after_calling
253
+ if error = super
254
+ refine_call_exception(error)
255
+ end
256
+ end
257
+
258
+ # Checks that the event can be emitted. Raises various exception
259
+ # when it is not the case.
260
+ def check_emission_validity # :nodoc:
261
+ if error = super
262
+ refine_emit_exception(error)
263
+ else
264
+ task.check_emission_validity(self)
265
+ end
266
+ end
267
+
268
+ # When an emissio and/or call exception is raised by the base
269
+ # EventGenerator methods, this method is used to transform it to the
270
+ # relevant task-related error.
271
+ def refine_call_exception (e) # :nodoc:
272
+ if task.partially_instanciated?
273
+ TaskEventNotExecutable.new(self).
274
+ exception("#{symbol}_event.call on #{task} which is partially instanciated\n" +
275
+ "The following arguments were not set:\n" +
276
+ task.list_unset_arguments.map {|n| " #{n}"}.join("\n"))
277
+ elsif !plan
278
+ TaskEventNotExecutable.new(self).
279
+ exception("#{symbol}_event.call on #{task} but the task has been removed from its plan")
280
+ elsif !plan.executable?
281
+ TaskEventNotExecutable.new(self).
282
+ exception("#{symbol}_event.call on #{task} but its plan is not executable")
283
+ elsif task.abstract?
284
+ TaskEventNotExecutable.new(self).
285
+ exception("#{symbol}_event.call on #{task} but the task is abstract")
286
+ elsif e.kind_of?(EventNotExecutable)
287
+ TaskEventNotExecutable.new(self).
288
+ exception("#{symbol}_event.call on #{task} which is not executable")
289
+ else e
290
+ end
291
+ end
292
+
293
+ # When an emissio and/or call exception is raised by the base
294
+ # EventGenerator methods, this method is used to transform it to the
295
+ # relevant task-related error.
296
+ def refine_emit_exception (e) # :nodoc:
297
+ if task.partially_instanciated?
298
+ TaskEventNotExecutable.new(self).
299
+ exception("#{symbol}_event.emit on #{task} which is partially instanciated\n" +
300
+ "The following arguments were not set:\n" +
301
+ task.list_unset_arguments.map {|n| " #{n}"}.join("\n"))
302
+ elsif !plan
303
+ TaskEventNotExecutable.new(self).
304
+ exception("#{symbol}_event.emit on #{task} but the task has been removed from its plan")
305
+ elsif !plan.executable?
306
+ TaskEventNotExecutable.new(self).
307
+ exception("#{symbol}_event.emit on #{task} but its plan is not executable")
308
+ elsif task.abstract?
309
+ TaskEventNotExecutable.new(self).
310
+ exception("#{symbol}_event.emit on #{task} but the task is abstract")
311
+ elsif e.kind_of?(EventNotExecutable)
312
+ TaskEventNotExecutable.new(self).
313
+ exception("#{symbol}_event.emit on #{task} which is not executable")
314
+ else e
315
+ end
316
+ end
317
+
318
+ def default_on_replace
319
+ if task.abstract? then :copy
320
+ else :drop
321
+ end
322
+ end
323
+
324
+ def on(on_replace: default_on_replace, once: false, &block)
325
+ super(on_replace: on_replace, once: once, &block)
326
+ end
327
+
328
+ def create_transaction_proxy(transaction)
329
+ # Ensure that the task is proxied already
330
+ transaction.wrap_task(task).event(symbol)
331
+ end
332
+
333
+ def match
334
+ Queries::TaskEventGeneratorMatcher.new(task, symbol)
335
+ end
336
+ end
337
+ end
@@ -0,0 +1,6 @@
1
+ module Roby
2
+ TaskService = Models::TaskServiceModel.new
3
+ TaskService.root = true
4
+ end
5
+
6
+
@@ -0,0 +1,104 @@
1
+ module Roby
2
+ module TaskStructure
3
+ relation :Conflicts, noinfo: true
4
+
5
+ class Conflicts
6
+ module Extension
7
+ def conflicts_with(task)
8
+ # task.event(:stop).add_precedence event(:start)
9
+ add_conflicts(task)
10
+ end
11
+ end
12
+
13
+ module ModelExtension
14
+ extend MetaRuby::Attributes
15
+ inherited_attribute(:conflicting_model, :conflicting_models) { Set.new }
16
+
17
+ def conflicts_with(model)
18
+ conflicting_models << model
19
+ model.conflicting_models << self
20
+ end
21
+
22
+ def conflicts_with?(model)
23
+ each_conflicting_model do |m|
24
+ return true if model <= m
25
+ end
26
+ false
27
+ end
28
+ end
29
+
30
+ module EventGeneratorExtension
31
+ def calling(context)
32
+ super
33
+ return unless symbol == :start
34
+
35
+ # Check for conflicting tasks
36
+ result = Set.new
37
+ task.each_conflicts do |conflicting_task|
38
+ result << conflicting_task
39
+ end
40
+
41
+ models = task.class.conflicting_models
42
+ for model in models
43
+ for t in plan.find_tasks(model)
44
+ if t.running? && t != task
45
+ result << t
46
+ end
47
+ end
48
+ end
49
+
50
+ if !result.empty?
51
+ plan.control.conflict(task, result)
52
+ return
53
+ end
54
+
55
+ # Add the needed conflict relations
56
+ models = task.class.conflicting_models
57
+ for model in models
58
+ for t in plan.find_tasks(model)
59
+ t.conflicts_with task if t.pending? && t != task
60
+ end
61
+ end
62
+ end
63
+
64
+ def fired(event)
65
+ super
66
+
67
+ if symbol == :stop
68
+ task.relation_graph_for(Conflicts).remove_vertex(task)
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+ # Class holding conflict error information
75
+ #
76
+ # Note that it is not an exception as a failed conflict is usually
77
+ # handled by calling #failed_to_start! on the newly started task
78
+ class ConflictError
79
+ attr_reader :starting_task
80
+ attr_reader :running_tasks
81
+
82
+ def initialize(starting_task, running_tasks)
83
+ @starting_task, @running_tasks = starting_task, running_tasks
84
+ end
85
+
86
+ def pretty_print(pp)
87
+ pp.text "failed to start "
88
+ starting_task.pretty_print(pp)
89
+ pp.text "because it conflicts with #{running_tasks.size} running tasks"
90
+ pp.nest(2) do
91
+ runnning_tasks.each do |t|
92
+ pp.breakable
93
+ t.pretty_print(pp)
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+
100
+ Roby::TaskEventGenerator.class_eval do
101
+ prepend TaskStructure::Conflicts::EventGeneratorExtension
102
+ end
103
+ end
104
+
@@ -0,0 +1,932 @@
1
+ module Roby
2
+ module TaskStructure
3
+ DEPENDENCY_RELATION_ARGUMENTS =
4
+ [:model, :success, :failure, :remove_when_done, :consider_in_pending, :roles, :role]
5
+
6
+ relation :Dependency, child_name: :child, parent_name: :parent_task
7
+
8
+ class Dependency < Relations::TaskRelationGraph
9
+ attr_reader :interesting_events
10
+ attr_reader :failing_tasks
11
+
12
+ def initialize(observer: nil)
13
+ super(observer: observer)
14
+ @interesting_events = Array.new
15
+ @failing_tasks = Set.new
16
+ end
17
+
18
+ # @api private
19
+ #
20
+ # Updates the dependency internal data to trigger errors / success when
21
+ # relevant events are emitted
22
+ def update_triggers_for(parent, child, info)
23
+ events = Set.new
24
+ if info[:success]
25
+ for event_name in info[:success].required_events
26
+ events << child.event(event_name)
27
+ end
28
+ end
29
+
30
+ if info[:failure]
31
+ for event_name in info[:failure].required_events
32
+ events << child.event(event_name)
33
+ end
34
+ end
35
+
36
+ if !events.empty?
37
+ parent.start_event.on(on_replace: :drop) do |ev|
38
+ ev.plan.task_relation_graph_for(self.class).interesting_events << ev.generator
39
+ end
40
+ events.each do |e|
41
+ e.if_unreachable do |reason, ev|
42
+ # The actualy graph of 'ev' might be different than self
43
+ # ... re-resolve
44
+ ev.plan.task_relation_graph_for(self.class).interesting_events << ev
45
+ end
46
+ e.on(on_replace: :drop) do |ev|
47
+ ev.plan.task_relation_graph_for(self.class).interesting_events << ev.generator
48
+ end
49
+ end
50
+ end
51
+
52
+ # Initial triggers
53
+ failing_tasks << child
54
+ end
55
+
56
+ def self.merge_fullfilled_model(model, required_models, required_arguments)
57
+ model, tags, arguments = *model
58
+
59
+ tags = tags.dup
60
+ required_models = Array(required_models)
61
+
62
+ for m in required_models
63
+ if m.kind_of?(Roby::Models::TaskServiceModel)
64
+ tags << m
65
+ elsif m.has_ancestor?(model)
66
+ model = m
67
+ elsif !model.has_ancestor?(m)
68
+ raise Roby::ModelViolation, "inconsistency in fullfilled models: #{model} and #{m} are incompatible"
69
+ end
70
+ end
71
+
72
+ arguments = arguments.merge(required_arguments) do |name, old, new|
73
+ if old != new
74
+ raise Roby::ModelViolation, "inconsistency in fullfilled models: #{old} and #{new}"
75
+ end
76
+ old
77
+ end
78
+
79
+ return [model, tags, arguments]
80
+ end
81
+
82
+ def self.validate_options(options, defaults = Hash.new)
83
+ defaults = Hash[model: [[Roby::Task], Hash.new],
84
+ success: nil,
85
+ failure: nil,
86
+ remove_when_done: true,
87
+ consider_in_pending: true,
88
+ roles: Set.new,
89
+ role: nil].merge(defaults)
90
+ Kernel.validate_options options, defaults
91
+ end
92
+
93
+ # Merges the dependency descriptions (i.e. the relation payload),
94
+ # verifying that the two provided option hashes are compatible
95
+ #
96
+ # @return [Hash] the merged options
97
+ # @raise [ModelViolation] if the two hashes are not compatible
98
+ def self.merge_dependency_options(opt1, opt2)
99
+ if opt1[:remove_when_done] != opt2[:remove_when_done]
100
+ raise Roby::ModelViolation, "incompatible dependency specification: trying to change the value of +remove_when_done+"
101
+ end
102
+
103
+ result = { remove_when_done: opt1[:remove_when_done], consider_in_pending: opt1[:consider_in_pending] }
104
+
105
+ if opt1[:success] || opt2[:success]
106
+ result[:success] =
107
+ if !opt1[:success] then opt2[:success]
108
+ elsif !opt2[:success] then opt1[:success]
109
+ else
110
+ opt1[:success].and(opt2[:success])
111
+ end
112
+ end
113
+
114
+ if opt1[:failure] || opt2[:failure]
115
+ result[:failure] =
116
+ if !opt1[:failure] then opt2[:failure]
117
+ elsif !opt2[:failure] then opt1[:failure]
118
+ else
119
+ opt1[:failure].or(opt2[:failure])
120
+ end
121
+ end
122
+
123
+ # Check model compatibility
124
+ models1, arguments1 = opt1[:model]
125
+ models2, arguments2 = opt2[:model]
126
+
127
+ task_model1 = models1.find { |m| m <= Roby::Task }
128
+ task_model2 = models2.find { |m| m <= Roby::Task }
129
+ result_model = []
130
+ if task_model1 && task_model2
131
+ if task_model1.fullfills?(task_model2)
132
+ result_model << task_model1
133
+ elsif task_model2.fullfills?(task_model1)
134
+ result_model << task_model2
135
+ else
136
+ raise Roby::ModelViolation, "incompatible models #{task_model1} and #{task_model2}"
137
+ end
138
+ elsif task_model1
139
+ result_model << task_model1
140
+ elsif task_model2
141
+ result_model << task_model2
142
+ end
143
+ models1.each do |m|
144
+ next if m <= Roby::Task
145
+ if !models2.any? { |other_m| other_m.fullfills?(m) }
146
+ result_model << m
147
+ end
148
+ end
149
+ models2.each do |m|
150
+ next if m <= Roby::Task
151
+ if !models1.any? { |other_m| other_m.fullfills?(m) }
152
+ result_model << m
153
+ end
154
+ end
155
+
156
+ result[:model] = [result_model]
157
+ # Merge arguments
158
+ result[:model][1] = arguments1.merge(arguments2) do |key, old_value, new_value|
159
+ if old_value != new_value
160
+ raise Roby::ModelViolation, "incompatible argument constraint #{old_value} and #{new_value} for #{key}"
161
+ end
162
+ old_value
163
+ end
164
+
165
+ # Finally, merge the roles (the easy part ;-))
166
+ result[:roles] = opt1[:roles] | opt2[:roles]
167
+
168
+ result
169
+ end
170
+
171
+ # Called by the relation management when two dependency relations need
172
+ # to be merged
173
+ #
174
+ # @see Dependency.merge_dependency_options
175
+ def merge_info(parent, child, opt1, opt2)
176
+ result = Dependency.merge_dependency_options(opt1, opt2)
177
+ update_triggers_for(parent, child, result)
178
+ result
179
+ rescue Exception => e
180
+ raise e, e.message + " while updating the dependency information for #{parent} -> #{child}", e.backtrace
181
+ end
182
+
183
+ # Checks the structure of +plan+ w.r.t. the constraints of the hierarchy
184
+ # relations. It returns an array of ChildFailedError for all failed
185
+ # hierarchy relations
186
+ def check_structure(plan)
187
+ # The Set in #interesting_events is also referenced
188
+ # *separately* in EventStructure.gather_events. We therefore have to
189
+ # keep it (and can't use #partition). Yuk
190
+ events = Array.new
191
+ interesting_events.delete_if do |ev|
192
+ if ev.plan == plan
193
+ events << ev
194
+ true
195
+ else !ev.plan
196
+ end
197
+ end
198
+ tasks = Set.new
199
+ failing_tasks.delete_if do |task|
200
+ if task.plan == plan
201
+ tasks << task
202
+ true
203
+ else !task.plan
204
+ end
205
+ end
206
+ return Array.new if events.empty? && tasks.empty?
207
+
208
+ result = []
209
+
210
+ # Get the set of tasks for which a possible failure has been
211
+ # registered The tasks that are failing the hierarchy requirements
212
+ # are registered in Hierarchy.failing_tasks.
213
+ events.each do |event|
214
+ task = event.task
215
+ tasks << task
216
+
217
+ if event.symbol == :start # also add the children
218
+ task.each_child do |child_task, _|
219
+ tasks << child_task
220
+ end
221
+ end
222
+ end
223
+
224
+ for child in tasks
225
+ # Check if the task has been removed from the plan
226
+ next unless child.plan
227
+
228
+ removed_parents = []
229
+ child.each_parent_task do |parent|
230
+ next if parent.finished?
231
+ next unless parent.self_owned?
232
+
233
+ options = parent[child, Dependency]
234
+ success = options[:success]
235
+ failure = options[:failure]
236
+
237
+ has_success = success && success.evaluate(child)
238
+ if !has_success
239
+ has_failure = failure && failure.evaluate(child)
240
+ end
241
+
242
+ error = nil
243
+ if has_success
244
+ if options[:remove_when_done]
245
+ # Must not delete it here as we are iterating over the
246
+ # parents
247
+ removed_parents << parent
248
+ end
249
+ elsif has_failure
250
+ explanation = failure.explain_true(child)
251
+ error = Roby::ChildFailedError.new(parent, child, explanation, :failed_event)
252
+ elsif success && success.static?(child)
253
+ explanation = success.explain_static(child)
254
+ error = Roby::ChildFailedError.new(parent, child, explanation, :unreachable_success)
255
+ end
256
+
257
+ if error
258
+ if parent.running?
259
+ result << error
260
+ failing_tasks << child
261
+ elsif options[:consider_in_pending] && plan.control.pending_dependency_failed(parent, child, error)
262
+ result << error
263
+ failing_tasks << child
264
+ end
265
+ end
266
+ end
267
+ for parent in removed_parents
268
+ parent.remove_child child
269
+ end
270
+ end
271
+
272
+ result
273
+ end
274
+
275
+ module Extension
276
+ # True if +obj+ is a parent of this object in the hierarchy relation
277
+ # (+obj+ is realized by +self+)
278
+ def depended_upon_by?(obj); parent_object?(obj, Dependency) end
279
+
280
+ # True if +obj+ is a child of this object in the hierarchy relation.
281
+ # If +recursive+ is true, take into account the whole subgraph.
282
+ # Otherwise, only direct children are checked.
283
+ def depends_on?(obj, recursive: false)
284
+ if recursive
285
+ relation_graph_for(Dependency).
286
+ depth_first_visit(obj) { |v| return true if v == obj }
287
+ return false
288
+ else
289
+ child_object?(obj, Dependency)
290
+ end
291
+ end
292
+ # The set of parent objects in the Dependency relation
293
+ def parents; parent_objects(Dependency) end
294
+ # The set of child objects in the Dependency relation
295
+ def children; child_objects(Dependency) end
296
+ # Returns the single parent task for this task
297
+ #
298
+ # If there is more than one parent or no parent at all, raise an exception
299
+ def parent_task
300
+ parents = each_parent_task.to_a
301
+ if parents.size > 1
302
+ raise ArgumentError, "#{self} has #{parents.size} parents (#{parents.map(&:to_s).join(", ")}. A single parent was expected"
303
+ elsif parents.empty?
304
+ raise ArgumentError, "#{self} has no parents. A single parent was expected"
305
+ end
306
+ parents.first
307
+ end
308
+
309
+ # Returns the set of roles that +child+ has
310
+ def roles_of(child)
311
+ info = self[child, Dependency]
312
+ info[:roles]
313
+ end
314
+
315
+ # Enumerates all the roles this task has
316
+ def each_role(&block)
317
+ if !block_given?
318
+ return enum_for(:each_role, &block)
319
+ end
320
+ each_parent_object(Dependency) do |parent|
321
+ yield(parent, parent.roles_of(self))
322
+ end
323
+ end
324
+
325
+ def roles
326
+ each_role.map { |_, roles| roles.to_a }.flatten.to_set
327
+ end
328
+
329
+ def has_role?(role_name)
330
+ !!find_child_from_role(role_name)
331
+ end
332
+
333
+ # Remove a given role this task's child
334
+ #
335
+ # @param [Task] child the child task
336
+ # @param [Array<String>] roles the roles that should be removed
337
+ # @param [Boolean] remove_child_when_empty if true (the default), the
338
+ # child will be removed from this task's children if the set of roles
339
+ # is empty
340
+ # @raise [ArgumentError] if the child does not have the expected role
341
+ # @return [Boolean] true if the child is still a child of this task
342
+ # after the call, and false otherwise
343
+ def remove_roles(child, *roles, remove_child_when_empty: true)
344
+ dependency_info = self[child, Dependency].dup
345
+ child_roles = dependency_info[:roles].dup
346
+ roles.each do |r|
347
+ if !child_roles.include?(r)
348
+ raise ArgumentError, "#{r} is not a role of #{child} with respect to #{self}"
349
+ end
350
+ child_roles.delete(r)
351
+ end
352
+
353
+ if child_roles.empty? && remove_child_when_empty
354
+ remove_child(child)
355
+ false
356
+ else
357
+ dependency_info[:roles] = child_roles
358
+ self[child, Dependency] = dependency_info
359
+ true
360
+ end
361
+ end
362
+
363
+ # Returns the child whose role is +role_name+
364
+ #
365
+ # @return [nil,Task] the task if a dependency with the given role is
366
+ # found, and nil otherwise
367
+ def find_child_from_role(role_name)
368
+ each_out_neighbour_merged(Dependency, intrusive: false) do |myself, child|
369
+ roles = myself[child, Dependency][:roles]
370
+ if roles.include?(role_name)
371
+ if plan
372
+ return plan[child]
373
+ else
374
+ return child
375
+ end
376
+ end
377
+ end
378
+ nil
379
+ end
380
+
381
+ # Returns the child whose role is +role_name+
382
+ #
383
+ # If +validate+ is true (the default), raises ArgumentError if there is
384
+ # none. Otherwise, returns nil. This argument is meant only to avoid the
385
+ # costly operation of raising an exception in cases it is expected that
386
+ # the role may not exist.
387
+ def child_from_role(role_name, validate = true)
388
+ if !validate
389
+ Roby.warn_deprecated "#child_from_role(name, false) has been replaced by #find_child_from_role"
390
+ end
391
+
392
+ child = find_child_from_role(role_name)
393
+ if !child && validate
394
+ known_children = Hash.new
395
+ each_out_neighbour_merged(Dependency, intrusive: false) do |myself, child|
396
+ myself[child, Dependency][:roles].each do |role|
397
+ known_children[role] = child
398
+ end
399
+ end
400
+ raise Roby::NoSuchChild.new(self, role_name, known_children), "#{self} has no child with the role '#{role_name}'"
401
+ end
402
+ child
403
+ end
404
+
405
+ # Returns a task in the dependency hierarchy of this task by following
406
+ # the roles. +path+ is an array of role names, and the method will
407
+ # follow the trail until the desired task
408
+ #
409
+ # Raises ArgumentError if the child does not exist
410
+ #
411
+ # See #role_path to get a role path for a specific task
412
+ def resolve_role_path(*path)
413
+ if path.size == 1 && path[0].respond_to?(:to_ary)
414
+ path = path[0]
415
+ end
416
+ # Special case for ease of use in algorithms
417
+ if path.empty?
418
+ return self
419
+ end
420
+
421
+ up_until_now = []
422
+ path.inject(self) do |task, role|
423
+ up_until_now << role
424
+ if !(next_task = task.find_child_from_role(role))
425
+ raise ArgumentError, "the child #{up_until_now.join(".")} of #{task} does not exist"
426
+ end
427
+ next_task
428
+ end
429
+ end
430
+
431
+ # Returns a set role paths that lead to +task+ when starting from +self+
432
+ #
433
+ # A role path is an array of roles that lead to +task+ when starting by
434
+ # +self+.
435
+ #
436
+ # I.e. if ['role1', 'role2', 'role3'] is a role path from +self+ to
437
+ # +task, it means that
438
+ #
439
+ # task1 = self.child_from_role('role1')
440
+ # task2 = task1.child_from_role('role2')
441
+ # task = task2.child_from_role('role3')
442
+ #
443
+ # The method returns a set of role paths, as there may be multiple paths
444
+ # leading from +self+ to +task+
445
+ #
446
+ # See #resolve_role_path to get a task from its role path
447
+ def role_paths(task, validate = true)
448
+ if task == self
449
+ return []
450
+ end
451
+
452
+ result = []
453
+ task.each_role do |parent, roles|
454
+ if parent == self
455
+ new_paths = roles.map { |r| [r] }
456
+ elsif heads = role_paths(parent, false)
457
+ heads.each do |h|
458
+ roles.each do |t|
459
+ result << (h.dup << t)
460
+ end
461
+ end
462
+ end
463
+ if new_paths
464
+ result.concat(new_paths)
465
+ end
466
+ end
467
+
468
+ if result.empty?
469
+ if validate
470
+ raise ArgumentError, "#{task} can not be reached from #{self}"
471
+ end
472
+ return
473
+ end
474
+ result
475
+ end
476
+
477
+ # Adds +task+ as a child of +self+ in the Dependency relation. The
478
+ # following options are allowed:
479
+ #
480
+ # success:: the list of success events. The default is [:success]
481
+ # failure:: the list of failing events. The default is [:failed]
482
+ # model::
483
+ # a <tt>[task_model, arguments]</tt> pair which defines the task
484
+ # model the parent is expecting. The default value is to get these
485
+ # parameters from +task+
486
+ #
487
+ # The +success+ set describes the events of the child task that are
488
+ # _required_ by the parent task. More specifically, the child task
489
+ # remains useful for the parent task as long as none of these events are
490
+ # emitted. By default, it is the +success+ event. Of course, an error
491
+ # condition is encountered when all events of +success+ become
492
+ # unreachable. In addition, the relation is removed if the
493
+ # +remove_when_done+ flag is set to true (false by default).
494
+ #
495
+ # The +failure+ set describes the events of the child task which are an
496
+ # error condition from the parent task point of view.
497
+ #
498
+ # In both error cases, a +ChildFailedError+ exception is raised.
499
+ def depends_on(task, options = {})
500
+ if task.respond_to?(:as_plan)
501
+ task = task.as_plan
502
+ end
503
+ if task == self
504
+ raise ArgumentError, "cannot add a dependency of a task to itself"
505
+ end
506
+
507
+ options = Dependency.validate_options options,
508
+ model: [task.provided_models, task.meaningful_arguments],
509
+ success: :success.to_unbound_task_predicate,
510
+ failure: false.to_unbound_task_predicate,
511
+ remove_when_done: true,
512
+ consider_in_pending: true,
513
+ roles: nil,
514
+ role: nil
515
+
516
+ # We accept
517
+ #
518
+ # model
519
+ # [model1, model2]
520
+ # [model1, arguments]
521
+ # [[model1, model2], arguments]
522
+ if !options[:model].respond_to?(:to_ary)
523
+ options[:model] = [Array(options[:model]), Hash.new]
524
+ elsif options[:model].size == 2
525
+ if !options[:model].first.respond_to?(:to_ary)
526
+ if options[:model].last.kind_of?(Hash)
527
+ options[:model] = [Array(options[:model].first), options[:model].last]
528
+ else
529
+ options[:model] = [options[:model], Hash.new]
530
+ end
531
+ end
532
+ elsif !options[:model].first.respond_to?(:to_ary)
533
+ options[:model] = [Array(options[:model]), Hash.new]
534
+ end
535
+
536
+ roles = options[:roles] || Set.new
537
+ if role = options.delete(:role)
538
+ roles << role.to_str
539
+ end
540
+ roles = roles.map { |r| r.to_str }
541
+ options[:roles] = roles.to_set
542
+
543
+ if options[:success].nil?
544
+ options[:success] = []
545
+ end
546
+ options[:success] = Array[*options[:success]].
547
+ map { |predicate| predicate.to_unbound_task_predicate }.
548
+ inject(&:or)
549
+
550
+ if options[:failure].nil?
551
+ options[:failure] = []
552
+ end
553
+ options[:failure] = Array[*options[:failure]].
554
+ map { |predicate| predicate.to_unbound_task_predicate }.
555
+ inject(&:or)
556
+
557
+ #options[:success] ||= false.to_unbound_task_predicate
558
+ #options[:failure] ||= false.to_unbound_task_predicate
559
+
560
+ # Validate failure and success event names
561
+ if options[:success]
562
+ not_there = options[:success].required_events.
563
+ find_all { |name| !task.has_event?(name) }
564
+ if !not_there.empty?
565
+ raise ArgumentError, "#{task} does not have the following events: #{not_there.join(", ")}"
566
+ end
567
+ end
568
+
569
+ if options[:failure]
570
+ not_there = options[:failure].required_events.
571
+ find_all { |name| !task.has_event?(name) }
572
+ if !not_there.empty?
573
+ raise ArgumentError, "#{task} does not have the following events: #{not_there.join(", ")}"
574
+ end
575
+ end
576
+
577
+ # There is no positive events in success. Behind the scenes, it
578
+ # actually means that the task does not have to start (since nothing
579
+ # in :success would become unreachable)
580
+ #
581
+ # Add !:start in failure
582
+ if !options[:success]
583
+ not_started = :start.to_unbound_task_predicate.never
584
+ if options[:failure]
585
+ options[:failure] = not_started.or(options[:failure])
586
+ else
587
+ options[:failure] = not_started
588
+ end
589
+ end
590
+
591
+ required_model, required_args = *options[:model]
592
+ if !required_args.respond_to?(:to_hash)
593
+ raise ArgumentError, "argument specification must be a hash, got #{required_args} (#{required_args.class})"
594
+ elsif !task.fullfills?(required_model, required_args)
595
+ raise ArgumentError, "task #{task} does not fullfill the provided model #{options[:model]}"
596
+ end
597
+
598
+ # Check if there is already a dependency link. If it is the case,
599
+ # merge the options. Otherwise, just add.
600
+ add_child(task, options)
601
+ task
602
+ end
603
+
604
+ def remove_dependency(task_or_role)
605
+ if task_or_role.respond_to?(:to_str)
606
+ remove_child(child_from_role(task_or_role))
607
+ else
608
+ remove_child(task_or_role)
609
+ end
610
+ end
611
+
612
+ # Set up the event gathering needed by Dependency.check_structure
613
+ def added_child(child, info) # :nodoc:
614
+ super
615
+ relation_graphs[Dependency].update_triggers_for(self, child, info)
616
+ end
617
+
618
+ # Return the set of this task children for which the :start event has
619
+ # no parent in CausalLinks
620
+ def first_children
621
+ result = Set.new
622
+
623
+ causal_link_graph = plan.event_relation_graph_for(EventStructure::CausalLink)
624
+ relation_graph_for(Dependency).depth_first_visit(self) do |task|
625
+ next if task == self
626
+ if task != self && causal_link_graph.root?(task.start_event)
627
+ result << task
628
+ end
629
+ end
630
+ result
631
+ end
632
+
633
+ # Sets a base model specification that must be met by this task
634
+ #
635
+ # In normal operations, the fullfilled model returned by
636
+ # #fullfilled_model is computed from the dependency relations in which
637
+ # +self+ is a child.
638
+ #
639
+ # However, this fails in case +self+ is a root task in the dependency
640
+ # relation. Moreover, it might be handy to over-constrain the model
641
+ # computed through the dependency relation.
642
+ #
643
+ # In both cases, a model can be specified explicitely by setting the
644
+ # fullfilled_model attribute. The value has to be
645
+ #
646
+ # [task_model, [tag1, tag2, ...], task_arguments]
647
+ #
648
+ # For instance, a completely non-constrained model would be
649
+ #
650
+ # [Roby::Task, [], {}]
651
+ #
652
+ # This parameter can be set model-wide by using #fullfilled_model= on
653
+ # the class object
654
+ def fullfilled_model=(model)
655
+ if !model[0].kind_of?(Class)
656
+ raise ArgumentError, "expected a task model as first element, got #{model[0]}"
657
+ end
658
+ if !model[1].respond_to?(:to_ary)
659
+ raise ArgumentError, "expected an array as second element, got #{model[1]}"
660
+ elsif !model[1].all? { |t| t.kind_of?(Roby::Models::TaskServiceModel) }
661
+ raise ArgumentError, "expected an array of model tags as second element, got #{model[1]}"
662
+ end
663
+
664
+ if !model[2].respond_to?(:to_hash)
665
+ raise ArgumentError, "expected a hash as third element, got #{model[2]}"
666
+ end
667
+ @fullfilled_model = model
668
+ end
669
+
670
+ # The list of models and arguments that this task fullfilles
671
+ #
672
+ # If there is a task model in the list of models, it is always the first
673
+ # element of the model set
674
+ #
675
+ # @return [(Array<Model<Task>,Model<TaskService>>,{String=>Object}]
676
+ #
677
+ # Beware that, for historical reasons, this is not the same format than
678
+ # {#fullfilled_model=}
679
+ def fullfilled_model
680
+ if current_model = explicit_fullfilled_model
681
+ has_value = true
682
+ else current_model = [Roby::Task, [], {}]
683
+ end
684
+
685
+ each_in_neighbour_merged(Dependency, intrusive: false) do |myself, parent|
686
+ has_value = true
687
+
688
+ required_models, required_arguments = parent[myself, Dependency][:model]
689
+ current_model = Dependency.merge_fullfilled_model(current_model,
690
+ required_models, required_arguments)
691
+ end
692
+
693
+ if !has_value
694
+ model = self.model.fullfilled_model.find_all { |m| m <= Roby::Task }.min
695
+ [[model], self.meaningful_arguments]
696
+ else
697
+ model, tags, arguments = *current_model
698
+ tags = tags.dup
699
+ tags.unshift model
700
+ [tags, arguments]
701
+ end
702
+ end
703
+
704
+ # True if #fullfilled_model has been set on this task or on this task's
705
+ # model
706
+ #
707
+ # @return [Boolean]
708
+ def explicit_fullfilled_model?
709
+ !!explicit_fullfilled_model
710
+ end
711
+
712
+ # Returns an explicitly set {#fullfilled_model}
713
+ #
714
+ # @return [nil,Object] either nil if no explicit model has been set, or
715
+ # the model in the same format as expected by {#fullfilled_model=}
716
+ # (which is different than the value returned by {#fullfilled_model})
717
+ def explicit_fullfilled_model
718
+ if explicit = @fullfilled_model
719
+ explicit
720
+ elsif explicit = self.model.explicit_fullfilled_model
721
+ tasks, tags = explicit.partition { |m| m <= Roby::Task }
722
+ [tasks.first || Roby::Task, tags, Hash.new]
723
+ end
724
+ end
725
+
726
+ # Returns the set of models this task is providing by itself
727
+ #
728
+ # It differs from #fullfilled_model because it is not considering the
729
+ # models that are required because of the dependency relation
730
+ #
731
+ # @return [Array<Models::Task,TaskService>]
732
+ # @see #fullfilled_model
733
+ def provided_models
734
+ if model = explicit_fullfilled_model
735
+ [model[0]] + model[1]
736
+ else
737
+ models = self.model.fullfilled_model
738
+ task_class = models.find { |m| m.kind_of?(Class) }
739
+ [task_class] + models.find_all { |m| !task_class.has_ancestor?(m) }
740
+ end
741
+ end
742
+
743
+ # Enumerates the models that are fullfilled by this task
744
+ #
745
+ # @return [Array<Model<Task>,TaskService>]
746
+ # @see #provided_models
747
+ def each_fullfilled_model(&block)
748
+ fullfilled_model[0].each(&block)
749
+ end
750
+
751
+ # Remove all children that have successfully finished
752
+ def remove_finished_children
753
+ # We call #to_a to get a copy of children, since we will remove
754
+ # children in the block. Note that we can't use #delete_if here
755
+ # since #children is a relation enumerator (not the relation list
756
+ # itself)
757
+ children = each_child.to_a
758
+ for child in children
759
+ child, info = child
760
+ if info[:success].evaluate(child)
761
+ remove_child(child)
762
+ end
763
+ end
764
+ end
765
+
766
+ def has_through_method_missing?(m)
767
+ MetaRuby::DSLs.has_through_method_missing?(
768
+ self, m, '_child' => :has_role?)
769
+ end
770
+ def find_through_method_missing(m, args)
771
+ MetaRuby::DSLs.find_through_method_missing(
772
+ self, m, args, '_child' => :find_child_from_role)
773
+ end
774
+ end
775
+
776
+ module ModelExtension
777
+ # True if a fullfilled model has been explicitly set on self
778
+ # @return [Boolean]
779
+ def explicit_fullfilled_model?; !!@fullfilled_model end
780
+
781
+ # Returns an explicitly set {#fullfilled_model}
782
+ #
783
+ # @return [nil,Array<Models::Task,TaskService>] either nil if no
784
+ # explicit model has been set, or the list of models it must fullfill
785
+ def explicit_fullfilled_model; @fullfilled_model end
786
+
787
+ # Specifies the models that all instances of this task must fullfill
788
+ #
789
+ # This is usually used to under-constraint the model instances
790
+ #
791
+ # @param [Array<Models::Task,TaskService>] the list of models
792
+ def fullfilled_model=(models)
793
+ if !models.respond_to?(:to_ary)
794
+ raise ArgumentError, "expected an array, got #{models}"
795
+ elsif !models.all? { |t| t.kind_of?(Roby::Models::TaskServiceModel) || (t.respond_to?(:<=) && (t <= Roby::Task)) }
796
+ raise ArgumentError, "expected a submodel of TaskService, got #{models}"
797
+ end
798
+
799
+ @fullfilled_model = models
800
+ end
801
+
802
+ # @api private
803
+ #
804
+ # @return [Array<Models::Task,TaskService>] the list of models
805
+ # fullfilled by this task
806
+ def implicit_fullfilled_model
807
+ if !@implicit_fullfilled_model
808
+ @implicit_fullfilled_model = Array.new
809
+ ancestors.each do |m|
810
+ next if m.singleton_class?
811
+ if m.kind_of?(Class) || (m.kind_of?(Roby::Models::TaskServiceModel) && m != Roby::TaskService)
812
+ @implicit_fullfilled_model << m
813
+ end
814
+
815
+ if m == Roby::Task
816
+ break
817
+ end
818
+ end
819
+ end
820
+ @implicit_fullfilled_model
821
+ end
822
+
823
+ # Returns the model that all instances of this taks model fullfill
824
+ #
825
+ # (see Dependency::Extension#fullfilled_model)
826
+ def fullfilled_model
827
+ explicit_fullfilled_model || implicit_fullfilled_model
828
+ end
829
+
830
+ # Enumerates the models that all instances of this task model fullfill
831
+ #
832
+ # @yieldparam [Model<Task>,Model<TaskService>] model
833
+ # @return [void]
834
+ def each_fullfilled_model(&block)
835
+ fullfilled_model.each(&block)
836
+ end
837
+ end
838
+ end
839
+ end
840
+
841
+ # This exception is raised when a {hierarchy relation}[classes/Roby/TaskStructure/Hierarchy.html] fails
842
+ class ChildFailedError < RelationFailedError
843
+ # The child in the relation
844
+ def child; failed_task end
845
+ # The relation parameters (i.e. the hash given to #depends_on)
846
+ attr_reader :relation
847
+ # The Explanation object that describes why the relation failed
848
+ attr_reader :explanation
849
+ # @return [Symbol] the fault mode. It can either be :failed_event or
850
+ # :unreachable_success
851
+ attr_reader :mode
852
+
853
+ def initialize(parent, child, explanation, mode)
854
+ @explanation = explanation
855
+ @mode = mode
856
+
857
+ events, generators, others = [], [], []
858
+ explanation.elements.each do |e|
859
+ case e
860
+ when Event then events << e
861
+ when EventGenerator then generators << e
862
+ else others << e
863
+ end
864
+ end
865
+
866
+ failure_point =
867
+ if events.size > 2 || !others.empty?
868
+ child
869
+ else
870
+ base_event = events.first || generators.first
871
+ if explanation.value.nil? # unreachability
872
+ reason = base_event.unreachability_reason
873
+ if reason.respond_to?(:task) && reason.task == child
874
+ reason
875
+ else
876
+ base_event
877
+ end
878
+ elsif base_event.respond_to?(:root_task_sources)
879
+ sources = base_event.root_task_sources
880
+ if sources.size == 1
881
+ sources.first
882
+ else
883
+ base_event
884
+ end
885
+ else
886
+ base_event
887
+ end
888
+ end
889
+
890
+ super(failure_point)
891
+
892
+ report_exceptions_from(explanation)
893
+ @parent = parent
894
+ @relation = parent[child, TaskStructure::Dependency]
895
+ if @relation
896
+ @relation = @relation.dup
897
+ end
898
+ end
899
+
900
+ def pretty_print(pp) # :nodoc:
901
+ pp.text "#{child} failed"
902
+ pp.breakable
903
+ pp.text "child #{relation[:roles].to_a.join(", ")} of #{parent}"
904
+ pp.breakable
905
+ if mode == :failed_event
906
+ pp.text "triggered the failure predicate '#{relation[:failure]}': "
907
+ elsif mode == :unreachable_success
908
+ pp.text "cannot reach the success condition '#{relation[:success]}': "
909
+ end
910
+ explanation.pretty_print(pp)
911
+
912
+ pp.breakable
913
+ pp.text "The failed relation is"
914
+ pp.breakable
915
+ pp.nest(2) do
916
+ pp.text " "
917
+ parent.pretty_print pp
918
+ pp.breakable
919
+ pp.text "depends_on "
920
+ child.pretty_print pp
921
+ end
922
+ pp.breakable
923
+ end
924
+ def backtrace; [] end
925
+
926
+ # True if +obj+ is involved in this exception
927
+ def involved_plan_object?(obj)
928
+ super || obj == parent
929
+ end
930
+ end
931
+ end
932
+