@axpecter/lync 1.5.2 → 2.1.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 (55) hide show
  1. package/README.md +499 -357
  2. package/package.json +2 -2
  3. package/src/Types.luau +69 -27
  4. package/src/api/Group.luau +101 -106
  5. package/src/api/Packet.luau +191 -157
  6. package/src/api/Query.luau +223 -282
  7. package/src/api/Scope.luau +46 -57
  8. package/src/api/Signal.luau +104 -137
  9. package/src/codec/Base.luau +69 -20
  10. package/src/codec/composite/Array.luau +189 -201
  11. package/src/codec/composite/Map.luau +97 -341
  12. package/src/codec/composite/Optional.luau +27 -23
  13. package/src/codec/composite/Shared.luau +96 -65
  14. package/src/codec/composite/Struct.luau +201 -339
  15. package/src/codec/composite/Tagged.luau +64 -178
  16. package/src/codec/composite/Tuple.luau +81 -95
  17. package/src/codec/datatype/Buffer.luau +49 -21
  18. package/src/codec/datatype/CFrame.luau +207 -30
  19. package/src/codec/datatype/Color.luau +21 -11
  20. package/src/codec/datatype/Instance.luau +29 -19
  21. package/src/codec/datatype/IntVector.luau +33 -21
  22. package/src/codec/datatype/NumberRange.luau +19 -10
  23. package/src/codec/datatype/Ray.luau +26 -22
  24. package/src/codec/datatype/Rect.luau +20 -16
  25. package/src/codec/datatype/Region.luau +51 -45
  26. package/src/codec/datatype/Sequence.luau +64 -56
  27. package/src/codec/datatype/String.luau +89 -56
  28. package/src/codec/datatype/UDim.luau +40 -22
  29. package/src/codec/datatype/Vector.luau +181 -17
  30. package/src/codec/meta/Auto.luau +295 -253
  31. package/src/codec/meta/Bitfield.luau +104 -148
  32. package/src/codec/meta/Custom.luau +8 -4
  33. package/src/codec/meta/Enum.luau +29 -57
  34. package/src/codec/meta/Float.luau +64 -0
  35. package/src/codec/meta/Nothing.luau +9 -5
  36. package/src/codec/meta/Unknown.luau +44 -35
  37. package/src/codec/primitive/Bool.luau +16 -26
  38. package/src/codec/primitive/Float16.luau +58 -64
  39. package/src/codec/primitive/Int.luau +68 -0
  40. package/src/codec/primitive/Number.luau +21 -43
  41. package/src/codec/primitive/Varint.luau +67 -46
  42. package/src/index.d.ts +249 -306
  43. package/src/init.luau +332 -173
  44. package/src/internal/Baseline.luau +29 -24
  45. package/src/internal/Channel.luau +346 -161
  46. package/src/internal/Middleware.luau +60 -85
  47. package/src/internal/Pool.luau +19 -30
  48. package/src/internal/Registry.luau +61 -101
  49. package/src/transport/Bridge.luau +64 -43
  50. package/src/transport/Client.luau +60 -117
  51. package/src/transport/Gate.luau +282 -133
  52. package/src/transport/Reader.luau +159 -100
  53. package/src/transport/Server.luau +147 -292
  54. package/src/api/Namespace.luau +0 -251
  55. package/src/codec/meta/Quantized.luau +0 -174
@@ -2,277 +2,265 @@
2
2
  --!native
3
3
  -- array and deltaArray codecs.
4
4
 
5
- local Baseline = require(script.Parent.Parent.Parent.internal.Baseline)
6
- local Channel = require(script.Parent.Parent.Parent.internal.Channel)
5
+ local Base = require(script.Parent.Parent.Base)
7
6
  local Shared = require(script.Parent.Shared)
8
7
  local Types = require(script.Parent.Parent.Parent.Types)
9
8
  local Varint = require(script.Parent.Parent.primitive.Varint)
10
9
 
11
- type ChannelState = Types.ChannelState
12
- type Codec<T> = Types.Codec<T>
13
- type InternalCodec<T> = Types.InternalCodec<T>
10
+ -- Private -------------------------------------------------------------
11
+
12
+ local alloc = Base.alloc
13
+ local writeu8 = buffer.writeu8
14
+ local readu8 = buffer.readu8
15
+ local band = bit32.band
16
+ local bor = bit32.bor
17
+ local lshift = bit32.lshift
14
18
 
