roby 0.7
Sign up to get free protection for your applications and to get access to all the features.
- 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/plan.rb
ADDED
@@ -0,0 +1,762 @@
|
|
1
|
+
require 'roby/event'
|
2
|
+
require 'roby/task'
|
3
|
+
require 'roby/relations'
|
4
|
+
require 'roby/basic_object'
|
5
|
+
|
6
|
+
module Roby
|
7
|
+
# A plan object is a collection of tasks and events. In plans, tasks can be
|
8
|
+
# +missions+ (#missions, #mission?), which means that they are the final
|
9
|
+
# goals of the system. A task is +useful+ if it helps into the realization
|
10
|
+
# of a mission (it is linked to a mission by #hierarchy_relation or one
|
11
|
+
# of the #service_relations), and is not useful otherwise. #garbage_collect
|
12
|
+
# removes the tasks that are not useful.
|
13
|
+
#
|
14
|
+
# The following event hooks are defined:
|
15
|
+
# * #inserted
|
16
|
+
# * #discarded
|
17
|
+
# * #discovered_tasks
|
18
|
+
# * #discovered_events
|
19
|
+
# * #replaced
|
20
|
+
# * #added_transaction
|
21
|
+
# * #removed_transaction
|
22
|
+
# * #garbage
|
23
|
+
# * #finalized_task
|
24
|
+
# * #finalized_event
|
25
|
+
#
|
26
|
+
class Plan < BasicObject
|
27
|
+
extend Logger::Hierarchy
|
28
|
+
extend Logger::Forward
|
29
|
+
|
30
|
+
# The task index for this plan
|
31
|
+
attr_reader :task_index
|
32
|
+
|
33
|
+
# The list of tasks that are included in this plan
|
34
|
+
attr_reader :known_tasks
|
35
|
+
# The set of events that are defined by #known_tasks
|
36
|
+
attr_reader :task_events
|
37
|
+
# The list of the robot's missions. Do not change that set directly, use
|
38
|
+
# #insert and #discard instead.
|
39
|
+
attr_reader :missions
|
40
|
+
# The list of events that are not included in a task
|
41
|
+
attr_reader :free_events
|
42
|
+
# The list of tasks that are kept outside GC. Do not change that set
|
43
|
+
# directly, use #permanent and #auto instead.
|
44
|
+
attr_reader :keepalive
|
45
|
+
|
46
|
+
# A map of event => task repairs. Whenever an exception is found,
|
47
|
+
# exception propagation checks that no repair is defined for that
|
48
|
+
# particular event or for events that are forwarded by it.
|
49
|
+
#
|
50
|
+
# See also #add_repair and #remove_repair
|
51
|
+
attr_reader :repairs
|
52
|
+
|
53
|
+
# A set of tasks which are useful (and as such would not been garbage
|
54
|
+
# collected), but we want to GC anyway
|
55
|
+
attr_reader :force_gc
|
56
|
+
|
57
|
+
# A set of task for which GC should not be attempted, either because
|
58
|
+
# they are not interruptible or because their start or stop command
|
59
|
+
# failed
|
60
|
+
attr_reader :gc_quarantine
|
61
|
+
|
62
|
+
# The set of transactions which are built on top of this plan
|
63
|
+
attr_reader :transactions
|
64
|
+
|
65
|
+
# If this object is the main plan, checks if we are subscribed to
|
66
|
+
# the whole remote plan
|
67
|
+
def sibling_on?(peer)
|
68
|
+
if Roby.plan == self then peer.remote_plan
|
69
|
+
else super
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def initialize
|
74
|
+
@missions = ValueSet.new
|
75
|
+
@keepalive = ValueSet.new
|
76
|
+
@known_tasks = ValueSet.new
|
77
|
+
@free_events = ValueSet.new
|
78
|
+
@task_events = ValueSet.new
|
79
|
+
@force_gc = ValueSet.new
|
80
|
+
@gc_quarantine = ValueSet.new
|
81
|
+
@transactions = ValueSet.new
|
82
|
+
@repairs = Hash.new
|
83
|
+
|
84
|
+
@task_index = Roby::TaskIndex.new
|
85
|
+
|
86
|
+
super() if defined? super
|
87
|
+
end
|
88
|
+
|
89
|
+
def inspect
|
90
|
+
"#<#{to_s}: missions=#{missions.to_s} tasks=#{known_tasks.to_s} events=#{free_events.to_s} transactions=#{transactions.to_s}>"
|
91
|
+
end
|
92
|
+
|
93
|
+
# call-seq:
|
94
|
+
# plan.partition_event_task(objects) => events, tasks
|
95
|
+
#
|
96
|
+
def partition_event_task(objects)
|
97
|
+
if objects.respond_to?(:to_task) then return nil, [objects.to_task]
|
98
|
+
elsif objects.respond_to?(:to_event) then return [objects.to_event], nil
|
99
|
+
elsif !objects.respond_to?(:each)
|
100
|
+
raise TypeError, "expecting a task, event, or a collection of tasks and events, got #{objects}"
|
101
|
+
end
|
102
|
+
|
103
|
+
evts, tasks = objects.partition do |o|
|
104
|
+
if o.respond_to?(:to_event) then true
|
105
|
+
elsif o.respond_to?(:to_task) then false
|
106
|
+
else raise ArgumentError, "found #{o || 'nil'} which is neither a task nor an event"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
return evts, tasks
|
110
|
+
end
|
111
|
+
|
112
|
+
# If this plan is a toplevel plan, returns self. If it is a
|
113
|
+
# transaction, returns the underlying plan
|
114
|
+
def real_plan
|
115
|
+
ret = self
|
116
|
+
while ret.respond_to?(:plan)
|
117
|
+
ret = ret.plan
|
118
|
+
end
|
119
|
+
ret
|
120
|
+
end
|
121
|
+
|
122
|
+
# Inserts a new mission in the plan. Its child tree is automatically
|
123
|
+
# inserted too. Returns the plan
|
124
|
+
def insert(task)
|
125
|
+
return if @missions.include?(task)
|
126
|
+
|
127
|
+
@missions << task
|
128
|
+
discover(task)
|
129
|
+
task.mission = true if task.self_owned?
|
130
|
+
inserted(task)
|
131
|
+
self
|
132
|
+
end
|
133
|
+
# Hook called when +tasks+ have been inserted in this plan
|
134
|
+
def inserted(tasks); super if defined? super end
|
135
|
+
|
136
|
+
# Forbid the GC to take out +task+
|
137
|
+
def permanent(task)
|
138
|
+
@keepalive << task
|
139
|
+
discover(task)
|
140
|
+
end
|
141
|
+
|
142
|
+
# Make GC finalize +task+ if it is not useful anymore
|
143
|
+
def auto(task); @keepalive.delete(task) end
|
144
|
+
|
145
|
+
def edit
|
146
|
+
if block_given?
|
147
|
+
Roby::Control.synchronize do
|
148
|
+
yield
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def permanent?(task); @keepalive.include?(task) end
|
154
|
+
|
155
|
+
# Removes the task in +tasks+ from the list of missions
|
156
|
+
def discard(task)
|
157
|
+
@missions.delete(task)
|
158
|
+
discover(task)
|
159
|
+
task.mission = false if task.self_owned?
|
160
|
+
|
161
|
+
discarded(task)
|
162
|
+
self
|
163
|
+
end
|
164
|
+
# Hook called when +tasks+ have been discarded from this plan
|
165
|
+
def discarded(tasks); super if defined? super end
|
166
|
+
|
167
|
+
def owns?(object)
|
168
|
+
(object.owners - owners).empty?
|
169
|
+
end
|
170
|
+
|
171
|
+
# Remove all tasks
|
172
|
+
def clear
|
173
|
+
@known_tasks.each { |t| t.clear_relations }
|
174
|
+
@known_tasks.clear
|
175
|
+
@free_events.each { |e| e.clear_relations }
|
176
|
+
@free_events.clear
|
177
|
+
@missions.clear
|
178
|
+
@keepalive.clear
|
179
|
+
end
|
180
|
+
|
181
|
+
def handle_replace(from, to)
|
182
|
+
return if from == to
|
183
|
+
|
184
|
+
# Check that +to+ is valid in all hierarchy relations where +from+ is a child
|
185
|
+
if !to.fullfills?(*from.fullfilled_model)
|
186
|
+
raise InvalidReplace.new(from, to, "to does not fullfills #{from.fullfilled_model}")
|
187
|
+
end
|
188
|
+
|
189
|
+
# Check that +to+ is in the same execution state than +from+
|
190
|
+
if !to.compatible_state?(from)
|
191
|
+
raise InvalidReplace.new(from, to, "state. #{from.running?}, #{to.running?}")
|
192
|
+
end
|
193
|
+
|
194
|
+
# Swap the subplans of +from+ and +to+
|
195
|
+
yield(from, to)
|
196
|
+
|
197
|
+
replaced(from, to)
|
198
|
+
if mission?(from)
|
199
|
+
discard(from)
|
200
|
+
insert(to)
|
201
|
+
elsif permanent?(from)
|
202
|
+
auto(from)
|
203
|
+
permanent(to)
|
204
|
+
else
|
205
|
+
discover(to)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def replace_task(from, to)
|
210
|
+
handle_replace(from, to) do
|
211
|
+
from.replace_by(to)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
def replace(from, to)
|
216
|
+
handle_replace(from, to) do
|
217
|
+
from.replace_subplan_by(to)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
# Hook called when +to+ has replaced +from+ in this plan
|
222
|
+
def replaced(from, to); super if defined? super end
|
223
|
+
|
224
|
+
# Check that this is an executable plan. This is always true for
|
225
|
+
# plain Plan objects and false for transcations
|
226
|
+
def executable?; true end
|
227
|
+
|
228
|
+
# call-seq:
|
229
|
+
# plan.discover([t1, t2, ...]) => plan
|
230
|
+
#
|
231
|
+
# Updates Plan#known_tasks with either the child tree of the tasks in
|
232
|
+
# +objects+
|
233
|
+
def discover(objects)
|
234
|
+
event_seeds, tasks = partition_event_task(objects)
|
235
|
+
event_seeds = (event_seeds || ValueSet.new).to_value_set
|
236
|
+
|
237
|
+
if tasks
|
238
|
+
tasks = tasks.to_value_set
|
239
|
+
new_tasks = useful_task_component(nil, tasks, tasks)
|
240
|
+
unless new_tasks.empty?
|
241
|
+
old_task_events = task_events.dup
|
242
|
+
new_tasks = discover_task_set(new_tasks)
|
243
|
+
event_seeds.merge(task_events - old_task_events)
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
if !event_seeds.empty?
|
248
|
+
events = event_seeds.dup
|
249
|
+
|
250
|
+
# now, we include the set of free events that are linked to
|
251
|
+
# +new_tasks+ in +events+
|
252
|
+
EventStructure.each_root_relation do |rel|
|
253
|
+
components = rel.generated_subgraphs(event_seeds, false)
|
254
|
+
components.concat rel.reverse.generated_subgraphs(event_seeds, false)
|
255
|
+
for c in components
|
256
|
+
events.merge(c.to_value_set)
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
discover_event_set(events - task_events - free_events)
|
261
|
+
end
|
262
|
+
|
263
|
+
self
|
264
|
+
end
|
265
|
+
|
266
|
+
# Add +events+ to the set of known events and call discovered_events
|
267
|
+
# for the new events
|
268
|
+
#
|
269
|
+
# This is for internal use, use #discover instead
|
270
|
+
def discover_event_set(events)
|
271
|
+
events = events.difference(free_events)
|
272
|
+
events.delete_if do |e|
|
273
|
+
if !e.root_object?
|
274
|
+
true
|
275
|
+
else
|
276
|
+
e.plan = self
|
277
|
+
false
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
unless events.empty?
|
282
|
+
free_events.merge(events)
|
283
|
+
discovered_events(events)
|
284
|
+
end
|
285
|
+
|
286
|
+
events
|
287
|
+
end
|
288
|
+
|
289
|
+
# Add +tasks+ to the set of known tasks and call discovered_tasks for
|
290
|
+
# the new tasks
|
291
|
+
#
|
292
|
+
# This is for internal use, use #discover instead
|
293
|
+
def discover_task_set(tasks)
|
294
|
+
tasks = tasks.difference(known_tasks)
|
295
|
+
for t in tasks
|
296
|
+
t.plan = self
|
297
|
+
task_events.merge t.bound_events.values.to_value_set
|
298
|
+
task_index.add t
|
299
|
+
end
|
300
|
+
known_tasks.merge tasks
|
301
|
+
discovered_tasks(tasks)
|
302
|
+
|
303
|
+
for t in tasks
|
304
|
+
t.instantiate_model_event_relations
|
305
|
+
end
|
306
|
+
tasks
|
307
|
+
end
|
308
|
+
|
309
|
+
# DEPRECATED. Use #discovered_tasks instead
|
310
|
+
def discovered(tasks); super if defined? super end
|
311
|
+
# Hook called when new tasks have been discovered in this plan
|
312
|
+
def discovered_tasks(tasks)
|
313
|
+
discovered(tasks)
|
314
|
+
super if defined? super
|
315
|
+
end
|
316
|
+
# Hook called when new events have been discovered in this plan
|
317
|
+
def discovered_events(events)
|
318
|
+
super if defined? super
|
319
|
+
end
|
320
|
+
|
321
|
+
# Hook called when a new transaction has been built on top of this plan
|
322
|
+
def added_transaction(trsc); super if defined? super end
|
323
|
+
# Removes the transaction +trsc+ from the list of known transactions
|
324
|
+
# built on this plan
|
325
|
+
def remove_transaction(trsc)
|
326
|
+
transactions.delete(trsc)
|
327
|
+
removed_transaction(trsc)
|
328
|
+
end
|
329
|
+
# Hook called when a new transaction has been built on top of this plan
|
330
|
+
def removed_transaction(trsc); super if defined? super end
|
331
|
+
|
332
|
+
# Merges the set of tasks that are useful for +seeds+ into +useful_set+.
|
333
|
+
# Only the tasks that are in +complete_set+ are included.
|
334
|
+
def useful_task_component(complete_set, useful_set, seeds)
|
335
|
+
old_useful_set = useful_set.dup
|
336
|
+
for rel in TaskStructure.relations
|
337
|
+
next unless rel.root_relation?
|
338
|
+
for subgraph in rel.generated_subgraphs(seeds, false)
|
339
|
+
useful_set.merge(subgraph)
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
if complete_set
|
344
|
+
useful_set &= complete_set
|
345
|
+
end
|
346
|
+
|
347
|
+
if useful_set.size == old_useful_set.size || (complete_set && useful_set.size == complete_set.size)
|
348
|
+
useful_set
|
349
|
+
else
|
350
|
+
useful_task_component(complete_set, useful_set, (useful_set - old_useful_set))
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
# Returns the set of useful tasks in this plan
|
355
|
+
def locally_useful_tasks
|
356
|
+
# Remove all missions that are finished
|
357
|
+
for finished_mission in (@missions & task_index.by_state[:finished?])
|
358
|
+
if !task_index.repaired_tasks.include?(finished_mission)
|
359
|
+
discard(finished_mission)
|
360
|
+
end
|
361
|
+
end
|
362
|
+
for finished_permanent in (@keepalive & task_index.by_state[:finished?])
|
363
|
+
if !task_index.repaired_tasks.include?(finished_permanent)
|
364
|
+
auto(finished_permanent)
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
# Create the set of tasks which must be kept as-is
|
369
|
+
seeds = @missions | @keepalive
|
370
|
+
for trsc in transactions
|
371
|
+
seeds.merge trsc.proxy_objects.keys.to_value_set
|
372
|
+
end
|
373
|
+
|
374
|
+
return ValueSet.new if seeds.empty?
|
375
|
+
|
376
|
+
# Compute the set of LOCAL tasks which serve the seeds. The set of
|
377
|
+
# locally_useful_tasks is the union of the seeds and of this one
|
378
|
+
useful_task_component(local_tasks, seeds & local_tasks, seeds) | seeds
|
379
|
+
end
|
380
|
+
|
381
|
+
def local_tasks
|
382
|
+
task_index.by_owner[Roby::Distributed] || ValueSet.new
|
383
|
+
end
|
384
|
+
|
385
|
+
def remote_tasks
|
386
|
+
if local_tasks = task_index.by_owner[Roby::Distributed]
|
387
|
+
known_tasks - local_tasks
|
388
|
+
else
|
389
|
+
known_tasks
|
390
|
+
end
|
391
|
+
end
|
392
|
+
|
393
|
+
# Returns the set of unused tasks
|
394
|
+
def unneeded_tasks
|
395
|
+
# Get the set of local tasks that are serving one of our own missions or
|
396
|
+
# permanent tasks
|
397
|
+
useful = self.locally_useful_tasks
|
398
|
+
|
399
|
+
# Append to that the set of tasks that are useful for our peers and
|
400
|
+
# include the set of local tasks that are serving tasks in
|
401
|
+
# +remotely_useful+
|
402
|
+
remotely_useful = Distributed.remotely_useful_objects(remote_tasks, true, nil)
|
403
|
+
serving_remote = useful_task_component(local_tasks, useful & local_tasks, remotely_useful)
|
404
|
+
|
405
|
+
useful.merge remotely_useful
|
406
|
+
useful.merge serving_remote
|
407
|
+
|
408
|
+
(known_tasks - useful)
|
409
|
+
end
|
410
|
+
|
411
|
+
# Computes the set of useful tasks and checks that +task+ is in it.
|
412
|
+
# This is quite slow. It is here for debugging purposes. Do not use it
|
413
|
+
# in production code
|
414
|
+
def useful_task?(task)
|
415
|
+
known_tasks.include?(task) && !unneeded_tasks.include?(task)
|
416
|
+
end
|
417
|
+
|
418
|
+
def useful_event_component(useful_events)
|
419
|
+
current_size = useful_events.size
|
420
|
+
for rel in EventStructure.relations
|
421
|
+
next unless rel.root_relation?
|
422
|
+
|
423
|
+
for subgraph in rel.components(free_events, false)
|
424
|
+
subgraph = subgraph.to_value_set
|
425
|
+
if subgraph.intersects?(useful_events) || subgraph.intersects?(task_events)
|
426
|
+
useful_events.merge(subgraph)
|
427
|
+
if useful_events.include_all?(free_events)
|
428
|
+
return free_events
|
429
|
+
end
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
433
|
+
if useful_events.include_all?(free_events)
|
434
|
+
return free_events
|
435
|
+
end
|
436
|
+
end
|
437
|
+
|
438
|
+
if current_size != useful_events.size
|
439
|
+
useful_event_component(useful_events)
|
440
|
+
else
|
441
|
+
useful_events
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
445
|
+
# Computes the set of events that are useful in the plan Events are
|
446
|
+
# 'useful' when they are chained to a task.
|
447
|
+
def useful_events
|
448
|
+
return ValueSet.new if free_events.empty?
|
449
|
+
(free_events & useful_event_component(ValueSet.new))
|
450
|
+
end
|
451
|
+
|
452
|
+
# The set of events that can be removed from the plan
|
453
|
+
def unneeded_events
|
454
|
+
useful_events = self.useful_events
|
455
|
+
|
456
|
+
result = (free_events - useful_events)
|
457
|
+
result.delete_if do |ev|
|
458
|
+
transactions.any? { |trsc| trsc.wrap(ev, false) }
|
459
|
+
end
|
460
|
+
result
|
461
|
+
end
|
462
|
+
|
463
|
+
# Checks if +task+ is included in this plan
|
464
|
+
def include?(object); @known_tasks.include?(object) || @free_events.include?(object) end
|
465
|
+
# Checks if +task+ is a mission of this plan
|
466
|
+
def mission?(task); @missions.include?(task) end
|
467
|
+
# Count of tasks in this plan
|
468
|
+
def size; @known_tasks.size end
|
469
|
+
# Returns true if there is no task in this plan
|
470
|
+
def empty?; @known_tasks.empty? end
|
471
|
+
# Iterates on all tasks
|
472
|
+
def each_task; @known_tasks.each { |t| yield(t) } end
|
473
|
+
|
474
|
+
# Install a plan repair for +failure_point+ with +task+. If +task+ is
|
475
|
+
# pending, it is started.
|
476
|
+
#
|
477
|
+
# See also #repairs and #remove_repair
|
478
|
+
def add_repair(failure_point, task)
|
479
|
+
if !failure_point.kind_of?(Event)
|
480
|
+
raise TypeError, "failure point #{failure_point} should be an event"
|
481
|
+
elsif task.plan && task.plan != self
|
482
|
+
raise ArgumentError, "wrong plan: #{task} is in #{task.plan}, not #{self}"
|
483
|
+
elsif repairs.has_key?(failure_point)
|
484
|
+
raise ArgumentError, "there is already a plan repair defined for #{failure_point}: #{repairs[failure_point]}"
|
485
|
+
elsif !task.plan
|
486
|
+
discover(task)
|
487
|
+
end
|
488
|
+
|
489
|
+
repairs[failure_point] = task
|
490
|
+
if failure_point.generator.respond_to?(:task)
|
491
|
+
task_index.repaired_tasks << failure_point.generator.task
|
492
|
+
end
|
493
|
+
|
494
|
+
if task.pending?
|
495
|
+
Roby.once { task.start! }
|
496
|
+
end
|
497
|
+
end
|
498
|
+
|
499
|
+
# Removes +task+ from the set of active plan repairs.
|
500
|
+
#
|
501
|
+
# See also #repairs and #add_repair
|
502
|
+
def remove_repair(task)
|
503
|
+
repairs.delete_if do |ev, repair|
|
504
|
+
if repair == task
|
505
|
+
if ev.generator.respond_to?(:task)
|
506
|
+
task_index.repaired_tasks.delete(ev.generator.task)
|
507
|
+
end
|
508
|
+
true
|
509
|
+
end
|
510
|
+
end
|
511
|
+
end
|
512
|
+
|
513
|
+
# Return all repairs which apply on +event+
|
514
|
+
def repairs_for(event)
|
515
|
+
result = Hash.new
|
516
|
+
|
517
|
+
if event.generator.respond_to?(:task)
|
518
|
+
equivalent_generators = event.generator.generated_subgraph(EventStructure::Forwarding)
|
519
|
+
|
520
|
+
history = event.generator.task.history
|
521
|
+
id = event.propagation_id
|
522
|
+
index = history.index(event)
|
523
|
+
while index < history.size
|
524
|
+
ev = history[index]
|
525
|
+
break if ev.propagation_id != id
|
526
|
+
|
527
|
+
if equivalent_generators.include?(ev.generator) &&
|
528
|
+
(task = repairs[ev])
|
529
|
+
|
530
|
+
result[ev] = task
|
531
|
+
end
|
532
|
+
|
533
|
+
index += 1
|
534
|
+
end
|
535
|
+
elsif task = repairs[event]
|
536
|
+
result[event] = task
|
537
|
+
end
|
538
|
+
|
539
|
+
result
|
540
|
+
end
|
541
|
+
|
542
|
+
# Returns +object+ if object is a plan object from this plan, or if
|
543
|
+
# it has no plan yet (in which case it is added to the plan first).
|
544
|
+
# Otherwise, raises ArgumentError.
|
545
|
+
#
|
546
|
+
# This method is provided for consistency with Transaction#[]
|
547
|
+
def [](object)
|
548
|
+
if object.plan != self
|
549
|
+
raise ArgumentError, "#{object} is not from #{plan}"
|
550
|
+
elsif !object.plan
|
551
|
+
discover(object)
|
552
|
+
end
|
553
|
+
object
|
554
|
+
end
|
555
|
+
|
556
|
+
def self.can_gc?(task)
|
557
|
+
if task.starting? then true # wait for the task to be started before deciding ...
|
558
|
+
elsif task.running? && !task.finishing?
|
559
|
+
task.event(:stop).controlable?
|
560
|
+
else true
|
561
|
+
end
|
562
|
+
end
|
563
|
+
|
564
|
+
# Kills and removes all unneeded tasks
|
565
|
+
def garbage_collect(force_on = nil)
|
566
|
+
if force_on && !force_on.empty?
|
567
|
+
force_gc.merge(force_on.to_value_set)
|
568
|
+
end
|
569
|
+
|
570
|
+
# The set of tasks for which we queued stop! at this cycle
|
571
|
+
# #finishing? is false until the next event propagation cycle
|
572
|
+
finishing = ValueSet.new
|
573
|
+
did_something = true
|
574
|
+
while did_something
|
575
|
+
did_something = false
|
576
|
+
|
577
|
+
tasks = unneeded_tasks | force_gc
|
578
|
+
local_tasks = self.local_tasks & tasks
|
579
|
+
remote_tasks = tasks - local_tasks
|
580
|
+
|
581
|
+
# Remote tasks are simply removed, regardless of other concerns
|
582
|
+
for t in remote_tasks
|
583
|
+
Plan.debug { "GC: removing the remote task #{t}" }
|
584
|
+
remove_object(t)
|
585
|
+
end
|
586
|
+
|
587
|
+
break if local_tasks.empty?
|
588
|
+
|
589
|
+
if local_tasks.all? { |t| t.pending? || t.finished? }
|
590
|
+
local_tasks.each do |t|
|
591
|
+
Plan.debug { "GC: #{t} is not running, removed" }
|
592
|
+
garbage(t)
|
593
|
+
remove_object(t)
|
594
|
+
end
|
595
|
+
break
|
596
|
+
end
|
597
|
+
|
598
|
+
# Mark all root local_tasks as garbage
|
599
|
+
roots = nil
|
600
|
+
2.times do |i|
|
601
|
+
roots = local_tasks.find_all do |t|
|
602
|
+
if t.root?
|
603
|
+
garbage(t)
|
604
|
+
true
|
605
|
+
else
|
606
|
+
Plan.debug { "GC: ignoring #{t}, it is not root" }
|
607
|
+
false
|
608
|
+
end
|
609
|
+
end
|
610
|
+
|
611
|
+
break if i == 1 || !roots.empty?
|
612
|
+
|
613
|
+
# There is a cycle somewhere. Try to break it by removing
|
614
|
+
# weak relations within elements of local_tasks
|
615
|
+
Plan.debug "cycle found, removing weak relations"
|
616
|
+
|
617
|
+
local_tasks.each do |t|
|
618
|
+
next if t.root?
|
619
|
+
t.each_graph do |rel|
|
620
|
+
rel.remove(t) if rel.weak?
|
621
|
+
end
|
622
|
+
end
|
623
|
+
end
|
624
|
+
|
625
|
+
(roots.to_value_set - finishing - gc_quarantine).each do |local_task|
|
626
|
+
if local_task.pending?
|
627
|
+
Plan.info "GC: removing pending task #{local_task}"
|
628
|
+
remove_object(local_task)
|
629
|
+
did_something = true
|
630
|
+
elsif local_task.starting?
|
631
|
+
# wait for task to be started before killing it
|
632
|
+
Plan.debug { "GC: #{local_task} is starting" }
|
633
|
+
elsif local_task.finished?
|
634
|
+
Plan.debug { "GC: #{local_task} is not running, removed" }
|
635
|
+
remove_object(local_task)
|
636
|
+
did_something = true
|
637
|
+
elsif !local_task.finishing?
|
638
|
+
if local_task.event(:stop).controlable?
|
639
|
+
Plan.debug { "GC: queueing #{local_task}/stop" }
|
640
|
+
if !local_task.respond_to?(:stop!)
|
641
|
+
Plan.fatal "something fishy: #{local_task}/stop is controlable but there is no #stop! method"
|
642
|
+
gc_quarantine << local_task
|
643
|
+
else
|
644
|
+
finishing << local_task
|
645
|
+
Roby::Control.once do
|
646
|
+
Plan.debug { "GC: stopping #{local_task}" }
|
647
|
+
local_task.stop!(nil)
|
648
|
+
end
|
649
|
+
end
|
650
|
+
else
|
651
|
+
Plan.warn "GC: ignored #{local_task}, it cannot be stopped"
|
652
|
+
gc_quarantine << local_task
|
653
|
+
end
|
654
|
+
elsif local_task.finishing?
|
655
|
+
Plan.debug { "GC: waiting for #{local_task} to finish" }
|
656
|
+
else
|
657
|
+
Plan.warn "GC: ignored #{local_task}"
|
658
|
+
end
|
659
|
+
end
|
660
|
+
end
|
661
|
+
|
662
|
+
unneeded_events.each do |event|
|
663
|
+
remove_object(event)
|
664
|
+
end
|
665
|
+
end
|
666
|
+
|
667
|
+
def remove_object(object)
|
668
|
+
if !object.root_object?
|
669
|
+
raise ArgumentError, "cannot remove #{object} which is a non-root object"
|
670
|
+
elsif object.plan != self
|
671
|
+
if known_tasks.include?(object) || free_events.include?(object)
|
672
|
+
raise ArgumentError, "#{object} is included in #{self} but #plan == #{object.plan}"
|
673
|
+
elsif !object.plan
|
674
|
+
if object.removed_at
|
675
|
+
raise ArgumentError, "#{object} has been removed at\n #{object.removed_at.join("\n ")}"
|
676
|
+
else
|
677
|
+
raise ArgumentError, "#{object} has not been included in this plan"
|
678
|
+
end
|
679
|
+
end
|
680
|
+
raise ArgumentError, "#{object} is not in #{self}: #plan == #{object.plan}"
|
681
|
+
end
|
682
|
+
|
683
|
+
# Remove relations first. This is needed by transaction since
|
684
|
+
# removing relations may need wrapping some new objects, and in
|
685
|
+
# that case these new objects will be discovered as well
|
686
|
+
object.clear_relations
|
687
|
+
|
688
|
+
@free_events.delete(object)
|
689
|
+
@missions.delete(object)
|
690
|
+
@known_tasks.delete(object)
|
691
|
+
@keepalive.delete(object)
|
692
|
+
force_gc.delete(object)
|
693
|
+
|
694
|
+
object.plan = nil
|
695
|
+
object.removed_at = caller
|
696
|
+
|
697
|
+
case object
|
698
|
+
when EventGenerator
|
699
|
+
finalized_event(object)
|
700
|
+
|
701
|
+
when Task
|
702
|
+
task_index.remove(object)
|
703
|
+
|
704
|
+
for ev in object.bound_events.values
|
705
|
+
task_events.delete(ev)
|
706
|
+
finalized_event(ev)
|
707
|
+
end
|
708
|
+
finalized_task(object)
|
709
|
+
|
710
|
+
else
|
711
|
+
raise ArgumentError, "unknown object type #{object}"
|
712
|
+
end
|
713
|
+
|
714
|
+
self
|
715
|
+
end
|
716
|
+
|
717
|
+
# Backward compatibility
|
718
|
+
def remove_task(t) # :nodoc:
|
719
|
+
remove_object(t)
|
720
|
+
end
|
721
|
+
|
722
|
+
# Hook called when +task+ is marked as garbage. It will be garbage
|
723
|
+
# collected as soon as possible
|
724
|
+
def garbage(task)
|
725
|
+
# Remove all signals that go *to* the task
|
726
|
+
#
|
727
|
+
# While we want events which come from the task to be properly
|
728
|
+
# forwarded, the signals that go to the task are to be ignored
|
729
|
+
if task.self_owned?
|
730
|
+
task.each_event do |ev|
|
731
|
+
ev.parent_objects(EventStructure::Signal).each do |signalling_event|
|
732
|
+
signalling_event.remove_signal ev
|
733
|
+
end
|
734
|
+
end
|
735
|
+
end
|
736
|
+
|
737
|
+
super if defined? super
|
738
|
+
end
|
739
|
+
|
740
|
+
# backward compatibility
|
741
|
+
def finalized(task) # :nodoc:
|
742
|
+
super if defined? super
|
743
|
+
end
|
744
|
+
# Hook called when +task+ has been removed from this plan
|
745
|
+
def finalized_task(task)
|
746
|
+
super if defined? super
|
747
|
+
finalized(task)
|
748
|
+
end
|
749
|
+
# Hook called when +event+ has been removed from this plan
|
750
|
+
def finalized_event(event); super if defined? super end
|
751
|
+
|
752
|
+
# Replace +task+ with a fresh copy of itself
|
753
|
+
def respawn(task)
|
754
|
+
new_task = task.class.new(task.arguments.dup)
|
755
|
+
|
756
|
+
replace_task(task, new_task)
|
757
|
+
Control.once { new_task.start!(nil) }
|
758
|
+
new_task
|
759
|
+
end
|
760
|
+
end
|
761
|
+
end
|
762
|
+
|