@axpecter/lync 2.2.0 → 2.3.1
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 +95 -143
- package/package.json +1 -1
- package/src/Types.luau +22 -16
- package/src/api/Group.luau +40 -33
- package/src/api/Packet.luau +95 -72
- package/src/api/Query.luau +128 -79
- package/src/api/Scope.luau +26 -10
- package/src/api/Signal.luau +43 -52
- package/src/codec/Base.luau +21 -14
- package/src/codec/composite/Array.luau +56 -134
- package/src/codec/composite/Map.luau +103 -72
- package/src/codec/composite/Optional.luau +6 -2
- package/src/codec/composite/Shared.luau +160 -69
- package/src/codec/composite/Struct.luau +283 -42
- package/src/codec/composite/Tagged.luau +13 -16
- package/src/codec/composite/Tuple.luau +21 -15
- package/src/codec/datatype/Buffer.luau +6 -4
- package/src/codec/datatype/CFrame.luau +56 -44
- package/src/codec/datatype/Color.luau +10 -10
- package/src/codec/datatype/Instance.luau +15 -12
- package/src/codec/datatype/IntVector.luau +2 -2
- package/src/codec/datatype/NumberRange.luau +15 -8
- package/src/codec/datatype/Ray.luau +13 -14
- package/src/codec/datatype/Rect.luau +12 -12
- package/src/codec/datatype/Region.luau +13 -14
- package/src/codec/datatype/Sequence.luau +87 -65
- package/src/codec/datatype/String.luau +17 -10
- package/src/codec/datatype/UDim.luau +10 -8
- package/src/codec/datatype/Vector.luau +20 -59
- package/src/codec/meta/Auto.luau +94 -126
- package/src/codec/meta/Bitfield.luau +12 -14
- package/src/codec/meta/Custom.luau +3 -1
- package/src/codec/meta/Enum.luau +9 -9
- package/src/codec/meta/Float.luau +6 -28
- package/src/codec/meta/Nothing.luau +1 -1
- package/src/codec/meta/Unknown.luau +10 -7
- package/src/codec/primitive/Bool.luau +6 -4
- package/src/codec/primitive/Float16.luau +5 -2
- package/src/codec/primitive/Int.luau +5 -6
- package/src/codec/primitive/Number.luau +6 -4
- package/src/codec/primitive/Signed.luau +2 -2
- package/src/codec/primitive/Varint.luau +69 -38
- package/src/index.d.ts +161 -53
- package/src/init.luau +123 -102
- package/src/internal/Baseline.luau +9 -1
- package/src/internal/Channel.luau +153 -154
- package/src/internal/Middleware.luau +22 -6
- package/src/internal/Pool.luau +12 -4
- package/src/internal/Registry.luau +25 -16
- package/src/internal/Transport.luau +1 -1
- package/src/transport/Bridge.luau +47 -33
- package/src/transport/Client.luau +25 -21
- package/src/transport/Gate.luau +227 -172
- package/src/transport/Reader.luau +162 -105
- package/src/transport/Server.luau +46 -57
- package/src/util/Array.luau +18 -0
- package/src/util/Buffer.luau +92 -0
- package/src/util/Constants.luau +30 -0
- package/src/util/Log.luau +68 -0
- package/src/util/Player.luau +14 -0
- package/src/util/Quantize.luau +60 -0
- package/src/internal/Util.luau +0 -26
package/src/init.luau
CHANGED
|
@@ -4,18 +4,20 @@
|
|
|
4
4
|
|
|
5
5
|
local RunService = game:GetService("RunService")
|
|
6
6
|
|
|
7
|
-
local
|
|
7
|
+
local Baseline = require(script.internal.Baseline)
|
|
8
|
+
local Bridge = require(script.transport.Bridge)
|
|
8
9
|
local Channel = require(script.internal.Channel)
|
|
9
10
|
local Gate = require(script.transport.Gate)
|
|
10
11
|
local Group = require(script.api.Group)
|
|
12
|
+
local Log = require(script.util.Log)
|
|
11
13
|
local Middleware = require(script.internal.Middleware)
|
|
12
14
|
local Packet = require(script.api.Packet)
|
|
15
|
+
local Player = require(script.util.Player)
|
|
13
16
|
local Pool = require(script.internal.Pool)
|
|
14
17
|
local Query = require(script.api.Query)
|
|
15
18
|
local Registry = require(script.internal.Registry)
|
|
16
19
|
local Scope = require(script.api.Scope)
|
|
17
20
|
local Types = require(script.Types)
|
|
18
|
-
local Util = require(script.internal.Util)
|
|
19
21
|
|
|
20
22
|
local ArrayC = require(script.codec.composite.Array)
|
|
21
23
|
local AutoC = require(script.codec.meta.Auto)
|
|
@@ -48,12 +50,31 @@ local UDimC = require(script.codec.datatype.UDim)
|
|
|
48
50
|
local UnknownC = require(script.codec.meta.Unknown)
|
|
49
51
|
local VectorC = require(script.codec.datatype.Vector)
|
|
50
52
|
|
|
53
|
+
-- Public types -----------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
export type Codec<T> = Types.Codec<T>
|
|
56
|
+
export type Connection = Types.Connection
|
|
57
|
+
export type Packet<T> = Packet.PacketHandle<T>
|
|
58
|
+
export type Query<Req, Resp> = Query.QueryHandle<Req, Resp>
|
|
59
|
+
export type Group = Group.GroupHandle
|
|
60
|
+
export type Scope = Scope.ScopeHandle
|
|
61
|
+
export type PacketOptions = Types.PacketOptions
|
|
62
|
+
export type QueryOptions = Types.QueryOptions
|
|
63
|
+
export type ConfigureOptions = Types.ConfigureOptions
|
|
64
|
+
export type RateLimitConfig = Types.RateLimitConfig
|
|
65
|
+
export type PacketStats = Types.PacketStats
|
|
66
|
+
export type PlayerStats = Types.PlayerStats
|
|
67
|
+
|
|
51
68
|
-- Constants --------------------------------------------------------------
|
|
52
69
|
|
|
53
70
|
local IS_SERVER = RunService:IsServer()
|
|
54
71
|
|
|
55
|
-
|
|
56
|
-
|
|
72
|
+
--[[
|
|
73
|
+
Singleton-string brand on a single `_lyncKind` field: dispatch is one
|
|
74
|
+
hash lookup at the same predicted slot, and the literals narrow on `==`.
|
|
75
|
+
]]
|
|
76
|
+
local ALL = table.freeze({ _lyncKind = "all" :: "all" })
|
|
77
|
+
local DROP = table.freeze({ _lyncKind = "drop" :: "drop" })
|
|
57
78
|
|
|
58
79
|
local CHANNEL_MAX_MIN, CHANNEL_MAX_MAX = 4096, 1048576
|
|
59
80
|
local DEPTH_MIN, DEPTH_MAX = 4, 32
|
|
@@ -62,12 +83,8 @@ local FLUSH_HZ_MIN, FLUSH_HZ_MAX = 1, 60
|
|
|
62
83
|
|
|
63
84
|
-- State ------------------------------------------------------------------
|
|
64
85
|
|
|
65
|
-
--
|
|
66
|
-
|
|
67
|
-
captures a single LCT_VAL upvalue instead of separate LCT_REF
|
|
68
|
-
upvalues for each field.
|
|
69
|
-
]]
|
|
70
|
-
local _flushState = {
|
|
86
|
+
-- Single table so the Heartbeat closure captures one LCT_VAL, not N LCT_REFs.
|
|
87
|
+
local flushState = {
|
|
71
88
|
started = false,
|
|
72
89
|
rate = FLUSH_HZ_MAX,
|
|
73
90
|
accum = 0,
|
|
@@ -77,95 +94,86 @@ local _flushState = {
|
|
|
77
94
|
|
|
78
95
|
-- Private ----------------------------------------------------------------
|
|
79
96
|
|
|
80
|
-
local isPlayer =
|
|
97
|
+
local isPlayer = Player.is
|
|
81
98
|
|
|
82
|
-
|
|
99
|
+
type ExceptValue = { _lyncKind: "except", _excluded: { [Player]: boolean } }
|
|
100
|
+
|
|
101
|
+
local function except(...: any): ExceptValue
|
|
83
102
|
local excluded: { [Player]: boolean } = {}
|
|
84
103
|
|
|
85
104
|
for i = 1, select("#", ...) do
|
|
86
105
|
local arg = select(i, ...)
|
|
87
106
|
if isPlayer(arg) then
|
|
88
107
|
excluded[arg :: Player] = true
|
|
89
|
-
elseif typeof(arg) == "table" and arg.
|
|
90
|
-
for player in arg._members
|
|
108
|
+
elseif typeof(arg) == "table" and arg._lyncKind == "group" then
|
|
109
|
+
for player in arg._members do
|
|
91
110
|
excluded[player] = true
|
|
92
111
|
end
|
|
93
112
|
else
|
|
94
|
-
error(
|
|
113
|
+
Log.error(`argument must be a Player or Group, got {typeof(arg)}`)
|
|
95
114
|
end
|
|
96
115
|
end
|
|
97
116
|
|
|
98
117
|
return table.freeze({
|
|
99
|
-
|
|
118
|
+
_lyncKind = "except" :: "except",
|
|
100
119
|
_excluded = table.freeze(excluded),
|
|
101
120
|
})
|
|
102
121
|
end
|
|
103
122
|
|
|
104
|
-
local function setupFlush(): ()
|
|
105
|
-
|
|
106
|
-
if IS_SERVER then
|
|
107
|
-
local Server = require(script.transport.Server)
|
|
108
|
-
transport = { flush = Server.flush }
|
|
109
|
-
else
|
|
110
|
-
local Client = require(script.transport.Client)
|
|
111
|
-
transport = { flush = Client.flush }
|
|
112
|
-
end
|
|
113
|
-
_flushState.transport = transport
|
|
123
|
+
local function setupFlush(transport: { flush: () -> () }): ()
|
|
124
|
+
flushState.transport = transport
|
|
114
125
|
|
|
115
|
-
|
|
116
|
-
local rate =
|
|
126
|
+
flushState.conn = RunService.Heartbeat:Connect(function(dt: number)
|
|
127
|
+
local rate = flushState.rate
|
|
117
128
|
if rate >= FLUSH_HZ_MAX then
|
|
118
129
|
transport.flush()
|
|
119
130
|
return
|
|
120
131
|
end
|
|
121
132
|
|
|
122
|
-
local accum =
|
|
133
|
+
local accum = flushState.accum + dt
|
|
123
134
|
local interval = 1 / rate
|
|
124
135
|
if accum >= interval then
|
|
125
|
-
--
|
|
126
|
-
-- stall) zero the accumulator instead of catching up.
|
|
136
|
+
-- Frame-stall guard: zero the accumulator instead of catching up.
|
|
127
137
|
accum = if accum >= 2 * interval then 0 else accum - interval
|
|
128
138
|
transport.flush()
|
|
129
139
|
end
|
|
130
|
-
|
|
140
|
+
flushState.accum = accum
|
|
131
141
|
end)
|
|
132
142
|
end
|
|
133
143
|
|
|
144
|
+
-- Validates `value` in [min, max] and applies it; errors with a configure-formatted message.
|
|
145
|
+
local function applyOption(
|
|
146
|
+
name: string,
|
|
147
|
+
value: number?,
|
|
148
|
+
min: number,
|
|
149
|
+
max: number,
|
|
150
|
+
apply: (number) -> ()
|
|
151
|
+
): ()
|
|
152
|
+
if value == nil then
|
|
153
|
+
return
|
|
154
|
+
end
|
|
155
|
+
if value < min or value > max then
|
|
156
|
+
Log.error(`{name} must be {min}-{max}`)
|
|
157
|
+
end
|
|
158
|
+
apply(value)
|
|
159
|
+
end
|
|
160
|
+
|
|
134
161
|
-- Public -----------------------------------------------------------------
|
|
135
162
|
|
|
136
163
|
local Lync = {}
|
|
137
164
|
|
|
138
165
|
function Lync.configure(options: Types.ConfigureOptions): ()
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
Channel.setMaxSize(size)
|
|
151
|
-
Base.setMaxSize(size)
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
local depth = options.validationDepth
|
|
155
|
-
if depth then
|
|
156
|
-
if depth < DEPTH_MIN or depth > DEPTH_MAX then
|
|
157
|
-
error(`[Lync] Lync.configure: validationDepth must be {DEPTH_MIN}-{DEPTH_MAX}`)
|
|
158
|
-
end
|
|
159
|
-
Gate.setDepth(depth)
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
local poolSize = options.poolSize
|
|
163
|
-
if poolSize then
|
|
164
|
-
if poolSize < POOL_MIN or poolSize > POOL_MAX then
|
|
165
|
-
error(`[Lync] Lync.configure: poolSize must be {POOL_MIN}-{POOL_MAX}`)
|
|
166
|
-
end
|
|
167
|
-
Pool.setMaxSize(poolSize)
|
|
168
|
-
end
|
|
166
|
+
Log.assert(not flushState.started, "must be called before Lync.start()")
|
|
167
|
+
|
|
168
|
+
applyOption(
|
|
169
|
+
"channelMaxSize",
|
|
170
|
+
options.channelMaxSize,
|
|
171
|
+
CHANNEL_MAX_MIN,
|
|
172
|
+
CHANNEL_MAX_MAX,
|
|
173
|
+
Channel.setMaxSize
|
|
174
|
+
)
|
|
175
|
+
applyOption("validationDepth", options.validationDepth, DEPTH_MIN, DEPTH_MAX, Gate.setDepth)
|
|
176
|
+
applyOption("poolSize", options.poolSize, POOL_MIN, POOL_MAX, Pool.setMaxSize)
|
|
169
177
|
|
|
170
178
|
if options.bandwidthLimit then
|
|
171
179
|
Gate.configureBandwidth(options.bandwidthLimit.softLimit, options.bandwidthLimit.maxStrikes)
|
|
@@ -177,49 +185,64 @@ function Lync.configure(options: Types.ConfigureOptions): ()
|
|
|
177
185
|
|
|
178
186
|
if options.stats then
|
|
179
187
|
Channel.enableStats()
|
|
180
|
-
if IS_SERVER then
|
|
181
|
-
local Server = require(script.transport.Server)
|
|
182
|
-
Server.enableStats()
|
|
183
|
-
end
|
|
184
188
|
end
|
|
185
189
|
end
|
|
186
190
|
|
|
187
191
|
function Lync.start(): ()
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
end
|
|
191
|
-
_flushState.started = true
|
|
192
|
+
Log.assert(not flushState.started, "already started")
|
|
193
|
+
flushState.started = true
|
|
192
194
|
|
|
193
195
|
if IS_SERVER then
|
|
194
196
|
local Server = require(script.transport.Server)
|
|
195
197
|
Server.start()
|
|
198
|
+
setupFlush(Server)
|
|
196
199
|
else
|
|
197
200
|
local Client = require(script.transport.Client)
|
|
198
201
|
Client.start()
|
|
202
|
+
setupFlush(Client)
|
|
199
203
|
end
|
|
200
|
-
|
|
201
|
-
setupFlush()
|
|
202
204
|
end
|
|
203
205
|
|
|
204
206
|
function Lync.flush(): ()
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
_flushState.accum = 0
|
|
207
|
+
Log.assert(flushState.started, "must call Lync.start() first")
|
|
208
|
+
-- Bind to a local: bare `(expr).flush()` after a statement parses as a continuation.
|
|
209
|
+
local transport = flushState.transport :: { flush: () -> () }
|
|
210
|
+
transport.flush()
|
|
211
|
+
flushState.accum = 0
|
|
211
212
|
end
|
|
212
213
|
|
|
213
214
|
function Lync.flushRate(hz: number): ()
|
|
214
215
|
if hz < FLUSH_HZ_MIN or hz > FLUSH_HZ_MAX then
|
|
215
|
-
error(`
|
|
216
|
+
Log.error(`hz must be {FLUSH_HZ_MIN}-{FLUSH_HZ_MAX}`)
|
|
216
217
|
end
|
|
217
|
-
|
|
218
|
-
|
|
218
|
+
flushState.rate = hz
|
|
219
|
+
flushState.accum = 0
|
|
219
220
|
end
|
|
220
221
|
|
|
221
222
|
function Lync.isStarted(): boolean
|
|
222
|
-
return
|
|
223
|
+
return flushState.started
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
-- Restore module state to post-require defaults. For tests / hot reload.
|
|
227
|
+
function Lync.reset(): ()
|
|
228
|
+
if flushState.conn then
|
|
229
|
+
flushState.conn:Disconnect()
|
|
230
|
+
flushState.conn = nil
|
|
231
|
+
end
|
|
232
|
+
flushState.started = false
|
|
233
|
+
flushState.rate = FLUSH_HZ_MAX
|
|
234
|
+
flushState.accum = 0
|
|
235
|
+
flushState.transport = nil
|
|
236
|
+
|
|
237
|
+
Baseline.reset()
|
|
238
|
+
Bridge.reset()
|
|
239
|
+
Channel.resetGlobals()
|
|
240
|
+
Gate.reset()
|
|
241
|
+
Group.reset()
|
|
242
|
+
Log.reset()
|
|
243
|
+
Middleware.reset()
|
|
244
|
+
Pool.reset()
|
|
245
|
+
Registry.reset()
|
|
223
246
|
end
|
|
224
247
|
|
|
225
248
|
Lync.packet = Packet.define
|
|
@@ -249,20 +272,21 @@ Lync.stats = table.freeze({
|
|
|
249
272
|
local Server = require(script.transport.Server)
|
|
250
273
|
Server.resetStats()
|
|
251
274
|
end
|
|
252
|
-
for
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
reg.recvFires = 0
|
|
259
|
-
reg.drops = 0
|
|
260
|
-
end
|
|
275
|
+
for _, reg in Registry.all() do
|
|
276
|
+
reg.bytesSent = 0
|
|
277
|
+
reg.bytesReceived = 0
|
|
278
|
+
reg.fires = 0
|
|
279
|
+
reg.recvFires = 0
|
|
280
|
+
reg.drops = 0
|
|
261
281
|
end
|
|
262
282
|
end,
|
|
263
283
|
})
|
|
264
284
|
|
|
265
285
|
Lync.debug = table.freeze({
|
|
286
|
+
--[[
|
|
287
|
+
Stubs reserved for capture/replay tooling. No-ops keep the API
|
|
288
|
+
surface stable across versions with or without the recorder.
|
|
289
|
+
]]
|
|
266
290
|
capture = function(_label: string?): () end,
|
|
267
291
|
stop = function(): () end,
|
|
268
292
|
dump = function(): () end,
|
|
@@ -272,20 +296,17 @@ Lync.debug = table.freeze({
|
|
|
272
296
|
end,
|
|
273
297
|
|
|
274
298
|
registrations = function(): { any }
|
|
299
|
+
-- Registry IDs are sequential 1..count and never freed, so get(i) is non-nil in range.
|
|
275
300
|
local count = Registry.count()
|
|
276
301
|
local result = table.create(count)
|
|
277
|
-
local n = 0
|
|
278
302
|
for i = 1, count do
|
|
279
|
-
local reg = Registry.get(i)
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
isUnreliable = reg.isUnreliable,
|
|
287
|
-
})
|
|
288
|
-
end
|
|
303
|
+
local reg = Registry.get(i) :: Types.Registration
|
|
304
|
+
result[i] = table.freeze({
|
|
305
|
+
name = reg.name,
|
|
306
|
+
id = reg.id,
|
|
307
|
+
kind = reg.kind,
|
|
308
|
+
isUnreliable = reg.isUnreliable,
|
|
309
|
+
})
|
|
289
310
|
end
|
|
290
311
|
return table.freeze(result)
|
|
291
312
|
end,
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
--!strict
|
|
2
2
|
--!optimize 2
|
|
3
|
-
-- Read-side delta cache
|
|
3
|
+
-- Read-side delta cache, keyed by (deltaId, peer).
|
|
4
4
|
|
|
5
5
|
local Types = require(script.Parent.Parent.Types)
|
|
6
6
|
|
|
7
7
|
-- State ------------------------------------------------------------------
|
|
8
8
|
|
|
9
|
+
-- Per-deltaId map keyed by BaselineKey: server uses Player, client uses `false`.
|
|
9
10
|
local _caches: { [number]: { [Types.BaselineKey]: any } } = {}
|
|
10
11
|
local _currentKey: Types.BaselineKey = false
|
|
11
12
|
local _hasDelta = false
|
|
@@ -38,6 +39,7 @@ function Baseline.clearPlayer(key: Types.BaselineKey): ()
|
|
|
38
39
|
end
|
|
39
40
|
end
|
|
40
41
|
|
|
42
|
+
-- Reader checks this once to skip baseline-key bookkeeping when no delta codec exists.
|
|
41
43
|
function Baseline.markHasDelta(): ()
|
|
42
44
|
_hasDelta = true
|
|
43
45
|
end
|
|
@@ -46,4 +48,10 @@ function Baseline.hasDelta(): boolean
|
|
|
46
48
|
return _hasDelta
|
|
47
49
|
end
|
|
48
50
|
|
|
51
|
+
function Baseline.reset(): ()
|
|
52
|
+
table.clear(_caches)
|
|
53
|
+
_currentKey = false
|
|
54
|
+
_hasDelta = false
|
|
55
|
+
end
|
|
56
|
+
|
|
49
57
|
return table.freeze(Baseline)
|