@axpecter/lync 1.5.2 → 2.1.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.
Files changed (55) hide show
  1. package/README.md +499 -357
  2. package/package.json +2 -2
  3. package/src/Types.luau +69 -27
  4. package/src/api/Group.luau +101 -106
  5. package/src/api/Packet.luau +191 -157
  6. package/src/api/Query.luau +223 -282
  7. package/src/api/Scope.luau +46 -57
  8. package/src/api/Signal.luau +104 -137
  9. package/src/codec/Base.luau +69 -20
  10. package/src/codec/composite/Array.luau +189 -201
  11. package/src/codec/composite/Map.luau +97 -341
  12. package/src/codec/composite/Optional.luau +27 -23
  13. package/src/codec/composite/Shared.luau +96 -65
  14. package/src/codec/composite/Struct.luau +201 -339
  15. package/src/codec/composite/Tagged.luau +64 -178
  16. package/src/codec/composite/Tuple.luau +81 -95
  17. package/src/codec/datatype/Buffer.luau +49 -21
  18. package/src/codec/datatype/CFrame.luau +207 -30
  19. package/src/codec/datatype/Color.luau +21 -11
  20. package/src/codec/datatype/Instance.luau +29 -19
  21. package/src/codec/datatype/IntVector.luau +33 -21
  22. package/src/codec/datatype/NumberRange.luau +19 -10
  23. package/src/codec/datatype/Ray.luau +26 -22
  24. package/src/codec/datatype/Rect.luau +20 -16
  25. package/src/codec/datatype/Region.luau +51 -45
  26. package/src/codec/datatype/Sequence.luau +64 -56
  27. package/src/codec/datatype/String.luau +89 -56
  28. package/src/codec/datatype/UDim.luau +40 -22
  29. package/src/codec/datatype/Vector.luau +181 -17
  30. package/src/codec/meta/Auto.luau +295 -253
  31. package/src/codec/meta/Bitfield.luau +104 -148
  32. package/src/codec/meta/Custom.luau +8 -4
  33. package/src/codec/meta/Enum.luau +29 -57
  34. package/src/codec/meta/Float.luau +64 -0
  35. package/src/codec/meta/Nothing.luau +9 -5
  36. package/src/codec/meta/Unknown.luau +44 -35
  37. package/src/codec/primitive/Bool.luau +16 -26
  38. package/src/codec/primitive/Float16.luau +58 -64
  39. package/src/codec/primitive/Int.luau +68 -0
  40. package/src/codec/primitive/Number.luau +21 -43
  41. package/src/codec/primitive/Varint.luau +67 -46
  42. package/src/index.d.ts +249 -306
  43. package/src/init.luau +332 -173
  44. package/src/internal/Baseline.luau +29 -24
  45. package/src/internal/Channel.luau +346 -161
  46. package/src/internal/Middleware.luau +60 -85
  47. package/src/internal/Pool.luau +19 -30
  48. package/src/internal/Registry.luau +61 -101
  49. package/src/transport/Bridge.luau +64 -43
  50. package/src/transport/Client.luau +60 -117
  51. package/src/transport/Gate.luau +282 -133
  52. package/src/transport/Reader.luau +159 -100
  53. package/src/transport/Server.luau +147 -292
  54. package/src/api/Namespace.luau +0 -251
  55. package/src/codec/meta/Quantized.luau +0 -174
@@ -1,227 +1,301 @@
1
1
  --!strict
2
2
  --!native
3
- -- Per-channel buffer state with batching, delta cache, frame XOR.
3
+ -- Per-channel buffer state with MSB batch framing, XOR, timestamps.
4
4
 
5
5
  local Types = require(script.Parent.Parent.Types)
6
6
 
7
- type ChannelState = Types.ChannelState
8
-
9
7
  -- Constants -----------------------------------------------------------
10
8
 
11
- local INITIAL_SIZE = 1024
12
- local MAX_SIZE = 262144
13
- local MAX_BATCH = 65535
9
+ local TS_NONE = 0
10
+ local TS_FRAME = 1
11
+ local TS_OFFSET = 2
12
+ local TS_FULL = 3
14
13
 
15
- local min = math.min
16
- local bxor = bit32.bxor
17
- local band32 = bit32.band
18
- local countlz = bit32.countlz
19
- local lshift = bit32.lshift
14
+ local DEFAULT_MAX = 262144
15
+ local EMPTY_REFS = table.freeze({})
20
16
 
