@axpecter/lync 1.3.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 (52) hide show
  1. package/README.md +300 -0
  2. package/package.json +38 -0
  3. package/src/Types.luau +63 -0
  4. package/src/api/Group.luau +126 -0
  5. package/src/api/Namespace.luau +226 -0
  6. package/src/api/Packet.luau +147 -0
  7. package/src/api/Query.luau +295 -0
  8. package/src/api/Signal.luau +224 -0
  9. package/src/codec/Base.luau +49 -0
  10. package/src/codec/composite/Array.luau +275 -0
  11. package/src/codec/composite/Map.luau +395 -0
  12. package/src/codec/composite/Optional.luau +47 -0
  13. package/src/codec/composite/Shared.luau +151 -0
  14. package/src/codec/composite/Struct.luau +440 -0
  15. package/src/codec/composite/Tagged.luau +222 -0
  16. package/src/codec/composite/Tuple.luau +143 -0
  17. package/src/codec/datatype/Buffer.luau +44 -0
  18. package/src/codec/datatype/CFrame.luau +51 -0
  19. package/src/codec/datatype/Color.luau +22 -0
  20. package/src/codec/datatype/Instance.luau +48 -0
  21. package/src/codec/datatype/IntVector.luau +25 -0
  22. package/src/codec/datatype/NumberRange.luau +14 -0
  23. package/src/codec/datatype/Ray.luau +27 -0
  24. package/src/codec/datatype/Rect.luau +21 -0
  25. package/src/codec/datatype/Region.luau +58 -0
  26. package/src/codec/datatype/Sequence.luau +129 -0
  27. package/src/codec/datatype/String.luau +87 -0
  28. package/src/codec/datatype/UDim.luau +27 -0
  29. package/src/codec/datatype/Vector.luau +25 -0
  30. package/src/codec/meta/Auto.luau +353 -0
  31. package/src/codec/meta/Bitfield.luau +191 -0
  32. package/src/codec/meta/Custom.luau +27 -0
  33. package/src/codec/meta/Enum.luau +80 -0
  34. package/src/codec/meta/Nothing.luau +9 -0
  35. package/src/codec/meta/Quantized.luau +170 -0
  36. package/src/codec/meta/Unknown.luau +35 -0
  37. package/src/codec/primitive/Bool.luau +30 -0
  38. package/src/codec/primitive/Float16.luau +111 -0
  39. package/src/codec/primitive/Number.luau +48 -0
  40. package/src/codec/primitive/Varint.luau +76 -0
  41. package/src/index.d.ts +279 -0
  42. package/src/init.luau +161 -0
  43. package/src/internal/Baseline.luau +41 -0
  44. package/src/internal/Channel.luau +235 -0
  45. package/src/internal/Middleware.luau +109 -0
  46. package/src/internal/Pool.luau +68 -0
  47. package/src/internal/Registry.luau +146 -0
  48. package/src/transport/Bridge.luau +66 -0
  49. package/src/transport/Client.luau +151 -0
  50. package/src/transport/Gate.luau +222 -0
  51. package/src/transport/Reader.luau +175 -0
  52. package/src/transport/Server.luau +364 -0
