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,62 @@
1
+ module Roby
2
+ module Models
3
+ # Model-level API for task events
4
+ module TaskEvent
5
+ include MetaRuby::ModelAsClass
6
+
7
+ # The task model this event is defined on
8
+ # @return [Model<Task>]
9
+ attr_accessor :task_model
10
+
11
+ # If the event model defines a controlable event
12
+ # By default, an event is controlable if the model
13
+ # responds to #call
14
+ def controlable?; respond_to?(:call) end
15
+
16
+ # Called by Task.update_terminal_flag to update the flag
17
+ attr_predicate :terminal?, true
18
+
19
+ # @return [Symbol] the event name
20
+ attr_accessor :symbol
21
+
22
+ def setup_submodel(submodel, task_model: nil, symbol: nil, command: false, terminal: false, **options, &block)
23
+ super(submodel, options, &block)
24
+ submodel.task_model = task_model
25
+ submodel.symbol = symbol
26
+ submodel.terminal = terminal
27
+
28
+ if command
29
+ if command.respond_to?(:call)
30
+ # check that the supplied command handler can take two arguments
31
+ check_arity(command, 2, strict: true)
32
+ submodel.singleton_class.class_eval do
33
+ define_method(:call, &command)
34
+ end
35
+ else
36
+ submodel.singleton_class.class_eval do
37
+ def call(task, context) # :nodoc:
38
+ task.event(symbol).emit(*context)
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ submodel
45
+ end
46
+
47
+ # @return [TaskEventGeneratorMatcher] returns an object that allows
48
+ # to match all generators of this type
49
+ def match
50
+ Queries::TaskEventGeneratorMatcher.new(task_model.match, symbol.to_s)
51
+ end
52
+
53
+ # @return [TaskEventGeneratorMatcher] returns an object that allows
54
+ # to match all generators of this type, as well as any generator
55
+ # that is forwarded to it
56
+ def generalized_match
57
+ Queries::TaskEventGeneratorMatcher.new(task_model.match, symbol.to_s).generalized
58
+ end
59
+ end
60
+ end
61
+ end
62
+
@@ -0,0 +1,78 @@
1
+ module Roby
2
+ module Models
3
+ # Ruby (the language) has no support for multiple inheritance. Instead, it
4
+ # uses module to extend classes outside of the class hierarchy.
5
+ #
6
+ # TaskService are the equivalent concept in the world of task models. They
7
+ # are a limited for of task models, which can be used to represent that
8
+ # certain task models have multiple functions.
9
+ #
10
+ # For instance,
11
+ #
12
+ # task_service "CameraDriver" do
13
+ # # CameraDriver is an abstract model used to represent that some tasks
14
+ # # are providing the services of cameras. They can be used to tag tasks
15
+ # # that belong to different class hirerachies.
16
+ # #
17
+ # # One can set up arguments on TaskService the same way than class models:
18
+ # argument :camera_name
19
+ # argument :aperture
20
+ # argument :aperture
21
+ # end
22
+ #
23
+ # FirewireDriver.provides CameraDriver
24
+ # # FirewireDriver can now be used in relationships where CameraDriver was
25
+ # # needed
26
+ class TaskServiceModel < Module
27
+ include MetaRuby::ModelAsModule
28
+ include Arguments
29
+
30
+ def clear_model
31
+ super
32
+ arguments.clear
33
+ end
34
+
35
+ def query(*args)
36
+ query = Queries::Query.new
37
+ if args.empty? && self != TaskService
38
+ query.which_fullfills(self)
39
+ else
40
+ query.which_fullfills(*args)
41
+ end
42
+ query
43
+ end
44
+
45
+ def match(*args)
46
+ matcher = Queries::TaskMatcher.new
47
+ if args.empty? && self != TaskService
48
+ matcher.which_fullfills(self)
49
+ else
50
+ matcher.which_fullfills(*args)
51
+ end
52
+ matcher
53
+ end
54
+ end
55
+
56
+ module TaskServiceDefinitionDSL
57
+ # Define a new task service. When defining the service, one does:
58
+ #
59
+ # module MyApplication
60
+ # task_service 'NavigationService' do
61
+ # argument :target, type: Eigen::Vector3
62
+ # end
63
+ # end
64
+ #
65
+ # Then, to use it:
66
+ #
67
+ # class GoTo
68
+ # provides MyApplication::NavigationService
69
+ # end
70
+ #
71
+ def task_service(name, &block)
72
+ MetaRuby::ModelAsModule.create_and_register_submodel(self, name, TaskService, &block)
73
+ end
74
+ end
75
+ Module.include TaskServiceDefinitionDSL
76
+ end
77
+ end
78
+
@@ -0,0 +1,88 @@
1
+ module Roby
2
+ # Fires when the first of its source events fires.
3
+ #
4
+ # For instance,
5
+ #
6
+ # a = task1.start_event
7
+ # b = task2.start_event
8
+ # (a | b) # will emit as soon as one of task1 and task2 are started
9
+ #
10
+ # Or events will emit only once, unless #reset is called:
11
+ #
12
+ # a = task1.intermediate_event
13
+ # b = task2.intermediate_event
14
+ # or_ev = (a | b)
15
+ #
16
+ # a.intermediate_event! # or_ev emits here
17
+ # b.intermediate_event! # or_ev does *not* emit
18
+ # a.intermediate_event! # or_ev does *not* emit
19
+ # b.intermediate_event! # or_ev does *not* emit
20
+ #
21
+ # or_ev.reset
22
+ # b.intermediate_event! # or_ev emits here
23
+ # a.intermediate_event! # or_ev does *not* emit
24
+ # b.intermediate_event! # or_ev does *not* emit
25
+ #
26
+ # The OrGenerator tracks its sources via the signalling relations, so
27
+ #
28
+ # or_ev << c.intermediate_event
29
+ #
30
+ # is equivalent to
31
+ #
32
+ # c.intermediate_event.add_signal or_ev
33
+ #
34
+ class OrGenerator < EventGenerator
35
+ # Creates a new OrGenerator without any sources.
36
+ def initialize
37
+ super do |context|
38
+ emit_if_first(context)
39
+ end
40
+ @active = true
41
+ end
42
+
43
+ # True if there is no source events
44
+ def empty?; parent_objects(EventStructure::Signal).empty? end
45
+
46
+ # Or generators will emit only once, unless this method is called. See
47
+ # the documentation of OrGenerator for an example.
48
+ def reset
49
+ @active = true
50
+ each_parent_object(EventStructure::Signal) do |source|
51
+ if source.respond_to?(:reset)
52
+ source.reset
53
+ end
54
+ end
55
+ end
56
+
57
+ # Helper method called to emit the event when it is required
58
+ def emit_if_first(context) # :nodoc:
59
+ return unless @active
60
+ @active = false
61
+ emit(context)
62
+ end
63
+
64
+ # Tracks the event's parents in the signalling relation
65
+ def added_signal_parent(parent, info) # :nodoc:
66
+ super
67
+ parent.if_unreachable(cancel_at_emission: true) do |reason, event|
68
+ if !emitted? && each_parent_object(EventStructure::Signal).all? { |ev| ev.unreachable? }
69
+ unreachable!(reason || parent)
70
+ end
71
+ end
72
+ end
73
+
74
+ def removed_signal_parent(parent)
75
+ super
76
+ if !emitted? && each_parent_object(EventStructure::Signal).all? { |ev| ev.unreachable? }
77
+ unreachable!
78
+ end
79
+ end
80
+
81
+ # Adds +generator+ to the sources of this event
82
+ def << (generator)
83
+ generator.add_signal self
84
+ self
85
+ end
86
+ end
87
+ end
88
+
@@ -1,946 +1,1833 @@
1
1
  module Roby
2
2
  # A plan object manages a collection of tasks and events.
3
- #
4
- # == Adding and removing objects from plans
5
- # The #add, #add_mission and #add_permanent calls allow to add objects in
6
- # plans. The #remove_object removes the same objects from the plan. Note
7
- # that you should never remove objects yourself: a GC mechanism will do
8
- # that properly for you, taking into account the consequences of the object
9
- # removal.
10
- #
11
- # To reduce the complexity of object management, a garbage collection
12
- # mechanism is in place during the plan execution, stopping and removing
13
- # tasks that are not useful anymore for the system's goals. This garbage
14
- # collection mechanism runs at the end of the execution cycle. Once an
15
- # object is not active (i.e. for a task, once it is stopped), the object is
16
- # /finalized/ and either the #finalized_task or the #finalized_event hook is
17
- # called.
18
- #
19
- # Two special kinds of objects exist in plans:
20
- # * the +missions+ (#missions, #mission?, #add_mission and #unmark_mission) are the
21
- # final goals of the system. A task is +useful+ if it helps into the
22
- # Realization of a mission (it is the child of a mission through one of the
23
- # task relations).
24
- # * the +permanent+ objects (#add_permanent, #unmark_permanent, #permanent?, #permanent_tasks and
25
- # #permanent_events) are plan objects that are not affected by the plan's
26
- # garbage collection mechanism. As for missions, task that are useful to
27
- # permanent tasks are also
28
- #
29
- class Plan < BasicObject
30
- extend Logger::Hierarchy
31
- extend Logger::Forward
32
-
33
- # The ExecutionEngine object which handles this plan. The role of this
34
- # object is to provide the event propagation, error propagation and
35
- # garbage collection mechanisms for the execution.
36
- attr_accessor :engine
37
- # The DecisionControl object which is associated with this plan. This
38
- # object's role is to handle the conflicts that can occur during event
39
- # propagation.
40
- def control; engine.control end
41
-
42
- # The task index for this plan. This is a TaskIndex object which allows
3
+ class Plan < DistributedObject
4
+ extend Logger::Hierarchy
5
+ extend Logger::Forward
6
+ include DRoby::EventLogging
7
+
8
+ # The Peer ID of the local owner (i.e. of the local process / execution
9
+ # engine)
10
+ attr_accessor :local_owner
11
+
12
+ # The task index for this plan. This is a {Queries::Index} object which allows
43
13
  # efficient resolving of queries.
44
- attr_reader :task_index
45
-
46
- # The list of tasks that are included in this plan
47
- attr_reader :known_tasks
48
- # The set of events that are defined by #known_tasks
49
- attr_reader :task_events
50
- # The list of the robot's missions. Do not change that set directly, use
51
- # #add_mission and #remove_mission instead.
52
- attr_reader :missions
53
- # The list of tasks that are kept outside GC. Do not change that set
54
- # directly, use #permanent and #auto instead.
55
- attr_reader :permanent_tasks
56
- # The list of events that are not included in a task
57
- attr_reader :free_events
58
- # The list of events that are kept outside GC. Do not change that set
14
+ attr_reader :task_index
15
+
16
+ # The list of tasks that are included in this plan
17
+ attr_reader :tasks
18
+ # The set of events that are defined by #tasks
19
+ attr_reader :task_events
20
+ # The set of the robot's missions
21
+ # @see add_mission_task unmark_mission_task
22
+ attr_reader :mission_tasks
23
+ # The set of tasks that are kept around "just in case"
24
+ # @see add_permanent_task unmark_permanent_task
25
+ attr_reader :permanent_tasks
26
+ # The list of events that are not included in a task
27
+ attr_reader :free_events
28
+ # The list of events that are kept outside GC. Do not change that set
59
29
  # directly, use #permanent and #auto instead.
