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,529 @@
1
+ require 'drb'
2
+ require 'set'
3
+ require 'rinda/tuplespace'
4
+ require 'utilrb/value_set'
5
+ require 'roby_marshalling'
6
+
7
+ require 'roby'
8
+
9
+ class NilClass
10
+ def droby_dump(dest); nil end
11
+ end
12
+ class Array
13
+ def proxy(peer) # :nodoc:
14
+ map do |element|
15
+ catch(:ignore_this_call) { peer.proxy(element) }
16
+ end
17
+ end
18
+ end
19
+ class Hash
20
+ def proxy(peer) # :nodoc:
21
+ inject({}) do |h, (k, v)|
22
+ h[peer.proxy(k)] = catch(:ignore_this_call) { peer.proxy(v) }
23
+ h
24
+ end
25
+ end
26
+ end
27
+ class Set
28
+ def proxy(peer) # :nodoc:
29
+ map do |element|
30
+ catch(:ignore_this_call) { peer.proxy(element) }
31
+ end.to_set
32
+ end
33
+ end
34
+ class ValueSet
35
+ def proxy(peer) # :nodoc:
36
+ map do |element|
37
+ catch(:ignore_this_call) { peer.proxy(element) }
38
+ end.to_value_set
39
+ end
40
+ end
41
+
42
+ class Module
43
+ def droby_dump(dest)
44
+ raise "can't dump modules"
45
+ end
46
+ end
47
+ class Class
48
+ def droby_dump(dest)
49
+ raise "can't dump class #{self}"
50
+ end
51
+ end
52
+
53
+ module Roby
54
+ class TaskModelTag
55
+ @@local_to_remote = Hash.new
56
+ @@remote_to_local = Hash.new
57
+ def self.local_to_remote; @@local_to_remote end
58
+ def self.remote_to_local; @@remote_to_local end
59
+
60
+ class DRoby
61
+ attr_reader :tagdef
62
+ def initialize(tagdef); @tagdef = tagdef end
63
+ def _dump(lvl); @__droby_marshalled__ ||= Marshal.dump(tagdef) end
64
+ def self._load(str); DRoby.new(Marshal.load(str)) end
65
+
66
+ def proxy(peer)
67
+ including = []
68
+ tagdef.each do |name, remote_tag|
69
+ tag = DRoby.local_tag(name, remote_tag) do |tag|
70
+ including.each { |mod| tag.include mod }
71
+ end
72
+ including << tag
73
+ end
74
+ including.last
75
+ end
76
+
77
+ def self.local_tag(name, remote_tag)
78
+ if !remote_tag.kind_of?(Distributed::RemoteID)
79
+ remote_tag
80
+ elsif local_model = TaskModelTag.remote_to_local[remote_tag]
81
+ local_model
82
+ else
83
+ if name && !name.empty?
84
+ local_model = constant(name) rescue nil
85
+ end
86
+ unless local_model
87
+ local_model = Roby::TaskModelTag.new do
88
+ define_method(:name) { name }
89
+ end
90
+ TaskModelTag.remote_to_local[remote_tag] = local_model
91
+ TaskModelTag.local_to_remote[local_model] = [name, remote_tag]
92
+ yield(local_model) if block_given?
93
+ end
94
+ local_model
95
+ end
96
+ end
97
+
98
+ def ==(other)
99
+ other.kind_of?(DRoby) &&
100
+ tagdef.zip(other.tagdef).all? { |a, b| a == b }
101
+ end
102
+ end
103
+
104
+ def droby_dump(dest)
105
+ unless @__droby_marshalled__
106
+ tagdef = ancestors.map do |mod|
107
+ if mod.instance_of?(Roby::TaskModelTag)
108
+ unless id = TaskModelTag.local_to_remote[mod]
109
+ id = [mod.name, mod.remote_id]
110
+ end
111
+ id
112
+ end
113
+ end
114
+ tagdef.compact!
115
+ @__droby_marshalled__ = DRoby.new(tagdef.reverse)
116
+ end
117
+ @__droby_marshalled__
118
+ end
119
+ end
120
+
121
+ class TaskMatcher
122
+ # An intermediate representation of TaskMatcher objects suitable to be
123
+ # sent to our peers.
124
+ class DRoby
125
+ attr_reader :args
126
+ def initialize(args); @args = args end
127
+ def _dump(lvl) # :nodoc:
128
+ Marshal.dump(args)
129
+ end
130
+
131
+ def self._load(str) # :nodoc:
132
+ setup_matcher(TaskMatcher.new, Marshal.load(str))
133
+ end
134
+
135
+ # Common initialization of a TaskMatcher object from the given
136
+ # argument set. This is to be used by DRoby-dumped versions of
137
+ # subclasses of TaskMatcher.
138
+ def self.setup_matcher(matcher, args)
139
+ model, args, improves, needs, predicates, neg_predicates, owners = *args
140
+ model = model.proxy(nil) if model
141
+ owners = owners.map { |peer| peer.proxy(nil) } if owners
142
+ args = args
143
+
144
+ matcher = matcher.with_model(model).with_arguments(args || {}).
145
+ which_improves(*improves).which_needs(*needs)
146
+ matcher.predicates.merge(predicates)
147
+ matcher.owners.concat(owners)
148
+ matcher
149
+ end
150
+ end
151
+
152
+ # Returns an intermediate representation of +self+ suitable to be sent
153
+ # to the +dest+ peer. +klass+ is the actual class of the intermediate
154
+ # representation. It is used for code reuse by subclasses of
155
+ # TaskMatcher.
156
+ def droby_dump(dest, klass = DRoby)
157
+ args = [model, arguments, improved_information, needed_information, predicates, neg_predicates, owners]
158
+ klass.new args.droby_dump(dest)
159
+ end
160
+ end
161
+ class Query
162
+ # An intermediate representation of Query objects suitable to be sent
163
+ # to our peers.
164
+ class DRoby # :nodoc:
165
+ attr_reader :plan_predicates, :neg_plan_predicates, :matcher
166
+ def initialize(plan_predicates, neg_plan_predicates, matcher)
167
+ @plan_predicates, @neg_plan_predicates, @matcher =
168
+ plan_predicates, neg_plan_predicates, matcher
169
+ end
170
+
171
+ def _dump(lvl) # :nodoc:
172
+ Marshal.dump([plan_predicates, neg_plan_predicates, matcher])
173
+ end
174
+
175
+ def self._load(str) # :nodoc:
176
+ DRoby.new(*Marshal.load(str))
177
+ end
178
+
179
+ def to_query(plan)
180
+ query = TaskMatcher::DRoby.setup_matcher(plan.find_tasks, matcher)
181
+ query.plan_predicates.concat(plan_predicates)
182
+ query.neg_plan_predicates.concat(neg_plan_predicates)
183
+ query
184
+ end
185
+
186
+ def proxy(peer)
187
+ to_query(peer.connection_space.plan)
188
+ end
189
+ end
190
+
191
+ # Returns an intermediate representation of +self+ suitable to be sent
192
+ # to the +dest+ peer.
193
+ def droby_dump(dest)
194
+ marshalled_matcher = super
195
+ DRoby.new(plan_predicates, neg_plan_predicates, marshalled_matcher.args)
196
+ end
197
+ end
198
+
199
+ class OrTaskMatcher
200
+ # An intermediate representation of OrTaskMatcher objects suitable to
201
+ # be sent to our peers.
202
+ class DRoby < TaskMatcher::DRoby
203
+ def self._load(str) # :nodoc:
204
+ args = Marshal.load(str)
205
+ ops = args.pop
206
+ setup_matcher(OrTaskMatcher.new(*ops), args)
207
+ end
208
+ end
209
+
210
+ # Returns an intermediate representation of +self+ suitable to be sent
211
+ # to the +dest+ peer.
212
+ def droby_dump(dest)
213
+ m = super(dest, OrTaskMatcher::DRoby)
214
+ m.args << @ops
215
+ m
216
+ end
217
+ end
218
+ class AndTaskMatcher
219
+ # An intermediate representation of AndTaskMatcher objects suitable to
220
+ # be sent to our peers.
221
+ class DRoby < TaskMatcher::DRoby
222
+ def self._load(str) # :nodoc:
223
+ args = Marshal.load(str)
224
+ ops = args.pop
225
+ setup_matcher(AndTaskMatcher.new(*ops), args)
226
+ end
227
+ end
228
+
229
+ # Returns an intermediate representation of +self+ suitable to be sent
230
+ # to the +dest+ peer.
231
+ def droby_dump(dest)
232
+ m = super(dest, AndTaskMatcher::DRoby)
233
+ m.args << @ops
234
+ m
235
+ end
236
+ end
237
+ class NegateTaskMatcher
238
+ # An intermediate representation of NegateTaskMatcher objects suitable to
239
+ # be sent to our peers.
240
+ class DRoby < TaskMatcher::DRoby
241
+ def self._load(str) # :nodoc:
242
+ args = Marshal.load(str)
243
+ op = args.pop
244
+ setup_matcher(NegateTaskMatcher.new(op), args)
245
+ end
246
+ end
247
+
248
+ # Returns an intermediate representation of +self+ suitable to be sent
249
+ # to the +dest+ peer.
250
+ def droby_dump(dest)
251
+ m = super(dest, NegateTaskMatcher::DRoby)
252
+ m.args << @op
253
+ m
254
+ end
255
+ end
256
+ end
257
+
258
+ module Roby
259
+ module Distributed
260
+ # If set to true, enable some consistency-checking code in the
261
+ # communication code.
262
+ DEBUG_MARSHALLING = false
263
+
264
+ class Peer
265
+ # An intermediate representation of Peer objects suitable to be
266
+ # sent to our peers.
267
+ class DRoby # :nodoc:
268
+ attr_reader :name, :peer_id
269
+ def initialize(name, peer_id); @name, @peer_id = name, peer_id end
270
+ def hash; peer_id.hash end
271
+ def eql?(obj); obj.respond_to?(:peer_id) && peer_id == obj.peer_id end
272
+ alias :== :eql?
273
+
274
+ def to_s; "#<dRoby:Peer #{name} #{peer_id}>" end
275
+ def proxy(peer)
276
+ if peer = Distributed.peer(peer_id)
277
+ peer
278
+ else
279
+ raise "unknown peer ID #{peer_id}, known peers are #{Distributed.peers}"
280
+ end
281
+ end
282
+ end
283
+
284
+ # Returns an intermediate representation of +self+ suitable to be sent
285
+ # to the +dest+ peer.
286
+ def droby_dump(dest = nil)
287
+ @__droby_marshalled__ ||= DRoby.new(remote_name, remote_id)
288
+ end
289
+ end
290
+
291
+ # Dumps a constant by using its name. On reload, #proxy searches for a
292
+ # constant with the same name, and raises ArgumentError if none exists.
293
+ class DRobyConstant
294
+ @@valid_constants = Hash.new
295
+ def self.valid_constants; @@valid_constants end
296
+ def to_s; "#<dRoby:Constant #{name}>" end
297
+
298
+ # Generic implementation of the constant-dumping method. This is to
299
+ # be included in all kind of classes which should be dumped by their
300
+ # constant name (for intance RelationGraph).
301
+ module Dump
302
+ # Returns a DRobyConstant object which references +self+. It
303
+ # checks that +self+ can actually be referenced locally by
304
+ # calling <tt>constant(name)</tt>, or raises ArgumentError if
305
+ # it is not the case.
306
+ def droby_dump(dest)
307
+ unless DRobyConstant.valid_constants[self]
308
+ if const_obj = (constant(name) rescue nil)
309
+ DRobyConstant.valid_constants[self] = DRobyConstant.new(name)
310
+ else
311
+ raise ArgumentError, "invalid constant name #{obj.name}"
312
+ end
313
+ end
314
+ DRobyConstant.valid_constants[self]
315
+ end
316
+ end
317
+
318
+ # The constant name
319
+ attr_reader :name
320
+ def initialize(name); @name = name end
321
+ # Returns the local object which can be referenced by this name, or
322
+ # raises ArgumentError.
323
+ def proxy(peer); constant(name) end
324
+ end
325
+
326
+ class Roby::RelationGraph
327
+ include Roby::Distributed::DRobyConstant::Dump
328
+ end
329
+
330
+ # Dumps a model (an event, task or planner class). When unmarshalling,
331
+ # it tries to search for the same model. If it does not find it, it
332
+ # rebuilds the same hierarchy using anonymous classes, basing itself on
333
+ # the less abstract class known to both the remote and local sides.
334
+ class DRobyModel
335
+ @@remote_to_local = Hash.new
336
+ @@local_to_remote = Hash.new
337
+
338
+ # A name -> class map which maps remote models to local anonymous classes
339
+ # Remote models are always identified by their name
340
+ def self.remote_to_local; @@remote_to_local end
341
+ # A class => ID object which maps the anonymous classes we built for remote
342
+ # models to the remote ID of these remote models
343
+ def self.local_to_remote; @@local_to_remote end
344
+ def to_s # :nodoc:
345
+ "#<dRoby:Model #{ancestors.first.first}"
346
+ end
347
+
348
+ # Generic implementation of #droby_dump for all classes which
349
+ # should be marshalled as DRobyModel.
350
+ module Dump
351
+ # Creates a DRobyModel object which can be used to reference
352
+ # +self+ in the communication protocol. It properly takes into
353
+ # account the anonymous models we have created to map remote
354
+ # unknown models.
355
+ def droby_dump(dest)
356
+ unless @__droby_marshalled__
357
+ formatted = ancestors.map do |klass|
358
+ if klass.instance_of?(Class) && !klass.is_singleton?
359
+ if result = DRobyModel.local_to_remote[klass]
360
+ result
361
+ else
362
+ [klass.name, klass.remote_id]
363
+ end
364
+ end
365
+ end
366
+ formatted.compact!
367
+ @__droby_marshalled__ = DRobyModel.new(formatted)
368
+ end
369
+ @__droby_marshalled__
370
+ end
371
+ end
372
+
373
+ # The set of ancestors for the model, as a [name, remote_id] array
374
+ attr_reader :ancestors
375
+
376
+ # Initialize a DRobyModel object with the given set of ancestors
377
+ def initialize(ancestors); @ancestors = ancestors end
378
+ def _dump(lvl) # :nodoc:
379
+ @__droby_marshalled__ ||= Marshal.dump(@ancestors)
380
+ end
381
+ def self._load(str) # :nodoc:
382
+ DRobyModel.new(Marshal.load(str))
383
+ end
384
+ # Returns a local Class object which maps the given model.
385
+ #
386
+ # See DRobyModel.local_model
387
+ def proxy(peer)
388
+ DRobyModel.local_model(ancestors.map { |name, id| [name, id.local_object] })
389
+ end
390
+
391
+ # True if the two objects reference the same model
392
+ def ==(other)
393
+ other.kind_of?(DRobyModel) &&
394
+ ancestors == other.ancestors
395
+ end
396
+
397
+ # Returns a local representation of the given model. If the model
398
+ # itself is known to us (i.e. there is a constant with the same
399
+ # name), it is returned. Otherwise, the model hierarchy is
400
+ # re-created using anonymous classes, branching the inheritance
401
+ # chain at a point commonly known between the local plan manager
402
+ # and the remote one.
403
+ def self.local_model(ancestors)
404
+ name, id = ancestors.shift
405
+ if !id.kind_of?(Distributed::RemoteID)
406
+ # this is a local task model
407
+ id
408
+ elsif !name.empty? && model = (constant(name) rescue nil)
409
+ model
410
+ elsif model = @@remote_to_local[id]
411
+ model
412
+ elsif !ancestors.empty?
413
+ parent_model = local_model(ancestors)
414
+ model = Class.new(parent_model) do
415
+ singleton_class.class_eval do
416
+ define_method(:remote_name) { name }
417
+ define_method(:name) { "AnonModel(#{remote_name})" }
418
+ end
419
+ end
420
+ @@remote_to_local[id] = model
421
+ @@local_to_remote[model] = [name, id]
422
+
423
+ model
424
+ else
425
+ raise ArgumentError, "cannot find a root class"
426
+ end
427
+ end
428
+ end
429
+ Roby::EventGenerator.extend Distributed::DRobyModel::Dump
430
+ Roby::Planning::Planner.extend Distributed::DRobyModel::Dump
431
+
432
+ # Dumping intermediate for Task classes. This dumps both the ancestor
433
+ # list via DRobyModel and the list of task tags.
434
+ class DRobyTaskModel < DRobyModel
435
+ # Set of task tags the task model was referring to
436
+ attr_reader :tags
437
+ # Create a DRobyTaskModel with the given tags and ancestor list
438
+ def initialize(tags, ancestors)
439
+ super(ancestors)
440
+ @tags = tags
441
+ end
442
+
443
+ # Generic implementation of #droby_dump for all classes which
444
+ # should be marshalled as DRobyTaskModel.
445
+ module Dump
446
+ include DRobyModel::Dump
447
+
448
+ # This augments DRobyModel::Dump#droby_dump by taking into
449
+ # account TaskModelTag modules in the ancestors list.
450
+ def droby_dump(dest)
451
+ unless @__droby_marshalled__
452
+ formatted_class = super
453
+ tags = ancestors.map do |mod|
454
+ if mod.instance_of?(Roby::TaskModelTag)
455
+ mod.droby_dump(dest)
456
+ end
457
+ end
458
+ tags.compact!
459
+ @__droby_marshalled__ = DRobyTaskModel.new(tags.reverse, formatted_class.ancestors)
460
+ end
461
+ @__droby_marshalled__
462
+ end
463
+ end
464
+
465
+ # True if +other+ describes the same task model than +self+
466
+ def ==(other)
467
+ super &&
468
+ tags == other.tags
469
+ end
470
+
471
+ def _dump(lvl) # :nodoc:
472
+ @__droby_marshalled__ ||= Marshal.dump([tags, ancestors])
473
+ end
474
+ def self._load(str) # :nodoc:
475
+ DRobyTaskModel.new(*Marshal.load(str))
476
+ end
477
+
478
+ # Returns or creates a Task-subclass which matches the task model
479
+ # described by this object
480
+ def proxy(peer)
481
+ model = super
482
+ tags.each do |tag|
483
+ tag = tag.proxy(nil)
484
+ model.include tag unless model < tag
485
+ end
486
+
487
+ model
488
+ end
489
+ end
490
+ Roby::Task.extend Distributed::DRobyTaskModel::Dump
491
+ end
492
+ end
493
+
494
+ Exception.extend Roby::Distributed::DRobyModel::Dump
495
+ class Exception
496
+ # An intermediate representation of Exception objects suitable to
497
+ # be sent to our peers.
498
+ class DRoby
499
+ attr_reader :model, :message
500
+ def initialize(model, message); @model, @message = model, message end
501
+
502
+ # Returns a local representation of the exception object +self+
503
+ # describes. If the real exception message is not available, it reuses
504
+ # the more-specific exception class which is available.
505
+ def proxy(peer)
506
+ error_model = model.proxy(peer)
507
+ error_model.exception(self.message)
508
+
509
+ rescue ArgumentError
510
+ # try to get a less-specific error model which does allow a simple
511
+ # message. In the worst case, we will fall back to Exception itself
512
+ #
513
+ # However, include the real model name in the message
514
+ message = "#{self.message} (#{model.ancestors.first.first})"
515
+ for model in error_model.ancestors
516
+ next unless model.kind_of?(Class)
517
+ begin
518
+ return model.exception(message)
519
+ rescue ArgumentError
520
+ end
521
+ end
522
+ end
523
+ end
524
+
525
+ # Returns an intermediate representation of +self+ suitable to be sent to
526
+ # the +dest+ peer.
527
+ def droby_dump(dest); DRoby.new(self.class.droby_dump(dest), message) end
528
+ end
529
+