@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,224 @@
1
+ --!strict
2
+ --!optimize 2
3
+ -- O(1) disconnect listener system with thread reuse.
4
+
5
+ local Types = require (script.Parent.Parent.Types)
6
+
7
+ type Connection = Types.Connection
8
+
9
+ -- Listener state: single numeric field for both connected and once checks.
10
+ local STATE_DISCONNECTED = 0
11
+ local STATE_CONNECTED = 1
12
+ local STATE_ONCE = 2
13
+
14
+ -- Private ----------------------------------------------------------------
15
+
16
+ local _freeThread: thread? = nil
17
+
18
+ local function passer (fn: (...any) -> (), ...: any): ()
19
+ local acquired = _freeThread
20
+ _freeThread = nil
21
+ fn (...)
22
+ _freeThread = acquired
23
+ end
24
+
25
+ local function yielder (): ()
26
+ while true do
27
+ passer (coroutine.yield ())
28
+ end
29
+ end
30
+
31
+ local function spawn (fn: (...any) -> (), ...: any): ()
32
+ if not _freeThread then
33
+ _freeThread = coroutine.create (yielder)
34
+ coroutine.resume (_freeThread :: thread)
35
+ end
36
+ task.spawn (_freeThread :: thread, fn, ...)
37
+ end
38
+
39
+ -- Entry doubles as the Connection handle , 1 alloc per listener instead of 2.
40
+ local EntryImpl = {}
41
+ EntryImpl.__index = EntryImpl
42
+
43
+ function EntryImpl.disconnect (self: any): ()
44
+ if self._state == STATE_DISCONNECTED then
45
+ return
46
+ end
47
+
48
+ self._state = STATE_DISCONNECTED
49
+ self.connected = false
50
+
51
+ local entries = self._signal._entries
52
+ local count = self._signal._count
53
+ local index = self._index
54
+
55
+ if count == 0 or index > count then
56
+ return
57
+ end
58
+
59
+ if index < count then
60
+ local last = entries[count]
61
+ entries[index] = last
62
+ last._index = index
63
+ end
64
+
65
+ entries[count] = nil
66
+ self._signal._count = count - 1
67
+ end
68
+
69
+ local SignalImpl = {}
70
+ SignalImpl.__index = SignalImpl
71
+
72
+ local function addEntry (self: any, callback: (...any) -> (), isOnce: boolean): Connection
73
+ local count = self._count + 1
74
+ self._count = count
75
+
76
+ local entry = setmetatable ({
77
+ fn = callback,
78
+ _state = if isOnce then STATE_ONCE else STATE_CONNECTED,
79
+ connected = true,
80
+ _signal = self,
81
+ _index = count,
82
+ }, EntryImpl)
83
+
84
+ self._entries[count] = entry
85
+ return entry :: any
86
+ end
87
+
88
+ function SignalImpl.connect (self: any, callback: (...any) -> ()): Connection
89
+ return addEntry (self, callback, false)
90
+ end
91
+
92
+ function SignalImpl.once (self: any, callback: (...any) -> ()): Connection
93
+ return addEntry (self, callback, true)
94
+ end
95
+
96
+ function SignalImpl.wait (self: any): ...any
97
+ local thread = coroutine.running ()
98
+ local entry: any
99
+
100
+ entry = addEntry (self, function (...: any): ()
101
+ entry:disconnect ()
102
+ task.spawn (thread, ...)
103
+ end, true)
104
+
105
+ return coroutine.yield ()
106
+ end
107
+
108
+ -- Dispatches to all listeners via task.spawn (safe for yielding handlers).
109
+ function SignalImpl.fire (self: any, ...: any): ()
110
+ local entries = self._entries
111
+ local count = self._count
112
+ if count == 0 then
113
+ return
114
+ end
115
+
116
+ if count == 1 then
117
+ local entry = entries[1]
118
+ local state = entry._state
119
+ if state == STATE_DISCONNECTED then
120
+ return
121
+ end
122
+
123
+ spawn (entry.fn, ...)
124
+
125
+ if state == STATE_ONCE then
126
+ entry:disconnect ()
127
+ end
128
+ return
129
+ end
130
+
131
+ local snapshot = self._snapshot
132
+ table.move (entries, 1, count, 1, snapshot)
133
+
134
+ local tail = count + 1
135
+ if snapshot[tail] ~= nil then
136
+ snapshot[tail] = nil
137
+ end
138
+
139
+ for i = 1, count do
140
+ local entry = snapshot[i]
141
+ snapshot[i] = nil
142
+
143
+ local state = entry._state
144
+ if state == STATE_DISCONNECTED then
145
+ continue
146
+ end
147
+
148
+ spawn (entry.fn, ...)
149
+
150
+ if state == STATE_ONCE then
151
+ entry:disconnect ()
152
+ end
153
+ end
154
+ end
155
+
156
+ -- Synchronous dispatch. Only safe when all listeners are non-yielding.
157
+ function SignalImpl.fireSync (self: any, ...: any): ()
158
+ local entries = self._entries
159
+ local count = self._count
160
+ if count == 0 then
161
+ return
162
+ end
163
+
164
+ if count == 1 then
165
+ local entry = entries[1]
166
+ local state = entry._state
167
+ if state == STATE_DISCONNECTED then
168
+ return
169
+ end
170
+
171
+ entry.fn (...)
172
+
173
+ if state == STATE_ONCE then
174
+ entry:disconnect ()
175
+ end
176
+ return
177
+ end
178
+
179
+ local snapshot = self._snapshot
180
+ table.move (entries, 1, count, 1, snapshot)
181
+
182
+ local tail = count + 1
183
+ if snapshot[tail] ~= nil then
184
+ snapshot[tail] = nil
185
+ end
186
+
187
+ for i = 1, count do
188
+ local entry = snapshot[i]
189
+ snapshot[i] = nil
190
+
191
+ local state = entry._state
192
+ if state == STATE_DISCONNECTED then
193
+ continue
194
+ end
195
+
196
+ entry.fn (...)
197
+
198
+ if state == STATE_ONCE then
199
+ entry:disconnect ()
200
+ end
201
+ end
202
+ end
203
+
204
+ function SignalImpl.disconnectAll (self: any): ()
205
+ local entries = self._entries
206
+
207
+ for i = 1, self._count do
208
+ entries[i]._state = STATE_DISCONNECTED
209
+ entries[i].connected = false
210
+ entries[i] = nil
211
+ end
212
+
213
+ self._count = 0
214
+ end
215
+
216
+ -- Public -----------------------------------------------------------------
217
+
218
+ local Signal = {}
219
+
220
+ function Signal.create (): any
221
+ return setmetatable ({ _entries = {}, _count = 0, _snapshot = {} }, SignalImpl)
222
+ end
223
+
224
+ return table.freeze (Signal)
@@ -0,0 +1,49 @@
1
+ --!strict
2
+ --!native
3
+ -- Fixed-size codec factory. Every fixed-size codec calls Base.define once.
4
+
5
+ local Channel = require (script.Parent.Parent.internal.Channel)
6
+ local alloc = Channel.alloc
7
+
8
+ -- Public -----------------------------------------------------------------
9
+
10
+ local Base = {}
11
+
12
+ function Base.define <T>(
13
+ size: number,
14
+ directWrite: (b: buffer, offset: number, value: T) -> (),
15
+ directRead: (b: buffer, offset: number) -> T
16
+ ): any
17
+ return table.freeze ({
18
+ _size = size,
19
+ _directWrite = directWrite,
20
+ _directRead = directRead,
21
+ write = function (ch: any, value: T): ()
22
+ local c = ch.cursor
23
+ if c + size > ch.size then
24
+ alloc (ch, size)
25
+ end
26
+ directWrite (ch.buff, c, value)
27
+ ch.cursor = c + size
28
+ end,
29
+ read = function (src: buffer, pos: number, _refs: { Instance }?): (T, number)
30
+ return directRead (src, pos), size
31
+ end,
32
+ })
33
+ end
34
+
35
+ function Base.zero (): any
36
+ return table.freeze ({
37
+ _size = 0,
38
+ _directWrite = function (_b: buffer, _offset: number, _value: any): () end,
39
+ _directRead = function (_b: buffer, _offset: number): any
40
+ return nil
41
+ end,
42
+ write = function (_ch: any, _value: any): () end,
43
+ read = function (_src: buffer, _pos: number, _refs: { Instance }?): (any, number)
44
+ return nil, 0
45
+ end,
46
+ })
47
+ end
48
+
49
+ return table.freeze (Base)
@@ -0,0 +1,275 @@
1
+ --!strict
2
+ --!native
3
+ -- array and deltaArray 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 Array = {}
26
+
27
+ function Array.deltaArray (element: Codec<any>, maxCount: number?): Codec<{ any }>
28
+ local deltaId = Shared.allocDeltaId ()
29
+
30
+ local writeElement = element.write
31
+ local readElement = element.read
32
+
33
+ local elemSize = (element :: any)._size :: number?
34
+ local elemDirect = (element :: any)._directWrite
35
+ local elemDRead = (element :: any)._directRead
36
+
37
+ return table.freeze ({
38
+ _isDelta = true,
39
+
40
+ write = function (ch: ChannelState, value: { any }): ()
41
+ local len = #value
42
+ local cache = ch.deltas[deltaId] :: { raw: buffer, bounds: { number }, len: number }?
43
+ local scratch = acquireScratch ()
44
+
45
+ local bounds = table.create (len + 1)
46
+ bounds[1] = 0
47
+
48
+ if elemDirect and elemSize then
49
+ local totalBytes = len * elemSize
50
+ alloc (scratch, totalBytes)
51
+ local b = scratch.buff
52
+ local c = 0
53
+ for i = 1, len do
54
+ elemDirect (b, c, value[i])
55
+ c += elemSize
56
+ bounds[i + 1] = c
57
+ end
58
+ scratch.cursor = totalBytes
59
+ else
60
+ for i = 1, len do
61
+ writeElement (scratch, value[i])
62
+ bounds[i + 1] = scratch.cursor
63
+ end
64
+ end
65
+
66
+ local scratchTotal = scratch.cursor
67
+
68
+ if not cache or cache.len ~= len then
69
+ alloc (ch, 1)
70
+ buffer.writeu8 (ch.buff, ch.cursor, FLAG_FULL)
71
+ ch.cursor += 1
72
+ Varint.write (ch, len)
73
+
74
+ alloc (ch, scratchTotal)
75
+ buffer.copy (ch.buff, ch.cursor, scratch.buff, 0, scratchTotal)
76
+ ch.cursor += scratchTotal
77
+ else
78
+ local cacheRaw = cache.raw
79
+ local cacheBds = cache.bounds
80
+ local dirty = table.create (len)
81
+ local dirtyN = 0
82
+
83
+ for i = 1, len do
84
+ local newOff = bounds[i]
85
+ local newLen = bounds[i + 1] - newOff
86
+ local oldOff = cacheBds[i]
87
+ local oldLen = cacheBds[i + 1] - oldOff
88
+
89
+ if
90
+ newLen ~= oldLen
91
+ or not rangeEqual (scratch.buff, newOff, cacheRaw, oldOff, newLen)
92
+ then
93
+ dirtyN += 1
94
+ dirty[dirtyN] = i
95
+ end
96
+ end
97
+
98
+ if dirtyN == 0 then
99
+ alloc (ch, 1)
100
+ buffer.writeu8 (ch.buff, ch.cursor, FLAG_UNCHANGED)
101
+ ch.cursor += 1
102
+ releaseScratch ()
103
+ return
104
+ end
105
+
106
+ alloc (ch, 1)
107
+ buffer.writeu8 (ch.buff, ch.cursor, FLAG_DELTA)
108
+ ch.cursor += 1
109
+ Varint.write (ch, dirtyN)
110
+
111
+ for j = 1, dirtyN do
112
+ local i = dirty[j]
113
+ local newOff = bounds[i]
114
+ local newLen = bounds[i + 1] - newOff
115
+
116
+ Varint.write (ch, i - 1)
117
+ alloc (ch, newLen)
118
+ buffer.copy (ch.buff, ch.cursor, scratch.buff, newOff, newLen)
119
+ ch.cursor += newLen
120
+ end
121
+ end
122
+
123
+ local rawBuf = buffer.create (scratchTotal)
124
+ buffer.copy (rawBuf, 0, scratch.buff, 0, scratchTotal)
125
+
126
+ table.freeze (bounds)
127
+ ch.deltas[deltaId] = { raw = rawBuf, bounds = bounds, len = len }
128
+ releaseScratch ()
129
+ end,
130
+
131
+ read = function (src: buffer, pos: number, refs: { Instance }?): ({ any }, number)
132
+ local flag = buffer.readu8 (src, pos)
133
+ local absPos = pos + 1
134
+
135
+ if flag == FLAG_UNCHANGED then
136
+ local cache = Baseline.getCache (deltaId)
137
+ return if cache then table.clone (cache) else {}, 1
138
+ end
139
+
140
+ if flag == FLAG_FULL then
141
+ local len, lenBytes = Varint.read (src, absPos)
142
+ absPos += lenBytes
143
+
144
+ if maxCount and len > maxCount then
145
+ error (`[Lync] Array count {len} exceeds max {maxCount}`)
146
+ end
147
+
148
+ local result = table.create (len)
149
+
150
+ if elemDRead and elemSize then
151
+ for i = 1, len do
152
+ result[i] = elemDRead (src, absPos)
153
+ absPos += elemSize
154
+ end
155
+ else
156
+ for i = 1, len do
157
+ local val, n = readElement (src, absPos, refs)
158
+ result[i] = val
159
+ absPos += n
160
+ end
161
+ end
162
+
163
+ Baseline.setCache (deltaId, result)
164
+ return result, absPos - pos
165
+ end
166
+
167
+ local cache = Baseline.getCache (deltaId) :: { any }?
168
+ local result = if cache then table.clone (cache) else {}
169
+
170
+ local dirtyN, dnBytes = Varint.read (src, absPos)
171
+ absPos += dnBytes
172
+
173
+ for _ = 1, dirtyN do
174
+ local idx, idxBytes = Varint.read (src, absPos)
175
+ absPos += idxBytes
176
+
177
+ if elemDRead and elemSize then
178
+ result[idx + 1] = elemDRead (src, absPos)
179
+ absPos += elemSize
180
+ else
181
+ local val, n = readElement (src, absPos, refs)
182
+ result[idx + 1] = val
183
+ absPos += n
184
+ end
185
+ end
186
+
187
+ Baseline.setCache (deltaId, result)
188
+ return result, absPos - pos
189
+ end,
190
+ })
191
+ end
192
+
193
+ function Array.array (element: Codec<any>, maxCount: number?): Codec<{ any }>
194
+ local elementSize = (element :: any)._size :: number?
195
+ local directWrite = (element :: any)._directWrite
196
+ local directRead = (element :: any)._directRead
197
+
198
+ if elementSize and directWrite and directRead then
199
+ return table.freeze ({
200
+ write = function (ch: ChannelState, value: { any }): ()
201
+ local len = #value
202
+ Varint.write (ch, len)
203
+ if len == 0 then
204
+ return
205
+ end
206
+
207
+ local totalBytes = len * elementSize
208
+ local c = ch.cursor
209
+ if c + totalBytes > ch.size then
210
+ alloc (ch, totalBytes)
211
+ end
212
+
213
+ local b = ch.buff
214
+ for i = 1, len do
215
+ directWrite (b, c, value[i])
216
+ c += elementSize
217
+ end
218
+ ch.cursor = c
219
+ end,
220
+ read = function (src: buffer, pos: number, _refs: { Instance }?): ({ any }, number)
221
+ local len, lenBytes = Varint.read (src, pos)
222
+
223
+ if maxCount and len > maxCount then
224
+ error (`[Lync] Array count {len} exceeds max {maxCount}`)
225
+ end
226
+
227
+ local result = table.create (len)
228
+ local c = pos + lenBytes
229
+
230
+ for i = 1, len do
231
+ result[i] = directRead (src, c)
232
+ c += elementSize
233
+ end
234
+
235
+ return result, c - pos
236
+ end,
237
+ })
238
+ end
239
+
240
+ local writeElement = element.write
241
+ local readElement = element.read
242
+
243
+ return table.freeze ({
244
+ write = function (ch: ChannelState, value: { any }): ()
245
+ local len = #value
246
+ Varint.write (ch, len)
247
+ if elementSize and len > 0 then
248
+ alloc (ch, len * elementSize)
249
+ end
250
+ for i = 1, len do
251
+ writeElement (ch, value[i])
252
+ end
253
+ end,
254
+ read = function (src: buffer, pos: number, refs: { Instance }?): ({ any }, number)
255
+ local len, lenBytes = Varint.read (src, pos)
256
+
257
+ if maxCount and len > maxCount then
258
+ error (`[Lync] Array count {len} exceeds max {maxCount}`)
259
+ end
260
+
261
+ local result = table.create (len)
262
+ local absPos = pos + lenBytes
263
+
264
+ for i = 1, len do
265
+ local value, n = readElement (src, absPos, refs)
266
+ result[i] = value
267
+ absPos += n
268
+ end
269
+
270
+ return result, absPos - pos
271
+ end,
272
+ })
273
+ end
274
+
275
+ return table.freeze (Array)