roby 0.8.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.deep-cover.rb +3 -0
- data/.gitattributes +1 -0
- data/.gitignore +24 -0
- data/.simplecov +10 -0
- data/.travis.yml +17 -0
- data/.yardopts +4 -0
- data/Gemfile +15 -0
- data/README.md +11 -0
- data/Rakefile +47 -177
- data/benchmark/{alloc_misc.rb → attic/alloc_misc.rb} +2 -2
- data/benchmark/{discovery_latency.rb → attic/discovery_latency.rb} +19 -19
- data/benchmark/{garbage_collection.rb → attic/garbage_collection.rb} +9 -9
- data/benchmark/{genom.rb → attic/genom.rb} +0 -0
- data/benchmark/attic/transactions.rb +62 -0
- data/benchmark/plan_basic_operations.rb +28 -0
- data/benchmark/relations/graph.rb +63 -0
- data/benchmark/ruby/identity.rb +18 -0
- data/benchmark/ruby/set_intersect_vs_hash_merge.rb +39 -0
- data/benchmark/ruby/yield_vs_block.rb +35 -0
- data/benchmark/run +5 -0
- data/benchmark/synthetic_plan_modifications_with_transactions.rb +79 -0
- data/benchmark/transactions.rb +99 -51
- data/bin/roby +38 -197
- data/bin/roby-display +14 -0
- data/bin/roby-log +3 -176
- data/doc/guide/{src → attic}/abstraction/achieve_with.page +1 -1
- data/doc/guide/{src → attic}/abstraction/forwarding.page +1 -1
- data/doc/guide/{src → attic}/abstraction/hierarchy.page +1 -1
- data/doc/guide/{src → attic}/abstraction/index.page +1 -1
- data/doc/guide/{src → attic}/abstraction/task_models.page +1 -1
- data/doc/guide/{overview.rdoc → attic/cycle/api_overview.rdoc} +6 -1
- data/doc/guide/{src → attic}/cycle/cycle-overview.png +0 -0
- data/doc/guide/{src → attic}/cycle/cycle-overview.svg +0 -0
- data/doc/guide/attic/cycle/error_handling.page +98 -0
- data/doc/guide/{src → attic}/cycle/error_instantaneous_repair.png +0 -0
- data/doc/guide/{src → attic}/cycle/error_instantaneous_repair.svg +0 -0
- data/doc/guide/{src/cycle/error_handling.page → attic/cycle/error_sources.page} +46 -89
- data/doc/guide/{src → attic}/cycle/garbage_collection.page +1 -1
- data/doc/guide/{src → attic}/cycle/index.page +1 -1
- data/doc/guide/{src → attic}/cycle/propagation.page +11 -1
- data/doc/guide/{src → attic}/cycle/propagation_diamond.png +0 -0
- data/doc/guide/{src → attic}/cycle/propagation_diamond.svg +0 -0
- data/doc/guide/attic/plans/building_plans.page +89 -0
- data/doc/guide/attic/plans/code.page +192 -0
- data/doc/guide/{src/basics → attic/plans}/events.page +3 -4
- data/doc/guide/attic/plans/index.page +7 -0
- data/doc/guide/{plan_modifications.rdoc → attic/plans/plan_modifications.rdoc} +5 -3
- data/doc/guide/{src/basics → attic/plans}/plan_objects.page +2 -1
- data/doc/guide/attic/plans/querying_plans.page +5 -0
- data/doc/guide/{src/basics → attic/plans}/tasks.page +20 -20
- data/doc/guide/config.yaml +7 -4
- data/doc/guide/ext/extended_menu.rb +29 -0
- data/doc/guide/ext/init.rb +6 -0
- data/doc/guide/ext/rdoc_links.rb +7 -6
- data/doc/guide/src/advanced_concepts/history.page +5 -0
- data/doc/guide/src/advanced_concepts/index.page +11 -0
- data/doc/guide/src/advanced_concepts/recognizing_patterns.page +83 -0
- data/doc/guide/src/advanced_concepts/scheduling.page +87 -0
- data/doc/guide/src/advanced_concepts/transactions.page +5 -0
- data/doc/guide/src/advanced_concepts/unreachability.page +42 -0
- data/doc/guide/src/base.template +96 -0
- data/doc/guide/src/basics_shell_header.txt +5 -7
- data/doc/guide/src/building/action_coordination.page +96 -0
- data/doc/guide/src/building/actions.page +124 -0
- data/doc/guide/src/building/file_layout.page +71 -0
- data/doc/guide/src/building/index.page +50 -0
- data/doc/guide/src/building/patterns.page +86 -0
- data/doc/guide/src/building/patterns_forwarding.png +0 -0
- data/doc/guide/src/building/patterns_forwarding.svg +277 -0
- data/doc/guide/src/building/runtime.page +95 -0
- data/doc/guide/src/building/task_models.page +94 -0
- data/doc/guide/src/building/tasks.page +284 -0
- data/doc/guide/src/concepts/error_handling.page +100 -0
- data/doc/guide/src/concepts/exception_propagation.png +0 -0
- data/doc/guide/src/concepts/exception_propagation.svg +445 -0
- data/doc/guide/src/concepts/execution.page +85 -0
- data/doc/guide/src/concepts/execution.png +0 -0
- data/doc/guide/src/concepts/execution.svg +573 -0
- data/doc/guide/src/concepts/execution_cycle.png +0 -0
- data/doc/guide/src/concepts/garbage_collection.page +57 -0
- data/doc/guide/src/concepts/index.page +27 -0
- data/doc/guide/src/concepts/plans.page +101 -0
- data/doc/guide/src/concepts/policy.page +31 -0
- data/doc/guide/src/concepts/reactor.page +61 -0
- data/doc/guide/src/concepts/simple_plan_example.png +0 -0
- data/doc/guide/src/concepts/simple_plan_example.svg +376 -0
- data/doc/guide/src/default.template +9 -74
- data/doc/guide/src/event_relations/forward.page +71 -0
- data/doc/guide/src/event_relations/index.page +12 -0
- data/doc/guide/src/event_relations/scheduling_constraints.page +43 -0
- data/doc/guide/src/event_relations/signal.page +55 -0
- data/doc/guide/src/event_relations/temporal_constraints.page +77 -0
- data/doc/guide/src/htmldoc.metainfo +21 -8
- data/doc/guide/src/index.page +8 -3
- data/doc/guide/src/{introduction/install.page → installation/index.page} +37 -25
- data/doc/guide/src/installation/publications.page +14 -0
- data/doc/guide/src/{introduction → installation}/videos.page +14 -7
- data/doc/guide/src/interacting/index.page +16 -0
- data/doc/guide/src/interacting/run.page +33 -0
- data/doc/guide/src/interacting/shell.page +95 -0
- data/doc/guide/src/plugins/creating_plugins.page +72 -0
- data/doc/guide/src/plugins/index.page +27 -5
- data/doc/guide/src/plugins/{fault_tolerance.page → standard_plugins/fault_tolerance.page} +2 -2
- data/doc/guide/src/plugins/standard_plugins/index.page +11 -0
- data/doc/guide/src/plugins/{subsystems.page → standard_plugins/subsystems.page} +2 -2
- data/doc/guide/src/style_screen.css +687 -0
- data/doc/guide/src/task_relations/dependency.page +107 -0
- data/doc/guide/src/task_relations/executed_by.page +77 -0
- data/doc/guide/src/task_relations/index.page +12 -0
- data/doc/guide/src/task_relations/new_relations.page +119 -0
- data/doc/guide/src/task_relations/planned_by.page +46 -0
- data/doc/guide/src/tutorial/app.page +117 -0
- data/doc/guide/src/{basics → tutorial}/code_examples.page +6 -5
- data/doc/guide/src/{basics → tutorial}/dry.page +15 -15
- data/doc/guide/src/{basics → tutorial}/errors.page +43 -68
- data/doc/guide/src/tutorial/events.page +195 -0
- data/doc/guide/src/{basics → tutorial}/hierarchy.page +53 -52
- data/doc/guide/src/tutorial/index.page +13 -0
- data/doc/guide/src/tutorial/log_replay/goForward_1.png +0 -0
- data/doc/guide/src/tutorial/log_replay/goForward_2.png +0 -0
- data/doc/guide/src/tutorial/log_replay/goForward_3.png +0 -0
- data/doc/guide/src/{basics → tutorial}/log_replay/goForward_4.png +0 -0
- data/doc/guide/src/tutorial/log_replay/goForward_5.png +0 -0
- data/doc/guide/src/{basics → tutorial}/log_replay/hierarchy_error_1.png +0 -0
- data/doc/guide/src/{basics → tutorial}/log_replay/hierarchy_error_2.png +0 -0
- data/doc/guide/src/{basics → tutorial}/log_replay/hierarchy_error_3.png +0 -0
- data/doc/guide/src/tutorial/log_replay/moveto_code_error.png +0 -0
- data/doc/guide/src/{basics → tutorial}/log_replay/plan_repair_1.png +0 -0
- data/doc/guide/src/{basics → tutorial}/log_replay/plan_repair_2.png +0 -0
- data/doc/guide/src/{basics → tutorial}/log_replay/plan_repair_3.png +0 -0
- data/doc/guide/src/tutorial/log_replay/plan_repair_4.png +0 -0
- data/doc/guide/src/tutorial/log_replay/roby_log_main_window.png +0 -0
- data/doc/guide/src/{basics → tutorial}/log_replay/roby_log_relation_window.png +0 -0
- data/doc/guide/src/{basics → tutorial}/log_replay/roby_replay_event_representation.png +0 -0
- data/doc/guide/src/tutorial/relations_display.page +153 -0
- data/doc/guide/src/{basics → tutorial}/roby_cycle_overview.png +0 -0
- data/doc/guide/src/tutorial/shell.page +121 -0
- data/doc/guide/src/{basics → tutorial}/summary.page +1 -1
- data/doc/guide/src/tutorial/tasks.page +374 -0
- data/lib/roby.rb +102 -47
- data/lib/roby/actions.rb +17 -0
- data/lib/roby/actions/action.rb +80 -0
- data/lib/roby/actions/interface.rb +45 -0
- data/lib/roby/actions/library.rb +23 -0
- data/lib/roby/actions/models/action.rb +224 -0
- data/lib/roby/actions/models/coordination_action.rb +58 -0
- data/lib/roby/actions/models/interface.rb +22 -0
- data/lib/roby/actions/models/interface_base.rb +294 -0
- data/lib/roby/actions/models/library.rb +12 -0
- data/lib/roby/actions/models/method_action.rb +90 -0
- data/lib/roby/actions/task.rb +114 -0
- data/lib/roby/and_generator.rb +125 -0
- data/lib/roby/app.rb +2795 -829
- data/lib/roby/app/autotest_console_reporter.rb +138 -0
- data/lib/roby/app/base.rb +21 -0
- data/lib/roby/app/cucumber.rb +2 -0
- data/lib/roby/app/cucumber/controller.rb +439 -0
- data/lib/roby/app/cucumber/helpers.rb +280 -0
- data/lib/roby/app/cucumber/world.rb +32 -0
- data/lib/roby/app/debug.rb +136 -0
- data/lib/roby/app/gen.rb +2 -0
- data/lib/roby/app/rake.rb +178 -38
- data/lib/roby/app/robot_config.rb +9 -0
- data/lib/roby/app/robot_names.rb +115 -0
- data/lib/roby/app/run.rb +3 -2
- data/lib/roby/app/scripts.rb +72 -0
- data/lib/roby/app/scripts/autotest.rb +173 -0
- data/lib/roby/app/scripts/display.rb +2 -0
- data/lib/roby/app/scripts/restart.rb +52 -0
- data/lib/roby/app/scripts/results.rb +17 -8
- data/lib/roby/app/scripts/run.rb +155 -24
- data/lib/roby/app/scripts/shell.rb +147 -62
- data/lib/roby/app/scripts/test.rb +107 -22
- data/lib/roby/app/test_reporter.rb +74 -0
- data/lib/roby/app/test_server.rb +159 -0
- data/lib/roby/app/vagrant.rb +47 -0
- data/lib/roby/backports.rb +16 -0
- data/lib/roby/cli/display.rb +190 -0
- data/lib/roby/cli/exceptions.rb +17 -0
- data/lib/roby/cli/gen/actions/class.rb +5 -0
- data/lib/roby/cli/gen/actions/test.rb +6 -0
- data/lib/roby/cli/gen/app/.yardopts +6 -0
- data/lib/roby/cli/gen/app/README.md +28 -0
- data/lib/roby/cli/gen/app/Rakefile +15 -0
- data/{app → lib/roby/cli/gen/app}/config/app.yml +29 -39
- data/lib/roby/cli/gen/app/models/.gitattributes +1 -0
- data/{app → lib/roby/cli/gen/app/scripts}/controllers/.gitattributes +0 -0
- data/{app/data/.gitattributes → lib/roby/cli/gen/app/test/.gitignore} +0 -0
- data/lib/roby/cli/gen/class/class.rb +6 -0
- data/lib/roby/cli/gen/class/test.rb +7 -0
- data/lib/roby/cli/gen/helpers.rb +203 -0
- data/lib/roby/cli/gen/module/module.rb +5 -0
- data/lib/roby/cli/gen/module/test.rb +6 -0
- data/lib/roby/cli/gen/roby_app/config/init.rb +17 -0
- data/lib/roby/cli/gen/roby_app/config/robots/robot.rb +40 -0
- data/lib/roby/cli/gen/task/class.rb +44 -0
- data/lib/roby/cli/gen/task/test.rb +6 -0
- data/lib/roby/cli/gen_main.rb +120 -0
- data/lib/roby/cli/log.rb +276 -0
- data/lib/roby/cli/log/flamegraph.html +499 -0
- data/lib/roby/cli/log/flamegraph_renderer.rb +88 -0
- data/lib/roby/cli/main.rb +153 -0
- data/lib/roby/coordination.rb +60 -0
- data/lib/roby/coordination/action_script.rb +25 -0
- data/lib/roby/coordination/action_state_machine.rb +125 -0
- data/lib/roby/coordination/actions.rb +106 -0
- data/lib/roby/coordination/base.rb +145 -0
- data/lib/roby/coordination/calculus.rb +40 -0
- data/lib/roby/coordination/child.rb +28 -0
- data/lib/roby/coordination/event.rb +29 -0
- data/lib/roby/coordination/fault_handler.rb +25 -0
- data/lib/roby/coordination/fault_handling_task.rb +13 -0
- data/lib/roby/coordination/fault_response_table.rb +110 -0
- data/lib/roby/coordination/models/action_script.rb +64 -0
- data/lib/roby/coordination/models/action_state_machine.rb +224 -0
- data/lib/roby/coordination/models/actions.rb +191 -0
- data/lib/roby/coordination/models/arguments.rb +55 -0
- data/lib/roby/coordination/models/base.rb +176 -0
- data/lib/roby/coordination/models/capture.rb +86 -0
- data/lib/roby/coordination/models/child.rb +35 -0
- data/lib/roby/coordination/models/event.rb +41 -0
- data/lib/roby/coordination/models/exceptions.rb +42 -0
- data/lib/roby/coordination/models/fault_handler.rb +219 -0
- data/lib/roby/coordination/models/fault_response_table.rb +77 -0
- data/lib/roby/coordination/models/root.rb +22 -0
- data/lib/roby/coordination/models/script.rb +283 -0
- data/lib/roby/coordination/models/task.rb +184 -0
- data/lib/roby/coordination/models/task_from_action.rb +50 -0
- data/lib/roby/coordination/models/task_from_as_plan.rb +33 -0
- data/lib/roby/coordination/models/task_from_instanciation_object.rb +31 -0
- data/lib/roby/coordination/models/task_from_variable.rb +27 -0
- data/lib/roby/coordination/models/task_with_dependencies.rb +48 -0
- data/lib/roby/coordination/models/variable.rb +32 -0
- data/lib/roby/coordination/script.rb +200 -0
- data/lib/roby/coordination/script_instruction.rb +12 -0
- data/lib/roby/coordination/task.rb +45 -0
- data/lib/roby/coordination/task_base.rb +69 -0
- data/lib/roby/coordination/task_script.rb +293 -0
- data/lib/roby/coordination/task_state_machine.rb +308 -0
- data/lib/roby/decision_control.rb +33 -21
- data/lib/roby/distributed_object.rb +76 -0
- data/lib/roby/droby.rb +17 -0
- data/lib/roby/droby/droby_id.rb +6 -0
- data/lib/roby/droby/enable.rb +153 -0
- data/lib/roby/droby/event_logger.rb +189 -0
- data/lib/roby/droby/event_logging.rb +57 -0
- data/lib/roby/droby/exceptions.rb +14 -0
- data/lib/roby/droby/identifiable.rb +22 -0
- data/lib/roby/droby/logfile.rb +141 -0
- data/lib/roby/droby/logfile/client.rb +176 -0
- data/lib/roby/droby/logfile/file_format.md +97 -0
- data/lib/roby/droby/logfile/index.rb +117 -0
- data/lib/roby/droby/logfile/reader.rb +139 -0
- data/lib/roby/droby/logfile/server.rb +199 -0
- data/lib/roby/droby/logfile/writer.rb +114 -0
- data/lib/roby/droby/marshal.rb +264 -0
- data/lib/roby/droby/marshallable.rb +12 -0
- data/lib/roby/droby/null_event_logger.rb +25 -0
- data/lib/roby/droby/object_manager.rb +205 -0
- data/lib/roby/droby/peer_id.rb +6 -0
- data/lib/roby/droby/plan_rebuilder.rb +373 -0
- data/lib/roby/droby/rebuilt_plan.rb +160 -0
- data/lib/roby/droby/remote_droby_id.rb +6 -0
- data/lib/roby/droby/timepoints.rb +205 -0
- data/lib/roby/droby/timepoints_ctf.metadata.erb +101 -0
- data/lib/roby/droby/timepoints_ctf.rb +125 -0
- data/lib/roby/droby/v5.rb +14 -0
- data/lib/roby/droby/v5/builtin.rb +120 -0
- data/lib/roby/droby/v5/droby_class.rb +45 -0
- data/lib/roby/droby/v5/droby_constant.rb +81 -0
- data/lib/roby/droby/v5/droby_dump.rb +1026 -0
- data/lib/roby/droby/v5/droby_id.rb +44 -0
- data/lib/roby/droby/v5/droby_model.rb +82 -0
- data/lib/roby/droby/v5/peer_id.rb +10 -0
- data/lib/roby/droby/v5/remote_droby_id.rb +42 -0
- data/lib/roby/event.rb +79 -957
- data/lib/roby/event_constraints.rb +835 -0
- data/lib/roby/event_generator.rb +1047 -0
- data/lib/roby/event_structure/causal_link.rb +6 -0
- data/lib/roby/event_structure/forwarding.rb +6 -0
- data/lib/roby/event_structure/precedence.rb +7 -0
- data/lib/roby/event_structure/signal.rb +8 -0
- data/lib/roby/event_structure/temporal_constraints.rb +640 -0
- data/lib/roby/exceptions.rb +446 -152
- data/lib/roby/executable_plan.rb +549 -0
- data/lib/roby/execution_engine.rb +1997 -950
- data/lib/roby/filter_generator.rb +26 -0
- data/lib/roby/gui/chronicle_view.rb +225 -0
- data/lib/roby/gui/chronicle_widget.rb +925 -0
- data/lib/roby/gui/dot_id.rb +11 -0
- data/lib/roby/gui/exception_view.rb +44 -0
- data/lib/roby/gui/log_display.rb +273 -0
- data/lib/roby/gui/model_views.rb +2 -0
- data/lib/roby/gui/model_views/action_interface.rb +53 -0
- data/lib/roby/gui/model_views/task.rb +47 -0
- data/lib/roby/gui/model_views/task.rhtml +41 -0
- data/lib/roby/gui/object_info_view.rb +89 -0
- data/lib/roby/gui/plan_dot_layout.rb +427 -0
- data/lib/roby/gui/plan_rebuilder_widget.rb +357 -0
- data/lib/roby/gui/qt4_toMSecsSinceEpoch.rb +8 -0
- data/lib/roby/gui/relations_view.rb +278 -0
- data/lib/roby/gui/relations_view/relations.ui +139 -0
- data/lib/roby/gui/relations_view/relations_canvas.rb +1088 -0
- data/lib/roby/gui/relations_view/relations_config.rb +292 -0
- data/lib/roby/gui/relations_view/relations_view.ui +53 -0
- data/lib/roby/gui/scheduler_view.css +24 -0
- data/lib/roby/gui/scheduler_view.rb +46 -0
- data/lib/roby/gui/scheduler_view.rhtml +53 -0
- data/lib/roby/gui/stepping.rb +93 -0
- data/lib/roby/gui/stepping.ui +181 -0
- data/lib/roby/gui/styles.rb +81 -0
- data/lib/roby/gui/task_display_configuration.rb +42 -0
- data/lib/roby/gui/task_state_at.rb +38 -0
- data/lib/roby/hooks.rb +26 -0
- data/lib/roby/interface.rb +136 -469
- data/lib/roby/interface/async.rb +20 -0
- data/lib/roby/interface/async/action_monitor.rb +188 -0
- data/lib/roby/interface/async/interface.rb +498 -0
- data/lib/roby/interface/async/job_monitor.rb +213 -0
- data/lib/roby/interface/async/log.rb +238 -0
- data/lib/roby/interface/async/new_job_listener.rb +79 -0
- data/lib/roby/interface/async/ui_connector.rb +183 -0
- data/lib/roby/interface/client.rb +553 -0
- data/lib/roby/interface/command.rb +24 -0
- data/lib/roby/interface/command_argument.rb +16 -0
- data/lib/roby/interface/command_library.rb +92 -0
- data/lib/roby/interface/droby_channel.rb +174 -0
- data/lib/roby/interface/exceptions.rb +22 -0
- data/lib/roby/interface/interface.rb +655 -0
- data/lib/roby/interface/job.rb +47 -0
- data/lib/roby/interface/rest.rb +10 -0
- data/lib/roby/interface/rest/api.rb +29 -0
- data/lib/roby/interface/rest/helpers.rb +24 -0
- data/lib/roby/interface/rest/server.rb +212 -0
- data/lib/roby/interface/server.rb +154 -0
- data/lib/roby/interface/shell_client.rb +468 -0
- data/lib/roby/interface/shell_subcommand.rb +24 -0
- data/lib/roby/interface/subcommand_client.rb +35 -0
- data/lib/roby/interface/tcp.rb +168 -0
- data/lib/roby/models/arguments.rb +112 -0
- data/lib/roby/models/plan_object.rb +83 -0
- data/lib/roby/models/task.rb +835 -0
- data/lib/roby/models/task_event.rb +62 -0
- data/lib/roby/models/task_service.rb +78 -0
- data/lib/roby/or_generator.rb +88 -0
- data/lib/roby/plan.rb +1751 -864
- data/lib/roby/plan_object.rb +611 -0
- data/lib/roby/plan_service.rb +200 -0
- data/lib/roby/promise.rb +332 -0
- data/lib/roby/queries.rb +23 -0
- data/lib/roby/queries/and_matcher.rb +32 -0
- data/lib/roby/queries/any.rb +27 -0
- data/lib/roby/queries/code_error_matcher.rb +58 -0
- data/lib/roby/queries/event_generator_matcher.rb +9 -0
- data/lib/roby/queries/execution_exception_matcher.rb +165 -0
- data/lib/roby/queries/index.rb +165 -0
- data/lib/roby/queries/localized_error_matcher.rb +149 -0
- data/lib/roby/queries/matcher_base.rb +107 -0
- data/lib/roby/queries/none.rb +27 -0
- data/lib/roby/queries/not_matcher.rb +30 -0
- data/lib/roby/queries/op_matcher.rb +8 -0
- data/lib/roby/queries/or_matcher.rb +30 -0
- data/lib/roby/queries/plan_object_matcher.rb +363 -0
- data/lib/roby/queries/query.rb +188 -0
- data/lib/roby/queries/task_event_generator_matcher.rb +86 -0
- data/lib/roby/queries/task_matcher.rb +344 -0
- data/lib/roby/relations.rb +42 -678
- data/lib/roby/relations/bidirectional_directed_adjacency_graph.rb +492 -0
- data/lib/roby/relations/directed_relation_support.rb +268 -0
- data/lib/roby/relations/event_relation_graph.rb +19 -0
- data/lib/roby/relations/fork_merge_visitor.rb +154 -0
- data/lib/roby/relations/graph.rb +533 -0
- data/lib/roby/relations/models/directed_relation_support.rb +11 -0
- data/lib/roby/relations/models/graph.rb +75 -0
- data/lib/roby/relations/models/task_relation_graph.rb +18 -0
- data/lib/roby/relations/space.rb +380 -0
- data/lib/roby/relations/task_relation_graph.rb +20 -0
- data/lib/roby/robot.rb +85 -38
- data/lib/roby/schedulers/basic.rb +155 -25
- data/lib/roby/schedulers/null.rb +20 -0
- data/lib/roby/schedulers/reporting.rb +31 -0
- data/lib/roby/schedulers/state.rb +129 -0
- data/lib/roby/schedulers/temporal.rb +91 -0
- data/lib/roby/singletons.rb +87 -0
- data/lib/roby/standalone.rb +4 -2
- data/lib/roby/standard_errors.rb +405 -82
- data/lib/roby/state.rb +6 -3
- data/lib/roby/state/conf_model.rb +5 -0
- data/lib/roby/state/events.rb +181 -95
- data/lib/roby/state/goal_model.rb +77 -0
- data/lib/roby/state/open_struct.rb +591 -0
- data/lib/roby/state/open_struct_model.rb +68 -0
- data/lib/roby/state/pos.rb +45 -45
- data/lib/roby/state/shapes.rb +11 -11
- data/lib/roby/state/state_model.rb +303 -0
- data/lib/roby/state/task.rb +43 -0
- data/lib/roby/support.rb +88 -148
- data/lib/roby/task.rb +1361 -1750
- data/lib/roby/task_arguments.rb +428 -0
- data/lib/roby/task_event.rb +127 -0
- data/lib/roby/task_event_generator.rb +337 -0
- data/lib/roby/task_service.rb +6 -0
- data/lib/roby/task_structure/conflicts.rb +104 -0
- data/lib/roby/task_structure/dependency.rb +932 -0
- data/lib/roby/task_structure/error_handling.rb +118 -0
- data/lib/roby/task_structure/executed_by.rb +234 -0
- data/lib/roby/task_structure/planned_by.rb +90 -0
- data/lib/roby/tasks/aggregator.rb +37 -0
- data/lib/roby/tasks/external_process.rb +275 -0
- data/lib/roby/tasks/group.rb +27 -0
- data/lib/roby/tasks/null.rb +19 -0
- data/lib/roby/tasks/parallel.rb +43 -0
- data/lib/roby/tasks/sequence.rb +88 -0
- data/lib/roby/tasks/simple.rb +21 -0
- data/lib/roby/{thread_task.rb → tasks/thread.rb} +50 -24
- data/lib/roby/tasks/timeout.rb +17 -0
- data/lib/roby/tasks/virtual.rb +55 -0
- data/lib/roby/template_plan.rb +7 -0
- data/lib/roby/test/aruba_minitest.rb +74 -0
- data/lib/roby/test/assertion.rb +16 -0
- data/lib/roby/test/assertions.rb +490 -0
- data/lib/roby/test/common.rb +368 -591
- data/lib/roby/test/dsl.rb +149 -0
- data/lib/roby/test/error.rb +18 -0
- data/lib/roby/test/event_reporter.rb +83 -0
- data/lib/roby/test/execution_expectations.rb +1134 -0
- data/lib/roby/test/expect_execution.rb +151 -0
- data/lib/roby/test/minitest_helpers.rb +166 -0
- data/lib/roby/test/roby_app_helpers.rb +200 -0
- data/lib/roby/test/run_planners.rb +155 -0
- data/lib/roby/test/self.rb +112 -0
- data/lib/roby/test/spec.rb +198 -0
- data/lib/roby/test/tasks/empty_task.rb +4 -4
- data/lib/roby/test/tasks/goto.rb +28 -27
- data/lib/roby/test/teardown_plans.rb +100 -0
- data/lib/roby/test/testcase.rb +239 -307
- data/lib/roby/test/tools.rb +159 -155
- data/lib/roby/test/validate_state_machine.rb +75 -0
- data/lib/roby/transaction.rb +1125 -0
- data/lib/roby/transaction/event_generator_proxy.rb +63 -0
- data/lib/roby/transaction/plan_object_proxy.rb +99 -0
- data/lib/roby/transaction/plan_service_proxy.rb +43 -0
- data/lib/roby/transaction/proxying.rb +120 -0
- data/lib/roby/transaction/task_event_generator_proxy.rb +19 -0
- data/lib/roby/transaction/task_proxy.rb +135 -0
- data/lib/roby/until_generator.rb +30 -0
- data/lib/roby/version.rb +5 -0
- data/lib/roby/yard.rb +169 -0
- data/lib/yard-roby.rb +1 -0
- data/manifest.xml +32 -6
- data/roby.gemspec +59 -0
- metadata +788 -587
- data/Manifest.txt +0 -321
- data/NOTES +0 -4
- data/README.txt +0 -166
- data/TODO.txt +0 -146
- data/app/README.txt +0 -24
- data/app/Rakefile +0 -8
- data/app/config/ROBOT.rb +0 -5
- data/app/config/init.rb +0 -33
- data/app/config/roby.yml +0 -3
- data/app/controllers/ROBOT.rb +0 -2
- data/app/planners/ROBOT/main.rb +0 -6
- data/app/planners/main.rb +0 -5
- data/app/scripts/distributed +0 -3
- data/app/scripts/generate/bookmarks +0 -3
- data/app/scripts/replay +0 -3
- data/app/scripts/results +0 -3
- data/app/scripts/run +0 -3
- data/app/scripts/server +0 -3
- data/app/scripts/shell +0 -3
- data/app/scripts/test +0 -3
- data/app/tasks/.gitattributes +0 -0
- data/app/tasks/ROBOT/.gitattributes +0 -0
- data/bin/roby-shell +0 -25
- data/doc/guide/src/basics/app.page +0 -139
- data/doc/guide/src/basics/index.page +0 -11
- data/doc/guide/src/basics/log_replay/goForward_1.png +0 -0
- data/doc/guide/src/basics/log_replay/goForward_2.png +0 -0
- data/doc/guide/src/basics/log_replay/goForward_3.png +0 -0
- data/doc/guide/src/basics/log_replay/goForward_5.png +0 -0
- data/doc/guide/src/basics/log_replay/plan_repair_4.png +0 -0
- data/doc/guide/src/basics/log_replay/roby_log_main_window.png +0 -0
- data/doc/guide/src/basics/relations_display.page +0 -203
- data/doc/guide/src/basics/shell.page +0 -102
- data/doc/guide/src/default.css +0 -319
- data/doc/guide/src/introduction/index.page +0 -29
- data/doc/guide/src/introduction/publications.page +0 -14
- data/doc/guide/src/relations/dependency.page +0 -89
- data/doc/guide/src/relations/index.page +0 -12
- data/ext/droby/dump.cc +0 -175
- data/ext/droby/extconf.rb +0 -3
- data/ext/graph/algorithm.cc +0 -746
- data/ext/graph/extconf.rb +0 -7
- data/ext/graph/graph.cc +0 -575
- data/ext/graph/graph.hh +0 -183
- data/ext/graph/iterator_sequence.hh +0 -102
- data/ext/graph/undirected_dfs.hh +0 -226
- data/ext/graph/undirected_graph.hh +0 -421
- data/lib/roby/app/scripts/generate/bookmarks.rb +0 -162
- data/lib/roby/app/scripts/replay.rb +0 -31
- data/lib/roby/app/scripts/server.rb +0 -18
- data/lib/roby/basic_object.rb +0 -151
- data/lib/roby/config.rb +0 -14
- data/lib/roby/distributed.rb +0 -36
- data/lib/roby/distributed/base.rb +0 -448
- data/lib/roby/distributed/communication.rb +0 -875
- data/lib/roby/distributed/connection_space.rb +0 -616
- data/lib/roby/distributed/distributed_object.rb +0 -206
- data/lib/roby/distributed/drb.rb +0 -62
- data/lib/roby/distributed/notifications.rb +0 -531
- data/lib/roby/distributed/peer.rb +0 -555
- data/lib/roby/distributed/protocol.rb +0 -529
- data/lib/roby/distributed/proxy.rb +0 -343
- data/lib/roby/distributed/subscription.rb +0 -311
- data/lib/roby/distributed/transaction.rb +0 -498
- data/lib/roby/external_process_task.rb +0 -225
- data/lib/roby/graph.rb +0 -160
- data/lib/roby/log.rb +0 -3
- data/lib/roby/log/chronicle.rb +0 -303
- data/lib/roby/log/console.rb +0 -74
- data/lib/roby/log/data_stream.rb +0 -275
- data/lib/roby/log/dot.rb +0 -279
- data/lib/roby/log/event_stream.rb +0 -161
- data/lib/roby/log/file.rb +0 -396
- data/lib/roby/log/gui/basic_display.ui +0 -83
- data/lib/roby/log/gui/basic_display_ui.rb +0 -89
- data/lib/roby/log/gui/chronicle.rb +0 -26
- data/lib/roby/log/gui/chronicle_view.rb +0 -40
- data/lib/roby/log/gui/chronicle_view.ui +0 -70
- data/lib/roby/log/gui/chronicle_view_ui.rb +0 -90
- data/lib/roby/log/gui/data_displays.rb +0 -171
- data/lib/roby/log/gui/data_displays.ui +0 -155
- data/lib/roby/log/gui/data_displays_ui.rb +0 -146
- data/lib/roby/log/gui/notifications.rb +0 -26
- data/lib/roby/log/gui/relations.rb +0 -269
- data/lib/roby/log/gui/relations.ui +0 -123
- data/lib/roby/log/gui/relations_ui.rb +0 -120
- data/lib/roby/log/gui/relations_view.rb +0 -185
- data/lib/roby/log/gui/relations_view.ui +0 -149
- data/lib/roby/log/gui/relations_view_ui.rb +0 -144
- data/lib/roby/log/gui/replay.rb +0 -366
- data/lib/roby/log/gui/replay_controls.rb +0 -206
- data/lib/roby/log/gui/replay_controls.ui +0 -282
- data/lib/roby/log/gui/replay_controls_ui.rb +0 -249
- data/lib/roby/log/gui/runtime.rb +0 -130
- data/lib/roby/log/hooks.rb +0 -186
- data/lib/roby/log/logger.rb +0 -203
- data/lib/roby/log/notifications.rb +0 -244
- data/lib/roby/log/plan_rebuilder.rb +0 -468
- data/lib/roby/log/relations.rb +0 -1084
- data/lib/roby/log/server.rb +0 -547
- data/lib/roby/log/sqlite.rb +0 -47
- data/lib/roby/log/timings.rb +0 -233
- data/lib/roby/plan-object.rb +0 -371
- data/lib/roby/planning.rb +0 -13
- data/lib/roby/planning/loops.rb +0 -309
- data/lib/roby/planning/model.rb +0 -1012
- data/lib/roby/planning/task.rb +0 -180
- data/lib/roby/query.rb +0 -655
- data/lib/roby/relations/conflicts.rb +0 -67
- data/lib/roby/relations/dependency.rb +0 -358
- data/lib/roby/relations/ensured.rb +0 -19
- data/lib/roby/relations/error_handling.rb +0 -22
- data/lib/roby/relations/events.rb +0 -7
- data/lib/roby/relations/executed_by.rb +0 -208
- data/lib/roby/relations/influence.rb +0 -10
- data/lib/roby/relations/planned_by.rb +0 -63
- data/lib/roby/state/information.rb +0 -55
- data/lib/roby/state/state.rb +0 -367
- data/lib/roby/task-operations.rb +0 -186
- data/lib/roby/task_index.rb +0 -80
- data/lib/roby/test/distributed.rb +0 -230
- data/lib/roby/test/tasks/simple_task.rb +0 -23
- data/lib/roby/transactions.rb +0 -507
- data/lib/roby/transactions/proxy.rb +0 -325
- data/plugins/fault_injection/History.txt +0 -4
- data/plugins/fault_injection/README.txt +0 -34
- data/plugins/fault_injection/Rakefile +0 -12
- data/plugins/fault_injection/TODO.txt +0 -0
- data/plugins/fault_injection/app.rb +0 -52
- data/plugins/fault_injection/fault_injection.rb +0 -89
- data/plugins/fault_injection/test/test_fault_injection.rb +0 -78
- data/plugins/subsystems/README.txt +0 -37
- data/plugins/subsystems/Rakefile +0 -13
- data/plugins/subsystems/app.rb +0 -182
- data/plugins/subsystems/test/app/README +0 -24
- data/plugins/subsystems/test/app/Rakefile +0 -8
- data/plugins/subsystems/test/app/config/app.yml +0 -71
- data/plugins/subsystems/test/app/config/init.rb +0 -12
- data/plugins/subsystems/test/app/config/roby.yml +0 -3
- data/plugins/subsystems/test/app/planners/main.rb +0 -20
- data/plugins/subsystems/test/app/scripts/distributed +0 -3
- data/plugins/subsystems/test/app/scripts/replay +0 -3
- data/plugins/subsystems/test/app/scripts/results +0 -3
- data/plugins/subsystems/test/app/scripts/run +0 -3
- data/plugins/subsystems/test/app/scripts/server +0 -3
- data/plugins/subsystems/test/app/scripts/shell +0 -3
- data/plugins/subsystems/test/app/scripts/test +0 -3
- data/plugins/subsystems/test/app/tasks/services.rb +0 -15
- data/plugins/subsystems/test/test_subsystems.rb +0 -78
- data/test/distributed/test_communication.rb +0 -195
- data/test/distributed/test_connection.rb +0 -284
- data/test/distributed/test_execution.rb +0 -378
- data/test/distributed/test_mixed_plan.rb +0 -341
- data/test/distributed/test_plan_notifications.rb +0 -238
- data/test/distributed/test_protocol.rb +0 -525
- data/test/distributed/test_query.rb +0 -106
- data/test/distributed/test_remote_plan.rb +0 -491
- data/test/distributed/test_transaction.rb +0 -466
- data/test/mockups/external_process +0 -28
- data/test/mockups/tasks.rb +0 -27
- data/test/planning/test_loops.rb +0 -432
- data/test/planning/test_model.rb +0 -427
- data/test/planning/test_task.rb +0 -126
- data/test/relations/test_conflicts.rb +0 -42
- data/test/relations/test_dependency.rb +0 -324
- data/test/relations/test_ensured.rb +0 -38
- data/test/relations/test_executed_by.rb +0 -224
- data/test/relations/test_planned_by.rb +0 -56
- data/test/suite_core.rb +0 -29
- data/test/suite_distributed.rb +0 -10
- data/test/suite_planning.rb +0 -4
- data/test/suite_relations.rb +0 -8
- data/test/tasks/test_external_process.rb +0 -126
- data/test/tasks/test_thread_task.rb +0 -70
- data/test/test_bgl.rb +0 -528
- data/test/test_event.rb +0 -969
- data/test/test_exceptions.rb +0 -591
- data/test/test_execution_engine.rb +0 -987
- data/test/test_gui.rb +0 -20
- data/test/test_interface.rb +0 -43
- data/test/test_log.rb +0 -125
- data/test/test_log_server.rb +0 -133
- data/test/test_plan.rb +0 -418
- data/test/test_query.rb +0 -424
- data/test/test_relations.rb +0 -260
- data/test/test_state.rb +0 -432
- data/test/test_support.rb +0 -16
- data/test/test_task.rb +0 -1181
- data/test/test_testcase.rb +0 -138
- data/test/test_transactions.rb +0 -610
- data/test/test_transactions_proxy.rb +0 -216
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
module Roby
|
|
2
|
+
# Specialization of EventGenerator to represent task events
|
|
3
|
+
#
|
|
4
|
+
# It gives access to the task-specific information (associated task, event
|
|
5
|
+
# name, ...)
|
|
6
|
+
class TaskEventGenerator < EventGenerator
|
|
7
|
+
# The task we are part of
|
|
8
|
+
attr_reader :task
|
|
9
|
+
# The event symbol (its name as a Symbol object)
|
|
10
|
+
attr_reader :symbol
|
|
11
|
+
# Changes the underlying model
|
|
12
|
+
def override_model(model)
|
|
13
|
+
@event_model = model
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def initialize(task, model)
|
|
17
|
+
super(model.respond_to?(:call), plan: task.plan)
|
|
18
|
+
@task, @event_model = task, model
|
|
19
|
+
@symbol = model.symbol
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# The default command if the event is created with controlable: true.
|
|
23
|
+
# It emits the event on the task.
|
|
24
|
+
def default_command(context)
|
|
25
|
+
event_model.call(task, context)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def command=(block)
|
|
29
|
+
event_model.singleton_class.class_eval do
|
|
30
|
+
define_method(:call, &block)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# See PlanObject::child_plan_object.
|
|
35
|
+
child_plan_object :task
|
|
36
|
+
|
|
37
|
+
# The event plan. It is the same as task.plan and is actually updated
|
|
38
|
+
# by task.plan=. It is redefined here for performance reasons.
|
|
39
|
+
attr_reader :plan
|
|
40
|
+
|
|
41
|
+
# The event plan. It is the same as task.plan and is actually updated
|
|
42
|
+
# by task.plan=. It is redefined here for performance reasons.
|
|
43
|
+
def plan=(plan)
|
|
44
|
+
@plan = plan
|
|
45
|
+
@relation_graphs =
|
|
46
|
+
if plan then plan.event_relation_graphs
|
|
47
|
+
end
|
|
48
|
+
@execution_engine =
|
|
49
|
+
if plan && plan.executable? then plan.execution_engine
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def pending(sources)
|
|
54
|
+
super
|
|
55
|
+
if symbol == :start
|
|
56
|
+
task.freeze_delayed_arguments
|
|
57
|
+
plan.task_index.set_state(task, :starting?) if plan && !plan.template?
|
|
58
|
+
task.pending = false
|
|
59
|
+
task.starting = true
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def clear_pending
|
|
64
|
+
if @pending && symbol == :start
|
|
65
|
+
if !emitted? && !task.failed_to_start?
|
|
66
|
+
plan.task_index.set_state(task, :pending?) if plan && !plan.template?
|
|
67
|
+
task.pending = true
|
|
68
|
+
task.starting = false
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
super
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def called(context)
|
|
75
|
+
super
|
|
76
|
+
if terminal? && pending?
|
|
77
|
+
task.finishing = true
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def fire(event)
|
|
82
|
+
super
|
|
83
|
+
if symbol == :start
|
|
84
|
+
task.do_poll(plan)
|
|
85
|
+
elsif symbol == :stop
|
|
86
|
+
task.each_event do |ev|
|
|
87
|
+
ev.unreachable!(task.terminal_event)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Actually emits the event. This should not be used directly.
|
|
93
|
+
#
|
|
94
|
+
# It forwards the call to Task#fire
|
|
95
|
+
def fired(event) # :nodoc:
|
|
96
|
+
super
|
|
97
|
+
task.fired_event(event)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# See EventGenerator#related_tasks
|
|
101
|
+
def related_tasks(result = nil) # :nodoc:
|
|
102
|
+
tasks = super
|
|
103
|
+
tasks.delete(task)
|
|
104
|
+
tasks
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# See EventGenerator#each_handler
|
|
108
|
+
def each_handler # :nodoc:
|
|
109
|
+
if self_owned?
|
|
110
|
+
task.model.each_handler(event_model.symbol) { |o| yield(o) }
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
super
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# See EventGenerator#each_precondition
|
|
117
|
+
def each_precondition # :nodoc:
|
|
118
|
+
task.model.each_precondition(event_model.symbol) { |o| yield(o) }
|
|
119
|
+
super
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# See EventGenerator#controlable?
|
|
123
|
+
def controlable? # :nodoc:
|
|
124
|
+
event_model.controlable?
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Cached value for #terminal?
|
|
128
|
+
attr_writer :terminal_flag # :nodoc:
|
|
129
|
+
|
|
130
|
+
# Returns the value for #terminal_flag, updating it if needed
|
|
131
|
+
def terminal_flag # :nodoc:
|
|
132
|
+
if task.invalidated_terminal_flag?
|
|
133
|
+
task.update_terminal_flag
|
|
134
|
+
end
|
|
135
|
+
return @terminal_flag
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# True if this event is either forwarded to or signals the task's :stop event
|
|
139
|
+
def terminal?; !!terminal_flag end
|
|
140
|
+
# True if this event is either forwarded to or signals the task's :success event
|
|
141
|
+
def success?; terminal_flag == :success end
|
|
142
|
+
# True if this event is either forwarded to or signals the task's :failed event
|
|
143
|
+
def failure?; terminal_flag == :failure end
|
|
144
|
+
|
|
145
|
+
# @api private
|
|
146
|
+
#
|
|
147
|
+
# Helper for the signal and forward relation hooks that invalidates the
|
|
148
|
+
# events terminal flags when the event structure is changed
|
|
149
|
+
def invalidate_task_terminal_flag_if_needed(child)
|
|
150
|
+
if child.respond_to?(:task) && child.task == task
|
|
151
|
+
task.invalidate_terminal_flag
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Invalidates the task's terminal flag when the Forwarding and/or the
|
|
156
|
+
# Signal relation gets modified.
|
|
157
|
+
def added_signal(child, info) # :nodoc:
|
|
158
|
+
super
|
|
159
|
+
invalidate_task_terminal_flag_if_needed(child)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Invalidates the task's terminal flag when the Forwarding and/or the
|
|
163
|
+
# Signal relation gets modified.
|
|
164
|
+
def added_forwarding(child, info) # :nodoc:
|
|
165
|
+
super
|
|
166
|
+
invalidate_task_terminal_flag_if_needed(child)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Invalidates the task's terminal flag when the Forwarding and/or the
|
|
170
|
+
# Signal relation gets modified.
|
|
171
|
+
def removed_signal(child)
|
|
172
|
+
super
|
|
173
|
+
invalidate_task_terminal_flag_if_needed(child)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def removed_forwarding(child)
|
|
177
|
+
super
|
|
178
|
+
invalidate_task_terminal_flag_if_needed(child)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# See EventGenerator#new
|
|
182
|
+
def new(context, propagation_id = nil, time = nil) # :nodoc:
|
|
183
|
+
event_model.new(task, self, propagation_id || execution_engine.propagation_id, context, time || Time.now)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def to_s # :nodoc:
|
|
187
|
+
"#{task}/#{symbol}"
|
|
188
|
+
end
|
|
189
|
+
def inspect # :nodoc:
|
|
190
|
+
"#{task.inspect}/#{symbol}: #{history.to_s}"
|
|
191
|
+
end
|
|
192
|
+
def pretty_print(pp) # :nodoc:
|
|
193
|
+
pp.text "#{symbol} event of #{task.class}:0x#{task.address.to_s(16)}"
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# See EventGenerator#achieve_with
|
|
197
|
+
def achieve_with(obj) # :nodoc:
|
|
198
|
+
child_task, child_event = case obj
|
|
199
|
+
when Roby::Task then [obj, obj.event(:success)]
|
|
200
|
+
when Roby::TaskEventGenerator then [obj.task, obj]
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
if child_task
|
|
204
|
+
unless task.depends_on?(child_task)
|
|
205
|
+
task.depends_on child_task,
|
|
206
|
+
success: [child_event.symbol],
|
|
207
|
+
remove_when_done: true
|
|
208
|
+
end
|
|
209
|
+
super(child_event)
|
|
210
|
+
else
|
|
211
|
+
super(obj)
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def emit_failed(error = nil, message = nil)
|
|
216
|
+
exception = super
|
|
217
|
+
if symbol == :start
|
|
218
|
+
if !task.failed_to_start?
|
|
219
|
+
task.failed_to_start!(exception)
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Checks that the event can be called. Raises various exception
|
|
225
|
+
# when it is not the case.
|
|
226
|
+
def check_call_validity # :nodoc:
|
|
227
|
+
if error = super
|
|
228
|
+
if !error.kind_of?(UnreachableEvent)
|
|
229
|
+
return refine_call_exception(error)
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
if task.failed_to_start?
|
|
234
|
+
CommandRejected.new(self).
|
|
235
|
+
exception("#{symbol}! called by #{execution_engine.propagation_sources.to_a} but the task has failed to start: #{task.failure_reason}")
|
|
236
|
+
elsif task.event(:stop).emitted?
|
|
237
|
+
CommandRejected.new(self).
|
|
238
|
+
exception("#{symbol}! called by #{execution_engine.propagation_sources.to_a} but the task has finished. Task has been terminated by #{task.event(:stop).history.first.sources.to_a}.")
|
|
239
|
+
elsif task.finished? && !terminal?
|
|
240
|
+
CommandRejected.new(self).
|
|
241
|
+
exception("#{symbol}! called by #{execution_engine.propagation_sources.to_a} but the task has finished. Task has been terminated by #{task.event(:stop).history.first.sources.to_a}.")
|
|
242
|
+
elsif task.pending? && symbol != :start
|
|
243
|
+
CommandRejected.new(self).
|
|
244
|
+
exception("#{symbol}! called by #{execution_engine.propagation_sources.to_a} but the task has never been started")
|
|
245
|
+
elsif task.running? && symbol == :start
|
|
246
|
+
CommandRejected.new(self).
|
|
247
|
+
exception("#{symbol}! called by #{execution_engine.propagation_sources.to_a} but the task is already running. Task has been started by #{task.event(:start).history.first.sources.to_a}.")
|
|
248
|
+
else error
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def check_call_validity_after_calling
|
|
253
|
+
if error = super
|
|
254
|
+
refine_call_exception(error)
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# Checks that the event can be emitted. Raises various exception
|
|
259
|
+
# when it is not the case.
|
|
260
|
+
def check_emission_validity # :nodoc:
|
|
261
|
+
if error = super
|
|
262
|
+
refine_emit_exception(error)
|
|
263
|
+
else
|
|
264
|
+
task.check_emission_validity(self)
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# When an emissio and/or call exception is raised by the base
|
|
269
|
+
# EventGenerator methods, this method is used to transform it to the
|
|
270
|
+
# relevant task-related error.
|
|
271
|
+
def refine_call_exception (e) # :nodoc:
|
|
272
|
+
if task.partially_instanciated?
|
|
273
|
+
TaskEventNotExecutable.new(self).
|
|
274
|
+
exception("#{symbol}_event.call on #{task} which is partially instanciated\n" +
|
|
275
|
+
"The following arguments were not set:\n" +
|
|
276
|
+
task.list_unset_arguments.map {|n| " #{n}"}.join("\n"))
|
|
277
|
+
elsif !plan
|
|
278
|
+
TaskEventNotExecutable.new(self).
|
|
279
|
+
exception("#{symbol}_event.call on #{task} but the task has been removed from its plan")
|
|
280
|
+
elsif !plan.executable?
|
|
281
|
+
TaskEventNotExecutable.new(self).
|
|
282
|
+
exception("#{symbol}_event.call on #{task} but its plan is not executable")
|
|
283
|
+
elsif task.abstract?
|
|
284
|
+
TaskEventNotExecutable.new(self).
|
|
285
|
+
exception("#{symbol}_event.call on #{task} but the task is abstract")
|
|
286
|
+
elsif e.kind_of?(EventNotExecutable)
|
|
287
|
+
TaskEventNotExecutable.new(self).
|
|
288
|
+
exception("#{symbol}_event.call on #{task} which is not executable")
|
|
289
|
+
else e
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
# When an emissio and/or call exception is raised by the base
|
|
294
|
+
# EventGenerator methods, this method is used to transform it to the
|
|
295
|
+
# relevant task-related error.
|
|
296
|
+
def refine_emit_exception (e) # :nodoc:
|
|
297
|
+
if task.partially_instanciated?
|
|
298
|
+
TaskEventNotExecutable.new(self).
|
|
299
|
+
exception("#{symbol}_event.emit on #{task} which is partially instanciated\n" +
|
|
300
|
+
"The following arguments were not set:\n" +
|
|
301
|
+
task.list_unset_arguments.map {|n| " #{n}"}.join("\n"))
|
|
302
|
+
elsif !plan
|
|
303
|
+
TaskEventNotExecutable.new(self).
|
|
304
|
+
exception("#{symbol}_event.emit on #{task} but the task has been removed from its plan")
|
|
305
|
+
elsif !plan.executable?
|
|
306
|
+
TaskEventNotExecutable.new(self).
|
|
307
|
+
exception("#{symbol}_event.emit on #{task} but its plan is not executable")
|
|
308
|
+
elsif task.abstract?
|
|
309
|
+
TaskEventNotExecutable.new(self).
|
|
310
|
+
exception("#{symbol}_event.emit on #{task} but the task is abstract")
|
|
311
|
+
elsif e.kind_of?(EventNotExecutable)
|
|
312
|
+
TaskEventNotExecutable.new(self).
|
|
313
|
+
exception("#{symbol}_event.emit on #{task} which is not executable")
|
|
314
|
+
else e
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
def default_on_replace
|
|
319
|
+
if task.abstract? then :copy
|
|
320
|
+
else :drop
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
def on(on_replace: default_on_replace, once: false, &block)
|
|
325
|
+
super(on_replace: on_replace, once: once, &block)
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def create_transaction_proxy(transaction)
|
|
329
|
+
# Ensure that the task is proxied already
|
|
330
|
+
transaction.wrap_task(task).event(symbol)
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def match
|
|
334
|
+
Queries::TaskEventGeneratorMatcher.new(task, symbol)
|
|
335
|
+
end
|
|
336
|
+
end
|
|
337
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
module Roby
|
|
2
|
+
module TaskStructure
|
|
3
|
+
relation :Conflicts, noinfo: true
|
|
4
|
+
|
|
5
|
+
class Conflicts
|
|
6
|
+
module Extension
|
|
7
|
+
def conflicts_with(task)
|
|
8
|
+
# task.event(:stop).add_precedence event(:start)
|
|
9
|
+
add_conflicts(task)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
module ModelExtension
|
|
14
|
+
extend MetaRuby::Attributes
|
|
15
|
+
inherited_attribute(:conflicting_model, :conflicting_models) { Set.new }
|
|
16
|
+
|
|
17
|
+
def conflicts_with(model)
|
|
18
|
+
conflicting_models << model
|
|
19
|
+
model.conflicting_models << self
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def conflicts_with?(model)
|
|
23
|
+
each_conflicting_model do |m|
|
|
24
|
+
return true if model <= m
|
|
25
|
+
end
|
|
26
|
+
false
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
module EventGeneratorExtension
|
|
31
|
+
def calling(context)
|
|
32
|
+
super
|
|
33
|
+
return unless symbol == :start
|
|
34
|
+
|
|
35
|
+
# Check for conflicting tasks
|
|
36
|
+
result = Set.new
|
|
37
|
+
task.each_conflicts do |conflicting_task|
|
|
38
|
+
result << conflicting_task
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
models = task.class.conflicting_models
|
|
42
|
+
for model in models
|
|
43
|
+
for t in plan.find_tasks(model)
|
|
44
|
+
if t.running? && t != task
|
|
45
|
+
result << t
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
if !result.empty?
|
|
51
|
+
plan.control.conflict(task, result)
|
|
52
|
+
return
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Add the needed conflict relations
|
|
56
|
+
models = task.class.conflicting_models
|
|
57
|
+
for model in models
|
|
58
|
+
for t in plan.find_tasks(model)
|
|
59
|
+
t.conflicts_with task if t.pending? && t != task
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def fired(event)
|
|
65
|
+
super
|
|
66
|
+
|
|
67
|
+
if symbol == :stop
|
|
68
|
+
task.relation_graph_for(Conflicts).remove_vertex(task)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Class holding conflict error information
|
|
75
|
+
#
|
|
76
|
+
# Note that it is not an exception as a failed conflict is usually
|
|
77
|
+
# handled by calling #failed_to_start! on the newly started task
|
|
78
|
+
class ConflictError
|
|
79
|
+
attr_reader :starting_task
|
|
80
|
+
attr_reader :running_tasks
|
|
81
|
+
|
|
82
|
+
def initialize(starting_task, running_tasks)
|
|
83
|
+
@starting_task, @running_tasks = starting_task, running_tasks
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def pretty_print(pp)
|
|
87
|
+
pp.text "failed to start "
|
|
88
|
+
starting_task.pretty_print(pp)
|
|
89
|
+
pp.text "because it conflicts with #{running_tasks.size} running tasks"
|
|
90
|
+
pp.nest(2) do
|
|
91
|
+
runnning_tasks.each do |t|
|
|
92
|
+
pp.breakable
|
|
93
|
+
t.pretty_print(pp)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
Roby::TaskEventGenerator.class_eval do
|
|
101
|
+
prepend TaskStructure::Conflicts::EventGeneratorExtension
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
@@ -0,0 +1,932 @@
|
|
|
1
|
+
module Roby
|
|
2
|
+
module TaskStructure
|
|
3
|
+
DEPENDENCY_RELATION_ARGUMENTS =
|
|
4
|
+
[:model, :success, :failure, :remove_when_done, :consider_in_pending, :roles, :role]
|
|
5
|
+
|
|
6
|
+
relation :Dependency, child_name: :child, parent_name: :parent_task
|
|
7
|
+
|
|
8
|
+
class Dependency < Relations::TaskRelationGraph
|
|
9
|
+
attr_reader :interesting_events
|
|
10
|
+
attr_reader :failing_tasks
|
|
11
|
+
|
|
12
|
+
def initialize(observer: nil)
|
|
13
|
+
super(observer: observer)
|
|
14
|
+
@interesting_events = Array.new
|
|
15
|
+
@failing_tasks = Set.new
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# @api private
|
|
19
|
+
#
|
|
20
|
+
# Updates the dependency internal data to trigger errors / success when
|
|
21
|
+
# relevant events are emitted
|
|
22
|
+
def update_triggers_for(parent, child, info)
|
|
23
|
+
events = Set.new
|
|
24
|
+
if info[:success]
|
|
25
|
+
for event_name in info[:success].required_events
|
|
26
|
+
events << child.event(event_name)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
if info[:failure]
|
|
31
|
+
for event_name in info[:failure].required_events
|
|
32
|
+
events << child.event(event_name)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
if !events.empty?
|
|
37
|
+
parent.start_event.on(on_replace: :drop) do |ev|
|
|
38
|
+
ev.plan.task_relation_graph_for(self.class).interesting_events << ev.generator
|
|
39
|
+
end
|
|
40
|
+
events.each do |e|
|
|
41
|
+
e.if_unreachable do |reason, ev|
|
|
42
|
+
# The actualy graph of 'ev' might be different than self
|
|
43
|
+
# ... re-resolve
|
|
44
|
+
ev.plan.task_relation_graph_for(self.class).interesting_events << ev
|
|
45
|
+
end
|
|
46
|
+
e.on(on_replace: :drop) do |ev|
|
|
47
|
+
ev.plan.task_relation_graph_for(self.class).interesting_events << ev.generator
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Initial triggers
|
|
53
|
+
failing_tasks << child
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def self.merge_fullfilled_model(model, required_models, required_arguments)
|
|
57
|
+
model, tags, arguments = *model
|
|
58
|
+
|
|
59
|
+
tags = tags.dup
|
|
60
|
+
required_models = Array(required_models)
|
|
61
|
+
|
|
62
|
+
for m in required_models
|
|
63
|
+
if m.kind_of?(Roby::Models::TaskServiceModel)
|
|
64
|
+
tags << m
|
|
65
|
+
elsif m.has_ancestor?(model)
|
|
66
|
+
model = m
|
|
67
|
+
elsif !model.has_ancestor?(m)
|
|
68
|
+
raise Roby::ModelViolation, "inconsistency in fullfilled models: #{model} and #{m} are incompatible"
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
arguments = arguments.merge(required_arguments) do |name, old, new|
|
|
73
|
+
if old != new
|
|
74
|
+
raise Roby::ModelViolation, "inconsistency in fullfilled models: #{old} and #{new}"
|
|
75
|
+
end
|
|
76
|
+
old
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
return [model, tags, arguments]
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def self.validate_options(options, defaults = Hash.new)
|
|
83
|
+
defaults = Hash[model: [[Roby::Task], Hash.new],
|
|
84
|
+
success: nil,
|
|
85
|
+
failure: nil,
|
|
86
|
+
remove_when_done: true,
|
|
87
|
+
consider_in_pending: true,
|
|
88
|
+
roles: Set.new,
|
|
89
|
+
role: nil].merge(defaults)
|
|
90
|
+
Kernel.validate_options options, defaults
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Merges the dependency descriptions (i.e. the relation payload),
|
|
94
|
+
# verifying that the two provided option hashes are compatible
|
|
95
|
+
#
|
|
96
|
+
# @return [Hash] the merged options
|
|
97
|
+
# @raise [ModelViolation] if the two hashes are not compatible
|
|
98
|
+
def self.merge_dependency_options(opt1, opt2)
|
|
99
|
+
if opt1[:remove_when_done] != opt2[:remove_when_done]
|
|
100
|
+
raise Roby::ModelViolation, "incompatible dependency specification: trying to change the value of +remove_when_done+"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
result = { remove_when_done: opt1[:remove_when_done], consider_in_pending: opt1[:consider_in_pending] }
|
|
104
|
+
|
|
105
|
+
if opt1[:success] || opt2[:success]
|
|
106
|
+
result[:success] =
|
|
107
|
+
if !opt1[:success] then opt2[:success]
|
|
108
|
+
elsif !opt2[:success] then opt1[:success]
|
|
109
|
+
else
|
|
110
|
+
opt1[:success].and(opt2[:success])
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
if opt1[:failure] || opt2[:failure]
|
|
115
|
+
result[:failure] =
|
|
116
|
+
if !opt1[:failure] then opt2[:failure]
|
|
117
|
+
elsif !opt2[:failure] then opt1[:failure]
|
|
118
|
+
else
|
|
119
|
+
opt1[:failure].or(opt2[:failure])
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Check model compatibility
|
|
124
|
+
models1, arguments1 = opt1[:model]
|
|
125
|
+
models2, arguments2 = opt2[:model]
|
|
126
|
+
|
|
127
|
+
task_model1 = models1.find { |m| m <= Roby::Task }
|
|
128
|
+
task_model2 = models2.find { |m| m <= Roby::Task }
|
|
129
|
+
result_model = []
|
|
130
|
+
if task_model1 && task_model2
|
|
131
|
+
if task_model1.fullfills?(task_model2)
|
|
132
|
+
result_model << task_model1
|
|
133
|
+
elsif task_model2.fullfills?(task_model1)
|
|
134
|
+
result_model << task_model2
|
|
135
|
+
else
|
|
136
|
+
raise Roby::ModelViolation, "incompatible models #{task_model1} and #{task_model2}"
|
|
137
|
+
end
|
|
138
|
+
elsif task_model1
|
|
139
|
+
result_model << task_model1
|
|
140
|
+
elsif task_model2
|
|
141
|
+
result_model << task_model2
|
|
142
|
+
end
|
|
143
|
+
models1.each do |m|
|
|
144
|
+
next if m <= Roby::Task
|
|
145
|
+
if !models2.any? { |other_m| other_m.fullfills?(m) }
|
|
146
|
+
result_model << m
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
models2.each do |m|
|
|
150
|
+
next if m <= Roby::Task
|
|
151
|
+
if !models1.any? { |other_m| other_m.fullfills?(m) }
|
|
152
|
+
result_model << m
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
result[:model] = [result_model]
|
|
157
|
+
# Merge arguments
|
|
158
|
+
result[:model][1] = arguments1.merge(arguments2) do |key, old_value, new_value|
|
|
159
|
+
if old_value != new_value
|
|
160
|
+
raise Roby::ModelViolation, "incompatible argument constraint #{old_value} and #{new_value} for #{key}"
|
|
161
|
+
end
|
|
162
|
+
old_value
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Finally, merge the roles (the easy part ;-))
|
|
166
|
+
result[:roles] = opt1[:roles] | opt2[:roles]
|
|
167
|
+
|
|
168
|
+
result
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Called by the relation management when two dependency relations need
|
|
172
|
+
# to be merged
|
|
173
|
+
#
|
|
174
|
+
# @see Dependency.merge_dependency_options
|
|
175
|
+
def merge_info(parent, child, opt1, opt2)
|
|
176
|
+
result = Dependency.merge_dependency_options(opt1, opt2)
|
|
177
|
+
update_triggers_for(parent, child, result)
|
|
178
|
+
result
|
|
179
|
+
rescue Exception => e
|
|
180
|
+
raise e, e.message + " while updating the dependency information for #{parent} -> #{child}", e.backtrace
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Checks the structure of +plan+ w.r.t. the constraints of the hierarchy
|
|
184
|
+
# relations. It returns an array of ChildFailedError for all failed
|
|
185
|
+
# hierarchy relations
|
|
186
|
+
def check_structure(plan)
|
|
187
|
+
# The Set in #interesting_events is also referenced
|
|
188
|
+
# *separately* in EventStructure.gather_events. We therefore have to
|
|
189
|
+
# keep it (and can't use #partition). Yuk
|
|
190
|
+
events = Array.new
|
|
191
|
+
interesting_events.delete_if do |ev|
|
|
192
|
+
if ev.plan == plan
|
|
193
|
+
events << ev
|
|
194
|
+
true
|
|
195
|
+
else !ev.plan
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
tasks = Set.new
|
|
199
|
+
failing_tasks.delete_if do |task|
|
|
200
|
+
if task.plan == plan
|
|
201
|
+
tasks << task
|
|
202
|
+
true
|
|
203
|
+
else !task.plan
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
return Array.new if events.empty? && tasks.empty?
|
|
207
|
+
|
|
208
|
+
result = []
|
|
209
|
+
|
|
210
|
+
# Get the set of tasks for which a possible failure has been
|
|
211
|
+
# registered The tasks that are failing the hierarchy requirements
|
|
212
|
+
# are registered in Hierarchy.failing_tasks.
|
|
213
|
+
events.each do |event|
|
|
214
|
+
task = event.task
|
|
215
|
+
tasks << task
|
|
216
|
+
|
|
217
|
+
if event.symbol == :start # also add the children
|
|
218
|
+
task.each_child do |child_task, _|
|
|
219
|
+
tasks << child_task
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
for child in tasks
|
|
225
|
+
# Check if the task has been removed from the plan
|
|
226
|
+
next unless child.plan
|
|
227
|
+
|
|
228
|
+
removed_parents = []
|
|
229
|
+
child.each_parent_task do |parent|
|
|
230
|
+
next if parent.finished?
|
|
231
|
+
next unless parent.self_owned?
|
|
232
|
+
|
|
233
|
+
options = parent[child, Dependency]
|
|
234
|
+
success = options[:success]
|
|
235
|
+
failure = options[:failure]
|
|
236
|
+
|
|
237
|
+
has_success = success && success.evaluate(child)
|
|
238
|
+
if !has_success
|
|
239
|
+
has_failure = failure && failure.evaluate(child)
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
error = nil
|
|
243
|
+
if has_success
|
|
244
|
+
if options[:remove_when_done]
|
|
245
|
+
# Must not delete it here as we are iterating over the
|
|
246
|
+
# parents
|
|
247
|
+
removed_parents << parent
|
|
248
|
+
end
|
|
249
|
+
elsif has_failure
|
|
250
|
+
explanation = failure.explain_true(child)
|
|
251
|
+
error = Roby::ChildFailedError.new(parent, child, explanation, :failed_event)
|
|
252
|
+
elsif success && success.static?(child)
|
|
253
|
+
explanation = success.explain_static(child)
|
|
254
|
+
error = Roby::ChildFailedError.new(parent, child, explanation, :unreachable_success)
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
if error
|
|
258
|
+
if parent.running?
|
|
259
|
+
result << error
|
|
260
|
+
failing_tasks << child
|
|
261
|
+
elsif options[:consider_in_pending] && plan.control.pending_dependency_failed(parent, child, error)
|
|
262
|
+
result << error
|
|
263
|
+
failing_tasks << child
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
for parent in removed_parents
|
|
268
|
+
parent.remove_child child
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
result
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
module Extension
|
|
276
|
+
# True if +obj+ is a parent of this object in the hierarchy relation
|
|
277
|
+
# (+obj+ is realized by +self+)
|
|
278
|
+
def depended_upon_by?(obj); parent_object?(obj, Dependency) end
|
|
279
|
+
|
|
280
|
+
# True if +obj+ is a child of this object in the hierarchy relation.
|
|
281
|
+
# If +recursive+ is true, take into account the whole subgraph.
|
|
282
|
+
# Otherwise, only direct children are checked.
|
|
283
|
+
def depends_on?(obj, recursive: false)
|
|
284
|
+
if recursive
|
|
285
|
+
relation_graph_for(Dependency).
|
|
286
|
+
depth_first_visit(obj) { |v| return true if v == obj }
|
|
287
|
+
return false
|
|
288
|
+
else
|
|
289
|
+
child_object?(obj, Dependency)
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
# The set of parent objects in the Dependency relation
|
|
293
|
+
def parents; parent_objects(Dependency) end
|
|
294
|
+
# The set of child objects in the Dependency relation
|
|
295
|
+
def children; child_objects(Dependency) end
|
|
296
|
+
# Returns the single parent task for this task
|
|
297
|
+
#
|
|
298
|
+
# If there is more than one parent or no parent at all, raise an exception
|
|
299
|
+
def parent_task
|
|
300
|
+
parents = each_parent_task.to_a
|
|
301
|
+
if parents.size > 1
|
|
302
|
+
raise ArgumentError, "#{self} has #{parents.size} parents (#{parents.map(&:to_s).join(", ")}. A single parent was expected"
|
|
303
|
+
elsif parents.empty?
|
|
304
|
+
raise ArgumentError, "#{self} has no parents. A single parent was expected"
|
|
305
|
+
end
|
|
306
|
+
parents.first
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# Returns the set of roles that +child+ has
|
|
310
|
+
def roles_of(child)
|
|
311
|
+
info = self[child, Dependency]
|
|
312
|
+
info[:roles]
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
# Enumerates all the roles this task has
|
|
316
|
+
def each_role(&block)
|
|
317
|
+
if !block_given?
|
|
318
|
+
return enum_for(:each_role, &block)
|
|
319
|
+
end
|
|
320
|
+
each_parent_object(Dependency) do |parent|
|
|
321
|
+
yield(parent, parent.roles_of(self))
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
def roles
|
|
326
|
+
each_role.map { |_, roles| roles.to_a }.flatten.to_set
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
def has_role?(role_name)
|
|
330
|
+
!!find_child_from_role(role_name)
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
# Remove a given role this task's child
|
|
334
|
+
#
|
|
335
|
+
# @param [Task] child the child task
|
|
336
|
+
# @param [Array<String>] roles the roles that should be removed
|
|
337
|
+
# @param [Boolean] remove_child_when_empty if true (the default), the
|
|
338
|
+
# child will be removed from this task's children if the set of roles
|
|
339
|
+
# is empty
|
|
340
|
+
# @raise [ArgumentError] if the child does not have the expected role
|
|
341
|
+
# @return [Boolean] true if the child is still a child of this task
|
|
342
|
+
# after the call, and false otherwise
|
|
343
|
+
def remove_roles(child, *roles, remove_child_when_empty: true)
|
|
344
|
+
dependency_info = self[child, Dependency].dup
|
|
345
|
+
child_roles = dependency_info[:roles].dup
|
|
346
|
+
roles.each do |r|
|
|
347
|
+
if !child_roles.include?(r)
|
|
348
|
+
raise ArgumentError, "#{r} is not a role of #{child} with respect to #{self}"
|
|
349
|
+
end
|
|
350
|
+
child_roles.delete(r)
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
if child_roles.empty? && remove_child_when_empty
|
|
354
|
+
remove_child(child)
|
|
355
|
+
false
|
|
356
|
+
else
|
|
357
|
+
dependency_info[:roles] = child_roles
|
|
358
|
+
self[child, Dependency] = dependency_info
|
|
359
|
+
true
|
|
360
|
+
end
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
# Returns the child whose role is +role_name+
|
|
364
|
+
#
|
|
365
|
+
# @return [nil,Task] the task if a dependency with the given role is
|
|
366
|
+
# found, and nil otherwise
|
|
367
|
+
def find_child_from_role(role_name)
|
|
368
|
+
each_out_neighbour_merged(Dependency, intrusive: false) do |myself, child|
|
|
369
|
+
roles = myself[child, Dependency][:roles]
|
|
370
|
+
if roles.include?(role_name)
|
|
371
|
+
if plan
|
|
372
|
+
return plan[child]
|
|
373
|
+
else
|
|
374
|
+
return child
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
nil
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
# Returns the child whose role is +role_name+
|
|
382
|
+
#
|
|
383
|
+
# If +validate+ is true (the default), raises ArgumentError if there is
|
|
384
|
+
# none. Otherwise, returns nil. This argument is meant only to avoid the
|
|
385
|
+
# costly operation of raising an exception in cases it is expected that
|
|
386
|
+
# the role may not exist.
|
|
387
|
+
def child_from_role(role_name, validate = true)
|
|
388
|
+
if !validate
|
|
389
|
+
Roby.warn_deprecated "#child_from_role(name, false) has been replaced by #find_child_from_role"
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
child = find_child_from_role(role_name)
|
|
393
|
+
if !child && validate
|
|
394
|
+
known_children = Hash.new
|
|
395
|
+
each_out_neighbour_merged(Dependency, intrusive: false) do |myself, child|
|
|
396
|
+
myself[child, Dependency][:roles].each do |role|
|
|
397
|
+
known_children[role] = child
|
|
398
|
+
end
|
|
399
|
+
end
|
|
400
|
+
raise Roby::NoSuchChild.new(self, role_name, known_children), "#{self} has no child with the role '#{role_name}'"
|
|
401
|
+
end
|
|
402
|
+
child
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
# Returns a task in the dependency hierarchy of this task by following
|
|
406
|
+
# the roles. +path+ is an array of role names, and the method will
|
|
407
|
+
# follow the trail until the desired task
|
|
408
|
+
#
|
|
409
|
+
# Raises ArgumentError if the child does not exist
|
|
410
|
+
#
|
|
411
|
+
# See #role_path to get a role path for a specific task
|
|
412
|
+
def resolve_role_path(*path)
|
|
413
|
+
if path.size == 1 && path[0].respond_to?(:to_ary)
|
|
414
|
+
path = path[0]
|
|
415
|
+
end
|
|
416
|
+
# Special case for ease of use in algorithms
|
|
417
|
+
if path.empty?
|
|
418
|
+
return self
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
up_until_now = []
|
|
422
|
+
path.inject(self) do |task, role|
|
|
423
|
+
up_until_now << role
|
|
424
|
+
if !(next_task = task.find_child_from_role(role))
|
|
425
|
+
raise ArgumentError, "the child #{up_until_now.join(".")} of #{task} does not exist"
|
|
426
|
+
end
|
|
427
|
+
next_task
|
|
428
|
+
end
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
# Returns a set role paths that lead to +task+ when starting from +self+
|
|
432
|
+
#
|
|
433
|
+
# A role path is an array of roles that lead to +task+ when starting by
|
|
434
|
+
# +self+.
|
|
435
|
+
#
|
|
436
|
+
# I.e. if ['role1', 'role2', 'role3'] is a role path from +self+ to
|
|
437
|
+
# +task, it means that
|
|
438
|
+
#
|
|
439
|
+
# task1 = self.child_from_role('role1')
|
|
440
|
+
# task2 = task1.child_from_role('role2')
|
|
441
|
+
# task = task2.child_from_role('role3')
|
|
442
|
+
#
|
|
443
|
+
# The method returns a set of role paths, as there may be multiple paths
|
|
444
|
+
# leading from +self+ to +task+
|
|
445
|
+
#
|
|
446
|
+
# See #resolve_role_path to get a task from its role path
|
|
447
|
+
def role_paths(task, validate = true)
|
|
448
|
+
if task == self
|
|
449
|
+
return []
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
result = []
|
|
453
|
+
task.each_role do |parent, roles|
|
|
454
|
+
if parent == self
|
|
455
|
+
new_paths = roles.map { |r| [r] }
|
|
456
|
+
elsif heads = role_paths(parent, false)
|
|
457
|
+
heads.each do |h|
|
|
458
|
+
roles.each do |t|
|
|
459
|
+
result << (h.dup << t)
|
|
460
|
+
end
|
|
461
|
+
end
|
|
462
|
+
end
|
|
463
|
+
if new_paths
|
|
464
|
+
result.concat(new_paths)
|
|
465
|
+
end
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
if result.empty?
|
|
469
|
+
if validate
|
|
470
|
+
raise ArgumentError, "#{task} can not be reached from #{self}"
|
|
471
|
+
end
|
|
472
|
+
return
|
|
473
|
+
end
|
|
474
|
+
result
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
# Adds +task+ as a child of +self+ in the Dependency relation. The
|
|
478
|
+
# following options are allowed:
|
|
479
|
+
#
|
|
480
|
+
# success:: the list of success events. The default is [:success]
|
|
481
|
+
# failure:: the list of failing events. The default is [:failed]
|
|
482
|
+
# model::
|
|
483
|
+
# a <tt>[task_model, arguments]</tt> pair which defines the task
|
|
484
|
+
# model the parent is expecting. The default value is to get these
|
|
485
|
+
# parameters from +task+
|
|
486
|
+
#
|
|
487
|
+
# The +success+ set describes the events of the child task that are
|
|
488
|
+
# _required_ by the parent task. More specifically, the child task
|
|
489
|
+
# remains useful for the parent task as long as none of these events are
|
|
490
|
+
# emitted. By default, it is the +success+ event. Of course, an error
|
|
491
|
+
# condition is encountered when all events of +success+ become
|
|
492
|
+
# unreachable. In addition, the relation is removed if the
|
|
493
|
+
# +remove_when_done+ flag is set to true (false by default).
|
|
494
|
+
#
|
|
495
|
+
# The +failure+ set describes the events of the child task which are an
|
|
496
|
+
# error condition from the parent task point of view.
|
|
497
|
+
#
|
|
498
|
+
# In both error cases, a +ChildFailedError+ exception is raised.
|
|
499
|
+
def depends_on(task, options = {})
|
|
500
|
+
if task.respond_to?(:as_plan)
|
|
501
|
+
task = task.as_plan
|
|
502
|
+
end
|
|
503
|
+
if task == self
|
|
504
|
+
raise ArgumentError, "cannot add a dependency of a task to itself"
|
|
505
|
+
end
|
|
506
|
+
|
|
507
|
+
options = Dependency.validate_options options,
|
|
508
|
+
model: [task.provided_models, task.meaningful_arguments],
|
|
509
|
+
success: :success.to_unbound_task_predicate,
|
|
510
|
+
failure: false.to_unbound_task_predicate,
|
|
511
|
+
remove_when_done: true,
|
|
512
|
+
consider_in_pending: true,
|
|
513
|
+
roles: nil,
|
|
514
|
+
role: nil
|
|
515
|
+
|
|
516
|
+
# We accept
|
|
517
|
+
#
|
|
518
|
+
# model
|
|
519
|
+
# [model1, model2]
|
|
520
|
+
# [model1, arguments]
|
|
521
|
+
# [[model1, model2], arguments]
|
|
522
|
+
if !options[:model].respond_to?(:to_ary)
|
|
523
|
+
options[:model] = [Array(options[:model]), Hash.new]
|
|
524
|
+
elsif options[:model].size == 2
|
|
525
|
+
if !options[:model].first.respond_to?(:to_ary)
|
|
526
|
+
if options[:model].last.kind_of?(Hash)
|
|
527
|
+
options[:model] = [Array(options[:model].first), options[:model].last]
|
|
528
|
+
else
|
|
529
|
+
options[:model] = [options[:model], Hash.new]
|
|
530
|
+
end
|
|
531
|
+
end
|
|
532
|
+
elsif !options[:model].first.respond_to?(:to_ary)
|
|
533
|
+
options[:model] = [Array(options[:model]), Hash.new]
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
roles = options[:roles] || Set.new
|
|
537
|
+
if role = options.delete(:role)
|
|
538
|
+
roles << role.to_str
|
|
539
|
+
end
|
|
540
|
+
roles = roles.map { |r| r.to_str }
|
|
541
|
+
options[:roles] = roles.to_set
|
|
542
|
+
|
|
543
|
+
if options[:success].nil?
|
|
544
|
+
options[:success] = []
|
|
545
|
+
end
|
|
546
|
+
options[:success] = Array[*options[:success]].
|
|
547
|
+
map { |predicate| predicate.to_unbound_task_predicate }.
|
|
548
|
+
inject(&:or)
|
|
549
|
+
|
|
550
|
+
if options[:failure].nil?
|
|
551
|
+
options[:failure] = []
|
|
552
|
+
end
|
|
553
|
+
options[:failure] = Array[*options[:failure]].
|
|
554
|
+
map { |predicate| predicate.to_unbound_task_predicate }.
|
|
555
|
+
inject(&:or)
|
|
556
|
+
|
|
557
|
+
#options[:success] ||= false.to_unbound_task_predicate
|
|
558
|
+
#options[:failure] ||= false.to_unbound_task_predicate
|
|
559
|
+
|
|
560
|
+
# Validate failure and success event names
|
|
561
|
+
if options[:success]
|
|
562
|
+
not_there = options[:success].required_events.
|
|
563
|
+
find_all { |name| !task.has_event?(name) }
|
|
564
|
+
if !not_there.empty?
|
|
565
|
+
raise ArgumentError, "#{task} does not have the following events: #{not_there.join(", ")}"
|
|
566
|
+
end
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
if options[:failure]
|
|
570
|
+
not_there = options[:failure].required_events.
|
|
571
|
+
find_all { |name| !task.has_event?(name) }
|
|
572
|
+
if !not_there.empty?
|
|
573
|
+
raise ArgumentError, "#{task} does not have the following events: #{not_there.join(", ")}"
|
|
574
|
+
end
|
|
575
|
+
end
|
|
576
|
+
|
|
577
|
+
# There is no positive events in success. Behind the scenes, it
|
|
578
|
+
# actually means that the task does not have to start (since nothing
|
|
579
|
+
# in :success would become unreachable)
|
|
580
|
+
#
|
|
581
|
+
# Add !:start in failure
|
|
582
|
+
if !options[:success]
|
|
583
|
+
not_started = :start.to_unbound_task_predicate.never
|
|
584
|
+
if options[:failure]
|
|
585
|
+
options[:failure] = not_started.or(options[:failure])
|
|
586
|
+
else
|
|
587
|
+
options[:failure] = not_started
|
|
588
|
+
end
|
|
589
|
+
end
|
|
590
|
+
|
|
591
|
+
required_model, required_args = *options[:model]
|
|
592
|
+
if !required_args.respond_to?(:to_hash)
|
|
593
|
+
raise ArgumentError, "argument specification must be a hash, got #{required_args} (#{required_args.class})"
|
|
594
|
+
elsif !task.fullfills?(required_model, required_args)
|
|
595
|
+
raise ArgumentError, "task #{task} does not fullfill the provided model #{options[:model]}"
|
|
596
|
+
end
|
|
597
|
+
|
|
598
|
+
# Check if there is already a dependency link. If it is the case,
|
|
599
|
+
# merge the options. Otherwise, just add.
|
|
600
|
+
add_child(task, options)
|
|
601
|
+
task
|
|
602
|
+
end
|
|
603
|
+
|
|
604
|
+
def remove_dependency(task_or_role)
|
|
605
|
+
if task_or_role.respond_to?(:to_str)
|
|
606
|
+
remove_child(child_from_role(task_or_role))
|
|
607
|
+
else
|
|
608
|
+
remove_child(task_or_role)
|
|
609
|
+
end
|
|
610
|
+
end
|
|
611
|
+
|
|
612
|
+
# Set up the event gathering needed by Dependency.check_structure
|
|
613
|
+
def added_child(child, info) # :nodoc:
|
|
614
|
+
super
|
|
615
|
+
relation_graphs[Dependency].update_triggers_for(self, child, info)
|
|
616
|
+
end
|
|
617
|
+
|
|
618
|
+
# Return the set of this task children for which the :start event has
|
|
619
|
+
# no parent in CausalLinks
|
|
620
|
+
def first_children
|
|
621
|
+
result = Set.new
|
|
622
|
+
|
|
623
|
+
causal_link_graph = plan.event_relation_graph_for(EventStructure::CausalLink)
|
|
624
|
+
relation_graph_for(Dependency).depth_first_visit(self) do |task|
|
|
625
|
+
next if task == self
|
|
626
|
+
if task != self && causal_link_graph.root?(task.start_event)
|
|
627
|
+
result << task
|
|
628
|
+
end
|
|
629
|
+
end
|
|
630
|
+
result
|
|
631
|
+
end
|
|
632
|
+
|
|
633
|
+
# Sets a base model specification that must be met by this task
|
|
634
|
+
#
|
|
635
|
+
# In normal operations, the fullfilled model returned by
|
|
636
|
+
# #fullfilled_model is computed from the dependency relations in which
|
|
637
|
+
# +self+ is a child.
|
|
638
|
+
#
|
|
639
|
+
# However, this fails in case +self+ is a root task in the dependency
|
|
640
|
+
# relation. Moreover, it might be handy to over-constrain the model
|
|
641
|
+
# computed through the dependency relation.
|
|
642
|
+
#
|
|
643
|
+
# In both cases, a model can be specified explicitely by setting the
|
|
644
|
+
# fullfilled_model attribute. The value has to be
|
|
645
|
+
#
|
|
646
|
+
# [task_model, [tag1, tag2, ...], task_arguments]
|
|
647
|
+
#
|
|
648
|
+
# For instance, a completely non-constrained model would be
|
|
649
|
+
#
|
|
650
|
+
# [Roby::Task, [], {}]
|
|
651
|
+
#
|
|
652
|
+
# This parameter can be set model-wide by using #fullfilled_model= on
|
|
653
|
+
# the class object
|
|
654
|
+
def fullfilled_model=(model)
|
|
655
|
+
if !model[0].kind_of?(Class)
|
|
656
|
+
raise ArgumentError, "expected a task model as first element, got #{model[0]}"
|
|
657
|
+
end
|
|
658
|
+
if !model[1].respond_to?(:to_ary)
|
|
659
|
+
raise ArgumentError, "expected an array as second element, got #{model[1]}"
|
|
660
|
+
elsif !model[1].all? { |t| t.kind_of?(Roby::Models::TaskServiceModel) }
|
|
661
|
+
raise ArgumentError, "expected an array of model tags as second element, got #{model[1]}"
|
|
662
|
+
end
|
|
663
|
+
|
|
664
|
+
if !model[2].respond_to?(:to_hash)
|
|
665
|
+
raise ArgumentError, "expected a hash as third element, got #{model[2]}"
|
|
666
|
+
end
|
|
667
|
+
@fullfilled_model = model
|
|
668
|
+
end
|
|
669
|
+
|
|
670
|
+
# The list of models and arguments that this task fullfilles
|
|
671
|
+
#
|
|
672
|
+
# If there is a task model in the list of models, it is always the first
|
|
673
|
+
# element of the model set
|
|
674
|
+
#
|
|
675
|
+
# @return [(Array<Model<Task>,Model<TaskService>>,{String=>Object}]
|
|
676
|
+
#
|
|
677
|
+
# Beware that, for historical reasons, this is not the same format than
|
|
678
|
+
# {#fullfilled_model=}
|
|
679
|
+
def fullfilled_model
|
|
680
|
+
if current_model = explicit_fullfilled_model
|
|
681
|
+
has_value = true
|
|
682
|
+
else current_model = [Roby::Task, [], {}]
|
|
683
|
+
end
|
|
684
|
+
|
|
685
|
+
each_in_neighbour_merged(Dependency, intrusive: false) do |myself, parent|
|
|
686
|
+
has_value = true
|
|
687
|
+
|
|
688
|
+
required_models, required_arguments = parent[myself, Dependency][:model]
|
|
689
|
+
current_model = Dependency.merge_fullfilled_model(current_model,
|
|
690
|
+
required_models, required_arguments)
|
|
691
|
+
end
|
|
692
|
+
|
|
693
|
+
if !has_value
|
|
694
|
+
model = self.model.fullfilled_model.find_all { |m| m <= Roby::Task }.min
|
|
695
|
+
[[model], self.meaningful_arguments]
|
|
696
|
+
else
|
|
697
|
+
model, tags, arguments = *current_model
|
|
698
|
+
tags = tags.dup
|
|
699
|
+
tags.unshift model
|
|
700
|
+
[tags, arguments]
|
|
701
|
+
end
|
|
702
|
+
end
|
|
703
|
+
|
|
704
|
+
# True if #fullfilled_model has been set on this task or on this task's
|
|
705
|
+
# model
|
|
706
|
+
#
|
|
707
|
+
# @return [Boolean]
|
|
708
|
+
def explicit_fullfilled_model?
|
|
709
|
+
!!explicit_fullfilled_model
|
|
710
|
+
end
|
|
711
|
+
|
|
712
|
+
# Returns an explicitly set {#fullfilled_model}
|
|
713
|
+
#
|
|
714
|
+
# @return [nil,Object] either nil if no explicit model has been set, or
|
|
715
|
+
# the model in the same format as expected by {#fullfilled_model=}
|
|
716
|
+
# (which is different than the value returned by {#fullfilled_model})
|
|
717
|
+
def explicit_fullfilled_model
|
|
718
|
+
if explicit = @fullfilled_model
|
|
719
|
+
explicit
|
|
720
|
+
elsif explicit = self.model.explicit_fullfilled_model
|
|
721
|
+
tasks, tags = explicit.partition { |m| m <= Roby::Task }
|
|
722
|
+
[tasks.first || Roby::Task, tags, Hash.new]
|
|
723
|
+
end
|
|
724
|
+
end
|
|
725
|
+
|
|
726
|
+
# Returns the set of models this task is providing by itself
|
|
727
|
+
#
|
|
728
|
+
# It differs from #fullfilled_model because it is not considering the
|
|
729
|
+
# models that are required because of the dependency relation
|
|
730
|
+
#
|
|
731
|
+
# @return [Array<Models::Task,TaskService>]
|
|
732
|
+
# @see #fullfilled_model
|
|
733
|
+
def provided_models
|
|
734
|
+
if model = explicit_fullfilled_model
|
|
735
|
+
[model[0]] + model[1]
|
|
736
|
+
else
|
|
737
|
+
models = self.model.fullfilled_model
|
|
738
|
+
task_class = models.find { |m| m.kind_of?(Class) }
|
|
739
|
+
[task_class] + models.find_all { |m| !task_class.has_ancestor?(m) }
|
|
740
|
+
end
|
|
741
|
+
end
|
|
742
|
+
|
|
743
|
+
# Enumerates the models that are fullfilled by this task
|
|
744
|
+
#
|
|
745
|
+
# @return [Array<Model<Task>,TaskService>]
|
|
746
|
+
# @see #provided_models
|
|
747
|
+
def each_fullfilled_model(&block)
|
|
748
|
+
fullfilled_model[0].each(&block)
|
|
749
|
+
end
|
|
750
|
+
|
|
751
|
+
# Remove all children that have successfully finished
|
|
752
|
+
def remove_finished_children
|
|
753
|
+
# We call #to_a to get a copy of children, since we will remove
|
|
754
|
+
# children in the block. Note that we can't use #delete_if here
|
|
755
|
+
# since #children is a relation enumerator (not the relation list
|
|
756
|
+
# itself)
|
|
757
|
+
children = each_child.to_a
|
|
758
|
+
for child in children
|
|
759
|
+
child, info = child
|
|
760
|
+
if info[:success].evaluate(child)
|
|
761
|
+
remove_child(child)
|
|
762
|
+
end
|
|
763
|
+
end
|
|
764
|
+
end
|
|
765
|
+
|
|
766
|
+
def has_through_method_missing?(m)
|
|
767
|
+
MetaRuby::DSLs.has_through_method_missing?(
|
|
768
|
+
self, m, '_child' => :has_role?)
|
|
769
|
+
end
|
|
770
|
+
def find_through_method_missing(m, args)
|
|
771
|
+
MetaRuby::DSLs.find_through_method_missing(
|
|
772
|
+
self, m, args, '_child' => :find_child_from_role)
|
|
773
|
+
end
|
|
774
|
+
end
|
|
775
|
+
|
|
776
|
+
module ModelExtension
|
|
777
|
+
# True if a fullfilled model has been explicitly set on self
|
|
778
|
+
# @return [Boolean]
|
|
779
|
+
def explicit_fullfilled_model?; !!@fullfilled_model end
|
|
780
|
+
|
|
781
|
+
# Returns an explicitly set {#fullfilled_model}
|
|
782
|
+
#
|
|
783
|
+
# @return [nil,Array<Models::Task,TaskService>] either nil if no
|
|
784
|
+
# explicit model has been set, or the list of models it must fullfill
|
|
785
|
+
def explicit_fullfilled_model; @fullfilled_model end
|
|
786
|
+
|
|
787
|
+
# Specifies the models that all instances of this task must fullfill
|
|
788
|
+
#
|
|
789
|
+
# This is usually used to under-constraint the model instances
|
|
790
|
+
#
|
|
791
|
+
# @param [Array<Models::Task,TaskService>] the list of models
|
|
792
|
+
def fullfilled_model=(models)
|
|
793
|
+
if !models.respond_to?(:to_ary)
|
|
794
|
+
raise ArgumentError, "expected an array, got #{models}"
|
|
795
|
+
elsif !models.all? { |t| t.kind_of?(Roby::Models::TaskServiceModel) || (t.respond_to?(:<=) && (t <= Roby::Task)) }
|
|
796
|
+
raise ArgumentError, "expected a submodel of TaskService, got #{models}"
|
|
797
|
+
end
|
|
798
|
+
|
|
799
|
+
@fullfilled_model = models
|
|
800
|
+
end
|
|
801
|
+
|
|
802
|
+
# @api private
|
|
803
|
+
#
|
|
804
|
+
# @return [Array<Models::Task,TaskService>] the list of models
|
|
805
|
+
# fullfilled by this task
|
|
806
|
+
def implicit_fullfilled_model
|
|
807
|
+
if !@implicit_fullfilled_model
|
|
808
|
+
@implicit_fullfilled_model = Array.new
|
|
809
|
+
ancestors.each do |m|
|
|
810
|
+
next if m.singleton_class?
|
|
811
|
+
if m.kind_of?(Class) || (m.kind_of?(Roby::Models::TaskServiceModel) && m != Roby::TaskService)
|
|
812
|
+
@implicit_fullfilled_model << m
|
|
813
|
+
end
|
|
814
|
+
|
|
815
|
+
if m == Roby::Task
|
|
816
|
+
break
|
|
817
|
+
end
|
|
818
|
+
end
|
|
819
|
+
end
|
|
820
|
+
@implicit_fullfilled_model
|
|
821
|
+
end
|
|
822
|
+
|
|
823
|
+
# Returns the model that all instances of this taks model fullfill
|
|
824
|
+
#
|
|
825
|
+
# (see Dependency::Extension#fullfilled_model)
|
|
826
|
+
def fullfilled_model
|
|
827
|
+
explicit_fullfilled_model || implicit_fullfilled_model
|
|
828
|
+
end
|
|
829
|
+
|
|
830
|
+
# Enumerates the models that all instances of this task model fullfill
|
|
831
|
+
#
|
|
832
|
+
# @yieldparam [Model<Task>,Model<TaskService>] model
|
|
833
|
+
# @return [void]
|
|
834
|
+
def each_fullfilled_model(&block)
|
|
835
|
+
fullfilled_model.each(&block)
|
|
836
|
+
end
|
|
837
|
+
end
|
|
838
|
+
end
|
|
839
|
+
end
|
|
840
|
+
|
|
841
|
+
# This exception is raised when a {hierarchy relation}[classes/Roby/TaskStructure/Hierarchy.html] fails
|
|
842
|
+
class ChildFailedError < RelationFailedError
|
|
843
|
+
# The child in the relation
|
|
844
|
+
def child; failed_task end
|
|
845
|
+
# The relation parameters (i.e. the hash given to #depends_on)
|
|
846
|
+
attr_reader :relation
|
|
847
|
+
# The Explanation object that describes why the relation failed
|
|
848
|
+
attr_reader :explanation
|
|
849
|
+
# @return [Symbol] the fault mode. It can either be :failed_event or
|
|
850
|
+
# :unreachable_success
|
|
851
|
+
attr_reader :mode
|
|
852
|
+
|
|
853
|
+
def initialize(parent, child, explanation, mode)
|
|
854
|
+
@explanation = explanation
|
|
855
|
+
@mode = mode
|
|
856
|
+
|
|
857
|
+
events, generators, others = [], [], []
|
|
858
|
+
explanation.elements.each do |e|
|
|
859
|
+
case e
|
|
860
|
+
when Event then events << e
|
|
861
|
+
when EventGenerator then generators << e
|
|
862
|
+
else others << e
|
|
863
|
+
end
|
|
864
|
+
end
|
|
865
|
+
|
|
866
|
+
failure_point =
|
|
867
|
+
if events.size > 2 || !others.empty?
|
|
868
|
+
child
|
|
869
|
+
else
|
|
870
|
+
base_event = events.first || generators.first
|
|
871
|
+
if explanation.value.nil? # unreachability
|
|
872
|
+
reason = base_event.unreachability_reason
|
|
873
|
+
if reason.respond_to?(:task) && reason.task == child
|
|
874
|
+
reason
|
|
875
|
+
else
|
|
876
|
+
base_event
|
|
877
|
+
end
|
|
878
|
+
elsif base_event.respond_to?(:root_task_sources)
|
|
879
|
+
sources = base_event.root_task_sources
|
|
880
|
+
if sources.size == 1
|
|
881
|
+
sources.first
|
|
882
|
+
else
|
|
883
|
+
base_event
|
|
884
|
+
end
|
|
885
|
+
else
|
|
886
|
+
base_event
|
|
887
|
+
end
|
|
888
|
+
end
|
|
889
|
+
|
|
890
|
+
super(failure_point)
|
|
891
|
+
|
|
892
|
+
report_exceptions_from(explanation)
|
|
893
|
+
@parent = parent
|
|
894
|
+
@relation = parent[child, TaskStructure::Dependency]
|
|
895
|
+
if @relation
|
|
896
|
+
@relation = @relation.dup
|
|
897
|
+
end
|
|
898
|
+
end
|
|
899
|
+
|
|
900
|
+
def pretty_print(pp) # :nodoc:
|
|
901
|
+
pp.text "#{child} failed"
|
|
902
|
+
pp.breakable
|
|
903
|
+
pp.text "child #{relation[:roles].to_a.join(", ")} of #{parent}"
|
|
904
|
+
pp.breakable
|
|
905
|
+
if mode == :failed_event
|
|
906
|
+
pp.text "triggered the failure predicate '#{relation[:failure]}': "
|
|
907
|
+
elsif mode == :unreachable_success
|
|
908
|
+
pp.text "cannot reach the success condition '#{relation[:success]}': "
|
|
909
|
+
end
|
|
910
|
+
explanation.pretty_print(pp)
|
|
911
|
+
|
|
912
|
+
pp.breakable
|
|
913
|
+
pp.text "The failed relation is"
|
|
914
|
+
pp.breakable
|
|
915
|
+
pp.nest(2) do
|
|
916
|
+
pp.text " "
|
|
917
|
+
parent.pretty_print pp
|
|
918
|
+
pp.breakable
|
|
919
|
+
pp.text "depends_on "
|
|
920
|
+
child.pretty_print pp
|
|
921
|
+
end
|
|
922
|
+
pp.breakable
|
|
923
|
+
end
|
|
924
|
+
def backtrace; [] end
|
|
925
|
+
|
|
926
|
+
# True if +obj+ is involved in this exception
|
|
927
|
+
def involved_plan_object?(obj)
|
|
928
|
+
super || obj == parent
|
|
929
|
+
end
|
|
930
|
+
end
|
|
931
|
+
end
|
|
932
|
+
|