60
- attr_reader :permanent_events
61
-
62
- # A map of event => task repairs. Whenever an exception is found,
63
- # exception propagation checks that no repair is defined for that
64
- # particular event or for events that are forwarded by it.
65
- #
66
- # See also #add_repair and #remove_repair
67
- attr_reader :repairs
68
-
69
- # A set of tasks which are useful (and as such would not been garbage
70
- # collected), but we want to GC anyway
71
- attr_reader :force_gc
72
-
73
- # A set of task for which GC should not be attempted, either because
74
- # they are not interruptible or because their start or stop command
75
- # failed
76
- attr_reader :gc_quarantine
77
-
78
- # The set of transactions which are built on top of this plan
79
- attr_reader :transactions
80
-
81
- # If this object is the main plan, checks if we are subscribed to
82
- # the whole remote plan
83
- def sibling_on?(peer)
84
- if Roby.plan == self then peer.remote_plan
85
- else super
86
- end
87
- end
88
-
89
- # The set of relations available for this plan
90
- attr_reader :relations
91
-
92
- # The propagation engine for this object. It is either nil (if no
93
- # propagation engine is available) or self.
94
- attr_reader :propagation_engine
95
-
96
- def initialize
97
- @missions = ValueSet.new
98
- @permanent_tasks = ValueSet.new
99
- @permanent_events = ValueSet.new
100
- @known_tasks = ValueSet.new
101
- @free_events = ValueSet.new
102
- @task_events = ValueSet.new
103
- @force_gc = ValueSet.new
104
- @gc_quarantine = ValueSet.new
105
- @transactions = ValueSet.new
106
- @repairs = Hash.new
107
- @exception_handlers = Array.new
108
-
109
- @relations = TaskStructure.relations + EventStructure.relations
110
- @structure_checks = relations.
111
- map { |r| r.method(:check_structure) if r.respond_to?(:check_structure) }.
112
- compact
113
-
114
- @task_index = Roby::TaskIndex.new
115
-
116
- super() if defined? super
117
- end
118
-
119
- def inspect # :nodoc:
120
- "#<#{to_s}: missions=#{missions.to_s} tasks=#{known_tasks.to_s} events=#{free_events.to_s} transactions=#{transactions.to_s}>"
121
- end
30
+ attr_reader :permanent_events
31
+ # A set of pair of task matching objects and blocks defining this plan's
32
+ # triggers
33
+ #
34
+ # See {#add_trigger}
35
+ attr_reader :triggers
36
+
37
+ # The set of transactions which are built on top of this plan
38
+ attr_reader :transactions
39
+
40
+ # If this object is the main plan, checks if we are subscribed to
41
+ # the whole remote plan
42
+ def sibling_on?(peer)
43
+ if Roby.plan == self then peer.remote_plan
44
+ else super
45
+ end
46
+ end
47
+
48
+ # The set of PlanService instances that are defined on this plan
49
+ attr_reader :plan_services
50
+
51
+ # A template plan is meant to be injected in another plan
52
+ #
53
+ # When a {PlanObject} is included in a template plan, adding relations
54
+ # to other tasks causes the plans to merge as needed. Doing the same
55
+ # operation with plain plans causes an error
56
+ #
57
+ # @see TemplatePlan
58
+ def template?; false end
59
+
60
+ # Check that this is an executable plan. This is always true for
61
+ # plain Plan objects and false for transcations
62
+ def executable?; false end
63
+
64
+ # The event logger
65
+ attr_accessor :event_logger
66
+
67
+ # The observer object that reacts to relation changes
68
+ attr_reader :graph_observer
69
+
70
+ def initialize(graph_observer: nil, event_logger: DRoby::NullEventLogger.new)
71
+ @local_owner = DRoby::PeerID.new('local')
72
+
73
+ @mission_tasks = Set.new
74
+ @permanent_tasks = Set.new
75
+ @permanent_events = Set.new
76
+ @tasks = Set.new
77
+ @free_events = Set.new
78
+ @task_events = Set.new
79
+ @transactions = Set.new
80
+ @fault_response_tables = Array.new
81
+ @triggers = []
82
+
83
+ @plan_services = Hash.new
84
+
85
+ self.event_logger = event_logger
86
+ @active_fault_response_tables = Array.new
87
+ @task_index = Roby::Queries::Index.new
88
+
89
+ @graph_observer = graph_observer
90
+ create_relations
91
+
92
+ super()
93
+ end
94
+
95
+ def create_relations
96
+ @task_relation_graphs, @event_relation_graphs =
97
+ self.class.instanciate_relation_graphs(graph_observer: graph_observer)
98
+
99
+ @structure_checks = Array.new
100
+ each_relation_graph do |graph|
101
+ if graph.respond_to?(:check_structure)
102
+ structure_checks << graph.method(:check_structure)
103
+ end
104
+ end
105
+ end
106
+
107
+ def refresh_relations
108
+ create_relations
109
+ end
110
+
111
+ def self.instanciate_relation_graphs(graph_observer: nil)
112
+ task_relation_graphs = Relations::Space.new_relation_graph_mapping
113
+ Task.all_relation_spaces.each do |space|
114
+ task_relation_graphs.merge!(
115
+ space.instanciate(observer: graph_observer))
116
+ end
117
+
118
+ event_relation_graphs = Relations::Space.new_relation_graph_mapping
119
+ EventGenerator.all_relation_spaces.each do |space|
120
+ event_relation_graphs.merge!(
121
+ space.instanciate(observer: graph_observer))
122
+ end
123
+ return task_relation_graphs, event_relation_graphs
124
+ end
125
+
126
+ def dedupe(source)
127
+ @task_relation_graphs.each do |relation, graph|
128
+ if relation != graph
129
+ graph.dedupe(source.task_relation_graph_for(relation))
130
+ end
131
+ end
132
+ @event_relation_graphs.each do |relation, graph|
133
+ if relation != graph
134
+ graph.dedupe(source.event_relation_graph_for(relation))
135
+ end
136
+ end
137
+ end
138
+
139
+ # The graphs that make task relations, formatted as required by
140
+ # {Relations::DirectedRelationSupport#relation_graphs}
141
+ #
142
+ # @see each_task_relation_graph
143
+ attr_reader :task_relation_graphs
144
+
145
+ # The graphs that make event relations, formatted as required by
146
+ # {Relations::DirectedRelationSupport#relation_graphs}
147
+ #
148
+ # @see each_event_relation_graph
149
+ attr_reader :event_relation_graphs
150
+
151
+ # Enumerate the graph objects that contain this plan's relation
152
+ # information
153
+ #
154
+ # @yieldparam [Relations::Graph] graph
155
+ def each_relation_graph(&block)
156
+ return enum_for(__method__) if !block_given?
157
+ each_event_relation_graph(&block)
158
+ each_task_relation_graph(&block)
159
+ end
160
+
161
+ # Enumerate all graphs (event and tasks) that form this plan
162
+ def each_relation_graph
163
+ return enum_for(__method__) if !block_given?
164
+ each_task_relation_graph { |g| yield(g) }
165
+ each_event_relation_graph { |g| yield(g) }
166
+ end
167
+
168
+ # Enumerate the graph objects that contain this plan's event relation
169
+ # information
170
+ #
171
+ # @yieldparam [Relations::EventRelationGraph] graph
172
+ def each_event_relation_graph
173
+ return enum_for(__method__) if !block_given?
174
+ event_relation_graphs.each do |k, v|
175
+ yield(v) if k == v
176
+ end
177
+ end
178
+
179
+ # Resolves an event graph object from the graph class (i.e. the graph model)
180
+ def event_relation_graph_for(model)
181
+ event_relation_graphs.fetch(model)
182
+ end
183
+
184
+ # Enumerate the graph objects that contain this plan's task relation
185
+ # information
186
+ #
187
+ # @yieldparam [Relations::TaskRelationGraph] graph
188
+ def each_task_relation_graph
189
+ return enum_for(__method__) if !block_given?
190
+ task_relation_graphs.each do |k, v|
191
+ yield(v) if k == v
192
+ end
193
+ end
194
+
195
+ # Resolves a task graph object from the graph class (i.e. the graph model)
196
+ def task_relation_graph_for(model)
197
+ task_relation_graphs.fetch(model)
198
+ end
199
+
200
+ def dup
201
+ new_plan = Plan.new
202
+ copy_to(new_plan)
203
+ new_plan
204
+ end
205
+
206
+ def inspect # :nodoc:
207
+ "#<#{to_s}: mission_tasks=#{mission_tasks.to_s} tasks=#{tasks.to_s} events=#{free_events.to_s} transactions=#{transactions.to_s}>"
208
+ end
209
+
210
+ # Calls the given block in the execution thread of this plan's engine.
211
+ # If there is no engine attached to this plan, yields immediately
212
+ #
213
+ # See ExecutionEngine#execute
214
+ def execute(&block)
215
+ yield
216
+ end
217
+
218
+ # @deprecated use {#merge} instead
219
+ def copy_to(copy)
220
+ copy.merge(self)
221
+ end
222
+
223
+ def merge_base(plan)
224
+ free_events.merge(plan.free_events)
225
+ mission_tasks.merge(plan.mission_tasks)
226
+ tasks.merge(plan.tasks)
227
+ permanent_tasks.merge(plan.permanent_tasks)
228
+ permanent_events.merge(plan.permanent_events)
229
+ task_index.merge(plan.task_index)
230
+ task_events.merge(plan.task_events)
231
+ end
232
+
233
+ def merge_relation_graphs(plan)
234
+ # Now merge the relation graphs
235
+ #
236
+ # Since task_relation_graphs contains both Class<Graph>=>Graph and
237
+ # Graph=>Graph, we merge only the graphs for which
238
+ # self.task_relation_graphs has an entry (i.e. Class<Graph>) and
239
+ # ignore the rest
240
+ plan.task_relation_graphs.each do |rel_id, rel|
241
+ next if rel_id == rel
242
+ next if !(this_rel = task_relation_graphs.fetch(rel_id, nil))
243
+ this_rel.merge(rel)
244
+ end
245
+ plan.event_relation_graphs.each do |rel_id, rel|
246
+ next if rel_id == rel
247
+ next if !(this_rel = event_relation_graphs.fetch(rel_id, nil))
248
+ this_rel.merge(rel)
249
+ end
250
+ end
251
+
252
+ def replace_relation_graphs(merged_graphs)
253
+ merged_graphs.each do |self_g, new_g|
254
+ self_g.replace(new_g)
255
+ end
256
+ end
257
+
258
+ def merge_transaction(transaction, merged_graphs, added, removed, updated)
259
+ merging_plan(transaction)
260
+ merge_base(transaction)
261
+ replace_relation_graphs(merged_graphs)
262
+ merged_plan(transaction)
263
+ end
264
+
265
+ def merge_transaction!(transaction, merged_graphs, added, removed, updated)
266
+ # Note: Task#plan= updates its bound events
267
+ tasks, events = transaction.tasks.dup, transaction.free_events.dup
268
+ tasks.each { |t| t.plan = self }
269
+ events.each { |e| e.plan = self }
270
+
271
+ merge_transaction(transaction, merged_graphs, added, removed, updated)
272
+ end
273
+
274
+ def find_triggers_matches(plan)
275
+ triggers.map do |tr|
276
+ [tr, tr.each(plan).to_a]
277
+ end
278
+ end
279
+
280
+ def apply_triggers_matches(matches)
281
+ matches.each do |trigger, matched_tasks|
282
+ matched_tasks.each do |t|
283
+ trigger.call(t)
284
+ end
285
+ end
286
+ end
287
+
288
+ # Merges the content of a plan into self
289
+ #
290
+ # It is assumed that self and plan do not intersect.
291
+ #
292
+ # Unlike {#merge!}, it does not update its argument, neither update the
293
+ # plan objects to point to self afterwards
294
+ #
295
+ # @param [Roby::Plan] plan the plan to merge into self
296
+ def merge(plan)
297
+ return if plan == self
298
+
299
+ trigger_matches = find_triggers_matches(plan)
300
+ merging_plan(plan)
301
+ merge_base(plan)
302
+ merge_relation_graphs(plan)
303
+ merged_plan(plan)
304
+ apply_triggers_matches(trigger_matches)
305
+ end
306
+
307
+ # Moves the content of other_plan into self, and clears other_plan
308
+ #
309
+ # It is assumed that other_plan and plan do not intersect
310
+ #
311
+ # Unlike {#merge}, it ensures that all plan objects have their
312
+ # {PlanObject#plan} attribute properly updated, and it cleans plan
313
+ #
314
+ # @param [Roby::Plan] plan the plan to merge into self
315
+ def merge!(plan)
316
+ return if plan == self
317
+
318
+ tasks, events = plan.tasks.dup, plan.free_events.dup
319
+ tasks.each { |t| t.plan = self }
320
+ events.each { |e| e.plan = self }
321
+ merge(plan)
322
+ end
323
+
324
+ # Hook called just before performing a {#merge}
325
+ def merging_plan(plan)
326
+ end
327
+
328
+ # Hook called when a {#merge} has been performed
329
+ def merged_plan(plan)
330
+ end
331
+
332
+ def deep_copy
333
+ plan = Roby::Plan.new
334
+ mappings = deep_copy_to(plan)
335
+ return plan, mappings
336
+ end
337
+
338
+ # Copies this plan's state (tasks, events and their relations) into the
339
+ # provided plan
340
+ #
341
+ # It returns the mapping from the plan objects in +self+ to the plan
342
+ # objects in +copy+. For instance, if +t+ is a task in +plan+, then
343
+ #
344
+ # mapping = plan.copy_to(copy)
345
+ # mapping[t] => corresponding task in +copy+
346
+ def deep_copy_to(copy)
347
+ mappings = Hash.new do |h, k|
348
+ if !self.include?(k)
349
+ raise InternalError, "#{k} is listed in a relation, but is not included in the corresponding plan #{self}"
350
+ else
351
+ raise InternalError, "#{k} is an object in #{self} for which no mapping has been created in #{copy}"
352
+ end
353
+ end
354
+
355
+ # First create a copy of all the tasks
356
+ tasks.each do |t|
357
+ new_t = t.dup
358
+ mappings[t] = new_t
359
+
360
+ t.each_event do |ev|
361
+ new_ev = ev.dup
362
+ new_ev.instance_variable_set :@task, new_t
363
+ new_t.bound_events[ev.symbol] = new_ev
364
+ mappings[ev] = new_ev
365
+ end
366
+
367
+ copy.register_task(new_t)
368
+ new_t.each_event do |ev|
369
+ copy.register_event(ev)
370
+ end
371
+ end
372
+ free_events.each do |e|
373
+ new_e = e.dup
374
+ mappings[e] = new_e
375
+ copy.register_event(new_e)
376
+ end
377
+
378
+ mission_tasks.each { |t| copy.add_mission_task(mappings[t]) }
379
+ permanent_tasks.each { |t| copy.add_permanent_task(mappings[t]) }
380
+ permanent_events.each { |e| copy.add_permanent_event(mappings[e]) }
381
+
382
+ copy_relation_graphs_to(copy, mappings)
383
+ mappings
384
+ end
385
+
386
+ def copy_relation_graphs_to(copy, mappings)
387
+ each_task_relation_graph do |graph|
388
+ target_graph = copy.task_relation_graph_for(graph.class)
389
+ graph.each_edge do |parent, child|
390
+ target_graph.add_edge(
391
+ mappings[parent], mappings[child], graph.edge_info(parent, child))
392
+ end
393
+ end
394
+
395
+ each_event_relation_graph do |graph|
396
+ target_graph = copy.event_relation_graph_for(graph.class)
397
+ graph.each_edge do |parent, child|
398
+ target_graph.add_edge(
399
+ mappings[parent], mappings[child], graph.edge_info(parent, child))
400
+ end
401
+ end
402
+ end
403
+
404
+ # Verifies that all graphs that should be acyclic are
405
+ def validate_graphs(graphs)
406
+ # Make a topological sort of the graphs
407
+ seen = Set.new
408
+ Relations.each_graph_topologically(graphs) do |g|
409
+ if seen.include?(g)
410
+ next
411
+ elsif !g.dag?
412
+ next
413
+ end
414
+
415
+ if !g.acyclic?
416
+ raise Relations::CycleFoundError, "#{g.class} has cycles"
417
+ end
418
+ seen << g
419
+ seen.merge(g.recursive_subsets)
420
+ end
421
+ end
422
+
423
+ # @api private
424
+ #
425
+ # Normalize an validate the arguments to {#add} into a list of plan objects
426
+ def normalize_add_arguments(objects)
427
+ if !objects.respond_to?(:each)
428
+ objects = [objects]
429
+ end
430
+
431
+ objects.map do |o|
432
+ if o.respond_to?(:as_plan) then o.as_plan
433
+ elsif o.respond_to?(:to_event) then o.to_event
434
+ elsif o.respond_to?(:to_task) then o.to_task
435
+ else raise ArgumentError, "found #{o || 'nil'} which is neither a task nor an event"
436
+ end
437
+ end
438
+ end
439
+
440
+ # If this plan is a toplevel plan, returns self. If it is a
441
+ # transaction, returns the underlying plan
442
+ def real_plan
443
+ ret = self
444
+ while ret.respond_to?(:plan)
445
+ ret = ret.plan
446
+ end
447
+ ret
448
+ end
449
+
450
+ # True if this plan is root in the plan hierarchy
451
+ def root_plan?
452
+ true
453
+ end
454
+
455
+ # Returns the set of stacked transaction
456
+ #
457
+ # @return [Array] the list of plans in the transaction stack, the first
458
+ # element being the most-nested transaction and the last element the
459
+ # underlying real plan (equal to {#real_plan})
460
+ def transaction_stack
461
+ plan_chain = [self]
462
+ while plan_chain.last.respond_to?(:plan)
463
+ plan_chain << plan_chain.last.plan
464
+ end
465
+ plan_chain
466
+ end
467
+
468
+ # @deprecated use {#add_mission_task} instead
469
+ def add_mission(task)
470
+ Roby.warn_deprecated "#add_mission is deprecated, use #add_mission_task instead"
471
+ add_mission_task(task)
472
+ end
473
+
474
+ # @deprecated use {#mission_task?} instead
475
+ def mission?(task)
476
+ Roby.warn_deprecated "#mission? is deprecated, use #mission_task? instead"
477
+ mission_task?(task)
478
+ end
479
+
480
+ # @deprecated use {#unmark_mission_task} instead
481
+ def unmark_mission(task)
482
+ Roby.warn_deprecated "#unmark_mission is deprecated, use #unmark_mission_task instead"
483
+ unmark_mission_task(task)
484
+ end
485
+
486
+ # Tests whether a task is present in the plan
487
+ def has_task?(task)
488
+ tasks.include?(task)
489
+ end
490
+
491
+ # Add a task to the plan's set of missions
492
+ #
493
+ # A mission represents the system's overall goal. As such a mission task
494
+ # and all its dependencies are protected against the garbage collection
495
+ # mechanisms, and the emission of a mission's failed event causes a
496
+ # MissionFailedError exception to be generated.
497
+ #
498
+ # Note that this method should be used to add the task to the plan and
499
+ # mark it as mission, and to mark an already added task as mission as
500
+ # well.
501
+ #
502
+ # @see mission_task? unmark_mission_task
503
+ def add_mission_task(task)
504
+ task = normalize_add_arguments([task]).first
505
+ return if mission_tasks.include?(task)
506
+ add([task])
507
+ mission_tasks << task
508
+ task.mission = true if task.self_owned?
509
+ notify_task_status_change(task, :mission)
510
+ task
511
+ end
512
+
513
+ # Checks if a task is part of the plan's missions
514
+ #
515
+ # @see add_mission_task unmark_mission_task
516
+ def mission_task?(task)
517
+ @mission_tasks.include?(task.to_task)
518
+ end
519
+
520
+ # Removes a task from the plan's missions
521
+ #
522
+ # It does not remove the task from the plan. In a plan that is being
523
+ # executed, it is done by garbage collection. In a static plan, it can
524
+ # either be done with {#static_garbage_collect} or directly by calling
525
+ # {#remove_task} or {#remove_free_event}
526
+ #
527
+ # @see add_mission_task mission_task?
528
+ def unmark_mission_task(task)
529
+ task = task.to_task
530
+ return if !@mission_tasks.include?(task)
531
+ @mission_tasks.delete(task)
532
+ task.mission = false if task.self_owned?
533
+ notify_task_status_change(task, :normal)
534
+ self
535
+ end
536
+
537
+ # @deprecated use {#add_permanent_task} or {#add_permanent_event} instead
538
+ def add_permanent(object)
539
+ Roby.warn_deprecated "#add_permanent is deprecated, use either #add_permanent_task or #add_permanent_event instead"
540
+ object = normalize_add_arguments([object]).first
541
+ if object.respond_to?(:to_task)
542
+ add_permanent_task(object)
543
+ else
544
+ add_permanent_event(object)
545
+ end
546
+ object
547
+ end
548
+
549
+ # @deprecated use {#unmark_permanent_task} or {#unmark_permanent_event} instead
550
+ def unmark_permanent(object)
551
+ Roby.warn_deprecated "#unmark_permanent is deprecated, use either #unmark_permanent_task or #unmark_permanent_event"
552
+ if object.respond_to?(:to_task)
553
+ unmark_permanent_task(object)
554
+ elsif object.respond_to?(:to_event)
555
+ unmark_permanent_event(object)
556
+ else
557
+ raise ArgumentError, "expected a task or event and got #{object}"
558
+ end
559
+ end
560
+
561
+ # @deprecated use {#permanent_task?} or {#permanent_event?} instead
562
+ def permanent?(object)
563
+ Roby.warn_deprecated "#permanent? is deprecated, use either #permanent_task? or #permanent_event?"
564
+ if object.respond_to?(:to_task)
565
+ permanent_task?(object)
566
+ elsif object.respond_to?(:to_event)
567
+ permanent_event?(object)
568
+ else
569
+ raise ArgumentError, "expected a task or event and got #{object}"
570
+ end
571
+ end
572
+
573
+ # Mark a task as permanent, optionally adding to the plan
574
+ #
575
+ # Permanent tasks are protected against garbage collection. Like
576
+ # missions, failure of a permanent task will generate a plan exception
577
+ # {PermanentTaskError}. Unlike missions, this exception is non-fatal.
578
+ def add_permanent_task(task)
579
+ task = normalize_add_arguments([task]).first
580
+ return if permanent_tasks.include?(task)
581
+ add([task])
582
+ permanent_tasks << task
583
+ notify_task_status_change(task, :permanent)
584
+ task
585
+ end
586
+
587
+ # True if the given task is registered as a permanent task on self
588
+ def permanent_task?(task)
589
+ @permanent_tasks.include?(task)
590
+ end
591
+
592
+ # Removes a task from the set of permanent tasks
593
+ #
594
+ # This does not remove the event from the plan. In plans being executed,
595
+ # the removal will be done by garabage collection. In plans used as data
596
+ # structures, either use {#static_garbage_collect} or remove the event
597
+ # directly with {#remove_task} or {#remove_free_event}
598
+ #
599
+ # @see add_permanent_event permanent_event?
600
+ def unmark_permanent_task(task)
601
+ if @permanent_tasks.delete?(task.to_task)
602
+ notify_task_status_change(task, :normal)
603
+ end
604
+ end
605
+
606
+ # Mark an event as permanent, optionally adding to the plan
607
+ #
608
+ # Permanent events are protected against garbage collection
609
+ def add_permanent_event(event)
610
+ event = normalize_add_arguments([event]).first
611
+ return if permanent_events.include?(event)
612
+ add([event])
613
+ permanent_events << event
614
+ notify_event_status_change(event, :permanent)
615
+ event
616
+ end
617
+
618
+ # True if the given event is registered as a permanent event on self
619
+ def permanent_event?(generator)
620
+ @permanent_events.include?(generator)
621
+ end
622
+
623
+ # Removes a task from the set of permanent tasks
624
+ #
625
+ # This does not remove the event from the plan. In plans being executed,
626
+ # the removal will be done by garabage collection. In plans used as data
627
+ # structures, either use {#static_garbage_collect} or remove the event
628
+ # directly with {#remove_task} or {#remove_free_event}
629
+ #
630
+ # @see add_permanent_event permanent_event?
631
+ def unmark_permanent_event(event)
632
+ if @permanent_events.delete?(event.to_event)
633
+ notify_event_status_change(event, :normal)
634
+ end
635
+ end
636
+
637
+ # @api private
638
+ #
639
+ # Perform notifications related to the status change of a task
640
+ def notify_task_status_change(task, status)
641
+ if services = plan_services[task]
642
+ services.each { |s| s.notify_task_status_change(status) }
643
+ end
644
+ log(:task_status_change, task, status)
645
+ end
646
+
647
+ # @api private
648
+ #
649
+ # Perform notifications related to the status change of an event
650
+ def notify_event_status_change(event, status)
651
+ log(:event_status_change, event, status)
652
+ end
653
+
654
+ def edit
655
+ if block_given?
656
+ yield
657
+ end
658
+ end
659
+
660
+ # True if this plan owns the given object, i.e. if all the owners of the
661
+ # object are also owners of the plan.
662
+ def owns?(object)
663
+ (object.owners - owners).empty?
664
+ end
665
+
666
+ def force_replace_task(from, to)
667
+ handle_force_replace(from, to) do
668
+ from.replace_by(to)
669
+ end
670
+ end
671
+
672
+ def force_replace(from, to)
673
+ handle_force_replace(from, to) do
674
+ from.replace_subplan_by(to)
675
+ end
676
+ end
677
+
678
+ def handle_force_replace(from, to)
679
+ if !from.plan
680
+ raise ArgumentError, "#{from} has been removed from plan, cannot use as source in a replacement"
681
+ elsif !to.plan
682
+ raise ArgumentError, "#{to} has been removed from plan, cannot use as target in a replacement"
683
+ elsif from.plan != self
684
+ raise ArgumentError, "trying to replace #{from} but its plan is #{from.plan}, expected #{self}"
685
+ elsif to.plan.template?
686
+ add(to)
687
+ elsif to.plan != self
688
+ raise ArgumentError, "trying to replace #{to} but its plan is #{to.plan}, expected #{self}"
689
+ elsif from == to
690
+ return
691
+ end
692
+
693
+ # Swap the subplans of +from+ and +to+
694
+ yield(from, to)
695
+
696
+ if mission_task?(from)
697
+ add_mission_task(to)
698
+ replaced(from, to)
699
+ unmark_mission_task(from)
700
+ elsif permanent_task?(from)
701
+ add_permanent_task(to)
702
+ replaced(from, to)
703
+ unmark_permanent_task(from)
704
+ else
705
+ add(to)
706
+ replaced(from, to)
707
+ end
708
+ end
709
+
710
+ def handle_replace(from, to) # :nodoc:
711
+ handle_force_replace(from, to) do
712
+ # Check that +to+ is valid in all hierarchy relations where +from+ is a child
713
+ if !to.fullfills?(*from.fullfilled_model)
714
+ models = from.fullfilled_model.first
715
+ missing = models.find_all do |m|
716
+ !to.fullfills?(m)
717
+ end
718
+ if missing.empty?
719
+ mismatching_argument = from.fullfilled_model.last.find do |key, expected_value|
720
+ (value = to.arguments[key]) && (expected_value != value)
721
+ end
722
+ raise InvalidReplace.new(from, to), "argument mismatch for #{mismatching_argument.first}"
723
+ else
724
+ raise InvalidReplace.new(from, to), "missing provided models #{missing.map(&:name).join(", ")}"
725
+ end
726
+ end
727
+
728
+ # Swap the subplans of +from+ and +to+
729
+ yield(from, to)
730
+ end
731
+ end
732
+
733
+ # Replace the task +from+ by +to+ in all relations +from+ is part of
734
+ # (including events).
735
+ #
736
+ # See also #replace
737
+ def replace_task(from, to)
738
+ handle_replace(from, to) do
739
+ from.replace_by(to)
740
+ end
741
+ end
742
+
743
+ # Replace +from+ by +to+ in the plan, in all relations in which +from+
744
+ # and its events are /children/. It therefore replaces the subplan
745
+ # generated by +from+ (i.e. +from+ and all the tasks/events that can be
746
+ # reached by following the task and event relations) by the subplan
747
+ # generated by +to+.
748
+ #
749
+ # See also #replace_task
750
+ def replace(from, to)
751
+ handle_replace(from, to) do
752
+ from.replace_subplan_by(to)
753
+ end
754
+ end
755
+
756
+ # Register a new plan service on this plan
757
+ def add_plan_service(service)
758
+ if service.task.plan != self
759
+ raise "trying to register a plan service on #{self} for #{service.task}, which is included in #{service.task.plan}"
760
+ end
761
+
762
+ set = (plan_services[service.task] ||= Set.new)
763
+ if !set.include?(service)
764
+ set << service
765
+ end
766
+ self
767
+ end
768
+
769
+ # Deregisters a plan service from this plan
770
+ def remove_plan_service(service)
771
+ if set = plan_services[service.task]
772
+ set.delete(service)
773
+ if set.empty?
774
+ plan_services.delete(service.task)
775
+ end
776
+ end
777
+ end
778
+
779
+ # Change the actual task a given plan service is representing
780
+ def move_plan_service(service, new_task)
781
+ return if new_task == service.task
782
+
783
+ remove_plan_service(service)
784
+ service.task = new_task
785
+ add_plan_service(service)
786
+ end
787
+
788
+ # Find all the defined plan services for a given task
789
+ def find_all_plan_services(task)
790
+ plan_services[task] || Array.new
791
+ end
792
+
793
+ # If at least one plan service is defined for +task+, returns one of
794
+ # them. Otherwise, returns nil.
795
+ def find_plan_service(task)
796
+ if set = plan_services[task]
797
+ set.find { true }
798
+ end
799
+ end
800
+
801
+ # Replace subgraphs by another in the plan
802
+ #
803
+ # It copies relations that are not within the keys in task_mappings and
804
+ # event_mappings to the corresponding task/events. The targets might be
805
+ # nil, in which case the relations involving the source will be simply
806
+ # ignored.
807
+ #
808
+ # If needed, instead of providing an object as target, one can provide a
809
+ # resolver object which will be called with #call and the source, The
810
+ # resolver should be given as a second element of a pair, e.g.
811
+ #
812
+ # source => [nil, #call]
813
+ #
814
+ def replace_subplan(task_mappings, event_mappings, task_children: true, event_children: true)
815
+ new_relations, removed_relations =
816
+ compute_subplan_replacement(task_mappings, each_task_relation_graph,
817
+ child_objects: task_children)
818
+ apply_replacement_operations(new_relations, removed_relations)
819
+
820
+ new_relations, removed_relations =
821
+ compute_subplan_replacement(event_mappings, each_event_relation_graph,
822
+ child_objects: event_children)
823
+ apply_replacement_operations(new_relations, removed_relations)
824
+ end
825
+
826
+ def compute_subplan_replacement(mappings, relation_graphs, child_objects: true)
827
+ new_relations, removed_relations = Array.new, Array.new
828
+ relation_graphs.each do |graph|
829
+ next if graph.strong?
830
+
831
+ resolved_mappings = Hash.new
832
+ mappings.each do |obj, (mapped_obj, mapped_obj_resolver)|
833
+ next if !mapped_obj && !mapped_obj_resolver
834
+
835
+ graph.each_in_neighbour(obj) do |parent|
836
+ next if mappings.has_key?(parent)
837
+ if !graph.copy_on_replace?
838
+ removed_relations << [graph, parent, obj]
839
+ end
840
+ if !mapped_obj
841
+ mapped_obj = mapped_obj_resolver.call(obj)
842
+ resolved_mappings[obj] = mapped_obj
843
+ end
844
+ new_relations << [graph, parent, mapped_obj, graph.edge_info(parent, obj)]
845
+ end
846
+
847
+ next if !child_objects
848
+ graph.each_out_neighbour(obj) do |child|
849
+ next if mappings.has_key?(child)
850
+ if !graph.copy_on_replace?
851
+ removed_relations << [graph, obj, child]
852
+ end
853
+ if !mapped_obj
854
+ mapped_obj = mapped_obj_resolver.call(obj)
855
+ resolved_mappings[obj] = mapped_obj
856
+ end
857
+ new_relations << [graph, mapped_obj, child, graph.edge_info(obj, child)]
858
+ end
859
+ end
860
+ mappings.merge!(resolved_mappings)
861
+ end
862
+ return new_relations, removed_relations
863
+ end
864
+
865
+ def apply_replacement_operations(new_relations, removed_relations)
866
+ removed_relations.each do |graph, parent, child|
867
+ graph.remove_relation(parent, child)
868
+ end
869
+ new_relations.each do |graph, parent, child, info|
870
+ graph.add_relation(parent, child, info)
871
+ end
872
+ end
873
+
874
+ # Hook called when +replacing_task+ has replaced +replaced_task+ in this plan
875
+ def replaced(replaced_task, replacing_task)
876
+ # Make the PlanService object follow the replacement
877
+ if services = plan_services.delete(replaced_task)
878
+ services.each do |srv|
879
+ srv.task = replacing_task
880
+ (plan_services[replacing_task] ||= Set.new) << srv
881
+ end
882
+ end
883
+ end
884
+
885
+ # @api private
886
+ #
887
+ # Registers a task object in this plan
888
+ #
889
+ # It is for Roby internal usage only, for the creation of template
890
+ # plans. Use {#add}.
891
+ def register_task(task)
892
+ task.plan = self
893
+ tasks << task
894
+ task_index.add(task)
895
+ task_events.merge(task.each_event)
896
+ end
897
+
898
+ # @api private
899
+ #
900
+ # Registers a task object in this plan
901
+ #
902
+ # It is for Roby internal usage only, for the creation of template
903
+ # plans. Use {#add}.
904
+ def register_event(event)
905
+ event.plan = self
906
+ if event.root_object?
907
+ free_events << event
908
+ else
909
+ task_events << event
910
+ end
911
+ end
912
+
913
+ # call-seq:
914
+ # plan.add(task) => plan
915
+ # plan.add(event) => plan
916
+ # plan.add([task, event, task2, ...]) => plan
917
+ # plan.add([t1, t2, ...]) => plan
918
+ #
919
+ # Adds the subplan of the given tasks and events into the plan.
920
+ #
921
+ # That means that it adds the listed tasks/events and the task/events
922
+ # that are reachable through any relations).
923
+ def add(objects)
924
+ is_scalar = objects.respond_to?(:each)
925
+ objects = normalize_add_arguments(objects)
926
+
927
+ plans = Set.new
928
+ objects.each do |plan_object|
929
+ p = plan_object.plan
930
+ next if p == self
931
+ if plan_object.removed_at
932
+ raise ArgumentError, "cannot add #{plan_object} in #{self}, it has been removed from the plan"
933
+ elsif !p
934
+ raise InternalError, "there seem to be an inconsistency, #{plan_object}#plan is nil but #removed_at is not set"
935
+ elsif p.empty?
936
+ raise InternalError, "there seem to be an inconsistency, #{plan_object} is associated with #{p} but #{p} is empty"
937
+ elsif !p.template?
938
+ raise ModelViolation, "cannot add #{plan_object} in #{self}, it is already included in #{p}"
939
+ end
940
+ plans << p
941
+ end
942
+
943
+ plans.each do |p|
944
+ merge!(p)
945
+ end
946
+
947
+ if is_scalar
948
+ objects.first
949
+ else objects
950
+ end
951
+ end
952
+
953
+ # @api private
954
+ #
955
+ # A trigger created by {Plan#add_trigger}
956
+ class Trigger
957
+ # The query that is being watched
958
+ attr_reader :query
959
+ # The block that will be called if {#query} matches
960
+ attr_reader :block
961
+
962
+ def initialize(query, block)
963
+ @query = query.query
964
+ @block = block
965
+ end
966
+
967
+ # Whether self would be triggering on task
968
+ #
969
+ # @param [Roby::Task] task
970
+ # @return [Boolean]
971
+ def ===(task)
972
+ query === task
973
+ end
974
+
975
+ # Lists the tasks that match the query
976
+ #
977
+ # @param [Plan] plan
978
+ # @yieldparam [Roby::Task] task tasks that match {#query}
979
+ def each(plan, &block)
980
+ query.plan = plan
981
+ query.reset
982
+ query.each(&block)
983
+ end
984
+
985
+ # Call the trigger's observer for the given task
986
+ def call(task)
987
+ block.call(task)
988
+ end
989
+ end
990
+
991
+ # Add a trigger
992
+ #
993
+ # This registers a notification: the given block will be called for each
994
+ # new task that match the given query object. It yields right away for
995
+ # the tasks that are already in the plan
996
+ #
997
+ # @param [#===] query_object the object against which tasks are tested.
998
+ # Tasks for which #=== returns true are yield to the block
999
+ # @yieldparam [Roby::Task] task the task that matched the query object
1000
+ # @return [Object] an ID object that can be used in {#remove_trigger}
1001
+ def add_trigger(query_object, &block)
1002
+ tr = Trigger.new(query_object, block)
1003
+ triggers << tr
1004
+ tr.each(self) do |t|
1005
+ tr.call(t)
1006
+ end
1007
+ tr
1008
+ end
1009
+
1010
+ # Removes a trigger
1011
+ #
1012
+ # @param [Object] trigger the trigger to be removed. This is the return value of
1013
+ # the corresponding {#add_trigger} call
1014
+ # @return [void]
1015
+ def remove_trigger(trigger)
1016
+ triggers.delete(trigger)
1017
+ nil
1018
+ end
1019
+
1020
+ # Creates a new transaction and yields it. Ensures that the transaction
1021
+ # is discarded if the block returns without having committed it.
1022
+ def in_transaction
1023
+ yield(trsc = Transaction.new(self))
1024
+
1025
+ ensure
1026
+ if trsc && !trsc.finalized?
1027
+ trsc.discard_transaction
1028
+ end
1029
+ end
122
1030
 
