roby 0.8.0 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|