roby 0.8.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (644) hide show
  1. checksums.yaml +7 -0
  2. data/.deep-cover.rb +3 -0
  3. data/.gitattributes +1 -0
  4. data/.gitignore +24 -0
  5. data/.simplecov +10 -0
  6. data/.travis.yml +17 -0
  7. data/.yardopts +4 -0
  8. data/Gemfile +15 -0
  9. data/README.md +11 -0
  10. data/Rakefile +47 -177
  11. data/benchmark/{alloc_misc.rb → attic/alloc_misc.rb} +2 -2
  12. data/benchmark/{discovery_latency.rb → attic/discovery_latency.rb} +19 -19
  13. data/benchmark/{garbage_collection.rb → attic/garbage_collection.rb} +9 -9
  14. data/benchmark/{genom.rb → attic/genom.rb} +0 -0
  15. data/benchmark/attic/transactions.rb +62 -0
  16. data/benchmark/plan_basic_operations.rb +28 -0
  17. data/benchmark/relations/graph.rb +63 -0
  18. data/benchmark/ruby/identity.rb +18 -0
  19. data/benchmark/ruby/set_intersect_vs_hash_merge.rb +39 -0
  20. data/benchmark/ruby/yield_vs_block.rb +35 -0
  21. data/benchmark/run +5 -0
  22. data/benchmark/synthetic_plan_modifications_with_transactions.rb +79 -0
  23. data/benchmark/transactions.rb +99 -51
  24. data/bin/roby +38 -197
  25. data/bin/roby-display +14 -0
  26. data/bin/roby-log +3 -176
  27. data/doc/guide/{src → attic}/abstraction/achieve_with.page +1 -1
  28. data/doc/guide/{src → attic}/abstraction/forwarding.page +1 -1
  29. data/doc/guide/{src → attic}/abstraction/hierarchy.page +1 -1
  30. data/doc/guide/{src → attic}/abstraction/index.page +1 -1
  31. data/doc/guide/{src → attic}/abstraction/task_models.page +1 -1
  32. data/doc/guide/{overview.rdoc → attic/cycle/api_overview.rdoc} +6 -1
  33. data/doc/guide/{src → attic}/cycle/cycle-overview.png +0 -0
  34. data/doc/guide/{src → attic}/cycle/cycle-overview.svg +0 -0
  35. data/doc/guide/attic/cycle/error_handling.page +98 -0
  36. data/doc/guide/{src → attic}/cycle/error_instantaneous_repair.png +0 -0
  37. data/doc/guide/{src → attic}/cycle/error_instantaneous_repair.svg +0 -0
  38. data/doc/guide/{src/cycle/error_handling.page → attic/cycle/error_sources.page} +46 -89
  39. data/doc/guide/{src → attic}/cycle/garbage_collection.page +1 -1
  40. data/doc/guide/{src → attic}/cycle/index.page +1 -1
  41. data/doc/guide/{src → attic}/cycle/propagation.page +11 -1
  42. data/doc/guide/{src → attic}/cycle/propagation_diamond.png +0 -0
  43. data/doc/guide/{src → attic}/cycle/propagation_diamond.svg +0 -0
  44. data/doc/guide/attic/plans/building_plans.page +89 -0
  45. data/doc/guide/attic/plans/code.page +192 -0
  46. data/doc/guide/{src/basics → attic/plans}/events.page +3 -4
  47. data/doc/guide/attic/plans/index.page +7 -0
  48. data/doc/guide/{plan_modifications.rdoc → attic/plans/plan_modifications.rdoc} +5 -3
  49. data/doc/guide/{src/basics → attic/plans}/plan_objects.page +2 -1
  50. data/doc/guide/attic/plans/querying_plans.page +5 -0
  51. data/doc/guide/{src/basics → attic/plans}/tasks.page +20 -20
  52. data/doc/guide/config.yaml +7 -4
  53. data/doc/guide/ext/extended_menu.rb +29 -0
  54. data/doc/guide/ext/init.rb +6 -0
  55. data/doc/guide/ext/rdoc_links.rb +7 -6
  56. data/doc/guide/src/advanced_concepts/history.page +5 -0
  57. data/doc/guide/src/advanced_concepts/index.page +11 -0
  58. data/doc/guide/src/advanced_concepts/recognizing_patterns.page +83 -0
  59. data/doc/guide/src/advanced_concepts/scheduling.page +87 -0
  60. data/doc/guide/src/advanced_concepts/transactions.page +5 -0
  61. data/doc/guide/src/advanced_concepts/unreachability.page +42 -0
  62. data/doc/guide/src/base.template +96 -0
  63. data/doc/guide/src/basics_shell_header.txt +5 -7
  64. data/doc/guide/src/building/action_coordination.page +96 -0
  65. data/doc/guide/src/building/actions.page +124 -0
  66. data/doc/guide/src/building/file_layout.page +71 -0
  67. data/doc/guide/src/building/index.page +50 -0
  68. data/doc/guide/src/building/patterns.page +86 -0
  69. data/doc/guide/src/building/patterns_forwarding.png +0 -0
  70. data/doc/guide/src/building/patterns_forwarding.svg +277 -0
  71. data/doc/guide/src/building/runtime.page +95 -0
  72. data/doc/guide/src/building/task_models.page +94 -0
  73. data/doc/guide/src/building/tasks.page +284 -0
  74. data/doc/guide/src/concepts/error_handling.page +100 -0
  75. data/doc/guide/src/concepts/exception_propagation.png +0 -0
  76. data/doc/guide/src/concepts/exception_propagation.svg +445 -0
  77. data/doc/guide/src/concepts/execution.page +85 -0
  78. data/doc/guide/src/concepts/execution.png +0 -0
  79. data/doc/guide/src/concepts/execution.svg +573 -0
  80. data/doc/guide/src/concepts/execution_cycle.png +0 -0
  81. data/doc/guide/src/concepts/garbage_collection.page +57 -0
  82. data/doc/guide/src/concepts/index.page +27 -0
  83. data/doc/guide/src/concepts/plans.page +101 -0
  84. data/doc/guide/src/concepts/policy.page +31 -0
  85. data/doc/guide/src/concepts/reactor.page +61 -0
  86. data/doc/guide/src/concepts/simple_plan_example.png +0 -0
  87. data/doc/guide/src/concepts/simple_plan_example.svg +376 -0
  88. data/doc/guide/src/default.template +9 -74
  89. data/doc/guide/src/event_relations/forward.page +71 -0
  90. data/doc/guide/src/event_relations/index.page +12 -0
  91. data/doc/guide/src/event_relations/scheduling_constraints.page +43 -0
  92. data/doc/guide/src/event_relations/signal.page +55 -0
  93. data/doc/guide/src/event_relations/temporal_constraints.page +77 -0
  94. data/doc/guide/src/htmldoc.metainfo +21 -8
  95. data/doc/guide/src/index.page +8 -3
  96. data/doc/guide/src/{introduction/install.page → installation/index.page} +37 -25
  97. data/doc/guide/src/installation/publications.page +14 -0
  98. data/doc/guide/src/{introduction → installation}/videos.page +14 -7
  99. data/doc/guide/src/interacting/index.page +16 -0
  100. data/doc/guide/src/interacting/run.page +33 -0
  101. data/doc/guide/src/interacting/shell.page +95 -0
  102. data/doc/guide/src/plugins/creating_plugins.page +72 -0
  103. data/doc/guide/src/plugins/index.page +27 -5
  104. data/doc/guide/src/plugins/{fault_tolerance.page → standard_plugins/fault_tolerance.page} +2 -2
  105. data/doc/guide/src/plugins/standard_plugins/index.page +11 -0
  106. data/doc/guide/src/plugins/{subsystems.page → standard_plugins/subsystems.page} +2 -2
  107. data/doc/guide/src/style_screen.css +687 -0
  108. data/doc/guide/src/task_relations/dependency.page +107 -0
  109. data/doc/guide/src/task_relations/executed_by.page +77 -0
  110. data/doc/guide/src/task_relations/index.page +12 -0
  111. data/doc/guide/src/task_relations/new_relations.page +119 -0
  112. data/doc/guide/src/task_relations/planned_by.page +46 -0
  113. data/doc/guide/src/tutorial/app.page +117 -0
  114. data/doc/guide/src/{basics → tutorial}/code_examples.page +6 -5
  115. data/doc/guide/src/{basics → tutorial}/dry.page +15 -15
  116. data/doc/guide/src/{basics → tutorial}/errors.page +43 -68
  117. data/doc/guide/src/tutorial/events.page +195 -0
  118. data/doc/guide/src/{basics → tutorial}/hierarchy.page +53 -52
  119. data/doc/guide/src/tutorial/index.page +13 -0
  120. data/doc/guide/src/tutorial/log_replay/goForward_1.png +0 -0
  121. data/doc/guide/src/tutorial/log_replay/goForward_2.png +0 -0
  122. data/doc/guide/src/tutorial/log_replay/goForward_3.png +0 -0
  123. data/doc/guide/src/{basics → tutorial}/log_replay/goForward_4.png +0 -0
  124. data/doc/guide/src/tutorial/log_replay/goForward_5.png +0 -0
  125. data/doc/guide/src/{basics → tutorial}/log_replay/hierarchy_error_1.png +0 -0
  126. data/doc/guide/src/{basics → tutorial}/log_replay/hierarchy_error_2.png +0 -0
  127. data/doc/guide/src/{basics → tutorial}/log_replay/hierarchy_error_3.png +0 -0
  128. data/doc/guide/src/tutorial/log_replay/moveto_code_error.png +0 -0
  129. data/doc/guide/src/{basics → tutorial}/log_replay/plan_repair_1.png +0 -0
  130. data/doc/guide/src/{basics → tutorial}/log_replay/plan_repair_2.png +0 -0
  131. data/doc/guide/src/{basics → tutorial}/log_replay/plan_repair_3.png +0 -0
  132. data/doc/guide/src/tutorial/log_replay/plan_repair_4.png +0 -0
  133. data/doc/guide/src/tutorial/log_replay/roby_log_main_window.png +0 -0
  134. data/doc/guide/src/{basics → tutorial}/log_replay/roby_log_relation_window.png +0 -0
  135. data/doc/guide/src/{basics → tutorial}/log_replay/roby_replay_event_representation.png +0 -0
  136. data/doc/guide/src/tutorial/relations_display.page +153 -0
  137. data/doc/guide/src/{basics → tutorial}/roby_cycle_overview.png +0 -0
  138. data/doc/guide/src/tutorial/shell.page +121 -0
  139. data/doc/guide/src/{basics → tutorial}/summary.page +1 -1
  140. data/doc/guide/src/tutorial/tasks.page +374 -0
  141. data/lib/roby.rb +102 -47
  142. data/lib/roby/actions.rb +17 -0
  143. data/lib/roby/actions/action.rb +80 -0
  144. data/lib/roby/actions/interface.rb +45 -0
  145. data/lib/roby/actions/library.rb +23 -0
  146. data/lib/roby/actions/models/action.rb +224 -0
  147. data/lib/roby/actions/models/coordination_action.rb +58 -0
  148. data/lib/roby/actions/models/interface.rb +22 -0
  149. data/lib/roby/actions/models/interface_base.rb +294 -0
  150. data/lib/roby/actions/models/library.rb +12 -0
  151. data/lib/roby/actions/models/method_action.rb +90 -0
  152. data/lib/roby/actions/task.rb +114 -0
  153. data/lib/roby/and_generator.rb +125 -0
  154. data/lib/roby/app.rb +2795 -829
  155. data/lib/roby/app/autotest_console_reporter.rb +138 -0
  156. data/lib/roby/app/base.rb +21 -0
  157. data/lib/roby/app/cucumber.rb +2 -0
  158. data/lib/roby/app/cucumber/controller.rb +439 -0
  159. data/lib/roby/app/cucumber/helpers.rb +280 -0
  160. data/lib/roby/app/cucumber/world.rb +32 -0
  161. data/lib/roby/app/debug.rb +136 -0
  162. data/lib/roby/app/gen.rb +2 -0
  163. data/lib/roby/app/rake.rb +178 -38
  164. data/lib/roby/app/robot_config.rb +9 -0
  165. data/lib/roby/app/robot_names.rb +115 -0
  166. data/lib/roby/app/run.rb +3 -2
  167. data/lib/roby/app/scripts.rb +72 -0
  168. data/lib/roby/app/scripts/autotest.rb +173 -0
  169. data/lib/roby/app/scripts/display.rb +2 -0
  170. data/lib/roby/app/scripts/restart.rb +52 -0
  171. data/lib/roby/app/scripts/results.rb +17 -8
  172. data/lib/roby/app/scripts/run.rb +155 -24
  173. data/lib/roby/app/scripts/shell.rb +147 -62
  174. data/lib/roby/app/scripts/test.rb +107 -22
  175. data/lib/roby/app/test_reporter.rb +74 -0
  176. data/lib/roby/app/test_server.rb +159 -0
  177. data/lib/roby/app/vagrant.rb +47 -0
  178. data/lib/roby/backports.rb +16 -0
  179. data/lib/roby/cli/display.rb +190 -0
  180. data/lib/roby/cli/exceptions.rb +17 -0
  181. data/lib/roby/cli/gen/actions/class.rb +5 -0
  182. data/lib/roby/cli/gen/actions/test.rb +6 -0
  183. data/lib/roby/cli/gen/app/.yardopts +6 -0
  184. data/lib/roby/cli/gen/app/README.md +28 -0
  185. data/lib/roby/cli/gen/app/Rakefile +15 -0
  186. data/{app → lib/roby/cli/gen/app}/config/app.yml +29 -39
  187. data/lib/roby/cli/gen/app/models/.gitattributes +1 -0
  188. data/{app → lib/roby/cli/gen/app/scripts}/controllers/.gitattributes +0 -0
  189. data/{app/data/.gitattributes → lib/roby/cli/gen/app/test/.gitignore} +0 -0
  190. data/lib/roby/cli/gen/class/class.rb +6 -0
  191. data/lib/roby/cli/gen/class/test.rb +7 -0
  192. data/lib/roby/cli/gen/helpers.rb +203 -0
  193. data/lib/roby/cli/gen/module/module.rb +5 -0
  194. data/lib/roby/cli/gen/module/test.rb +6 -0
  195. data/lib/roby/cli/gen/roby_app/config/init.rb +17 -0
  196. data/lib/roby/cli/gen/roby_app/config/robots/robot.rb +40 -0
  197. data/lib/roby/cli/gen/task/class.rb +44 -0
  198. data/lib/roby/cli/gen/task/test.rb +6 -0
  199. data/lib/roby/cli/gen_main.rb +120 -0
  200. data/lib/roby/cli/log.rb +276 -0
  201. data/lib/roby/cli/log/flamegraph.html +499 -0
  202. data/lib/roby/cli/log/flamegraph_renderer.rb +88 -0
  203. data/lib/roby/cli/main.rb +153 -0
  204. data/lib/roby/coordination.rb +60 -0
  205. data/lib/roby/coordination/action_script.rb +25 -0
  206. data/lib/roby/coordination/action_state_machine.rb +125 -0
  207. data/lib/roby/coordination/actions.rb +106 -0
  208. data/lib/roby/coordination/base.rb +145 -0
  209. data/lib/roby/coordination/calculus.rb +40 -0
  210. data/lib/roby/coordination/child.rb +28 -0
  211. data/lib/roby/coordination/event.rb +29 -0
  212. data/lib/roby/coordination/fault_handler.rb +25 -0
  213. data/lib/roby/coordination/fault_handling_task.rb +13 -0
  214. data/lib/roby/coordination/fault_response_table.rb +110 -0
  215. data/lib/roby/coordination/models/action_script.rb +64 -0
  216. data/lib/roby/coordination/models/action_state_machine.rb +224 -0
  217. data/lib/roby/coordination/models/actions.rb +191 -0
  218. data/lib/roby/coordination/models/arguments.rb +55 -0
  219. data/lib/roby/coordination/models/base.rb +176 -0
  220. data/lib/roby/coordination/models/capture.rb +86 -0
  221. data/lib/roby/coordination/models/child.rb +35 -0
  222. data/lib/roby/coordination/models/event.rb +41 -0
  223. data/lib/roby/coordination/models/exceptions.rb +42 -0
  224. data/lib/roby/coordination/models/fault_handler.rb +219 -0
  225. data/lib/roby/coordination/models/fault_response_table.rb +77 -0
  226. data/lib/roby/coordination/models/root.rb +22 -0
  227. data/lib/roby/coordination/models/script.rb +283 -0
  228. data/lib/roby/coordination/models/task.rb +184 -0
  229. data/lib/roby/coordination/models/task_from_action.rb +50 -0
  230. data/lib/roby/coordination/models/task_from_as_plan.rb +33 -0
  231. data/lib/roby/coordination/models/task_from_instanciation_object.rb +31 -0
  232. data/lib/roby/coordination/models/task_from_variable.rb +27 -0
  233. data/lib/roby/coordination/models/task_with_dependencies.rb +48 -0
  234. data/lib/roby/coordination/models/variable.rb +32 -0
  235. data/lib/roby/coordination/script.rb +200 -0
  236. data/lib/roby/coordination/script_instruction.rb +12 -0
  237. data/lib/roby/coordination/task.rb +45 -0
  238. data/lib/roby/coordination/task_base.rb +69 -0
  239. data/lib/roby/coordination/task_script.rb +293 -0
  240. data/lib/roby/coordination/task_state_machine.rb +308 -0
  241. data/lib/roby/decision_control.rb +33 -21
  242. data/lib/roby/distributed_object.rb +76 -0
  243. data/lib/roby/droby.rb +17 -0
  244. data/lib/roby/droby/droby_id.rb +6 -0
  245. data/lib/roby/droby/enable.rb +153 -0
  246. data/lib/roby/droby/event_logger.rb +189 -0
  247. data/lib/roby/droby/event_logging.rb +57 -0
  248. data/lib/roby/droby/exceptions.rb +14 -0
  249. data/lib/roby/droby/identifiable.rb +22 -0
  250. data/lib/roby/droby/logfile.rb +141 -0
  251. data/lib/roby/droby/logfile/client.rb +176 -0
  252. data/lib/roby/droby/logfile/file_format.md +97 -0
  253. data/lib/roby/droby/logfile/index.rb +117 -0
  254. data/lib/roby/droby/logfile/reader.rb +139 -0
  255. data/lib/roby/droby/logfile/server.rb +199 -0
  256. data/lib/roby/droby/logfile/writer.rb +114 -0
  257. data/lib/roby/droby/marshal.rb +264 -0
  258. data/lib/roby/droby/marshallable.rb +12 -0
  259. data/lib/roby/droby/null_event_logger.rb +25 -0
  260. data/lib/roby/droby/object_manager.rb +205 -0
  261. data/lib/roby/droby/peer_id.rb +6 -0
  262. data/lib/roby/droby/plan_rebuilder.rb +373 -0
  263. data/lib/roby/droby/rebuilt_plan.rb +160 -0
  264. data/lib/roby/droby/remote_droby_id.rb +6 -0
  265. data/lib/roby/droby/timepoints.rb +205 -0
  266. data/lib/roby/droby/timepoints_ctf.metadata.erb +101 -0
  267. data/lib/roby/droby/timepoints_ctf.rb +125 -0
  268. data/lib/roby/droby/v5.rb +14 -0
  269. data/lib/roby/droby/v5/builtin.rb +120 -0
  270. data/lib/roby/droby/v5/droby_class.rb +45 -0
  271. data/lib/roby/droby/v5/droby_constant.rb +81 -0
  272. data/lib/roby/droby/v5/droby_dump.rb +1026 -0
  273. data/lib/roby/droby/v5/droby_id.rb +44 -0
  274. data/lib/roby/droby/v5/droby_model.rb +82 -0
  275. data/lib/roby/droby/v5/peer_id.rb +10 -0
  276. data/lib/roby/droby/v5/remote_droby_id.rb +42 -0
  277. data/lib/roby/event.rb +79 -957
  278. data/lib/roby/event_constraints.rb +835 -0
  279. data/lib/roby/event_generator.rb +1047 -0
  280. data/lib/roby/event_structure/causal_link.rb +6 -0
  281. data/lib/roby/event_structure/forwarding.rb +6 -0
  282. data/lib/roby/event_structure/precedence.rb +7 -0
  283. data/lib/roby/event_structure/signal.rb +8 -0
  284. data/lib/roby/event_structure/temporal_constraints.rb +640 -0
  285. data/lib/roby/exceptions.rb +446 -152
  286. data/lib/roby/executable_plan.rb +549 -0
  287. data/lib/roby/execution_engine.rb +1997 -950
  288. data/lib/roby/filter_generator.rb +26 -0
  289. data/lib/roby/gui/chronicle_view.rb +225 -0
  290. data/lib/roby/gui/chronicle_widget.rb +925 -0
  291. data/lib/roby/gui/dot_id.rb +11 -0
  292. data/lib/roby/gui/exception_view.rb +44 -0
  293. data/lib/roby/gui/log_display.rb +273 -0
  294. data/lib/roby/gui/model_views.rb +2 -0
  295. data/lib/roby/gui/model_views/action_interface.rb +53 -0
  296. data/lib/roby/gui/model_views/task.rb +47 -0
  297. data/lib/roby/gui/model_views/task.rhtml +41 -0
  298. data/lib/roby/gui/object_info_view.rb +89 -0
  299. data/lib/roby/gui/plan_dot_layout.rb +427 -0
  300. data/lib/roby/gui/plan_rebuilder_widget.rb +357 -0
  301. data/lib/roby/gui/qt4_toMSecsSinceEpoch.rb +8 -0
  302. data/lib/roby/gui/relations_view.rb +278 -0
  303. data/lib/roby/gui/relations_view/relations.ui +139 -0
  304. data/lib/roby/gui/relations_view/relations_canvas.rb +1088 -0
  305. data/lib/roby/gui/relations_view/relations_config.rb +292 -0
  306. data/lib/roby/gui/relations_view/relations_view.ui +53 -0
  307. data/lib/roby/gui/scheduler_view.css +24 -0
  308. data/lib/roby/gui/scheduler_view.rb +46 -0
  309. data/lib/roby/gui/scheduler_view.rhtml +53 -0
  310. data/lib/roby/gui/stepping.rb +93 -0
  311. data/lib/roby/gui/stepping.ui +181 -0
  312. data/lib/roby/gui/styles.rb +81 -0
  313. data/lib/roby/gui/task_display_configuration.rb +42 -0
  314. data/lib/roby/gui/task_state_at.rb +38 -0
  315. data/lib/roby/hooks.rb +26 -0
  316. data/lib/roby/interface.rb +136 -469
  317. data/lib/roby/interface/async.rb +20 -0
  318. data/lib/roby/interface/async/action_monitor.rb +188 -0
  319. data/lib/roby/interface/async/interface.rb +498 -0
  320. data/lib/roby/interface/async/job_monitor.rb +213 -0
  321. data/lib/roby/interface/async/log.rb +238 -0
  322. data/lib/roby/interface/async/new_job_listener.rb +79 -0
  323. data/lib/roby/interface/async/ui_connector.rb +183 -0
  324. data/lib/roby/interface/client.rb +553 -0
  325. data/lib/roby/interface/command.rb +24 -0
  326. data/lib/roby/interface/command_argument.rb +16 -0
  327. data/lib/roby/interface/command_library.rb +92 -0
  328. data/lib/roby/interface/droby_channel.rb +174 -0
  329. data/lib/roby/interface/exceptions.rb +22 -0
  330. data/lib/roby/interface/interface.rb +655 -0
  331. data/lib/roby/interface/job.rb +47 -0
  332. data/lib/roby/interface/rest.rb +10 -0
  333. data/lib/roby/interface/rest/api.rb +29 -0
  334. data/lib/roby/interface/rest/helpers.rb +24 -0
  335. data/lib/roby/interface/rest/server.rb +212 -0
  336. data/lib/roby/interface/server.rb +154 -0
  337. data/lib/roby/interface/shell_client.rb +468 -0
  338. data/lib/roby/interface/shell_subcommand.rb +24 -0
  339. data/lib/roby/interface/subcommand_client.rb +35 -0
  340. data/lib/roby/interface/tcp.rb +168 -0
  341. data/lib/roby/models/arguments.rb +112 -0
  342. data/lib/roby/models/plan_object.rb +83 -0
  343. data/lib/roby/models/task.rb +835 -0
  344. data/lib/roby/models/task_event.rb +62 -0
  345. data/lib/roby/models/task_service.rb +78 -0
  346. data/lib/roby/or_generator.rb +88 -0
  347. data/lib/roby/plan.rb +1751 -864
  348. data/lib/roby/plan_object.rb +611 -0
  349. data/lib/roby/plan_service.rb +200 -0
  350. data/lib/roby/promise.rb +332 -0
  351. data/lib/roby/queries.rb +23 -0
  352. data/lib/roby/queries/and_matcher.rb +32 -0
  353. data/lib/roby/queries/any.rb +27 -0
  354. data/lib/roby/queries/code_error_matcher.rb +58 -0
  355. data/lib/roby/queries/event_generator_matcher.rb +9 -0
  356. data/lib/roby/queries/execution_exception_matcher.rb +165 -0
  357. data/lib/roby/queries/index.rb +165 -0
  358. data/lib/roby/queries/localized_error_matcher.rb +149 -0
  359. data/lib/roby/queries/matcher_base.rb +107 -0
  360. data/lib/roby/queries/none.rb +27 -0
  361. data/lib/roby/queries/not_matcher.rb +30 -0
  362. data/lib/roby/queries/op_matcher.rb +8 -0
  363. data/lib/roby/queries/or_matcher.rb +30 -0
  364. data/lib/roby/queries/plan_object_matcher.rb +363 -0
  365. data/lib/roby/queries/query.rb +188 -0
  366. data/lib/roby/queries/task_event_generator_matcher.rb +86 -0
  367. data/lib/roby/queries/task_matcher.rb +344 -0
  368. data/lib/roby/relations.rb +42 -678
  369. data/lib/roby/relations/bidirectional_directed_adjacency_graph.rb +492 -0
  370. data/lib/roby/relations/directed_relation_support.rb +268 -0
  371. data/lib/roby/relations/event_relation_graph.rb +19 -0
  372. data/lib/roby/relations/fork_merge_visitor.rb +154 -0
  373. data/lib/roby/relations/graph.rb +533 -0
  374. data/lib/roby/relations/models/directed_relation_support.rb +11 -0
  375. data/lib/roby/relations/models/graph.rb +75 -0
  376. data/lib/roby/relations/models/task_relation_graph.rb +18 -0
  377. data/lib/roby/relations/space.rb +380 -0
  378. data/lib/roby/relations/task_relation_graph.rb +20 -0
  379. data/lib/roby/robot.rb +85 -38
  380. data/lib/roby/schedulers/basic.rb +155 -25
  381. data/lib/roby/schedulers/null.rb +20 -0
  382. data/lib/roby/schedulers/reporting.rb +31 -0
  383. data/lib/roby/schedulers/state.rb +129 -0
  384. data/lib/roby/schedulers/temporal.rb +91 -0
  385. data/lib/roby/singletons.rb +87 -0
  386. data/lib/roby/standalone.rb +4 -2
  387. data/lib/roby/standard_errors.rb +405 -82
  388. data/lib/roby/state.rb +6 -3
  389. data/lib/roby/state/conf_model.rb +5 -0
  390. data/lib/roby/state/events.rb +181 -95
  391. data/lib/roby/state/goal_model.rb +77 -0
  392. data/lib/roby/state/open_struct.rb +591 -0
  393. data/lib/roby/state/open_struct_model.rb +68 -0
  394. data/lib/roby/state/pos.rb +45 -45
  395. data/lib/roby/state/shapes.rb +11 -11
  396. data/lib/roby/state/state_model.rb +303 -0
  397. data/lib/roby/state/task.rb +43 -0
  398. data/lib/roby/support.rb +88 -148
  399. data/lib/roby/task.rb +1361 -1750
  400. data/lib/roby/task_arguments.rb +428 -0
  401. data/lib/roby/task_event.rb +127 -0
  402. data/lib/roby/task_event_generator.rb +337 -0
  403. data/lib/roby/task_service.rb +6 -0
  404. data/lib/roby/task_structure/conflicts.rb +104 -0
  405. data/lib/roby/task_structure/dependency.rb +932 -0
  406. data/lib/roby/task_structure/error_handling.rb +118 -0
  407. data/lib/roby/task_structure/executed_by.rb +234 -0
  408. data/lib/roby/task_structure/planned_by.rb +90 -0
  409. data/lib/roby/tasks/aggregator.rb +37 -0
  410. data/lib/roby/tasks/external_process.rb +275 -0
  411. data/lib/roby/tasks/group.rb +27 -0
  412. data/lib/roby/tasks/null.rb +19 -0
  413. data/lib/roby/tasks/parallel.rb +43 -0
  414. data/lib/roby/tasks/sequence.rb +88 -0
  415. data/lib/roby/tasks/simple.rb +21 -0
  416. data/lib/roby/{thread_task.rb → tasks/thread.rb} +50 -24
  417. data/lib/roby/tasks/timeout.rb +17 -0
  418. data/lib/roby/tasks/virtual.rb +55 -0
  419. data/lib/roby/template_plan.rb +7 -0
  420. data/lib/roby/test/aruba_minitest.rb +74 -0
  421. data/lib/roby/test/assertion.rb +16 -0
  422. data/lib/roby/test/assertions.rb +490 -0
  423. data/lib/roby/test/common.rb +368 -591
  424. data/lib/roby/test/dsl.rb +149 -0
  425. data/lib/roby/test/error.rb +18 -0
  426. data/lib/roby/test/event_reporter.rb +83 -0
  427. data/lib/roby/test/execution_expectations.rb +1134 -0
  428. data/lib/roby/test/expect_execution.rb +151 -0
  429. data/lib/roby/test/minitest_helpers.rb +166 -0
  430. data/lib/roby/test/roby_app_helpers.rb +200 -0
  431. data/lib/roby/test/run_planners.rb +155 -0
  432. data/lib/roby/test/self.rb +112 -0
  433. data/lib/roby/test/spec.rb +198 -0
  434. data/lib/roby/test/tasks/empty_task.rb +4 -4
  435. data/lib/roby/test/tasks/goto.rb +28 -27
  436. data/lib/roby/test/teardown_plans.rb +100 -0
  437. data/lib/roby/test/testcase.rb +239 -307
  438. data/lib/roby/test/tools.rb +159 -155
  439. data/lib/roby/test/validate_state_machine.rb +75 -0
  440. data/lib/roby/transaction.rb +1125 -0
  441. data/lib/roby/transaction/event_generator_proxy.rb +63 -0
  442. data/lib/roby/transaction/plan_object_proxy.rb +99 -0
  443. data/lib/roby/transaction/plan_service_proxy.rb +43 -0
  444. data/lib/roby/transaction/proxying.rb +120 -0
  445. data/lib/roby/transaction/task_event_generator_proxy.rb +19 -0
  446. data/lib/roby/transaction/task_proxy.rb +135 -0
  447. data/lib/roby/until_generator.rb +30 -0
  448. data/lib/roby/version.rb +5 -0
  449. data/lib/roby/yard.rb +169 -0
  450. data/lib/yard-roby.rb +1 -0
  451. data/manifest.xml +32 -6
  452. data/roby.gemspec +59 -0
  453. metadata +788 -587
  454. data/Manifest.txt +0 -321
  455. data/NOTES +0 -4
  456. data/README.txt +0 -166
  457. data/TODO.txt +0 -146
  458. data/app/README.txt +0 -24
  459. data/app/Rakefile +0 -8
  460. data/app/config/ROBOT.rb +0 -5
  461. data/app/config/init.rb +0 -33
  462. data/app/config/roby.yml +0 -3
  463. data/app/controllers/ROBOT.rb +0 -2
  464. data/app/planners/ROBOT/main.rb +0 -6
  465. data/app/planners/main.rb +0 -5
  466. data/app/scripts/distributed +0 -3
  467. data/app/scripts/generate/bookmarks +0 -3
  468. data/app/scripts/replay +0 -3
  469. data/app/scripts/results +0 -3
  470. data/app/scripts/run +0 -3
  471. data/app/scripts/server +0 -3
  472. data/app/scripts/shell +0 -3
  473. data/app/scripts/test +0 -3
  474. data/app/tasks/.gitattributes +0 -0
  475. data/app/tasks/ROBOT/.gitattributes +0 -0
  476. data/bin/roby-shell +0 -25
  477. data/doc/guide/src/basics/app.page +0 -139
  478. data/doc/guide/src/basics/index.page +0 -11
  479. data/doc/guide/src/basics/log_replay/goForward_1.png +0 -0
  480. data/doc/guide/src/basics/log_replay/goForward_2.png +0 -0
  481. data/doc/guide/src/basics/log_replay/goForward_3.png +0 -0
  482. data/doc/guide/src/basics/log_replay/goForward_5.png +0 -0
  483. data/doc/guide/src/basics/log_replay/plan_repair_4.png +0 -0
  484. data/doc/guide/src/basics/log_replay/roby_log_main_window.png +0 -0
  485. data/doc/guide/src/basics/relations_display.page +0 -203
  486. data/doc/guide/src/basics/shell.page +0 -102
  487. data/doc/guide/src/default.css +0 -319
  488. data/doc/guide/src/introduction/index.page +0 -29
  489. data/doc/guide/src/introduction/publications.page +0 -14
  490. data/doc/guide/src/relations/dependency.page +0 -89
  491. data/doc/guide/src/relations/index.page +0 -12
  492. data/ext/droby/dump.cc +0 -175
  493. data/ext/droby/extconf.rb +0 -3
  494. data/ext/graph/algorithm.cc +0 -746
  495. data/ext/graph/extconf.rb +0 -7
  496. data/ext/graph/graph.cc +0 -575
  497. data/ext/graph/graph.hh +0 -183
  498. data/ext/graph/iterator_sequence.hh +0 -102
  499. data/ext/graph/undirected_dfs.hh +0 -226
  500. data/ext/graph/undirected_graph.hh +0 -421
  501. data/lib/roby/app/scripts/generate/bookmarks.rb +0 -162
  502. data/lib/roby/app/scripts/replay.rb +0 -31
  503. data/lib/roby/app/scripts/server.rb +0 -18
  504. data/lib/roby/basic_object.rb +0 -151
  505. data/lib/roby/config.rb +0 -14
  506. data/lib/roby/distributed.rb +0 -36
  507. data/lib/roby/distributed/base.rb +0 -448
  508. data/lib/roby/distributed/communication.rb +0 -875
  509. data/lib/roby/distributed/connection_space.rb +0 -616
  510. data/lib/roby/distributed/distributed_object.rb +0 -206
  511. data/lib/roby/distributed/drb.rb +0 -62
  512. data/lib/roby/distributed/notifications.rb +0 -531
  513. data/lib/roby/distributed/peer.rb +0 -555
  514. data/lib/roby/distributed/protocol.rb +0 -529
  515. data/lib/roby/distributed/proxy.rb +0 -343
  516. data/lib/roby/distributed/subscription.rb +0 -311
  517. data/lib/roby/distributed/transaction.rb +0 -498
  518. data/lib/roby/external_process_task.rb +0 -225
  519. data/lib/roby/graph.rb +0 -160
  520. data/lib/roby/log.rb +0 -3
  521. data/lib/roby/log/chronicle.rb +0 -303
  522. data/lib/roby/log/console.rb +0 -74
  523. data/lib/roby/log/data_stream.rb +0 -275
  524. data/lib/roby/log/dot.rb +0 -279
  525. data/lib/roby/log/event_stream.rb +0 -161
  526. data/lib/roby/log/file.rb +0 -396
  527. data/lib/roby/log/gui/basic_display.ui +0 -83
  528. data/lib/roby/log/gui/basic_display_ui.rb +0 -89
  529. data/lib/roby/log/gui/chronicle.rb +0 -26
  530. data/lib/roby/log/gui/chronicle_view.rb +0 -40
  531. data/lib/roby/log/gui/chronicle_view.ui +0 -70
  532. data/lib/roby/log/gui/chronicle_view_ui.rb +0 -90
  533. data/lib/roby/log/gui/data_displays.rb +0 -171
  534. data/lib/roby/log/gui/data_displays.ui +0 -155
  535. data/lib/roby/log/gui/data_displays_ui.rb +0 -146
  536. data/lib/roby/log/gui/notifications.rb +0 -26
  537. data/lib/roby/log/gui/relations.rb +0 -269
  538. data/lib/roby/log/gui/relations.ui +0 -123
  539. data/lib/roby/log/gui/relations_ui.rb +0 -120
  540. data/lib/roby/log/gui/relations_view.rb +0 -185
  541. data/lib/roby/log/gui/relations_view.ui +0 -149
  542. data/lib/roby/log/gui/relations_view_ui.rb +0 -144
  543. data/lib/roby/log/gui/replay.rb +0 -366
  544. data/lib/roby/log/gui/replay_controls.rb +0 -206
  545. data/lib/roby/log/gui/replay_controls.ui +0 -282
  546. data/lib/roby/log/gui/replay_controls_ui.rb +0 -249
  547. data/lib/roby/log/gui/runtime.rb +0 -130
  548. data/lib/roby/log/hooks.rb +0 -186
  549. data/lib/roby/log/logger.rb +0 -203
  550. data/lib/roby/log/notifications.rb +0 -244
  551. data/lib/roby/log/plan_rebuilder.rb +0 -468
  552. data/lib/roby/log/relations.rb +0 -1084
  553. data/lib/roby/log/server.rb +0 -547
  554. data/lib/roby/log/sqlite.rb +0 -47
  555. data/lib/roby/log/timings.rb +0 -233
  556. data/lib/roby/plan-object.rb +0 -371
  557. data/lib/roby/planning.rb +0 -13
  558. data/lib/roby/planning/loops.rb +0 -309
  559. data/lib/roby/planning/model.rb +0 -1012
  560. data/lib/roby/planning/task.rb +0 -180
  561. data/lib/roby/query.rb +0 -655
  562. data/lib/roby/relations/conflicts.rb +0 -67
  563. data/lib/roby/relations/dependency.rb +0 -358
  564. data/lib/roby/relations/ensured.rb +0 -19
  565. data/lib/roby/relations/error_handling.rb +0 -22
  566. data/lib/roby/relations/events.rb +0 -7
  567. data/lib/roby/relations/executed_by.rb +0 -208
  568. data/lib/roby/relations/influence.rb +0 -10
  569. data/lib/roby/relations/planned_by.rb +0 -63
  570. data/lib/roby/state/information.rb +0 -55
  571. data/lib/roby/state/state.rb +0 -367
  572. data/lib/roby/task-operations.rb +0 -186
  573. data/lib/roby/task_index.rb +0 -80
  574. data/lib/roby/test/distributed.rb +0 -230
  575. data/lib/roby/test/tasks/simple_task.rb +0 -23
  576. data/lib/roby/transactions.rb +0 -507
  577. data/lib/roby/transactions/proxy.rb +0 -325
  578. data/plugins/fault_injection/History.txt +0 -4
  579. data/plugins/fault_injection/README.txt +0 -34
  580. data/plugins/fault_injection/Rakefile +0 -12
  581. data/plugins/fault_injection/TODO.txt +0 -0
  582. data/plugins/fault_injection/app.rb +0 -52
  583. data/plugins/fault_injection/fault_injection.rb +0 -89
  584. data/plugins/fault_injection/test/test_fault_injection.rb +0 -78
  585. data/plugins/subsystems/README.txt +0 -37
  586. data/plugins/subsystems/Rakefile +0 -13
  587. data/plugins/subsystems/app.rb +0 -182
  588. data/plugins/subsystems/test/app/README +0 -24
  589. data/plugins/subsystems/test/app/Rakefile +0 -8
  590. data/plugins/subsystems/test/app/config/app.yml +0 -71
  591. data/plugins/subsystems/test/app/config/init.rb +0 -12
  592. data/plugins/subsystems/test/app/config/roby.yml +0 -3
  593. data/plugins/subsystems/test/app/planners/main.rb +0 -20
  594. data/plugins/subsystems/test/app/scripts/distributed +0 -3
  595. data/plugins/subsystems/test/app/scripts/replay +0 -3
  596. data/plugins/subsystems/test/app/scripts/results +0 -3
  597. data/plugins/subsystems/test/app/scripts/run +0 -3
  598. data/plugins/subsystems/test/app/scripts/server +0 -3
  599. data/plugins/subsystems/test/app/scripts/shell +0 -3
  600. data/plugins/subsystems/test/app/scripts/test +0 -3
  601. data/plugins/subsystems/test/app/tasks/services.rb +0 -15
  602. data/plugins/subsystems/test/test_subsystems.rb +0 -78
  603. data/test/distributed/test_communication.rb +0 -195
  604. data/test/distributed/test_connection.rb +0 -284
  605. data/test/distributed/test_execution.rb +0 -378
  606. data/test/distributed/test_mixed_plan.rb +0 -341
  607. data/test/distributed/test_plan_notifications.rb +0 -238
  608. data/test/distributed/test_protocol.rb +0 -525
  609. data/test/distributed/test_query.rb +0 -106
  610. data/test/distributed/test_remote_plan.rb +0 -491
  611. data/test/distributed/test_transaction.rb +0 -466
  612. data/test/mockups/external_process +0 -28
  613. data/test/mockups/tasks.rb +0 -27
  614. data/test/planning/test_loops.rb +0 -432
  615. data/test/planning/test_model.rb +0 -427
  616. data/test/planning/test_task.rb +0 -126
  617. data/test/relations/test_conflicts.rb +0 -42
  618. data/test/relations/test_dependency.rb +0 -324
  619. data/test/relations/test_ensured.rb +0 -38
  620. data/test/relations/test_executed_by.rb +0 -224
  621. data/test/relations/test_planned_by.rb +0 -56
  622. data/test/suite_core.rb +0 -29
  623. data/test/suite_distributed.rb +0 -10
  624. data/test/suite_planning.rb +0 -4
  625. data/test/suite_relations.rb +0 -8
  626. data/test/tasks/test_external_process.rb +0 -126
  627. data/test/tasks/test_thread_task.rb +0 -70
  628. data/test/test_bgl.rb +0 -528
  629. data/test/test_event.rb +0 -969
  630. data/test/test_exceptions.rb +0 -591
  631. data/test/test_execution_engine.rb +0 -987
  632. data/test/test_gui.rb +0 -20
  633. data/test/test_interface.rb +0 -43
  634. data/test/test_log.rb +0 -125
  635. data/test/test_log_server.rb +0 -133
  636. data/test/test_plan.rb +0 -418
  637. data/test/test_query.rb +0 -424
  638. data/test/test_relations.rb +0 -260
  639. data/test/test_state.rb +0 -432
  640. data/test/test_support.rb +0 -16
  641. data/test/test_task.rb +0 -1181
  642. data/test/test_testcase.rb +0 -138
  643. data/test/test_transactions.rb +0 -610
  644. data/test/test_transactions_proxy.rb +0 -216
@@ -0,0 +1,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