21
17
  -- State ---------------------------------------------------------------
22
18
 
23
- local _maxSize = MAX_SIZE
24
-
25
- -- Packet name for overflow diagnostics. Not coroutine-safe: concurrent
26
- -- serializers may overwrite before the error fires.
27
- local _currentPacket = ""
28
-
29
- -- Public --------------------------------------------------------------
30
-
31
- local Channel = {}
32
-
33
- -- Override the maximum buffer size. Call before start(). Range: 4096–1048576.
34
- function Channel.setMaxSize(bytes: number): ()
35
- if bytes < 4096 or bytes > 1048576 then
36
- error(`[Lync] Channel max size must be 4096–1048576, got {bytes}`)
37
- end
38
- _maxSize = bytes
39
- end
19
+ local _maxSize = DEFAULT_MAX
20
+ local _anyTimestamp = false
21
+ local _statsEnabled = false
22
+ local _frameCounter = 0
23
+ local _offsetMs = 0
24
+ local _clock = 0.0
40
25
 
41
- function Channel.getMaxSize(): number
42
- return _maxSize
43
- end
26
+ -- Private -------------------------------------------------------------
44
27
 
45
- function Channel.create(): ChannelState
46
- return {
47
- buff = buffer.create(INITIAL_SIZE),
48
- cursor = 0,
49
- size = INITIAL_SIZE,
50
- refs = {},
51
- lastId = -1,
52
- countPos = 0,
53
- itemCount = 0,
54
- deltas = {},
55
- prevDump = nil,
56
- }
57
- end
28
+ local countlz = bit32.countlz
29
+ local lshift = bit32.lshift
30
+ local bxor = bit32.bxor
31
+ local bor = bit32.bor
32
+ local band = bit32.band
33
+ local writeu8 = buffer.writeu8
34
+ local writeu16 = buffer.writeu16
35
+ local writef64 = buffer.writef64
36
+ local readu8 = buffer.readu8
37
+ local readu32 = buffer.readu32
38
+ local round = math.round
39
+ local min = math.min
58
40
 
59
- -- Grows the channel buffer to fit the requested bytes. Power-of-two sizing.
60
- function Channel.alloc(ch: ChannelState, bytes: number): ()
41
+ local function alloc(ch: Types.ChannelState, bytes: number): ()
61
42
  local needed = ch.cursor + bytes
62
43
  if needed <= ch.size then
63
44
  return
64
45
  end
65
46
  if needed > _maxSize then
66
47
  error(
67
- `[Lync] Channel overflow writing "{_currentPacket}": {needed} bytes > {_maxSize} max(call Channel.setMaxSize to raise)`
48
+ if ch.currentPacket
49
+ then `[Lync] Channel: buffer overflow ({needed}B, max {_maxSize}B) in "{ch.currentPacket}"`
50
+ else `[Lync] Channel: buffer overflow ({needed}B, max {_maxSize}B)`
68
51
  )
69
52
  end
70
-
71
53
  local newSize = lshift(1, 32 - countlz(needed - 1))
72
-
73
54
  local newBuff = buffer.create(newSize)
74
55
  buffer.copy(newBuff, 0, ch.buff, 0, ch.cursor)
75
56
  ch.buff = newBuff
76
57
  ch.size = newSize
77
58
  end
78
59
 
79
- local alloc = Channel.alloc
60
+ -- Public --------------------------------------------------------------
80
61
 
81
- -- Seals the open batch header and returns a snapshot of the buffer + refs.
82
- function Channel.sealAndDump(ch: ChannelState): (buffer, { Instance })
83
- if ch.lastId >= 0 then
84
- buffer.writeu16(ch.buff, ch.countPos, ch.itemCount)
85
- ch.lastId = -1
86
- ch.countPos = 0
87
- ch.itemCount = 0
88
- end
62
+ local Channel = {}
89
63
 
90
- local cur = ch.cursor
91
- local out = buffer.create(cur)
92
- buffer.copy(out, 0, ch.buff, 0, cur)
64
+ Channel.TS_NONE = TS_NONE
65
+ Channel.TS_FRAME = TS_FRAME
66
+ Channel.TS_OFFSET = TS_OFFSET
67
+ Channel.TS_FULL = TS_FULL
93
68
 
