roby 0.8.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (644) hide show
  1. checksums.yaml +7 -0
  2. data/.deep-cover.rb +3 -0
  3. data/.gitattributes +1 -0
  4. data/.gitignore +24 -0
  5. data/.simplecov +10 -0
  6. data/.travis.yml +17 -0
  7. data/.yardopts +4 -0
  8. data/Gemfile +15 -0
  9. data/README.md +11 -0
  10. data/Rakefile +47 -177
  11. data/benchmark/{alloc_misc.rb → attic/alloc_misc.rb} +2 -2
  12. data/benchmark/{discovery_latency.rb → attic/discovery_latency.rb} +19 -19
  13. data/benchmark/{garbage_collection.rb → attic/garbage_collection.rb} +9 -9
  14. data/benchmark/{genom.rb → attic/genom.rb} +0 -0
  15. data/benchmark/attic/transactions.rb +62 -0
  16. data/benchmark/plan_basic_operations.rb +28 -0
  17. data/benchmark/relations/graph.rb +63 -0
  18. data/benchmark/ruby/identity.rb +18 -0
  19. data/benchmark/ruby/set_intersect_vs_hash_merge.rb +39 -0
  20. data/benchmark/ruby/yield_vs_block.rb +35 -0
  21. data/benchmark/run +5 -0
  22. data/benchmark/synthetic_plan_modifications_with_transactions.rb +79 -0
  23. data/benchmark/transactions.rb +99 -51
  24. data/bin/roby +38 -197
  25. data/bin/roby-display +14 -0
  26. data/bin/roby-log +3 -176
  27. data/doc/guide/{src → attic}/abstraction/achieve_with.page +1 -1
  28. data/doc/guide/{src → attic}/abstraction/forwarding.page +1 -1
  29. data/doc/guide/{src → attic}/abstraction/hierarchy.page +1 -1
  30. data/doc/guide/{src → attic}/abstraction/index.page +1 -1
  31. data/doc/guide/{src → attic}/abstraction/task_models.page +1 -1
  32. data/doc/guide/{overview.rdoc → attic/cycle/api_overview.rdoc} +6 -1
  33. data/doc/guide/{src → attic}/cycle/cycle-overview.png +0 -0
  34. data/doc/guide/{src → attic}/cycle/cycle-overview.svg +0 -0
  35. data/doc/guide/attic/cycle/error_handling.page +98 -0
  36. data/doc/guide/{src → attic}/cycle/error_instantaneous_repair.png +0 -0
  37. data/doc/guide/{src → attic}/cycle/error_instantaneous_repair.svg +0 -0
  38. data/doc/guide/{src/cycle/error_handling.page → attic/cycle/error_sources.page} +46 -89
  39. data/doc/guide/{src → attic}/cycle/garbage_collection.page +1 -1
  40. data/doc/guide/{src → attic}/cycle/index.page +1 -1
  41. data/doc/guide/{src → attic}/cycle/propagation.page +11 -1
  42. data/doc/guide/{src → attic}/cycle/propagation_diamond.png +0 -0
  43. data/doc/guide/{src → attic}/cycle/propagation_diamond.svg +0 -0
  44. data/doc/guide/attic/plans/building_plans.page +89 -0
  45. data/doc/guide/attic/plans/code.page +192 -0
  46. data/doc/guide/{src/basics → attic/plans}/events.page +3 -4
  47. data/doc/guide/attic/plans/index.page +7 -0
  48. data/doc/guide/{plan_modifications.rdoc → attic/plans/plan_modifications.rdoc} +5 -3
  49. data/doc/guide/{src/basics → attic/plans}/plan_objects.page +2 -1
  50. data/doc/guide/attic/plans/querying_plans.page +5 -0
  51. data/doc/guide/{src/basics → attic/plans}/tasks.page +20 -20
  52. data/doc/guide/config.yaml +7 -4
  53. data/doc/guide/ext/extended_menu.rb +29 -0
  54. data/doc/guide/ext/init.rb +6 -0
  55. data/doc/guide/ext/rdoc_links.rb +7 -6
  56. data/doc/guide/src/advanced_concepts/history.page +5 -0
  57. data/doc/guide/src/advanced_concepts/index.page +11 -0
  58. data/doc/guide/src/advanced_concepts/recognizing_patterns.page +83 -0
  59. data/doc/guide/src/advanced_concepts/scheduling.page +87 -0
  60. data/doc/guide/src/advanced_concepts/transactions.page +5 -0
  61. data/doc/guide/src/advanced_concepts/unreachability.page +42 -0
  62. data/doc/guide/src/base.template +96 -0
  63. data/doc/guide/src/basics_shell_header.txt +5 -7
  64. data/doc/guide/src/building/action_coordination.page +96 -0
  65. data/doc/guide/src/building/actions.page +124 -0
  66. data/doc/guide/src/building/file_layout.page +71 -0
  67. data/doc/guide/src/building/index.page +50 -0
  68. data/doc/guide/src/building/patterns.page +86 -0
  69. data/doc/guide/src/building/patterns_forwarding.png +0 -0
  70. data/doc/guide/src/building/patterns_forwarding.svg +277 -0
  71. data/doc/guide/src/building/runtime.page +95 -0
  72. data/doc/guide/src/building/task_models.page +94 -0
  73. data/doc/guide/src/building/tasks.page +284 -0
  74. data/doc/guide/src/concepts/error_handling.page +100 -0
  75. data/doc/guide/src/concepts/exception_propagation.png +0 -0
  76. data/doc/guide/src/concepts/exception_propagation.svg +445 -0
  77. data/doc/guide/src/concepts/execution.page +85 -0
  78. data/doc/guide/src/concepts/execution.png +0 -0
  79. data/doc/guide/src/concepts/execution.svg +573 -0
  80. data/doc/guide/src/concepts/execution_cycle.png +0 -0
  81. data/doc/guide/src/concepts/garbage_collection.page +57 -0
  82. data/doc/guide/src/concepts/index.page +27 -0
  83. data/doc/guide/src/concepts/plans.page +101 -0
  84. data/doc/guide/src/concepts/policy.page +31 -0
  85. data/doc/guide/src/concepts/reactor.page +61 -0
  86. data/doc/guide/src/concepts/simple_plan_example.png +0 -0
  87. data/doc/guide/src/concepts/simple_plan_example.svg +376 -0
  88. data/doc/guide/src/default.template +9 -74
  89. data/doc/guide/src/event_relations/forward.page +71 -0
  90. data/doc/guide/src/event_relations/index.page +12 -0
  91. data/doc/guide/src/event_relations/scheduling_constraints.page +43 -0
  92. data/doc/guide/src/event_relations/signal.page +55 -0
  93. data/doc/guide/src/event_relations/temporal_constraints.page +77 -0
  94. data/doc/guide/src/htmldoc.metainfo +21 -8
  95. data/doc/guide/src/index.page +8 -3
  96. data/doc/guide/src/{introduction/install.page → installation/index.page} +37 -25
  97. data/doc/guide/src/installation/publications.page +14 -0
  98. data/doc/guide/src/{introduction → installation}/videos.page +14 -7
  99. data/doc/guide/src/interacting/index.page +16 -0
  100. data/doc/guide/src/interacting/run.page +33 -0
  101. data/doc/guide/src/interacting/shell.page +95 -0
  102. data/doc/guide/src/plugins/creating_plugins.page +72 -0
  103. data/doc/guide/src/plugins/index.page +27 -5
  104. data/doc/guide/src/plugins/{fault_tolerance.page → standard_plugins/fault_tolerance.page} +2 -2
  105. data/doc/guide/src/plugins/standard_plugins/index.page +11 -0
  106. data/doc/guide/src/plugins/{subsystems.page → standard_plugins/subsystems.page} +2 -2
  107. data/doc/guide/src/style_screen.css +687 -0
  108. data/doc/guide/src/task_relations/dependency.page +107 -0
  109. data/doc/guide/src/task_relations/executed_by.page +77 -0
  110. data/doc/guide/src/task_relations/index.page +12 -0
  111. data/doc/guide/src/task_relations/new_relations.page +119 -0
  112. data/doc/guide/src/task_relations/planned_by.page +46 -0
  113. data/doc/guide/src/tutorial/app.page +117 -0
  114. data/doc/guide/src/{basics → tutorial}/code_examples.page +6 -5
  115. data/doc/guide/src/{basics → tutorial}/dry.page +15 -15
  116. data/doc/guide/src/{basics → tutorial}/errors.page +43 -68
  117. data/doc/guide/src/tutorial/events.page +195 -0
  118. data/doc/guide/src/{basics → tutorial}/hierarchy.page +53 -52
  119. data/doc/guide/src/tutorial/index.page +13 -0
  120. data/doc/guide/src/tutorial/log_replay/goForward_1.png +0 -0
  121. data/doc/guide/src/tutorial/log_replay/goForward_2.png +0 -0
  122. data/doc/guide/src/tutorial/log_replay/goForward_3.png +0 -0
  123. data/doc/guide/src/{basics → tutorial}/log_replay/goForward_4.png +0 -0
  124. data/doc/guide/src/tutorial/log_replay/goForward_5.png +0 -0
  125. data/doc/guide/src/{basics → tutorial}/log_replay/hierarchy_error_1.png +0 -0
  126. data/doc/guide/src/{basics → tutorial}/log_replay/hierarchy_error_2.png +0 -0
  127. data/doc/guide/src/{basics → tutorial}/log_replay/hierarchy_error_3.png +0 -0
  128. data/doc/guide/src/tutorial/log_replay/moveto_code_error.png +0 -0
  129. data/doc/guide/src/{basics → tutorial}/log_replay/plan_repair_1.png +0 -0
  130. data/doc/guide/src/{basics → tutorial}/log_replay/plan_repair_2.png +0 -0
  131. data/doc/guide/src/{basics → tutorial}/log_replay/plan_repair_3.png +0 -0
  132. data/doc/guide/src/tutorial/log_replay/plan_repair_4.png +0 -0
  133. data/doc/guide/src/tutorial/log_replay/roby_log_main_window.png +0 -0
  134. data/doc/guide/src/{basics → tutorial}/log_replay/roby_log_relation_window.png +0 -0
  135. data/doc/guide/src/{basics → tutorial}/log_replay/roby_replay_event_representation.png +0 -0
  136. data/doc/guide/src/tutorial/relations_display.page +153 -0
  137. data/doc/guide/src/{basics → tutorial}/roby_cycle_overview.png +0 -0
  138. data/doc/guide/src/tutorial/shell.page +121 -0
  139. data/doc/guide/src/{basics → tutorial}/summary.page +1 -1
  140. data/doc/guide/src/tutorial/tasks.page +374 -0
  141. data/lib/roby.rb +102 -47
  142. data/lib/roby/actions.rb +17 -0
  143. data/lib/roby/actions/action.rb +80 -0
  144. data/lib/roby/actions/interface.rb +45 -0
  145. data/lib/roby/actions/library.rb +23 -0
  146. data/lib/roby/actions/models/action.rb +224 -0
  147. data/lib/roby/actions/models/coordination_action.rb +58 -0
  148. data/lib/roby/actions/models/interface.rb +22 -0
  149. data/lib/roby/actions/models/interface_base.rb +294 -0
  150. data/lib/roby/actions/models/library.rb +12 -0
  151. data/lib/roby/actions/models/method_action.rb +90 -0
  152. data/lib/roby/actions/task.rb +114 -0
  153. data/lib/roby/and_generator.rb +125 -0
  154. data/lib/roby/app.rb +2795 -829
  155. data/lib/roby/app/autotest_console_reporter.rb +138 -0
  156. data/lib/roby/app/base.rb +21 -0
  157. data/lib/roby/app/cucumber.rb +2 -0
  158. data/lib/roby/app/cucumber/controller.rb +439 -0
  159. data/lib/roby/app/cucumber/helpers.rb +280 -0
  160. data/lib/roby/app/cucumber/world.rb +32 -0
  161. data/lib/roby/app/debug.rb +136 -0
  162. data/lib/roby/app/gen.rb +2 -0
  163. data/lib/roby/app/rake.rb +178 -38
  164. data/lib/roby/app/robot_config.rb +9 -0
  165. data/lib/roby/app/robot_names.rb +115 -0
  166. data/lib/roby/app/run.rb +3 -2
  167. data/lib/roby/app/scripts.rb +72 -0
  168. data/lib/roby/app/scripts/autotest.rb +173 -0
  169. data/lib/roby/app/scripts/display.rb +2 -0
  170. data/lib/roby/app/scripts/restart.rb +52 -0
  171. data/lib/roby/app/scripts/results.rb +17 -8
  172. data/lib/roby/app/scripts/run.rb +155 -24
  173. data/lib/roby/app/scripts/shell.rb +147 -62
  174. data/lib/roby/app/scripts/test.rb +107 -22
  175. data/lib/roby/app/test_reporter.rb +74 -0
  176. data/lib/roby/app/test_server.rb +159 -0
  177. data/lib/roby/app/vagrant.rb +47 -0
  178. data/lib/roby/backports.rb +16 -0
  179. data/lib/roby/cli/display.rb +190 -0
  180. data/lib/roby/cli/exceptions.rb +17 -0
  181. data/lib/roby/cli/gen/actions/class.rb +5 -0
  182. data/lib/roby/cli/gen/actions/test.rb +6 -0
  183. data/lib/roby/cli/gen/app/.yardopts +6 -0
  184. data/lib/roby/cli/gen/app/README.md +28 -0
  185. data/lib/roby/cli/gen/app/Rakefile +15 -0
  186. data/{app → lib/roby/cli/gen/app}/config/app.yml +29 -39
  187. data/lib/roby/cli/gen/app/models/.gitattributes +1 -0
  188. data/{app → lib/roby/cli/gen/app/scripts}/controllers/.gitattributes +0 -0
  189. data/{app/data/.gitattributes → lib/roby/cli/gen/app/test/.gitignore} +0 -0
  190. data/lib/roby/cli/gen/class/class.rb +6 -0
  191. data/lib/roby/cli/gen/class/test.rb +7 -0
  192. data/lib/roby/cli/gen/helpers.rb +203 -0
  193. data/lib/roby/cli/gen/module/module.rb +5 -0
  194. data/lib/roby/cli/gen/module/test.rb +6 -0
  195. data/lib/roby/cli/gen/roby_app/config/init.rb +17 -0
  196. data/lib/roby/cli/gen/roby_app/config/robots/robot.rb +40 -0
  197. data/lib/roby/cli/gen/task/class.rb +44 -0
  198. data/lib/roby/cli/gen/task/test.rb +6 -0
  199. data/lib/roby/cli/gen_main.rb +120 -0
  200. data/lib/roby/cli/log.rb +276 -0
  201. data/lib/roby/cli/log/flamegraph.html +499 -0
  202. data/lib/roby/cli/log/flamegraph_renderer.rb +88 -0
  203. data/lib/roby/cli/main.rb +153 -0
  204. data/lib/roby/coordination.rb +60 -0
  205. data/lib/roby/coordination/action_script.rb +25 -0
  206. data/lib/roby/coordination/action_state_machine.rb +125 -0
  207. data/lib/roby/coordination/actions.rb +106 -0
  208. data/lib/roby/coordination/base.rb +145 -0
  209. data/lib/roby/coordination/calculus.rb +40 -0
  210. data/lib/roby/coordination/child.rb +28 -0
  211. data/lib/roby/coordination/event.rb +29 -0
  212. data/lib/roby/coordination/fault_handler.rb +25 -0
  213. data/lib/roby/coordination/fault_handling_task.rb +13 -0
  214. data/lib/roby/coordination/fault_response_table.rb +110 -0
  215. data/lib/roby/coordination/models/action_script.rb +64 -0
  216. data/lib/roby/coordination/models/action_state_machine.rb +224 -0
  217. data/lib/roby/coordination/models/actions.rb +191 -0
  218. data/lib/roby/coordination/models/arguments.rb +55 -0
  219. data/lib/roby/coordination/models/base.rb +176 -0
  220. data/lib/roby/coordination/models/capture.rb +86 -0
  221. data/lib/roby/coordination/models/child.rb +35 -0
  222. data/lib/roby/coordination/models/event.rb +41 -0
  223. data/lib/roby/coordination/models/exceptions.rb +42 -0
  224. data/lib/roby/coordination/models/fault_handler.rb +219 -0
  225. data/lib/roby/coordination/models/fault_response_table.rb +77 -0
  226. data/lib/roby/coordination/models/root.rb +22 -0
  227. data/lib/roby/coordination/models/script.rb +283 -0
  228. data/lib/roby/coordination/models/task.rb +184 -0
  229. data/lib/roby/coordination/models/task_from_action.rb +50 -0
  230. data/lib/roby/coordination/models/task_from_as_plan.rb +33 -0
  231. data/lib/roby/coordination/models/task_from_instanciation_object.rb +31 -0
  232. data/lib/roby/coordination/models/task_from_variable.rb +27 -0
  233. data/lib/roby/coordination/models/task_with_dependencies.rb +48 -0
  234. data/lib/roby/coordination/models/variable.rb +32 -0
  235. data/lib/roby/coordination/script.rb +200 -0
  236. data/lib/roby/coordination/script_instruction.rb +12 -0
  237. data/lib/roby/coordination/task.rb +45 -0
  238. data/lib/roby/coordination/task_base.rb +69 -0
  239. data/lib/roby/coordination/task_script.rb +293 -0
  240. data/lib/roby/coordination/task_state_machine.rb +308 -0
  241. data/lib/roby/decision_control.rb +33 -21
  242. data/lib/roby/distributed_object.rb +76 -0
  243. data/lib/roby/droby.rb +17 -0
  244. data/lib/roby/droby/droby_id.rb +6 -0
  245. data/lib/roby/droby/enable.rb +153 -0
  246. data/lib/roby/droby/event_logger.rb +189 -0
  247. data/lib/roby/droby/event_logging.rb +57 -0
  248. data/lib/roby/droby/exceptions.rb +14 -0
  249. data/lib/roby/droby/identifiable.rb +22 -0
  250. data/lib/roby/droby/logfile.rb +141 -0
  251. data/lib/roby/droby/logfile/client.rb +176 -0
  252. data/lib/roby/droby/logfile/file_format.md +97 -0
  253. data/lib/roby/droby/logfile/index.rb +117 -0
  254. data/lib/roby/droby/logfile/reader.rb +139 -0
  255. data/lib/roby/droby/logfile/server.rb +199 -0
  256. data/lib/roby/droby/logfile/writer.rb +114 -0
  257. data/lib/roby/droby/marshal.rb +264 -0
  258. data/lib/roby/droby/marshallable.rb +12 -0
  259. data/lib/roby/droby/null_event_logger.rb +25 -0
  260. data/lib/roby/droby/object_manager.rb +205 -0
  261. data/lib/roby/droby/peer_id.rb +6 -0
  262. data/lib/roby/droby/plan_rebuilder.rb +373 -0
  263. data/lib/roby/droby/rebuilt_plan.rb +160 -0
  264. data/lib/roby/droby/remote_droby_id.rb +6 -0
  265. data/lib/roby/droby/timepoints.rb +205 -0
  266. data/lib/roby/droby/timepoints_ctf.metadata.erb +101 -0
  267. data/lib/roby/droby/timepoints_ctf.rb +125 -0
  268. data/lib/roby/droby/v5.rb +14 -0
  269. data/lib/roby/droby/v5/builtin.rb +120 -0
  270. data/lib/roby/droby/v5/droby_class.rb +45 -0
  271. data/lib/roby/droby/v5/droby_constant.rb +81 -0
  272. data/lib/roby/droby/v5/droby_dump.rb +1026 -0
  273. data/lib/roby/droby/v5/droby_id.rb +44 -0
  274. data/lib/roby/droby/v5/droby_model.rb +82 -0
  275. data/lib/roby/droby/v5/peer_id.rb +10 -0
  276. data/lib/roby/droby/v5/remote_droby_id.rb +42 -0
  277. data/lib/roby/event.rb +79 -957
  278. data/lib/roby/event_constraints.rb +835 -0
  279. data/lib/roby/event_generator.rb +1047 -0
  280. data/lib/roby/event_structure/causal_link.rb +6 -0
  281. data/lib/roby/event_structure/forwarding.rb +6 -0
  282. data/lib/roby/event_structure/precedence.rb +7 -0
  283. data/lib/roby/event_structure/signal.rb +8 -0
  284. data/lib/roby/event_structure/temporal_constraints.rb +640 -0
  285. data/lib/roby/exceptions.rb +446 -152
  286. data/lib/roby/executable_plan.rb +549 -0
  287. data/lib/roby/execution_engine.rb +1997 -950
  288. data/lib/roby/filter_generator.rb +26 -0
  289. data/lib/roby/gui/chronicle_view.rb +225 -0
  290. data/lib/roby/gui/chronicle_widget.rb +925 -0
  291. data/lib/roby/gui/dot_id.rb +11 -0
  292. data/lib/roby/gui/exception_view.rb +44 -0
  293. data/lib/roby/gui/log_display.rb +273 -0
  294. data/lib/roby/gui/model_views.rb +2 -0
  295. data/lib/roby/gui/model_views/action_interface.rb +53 -0
  296. data/lib/roby/gui/model_views/task.rb +47 -0
  297. data/lib/roby/gui/model_views/task.rhtml +41 -0
  298. data/lib/roby/gui/object_info_view.rb +89 -0
  299. data/lib/roby/gui/plan_dot_layout.rb +427 -0
  300. data/lib/roby/gui/plan_rebuilder_widget.rb +357 -0
  301. data/lib/roby/gui/qt4_toMSecsSinceEpoch.rb +8 -0
  302. data/lib/roby/gui/relations_view.rb +278 -0
  303. data/lib/roby/gui/relations_view/relations.ui +139 -0
  304. data/lib/roby/gui/relations_view/relations_canvas.rb +1088 -0
  305. data/lib/roby/gui/relations_view/relations_config.rb +292 -0
  306. data/lib/roby/gui/relations_view/relations_view.ui +53 -0
  307. data/lib/roby/gui/scheduler_view.css +24 -0
  308. data/lib/roby/gui/scheduler_view.rb +46 -0
  309. data/lib/roby/gui/scheduler_view.rhtml +53 -0
  310. data/lib/roby/gui/stepping.rb +93 -0
  311. data/lib/roby/gui/stepping.ui +181 -0
  312. data/lib/roby/gui/styles.rb +81 -0
  313. data/lib/roby/gui/task_display_configuration.rb +42 -0
  314. data/lib/roby/gui/task_state_at.rb +38 -0
  315. data/lib/roby/hooks.rb +26 -0
  316. data/lib/roby/interface.rb +136 -469
  317. data/lib/roby/interface/async.rb +20 -0
  318. data/lib/roby/interface/async/action_monitor.rb +188 -0
  319. data/lib/roby/interface/async/interface.rb +498 -0
  320. data/lib/roby/interface/async/job_monitor.rb +213 -0
  321. data/lib/roby/interface/async/log.rb +238 -0
  322. data/lib/roby/interface/async/new_job_listener.rb +79 -0
  323. data/lib/roby/interface/async/ui_connector.rb +183 -0
  324. data/lib/roby/interface/client.rb +553 -0
  325. data/lib/roby/interface/command.rb +24 -0
  326. data/lib/roby/interface/command_argument.rb +16 -0
  327. data/lib/roby/interface/command_library.rb +92 -0
  328. data/lib/roby/interface/droby_channel.rb +174 -0
  329. data/lib/roby/interface/exceptions.rb +22 -0
  330. data/lib/roby/interface/interface.rb +655 -0
  331. data/lib/roby/interface/job.rb +47 -0
  332. data/lib/roby/interface/rest.rb +10 -0
  333. data/lib/roby/interface/rest/api.rb +29 -0
  334. data/lib/roby/interface/rest/helpers.rb +24 -0
  335. data/lib/roby/interface/rest/server.rb +212 -0
  336. data/lib/roby/interface/server.rb +154 -0
  337. data/lib/roby/interface/shell_client.rb +468 -0
  338. data/lib/roby/interface/shell_subcommand.rb +24 -0
  339. data/lib/roby/interface/subcommand_client.rb +35 -0
  340. data/lib/roby/interface/tcp.rb +168 -0
  341. data/lib/roby/models/arguments.rb +112 -0
  342. data/lib/roby/models/plan_object.rb +83 -0
  343. data/lib/roby/models/task.rb +835 -0
  344. data/lib/roby/models/task_event.rb +62 -0
  345. data/lib/roby/models/task_service.rb +78 -0
  346. data/lib/roby/or_generator.rb +88 -0
  347. data/lib/roby/plan.rb +1751 -864
  348. data/lib/roby/plan_object.rb +611 -0
  349. data/lib/roby/plan_service.rb +200 -0
  350. data/lib/roby/promise.rb +332 -0
  351. data/lib/roby/queries.rb +23 -0
  352. data/lib/roby/queries/and_matcher.rb +32 -0
  353. data/lib/roby/queries/any.rb +27 -0
  354. data/lib/roby/queries/code_error_matcher.rb +58 -0
  355. data/lib/roby/queries/event_generator_matcher.rb +9 -0
  356. data/lib/roby/queries/execution_exception_matcher.rb +165 -0
  357. data/lib/roby/queries/index.rb +165 -0
  358. data/lib/roby/queries/localized_error_matcher.rb +149 -0
  359. data/lib/roby/queries/matcher_base.rb +107 -0
  360. data/lib/roby/queries/none.rb +27 -0
  361. data/lib/roby/queries/not_matcher.rb +30 -0
  362. data/lib/roby/queries/op_matcher.rb +8 -0
  363. data/lib/roby/queries/or_matcher.rb +30 -0
  364. data/lib/roby/queries/plan_object_matcher.rb +363 -0
  365. data/lib/roby/queries/query.rb +188 -0
  366. data/lib/roby/queries/task_event_generator_matcher.rb +86 -0
  367. data/lib/roby/queries/task_matcher.rb +344 -0
  368. data/lib/roby/relations.rb +42 -678
  369. data/lib/roby/relations/bidirectional_directed_adjacency_graph.rb +492 -0
  370. data/lib/roby/relations/directed_relation_support.rb +268 -0
  371. data/lib/roby/relations/event_relation_graph.rb +19 -0
  372. data/lib/roby/relations/fork_merge_visitor.rb +154 -0
  373. data/lib/roby/relations/graph.rb +533 -0
  374. data/lib/roby/relations/models/directed_relation_support.rb +11 -0
  375. data/lib/roby/relations/models/graph.rb +75 -0
  376. data/lib/roby/relations/models/task_relation_graph.rb +18 -0
  377. data/lib/roby/relations/space.rb +380 -0
  378. data/lib/roby/relations/task_relation_graph.rb +20 -0
  379. data/lib/roby/robot.rb +85 -38
  380. data/lib/roby/schedulers/basic.rb +155 -25
  381. data/lib/roby/schedulers/null.rb +20 -0
  382. data/lib/roby/schedulers/reporting.rb +31 -0
  383. data/lib/roby/schedulers/state.rb +129 -0
  384. data/lib/roby/schedulers/temporal.rb +91 -0
  385. data/lib/roby/singletons.rb +87 -0
  386. data/lib/roby/standalone.rb +4 -2
  387. data/lib/roby/standard_errors.rb +405 -82
  388. data/lib/roby/state.rb +6 -3
  389. data/lib/roby/state/conf_model.rb +5 -0
  390. data/lib/roby/state/events.rb +181 -95
  391. data/lib/roby/state/goal_model.rb +77 -0
  392. data/lib/roby/state/open_struct.rb +591 -0
  393. data/lib/roby/state/open_struct_model.rb +68 -0
  394. data/lib/roby/state/pos.rb +45 -45
  395. data/lib/roby/state/shapes.rb +11 -11
  396. data/lib/roby/state/state_model.rb +303 -0
  397. data/lib/roby/state/task.rb +43 -0
  398. data/lib/roby/support.rb +88 -148
  399. data/lib/roby/task.rb +1361 -1750
  400. data/lib/roby/task_arguments.rb +428 -0
  401. data/lib/roby/task_event.rb +127 -0
  402. data/lib/roby/task_event_generator.rb +337 -0
  403. data/lib/roby/task_service.rb +6 -0
  404. data/lib/roby/task_structure/conflicts.rb +104 -0
  405. data/lib/roby/task_structure/dependency.rb +932 -0
  406. data/lib/roby/task_structure/error_handling.rb +118 -0
  407. data/lib/roby/task_structure/executed_by.rb +234 -0
  408. data/lib/roby/task_structure/planned_by.rb +90 -0
  409. data/lib/roby/tasks/aggregator.rb +37 -0
  410. data/lib/roby/tasks/external_process.rb +275 -0
  411. data/lib/roby/tasks/group.rb +27 -0
  412. data/lib/roby/tasks/null.rb +19 -0
  413. data/lib/roby/tasks/parallel.rb +43 -0
  414. data/lib/roby/tasks/sequence.rb +88 -0
  415. data/lib/roby/tasks/simple.rb +21 -0
  416. data/lib/roby/{thread_task.rb → tasks/thread.rb} +50 -24
  417. data/lib/roby/tasks/timeout.rb +17 -0
  418. data/lib/roby/tasks/virtual.rb +55 -0
  419. data/lib/roby/template_plan.rb +7 -0
  420. data/lib/roby/test/aruba_minitest.rb +74 -0
  421. data/lib/roby/test/assertion.rb +16 -0
  422. data/lib/roby/test/assertions.rb +490 -0
  423. data/lib/roby/test/common.rb +368 -591
  424. data/lib/roby/test/dsl.rb +149 -0
  425. data/lib/roby/test/error.rb +18 -0
  426. data/lib/roby/test/event_reporter.rb +83 -0
  427. data/lib/roby/test/execution_expectations.rb +1134 -0
  428. data/lib/roby/test/expect_execution.rb +151 -0
  429. data/lib/roby/test/minitest_helpers.rb +166 -0
  430. data/lib/roby/test/roby_app_helpers.rb +200 -0
  431. data/lib/roby/test/run_planners.rb +155 -0
  432. data/lib/roby/test/self.rb +112 -0
  433. data/lib/roby/test/spec.rb +198 -0
  434. data/lib/roby/test/tasks/empty_task.rb +4 -4
  435. data/lib/roby/test/tasks/goto.rb +28 -27
  436. data/lib/roby/test/teardown_plans.rb +100 -0
  437. data/lib/roby/test/testcase.rb +239 -307
  438. data/lib/roby/test/tools.rb +159 -155
  439. data/lib/roby/test/validate_state_machine.rb +75 -0
  440. data/lib/roby/transaction.rb +1125 -0
  441. data/lib/roby/transaction/event_generator_proxy.rb +63 -0
  442. data/lib/roby/transaction/plan_object_proxy.rb +99 -0
  443. data/lib/roby/transaction/plan_service_proxy.rb +43 -0
  444. data/lib/roby/transaction/proxying.rb +120 -0
  445. data/lib/roby/transaction/task_event_generator_proxy.rb +19 -0
  446. data/lib/roby/transaction/task_proxy.rb +135 -0
  447. data/lib/roby/until_generator.rb +30 -0
  448. data/lib/roby/version.rb +5 -0
  449. data/lib/roby/yard.rb +169 -0
  450. data/lib/yard-roby.rb +1 -0
  451. data/manifest.xml +32 -6
  452. data/roby.gemspec +59 -0
  453. metadata +788 -587
  454. data/Manifest.txt +0 -321
  455. data/NOTES +0 -4
  456. data/README.txt +0 -166
  457. data/TODO.txt +0 -146
  458. data/app/README.txt +0 -24
  459. data/app/Rakefile +0 -8
  460. data/app/config/ROBOT.rb +0 -5
  461. data/app/config/init.rb +0 -33
  462. data/app/config/roby.yml +0 -3
  463. data/app/controllers/ROBOT.rb +0 -2
  464. data/app/planners/ROBOT/main.rb +0 -6
  465. data/app/planners/main.rb +0 -5
  466. data/app/scripts/distributed +0 -3
  467. data/app/scripts/generate/bookmarks +0 -3
  468. data/app/scripts/replay +0 -3
  469. data/app/scripts/results +0 -3
  470. data/app/scripts/run +0 -3
  471. data/app/scripts/server +0 -3
  472. data/app/scripts/shell +0 -3
  473. data/app/scripts/test +0 -3
  474. data/app/tasks/.gitattributes +0 -0
  475. data/app/tasks/ROBOT/.gitattributes +0 -0
  476. data/bin/roby-shell +0 -25
  477. data/doc/guide/src/basics/app.page +0 -139
  478. data/doc/guide/src/basics/index.page +0 -11
  479. data/doc/guide/src/basics/log_replay/goForward_1.png +0 -0
  480. data/doc/guide/src/basics/log_replay/goForward_2.png +0 -0
  481. data/doc/guide/src/basics/log_replay/goForward_3.png +0 -0
  482. data/doc/guide/src/basics/log_replay/goForward_5.png +0 -0
  483. data/doc/guide/src/basics/log_replay/plan_repair_4.png +0 -0
  484. data/doc/guide/src/basics/log_replay/roby_log_main_window.png +0 -0
  485. data/doc/guide/src/basics/relations_display.page +0 -203
  486. data/doc/guide/src/basics/shell.page +0 -102
  487. data/doc/guide/src/default.css +0 -319
  488. data/doc/guide/src/introduction/index.page +0 -29
  489. data/doc/guide/src/introduction/publications.page +0 -14
  490. data/doc/guide/src/relations/dependency.page +0 -89
  491. data/doc/guide/src/relations/index.page +0 -12
  492. data/ext/droby/dump.cc +0 -175
  493. data/ext/droby/extconf.rb +0 -3
  494. data/ext/graph/algorithm.cc +0 -746
  495. data/ext/graph/extconf.rb +0 -7
  496. data/ext/graph/graph.cc +0 -575
  497. data/ext/graph/graph.hh +0 -183
  498. data/ext/graph/iterator_sequence.hh +0 -102
  499. data/ext/graph/undirected_dfs.hh +0 -226
  500. data/ext/graph/undirected_graph.hh +0 -421
  501. data/lib/roby/app/scripts/generate/bookmarks.rb +0 -162
  502. data/lib/roby/app/scripts/replay.rb +0 -31
  503. data/lib/roby/app/scripts/server.rb +0 -18
  504. data/lib/roby/basic_object.rb +0 -151
  505. data/lib/roby/config.rb +0 -14
  506. data/lib/roby/distributed.rb +0 -36
  507. data/lib/roby/distributed/base.rb +0 -448
  508. data/lib/roby/distributed/communication.rb +0 -875
  509. data/lib/roby/distributed/connection_space.rb +0 -616
  510. data/lib/roby/distributed/distributed_object.rb +0 -206
  511. data/lib/roby/distributed/drb.rb +0 -62
  512. data/lib/roby/distributed/notifications.rb +0 -531
  513. data/lib/roby/distributed/peer.rb +0 -555
  514. data/lib/roby/distributed/protocol.rb +0 -529
  515. data/lib/roby/distributed/proxy.rb +0 -343
  516. data/lib/roby/distributed/subscription.rb +0 -311
  517. data/lib/roby/distributed/transaction.rb +0 -498
  518. data/lib/roby/external_process_task.rb +0 -225
  519. data/lib/roby/graph.rb +0 -160
  520. data/lib/roby/log.rb +0 -3
  521. data/lib/roby/log/chronicle.rb +0 -303
  522. data/lib/roby/log/console.rb +0 -74
  523. data/lib/roby/log/data_stream.rb +0 -275
  524. data/lib/roby/log/dot.rb +0 -279
  525. data/lib/roby/log/event_stream.rb +0 -161
  526. data/lib/roby/log/file.rb +0 -396
  527. data/lib/roby/log/gui/basic_display.ui +0 -83
  528. data/lib/roby/log/gui/basic_display_ui.rb +0 -89
  529. data/lib/roby/log/gui/chronicle.rb +0 -26
  530. data/lib/roby/log/gui/chronicle_view.rb +0 -40
  531. data/lib/roby/log/gui/chronicle_view.ui +0 -70
  532. data/lib/roby/log/gui/chronicle_view_ui.rb +0 -90
  533. data/lib/roby/log/gui/data_displays.rb +0 -171
  534. data/lib/roby/log/gui/data_displays.ui +0 -155
  535. data/lib/roby/log/gui/data_displays_ui.rb +0 -146
  536. data/lib/roby/log/gui/notifications.rb +0 -26
  537. data/lib/roby/log/gui/relations.rb +0 -269
  538. data/lib/roby/log/gui/relations.ui +0 -123
  539. data/lib/roby/log/gui/relations_ui.rb +0 -120
  540. data/lib/roby/log/gui/relations_view.rb +0 -185
  541. data/lib/roby/log/gui/relations_view.ui +0 -149
  542. data/lib/roby/log/gui/relations_view_ui.rb +0 -144
  543. data/lib/roby/log/gui/replay.rb +0 -366
  544. data/lib/roby/log/gui/replay_controls.rb +0 -206
  545. data/lib/roby/log/gui/replay_controls.ui +0 -282
  546. data/lib/roby/log/gui/replay_controls_ui.rb +0 -249
  547. data/lib/roby/log/gui/runtime.rb +0 -130
  548. data/lib/roby/log/hooks.rb +0 -186
  549. data/lib/roby/log/logger.rb +0 -203
  550. data/lib/roby/log/notifications.rb +0 -244
  551. data/lib/roby/log/plan_rebuilder.rb +0 -468
  552. data/lib/roby/log/relations.rb +0 -1084
  553. data/lib/roby/log/server.rb +0 -547
  554. data/lib/roby/log/sqlite.rb +0 -47
  555. data/lib/roby/log/timings.rb +0 -233
  556. data/lib/roby/plan-object.rb +0 -371
  557. data/lib/roby/planning.rb +0 -13
  558. data/lib/roby/planning/loops.rb +0 -309
  559. data/lib/roby/planning/model.rb +0 -1012
  560. data/lib/roby/planning/task.rb +0 -180
  561. data/lib/roby/query.rb +0 -655
  562. data/lib/roby/relations/conflicts.rb +0 -67
  563. data/lib/roby/relations/dependency.rb +0 -358
  564. data/lib/roby/relations/ensured.rb +0 -19
  565. data/lib/roby/relations/error_handling.rb +0 -22
  566. data/lib/roby/relations/events.rb +0 -7
  567. data/lib/roby/relations/executed_by.rb +0 -208
  568. data/lib/roby/relations/influence.rb +0 -10
  569. data/lib/roby/relations/planned_by.rb +0 -63
  570. data/lib/roby/state/information.rb +0 -55
  571. data/lib/roby/state/state.rb +0 -367
  572. data/lib/roby/task-operations.rb +0 -186
  573. data/lib/roby/task_index.rb +0 -80
  574. data/lib/roby/test/distributed.rb +0 -230
  575. data/lib/roby/test/tasks/simple_task.rb +0 -23
  576. data/lib/roby/transactions.rb +0 -507
  577. data/lib/roby/transactions/proxy.rb +0 -325
  578. data/plugins/fault_injection/History.txt +0 -4
  579. data/plugins/fault_injection/README.txt +0 -34
  580. data/plugins/fault_injection/Rakefile +0 -12
  581. data/plugins/fault_injection/TODO.txt +0 -0
  582. data/plugins/fault_injection/app.rb +0 -52
  583. data/plugins/fault_injection/fault_injection.rb +0 -89
  584. data/plugins/fault_injection/test/test_fault_injection.rb +0 -78
  585. data/plugins/subsystems/README.txt +0 -37
  586. data/plugins/subsystems/Rakefile +0 -13
  587. data/plugins/subsystems/app.rb +0 -182
  588. data/plugins/subsystems/test/app/README +0 -24
  589. data/plugins/subsystems/test/app/Rakefile +0 -8
  590. data/plugins/subsystems/test/app/config/app.yml +0 -71
  591. data/plugins/subsystems/test/app/config/init.rb +0 -12
  592. data/plugins/subsystems/test/app/config/roby.yml +0 -3
  593. data/plugins/subsystems/test/app/planners/main.rb +0 -20
  594. data/plugins/subsystems/test/app/scripts/distributed +0 -3
  595. data/plugins/subsystems/test/app/scripts/replay +0 -3
  596. data/plugins/subsystems/test/app/scripts/results +0 -3
  597. data/plugins/subsystems/test/app/scripts/run +0 -3
  598. data/plugins/subsystems/test/app/scripts/server +0 -3
  599. data/plugins/subsystems/test/app/scripts/shell +0 -3
  600. data/plugins/subsystems/test/app/scripts/test +0 -3
  601. data/plugins/subsystems/test/app/tasks/services.rb +0 -15
  602. data/plugins/subsystems/test/test_subsystems.rb +0 -78
  603. data/test/distributed/test_communication.rb +0 -195
  604. data/test/distributed/test_connection.rb +0 -284
  605. data/test/distributed/test_execution.rb +0 -378
  606. data/test/distributed/test_mixed_plan.rb +0 -341
  607. data/test/distributed/test_plan_notifications.rb +0 -238
  608. data/test/distributed/test_protocol.rb +0 -525
  609. data/test/distributed/test_query.rb +0 -106
  610. data/test/distributed/test_remote_plan.rb +0 -491
  611. data/test/distributed/test_transaction.rb +0 -466
  612. data/test/mockups/external_process +0 -28
  613. data/test/mockups/tasks.rb +0 -27
  614. data/test/planning/test_loops.rb +0 -432
  615. data/test/planning/test_model.rb +0 -427
  616. data/test/planning/test_task.rb +0 -126
  617. data/test/relations/test_conflicts.rb +0 -42
  618. data/test/relations/test_dependency.rb +0 -324
  619. data/test/relations/test_ensured.rb +0 -38
  620. data/test/relations/test_executed_by.rb +0 -224
  621. data/test/relations/test_planned_by.rb +0 -56
  622. data/test/suite_core.rb +0 -29
  623. data/test/suite_distributed.rb +0 -10
  624. data/test/suite_planning.rb +0 -4
  625. data/test/suite_relations.rb +0 -8
  626. data/test/tasks/test_external_process.rb +0 -126
  627. data/test/tasks/test_thread_task.rb +0 -70
  628. data/test/test_bgl.rb +0 -528
  629. data/test/test_event.rb +0 -969
  630. data/test/test_exceptions.rb +0 -591
  631. data/test/test_execution_engine.rb +0 -987
  632. data/test/test_gui.rb +0 -20
  633. data/test/test_interface.rb +0 -43
  634. data/test/test_log.rb +0 -125
  635. data/test/test_log_server.rb +0 -133
  636. data/test/test_plan.rb +0 -418
  637. data/test/test_query.rb +0 -424
  638. data/test/test_relations.rb +0 -260
  639. data/test/test_state.rb +0 -432
  640. data/test/test_support.rb +0 -16
  641. data/test/test_task.rb +0 -1181
  642. data/test/test_testcase.rb +0 -138
  643. data/test/test_transactions.rb +0 -610
  644. data/test/test_transactions_proxy.rb +0 -216
