@axpecter/lync 2.3.2 → 3.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/LICENSE +21 -0
- package/README.md +302 -299
- package/Types.luau +215 -0
- package/api/Define.luau +154 -0
- package/api/Lifecycle.luau +119 -0
- package/api/Packet.luau +108 -0
- package/api/Query.luau +149 -0
- package/api/Set.luau +189 -0
- package/channel/Blocks.luau +124 -0
- package/channel/Group.luau +118 -0
- package/channel/Link.luau +135 -0
- package/channel/Outbox.luau +261 -0
- package/channel/Recipients.luau +129 -0
- package/channel/Transport.luau +138 -0
- package/codec/Base.luau +236 -0
- package/codec/Composites.luau +128 -0
- package/codec/Datatypes.luau +162 -0
- package/codec/Legality.luau +61 -0
- package/codec/Scalars.luau +72 -0
- package/codec/Text.luau +89 -0
- package/codec/init.luau +39 -0
- package/index.d.ts +285 -0
- package/init.luau +91 -0
- package/kernel/Bits.luau +16 -0
- package/kernel/Catalog.luau +121 -0
- package/kernel/Heap.luau +80 -0
- package/kernel/Listeners.luau +95 -0
- package/kernel/Log.luau +197 -0
- package/kernel/Tally.luau +128 -0
- package/kernel/Threads.luau +44 -0
- package/kernel/Wide.luau +267 -0
- package/package.json +15 -32
- package/runtime/Boundary.luau +180 -0
- package/runtime/Dispatch.luau +91 -0
- package/runtime/Handles.luau +114 -0
- package/runtime/Handshake.luau +90 -0
- package/runtime/Pending.luau +196 -0
- package/runtime/Router.luau +379 -0
- package/runtime/Session.luau +731 -0
- package/shape/Compile.luau +340 -0
- package/shape/Folds.luau +332 -0
- package/shape/Frames.luau +170 -0
- package/shape/Grid.luau +169 -0
- package/shape/Ir.luau +540 -0
- package/shape/Joins.luau +125 -0
- package/shape/Legality.luau +273 -0
- package/shape/Lower.luau +419 -0
- package/shape/Order.luau +64 -0
- package/shape/Regions.luau +113 -0
- package/shape/Rotation.luau +472 -0
- package/shape/Serial.luau +258 -0
- package/shape/Words.luau +89 -0
- package/shape/emit/Block.luau +453 -0
- package/shape/emit/Column.luau +338 -0
- package/shape/emit/Leaf.luau +462 -0
- package/shape/emit/Word.luau +234 -0
- package/shape/emit/init.luau +222 -0
- package/shape/init.luau +78 -0
- package/state/Apply.luau +330 -0
- package/state/Audience.luau +126 -0
- package/state/Catchup.luau +218 -0
- package/state/Coalesce.luau +43 -0
- package/state/Fieldset.luau +177 -0
- package/state/Newest.luau +339 -0
- package/state/Plan.luau +441 -0
- package/state/Sections.luau +468 -0
- package/state/Store.luau +188 -0
- package/state/Sync.luau +762 -0
- package/wire/Bytes.luau +118 -0
- package/wire/Clock.luau +37 -0
- package/wire/Protocol.luau +110 -0
- package/wire/Sequence.luau +40 -0
- package/wire/Varint.luau +124 -0
- package/src/Types.luau +0 -180
- package/src/api/Group.luau +0 -156
- package/src/api/Packet.luau +0 -319
- package/src/api/Query.luau +0 -338
- package/src/api/Scope.luau +0 -90
- package/src/api/Signal.luau +0 -188
- package/src/codec/Base.luau +0 -136
- package/src/codec/composite/Array.luau +0 -456
- package/src/codec/composite/Map.luau +0 -469
- package/src/codec/composite/Optional.luau +0 -54
- package/src/codec/composite/Shared.luau +0 -291
- package/src/codec/composite/Struct.luau +0 -494
- package/src/codec/composite/Tagged.luau +0 -103
- package/src/codec/composite/Tuple.luau +0 -143
- package/src/codec/datatype/Buffer.luau +0 -66
- package/src/codec/datatype/CFrame.luau +0 -109
- package/src/codec/datatype/Color.luau +0 -32
- package/src/codec/datatype/Instance.luau +0 -52
- package/src/codec/datatype/IntVector.luau +0 -39
- package/src/codec/datatype/NumberRange.luau +0 -39
- package/src/codec/datatype/Ray.luau +0 -36
- package/src/codec/datatype/Rect.luau +0 -31
- package/src/codec/datatype/Region.luau +0 -62
- package/src/codec/datatype/Sequence.luau +0 -184
- package/src/codec/datatype/String.luau +0 -94
- package/src/codec/datatype/UDim.luau +0 -46
- package/src/codec/datatype/Vector.luau +0 -130
- package/src/codec/meta/Auto.luau +0 -318
- package/src/codec/meta/Bitfield.luau +0 -147
- package/src/codec/meta/Custom.luau +0 -33
- package/src/codec/meta/DeltaScalar.luau +0 -390
- package/src/codec/meta/Enum.luau +0 -50
- package/src/codec/meta/Float.luau +0 -38
- package/src/codec/meta/Nothing.luau +0 -13
- package/src/codec/meta/Unknown.luau +0 -41
- package/src/codec/primitive/Bool.luau +0 -26
- package/src/codec/primitive/Float16.luau +0 -99
- package/src/codec/primitive/Int.luau +0 -59
- package/src/codec/primitive/Number.luau +0 -24
- package/src/codec/primitive/Signed.luau +0 -32
- package/src/codec/primitive/Varint.luau +0 -165
- package/src/codec/primitive/Zint.luau +0 -99
- package/src/index.d.ts +0 -423
- package/src/init.luau +0 -362
- package/src/internal/Baseline.luau +0 -57
- package/src/internal/Channel.luau +0 -390
- package/src/internal/Middleware.luau +0 -100
- package/src/internal/Pool.luau +0 -65
- package/src/internal/Registry.luau +0 -119
- package/src/internal/Transport.luau +0 -28
- package/src/transport/Bridge.luau +0 -110
- package/src/transport/Client.luau +0 -97
- package/src/transport/Gate.luau +0 -488
- package/src/transport/Reader.luau +0 -331
- package/src/transport/Server.luau +0 -211
- package/src/util/Array.luau +0 -18
- package/src/util/Buffer.luau +0 -90
- package/src/util/Constants.luau +0 -30
- package/src/util/Log.luau +0 -68
- package/src/util/Player.luau +0 -14
- package/src/util/Quantize.luau +0 -84
- package/src/util/Quat.luau +0 -124
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--!native
|
|
3
|
+
--!optimize 2
|
|
4
|
+
-- Every value that is not a bit region, as the pair of closures that writes it and reads it back.
|
|
5
|
+
|
|
6
|
+
local Bits = require (script.Parent.Parent.Parent.kernel.Bits)
|
|
7
|
+
local Bytes = require (script.Parent.Parent.Parent.wire.Bytes)
|
|
8
|
+
local Catalog = require (script.Parent.Parent.Parent.kernel.Catalog)
|
|
9
|
+
local Ir = require (script.Parent.Parent.Ir)
|
|
10
|
+
local Log = require (script.Parent.Parent.Parent.kernel.Log)
|
|
11
|
+
local Protocol = require (script.Parent.Parent.Parent.wire.Protocol)
|
|
12
|
+
local Serial = require (script.Parent.Parent.Serial)
|
|
13
|
+
local Types = require (script.Parent.Parent.Parent.Types)
|
|
14
|
+
local Varint = require (script.Parent.Parent.Parent.wire.Varint)
|
|
15
|
+
local Word = require (script.Parent.Word)
|
|
16
|
+
|
|
17
|
+
local Leaf = {}
|
|
18
|
+
|
|
19
|
+
-- Constants -----------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
const BYTE = Protocol.BYTE_BITS
|
|
22
|
+
const EXACT = Protocol.EXACT
|
|
23
|
+
|
|
24
|
+
-- The widths a whole number of bytes is read and written in, and the value one word carries.
|
|
25
|
+
const PAIR = 2
|
|
26
|
+
const TRIO = PAIR + 1
|
|
27
|
+
const WORD = 4
|
|
28
|
+
const HALF = 65536
|
|
29
|
+
const HIGH = 2 ^ (WORD * BYTE)
|
|
30
|
+
|
|
31
|
+
-- The slot a reference nobody made takes, which is none, since the array it indexes starts at one.
|
|
32
|
+
const NOTHING = 0
|
|
33
|
+
|
|
34
|
+
-- Types ---------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
-- What a node is told about itself, which is where it is and what it wanted, already in words.
|
|
37
|
+
type Named = {
|
|
38
|
+
path: string,
|
|
39
|
+
wanted: string,
|
|
40
|
+
pathId: number,
|
|
41
|
+
expectId: number,
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
-- Private -------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
local function named (tag: Ir.Tag, paths: Serial.Interner, expected: Serial.Interner): Named
|
|
47
|
+
return {
|
|
48
|
+
path = paths.texts[tag.pathId],
|
|
49
|
+
wanted = expected.texts[tag.expectId],
|
|
50
|
+
pathId = tag.pathId,
|
|
51
|
+
expectId = tag.expectId,
|
|
52
|
+
}
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
--[[
|
|
56
|
+
A value the schema has no place for, as the line the caller reads. The same sentence a drop
|
|
57
|
+
reads as, because a caller holds one vocabulary for what their schema wanted whether the value
|
|
58
|
+
that did not fit was theirs going out or somebody else's coming in.
|
|
59
|
+
]]
|
|
60
|
+
local function refuse (of: Named, got: any): never return Word.refused (of.path, of.wanted, got) end
|
|
61
|
+
|
|
62
|
+
-- Bytes nobody here wrote, as the two numbers the boundary reads a line back out of.
|
|
63
|
+
local function reject (code: Catalog.Code, of: Named, got: any)
|
|
64
|
+
Log.reject (code, { pathId = of.pathId, expectId = of.expectId, got = got })
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
-- A whole number of bytes, little end first, for every field that is one.
|
|
68
|
+
local function laid (b: buffer, at: number, bytes: number, value: number)
|
|
69
|
+
if bytes <= WORD then
|
|
70
|
+
if bytes == 0 then return end
|
|
71
|
+
if bytes == 1 then return buffer.writeu8 (b, at, value) end
|
|
72
|
+
if bytes == PAIR then return buffer.writeu16 (b, at, value) end
|
|
73
|
+
if bytes == TRIO then
|
|
74
|
+
buffer.writeu16 (b, at, value % HALF)
|
|
75
|
+
return buffer.writeu8 (b, at + PAIR, value // HALF)
|
|
76
|
+
end
|
|
77
|
+
return buffer.writeu32 (b, at, value)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
buffer.writeu32 (b, at, value % HIGH)
|
|
81
|
+
|
|
82
|
+
local high = value // HIGH
|
|
83
|
+
if bytes == 5 then return buffer.writeu8 (b, at + WORD, high) end
|
|
84
|
+
if bytes == 6 then return buffer.writeu16 (b, at + WORD, high) end
|
|
85
|
+
|
|
86
|
+
buffer.writeu16 (b, at + WORD, high % HALF)
|
|
87
|
+
buffer.writeu8 (b, at + WORD + PAIR, high // HALF)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
local function taken (b: buffer, at: number, bytes: number): number
|
|
91
|
+
if bytes <= WORD then
|
|
92
|
+
if bytes == 0 then return 0 end
|
|
93
|
+
if bytes == 1 then return buffer.readu8 (b, at) end
|
|
94
|
+
if bytes == PAIR then return buffer.readu16 (b, at) end
|
|
95
|
+
if bytes == TRIO then
|
|
96
|
+
return buffer.readu16 (b, at) + buffer.readu8 (b, at + PAIR) * HALF
|
|
97
|
+
end
|
|
98
|
+
return buffer.readu32 (b, at)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
local low = buffer.readu32 (b, at)
|
|
102
|
+
if bytes == 5 then return low + buffer.readu8 (b, at + WORD) * HIGH end
|
|
103
|
+
if bytes == 6 then return low + buffer.readu16 (b, at + WORD) * HIGH end
|
|
104
|
+
|
|
105
|
+
local high = buffer.readu16 (b, at + WORD) + buffer.readu8 (b, at + WORD + PAIR) * HALF
|
|
106
|
+
return low + high * HIGH
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
-- Public --------------------------------------------------------------
|
|
110
|
+
|
|
111
|
+
-- Elements of one width with nothing between them, which is the 24-bit law's whole point.
|
|
112
|
+
function Leaf.packed (
|
|
113
|
+
b: buffer,
|
|
114
|
+
at: number,
|
|
115
|
+
width: number,
|
|
116
|
+
count: number,
|
|
117
|
+
indices: { number }
|
|
118
|
+
): number
|
|
119
|
+
local acc, used = 0, 0
|
|
120
|
+
|
|
121
|
+
for index = 1, count do
|
|
122
|
+
acc = bit32.bor (acc, bit32.lshift (indices[index], used))
|
|
123
|
+
used += width
|
|
124
|
+
|
|
125
|
+
while used >= BYTE do
|
|
126
|
+
buffer.writeu8 (b, at, bit32.band (acc, 0xFF))
|
|
127
|
+
at += 1
|
|
128
|
+
acc = bit32.rshift (acc, BYTE)
|
|
129
|
+
used -= BYTE
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
if used == 0 then return at end
|
|
134
|
+
|
|
135
|
+
buffer.writeu8 (b, at, acc)
|
|
136
|
+
return at + 1
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
--[[
|
|
140
|
+
The same run, taken apart. The bits left over when the last element is out are the pad the
|
|
141
|
+
stream aligns on, and they are held to being zero: padding that could differ would make two
|
|
142
|
+
encodings of one value, which is what the ordering of map keys rests on not being possible.
|
|
143
|
+
]]
|
|
144
|
+
function Leaf.unpacked (
|
|
145
|
+
b: buffer,
|
|
146
|
+
at: number,
|
|
147
|
+
width: number,
|
|
148
|
+
count: number,
|
|
149
|
+
into: { number }
|
|
150
|
+
): (number, boolean)
|
|
151
|
+
local acc, used = 0, 0
|
|
152
|
+
local mask = bit32.lshift (1, width) - 1
|
|
153
|
+
|
|
154
|
+
for index = 1, count do
|
|
155
|
+
while used < width do
|
|
156
|
+
acc = bit32.bor (acc, bit32.lshift (buffer.readu8 (b, at), used))
|
|
157
|
+
at += 1
|
|
158
|
+
used += BYTE
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
into[index] = bit32.band (acc, mask)
|
|
162
|
+
acc = bit32.rshift (acc, width)
|
|
163
|
+
used -= width
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
return at, acc == 0
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
-- An IEEE span, which has a place for every bit pattern a double takes and so refuses no number.
|
|
170
|
+
function Leaf.float (
|
|
171
|
+
of: Ir.Float,
|
|
172
|
+
paths: Serial.Interner,
|
|
173
|
+
expected: Serial.Interner
|
|
174
|
+
): (Ir.Write, Ir.Read)
|
|
175
|
+
local at = named (of.tag, paths, expected)
|
|
176
|
+
local bytes = of.maxBytes
|
|
177
|
+
|
|
178
|
+
if of.op == "f32" then
|
|
179
|
+
local function write (b: buffer, pos: number, value: any): number
|
|
180
|
+
if type (value) ~= "number" then refuse (at, value) end
|
|
181
|
+
|
|
182
|
+
buffer.writef32 (b, pos, value)
|
|
183
|
+
return pos + bytes
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
local function read (b: buffer, pos: number, _ctx: Types.ValidateContext): (number, any)
|
|
187
|
+
return pos + bytes, buffer.readf32 (b, pos)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
return write, read
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
local function write (b: buffer, pos: number, value: any): number
|
|
194
|
+
if type (value) ~= "number" then refuse (at, value) end
|
|
195
|
+
|
|
196
|
+
buffer.writef64 (b, pos, value)
|
|
197
|
+
return pos + bytes
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
local function read (b: buffer, pos: number, _ctx: Types.ValidateContext): (number, any)
|
|
201
|
+
return pos + bytes, buffer.readf64 (b, pos)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
return write, read
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
-- One fixed field the run accumulator cannot pack, written as whole bytes of its own.
|
|
208
|
+
function Leaf.wide (
|
|
209
|
+
of: Ir.Wide,
|
|
210
|
+
paths: Serial.Interner,
|
|
211
|
+
expected: Serial.Interner
|
|
212
|
+
): (Ir.Write, Ir.Read)
|
|
213
|
+
local at = named (of.tag, paths, expected)
|
|
214
|
+
local grid = of.grid
|
|
215
|
+
local toIndex, fromIndex, span = grid.toIndex, grid.fromIndex, grid.span
|
|
216
|
+
local bytes = of.maxBytes
|
|
217
|
+
local ceiling = 2 ^ grid.width - 1
|
|
218
|
+
|
|
219
|
+
local function write (b: buffer, pos: number, value: any): number
|
|
220
|
+
local index = toIndex (value)
|
|
221
|
+
if index == nil then refuse (at, value) end
|
|
222
|
+
|
|
223
|
+
laid (b, pos, bytes, index :: number)
|
|
224
|
+
return pos + bytes
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
local function read (b: buffer, pos: number, _ctx: Types.ValidateContext): (number, any)
|
|
228
|
+
local index = taken (b, pos, bytes)
|
|
229
|
+
if index > ceiling then reject (Catalog.PAD, at, index) end
|
|
230
|
+
if index > span then reject (Catalog.TAG, at, index) end
|
|
231
|
+
|
|
232
|
+
return pos + bytes, fromIndex (index)
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
return write, read
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
-- A varint, through the one module that spells the groups.
|
|
239
|
+
function Leaf.varint (
|
|
240
|
+
of: Ir.Varint,
|
|
241
|
+
paths: Serial.Interner,
|
|
242
|
+
expected: Serial.Interner
|
|
243
|
+
): (Ir.Write, Ir.Read)
|
|
244
|
+
local at = named (of.tag, paths, expected)
|
|
245
|
+
|
|
246
|
+
-- Whole as well as within the ceiling, on both forms.
|
|
247
|
+
if of.op == "vlq" then
|
|
248
|
+
local function write (b: buffer, pos: number, value: any): number
|
|
249
|
+
if not (value >= 0 and value < EXACT and value % 1 == 0) then refuse (at, value) end
|
|
250
|
+
return Varint.write (b, pos, value)
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
local function read (b: buffer, pos: number, _ctx: Types.ValidateContext): (number, any)
|
|
254
|
+
local ok, value, after = pcall (Varint.read, b, pos)
|
|
255
|
+
if not ok then reject (Catalog.VARINT, at, value) end
|
|
256
|
+
|
|
257
|
+
return after :: number, value
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
return write, read
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
local function write (b: buffer, pos: number, value: any): number
|
|
264
|
+
if not (value > -EXACT and value < EXACT and value % 1 == 0) then refuse (at, value) end
|
|
265
|
+
return Varint.writeSigned (b, pos, value)
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
local function read (b: buffer, pos: number, _ctx: Types.ValidateContext): (number, any)
|
|
269
|
+
local ok, value, after = pcall (Varint.readSigned, b, pos)
|
|
270
|
+
if not ok then reject (Catalog.VARINT, at, value) end
|
|
271
|
+
|
|
272
|
+
return after :: number, value
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
return write, read
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
-- A string or a buffer, as its count and then its bytes.
|
|
279
|
+
function Leaf.bytes (
|
|
280
|
+
of: Ir.Bytes,
|
|
281
|
+
paths: Serial.Interner,
|
|
282
|
+
expected: Serial.Interner
|
|
283
|
+
): (Ir.Write, Ir.Read)
|
|
284
|
+
local at = named (of.tag, paths, expected)
|
|
285
|
+
local low, high = of.count.min, of.count.max
|
|
286
|
+
local span = high - low
|
|
287
|
+
local held = Bytes.whole (Bits.width (span))
|
|
288
|
+
local text = of.form == "string"
|
|
289
|
+
|
|
290
|
+
local alphabet = of.alphabet
|
|
291
|
+
if alphabet == nil then
|
|
292
|
+
local function write (b: buffer, pos: number, value: any): number
|
|
293
|
+
local count = if text then #value else buffer.len (value)
|
|
294
|
+
if count < low or count > high then refuse (at, count) end
|
|
295
|
+
|
|
296
|
+
laid (b, pos, held, count - low)
|
|
297
|
+
local after = pos + held
|
|
298
|
+
|
|
299
|
+
if text then
|
|
300
|
+
buffer.writestring (b, after, value)
|
|
301
|
+
else
|
|
302
|
+
buffer.copy (b, after, value)
|
|
303
|
+
end
|
|
304
|
+
return after + count
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
local function read (b: buffer, pos: number, _ctx: Types.ValidateContext): (number, any)
|
|
308
|
+
local index = taken (b, pos, held)
|
|
309
|
+
if index > span then reject (Catalog.COUNT, at, index) end
|
|
310
|
+
|
|
311
|
+
local count = index + low
|
|
312
|
+
local after = pos + held
|
|
313
|
+
if text then return after + count, buffer.readstring (b, after, count) end
|
|
314
|
+
|
|
315
|
+
local out = buffer.create (count)
|
|
316
|
+
buffer.copy (out, 0, b, after, count)
|
|
317
|
+
return after + count, out
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
return write, read
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
local width = Bits.width (#alphabet - 1)
|
|
324
|
+
local index: { [string]: number } = {}
|
|
325
|
+
local symbol: { string } = {}
|
|
326
|
+
|
|
327
|
+
for place = 1, #alphabet do
|
|
328
|
+
local one = string.sub (alphabet, place, place)
|
|
329
|
+
index[one] = place - 1
|
|
330
|
+
symbol[place] = one
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
local scratch: { number } = {}
|
|
334
|
+
|
|
335
|
+
local function write (b: buffer, pos: number, value: any): number
|
|
336
|
+
local count = #value
|
|
337
|
+
if count < low or count > high then refuse (at, count) end
|
|
338
|
+
|
|
339
|
+
for place = 1, count do
|
|
340
|
+
local one = index[string.sub (value, place, place)]
|
|
341
|
+
if one == nil then refuse (at, value) end
|
|
342
|
+
scratch[place] = one :: number
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
laid (b, pos, held, count - low)
|
|
346
|
+
return Leaf.packed (b, pos + held, width, count, scratch)
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
local function read (b: buffer, pos: number, _ctx: Types.ValidateContext): (number, any)
|
|
350
|
+
local counted = taken (b, pos, held)
|
|
351
|
+
if counted > span then reject (Catalog.COUNT, at, counted) end
|
|
352
|
+
|
|
353
|
+
local count = counted + low
|
|
354
|
+
local after, clean = Leaf.unpacked (b, pos + held, width, count, scratch)
|
|
355
|
+
if not clean then reject (Catalog.PAD, at, count) end
|
|
356
|
+
|
|
357
|
+
local out = table.create (count) :: { string }
|
|
358
|
+
for place = 1, count do
|
|
359
|
+
local one = symbol[scratch[place] + 1]
|
|
360
|
+
if one == nil then reject (Catalog.TAG, at, scratch[place]) end
|
|
361
|
+
out[place] = one :: string
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
return after, table.concat (out)
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
return write, read
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
-- Elements of one width, counted and then packed with nothing between them.
|
|
371
|
+
function Leaf.run (
|
|
372
|
+
of: Ir.Run,
|
|
373
|
+
paths: Serial.Interner,
|
|
374
|
+
expected: Serial.Interner
|
|
375
|
+
): (Ir.Write, Ir.Read)
|
|
376
|
+
local at = named (of.tag, paths, expected)
|
|
377
|
+
local low, high = of.count.min, of.count.max
|
|
378
|
+
local span = high - low
|
|
379
|
+
local held = Bytes.whole (Bits.width (span))
|
|
380
|
+
|
|
381
|
+
local elem = of.elem
|
|
382
|
+
local toIndex, fromIndex, reach = elem.toIndex, elem.fromIndex, elem.span
|
|
383
|
+
local width = elem.width
|
|
384
|
+
|
|
385
|
+
local scratch: { number } = {}
|
|
386
|
+
|
|
387
|
+
local function write (b: buffer, pos: number, value: any): number
|
|
388
|
+
local count = #value
|
|
389
|
+
if count < low or count > high then refuse (at, count) end
|
|
390
|
+
|
|
391
|
+
for place = 1, count do
|
|
392
|
+
local one = toIndex (value[place])
|
|
393
|
+
if one == nil then refuse (at, value[place]) end
|
|
394
|
+
scratch[place] = one :: number
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
laid (b, pos, held, count - low)
|
|
398
|
+
return Leaf.packed (b, pos + held, width, count, scratch)
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
local function read (b: buffer, pos: number, _ctx: Types.ValidateContext): (number, any)
|
|
402
|
+
local counted = taken (b, pos, held)
|
|
403
|
+
if counted > span then reject (Catalog.COUNT, at, counted) end
|
|
404
|
+
|
|
405
|
+
local count = counted + low
|
|
406
|
+
local after, clean = Leaf.unpacked (b, pos + held, width, count, scratch)
|
|
407
|
+
if not clean then reject (Catalog.PAD, at, count) end
|
|
408
|
+
|
|
409
|
+
local out = table.create (count) :: { any }
|
|
410
|
+
for place = 1, count do
|
|
411
|
+
local one = scratch[place]
|
|
412
|
+
if one > reach then reject (Catalog.TAG, at, one) end
|
|
413
|
+
out[place] = fromIndex (one)
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
return after, out
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
return write, read
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
-- An instance, as its slot in the array that rides beside the frame rather than in the bytes.
|
|
423
|
+
function Leaf.slot (
|
|
424
|
+
of: Ir.Slot,
|
|
425
|
+
paths: Serial.Interner,
|
|
426
|
+
expected: Serial.Interner,
|
|
427
|
+
side: Ir.Sidecar
|
|
428
|
+
): (Ir.Write, Ir.Read)
|
|
429
|
+
local at = named (of.tag, paths, expected)
|
|
430
|
+
local class = of.class
|
|
431
|
+
|
|
432
|
+
local refs, slotOf = side.refs, side.slotOf
|
|
433
|
+
side.bears = true
|
|
434
|
+
|
|
435
|
+
local function write (b: buffer, pos: number, value: any): number
|
|
436
|
+
if value == nil then return Varint.write (b, pos, NOTHING) end
|
|
437
|
+
|
|
438
|
+
local held = slotOf[value]
|
|
439
|
+
if held == nil then
|
|
440
|
+
held = #refs + 1
|
|
441
|
+
refs[held] = value
|
|
442
|
+
slotOf[value] = held
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
return Varint.write (b, pos, held :: number)
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
local function read (b: buffer, pos: number, _ctx: Types.ValidateContext): (number, any)
|
|
449
|
+
local ok, slot, after = pcall (Varint.read, b, pos)
|
|
450
|
+
if not ok then reject (Catalog.VARINT, at, slot) end
|
|
451
|
+
|
|
452
|
+
local one = refs[slot :: number]
|
|
453
|
+
if one == nil then return after :: number, nil end
|
|
454
|
+
if class ~= nil and not one:IsA (class) then reject (Catalog.SIDECAR, at, one.ClassName) end
|
|
455
|
+
|
|
456
|
+
return after :: number, one
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
return write, read
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
return table.freeze (Leaf)
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--!native
|
|
3
|
+
--!optimize 2
|
|
4
|
+
-- One bit region, as the pair of closures that lay it down and take it back up.
|
|
5
|
+
|
|
6
|
+
local Catalog = require (script.Parent.Parent.Parent.kernel.Catalog)
|
|
7
|
+
local Ir = require (script.Parent.Parent.Ir)
|
|
8
|
+
local Log = require (script.Parent.Parent.Parent.kernel.Log)
|
|
9
|
+
local Serial = require (script.Parent.Parent.Serial)
|
|
10
|
+
local Words = require (script.Parent.Parent.Words)
|
|
11
|
+
|
|
12
|
+
local Word = {}
|
|
13
|
+
|
|
14
|
+
-- Constants -----------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
-- The same two widths a frame header reads, a region and a header being one kind of bit run.
|
|
17
|
+
const ONE = Words.ONE
|
|
18
|
+
const PAIR = Words.PAIR
|
|
19
|
+
|
|
20
|
+
-- Types ---------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
-- The two halves of a region, and the array of indices they meet in.
|
|
23
|
+
export type Pack = (b: buffer, pos: number, value: any, slots: { number }, holds: { any }) -> ()
|
|
24
|
+
export type Unpack = (b: buffer, pos: number, into: any, slots: { number }, holds: { any }) -> any
|
|
25
|
+
|
|
26
|
+
-- Public --------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
-- A value the schema has no place for, refused in the words a caller wrote.
|
|
29
|
+
function Word.refused (path: string, wanted: string, got: any): never
|
|
30
|
+
return Log.refuse (
|
|
31
|
+
Log.line (Catalog.TAG, { path = path, expected = wanted, got = tostring (got) })
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
--[[
|
|
36
|
+
The same refusal from a tag, which is what a composite carries in place of the words.
|
|
37
|
+
|
|
38
|
+
A leaf holds its own and a composite holds the numbers they were interned under, so the lookup
|
|
39
|
+
is the half that differs and this is where the two meet.
|
|
40
|
+
]]
|
|
41
|
+
function Word.refusedAt (
|
|
42
|
+
tag: Ir.Tag,
|
|
43
|
+
paths: Serial.Interner,
|
|
44
|
+
said: Serial.Interner,
|
|
45
|
+
got: any
|
|
46
|
+
): never
|
|
47
|
+
return Word.refused (paths.texts[tag.pathId], said.texts[tag.expectId], got)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
-- The pair, with every constant the region is laid out by read once and held.
|
|
51
|
+
function Word.emit (of: Ir.Word, paths: Serial.Interner, expected: Serial.Interner): (Pack, Unpack)
|
|
52
|
+
local leaves = of.leaves
|
|
53
|
+
local joins = of.joins
|
|
54
|
+
local held = #leaves
|
|
55
|
+
|
|
56
|
+
-- Per leaf, in wire order.
|
|
57
|
+
local spans = table.create (held, 0)
|
|
58
|
+
local toIndex = table.create (held) :: { (number) -> number? }
|
|
59
|
+
local fromIndex = table.create (held) :: { (number) -> number }
|
|
60
|
+
|
|
61
|
+
for index, grid in leaves do
|
|
62
|
+
spans[index] = grid.span
|
|
63
|
+
toIndex[index] = grid.toIndex
|
|
64
|
+
fromIndex[index] = grid.fromIndex
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
-- The leaves that occupy no bits, which is every grid holding one value.
|
|
68
|
+
local empties: { number } = {}
|
|
69
|
+
for index = 1, held do
|
|
70
|
+
if spans[index] == 0 then table.insert (empties, index) end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
-- Per join, in the order the region reads its fields.
|
|
74
|
+
local reach = #joins
|
|
75
|
+
local starts = table.create (reach, 0)
|
|
76
|
+
local ends = table.create (reach, 0)
|
|
77
|
+
local keys = table.create (reach) :: { string? }
|
|
78
|
+
local inside = table.create (reach, 0)
|
|
79
|
+
local pulls = table.create (reach) :: { ((any, { number }, number) -> boolean)? }
|
|
80
|
+
local pushes = table.create (reach) :: { (({ number }, number) -> any)? }
|
|
81
|
+
local wheres = table.create (reach) :: { string }
|
|
82
|
+
local wanted = table.create (reach) :: { string }
|
|
83
|
+
|
|
84
|
+
for index, join in joins do
|
|
85
|
+
starts[index] = join.at
|
|
86
|
+
ends[index] = join.at + join.count - 1
|
|
87
|
+
keys[index] = join.key
|
|
88
|
+
inside[index] = join.nest or 0
|
|
89
|
+
pulls[index] = join.pull
|
|
90
|
+
pushes[index] = join.push
|
|
91
|
+
wheres[index] = paths.texts[join.tag.pathId]
|
|
92
|
+
wanted[index] = expected.texts[join.tag.expectId]
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
-- Per window, and then the parts of every window end to end.
|
|
96
|
+
local plan = of.plan
|
|
97
|
+
local windows = #plan
|
|
98
|
+
local width = table.create (windows, 0)
|
|
99
|
+
local where = table.create (windows, 0)
|
|
100
|
+
local first = table.create (windows, 0)
|
|
101
|
+
local last = table.create (windows, 0)
|
|
102
|
+
|
|
103
|
+
local slot: { number } = {}
|
|
104
|
+
local offset: { number } = {}
|
|
105
|
+
local span: { number } = {}
|
|
106
|
+
local shift: { number } = {}
|
|
107
|
+
|
|
108
|
+
for index, window in plan do
|
|
109
|
+
width[index] = window.bytes
|
|
110
|
+
where[index] = window.at
|
|
111
|
+
first[index] = #slot + 1
|
|
112
|
+
|
|
113
|
+
for _, part in window.parts do
|
|
114
|
+
table.insert (slot, part.field)
|
|
115
|
+
table.insert (offset, part.offset)
|
|
116
|
+
table.insert (span, part.width)
|
|
117
|
+
table.insert (shift, part.shift)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
last[index] = #slot
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
--[[
|
|
124
|
+
Every field to its index, then every window to its bytes. In that order rather than one
|
|
125
|
+
pass, because a field can straddle two windows and a window can hold pieces of fields that
|
|
126
|
+
are nowhere near each other in the record.
|
|
127
|
+
]]
|
|
128
|
+
local function pack (b: buffer, pos: number, value: any, slots: { number }, held: { any })
|
|
129
|
+
for index = 1, reach do
|
|
130
|
+
local key = keys[index]
|
|
131
|
+
local within = inside[index]
|
|
132
|
+
local from = if within == 0 then value else held[within]
|
|
133
|
+
|
|
134
|
+
local one = if key == nil then from else from[key]
|
|
135
|
+
local pull = pulls[index]
|
|
136
|
+
|
|
137
|
+
if pull == nil then
|
|
138
|
+
local at = starts[index]
|
|
139
|
+
local made = toIndex[at] (one)
|
|
140
|
+
if made == nil then Word.refused (wheres[index], wanted[index], one) end
|
|
141
|
+
|
|
142
|
+
slots[at] = made :: number
|
|
143
|
+
continue
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
if not pull (one, slots, starts[index]) then
|
|
147
|
+
Word.refused (wheres[index], wanted[index], one)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
for index = 1, windows do
|
|
152
|
+
local acc = 0
|
|
153
|
+
for part = first[index], last[index] do
|
|
154
|
+
local piece = bit32.extract (slots[slot[part] ], shift[part], span[part])
|
|
155
|
+
acc = bit32.bor (acc, bit32.lshift (piece, offset[part]))
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
local at = pos + where[index]
|
|
159
|
+
local bytes = width[index]
|
|
160
|
+
if bytes == ONE then
|
|
161
|
+
buffer.writeu8 (b, at, acc)
|
|
162
|
+
elseif bytes == PAIR then
|
|
163
|
+
buffer.writeu16 (b, at, acc)
|
|
164
|
+
else
|
|
165
|
+
buffer.writeu32 (b, at, acc)
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
--[[
|
|
171
|
+
Every window to its pieces, then every field to its value. A piece at no shift is written
|
|
172
|
+
rather than merged, because what is under it is whatever the last call through this region
|
|
173
|
+
left there, and a field that straddles two windows meets its low piece first.
|
|
174
|
+
]]
|
|
175
|
+
local function unpack (b: buffer, pos: number, into: any, slots: { number }, held: { any }): any
|
|
176
|
+
for _, leaf in empties do
|
|
177
|
+
slots[leaf] = 0
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
for index = 1, windows do
|
|
181
|
+
local at = pos + where[index]
|
|
182
|
+
local bytes = width[index]
|
|
183
|
+
local acc = if bytes == ONE
|
|
184
|
+
then buffer.readu8 (b, at)
|
|
185
|
+
elseif bytes == PAIR then buffer.readu16 (b, at)
|
|
186
|
+
else buffer.readu32 (b, at)
|
|
187
|
+
|
|
188
|
+
for part = first[index], last[index] do
|
|
189
|
+
local piece = bit32.extract (acc, offset[part], span[part])
|
|
190
|
+
local up = shift[part]
|
|
191
|
+
if up == 0 then
|
|
192
|
+
slots[slot[part] ] = piece
|
|
193
|
+
continue
|
|
194
|
+
end
|
|
195
|
+
slots[slot[part] ] = bit32.bor (slots[slot[part] ], bit32.lshift (piece, up))
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
local bare: any = nil
|
|
200
|
+
|
|
201
|
+
for index = 1, reach do
|
|
202
|
+
local join = joins[index]
|
|
203
|
+
for leaf = starts[index], ends[index] do
|
|
204
|
+
if slots[leaf] <= spans[leaf] then continue end
|
|
205
|
+
Log.reject (Catalog.TAG, {
|
|
206
|
+
pathId = join.tag.pathId,
|
|
207
|
+
expectId = join.tag.expectId,
|
|
208
|
+
got = slots[leaf],
|
|
209
|
+
})
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
local push = pushes[index]
|
|
213
|
+
local one = if push == nil
|
|
214
|
+
then fromIndex[starts[index] ] (slots[starts[index] ])
|
|
215
|
+
else push (slots, starts[index])
|
|
216
|
+
|
|
217
|
+
local key = keys[index]
|
|
218
|
+
if key == nil then
|
|
219
|
+
bare = one
|
|
220
|
+
continue
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
local within = inside[index]
|
|
224
|
+
local up = if within == 0 then into else held[within]
|
|
225
|
+
up[key] = one
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
return bare
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
return pack, unpack
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
return table.freeze (Word)
|