15
- local alloc = Channel.alloc
16
- local rangeEqual = Shared.rangeEqual
17
19
  local acquireScratch = Shared.acquireScratch
18
20
  local releaseScratch = Shared.releaseScratch
21
+ local rangeEqual = Shared.rangeEqual
22
+ local allocDeltaId = Shared.allocDeltaId
19
23
 
20
- local FLAG_DELTA = Shared.FLAG_DELTA
21
- local FLAG_FULL = Shared.FLAG_FULL
22
- local FLAG_UNCHANGED = Shared.FLAG_UNCHANGED
24
+ local DELTA_UNCHANGED = Shared.DELTA_UNCHANGED
25
+ local DELTA_FULL_OFFSET = Shared.DELTA_FULL_OFFSET
26
+ local DELTA_MULTI_OFFSET = Shared.DELTA_MULTI_OFFSET
23
27
 
24
28
  -- Public --------------------------------------------------------------
25
29
 
26
30
  local Array = {}
27
31
 
28
- function Array.deltaArray(element: Codec<any>, maxCount: number?): Codec<{ any }>
29
- local deltaId = Shared.allocDeltaId()
30
-
31
- local writeElement = element.write
32
- local readElement = element.read
33
-
34
- local internal = element :: InternalCodec<any>
35
- local elemSize = internal._size
36
- local elemWrite = internal._directWrite
37
- local elemRead = internal._directRead
38
-
39
- return table.freeze({
40
- _isDelta = true,
41
-
42
- write = function(ch: ChannelState, value: { any }): ()
43
- local len = #value
44
- local cache = ch.deltas[deltaId] :: { raw: buffer, bounds: { number }, len: number }?
45
- local scratch = acquireScratch()
32
+ function Array.array(element: Types.InternalCodec<any>, maxCount: number?): Types.InternalCodec<any>
33
+ local elemAny = element :: any
46
34
 
47
- local bounds = table.create(len + 1)
48
- bounds[1] = 0
49
-
50
- if elemWrite and elemSize then
51
- local totalBytes = len * elemSize
52
- alloc(scratch, totalBytes)
53
- local b = scratch.buff
54
- local c = 0
55
- for i = 1, len do
56
- elemWrite(b, c, value[i])
57
- c += elemSize
58
- bounds[i + 1] = c
59
- end
60
- scratch.cursor = totalBytes
61
- else
62
- for i = 1, len do
63
- writeElement(scratch, value[i])
64
- bounds[i + 1] = scratch.cursor
65
- end
66
- end
67
-
68
- local scratchTotal = scratch.cursor
69
-
70
- if not cache or cache.len ~= len then
71
- alloc(ch, 1)
72
- buffer.writeu8(ch.buff, ch.cursor, FLAG_FULL)
73
- ch.cursor += 1
35
+ -- Bool array: bitpacked
36
+ if elemAny._isBool then
37
+ return table.freeze({
38
+ write = function(ch: Types.ChannelState, value: { any }): ()
39
+ local len = #value
74
40
  Varint.write(ch, len)
75
-
76
- alloc(ch, scratchTotal)
77
- buffer.copy(ch.buff, ch.cursor, scratch.buff, 0, scratchTotal)
78
- ch.cursor += scratchTotal
79
- else
80
- local cacheRaw = cache.raw
81
- local cacheBds = cache.bounds
82
- local dirty = table.create(len)
83
- local dirtyN = 0
84
-
85
- for i = 1, len do
86
- local newOff = bounds[i]
87
- local newLen = bounds[i + 1] - newOff
88
- local oldOff = cacheBds[i]
89
- local oldLen = cacheBds[i + 1] - oldOff
90
-
91
- if
92
- newLen ~= oldLen
93
- or not rangeEqual(scratch.buff, newOff, cacheRaw, oldOff, newLen)
94
- then
95
- dirtyN += 1
96
- dirty[dirtyN] = i
97
- end
98
- end
99
-
100
- if dirtyN == 0 then
101
- alloc(ch, 1)
102
- buffer.writeu8(ch.buff, ch.cursor, FLAG_UNCHANGED)
103
- ch.cursor += 1
104
- releaseScratch()
41
+ if len == 0 then
105
42
  return
106
43
  end
107
44
 
108
- alloc(ch, 1)
109
- buffer.writeu8(ch.buff, ch.cursor, FLAG_DELTA)
110
- ch.cursor += 1
111
- Varint.write(ch, dirtyN)
112
-
113
- for j = 1, dirtyN do
114
- local i = dirty[j]
115
- local newOff = bounds[i]
116
- local newLen = bounds[i + 1] - newOff
117
-
118
- Varint.write(ch, i - 1)
119
- alloc(ch, newLen)
120
- buffer.copy(ch.buff, ch.cursor, scratch.buff, newOff, newLen)
121
- ch.cursor += newLen
45
+ local byteCount = (len + 7) // 8
46
+ local cursor = ch.cursor
47
+ if cursor + byteCount > ch.size then
48
+ alloc(ch, byteCount)
122
49
  end
123
- end
124
-
125
- local rawBuf = buffer.create(scratchTotal)
126
- buffer.copy(rawBuf, 0, scratch.buff, 0, scratchTotal)
127
-
128
- table.freeze(bounds)
129
- ch.deltas[deltaId] = { raw = rawBuf, bounds = bounds, len = len }
130
- releaseScratch()
131
- end,
132
-
133
- read = function(src: buffer, pos: number, refs: { Instance }?): ({ any }, number)
134
- local flag = buffer.readu8(src, pos)
135
- local absPos = pos + 1
136
-
137
- if flag == FLAG_UNCHANGED then
138
- local cache = Baseline.getCache(deltaId)
139
- return if cache then table.clone(cache) else {}, 1
140
- end
50
+ local b = ch.buff
141
51
 
142
- if flag == FLAG_FULL then
143
- local len, lenBytes = Varint.read(src, absPos)
144
- absPos += lenBytes
52
+ local idx = 1
53
+ for byteIdx = 0, byteCount - 1 do
54
+ local packed = 0
55
+ local limit = if idx + 8 <= len + 1 then 8 else len - idx + 1
56
+ for bit = 0, limit - 1 do
57
+ if value[idx] then
58
+ packed = bor(packed, lshift(1, bit))
59
+ end
60
+ idx += 1
61
+ end
62
+ writeu8(b, cursor + byteIdx, packed)
63
+ end
64
+ ch.cursor = cursor + byteCount
65
+ end,
145
66
 
67
+ read = function(src: buffer, pos: number, _refs: { Instance }?): ({ boolean }, number)
68
+ local len, lenBytes = Varint.read(src, pos)
146
69
  if maxCount and len > maxCount then
147
- error(`[Lync] Array count {len} exceeds max {maxCount}`)
70
+ error(`[Lync] Array: count {len} exceeds max {maxCount}`)
71
+ end
72
+ if len == 0 then
73
+ return {}, lenBytes
148
74
  end
149
75
 
76
+ local byteCount = (len + 7) // 8
77
+ local dataStart = pos + lenBytes
150
78
  local result = table.create(len)
151
-
152
- if elemRead and elemSize then
153
- for i = 1, len do
154
- result[i] = elemRead(src, absPos)
155
- absPos += elemSize
156
- end
157
- else
158
- for i = 1, len do
159
- local val, n = readElement(src, absPos, refs)
160
- result[i] = val
161
- absPos += n
79
+ local idx = 1
80
+
81
+ for byteIdx = 0, byteCount - 1 do
82
+ local byte = readu8(src, dataStart + byteIdx)
83
+ local limit = if idx + 8 <= len + 1 then 8 else len - idx + 1
84
+ for bit = 0, limit - 1 do
85
+ result[idx] = band(byte, lshift(1, bit)) ~= 0
86
+ idx += 1
162
87
  end
163
88
  end
164
89
 
165
- Baseline.setCache(deltaId, result)
166
- return result, absPos - pos
167
- end
168
-
169
- local cache = Baseline.getCache(deltaId) :: { any }?
170
- local result = if cache then table.clone(cache) else {}
171
-
172
- local dirtyN, dnBytes = Varint.read(src, absPos)
173
- absPos += dnBytes
174
-
175
- for _ = 1, dirtyN do
176
- local idx, idxBytes = Varint.read(src, absPos)
177
- absPos += idxBytes
178
-
179
- if elemRead and elemSize then
180
- result[idx + 1] = elemRead(src, absPos)
181
- absPos += elemSize
182
- else
183
- local val, n = readElement(src, absPos, refs)
184
- result[idx + 1] = val
185
- absPos += n
186
- end
187
- end
188
-
189
- Baseline.setCache(deltaId, result)
190
- return result, absPos - pos
191
- end,
192
- })
193
- end
90
+ return result, lenBytes + byteCount
91
+ end,
92
+ } :: any)
93
+ end
194
94
 