123
- # Calls the given block in the execution thread of this plan's engine.
124
- # If there is no engine attached to this plan, yields immediately
1031
+ # Hook called when a new transaction has been built on top of this plan
1032
+ def added_transaction(trsc)
1033
+ end
1034
+
1035
+ # Removes the transaction +trsc+ from the list of known transactions
1036
+ # built on this plan
1037
+ def remove_transaction(trsc)
1038
+ transactions.delete(trsc)
1039
+ end
1040
+
1041
+ # @api private
125
1042
  #
126
- # See ExecutionEngine#execute
127
- def execute(&block)
128
- if engine
129
- engine.execute(&block)
130
- else
131
- yield
1043
+ # Default set of graphs that should be discovered by
1044
+ # {#compute_useful_tasks}
1045
+ def default_useful_task_graphs
1046
+ each_task_relation_graph.find_all { |g| g.root_relation? && !g.weak? }
1047
+ end
1048
+
1049
+ # @api private
1050
+ #
1051
+ # Compute the subplan that is useful for a given set of tasks
1052
+ #
1053
+ # @param [Set<Roby::Task>] seeds the root "useful" tasks
1054
+ # @param [Array<Relations::BidirectionalDirectedAdjancencyGraph>] graphs the
1055
+ # graphs through which "usefulness" is propagated
1056
+ def compute_useful_tasks(seeds, graphs: default_useful_task_graphs)
1057
+ seeds = seeds.to_set
1058
+ visitors = graphs.map do |g|
1059
+ [g, RGL::DFSVisitor.new(g), seeds.dup]
132
1060
  end