@@ -0,0 +1,395 @@
1
+ --!strict
2
+ --!native
3
+ -- map and deltaMap codecs.
4
+
5
+ local Baseline = require (script.Parent.Parent.Parent.internal.Baseline)
6
+ local Channel = require (script.Parent.Parent.Parent.internal.Channel)
7
+ local alloc = Channel.alloc
8
+ local Shared = require (script.Parent.Shared)
9
+ local Types = require (script.Parent.Parent.Parent.Types)
10
+
11
+ type ChannelState = Types.ChannelState
12
+ type Codec<T> = Types.Codec<T>
13
+ local Varint = require (script.Parent.Parent.primitive.Varint)
14
+
15
+ local rangeEqual = Shared.rangeEqual
16
+ local acquireScratch = Shared.acquireScratch
17
+ local releaseScratch = Shared.releaseScratch
18
+
19
+ local FLAG_DELTA = Shared.FLAG_DELTA
20
+ local FLAG_FULL = Shared.FLAG_FULL
21
+ local FLAG_UNCHANGED = Shared.FLAG_UNCHANGED
22
+
23
+ -- Public -----------------------------------------------------------------
24
+
25
+ local Map = {}
26
+
27
+ function Map.map (
28
+ keyCodec: Codec<any>,
29
+ valueCodec: Codec<any>,
30
+ maxCount: number?
31
+ ): Codec<{ [any]: any }>
32
+ local keySize = (keyCodec :: any)._size :: number?
33
+ local valSize = (valueCodec :: any)._size :: number?
34
+ local keyDWrite = (keyCodec :: any)._directWrite
35
+ local valDWrite = (valueCodec :: any)._directWrite
36
+ local keyDRead = (keyCodec :: any)._directRead
37
+ local valDRead = (valueCodec :: any)._directRead
38
+
39
+ if keySize and valSize and keyDWrite and valDWrite and keyDRead and valDRead then
40
+ local entrySize = keySize + valSize
41
+
42
+ return table.freeze ({
43
+ write = function (ch: ChannelState, value: { [any]: any }): ()
44
+ local count = 0
45
+ for _ in value do
46
+ count += 1
47
+ end
48
+
49
+ Varint.write (ch, count)
50
+
51
+ if count > 0 then
52
+ local totalBytes = count * entrySize
53
+ local c = ch.cursor
54
+ if c + totalBytes > ch.size then
55
+ alloc (ch, totalBytes)
56
+ end
57
+
58
+ local b = ch.buff
59
+ for k, v in value do
60
+ keyDWrite (b, c, k)
61
+ valDWrite (b, c + keySize, v)
62
+ c += entrySize
63
+ end
64
+ ch.cursor = c
65
+ end
66
+ end,
67
+ read = function (
68
+ src: buffer,
69
+ pos: number,
70
+ _refs: { Instance }?
71
+ ): ({ [any]: any }, number)
72
+ local count, lenBytes = Varint.read (src, pos)
73
+
74
+ if maxCount and count > maxCount then
75
+ error (`[Lync] Map count {count} exceeds max {maxCount}`)
76
+ end
77
+
78
+ local result = {}
79
+ local c = pos + lenBytes
80
+
81
+ for _ = 1, count do
82
+ local k = keyDRead (src, c)
83
+ local v = valDRead (src, c + keySize)
84
+ result[k] = v
85
+ c += entrySize
86
+ end
87
+
88
+ return result, c - pos
89
+ end,
90
+ })
91
+ end
92
+
93
+ local writeKey = keyCodec.write
94
+ local writeVal = valueCodec.write
95
+ local readKey = keyCodec.read
96
+ local readVal = valueCodec.read
97
+
98
+ return table.freeze ({
99
+ write = function (ch: ChannelState, value: { [any]: any }): ()
100
+ local count = 0
101
+ for _ in value do
102
+ count += 1
103
+ end
104
+
105
+ Varint.write (ch, count)
106
+
107
+ for k, v in value do
108
+ writeKey (ch, k)
109
+ writeVal (ch, v)
110
+ end
111
+ end,
112
+ read = function (src: buffer, pos: number, refs: { Instance }?): ({ [any]: any }, number)
113
+ local count, lenBytes = Varint.read (src, pos)
114
+
115
+ if maxCount and count > maxCount then
116
+ error (`[Lync] Map count {count} exceeds max {maxCount}`)
117
+ end
118
+
119
+ local result = {}
120
+ local absPos = pos + lenBytes
121
+
122
+ for _ = 1, count do
123
+ local k, kn = readKey (src, absPos, refs)
124
+ absPos += kn
125
+ local v, vn = readVal (src, absPos, refs)
126
+ absPos += vn
127
+ result[k] = v
128
+ end
129
+
130
+ return result, absPos - pos
131
+ end,
132
+ })
133
+ end
134
+
135
+ -- Delta map: tracks per-key changes. First frame is full, then upserts + removes only.
136
+ --
137
+ -- Wire format:
138
+ -- FLAG_FULL: u8(1) + varint(count) + [key+value ...]
139
+ -- FLAG_UNCHANGED: u8(2)
140
+ -- FLAG_DELTA: u8(0) + varint(upsertN) + [key+value ...] + varint(removeN) + [key ...]
141
+ function Map.deltaMap (
142
+ keyCodec: Codec<any>,
143
+ valueCodec: Codec<any>,
144
+ maxCount: number?
145
+ ): Codec<{ [any]: any }>
146
+ local deltaId = Shared.allocDeltaId ()
147
+
148
+ local writeKey = keyCodec.write
149
+ local writeVal = valueCodec.write
150
+ local readKey = keyCodec.read
151
+ local readVal = valueCodec.read
152
+
153
+ local keySize = (keyCodec :: any)._size :: number?
154
+ local valSize = (valueCodec :: any)._size :: number?
155
+ local keyDWrite = (keyCodec :: any)._directWrite
156
+ local valDWrite = (valueCodec :: any)._directWrite
157
+ local keyDRead = (keyCodec :: any)._directRead
158
+ local valDRead = (valueCodec :: any)._directRead
159
+ local scratchDirect = keySize and valSize and keyDWrite and valDWrite
160
+ local entrySize = if keySize and valSize then keySize + valSize else nil
161
+
162
+ return table.freeze ({
163
+ _isDelta = true,
164
+
165
+ write = function (ch: ChannelState, value: { [any]: any }): ()
166
+ local scratch = acquireScratch ()
167
+
168
+ type EntryBounds = { keyOff: number, keyLen: number, valOff: number, valLen: number }
169
+
170
+ local entries = {} :: { EntryBounds }
171
+ local keyLookup = {} :: { [string]: number }
172
+ local entryCount = 0
173
+
174
+ if scratchDirect then
175
+ for k, v in value do
176
+ entryCount += 1
177
+ local c = scratch.cursor
178
+ alloc (scratch, entrySize :: number)
179
+ local b = scratch.buff
180
+ keyDWrite (b, c, k)
181
+ valDWrite (b, c + (keySize :: number), v)
182
+ scratch.cursor = c + (entrySize :: number)
183
+
184
+ entries[entryCount] = {
185
+ keyOff = c,
186
+ keyLen = keySize :: number,
187
+ valOff = c + (keySize :: number),
188
+ valLen = valSize :: number,
189
+ }
190
+
191
+ local keyId = buffer.readstring (b, c, keySize :: number)
192
+ keyLookup[keyId] = entryCount
193
+ end
194
+ else
195
+ for k, v in value do
196
+ entryCount += 1
197
+ local keyStart = scratch.cursor
198
+ writeKey (scratch, k)
199
+ local keyEnd = scratch.cursor
200
+ writeVal (scratch, v)
201
+ local valEnd = scratch.cursor
202
+
203
+ entries[entryCount] = {
204
+ keyOff = keyStart,
205
+ keyLen = keyEnd - keyStart,
206
+ valOff = keyEnd,
207
+ valLen = valEnd - keyEnd,
208
+ }
209
+
210
+ local keyId = buffer.readstring (scratch.buff, keyStart, keyEnd - keyStart)
211
+ keyLookup[keyId] = entryCount
212
+ end
213
+ end
214
+
215
+ local scratchTotal = scratch.cursor
216
+
217
+ type DeltaMapCache = {
218
+ raw: buffer,
219
+ entries: { EntryBounds },
220
+ keyLookup: { [string]: number },
221
+ count: number,
222
+ }
223
+
224
+ local cache = ch.deltas[deltaId] :: DeltaMapCache?
225
+
226
+ if not cache then
227
+ alloc (ch, 1)
228
+ buffer.writeu8 (ch.buff, ch.cursor, FLAG_FULL)
229
+ ch.cursor += 1
230
+ Varint.write (ch, entryCount)
231
+
232
+ if scratchTotal > 0 then
233
+ alloc (ch, scratchTotal)
234
+ buffer.copy (ch.buff, ch.cursor, scratch.buff, 0, scratchTotal)
235
+ ch.cursor += scratchTotal
236
+ end
237
+ else
238
+ local cacheRaw = cache.raw
239
+ local cacheEntries = cache.entries
240
+ local cacheLookup = cache.keyLookup
241
+
242
+ local upsertKeys = {} :: { number }
243
+ local upsertN = 0
244
+
245
+ for keyId, idx in keyLookup do
246
+ local oldIdx = cacheLookup[keyId]
247
+ if not oldIdx then
248
+ upsertN += 1
249
+ upsertKeys[upsertN] = idx
250
+ else
251
+ local newE = entries[idx]
252
+ local oldE = cacheEntries[oldIdx]
253
+ if
254
+ newE.valLen ~= oldE.valLen
255
+ or not rangeEqual (
256
+ scratch.buff,
257
+ newE.valOff,
258
+ cacheRaw,
259
+ oldE.valOff,
260
+ newE.valLen
261
+ )
262
+ then
263
+ upsertN += 1
264
+ upsertKeys[upsertN] = idx
265
+ end
266
+ end
267
+ end
268
+
269
+ local removeKeyIds = {} :: { string }
270
+ local removeN = 0
271
+
272
+ for keyId in cacheLookup do
273
+ if not keyLookup[keyId] then
274
+ removeN += 1
275
+ removeKeyIds[removeN] = keyId
276
+ end
277
+ end
278
+
279
+ if upsertN == 0 and removeN == 0 then
280
+ alloc (ch, 1)
281
+ buffer.writeu8 (ch.buff, ch.cursor, FLAG_UNCHANGED)
282
+ ch.cursor += 1
283
+ releaseScratch ()
284
+ return
285
+ end
286
+
287
+ alloc (ch, 1)
288
+ buffer.writeu8 (ch.buff, ch.cursor, FLAG_DELTA)
289
+ ch.cursor += 1
290
+
291
+ Varint.write (ch, upsertN)
292
+ for i = 1, upsertN do
293
+ local e = entries[upsertKeys[i]]
294
+ local totalLen = e.keyLen + e.valLen
295
+ alloc (ch, totalLen)
296
+ buffer.copy (ch.buff, ch.cursor, scratch.buff, e.keyOff, totalLen)
297
+ ch.cursor += totalLen
298
+ end
299
+
300
+ Varint.write (ch, removeN)
301
+ for i = 1, removeN do
302
+ local keyId = removeKeyIds[i]
303
+ local oldIdx = cacheLookup[keyId]
304
+ local oldE = cacheEntries[oldIdx]
305
+ alloc (ch, oldE.keyLen)
306
+ buffer.copy (ch.buff, ch.cursor, cacheRaw, oldE.keyOff, oldE.keyLen)
307
+ ch.cursor += oldE.keyLen
308
+ end
309
+ end
310
+
311
+ local rawBuf = buffer.create (scratchTotal)
312
+ if scratchTotal > 0 then
313
+ buffer.copy (rawBuf, 0, scratch.buff, 0, scratchTotal)
314
+ end
315
+
316
+ ch.deltas[deltaId] = {
317
+ raw = rawBuf,
318
+ entries = entries,
319
+ keyLookup = keyLookup,
320
+ count = entryCount,
321
+ }
322
+
323
+ releaseScratch ()
324
+ end,
325
+
326
+ read = function (src: buffer, pos: number, refs: { Instance }?): ({ [any]: any }, number)
327
+ local flag = buffer.readu8 (src, pos)
328
+ local absPos = pos + 1
329
+
330
+ if flag == FLAG_UNCHANGED then
331
+ local cache = Baseline.getCache (deltaId)
332
+ return if cache then table.clone (cache) else {}, 1
333
+ end
334
+
335
+ if flag == FLAG_FULL then
336
+ local count, lenBytes = Varint.read (src, absPos)
337
+ absPos += lenBytes
338
+
339
+ if maxCount and count > maxCount then
340
+ error (`[Lync] Map count {count} exceeds max {maxCount}`)
341
+ end
342
+
343
+ local result = {}
344
+
345
+ if keyDRead and valDRead and keySize and valSize then
346
+ for _ = 1, count do
347
+ local k = keyDRead (src, absPos)
348
+ local v = valDRead (src, absPos + keySize)
349
+ result[k] = v
350
+ absPos += entrySize :: number
351
+ end
352
+ else
353
+ for _ = 1, count do
354
+ local k, kn = readKey (src, absPos, refs)
355
+ absPos += kn
356
+ local v, vn = readVal (src, absPos, refs)
357
+ absPos += vn
358
+ result[k] = v
359
+ end
360
+ end
361
+
362
+ Baseline.setCache (deltaId, result)
363
+ return result, absPos - pos
364
+ end
365
+
366
+ local cache = Baseline.getCache (deltaId) :: { [any]: any }?
367
+ local result = if cache then table.clone (cache) else {}
368
+
369
+ local upsertN, unBytes = Varint.read (src, absPos)
370
+ absPos += unBytes
371
+
372
+ for _ = 1, upsertN do
373
+ local k, kn = readKey (src, absPos, refs)
374
+ absPos += kn
375
+ local v, vn = readVal (src, absPos, refs)
376
+ absPos += vn
377
+ result[k] = v
378
+ end
379
+
380
+ local removeN, rmBytes = Varint.read (src, absPos)
381
+ absPos += rmBytes
382
+
383
+ for _ = 1, removeN do
384
+ local k, kn = readKey (src, absPos, refs)
385
+ absPos += kn
386
+ result[k] = nil
387
+ end
388
+
389
+ Baseline.setCache (deltaId, result)
390
+ return result, absPos - pos
391
+ end,
392
+ })
393
+ end
394
+
395
+ return table.freeze (Map)
@@ -0,0 +1,47 @@
1
+ --!strict
2
+ --!native
3
+ -- Optional codec. 1 byte flag, value only if present.
4
+
5
+ local Channel = require (script.Parent.Parent.Parent.internal.Channel)
6
+ local alloc = Channel.alloc
7
+ local Types = require (script.Parent.Parent.Parent.Types)
8
+
9
+ type ChannelState = Types.ChannelState
10
+ type Codec<T> = Types.Codec<T>
11
+
12
+ -- Public -----------------------------------------------------------------
13
+
14
+ local Optional = {}
15
+
16
+ function Optional.optional (inner: Codec<any>): Codec<any>
17
+ local innerWrite = inner.write
18
+ local innerRead = inner.read
19
+
20
+ return table.freeze ({
21
+ write = function (ch: ChannelState, value: any): ()
22
+ local c = ch.cursor
23
+ if c + 1 > ch.size then
24
+ alloc (ch, 1)
25
+ end
26
+ if value == nil then
27
+ buffer.writeu8 (ch.buff, c, 0)
28
+ ch.cursor = c + 1
29
+ else
30
+ buffer.writeu8 (ch.buff, c, 1)
31
+ ch.cursor = c + 1
32
+ innerWrite (ch, value)
33
+ end
34
+ end,
35
+ read = function (src: buffer, pos: number, refs: { Instance }?): (any, number)
36
+ local flag = buffer.readu8 (src, pos)
37
+ if flag == 0 then
38
+ return nil, 1
39
+ end
40
+
41
+ local value, n = innerRead (src, pos + 1, refs)
42
+ return value, 1 + n
43
+ end,
44
+ })
45
+ end
46
+
47
+ return table.freeze (Optional)
@@ -0,0 +1,151 @@
1
+ --!strict
2
+ --!native
3
+ -- Shared helpers for composite codecs: delta flags, scratch pool, struct field extraction, bool packing.
4
+
5
+ local Channel = require (script.Parent.Parent.Parent.internal.Channel)
6
+ local alloc = Channel.alloc
7
+
8
+ type ChannelState = any
9
+
10
+ -- Constants --------------------------------------------------------------
11
+
12
+ local band = bit32.band
13
+ local bor = bit32.bor
14
+ local lshift = bit32.lshift
15
+ local ceil = math.ceil
16
+ local min = math.min
17
+
18
+ local FLAG_DELTA = 0
19
+ local FLAG_FULL = 1
20
+ local FLAG_UNCHANGED = 2
21
+
22
+ -- State ------------------------------------------------------------------
23
+
24
+ local _nextDeltaId = 1
25
+ local _scratchStack = {} :: { ChannelState }
26
+ local _scratchDepth = 0
27
+
28
+ -- Public -----------------------------------------------------------------
29
+
30
+ local Shared = {
31
+ FLAG_DELTA = FLAG_DELTA,
32
+ FLAG_FULL = FLAG_FULL,
33
+ FLAG_UNCHANGED = FLAG_UNCHANGED,
34
+ }
35
+
36
+ function Shared.allocDeltaId (): number
37
+ local id = _nextDeltaId
38
+ _nextDeltaId += 1
39
+ return id
40
+ end
41
+
42
+ function Shared.acquireScratch (): ChannelState
43
+ _scratchDepth += 1
44
+ local scratch = _scratchStack[_scratchDepth]
45
+ if not scratch then
46
+ scratch = Channel.create ()
47
+ _scratchStack[_scratchDepth] = scratch
48
+ end
49
+ scratch.cursor = 0
50
+ table.clear (scratch.refs)
51
+ return scratch
52
+ end
53
+
54
+ function Shared.releaseScratch (): ()
55
+ _scratchDepth -= 1
56
+ end
57
+
58
+ function Shared.rangeEqual (a: buffer, offA: number, b: buffer, offB: number, len: number): boolean
59
+ local aligned = band (len, 0xFFFFFFFC)
60
+ for i = 0, aligned - 4, 4 do
61
+ if buffer.readu32 (a, offA + i) ~= buffer.readu32 (b, offB + i) then
62
+ return false
63
+ end
64
+ end
65
+
66
+ for i = aligned, len - 1 do
67
+ if buffer.readu8 (a, offA + i) ~= buffer.readu8 (b, offB + i) then
68
+ return false
69
+ end
70
+ end
71
+
72
+ return true
73
+ end
74
+
75
+ function Shared.extractFields (schema: { [string]: any }): ({ string }, { any }, { string })
76
+ local dataKeys = {} :: { string }
77
+ local dataCodecs = {} :: { any }
78
+ local boolKeys = {} :: { string }
79
+
80
+ for key, codec in schema do
81
+ if codec._isBool then
82
+ table.insert (boolKeys, key)
83
+ else
84
+ table.insert (dataKeys, key)
85
+ end
86
+ end
87
+
88
+ table.sort (dataKeys)
89
+ table.sort (boolKeys)
90
+
91
+ if #dataKeys == 0 and #boolKeys == 0 then
92
+ error ("[Lync] Struct requires at least one field")
93
+ end
94
+
95
+ for i = 1, #dataKeys do
96
+ dataCodecs[i] = schema[dataKeys[i]]
97
+ end
98
+
99
+ table.freeze (dataKeys)
100
+ table.freeze (dataCodecs)
101
+ table.freeze (boolKeys)
102
+
103
+ return dataKeys, dataCodecs, boolKeys
104
+ end
105
+
106
+ function Shared.packBools (ch: ChannelState, boolKeys: { string }, boolCount: number, value: any): ()
107
+ local boolByteCount = ceil (boolCount / 8)
108
+ alloc (ch, boolByteCount)
109
+ local b = ch.buff
110
+ local c = ch.cursor
111
+
112
+ for byteIdx = 0, boolByteCount - 1 do
113
+ local packed = 0
114
+ local base = byteIdx * 8
115
+ local limit = min (8, boolCount - base)
116
+
117
+ for bit = 0, limit - 1 do
118
+ if value[boolKeys[base + bit + 1]] then
119
+ packed = bor (packed, lshift (1, bit))
120
+ end
121
+ end
122
+
123
+ buffer.writeu8 (b, c + byteIdx, packed)
124
+ end
125
+
126
+ ch.cursor = c + boolByteCount
127
+ end
128
+
129
+ function Shared.unpackBools (
130
+ src: buffer,
131
+ pos: number,
132
+ boolKeys: { string },
133
+ boolCount: number,
134
+ result: any
135
+ ): number
136
+ local boolByteCount = ceil (boolCount / 8)
137
+
138
+ for byteIdx = 0, boolByteCount - 1 do
139
+ local byte = buffer.readu8 (src, pos + byteIdx)
140
+ local base = byteIdx * 8
141
+ local limit = min (8, boolCount - base)
142
+
143
+ for bit = 0, limit - 1 do
144
+ result[boolKeys[base + bit + 1]] = band (byte, lshift (1, bit)) ~= 0
145
+ end
146
+ end
147
+
148
+ return boolByteCount
149
+ end
150
+
151
+ return table.freeze (Shared)