@axpecter/lync 2.3.3 → 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.
Files changed (135) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +302 -299
  3. package/Types.luau +215 -0
  4. package/api/Define.luau +154 -0
  5. package/api/Lifecycle.luau +119 -0
  6. package/api/Packet.luau +108 -0
  7. package/api/Query.luau +149 -0
  8. package/api/Set.luau +189 -0
  9. package/channel/Blocks.luau +124 -0
  10. package/channel/Group.luau +118 -0
  11. package/channel/Link.luau +135 -0
  12. package/channel/Outbox.luau +261 -0
  13. package/channel/Recipients.luau +129 -0
  14. package/channel/Transport.luau +138 -0
  15. package/codec/Base.luau +236 -0
  16. package/codec/Composites.luau +128 -0
  17. package/codec/Datatypes.luau +162 -0
  18. package/codec/Legality.luau +61 -0
  19. package/codec/Scalars.luau +72 -0
  20. package/codec/Text.luau +89 -0
  21. package/codec/init.luau +39 -0
  22. package/index.d.ts +285 -0
  23. package/init.luau +91 -0
  24. package/kernel/Bits.luau +16 -0
  25. package/kernel/Catalog.luau +121 -0
  26. package/kernel/Heap.luau +80 -0
  27. package/kernel/Listeners.luau +95 -0
  28. package/kernel/Log.luau +197 -0
  29. package/kernel/Tally.luau +128 -0
  30. package/kernel/Threads.luau +44 -0
  31. package/kernel/Wide.luau +267 -0
  32. package/package.json +15 -32
  33. package/runtime/Boundary.luau +180 -0
  34. package/runtime/Dispatch.luau +91 -0
  35. package/runtime/Handles.luau +114 -0
  36. package/runtime/Handshake.luau +90 -0
  37. package/runtime/Pending.luau +196 -0
  38. package/runtime/Router.luau +379 -0
  39. package/runtime/Session.luau +731 -0
  40. package/shape/Compile.luau +340 -0
  41. package/shape/Folds.luau +332 -0
  42. package/shape/Frames.luau +170 -0
  43. package/shape/Grid.luau +169 -0
  44. package/shape/Ir.luau +540 -0
  45. package/shape/Joins.luau +125 -0
  46. package/shape/Legality.luau +273 -0
  47. package/shape/Lower.luau +419 -0
  48. package/shape/Order.luau +64 -0
  49. package/shape/Regions.luau +113 -0
  50. package/shape/Rotation.luau +472 -0
  51. package/shape/Serial.luau +258 -0
  52. package/shape/Words.luau +89 -0
  53. package/shape/emit/Block.luau +453 -0
  54. package/shape/emit/Column.luau +338 -0
  55. package/shape/emit/Leaf.luau +462 -0
  56. package/shape/emit/Word.luau +234 -0
  57. package/shape/emit/init.luau +222 -0
  58. package/shape/init.luau +78 -0
  59. package/state/Apply.luau +330 -0
  60. package/state/Audience.luau +126 -0
  61. package/state/Catchup.luau +218 -0
  62. package/state/Coalesce.luau +43 -0
  63. package/state/Fieldset.luau +177 -0
  64. package/state/Newest.luau +339 -0
  65. package/state/Plan.luau +441 -0
  66. package/state/Sections.luau +468 -0
  67. package/state/Store.luau +188 -0
  68. package/state/Sync.luau +762 -0
  69. package/wire/Bytes.luau +118 -0
  70. package/wire/Clock.luau +37 -0
  71. package/wire/Protocol.luau +110 -0
  72. package/wire/Sequence.luau +40 -0
  73. package/wire/Varint.luau +124 -0
  74. package/src/Types.luau +0 -180
  75. package/src/api/Group.luau +0 -156
  76. package/src/api/Packet.luau +0 -317
  77. package/src/api/Query.luau +0 -338
  78. package/src/api/Scope.luau +0 -90
  79. package/src/api/Signal.luau +0 -193
  80. package/src/codec/Base.luau +0 -136
  81. package/src/codec/composite/Array.luau +0 -473
  82. package/src/codec/composite/Map.luau +0 -485
  83. package/src/codec/composite/Optional.luau +0 -54
  84. package/src/codec/composite/Shared.luau +0 -311
  85. package/src/codec/composite/Struct.luau +0 -494
  86. package/src/codec/composite/Tagged.luau +0 -103
  87. package/src/codec/composite/Tuple.luau +0 -143
  88. package/src/codec/datatype/Buffer.luau +0 -66
  89. package/src/codec/datatype/CFrame.luau +0 -109
  90. package/src/codec/datatype/Color.luau +0 -32
  91. package/src/codec/datatype/Instance.luau +0 -52
  92. package/src/codec/datatype/IntVector.luau +0 -39
  93. package/src/codec/datatype/NumberRange.luau +0 -39
  94. package/src/codec/datatype/Ray.luau +0 -36
  95. package/src/codec/datatype/Rect.luau +0 -31
  96. package/src/codec/datatype/Region.luau +0 -62
  97. package/src/codec/datatype/Sequence.luau +0 -184
  98. package/src/codec/datatype/String.luau +0 -94
  99. package/src/codec/datatype/UDim.luau +0 -46
  100. package/src/codec/datatype/Vector.luau +0 -130
  101. package/src/codec/meta/Auto.luau +0 -318
  102. package/src/codec/meta/Bitfield.luau +0 -160
  103. package/src/codec/meta/Custom.luau +0 -33
  104. package/src/codec/meta/DeltaScalar.luau +0 -390
  105. package/src/codec/meta/Enum.luau +0 -50
  106. package/src/codec/meta/Float.luau +0 -38
  107. package/src/codec/meta/Nothing.luau +0 -13
  108. package/src/codec/meta/Unknown.luau +0 -41
  109. package/src/codec/primitive/Bool.luau +0 -26
  110. package/src/codec/primitive/Float16.luau +0 -99
  111. package/src/codec/primitive/Int.luau +0 -59
  112. package/src/codec/primitive/Number.luau +0 -24
  113. package/src/codec/primitive/Signed.luau +0 -32
  114. package/src/codec/primitive/Varint.luau +0 -165
  115. package/src/codec/primitive/Zint.luau +0 -99
  116. package/src/index.d.ts +0 -423
  117. package/src/init.luau +0 -362
  118. package/src/internal/Baseline.luau +0 -57
  119. package/src/internal/Channel.luau +0 -484
  120. package/src/internal/Middleware.luau +0 -100
  121. package/src/internal/Pool.luau +0 -66
  122. package/src/internal/Registry.luau +0 -119
  123. package/src/internal/Transport.luau +0 -28
  124. package/src/transport/Bridge.luau +0 -110
  125. package/src/transport/Client.luau +0 -97
  126. package/src/transport/Gate.luau +0 -488
  127. package/src/transport/Reader.luau +0 -331
  128. package/src/transport/Server.luau +0 -233
  129. package/src/util/Array.luau +0 -18
  130. package/src/util/Buffer.luau +0 -90
  131. package/src/util/Constants.luau +0 -30
  132. package/src/util/Log.luau +0 -68
  133. package/src/util/Player.luau +0 -14
  134. package/src/util/Quantize.luau +0 -84
  135. 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)