94
- local refs = ch.refs
95
- return out, if #refs > 0 then table.clone(refs) else {}
69
+ function Channel.create(): Types.ChannelState
70
+ return {
71
+ buff = buffer.create(1024),
72
+ cursor = 0,
73
+ size = 1024,
74
+ refs = {},
75
+ refCount = 0,
76
+ lastId = -1,
77
+ countPos = -1,
78
+ itemCount = 0,
79
+ singleMode = false,
80
+ singlePos = 0,
81
+ deltas = {},
82
+ prevDump = nil,
83
+ prevDumpLen = 0,
84
+ currentPacket = nil,
85
+ }
96
86
  end
97
87
 
98
- -- Sets the packet name for overflow diagnostics.
99
- function Channel.setCurrentPacket(name: string): ()
100
- _currentPacket = name
88
+ function Channel.alloc(ch: Types.ChannelState, bytes: number): ()
89
+ alloc(ch, bytes)
101
90
  end
102
91
 
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
109
-
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
92
+ function Channel.reset(ch: Types.ChannelState): ()
93
+ ch.cursor = 0
94
+ ch.lastId = -1
95
+ ch.countPos = -1
96
+ ch.itemCount = 0
97
+ ch.singleMode = false
98
+ ch.singlePos = 0
99
+ ch.currentPacket = nil
100
+ if ch.refCount > 0 then
101
+ table.clear(ch.refs)
102
+ ch.refCount = 0
118
103
  end
119
-
120
- ch.itemCount += 1
121
- codec.write(ch, data)
122
104
  end
123
105
 
124
- -- Appends pre-serialized bytes into a batch. Used by broadcast fan-out.
125
- function Channel.writeBatchRaw(
126
- ch: ChannelState,
127
- id: number,
128
- src: buffer,
129
- srcLen: number,
130
- srcRefs: { Instance }?
131
- ): ()
132
- 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)
106
+ --[[
107
+ Resolves the batch-open function for a given timestamp mode.
108
+ The returned function handles MSB single-item framing:
109
+ First item: writes [0x80 | id] (single mode, no count).
110
+ Second item: upgrades to multi mode with u16 count.
111
+ ]]
112
+ function Channel.resolveOpenFn(
113
+ timestampMode: number,
114
+ name: string
115
+ ): (ch: Types.ChannelState, id: number) -> ()
116
+ if timestampMode == TS_NONE then
117
+ return function(ch: Types.ChannelState, id: number): ()
118
+ -- Seal previous batch
119
+ if ch.lastId >= 0 and not ch.singleMode then
120
+ writeu16(ch.buff, ch.countPos, ch.itemCount)
121
+ end
122
+ ch.currentPacket = name
123
+ ch.lastId = id
124
+ ch.itemCount = 0
125
+
126
+ -- Write single-mode header: [1IIIIIII]
127
+ local cursor = ch.cursor
128
+ if cursor + 1 > ch.size then
129
+ alloc(ch, 1)
130
+ end
131
+ writeu8(ch.buff, cursor, bor(0x80, id))
132
+ ch.singleMode = true
133
+ ch.singlePos = cursor
134
+ ch.cursor = cursor + 1
135
+ end
136
+ elseif timestampMode == TS_FRAME then
137
+ return function(ch: Types.ChannelState, id: number): ()
138
+ if ch.lastId >= 0 and not ch.singleMode then
139
+ writeu16(ch.buff, ch.countPos, ch.itemCount)
140
+ end
141
+ ch.currentPacket = name
142
+ ch.lastId = id
143
+ ch.itemCount = 0
144
+
145
+ local cursor = ch.cursor
146
+ if cursor + 2 > ch.size then
147
+ alloc(ch, 2)
148
+ end
149
+ local b = ch.buff
150
+ writeu8(b, cursor, bor(0x80, id))
151
+ writeu8(b, cursor + 1, _frameCounter)
152
+ ch.singleMode = true
153
+ ch.singlePos = cursor
154
+ ch.cursor = cursor + 2
155
+ end
156
+ elseif timestampMode == TS_OFFSET then
157
+ return function(ch: Types.ChannelState, id: number): ()
158
+ if ch.lastId >= 0 and not ch.singleMode then
159
+ writeu16(ch.buff, ch.countPos, ch.itemCount)
160
+ end
161
+ ch.currentPacket = name
162
+ ch.lastId = id
163
+ ch.itemCount = 0
164
+
165
+ local cursor = ch.cursor
166
+ if cursor + 3 > ch.size then
167
+ alloc(ch, 3)
168
+ end
169
+ local b = ch.buff
170
+ writeu8(b, cursor, bor(0x80, id))
171
+ writeu16(b, cursor + 1, _offsetMs)
172
+ ch.singleMode = true
173
+ ch.singlePos = cursor
174
+ ch.cursor = cursor + 3
175
+ end
176
+ else -- TS_FULL
177
+ return function(ch: Types.ChannelState, id: number): ()
178
+ if ch.lastId >= 0 and not ch.singleMode then
179
+ writeu16(ch.buff, ch.countPos, ch.itemCount)
180
+ end
181
+ ch.currentPacket = name
182
+ ch.lastId = id
183
+ ch.itemCount = 0
184
+
185
+ local cursor = ch.cursor
186
+ if cursor + 9 > ch.size then
187
+ alloc(ch, 9)
188
+ end
189
+ local b = ch.buff
190
+ writeu8(b, cursor, bor(0x80, id))
191
+ writef64(b, cursor + 1, _clock)
192
+ ch.singleMode = true
193
+ ch.singlePos = cursor
194
+ ch.cursor = cursor + 9
135
195
  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
