@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.
@@ -9,20 +9,43 @@ local Shared = require(script.Parent.Parent.composite.Shared)
9
9
  local Types = require(script.Parent.Parent.Parent.Types)
10
10
  local Varint = require(script.Parent.Parent.primitive.Varint)
11
11
 
12
+ -- Constants --------------------------------------------------------------
13
+
14
+ -- deltaMap wire flags. Distinct from Shared's struct/array flag spaces.
15
+ local DM_UNCHANGED = 0
16
+ local DM_FULL = 1
17
+ local DM_DIFF = 2
18
+
12
19
  -- State ------------------------------------------------------------------
13
20
 
14
21
  --[[
15
22
  Depth-stacked key scratch. A nested Map.write (Map<K, Map<K2, V>>) would
16
23
  clobber a single shared array mid-iteration; the stack gives each frame
17
- its own slot. Mirrors Shared.acquireScratch / releaseScratch.
24
+ its own slot. _keyTails parallels _keyStacks because `#keys` after
25
+ `keys[count] = k` is undefined when count < the prior count.
18
26
  ]]
19
27
  local _keyStacks: { { any } } = {}
28
+ local _keyTails: { number } = {}
20
29
  local _keyDepth = 0
21
30
 
22
31
  -- Private ----------------------------------------------------------------
23
32
 
24
- local alloc = Base.alloc
33
+ local ensure = Base.ensure
25
34
  local allocDeltaId = Shared.allocDeltaId
35
+ local acquireScratch = Shared.acquireScratch
36
+ local releaseScratch = Shared.releaseScratch
37
+ local rangeEqual = Shared.rangeEqual
38
+ local copyToBuf = Shared.copyToBuf
39
+ local containsDelta = Shared.containsDelta
40
+ local enforceMaxCount = Shared.enforceMaxCount
41
+ local writeu8 = buffer.writeu8
42
+ local readu8 = buffer.readu8
43
+ local bufCopy = buffer.copy
44
+ local bufLen = buffer.len
45
+ local varintRead = Varint.read
46
+ local varintWrite = Varint.write
47
+
48
+ type DeltaMapEntry = { kBuf: buffer, kLen: number, vBuf: buffer, vLen: number }
26
49
 
27
50
  local function acquireKeys(): { any }
28
51
  _keyDepth += 1
@@ -44,6 +67,34 @@ local function tostringLess(a: any, b: any): boolean
44
67
  return tostring(a) < tostring(b)
45
68
  end
46
69
 
70
+ local function sortKeys(keys: { any }, canSortDirect: boolean): ()
71
+ if canSortDirect then
72
+ table.sort(keys :: { any })
73
+ else
74
+ table.sort(keys, tostringLess)
75
+ end
76
+ end
77
+
78
+ local function collectAndSortKeys(value: { [any]: any }, canSortDirect: boolean): ({ any }, number)
79
+ local keys = acquireKeys()
80
+ local prevTail = _keyTails[_keyDepth] or 0
81
+ local count = 0
82
+ for k in value do
83
+ count += 1
84
+ keys[count] = k
85
+ end
86
+ if count < prevTail then
87
+ for i = count + 1, prevTail do
88
+ keys[i] = nil
89
+ end
90
+ end
91
+ _keyTails[_keyDepth] = count
92
+ if count > 1 then
93
+ sortKeys(keys, canSortDirect)
94
+ end
95
+ return keys, count
96
+ end
97
+
47
98
  -- Public -----------------------------------------------------------------
48
99
 
49
100
  local Map = {}
