@axpecter/lync 2.2.1 → 2.3.1
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 +95 -143
- package/package.json +1 -1
- package/src/Types.luau +22 -16
- package/src/api/Group.luau +40 -33
- package/src/api/Packet.luau +68 -54
- 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 +21 -14
- package/src/codec/composite/Array.luau +56 -134
- package/src/codec/composite/Map.luau +103 -72
- package/src/codec/composite/Optional.luau +6 -2
- package/src/codec/composite/Shared.luau +160 -69
- package/src/codec/composite/Struct.luau +283 -42
- package/src/codec/composite/Tagged.luau +13 -16
- package/src/codec/composite/Tuple.luau +21 -15
- package/src/codec/datatype/Buffer.luau +6 -4
- package/src/codec/datatype/CFrame.luau +56 -44
- 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/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 +69 -38
- package/src/index.d.ts +161 -53
- package/src/init.luau +109 -103
- package/src/internal/Baseline.luau +9 -1
- package/src/internal/Channel.luau +153 -154
- 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 +162 -105
- package/src/transport/Server.luau +46 -57
- package/src/util/Array.luau +18 -0
- package/src/util/Buffer.luau +92 -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 +60 -0
- package/src/internal/Util.luau +0 -26
package/src/codec/meta/Auto.luau
CHANGED
|
@@ -8,6 +8,7 @@ local CFrameC = require(script.Parent.Parent.datatype.CFrame)
|
|
|
8
8
|
local Channel = require(script.Parent.Parent.Parent.internal.Channel)
|
|
9
9
|
local ColorC = require(script.Parent.Parent.datatype.Color)
|
|
10
10
|
local IVC = require(script.Parent.Parent.datatype.IntVector)
|
|
11
|
+
local Log = require(script.Parent.Parent.Parent.util.Log)
|
|
11
12
|
local NRC = require(script.Parent.Parent.datatype.NumberRange)
|
|
12
13
|
local NumberC = require(script.Parent.Parent.primitive.Number)
|
|
13
14
|
local RayC = require(script.Parent.Parent.datatype.Ray)
|
|
@@ -53,6 +54,78 @@ local TAG_COLSEQ = 26
|
|
|
53
54
|
local INLINE_MAX = Varint.INLINE_MAX
|
|
54
55
|
local F32_SCRATCH = NumberC._f32Scratch
|
|
55
56
|
|
|
57
|
+
-- Tag -> numeric primitive dispatch tables.
|
|
58
|
+
local NUM_SIZE: { [number]: number } = table.freeze({
|
|
59
|
+
[TAG_U8] = 1,
|
|
60
|
+
[TAG_I8] = 1,
|
|
61
|
+
[TAG_U16] = 2,
|
|
62
|
+
[TAG_I16] = 2,
|
|
63
|
+
[TAG_U32] = 4,
|
|
64
|
+
[TAG_I32] = 4,
|
|
65
|
+
[TAG_F32] = 4,
|
|
66
|
+
[TAG_F64] = 8,
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
local NUM_WRITE: { [number]: (buffer, number, number) -> () } = table.freeze({
|
|
70
|
+
[TAG_U8] = buffer.writeu8,
|
|
71
|
+
[TAG_U16] = buffer.writeu16,
|
|
72
|
+
[TAG_U32] = buffer.writeu32,
|
|
73
|
+
[TAG_I8] = Signed.writeI8,
|
|
74
|
+
[TAG_I16] = Signed.writeI16,
|
|
75
|
+
[TAG_I32] = Signed.writeI32,
|
|
76
|
+
[TAG_F32] = buffer.writef32,
|
|
77
|
+
[TAG_F64] = buffer.writef64,
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
local NUM_READ: { [number]: (buffer, number) -> number } = table.freeze({
|
|
81
|
+
[TAG_U8] = buffer.readu8,
|
|
82
|
+
[TAG_U16] = buffer.readu16,
|
|
83
|
+
[TAG_U32] = buffer.readu32,
|
|
84
|
+
[TAG_I8] = buffer.readi8,
|
|
85
|
+
[TAG_I16] = buffer.readi16,
|
|
86
|
+
[TAG_I32] = buffer.readi32,
|
|
87
|
+
[TAG_F32] = buffer.readf32,
|
|
88
|
+
[TAG_F64] = buffer.readf64,
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
type TypeEntry = { tag: number, codec: Types.InternalCodec<any> }
|
|
92
|
+
|
|
93
|
+
local TYPE_DISPATCH: { [string]: TypeEntry } = table.freeze({
|
|
94
|
+
Vector2 = { tag = TAG_VEC2, codec = VecC.vec2 },
|
|
95
|
+
Vector3 = { tag = TAG_VEC3, codec = VecC.vec3 },
|
|
96
|
+
Color3 = { tag = TAG_COLOR3, codec = ColorC.color3 },
|
|
97
|
+
CFrame = { tag = TAG_CFRAME, codec = CFrameC.cframe },
|
|
98
|
+
UDim = { tag = TAG_UDIM, codec = UDimC.udim },
|
|
99
|
+
UDim2 = { tag = TAG_UDIM2, codec = UDimC.udim2 },
|
|
100
|
+
NumberRange = { tag = TAG_NUMRANGE, codec = NRC.numberRange },
|
|
101
|
+
Rect = { tag = TAG_RECT, codec = RectC.rect },
|
|
102
|
+
Vector2int16 = { tag = TAG_V2I16, codec = IVC.vec2int16 },
|
|
103
|
+
Vector3int16 = { tag = TAG_V3I16, codec = IVC.vec3int16 },
|
|
104
|
+
Region3 = { tag = TAG_REGION3, codec = RegionC.region3 },
|
|
105
|
+
Region3int16 = { tag = TAG_R3I16, codec = RegionC.region3int16 },
|
|
106
|
+
Ray = { tag = TAG_RAY, codec = RayC.ray },
|
|
107
|
+
NumberSequence = { tag = TAG_NUMSEQ, codec = SeqC.numberSequence },
|
|
108
|
+
ColorSequence = { tag = TAG_COLSEQ, codec = SeqC.colorSequence },
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
local READ_DISPATCH: { [number]: Types.InternalCodec<any> } = table.freeze({
|
|
112
|
+
[TAG_VEC2] = VecC.vec2,
|
|
113
|
+
[TAG_VEC3] = VecC.vec3,
|
|
114
|
+
[TAG_COLOR3] = ColorC.color3,
|
|
115
|
+
[TAG_CFRAME] = CFrameC.cframe,
|
|
116
|
+
[TAG_UDIM] = UDimC.udim,
|
|
117
|
+
[TAG_UDIM2] = UDimC.udim2,
|
|
118
|
+
[TAG_NUMRANGE] = NRC.numberRange,
|
|
119
|
+
[TAG_RECT] = RectC.rect,
|
|
120
|
+
[TAG_V2I16] = IVC.vec2int16,
|
|
121
|
+
[TAG_V3I16] = IVC.vec3int16,
|
|
122
|
+
[TAG_REGION3] = RegionC.region3,
|
|
123
|
+
[TAG_R3I16] = RegionC.region3int16,
|
|
124
|
+
[TAG_RAY] = RayC.ray,
|
|
125
|
+
[TAG_NUMSEQ] = SeqC.numberSequence,
|
|
126
|
+
[TAG_COLSEQ] = SeqC.colorSequence,
|
|
127
|
+
})
|
|
128
|
+
|
|
56
129
|
-- Private ----------------------------------------------------------------
|
|
57
130
|
|
|
58
131
|
local alloc = Base.alloc
|
|
@@ -62,10 +135,12 @@ local readu8 = buffer.readu8
|
|
|
62
135
|
local floor = math.floor
|
|
63
136
|
local isnan = math.isnan
|
|
64
137
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
138
|
+
--[[
|
|
139
|
+
Pick the narrowest tag for a number. NaN forces F64 (no integer or F32
|
|
140
|
+
can represent it). Integers walk u8 -> u16 -> u32 / i8 -> i16 -> i32; non-
|
|
141
|
+
integers try f32 by round-tripping through scratch (see Number.luau) and
|
|
142
|
+
fall through to f64 if precision is lost.
|
|
143
|
+
]]
|
|
69
144
|
local function selectNumberTag(value: number): number
|
|
70
145
|
if isnan(value) then
|
|
71
146
|
return TAG_F64
|
|
@@ -95,7 +170,6 @@ local function selectNumberTag(value: number): number
|
|
|
95
170
|
end
|
|
96
171
|
end
|
|
97
172
|
|
|
98
|
-
-- Roblox is single-threaded so a module scratch cannot be aliased.
|
|
99
173
|
buffer.writef32(F32_SCRATCH, 0, value)
|
|
100
174
|
if buffer.readf32(F32_SCRATCH, 0) == value then
|
|
101
175
|
return TAG_F32
|
|
@@ -103,114 +177,6 @@ local function selectNumberTag(value: number): number
|
|
|
103
177
|
return TAG_F64
|
|
104
178
|
end
|
|
105
179
|
|
|
106
|
-
local function numberSize(tag: number): number
|
|
107
|
-
if tag == TAG_U8 or tag == TAG_I8 then
|
|
108
|
-
return 1
|
|
109
|
-
end
|
|
110
|
-
if tag == TAG_U16 or tag == TAG_I16 then
|
|
111
|
-
return 2
|
|
112
|
-
end
|
|
113
|
-
if tag == TAG_U32 or tag == TAG_I32 or tag == TAG_F32 then
|
|
114
|
-
return 4
|
|
115
|
-
end
|
|
116
|
-
return 8
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
local function writeNumber(b: buffer, off: number, tag: number, value: number): ()
|
|
120
|
-
if tag == TAG_U8 then
|
|
121
|
-
buffer.writeu8(b, off, value)
|
|
122
|
-
return
|
|
123
|
-
end
|
|
124
|
-
if tag == TAG_U16 then
|
|
125
|
-
buffer.writeu16(b, off, value)
|
|
126
|
-
return
|
|
127
|
-
end
|
|
128
|
-
if tag == TAG_U32 then
|
|
129
|
-
buffer.writeu32(b, off, value)
|
|
130
|
-
return
|
|
131
|
-
end
|
|
132
|
-
if tag == TAG_I8 then
|
|
133
|
-
writeI8(b, off, value)
|
|
134
|
-
return
|
|
135
|
-
end
|
|
136
|
-
if tag == TAG_I16 then
|
|
137
|
-
writeI16(b, off, value)
|
|
138
|
-
return
|
|
139
|
-
end
|
|
140
|
-
if tag == TAG_I32 then
|
|
141
|
-
writeI32(b, off, value)
|
|
142
|
-
return
|
|
143
|
-
end
|
|
144
|
-
if tag == TAG_F32 then
|
|
145
|
-
buffer.writef32(b, off, value)
|
|
146
|
-
return
|
|
147
|
-
end
|
|
148
|
-
buffer.writef64(b, off, value)
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
local function readNumber(b: buffer, off: number, tag: number): number
|
|
152
|
-
if tag == TAG_U8 then
|
|
153
|
-
return buffer.readu8(b, off)
|
|
154
|
-
end
|
|
155
|
-
if tag == TAG_U16 then
|
|
156
|
-
return buffer.readu16(b, off)
|
|
157
|
-
end
|
|
158
|
-
if tag == TAG_U32 then
|
|
159
|
-
return buffer.readu32(b, off)
|
|
160
|
-
end
|
|
161
|
-
if tag == TAG_I8 then
|
|
162
|
-
return buffer.readi8(b, off)
|
|
163
|
-
end
|
|
164
|
-
if tag == TAG_I16 then
|
|
165
|
-
return buffer.readi16(b, off)
|
|
166
|
-
end
|
|
167
|
-
if tag == TAG_I32 then
|
|
168
|
-
return buffer.readi32(b, off)
|
|
169
|
-
end
|
|
170
|
-
if tag == TAG_F32 then
|
|
171
|
-
return buffer.readf32(b, off)
|
|
172
|
-
end
|
|
173
|
-
return buffer.readf64(b, off)
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
type TypeEntry = { tag: number, codec: Types.InternalCodec<any> }
|
|
177
|
-
|
|
178
|
-
local TYPE_DISPATCH: { [string]: TypeEntry } = table.freeze({
|
|
179
|
-
Vector2 = { tag = TAG_VEC2, codec = VecC.vec2 },
|
|
180
|
-
Vector3 = { tag = TAG_VEC3, codec = VecC.vec3 },
|
|
181
|
-
Color3 = { tag = TAG_COLOR3, codec = ColorC.color3 },
|
|
182
|
-
CFrame = { tag = TAG_CFRAME, codec = CFrameC.cframe },
|
|
183
|
-
UDim = { tag = TAG_UDIM, codec = UDimC.udim },
|
|
184
|
-
UDim2 = { tag = TAG_UDIM2, codec = UDimC.udim2 },
|
|
185
|
-
NumberRange = { tag = TAG_NUMRANGE, codec = NRC.numberRange },
|
|
186
|
-
Rect = { tag = TAG_RECT, codec = RectC.rect },
|
|
187
|
-
Vector2int16 = { tag = TAG_V2I16, codec = IVC.vec2int16 },
|
|
188
|
-
Vector3int16 = { tag = TAG_V3I16, codec = IVC.vec3int16 },
|
|
189
|
-
Region3 = { tag = TAG_REGION3, codec = RegionC.region3 },
|
|
190
|
-
Region3int16 = { tag = TAG_R3I16, codec = RegionC.region3int16 },
|
|
191
|
-
Ray = { tag = TAG_RAY, codec = RayC.ray },
|
|
192
|
-
NumberSequence = { tag = TAG_NUMSEQ, codec = SeqC.numberSequence },
|
|
193
|
-
ColorSequence = { tag = TAG_COLSEQ, codec = SeqC.colorSequence },
|
|
194
|
-
})
|
|
195
|
-
|
|
196
|
-
local READ_DISPATCH: { [number]: Types.InternalCodec<any> } = table.freeze({
|
|
197
|
-
[TAG_VEC2] = VecC.vec2,
|
|
198
|
-
[TAG_VEC3] = VecC.vec3,
|
|
199
|
-
[TAG_COLOR3] = ColorC.color3,
|
|
200
|
-
[TAG_CFRAME] = CFrameC.cframe,
|
|
201
|
-
[TAG_UDIM] = UDimC.udim,
|
|
202
|
-
[TAG_UDIM2] = UDimC.udim2,
|
|
203
|
-
[TAG_NUMRANGE] = NRC.numberRange,
|
|
204
|
-
[TAG_RECT] = RectC.rect,
|
|
205
|
-
[TAG_V2I16] = IVC.vec2int16,
|
|
206
|
-
[TAG_V3I16] = IVC.vec3int16,
|
|
207
|
-
[TAG_REGION3] = RegionC.region3,
|
|
208
|
-
[TAG_R3I16] = RegionC.region3int16,
|
|
209
|
-
[TAG_RAY] = RayC.ray,
|
|
210
|
-
[TAG_NUMSEQ] = SeqC.numberSequence,
|
|
211
|
-
[TAG_COLSEQ] = SeqC.colorSequence,
|
|
212
|
-
})
|
|
213
|
-
|
|
214
180
|
local function writeAuto(ch: Types.ChannelState, value: any): ()
|
|
215
181
|
if value == nil then
|
|
216
182
|
writeByte(ch, TAG_NIL)
|
|
@@ -233,15 +199,16 @@ local function writeAuto(ch: Types.ChannelState, value: any): ()
|
|
|
233
199
|
|
|
234
200
|
if t == "number" then
|
|
235
201
|
local tag = selectNumberTag(value)
|
|
236
|
-
local
|
|
202
|
+
local size = NUM_SIZE[tag]
|
|
203
|
+
local total = 1 + size
|
|
237
204
|
local cursor = ch.cursor
|
|
238
|
-
if cursor +
|
|
239
|
-
alloc(ch,
|
|
205
|
+
if cursor + total > ch.size then
|
|
206
|
+
alloc(ch, total)
|
|
240
207
|
end
|
|
241
208
|
local b = ch.buff
|
|
242
209
|
writeu8(b, cursor, tag)
|
|
243
|
-
|
|
244
|
-
ch.cursor = cursor +
|
|
210
|
+
NUM_WRITE[tag](b, cursor + 1, value)
|
|
211
|
+
ch.cursor = cursor + total
|
|
245
212
|
return
|
|
246
213
|
end
|
|
247
214
|
|
|
@@ -250,6 +217,7 @@ local function writeAuto(ch: Types.ChannelState, value: any): ()
|
|
|
250
217
|
|
|
251
218
|
local len = #value
|
|
252
219
|
local cursor = ch.cursor
|
|
220
|
+
-- Inline single-byte length fast path; matches String.luau.
|
|
253
221
|
if len <= INLINE_MAX then
|
|
254
222
|
if cursor + 1 + len > ch.size then
|
|
255
223
|
alloc(ch, 1 + len)
|
|
@@ -280,22 +248,22 @@ local function writeAuto(ch: Types.ChannelState, value: any): ()
|
|
|
280
248
|
|
|
281
249
|
local entry = TYPE_DISPATCH[t]
|
|
282
250
|
if not entry then
|
|
283
|
-
error(`
|
|
251
|
+
Log.error(`unsupported type "{t}"`)
|
|
284
252
|
end
|
|
285
|
-
|
|
286
253
|
local codec = entry.codec
|
|
287
254
|
local size = codec._size
|
|
288
255
|
local dw = codec._directWrite
|
|
256
|
+
-- Fixed-size + direct writer: single alloc + tag + payload.
|
|
289
257
|
if size and dw then
|
|
290
|
-
local
|
|
258
|
+
local total = 1 + size
|
|
291
259
|
local cursor = ch.cursor
|
|
292
|
-
if cursor +
|
|
293
|
-
alloc(ch,
|
|
260
|
+
if cursor + total > ch.size then
|
|
261
|
+
alloc(ch, total)
|
|
294
262
|
end
|
|
295
263
|
local b = ch.buff
|
|
296
264
|
writeu8(b, cursor, entry.tag)
|
|
297
265
|
dw(b, cursor + 1, value)
|
|
298
|
-
ch.cursor = cursor +
|
|
266
|
+
ch.cursor = cursor + total
|
|
299
267
|
return
|
|
300
268
|
end
|
|
301
269
|
|
|
@@ -314,7 +282,7 @@ local function readAuto(src: buffer, pos: number, refs: { Instance }?): (any, nu
|
|
|
314
282
|
end
|
|
315
283
|
|
|
316
284
|
if tag >= TAG_U8 and tag <= TAG_F64 then
|
|
317
|
-
return
|
|
285
|
+
return NUM_READ[tag](src, pos + 1), 1 + NUM_SIZE[tag]
|
|
318
286
|
end
|
|
319
287
|
|
|
320
288
|
if tag == TAG_STRING then
|
|
@@ -332,7 +300,7 @@ local function readAuto(src: buffer, pos: number, refs: { Instance }?): (any, nu
|
|
|
332
300
|
|
|
333
301
|
local codec = READ_DISPATCH[tag]
|
|
334
302
|
if not codec then
|
|
335
|
-
error(`
|
|
303
|
+
Log.error(`unknown tag {tag}`)
|
|
336
304
|
end
|
|
337
305
|
local value, consumed = codec.read(src, pos + 1, refs)
|
|
338
306
|
return value, 1 + consumed
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
--!strict
|
|
2
2
|
--!native
|
|
3
|
-
-- Sub-byte
|
|
3
|
+
-- Sub-byte packed bool/uint/int fields. Up to 32 bits total per record.
|
|
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
|
-- Constants --------------------------------------------------------------
|
|
@@ -18,12 +19,7 @@ local bor = bit32.bor
|
|
|
18
19
|
local lshift = bit32.lshift
|
|
19
20
|
local rshift = bit32.rshift
|
|
20
21
|
|
|
21
|
-
--
|
|
22
|
-
Width-aware mask: bit32.lshift(1, 32) returns 0, so 32-bit fields
|
|
23
|
-
need the all-ones mask explicitly. Sign-extension constants for int
|
|
24
|
-
fields use 2^(width-1) and 2^width via numeric exponentiation so
|
|
25
|
-
width=32 still produces 0x80000000 / 0x100000000 correctly.
|
|
26
|
-
]]
|
|
22
|
+
-- bit32.lshift(1, 32) wraps to 0; force all-ones in the 32-bit case.
|
|
27
23
|
local function widthMask(width: number): number
|
|
28
24
|
if width >= 32 then
|
|
29
25
|
return 0xFFFFFFFF
|
|
@@ -38,6 +34,7 @@ local Bitfield = {}
|
|
|
38
34
|
export type FieldSpec = { type: "bool" | "uint" | "int", width: number? }
|
|
39
35
|
|
|
40
36
|
function Bitfield.define(schema: { [string]: FieldSpec }): Types.InternalCodec<any>
|
|
37
|
+
-- Sort field names so bit positions are stable across runs.
|
|
41
38
|
local keys: { string } = {}
|
|
42
39
|
for key in schema do
|
|
43
40
|
table.insert(keys, key)
|
|
@@ -45,9 +42,7 @@ function Bitfield.define(schema: { [string]: FieldSpec }): Types.InternalCodec<a
|
|
|
45
42
|
table.sort(keys)
|
|
46
43
|
|
|
47
44
|
local fieldCount = #keys
|
|
48
|
-
|
|
49
|
-
error("[Lync] Lync.bitfield: requires at least one field")
|
|
50
|
-
end
|
|
45
|
+
Log.assert(fieldCount > 0, "requires at least one field")
|
|
51
46
|
|
|
52
47
|
local fieldKeys = table.create(fieldCount)
|
|
53
48
|
local fieldTypes = table.create(fieldCount)
|
|
@@ -72,7 +67,7 @@ function Bitfield.define(schema: { [string]: FieldSpec }): Types.InternalCodec<a
|
|
|
72
67
|
elseif spec.type == "uint" then
|
|
73
68
|
local width = spec.width
|
|
74
69
|
if not width or width < 1 or width > 32 then
|
|
75
|
-
error(`
|
|
70
|
+
Log.error(`field "{key}" uint width must be 1-32`)
|
|
76
71
|
end
|
|
77
72
|
fieldTypes[i] = TYPE_UINT
|
|
78
73
|
fieldMasks[i] = widthMask(width)
|
|
@@ -81,8 +76,9 @@ function Bitfield.define(schema: { [string]: FieldSpec }): Types.InternalCodec<a
|
|
|
81
76
|
totalBits += width
|
|
82
77
|
elseif spec.type == "int" then
|
|
83
78
|
local width = spec.width
|
|
79
|
+
-- Min int width is 2: 1 bit can't represent both a sign and a value.
|
|
84
80
|
if not width or width < 2 or width > 32 then
|
|
85
|
-
error(`
|
|
81
|
+
Log.error(`field "{key}" int width must be 2-32`)
|
|
86
82
|
end
|
|
87
83
|
fieldTypes[i] = TYPE_INT
|
|
88
84
|
fieldMasks[i] = widthMask(width)
|
|
@@ -90,14 +86,15 @@ function Bitfield.define(schema: { [string]: FieldSpec }): Types.InternalCodec<a
|
|
|
90
86
|
fieldSignExt[i] = 2 ^ width
|
|
91
87
|
totalBits += width
|
|
92
88
|
else
|
|
93
|
-
error(`
|
|
89
|
+
Log.error(`field "{key}" has unknown type "{spec.type}"`)
|
|
94
90
|
end
|
|
95
91
|
end
|
|
96
92
|
|
|
97
93
|
if totalBits > 32 then
|
|
98
|
-
error(`
|
|
94
|
+
Log.error(`total exceeds 32 bits ({totalBits})`)
|
|
99
95
|
end
|
|
100
96
|
|
|
97
|
+
-- Pick the narrowest u8/u16/u32 wire form.
|
|
101
98
|
local wireBytes: number
|
|
102
99
|
local wfn: (buffer, number, number) -> ()
|
|
103
100
|
local rfn: (buffer, number) -> number
|
|
@@ -137,6 +134,7 @@ function Bitfield.define(schema: { [string]: FieldSpec }): Types.InternalCodec<a
|
|
|
137
134
|
if fieldTypes[i] == TYPE_BOOL then
|
|
138
135
|
result[fieldKeys[i]] = raw ~= 0
|
|
139
136
|
elseif fieldTypes[i] == TYPE_INT then
|
|
137
|
+
-- Sign-extend: subtract 2^width when the sign bit is set.
|
|
140
138
|
result[fieldKeys[i]] = if raw >= fieldSignBit[i] then raw - fieldSignExt[i] else raw
|
|
141
139
|
else
|
|
142
140
|
result[fieldKeys[i]] = raw
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
-- User-defined fixed-size codec.
|
|
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
|
-- Private ----------------------------------------------------------------
|
|
@@ -20,8 +21,9 @@ function Custom.define<T>(
|
|
|
20
21
|
typeCheck: string?
|
|
21
22
|
): Types.InternalCodec<T>
|
|
22
23
|
if type(size) ~= "number" or size < 0 or floor(size) ~= size then
|
|
23
|
-
error(
|
|
24
|
+
Log.error(`size must be a non-negative integer, got {size}`)
|
|
24
25
|
end
|
|
26
|
+
-- size == 0 collapses to constant(nil): user has no payload to encode.
|
|
25
27
|
if size == 0 then
|
|
26
28
|
return Base.constant(nil :: any) :: Types.InternalCodec<T>
|
|
27
29
|
end
|
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 -----------------------------------------------------------------
|