roby 0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+