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.
- data/.gitignore +29 -0
- data/History.txt +4 -0
- data/License-fr.txt +519 -0
- data/License.txt +515 -0
- data/Manifest.txt +245 -0
- data/NOTES +4 -0
- data/README.txt +163 -0
- data/Rakefile +161 -0
- data/TODO.txt +146 -0
- data/app/README.txt +24 -0
- data/app/Rakefile +8 -0
- data/app/config/ROBOT.rb +5 -0
- data/app/config/app.yml +91 -0
- data/app/config/init.rb +7 -0
- data/app/config/roby.yml +3 -0
- data/app/controllers/.gitattributes +0 -0
- data/app/controllers/ROBOT.rb +2 -0
- data/app/data/.gitattributes +0 -0
- data/app/planners/ROBOT/main.rb +6 -0
- data/app/planners/main.rb +5 -0
- data/app/scripts/distributed +3 -0
- data/app/scripts/generate/bookmarks +3 -0
- data/app/scripts/replay +3 -0
- data/app/scripts/results +3 -0
- data/app/scripts/run +3 -0
- data/app/scripts/server +3 -0
- data/app/scripts/shell +3 -0
- data/app/scripts/test +3 -0
- data/app/tasks/.gitattributes +0 -0
- data/app/tasks/ROBOT/.gitattributes +0 -0
- data/bin/roby +210 -0
- data/bin/roby-log +168 -0
- data/bin/roby-shell +25 -0
- data/doc/images/event_generalization.png +0 -0
- data/doc/images/exception_propagation_1.png +0 -0
- data/doc/images/exception_propagation_2.png +0 -0
- data/doc/images/exception_propagation_3.png +0 -0
- data/doc/images/exception_propagation_4.png +0 -0
- data/doc/images/exception_propagation_5.png +0 -0
- data/doc/images/replay_handler_error.png +0 -0
- data/doc/images/replay_handler_error_0.png +0 -0
- data/doc/images/replay_handler_error_1.png +0 -0
- data/doc/images/roby_cycle_overview.png +0 -0
- data/doc/images/roby_replay_02.png +0 -0
- data/doc/images/roby_replay_03.png +0 -0
- data/doc/images/roby_replay_04.png +0 -0
- data/doc/images/roby_replay_event_representation.png +0 -0
- data/doc/images/roby_replay_first_state.png +0 -0
- data/doc/images/roby_replay_relations.png +0 -0
- data/doc/images/roby_replay_startup.png +0 -0
- data/doc/images/task_event_generalization.png +0 -0
- data/doc/papers.rdoc +11 -0
- data/doc/styles/allison.css +314 -0
- data/doc/styles/allison.js +316 -0
- data/doc/styles/allison.rb +276 -0
- data/doc/styles/jamis.rb +593 -0
- data/doc/tutorials/01-GettingStarted.rdoc +86 -0
- data/doc/tutorials/02-GoForward.rdoc +220 -0
- data/doc/tutorials/03-PlannedPath.rdoc +268 -0
- data/doc/tutorials/04-EventPropagation.rdoc +236 -0
- data/doc/tutorials/05-ErrorHandling.rdoc +319 -0
- data/doc/tutorials/06-Overview.rdoc +40 -0
- data/doc/videos.rdoc +69 -0
- data/ext/droby/dump.cc +175 -0
- data/ext/droby/extconf.rb +3 -0
- data/ext/graph/algorithm.cc +746 -0
- data/ext/graph/extconf.rb +7 -0
- data/ext/graph/graph.cc +529 -0
- data/ext/graph/graph.hh +183 -0
- data/ext/graph/iterator_sequence.hh +102 -0
- data/ext/graph/undirected_dfs.hh +226 -0
- data/ext/graph/undirected_graph.hh +421 -0
- data/lib/roby.rb +41 -0
- data/lib/roby/app.rb +870 -0
- data/lib/roby/app/rake.rb +56 -0
- data/lib/roby/app/run.rb +14 -0
- data/lib/roby/app/scripts/distributed.rb +13 -0
- data/lib/roby/app/scripts/generate/bookmarks.rb +162 -0
- data/lib/roby/app/scripts/replay.rb +31 -0
- data/lib/roby/app/scripts/results.rb +15 -0
- data/lib/roby/app/scripts/run.rb +26 -0
- data/lib/roby/app/scripts/server.rb +18 -0
- data/lib/roby/app/scripts/shell.rb +88 -0
- data/lib/roby/app/scripts/test.rb +40 -0
- data/lib/roby/basic_object.rb +151 -0
- data/lib/roby/config.rb +5 -0
- data/lib/roby/control.rb +747 -0
- data/lib/roby/decision_control.rb +17 -0
- data/lib/roby/distributed.rb +32 -0
- data/lib/roby/distributed/base.rb +440 -0
- data/lib/roby/distributed/communication.rb +871 -0
- data/lib/roby/distributed/connection_space.rb +592 -0
- data/lib/roby/distributed/distributed_object.rb +206 -0
- data/lib/roby/distributed/drb.rb +62 -0
- data/lib/roby/distributed/notifications.rb +539 -0
- data/lib/roby/distributed/peer.rb +550 -0
- data/lib/roby/distributed/protocol.rb +529 -0
- data/lib/roby/distributed/proxy.rb +343 -0
- data/lib/roby/distributed/subscription.rb +311 -0
- data/lib/roby/distributed/transaction.rb +498 -0
- data/lib/roby/event.rb +897 -0
- data/lib/roby/exceptions.rb +234 -0
- data/lib/roby/executives/simple.rb +30 -0
- data/lib/roby/graph.rb +166 -0
- data/lib/roby/interface.rb +390 -0
- data/lib/roby/log.rb +3 -0
- data/lib/roby/log/chronicle.rb +303 -0
- data/lib/roby/log/console.rb +72 -0
- data/lib/roby/log/data_stream.rb +197 -0
- data/lib/roby/log/dot.rb +279 -0
- data/lib/roby/log/event_stream.rb +151 -0
- data/lib/roby/log/file.rb +340 -0
- data/lib/roby/log/gui/basic_display.ui +83 -0
- data/lib/roby/log/gui/chronicle.rb +26 -0
- data/lib/roby/log/gui/chronicle_view.rb +40 -0
- data/lib/roby/log/gui/chronicle_view.ui +70 -0
- data/lib/roby/log/gui/data_displays.rb +172 -0
- data/lib/roby/log/gui/data_displays.ui +155 -0
- data/lib/roby/log/gui/notifications.rb +26 -0
- data/lib/roby/log/gui/relations.rb +248 -0
- data/lib/roby/log/gui/relations.ui +123 -0
- data/lib/roby/log/gui/relations_view.rb +185 -0
- data/lib/roby/log/gui/relations_view.ui +149 -0
- data/lib/roby/log/gui/replay.rb +327 -0
- data/lib/roby/log/gui/replay_controls.rb +200 -0
- data/lib/roby/log/gui/replay_controls.ui +259 -0
- data/lib/roby/log/gui/runtime.rb +130 -0
- data/lib/roby/log/hooks.rb +185 -0
- data/lib/roby/log/logger.rb +202 -0
- data/lib/roby/log/notifications.rb +244 -0
- data/lib/roby/log/plan_rebuilder.rb +470 -0
- data/lib/roby/log/relations.rb +1056 -0
- data/lib/roby/log/server.rb +550 -0
- data/lib/roby/log/sqlite.rb +47 -0
- data/lib/roby/log/timings.rb +164 -0
- data/lib/roby/plan-object.rb +247 -0
- data/lib/roby/plan.rb +762 -0
- data/lib/roby/planning.rb +13 -0
- data/lib/roby/planning/loops.rb +302 -0
- data/lib/roby/planning/model.rb +906 -0
- data/lib/roby/planning/task.rb +151 -0
- data/lib/roby/propagation.rb +562 -0
- data/lib/roby/query.rb +619 -0
- data/lib/roby/relations.rb +583 -0
- data/lib/roby/relations/conflicts.rb +70 -0
- data/lib/roby/relations/ensured.rb +20 -0
- data/lib/roby/relations/error_handling.rb +23 -0
- data/lib/roby/relations/events.rb +9 -0
- data/lib/roby/relations/executed_by.rb +193 -0
- data/lib/roby/relations/hierarchy.rb +239 -0
- data/lib/roby/relations/influence.rb +10 -0
- data/lib/roby/relations/planned_by.rb +63 -0
- data/lib/roby/robot.rb +7 -0
- data/lib/roby/standard_errors.rb +218 -0
- data/lib/roby/state.rb +5 -0
- data/lib/roby/state/events.rb +221 -0
- data/lib/roby/state/information.rb +55 -0
- data/lib/roby/state/pos.rb +110 -0
- data/lib/roby/state/shapes.rb +32 -0
- data/lib/roby/state/state.rb +353 -0
- data/lib/roby/support.rb +92 -0
- data/lib/roby/task-operations.rb +182 -0
- data/lib/roby/task.rb +1618 -0
- data/lib/roby/test/common.rb +399 -0
- data/lib/roby/test/distributed.rb +214 -0
- data/lib/roby/test/tasks/empty_task.rb +9 -0
- data/lib/roby/test/tasks/goto.rb +36 -0
- data/lib/roby/test/tasks/simple_task.rb +23 -0
- data/lib/roby/test/testcase.rb +519 -0
- data/lib/roby/test/tools.rb +160 -0
- data/lib/roby/thread_task.rb +87 -0
- data/lib/roby/transactions.rb +462 -0
- data/lib/roby/transactions/proxy.rb +292 -0
- data/lib/roby/transactions/updates.rb +139 -0
- data/plugins/fault_injection/History.txt +4 -0
- data/plugins/fault_injection/README.txt +37 -0
- data/plugins/fault_injection/Rakefile +18 -0
- data/plugins/fault_injection/TODO.txt +0 -0
- data/plugins/fault_injection/app.rb +52 -0
- data/plugins/fault_injection/fault_injection.rb +89 -0
- data/plugins/fault_injection/test/test_fault_injection.rb +84 -0
- data/plugins/subsystems/README.txt +40 -0
- data/plugins/subsystems/Rakefile +18 -0
- data/plugins/subsystems/app.rb +171 -0
- data/plugins/subsystems/test/app/README +24 -0
- data/plugins/subsystems/test/app/Rakefile +8 -0
- data/plugins/subsystems/test/app/config/app.yml +71 -0
- data/plugins/subsystems/test/app/config/init.rb +9 -0
- data/plugins/subsystems/test/app/config/roby.yml +3 -0
- data/plugins/subsystems/test/app/planners/main.rb +20 -0
- data/plugins/subsystems/test/app/scripts/distributed +3 -0
- data/plugins/subsystems/test/app/scripts/replay +3 -0
- data/plugins/subsystems/test/app/scripts/results +3 -0
- data/plugins/subsystems/test/app/scripts/run +3 -0
- data/plugins/subsystems/test/app/scripts/server +3 -0
- data/plugins/subsystems/test/app/scripts/shell +3 -0
- data/plugins/subsystems/test/app/scripts/test +3 -0
- data/plugins/subsystems/test/app/tasks/services.rb +15 -0
- data/plugins/subsystems/test/test_subsystems.rb +71 -0
- data/test/distributed/test_communication.rb +178 -0
- data/test/distributed/test_connection.rb +282 -0
- data/test/distributed/test_execution.rb +373 -0
- data/test/distributed/test_mixed_plan.rb +341 -0
- data/test/distributed/test_plan_notifications.rb +238 -0
- data/test/distributed/test_protocol.rb +516 -0
- data/test/distributed/test_query.rb +102 -0
- data/test/distributed/test_remote_plan.rb +491 -0
- data/test/distributed/test_transaction.rb +463 -0
- data/test/mockups/tasks.rb +27 -0
- data/test/planning/test_loops.rb +380 -0
- data/test/planning/test_model.rb +427 -0
- data/test/planning/test_task.rb +106 -0
- data/test/relations/test_conflicts.rb +42 -0
- data/test/relations/test_ensured.rb +38 -0
- data/test/relations/test_executed_by.rb +149 -0
- data/test/relations/test_hierarchy.rb +158 -0
- data/test/relations/test_planned_by.rb +54 -0
- data/test/suite_core.rb +24 -0
- data/test/suite_distributed.rb +9 -0
- data/test/suite_planning.rb +3 -0
- data/test/suite_relations.rb +8 -0
- data/test/test_bgl.rb +508 -0
- data/test/test_control.rb +399 -0
- data/test/test_event.rb +894 -0
- data/test/test_exceptions.rb +592 -0
- data/test/test_interface.rb +37 -0
- data/test/test_log.rb +114 -0
- data/test/test_log_server.rb +132 -0
- data/test/test_plan.rb +584 -0
- data/test/test_propagation.rb +210 -0
- data/test/test_query.rb +266 -0
- data/test/test_relations.rb +180 -0
- data/test/test_state.rb +414 -0
- data/test/test_support.rb +16 -0
- data/test/test_task.rb +938 -0
- data/test/test_testcase.rb +122 -0
- data/test/test_thread_task.rb +73 -0
- data/test/test_transactions.rb +569 -0
- data/test/test_transactions_proxy.rb +198 -0
- 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
|
+
|