@axpecter/lync 2.3.3 → 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.
Files changed (135) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +302 -299
  3. package/Types.luau +215 -0
  4. package/api/Define.luau +154 -0
  5. package/api/Lifecycle.luau +119 -0
  6. package/api/Packet.luau +108 -0
  7. package/api/Query.luau +149 -0
  8. package/api/Set.luau +189 -0
  9. package/channel/Blocks.luau +124 -0
  10. package/channel/Group.luau +118 -0
  11. package/channel/Link.luau +135 -0
  12. package/channel/Outbox.luau +261 -0
  13. package/channel/Recipients.luau +129 -0
  14. package/channel/Transport.luau +138 -0
  15. package/codec/Base.luau +236 -0
  16. package/codec/Composites.luau +128 -0
  17. package/codec/Datatypes.luau +162 -0
  18. package/codec/Legality.luau +61 -0
  19. package/codec/Scalars.luau +72 -0
  20. package/codec/Text.luau +89 -0
  21. package/codec/init.luau +39 -0
  22. package/index.d.ts +285 -0
  23. package/init.luau +91 -0
  24. package/kernel/Bits.luau +16 -0
  25. package/kernel/Catalog.luau +121 -0
  26. package/kernel/Heap.luau +80 -0
  27. package/kernel/Listeners.luau +95 -0
  28. package/kernel/Log.luau +197 -0
  29. package/kernel/Tally.luau +128 -0
  30. package/kernel/Threads.luau +44 -0
  31. package/kernel/Wide.luau +267 -0
  32. package/package.json +15 -32
  33. package/runtime/Boundary.luau +180 -0
  34. package/runtime/Dispatch.luau +91 -0
  35. package/runtime/Handles.luau +114 -0
  36. package/runtime/Handshake.luau +90 -0
  37. package/runtime/Pending.luau +196 -0
  38. package/runtime/Router.luau +379 -0
  39. package/runtime/Session.luau +731 -0
  40. package/shape/Compile.luau +340 -0
  41. package/shape/Folds.luau +332 -0
  42. package/shape/Frames.luau +170 -0
  43. package/shape/Grid.luau +169 -0
  44. package/shape/Ir.luau +540 -0
  45. package/shape/Joins.luau +125 -0
  46. package/shape/Legality.luau +273 -0
  47. package/shape/Lower.luau +419 -0
  48. package/shape/Order.luau +64 -0
  49. package/shape/Regions.luau +113 -0
  50. package/shape/Rotation.luau +472 -0
  51. package/shape/Serial.luau +258 -0
  52. package/shape/Words.luau +89 -0
  53. package/shape/emit/Block.luau +453 -0
  54. package/shape/emit/Column.luau +338 -0
  55. package/shape/emit/Leaf.luau +462 -0
  56. package/shape/emit/Word.luau +234 -0
  57. package/shape/emit/init.luau +222 -0
  58. package/shape/init.luau +78 -0
  59. package/state/Apply.luau +330 -0
  60. package/state/Audience.luau +126 -0
  61. package/state/Catchup.luau +218 -0
  62. package/state/Coalesce.luau +43 -0
  63. package/state/Fieldset.luau +177 -0
  64. package/state/Newest.luau +339 -0
  65. package/state/Plan.luau +441 -0
  66. package/state/Sections.luau +468 -0
  67. package/state/Store.luau +188 -0
  68. package/state/Sync.luau +762 -0
  69. package/wire/Bytes.luau +118 -0
  70. package/wire/Clock.luau +37 -0
  71. package/wire/Protocol.luau +110 -0
  72. package/wire/Sequence.luau +40 -0
  73. package/wire/Varint.luau +124 -0
  74. package/src/Types.luau +0 -180
  75. package/src/api/Group.luau +0 -156
  76. package/src/api/Packet.luau +0 -317
  77. package/src/api/Query.luau +0 -338
  78. package/src/api/Scope.luau +0 -90
  79. package/src/api/Signal.luau +0 -193
  80. package/src/codec/Base.luau +0 -136
  81. package/src/codec/composite/Array.luau +0 -473
  82. package/src/codec/composite/Map.luau +0 -485
  83. package/src/codec/composite/Optional.luau +0 -54
  84. package/src/codec/composite/Shared.luau +0 -311
  85. package/src/codec/composite/Struct.luau +0 -494
  86. package/src/codec/composite/Tagged.luau +0 -103
  87. package/src/codec/composite/Tuple.luau +0 -143
  88. package/src/codec/datatype/Buffer.luau +0 -66
  89. package/src/codec/datatype/CFrame.luau +0 -109
  90. package/src/codec/datatype/Color.luau +0 -32
  91. package/src/codec/datatype/Instance.luau +0 -52
  92. package/src/codec/datatype/IntVector.luau +0 -39
  93. package/src/codec/datatype/NumberRange.luau +0 -39
  94. package/src/codec/datatype/Ray.luau +0 -36
  95. package/src/codec/datatype/Rect.luau +0 -31
  96. package/src/codec/datatype/Region.luau +0 -62
  97. package/src/codec/datatype/Sequence.luau +0 -184
  98. package/src/codec/datatype/String.luau +0 -94
  99. package/src/codec/datatype/UDim.luau +0 -46
  100. package/src/codec/datatype/Vector.luau +0 -130
  101. package/src/codec/meta/Auto.luau +0 -318
  102. package/src/codec/meta/Bitfield.luau +0 -160
  103. package/src/codec/meta/Custom.luau +0 -33
  104. package/src/codec/meta/DeltaScalar.luau +0 -390
  105. package/src/codec/meta/Enum.luau +0 -50
  106. package/src/codec/meta/Float.luau +0 -38
  107. package/src/codec/meta/Nothing.luau +0 -13
  108. package/src/codec/meta/Unknown.luau +0 -41
  109. package/src/codec/primitive/Bool.luau +0 -26
  110. package/src/codec/primitive/Float16.luau +0 -99
  111. package/src/codec/primitive/Int.luau +0 -59
  112. package/src/codec/primitive/Number.luau +0 -24
  113. package/src/codec/primitive/Signed.luau +0 -32
  114. package/src/codec/primitive/Varint.luau +0 -165
  115. package/src/codec/primitive/Zint.luau +0 -99
  116. package/src/index.d.ts +0 -423
  117. package/src/init.luau +0 -362
  118. package/src/internal/Baseline.luau +0 -57
  119. package/src/internal/Channel.luau +0 -484
  120. package/src/internal/Middleware.luau +0 -100
  121. package/src/internal/Pool.luau +0 -66
  122. package/src/internal/Registry.luau +0 -119
  123. package/src/internal/Transport.luau +0 -28
  124. package/src/transport/Bridge.luau +0 -110
  125. package/src/transport/Client.luau +0 -97
  126. package/src/transport/Gate.luau +0 -488
  127. package/src/transport/Reader.luau +0 -331
  128. package/src/transport/Server.luau +0 -233
  129. package/src/util/Array.luau +0 -18
  130. package/src/util/Buffer.luau +0 -90
  131. package/src/util/Constants.luau +0 -30
  132. package/src/util/Log.luau +0 -68
  133. package/src/util/Player.luau +0 -14
  134. package/src/util/Quantize.luau +0 -84
  135. package/src/util/Quat.luau +0 -124
