@axpecter/lync 2.3.2 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +302 -299
- package/Types.luau +215 -0
- package/api/Define.luau +154 -0
- package/api/Lifecycle.luau +119 -0
- package/api/Packet.luau +108 -0
- package/api/Query.luau +149 -0
- package/api/Set.luau +189 -0
- package/channel/Blocks.luau +124 -0
- package/channel/Group.luau +118 -0
- package/channel/Link.luau +135 -0
- package/channel/Outbox.luau +261 -0
- package/channel/Recipients.luau +129 -0
- package/channel/Transport.luau +138 -0
- package/codec/Base.luau +236 -0
- package/codec/Composites.luau +128 -0
- package/codec/Datatypes.luau +162 -0
- package/codec/Legality.luau +61 -0
- package/codec/Scalars.luau +72 -0
- package/codec/Text.luau +89 -0
- package/codec/init.luau +39 -0
- package/index.d.ts +285 -0
- package/init.luau +91 -0
- package/kernel/Bits.luau +16 -0
- package/kernel/Catalog.luau +121 -0
- package/kernel/Heap.luau +80 -0
- package/kernel/Listeners.luau +95 -0
- package/kernel/Log.luau +197 -0
- package/kernel/Tally.luau +128 -0
- package/kernel/Threads.luau +44 -0
- package/kernel/Wide.luau +267 -0
- package/package.json +15 -32
- package/runtime/Boundary.luau +180 -0
- package/runtime/Dispatch.luau +91 -0
- package/runtime/Handles.luau +114 -0
- package/runtime/Handshake.luau +90 -0
- package/runtime/Pending.luau +196 -0
- package/runtime/Router.luau +379 -0
- package/runtime/Session.luau +731 -0
- package/shape/Compile.luau +340 -0
- package/shape/Folds.luau +332 -0
- package/shape/Frames.luau +170 -0
- package/shape/Grid.luau +169 -0
- package/shape/Ir.luau +540 -0
- package/shape/Joins.luau +125 -0
- package/shape/Legality.luau +273 -0
- package/shape/Lower.luau +419 -0
- package/shape/Order.luau +64 -0
- package/shape/Regions.luau +113 -0
- package/shape/Rotation.luau +472 -0
- package/shape/Serial.luau +258 -0
- package/shape/Words.luau +89 -0
- package/shape/emit/Block.luau +453 -0
- package/shape/emit/Column.luau +338 -0
- package/shape/emit/Leaf.luau +462 -0
- package/shape/emit/Word.luau +234 -0
- package/shape/emit/init.luau +222 -0
- package/shape/init.luau +78 -0
- package/state/Apply.luau +330 -0
- package/state/Audience.luau +126 -0
- package/state/Catchup.luau +218 -0
- package/state/Coalesce.luau +43 -0
- package/state/Fieldset.luau +177 -0
- package/state/Newest.luau +339 -0
- package/state/Plan.luau +441 -0
- package/state/Sections.luau +468 -0
- package/state/Store.luau +188 -0
- package/state/Sync.luau +762 -0
- package/wire/Bytes.luau +118 -0
- package/wire/Clock.luau +37 -0
- package/wire/Protocol.luau +110 -0
- package/wire/Sequence.luau +40 -0
- package/wire/Varint.luau +124 -0
- package/src/Types.luau +0 -180
- package/src/api/Group.luau +0 -156
- package/src/api/Packet.luau +0 -319
- package/src/api/Query.luau +0 -338
- package/src/api/Scope.luau +0 -90
- package/src/api/Signal.luau +0 -188
- package/src/codec/Base.luau +0 -136
- package/src/codec/composite/Array.luau +0 -456
- package/src/codec/composite/Map.luau +0 -469
- package/src/codec/composite/Optional.luau +0 -54
- package/src/codec/composite/Shared.luau +0 -291
- package/src/codec/composite/Struct.luau +0 -494
- package/src/codec/composite/Tagged.luau +0 -103
- package/src/codec/composite/Tuple.luau +0 -143
- package/src/codec/datatype/Buffer.luau +0 -66
- package/src/codec/datatype/CFrame.luau +0 -109
- package/src/codec/datatype/Color.luau +0 -32
- package/src/codec/datatype/Instance.luau +0 -52
- package/src/codec/datatype/IntVector.luau +0 -39
- package/src/codec/datatype/NumberRange.luau +0 -39
- package/src/codec/datatype/Ray.luau +0 -36
- package/src/codec/datatype/Rect.luau +0 -31
- package/src/codec/datatype/Region.luau +0 -62
- package/src/codec/datatype/Sequence.luau +0 -184
- package/src/codec/datatype/String.luau +0 -94
- package/src/codec/datatype/UDim.luau +0 -46
- package/src/codec/datatype/Vector.luau +0 -130
- package/src/codec/meta/Auto.luau +0 -318
- package/src/codec/meta/Bitfield.luau +0 -147
- package/src/codec/meta/Custom.luau +0 -33
- package/src/codec/meta/DeltaScalar.luau +0 -390
- package/src/codec/meta/Enum.luau +0 -50
- package/src/codec/meta/Float.luau +0 -38
- package/src/codec/meta/Nothing.luau +0 -13
- package/src/codec/meta/Unknown.luau +0 -41
- package/src/codec/primitive/Bool.luau +0 -26
- package/src/codec/primitive/Float16.luau +0 -99
- package/src/codec/primitive/Int.luau +0 -59
- package/src/codec/primitive/Number.luau +0 -24
- package/src/codec/primitive/Signed.luau +0 -32
- package/src/codec/primitive/Varint.luau +0 -165
- package/src/codec/primitive/Zint.luau +0 -99
- package/src/index.d.ts +0 -423
- package/src/init.luau +0 -362
- package/src/internal/Baseline.luau +0 -57
- package/src/internal/Channel.luau +0 -390
- package/src/internal/Middleware.luau +0 -100
- package/src/internal/Pool.luau +0 -65
- package/src/internal/Registry.luau +0 -119
- package/src/internal/Transport.luau +0 -28
- package/src/transport/Bridge.luau +0 -110
- package/src/transport/Client.luau +0 -97
- package/src/transport/Gate.luau +0 -488
- package/src/transport/Reader.luau +0 -331
- package/src/transport/Server.luau +0 -211
- package/src/util/Array.luau +0 -18
- package/src/util/Buffer.luau +0 -90
- package/src/util/Constants.luau +0 -30
- package/src/util/Log.luau +0 -68
- package/src/util/Player.luau +0 -14
- package/src/util/Quantize.luau +0 -84
- package/src/util/Quat.luau +0 -124
package/src/codec/Base.luau
DELETED
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
--!strict
|
|
2
|
-
--!optimize 2
|
|
3
|
-
-- Fixed-size codec factory + channel buffer-grow primitive.
|
|
4
|
-
|
|
5
|
-
local Log = require(script.Parent.Parent.util.Log)
|
|
6
|
-
local Types = require(script.Parent.Parent.Types)
|
|
7
|
-
|
|
8
|
-
-- Constants --------------------------------------------------------------
|
|
9
|
-
|
|
10
|
-
local DEFAULT_MAX = 262144
|
|
11
|
-
local INITIAL_BUF = 1024
|
|
12
|
-
|
|
13
|
-
-- State ------------------------------------------------------------------
|
|
14
|
-
|
|
15
|
-
local _maxSize = DEFAULT_MAX
|
|
16
|
-
|
|
17
|
-
-- Private ----------------------------------------------------------------
|
|
18
|
-
|
|
19
|
-
local countlz = bit32.countlz
|
|
20
|
-
local lshift = bit32.lshift
|
|
21
|
-
|
|
22
|
-
--[[
|
|
23
|
-
Grow `ch.buff` so cursor + bytes fits. New size is the next power of two
|
|
24
|
-
>= needed, capped by _maxSize. Callers MUST re-read `ch.buff` after
|
|
25
|
-
return: the buffer reference is replaced.
|
|
26
|
-
]]
|
|
27
|
-
local function alloc(ch: Types.ChannelState, bytes: number): ()
|
|
28
|
-
if bytes == 0 then
|
|
29
|
-
return
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
local needed = ch.cursor + bytes
|
|
33
|
-
if needed <= ch.size then
|
|
34
|
-
return
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
if needed > _maxSize then
|
|
38
|
-
local name = ch.currentPacket
|
|
39
|
-
if name then
|
|
40
|
-
Log.error(`packet "{name}" overflowed at {needed}B (max {_maxSize}B)`)
|
|
41
|
-
end
|
|
42
|
-
Log.error(`overflowed at {needed}B (max {_maxSize}B)`)
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
local newSize = lshift(1, 32 - countlz(needed - 1))
|
|
46
|
-
local newBuff = buffer.create(newSize)
|
|
47
|
-
buffer.copy(newBuff, 0, ch.buff, 0, ch.cursor)
|
|
48
|
-
ch.buff = newBuff
|
|
49
|
-
ch.size = newSize
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
-- Public -----------------------------------------------------------------
|
|
53
|
-
|
|
54
|
-
local Base = {}
|
|
55
|
-
|
|
56
|
-
Base.INITIAL_BUF = INITIAL_BUF
|
|
57
|
-
Base.alloc = alloc
|
|
58
|
-
|
|
59
|
-
-- Cheap inline guard hoisted into every codec that grows a channel directly.
|
|
60
|
-
function Base.ensure(ch: Types.ChannelState, n: number): ()
|
|
61
|
-
if ch.cursor + n > ch.size then
|
|
62
|
-
alloc(ch, n)
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
-- Floor is INITIAL_BUF: anything smaller errors on the very first write.
|
|
67
|
-
function Base.setMaxSize(bytes: number): ()
|
|
68
|
-
if bytes < INITIAL_BUF then
|
|
69
|
-
Log.error(`bytes ({bytes}) must be >= {INITIAL_BUF}`)
|
|
70
|
-
end
|
|
71
|
-
_maxSize = bytes
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
function Base.reset(): ()
|
|
75
|
-
_maxSize = DEFAULT_MAX
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
--[[
|
|
79
|
-
Build a fixed-size codec. Exposes channel-level write/read (handles
|
|
80
|
-
buffer growth + cursor) plus raw _directWrite/_directRead for composite
|
|
81
|
-
fast paths that pre-size the channel themselves.
|
|
82
|
-
]]
|
|
83
|
-
function Base.define<T>(
|
|
84
|
-
size: number,
|
|
85
|
-
directWrite: (b: buffer, offset: number, value: T) -> (),
|
|
86
|
-
directRead: (b: buffer, offset: number) -> T,
|
|
87
|
-
meta: { [string]: any }?
|
|
88
|
-
): Types.InternalCodec<T>
|
|
89
|
-
local codec: { [string]: any } = {
|
|
90
|
-
_size = size,
|
|
91
|
-
_directWrite = directWrite,
|
|
92
|
-
_directRead = directRead,
|
|
93
|
-
|
|
94
|
-
write = function(ch: Types.ChannelState, value: T): ()
|
|
95
|
-
local cursor = ch.cursor
|
|
96
|
-
if cursor + size > ch.size then
|
|
97
|
-
alloc(ch, size)
|
|
98
|
-
end
|
|
99
|
-
directWrite(ch.buff, cursor, value)
|
|
100
|
-
ch.cursor = cursor + size
|
|
101
|
-
end,
|
|
102
|
-
|
|
103
|
-
read = function(src: buffer, pos: number, _refs: { Instance }?): (T, number)
|
|
104
|
-
return directRead(src, pos), size
|
|
105
|
-
end,
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if meta then
|
|
109
|
-
for key, val in meta do
|
|
110
|
-
codec[key] = val
|
|
111
|
-
end
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
return table.freeze(codec) :: Types.InternalCodec<T>
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
--[[
|
|
118
|
-
Zero-byte codec: writes nothing, reads always return `value`. Used by
|
|
119
|
-
quantizers when min == max so the wire emits nothing yet decode produces
|
|
120
|
-
a type-faithful value.
|
|
121
|
-
]]
|
|
122
|
-
function Base.constant<T>(value: T): Types.InternalCodec<T>
|
|
123
|
-
return table.freeze({
|
|
124
|
-
_size = 0,
|
|
125
|
-
_directWrite = function(_b: buffer, _off: number, _v: T): () end,
|
|
126
|
-
_directRead = function(_b: buffer, _off: number): T
|
|
127
|
-
return value
|
|
128
|
-
end,
|
|
129
|
-
write = function(_ch: Types.ChannelState, _v: T): () end,
|
|
130
|
-
read = function(_src: buffer, _pos: number, _refs: { Instance }?): (T, number)
|
|
131
|
-
return value, 0
|
|
132
|
-
end,
|
|
133
|
-
}) :: Types.InternalCodec<T>
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
return table.freeze(Base)
|
|
@@ -1,456 +0,0 @@
|
|
|
1
|
-
--!strict
|
|
2
|
-
--!native
|
|
3
|
-
-- array() and deltaArray(). Bool elements are bit-packed.
|
|
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 Shared = require(script.Parent.Parent.composite.Shared)
|
|
9
|
-
local Types = require(script.Parent.Parent.Parent.Types)
|
|
10
|
-
local Varint = require(script.Parent.Parent.primitive.Varint)
|
|
11
|
-
|
|
12
|
-
-- Constants --------------------------------------------------------------
|
|
13
|
-
|
|
14
|
-
-- deltaArray wire flags. Distinct from Shared's struct/map flag spaces.
|
|
15
|
-
local DA_UNCHANGED = 0
|
|
16
|
-
local DA_FULL = 1
|
|
17
|
-
local DA_PATCH = 2
|
|
18
|
-
|
|
19
|
-
-- Private ----------------------------------------------------------------
|
|
20
|
-
|
|
21
|
-
local alloc = Base.alloc
|
|
22
|
-
local ensure = Base.ensure
|
|
23
|
-
local allocDeltaId = Shared.allocDeltaId
|
|
24
|
-
local acquireScratch = Shared.acquireScratch
|
|
25
|
-
local releaseScratch = Shared.releaseScratch
|
|
26
|
-
local rangeEqual = Shared.rangeEqual
|
|
27
|
-
local copyToBuf = Shared.copyToBuf
|
|
28
|
-
local containsDelta = Shared.containsDelta
|
|
29
|
-
local enforceMaxCount = Shared.enforceMaxCount
|
|
30
|
-
local boolBytes = Shared.boolBytes
|
|
31
|
-
local packBoolArray = Shared.packBoolArray
|
|
32
|
-
local unpackBoolArray = Shared.unpackBoolArray
|
|
33
|
-
local varintRead = Varint.read
|
|
34
|
-
local varintWrite = Varint.write
|
|
35
|
-
local varintLength = Varint.length
|
|
36
|
-
local writeu8 = buffer.writeu8
|
|
37
|
-
local readu8 = buffer.readu8
|
|
38
|
-
local bufCopy = buffer.copy
|
|
39
|
-
local bufLen = buffer.len
|
|
40
|
-
|
|
41
|
-
type DeltaArrayEntry = { eBuf: buffer, eLen: number }
|
|
42
|
-
|
|
43
|
-
-- Bit-packed bool element fast path: 1 bit per entry instead of 1 byte.
|
|
44
|
-
local function makeBoolArray(
|
|
45
|
-
element: Types.InternalCodec<any>,
|
|
46
|
-
maxCount: number?
|
|
47
|
-
): Types.InternalCodec<any>
|
|
48
|
-
return {
|
|
49
|
-
_isArray = true,
|
|
50
|
-
_element = element,
|
|
51
|
-
_maxCount = maxCount,
|
|
52
|
-
|
|
53
|
-
write = function(ch: Types.ChannelState, value: { boolean }): ()
|
|
54
|
-
local len = #value
|
|
55
|
-
varintWrite(ch, len)
|
|
56
|
-
packBoolArray(ch, value, len)
|
|
57
|
-
end,
|
|
58
|
-
|
|
59
|
-
read = function(src: buffer, pos: number, _refs: { Instance }?): ({ boolean }, number)
|
|
60
|
-
local len, lenBytes = varintRead(src, pos)
|
|
61
|
-
enforceMaxCount(len, maxCount)
|
|
62
|
-
if len == 0 then
|
|
63
|
-
return {}, lenBytes
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
local byteCount = boolBytes(len)
|
|
67
|
-
local dataStart = pos + lenBytes
|
|
68
|
-
if dataStart + byteCount > bufLen(src) then
|
|
69
|
-
Log.error(`payload {byteCount}B exceeds remaining buffer`)
|
|
70
|
-
end
|
|
71
|
-
local result = table.create(len, false)
|
|
72
|
-
unpackBoolArray(src, dataStart, len, result)
|
|
73
|
-
return result, lenBytes + byteCount
|
|
74
|
-
end,
|
|
75
|
-
}
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
-- Fixed-size element fast path: single alloc + tight loop calling _direct fns.
|
|
79
|
-
local function makeDirectArray(
|
|
80
|
-
element: Types.InternalCodec<any>,
|
|
81
|
-
elemSize: number,
|
|
82
|
-
elemDW: (b: buffer, off: number, value: any) -> (),
|
|
83
|
-
elemDR: (b: buffer, off: number) -> any,
|
|
84
|
-
maxCount: number?
|
|
85
|
-
): Types.InternalCodec<any>
|
|
86
|
-
return {
|
|
87
|
-
_isArray = true,
|
|
88
|
-
_element = element,
|
|
89
|
-
_maxCount = maxCount,
|
|
90
|
-
|
|
91
|
-
write = function(ch: Types.ChannelState, value: { any }): ()
|
|
92
|
-
local len = #value
|
|
93
|
-
varintWrite(ch, len)
|
|
94
|
-
if len == 0 then
|
|
95
|
-
return
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
local payloadBytes = len * elemSize
|
|
99
|
-
local cursor = ch.cursor
|
|
100
|
-
if cursor + payloadBytes > ch.size then
|
|
101
|
-
alloc(ch, payloadBytes)
|
|
102
|
-
end
|
|
103
|
-
local b = ch.buff
|
|
104
|
-
local off = cursor
|
|
105
|
-
for i = 1, len do
|
|
106
|
-
elemDW(b, off, value[i])
|
|
107
|
-
off += elemSize
|
|
108
|
-
end
|
|
109
|
-
ch.cursor = cursor + payloadBytes
|
|
110
|
-
end,
|
|
111
|
-
|
|
112
|
-
read = function(src: buffer, pos: number, _refs: { Instance }?): ({ any }, number)
|
|
113
|
-
local len, lenBytes = varintRead(src, pos)
|
|
114
|
-
enforceMaxCount(len, maxCount)
|
|
115
|
-
if len == 0 then
|
|
116
|
-
return {}, lenBytes
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
local payloadBytes = len * elemSize
|
|
120
|
-
local dataStart = pos + lenBytes
|
|
121
|
-
if payloadBytes > bufLen(src) - dataStart then
|
|
122
|
-
Log.error(`payload {payloadBytes}B exceeds remaining buffer`)
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
local result = table.create(len)
|
|
126
|
-
local off = dataStart
|
|
127
|
-
for i = 1, len do
|
|
128
|
-
result[i] = elemDR(src, off)
|
|
129
|
-
off += elemSize
|
|
130
|
-
end
|
|
131
|
-
return result, lenBytes + payloadBytes
|
|
132
|
-
end,
|
|
133
|
-
}
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
-- Generic fallback: variable-size or composite element codec.
|
|
137
|
-
local function makeGenericArray(
|
|
138
|
-
element: Types.InternalCodec<any>,
|
|
139
|
-
maxCount: number?
|
|
140
|
-
): Types.InternalCodec<any>
|
|
141
|
-
return {
|
|
142
|
-
_isArray = true,
|
|
143
|
-
_element = element,
|
|
144
|
-
_maxCount = maxCount,
|
|
145
|
-
|
|
146
|
-
write = function(ch: Types.ChannelState, value: { any }): ()
|
|
147
|
-
local len = #value
|
|
148
|
-
varintWrite(ch, len)
|
|
149
|
-
for i = 1, len do
|
|
150
|
-
element.write(ch, value[i])
|
|
151
|
-
end
|
|
152
|
-
end,
|
|
153
|
-
|
|
154
|
-
read = function(src: buffer, pos: number, refs: { Instance }?): ({ any }, number)
|
|
155
|
-
local len, lenBytes = varintRead(src, pos)
|
|
156
|
-
enforceMaxCount(len, maxCount)
|
|
157
|
-
if len == 0 then
|
|
158
|
-
return {}, lenBytes
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
local result = table.create(len)
|
|
162
|
-
local total = lenBytes
|
|
163
|
-
for i = 1, len do
|
|
164
|
-
local v, consumed = element.read(src, pos + total, refs)
|
|
165
|
-
if consumed == 0 then
|
|
166
|
-
Log.error(`element codec consumed 0 bytes at index {i}`)
|
|
167
|
-
end
|
|
168
|
-
result[i] = v
|
|
169
|
-
total += consumed
|
|
170
|
-
end
|
|
171
|
-
return result, total
|
|
172
|
-
end,
|
|
173
|
-
}
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
-- Public -----------------------------------------------------------------
|
|
177
|
-
|
|
178
|
-
local Array = {}
|
|
179
|
-
|
|
180
|
-
function Array.array(element: Types.InternalCodec<any>, maxCount: number?): Types.InternalCodec<any>
|
|
181
|
-
local codec: Types.InternalCodec<any>
|
|
182
|
-
if element._isBool then
|
|
183
|
-
codec = makeBoolArray(element, maxCount)
|
|
184
|
-
else
|
|
185
|
-
local elemSize = element._size
|
|
186
|
-
local elemDW = element._directWrite
|
|
187
|
-
local elemDR = element._directRead
|
|
188
|
-
if elemSize and elemDW and elemDR then
|
|
189
|
-
codec = makeDirectArray(element, elemSize, elemDW, elemDR, maxCount)
|
|
190
|
-
else
|
|
191
|
-
codec = makeGenericArray(element, maxCount)
|
|
192
|
-
end
|
|
193
|
-
end
|
|
194
|
-
if element._hasUnknown then
|
|
195
|
-
codec._hasUnknown = true
|
|
196
|
-
end
|
|
197
|
-
if containsDelta(element) then
|
|
198
|
-
codec._hasDelta = true
|
|
199
|
-
end
|
|
200
|
-
return table.freeze(codec)
|
|
201
|
-
end
|
|
202
|
-
|
|
203
|
-
--[[
|
|
204
|
-
Per-index deltaArray. Wire:
|
|
205
|
-
[0] UNCHANGED (1 byte)
|
|
206
|
-
[1] FULL <count varint> <element bytes ...>
|
|
207
|
-
[2] PATCH <newLen varint> <changeCount varint>
|
|
208
|
-
<(idx varint, element bytes) ...>
|
|
209
|
-
|
|
210
|
-
Length shrinkage is implicit in newLen — entries past newLen drop from
|
|
211
|
-
the cached map. Length growth: appended indices show up as changes.
|
|
212
|
-
Reordered lists tend to mark every index changed; the writer falls back
|
|
213
|
-
to FULL when changeCount * 2 >= newLen since PATCH index varints eat
|
|
214
|
-
any savings past that ratio.
|
|
215
|
-
]]
|
|
216
|
-
function Array.deltaArray(
|
|
217
|
-
element: Types.InternalCodec<any>,
|
|
218
|
-
maxCount: number?
|
|
219
|
-
): Types.InternalCodec<any>
|
|
220
|
-
-- Inner delta would chain-diff across elements within a frame, not across frames.
|
|
221
|
-
if containsDelta(element) then
|
|
222
|
-
Log.error("deltaArray element cannot contain delta state")
|
|
223
|
-
end
|
|
224
|
-
|
|
225
|
-
local deltaId = allocDeltaId()
|
|
226
|
-
local inner = Array.array(element, maxCount)
|
|
227
|
-
|
|
228
|
-
--[[
|
|
229
|
-
Encode each element into `scratch` and capture per-index byte ranges.
|
|
230
|
-
Returns the array length plus parallel arrays of offset/length so the
|
|
231
|
-
diff path can compare and emit individual entries without re-encoding.
|
|
232
|
-
]]
|
|
233
|
-
local function encodePerIdx(
|
|
234
|
-
scratch: Types.ChannelState,
|
|
235
|
-
value: { any }
|
|
236
|
-
): (number, { number }, { number })
|
|
237
|
-
local len = #value
|
|
238
|
-
enforceMaxCount(len, maxCount)
|
|
239
|
-
local off = table.create(len)
|
|
240
|
-
local elen = table.create(len)
|
|
241
|
-
for i = 1, len do
|
|
242
|
-
local before = scratch.cursor
|
|
243
|
-
element.write(scratch, value[i])
|
|
244
|
-
off[i] = before
|
|
245
|
-
elen[i] = scratch.cursor - before
|
|
246
|
-
end
|
|
247
|
-
return len, off, elen
|
|
248
|
-
end
|
|
249
|
-
|
|
250
|
-
return table.freeze({
|
|
251
|
-
_isDelta = true,
|
|
252
|
-
_isArray = true,
|
|
253
|
-
_element = element,
|
|
254
|
-
_maxCount = maxCount,
|
|
255
|
-
|
|
256
|
-
write = function(ch: Types.ChannelState, value: { any }): ()
|
|
257
|
-
if typeof(value) ~= "table" then
|
|
258
|
-
Log.error(`expected table, got {typeof(value)}`)
|
|
259
|
-
end
|
|
260
|
-
|
|
261
|
-
local cache = ch.deltas[deltaId] :: any
|
|
262
|
-
local scratch = acquireScratch()
|
|
263
|
-
local len, off, elen = encodePerIdx(scratch, value)
|
|
264
|
-
local scratchBuf = scratch.buff
|
|
265
|
-
|
|
266
|
-
-- First frame: emit FULL using the per-index scratch bytes.
|
|
267
|
-
if not cache then
|
|
268
|
-
ensure(ch, 1)
|
|
269
|
-
writeu8(ch.buff, ch.cursor, DA_FULL)
|
|
270
|
-
ch.cursor += 1
|
|
271
|
-
varintWrite(ch, len)
|
|
272
|
-
local payloadLen = scratch.cursor
|
|
273
|
-
ensure(ch, payloadLen)
|
|
274
|
-
bufCopy(ch.buff, ch.cursor, scratchBuf, 0, payloadLen)
|
|
275
|
-
ch.cursor += payloadLen
|
|
276
|
-
|
|
277
|
-
local perIdx: { [number]: DeltaArrayEntry } = {}
|
|
278
|
-
for i = 1, len do
|
|
279
|
-
local n = elen[i]
|
|
280
|
-
perIdx[i] = { eBuf = copyToBuf(nil, scratchBuf, off[i], n), eLen = n }
|
|
281
|
-
end
|
|
282
|
-
ch.deltas[deltaId] = { perIdx = perIdx, length = len } :: any
|
|
283
|
-
releaseScratch()
|
|
284
|
-
return
|
|
285
|
-
end
|
|
286
|
-
|
|
287
|
-
local cachedPerIdx = cache.perIdx :: { [number]: DeltaArrayEntry }
|
|
288
|
-
local cachedLen = cache.length :: number
|
|
289
|
-
|
|
290
|
-
-- Find changed indices in the overlap; appended indices count as changed.
|
|
291
|
-
local changed: { number } = {}
|
|
292
|
-
local changedCount = 0
|
|
293
|
-
local overlap = if len < cachedLen then len else cachedLen
|
|
294
|
-
for i = 1, overlap do
|
|
295
|
-
local entry = cachedPerIdx[i]
|
|
296
|
-
local n = elen[i]
|
|
297
|
-
if
|
|
298
|
-
not entry
|
|
299
|
-
or n ~= entry.eLen
|
|
300
|
-
or not rangeEqual(scratchBuf, off[i], entry.eBuf, 0, n)
|
|
301
|
-
then
|
|
302
|
-
changedCount += 1
|
|
303
|
-
changed[changedCount] = i
|
|
304
|
-
end
|
|
305
|
-
end
|
|
306
|
-
for i = overlap + 1, len do
|
|
307
|
-
changedCount += 1
|
|
308
|
-
changed[changedCount] = i
|
|
309
|
-
end
|
|
310
|
-
|
|
311
|
-
if changedCount == 0 and len == cachedLen then
|
|
312
|
-
ensure(ch, 1)
|
|
313
|
-
writeu8(ch.buff, ch.cursor, DA_UNCHANGED)
|
|
314
|
-
ch.cursor += 1
|
|
315
|
-
releaseScratch()
|
|
316
|
-
return
|
|
317
|
-
end
|
|
318
|
-
|
|
319
|
-
-- Pick FULL vs PATCH on actual byte cost. Both share `1 + varintLength(len)`
|
|
320
|
-
-- so it cancels: compare PATCH's index-header overhead + changed payload
|
|
321
|
-
-- against FULL's full payload.
|
|
322
|
-
local fullPayload = scratch.cursor
|
|
323
|
-
local patchPayload = varintLength(changedCount)
|
|
324
|
-
for i = 1, changedCount do
|
|
325
|
-
local idx = changed[i]
|
|
326
|
-
patchPayload += varintLength(idx) + elen[idx]
|
|
327
|
-
end
|
|
328
|
-
|
|
329
|
-
if patchPayload < fullPayload then
|
|
330
|
-
ensure(ch, 1)
|
|
331
|
-
writeu8(ch.buff, ch.cursor, DA_PATCH)
|
|
332
|
-
ch.cursor += 1
|
|
333
|
-
varintWrite(ch, len)
|
|
334
|
-
varintWrite(ch, changedCount)
|
|
335
|
-
for i = 1, changedCount do
|
|
336
|
-
local idx = changed[i]
|
|
337
|
-
varintWrite(ch, idx)
|
|
338
|
-
local n = elen[idx]
|
|
339
|
-
ensure(ch, n)
|
|
340
|
-
bufCopy(ch.buff, ch.cursor, scratchBuf, off[idx], n)
|
|
341
|
-
ch.cursor += n
|
|
342
|
-
end
|
|
343
|
-
else
|
|
344
|
-
ensure(ch, 1)
|
|
345
|
-
writeu8(ch.buff, ch.cursor, DA_FULL)
|
|
346
|
-
ch.cursor += 1
|
|
347
|
-
varintWrite(ch, len)
|
|
348
|
-
ensure(ch, fullPayload)
|
|
349
|
-
bufCopy(ch.buff, ch.cursor, scratchBuf, 0, fullPayload)
|
|
350
|
-
ch.cursor += fullPayload
|
|
351
|
-
end
|
|
352
|
-
|
|
353
|
-
-- Update cache: refresh changed (and appended), drop truncated.
|
|
354
|
-
for i = 1, changedCount do
|
|
355
|
-
local idx = changed[i]
|
|
356
|
-
local n = elen[idx]
|
|
357
|
-
local entry = cachedPerIdx[idx]
|
|
358
|
-
if entry then
|
|
359
|
-
entry.eBuf = copyToBuf(entry.eBuf, scratchBuf, off[idx], n)
|
|
360
|
-
entry.eLen = n
|
|
361
|
-
else
|
|
362
|
-
cachedPerIdx[idx] = { eBuf = copyToBuf(nil, scratchBuf, off[idx], n), eLen = n }
|
|
363
|
-
end
|
|
364
|
-
end
|
|
365
|
-
for i = len + 1, cachedLen do
|
|
366
|
-
cachedPerIdx[i] = nil
|
|
367
|
-
end
|
|
368
|
-
cache.length = len
|
|
369
|
-
|
|
370
|
-
releaseScratch()
|
|
371
|
-
end,
|
|
372
|
-
|
|
373
|
-
read = function(src: buffer, pos: number, refs: { Instance }?): ({ any }, number)
|
|
374
|
-
if pos >= bufLen(src) then
|
|
375
|
-
Log.error("truncated deltaArray header")
|
|
376
|
-
end
|
|
377
|
-
local flag = readu8(src, pos)
|
|
378
|
-
|
|
379
|
-
if flag == DA_UNCHANGED then
|
|
380
|
-
local cached = Baseline.getCache(deltaId)
|
|
381
|
-
if cached == nil then
|
|
382
|
-
Log.error("UNCHANGED flag before any FULL frame; baseline missing")
|
|
383
|
-
end
|
|
384
|
-
return cached, 1
|
|
385
|
-
end
|
|
386
|
-
|
|
387
|
-
if flag == DA_FULL then
|
|
388
|
-
local value, consumed = inner.read(src, pos + 1, refs)
|
|
389
|
-
Baseline.setCache(deltaId, value)
|
|
390
|
-
return value, 1 + consumed
|
|
391
|
-
end
|
|
392
|
-
|
|
393
|
-
if flag == DA_PATCH then
|
|
394
|
-
local cached = Baseline.getCache(deltaId)
|
|
395
|
-
if cached == nil then
|
|
396
|
-
Log.error("deltaArray PATCH before any FULL frame; baseline missing")
|
|
397
|
-
end
|
|
398
|
-
--[[
|
|
399
|
-
Capture the cached length BEFORE clone+mutate; #result
|
|
400
|
-
becomes unreliable once we set entries at appended indices
|
|
401
|
-
past the original boundary.
|
|
402
|
-
]]
|
|
403
|
-
local cachedLen = #cached
|
|
404
|
-
local result: { any } = table.clone(cached)
|
|
405
|
-
local total = 1
|
|
406
|
-
|
|
407
|
-
local newLen, lLen = varintRead(src, pos + total)
|
|
408
|
-
if lLen == 0 then
|
|
409
|
-
Log.error("truncated deltaArray length")
|
|
410
|
-
end
|
|
411
|
-
enforceMaxCount(newLen, maxCount, "newLen")
|
|
412
|
-
total += lLen
|
|
413
|
-
|
|
414
|
-
local changeCount, cLen = varintRead(src, pos + total)
|
|
415
|
-
if cLen == 0 then
|
|
416
|
-
Log.error("truncated deltaArray change count")
|
|
417
|
-
end
|
|
418
|
-
-- changeCount is also bounded by newLen: every index appears at most once.
|
|
419
|
-
if changeCount > newLen then
|
|
420
|
-
Log.error(`deltaArray changeCount {changeCount} exceeds newLen {newLen}`)
|
|
421
|
-
end
|
|
422
|
-
total += cLen
|
|
423
|
-
|
|
424
|
-
for _ = 1, changeCount do
|
|
425
|
-
local idx, iLen = varintRead(src, pos + total)
|
|
426
|
-
if iLen == 0 then
|
|
427
|
-
Log.error("truncated deltaArray index")
|
|
428
|
-
end
|
|
429
|
-
-- Reject out-of-range idx so the wire can't poison the cache with
|
|
430
|
-
-- arbitrary keys that survive truncation.
|
|
431
|
-
if idx < 1 or idx > newLen then
|
|
432
|
-
Log.error(`deltaArray index {idx} out of range [1, {newLen}]`)
|
|
433
|
-
end
|
|
434
|
-
total += iLen
|
|
435
|
-
local v, vc = element.read(src, pos + total, refs)
|
|
436
|
-
if vc == 0 then
|
|
437
|
-
Log.error("truncated deltaArray element")
|
|
438
|
-
end
|
|
439
|
-
total += vc
|
|
440
|
-
result[idx] = v
|
|
441
|
-
end
|
|
442
|
-
|
|
443
|
-
for i = newLen + 1, cachedLen do
|
|
444
|
-
result[i] = nil
|
|
445
|
-
end
|
|
446
|
-
|
|
447
|
-
Baseline.setCache(deltaId, result)
|
|
448
|
-
return result, total
|
|
449
|
-
end
|
|
450
|
-
|
|
451
|
-
Log.error(`invalid deltaArray flag {flag}`)
|
|
452
|
-
end,
|
|
453
|
-
}) :: Types.InternalCodec<any>
|
|
454
|
-
end
|
|
455
|
-
|
|
456
|
-
return table.freeze(Array)
|