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,550 @@
1
+ require 'roby/log'
2
+ require 'roby/log/data_stream'
3
+ require 'roby/distributed/communication'
4
+ require 'roby/distributed/drb'
5
+ require 'tempfile'
6
+
7
+ module Roby
8
+ module Log
9
+ class Server
10
+ RING_PORT = 48904
11
+
12
+ class << self
13
+ attr_accessor :logger
14
+ end
15
+ @logger = Logger.new(STDERR)
16
+ @logger.level = Logger::INFO
17
+ @logger.progname = "Roby server"
18
+ @logger.formatter = lambda { |severity, time, progname, msg| "#{time.to_hms} #{progname} #{msg}\n" }
19
+ extend Logger::Forward
20
+
21
+ @mutex = Mutex.new
22
+ def self.synchronize
23
+ @mutex.synchronize { yield }
24
+ end
25
+
26
+ # Returns the set of servers that have been discovered by the discovery
27
+ # mechanism at this time
28
+ #
29
+ # See also enable_discovery and disable_discovery
30
+ def self.available_servers
31
+ synchronize do
32
+ @available_servers.dup
33
+ end
34
+ end
35
+
36
+ # Start an asynchronous discovery mechanism. This will fill the
37
+ # #available_servers set of servers. +broadcast+ is an array of
38
+ # addresses on which discovery should be done and +period+ is the
39
+ # discovery period in seconds
40
+ def self.enable_discovery(broadcast, port = RING_PORT, period = 10)
41
+ if @discovery_thread
42
+ raise ArgumentError, "already enabled discovery"
43
+ end
44
+
45
+ finger = Rinda::RingFinger.new(broadcast, port)
46
+
47
+ discovered_displays = Array.new
48
+ @available_servers = Array.new
49
+
50
+ # Add disable_discovery in the list of finalizers
51
+ Control.finalizers << method(:disable_discovery)
52
+
53
+ @discovery_thread = Thread.new do
54
+ begin
55
+ loop do
56
+ finger.lookup_ring(period) do |remote|
57
+ synchronize do
58
+ unless @available_servers.include?(remote)
59
+ @available_servers << remote
60
+ end
61
+ discovered_displays << remote
62
+ end
63
+ end
64
+ sleep(period)
65
+
66
+ synchronize do
67
+ @available_servers, discovered_displays = discovered_displays, @available_servers
68
+ discovered_displays.clear
69
+ end
70
+ end
71
+ rescue Interrupt
72
+ end
73
+ end
74
+ end
75
+
76
+ # Stops the discovery thread if it is running
77
+ def self.disable_discovery
78
+ Control.finalizers.delete(method(:disable_discovery))
79
+ if @discovery_thread
80
+ @discovery_thread.raise Interrupt, "quitting"
81
+ @discovery_thread.join
82
+ @discovery_thread = nil
83
+
84
+ synchronize do
85
+ @available_servers.clear
86
+ end
87
+ end
88
+ end
89
+
90
+ # A <tt>stream_id => [remote_server, ...]</tt> hash containing the
91
+ # set of subscribed remote peer for each stream
92
+ attr_reader :subscriptions
93
+ # A <tt>remote_server => [queue, thread]</tt> hash which contains
94
+ # the set of connection parameters for each connected peer
95
+ attr_reader :connections
96
+
97
+ # The Distributed::RingServer object which publishes this display
98
+ # server on the network
99
+ attr_reader :ring_server
100
+
101
+ # Default value for #polling_timeout
102
+ POLLING_TIMEOUT = 0.1
103
+
104
+ attr_reader :polling_timeout
105
+
106
+ def initialize(port = RING_PORT, polling_timeout = POLLING_TIMEOUT)
107
+ @ring_server = Distributed::RingServer.new(DRbObject.new(self), :port => port)
108
+ @mutex = Mutex.new
109
+ @streams = Array.new
110
+ @connections = Hash.new
111
+ @subscriptions = Hash.new { |h, k| h[k] = Set.new }
112
+ @polling_timeout = polling_timeout
113
+ @polling = Thread.new(&method(:polling))
114
+ end
115
+
116
+ def synchronize
117
+ @mutex.synchronize { yield }
118
+ end
119
+
120
+ def connect(remote)
121
+ synchronize do
122
+ queue = Queue.new
123
+ receiver_thread = pushing_loop(remote, queue)
124
+ connections[remote] = [queue, receiver_thread]
125
+
126
+ Server.info "#{remote.__drburi} connected"
127
+ end
128
+ streams.map do |s|
129
+ [s.class.name, s.id, s.name, s.type]
130
+ end
131
+ end
132
+
133
+ def disconnect(remote)
134
+ thread = synchronize do
135
+ queue, thread = connections[remote]
136
+ if thread
137
+ thread.raise Interrupt, "quitting"
138
+ thread
139
+ end
140
+ end
141
+ thread.join if thread
142
+
143
+ Server.info "#{remote.__drburi} disconnected"
144
+ end
145
+
146
+ # Polls all data sources and pushes the samples to the subscribed
147
+ # clients
148
+ def polling
149
+ loop do
150
+ s, data = nil
151
+ done_sth = false
152
+ synchronize do
153
+ @streams.each do |s|
154
+ done_sth ||= if s.reinit?
155
+ Roby::Log::Server.info "reinitializing #{s}"
156
+ s.reinit!
157
+ reinit(s.id)
158
+ true
159
+ elsif s.has_sample?
160
+ if Roby::Log::Server.logger.debug?
161
+ Roby::Log::Server.debug "new sample for #{s} at #{s.current_time.to_hms}"
162
+ end
163
+ push(s.id, s.current_time, s.read)
164
+ true
165
+ end
166
+ end
167
+ end
168
+
169
+ unless done_sth
170
+ sleep(polling_timeout)
171
+ end
172
+ end
173
+ rescue Interrupt
174
+ end
175
+
176
+ # Creates a new thread to send updates to +remote+
177
+ def pushing_loop(remote, queue)
178
+ Thread.new do
179
+ begin
180
+ loop do
181
+ calls = []
182
+ while !queue.empty?
183
+ calls << queue.pop
184
+ end
185
+ remote.demux(calls)
186
+ if calls.find { |m, _| m == :quit }
187
+ break
188
+ end
189
+ end
190
+ rescue Interrupt
191
+ rescue DRb::DRbConnError => e
192
+ Server.warn "cannot communicate with #{remote.__drburi}. Assuming we are disconnected"
193
+
194
+ ensure
195
+ synchronize do
196
+ # Remove all subscriptions for +remote+
197
+ subscriptions.each_value do |subscribed|
198
+ subscribed.delete(remote)
199
+ end
200
+ queue, thread = connections.delete(remote)
201
+ end
202
+ end
203
+ end
204
+ end
205
+ private :pushing_loop
206
+
207
+ # New stream
208
+ def added_stream(stream)
209
+ synchronize do
210
+ @streams << stream
211
+ connections.each_value do |queue, _|
212
+ queue.push [:added_stream, stream.class.name, stream.id, stream.name, stream.type]
213
+ end
214
+ end
215
+ end
216
+
217
+ # Stream +id+ has stopped
218
+ def removed_stream(id)
219
+ synchronize do
220
+ found = false
221
+ @streams.delete_if { |s| found ||= (s.id == id) }
222
+ unless found
223
+ raise ArgumentError, "no such stream"
224
+ end
225
+
226
+ connections.each_value do |queue, _|
227
+ queue.push [:removed_stream, id]
228
+ end
229
+ subscriptions.delete(id)
230
+ end
231
+ end
232
+
233
+ # Returns a set of Roby::Log::DataStream objects describing the
234
+ # available data sources on this stream
235
+ def streams
236
+ synchronize do
237
+ @streams.dup
238
+ end
239
+ end
240
+
241
+ # Make +remote+ subscribe to the stream identified by +id+. When
242
+ # new data is available, #push will be called on +remote+. The
243
+ # exact format of the pushed sample depends on the type of the
244
+ # stream
245
+ #
246
+ # If the stream stop existing (because it source has quit for
247
+ # instance), #removed_stream will be called on the remote object
248
+ def subscribe(id, remote)
249
+ synchronize do
250
+ if s = @streams.find { |s| s.id == id }
251
+ subscriptions[id] << remote
252
+ if data = s.read_all
253
+ remote.init(id, data)
254
+ end
255
+ else
256
+ raise ArgumentError, "no such stream"
257
+ end
258
+ end
259
+ end
260
+
261
+ # Rmoves a subscription of +remote+ on +id+
262
+ def unsubscribe(id, remote)
263
+ synchronize do
264
+ if subscriptions.has_key?(id)
265
+ subscriptions[id].delete(remote)
266
+ end
267
+ end
268
+ end
269
+
270
+ # Reinitializes the stream +id+. It is used when a stream has
271
+ # been truncated (for instance when a log file has been restarted)
272
+ #
273
+ # This must be called in a synchronize { } block
274
+ def reinit(id)
275
+ subscriptions[id].each do |remote|
276
+ queue, _ = connections[remote]
277
+ queue.push [:reinit, id]
278
+ end
279
+ end
280
+ private :reinit
281
+
282
+ # Pushes a new sample on stream +id+
283
+ #
284
+ # This must be called in a synchronize { } block
285
+ def push(id, time, sample)
286
+ if subscriptions.has_key?(id)
287
+ subscriptions[id].each do |remote|
288
+ queue, _ = connections[remote]
289
+ queue.push [:push, id, time, sample]
290
+ end
291
+ end
292
+ end
293
+ private :push
294
+
295
+ def quit
296
+ if @polling
297
+ @polling.raise Interrupt, "quitting"
298
+ @polling.join
299
+ end
300
+
301
+ connections.each_value do |queue, thread|
302
+ queue.push [:quit]
303
+ thread.join
304
+ end
305
+ end
306
+ end
307
+
308
+ # This class manages a data stream which is present remotely. Data is sent
309
+ # as-is over the network from a Server object to a Client object.
310
+ class RemoteStream < DataStream
311
+ def initialize(stream_model, id, name, type)
312
+ super(name, type)
313
+ @id = id
314
+ @stream_model = stream_model
315
+
316
+ @data_file = Tempfile.new("remote_stream_#{name}_#{type}".gsub("/", "_"))
317
+ @data_file.sync = true
318
+
319
+ @mutex = Mutex.new
320
+ @pending_samples = Array.new
321
+ end
322
+ def synchronize; @mutex.synchronize { yield } end
323
+
324
+ # The data file in which we save the data received so far
325
+ attr_reader :data_file
326
+ # The DataStream class of the remote stream. This is used for
327
+ # decoding
328
+ attr_reader :stream_model
329
+
330
+ def added_decoder(dec)
331
+ synchronize do
332
+ Server.info "#{self} initializing #{dec}"
333
+ if data_file.stat.size == 0
334
+ return
335
+ end
336
+
337
+ data_file.rewind
338
+ chunk_length = data_file.read(4).unpack("N").first
339
+ chunk = data_file.read(chunk_length)
340
+ init(chunk) do |sample|
341
+ dec.process(sample)
342
+ end
343
+
344
+ while !data_file.eof?
345
+ chunk_length = data_file.read(4).unpack("N").first
346
+ chunk = data_file.read(chunk_length)
347
+ dec.process(decode(chunk))
348
+ end
349
+
350
+ display
351
+ end
352
+ end
353
+
354
+ def reinit!
355
+ data_file.truncate(0)
356
+ @pending_samples.clear
357
+ @current_time = nil
358
+
359
+ super
360
+ end
361
+
362
+ # Called when new data is available
363
+ def push(time, data)
364
+ Server.info "#{self} got #{data.size} bytes of data at #{time.to_hms}"
365
+ synchronize do
366
+ @range[0] ||= time
367
+ @range[1] = time
368
+ @current_time ||= time
369
+
370
+ @pending_samples.unshift [time, data]
371
+ data_file << [data.size].pack("N") << data
372
+ end
373
+ end
374
+
375
+ attr_reader :current_time
376
+
377
+ def next_time
378
+ synchronize do
379
+ if has_sample?
380
+ @pending_samples.first
381
+ end
382
+ end
383
+ end
384
+
385
+ def range
386
+ synchronize { super }
387
+ end
388
+
389
+ def has_sample?
390
+ synchronize do
391
+ !@pending_samples.empty?
392
+ end
393
+ end
394
+
395
+ def read
396
+ if reinit?
397
+ reinit!
398
+ end
399
+
400
+ @current_time, sample = @pending_samples.pop
401
+ sample
402
+ end
403
+
404
+ def init(data, &block)
405
+ Server.info "#{self} initializing with #{data.size} bytes of data"
406
+ data_file.rewind
407
+ data_file << [data.size].pack("N") << data
408
+ stream_model.init(data, &block)
409
+ end
410
+ def decode(data)
411
+ stream_model.decode(data)
412
+ end
413
+ end
414
+
415
+ class Client
416
+ # The remote display server
417
+ attr_reader :server
418
+
419
+ def initialize(server)
420
+ @server = server
421
+ @pending = Hash.new
422
+ connect
423
+ end
424
+
425
+ def streams
426
+ @streams.values
427
+ end
428
+
429
+ def added_stream(klass_name, id, name, type)
430
+ begin
431
+ require klass_name.underscore
432
+ rescue LoadError
433
+ end
434
+
435
+ @streams[id] = RemoteStream.new(klass_name.constantize, id, name, type)
436
+ super if defined? super
437
+ end
438
+ def removed_stream(id)
439
+ @streams.delete(id)
440
+ super if defined? super
441
+ end
442
+
443
+ attr_reader :last_update
444
+ MIN_DISPLAY_DURATION = 5
445
+ def demux(calls)
446
+ calls.each do |args|
447
+ send(*args)
448
+ end
449
+
450
+ streams.each do |s|
451
+ while s.has_sample?
452
+ s.synchronize do
453
+ s.advance
454
+ end
455
+ end
456
+ end
457
+
458
+ sleep(0.5)
459
+ end
460
+
461
+ def subscribe(stream)
462
+ @server.subscribe(stream.id, DRbObject.new(self))
463
+ end
464
+
465
+ def unsubscribe(stream)
466
+ @server.unsubscribe(stream.id, DRbObject.new(self))
467
+ end
468
+
469
+ def init(id, data)
470
+ s = @streams[id]
471
+ Server.info "initializing #{s}"
472
+ s.synchronize do
473
+ s.init(data) do |sample|
474
+ s.decoders.each do |dec|
475
+ dec.process(sample)
476
+ end
477
+ end
478
+ end
479
+ end
480
+
481
+ def reinit(id)
482
+ @streams[id].reinit = true
483
+ end
484
+
485
+ def push(id, time, data)
486
+ @streams[id].push(time, data)
487
+ end
488
+
489
+ def connected?; !!@streams end
490
+ def connect
491
+ if connected?
492
+ raise ArgumentError, "already connected"
493
+ end
494
+
495
+ @streams = Hash.new
496
+ server.connect(DRbObject.new(self)).
497
+ each do |klass, id, name, type|
498
+ added_stream(klass, id, name, type)
499
+ end
500
+
501
+ ObjectSpace.define_finalizer(self, Client.remote_streams_finalizer(server, DRbObject.new(self)))
502
+
503
+ rescue Exception
504
+ disconnect
505
+ raise
506
+ end
507
+
508
+ def self.remote_streams_finalizer(server, drb_object)
509
+ Proc.new do
510
+ begin
511
+ server.disconnect(drb_object)
512
+ rescue DRb::DRbConnError
513
+ rescue Exception => e
514
+ STDERR.puts e.full_message
515
+ end
516
+ end
517
+ end
518
+
519
+ def disconnect
520
+ @streams = nil
521
+ server.disconnect(DRbObject.new(self))
522
+ rescue DRb::DRbConnError
523
+ end
524
+
525
+ def quit
526
+ @streams = nil
527
+ @server = nil
528
+ end
529
+ end
530
+ end
531
+ end
532
+
533
+ if $0 == __FILE__
534
+ include Roby
535
+ include Roby::Log
536
+
537
+ # First find the available servers
538
+ STDERR.puts "Finding available servers ..."
539
+ DRb.start_service
540
+ Server.enable_discovery 'localhost'
541
+ sleep(0.5)
542
+ Server.available_servers.each do |server|
543
+ remote = RemoteStreams.new(server)
544
+ puts "#{server.__drburi}:"
545
+ remote.streams.each do |s|
546
+ puts " #{s.name} [#{s.type}]"
547
+ end
548
+ end
549
+ end
550
+