@@ -56,6 +107,7 @@ function Map.map(
56
107
  local fixedKey = keyCodec._size
57
108
  local fixedValue = valueCodec._size
58
109
  local hasUnknown = keyCodec._hasUnknown or valueCodec._hasUnknown
110
+ local hasDelta = containsDelta(keyCodec) or containsDelta(valueCodec)
59
111
 
60
112
  --[[
61
113
  Numbers and strings sort by `<` directly; everything else (Instance,
@@ -82,63 +134,42 @@ function Map.map(
82
134
  Log.error(`expected table, got {typeof(value)}`)
83
135
  end
84
136
 
85
- local keys = acquireKeys()
86
- local count = 0
87
- for k in value do
88
- count += 1
89
- keys[count] = k
90
- end
137
+ local keys, count = collectAndSortKeys(value, canSortDirect)
91
138
 
92
- -- Drop tail entries from the prior reuse so table.sort sees only `count` items.
93
- local prevTail = #keys
94
- for i = count + 1, prevTail do
95
- keys[i] = nil
96
- end
97
-
98
- Varint.write(ch, count)
139
+ varintWrite(ch, count)
99
140
  if count == 0 then
100
141
  releaseKeys()
101
142
  return
102
143
  end
103
144
 
104
- if canSortDirect then
105
- table.sort(keys :: { any })
106
- else
107
- table.sort(keys, tostringLess)
108
- end
109
-
110
145
  -- Single alloc when both halves are fixed-size.
111
146
  if fixedKey and fixedValue then
112
- local entrySize = fixedKey + fixedValue
113
- local cursor = ch.cursor
114
- if cursor + count * entrySize > ch.size then
115
- alloc(ch, count * entrySize)
116
- end
147
+ ensure(ch, count * (fixedKey + fixedValue))
117
148
  end
118
149
 
119
150
  for i = 1, count do
120
151
  local k = keys[i]
121
152
  keyCodec.write(ch, k)
122
153
  valueCodec.write(ch, value[k])
123
- keys[i] = nil
124
154
  end
125
155
  releaseKeys()
126
156
  end,
127
157
 
128
158
  read = function(src: buffer, pos: number, refs: { Instance }?): ({ [any]: any }, number)
129
- local count, lenBytes = Varint.read(src, pos)
130
- if maxCount and count > maxCount then
131
- Log.error(`count {count} exceeds max {maxCount}`)
132
- end
159
+ local count, lenBytes = varintRead(src, pos)
160
+ enforceMaxCount(count, maxCount)
133
161
 
134
162
  -- Fixed-size: reject corrupt counts before per-entry decode.
135
163
  if fixedKey and fixedValue and count > 0 then
136
- local entrySize = fixedKey + fixedValue
137
- local dataStart = pos + lenBytes
138
- local payloadBytes = count * entrySize
139
- if payloadBytes > buffer.len(src) - dataStart then
164
+ local payloadBytes = count * (fixedKey + fixedValue)
165
+ if payloadBytes > bufLen(src) - (pos + lenBytes) then
140
166
  Log.error(`payload {payloadBytes}B exceeds remaining buffer`)
141
167
  end
168
+ elseif count > 0 then
169
+ -- Variable-size: each pair is at least 2 bytes (key + value).
170
+ if count * 2 > bufLen(src) - (pos + lenBytes) then
171
+ Log.error(`map count {count} exceeds remaining buffer`)
172
+ end
142
173
  end
143
174
 
144
175
  local result: { [any]: any } = {}
@@ -162,19 +193,293 @@ function Map.map(
162
193
  if hasUnknown then
163
194
  codec._hasUnknown = true
164
195
  end
196
+ if hasDelta then
197
+ codec._hasDelta = true
198
+ end
165
199
  return table.freeze(codec)
166
200
  end
167
201
 
202
+ --[[
203
+ Per-key deltaMap. Wire:
204
+ [0] UNCHANGED (1 byte)
205
+ [1] FULL <count varint> <(k,v) pairs ...>
206
+ [2] DIFF <removeCount varint> <removed key bytes ...>
207
+ <changeCount varint> <(k,v) pair bytes ...>
208
+
209
+ Cache: per-key encoded value bytes for byte-equality diffing plus per-key
210
+ encoded key bytes so removed-key emission doesn't re-encode the key codec
211
+ for keys that no longer live in the map.
212
+ ]]
168
213
  function Map.deltaMap(
169
214
  keyCodec: Types.InternalCodec<any>,
170
215
  valueCodec: Types.InternalCodec<any>,
171
216
  maxCount: number?
172
217
  ): Types.InternalCodec<any>
173
- return Shared.makeUnchangedOrFullDelta(
174
- Map.map(keyCodec, valueCodec, maxCount),
175
- allocDeltaId(),
176
- Baseline
177
- )
218
+ -- Inner delta would chain-diff across pairs within a frame, not across frames.
219
+ if containsDelta(keyCodec) then
220
+ Log.error("deltaMap key codec cannot contain delta state")
221
+ end
222
+ if containsDelta(valueCodec) then
223
+ Log.error("deltaMap value codec cannot contain delta state")
224
+ end
225
+
226
+ local deltaId = allocDeltaId()
227
+ local typeCheck = keyCodec._typeCheck
228
+ local canSortDirect = typeCheck == "number" or typeCheck == "string"
229
+ local inner = Map.map(keyCodec, valueCodec, maxCount)
230
+
231
+ --[[
232
+ Encode each (key, value) pair into `scratch` and capture per-pair byte
233
+ ranges. Returns the sorted-keys list plus four parallel arrays of
234
+ offset/length so the diff path can compare and emit individual entries
235
+ without re-encoding.
236
+ ]]
237
+ local function encodePerKey(
238
+ scratch: Types.ChannelState,
239
+ value: { [any]: any }
240
+ ): ({ any }, { number }, { number }, { number }, { number })
241
+ local keys, count = collectAndSortKeys(value, canSortDirect)
242
+ local kOff = table.create(count)
243
+ local kLen = table.create(count)
244
+ local vOff = table.create(count)
245
+ local vLen = table.create(count)
246
+
247
+ for i = 1, count do
248
+ local k = keys[i]
249
+ local before = scratch.cursor
250
+ keyCodec.write(scratch, k)
251
+ local mid = scratch.cursor
252
+ kOff[i] = before
253
+ kLen[i] = mid - before
254
+ valueCodec.write(scratch, value[k])
255
+ vOff[i] = mid
256
+ vLen[i] = scratch.cursor - mid
257
+ end
258
+ return keys, kOff, kLen, vOff, vLen
259
+ end
260
+
261
+ return table.freeze({
262
+ _isDelta = true,
263
+ _isMap = true,
264
+ _keyCodec = keyCodec,
265
+ _valueCodec = valueCodec,
266
+ _maxCount = maxCount,
267
+
268
+ write = function(ch: Types.ChannelState, value: { [any]: any }): ()
269
+ if typeof(value) ~= "table" then
270
+ Log.error(`expected table, got {typeof(value)}`)
271
+ end
272
+
273
+ local cache = ch.deltas[deltaId] :: any
274
+ local scratch = acquireScratch()
275
+ local keys, kOff, kLen, vOff, vLen = encodePerKey(scratch, value)
276
+ local count = #keys
277
+ local scratchBuf = scratch.buff
278
+
279
+ -- First frame: emit FULL. Per-key scratch bytes ARE the inner map payload.
280
+ if not cache then
281
+ ensure(ch, 1)
282
+ writeu8(ch.buff, ch.cursor, DM_FULL)
283
+ ch.cursor += 1
284
+ varintWrite(ch, count)
285
+ local payloadLen = scratch.cursor
286
+ ensure(ch, payloadLen)
287
+ bufCopy(ch.buff, ch.cursor, scratchBuf, 0, payloadLen)
288
+ ch.cursor += payloadLen
289
+
290
+ local perKey: { [any]: DeltaMapEntry } = {}
291
+ for i = 1, count do
292
+ local kbLen = kLen[i]
293
+ local vbLen = vLen[i]
294
+ perKey[keys[i]] = {
295
+ kBuf = copyToBuf(nil, scratchBuf, kOff[i], kbLen),
296
+ kLen = kbLen,
297
+ vBuf = copyToBuf(nil, scratchBuf, vOff[i], vbLen),
298
+ vLen = vbLen,
299
+ }
300
+ end
301
+ ch.deltas[deltaId] = { perKey = perKey } :: any
302
+ releaseKeys()
303
+ releaseScratch()
304
+ return
305
+ end
306
+
307
+ local cachedPerKey = cache.perKey :: { [any]: DeltaMapEntry }
308
+
309
+ local seen: { [any]: boolean } = {}
310
+ local changed: { number } = {}
311
+ local changedCount = 0
312
+ for i = 1, count do
313
+ local k = keys[i]
314
+ seen[k] = true
315
+ local entry = cachedPerKey[k]
316
+ local newLen = vLen[i]
317
+ if
318
+ not entry
319
+ or newLen ~= entry.vLen
320
+ or not rangeEqual(scratchBuf, vOff[i], entry.vBuf, 0, newLen)
321
+ then
322
+ changedCount += 1
323
+ changed[changedCount] = i
324
+ end
325
+ end
326
+
327
+ local removed: { any } = {}
328
+ local removedCount = 0
329
+ for k in cachedPerKey do
330
+ if not seen[k] then
331
+ removedCount += 1
332
+ removed[removedCount] = k
333
+ end
334
+ end
335
+
336
+ if removedCount == 0 and changedCount == 0 then
337
+ ensure(ch, 1)
338
+ writeu8(ch.buff, ch.cursor, DM_UNCHANGED)
339
+ ch.cursor += 1
340
+ releaseKeys()
341
+ releaseScratch()
342
+ return
343
+ end
344
+
345
+ -- Sort removed keys so the XOR baseline can collapse identical drops.
346
+ sortKeys(removed, canSortDirect)
347
+
348
+ ensure(ch, 1)
349
+ writeu8(ch.buff, ch.cursor, DM_DIFF)
350
+ ch.cursor += 1
351
+
352
+ varintWrite(ch, removedCount)
353
+ for i = 1, removedCount do
354
+ local entry = cachedPerKey[removed[i]]
355
+ local n = entry.kLen
356
+ ensure(ch, n)
357
+ bufCopy(ch.buff, ch.cursor, entry.kBuf, 0, n)
358
+ ch.cursor += n
359
+ end
360
+
361
+ varintWrite(ch, changedCount)
362
+ for i = 1, changedCount do
363
+ local idx = changed[i]
364
+ local kbLen = kLen[idx]
365
+ local vbLen = vLen[idx]
366
+ ensure(ch, kbLen + vbLen)
367
+ bufCopy(ch.buff, ch.cursor, scratchBuf, kOff[idx], kbLen)
368
+ bufCopy(ch.buff, ch.cursor + kbLen, scratchBuf, vOff[idx], vbLen)
369
+ ch.cursor += kbLen + vbLen
370
+ end
371
+
372
+ -- Update cache: drop removed, refresh changed, leave untouched in place.
373
+ for i = 1, removedCount do
374
+ cachedPerKey[removed[i]] = nil
375
+ end
376
+ for i = 1, changedCount do
377
+ local idx = changed[i]
378
+ local k = keys[idx]
379
+ local kbLen = kLen[idx]
380
+ local vbLen = vLen[idx]
381
+ local entry = cachedPerKey[k]
382
+ if entry then
383
+ entry.kBuf = copyToBuf(entry.kBuf, scratchBuf, kOff[idx], kbLen)
384
+ entry.kLen = kbLen
385
+ entry.vBuf = copyToBuf(entry.vBuf, scratchBuf, vOff[idx], vbLen)
386
+ entry.vLen = vbLen
387
+ else
388
+ cachedPerKey[k] = {
389
+ kBuf = copyToBuf(nil, scratchBuf, kOff[idx], kbLen),
390
+ kLen = kbLen,
391
+ vBuf = copyToBuf(nil, scratchBuf, vOff[idx], vbLen),
392
+ vLen = vbLen,
393
+ }
394
+ end
395
+ end
396
+
397
+ releaseKeys()
398
+ releaseScratch()
399
+ end,
400
+
401
+ read = function(src: buffer, pos: number, refs: { Instance }?): ({ [any]: any }, number)
402
+ if pos >= bufLen(src) then
403
+ Log.error("truncated deltaMap header")
404
+ end
405
+ local flag = readu8(src, pos)
406
+
407
+ if flag == DM_UNCHANGED then
408
+ local cached = Baseline.getCache(deltaId)
409
+ if cached == nil then
410
+ Log.error("UNCHANGED flag before any FULL frame; baseline missing")
411
+ end
412
+ return cached, 1
413
+ end
414
+
415
+ if flag == DM_FULL then
416
+ local value, consumed = inner.read(src, pos + 1, refs)
417
+ Baseline.setCache(deltaId, value)
418
+ return value, 1 + consumed
419
+ end
420
+
421
+ if flag == DM_DIFF then
422
+ local cached = Baseline.getCache(deltaId)
423
+ if cached == nil then
424
+ Log.error("deltaMap DIFF before any FULL frame; baseline missing")
425
+ end
426
+ local result: { [any]: any } = table.clone(cached)
427
+ local total = 1
428
+
429
+ local cachedSize = 0
430
+ for _ in cached do
431
+ cachedSize += 1
432
+ end
433
+
434
+ local removeCount, rcLen = varintRead(src, pos + total)
435
+ if rcLen == 0 then
436
+ Log.error("truncated deltaMap remove count")
437
+ end
438
+ enforceMaxCount(removeCount, maxCount, "removeCount")
439
+ if removeCount > cachedSize then
440
+ Log.error(`deltaMap removeCount {removeCount} exceeds cached size {cachedSize}`)
441
+ end
442
+ total += rcLen
443
+ for _ = 1, removeCount do
444
+ local k, kc = keyCodec.read(src, pos + total, refs)
445
+ if kc == 0 then
446
+ Log.error("truncated deltaMap removed key")
447
+ end
448
+ total += kc
449
+ result[k] = nil
450
+ end
451
+
452
+ local changeCount, ccLen = varintRead(src, pos + total)
453
+ if ccLen == 0 then
454
+ Log.error("truncated deltaMap change count")
455
+ end
456
+ enforceMaxCount(changeCount, maxCount, "changeCount")
457
+ -- Joint bound: post-DIFF size can't exceed maxCount either.
458
+ if maxCount and (cachedSize - removeCount + changeCount) > maxCount then
459
+ Log.error(`deltaMap DIFF would grow map past maxCount {maxCount}`)
460
+ end
461
+ total += ccLen
462
+ for _ = 1, changeCount do
463
+ local k, kc = keyCodec.read(src, pos + total, refs)
464
+ if kc == 0 then
465
+ Log.error("truncated deltaMap changed key")
466
+ end
467
+ total += kc
468
+ local v, vc = valueCodec.read(src, pos + total, refs)
469
+ if vc == 0 then
470
+ Log.error("truncated deltaMap changed value")
471
+ end
472
+ total += vc
473
+ result[k] = v
474
+ end
475
+
476
+ Baseline.setCache(deltaId, result)
477
+ return result, total
478
+ end
479
+
480
+ Log.error(`invalid deltaMap flag {flag}`)
481
+ end,
482
+ }) :: Types.InternalCodec<any>
178
483
  end
179
484
 
180
485
  return table.freeze(Map)
@@ -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 ----------------------------------------------------------------
@@ -44,6 +45,9 @@ function Optional.optional(inner: Types.InternalCodec<any>): Types.InternalCodec
44
45
  if inner._hasUnknown then
45
46
  codec._hasUnknown = true
46
47
  end
48
+ if Shared.containsDelta(inner) then
49
+ codec._hasDelta = true
50
+ end
47
51
  return table.freeze(codec)
48
52
  end
49
53