asterism 0.2.0 → 0.4.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +110 -0
- data/README.md +277 -62
- data/lib/asterism/cruby/objects.rb +25 -10
- data/lib/asterism/cruby/ros.rb +6 -4
- data/lib/asterism/cruby/zenoh.rb +428 -72
- data/lib/asterism/version.rb +1 -1
- data/lib/asterism.rb +2 -2
- data/mrblib/asterism.rb +112 -19
- data/mrblib/cdr.rb +5 -1
- data/mrblib/future.rb +11 -2
- data/mrblib/proxy.rb +13 -3
- data/mrblib/ros.rb +121 -41
- metadata +5 -4
data/lib/asterism/cruby/zenoh.rb
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Asterism::Zenoh with blocks, threads and Enumerators (CRuby only). The
|
|
2
|
-
#
|
|
2
|
+
# portable API of asterism-zenoh (Session, each_pending, each_reply) is the
|
|
3
3
|
# same on the boards and stays as it is; this is a layer on top of it.
|
|
4
4
|
#
|
|
5
5
|
# Asterism::Zenoh.open("tcp/192.0.2.2:7447") do |s|
|
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
# s.put("home/pc/hello", "hi")
|
|
9
9
|
# s.get("home/**/status").each { |reply| p reply }
|
|
10
10
|
# s.subscribe("sensor/temp").each.lazy.map { _1.payload.to_f }.first(3)
|
|
11
|
+
# pub = s.publisher("home/pc/temp", encoding: "text/plain")
|
|
12
|
+
# pub.on_matching { |listening| puts "someone listens: #{listening}" }
|
|
13
|
+
# s.on_transport { |ev| puts "#{ev.zid} #{ev.kind}" }
|
|
11
14
|
# s.run # until Ctrl-C (or s.start / s.stop)
|
|
12
15
|
# end
|
|
13
16
|
#
|
|
@@ -19,28 +22,8 @@ module Asterism
|
|
|
19
22
|
# Pause between looks while an Enumerator waits for data (seconds).
|
|
20
23
|
ENUM_STEP = 0.002
|
|
21
24
|
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
Sample = Data.define(:key, :payload, :attachment) do
|
|
25
|
-
def text
|
|
26
|
-
payload.dup.force_encoding(Encoding::UTF_8)
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def to_s
|
|
30
|
-
"#{key}: #{payload}"
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
# A reply to a get: the key it was sent with, payload and attachment.
|
|
35
|
-
Reply = Data.define(:key, :payload, :attachment) do
|
|
36
|
-
def text
|
|
37
|
-
payload.dup.force_encoding(Encoding::UTF_8)
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def to_s
|
|
41
|
-
"#{key}: #{payload}"
|
|
42
|
-
end
|
|
43
|
-
end
|
|
25
|
+
# Sample and Reply (the received values, with kind, encoding,
|
|
26
|
+
# timestamp and the rest) are asterism-zenoh's (asterism/zenoh/values).
|
|
44
27
|
|
|
45
28
|
# A liveliness change: key of the token, and whether it appeared.
|
|
46
29
|
Liveliness = Data.define(:key, :alive) do
|
|
@@ -57,6 +40,20 @@ module Asterism
|
|
|
57
40
|
args.unshift(@asterism_key) if args.size == 1 && @asterism_key
|
|
58
41
|
super(*args, **kw)
|
|
59
42
|
end
|
|
43
|
+
|
|
44
|
+
# reply_del without a key: the queryable's own key, as reply.
|
|
45
|
+
def reply_del(key = nil, **kw)
|
|
46
|
+
super(key || @asterism_key, **kw)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def reply_err(payload, **kw)
|
|
50
|
+
super(payload.is_a?(String) ? payload : payload.to_s, **kw)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# A payload as a String (anything else is sent as to_s).
|
|
55
|
+
def self.bytes(payload)
|
|
56
|
+
payload.is_a?(String) ? payload : payload.to_s
|
|
60
57
|
end
|
|
61
58
|
|
|
62
59
|
# The queryable's key when it names one key (no wildcard).
|
|
@@ -83,19 +80,72 @@ module Asterism
|
|
|
83
80
|
end
|
|
84
81
|
end
|
|
85
82
|
|
|
86
|
-
# Seconds (Float) or nil -> milliseconds for the
|
|
83
|
+
# Seconds (Float) or nil -> milliseconds for the portable API.
|
|
84
|
+
# @api private
|
|
87
85
|
def self.ms(seconds, default_ms)
|
|
88
|
-
|
|
89
|
-
|
|
86
|
+
::Asterism.time_ms("timeout:", seconds, nil, nil, default_ms)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Warns once for owner (a wrapper, or a Get) when raw (what it wraps)
|
|
90
|
+
# dropped something. Only for queues whose depth the application left
|
|
91
|
+
# at the default: one that chose depth: reads dropped itself.
|
|
92
|
+
# @api private
|
|
93
|
+
def self.note_dropped(owner, raw, what, unit)
|
|
94
|
+
d = raw.dropped
|
|
95
|
+
return false if d == 0
|
|
96
|
+
::Asterism.warn_once(owner, "asterism: #{what}: #{d} #{unit} dropped, the queue was full " \
|
|
97
|
+
"(pass a larger depth:, or read dropped)")
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# The keys liveliness_get returns: an Array, with the counts of the get
|
|
101
|
+
# behind it.
|
|
102
|
+
class KeyList < Array
|
|
103
|
+
# Replies that did not fit the get's queue (oldest first), and all
|
|
104
|
+
# that came.
|
|
105
|
+
attr_reader :dropped, :received
|
|
106
|
+
|
|
107
|
+
# @api private
|
|
108
|
+
def finish(get)
|
|
109
|
+
@dropped = get.dropped
|
|
110
|
+
@received = get.received
|
|
111
|
+
self
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# What Connection#get and Querier#get return without a block: an
|
|
116
|
+
# Enumerator (each iteration sends the get again) that also has the
|
|
117
|
+
# counts of the last get it sent: dropped, received, errors (nil before
|
|
118
|
+
# the first).
|
|
119
|
+
class GetEnumerator < Enumerator
|
|
120
|
+
attr_reader :dropped, :received, :errors
|
|
121
|
+
|
|
122
|
+
# @api private
|
|
123
|
+
def finish(get)
|
|
124
|
+
@dropped = get.dropped
|
|
125
|
+
@received = get.received
|
|
126
|
+
@errors = get.errors
|
|
127
|
+
self
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Yields obj and closes it after the block (the block's value), or
|
|
132
|
+
# returns obj without a block. @api private
|
|
133
|
+
def self.scoped(obj)
|
|
134
|
+
return obj unless block_given?
|
|
135
|
+
begin
|
|
136
|
+
yield obj
|
|
137
|
+
ensure
|
|
138
|
+
obj.close
|
|
139
|
+
end
|
|
90
140
|
end
|
|
91
141
|
|
|
92
142
|
# Waits on the calling thread until a sample comes into sub (an
|
|
93
143
|
# Asterism::Zenoh::Subscriber or LivelinessWatch), yielding each.
|
|
94
144
|
# Ends when sub or the session closes, or after timeout seconds.
|
|
95
|
-
def self.drain(sub, session, timeout)
|
|
145
|
+
def self.drain(sub, session, timeout, take: :each_pending)
|
|
96
146
|
deadline = timeout && (Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout)
|
|
97
147
|
loop do
|
|
98
|
-
got = sub.
|
|
148
|
+
got = sub.public_send(take)
|
|
99
149
|
got.each { |e| yield e }
|
|
100
150
|
break if sub.closed? || session.closed?
|
|
101
151
|
break if deadline && Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline
|
|
@@ -115,16 +165,75 @@ module Asterism
|
|
|
115
165
|
@runner.add { @session.poll ? 0 : false }
|
|
116
166
|
end
|
|
117
167
|
|
|
118
|
-
|
|
119
|
-
|
|
168
|
+
# opts: those of Session#put (attachment:, encoding:, priority:,
|
|
169
|
+
# congestion_control:, express:, reliability:, timestamp:,
|
|
170
|
+
# allowed_destination:). A payload that is not a String is sent as
|
|
171
|
+
# to_s.
|
|
172
|
+
def put(key, payload, **opts)
|
|
173
|
+
@session.put(key, Zenoh.bytes(payload), **opts)
|
|
174
|
+
nil
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Deletes key (subscribers get a Sample of kind :delete). opts: those
|
|
178
|
+
# of Session#delete.
|
|
179
|
+
def delete(key, **opts)
|
|
180
|
+
@session.delete(key, **opts)
|
|
120
181
|
nil
|
|
121
182
|
end
|
|
122
183
|
|
|
184
|
+
# A declared publisher (Connection::Publisher): put / delete,
|
|
185
|
+
# matching?, on_matching { |listening| }. opts: those of
|
|
186
|
+
# Session#publisher (encoding:, priority:, ...). With a block: yields
|
|
187
|
+
# it and closes it after the block (returns the block's value).
|
|
188
|
+
def publisher(key, **opts, &blk)
|
|
189
|
+
Zenoh.scoped(Publisher.new(self, @session.publisher(key, **opts), key), &blk)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# An advanced publisher (cache:, sample_miss_detection:,
|
|
193
|
+
# publisher_detection: and the publisher options): late subscribers
|
|
194
|
+
# with history get what it kept. Same object as publisher (and the
|
|
195
|
+
# same block form).
|
|
196
|
+
def advanced_publisher(key, **opts, &blk)
|
|
197
|
+
Zenoh.scoped(Publisher.new(self, @session.advanced_publisher(key, **opts), key), &blk)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# An advanced subscriber (history:, recovery:, subscriber_detection:,
|
|
201
|
+
# query_timeout: in seconds or query_timeout_ms:): like subscribe,
|
|
202
|
+
# plus on_publisher and on_miss.
|
|
203
|
+
def advanced_subscriber(key, depth: 16, **opts, &blk)
|
|
204
|
+
sub = AdvancedSubscription.new(self, @session.advanced_subscriber(key, depth: depth, **opts), key)
|
|
205
|
+
sub.handle(&blk) if blk
|
|
206
|
+
sub
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# A declared get (Connection::Querier): get { |reply| }, matching?,
|
|
210
|
+
# on_matching. timeout: in seconds (2.0) or timeout_ms:; the rest as
|
|
211
|
+
# Session#querier.
|
|
212
|
+
def querier(key, timeout: nil, **opts)
|
|
213
|
+
Querier.new(self, @session.querier(key, timeout: timeout, **opts), key)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# on_transport(history: false) { |event| }: a TransportEvent (kind
|
|
217
|
+
# :added / :removed, zid, whatami) on the receiving thread each time a
|
|
218
|
+
# peer or router connects or goes. Returns an Events (close it).
|
|
219
|
+
def on_transport(history: false, depth: 16, &blk)
|
|
220
|
+
raise ArgumentError, "a block is needed" unless blk
|
|
221
|
+
Events.new(self, @session.transport_events(depth: depth, history: history), "on_transport", blk)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# on_link(history: false) { |event| }: a LinkEvent each time a link
|
|
225
|
+
# opens or closes.
|
|
226
|
+
def on_link(history: false, depth: 16, &blk)
|
|
227
|
+
raise ArgumentError, "a block is needed" unless blk
|
|
228
|
+
Events.new(self, @session.link_events(depth: depth, history: history), "on_link", blk)
|
|
229
|
+
end
|
|
230
|
+
|
|
123
231
|
# With a block: every sample is given to it (as a Sample) on the
|
|
124
232
|
# receiving thread; returns the Subscription (close it to stop).
|
|
125
|
-
# Without: a Subscription to take from (each,
|
|
126
|
-
def subscribe(key, depth:
|
|
127
|
-
|
|
233
|
+
# Without: a Subscription to take from (each, each_sample).
|
|
234
|
+
def subscribe(key, depth: nil, &blk)
|
|
235
|
+
d = depth.nil? ? DEFAULT_DEPTH : depth
|
|
236
|
+
sub = Subscription.new(self, @session.subscribe(key, depth: d), key, warn: depth.nil?)
|
|
128
237
|
sub.handle(&blk) if blk
|
|
129
238
|
sub
|
|
130
239
|
end
|
|
@@ -133,47 +242,65 @@ module Asterism
|
|
|
133
242
|
# finished when it returns (reply in the block with q.reply). Without:
|
|
134
243
|
# a Queryable whose each yields the queries (finished after each).
|
|
135
244
|
def queryable(key, depth: 16, complete: false, &blk)
|
|
136
|
-
qa = Queryable.new(self, @session.queryable(key, depth, complete: complete), key)
|
|
245
|
+
qa = Queryable.new(self, @session.queryable(key, depth: depth, complete: complete), key)
|
|
137
246
|
qa.handle(&blk) if blk
|
|
138
247
|
qa
|
|
139
248
|
end
|
|
140
249
|
|
|
141
250
|
# The replies to a get: yields each Reply until every answer came or
|
|
142
|
-
# timeout (seconds) ran out, and returns their number
|
|
143
|
-
#
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
251
|
+
# timeout (seconds; or timeout_ms:) ran out, and returns their number
|
|
252
|
+
# (not named each, so the count stays). Without a block, a
|
|
253
|
+
# GetEnumerator (each iteration sends the get again; dropped /
|
|
254
|
+
# received / errors of the last one). errors: true also yields the
|
|
255
|
+
# error replies (Reply#error?). depth: the replies kept until taken
|
|
256
|
+
# (DEFAULT_GET_DEPTH, 1024); replies past it are dropped, which warns
|
|
257
|
+
# once unless depth: was given. opts: encoding:, priority:,
|
|
258
|
+
# congestion_control:, express:, accept_replies: (Session#get).
|
|
259
|
+
def get(key, timeout: nil, timeout_ms: nil, params: nil, payload: nil, attachment: nil, target: :all,
|
|
260
|
+
consolidation: :none, errors: false, depth: nil, **opts, &blk)
|
|
261
|
+
ms = ::Asterism.time_ms("Connection#get", timeout, timeout_ms, nil, 2000)
|
|
262
|
+
opts[:depth] = depth unless depth.nil?
|
|
263
|
+
start = lambda do
|
|
264
|
+
@session.get(key, timeout_ms: ms, params: params, payload: payload,
|
|
265
|
+
attachment: attachment, target: target, consolidation: consolidation, **opts)
|
|
266
|
+
end
|
|
267
|
+
get_run(start, errors, "get #{key}", depth.nil?, &blk)
|
|
149
268
|
end
|
|
150
269
|
|
|
151
|
-
# A liveliness token (close it to withdraw).
|
|
152
|
-
|
|
153
|
-
|
|
270
|
+
# A liveliness token (close it to withdraw). With a block: the token
|
|
271
|
+
# is withdrawn after the block (returns the block's value).
|
|
272
|
+
def liveliness(key, &blk)
|
|
273
|
+
Zenoh.scoped(@session.liveliness(key), &blk)
|
|
154
274
|
end
|
|
155
275
|
|
|
156
276
|
# With a block: yields key and alive (true when the token appeared) on
|
|
157
277
|
# the receiving thread for each change; the tokens alive now come
|
|
158
|
-
# first. Without: a Watch whose each yields Liveliness
|
|
159
|
-
|
|
160
|
-
|
|
278
|
+
# first, in one burst. Without: a Watch whose each yields Liveliness
|
|
279
|
+
# values. depth: DEFAULT_WATCH_DEPTH (1024); changes past it are
|
|
280
|
+
# dropped (Watch#dropped), which warns once unless depth: was given.
|
|
281
|
+
def liveliness_watch(key, depth: nil, &blk)
|
|
282
|
+
d = depth.nil? ? DEFAULT_WATCH_DEPTH : depth
|
|
283
|
+
w = Watch.new(self, @session.liveliness_watch(key, depth: d), key, warn: depth.nil?)
|
|
161
284
|
w.handle(&blk) if blk
|
|
162
285
|
w
|
|
163
286
|
end
|
|
164
287
|
|
|
165
|
-
# The keys of the liveliness tokens alive now (an Array
|
|
166
|
-
# the answers, at most timeout
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
288
|
+
# The keys of the liveliness tokens alive now (a KeyList, an Array
|
|
289
|
+
# with dropped and received; waits for the answers, at most timeout
|
|
290
|
+
# seconds, or timeout_ms:). depth: DEFAULT_GET_DEPTH (1024); keys
|
|
291
|
+
# past it are dropped, which warns once unless depth: was given.
|
|
292
|
+
def liveliness_get(key, timeout: nil, timeout_ms: nil, depth: nil)
|
|
293
|
+
ms = ::Asterism.time_ms("Connection#liveliness_get", timeout, timeout_ms, nil, 2000)
|
|
294
|
+
g = @session.liveliness_get(key, timeout_ms: ms, depth: depth)
|
|
295
|
+
keys = KeyList.new
|
|
170
296
|
loop do
|
|
297
|
+
ended = g.done? # before taking the replies (see get_each)
|
|
171
298
|
g.each_reply.each { |r| keys << r[0] }
|
|
172
|
-
break if g.
|
|
299
|
+
break if ended && g.pending == 0
|
|
173
300
|
sleep(ENUM_STEP)
|
|
174
301
|
end
|
|
175
|
-
g
|
|
176
|
-
keys
|
|
302
|
+
Zenoh.note_dropped(g, g, "liveliness_get #{key}", "keys") if depth.nil?
|
|
303
|
+
keys.finish(g)
|
|
177
304
|
end
|
|
178
305
|
|
|
179
306
|
# Receives on a thread of its own (blocks run there). Returns self.
|
|
@@ -217,10 +344,31 @@ module Asterism
|
|
|
217
344
|
@session.zid
|
|
218
345
|
end
|
|
219
346
|
|
|
347
|
+
# The routers (client) or peers (peer mode) connected now.
|
|
348
|
+
def connection_count
|
|
349
|
+
@session.connection_count
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
# Deprecated: connection_count.
|
|
220
353
|
def peers
|
|
221
|
-
|
|
354
|
+
::Asterism.deprecated("Connection#peers", "Connection#connection_count")
|
|
355
|
+
@session.connection_count
|
|
222
356
|
end
|
|
223
357
|
|
|
358
|
+
# The Zenoh IDs of the peers / routers connected now.
|
|
359
|
+
def peer_zids = @session.peer_zids
|
|
360
|
+
def router_zids = @session.router_zids
|
|
361
|
+
|
|
362
|
+
# What is connected now: Arrays of Transport / Link.
|
|
363
|
+
def transports = @session.transports
|
|
364
|
+
def links = @session.links
|
|
365
|
+
|
|
366
|
+
# A new Timestamp from the session's clock (put(timestamp:)).
|
|
367
|
+
def new_timestamp = @session.new_timestamp
|
|
368
|
+
|
|
369
|
+
# A KeyExpr declared on the session (use it as the key afterwards).
|
|
370
|
+
def declare_keyexpr(key) = @session.declare_keyexpr(key)
|
|
371
|
+
|
|
224
372
|
# Stops receiving and closes the session (with everything declared on
|
|
225
373
|
# it). Idempotent.
|
|
226
374
|
def close
|
|
@@ -235,36 +383,54 @@ module Asterism
|
|
|
235
383
|
|
|
236
384
|
private
|
|
237
385
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
386
|
+
# The block form (get_each, the count) or a GetEnumerator over it.
|
|
387
|
+
def get_run(start, errors, what, warn, &blk)
|
|
388
|
+
return get_each(start, errors, what, warn, nil, &blk) if blk
|
|
389
|
+
e = nil
|
|
390
|
+
e = GetEnumerator.new { |y| get_each(start, errors, what, warn, e) { |r| y << r } }
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
# Sends the get (start) and yields each Reply; error replies only
|
|
394
|
+
# with errors. The done? before taking the replies: once the get is
|
|
395
|
+
# done, every reply it had is already in the queue. Warns once when
|
|
396
|
+
# replies were dropped (warn: depth left at the default); stats (a
|
|
397
|
+
# GetEnumerator or nil) gets the counts.
|
|
398
|
+
def get_each(start, errors, what = "get", warn = true, stats = nil)
|
|
399
|
+
g = start.call
|
|
241
400
|
n = 0
|
|
242
401
|
loop do
|
|
243
|
-
|
|
402
|
+
done = g.done?
|
|
403
|
+
got = g.each_result
|
|
244
404
|
got.each do |r|
|
|
405
|
+
next if r.error? && !errors
|
|
245
406
|
n += 1
|
|
246
|
-
yield
|
|
407
|
+
yield r
|
|
247
408
|
end
|
|
248
|
-
break if
|
|
409
|
+
break if done && g.pending == 0
|
|
249
410
|
sleep(ENUM_STEP) if got.empty?
|
|
250
411
|
end
|
|
412
|
+
Zenoh.note_dropped(g, g, what, "replies") if warn
|
|
413
|
+
stats&.finish(g)
|
|
251
414
|
n
|
|
252
415
|
end
|
|
253
416
|
|
|
254
417
|
# A subscription of a Connection. Either its block gets the samples
|
|
255
418
|
# (on the receiving thread), or the application takes them: each (an
|
|
256
419
|
# Enumerator that waits for samples) or each_pending (what is there
|
|
257
|
-
# now, as the
|
|
420
|
+
# now, as the portable API).
|
|
258
421
|
class Subscription
|
|
259
422
|
include Enumerable
|
|
260
423
|
|
|
261
424
|
attr_reader :key, :subscriber
|
|
262
425
|
|
|
263
|
-
|
|
426
|
+
# warn: say once when samples were dropped (the depth was left at
|
|
427
|
+
# the default).
|
|
428
|
+
def initialize(conn, subscriber, key, warn: false)
|
|
264
429
|
@conn = conn
|
|
265
430
|
@subscriber = subscriber
|
|
266
431
|
@key = key
|
|
267
432
|
@task = nil
|
|
433
|
+
@warn = warn
|
|
268
434
|
end
|
|
269
435
|
|
|
270
436
|
def handle(&blk)
|
|
@@ -272,31 +438,50 @@ module Asterism
|
|
|
272
438
|
where = "subscribe #{@key}"
|
|
273
439
|
r = @conn.runner
|
|
274
440
|
@task = r.add do
|
|
275
|
-
got = @subscriber.
|
|
276
|
-
got.each { |
|
|
441
|
+
got = @subscriber.each_sample
|
|
442
|
+
got.each { |s| r.guard(where) { blk.call(s) } }
|
|
443
|
+
note_dropped unless got.empty?
|
|
277
444
|
got.size
|
|
278
445
|
end
|
|
279
446
|
self
|
|
280
447
|
end
|
|
281
448
|
|
|
449
|
+
# @api private
|
|
450
|
+
def note_dropped
|
|
451
|
+
@warn && Zenoh.note_dropped(self, @subscriber, "subscribe #{@key}", "samples")
|
|
452
|
+
end
|
|
453
|
+
|
|
282
454
|
# Yields each Sample as it comes, waiting for the next; ends when the
|
|
283
455
|
# subscription or the session closes, or after timeout seconds.
|
|
284
456
|
# Without a block, an Enumerator (each.lazy.map { ... }.first(3)).
|
|
285
457
|
def each(timeout: nil, &blk)
|
|
286
458
|
return enum_for(:each, timeout: timeout) unless blk
|
|
287
459
|
raise ::Asterism::Error, "this subscription has a block; its samples go there" if @task
|
|
288
|
-
Zenoh.drain(@subscriber, @conn.session, timeout)
|
|
460
|
+
Zenoh.drain(@subscriber, @conn.session, timeout, take: :each_sample) do |s|
|
|
461
|
+
note_dropped
|
|
462
|
+
blk.call(s)
|
|
463
|
+
end
|
|
289
464
|
self
|
|
290
465
|
end
|
|
291
466
|
|
|
292
|
-
# The samples there now as Samples (an Array), or yields them
|
|
293
|
-
|
|
294
|
-
|
|
467
|
+
# The samples there now as Samples (an Array), or yields them and
|
|
468
|
+
# returns their number. (The portable API's each_sample.)
|
|
469
|
+
def each_sample
|
|
470
|
+
got = @subscriber.each_sample
|
|
471
|
+
note_dropped unless got.empty?
|
|
295
472
|
return got unless block_given?
|
|
296
473
|
got.each { |s| yield s }
|
|
297
474
|
got.size
|
|
298
475
|
end
|
|
299
476
|
|
|
477
|
+
# Deprecated: each_sample. In the portable API each_pending gives
|
|
478
|
+
# [key, payload, attachment] Arrays; here it gave Samples, so one
|
|
479
|
+
# name had two shapes.
|
|
480
|
+
def each_pending(&blk)
|
|
481
|
+
::Asterism.deprecated("Connection::Subscription#each_pending", "each_sample")
|
|
482
|
+
each_sample(&blk)
|
|
483
|
+
end
|
|
484
|
+
|
|
300
485
|
def pending = @subscriber.pending
|
|
301
486
|
def received = @subscriber.received
|
|
302
487
|
def dropped = @subscriber.dropped
|
|
@@ -380,11 +565,14 @@ module Asterism
|
|
|
380
565
|
|
|
381
566
|
attr_reader :key, :watch
|
|
382
567
|
|
|
383
|
-
|
|
568
|
+
# warn: say once when changes were dropped (the depth was left at
|
|
569
|
+
# the default).
|
|
570
|
+
def initialize(conn, watch, key, warn: false)
|
|
384
571
|
@conn = conn
|
|
385
572
|
@watch = watch
|
|
386
573
|
@key = key
|
|
387
574
|
@task = nil
|
|
575
|
+
@warn = warn
|
|
388
576
|
end
|
|
389
577
|
|
|
390
578
|
def handle(&blk)
|
|
@@ -394,6 +582,7 @@ module Asterism
|
|
|
394
582
|
@task = r.add do
|
|
395
583
|
got = @watch.each_pending
|
|
396
584
|
got.each { |e| r.guard(where) { blk.call(e[0], e[1]) } }
|
|
585
|
+
note_dropped unless got.empty?
|
|
397
586
|
got.size
|
|
398
587
|
end
|
|
399
588
|
self
|
|
@@ -404,10 +593,24 @@ module Asterism
|
|
|
404
593
|
def each(timeout: nil, &blk)
|
|
405
594
|
return enum_for(:each, timeout: timeout) unless blk
|
|
406
595
|
raise ::Asterism::Error, "this watch has a block; its changes go there" if @task
|
|
407
|
-
Zenoh.drain(@watch, @conn.session, timeout)
|
|
596
|
+
Zenoh.drain(@watch, @conn.session, timeout) do |e|
|
|
597
|
+
note_dropped
|
|
598
|
+
blk.call(Liveliness.new(e[0], e[1]))
|
|
599
|
+
end
|
|
408
600
|
self
|
|
409
601
|
end
|
|
410
602
|
|
|
603
|
+
# @api private
|
|
604
|
+
def note_dropped
|
|
605
|
+
@warn && Zenoh.note_dropped(self, @watch, "liveliness_watch #{@key}", "changes")
|
|
606
|
+
end
|
|
607
|
+
|
|
608
|
+
# Changes waiting / received so far / dropped because the queue was
|
|
609
|
+
# full (the oldest go; the watch no longer knows which tokens are
|
|
610
|
+
# alive, so declare it again).
|
|
611
|
+
def pending = @watch.pending
|
|
612
|
+
def received = @watch.received
|
|
613
|
+
def dropped = @watch.dropped
|
|
411
614
|
def closed? = @watch.closed?
|
|
412
615
|
|
|
413
616
|
def close
|
|
@@ -417,6 +620,159 @@ module Asterism
|
|
|
417
620
|
nil
|
|
418
621
|
end
|
|
419
622
|
end
|
|
623
|
+
|
|
624
|
+
# A publisher (or advanced publisher) of a Connection.
|
|
625
|
+
class Publisher
|
|
626
|
+
attr_reader :key, :publisher
|
|
627
|
+
|
|
628
|
+
def initialize(conn, publisher, key)
|
|
629
|
+
@conn = conn
|
|
630
|
+
@publisher = publisher
|
|
631
|
+
@key = key.to_s
|
|
632
|
+
@tasks = []
|
|
633
|
+
end
|
|
634
|
+
|
|
635
|
+
# opts: attachment:, encoding:, timestamp:. A payload that is not a
|
|
636
|
+
# String is sent as to_s.
|
|
637
|
+
def put(payload, **opts)
|
|
638
|
+
@publisher.put(Zenoh.bytes(payload), **opts)
|
|
639
|
+
nil
|
|
640
|
+
end
|
|
641
|
+
|
|
642
|
+
def delete(**opts)
|
|
643
|
+
@publisher.delete(**opts)
|
|
644
|
+
nil
|
|
645
|
+
end
|
|
646
|
+
|
|
647
|
+
# True when a subscriber matches the key now.
|
|
648
|
+
def matching? = @publisher.matching?
|
|
649
|
+
|
|
650
|
+
# on_matching { |listening| }: true when the first subscriber
|
|
651
|
+
# appears, false when the last one goes, on the receiving thread.
|
|
652
|
+
def on_matching(depth: 16, &blk)
|
|
653
|
+
raise ArgumentError, "a block is needed" unless blk
|
|
654
|
+
@tasks << Events.new(@conn, @publisher.matching_listener(depth), "on_matching #{@key}", blk)
|
|
655
|
+
self
|
|
656
|
+
end
|
|
657
|
+
|
|
658
|
+
def closed? = @publisher.closed?
|
|
659
|
+
|
|
660
|
+
def close
|
|
661
|
+
@tasks.each(&:close)
|
|
662
|
+
@tasks.clear
|
|
663
|
+
@publisher.close
|
|
664
|
+
nil
|
|
665
|
+
end
|
|
666
|
+
|
|
667
|
+
def inspect
|
|
668
|
+
"#<Asterism::Zenoh::Connection::Publisher #{@key}>"
|
|
669
|
+
end
|
|
670
|
+
end
|
|
671
|
+
|
|
672
|
+
# A querier of a Connection: the same get, sent again and again.
|
|
673
|
+
class Querier
|
|
674
|
+
attr_reader :key, :querier
|
|
675
|
+
|
|
676
|
+
def initialize(conn, querier, key)
|
|
677
|
+
@conn = conn
|
|
678
|
+
@querier = querier
|
|
679
|
+
@key = key.to_s
|
|
680
|
+
@tasks = []
|
|
681
|
+
end
|
|
682
|
+
|
|
683
|
+
# Yields each Reply until every answer came or the querier's time
|
|
684
|
+
# ran out; returns their number. errors: true also yields the error
|
|
685
|
+
# replies. depth: as Connection#get. Without a block, a
|
|
686
|
+
# GetEnumerator.
|
|
687
|
+
def get(params: nil, payload: nil, attachment: nil, encoding: nil, errors: false, depth: nil, &blk)
|
|
688
|
+
opts = { attachment: attachment, encoding: encoding }
|
|
689
|
+
opts[:depth] = depth unless depth.nil?
|
|
690
|
+
start = -> { @querier.get(params, payload, **opts) }
|
|
691
|
+
@conn.send(:get_run, start, errors, "querier get #{@key}", depth.nil?, &blk)
|
|
692
|
+
end
|
|
693
|
+
|
|
694
|
+
def matching? = @querier.matching?
|
|
695
|
+
|
|
696
|
+
# on_matching { |answered| }: true when a queryable matches, false
|
|
697
|
+
# when none does any more.
|
|
698
|
+
def on_matching(depth: 16, &blk)
|
|
699
|
+
raise ArgumentError, "a block is needed" unless blk
|
|
700
|
+
@tasks << Events.new(@conn, @querier.matching_listener(depth), "on_matching #{@key}", blk)
|
|
701
|
+
self
|
|
702
|
+
end
|
|
703
|
+
|
|
704
|
+
def closed? = @querier.closed?
|
|
705
|
+
|
|
706
|
+
def close
|
|
707
|
+
@tasks.each(&:close)
|
|
708
|
+
@tasks.clear
|
|
709
|
+
@querier.close
|
|
710
|
+
nil
|
|
711
|
+
end
|
|
712
|
+
end
|
|
713
|
+
|
|
714
|
+
# A subscription with history and recovery (advanced_subscriber).
|
|
715
|
+
class AdvancedSubscription < Subscription
|
|
716
|
+
def initialize(conn, subscriber, key)
|
|
717
|
+
super
|
|
718
|
+
@extra = []
|
|
719
|
+
end
|
|
720
|
+
|
|
721
|
+
# on_publisher { |key, alive| }: the advanced publishers on the key
|
|
722
|
+
# that declared publisher_detection, as they come and go (the ones
|
|
723
|
+
# there now first).
|
|
724
|
+
def on_publisher(depth: 16, history: true, &blk)
|
|
725
|
+
raise ArgumentError, "a block is needed" unless blk
|
|
726
|
+
w = Watch.new(@conn, @subscriber.detect_publishers(depth, history: history), @key)
|
|
727
|
+
w.handle(&blk)
|
|
728
|
+
@extra << w
|
|
729
|
+
self
|
|
730
|
+
end
|
|
731
|
+
|
|
732
|
+
# on_miss { |miss| }: samples that never came (Miss: source_zid,
|
|
733
|
+
# source_eid, count); needs sample_miss_detection on the publisher.
|
|
734
|
+
def on_miss(depth: 16, &blk)
|
|
735
|
+
raise ArgumentError, "a block is needed" unless blk
|
|
736
|
+
@extra << Events.new(@conn, @subscriber.miss_listener(depth), "on_miss #{@key}", blk)
|
|
737
|
+
self
|
|
738
|
+
end
|
|
739
|
+
|
|
740
|
+
def close
|
|
741
|
+
@extra.each(&:close)
|
|
742
|
+
@extra.clear
|
|
743
|
+
super
|
|
744
|
+
end
|
|
745
|
+
|
|
746
|
+
def inspect
|
|
747
|
+
"#<Asterism::Zenoh::Connection::AdvancedSubscription #{@key}#{@task ? ' (block)' : ''}>"
|
|
748
|
+
end
|
|
749
|
+
end
|
|
750
|
+
|
|
751
|
+
# The values of a listener (MatchingListener or EventListener) given
|
|
752
|
+
# to a block on the receiving thread. close stops it.
|
|
753
|
+
class Events
|
|
754
|
+
attr_reader :listener
|
|
755
|
+
|
|
756
|
+
def initialize(conn, listener, where, blk)
|
|
757
|
+
@conn = conn
|
|
758
|
+
@listener = listener
|
|
759
|
+
r = conn.runner
|
|
760
|
+
@task = r.add do
|
|
761
|
+
got = @listener.each_pending
|
|
762
|
+
got.each { |v| r.guard(where) { blk.call(v) } }
|
|
763
|
+
got.size
|
|
764
|
+
end
|
|
765
|
+
end
|
|
766
|
+
|
|
767
|
+
def closed? = @listener.closed?
|
|
768
|
+
|
|
769
|
+
def close
|
|
770
|
+
@conn.runner.remove(@task) if @task
|
|
771
|
+
@task = nil
|
|
772
|
+
@listener.close
|
|
773
|
+
nil
|
|
774
|
+
end
|
|
775
|
+
end
|
|
420
776
|
end
|
|
421
777
|
end
|
|
422
778
|
end
|
data/lib/asterism/version.rb
CHANGED