roby 0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (240) hide show
  1. data/.gitignore +29 -0
  2. data/History.txt +4 -0
  3. data/License-fr.txt +519 -0
  4. data/License.txt +515 -0
  5. data/Manifest.txt +245 -0
  6. data/NOTES +4 -0
  7. data/README.txt +163 -0
  8. data/Rakefile +161 -0
  9. data/TODO.txt +146 -0
  10. data/app/README.txt +24 -0
  11. data/app/Rakefile +8 -0
  12. data/app/config/ROBOT.rb +5 -0
  13. data/app/config/app.yml +91 -0
  14. data/app/config/init.rb +7 -0
  15. data/app/config/roby.yml +3 -0
  16. data/app/controllers/.gitattributes +0 -0
  17. data/app/controllers/ROBOT.rb +2 -0
  18. data/app/data/.gitattributes +0 -0
  19. data/app/planners/ROBOT/main.rb +6 -0
  20. data/app/planners/main.rb +5 -0
  21. data/app/scripts/distributed +3 -0
  22. data/app/scripts/generate/bookmarks +3 -0
  23. data/app/scripts/replay +3 -0
  24. data/app/scripts/results +3 -0
  25. data/app/scripts/run +3 -0
  26. data/app/scripts/server +3 -0
  27. data/app/scripts/shell +3 -0
  28. data/app/scripts/test +3 -0
  29. data/app/tasks/.gitattributes +0 -0
  30. data/app/tasks/ROBOT/.gitattributes +0 -0
  31. data/bin/roby +210 -0
  32. data/bin/roby-log +168 -0
  33. data/bin/roby-shell +25 -0
  34. data/doc/images/event_generalization.png +0 -0
  35. data/doc/images/exception_propagation_1.png +0 -0
  36. data/doc/images/exception_propagation_2.png +0 -0
  37. data/doc/images/exception_propagation_3.png +0 -0
  38. data/doc/images/exception_propagation_4.png +0 -0
  39. data/doc/images/exception_propagation_5.png +0 -0
  40. data/doc/images/replay_handler_error.png +0 -0
  41. data/doc/images/replay_handler_error_0.png +0 -0
  42. data/doc/images/replay_handler_error_1.png +0 -0
  43. data/doc/images/roby_cycle_overview.png +0 -0
  44. data/doc/images/roby_replay_02.png +0 -0
  45. data/doc/images/roby_replay_03.png +0 -0
  46. data/doc/images/roby_replay_04.png +0 -0
  47. data/doc/images/roby_replay_event_representation.png +0 -0
  48. data/doc/images/roby_replay_first_state.png +0 -0
  49. data/doc/images/roby_replay_relations.png +0 -0
  50. data/doc/images/roby_replay_startup.png +0 -0
  51. data/doc/images/task_event_generalization.png +0 -0
  52. data/doc/papers.rdoc +11 -0
  53. data/doc/styles/allison.css +314 -0
  54. data/doc/styles/allison.js +316 -0
  55. data/doc/styles/allison.rb +276 -0
  56. data/doc/styles/jamis.rb +593 -0
  57. data/doc/tutorials/01-GettingStarted.rdoc +86 -0
  58. data/doc/tutorials/02-GoForward.rdoc +220 -0
  59. data/doc/tutorials/03-PlannedPath.rdoc +268 -0
  60. data/doc/tutorials/04-EventPropagation.rdoc +236 -0
  61. data/doc/tutorials/05-ErrorHandling.rdoc +319 -0
  62. data/doc/tutorials/06-Overview.rdoc +40 -0
  63. data/doc/videos.rdoc +69 -0
  64. data/ext/droby/dump.cc +175 -0
  65. data/ext/droby/extconf.rb +3 -0
  66. data/ext/graph/algorithm.cc +746 -0
  67. data/ext/graph/extconf.rb +7 -0
  68. data/ext/graph/graph.cc +529 -0
  69. data/ext/graph/graph.hh +183 -0
  70. data/ext/graph/iterator_sequence.hh +102 -0
  71. data/ext/graph/undirected_dfs.hh +226 -0
  72. data/ext/graph/undirected_graph.hh +421 -0
  73. data/lib/roby.rb +41 -0
  74. data/lib/roby/app.rb +870 -0
  75. data/lib/roby/app/rake.rb +56 -0
  76. data/lib/roby/app/run.rb +14 -0
  77. data/lib/roby/app/scripts/distributed.rb +13 -0
  78. data/lib/roby/app/scripts/generate/bookmarks.rb +162 -0
  79. data/lib/roby/app/scripts/replay.rb +31 -0
  80. data/lib/roby/app/scripts/results.rb +15 -0
  81. data/lib/roby/app/scripts/run.rb +26 -0
  82. data/lib/roby/app/scripts/server.rb +18 -0
  83. data/lib/roby/app/scripts/shell.rb +88 -0
  84. data/lib/roby/app/scripts/test.rb +40 -0
  85. data/lib/roby/basic_object.rb +151 -0
  86. data/lib/roby/config.rb +5 -0
  87. data/lib/roby/control.rb +747 -0
  88. data/lib/roby/decision_control.rb +17 -0
  89. data/lib/roby/distributed.rb +32 -0
  90. data/lib/roby/distributed/base.rb +440 -0
  91. data/lib/roby/distributed/communication.rb +871 -0
  92. data/lib/roby/distributed/connection_space.rb +592 -0
  93. data/lib/roby/distributed/distributed_object.rb +206 -0
  94. data/lib/roby/distributed/drb.rb +62 -0
  95. data/lib/roby/distributed/notifications.rb +539 -0
  96. data/lib/roby/distributed/peer.rb +550 -0
  97. data/lib/roby/distributed/protocol.rb +529 -0
  98. data/lib/roby/distributed/proxy.rb +343 -0
  99. data/lib/roby/distributed/subscription.rb +311 -0
  100. data/lib/roby/distributed/transaction.rb +498 -0
  101. data/lib/roby/event.rb +897 -0
  102. data/lib/roby/exceptions.rb +234 -0
  103. data/lib/roby/executives/simple.rb +30 -0
  104. data/lib/roby/graph.rb +166 -0
  105. data/lib/roby/interface.rb +390 -0
  106. data/lib/roby/log.rb +3 -0
  107. data/lib/roby/log/chronicle.rb +303 -0
  108. data/lib/roby/log/console.rb +72 -0
  109. data/lib/roby/log/data_stream.rb +197 -0
  110. data/lib/roby/log/dot.rb +279 -0
  111. data/lib/roby/log/event_stream.rb +151 -0
  112. data/lib/roby/log/file.rb +340 -0
  113. data/lib/roby/log/gui/basic_display.ui +83 -0
  114. data/lib/roby/log/gui/chronicle.rb +26 -0
  115. data/lib/roby/log/gui/chronicle_view.rb +40 -0
  116. data/lib/roby/log/gui/chronicle_view.ui +70 -0
  117. data/lib/roby/log/gui/data_displays.rb +172 -0
  118. data/lib/roby/log/gui/data_displays.ui +155 -0
  119. data/lib/roby/log/gui/notifications.rb +26 -0
  120. data/lib/roby/log/gui/relations.rb +248 -0
  121. data/lib/roby/log/gui/relations.ui +123 -0
  122. data/lib/roby/log/gui/relations_view.rb +185 -0
  123. data/lib/roby/log/gui/relations_view.ui +149 -0
  124. data/lib/roby/log/gui/replay.rb +327 -0
  125. data/lib/roby/log/gui/replay_controls.rb +200 -0
  126. data/lib/roby/log/gui/replay_controls.ui +259 -0
  127. data/lib/roby/log/gui/runtime.rb +130 -0
  128. data/lib/roby/log/hooks.rb +185 -0
  129. data/lib/roby/log/logger.rb +202 -0
  130. data/lib/roby/log/notifications.rb +244 -0
  131. data/lib/roby/log/plan_rebuilder.rb +470 -0
  132. data/lib/roby/log/relations.rb +1056 -0
  133. data/lib/roby/log/server.rb +550 -0
  134. data/lib/roby/log/sqlite.rb +47 -0
  135. data/lib/roby/log/timings.rb +164 -0
  136. data/lib/roby/plan-object.rb +247 -0
  137. data/lib/roby/plan.rb +762 -0
  138. data/lib/roby/planning.rb +13 -0
  139. data/lib/roby/planning/loops.rb +302 -0
  140. data/lib/roby/planning/model.rb +906 -0
  141. data/lib/roby/planning/task.rb +151 -0
  142. data/lib/roby/propagation.rb +562 -0
  143. data/lib/roby/query.rb +619 -0
  144. data/lib/roby/relations.rb +583 -0
  145. data/lib/roby/relations/conflicts.rb +70 -0
  146. data/lib/roby/relations/ensured.rb +20 -0
  147. data/lib/roby/relations/error_handling.rb +23 -0
  148. data/lib/roby/relations/events.rb +9 -0
  149. data/lib/roby/relations/executed_by.rb +193 -0
  150. data/lib/roby/relations/hierarchy.rb +239 -0
  151. data/lib/roby/relations/influence.rb +10 -0
  152. data/lib/roby/relations/planned_by.rb +63 -0
  153. data/lib/roby/robot.rb +7 -0
  154. data/lib/roby/standard_errors.rb +218 -0
  155. data/lib/roby/state.rb +5 -0
  156. data/lib/roby/state/events.rb +221 -0
  157. data/lib/roby/state/information.rb +55 -0
  158. data/lib/roby/state/pos.rb +110 -0
  159. data/lib/roby/state/shapes.rb +32 -0
  160. data/lib/roby/state/state.rb +353 -0
  161. data/lib/roby/support.rb +92 -0
  162. data/lib/roby/task-operations.rb +182 -0
  163. data/lib/roby/task.rb +1618 -0
  164. data/lib/roby/test/common.rb +399 -0
  165. data/lib/roby/test/distributed.rb +214 -0
  166. data/lib/roby/test/tasks/empty_task.rb +9 -0
  167. data/lib/roby/test/tasks/goto.rb +36 -0
  168. data/lib/roby/test/tasks/simple_task.rb +23 -0
  169. data/lib/roby/test/testcase.rb +519 -0
  170. data/lib/roby/test/tools.rb +160 -0
  171. data/lib/roby/thread_task.rb +87 -0
  172. data/lib/roby/transactions.rb +462 -0
  173. data/lib/roby/transactions/proxy.rb +292 -0
  174. data/lib/roby/transactions/updates.rb +139 -0
  175. data/plugins/fault_injection/History.txt +4 -0
  176. data/plugins/fault_injection/README.txt +37 -0
  177. data/plugins/fault_injection/Rakefile +18 -0
  178. data/plugins/fault_injection/TODO.txt +0 -0
  179. data/plugins/fault_injection/app.rb +52 -0
  180. data/plugins/fault_injection/fault_injection.rb +89 -0
  181. data/plugins/fault_injection/test/test_fault_injection.rb +84 -0
  182. data/plugins/subsystems/README.txt +40 -0
  183. data/plugins/subsystems/Rakefile +18 -0
  184. data/plugins/subsystems/app.rb +171 -0
  185. data/plugins/subsystems/test/app/README +24 -0
  186. data/plugins/subsystems/test/app/Rakefile +8 -0
  187. data/plugins/subsystems/test/app/config/app.yml +71 -0
  188. data/plugins/subsystems/test/app/config/init.rb +9 -0
  189. data/plugins/subsystems/test/app/config/roby.yml +3 -0
  190. data/plugins/subsystems/test/app/planners/main.rb +20 -0
  191. data/plugins/subsystems/test/app/scripts/distributed +3 -0
  192. data/plugins/subsystems/test/app/scripts/replay +3 -0
  193. data/plugins/subsystems/test/app/scripts/results +3 -0
  194. data/plugins/subsystems/test/app/scripts/run +3 -0
  195. data/plugins/subsystems/test/app/scripts/server +3 -0
  196. data/plugins/subsystems/test/app/scripts/shell +3 -0
  197. data/plugins/subsystems/test/app/scripts/test +3 -0
  198. data/plugins/subsystems/test/app/tasks/services.rb +15 -0
  199. data/plugins/subsystems/test/test_subsystems.rb +71 -0
  200. data/test/distributed/test_communication.rb +178 -0
  201. data/test/distributed/test_connection.rb +282 -0
  202. data/test/distributed/test_execution.rb +373 -0
  203. data/test/distributed/test_mixed_plan.rb +341 -0
  204. data/test/distributed/test_plan_notifications.rb +238 -0
  205. data/test/distributed/test_protocol.rb +516 -0
  206. data/test/distributed/test_query.rb +102 -0
  207. data/test/distributed/test_remote_plan.rb +491 -0
  208. data/test/distributed/test_transaction.rb +463 -0
  209. data/test/mockups/tasks.rb +27 -0
  210. data/test/planning/test_loops.rb +380 -0
  211. data/test/planning/test_model.rb +427 -0
  212. data/test/planning/test_task.rb +106 -0
  213. data/test/relations/test_conflicts.rb +42 -0
  214. data/test/relations/test_ensured.rb +38 -0
  215. data/test/relations/test_executed_by.rb +149 -0
  216. data/test/relations/test_hierarchy.rb +158 -0
  217. data/test/relations/test_planned_by.rb +54 -0
  218. data/test/suite_core.rb +24 -0
  219. data/test/suite_distributed.rb +9 -0
  220. data/test/suite_planning.rb +3 -0
  221. data/test/suite_relations.rb +8 -0
  222. data/test/test_bgl.rb +508 -0
  223. data/test/test_control.rb +399 -0
  224. data/test/test_event.rb +894 -0
  225. data/test/test_exceptions.rb +592 -0
  226. data/test/test_interface.rb +37 -0
  227. data/test/test_log.rb +114 -0
  228. data/test/test_log_server.rb +132 -0
  229. data/test/test_plan.rb +584 -0
  230. data/test/test_propagation.rb +210 -0
  231. data/test/test_query.rb +266 -0
  232. data/test/test_relations.rb +180 -0
  233. data/test/test_state.rb +414 -0
  234. data/test/test_support.rb +16 -0
  235. data/test/test_task.rb +938 -0
  236. data/test/test_testcase.rb +122 -0
  237. data/test/test_thread_task.rb +73 -0
  238. data/test/test_transactions.rb +569 -0
  239. data/test/test_transactions_proxy.rb +198 -0
  240. 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
+