@axpecter/lync 1.5.1 → 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.
@@ -17,23 +17,35 @@ local bxor = bit32.bxor
17
17
  local band32 = bit32.band
18
18
  local countlz = bit32.countlz
19
19
  local lshift = bit32.lshift
20
+ local bor = bit32.bor
21
+ local rshift = bit32.rshift
22
+
23
+ local TS_NONE = 0
24
+ local TS_FRAME = 1
25
+ local TS_OFFSET = 2
26
+ local TS_FULL = 3
20
27
 
21
28
  -- State ---------------------------------------------------------------
22
29
 
23
30
  local _maxSize = MAX_SIZE
24
-
25
- -- Packet name for overflow diagnostics. Not coroutine-safe: concurrent
26
- -- serializers may overwrite before the error fires.
27
31
  local _currentPacket = ""
28
32
 
33
+ -- Timestamp state updated once per flush via updateTimestamp().
34
+ local _frameCounter: number = 0
35
+ local _offsetMs: number = 0
36
+ local _clockTime: number = 0
37
+ local _anyTimestamp = false
38
+
39
+ -- Stats gating. Default off; set via Lync.enableStats().
40
+ local _statsEnabled = false
41
+
29
42
  -- Public --------------------------------------------------------------
30
43
 
31
44
  local Channel = {}
32
45
 
33
- -- Override the maximum buffer size. Call before start(). Range: 4096–1048576.
34
46
  function Channel.setMaxSize(bytes: number): ()
35
47
  if bytes < 4096 or bytes > 1048576 then
36
- error(`[Lync] Channel max size must be 40961048576, got {bytes}`)
48
+ error(`[Lync] Channel max size must be 4096-1048576, got {bytes}`)
37
49
  end
38
50
  _maxSize = bytes
39
51
  end
@@ -56,7 +68,6 @@ function Channel.create(): ChannelState
56
68
  }
57
69
  end
58
70
 
59
- -- Grows the channel buffer to fit the requested bytes. Power-of-two sizing.
60
71
  function Channel.alloc(ch: ChannelState, bytes: number): ()
61
72
  local needed = ch.cursor + bytes
62
73
  if needed <= ch.size then
@@ -95,52 +106,126 @@ function Channel.sealAndDump(ch: ChannelState): (buffer, { Instance })
95
106
  return out, if #refs > 0 then table.clone(refs) else {}
96
107
  end
97
108
 
98
- -- Sets the packet name for overflow diagnostics.
99
109
  function Channel.setCurrentPacket(name: string): ()
100
110
  _currentPacket = name
101
111
  end
102
112
 
103
- -- Writes a single value into the batch. Opens a new header on ID change or overflow.
104
- function Channel.writeBatch(ch: ChannelState, id: number, name: string, codec: any, data: any): ()
105
- if id ~= ch.lastId or ch.itemCount >= MAX_BATCH then
106
- if ch.lastId >= 0 then
107
- buffer.writeu16(ch.buff, ch.countPos, ch.itemCount)
108
- end
113
+ function Channel.updateTimestamp(frameCounter: number, offsetMs: number, clockTime: number): ()
114
+ _frameCounter = frameCounter
115
+ _offsetMs = offsetMs
116
+ _clockTime = clockTime
117
+ end
109
118
 
