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
@@ -0,0 +1,234 @@
|
|
1
|
+
class Exception
|
2
|
+
def pretty_print(pp)
|
3
|
+
pp.text "#{message} (#{self.class.name})"
|
4
|
+
pp.breakable
|
5
|
+
Roby.pretty_print_backtrace(pp, backtrace)
|
6
|
+
end
|
7
|
+
|
8
|
+
# True if +obj+ is involved in this error
|
9
|
+
def involved_plan_object?(obj)
|
10
|
+
false
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module Roby
|
15
|
+
class ConfigError < RuntimeError; end
|
16
|
+
class ModelViolation < RuntimeError; end
|
17
|
+
|
18
|
+
# ExecutionException objects are used during the exception handling stage
|
19
|
+
# to keep information about the propagation.
|
20
|
+
#
|
21
|
+
# When a propagation fork is found (for instance, a task with two parents),
|
22
|
+
# two or more siblings are created with #fork. If at some point two
|
23
|
+
# siblings are to be handled by the same task, coming for instance from two
|
24
|
+
# different children, then they are merged with #merge to from one single
|
25
|
+
# ExecutionException object.
|
26
|
+
class ExecutionException
|
27
|
+
# The propagation trace. Because of forks and merges, this should be a
|
28
|
+
# graph. We don't use graph properties (at least not yet), so consider
|
29
|
+
# this as the list of objects which did not handle the exeption. Only
|
30
|
+
# trace.last and trace.first have a definite meaning: the former
|
31
|
+
# is the last object(s) that handled the propagation and the latter
|
32
|
+
# is the object from which the exception originated. They can be
|
33
|
+
# accessed through #task and #origin.
|
34
|
+
attr_reader :trace
|
35
|
+
# The last object(s) that handled the exception. This is either a
|
36
|
+
# single object or an array
|
37
|
+
def task; trace.last end
|
38
|
+
# The object from which the exception originates
|
39
|
+
def origin; trace.first end
|
40
|
+
|
41
|
+
# The exception siblings (the ExecutionException objects
|
42
|
+
# that come from the same exception object)
|
43
|
+
attr_reader :siblings
|
44
|
+
# The origin EventGenerator if there is one
|
45
|
+
attr_reader :generator
|
46
|
+
# The exception object
|
47
|
+
attr_reader :exception
|
48
|
+
|
49
|
+
# If this specific exception has been marked has handled
|
50
|
+
attr_accessor :handled
|
51
|
+
# If this exception or one of its siblings has been marked as handled
|
52
|
+
def handled?
|
53
|
+
siblings.find { |s| s.handled }
|
54
|
+
end
|
55
|
+
# Enumerates this exception's siblings
|
56
|
+
def each_sibling
|
57
|
+
for e in siblings
|
58
|
+
yield(e) unless e == self
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Creates a new execution exception object with the specified source
|
63
|
+
# If +source+ is nil, tries to guess the source from +exception+: if
|
64
|
+
# +exception+ responds to #task or #generator we use either #task or
|
65
|
+
# call #generator.task
|
66
|
+
def initialize(exception)
|
67
|
+
@exception = exception
|
68
|
+
@trace = Array.new
|
69
|
+
@siblings = [self]
|
70
|
+
|
71
|
+
if task = exception.failed_task
|
72
|
+
@trace << exception.failed_task
|
73
|
+
end
|
74
|
+
if generator = exception.failed_generator
|
75
|
+
@generator = exception.failed_generator
|
76
|
+
end
|
77
|
+
|
78
|
+
if !task && !generator
|
79
|
+
raise ArgumentError, "invalid exception specification: cannot get the exception source"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Create a sibling from this exception
|
84
|
+
def fork
|
85
|
+
sibling = dup
|
86
|
+
self.siblings << sibling
|
87
|
+
sibling
|
88
|
+
end
|
89
|
+
|
90
|
+
# Merges +sibling+ into this object
|
91
|
+
def merge(sibling)
|
92
|
+
siblings.delete(sibling)
|
93
|
+
|
94
|
+
topstack = trace.pop
|
95
|
+
s_topstack = sibling.trace.pop
|
96
|
+
|
97
|
+
origin = trace.shift
|
98
|
+
s_origin = sibling.trace.shift
|
99
|
+
origin = origin || s_origin || topstack
|
100
|
+
|
101
|
+
new_top = *(Array[*topstack] | Array[*s_topstack])
|
102
|
+
@trace = [origin] + (trace | sibling.trace) << new_top
|
103
|
+
end
|
104
|
+
|
105
|
+
def initialize_copy(from)
|
106
|
+
super
|
107
|
+
@trace = from.trace.dup
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# This module is to be included in all objects that are
|
112
|
+
# able to handle exception. These objects should define
|
113
|
+
# #each_exception_handler { |matchers, handler| ... }
|
114
|
+
#
|
115
|
+
# See Task::on_exception and Task#on_exception
|
116
|
+
module ExceptionHandlingObject
|
117
|
+
# To be used in exception handlers themselves. Passes the exception to
|
118
|
+
# the next matching exception handler
|
119
|
+
def pass_exception
|
120
|
+
throw :next_exception_handler
|
121
|
+
end
|
122
|
+
|
123
|
+
# Calls the exception handlers defined in this task for +exception_object.exception+
|
124
|
+
# Returns true if the exception has been handled, false otherwise
|
125
|
+
def handle_exception(exception_object)
|
126
|
+
each_exception_handler do |matchers, handler|
|
127
|
+
if matchers.find { |m| m === exception_object.exception }
|
128
|
+
catch(:next_exception_handler) do
|
129
|
+
begin
|
130
|
+
handler.call(self, exception_object)
|
131
|
+
return true
|
132
|
+
rescue Exception => e
|
133
|
+
if self == Roby
|
134
|
+
Propagation.add_framework_error(e, 'global exception handling')
|
135
|
+
else
|
136
|
+
Propagation.add_error(FailedExceptionHandler.new(e, self, exception_object))
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
return false
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
RX_IN_FRAMEWORK = /^((?:\s*\(druby:\/\/.+\)\s*)?#{Regexp.quote(ROBY_LIB_DIR)}\/)/
|
147
|
+
def self.filter_backtrace(original_backtrace)
|
148
|
+
if Roby.app.filter_backtraces? && original_backtrace
|
149
|
+
app_dir = if defined? APP_DIR then Regexp.quote(APP_DIR) end
|
150
|
+
|
151
|
+
original_backtrace = original_backtrace.dup
|
152
|
+
backtrace_bottom = []
|
153
|
+
while !original_backtrace.empty? && original_backtrace.last !~ RX_IN_FRAMEWORK
|
154
|
+
backtrace_bottom.unshift original_backtrace.pop
|
155
|
+
end
|
156
|
+
|
157
|
+
backtrace = original_backtrace.enum_for(:each_with_index).map do |line, idx|
|
158
|
+
case line
|
159
|
+
when /in `poll_handler'$/
|
160
|
+
line.gsub /:in.*/, ':in the polling handler'
|
161
|
+
when /in `event_command_(\w+)'$/
|
162
|
+
line.gsub /:in.*/, ":in command for '#{$1}'"
|
163
|
+
when /in `event_handler_(\w+)_(?:[a-f0-9]+)'$/
|
164
|
+
line.gsub /:in.*/, ":in event handler for '#{$1}'"
|
165
|
+
else
|
166
|
+
if original_backtrace.size > idx + 4 &&
|
167
|
+
original_backtrace[idx + 1] =~ /in `call'$/ &&
|
168
|
+
original_backtrace[idx + 2] =~ /in `call_handlers'$/ &&
|
169
|
+
original_backtrace[idx + 3] =~ /`each'$/ &&
|
170
|
+
original_backtrace[idx + 4] =~ /`each_handler'$/
|
171
|
+
|
172
|
+
line.gsub /:in /, ":in event handler, "
|
173
|
+
else
|
174
|
+
case line
|
175
|
+
when /in `(gem_original_)?require'$/
|
176
|
+
when /^((?:\s*\(druby:\/\/.+\)\s*)?#{Regexp.quote(ROBY_LIB_DIR)}\/)/
|
177
|
+
when /^(#{app_dir}\/)?scripts\//
|
178
|
+
when /^\(eval\):\d+:in `each(?:_handler)?'/
|
179
|
+
else
|
180
|
+
line
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
while !backtrace.empty? && !backtrace.last
|
187
|
+
backtrace.pop
|
188
|
+
end
|
189
|
+
backtrace.each_with_index do |line, i|
|
190
|
+
backtrace[i] = line || original_backtrace[i]
|
191
|
+
end
|
192
|
+
|
193
|
+
if app_dir
|
194
|
+
backtrace = backtrace.map do |line|
|
195
|
+
line.gsub /^#{app_dir}\/?/, './'
|
196
|
+
end
|
197
|
+
end
|
198
|
+
backtrace.concat backtrace_bottom
|
199
|
+
end
|
200
|
+
backtrace || original_backtrace || []
|
201
|
+
end
|
202
|
+
|
203
|
+
def self.pretty_print_backtrace(pp, backtrace)
|
204
|
+
if backtrace && !backtrace.empty?
|
205
|
+
pp.group(2) do
|
206
|
+
pp.seplist(filter_backtrace(backtrace)) { |line| pp.text line }
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def self.format_exception(exception)
|
212
|
+
message = begin
|
213
|
+
PP.pp(exception, "")
|
214
|
+
rescue Exception => formatting_error
|
215
|
+
begin
|
216
|
+
"error formatting exception\n" +
|
217
|
+
exception.full_message +
|
218
|
+
"\nplease report the formatting error: \n" +
|
219
|
+
formatting_error.full_message
|
220
|
+
rescue Exception => formatting_error
|
221
|
+
"\nerror formatting exception\n" +
|
222
|
+
formatting_error.full_message
|
223
|
+
end
|
224
|
+
end
|
225
|
+
message.split("\n")
|
226
|
+
end
|
227
|
+
|
228
|
+
def self.log_exception(e, logger, level)
|
229
|
+
format_exception(e).each do |line|
|
230
|
+
logger.send(level, line)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Roby
|
2
|
+
module Executives
|
3
|
+
class Simple
|
4
|
+
attr_reader :query
|
5
|
+
def initialize
|
6
|
+
@query = Roby.plan.find_tasks.
|
7
|
+
executable.
|
8
|
+
pending.
|
9
|
+
self_owned
|
10
|
+
end
|
11
|
+
def initial_events
|
12
|
+
query.reset.each do |task|
|
13
|
+
next unless task.event(:start).root? && task.event(:start).controlable?
|
14
|
+
root_task = task.enum_for(:each_relation).all? do |rel|
|
15
|
+
if task.root?(rel)
|
16
|
+
true
|
17
|
+
elsif rel == TaskStructure::PlannedBy
|
18
|
+
task.planned_tasks.all? { |t| !t.executable? }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
if root_task
|
23
|
+
task.start!
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
data/lib/roby/graph.rb
ADDED
@@ -0,0 +1,166 @@
|
|
1
|
+
require 'utilrb/module'
|
2
|
+
require 'utilrb/kernel'
|
3
|
+
require 'utilrb/enumerable'
|
4
|
+
require 'utilrb/value_set'
|
5
|
+
require 'roby_bgl'
|
6
|
+
|
7
|
+
Utilrb.unless_ext do
|
8
|
+
raise LoadError, "Roby needs Utilrb's C extension to be compiled"
|
9
|
+
end
|
10
|
+
|
11
|
+
module BGL
|
12
|
+
module Vertex
|
13
|
+
def initialize_copy(old)
|
14
|
+
super
|
15
|
+
@__bgl_graphs__ = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
# Removes +self+ from all the graphs it is included in.
|
19
|
+
def clear_vertex
|
20
|
+
each_graph { |g| g.remove(self) }
|
21
|
+
end
|
22
|
+
|
23
|
+
attribute(:singleton_set) { [self].to_value_set.freeze }
|
24
|
+
# Returns the connected component +self+ is part of in +graph+
|
25
|
+
def component(graph)
|
26
|
+
graph.components(singleton_set, false).first || singleton_set
|
27
|
+
end
|
28
|
+
# Returns the vertex set which are reachable from +self+ in +graph+
|
29
|
+
def generated_subgraph(graph)
|
30
|
+
graph.generated_subgraphs(singleton_set, false).first || singleton_set
|
31
|
+
end
|
32
|
+
# Returns the vertex set which can reach +self+ in +graph+
|
33
|
+
def reverse_generated_subgraph(graph)
|
34
|
+
graph.reverse.generated_subgraphs(singleton_set, false).first || singleton_set
|
35
|
+
end
|
36
|
+
|
37
|
+
# Replace this vertex by +to+ in all graphs. See Graph#replace_vertex.
|
38
|
+
def replace_vertex_by(to)
|
39
|
+
each_graph { |g| g.replace_vertex(self, to) }
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns an array of [graph, [parent, child, info], [parent, child,
|
43
|
+
# info], ...] elements for all edges +self+ is involved in
|
44
|
+
def edges
|
45
|
+
result = []
|
46
|
+
each_graph do |graph|
|
47
|
+
graph_edges = []
|
48
|
+
each_child_object do |child|
|
49
|
+
graph_edges << [self, child, self[child, graph]]
|
50
|
+
end
|
51
|
+
each_parent_object do |parent|
|
52
|
+
graph_edges << [parent, self, parent[self, graph]]
|
53
|
+
end
|
54
|
+
result << [graph, graph_edges]
|
55
|
+
end
|
56
|
+
|
57
|
+
result
|
58
|
+
end
|
59
|
+
|
60
|
+
# call-seq:
|
61
|
+
# neighborhood(distance, graph) => [[graph, v, v1, data], [graph, v2, v, data], ...]
|
62
|
+
# neighborhood(distance) => [[g1, v, v1, data], [g2, v2, v, data], ...]
|
63
|
+
#
|
64
|
+
# Returns a list of [graph, edge] representing all edges at a maximum distance
|
65
|
+
# of +distance+ from +self+. If +graph+ is given, only enumerate the neighborhood
|
66
|
+
# in +graph+.
|
67
|
+
def neighborhood(distance, graph = nil)
|
68
|
+
if graph
|
69
|
+
graph.neighborhood(self, distance).
|
70
|
+
map! { |args| args.unshift(graph) }
|
71
|
+
else
|
72
|
+
edges = []
|
73
|
+
each_graph do |graph|
|
74
|
+
edges += neighborhood(distance, graph)
|
75
|
+
end
|
76
|
+
edges
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
class Graph
|
82
|
+
# This class is an adaptor which transforms a directed graph by
|
83
|
+
# swapping its edges
|
84
|
+
class Reverse
|
85
|
+
# Create a directed graph whose edges are the ones of +g+, but with
|
86
|
+
# source and destination swapped.
|
87
|
+
def initialize(g)
|
88
|
+
@__bgl_real_graph__ = g
|
89
|
+
end
|
90
|
+
end
|
91
|
+
attribute(:reverse) { @reverse = Graph::Reverse.new(self) }
|
92
|
+
|
93
|
+
# This class is a graph adaptor which transforms a directed graph into
|
94
|
+
# an undirected graph
|
95
|
+
class Undirected
|
96
|
+
# Create an undirected graph which has the same edge set than +g+
|
97
|
+
def initialize(g)
|
98
|
+
@__bgl_real_graph__ = g
|
99
|
+
end
|
100
|
+
end
|
101
|
+
attribute(:undirected) { @undirected = Graph::Undirected.new(self) }
|
102
|
+
|
103
|
+
def initialize_copy(source) # :nodoc:
|
104
|
+
super
|
105
|
+
|
106
|
+
source.each_vertex { |v| insert(v) }
|
107
|
+
source.each_edge { |s, t, i| link(s, t, i) }
|
108
|
+
end
|
109
|
+
|
110
|
+
# Replaces +from+ by +to+. This means +to+ takes the role of +from+ in
|
111
|
+
# all edges +from+ is involved in. +from+ is removed from the graph.
|
112
|
+
def replace_vertex(from, to)
|
113
|
+
from.each_parent_vertex(self) do |parent|
|
114
|
+
link(parent, to, parent[from, self])
|
115
|
+
end
|
116
|
+
from.each_child_vertex(self) do |child|
|
117
|
+
link(to, child, from[child, self])
|
118
|
+
end
|
119
|
+
remove(from)
|
120
|
+
end
|
121
|
+
|
122
|
+
# Returns a list of [parent, child, info] for all edges that are at a
|
123
|
+
# distance no more than +distance+ from +vertex+.
|
124
|
+
def neighborhood(vertex, distance)
|
125
|
+
result = []
|
126
|
+
seen = Set.new
|
127
|
+
depth = { vertex => 0 }
|
128
|
+
undirected.each_bfs(vertex, ALL) do |from, to, info, kind|
|
129
|
+
new_depth = depth[from] + 1
|
130
|
+
if kind == TREE
|
131
|
+
depth[to] = new_depth
|
132
|
+
else
|
133
|
+
next if seen.include?(to)
|
134
|
+
end
|
135
|
+
seen << from
|
136
|
+
|
137
|
+
if depth[from] > distance
|
138
|
+
break
|
139
|
+
end
|
140
|
+
|
141
|
+
if new_depth <= distance
|
142
|
+
if linked?(from, to)
|
143
|
+
result << [from, to, info]
|
144
|
+
else
|
145
|
+
result << [to, from, info]
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
result
|
150
|
+
end
|
151
|
+
|
152
|
+
# Two graphs are the same if they have the same vertex set
|
153
|
+
# and the same edge set
|
154
|
+
def same_graph?(other)
|
155
|
+
unless other.respond_to?(:each_vertex) && other.respond_to?(:each_edge)
|
156
|
+
return false
|
157
|
+
end
|
158
|
+
|
159
|
+
# cannot use to_value_set for edges since we are comparing arrays (and ValueSet
|
160
|
+
# bases its comparison on VALUE)
|
161
|
+
(other.enum_for(:each_vertex).to_value_set == enum_for(:each_vertex).to_value_set) &&
|
162
|
+
(other.enum_for(:each_edge).to_set == enum_for(:each_edge).to_set)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
@@ -0,0 +1,390 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'roby'
|
3
|
+
require 'roby/planning'
|
4
|
+
require 'facets/basicobject'
|
5
|
+
require 'utilrb/column_formatter'
|
6
|
+
require 'stringio'
|
7
|
+
require 'roby/robot'
|
8
|
+
|
9
|
+
module Robot
|
10
|
+
def self.prepare_action(name, arguments)
|
11
|
+
control = Roby.control
|
12
|
+
|
13
|
+
# Check if +name+ is a planner method, and in that case
|
14
|
+
# add a planning method for it and plan it
|
15
|
+
planner_model = control.planners.find do |planner_model|
|
16
|
+
planner_model.has_method?(name)
|
17
|
+
end
|
18
|
+
if !planner_model
|
19
|
+
raise ArgumentError, "no such planning method #{name}"
|
20
|
+
end
|
21
|
+
|
22
|
+
m = planner_model.model_of(name, arguments)
|
23
|
+
|
24
|
+
# HACK: m.returns should not be nil, but it sometimes happen
|
25
|
+
returns_model = (m.returns if m && m.returns) || Task
|
26
|
+
|
27
|
+
if returns_model.kind_of?(Roby::TaskModelTag)
|
28
|
+
task = Roby::Task.new
|
29
|
+
task.extend returns_model
|
30
|
+
else
|
31
|
+
# Create an abstract task which will be planned
|
32
|
+
task = returns_model.new
|
33
|
+
end
|
34
|
+
|
35
|
+
planner = Roby::PlanningTask.new(:planner_model => planner_model, :method_name => name, :method_options => arguments)
|
36
|
+
task.planned_by planner
|
37
|
+
return task, planner
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.method_missing(name, *args)
|
41
|
+
if name.to_s =~ /!$/
|
42
|
+
name = $`.to_sym
|
43
|
+
else
|
44
|
+
super
|
45
|
+
end
|
46
|
+
|
47
|
+
if args.size > 1
|
48
|
+
raise ArgumentError, "wrong number of arguments (#{args.size} for 1) in #{name}!"
|
49
|
+
end
|
50
|
+
|
51
|
+
options = args.first || {}
|
52
|
+
task, planner = Robot.prepare_action(name, options)
|
53
|
+
Roby.control.plan.insert(task)
|
54
|
+
|
55
|
+
return task, planner
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
module Roby
|
60
|
+
# An augmented DRbObject which allow to properly interface with remotely
|
61
|
+
# running plan objects.
|
62
|
+
class RemoteObjectProxy < DRbObject
|
63
|
+
attr_accessor :remote_interface
|
64
|
+
|
65
|
+
def to_s
|
66
|
+
__method_missing__(:to_s)
|
67
|
+
end
|
68
|
+
def pretty_print(pp)
|
69
|
+
pp.text to_s
|
70
|
+
end
|
71
|
+
|
72
|
+
alias __method_missing__ method_missing
|
73
|
+
def method_missing(*args, &block)
|
74
|
+
if remote_interface
|
75
|
+
remote_interface.call(self, *args, &block)
|
76
|
+
else
|
77
|
+
super
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# RemoteInterface objects are used as local representation of remote
|
83
|
+
# interface objects. They offer a seamless interface to a remotely running
|
84
|
+
# Roby controller.
|
85
|
+
class RemoteInterface
|
86
|
+
# Create a RemoteInterface object for the remote object represented by
|
87
|
+
# +interface+, where +interface+ is a DRbObject for a remote Interface
|
88
|
+
# object.
|
89
|
+
def initialize(interface)
|
90
|
+
@interface = interface
|
91
|
+
end
|
92
|
+
|
93
|
+
# Returns a Query object which can be used to interactively query the
|
94
|
+
# running plan
|
95
|
+
def find_tasks(model = nil, args = nil)
|
96
|
+
q = Query.new(self)
|
97
|
+
if model
|
98
|
+
q.which_fullfills(model, args)
|
99
|
+
end
|
100
|
+
q
|
101
|
+
end
|
102
|
+
|
103
|
+
# Defined for remotes queries to work
|
104
|
+
def query_result_set(query) # :nodoc:
|
105
|
+
@interface.remote_query_result_set(Distributed.format(query)).each do |t|
|
106
|
+
t.remote_interface = self
|
107
|
+
end
|
108
|
+
end
|
109
|
+
# Defined for remotes queries to work
|
110
|
+
def query_each(result_set) # :nodoc:
|
111
|
+
result_set.each do |t|
|
112
|
+
yield(t)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
# Defined for remotes queries to work
|
116
|
+
def query_roots(result_set, relation) # :nodoc:
|
117
|
+
@interface.remote_query_roots(result_set, Distributed.format(relation)).each do |t|
|
118
|
+
t.remote_interface = self
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# Returns the DRbObject for the remote controller state object
|
123
|
+
def state
|
124
|
+
remote_constant('State')
|
125
|
+
end
|
126
|
+
|
127
|
+
def instance_methods(include_super = false) # :nodoc:
|
128
|
+
Interface.instance_methods(false).
|
129
|
+
actions.map { |name| "#{name}!" }
|
130
|
+
end
|
131
|
+
|
132
|
+
|
133
|
+
def method_missing(m, *args) # :nodoc:
|
134
|
+
result = @interface.send(m, *args)
|
135
|
+
if result.kind_of?(RemoteObjectProxy)
|
136
|
+
result.remote_interface = @interface
|
137
|
+
end
|
138
|
+
result
|
139
|
+
|
140
|
+
rescue Exception => e
|
141
|
+
raise e, e.message, Roby.filter_backtrace(e.backtrace)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# This class is used to interface with the Roby event loop and plan. It is the
|
146
|
+
# main front object when accessing a Roby core remotely
|
147
|
+
class Interface
|
148
|
+
module GatherExceptions
|
149
|
+
# The set of Interface objects that have been registered to us
|
150
|
+
attribute(:interfaces) { Array.new }
|
151
|
+
|
152
|
+
# Register a new Interface object so that it gets feedback information
|
153
|
+
# from the running controller.
|
154
|
+
def register_interface(iface)
|
155
|
+
Roby::Control.synchronize do
|
156
|
+
interfaces << iface
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# Pushes a exception message to all the already registered remote interfaces.
|
161
|
+
def push_exception_message(name, error, tasks)
|
162
|
+
Roby::Control.synchronize do
|
163
|
+
msg = Roby.format_exception(error.exception).join("\n")
|
164
|
+
msg << "\nThe following tasks have been killed:\n"
|
165
|
+
tasks.each do |t|
|
166
|
+
msg << " "
|
167
|
+
if error.exception.involved_plan_object?(t)
|
168
|
+
msg << "#{t.class}:0x#{t.address.to_s(16)}\n"
|
169
|
+
else
|
170
|
+
PP.pp(t, msg)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
interfaces.each do |iface|
|
175
|
+
iface.pending_messages << msg
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# Pushes an exception information on all remote interfaces connected to us
|
181
|
+
def handled_exception(error, task)
|
182
|
+
super if defined? super
|
183
|
+
push_exception_message("exception", error, [task])
|
184
|
+
end
|
185
|
+
|
186
|
+
# Pushes an exception information on all remote interfaces connected to us
|
187
|
+
def fatal_exception(error, tasks)
|
188
|
+
super if defined? super
|
189
|
+
push_exception_message("fatal exception", error, tasks)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# The Roby::Control object this interface is working on
|
194
|
+
attr_reader :control
|
195
|
+
# The set of pending messages that are to be displayed on the remote interface
|
196
|
+
attr_reader :pending_messages
|
197
|
+
# Creates a local server for a remote interface, acting on +control+
|
198
|
+
def initialize(control)
|
199
|
+
@control = control
|
200
|
+
@pending_messages = Queue.new
|
201
|
+
|
202
|
+
Roby::Control.extend GatherExceptions
|
203
|
+
Roby::Control.register_interface self
|
204
|
+
end
|
205
|
+
|
206
|
+
# Clear the current plan: remove all running and permanent tasks.
|
207
|
+
def clear
|
208
|
+
Roby.execute do
|
209
|
+
plan.missions.dup.each { |t| plan.discard(t) }
|
210
|
+
plan.keepalive.dup.each { |t| plan.auto(t) }
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
# Make the Roby event loop quit
|
215
|
+
def stop; control.quit; nil end
|
216
|
+
# The Roby plan
|
217
|
+
def plan; Roby.plan end
|
218
|
+
|
219
|
+
# Synchronously call +m+ on +tasks+ with the given arguments. This,
|
220
|
+
# along with the implementation of RemoteInterface#method_missing,
|
221
|
+
# ensures that no interactive operations are performed outside the
|
222
|
+
# control thread.
|
223
|
+
def call(task, m, *args)
|
224
|
+
Roby.execute do
|
225
|
+
if m.to_s =~ /!$/
|
226
|
+
event_name = $`
|
227
|
+
# Check if the called event is terminal. If it is the case,
|
228
|
+
# discard the task before calling it, and make sure the user
|
229
|
+
# will get a message
|
230
|
+
#
|
231
|
+
if task.event(event_name).terminal?
|
232
|
+
plan.discard(task)
|
233
|
+
task.on(:stop) { |ev| pending_messages << "task #{ev.task} stopped by user request" }
|
234
|
+
else
|
235
|
+
task.on(event_name) { |ev| pending_messages << "done emitting #{ev.generator}" }
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
task.send(m, *args)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
def find_tasks(model = nil, args = nil)
|
244
|
+
plan.find_tasks(model, args)
|
245
|
+
end
|
246
|
+
|
247
|
+
# For using Query on Interface objects
|
248
|
+
def remote_query_result_set(m_query) # :nodoc:
|
249
|
+
plan.query_result_set(m_query.to_query(plan)).
|
250
|
+
map { |t| RemoteObjectProxy.new(t) }
|
251
|
+
end
|
252
|
+
# For using Query on Interface objects
|
253
|
+
def remote_query_roots(result_set, m_relation) # :nodoc:
|
254
|
+
plan.query_roots(result_set, m_relation.proxy(nil)).
|
255
|
+
map { |t| RemoteObjectProxy.new(t) }
|
256
|
+
end
|
257
|
+
|
258
|
+
# Returns a DRbObject on the given named constant. Use this to get a
|
259
|
+
# remote interface to a given object, not taking into account its
|
260
|
+
# 'marshallability'
|
261
|
+
def remote_constant(name)
|
262
|
+
DRbObject.new(name.to_s.constantize)
|
263
|
+
end
|
264
|
+
|
265
|
+
# Reload the Roby framework code
|
266
|
+
#
|
267
|
+
# WARNING: does not work for now
|
268
|
+
def reload
|
269
|
+
Roby.app.reload
|
270
|
+
nil
|
271
|
+
end
|
272
|
+
|
273
|
+
# Displays the set of models as well as their superclasses
|
274
|
+
def models
|
275
|
+
task_models = []
|
276
|
+
Roby.execute do
|
277
|
+
ObjectSpace.each_object(Class) do |obj|
|
278
|
+
task_models << obj if obj <= Roby::Task && obj.name !~ /^Roby::/
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
task_models.map do |model|
|
283
|
+
"#{model} #{model.superclass}"
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
# Displays the set of actions which are available through the planners
|
288
|
+
# registered on #control. See Control#planners
|
289
|
+
def actions
|
290
|
+
control.planners.
|
291
|
+
map { |p| p.planning_methods_names.to_a }.
|
292
|
+
flatten.
|
293
|
+
sort
|
294
|
+
end
|
295
|
+
|
296
|
+
# Pretty-prints a set of tasks
|
297
|
+
def task_set_to_s(task_set) # :nodoc:
|
298
|
+
if task_set.empty?
|
299
|
+
return "no tasks"
|
300
|
+
end
|
301
|
+
|
302
|
+
task = task_set.map do |task|
|
303
|
+
state_name = %w{pending starting running finishing finished}.find do |state_name|
|
304
|
+
task.send("#{state_name}?")
|
305
|
+
end
|
306
|
+
|
307
|
+
start_event = task.history.find { |ev| ev.symbol == :start }
|
308
|
+
since = if start_event then start_event.time
|
309
|
+
else 'N/A'
|
310
|
+
end
|
311
|
+
{ 'Task' => task.to_s, 'Since' => since, 'State' => state_name }
|
312
|
+
end
|
313
|
+
|
314
|
+
io = StringIO.new
|
315
|
+
ColumnFormatter.from_hashes(task, io) { %w{Task Since State} }
|
316
|
+
"\n#{io.string}"
|
317
|
+
end
|
318
|
+
|
319
|
+
# Returns a string representing the set of running tasks
|
320
|
+
def running_tasks
|
321
|
+
Roby.execute do
|
322
|
+
task_set_to_s(Roby.plan.find_tasks.running.to_a)
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
# Returns a string representing the set of missions
|
327
|
+
def missions
|
328
|
+
Roby.execute do
|
329
|
+
task_set_to_s(control.plan.missions)
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
# Returns a string representing the set of tasks present in the plan
|
334
|
+
def tasks
|
335
|
+
Roby.execute do
|
336
|
+
task_set_to_s(Roby.plan.known_tasks)
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
def methods
|
341
|
+
result = super
|
342
|
+
result + actions.map { |n| "#{n}!" }
|
343
|
+
end
|
344
|
+
|
345
|
+
# Called every once in a while by RemoteInterface to read and clear the
|
346
|
+
# set of pending messages.
|
347
|
+
def poll_messages
|
348
|
+
result = []
|
349
|
+
while !pending_messages.empty?
|
350
|
+
msg = pending_messages.pop
|
351
|
+
result << msg
|
352
|
+
end
|
353
|
+
result
|
354
|
+
end
|
355
|
+
|
356
|
+
# Tries to find a planner method which matches +name+ with +args+. If it finds
|
357
|
+
# one, creates a task planned by a planning task and yields both
|
358
|
+
def method_missing(name, *args)
|
359
|
+
if name.to_s =~ /!$/
|
360
|
+
name = $`.to_sym
|
361
|
+
else
|
362
|
+
super
|
363
|
+
end
|
364
|
+
|
365
|
+
if args.size > 1
|
366
|
+
raise ArgumentError, "wrong number of arguments (#{args.size} for 1) in #{name}!"
|
367
|
+
end
|
368
|
+
|
369
|
+
options = args.first || {}
|
370
|
+
task, planner = Robot.prepare_action(name, options)
|
371
|
+
begin
|
372
|
+
Roby.wait_until(planner.event(:success)) do
|
373
|
+
control.plan.insert(task)
|
374
|
+
yield(task, planner) if block_given?
|
375
|
+
end
|
376
|
+
rescue Roby::UnreachableEvent
|
377
|
+
raise RuntimeError, "cannot start #{name}: #{planner.terminal_event.context.first}"
|
378
|
+
end
|
379
|
+
|
380
|
+
Roby.execute do
|
381
|
+
result = planner.result
|
382
|
+
result.on(:failed) { |ev| pending_messages << "task #{ev.task} failed" }
|
383
|
+
result.on(:success) { |ev| pending_messages << "task #{ev.task} finished successfully" }
|
384
|
+
RemoteObjectProxy.new(result)
|
385
|
+
end
|
386
|
+
end
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
|