195
- function Array.array(element: Codec<any>, maxCount: number?): Codec<{ any }>
196
- local internal = element :: InternalCodec<any>
197
- local elementSize = internal._size
198
- local directWrite = internal._directWrite
199
- local directRead = internal._directRead
95
+ -- Direct array: element has _size + _directWrite + _directRead
96
+ local elemSize = elemAny._size
97
+ local elemDW = elemAny._directWrite
98
+ local elemDR = elemAny._directRead
200
99
 
201
- if elementSize and directWrite and directRead then
100
+ if elemSize and elemDW and elemDR then
202
101
  return table.freeze({
203
- write = function(ch: ChannelState, value: { any }): ()
102
+ write = function(ch: Types.ChannelState, value: { any }): ()
204
103
  local len = #value
205
104
  Varint.write(ch, len)
206
105
  if len == 0 then
207
106
  return
208
107
  end
209
108
 
210
- local totalBytes = len * elementSize
211
- local c = ch.cursor
212
- if c + totalBytes > ch.size then
213
- alloc(ch, totalBytes)
109
+ local payloadBytes = len * elemSize
110
+ local cursor = ch.cursor
111
+ if cursor + payloadBytes > ch.size then
112
+ alloc(ch, payloadBytes)
214
113
  end
215
-
216
114
  local b = ch.buff
115
+
217
116
  for i = 1, len do
218
- directWrite(b, c, value[i])
219
- c += elementSize
117
+ elemDW(b, cursor + (i - 1) * elemSize, value[i])
220
118
  end
221
- ch.cursor = c
119
+ ch.cursor = cursor + payloadBytes
222
120
  end,
121
+
223
122
  read = function(src: buffer, pos: number, _refs: { Instance }?): ({ any }, number)
224
123
  local len, lenBytes = Varint.read(src, pos)
225
-
226
124
  if maxCount and len > maxCount then
227
- error(`[Lync] Array count {len} exceeds max {maxCount}`)
125
+ error(`[Lync] Array: count {len} exceeds max {maxCount}`)
126
+ end
127
+ if len == 0 then
128
+ return {}, lenBytes
228
129
  end
229
130
 
230
- local result = table.create(len)
231
- local c = pos + lenBytes
131
+ local payloadBytes = len * elemSize
132
+ local dataStart = pos + lenBytes
133
+
134
+ -- Safety cap
135
+ local remaining = buffer.len(src) - dataStart
136
+ if payloadBytes > remaining then
137
+ len = remaining // elemSize
138
+ payloadBytes = len * elemSize
139
+ end
232
140
 
141
+ local result = table.create(len)
233
142
  for i = 1, len do
234
- result[i] = directRead(src, c)
235
- c += elementSize
143
+ result[i] = elemDR(src, dataStart + (i - 1) * elemSize)
236
144
  end
237
145
 
238
- return result, c - pos
146
+ return result, lenBytes + payloadBytes
239
147
  end,
240
- })
148
+ } :: any)
241
149
  end
