@axpecter/lync 2.2.1 → 2.3.2
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.
- package/README.md +109 -147
- package/package.json +1 -1
- package/src/Types.luau +34 -22
- package/src/api/Group.luau +40 -33
- package/src/api/Packet.luau +140 -66
- package/src/api/Query.luau +103 -65
- package/src/api/Scope.luau +26 -10
- package/src/api/Signal.luau +43 -52
- package/src/codec/Base.luau +28 -14
- package/src/codec/composite/Array.luau +296 -115
- package/src/codec/composite/Map.luau +377 -57
- package/src/codec/composite/Optional.luau +10 -2
- package/src/codec/composite/Shared.luau +134 -64
- package/src/codec/composite/Struct.luau +294 -43
- package/src/codec/composite/Tagged.luau +21 -16
- package/src/codec/composite/Tuple.luau +32 -15
- package/src/codec/datatype/Buffer.luau +7 -7
- package/src/codec/datatype/CFrame.luau +34 -122
- package/src/codec/datatype/Color.luau +10 -10
- package/src/codec/datatype/Instance.luau +15 -12
- package/src/codec/datatype/IntVector.luau +2 -2
- package/src/codec/datatype/NumberRange.luau +15 -8
- package/src/codec/datatype/Ray.luau +13 -14
- package/src/codec/datatype/Rect.luau +12 -12
- package/src/codec/datatype/Region.luau +13 -14
- package/src/codec/datatype/Sequence.luau +87 -65
- package/src/codec/datatype/String.luau +17 -10
- package/src/codec/datatype/UDim.luau +10 -8
- package/src/codec/datatype/Vector.luau +20 -59
- package/src/codec/meta/Auto.luau +94 -126
- package/src/codec/meta/Bitfield.luau +12 -14
- package/src/codec/meta/Custom.luau +3 -1
- package/src/codec/meta/DeltaScalar.luau +390 -0
- package/src/codec/meta/Enum.luau +9 -9
- package/src/codec/meta/Float.luau +6 -28
- package/src/codec/meta/Nothing.luau +1 -1
- package/src/codec/meta/Unknown.luau +10 -7
- package/src/codec/primitive/Bool.luau +6 -4
- package/src/codec/primitive/Float16.luau +5 -2
- package/src/codec/primitive/Int.luau +5 -6
- package/src/codec/primitive/Number.luau +6 -4
- package/src/codec/primitive/Signed.luau +2 -2
- package/src/codec/primitive/Varint.luau +76 -39
- package/src/codec/primitive/Zint.luau +99 -0
- package/src/index.d.ts +207 -53
- package/src/init.luau +116 -103
- package/src/internal/Baseline.luau +9 -1
- package/src/internal/Channel.luau +191 -157
- package/src/internal/Middleware.luau +22 -6
- package/src/internal/Pool.luau +12 -4
- package/src/internal/Registry.luau +25 -16
- package/src/internal/Transport.luau +1 -1
- package/src/transport/Bridge.luau +47 -33
- package/src/transport/Client.luau +25 -21
- package/src/transport/Gate.luau +227 -172
- package/src/transport/Reader.luau +174 -106
- package/src/transport/Server.luau +46 -57
- package/src/util/Array.luau +18 -0
- package/src/util/Buffer.luau +90 -0
- package/src/util/Constants.luau +30 -0
- package/src/util/Log.luau +68 -0
- package/src/util/Player.luau +14 -0
- package/src/util/Quantize.luau +84 -0
- package/src/util/Quat.luau +124 -0
- package/src/internal/Util.luau +0 -26
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--!native
|
|
3
|
+
-- Delta scalars: zigzag-varint diff against previous frame for ints, floats, vec3, CFrame.
|
|
4
|
+
|
|
5
|
+
local Base = require(script.Parent.Parent.Base)
|
|
6
|
+
local Baseline = require(script.Parent.Parent.Parent.internal.Baseline)
|
|
7
|
+
local Log = require(script.Parent.Parent.Parent.util.Log)
|
|
8
|
+
local Quantize = require(script.Parent.Parent.Parent.util.Quantize)
|
|
9
|
+
local Quat = require(script.Parent.Parent.Parent.util.Quat)
|
|
10
|
+
local Shared = require(script.Parent.Parent.composite.Shared)
|
|
11
|
+
local Types = require(script.Parent.Parent.Parent.Types)
|
|
12
|
+
local Varint = require(script.Parent.Parent.primitive.Varint)
|
|
13
|
+
local Zint = require(script.Parent.Parent.primitive.Zint)
|
|
14
|
+
|
|
15
|
+
-- Constants --------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
local I32_MIN = -0x80000000
|
|
18
|
+
local I32_MAX = 0x7FFFFFFF
|
|
19
|
+
|
|
20
|
+
-- deltaCFrame header bits: which components changed since the last frame.
|
|
21
|
+
local CF_X = 0x01
|
|
22
|
+
local CF_Y = 0x02
|
|
23
|
+
local CF_Z = 0x04
|
|
24
|
+
local CF_ROT = 0x08
|
|
25
|
+
local CF_ALL = 0x0F
|
|
26
|
+
|
|
27
|
+
-- Private ----------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
local ensure = Base.ensure
|
|
30
|
+
local allocDeltaId = Shared.allocDeltaId
|
|
31
|
+
local floor = math.floor
|
|
32
|
+
local clamp = math.clamp
|
|
33
|
+
local round = math.round
|
|
34
|
+
local band = bit32.band
|
|
35
|
+
local bor = bit32.bor
|
|
36
|
+
local readu8 = buffer.readu8
|
|
37
|
+
local writeu8 = buffer.writeu8
|
|
38
|
+
local readu32 = buffer.readu32
|
|
39
|
+
local writeu32 = buffer.writeu32
|
|
40
|
+
local bufLen = buffer.len
|
|
41
|
+
local varintWrite = Varint.write
|
|
42
|
+
local varintRead = Varint.read
|
|
43
|
+
local zigzagEncode = Zint.encode
|
|
44
|
+
local zigzagDecode = Zint.decode
|
|
45
|
+
local quatPack = Quat.pack
|
|
46
|
+
local quatUnpack = Quat.unpack
|
|
47
|
+
local quatFromCFrame = Quat.fromCFrame
|
|
48
|
+
|
|
49
|
+
local function quantizeVec3(
|
|
50
|
+
v: Vector3,
|
|
51
|
+
min: number,
|
|
52
|
+
delta: number,
|
|
53
|
+
scale: number
|
|
54
|
+
): (number, number, number)
|
|
55
|
+
return round(clamp(v.X - min, 0, delta) * scale),
|
|
56
|
+
round(clamp(v.Y - min, 0, delta) * scale),
|
|
57
|
+
round(clamp(v.Z - min, 0, delta) * scale)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
-- Reject out-of-range components rather than silently snapping via clamp().
|
|
61
|
+
local function validateVec3(v: Vector3, min: number, max: number, label: string): ()
|
|
62
|
+
if v.X < min or v.X > max or v.Y < min or v.Y > max or v.Z < min or v.Z > max then
|
|
63
|
+
Log.error(`{label} ({v.X}, {v.Y}, {v.Z}) has component out of range [{min}, {max}]`)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
local function getScalarPrev(ch: Types.ChannelState, id: number, fallback: number): number
|
|
68
|
+
local cache = ch.deltas[id] :: any
|
|
69
|
+
return if cache then cache.scalar :: number else fallback
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
local function setScalarCache(ch: Types.ChannelState, id: number, value: number): ()
|
|
73
|
+
local cache = ch.deltas[id] :: any
|
|
74
|
+
if cache then
|
|
75
|
+
cache.scalar = value
|
|
76
|
+
else
|
|
77
|
+
ch.deltas[id] = { scalar = value } :: any
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
-- Public -----------------------------------------------------------------
|
|
82
|
+
|
|
83
|
+
local DeltaScalar = {}
|
|
84
|
+
|
|
85
|
+
--[[
|
|
86
|
+
Variable-length integer that emits zigzag varint of (current - previous).
|
|
87
|
+
First frame uses `min` as the implicit prev so the initial value encodes
|
|
88
|
+
as (value - min). Reliable transport only — a dropped frame desyncs prev.
|
|
89
|
+
|
|
90
|
+
Wire: 1 byte for unchanged or [-96, 95] mutations; up to 5 bytes for full i32.
|
|
91
|
+
]]
|
|
92
|
+
function DeltaScalar.deltaInt(min: number, max: number): Types.InternalCodec<number>
|
|
93
|
+
if min > max then
|
|
94
|
+
Log.error(`min ({min}) must be <= max ({max})`)
|
|
95
|
+
end
|
|
96
|
+
if floor(min) ~= min or floor(max) ~= max then
|
|
97
|
+
Log.error("bounds must be integers")
|
|
98
|
+
end
|
|
99
|
+
if max - min > I32_MAX then
|
|
100
|
+
Log.error(`range too wide; (max - min) must fit in i32, got {max - min}`)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
local deltaId = allocDeltaId()
|
|
104
|
+
|
|
105
|
+
return table.freeze({
|
|
106
|
+
_isDelta = true,
|
|
107
|
+
_typeCheck = "number",
|
|
108
|
+
_isInteger = true,
|
|
109
|
+
_min = min,
|
|
110
|
+
_max = max,
|
|
111
|
+
|
|
112
|
+
write = function(ch: Types.ChannelState, value: number): ()
|
|
113
|
+
if value < min or value > max then
|
|
114
|
+
Log.error(`value {value} out of range [{min}, {max}]`)
|
|
115
|
+
end
|
|
116
|
+
if floor(value) ~= value then
|
|
117
|
+
Log.error(`value {value} must be an integer`)
|
|
118
|
+
end
|
|
119
|
+
varintWrite(ch, zigzagEncode(value - getScalarPrev(ch, deltaId, min)))
|
|
120
|
+
setScalarCache(ch, deltaId, value)
|
|
121
|
+
end,
|
|
122
|
+
|
|
123
|
+
read = function(src: buffer, pos: number, _refs: { Instance }?): (number, number)
|
|
124
|
+
local raw, consumed = varintRead(src, pos)
|
|
125
|
+
if consumed == 0 then
|
|
126
|
+
Log.error("truncated deltaInt")
|
|
127
|
+
end
|
|
128
|
+
local cached = Baseline.getCache(deltaId)
|
|
129
|
+
local value = (if cached ~= nil then cached :: number else min) + zigzagDecode(raw)
|
|
130
|
+
if value < min or value > max then
|
|
131
|
+
Log.error(`deltaInt decoded value {value} out of range [{min}, {max}]`)
|
|
132
|
+
end
|
|
133
|
+
Baseline.setCache(deltaId, value)
|
|
134
|
+
return value, consumed
|
|
135
|
+
end,
|
|
136
|
+
}) :: Types.InternalCodec<number>
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
--[[
|
|
140
|
+
Quantized float with diffs in integer wire space (post-quantize). Storing
|
|
141
|
+
the quantized representative — not the float — across frames means
|
|
142
|
+
accumulating diffs over thousands of frames cannot drift past the grid.
|
|
143
|
+
]]
|
|
144
|
+
function DeltaScalar.deltaFloat(
|
|
145
|
+
min: number,
|
|
146
|
+
max: number,
|
|
147
|
+
precision: number
|
|
148
|
+
): Types.InternalCodec<number>
|
|
149
|
+
if max - min == 0 then
|
|
150
|
+
return Base.constant(min)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
local _, _, _, scale, invScale, delta = Quantize.setup("Lync.deltaFloat", min, max, precision)
|
|
154
|
+
local deltaId = allocDeltaId()
|
|
155
|
+
|
|
156
|
+
return table.freeze({
|
|
157
|
+
_isDelta = true,
|
|
158
|
+
_typeCheck = "number",
|
|
159
|
+
_min = min,
|
|
160
|
+
_max = max,
|
|
161
|
+
|
|
162
|
+
write = function(ch: Types.ChannelState, value: number): ()
|
|
163
|
+
local q = round(clamp(value - min, 0, delta) * scale)
|
|
164
|
+
varintWrite(ch, zigzagEncode(q - getScalarPrev(ch, deltaId, 0)))
|
|
165
|
+
setScalarCache(ch, deltaId, q)
|
|
166
|
+
end,
|
|
167
|
+
|
|
168
|
+
read = function(src: buffer, pos: number, _refs: { Instance }?): (number, number)
|
|
169
|
+
local raw, consumed = varintRead(src, pos)
|
|
170
|
+
if consumed == 0 then
|
|
171
|
+
Log.error("truncated deltaFloat")
|
|
172
|
+
end
|
|
173
|
+
local cached = Baseline.getCache(deltaId)
|
|
174
|
+
local q = (if cached ~= nil then cached :: number else 0) + zigzagDecode(raw)
|
|
175
|
+
Baseline.setCache(deltaId, q)
|
|
176
|
+
return q * invScale + min, consumed
|
|
177
|
+
end,
|
|
178
|
+
}) :: Types.InternalCodec<number>
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
--[[
|
|
182
|
+
Quantized Vector3 with per-axis zigzag varint diffs. Static value: 3 bytes
|
|
183
|
+
(three zero zigzags); typical < 1 stud motion at 0.01-stud precision: 3-6
|
|
184
|
+
bytes. Baseline vec3 = 12 bytes, so 4x reduction at the static end.
|
|
185
|
+
]]
|
|
186
|
+
function DeltaScalar.deltaVec3(
|
|
187
|
+
min: number,
|
|
188
|
+
max: number,
|
|
189
|
+
precision: number
|
|
190
|
+
): Types.InternalCodec<Vector3>
|
|
191
|
+
if max - min == 0 then
|
|
192
|
+
return Base.constant(Vector3.new(min, min, min)) :: Types.InternalCodec<Vector3>
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
local _, _, _, scale, invScale, delta = Quantize.setup("Lync.deltaVec3", min, max, precision)
|
|
196
|
+
local deltaId = allocDeltaId()
|
|
197
|
+
|
|
198
|
+
return table.freeze({
|
|
199
|
+
_isDelta = true,
|
|
200
|
+
_typeCheck = "Vector3",
|
|
201
|
+
_min = min,
|
|
202
|
+
_max = max,
|
|
203
|
+
|
|
204
|
+
write = function(ch: Types.ChannelState, value: Vector3): ()
|
|
205
|
+
validateVec3(value, min, max, "Vector3")
|
|
206
|
+
local qx, qy, qz = quantizeVec3(value, min, delta, scale)
|
|
207
|
+
local cache = ch.deltas[deltaId] :: any
|
|
208
|
+
local px, py, pz = 0, 0, 0
|
|
209
|
+
if cache then
|
|
210
|
+
px, py, pz = cache.qx :: number, cache.qy :: number, cache.qz :: number
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
varintWrite(ch, zigzagEncode(qx - px))
|
|
214
|
+
varintWrite(ch, zigzagEncode(qy - py))
|
|
215
|
+
varintWrite(ch, zigzagEncode(qz - pz))
|
|
216
|
+
|
|
217
|
+
if cache then
|
|
218
|
+
cache.qx, cache.qy, cache.qz = qx, qy, qz
|
|
219
|
+
else
|
|
220
|
+
ch.deltas[deltaId] = { qx = qx, qy = qy, qz = qz } :: any
|
|
221
|
+
end
|
|
222
|
+
end,
|
|
223
|
+
|
|
224
|
+
read = function(src: buffer, pos: number, _refs: { Instance }?): (Vector3, number)
|
|
225
|
+
local rx, cx = varintRead(src, pos)
|
|
226
|
+
if cx == 0 then
|
|
227
|
+
Log.error("truncated deltaVec3 X")
|
|
228
|
+
end
|
|
229
|
+
local ry, cy = varintRead(src, pos + cx)
|
|
230
|
+
if cy == 0 then
|
|
231
|
+
Log.error("truncated deltaVec3 Y")
|
|
232
|
+
end
|
|
233
|
+
local rz, cz = varintRead(src, pos + cx + cy)
|
|
234
|
+
if cz == 0 then
|
|
235
|
+
Log.error("truncated deltaVec3 Z")
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
local cached = Baseline.getCache(deltaId) :: any
|
|
239
|
+
local px, py, pz = 0, 0, 0
|
|
240
|
+
if cached then
|
|
241
|
+
px, py, pz = cached.qx, cached.qy, cached.qz
|
|
242
|
+
end
|
|
243
|
+
local qx = px + zigzagDecode(rx)
|
|
244
|
+
local qy = py + zigzagDecode(ry)
|
|
245
|
+
local qz = pz + zigzagDecode(rz)
|
|
246
|
+
Baseline.setCache(deltaId, { qx = qx, qy = qy, qz = qz })
|
|
247
|
+
|
|
248
|
+
return Vector3.new(qx * invScale + min, qy * invScale + min, qz * invScale + min),
|
|
249
|
+
cx + cy + cz
|
|
250
|
+
end,
|
|
251
|
+
}) :: Types.InternalCodec<Vector3>
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
--[[
|
|
255
|
+
Quantized position + smallest-three quaternion rotation. Wire layout:
|
|
256
|
+
byte 0 header bits: bit0=Δx, bit1=Δy, bit2=Δz, bit3=Δrot
|
|
257
|
+
For each set position bit: zigzag varint of quantized diff
|
|
258
|
+
If rotation bit set: 4-byte packed quat
|
|
259
|
+
|
|
260
|
+
Static CFrame: 1 byte. Pos-only motion: 1 + 3 zint bytes (~4-7).
|
|
261
|
+
Full update: 1 + 3 zint + 4 quat (~8-13). Baseline lossless: 24 bytes.
|
|
262
|
+
]]
|
|
263
|
+
function DeltaScalar.deltaCFrame(
|
|
264
|
+
posMin: number,
|
|
265
|
+
posMax: number,
|
|
266
|
+
posPrecision: number
|
|
267
|
+
): Types.InternalCodec<CFrame>
|
|
268
|
+
-- Quantize.setup itself rejects min == max; the redundant guard is gone.
|
|
269
|
+
local _, _, _, scale, invScale, delta =
|
|
270
|
+
Quantize.setup("Lync.deltaCFrame", posMin, posMax, posPrecision)
|
|
271
|
+
local deltaId = allocDeltaId()
|
|
272
|
+
|
|
273
|
+
return table.freeze({
|
|
274
|
+
_isDelta = true,
|
|
275
|
+
_typeCheck = "CFrame",
|
|
276
|
+
|
|
277
|
+
write = function(ch: Types.ChannelState, value: CFrame): ()
|
|
278
|
+
local pos = value.Position
|
|
279
|
+
validateVec3(pos, posMin, posMax, "CFrame position")
|
|
280
|
+
local qx, qy, qz = quantizeVec3(pos, posMin, delta, scale)
|
|
281
|
+
local packed = quatPack(quatFromCFrame(value))
|
|
282
|
+
|
|
283
|
+
-- prev = 0 so the first frame writes absolute values; receiver also defaults to 0.
|
|
284
|
+
local cache = ch.deltas[deltaId] :: any
|
|
285
|
+
local flag: number
|
|
286
|
+
local px, py, pz = 0, 0, 0
|
|
287
|
+
if cache then
|
|
288
|
+
px = cache.qx :: number
|
|
289
|
+
py = cache.qy :: number
|
|
290
|
+
pz = cache.qz :: number
|
|
291
|
+
local ppacked = cache.qrot :: number
|
|
292
|
+
flag = 0
|
|
293
|
+
if qx ~= px then
|
|
294
|
+
flag = bor(flag, CF_X)
|
|
295
|
+
end
|
|
296
|
+
if qy ~= py then
|
|
297
|
+
flag = bor(flag, CF_Y)
|
|
298
|
+
end
|
|
299
|
+
if qz ~= pz then
|
|
300
|
+
flag = bor(flag, CF_Z)
|
|
301
|
+
end
|
|
302
|
+
if packed ~= ppacked then
|
|
303
|
+
flag = bor(flag, CF_ROT)
|
|
304
|
+
end
|
|
305
|
+
else
|
|
306
|
+
-- First frame: emit everything so the receiver has a baseline.
|
|
307
|
+
flag = CF_ALL
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
ensure(ch, 1)
|
|
311
|
+
writeu8(ch.buff, ch.cursor, flag)
|
|
312
|
+
ch.cursor += 1
|
|
313
|
+
|
|
314
|
+
if band(flag, CF_X) ~= 0 then
|
|
315
|
+
varintWrite(ch, zigzagEncode(qx - px))
|
|
316
|
+
end
|
|
317
|
+
if band(flag, CF_Y) ~= 0 then
|
|
318
|
+
varintWrite(ch, zigzagEncode(qy - py))
|
|
319
|
+
end
|
|
320
|
+
if band(flag, CF_Z) ~= 0 then
|
|
321
|
+
varintWrite(ch, zigzagEncode(qz - pz))
|
|
322
|
+
end
|
|
323
|
+
if band(flag, CF_ROT) ~= 0 then
|
|
324
|
+
ensure(ch, 4)
|
|
325
|
+
writeu32(ch.buff, ch.cursor, packed)
|
|
326
|
+
ch.cursor += 4
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
if cache then
|
|
330
|
+
cache.qx, cache.qy, cache.qz, cache.qrot = qx, qy, qz, packed
|
|
331
|
+
else
|
|
332
|
+
ch.deltas[deltaId] = { qx = qx, qy = qy, qz = qz, qrot = packed } :: any
|
|
333
|
+
end
|
|
334
|
+
end,
|
|
335
|
+
|
|
336
|
+
read = function(src: buffer, pos: number, _refs: { Instance }?): (CFrame, number)
|
|
337
|
+
if pos >= bufLen(src) then
|
|
338
|
+
Log.error("truncated deltaCFrame header")
|
|
339
|
+
end
|
|
340
|
+
local flag = readu8(src, pos)
|
|
341
|
+
local total = 1
|
|
342
|
+
|
|
343
|
+
local cached = Baseline.getCache(deltaId) :: any
|
|
344
|
+
local qx, qy, qz, packed = 0, 0, 0, 0
|
|
345
|
+
if cached then
|
|
346
|
+
qx, qy, qz, packed = cached.qx, cached.qy, cached.qz, cached.qrot
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
if band(flag, CF_X) ~= 0 then
|
|
350
|
+
local r, c = varintRead(src, pos + total)
|
|
351
|
+
if c == 0 then
|
|
352
|
+
Log.error("truncated deltaCFrame X")
|
|
353
|
+
end
|
|
354
|
+
qx += zigzagDecode(r)
|
|
355
|
+
total += c
|
|
356
|
+
end
|
|
357
|
+
if band(flag, CF_Y) ~= 0 then
|
|
358
|
+
local r, c = varintRead(src, pos + total)
|
|
359
|
+
if c == 0 then
|
|
360
|
+
Log.error("truncated deltaCFrame Y")
|
|
361
|
+
end
|
|
362
|
+
qy += zigzagDecode(r)
|
|
363
|
+
total += c
|
|
364
|
+
end
|
|
365
|
+
if band(flag, CF_Z) ~= 0 then
|
|
366
|
+
local r, c = varintRead(src, pos + total)
|
|
367
|
+
if c == 0 then
|
|
368
|
+
Log.error("truncated deltaCFrame Z")
|
|
369
|
+
end
|
|
370
|
+
qz += zigzagDecode(r)
|
|
371
|
+
total += c
|
|
372
|
+
end
|
|
373
|
+
if band(flag, CF_ROT) ~= 0 then
|
|
374
|
+
if pos + total + 4 > bufLen(src) then
|
|
375
|
+
Log.error("truncated deltaCFrame rotation")
|
|
376
|
+
end
|
|
377
|
+
packed = readu32(src, pos + total)
|
|
378
|
+
total += 4
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
Baseline.setCache(deltaId, { qx = qx, qy = qy, qz = qz, qrot = packed })
|
|
382
|
+
|
|
383
|
+
local position =
|
|
384
|
+
Vector3.new(qx * invScale + posMin, qy * invScale + posMin, qz * invScale + posMin)
|
|
385
|
+
return CFrame.new(position) * quatUnpack(packed), total
|
|
386
|
+
end,
|
|
387
|
+
}) :: Types.InternalCodec<CFrame>
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
return table.freeze(DeltaScalar)
|
package/src/codec/meta/Enum.luau
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
--!strict
|
|
2
|
-
--!
|
|
2
|
+
--!native
|
|
3
3
|
-- String-keyed enum codec. Maps values to u8 indices at definition time.
|
|
4
4
|
|
|
5
5
|
local Base = require(script.Parent.Parent.Base)
|
|
6
|
+
local Log = require(script.Parent.Parent.Parent.util.Log)
|
|
6
7
|
local Types = require(script.Parent.Parent.Parent.Types)
|
|
7
8
|
|
|
8
9
|
-- Public -----------------------------------------------------------------
|
|
@@ -12,11 +13,9 @@ local EnumCodec = {}
|
|
|
12
13
|
function EnumCodec.define(...: string): Types.InternalCodec<string>
|
|
13
14
|
local values = { ... }
|
|
14
15
|
local count = #values
|
|
15
|
-
|
|
16
|
-
error("[Lync] Lync.enum: requires at least one value")
|
|
17
|
-
end
|
|
16
|
+
Log.assert(count > 0, "requires at least one variant")
|
|
18
17
|
if count > 256 then
|
|
19
|
-
error(
|
|
18
|
+
Log.error(`exceeds 256 variants ({count})`)
|
|
20
19
|
end
|
|
21
20
|
|
|
22
21
|
local toIndex: { [string]: number } = {}
|
|
@@ -24,7 +23,7 @@ function EnumCodec.define(...: string): Types.InternalCodec<string>
|
|
|
24
23
|
for i = 1, count do
|
|
25
24
|
local val = values[i]
|
|
26
25
|
if toIndex[val] then
|
|
27
|
-
error(`
|
|
26
|
+
Log.error(`duplicate value "{val}"`)
|
|
28
27
|
end
|
|
29
28
|
toIndex[val] = i - 1
|
|
30
29
|
toValue[i] = val
|
|
@@ -35,13 +34,14 @@ function EnumCodec.define(...: string): Types.InternalCodec<string>
|
|
|
35
34
|
return Base.define(1, function(b: buffer, off: number, value: string): ()
|
|
36
35
|
local idx = toIndex[value]
|
|
37
36
|
if idx == nil then
|
|
38
|
-
error(`
|
|
37
|
+
Log.error(`invalid value "{value}"`)
|
|
39
38
|
end
|
|
40
39
|
buffer.writeu8(b, off, idx)
|
|
41
40
|
end, function(b: buffer, off: number): string
|
|
42
|
-
local
|
|
41
|
+
local raw = buffer.readu8(b, off)
|
|
42
|
+
local val = toValue[raw + 1]
|
|
43
43
|
if val == nil then
|
|
44
|
-
error(`
|
|
44
|
+
Log.error(`invalid index {raw}`)
|
|
45
45
|
end
|
|
46
46
|
return val
|
|
47
47
|
end, { _typeCheck = "string", _isEnum = true, _enumValues = toIndex })
|
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
--!strict
|
|
2
2
|
--!native
|
|
3
|
-
-- Fixed-point
|
|
3
|
+
-- Fixed-point float quantizer. Maps a float range to u8/u16/u32.
|
|
4
4
|
|
|
5
5
|
local Base = require(script.Parent.Parent.Base)
|
|
6
|
+
local Quantize = require(script.Parent.Parent.Parent.util.Quantize)
|
|
6
7
|
local Types = require(script.Parent.Parent.Parent.Types)
|
|
7
8
|
|
|
8
9
|
-- Private ----------------------------------------------------------------
|
|
9
10
|
|
|
10
|
-
local ceil = math.ceil
|
|
11
11
|
local clamp = math.clamp
|
|
12
12
|
local round = math.round
|
|
13
|
-
local maxN = math.max
|
|
14
|
-
local minN = math.min
|
|
15
13
|
|
|
16
14
|
-- Public -----------------------------------------------------------------
|
|
17
15
|
|
|
@@ -22,33 +20,13 @@ function Float.float(
|
|
|
22
20
|
rangeMax: number,
|
|
23
21
|
precision: number
|
|
24
22
|
): Types.InternalCodec<number>
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
end
|
|
28
|
-
if precision <= 0 then
|
|
29
|
-
error("[Lync] Lync.float: precision must be positive")
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
local delta = rangeMax - rangeMin
|
|
33
|
-
if delta == 0 then
|
|
23
|
+
-- Degenerate range: emit nothing, decode to the constant.
|
|
24
|
+
if rangeMax - rangeMin == 0 then
|
|
34
25
|
return Base.constant(rangeMin)
|
|
35
26
|
end
|
|
36
27
|
|
|
37
|
-
local
|
|
38
|
-
|
|
39
|
-
local compBytes: number
|
|
40
|
-
local wfn: (buffer, number, number) -> ()
|
|
41
|
-
local rfn: (buffer, number) -> number
|
|
42
|
-
if maxInt <= 0xFF then
|
|
43
|
-
compBytes, wfn, rfn = 1, buffer.writeu8, buffer.readu8
|
|
44
|
-
elseif maxInt <= 0xFFFF then
|
|
45
|
-
compBytes, wfn, rfn = 2, buffer.writeu16, buffer.readu16
|
|
46
|
-
else
|
|
47
|
-
compBytes, wfn, rfn = 4, buffer.writeu32, buffer.readu32
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
local scale = maxInt / delta
|
|
51
|
-
local invScale = delta / maxInt
|
|
28
|
+
local compBytes, wfn, rfn, scale, invScale, delta =
|
|
29
|
+
Quantize.setup("Lync.float", rangeMin, rangeMax, precision)
|
|
52
30
|
|
|
53
31
|
return Base.define(compBytes, function(b: buffer, off: number, value: number): ()
|
|
54
32
|
wfn(b, off, round(clamp(value - rangeMin, 0, delta) * scale))
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
--!strict
|
|
2
|
-
--!
|
|
3
|
-
--
|
|
2
|
+
--!native
|
|
3
|
+
-- Bypass codec. Stashes the value in the channel sidecar refs (no schema).
|
|
4
4
|
|
|
5
5
|
local Channel = require(script.Parent.Parent.Parent.internal.Channel)
|
|
6
|
+
local Log = require(script.Parent.Parent.Parent.util.Log)
|
|
6
7
|
local Types = require(script.Parent.Parent.Parent.Types)
|
|
7
8
|
|
|
8
9
|
-- Private ----------------------------------------------------------------
|
|
@@ -15,20 +16,22 @@ local function writeUnknown(ch: Types.ChannelState, value: any): ()
|
|
|
15
16
|
end
|
|
16
17
|
|
|
17
18
|
local function readUnknown(src: buffer, pos: number, refs: { Instance }?): (any, number)
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
local r = refs
|
|
20
|
+
if not r then
|
|
21
|
+
Log.error("refs array is required")
|
|
20
22
|
end
|
|
21
23
|
local idx = readu16(src, pos)
|
|
22
|
-
if idx < 1 or idx > #
|
|
23
|
-
error(`
|
|
24
|
+
if idx < 1 or idx > #r then
|
|
25
|
+
Log.error(`ref index {idx} out of bounds`)
|
|
24
26
|
end
|
|
25
|
-
return
|
|
27
|
+
return r[idx], 2
|
|
26
28
|
end
|
|
27
29
|
|
|
28
30
|
-- Public -----------------------------------------------------------------
|
|
29
31
|
|
|
30
32
|
local Unknown = {}
|
|
31
33
|
|
|
34
|
+
-- _hasUnknown propagates upward so containers warn when used without validate.
|
|
32
35
|
Unknown.unknown = table.freeze({
|
|
33
36
|
_hasUnknown = true,
|
|
34
37
|
write = writeUnknown,
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
--!strict
|
|
2
2
|
--!optimize 2
|
|
3
|
-
--
|
|
3
|
+
-- 1-byte boolean codec. _isBool flag opts struct/array into bit packing.
|
|
4
4
|
|
|
5
5
|
local Base = require(script.Parent.Parent.Base)
|
|
6
6
|
|
|
7
|
-
--
|
|
8
|
-
|
|
9
|
-
local Bool = {}
|
|
7
|
+
-- Private ----------------------------------------------------------------
|
|
10
8
|
|
|
11
9
|
local function writeBool(b: buffer, off: number, value: boolean): ()
|
|
12
10
|
buffer.writeu8(b, off, if value then 1 else 0)
|
|
@@ -16,6 +14,10 @@ local function readBool(b: buffer, off: number): boolean
|
|
|
16
14
|
return buffer.readu8(b, off) ~= 0
|
|
17
15
|
end
|
|
18
16
|
|
|
17
|
+
-- Public -----------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
local Bool = {}
|
|
20
|
+
|
|
19
21
|
Bool.bool = Base.define(1, writeBool, readBool, {
|
|
20
22
|
_isBool = true,
|
|
21
23
|
_typeCheck = "boolean",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
--!strict
|
|
2
2
|
--!native
|
|
3
|
-
-- IEEE 754 binary16 codec. 2 bytes, +/-
|
|
3
|
+
-- IEEE 754 binary16 codec. 2 bytes, +/-65504 finite range, ~3 decimal digits.
|
|
4
4
|
|
|
5
5
|
local Base = require(script.Parent.Parent.Base)
|
|
6
6
|
|
|
@@ -20,9 +20,10 @@ local frexp = math.frexp
|
|
|
20
20
|
local ldexp = math.ldexp
|
|
21
21
|
local round = math.round
|
|
22
22
|
local huge = math.huge
|
|
23
|
+
local isnan = math.isnan
|
|
23
24
|
|
|
24
25
|
local function encode(value: number): number
|
|
25
|
-
if value
|
|
26
|
+
if isnan(value) then
|
|
26
27
|
return NAN_BITS
|
|
27
28
|
end
|
|
28
29
|
if value == 0 then
|
|
@@ -42,6 +43,7 @@ local function encode(value: number): number
|
|
|
42
43
|
local mantissa, exponent = frexp(value)
|
|
43
44
|
local biasedExp = exponent + 14
|
|
44
45
|
|
|
46
|
+
-- Subnormal: shift mantissa into the 10-bit fraction with the implicit bit gone.
|
|
45
47
|
if biasedExp <= 0 then
|
|
46
48
|
local frac = round(ldexp(mantissa, biasedExp + 10))
|
|
47
49
|
if frac >= 1024 then
|
|
@@ -54,6 +56,7 @@ local function encode(value: number): number
|
|
|
54
56
|
end
|
|
55
57
|
|
|
56
58
|
local frac = round((mantissa * 2 - 1) * 1024)
|
|
59
|
+
-- Rounding can carry into the exponent; absorb it.
|
|
57
60
|
if frac >= 1024 then
|
|
58
61
|
frac = 0
|
|
59
62
|
biasedExp += 1
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
--!strict
|
|
2
2
|
--!optimize 2
|
|
3
|
-
-- Range-typed integer codec. Picks the narrowest u8/u16/u32/i8/i16/i32
|
|
3
|
+
-- Range-typed integer codec. Picks the narrowest u8/u16/u32/i8/i16/i32 form.
|
|
4
4
|
|
|
5
5
|
local Base = require(script.Parent.Parent.Base)
|
|
6
|
+
local Log = require(script.Parent.Parent.Parent.util.Log)
|
|
6
7
|
local Signed = require(script.Parent.Signed)
|
|
7
8
|
local Types = require(script.Parent.Parent.Parent.Types)
|
|
8
9
|
|
|
@@ -19,11 +20,9 @@ local Int = {}
|
|
|
19
20
|
|
|
20
21
|
function Int.int(min: number, max: number): Types.InternalCodec<number>
|
|
21
22
|
if min > max then
|
|
22
|
-
error(`
|
|
23
|
-
end
|
|
24
|
-
if floor(min) ~= min or floor(max) ~= max then
|
|
25
|
-
error("[Lync] Lync.int: bounds must be integers")
|
|
23
|
+
Log.error(`min ({min}) must be <= max ({max})`)
|
|
26
24
|
end
|
|
25
|
+
Log.assert(floor(min) == min and floor(max) == max, "bounds must be integers")
|
|
27
26
|
|
|
28
27
|
local meta = {
|
|
29
28
|
_min = min,
|
|
@@ -54,7 +53,7 @@ function Int.int(min: number, max: number): Types.InternalCodec<number>
|
|
|
54
53
|
end
|
|
55
54
|
end
|
|
56
55
|
|
|
57
|
-
error(`
|
|
56
|
+
Log.error(`range [{min}, {max}] exceeds i32 bounds`)
|
|
58
57
|
end
|
|
59
58
|
|
|
60
59
|
return table.freeze(Int)
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
--!strict
|
|
2
2
|
--!optimize 2
|
|
3
|
-
-- Explicit-precision float codecs: f32 and f64.
|
|
3
|
+
-- Explicit-precision float codecs: f32 (4B) and f64 (8B).
|
|
4
4
|
|
|
5
5
|
local Base = require(script.Parent.Parent.Base)
|
|
6
6
|
|
|
7
|
-
--
|
|
7
|
+
-- State ------------------------------------------------------------------
|
|
8
8
|
|
|
9
|
-
--
|
|
10
|
-
|
|
9
|
+
--[[
|
|
10
|
+
Roblox is single-threaded, so a module-level scratch is safe to alias.
|
|
11
|
+
Auto codec uses this to round-trip a float through f32 and check fidelity.
|
|
12
|
+
]]
|
|
11
13
|
local F32_SCRATCH = buffer.create(4)
|
|
12
14
|
|
|
13
15
|
-- Public -----------------------------------------------------------------
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
--!strict
|
|
2
2
|
--!native
|
|
3
|
-
-- Signed integer
|
|
4
|
-
-- emulate via writeu* + two's complement (writei* is not FASTCALL).
|
|
3
|
+
-- Signed integer writers via writeu* + two's complement (writei* is not FASTCALL).
|
|
5
4
|
|
|
6
5
|
-- Private ----------------------------------------------------------------
|
|
7
6
|
|
|
@@ -25,6 +24,7 @@ function Signed.writeI32(b: buffer, off: number, value: number): ()
|
|
|
25
24
|
writeu32(b, off, if value < 0 then value + 0x100000000 else value)
|
|
26
25
|
end
|
|
27
26
|
|
|
27
|
+
-- Reads stay on buffer.readi* (already FASTCALL).
|
|
28
28
|
Signed.readI8 = buffer.readi8
|
|
29
29
|
Signed.readI16 = buffer.readi16
|
|
30
30
|
Signed.readI32 = buffer.readi32
|