@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
@@ -4,84 +4,73 @@
4
4
 
5
5
  local Types = require(script.Parent.Parent.Types)
6
6
 
7
- type Connection = Types.Connection
7
+ -- Public --------------------------------------------------------------
8
8
 
9
- -- Private -------------------------------------------------------------
9
+ local Scope = {}
10
10
 
11
- type ScopeFields = {
12
- _entries: { any }, -- Connection | RBXScriptConnection
13
- _count: number,
11
+ export type ScopeHandle = {
12
+ on: (self: ScopeHandle, source: any, fn: (...any) -> ()) -> Types.Connection,
13
+ once: (self: ScopeHandle, source: any, fn: (...any) -> ()) -> Types.Connection,
14
+ add: (self: ScopeHandle, connection: any) -> (),
15
+ destroy: (self: ScopeHandle) -> (),
14
16
  }
15
17
 
16
- local ScopeImpl = {}
17
- ScopeImpl.__index = ScopeImpl
18
-
19
- export type Scope = typeof(setmetatable({} :: ScopeFields, ScopeImpl))
20
-
21
- function ScopeImpl.add(self: Scope, conn: any): ()
22
- if not conn then
23
- return
24
- end
25
-
26
- local entries = self._entries
27
- local count = self._count + 1
28
- self._count = count
29
- entries[count] = conn
30
- end
18
+ local ScopeMeta = {}
19
+ ScopeMeta.__index = ScopeMeta
31
20
 
32
- function ScopeImpl.listen(self: Scope, source: any, callback: (...any) -> ()): ()
33
- local conn = source:listen(callback)
34
- local entries = self._entries
35
- local count = self._count + 1
36
- self._count = count
37
- entries[count] = conn
21
+ function ScopeMeta.on(self: any, source: any, fn: (...any) -> ()): Types.Connection
22
+ local conn = source:on(fn)
23
+ local conns = self._connections
24
+ local idx = self._count + 1
25
+ self._count = idx
26
+ conns[idx] = conn
27
+ return conn
38
28
  end
39
29
 
40
- function ScopeImpl.once(self: Scope, source: any, callback: (...any) -> ()): ()
41
- local conn = source:once(callback)
42
- local entries = self._entries
43
- local count = self._count + 1
44
- self._count = count
45
- entries[count] = conn
30
+ function ScopeMeta.once(self: any, source: any, fn: (...any) -> ()): Types.Connection
31
+ local conn = source:once(fn)
32
+ local conns = self._connections
33
+ local idx = self._count + 1
34
+ self._count = idx
35
+ conns[idx] = conn
36
+ return conn
46
37
  end
47
38
 
48
- function ScopeImpl.listenAll(self: Scope, namespace: any, callback: (...any) -> ()): ()
49
- local conn = namespace:listenAll(callback)
50
- local entries = self._entries
51
- local count = self._count + 1
52
- self._count = count
53
- entries[count] = conn
39
+ function ScopeMeta.add(self: any, connection: any): ()
40
+ local idx = self._count + 1
41
+ self._count = idx
42
+ self._connections[idx] = connection
54
43
  end
55
44
 
56
- function ScopeImpl.destroy(self: Scope): ()
57
- local entries = self._entries
45
+ function ScopeMeta.destroy(self: any): ()
46
+ local conns = self._connections
58
47
  local count = self._count
59
48
 
60
49
  for i = 1, count do
61
- local conn = entries[i]
62
- entries[i] = nil
63
-
64
- if typeof(conn) == "RBXScriptConnection" then
65
- conn:Disconnect()
66
- elseif conn.disconnect then
67
- conn:disconnect()
50
+ local conn = conns[i]
51
+ conns[i] = nil :: any
52
+
53
+ --[[
54
+ Support both Lync Connection (conn:disconnect())
55
+ and RBXScriptConnection (conn:Disconnect()).
56
+ ]]
57
+ if typeof(conn) == "table" then
58
+ if conn.disconnect then
59
+ conn:disconnect()
60
+ end
61
+ elseif typeof(conn) == "RBXScriptConnection" then
62
+ (conn :: RBXScriptConnection):Disconnect()
68
63
  end
69
64
  end
70
65
 
71
66
  self._count = 0
72
67
  end
73
68
 
74
- table.freeze(ScopeImpl)
75
-
76
- -- Public --------------------------------------------------------------
77
-
78
- local ScopeModule = {}
79
-
80
- function ScopeModule.create(): Scope
69
+ function Scope.create(): ScopeHandle
81
70
  return setmetatable({
82
- _entries = {},
71
+ _connections = {},
83
72
  _count = 0,
84
- }, ScopeImpl)
73
+ }, ScopeMeta) :: any
85
74
  end
