@axpecter/lync 1.5.2 → 2.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/README.md +102 -9
- package/package.json +2 -2
- package/src/Types.luau +10 -0
- package/src/api/Namespace.luau +8 -3
- package/src/api/Packet.luau +76 -43
- package/src/api/Query.luau +3 -1
- package/src/codec/composite/Array.luau +85 -6
- package/src/codec/composite/Map.luau +7 -1
- package/src/codec/composite/Optional.luau +1 -0
- package/src/codec/composite/Struct.luau +23 -1
- package/src/codec/composite/Tagged.luau +11 -0
- package/src/codec/composite/Tuple.luau +10 -0
- package/src/codec/datatype/Instance.luau +3 -0
- package/src/codec/meta/Unknown.luau +1 -0
- package/src/index.d.ts +36 -7
- package/src/init.luau +49 -2
- package/src/internal/Channel.luau +172 -42
- package/src/internal/Registry.luau +20 -3
- package/src/transport/Client.luau +65 -11
- package/src/transport/Gate.luau +9 -0
- package/src/transport/Reader.luau +75 -24
- package/src/transport/Server.luau +273 -68
|
@@ -12,6 +12,7 @@ local Types = require(script.Parent.Parent.Types)
|
|
|
12
12
|
|
|
13
13
|
type ChannelState = Types.ChannelState
|
|
14
14
|
type Codec<T> = Types.Codec<T>
|
|
15
|
+
type Registration = Types.Registration
|
|
15
16
|
|
|
16
17
|
-- State ---------------------------------------------------------------
|
|
17
18
|
|
|
@@ -22,6 +23,16 @@ local _unreliable = nil :: ChannelState?
|
|
|
22
23
|
local _prevRecv = nil :: buffer?
|
|
23
24
|
local _isStarted = false
|
|
24
25
|
|
|
26
|
+
-- Flush control
|
|
27
|
+
local _flushRate = 60
|
|
28
|
+
local _flushInterval = 1 / 60
|
|
29
|
+
local _useAccumulator = false
|
|
30
|
+
local _flushAccum = 0
|
|
31
|
+
local _flushedThisFrame = false
|
|
32
|
+
|
|
33
|
+
-- Timestamp
|
|
34
|
+
local _frameCounter: number = 0
|
|
35
|
+
|
|
25
36
|
-- Private -------------------------------------------------------------
|
|
26
37
|
|
|
27
38
|
local function receive(data: any, refs: any, applyXor: boolean): ()
|
|
@@ -64,6 +75,11 @@ local function flushChannel(remote: any, ch: ChannelState, applyXor: boolean): (
|
|
|
64
75
|
end
|
|
65
76
|
|
|
66
77
|
local function flush(): ()
|
|
78
|
+
if Channel.hasTimestamps() then
|
|
79
|
+
_frameCounter = (_frameCounter + 1) % 256
|
|
80
|
+
Channel.updateTimestamp(_frameCounter, 0, os.clock())
|
|
81
|
+
end
|
|
82
|
+
|
|
67
83
|
flushChannel(Bridge.reliable, _reliable :: ChannelState, true)
|
|
68
84
|
flushChannel(Bridge.unreliable, _unreliable :: ChannelState, false)
|
|
69
85
|
end
|
|
@@ -89,23 +105,39 @@ function Client.start(): ()
|
|
|
89
105
|
Bridge.unreliable.OnClientEvent:Connect(function(data: any, refs: any): ()
|
|
90
106
|
receive(data, refs, false)
|
|
91
107
|
end)
|
|
92
|
-
|
|
108
|
+
|
|
109
|
+
if _useAccumulator then
|
|
110
|
+
RunService.Heartbeat:Connect(function(dt: number)
|
|
111
|
+
if _flushedThisFrame then
|
|
112
|
+
_flushedThisFrame = false
|
|
113
|
+
return
|
|
114
|
+
end
|
|
115
|
+
_flushAccum += dt
|
|
116
|
+
if _flushAccum < _flushInterval then
|
|
117
|
+
return
|
|
118
|
+
end
|
|
119
|
+
_flushAccum -= _flushInterval
|
|
120
|
+
flush()
|
|
121
|
+
end)
|
|
122
|
+
else
|
|
123
|
+
RunService.Heartbeat:Connect(function()
|
|
124
|
+
if _flushedThisFrame then
|
|
125
|
+
_flushedThisFrame = false
|
|
126
|
+
return
|
|
127
|
+
end
|
|
128
|
+
flush()
|
|
129
|
+
end)
|
|
130
|
+
end
|
|
93
131
|
end
|
|
94
132
|
|
|
95
|
-
function Client.write(
|
|
96
|
-
id: number,
|
|
97
|
-
name: string,
|
|
98
|
-
codec: Codec<any>,
|
|
99
|
-
data: any,
|
|
100
|
-
isUnreliable: boolean
|
|
101
|
-
): ()
|
|
133
|
+
function Client.write(reg: Registration, data: any, isUnreliable: boolean): ()
|
|
102
134
|
if not _isStarted then
|
|
103
135
|
error(NOT_STARTED)
|
|
104
136
|
end
|
|
105
137
|
|
|
106
138
|
local final: any
|
|
107
139
|
if Middleware.hasSend then
|
|
108
|
-
final = Middleware.runSend(data, name, nil)
|
|
140
|
+
final = Middleware.runSend(data, reg.name, nil)
|
|
109
141
|
if final == nil then
|
|
110
142
|
return
|
|
111
143
|
end
|
|
@@ -114,10 +146,16 @@ function Client.write(
|
|
|
114
146
|
end
|
|
115
147
|
|
|
116
148
|
local ch = if isUnreliable then _unreliable :: ChannelState else _reliable :: ChannelState
|
|
117
|
-
Channel.
|
|
149
|
+
if Channel.statsEnabled() then
|
|
150
|
+
local before = ch.cursor
|
|
151
|
+
Channel.writeBatch(ch, reg, final)
|
|
152
|
+
reg.bytesSent += ch.cursor - before
|
|
153
|
+
reg.fires += 1
|
|
154
|
+
else
|
|
155
|
+
Channel.writeBatch(ch, reg, final)
|
|
156
|
+
end
|
|
118
157
|
end
|
|
119
158
|
|
|
120
|
-
-- Shared for both query requests and responses(identical wire format).
|
|
121
159
|
function Client.writeQuery(
|
|
122
160
|
id: number,
|
|
123
161
|
name: string,
|
|
@@ -149,4 +187,20 @@ function Client.writeQueryNil(id: number, name: string, correlationId: number):
|
|
|
149
187
|
Channel.writeQuery(_reliable :: ChannelState, id, name, correlationId, nil, nil)
|
|
150
188
|
end
|
|
151
189
|
|
|
190
|
+
function Client.flush(): ()
|
|
191
|
+
_flushAccum = 0
|
|
192
|
+
_flushedThisFrame = true
|
|
193
|
+
flush()
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
function Client.setFlushRate(hz: number): ()
|
|
197
|
+
_flushRate = math.clamp(hz, 1, 60)
|
|
198
|
+
_flushInterval = 1 / _flushRate
|
|
199
|
+
_useAccumulator = _flushRate < 60
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
function Client.getFlushRate(): number
|
|
203
|
+
return _flushRate
|
|
204
|
+
end
|
|
205
|
+
|
|
152
206
|
return table.freeze(Client)
|
package/src/transport/Gate.luau
CHANGED
|
@@ -162,11 +162,17 @@ end
|
|
|
162
162
|
|
|
163
163
|
function Gate.check(value: any, reg: Registration, player: Player): boolean
|
|
164
164
|
if not checkRate(player, reg) then
|
|
165
|
+
if reg.drops then
|
|
166
|
+
reg.drops += 1
|
|
167
|
+
end
|
|
165
168
|
fireDrop(player, "rate", reg.name, nil)
|
|
166
169
|
return false
|
|
167
170
|
end
|
|
168
171
|
|
|
169
172
|
if not isClean(value, 1) then
|
|
173
|
+
if reg.drops then
|
|
174
|
+
reg.drops += 1
|
|
175
|
+
end
|
|
170
176
|
fireDrop(player, "nan", reg.name, value)
|
|
171
177
|
return false
|
|
172
178
|
end
|
|
@@ -175,6 +181,9 @@ function Gate.check(value: any, reg: Registration, player: Player): boolean
|
|
|
175
181
|
if validate then
|
|
176
182
|
local ok, reason = validate(value, player)
|
|
177
183
|
if not ok then
|
|
184
|
+
if reg.drops then
|
|
185
|
+
reg.drops += 1
|
|
186
|
+
end
|
|
178
187
|
fireDrop(player, reason or "validate", reg.name, value)
|
|
179
188
|
return false
|
|
180
189
|
end
|
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
-- Deserializes incoming buffers and routes by registration kind.
|
|
4
4
|
|
|
5
5
|
local Baseline = require(script.Parent.Parent.internal.Baseline)
|
|
6
|
+
local Channel = require(script.Parent.Parent.internal.Channel)
|
|
6
7
|
local Gate = require(script.Parent.Gate)
|
|
7
8
|
local Middleware = require(script.Parent.Parent.internal.Middleware)
|
|
8
9
|
local Registry = require(script.Parent.Parent.internal.Registry)
|
|
10
|
+
local Varint = require(script.Parent.Parent.codec.primitive.Varint)
|
|
9
11
|
|
|
10
12
|
local registryGet = Registry.get
|
|
11
13
|
local gateCheck = Gate.check
|
|
@@ -16,8 +18,14 @@ local gateReportDrop = Gate.reportDrop
|
|
|
16
18
|
local KIND_PACKET = Registry.KIND_PACKET
|
|
17
19
|
local KIND_REQUEST = Registry.KIND_REQUEST
|
|
18
20
|
|
|
21
|
+
local TS_FRAME = Channel.TS_FRAME
|
|
22
|
+
local TS_OFFSET = Channel.TS_OFFSET
|
|
23
|
+
local TS_FULL = Channel.TS_FULL
|
|
24
|
+
|
|
19
25
|
local MAX_INCOMING = 65536
|
|
20
26
|
|
|
27
|
+
local floor = math.floor
|
|
28
|
+
|
|
21
29
|
-- Public --------------------------------------------------------------
|
|
22
30
|
|
|
23
31
|
local Reader = {}
|
|
@@ -47,9 +55,9 @@ function Reader.process(
|
|
|
47
55
|
Baseline.setReadKey(player or false)
|
|
48
56
|
end
|
|
49
57
|
|
|
50
|
-
-- Cache flags once per frame-buffer, not per packet
|
|
51
58
|
local hasReceiveMiddleware = Middleware.hasReceive
|
|
52
59
|
local isServer = player ~= nil
|
|
60
|
+
local statsOn = Channel.statsEnabled()
|
|
53
61
|
|
|
54
62
|
while pos < length do
|
|
55
63
|
local id = buffer.readu8(incoming, pos)
|
|
@@ -80,21 +88,62 @@ function Reader.process(
|
|
|
80
88
|
local batchStart = pos
|
|
81
89
|
local maxBytes = reg.maxPayloadBytes
|
|
82
90
|
|
|
91
|
+
-- Timestamp is per-batch, read once after count
|
|
92
|
+
local timestamp: number? = nil
|
|
93
|
+
local tsMode = reg.timestampMode
|
|
94
|
+
if tsMode == TS_FRAME then
|
|
95
|
+
timestamp = buffer.readu8(incoming, pos)
|
|
96
|
+
pos += 1
|
|
97
|
+
elseif tsMode == TS_OFFSET then
|
|
98
|
+
timestamp = buffer.readu16(incoming, pos)
|
|
99
|
+
pos += 2
|
|
100
|
+
elseif tsMode == TS_FULL then
|
|
101
|
+
timestamp = buffer.readf64(incoming, pos)
|
|
102
|
+
pos += 8
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
-- Cap count for fixed-size codecs to prevent wasted iterations
|
|
106
|
+
local fixedSize = (codec :: any)._size
|
|
107
|
+
if fixedSize and fixedSize > 0 then
|
|
108
|
+
local maxItems = floor((length - pos) / fixedSize)
|
|
109
|
+
if count > maxItems then
|
|
110
|
+
count = maxItems
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
83
114
|
-- Hot path: no gate, no middleware, no byte budget
|
|
84
115
|
if not (isServer and reg.needsGate) and not hasReceiveMiddleware and not maxBytes then
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
116
|
+
if statsOn then
|
|
117
|
+
local batchBytes = 0
|
|
118
|
+
for _ = 1, count do
|
|
119
|
+
if pos >= length then
|
|
120
|
+
break
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
local value, consumed = codec.read(incoming, pos, refs)
|
|
124
|
+
pos += consumed
|
|
125
|
+
batchBytes += consumed
|
|
126
|
+
signal:fire(value, player, timestamp)
|
|
88
127
|
end
|
|
128
|
+
reg.bytesReceived += batchBytes
|
|
129
|
+
reg.recvFires += count
|
|
130
|
+
else
|
|
131
|
+
for _ = 1, count do
|
|
132
|
+
if pos >= length then
|
|
133
|
+
break
|
|
134
|
+
end
|
|
89
135
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
136
|
+
local value, consumed = codec.read(incoming, pos, refs)
|
|
137
|
+
pos += consumed
|
|
138
|
+
signal:fire(value, player, timestamp)
|
|
139
|
+
end
|
|
93
140
|
end
|
|
94
141
|
else
|
|
95
142
|
-- Cold path: gate and/or middleware and/or byte budget active
|
|
96
143
|
local baseName = reg.baseName
|
|
97
144
|
local needsGate = isServer and reg.needsGate
|
|
145
|
+
local batchBytes = if statsOn then 0 else nil
|
|
146
|
+
local recvCount = if statsOn then 0 else nil
|
|
98
147
|
|
|
99
148
|
for _ = 1, count do
|
|
100
149
|
if pos >= length then
|
|
@@ -109,6 +158,11 @@ function Reader.process(
|
|
|
109
158
|
local value, consumed = codec.read(incoming, pos, refs)
|
|
110
159
|
pos += consumed
|
|
111
160
|
|
|
161
|
+
if batchBytes then
|
|
162
|
+
batchBytes += consumed
|
|
163
|
+
recvCount += 1
|
|
164
|
+
end
|
|
165
|
+
|
|
112
166
|
if needsGate and not gateCheck(value, reg, player :: Player) then
|
|
113
167
|
continue
|
|
114
168
|
end
|
|
@@ -118,31 +172,32 @@ function Reader.process(
|
|
|
118
172
|
if final == nil then
|
|
119
173
|
continue
|
|
120
174
|
end
|
|
121
|
-
signal:fire(final, player)
|
|
175
|
+
signal:fire(final, player, timestamp)
|
|
122
176
|
else
|
|
123
|
-
signal:fire(value, player)
|
|
177
|
+
signal:fire(value, player, timestamp)
|
|
124
178
|
end
|
|
125
179
|
end
|
|
180
|
+
if batchBytes then
|
|
181
|
+
reg.bytesReceived += batchBytes
|
|
182
|
+
reg.recvFires += recvCount :: number
|
|
183
|
+
end
|
|
126
184
|
end
|
|
127
185
|
else
|
|
128
|
-
-- Query frame: id(u8, already read) + correlationId(
|
|
129
|
-
if pos +
|
|
186
|
+
-- Query frame: id(u8, already read) + correlationId(varint) + status(u8)
|
|
187
|
+
if pos + 2 > length then
|
|
130
188
|
break
|
|
131
189
|
end
|
|
132
190
|
|
|
133
191
|
local queryCodec = reg.codec
|
|
134
192
|
local querySignal = reg.signal
|
|
135
193
|
|
|
136
|
-
local correlationId =
|
|
137
|
-
|
|
138
|
-
|
|
194
|
+
local correlationId, corrBytes = Varint.read(incoming, pos)
|
|
195
|
+
pos += corrBytes
|
|
196
|
+
local status = buffer.readu8(incoming, pos)
|
|
197
|
+
pos += 1
|
|
139
198
|
|
|
140
199
|
if status == 1 then
|
|
141
|
-
|
|
142
|
-
querySignal:fire(nil, player, correlationId)
|
|
143
|
-
else
|
|
144
|
-
querySignal:fire(nil, player, correlationId)
|
|
145
|
-
end
|
|
200
|
+
querySignal:fire(nil, player, correlationId)
|
|
146
201
|
continue
|
|
147
202
|
end
|
|
148
203
|
|
|
@@ -163,11 +218,7 @@ function Reader.process(
|
|
|
163
218
|
final = value
|
|
164
219
|
end
|
|
165
220
|
|
|
166
|
-
|
|
167
|
-
querySignal:fire(final, player, correlationId)
|
|
168
|
-
else
|
|
169
|
-
querySignal:fire(final, player, correlationId)
|
|
170
|
-
end
|
|
221
|
+
querySignal:fire(final, player, correlationId)
|
|
171
222
|
end
|
|
172
223
|
end
|
|
173
224
|
end
|