@axpecter/lync 2.0.0 → 2.1.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 +342 -428
- package/package.json +1 -1
- package/src/Types.luau +63 -31
- package/src/api/Group.luau +101 -106
- package/src/api/Packet.luau +187 -186
- package/src/api/Query.luau +223 -284
- package/src/api/Scope.luau +46 -57
- package/src/api/Signal.luau +104 -137
- package/src/codec/Base.luau +69 -20
- package/src/codec/composite/Array.luau +179 -270
- package/src/codec/composite/Map.luau +97 -347
- package/src/codec/composite/Optional.luau +27 -24
- package/src/codec/composite/Shared.luau +96 -65
- package/src/codec/composite/Struct.luau +200 -360
- package/src/codec/composite/Tagged.luau +62 -187
- package/src/codec/composite/Tuple.luau +79 -103
- package/src/codec/datatype/Buffer.luau +49 -21
- package/src/codec/datatype/CFrame.luau +207 -30
- package/src/codec/datatype/Color.luau +21 -11
- package/src/codec/datatype/Instance.luau +28 -21
- package/src/codec/datatype/IntVector.luau +33 -21
- package/src/codec/datatype/NumberRange.luau +19 -10
- package/src/codec/datatype/Ray.luau +26 -22
- package/src/codec/datatype/Rect.luau +20 -16
- package/src/codec/datatype/Region.luau +51 -45
- package/src/codec/datatype/Sequence.luau +64 -56
- package/src/codec/datatype/String.luau +89 -56
- package/src/codec/datatype/UDim.luau +40 -22
- package/src/codec/datatype/Vector.luau +181 -17
- package/src/codec/meta/Auto.luau +295 -253
- package/src/codec/meta/Bitfield.luau +104 -148
- package/src/codec/meta/Custom.luau +8 -4
- package/src/codec/meta/Enum.luau +29 -57
- package/src/codec/meta/Float.luau +64 -0
- package/src/codec/meta/Nothing.luau +9 -5
- package/src/codec/meta/Unknown.luau +44 -36
- package/src/codec/primitive/Bool.luau +16 -26
- package/src/codec/primitive/Float16.luau +58 -64
- package/src/codec/primitive/Int.luau +68 -0
- package/src/codec/primitive/Number.luau +21 -43
- package/src/codec/primitive/Varint.luau +67 -46
- package/src/index.d.ts +251 -335
- package/src/init.luau +319 -207
- package/src/internal/Baseline.luau +29 -24
- package/src/internal/Channel.luau +333 -278
- package/src/internal/Middleware.luau +60 -85
- package/src/internal/Pool.luau +19 -30
- package/src/internal/Registry.luau +56 -113
- package/src/transport/Bridge.luau +64 -43
- package/src/transport/Client.luau +59 -170
- package/src/transport/Gate.luau +282 -142
- package/src/transport/Reader.luau +148 -140
- package/src/transport/Server.luau +138 -488
- package/src/api/Namespace.luau +0 -256
- package/src/codec/meta/Quantized.luau +0 -174
|
@@ -1,333 +1,301 @@
|
|
|
1
1
|
--!strict
|
|
2
2
|
--!native
|
|
3
|
-
-- Per-channel buffer state with
|
|
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
|
|
14
|
-
|
|
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
|
|
20
|
-
local bor = bit32.bor
|
|
21
|
-
local rshift = bit32.rshift
|
|
22
|
-
|
|
23
9
|
local TS_NONE = 0
|
|
24
10
|
local TS_FRAME = 1
|
|
25
11
|
local TS_OFFSET = 2
|
|
26
12
|
local TS_FULL = 3
|
|
27
13
|
|
|
28
|
-
|
|
14
|
+
local DEFAULT_MAX = 262144
|
|
15
|
+
local EMPTY_REFS = table.freeze({})
|
|
29
16
|
|
|
30
|
-
|
|
31
|
-
local _currentPacket = ""
|
|
17
|
+
-- State ---------------------------------------------------------------
|
|
32
18
|
|
|
33
|
-
|
|
34
|
-
local _frameCounter: number = 0
|
|
35
|
-
local _offsetMs: number = 0
|
|
36
|
-
local _clockTime: number = 0
|
|
19
|
+
local _maxSize = DEFAULT_MAX
|
|
37
20
|
local _anyTimestamp = false
|
|
38
|
-
|
|
39
|
-
-- Stats gating. Default off; set via Lync.enableStats().
|
|
40
21
|
local _statsEnabled = false
|
|
22
|
+
local _frameCounter = 0
|
|
23
|
+
local _offsetMs = 0
|
|
24
|
+
local _clock = 0.0
|
|
41
25
|
|
|
42
|
-
--
|
|
43
|
-
|
|
44
|
-
local Channel = {}
|
|
45
|
-
|
|
46
|
-
function Channel.setMaxSize(bytes: number): ()
|
|
47
|
-
if bytes < 4096 or bytes > 1048576 then
|
|
48
|
-
error(`[Lync] Channel max size must be 4096-1048576, got {bytes}`)
|
|
49
|
-
end
|
|
50
|
-
_maxSize = bytes
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
function Channel.getMaxSize(): number
|
|
54
|
-
return _maxSize
|
|
55
|
-
end
|
|
26
|
+
-- Private -------------------------------------------------------------
|
|
56
27
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
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
|
|
70
40
|
|
|
71
|
-
function
|
|
41
|
+
local function alloc(ch: Types.ChannelState, bytes: number): ()
|
|
72
42
|
local needed = ch.cursor + bytes
|
|
73
43
|
if needed <= ch.size then
|
|
74
44
|
return
|
|
75
45
|
end
|
|
76
46
|
if needed > _maxSize then
|
|
77
47
|
error(
|
|
78
|
-
|
|
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)`
|
|
79
51
|
)
|
|
80
52
|
end
|
|
81
|
-
|
|
82
53
|
local newSize = lshift(1, 32 - countlz(needed - 1))
|
|
83
|
-
|
|
84
54
|
local newBuff = buffer.create(newSize)
|
|
85
55
|
buffer.copy(newBuff, 0, ch.buff, 0, ch.cursor)
|
|
86
56
|
ch.buff = newBuff
|
|
87
57
|
ch.size = newSize
|
|
88
58
|
end
|
|
89
59
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
-- Seals the open batch header and returns a snapshot of the buffer + refs.
|
|
93
|
-
function Channel.sealAndDump(ch: ChannelState): (buffer, { Instance })
|
|
94
|
-
if ch.lastId >= 0 then
|
|
95
|
-
buffer.writeu16(ch.buff, ch.countPos, ch.itemCount)
|
|
96
|
-
ch.lastId = -1
|
|
97
|
-
ch.countPos = 0
|
|
98
|
-
ch.itemCount = 0
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
local cur = ch.cursor
|
|
102
|
-
local out = buffer.create(cur)
|
|
103
|
-
buffer.copy(out, 0, ch.buff, 0, cur)
|
|
104
|
-
|
|
105
|
-
local refs = ch.refs
|
|
106
|
-
return out, if #refs > 0 then table.clone(refs) else {}
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
function Channel.setCurrentPacket(name: string): ()
|
|
110
|
-
_currentPacket = name
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
function Channel.updateTimestamp(frameCounter: number, offsetMs: number, clockTime: number): ()
|
|
114
|
-
_frameCounter = frameCounter
|
|
115
|
-
_offsetMs = offsetMs
|
|
116
|
-
_clockTime = clockTime
|
|
117
|
-
end
|
|
60
|
+
-- Public --------------------------------------------------------------
|
|
118
61
|
|
|
119
|
-
|
|
120
|
-
_anyTimestamp = true
|
|
121
|
-
end
|
|
62
|
+
local Channel = {}
|
|
122
63
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
64
|
+
Channel.TS_NONE = TS_NONE
|
|
65
|
+
Channel.TS_FRAME = TS_FRAME
|
|
66
|
+
Channel.TS_OFFSET = TS_OFFSET
|
|
67
|
+
Channel.TS_FULL = TS_FULL
|
|
126
68
|
|
|
127
|
-
function Channel.
|
|
128
|
-
|
|
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
|
+
}
|
|
129
86
|
end
|
|
130
87
|
|
|
131
|
-
function Channel.
|
|
132
|
-
|
|
88
|
+
function Channel.alloc(ch: Types.ChannelState, bytes: number): ()
|
|
89
|
+
alloc(ch, bytes)
|
|
133
90
|
end
|
|
134
91
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
|
92
|
+
function Channel.reset(ch: Types.ChannelState): ()
|
|
93
|
+
ch.cursor = 0
|
|
94
|
+
ch.lastId = -1
|
|
95
|
+
ch.countPos = -1
|
|
147
96
|
ch.itemCount = 0
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
if ch.
|
|
152
|
-
|
|
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
|
|
153
103
|
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
104
|
end
|
|
164
105
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
|
195
|
+
end
|
|
168
196
|
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
197
|
end
|
|
179
198
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
|
194
228
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
|
229
|
+
if payloadLen > 0 then
|
|
230
|
+
buffer.copy(b2, afterTs + 2, b2, afterTs, payloadLen)
|
|
231
|
+
end
|
|
206
232
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
reg._openFn(ch, id, reg.name)
|
|
233
|
+
ch.countPos = afterTs
|
|
234
|
+
writeu16(b2, afterTs, 0) -- placeholder u16; sealAndDump writes final count
|
|
235
|
+
ch.cursor = cursor + 2
|
|
236
|
+
ch.singleMode = false
|
|
212
237
|
end
|
|
213
238
|
|
|
214
239
|
ch.itemCount += 1
|
|
215
240
|
reg.codec.write(ch, data)
|
|
216
241
|
end
|
|
217
242
|
|
|
218
|
-
--
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
): ()
|
|
226
|
-
local id = reg.id
|
|
227
|
-
if id ~= ch.lastId or ch.itemCount >= MAX_BATCH then
|
|
228
|
-
reg._openFn(ch, id, reg.name)
|
|
243
|
+
--[[
|
|
244
|
+
Seals the current batch and returns a snapshot for transmission.
|
|
245
|
+
]]
|
|
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)
|
|
229
250
|
end
|
|
230
251
|
|
|
231
|
-
ch.
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
alloc(ch, srcLen)
|
|
235
|
-
buffer.copy(ch.buff, ch.cursor, src, 0, srcLen)
|
|
236
|
-
ch.cursor += srcLen
|
|
237
|
-
end
|
|
252
|
+
local cursor = ch.cursor
|
|
253
|
+
local snapshot = buffer.create(cursor)
|
|
254
|
+
buffer.copy(snapshot, 0, ch.buff, 0, cursor)
|
|
238
255
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
256
|
+
local refs: { Instance }
|
|
257
|
+
local refCount = ch.refCount
|
|
258
|
+
if refCount > 0 then
|
|
259
|
+
refs = table.clone(ch.refs)
|
|
260
|
+
else
|
|
261
|
+
refs = EMPTY_REFS :: any
|
|
242
262
|
end
|
|
243
|
-
end
|
|
244
263
|
|
|
245
|
-
|
|
246
|
-
if ch.lastId >= 0 then
|
|
247
|
-
buffer.writeu16(ch.buff, ch.countPos, ch.itemCount)
|
|
248
|
-
ch.lastId = -1
|
|
249
|
-
ch.countPos = 0
|
|
250
|
-
ch.itemCount = 0
|
|
251
|
-
end
|
|
264
|
+
return snapshot, refs, cursor, refCount
|
|
252
265
|
end
|
|
253
266
|
|
|
254
267
|
--[[
|
|
255
|
-
|
|
256
|
-
id(u8) + correlationId(varint) + status(u8)
|
|
257
|
-
Status 0 = payload follows. Status 1 = nil response.
|
|
268
|
+
XOR current frame against previous for deflate-friendly output.
|
|
258
269
|
]]
|
|
259
|
-
function Channel.
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
data: any?
|
|
266
|
-
): ()
|
|
267
|
-
_currentPacket = name
|
|
268
|
-
sealOpen(ch)
|
|
269
|
-
|
|
270
|
-
alloc(ch, 2)
|
|
271
|
-
local c = ch.cursor
|
|
272
|
-
buffer.writeu8(ch.buff, c, id)
|
|
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
|
|
293
|
-
|
|
294
|
-
local c2 = ch.cursor
|
|
295
|
-
alloc(ch, 1)
|
|
296
|
-
if codec then
|
|
297
|
-
buffer.writeu8(ch.buff, c2, 0)
|
|
298
|
-
ch.cursor = c2 + 1
|
|
299
|
-
codec.write(ch, data)
|
|
300
|
-
else
|
|
301
|
-
buffer.writeu8(ch.buff, c2, 1)
|
|
302
|
-
ch.cursor = c2 + 1
|
|
303
|
-
end
|
|
304
|
-
end
|
|
305
|
-
|
|
306
|
-
-- XOR current frame against previous. Produces zero-heavy output for deflate.
|
|
307
|
-
function Channel.xorApply(current: buffer, previous: buffer?): buffer
|
|
308
|
-
local curLen = buffer.len(current)
|
|
270
|
+
function Channel.xorApply(
|
|
271
|
+
current: buffer,
|
|
272
|
+
curLen: number,
|
|
273
|
+
previous: buffer?,
|
|
274
|
+
prevLen: number
|
|
275
|
+
): buffer
|
|
309
276
|
if not previous then
|
|
310
277
|
return current
|
|
311
278
|
end
|
|
312
279
|
|
|
313
|
-
local prevLen = buffer.len(previous :: buffer)
|
|
314
|
-
if prevLen == 0 then
|
|
315
|
-
return current
|
|
316
|
-
end
|
|
317
|
-
|
|
318
280
|
local result = buffer.create(curLen)
|
|
319
281
|
local overlap = min(curLen, prevLen)
|
|
320
282
|
local prev = previous :: buffer
|
|
321
283
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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
|
|
325
290
|
end
|
|
326
291
|
|
|
327
|
-
|
|
328
|
-
|
|
292
|
+
-- u8 remainder of overlap
|
|
293
|
+
while i < overlap do
|
|
294
|
+
writeu8(result, i, bxor(readu8(current, i), readu8(prev, i)))
|
|
295
|
+
i += 1
|
|
329
296
|
end
|
|
330
297
|
|
|
298
|
+
-- Excess bytes (current longer than previous)
|
|
331
299
|
if curLen > overlap then
|
|
332
300
|
buffer.copy(result, overlap, current, overlap, curLen - overlap)
|
|
333
301
|
end
|
|
@@ -335,36 +303,123 @@ function Channel.xorApply(current: buffer, previous: buffer?): buffer
|
|
|
335
303
|
return result
|
|
336
304
|
end
|
|
337
305
|
|
|
338
|
-
--
|
|
339
|
-
|
|
340
|
-
|
|
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
|
|
341
321
|
ch.lastId = -1
|
|
342
|
-
ch.
|
|
343
|
-
|
|
344
|
-
if
|
|
345
|
-
|
|
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
|
|
346
394
|
end
|
|
347
395
|
end
|
|
348
396
|
|
|
349
|
-
--
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
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
|
-
}
|
|
397
|
+
-- Configuration -------------------------------------------------------
|
|
398
|
+
|
|
399
|
+
function Channel.setMaxSize(bytes: number): ()
|
|
400
|
+
_maxSize = bytes
|
|
363
401
|
end
|
|
364
402
|
|
|
365
|
-
Channel.
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
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
|
|
423
|
+
end
|
|
369
424
|
|
|
370
425
|
return table.freeze(Channel)
|