1061
+
1062
+ result = seeds.dup
1063
+
1064
+ has_pending_seeds = true
1065
+ while has_pending_seeds
1066
+ has_pending_seeds = false
1067
+ visitors.each do |graph, visitor, seeds|
1068
+ next if seeds.empty?
1069
+
1070
+ new_seeds = Array.new
1071
+ seeds.each do |vertex|
1072
+ if !visitor.finished_vertex?(vertex) && graph.has_vertex?(vertex)
1073
+ graph.depth_first_visit(vertex, visitor) do |v|
1074
+ yield(v) if block_given?
1075
+ new_seeds << v
1076
+ end
1077
+ end
1078
+ end
1079
+ if !new_seeds.empty?
1080
+ has_pending_seeds = true
1081
+ result.merge(new_seeds)
1082
+ visitors.each { |g, _, s| s.merge(new_seeds) if g != graph }
1083
+ end
1084
+ seeds.clear
1085
+ end
1086
+ end
1087
+
1088
+ result
133
1089
  end
134
1090
 
135
- # call-seq:
136
- # plan.partition_event_task(objects) => events, tasks
137
- #
138
- def partition_event_task(objects)
139
- if objects.respond_to?(:to_task) then return nil, [objects.to_task]
140
- elsif objects.respond_to?(:to_event) then return [objects.to_event], nil
141
- elsif !objects.respond_to?(:each)
142
- raise TypeError, "expecting a task, event, or a collection of tasks and events, got #{objects}"
143
- end
144
-
145
- evts, tasks = objects.partition do |o|
146
- if o.respond_to?(:to_event) then true
147
- elsif o.respond_to?(:to_task) then false
148
- else raise ArgumentError, "found #{o || 'nil'} which is neither a task nor an event"
149
- end
150
- end
151
- return evts, tasks
152
- end
153
-
154
- # If this plan is a toplevel plan, returns self. If it is a
155
- # transaction, returns the underlying plan
156
- def real_plan
157
- ret = self
158
- while ret.respond_to?(:plan)
159
- ret = ret.plan
160
- end
161
- ret
162
- end
163
-
164
- # Returns the set of stacked transaction, starting at +self+
165
- def transaction_stack
166
- plan_chain = [self]
167
- while plan_chain.last.respond_to?(:plan)
168
- plan_chain << plan_chain.last.plan
1091
+ def locally_useful_roots(with_transactions: true)
1092
+ # Create the set of tasks which must be kept as-is
1093
+ seeds = @mission_tasks | @permanent_tasks
1094
+ if with_transactions
1095
+ for trsc in transactions
1096
+ seeds.merge trsc.proxy_tasks.keys.to_set
1097
+ end
169
1098
  end