@@ -1,160 +1,164 @@
1
1
  module Roby
2
2
  module Test
3
- class << self
4
- def sampling(duration, period, *fields)
5
- Test.info "starting sampling #{fields.join(", ")} every #{period}s for #{duration}s"
6
-
7
- samples = Array.new
8
- fields.map! { |n| n.to_sym }
9
- if fields.include?(:dt)
10
- raise ArgumentError, "dt is reserved by #sampling"
11
- end
12
-
13
- if compute_time = !fields.include?(:t)
14
- fields << :t
15
- end
16
- fields << :dt
17
-
18
- sample_type = Struct.new(*fields)
19
-
20
- start = Time.now
21
- Roby.condition_variable(true) do |cv, mt|
22
- first_sample = nil
23
- mt.synchronize do
24
- id = Roby.every(period) do
25
- result = yield
26
- if result
27
- if compute_time
28
- result << Roby.engine.cycle_start
29
- end
30
- new_sample = sample_type.new(*result)
31
-
32
- unless samples.empty?
33
- new_sample.dt = new_sample.t- samples.last.t
34
- end
35
- samples << new_sample
36
-
37
- if samples.last.t - samples.first.t > duration
38
- mt.synchronize do
39
- cv.broadcast
40
- end
41
- end
42
- end
43
- end
44
-
45
- cv.wait(mt)
46
- Roby.engine.remove_periodic_handler(id)
47
- end
48
- end
49
-
50
- samples
51
- end
52
-
53
- Stat = Struct.new :total, :count, :mean, :stddev, :min, :max
54
-
55
- # Computes mean and standard deviation about the samples in
56
- # +samples+ +spec+ describes what to compute:
57
- # * if nothing is specified, we compute the statistics on
58
- # v(i - 1) - v(i)
59
- # * if spec['fieldname'] is 'rate', we compute the statistics on
60
- # (v(i - 1) - v(i)) / (t(i - 1) / t(i))
61
- # * if spec['fieldname'] is 'absolute', we compute the
62
- # statistics on
63
- # v(i)
64
- # * if spec['fieldname'] is 'absolute_rate', we compute the
65
- # statistics on
66
- # v(i) / (t(i - 1) / t(i))
67
- #
68
- # The returned value is a struct with the same fields than the
69
- # samples. Each element is a Stats object
70
- def stats(samples, spec)
71
- return if samples.empty?
72
- type = samples.first.class
73
- spec = spec.inject(Hash.new) do |h, (k, v)|
74
- spec[k.to_sym] = v.to_sym
75
- spec
76
- end
77
- spec[:t] = :exclude
78
- spec[:dt] = :absolute
79
-
80
- # Initialize the result value
81
- fields = type.members.
82
- find_all { |n| spec[n.to_sym] != :exclude }.
83
- map { |n| n.to_sym }
84
- result = Struct.new(*fields).new
85
- fields.each do |name|
86
- result[name] = Stat.new(0, 0, 0, 0, nil, nil)
87
- end
88
-
89
- # Compute the deltas if the mode is not absolute
90
- last_sample = nil
91
- samples = samples.map do |original_sample|
92
- sample = original_sample.dup
93
- fields.each do |name|
94
- next unless value = sample[name]
95
- unless spec[name] == :absolute || spec[name] == :absolute_rate
96
- if last_sample && last_sample[name]
97
- sample[name] -= last_sample[name]
98
- else
99
- sample[name] = nil
100
- next
101
- end
102
- end
103
- end
104
- last_sample = original_sample
105
- sample
106
- end
107
-
108
- # Compute the rates if needed
109
- samples = samples.map do |sample|
110
- fields.each do |name|
111
- next unless value = sample[name]
112
- if spec[name] == :rate || spec[name] == :absolute_rate
113
- if sample.dt
114
- sample[name] = value / sample.dt
115
- else
116
- sample[name] = nil
117
- next
118
- end
119
- end
120
- end
121
- sample
122
- end
123
-
124
- samples.each do |sample|
125
- fields.each do |name|
126
- next unless value = sample[name]
127
- if !result[name].max || value > result[name].max
128
- result[name].max = value
129
- end
130
- if !result[name].min || value < result[name].min
131
- result[name].min = value
132
- end
133
-
134
- result[name].total += value
135
- result[name].count += 1
136
- end
137
- last_sample = sample
138
- end
139
-
140
- result.each do |r|
141
- r.mean = Float(r.total) / r.count
142
- end
143
-
144
- samples.each do |sample|
145
- fields.each do |name|
146
- next unless value = sample[name]
147
- result[name].stddev += (value - result[name].mean) ** 2
148
- end
149
- end
150
-
151
- result.each do |r|
152
- r.stddev = Math.sqrt(r.stddev / r.count)
153
- end
154
-
155
- result
156
- end
157
- end
3
+ class << self
4
+ def sampling(engine, duration, period, *fields)
5
+ Test.info "starting sampling #{fields.join(", ")} every #{period}s for #{duration}s"
6
+
7
+ samples = Array.new
8
+ fields.map! { |n| n.to_sym }
9
+ if fields.include?(:dt)
10
+ raise ArgumentError, "dt is reserved by #sampling"
11
+ end
12
+
13
+ if compute_time = !fields.include?(:t)
14
+ fields << :t
15
+ end
16
+ fields << :dt
17
+
18
+ sample_type = Struct.new(*fields)
19
+
20
+ start = Time.now
21
+ Roby.condition_variable(true) do |cv, mt|
22
+ first_sample = nil
23
+ mt.synchronize do
24
+ timeout = false
25
+ id = engine.every(period) do
26
+ result = yield
27
+ if result
28
+ if compute_time
29
+ result << engine.cycle_start
30
+ end
31
+ new_sample = sample_type.new(*result)
32
+
33
+ unless samples.empty?
34
+ new_sample.dt = new_sample.t- samples.last.t
35
+ end
36
+ samples << new_sample
37
+
38
+ if samples.last.t - samples.first.t > duration
39
+ mt.synchronize do
40
+ timeout = true
41
+ cv.broadcast
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ while !timeout
48
+ cv.wait(mt)
49
+ end
50
+ engine.remove_periodic_handler(id)
51
+ end
52
+ end
53
+
54
+ samples
55
+ end
56
+
57
+ Stat = Struct.new :total, :count, :mean, :stddev, :min, :max
58
+
59
+ # Computes mean and standard deviation about the samples in
60
+ # +samples+ +spec+ describes what to compute:
61
+ # * if nothing is specified, we compute the statistics on
62
+ # v(i - 1) - v(i)
63
+ # * if spec['fieldname'] is 'rate', we compute the statistics on
64
+ # (v(i - 1) - v(i)) / (t(i - 1) / t(i))
65
+ # * if spec['fieldname'] is 'absolute', we compute the
66
+ # statistics on
67
+ # v(i)
68
+ # * if spec['fieldname'] is 'absolute_rate', we compute the
69
+ # statistics on
70
+ # v(i) / (t(i - 1) / t(i))
71
+ #
72
+ # The returned value is a struct with the same fields than the
73
+ # samples. Each element is a Stats object
74
+ def stats(samples, spec)
75
+ return if samples.empty?
76
+ type = samples.first.class
77
+ spec = spec.inject(Hash.new) do |h, (k, v)|
78
+ spec[k.to_sym] = v.to_sym
79
+ spec
80
+ end
81
+ spec[:t] = :exclude
82
+ spec[:dt] = :absolute
83
+
84
+ # Initialize the result value
85
+ fields = type.members.
86
+ find_all { |n| spec[n.to_sym] != :exclude }.
87
+ map { |n| n.to_sym }
88
+ result = Struct.new(*fields).new
89
+ fields.each do |name|
90
+ result[name] = Stat.new(0, 0, 0, 0, nil, nil)
91
+ end
92
+
93
+ # Compute the deltas if the mode is not absolute
94
+ last_sample = nil
95
+ samples = samples.map do |original_sample|
96
+ sample = original_sample.dup
97
+ fields.each do |name|
98
+ next unless value = sample[name]
99
+ unless spec[name] == :absolute || spec[name] == :absolute_rate
100
+ if last_sample && last_sample[name]
101
+ sample[name] -= last_sample[name]
102
+ else
103
+ sample[name] = nil
104
+ next
105
+ end
106
+ end
107
+ end
108
+ last_sample = original_sample
109
+ sample
110
+ end
111
+
112
+ # Compute the rates if needed
113
+ samples = samples.map do |sample|
114
+ fields.each do |name|
115
+ next unless value = sample[name]
116
+ if spec[name] == :rate || spec[name] == :absolute_rate
117
+ if sample.dt
118
+ sample[name] = value / sample.dt
119
+ else
120
+ sample[name] = nil
121
+ next
122
+ end
123
+ end
124
+ end
125
+ sample
126
+ end
127
+
128
+ samples.each do |sample|
129
+ fields.each do |name|
130
+ next unless value = sample[name]
131
+ if !result[name].max || value > result[name].max
132
+ result[name].max = value
133
+ end
134
+ if !result[name].min || value < result[name].min
135
+ result[name].min = value
136
+ end
137
+
138
+ result[name].total += value
139
+ result[name].count += 1
140
+ end
141
+ last_sample = sample
142
+ end
143
+
144
+ result.each do |r|
145
+ r.mean = Float(r.total) / r.count
146
+ end
147
+
148
+ samples.each do |sample|
149
+ fields.each do |name|
150
+ next unless value = sample[name]
151
+ result[name].stddev += (value - result[name].mean) ** 2
152
+ end
153
+ end
154
+
155
+ result.each do |r|
156
+ r.stddev = Math.sqrt(r.stddev / r.count)
157
+ end
158
+
159
+ result
160
+ end
161
+ end
158
162
  end
