@axpecter/lync 2.1.2 → 2.2.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 (56) hide show
  1. package/README.md +241 -349
  2. package/package.json +1 -1
  3. package/src/Types.luau +53 -13
  4. package/src/api/Group.luau +41 -51
  5. package/src/api/Packet.luau +145 -143
  6. package/src/api/Query.luau +204 -202
  7. package/src/api/Scope.luau +36 -38
  8. package/src/api/Signal.luau +68 -94
  9. package/src/codec/Base.luau +43 -23
  10. package/src/codec/composite/Array.luau +161 -152
  11. package/src/codec/composite/Map.luau +37 -53
  12. package/src/codec/composite/Optional.luau +15 -21
  13. package/src/codec/composite/Shared.luau +78 -41
  14. package/src/codec/composite/Struct.luau +64 -125
  15. package/src/codec/composite/Tagged.luau +15 -25
  16. package/src/codec/composite/Tuple.luau +15 -20
  17. package/src/codec/datatype/Buffer.luau +44 -51
  18. package/src/codec/datatype/CFrame.luau +72 -104
  19. package/src/codec/datatype/Color.luau +14 -10
  20. package/src/codec/datatype/Instance.luau +32 -42
  21. package/src/codec/datatype/IntVector.luau +24 -22
  22. package/src/codec/datatype/NumberRange.luau +21 -12
  23. package/src/codec/datatype/Ray.luau +13 -7
  24. package/src/codec/datatype/Rect.luau +13 -7
  25. package/src/codec/datatype/Region.luau +25 -22
  26. package/src/codec/datatype/Sequence.luau +144 -118
  27. package/src/codec/datatype/String.luau +29 -59
  28. package/src/codec/datatype/UDim.luau +29 -30
  29. package/src/codec/datatype/Vector.luau +89 -105
  30. package/src/codec/meta/Auto.luau +194 -246
  31. package/src/codec/meta/Bitfield.luau +37 -36
  32. package/src/codec/meta/Custom.luau +10 -10
  33. package/src/codec/meta/Enum.luau +8 -11
  34. package/src/codec/meta/Float.luau +16 -20
  35. package/src/codec/meta/Nothing.luau +2 -2
  36. package/src/codec/meta/Unknown.luau +22 -32
  37. package/src/codec/primitive/Bool.luau +9 -5
  38. package/src/codec/primitive/Float16.luau +13 -23
  39. package/src/codec/primitive/Int.luau +20 -28
  40. package/src/codec/primitive/Number.luau +6 -10
  41. package/src/codec/primitive/Signed.luau +32 -0
  42. package/src/codec/primitive/Varint.luau +59 -28
  43. package/src/index.d.ts +8 -2
  44. package/src/init.luau +118 -143
  45. package/src/internal/Baseline.luau +17 -18
  46. package/src/internal/Channel.luau +143 -212
  47. package/src/internal/Middleware.luau +37 -47
  48. package/src/internal/Pool.luau +10 -10
  49. package/src/internal/Registry.luau +38 -34
  50. package/src/internal/Transport.luau +28 -0
  51. package/src/internal/Util.luau +26 -0
  52. package/src/transport/Bridge.luau +32 -24
  53. package/src/transport/Client.luau +51 -53
  54. package/src/transport/Gate.luau +183 -113
  55. package/src/transport/Reader.luau +95 -66
  56. package/src/transport/Server.luau +130 -125
@@ -1,20 +1,20 @@
1
1
  --!strict
2
2
  --!optimize 2
3
- -- O(1) disconnect signal system with thread reuse.
3
+ -- O(1) disconnect signal system with thread reuse and reentrancy-safe fire.
4
4
 
5
5
  local Types = require(script.Parent.Parent.Types)
6
6
 
7
- -- Constants -----------------------------------------------------------
7
+ -- Constants --------------------------------------------------------------
8
8
 
9
9
  local STATE_DISCONNECTED = 0
10
10
  local STATE_CONNECTED = 1
11
11
  local STATE_ONCE = 2
12
12
 
13
- -- State ---------------------------------------------------------------
13
+ -- State ------------------------------------------------------------------
14
14
 
15
15
  local _freeThread: thread? = nil
16
16
 
17
- -- Private -------------------------------------------------------------
17
+ -- Private ----------------------------------------------------------------
18
18
 
19
19
  local function passer(fn: (...any) -> (), ...): ()
20
20
  local thread = _freeThread
