@axpecter/lync 2.2.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.
Files changed (65) hide show
  1. package/README.md +109 -147
  2. package/package.json +1 -1
  3. package/src/Types.luau +34 -22
  4. package/src/api/Group.luau +40 -33
  5. package/src/api/Packet.luau +140 -66
  6. package/src/api/Query.luau +103 -65
  7. package/src/api/Scope.luau +26 -10
  8. package/src/api/Signal.luau +43 -52
  9. package/src/codec/Base.luau +28 -14
  10. package/src/codec/composite/Array.luau +296 -115
  11. package/src/codec/composite/Map.luau +377 -57
  12. package/src/codec/composite/Optional.luau +10 -2
  13. package/src/codec/composite/Shared.luau +134 -64
  14. package/src/codec/composite/Struct.luau +294 -43
  15. package/src/codec/composite/Tagged.luau +21 -16
  16. package/src/codec/composite/Tuple.luau +32 -15
  17. package/src/codec/datatype/Buffer.luau +7 -7
  18. package/src/codec/datatype/CFrame.luau +34 -122
  19. package/src/codec/datatype/Color.luau +10 -10
  20. package/src/codec/datatype/Instance.luau +15 -12
  21. package/src/codec/datatype/IntVector.luau +2 -2
  22. package/src/codec/datatype/NumberRange.luau +15 -8
  23. package/src/codec/datatype/Ray.luau +13 -14
  24. package/src/codec/datatype/Rect.luau +12 -12
  25. package/src/codec/datatype/Region.luau +13 -14
  26. package/src/codec/datatype/Sequence.luau +87 -65
  27. package/src/codec/datatype/String.luau +17 -10
  28. package/src/codec/datatype/UDim.luau +10 -8
  29. package/src/codec/datatype/Vector.luau +20 -59
  30. package/src/codec/meta/Auto.luau +94 -126
  31. package/src/codec/meta/Bitfield.luau +12 -14
  32. package/src/codec/meta/Custom.luau +3 -1
  33. package/src/codec/meta/DeltaScalar.luau +390 -0
  34. package/src/codec/meta/Enum.luau +9 -9
  35. package/src/codec/meta/Float.luau +6 -28
  36. package/src/codec/meta/Nothing.luau +1 -1
  37. package/src/codec/meta/Unknown.luau +10 -7
  38. package/src/codec/primitive/Bool.luau +6 -4
  39. package/src/codec/primitive/Float16.luau +5 -2
  40. package/src/codec/primitive/Int.luau +5 -6
  41. package/src/codec/primitive/Number.luau +6 -4
  42. package/src/codec/primitive/Signed.luau +2 -2
  43. package/src/codec/primitive/Varint.luau +76 -39
  44. package/src/codec/primitive/Zint.luau +99 -0
  45. package/src/index.d.ts +207 -53
  46. package/src/init.luau +116 -103
  47. package/src/internal/Baseline.luau +9 -1
  48. package/src/internal/Channel.luau +191 -157
  49. package/src/internal/Middleware.luau +22 -6
  50. package/src/internal/Pool.luau +12 -4
  51. package/src/internal/Registry.luau +25 -16
  52. package/src/internal/Transport.luau +1 -1
  53. package/src/transport/Bridge.luau +47 -33
  54. package/src/transport/Client.luau +25 -21
  55. package/src/transport/Gate.luau +227 -172
  56. package/src/transport/Reader.luau +174 -106
  57. package/src/transport/Server.luau +46 -57
  58. package/src/util/Array.luau +18 -0
  59. package/src/util/Buffer.luau +90 -0
  60. package/src/util/Constants.luau +30 -0
  61. package/src/util/Log.luau +68 -0
  62. package/src/util/Player.luau +14 -0
  63. package/src/util/Quantize.luau +84 -0
  64. package/src/util/Quat.luau +124 -0
  65. package/src/internal/Util.luau +0 -26
@@ -1,29 +1,27 @@
1
1
  --!strict
2
2
  --!native