@@ -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 = 1048576
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,473 +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, if len > 0 then boolBytes(len) else 0)
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, byteCount)
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
- -- Every element occupies at least 1 byte; len > remaining is
161
- -- unambiguously corrupt and would OOM table.create otherwise.
162
- if len > bufLen(src) - (pos + lenBytes) then
163
- Log.error(`array length {len} exceeds remaining buffer`)
164
- end
165
-
166
- local result = table.create(len)
167
- local total = lenBytes
168
- for i = 1, len do
169
- local v, consumed = element.read(src, pos + total, refs)
170
- if consumed == 0 then
171
- Log.error(`element codec consumed 0 bytes at index {i}`)
172
- end
173
- result[i] = v
174
- total += consumed
175
- end
176
- return result, total
177
- end,
178
- }
179
- end
180
-
181
- -- Public -----------------------------------------------------------------
182
-
183
- local Array = {}
184
-
185
- function Array.array(element: Types.InternalCodec<any>, maxCount: number?): Types.InternalCodec<any>
186
- local codec: Types.InternalCodec<any>
187
- if element._isBool then
188
- codec = makeBoolArray(element, maxCount)
189
- else
190
- local elemSize = element._size
191
- local elemDW = element._directWrite
192
- local elemDR = element._directRead
193
- if elemSize and elemDW and elemDR then
194
- codec = makeDirectArray(element, elemSize, elemDW, elemDR, maxCount)
195
- else
196
- codec = makeGenericArray(element, maxCount)
197
- end
198
- end
199
- if element._hasUnknown then
200
- codec._hasUnknown = true
201
- end
202
- if containsDelta(element) then
203
- codec._hasDelta = true
204
- end
205
- return table.freeze(codec)
206
- end
207
-
208
- --[[
209
- Per-index deltaArray. Wire:
210
- [0] UNCHANGED (1 byte)
211
- [1] FULL <count varint> <element bytes ...>
212
- [2] PATCH <newLen varint> <changeCount varint>
213
- <(idx varint, element bytes) ...>
214
-
215
- Length shrinkage is implicit in newLen — entries past newLen drop from
216
- the cached map. Length growth: appended indices show up as changes.
217
- Reordered lists tend to mark every index changed; the writer falls back
218
- to FULL when changeCount * 2 >= newLen since PATCH index varints eat
219
- any savings past that ratio.
220
- ]]
221
- function Array.deltaArray(
222
- element: Types.InternalCodec<any>,
223
- maxCount: number?
224
- ): Types.InternalCodec<any>
225
- -- Inner delta would chain-diff across elements within a frame, not across frames.
226
- if containsDelta(element) then
227
- Log.error("deltaArray element cannot contain delta state")
228
- end
229
-
230
- local deltaId = allocDeltaId()
231
- local inner = Array.array(element, maxCount)
232
-
233
- --[[
234
- Encode each element into `scratch` and capture per-index byte ranges.
235
- Returns the array length plus parallel arrays of offset/length so the
236
- diff path can compare and emit individual entries without re-encoding.
237
- ]]
238
- local function encodePerIdx(
239
- scratch: Types.ChannelState,
240
- value: { any }
241
- ): (number, { number }, { number })
242
- local len = #value
243
- enforceMaxCount(len, maxCount)
244
- local off = table.create(len)
245
- local elen = table.create(len)
246
- for i = 1, len do
247
- local before = scratch.cursor
248
- element.write(scratch, value[i])
249
- off[i] = before
250
- elen[i] = scratch.cursor - before
251
- end
252
- return len, off, elen
253
- end
254
-
255
- return table.freeze({
256
- _isDelta = true,
257
- _isArray = true,
258
- _element = element,
259
- _maxCount = maxCount,
260
-
261
- write = function(ch: Types.ChannelState, value: { any }): ()
262
- if typeof(value) ~= "table" then
263
- Log.error(`expected table, got {typeof(value)}`)
264
- end
265
-
266
- local cache = ch.deltas[deltaId] :: any
267
- local scratch = acquireScratch()
268
- local len, off, elen = encodePerIdx(scratch, value)
269
- local scratchBuf = scratch.buff
270
-
271
- -- First frame: emit FULL using the per-index scratch bytes.
272
- if not cache then
273
- ensure(ch, 1)
274
- writeu8(ch.buff, ch.cursor, DA_FULL)
275
- ch.cursor += 1
276
- varintWrite(ch, len)
277
- local payloadLen = scratch.cursor
278
- ensure(ch, payloadLen)
279
- bufCopy(ch.buff, ch.cursor, scratchBuf, 0, payloadLen)
280
- ch.cursor += payloadLen
281
-
282
- local perIdx: { [number]: DeltaArrayEntry } = {}
283
- for i = 1, len do
284
- local n = elen[i]
285
- perIdx[i] = { eBuf = copyToBuf(nil, scratchBuf, off[i], n), eLen = n }
286
- end
287
- ch.deltas[deltaId] = { perIdx = perIdx, length = len } :: any
288
- releaseScratch()
289
- return
290
- end
291
-
292
- local cachedPerIdx = cache.perIdx :: { [number]: DeltaArrayEntry }
293
- local cachedLen = cache.length :: number
294
-
295
- -- Find changed indices in the overlap; appended indices count as changed.
296
- local changed: { number } = {}
297
- local changedCount = 0
298
- local overlap = if len < cachedLen then len else cachedLen
299
- for i = 1, overlap do
300
- local entry = cachedPerIdx[i]
301
- local n = elen[i]
302
- if
303
- not entry
304
- or n ~= entry.eLen
305
- or not rangeEqual(scratchBuf, off[i], entry.eBuf, 0, n)
306
- then
307
- changedCount += 1
308
- changed[changedCount] = i
309
- end
310
- end
311
- for i = overlap + 1, len do
312
- changedCount += 1
313
- changed[changedCount] = i
314
- end
315
-
316
- if changedCount == 0 and len == cachedLen then
317
- ensure(ch, 1)
318
- writeu8(ch.buff, ch.cursor, DA_UNCHANGED)
319
- ch.cursor += 1
320
- releaseScratch()
321
- return
322
- end
323
-
324
- -- Pick FULL vs PATCH on actual byte cost. Both share `1 + varintLength(len)`
325
- -- so it cancels: compare PATCH's index-header overhead + changed payload
326
- -- against FULL's full payload.
327
- local fullPayload = scratch.cursor
328
- local patchPayload = varintLength(changedCount)
329
- for i = 1, changedCount do
330
- local idx = changed[i]
331
- patchPayload += varintLength(idx) + elen[idx]
332
- end
333
-
334
- if patchPayload < fullPayload then
335
- ensure(ch, 1)
336
- writeu8(ch.buff, ch.cursor, DA_PATCH)
337
- ch.cursor += 1
338
- varintWrite(ch, len)
339
- varintWrite(ch, changedCount)
340
- for i = 1, changedCount do
341
- local idx = changed[i]
342
- varintWrite(ch, idx)
343
- local n = elen[idx]
344
- ensure(ch, n)
345
- bufCopy(ch.buff, ch.cursor, scratchBuf, off[idx], n)
346
- ch.cursor += n
347
- end
348
- else
349
- ensure(ch, 1)
350
- writeu8(ch.buff, ch.cursor, DA_FULL)
351
- ch.cursor += 1
352
- varintWrite(ch, len)
353
- ensure(ch, fullPayload)
354
- bufCopy(ch.buff, ch.cursor, scratchBuf, 0, fullPayload)
355
- ch.cursor += fullPayload
356
- end
357
-
358
- -- Update cache: refresh changed (and appended), drop truncated.
359
- for i = 1, changedCount do
360
- local idx = changed[i]
361
- local n = elen[idx]
362
- local entry = cachedPerIdx[idx]
363
- if entry then
364
- entry.eBuf = copyToBuf(entry.eBuf, scratchBuf, off[idx], n)
365
- entry.eLen = n
366
- else
367
- cachedPerIdx[idx] = { eBuf = copyToBuf(nil, scratchBuf, off[idx], n), eLen = n }
368
- end
369
- end
370
- for i = len + 1, cachedLen do
371
- cachedPerIdx[i] = nil
372
- end
373
- cache.length = len
374
-
375
- releaseScratch()
376
- end,
377
-
378
- read = function(src: buffer, pos: number, refs: { Instance }?): ({ any }, number)
379
- if pos >= bufLen(src) then
380
- Log.error("truncated deltaArray header")
381
- end
382
- local flag = readu8(src, pos)
383
-
384
- if flag == DA_UNCHANGED then
385
- local cached = Baseline.getCache(deltaId)
386
- if cached == nil then
387
- Log.error("UNCHANGED flag before any FULL frame; baseline missing")
388
- end
389
- return cached, 1
390
- end
391
-
392
- if flag == DA_FULL then
393
- local value, consumed = inner.read(src, pos + 1, refs)
394
- Baseline.setCache(deltaId, value)
395
- return value, 1 + consumed
396
- end
397
-
398
- if flag == DA_PATCH then
399
- local cached = Baseline.getCache(deltaId)
400
- if cached == nil then
401
- Log.error("deltaArray PATCH before any FULL frame; baseline missing")
402
- end
403
- --[[
404
- Capture the cached length BEFORE clone+mutate; #result
405
- becomes unreliable once we set entries at appended indices
406
- past the original boundary.
407
- ]]
408
- local cachedLen = #cached
409
- local result: { any } = table.clone(cached)
410
- local total = 1
411
-
412
- local newLen, lLen = varintRead(src, pos + total)
413
- if lLen == 0 then
414
- Log.error("truncated deltaArray length")
415
- end
416
- enforceMaxCount(newLen, maxCount, "newLen")
417
- total += lLen
418
-
419
- local changeCount, cLen = varintRead(src, pos + total)
420
- if cLen == 0 then
421
- Log.error("truncated deltaArray change count")
422
- end
423
- if changeCount > newLen then
424
- Log.error(`deltaArray changeCount {changeCount} exceeds newLen {newLen}`)
425
- end
426
- total += cLen
427
-
428
- -- The writer marks every appended index (cachedLen+1 .. newLen)
429
- -- as changed; the reader must enforce this so the wire can't
430
- -- ship a holey baseline that breaks #result downstream.
431
- local appendedNeeded = if newLen > cachedLen then newLen - cachedLen else 0
432
- local appendedSeen = 0
433
-
434
- for _ = 1, changeCount do
435
- local idx, iLen = varintRead(src, pos + total)
436
- if iLen == 0 then
437
- Log.error("truncated deltaArray index")
438
- end
439
- if idx < 1 or idx > newLen then
440
- Log.error(`deltaArray index {idx} out of range [1, {newLen}]`)
441
- end
442
- if idx > cachedLen then
443
- appendedSeen += 1
444
- end
445
- total += iLen
446
- local v, vc = element.read(src, pos + total, refs)
447
- if vc == 0 then
448
- Log.error("truncated deltaArray element")
449
- end
450
- total += vc
451
- result[idx] = v
452
- end
453
-
454
- if appendedSeen ~= appendedNeeded then
455
- Log.error(
456
- `deltaArray PATCH grew length to {newLen} but only {appendedSeen}/{appendedNeeded} appended indices supplied`
457
- )
458
- end
459
-
460
- for i = newLen + 1, cachedLen do
461
- result[i] = nil
462
- end
463
-
464
- Baseline.setCache(deltaId, result)
465
- return result, total
466
- end
467
-
468
- Log.error(`invalid deltaArray flag {flag}`)
469
- end,
470
- }) :: Types.InternalCodec<any>
471
- end
472
-
473
- return table.freeze(Array)