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.rb
CHANGED
|
@@ -16,8 +16,8 @@ require_relative "asterism/version"
|
|
|
16
16
|
require_relative "../mrblib/#{f}"
|
|
17
17
|
end
|
|
18
18
|
require_relative "asterism/cruby"
|
|
19
|
-
# The
|
|
20
|
-
# pattern matching): CRuby only, see README "The
|
|
19
|
+
# The CRuby API on top (blocks, a receiving thread, Enumerators,
|
|
20
|
+
# pattern matching): CRuby only, see README "The CRuby API".
|
|
21
21
|
%w[runner zenoh objects ros].each do |f|
|
|
22
22
|
require_relative "asterism/cruby/#{f}"
|
|
23
23
|
end
|
data/mrblib/asterism.rb
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
# Asterism.connect("tcp/192.0.2.2:7447", node: "fmruby-bbbbbb", app: "demo")
|
|
4
4
|
# Asterism.expose("apu", apu, methods: [:play, :stop])
|
|
5
5
|
# p = Asterism["linux/demo/apu"] # <node>/<app>/<object>
|
|
6
|
-
# p.play("cde") # waits for the answer (2 s by default
|
|
6
|
+
# p.play("cde") # waits for the answer (2 s by default;
|
|
7
|
+
# # Asterism["...", timeout: 5.0])
|
|
7
8
|
# Asterism.poll # call regularly (answers incoming calls)
|
|
8
9
|
#
|
|
9
10
|
# Keys (doc/ruby_asterism/design.md ch. 4):
|
|
@@ -19,13 +20,21 @@
|
|
|
19
20
|
# that come in, so two machines calling each other do not lock up.
|
|
20
21
|
module Asterism
|
|
21
22
|
ROOT = "asterism"
|
|
23
|
+
# The time limit of a call (Asterism[path] without timeout:), in seconds
|
|
24
|
+
# and in milliseconds.
|
|
25
|
+
DEFAULT_TIMEOUT = 2.0
|
|
22
26
|
DEFAULT_TIMEOUT_MS = 2000
|
|
27
|
+
# How long Asterism.connect looks for another copy of the same
|
|
28
|
+
# <node>/<app> (check_timeout:), in seconds.
|
|
29
|
+
CHECK_TIMEOUT = 1.5
|
|
23
30
|
# Waits that may be open inside each other (a call answered while waiting
|
|
24
31
|
# that itself calls and waits, and so on).
|
|
25
32
|
MAX_NESTING = 4
|
|
26
33
|
# Pause between polls while a call waits (ms).
|
|
27
34
|
WAIT_STEP_MS = 2
|
|
28
35
|
|
|
36
|
+
# Every Asterism error is an Asterism::Error (the Zenoh bindings define
|
|
37
|
+
# it; Asterism::Zenoh::Error and the ROS errors are under it too).
|
|
29
38
|
class Error < StandardError; end
|
|
30
39
|
|
|
31
40
|
# A value that MessagePack cannot carry (only nil, true, false, Integer,
|
|
@@ -33,7 +42,9 @@ module Asterism
|
|
|
33
42
|
class EncodeError < Error; end
|
|
34
43
|
|
|
35
44
|
# No answer within the time limit (the object is gone, or slow).
|
|
36
|
-
|
|
45
|
+
# Asterism::ROS::TimeoutError is one too. (Its old name Asterism::Timeout
|
|
46
|
+
# still works, with a deprecation warning.)
|
|
47
|
+
class TimeoutError < Error; end
|
|
37
48
|
|
|
38
49
|
# The connection is closed or was lost (Asterism::Zenoh::Error inside).
|
|
39
50
|
class Disconnected < Error; end
|
|
@@ -50,6 +61,17 @@ module Asterism
|
|
|
50
61
|
end
|
|
51
62
|
end
|
|
52
63
|
|
|
64
|
+
# The old names of renamed constants: Asterism::Timeout (deprecated) is
|
|
65
|
+
# TimeoutError. A constant alias would shadow Ruby's Timeout module inside
|
|
66
|
+
# `module Asterism`, and cannot warn.
|
|
67
|
+
def self.const_missing(name)
|
|
68
|
+
if name == :Timeout
|
|
69
|
+
deprecated("Asterism::Timeout", "Asterism::TimeoutError")
|
|
70
|
+
return TimeoutError
|
|
71
|
+
end
|
|
72
|
+
super
|
|
73
|
+
end
|
|
74
|
+
|
|
53
75
|
# ------------------------------------------------------------ values
|
|
54
76
|
|
|
55
77
|
module Codec
|
|
@@ -91,6 +113,7 @@ module Asterism
|
|
|
91
113
|
|
|
92
114
|
# Milliseconds and a short pause. PicoRuby's Machine when it is there,
|
|
93
115
|
# otherwise Time and sleep.
|
|
116
|
+
# @api private
|
|
94
117
|
def self.now_ms
|
|
95
118
|
if machine?
|
|
96
119
|
::Machine.board_millis
|
|
@@ -99,6 +122,7 @@ module Asterism
|
|
|
99
122
|
end
|
|
100
123
|
end
|
|
101
124
|
|
|
125
|
+
# @api private
|
|
102
126
|
def self.pause(ms)
|
|
103
127
|
if machine?
|
|
104
128
|
::Machine.delay_ms(ms)
|
|
@@ -107,6 +131,7 @@ module Asterism
|
|
|
107
131
|
end
|
|
108
132
|
end
|
|
109
133
|
|
|
134
|
+
# @api private
|
|
110
135
|
def self.machine?
|
|
111
136
|
@machine = (Object.const_defined?(:Machine) && ::Machine.respond_to?(:delay_ms)) if @machine.nil?
|
|
112
137
|
@machine
|
|
@@ -116,6 +141,7 @@ module Asterism
|
|
|
116
141
|
|
|
117
142
|
# A node, app or object name: one key chunk, no wildcard or special
|
|
118
143
|
# characters.
|
|
144
|
+
# @api private
|
|
119
145
|
def self.check_name(what, name)
|
|
120
146
|
s = name.to_s
|
|
121
147
|
bad = s.empty? || s.start_with?("@")
|
|
@@ -125,6 +151,7 @@ module Asterism
|
|
|
125
151
|
end
|
|
126
152
|
|
|
127
153
|
# "node/app/object" -> [node, app, object]
|
|
154
|
+
# @api private
|
|
128
155
|
def self.split_path(path)
|
|
129
156
|
parts = path.to_s.split("/")
|
|
130
157
|
raise ArgumentError, "expected <node>/<app>/<object>, got #{path.inspect}" unless parts.size == 3
|
|
@@ -135,10 +162,12 @@ module Asterism
|
|
|
135
162
|
end
|
|
136
163
|
|
|
137
164
|
# Glob over "/"-separated chunks: * is one chunk, ** any number of them.
|
|
165
|
+
# @api private
|
|
138
166
|
def self.glob?(pattern, path)
|
|
139
167
|
glob_parts?(pattern.split("/"), 0, path.split("/"), 0)
|
|
140
168
|
end
|
|
141
169
|
|
|
170
|
+
# @api private
|
|
142
171
|
def self.glob_parts?(pat, i, parts, j)
|
|
143
172
|
while i < pat.size
|
|
144
173
|
if pat[i] == "**"
|
|
@@ -161,27 +190,37 @@ module Asterism
|
|
|
161
190
|
|
|
162
191
|
# Opens the connection. locator, mode: and listen: go to
|
|
163
192
|
# Asterism::Zenoh::Session.open (client of a router by default; mode: :peer
|
|
164
|
-
# with or without listen: for no router)
|
|
165
|
-
#
|
|
166
|
-
#
|
|
167
|
-
|
|
193
|
+
# with or without listen: for no router), and so do config: (a Hash of
|
|
194
|
+
# "key/path" => value or a JSON5 String, e.g. TLS certificates) and
|
|
195
|
+
# connect_timeout: (seconds) when they are given (CRuby's asterism-zenoh
|
|
196
|
+
# only; the boards raise ArgumentError). node: is this machine's ID and
|
|
197
|
+
# app: this application's name. check_timeout: how long to look for
|
|
198
|
+
# another copy of the same <node>/<app> (seconds, CHECK_TIMEOUT). Raises
|
|
199
|
+
# Disconnected when it cannot connect, and Error when the same
|
|
200
|
+
# <node>/<app> is already on the network.
|
|
201
|
+
def self.connect(locator, node:, app:, mode: nil, listen: nil, config: nil, connect_timeout: nil,
|
|
202
|
+
check_timeout: nil)
|
|
168
203
|
raise Error, "already connected (Asterism.close first)" if connected?
|
|
169
204
|
release
|
|
170
205
|
node = check_name("node", node)
|
|
171
206
|
app = check_name("app", app)
|
|
207
|
+
check_ms = time_ms("Asterism.connect check_timeout:", check_timeout, nil, nil, (CHECK_TIMEOUT * 1000).round)
|
|
208
|
+
opts = { mode: mode, listen: listen }
|
|
209
|
+
opts[:config] = config unless config.nil?
|
|
210
|
+
opts[:connect_timeout] = connect_timeout unless connect_timeout.nil?
|
|
172
211
|
begin
|
|
173
|
-
s = Asterism::Zenoh::Session.open(locator,
|
|
212
|
+
s = Asterism::Zenoh::Session.open(locator, **opts)
|
|
174
213
|
rescue Asterism::Zenoh::Error => e
|
|
175
214
|
raise Disconnected, e.message
|
|
176
215
|
end
|
|
177
216
|
begin
|
|
178
|
-
if already_there?(s, node, app)
|
|
217
|
+
if already_there?(s, node, app, check_ms)
|
|
179
218
|
s.close
|
|
180
219
|
raise Error, "#{node}/#{app} is already on the network"
|
|
181
220
|
end
|
|
182
221
|
@node_token = s.liveliness("#{ROOT}/#{node}")
|
|
183
|
-
@queryable = s.queryable("#{ROOT}/#{node}/#{app}/**", 32)
|
|
184
|
-
@watch = s.liveliness_watch("#{ROOT}/**",
|
|
222
|
+
@queryable = s.queryable("#{ROOT}/#{node}/#{app}/**", depth: 32)
|
|
223
|
+
@watch = s.liveliness_watch("#{ROOT}/**", depth: watch_depth)
|
|
185
224
|
rescue Asterism::Zenoh::Error => e
|
|
186
225
|
s.close
|
|
187
226
|
raise Disconnected, e.message
|
|
@@ -200,16 +239,18 @@ module Asterism
|
|
|
200
239
|
end
|
|
201
240
|
|
|
202
241
|
# Whether objects of node/app are alive already (a second copy of the
|
|
203
|
-
# same application). Waits for the answer (a router answers at once)
|
|
204
|
-
|
|
205
|
-
|
|
242
|
+
# same application). Waits for the answer (a router answers at once), at
|
|
243
|
+
# most check_ms. @api private
|
|
244
|
+
def self.already_there?(s, node, app, check_ms = 1500)
|
|
245
|
+
get_ms = check_ms > 1000 ? check_ms - 500 : check_ms
|
|
246
|
+
g = s.liveliness_get("#{ROOT}/#{node}/#{app}/**", timeout_ms: get_ms)
|
|
206
247
|
t0 = now_ms
|
|
207
248
|
found = false
|
|
208
249
|
while true
|
|
209
250
|
s.poll
|
|
210
251
|
ended = g.done? # before taking the replies (see Future#collect)
|
|
211
252
|
found = true if g.each_reply.size > 0
|
|
212
|
-
break if found || ended || now_ms - t0 >
|
|
253
|
+
break if found || ended || now_ms - t0 > check_ms
|
|
213
254
|
pause(WAIT_STEP_MS)
|
|
214
255
|
end
|
|
215
256
|
found
|
|
@@ -247,6 +288,7 @@ module Asterism
|
|
|
247
288
|
nil
|
|
248
289
|
end
|
|
249
290
|
|
|
291
|
+
# @api private
|
|
250
292
|
def self.release
|
|
251
293
|
s = @session
|
|
252
294
|
@session = nil
|
|
@@ -260,6 +302,7 @@ module Asterism
|
|
|
260
302
|
@watch = nil
|
|
261
303
|
end
|
|
262
304
|
|
|
305
|
+
# @api private
|
|
263
306
|
def self.session!
|
|
264
307
|
raise Disconnected, (@lost || "not connected") unless @session
|
|
265
308
|
@session
|
|
@@ -267,6 +310,7 @@ module Asterism
|
|
|
267
310
|
|
|
268
311
|
# The connection went away: closed, but the next Asterism.poll still tells
|
|
269
312
|
# on_leave about every node known until then.
|
|
313
|
+
# @api private
|
|
270
314
|
def self.lost(reason)
|
|
271
315
|
@lost = reason
|
|
272
316
|
release
|
|
@@ -341,8 +385,22 @@ module Asterism
|
|
|
341
385
|
self
|
|
342
386
|
end
|
|
343
387
|
|
|
388
|
+
# Removes a block given to on_join / on_leave (keep the Proc to remove
|
|
389
|
+
# it: h = proc { |n| ... }; Asterism.on_join(&h); Asterism.off_join(h)).
|
|
390
|
+
# on_* adds a block; on_error (CRuby) is the only one that replaces.
|
|
391
|
+
def self.off_join(blk)
|
|
392
|
+
@on_join.delete(blk) if @on_join
|
|
393
|
+
self
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
def self.off_leave(blk)
|
|
397
|
+
@on_leave.delete(blk) if @on_leave
|
|
398
|
+
self
|
|
399
|
+
end
|
|
400
|
+
|
|
344
401
|
# Compares the nodes alive now with those told before and calls the
|
|
345
402
|
# blocks for the difference.
|
|
403
|
+
# @api private
|
|
346
404
|
def self.tell_nodes
|
|
347
405
|
known = @known
|
|
348
406
|
return if known.nil?
|
|
@@ -360,6 +418,7 @@ module Asterism
|
|
|
360
418
|
|
|
361
419
|
# While loops, not each with a block: the blocks are the application's,
|
|
362
420
|
# and nothing else stands between them and the update loop on the stack.
|
|
421
|
+
# @api private
|
|
363
422
|
def self.tell_each(where, blocks, names)
|
|
364
423
|
return if blocks.nil? || names.empty?
|
|
365
424
|
i = 0
|
|
@@ -374,10 +433,12 @@ module Asterism
|
|
|
374
433
|
end
|
|
375
434
|
|
|
376
435
|
# One block for one node (CRuby routes what it raises to on_error).
|
|
436
|
+
# @api private
|
|
377
437
|
def self.tell(_where, blk, node)
|
|
378
438
|
blk.call(node)
|
|
379
439
|
end
|
|
380
440
|
|
|
441
|
+
# @api private
|
|
381
442
|
def self.pump
|
|
382
443
|
s = @session
|
|
383
444
|
return false unless s
|
|
@@ -394,6 +455,7 @@ module Asterism
|
|
|
394
455
|
end
|
|
395
456
|
|
|
396
457
|
# Polls until the block is true. Answers incoming calls meanwhile.
|
|
458
|
+
# @api private
|
|
397
459
|
def self.wait_until
|
|
398
460
|
raise Error, "calls nested too deep (max #{MAX_NESTING})" if (@depth || 0) >= MAX_NESTING
|
|
399
461
|
@depth = (@depth || 0) + 1
|
|
@@ -408,15 +470,31 @@ module Asterism
|
|
|
408
470
|
end
|
|
409
471
|
end
|
|
410
472
|
|
|
473
|
+
# @api private
|
|
411
474
|
def self.depth
|
|
412
475
|
(@depth || 0)
|
|
413
476
|
end
|
|
414
477
|
|
|
478
|
+
# The depth of the liveliness watch behind each / nodes. The tokens alive
|
|
479
|
+
# when it is declared (every node and object on the network) come in one
|
|
480
|
+
# burst: 64 on the boards (the ring comes from the VM's pool), the
|
|
481
|
+
# binding's deeper default where it has one (CRuby: 1024).
|
|
482
|
+
# @api private
|
|
483
|
+
def self.watch_depth
|
|
484
|
+
d = Asterism::Zenoh::DEFAULT_WATCH_DEPTH
|
|
485
|
+
d > 64 ? d : 64
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
# @api private
|
|
415
489
|
def self.follow_liveliness
|
|
416
490
|
w = @watch
|
|
417
491
|
return unless w
|
|
418
492
|
# Array forms instead of blocks called from C (see Future#collect).
|
|
419
493
|
events = w.each_pending
|
|
494
|
+
if events.size > 0 && w.dropped > 0
|
|
495
|
+
warn_once(w, "asterism: #{w.dropped} liveliness changes dropped (more than #{watch_depth} at once); " \
|
|
496
|
+
"Asterism.each and Asterism.nodes may miss some objects")
|
|
497
|
+
end
|
|
420
498
|
i = 0
|
|
421
499
|
while i < events.size
|
|
422
500
|
key = events[i][0]
|
|
@@ -454,7 +532,9 @@ module Asterism
|
|
|
454
532
|
|
|
455
533
|
# The exposed objects alive now whose <node>/<app>/<object> matches the
|
|
456
534
|
# pattern (* is one chunk, ** any number), this application's own
|
|
457
|
-
# included. With a block, yields a proxy for each
|
|
535
|
+
# included. With a block, yields a proxy for each and returns their
|
|
536
|
+
# number (1.0 will return the proxies instead, as Ruby's each returns
|
|
537
|
+
# its receiver; use the Array form for the count); else returns them.
|
|
458
538
|
def self.each(pattern = "**")
|
|
459
539
|
paths = []
|
|
460
540
|
if @session
|
|
@@ -471,8 +551,12 @@ module Asterism
|
|
|
471
551
|
end
|
|
472
552
|
|
|
473
553
|
# A proxy for <node>/<app>/<object>. Nothing is sent until a method is
|
|
474
|
-
# called on it.
|
|
475
|
-
|
|
554
|
+
# called on it. timeout: the time limit of its calls in seconds (2.0), or
|
|
555
|
+
# timeout_ms:. (Asterism[path, ms], the time as a positional argument, is
|
|
556
|
+
# deprecated.)
|
|
557
|
+
def self.[](path, *args, timeout: nil, timeout_ms: nil)
|
|
558
|
+
raise ArgumentError, "Asterism[]: wrong number of arguments (given #{args.size + 1}, expected 1)" if args.size > 1
|
|
559
|
+
timeout_ms = time_ms("Asterism[]", timeout, timeout_ms, args[0], DEFAULT_TIMEOUT_MS)
|
|
476
560
|
split_path(path)
|
|
477
561
|
@proxies ||= {}
|
|
478
562
|
key = "#{path}|#{timeout_ms}"
|
|
@@ -483,6 +567,7 @@ module Asterism
|
|
|
483
567
|
|
|
484
568
|
# Starts a call; returns a Future. Raises EncodeError (before sending)
|
|
485
569
|
# and Disconnected.
|
|
570
|
+
# @api private
|
|
486
571
|
def self.call_async(path, method, args, kwargs, timeout_ms)
|
|
487
572
|
parts = split_path(path)
|
|
488
573
|
payload = Codec.pack([method.to_s, args, kwargs])
|
|
@@ -492,7 +577,7 @@ module Asterism
|
|
|
492
577
|
return Future.answered(path, method, dispatch(parts[2], payload))
|
|
493
578
|
end
|
|
494
579
|
begin
|
|
495
|
-
g = session!.get("#{ROOT}/#{path}/call", timeout_ms
|
|
580
|
+
g = session!.get("#{ROOT}/#{path}/call", timeout_ms: timeout_ms, payload: payload)
|
|
496
581
|
rescue Asterism::Zenoh::Error => e
|
|
497
582
|
lost(e.message)
|
|
498
583
|
raise Disconnected, e.message
|
|
@@ -500,11 +585,13 @@ module Asterism
|
|
|
500
585
|
Future.new(path, method, g, now_ms + timeout_ms, timeout_ms)
|
|
501
586
|
end
|
|
502
587
|
|
|
588
|
+
# @api private
|
|
503
589
|
def self.call(path, method, args, kwargs, timeout_ms)
|
|
504
590
|
call_async(path, method, args, kwargs, timeout_ms).value
|
|
505
591
|
end
|
|
506
592
|
|
|
507
593
|
# The meta reply of path: {"methods" => [[name, arity], ...]}.
|
|
594
|
+
# @api private
|
|
508
595
|
def self.meta(path, timeout_ms = DEFAULT_TIMEOUT_MS)
|
|
509
596
|
parts = split_path(path)
|
|
510
597
|
if parts[0] == @node && parts[1] == @app && @session
|
|
@@ -513,7 +600,7 @@ module Asterism
|
|
|
513
600
|
return m
|
|
514
601
|
end
|
|
515
602
|
begin
|
|
516
|
-
g = session!.get("#{ROOT}/#{path}/meta", timeout_ms)
|
|
603
|
+
g = session!.get("#{ROOT}/#{path}/meta", timeout_ms: timeout_ms)
|
|
517
604
|
rescue Asterism::Zenoh::Error => e
|
|
518
605
|
lost(e.message)
|
|
519
606
|
raise Disconnected, e.message
|
|
@@ -524,6 +611,7 @@ module Asterism
|
|
|
524
611
|
|
|
525
612
|
# ------------------------------------------------------------ answering
|
|
526
613
|
|
|
614
|
+
# @api private
|
|
527
615
|
def self.answer_calls
|
|
528
616
|
qa = @queryable
|
|
529
617
|
return unless qa
|
|
@@ -548,6 +636,7 @@ module Asterism
|
|
|
548
636
|
|
|
549
637
|
# asterism/<node>/<app>/<object>/<call|meta>; the node and app may come
|
|
550
638
|
# as wildcards (the query matched this application's queryable).
|
|
639
|
+
# @api private
|
|
551
640
|
def self.answer(q)
|
|
552
641
|
parts = q.key.split("/")
|
|
553
642
|
return unless parts.size == 5
|
|
@@ -565,6 +654,7 @@ module Asterism
|
|
|
565
654
|
end
|
|
566
655
|
end
|
|
567
656
|
|
|
657
|
+
# @api private
|
|
568
658
|
def self.meta_of(name)
|
|
569
659
|
entry = @objects[name]
|
|
570
660
|
return nil unless entry
|
|
@@ -574,6 +664,7 @@ module Asterism
|
|
|
574
664
|
end
|
|
575
665
|
|
|
576
666
|
# Runs a call on an exposed object; returns the packed reply.
|
|
667
|
+
# @api private
|
|
577
668
|
def self.dispatch(obj_name, payload)
|
|
578
669
|
req = Codec.unpack(payload)
|
|
579
670
|
unless req.is_a?(Array) && req.size == 3 && req[0].is_a?(String) && req[1].is_a?(Array) && req[2].is_a?(Hash)
|
|
@@ -601,6 +692,7 @@ module Asterism
|
|
|
601
692
|
end
|
|
602
693
|
end
|
|
603
694
|
|
|
695
|
+
# @api private
|
|
604
696
|
def self.invoke(obj, name, args, kw)
|
|
605
697
|
if kw.empty?
|
|
606
698
|
obj.public_send(name, *args)
|
|
@@ -611,6 +703,7 @@ module Asterism
|
|
|
611
703
|
end
|
|
612
704
|
end
|
|
613
705
|
|
|
706
|
+
# @api private
|
|
614
707
|
def self.error_reply(klass, message)
|
|
615
708
|
::MessagePack.pack(["error", klass.to_s, message.to_s])
|
|
616
709
|
end
|
data/mrblib/cdr.rb
CHANGED
|
@@ -18,9 +18,13 @@
|
|
|
18
18
|
# String#unpack when the VM has them (mruby-pack, CRuby) and fall back to
|
|
19
19
|
# arithmetic (CDR.f64_bytes and friends) when it does not.
|
|
20
20
|
module Asterism
|
|
21
|
+
# (asterism.rb and the Zenoh binding define it too; this file is also
|
|
22
|
+
# loaded on its own by the type tools.)
|
|
23
|
+
class Error < StandardError; end
|
|
24
|
+
|
|
21
25
|
module CDR
|
|
22
26
|
# Malformed or truncated CDR.
|
|
23
|
-
class DecodeError < ::
|
|
27
|
+
class DecodeError < ::Asterism::Error; end
|
|
24
28
|
|
|
25
29
|
HEADER_LE = "\x00\x01\x00\x00".freeze
|
|
26
30
|
HEADER_BE = "\x00\x00\x00\x00".freeze
|
data/mrblib/future.rb
CHANGED
|
@@ -5,6 +5,7 @@ module Asterism
|
|
|
5
5
|
class Future
|
|
6
6
|
attr_reader :path, :method_name
|
|
7
7
|
|
|
8
|
+
# @api private
|
|
8
9
|
def self.answered(path, method, reply)
|
|
9
10
|
f = new(path, method, nil, 0, 0)
|
|
10
11
|
f.set_reply(reply)
|
|
@@ -23,6 +24,7 @@ module Asterism
|
|
|
23
24
|
@took = nil
|
|
24
25
|
end
|
|
25
26
|
|
|
27
|
+
# @api private
|
|
26
28
|
def set_reply(reply)
|
|
27
29
|
@reply = reply
|
|
28
30
|
@finished = true
|
|
@@ -34,6 +36,11 @@ module Asterism
|
|
|
34
36
|
@took
|
|
35
37
|
end
|
|
36
38
|
|
|
39
|
+
# The same in seconds (a Float; nil until done).
|
|
40
|
+
def took
|
|
41
|
+
@took.nil? ? nil : @took / 1000.0
|
|
42
|
+
end
|
|
43
|
+
|
|
37
44
|
# True once the answer came, the time ran out or the connection closed.
|
|
38
45
|
# Call Asterism.poll in the update loop for it to move on.
|
|
39
46
|
def done?
|
|
@@ -58,15 +65,17 @@ module Asterism
|
|
|
58
65
|
if @took && @took < @timeout_ms
|
|
59
66
|
# Finished early without a reply: nobody answers that key (the
|
|
60
67
|
# object is not exposed, or its application is gone).
|
|
61
|
-
raise ::Asterism::
|
|
68
|
+
raise ::Asterism::TimeoutError, "no answer from #{@path} #{@method_name} (nobody answers)"
|
|
62
69
|
end
|
|
63
|
-
raise ::Asterism::
|
|
70
|
+
raise ::Asterism::TimeoutError,
|
|
71
|
+
"no answer from #{@path} #{@method_name} within #{@timeout_ms / 1000.0} s (#{@timeout_ms} ms)"
|
|
64
72
|
end
|
|
65
73
|
raw = ::Asterism::Codec.unpack(@reply)
|
|
66
74
|
raise ::Asterism::Error, "malformed answer from #{@path}" if raw.nil?
|
|
67
75
|
raw
|
|
68
76
|
end
|
|
69
77
|
|
|
78
|
+
# @api private
|
|
70
79
|
def collect
|
|
71
80
|
return if @finished
|
|
72
81
|
g = @get
|
data/mrblib/proxy.rb
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
module Asterism
|
|
2
2
|
# A stand-in for an object on another machine (Asterism["node/app/obj"]).
|
|
3
3
|
# Any method it does not have itself becomes a call that waits for the
|
|
4
|
-
# answer. respond_to? and
|
|
4
|
+
# answer. respond_to? and remote_methods come from the object's meta.
|
|
5
5
|
#
|
|
6
6
|
# Its own methods (not forwarded): async, asterism_path, asterism_meta,
|
|
7
|
-
# asterism_refresh, respond_to?, methods, inspect, to_s
|
|
7
|
+
# asterism_refresh, remote_methods, respond_to?, methods, inspect, to_s
|
|
8
|
+
# and those of Object.
|
|
8
9
|
class Proxy
|
|
9
10
|
# Conversions Ruby asks for implicitly (puts, Array#flatten, ...): never
|
|
10
11
|
# forwarded.
|
|
@@ -39,10 +40,18 @@ module Asterism
|
|
|
39
40
|
|
|
40
41
|
# The exposed methods (Symbols), from the meta. Raises when the object
|
|
41
42
|
# does not answer.
|
|
42
|
-
def
|
|
43
|
+
def remote_methods
|
|
43
44
|
remote_names.map { |n| n.to_sym }
|
|
44
45
|
end
|
|
45
46
|
|
|
47
|
+
# Deprecated: returns remote_methods. From 1.0 it is Object#methods
|
|
48
|
+
# again (irb, pp and test doubles rely on it), and remote_methods is the
|
|
49
|
+
# way to list the remote ones.
|
|
50
|
+
def methods(*_args)
|
|
51
|
+
::Asterism.deprecated("Asterism::Proxy#methods", "Asterism::Proxy#remote_methods")
|
|
52
|
+
remote_methods
|
|
53
|
+
end
|
|
54
|
+
|
|
46
55
|
# The meta reply, fetched once: {"methods" => [[name, arity], ...]}.
|
|
47
56
|
def asterism_meta
|
|
48
57
|
@meta ||= ::Asterism.meta(@path, @timeout_ms)
|
|
@@ -54,6 +63,7 @@ module Asterism
|
|
|
54
63
|
self
|
|
55
64
|
end
|
|
56
65
|
|
|
66
|
+
# @api private
|
|
57
67
|
def remote_names
|
|
58
68
|
list = asterism_meta["methods"]
|
|
59
69
|
return [] unless list.is_a?(Array)
|