3
- -- Shared helpers for composite codecs: delta flags, scratch pool, bool packing.
3
+ -- Shared composite-codec helpers: delta flags, scratch pool, bool packing.
4
4
 
5
5
  local Base = require(script.Parent.Parent.Base)
6
+ local Buf = require(script.Parent.Parent.Parent.util.Buffer)
7
+ local Channel = require(script.Parent.Parent.Parent.internal.Channel)
8
+ local Log = require(script.Parent.Parent.Parent.util.Log)
6
9
  local Types = require(script.Parent.Parent.Parent.Types)
7
10
 
8
11
  -- Constants --------------------------------------------------------------
9
12
 
10
13
  --[[
11
- Delta encoding flag space, shared across deltaStruct/deltaArray/deltaMap:
12
- 0 UNCHANGED : sender skipped this entry, decoder reuses cache
13
- 1 FULL : full payload follows
14
- 2.. reserved : per-segment / per-key delta encodings (future)
15
-
16
- Struct's per-segment scheme uses [1..segCount] as "single dirty
17
- segment index" then segCount + DELTA_FULL_OFFSET for full and
18
- segCount + DELTA_MULTI_OFFSET for multi-dirty bitmap.
14
+ Delta flag base shared by deltaStruct's per-segment scheme:
15
+ 0 UNCHANGED
16
+ 1..segCount single dirty segment k
17
+ segCount + FULL_OFFSET FULL payload
18
+ segCount + MULTI_OFFSET MULTI: bitmap + dirty segs in order
19
+ deltaArray and deltaMap manage their own flag spaces.
19
20
  ]]
20
21
  local DELTA_UNCHANGED = 0
21
- local DELTA_FULL = 1
22
22
  local DELTA_FULL_OFFSET = 1
23
23
  local DELTA_MULTI_OFFSET = 2
24
24
 
25
- local INITIAL_SCRATCH = 1024
26
-
27
25
  -- State ------------------------------------------------------------------
28
26
 
29
27
  local _nextDeltaId = 0
@@ -35,29 +33,31 @@ local _scratchStack: { Types.ChannelState } = {}
35
33
  local alloc = Base.alloc
36
34
  local band = bit32.band
37
35
  local bor = bit32.bor
38
- local bxor = bit32.bxor
39
36
  local lshift = bit32.lshift
40
37
  local readu8 = buffer.readu8
41
- local readu32 = buffer.readu32
42
38
  local writeu8 = buffer.writeu8
39
+ local bufCopy = buffer.copy
40
+ local bufLen = buffer.len
41
+ local rangeEqual = Buf.rangeEqual
42
+ local snapshot = Buf.snapshot
43
43
 
44
- local function newScratch(): Types.ChannelState
45
- return {
46
- buff = buffer.create(INITIAL_SCRATCH),
47
- cursor = 0,
48
- size = INITIAL_SCRATCH,
49
- refs = {},
50
- refCount = 0,
51
- lastId = -1,
52
- countPos = -1,
53
- itemCount = 0,
54
- singleMode = false,
55
- singlePos = 0,
56
- deltas = {},
57
- prevDump = nil,
58
- prevDumpLen = 0,
59
- currentPacket = nil,
60
- }
44
+ --[[
45
+ Reuse `existing` when it's already large enough, else snapshot via
46
+ Buf.snapshot. Avoids per-write buffer allocations on delta caches that
47
+ hover at a stable size.
48
+ ]]
49
+ local function copyToBuf(existing: buffer?, src: buffer, srcOff: number, len: number): buffer
50
+ if existing and bufLen(existing) >= len then
51
+ bufCopy(existing, 0, src, srcOff, len)
52
+ return existing
53
+ end
54
+ return snapshot(src, len, srcOff)
55
+ end
56
+
57
+ -- Update a `{raw, len}` cache shape from scratch bytes; reuses raw via copyToBuf.
58
+ local function refreshCache(cache: any, src: buffer, len: number): ()
59
+ cache.raw = copyToBuf(cache.raw, src, 0, len)
60
+ cache.len = len
61
61
  end