159
163
  end
160
164
 
@@ -0,0 +1,75 @@
1
+ module Roby
2
+ module Test
3
+ # Implementation of the #validate_state_machine context
4
+ class ValidateStateMachine
5
+ def initialize(test, task_or_action)
6
+ @test = test
7
+ @toplevel_task = @test.roby_run_planner(task_or_action)
8
+
9
+ @state_machines = @toplevel_task.each_coordination_object.
10
+ find_all { |obj| obj.kind_of?(Coordination::ActionStateMachine) }
11
+ if @state_machines.empty?
12
+ raise ArgumentError, "#{task_or_action} has no state machines"
13
+ end
14
+ end
15
+
16
+ def assert_transitions_to_state(state_name, timeout: 5, start: true)
17
+ if state_name.respond_to?(:to_str) && !state_name.end_with?('_state')
18
+ state_name = "#{state_name}_state"
19
+ end
20
+
21
+ done = false
22
+ @state_machines.each do |m|
23
+ m.on_transition do |_, new_state|
24
+ if state_name === new_state.name
25
+ done = true
26
+ end
27
+ end
28
+ end
29
+ yield if block_given?
30
+ @test.process_events_until(timeout: timeout, garbage_collect_pass: false) do
31
+ done
32
+ end
33
+ @test.roby_run_planner(@toplevel_task)
34
+ state_task = @toplevel_task.current_task_child
35
+ if start
36
+ expect_execution.to { emit state_task.start_event }
37
+ end
38
+ state_task
39
+ end
40
+
41
+ def evaluate(&block)
42
+ instance_eval(&block)
43
+ end
44
+
45
+ def find_through_method_missing(m, args)
46
+ MetaRuby::DSLs.find_through_method_missing(
47
+ @toplevel_task, m, args,
48
+ '_event' => :find_event,
49
+ '_child' => :find_child_from_role) || super
50
+ end
51
+
52
+ def has_through_method_missing?(m)
53
+ MetaRuby::DSLs.has_through_method_missing?(
54
+ @toplevel_task, m,
55
+ '_event' => :has_event?,
56
+ '_child' => :has_role?) || super
57
+ end
58
+
59
+ include MetaRuby::DSLs::FindThroughMethodMissing
60
+
61
+ def respond_to_missing?(m, include_private)
62
+ @test.respond_to?(m) || super
63
+ end
64
+
65
+ def method_missing(m, *args, &block)
66
+ if @test.respond_to?(m)
67
+ @test.public_send(m, *args, &block)
68
+ else
69
+ super
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+
@@ -0,0 +1,1125 @@
1
+ module Roby
2
+ # Exception raised when someone tries do commit an invalid transaction
3
+ class InvalidTransaction < RuntimeError; end
4
+
5
+ # A transaction is a special kind of plan. It allows to build plans in a separate
6
+ # sandbox, and then to apply the modifications to the real plan (using #commit_transaction), or
7
+ # to discard all modifications (using #discard)
8
+ class Transaction < Plan
9
+ # If true, an engine could execute tasks included in this plan. This is
10
+ # alwxays false for transactions
11
+ #
12
+ # @return [Boolean]
13
+ def executable?; false end
14
+
15
+ # If this is true, no new proxies can be created on the transaction.
16
+ # This is used during the commit process to verify that no new
17
+ # modifications are applied to the transaction
18
+ attr_predicate :frozen?
19
+
20
+ # True if this transaction has been committed
21
+ #
22
+ # @see #finalized?
23
+ attr_predicate :committed?
24
+
25
+ # True if this transaction has either been discarded or committed
26
+ #
27
+ # @return [Boolean]
28
+ # @see #committed?
29
+ def finalized?; !plan end
30
+
31
+ # (see Plan#root_plan?)
32
+ def root_plan?
33
+ false
34
+ end
35
+
36
+ def extend_proxy_object(proxy, object, klass = object.class)
37
+ proxy.extend Roby::Transaction::Proxying.proxying_module_for(klass)
38
+ end
39
+
40
+ def setup_and_register_proxy_task(proxy, task)
41
+ raise "transaction #{self} has been either committed or discarded. No modification allowed" if frozen?
42
+
43
+ proxy_tasks[task] = proxy
44
+ extend_proxy_object(proxy, task)
45
+ proxy.plan = self
46
+ proxy.setup_proxy(task, self)
47
+ register_task(proxy)
48
+ if plan.mission_task?(task)
49
+ add_mission_task(proxy)
50
+ elsif plan.permanent_task?(task)
51
+ add_permanent_task(proxy)
52
+ end
53
+ if services = plan.find_all_plan_services(task)
54
+ services.each do |original_srv|
55
+ create_and_register_proxy_plan_service(original_srv)
56
+ end
57
+ end
58
+ proxy
59
+ end
60
+
61
+ def setup_and_register_proxy_event(proxy, event)
62
+ raise "transaction #{self} has been either committed or discarded. No modification allowed" if frozen?
63
+
64
+ proxy_events[event] = proxy
65
+ extend_proxy_object(proxy, event)
66
+ proxy.plan = self
67
+ proxy.setup_proxy(event, self)
68
+ register_event(proxy)
69
+ if plan.permanent_event?(event)
70
+ add_permanent_event(proxy)
71
+ end
72
+ proxy
73
+ end
74
+
75
+ def setup_and_register_proxy_plan_service(proxy, plan_service)
76
+ raise "transaction #{self} has been either committed or discarded. No modification allowed" if frozen?
77
+
78
+ extend_proxy_object(proxy, plan_service)
79
+ proxy.setup_proxy(plan_service, self)
80
+ proxy.task = wrap_task(plan_service.to_task)
81
+ add_plan_service(proxy)
82
+ proxy
83
+ end
84
+
85
+ def create_and_register_proxy_task(object)
86
+ raise "transaction #{self} has been either committed or discarded. No modification allowed" if frozen?
87
+ proxy = object.dup
88
+ setup_and_register_proxy_task(proxy, object)
89
+ copy_object_relations(object, proxy, proxy_tasks)
90
+ proxy
91
+ end
92
+
93
+ def create_and_register_proxy_event(object)
94
+ raise "transaction #{self} has been either committed or discarded. No modification allowed" if frozen?
95
+ proxy = object.dup
96
+ setup_and_register_proxy_event(proxy, object)
97
+ copy_object_relations(object, proxy, proxy_events)
98
+ proxy
99
+ end
100
+
101
+ def create_and_register_proxy_plan_service(object)
102
+ raise "transaction #{self} has been either committed or discarded. No modification allowed" if frozen?
103
+ # Ensure the underlying task is wrapped
104
+ proxy = object.dup
105
+ setup_and_register_proxy_plan_service(proxy, object)
106
+ proxy
107
+ end
108
+
109
+ def find_local_object_for_plan_object(object, proxy_map)
110
+ if object.plan == self
111
+ return object
112
+ elsif proxy = proxy_map[object]
113
+ return proxy
114
+ elsif !object.plan
115
+ raise ArgumentError, "#{object} has been removed from plan"
116
+ elsif object.plan.template?
117
+ add(object)
118
+ return object
119
+ end
120
+ end
121
+
122
+ def find_local_object_for_task(object)
123
+ find_local_object_for_plan_object(object, proxy_tasks)
124
+ end
125
+
126
+ def find_local_object_for_event(object)
127
+ find_local_object_for_plan_object(object, proxy_events)
128
+ end
129
+
130
+ def find_local_object_for_plan_service(object)
131
+ if local_task = find_local_object_for_task(object.task)
132
+ find_plan_service(local_task)
133
+ end
134
+ end
135
+
136
+ def wrap_plan_object(object, proxy_map)
137
+ if object.plan != self.plan
138
+ raise ArgumentError, "#{object} is in #{object.plan}, this transaction #{self} applies on #{self.plan}"
139
+ else
140
+ return object.create_transaction_proxy(self)
141
+ end
142
+ end
143
+
144
+ def wrap_task(task, create: true)
145
+ if local_task = find_local_object_for_task(task)
146
+ local_task
147
+ elsif create
148
+ wrap_plan_object(task, proxy_tasks)
149
+ end
150
+ end
151
+
152
+ def wrap_event(event, create: true)
153
+ if local_event = find_local_object_for_event(event)
154
+ local_event
155
+ elsif create
156
+ wrap_plan_object(event, proxy_events)
157
+ end
158
+ end
159
+
160
+ def wrap_plan_service(plan_service, create: true)
161
+ if local_plan_service = find_local_object_for_plan_service(plan_service)
162
+ local_plan_service
163
+ elsif create
164
+ plan_service.create_transaction_proxy(self)
165
+ end
166
+ end
167
+
168
+ # Get the transaction proxy for +object+
169
+ def wrap(object, create: true)
170
+ if object.kind_of?(PlanService)
171
+ wrap_plan_service(object, create: create)
172
+ elsif object.respond_to?(:to_task)
173
+ wrap_task(object, create: create)
174
+ elsif object.respond_to?(:to_event)
175
+ wrap_event(object, create: create)
176
+ elsif object.respond_to?(:to_ary)
177
+ object.map { |o| wrap(o, create: create) }
178
+ elsif object.respond_to?(:each)
179
+ raise ArgumentError, "don't know how to wrap containers of class #{object.class}"
180
+ else
181
+ raise TypeError, "don't know how to wrap #{object || 'nil'} of type #{object.class.ancestors}"
182
+ end
183
+ end
184
+
185
+ def [](object, create: true)
186
+ wrap(object, create: create)
187
+ end
188
+
189
+
190
+ def propose; end
191
+ def edit
192
+ yield if block_given?
193
+ end
194
+
195
+ # @api private
196
+ #
197
+ # Copies relations when importing a new subplan from the main plan
198
+ #
199
+ # @param [Hash<Relations::Graph,Relations::Graph>] relation graph
200
+ # mapping from the plan graphs to the transaction graphs
201
+ # @param [Hash<PlanObject,PlanObject>] mappings
202
+ # mapping from the plan objects to the transaction objects
203
+ def import_subplan_relations(graphs, mappings, proxy_map)
204
+ graphs.each do |plan_g, self_g|
205
+ plan_g.copy_subgraph_to(self_g, mappings)
206
+ mappings.each do |plan_v, self_v|
207
+ # The method assumes that the plan objects are new. We are
208
+ # therefore done if there is the same number of relations in
209
+ # both plan and transactions
210
+ #
211
+ # It is NOT true in the general case as one can add extra
212
+ # relations in the transaction
213
+ if plan_g.in_degree(plan_v) != self_g.in_degree(self_v)
214
+ plan_g.each_in_neighbour(plan_v) do |plan_parent|
215
+ next if mappings.has_key?(plan_parent)
216
+ if self_parent = proxy_map[plan_parent]
217
+ self_g.add_edge(self_parent, self_v, plan_g.edge_info(plan_parent, plan_v))
218
+ end
219
+ end
220
+ end
221
+
222
+ if plan_g.out_degree(plan_v) != self_g.out_degree(self_v)
223
+ plan_g.each_out_neighbour(plan_v) do |plan_child|
224
+ next if mappings.has_key?(plan_child)
225
+ if self_child = proxy_map[plan_child]
226
+ self_g.add_edge(self_v, self_child, plan_g.edge_info(plan_v, plan_child))
227
+ end
228
+ end
229
+ end
230
+ end
231
+ end
232
+ end
233
+
234
+ # This method copies on +proxy+ all relations of +object+ for which
235
+ # both ends of the relation are already in the transaction.
236
+ def copy_object_relations(object, proxy, proxy_map)
237
+ # Create edges between the neighbours that are really in the transaction
238
+ object.each_relation do |rel|
239
+ plan_graph = object.relation_graph_for(rel)
240
+ trsc_graph = proxy.relation_graph_for(rel)
241
+
242
+ plan_graph.each_in_neighbour(object) do |parent|
243
+ if parent_proxy = proxy_map[parent]
244
+ trsc_graph.add_edge(parent_proxy, proxy, plan_graph.edge_info(parent, object))
245
+ end
246
+ end
247
+ plan_graph.each_out_neighbour(object) do |child|
248
+ if child_proxy = proxy_map[child]
249
+ trsc_graph.add_edge(proxy, child_proxy, plan_graph.edge_info(object, child))
250
+ end
251
+ end
252
+ end
253
+ end
254
+
255
+ # Tests whether a plan object has a proxy in self
256
+ #
257
+ # Unlike {#wrap}, the provided object must be a plan object from the
258
+ # transaction's underlying plan
259
+ #
260
+ # @param [Roby::Task] object the object to test for
261
+ def has_proxy_for_task?(object)
262
+ if object.plan != self.plan
263
+ raise ArgumentError, "#{object} is not in #{self}.plan (#{plan})"
264
+ end
265
+ proxy_tasks.has_key?(object)
266
+ end
267
+
268
+ # Tests whether an event has a proxy in self
269
+ #
270
+ # Unlike {#wrap}, the provided object must be a plan object from the
271
+ # transaction's underlying plan
272
+ #
273
+ # @param [Roby::EventGenerator] object the object to test for
274
+ def has_proxy_for_event?(object)
275
+ if object.plan != self.plan
276
+ raise ArgumentError, "#{object} is not in #{self}.plan (#{plan})"
277
+ end
278
+ proxy_events.has_key?(object)
279
+ end
280
+
281
+ def restore_relation(proxy, relation)
282
+ object = proxy.__getobj__
283
+
284
+ proxy_children = proxy.child_objects(relation)
285
+ object.child_objects(relation).each do |object_child|
286
+ next unless proxy_child = wrap(object_child, create: false)
287
+ if proxy_children.include?(proxy_child)
288
+ relation.remove_edge(proxy, proxy_child)
289
+ end
290
+ end
291
+
292
+ proxy_parents = proxy.parent_objects(relation)
293
+ object.parent_objects(relation).each do |object_parent|
294
+ next unless proxy_parent = wrap(object_parent, create: false)
295
+ if proxy_parents.include?(proxy_parent)
296
+ relation.remove_edge(parent, proxy_parent)
297
+ end
298
+ end
299
+
300
+ added_objects.delete(proxy)
301
+ proxy.discovered_relations.delete(relation)
302
+ proxy.do_discover(relation, false)
303
+ end
304
+
305
+ # Removes an object from this transaction
306
+ #
307
+ # This does *not* remove the object from the underlying plan. Removing
308
+ # objects directly is (at best) dangerous, and should be handled by
309
+ # garbage collection.
310
+ def remove_plan_object(object, proxy_map)
311
+ raise "transaction #{self} has been either committed or discarded. No modification allowed" if frozen?
312
+
313
+ object = may_unwrap(object)
314
+ proxy = proxy_map.delete(object)
315
+ actual_plan = (proxy || object).plan
316
+
317
+ if actual_plan != self
318
+ raise InternalError, "inconsistency: #{proxy || object} plan is #{actual_plan}, was expected to be #{self}"
319
+ end
320
+ return object, proxy
321
+ end
322
+
323
+ def remove_task(task, timestamp = Time.now)
324
+ unwrapped, proxy = remove_plan_object(task, proxy_tasks)
325
+ if proxy
326
+ unmarked_mission_tasks.delete(unwrapped)
327
+ unmarked_permanent_tasks.delete(unwrapped)
328
+ proxy.each_plan_child do |task_event_proxy|
329
+ remove_plan_object(task_event_proxy, proxy_events)
330
+ end
331
+ end
332
+ super(proxy || task, timestamp)
333
+ end
334
+
335
+ def remove_free_event(event, timestamp = Time.now)
336
+ unwrapped, proxy = remove_plan_object(event, proxy_events)
337
+ if proxy
338
+ unmarked_permanent_events.delete(unwrapped)
339
+ end
340
+ super(proxy || event, timestamp)
341
+ end
342
+
343
+ def may_wrap(objects, create: true)
344
+ if objects.respond_to?(:to_ary)
345
+ objects.map { |obj| may_wrap(obj, create: create) }
346
+ elsif objects.respond_to?(:each)
347
+ raise ArgumentError, "don't know how to wrap containers of class #{objects.class}"
348
+ elsif objects.kind_of?(PlanObject)
349
+ wrap(objects, create: create)
350
+ else
351
+ objects
352
+ end
353
+ end
354
+
355
+ # If +object+ is in this transaction, may_unwrap will return the
356
+ # underlying plan object. In all other cases, returns object.
357
+ def may_unwrap(object)
358
+ if object.respond_to?(:plan)
359
+ if object.plan == self && object.respond_to?(:__getobj__)
360
+ object.__getobj__
361
+ elsif object.plan == self.plan
362
+ object
363
+ else
364
+ object
365
+ end
366
+ else object
367
+ end
368
+ end
369
+
370
+ # The list of missions of the underlying plan that have been unmarked in
371
+ # the transaction
372
+ attr_reader :unmarked_mission_tasks
373
+ # The list of permanent tasks of the underlying plan that have been unmarked in
374
+ # the transaction
375
+ attr_reader :unmarked_permanent_tasks
376
+ # The list of permanent events of the underlying plan that have been unmarked in
377
+ # the transaction
378
+ attr_reader :unmarked_permanent_events
379
+ # The plan this transaction applies on
380
+ attr_reader :plan
381
+ # The proxy objects built for tasks in this transaction
382
+ attr_reader :proxy_tasks
383
+ # The proxy objects built for events this transaction
384
+ attr_reader :proxy_events
385
+ # The option hash given at initialization
386
+ attr_reader :options
387
+
388
+ # The decision control object associated with this transaction. It is
389
+ # in general plan.control
390
+ def control; plan.control end
391
+
392
+ # Creates a new transaction which applies on +plan+
393
+ def initialize(plan, options = {})
394
+ if !plan
395
+ raise ArgumentError, "cannot create a transaction with no plan"
396
+ end
397
+
398
+ @options = options
399
+ @frozen = false
400
+ @disable_proxying = false
401
+ @invalid = false
402
+
403
+ super()
404
+
405
+ @plan = plan
406
+
407
+ @proxy_tasks = Hash.new
408
+ @proxy_events = Hash.new
409
+ @unmarked_mission_tasks = Set.new
410
+ @unmarked_permanent_tasks = Set.new
411
+ @unmarked_permanent_events = Set.new
412
+
413
+ plan.transactions << self
414
+ plan.added_transaction(self)
415
+ end
416
+
417
+ # Calls the given block in the execution thread of the engine of the
418
+ # underlying plan. If there is no engine attached to this plan, yields
419
+ # immediately.
420
+ #
421
+ # See Plan#execute and ExecutionEngine#execute
422
+ def execute(&block)
423
+ plan.execute(&block)
424
+ end
425
+
426
+ def add_mission_task(t)
427
+ raise "transaction #{self} has been either committed or discarded. No modification allowed" if frozen?
428
+ if t.transaction_proxy?
429
+ unmarked_mission_tasks.delete(t.__getobj__)
430
+ end
431
+ super(t)
432
+ end
433
+
434
+ def add_permanent_task(t)
435
+ raise "transaction #{self} has been either committed or discarded. No modification allowed" if frozen?
436
+ if t.transaction_proxy?
437
+ unmarked_permanent_tasks.delete(t.__getobj__)
438
+ end
439
+ super(t)
440
+ end
441
+
442
+ def add_permanent_event(e)
443
+ raise "transaction #{self} has been either committed or discarded. No modification allowed" if frozen?
444
+ if e.transaction_proxy?
445
+ unmarked_permanent_events.delete(e.__getobj__)
446
+ end
447
+ super(e)
448
+ end
449
+
450
+ def add(objects)
451
+ raise "transaction #{self} has been either committed or discarded. No modification allowed" if frozen?
452
+ super(objects)
453
+ self
454
+ end
455
+
456
+ def unmark_permanent_event(t)
457
+ raise "transaction #{self} has been either committed or discarded. No modification allowed" if frozen?
458
+ t = t.as_plan
459
+ if proxy = find_local_object_for_event(t)
460
+ super(proxy)
461
+ end
462
+
463
+ t = may_unwrap(t)
464
+ if t.plan == self.plan
465
+ unmarked_permanent_events.add(t)
466
+ end
467
+ end
468
+
469
+ def unmark_permanent_task(t)
470
+ raise "transaction #{self} has been either committed or discarded. No modification allowed" if frozen?
471
+ t = t.as_plan
472
+ if proxy = find_local_object_for_task(t)
473
+ super(proxy)
474
+ end
475
+
476
+ t = may_unwrap(t)
477
+ if t.plan == self.plan
478
+ unmarked_permanent_tasks.add(t)
479
+ end
480
+ end
481
+
482
+ def unmark_mission_task(t)
483
+ raise "transaction #{self} has been either committed or discarded. No modification allowed" if frozen?
484
+ t = t.as_plan
485
+ if proxy = find_local_object_for_task(t)
486
+ super(proxy)
487
+ end
488
+
489
+ t = may_unwrap(t)
490
+ if t.plan == self.plan
491
+ unmarked_mission_tasks.add(t)
492
+ end
493
+ end
494
+
495
+ # The set of invalidation reasons registered with {#invalidate}. It is
496
+ # cleared if the transaction is marked as valid again by calling
497
+ # {#invalid=}.
498
+ #
499
+ # @return [Array<String>]
500
+ attribute(:invalidation_reasons) { Array.new }
501
+
502
+ # Marks this transaction as either invalid or valid. If it is marked as
503
+ # valid, it clears {#invalidation_reasons}.
504
+ def invalid=(flag)
505
+ if !flag
506
+ invalidation_reasons.clear
507
+ end
508
+ @invalid = flag
509
+ end
510
+
511
+ # True if {#invalidate} has been called, and {#invalid=} has not been
512
+ # called to clear the invalidation afterwards.
513
+ def invalid?; @invalid end
514
+
515
+ # Tests if it is safe to commit this transaction
516
+ #
517
+ # @return [Boolean] it returns false if there are other transactions on
518
+ # top of it. They must be committed or discarded before this transaction
519
+ # can be committed or discarded. It also returns safe if if this
520
+ # transaction has been marked as invalid with {#invalidate}
521
+ def valid_transaction?; transactions.empty? && !invalid? end
522
+
523
+ # Marks this transaction as valid
524
+ def invalidate(reason = nil)
525
+ self.invalid = true
526
+ invalidation_reasons << [reason, caller(1)] if reason
527
+ Roby.debug do
528
+ "invalidating #{self}: #{reason}"
529
+ end
530
+ end
531
+
532
+ # Tests if it is safe to commit this transaction
533
+ #
534
+ # @return [void]
535
+ # @raise [InvalidTransaction] in all cases where {#valid_transaction?}
536
+ # returns false
537
+ def check_valid_transaction
538
+ return if valid_transaction?
539
+
540
+ unless transactions.empty?
541
+ raise InvalidTransaction, "there is still transactions on top of this one"
542
+ end
543
+ message = invalidation_reasons.map do |reason, trace|
544
+ "#{trace[0]}: #{reason}\n #{trace[1..-1].join("\n ")}"
545
+ end.join("\n")
546
+ raise InvalidTransaction, "invalid transaction: #{message}"
547
+ end
548
+
549
+ # @api private
550
+ #
551
+ # Apply the graph modifications returned by
552
+ # {#compute_graph_modifications_for}
553
+ def apply_graph_modifications(work_graphs, added, removed, updated)
554
+ added.each do |graph, parent, child, info|
555
+ work_graphs[graph].add_edge(parent, child, info)
556
+ end
557
+ removed.each do |graph, parent, child|
558
+ work_graphs[graph].remove_edge(parent, child)
559
+ end
560
+ updated.each do |graph, parent, child, info|
561
+ work_graphs[graph].set_edge_info(parent, child, info)
562
+ end
563
+ end
564
+
565
+ # @api private
566
+ #
567
+ # Compute the graph modifications that are involving the given proxy
568
+ #
569
+ # It only computes the parent modifications involving objects that are
570
+ # not proxies themselves. It computes the child modifications for every
571
+ # child
572
+ def compute_graph_modifications_for(proxy, new_relations, removed_relations, updated_relations)
573
+ real_object = proxy.__getobj__
574
+ proxy.partition_new_old_relations(:each_parent_object, include_proxies: false) do |trsc_objects, rel, new, del, existing|
575
+ trsc_graph = proxy.relation_graph_for(rel)
576
+ plan_graph = proxy.__getobj__.relation_graph_for(rel)
577
+
578
+ new.each do |task|
579
+ edge_info = trsc_graph.edge_info(trsc_objects[task], proxy)
580
+ new_relations << [plan_graph, task, real_object, edge_info]
581
+ end
582
+ del.each do |task|
583
+ removed_relations << [plan_graph, task, real_object]
584
+ end
585
+ existing.each do |task|
586
+ edge_info = trsc_graph.edge_info(trsc_objects[task], proxy)
587
+ if plan_graph.edge_info(task, real_object) != edge_info
588
+ updated_relations << [plan_graph, task, real_object, edge_info]
589
+ end
590
+ end
591
+ end
592
+
593
+ proxy.partition_new_old_relations(:each_child_object) do |trsc_objects, rel, new, del, existing|
594
+ trsc_graph = proxy.relation_graph_for(rel)
595
+ plan_graph = proxy.__getobj__.relation_graph_for(rel)
596
+
597
+ new.each do |task|
598
+ edge_info = trsc_graph.edge_info(proxy, trsc_objects[task])
599
+ new_relations << [plan_graph, real_object, task, edge_info]
600
+ end
601
+ del.each do |task|
602
+ removed_relations << [plan_graph, real_object, task]
603
+ end
604
+ existing.each do |task|
605
+ edge_info = trsc_graph.edge_info(proxy, trsc_objects[task])
606
+ if plan_graph.edge_info(real_object, task) != edge_info
607
+ updated_relations << [plan_graph, real_object, task, edge_info]
608
+ end
609
+ end
610
+ end
611
+ end
612
+
613
+ # @api private
614
+ #
615
+ # This compute the triggers that shoul be applied if we commit this
616
+ # transaction
617
+ def compute_triggers_for_committed_transaction
618
+ trigger_matches = Hash.new
619
+ plan.triggers.each do |tr|
620
+ tr.each(self) do |t|
621
+ trigger_matches[t] = tr
622
+ end
623
+ end
624
+ proxy_tasks.each do |obj, proxy|
625
+ if tr = trigger_matches.delete(proxy)
626
+ if !(tr === obj) # already triggered
627
+ trigger_matches[obj] = tr
628
+ end
629
+ end
630
+ end
631
+ trigger_matches
632
+ end
633
+
634
+ # @api private
635
+ #
636
+ # Apply the triggers as returned by
637
+ # {#compute_triggers_for_committed_transaction}
638
+ def apply_triggers_on_committed_transaction(triggered_matches)
639
+ triggered_matches.each do |task, trigger|
640
+ trigger.call(task)
641
+ end
642
+ end
643
+
644
+ # Apply the modifications represented by self to the underlying plan
645
+ # snippet in your redefinition if you do so.
646
+ def apply_modifications_to_plan
647
+ new_mission_tasks = Set.new
648
+ new_permanent_tasks = Set.new
649
+ new_permanent_events = Set.new
650
+
651
+ added_relations = Array.new
652
+ removed_relations = Array.new
653
+ updated_relations = Array.new
654
+
655
+ # We're doing a lot of modifications of this plan .. store some of
656
+ # the sets we need for later, one part to keep them unchanged, one
657
+ # part to make sure we don't do modify-while-iterate
658
+ proxy_tasks = self.proxy_tasks.dup
659
+ proxy_events = self.proxy_events.dup
660
+ plan_services = self.plan_services.dup
661
+ unmarked_mission_tasks = self.unmarked_mission_tasks.dup
662
+ unmarked_permanent_tasks = self.unmarked_permanent_tasks.dup
663
+ unmarked_permanent_events = self.unmarked_permanent_events.dup
664
+ # We're taking care of the proxies first, so that we can merge the
665
+ # transaction using Plan#merge!. However, this means that
666
+ # #may_unwrap does not work after the first few steps. We therefore
667
+ # have to store the object-to-proxy mapping
668
+ real_objects = Hash.new
669
+
670
+ # We make a copy of all relation graphs, and update them with the
671
+ # transaction data. The underlying plan graphs are not modified
672
+ #
673
+ # We do not #dup them because we don't want to dup the edge info.
674
+ # Instead, we instanciate anew and merge. The add_vertex calls are
675
+ # needed to make sure that the graph dups the in/out sets instead
676
+ # of just copying them
677
+ task_work_graphs, event_work_graphs =
678
+ plan.class.instanciate_relation_graphs
679
+ work_graphs, transaction_graphs = Hash.new, Hash.new
680
+ plan.each_task_relation_graph do |g|
681
+ work_g = work_graphs[g] = task_work_graphs[g.class]
682
+ g.each_vertex { |v| work_g.add_vertex(v) }
683
+ work_g.merge(g)
684
+ transaction_graphs[g] = task_relation_graph_for(g.class)
685
+ end
686
+ plan.each_event_relation_graph do |g|
687
+ work_g = work_graphs[g] = event_work_graphs[g.class]
688
+ g.each_vertex { |v| work_g.add_vertex(v) }
689
+ work_g.merge(g)
690
+ transaction_graphs[g] = event_relation_graph_for(g.class)
691
+ end
692
+
693
+ # First apply all changes related to the proxies to the underlying
694
+ # plan. This adds some new tasks to the plan graph, but does not add
695
+ # them to the plan itself
696
+ #
697
+ # Note that we need to do that in two passes. The first one keeps
698
+ # the transaction unchanged, the second one removes the proxies from
699
+ # the transaction. This is needed so that #commit_transaction sees
700
+ # the graph unchanged
701
+ proxy_objects = proxy_tasks.merge(proxy_events)
702
+
703
+ proxy_objects.each do |object, proxy|
704
+ real_objects[proxy] = object
705
+ compute_graph_modifications_for(
706
+ proxy, added_relations, removed_relations, updated_relations)
707
+ proxy.commit_transaction
708
+ end
709
+ proxy_tasks.dup.each do |object, proxy|
710
+ if proxy.self_owned?
711
+ if mission_task?(proxy)
712
+ new_mission_tasks << object
713
+ elsif permanent_task?(proxy)
714
+ new_permanent_tasks << object
715
+ end
716
+ end
717
+ remove_task(proxy)
718
+ end
719
+ proxy_events.dup.each do |object, proxy|
720
+ if proxy.root_object?
721
+ if permanent_event?(proxy)
722
+ new_permanent_events << object
723
+ end
724
+ remove_free_event(proxy)
725
+ end
726
+ end
727
+
728
+ work_graphs.each do |plan_g, work_g|
729
+ work_g.merge(transaction_graphs[plan_g])
730
+ end
731
+ apply_graph_modifications(work_graphs, added_relations, removed_relations, updated_relations)
732
+
733
+ begin
734
+ validate_graphs(work_graphs.values)
735
+ rescue Exception => e
736
+ raise e, "cannot apply #{self}: #{e.message}", e.backtrace
737
+ end
738
+
739
+ #### UNTIL THIS POINT we have not modified the underlying plan AT ALL
740
+ # We DID update the transaction, though
741
+
742
+ # Apply #commit_transaction on the remaining tasks
743
+ tasks.each(&:commit_transaction)
744
+ free_events.each(&:commit_transaction)
745
+
746
+ # What is left in the transaction is the network of new tasks. Just
747
+ # merge it
748
+ plan.merge_transaction!(self, work_graphs,
749
+ added_relations, removed_relations, updated_relations)
750
+
751
+ # Update the plan services on the underlying plan. The only
752
+ # thing we need to take care of is replacements and new
753
+ # services. Other modifications will be applied automatically
754
+ plan_services.each do |task, services|
755
+ services.each do |srv|
756
+ if srv.transaction_proxy?
757
+ # Modified service. Might be moved to a new task
758
+ original = srv.__getobj__
759
+ # Do NOT use may_unwrap here ... See comments at the top
760
+ # of the method
761
+ task = real_objects[task] || task
762
+ srv.commit_transaction
763
+ if original.task != task
764
+ plan.move_plan_service(original, task)
765
+ end
766
+ elsif task.transaction_proxy?
767
+ # New service on an already existing task
768
+ srv.task = task.__getobj__
769
+ plan.add_plan_service(srv)
770
+ else
771
+ # New service on a new task
772
+ plan.add_plan_service(srv)
773
+ end
774
+ end
775
+ end
776
+
777
+ new_mission_tasks.each { |t| plan.add_mission_task(t) }
778
+ new_permanent_tasks.each { |t| plan.add_permanent_task(t) }
779
+ new_permanent_events.each { |e| plan.add_permanent_event(e) }
780
+
781
+ active_fault_response_tables.each do |tbl|
782
+ plan.use_fault_response_table tbl.model, tbl.arguments
783
+ end
784
+
785
+ unmarked_permanent_events.each { |t| plan.unmark_permanent_event(t) }
786
+ unmarked_permanent_tasks.each { |t| plan.unmark_permanent_task(t) }
787
+ unmarked_mission_tasks.each { |t| plan.unmark_mission_task(t) }
788
+
789
+ proxy_objects.each do |object, proxy|
790
+ forwarder_module = Transaction::Proxying.forwarder_module_for(object.model)
791
+ proxy.extend forwarder_module
792
+ proxy.__getobj__ = object
793
+ proxy.__freeze__
794
+ end
795
+ end
796
+
797
+ # Commit all modifications that have been registered
798
+ # in this transaction
799
+ def commit_transaction
800
+ check_valid_transaction
801
+ trigger_matches = compute_triggers_for_committed_transaction
802
+ apply_modifications_to_plan
803
+ apply_triggers_on_committed_transaction(trigger_matches)
804
+ frozen!
805
+
806
+ @committed = true
807
+ committed_transaction
808
+ plan.remove_transaction(self)
809
+ @plan = nil
810
+
811
+ yield if block_given?
812
+ end
813
+
814
+ # Hook called just after this transaction has been committed
815
+ #
816
+ # @return [void]
817
+ def committed_transaction; end
818
+
819
+ def enable_proxying; @disable_proxying = false end
820
+ def disable_proxying
821
+ @disable_proxying = true
822
+ if block_given?
823
+ begin
824
+ yield
825
+ ensure
826
+ @disable_proxying = false
827
+ end
828
+ end
829
+ end
830
+ def proxying?; !@frozen && !@disable_proxying end
831
+
832
+ # Discards this transaction and all the transactions it is part of
833
+ #
834
+ # @return [void]
835
+ def discard_transaction!
836
+ transactions.each do |trsc|
837
+ trsc.discard_transaction!
838
+ end
839
+ discard_transaction
840
+ end
841
+
842
+ # Discard all the modifications that have been registered
843
+ # in this transaction
844
+ #
845
+ # @return [void]
846
+ def discard_transaction
847
+ if !transactions.empty?
848
+ raise InvalidTransaction, "there is still transactions on top of this one"
849
+ end
850
+
851
+ frozen!
852
+
853
+ discarded_transaction
854
+ plan.remove_transaction(self)
855
+ @plan = nil
856
+ end
857
+
858
+ # Hook called just after this transaction has been discarded
859
+ #
860
+ # @return [void]
861
+ def discarded_transaction; end
862
+
863
+ def frozen!
864
+ @frozen = true
865
+ end
866
+
867
+ # Clears this transaction
868
+ #
869
+ # A cleared transaction behaves as a new transaction on the same plan
870
+ # @return [void]
871
+ def clear
872
+ unmarked_mission_tasks.clear
873
+ unmarked_permanent_tasks.clear
874
+ unmarked_permanent_events.clear
875
+ proxy_tasks.each_value { |proxy| proxy.clear_relations }
876
+ proxy_tasks.clear
877
+ proxy_events.each_value { |proxy| proxy.clear_relations }
878
+ proxy_events.clear
879
+ super
880
+ end
881
+
882
+ # Hook called when a task included in self got finalized from {#plan}
883
+ #
884
+ # It invalidates the transaction and calls
885
+ # DecisionControl#finalized_plan_task(self, event) for further actions
886
+ #
887
+ # @param [Task] task the finalized task represented by its proxy in self
888
+ # @return [void]
889
+ def finalized_plan_task(task)
890
+ proxied_task = task.__getobj__
891
+
892
+ invalidate("task #{task} has been removed from the plan")
893
+ discard_modifications(proxied_task)
894
+ control.finalized_plan_task(self, task)
895
+ end
896
+
897
+ # Hook called when an event included in self got finalized from {#plan}
898
+ #
899
+ # It invalidates the transaction and calls
900
+ # DecisionControl#finalized_plan_event(self, event) for further actions
901
+ #
902
+ # @param [EventGenerator] event the finalized event represented by its proxy in self
903
+ # @return [void]
904
+ def finalized_plan_event(event)
905
+ proxied_event = event.__getobj__
906
+
907
+ invalidate("event #{event} has been removed from the plan")
908
+ discard_modifications(proxied_event)
909
+ control.finalized_plan_event(self, event)
910
+ end
911
+
912
+ # Hook called when a relation is added between plan objects that are
913
+ # present in the transaction
914
+ #
915
+ # If the new relation is not present in the transaction as well, it
916
+ # invalidates the transaction and calls
917
+ # DecisionControl#adding_plan_relation(self, parent, child, relations, info) for further action
918
+ #
919
+ # @param [PlanObject] parent the parent object represented by its proxy in self
920
+ # @param [PlanObject] child the child object represented by its proxy in self
921
+ # @param [Array<Relations::Graph>] relations the graphs in which a relation
922
+ # has been added
923
+ # @param [Object] info the added information for the new edges
924
+ # (relation specific)
925
+ # @return [void]
926
+ def adding_plan_relation(parent, child, relations, info)
927
+ missing_relations = relations.find_all do |rel|
928
+ !parent.child_object?(child, rel)
929
+ end
930
+ unless missing_relations.empty?
931
+ invalidate("plan added a relation #{parent} -> #{child} in #{relations} with info #{info}")
932
+ control.adding_plan_relation(self, parent, child, relations, info)
933
+ end
934
+ end
935
+
936
+ # Hook called when a relation is removed between plan objects that are
937
+ # present in the transaction
938
+ #
939
+ # If the removed relation is still present in the transaction as well, it
940
+ # invalidates the transaction and calls
941
+ # DecisionControl#removing_plan_relation(self, parent, child, relations, info) for further action
942
+ #
943
+ # @param [PlanObject] parent the parent object represented by its proxy in self
944
+ # @param [PlanObject] child the child object represented by its proxy in self
945
+ # @param [Array<Relations::Graph>] relations the graphs in which a relation
946
+ # has been added
947
+ # @return [void]
948
+ def removing_plan_relation(parent, child, relations)
949
+ present_relations = relations.find_all do |rel|
950
+ parent.child_object?(child, rel)
951
+ end
952
+ unless present_relations.empty?
953
+ invalidate("plan removed a relation #{parent} -> #{child} in #{relations}")
954
+ control.removing_plan_relation(self, parent, child, relations)
955
+ end
956
+ end
957
+
958
+ # Returns [plan_set, transaction_set], where the first is the set of
959
+ # plan tasks matching +matcher+ and the second the set of transaction
960
+ # tasks matching it. The two sets are disjoint.
961
+ #
962
+ # This will be stored by the Query object as the query result. Note
963
+ # that, at this point, the transaction has not been modified even though
964
+ # it applies on the global scope. New proxies will only be created when
965
+ # Query#each is called.
966
+ def query_result_set(matcher) # :nodoc:
967
+ plan_set = Set.new
968
+ if matcher.scope == :global
969
+ plan_result_set = plan.query_result_set(matcher)
970
+ plan.query_each(plan_result_set) do |task|
971
+ plan_set << task if !has_proxy_for_task?(task)
972
+ end
973
+ end
974
+
975
+ transaction_set = super
976
+ [plan_set, transaction_set]
977
+ end
978
+
979
+ # Yields tasks in the result set of +query+. Unlike Query#result_set,
980
+ # all the tasks are included in the transaction
981
+ #
982
+ # +result_set+ is the value returned by #query_result_set.
983
+ def query_each(result_set) # :nodoc:
984
+ plan_set, trsc_set = result_set
985
+ plan_set.each { |task| yield(wrap_task(task)) }
986
+ trsc_set.each { |task| yield(task) }
987
+ end
988
+
989
+ class ReachabilityVisitor < RGL::DFSVisitor
990
+ attr_reader :transaction
991
+ attr_reader :start_vertex
992
+
993
+ def initialize(graph, transaction)
994
+ super(graph)
995
+ @transaction = transaction
996
+ end
997
+
998
+ def handle_start_vertex(v)
999
+ @start_vertex = v
1000
+ end
1001
+ end
1002
+
1003
+ class ReachabilityPlanVisitor < ReachabilityVisitor
1004
+ attr_reader :transaction_seeds
1005
+ attr_reader :plan_set
1006
+
1007
+ def initialize(graph, transaction, transaction_seeds, plan_set)
1008
+ super(graph, transaction)
1009
+ @transaction_seeds = transaction_seeds
1010
+ @plan_set = plan_set
1011
+ end
1012
+
1013
+ def follow_edge?(u, v)
1014
+ if transaction.find_local_object_for_task(u) && transaction.find_local_object_for_task(v)
1015
+ false
1016
+ else true
1017
+ end
1018
+ end
1019
+
1020
+ def handle_examine_vertex(v)
1021
+ if (start_vertex != v) && plan_set.include?(v)
1022
+ throw :reachable, true
1023
+ elsif proxy = transaction.find_local_object_for_task(v)
1024
+ transaction_seeds << proxy
1025
+ end
1026
+ end
1027
+ end
1028
+
1029
+ class ReachabilityTransactionVisitor < ReachabilityVisitor
1030
+ attr_reader :transaction_set
1031
+ attr_reader :plan_seeds
1032
+
1033
+ def initialize(graph, transaction, plan_seeds, transaction_set)
1034
+ super(graph, transaction)
1035
+ @plan_seeds = plan_seeds
1036
+ @transaction_set = transaction_set
1037
+ end
1038
+
1039
+ def handle_examine_vertex(v)
1040
+ if (start_vertex != v) && transaction_set.include?(v)
1041
+ throw :reachable, true
1042
+ elsif v.transaction_proxy?
1043
+ plan_seeds << v.__getobj__
1044
+ end
1045
+ end
1046
+ end
1047
+
1048
+ # @api private
1049
+ #
1050
+ # Tests whether a task in plan_set or proxy_set would be reachable from
1051
+ # 'task' if the transaction was applied
1052
+ def reachable_on_applied_transaction?(transaction_seeds, transaction_set, transaction_graph,
1053
+ plan_seeds, plan_set, plan_graph)
1054
+ transaction_visitor = ReachabilityTransactionVisitor.new(
1055
+ transaction_graph, self, plan_seeds, transaction_set)
1056
+ if task = transaction_seeds.first
1057
+ transaction_visitor.handle_start_vertex(task)
1058
+ end
1059
+ plan_visitor = ReachabilityPlanVisitor.new(
1060
+ plan_graph, self, transaction_seeds, plan_set)
1061
+ if task = plan_seeds.first
1062
+ plan_visitor.handle_start_vertex(task)
1063
+ end
1064
+
1065
+ catch(:reachable) do
1066
+ while !transaction_seeds.empty? || !plan_seeds.empty?
1067
+ transaction_seeds.each do |seed|
1068
+ seed = transaction_seeds.shift
1069
+ if !transaction_visitor.finished_vertex?(seed)
1070
+ transaction_graph.depth_first_visit(seed, transaction_visitor) {}
1071
+ end
1072
+ end
1073
+ transaction_seeds.clear
1074
+
1075
+ plan_seeds.each do |seed|
1076
+ seed = plan_seeds.shift
1077
+ if !plan_visitor.finished_vertex?(seed)
1078
+ plan_graph.depth_first_visit(seed, plan_visitor) {}
1079
+ end
1080
+ end
1081
+ plan_seeds.clear
1082
+ end
1083
+ return false
1084
+ end
1085
+ true
1086
+ end
1087
+
1088
+ # @api private
1089
+ #
1090
+ # Given the result set of +query+, returns the subset of tasks which
1091
+ # have no parent in +query+
1092
+ #
1093
+ # This is never called directly, but is used by the Query API
1094
+ def query_roots(result_set, relation) # :nodoc:
1095
+ plan_set , trsc_set = *result_set
1096
+ plan_children , trsc_children = Set.new , Set.new
1097
+
1098
+ trsc_graph = task_relation_graph_for(relation).reverse
1099
+ plan_graph = plan.task_relation_graph_for(relation).reverse
1100
+
1101
+ plan_result = plan_set.find_all do |task|
1102
+ !reachable_on_applied_transaction?(
1103
+ [], trsc_set, trsc_graph,
1104
+ [task], plan_set, plan_graph)
1105
+ end
1106
+
1107
+ trsc_result = trsc_set.find_all do |task|
1108
+ !reachable_on_applied_transaction?(
1109
+ [task], trsc_set, trsc_graph,
1110
+ [], plan_set, plan_graph)
1111
+ end
1112
+
1113
+ [plan_result.to_set, trsc_result.to_set]
1114
+ end
1115
+
1116
+ def discard_modifications(object)
1117
+ if object.respond_to?(:to_task)
1118
+ remove_task(object.to_task)
1119
+ else
1120
+ remove_event(object.to_task)
1121
+ end
1122
+ end
1123
+ end
1124
+ end
1125
+