170
- plan_chain
1099
+ seeds
171
1100
  end
172
1101
 
173
- # Inserts a new mission in the plan.
174
- #
175
- # In the plan manager, missions are the tasks which constitute the
176
- # robot's goal. This is the base for two things:
177
- # * if a mission fails, the MissionFailedError is raised
178
- # * the mission and all the tasks and events which are useful for it,
179
- # are not removed automatically by the garbage collection mechanism.
180
- # A task or event is <b>useful</b> if it is part of the child subgraph
181
- # of the mission, i.e. if there is a path in the relation graphs where
182
- # the mission is the source and the task is the target.
183
- def add_mission(task)
184
- return if @missions.include?(task)
185
-
186
- @missions << task
187
- add(task)
188
- task.mission = true if task.self_owned?
189
- added_mission(task)
190
- self
191
- end
192
- def insert(task) # :nodoc:
193
- Roby.warn_deprecated "#insert has been replaced by #add_mission"
194
- add_mission(task)
195
- end
196
- # Hook called when +tasks+ have been inserted in this plan
197
- def added_mission(tasks)
198
- super if defined? super
199
- if respond_to?(:inserted)
200
- Roby.warn_deprecated "the #inserted hook has been replaced by #added_mission"
201
- inserted(tasks)
202
- end
203
- end
204
- # Checks if +task+ is a mission of this plan
205
- def mission?(task); @missions.include?(task) end
206
-
207
- def remove_mission(task) # :nodoc:
208
- Roby.warn_deprecated "#remove_mission renamed #unmark_mission"
209
- unmark_mission(task)
210
- end
211
-
212
- # Removes the task in +tasks+ from the list of missions
213
- def unmark_mission(task)
214
- @missions.delete(task)
215
- add(task)
216
- task.mission = false if task.self_owned?
217
-
218
- unmarked_mission(task)
219
- self
220
- end
221
- # Hook called when +tasks+ have been discarded from this plan
222
- def unmarked_mission(task)
223
- super if defined? super
224
- if respond_to?(:removed_mission)
225
- Roby.warn_deprecated "the #removed_mission hook has been replaced by #unmarked_mission"
226
- removed_mission(task)
227
- end
228
- if respond_to?(:discarded)
229
- Roby.warn_deprecated "the #discarded hook has been replaced by #unmarked_mission"
230
- discarded(task)
231
- end
232
- end
233
- def discard(task) # :nodoc:
234
- Roby.warn_deprecated "#discard has been replaced by #unmark_mission"
235
- unmark_mission(task)
236
- end
237
-
238
- # Adds +object+ in the list of permanent tasks. Permanent tasks are
239
- # tasks that are not to be subject to the plan's garbage collection
240
- # mechanism (i.e. they will not be removed even though they are not
241
- # directly linked to a mission).
242
- #
243
- # #object is at the same time added in the plan, meaning that all the
244
- # tasks and events related to it are added in the plan as well. See
245
- # #add.
246
- #
247
- # Unlike missions, the failure of a permanent task does not constitute
248
- # an error.
249
- #
250
- # See also #unmark_permanent and #permanent?
251
- def add_permanent(object)
252
- if object.kind_of?(Task)
253
- @permanent_tasks << object
1102
+ def locally_useful_tasks
1103
+ compute_useful_tasks(locally_useful_roots)
1104
+ end
1105
+
1106
+ def useful_tasks(with_transactions: true)
1107
+ compute_useful_tasks(locally_useful_roots(with_transactions: with_transactions))
1108
+ end
1109
+
1110
+ def unneeded_tasks
1111
+ tasks - useful_tasks
1112
+ end
1113
+
1114
+ def local_tasks
1115
+ task_index.self_owned
1116
+ end
1117
+
1118
+ def quarantined_tasks
1119
+ tasks.find_all(&:quarantined?)
1120
+ end
1121
+
1122
+ def remote_tasks
1123
+ if local_tasks = task_index.self_owned
1124
+ tasks - local_tasks
254
1125
  else
255
- @permanent_events << object
1126
+ tasks
1127
+ end
1128
+ end
1129
+
1130
+ # Computes the set of useful tasks and checks that +task+ is in it.
1131
+ # This is quite slow. It is here for debugging purposes. Do not use it
1132
+ # in production code
1133
+ def useful_task?(task)
1134
+ tasks.include?(task) && !unneeded_tasks.include?(task)
1135
+ end
1136
+
1137
+ class UsefulFreeEventVisitor < RGL::DFSVisitor
1138
+ attr_reader :useful_free_events, :task_events, :stack
1139
+ def initialize(graph, task_events, permanent_events)
1140
+ super(graph)
1141
+ @task_events = task_events
1142
+ @useful_free_events = permanent_events.dup
1143
+ @useful = false
1144
+ end
1145
+
1146
+ def useful?
1147
+ @useful
1148
+ end
1149
+
1150
+ def handle_examine_edge(u, v)
1151
+ if task_events.include?(v) || useful_free_events.include?(v)
1152
+ color_map[v] = :BLACK
1153
+ @useful = true
1154
+ end
256
1155
  end
257
- add(object)
258
- self
259
- end
260
1156
 
261
- def permanent(object) # :nodoc:
262
- Roby.warn_deprecated "#permanent has been replaced by #add_permanent"
263
- add_permanent(object)
1157
+ def follow_edge?(u, v)
1158
+ !task_events.include?(v)
1159
+ end
264
1160
  end
265
1161
 
266
- # Removes +object+ from the list of permanent objects. Permanent objects
267
- # are protected from the plan's garbage collection. This does not remove
268
- # the task/event itself from the plan.
1162
+ # @api private
1163
+ #
1164
+ # Compute the set of events that are "useful" to the plan.
269
1165
  #
270
- # See also #add_permanent and #permanent?
271
- def unmark_permanent(object)
272
- @permanent_tasks.delete(object)
273
- @permanent_events.delete(object)
1166
+ # It contains every event that is connected to an event in
1167
+ # {#permanent_events} or to an event on a task in the plan
1168
+ #
1169
+ # @return [Set<EventGenerator>]
1170
+ def compute_useful_free_events
1171
+ # Quick path for a very common case
1172
+ return Set.new if free_events.empty?
1173
+
1174
+ graphs = each_event_relation_graph.
1175
+ find_all { |g| g.root_relation? && !g.weak? }
1176
+
1177
+ seen = Set.new
1178
+ result = permanent_events.dup
1179
+ pending_events = free_events.to_a
1180
+ while !pending_events.empty?
1181
+ # This basically computes the subplan that contains "seed" and
1182
+ # determines if it is useful or not
1183
+ seed = pending_events.shift
1184
+ next if seen.include?(seed)
1185
+
1186
+ visitors = Array.new
1187
+ graphs.each do |g|
1188
+ visitors << [g, UsefulFreeEventVisitor.new(g, task_events, permanent_events), [seed].to_set]
1189
+ visitors << [g.reverse, UsefulFreeEventVisitor.new(g.reverse, task_events, permanent_events), [seed].to_set]
1190
+ end
1191
+
1192
+ component = [seed].to_set
1193
+ has_pending_seeds = true
1194
+ while has_pending_seeds
1195
+ has_pending_seeds = false
1196
+ visitors.each do |graph, visitor, seeds|
1197
+ next if seeds.empty?
1198
+
1199
+ new_seeds = Array.new
1200
+ seeds.each do |vertex|
1201
+ if !visitor.finished_vertex?(vertex) && graph.has_vertex?(vertex)
1202
+ graph.depth_first_visit(vertex, visitor) { |v| new_seeds << v }
1203
+ end
1204
+ end
1205
+ if !new_seeds.empty?
1206
+ has_pending_seeds = true
1207
+ component.merge(new_seeds)
1208
+ visitors.each { |g, _, s| s.merge(new_seeds) if g != graph }
1209
+ end
1210
+ seeds.clear
1211
+ end
1212
+ end
1213
+ seen.merge(component)
1214
+ if visitors.any? { |_, v, _| v.useful? }
1215
+ result.merge(component)
1216
+ end
1217
+ end
1218
+
1219
+ result
274
1220
  end
275
1221
 
276
- def auto(obj) # :nodoc:
277
- Roby.warn_deprecated "#auto has been replaced by #unmark_permanent"
278
- unmark_permanent(obj)
1222
+ # Computes the set of events that are useful in the plan Events are
1223
+ # 'useful' when they are chained to a task.
1224
+ def useful_events
1225
+ compute_useful_free_events
279
1226
  end
280
1227
 
281
- # True if +obj+ is neither a permanent task nor a permanent object.
282
- #
283
- # See also #add_permanent and #unmark_permanent
284
- def permanent?(obj); @permanent_tasks.include?(obj) || @permanent_events.include?(obj) end
1228
+ # The set of events that can be removed from the plan
1229
+ def unneeded_events
1230
+ useful_events = self.useful_events
285
1231
 
286
- def edit
287
- if block_given?
288
- Roby.synchronize do
289
- yield
290
- end
291
- end
292
- end
1232
+ result = (free_events - useful_events)
1233
+ result.delete_if do |ev|
1234
+ transactions.any? { |trsc| trsc.find_local_object_for_event(ev) }
1235
+ end
1236
+ result
1237
+ end
293
1238
 
294
- # True if this plan owns the given object, i.e. if all the owners of the
295
- # object are also owners of the plan.
296
- def owns?(object)
297
- (object.owners - owners).empty?
298
- end
299
-
300
- # Remove all tasks
301
- def clear
302
- @known_tasks.each { |t| t.clear_relations }
303
- @known_tasks.clear
304
- @free_events.each { |e| e.clear_relations }
305
- @free_events.clear
306
- @missions.clear
307
- @permanent_tasks.clear
308
- @permanent_events.clear
309
- end
310
-
311
- def handle_replace(from, to) # :nodoc:
312
- return if from == to
313
-
314
- # Check that +to+ is valid in all hierarchy relations where +from+ is a child
315
- if !to.fullfills?(*from.fullfilled_model)
316
- raise InvalidReplace.new(from, to), "task #{to} does not fullfill #{from.fullfilled_model}"
317
- end
318
-
319
- # Check that +to+ is in the same execution state than +from+
320
- if !to.compatible_state?(from)
321
- raise InvalidReplace.new(from, to), "cannot replace #{from} by #{to} as their state is incompatible: from is #{from.running?} and to is #{to.running?}"
322
- end
323
-
324
- # Swap the subplans of +from+ and +to+
325
- yield(from, to)
326
-
327
- if mission?(from)
328
- unmark_mission(from)
329
- add_mission(to)
330
- elsif permanent?(from)
331
- unmark_permanent(from)
332
- add_permanent(to)
333
- else
334
- add(to)
335
- end
336
- replaced(from, to)
337
- end
1239
+ # The number of tasks
1240
+ def num_tasks
1241
+ tasks.size
1242
+ end
338
1243
 
339
- # Replace the task +from+ by +to+ in all relations +from+ is part of
340
- # (including events).
1244
+ # The number of events that are not task events
1245
+ def num_free_events
1246
+ free_events.size
1247
+ end
1248
+
1249
+ # The number of events, both free and task events
1250
+ def num_events
1251
+ task_events.size + free_events.size
1252
+ end
1253
+
1254
+ # Tests whether a task is present in this plan
1255
+ def has_task?(task)
1256
+ tasks.include?(task)
1257
+ end
1258
+
1259
+ # Tests whether a task event is present in this plan
1260
+ def has_task_event?(generator)
1261
+ task_events.include?(generator)
1262
+ end
1263
+
1264
+ # Tests whether a free event is present in this plan
1265
+ def has_free_event?(generator)
1266
+ free_events.include?(generator)
1267
+ end
1268
+
1269
+ # @deprecated use the more specific {#has_task?}, {#has_free_event?} or
1270
+ # {#has_task_event?} instead
1271
+ def include?(object)
1272
+ Roby.warn_deprecated "Plan#include? is deprecated, use one of the more specific #has_task? #has_task_event? and #has_free_event?"
1273
+ has_free_event?(object) || has_task_event?(object) || has_task?(object)
1274
+ end
1275
+
1276
+ # Count of tasks in this plan
1277
+ def size
1278
+ Roby.warn_deprecated "Plan#size is deprecated, use #num_tasks instead"
1279
+ @tasks.size
1280
+ end
1281
+ # Returns true if there is no task in this plan
1282
+ def empty?; @tasks.empty? && @free_events.empty? end
1283
+ # Iterates on all tasks
341
1284
  #