62
62
 
63
63
  -- Public -----------------------------------------------------------------
@@ -65,19 +65,39 @@ end
65
65
  local Shared = {}
66
66
 
67
67
  Shared.DELTA_UNCHANGED = DELTA_UNCHANGED
68
- Shared.DELTA_FULL = DELTA_FULL
69
68
  Shared.DELTA_FULL_OFFSET = DELTA_FULL_OFFSET
70
69
  Shared.DELTA_MULTI_OFFSET = DELTA_MULTI_OFFSET
70
+ Shared.rangeEqual = rangeEqual
71
+ Shared.snapshot = snapshot
72
+ Shared.refreshCache = refreshCache
73
+ Shared.copyToBuf = copyToBuf
74
+
75
+ -- True when `c` itself is a delta codec OR contains one in its tree.
76
+ function Shared.containsDelta(c: Types.InternalCodec<any>): boolean
77
+ return c._isDelta == true or c._hasDelta == true
78
+ end
79
+
80
+ -- Common bounds check for variable-length container codecs.
81
+ function Shared.enforceMaxCount(len: number, maxCount: number?, label: string?): ()
82
+ if maxCount and len > maxCount then
83
+ Log.error(`{label or "count"} {len} exceeds max {maxCount}`)
84
+ end
85
+ end
71
86
 
72
87
  function Shared.allocDeltaId(): number
73
88
  _nextDeltaId += 1
74
89
  return _nextDeltaId
75
90
  end
76
91
 
