@axpecter/lync 2.3.1 → 2.3.3
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 +29 -19
- package/package.json +1 -1
- package/src/Types.luau +12 -6
- package/src/api/Packet.luau +93 -35
- package/src/api/Query.luau +2 -2
- package/src/api/Signal.luau +6 -1
- package/src/codec/Base.luau +8 -1
- package/src/codec/composite/Array.luau +293 -17
- package/src/codec/composite/Map.luau +345 -40
- package/src/codec/composite/Optional.luau +4 -0
- package/src/codec/composite/Shared.luau +101 -102
- package/src/codec/composite/Struct.luau +20 -10
- package/src/codec/composite/Tagged.luau +8 -0
- package/src/codec/composite/Tuple.luau +12 -1
- package/src/codec/datatype/Buffer.luau +1 -3
- package/src/codec/datatype/CFrame.luau +6 -106
- package/src/codec/meta/Bitfield.luau +14 -1
- package/src/codec/meta/DeltaScalar.luau +390 -0
- package/src/codec/primitive/Varint.luau +13 -7
- package/src/codec/primitive/Zint.luau +99 -0
- package/src/index.d.ts +46 -0
- package/src/init.luau +7 -0
- package/src/internal/Channel.luau +187 -58
- package/src/internal/Pool.luau +4 -3
- package/src/transport/Reader.luau +19 -8
- package/src/transport/Server.luau +35 -13
- package/src/util/Buffer.luau +2 -4
- package/src/util/Quantize.luau +27 -3
- package/src/util/Quat.luau +124 -0
|
@@ -11,14 +11,14 @@ local Types = require(script.Parent.Parent.Parent.Types)
|
|
|
11
11
|
-- Constants --------------------------------------------------------------
|
|
12
12
|
|
|
13
13
|
--[[
|
|
14
|
-
Delta flag
|
|
15
|
-
0 UNCHANGED
|
|
16
|
-
1
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
Delta flag base shared by deltaStruct's per-segment scheme:
|
|
15
|
+
0 UNCHANGED
|
|
16
|
+
1..segCount single dirty segment k
|
|
17
|
+
segCount + FULL_OFFSET FULL payload
|
|
18
|
+
segCount + MULTI_OFFSET MULTI: bitmap + dirty segs in order
|
|
19
|
+
deltaArray and deltaMap manage their own flag spaces.
|
|
19
20
|
]]
|
|
20
21
|
local DELTA_UNCHANGED = 0
|
|
21
|
-
local DELTA_FULL = 1
|
|
22
22
|
local DELTA_FULL_OFFSET = 1
|
|
23
23
|
local DELTA_MULTI_OFFSET = 2
|
|
24
24
|
|
|
@@ -37,9 +37,29 @@ local lshift = bit32.lshift
|
|
|
37
37
|
local readu8 = buffer.readu8
|
|
38
38
|
local writeu8 = buffer.writeu8
|
|
39
39
|
local bufCopy = buffer.copy
|
|
40
|
+
local bufLen = buffer.len
|
|
40
41
|
local rangeEqual = Buf.rangeEqual
|
|
41
42
|
local snapshot = Buf.snapshot
|
|
42
43
|
|
|
44
|
+
--[[
|
|
45
|
+
Reuse `existing` when it's already large enough, else snapshot via
|
|
46
|
+
Buf.snapshot. Avoids per-write buffer allocations on delta caches that
|
|
47
|
+
hover at a stable size.
|
|
48
|
+
]]
|
|
49
|
+
local function copyToBuf(existing: buffer?, src: buffer, srcOff: number, len: number): buffer
|
|
50
|
+
if existing and bufLen(existing) >= len then
|
|
51
|
+
bufCopy(existing, 0, src, srcOff, len)
|
|
52
|
+
return existing
|
|
53
|
+
end
|
|
54
|
+
return snapshot(src, len, srcOff)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
-- Update a `{raw, len}` cache shape from scratch bytes; reuses raw via copyToBuf.
|
|
58
|
+
local function refreshCache(cache: any, src: buffer, len: number): ()
|
|
59
|
+
cache.raw = copyToBuf(cache.raw, src, 0, len)
|
|
60
|
+
cache.len = len
|
|
61
|
+
end
|
|
62
|
+
|
|
43
63
|
-- Public -----------------------------------------------------------------
|
|
44
64
|
|
|
45
65
|
local Shared = {}
|
|
@@ -49,6 +69,20 @@ Shared.DELTA_FULL_OFFSET = DELTA_FULL_OFFSET
|
|
|
49
69
|
Shared.DELTA_MULTI_OFFSET = DELTA_MULTI_OFFSET
|
|
50
70
|
Shared.rangeEqual = rangeEqual
|
|
51
71
|
Shared.snapshot = snapshot
|
|
72
|
+
Shared.refreshCache = refreshCache
|
|
73
|
+
Shared.copyToBuf = copyToBuf
|
|
74
|
+
|
|
75
|
+
-- True when `c` itself is a delta codec OR contains one in its tree.
|
|
76
|
+
function Shared.containsDelta(c: Types.InternalCodec<any>): boolean
|
|
77
|
+
return c._isDelta == true or c._hasDelta == true
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
-- Common bounds check for variable-length container codecs.
|
|
81
|
+
function Shared.enforceMaxCount(len: number, maxCount: number?, label: string?): ()
|
|
82
|
+
if maxCount and len > maxCount then
|
|
83
|
+
Log.error(`{label or "count"} {len} exceeds max {maxCount}`)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
52
86
|
|
|
53
87
|
function Shared.allocDeltaId(): number
|
|
54
88
|
_nextDeltaId += 1
|
|
@@ -63,7 +97,9 @@ end
|
|
|
63
97
|
--[[
|
|
64
98
|
Stack-based scratch ChannelState pool. Re-entrant codec serialization
|
|
65
99
|
(a codec writing into scratch that triggers another codec's scratch
|
|
66
|
-
acquire) works without aliasing.
|
|
100
|
+
acquire) works without aliasing. Full scrub on reuse so any codec that
|
|
101
|
+
reads framing fields (lastId/singleMode/itemCount/countPos) on scratch
|
|
102
|
+
sees a clean state, not stale bytes from the previous owner.
|
|
67
103
|
]]
|
|
68
104
|
function Shared.acquireScratch(): Types.ChannelState
|
|
69
105
|
_scratchDepth += 1
|
|
@@ -71,6 +107,15 @@ function Shared.acquireScratch(): Types.ChannelState
|
|
|
71
107
|
if ch then
|
|
72
108
|
ch.cursor = 0
|
|
73
109
|
ch.refCount = 0
|
|
110
|
+
ch.lastId = -1
|
|
111
|
+
ch.countPos = -1
|
|
112
|
+
ch.itemCount = 0
|
|
113
|
+
ch.singleMode = false
|
|
114
|
+
ch.singlePos = 0
|
|
115
|
+
ch.currentPacket = nil
|
|
116
|
+
if next(ch.deltas) ~= nil then
|
|
117
|
+
table.clear(ch.deltas)
|
|
118
|
+
end
|
|
74
119
|
return ch
|
|
75
120
|
end
|
|
76
121
|
ch = Channel.create()
|
|
@@ -82,6 +127,26 @@ function Shared.releaseScratch(): ()
|
|
|
82
127
|
_scratchDepth -= 1
|
|
83
128
|
end
|
|
84
129
|
|
|
130
|
+
--[[
|
|
131
|
+
Reset module-private state. Called by Sandbox in tests so that a thrown
|
|
132
|
+
codec.write (which skips releaseScratch and leaves depth inflated) can't
|
|
133
|
+
leak into the next test's scratch acquisition. Also resets the deltaId
|
|
134
|
+
counter so test ordering doesn't bleed.
|
|
135
|
+
]]
|
|
136
|
+
function Shared.reset(): ()
|
|
137
|
+
_scratchDepth = 0
|
|
138
|
+
_nextDeltaId = 0
|
|
139
|
+
for i = 1, #_scratchStack do
|
|
140
|
+
local ch = _scratchStack[i]
|
|
141
|
+
ch.cursor = 0
|
|
142
|
+
ch.refCount = 0
|
|
143
|
+
if ch.refCount > 0 then
|
|
144
|
+
table.clear(ch.refs)
|
|
145
|
+
end
|
|
146
|
+
table.clear(ch.deltas)
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
85
150
|
--[[
|
|
86
151
|
Split a struct schema into (dataKeys, dataCodecs, boolKeys), each sorted
|
|
87
152
|
alphabetically for deterministic wire layout.
|
|
@@ -117,25 +182,27 @@ function Shared.extractFields(
|
|
|
117
182
|
return dataKeys, dataCodecs, boolKeys
|
|
118
183
|
end
|
|
119
184
|
|
|
185
|
+
-- `byteCount` is `(boolCount + 7) // 8`; callers with it cached pass it through.
|
|
120
186
|
function Shared.packBools(
|
|
121
187
|
ch: Types.ChannelState,
|
|
122
188
|
value: { [string]: any },
|
|
123
189
|
boolKeys: { string },
|
|
124
|
-
boolCount: number
|
|
190
|
+
boolCount: number,
|
|
191
|
+
byteCount: number?
|
|
125
192
|
): ()
|
|
126
193
|
if boolCount == 0 then
|
|
127
194
|
return
|
|
128
195
|
end
|
|
129
196
|
|
|
130
|
-
local
|
|
197
|
+
local bc = byteCount or (boolCount + 7) // 8
|
|
131
198
|
local cursor = ch.cursor
|
|
132
|
-
if cursor +
|
|
133
|
-
alloc(ch,
|
|
199
|
+
if cursor + bc > ch.size then
|
|
200
|
+
alloc(ch, bc)
|
|
134
201
|
end
|
|
135
202
|
|
|
136
203
|
local idx = 1
|
|
137
204
|
local b = ch.buff
|
|
138
|
-
for byteIdx = 0,
|
|
205
|
+
for byteIdx = 0, bc - 1 do
|
|
139
206
|
local packed = 0
|
|
140
207
|
local remaining = boolCount - idx + 1
|
|
141
208
|
local limit = if remaining >= 8 then 8 else remaining
|
|
@@ -148,27 +215,32 @@ function Shared.packBools(
|
|
|
148
215
|
writeu8(b, cursor + byteIdx, packed)
|
|
149
216
|
end
|
|
150
217
|
|
|
151
|
-
ch.cursor = cursor +
|
|
218
|
+
ch.cursor = cursor + bc
|
|
152
219
|
end
|
|
153
220
|
|
|
154
221
|
--[[
|
|
155
222
|
Integer-indexed bool-array pack. Split from packBools to skip the
|
|
156
223
|
per-bit string-key getter on the bool-array hot path.
|
|
157
224
|
]]
|
|
158
|
-
function Shared.packBoolArray(
|
|
225
|
+
function Shared.packBoolArray(
|
|
226
|
+
ch: Types.ChannelState,
|
|
227
|
+
value: { boolean },
|
|
228
|
+
count: number,
|
|
229
|
+
byteCount: number?
|
|
230
|
+
): ()
|
|
159
231
|
if count == 0 then
|
|
160
232
|
return
|
|
161
233
|
end
|
|
162
234
|
|
|
163
|
-
local
|
|
235
|
+
local bc = byteCount or (count + 7) // 8
|
|
164
236
|
local cursor = ch.cursor
|
|
165
|
-
if cursor +
|
|
166
|
-
alloc(ch,
|
|
237
|
+
if cursor + bc > ch.size then
|
|
238
|
+
alloc(ch, bc)
|
|
167
239
|
end
|
|
168
240
|
|
|
169
241
|
local idx = 1
|
|
170
242
|
local b = ch.buff
|
|
171
|
-
for byteIdx = 0,
|
|
243
|
+
for byteIdx = 0, bc - 1 do
|
|
172
244
|
local packed = 0
|
|
173
245
|
local remaining = count - idx + 1
|
|
174
246
|
local limit = if remaining >= 8 then 8 else remaining
|
|
@@ -181,7 +253,7 @@ function Shared.packBoolArray(ch: Types.ChannelState, value: { boolean }, count:
|
|
|
181
253
|
writeu8(b, cursor + byteIdx, packed)
|
|
182
254
|
end
|
|
183
255
|
|
|
184
|
-
ch.cursor = cursor +
|
|
256
|
+
ch.cursor = cursor + bc
|
|
185
257
|
end
|
|
186
258
|
|
|
187
259
|
function Shared.unpackBools(
|
|
@@ -189,15 +261,16 @@ function Shared.unpackBools(
|
|
|
189
261
|
pos: number,
|
|
190
262
|
boolKeys: { string },
|
|
191
263
|
boolCount: number,
|
|
192
|
-
result: { [string]: any }
|
|
264
|
+
result: { [string]: any },
|
|
265
|
+
byteCount: number?
|
|
193
266
|
): number
|
|
194
267
|
if boolCount == 0 then
|
|
195
268
|
return 0
|
|
196
269
|
end
|
|
197
270
|
|
|
198
|
-
local
|
|
271
|
+
local bc = byteCount or (boolCount + 7) // 8
|
|
199
272
|
local idx = 1
|
|
200
|
-
for byteIdx = 0,
|
|
273
|
+
for byteIdx = 0, bc - 1 do
|
|
201
274
|
local byte = readu8(src, pos + byteIdx)
|
|
202
275
|
local remaining = boolCount - idx + 1
|
|
203
276
|
local limit = if remaining >= 8 then 8 else remaining
|
|
@@ -206,7 +279,7 @@ function Shared.unpackBools(
|
|
|
206
279
|
idx += 1
|
|
207
280
|
end
|
|
208
281
|
end
|
|
209
|
-
return
|
|
282
|
+
return bc
|
|
210
283
|
end
|
|
211
284
|
|
|
212
285
|
-- Fills `result[1..count]` in place.
|
|
@@ -214,15 +287,16 @@ function Shared.unpackBoolArray(
|
|
|
214
287
|
src: buffer,
|
|
215
288
|
pos: number,
|
|
216
289
|
count: number,
|
|
217
|
-
result: { boolean }
|
|
290
|
+
result: { boolean },
|
|
291
|
+
byteCount: number?
|
|
218
292
|
): number
|
|
219
293
|
if count == 0 then
|
|
220
294
|
return 0
|
|
221
295
|
end
|
|
222
296
|
|
|
223
|
-
local
|
|
297
|
+
local bc = byteCount or (count + 7) // 8
|
|
224
298
|
local idx = 1
|
|
225
|
-
for byteIdx = 0,
|
|
299
|
+
for byteIdx = 0, bc - 1 do
|
|
226
300
|
local byte = readu8(src, pos + byteIdx)
|
|
227
301
|
local remaining = count - idx + 1
|
|
228
302
|
local limit = if remaining >= 8 then 8 else remaining
|
|
@@ -231,82 +305,7 @@ function Shared.unpackBoolArray(
|
|
|
231
305
|
idx += 1
|
|
232
306
|
end
|
|
233
307
|
end
|
|
234
|
-
return
|
|
235
|
-
end
|
|
236
|
-
|
|
237
|
-
--[[
|
|
238
|
-
Build a byte-equality delta codec around any inner codec. Wire:
|
|
239
|
-
[DELTA_UNCHANGED] 1 byte
|
|
240
|
-
[DELTA_FULL] <inner-encoded payload> 1 + N bytes
|
|
241
|
-
Used by deltaArray and deltaMap. The FULL path encodes once into scratch
|
|
242
|
-
then bufCopies into the channel — avoids a double encode.
|
|
243
|
-
]]
|
|
244
|
-
function Shared.makeUnchangedOrFullDelta(
|
|
245
|
-
inner: Types.InternalCodec<any>,
|
|
246
|
-
deltaId: number,
|
|
247
|
-
Baseline: {
|
|
248
|
-
getCache: (number) -> any,
|
|
249
|
-
setCache: (number, any) -> (),
|
|
250
|
-
}
|
|
251
|
-
): Types.InternalCodec<any>
|
|
252
|
-
return table.freeze({
|
|
253
|
-
_isDelta = true,
|
|
254
|
-
|
|
255
|
-
write = function(ch: Types.ChannelState, value: any): ()
|
|
256
|
-
local scratch = Shared.acquireScratch()
|
|
257
|
-
inner.write(scratch, value)
|
|
258
|
-
local scratchLen = scratch.cursor
|
|
259
|
-
|
|
260
|
-
local cache = ch.deltas[deltaId]
|
|
261
|
-
if
|
|
262
|
-
cache
|
|
263
|
-
and scratchLen == cache.len
|
|
264
|
-
and rangeEqual(scratch.buff, 0, cache.raw, 0, scratchLen)
|
|
265
|
-
then
|
|
266
|
-
local cursor = ch.cursor
|
|
267
|
-
if cursor + 1 > ch.size then
|
|
268
|
-
alloc(ch, 1)
|
|
269
|
-
end
|
|
270
|
-
writeu8(ch.buff, cursor, DELTA_UNCHANGED)
|
|
271
|
-
ch.cursor = cursor + 1
|
|
272
|
-
Shared.releaseScratch()
|
|
273
|
-
return
|
|
274
|
-
end
|
|
275
|
-
|
|
276
|
-
local cursor = ch.cursor
|
|
277
|
-
if cursor + 1 + scratchLen > ch.size then
|
|
278
|
-
alloc(ch, 1 + scratchLen)
|
|
279
|
-
end
|
|
280
|
-
local b = ch.buff
|
|
281
|
-
writeu8(b, cursor, DELTA_FULL)
|
|
282
|
-
if scratchLen > 0 then
|
|
283
|
-
bufCopy(b, cursor + 1, scratch.buff, 0, scratchLen)
|
|
284
|
-
end
|
|
285
|
-
ch.cursor = cursor + 1 + scratchLen
|
|
286
|
-
|
|
287
|
-
local snap = snapshot(scratch.buff, scratchLen)
|
|
288
|
-
if cache then
|
|
289
|
-
cache.raw = snap
|
|
290
|
-
cache.len = scratchLen
|
|
291
|
-
else
|
|
292
|
-
ch.deltas[deltaId] = { raw = snap, len = scratchLen }
|
|
293
|
-
end
|
|
294
|
-
Shared.releaseScratch()
|
|
295
|
-
end,
|
|
296
|
-
|
|
297
|
-
read = function(src: buffer, pos: number, refs: { Instance }?): (any, number)
|
|
298
|
-
if readu8(src, pos) == DELTA_UNCHANGED then
|
|
299
|
-
local cached = Baseline.getCache(deltaId)
|
|
300
|
-
if not cached then
|
|
301
|
-
Log.error("UNCHANGED flag before any FULL frame; baseline missing")
|
|
302
|
-
end
|
|
303
|
-
return cached, 1
|
|
304
|
-
end
|
|
305
|
-
local value, consumed = inner.read(src, pos + 1, refs)
|
|
306
|
-
Baseline.setCache(deltaId, value)
|
|
307
|
-
return value, 1 + consumed
|
|
308
|
-
end,
|
|
309
|
-
})
|
|
308
|
+
return bc
|
|
310
309
|
end
|
|
311
310
|
|
|
312
311
|
return table.freeze(Shared)
|
|
@@ -55,6 +55,7 @@ local packBools = Shared.packBools
|
|
|
55
55
|
local unpackBools = Shared.unpackBools
|
|
56
56
|
local rangeEqual = Shared.rangeEqual
|
|
57
57
|
local snapshot = Shared.snapshot
|
|
58
|
+
local refreshCache = Shared.refreshCache
|
|
58
59
|
local acquireScratch = Shared.acquireScratch
|
|
59
60
|
local releaseScratch = Shared.releaseScratch
|
|
60
61
|
local allocDeltaId = Shared.allocDeltaId
|
|
@@ -89,14 +90,18 @@ function Struct.struct(schema: { [string]: Types.InternalCodec<any> }): Types.In
|
|
|
89
90
|
local boolCount = #boolKeys
|
|
90
91
|
local boolBytes = Shared.boolBytes(boolCount)
|
|
91
92
|
|
|
92
|
-
-- Single pass: detect all-direct fast-write path + propagate _hasUnknown.
|
|
93
|
+
-- Single pass: detect all-direct fast-write path + propagate _hasUnknown / _hasDelta.
|
|
93
94
|
local allDirect, fixedSize = true, 0
|
|
94
95
|
local hasUnknown = false
|
|
96
|
+
local hasDelta = false
|
|
95
97
|
for i = 1, dataCount do
|
|
96
98
|
local c = dataCodecs[i]
|
|
97
99
|
if c._hasUnknown then
|
|
98
100
|
hasUnknown = true
|
|
99
101
|
end
|
|
102
|
+
if Shared.containsDelta(c) then
|
|
103
|
+
hasDelta = true
|
|
104
|
+
end
|
|
100
105
|
if allDirect then
|
|
101
106
|
if c._size and c._directWrite and c._directRead then
|
|
102
107
|
fixedSize += c._size
|
|
@@ -160,7 +165,7 @@ function Struct.struct(schema: { [string]: Types.InternalCodec<any> }): Types.In
|
|
|
160
165
|
result[dataKeys[i]] = directReads[i](b, off + offsets[i])
|
|
161
166
|
end
|
|
162
167
|
if boolCount > 0 then
|
|
163
|
-
unpackBools(b, off + fixedSize, boolKeys, boolCount, result)
|
|
168
|
+
unpackBools(b, off + fixedSize, boolKeys, boolCount, result, boolBytes)
|
|
164
169
|
end
|
|
165
170
|
return result
|
|
166
171
|
end,
|
|
@@ -176,7 +181,7 @@ function Struct.struct(schema: { [string]: Types.InternalCodec<any> }): Types.In
|
|
|
176
181
|
end
|
|
177
182
|
ch.cursor = cursor + fixedSize
|
|
178
183
|
if boolCount > 0 then
|
|
179
|
-
packBools(ch, value, boolKeys, boolCount)
|
|
184
|
+
packBools(ch, value, boolKeys, boolCount, boolBytes)
|
|
180
185
|
end
|
|
181
186
|
end,
|
|
182
187
|
|
|
@@ -186,7 +191,7 @@ function Struct.struct(schema: { [string]: Types.InternalCodec<any> }): Types.In
|
|
|
186
191
|
result[dataKeys[i]] = directReads[i](src, pos + offsets[i])
|
|
187
192
|
end
|
|
188
193
|
if boolCount > 0 then
|
|
189
|
-
unpackBools(src, pos + fixedSize, boolKeys, boolCount, result)
|
|
194
|
+
unpackBools(src, pos + fixedSize, boolKeys, boolCount, result, boolBytes)
|
|
190
195
|
end
|
|
191
196
|
return result, totalFixed
|
|
192
197
|
end,
|
|
@@ -194,6 +199,9 @@ function Struct.struct(schema: { [string]: Types.InternalCodec<any> }): Types.In
|
|
|
194
199
|
if hasUnknown then
|
|
195
200
|
codec._hasUnknown = true
|
|
196
201
|
end
|
|
202
|
+
if hasDelta then
|
|
203
|
+
codec._hasDelta = true
|
|
204
|
+
end
|
|
197
205
|
return table.freeze(codec)
|
|
198
206
|
end
|
|
199
207
|
|
|
@@ -205,7 +213,7 @@ function Struct.struct(schema: { [string]: Types.InternalCodec<any> }): Types.In
|
|
|
205
213
|
dataCodecs[i].write(ch, value[dataKeys[i]])
|
|
206
214
|
end
|
|
207
215
|
if boolCount > 0 then
|
|
208
|
-
packBools(ch, value, boolKeys, boolCount)
|
|
216
|
+
packBools(ch, value, boolKeys, boolCount, boolBytes)
|
|
209
217
|
end
|
|
210
218
|
end,
|
|
211
219
|
|
|
@@ -218,7 +226,7 @@ function Struct.struct(schema: { [string]: Types.InternalCodec<any> }): Types.In
|
|
|
218
226
|
total += consumed
|
|
219
227
|
end
|
|
220
228
|
if boolCount > 0 then
|
|
221
|
-
total += unpackBools(src, pos + total, boolKeys, boolCount, result)
|
|
229
|
+
total += unpackBools(src, pos + total, boolKeys, boolCount, result, boolBytes)
|
|
222
230
|
end
|
|
223
231
|
return result, total
|
|
224
232
|
end,
|
|
@@ -226,6 +234,9 @@ function Struct.struct(schema: { [string]: Types.InternalCodec<any> }): Types.In
|
|
|
226
234
|
if hasUnknown then
|
|
227
235
|
codec._hasUnknown = true
|
|
228
236
|
end
|
|
237
|
+
if hasDelta then
|
|
238
|
+
codec._hasDelta = true
|
|
239
|
+
end
|
|
229
240
|
return table.freeze(codec)
|
|
230
241
|
end
|
|
231
242
|
|
|
@@ -305,7 +316,7 @@ function Struct.deltaStruct(
|
|
|
305
316
|
refs: { Instance }?
|
|
306
317
|
): number
|
|
307
318
|
if hasBoolSeg and segIdx == boolSegIdx then
|
|
308
|
-
return unpackBools(src, pos, boolKeys, boolCount, target)
|
|
319
|
+
return unpackBools(src, pos, boolKeys, boolCount, target, boolBytes)
|
|
309
320
|
end
|
|
310
321
|
local v, consumed = dataCodecs[segIdx].read(src, pos, refs)
|
|
311
322
|
target[dataKeys[segIdx]] = v
|
|
@@ -350,7 +361,7 @@ function Struct.deltaStruct(
|
|
|
350
361
|
prev = cur
|
|
351
362
|
end
|
|
352
363
|
if hasBoolSeg then
|
|
353
|
-
packBools(scratch, value, boolKeys, boolCount)
|
|
364
|
+
packBools(scratch, value, boolKeys, boolCount, boolBytes)
|
|
354
365
|
segOff[boolSegIdx] = prev
|
|
355
366
|
segLen[boolSegIdx] = scratch.cursor - prev
|
|
356
367
|
end
|
|
@@ -421,8 +432,7 @@ function Struct.deltaStruct(
|
|
|
421
432
|
end
|
|
422
433
|
end
|
|
423
434
|
|
|
424
|
-
cache
|
|
425
|
-
cache.len = scratchLen;
|
|
435
|
+
refreshCache(cache, scratch.buff, scratchLen);
|
|
426
436
|
(cache :: any).segOff = segOff;
|
|
427
437
|
(cache :: any).segLen = segLen
|
|
428
438
|
releaseScratch()
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
local Channel = require(script.Parent.Parent.Parent.internal.Channel)
|
|
6
6
|
local Log = require(script.Parent.Parent.Parent.util.Log)
|
|
7
|
+
local Shared = require(script.Parent.Parent.composite.Shared)
|
|
7
8
|
local Types = require(script.Parent.Parent.Parent.Types)
|
|
8
9
|
|
|
9
10
|
-- Private ----------------------------------------------------------------
|
|
@@ -37,6 +38,7 @@ function Tagged.define(
|
|
|
37
38
|
local nameToTag: { [string]: number } = {}
|
|
38
39
|
local tagToCodec = table.create(variantCount)
|
|
39
40
|
local hasUnknown = false
|
|
41
|
+
local hasDelta = false
|
|
40
42
|
|
|
41
43
|
for i = 1, variantCount do
|
|
42
44
|
local name = names[i]
|
|
@@ -46,6 +48,9 @@ function Tagged.define(
|
|
|
46
48
|
if codec._hasUnknown then
|
|
47
49
|
hasUnknown = true
|
|
48
50
|
end
|
|
51
|
+
if Shared.containsDelta(codec) then
|
|
52
|
+
hasDelta = true
|
|
53
|
+
end
|
|
49
54
|
end
|
|
50
55
|
|
|
51
56
|
table.freeze(names)
|
|
@@ -89,6 +94,9 @@ function Tagged.define(
|
|
|
89
94
|
if hasUnknown then
|
|
90
95
|
codec._hasUnknown = true
|
|
91
96
|
end
|
|
97
|
+
if hasDelta then
|
|
98
|
+
codec._hasDelta = true
|
|
99
|
+
end
|
|
92
100
|
return table.freeze(codec)
|
|
93
101
|
end
|
|
94
102
|
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
local Base = require(script.Parent.Parent.Base)
|
|
6
6
|
local Log = require(script.Parent.Parent.Parent.util.Log)
|
|
7
|
+
local Shared = require(script.Parent.Parent.composite.Shared)
|
|
7
8
|
local Types = require(script.Parent.Parent.Parent.Types)
|
|
8
9
|
|
|
9
10
|
-- Private ----------------------------------------------------------------
|
|
@@ -19,13 +20,17 @@ function Tuple.define(...: Types.InternalCodec<any>): Types.InternalCodec<any>
|
|
|
19
20
|
local count = #codecs
|
|
20
21
|
Log.assert(count > 0, "requires at least one codec")
|
|
21
22
|
|
|
22
|
-
-- Single pass: detect all-direct fast path + propagate _hasUnknown.
|
|
23
|
+
-- Single pass: detect all-direct fast path + propagate _hasUnknown / _hasDelta.
|
|
23
24
|
local allDirect, totalSize, hasUnknown = true, 0, false
|
|
25
|
+
local hasDelta = false
|
|
24
26
|
for i = 1, count do
|
|
25
27
|
local c = codecs[i]
|
|
26
28
|
if c._hasUnknown then
|
|
27
29
|
hasUnknown = true
|
|
28
30
|
end
|
|
31
|
+
if Shared.containsDelta(c) then
|
|
32
|
+
hasDelta = true
|
|
33
|
+
end
|
|
29
34
|
if allDirect then
|
|
30
35
|
if c._size and c._directWrite and c._directRead then
|
|
31
36
|
totalSize += c._size
|
|
@@ -96,6 +101,9 @@ function Tuple.define(...: Types.InternalCodec<any>): Types.InternalCodec<any>
|
|
|
96
101
|
if hasUnknown then
|
|
97
102
|
codec._hasUnknown = true
|
|
98
103
|
end
|
|
104
|
+
if hasDelta then
|
|
105
|
+
codec._hasDelta = true
|
|
106
|
+
end
|
|
99
107
|
return table.freeze(codec)
|
|
100
108
|
end
|
|
101
109
|
|
|
@@ -126,6 +134,9 @@ function Tuple.define(...: Types.InternalCodec<any>): Types.InternalCodec<any>
|
|
|
126
134
|
if hasUnknown then
|
|
127
135
|
codec._hasUnknown = true
|
|
128
136
|
end
|
|
137
|
+
if hasDelta then
|
|
138
|
+
codec._hasDelta = true
|
|
139
|
+
end
|
|
129
140
|
return table.freeze(codec)
|
|
130
141
|
end
|
|
131
142
|
|
|
@@ -23,9 +23,7 @@ local function writeBuffer(ch: Types.ChannelState, value: buffer): ()
|
|
|
23
23
|
end
|
|
24
24
|
local b = ch.buff
|
|
25
25
|
buffer.writeu8(b, cursor, len)
|
|
26
|
-
|
|
27
|
-
buffer.copy(b, cursor + 1, value, 0, len)
|
|
28
|
-
end
|
|
26
|
+
buffer.copy(b, cursor + 1, value, 0, len)
|
|
29
27
|
ch.cursor = cursor + 1 + len
|
|
30
28
|
return
|
|
31
29
|
end
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
-- CFrame codec. Default lossless (24B); __call yields smallest-three quat (16B).
|
|
4
4
|
|
|
5
5
|
local Base = require(script.Parent.Parent.Base)
|
|
6
|
+
local Quat = require(script.Parent.Parent.Parent.util.Quat)
|
|
6
7
|
local Types = require(script.Parent.Parent.Parent.Types)
|
|
7
8
|
|
|
8
9
|
-- Constants --------------------------------------------------------------
|
|
@@ -10,10 +11,6 @@ local Types = require(script.Parent.Parent.Parent.Types)
|
|
|
10
11
|
local EPSILON = 2.4e-7
|
|
11
12
|
local EPSILON_SQ = EPSILON * EPSILON
|
|
12
13
|
|
|
13
|
-
local INV_SQRT2 = 1 / math.sqrt(2)
|
|
14
|
-
local Q_SCALE = 1023 / (2 * INV_SQRT2)
|
|
15
|
-
local Q_INV = (2 * INV_SQRT2) / 1023
|
|
16
|
-
|
|
17
14
|
-- Private ----------------------------------------------------------------
|
|
18
15
|
|
|
19
16
|
local alloc = Base.alloc
|
|
@@ -21,19 +18,10 @@ local writef32 = buffer.writef32
|
|
|
21
18
|
local readf32 = buffer.readf32
|
|
22
19
|
local writeu32 = buffer.writeu32
|
|
23
20
|
local readu32 = buffer.readu32
|
|
24
|
-
|
|
25
21
|
local sqrt = math.sqrt
|
|
26
|
-
local
|
|
27
|
-
local
|
|
28
|
-
local
|
|
29
|
-
local round = math.round
|
|
30
|
-
local acos = math.acos
|
|
31
|
-
local clamp = math.clamp
|
|
32
|
-
|
|
33
|
-
local band = bit32.band
|
|
34
|
-
local bor = bit32.bor
|
|
35
|
-
local lshift = bit32.lshift
|
|
36
|
-
local rshift = bit32.rshift
|
|
22
|
+
local quatPack = Quat.pack
|
|
23
|
+
local quatUnpack = Quat.unpack
|
|
24
|
+
local quatFromCFrame = Quat.fromCFrame
|
|
37
25
|
|
|
38
26
|
local function writeLossless(b: buffer, off: number, value: CFrame): ()
|
|
39
27
|
local pos = value.Position
|
|
@@ -82,102 +70,14 @@ local function writeCompressed(b: buffer, off: number, value: CFrame): ()
|
|
|
82
70
|
writef32(b, off, pos.X)
|
|
83
71
|
writef32(b, off + 4, pos.Y)
|
|
84
72
|
writef32(b, off + 8, pos.Z)
|
|
85
|
-
|
|
86
|
-
local axis, angle = value:ToAxisAngle()
|
|
87
|
-
local h = angle * 0.5
|
|
88
|
-
local s = sin(h)
|
|
89
|
-
local qx = axis.X * s
|
|
90
|
-
local qy = axis.Y * s
|
|
91
|
-
local qz = axis.Z * s
|
|
92
|
-
local qw = cos(h)
|
|
93
|
-
|
|
94
|
-
-- Smallest-three: drop the largest |component|; track index alongside maxVal.
|
|
95
|
-
local ax, ay, az, aw = abs(qx), abs(qy), abs(qz), abs(qw)
|
|
96
|
-
local largest, maxVal = 3, aw
|
|
97
|
-
if ax > maxVal then
|
|
98
|
-
largest, maxVal = 0, ax
|
|
99
|
-
end
|
|
100
|
-
if ay > maxVal then
|
|
101
|
-
largest, maxVal = 1, ay
|
|
102
|
-
end
|
|
103
|
-
if az > maxVal then
|
|
104
|
-
largest, maxVal = 2, az
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
--[[
|
|
108
|
-
Force the dropped component non-negative so the decoder's
|
|
109
|
-
+sqrt(1 - a^2 - b^2 - c^2) reconstructs the right sign. Dispatch on
|
|
110
|
-
`largest` instead of building a 4-element table per call.
|
|
111
|
-
]]
|
|
112
|
-
local a: number, bComp: number, c: number
|
|
113
|
-
if largest == 0 then
|
|
114
|
-
if qx < 0 then
|
|
115
|
-
qy, qz, qw = -qy, -qz, -qw
|
|
116
|
-
end
|
|
117
|
-
a, bComp, c = qy, qz, qw
|
|
118
|
-
elseif largest == 1 then
|
|
119
|
-
if qy < 0 then
|
|
120
|
-
qx, qz, qw = -qx, -qz, -qw
|
|
121
|
-
end
|
|
122
|
-
a, bComp, c = qx, qz, qw
|
|
123
|
-
elseif largest == 2 then
|
|
124
|
-
if qz < 0 then
|
|
125
|
-
qx, qy, qw = -qx, -qy, -qw
|
|
126
|
-
end
|
|
127
|
-
a, bComp, c = qx, qy, qw
|
|
128
|
-
else
|
|
129
|
-
if qw < 0 then
|
|
130
|
-
qx, qy, qz = -qx, -qy, -qz
|
|
131
|
-
end
|
|
132
|
-
a, bComp, c = qx, qy, qz
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
local qa = clamp(round((a + INV_SQRT2) * Q_SCALE), 0, 1023)
|
|
136
|
-
local qb = clamp(round((bComp + INV_SQRT2) * Q_SCALE), 0, 1023)
|
|
137
|
-
local qc = clamp(round((c + INV_SQRT2) * Q_SCALE), 0, 1023)
|
|
138
|
-
|
|
139
|
-
writeu32(b, off + 12, bor(lshift(largest, 30), lshift(qa, 20), lshift(qb, 10), qc))
|
|
73
|
+
writeu32(b, off + 12, quatPack(quatFromCFrame(value)))
|
|
140
74
|
end
|
|
141
75
|
|
|
142
76
|
local function readCompressed(b: buffer, off: number): CFrame
|
|
143
77
|
local px = readf32(b, off)
|
|
144
78
|
local py = readf32(b, off + 4)
|
|
145
79
|
local pz = readf32(b, off + 8)
|
|
146
|
-
|
|
147
|
-
local packed = readu32(b, off + 12)
|
|
148
|
-
local largest = rshift(packed, 30)
|
|
149
|
-
local qa = band(rshift(packed, 20), 0x3FF)
|
|
150
|
-
local qb = band(rshift(packed, 10), 0x3FF)
|
|
151
|
-
local qc = band(packed, 0x3FF)
|
|
152
|
-
|
|
153
|
-
local a = qa * Q_INV - INV_SQRT2
|
|
154
|
-
local bComp = qb * Q_INV - INV_SQRT2
|
|
155
|
-
local c = qc * Q_INV - INV_SQRT2
|
|
156
|
-
|
|
157
|
-
local sqSum = a * a + bComp * bComp + c * c
|
|
158
|
-
local d = sqrt(if sqSum < 1 then 1 - sqSum else 0)
|
|
159
|
-
|
|
160
|
-
-- Mirror encoder: dropped index gets `d`, others receive (a, bComp, c).
|
|
161
|
-
local qx: number, qy: number, qz: number, qw: number
|
|
162
|
-
if largest == 0 then
|
|
163
|
-
qx, qy, qz, qw = d, a, bComp, c
|
|
164
|
-
elseif largest == 1 then
|
|
165
|
-
qx, qy, qz, qw = a, d, bComp, c
|
|
166
|
-
elseif largest == 2 then
|
|
167
|
-
qx, qy, qz, qw = a, bComp, d, c
|
|
168
|
-
else
|
|
169
|
-
qx, qy, qz, qw = a, bComp, c, d
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
local origin = CFrame.new(px, py, pz)
|
|
173
|
-
local sqLen = qx * qx + qy * qy + qz * qz
|
|
174
|
-
if sqLen < EPSILON_SQ then
|
|
175
|
-
return origin
|
|
176
|
-
end
|
|
177
|
-
|
|
178
|
-
local angle = 2 * acos(clamp(qw, -1, 1))
|
|
179
|
-
local inv = 1 / sqrt(sqLen)
|
|
180
|
-
return origin * CFrame.fromAxisAngle(Vector3.new(qx * inv, qy * inv, qz * inv), angle)
|
|
80
|
+
return CFrame.new(px, py, pz) * quatUnpack(readu32(b, off + 12))
|
|
181
81
|
end
|
|
182
82
|
|
|
183
83
|
local function compressedFactory(_self): Types.InternalCodec<CFrame>
|