@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.
- package/README.md +241 -349
- package/package.json +1 -1
- package/src/Types.luau +53 -13
- package/src/api/Group.luau +41 -51
- package/src/api/Packet.luau +145 -143
- package/src/api/Query.luau +204 -202
- package/src/api/Scope.luau +36 -38
- package/src/api/Signal.luau +68 -94
- package/src/codec/Base.luau +43 -23
- package/src/codec/composite/Array.luau +161 -152
- package/src/codec/composite/Map.luau +37 -53
- package/src/codec/composite/Optional.luau +15 -21
- package/src/codec/composite/Shared.luau +78 -41
- package/src/codec/composite/Struct.luau +64 -125
- package/src/codec/composite/Tagged.luau +15 -25
- package/src/codec/composite/Tuple.luau +15 -20
- package/src/codec/datatype/Buffer.luau +44 -51
- package/src/codec/datatype/CFrame.luau +72 -104
- package/src/codec/datatype/Color.luau +14 -10
- package/src/codec/datatype/Instance.luau +32 -42
- package/src/codec/datatype/IntVector.luau +24 -22
- package/src/codec/datatype/NumberRange.luau +21 -12
- package/src/codec/datatype/Ray.luau +13 -7
- package/src/codec/datatype/Rect.luau +13 -7
- package/src/codec/datatype/Region.luau +25 -22
- package/src/codec/datatype/Sequence.luau +144 -118
- package/src/codec/datatype/String.luau +29 -59
- package/src/codec/datatype/UDim.luau +29 -30
- package/src/codec/datatype/Vector.luau +89 -105
- package/src/codec/meta/Auto.luau +194 -246
- package/src/codec/meta/Bitfield.luau +37 -36
- package/src/codec/meta/Custom.luau +10 -10
- package/src/codec/meta/Enum.luau +8 -11
- package/src/codec/meta/Float.luau +16 -20
- package/src/codec/meta/Nothing.luau +2 -2
- package/src/codec/meta/Unknown.luau +22 -32
- package/src/codec/primitive/Bool.luau +9 -5
- package/src/codec/primitive/Float16.luau +13 -23
- package/src/codec/primitive/Int.luau +20 -28
- package/src/codec/primitive/Number.luau +6 -10
- package/src/codec/primitive/Signed.luau +32 -0
- package/src/codec/primitive/Varint.luau +59 -28
- package/src/index.d.ts +8 -2
- package/src/init.luau +118 -143
- package/src/internal/Baseline.luau +17 -18
- package/src/internal/Channel.luau +143 -212
- package/src/internal/Middleware.luau +37 -47
- package/src/internal/Pool.luau +10 -10
- package/src/internal/Registry.luau +38 -34
- package/src/internal/Transport.luau +28 -0
- package/src/internal/Util.luau +26 -0
- package/src/transport/Bridge.luau +32 -24
- package/src/transport/Client.luau +51 -53
- package/src/transport/Gate.luau +183 -113
- package/src/transport/Reader.luau +95 -66
- package/src/transport/Server.luau +130 -125
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
--!strict
|
|
2
2
|
--!native
|
|
3
|
-
-- Per-channel buffer state with MSB batch framing, XOR, timestamps.
|
|
3
|
+
-- Per-channel buffer state with MSB batch framing, XOR delta, timestamps.
|
|
4
4
|
|
|
5
5
|
local Types = require(script.Parent.Parent.Types)
|
|
6
|
+
local Varint = require(script.Parent.Parent.codec.primitive.Varint)
|
|
6
7
|
|
|
7
|
-
-- Constants
|
|
8
|
+
-- Constants --------------------------------------------------------------
|
|
8
9
|
|
|
9
10
|
local TS_NONE = 0
|
|
10
11
|
local TS_FRAME = 1
|
|
@@ -12,9 +13,11 @@ local TS_OFFSET = 2
|
|
|
12
13
|
local TS_FULL = 3
|
|
13
14
|
|
|
14
15
|
local DEFAULT_MAX = 262144
|
|
15
|
-
local
|
|
16
|
+
local INITIAL_BUF = 1024
|
|
17
|
+
local FRAME_MASK = 0xFF
|
|
18
|
+
local OFFSET_WIN = 65.536
|
|
16
19
|
|
|
17
|
-
-- State
|
|
20
|
+
-- State ------------------------------------------------------------------
|
|
18
21
|
|
|
19
22
|
local _maxSize = DEFAULT_MAX
|
|
20
23
|
local _anyTimestamp = false
|
|
@@ -23,20 +26,23 @@ local _frameCounter = 0
|
|
|
23
26
|
local _offsetMs = 0
|
|
24
27
|
local _clock = 0.0
|
|
25
28
|
|
|
26
|
-
|
|
29
|
+
local EMPTY_REFS = table.freeze({}) :: { Instance }
|
|
30
|
+
|
|
31
|
+
-- Private ----------------------------------------------------------------
|
|
27
32
|
|
|
28
33
|
local countlz = bit32.countlz
|
|
29
34
|
local lshift = bit32.lshift
|
|
35
|
+
local band = bit32.band
|
|
30
36
|
local bxor = bit32.bxor
|
|
31
37
|
local bor = bit32.bor
|
|
32
|
-
local band = bit32.band
|
|
33
38
|
local writeu8 = buffer.writeu8
|
|
34
39
|
local writeu16 = buffer.writeu16
|
|
40
|
+
local writeu32 = buffer.writeu32
|
|
35
41
|
local writef64 = buffer.writef64
|
|
36
42
|
local readu8 = buffer.readu8
|
|
37
43
|
local readu32 = buffer.readu32
|
|
38
44
|
local round = math.round
|
|
39
|
-
local
|
|
45
|
+
local minN = math.min
|
|
40
46
|
|
|
41
47
|
local function alloc(ch: Types.ChannelState, bytes: number): ()
|
|
42
48
|
local needed = ch.cursor + bytes
|
|
@@ -44,11 +50,11 @@ local function alloc(ch: Types.ChannelState, bytes: number): ()
|
|
|
44
50
|
return
|
|
45
51
|
end
|
|
46
52
|
if needed > _maxSize then
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
)
|
|
53
|
+
local name = ch.currentPacket
|
|
54
|
+
if name then
|
|
55
|
+
error(`[Lync] Channel.alloc("{name}"): buffer overflow ({needed}B, max {_maxSize}B)`)
|
|
56
|
+
end
|
|
57
|
+
error(`[Lync] Channel.alloc: buffer overflow ({needed}B, max {_maxSize}B)`)
|
|
52
58
|
end
|
|
53
59
|
local newSize = lshift(1, 32 - countlz(needed - 1))
|
|
54
60
|
local newBuff = buffer.create(newSize)
|
|
@@ -57,7 +63,30 @@ local function alloc(ch: Types.ChannelState, bytes: number): ()
|
|
|
57
63
|
ch.size = newSize
|
|
58
64
|
end
|
|
59
65
|
|
|
60
|
-
|
|
66
|
+
local function tsBytes(mode: number): number
|
|
67
|
+
if mode == TS_NONE then
|
|
68
|
+
return 0
|
|
69
|
+
end
|
|
70
|
+
if mode == TS_FRAME then
|
|
71
|
+
return 1
|
|
72
|
+
end
|
|
73
|
+
if mode == TS_OFFSET then
|
|
74
|
+
return 2
|
|
75
|
+
end
|
|
76
|
+
return 8
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
local function writeTimestamp(b: buffer, off: number, mode: number): ()
|
|
80
|
+
if mode == TS_FRAME then
|
|
81
|
+
writeu8(b, off, _frameCounter)
|
|
82
|
+
elseif mode == TS_OFFSET then
|
|
83
|
+
writeu16(b, off, _offsetMs)
|
|
84
|
+
elseif mode == TS_FULL then
|
|
85
|
+
writef64(b, off, _clock)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
-- Public -----------------------------------------------------------------
|
|
61
90
|
|
|
62
91
|
local Channel = {}
|
|
63
92
|
|
|
@@ -68,9 +97,9 @@ Channel.TS_FULL = TS_FULL
|
|
|
68
97
|
|
|
69
98
|
function Channel.create(): Types.ChannelState
|
|
70
99
|
return {
|
|
71
|
-
buff = buffer.create(
|
|
100
|
+
buff = buffer.create(INITIAL_BUF),
|
|
72
101
|
cursor = 0,
|
|
73
|
-
size =
|
|
102
|
+
size = INITIAL_BUF,
|
|
74
103
|
refs = {},
|
|
75
104
|
refCount = 0,
|
|
76
105
|
lastId = -1,
|
|
@@ -86,7 +115,45 @@ function Channel.create(): Types.ChannelState
|
|
|
86
115
|
end
|
|
87
116
|
|
|
88
117
|
function Channel.alloc(ch: Types.ChannelState, bytes: number): ()
|
|
89
|
-
|
|
118
|
+
if bytes > 0 then
|
|
119
|
+
alloc(ch, bytes)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
--[[
|
|
124
|
+
Write a single byte at the cursor and advance. Centralizes the
|
|
125
|
+
ubiquitous `if cursor + 1 > size then alloc(ch, 1) end; writeu8(...);
|
|
126
|
+
cursor += 1` pattern.
|
|
127
|
+
]]
|
|
128
|
+
function Channel.writeByte(ch: Types.ChannelState, byte: number): ()
|
|
129
|
+
local cursor = ch.cursor
|
|
130
|
+
if cursor + 1 > ch.size then
|
|
131
|
+
alloc(ch, 1)
|
|
132
|
+
end
|
|
133
|
+
writeu8(ch.buff, cursor, byte)
|
|
134
|
+
ch.cursor = cursor + 1
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
--[[
|
|
138
|
+
Push `value` into the channel's sidecar refs array, write its
|
|
139
|
+
1-based index as a u16 to the buffer, advance cursor. Returns the
|
|
140
|
+
index. Used by the Instance and Unknown codecs.
|
|
141
|
+
]]
|
|
142
|
+
function Channel.pushRef(ch: Types.ChannelState, value: any): number
|
|
143
|
+
local idx = ch.refCount + 1
|
|
144
|
+
if idx > 0xFFFF then
|
|
145
|
+
error("[Lync] Channel.pushRef: ref overflow (>65535)")
|
|
146
|
+
end
|
|
147
|
+
ch.refs[idx] = value
|
|
148
|
+
ch.refCount = idx
|
|
149
|
+
|
|
150
|
+
local cursor = ch.cursor
|
|
151
|
+
if cursor + 2 > ch.size then
|
|
152
|
+
alloc(ch, 2)
|
|
153
|
+
end
|
|
154
|
+
writeu16(ch.buff, cursor, idx)
|
|
155
|
+
ch.cursor = cursor + 2
|
|
156
|
+
return idx
|
|
90
157
|
end
|
|
91
158
|
|
|
92
159
|
function Channel.reset(ch: Types.ChannelState): ()
|
|
@@ -104,121 +171,56 @@ function Channel.reset(ch: Types.ChannelState): ()
|
|
|
104
171
|
end
|
|
105
172
|
|
|
106
173
|
--[[
|
|
107
|
-
|
|
108
|
-
The returned function
|
|
109
|
-
|
|
110
|
-
|
|
174
|
+
Resolve the batch-open closure for a given timestamp mode and packet
|
|
175
|
+
name. The returned function:
|
|
176
|
+
- seals the previous batch (writing the deferred multi-mode count),
|
|
177
|
+
- writes the single-mode header [0x80 | id] + optional timestamp,
|
|
178
|
+
- records cursor position so writeBatch can upgrade single->multi later.
|
|
111
179
|
]]
|
|
112
180
|
function Channel.resolveOpenFn(
|
|
113
181
|
timestampMode: number,
|
|
114
182
|
name: string
|
|
115
183
|
): (ch: Types.ChannelState, id: number) -> ()
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
end
|
|
122
|
-
ch.currentPacket = name
|
|
123
|
-
ch.lastId = id
|
|
124
|
-
ch.itemCount = 0
|
|
125
|
-
|
|
126
|
-
-- Write single-mode header: [1IIIIIII]
|
|
127
|
-
local cursor = ch.cursor
|
|
128
|
-
if cursor + 1 > ch.size then
|
|
129
|
-
alloc(ch, 1)
|
|
130
|
-
end
|
|
131
|
-
writeu8(ch.buff, cursor, bor(0x80, id))
|
|
132
|
-
ch.singleMode = true
|
|
133
|
-
ch.singlePos = cursor
|
|
134
|
-
ch.cursor = cursor + 1
|
|
135
|
-
end
|
|
136
|
-
elseif timestampMode == TS_FRAME then
|
|
137
|
-
return function(ch: Types.ChannelState, id: number): ()
|
|
138
|
-
if ch.lastId >= 0 and not ch.singleMode then
|
|
139
|
-
writeu16(ch.buff, ch.countPos, ch.itemCount)
|
|
140
|
-
end
|
|
141
|
-
ch.currentPacket = name
|
|
142
|
-
ch.lastId = id
|
|
143
|
-
ch.itemCount = 0
|
|
144
|
-
|
|
145
|
-
local cursor = ch.cursor
|
|
146
|
-
if cursor + 2 > ch.size then
|
|
147
|
-
alloc(ch, 2)
|
|
148
|
-
end
|
|
149
|
-
local b = ch.buff
|
|
150
|
-
writeu8(b, cursor, bor(0x80, id))
|
|
151
|
-
writeu8(b, cursor + 1, _frameCounter)
|
|
152
|
-
ch.singleMode = true
|
|
153
|
-
ch.singlePos = cursor
|
|
154
|
-
ch.cursor = cursor + 2
|
|
155
|
-
end
|
|
156
|
-
elseif timestampMode == TS_OFFSET then
|
|
157
|
-
return function(ch: Types.ChannelState, id: number): ()
|
|
158
|
-
if ch.lastId >= 0 and not ch.singleMode then
|
|
159
|
-
writeu16(ch.buff, ch.countPos, ch.itemCount)
|
|
160
|
-
end
|
|
161
|
-
ch.currentPacket = name
|
|
162
|
-
ch.lastId = id
|
|
163
|
-
ch.itemCount = 0
|
|
164
|
-
|
|
165
|
-
local cursor = ch.cursor
|
|
166
|
-
if cursor + 3 > ch.size then
|
|
167
|
-
alloc(ch, 3)
|
|
168
|
-
end
|
|
169
|
-
local b = ch.buff
|
|
170
|
-
writeu8(b, cursor, bor(0x80, id))
|
|
171
|
-
writeu16(b, cursor + 1, _offsetMs)
|
|
172
|
-
ch.singleMode = true
|
|
173
|
-
ch.singlePos = cursor
|
|
174
|
-
ch.cursor = cursor + 3
|
|
184
|
+
local headerLen = 1 + tsBytes(timestampMode)
|
|
185
|
+
|
|
186
|
+
return function(ch: Types.ChannelState, id: number): ()
|
|
187
|
+
if ch.lastId >= 0 and not ch.singleMode then
|
|
188
|
+
writeu16(ch.buff, ch.countPos, ch.itemCount)
|
|
175
189
|
end
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
ch
|
|
184
|
-
|
|
185
|
-
local cursor = ch.cursor
|
|
186
|
-
if cursor + 9 > ch.size then
|
|
187
|
-
alloc(ch, 9)
|
|
188
|
-
end
|
|
189
|
-
local b = ch.buff
|
|
190
|
-
writeu8(b, cursor, bor(0x80, id))
|
|
191
|
-
writef64(b, cursor + 1, _clock)
|
|
192
|
-
ch.singleMode = true
|
|
193
|
-
ch.singlePos = cursor
|
|
194
|
-
ch.cursor = cursor + 9
|
|
190
|
+
|
|
191
|
+
ch.currentPacket = name
|
|
192
|
+
ch.lastId = id
|
|
193
|
+
ch.itemCount = 0
|
|
194
|
+
|
|
195
|
+
local cursor = ch.cursor
|
|
196
|
+
if cursor + headerLen > ch.size then
|
|
197
|
+
alloc(ch, headerLen)
|
|
195
198
|
end
|
|
199
|
+
local b = ch.buff
|
|
200
|
+
writeu8(b, cursor, bor(0x80, id))
|
|
201
|
+
writeTimestamp(b, cursor + 1, timestampMode)
|
|
202
|
+
|
|
203
|
+
ch.singleMode = true
|
|
204
|
+
ch.singlePos = cursor
|
|
205
|
+
ch.cursor = cursor + headerLen
|
|
196
206
|
end
|
|
197
207
|
end
|
|
198
208
|
|
|
199
209
|
--[[
|
|
200
|
-
|
|
210
|
+
Write one item into the current batch. Handles single->multi mode
|
|
211
|
+
upgrade when a second item lands on the same packet ID: clears the
|
|
212
|
+
header byte's MSB, then memmoves payload right by 2 to insert the
|
|
213
|
+
deferred u16 count between the timestamp and first payload.
|
|
201
214
|
]]
|
|
202
215
|
function Channel.writeBatch(ch: Types.ChannelState, reg: Types.Registration, data: any): ()
|
|
203
216
|
if reg.id ~= ch.lastId or ch.itemCount >= 0xFFFF then
|
|
204
217
|
reg._openFn(ch, reg.id)
|
|
205
218
|
elseif ch.singleMode then
|
|
206
|
-
--[[
|
|
207
|
-
Second item for same packet: upgrade to multi mode.
|
|
208
|
-
Rewrite header byte to clear MSB, insert u16 count after.
|
|
209
|
-
]]
|
|
210
|
-
local b = ch.buff
|
|
211
219
|
local headerPos = ch.singlePos
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
local tsBytes = reg.headerSize - 1
|
|
215
|
-
local afterTs = headerPos + 1 + tsBytes
|
|
220
|
+
local b = ch.buff
|
|
221
|
+
writeu8(b, headerPos, reg.id)
|
|
216
222
|
|
|
217
|
-
|
|
218
|
-
Upgrading from single→multi requires inserting 2 bytes (u16 count)
|
|
219
|
-
between the timestamp and the first item's payload, shifting
|
|
220
|
-
existing data right by 2.
|
|
221
|
-
]]
|
|
223
|
+
local afterTs = headerPos + reg.headerSize
|
|
222
224
|
local cursor = ch.cursor
|
|
223
225
|
local payloadLen = cursor - afterTs
|
|
224
226
|
if cursor + 2 > ch.size then
|
|
@@ -231,7 +233,7 @@ function Channel.writeBatch(ch: Types.ChannelState, reg: Types.Registration, dat
|
|
|
231
233
|
end
|
|
232
234
|
|
|
233
235
|
ch.countPos = afterTs
|
|
234
|
-
writeu16(b2, afterTs, 0)
|
|
236
|
+
writeu16(b2, afterTs, 0)
|
|
235
237
|
ch.cursor = cursor + 2
|
|
236
238
|
ch.singleMode = false
|
|
237
239
|
end
|
|
@@ -240,32 +242,25 @@ function Channel.writeBatch(ch: Types.ChannelState, reg: Types.Registration, dat
|
|
|
240
242
|
reg.codec.write(ch, data)
|
|
241
243
|
end
|
|
242
244
|
|
|
243
|
-
--[[
|
|
244
|
-
Seals the current batch and returns a snapshot for transmission.
|
|
245
|
-
]]
|
|
246
245
|
function Channel.sealAndDump(ch: Types.ChannelState): (buffer, { Instance }, number, number)
|
|
247
|
-
-- Multi-item batches need their count finalized before transmission
|
|
248
246
|
if ch.lastId >= 0 and not ch.singleMode and ch.countPos >= 0 then
|
|
249
247
|
writeu16(ch.buff, ch.countPos, ch.itemCount)
|
|
250
248
|
end
|
|
251
249
|
|
|
252
250
|
local cursor = ch.cursor
|
|
253
251
|
local snapshot = buffer.create(cursor)
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
local refs: { Instance }
|
|
257
|
-
local refCount = ch.refCount
|
|
258
|
-
if refCount > 0 then
|
|
259
|
-
refs = table.clone(ch.refs)
|
|
260
|
-
else
|
|
261
|
-
refs = EMPTY_REFS :: any
|
|
252
|
+
if cursor > 0 then
|
|
253
|
+
buffer.copy(snapshot, 0, ch.buff, 0, cursor)
|
|
262
254
|
end
|
|
263
255
|
|
|
256
|
+
local refCount = ch.refCount
|
|
257
|
+
local refs = if refCount > 0 then table.clone(ch.refs) else EMPTY_REFS
|
|
264
258
|
return snapshot, refs, cursor, refCount
|
|
265
259
|
end
|
|
266
260
|
|
|
267
261
|
--[[
|
|
268
|
-
XOR current
|
|
262
|
+
XOR `current` against `previous`, returning a fresh buffer of curLen
|
|
263
|
+
bytes. When `previous` is nil, returns `current` unchanged.
|
|
269
264
|
]]
|
|
270
265
|
function Channel.xorApply(
|
|
271
266
|
current: buffer,
|
|
@@ -278,34 +273,28 @@ function Channel.xorApply(
|
|
|
278
273
|
end
|
|
279
274
|
|
|
280
275
|
local result = buffer.create(curLen)
|
|
281
|
-
local overlap =
|
|
282
|
-
local prev = previous
|
|
276
|
+
local overlap = minN(curLen, prevLen)
|
|
277
|
+
local prev = previous
|
|
283
278
|
|
|
284
|
-
|
|
285
|
-
local aligned = overlap - (overlap % 4)
|
|
279
|
+
local aligned = band(overlap, -4)
|
|
286
280
|
local i = 0
|
|
287
281
|
while i < aligned do
|
|
288
|
-
|
|
282
|
+
writeu32(result, i, bxor(readu32(current, i), readu32(prev, i)))
|
|
289
283
|
i += 4
|
|
290
284
|
end
|
|
291
|
-
|
|
292
|
-
-- u8 remainder of overlap
|
|
293
285
|
while i < overlap do
|
|
294
286
|
writeu8(result, i, bxor(readu8(current, i), readu8(prev, i)))
|
|
295
287
|
i += 1
|
|
296
288
|
end
|
|
297
|
-
|
|
298
|
-
-- Excess bytes (current longer than previous)
|
|
299
289
|
if curLen > overlap then
|
|
300
290
|
buffer.copy(result, overlap, current, overlap, curLen - overlap)
|
|
301
291
|
end
|
|
302
|
-
|
|
303
292
|
return result
|
|
304
293
|
end
|
|
305
294
|
|
|
306
295
|
--[[
|
|
307
|
-
|
|
308
|
-
|
|
296
|
+
Write a query frame. MSB on the id byte signals nil response;
|
|
297
|
+
cleared means a payload follows. Correlation IDs are varint-encoded.
|
|
309
298
|
]]
|
|
310
299
|
function Channel.writeQuery(
|
|
311
300
|
ch: Types.ChannelState,
|
|
@@ -314,95 +303,37 @@ function Channel.writeQuery(
|
|
|
314
303
|
codec: Types.InternalCodec<any>?,
|
|
315
304
|
data: any
|
|
316
305
|
): ()
|
|
317
|
-
-- Queries are standalone frames, so close any open packet batch first
|
|
318
306
|
if ch.lastId >= 0 and not ch.singleMode and ch.countPos >= 0 then
|
|
319
307
|
writeu16(ch.buff, ch.countPos, ch.itemCount)
|
|
320
308
|
end
|
|
321
309
|
ch.lastId = -1
|
|
322
310
|
ch.singleMode = false
|
|
323
311
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
if cursor + 1 > ch.size then
|
|
327
|
-
alloc(ch, 1)
|
|
328
|
-
end
|
|
329
|
-
writeu8(ch.buff, cursor, id)
|
|
330
|
-
ch.cursor = cursor + 1
|
|
331
|
-
|
|
332
|
-
-- Inlined varint avoids the function call overhead for correlation IDs
|
|
333
|
-
if correlationId <= 0xBF then
|
|
334
|
-
local c2 = ch.cursor
|
|
335
|
-
if c2 + 1 > ch.size then
|
|
336
|
-
alloc(ch, 1)
|
|
337
|
-
end
|
|
338
|
-
writeu8(ch.buff, c2, correlationId)
|
|
339
|
-
ch.cursor = c2 + 1
|
|
340
|
-
elseif correlationId <= 8383 then
|
|
341
|
-
local c2 = ch.cursor
|
|
342
|
-
if c2 + 2 > ch.size then
|
|
343
|
-
alloc(ch, 2)
|
|
344
|
-
end
|
|
345
|
-
local adjusted = correlationId - 192
|
|
346
|
-
writeu8(ch.buff, c2, bor(0xC0, band(adjusted // 256, 0x1F)))
|
|
347
|
-
writeu8(ch.buff, c2 + 1, band(adjusted, 0xFF))
|
|
348
|
-
ch.cursor = c2 + 2
|
|
349
|
-
else
|
|
350
|
-
local c2 = ch.cursor
|
|
351
|
-
if c2 + 5 > ch.size then
|
|
352
|
-
alloc(ch, 5)
|
|
353
|
-
end
|
|
354
|
-
writeu8(ch.buff, c2, 0xF0)
|
|
355
|
-
buffer.writeu32(ch.buff, c2 + 1, correlationId)
|
|
356
|
-
ch.cursor = c2 + 5
|
|
357
|
-
end
|
|
312
|
+
local hasPayload = codec ~= nil and data ~= nil
|
|
313
|
+
local headerByte = if hasPayload then id else bor(0x80, id)
|
|
358
314
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
if correlationId <= 0xBF then
|
|
370
|
-
local c2 = ch.cursor
|
|
371
|
-
if c2 + 1 > ch.size then
|
|
372
|
-
alloc(ch, 1)
|
|
373
|
-
end
|
|
374
|
-
writeu8(ch.buff, c2, correlationId)
|
|
375
|
-
ch.cursor = c2 + 1
|
|
376
|
-
elseif correlationId <= 8383 then
|
|
377
|
-
local c2 = ch.cursor
|
|
378
|
-
if c2 + 2 > ch.size then
|
|
379
|
-
alloc(ch, 2)
|
|
380
|
-
end
|
|
381
|
-
local adjusted = correlationId - 192
|
|
382
|
-
writeu8(ch.buff, c2, bor(0xC0, band(adjusted // 256, 0x1F)))
|
|
383
|
-
writeu8(ch.buff, c2 + 1, band(adjusted, 0xFF))
|
|
384
|
-
ch.cursor = c2 + 2
|
|
385
|
-
else
|
|
386
|
-
local c2 = ch.cursor
|
|
387
|
-
if c2 + 5 > ch.size then
|
|
388
|
-
alloc(ch, 5)
|
|
389
|
-
end
|
|
390
|
-
writeu8(ch.buff, c2, 0xF0)
|
|
391
|
-
buffer.writeu32(ch.buff, c2 + 1, correlationId)
|
|
392
|
-
ch.cursor = c2 + 5
|
|
393
|
-
end
|
|
315
|
+
local cursor = ch.cursor
|
|
316
|
+
if cursor + 1 > ch.size then
|
|
317
|
+
alloc(ch, 1)
|
|
318
|
+
end
|
|
319
|
+
writeu8(ch.buff, cursor, headerByte)
|
|
320
|
+
ch.cursor = cursor + 1
|
|
321
|
+
|
|
322
|
+
Varint.write(ch, correlationId)
|
|
323
|
+
if hasPayload then
|
|
324
|
+
(codec :: Types.InternalCodec<any>).write(ch, data)
|
|
394
325
|
end
|
|
395
326
|
end
|
|
396
327
|
|
|
397
|
-
-- Configuration
|
|
328
|
+
-- Configuration ----------------------------------------------------------
|
|
398
329
|
|
|
399
330
|
function Channel.setMaxSize(bytes: number): ()
|
|
400
331
|
_maxSize = bytes
|
|
401
332
|
end
|
|
402
333
|
|
|
403
334
|
function Channel.updateTimestamp(): ()
|
|
404
|
-
_frameCounter = (_frameCounter + 1)
|
|
405
|
-
_offsetMs = round((os.clock() %
|
|
335
|
+
_frameCounter = band(_frameCounter + 1, FRAME_MASK)
|
|
336
|
+
_offsetMs = round((os.clock() % OFFSET_WIN) * 1000)
|
|
406
337
|
_clock = os.clock()
|
|
407
338
|
end
|
|
408
339
|
|
|
@@ -1,17 +1,47 @@
|
|
|
1
1
|
--!strict
|
|
2
2
|
--!optimize 2
|
|
3
|
-
-- Ordered intercept chains
|
|
3
|
+
-- Ordered intercept chains for send/receive/drop hooks.
|
|
4
4
|
|
|
5
5
|
local Signal = require(script.Parent.Parent.api.Signal)
|
|
6
6
|
local Types = require(script.Parent.Parent.Types)
|
|
7
7
|
|
|
8
|
-
-- State
|
|
8
|
+
-- State ------------------------------------------------------------------
|
|
9
9
|
|
|
10
10
|
local _sendSignal = Signal.create()
|
|
11
11
|
local _receiveSignal = Signal.create()
|
|
12
12
|
local _dropSignal = Signal.create()
|
|
13
13
|
|
|
14
|
-
--
|
|
14
|
+
-- Private ----------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
--[[
|
|
17
|
+
Walk a transform chain in order. Each callback returns a transformed
|
|
18
|
+
value or nil to keep the previous value. A fresh snapshot guards
|
|
19
|
+
against disconnect-during-fire reentry.
|
|
20
|
+
]]
|
|
21
|
+
local function runChain(signal: any, value: any, name: string, player: Player?): any
|
|
22
|
+
local count = signal._count[1]
|
|
23
|
+
if count == 0 then
|
|
24
|
+
return value
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
local entries = signal._entries
|
|
28
|
+
local snapshot = table.create(count)
|
|
29
|
+
table.move(entries, 1, count, 1, snapshot)
|
|
30
|
+
|
|
31
|
+
local result = value
|
|
32
|
+
for i = 1, count do
|
|
33
|
+
local entry = snapshot[i]
|
|
34
|
+
if entry._state ~= 0 then
|
|
35
|
+
local returned = entry.fn(result, name, player)
|
|
36
|
+
if returned ~= nil then
|
|
37
|
+
result = returned
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
return result
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
-- Public -----------------------------------------------------------------
|
|
15
45
|
|
|
16
46
|
local Middleware = {}
|
|
17
47
|
|
|
@@ -31,52 +61,12 @@ function Middleware.onDrop(
|
|
|
31
61
|
return _dropSignal:connect(fn)
|
|
32
62
|
end
|
|
33
63
|
|
|
34
|
-
--[[
|
|
35
|
-
Runs send middleware chain. Returns transformed data, or DROP sentinel.
|
|
36
|
-
If no hooks, returns data unchanged (zero overhead).
|
|
37
|
-
]]
|
|
38
64
|
function Middleware.runSend(data: any, name: string, player: Player?): any
|
|
39
|
-
|
|
40
|
-
if count == 0 then
|
|
41
|
-
return data
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
local entries = _sendSignal._entries
|
|
45
|
-
local result = data
|
|
46
|
-
|
|
47
|
-
for i = 1, count do
|
|
48
|
-
local entry = entries[i]
|
|
49
|
-
if entry._state ~= 0 then -- not disconnected
|
|
50
|
-
local returned = entry.fn(result, name, player)
|
|
51
|
-
if returned ~= nil then
|
|
52
|
-
result = returned
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
return result
|
|
65
|
+
return runChain(_sendSignal, data, name, player)
|
|
58
66
|
end
|
|
59
67
|
|
|
60
68
|
function Middleware.runReceive(data: any, name: string, player: Player?): any
|
|
61
|
-
|
|
62
|
-
if count == 0 then
|
|
63
|
-
return data
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
local entries = _receiveSignal._entries
|
|
67
|
-
local result = data
|
|
68
|
-
|
|
69
|
-
for i = 1, count do
|
|
70
|
-
local entry = entries[i]
|
|
71
|
-
if entry._state ~= 0 then
|
|
72
|
-
local returned = entry.fn(result, name, player)
|
|
73
|
-
if returned ~= nil then
|
|
74
|
-
result = returned
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
return result
|
|
69
|
+
return runChain(_receiveSignal, data, name, player)
|
|
80
70
|
end
|
|
81
71
|
|
|
82
72
|
function Middleware.fireDrop(player: Player, reason: string, name: string, data: any?): ()
|
|
@@ -84,11 +74,11 @@ function Middleware.fireDrop(player: Player, reason: string, name: string, data:
|
|
|
84
74
|
end
|
|
85
75
|
|
|
86
76
|
function Middleware.hasSendHooks(): boolean
|
|
87
|
-
return _sendSignal
|
|
77
|
+
return _sendSignal:hasListeners()
|
|
88
78
|
end
|
|
89
79
|
|
|
90
80
|
function Middleware.hasReceiveHooks(): boolean
|
|
91
|
-
return _receiveSignal
|
|
81
|
+
return _receiveSignal:hasListeners()
|
|
92
82
|
end
|
|
93
83
|
|
|
94
84
|
return table.freeze(Middleware)
|
package/src/internal/Pool.luau
CHANGED
|
@@ -1,33 +1,33 @@
|
|
|
1
1
|
--!strict
|
|
2
2
|
--!optimize 2
|
|
3
|
-
-- Stack-based ChannelState pool
|
|
3
|
+
-- Stack-based ChannelState pool. Reset-on-acquire keeps released
|
|
4
|
+
-- channels reusable without per-release reset cost.
|
|
4
5
|
|
|
5
6
|
local Channel = require(script.Parent.Channel)
|
|
6
7
|
local Types = require(script.Parent.Parent.Types)
|
|
7
8
|
|
|
8
|
-
-- Constants
|
|
9
|
+
-- Constants --------------------------------------------------------------
|
|
9
10
|
|
|
10
11
|
local DEFAULT_SIZE = 16
|
|
11
12
|
|
|
12
|
-
-- State
|
|
13
|
+
-- State ------------------------------------------------------------------
|
|
13
14
|
|
|
14
15
|
local _stack: { Types.ChannelState } = {}
|
|
15
16
|
local _depth = 0
|
|
16
17
|
local _maxSize = DEFAULT_SIZE
|
|
17
18
|
|
|
18
|
-
-- Public
|
|
19
|
+
-- Public -----------------------------------------------------------------
|
|
19
20
|
|
|
20
21
|
local Pool = {}
|
|
21
22
|
|
|
22
23
|
function Pool.acquire(): Types.ChannelState
|
|
23
24
|
if _depth > 0 then
|
|
24
25
|
local ch = _stack[_depth]
|
|
25
|
-
_stack[_depth] = nil
|
|
26
|
+
_stack[_depth] = nil
|
|
26
27
|
_depth -= 1
|
|
27
28
|
Channel.reset(ch)
|
|
28
29
|
return ch
|
|
29
30
|
end
|
|
30
|
-
|
|
31
31
|
return Channel.create()
|
|
32
32
|
end
|
|
33
33
|
|
|
@@ -35,17 +35,17 @@ function Pool.release(ch: Types.ChannelState): ()
|
|
|
35
35
|
if _depth >= _maxSize then
|
|
36
36
|
return
|
|
37
37
|
end
|
|
38
|
-
|
|
39
38
|
_depth += 1
|
|
40
39
|
_stack[_depth] = ch
|
|
41
40
|
end
|
|
42
41
|
|
|
43
42
|
function Pool.setMaxSize(size: number): ()
|
|
43
|
+
if size < 0 then
|
|
44
|
+
error(`[Lync] Pool.setMaxSize: size must be >= 0, got {size}`)
|
|
45
|
+
end
|
|
44
46
|
_maxSize = size
|
|
45
|
-
|
|
46
|
-
-- Trim excess
|
|
47
47
|
while _depth > _maxSize do
|
|
48
|
-
_stack[_depth] = nil
|
|
48
|
+
_stack[_depth] = nil
|
|
49
49
|
_depth -= 1
|
|
50
50
|
end
|
|
51
51
|
end
|