@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.
- package/README.md +300 -0
- package/package.json +38 -0
- package/src/Types.luau +63 -0
- package/src/api/Group.luau +126 -0
- package/src/api/Namespace.luau +226 -0
- package/src/api/Packet.luau +147 -0
- package/src/api/Query.luau +295 -0
- package/src/api/Signal.luau +224 -0
- package/src/codec/Base.luau +49 -0
- package/src/codec/composite/Array.luau +275 -0
- package/src/codec/composite/Map.luau +395 -0
- package/src/codec/composite/Optional.luau +47 -0
- package/src/codec/composite/Shared.luau +151 -0
- package/src/codec/composite/Struct.luau +440 -0
- package/src/codec/composite/Tagged.luau +222 -0
- package/src/codec/composite/Tuple.luau +143 -0
- package/src/codec/datatype/Buffer.luau +44 -0
- package/src/codec/datatype/CFrame.luau +51 -0
- package/src/codec/datatype/Color.luau +22 -0
- package/src/codec/datatype/Instance.luau +48 -0
- package/src/codec/datatype/IntVector.luau +25 -0
- package/src/codec/datatype/NumberRange.luau +14 -0
- package/src/codec/datatype/Ray.luau +27 -0
- package/src/codec/datatype/Rect.luau +21 -0
- package/src/codec/datatype/Region.luau +58 -0
- package/src/codec/datatype/Sequence.luau +129 -0
- package/src/codec/datatype/String.luau +87 -0
- package/src/codec/datatype/UDim.luau +27 -0
- package/src/codec/datatype/Vector.luau +25 -0
- package/src/codec/meta/Auto.luau +353 -0
- package/src/codec/meta/Bitfield.luau +191 -0
- package/src/codec/meta/Custom.luau +27 -0
- package/src/codec/meta/Enum.luau +80 -0
- package/src/codec/meta/Nothing.luau +9 -0
- package/src/codec/meta/Quantized.luau +170 -0
- package/src/codec/meta/Unknown.luau +35 -0
- package/src/codec/primitive/Bool.luau +30 -0
- package/src/codec/primitive/Float16.luau +111 -0
- package/src/codec/primitive/Number.luau +48 -0
- package/src/codec/primitive/Varint.luau +76 -0
- package/src/index.d.ts +279 -0
- package/src/init.luau +161 -0
- package/src/internal/Baseline.luau +41 -0
- package/src/internal/Channel.luau +235 -0
- package/src/internal/Middleware.luau +109 -0
- package/src/internal/Pool.luau +68 -0
- package/src/internal/Registry.luau +146 -0
- package/src/transport/Bridge.luau +66 -0
- package/src/transport/Client.luau +151 -0
- package/src/transport/Gate.luau +222 -0
- package/src/transport/Reader.luau +175 -0
- package/src/transport/Server.luau +364 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--!native
|
|
3
|
+
-- Per-channel buffer state with batching, delta cache, frame XOR.
|
|
4
|
+
|
|
5
|
+
local Types = require (script.Parent.Parent.Types)
|
|
6
|
+
|
|
7
|
+
type ChannelState = Types.ChannelState
|
|
8
|
+
|
|
9
|
+
-- Constants --------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
local INITIAL_SIZE = 1024
|
|
12
|
+
local MAX_SIZE = 262144
|
|
13
|
+
local MAX_BATCH = 65535
|
|
14
|
+
|
|
15
|
+
local min = math.min
|
|
16
|
+
local bxor = bit32.bxor
|
|
17
|
+
local band32 = bit32.band
|
|
18
|
+
local countlz = bit32.countlz
|
|
19
|
+
local lshift = bit32.lshift
|
|
20
|
+
|
|
21
|
+
-- State ------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
local _maxSize = MAX_SIZE
|
|
24
|
+
local _currentPacket = ""
|
|
25
|
+
|
|
26
|
+
-- Public -----------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
local Channel = {}
|
|
29
|
+
|
|
30
|
+
-- Override the maximum buffer size. Call before start(). Range: 4096–1048576.
|
|
31
|
+
function Channel.setMaxSize (bytes: number): ()
|
|
32
|
+
if bytes < 4096 or bytes > 1048576 then
|
|
33
|
+
error (`[Lync] Channel max size must be 4096–1048576, got {bytes}`)
|
|
34
|
+
end
|
|
35
|
+
_maxSize = bytes
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
function Channel.getMaxSize (): number
|
|
39
|
+
return _maxSize
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
function Channel.create (): ChannelState
|
|
43
|
+
return {
|
|
44
|
+
buff = buffer.create (INITIAL_SIZE),
|
|
45
|
+
cursor = 0,
|
|
46
|
+
size = INITIAL_SIZE,
|
|
47
|
+
refs = {},
|
|
48
|
+
lastId = -1,
|
|
49
|
+
countPos = 0,
|
|
50
|
+
itemCount = 0,
|
|
51
|
+
deltas = {},
|
|
52
|
+
prevDump = nil,
|
|
53
|
+
}
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
-- Grows the channel buffer to fit the requested bytes. Power-of-two sizing.
|
|
57
|
+
function Channel.alloc (ch: ChannelState, bytes: number): ()
|
|
58
|
+
local needed = ch.cursor + bytes
|
|
59
|
+
if needed <= ch.size then
|
|
60
|
+
return
|
|
61
|
+
end
|
|
62
|
+
if needed > _maxSize then
|
|
63
|
+
error (
|
|
64
|
+
`[Lync] Channel overflow writing "{_currentPacket}": {needed} bytes > {_maxSize} max (call Channel.setMaxSize to raise)`
|
|
65
|
+
)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
local newSize = lshift (1, 32 - countlz (needed - 1))
|
|
69
|
+
|
|
70
|
+
local newBuff = buffer.create (newSize)
|
|
71
|
+
buffer.copy (newBuff, 0, ch.buff, 0, ch.cursor)
|
|
72
|
+
ch.buff = newBuff
|
|
73
|
+
ch.size = newSize
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
local alloc = Channel.alloc
|
|
77
|
+
|
|
78
|
+
-- Seals the open batch header and returns a snapshot of the buffer + refs.
|
|
79
|
+
function Channel.sealAndDump (ch: ChannelState): (buffer, { Instance })
|
|
80
|
+
if ch.lastId >= 0 then
|
|
81
|
+
buffer.writeu16 (ch.buff, ch.countPos, ch.itemCount)
|
|
82
|
+
ch.lastId = -1
|
|
83
|
+
ch.countPos = 0
|
|
84
|
+
ch.itemCount = 0
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
local cur = ch.cursor
|
|
88
|
+
local out = buffer.create (cur)
|
|
89
|
+
buffer.copy (out, 0, ch.buff, 0, cur)
|
|
90
|
+
|
|
91
|
+
local refs = ch.refs
|
|
92
|
+
return out, if #refs > 0 then table.clone (refs) else {}
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
-- Sets the packet name for overflow diagnostics.
|
|
96
|
+
function Channel.setCurrentPacket (name: string): ()
|
|
97
|
+
_currentPacket = name
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
-- Writes a single value into the batch. Opens a new header on ID change or overflow.
|
|
101
|
+
function Channel.writeBatch (ch: ChannelState, id: number, name: string, codec: any, data: any): ()
|
|
102
|
+
if id ~= ch.lastId or ch.itemCount >= MAX_BATCH then
|
|
103
|
+
if ch.lastId >= 0 then
|
|
104
|
+
buffer.writeu16 (ch.buff, ch.countPos, ch.itemCount)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
_currentPacket = name
|
|
108
|
+
alloc (ch, 3)
|
|
109
|
+
local c = ch.cursor
|
|
110
|
+
buffer.writeu8 (ch.buff, c, id)
|
|
111
|
+
ch.countPos = c + 1
|
|
112
|
+
ch.cursor = c + 3
|
|
113
|
+
ch.lastId = id
|
|
114
|
+
ch.itemCount = 0
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
ch.itemCount += 1
|
|
118
|
+
codec.write (ch, data)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
-- Appends pre-serialized bytes into a batch. Used by broadcast fan-out.
|
|
122
|
+
function Channel.writeBatchRaw (
|
|
123
|
+
ch: ChannelState,
|
|
124
|
+
id: number,
|
|
125
|
+
src: buffer,
|
|
126
|
+
srcLen: number,
|
|
127
|
+
srcRefs: { Instance }?
|
|
128
|
+
): ()
|
|
129
|
+
if id ~= ch.lastId or ch.itemCount >= MAX_BATCH then
|
|
130
|
+
if ch.lastId >= 0 then
|
|
131
|
+
buffer.writeu16 (ch.buff, ch.countPos, ch.itemCount)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
alloc (ch, 3)
|
|
135
|
+
local c = ch.cursor
|
|
136
|
+
buffer.writeu8 (ch.buff, c, id)
|
|
137
|
+
ch.countPos = c + 1
|
|
138
|
+
ch.cursor = c + 3
|
|
139
|
+
ch.lastId = id
|
|
140
|
+
ch.itemCount = 0
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
ch.itemCount += 1
|
|
144
|
+
|
|
145
|
+
if srcLen > 0 then
|
|
146
|
+
alloc (ch, srcLen)
|
|
147
|
+
buffer.copy (ch.buff, ch.cursor, src, 0, srcLen)
|
|
148
|
+
ch.cursor += srcLen
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
if srcRefs then
|
|
152
|
+
local refs = ch.refs
|
|
153
|
+
table.move (srcRefs, 1, #srcRefs, #refs + 1, refs)
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
local function sealOpen (ch: ChannelState): ()
|
|
158
|
+
if ch.lastId >= 0 then
|
|
159
|
+
buffer.writeu16 (ch.buff, ch.countPos, ch.itemCount)
|
|
160
|
+
ch.lastId = -1
|
|
161
|
+
ch.countPos = 0
|
|
162
|
+
ch.itemCount = 0
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
-- Writes a query frame. Seals any open batch, then writes:
|
|
167
|
+
-- id (u8) + correlationId (u16) + status (u8)
|
|
168
|
+
-- Status 0 = payload follows. Status 1 = nil response.
|
|
169
|
+
function Channel.writeQuery (
|
|
170
|
+
ch: ChannelState,
|
|
171
|
+
id: number,
|
|
172
|
+
name: string,
|
|
173
|
+
correlationId: number,
|
|
174
|
+
codec: any?,
|
|
175
|
+
data: any?
|
|
176
|
+
): ()
|
|
177
|
+
_currentPacket = name
|
|
178
|
+
sealOpen (ch)
|
|
179
|
+
|
|
180
|
+
alloc (ch, 4)
|
|
181
|
+
local c = ch.cursor
|
|
182
|
+
buffer.writeu8 (ch.buff, c, id)
|
|
183
|
+
buffer.writeu16 (ch.buff, c + 1, correlationId)
|
|
184
|
+
|
|
185
|
+
if codec then
|
|
186
|
+
buffer.writeu8 (ch.buff, c + 3, 0)
|
|
187
|
+
ch.cursor = c + 4
|
|
188
|
+
codec.write (ch, data)
|
|
189
|
+
else
|
|
190
|
+
buffer.writeu8 (ch.buff, c + 3, 1)
|
|
191
|
+
ch.cursor = c + 4
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
-- XOR current frame against previous. Produces zero-heavy output for deflate.
|
|
196
|
+
function Channel.xorApply (current: buffer, previous: buffer?): buffer
|
|
197
|
+
local curLen = buffer.len (current)
|
|
198
|
+
if not previous then
|
|
199
|
+
return current
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
local prevLen = buffer.len (previous :: buffer)
|
|
203
|
+
if prevLen == 0 then
|
|
204
|
+
return current
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
local result = buffer.create (curLen)
|
|
208
|
+
local overlap = min (curLen, prevLen)
|
|
209
|
+
local prev = previous :: buffer
|
|
210
|
+
|
|
211
|
+
local aligned = band32 (overlap, 0xFFFFFFFC)
|
|
212
|
+
for i = 0, aligned - 4, 4 do
|
|
213
|
+
buffer.writeu32 (result, i, bxor (buffer.readu32 (current, i), buffer.readu32 (prev, i)))
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
for i = aligned, overlap - 1 do
|
|
217
|
+
buffer.writeu8 (result, i, bxor (buffer.readu8 (current, i), buffer.readu8 (prev, i)))
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
if curLen > overlap then
|
|
221
|
+
buffer.copy (result, overlap, current, overlap, curLen - overlap)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
return result
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
function Channel.reset (ch: ChannelState): ()
|
|
228
|
+
ch.cursor = 0
|
|
229
|
+
ch.lastId = -1
|
|
230
|
+
ch.countPos = 0
|
|
231
|
+
ch.itemCount = 0
|
|
232
|
+
table.clear (ch.refs)
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
return table.freeze (Channel)
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--!optimize 2
|
|
3
|
+
-- Ordered intercept chains for send and receive.
|
|
4
|
+
|
|
5
|
+
-- State ------------------------------------------------------------------
|
|
6
|
+
|
|
7
|
+
type Handler = (data: any, name: string, player: Player?) -> any?
|
|
8
|
+
|
|
9
|
+
type Chain = {
|
|
10
|
+
handlers: { Handler },
|
|
11
|
+
snapshot: { Handler }?,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
local _send: Chain = { handlers = {}, snapshot = nil }
|
|
15
|
+
local _receive: Chain = { handlers = {}, snapshot = nil }
|
|
16
|
+
|
|
17
|
+
-- Private ----------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
local function runChain (chain: { Handler }, data: any, name: string, player: Player?): any?
|
|
20
|
+
local current = data
|
|
21
|
+
for i = 1, #chain do
|
|
22
|
+
local result = chain[i] (current, name, player)
|
|
23
|
+
if result == nil then
|
|
24
|
+
return nil
|
|
25
|
+
end
|
|
26
|
+
current = result
|
|
27
|
+
end
|
|
28
|
+
return current
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
local function run (chain: Chain, data: any, name: string, player: Player?): any?
|
|
32
|
+
local handlers = chain.handlers
|
|
33
|
+
local count = #handlers
|
|
34
|
+
if count == 0 then
|
|
35
|
+
return data
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
local snapshot = chain.snapshot
|
|
39
|
+
if not snapshot then
|
|
40
|
+
snapshot = table.clone (handlers)
|
|
41
|
+
chain.snapshot = snapshot
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
local ok: boolean
|
|
45
|
+
local result: any
|
|
46
|
+
|
|
47
|
+
if count == 1 then
|
|
48
|
+
ok, result = pcall (snapshot[1], data, name, player)
|
|
49
|
+
else
|
|
50
|
+
ok, result = pcall (runChain, snapshot, data, name, player)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
if not ok then
|
|
54
|
+
warn (`[Lync] Middleware error on "{name}": {result}`)
|
|
55
|
+
return nil
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
return result
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
local function addTo (chain: Chain, fn: Handler): () -> ()
|
|
62
|
+
table.insert (chain.handlers, fn)
|
|
63
|
+
chain.snapshot = nil
|
|
64
|
+
|
|
65
|
+
return function (): ()
|
|
66
|
+
local pos = table.find (chain.handlers, fn)
|
|
67
|
+
if pos then
|
|
68
|
+
table.remove (chain.handlers, pos)
|
|
69
|
+
chain.snapshot = nil
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
-- Public -----------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
local Middleware = {}
|
|
77
|
+
|
|
78
|
+
function Middleware.addSend (fn: Handler): () -> ()
|
|
79
|
+
local remover = addTo (_send, fn)
|
|
80
|
+
Middleware.hasSend = true
|
|
81
|
+
|
|
82
|
+
return function (): ()
|
|
83
|
+
remover ()
|
|
84
|
+
Middleware.hasSend = #_send.handlers > 0
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
function Middleware.addReceive (fn: Handler): () -> ()
|
|
89
|
+
local remover = addTo (_receive, fn)
|
|
90
|
+
Middleware.hasReceive = true
|
|
91
|
+
|
|
92
|
+
return function (): ()
|
|
93
|
+
remover ()
|
|
94
|
+
Middleware.hasReceive = #_receive.handlers > 0
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
function Middleware.runSend (data: any, name: string, player: Player?): any?
|
|
99
|
+
return run (_send, data, name, player)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
function Middleware.runReceive (data: any, name: string, player: Player?): any?
|
|
103
|
+
return run (_receive, data, name, player)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
Middleware.hasSend = false
|
|
107
|
+
Middleware.hasReceive = false
|
|
108
|
+
|
|
109
|
+
return Middleware
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--!optimize 2
|
|
3
|
+
-- Stack-based ChannelState pool with warmup support.
|
|
4
|
+
|
|
5
|
+
local Channel = require (script.Parent.Channel)
|
|
6
|
+
local Types = require (script.Parent.Parent.Types)
|
|
7
|
+
|
|
8
|
+
type ChannelState = Types.ChannelState
|
|
9
|
+
|
|
10
|
+
-- Constants --------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
local DEFAULT_MAX = 16
|
|
13
|
+
|
|
14
|
+
-- State ------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
local _stack = {} :: { ChannelState }
|
|
17
|
+
local _depth = 0
|
|
18
|
+
local _maxSize = DEFAULT_MAX
|
|
19
|
+
|
|
20
|
+
-- Public -----------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
local Pool = {}
|
|
23
|
+
|
|
24
|
+
-- Override the max idle ChannelStates in the pool. Default 16. Range: 2–128.
|
|
25
|
+
function Pool.setMaxSize (count: number): ()
|
|
26
|
+
if count < 2 or count > 128 then
|
|
27
|
+
error (`[Lync] Pool max size must be 2–128, got {count}`)
|
|
28
|
+
end
|
|
29
|
+
_maxSize = count
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
function Pool.getMaxSize (): number
|
|
33
|
+
return _maxSize
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
function Pool.acquire (): ChannelState
|
|
37
|
+
if _depth > 0 then
|
|
38
|
+
local ch = _stack[_depth]
|
|
39
|
+
_stack[_depth] = nil :: any
|
|
40
|
+
_depth -= 1
|
|
41
|
+
return ch
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
return Channel.create ()
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
function Pool.release (ch: ChannelState): ()
|
|
48
|
+
ch.cursor = 0
|
|
49
|
+
ch.lastId = -1
|
|
50
|
+
ch.countPos = 0
|
|
51
|
+
ch.itemCount = 0
|
|
52
|
+
table.clear (ch.refs)
|
|
53
|
+
table.clear (ch.deltas)
|
|
54
|
+
ch.prevDump = nil
|
|
55
|
+
|
|
56
|
+
if _depth >= _maxSize then
|
|
57
|
+
return -- discard; GC will reclaim
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
_depth += 1
|
|
61
|
+
_stack[_depth] = ch
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
function Pool.size (): number
|
|
65
|
+
return _depth
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
return table.freeze (Pool)
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--!optimize 2
|
|
3
|
+
-- Deterministic ID assignment for packets and queries.
|
|
4
|
+
|
|
5
|
+
local Types = require (script.Parent.Parent.Types)
|
|
6
|
+
|
|
7
|
+
type Codec<T> = Types.Codec<T>
|
|
8
|
+
type Registration = Types.Registration
|
|
9
|
+
type RateLimitConfig = Types.RateLimitConfig
|
|
10
|
+
|
|
11
|
+
-- Constants --------------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
local KIND_PACKET = 0
|
|
14
|
+
local KIND_REQUEST = 1
|
|
15
|
+
local KIND_RESPONSE = 2
|
|
16
|
+
local MAX_PACKETS = 255
|
|
17
|
+
|
|
18
|
+
-- State ------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
local _entries = {} :: { Registration }
|
|
21
|
+
local _names = {} :: { [string]: number }
|
|
22
|
+
local _nextId = 1
|
|
23
|
+
|
|
24
|
+
-- Private ----------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
local function assign (
|
|
27
|
+
name: string,
|
|
28
|
+
baseName: string,
|
|
29
|
+
codec: Codec<any>,
|
|
30
|
+
isUnreliable: boolean,
|
|
31
|
+
kind: number,
|
|
32
|
+
signal: any,
|
|
33
|
+
rateLimit: RateLimitConfig?,
|
|
34
|
+
validate: ((data: any, player: Player) -> (boolean, string?))?,
|
|
35
|
+
maxPayloadBytes: number?
|
|
36
|
+
): Registration
|
|
37
|
+
if _names[name] then
|
|
38
|
+
error (`[Lync] Duplicate registration: "{name}"`)
|
|
39
|
+
end
|
|
40
|
+
if _nextId > MAX_PACKETS then
|
|
41
|
+
error (`[Lync] Registration limit reached: {MAX_PACKETS} max`)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
if rateLimit then
|
|
45
|
+
if rateLimit.maxPerSecond <= 0 then
|
|
46
|
+
error (`[Lync] maxPerSecond must be positive: "{name}"`)
|
|
47
|
+
end
|
|
48
|
+
if rateLimit.burstAllowance and rateLimit.burstAllowance <= 0 then
|
|
49
|
+
error (`[Lync] burstAllowance must be positive: "{name}"`)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
if maxPayloadBytes and maxPayloadBytes <= 0 then
|
|
54
|
+
error (`[Lync] maxPayloadBytes must be positive: "{name}"`)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
local id = _nextId
|
|
58
|
+
_nextId += 1
|
|
59
|
+
|
|
60
|
+
local reg: Registration = {
|
|
61
|
+
id = id,
|
|
62
|
+
name = name,
|
|
63
|
+
baseName = baseName,
|
|
64
|
+
codec = codec,
|
|
65
|
+
isUnreliable = isUnreliable,
|
|
66
|
+
kind = kind,
|
|
67
|
+
partner = nil,
|
|
68
|
+
signal = signal,
|
|
69
|
+
rateLimit = rateLimit,
|
|
70
|
+
validate = validate,
|
|
71
|
+
needsGate = rateLimit ~= nil or validate ~= nil,
|
|
72
|
+
maxPayloadBytes = maxPayloadBytes,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
_entries[id] = reg
|
|
76
|
+
_names[name] = id
|
|
77
|
+
return reg
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
-- Public -----------------------------------------------------------------
|
|
81
|
+
|
|
82
|
+
local Registry = {
|
|
83
|
+
KIND_PACKET = KIND_PACKET,
|
|
84
|
+
KIND_REQUEST = KIND_REQUEST,
|
|
85
|
+
KIND_RESPONSE = KIND_RESPONSE,
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function Registry.register (
|
|
89
|
+
name: string,
|
|
90
|
+
codec: Codec<any>,
|
|
91
|
+
isUnreliable: boolean,
|
|
92
|
+
signal: any,
|
|
93
|
+
rateLimit: RateLimitConfig?,
|
|
94
|
+
validate: ((data: any, player: Player) -> (boolean, string?))?,
|
|
95
|
+
maxPayloadBytes: number?
|
|
96
|
+
): Registration
|
|
97
|
+
return assign (
|
|
98
|
+
name,
|
|
99
|
+
name,
|
|
100
|
+
codec,
|
|
101
|
+
isUnreliable,
|
|
102
|
+
KIND_PACKET,
|
|
103
|
+
signal,
|
|
104
|
+
rateLimit,
|
|
105
|
+
validate,
|
|
106
|
+
maxPayloadBytes
|
|
107
|
+
)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
function Registry.registerQueryPair (
|
|
111
|
+
name: string,
|
|
112
|
+
requestCodec: Codec<any>,
|
|
113
|
+
responseCodec: Codec<any>,
|
|
114
|
+
requestSignal: any,
|
|
115
|
+
responseSignal: any,
|
|
116
|
+
rateLimit: RateLimitConfig?,
|
|
117
|
+
validate: ((data: any, player: Player) -> (boolean, string?))?
|
|
118
|
+
): (Registration, Registration)
|
|
119
|
+
local req = assign (
|
|
120
|
+
`{name}:req`,
|
|
121
|
+
name,
|
|
122
|
+
requestCodec,
|
|
123
|
+
false,
|
|
124
|
+
KIND_REQUEST,
|
|
125
|
+
requestSignal,
|
|
126
|
+
rateLimit,
|
|
127
|
+
validate
|
|
128
|
+
)
|
|
129
|
+
local resp =
|
|
130
|
+
assign (`{name}:resp`, name, responseCodec, false, KIND_RESPONSE, responseSignal, nil, nil)
|
|
131
|
+
|
|
132
|
+
req.partner = resp.id
|
|
133
|
+
resp.partner = req.id
|
|
134
|
+
return req, resp
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
function Registry.get (id: number): Registration?
|
|
138
|
+
return _entries[id]
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
function Registry.getByName (name: string): Registration?
|
|
142
|
+
local id = _names[name]
|
|
143
|
+
return if id then _entries[id] else nil
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
return table.freeze (Registry)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--!optimize 2
|
|
3
|
+
-- Creates and exposes the network remote instances.
|
|
4
|
+
|
|
5
|
+
local ReplicatedStorage = game:GetService ("ReplicatedStorage")
|
|
6
|
+
local RunService = game:GetService ("RunService")
|
|
7
|
+
|
|
8
|
+
-- Constants --------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
local FOLDER = "_lync"
|
|
11
|
+
local RELIABLE = "R"
|
|
12
|
+
local UNRELIABLE = "U"
|
|
13
|
+
local TIMEOUT = 10
|
|
14
|
+
|
|
15
|
+
-- State ------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
local _reliable: RemoteEvent
|
|
18
|
+
local _unreliable: UnreliableRemoteEvent
|
|
19
|
+
|
|
20
|
+
-- Public -----------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
local Bridge = {}
|
|
23
|
+
|
|
24
|
+
function Bridge.setup (): ()
|
|
25
|
+
if RunService:IsServer () then
|
|
26
|
+
local folder = Instance.new ("Folder")
|
|
27
|
+
folder.Name = FOLDER
|
|
28
|
+
|
|
29
|
+
local reliable = Instance.new ("RemoteEvent")
|
|
30
|
+
reliable.Name = RELIABLE
|
|
31
|
+
reliable.Parent = folder
|
|
32
|
+
|
|
33
|
+
local unreliable = Instance.new ("UnreliableRemoteEvent")
|
|
34
|
+
unreliable.Name = UNRELIABLE
|
|
35
|
+
unreliable.Parent = folder
|
|
36
|
+
|
|
37
|
+
folder.Parent = ReplicatedStorage
|
|
38
|
+
|
|
39
|
+
_reliable = reliable
|
|
40
|
+
_unreliable = unreliable
|
|
41
|
+
else
|
|
42
|
+
local folder = ReplicatedStorage:WaitForChild (FOLDER, TIMEOUT)
|
|
43
|
+
if not folder then
|
|
44
|
+
error ("[Lync] Network folder not found, is the server started?")
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
local reliable = folder:WaitForChild (RELIABLE, TIMEOUT)
|
|
48
|
+
if not reliable then
|
|
49
|
+
error ("[Lync] Reliable remote not found")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
local unreliable = folder:WaitForChild (UNRELIABLE, TIMEOUT)
|
|
53
|
+
if not unreliable then
|
|
54
|
+
error ("[Lync] Unreliable remote not found")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
_reliable = reliable :: RemoteEvent
|
|
58
|
+
_unreliable = unreliable :: UnreliableRemoteEvent
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
Bridge.reliable = _reliable :: any
|
|
62
|
+
Bridge.unreliable = _unreliable :: any
|
|
63
|
+
table.freeze (Bridge)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
return Bridge
|