@@ -37,7 +37,6 @@ local function spawnCallback(fn: (...any) -> (), ...: any): ()
37
37
  task.spawn(_freeThread :: thread, fn, ...)
38
38
  end
39
39
 
40
- -- Entry type
41
40
  type Entry = {
42
41
  fn: (...any) -> (),
43
42
  _state: number,
@@ -47,57 +46,49 @@ type Entry = {
47
46
  _index: number,
48
47
  }
49
48
 
50
- -- EntryImpl metatable
51
49
  local EntryImpl = {}
52
50
  EntryImpl.__index = EntryImpl
53
51
 
54
- function EntryImpl.disconnect(self: Entry): ()
55
- if self._state == STATE_DISCONNECTED then
52
+ local function disconnectEntry(entry: Entry): ()
53
+ if entry._state == STATE_DISCONNECTED then
56
54
  return
57
55
  end
58
56
 
59
- self._state = STATE_DISCONNECTED
60
- self.connected = false
57
+ entry._state = STATE_DISCONNECTED
58
+ entry.connected = false
61
59
 
62
- local entries = self._signalEntries
63
- local countRef = self._signalCount
64
- local idx = self._index
65
- local last = countRef[1]
60
+ local entries = entry._signalEntries
61
+ local countRef = entry._signalCount
62
+ local idx, last = entry._index, countRef[1]
66
63
 
67
64
  if idx < last then
68
- -- Swap-remove: move last entry to this slot
69
65
  local moved = entries[last]
70
66
  entries[idx] = moved
71
67
  moved._index = idx
72
68
  end
73
-
74
- entries[last] = nil :: any
69
+ entries[last] = nil
75
70
  countRef[1] = last - 1
76
71
  end
77
72
 
73
+ EntryImpl.disconnect = disconnectEntry
78
74
  table.freeze(EntryImpl)
79
75
 
80
- -- Public --------------------------------------------------------------
81
-
82
- local SignalModule = {}
83
-
84
76
  type SignalFields = {
85
77
  _entries: { Entry },
86
78
  _count: { number },
87
- _snapshot: { Entry },
88
79
  }
89
80
 
90
81
  local SignalImpl = {}
91
82
  SignalImpl.__index = SignalImpl
92
83
 
93
- function SignalImpl.connect(self: SignalFields, callback: (...any) -> ()): Types.Connection
84
+ local function newEntry(self: SignalFields, callback: (...any) -> (), state: number): Entry
94
85
  local countRef = self._count
95
86
  local idx = countRef[1] + 1
96
87
  countRef[1] = idx
97
88
 
98
89
  local entry: Entry = setmetatable({
99
90
  fn = callback,
100
- _state = STATE_CONNECTED,
91
+ _state = state,
101
92
  connected = true,
102
93
  _signalEntries = self._entries,
103
94
  _signalCount = countRef,
@@ -105,40 +96,28 @@ function SignalImpl.connect(self: SignalFields, callback: (...any) -> ()): Types
105
96
  }, EntryImpl) :: any
106
97
 
107
98
  self._entries[idx] = entry
108
- return entry :: any
99
+ return entry
109
100
  end
110
101
 
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
115
-
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
102
+ function SignalImpl.connect(self: SignalFields, callback: (...any) -> ()): Types.Connection
103
+ return newEntry(self, callback, STATE_CONNECTED) :: any
104
+ end
124
105
 
125
- self._entries[idx] = entry
126
- return entry :: any
106
+ function SignalImpl.once(self: SignalFields, callback: (...any) -> ()): Types.Connection
107
+ return newEntry(self, callback, STATE_ONCE) :: any
127
108
  end
128
109
 
129
110
  function SignalImpl.wait(self: SignalFields): ...any
130
111
  local running = coroutine.running()
131
112
  local conn: Types.Connection
132
-
133
113
  conn = SignalImpl.connect(self, function(...: any)
134
114
  conn:disconnect()
135
115
  task.spawn(running, ...)
136
116
  end)
137
-
138
117
  return coroutine.yield()
139
118
  end
140
119
 
141
- function SignalImpl.fire(self: SignalFields, ...: any): ()
120
+ local function dispatch(self: SignalFields, sync: boolean, ...: any): ()
142
121
  local count = self._count[1]
143
122
  if count == 0 then
144
123
  return
@@ -146,78 +125,73 @@ function SignalImpl.fire(self: SignalFields, ...: any): ()
146
125
 
147
126
  local entries = self._entries
148
127
 
149
- -- Single-listener fast path
150
128
  if count == 1 then
151
129
  local entry = entries[1]
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, ...)
130
+ local state = entry._state
131
+ if state == STATE_ONCE then
132
+ disconnectEntry(entry)
133
+ if sync then
134
+ entry.fn(...)
135
+ else
136
+ spawnCallback(entry.fn, ...)
137
+ end
138
+ elseif state == STATE_CONNECTED then
139
+ if sync then
140
+ entry.fn(...)
141
+ else
142
+ spawnCallback(entry.fn, ...)
143
+ end
157
144
  end
