@axpecter/lync 2.3.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 +29 -19
- package/package.json +1 -1
- package/src/Types.luau +12 -6
- package/src/api/Packet.luau +95 -35
- package/src/codec/Base.luau +7 -0
- package/src/codec/composite/Array.luau +274 -15
- package/src/codec/composite/Map.luau +328 -39
- package/src/codec/composite/Optional.luau +4 -0
- package/src/codec/composite/Shared.luau +60 -81
- package/src/codec/composite/Struct.luau +13 -3
- 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/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 +60 -25
- package/src/transport/Reader.luau +16 -5
- package/src/util/Buffer.luau +2 -4
- package/src/util/Quantize.luau +27 -3
- package/src/util/Quat.luau +124 -0
|
@@ -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>
|
|
@@ -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)
|
|
@@ -35,7 +35,7 @@ local U32_MAX = Constants.U32_MAX
|
|
|
35
35
|
|
|
36
36
|
-- Private ----------------------------------------------------------------
|
|
37
37
|
|
|
38
|
-
local
|
|
38
|
+
local ensure = Base.ensure
|
|
39
39
|
local band = bit32.band
|
|
40
40
|
local bor = bit32.bor
|
|
41
41
|
local rshift = bit32.rshift
|
|
@@ -46,18 +46,24 @@ local readu8 = buffer.readu8
|
|
|
46
46
|
local readu16 = buffer.readu16
|
|
47
47
|
local readu32 = buffer.readu32
|
|
48
48
|
|
|
49
|
-
local function ensure(ch: Types.ChannelState, n: number): ()
|
|
50
|
-
if ch.cursor + n > ch.size then
|
|
51
|
-
alloc(ch, n)
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
|
-
|
|
55
49
|
-- Public -----------------------------------------------------------------
|
|
56
50
|
|
|
57
51
|
local Varint = {}
|
|
58
52
|
|
|
59
53
|
Varint.INLINE_MAX = INLINE_MAX
|
|
60
54
|
|
|
55
|
+
-- Wire byte count for a value, without encoding it. Used by cost heuristics.
|
|
56
|
+
function Varint.length(value: number): number
|
|
57
|
+
if value <= INLINE_MAX then
|
|
58
|
+
return 1
|
|
59
|
+
elseif value <= TWO_MAX then
|
|
60
|
+
return 2
|
|
61
|
+
elseif value <= THREE_MAX then
|
|
62
|
+
return 3
|
|
63
|
+
end
|
|
64
|
+
return 5
|
|
65
|
+
end
|
|
66
|
+
|
|
61
67
|
function Varint.write(ch: Types.ChannelState, value: number): ()
|
|
62
68
|
if value < 0 or value > U32_MAX or value % 1 ~= 0 then
|
|
63
69
|
Log.error(`value must be an integer in [0, {U32_MAX}], got {value}`)
|