roby 0.7
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.
- data/.gitignore +29 -0
- data/History.txt +4 -0
- data/License-fr.txt +519 -0
- data/License.txt +515 -0
- data/Manifest.txt +245 -0
- data/NOTES +4 -0
- data/README.txt +163 -0
- data/Rakefile +161 -0
- data/TODO.txt +146 -0
- data/app/README.txt +24 -0
- data/app/Rakefile +8 -0
- data/app/config/ROBOT.rb +5 -0
- data/app/config/app.yml +91 -0
- data/app/config/init.rb +7 -0
- data/app/config/roby.yml +3 -0
- data/app/controllers/.gitattributes +0 -0
- data/app/controllers/ROBOT.rb +2 -0
- data/app/data/.gitattributes +0 -0
- data/app/planners/ROBOT/main.rb +6 -0
- data/app/planners/main.rb +5 -0
- data/app/scripts/distributed +3 -0
- data/app/scripts/generate/bookmarks +3 -0
- data/app/scripts/replay +3 -0
- data/app/scripts/results +3 -0
- data/app/scripts/run +3 -0
- data/app/scripts/server +3 -0
- data/app/scripts/shell +3 -0
- data/app/scripts/test +3 -0
- data/app/tasks/.gitattributes +0 -0
- data/app/tasks/ROBOT/.gitattributes +0 -0
- data/bin/roby +210 -0
- data/bin/roby-log +168 -0
- data/bin/roby-shell +25 -0
- data/doc/images/event_generalization.png +0 -0
- data/doc/images/exception_propagation_1.png +0 -0
- data/doc/images/exception_propagation_2.png +0 -0
- data/doc/images/exception_propagation_3.png +0 -0
- data/doc/images/exception_propagation_4.png +0 -0
- data/doc/images/exception_propagation_5.png +0 -0
- data/doc/images/replay_handler_error.png +0 -0
- data/doc/images/replay_handler_error_0.png +0 -0
- data/doc/images/replay_handler_error_1.png +0 -0
- data/doc/images/roby_cycle_overview.png +0 -0
- data/doc/images/roby_replay_02.png +0 -0
- data/doc/images/roby_replay_03.png +0 -0
- data/doc/images/roby_replay_04.png +0 -0
- data/doc/images/roby_replay_event_representation.png +0 -0
- data/doc/images/roby_replay_first_state.png +0 -0
- data/doc/images/roby_replay_relations.png +0 -0
- data/doc/images/roby_replay_startup.png +0 -0
- data/doc/images/task_event_generalization.png +0 -0
- data/doc/papers.rdoc +11 -0
- data/doc/styles/allison.css +314 -0
- data/doc/styles/allison.js +316 -0
- data/doc/styles/allison.rb +276 -0
- data/doc/styles/jamis.rb +593 -0
- data/doc/tutorials/01-GettingStarted.rdoc +86 -0
- data/doc/tutorials/02-GoForward.rdoc +220 -0
- data/doc/tutorials/03-PlannedPath.rdoc +268 -0
- data/doc/tutorials/04-EventPropagation.rdoc +236 -0
- data/doc/tutorials/05-ErrorHandling.rdoc +319 -0
- data/doc/tutorials/06-Overview.rdoc +40 -0
- data/doc/videos.rdoc +69 -0
- data/ext/droby/dump.cc +175 -0
- data/ext/droby/extconf.rb +3 -0
- data/ext/graph/algorithm.cc +746 -0
- data/ext/graph/extconf.rb +7 -0
- data/ext/graph/graph.cc +529 -0
- data/ext/graph/graph.hh +183 -0
- data/ext/graph/iterator_sequence.hh +102 -0
- data/ext/graph/undirected_dfs.hh +226 -0
- data/ext/graph/undirected_graph.hh +421 -0
- data/lib/roby.rb +41 -0
- data/lib/roby/app.rb +870 -0
- data/lib/roby/app/rake.rb +56 -0
- data/lib/roby/app/run.rb +14 -0
- data/lib/roby/app/scripts/distributed.rb +13 -0
- data/lib/roby/app/scripts/generate/bookmarks.rb +162 -0
- data/lib/roby/app/scripts/replay.rb +31 -0
- data/lib/roby/app/scripts/results.rb +15 -0
- data/lib/roby/app/scripts/run.rb +26 -0
- data/lib/roby/app/scripts/server.rb +18 -0
- data/lib/roby/app/scripts/shell.rb +88 -0
- data/lib/roby/app/scripts/test.rb +40 -0
- data/lib/roby/basic_object.rb +151 -0
- data/lib/roby/config.rb +5 -0
- data/lib/roby/control.rb +747 -0
- data/lib/roby/decision_control.rb +17 -0
- data/lib/roby/distributed.rb +32 -0
- data/lib/roby/distributed/base.rb +440 -0
- data/lib/roby/distributed/communication.rb +871 -0
- data/lib/roby/distributed/connection_space.rb +592 -0
- data/lib/roby/distributed/distributed_object.rb +206 -0
- data/lib/roby/distributed/drb.rb +62 -0
- data/lib/roby/distributed/notifications.rb +539 -0
- data/lib/roby/distributed/peer.rb +550 -0
- data/lib/roby/distributed/protocol.rb +529 -0
- data/lib/roby/distributed/proxy.rb +343 -0
- data/lib/roby/distributed/subscription.rb +311 -0
- data/lib/roby/distributed/transaction.rb +498 -0
- data/lib/roby/event.rb +897 -0
- data/lib/roby/exceptions.rb +234 -0
- data/lib/roby/executives/simple.rb +30 -0
- data/lib/roby/graph.rb +166 -0
- data/lib/roby/interface.rb +390 -0
- data/lib/roby/log.rb +3 -0
- data/lib/roby/log/chronicle.rb +303 -0
- data/lib/roby/log/console.rb +72 -0
- data/lib/roby/log/data_stream.rb +197 -0
- data/lib/roby/log/dot.rb +279 -0
- data/lib/roby/log/event_stream.rb +151 -0
- data/lib/roby/log/file.rb +340 -0
- data/lib/roby/log/gui/basic_display.ui +83 -0
- data/lib/roby/log/gui/chronicle.rb +26 -0
- data/lib/roby/log/gui/chronicle_view.rb +40 -0
- data/lib/roby/log/gui/chronicle_view.ui +70 -0
- data/lib/roby/log/gui/data_displays.rb +172 -0
- data/lib/roby/log/gui/data_displays.ui +155 -0
- data/lib/roby/log/gui/notifications.rb +26 -0
- data/lib/roby/log/gui/relations.rb +248 -0
- data/lib/roby/log/gui/relations.ui +123 -0
- data/lib/roby/log/gui/relations_view.rb +185 -0
- data/lib/roby/log/gui/relations_view.ui +149 -0
- data/lib/roby/log/gui/replay.rb +327 -0
- data/lib/roby/log/gui/replay_controls.rb +200 -0
- data/lib/roby/log/gui/replay_controls.ui +259 -0
- data/lib/roby/log/gui/runtime.rb +130 -0
- data/lib/roby/log/hooks.rb +185 -0
- data/lib/roby/log/logger.rb +202 -0
- data/lib/roby/log/notifications.rb +244 -0
- data/lib/roby/log/plan_rebuilder.rb +470 -0
- data/lib/roby/log/relations.rb +1056 -0
- data/lib/roby/log/server.rb +550 -0
- data/lib/roby/log/sqlite.rb +47 -0
- data/lib/roby/log/timings.rb +164 -0
- data/lib/roby/plan-object.rb +247 -0
- data/lib/roby/plan.rb +762 -0
- data/lib/roby/planning.rb +13 -0
- data/lib/roby/planning/loops.rb +302 -0
- data/lib/roby/planning/model.rb +906 -0
- data/lib/roby/planning/task.rb +151 -0
- data/lib/roby/propagation.rb +562 -0
- data/lib/roby/query.rb +619 -0
- data/lib/roby/relations.rb +583 -0
- data/lib/roby/relations/conflicts.rb +70 -0
- data/lib/roby/relations/ensured.rb +20 -0
- data/lib/roby/relations/error_handling.rb +23 -0
- data/lib/roby/relations/events.rb +9 -0
- data/lib/roby/relations/executed_by.rb +193 -0
- data/lib/roby/relations/hierarchy.rb +239 -0
- data/lib/roby/relations/influence.rb +10 -0
- data/lib/roby/relations/planned_by.rb +63 -0
- data/lib/roby/robot.rb +7 -0
- data/lib/roby/standard_errors.rb +218 -0
- data/lib/roby/state.rb +5 -0
- data/lib/roby/state/events.rb +221 -0
- data/lib/roby/state/information.rb +55 -0
- data/lib/roby/state/pos.rb +110 -0
- data/lib/roby/state/shapes.rb +32 -0
- data/lib/roby/state/state.rb +353 -0
- data/lib/roby/support.rb +92 -0
- data/lib/roby/task-operations.rb +182 -0
- data/lib/roby/task.rb +1618 -0
- data/lib/roby/test/common.rb +399 -0
- data/lib/roby/test/distributed.rb +214 -0
- data/lib/roby/test/tasks/empty_task.rb +9 -0
- data/lib/roby/test/tasks/goto.rb +36 -0
- data/lib/roby/test/tasks/simple_task.rb +23 -0
- data/lib/roby/test/testcase.rb +519 -0
- data/lib/roby/test/tools.rb +160 -0
- data/lib/roby/thread_task.rb +87 -0
- data/lib/roby/transactions.rb +462 -0
- data/lib/roby/transactions/proxy.rb +292 -0
- data/lib/roby/transactions/updates.rb +139 -0
- data/plugins/fault_injection/History.txt +4 -0
- data/plugins/fault_injection/README.txt +37 -0
- data/plugins/fault_injection/Rakefile +18 -0
- data/plugins/fault_injection/TODO.txt +0 -0
- data/plugins/fault_injection/app.rb +52 -0
- data/plugins/fault_injection/fault_injection.rb +89 -0
- data/plugins/fault_injection/test/test_fault_injection.rb +84 -0
- data/plugins/subsystems/README.txt +40 -0
- data/plugins/subsystems/Rakefile +18 -0
- data/plugins/subsystems/app.rb +171 -0
- data/plugins/subsystems/test/app/README +24 -0
- data/plugins/subsystems/test/app/Rakefile +8 -0
- data/plugins/subsystems/test/app/config/app.yml +71 -0
- data/plugins/subsystems/test/app/config/init.rb +9 -0
- data/plugins/subsystems/test/app/config/roby.yml +3 -0
- data/plugins/subsystems/test/app/planners/main.rb +20 -0
- data/plugins/subsystems/test/app/scripts/distributed +3 -0
- data/plugins/subsystems/test/app/scripts/replay +3 -0
- data/plugins/subsystems/test/app/scripts/results +3 -0
- data/plugins/subsystems/test/app/scripts/run +3 -0
- data/plugins/subsystems/test/app/scripts/server +3 -0
- data/plugins/subsystems/test/app/scripts/shell +3 -0
- data/plugins/subsystems/test/app/scripts/test +3 -0
- data/plugins/subsystems/test/app/tasks/services.rb +15 -0
- data/plugins/subsystems/test/test_subsystems.rb +71 -0
- data/test/distributed/test_communication.rb +178 -0
- data/test/distributed/test_connection.rb +282 -0
- data/test/distributed/test_execution.rb +373 -0
- data/test/distributed/test_mixed_plan.rb +341 -0
- data/test/distributed/test_plan_notifications.rb +238 -0
- data/test/distributed/test_protocol.rb +516 -0
- data/test/distributed/test_query.rb +102 -0
- data/test/distributed/test_remote_plan.rb +491 -0
- data/test/distributed/test_transaction.rb +463 -0
- data/test/mockups/tasks.rb +27 -0
- data/test/planning/test_loops.rb +380 -0
- data/test/planning/test_model.rb +427 -0
- data/test/planning/test_task.rb +106 -0
- data/test/relations/test_conflicts.rb +42 -0
- data/test/relations/test_ensured.rb +38 -0
- data/test/relations/test_executed_by.rb +149 -0
- data/test/relations/test_hierarchy.rb +158 -0
- data/test/relations/test_planned_by.rb +54 -0
- data/test/suite_core.rb +24 -0
- data/test/suite_distributed.rb +9 -0
- data/test/suite_planning.rb +3 -0
- data/test/suite_relations.rb +8 -0
- data/test/test_bgl.rb +508 -0
- data/test/test_control.rb +399 -0
- data/test/test_event.rb +894 -0
- data/test/test_exceptions.rb +592 -0
- data/test/test_interface.rb +37 -0
- data/test/test_log.rb +114 -0
- data/test/test_log_server.rb +132 -0
- data/test/test_plan.rb +584 -0
- data/test/test_propagation.rb +210 -0
- data/test/test_query.rb +266 -0
- data/test/test_relations.rb +180 -0
- data/test/test_state.rb +414 -0
- data/test/test_support.rb +16 -0
- data/test/test_task.rb +938 -0
- data/test/test_testcase.rb +122 -0
- data/test/test_thread_task.rb +73 -0
- data/test/test_transactions.rb +569 -0
- data/test/test_transactions_proxy.rb +198 -0
- metadata +570 -0
data/lib/roby/support.rb
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
require 'active_support/core_ext/string/inflections'
|
|
2
|
+
class String # :nodoc: all
|
|
3
|
+
include ActiveSupport::CoreExtensions::String::Inflections
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
require 'roby/config'
|
|
7
|
+
require 'facets/kernel/constant'
|
|
8
|
+
require 'utilrb/enumerable'
|
|
9
|
+
require 'utilrb/time/to_hms'
|
|
10
|
+
require 'utilrb/module/cached_enum'
|
|
11
|
+
require 'utilrb/logger'
|
|
12
|
+
require 'utilrb/gc/force'
|
|
13
|
+
require 'utilrb/hash/to_sym_keys'
|
|
14
|
+
require 'utilrb/array/to_s'
|
|
15
|
+
require 'utilrb/hash/to_s'
|
|
16
|
+
require 'utilrb/set/to_s'
|
|
17
|
+
|
|
18
|
+
class IO
|
|
19
|
+
def ask(question, default, output_io = STDOUT)
|
|
20
|
+
output_io.print question
|
|
21
|
+
output_io.flush
|
|
22
|
+
loop do
|
|
23
|
+
answer = readline.chomp.downcase
|
|
24
|
+
if answer.empty?
|
|
25
|
+
return default
|
|
26
|
+
elsif answer == 'y'
|
|
27
|
+
return true
|
|
28
|
+
elsif answer == 'n'
|
|
29
|
+
return false
|
|
30
|
+
else
|
|
31
|
+
output_io.print "\nInvalid answer, try again: "
|
|
32
|
+
output_io.flush
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
module Enumerable
|
|
39
|
+
def empty?
|
|
40
|
+
for i in self
|
|
41
|
+
return false
|
|
42
|
+
end
|
|
43
|
+
true
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
class Module
|
|
48
|
+
# Defines a new constant under a given module
|
|
49
|
+
# :call-seq
|
|
50
|
+
# define_under(name, value) -> value
|
|
51
|
+
# define_under(name) { ... } -> value
|
|
52
|
+
#
|
|
53
|
+
# In the first form, the method gets its value from its argument.
|
|
54
|
+
# In the second case, it calls the provided block
|
|
55
|
+
def define_under(name, value = nil)
|
|
56
|
+
if old = constants.find { |cn| cn == name.to_s }
|
|
57
|
+
return const_get(old)
|
|
58
|
+
else
|
|
59
|
+
const_set(name, (value || yield))
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
class Thread
|
|
65
|
+
def send_to(object, name, *args, &prc)
|
|
66
|
+
if Thread.current == self
|
|
67
|
+
object.send(name, *args, &prc)
|
|
68
|
+
else
|
|
69
|
+
@msg_queue ||= Queue.new
|
|
70
|
+
@msg_queue << [ object, name, args, prc ]
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
def process_events
|
|
74
|
+
@msg_queue ||= Queue.new
|
|
75
|
+
loop do
|
|
76
|
+
object, name, args, block = *@msg_queue.deq(true)
|
|
77
|
+
object.send(name, *args, &block)
|
|
78
|
+
end
|
|
79
|
+
rescue ThreadError
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
module Roby
|
|
84
|
+
@logger = Logger.new(STDERR)
|
|
85
|
+
@logger.level = Logger::WARN
|
|
86
|
+
@logger.progname = "Roby"
|
|
87
|
+
@logger.formatter = lambda { |severity, time, progname, msg| "#{time.to_hms} (#{progname}) #{msg}\n" }
|
|
88
|
+
|
|
89
|
+
extend Logger::Hierarchy
|
|
90
|
+
extend Logger::Forward
|
|
91
|
+
end
|
|
92
|
+
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
require 'roby/task'
|
|
2
|
+
|
|
3
|
+
module Roby
|
|
4
|
+
module TaskOperations
|
|
5
|
+
def +(task)
|
|
6
|
+
# !!!! + is NOT commutative
|
|
7
|
+
if task.null?
|
|
8
|
+
self
|
|
9
|
+
elsif self.null?
|
|
10
|
+
task
|
|
11
|
+
else
|
|
12
|
+
Sequence.new << self << task
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
def |(task)
|
|
16
|
+
if self.null?
|
|
17
|
+
task
|
|
18
|
+
elsif task.null?
|
|
19
|
+
self
|
|
20
|
+
else
|
|
21
|
+
Parallel.new << self << task
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
class Task
|
|
28
|
+
include TaskOperations
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
class TaskAggregator < Roby::Task
|
|
32
|
+
terminates
|
|
33
|
+
event(:start, :command => true)
|
|
34
|
+
|
|
35
|
+
attr_reader :tasks
|
|
36
|
+
def initialize(arguments = {}); @tasks = Array.new; super end
|
|
37
|
+
def each_task(&iterator)
|
|
38
|
+
yield(self)
|
|
39
|
+
tasks.each(&iterator)
|
|
40
|
+
end
|
|
41
|
+
def empty?; tasks.empty? end
|
|
42
|
+
|
|
43
|
+
def delete
|
|
44
|
+
@name = self.name
|
|
45
|
+
@tasks = nil
|
|
46
|
+
if plan
|
|
47
|
+
plan.remove_object(self)
|
|
48
|
+
else
|
|
49
|
+
clear_relations
|
|
50
|
+
freeze
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
class Sequence < TaskAggregator
|
|
56
|
+
def name
|
|
57
|
+
@name || @tasks.map { |t| t.name }.join("+")
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def to_task(task = nil)
|
|
61
|
+
return super() unless task
|
|
62
|
+
task = task.new unless task.kind_of?(Roby::Task)
|
|
63
|
+
@tasks.each { |t| task.realized_by t }
|
|
64
|
+
|
|
65
|
+
task.on(:start, @tasks.first, :start)
|
|
66
|
+
@tasks.last.forward(:success, task, :success)
|
|
67
|
+
|
|
68
|
+
delete
|
|
69
|
+
|
|
70
|
+
task
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def connect_start(task)
|
|
74
|
+
if old = @tasks.first
|
|
75
|
+
event(:start).remove_signal old.event(:start)
|
|
76
|
+
task.on(:success, old, :start)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
event(:start).on task.event(:start)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def connect_stop(task)
|
|
83
|
+
if old = @tasks.last
|
|
84
|
+
old.on(:success, task, :start)
|
|
85
|
+
old.event(:success).remove_forwarding event(:success)
|
|
86
|
+
end
|
|
87
|
+
task.forward(:success, self)
|
|
88
|
+
end
|
|
89
|
+
private :connect_stop, :connect_start
|
|
90
|
+
|
|
91
|
+
def unshift(task)
|
|
92
|
+
raise "trying to do Sequence#unshift on a running or finished sequence" if (running? || finished?)
|
|
93
|
+
connect_start(task)
|
|
94
|
+
connect_stop(task) if @tasks.empty?
|
|
95
|
+
|
|
96
|
+
@tasks.unshift(task)
|
|
97
|
+
realized_by task
|
|
98
|
+
self
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def <<(task)
|
|
102
|
+
raise "trying to do Sequence#<< on a finished sequence" if finished?
|
|
103
|
+
connect_start(task) if @tasks.empty?
|
|
104
|
+
connect_stop(task)
|
|
105
|
+
|
|
106
|
+
@tasks << task
|
|
107
|
+
realized_by task
|
|
108
|
+
self
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def to_sequence; self end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
class Parallel < TaskAggregator
|
|
115
|
+
def name
|
|
116
|
+
@name || @tasks.map { |t| t.name }.join("|")
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
attr_reader :children_success
|
|
120
|
+
def initialize(arguments = {})
|
|
121
|
+
super
|
|
122
|
+
|
|
123
|
+
@children_success = Roby::AndGenerator.new
|
|
124
|
+
@children_success.forward event(:success)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def to_task(task = nil)
|
|
128
|
+
return super() unless task
|
|
129
|
+
|
|
130
|
+
task = task.new unless task.kind_of?(Roby::Task)
|
|
131
|
+
@tasks.each do |t|
|
|
132
|
+
task.realized_by t
|
|
133
|
+
task.on(:start, t, :start)
|
|
134
|
+
end
|
|
135
|
+
task.event(:success).emit_on children_success
|
|
136
|
+
|
|
137
|
+
delete
|
|
138
|
+
|
|
139
|
+
task
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def <<(task)
|
|
143
|
+
raise "trying to change a running parallel task" if running?
|
|
144
|
+
@tasks << task
|
|
145
|
+
|
|
146
|
+
on(:start, task, :start)
|
|
147
|
+
realized_by task
|
|
148
|
+
children_success << task.event(:success)
|
|
149
|
+
|
|
150
|
+
self
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def to_parallel; self end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
class Group < Roby::Task
|
|
157
|
+
def initialize(*tasks)
|
|
158
|
+
super()
|
|
159
|
+
if tasks.empty? || tasks.first.kind_of?(Hash)
|
|
160
|
+
return
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
success = AndGenerator.new
|
|
164
|
+
tasks.each do |task|
|
|
165
|
+
realized_by task
|
|
166
|
+
task.event(:success).on success
|
|
167
|
+
end
|
|
168
|
+
success.forward event(:success)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
event :start do
|
|
172
|
+
children.each do |child|
|
|
173
|
+
if child.pending? && child.event(:start).root?
|
|
174
|
+
child.start!
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
emit :start
|
|
178
|
+
end
|
|
179
|
+
terminates
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
data/lib/roby/task.rb
ADDED
|
@@ -0,0 +1,1618 @@
|
|
|
1
|
+
require 'roby/plan-object'
|
|
2
|
+
require 'roby/exceptions'
|
|
3
|
+
require 'roby/event'
|
|
4
|
+
require 'utilrb/module/attr_predicate'
|
|
5
|
+
|
|
6
|
+
module Roby
|
|
7
|
+
class TaskModelTag < Module
|
|
8
|
+
module ClassExtension
|
|
9
|
+
# Returns the list of static arguments required by this task model
|
|
10
|
+
def arguments(*new_arguments)
|
|
11
|
+
new_arguments.each do |arg_name|
|
|
12
|
+
argument_set << arg_name.to_sym
|
|
13
|
+
unless method_defined?(arg_name)
|
|
14
|
+
define_method(arg_name) { arguments[arg_name] }
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
@argument_enumerator ||= enum_for(:each_argument_set)
|
|
19
|
+
end
|
|
20
|
+
# Declares a set of arguments required by this task model
|
|
21
|
+
def argument(*args); arguments(*args) end
|
|
22
|
+
end
|
|
23
|
+
include TaskModelTag::ClassExtension
|
|
24
|
+
|
|
25
|
+
def initialize(&block)
|
|
26
|
+
super do
|
|
27
|
+
inherited_enumerable("argument_set", "argument_set") { ValueSet.new }
|
|
28
|
+
unless const_defined? :ClassExtension
|
|
29
|
+
const_set(:ClassExtension, Module.new)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
self::ClassExtension.include TaskModelTag::ClassExtension
|
|
33
|
+
end
|
|
34
|
+
class_eval(&block) if block_given?
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def clear_model
|
|
38
|
+
@argument_set.clear if @argument_set
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Base class for task events
|
|
43
|
+
# When events are emitted, then the created object is
|
|
44
|
+
# an instance of a class derived from this one
|
|
45
|
+
class TaskEvent < Event
|
|
46
|
+
# The task which fired this event
|
|
47
|
+
attr_reader :task
|
|
48
|
+
|
|
49
|
+
def initialize(task, generator, propagation_id, context, time = Time.now)
|
|
50
|
+
@task = task
|
|
51
|
+
@terminal_flag = generator.terminal_flag
|
|
52
|
+
super(generator, propagation_id, context, time)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Returns the set of events from the task that are the cause of this
|
|
56
|
+
# event
|
|
57
|
+
def task_sources
|
|
58
|
+
result = ValueSet.new
|
|
59
|
+
if sources
|
|
60
|
+
for ev in sources
|
|
61
|
+
gen = ev.generator
|
|
62
|
+
if gen.respond_to?(:task) && gen.task == task
|
|
63
|
+
result.merge ev.task_sources
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
if result.empty?
|
|
68
|
+
result << self
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
result
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def to_s
|
|
75
|
+
"#{generator.to_s}@#{propagation_id} [#{time.to_hms}]: #{context}"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def pretty_print(pp)
|
|
79
|
+
pp.text "at [#{time.to_hms}/#{propagation_id}] in the "
|
|
80
|
+
generator.pretty_print(pp)
|
|
81
|
+
pp.breakable
|
|
82
|
+
pp.group(2) do
|
|
83
|
+
pp.seplist(context || []) { |v| v.pretty_print pp }
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# If the event model defines a controlable event
|
|
88
|
+
# By default, an event is controlable if the model
|
|
89
|
+
# responds to #call
|
|
90
|
+
def self.controlable?; respond_to?(:call) end
|
|
91
|
+
# If the event is controlable
|
|
92
|
+
def controlable?; self.class.controlable? end
|
|
93
|
+
class << self
|
|
94
|
+
# Called by Task.update_terminal_flag to update the flag
|
|
95
|
+
attr_writer :terminal
|
|
96
|
+
end
|
|
97
|
+
# If the event model defines a terminal event
|
|
98
|
+
def self.terminal?; @terminal end
|
|
99
|
+
# If this event is terminal
|
|
100
|
+
def success?; @terminal_flag == :success end
|
|
101
|
+
# If this event is terminal
|
|
102
|
+
def failure?; @terminal_flag == :failure end
|
|
103
|
+
# If this event is terminal
|
|
104
|
+
def terminal?; @terminal_flag end
|
|
105
|
+
# The event symbol
|
|
106
|
+
def self.symbol; @symbol end
|
|
107
|
+
# The event symbol
|
|
108
|
+
def symbol; self.class.symbol end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# A task event model bound to a particular task instance
|
|
112
|
+
# The Task/TaskEvent/TaskEventGenerator relationship is
|
|
113
|
+
# comparable to the Class/UnboundMethod/Method one:
|
|
114
|
+
# * a Task object is a model for a task, a Class in a model for an object
|
|
115
|
+
# * a TaskEvent object is a model for an event instance (the instance being unspecified),
|
|
116
|
+
# an UnboundMethod is a model for an instance method
|
|
117
|
+
# * a TaskEventGenerator object represents a particular event model
|
|
118
|
+
# *bound* to a particular task instance, a Method object represents a particular method
|
|
119
|
+
# bound to a particular object
|
|
120
|
+
class TaskEventGenerator < EventGenerator
|
|
121
|
+
# The task we are part of
|
|
122
|
+
attr_reader :task
|
|
123
|
+
# The event symbol (its name as a Symbol object)
|
|
124
|
+
attr_reader :symbol
|
|
125
|
+
# The event class
|
|
126
|
+
attr_reader :event_model
|
|
127
|
+
def initialize(task, model)
|
|
128
|
+
@task, @event_model = task, model
|
|
129
|
+
@symbol = model.symbol
|
|
130
|
+
super(model.respond_to?(:call))
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def default_command(context)
|
|
134
|
+
event_model.call(task, context)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# See PlanObject::child_plan_object.
|
|
138
|
+
child_plan_object :task
|
|
139
|
+
|
|
140
|
+
# The event plan. It is the same as task.plan and is actually updated
|
|
141
|
+
# by task.plan=. It is redefined here for performance reasons.
|
|
142
|
+
attr_accessor :plan
|
|
143
|
+
|
|
144
|
+
# Fire the event
|
|
145
|
+
def fire(event)
|
|
146
|
+
task.fire_event(event)
|
|
147
|
+
super
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# See EventGenerator#calling
|
|
151
|
+
#
|
|
152
|
+
# In TaskEventGenerator, this hook checks that the task is running
|
|
153
|
+
def calling(context)
|
|
154
|
+
super if defined? super
|
|
155
|
+
if task.finished? && !terminal?
|
|
156
|
+
raise CommandFailed.new(nil, self),
|
|
157
|
+
"#{symbol}!(#{context})) called by #{Propagation.sources} but the task has finished. Task has been terminated by #{task.event(:stop).history.first.sources}."
|
|
158
|
+
elsif task.pending? && symbol != :start
|
|
159
|
+
raise CommandFailed.new(nil, self),
|
|
160
|
+
"#{symbol}!(#{context})) called by #{Propagation.sources} but the task is not running"
|
|
161
|
+
elsif task.running? && symbol == :start
|
|
162
|
+
raise CommandFailed.new(nil, self),
|
|
163
|
+
"#{symbol}!(#{context})) called by #{Propagation.sources} but the task is already running. Task has been started by #{task.event(:start).history.first.sources}."
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# See EventGenerator#fired
|
|
168
|
+
#
|
|
169
|
+
# In TaskEventGenerator, this hook calls the unreachable handlers added
|
|
170
|
+
# by EventGenerator#if_unreachable when the task has finished, not
|
|
171
|
+
# before
|
|
172
|
+
def fired(event)
|
|
173
|
+
super if defined? super
|
|
174
|
+
|
|
175
|
+
if symbol == :stop
|
|
176
|
+
task.each_event { |ev| ev.unreachable!(task.terminal_event) }
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def related_tasks(result = nil)
|
|
181
|
+
tasks = super
|
|
182
|
+
tasks.delete(task)
|
|
183
|
+
tasks
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def each_handler
|
|
187
|
+
super
|
|
188
|
+
|
|
189
|
+
if self_owned?
|
|
190
|
+
task.each_handler(event_model.symbol) { |o| yield(o) }
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
def each_precondition
|
|
194
|
+
super
|
|
195
|
+
task.each_precondition(event_model.symbol) { |o| yield(o) }
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def controlable?; event_model.controlable? end
|
|
199
|
+
attr_accessor :terminal_flag
|
|
200
|
+
def terminal?; !!@terminal_flag end
|
|
201
|
+
def success?; @terminal_flag == :success end
|
|
202
|
+
def failure?; @terminal_flag == :failure end
|
|
203
|
+
def added_child_object(child, relations, info)
|
|
204
|
+
super if defined? super
|
|
205
|
+
|
|
206
|
+
if relations.include?(EventStructure::CausalLink) &&
|
|
207
|
+
child.respond_to?(:task) && child.task == task &&
|
|
208
|
+
child.terminal_flag != terminal_flag
|
|
209
|
+
|
|
210
|
+
task.update_terminal_flag
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
def removed_child_object(child, relations)
|
|
214
|
+
super if defined? super
|
|
215
|
+
|
|
216
|
+
if relations.include?(EventStructure::CausalLink) &&
|
|
217
|
+
child.respond_to?(:task) && child.task == task &&
|
|
218
|
+
terminal_flag
|
|
219
|
+
|
|
220
|
+
task.update_terminal_flag
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
def new(context); event_model.new(task, self, Propagation.propagation_id, context) end
|
|
224
|
+
|
|
225
|
+
def to_s
|
|
226
|
+
"#{task}/#{symbol}"
|
|
227
|
+
end
|
|
228
|
+
def inspect
|
|
229
|
+
"#{task.inspect}/#{symbol}: #{history.to_s}"
|
|
230
|
+
end
|
|
231
|
+
def pretty_print(pp)
|
|
232
|
+
pp.text "#{symbol} event of #{task.class}:0x#{task.address.to_s(16)}"
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def achieve_with(obj)
|
|
236
|
+
child_task, child_event = case obj
|
|
237
|
+
when Roby::Task: [obj, obj.event(:success)]
|
|
238
|
+
when Roby::TaskEventGenerator: [obj.task, obj]
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
if child_task
|
|
242
|
+
unless task.realized_by?(child_task)
|
|
243
|
+
task.realized_by child_task,
|
|
244
|
+
:success => [child_event.symbol],
|
|
245
|
+
:remove_when_done => true
|
|
246
|
+
end
|
|
247
|
+
super(child_event)
|
|
248
|
+
else
|
|
249
|
+
super(obj)
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
class TaskArguments < Hash
|
|
255
|
+
private :delete, :delete_if
|
|
256
|
+
|
|
257
|
+
attr_reader :task
|
|
258
|
+
def initialize(task)
|
|
259
|
+
@task = task
|
|
260
|
+
super()
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def writable?(key)
|
|
264
|
+
!(has_key?(key) && task.model.arguments.include?(key))
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def dup; self.to_hash end
|
|
268
|
+
def to_hash
|
|
269
|
+
inject({}) { |h, (k, v)| h[k] = v ; h }
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
alias :update! :[]=
|
|
273
|
+
def []=(key, value)
|
|
274
|
+
if writable?(key)
|
|
275
|
+
if !task.read_write?
|
|
276
|
+
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}"
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
updating
|
|
280
|
+
super
|
|
281
|
+
updated
|
|
282
|
+
else
|
|
283
|
+
raise ArgumentError, "cannot override task arguments"
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
def updating; super if defined? super end
|
|
287
|
+
def updated; super if defined? super end
|
|
288
|
+
|
|
289
|
+
alias :do_merge! :merge!
|
|
290
|
+
def merge!(hash)
|
|
291
|
+
super do |key, old, new|
|
|
292
|
+
if old == new then old
|
|
293
|
+
elsif writable?(key) then new
|
|
294
|
+
else
|
|
295
|
+
raise ArgumentError, "cannot override task argument #{key}: trying to replace #{old} by #{new}"
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# In a plan, Task objects represent the activities of the robot.
|
|
302
|
+
#
|
|
303
|
+
# === Task models
|
|
304
|
+
#
|
|
305
|
+
# A task model is mainly described by:
|
|
306
|
+
#
|
|
307
|
+
# <b>a set of named arguments</b>, which are required to parametrize the
|
|
308
|
+
# task instance. The argument list is described using Task.argument and
|
|
309
|
+
# arguments are either set at object creation by passing an argument hash
|
|
310
|
+
# to Task.new, or by calling Task#argument explicitely.
|
|
311
|
+
#
|
|
312
|
+
# <b>a set of events</b>, which are situations describing the task
|
|
313
|
+
# progression. The base Roby::Task model defines the
|
|
314
|
+
# +start+,+success+,+failed+ and +stop+ events. Events can be defined on
|
|
315
|
+
# the models by using Task.event:
|
|
316
|
+
#
|
|
317
|
+
# class MyTask < Roby::Task
|
|
318
|
+
# event :intermediate_event
|
|
319
|
+
# end
|
|
320
|
+
#
|
|
321
|
+
# defines a non-controllable event, i.e. an event which can be emitted, but
|
|
322
|
+
# cannot be triggered explicitely by the system. Controllable events are defined
|
|
323
|
+
# by associating a block of code with the event, this block being responsible for
|
|
324
|
+
# making the event emitted either in the future or just now. For instance,
|
|
325
|
+
#
|
|
326
|
+
# class MyTask < Roby::Task
|
|
327
|
+
# event :intermediate_event do |context|
|
|
328
|
+
# emit :intermediate_event
|
|
329
|
+
# end
|
|
330
|
+
#
|
|
331
|
+
# event :other_event do |context|
|
|
332
|
+
# Roby.once { emit :other_event }
|
|
333
|
+
# end
|
|
334
|
+
# end
|
|
335
|
+
#
|
|
336
|
+
# define two controllable event. In the first case, the event is
|
|
337
|
+
# immediately emitted, and in the second case it will be emitted at the
|
|
338
|
+
# beginning of the next execution cycle.
|
|
339
|
+
#
|
|
340
|
+
# === Executability
|
|
341
|
+
#
|
|
342
|
+
# By default, a task is not executable, which means that no event command
|
|
343
|
+
# can be called and no event can be emitted. A task becomes executable
|
|
344
|
+
# either because Task#executable= has explicitely been called or because it
|
|
345
|
+
# has been inserted in a Plan object. Note that forcing executability with
|
|
346
|
+
# #executable= is only useful for testing. When the Roby controller manages
|
|
347
|
+
# a real systems, the executability property enforces the constraint that a
|
|
348
|
+
# task cannot be executed outside of the plan supervision.
|
|
349
|
+
#
|
|
350
|
+
# Finally, it is possible to describe _abstract_ task models: tasks which
|
|
351
|
+
# do represent an action, but for which the _means_ to perform that action
|
|
352
|
+
# are still unknown. This is done by calling Task.abstract in the task definition:
|
|
353
|
+
#
|
|
354
|
+
# class AbstTask < Roby::Task
|
|
355
|
+
# abstract
|
|
356
|
+
# end
|
|
357
|
+
#
|
|
358
|
+
# An instance of an abstract model cannot be executed, even if it is included
|
|
359
|
+
# in a plan.
|
|
360
|
+
#
|
|
361
|
+
# === Inheritance rules
|
|
362
|
+
#
|
|
363
|
+
# On task models, a submodel can inherit from a parent model if the actions
|
|
364
|
+
# described by the parent model are also performed by the child model. For
|
|
365
|
+
# instance, a <tt>Goto(x, y)</tt> model could be subclassed into a
|
|
366
|
+
# <tt>Goto::ByFoot(x, y)</tt> model.
|
|
367
|
+
#
|
|
368
|
+
# The following constraints apply when subclassing a task model:
|
|
369
|
+
# * a task subclass has at least the same events than the parent class
|
|
370
|
+
# * changes to event attributes are limited. The rules are:
|
|
371
|
+
# - a controlable event must remain controlable. Nonetheless, a
|
|
372
|
+
# non-controlable event can become a controlable one
|
|
373
|
+
# - a terminal event (i.e. a terminal event which ends the task
|
|
374
|
+
# execution) cannot become non-terminal. Nonetheless, a non-terminal
|
|
375
|
+
# event can become terminal.
|
|
376
|
+
#
|
|
377
|
+
class Task < PlanObject
|
|
378
|
+
unless defined? RootTaskTag
|
|
379
|
+
RootTaskTag = TaskModelTag.new
|
|
380
|
+
include RootTaskTag
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
# Clears all definitions saved in this model. This is to be used by the
|
|
384
|
+
# reloading code
|
|
385
|
+
def self.clear_model
|
|
386
|
+
class_eval do
|
|
387
|
+
# Remove event models
|
|
388
|
+
events.each_key do |ev_symbol|
|
|
389
|
+
remove_const ev_symbol.to_s.camelize
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
[@events, @signal_sets, @forwarding_sets, @causal_link_sets,
|
|
393
|
+
@argument_set, @handler_sets, @precondition_sets].each do |set|
|
|
394
|
+
set.clear if set
|
|
395
|
+
end
|
|
396
|
+
end
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
# Declares an attribute set which follows the task models inheritance
|
|
400
|
+
# hierarchy. Define the corresponding enumeration methods as well.
|
|
401
|
+
#
|
|
402
|
+
# For instance,
|
|
403
|
+
# model_attribute_list 'signal'
|
|
404
|
+
#
|
|
405
|
+
# defines the model-level signals, which can be accessed through
|
|
406
|
+
# .each_signal(model)
|
|
407
|
+
# .signals(model)
|
|
408
|
+
# #each_signal(model)
|
|
409
|
+
#
|
|
410
|
+
def self.model_attribute_list(name) # :nodoc:
|
|
411
|
+
inherited_enumerable("#{name}_set", "#{name}_sets", :map => true) { Hash.new { |h, k| h[k] = ValueSet.new } }
|
|
412
|
+
class_eval <<-EOD
|
|
413
|
+
def self.each_#{name}(model)
|
|
414
|
+
for obj in #{name}s(model)
|
|
415
|
+
yield(obj)
|
|
416
|
+
end
|
|
417
|
+
self
|
|
418
|
+
end
|
|
419
|
+
def self.#{name}s(model)
|
|
420
|
+
result = ValueSet.new
|
|
421
|
+
each_#{name}_set(model, false) do |set|
|
|
422
|
+
result.merge set
|
|
423
|
+
end
|
|
424
|
+
result
|
|
425
|
+
end
|
|
426
|
+
def each_#{name}(model); self.model.each_#{name}(model) { |o| yield(o) } end
|
|
427
|
+
EOD
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
model_attribute_list('signal')
|
|
431
|
+
model_attribute_list('forwarding')
|
|
432
|
+
model_attribute_list('causal_link')
|
|
433
|
+
model_attribute_list('handler')
|
|
434
|
+
model_attribute_list('precondition')
|
|
435
|
+
|
|
436
|
+
# The task arguments as symbol => value associative container
|
|
437
|
+
attr_reader :arguments
|
|
438
|
+
# The part of +arguments+ that is meaningful for this task model
|
|
439
|
+
def meaningful_arguments(task_model = self.model)
|
|
440
|
+
arguments.slice(*task_model.arguments)
|
|
441
|
+
end
|
|
442
|
+
# The task name
|
|
443
|
+
def name
|
|
444
|
+
@name ||= "#{model.name || self.class.name}#{arguments.to_s}:0x#{address.to_s(16)}"
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
# This predicate is true if this task is a mission for its owners. If
|
|
448
|
+
# you want to know if it a mission for the local pDB, use Plan#mission?
|
|
449
|
+
attr_predicate :mission?, true
|
|
450
|
+
|
|
451
|
+
def inspect
|
|
452
|
+
state = if pending? then 'pending'
|
|
453
|
+
elsif starting? then 'starting'
|
|
454
|
+
elsif running? then 'running'
|
|
455
|
+
elsif finishing? then 'finishing'
|
|
456
|
+
else 'finished'
|
|
457
|
+
end
|
|
458
|
+
"#<#{to_s} executable=#{executable?} state=#{state} plan=#{plan.to_s}>"
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
# Builds a task object using this task model
|
|
462
|
+
#
|
|
463
|
+
# The task object can be configured by a given block. After the
|
|
464
|
+
# block is called, two things are checked:
|
|
465
|
+
# * the task shall have a +start+ event
|
|
466
|
+
# * the task shall have at least one terminal event. If no +stop+ event
|
|
467
|
+
# is defined, then all terminal events are aliased to +stop+
|
|
468
|
+
def initialize(arguments = nil) #:yields: task_object
|
|
469
|
+
super() if defined? super
|
|
470
|
+
|
|
471
|
+
@arguments = TaskArguments.new(self)
|
|
472
|
+
@arguments.merge!(arguments) if arguments
|
|
473
|
+
|
|
474
|
+
@model = self.class
|
|
475
|
+
|
|
476
|
+
yield(self) if block_given?
|
|
477
|
+
|
|
478
|
+
initialize_events
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
# Helper methods which creates all the necessary TaskEventGenerator
|
|
482
|
+
# objects and stores them in the #bound_events map
|
|
483
|
+
def initialize_events # :nodoc:
|
|
484
|
+
@instantiated_model_events = false
|
|
485
|
+
|
|
486
|
+
# Create all event generators
|
|
487
|
+
bound_events = Hash.new
|
|
488
|
+
model.each_event do |ev_symbol, ev_model|
|
|
489
|
+
bound_events[ev_symbol.to_sym] = TaskEventGenerator.new(self, ev_model)
|
|
490
|
+
end
|
|
491
|
+
@bound_events = bound_events
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
def model; self.class end
|
|
495
|
+
|
|
496
|
+
def initialize_copy(old) # :nodoc:
|
|
497
|
+
super
|
|
498
|
+
|
|
499
|
+
@name = nil
|
|
500
|
+
@history = old.history.dup
|
|
501
|
+
|
|
502
|
+
@arguments = TaskArguments.new(self)
|
|
503
|
+
arguments.do_merge! old.arguments
|
|
504
|
+
arguments.instance_variable_set(:@task, self)
|
|
505
|
+
|
|
506
|
+
initialize_events
|
|
507
|
+
plan.discover(self)
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
def instantiate_model_event_relations
|
|
511
|
+
return if @instantiated_model_events
|
|
512
|
+
# Add the model-level signals to this instance
|
|
513
|
+
@instantiated_model_events = true
|
|
514
|
+
|
|
515
|
+
left_border = bound_events.values.to_value_set
|
|
516
|
+
right_border = bound_events.values.to_value_set
|
|
517
|
+
|
|
518
|
+
model.each_signal_set do |generator, signalled_events|
|
|
519
|
+
next if signalled_events.empty?
|
|
520
|
+
generator = bound_events[generator]
|
|
521
|
+
right_border.delete(generator)
|
|
522
|
+
|
|
523
|
+
for signalled in signalled_events
|
|
524
|
+
signalled = bound_events[signalled]
|
|
525
|
+
generator.signal signalled
|
|
526
|
+
left_border.delete(signalled)
|
|
527
|
+
end
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
model.each_forwarding_set do |generator, signalled_events|
|
|
532
|
+
next if signalled_events.empty?
|
|
533
|
+
generator = bound_events[generator]
|
|
534
|
+
right_border.delete(generator)
|
|
535
|
+
|
|
536
|
+
for signalled in signalled_events
|
|
537
|
+
signalled = bound_events[signalled]
|
|
538
|
+
generator.forward signalled
|
|
539
|
+
left_border.delete(signalled)
|
|
540
|
+
end
|
|
541
|
+
end
|
|
542
|
+
|
|
543
|
+
model.each_causal_link_set do |generator, signalled_events|
|
|
544
|
+
next if signalled_events.empty?
|
|
545
|
+
generator = bound_events[generator]
|
|
546
|
+
right_border.delete(generator)
|
|
547
|
+
|
|
548
|
+
for signalled in signalled_events
|
|
549
|
+
signalled = bound_events[signalled]
|
|
550
|
+
generator.add_causal_link signalled
|
|
551
|
+
left_border.delete(signalled)
|
|
552
|
+
end
|
|
553
|
+
end
|
|
554
|
+
|
|
555
|
+
update_terminal_flag
|
|
556
|
+
|
|
557
|
+
# WARNING: this works only because:
|
|
558
|
+
# * there is always at least updated_data as an intermediate event
|
|
559
|
+
# * there is always one terminal event which is not stop
|
|
560
|
+
start_event = bound_events[:start]
|
|
561
|
+
stop_event = bound_events[:stop]
|
|
562
|
+
left_border.delete(start_event)
|
|
563
|
+
right_border.delete(start_event)
|
|
564
|
+
left_border.delete(stop_event)
|
|
565
|
+
right_border.delete(stop_event)
|
|
566
|
+
|
|
567
|
+
for generator in left_border
|
|
568
|
+
start_event.add_precedence(generator) unless generator.terminal?
|
|
569
|
+
end
|
|
570
|
+
|
|
571
|
+
# WARN: the start event CAN be terminal: it can be a signal from
|
|
572
|
+
# :start to a terminal event
|
|
573
|
+
#
|
|
574
|
+
# Create the precedence relations between 'normal' events and the terminal events
|
|
575
|
+
for terminal in left_border
|
|
576
|
+
next unless terminal.terminal?
|
|
577
|
+
for generator in right_border
|
|
578
|
+
unless generator.terminal?
|
|
579
|
+
generator.add_precedence(terminal)
|
|
580
|
+
end
|
|
581
|
+
end
|
|
582
|
+
end
|
|
583
|
+
end
|
|
584
|
+
|
|
585
|
+
def plan=(new_plan) # :nodoc:
|
|
586
|
+
if plan != new_plan
|
|
587
|
+
if plan && plan.include?(self)
|
|
588
|
+
raise ModelViolation.new, "still included in #{plan}, cannot change the plan"
|
|
589
|
+
elsif self_owned? && running?
|
|
590
|
+
raise ModelViolation.new, "cannot change the plan of a running task"
|
|
591
|
+
end
|
|
592
|
+
end
|
|
593
|
+
|
|
594
|
+
super
|
|
595
|
+
|
|
596
|
+
for _, ev in bound_events
|
|
597
|
+
ev.plan = plan
|
|
598
|
+
end
|
|
599
|
+
end
|
|
600
|
+
|
|
601
|
+
class << self
|
|
602
|
+
# If this task is an abstract task
|
|
603
|
+
# Abstract tasks are not executable. This attribute is
|
|
604
|
+
# not inherited in the task hierarchy
|
|
605
|
+
attr_reader :abstract
|
|
606
|
+
alias :abstract? :abstract
|
|
607
|
+
|
|
608
|
+
# Mark this task model as an abstract model
|
|
609
|
+
def abstract
|
|
610
|
+
@abstract = true
|
|
611
|
+
end
|
|
612
|
+
|
|
613
|
+
# Declare that nothing special is required to stop this task.
|
|
614
|
+
# This makes +failed+ and +stop+ controlable events, and
|
|
615
|
+
# makes the interruption sequence be stop! => calls failed! =>
|
|
616
|
+
# emits +failed+ => emits +stop+.
|
|
617
|
+
def terminates
|
|
618
|
+
event :failed, :command => true, :terminal => true
|
|
619
|
+
interruptible
|
|
620
|
+
end
|
|
621
|
+
|
|
622
|
+
# Sets up a command for +stop+ in the case where +failed+ is also
|
|
623
|
+
# controllable, if the command of +failed+ should be used to stop
|
|
624
|
+
# the task.
|
|
625
|
+
def interruptible
|
|
626
|
+
if !has_event?(:failed) || !event_model(:failed).controlable?
|
|
627
|
+
raise ArgumentError, "failed is not controlable"
|
|
628
|
+
end
|
|
629
|
+
event(:stop) do |context|
|
|
630
|
+
if starting?
|
|
631
|
+
on :start, self, :stop
|
|
632
|
+
return
|
|
633
|
+
end
|
|
634
|
+
failed!(context)
|
|
635
|
+
end
|
|
636
|
+
end
|
|
637
|
+
|
|
638
|
+
def setup_poll_method(block) # :nodoc:
|
|
639
|
+
define_method(:poll) do
|
|
640
|
+
return unless self_owned?
|
|
641
|
+
begin
|
|
642
|
+
poll_handler
|
|
643
|
+
rescue Exception => e
|
|
644
|
+
emit :failed, e
|
|
645
|
+
end
|
|
646
|
+
end
|
|
647
|
+
|
|
648
|
+
define_method(:poll_handler, &block)
|
|
649
|
+
end
|
|
650
|
+
|
|
651
|
+
# Defines a block which will be called at each execution cycle for
|
|
652
|
+
# each running task of this model. The block is called in the
|
|
653
|
+
# instance context of the target task (i.e. using instance_eval)
|
|
654
|
+
def poll(&block)
|
|
655
|
+
if !block_given?
|
|
656
|
+
raise "no block given"
|
|
657
|
+
end
|
|
658
|
+
|
|
659
|
+
setup_poll_method(block)
|
|
660
|
+
|
|
661
|
+
on(:start) { Control.event_processing << method(:poll) }
|
|
662
|
+
on(:stop) { Control.event_processing.delete(method(:poll)) }
|
|
663
|
+
end
|
|
664
|
+
end
|
|
665
|
+
|
|
666
|
+
# Roby::Task is an abstract model. See Task::abstract
|
|
667
|
+
abstract
|
|
668
|
+
|
|
669
|
+
# Returns true if this task is from an abstract model. If it is the
|
|
670
|
+
# case, the task is not executable.
|
|
671
|
+
def abstract?; self.class.abstract? end
|
|
672
|
+
# Check if this task is executable
|
|
673
|
+
def executable?; !abstract? && !partially_instanciated? && super end
|
|
674
|
+
# Returns true if this task's stop event is controlable
|
|
675
|
+
def interruptible?; event(:stop).controlable? end
|
|
676
|
+
# Set the executable flag. executable cannot be set to +false+ is the
|
|
677
|
+
# task is running, and cannot be set to true on a finished task.
|
|
678
|
+
def executable=(flag)
|
|
679
|
+
return if flag == @executable
|
|
680
|
+
return unless self_owned?
|
|
681
|
+
if flag && !pending?
|
|
682
|
+
raise ModelViolation, "cannot set the executable flag on a task which is not pending"
|
|
683
|
+
elsif !flag && running?
|
|
684
|
+
raise ModelViolation, "cannot unset the executable flag on a task which is running"
|
|
685
|
+
end
|
|
686
|
+
super
|
|
687
|
+
end
|
|
688
|
+
|
|
689
|
+
# True if all arguments defined by Task.argument on the task model are set.
|
|
690
|
+
def fully_instanciated?
|
|
691
|
+
@fully_instanciated ||= model.arguments.all? { |name| arguments.has_key?(name) }
|
|
692
|
+
end
|
|
693
|
+
# True if at least one argument required by the task model is not set.
|
|
694
|
+
# See Task.argument.
|
|
695
|
+
def partially_instanciated?; !fully_instanciated? end
|
|
696
|
+
|
|
697
|
+
# True if this task has an event of the required model. The event model
|
|
698
|
+
# can either be a event class or an event name.
|
|
699
|
+
def has_event?(event_model)
|
|
700
|
+
bound_events.has_key?(event_model) ||
|
|
701
|
+
self.class.has_event?(event_model)
|
|
702
|
+
end
|
|
703
|
+
|
|
704
|
+
# True if this task is starting, i.e. if its start event is pending
|
|
705
|
+
# (has been called, but is not emitted yet)
|
|
706
|
+
def starting?; event(:start).pending? end
|
|
707
|
+
# True if this task has never been started
|
|
708
|
+
def pending?; !starting? && !started? end
|
|
709
|
+
# True if this task is currently running (i.e. is has already started,
|
|
710
|
+
# and is not finished)
|
|
711
|
+
def running?; started? && !finished? end
|
|
712
|
+
# True if the task is finishing, i.e. if a terminal event is pending.
|
|
713
|
+
def finishing?
|
|
714
|
+
if running?
|
|
715
|
+
each_event { |ev| return true if ev.terminal? && ev.pending? }
|
|
716
|
+
end
|
|
717
|
+
false
|
|
718
|
+
end
|
|
719
|
+
|
|
720
|
+
attr_predicate :started?, true
|
|
721
|
+
attr_predicate :finished?, true
|
|
722
|
+
attr_predicate :success?, true
|
|
723
|
+
|
|
724
|
+
# True if the +failed+ event of this task has been fired
|
|
725
|
+
def failed?; finished? && @success == false end
|
|
726
|
+
|
|
727
|
+
# Remove all relations in which +self+ or its event are involved
|
|
728
|
+
def clear_relations
|
|
729
|
+
each_event { |ev| ev.clear_relations }
|
|
730
|
+
super
|
|
731
|
+
end
|
|
732
|
+
|
|
733
|
+
# Update the terminal flag for the event models that are defined in
|
|
734
|
+
# this task model. The event is terminal if model-level signals (set up
|
|
735
|
+
# by Task::on) lead to the emission of the +stop+ event
|
|
736
|
+
def self.update_terminal_flag # :nodoc:
|
|
737
|
+
events = enum_events.map { |name, _| name }
|
|
738
|
+
terminal_events = [:stop]
|
|
739
|
+
events.delete(:stop)
|
|
740
|
+
|
|
741
|
+
loop do
|
|
742
|
+
old_size = terminal_events.size
|
|
743
|
+
events.delete_if do |ev|
|
|
744
|
+
if signals(ev).any? { |sig_ev| terminal_events.include?(sig_ev) } ||
|
|
745
|
+
forwardings(ev).any? { |sig_ev| terminal_events.include?(sig_ev) }
|
|
746
|
+
terminal_events << ev
|
|
747
|
+
true
|
|
748
|
+
end
|
|
749
|
+
end
|
|
750
|
+
break if old_size == terminal_events.size
|
|
751
|
+
end
|
|
752
|
+
|
|
753
|
+
terminal_events.each do |sym|
|
|
754
|
+
if ev = self.events[sym]
|
|
755
|
+
ev.terminal = true
|
|
756
|
+
else
|
|
757
|
+
ev = superclass.event_model(sym)
|
|
758
|
+
unless ev.terminal?
|
|
759
|
+
event sym, :model => ev, :terminal => true,
|
|
760
|
+
:command => (ev.method(:call) rescue nil)
|
|
761
|
+
end
|
|
762
|
+
end
|
|
763
|
+
end
|
|
764
|
+
end
|
|
765
|
+
|
|
766
|
+
# Updates the terminal flag for all events in the task. An event is
|
|
767
|
+
# terminal if the +stop+ event of the task will be called because this
|
|
768
|
+
# event is.
|
|
769
|
+
def update_terminal_flag # :nodoc:
|
|
770
|
+
return unless @instantiated_model_events
|
|
771
|
+
|
|
772
|
+
for _, ev in bound_events
|
|
773
|
+
ev.terminal_flag = nil
|
|
774
|
+
end
|
|
775
|
+
|
|
776
|
+
success_events, failure_events, terminal_events =
|
|
777
|
+
[event(:success)].to_value_set,
|
|
778
|
+
[event(:failed)].to_value_set,
|
|
779
|
+
[event(:stop), event(:success), event(:failed)].to_value_set
|
|
780
|
+
|
|
781
|
+
loop do
|
|
782
|
+
old_size = terminal_events.size
|
|
783
|
+
for _, ev in bound_events
|
|
784
|
+
for relation in [EventStructure::Signal, EventStructure::Forwarding]
|
|
785
|
+
for target in ev.child_objects(relation)
|
|
786
|
+
next if !target.respond_to?(:task) || target.task != self
|
|
787
|
+
next if ev[target, relation]
|
|
788
|
+
|
|
789
|
+
if success_events.include?(target)
|
|
790
|
+
success_events << ev
|
|
791
|
+
terminal_events << ev
|
|
792
|
+
break
|
|
793
|
+
elsif failure_events.include?(target)
|
|
794
|
+
failure_events << ev
|
|
795
|
+
terminal_events << ev
|
|
796
|
+
break
|
|
797
|
+
elsif terminal_events.include?(target)
|
|
798
|
+
terminal_events << ev
|
|
799
|
+
end
|
|
800
|
+
end
|
|
801
|
+
end
|
|
802
|
+
|
|
803
|
+
success_events.include?(ev) || failure_events.include?(ev) || terminal_events.include?(ev)
|
|
804
|
+
end
|
|
805
|
+
break if old_size == terminal_events.size
|
|
806
|
+
end
|
|
807
|
+
|
|
808
|
+
for ev in success_events
|
|
809
|
+
ev.terminal_flag = :success if ev.respond_to?(:task) && ev.task == self
|
|
810
|
+
end
|
|
811
|
+
for ev in failure_events
|
|
812
|
+
ev.terminal_flag = :failure if ev.respond_to?(:task) && ev.task == self
|
|
813
|
+
end
|
|
814
|
+
for ev in (terminal_events - success_events - failure_events)
|
|
815
|
+
ev.terminal_flag = true if ev.respond_to?(:task) && ev.task == self
|
|
816
|
+
end
|
|
817
|
+
end
|
|
818
|
+
|
|
819
|
+
# Returns a sorted list of Event objects, for all events that have been
|
|
820
|
+
# fired by this task
|
|
821
|
+
def history
|
|
822
|
+
history = []
|
|
823
|
+
each_event do |event|
|
|
824
|
+
history += event.history
|
|
825
|
+
end
|
|
826
|
+
|
|
827
|
+
history.sort_by { |ev| ev.time }
|
|
828
|
+
end
|
|
829
|
+
|
|
830
|
+
# Returns the set of tasks directly related to this task, either
|
|
831
|
+
# because of task relations or because of task events that are related
|
|
832
|
+
# to other task events
|
|
833
|
+
def related_tasks(result = nil)
|
|
834
|
+
result = related_objects(nil, result)
|
|
835
|
+
each_event do |ev|
|
|
836
|
+
ev.related_tasks(result)
|
|
837
|
+
end
|
|
838
|
+
|
|
839
|
+
result
|
|
840
|
+
end
|
|
841
|
+
|
|
842
|
+
# Returns the set of events directly related to this task
|
|
843
|
+
def related_events(result = nil)
|
|
844
|
+
each_event do |ev|
|
|
845
|
+
result = ev.related_events(result)
|
|
846
|
+
end
|
|
847
|
+
|
|
848
|
+
result.reject { |ev| ev.respond_to?(:task) && ev.task == self }.
|
|
849
|
+
to_value_set
|
|
850
|
+
end
|
|
851
|
+
|
|
852
|
+
# This method is called by TaskEventGenerator#fire just before the event handlers
|
|
853
|
+
# and commands are called
|
|
854
|
+
def fire_event(event) # :nodoc:
|
|
855
|
+
if !executable?
|
|
856
|
+
raise TaskNotExecutable.new(self), "trying to fire #{event.generator.symbol} on #{self} but #{self} is not executable"
|
|
857
|
+
end
|
|
858
|
+
|
|
859
|
+
if finished? && !event.terminal?
|
|
860
|
+
raise EmissionFailed.new(nil, self),
|
|
861
|
+
"emit(#{event.symbol}: #{event.model}[#{event.context}]) called @#{event.propagation_id} by #{Propagation.sources} but the task has finished. Task has been terminated by #{event(:stop).history.first.sources}."
|
|
862
|
+
elsif pending? && event.symbol != :start
|
|
863
|
+
raise EmissionFailed.new(nil, self),
|
|
864
|
+
"emit(#{event.symbol}: #{event.model}[#{event.context}]) called @#{event.propagation_id} by #{Propagation.sources} but the task is not running"
|
|
865
|
+
elsif running? && event.symbol == :start
|
|
866
|
+
raise EmissionFailed.new(nil, self),
|
|
867
|
+
"emit(#{event.symbol}: #{event.model}[#{event.context}]) called @#{event.propagation_id} by #{Propagation.sources} but the task is already running. Task has been started by #{event(:start).history.first.sources}."
|
|
868
|
+
end
|
|
869
|
+
|
|
870
|
+
update_task_status(event)
|
|
871
|
+
|
|
872
|
+
super if defined? super
|
|
873
|
+
end
|
|
874
|
+
|
|
875
|
+
# The event which has finished the task (if there is one)
|
|
876
|
+
attr_reader :terminal_event
|
|
877
|
+
|
|
878
|
+
# Call to update the task status because of +event+
|
|
879
|
+
def update_task_status(event) # :nodoc:
|
|
880
|
+
if event.success?
|
|
881
|
+
plan.task_index.set_state(self, :success?)
|
|
882
|
+
self.success = true
|
|
883
|
+
self.finished = true
|
|
884
|
+
@terminal_event ||= event
|
|
885
|
+
elsif event.failure?
|
|
886
|
+
plan.task_index.set_state(self, :failed?)
|
|
887
|
+
self.success = false
|
|
888
|
+
self.finished = true
|
|
889
|
+
@terminal_event ||= event
|
|
890
|
+
elsif event.terminal? && !finished?
|
|
891
|
+
plan.task_index.set_state(self, :finished?)
|
|
892
|
+
self.finished = true
|
|
893
|
+
@terminal_event ||= event
|
|
894
|
+
end
|
|
895
|
+
|
|
896
|
+
if event.symbol == :start
|
|
897
|
+
plan.task_index.set_state(self, :running?)
|
|
898
|
+
self.started = true
|
|
899
|
+
end
|
|
900
|
+
end
|
|
901
|
+
|
|
902
|
+
# List of EventGenerator objects bound to this task
|
|
903
|
+
attr_reader :bound_events
|
|
904
|
+
|
|
905
|
+
# call-seq:
|
|
906
|
+
# emit(event_model, *context) => self
|
|
907
|
+
#
|
|
908
|
+
# Emits +event_model+ in the given +context+. Event handlers are fired.
|
|
909
|
+
# This is equivalent to
|
|
910
|
+
# event(event_model).emit(*context)
|
|
911
|
+
#
|
|
912
|
+
def emit(event_model, *context)
|
|
913
|
+
event(event_model).emit(*context)
|
|
914
|
+
self
|
|
915
|
+
end
|
|
916
|
+
|
|
917
|
+
# Returns the TaskEventGenerator which describes the required event
|
|
918
|
+
# model. +event_model+ can either be an event name or an Event class.
|
|
919
|
+
def event(event_model)
|
|
920
|
+
unless event = bound_events[event_model]
|
|
921
|
+
event_model = self.event_model(event_model)
|
|
922
|
+
unless event = bound_events[event_model.symbol]
|
|
923
|
+
raise "cannot find #{event_model.symbol.inspect} in the set of bound events in #{self}. Known events are #{bound_events}."
|
|
924
|
+
end
|
|
925
|
+
end
|
|
926
|
+
event
|
|
927
|
+
end
|
|
928
|
+
|
|
929
|
+
# call-seq:
|
|
930
|
+
# on(event, task[, event1, event2, ...])
|
|
931
|
+
# on(event) { |event| ... }
|
|
932
|
+
# on(event[, task, event1, event2, ...]) { |event| ... }
|
|
933
|
+
# on(event, task[, event1, event2, ...], delay)
|
|
934
|
+
#
|
|
935
|
+
# Adds a signal from the given event to the specified targets, and/or
|
|
936
|
+
# defines an event handler. Note that <tt>on(event, task)</tt> is
|
|
937
|
+
# equivalent to <tt>on(event, task, event)</tt>
|
|
938
|
+
#
|
|
939
|
+
# +delay+, if given, specifies that the signal must be postponed for as
|
|
940
|
+
# much time as specified. See EventGenerator#signal for valid values.
|
|
941
|
+
def on(event_model, to = nil, *to_task_events, &user_handler)
|
|
942
|
+
unless to || user_handler
|
|
943
|
+
raise ArgumentError, "you must provide either a task or an event handler (got nil for both)"
|
|
944
|
+
end
|
|
945
|
+
|
|
946
|
+
generator = event(event_model)
|
|
947
|
+
if Hash === to_task_events.last
|
|
948
|
+
delay = to_task_events.pop
|
|
949
|
+
end
|
|
950
|
+
to_events = case to
|
|
951
|
+
when Task
|
|
952
|
+
if to_task_events.empty?
|
|
953
|
+
[to.event(generator.symbol)]
|
|
954
|
+
else
|
|
955
|
+
to_task_events.map { |ev_model| to.event(ev_model) }
|
|
956
|
+
end
|
|
957
|
+
when EventGenerator: [to]
|
|
958
|
+
else []
|
|
959
|
+
end
|
|
960
|
+
|
|
961
|
+
to_events.push delay if delay
|
|
962
|
+
generator.on(*to_events, &user_handler)
|
|
963
|
+
self
|
|
964
|
+
end
|
|
965
|
+
|
|
966
|
+
# call-seq:
|
|
967
|
+
# forward source_event, dest_task, ev1, ev2, ev3, ...
|
|
968
|
+
# forward source_event, dest_task, ev1, ev2, ev3, delay_options
|
|
969
|
+
#
|
|
970
|
+
# Fowards +name+ to the events in +to_task_events+ on task +to+. The
|
|
971
|
+
# target events will be emitted as soon as the +name+ event is emitted
|
|
972
|
+
# on the receiving task, without calling any command.
|
|
973
|
+
#
|
|
974
|
+
# To call an event whenever other events are emitted, use the Signal
|
|
975
|
+
# relation. See Task#on, Task.on and EventGenerator#on. As for Task#on,
|
|
976
|
+
# <tt>forward(:start, task)</tt> is a shortcut to <tt>forward(:start,
|
|
977
|
+
# task, :start)</tt>.
|
|
978
|
+
#
|
|
979
|
+
# If a +delay_options+ hash is provided, the forwarding is not performed
|
|
980
|
+
# immediately, but with a given delay. See EventGenerator#forward for
|
|
981
|
+
# the delay specification.
|
|
982
|
+
def forward(name, to, *to_task_events)
|
|
983
|
+
generator = event(name)
|
|
984
|
+
if Hash === to_task_events.last
|
|
985
|
+
delay = to_task_events.pop
|
|
986
|
+
end
|
|
987
|
+
|
|
988
|
+
to_events = if to.respond_to?(:event)
|
|
989
|
+
if to_task_events.empty?
|
|
990
|
+
[to.event(generator.symbol)]
|
|
991
|
+
else
|
|
992
|
+
to_task_events.map { |ev| to.event(ev) }
|
|
993
|
+
end
|
|
994
|
+
elsif to.kind_of?(EventGenerator)
|
|
995
|
+
[to]
|
|
996
|
+
else
|
|
997
|
+
raise ArgumentError, "expected Task or EventGenerator, got #{to}(#{to.class}: #{to.class.ancestors})"
|
|
998
|
+
end
|
|
999
|
+
|
|
1000
|
+
to_events.each do |ev|
|
|
1001
|
+
generator.forward ev, delay
|
|
1002
|
+
end
|
|
1003
|
+
end
|
|
1004
|
+
|
|
1005
|
+
attr_accessor :calling_event
|
|
1006
|
+
def method_missing(name, *args, &block) # :nodoc:
|
|
1007
|
+
if calling_event && calling_event.respond_to?(name)
|
|
1008
|
+
calling_event.send(name, *args, &block)
|
|
1009
|
+
else
|
|
1010
|
+
super
|
|
1011
|
+
end
|
|
1012
|
+
rescue
|
|
1013
|
+
raise $!, $!.message, $!.backtrace[1..-1]
|
|
1014
|
+
end
|
|
1015
|
+
|
|
1016
|
+
@@event_command_id = 0
|
|
1017
|
+
def self.allocate_event_command_id # :nodoc:
|
|
1018
|
+
@@event_command_id += 1
|
|
1019
|
+
end
|
|
1020
|
+
# call-seq:
|
|
1021
|
+
# self.event(name, options = nil) { ... } -> event class or nil
|
|
1022
|
+
#
|
|
1023
|
+
# Define a new event in this task.
|
|
1024
|
+
#
|
|
1025
|
+
# ==== Available options
|
|
1026
|
+
#
|
|
1027
|
+
# <tt>command</tt>::
|
|
1028
|
+
# either true, false or an event command for the new event. In that
|
|
1029
|
+
# latter case, the command is an object which must respond to
|
|
1030
|
+
# #to_proc. If it is true, a default handler is defined which simply
|
|
1031
|
+
# emits the event. If a block is given, it is used as the event
|
|
1032
|
+
# command.
|
|
1033
|
+
#
|
|
1034
|
+
# <tt>terminal</tt>::
|
|
1035
|
+
# set to true if this event is a terminal event, i.e. if its emission
|
|
1036
|
+
# means that the task has finished.
|
|
1037
|
+
#
|
|
1038
|
+
# <tt>model</tt>::
|
|
1039
|
+
# base class for the event model (see "Event models" below). The default is the
|
|
1040
|
+
# TaskEvent class
|
|
1041
|
+
#
|
|
1042
|
+
# ==== Event models
|
|
1043
|
+
#
|
|
1044
|
+
# When a task event (for instance +start+) is emitted, a Roby::Event
|
|
1045
|
+
# object is created to describe the information related to this
|
|
1046
|
+
# emission (time, sources, context information, ...). Task.event
|
|
1047
|
+
# defines a specific event model MyTask::MyEvent for each task event
|
|
1048
|
+
# with name :my_event. This specific model is by default a subclass of
|
|
1049
|
+
# Roby::TaskEvent, but it is possible to override that by using the +model+
|
|
1050
|
+
# option.
|
|
1051
|
+
def self.event(ev, options = Hash.new, &block)
|
|
1052
|
+
options = validate_options(options, :command => nil, :terminal => nil, :model => TaskEvent)
|
|
1053
|
+
|
|
1054
|
+
ev_s = ev.to_s
|
|
1055
|
+
ev = ev.to_sym
|
|
1056
|
+
|
|
1057
|
+
if !options.has_key?(:command)
|
|
1058
|
+
if block
|
|
1059
|
+
define_method("event_command_#{ev_s}", &block)
|
|
1060
|
+
method = instance_method("event_command_#{ev_s}")
|
|
1061
|
+
end
|
|
1062
|
+
|
|
1063
|
+
if method
|
|
1064
|
+
check_arity(method, 1)
|
|
1065
|
+
options[:command] = lambda do |dst_task, *event_context|
|
|
1066
|
+
begin
|
|
1067
|
+
dst_task.calling_event = dst_task.event(ev)
|
|
1068
|
+
method.bind(dst_task).call(*event_context)
|
|
1069
|
+
ensure
|
|
1070
|
+
dst_task.calling_event = nil
|
|
1071
|
+
end
|
|
1072
|
+
end
|
|
1073
|
+
end
|
|
1074
|
+
end
|
|
1075
|
+
validate_event_definition_request(ev, options)
|
|
1076
|
+
|
|
1077
|
+
command_handler = options[:command] if options[:command].respond_to?(:call)
|
|
1078
|
+
|
|
1079
|
+
# Define the event class
|
|
1080
|
+
task_klass = self
|
|
1081
|
+
new_event = Class.new(options[:model]) do
|
|
1082
|
+
@terminal = options[:terminal]
|
|
1083
|
+
@symbol = ev
|
|
1084
|
+
@command_handler = command_handler
|
|
1085
|
+
|
|
1086
|
+
define_method(:name) { "#{task.name}::#{ev_s.camelize}" }
|
|
1087
|
+
singleton_class.class_eval do
|
|
1088
|
+
attr_reader :command_handler
|
|
1089
|
+
define_method(:name) { "#{task_klass.name}::#{ev_s.camelize}" }
|
|
1090
|
+
def to_s; name end
|
|
1091
|
+
end
|
|
1092
|
+
end
|
|
1093
|
+
|
|
1094
|
+
setup_terminal_handler = false
|
|
1095
|
+
old_model = find_event_model(ev)
|
|
1096
|
+
if new_event.symbol != :stop && options[:terminal] && (!old_model || !old_model.terminal?)
|
|
1097
|
+
setup_terminal_handler = true
|
|
1098
|
+
end
|
|
1099
|
+
|
|
1100
|
+
events[new_event.symbol] = new_event
|
|
1101
|
+
if setup_terminal_handler
|
|
1102
|
+
forward(new_event => :stop)
|
|
1103
|
+
end
|
|
1104
|
+
const_set(ev_s.camelize, new_event)
|
|
1105
|
+
|
|
1106
|
+
if options[:command]
|
|
1107
|
+
# check that the supplied command handler can take two arguments
|
|
1108
|
+
check_arity(command_handler, 2) if command_handler
|
|
1109
|
+
|
|
1110
|
+
# define #call on the event model
|
|
1111
|
+
new_event.singleton_class.class_eval do
|
|
1112
|
+
if command_handler
|
|
1113
|
+
define_method(:call, &command_handler)
|
|
1114
|
+
else
|
|
1115
|
+
def call(task, context) # :nodoc:
|
|
1116
|
+
task.emit(symbol, *context)
|
|
1117
|
+
end
|
|
1118
|
+
end
|
|
1119
|
+
end
|
|
1120
|
+
|
|
1121
|
+
# define an instance method which calls the event command
|
|
1122
|
+
define_method("#{ev_s}!") do |*context|
|
|
1123
|
+
begin
|
|
1124
|
+
generator = event(ev)
|
|
1125
|
+
generator.call(*context)
|
|
1126
|
+
rescue EventNotExecutable => e
|
|
1127
|
+
if partially_instanciated?
|
|
1128
|
+
raise EventNotExecutable.new(generator), "#{ev_s}! called on #{generator.task} which is partially instanciated"
|
|
1129
|
+
elsif !plan
|
|
1130
|
+
raise EventNotExecutable.new(generator), "#{ev_s}! called on #{generator.task} but the task is in no plan"
|
|
1131
|
+
elsif !plan.executable?
|
|
1132
|
+
raise EventNotExecutable.new(generator), "#{ev_s}! called on #{generator.task} but the plan is not executable"
|
|
1133
|
+
elsif abstract?
|
|
1134
|
+
raise EventNotExecutable.new(generator), "#{ev_s}! called on #{generator.task} but the task is abstract"
|
|
1135
|
+
else
|
|
1136
|
+
raise EventNotExecutable.new(generator), "#{ev_s}! called on #{generator.task} which is not executable: #{e.message}"
|
|
1137
|
+
end
|
|
1138
|
+
end
|
|
1139
|
+
end
|
|
1140
|
+
end
|
|
1141
|
+
|
|
1142
|
+
new_event
|
|
1143
|
+
end
|
|
1144
|
+
|
|
1145
|
+
def self.validate_event_definition_request(ev, options) #:nodoc:
|
|
1146
|
+
if options[:command] && options[:command] != true && !options[:command].respond_to?(:call)
|
|
1147
|
+
raise ArgumentError, "Allowed values for :command option: true, false, nil and an object responding to #call. Got #{options[:command]}"
|
|
1148
|
+
end
|
|
1149
|
+
|
|
1150
|
+
if ev.to_sym == :stop
|
|
1151
|
+
if options.has_key?(:terminal) && !options[:terminal]
|
|
1152
|
+
raise ArgumentError, "the 'stop' event cannot be non-terminal"
|
|
1153
|
+
end
|
|
1154
|
+
options[:terminal] = true
|
|
1155
|
+
end
|
|
1156
|
+
|
|
1157
|
+
# Check for inheritance rules
|
|
1158
|
+
if events.include?(ev)
|
|
1159
|
+
raise ArgumentError, "event #{ev} already defined"
|
|
1160
|
+
elsif old_event = find_event_model(ev)
|
|
1161
|
+
if old_event.terminal? && !options[:terminal]
|
|
1162
|
+
raise ArgumentError, "trying to override a terminal event into a non-terminal one", caller(2)
|
|
1163
|
+
elsif old_event.controlable? && !options[:command]
|
|
1164
|
+
raise ArgumentError, "trying to override a controlable event into a non-controlable one", caller(2)
|
|
1165
|
+
end
|
|
1166
|
+
end
|
|
1167
|
+
end
|
|
1168
|
+
|
|
1169
|
+
# Events defined by the task model
|
|
1170
|
+
inherited_enumerable(:event, :events, :map => true) { Hash.new }
|
|
1171
|
+
def self.enum_events
|
|
1172
|
+
@__enum_events__ ||= enum_for(:each_event)
|
|
1173
|
+
end
|
|
1174
|
+
|
|
1175
|
+
# Iterates on all the events defined for this task
|
|
1176
|
+
def each_event # :yield:bound_event
|
|
1177
|
+
for _, ev in bound_events
|
|
1178
|
+
yield(ev)
|
|
1179
|
+
end
|
|
1180
|
+
end
|
|
1181
|
+
alias :each_plan_child :each_event
|
|
1182
|
+
|
|
1183
|
+
# Returns the set of terminal events this task has. A terminal event is
|
|
1184
|
+
# an event whose emission announces the end of the task. In most case,
|
|
1185
|
+
# it is an event which is forwarded directly on indirectly to +stop+.
|
|
1186
|
+
def terminal_events
|
|
1187
|
+
bound_events.values.find_all { |ev| ev.terminal? }
|
|
1188
|
+
end
|
|
1189
|
+
|
|
1190
|
+
# Get the list of terminal events for this task model
|
|
1191
|
+
def self.terminal_events
|
|
1192
|
+
enum_events.find_all { |_, e| e.terminal? }.
|
|
1193
|
+
map { |_, e| e }
|
|
1194
|
+
end
|
|
1195
|
+
|
|
1196
|
+
# Get the event model for +event+
|
|
1197
|
+
def event_model(model); self.model.event_model(model) end
|
|
1198
|
+
|
|
1199
|
+
# Find the event class for +event+, or nil if +event+ is not an event name for this model
|
|
1200
|
+
def self.find_event_model(name)
|
|
1201
|
+
name = name.to_sym
|
|
1202
|
+
each_event { |sym, e| return e if sym == name }
|
|
1203
|
+
nil
|
|
1204
|
+
end
|
|
1205
|
+
|
|
1206
|
+
# Checks that all events in +events+ are valid events for this task.
|
|
1207
|
+
# The requested events can be either an event name (symbol or string)
|
|
1208
|
+
# or an event class
|
|
1209
|
+
#
|
|
1210
|
+
# Returns the corresponding array of event classes
|
|
1211
|
+
def self.event_model(model_def) #:nodoc:
|
|
1212
|
+
if model_def.respond_to?(:to_sym)
|
|
1213
|
+
ev_model = find_event_model(model_def.to_sym)
|
|
1214
|
+
unless ev_model
|
|
1215
|
+
all_events = enum_events.map { |name, _| name }
|
|
1216
|
+
raise ArgumentError, "#{model_def} is not an event of #{name}: #{all_events}" unless ev_model
|
|
1217
|
+
end
|
|
1218
|
+
elsif model_def.respond_to?(:has_ancestor?) && model_def.has_ancestor?(TaskEvent)
|
|
1219
|
+
# Check that model_def is an event class for us
|
|
1220
|
+
ev_model = find_event_model(model_def.symbol)
|
|
1221
|
+
if !ev_model
|
|
1222
|
+
raise ArgumentError, "no #{model_def.symbol} event in #{name}"
|
|
1223
|
+
elsif ev_model != model_def
|
|
1224
|
+
raise ArgumentError, "the event model #{model_def} is not a model for #{name} (found #{ev_model} with the same name)"
|
|
1225
|
+
end
|
|
1226
|
+
else
|
|
1227
|
+
raise ArgumentError, "wanted either a symbol or an event class, got #{model_def}"
|
|
1228
|
+
end
|
|
1229
|
+
|
|
1230
|
+
ev_model
|
|
1231
|
+
end
|
|
1232
|
+
|
|
1233
|
+
class << self
|
|
1234
|
+
# Checks if _name_ is a name for an event of this task
|
|
1235
|
+
alias :has_event? :find_event_model
|
|
1236
|
+
|
|
1237
|
+
private :validate_event_definition_request
|
|
1238
|
+
end
|
|
1239
|
+
|
|
1240
|
+
# call-seq:
|
|
1241
|
+
# on(event_model) { |event| ... }
|
|
1242
|
+
# on(event_model => ev1, ev2 => [ ev3, ev4 ]) { |event| ... }
|
|
1243
|
+
#
|
|
1244
|
+
# Adds an event handler for the given event model. When the event is fired,
|
|
1245
|
+
# all events given in argument will be called. If they are controlable,
|
|
1246
|
+
# then the command is called. If not, they are just fired
|
|
1247
|
+
def self.on(mappings, &user_handler)
|
|
1248
|
+
if user_handler
|
|
1249
|
+
check_arity(user_handler, 1)
|
|
1250
|
+
end
|
|
1251
|
+
|
|
1252
|
+
mappings = [*mappings].zip([]) unless Hash === mappings
|
|
1253
|
+
mappings.each do |from, to|
|
|
1254
|
+
from = event_model(from).symbol
|
|
1255
|
+
to = if to
|
|
1256
|
+
Array[*to].map do |ev|
|
|
1257
|
+
model = event_model(ev)
|
|
1258
|
+
raise ArgumentError, "trying to signal #{ev} which is not controlable" unless model.controlable?
|
|
1259
|
+
model.symbol
|
|
1260
|
+
end
|
|
1261
|
+
else; []
|
|
1262
|
+
end
|
|
1263
|
+
|
|
1264
|
+
signal_sets[from].merge to.to_value_set
|
|
1265
|
+
update_terminal_flag
|
|
1266
|
+
|
|
1267
|
+
if user_handler
|
|
1268
|
+
method_name = "event_handler_#{from}_#{Object.address_from_id(user_handler.object_id).to_s(16)}"
|
|
1269
|
+
define_method(method_name, &user_handler)
|
|
1270
|
+
handler_sets[from] << lambda { |event| event.task.send(method_name, event) }
|
|
1271
|
+
end
|
|
1272
|
+
end
|
|
1273
|
+
end
|
|
1274
|
+
|
|
1275
|
+
# call-seq:
|
|
1276
|
+
# causal_link(:from => :to)
|
|
1277
|
+
#
|
|
1278
|
+
# Declares a causal link between two events in the task. See
|
|
1279
|
+
# EventStructure::CausalLink for a description of the causal link
|
|
1280
|
+
# relation.
|
|
1281
|
+
def self.causal_link(mappings)
|
|
1282
|
+
mappings.each do |from, to|
|
|
1283
|
+
from = event_model(from).symbol
|
|
1284
|
+
causal_link_sets[from].merge Array[*to].map { |ev| event_model(ev).symbol }.to_value_set
|
|
1285
|
+
end
|
|
1286
|
+
update_terminal_flag
|
|
1287
|
+
end
|
|
1288
|
+
|
|
1289
|
+
# call-seq:
|
|
1290
|
+
# forward :from => :to
|
|
1291
|
+
#
|
|
1292
|
+
# Defines a forwarding relation between two events of the same task
|
|
1293
|
+
# instance. See EventStructure::Forward for a description of the
|
|
1294
|
+
# forwarding relation.
|
|
1295
|
+
#
|
|
1296
|
+
# See also Task#forward and EventGenerator#forward.
|
|
1297
|
+
def self.forward(mappings)
|
|
1298
|
+
mappings.each do |from, to|
|
|
1299
|
+
from = event_model(from).symbol
|
|
1300
|
+
forwarding_sets[from].merge Array[*to].map { |ev| event_model(ev).symbol }.to_value_set
|
|
1301
|
+
end
|
|
1302
|
+
update_terminal_flag
|
|
1303
|
+
end
|
|
1304
|
+
|
|
1305
|
+
def self.precondition(event, reason, &block)
|
|
1306
|
+
event = event_model(event)
|
|
1307
|
+
precondition_sets[event.symbol] << [reason, block]
|
|
1308
|
+
end
|
|
1309
|
+
|
|
1310
|
+
def to_s # :nodoc:
|
|
1311
|
+
s = name.dup
|
|
1312
|
+
id = owners.map do |owner|
|
|
1313
|
+
next if owner == Roby::Distributed
|
|
1314
|
+
sibling = remote_siblings[owner]
|
|
1315
|
+
"#{sibling ? Object.address_from_id(sibling.ref).to_s(16) : 'nil'}@#{owner.remote_name}"
|
|
1316
|
+
end
|
|
1317
|
+
unless id.empty?
|
|
1318
|
+
s << "[" << id.join(",") << "]"
|
|
1319
|
+
end
|
|
1320
|
+
s
|
|
1321
|
+
end
|
|
1322
|
+
|
|
1323
|
+
def pretty_print(pp) # :nodoc:
|
|
1324
|
+
pp.text "#{self.class.name}:0x#{self.address.to_s(16)}"
|
|
1325
|
+
pp.breakable
|
|
1326
|
+
pp.nest(2) do
|
|
1327
|
+
pp.text " owners: "
|
|
1328
|
+
pp.seplist(owners) { |r| pp.text r.to_s }
|
|
1329
|
+
|
|
1330
|
+
pp.breakable
|
|
1331
|
+
pp.text "arguments: "
|
|
1332
|
+
arguments.pretty_print(pp)
|
|
1333
|
+
end
|
|
1334
|
+
end
|
|
1335
|
+
|
|
1336
|
+
# True if this task is a null task. See NullTask.
|
|
1337
|
+
def null?; false end
|
|
1338
|
+
# Converts this object into a task object
|
|
1339
|
+
def to_task; self end
|
|
1340
|
+
|
|
1341
|
+
event :start, :command => true
|
|
1342
|
+
|
|
1343
|
+
# Define :stop before any other terminal event
|
|
1344
|
+
event :stop
|
|
1345
|
+
event :success, :terminal => true
|
|
1346
|
+
event :failed, :terminal => true
|
|
1347
|
+
|
|
1348
|
+
event :aborted
|
|
1349
|
+
forward :aborted => :failed
|
|
1350
|
+
|
|
1351
|
+
# The internal data for this task
|
|
1352
|
+
attr_reader :data
|
|
1353
|
+
# Sets the internal data value for this task. This calls the
|
|
1354
|
+
# #updated_data hook, and emits +updated_data+ if the task is running.
|
|
1355
|
+
def data=(value)
|
|
1356
|
+
@data = value
|
|
1357
|
+
updated_data
|
|
1358
|
+
emit :updated_data if running?
|
|
1359
|
+
end
|
|
1360
|
+
# This hook is called whenever the internal data of this task is
|
|
1361
|
+
# updated. See #data, #data= and the +updated_data+ event
|
|
1362
|
+
def updated_data
|
|
1363
|
+
super if defined? super
|
|
1364
|
+
end
|
|
1365
|
+
event :updated_data, :command => false
|
|
1366
|
+
|
|
1367
|
+
# Checks if +task+ is in the same execution state than +self+
|
|
1368
|
+
# Returns true if they are either both running or both pending
|
|
1369
|
+
def compatible_state?(task)
|
|
1370
|
+
finished? || !(running? ^ task.running?)
|
|
1371
|
+
end
|
|
1372
|
+
|
|
1373
|
+
# Returns the lists of tags this model fullfills.
|
|
1374
|
+
def self.tags
|
|
1375
|
+
ancestors.find_all { |m| m.instance_of?(TaskModelTag) }
|
|
1376
|
+
end
|
|
1377
|
+
|
|
1378
|
+
# The fullfills? predicate checks if this task can be used
|
|
1379
|
+
# to fullfill the need of the given +model+ and +arguments+
|
|
1380
|
+
# The default is to check if
|
|
1381
|
+
# * the needed task model is an ancestor of this task
|
|
1382
|
+
# * the task
|
|
1383
|
+
# * +args+ is included in the task arguments
|
|
1384
|
+
def fullfills?(models, args = {})
|
|
1385
|
+
if models.kind_of?(Task)
|
|
1386
|
+
klass, args =
|
|
1387
|
+
models.class,
|
|
1388
|
+
models.meaningful_arguments
|
|
1389
|
+
models = [klass]
|
|
1390
|
+
else
|
|
1391
|
+
models = [*models]
|
|
1392
|
+
end
|
|
1393
|
+
self_model = self.model
|
|
1394
|
+
self_args = self.arguments
|
|
1395
|
+
args = args.dup
|
|
1396
|
+
|
|
1397
|
+
# Check the arguments that are required by the model
|
|
1398
|
+
for tag in models
|
|
1399
|
+
unless self_model.has_ancestor?(tag)
|
|
1400
|
+
return false
|
|
1401
|
+
end
|
|
1402
|
+
|
|
1403
|
+
unless args.empty?
|
|
1404
|
+
for arg_name in tag.arguments
|
|
1405
|
+
if user_arg = args.delete(arg_name)
|
|
1406
|
+
return false unless user_arg == self_args[arg_name]
|
|
1407
|
+
end
|
|
1408
|
+
break if args.empty?
|
|
1409
|
+
end
|
|
1410
|
+
end
|
|
1411
|
+
end
|
|
1412
|
+
|
|
1413
|
+
if !args.empty?
|
|
1414
|
+
raise ArgumentError, "the arguments '#{args.keys.join(", ")}' are unknown to the tags #{models.join(", ")}"
|
|
1415
|
+
end
|
|
1416
|
+
true
|
|
1417
|
+
end
|
|
1418
|
+
|
|
1419
|
+
include ExceptionHandlingObject
|
|
1420
|
+
inherited_enumerable('exception_handler', 'exception_handlers') { Array.new }
|
|
1421
|
+
|
|
1422
|
+
# Lists all exception handlers attached to this task
|
|
1423
|
+
def each_exception_handler(&iterator); model.each_exception_handler(&iterator) end
|
|
1424
|
+
|
|
1425
|
+
@@exception_handler_id = 0
|
|
1426
|
+
|
|
1427
|
+
# call-seq:
|
|
1428
|
+
# on_exception(TaskModelViolation, ...) { |task, exception_object| ... }
|
|
1429
|
+
#
|
|
1430
|
+
# Defines an exception handler. matcher === exception_object is used to
|
|
1431
|
+
# determine if the handler should be called when +exception_object+ has
|
|
1432
|
+
# been fired. The first matching handler is called. Call #pass_exception to pass
|
|
1433
|
+
# the exception to previous handlers
|
|
1434
|
+
#
|
|
1435
|
+
# on_exception(TaskModelViolation, ...) do |task, exception_object|
|
|
1436
|
+
# if cannot_handle
|
|
1437
|
+
# task.pass_exception # send to the next handler
|
|
1438
|
+
# end
|
|
1439
|
+
# do_handle
|
|
1440
|
+
# end
|
|
1441
|
+
def self.on_exception(*matchers, &handler)
|
|
1442
|
+
check_arity(handler, 1)
|
|
1443
|
+
id = (@@exception_handler_id += 1)
|
|
1444
|
+
define_method("exception_handler_#{id}", &handler)
|
|
1445
|
+
exception_handlers.unshift [matchers, instance_method("exception_handler_#{id}")]
|
|
1446
|
+
end
|
|
1447
|
+
|
|
1448
|
+
# We can't add relations on objects we don't own
|
|
1449
|
+
def add_child_object(child, type, info)
|
|
1450
|
+
unless read_write? && child.read_write?
|
|
1451
|
+
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}"
|
|
1452
|
+
end
|
|
1453
|
+
|
|
1454
|
+
super
|
|
1455
|
+
end
|
|
1456
|
+
|
|
1457
|
+
# This method is called during the commit process to
|
|
1458
|
+
def commit_transaction
|
|
1459
|
+
super if defined? super
|
|
1460
|
+
|
|
1461
|
+
arguments.dup.each do |key, value|
|
|
1462
|
+
if value.kind_of?(Roby::Transactions::Proxy)
|
|
1463
|
+
arguments.update!(key, value.__getobj__)
|
|
1464
|
+
end
|
|
1465
|
+
end
|
|
1466
|
+
end
|
|
1467
|
+
|
|
1468
|
+
# Create a new task of the same model and with the same arguments
|
|
1469
|
+
# than this one. Insert this task in the plan and make it replace
|
|
1470
|
+
# the fresh one.
|
|
1471
|
+
#
|
|
1472
|
+
# See Plan#respawn
|
|
1473
|
+
def respawn
|
|
1474
|
+
plan.respawn(self)
|
|
1475
|
+
end
|
|
1476
|
+
|
|
1477
|
+
# Replaces, in the plan, the subplan generated by this plan object by
|
|
1478
|
+
# the one generated by +object+. In practice, it means that we transfer
|
|
1479
|
+
# all parent edges whose target is +self+ from the receiver to
|
|
1480
|
+
# +object+. It calls the various add/remove hooks defined in
|
|
1481
|
+
# DirectedRelationSupport.
|
|
1482
|
+
def replace_subplan_by(object)
|
|
1483
|
+
super
|
|
1484
|
+
|
|
1485
|
+
# Compute the set of tasks that are in our subtree and not in
|
|
1486
|
+
# object's *after* the replacement
|
|
1487
|
+
own_subtree = ValueSet.new
|
|
1488
|
+
TaskStructure.each_root_relation do |rel|
|
|
1489
|
+
own_subtree.merge generated_subgraph(rel)
|
|
1490
|
+
own_subtree -= object.generated_subgraph(rel)
|
|
1491
|
+
end
|
|
1492
|
+
|
|
1493
|
+
changes = []
|
|
1494
|
+
each_event do |event|
|
|
1495
|
+
next unless object.has_event?(event.symbol)
|
|
1496
|
+
changes.clear
|
|
1497
|
+
|
|
1498
|
+
event.each_relation do |rel|
|
|
1499
|
+
parents = []
|
|
1500
|
+
event.each_parent_object(rel) do |parent|
|
|
1501
|
+
if !parent.respond_to?(:task) || !own_subtree.include?(parent.task)
|
|
1502
|
+
parents << parent << parent[event, rel]
|
|
1503
|
+
end
|
|
1504
|
+
end
|
|
1505
|
+
children = []
|
|
1506
|
+
event.each_child_object(rel) do |child|
|
|
1507
|
+
if !child.respond_to?(:task) || !own_subtree.include?(child.task)
|
|
1508
|
+
children << child << event[child, rel]
|
|
1509
|
+
end
|
|
1510
|
+
end
|
|
1511
|
+
changes << rel << parents << children
|
|
1512
|
+
end
|
|
1513
|
+
|
|
1514
|
+
event.apply_relation_changes(object.event(event.symbol), changes)
|
|
1515
|
+
end
|
|
1516
|
+
end
|
|
1517
|
+
|
|
1518
|
+
# Replaces +self+ by +object+ in all relations +self+ is part of, and
|
|
1519
|
+
# do the same for the task's event generators.
|
|
1520
|
+
def replace_by(object)
|
|
1521
|
+
each_event do |event|
|
|
1522
|
+
event_name = event.symbol
|
|
1523
|
+
if object.has_event?(event_name)
|
|
1524
|
+
event.replace_by object.event(event_name)
|
|
1525
|
+
end
|
|
1526
|
+
end
|
|
1527
|
+
super
|
|
1528
|
+
end
|
|
1529
|
+
|
|
1530
|
+
# Define a polling block for this task. The polling block is called at
|
|
1531
|
+
# each execution cycle while the task is running (i.e. in-between the
|
|
1532
|
+
# emission of +start+ and the emission of +stop+.
|
|
1533
|
+
#
|
|
1534
|
+
# Raises ArgumentError if the task is already running.
|
|
1535
|
+
#
|
|
1536
|
+
# See also Task::poll
|
|
1537
|
+
def poll(&block)
|
|
1538
|
+
if !pending?
|
|
1539
|
+
raise ArgumentError, "cannot set a polling block when the task is not pending"
|
|
1540
|
+
end
|
|
1541
|
+
|
|
1542
|
+
|
|
1543
|
+
singleton_class.class_eval do
|
|
1544
|
+
setup_poll_method(block)
|
|
1545
|
+
end
|
|
1546
|
+
on(:start) { Control.event_processing << method(:poll) }
|
|
1547
|
+
on(:stop) { Control.event_processing.delete(method(:poll)) }
|
|
1548
|
+
end
|
|
1549
|
+
end
|
|
1550
|
+
|
|
1551
|
+
# A special task model which does nothing and emits +success+
|
|
1552
|
+
# as soon as it is started.
|
|
1553
|
+
class NullTask < Task
|
|
1554
|
+
event :start, :command => true
|
|
1555
|
+
event :stop
|
|
1556
|
+
forward :start => :success
|
|
1557
|
+
|
|
1558
|
+
# Always true. See Task#null?
|
|
1559
|
+
def null?; true end
|
|
1560
|
+
end
|
|
1561
|
+
|
|
1562
|
+
# A virtual task is a task representation for a combination of two events.
|
|
1563
|
+
# This allows to combine two unrelated events, one being the +start+ event
|
|
1564
|
+
# of the virtual task and the other its success event.
|
|
1565
|
+
#
|
|
1566
|
+
# The task fails if the success event becomes unreachable.
|
|
1567
|
+
#
|
|
1568
|
+
# See VirtualTask.create
|
|
1569
|
+
class VirtualTask < Task
|
|
1570
|
+
# The start event
|
|
1571
|
+
attr_reader :start_event
|
|
1572
|
+
# The success event
|
|
1573
|
+
attr_accessor :success_event
|
|
1574
|
+
# Set the start event
|
|
1575
|
+
def start_event=(ev)
|
|
1576
|
+
if !ev.controlable?
|
|
1577
|
+
raise ArgumentError, "the start event of a virtual task must be controlable"
|
|
1578
|
+
end
|
|
1579
|
+
@start_event = ev
|
|
1580
|
+
end
|
|
1581
|
+
|
|
1582
|
+
event :start do
|
|
1583
|
+
event(:start).achieve_with(start_event)
|
|
1584
|
+
start_event.call
|
|
1585
|
+
end
|
|
1586
|
+
on :start do
|
|
1587
|
+
success_event.forward_once event(:success)
|
|
1588
|
+
success_event.if_unreachable(true) do
|
|
1589
|
+
emit :failed if executable?
|
|
1590
|
+
end
|
|
1591
|
+
end
|
|
1592
|
+
|
|
1593
|
+
terminates
|
|
1594
|
+
|
|
1595
|
+
# Creates a new VirtualTask with the given start and success events
|
|
1596
|
+
def self.create(start, success)
|
|
1597
|
+
task = VirtualTask.new
|
|
1598
|
+
task.start_event = start
|
|
1599
|
+
task.success_event = success
|
|
1600
|
+
|
|
1601
|
+
if start.respond_to?(:task)
|
|
1602
|
+
task.realized_by start.task
|
|
1603
|
+
end
|
|
1604
|
+
if success.respond_to?(:task)
|
|
1605
|
+
task.realized_by success.task
|
|
1606
|
+
end
|
|
1607
|
+
|
|
1608
|
+
task
|
|
1609
|
+
end
|
|
1610
|
+
end
|
|
1611
|
+
|
|
1612
|
+
unless defined? TaskStructure
|
|
1613
|
+
TaskStructure = RelationSpace(Task)
|
|
1614
|
+
end
|
|
1615
|
+
end
|
|
1616
|
+
|
|
1617
|
+
require 'roby/task-operations'
|
|
1618
|
+
|