158
145
  return
159
146
  end
160
147
 
161
- -- Multi-listener: snapshot to handle mid-fire disconnects
162
- local snapshot = self._snapshot
148
+ local snapshot = table.create(count)
163
149
  table.move(entries, 1, count, 1, snapshot)
164
150
 
165
151
  for i = 1, count do
166
152
  local entry = snapshot[i]
167
- snapshot[i] = nil :: any
168
-
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, ...)
153
+ local state = entry._state
154
+ if state == STATE_ONCE then
155
+ disconnectEntry(entry)
156
+ if sync then
157
+ entry.fn(...)
158
+ else
159
+ spawnCallback(entry.fn, ...)
160
+ end
161
+ elseif state == STATE_CONNECTED then
162
+ if sync then
163
+ entry.fn(...)
164
+ else
165
+ spawnCallback(entry.fn, ...)
166
+ end
174
167
  end
175
168
  end
176
169
  end
177
170
 
178
- function SignalImpl.fireSync(self: SignalFields, ...: any): ()
179
- local count = self._count[1]
180
- if count == 0 then
181
- return
182
- end
183
-
184
- local entries = self._entries
185
-
186
- if count == 1 then
187
- local entry = entries[1]
188
- if entry._state == STATE_ONCE then
189
- EntryImpl.disconnect(entry)
190
- entry.fn(...)
191
- elseif entry._state == STATE_CONNECTED then
192
- entry.fn(...)
193
- end
194
- return
195
- end
171
+ function SignalImpl.fire(self: SignalFields, ...: any): ()
172
+ dispatch(self, false, ...)
173
+ end
196
174
 
197
- local snapshot = self._snapshot
198
- table.move(entries, 1, count, 1, snapshot)
175
+ function SignalImpl.fireSync(self: SignalFields, ...: any): ()
176
+ dispatch(self, true, ...)
177
+ end
199
178
 
200
- for i = 1, count do
201
- local entry = snapshot[i]
202
- snapshot[i] = nil :: any
179
+ function SignalImpl.count(self: SignalFields): number
180
+ return self._count[1]
181
+ end
203
182
 
204
- if entry._state == STATE_ONCE then
205
- EntryImpl.disconnect(entry)
206
- entry.fn(...)
207
- elseif entry._state == STATE_CONNECTED then
208
- entry.fn(...)
209
- end
210
- end
183
+ function SignalImpl.hasListeners(self: SignalFields): boolean
184
+ return self._count[1] > 0
211
185
  end
212
186
 
213
187
  table.freeze(SignalImpl)
214
188
 
215
- function SignalModule.create(): any
216
- return setmetatable({
217
- _entries = {},
218
- _count = { 0 },
219
- _snapshot = {},
220
- }, SignalImpl)
189
+ -- Public -----------------------------------------------------------------
190
+
191
+ local SignalModule = {}
192
+
193
+ function SignalModule.create(): Types.SignalLike
194
+ return (setmetatable({ _entries = {}, _count = { 0 } }, SignalImpl) :: any) :: Types.SignalLike
221
195
  end
222
196
 
223
197
  return table.freeze(SignalModule)
@@ -1,23 +1,33 @@
1
1
  --!strict
2
2
  --!optimize 2
3
- -- Fixed-size codec factory with buffer allocation.
3
+ -- Fixed-size codec factory and channel buffer-grow primitive.
4
4
 
5
5
  local Types = require(script.Parent.Parent.Types)
6
6
 
7
- -- Constants -----------------------------------------------------------
7
+ -- Constants --------------------------------------------------------------
8
8
 
9
9
  local DEFAULT_MAX = 262144
10
+ local INITIAL_BUF = 1024
10
11
 
11
- -- State ---------------------------------------------------------------
12
+ -- State ------------------------------------------------------------------
12
13
 
13
14
  local _maxSize = DEFAULT_MAX
14
15
 