92
+ -- 8 bools per byte.
93
+ function Shared.boolBytes(n: number): number
94
+ return (n + 7) // 8
95
+ end
96
+
77
97
  --[[
78
- Acquire a scratch ChannelState. Stack-based so re-entrant codec
79
- serialization (a codec writing into scratch that triggers another
80
- codec's scratch acquire) works without aliasing.
98
+ Stack-based scratch ChannelState pool. Re-entrant codec serialization
99
+ (a codec writing into scratch that triggers another codec's scratch
100
+ acquire) works without aliasing.
81
101
  ]]
82
102
  function Shared.acquireScratch(): Types.ChannelState
83
103
  _scratchDepth += 1
@@ -87,7 +107,7 @@ function Shared.acquireScratch(): Types.ChannelState
87
107
  ch.refCount = 0
88
108
  return ch
89
109
  end
90
- ch = newScratch()
110
+ ch = Channel.create()
91
111
  _scratchStack[_scratchDepth] = ch
92
112
  return ch
93
113
  end
@@ -96,35 +116,29 @@ function Shared.releaseScratch(): ()
96
116
  _scratchDepth -= 1
97
117
  end
98
118
 
99
- function Shared.rangeEqual(a: buffer, offA: number, b: buffer, offB: number, len: number): boolean
100
- local aligned = band(len, -4)
101
- local i = 0
102
- while i < aligned do
103
- if bxor(readu32(a, offA + i), readu32(b, offB + i)) ~= 0 then
104
- return false
105
- end
106
- i += 4
107
- end
108
- while i < len do
109
- if readu8(a, offA + i) ~= readu8(b, offB + i) then
110
- return false
119
+ --[[
120
+ Reset module-private state. Called by Sandbox in tests so that a thrown
121
+ codec.write (which skips releaseScratch and leaves depth inflated) can't
122
+ leak into the next test's scratch acquisition. Also resets the deltaId
123
+ counter so test ordering doesn't bleed.
124
+ ]]
125
+ function Shared.reset(): ()
126
+ _scratchDepth = 0
127
+ _nextDeltaId = 0
128
+ for i = 1, #_scratchStack do
129
+ local ch = _scratchStack[i]
130
+ ch.cursor = 0
131
+ ch.refCount = 0
132
+ if ch.refCount > 0 then
133
+ table.clear(ch.refs)
111
134
  end
112
- i += 1
113
- end
114
- return true
115
- end
116
-
117
- function Shared.snapshot(src: buffer, len: number): buffer
118
- local out = buffer.create(len)
119
- if len > 0 then
120
- buffer.copy(out, 0, src, 0, len)
135
+ table.clear(ch.deltas)
121
136
  end
122
- return out
123
137
  end
124
138
 
125
139
  --[[
126
- Split a struct schema into (dataKeys, dataCodecs, boolKeys), all
127
- sorted alphabetically for deterministic wire layout.
140
+ Split a struct schema into (dataKeys, dataCodecs, boolKeys), each sorted
141
+ alphabetically for deterministic wire layout.
128
142
  ]]
129
143
  function Shared.extractFields(
130
144
  schema: { [string]: Types.InternalCodec<any> }
@@ -140,9 +154,7 @@ function Shared.extractFields(
140
154
  end
141
155
  end
142
156
 
143
- if #dataKeys == 0 and #boolKeys == 0 then
144
- error("[Lync] Lync.struct: requires at least one field")
145
- end
157
+ Log.assert(#dataKeys > 0 or #boolKeys > 0, "requires at least one field")
146
158
 
147
159
  table.sort(dataKeys)
148
160
  table.sort(boolKeys)
@@ -174,9 +186,9 @@ function Shared.packBools(
174
186
  if cursor + byteCount > ch.size then
175
187
  alloc(ch, byteCount)
176
188
  end
177
- local b = ch.buff
178
189
 
179
190
  local idx = 1
191
+ local b = ch.buff
180
192
  for byteIdx = 0, byteCount - 1 do
181
193
  local packed = 0
182
194
  local remaining = boolCount - idx + 1
@@ -193,6 +205,39 @@ function Shared.packBools(
193
205
  ch.cursor = cursor + byteCount
194
206
  end
195
207
 
208
+ --[[
209
+ Integer-indexed bool-array pack. Split from packBools to skip the
210
+ per-bit string-key getter on the bool-array hot path.
211
+ ]]
212
+ function Shared.packBoolArray(ch: Types.ChannelState, value: { boolean }, count: number): ()
213
+ if count == 0 then
214
+ return
215
+ end
216
+
217
+ local byteCount = (count + 7) // 8
218
+ local cursor = ch.cursor
219
+ if cursor + byteCount > ch.size then
220
+ alloc(ch, byteCount)
221
+ end
222
+
223
+ local idx = 1
224
+ local b = ch.buff
225
+ for byteIdx = 0, byteCount - 1 do
226
+ local packed = 0
227
+ local remaining = count - idx + 1
228
+ local limit = if remaining >= 8 then 8 else remaining
229
+ for bit = 0, limit - 1 do
230
+ if value[idx] then
231
+ packed = bor(packed, lshift(1, bit))
232
+ end
233
+ idx += 1
234
+ end
235
+ writeu8(b, cursor + byteIdx, packed)
236
+ end
237
+
238
+ ch.cursor = cursor + byteCount
239
+ end
240
+
196
241
  function Shared.unpackBools(
197
242
  src: buffer,
198
243
  pos: number,
@@ -218,4 +263,29 @@ function Shared.unpackBools(
218
263
  return byteCount
219
264
  end
220
265
 
266
+ -- Fills `result[1..count]` in place.
267
+ function Shared.unpackBoolArray(
268
+ src: buffer,
269
+ pos: number,
270
+ count: number,
271
+ result: { boolean }
272
+ ): number
273
+ if count == 0 then
274
+ return 0
275
+ end
276
+
277
+ local byteCount = (count + 7) // 8
278
+ local idx = 1
279
+ for byteIdx = 0, byteCount - 1 do
280
+ local byte = readu8(src, pos + byteIdx)
281
+ local remaining = count - idx + 1
282
+ local limit = if remaining >= 8 then 8 else remaining
283
+ for bit = 0, limit - 1 do
284
+ result[idx] = band(byte, lshift(1, bit)) ~= 0
285
+ idx += 1
286
+ end
287
+ end
288
+ return byteCount
289
+ end
290
+
221
291
  return table.freeze(Shared)