342
- # See also #replace
343
- def replace_task(from, to)
344
- handle_replace(from, to) do
345
- from.replace_by(to)
346
- end
347
- end
1285
+ # @yieldparam [Task] task
1286
+ def each_task
1287
+ return enum_for(__method__) if !block_given?
1288
+ @tasks.each { |t| yield(t) }
1289
+ end
1290
+
1291
+ # Returns +object+ if object is a plan object from this plan, or if
1292
+ # it has no plan yet (in which case it is added to the plan first).
1293
+ # Otherwise, raises ArgumentError.
1294
+ #
1295
+ # This method is provided for consistency with Transaction#[]
1296
+ def [](object, create = true)
1297
+ if object.plan == self
1298
+ object
1299
+ elsif !object.finalized? && object.plan.template?
1300
+ add(object)
1301
+ object
1302
+ elsif object.finalized? && create
1303
+ raise ArgumentError, "#{object} is has been finalized, and can't be reused"
1304
+ else
1305
+ raise ArgumentError, "#{object} is not from #{self}"
1306
+ end
1307
+ end
348
1308
 
349
- # Replace +from+ by +to+ in the plan, in all relations in which +from+
350
- # and its events are /children/. It therefore replaces the subplan
351
- # generated by +from+ (i.e. +from+ and all the tasks/events that can be
352
- # reached by following the task and event relations) by the subplan
353
- # generated by +to+.
1309
+ def self.can_gc?(task)
1310
+ if task.starting? then true # wait for the task to be started before deciding ...
1311
+ elsif task.running? && !task.finishing?
1312
+ task.event(:stop).controlable?
1313
+ else true
1314
+ end
1315
+ end
1316
+
1317
+ # @api private
354
1318
  #
355
- # See also #replace_task
356
- def replace(from, to)
357
- handle_replace(from, to) do
358
- from.replace_subplan_by(to)
359
- end
360
- end
1319
+ # Perform sanity checks on a plan object that will be finalized
1320
+ def verify_plan_object_finalization_sanity(object)
1321
+ if !object.root_object?
1322
+ raise ArgumentError, "cannot remove #{object} which is a non-root object"
1323
+ elsif object.plan != self
1324
+ if !object.plan
1325
+ if object.removed_at
1326
+ if PlanObject.debug_finalization_place?
1327
+ raise ArgumentError, "#{object} has already been removed from its plan\n" +
1328
+ "Removed at\n #{object.removed_at.join("\n ")}"
1329
+ else
1330
+ raise ArgumentError, "#{object} has already been removed from its plan. Set PlanObject.debug_finalization_place to true to get the backtrace of where (in the code) the object got finalized"
1331
+ end
1332
+ else
1333
+ raise ArgumentError, "#{object} has never been included in this plan"
1334
+ end
1335
+ end
1336
+ raise ArgumentError, "#{object} is not in #{self}: #plan == #{object.plan}"
1337
+ end
1338
+ end
1339
+
1340
+ def finalize_task(task, timestamp = nil)
1341
+ verify_plan_object_finalization_sanity(task)
1342
+ if (task.plan != self) && has_task?(task)
1343
+ raise ArgumentError, "#{task} is included in #{self} but #plan == #{task.plan}"
1344
+ end
1345
+
1346
+ if services = plan_services.delete(task)
1347
+ services.each(&:finalized!)
1348
+ end
361
1349
 
362
- # Hook called when +to+ has replaced +from+ in this plan
363
- def replaced(from, to); super if defined? super end
1350
+ # Remove relations first. This is needed by transaction since
1351
+ # removing relations may need wrapping some new task, and in
1352
+ # that case these new task will be discovered as well
1353
+ task.clear_relations(remove_internal: true)
1354
+ task.mission = false
364
1355
 
365
- # Check that this is an executable plan. This is always true for
366
- # plain Plan objects and false for transcations
367
- def executable?; true end
1356
+ for ev in task.bound_events.each_value
1357
+ finalized_event(ev)
1358
+ end
1359
+ finalized_task(task)
368
1360
 
369
- def discover(objects) # :nodoc:
370
- Roby.warn_deprecated "#discover has been replaced by #add"
371
- add(objects)
1361
+ for ev in task.bound_events.each_value
1362
+ ev.finalized!(timestamp)
1363
+ end
1364
+ task.finalized!(timestamp)
372
1365
  end
373
1366
 
374
- # call-seq:
375
- # plan.add(task) => plan
376
- # plan.add(event) => plan
377
- # plan.add([task, event, task2, ...]) => plan
378
- # plan.add([t1, t2, ...]) => plan
379
- #
380
- # Adds the subplan of the given tasks and events into the plan.
381
- #
382
- # That means that it adds the listed tasks/events and the task/events
383
- # that are reachable through any relations). The #added_events and
384
- # #added_tasks hooks are called for the objects that were not in
385
- # the plan.
386
- def add(objects)
387
- event_seeds, tasks = partition_event_task(objects)
388
- event_seeds = (event_seeds || ValueSet.new).to_value_set
389
-
390
- if tasks
391
- tasks = tasks.to_value_set
392
- new_tasks = useful_task_component(nil, tasks, tasks)
393
- unless new_tasks.empty?
394
- old_task_events = task_events.dup
395
- new_tasks = add_task_set(new_tasks)
396
- event_seeds.merge(task_events - old_task_events)
397
- end
398
- end
399
-
400
- if !event_seeds.empty?
401
- events = event_seeds.dup
402
-
403
- # now, we include the set of free events that are linked to
404
- # +new_tasks+ in +events+
405
- EventStructure.each_root_relation do |rel|
406
- components = rel.generated_subgraphs(event_seeds, false)
407
- components.concat rel.reverse.generated_subgraphs(event_seeds, false)
408
- for c in components
409
- events.merge(c.to_value_set)
410
- end
411
- end
412
-
413
- add_event_set(events - task_events - free_events)
414
- end
415
-
416
- self
417
- end
418
-
419
- # Add +events+ to the set of known events and call added_events
420
- # for the new events
421
- #
422
- # This is for internal use, use #add instead
423
- def add_event_set(events)
424
- events = events.difference(free_events)
425
- events.delete_if do |e|
426
- if !e.root_object?
427
- true
428
- else
429
- e.plan = self
430
- false
431
- end
432
- end
433
-
434
- unless events.empty?
435
- free_events.merge(events)
436
- added_events(events)
437
- end
438
-
439
- events
440
- end
441
-
442
- # Add +tasks+ to the set of known tasks and call added_tasks for
443
- # the new tasks
444
- #
445
- # This is for internal use, use #add instead
446
- def add_task_set(tasks)
447
- tasks = tasks.difference(known_tasks)
448
- for t in tasks
449
- t.plan = self
450
- task_events.merge t.bound_events.values.to_value_set
451
- task_index.add t
452
- end
453
- known_tasks.merge tasks
454
- added_tasks(tasks)
455
-
456
- for t in tasks
457
- t.instantiate_model_event_relations
458
- end
459
- tasks
460
- end
461
-
462
- def added_tasks(tasks)
463
- if respond_to?(:discovered)
464
- Roby.warn_deprecated "the #discovered hook is deprecated, use #added_tasks instead"
465
- discovered(tasks)
466
- end
467
- if respond_to?(:discovered_tasks)
468
- Roby.warn_deprecated "the #discovered_tasks hook is deprecated, use #added_tasks instead"
469
- discovered_tasks(tasks)
470
- end
471
-
472
- if engine
473
- engine.event_ordering.clear
474
- end
475
- super if defined? super
476
- end
477
-
478
- # Hook called when new events have been discovered in this plan
479
- def added_events(events)
480
- if engine
481
- engine.event_ordering.clear
482
- end
483
-
484
- if respond_to?(:discovered_events)
485
- Roby.warn_deprecated "the #discovered_events hook has been replaced by #added_events"
486
- discovered_events(events)
487
- end
488
- super if defined? super
489
- end
1367
+ def finalize_event(event, timestamp = nil)
1368
+ verify_plan_object_finalization_sanity(event)
1369
+ if (event.plan != self) && has_free_event?(event)
1370
+ raise ArgumentError, "#{event} is included in #{self} but #plan == #{event.plan}"
1371
+ end
490
1372
 
491
- # Creates a new transaction and yields it. Ensures that the transaction
492
- # is discarded if the block returns without having committed it.
493
- def in_transaction
494
- yield(trsc = Transaction.new(self))
1373
+ # Remove relations first. This is needed by transaction since
1374
+ # removing relations may need wrapping some new event, and in
1375
+ # that case these new event will be discovered as well
1376
+ event.clear_relations
1377
+ finalized_event(event)
1378
+ event.finalized!(timestamp)
1379
+ end
495
1380
 
496
- ensure
497
- if trsc && !trsc.finalized?
498
- trsc.discard_transaction
1381
+ def remove_task(task, timestamp = Time.now)
1382
+ if !@tasks.delete?(task)
1383
+ raise ArgumentError, "#{task} is not a task of #{self}"
499
1384
  end
1385
+ remove_task!(task, timestamp)
500
1386
  end