15
- -- Private -------------------------------------------------------------
16
+ -- Private ----------------------------------------------------------------
16
17
 
17
18
  local countlz = bit32.countlz
18
19
  local lshift = bit32.lshift
19
20
 
21
+ --[[
22
+ Grow `ch.buff` so `ch.cursor + bytes` fits. New size is the next
23
+ power of two >= needed, capped by _maxSize. Caller MUST re-read
24
+ `ch.buff` after this returns: the buffer reference is replaced.
25
+ ]]
20
26
  local function alloc(ch: Types.ChannelState, bytes: number): ()
27
+ if bytes == 0 then
28
+ return
29
+ end
30
+
21
31
  local needed = ch.cursor + bytes
22
32
  if needed <= ch.size then
23
33
  return
@@ -25,11 +35,10 @@ local function alloc(ch: Types.ChannelState, bytes: number): ()
25
35
 
26
36
  if needed > _maxSize then
27
37
  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
- )
38
+ if name then
39
+ error(`[Lync] Channel.alloc("{name}"): buffer overflow ({needed}B, max {_maxSize}B)`)
40
+ end
41
+ error(`[Lync] Channel.alloc: buffer overflow ({needed}B, max {_maxSize}B)`)
33
42
  end
34
43
 
35
44
  local newSize = lshift(1, 32 - countlz(needed - 1))
@@ -39,25 +48,31 @@ local function alloc(ch: Types.ChannelState, bytes: number): ()
39
48
  ch.size = newSize
40
49
  end
41
50
 
42
- -- Public --------------------------------------------------------------
51
+ -- Public -----------------------------------------------------------------
43
52
 
44
53
  local Base = {}
45
54
 
46
- function Base.alloc(ch: Types.ChannelState, bytes: number): ()
47
- alloc(ch, bytes)
48
- end
55
+ Base.INITIAL_BUF = INITIAL_BUF
56
+
57
+ Base.alloc = alloc
49
58
 
50
59
  function Base.setMaxSize(bytes: number): ()
51
60
  _maxSize = bytes
52
61
  end
53
62
 
63
+ --[[
64
+ Build a fixed-size codec. The codec exposes channel-level write/read
65
+ (which handles buffer growth and cursor bookkeeping) plus the raw
66
+ _directWrite / _directRead primitives composite codecs use in their
67
+ pre-sized fast paths.
68
+ ]]
54
69
  function Base.define<T>(
55
70
  size: number,
56
71
  directWrite: (b: buffer, offset: number, value: T) -> (),
57
72
  directRead: (b: buffer, offset: number) -> T,
58
73
  meta: { [string]: any }?
59
74
  ): Types.InternalCodec<T>
60
- local codec: any = {
75
+ local codec: { [string]: any } = {
61
76
  _size = size,
62
77
  _directWrite = directWrite,
63
78
  _directRead = directRead,
@@ -82,21 +97,26 @@ function Base.define<T>(
82
97
  end
83
98
  end
84
99
 
85
- return table.freeze(codec)
100
+ return table.freeze(codec) :: Types.InternalCodec<T>
86
101
  end
87
102
 
88
- function Base.zero(): Types.InternalCodec<nil>
103
+ --[[
104
+ Constant codec: zero bytes wire, decode always returns `value`. Used
105
+ by quantizers when min == max so the wire emits nothing yet decode
106
+ still produces a type-faithful value.
107
+ ]]
108
+ function Base.constant<T>(value: T): Types.InternalCodec<T>
89
109
  return table.freeze({
90
110
  _size = 0,
91
- _directWrite = function(_b: buffer, _off: number, _v: nil): () end,
92
- _directRead = function(_b: buffer, _off: number): nil
93
- return nil
111
+ _directWrite = function(_b: buffer, _off: number, _v: T): () end,
112
+ _directRead = function(_b: buffer, _off: number): T
113
+ return value
94
114
  end,
95
- write = function(_ch: Types.ChannelState, _v: nil): () end,
96
- read = function(_src: buffer, _pos: number, _refs: { Instance }?): (nil, number)
97
- return nil, 0
115
+ write = function(_ch: Types.ChannelState, _v: T): () end,
116
+ read = function(_src: buffer, _pos: number, _refs: { Instance }?): (T, number)
117
+ return value, 0
98
118
  end,
99
- } :: any)
119
+ }) :: Types.InternalCodec<T>
100
120
  end
101
121
 
102
122
  return table.freeze(Base)