@axpecter/lync 2.3.2 → 3.0.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/LICENSE +21 -0
- package/README.md +302 -299
- package/Types.luau +215 -0
- package/api/Define.luau +154 -0
- package/api/Lifecycle.luau +119 -0
- package/api/Packet.luau +108 -0
- package/api/Query.luau +149 -0
- package/api/Set.luau +189 -0
- package/channel/Blocks.luau +124 -0
- package/channel/Group.luau +118 -0
- package/channel/Link.luau +135 -0
- package/channel/Outbox.luau +261 -0
- package/channel/Recipients.luau +129 -0
- package/channel/Transport.luau +138 -0
- package/codec/Base.luau +236 -0
- package/codec/Composites.luau +128 -0
- package/codec/Datatypes.luau +162 -0
- package/codec/Legality.luau +61 -0
- package/codec/Scalars.luau +72 -0
- package/codec/Text.luau +89 -0
- package/codec/init.luau +39 -0
- package/index.d.ts +285 -0
- package/init.luau +91 -0
- package/kernel/Bits.luau +16 -0
- package/kernel/Catalog.luau +121 -0
- package/kernel/Heap.luau +80 -0
- package/kernel/Listeners.luau +95 -0
- package/kernel/Log.luau +197 -0
- package/kernel/Tally.luau +128 -0
- package/kernel/Threads.luau +44 -0
- package/kernel/Wide.luau +267 -0
- package/package.json +15 -32
- package/runtime/Boundary.luau +180 -0
- package/runtime/Dispatch.luau +91 -0
- package/runtime/Handles.luau +114 -0
- package/runtime/Handshake.luau +90 -0
- package/runtime/Pending.luau +196 -0
- package/runtime/Router.luau +379 -0
- package/runtime/Session.luau +731 -0
- package/shape/Compile.luau +340 -0
- package/shape/Folds.luau +332 -0
- package/shape/Frames.luau +170 -0
- package/shape/Grid.luau +169 -0
- package/shape/Ir.luau +540 -0
- package/shape/Joins.luau +125 -0
- package/shape/Legality.luau +273 -0
- package/shape/Lower.luau +419 -0
- package/shape/Order.luau +64 -0
- package/shape/Regions.luau +113 -0
- package/shape/Rotation.luau +472 -0
- package/shape/Serial.luau +258 -0
- package/shape/Words.luau +89 -0
- package/shape/emit/Block.luau +453 -0
- package/shape/emit/Column.luau +338 -0
- package/shape/emit/Leaf.luau +462 -0
- package/shape/emit/Word.luau +234 -0
- package/shape/emit/init.luau +222 -0
- package/shape/init.luau +78 -0
- package/state/Apply.luau +330 -0
- package/state/Audience.luau +126 -0
- package/state/Catchup.luau +218 -0
- package/state/Coalesce.luau +43 -0
- package/state/Fieldset.luau +177 -0
- package/state/Newest.luau +339 -0
- package/state/Plan.luau +441 -0
- package/state/Sections.luau +468 -0
- package/state/Store.luau +188 -0
- package/state/Sync.luau +762 -0
- package/wire/Bytes.luau +118 -0
- package/wire/Clock.luau +37 -0
- package/wire/Protocol.luau +110 -0
- package/wire/Sequence.luau +40 -0
- package/wire/Varint.luau +124 -0
- package/src/Types.luau +0 -180
- package/src/api/Group.luau +0 -156
- package/src/api/Packet.luau +0 -319
- package/src/api/Query.luau +0 -338
- package/src/api/Scope.luau +0 -90
- package/src/api/Signal.luau +0 -188
- package/src/codec/Base.luau +0 -136
- package/src/codec/composite/Array.luau +0 -456
- package/src/codec/composite/Map.luau +0 -469
- package/src/codec/composite/Optional.luau +0 -54
- package/src/codec/composite/Shared.luau +0 -291
- package/src/codec/composite/Struct.luau +0 -494
- package/src/codec/composite/Tagged.luau +0 -103
- package/src/codec/composite/Tuple.luau +0 -143
- package/src/codec/datatype/Buffer.luau +0 -66
- package/src/codec/datatype/CFrame.luau +0 -109
- package/src/codec/datatype/Color.luau +0 -32
- package/src/codec/datatype/Instance.luau +0 -52
- package/src/codec/datatype/IntVector.luau +0 -39
- package/src/codec/datatype/NumberRange.luau +0 -39
- package/src/codec/datatype/Ray.luau +0 -36
- package/src/codec/datatype/Rect.luau +0 -31
- package/src/codec/datatype/Region.luau +0 -62
- package/src/codec/datatype/Sequence.luau +0 -184
- package/src/codec/datatype/String.luau +0 -94
- package/src/codec/datatype/UDim.luau +0 -46
- package/src/codec/datatype/Vector.luau +0 -130
- package/src/codec/meta/Auto.luau +0 -318
- package/src/codec/meta/Bitfield.luau +0 -147
- package/src/codec/meta/Custom.luau +0 -33
- package/src/codec/meta/DeltaScalar.luau +0 -390
- package/src/codec/meta/Enum.luau +0 -50
- package/src/codec/meta/Float.luau +0 -38
- package/src/codec/meta/Nothing.luau +0 -13
- package/src/codec/meta/Unknown.luau +0 -41
- package/src/codec/primitive/Bool.luau +0 -26
- package/src/codec/primitive/Float16.luau +0 -99
- package/src/codec/primitive/Int.luau +0 -59
- package/src/codec/primitive/Number.luau +0 -24
- package/src/codec/primitive/Signed.luau +0 -32
- package/src/codec/primitive/Varint.luau +0 -165
- package/src/codec/primitive/Zint.luau +0 -99
- package/src/index.d.ts +0 -423
- package/src/init.luau +0 -362
- package/src/internal/Baseline.luau +0 -57
- package/src/internal/Channel.luau +0 -390
- package/src/internal/Middleware.luau +0 -100
- package/src/internal/Pool.luau +0 -65
- package/src/internal/Registry.luau +0 -119
- package/src/internal/Transport.luau +0 -28
- package/src/transport/Bridge.luau +0 -110
- package/src/transport/Client.luau +0 -97
- package/src/transport/Gate.luau +0 -488
- package/src/transport/Reader.luau +0 -331
- package/src/transport/Server.luau +0 -211
- package/src/util/Array.luau +0 -18
- package/src/util/Buffer.luau +0 -90
- package/src/util/Constants.luau +0 -30
- package/src/util/Log.luau +0 -68
- package/src/util/Player.luau +0 -14
- package/src/util/Quantize.luau +0 -84
- package/src/util/Quat.luau +0 -124
|
@@ -1,390 +0,0 @@
|
|
|
1
|
-
--!strict
|
|
2
|
-
--!native
|
|
3
|
-
-- Per-channel buffer state with MSB batch framing, XOR delta, timestamps.
|
|
4
|
-
|
|
5
|
-
local Base = require(script.Parent.Parent.codec.Base)
|
|
6
|
-
local Buf = require(script.Parent.Parent.util.Buffer)
|
|
7
|
-
local Constants = require(script.Parent.Parent.util.Constants)
|
|
8
|
-
local Log = require(script.Parent.Parent.util.Log)
|
|
9
|
-
local Types = require(script.Parent.Parent.Types)
|
|
10
|
-
local Varint = require(script.Parent.Parent.codec.primitive.Varint)
|
|
11
|
-
|
|
12
|
-
-- Constants --------------------------------------------------------------
|
|
13
|
-
|
|
14
|
-
local TS_NONE = 0
|
|
15
|
-
local TS_FRAME = 1
|
|
16
|
-
local TS_OFFSET = 2
|
|
17
|
-
local TS_FULL = 3
|
|
18
|
-
|
|
19
|
-
local INITIAL_BUF = 1024
|
|
20
|
-
local HEADER_BYTE = 1
|
|
21
|
-
local FRAME_MSB = Constants.FRAME_MSB
|
|
22
|
-
local U8_MAX = Constants.U8_MAX
|
|
23
|
-
local U16_MAX = Constants.U16_MAX
|
|
24
|
-
local MAX_BATCH_ITEMS = Constants.MAX_BATCH_ITEMS
|
|
25
|
-
|
|
26
|
-
-- 65.535s window so round((clock % win) * 1000) cannot overflow u16.
|
|
27
|
-
local OFFSET_WIN = 65.535
|
|
28
|
-
|
|
29
|
-
local TS_BYTES = table.freeze({
|
|
30
|
-
[TS_NONE] = 0,
|
|
31
|
-
[TS_FRAME] = 1,
|
|
32
|
-
[TS_OFFSET] = 2,
|
|
33
|
-
[TS_FULL] = 8,
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
-- State ------------------------------------------------------------------
|
|
37
|
-
|
|
38
|
-
local _timestampsEnabled = false
|
|
39
|
-
local _statsEnabled = false
|
|
40
|
-
|
|
41
|
-
-- Refreshed by updateTimestamp once per flush.
|
|
42
|
-
local _frameCounter = 0
|
|
43
|
-
local _offsetMs = 0
|
|
44
|
-
local _clock = 0.0
|
|
45
|
-
|
|
46
|
-
local EMPTY_REFS = table.freeze({}) :: { Instance }
|
|
47
|
-
|
|
48
|
-
-- Private ----------------------------------------------------------------
|
|
49
|
-
|
|
50
|
-
local alloc = Base.alloc
|
|
51
|
-
local ensure = Base.ensure
|
|
52
|
-
local band = bit32.band
|
|
53
|
-
local bor = bit32.bor
|
|
54
|
-
local bufCopy = buffer.copy
|
|
55
|
-
local writeu8 = buffer.writeu8
|
|
56
|
-
local writeu16 = buffer.writeu16
|
|
57
|
-
local writef64 = buffer.writef64
|
|
58
|
-
local round = math.round
|
|
59
|
-
local osClock = os.clock
|
|
60
|
-
|
|
61
|
-
local function writeTimestamp(b: buffer, off: number, mode: number): ()
|
|
62
|
-
if mode == TS_FRAME then
|
|
63
|
-
writeu8(b, off, _frameCounter)
|
|
64
|
-
elseif mode == TS_OFFSET then
|
|
65
|
-
writeu16(b, off, _offsetMs)
|
|
66
|
-
elseif mode == TS_FULL then
|
|
67
|
-
writef64(b, off, _clock)
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
--[[
|
|
72
|
-
Single -> multi upgrade. Patches the previously-emitted single-item
|
|
73
|
-
header, splices a 2-byte count cell after the timestamp, and shifts the
|
|
74
|
-
payload right. Hot only when a packet fires twice in a row to the same
|
|
75
|
-
channel without an intervening flush.
|
|
76
|
-
]]
|
|
77
|
-
local function upgradeSingleToMulti(ch: Types.ChannelState, regId: number, headerSize: number): ()
|
|
78
|
-
local headerPos = ch.singlePos
|
|
79
|
-
writeu8(ch.buff, headerPos, regId)
|
|
80
|
-
|
|
81
|
-
local afterTs = headerPos + headerSize
|
|
82
|
-
local cursor = ch.cursor
|
|
83
|
-
local payloadLen = cursor - afterTs
|
|
84
|
-
ensure(ch, 2)
|
|
85
|
-
-- Re-bind: ensure() may have realloced the buffer.
|
|
86
|
-
local b = ch.buff
|
|
87
|
-
bufCopy(b, afterTs + 2, b, afterTs, payloadLen)
|
|
88
|
-
|
|
89
|
-
ch.countPos = afterTs
|
|
90
|
-
writeu16(b, afterTs, 0)
|
|
91
|
-
ch.cursor = cursor + 2
|
|
92
|
-
ch.singleMode = false
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
-- itemCount bump is left to the caller, AFTER the body write succeeds.
|
|
96
|
-
local function openOrUpgrade(ch: Types.ChannelState, reg: Types.Registration): ()
|
|
97
|
-
if reg.id ~= ch.lastId or ch.itemCount >= MAX_BATCH_ITEMS then
|
|
98
|
-
reg._openFn(ch, reg.id)
|
|
99
|
-
elseif ch.singleMode then
|
|
100
|
-
upgradeSingleToMulti(ch, reg.id, reg.headerSize)
|
|
101
|
-
end
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
local function spliceEncoded(ch: Types.ChannelState, payload: buffer, payloadLen: number): ()
|
|
105
|
-
local cursor = ch.cursor
|
|
106
|
-
if cursor + payloadLen > ch.size then
|
|
107
|
-
alloc(ch, payloadLen)
|
|
108
|
-
end
|
|
109
|
-
bufCopy(ch.buff, cursor, payload, 0, payloadLen)
|
|
110
|
-
ch.cursor = cursor + payloadLen
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
--[[
|
|
114
|
-
Hot-swapped by enableStats() so the per-item dispatch avoids a runtime
|
|
115
|
-
branch on the stats flag. itemCount bumps AFTER codec.write so a
|
|
116
|
-
throwing codec leaves the count consistent with the buffer.
|
|
117
|
-
]]
|
|
118
|
-
local writeBatchPlain: (Types.ChannelState, Types.Registration, any) -> ()
|
|
119
|
-
local writeBatchStats: (Types.ChannelState, Types.Registration, any) -> ()
|
|
120
|
-
|
|
121
|
-
writeBatchPlain = function(ch: Types.ChannelState, reg: Types.Registration, data: any): ()
|
|
122
|
-
openOrUpgrade(ch, reg)
|
|
123
|
-
reg.codec.write(ch, data)
|
|
124
|
-
ch.itemCount += 1
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
writeBatchStats = function(ch: Types.ChannelState, reg: Types.Registration, data: any): ()
|
|
128
|
-
openOrUpgrade(ch, reg)
|
|
129
|
-
local before = ch.cursor
|
|
130
|
-
reg.codec.write(ch, data)
|
|
131
|
-
ch.itemCount += 1
|
|
132
|
-
reg.bytesSent += ch.cursor - before
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
--[[
|
|
136
|
-
Splice a pre-encoded payload into the channel. Used by Packet.broadcast
|
|
137
|
-
to encode the codec output once per fanout instead of N times.
|
|
138
|
-
]]
|
|
139
|
-
local writeBatchEncodedPlain: (Types.ChannelState, Types.Registration, buffer, number) -> ()
|
|
140
|
-
local writeBatchEncodedStats: (Types.ChannelState, Types.Registration, buffer, number) -> ()
|
|
141
|
-
|
|
142
|
-
writeBatchEncodedPlain = function(
|
|
143
|
-
ch: Types.ChannelState,
|
|
144
|
-
reg: Types.Registration,
|
|
145
|
-
payload: buffer,
|
|
146
|
-
payloadLen: number
|
|
147
|
-
): ()
|
|
148
|
-
openOrUpgrade(ch, reg)
|
|
149
|
-
spliceEncoded(ch, payload, payloadLen)
|
|
150
|
-
ch.itemCount += 1
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
writeBatchEncodedStats = function(
|
|
154
|
-
ch: Types.ChannelState,
|
|
155
|
-
reg: Types.Registration,
|
|
156
|
-
payload: buffer,
|
|
157
|
-
payloadLen: number
|
|
158
|
-
): ()
|
|
159
|
-
openOrUpgrade(ch, reg)
|
|
160
|
-
spliceEncoded(ch, payload, payloadLen)
|
|
161
|
-
ch.itemCount += 1
|
|
162
|
-
reg.bytesSent += payloadLen
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
-- Public -----------------------------------------------------------------
|
|
166
|
-
|
|
167
|
-
-- Module table is intentionally unfrozen: enableStats() hot-swaps writeBatch.
|
|
168
|
-
local Channel = {}
|
|
169
|
-
|
|
170
|
-
Channel.TS_NONE = TS_NONE
|
|
171
|
-
Channel.TS_FRAME = TS_FRAME
|
|
172
|
-
Channel.TS_OFFSET = TS_OFFSET
|
|
173
|
-
Channel.TS_FULL = TS_FULL
|
|
174
|
-
|
|
175
|
-
function Channel.create(): Types.ChannelState
|
|
176
|
-
return {
|
|
177
|
-
buff = buffer.create(INITIAL_BUF),
|
|
178
|
-
cursor = 0,
|
|
179
|
-
size = INITIAL_BUF,
|
|
180
|
-
refs = {},
|
|
181
|
-
refCount = 0,
|
|
182
|
-
lastId = -1,
|
|
183
|
-
countPos = -1,
|
|
184
|
-
itemCount = 0,
|
|
185
|
-
singleMode = false,
|
|
186
|
-
singlePos = 0,
|
|
187
|
-
deltas = {},
|
|
188
|
-
prevDump = nil,
|
|
189
|
-
prevDumpLen = 0,
|
|
190
|
-
currentPacket = nil,
|
|
191
|
-
}
|
|
192
|
-
end
|
|
193
|
-
|
|
194
|
-
function Channel.writeByte(ch: Types.ChannelState, byte: number): ()
|
|
195
|
-
local cursor = ch.cursor
|
|
196
|
-
if cursor + HEADER_BYTE > ch.size then
|
|
197
|
-
alloc(ch, HEADER_BYTE)
|
|
198
|
-
end
|
|
199
|
-
writeu8(ch.buff, cursor, byte)
|
|
200
|
-
ch.cursor = cursor + HEADER_BYTE
|
|
201
|
-
end
|
|
202
|
-
|
|
203
|
-
-- Append `value` to the sidecar refs, write its 1-based u16 index.
|
|
204
|
-
function Channel.pushRef(ch: Types.ChannelState, value: any): ()
|
|
205
|
-
local idx = ch.refCount + 1
|
|
206
|
-
Log.assert(idx <= U16_MAX, "ref overflow, max 65535")
|
|
207
|
-
ch.refs[idx] = value
|
|
208
|
-
ch.refCount = idx
|
|
209
|
-
|
|
210
|
-
local cursor = ch.cursor
|
|
211
|
-
if cursor + 2 > ch.size then
|
|
212
|
-
alloc(ch, 2)
|
|
213
|
-
end
|
|
214
|
-
writeu16(ch.buff, cursor, idx)
|
|
215
|
-
ch.cursor = cursor + 2
|
|
216
|
-
end
|
|
217
|
-
|
|
218
|
-
--[[
|
|
219
|
-
Per-flush reset. Preserves prevDump/prevDumpLen (XOR baseline) and
|
|
220
|
-
ch.deltas (delta caches): both must outlive flushes for the same owner.
|
|
221
|
-
]]
|
|
222
|
-
function Channel.reset(ch: Types.ChannelState): ()
|
|
223
|
-
ch.cursor = 0
|
|
224
|
-
ch.lastId = -1
|
|
225
|
-
ch.countPos = -1
|
|
226
|
-
ch.itemCount = 0
|
|
227
|
-
ch.singleMode = false
|
|
228
|
-
ch.singlePos = 0
|
|
229
|
-
ch.currentPacket = nil
|
|
230
|
-
if ch.refCount > 0 then
|
|
231
|
-
table.clear(ch.refs)
|
|
232
|
-
ch.refCount = 0
|
|
233
|
-
end
|
|
234
|
-
end
|
|
235
|
-
|
|
236
|
-
--[[
|
|
237
|
-
Full reset including XOR baseline and delta caches. Pool.acquire calls
|
|
238
|
-
this so a recycled state cannot leak the previous owner's baseline.
|
|
239
|
-
]]
|
|
240
|
-
function Channel.fullReset(ch: Types.ChannelState): ()
|
|
241
|
-
Channel.reset(ch)
|
|
242
|
-
ch.prevDump = nil
|
|
243
|
-
ch.prevDumpLen = 0
|
|
244
|
-
table.clear(ch.deltas)
|
|
245
|
-
end
|
|
246
|
-
|
|
247
|
-
--[[
|
|
248
|
-
Resolve the batch-open closure for a given timestamp mode and packet
|
|
249
|
-
name. Returned function seals the prior batch, writes the single-mode
|
|
250
|
-
header [0x80 | id] + optional timestamp, and records the cursor so
|
|
251
|
-
writeBatch can upgrade single -> multi later.
|
|
252
|
-
]]
|
|
253
|
-
function Channel.resolveOpenFn(
|
|
254
|
-
timestampMode: number,
|
|
255
|
-
name: string
|
|
256
|
-
): (ch: Types.ChannelState, id: number) -> ()
|
|
257
|
-
local headerLen = HEADER_BYTE + TS_BYTES[timestampMode]
|
|
258
|
-
|
|
259
|
-
return function(ch: Types.ChannelState, id: number): ()
|
|
260
|
-
--[[
|
|
261
|
-
Inline sealCount: this is the only call site that can take the
|
|
262
|
-
multi-mode branch on a non-fresh channel.
|
|
263
|
-
]]
|
|
264
|
-
if ch.lastId >= 0 and not ch.singleMode then
|
|
265
|
-
writeu16(ch.buff, ch.countPos, ch.itemCount)
|
|
266
|
-
end
|
|
267
|
-
|
|
268
|
-
ch.currentPacket = name
|
|
269
|
-
ch.lastId = id
|
|
270
|
-
ch.itemCount = 0
|
|
271
|
-
|
|
272
|
-
local cursor = ch.cursor
|
|
273
|
-
if cursor + headerLen > ch.size then
|
|
274
|
-
alloc(ch, headerLen)
|
|
275
|
-
end
|
|
276
|
-
local b = ch.buff
|
|
277
|
-
writeu8(b, cursor, bor(FRAME_MSB, id))
|
|
278
|
-
writeTimestamp(b, cursor + 1, timestampMode)
|
|
279
|
-
|
|
280
|
-
ch.singleMode = true
|
|
281
|
-
ch.singlePos = cursor
|
|
282
|
-
ch.cursor = cursor + headerLen
|
|
283
|
-
end
|
|
284
|
-
end
|
|
285
|
-
|
|
286
|
-
Channel.writeBatch = writeBatchPlain
|
|
287
|
-
Channel.writeBatchEncoded = writeBatchEncodedPlain
|
|
288
|
-
|
|
289
|
-
--[[
|
|
290
|
-
Every caller (Server.flushReliable / flushUnreliable, Client.flush)
|
|
291
|
-
pre-checks `ch.cursor > 0`, so cursor here is always positive.
|
|
292
|
-
]]
|
|
293
|
-
function Channel.sealAndDump(ch: Types.ChannelState): (buffer, { Instance }, number, number)
|
|
294
|
-
if ch.lastId >= 0 and not ch.singleMode then
|
|
295
|
-
writeu16(ch.buff, ch.countPos, ch.itemCount)
|
|
296
|
-
end
|
|
297
|
-
|
|
298
|
-
local cursor = ch.cursor
|
|
299
|
-
local snapshot = buffer.create(cursor)
|
|
300
|
-
bufCopy(snapshot, 0, ch.buff, 0, cursor)
|
|
301
|
-
|
|
302
|
-
local refCount = ch.refCount
|
|
303
|
-
local refs = if refCount > 0 then table.clone(ch.refs) else EMPTY_REFS
|
|
304
|
-
return snapshot, refs, cursor, refCount
|
|
305
|
-
end
|
|
306
|
-
|
|
307
|
-
Channel.xorApply = Buf.xorApply
|
|
308
|
-
|
|
309
|
-
--[[
|
|
310
|
-
Query frame. MSB on the id byte = nil response; cleared = payload follows.
|
|
311
|
-
Correlation IDs are varint-encoded.
|
|
312
|
-
]]
|
|
313
|
-
function Channel.writeQuery(
|
|
314
|
-
ch: Types.ChannelState,
|
|
315
|
-
id: number,
|
|
316
|
-
correlationId: number,
|
|
317
|
-
codec: Types.InternalCodec<any>?,
|
|
318
|
-
data: any
|
|
319
|
-
): ()
|
|
320
|
-
if ch.lastId >= 0 and not ch.singleMode then
|
|
321
|
-
writeu16(ch.buff, ch.countPos, ch.itemCount)
|
|
322
|
-
end
|
|
323
|
-
--[[
|
|
324
|
-
lastId = -1 forces the next writeBatch to call _openFn (which resets
|
|
325
|
-
singleMode). Every other reader of singleMode (writeQuery,
|
|
326
|
-
sealAndDump) is gated by `lastId >= 0` first, so stamping singleMode
|
|
327
|
-
here would never be observed before the next reset overwrites it.
|
|
328
|
-
]]
|
|
329
|
-
ch.lastId = -1
|
|
330
|
-
|
|
331
|
-
local hasPayload = codec ~= nil and data ~= nil
|
|
332
|
-
local headerByte = if hasPayload then id else bor(FRAME_MSB, id)
|
|
333
|
-
|
|
334
|
-
local cursor = ch.cursor
|
|
335
|
-
if cursor + 1 > ch.size then
|
|
336
|
-
alloc(ch, 1)
|
|
337
|
-
end
|
|
338
|
-
writeu8(ch.buff, cursor, headerByte)
|
|
339
|
-
ch.cursor = cursor + 1
|
|
340
|
-
|
|
341
|
-
Varint.write(ch, correlationId)
|
|
342
|
-
if hasPayload then
|
|
343
|
-
(codec :: Types.InternalCodec<any>).write(ch, data)
|
|
344
|
-
end
|
|
345
|
-
end
|
|
346
|
-
|
|
347
|
-
-- Configuration ----------------------------------------------------------
|
|
348
|
-
|
|
349
|
-
function Channel.setMaxSize(bytes: number): ()
|
|
350
|
-
Base.setMaxSize(bytes)
|
|
351
|
-
end
|
|
352
|
-
|
|
353
|
-
function Channel.updateTimestamp(): ()
|
|
354
|
-
_frameCounter = band(_frameCounter + 1, U8_MAX)
|
|
355
|
-
local now = osClock()
|
|
356
|
-
_offsetMs = round((now % OFFSET_WIN) * 1000)
|
|
357
|
-
_clock = now
|
|
358
|
-
end
|
|
359
|
-
|
|
360
|
-
function Channel.enableTimestamps(): ()
|
|
361
|
-
_timestampsEnabled = true
|
|
362
|
-
end
|
|
363
|
-
|
|
364
|
-
function Channel.hasTimestamps(): boolean
|
|
365
|
-
return _timestampsEnabled
|
|
366
|
-
end
|
|
367
|
-
|
|
368
|
-
function Channel.enableStats(): ()
|
|
369
|
-
_statsEnabled = true
|
|
370
|
-
Channel.writeBatch = writeBatchStats
|
|
371
|
-
Channel.writeBatchEncoded = writeBatchEncodedStats
|
|
372
|
-
end
|
|
373
|
-
|
|
374
|
-
function Channel.statsEnabled(): boolean
|
|
375
|
-
return _statsEnabled
|
|
376
|
-
end
|
|
377
|
-
|
|
378
|
-
-- Module-level config + timestamp counters. Distinct from Channel.reset(ch).
|
|
379
|
-
function Channel.resetGlobals(): ()
|
|
380
|
-
Base.reset()
|
|
381
|
-
_timestampsEnabled = false
|
|
382
|
-
_statsEnabled = false
|
|
383
|
-
_frameCounter = 0
|
|
384
|
-
_offsetMs = 0
|
|
385
|
-
_clock = 0.0
|
|
386
|
-
Channel.writeBatch = writeBatchPlain
|
|
387
|
-
Channel.writeBatchEncoded = writeBatchEncodedPlain
|
|
388
|
-
end
|
|
389
|
-
|
|
390
|
-
return Channel
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
--!strict
|
|
2
|
-
--!optimize 2
|
|
3
|
-
-- Ordered intercept chains for send/receive/drop hooks.
|
|
4
|
-
|
|
5
|
-
local Signal = require(script.Parent.Parent.api.Signal)
|
|
6
|
-
local Types = require(script.Parent.Parent.Types)
|
|
7
|
-
|
|
8
|
-
-- State ------------------------------------------------------------------
|
|
9
|
-
|
|
10
|
-
local _sendSignal = Signal.create()
|
|
11
|
-
local _receiveSignal = Signal.create()
|
|
12
|
-
local _dropSignal = Signal.create()
|
|
13
|
-
|
|
14
|
-
-- Private ----------------------------------------------------------------
|
|
15
|
-
|
|
16
|
-
-- Mirrors Signal's STATE_DISCONNECTED. Entries in this state are mid-fire disconnects.
|
|
17
|
-
local STATE_DISCONNECTED = 0
|
|
18
|
-
|
|
19
|
-
--[[
|
|
20
|
-
Snapshot before iterating: a handler can disconnect itself or others,
|
|
21
|
-
and a fresh chain pull would skip or double-fire entries depending on
|
|
22
|
-
swap direction. Snapshotting locks the per-frame view.
|
|
23
|
-
]]
|
|
24
|
-
local function runChain(
|
|
25
|
-
signal: { _count: number, _entries: { any } },
|
|
26
|
-
value: any,
|
|
27
|
-
name: string,
|
|
28
|
-
player: Player?
|
|
29
|
-
): any
|
|
30
|
-
local count = signal._count
|
|
31
|
-
if count == 0 then
|
|
32
|
-
return value
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
local entries = signal._entries
|
|
36
|
-
local snapshot = table.create(count)
|
|
37
|
-
table.move(entries, 1, count, 1, snapshot)
|
|
38
|
-
|
|
39
|
-
local result = value
|
|
40
|
-
for i = 1, count do
|
|
41
|
-
local entry = snapshot[i]
|
|
42
|
-
if entry._state ~= STATE_DISCONNECTED then
|
|
43
|
-
local returned = entry.fn(result, name, player)
|
|
44
|
-
-- Hooks return non-nil to replace the value; nil keeps the prior result.
|
|
45
|
-
if returned ~= nil then
|
|
46
|
-
result = returned
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
return result
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
-- Public -----------------------------------------------------------------
|
|
54
|
-
|
|
55
|
-
local Middleware = {}
|
|
56
|
-
|
|
57
|
-
function Middleware.onSend(fn: (data: any, name: string, player: Player?) -> any): Types.Connection
|
|
58
|
-
return _sendSignal:connect(fn)
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
function Middleware.onReceive(
|
|
62
|
-
fn: (data: any, name: string, player: Player?) -> any
|
|
63
|
-
): Types.Connection
|
|
64
|
-
return _receiveSignal:connect(fn)
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
function Middleware.onDrop(
|
|
68
|
-
fn: (player: Player, reason: string, name: string, data: any?) -> ()
|
|
69
|
-
): Types.Connection
|
|
70
|
-
return _dropSignal:connect(fn)
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
function Middleware.runSend(data: any, name: string, player: Player?): any
|
|
74
|
-
return runChain(_sendSignal, data, name, player)
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
function Middleware.runReceive(data: any, name: string, player: Player?): any
|
|
78
|
-
return runChain(_receiveSignal, data, name, player)
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
function Middleware.fireDrop(player: Player, reason: string, name: string, data: any?): ()
|
|
82
|
-
_dropSignal:fireSync(player, reason, name, data)
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
-- Hot-path callers gate on these to skip runChain's snapshot allocation.
|
|
86
|
-
function Middleware.hasSendHooks(): boolean
|
|
87
|
-
return _sendSignal:hasListeners()
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
function Middleware.hasReceiveHooks(): boolean
|
|
91
|
-
return _receiveSignal:hasListeners()
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
function Middleware.reset(): ()
|
|
95
|
-
_sendSignal = Signal.create()
|
|
96
|
-
_receiveSignal = Signal.create()
|
|
97
|
-
_dropSignal = Signal.create()
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
return table.freeze(Middleware)
|
package/src/internal/Pool.luau
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
--!strict
|
|
2
|
-
--!optimize 2
|
|
3
|
-
-- ChannelState pool. Acquire fullResets to wipe prior owner state.
|
|
4
|
-
|
|
5
|
-
local Channel = require(script.Parent.Channel)
|
|
6
|
-
local Log = require(script.Parent.Parent.util.Log)
|
|
7
|
-
local Types = require(script.Parent.Parent.Types)
|
|
8
|
-
|
|
9
|
-
-- Constants --------------------------------------------------------------
|
|
10
|
-
|
|
11
|
-
local DEFAULT_SIZE = 16
|
|
12
|
-
|
|
13
|
-
-- State ------------------------------------------------------------------
|
|
14
|
-
|
|
15
|
-
local _stack: { Types.ChannelState } = {}
|
|
16
|
-
local _depth = 0
|
|
17
|
-
local _maxSize = DEFAULT_SIZE
|
|
18
|
-
|
|
19
|
-
-- Public -----------------------------------------------------------------
|
|
20
|
-
|
|
21
|
-
local Pool = {}
|
|
22
|
-
|
|
23
|
-
-- fullReset wipes the prior owner's XOR baseline and delta caches.
|
|
24
|
-
function Pool.acquire(): Types.ChannelState
|
|
25
|
-
if _depth > 0 then
|
|
26
|
-
local ch = _stack[_depth]
|
|
27
|
-
_stack[_depth] = nil
|
|
28
|
-
_depth -= 1
|
|
29
|
-
Channel.fullReset(ch)
|
|
30
|
-
return ch
|
|
31
|
-
end
|
|
32
|
-
return Channel.create()
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
-- Above the cap, drop on the floor; the GC reclaims the buffer.
|
|
36
|
-
function Pool.release(ch: Types.ChannelState): ()
|
|
37
|
-
if _depth >= _maxSize then
|
|
38
|
-
return
|
|
39
|
-
end
|
|
40
|
-
_depth += 1
|
|
41
|
-
_stack[_depth] = ch
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
function Pool.setMaxSize(size: number): ()
|
|
45
|
-
if size < 0 then
|
|
46
|
-
Log.error(`size must be non-negative, got {size}`)
|
|
47
|
-
end
|
|
48
|
-
_maxSize = size
|
|
49
|
-
while _depth > _maxSize do
|
|
50
|
-
_stack[_depth] = nil
|
|
51
|
-
_depth -= 1
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
function Pool.count(): number
|
|
56
|
-
return _depth
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
function Pool.reset(): ()
|
|
60
|
-
table.clear(_stack)
|
|
61
|
-
_depth = 0
|
|
62
|
-
_maxSize = DEFAULT_SIZE
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
return table.freeze(Pool)
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
--!strict
|
|
2
|
-
--!optimize 2
|
|
3
|
-
-- Sequential 7-bit packet/query ID assignment.
|
|
4
|
-
|
|
5
|
-
local Log = require(script.Parent.Parent.util.Log)
|
|
6
|
-
local Signal = require(script.Parent.Parent.api.Signal)
|
|
7
|
-
local Types = require(script.Parent.Parent.Types)
|
|
8
|
-
|
|
9
|
-
-- Constants --------------------------------------------------------------
|
|
10
|
-
|
|
11
|
-
local KIND_PACKET = 0
|
|
12
|
-
local KIND_REQUEST = 1
|
|
13
|
-
local KIND_RESPONSE = 2
|
|
14
|
-
|
|
15
|
-
-- 7-bit id leaves the MSB free for the frame single/multi flag (see Constants.FRAME_MSB).
|
|
16
|
-
local MAX_ID = 127
|
|
17
|
-
|
|
18
|
-
-- State ------------------------------------------------------------------
|
|
19
|
-
|
|
20
|
-
local _nextId = 0
|
|
21
|
-
local _registrationsById: { [number]: Types.Registration } = {}
|
|
22
|
-
local _registrationsByName: { [string]: Types.Registration } = {}
|
|
23
|
-
|
|
24
|
-
-- Public -----------------------------------------------------------------
|
|
25
|
-
|
|
26
|
-
local Registry = {}
|
|
27
|
-
|
|
28
|
-
Registry.KIND_PACKET = KIND_PACKET
|
|
29
|
-
Registry.KIND_REQUEST = KIND_REQUEST
|
|
30
|
-
Registry.KIND_RESPONSE = KIND_RESPONSE
|
|
31
|
-
Registry.MAX_ID = MAX_ID
|
|
32
|
-
|
|
33
|
-
export type RegisterOptions = {
|
|
34
|
-
name: string,
|
|
35
|
-
kind: number,
|
|
36
|
-
codec: Types.InternalCodec<any>,
|
|
37
|
-
isUnreliable: boolean,
|
|
38
|
-
partner: number?,
|
|
39
|
-
needsGate: boolean,
|
|
40
|
-
headerSize: number,
|
|
41
|
-
timestampMode: number,
|
|
42
|
-
openFn: (ch: Types.ChannelState, id: number) -> (),
|
|
43
|
-
rateLimit: Types.RateLimitConfig?,
|
|
44
|
-
validate: Types.ValidateFn?,
|
|
45
|
-
maxPayloadBytes: number?,
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function Registry.register(opts: RegisterOptions): Types.Registration
|
|
49
|
-
local nextCandidate = _nextId + 1
|
|
50
|
-
if nextCandidate > MAX_ID then
|
|
51
|
-
Log.error(`ID limit ({MAX_ID}) exceeded; cannot register "{opts.name}"`)
|
|
52
|
-
end
|
|
53
|
-
--[[
|
|
54
|
-
RESPONSE entries share their request's name with a "\0resp" suffix; only
|
|
55
|
-
request/packet names need uniqueness in the by-name index.
|
|
56
|
-
]]
|
|
57
|
-
if opts.kind ~= KIND_RESPONSE and _registrationsByName[opts.name] ~= nil then
|
|
58
|
-
Log.error(`duplicate name "{opts.name}"`)
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
_nextId = nextCandidate
|
|
62
|
-
|
|
63
|
-
local reg: Types.Registration = {
|
|
64
|
-
id = nextCandidate,
|
|
65
|
-
name = opts.name,
|
|
66
|
-
kind = opts.kind,
|
|
67
|
-
codec = opts.codec,
|
|
68
|
-
isUnreliable = opts.isUnreliable,
|
|
69
|
-
partner = opts.partner,
|
|
70
|
-
signal = Signal.create(),
|
|
71
|
-
rateLimit = opts.rateLimit,
|
|
72
|
-
validate = opts.validate,
|
|
73
|
-
needsGate = opts.needsGate,
|
|
74
|
-
maxPayloadBytes = opts.maxPayloadBytes,
|
|
75
|
-
timestampMode = opts.timestampMode,
|
|
76
|
-
headerSize = opts.headerSize,
|
|
77
|
-
_openFn = opts.openFn,
|
|
78
|
-
bytesSent = 0,
|
|
79
|
-
bytesReceived = 0,
|
|
80
|
-
fires = 0,
|
|
81
|
-
recvFires = 0,
|
|
82
|
-
drops = 0,
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
_registrationsById[nextCandidate] = reg
|
|
86
|
-
if opts.kind ~= KIND_RESPONSE then
|
|
87
|
-
_registrationsByName[opts.name] = reg
|
|
88
|
-
end
|
|
89
|
-
return reg
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
function Registry.get(id: number): Types.Registration?
|
|
93
|
-
return _registrationsById[id]
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
function Registry.getByName(name: string): Types.Registration?
|
|
97
|
-
return _registrationsByName[name]
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
function Registry.count(): number
|
|
101
|
-
return _nextId
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
-- IDs are sequential and never freed, so the table has no holes.
|
|
105
|
-
function Registry.all(): { Types.Registration }
|
|
106
|
-
local result = table.create(_nextId)
|
|
107
|
-
for i = 1, _nextId do
|
|
108
|
-
result[i] = _registrationsById[i]
|
|
109
|
-
end
|
|
110
|
-
return result
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
function Registry.reset(): ()
|
|
114
|
-
table.clear(_registrationsById)
|
|
115
|
-
table.clear(_registrationsByName)
|
|
116
|
-
_nextId = 0
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
return table.freeze(Registry)
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
--!strict
|
|
2
|
-
--!optimize 2
|
|
3
|
-
-- Lazy Server/Client require to break the api -> transport import cycle.
|
|
4
|
-
|
|
5
|
-
-- State ------------------------------------------------------------------
|
|
6
|
-
|
|
7
|
-
local _server: any = nil
|
|
8
|
-
local _client: any = nil
|
|
9
|
-
|
|
10
|
-
-- Public -----------------------------------------------------------------
|
|
11
|
-
|
|
12
|
-
local Transport = {}
|
|
13
|
-
|
|
14
|
-
function Transport.server(): any
|
|
15
|
-
if not _server then
|
|
16
|
-
_server = require(script.Parent.Parent.transport.Server)
|
|
17
|
-
end
|
|
18
|
-
return _server
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
function Transport.client(): any
|
|
22
|
-
if not _client then
|
|
23
|
-
_client = require(script.Parent.Parent.transport.Client)
|
|
24
|
-
end
|
|
25
|
-
return _client
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
return table.freeze(Transport)
|