501
- # Hook called when a new transaction has been built on top of this plan
502
- def added_transaction(trsc); super if defined? super end
503
- # Removes the transaction +trsc+ from the list of known transactions
504
- # built on this plan
505
- def remove_transaction(trsc)
506
- transactions.delete(trsc)
507
- removed_transaction(trsc)
508
- end
509
- # Hook called when a new transaction has been built on top of this plan
510
- def removed_transaction(trsc); super if defined? super end
511
-
512
- # Merges the set of tasks that are useful for +seeds+ into +useful_set+.
513
- # Only the tasks that are in +complete_set+ are included.
514
- def useful_task_component(complete_set, useful_set, seeds)
515
- old_useful_set = useful_set.dup
516
- for rel in TaskStructure.relations
517
- next if !rel.root_relation?
518
- for subgraph in rel.generated_subgraphs(seeds, false)
519
- useful_set.merge(subgraph)
520
- end
521
- end
522
-
523
- if complete_set
524
- useful_set &= complete_set
525
- end
526
-
527
- if useful_set.size == old_useful_set.size || (complete_set && useful_set.size == complete_set.size)
528
- useful_set
529
- else
530
- useful_task_component(complete_set, useful_set, (useful_set - old_useful_set))
531
- end
532
- end
533
-
534
- # Returns the set of useful tasks in this plan
535
- def locally_useful_tasks
536
- # Remove all missions that are finished
537
- for finished_mission in (@missions & task_index.by_state[:finished?])
538
- if !task_index.repaired_tasks.include?(finished_mission)
539
- unmark_mission(finished_mission)
540
- end
541
- end
542
- for finished_permanent in (@permanent_tasks & task_index.by_state[:finished?])
543
- if !task_index.repaired_tasks.include?(finished_permanent)
544
- unmark_permanent(finished_permanent)
545
- end
546
- end
547
-
548
- # Create the set of tasks which must be kept as-is
549
- seeds = @missions | @permanent_tasks
550
- for trsc in transactions
551
- seeds.merge trsc.proxy_objects.keys.to_value_set
552
- end
553
-
554
- return ValueSet.new if seeds.empty?
555
-
556
- # Compute the set of LOCAL tasks which serve the seeds. The set of
557
- # locally_useful_tasks is the union of the seeds and of this one
558
- useful_task_component(local_tasks, seeds & local_tasks, seeds) | seeds
559
- end
560
-
561
- def local_tasks
562
- task_index.by_owner[Roby::Distributed] || ValueSet.new
563
- end
564
-
565
- def remote_tasks
566
- if local_tasks = task_index.by_owner[Roby::Distributed]
567
- known_tasks - local_tasks
568
- else
569
- known_tasks
570
- end
571
- end
572
-
573
- # Returns the set of unused tasks
574
- def unneeded_tasks
575
- # Get the set of local tasks that are serving one of our own missions or
576
- # permanent tasks
577
- useful = self.locally_useful_tasks
578
-
579
- # Append to that the set of tasks that are useful for our peers and
580
- # include the set of local tasks that are serving tasks in
581
- # +remotely_useful+
582
- remotely_useful = Distributed.remotely_useful_objects(remote_tasks, true, nil)
583
- serving_remote = useful_task_component(local_tasks, useful & local_tasks, remotely_useful)
584
-
585
- useful.merge remotely_useful
586
- useful.merge serving_remote
587
-
588
- (known_tasks - useful)
589
- end
590
-
591
- # Computes the set of useful tasks and checks that +task+ is in it.
592
- # This is quite slow. It is here for debugging purposes. Do not use it
593
- # in production code
594
- def useful_task?(task)
595
- known_tasks.include?(task) && !unneeded_tasks.include?(task)
596
- end
597
-
598
- def useful_event_component(useful_events)
599
- current_size = useful_events.size
600
- for rel in EventStructure.relations
601
- next unless rel.root_relation?
602
-
603
- for subgraph in rel.components(free_events, false)
604
- subgraph = subgraph.to_value_set
605
- if subgraph.intersects?(useful_events) || subgraph.intersects?(task_events)
606
- useful_events.merge(subgraph)
607
- if useful_events.include_all?(free_events)
608
- return free_events
609
- end
610
- end
611
- end
612
-
613
- if useful_events.include_all?(free_events)
614
- return free_events
615
- end
616
- end
617
-
618
- if current_size != useful_events.size
619
- useful_event_component(useful_events)
620
- else
621
- useful_events
622
- end
623
- end
624
-
625
- # Computes the set of events that are useful in the plan Events are
626
- # 'useful' when they are chained to a task.
627
- def useful_events
628
- return ValueSet.new if free_events.empty?
629
- (free_events & useful_event_component(permanent_events.dup))
630
- end
631
-
632
- # The set of events that can be removed from the plan
633
- def unneeded_events
634
- useful_events = self.useful_events
635
-
636
- result = (free_events - useful_events)
637
- result.delete_if do |ev|
638
- transactions.any? { |trsc| trsc.wrap(ev, false) }
639
- end
640
- result
641
- end
642
-
643
- # Checks if +task+ is included in this plan
644
- def include?(object); @known_tasks.include?(object) || @free_events.include?(object) end
645
- # Count of tasks in this plan
646
- def size; @known_tasks.size end
647
- # Returns true if there is no task in this plan
648
- def empty?; @known_tasks.empty? end
649
- # Iterates on all tasks
650
- def each_task; @known_tasks.each { |t| yield(t) } end
651
-
652
- # Install a plan repair for +failure_point+ with +task+. A plan repair
653
- # is a task which, during its lifetime, is supposed to fix the problem
654
- # encountered at +failure_point+
655
- #
656
- # +failure_point+ is an Event object which represents the event causing
657
- # the problem.
658
- #
659
- # See also #repairs and #remove_repair
660
- def add_repair(failure_point, task)
661
- if !failure_point.kind_of?(Event)
662
- raise TypeError, "failure point #{failure_point} should be an event"
663
- elsif task.plan && task.plan != self
664
- raise ArgumentError, "wrong plan: #{task} is in #{task.plan}, not #{plan}"
665
- elsif repairs.has_key?(failure_point)
666
- raise ArgumentError, "there is already a plan repair defined for #{failure_point}: #{repairs[failure_point]}"
667
- elsif !task.plan
668
- add(task)
669
- end
670
-
671
- repairs[failure_point] = task
672
- if failure_point.generator.respond_to?(:task)
673
- task_index.repaired_tasks << failure_point.generator.task
674
- end
675
- end
676
-
677
- # Removes +task+ from the set of active plan repairs.
678
- #
679
- # See also #repairs and #add_repair
680
- def remove_repair(task)
681
- repairs.delete_if do |ev, repair|
682
- if repair == task
683
- if ev.generator.respond_to?(:task)
684
- task_index.repaired_tasks.delete(ev.generator.task)
685
- end
686
- true
687
- end
688
- end
689
- end
690
-
691
- # Return all repairs which apply on +event+
692
- def repairs_for(event)
693
- result = Hash.new
694
-
695
- if event.generator.respond_to?(:task)
696
- equivalent_generators = event.generator.generated_subgraph(EventStructure::Forwarding)
697
-
698
- history = event.generator.task.history
699
- id = event.propagation_id
700
- index = history.index(event)
701
- while index < history.size
702
- ev = history[index]
703
- break if ev.propagation_id != id
704
-
705
- if equivalent_generators.include?(ev.generator) &&
706
- (task = repairs[ev])
707
-
708
- result[ev] = task
709
- end
710
-
711
- index += 1
712
- end
713
- elsif task = repairs[event]
714
- result[event] = task
715
- end
716
-
717
- result
718
- end
719
-
720
- # Returns +object+ if object is a plan object from this plan, or if
721
- # it has no plan yet (in which case it is added to the plan first).
722
- # Otherwise, raises ArgumentError.
723
- #
724
- # This method is provided for consistency with Transaction#[]
725
- def [](object, create = true)
726
- if !object.plan && !object.finalized?
727
- add(object)
728
- elsif object.finalized? && create
729
- raise ArgumentError, "#{object} is has been finalized, and can't be reused"
730
- elsif object.plan != self
731
- raise ArgumentError, "#{object} is not from #{self}"
732
- end
733
- object
734
- end
735
-
736
- def self.can_gc?(task)
737
- if task.starting? then true # wait for the task to be started before deciding ...
738
- elsif task.running? && !task.finishing?
739
- task.event(:stop).controlable?
740
- else true
741
- end
742
- end
743
-
744
- def discard_modifications(object)
745
- remove_object(object)
746
- end
747
-
748
- # Remove +object+ from this plan. You usually don't have to do that
749
- # manually. Object removal is handled by the plan's garbage collection
750
- # mechanism.
751
- def remove_object(object)
752
- if !object.root_object?
753
- raise ArgumentError, "cannot remove #{object} which is a non-root object"
754
- elsif object.plan != self
755
- if known_tasks.include?(object) || free_events.include?(object)
756
- raise ArgumentError, "#{object} is included in #{self} but #plan == #{object.plan}"
757
- elsif !object.plan
758
- if object.removed_at
759
- raise ArgumentError, "#{object} has been removed at\n #{object.removed_at.join("\n ")}"
760
- else
761
- raise ArgumentError, "#{object} has not been included in this plan"
762
- end
763
- end
764
- raise ArgumentError, "#{object} is not in #{self}: #plan == #{object.plan}"
765
- end
766
-
767
- # Remove relations first. This is needed by transaction since
768
- # removing relations may need wrapping some new objects, and in
769
- # that case these new objects will be discovered as well
770
- object.clear_relations
771
-
772
- @free_events.delete(object)
773
- @missions.delete(object)
774
- if object.respond_to? :mission=
775
- object.mission = false
776
- end
777
- @known_tasks.delete(object)
778
- @permanent_tasks.delete(object)
779
- @permanent_events.delete(object)
780
- force_gc.delete(object)
781
-
782
- object.plan = nil
783
- object.removed_at = caller
784
-
785
- case object
786
- when EventGenerator
787
- finalized_event(object)
788
-
789
- when Task
790
- task_index.remove(object)
791
-
792
- for ev in object.bound_events.values
793
- task_events.delete(ev)
794
- finalized_event(ev)
795
- end
796
- finalized_task(object)
797
-
798
- else
799
- raise ArgumentError, "unknown object type #{object}"
800
- end
801
-
802
- self
803
- end
804
-
805
- # Hook called when +task+ is marked as garbage. It will be garbage
806
- # collected as soon as possible
807
- def garbage(task)
808
- # Remove all signals that go *to* the task
809
- #
810
- # While we want events which come from the task to be properly
811
- # forwarded, the signals that go to the task are to be ignored
812
- if task.self_owned?
813
- task.each_event do |ev|
814
- ev.parent_objects(EventStructure::Signal).each do |signalling_event|
815
- signalling_event.remove_signal ev
816
- end
817
- end
818
- end
819
-
820
- super if defined? super
821
- end
822
-
823
- # backward compatibility
824
- def finalized(task) # :nodoc:
825
- super if defined? super
826
- end
827
-
828
- # Hook called when +task+ has been removed from this plan
829
- def finalized_task(task)
830
- finalized_transaction_object(task) { |trsc, proxy| trsc.finalized_plan_task(proxy) }
831
- super if defined? super
832
- finalized(task)
833
- end
834
-
835
- # Hook called when +event+ has been removed from this plan
836
- def finalized_event(event)
837
- if engine && executable?
838
- engine.finalized_event(event)
839
- end
840
- finalized_transaction_object(event) { |trsc, proxy| trsc.finalized_plan_event(proxy) }
841
- super if defined? super
842
- end
843
-
844
- # Generic filter which checks if +object+ is included in one of the
845
- # transactions of this plan. If it is the case, it yields the
846
- # transaction and the associated proxy
847
- def finalized_transaction_object(object)
848
- return unless object.root_object?
1387
+
1388
+ def remove_task!(task, timestamp = Time.now)
1389
+ @tasks.delete(task)
1390
+ @mission_tasks.delete(task)
1391
+ @permanent_tasks.delete(task)
1392
+ @task_index.remove(task)
1393
+
1394
+ for ev in task.bound_events.each_value
1395
+ @task_events.delete(ev)
1396
+ end
1397
+ finalize_task(task, timestamp)
1398
+ self
1399
+ end
1400
+
1401
+ def remove_free_event(event, timestamp = Time.now)
1402
+ if !@free_events.delete?(event)
1403
+ raise ArgumentError, "#{event} is not a free event of #{self}"
1404
+ end
1405
+ remove_free_event!(event, timestamp)
1406
+ end
1407
+
1408
+ def remove_free_event!(event, timestamp = Time.now)
1409
+ @free_events.delete(event)
1410
+ @permanent_events.delete(event)
1411
+ finalize_event(event, timestamp)
1412
+ self
1413
+ end
1414
+
1415
+ # @deprecated use {#remove_task} or {#remove_free_event} instead
1416
+ def remove_object(object, timestamp = Time.now)
1417
+ Roby.warn_deprecated "#remove_object is deprecated, use either #remove_task or #remove_free_event"
1418
+ if has_task?(object)
1419
+ remove_task(object, timestamp)
1420
+ elsif has_free_event?(object)
1421
+ remove_free_event(object, timestamp)
1422
+ else
1423
+ raise ArgumentError, "#{object} is neither a task nor a free event of #{self}"
1424
+ end
1425
+ end
1426
+
1427
+ def clear!
1428
+ each_task_relation_graph do |g|
1429
+ g.clear
1430
+ end
1431
+ each_event_relation_graph do |g|
1432
+ g.clear
1433
+ end
1434
+ @free_events.clear
1435
+ @mission_tasks.clear
1436
+ @tasks.clear
1437
+ @permanent_tasks.clear
1438
+ @permanent_events.clear
1439
+ @task_index.clear
1440
+ @task_events.clear
1441
+ end
1442
+
1443
+ # Remove all tasks
1444
+ def clear
1445
+ tasks, @tasks = @tasks, Set.new
1446
+ free_events, @free_events = @free_events, Set.new
1447
+
1448
+ clear!
1449
+
1450
+ remaining = tasks.find_all do |t|
1451
+ if executable? && t.running?
1452
+ true
1453
+ else
1454
+ finalize_task(t)
1455
+ false
1456
+ end
1457
+ end
1458
+ if !remaining.empty?
1459
+ Roby.warn "#{remaining.size} tasks remaining after clearing the plan as they are still running"
1460
+ remaining.each do |t|
1461
+ Roby.warn " #{t}"
1462
+ end
1463
+ end
1464
+ free_events.each do |e|
1465
+ finalize_event(e)
1466
+ end
1467
+
1468
+ self
1469
+ end
1470
+
1471
+ # Hook called when +task+ has been removed from this plan
1472
+ def finalized_task(task)
849
1473
  for trsc in transactions
850
1474
  next unless trsc.proxying?
1475
+ if proxy = trsc.find_local_object_for_task(task)
1476
+ trsc.finalized_plan_task(proxy)
1477
+ end
1478
+ end
1479
+ log(:finalized_task, droby_id, task)
1480
+ end
851
1481
 
852
- if proxy = trsc.wrap(object, false)
853
- yield(trsc, proxy)
1482
+ # Hook called when +event+ has been removed from this plan
1483
+ def finalized_event(event)
1484
+ log(:finalized_event, droby_id, event)
1485
+ return unless event.root_object?
1486
+ for trsc in transactions
1487
+ next unless trsc.proxying?
1488
+ if proxy = trsc.find_local_object_for_event(event)
1489
+ trsc.finalized_plan_event(proxy)
854
1490
  end
855
1491
  end
856
1492
  end
857
1493
 
858
- # Replace +task+ with a fresh copy of itself.
1494
+ # Replace +task+ with a fresh copy of itself.
859
1495
  #
860
1496
  # The new task takes the place of the old one in the plan: any relation
861
1497
  # that was going to/from +task+ or one of its events is removed, and the
862
1498
  # corresponding one is created, but this time involving the newly
863
1499
  # created task.
864
1500
  def recreate(task)
865
- new_task = task.create_fresh_copy
866
- replace_task(task, new_task)
1501
+ new_task = task.create_fresh_copy
1502
+ replace_task(task, new_task)
867
1503
  new_task
868
1504
  end
869
1505
 
870
- # Replace +task+ with a fresh copy of itself and start it.
1506
+ # Creates a new planning pattern replacing the given task and its
1507
+ # current planner
871
1508
  #
872
- # See #recreate for details about the new task.
873
- def respawn(task)
874
- new = recreate(task)
875
- engine.once { new.start!(nil) }
876
- new
877
- end
1509
+ # @param [Roby::Task] task the task that needs to be replanned
1510
+ # @return [Roby::Task] the new planning pattern
1511
+ def replan(task)
1512
+ if !task.planning_task
1513
+ return task.create_fresh_copy
1514
+ end
1515
+
1516
+ planner = replan(old_planner = task.planning_task)
1517
+ planned = task.create_fresh_copy
1518
+ planned.abstract = true
1519
+ planned.planned_by planner
1520
+ replace(task, planned)
1521
+ planned
1522
+ end
1523
+
878
1524
 
879
1525
  # The set of blocks that should be called to check the structure of the
880
- # plan. See also Plan.structure_checks.
1526
+ # plan.
1527
+ #
1528
+ # @yieldparam [Plan] the plan
1529
+ # @yieldreturn [Array<(#to_execution_exception,Array<Task>)>] a list
1530
+ # of exceptions, and the tasks toward which these exceptions
1531
+ # should be propagated. If the list of tasks is nil, all parents
1532
+ # of the exception's origin will be selected
881
1533
  attr_reader :structure_checks
