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.
- checksums.yaml +7 -0
- data/.deep-cover.rb +3 -0
- data/.gitattributes +1 -0
- data/.gitignore +24 -0
- data/.simplecov +10 -0
- data/.travis.yml +17 -0
- data/.yardopts +4 -0
- data/Gemfile +15 -0
- data/README.md +11 -0
- data/Rakefile +47 -177
- data/benchmark/{alloc_misc.rb → attic/alloc_misc.rb} +2 -2
- data/benchmark/{discovery_latency.rb → attic/discovery_latency.rb} +19 -19
- data/benchmark/{garbage_collection.rb → attic/garbage_collection.rb} +9 -9
- data/benchmark/{genom.rb → attic/genom.rb} +0 -0
- data/benchmark/attic/transactions.rb +62 -0
- data/benchmark/plan_basic_operations.rb +28 -0
- data/benchmark/relations/graph.rb +63 -0
- data/benchmark/ruby/identity.rb +18 -0
- data/benchmark/ruby/set_intersect_vs_hash_merge.rb +39 -0
- data/benchmark/ruby/yield_vs_block.rb +35 -0
- data/benchmark/run +5 -0
- data/benchmark/synthetic_plan_modifications_with_transactions.rb +79 -0
- data/benchmark/transactions.rb +99 -51
- data/bin/roby +38 -197
- data/bin/roby-display +14 -0
- data/bin/roby-log +3 -176
- data/doc/guide/{src → attic}/abstraction/achieve_with.page +1 -1
- data/doc/guide/{src → attic}/abstraction/forwarding.page +1 -1
- data/doc/guide/{src → attic}/abstraction/hierarchy.page +1 -1
- data/doc/guide/{src → attic}/abstraction/index.page +1 -1
- data/doc/guide/{src → attic}/abstraction/task_models.page +1 -1
- data/doc/guide/{overview.rdoc → attic/cycle/api_overview.rdoc} +6 -1
- data/doc/guide/{src → attic}/cycle/cycle-overview.png +0 -0
- data/doc/guide/{src → attic}/cycle/cycle-overview.svg +0 -0
- data/doc/guide/attic/cycle/error_handling.page +98 -0
- data/doc/guide/{src → attic}/cycle/error_instantaneous_repair.png +0 -0
- data/doc/guide/{src → attic}/cycle/error_instantaneous_repair.svg +0 -0
- data/doc/guide/{src/cycle/error_handling.page → attic/cycle/error_sources.page} +46 -89
- data/doc/guide/{src → attic}/cycle/garbage_collection.page +1 -1
- data/doc/guide/{src → attic}/cycle/index.page +1 -1
- data/doc/guide/{src → attic}/cycle/propagation.page +11 -1
- data/doc/guide/{src → attic}/cycle/propagation_diamond.png +0 -0
- data/doc/guide/{src → attic}/cycle/propagation_diamond.svg +0 -0
- data/doc/guide/attic/plans/building_plans.page +89 -0
- data/doc/guide/attic/plans/code.page +192 -0
- data/doc/guide/{src/basics → attic/plans}/events.page +3 -4
- data/doc/guide/attic/plans/index.page +7 -0
- data/doc/guide/{plan_modifications.rdoc → attic/plans/plan_modifications.rdoc} +5 -3
- data/doc/guide/{src/basics → attic/plans}/plan_objects.page +2 -1
- data/doc/guide/attic/plans/querying_plans.page +5 -0
- data/doc/guide/{src/basics → attic/plans}/tasks.page +20 -20
- data/doc/guide/config.yaml +7 -4
- data/doc/guide/ext/extended_menu.rb +29 -0
- data/doc/guide/ext/init.rb +6 -0
- data/doc/guide/ext/rdoc_links.rb +7 -6
- data/doc/guide/src/advanced_concepts/history.page +5 -0
- data/doc/guide/src/advanced_concepts/index.page +11 -0
- data/doc/guide/src/advanced_concepts/recognizing_patterns.page +83 -0
- data/doc/guide/src/advanced_concepts/scheduling.page +87 -0
- data/doc/guide/src/advanced_concepts/transactions.page +5 -0
- data/doc/guide/src/advanced_concepts/unreachability.page +42 -0
- data/doc/guide/src/base.template +96 -0
- data/doc/guide/src/basics_shell_header.txt +5 -7
- data/doc/guide/src/building/action_coordination.page +96 -0
- data/doc/guide/src/building/actions.page +124 -0
- data/doc/guide/src/building/file_layout.page +71 -0
- data/doc/guide/src/building/index.page +50 -0
- data/doc/guide/src/building/patterns.page +86 -0
- data/doc/guide/src/building/patterns_forwarding.png +0 -0
- data/doc/guide/src/building/patterns_forwarding.svg +277 -0
- data/doc/guide/src/building/runtime.page +95 -0
- data/doc/guide/src/building/task_models.page +94 -0
- data/doc/guide/src/building/tasks.page +284 -0
- data/doc/guide/src/concepts/error_handling.page +100 -0
- data/doc/guide/src/concepts/exception_propagation.png +0 -0
- data/doc/guide/src/concepts/exception_propagation.svg +445 -0
- data/doc/guide/src/concepts/execution.page +85 -0
- data/doc/guide/src/concepts/execution.png +0 -0
- data/doc/guide/src/concepts/execution.svg +573 -0
- data/doc/guide/src/concepts/execution_cycle.png +0 -0
- data/doc/guide/src/concepts/garbage_collection.page +57 -0
- data/doc/guide/src/concepts/index.page +27 -0
- data/doc/guide/src/concepts/plans.page +101 -0
- data/doc/guide/src/concepts/policy.page +31 -0
- data/doc/guide/src/concepts/reactor.page +61 -0
- data/doc/guide/src/concepts/simple_plan_example.png +0 -0
- data/doc/guide/src/concepts/simple_plan_example.svg +376 -0
- data/doc/guide/src/default.template +9 -74
- data/doc/guide/src/event_relations/forward.page +71 -0
- data/doc/guide/src/event_relations/index.page +12 -0
- data/doc/guide/src/event_relations/scheduling_constraints.page +43 -0
- data/doc/guide/src/event_relations/signal.page +55 -0
- data/doc/guide/src/event_relations/temporal_constraints.page +77 -0
- data/doc/guide/src/htmldoc.metainfo +21 -8
- data/doc/guide/src/index.page +8 -3
- data/doc/guide/src/{introduction/install.page → installation/index.page} +37 -25
- data/doc/guide/src/installation/publications.page +14 -0
- data/doc/guide/src/{introduction → installation}/videos.page +14 -7
- data/doc/guide/src/interacting/index.page +16 -0
- data/doc/guide/src/interacting/run.page +33 -0
- data/doc/guide/src/interacting/shell.page +95 -0
- data/doc/guide/src/plugins/creating_plugins.page +72 -0
- data/doc/guide/src/plugins/index.page +27 -5
- data/doc/guide/src/plugins/{fault_tolerance.page → standard_plugins/fault_tolerance.page} +2 -2
- data/doc/guide/src/plugins/standard_plugins/index.page +11 -0
- data/doc/guide/src/plugins/{subsystems.page → standard_plugins/subsystems.page} +2 -2
- data/doc/guide/src/style_screen.css +687 -0
- data/doc/guide/src/task_relations/dependency.page +107 -0
- data/doc/guide/src/task_relations/executed_by.page +77 -0
- data/doc/guide/src/task_relations/index.page +12 -0
- data/doc/guide/src/task_relations/new_relations.page +119 -0
- data/doc/guide/src/task_relations/planned_by.page +46 -0
- data/doc/guide/src/tutorial/app.page +117 -0
- data/doc/guide/src/{basics → tutorial}/code_examples.page +6 -5
- data/doc/guide/src/{basics → tutorial}/dry.page +15 -15
- data/doc/guide/src/{basics → tutorial}/errors.page +43 -68
- data/doc/guide/src/tutorial/events.page +195 -0
- data/doc/guide/src/{basics → tutorial}/hierarchy.page +53 -52
- data/doc/guide/src/tutorial/index.page +13 -0
- data/doc/guide/src/tutorial/log_replay/goForward_1.png +0 -0
- data/doc/guide/src/tutorial/log_replay/goForward_2.png +0 -0
- data/doc/guide/src/tutorial/log_replay/goForward_3.png +0 -0
- data/doc/guide/src/{basics → tutorial}/log_replay/goForward_4.png +0 -0
- data/doc/guide/src/tutorial/log_replay/goForward_5.png +0 -0
- data/doc/guide/src/{basics → tutorial}/log_replay/hierarchy_error_1.png +0 -0
- data/doc/guide/src/{basics → tutorial}/log_replay/hierarchy_error_2.png +0 -0
- data/doc/guide/src/{basics → tutorial}/log_replay/hierarchy_error_3.png +0 -0
- data/doc/guide/src/tutorial/log_replay/moveto_code_error.png +0 -0
- data/doc/guide/src/{basics → tutorial}/log_replay/plan_repair_1.png +0 -0
- data/doc/guide/src/{basics → tutorial}/log_replay/plan_repair_2.png +0 -0
- data/doc/guide/src/{basics → tutorial}/log_replay/plan_repair_3.png +0 -0
- data/doc/guide/src/tutorial/log_replay/plan_repair_4.png +0 -0
- data/doc/guide/src/tutorial/log_replay/roby_log_main_window.png +0 -0
- data/doc/guide/src/{basics → tutorial}/log_replay/roby_log_relation_window.png +0 -0
- data/doc/guide/src/{basics → tutorial}/log_replay/roby_replay_event_representation.png +0 -0
- data/doc/guide/src/tutorial/relations_display.page +153 -0
- data/doc/guide/src/{basics → tutorial}/roby_cycle_overview.png +0 -0
- data/doc/guide/src/tutorial/shell.page +121 -0
- data/doc/guide/src/{basics → tutorial}/summary.page +1 -1
- data/doc/guide/src/tutorial/tasks.page +374 -0
- data/lib/roby.rb +102 -47
- data/lib/roby/actions.rb +17 -0
- data/lib/roby/actions/action.rb +80 -0
- data/lib/roby/actions/interface.rb +45 -0
- data/lib/roby/actions/library.rb +23 -0
- data/lib/roby/actions/models/action.rb +224 -0
- data/lib/roby/actions/models/coordination_action.rb +58 -0
- data/lib/roby/actions/models/interface.rb +22 -0
- data/lib/roby/actions/models/interface_base.rb +294 -0
- data/lib/roby/actions/models/library.rb +12 -0
- data/lib/roby/actions/models/method_action.rb +90 -0
- data/lib/roby/actions/task.rb +114 -0
- data/lib/roby/and_generator.rb +125 -0
- data/lib/roby/app.rb +2795 -829
- data/lib/roby/app/autotest_console_reporter.rb +138 -0
- data/lib/roby/app/base.rb +21 -0
- data/lib/roby/app/cucumber.rb +2 -0
- data/lib/roby/app/cucumber/controller.rb +439 -0
- data/lib/roby/app/cucumber/helpers.rb +280 -0
- data/lib/roby/app/cucumber/world.rb +32 -0
- data/lib/roby/app/debug.rb +136 -0
- data/lib/roby/app/gen.rb +2 -0
- data/lib/roby/app/rake.rb +178 -38
- data/lib/roby/app/robot_config.rb +9 -0
- data/lib/roby/app/robot_names.rb +115 -0
- data/lib/roby/app/run.rb +3 -2
- data/lib/roby/app/scripts.rb +72 -0
- data/lib/roby/app/scripts/autotest.rb +173 -0
- data/lib/roby/app/scripts/display.rb +2 -0
- data/lib/roby/app/scripts/restart.rb +52 -0
- data/lib/roby/app/scripts/results.rb +17 -8
- data/lib/roby/app/scripts/run.rb +155 -24
- data/lib/roby/app/scripts/shell.rb +147 -62
- data/lib/roby/app/scripts/test.rb +107 -22
- data/lib/roby/app/test_reporter.rb +74 -0
- data/lib/roby/app/test_server.rb +159 -0
- data/lib/roby/app/vagrant.rb +47 -0
- data/lib/roby/backports.rb +16 -0
- data/lib/roby/cli/display.rb +190 -0
- data/lib/roby/cli/exceptions.rb +17 -0
- data/lib/roby/cli/gen/actions/class.rb +5 -0
- data/lib/roby/cli/gen/actions/test.rb +6 -0
- data/lib/roby/cli/gen/app/.yardopts +6 -0
- data/lib/roby/cli/gen/app/README.md +28 -0
- data/lib/roby/cli/gen/app/Rakefile +15 -0
- data/{app → lib/roby/cli/gen/app}/config/app.yml +29 -39
- data/lib/roby/cli/gen/app/models/.gitattributes +1 -0
- data/{app → lib/roby/cli/gen/app/scripts}/controllers/.gitattributes +0 -0
- data/{app/data/.gitattributes → lib/roby/cli/gen/app/test/.gitignore} +0 -0
- data/lib/roby/cli/gen/class/class.rb +6 -0
- data/lib/roby/cli/gen/class/test.rb +7 -0
- data/lib/roby/cli/gen/helpers.rb +203 -0
- data/lib/roby/cli/gen/module/module.rb +5 -0
- data/lib/roby/cli/gen/module/test.rb +6 -0
- data/lib/roby/cli/gen/roby_app/config/init.rb +17 -0
- data/lib/roby/cli/gen/roby_app/config/robots/robot.rb +40 -0
- data/lib/roby/cli/gen/task/class.rb +44 -0
- data/lib/roby/cli/gen/task/test.rb +6 -0
- data/lib/roby/cli/gen_main.rb +120 -0
- data/lib/roby/cli/log.rb +276 -0
- data/lib/roby/cli/log/flamegraph.html +499 -0
- data/lib/roby/cli/log/flamegraph_renderer.rb +88 -0
- data/lib/roby/cli/main.rb +153 -0
- data/lib/roby/coordination.rb +60 -0
- data/lib/roby/coordination/action_script.rb +25 -0
- data/lib/roby/coordination/action_state_machine.rb +125 -0
- data/lib/roby/coordination/actions.rb +106 -0
- data/lib/roby/coordination/base.rb +145 -0
- data/lib/roby/coordination/calculus.rb +40 -0
- data/lib/roby/coordination/child.rb +28 -0
- data/lib/roby/coordination/event.rb +29 -0
- data/lib/roby/coordination/fault_handler.rb +25 -0
- data/lib/roby/coordination/fault_handling_task.rb +13 -0
- data/lib/roby/coordination/fault_response_table.rb +110 -0
- data/lib/roby/coordination/models/action_script.rb +64 -0
- data/lib/roby/coordination/models/action_state_machine.rb +224 -0
- data/lib/roby/coordination/models/actions.rb +191 -0
- data/lib/roby/coordination/models/arguments.rb +55 -0
- data/lib/roby/coordination/models/base.rb +176 -0
- data/lib/roby/coordination/models/capture.rb +86 -0
- data/lib/roby/coordination/models/child.rb +35 -0
- data/lib/roby/coordination/models/event.rb +41 -0
- data/lib/roby/coordination/models/exceptions.rb +42 -0
- data/lib/roby/coordination/models/fault_handler.rb +219 -0
- data/lib/roby/coordination/models/fault_response_table.rb +77 -0
- data/lib/roby/coordination/models/root.rb +22 -0
- data/lib/roby/coordination/models/script.rb +283 -0
- data/lib/roby/coordination/models/task.rb +184 -0
- data/lib/roby/coordination/models/task_from_action.rb +50 -0
- data/lib/roby/coordination/models/task_from_as_plan.rb +33 -0
- data/lib/roby/coordination/models/task_from_instanciation_object.rb +31 -0
- data/lib/roby/coordination/models/task_from_variable.rb +27 -0
- data/lib/roby/coordination/models/task_with_dependencies.rb +48 -0
- data/lib/roby/coordination/models/variable.rb +32 -0
- data/lib/roby/coordination/script.rb +200 -0
- data/lib/roby/coordination/script_instruction.rb +12 -0
- data/lib/roby/coordination/task.rb +45 -0
- data/lib/roby/coordination/task_base.rb +69 -0
- data/lib/roby/coordination/task_script.rb +293 -0
- data/lib/roby/coordination/task_state_machine.rb +308 -0
- data/lib/roby/decision_control.rb +33 -21
- data/lib/roby/distributed_object.rb +76 -0
- data/lib/roby/droby.rb +17 -0
- data/lib/roby/droby/droby_id.rb +6 -0
- data/lib/roby/droby/enable.rb +153 -0
- data/lib/roby/droby/event_logger.rb +189 -0
- data/lib/roby/droby/event_logging.rb +57 -0
- data/lib/roby/droby/exceptions.rb +14 -0
- data/lib/roby/droby/identifiable.rb +22 -0
- data/lib/roby/droby/logfile.rb +141 -0
- data/lib/roby/droby/logfile/client.rb +176 -0
- data/lib/roby/droby/logfile/file_format.md +97 -0
- data/lib/roby/droby/logfile/index.rb +117 -0
- data/lib/roby/droby/logfile/reader.rb +139 -0
- data/lib/roby/droby/logfile/server.rb +199 -0
- data/lib/roby/droby/logfile/writer.rb +114 -0
- data/lib/roby/droby/marshal.rb +264 -0
- data/lib/roby/droby/marshallable.rb +12 -0
- data/lib/roby/droby/null_event_logger.rb +25 -0
- data/lib/roby/droby/object_manager.rb +205 -0
- data/lib/roby/droby/peer_id.rb +6 -0
- data/lib/roby/droby/plan_rebuilder.rb +373 -0
- data/lib/roby/droby/rebuilt_plan.rb +160 -0
- data/lib/roby/droby/remote_droby_id.rb +6 -0
- data/lib/roby/droby/timepoints.rb +205 -0
- data/lib/roby/droby/timepoints_ctf.metadata.erb +101 -0
- data/lib/roby/droby/timepoints_ctf.rb +125 -0
- data/lib/roby/droby/v5.rb +14 -0
- data/lib/roby/droby/v5/builtin.rb +120 -0
- data/lib/roby/droby/v5/droby_class.rb +45 -0
- data/lib/roby/droby/v5/droby_constant.rb +81 -0
- data/lib/roby/droby/v5/droby_dump.rb +1026 -0
- data/lib/roby/droby/v5/droby_id.rb +44 -0
- data/lib/roby/droby/v5/droby_model.rb +82 -0
- data/lib/roby/droby/v5/peer_id.rb +10 -0
- data/lib/roby/droby/v5/remote_droby_id.rb +42 -0
- data/lib/roby/event.rb +79 -957
- data/lib/roby/event_constraints.rb +835 -0
- data/lib/roby/event_generator.rb +1047 -0
- data/lib/roby/event_structure/causal_link.rb +6 -0
- data/lib/roby/event_structure/forwarding.rb +6 -0
- data/lib/roby/event_structure/precedence.rb +7 -0
- data/lib/roby/event_structure/signal.rb +8 -0
- data/lib/roby/event_structure/temporal_constraints.rb +640 -0
- data/lib/roby/exceptions.rb +446 -152
- data/lib/roby/executable_plan.rb +549 -0
- data/lib/roby/execution_engine.rb +1997 -950
- data/lib/roby/filter_generator.rb +26 -0
- data/lib/roby/gui/chronicle_view.rb +225 -0
- data/lib/roby/gui/chronicle_widget.rb +925 -0
- data/lib/roby/gui/dot_id.rb +11 -0
- data/lib/roby/gui/exception_view.rb +44 -0
- data/lib/roby/gui/log_display.rb +273 -0
- data/lib/roby/gui/model_views.rb +2 -0
- data/lib/roby/gui/model_views/action_interface.rb +53 -0
- data/lib/roby/gui/model_views/task.rb +47 -0
- data/lib/roby/gui/model_views/task.rhtml +41 -0
- data/lib/roby/gui/object_info_view.rb +89 -0
- data/lib/roby/gui/plan_dot_layout.rb +427 -0
- data/lib/roby/gui/plan_rebuilder_widget.rb +357 -0
- data/lib/roby/gui/qt4_toMSecsSinceEpoch.rb +8 -0
- data/lib/roby/gui/relations_view.rb +278 -0
- data/lib/roby/gui/relations_view/relations.ui +139 -0
- data/lib/roby/gui/relations_view/relations_canvas.rb +1088 -0
- data/lib/roby/gui/relations_view/relations_config.rb +292 -0
- data/lib/roby/gui/relations_view/relations_view.ui +53 -0
- data/lib/roby/gui/scheduler_view.css +24 -0
- data/lib/roby/gui/scheduler_view.rb +46 -0
- data/lib/roby/gui/scheduler_view.rhtml +53 -0
- data/lib/roby/gui/stepping.rb +93 -0
- data/lib/roby/gui/stepping.ui +181 -0
- data/lib/roby/gui/styles.rb +81 -0
- data/lib/roby/gui/task_display_configuration.rb +42 -0
- data/lib/roby/gui/task_state_at.rb +38 -0
- data/lib/roby/hooks.rb +26 -0
- data/lib/roby/interface.rb +136 -469
- data/lib/roby/interface/async.rb +20 -0
- data/lib/roby/interface/async/action_monitor.rb +188 -0
- data/lib/roby/interface/async/interface.rb +498 -0
- data/lib/roby/interface/async/job_monitor.rb +213 -0
- data/lib/roby/interface/async/log.rb +238 -0
- data/lib/roby/interface/async/new_job_listener.rb +79 -0
- data/lib/roby/interface/async/ui_connector.rb +183 -0
- data/lib/roby/interface/client.rb +553 -0
- data/lib/roby/interface/command.rb +24 -0
- data/lib/roby/interface/command_argument.rb +16 -0
- data/lib/roby/interface/command_library.rb +92 -0
- data/lib/roby/interface/droby_channel.rb +174 -0
- data/lib/roby/interface/exceptions.rb +22 -0
- data/lib/roby/interface/interface.rb +655 -0
- data/lib/roby/interface/job.rb +47 -0
- data/lib/roby/interface/rest.rb +10 -0
- data/lib/roby/interface/rest/api.rb +29 -0
- data/lib/roby/interface/rest/helpers.rb +24 -0
- data/lib/roby/interface/rest/server.rb +212 -0
- data/lib/roby/interface/server.rb +154 -0
- data/lib/roby/interface/shell_client.rb +468 -0
- data/lib/roby/interface/shell_subcommand.rb +24 -0
- data/lib/roby/interface/subcommand_client.rb +35 -0
- data/lib/roby/interface/tcp.rb +168 -0
- data/lib/roby/models/arguments.rb +112 -0
- data/lib/roby/models/plan_object.rb +83 -0
- data/lib/roby/models/task.rb +835 -0
- data/lib/roby/models/task_event.rb +62 -0
- data/lib/roby/models/task_service.rb +78 -0
- data/lib/roby/or_generator.rb +88 -0
- data/lib/roby/plan.rb +1751 -864
- data/lib/roby/plan_object.rb +611 -0
- data/lib/roby/plan_service.rb +200 -0
- data/lib/roby/promise.rb +332 -0
- data/lib/roby/queries.rb +23 -0
- data/lib/roby/queries/and_matcher.rb +32 -0
- data/lib/roby/queries/any.rb +27 -0
- data/lib/roby/queries/code_error_matcher.rb +58 -0
- data/lib/roby/queries/event_generator_matcher.rb +9 -0
- data/lib/roby/queries/execution_exception_matcher.rb +165 -0
- data/lib/roby/queries/index.rb +165 -0
- data/lib/roby/queries/localized_error_matcher.rb +149 -0
- data/lib/roby/queries/matcher_base.rb +107 -0
- data/lib/roby/queries/none.rb +27 -0
- data/lib/roby/queries/not_matcher.rb +30 -0
- data/lib/roby/queries/op_matcher.rb +8 -0
- data/lib/roby/queries/or_matcher.rb +30 -0
- data/lib/roby/queries/plan_object_matcher.rb +363 -0
- data/lib/roby/queries/query.rb +188 -0
- data/lib/roby/queries/task_event_generator_matcher.rb +86 -0
- data/lib/roby/queries/task_matcher.rb +344 -0
- data/lib/roby/relations.rb +42 -678
- data/lib/roby/relations/bidirectional_directed_adjacency_graph.rb +492 -0
- data/lib/roby/relations/directed_relation_support.rb +268 -0
- data/lib/roby/relations/event_relation_graph.rb +19 -0
- data/lib/roby/relations/fork_merge_visitor.rb +154 -0
- data/lib/roby/relations/graph.rb +533 -0
- data/lib/roby/relations/models/directed_relation_support.rb +11 -0
- data/lib/roby/relations/models/graph.rb +75 -0
- data/lib/roby/relations/models/task_relation_graph.rb +18 -0
- data/lib/roby/relations/space.rb +380 -0
- data/lib/roby/relations/task_relation_graph.rb +20 -0
- data/lib/roby/robot.rb +85 -38
- data/lib/roby/schedulers/basic.rb +155 -25
- data/lib/roby/schedulers/null.rb +20 -0
- data/lib/roby/schedulers/reporting.rb +31 -0
- data/lib/roby/schedulers/state.rb +129 -0
- data/lib/roby/schedulers/temporal.rb +91 -0
- data/lib/roby/singletons.rb +87 -0
- data/lib/roby/standalone.rb +4 -2
- data/lib/roby/standard_errors.rb +405 -82
- data/lib/roby/state.rb +6 -3
- data/lib/roby/state/conf_model.rb +5 -0
- data/lib/roby/state/events.rb +181 -95
- data/lib/roby/state/goal_model.rb +77 -0
- data/lib/roby/state/open_struct.rb +591 -0
- data/lib/roby/state/open_struct_model.rb +68 -0
- data/lib/roby/state/pos.rb +45 -45
- data/lib/roby/state/shapes.rb +11 -11
- data/lib/roby/state/state_model.rb +303 -0
- data/lib/roby/state/task.rb +43 -0
- data/lib/roby/support.rb +88 -148
- data/lib/roby/task.rb +1361 -1750
- data/lib/roby/task_arguments.rb +428 -0
- data/lib/roby/task_event.rb +127 -0
- data/lib/roby/task_event_generator.rb +337 -0
- data/lib/roby/task_service.rb +6 -0
- data/lib/roby/task_structure/conflicts.rb +104 -0
- data/lib/roby/task_structure/dependency.rb +932 -0
- data/lib/roby/task_structure/error_handling.rb +118 -0
- data/lib/roby/task_structure/executed_by.rb +234 -0
- data/lib/roby/task_structure/planned_by.rb +90 -0
- data/lib/roby/tasks/aggregator.rb +37 -0
- data/lib/roby/tasks/external_process.rb +275 -0
- data/lib/roby/tasks/group.rb +27 -0
- data/lib/roby/tasks/null.rb +19 -0
- data/lib/roby/tasks/parallel.rb +43 -0
- data/lib/roby/tasks/sequence.rb +88 -0
- data/lib/roby/tasks/simple.rb +21 -0
- data/lib/roby/{thread_task.rb → tasks/thread.rb} +50 -24
- data/lib/roby/tasks/timeout.rb +17 -0
- data/lib/roby/tasks/virtual.rb +55 -0
- data/lib/roby/template_plan.rb +7 -0
- data/lib/roby/test/aruba_minitest.rb +74 -0
- data/lib/roby/test/assertion.rb +16 -0
- data/lib/roby/test/assertions.rb +490 -0
- data/lib/roby/test/common.rb +368 -591
- data/lib/roby/test/dsl.rb +149 -0
- data/lib/roby/test/error.rb +18 -0
- data/lib/roby/test/event_reporter.rb +83 -0
- data/lib/roby/test/execution_expectations.rb +1134 -0
- data/lib/roby/test/expect_execution.rb +151 -0
- data/lib/roby/test/minitest_helpers.rb +166 -0
- data/lib/roby/test/roby_app_helpers.rb +200 -0
- data/lib/roby/test/run_planners.rb +155 -0
- data/lib/roby/test/self.rb +112 -0
- data/lib/roby/test/spec.rb +198 -0
- data/lib/roby/test/tasks/empty_task.rb +4 -4
- data/lib/roby/test/tasks/goto.rb +28 -27
- data/lib/roby/test/teardown_plans.rb +100 -0
- data/lib/roby/test/testcase.rb +239 -307
- data/lib/roby/test/tools.rb +159 -155
- data/lib/roby/test/validate_state_machine.rb +75 -0
- data/lib/roby/transaction.rb +1125 -0
- data/lib/roby/transaction/event_generator_proxy.rb +63 -0
- data/lib/roby/transaction/plan_object_proxy.rb +99 -0
- data/lib/roby/transaction/plan_service_proxy.rb +43 -0
- data/lib/roby/transaction/proxying.rb +120 -0
- data/lib/roby/transaction/task_event_generator_proxy.rb +19 -0
- data/lib/roby/transaction/task_proxy.rb +135 -0
- data/lib/roby/until_generator.rb +30 -0
- data/lib/roby/version.rb +5 -0
- data/lib/roby/yard.rb +169 -0
- data/lib/yard-roby.rb +1 -0
- data/manifest.xml +32 -6
- data/roby.gemspec +59 -0
- metadata +788 -587
- data/Manifest.txt +0 -321
- data/NOTES +0 -4
- data/README.txt +0 -166
- data/TODO.txt +0 -146
- data/app/README.txt +0 -24
- data/app/Rakefile +0 -8
- data/app/config/ROBOT.rb +0 -5
- data/app/config/init.rb +0 -33
- data/app/config/roby.yml +0 -3
- data/app/controllers/ROBOT.rb +0 -2
- data/app/planners/ROBOT/main.rb +0 -6
- data/app/planners/main.rb +0 -5
- data/app/scripts/distributed +0 -3
- data/app/scripts/generate/bookmarks +0 -3
- data/app/scripts/replay +0 -3
- data/app/scripts/results +0 -3
- data/app/scripts/run +0 -3
- data/app/scripts/server +0 -3
- data/app/scripts/shell +0 -3
- data/app/scripts/test +0 -3
- data/app/tasks/.gitattributes +0 -0
- data/app/tasks/ROBOT/.gitattributes +0 -0
- data/bin/roby-shell +0 -25
- data/doc/guide/src/basics/app.page +0 -139
- data/doc/guide/src/basics/index.page +0 -11
- data/doc/guide/src/basics/log_replay/goForward_1.png +0 -0
- data/doc/guide/src/basics/log_replay/goForward_2.png +0 -0
- data/doc/guide/src/basics/log_replay/goForward_3.png +0 -0
- data/doc/guide/src/basics/log_replay/goForward_5.png +0 -0
- data/doc/guide/src/basics/log_replay/plan_repair_4.png +0 -0
- data/doc/guide/src/basics/log_replay/roby_log_main_window.png +0 -0
- data/doc/guide/src/basics/relations_display.page +0 -203
- data/doc/guide/src/basics/shell.page +0 -102
- data/doc/guide/src/default.css +0 -319
- data/doc/guide/src/introduction/index.page +0 -29
- data/doc/guide/src/introduction/publications.page +0 -14
- data/doc/guide/src/relations/dependency.page +0 -89
- data/doc/guide/src/relations/index.page +0 -12
- data/ext/droby/dump.cc +0 -175
- data/ext/droby/extconf.rb +0 -3
- data/ext/graph/algorithm.cc +0 -746
- data/ext/graph/extconf.rb +0 -7
- data/ext/graph/graph.cc +0 -575
- data/ext/graph/graph.hh +0 -183
- data/ext/graph/iterator_sequence.hh +0 -102
- data/ext/graph/undirected_dfs.hh +0 -226
- data/ext/graph/undirected_graph.hh +0 -421
- data/lib/roby/app/scripts/generate/bookmarks.rb +0 -162
- data/lib/roby/app/scripts/replay.rb +0 -31
- data/lib/roby/app/scripts/server.rb +0 -18
- data/lib/roby/basic_object.rb +0 -151
- data/lib/roby/config.rb +0 -14
- data/lib/roby/distributed.rb +0 -36
- data/lib/roby/distributed/base.rb +0 -448
- data/lib/roby/distributed/communication.rb +0 -875
- data/lib/roby/distributed/connection_space.rb +0 -616
- data/lib/roby/distributed/distributed_object.rb +0 -206
- data/lib/roby/distributed/drb.rb +0 -62
- data/lib/roby/distributed/notifications.rb +0 -531
- data/lib/roby/distributed/peer.rb +0 -555
- data/lib/roby/distributed/protocol.rb +0 -529
- data/lib/roby/distributed/proxy.rb +0 -343
- data/lib/roby/distributed/subscription.rb +0 -311
- data/lib/roby/distributed/transaction.rb +0 -498
- data/lib/roby/external_process_task.rb +0 -225
- data/lib/roby/graph.rb +0 -160
- data/lib/roby/log.rb +0 -3
- data/lib/roby/log/chronicle.rb +0 -303
- data/lib/roby/log/console.rb +0 -74
- data/lib/roby/log/data_stream.rb +0 -275
- data/lib/roby/log/dot.rb +0 -279
- data/lib/roby/log/event_stream.rb +0 -161
- data/lib/roby/log/file.rb +0 -396
- data/lib/roby/log/gui/basic_display.ui +0 -83
- data/lib/roby/log/gui/basic_display_ui.rb +0 -89
- data/lib/roby/log/gui/chronicle.rb +0 -26
- data/lib/roby/log/gui/chronicle_view.rb +0 -40
- data/lib/roby/log/gui/chronicle_view.ui +0 -70
- data/lib/roby/log/gui/chronicle_view_ui.rb +0 -90
- data/lib/roby/log/gui/data_displays.rb +0 -171
- data/lib/roby/log/gui/data_displays.ui +0 -155
- data/lib/roby/log/gui/data_displays_ui.rb +0 -146
- data/lib/roby/log/gui/notifications.rb +0 -26
- data/lib/roby/log/gui/relations.rb +0 -269
- data/lib/roby/log/gui/relations.ui +0 -123
- data/lib/roby/log/gui/relations_ui.rb +0 -120
- data/lib/roby/log/gui/relations_view.rb +0 -185
- data/lib/roby/log/gui/relations_view.ui +0 -149
- data/lib/roby/log/gui/relations_view_ui.rb +0 -144
- data/lib/roby/log/gui/replay.rb +0 -366
- data/lib/roby/log/gui/replay_controls.rb +0 -206
- data/lib/roby/log/gui/replay_controls.ui +0 -282
- data/lib/roby/log/gui/replay_controls_ui.rb +0 -249
- data/lib/roby/log/gui/runtime.rb +0 -130
- data/lib/roby/log/hooks.rb +0 -186
- data/lib/roby/log/logger.rb +0 -203
- data/lib/roby/log/notifications.rb +0 -244
- data/lib/roby/log/plan_rebuilder.rb +0 -468
- data/lib/roby/log/relations.rb +0 -1084
- data/lib/roby/log/server.rb +0 -547
- data/lib/roby/log/sqlite.rb +0 -47
- data/lib/roby/log/timings.rb +0 -233
- data/lib/roby/plan-object.rb +0 -371
- data/lib/roby/planning.rb +0 -13
- data/lib/roby/planning/loops.rb +0 -309
- data/lib/roby/planning/model.rb +0 -1012
- data/lib/roby/planning/task.rb +0 -180
- data/lib/roby/query.rb +0 -655
- data/lib/roby/relations/conflicts.rb +0 -67
- data/lib/roby/relations/dependency.rb +0 -358
- data/lib/roby/relations/ensured.rb +0 -19
- data/lib/roby/relations/error_handling.rb +0 -22
- data/lib/roby/relations/events.rb +0 -7
- data/lib/roby/relations/executed_by.rb +0 -208
- data/lib/roby/relations/influence.rb +0 -10
- data/lib/roby/relations/planned_by.rb +0 -63
- data/lib/roby/state/information.rb +0 -55
- data/lib/roby/state/state.rb +0 -367
- data/lib/roby/task-operations.rb +0 -186
- data/lib/roby/task_index.rb +0 -80
- data/lib/roby/test/distributed.rb +0 -230
- data/lib/roby/test/tasks/simple_task.rb +0 -23
- data/lib/roby/transactions.rb +0 -507
- data/lib/roby/transactions/proxy.rb +0 -325
- data/plugins/fault_injection/History.txt +0 -4
- data/plugins/fault_injection/README.txt +0 -34
- data/plugins/fault_injection/Rakefile +0 -12
- data/plugins/fault_injection/TODO.txt +0 -0
- data/plugins/fault_injection/app.rb +0 -52
- data/plugins/fault_injection/fault_injection.rb +0 -89
- data/plugins/fault_injection/test/test_fault_injection.rb +0 -78
- data/plugins/subsystems/README.txt +0 -37
- data/plugins/subsystems/Rakefile +0 -13
- data/plugins/subsystems/app.rb +0 -182
- data/plugins/subsystems/test/app/README +0 -24
- data/plugins/subsystems/test/app/Rakefile +0 -8
- data/plugins/subsystems/test/app/config/app.yml +0 -71
- data/plugins/subsystems/test/app/config/init.rb +0 -12
- data/plugins/subsystems/test/app/config/roby.yml +0 -3
- data/plugins/subsystems/test/app/planners/main.rb +0 -20
- data/plugins/subsystems/test/app/scripts/distributed +0 -3
- data/plugins/subsystems/test/app/scripts/replay +0 -3
- data/plugins/subsystems/test/app/scripts/results +0 -3
- data/plugins/subsystems/test/app/scripts/run +0 -3
- data/plugins/subsystems/test/app/scripts/server +0 -3
- data/plugins/subsystems/test/app/scripts/shell +0 -3
- data/plugins/subsystems/test/app/scripts/test +0 -3
- data/plugins/subsystems/test/app/tasks/services.rb +0 -15
- data/plugins/subsystems/test/test_subsystems.rb +0 -78
- data/test/distributed/test_communication.rb +0 -195
- data/test/distributed/test_connection.rb +0 -284
- data/test/distributed/test_execution.rb +0 -378
- data/test/distributed/test_mixed_plan.rb +0 -341
- data/test/distributed/test_plan_notifications.rb +0 -238
- data/test/distributed/test_protocol.rb +0 -525
- data/test/distributed/test_query.rb +0 -106
- data/test/distributed/test_remote_plan.rb +0 -491
- data/test/distributed/test_transaction.rb +0 -466
- data/test/mockups/external_process +0 -28
- data/test/mockups/tasks.rb +0 -27
- data/test/planning/test_loops.rb +0 -432
- data/test/planning/test_model.rb +0 -427
- data/test/planning/test_task.rb +0 -126
- data/test/relations/test_conflicts.rb +0 -42
- data/test/relations/test_dependency.rb +0 -324
- data/test/relations/test_ensured.rb +0 -38
- data/test/relations/test_executed_by.rb +0 -224
- data/test/relations/test_planned_by.rb +0 -56
- data/test/suite_core.rb +0 -29
- data/test/suite_distributed.rb +0 -10
- data/test/suite_planning.rb +0 -4
- data/test/suite_relations.rb +0 -8
- data/test/tasks/test_external_process.rb +0 -126
- data/test/tasks/test_thread_task.rb +0 -70
- data/test/test_bgl.rb +0 -528
- data/test/test_event.rb +0 -969
- data/test/test_exceptions.rb +0 -591
- data/test/test_execution_engine.rb +0 -987
- data/test/test_gui.rb +0 -20
- data/test/test_interface.rb +0 -43
- data/test/test_log.rb +0 -125
- data/test/test_log_server.rb +0 -133
- data/test/test_plan.rb +0 -418
- data/test/test_query.rb +0 -424
- data/test/test_relations.rb +0 -260
- data/test/test_state.rb +0 -432
- data/test/test_support.rb +0 -16
- data/test/test_task.rb +0 -1181
- data/test/test_testcase.rb +0 -138
- data/test/test_transactions.rb +0 -610
- 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
|
+
|
data/lib/roby/plan.rb
CHANGED
|
@@ -1,946 +1,1833 @@
|
|
|
1
1
|
module Roby
|
|
2
2
|
# A plan object manages a collection of tasks and events.
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
# The
|
|
51
|
-
#
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
#
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
@
|
|
108
|
-
|
|
109
|
-
@
|
|
110
|
-
@
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
#
|
|
124
|
-
|
|
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
|
-
#
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
1099
|
+
seeds
|
|
171
1100
|
end
|
|
172
1101
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
|
|
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
|
-
|
|
262
|
-
|
|
263
|
-
|
|
1157
|
+
def follow_edge?(u, v)
|
|
1158
|
+
!task_events.include?(v)
|
|
1159
|
+
end
|
|
264
1160
|
end
|
|
265
1161
|
|
|
266
|
-
|
|
267
|
-
#
|
|
268
|
-
# the
|
|
1162
|
+
# @api private
|
|
1163
|
+
#
|
|
1164
|
+
# Compute the set of events that are "useful" to the plan.
|
|
269
1165
|
#
|
|
270
|
-
#
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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
|
-
#
|
|
282
|
-
|
|
283
|
-
|
|
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
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
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
|
-
#
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
-
#
|
|
340
|
-
|
|
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
|
-
#
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
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
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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
|
-
#
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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
|
-
|
|
363
|
-
|
|
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
|
-
|
|
366
|
-
|
|
367
|
-
|
|
1356
|
+
for ev in task.bound_events.each_value
|
|
1357
|
+
finalized_event(ev)
|
|
1358
|
+
end
|
|
1359
|
+
finalized_task(task)
|
|
368
1360
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
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
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
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
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
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
|
-
|
|
497
|
-
if
|
|
498
|
-
|
|
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
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
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
|
-
|
|
853
|
-
|
|
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
|
-
|
|
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
|
-
|
|
866
|
-
|
|
1501
|
+
new_task = task.create_fresh_copy
|
|
1502
|
+
replace_task(task, new_task)
|
|
867
1503
|
new_task
|
|
868
1504
|
end
|
|
869
1505
|
|
|
870
|
-
|
|
1506
|
+
# Creates a new planning pattern replacing the given task and its
|
|
1507
|
+
# current planner
|
|
871
1508
|
#
|
|
872
|
-
#
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
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.
|
|
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.
|
|
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
|
-
#
|
|
900
|
-
#
|
|
901
|
-
#
|
|
902
|
-
#
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
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
|
-
|
|
916
|
-
|
|
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
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
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
|
-
|
|
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
|
-
|
|
930
|
-
|
|
931
|
-
def
|
|
932
|
-
|
|
933
|
-
|
|
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
|
-
|
|
938
|
-
#
|
|
939
|
-
|
|
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
|
|