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,343 @@
1
+ require 'roby'
2
+ require 'roby/distributed/protocol'
3
+ module Roby
4
+ class BasicObject::DRoby
5
+ # The set of remote siblings for that object, as known by the peer who
6
+ # called #droby_dump. This is used to match object identity among plan
7
+ # managers.
8
+ attr_reader :remote_siblings
9
+ # The set of owners for that object.
10
+ attr_reader :owners
11
+ # Create a BasicObject::DRoby object with the given information
12
+ def initialize(remote_siblings, owners)
13
+ @remote_siblings, @owners = remote_siblings, owners
14
+ end
15
+
16
+ def remote_siblings_to_s # :nodoc:
17
+ "{ " << remote_siblings.map { |peer, id| id.to_s(peer) }.join(", ") << " }"
18
+ end
19
+ def owners_to_s # :nodoc:
20
+ "[ " << owners.map { |peer| peer.name }.join(", ") << " ]"
21
+ end
22
+ def to_s # :nodoc:
23
+ "#<dRoby:BasicObject#{remote_siblings_to_s} owners=#{owners_to_s}>"
24
+ end
25
+
26
+ # If we know of a sibling on +peer+, return it. Otherwise, raises RemotePeerMismatch.
27
+ def sibling_on(peer)
28
+ remote_siblings.each do |m_peer, remote_id|
29
+ if m_peer.peer_id == peer.remote_id
30
+ return remote_id
31
+ end
32
+ end
33
+ raise RemotePeerMismatch, "#{self} has no known sibling on #{peer}"
34
+ end
35
+
36
+ # Update an existing proxy, using the information stored in this DRoby
37
+ # object.
38
+ def update(peer, proxy)
39
+ proxy.owners.clear
40
+ owners.each do |m_owner|
41
+ proxy.owners << peer.local_object(m_owner)
42
+ end
43
+
44
+ remote_siblings.each do |m_peer_sibling, remote_id|
45
+ peer_sibling = peer.local_object(m_peer_sibling)
46
+
47
+ if current = proxy.remote_siblings[peer_sibling]
48
+ if current != remote_id && peer_sibling != Roby::Distributed
49
+ raise "inconsistency for sibling on #{peer_sibling}: #{proxy} has #{current} while #{self} has #{remote_id}"
50
+ end
51
+ else
52
+ proxy.sibling_of(remote_id, peer_sibling)
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ # Base class for all marshalled plan objects.
59
+ class PlanObject::DRoby < BasicObject::DRoby
60
+ # The model for this plan object
61
+ attr_reader :model
62
+ # The plan of this object
63
+ attr_reader :plan
64
+
65
+ # Create a DRoby object with the given information. See also
66
+ # BasicObject::DRoby
67
+ def initialize(remote_siblings, owners, model, plan)
68
+ super(remote_siblings, owners)
69
+ @model, @plan = model, plan
70
+ end
71
+
72
+ def to_s # :nodoc:
73
+ "#<dRoby:#{model.ancestors.first.first}#{remote_siblings_to_s} plan=#{plan} owners=#{owners_to_s}>"
74
+ end
75
+
76
+ # Update an existing proxy, using the information stored in this DRoby
77
+ # object.
78
+ def update(peer, proxy)
79
+ super(peer, proxy)
80
+
81
+ if proxy.root_object?
82
+ if self.plan
83
+ plan = peer.local_object(self.plan)
84
+ return if proxy.plan == plan
85
+ Distributed.update_all([plan, proxy]) do
86
+ plan.discover(proxy)
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+
93
+ class EventGenerator
94
+ def _dump(lvl) # :nodoc:
95
+ Marshal.dump(remote_id)
96
+ end
97
+ def self._load(str) # :nodoc:
98
+ Marshal.load(str)
99
+ end
100
+
101
+ # Returns an intermediate representation of +self+ suitable to be sent
102
+ # to the +dest+ peer.
103
+ def droby_dump(dest)
104
+ DRoby.new(remote_siblings.droby_dump(dest), owners.droby_dump(dest),
105
+ model.droby_dump(dest), plan.droby_dump(dest),
106
+ controlable?, happened?)
107
+ end
108
+
109
+ # An intermediate representation of EventGenerator objects suitable to
110
+ # be sent to our peers.
111
+ class DRoby < PlanObject::DRoby
112
+ # True if the generator is controlable
113
+ attr_reader :controlable
114
+ # True if the generator has already been emitted once at the time
115
+ # EventGenerator#droby_dump has been called.
116
+ attr_reader :happened
117
+
118
+ # Create a DRoby object with the given information. See also
119
+ # PlanObject::DRoby
120
+ def initialize(remote_siblings, owners, model, plan, controlable, happened)
121
+ super(remote_siblings, owners, model, plan)
122
+ @controlable, @happened = controlable, happened
123
+ end
124
+
125
+ # Create a new proxy which maps the object of +peer+ represented by
126
+ # this communication intermediate.
127
+ def proxy(peer)
128
+ local_object = peer.local_object(model).new
129
+ if controlable
130
+ local_object.command = lambda { }
131
+ end
132
+ local_object
133
+ end
134
+
135
+ # Updates an already existing proxy using the information contained
136
+ # in this object.
137
+ def update(peer, proxy)
138
+ super
139
+ if happened && !proxy.happened?
140
+ proxy.instance_eval { @happened = true }
141
+ end
142
+ end
143
+ end
144
+ end
145
+
146
+ class TaskEventGenerator
147
+ def _dump(lvl) # :nodoc:
148
+ Marshal.dump(remote_id)
149
+ end
150
+ def self._load(str) # :nodoc:
151
+ Marshal.load(str)
152
+ end
153
+
154
+ # Returns an intermediate representation of +self+ suitable to be sent
155
+ # to the +dest+ peer.
156
+ def droby_dump(dest)
157
+ DRoby.new(controlable?, happened?, Distributed.format(task, dest), symbol)
158
+ end
159
+
160
+ # An intermediate representation of TaskEventGenerator objects suitable
161
+ # to be sent to our peers.
162
+ class DRoby
163
+ # True if the generator is controlable
164
+ attr_reader :controlable
165
+ # True if the generator has already emitted once at the time
166
+ # TaskEventGenerator#droby_dump has been called.
167
+ attr_reader :happened
168
+ # An object representing the task of this generator on our remote
169
+ # peer.
170
+ attr_reader :task
171
+ # The event name
172
+ attr_reader :symbol
173
+
174
+ # Create a new DRoby object with the given information
175
+ def initialize(controlable, happened, task, symbol)
176
+ @controlable = controlable
177
+ @happened = happened
178
+ @task = task
179
+ @symbol = symbol
180
+ end
181
+
182
+ def to_s # :nodoc:
183
+ if task.respond_to?(:model)
184
+ "#<dRoby:#{task.model.ancestors.first.first}/#{symbol}#{task.remote_siblings_to_s} task_arguments=#{task.arguments} plan=#{task.plan} owners=#{task.owners_to_s}>"
185
+ else
186
+ "#<dRoby:#{task}/#{symbol}>"
187
+ end
188
+ end
189
+
190
+ # Create a new proxy which maps the object of +peer+ represented by
191
+ # this communication intermediate.
192
+ def proxy(peer)
193
+ task = peer.local_object(self.task)
194
+ unless task.has_event?(symbol)
195
+ Roby::Distributed.debug { "ignoring #{self}: #{symbol} is not known on #{task}" }
196
+ Roby::Distributed.ignore!
197
+ end
198
+ event = task.event(symbol)
199
+
200
+ if happened && !event.happened?
201
+ event.instance_eval { @happened = true }
202
+ end
203
+ event
204
+ end
205
+ end
206
+ end
207
+
208
+ class Task
209
+ def _dump(lvl) # :nodoc:
210
+ Marshal.dump(remote_id)
211
+ end
212
+ def self._load(str) # :nodoc:
213
+ Marshal.load(str)
214
+ end
215
+
216
+ # Returns an intermediate representation of +self+ suitable to be sent
217
+ # to the +dest+ peer.
218
+ def droby_dump(dest)
219
+ DRoby.new(remote_siblings.droby_dump(dest), owners.droby_dump(dest),
220
+ model.droby_dump(dest), plan.droby_dump(dest),
221
+ Distributed.format(arguments, dest), Distributed.format(data, dest),
222
+ :mission => mission?, :started => started?,
223
+ :finished => finished?, :success => success?)
224
+ end
225
+
226
+ # An intermediate representation of Task objects suitable
227
+ # to be sent to our peers.
228
+ class DRoby < PlanObject::DRoby
229
+ # The set of dRoby-formatted arguments
230
+ attr_reader :arguments
231
+ # The task's internal data
232
+ attr_reader :data
233
+ # A set of boolean flags which describe the task's status. It is a
234
+ # symbol => bool flag where the following parameters are save:
235
+ # started:: if the task has started
236
+ # finished:: if the task has finished
237
+ # success:: if the task has finished with success
238
+ # mission:: if the task is a mission in its plan
239
+ attr_reader :flags
240
+
241
+ # Create a new DRoby object with the given information
242
+ # See also PlanObject::DRoby.new
243
+ def initialize(remote_siblings, owners, model, plan, arguments, data, flags)
244
+ super(remote_siblings, owners, model, plan)
245
+ @arguments, @data, @flags = arguments, data, flags
246
+ end
247
+
248
+ def to_s # :nodoc:
249
+ "#<dRoby:#{model.ancestors.first.first}#{remote_siblings_to_s} plan=#{plan} owners=#{owners_to_s} arguments=#{arguments}>"
250
+ end
251
+
252
+ # Create a new proxy which maps the object of +peer+ represented by
253
+ # this communication intermediate.
254
+ def proxy(peer)
255
+ arguments = peer.local_object(self.arguments)
256
+ peer.local_object(model).new(arguments) do
257
+ Roby::Distributed.updated_objects << self
258
+ end
259
+
260
+ ensure
261
+ Roby::Distributed.updated_objects.delete(self)
262
+ end
263
+
264
+ # Updates an already existing proxy using the information contained
265
+ # in this object.
266
+ def update(peer, task)
267
+ super
268
+
269
+ task.started = flags[:started]
270
+ task.finished = flags[:finished]
271
+ task.success = flags[:success]
272
+
273
+ if task.mission? != flags[:mission]
274
+ plan = peer.local_object(self.plan) || Roby.plan
275
+ if plan.owns?(task)
276
+ if flags[:mission]
277
+ plan.insert(task)
278
+ else
279
+ plan.discard(task)
280
+ end
281
+ else
282
+ task.mission = flags[:mission]
283
+ end
284
+ end
285
+
286
+ task.arguments.merge!(peer.proxy(arguments))
287
+ task.instance_variable_set("@data", peer.proxy(data))
288
+ end
289
+ end
290
+ end
291
+
292
+ class Plan
293
+ # Returns an intermediate representation of +self+ suitable to be sent
294
+ # to the +dest+ peer.
295
+ def droby_dump(dest)
296
+ @__droby_marshalled__ ||= DRoby.new(Roby::Distributed.droby_dump(dest), remote_id)
297
+ end
298
+
299
+ # An intermediate representation of Plan objects suitable to be sent to
300
+ # our peers.
301
+ #
302
+ # FIXME: It assumes that the only Plan object sent to the peers is
303
+ # actually the main plan of the plan manager. We must fix that.
304
+ class DRoby
305
+ # The peer which manages this plan
306
+ attr_accessor :peer
307
+ # The plan remote_id
308
+ attr_accessor :id
309
+ # Create a DRoby representation of a plan object with the given
310
+ # parameters
311
+ def initialize(peer, id); @peer, @id = peer, id end
312
+ # Create a new proxy which maps the object of +peer+ represented by
313
+ # this communication intermediate.
314
+ def proxy(peer); peer.connection_space.plan end
315
+ def to_s # :nodoc:
316
+ "#<dRoby:Plan #{id.to_s(peer)}>"
317
+ end
318
+ # The set of remote siblings for that object. This is used to avoid
319
+ # creating proxies when not needed. See
320
+ # PlanObject::DRoby#remote_siblings.
321
+ def remote_siblings; @remote_siblings ||= Hash[peer, id] end
322
+ # If +peer+ is the plan's owner, returns #id. Otherwise, raises
323
+ # RemotePeerMismatch. This is used to avoid creating proxies when not
324
+ # needed. See BasicObject::DRoby#sibling_on.
325
+ def sibling_on(peer)
326
+ if peer.remote_id == self.peer.peer_id then id
327
+ else raise RemotePeerMismatch, "no known sibling for #{self} on #{peer}"
328
+ end
329
+ end
330
+ end
331
+ end
332
+
333
+ module Distributed
334
+ # Builds a remote proxy model for +object_model+. +object_model+ is
335
+ # either a string or a class. In the first case, it is interpreted
336
+ # as a constant name.
337
+ def self.RemoteProxyModel(object_model)
338
+ object_model
339
+ end
340
+
341
+ end
342
+ end
343
+
@@ -0,0 +1,311 @@
1
+
2
+ module Roby
3
+ module Distributed
4
+ class << self
5
+ # Yields the peers which are interested in at least one of the
6
+ # objects in +objects+.
7
+ def each_updated_peer(*objects)
8
+ for obj in objects
9
+ return if !obj.distribute?
10
+ end
11
+
12
+ for _, peer in Distributed.peers
13
+ next unless peer.connected?
14
+ for obj in objects
15
+ if obj.update_on?(peer)
16
+ yield(peer)
17
+ break
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ class PeerServer
25
+ # Called by the peer to subscribe on +object+. Returns an array which
26
+ # is to be fed to #demux to update the object relations on the remote
27
+ # host
28
+ #
29
+ # In case of distributed transaction, it is forbidden to subscribe to a
30
+ # proxy without having subscribed to the proxied object first. This
31
+ # method will thus subscribe to both at the same time. Peer#subscribe
32
+ # is supposed to do the same
33
+ def subscribe_plan_object(object)
34
+ if Transactions::Proxy === object && object.__getobj__.self_owned?
35
+ subscribe_plan_object(object.__getobj__)
36
+ end
37
+ set_relations_commands(object)
38
+ end
39
+
40
+ # The peer wants to subscribe to our main plan
41
+ def subscribe_plan(sibling)
42
+ added_sibling(Roby.plan.remote_id, sibling)
43
+ peer.transmit(:subscribed_plan, Roby.plan.remote_id)
44
+ subscribe(Roby.plan)
45
+ end
46
+
47
+ # Called by our peer because it has subscribed us to its main plan
48
+ def subscribed_plan(remote_plan_id)
49
+ peer.proxies[remote_plan_id] = Roby.plan
50
+ peer.remote_plan = remote_plan_id
51
+ end
52
+
53
+ # Subscribe the remote peer to changes on +object+. +object+ must be
54
+ # an object owned locally.
55
+ def subscribe(m_object)
56
+ if !(local_object = peer.local_object(m_object, false))
57
+ raise OwnershipError, "no object for #{m_object}"
58
+ elsif !local_object.self_owned?
59
+ raise OwnershipError, "not owner of #{local_object}"
60
+ end
61
+
62
+ # We put the subscription process outside the communication
63
+ # thread so that the remote peer can send back the siblings it
64
+ # has created
65
+ peer.queueing do
66
+ peer.transmit(:subscribed, [local_object])
67
+
68
+ case local_object
69
+ when PlanObject
70
+ if !local_object.root_object?
71
+ raise ArgumentError, "cannot subscribe to non-root objects"
72
+ end
73
+ subscribe_plan_object(local_object)
74
+
75
+ when Plan
76
+ tasks, events = local_object.known_tasks, local_object.free_events
77
+ tasks.delete_if { |t| !t.distribute? }
78
+ events.delete_if { |t| !t.distribute? }
79
+
80
+ peer.transmit(:discover_plan, local_object, tasks, events)
81
+ tasks.each { |obj| subscribe_plan_object(obj) }
82
+ events.each { |obj| subscribe_plan_object(obj) }
83
+ end
84
+ end
85
+
86
+ local_object.remote_id
87
+ end
88
+
89
+ # Called by the remote host because it has subscribed us to a plan
90
+ # (a set of tasks and events).
91
+ def discover_plan(marshalled_plan, m_tasks, m_events)
92
+ plan = peer.local_object(marshalled_plan)
93
+ Distributed.update(plan) do
94
+ peer.local_object(m_tasks)
95
+ peer.local_object(m_events)
96
+ end
97
+ nil
98
+ end
99
+
100
+ # Called by the remote peer to announce that it has created the
101
+ # given siblings. +siblings+ is a remote_drbobject => local_object
102
+ # hash
103
+ #
104
+ # It is also used by BasicObject#sibling_of to register a new
105
+ # sibling
106
+ def added_sibling(local_id, remote_id)
107
+ local_id.local_object.add_sibling_for(peer, remote_id)
108
+ nil
109
+ end
110
+
111
+ # Called by the remote peer to announce that it has removed the
112
+ # given siblings. +objects+ is the list of local objects.
113
+ #
114
+ # It is also used by BasicObject#forget_peer to remove references
115
+ # to an old sibling
116
+ def removed_sibling(local_id, remote_id)
117
+ local_object = local_id.local_object
118
+ sibling = local_object.remove_sibling_for(peer, remote_id)
119
+
120
+ # It is fine to remove a sibling twice: you nay for instance
121
+ # decide in both sides that the sibling should be removed (for
122
+ # instance during the disconnection process)
123
+ if sibling && sibling != remote_id
124
+ raise "removed sibling #{sibling} for #{local_id} on peer #{peer} does not match the provided remote id (#{remote_id})"
125
+ end
126
+
127
+ unless local_object.remotely_useful?
128
+ Distributed.removed_objects.delete(local_object)
129
+ end
130
+ end
131
+
132
+ # Called by the remote peer to announce that is has subscribed us to +objects+
133
+ def subscribed(objects)
134
+ # Register the subscription
135
+ objects.each do |object|
136
+ peer.subscriptions << peer.remote_object(object)
137
+ end
138
+ # Create the proxies
139
+ peer.local_object(objects)
140
+ nil
141
+ end
142
+
143
+ # Sends to the peer the set of relations needed to copy the state of +plan_object+
144
+ # on the remote peer.
145
+ def set_relations_commands(plan_object)
146
+ peer.transmit(:set_relations, plan_object, Distributed.relations_of(plan_object))
147
+
148
+ if plan_object.respond_to?(:each_plan_child)
149
+ plan_object.each_plan_child do |plan_child|
150
+ peer.transmit(:set_relations, plan_child, Distributed.relations_of(plan_child))
151
+ end
152
+ end
153
+ end
154
+
155
+ # Sets the relation of +objects+ according to the description in +relations+.
156
+ # See #relations_of for how +relations+ is formatted
157
+ #
158
+ # Note that any relation not listed in +relations+ will actually be
159
+ # *removed* from the plan. Therefore, if +relations+ is empty, then
160
+ # all relations of +object+ are removed.
161
+ def set_relations(object, relations)
162
+ object = peer.local_object(object)
163
+ relations = peer.local_object(relations)
164
+
165
+ Distributed.update(object.root_object) do
166
+ all_parents = Hash.new { |h, k| h[k] = ValueSet.new }
167
+ all_children = Hash.new { |h, k| h[k] = ValueSet.new }
168
+
169
+ # Add or update existing relations
170
+ relations.each_slice(3) do |graph, parents, children|
171
+ all_objects = parents.map { |p, _| p } + children.map { |c, _| c }
172
+ Distributed.update_all(all_objects) do
173
+ parents.each_slice(2) do |parent, info|
174
+ next unless parent
175
+ all_parents[graph] << parent
176
+
177
+ if graph.linked?(parent, object)
178
+ parent[object, graph] = info
179
+ else
180
+ Distributed.update(parent.root_object) do
181
+ parent.add_child_object(object, graph, info)
182
+ end
183
+ end
184
+ end
185
+ children.each_slice(2) do |child, info|
186
+ next unless child
187
+ all_children[graph] << child
188
+
189
+ if graph.linked?(object, child)
190
+ object[child, graph] = info
191
+ else
192
+ Distributed.update(child.root_object) do
193
+ object.add_child_object(child, graph, info)
194
+ end
195
+ end
196
+ end
197
+ end
198
+ end
199
+
200
+ Distributed.each_object_relation(object) do |rel|
201
+ # Remove relations that do not exist anymore
202
+ #
203
+ # If the other end of this relation cannot be seen by
204
+ # our remote peer, keep it: it means that the relation
205
+ # is a local-only annotation this pDB has added to the
206
+ # task
207
+ (object.parent_objects(rel).to_value_set - all_parents[rel]).each do |p|
208
+ # See comment above
209
+ next unless p.distribute?
210
+ Distributed.update_all([p.root_object, object.root_object]) do
211
+ p.remove_child_object(object, rel)
212
+ end
213
+ end
214
+ (object.child_objects(rel).to_value_set - all_children[rel]).each do |c|
215
+ # See comment above
216
+ next unless c.distribute?
217
+ Distributed.update_all([c.root_object, object.root_object]) do
218
+ object.remove_child_object(c, rel)
219
+ end
220
+ end
221
+ end
222
+ end
223
+
224
+ nil
225
+ end
226
+
227
+ end
228
+
229
+ class Peer
230
+ # The set of remote objects we *want* notifications on, as
231
+ # RemoteID objects. This does not include automatically susbcribed
232
+ # objects, but only those explicitely subscribed to by calling
233
+ # Peer#subscribe
234
+ #
235
+ # See also #subscribe, #subscribed? and #unsubscribe
236
+ #
237
+ #--
238
+ # DO NOT USE a ValueSet here. RemoteIDs must be compared using #==
239
+ #++
240
+ attribute(:subscriptions) { Set.new }
241
+
242
+ # Explicitely subscribe to #object
243
+ #
244
+ # See also #subscriptions, #subscribed? and #unsubscribe
245
+ def subscribe(object)
246
+ while object.respond_to?(:__getobj__)
247
+ object = object.__getobj__
248
+ end
249
+
250
+ if remote_object = (remote_object(object) rescue nil)
251
+ if !subscriptions.include?(remote_object)
252
+ remote_object = nil
253
+ end
254
+ end
255
+
256
+ unless remote_object
257
+ remote_sibling = object.sibling_on(self)
258
+ remote_object = call(:subscribe, remote_sibling)
259
+ synchro_point
260
+ end
261
+ local_object = local_object(remote_object)
262
+ end
263
+
264
+ # Make our peer subscribe to +object+
265
+ def push_subscription(object)
266
+ local_server.subscribe(object)
267
+ synchro_point
268
+ end
269
+
270
+ # The RemoteID for the peer main plan
271
+ attr_accessor :remote_plan
272
+
273
+ # Subscribe to the remote plan
274
+ def subscribe_plan
275
+ call(:subscribe_plan, connection_space.plan.remote_id)
276
+ synchro_point
277
+ end
278
+
279
+ # Unsubscribe from the remote plan
280
+ def unsubscribe_plan
281
+ proxies.delete(remote_plan)
282
+ subscriptions.delete(remote_plan)
283
+ if connected?
284
+ call(:removed_sibling, @remote_plan, connection_space.plan.remote_id)
285
+ end
286
+ end
287
+
288
+ def subscribed_plan?; remote_plan && subscriptions.include?(remote_plan) end
289
+
290
+ # True if we are explicitely subscribed to +object+. Automatically
291
+ # subscribed objects will not be included here, but
292
+ # BasicObject#updated? will return true for them
293
+ #
294
+ # See also #subscriptions, #subscribe and #unsubscribe
295
+ def subscribed?(object)
296
+ subscriptions.include?(remote_object(object))
297
+ rescue RemotePeerMismatch
298
+ false
299
+ end
300
+
301
+ # Remove an explicit subscription. See also #subscriptions,
302
+ # #subscribe and #subscribed?
303
+ #
304
+ # See also #subscriptions, #subscribe and #subscribed?
305
+ def unsubscribe(object)
306
+ subscriptions.delete(remote_object(object))
307
+ end
308
+ end
309
+ end
310
+ end
311
+