882
1534
 
883
1535
  @structure_checks = Array.new
884
1536
  class << self
885
1537
  # A set of structure checking procedures that must be performed on all plans
1538
+ #
1539
+ # @yieldparam [Plan] the plan
1540
+ # @yieldreturn [Array<(#to_execution_exception,Array<Task>)>] a list
1541
+ # of exceptions, and the tasks toward which these exceptions
1542
+ # should be propagated. If the list of tasks is nil, all parents
1543
+ # of the exception's origin will be selected
886
1544
  attr_reader :structure_checks
887
1545
  end
888
1546
 
889
1547
  # Get all missions that have failed
890
1548
  def self.check_failed_missions(plan)
891
- result = []
892
- for task in plan.missions
1549
+ result = Array.new
1550
+ for task in plan.mission_tasks
893
1551
  result << MissionFailedError.new(task) if task.failed?
894
1552
  end
1553
+ for task in plan.permanent_tasks
1554
+ result << PermanentTaskError.new(task) if task.failed?
1555
+ end
895
1556
  result
896
1557
  end
897
1558
  structure_checks << method(:check_failed_missions)
898
1559
 
899
- # Perform the structure checking step by calling the procs registered
900
- # in #structure_checks and Plan.structure_checks. These procs are
901
- # supposed to return a collection of exception objects, or nil if no
902
- # error has been found
903
- def check_structure
904
- # Do structure checking and gather the raised exceptions
905
- exceptions = {}
906
- for prc in (Plan.structure_checks + structure_checks)
907
- begin
908
- new_exceptions = prc.call(self)
909
- rescue Exception => e
910
- if engine
911
- engine.add_framework_error(e, 'structure checking')
912
- else
913
- raise
1560
+ # @api private
1561
+ #
1562
+ # Normalize the value returned by one of the {#structure_checks}, by
1563
+ # computing the list of propagation parents if they were not specified
1564
+ # in the return value
1565
+ #
1566
+ # @param [Hash] result
1567
+ # @param [Array,Hash] new
1568
+ def format_exception_set(result, new)
1569
+ [*new].each do |error, tasks|
1570
+ roby_exception = error.to_execution_exception
1571
+ if !tasks
1572
+ if error.kind_of?(RelationFailedError)
1573
+ tasks = [error.parent]
914
1574
  end
915
- end
916
- next unless new_exceptions
1575
+ end
1576
+ result[roby_exception] = tasks
1577
+ end
1578
+ result
1579
+ end
1580
+
1581
+ def call_structure_check_handler(handler)
1582
+ handler.call(self)
1583
+ end
1584
+
1585
+ # Perform the structure checking step by calling the procs registered
1586
+ # in {#structure_checks} and {Plan.structure_checks}
1587
+ #
1588
+ # @return [Hash<ExecutionException,Array<Roby::Task>,nil>
1589
+ def check_structure
1590
+ # Do structure checking and gather the raised exceptions
1591
+ exceptions = Hash.new
1592
+ for prc in (Plan.structure_checks + structure_checks)
1593
+ new_exceptions = call_structure_check_handler(prc)
1594
+ next unless new_exceptions
1595
+
1596
+ format_exception_set(exceptions, new_exceptions)
1597
+ end
1598
+ exceptions
1599
+ end
1600
+
1601
+ # Run a garbage collection pass. This is 'static', as it does not care
1602
+ # about the task's state: it will simply remove *from the plan* any task
1603
+ # that is not useful *in the context of the plan*.
1604
+ #
1605
+ # This is mainly useful for static tests, and for transactions
1606
+ #
1607
+ # Do *not* use it on executed plans.
1608
+ def static_garbage_collect
1609
+ if block_given?
1610
+ for t in unneeded_tasks
1611
+ yield(t)
1612
+ end
1613
+ else
1614
+ for t in unneeded_tasks
1615
+ remove_task(t)
1616
+ end
1617
+ end
1618
+ end
1619
+
1620
+ # Finds a single difference between this plan and the other plan, using
1621
+ # the provided mappings to map objects from self to object in other_plan
1622
+ def find_plan_difference(other_plan, mappings)
1623
+ all_self_objects = tasks | free_events | task_events
1624
+ all_other_objects = (other_plan.tasks | other_plan.free_events | other_plan.task_events)
917
1625
 
918
- [*new_exceptions].each do |e, tasks|
919
- e = ExecutionEngine.to_execution_exception(e)
920
- exceptions[e] = tasks
921
- end
922
- end
923
- exceptions
924
- end
1626
+ all_mapped_objects = all_self_objects.map do |obj|
1627
+ if !mappings.has_key?(obj)
1628
+ return [:new_object, obj]
1629
+ end
1630
+ mappings[obj]
1631
+ end.to_set
1632
+
1633
+ if all_mapped_objects != all_other_objects
1634
+ return [:removed_objects, all_other_objects - all_mapped_objects]
1635
+ elsif mission_tasks.map { |m| mappings[m] }.to_set != other_plan.mission_tasks
1636
+ return [:missions_differ]
1637
+ elsif permanent_tasks.map { |p| mappings[p] }.to_set != other_plan.permanent_tasks
1638
+ return [:permanent_tasks_differ]
1639
+ elsif permanent_events.map { |p| mappings[p] }.to_set != other_plan.permanent_events
1640
+ return [:permanent_events_differ]
1641
+ end
925
1642
 
1643
+ each_task_relation_graph do |graph|
1644
+ other_graph = other_plan.task_relation_graph_for(graph.class)
1645
+ if diff = graph.find_edge_difference(other_graph, mappings)
1646
+ return [graph.class] + diff
1647
+ end
1648
+ end
926
1649
 
927
- include Roby::ExceptionHandlingObject
1650
+ each_event_relation_graph do |graph|
1651
+ other_graph = other_plan.event_relation_graph_for(graph.class)
1652
+ if diff = graph.find_edge_difference(other_graph, mappings)
1653
+ return [graph.class] + diff
1654
+ end
1655
+ end
1656
+ nil
1657
+ end
928
1658
 
929
- attr_reader :exception_handlers
930
- def each_exception_handler(&iterator); exception_handlers.each(&iterator) end
931
- def on_exception(*matchers, &handler)
932
- check_arity(handler, 2)
933
- exception_handlers.unshift [matchers, handler]
1659
+ # Compares this plan to +other_plan+, mappings providing the mapping
1660
+ # from task/Events in +self+ to task/events in other_plan
1661
+ def same_plan?(other_plan, mappings)
1662
+ !find_plan_difference(other_plan, mappings)
1663
+ end
1664
+
1665
+ # Returns a Query object that applies on this plan.
1666
+ #
1667
+ # This is equivalent to
1668
+ #
1669
+ # Roby::Query.new(self)
1670
+ #
1671
+ # Additionally, the +model+ and +args+ options are passed to
1672
+ # Query#which_fullfills. For example:
1673
+ #
1674
+ # plan.find_tasks(Tasks::SimpleTask, id: 20)
1675
+ #
1676
+ # is equivalent to
1677
+ #
1678
+ # Roby::Query.new(self).which_fullfills(Tasks::SimpleTask, id: 20)
1679
+ #
1680
+ # The returned query is applied on the global scope by default. This
1681
+ # means that, if it is applied on a transaction, it will match tasks
1682
+ # that are in the underlying plans but not yet in the transaction,
1683
+ # import the matches in the transaction and return the new proxies.
1684
+ #
1685
+ # See #find_local_tasks for a local query.
1686
+ def find_tasks(model = nil, args = nil)
1687
+ q = Queries::Query.new(self)
1688
+ if model || args
1689
+ q.which_fullfills(model, args)
1690
+ end
1691
+ q
1692
+ end
1693
+
1694
+ # Starts a local query on this plan.
1695
+ #
1696
+ # Unlike #find_tasks, when applied on a transaction, it will only match
1697
+ # tasks that are already in the transaction.
1698
+ #
1699
+ # See #find_global_tasks for a local query.
1700
+ def find_local_tasks(*args, &block)
1701
+ query = find_tasks(*args, &block)
1702
+ query.local_scope
1703
+ query
1704
+ end
1705
+
1706
+ # Called by TaskMatcher#result_set and Query#result_set to get the set
1707
+ # of tasks matching +matcher+
1708
+ def query_result_set(matcher) # :nodoc:
1709
+ filtered = matcher.filter(tasks, task_index, initial_is_complete: true)
1710
+
1711
+ if matcher.indexed_query?
1712
+ filtered
1713
+ else
1714
+ result = Set.new
1715
+ for task in filtered
1716
+ result << task if matcher === task
1717
+ end
1718
+ result
1719
+ end
1720
+ end
1721
+
1722
+ # Called by TaskMatcher#each and Query#each to return the result of
1723
+ # this query on +self+
1724
+ def query_each(result_set, &block) # :nodoc:
1725
+ for task in result_set
1726
+ yield(task)
1727
+ end
1728
+ end
1729
+
1730
+ def root_in_query?(result_set, task, graph)
1731
+ graph.depth_first_visit(task) do |v|
1732
+ return false if v != task && result_set.include?(v)
1733
+ end
1734
+ true
1735
+ end
1736
+
1737
+ # Given the result set of +query+, returns the subset of tasks which
1738
+ # have no parent in +query+
1739
+ def query_roots(result_set, relation) # :nodoc:
1740
+ graph = task_relation_graph_for(relation).reverse
1741
+ result_set.find_all do |task|
1742
+ root_in_query?(result_set, task, graph)
1743
+ end
1744
+ end
1745
+
1746
+ # The list of fault response tables that are currently globally active
1747
+ # on this plan
1748
+ attr_reader :active_fault_response_tables
1749
+
1750
+ # Enables a fault response table on this plan
1751
+ #
1752
+ # @param [Model<Coordination::FaultResponseTable>] table_model the fault
1753
+ # response table model
1754
+ # @param [Hash] arguments the arguments that should be passed to the
1755
+ # created table
1756
+ # @return [Coordination::FaultResponseTable] the fault response table
1757
+ # that got added to this plan. It can be removed using
1758
+ # {#remove_fault_response_table}
1759
+ # @return [void]
1760
+ # @see remove_fault_response_table
1761
+ def use_fault_response_table(table_model, arguments = Hash.new)
1762
+ table = table_model.new(self, arguments)
1763
+ table.attach_to(self)
1764
+ active_fault_response_tables << table
1765
+ table
1766
+ end
1767
+
1768
+ # Remove a fault response table that has been added with
1769
+ # {#use_fault_response_table}
1770
+ #
1771
+ # @overload remove_fault_response_table(table)
1772
+ # @param [Coordination::FaultResponseTable] table the table that
1773
+ # should be removed. This is the return value of
1774
+ # {#use_fault_response_table}
1775
+ #
1776
+ # @overload remove_fault_response_table(table_model)
1777
+ # Removes all the tables whose model is the given table model
1778
+ #
1779
+ # @param [Model<Coordination::FaultResponseTable>] table_model
1780
+ #
1781
+ # @return [void]
1782
+ # @see use_fault_response_table
1783
+ def remove_fault_response_table(table_model)
1784
+ active_fault_response_tables.delete_if do |t|
1785
+ if (table_model.kind_of?(Class) && t.kind_of?(table_model)) || t == table_model
1786
+ t.removed!
1787
+ true
1788
+ end
1789
+ end
1790
+ end
1791
+
1792
+ # Tests whether a task is useful for another one task
1793
+ #
1794
+ # It is O(N) where N is the number of edges in the combined task
1795
+ # relation graphs. If you have to do a lot of tests with the same task,
1796
+ # compute the set of useful tasks with {Plan#compute_useful_tasks}
1797
+ #
1798
+ # @param reference_task the reference task
1799
+ # @param task the task whose usefulness is being tested
1800
+ # @return [Boolean]
1801
+ def in_useful_subplan?(reference_task, task)
1802
+ compute_useful_tasks([task]) do |useful_t|
1803
+ if useful_t == self
1804
+ return true
1805
+ end
1806
+ end
1807
+ return false
934
1808
  end
935
- end
936
1809
 
937
- class << self
938
- # Returns the main plan
939
- attr_reader :plan
1810
+ # Enumerate object identities along the transaction stack
1811
+ #
1812
+ # The enumeration starts with the deepest transaction and stops at the
1813
+ # topmost plan where the object is not a transaction proxy.
1814
+ #
1815
+ # @param [PlanObject] object
1816
+ # @yieldparam [PlanObject] object the object's identity at the
1817
+ # given level of the stack. Note that the last element is guaranteed
1818
+ # to not be a transaction proxy.
1819
+ def each_object_in_transaction_stack(object)
1820
+ return enum_for(__method__, object) if !block_given?
1821
+ current_plan = self
1822
+ while true
1823
+ yield(current_plan, object)
1824
+
1825
+ return if !object.transaction_proxy?
1826
+ current_plan = current_plan.plan
1827
+ object = object.__getobj__
1828
+ end
1829
+ nil
1830
+ end
940
1831
  end
941
-
942
- # Defines a global exception handler on the main plan.
943
- # See also Plan#on_exception
944
- def self.on_exception(*matchers, &handler); Roby.plan.on_exception(*matchers, &handler) end
945
1832
  end
946
1833