242
150
 
243
- local writeElement = element.write
244
- local readElement = element.read
245
-
151
+ -- Generic array
246
152
  return table.freeze({
247
- write = function(ch: ChannelState, value: { any }): ()
153
+ write = function(ch: Types.ChannelState, value: { any }): ()
248
154
  local len = #value
249
155
  Varint.write(ch, len)
250
- if elementSize and len > 0 then
251
- alloc(ch, len * elementSize)
252
- end
253
156
  for i = 1, len do
254
- writeElement(ch, value[i])
157
+ element.write(ch, value[i])
255
158
  end
256
159
  end,
160
+
257
161
  read = function(src: buffer, pos: number, refs: { Instance }?): ({ any }, number)
258
162
  local len, lenBytes = Varint.read(src, pos)
259
-
260
163
  if maxCount and len > maxCount then
261
- error(`[Lync] Array count {len} exceeds max {maxCount}`)
164
+ error(`[Lync] Array: count {len} exceeds max {maxCount}`)
165
+ end
166
+ if len == 0 then
167
+ return {}, lenBytes
262
168
  end
263
169
 
264
170
  local result = table.create(len)
265
- local absPos = pos + lenBytes
266
-
171
+ local total = lenBytes
267
172
  for i = 1, len do
268
- local value, n = readElement(src, absPos, refs)
269
- result[i] = value
270
- absPos += n
173
+ local v, consumed = element.read(src, pos + total, refs)
174
+ result[i] = v
175
+ total += consumed
176
+ end
177
+
178
+ return result, total
179
+ end,
180
+ } :: any)
181
+ end
182
+
183
+ function Array.deltaArray(
184
+ element: Types.InternalCodec<any>,
185
+ maxCount: number?
186
+ ): Types.InternalCodec<any>
187
+ local deltaId = allocDeltaId()
188
+ local innerArray = Array.array(element, maxCount)
189
+
190
+ return table.freeze({
191
+ _isDelta = true,
192
+
193
+ write = function(ch: Types.ChannelState, value: { any }): ()
194
+ local cache = ch.deltas[deltaId]
195
+
196
+ if not cache then
197
+ -- First frame: full
198
+ local cursor = ch.cursor
199
+ if cursor + 1 > ch.size then
200
+ alloc(ch, 1)
201
+ end
202
+ writeu8(ch.buff, cursor, 1) -- FLAG_FULL (len+1 where len=0 for first)
203
+ ch.cursor = cursor + 1
204
+
205
+ local scratch = acquireScratch()
206
+ innerArray.write(scratch, value)
207
+ local snapLen = scratch.cursor
208
+ local snapBuf = buffer.create(snapLen)
209
+ buffer.copy(snapBuf, 0, scratch.buff, 0, snapLen)
210
+
211
+ ch.deltas[deltaId] = { raw = snapBuf, len = snapLen }
212
+ innerArray.write(ch, value)
213
+ releaseScratch()
214
+ return
215
+ end
216
+
217
+ -- Compare against cache
218
+ local scratch = acquireScratch()
219
+ innerArray.write(scratch, value)
220
+ local scratchLen = scratch.cursor
221
+ local cachedLen = cache.len
222
+
223
+ if
224
+ scratchLen == cachedLen and rangeEqual(scratch.buff, 0, cache.raw, 0, scratchLen)
225
+ then
226
+ local cursor = ch.cursor
227
+ if cursor + 1 > ch.size then
228
+ alloc(ch, 1)
229
+ end
230
+ writeu8(ch.buff, cursor, DELTA_UNCHANGED)
231
+ ch.cursor = cursor + 1
232
+ releaseScratch()
233
+ return
234
+ end
235
+
236
+ -- Changed: full re-send
237
+ local cursor = ch.cursor
238
+ if cursor + 1 > ch.size then
239
+ alloc(ch, 1)
240
+ end
241
+ writeu8(ch.buff, cursor, 1)
242
+ ch.cursor = cursor + 1
243
+ innerArray.write(ch, value)
244
+
245
+ local newBuf = buffer.create(scratchLen)
246
+ buffer.copy(newBuf, 0, scratch.buff, 0, scratchLen)
247
+ cache.raw = newBuf
248
+ cache.len = scratchLen
249
+
250
+ releaseScratch()
251
+ end,
252
+
253
+ read = function(src: buffer, pos: number, refs: { Instance }?): ({ any }, number)
254
+ local flag = readu8(src, pos)
255
+
256
+ if flag == DELTA_UNCHANGED then
257
+ return {} :: any, 1
271
258
  end
272
259
 
273
- return result, absPos - pos
260
+ local value, consumed = innerArray.read(src, pos + 1, refs)
261
+ return value, 1 + consumed
274
262
  end,
275
- })
263
+ } :: any)
276
264
  end
277
265
 
278
266
  return table.freeze(Array)