asterism 0.0.0 → 0.1.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.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +21 -0
  3. data/NOTICE +27 -0
  4. data/README.md +405 -3
  5. data/data/msgs/LICENSE-Apache-2.0.txt +202 -0
  6. data/data/msgs/NOTICE +17 -0
  7. data/data/msgs/builtin_interfaces/msg/Duration.rb +33 -0
  8. data/data/msgs/builtin_interfaces/msg/Time.rb +33 -0
  9. data/data/msgs/example_interfaces/srv/AddTwoInts.rb +64 -0
  10. data/data/msgs/geometry_msgs/msg/Accel.rb +35 -0
  11. data/data/msgs/geometry_msgs/msg/AccelStamped.rb +36 -0
  12. data/data/msgs/geometry_msgs/msg/Point.rb +36 -0
  13. data/data/msgs/geometry_msgs/msg/Point32.rb +36 -0
  14. data/data/msgs/geometry_msgs/msg/PointStamped.rb +36 -0
  15. data/data/msgs/geometry_msgs/msg/Pose.rb +36 -0
  16. data/data/msgs/geometry_msgs/msg/Pose2D.rb +36 -0
  17. data/data/msgs/geometry_msgs/msg/PoseStamped.rb +36 -0
  18. data/data/msgs/geometry_msgs/msg/Quaternion.rb +39 -0
  19. data/data/msgs/geometry_msgs/msg/QuaternionStamped.rb +36 -0
  20. data/data/msgs/geometry_msgs/msg/Transform.rb +36 -0
  21. data/data/msgs/geometry_msgs/msg/TransformStamped.rb +39 -0
  22. data/data/msgs/geometry_msgs/msg/Twist.rb +35 -0
  23. data/data/msgs/geometry_msgs/msg/TwistStamped.rb +36 -0
  24. data/data/msgs/geometry_msgs/msg/Vector3.rb +36 -0
  25. data/data/msgs/geometry_msgs/msg/Vector3Stamped.rb +36 -0
  26. data/data/msgs/geometry_msgs/msg/Wrench.rb +35 -0
  27. data/data/msgs/geometry_msgs/msg/WrenchStamped.rb +36 -0
  28. data/data/msgs/sensor_msgs/msg/BatteryState.rb +100 -0
  29. data/data/msgs/sensor_msgs/msg/FluidPressure.rb +38 -0
  30. data/data/msgs/sensor_msgs/msg/Illuminance.rb +38 -0
  31. data/data/msgs/sensor_msgs/msg/Imu.rb +52 -0
  32. data/data/msgs/sensor_msgs/msg/JointState.rb +44 -0
  33. data/data/msgs/sensor_msgs/msg/MagneticField.rb +39 -0
  34. data/data/msgs/sensor_msgs/msg/NavSatFix.rb +55 -0
  35. data/data/msgs/sensor_msgs/msg/NavSatStatus.rb +43 -0
  36. data/data/msgs/sensor_msgs/msg/Range.rb +52 -0
  37. data/data/msgs/sensor_msgs/msg/RelativeHumidity.rb +38 -0
  38. data/data/msgs/sensor_msgs/msg/Temperature.rb +38 -0
  39. data/data/msgs/std_msgs/msg/Bool.rb +30 -0
  40. data/data/msgs/std_msgs/msg/Byte.rb +30 -0
  41. data/data/msgs/std_msgs/msg/ByteMultiArray.rb +35 -0
  42. data/data/msgs/std_msgs/msg/Char.rb +30 -0
  43. data/data/msgs/std_msgs/msg/ColorRGBA.rb +39 -0
  44. data/data/msgs/std_msgs/msg/Empty.rb +26 -0
  45. data/data/msgs/std_msgs/msg/Float32.rb +30 -0
  46. data/data/msgs/std_msgs/msg/Float32MultiArray.rb +35 -0
  47. data/data/msgs/std_msgs/msg/Float64.rb +30 -0
  48. data/data/msgs/std_msgs/msg/Float64MultiArray.rb +35 -0
  49. data/data/msgs/std_msgs/msg/Header.rb +35 -0
  50. data/data/msgs/std_msgs/msg/Int16.rb +30 -0
  51. data/data/msgs/std_msgs/msg/Int16MultiArray.rb +35 -0
  52. data/data/msgs/std_msgs/msg/Int32.rb +30 -0
  53. data/data/msgs/std_msgs/msg/Int32MultiArray.rb +35 -0
  54. data/data/msgs/std_msgs/msg/Int64.rb +30 -0
  55. data/data/msgs/std_msgs/msg/Int64MultiArray.rb +35 -0
  56. data/data/msgs/std_msgs/msg/Int8.rb +30 -0
  57. data/data/msgs/std_msgs/msg/Int8MultiArray.rb +35 -0
  58. data/data/msgs/std_msgs/msg/MultiArrayDimension.rb +36 -0
  59. data/data/msgs/std_msgs/msg/MultiArrayLayout.rb +35 -0
  60. data/data/msgs/std_msgs/msg/String.rb +30 -0
  61. data/data/msgs/std_msgs/msg/UInt16.rb +30 -0
  62. data/data/msgs/std_msgs/msg/UInt16MultiArray.rb +35 -0
  63. data/data/msgs/std_msgs/msg/UInt32.rb +30 -0
  64. data/data/msgs/std_msgs/msg/UInt32MultiArray.rb +35 -0
  65. data/data/msgs/std_msgs/msg/UInt64.rb +30 -0
  66. data/data/msgs/std_msgs/msg/UInt64MultiArray.rb +35 -0
  67. data/data/msgs/std_msgs/msg/UInt8.rb +30 -0
  68. data/data/msgs/std_msgs/msg/UInt8MultiArray.rb +35 -0
  69. data/lib/asterism/cruby.rb +24 -0
  70. data/lib/asterism/version.rb +5 -0
  71. data/lib/asterism.rb +17 -4
  72. data/mrblib/asterism.rb +545 -0
  73. data/mrblib/cdr.rb +512 -0
  74. data/mrblib/future.rb +83 -0
  75. data/mrblib/proxy.rb +97 -0
  76. data/mrblib/ros.rb +691 -0
  77. data/tools/asterism_msggen.rb +826 -0
  78. metadata +118 -9
