asterism 0.0.0 → 0.2.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/LICENSE +21 -0
- data/NOTICE +27 -0
- data/README.md +581 -3
- data/data/msgs/LICENSE-Apache-2.0.txt +202 -0
- data/data/msgs/NOTICE +17 -0
- data/data/msgs/builtin_interfaces/msg/Duration.rb +33 -0
- data/data/msgs/builtin_interfaces/msg/Time.rb +33 -0
- data/data/msgs/example_interfaces/srv/AddTwoInts.rb +64 -0
- data/data/msgs/geometry_msgs/msg/Accel.rb +35 -0
- data/data/msgs/geometry_msgs/msg/AccelStamped.rb +36 -0
- data/data/msgs/geometry_msgs/msg/Point.rb +36 -0
- data/data/msgs/geometry_msgs/msg/Point32.rb +36 -0
- data/data/msgs/geometry_msgs/msg/PointStamped.rb +36 -0
- data/data/msgs/geometry_msgs/msg/Pose.rb +36 -0
- data/data/msgs/geometry_msgs/msg/Pose2D.rb +36 -0
- data/data/msgs/geometry_msgs/msg/PoseStamped.rb +36 -0
- data/data/msgs/geometry_msgs/msg/Quaternion.rb +39 -0
- data/data/msgs/geometry_msgs/msg/QuaternionStamped.rb +36 -0
- data/data/msgs/geometry_msgs/msg/Transform.rb +36 -0
- data/data/msgs/geometry_msgs/msg/TransformStamped.rb +39 -0
- data/data/msgs/geometry_msgs/msg/Twist.rb +35 -0
- data/data/msgs/geometry_msgs/msg/TwistStamped.rb +36 -0
- data/data/msgs/geometry_msgs/msg/Vector3.rb +36 -0
- data/data/msgs/geometry_msgs/msg/Vector3Stamped.rb +36 -0
- data/data/msgs/geometry_msgs/msg/Wrench.rb +35 -0
- data/data/msgs/geometry_msgs/msg/WrenchStamped.rb +36 -0
- data/data/msgs/sensor_msgs/msg/BatteryState.rb +100 -0
- data/data/msgs/sensor_msgs/msg/FluidPressure.rb +38 -0
- data/data/msgs/sensor_msgs/msg/Illuminance.rb +38 -0
- data/data/msgs/sensor_msgs/msg/Imu.rb +52 -0
- data/data/msgs/sensor_msgs/msg/JointState.rb +44 -0
- data/data/msgs/sensor_msgs/msg/MagneticField.rb +39 -0
- data/data/msgs/sensor_msgs/msg/NavSatFix.rb +55 -0
- data/data/msgs/sensor_msgs/msg/NavSatStatus.rb +43 -0
- data/data/msgs/sensor_msgs/msg/Range.rb +52 -0
- data/data/msgs/sensor_msgs/msg/RelativeHumidity.rb +38 -0
- data/data/msgs/sensor_msgs/msg/Temperature.rb +38 -0
- data/data/msgs/std_msgs/msg/Bool.rb +30 -0
- data/data/msgs/std_msgs/msg/Byte.rb +30 -0
- data/data/msgs/std_msgs/msg/ByteMultiArray.rb +35 -0
- data/data/msgs/std_msgs/msg/Char.rb +30 -0
- data/data/msgs/std_msgs/msg/ColorRGBA.rb +39 -0
- data/data/msgs/std_msgs/msg/Empty.rb +26 -0
- data/data/msgs/std_msgs/msg/Float32.rb +30 -0
- data/data/msgs/std_msgs/msg/Float32MultiArray.rb +35 -0
- data/data/msgs/std_msgs/msg/Float64.rb +30 -0
- data/data/msgs/std_msgs/msg/Float64MultiArray.rb +35 -0
- data/data/msgs/std_msgs/msg/Header.rb +35 -0
- data/data/msgs/std_msgs/msg/Int16.rb +30 -0
- data/data/msgs/std_msgs/msg/Int16MultiArray.rb +35 -0
- data/data/msgs/std_msgs/msg/Int32.rb +30 -0
- data/data/msgs/std_msgs/msg/Int32MultiArray.rb +35 -0
- data/data/msgs/std_msgs/msg/Int64.rb +30 -0
- data/data/msgs/std_msgs/msg/Int64MultiArray.rb +35 -0
- data/data/msgs/std_msgs/msg/Int8.rb +30 -0
- data/data/msgs/std_msgs/msg/Int8MultiArray.rb +35 -0
- data/data/msgs/std_msgs/msg/MultiArrayDimension.rb +36 -0
- data/data/msgs/std_msgs/msg/MultiArrayLayout.rb +35 -0
- data/data/msgs/std_msgs/msg/String.rb +30 -0
- data/data/msgs/std_msgs/msg/UInt16.rb +30 -0
- data/data/msgs/std_msgs/msg/UInt16MultiArray.rb +35 -0
- data/data/msgs/std_msgs/msg/UInt32.rb +30 -0
- data/data/msgs/std_msgs/msg/UInt32MultiArray.rb +35 -0
- data/data/msgs/std_msgs/msg/UInt64.rb +30 -0
- data/data/msgs/std_msgs/msg/UInt64MultiArray.rb +35 -0
- data/data/msgs/std_msgs/msg/UInt8.rb +30 -0
- data/data/msgs/std_msgs/msg/UInt8MultiArray.rb +35 -0
- data/lib/asterism/cruby/objects.rb +224 -0
- data/lib/asterism/cruby/ros.rb +305 -0
- data/lib/asterism/cruby/runner.rb +253 -0
- data/lib/asterism/cruby/zenoh.rb +422 -0
- data/lib/asterism/cruby.rb +30 -0
- data/lib/asterism/version.rb +5 -0
- data/lib/asterism.rb +22 -4
- data/mrblib/asterism.rb +617 -0
- data/mrblib/cdr.rb +512 -0
- data/mrblib/future.rb +88 -0
- data/mrblib/proxy.rb +97 -0
- data/mrblib/ros.rb +861 -0
- data/tools/asterism_msggen.rb +826 -0
- metadata +122 -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,88 @@
|
|
|
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
|
+
# done? before taking the replies: with a binding whose replies come
|
|
74
|
+
# in on another thread (zenoh-c), a reply and the end of the get may
|
|
75
|
+
# both arrive between the two; read the other way round, the get
|
|
76
|
+
# would look finished with its reply still queued.
|
|
77
|
+
ended = g.done?
|
|
78
|
+
# The Array form, not a block: a block called from C costs another
|
|
79
|
+
# interpreter entry on the C stack, and this runs inside nested waits.
|
|
80
|
+
replies = g.each_reply
|
|
81
|
+
@reply = replies[0][1] if @reply.nil? && replies.size > 0
|
|
82
|
+
if !@reply.nil? || ended || ::Asterism.now_ms >= @deadline || !::Asterism.connected?
|
|
83
|
+
@finished = true
|
|
84
|
+
@took = ::Asterism.now_ms - @started
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
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
|