144
196
  end
197
+ end
145
198
 
146
- ch.itemCount += 1
199
+ --[[
200
+ Writes one item into a batch. Handles MSB single→multi upgrade.
201
+ ]]
202
+ function Channel.writeBatch(ch: Types.ChannelState, reg: Types.Registration, data: any): ()
203
+ if reg.id ~= ch.lastId or ch.itemCount >= 0xFFFF then
204
+ reg._openFn(ch, reg.id)
205
+ elseif ch.singleMode then
206
+ --[[
207
+ Second item for same packet: upgrade to multi mode.
208
+ Rewrite header byte to clear MSB, insert u16 count after.
209
+ ]]
210
+ local b = ch.buff
211
+ local headerPos = ch.singlePos
212
+ writeu8(b, headerPos, reg.id) -- MSB cleared = multi-item mode
213
+
214
+ local tsBytes = reg.headerSize - 1
215
+ local afterTs = headerPos + 1 + tsBytes
216
+
217
+ --[[
218
+ Upgrading from single→multi requires inserting 2 bytes (u16 count)
219
+ between the timestamp and the first item's payload, shifting
220
+ existing data right by 2.
221
+ ]]
222
+ local cursor = ch.cursor
223
+ local payloadLen = cursor - afterTs
224
+ if cursor + 2 > ch.size then
225
+ alloc(ch, 2)
226
+ end
227
+ local b2 = ch.buff
147
228
 
148
- if srcLen > 0 then
149
- alloc(ch, srcLen)
150
- buffer.copy(ch.buff, ch.cursor, src, 0, srcLen)
151
- ch.cursor += srcLen
152
- end
229
+ if payloadLen > 0 then
230
+ buffer.copy(b2, afterTs + 2, b2, afterTs, payloadLen)
231
+ end
153
232
 