110
- _currentPacket = name
111
- alloc(ch, 3)
112
- local c = ch.cursor
113
- buffer.writeu8(ch.buff, c, id)
114
- ch.countPos = c + 1
115
- ch.cursor = c + 3
116
- ch.lastId = id
117
- ch.itemCount = 0
119
+ function Channel.enableTimestamps(): ()
120
+ _anyTimestamp = true
121
+ end
122
+
123
+ function Channel.hasTimestamps(): boolean
124
+ return _anyTimestamp
125
+ end
126
+
127
+ function Channel.enableStats(): ()
128
+ _statsEnabled = true
129
+ end
130
+
131
+ function Channel.statsEnabled(): boolean
132
+ return _statsEnabled
133
+ end
134
+
135
+ -- Batch opener: no timestamp variant. Resolved at define time via reg._openFn (#2)
136
+ local function openBatchPlain(ch: ChannelState, id: number, name: string): ()
137
+ if ch.lastId >= 0 then
138
+ buffer.writeu16(ch.buff, ch.countPos, ch.itemCount)
139
+ end
140
+ _currentPacket = name
141
+ alloc(ch, 3)
142
+ local c = ch.cursor
143
+ buffer.writeu8(ch.buff, c, id)
144
+ ch.countPos = c + 1
145
+ ch.cursor = c + 3
146
+ ch.lastId = id
147
+ ch.itemCount = 0
148
+ end
149
+
150
+ local function openBatchFrame(ch: ChannelState, id: number, name: string): ()
151
+ if ch.lastId >= 0 then
152
+ buffer.writeu16(ch.buff, ch.countPos, ch.itemCount)
153
+ end
154
+ _currentPacket = name
155
+ alloc(ch, 4)
156
+ local c = ch.cursor
157
+ buffer.writeu8(ch.buff, c, id)
158
+ ch.countPos = c + 1
159
+ buffer.writeu8(ch.buff, c + 3, _frameCounter)
160
+ ch.cursor = c + 4
161
+ ch.lastId = id
162
+ ch.itemCount = 0
163
+ end
164
+
165
+ local function openBatchOffset(ch: ChannelState, id: number, name: string): ()
166
+ if ch.lastId >= 0 then
167
+ buffer.writeu16(ch.buff, ch.countPos, ch.itemCount)
168
+ end
169
+ _currentPacket = name
170
+ alloc(ch, 5)
171
+ local c = ch.cursor
172
+ buffer.writeu8(ch.buff, c, id)
173
+ ch.countPos = c + 1
174
+ buffer.writeu16(ch.buff, c + 3, _offsetMs)
175
+ ch.cursor = c + 5
176
+ ch.lastId = id
177
+ ch.itemCount = 0
178
+ end
179
+
180
+ local function openBatchFull(ch: ChannelState, id: number, name: string): ()
181
+ if ch.lastId >= 0 then
182
+ buffer.writeu16(ch.buff, ch.countPos, ch.itemCount)
183
+ end
184
+ _currentPacket = name
185
+ alloc(ch, 11)
186
+ local c = ch.cursor
187
+ buffer.writeu8(ch.buff, c, id)
188
+ ch.countPos = c + 1
189
+ buffer.writef64(ch.buff, c + 3, _clockTime)
190
+ ch.cursor = c + 11
191
+ ch.lastId = id
192
+ ch.itemCount = 0
193
+ end
194
+
195
+ -- Returns the correct openBatch function for a given timestamp mode.
196
+ function Channel.resolveOpenFn(tsMode: number): (ch: ChannelState, id: number, name: string) -> ()
197
+ if tsMode == TS_FRAME then
198
+ return openBatchFrame
199
+ elseif tsMode == TS_OFFSET then
200
+ return openBatchOffset
201
+ elseif tsMode == TS_FULL then
202
+ return openBatchFull
203
+ end
204
+ return openBatchPlain
205
+ end
206
+
207
+ -- Writes a single value into the batch. Zero branching on the hot path.
208
+ function Channel.writeBatch(ch: ChannelState, reg: any, data: any): ()
209
+ local id = reg.id
210
+ if id ~= ch.lastId or ch.itemCount >= MAX_BATCH then
211
+ reg._openFn(ch, id, reg.name)
118
212
  end
119
213
 
120
214
  ch.itemCount += 1
121
- codec.write(ch, data)
215
+ reg.codec.write(ch, data)
122
216
  end
123
217
 
124
218
  -- Appends pre-serialized bytes into a batch. Used by broadcast fan-out.
125
219
  function Channel.writeBatchRaw(
126
220
  ch: ChannelState,
127
- id: number,
221
+ reg: any,
128
222
  src: buffer,
129
223
  srcLen: number,
130
224
  srcRefs: { Instance }?
131
225
  ): ()
226
+ local id = reg.id
132
227
  if id ~= ch.lastId or ch.itemCount >= MAX_BATCH then
133
- if ch.lastId >= 0 then
134
- buffer.writeu16(ch.buff, ch.countPos, ch.itemCount)
135
- end
136
-
137
- alloc(ch, 3)
138
- local c = ch.cursor
139
- buffer.writeu8(ch.buff, c, id)
140
- ch.countPos = c + 1
141
- ch.cursor = c + 3
142
- ch.lastId = id
143
- ch.itemCount = 0
228
+ reg._openFn(ch, id, reg.name)
144
229
  end
145
230
 
146
231
  ch.itemCount += 1
@@ -168,7 +253,7 @@ end
168
253
 
169
254
  --[[
170
255
  Writes a query frame. Seals any open batch, then writes:
171
- id(u8) + correlationId(u16) + status(u8)
256
+ id(u8) + correlationId(varint) + status(u8)
172
257
  Status 0 = payload follows. Status 1 = nil response.
173
258
  ]]
174
259
  function Channel.writeQuery(
@@ -182,18 +267,39 @@ function Channel.writeQuery(
182
267
  _currentPacket = name
183
268
  sealOpen(ch)
184
269
 
185
- alloc(ch, 4)
270
+ alloc(ch, 2)
186
271
  local c = ch.cursor
187
272
  buffer.writeu8(ch.buff, c, id)
188
- buffer.writeu16(ch.buff, c + 1, correlationId)
273
+ ch.cursor = c + 1
274
+
275
+ -- Inline varint write to avoid circular dependency (Varint requires Channel)
276
+ local v = correlationId
277
+ if v < 0x80 then
278
+ alloc(ch, 1)
279
+ buffer.writeu8(ch.buff, ch.cursor, v)
280
+ ch.cursor += 1
281
+ else
282
+ alloc(ch, 5)
283
+ local b = ch.buff
284
+ local vc = ch.cursor
285
+ while v >= 0x80 do
286
+ buffer.writeu8(b, vc, bor(band32(v, 0x7F), 0x80))
287
+ v = rshift(v, 7)
288
+ vc += 1
289
+ end
290
+ buffer.writeu8(b, vc, v)
291
+ ch.cursor = vc + 1
292
+ end
189
293
 
294
+ local c2 = ch.cursor
295
+ alloc(ch, 1)
190
296
  if codec then
191
- buffer.writeu8(ch.buff, c + 3, 0)
192
- ch.cursor = c + 4
297
+ buffer.writeu8(ch.buff, c2, 0)
298
+ ch.cursor = c2 + 1
193
299
  codec.write(ch, data)
194
300
  else
195
- buffer.writeu8(ch.buff, c + 3, 1)
196
- ch.cursor = c + 4
301
+ buffer.writeu8(ch.buff, c2, 1)
302
+ ch.cursor = c2 + 1
197
303
  end
198
304
  end
199
305
 
@@ -229,12 +335,36 @@ function Channel.xorApply(current: buffer, previous: buffer?): buffer
229
335
  return result
230
336
  end
231
337
 
338
+ -- Conditional refs clear (#6)
232
339
  function Channel.reset(ch: ChannelState): ()
233
340
  ch.cursor = 0
234
341
  ch.lastId = -1
235
342
  ch.countPos = 0
236
343
  ch.itemCount = 0
237
- table.clear(ch.refs)
344
+ if #ch.refs > 0 then
345
+ table.clear(ch.refs)
346
+ end
347
+ end
348
+
349
+ -- Test helper: builds a minimal reg-like object for writeBatch.
350
+ function Channel.mockReg(id: number, name: string, codec: any): any
351
+ return {
352
+ id = id,
353
+ name = name,
354
+ codec = codec,
355
+ timestampMode = 0,
356
+ _openFn = openBatchPlain,
357
+ drops = 0,
358
+ bytesSent = 0,
359
+ bytesReceived = 0,
360
+ fires = 0,
361
+ recvFires = 0,
362
+ }
238
363
  end
239
364
 
365
+ Channel.TS_NONE = TS_NONE
366
+ Channel.TS_FRAME = TS_FRAME
367
+ Channel.TS_OFFSET = TS_OFFSET
368
+ Channel.TS_FULL = TS_FULL
369
+
240
370
  return table.freeze(Channel)
@@ -2,6 +2,7 @@
2
2
  --!optimize 2
3
3
  -- Deterministic ID assignment for packets and queries.
4
4
 
5
+ local Channel = require(script.Parent.Channel)
5
6
  local Types = require(script.Parent.Parent.Types)
6
7
 
7
8
  type Codec<T> = Types.Codec<T>
@@ -32,7 +33,8 @@ local function assign(
32
33
  signal: any,
33
34
  rateLimit: RateLimitConfig?,
34
35
  validate: ((data: any, player: Player) -> (boolean, string?))?,
35
- maxPayloadBytes: number?
36
+ maxPayloadBytes: number?,
37
+ timestampMode: number?
36
38
  ): Registration
37
39
  if _names[name] then
38
40
  error(`[Lync] Duplicate registration: "{name}"`)
@@ -70,6 +72,13 @@ local function assign(
70
72
  validate = validate,
71
73
  needsGate = rateLimit ~= nil or validate ~= nil,
72
74
  maxPayloadBytes = maxPayloadBytes,
75
+ timestampMode = timestampMode or 0,
76
+ _openFn = Channel.resolveOpenFn(timestampMode or 0),
77
+ bytesSent = 0,
78
+ bytesReceived = 0,
79
+ fires = 0,
80
+ recvFires = 0,
81
+ drops = 0,
73
82
  }
74
83
 
75
84
  _entries[id] = reg
@@ -92,7 +101,8 @@ function Registry.register(
92
101
  signal: any,
93
102
  rateLimit: RateLimitConfig?,
94
103
  validate: ((data: any, player: Player) -> (boolean, string?))?,
95
- maxPayloadBytes: number?
104
+ maxPayloadBytes: number?,
105
+ timestampMode: number?
96
106
  ): Registration
97
107
  return assign(
98
108
  name,
@@ -103,7 +113,8 @@ function Registry.register(
103
113
  signal,
104
114
  rateLimit,
105
115
  validate,
106
- maxPayloadBytes
116
+ maxPayloadBytes,
117
+ timestampMode
107
118
  )
108
119
  end
109
120
 
@@ -143,4 +154,10 @@ function Registry.getByName(name: string): Registration?
143
154
  return if id then _entries[id] else nil
144
155
  end
145
156
 
157
+ function Registry.forEach(fn: (reg: Registration) -> ()): ()
158
+ for _, reg in _entries do
159
+ fn(reg)
160
+ end
161
+ end
162
+
146
163
  return table.freeze(Registry)
@@ -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
- RunService.Heartbeat:Connect(flush)
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.writeBatch(ch, id, name, codec, final)
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)
@@ -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
- for _ = 1, count do
86
- if pos >= length then
87
- break
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
- local value, consumed = codec.read(incoming, pos, refs)
91
- pos += consumed
92
- signal:fire(value, player)
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(u16) + status(u8) = 3 more bytes
129
- if pos + 3 > length then
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 = buffer.readu16(incoming, pos)
137
- local status = buffer.readu8(incoming, pos + 2)
138
- pos += 3
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
- if kind == KIND_REQUEST then
142
- querySignal:fire(nil, player, correlationId)
143
- else
144
- querySignal:fire(nil, 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
- if kind == KIND_REQUEST then
167
- querySignal:fire(final, player, correlationId)
168
- else
169
- querySignal:fire(final, correlationId)
170
- end
221
+ querySignal:fire(final, player, correlationId)
171
222
  end
172
223
  end
173
224
  end