data/mrblib/cdr.rb ADDED
@@ -0,0 +1,512 @@
1
+ # Asterism::CDR: the CDR encoding that ROS 2 messages use on the wire
2
+ # (doc/ruby_asterism/design.md ch. 5, R1).
3
+ #
4
+ # w = Asterism::CDR::Writer.new # starts with the 4-byte header
5
+ # w.string("hello") # uint32 length (with the NUL) + bytes + NUL
6
+ # bytes = w.to_s # "\x00\x01\x00\x00\x06\x00\x00\x00hello\x00"
7
+ # r = Asterism::CDR::Reader.new(bytes) # either byte order
8
+ # r.string # => "hello"
9
+ #
10
+ # Plain CDR (XCDR1) as rmw_zenoh writes it: a 4-byte encapsulation header
11
+ # (00 01 00 00 = little endian, 00 00 00 00 = big endian), then each value
12
+ # aligned to its own size counted from the end of the header. Writes are
13
+ # always little endian. Primitives, strings, arrays and sequences are here;
14
+ # message layouts are built from them by the generated types
15
+ # (tools/asterism_msggen.rb, loaded with Asterism::ROS.require_type).
16
+ #
17
+ # Pure Ruby. Integers are set byte by byte; floats use Array#pack /
18
+ # String#unpack when the VM has them (mruby-pack, CRuby) and fall back to
19
+ # arithmetic (CDR.f64_bytes and friends) when it does not.
20
+ module Asterism
21
+ module CDR
22
+ # Malformed or truncated CDR.
23
+ class DecodeError < ::StandardError; end
24
+
25
+ HEADER_LE = "\x00\x01\x00\x00".freeze
26
+ HEADER_BE = "\x00\x00\x00\x00".freeze
27
+
28
+ # Array#pack / String#unpack are there (mruby-pack, CRuby).
29
+ PACK = [].respond_to?(:pack) && "".respond_to?(:unpack)
30
+ MATH_LDEXP = Object.const_defined?(:Math) && ::Math.respond_to?(:ldexp)
31
+ MATH_FREXP = Object.const_defined?(:Math) && ::Math.respond_to?(:frexp)
32
+ # Strings carry an encoding (CRuby): the buffers are binary, decoded
33
+ # strings UTF-8. mruby has no encodings and skips this.
34
+ ENCODINGS = "".respond_to?(:force_encoding)
35
+
36
+ # A binary copy of s (CRuby); s itself where there are no encodings.
37
+ def self.binary(s)
38
+ ENCODINGS ? s.dup.force_encoding("BINARY") : s
39
+ end
40
+
41
+ def self.utf8(s)
42
+ ENCODINGS ? s.force_encoding("UTF-8") : s
43
+ end
44
+
45
+ # n bytes of v (two's complement), little endian.
46
+ def self.le_bytes(v, n)
47
+ v = v.to_i
48
+ s = ENCODINGS ? ::Asterism::CDR.binary("\x00" * n) : "\x00" * n
49
+ i = 0
50
+ while i < n
51
+ s.setbyte(i, (v >> (8 * i)) & 0xff)
52
+ i += 1
53
+ end
54
+ s
55
+ end
56
+
57
+ # A binary String from a String or an Array of byte values.
58
+ def self.byte_string(v)
59
+ return binary("") if v.nil?
60
+ return binary(v) if v.is_a?(::String)
61
+ s = binary("\x00" * v.size)
62
+ i = 0
63
+ while i < v.size
64
+ s.setbyte(i, v[i].to_i & 0xff)
65
+ i += 1
66
+ end
67
+ s
68
+ end
69
+
70
+ # x * 2**e, exact while the result is representable (e may be far
71
+ # from 0: x is scaled in steps, never 2**e on its own).
72
+ def self.ldexp(x, e)
73
+ return ::Math.ldexp(x, e) if MATH_LDEXP
74
+ x = x.to_f
75
+ while e > 0
76
+ step = e > 60 ? 60 : e
77
+ x *= (1 << step)
78
+ e -= step
79
+ end
80
+ while e < 0
81
+ step = e < -60 ? 60 : -e
82
+ x /= (1 << step)
83
+ e += step
84
+ end
85
+ x
86
+ end
87
+
88
+ # [mantissa in [0.5, 1), exponent] of a positive finite Float.
89
+ def self.frexp(a)
90
+ return ::Math.frexp(a) if MATH_FREXP
91
+ e = 0
92
+ while a >= 1.0
93
+ a /= 2.0
94
+ e += 1
95
+ end
96
+ while a < 0.5
97
+ a *= 2.0
98
+ e -= 1
99
+ end
100
+ [a, e]
101
+ end
102
+
103
+ # x rounded to an Integer, ties to even (x >= 0).
104
+ def self.round_even(x)
105
+ i = x.floor
106
+ d = x - i
107
+ i += 1 if d > 0.5 || (d == 0.5 && i.odd?)
108
+ i
109
+ end
110
+
111
+ # IEEE 754 bits of a Float as [sign, biased exponent, fraction]
112
+ # for a format with mbits fraction bits and the given bias.
113
+ def self.float_fields(v, mbits, ebits)
114
+ bias = (1 << (ebits - 1)) - 1
115
+ emax = (1 << ebits) - 1
116
+ return [0, emax, 1 << (mbits - 1)] if v != v # NaN
117
+ sign = v < 0 || (v == 0 && 1.0 / v < 0) ? 1 : 0
118
+ a = v < 0 ? -v : v
119
+ return [sign, 0, 0] if a == 0
120
+ return [sign, emax, 0] if a == ::Float::INFINITY
121
+ m, e = ::Asterism::CDR.frexp(a)
122
+ biased = e - 1 + bias
123
+ if biased >= 1
124
+ frac = ::Asterism::CDR.round_even(::Asterism::CDR.ldexp(m * 2.0 - 1.0, mbits))
125
+ if frac == (1 << mbits)
126
+ frac = 0
127
+ biased += 1
128
+ end
129
+ return [sign, emax, 0] if biased >= emax
130
+ [sign, biased, frac]
131
+ else
132
+ # Subnormal: a / 2**(1 - bias - mbits).
133
+ frac = ::Asterism::CDR.round_even(::Asterism::CDR.ldexp(a, bias - 1 + mbits))
134
+ return [sign, 1, 0] if frac == (1 << mbits)
135
+ [sign, 0, frac]
136
+ end
137
+ end
138
+
139
+ # 8 bytes (little endian) of a float64, without Array#pack.
140
+ def self.f64_bytes(v)
141
+ sign, ex, frac = float_fields(v.to_f, 52, 11)
142
+ hi = (sign << 31) | (ex << 20) | (frac >> 32)
143
+ le_bytes(frac & 0xffffffff, 4) + le_bytes(hi, 4)
144
+ end
145
+
146
+ # 4 bytes (little endian) of a float32, without Array#pack.
147
+ def self.f32_bytes(v)
148
+ sign, ex, frac = float_fields(v.to_f, 23, 8)
149
+ le_bytes((sign << 31) | (ex << 23) | frac, 4)
150
+ end
151
+
152
+ # A Float from its IEEE 754 fields.
153
+ def self.float_from(sign, ex, frac, mbits, ebits)
154
+ bias = (1 << (ebits - 1)) - 1
155
+ emax = (1 << ebits) - 1
156
+ v = if ex == emax
157
+ frac == 0 ? ::Float::INFINITY : ::Float::NAN
158
+ elsif ex == 0
159
+ ldexp(frac, 1 - bias - mbits)
160
+ else
161
+ ldexp(frac + (1 << mbits), ex - bias - mbits)
162
+ end
163
+ sign == 1 ? -v : v
164
+ end
165
+
166
+ # Float from 8 little-endian bytes (lo: low 32 bits, hi: high 32 bits).
167
+ def self.f64_from(lo, hi)
168
+ float_from(hi >> 31, (hi >> 20) & 0x7ff, ((hi & 0xfffff) << 32) | lo, 52, 11)
169
+ end
170
+
171
+ def self.f32_from(bits)
172
+ float_from(bits >> 31, (bits >> 23) & 0xff, bits & 0x7fffff, 23, 8)
173
+ end
174
+
175
+ class Writer
176
+ def initialize
177
+ # A new String each time (the constant must never be appended to).
178
+ @buf = ENCODINGS ? ::Asterism::CDR.binary(HEADER_LE) : "" + HEADER_LE
179
+ end
180
+
181
+ # Pads with zeros to a multiple of n, counted from the end of the header.
182
+ def align(n)
183
+ rem = (@buf.bytesize - 4) % n
184
+ @buf << ("\x00" * (n - rem)) if rem > 0
185
+ self
186
+ end
187
+
188
+ def uint8(v)
189
+ @buf << ::Asterism::CDR.le_bytes(v, 1)
190
+ self
191
+ end
192
+
193
+ def bool(v)
194
+ uint8(v ? 1 : 0)
195
+ end
196
+
197
+ def uint16(v)
198
+ align(2)
199
+ @buf << ::Asterism::CDR.le_bytes(v, 2)
200
+ self
201
+ end
202
+
203
+ def uint32(v)
204
+ align(4)
205
+ @buf << ::Asterism::CDR.le_bytes(v, 4)
206
+ self
207
+ end
208
+
209
+ def uint64(v)
210
+ align(8)
211
+ @buf << ::Asterism::CDR.le_bytes(v, 8)
212
+ self
213
+ end
214
+
215
+ alias int8 uint8
216
+ alias int16 uint16
217
+ alias int32 uint32
218
+ alias int64 uint64
219
+
220
+ def float32(v)
221
+ align(4)
222
+ @buf << (PACK ? [v.to_f].pack("e") : ::Asterism::CDR.f32_bytes(v))
223
+ self
224
+ end
225
+
226
+ def float64(v)
227
+ align(8)
228
+ @buf << (PACK ? [v.to_f].pack("E") : ::Asterism::CDR.f64_bytes(v))
229
+ self
230
+ end
231
+
232
+ # A string: uint32 length including the terminating NUL, the bytes, NUL.
233
+ # max: the bound of a bounded string (string<=N), in bytes.
234
+ def string(s, max = nil)
235
+ s = s.to_s
236
+ if max && s.bytesize > max
237
+ raise ArgumentError, "string of #{s.bytesize} bytes is longer than its bound #{max}"
238
+ end
239
+ uint32(s.bytesize + 1)
240
+ @buf << (ENCODINGS ? ::Asterism::CDR.binary(s) : s)
241
+ @buf << "\x00"
242
+ self
243
+ end
244
+
245
+ def wstring(_s, _max = nil)
246
+ raise NotImplementedError, "wstring is not supported"
247
+ end
248
+
249
+ # The element count of an array (fixed: its length, nothing written)
250
+ # or a sequence (max: its bound or nil; the count is written).
251
+ def count(n, fixed, max)
252
+ if fixed
253
+ raise ArgumentError, "array needs #{fixed} elements, got #{n}" if n != fixed
254
+ else
255
+ raise ArgumentError, "sequence of #{n} is longer than its bound #{max}" if max && n > max
256
+ uint32(n)
257
+ end
258
+ self
259
+ end
260
+
261
+ # An array or sequence of a primitive kind (:float64, :string, ...).
262
+ # string_max: the bound of each string.
263
+ def array(kind, v, fixed = nil, max = nil, string_max = nil)
264
+ a = v.nil? ? [] : v
265
+ n = a.size
266
+ count(n, fixed, max)
267
+ i = 0
268
+ if string_max
269
+ while i < n
270
+ string(a[i], string_max)
271
+ i += 1
272
+ end
273
+ else
274
+ while i < n
275
+ __send__(kind, a[i])
276
+ i += 1
277
+ end
278
+ end
279
+ self
280
+ end
281
+
282
+ # An array or sequence of bytes (byte, char, uint8): a String or an
283
+ # Array of Integers.
284
+ def bytes(v, fixed = nil, max = nil)
285
+ s = ::Asterism::CDR.byte_string(v)
286
+ count(s.bytesize, fixed, max)
287
+ @buf << s
288
+ self
289
+ end
290
+
291
+ # An array or sequence of messages of type t (a generated type, with
292
+ # from and write); the elements are t's or Hashes.
293
+ def structs(t, v, fixed = nil, max = nil)
294
+ a = v.nil? ? [] : v
295
+ n = a.size
296
+ count(n, fixed, max)
297
+ i = 0
298
+ while i < n
299
+ t.write(self, t.from(a[i]))
300
+ i += 1
301
+ end
302
+ self
303
+ end
304
+
305
+ def to_s
306
+ @buf
307
+ end
308
+ end
309
+
310
+ class Reader
311
+ attr_reader :pos
312
+
313
+ def initialize(bytes)
314
+ @buf = bytes.to_s
315
+ raise DecodeError, "shorter than the CDR header" if @buf.bytesize < 4
316
+ kind = @buf.getbyte(1)
317
+ raise DecodeError, "unknown CDR encapsulation #{kind}" if @buf.getbyte(0) != 0 || kind > 1
318
+ @le = (kind == 1)
319
+ @pos = 4
320
+ end
321
+
322
+ def little_endian?
323
+ @le
324
+ end
325
+
326
+ def align(n)
327
+ rem = (@pos - 4) % n
328
+ @pos += n - rem if rem > 0
329
+ self
330
+ end
331
+
332
+ def uint8
333
+ need(1)
334
+ v = @buf.getbyte(@pos)
335
+ @pos += 1
336
+ v
337
+ end
338
+
339
+ def int8
340
+ v = uint8
341
+ v >= 0x80 ? v - 0x100 : v
342
+ end
343
+
344
+ def bool
345
+ uint8 != 0
346
+ end
347
+
348
+ def uint16
349
+ unsigned(2)
350
+ end
351
+
352
+ def uint32
353
+ unsigned(4)
354
+ end
355
+
356
+ # Integers are 64-bit signed here: a uint64 at or above 2**63 comes out
357
+ # negative (the same bits).
358
+ def uint64
359
+ signed(8)
360
+ end
361
+
362
+ def int16
363
+ signed(2)
364
+ end
365
+
366
+ def int32
367
+ signed(4)
368
+ end
369
+
370
+ def int64
371
+ signed(8)
372
+ end
373
+
374
+ def float32
375
+ align(4)
376
+ need(4)
377
+ v = if PACK
378
+ @buf.byteslice(@pos, 4).unpack(@le ? "e" : "g")[0]
379
+ else
380
+ ::Asterism::CDR.f32_from(raw(4))
381
+ end
382
+ @pos += 4
383
+ v
384
+ end
385
+
386
+ def float64
387
+ align(8)
388
+ need(8)
389
+ v = if PACK
390
+ @buf.byteslice(@pos, 8).unpack(@le ? "E" : "G")[0]
391
+ else
392
+ lo = raw_at(@le ? 0 : 4)
393
+ hi = raw_at(@le ? 4 : 0)
394
+ ::Asterism::CDR.f64_from(lo, hi)
395
+ end
396
+ @pos += 8
397
+ v
398
+ end
399
+
400
+ def string
401
+ len = uint32
402
+ raise DecodeError, "string without its NUL" if len == 0
403
+ need(len)
404
+ s = @buf.byteslice(@pos, len - 1)
405
+ @pos += len
406
+ ENCODINGS ? ::Asterism::CDR.utf8(s) : s
407
+ end
408
+
409
+ def wstring
410
+ raise NotImplementedError, "wstring is not supported"
411
+ end
412
+
413
+ # The element count of an array (fixed) or a sequence (read). Each
414
+ # element takes at least min bytes, so a corrupt count cannot make a
415
+ # huge Array.
416
+ def count(fixed, min = 1)
417
+ return fixed if fixed
418
+ n = uint32
419
+ raise DecodeError, "sequence of #{n} does not fit in the data" if n * min > @buf.bytesize - @pos
420
+ n
421
+ end
422
+
423
+ def array(kind, fixed = nil)
424
+ n = count(fixed)
425
+ out = []
426
+ i = 0
427
+ while i < n
428
+ out << __send__(kind)
429
+ i += 1
430
+ end
431
+ out
432
+ end
433
+
434
+ # An array or sequence of bytes, as a binary String.
435
+ def bytes(fixed = nil)
436
+ n = count(fixed)
437
+ need(n)
438
+ s = @buf.byteslice(@pos, n)
439
+ @pos += n
440
+ ENCODINGS ? s.force_encoding("BINARY") : s
441
+ end
442
+
443
+ def structs(t, fixed = nil)
444
+ n = count(fixed, 0)
445
+ out = []
446
+ i = 0
447
+ while i < n
448
+ out << t.read(self)
449
+ i += 1
450
+ end
451
+ out
452
+ end
453
+
454
+ private
455
+
456
+ def need(n)
457
+ raise DecodeError, "truncated CDR (#{n} bytes at #{@pos} of #{@buf.bytesize})" if @pos + n > @buf.bytesize
458
+ end
459
+
460
+ # Byte i (0 = least significant) of the n-byte value at @pos.
461
+ def byte_at(i, n)
462
+ @buf.getbyte(@le ? @pos + i : @pos + n - 1 - i)
463
+ end
464
+
465
+ # The n bytes at @pos (in the data's byte order) as an unsigned Integer.
466
+ def raw(n)
467
+ v = 0
468
+ i = 0
469
+ while i < n
470
+ v |= byte_at(i, n) << (8 * i)
471
+ i += 1
472
+ end
473
+ v
474
+ end
475
+
476
+ # 32 bits at @pos + off, in the data's byte order (for float64 halves).
477
+ def raw_at(off)
478
+ v = 0
479
+ i = 0
480
+ while i < 4
481
+ b = @le ? @buf.getbyte(@pos + off + i) : @buf.getbyte(@pos + off + 3 - i)
482
+ v |= b << (8 * i)
483
+ i += 1
484
+ end
485
+ v
486
+ end
487
+
488
+ def unsigned(n)
489
+ align(n)
490
+ need(n)
491
+ v = raw(n)
492
+ @pos += n
493
+ v
494
+ end
495
+
496
+ # Built from the signed top byte down, so 8 bytes never overflow.
497
+ def signed(n)
498
+ align(n)
499
+ need(n)
500
+ top = byte_at(n - 1, n)
501
+ v = top >= 0x80 ? top - 0x100 : top
502
+ i = n - 2
503
+ while i >= 0
504
+ v = (v << 8) | byte_at(i, n)
505
+ i -= 1
506
+ end
507
+ @pos += n
508
+ v
509
+ end
510
+ end
511
+ end
512
+ end
data/mrblib/future.rb ADDED
@@ -0,0 +1,83 @@
1
+ module Asterism
2
+ # The answer to a call that is on its way (Proxy#async). done? never
3
+ # waits; value waits (polling, and answering calls meanwhile) until the
4
+ # answer or the time limit, then returns the value or raises.
5
+ class Future
6
+ attr_reader :path, :method_name
7
+
8
+ def self.answered(path, method, reply)
9
+ f = new(path, method, nil, 0, 0)
10
+ f.set_reply(reply)
11
+ f
12
+ end
13
+
14
+ def initialize(path, method, get, deadline_ms, timeout_ms)
15
+ @path = path
16
+ @method_name = method.to_s
17
+ @get = get
18
+ @deadline = deadline_ms
19
+ @timeout_ms = timeout_ms
20
+ @reply = nil
21
+ @finished = false
22
+ @started = ::Asterism.now_ms
23
+ @took = nil
24
+ end
25
+
26
+ def set_reply(reply)
27
+ @reply = reply
28
+ @finished = true
29
+ @took = 0
30
+ end
31
+
32
+ # Milliseconds from the call to its answer (nil until done).
33
+ def took_ms
34
+ @took
35
+ end
36
+
37
+ # True once the answer came, the time ran out or the connection closed.
38
+ # Call Asterism.poll in the update loop for it to move on.
39
+ def done?
40
+ collect
41
+ @finished
42
+ end
43
+
44
+ def value
45
+ raw = raw_value
46
+ unless raw.is_a?(Array) && (raw[0] == "ok" || raw[0] == "error")
47
+ raise ::Asterism::Error, "malformed answer from #{@path}"
48
+ end
49
+ raise ::Asterism::RemoteError.new(raw[1], raw[2]) if raw[0] == "error"
50
+ raw[1]
51
+ end
52
+
53
+ # The decoded answer (meta replies are not ["ok", value] pairs).
54
+ def raw_value
55
+ ::Asterism.wait_until { done? } unless done?
56
+ if @reply.nil?
57
+ raise ::Asterism::Disconnected, (::Asterism.lost_reason || "not connected") unless ::Asterism.connected?
58
+ if @took && @took < @timeout_ms
59
+ # Finished early without a reply: nobody answers that key (the
60
+ # object is not exposed, or its application is gone).
61
+ raise ::Asterism::Timeout, "no answer from #{@path} #{@method_name} (nobody answers)"
62
+ end
63
+ raise ::Asterism::Timeout, "no answer from #{@path} #{@method_name} within #{@timeout_ms} ms"
64
+ end
65
+ raw = ::Asterism::Codec.unpack(@reply)
66
+ raise ::Asterism::Error, "malformed answer from #{@path}" if raw.nil?
67
+ raw
68
+ end
69
+
70
+ def collect
71
+ return if @finished
72
+ g = @get
73
+ # The Array form, not a block: a block called from C costs another
74
+ # interpreter entry on the C stack, and this runs inside nested waits.
75
+ replies = g.each_reply
76
+ @reply = replies[0][1] if @reply.nil? && replies.size > 0
77
+ if !@reply.nil? || g.done? || ::Asterism.now_ms >= @deadline || !::Asterism.connected?
78
+ @finished = true
79
+ @took = ::Asterism.now_ms - @started
80
+ end
81
+ end
82
+ end
83
+ end
data/mrblib/proxy.rb ADDED
@@ -0,0 +1,97 @@
1
+ module Asterism
2
+ # A stand-in for an object on another machine (Asterism["node/app/obj"]).
3
+ # Any method it does not have itself becomes a call that waits for the
4
+ # answer. respond_to? and methods come from the object's meta.
5
+ #
6
+ # Its own methods (not forwarded): async, asterism_path, asterism_meta,
7
+ # asterism_refresh, respond_to?, methods, inspect, to_s and those of Object.
8
+ class Proxy
9
+ # Conversions Ruby asks for implicitly (puts, Array#flatten, ...): never
10
+ # forwarded.
11
+ LOCAL_ONLY = [:to_ary, :to_str, :to_hash, :to_proc, :to_a, :to_io]
12
+
13
+ def initialize(path, timeout_ms)
14
+ @path = path
15
+ @timeout_ms = timeout_ms
16
+ @meta = nil
17
+ end
18
+
19
+ def asterism_path
20
+ @path
21
+ end
22
+
23
+ def method_missing(name, *args, **kw, &blk)
24
+ return super if ::Asterism::Proxy::LOCAL_ONLY.include?(name)
25
+ raise ::ArgumentError, "a block cannot be sent to #{@path}" if blk
26
+ ::Asterism.call(@path, name, args, kw, @timeout_ms)
27
+ end
28
+
29
+ # In Ruby rather than through respond_to_missing?: Object#respond_to? is
30
+ # C, and calling back into Ruby from it (which then waits for the meta)
31
+ # costs another interpreter entry on the C stack.
32
+ def respond_to?(name, include_all = false)
33
+ return true if super
34
+ return false if ::Asterism::Proxy::LOCAL_ONLY.include?(name.to_sym)
35
+ remote_names.include?(name.to_s)
36
+ rescue ::Asterism::Error
37
+ false
38
+ end
39
+
40
+ # The exposed methods (Symbols), from the meta. Raises when the object
41
+ # does not answer.
42
+ def methods(*_args)
43
+ remote_names.map { |n| n.to_sym }
44
+ end
45
+
46
+ # The meta reply, fetched once: {"methods" => [[name, arity], ...]}.
47
+ def asterism_meta
48
+ @meta ||= ::Asterism.meta(@path, @timeout_ms)
49
+ end
50
+
51
+ # Forget the meta (the object may have been exposed again).
52
+ def asterism_refresh
53
+ @meta = nil
54
+ self
55
+ end
56
+
57
+ def remote_names
58
+ list = asterism_meta["methods"]
59
+ return [] unless list.is_a?(Array)
60
+ list.map { |pair| pair.is_a?(Array) ? pair[0].to_s : pair.to_s }
61
+ end
62
+
63
+ # The same object, calls that do not wait: each returns a Future.
64
+ def async
65
+ AsyncProxy.new(@path, @timeout_ms)
66
+ end
67
+
68
+ def inspect
69
+ "#<Asterism::Proxy #{@path}>"
70
+ end
71
+
72
+ def to_s
73
+ inspect
74
+ end
75
+ end
76
+
77
+ class AsyncProxy
78
+ def initialize(path, timeout_ms)
79
+ @path = path
80
+ @timeout_ms = timeout_ms
81
+ end
82
+
83
+ def method_missing(name, *args, **kw, &blk)
84
+ return super if ::Asterism::Proxy::LOCAL_ONLY.include?(name)
85
+ raise ::ArgumentError, "a block cannot be sent to #{@path}" if blk
86
+ ::Asterism.call_async(@path, name, args, kw, @timeout_ms)
87
+ end
88
+
89
+ def respond_to_missing?(name, _include_private = false)
90
+ !::Asterism::Proxy::LOCAL_ONLY.include?(name.to_sym)
91
+ end
92
+
93
+ def inspect
94
+ "#<Asterism::AsyncProxy #{@path}>"
95
+ end
96
+ end
97
+ end