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,43 @@
|
|
|
1
|
+
module Roby
|
|
2
|
+
class Task
|
|
3
|
+
def self.state
|
|
4
|
+
if !@state
|
|
5
|
+
if superclass.respond_to?(:state)
|
|
6
|
+
supermodel = superclass.state
|
|
7
|
+
end
|
|
8
|
+
@state = StateModel.new(supermodel)
|
|
9
|
+
end
|
|
10
|
+
@state
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def state
|
|
14
|
+
@state ||= StateSpace.new(self.model.state)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def resolve_state_sources
|
|
18
|
+
model.state.resolve_data_sources(self, state)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.goal
|
|
22
|
+
if !@goal
|
|
23
|
+
if superclass.respond_to?(:goal)
|
|
24
|
+
supermodel = superclass.goal
|
|
25
|
+
end
|
|
26
|
+
@goal = GoalModel.new(self.state, supermodel)
|
|
27
|
+
end
|
|
28
|
+
@goal
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def goal
|
|
32
|
+
@goal ||= GoalSpace.new(self.model.goal)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def resolve_goals
|
|
36
|
+
if !fully_instanciated?
|
|
37
|
+
raise ArgumentError, "cannot resolve goals on a task that is not fully instanciated"
|
|
38
|
+
end
|
|
39
|
+
self.model.goal.resolve_goals(self, self.goal)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
data/lib/roby/support.rb
CHANGED
|
@@ -1,75 +1,95 @@
|
|
|
1
|
-
require '
|
|
1
|
+
require 'thread'
|
|
2
2
|
require 'facets/string/camelcase'
|
|
3
3
|
require 'facets/string/snakecase'
|
|
4
|
+
require 'facets/string/modulize'
|
|
4
5
|
require 'facets/kernel/constant'
|
|
5
|
-
require 'utilrb/enumerable'
|
|
6
6
|
require 'utilrb/time/to_hms'
|
|
7
|
-
require 'utilrb/module/
|
|
7
|
+
require 'utilrb/module/define_or_reuse'
|
|
8
8
|
require 'utilrb/logger'
|
|
9
|
-
require 'utilrb/
|
|
10
|
-
require 'utilrb/hash/to_sym_keys'
|
|
11
|
-
require 'utilrb/array/to_s'
|
|
12
|
-
require 'utilrb/hash/to_s'
|
|
13
|
-
require 'utilrb/set/to_s'
|
|
9
|
+
require 'utilrb/marshal/load_with_missing_constants'
|
|
14
10
|
|
|
15
11
|
class IO
|
|
16
12
|
def ask(question, default, output_io = STDOUT)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
13
|
+
output_io.print question
|
|
14
|
+
output_io.flush
|
|
15
|
+
loop do
|
|
16
|
+
answer = readline.chomp.downcase
|
|
17
|
+
if answer.empty?
|
|
18
|
+
return default
|
|
19
|
+
elsif answer == 'y'
|
|
20
|
+
return true
|
|
21
|
+
elsif answer == 'n'
|
|
22
|
+
return false
|
|
23
|
+
else
|
|
24
|
+
output_io.print "\nInvalid answer, try again: "
|
|
25
|
+
output_io.flush
|
|
26
|
+
end
|
|
27
|
+
end
|
|
32
28
|
end
|
|
33
29
|
end
|
|
34
30
|
|
|
35
|
-
|
|
36
|
-
def
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
end
|
|
40
|
-
true
|
|
31
|
+
class Module
|
|
32
|
+
def each_fullfilled_model
|
|
33
|
+
return enum_for(__method__) if !block_given?
|
|
34
|
+
yield self
|
|
41
35
|
end
|
|
42
36
|
end
|
|
43
37
|
|
|
44
|
-
class
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
38
|
+
class Object
|
|
39
|
+
def inspect
|
|
40
|
+
guard = (Thread.current[:ROBY_SUPPORT_INSPECT_RECURSION_GUARD] ||= Hash.new)
|
|
41
|
+
guard.compare_by_identity
|
|
42
|
+
if guard.has_key?(self)
|
|
43
|
+
return "..."
|
|
44
|
+
else
|
|
45
|
+
begin
|
|
46
|
+
guard[self] = self
|
|
47
|
+
to_s
|
|
48
|
+
ensure
|
|
49
|
+
guard.delete(self)
|
|
50
|
+
end
|
|
57
51
|
end
|
|
58
52
|
end
|
|
59
53
|
end
|
|
60
54
|
|
|
55
|
+
class Set
|
|
56
|
+
def inspect
|
|
57
|
+
to_s
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
if !method_defined?(:intersect?)
|
|
61
|
+
def intersect?(set)
|
|
62
|
+
set.is_a?(Set) or raise ArgumentError, "value must be a set"
|
|
63
|
+
if size < set.size
|
|
64
|
+
any? { |o| set.include?(o) }
|
|
65
|
+
else
|
|
66
|
+
set.any? { |o| include?(o) }
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
module Enumerable
|
|
73
|
+
def empty?
|
|
74
|
+
for i in self
|
|
75
|
+
return false
|
|
76
|
+
end
|
|
77
|
+
true
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
61
81
|
class Thread
|
|
62
82
|
def send_to(object, name, *args, &prc)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
83
|
+
if Thread.current == self
|
|
84
|
+
object.send(name, *args, &prc)
|
|
85
|
+
else
|
|
86
|
+
@msg_queue ||= Queue.new
|
|
87
|
+
@msg_queue << [ object, name, args, prc ]
|
|
88
|
+
end
|
|
69
89
|
end
|
|
70
90
|
def process_events
|
|
71
91
|
@msg_queue ||= Queue.new
|
|
72
|
-
|
|
92
|
+
loop do
|
|
73
93
|
object, name, args, block = *@msg_queue.deq(true)
|
|
74
94
|
object.send(name, *args, &block)
|
|
75
95
|
end
|
|
@@ -78,116 +98,36 @@ def process_events
|
|
|
78
98
|
end
|
|
79
99
|
|
|
80
100
|
module Roby
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
extend Logger::Hierarchy
|
|
87
|
-
extend Logger::Forward
|
|
88
|
-
|
|
89
|
-
class Pool < Queue
|
|
90
|
-
def initialize(klass)
|
|
91
|
-
@klass = klass
|
|
92
|
-
super()
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
def pop
|
|
96
|
-
value = super(true) rescue nil
|
|
97
|
-
value || @klass.new
|
|
98
|
-
end
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
@mutexes = Pool.new(Mutex)
|
|
102
|
-
@condition_variables = Pool.new(ConditionVariable)
|
|
103
|
-
class << self
|
|
104
|
-
# A pool of mutexes (as a Queue)
|
|
105
|
-
attr_reader :mutexes
|
|
106
|
-
# A pool of condition variables (as a Queue)
|
|
107
|
-
attr_reader :condition_variables
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
# call-seq:
|
|
111
|
-
# condition_variable => cv
|
|
112
|
-
# condition_variable(true) => cv, mutex
|
|
113
|
-
# condition_variable { |cv| ... } => value returned by the block
|
|
114
|
-
# condition_variable(true) { |cv, mutex| ... } => value returned by the block
|
|
115
|
-
#
|
|
116
|
-
# Get a condition variable object from the Roby.condition_variables
|
|
117
|
-
# pool and, if mutex is not true, a Mutex object
|
|
118
|
-
#
|
|
119
|
-
# If a block is given, the two objects are yield and returned into the
|
|
120
|
-
# pool after the block has returned. In that case, the method returns
|
|
121
|
-
# the value returned by the block
|
|
122
|
-
def self.condition_variable(mutex = false)
|
|
123
|
-
cv = condition_variables.pop
|
|
124
|
-
|
|
125
|
-
if block_given?
|
|
126
|
-
begin
|
|
127
|
-
if mutex
|
|
128
|
-
mt = mutexes.pop
|
|
129
|
-
yield(cv, mt)
|
|
130
|
-
else
|
|
131
|
-
yield(cv)
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
ensure
|
|
135
|
-
return_condition_variable(cv, mt)
|
|
136
|
-
end
|
|
101
|
+
def self.format_time(time, format = 'hms')
|
|
102
|
+
if format == 'sec'
|
|
103
|
+
time.to_f.to_s
|
|
104
|
+
elsif format == 'hms'
|
|
105
|
+
"#{time.strftime('%H:%M:%S.%3N')}"
|
|
137
106
|
else
|
|
138
|
-
|
|
139
|
-
return cv, mutexes.pop
|
|
140
|
-
else
|
|
141
|
-
return cv
|
|
142
|
-
end
|
|
143
|
-
end
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
# Returns a ConditionVariable and optionally a Mutex into the
|
|
147
|
-
# Roby.condition_variables and Roby.mutexes pools
|
|
148
|
-
def self.return_condition_variable(cv, mutex = nil)
|
|
149
|
-
condition_variables.push cv
|
|
150
|
-
if mutex
|
|
151
|
-
mutexes.push mutex
|
|
107
|
+
"#{time.strftime(format)}"
|
|
152
108
|
end
|
|
153
|
-
nil
|
|
154
109
|
end
|
|
155
110
|
|
|
156
|
-
|
|
157
|
-
class << self
|
|
158
|
-
# This Mutex object is locked during the event propagation loop, and
|
|
159
|
-
# unlock while this loop is sleeping. It is used to wait for the
|
|
160
|
-
# availability of the main plan.
|
|
161
|
-
attr_reader :global_lock
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
def self.taken_global_lock?; Thread.current[:global_lock_taken] end
|
|
165
|
-
|
|
166
|
-
# Implements a recursive behaviour on Roby.mutex
|
|
167
|
-
def self.synchronize
|
|
168
|
-
if Thread.current[:global_lock_taken]
|
|
169
|
-
yield
|
|
170
|
-
else
|
|
171
|
-
global_lock.lock
|
|
172
|
-
begin
|
|
173
|
-
Thread.current[:global_lock_taken] = true
|
|
174
|
-
yield
|
|
175
|
-
ensure
|
|
176
|
-
Thread.current[:global_lock_taken] = false
|
|
177
|
-
global_lock.unlock
|
|
178
|
-
end
|
|
179
|
-
end
|
|
180
|
-
end
|
|
111
|
+
extend Logger::Root('Roby', Logger::WARN) { |severity, time, progname, msg| "#{Roby.format_time(time)} (#{progname}) #{msg}\n" }
|
|
181
112
|
|
|
182
113
|
class << self
|
|
183
114
|
attr_accessor :enable_deprecation_warnings
|
|
115
|
+
attr_accessor :deprecation_warnings_are_errors
|
|
184
116
|
end
|
|
185
117
|
@enable_deprecation_warnings = true
|
|
118
|
+
@deprecation_warnings_are_errors = (ENV['ROBY_ALL_DEPRECATIONS_ARE_ERRORS'] == '1')
|
|
186
119
|
|
|
187
|
-
def self.warn_deprecated(msg)
|
|
188
|
-
if
|
|
189
|
-
|
|
120
|
+
def self.warn_deprecated(msg, caller_depth = 1)
|
|
121
|
+
if deprecation_warnings_are_errors
|
|
122
|
+
error_deprecated(msg, caller_depth)
|
|
123
|
+
elsif enable_deprecation_warnings
|
|
124
|
+
Roby.warn "Deprecation Warning: #{msg} at #{caller[1, caller_depth].join("\n")}"
|
|
190
125
|
end
|
|
191
126
|
end
|
|
127
|
+
|
|
128
|
+
def self.error_deprecated(msg, caller_depth = 1)
|
|
129
|
+
Roby.fatal "Deprecation Error: #{msg} at #{caller[1, caller_depth].join("\n")}"
|
|
130
|
+
raise NotImplementedError
|
|
131
|
+
end
|
|
192
132
|
end
|
|
193
133
|
|
data/lib/roby/task.rb
CHANGED
|
@@ -1,390 +1,5 @@
|
|
|
1
1
|
module Roby
|
|
2
|
-
|
|
3
|
-
module ClassExtension
|
|
4
|
-
# Returns the list of static arguments required by this task model
|
|
5
|
-
def arguments(*new_arguments)
|
|
6
|
-
new_arguments.each do |arg_name|
|
|
7
|
-
arg_name = arg_name.to_sym
|
|
8
|
-
argument_set << arg_name.to_sym
|
|
9
|
-
unless method_defined?(arg_name)
|
|
10
|
-
define_method(arg_name) { arguments[arg_name] }
|
|
11
|
-
define_method("#{arg_name}=") { |value| arguments[arg_name] = value }
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
@argument_enumerator ||= enum_for(:each_argument_set)
|
|
16
|
-
end
|
|
17
|
-
# Declares a set of arguments required by this task model
|
|
18
|
-
def argument(*args); arguments(*args) end
|
|
19
|
-
# The part of +arguments+ that is meaningful for this task model
|
|
20
|
-
def meaningful_arguments(arguments)
|
|
21
|
-
self_arguments = self.arguments.to_set
|
|
22
|
-
arguments.to_hash.delete_if do |key, _|
|
|
23
|
-
!self_arguments.include?(key)
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
# Checks if this model fullfills everything in +models+
|
|
28
|
-
def fullfills?(models)
|
|
29
|
-
if !models.respond_to?(:each)
|
|
30
|
-
models = [models]
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
for tag in models
|
|
34
|
-
if !has_ancestor?(tag)
|
|
35
|
-
return false
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
true
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
include TaskModelTag::ClassExtension
|
|
42
|
-
|
|
43
|
-
def initialize(&block)
|
|
44
|
-
super do
|
|
45
|
-
inherited_enumerable("argument_set", "argument_set") { ValueSet.new }
|
|
46
|
-
unless const_defined? :ClassExtension
|
|
47
|
-
const_set(:ClassExtension, Module.new)
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
self::ClassExtension.include TaskModelTag::ClassExtension
|
|
51
|
-
end
|
|
52
|
-
class_eval(&block) if block_given?
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
def clear_model
|
|
56
|
-
@argument_set.clear if @argument_set
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
# Base class for task events
|
|
61
|
-
# When events are emitted, then the created object is
|
|
62
|
-
# an instance of a class derived from this one
|
|
63
|
-
class TaskEvent < Event
|
|
64
|
-
# The task which fired this event
|
|
65
|
-
attr_reader :task
|
|
66
|
-
|
|
67
|
-
def model; self.class end
|
|
68
|
-
|
|
69
|
-
def initialize(task, generator, propagation_id, context, time = Time.now)
|
|
70
|
-
@task = task
|
|
71
|
-
@terminal_flag = generator.terminal_flag
|
|
72
|
-
super(generator, propagation_id, context, time)
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
# Returns the set of events from the task that are the cause of this
|
|
76
|
-
# event
|
|
77
|
-
def task_sources
|
|
78
|
-
result = ValueSet.new
|
|
79
|
-
event_sources = sources
|
|
80
|
-
for ev in event_sources
|
|
81
|
-
gen = ev.generator
|
|
82
|
-
if gen.respond_to?(:task) && gen.task == task
|
|
83
|
-
result.merge ev.task_sources
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
if result.empty?
|
|
87
|
-
result << self
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
result
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def to_s
|
|
94
|
-
"#{generator.to_s}@#{propagation_id} [#{time.to_hms}]: #{context}"
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
def pretty_print(pp)
|
|
98
|
-
pp.text "at [#{time.to_hms}/#{propagation_id}] in the "
|
|
99
|
-
generator.pretty_print(pp)
|
|
100
|
-
pp.breakable
|
|
101
|
-
pp.group(2) do
|
|
102
|
-
pp.seplist(context || []) { |v| v.pretty_print pp }
|
|
103
|
-
end
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
# If the event model defines a controlable event
|
|
107
|
-
# By default, an event is controlable if the model
|
|
108
|
-
# responds to #call
|
|
109
|
-
def self.controlable?; respond_to?(:call) end
|
|
110
|
-
# If the event is controlable
|
|
111
|
-
def controlable?; model.controlable? end
|
|
112
|
-
class << self
|
|
113
|
-
# Called by Task.update_terminal_flag to update the flag
|
|
114
|
-
attr_writer :terminal
|
|
115
|
-
end
|
|
116
|
-
# If the event model defines a terminal event
|
|
117
|
-
def self.terminal?; @terminal end
|
|
118
|
-
# If this event is terminal
|
|
119
|
-
def success?; @terminal_flag == :success end
|
|
120
|
-
# If this event is terminal
|
|
121
|
-
def failure?; @terminal_flag == :failure end
|
|
122
|
-
# If this event is terminal
|
|
123
|
-
def terminal?; @terminal_flag end
|
|
124
|
-
# The event symbol
|
|
125
|
-
def self.symbol; @symbol end
|
|
126
|
-
# The event symbol
|
|
127
|
-
def symbol; model.symbol end
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
# A task event model bound to a particular task instance
|
|
131
|
-
# The Task/TaskEvent/TaskEventGenerator relationship is
|
|
132
|
-
# comparable to the Class/UnboundMethod/Method one:
|
|
133
|
-
# * a Task object is a model for a task, a Class in a model for an object
|
|
134
|
-
# * a TaskEvent object is a model for an event instance (the instance being unspecified),
|
|
135
|
-
# an UnboundMethod is a model for an instance method
|
|
136
|
-
# * a TaskEventGenerator object represents a particular event model
|
|
137
|
-
# *bound* to a particular task instance, a Method object represents a particular method
|
|
138
|
-
# bound to a particular object
|
|
139
|
-
class TaskEventGenerator < EventGenerator
|
|
140
|
-
# The task we are part of
|
|
141
|
-
attr_reader :task
|
|
142
|
-
# The event symbol (its name as a Symbol object)
|
|
143
|
-
attr_reader :symbol
|
|
144
|
-
# The event class
|
|
145
|
-
attr_reader :event_model
|
|
146
|
-
def initialize(task, model)
|
|
147
|
-
@task, @event_model = task, model
|
|
148
|
-
@symbol = model.symbol
|
|
149
|
-
super(model.respond_to?(:call))
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
def default_command(context)
|
|
153
|
-
event_model.call(task, context)
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
# See PlanObject::child_plan_object.
|
|
157
|
-
child_plan_object :task
|
|
158
|
-
|
|
159
|
-
# The event plan. It is the same as task.plan and is actually updated
|
|
160
|
-
# by task.plan=. It is redefined here for performance reasons.
|
|
161
|
-
attr_accessor :plan
|
|
162
|
-
|
|
163
|
-
# Check that the event can be emitted
|
|
164
|
-
def emitting(context)
|
|
165
|
-
task.emitting_event(self, context)
|
|
166
|
-
super if defined? super
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
def fire(event)
|
|
170
|
-
task.fire_event(event)
|
|
171
|
-
super if defined? super
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
def emit_failed(*reason)
|
|
175
|
-
if symbol == :start
|
|
176
|
-
task.failed_to_start = true
|
|
177
|
-
task.plan.task_index.set_state(task, :failed?)
|
|
178
|
-
end
|
|
179
|
-
super
|
|
180
|
-
end
|
|
181
|
-
|
|
182
|
-
# See EventGenerator#calling
|
|
183
|
-
#
|
|
184
|
-
# In TaskEventGenerator, this hook checks that the task is running
|
|
185
|
-
def calling(context)
|
|
186
|
-
super if defined? super
|
|
187
|
-
if task.finished? && !terminal?
|
|
188
|
-
raise CommandFailed.new(nil, self),
|
|
189
|
-
"#{symbol}!(#{context}) called by #{plan.engine.propagation_sources.to_a} but the task has finished. Task has been terminated by #{task.event(:stop).history.first.sources}."
|
|
190
|
-
elsif task.pending? && symbol != :start
|
|
191
|
-
raise CommandFailed.new(nil, self),
|
|
192
|
-
"#{symbol}!(#{context}) called by #{plan.engine.propagation_sources.to_a} but the task has never been started"
|
|
193
|
-
elsif task.running? && symbol == :start
|
|
194
|
-
raise CommandFailed.new(nil, self),
|
|
195
|
-
"#{symbol}!(#{context}) called by #{plan.engine.propagation_sources.to_a} but the task is already running. Task has been started by #{task.event(:start).history.first.sources}."
|
|
196
|
-
end
|
|
197
|
-
end
|
|
198
|
-
|
|
199
|
-
# See EventGenerator#fired
|
|
200
|
-
#
|
|
201
|
-
# In TaskEventGenerator, this hook calls the unreachable handlers added
|
|
202
|
-
# by EventGenerator#if_unreachable when the task has finished, not
|
|
203
|
-
# before
|
|
204
|
-
def fired(event)
|
|
205
|
-
super if defined? super
|
|
206
|
-
|
|
207
|
-
if symbol == :stop
|
|
208
|
-
task.each_event { |ev| ev.unreachable!(task.terminal_event) }
|
|
209
|
-
end
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
def related_tasks(result = nil)
|
|
213
|
-
tasks = super
|
|
214
|
-
tasks.delete(task)
|
|
215
|
-
tasks
|
|
216
|
-
end
|
|
217
|
-
|
|
218
|
-
def each_handler
|
|
219
|
-
super
|
|
220
|
-
|
|
221
|
-
if self_owned?
|
|
222
|
-
task.each_handler(event_model.symbol) { |o| yield(o) }
|
|
223
|
-
end
|
|
224
|
-
end
|
|
225
|
-
def each_precondition
|
|
226
|
-
super
|
|
227
|
-
task.each_precondition(event_model.symbol) { |o| yield(o) }
|
|
228
|
-
end
|
|
229
|
-
|
|
230
|
-
def controlable?; event_model.controlable? end
|
|
231
|
-
attr_accessor :terminal_flag
|
|
232
|
-
def terminal?; !!@terminal_flag end
|
|
233
|
-
def success?; @terminal_flag == :success end
|
|
234
|
-
def failure?; @terminal_flag == :failure end
|
|
235
|
-
def added_child_object(child, relations, info)
|
|
236
|
-
super if defined? super
|
|
237
|
-
|
|
238
|
-
if relations.include?(EventStructure::CausalLink) &&
|
|
239
|
-
child.respond_to?(:task) && child.task == task &&
|
|
240
|
-
child.terminal_flag != terminal_flag
|
|
241
|
-
|
|
242
|
-
task.update_terminal_flag
|
|
243
|
-
end
|
|
244
|
-
end
|
|
245
|
-
def removed_child_object(child, relations)
|
|
246
|
-
super if defined? super
|
|
247
|
-
|
|
248
|
-
if relations.include?(EventStructure::CausalLink) &&
|
|
249
|
-
child.respond_to?(:task) && child.task == task &&
|
|
250
|
-
terminal_flag
|
|
251
|
-
|
|
252
|
-
task.update_terminal_flag
|
|
253
|
-
end
|
|
254
|
-
end
|
|
255
|
-
def new(context); event_model.new(task, self, plan.engine.propagation_id, context) end
|
|
256
|
-
|
|
257
|
-
def to_s
|
|
258
|
-
"#{task}/#{symbol}"
|
|
259
|
-
end
|
|
260
|
-
def inspect
|
|
261
|
-
"#{task.inspect}/#{symbol}: #{history.to_s}"
|
|
262
|
-
end
|
|
263
|
-
def pretty_print(pp)
|
|
264
|
-
pp.text "#{symbol} event of #{task.class}:0x#{task.address.to_s(16)}"
|
|
265
|
-
end
|
|
266
|
-
|
|
267
|
-
def achieve_with(obj)
|
|
268
|
-
child_task, child_event = case obj
|
|
269
|
-
when Roby::Task: [obj, obj.event(:success)]
|
|
270
|
-
when Roby::TaskEventGenerator: [obj.task, obj]
|
|
271
|
-
end
|
|
272
|
-
|
|
273
|
-
if child_task
|
|
274
|
-
unless task.depends_on?(child_task, false)
|
|
275
|
-
task.depends_on child_task,
|
|
276
|
-
:success => [child_event.symbol],
|
|
277
|
-
:remove_when_done => true
|
|
278
|
-
end
|
|
279
|
-
super(child_event)
|
|
280
|
-
else
|
|
281
|
-
super(obj)
|
|
282
|
-
end
|
|
283
|
-
end
|
|
284
|
-
|
|
285
|
-
# Refines exceptions that may be thrown by #call_without_propagation
|
|
286
|
-
def call_without_propagation(context)
|
|
287
|
-
super
|
|
288
|
-
rescue EventNotExecutable => e
|
|
289
|
-
refine_exception(e)
|
|
290
|
-
end
|
|
291
|
-
|
|
292
|
-
# Checks that the event can be called. Raises various exception
|
|
293
|
-
# when it is not the case.
|
|
294
|
-
def check_call_validity
|
|
295
|
-
super
|
|
296
|
-
rescue EventNotExecutable => e
|
|
297
|
-
refine_exception(e)
|
|
298
|
-
end
|
|
299
|
-
|
|
300
|
-
# Checks that the event can be emitted. Raises various exception
|
|
301
|
-
# when it is not the case.
|
|
302
|
-
def check_emission_validity
|
|
303
|
-
super
|
|
304
|
-
rescue EventNotExecutable => e
|
|
305
|
-
refine_exception(e)
|
|
306
|
-
end
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
def refine_exception (e)
|
|
310
|
-
if task.partially_instanciated?
|
|
311
|
-
raise EventNotExecutable.new(self), "#{symbol}! called on #{task} which is partially instanciated\n" +
|
|
312
|
-
"The following arguments were not set: \n" +
|
|
313
|
-
task.list_unset_arguments.map {|n| "\t#{n}"}.join("\n")+"\n"
|
|
314
|
-
#
|
|
315
|
-
elsif !plan
|
|
316
|
-
raise EventNotExecutable.new(self), "#{symbol}! called on #{task} but the task is in no plan"
|
|
317
|
-
elsif !plan.executable?
|
|
318
|
-
raise EventNotExecutable.new(self), "#{symbol}! called on #{task} but the plan is not executable"
|
|
319
|
-
elsif task.abstract?
|
|
320
|
-
raise EventNotExecutable.new(self), "#{symbol}! called on #{task} but the task is abstract"
|
|
321
|
-
else
|
|
322
|
-
raise EventNotExecutable.new(self), "#{symbol}! called on #{task} which is not executable: #{e.message}"
|
|
323
|
-
end
|
|
324
|
-
end
|
|
325
|
-
|
|
326
|
-
end
|
|
327
|
-
|
|
328
|
-
# Class that handles task arguments. They are handled specially as the
|
|
329
|
-
# arguments cannot be overwritten and can not be changed by a task that is
|
|
330
|
-
# not owned.
|
|
331
|
-
#
|
|
332
|
-
# Moreover, two hooks #updating and #updated allow to hook into the argument
|
|
333
|
-
# update system.
|
|
334
|
-
class TaskArguments < Hash
|
|
335
|
-
private :delete, :delete_if
|
|
336
|
-
|
|
337
|
-
attr_reader :task
|
|
338
|
-
def initialize(task)
|
|
339
|
-
@task = task
|
|
340
|
-
super()
|
|
341
|
-
end
|
|
342
|
-
|
|
343
|
-
def writable?(key)
|
|
344
|
-
!(has_key?(key) && task.model.arguments.include?(key))
|
|
345
|
-
end
|
|
346
|
-
|
|
347
|
-
def dup; self.to_hash end
|
|
348
|
-
def to_hash
|
|
349
|
-
inject({}) { |h, (k, v)| h[k] = v ; h }
|
|
350
|
-
end
|
|
351
|
-
|
|
352
|
-
alias :update! :[]=
|
|
353
|
-
def []=(key, value)
|
|
354
|
-
key = key.to_sym if key.respond_to?(:to_str)
|
|
355
|
-
if writable?(key)
|
|
356
|
-
if !task.read_write?
|
|
357
|
-
raise OwnershipError, "cannot change the argument set of a task which is not owned #{task} is owned by #{task.owners} and #{task.plan} by #{task.plan.owners}"
|
|
358
|
-
end
|
|
359
|
-
|
|
360
|
-
updating
|
|
361
|
-
super
|
|
362
|
-
updated
|
|
363
|
-
else
|
|
364
|
-
raise ArgumentError, "cannot override task arguments"
|
|
365
|
-
end
|
|
366
|
-
end
|
|
367
|
-
def updating; super if defined? super end
|
|
368
|
-
def updated; super if defined? super end
|
|
369
|
-
|
|
370
|
-
def [](key)
|
|
371
|
-
key = key.to_sym if key.respond_to?(:to_str)
|
|
372
|
-
super(key)
|
|
373
|
-
end
|
|
374
|
-
|
|
375
|
-
alias :do_merge! :merge!
|
|
376
|
-
def merge!(hash)
|
|
377
|
-
super do |key, old, new|
|
|
378
|
-
if old == new then old
|
|
379
|
-
elsif writable?(key) then new
|
|
380
|
-
else
|
|
381
|
-
raise ArgumentError, "cannot override task argument #{key}: trying to replace #{old} by #{new}"
|
|
382
|
-
end
|
|
383
|
-
end
|
|
384
|
-
end
|
|
385
|
-
end
|
|
386
|
-
|
|
387
|
-
# In a plan, Task objects represent the activities of the robot.
|
|
2
|
+
# In a plan, Task objects represent the system's activities.
|
|
388
3
|
#
|
|
389
4
|
# === Task models
|
|
390
5
|
#
|
|
@@ -415,7 +30,7 @@ def merge!(hash)
|
|
|
415
30
|
# end
|
|
416
31
|
#
|
|
417
32
|
# event :other_event do |context|
|
|
418
|
-
#
|
|
33
|
+
# execution_engine.once { emit :other_event }
|
|
419
34
|
# end
|
|
420
35
|
# end
|
|
421
36
|
#
|
|
@@ -425,7 +40,7 @@ def merge!(hash)
|
|
|
425
40
|
#
|
|
426
41
|
# === Task relations
|
|
427
42
|
#
|
|
428
|
-
# Task relations are defined in the TaskStructure
|
|
43
|
+
# Task relations are defined in the TaskStructure Relations::Space instance.
|
|
429
44
|
# See TaskStructure documentation for the list of special methods defined
|
|
430
45
|
# by the various graphs, and the TaskStructure namespace for the name and
|
|
431
46
|
# purpose of the various relation graphs themselves.
|
|
@@ -467,713 +82,636 @@ def merge!(hash)
|
|
|
467
82
|
# execution) cannot become non-terminal. Nonetheless, a non-terminal
|
|
468
83
|
# event can become terminal.
|
|
469
84
|
#
|
|
85
|
+
# @!macro InstanceHandlerOptions
|
|
86
|
+
# @option options [:copy,:drop] :on_replace defines the behaviour
|
|
87
|
+
# when this object gets replaced in the plan. If :copy is used,
|
|
88
|
+
# the handler is added to the replacing task and is also kept
|
|
89
|
+
# in the original task. If :drop, it is not copied (but is
|
|
90
|
+
# kept).
|
|
470
91
|
class Task < PlanObject
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
include RootTaskTag
|
|
474
|
-
end
|
|
475
|
-
|
|
476
|
-
# Clears all definitions saved in this model. This is to be used by the
|
|
477
|
-
# reloading code
|
|
478
|
-
def self.clear_model
|
|
479
|
-
class_eval do
|
|
480
|
-
# Remove event models
|
|
481
|
-
events.each_key do |ev_symbol|
|
|
482
|
-
remove_const ev_symbol.to_s.camelcase(true)
|
|
483
|
-
end
|
|
484
|
-
|
|
485
|
-
[@events, @signal_sets, @forwarding_sets, @causal_link_sets,
|
|
486
|
-
@argument_set, @handler_sets, @precondition_sets].each do |set|
|
|
487
|
-
set.clear if set
|
|
488
|
-
end
|
|
489
|
-
end
|
|
490
|
-
end
|
|
491
|
-
|
|
492
|
-
# Declares an attribute set which follows the task models inheritance
|
|
493
|
-
# hierarchy. Define the corresponding enumeration methods as well.
|
|
494
|
-
#
|
|
495
|
-
# For instance,
|
|
496
|
-
# model_attribute_list 'signal'
|
|
497
|
-
#
|
|
498
|
-
# defines the model-level signals, which can be accessed through
|
|
499
|
-
# .each_signal(model)
|
|
500
|
-
# .signals(model)
|
|
501
|
-
# #each_signal(model)
|
|
502
|
-
#
|
|
503
|
-
def self.model_attribute_list(name) # :nodoc:
|
|
504
|
-
inherited_enumerable("#{name}_set", "#{name}_sets", :map => true) { Hash.new { |h, k| h[k] = ValueSet.new } }
|
|
505
|
-
class_eval <<-EOD
|
|
506
|
-
def self.each_#{name}(model)
|
|
507
|
-
for obj in #{name}s(model)
|
|
508
|
-
yield(obj)
|
|
509
|
-
end
|
|
510
|
-
self
|
|
511
|
-
end
|
|
512
|
-
def self.#{name}s(model)
|
|
513
|
-
result = ValueSet.new
|
|
514
|
-
each_#{name}_set(model, false) do |set|
|
|
515
|
-
result.merge set
|
|
516
|
-
end
|
|
517
|
-
result
|
|
518
|
-
end
|
|
519
|
-
def each_#{name}(model); self.model.each_#{name}(model) { |o| yield(o) } end
|
|
520
|
-
EOD
|
|
521
|
-
end
|
|
522
|
-
|
|
523
|
-
##
|
|
524
|
-
# :singleton-method: signals
|
|
525
|
-
# :call-seq:
|
|
526
|
-
# task_model.signals(event_model) => [target_event_models]
|
|
527
|
-
#
|
|
528
|
-
# Returns the set of model-level signal targets for the given event.
|
|
529
|
-
|
|
530
|
-
##
|
|
531
|
-
# :singleton-method: each_signal
|
|
532
|
-
# :call-seq:
|
|
533
|
-
# task_model.each_signal(event_model) do |target_event_model|
|
|
534
|
-
# end
|
|
535
|
-
#
|
|
536
|
-
# Enumerates the set of model-level causal links that are defined for
|
|
537
|
-
# the given event. It enumerates all the ones defined on this model
|
|
538
|
-
# (using Task::signal) and also on its parent classes.
|
|
92
|
+
extend Models::Task
|
|
93
|
+
provides TaskService
|
|
539
94
|
|
|
540
|
-
|
|
541
|
-
# :method: each_signal
|
|
542
|
-
# :call-seq:
|
|
543
|
-
# task.each_signal(event_model) do |target_event_model|
|
|
544
|
-
# end
|
|
95
|
+
# The task arguments
|
|
545
96
|
#
|
|
546
|
-
#
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
##
|
|
554
|
-
# :singleton-method: forwardings
|
|
555
|
-
# :call-seq:
|
|
556
|
-
# task_model.forwardings(event_model) => [target_event_models]
|
|
97
|
+
# @return [TaskArguments]
|
|
98
|
+
attr_reader :arguments
|
|
99
|
+
|
|
100
|
+
# The accumulated history of this task
|
|
557
101
|
#
|
|
558
|
-
#
|
|
559
|
-
|
|
560
|
-
##
|
|
561
|
-
# :singleton-method: each_forwarding
|
|
562
|
-
# :call-seq:
|
|
563
|
-
# task_model.each_forwarding(event_model) do |target_event_model|
|
|
564
|
-
# end
|
|
102
|
+
# This is the list of events that this task ever emitted, sorted by
|
|
103
|
+
# emission time (oldest first)
|
|
565
104
|
#
|
|
566
|
-
#
|
|
567
|
-
|
|
568
|
-
|
|
105
|
+
# @return [Array<Event>]
|
|
106
|
+
attr_reader :history
|
|
107
|
+
|
|
108
|
+
# The part of {#arguments} that is meaningful for this task model. I.e.
|
|
109
|
+
# it returns the set of elements in {#arguments} that are listed in the
|
|
110
|
+
# task model
|
|
111
|
+
def meaningful_arguments(task_model = self.model)
|
|
112
|
+
task_model.meaningful_arguments(arguments)
|
|
113
|
+
end
|
|
569
114
|
|
|
570
|
-
|
|
571
|
-
# :method: each_forwarding
|
|
572
|
-
# :call-seq:
|
|
573
|
-
# task.each_forwarding(event_model) do |target_event_model|
|
|
574
|
-
# end
|
|
115
|
+
# @api private
|
|
575
116
|
#
|
|
576
|
-
#
|
|
577
|
-
#
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
117
|
+
# Evaluate delayed arguments, and replace in {#arguments} the ones that
|
|
118
|
+
# currently have a value
|
|
119
|
+
def freeze_delayed_arguments
|
|
120
|
+
if !arguments.static?
|
|
121
|
+
result = Hash.new
|
|
122
|
+
arguments.each do |key, value|
|
|
123
|
+
if TaskArguments.delayed_argument?(value)
|
|
124
|
+
catch(:no_value) do
|
|
125
|
+
result[key] = value.evaluate_delayed_argument(self)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
assign_arguments(**result)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
582
132
|
|
|
583
|
-
|
|
584
|
-
# :singleton-method: causal_links
|
|
585
|
-
# :call-seq:
|
|
586
|
-
# task_model.causal_links(event_model) => [target_event_models]
|
|
133
|
+
# The task name
|
|
587
134
|
#
|
|
588
|
-
#
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
135
|
+
# @return [String]
|
|
136
|
+
def name
|
|
137
|
+
return @name if @name
|
|
138
|
+
name = "#{model.name || self.class.name}:0x#{address.to_s(16)}"
|
|
139
|
+
if !frozen?
|
|
140
|
+
@name = name
|
|
141
|
+
end
|
|
142
|
+
name
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Whether the task is a mission for its owners.
|
|
595
146
|
#
|
|
596
|
-
#
|
|
597
|
-
#
|
|
598
|
-
|
|
147
|
+
# If you want to know if it a mission for the local system, use
|
|
148
|
+
# Plan#mission_task?. In non-distributed Roby, the two are identical
|
|
149
|
+
attr_predicate :mission?, true
|
|
150
|
+
|
|
151
|
+
def inspect
|
|
152
|
+
state = if pending? then 'pending'
|
|
153
|
+
elsif failed_to_start? then 'failed to start'
|
|
154
|
+
elsif starting? then 'starting'
|
|
155
|
+
elsif running? then 'running'
|
|
156
|
+
elsif finishing? then 'finishing'
|
|
157
|
+
else 'finished'
|
|
158
|
+
end
|
|
159
|
+
"#<#{to_s} executable=#{executable?} state=#{state} plan=#{plan.to_s}>"
|
|
160
|
+
end
|
|
599
161
|
|
|
600
|
-
|
|
601
|
-
# :method: each_causal_link
|
|
602
|
-
# :call-seq:
|
|
603
|
-
# task.each_causal_link(event_model) do |target_event_model|
|
|
604
|
-
# end
|
|
162
|
+
# @api private
|
|
605
163
|
#
|
|
606
|
-
#
|
|
607
|
-
# the given event. It enumerates all the ones defined on this task's model
|
|
608
|
-
# (using Task::causal_link) and also on its parent classes.
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
##
|
|
614
|
-
# :singleton-method: handlers
|
|
615
|
-
# :call-seq:
|
|
616
|
-
# task_model.handlers(event_model) => [target_event_models]
|
|
164
|
+
# Helper to assign multiple argument values at once
|
|
617
165
|
#
|
|
618
|
-
#
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
#
|
|
622
|
-
#
|
|
623
|
-
#
|
|
624
|
-
#
|
|
166
|
+
# It differs from calling assign_argument in a loop in two ways:
|
|
167
|
+
#
|
|
168
|
+
# - it is common for subclasses to define a high-level argument that is,
|
|
169
|
+
# in the end, propagated to lower-level arguments. This method handles
|
|
170
|
+
# the fact that, when doing this, one will get parallel assignment of
|
|
171
|
+
# the high-level and low-level values during e.g. log replay which would
|
|
172
|
+
# fail in assign_arguments since arguments are single-assignation
|
|
625
173
|
#
|
|
626
|
-
#
|
|
627
|
-
|
|
628
|
-
|
|
174
|
+
# - assignation is all-or-nothing
|
|
175
|
+
def assign_arguments(**arguments)
|
|
176
|
+
initial_arguments = @arguments
|
|
177
|
+
initial_set_arguments = initial_arguments.assigned_arguments
|
|
178
|
+
current_arguments = initial_set_arguments.dup
|
|
629
179
|
|
|
630
|
-
|
|
631
|
-
# :method: each_handler
|
|
632
|
-
# :call-seq:
|
|
633
|
-
# task.each_handler(event_model) do |target_event_model|
|
|
634
|
-
# end
|
|
635
|
-
#
|
|
636
|
-
# Enumerates the set of model-level event handlers that are defined for
|
|
637
|
-
# the given event. It enumerates all handlers defined on the instance's
|
|
638
|
-
# task model and its parent classes.
|
|
639
|
-
|
|
640
|
-
model_attribute_list('signal')
|
|
641
|
-
model_attribute_list('forwarding')
|
|
642
|
-
model_attribute_list('causal_link')
|
|
643
|
-
model_attribute_list('handler')
|
|
644
|
-
model_attribute_list('precondition')
|
|
645
|
-
|
|
646
|
-
# The task arguments as symbol => value associative container
|
|
647
|
-
attr_reader :arguments
|
|
648
|
-
|
|
649
|
-
# The part of +arguments+ that is meaningful for this task model. I.e.
|
|
650
|
-
# it returns the set of elements in the +arguments+ property that define
|
|
651
|
-
# arguments listed in the task model
|
|
652
|
-
def meaningful_arguments(task_model = self.model)
|
|
653
|
-
arguments.slice(*task_model.arguments)
|
|
654
|
-
end
|
|
655
|
-
|
|
656
|
-
# The task name
|
|
657
|
-
def name
|
|
658
|
-
@name ||= "#{model.name || self.class.name}:0x#{address.to_s(16)}"
|
|
659
|
-
end
|
|
660
|
-
|
|
661
|
-
# This predicate is true if this task is a mission for its owners. If
|
|
662
|
-
# you want to know if it a mission for the local pDB, use Plan#mission?
|
|
663
|
-
attr_predicate :mission?, true
|
|
664
|
-
|
|
665
|
-
def inspect
|
|
666
|
-
state = if pending? then 'pending'
|
|
667
|
-
elsif failed_to_start? then 'failed to start'
|
|
668
|
-
elsif starting? then 'starting'
|
|
669
|
-
elsif running? then 'running'
|
|
670
|
-
elsif finishing? then 'finishing'
|
|
671
|
-
else 'finished'
|
|
672
|
-
end
|
|
673
|
-
"#<#{to_s} executable=#{executable?} state=#{state} plan=#{plan.to_s}>"
|
|
674
|
-
end
|
|
675
|
-
|
|
676
|
-
# Builds a task object using this task model
|
|
677
|
-
#
|
|
678
|
-
# The task object can be configured by a given block. After the
|
|
679
|
-
# block is called, two things are checked:
|
|
680
|
-
# * the task shall have a +start+ event
|
|
681
|
-
# * the task shall have at least one terminal event. If no +stop+ event
|
|
682
|
-
# is defined, then all terminal events are aliased to +stop+
|
|
683
|
-
def initialize(arguments = Hash.new) #:yields: task_object
|
|
684
|
-
super() if defined? super
|
|
685
|
-
|
|
686
|
-
@arguments = TaskArguments.new(self)
|
|
180
|
+
# First assign normal values
|
|
687
181
|
arguments.each do |key, value|
|
|
182
|
+
@arguments = TaskArguments.new(self)
|
|
183
|
+
@arguments.merge!(initial_set_arguments)
|
|
184
|
+
assign_argument(key, value)
|
|
185
|
+
current_arguments.merge!(@arguments) do |k, v1, v2|
|
|
186
|
+
if v1 != v2
|
|
187
|
+
raise ArgumentError, "trying to override #{k}=#{v1} to #{v2}"
|
|
188
|
+
end
|
|
189
|
+
v1
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
initial_arguments.merge!(current_arguments)
|
|
193
|
+
|
|
194
|
+
ensure
|
|
195
|
+
@arguments = initial_arguments
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# @api private
|
|
199
|
+
#
|
|
200
|
+
# Sets one of this task's arguments
|
|
201
|
+
def assign_argument(key, value)
|
|
202
|
+
key = key.to_sym
|
|
203
|
+
if TaskArguments.delayed_argument?(value)
|
|
204
|
+
@arguments[key] = value
|
|
205
|
+
else
|
|
688
206
|
if self.respond_to?("#{key}=")
|
|
689
207
|
self.send("#{key}=", value)
|
|
690
|
-
|
|
208
|
+
end
|
|
209
|
+
if @arguments.writable?(key, value)
|
|
210
|
+
# The accessor did not write the argument. That's alright
|
|
691
211
|
@arguments[key] = value
|
|
692
212
|
end
|
|
693
213
|
end
|
|
214
|
+
end
|
|
694
215
|
|
|
695
|
-
|
|
216
|
+
|
|
217
|
+
# Create a new task object
|
|
218
|
+
#
|
|
219
|
+
# @param [Plan] plan the plan this task should be added two. The default
|
|
220
|
+
# is to add it to its own TemplatePlan object
|
|
221
|
+
# @param [Hash<Symbol,Object>] arguments assignation to task arguments
|
|
222
|
+
def initialize(plan: TemplatePlan.new, **arguments)
|
|
223
|
+
@bound_events = Hash.new
|
|
224
|
+
super(plan: plan)
|
|
225
|
+
|
|
226
|
+
@model = self.class
|
|
227
|
+
@abstract = @model.abstract?
|
|
228
|
+
|
|
229
|
+
@failed_to_start = false
|
|
230
|
+
@pending = true
|
|
231
|
+
@started = false
|
|
232
|
+
@running = false
|
|
233
|
+
@starting = false
|
|
234
|
+
@finished = false
|
|
235
|
+
@finishing = false
|
|
236
|
+
@success = nil
|
|
237
|
+
@reusable = true
|
|
238
|
+
@history = Array.new
|
|
239
|
+
@coordination_objects = Array.new
|
|
240
|
+
|
|
241
|
+
@arguments = TaskArguments.new(self)
|
|
242
|
+
assign_arguments(**arguments)
|
|
243
|
+
# Now assign default values for the arguments that have not yet been
|
|
244
|
+
# set
|
|
245
|
+
model.arguments.each do |argname|
|
|
246
|
+
next if @arguments.has_key?(argname)
|
|
247
|
+
|
|
248
|
+
has_default, default = model.default_argument(argname)
|
|
249
|
+
if has_default
|
|
250
|
+
assign_argument(argname, default)
|
|
251
|
+
end
|
|
252
|
+
end
|
|
696
253
|
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
# events. Note that the event relations are instanciated by
|
|
700
|
-
# Plan#discover when this task is included in a plan, thus avoiding
|
|
701
|
-
# filling up the relation graphs with unused relations.
|
|
702
|
-
initialize_events
|
|
703
|
-
end
|
|
254
|
+
@poll_handlers = []
|
|
255
|
+
@execute_handlers = []
|
|
704
256
|
|
|
257
|
+
initialize_events
|
|
258
|
+
plan.register_task(self)
|
|
259
|
+
template = self.model.template
|
|
705
260
|
|
|
706
|
-
|
|
707
|
-
|
|
261
|
+
mappings = Hash.new
|
|
262
|
+
template.events_by_name.each do |name, template_event|
|
|
263
|
+
mappings[template_event] = bound_events[name]
|
|
264
|
+
end
|
|
265
|
+
template.copy_relation_graphs_to(plan, mappings)
|
|
266
|
+
apply_terminal_flags(
|
|
267
|
+
template.terminal_events.map(&mappings.method(:[])),
|
|
268
|
+
template.success_events.map(&mappings.method(:[])),
|
|
269
|
+
template.failure_events.map(&mappings.method(:[])))
|
|
270
|
+
@terminal_flag_invalid = false
|
|
271
|
+
|
|
272
|
+
if self.model.state_machine
|
|
273
|
+
@state_machine = TaskStateMachine.new(self.model.state_machine)
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# Retrieve the current state of the task
|
|
708
278
|
#
|
|
709
|
-
#
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
279
|
+
# Can be one of the core states: pending, failed_to_start, starting,
|
|
280
|
+
# started, running, finishing, succeeded or failed
|
|
281
|
+
#
|
|
282
|
+
# If the task has a state machine defined with
|
|
283
|
+
# {TaskStateHelper#refine_running_state}, the state
|
|
284
|
+
# machine's current state will be returned in place of :running
|
|
285
|
+
#
|
|
286
|
+
# @return [Symbol]
|
|
287
|
+
def current_state
|
|
288
|
+
# Started and not finished
|
|
289
|
+
if running?
|
|
290
|
+
if respond_to?("state_machine")
|
|
291
|
+
# state_machine.status # => String
|
|
292
|
+
# state_machine.status_name # => Symbol
|
|
293
|
+
return state_machine.status_name
|
|
294
|
+
else
|
|
295
|
+
return :running
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
# True, when task has never been started
|
|
300
|
+
if pending?
|
|
301
|
+
return :pending
|
|
302
|
+
elsif failed_to_start?
|
|
303
|
+
return :failed_to_start
|
|
304
|
+
elsif starting?
|
|
305
|
+
return :starting
|
|
306
|
+
# True, when terminal event is pending
|
|
307
|
+
elsif finishing?
|
|
308
|
+
return :finishing
|
|
309
|
+
# Terminated with success or failure
|
|
310
|
+
elsif success?
|
|
311
|
+
return :succeeded
|
|
312
|
+
elsif failed?
|
|
313
|
+
return :failed
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
# Test if that current state corresponds to the provided state (symbol)
|
|
318
|
+
#
|
|
319
|
+
# @param [Symbol] state
|
|
320
|
+
# @return [Boolean]
|
|
321
|
+
def current_state?(state)
|
|
322
|
+
return state == current_state.to_sym
|
|
717
323
|
end
|
|
718
324
|
|
|
719
325
|
# Helper methods which creates all the necessary TaskEventGenerator
|
|
720
326
|
# objects and stores them in the #bound_events map
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
327
|
+
def initialize_events # :nodoc:
|
|
328
|
+
# Create all event generators
|
|
329
|
+
bound_events = Hash.new
|
|
330
|
+
model.each_event do |ev_symbol, ev_model|
|
|
331
|
+
ev = TaskEventGenerator.new(self, ev_model)
|
|
332
|
+
ev.plan = plan
|
|
333
|
+
bound_events[ev_symbol.to_sym] = ev
|
|
334
|
+
end
|
|
335
|
+
@bound_events = bound_events
|
|
730
336
|
end
|
|
337
|
+
private :initialize_events
|
|
731
338
|
|
|
732
|
-
|
|
339
|
+
# (see PlanObject#promise)
|
|
340
|
+
#
|
|
341
|
+
# @raise [PromiseInFinishedTask] if attempting to create a promise on a
|
|
342
|
+
# task that is either finished, or failed to start
|
|
343
|
+
def promise(description: "#{self}.promise", executor: promise_executor, &block)
|
|
344
|
+
if failed_to_start?
|
|
345
|
+
raise PromiseInFinishedTask, "attempting to create a promise on #{self} that has failed to start"
|
|
346
|
+
elsif finished?
|
|
347
|
+
raise PromiseInFinishedTask, "attempting to create a promise on #{self} that is finished"
|
|
348
|
+
end
|
|
349
|
+
super
|
|
350
|
+
end
|
|
733
351
|
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
352
|
+
# Returns for how many seconds this task is running. Returns nil if
|
|
353
|
+
# the task is not running.
|
|
354
|
+
def lifetime
|
|
355
|
+
if running?
|
|
356
|
+
Time.now - start_time
|
|
357
|
+
elsif finished?
|
|
358
|
+
end_time - start_time
|
|
359
|
+
end
|
|
360
|
+
end
|
|
741
361
|
|
|
742
362
|
# Returns when this task has been started
|
|
743
363
|
def start_time
|
|
744
|
-
if
|
|
745
|
-
|
|
364
|
+
if ev = start_event.last
|
|
365
|
+
ev.time
|
|
746
366
|
end
|
|
747
367
|
end
|
|
748
368
|
|
|
369
|
+
# Returns when this task has finished
|
|
370
|
+
def end_time
|
|
371
|
+
if ev = stop_event.last
|
|
372
|
+
ev.time
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
# The last event emitted by this task
|
|
377
|
+
#
|
|
378
|
+
# @return [TaskEvent,nil]
|
|
379
|
+
def last_event
|
|
380
|
+
history.last
|
|
381
|
+
end
|
|
382
|
+
|
|
749
383
|
def create_fresh_copy
|
|
750
|
-
|
|
751
|
-
end
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
model.each_forwarding_set do |generator, signalled_events|
|
|
789
|
-
next if signalled_events.empty?
|
|
790
|
-
generator = bound_events[generator]
|
|
791
|
-
right_border.delete(generator)
|
|
792
|
-
|
|
793
|
-
for signalled in signalled_events
|
|
794
|
-
signalled = bound_events[signalled]
|
|
795
|
-
generator.forward_to signalled
|
|
796
|
-
left_border.delete(signalled)
|
|
797
|
-
end
|
|
798
|
-
end
|
|
799
|
-
|
|
800
|
-
model.each_causal_link_set do |generator, signalled_events|
|
|
801
|
-
next if signalled_events.empty?
|
|
802
|
-
generator = bound_events[generator]
|
|
803
|
-
right_border.delete(generator)
|
|
804
|
-
|
|
805
|
-
for signalled in signalled_events
|
|
806
|
-
signalled = bound_events[signalled]
|
|
807
|
-
generator.add_causal_link signalled
|
|
808
|
-
left_border.delete(signalled)
|
|
809
|
-
end
|
|
810
|
-
end
|
|
811
|
-
|
|
812
|
-
update_terminal_flag
|
|
813
|
-
|
|
814
|
-
# WARNING: this works only because:
|
|
815
|
-
# * there is always at least updated_data as an intermediate event
|
|
816
|
-
# * there is always one terminal event which is not stop
|
|
817
|
-
start_event = bound_events[:start]
|
|
818
|
-
stop_event = bound_events[:stop]
|
|
819
|
-
left_border.delete(start_event)
|
|
820
|
-
right_border.delete(start_event)
|
|
821
|
-
left_border.delete(stop_event)
|
|
822
|
-
right_border.delete(stop_event)
|
|
823
|
-
|
|
824
|
-
for generator in left_border
|
|
825
|
-
start_event.add_precedence(generator) unless generator.terminal?
|
|
826
|
-
end
|
|
827
|
-
|
|
828
|
-
# WARN: the start event CAN be terminal: it can be a signal from
|
|
829
|
-
# :start to a terminal event
|
|
830
|
-
#
|
|
831
|
-
# Create the precedence relations between 'normal' events and the terminal events
|
|
832
|
-
for terminal in left_border
|
|
833
|
-
next unless terminal.terminal?
|
|
834
|
-
for generator in right_border
|
|
835
|
-
unless generator.terminal?
|
|
836
|
-
generator.add_precedence(terminal)
|
|
837
|
-
end
|
|
838
|
-
end
|
|
839
|
-
end
|
|
840
|
-
end
|
|
841
|
-
|
|
842
|
-
def plan=(new_plan) # :nodoc:
|
|
843
|
-
if plan != new_plan
|
|
844
|
-
if plan && plan.include?(self)
|
|
845
|
-
raise ModelViolation.new, "still included in #{plan}, cannot change the plan"
|
|
846
|
-
elsif self_owned? && running?
|
|
847
|
-
raise ModelViolation.new, "cannot change the plan of a running task"
|
|
848
|
-
end
|
|
849
|
-
end
|
|
850
|
-
|
|
851
|
-
super
|
|
852
|
-
|
|
853
|
-
for _, ev in bound_events
|
|
854
|
-
ev.plan = plan
|
|
855
|
-
end
|
|
856
|
-
end
|
|
857
|
-
|
|
858
|
-
class << self
|
|
859
|
-
##
|
|
860
|
-
# :singleton-method: abstract?
|
|
861
|
-
#
|
|
862
|
-
# True if this task is an abstract task.
|
|
863
|
-
#
|
|
864
|
-
# See Task::abstract() for more information.
|
|
865
|
-
attr_predicate :abstract
|
|
866
|
-
|
|
867
|
-
# Declare that this task model defines abstract tasks. Abstract
|
|
868
|
-
# tasks can be used to represent an action, without specifically
|
|
869
|
-
# representing how this action should be done.
|
|
870
|
-
#
|
|
871
|
-
# Instances of abstract task models are not executable, i.e. they
|
|
872
|
-
# cannot be started.
|
|
873
|
-
#
|
|
874
|
-
# See also #abstract? and #executable?
|
|
875
|
-
def abstract
|
|
876
|
-
@abstract = true
|
|
877
|
-
end
|
|
878
|
-
|
|
879
|
-
# Declare that tasks of this model can finish by simply emitting
|
|
880
|
-
# +stop+. Use it this way:
|
|
881
|
-
#
|
|
882
|
-
# class MyTask < Roby::Task
|
|
883
|
-
# terminates
|
|
884
|
-
# end
|
|
885
|
-
#
|
|
886
|
-
# It adds a +stop!+ command that emits the +failed+ event.
|
|
887
|
-
def terminates
|
|
888
|
-
event :failed, :command => true, :terminal => true
|
|
889
|
-
interruptible
|
|
890
|
-
end
|
|
891
|
-
|
|
892
|
-
# Declare that tasks of this model can be interrupted. It does so by
|
|
893
|
-
# defining a command for +stop+, which in effect calls the command
|
|
894
|
-
# for +failed+.
|
|
895
|
-
#
|
|
896
|
-
# Raises ArgumentError if failed is not controlable.
|
|
897
|
-
def interruptible
|
|
898
|
-
if !has_event?(:failed) || !event_model(:failed).controlable?
|
|
899
|
-
raise ArgumentError, "failed is not controlable"
|
|
900
|
-
end
|
|
901
|
-
event(:stop) do |context|
|
|
902
|
-
if starting?
|
|
903
|
-
on :start, self, :stop
|
|
904
|
-
return
|
|
905
|
-
end
|
|
906
|
-
failed!(context)
|
|
907
|
-
end
|
|
908
|
-
end
|
|
909
|
-
|
|
910
|
-
def setup_poll_method(block) # :nodoc:
|
|
911
|
-
define_method(:poll) do |plan|
|
|
912
|
-
return unless self_owned?
|
|
913
|
-
begin
|
|
914
|
-
poll_handler
|
|
915
|
-
rescue Exception => e
|
|
916
|
-
emit :failed, e
|
|
917
|
-
end
|
|
918
|
-
end
|
|
919
|
-
|
|
920
|
-
define_method(:poll_handler, &block)
|
|
921
|
-
end
|
|
922
|
-
|
|
923
|
-
# Declares that the given block should be called at each execution
|
|
924
|
-
# cycle, when the task is running. Use it that way:
|
|
925
|
-
#
|
|
926
|
-
# class MyTask < Roby::Task
|
|
927
|
-
# poll do
|
|
928
|
-
# ... do something ...
|
|
929
|
-
# end
|
|
930
|
-
# end
|
|
931
|
-
#
|
|
932
|
-
# If the given polling block raises an exception, the task will be
|
|
933
|
-
# terminated by emitting its +failed+ event.
|
|
934
|
-
def poll(&block)
|
|
935
|
-
if !block_given?
|
|
936
|
-
raise "no block given"
|
|
937
|
-
end
|
|
938
|
-
|
|
939
|
-
setup_poll_method(block)
|
|
940
|
-
|
|
941
|
-
on(:start) { |ev| ev.task.plan.engine.propagation_handlers << method(:poll) }
|
|
942
|
-
on(:stop) { |ev| ev.task.plan.engine.propagation_handlers.delete(method(:poll)) }
|
|
943
|
-
end
|
|
944
|
-
end
|
|
945
|
-
|
|
946
|
-
# Roby::Task is an abstract model. See Task::abstract
|
|
947
|
-
abstract
|
|
948
|
-
|
|
949
|
-
# Returns true if this task is from an abstract model. If it is the
|
|
950
|
-
# case, the task is not executable.
|
|
384
|
+
model.new(arguments.dup)
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
def initialize_copy(old) # :nodoc:
|
|
388
|
+
super
|
|
389
|
+
|
|
390
|
+
@name = nil
|
|
391
|
+
|
|
392
|
+
@arguments = TaskArguments.new(self)
|
|
393
|
+
arguments.force_merge! old.arguments
|
|
394
|
+
arguments.instance_variable_set(:@task, self)
|
|
395
|
+
|
|
396
|
+
@instantiated_model_events = false
|
|
397
|
+
|
|
398
|
+
# Create all event generators
|
|
399
|
+
@bound_events = Hash.new
|
|
400
|
+
@execute_handlers = old.execute_handlers.dup
|
|
401
|
+
@poll_handlers = old.poll_handlers.dup
|
|
402
|
+
if m = old.instance_variable_get(:@fullfilled_model)
|
|
403
|
+
@fullfilled_model = m.dup
|
|
404
|
+
end
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
def plan=(new_plan) # :nodoc:
|
|
408
|
+
super
|
|
409
|
+
|
|
410
|
+
@relation_graphs =
|
|
411
|
+
if plan then plan.task_relation_graphs
|
|
412
|
+
end
|
|
413
|
+
for ev in bound_events.each_value
|
|
414
|
+
ev.plan = plan
|
|
415
|
+
end
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
# Roby::Task is an abstract model. See Models::Task#abstract
|
|
419
|
+
abstract
|
|
420
|
+
|
|
421
|
+
## :method:abstract?
|
|
951
422
|
#
|
|
952
|
-
#
|
|
953
|
-
|
|
954
|
-
|
|
423
|
+
# If true, this instance is marked as abstract, i.e. as a placeholder
|
|
424
|
+
# for future actions.
|
|
425
|
+
#
|
|
426
|
+
# By default, it takes the value of its model, i.e. through
|
|
427
|
+
# model.abstract, set by calling abstract in a task model definition as
|
|
428
|
+
# in
|
|
429
|
+
#
|
|
430
|
+
# class MyModel < Roby::Task
|
|
431
|
+
# abstract
|
|
432
|
+
# end
|
|
433
|
+
#
|
|
434
|
+
# It can also be overriden on a per instance basis with
|
|
435
|
+
#
|
|
436
|
+
# task.abstract = <value>
|
|
437
|
+
#
|
|
438
|
+
attr_predicate :abstract?, true
|
|
439
|
+
|
|
440
|
+
# True if this task is executable. A task is not executable if it is
|
|
955
441
|
# abstract or partially instanciated.
|
|
956
442
|
#
|
|
957
|
-
#
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
443
|
+
# @see abstract? partially_instanciated?
|
|
444
|
+
def executable?
|
|
445
|
+
if @executable == true
|
|
446
|
+
true
|
|
447
|
+
elsif @executable.nil?
|
|
448
|
+
(!abstract? && !partially_instanciated? && super)
|
|
449
|
+
end
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
# Returns true if this task's stop event is controlable
|
|
453
|
+
def interruptible?; stop_event.controlable? end
|
|
454
|
+
# Set the executable flag. executable cannot be set to +false+ if the
|
|
455
|
+
# task is running, and cannot be set to true on a finished task.
|
|
456
|
+
def executable=(flag)
|
|
457
|
+
return if flag == @executable
|
|
458
|
+
return unless self_owned?
|
|
459
|
+
if flag && !pending?
|
|
460
|
+
raise ModelViolation, "cannot set the executable flag of #{self} since it is not pending"
|
|
461
|
+
elsif !flag && running?
|
|
462
|
+
raise ModelViolation, "cannot unset the executable flag of #{self} since it is running"
|
|
463
|
+
end
|
|
464
|
+
super
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
# Lists all arguments, that are set to be needed via the :argument
|
|
468
|
+
# syntax but are not set.
|
|
469
|
+
#
|
|
470
|
+
# This is needed for debugging purposes.
|
|
471
|
+
def list_unset_arguments # :nodoc:
|
|
472
|
+
actual_arguments =
|
|
473
|
+
if arguments.static?
|
|
474
|
+
arguments
|
|
475
|
+
else
|
|
476
|
+
arguments.evaluate_delayed_arguments
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
model.arguments.find_all do |name|
|
|
480
|
+
!actual_arguments.has_key?(name)
|
|
481
|
+
end
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
# True if all arguments defined by Task.argument on the task model are
|
|
485
|
+
# either explicitely set or have a default value.
|
|
486
|
+
def fully_instanciated?
|
|
487
|
+
if arguments.static?
|
|
488
|
+
@fully_instanciated ||= list_unset_arguments.empty?
|
|
489
|
+
else
|
|
490
|
+
list_unset_arguments.empty?
|
|
491
|
+
end
|
|
492
|
+
end
|
|
493
|
+
|
|
978
494
|
# True if at least one argument required by the task model is not set.
|
|
979
495
|
# See Task.argument.
|
|
980
|
-
|
|
496
|
+
def partially_instanciated?; !fully_instanciated? end
|
|
981
497
|
|
|
982
498
|
# True if this task has an event of the required model. The event model
|
|
983
499
|
# can either be a event class or an event name.
|
|
984
500
|
def has_event?(event_model)
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
end
|
|
501
|
+
bound_events.has_key?(event_model)
|
|
502
|
+
end
|
|
988
503
|
|
|
989
504
|
# True if this task is starting, i.e. if its start event is pending
|
|
990
505
|
# (has been called, but is not emitted yet)
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
506
|
+
attr_predicate :starting?, true
|
|
507
|
+
# True if this task can be started
|
|
508
|
+
attr_predicate :pending?, true
|
|
994
509
|
# True if this task is currently running (i.e. is has already started,
|
|
995
510
|
# and is not finished)
|
|
996
511
|
def running?; started? && !finished? end
|
|
512
|
+
|
|
513
|
+
attr_predicate :started?, true
|
|
514
|
+
attr_predicate :finished?, true
|
|
515
|
+
attr_predicate :failed?, true
|
|
516
|
+
attr_predicate :success?, true
|
|
997
517
|
# True if the task is finishing, i.e. if a terminal event is pending.
|
|
998
|
-
|
|
999
|
-
if running?
|
|
1000
|
-
each_event { |ev| return true if ev.terminal? && ev.pending? }
|
|
1001
|
-
end
|
|
1002
|
-
false
|
|
1003
|
-
end
|
|
518
|
+
attr_predicate :finishing?, true
|
|
1004
519
|
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
520
|
+
# Call to force the value of {#reusable?} to false
|
|
521
|
+
# @return [void]
|
|
522
|
+
def do_not_reuse
|
|
523
|
+
@reusable = false
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
# True if this task can be reused by some other parts in the plan
|
|
527
|
+
def reusable?
|
|
528
|
+
plan && @reusable && !quarantined? && !garbage? && !failed_to_start? && !finished? && !finishing?
|
|
529
|
+
end
|
|
1009
530
|
|
|
1010
|
-
|
|
1011
|
-
|
|
531
|
+
def garbage!
|
|
532
|
+
bound_events.each_value(&:garbage!)
|
|
533
|
+
super
|
|
534
|
+
end
|
|
1012
535
|
|
|
1013
|
-
|
|
1014
|
-
# task.clear_relations => task
|
|
536
|
+
# @!method quarantined?
|
|
1015
537
|
#
|
|
1016
|
-
#
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
#
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
end
|
|
1070
|
-
|
|
1071
|
-
success_events, failure_events, terminal_events =
|
|
1072
|
-
[event(:success)].to_value_set,
|
|
1073
|
-
[event(:failed)].to_value_set,
|
|
1074
|
-
[event(:stop), event(:success), event(:failed)].to_value_set
|
|
1075
|
-
|
|
1076
|
-
loop do
|
|
1077
|
-
old_size = terminal_events.size
|
|
1078
|
-
for _, ev in bound_events
|
|
1079
|
-
for relation in [EventStructure::Signal, EventStructure::Forwarding]
|
|
1080
|
-
for target in ev.child_objects(relation)
|
|
1081
|
-
next if !target.respond_to?(:task) || target.task != self
|
|
1082
|
-
next if ev[target, relation]
|
|
1083
|
-
|
|
1084
|
-
if success_events.include?(target)
|
|
1085
|
-
success_events << ev
|
|
1086
|
-
terminal_events << ev
|
|
1087
|
-
break
|
|
1088
|
-
elsif failure_events.include?(target)
|
|
1089
|
-
failure_events << ev
|
|
1090
|
-
terminal_events << ev
|
|
1091
|
-
break
|
|
1092
|
-
elsif terminal_events.include?(target)
|
|
1093
|
-
terminal_events << ev
|
|
1094
|
-
end
|
|
538
|
+
# Whether this task has been quarantined
|
|
539
|
+
attr_predicate :quarantined?
|
|
540
|
+
|
|
541
|
+
# Mark the task as quarantined
|
|
542
|
+
#
|
|
543
|
+
# Once set it cannot be unset
|
|
544
|
+
def quarantined!
|
|
545
|
+
@quarantined = true
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
def failed_to_start?; @failed_to_start end
|
|
549
|
+
|
|
550
|
+
def mark_failed_to_start(reason, time)
|
|
551
|
+
if failed_to_start?
|
|
552
|
+
return
|
|
553
|
+
elsif !pending? && !starting?
|
|
554
|
+
raise Roby::InternalError, "#{self} is neither pending nor starting, cannot mark as failed_to_start!"
|
|
555
|
+
end
|
|
556
|
+
@failed_to_start = true
|
|
557
|
+
@failed_to_start_time = time
|
|
558
|
+
@failure_reason = reason
|
|
559
|
+
@pending = false
|
|
560
|
+
@starting = false
|
|
561
|
+
@failed = true
|
|
562
|
+
plan.task_index.set_state(self, :failed?)
|
|
563
|
+
end
|
|
564
|
+
|
|
565
|
+
def failed_to_start!(reason, time = Time.now)
|
|
566
|
+
mark_failed_to_start(reason, time)
|
|
567
|
+
each_event do |ev|
|
|
568
|
+
ev.unreachable!(reason)
|
|
569
|
+
end
|
|
570
|
+
execution_engine.log(:task_failed_to_start, self, reason)
|
|
571
|
+
end
|
|
572
|
+
|
|
573
|
+
# Clear relations events of this task have with events outside the task
|
|
574
|
+
def clear_events_external_relations(remove_strong: true)
|
|
575
|
+
removed = false
|
|
576
|
+
task_events = bound_events.values
|
|
577
|
+
each_event do |event|
|
|
578
|
+
for rel in event.sorted_relations
|
|
579
|
+
graph = plan.event_relation_graph_for(rel)
|
|
580
|
+
next if !remove_strong && graph.strong?
|
|
581
|
+
|
|
582
|
+
to_remove = Array.new
|
|
583
|
+
graph.each_in_neighbour(event) do |neighbour|
|
|
584
|
+
if !task_events.include?(neighbour)
|
|
585
|
+
to_remove << neighbour << event
|
|
586
|
+
end
|
|
587
|
+
end
|
|
588
|
+
graph.each_out_neighbour(event) do |neighbour|
|
|
589
|
+
if !task_events.include?(neighbour)
|
|
590
|
+
to_remove << event << neighbour
|
|
1095
591
|
end
|
|
1096
592
|
end
|
|
593
|
+
to_remove.each_slice(2) do |from, to|
|
|
594
|
+
graph.remove_edge(from, to)
|
|
595
|
+
end
|
|
596
|
+
removed ||= !to_remove.empty?
|
|
597
|
+
end
|
|
598
|
+
end
|
|
599
|
+
removed
|
|
600
|
+
end
|
|
601
|
+
|
|
602
|
+
# Remove all relations in which +self+ or its event are involved
|
|
603
|
+
#
|
|
604
|
+
# @param [Boolean] remove_internal if true, remove in-task relations between
|
|
605
|
+
# events
|
|
606
|
+
# @param [Boolean] remove_strong if true, remove strong relations as well
|
|
607
|
+
def clear_relations(remove_internal: false, remove_strong: true)
|
|
608
|
+
modified_plan = false
|
|
609
|
+
if remove_internal
|
|
610
|
+
each_event do |ev|
|
|
611
|
+
if ev.clear_relations(remove_strong: remove_strong)
|
|
612
|
+
modified_plan = true
|
|
613
|
+
end
|
|
614
|
+
end
|
|
615
|
+
else
|
|
616
|
+
modified_plan = clear_events_external_relations(remove_strong: remove_strong)
|
|
617
|
+
end
|
|
618
|
+
super(remove_strong: remove_strong) || modified_plan
|
|
619
|
+
end
|
|
1097
620
|
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
621
|
+
def invalidated_terminal_flag?; !!@terminal_flag_invalid end
|
|
622
|
+
def invalidate_terminal_flag; @terminal_flag_invalid = true end
|
|
623
|
+
|
|
624
|
+
# Updates the terminal flag for all events in the task. An event is
|
|
625
|
+
# terminal if the +stop+ event of the task will be called because this
|
|
626
|
+
# event is.
|
|
627
|
+
def update_terminal_flag # :nodoc:
|
|
628
|
+
return if !invalidated_terminal_flag?
|
|
629
|
+
terminal_events, success_events, failure_events =
|
|
630
|
+
self.model.compute_terminal_events(bound_events)
|
|
631
|
+
apply_terminal_flags(terminal_events, success_events, failure_events)
|
|
632
|
+
@terminal_flag_invalid = false
|
|
633
|
+
return terminal_events, success_events, failure_events
|
|
634
|
+
end
|
|
635
|
+
|
|
636
|
+
def apply_terminal_flags(terminal_events, success_events, failure_events)
|
|
637
|
+
for ev in bound_events.each_value
|
|
638
|
+
ev.terminal_flag = nil
|
|
639
|
+
if terminal_events.include?(ev)
|
|
640
|
+
if success_events.include?(ev)
|
|
641
|
+
ev.terminal_flag = :success
|
|
642
|
+
elsif failure_events.include?(ev)
|
|
643
|
+
ev.terminal_flag = :failure
|
|
644
|
+
else
|
|
645
|
+
ev.terminal_flag = true
|
|
646
|
+
end
|
|
647
|
+
end
|
|
648
|
+
end
|
|
649
|
+
end
|
|
1113
650
|
|
|
1114
651
|
# Returns a list of Event objects, for all events that have been fired
|
|
1115
652
|
# by this task. The list is sorted by emission times.
|
|
1116
|
-
|
|
1117
|
-
history = []
|
|
1118
|
-
each_event do |event|
|
|
1119
|
-
history += event.history
|
|
1120
|
-
end
|
|
1121
|
-
|
|
1122
|
-
history.sort_by { |ev| ev.time }
|
|
1123
|
-
end
|
|
653
|
+
attr_reader :history
|
|
1124
654
|
|
|
1125
655
|
# Returns the set of tasks directly related to this task, either because
|
|
1126
656
|
# of task relations or because of task events that are related to other
|
|
1127
657
|
# task events
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
658
|
+
def related_tasks(result = Set.new)
|
|
659
|
+
result = related_objects(nil, result)
|
|
660
|
+
each_event do |ev|
|
|
661
|
+
ev.related_tasks(result)
|
|
662
|
+
end
|
|
663
|
+
|
|
664
|
+
result
|
|
665
|
+
end
|
|
1136
666
|
|
|
1137
667
|
# Returns the set of events directly related to this task
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
668
|
+
def related_events(result = Set.new)
|
|
669
|
+
each_event do |ev|
|
|
670
|
+
ev.related_events(result)
|
|
671
|
+
end
|
|
672
|
+
|
|
673
|
+
result.reject { |ev| ev.respond_to?(:task) && ev.task == self }.
|
|
674
|
+
to_set
|
|
675
|
+
end
|
|
1146
676
|
|
|
1147
677
|
# This method is called by TaskEventGenerator#fire just before the event handlers
|
|
1148
678
|
# and commands are called
|
|
1149
|
-
def
|
|
1150
|
-
if !executable?
|
|
1151
|
-
raise TaskNotExecutable.new(self), "trying to emit #{symbol} on #{self} but #{self} is not executable"
|
|
1152
|
-
end
|
|
1153
|
-
|
|
679
|
+
def check_emission_validity(event) # :nodoc:
|
|
1154
680
|
if finished? && !event.terminal?
|
|
1155
|
-
|
|
1156
|
-
|
|
681
|
+
EmissionRejected.new(event).
|
|
682
|
+
exception("#{self}.emit(#{event.symbol}) called by #{execution_engine.propagation_sources.to_a} but the task has finished. Task has been terminated by #{stop_event.last.sources.to_a}.")
|
|
1157
683
|
elsif pending? && event.symbol != :start
|
|
1158
|
-
|
|
1159
|
-
|
|
684
|
+
EmissionRejected.new(event).
|
|
685
|
+
exception("#{self}.emit(#{event.symbol}) called by #{execution_engine.propagation_sources.to_a} but the task has never been started")
|
|
1160
686
|
elsif running? && event.symbol == :start
|
|
1161
|
-
|
|
1162
|
-
|
|
687
|
+
EmissionRejected.new(event).
|
|
688
|
+
exception("#{self}.emit(#{event.symbol}) called by #{execution_engine.propagation_sources.to_a} but the task is already running. Task has been started by #{start_event.last.sources.to_a}.")
|
|
1163
689
|
end
|
|
1164
|
-
|
|
1165
|
-
super if defined? super
|
|
1166
690
|
end
|
|
1167
691
|
|
|
1168
692
|
# Hook called by TaskEventGenerator#fired when one of this task's events
|
|
1169
|
-
#
|
|
1170
|
-
def
|
|
1171
|
-
|
|
1172
|
-
|
|
693
|
+
# has been fired.
|
|
694
|
+
def fired_event(event)
|
|
695
|
+
history << event
|
|
696
|
+
update_task_status(event)
|
|
1173
697
|
end
|
|
698
|
+
|
|
699
|
+
# The most specialized event that caused this task to end
|
|
700
|
+
attr_reader :terminal_event
|
|
1174
701
|
|
|
1175
|
-
|
|
1176
|
-
|
|
702
|
+
# The reason for which this task failed.
|
|
703
|
+
#
|
|
704
|
+
# It can either be an event or a LocalizedError object.
|
|
705
|
+
#
|
|
706
|
+
# If it is an event, it is the most specialized event whose emission
|
|
707
|
+
# has been forwarded to :failed
|
|
708
|
+
#
|
|
709
|
+
# If it is a LocalizedError object, it is the exception that caused the
|
|
710
|
+
# task failure.
|
|
711
|
+
attr_reader :failure_reason
|
|
712
|
+
|
|
713
|
+
# The time at which the task failed to start
|
|
714
|
+
attr_reader :failed_to_start_time
|
|
1177
715
|
|
|
1178
716
|
# The event that caused this task to fail. This is equivalent to taking
|
|
1179
717
|
# the first emitted element of
|
|
@@ -1181,846 +719,919 @@ def fire_event(event)
|
|
|
1181
719
|
#
|
|
1182
720
|
# It is only much more efficient
|
|
1183
721
|
attr_reader :failure_event
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
722
|
+
|
|
723
|
+
# Call to update the task status because of +event+
|
|
724
|
+
def update_task_status(event) # :nodoc:
|
|
725
|
+
if event.symbol == :start
|
|
726
|
+
plan.task_index.set_state(self, :running?)
|
|
727
|
+
@starting = false
|
|
728
|
+
@pending = false
|
|
729
|
+
@started = true
|
|
730
|
+
@running = true
|
|
731
|
+
@executable = true
|
|
732
|
+
end
|
|
733
|
+
|
|
734
|
+
if event.success?
|
|
735
|
+
plan.task_index.add_state(self, :success?)
|
|
736
|
+
@success = true
|
|
737
|
+
elsif event.failure?
|
|
738
|
+
plan.task_index.add_state(self, :failed?)
|
|
739
|
+
@failed = true
|
|
740
|
+
@failure_reason ||= event
|
|
1197
741
|
@failure_event ||= event
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
742
|
+
end
|
|
743
|
+
|
|
744
|
+
if event.terminal?
|
|
745
|
+
@terminal_event ||= event
|
|
746
|
+
end
|
|
747
|
+
|
|
748
|
+
if event.symbol == :stop
|
|
749
|
+
plan.task_index.remove_state(self, :running?)
|
|
750
|
+
plan.task_index.add_state(self, :finished?)
|
|
751
|
+
@running = false
|
|
752
|
+
@finishing = false
|
|
753
|
+
@finished = true
|
|
754
|
+
@executable = false
|
|
755
|
+
end
|
|
756
|
+
end
|
|
1209
757
|
|
|
1210
|
-
|
|
758
|
+
# List of EventGenerator objects bound to this task
|
|
1211
759
|
attr_reader :bound_events
|
|
1212
760
|
|
|
1213
|
-
#
|
|
1214
|
-
# emit(event_model, *context) => self
|
|
761
|
+
# Returns the event generator by its name
|
|
1215
762
|
#
|
|
1216
|
-
#
|
|
1217
|
-
#
|
|
1218
|
-
|
|
763
|
+
# @param [Symbol] name the event name
|
|
764
|
+
# @return [TaskEventGenerator,nil]
|
|
765
|
+
def find_event(name)
|
|
766
|
+
bound_events[name] ||
|
|
767
|
+
bound_events[event_model(name).symbol]
|
|
768
|
+
end
|
|
769
|
+
|
|
770
|
+
# Returns the event generator by its name or model
|
|
1219
771
|
#
|
|
772
|
+
# @param [Symbol] name the event name
|
|
773
|
+
# @return [TaskEventGenerator,nil]
|
|
774
|
+
# @raise [ArgumentError] if the event does not exist
|
|
775
|
+
def event(event_model)
|
|
776
|
+
if event = find_event(event_model)
|
|
777
|
+
event
|
|
778
|
+
else
|
|
779
|
+
raise ArgumentError, "cannot find #{event_model} in the set of bound events in #{self}. Known events are #{bound_events}."
|
|
780
|
+
end
|
|
781
|
+
end
|
|
782
|
+
|
|
783
|
+
# @!group Deprecated Event API
|
|
784
|
+
|
|
785
|
+
# @deprecated use {TaskEventGenerator#emit} instead (e.g. task.start_event.emit)
|
|
1220
786
|
def emit(event_model, *context)
|
|
787
|
+
Roby.warn_deprecated "Roby::Task#emit(event_name) is deprecated, use EventGenerator#emit (e.g. task.start_event.emit or task.event(:start).emit)"
|
|
1221
788
|
event(event_model).emit(*context)
|
|
1222
789
|
self
|
|
1223
790
|
end
|
|
1224
791
|
|
|
1225
|
-
# Returns the TaskEventGenerator which describes the required event
|
|
1226
|
-
# model. +event_model+ can either be an event name or an Event class.
|
|
1227
|
-
def event(event_model)
|
|
1228
|
-
unless event = bound_events[event_model]
|
|
1229
|
-
event_model = self.event_model(event_model)
|
|
1230
|
-
unless event = bound_events[event_model.symbol]
|
|
1231
|
-
raise "cannot find #{event_model.symbol.inspect} in the set of bound events in #{self}. Known events are #{bound_events}."
|
|
1232
|
-
end
|
|
1233
|
-
end
|
|
1234
|
-
event
|
|
1235
|
-
end
|
|
1236
|
-
|
|
1237
|
-
# call-seq:
|
|
1238
|
-
# on(event) { |event| ... }
|
|
1239
|
-
#
|
|
1240
|
-
# Defines an event handler for the given event.
|
|
1241
|
-
def on(event_model, to = nil, *to_task_events, &user_handler)
|
|
1242
|
-
if to
|
|
1243
|
-
Roby.warn_deprecated "on(event_name, task, target_events) has been replaced by #signals"
|
|
1244
|
-
elsif !(to || user_handler)
|
|
1245
|
-
raise ArgumentError, "you must provide either a task or an event handler (got nil for both)"
|
|
1246
|
-
end
|
|
1247
792
|
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
generator.on(&user_handler)
|
|
1254
|
-
end
|
|
793
|
+
# @deprecated use {TaskEventGenerator#on} on the event object, e.g.
|
|
794
|
+
# task.start_event.on { |event| ... }
|
|
795
|
+
def on(event_model, options = Hash.new, &user_handler)
|
|
796
|
+
Roby.warn_deprecated "Task#on is deprecated, use EventGenerator#on instead (e.g. #{event_model}_event.signals other_event)"
|
|
797
|
+
event(event_model).on(options, &user_handler)
|
|
1255
798
|
self
|
|
1256
799
|
end
|
|
1257
800
|
|
|
1258
|
-
#
|
|
1259
|
-
# signals source_event, dest_task, ev1, ev2, ev3, ...
|
|
1260
|
-
# signals source_event, dest_task, ev1, ev2, ev3, delay_options
|
|
1261
|
-
#
|
|
1262
|
-
# Creates a signal from +source_event+, which is an event name of
|
|
1263
|
-
# +self+, to the listed events of +dest_task+. The destination events
|
|
1264
|
-
# will be called when the source event is emitted.
|
|
1265
|
-
#
|
|
1266
|
-
# To simply emit target events (i.e. not calling the event's commands),
|
|
1267
|
-
# use #forwards
|
|
1268
|
-
#
|
|
1269
|
-
# Optionally, a delay can be added to the signal. +delay_options+ can be
|
|
1270
|
-
# either:
|
|
1271
|
-
# :delay => relative_delay_in_seconds
|
|
1272
|
-
# :at => absolute_time
|
|
801
|
+
# @deprecated use {TaskEventGenerator#signal} instead (e.g. task.start_event.signal other_task.stop_event)
|
|
1273
802
|
def signals(event_model, to, *to_task_events)
|
|
803
|
+
Roby.warn_deprecated "Task#signals is deprecated, use EventGenerator#signal instead (e.g. #{event_model}_event.signals other_event)"
|
|
804
|
+
|
|
1274
805
|
generator = event(event_model)
|
|
1275
806
|
if Hash === to_task_events.last
|
|
1276
807
|
delay = to_task_events.pop
|
|
1277
808
|
end
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
to_events.push delay if delay
|
|
1291
|
-
generator.signals(*to_events)
|
|
809
|
+
to_events = case to
|
|
810
|
+
when Task
|
|
811
|
+
to_task_events.map { |ev_model| to.event(ev_model) }
|
|
812
|
+
when EventGenerator then [to]
|
|
813
|
+
else
|
|
814
|
+
raise ArgumentError, "expected Task or EventGenerator, got #{to}(#{to.class}: #{to.class.ancestors})"
|
|
815
|
+
end
|
|
816
|
+
|
|
817
|
+
to_events.each do |event|
|
|
818
|
+
generator.signals event, delay
|
|
819
|
+
end
|
|
1292
820
|
self
|
|
1293
821
|
end
|
|
1294
822
|
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
823
|
+
# @deprecated use {TaskEventGenerator#forward_to} instead (e.g. task.start_event.forward_to other_task.stop_event)
|
|
824
|
+
def forward_to(event_model, to, *to_task_events)
|
|
825
|
+
Roby.warn_deprecated "Task#forward_to is deprecated, use EventGenerator#forward_to instead (e.g. #{event_model}_event.forward_to other_event)"
|
|
826
|
+
|
|
827
|
+
generator = event(event_model)
|
|
828
|
+
if Hash === to_task_events.last
|
|
829
|
+
delay = to_task_events.pop
|
|
1299
830
|
end
|
|
831
|
+
to_events = case
|
|
832
|
+
when Task
|
|
833
|
+
to_task_events.map { |ev| to.event(ev) }
|
|
834
|
+
when EventGenerator then [to]
|
|
835
|
+
else
|
|
836
|
+
raise ArgumentError, "expected Task or EventGenerator, got #{to}(#{to.class}: #{to.class.ancestors})"
|
|
837
|
+
end
|
|
1300
838
|
|
|
1301
|
-
|
|
839
|
+
to_events.each do |ev|
|
|
840
|
+
generator.forward_to ev, delay
|
|
841
|
+
end
|
|
1302
842
|
end
|
|
1303
843
|
|
|
1304
|
-
#
|
|
1305
|
-
|
|
1306
|
-
|
|
844
|
+
# @!endgroup Deprecated Event API
|
|
845
|
+
|
|
846
|
+
include MetaRuby::DSLs::FindThroughMethodMissing
|
|
847
|
+
|
|
848
|
+
# Iterates on all the events defined for this task
|
|
1307
849
|
#
|
|
1308
|
-
#
|
|
1309
|
-
#
|
|
1310
|
-
#
|
|
850
|
+
# @param [Boolean] only_wrapped For consistency with transaction
|
|
851
|
+
# proxies. Should not be used in user code.
|
|
852
|
+
# @yield [generator]
|
|
853
|
+
# @yieldparam [TaskEventGenerator] generator the generators that are
|
|
854
|
+
# tied to this task
|
|
855
|
+
# @return self
|
|
856
|
+
def each_event(only_wrapped = true)
|
|
857
|
+
return enum_for(__method__, only_wrapped) if !block_given?
|
|
858
|
+
for ev in bound_events.each_value
|
|
859
|
+
yield(ev)
|
|
860
|
+
end
|
|
861
|
+
self
|
|
862
|
+
end
|
|
863
|
+
alias :each_plan_child :each_event
|
|
864
|
+
|
|
865
|
+
# Returns this task's set of terminal events.
|
|
1311
866
|
#
|
|
1312
|
-
#
|
|
1313
|
-
#
|
|
867
|
+
# A terminal event is an event whose emission announces the end of the
|
|
868
|
+
# task. In most case, it is an event which is forwarded directly on
|
|
869
|
+
# indirectly to +stop+.
|
|
1314
870
|
#
|
|
1315
|
-
#
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
871
|
+
# @return [Array<TaskEventGenerator>]
|
|
872
|
+
def terminal_events
|
|
873
|
+
bound_events.each_value.find_all { |ev| ev.terminal? }
|
|
874
|
+
end
|
|
875
|
+
|
|
876
|
+
# (see Models::Task#event_model)
|
|
877
|
+
def event_model(model)
|
|
878
|
+
self.model.event_model(model)
|
|
879
|
+
end
|
|
880
|
+
|
|
881
|
+
def to_s # :nodoc:
|
|
882
|
+
s = "#{name}<id:#{droby_id.id}>(#{arguments})"
|
|
883
|
+
id = owners.map do |owner|
|
|
884
|
+
next if plan && (owner == plan.local_owner)
|
|
885
|
+
sibling = remote_siblings[owner]
|
|
886
|
+
"#{sibling ? Object.address_from_id(sibling.ref).to_s(16) : 'nil'}@#{owner.remote_name}"
|
|
1323
887
|
end
|
|
888
|
+
unless id.empty?
|
|
889
|
+
s << "[" << id.join(",") << "]"
|
|
890
|
+
end
|
|
891
|
+
s
|
|
892
|
+
end
|
|
1324
893
|
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
else
|
|
1335
|
-
raise ArgumentError, "expected Task or EventGenerator, got #{to}(#{to.class}: #{to.class.ancestors})"
|
|
1336
|
-
end
|
|
1337
|
-
|
|
1338
|
-
to_events.each do |ev|
|
|
1339
|
-
generator.forward_to ev, delay
|
|
1340
|
-
end
|
|
1341
|
-
end
|
|
1342
|
-
|
|
1343
|
-
# :stopdoc:
|
|
1344
|
-
attr_accessor :calling_event
|
|
1345
|
-
|
|
1346
|
-
def method_missing(name, *args, &block) # :nodoc:
|
|
1347
|
-
if calling_event && calling_event.respond_to?(name)
|
|
1348
|
-
calling_event.send(name, *args, &block)
|
|
1349
|
-
else
|
|
1350
|
-
super
|
|
1351
|
-
end
|
|
1352
|
-
rescue NameError => e
|
|
1353
|
-
raise e, e.message, caller(1)
|
|
1354
|
-
rescue NoMethodError => e
|
|
1355
|
-
raise e, e.message, caller(1)
|
|
1356
|
-
end
|
|
1357
|
-
|
|
1358
|
-
# Declares that this task model provides the given interface. +model+
|
|
1359
|
-
# must be an instance of TaskModelTag
|
|
1360
|
-
def self.provides(model)
|
|
1361
|
-
include model
|
|
1362
|
-
end
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
@@event_command_id = 0
|
|
1366
|
-
def self.allocate_event_command_id # :nodoc:
|
|
1367
|
-
@@event_command_id += 1
|
|
1368
|
-
end
|
|
1369
|
-
# :startdoc:
|
|
1370
|
-
|
|
1371
|
-
# call-seq:
|
|
1372
|
-
# self.event(name, options = nil) { ... } => event class or nil
|
|
1373
|
-
#
|
|
1374
|
-
# Define a new event in this task.
|
|
1375
|
-
#
|
|
1376
|
-
# <b>Available options</b>
|
|
1377
|
-
#
|
|
1378
|
-
# <tt>command</tt>::
|
|
1379
|
-
# either true, false or an event command for the new event. In that
|
|
1380
|
-
# latter case, the command is an object which must respond to
|
|
1381
|
-
# #to_proc. If it is true, a default handler is defined which simply
|
|
1382
|
-
# emits the event. If a block is given, it is used as the event
|
|
1383
|
-
# command.
|
|
1384
|
-
#
|
|
1385
|
-
# <tt>terminal</tt>::
|
|
1386
|
-
# set to true if this event is a terminal event, i.e. if its emission
|
|
1387
|
-
# means that the task has finished.
|
|
1388
|
-
#
|
|
1389
|
-
# <tt>model</tt>::
|
|
1390
|
-
# base class for the event model (see "Event models" below). The default is the
|
|
1391
|
-
# TaskEvent class
|
|
1392
|
-
#
|
|
1393
|
-
# <b>Event models</b>
|
|
1394
|
-
#
|
|
1395
|
-
# When a task event (for instance +start+) is emitted, a Roby::Event
|
|
1396
|
-
# object is created to describe the information related to this
|
|
1397
|
-
# emission (time, sources, context information, ...). Task.event
|
|
1398
|
-
# defines a specific event model MyTask::MyEvent for each task event
|
|
1399
|
-
# with name :my_event. This specific model is by default a subclass of
|
|
1400
|
-
# Roby::TaskEvent, but it is possible to override that by using the +model+
|
|
1401
|
-
# option.
|
|
1402
|
-
def self.event(ev, options = Hash.new, &block)
|
|
1403
|
-
options = validate_options(options, :command => nil, :terminal => nil, :model => TaskEvent)
|
|
1404
|
-
|
|
1405
|
-
ev_s = ev.to_s
|
|
1406
|
-
ev = ev.to_sym
|
|
1407
|
-
|
|
1408
|
-
if !options.has_key?(:command)
|
|
1409
|
-
if block
|
|
1410
|
-
define_method("event_command_#{ev_s}", &block)
|
|
1411
|
-
method = instance_method("event_command_#{ev_s}")
|
|
1412
|
-
end
|
|
1413
|
-
|
|
1414
|
-
if method
|
|
1415
|
-
check_arity(method, 1)
|
|
1416
|
-
options[:command] = lambda do |dst_task, *event_context|
|
|
1417
|
-
begin
|
|
1418
|
-
dst_task.calling_event = dst_task.event(ev)
|
|
1419
|
-
method.bind(dst_task).call(*event_context)
|
|
1420
|
-
ensure
|
|
1421
|
-
dst_task.calling_event = nil
|
|
1422
|
-
end
|
|
1423
|
-
end
|
|
1424
|
-
end
|
|
1425
|
-
end
|
|
1426
|
-
validate_event_definition_request(ev, options)
|
|
1427
|
-
|
|
1428
|
-
command_handler = options[:command] if options[:command].respond_to?(:call)
|
|
1429
|
-
|
|
1430
|
-
# Define the event class
|
|
1431
|
-
task_klass = self
|
|
1432
|
-
new_event = Class.new(options[:model]) do
|
|
1433
|
-
@terminal = options[:terminal]
|
|
1434
|
-
@symbol = ev
|
|
1435
|
-
@command_handler = command_handler
|
|
1436
|
-
|
|
1437
|
-
define_method(:name) { "#{task.name}::#{ev_s.camelcase(true)}" }
|
|
1438
|
-
singleton_class.class_eval do
|
|
1439
|
-
attr_reader :command_handler
|
|
1440
|
-
define_method(:name) { "#{task_klass.name}::#{ev_s.camelcase(true)}" }
|
|
1441
|
-
def to_s; name end
|
|
894
|
+
def pretty_print(pp, with_owners = true) # :nodoc:
|
|
895
|
+
pp.text "#{model.name}:0x#{self.address.to_s(16)}"
|
|
896
|
+
if with_owners
|
|
897
|
+
pp.nest(2) do
|
|
898
|
+
pp.breakable
|
|
899
|
+
pp.text "owners: "
|
|
900
|
+
pp.nest(2) do
|
|
901
|
+
pp.seplist(owners) { |r| pp.text r.to_s }
|
|
902
|
+
end
|
|
1442
903
|
end
|
|
1443
904
|
end
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
if setup_terminal_handler
|
|
1453
|
-
forward(new_event => :stop)
|
|
1454
|
-
end
|
|
1455
|
-
const_set(ev_s.camelcase(true), new_event)
|
|
1456
|
-
|
|
1457
|
-
if options[:command]
|
|
1458
|
-
# check that the supplied command handler can take two arguments
|
|
1459
|
-
check_arity(command_handler, 2) if command_handler
|
|
1460
|
-
|
|
1461
|
-
# define #call on the event model
|
|
1462
|
-
new_event.singleton_class.class_eval do
|
|
1463
|
-
if command_handler
|
|
1464
|
-
define_method(:call, &command_handler)
|
|
1465
|
-
else
|
|
1466
|
-
def call(task, context) # :nodoc:
|
|
1467
|
-
task.emit(symbol, *context)
|
|
1468
|
-
end
|
|
1469
|
-
end
|
|
905
|
+
pp.nest(2) do
|
|
906
|
+
pp.breakable
|
|
907
|
+
pp.text "arguments: "
|
|
908
|
+
if !arguments.empty?
|
|
909
|
+
pp.nest(2) do
|
|
910
|
+
pp.breakable
|
|
911
|
+
arguments.pretty_print(pp)
|
|
912
|
+
end
|
|
1470
913
|
end
|
|
914
|
+
end
|
|
915
|
+
end
|
|
916
|
+
|
|
917
|
+
# True if this task is a null task. See NullTask.
|
|
918
|
+
def null?; false end
|
|
919
|
+
# Converts this object into a task object
|
|
920
|
+
def to_task; self end
|
|
921
|
+
|
|
922
|
+
# Event emitted when the task is started
|
|
923
|
+
#
|
|
924
|
+
# It is controlable by default, its command simply emitting the start
|
|
925
|
+
# event
|
|
926
|
+
event :start, command: true
|
|
1471
927
|
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
928
|
+
# Event emitted when the task has stopped
|
|
929
|
+
#
|
|
930
|
+
# It is not controlable by default. If the task can be stopped without
|
|
931
|
+
# any specific action, call {Models::Task#terminates} on the task model. If it
|
|
932
|
+
# needs specific actions, define a controlable failed event and call
|
|
933
|
+
# {Models::Task#interruptible}
|
|
934
|
+
#
|
|
935
|
+
# @example task with simple termination
|
|
936
|
+
# class MyTask < Roby::Task
|
|
937
|
+
# terminates
|
|
938
|
+
# end
|
|
939
|
+
#
|
|
940
|
+
# @example task with complex termination
|
|
941
|
+
# class Mytask < Roby::Task
|
|
942
|
+
# event :failed do
|
|
943
|
+
# # Terminate the underlying process
|
|
944
|
+
# end
|
|
945
|
+
# interruptible
|
|
946
|
+
# end
|
|
947
|
+
event :stop
|
|
948
|
+
|
|
949
|
+
# Event emitted when the task has successfully finished
|
|
950
|
+
#
|
|
951
|
+
# It is obviously forwarded to {#stop_event}
|
|
952
|
+
event :success, terminal: true
|
|
953
|
+
|
|
954
|
+
# Event emitted when the task has finished without performing its duty
|
|
955
|
+
#
|
|
956
|
+
# It is obviously forwarded to {#stop_event}
|
|
957
|
+
event :failed, terminal: true
|
|
958
|
+
|
|
959
|
+
# Event emitted when the task's underlying
|
|
960
|
+
# {TaskStructure::ExecutionAgent#execution_agent} finished
|
|
961
|
+
# while the task was running
|
|
962
|
+
#
|
|
963
|
+
# It is obviously forwarded to {#failed_event}
|
|
964
|
+
event :aborted
|
|
965
|
+
forward aborted: :failed
|
|
966
|
+
|
|
967
|
+
# Event emitted when a task internal code block ({Models::Task#on} handler,
|
|
968
|
+
# {Models::Task#poll} block) raised an exception
|
|
969
|
+
#
|
|
970
|
+
# It signals {#stop_event} if {#stop_event} is controlable
|
|
971
|
+
event :internal_error
|
|
972
|
+
|
|
973
|
+
class InternalError
|
|
974
|
+
# Mark the InternalError event as a failure event, even if it is not
|
|
975
|
+
# forwarded to the stop event at the model level
|
|
976
|
+
def failure?
|
|
977
|
+
true
|
|
1477
978
|
end
|
|
979
|
+
end
|
|
1478
980
|
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
981
|
+
on :internal_error do |error|
|
|
982
|
+
if error.context
|
|
983
|
+
@failure_reason = error.context.first
|
|
984
|
+
end
|
|
985
|
+
end
|
|
986
|
+
|
|
987
|
+
# The internal data for this task
|
|
988
|
+
#
|
|
989
|
+
# @see data= updated_data updated_data_event
|
|
990
|
+
attr_reader :data
|
|
991
|
+
|
|
992
|
+
# Sets the internal data value for this task. This calls the
|
|
993
|
+
# {#updated_data} hook, and emits {#updated_data_event} if the task is running.
|
|
994
|
+
def data=(value)
|
|
995
|
+
@data = value
|
|
996
|
+
updated_data
|
|
997
|
+
emit :updated_data if running?
|
|
998
|
+
end
|
|
999
|
+
|
|
1000
|
+
# This hook is called whenever the internal data of this task is
|
|
1001
|
+
# updated. See #data, #data= and the +updated_data+ event
|
|
1002
|
+
def updated_data
|
|
1003
|
+
end
|
|
1004
|
+
event :updated_data, command: false
|
|
1005
|
+
|
|
1006
|
+
# Checks if +task+ is in the same execution state than +self+
|
|
1007
|
+
# Returns true if they are either both running or both pending
|
|
1008
|
+
def compatible_state?(task)
|
|
1009
|
+
finished? || !(running? ^ task.running?)
|
|
1010
|
+
end
|
|
1011
|
+
|
|
1012
|
+
# @api private
|
|
1013
|
+
#
|
|
1014
|
+
# The set of instance-level execute blocks
|
|
1015
|
+
#
|
|
1016
|
+
# @return [Array<InstanceHandler>]
|
|
1017
|
+
attr_reader :execute_handlers
|
|
1018
|
+
|
|
1019
|
+
# @api private
|
|
1020
|
+
#
|
|
1021
|
+
# The set of instance-level poll blocks
|
|
1022
|
+
#
|
|
1023
|
+
# @return [Array<InstanceHandler>]
|
|
1024
|
+
attr_reader :poll_handlers
|
|
1025
|
+
|
|
1026
|
+
# Add a block that is going to be executed once, either at the next
|
|
1027
|
+
# cycle if the task is already running, or when the task is started
|
|
1028
|
+
#
|
|
1029
|
+
# @macro InstanceHandlerOptions
|
|
1030
|
+
# @return [void]
|
|
1031
|
+
def execute(options = Hash.new, &block)
|
|
1032
|
+
default_on_replace = if abstract? then :copy else :drop end
|
|
1033
|
+
options = InstanceHandler.validate_options(options, on_replace: default_on_replace)
|
|
1034
|
+
|
|
1035
|
+
check_arity(block, 1)
|
|
1036
|
+
@execute_handlers << InstanceHandler.new(block, (options[:on_replace] == :copy))
|
|
1037
|
+
ensure_poll_handler_called
|
|
1038
|
+
end
|
|
1039
|
+
|
|
1040
|
+
# Adds a new poll block on this instance
|
|
1041
|
+
#
|
|
1042
|
+
# @macro InstanceHandlerOptions
|
|
1043
|
+
# @yieldparam [Roby::Task] task the task on which the poll block is
|
|
1044
|
+
# executed. It might be different than the one on which it has been
|
|
1045
|
+
# added because of replacements.
|
|
1046
|
+
# @return [Object] an ID that can be used in {#remove_poll_handler}
|
|
1047
|
+
def poll(options = Hash.new, &block)
|
|
1048
|
+
default_on_replace = if abstract? then :copy else :drop end
|
|
1049
|
+
options = InstanceHandler.validate_options(options, on_replace: default_on_replace)
|
|
1050
|
+
|
|
1051
|
+
check_arity(block, 1)
|
|
1052
|
+
@poll_handlers << (handler = InstanceHandler.new(block, (options[:on_replace] == :copy)))
|
|
1053
|
+
ensure_poll_handler_called
|
|
1054
|
+
handler
|
|
1055
|
+
end
|
|
1056
|
+
|
|
1057
|
+
# Remove a poll handler from this instance
|
|
1058
|
+
#
|
|
1059
|
+
# @param [Object] handler the ID returned by {#poll}
|
|
1060
|
+
# @return [void]
|
|
1061
|
+
def remove_poll_handler(handler)
|
|
1062
|
+
@poll_handlers.delete(handler)
|
|
1063
|
+
end
|
|
1064
|
+
|
|
1065
|
+
# @api private
|
|
1066
|
+
#
|
|
1067
|
+
# Helper for {#execute} and {#poll} that ensures that the {#do_poll} is
|
|
1068
|
+
# called by the execution engine
|
|
1069
|
+
def ensure_poll_handler_called
|
|
1070
|
+
if !transaction_proxy? && running?
|
|
1071
|
+
@poll_handler_id ||= execution_engine.add_propagation_handler(description: "poll block for #{self}", type: :external_events, &method(:do_poll))
|
|
1483
1072
|
end
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1073
|
+
end
|
|
1074
|
+
|
|
1075
|
+
# @api private
|
|
1076
|
+
#
|
|
1077
|
+
# Internal method used to register the poll blocks in the engine
|
|
1078
|
+
# execution cycle
|
|
1079
|
+
def do_poll(plan) # :nodoc:
|
|
1080
|
+
return unless self_owned?
|
|
1081
|
+
# Don't call if we are terminating
|
|
1082
|
+
return if finished?
|
|
1083
|
+
# Don't call if we already had an error in the poll block
|
|
1084
|
+
return if event(:internal_error).emitted?
|
|
1085
|
+
|
|
1086
|
+
begin
|
|
1087
|
+
while execute_block = @execute_handlers.pop
|
|
1088
|
+
execute_block.block.call(self)
|
|
1089
|
+
end
|
|
1090
|
+
|
|
1091
|
+
if respond_to?(:poll_handler)
|
|
1092
|
+
poll_handler
|
|
1093
|
+
end
|
|
1094
|
+
|
|
1095
|
+
if machine = state_machine
|
|
1096
|
+
machine.do_poll(self)
|
|
1487
1097
|
end
|
|
1098
|
+
|
|
1099
|
+
@poll_handlers.each do |poll_block|
|
|
1100
|
+
poll_block.block.call(self)
|
|
1101
|
+
end
|
|
1102
|
+
rescue LocalizedError => e
|
|
1103
|
+
execution_engine.add_error(e)
|
|
1104
|
+
rescue Exception => e
|
|
1105
|
+
execution_engine.add_error(CodeError.new(e, self))
|
|
1488
1106
|
end
|
|
1107
|
+
end
|
|
1108
|
+
|
|
1109
|
+
on :start do |ev|
|
|
1110
|
+
engine = execution_engine
|
|
1489
1111
|
|
|
1490
|
-
|
|
1112
|
+
# Register poll:
|
|
1113
|
+
# - single class poll_handler add be class method Task#poll
|
|
1114
|
+
# - additional instance poll_handler added by instance method poll
|
|
1115
|
+
# - polling as defined in state of the state_machine, i.e. substates of running
|
|
1116
|
+
if respond_to?(:poll_handler) || !poll_handlers.empty? || state_machine
|
|
1117
|
+
@poll_handler_id = engine.add_propagation_handler(description: "poll block of #{self}", type: :external_events, &method(:do_poll))
|
|
1118
|
+
end
|
|
1119
|
+
end
|
|
1120
|
+
|
|
1121
|
+
on :stop do |ev|
|
|
1122
|
+
if @poll_handler_id
|
|
1123
|
+
execution_engine.remove_propagation_handler(@poll_handler_id)
|
|
1124
|
+
end
|
|
1491
1125
|
end
|
|
1492
1126
|
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1127
|
+
# Declares that this fault response table should be made active when
|
|
1128
|
+
# this task starts, and deactivated when it ends
|
|
1129
|
+
def use_fault_response_table(table_model, arguments = Hash.new)
|
|
1130
|
+
arguments = table_model.validate_arguments(arguments)
|
|
1131
|
+
|
|
1132
|
+
table = nil
|
|
1133
|
+
execute do |task|
|
|
1134
|
+
table = task.plan.use_fault_response_table(table_model, arguments)
|
|
1135
|
+
end
|
|
1136
|
+
stop_event.on do |event|
|
|
1137
|
+
plan.remove_fault_response_table(table)
|
|
1496
1138
|
end
|
|
1139
|
+
end
|
|
1497
1140
|
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1141
|
+
# Whether this task instance provides a set of models and arguments
|
|
1142
|
+
#
|
|
1143
|
+
# The fullfills? predicate checks if this task can be used
|
|
1144
|
+
# to fullfill the need of the given model and arguments
|
|
1145
|
+
# The default is to check if
|
|
1146
|
+
# * the needed task model is an ancestor of this task
|
|
1147
|
+
# * the task
|
|
1148
|
+
# * +args+ is included in the task arguments
|
|
1149
|
+
def fullfills?(models, args = nil)
|
|
1150
|
+
if models.kind_of?(Roby::Task)
|
|
1151
|
+
args ||= models.meaningful_arguments
|
|
1152
|
+
models = models.model
|
|
1153
|
+
end
|
|
1154
|
+
if !model.fullfills?(models)
|
|
1155
|
+
return false
|
|
1156
|
+
end
|
|
1157
|
+
|
|
1158
|
+
if args
|
|
1159
|
+
args.each do |key, name|
|
|
1160
|
+
if self.arguments[key] != name
|
|
1161
|
+
return false
|
|
1162
|
+
end
|
|
1501
1163
|
end
|
|
1502
|
-
options[:terminal] = true
|
|
1503
1164
|
end
|
|
1504
1165
|
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1166
|
+
true
|
|
1167
|
+
end
|
|
1168
|
+
|
|
1169
|
+
# True if this model requires an argument named key and that argument is
|
|
1170
|
+
# set
|
|
1171
|
+
def has_argument?(key)
|
|
1172
|
+
self.arguments.set?(key)
|
|
1173
|
+
end
|
|
1174
|
+
|
|
1175
|
+
# True if self can be used to replace target
|
|
1176
|
+
def can_replace?(target)
|
|
1177
|
+
fullfills?(*target.fullfilled_model)
|
|
1178
|
+
end
|
|
1179
|
+
|
|
1180
|
+
# Tests if a task could be merged within self
|
|
1181
|
+
#
|
|
1182
|
+
# Unlike a replacement, a merge implies that self is modified to match
|
|
1183
|
+
# both its current role and the target's role. Roby has no built-in
|
|
1184
|
+
# merge logic (no merge method). This method is a helper for Roby
|
|
1185
|
+
# extensions that implement such a scheme, to check for attributes
|
|
1186
|
+
# common to all tasks that would forbid a merge
|
|
1187
|
+
def can_merge?(target)
|
|
1188
|
+
if defined?(super) && !super
|
|
1189
|
+
return
|
|
1190
|
+
end
|
|
1191
|
+
|
|
1192
|
+
if finished? || target.finished?
|
|
1193
|
+
return false
|
|
1194
|
+
end
|
|
1195
|
+
|
|
1196
|
+
if !model.can_merge?(target.model)
|
|
1197
|
+
return false
|
|
1198
|
+
end
|
|
1199
|
+
|
|
1200
|
+
target.arguments.each_assigned_argument do |key, val|
|
|
1201
|
+
if arguments.set?(key) && arguments[key] != val
|
|
1202
|
+
return false
|
|
1513
1203
|
end
|
|
1514
1204
|
end
|
|
1205
|
+
true
|
|
1515
1206
|
end
|
|
1516
1207
|
|
|
1517
|
-
#
|
|
1518
|
-
|
|
1208
|
+
# "Simply" mark this task as terminated. This is meant to be used on
|
|
1209
|
+
# quarantined tasks in tests.
|
|
1210
|
+
#
|
|
1211
|
+
# Do not use this unless you really know what you are doing
|
|
1212
|
+
def forcefully_terminate
|
|
1213
|
+
update_task_status(event(:stop).new([]))
|
|
1214
|
+
end
|
|
1519
1215
|
|
|
1520
|
-
|
|
1521
|
-
@__enum_events__ ||= enum_for(:each_event)
|
|
1522
|
-
end
|
|
1216
|
+
include ExceptionHandlingObject
|
|
1523
1217
|
|
|
1524
|
-
#
|
|
1525
|
-
# task.each_event { |event_object| ... } => task
|
|
1218
|
+
# @api private
|
|
1526
1219
|
#
|
|
1527
|
-
#
|
|
1528
|
-
|
|
1529
|
-
#
|
|
1530
|
-
#
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1220
|
+
# Handles the given exception.
|
|
1221
|
+
#
|
|
1222
|
+
# In addition to the exception handlers provided by
|
|
1223
|
+
# {ExceptionHandlingObject}, it checks for repair tasks (as defined by
|
|
1224
|
+
# TaskStructure::ErrorHandling)
|
|
1225
|
+
#
|
|
1226
|
+
# @param [ExecutionException] e
|
|
1227
|
+
def handle_exception(e)
|
|
1228
|
+
return if !plan
|
|
1229
|
+
|
|
1230
|
+
tasks = find_all_matching_repair_tasks(e)
|
|
1231
|
+
return super if tasks.empty?
|
|
1232
|
+
if !tasks.any? { |t| t.running? }
|
|
1233
|
+
tasks.first.start!
|
|
1234
|
+
end
|
|
1235
|
+
true
|
|
1536
1236
|
end
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
# it is an event which is forwarded directly on indirectly to +stop+.
|
|
1542
|
-
def terminal_events
|
|
1543
|
-
bound_events.values.find_all { |ev| ev.terminal? }
|
|
1544
|
-
end
|
|
1545
|
-
|
|
1546
|
-
# Get the list of terminal events for this task model
|
|
1547
|
-
def self.terminal_events
|
|
1548
|
-
enum_events.find_all { |_, e| e.terminal? }.
|
|
1549
|
-
map { |_, e| e }
|
|
1550
|
-
end
|
|
1551
|
-
|
|
1552
|
-
# Get the event model for +event+
|
|
1553
|
-
def event_model(model); self.model.event_model(model) end
|
|
1554
|
-
|
|
1555
|
-
# Find the event class for +event+, or nil if +event+ is not an event name for this model
|
|
1556
|
-
def self.find_event_model(name)
|
|
1557
|
-
name = name.to_sym
|
|
1558
|
-
each_event { |sym, e| return e if sym == name }
|
|
1559
|
-
nil
|
|
1560
|
-
end
|
|
1561
|
-
|
|
1562
|
-
# Checks that all events in +events+ are valid events for this task.
|
|
1563
|
-
# The requested events can be either an event name (symbol or string)
|
|
1564
|
-
# or an event class
|
|
1565
|
-
#
|
|
1566
|
-
# Returns the corresponding array of event classes
|
|
1567
|
-
def self.event_model(model_def) #:nodoc:
|
|
1568
|
-
if model_def.respond_to?(:to_sym)
|
|
1569
|
-
ev_model = find_event_model(model_def.to_sym)
|
|
1570
|
-
unless ev_model
|
|
1571
|
-
all_events = enum_events.map { |name, _| name }
|
|
1572
|
-
raise ArgumentError, "#{model_def} is not an event of #{name}: #{all_events}" unless ev_model
|
|
1573
|
-
end
|
|
1574
|
-
elsif model_def.respond_to?(:has_ancestor?) && model_def.has_ancestor?(TaskEvent)
|
|
1575
|
-
# Check that model_def is an event class for us
|
|
1576
|
-
ev_model = find_event_model(model_def.symbol)
|
|
1577
|
-
if !ev_model
|
|
1578
|
-
raise ArgumentError, "no #{model_def.symbol} event in #{name}"
|
|
1579
|
-
elsif ev_model != model_def
|
|
1580
|
-
raise ArgumentError, "the event model #{model_def} is not a model for #{name} (found #{ev_model} with the same name)"
|
|
1581
|
-
end
|
|
1582
|
-
else
|
|
1583
|
-
raise ArgumentError, "wanted either a symbol or an event class, got #{model_def}"
|
|
1584
|
-
end
|
|
1585
|
-
|
|
1586
|
-
ev_model
|
|
1587
|
-
end
|
|
1588
|
-
|
|
1589
|
-
class << self
|
|
1590
|
-
# Checks if _name_ is a name for an event of this task
|
|
1591
|
-
alias :has_event? :find_event_model
|
|
1592
|
-
|
|
1593
|
-
private :validate_event_definition_request
|
|
1237
|
+
|
|
1238
|
+
# Lists all exception handlers attached to this task
|
|
1239
|
+
def each_exception_handler(&iterator)
|
|
1240
|
+
model.each_exception_handler(&iterator)
|
|
1594
1241
|
end
|
|
1595
|
-
|
|
1596
|
-
# call-seq:
|
|
1597
|
-
# signal(name1 => name2, name3 => [name4, name5])
|
|
1598
|
-
#
|
|
1599
|
-
# Establish model-level signals between events of that task. These
|
|
1600
|
-
# signals will be established on all the instances of this task model
|
|
1601
|
-
# (and its subclasses).
|
|
1602
|
-
def self.signal(mappings)
|
|
1603
|
-
mappings.each do |from, to|
|
|
1604
|
-
from = event_model(from)
|
|
1605
|
-
targets = Array[*to].map { |ev| event_model(ev) }
|
|
1606
|
-
|
|
1607
|
-
if from.terminal?
|
|
1608
|
-
non_terminal = targets.find_all { |ev| !ev.terminal? }
|
|
1609
|
-
if !non_terminal.empty?
|
|
1610
|
-
raise ArgumentError, "trying to establish a forwarding relation from the terminal event #{from} to the non-terminal events #{non_terminal}"
|
|
1611
|
-
end
|
|
1612
|
-
end
|
|
1613
|
-
non_controlable = targets.find_all { |ev| !ev.controlable? }
|
|
1614
|
-
if !non_controlable.empty?
|
|
1615
|
-
raise ArgumentError, "trying to signal #{non_controlable.join(" ")} which is/are not controlable"
|
|
1616
|
-
end
|
|
1617
1242
|
|
|
1618
|
-
|
|
1243
|
+
# @api private
|
|
1244
|
+
#
|
|
1245
|
+
# Validates that both self and the child object are owned by the local
|
|
1246
|
+
# instance
|
|
1247
|
+
def add_child_object(child, type, info)
|
|
1248
|
+
unless read_write? && child.read_write?
|
|
1249
|
+
raise OwnershipError, "cannot add a relation between tasks we don't own. #{self} by #{owners.to_a} and #{child} is owned by #{child.owners.to_a}"
|
|
1619
1250
|
end
|
|
1620
|
-
|
|
1251
|
+
|
|
1252
|
+
super
|
|
1621
1253
|
end
|
|
1622
1254
|
|
|
1623
|
-
#
|
|
1624
|
-
# on(event_name) { |event| ... }
|
|
1255
|
+
# @api private
|
|
1625
1256
|
#
|
|
1626
|
-
#
|
|
1627
|
-
#
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1257
|
+
# This method is called during the commit process to apply changes
|
|
1258
|
+
# stored in a proxy
|
|
1259
|
+
def commit_transaction
|
|
1260
|
+
super
|
|
1261
|
+
|
|
1262
|
+
arguments.dup.each do |key, value|
|
|
1263
|
+
if value.respond_to?(:transaction_proxy?) && value.transaction_proxy?
|
|
1264
|
+
arguments.update!(key, value.__getobj__)
|
|
1265
|
+
end
|
|
1266
|
+
end
|
|
1267
|
+
end
|
|
1268
|
+
|
|
1269
|
+
# Create a new task of the same model and with the same arguments
|
|
1270
|
+
# than this one. Insert this task in the plan and make it replace
|
|
1271
|
+
# the fresh one.
|
|
1272
|
+
#
|
|
1273
|
+
# See Plan#respawn
|
|
1274
|
+
def respawn
|
|
1275
|
+
plan.respawn(self)
|
|
1276
|
+
end
|
|
1277
|
+
|
|
1278
|
+
# @api private
|
|
1279
|
+
def compute_subplan_replacement_operation(object)
|
|
1280
|
+
edges, edges_candidates = [], []
|
|
1281
|
+
subplan_tasks = Set[self, object]
|
|
1282
|
+
parent_tasks = Set.new
|
|
1283
|
+
plan.each_task_relation_graph do |g|
|
|
1284
|
+
next if g.strong?
|
|
1285
|
+
rel = g.class
|
|
1286
|
+
|
|
1287
|
+
each_in_neighbour_merged(rel, intrusive: true) do |parent|
|
|
1288
|
+
parent_tasks << parent
|
|
1289
|
+
edges << [g, parent, self, parent, object]
|
|
1290
|
+
end
|
|
1291
|
+
object.each_in_neighbour_merged(rel, intrusive: true) do |parent|
|
|
1292
|
+
parent_tasks << parent
|
|
1293
|
+
end
|
|
1294
|
+
|
|
1295
|
+
if g.weak?
|
|
1296
|
+
each_out_neighbour_merged(rel, intrusive: true) do |child|
|
|
1297
|
+
edges_candidates << [child, [g, self, child, object, child]]
|
|
1298
|
+
end
|
|
1299
|
+
else
|
|
1300
|
+
object.each_out_neighbour_merged(rel, intrusive: true) do |child|
|
|
1301
|
+
subplan_tasks << child
|
|
1302
|
+
end
|
|
1303
|
+
each_out_neighbour_merged(rel, intrusive: true) do |child|
|
|
1304
|
+
subplan_tasks << child
|
|
1305
|
+
end
|
|
1306
|
+
end
|
|
1632
1307
|
end
|
|
1633
1308
|
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1309
|
+
plan.each_event_relation_graph do |g|
|
|
1310
|
+
next if g.strong?
|
|
1311
|
+
rel = g.class
|
|
1312
|
+
|
|
1313
|
+
model.each_event do |_, event|
|
|
1314
|
+
event = plan.each_object_in_transaction_stack(self).
|
|
1315
|
+
find { |_, o| o.find_event(event.symbol) }.
|
|
1316
|
+
last.event(event.symbol)
|
|
1317
|
+
object_event = plan.each_object_in_transaction_stack(object).
|
|
1318
|
+
find { |_, o| o.find_event(event.symbol) }.
|
|
1319
|
+
last.event(event.symbol)
|
|
1320
|
+
|
|
1321
|
+
event.each_in_neighbour_merged(rel, intrusive: false) do |_, parent|
|
|
1322
|
+
if parent.respond_to?(:task)
|
|
1323
|
+
edges_candidates <<
|
|
1324
|
+
[plan[parent.task], [g, parent, event, parent, object_event]]
|
|
1325
|
+
end
|
|
1326
|
+
end
|
|
1327
|
+
event.each_out_neighbour_merged(rel, intrusive: false) do |_, child|
|
|
1328
|
+
if child.respond_to?(:task)
|
|
1329
|
+
edges_candidates <<
|
|
1330
|
+
[plan[child.task], [g, event, child, object_event, child]]
|
|
1331
|
+
end
|
|
1332
|
+
end
|
|
1333
|
+
end
|
|
1334
|
+
end
|
|
1335
|
+
|
|
1336
|
+
edges_candidates.each do |reference_task, op|
|
|
1337
|
+
if subplan_tasks.include?(reference_task)
|
|
1338
|
+
next
|
|
1339
|
+
elsif parent_tasks.include?(reference_task)
|
|
1340
|
+
edges << op
|
|
1341
|
+
elsif plan.in_useful_subplan?(self, reference_task) || plan.in_useful_subplan?(object, reference_task)
|
|
1342
|
+
subplan_tasks << reference_task
|
|
1343
|
+
else
|
|
1344
|
+
edges << op
|
|
1345
|
+
end
|
|
1637
1346
|
end
|
|
1638
1347
|
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
from = event_model(from).symbol
|
|
1642
|
-
if user_handler
|
|
1643
|
-
method_name = "event_handler_#{from}_#{Object.address_from_id(user_handler.object_id).to_s(16)}"
|
|
1644
|
-
define_method(method_name, &user_handler)
|
|
1645
|
-
handler_sets[from] << lambda { |event| event.task.send(method_name, event) }
|
|
1646
|
-
end
|
|
1348
|
+
edges = edges.map do |g, removed_parent, removed_child, added_parent, added_child|
|
|
1349
|
+
[g, plan[removed_parent], plan[removed_child], plan[added_parent], plan[added_child]]
|
|
1647
1350
|
end
|
|
1351
|
+
edges
|
|
1648
1352
|
end
|
|
1649
1353
|
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
# relation.
|
|
1656
|
-
def self.causal_link(mappings)
|
|
1657
|
-
mappings.each do |from, to|
|
|
1658
|
-
from = event_model(from).symbol
|
|
1659
|
-
causal_link_sets[from].merge Array[*to].map { |ev| event_model(ev).symbol }.to_value_set
|
|
1354
|
+
# @api private
|
|
1355
|
+
def apply_replacement_operations(edges)
|
|
1356
|
+
edges.each do |g, removed_parent, removed_child, added_parent, added_child|
|
|
1357
|
+
info = g.edge_info(removed_parent, removed_child)
|
|
1358
|
+
g.add_relation(plan[added_parent], plan[added_child], info)
|
|
1660
1359
|
end
|
|
1661
|
-
|
|
1662
|
-
|
|
1360
|
+
edges.each do |g, removed_parent, removed_child, added_parent, added_child|
|
|
1361
|
+
if !g.copy_on_replace?
|
|
1362
|
+
g.remove_relation(plan[removed_parent], plan[removed_child])
|
|
1363
|
+
end
|
|
1364
|
+
end
|
|
1365
|
+
end
|
|
1663
1366
|
|
|
1664
|
-
|
|
1665
|
-
#
|
|
1367
|
+
# Replaces self's subplan by another subplan
|
|
1368
|
+
#
|
|
1369
|
+
# Replaces the subplan generated by self by the one generated by object.
|
|
1370
|
+
# In practice, it means that we transfer all parent edges whose target
|
|
1371
|
+
# is self from the receiver to object. It calls the various add/remove
|
|
1372
|
+
# hooks defined in {DirectedRelationSupport}.
|
|
1666
1373
|
#
|
|
1667
|
-
#
|
|
1668
|
-
# instance. See EventStructure::Forward for a description of the
|
|
1669
|
-
# forwarding relation.
|
|
1374
|
+
# Relations to free events are not copied during replacement
|
|
1670
1375
|
#
|
|
1671
|
-
#
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1376
|
+
# @see replace_by
|
|
1377
|
+
def replace_subplan_by(object)
|
|
1378
|
+
edges = compute_subplan_replacement_operation(object)
|
|
1379
|
+
apply_replacement_operations(edges)
|
|
1380
|
+
|
|
1381
|
+
initialize_replacement(object)
|
|
1382
|
+
each_event do |event|
|
|
1383
|
+
event.initialize_replacement(object.event(event.symbol))
|
|
1384
|
+
end
|
|
1385
|
+
end
|
|
1676
1386
|
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1387
|
+
# @api private
|
|
1388
|
+
def compute_object_replacement_operation(object)
|
|
1389
|
+
edges = []
|
|
1390
|
+
plan.each_task_relation_graph do |g|
|
|
1391
|
+
next if g.strong?
|
|
1392
|
+
|
|
1393
|
+
g.each_in_neighbour(self) do |parent|
|
|
1394
|
+
if parent != object
|
|
1395
|
+
edges << [g, parent, self, parent, object]
|
|
1396
|
+
end
|
|
1397
|
+
end
|
|
1398
|
+
g.each_out_neighbour(self) do |child|
|
|
1399
|
+
if object != child
|
|
1400
|
+
edges << [g, self, child, object, child]
|
|
1681
1401
|
end
|
|
1682
1402
|
end
|
|
1403
|
+
end
|
|
1683
1404
|
|
|
1684
|
-
|
|
1405
|
+
plan.each_event_relation_graph do |g|
|
|
1406
|
+
next if g.strong?
|
|
1407
|
+
|
|
1408
|
+
each_event do |event|
|
|
1409
|
+
object_event = nil
|
|
1410
|
+
g.each_in_neighbour(event) do |parent|
|
|
1411
|
+
if !parent.respond_to?(:task) || (parent.task != self && parent.task != object)
|
|
1412
|
+
object_event ||= object.event(event.symbol)
|
|
1413
|
+
edges << [g, parent, event, parent, object_event]
|
|
1414
|
+
end
|
|
1415
|
+
end
|
|
1416
|
+
g.each_out_neighbour(event) do |child|
|
|
1417
|
+
if !child.respond_to?(:task) || (child.task != self && child.task != object)
|
|
1418
|
+
object_event ||= object.event(event.symbol)
|
|
1419
|
+
edges << [g, event, child, object_event, child]
|
|
1420
|
+
end
|
|
1421
|
+
end
|
|
1422
|
+
end
|
|
1685
1423
|
end
|
|
1686
|
-
|
|
1687
|
-
|
|
1424
|
+
edges
|
|
1425
|
+
end
|
|
1688
1426
|
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1427
|
+
# Replaces self by object
|
|
1428
|
+
#
|
|
1429
|
+
# It replaces self by object in all relations +self+ is part of, and do
|
|
1430
|
+
# the same for the task's event generators.
|
|
1431
|
+
#
|
|
1432
|
+
# @see replace_subplan_by
|
|
1433
|
+
def replace_by(object)
|
|
1434
|
+
event_mappings = Hash.new
|
|
1435
|
+
event_resolver = ->(e) { object.event(e.symbol) }
|
|
1436
|
+
each_event do |ev|
|
|
1437
|
+
event_mappings[ev] = [nil, event_resolver]
|
|
1438
|
+
end
|
|
1439
|
+
object.each_event do |ev|
|
|
1440
|
+
event_mappings[ev] = nil
|
|
1441
|
+
end
|
|
1442
|
+
plan.replace_subplan(Hash[self => object, object => nil], event_mappings)
|
|
1693
1443
|
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
end
|
|
1704
|
-
s
|
|
1705
|
-
end
|
|
1706
|
-
|
|
1707
|
-
def pretty_print(pp, with_owners = true) # :nodoc:
|
|
1708
|
-
pp.text "#{model.name}:0x#{self.address.to_s(16)}"
|
|
1709
|
-
if with_owners
|
|
1710
|
-
pp.breakable
|
|
1711
|
-
pp.nest(2) do
|
|
1712
|
-
pp.text " owners: "
|
|
1713
|
-
pp.seplist(owners) { |r| pp.text r.to_s }
|
|
1714
|
-
pp.breakable
|
|
1444
|
+
initialize_replacement(object)
|
|
1445
|
+
each_event do |event|
|
|
1446
|
+
event.initialize_replacement(nil) { object.event(event.symbol) }
|
|
1447
|
+
end
|
|
1448
|
+
end
|
|
1449
|
+
|
|
1450
|
+
# @api private
|
|
1451
|
+
def initialize_replacement(task)
|
|
1452
|
+
super
|
|
1715
1453
|
|
|
1716
|
-
|
|
1717
|
-
|
|
1454
|
+
execute_handlers.each do |handler|
|
|
1455
|
+
if handler.copy_on_replace?
|
|
1456
|
+
task.execute(handler.as_options, &handler.block)
|
|
1718
1457
|
end
|
|
1719
|
-
else
|
|
1720
|
-
pp.text " "
|
|
1721
|
-
arguments.pretty_print(pp)
|
|
1722
1458
|
end
|
|
1723
|
-
end
|
|
1724
1459
|
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1460
|
+
poll_handlers.each do |handler|
|
|
1461
|
+
if handler.copy_on_replace?
|
|
1462
|
+
task.poll(handler.as_options, &handler.block)
|
|
1463
|
+
end
|
|
1464
|
+
end
|
|
1465
|
+
end
|
|
1731
1466
|
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1467
|
+
# @deprecated this has no equivalent. It really has never seen proper support
|
|
1468
|
+
def simulate
|
|
1469
|
+
simulation_task = self.model.simulation_model.new(arguments.to_hash)
|
|
1470
|
+
plan.force_replace(self, simulation_task)
|
|
1471
|
+
simulation_task
|
|
1472
|
+
end
|
|
1736
1473
|
|
|
1737
|
-
|
|
1738
|
-
|
|
1474
|
+
# Returns an object that will allow to track this task's role in the
|
|
1475
|
+
# plan regardless of replacements
|
|
1476
|
+
#
|
|
1477
|
+
# The returning object will point to the replacing object when self is
|
|
1478
|
+
# replaced by something. In effect, it points to the task's role in the
|
|
1479
|
+
# plan instead of to the actual task itself.
|
|
1480
|
+
#
|
|
1481
|
+
# @return [PlanService]
|
|
1482
|
+
def as_service
|
|
1483
|
+
@service ||= (plan.find_plan_service(self) || PlanService.new(self))
|
|
1484
|
+
end
|
|
1739
1485
|
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
# Sets the internal data value for this task. This calls the
|
|
1743
|
-
# #updated_data hook, and emits +updated_data+ if the task is running.
|
|
1744
|
-
def data=(value)
|
|
1745
|
-
@data = value
|
|
1746
|
-
updated_data
|
|
1747
|
-
emit :updated_data if running?
|
|
1748
|
-
end
|
|
1749
|
-
# This hook is called whenever the internal data of this task is
|
|
1750
|
-
# updated. See #data, #data= and the +updated_data+ event
|
|
1751
|
-
def updated_data
|
|
1752
|
-
super if defined? super
|
|
1753
|
-
end
|
|
1754
|
-
event :updated_data, :command => false
|
|
1755
|
-
|
|
1756
|
-
# Checks if +task+ is in the same execution state than +self+
|
|
1757
|
-
# Returns true if they are either both running or both pending
|
|
1758
|
-
def compatible_state?(task)
|
|
1759
|
-
finished? || !(running? ^ task.running?)
|
|
1760
|
-
end
|
|
1761
|
-
|
|
1762
|
-
# Returns the lists of tags this model fullfills.
|
|
1763
|
-
def self.tags
|
|
1764
|
-
ancestors.find_all { |m| m.instance_of?(TaskModelTag) }
|
|
1765
|
-
end
|
|
1766
|
-
|
|
1767
|
-
# The fullfills? predicate checks if this task can be used
|
|
1768
|
-
# to fullfill the need of the given +model+ and +arguments+
|
|
1769
|
-
# The default is to check if
|
|
1770
|
-
# * the needed task model is an ancestor of this task
|
|
1771
|
-
# * the task
|
|
1772
|
-
# * +args+ is included in the task arguments
|
|
1773
|
-
def fullfills?(models, args = {})
|
|
1774
|
-
if models.kind_of?(Task)
|
|
1775
|
-
klass, args =
|
|
1776
|
-
models.model,
|
|
1777
|
-
models.meaningful_arguments
|
|
1778
|
-
models = [klass]
|
|
1779
|
-
else
|
|
1780
|
-
models = [*models]
|
|
1781
|
-
end
|
|
1782
|
-
self_model = self.model
|
|
1783
|
-
self_args = self.arguments
|
|
1784
|
-
args = args.dup
|
|
1785
|
-
|
|
1786
|
-
# Check the arguments that are required by the model
|
|
1787
|
-
for tag in models
|
|
1788
|
-
if !self_model.has_ancestor?(tag)
|
|
1789
|
-
return false
|
|
1790
|
-
end
|
|
1791
|
-
|
|
1792
|
-
unless args.empty?
|
|
1793
|
-
for arg_name in tag.arguments
|
|
1794
|
-
if user_arg = args.delete(arg_name)
|
|
1795
|
-
return false unless user_arg == self_args[arg_name]
|
|
1796
|
-
end
|
|
1797
|
-
break if args.empty?
|
|
1798
|
-
end
|
|
1799
|
-
end
|
|
1800
|
-
end
|
|
1801
|
-
|
|
1802
|
-
if !args.empty?
|
|
1803
|
-
raise ArgumentError, "the arguments '#{args.keys.join(", ")}' are unknown to the tags #{models.join(", ")}"
|
|
1804
|
-
end
|
|
1805
|
-
true
|
|
1806
|
-
end
|
|
1807
|
-
|
|
1808
|
-
# True if +self+ can be used to replace +target+
|
|
1809
|
-
def can_replace?(target)
|
|
1810
|
-
fullfills?(*target.fullfilled_model)
|
|
1486
|
+
def as_plan
|
|
1487
|
+
self
|
|
1811
1488
|
end
|
|
1812
1489
|
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1490
|
+
# Register a hook that is called when this task is finalized (removed from its plan)
|
|
1491
|
+
#
|
|
1492
|
+
# @macro InstanceHandlerOptions
|
|
1493
|
+
def when_finalized(options = Hash.new, &block)
|
|
1494
|
+
default = if abstract? then :copy else :drop end
|
|
1495
|
+
options, remaining = InstanceHandler.filter_options options, on_replace: default
|
|
1496
|
+
super(options.merge(remaining), &block)
|
|
1497
|
+
end
|
|
1818
1498
|
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
end
|
|
1824
|
-
true
|
|
1825
|
-
end
|
|
1499
|
+
def internal_error_handler(exception)
|
|
1500
|
+
if !exception.originates_from?(self)
|
|
1501
|
+
return pass_exception
|
|
1502
|
+
end
|
|
1826
1503
|
|
|
1827
|
-
|
|
1828
|
-
|
|
1504
|
+
gen = exception.generator
|
|
1505
|
+
error = exception.exception
|
|
1506
|
+
if (gen == start_event) && !gen.emitted?
|
|
1507
|
+
if !failed_to_start?
|
|
1508
|
+
failed_to_start!(error)
|
|
1509
|
+
end
|
|
1510
|
+
elsif !running?
|
|
1511
|
+
pass_exception
|
|
1512
|
+
elsif (!gen || !gen.terminal?) && !internal_error_event.emitted?
|
|
1513
|
+
internal_error_event.emit(error)
|
|
1514
|
+
if stop_event.pending? || !stop_event.controlable?
|
|
1515
|
+
# In this case, we can't "just" stop the task. We have
|
|
1516
|
+
# to inject +error+ in the exception handling and kill
|
|
1517
|
+
# everything that depends on it.
|
|
1518
|
+
add_error(TaskEmergencyTermination.new(self, error, false))
|
|
1519
|
+
end
|
|
1520
|
+
else
|
|
1521
|
+
if execution_engine.display_exceptions?
|
|
1522
|
+
# No nice way to isolate this error through the task
|
|
1523
|
+
# interface, as we can't emergency stop it. Quarantine it
|
|
1524
|
+
# and inject it in the normal exception propagation
|
|
1525
|
+
# mechanisms.
|
|
1526
|
+
execution_engine.fatal "putting #{self} in quarantine: #{self} failed to emit"
|
|
1527
|
+
execution_engine.fatal "the error is:"
|
|
1528
|
+
Roby.log_exception_with_backtrace(error, execution_engine, :fatal)
|
|
1529
|
+
end
|
|
1829
1530
|
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
##
|
|
1836
|
-
# :call-seq:
|
|
1837
|
-
# on_exception(exception_class, ...) { |task, exception_object| ... }
|
|
1838
|
-
#
|
|
1839
|
-
# Defines an exception handler. matcher === exception_object is used to
|
|
1840
|
-
# determine if the handler should be called when +exception_object+ has
|
|
1841
|
-
# been fired. The first matching handler is called. Call #pass_exception to pass
|
|
1842
|
-
# the exception to previous handlers
|
|
1843
|
-
#
|
|
1844
|
-
# on_exception(TaskModelViolation, ...) do |task, exception_object|
|
|
1845
|
-
# if cannot_handle
|
|
1846
|
-
# task.pass_exception # send to the next handler
|
|
1847
|
-
# end
|
|
1848
|
-
# do_handle
|
|
1849
|
-
# end
|
|
1850
|
-
def self.on_exception(*matchers, &handler)
|
|
1851
|
-
check_arity(handler, 1)
|
|
1852
|
-
id = (@@exception_handler_id += 1)
|
|
1853
|
-
define_method("exception_handler_#{id}", &handler)
|
|
1854
|
-
exception_handlers.unshift [matchers, instance_method("exception_handler_#{id}")]
|
|
1855
|
-
end
|
|
1856
|
-
|
|
1857
|
-
# We can't add relations on objects we don't own
|
|
1858
|
-
def add_child_object(child, type, info)
|
|
1859
|
-
unless read_write? && child.read_write?
|
|
1860
|
-
raise OwnershipError, "cannot add a relation between tasks we don't own. #{self} by #{owners.to_a} and #{child} is owned by #{child.owners.to_a}"
|
|
1861
|
-
end
|
|
1862
|
-
|
|
1863
|
-
super
|
|
1864
|
-
end
|
|
1865
|
-
|
|
1866
|
-
# This method is called during the commit process to
|
|
1867
|
-
def commit_transaction
|
|
1868
|
-
super if defined? super
|
|
1869
|
-
|
|
1870
|
-
arguments.dup.each do |key, value|
|
|
1871
|
-
if value.kind_of?(Roby::Transactions::Proxy)
|
|
1872
|
-
arguments.update!(key, value.__getobj__)
|
|
1873
|
-
end
|
|
1874
|
-
end
|
|
1875
|
-
end
|
|
1531
|
+
plan.quarantine_task(self)
|
|
1532
|
+
add_error(TaskEmergencyTermination.new(self, error, true))
|
|
1533
|
+
end
|
|
1534
|
+
end
|
|
1535
|
+
private :internal_error_handler
|
|
1876
1536
|
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1537
|
+
on_exception(Roby::CodeError) do |exception|
|
|
1538
|
+
internal_error_handler(exception)
|
|
1539
|
+
end
|
|
1540
|
+
|
|
1541
|
+
# Creates a sequence where +self+ will be started first, and +task+ is
|
|
1542
|
+
# started if +self+ finished successfully. The returned value is an
|
|
1543
|
+
# instance of Sequence.
|
|
1880
1544
|
#
|
|
1881
|
-
#
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
#
|
|
1887
|
-
#
|
|
1888
|
-
#
|
|
1889
|
-
#
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
end
|
|
1901
|
-
|
|
1902
|
-
changes = []
|
|
1903
|
-
each_event do |event|
|
|
1904
|
-
next unless object.has_event?(event.symbol)
|
|
1905
|
-
changes.clear
|
|
1906
|
-
|
|
1907
|
-
event.each_relation do |rel|
|
|
1908
|
-
parents = []
|
|
1909
|
-
event.each_parent_object(rel) do |parent|
|
|
1910
|
-
if !parent.respond_to?(:task) || !own_subtree.include?(parent.task)
|
|
1911
|
-
parents << parent << parent[event, rel]
|
|
1912
|
-
end
|
|
1913
|
-
end
|
|
1914
|
-
children = []
|
|
1915
|
-
event.each_child_object(rel) do |child|
|
|
1916
|
-
if !child.respond_to?(:task) || !own_subtree.include?(child.task)
|
|
1917
|
-
children << child << event[child, rel]
|
|
1918
|
-
end
|
|
1919
|
-
end
|
|
1920
|
-
changes << rel << parents << children
|
|
1921
|
-
end
|
|
1922
|
-
|
|
1923
|
-
event.apply_relation_changes(object.event(event.symbol), changes)
|
|
1924
|
-
end
|
|
1925
|
-
end
|
|
1926
|
-
|
|
1927
|
-
# Replaces +self+ by +object+ in all relations +self+ is part of, and
|
|
1928
|
-
# do the same for the task's event generators.
|
|
1929
|
-
def replace_by(object)
|
|
1930
|
-
each_event do |event|
|
|
1931
|
-
event_name = event.symbol
|
|
1932
|
-
if object.has_event?(event_name)
|
|
1933
|
-
event.replace_by object.event(event_name)
|
|
1934
|
-
end
|
|
1935
|
-
end
|
|
1936
|
-
super
|
|
1937
|
-
end
|
|
1938
|
-
|
|
1939
|
-
# Define a polling block for this task. The polling block is called at
|
|
1940
|
-
# each execution cycle while the task is running (i.e. in-between the
|
|
1941
|
-
# emission of +start+ and the emission of +stop+.
|
|
1942
|
-
#
|
|
1943
|
-
# Raises ArgumentError if the task is already running.
|
|
1944
|
-
#
|
|
1945
|
-
# See also Task::poll
|
|
1946
|
-
def poll(&block)
|
|
1947
|
-
if !pending?
|
|
1948
|
-
raise ArgumentError, "cannot set a polling block when the task is not pending"
|
|
1949
|
-
end
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
singleton_class.class_eval do
|
|
1953
|
-
setup_poll_method(block)
|
|
1954
|
-
end
|
|
1955
|
-
on(:start) { |ev| @poll_handler_id = plan.engine.add_propagation_handler(method(:poll)) }
|
|
1956
|
-
on(:stop) { |ev| plan.engine.remove_propagation_handler(@poll_handler_id) }
|
|
1957
|
-
end
|
|
1958
|
-
end
|
|
1545
|
+
# Note that this operator always creates a new Sequence object, so
|
|
1546
|
+
#
|
|
1547
|
+
# a + b + c + d
|
|
1548
|
+
#
|
|
1549
|
+
# will create 3 Sequence instances. If more than two tasks should be
|
|
1550
|
+
# organized in a sequence, one should instead use Sequence#<<:
|
|
1551
|
+
#
|
|
1552
|
+
# Sequence.new << a << b << c << d
|
|
1553
|
+
#
|
|
1554
|
+
def +(task)
|
|
1555
|
+
# !!!! + is NOT commutative
|
|
1556
|
+
if task.null?
|
|
1557
|
+
self
|
|
1558
|
+
elsif self.null?
|
|
1559
|
+
task
|
|
1560
|
+
else
|
|
1561
|
+
Tasks::Sequence.new << self << task
|
|
1562
|
+
end
|
|
1563
|
+
end
|
|
1959
1564
|
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1565
|
+
# Creates a parallel aggregation between +self+ and +task+. Both tasks
|
|
1566
|
+
# are started at the same time, and the returned instance finishes when
|
|
1567
|
+
# both tasks are finished. The returned value is an instance of
|
|
1568
|
+
# Parallel.
|
|
1569
|
+
#
|
|
1570
|
+
# Note that this operator always creates a new Parallel object, so
|
|
1571
|
+
#
|
|
1572
|
+
# a | b | c | d
|
|
1573
|
+
#
|
|
1574
|
+
# will create three instances of Parallel. If more than two tasks should
|
|
1575
|
+
# be organized that way, one should instead use Parallel#<<:
|
|
1576
|
+
#
|
|
1577
|
+
# Parallel.new << a << b << c << d
|
|
1578
|
+
#
|
|
1579
|
+
def |(task)
|
|
1580
|
+
if self.null?
|
|
1581
|
+
task
|
|
1582
|
+
elsif task.null?
|
|
1583
|
+
self
|
|
1584
|
+
else
|
|
1585
|
+
Tasks::Parallel.new << self << task
|
|
1586
|
+
end
|
|
1587
|
+
end
|
|
1966
1588
|
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1589
|
+
def to_execution_exception
|
|
1590
|
+
ExecutionException.new(LocalizedError.new(self))
|
|
1591
|
+
end
|
|
1970
1592
|
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
#
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
task.start_event = start
|
|
2008
|
-
task.success_event = success
|
|
2009
|
-
|
|
2010
|
-
if start.respond_to?(:task)
|
|
2011
|
-
task.realized_by start.task
|
|
2012
|
-
end
|
|
2013
|
-
if success.respond_to?(:task)
|
|
2014
|
-
task.realized_by success.task
|
|
2015
|
-
end
|
|
2016
|
-
|
|
2017
|
-
task
|
|
2018
|
-
end
|
|
1593
|
+
# @api private
|
|
1594
|
+
def create_transaction_proxy(transaction)
|
|
1595
|
+
transaction.create_and_register_proxy_task(self)
|
|
1596
|
+
end
|
|
1597
|
+
|
|
1598
|
+
# Return a task match object that matches self
|
|
1599
|
+
#
|
|
1600
|
+
# @return [Queries::TaskMatcher]
|
|
1601
|
+
def match
|
|
1602
|
+
self.class.match.with_instance(self)
|
|
1603
|
+
end
|
|
1604
|
+
|
|
1605
|
+
# Enumerate the coordination objects currently attached to this task
|
|
1606
|
+
#
|
|
1607
|
+
# @yieldparam [Coordination::Base] object
|
|
1608
|
+
def each_coordination_object(&block)
|
|
1609
|
+
@coordination_objects.each(&block)
|
|
1610
|
+
end
|
|
1611
|
+
|
|
1612
|
+
# @api private
|
|
1613
|
+
#
|
|
1614
|
+
# Declare that a coordination object is attached to this task
|
|
1615
|
+
#
|
|
1616
|
+
# @param [Coordination::Base] object
|
|
1617
|
+
def add_coordination_object(object)
|
|
1618
|
+
@coordination_objects.push(object)
|
|
1619
|
+
end
|
|
1620
|
+
|
|
1621
|
+
# @api private
|
|
1622
|
+
#
|
|
1623
|
+
# Declare that a coordination object is no longer attached to this task
|
|
1624
|
+
#
|
|
1625
|
+
# @param [Coordination::Base] object
|
|
1626
|
+
def remove_coordination_object(object)
|
|
1627
|
+
@coordination_objects.delete(object)
|
|
1628
|
+
end
|
|
2019
1629
|
end
|
|
2020
1630
|
|
|
1631
|
+
|
|
2021
1632
|
unless defined? TaskStructure
|
|
2022
|
-
|
|
1633
|
+
TaskStructure = RelationSpace(Task)
|
|
1634
|
+
TaskStructure.default_graph_class = Relations::TaskRelationGraph
|
|
2023
1635
|
end
|
|
2024
|
-
|
|
2025
1636
|
end
|
|
2026
1637
|
|