154
- if srcRefs then
155
- local refs = ch.refs
156
- table.move(srcRefs, 1, #srcRefs, #refs + 1, refs)
233
+ ch.countPos = afterTs
234
+ writeu16(b2, afterTs, 0) -- placeholder u16; sealAndDump writes final count
235
+ ch.cursor = cursor + 2
236
+ ch.singleMode = false
157
237
  end
158
- end
159
238
 
160
- local function sealOpen(ch: ChannelState): ()
161
- if ch.lastId >= 0 then
162
- buffer.writeu16(ch.buff, ch.countPos, ch.itemCount)
163
- ch.lastId = -1
164
- ch.countPos = 0
165
- ch.itemCount = 0
166
- end
239
+ ch.itemCount += 1
240
+ reg.codec.write(ch, data)
167
241
  end
168
242
 
169
243
  --[[
170
- Writes a query frame. Seals any open batch, then writes:
171
- id(u8) + correlationId(u16) + status(u8)
172
- Status 0 = payload follows. Status 1 = nil response.
244
+ Seals the current batch and returns a snapshot for transmission.
173
245
  ]]
174
- function Channel.writeQuery(
175
- ch: ChannelState,
176
- id: number,
177
- name: string,
178
- correlationId: number,
179
- codec: any?,
180
- data: any?
181
- ): ()
182
- _currentPacket = name
183
- sealOpen(ch)
246
+ function Channel.sealAndDump(ch: Types.ChannelState): (buffer, { Instance }, number, number)
247
+ -- Multi-item batches need their count finalized before transmission
248
+ if ch.lastId >= 0 and not ch.singleMode and ch.countPos >= 0 then
249
+ writeu16(ch.buff, ch.countPos, ch.itemCount)
250
+ end
184
251
 
185
- alloc(ch, 4)
186
- local c = ch.cursor
187
- buffer.writeu8(ch.buff, c, id)
188
- buffer.writeu16(ch.buff, c + 1, correlationId)
252
+ local cursor = ch.cursor
253
+ local snapshot = buffer.create(cursor)
254
+ buffer.copy(snapshot, 0, ch.buff, 0, cursor)
189
255
 
190
- if codec then
191
- buffer.writeu8(ch.buff, c + 3, 0)
192
- ch.cursor = c + 4
193
- codec.write(ch, data)
256
+ local refs: { Instance }
257
+ local refCount = ch.refCount
258
+ if refCount > 0 then
259
+ refs = table.clone(ch.refs)
194
260
  else
195
- buffer.writeu8(ch.buff, c + 3, 1)
196
- ch.cursor = c + 4
261
+ refs = EMPTY_REFS :: any
197
262
  end
263
+
264
+ return snapshot, refs, cursor, refCount
198
265
  end
199
266
 
200
- -- XOR current frame against previous. Produces zero-heavy output for deflate.
201
- function Channel.xorApply(current: buffer, previous: buffer?): buffer
202
- local curLen = buffer.len(current)
267
+ --[[
268
+ XOR current frame against previous for deflate-friendly output.
269
+ ]]
270
+ function Channel.xorApply(
271
+ current: buffer,
272
+ curLen: number,
273
+ previous: buffer?,
274
+ prevLen: number
275
+ ): buffer
203
276
  if not previous then
204
277
  return current
205
278
  end
206
279
 
207
- local prevLen = buffer.len(previous :: buffer)
208
- if prevLen == 0 then
209
- return current
210
- end
211
-
212
280
  local result = buffer.create(curLen)
213
281
  local overlap = min(curLen, prevLen)
214
282
  local prev = previous :: buffer
215
283
 
216
- local aligned = band32(overlap, 0xFFFFFFFC)
217
- for i = 0, aligned - 4, 4 do
218
- buffer.writeu32(result, i, bxor(buffer.readu32(current, i), buffer.readu32(prev, i)))
284
+ -- u32 chunks
285
+ local aligned = overlap - (overlap % 4)
286
+ local i = 0
287
+ while i < aligned do
288
+ buffer.writeu32(result, i, bxor(readu32(current, i), readu32(prev, i)))
289
+ i += 4
219
290
  end
220
291
 
221
- for i = aligned, overlap - 1 do
222
- buffer.writeu8(result, i, bxor(buffer.readu8(current, i), buffer.readu8(prev, i)))
292
+ -- u8 remainder of overlap
293
+ while i < overlap do
294
+ writeu8(result, i, bxor(readu8(current, i), readu8(prev, i)))
295
+ i += 1
223
296
  end
224
297
 
298
+ -- Excess bytes (current longer than previous)
225
299
  if curLen > overlap then
226
300
  buffer.copy(result, overlap, current, overlap, curLen - overlap)
227
301
  end
@@ -229,12 +303,123 @@ function Channel.xorApply(current: buffer, previous: buffer?): buffer
229
303
  return result
230
304
  end
231
305
 
232
- function Channel.reset(ch: ChannelState): ()
233
- ch.cursor = 0
306
+ --[[
307
+ Writes a query frame with MSB encoding for nil-status.
308
+ MSB set = nil response, MSB clear = payload follows.
309
+ ]]
310
+ function Channel.writeQuery(
311
+ ch: Types.ChannelState,
312
+ id: number,
313
+ correlationId: number,
314
+ codec: Types.InternalCodec<any>?,
315
+ data: any
316
+ ): ()
317
+ -- Queries are standalone frames, so close any open packet batch first
318
+ if ch.lastId >= 0 and not ch.singleMode and ch.countPos >= 0 then
319
+ writeu16(ch.buff, ch.countPos, ch.itemCount)
320
+ end
234
321
  ch.lastId = -1
235
- ch.countPos = 0
236
- ch.itemCount = 0
237
- table.clear(ch.refs)
322
+ ch.singleMode = false
323
+
324
+ if codec and data ~= nil then
325
+ local cursor = ch.cursor
326
+ if cursor + 1 > ch.size then
327
+ alloc(ch, 1)
328
+ end
329
+ writeu8(ch.buff, cursor, id)
330
+ ch.cursor = cursor + 1
331
+
332
+ -- Inlined varint avoids the function call overhead for correlation IDs
333
+ if correlationId <= 0xBF then
334
+ local c2 = ch.cursor
335
+ if c2 + 1 > ch.size then
336
+ alloc(ch, 1)
337
+ end
338
+ writeu8(ch.buff, c2, correlationId)
339
+ ch.cursor = c2 + 1
340
+ elseif correlationId <= 8383 then
341
+ local c2 = ch.cursor
342
+ if c2 + 2 > ch.size then
343
+ alloc(ch, 2)
344
+ end
345
+ local adjusted = correlationId - 192
346
+ writeu8(ch.buff, c2, bor(0xC0, band(adjusted // 256, 0x1F)))
347
+ writeu8(ch.buff, c2 + 1, band(adjusted, 0xFF))
348
+ ch.cursor = c2 + 2
349
+ else
350
+ local c2 = ch.cursor
351
+ if c2 + 5 > ch.size then
352
+ alloc(ch, 5)
353
+ end
354
+ writeu8(ch.buff, c2, 0xF0)
355
+ buffer.writeu32(ch.buff, c2 + 1, correlationId)
356
+ ch.cursor = c2 + 5
357
+ end
358
+
359
+ codec.write(ch, data)
360
+ else
361
+ -- MSB set signals nil response to the reader
362
+ local cursor = ch.cursor
363
+ if cursor + 1 > ch.size then
364
+ alloc(ch, 1)
365
+ end
366
+ writeu8(ch.buff, cursor, bor(0x80, id))
367
+ ch.cursor = cursor + 1
368
+
369
+ if correlationId <= 0xBF then
370
+ local c2 = ch.cursor
371
+ if c2 + 1 > ch.size then
372
+ alloc(ch, 1)
373
+ end
374
+ writeu8(ch.buff, c2, correlationId)
375
+ ch.cursor = c2 + 1
376
+ elseif correlationId <= 8383 then
377
+ local c2 = ch.cursor
378
+ if c2 + 2 > ch.size then
379
+ alloc(ch, 2)
380
+ end
381
+ local adjusted = correlationId - 192
382
+ writeu8(ch.buff, c2, bor(0xC0, band(adjusted // 256, 0x1F)))
383
+ writeu8(ch.buff, c2 + 1, band(adjusted, 0xFF))
384
+ ch.cursor = c2 + 2
385
+ else
386
+ local c2 = ch.cursor
387
+ if c2 + 5 > ch.size then
388
+ alloc(ch, 5)
389
+ end
390
+ writeu8(ch.buff, c2, 0xF0)
391
+ buffer.writeu32(ch.buff, c2 + 1, correlationId)
392
+ ch.cursor = c2 + 5
393
+ end
394
+ end
395
+ end
396
+
397
+ -- Configuration -------------------------------------------------------
398
+
399
+ function Channel.setMaxSize(bytes: number): ()
400
+ _maxSize = bytes
401
+ end
402
+
403
+ function Channel.updateTimestamp(): ()
404
+ _frameCounter = (_frameCounter + 1) % 256
405
+ _offsetMs = round((os.clock() % 65.536) * 1000)
406
+ _clock = os.clock()
407
+ end
408
+
409
+ function Channel.enableTimestamps(): ()
410
+ _anyTimestamp = true
411
+ end
412
+
413
+ function Channel.hasTimestamps(): boolean
414
+ return _anyTimestamp
415
+ end
416
+
417
+ function Channel.enableStats(): ()
418
+ _statsEnabled = true
419
+ end
420
+
421
+ function Channel.statsEnabled(): boolean
422
+ return _statsEnabled
238
423
  end
239
424
 
240
425
  return table.freeze(Channel)