86
75
 
87
- return table.freeze(ScopeModule)
76
+ return table.freeze(Scope)
@@ -1,26 +1,26 @@
1
1
  --!strict
2
2
  --!optimize 2
3
- -- O(1) disconnect listener system with thread reuse.
3
+ -- O(1) disconnect signal system with thread reuse.
4
4
 
5
5
  local Types = require(script.Parent.Parent.Types)
6
6
 
7
- type Connection = Types.Connection
8
-
9
7
  -- Constants -----------------------------------------------------------
10
8
 
11
9
  local STATE_DISCONNECTED = 0
12
10
  local STATE_CONNECTED = 1
13
11
  local STATE_ONCE = 2
14
12
 
15
- -- Private -------------------------------------------------------------
13
+ -- State ---------------------------------------------------------------
16
14
 
17
15
  local _freeThread: thread? = nil
18
16
 
19
- local function passer(fn: (...any) -> (), ...: any): ()
20
- local acquired = _freeThread
17
+ -- Private -------------------------------------------------------------
18
+
19
+ local function passer(fn: (...any) -> (), ...): ()
20
+ local thread = _freeThread
21
21
  _freeThread = nil
22
22
  fn(...)
23
- _freeThread = acquired
23
+ _freeThread = thread
24
24
  end
25
25
 
26
26
  local function yielder(): ()
@@ -29,7 +29,7 @@ local function yielder(): ()
29
29
  end
30
30
  end
31
31
 
32
- local function spawn(fn: (...any) -> (), ...: any): ()
32
+ local function spawnCallback(fn: (...any) -> (), ...: any): ()
33
33
  if not _freeThread then
34
34
  _freeThread = coroutine.create(yielder)
35
35
  coroutine.resume(_freeThread :: thread)
@@ -37,29 +37,21 @@ local function spawn(fn: (...any) -> (), ...: any): ()
37
37
  task.spawn(_freeThread :: thread, fn, ...)
38
38
  end
39
39
 
