@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.
@@ -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, 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
- if kind == KIND_REQUEST then
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