40
- -- Mutually recursive field types for Entry and Signal.
41
- -- Entry._signal references the signal's fields; Signal._entries holds entries.
42
- -- Luau resolves the cycle at type-check time.
43
- type EntryFields = {
40
+ -- Entry type
41
+ type Entry = {
44
42
  fn: (...any) -> (),
45
43
  _state: number,
46
44
  connected: boolean,
47
- _signal: SignalFields,
45
+ _signalEntries: { Entry },
46
+ _signalCount: { number },
48
47
  _index: number,
49
48
  }
50
49
 
51
- type SignalFields = {
52
- _entries: { EntryFields },
53
- _count: number,
54
- _snapshot: { EntryFields },
55
- }
56
-
50
+ -- EntryImpl metatable
57
51
  local EntryImpl = {}
58
52
  EntryImpl.__index = EntryImpl
59
53
 
60
- type EntrySelf = typeof(setmetatable({} :: EntryFields, EntryImpl))
61
-
62
- function EntryImpl.disconnect(self: EntrySelf): ()
54
+ function EntryImpl.disconnect(self: Entry): ()
63
55
  if self._state == STATE_DISCONNECTED then
64
56
  return
65
57
  end
@@ -67,136 +59,137 @@ function EntryImpl.disconnect(self: EntrySelf): ()
67
59
  self._state = STATE_DISCONNECTED
68
60
  self.connected = false
69
61
 
70
- local entries = self._signal._entries
71
- local count = self._signal._count
72
- local index = self._index
62
+ local entries = self._signalEntries
63
+ local countRef = self._signalCount
64
+ local idx = self._index
65
+ local last = countRef[1]
73
66
 
74
- if count == 0 or index > count then
75
- return
76
- end
77
-
78
- if index < count then
79
- local last = entries[count]
80
- entries[index] = last
81
- last._index = index
67
+ if idx < last then
68
+ -- Swap-remove: move last entry to this slot
69
+ local moved = entries[last]
70
+ entries[idx] = moved
71
+ moved._index = idx
82
72
  end
83
73
 
84
- entries[count] = nil :: any
85
- self._signal._count = count - 1
74
+ entries[last] = nil :: any
75
+ countRef[1] = last - 1
86
76
  end
87
77
 
88
- local SignalImpl = {}
89
- SignalImpl.__index = SignalImpl
78
+ table.freeze(EntryImpl)
90
79
 
91
- export type Signal = typeof(setmetatable({} :: SignalFields, SignalImpl))
80
+ -- Public --------------------------------------------------------------
92
81
 
93
- local function addEntry(self: Signal, callback: (...any) -> (), isOnce: boolean): Connection
94
- if type(callback) ~= "function" then
95
- error("[Lync] Expected function, got " .. type(callback))
96
- end
82
+ local SignalModule = {}
83
+
84
+ type SignalFields = {
85
+ _entries: { Entry },
86
+ _count: { number },
87
+ _snapshot: { Entry },
88
+ }
97
89
 
98
- local count = self._count + 1
99
- self._count = count
90
+ local SignalImpl = {}
91
+ SignalImpl.__index = SignalImpl
100
92
 
101
- local entry = setmetatable({
93
+ function SignalImpl.connect(self: SignalFields, callback: (...any) -> ()): Types.Connection
94
+ local countRef = self._count
95
+ local idx = countRef[1] + 1
96
+ countRef[1] = idx
97
+
98
+ local entry: Entry = setmetatable({
102
99
  fn = callback,
103
- _state = if isOnce then STATE_ONCE else STATE_CONNECTED,
100
+ _state = STATE_CONNECTED,
104
101
  connected = true,
105
- _signal = self :: any,
106
- _index = count,
107
- }, EntryImpl)
102
+ _signalEntries = self._entries,
103
+ _signalCount = countRef,
104
+ _index = idx,
105
+ }, EntryImpl) :: any
108
106
 
109
- self._entries[count] = entry :: any
107
+ self._entries[idx] = entry
110
108
  return entry :: any
111
109
  end
112
110
 
113
- function SignalImpl.connect(self: Signal, callback: (...any) -> ()): Connection
114
- return addEntry(self, callback, false)
115
- end
111
+ function SignalImpl.once(self: SignalFields, callback: (...any) -> ()): Types.Connection
112
+ local countRef = self._count
113
+ local idx = countRef[1] + 1
114
+ countRef[1] = idx
116
115
 
117
- function SignalImpl.once(self: Signal, callback: (...any) -> ()): Connection
118
- return addEntry(self, callback, true)
116
+ local entry: Entry = setmetatable({
117
+ fn = callback,
118
+ _state = STATE_ONCE,
119
+ connected = true,
120
+ _signalEntries = self._entries,
121
+ _signalCount = countRef,
122
+ _index = idx,
123
+ }, EntryImpl) :: any
124
+
125
+ self._entries[idx] = entry
126
+ return entry :: any
119
127
  end
120
128
 
121
- function SignalImpl.wait(self: Signal): ...any
122
- local thread = coroutine.running()
123
- local entry: any
129
+ function SignalImpl.wait(self: SignalFields): ...any
130
+ local running = coroutine.running()
131
+ local conn: Types.Connection
124
132
 
125
- entry = addEntry(self, function(...: any): ()
126
- entry:disconnect()
127
- task.spawn(thread, ...)
128
- end, true)
133
+ conn = SignalImpl.connect(self, function(...: any)
134
+ conn:disconnect()
135
+ task.spawn(running, ...)
136
+ end)
129
137
 
130
138
  return coroutine.yield()
131
139
  end
132
140
 
133
- -- Dispatches to all listeners via task.spawn(safe for yielding handlers).
134
- function SignalImpl.fire(self: Signal, ...: any): ()
135
- local entries = self._entries
136
- local count = self._count
141
+ function SignalImpl.fire(self: SignalFields, ...: any): ()
142
+ local count = self._count[1]
137
143
  if count == 0 then
138
144
  return
139
145
  end
140
146
 
147
+ local entries = self._entries
148
+
149
+ -- Single-listener fast path
141
150
  if count == 1 then
142
151
  local entry = entries[1]
143
- local state = entry._state
144
- if state == STATE_DISCONNECTED then
145
- return
146
- end
147
-
148
- spawn(entry.fn, ...)
149
-
150
- if state == STATE_ONCE then
151
- (entry :: any):disconnect()
152
+ if entry._state == STATE_ONCE then
153
+ EntryImpl.disconnect(entry)
154
+ spawnCallback(entry.fn, ...)
155
+ elseif entry._state == STATE_CONNECTED then
156
+ spawnCallback(entry.fn, ...)
152
157
  end
153
158
  return
154
159
  end
155
160
 
161
+ -- Multi-listener: snapshot to handle mid-fire disconnects
156
162
  local snapshot = self._snapshot
157
163
  table.move(entries, 1, count, 1, snapshot)
158
164
 
159
- local tail = count + 1
160
- if snapshot[tail] ~= nil then
161
- snapshot[tail] = nil :: any
162
- end
163
-
164
165
  for i = 1, count do
165
166
  local entry = snapshot[i]
166
167
  snapshot[i] = nil :: any
167
168
 
168
- local state = entry._state
169
- if state == STATE_DISCONNECTED then
170
- continue
171
- end
172
-
173
- spawn(entry.fn, ...)
174
-
175
- if state == STATE_ONCE then
176
- (entry :: any):disconnect()
169
+ if entry._state == STATE_ONCE then
170
+ EntryImpl.disconnect(entry)
171
+ spawnCallback(entry.fn, ...)
172
+ elseif entry._state == STATE_CONNECTED then
173
+ spawnCallback(entry.fn, ...)
177
174
  end
178
175
  end
179
176
  end
180
177
 
181
- -- Synchronous dispatch. Only safe when all listeners are non-yielding.
182
- function SignalImpl.fireSync(self: Signal, ...: any): ()
183
- local entries = self._entries
184
- local count = self._count
178
+ function SignalImpl.fireSync(self: SignalFields, ...: any): ()
179
+ local count = self._count[1]
185
180
  if count == 0 then
186
181
  return
187
182
  end
188
183
 
184
+ local entries = self._entries
185
+
189
186
  if count == 1 then
190
187
  local entry = entries[1]
191
- local state = entry._state
192
- if state == STATE_DISCONNECTED then
193
- return
194
- end
195
-
196
- entry.fn(...)
197
-
198
- if state == STATE_ONCE then
199
- (entry :: any):disconnect()
188
+ if entry._state == STATE_ONCE then
189
+ EntryImpl.disconnect(entry)
190
+ entry.fn(...)
191
+ elseif entry._state == STATE_CONNECTED then
192
+ entry.fn(...)
200
193
  end
201
194
  return
202
195
  end
@@ -204,52 +197,26 @@ function SignalImpl.fireSync(self: Signal, ...: any): ()
204
197
  local snapshot = self._snapshot
205
198
  table.move(entries, 1, count, 1, snapshot)
206
199
 
207
- local tail = count + 1
208
- if snapshot[tail] ~= nil then
209
- snapshot[tail] = nil :: any
210
- end
211
-
212
200
  for i = 1, count do
213
201
  local entry = snapshot[i]
214
202
  snapshot[i] = nil :: any
215
203
 
216
- local state = entry._state
217
- if state == STATE_DISCONNECTED then
218
- continue
219
- end
220
-
221
- entry.fn(...)
222
-
223
- if state == STATE_ONCE then
224
- (entry :: any):disconnect()
204
+ if entry._state == STATE_ONCE then
205
+ EntryImpl.disconnect(entry)
206
+ entry.fn(...)
207
+ elseif entry._state == STATE_CONNECTED then
208
+ entry.fn(...)
225
209
  end
226
210
  end
227
211
  end
228
212
 
229
- function SignalImpl.disconnectAll(self: Signal): ()
230
- local entries = self._entries
231
-
232
- for i = 1, self._count do
233
- entries[i]._state = STATE_DISCONNECTED
234
- entries[i].connected = false
235
- entries[i] = nil :: any
236
- end
237
-
238
- self._count = 0
239
- end
240
-
241
- table.freeze(EntryImpl)
242
213
  table.freeze(SignalImpl)
243
214
 
244
- -- Public --------------------------------------------------------------
245
-
246
- local SignalModule = {}
247
-
248
- function SignalModule.create(): Signal
215
+ function SignalModule.create(): any
249
216
  return setmetatable({
250
- _entries = {} :: { EntryFields },
251
- _count = 0,
252
- _snapshot = {} :: { EntryFields },
217
+ _entries = {},
218
+ _count = { 0 },
219
+ _snapshot = {},
253
220
  }, SignalImpl)
254
221
  end
255
222
 
@@ -1,53 +1,102 @@
1
1
  --!strict
2
- --!native
3
- -- Fixed-size codec factory. Every fixed-size codec calls Base.define once.
2
+ --!optimize 2
3
+ -- Fixed-size codec factory with buffer allocation.
4
4
 
5
- local Channel = require(script.Parent.Parent.internal.Channel)
6
5
  local Types = require(script.Parent.Parent.Types)
7
6
 
8
- type ChannelState = Types.ChannelState
7
+ -- Constants -----------------------------------------------------------
9
8
 
10
- local alloc = Channel.alloc
9
+ local DEFAULT_MAX = 262144
10
+
11
+ -- State ---------------------------------------------------------------
12
+
13
+ local _maxSize = DEFAULT_MAX
14
+
15
+ -- Private -------------------------------------------------------------
16
+
17
+ local countlz = bit32.countlz
18
+ local lshift = bit32.lshift
19
+
20
+ local function alloc(ch: Types.ChannelState, bytes: number): ()
21
+ local needed = ch.cursor + bytes
22
+ if needed <= ch.size then
23
+ return
24
+ end
25
+
26
+ if needed > _maxSize then
27
+ local name = ch.currentPacket
28
+ error(
29
+ if name
30
+ then `[Lync] Channel: buffer overflow ({needed}B, max {_maxSize}B) in "{name}"`
31
+ else `[Lync] Channel: buffer overflow ({needed}B, max {_maxSize}B)`
32
+ )
33
+ end
34
+
35
+ local newSize = lshift(1, 32 - countlz(needed - 1))
36
+ local newBuff = buffer.create(newSize)
37
+ buffer.copy(newBuff, 0, ch.buff, 0, ch.cursor)
38
+ ch.buff = newBuff
39
+ ch.size = newSize
40
+ end
11
41
 
12
42
  -- Public --------------------------------------------------------------
13
43
 
14
44
  local Base = {}
15
45
 
46
+ function Base.alloc(ch: Types.ChannelState, bytes: number): ()
47
+ alloc(ch, bytes)
48
+ end
49
+
50
+ function Base.setMaxSize(bytes: number): ()
51
+ _maxSize = bytes
52
+ end
53
+
16
54
  function Base.define<T>(
17
55
  size: number,
18
56
  directWrite: (b: buffer, offset: number, value: T) -> (),
19
- directRead: (b: buffer, offset: number) -> T
20
- ): any
21
- return table.freeze({
57
+ directRead: (b: buffer, offset: number) -> T,
58
+ meta: { [string]: any }?
59
+ ): Types.InternalCodec<T>
60
+ local codec: any = {
22
61
  _size = size,
23
62
  _directWrite = directWrite,
24
63
  _directRead = directRead,
25
- write = function(ch: ChannelState, value: T): ()
26
- local c = ch.cursor
27
- if c + size > ch.size then
64
+
65
+ write = function(ch: Types.ChannelState, value: T): ()
66
+ local cursor = ch.cursor
67
+ if cursor + size > ch.size then
28
68
  alloc(ch, size)
29
69
  end
30
- directWrite(ch.buff, c, value)
31
- ch.cursor = c + size
70
+ directWrite(ch.buff, cursor, value)
71
+ ch.cursor = cursor + size
32
72
  end,
73
+
33
74
  read = function(src: buffer, pos: number, _refs: { Instance }?): (T, number)
34
75
  return directRead(src, pos), size
35
76
  end,
36
- })
77
+ }
78
+
79
+ if meta then
80
+ for key, val in meta do
81
+ codec[key] = val
82
+ end
83
+ end
84
+
85
+ return table.freeze(codec)
37
86
  end
38
87
 
39
- function Base.zero(): any
88
+ function Base.zero(): Types.InternalCodec<nil>
40
89
  return table.freeze({
41
90
  _size = 0,
42
- _directWrite = function(_b: buffer, _offset: number, _value: any): () end,
43
- _directRead = function(_b: buffer, _offset: number): any
91
+ _directWrite = function(_b: buffer, _off: number, _v: nil): () end,
92
+ _directRead = function(_b: buffer, _off: number): nil
44
93
  return nil
45
94
  end,
46
- write = function(_ch: ChannelState, _value: any): () end,
47
- read = function(_src: buffer, _pos: number, _refs: { Instance }?): (any, number)
95
+ write = function(_ch: Types.ChannelState, _v: nil): () end,
96
+ read = function(_src: buffer, _pos: number, _refs: { Instance }?): (nil, number)
48
97
  return nil, 0
49
98
  end,
50
- })
99
+ } :: any)
51
100
  end
52
101
 
53
102
  return table.freeze(Base)