@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
package/kernel/Wide.luau
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--!optimize 2
|
|
3
|
+
-- Sixty-four bits: the platform's own where it has them, and two halves where it has not.
|
|
4
|
+
|
|
5
|
+
local Bits = require (script.Parent.Bits)
|
|
6
|
+
|
|
7
|
+
local Wide = {}
|
|
8
|
+
|
|
9
|
+
-- Constants -----------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
-- Whether this platform carries a sixty-four bit integer.
|
|
12
|
+
local carried: any = integer
|
|
13
|
+
const NATIVE = carried ~= nil
|
|
14
|
+
|
|
15
|
+
--[[
|
|
16
|
+
The piece a half is cut into for a multiply, what a piece and a word count to, and how wide a
|
|
17
|
+
half is.
|
|
18
|
+
]]
|
|
19
|
+
const PIECE = 16
|
|
20
|
+
const PLACE = 65536
|
|
21
|
+
const WORD = PLACE * PLACE
|
|
22
|
+
const HALF = Bits.width (WORD - 1)
|
|
23
|
+
|
|
24
|
+
-- Where the low half of a word sits in the eight bytes it is written as, and where the high half.
|
|
25
|
+
const LOW = 0
|
|
26
|
+
const HIGH = 4
|
|
27
|
+
|
|
28
|
+
-- The base a word is spelled in, and how many of those digits one half spells.
|
|
29
|
+
const BASE = 16
|
|
30
|
+
const DIGITS = 8
|
|
31
|
+
|
|
32
|
+
-- The spelling itself, so the width a half is written to and the width one is read back at agree.
|
|
33
|
+
const SPELLING = `%0{DIGITS}X%0{DIGITS}X`
|
|
34
|
+
|
|
35
|
+
-- Types ---------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
-- A sixty-four bit word, which is one thing to every caller and two to this module.
|
|
38
|
+
export type Word = any
|
|
39
|
+
|
|
40
|
+
-- One carrier: every operation on a word, in the vocabulary this library asks them in.
|
|
41
|
+
export type Carrier = {
|
|
42
|
+
read NONE: Word,
|
|
43
|
+
read bit: (place: number) -> Word,
|
|
44
|
+
read union: (a: Word, b: Word) -> Word,
|
|
45
|
+
read meets: (a: Word, b: Word) -> boolean,
|
|
46
|
+
read any: (of: Word) -> boolean,
|
|
47
|
+
read lowest: (of: Word) -> number,
|
|
48
|
+
read without: (of: Word) -> Word,
|
|
49
|
+
read bxor: (a: Word, b: Word) -> Word,
|
|
50
|
+
read mul: (a: Word, b: Word) -> Word,
|
|
51
|
+
read of: (value: number) -> Word,
|
|
52
|
+
read parse: (digits: string) -> Word,
|
|
53
|
+
read same: (a: Word, b: Word) -> boolean,
|
|
54
|
+
read below: (a: Word, b: Word) -> boolean,
|
|
55
|
+
read read: (b: buffer, at: number) -> Word,
|
|
56
|
+
read write: (b: buffer, at: number, of: Word) -> number,
|
|
57
|
+
read ordered: (b: buffer, at: number) -> Word,
|
|
58
|
+
read text: (of: Word) -> string,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
-- Private -------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
-- The platform's own, which is every operation straight through.
|
|
64
|
+
local SPLIT = if NATIVE then carried.create (HALF) else nil
|
|
65
|
+
local NOTHING = if NATIVE then carried.create (0) else nil
|
|
66
|
+
|
|
67
|
+
local held: Carrier = {
|
|
68
|
+
NONE = NOTHING,
|
|
69
|
+
|
|
70
|
+
bit = function (place: number): Word
|
|
71
|
+
return carried.lshift (carried.create (1), carried.create (place - 1))
|
|
72
|
+
end,
|
|
73
|
+
|
|
74
|
+
union = function (a: Word, b: Word): Word return carried.bor (a, b) end,
|
|
75
|
+
meets = function (a: Word, b: Word): boolean return carried.btest (a, b) end,
|
|
76
|
+
any = function (of: Word): boolean return carried.btest (of) end,
|
|
77
|
+
lowest = function (of: Word): number return carried.tonumber (carried.countrz (of)) + 1 end,
|
|
78
|
+
|
|
79
|
+
without = function (of: Word): Word
|
|
80
|
+
return carried.band (of, carried.sub (of, carried.create (1)))
|
|
81
|
+
end,
|
|
82
|
+
|
|
83
|
+
bxor = function (a: Word, b: Word): Word return carried.bxor (a, b) end,
|
|
84
|
+
mul = function (a: Word, b: Word): Word return carried.mul (a, b) end,
|
|
85
|
+
of = function (value: number): Word return carried.create (value) end,
|
|
86
|
+
parse = function (digits: string): Word return carried.fromstring (digits, BASE) end,
|
|
87
|
+
same = function (a: Word, b: Word): boolean return a == b end,
|
|
88
|
+
below = function (a: Word, b: Word): boolean return carried.ult (a, b) end,
|
|
89
|
+
read = function (b: buffer, at: number): Word return buffer.readinteger (b, at) end,
|
|
90
|
+
|
|
91
|
+
write = function (b: buffer, at: number, of: Word): number
|
|
92
|
+
buffer.writeinteger (b, at, of)
|
|
93
|
+
return at + HIGH + HIGH
|
|
94
|
+
end,
|
|
95
|
+
|
|
96
|
+
ordered = function (b: buffer, at: number): Word
|
|
97
|
+
return carried.bswap (buffer.readinteger (b, at))
|
|
98
|
+
end,
|
|
99
|
+
|
|
100
|
+
text = function (of: Word): string
|
|
101
|
+
return string.format (
|
|
102
|
+
SPELLING,
|
|
103
|
+
carried.tonumber (carried.extract (of, SPLIT, SPLIT)),
|
|
104
|
+
carried.tonumber (carried.extract (of, NOTHING, SPLIT))
|
|
105
|
+
)
|
|
106
|
+
end,
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
-- Two halves, each a `bit32` word, most significant first.
|
|
110
|
+
local halved: Carrier = {
|
|
111
|
+
NONE = table.freeze ({ 0, 0 }),
|
|
112
|
+
|
|
113
|
+
bit = function (place: number): Word
|
|
114
|
+
local value = 2 ^ (place - 1)
|
|
115
|
+
if value < WORD then return table.freeze ({ 0, value }) end
|
|
116
|
+
|
|
117
|
+
return table.freeze ({ value / WORD, 0 })
|
|
118
|
+
end,
|
|
119
|
+
|
|
120
|
+
union = function (a: Word, b: Word): Word
|
|
121
|
+
return { bit32.bor (a[1], b[1]), bit32.bor (a[2], b[2]) }
|
|
122
|
+
end,
|
|
123
|
+
|
|
124
|
+
meets = function (a: Word, b: Word): boolean
|
|
125
|
+
return bit32.btest (a[1], b[1]) or bit32.btest (a[2], b[2])
|
|
126
|
+
end,
|
|
127
|
+
|
|
128
|
+
any = function (of: Word): boolean return of[1] ~= 0 or of[2] ~= 0 end,
|
|
129
|
+
|
|
130
|
+
lowest = function (of: Word): number
|
|
131
|
+
if of[2] ~= 0 then return bit32.countrz (of[2]) + 1 end
|
|
132
|
+
return bit32.countrz (of[1]) + HALF + 1
|
|
133
|
+
end,
|
|
134
|
+
|
|
135
|
+
-- The lowest set bit cleared, which is the one place a half borrows from nothing.
|
|
136
|
+
without = function (of: Word): Word
|
|
137
|
+
if of[2] ~= 0 then return { of[1], bit32.band (of[2], of[2] - 1) } end
|
|
138
|
+
return { bit32.band (of[1], of[1] - 1), 0 }
|
|
139
|
+
end,
|
|
140
|
+
|
|
141
|
+
bxor = function (a: Word, b: Word): Word
|
|
142
|
+
return { bit32.bxor (a[1], b[1]), bit32.bxor (a[2], b[2]) }
|
|
143
|
+
end,
|
|
144
|
+
|
|
145
|
+
-- A product modulo 2^64, in four sixteen bit pieces.
|
|
146
|
+
mul = function (a: Word, b: Word): Word
|
|
147
|
+
local a0 = bit32.band (a[2], PLACE - 1)
|
|
148
|
+
local a1 = bit32.rshift (a[2], PIECE)
|
|
149
|
+
local a2 = bit32.band (a[1], PLACE - 1)
|
|
150
|
+
local a3 = bit32.rshift (a[1], PIECE)
|
|
151
|
+
|
|
152
|
+
local b0 = bit32.band (b[2], PLACE - 1)
|
|
153
|
+
local b1 = bit32.rshift (b[2], PIECE)
|
|
154
|
+
local b2 = bit32.band (b[1], PLACE - 1)
|
|
155
|
+
local b3 = bit32.rshift (b[1], PIECE)
|
|
156
|
+
|
|
157
|
+
local c0 = a0 * b0
|
|
158
|
+
local c1 = a0 * b1 + a1 * b0
|
|
159
|
+
local c2 = a0 * b2 + a1 * b1 + a2 * b0
|
|
160
|
+
local c3 = a0 * b3 + a1 * b2 + a2 * b1 + a3 * b0
|
|
161
|
+
|
|
162
|
+
local r0 = c0 % PLACE
|
|
163
|
+
local up = (c0 - r0) / PLACE + c1
|
|
164
|
+
|
|
165
|
+
local r1 = up % PLACE
|
|
166
|
+
up = (up - r1) / PLACE + c2
|
|
167
|
+
|
|
168
|
+
local r2 = up % PLACE
|
|
169
|
+
up = (up - r2) / PLACE + c3
|
|
170
|
+
|
|
171
|
+
local r3 = up % PLACE
|
|
172
|
+
return { r3 * PLACE + r2, r1 * PLACE + r0 }
|
|
173
|
+
end,
|
|
174
|
+
|
|
175
|
+
of = function (value: number): Word return { 0, value } end,
|
|
176
|
+
|
|
177
|
+
parse = function (digits: string): Word
|
|
178
|
+
local high = tonumber (string.sub (digits, 1, #digits - DIGITS), BASE)
|
|
179
|
+
local low = tonumber (string.sub (digits, #digits - DIGITS + 1), BASE)
|
|
180
|
+
|
|
181
|
+
return { high or 0, low or 0 }
|
|
182
|
+
end,
|
|
183
|
+
|
|
184
|
+
same = function (a: Word, b: Word): boolean return a[1] == b[1] and a[2] == b[2] end,
|
|
185
|
+
|
|
186
|
+
below = function (a: Word, b: Word): boolean
|
|
187
|
+
if a[1] ~= b[1] then return a[1] < b[1] end
|
|
188
|
+
return a[2] < b[2]
|
|
189
|
+
end,
|
|
190
|
+
|
|
191
|
+
-- The low half first, which is where a little endian sixty-four bit read finds it.
|
|
192
|
+
read = function (b: buffer, at: number): Word
|
|
193
|
+
return { buffer.readu32 (b, at + HIGH), buffer.readu32 (b, at + LOW) }
|
|
194
|
+
end,
|
|
195
|
+
|
|
196
|
+
write = function (b: buffer, at: number, of: Word): number
|
|
197
|
+
buffer.writeu32 (b, at + LOW, of[2])
|
|
198
|
+
buffer.writeu32 (b, at + HIGH, of[1])
|
|
199
|
+
return at + HIGH + HIGH
|
|
200
|
+
end,
|
|
201
|
+
|
|
202
|
+
-- The first byte most significant, which is what a bytewise order compares.
|
|
203
|
+
ordered = function (b: buffer, at: number): Word
|
|
204
|
+
return {
|
|
205
|
+
bit32.byteswap (buffer.readu32 (b, at + LOW)),
|
|
206
|
+
bit32.byteswap (buffer.readu32 (b, at + HIGH)),
|
|
207
|
+
}
|
|
208
|
+
end,
|
|
209
|
+
|
|
210
|
+
text = function (of: Word): string return string.format (SPELLING, of[1], of[2]) end,
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
local chosen = if NATIVE then held else halved
|
|
214
|
+
|
|
215
|
+
-- Every bit that has been asked for, made once.
|
|
216
|
+
local bits: { Word } = {}
|
|
217
|
+
|
|
218
|
+
-- Public --------------------------------------------------------------
|
|
219
|
+
|
|
220
|
+
--[[
|
|
221
|
+
Which carrier this build got. Reported so a place can say so, and read by the suites that hold
|
|
222
|
+
the two to each other wherever both exist.
|
|
223
|
+
]]
|
|
224
|
+
Wide.native = NATIVE
|
|
225
|
+
|
|
226
|
+
-- Both carriers, under names, where this build has both.
|
|
227
|
+
local both: { [string]: Carrier } = { halves = halved }
|
|
228
|
+
if NATIVE then both.own = held end
|
|
229
|
+
|
|
230
|
+
Wide.carriers = table.freeze (both)
|
|
231
|
+
|
|
232
|
+
-- A word of no bits, which is what a mask starts as and what an empty union comes to.
|
|
233
|
+
Wide.NONE = chosen.NONE
|
|
234
|
+
|
|
235
|
+
-- Everything a word is asked, in the vocabulary the library asks it in rather than the platform's.
|
|
236
|
+
Wide.union = chosen.union
|
|
237
|
+
Wide.meets = chosen.meets
|
|
238
|
+
Wide.any = chosen.any
|
|
239
|
+
Wide.lowest = chosen.lowest
|
|
240
|
+
Wide.without = chosen.without
|
|
241
|
+
Wide.bxor = chosen.bxor
|
|
242
|
+
Wide.mul = chosen.mul
|
|
243
|
+
Wide.of = chosen.of
|
|
244
|
+
Wide.parse = chosen.parse
|
|
245
|
+
Wide.same = chosen.same
|
|
246
|
+
Wide.below = chosen.below
|
|
247
|
+
Wide.read = chosen.read
|
|
248
|
+
Wide.write = chosen.write
|
|
249
|
+
Wide.ordered = chosen.ordered
|
|
250
|
+
Wide.text = chosen.text
|
|
251
|
+
|
|
252
|
+
--[[
|
|
253
|
+
One bit at a place, counted from one the way a field index is.
|
|
254
|
+
|
|
255
|
+
Answered from the pile above where it has been asked for before, which is every ask after the
|
|
256
|
+
first for any place a schema actually names.
|
|
257
|
+
]]
|
|
258
|
+
function Wide.bit (place: number): Word
|
|
259
|
+
local found = bits[place]
|
|
260
|
+
if found ~= nil then return found end
|
|
261
|
+
|
|
262
|
+
found = chosen.bit (place)
|
|
263
|
+
bits[place] = found
|
|
264
|
+
return found
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
return table.freeze (Wide)
|
package/package.json
CHANGED
|
@@ -1,39 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axpecter/lync",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
6
|
-
"types": "src/index.d.ts",
|
|
7
|
-
"module": "commonjs",
|
|
8
|
-
"author": "Axp3cter",
|
|
9
|
-
"license": "MIT",
|
|
10
|
-
"repository": {
|
|
11
|
-
"type": "git",
|
|
12
|
-
"url": "git+https://github.com/Axp3cter/Lync.git"
|
|
13
|
-
},
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"description": "Typed buffer networking for Roblox: packets, queries, and replicated sets.",
|
|
5
|
+
"keywords": ["roblox", "roblox-ts", "luau", "networking", "buffer", "packets", "replication"],
|
|
14
6
|
"homepage": "https://github.com/Axp3cter/Lync",
|
|
15
|
-
"bugs": {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
"delta",
|
|
26
|
-
"compression",
|
|
27
|
-
"roblox-ts",
|
|
28
|
-
"luau"
|
|
29
|
-
],
|
|
30
|
-
"files": [
|
|
31
|
-
"src",
|
|
32
|
-
"README.md"
|
|
33
|
-
],
|
|
7
|
+
"bugs": { "url": "https://github.com/Axp3cter/Lync/issues" },
|
|
8
|
+
"repository": { "type": "git", "url": "git+https://github.com/Axp3cter/Lync.git" },
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"author": "Axp3cter",
|
|
11
|
+
"main": "init.luau",
|
|
12
|
+
"types": "index.d.ts",
|
|
13
|
+
"files": ["**/*.luau", "index.d.ts"],
|
|
14
|
+
"scripts": { "check": "tsc" },
|
|
15
|
+
"publishConfig": { "access": "public" },
|
|
16
|
+
"peerDependencies": { "@rbxts/types": "^1.0.0" },
|
|
34
17
|
"devDependencies": {
|
|
35
18
|
"@rbxts/compiler-types": "^3.0.0-types.0",
|
|
36
|
-
"@rbxts/types": "^1.0.
|
|
37
|
-
"typescript": "
|
|
19
|
+
"@rbxts/types": "^1.0.941",
|
|
20
|
+
"typescript": "5.5.3"
|
|
38
21
|
}
|
|
39
22
|
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--!optimize 2
|
|
3
|
+
-- The trust split: bytes from a client are hostile, bytes from the server are this library's own.
|
|
4
|
+
|
|
5
|
+
local Catalog = require (script.Parent.Parent.kernel.Catalog)
|
|
6
|
+
local Compile = require (script.Parent.Parent.shape.Compile)
|
|
7
|
+
local Log = require (script.Parent.Parent.kernel.Log)
|
|
8
|
+
local Serial = require (script.Parent.Parent.shape.Serial)
|
|
9
|
+
local Tally = require (script.Parent.Parent.kernel.Tally)
|
|
10
|
+
local Types = require (script.Parent.Parent.Types)
|
|
11
|
+
|
|
12
|
+
local Boundary = {}
|
|
13
|
+
|
|
14
|
+
-- Constants -----------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
-- What a drop is on each lane, which is the whole of what the trust split comes to afterwards.
|
|
17
|
+
const HOSTILE: Types.LogKind = "warn"
|
|
18
|
+
const TRUSTED: Types.LogKind = "error"
|
|
19
|
+
|
|
20
|
+
-- Types ---------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
-- What a validate is handed, as the record this module keeps rather than the read one it is.
|
|
23
|
+
export type Context = {
|
|
24
|
+
player: Player?,
|
|
25
|
+
now: number,
|
|
26
|
+
last: number?,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
-- The two tables a rejection's numbers are read back through.
|
|
30
|
+
export type Names = {
|
|
31
|
+
read paths: Serial.Interner,
|
|
32
|
+
read expected: Serial.Interner,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
-- One link's boundary: the two contexts, the arrival stamps, and whether this frame has a cursor.
|
|
36
|
+
export type Boundary = {
|
|
37
|
+
read name: string,
|
|
38
|
+
read tally: Tally.Tally,
|
|
39
|
+
read hostile: Context,
|
|
40
|
+
read trusted: Context,
|
|
41
|
+
read stamps: { [Player]: { [number]: number } },
|
|
42
|
+
read heard: { [number]: number },
|
|
43
|
+
ctx: Context,
|
|
44
|
+
live: boolean,
|
|
45
|
+
took: boolean,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
-- Private -------------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
-- An empty context, which is what a link holds two of for as long as it is open.
|
|
51
|
+
local function context (): Context return { player = nil, now = 0, last = nil } end
|
|
52
|
+
|
|
53
|
+
-- What one sender's definitions were last heard at, made the first time they are heard at all.
|
|
54
|
+
local function marks (of: Boundary, from: Player): { [number]: number }
|
|
55
|
+
local held = of.stamps[from]
|
|
56
|
+
if held ~= nil then return held end
|
|
57
|
+
|
|
58
|
+
held = {}
|
|
59
|
+
of.stamps[from] = held
|
|
60
|
+
return held
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
-- Where the reader got to, for a failure that can be asked.
|
|
64
|
+
local function resumed (thrown: unknown): number?
|
|
65
|
+
if typeof (thrown) ~= "table" then return nil end
|
|
66
|
+
|
|
67
|
+
local rejection = thrown :: Log.Rejection
|
|
68
|
+
if rejection.code ~= Catalog.VALIDATE then return nil end
|
|
69
|
+
|
|
70
|
+
local at = rejection.facts.at
|
|
71
|
+
return if typeof (at) == "number" then at else nil
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
-- A rejection's numbers as the words they were interned from.
|
|
75
|
+
local function diagnosed (thrown: unknown, names: Names, about: Log.Facts)
|
|
76
|
+
if typeof (thrown) ~= "table" then return end
|
|
77
|
+
|
|
78
|
+
local facts = (thrown :: Log.Rejection).facts
|
|
79
|
+
local path = facts.pathId
|
|
80
|
+
local expected = facts.expectId
|
|
81
|
+
|
|
82
|
+
if typeof (path) == "number" then about.path = names.paths.texts[path] end
|
|
83
|
+
if typeof (expected) == "number" then about.expected = names.expected.texts[expected] end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
-- Public --------------------------------------------------------------
|
|
87
|
+
|
|
88
|
+
-- One link's boundary, before anything has arrived.
|
|
89
|
+
function Boundary.new (name: string, tally: Tally.Tally): Boundary
|
|
90
|
+
local hostile = context ()
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
name = name,
|
|
94
|
+
tally = tally,
|
|
95
|
+
hostile = hostile,
|
|
96
|
+
trusted = context (),
|
|
97
|
+
stamps = {},
|
|
98
|
+
heard = {},
|
|
99
|
+
ctx = hostile,
|
|
100
|
+
live = true,
|
|
101
|
+
took = false,
|
|
102
|
+
}
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
-- A frame arriving, and the context every payload in it is read under.
|
|
106
|
+
function Boundary.opened (of: Boundary, from: Player?, now: number)
|
|
107
|
+
local ctx = if from == nil then of.trusted else of.hostile
|
|
108
|
+
|
|
109
|
+
ctx.player = from
|
|
110
|
+
ctx.now = now
|
|
111
|
+
ctx.last = nil
|
|
112
|
+
|
|
113
|
+
of.ctx = ctx
|
|
114
|
+
of.live = true
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
-- One definition heard from one sender, stamped whether or not what follows decodes.
|
|
118
|
+
function Boundary.stamped (of: Boundary, from: Player?, id: number, now: number)
|
|
119
|
+
local held = if from == nil then of.heard else marks (of, from)
|
|
120
|
+
|
|
121
|
+
of.ctx.last = held[id]
|
|
122
|
+
held[id] = now
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
-- One payload or one entry, decoded under containment, and the byte past it.
|
|
126
|
+
function Boundary.ran (
|
|
127
|
+
of: Boundary,
|
|
128
|
+
def: Compile.Def,
|
|
129
|
+
names: Names,
|
|
130
|
+
take: (buffer, number, Types.ValidateContext) -> (number, any),
|
|
131
|
+
b: buffer,
|
|
132
|
+
pos: number
|
|
133
|
+
): (number?, any)
|
|
134
|
+
local ok, at, value = pcall (take, b, pos, of.ctx)
|
|
135
|
+
if ok then
|
|
136
|
+
of.took = true
|
|
137
|
+
return at, value
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
of.took = false
|
|
141
|
+
|
|
142
|
+
local ends = resumed (at)
|
|
143
|
+
if ends == nil then of.live = false end
|
|
144
|
+
|
|
145
|
+
local about: Log.Facts = {
|
|
146
|
+
definition = def.name,
|
|
147
|
+
file = def.file,
|
|
148
|
+
line = def.line,
|
|
149
|
+
player = of.ctx.player,
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
diagnosed (at, names, about)
|
|
153
|
+
Log.dropped (at, about, if of.ctx.player == nil then TRUSTED else HOSTILE)
|
|
154
|
+
Tally.dropped (of.tally, def.name)
|
|
155
|
+
|
|
156
|
+
return ends, nil
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
-- A failure about the frame rather than about anything in it, which closes it.
|
|
160
|
+
function Boundary.lost (of: Boundary, code: Catalog.Code, facts: Log.Facts)
|
|
161
|
+
of.live = false
|
|
162
|
+
|
|
163
|
+
local raised = { code = code, facts = facts }
|
|
164
|
+
Log.dropped (
|
|
165
|
+
raised,
|
|
166
|
+
{ player = of.ctx.player },
|
|
167
|
+
if of.ctx.player == nil then TRUSTED else HOSTILE
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
--[[
|
|
171
|
+
Against the namespace and no definition, which is what a count with no name to charge is
|
|
172
|
+
for. What went wrong here is exactly which definition the frame said it was.
|
|
173
|
+
]]
|
|
174
|
+
Tally.dropped (of.tally, nil)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
-- A sender gone, and the stamps that were only ever about them.
|
|
178
|
+
function Boundary.left (of: Boundary, who: Player) of.stamps[who] = nil end
|
|
179
|
+
|
|
180
|
+
return table.freeze (Boundary)
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--!optimize 2
|
|
3
|
+
-- Where a decoded definition goes: the listeners on one, its lone responder, and neither's mistake.
|
|
4
|
+
|
|
5
|
+
local Listeners = require (script.Parent.Parent.kernel.Listeners)
|
|
6
|
+
local Log = require (script.Parent.Parent.kernel.Log)
|
|
7
|
+
local Types = require (script.Parent.Parent.Types)
|
|
8
|
+
|
|
9
|
+
local Dispatch = {}
|
|
10
|
+
|
|
11
|
+
-- Types ---------------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
--[[
|
|
14
|
+
A query's one answer. What it is handed is its side's business and not this module's: a server
|
|
15
|
+
responder reads a value and a player, a client responder reads a value, and a dispatch that knew
|
|
16
|
+
which would be a second home for a rule the surface already states twice.
|
|
17
|
+
]]
|
|
18
|
+
export type Responder = (...any) -> any
|
|
19
|
+
|
|
20
|
+
--[[
|
|
21
|
+
Everything a machine has attached, keyed by the id the wire names. Held by whoever owns the
|
|
22
|
+
namespace rather than here, because two namespaces in one place are two of these and a module
|
|
23
|
+
keeping them itself would be state that depends on which of them loaded first.
|
|
24
|
+
]]
|
|
25
|
+
export type Wiring = {
|
|
26
|
+
heard: { [number]: Listeners.List<...any> },
|
|
27
|
+
answering: { [number]: Responder },
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
-- Private -------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
-- A definition nothing on this machine listens for.
|
|
33
|
+
local function unheard (name: string)
|
|
34
|
+
local said = `nothing on this machine listens for {name}`
|
|
35
|
+
local facts: Log.Facts = { definition = name }
|
|
36
|
+
--[[
|
|
37
|
+
Not two reports but two builds. The raise never returns, so a place being worked on stops
|
|
38
|
+
on the mistake, and the line under it is what a shipped one does with the same fact.
|
|
39
|
+
]]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
Log.warn (said, facts)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
-- Public --------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
function Dispatch.wiring (): Wiring return { heard = {}, answering = {} } end
|
|
48
|
+
|
|
49
|
+
-- One more listener on a definition, at the end of the order, and the connection that ends it.
|
|
50
|
+
function Dispatch.listen (of: Wiring, id: number, fn: (...any) -> ()): Types.Connection
|
|
51
|
+
local heard = of.heard[id]
|
|
52
|
+
if heard == nil then
|
|
53
|
+
heard = Listeners.list (Log.contained :: Listeners.Run<...any>)
|
|
54
|
+
of.heard[id] = heard
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
return Listeners.connect (heard :: Listeners.List<...any>, fn)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
--[[
|
|
61
|
+
The one responder a query gets, and a second is refused at once: by reply time both exist, and
|
|
62
|
+
whichever was picked would be right half the time and silent about the rest.
|
|
63
|
+
]]
|
|
64
|
+
function Dispatch.respond (of: Wiring, id: number, name: string, fn: Responder)
|
|
65
|
+
if of.answering[id] ~= nil then
|
|
66
|
+
Log.error (`{name} already has a responder, and a query has one`, { definition = name })
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
of.answering[id] = fn
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
-- A decoded packet, to everything attached, in the order it was attached.
|
|
73
|
+
function Dispatch.packet (of: Wiring, id: number, name: string, ...: any)
|
|
74
|
+
local heard = of.heard[id]
|
|
75
|
+
if heard == nil or heard.live == 0 then
|
|
76
|
+
unheard (name)
|
|
77
|
+
return
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
Listeners.fan (heard, ...)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
-- A decoded request, run, and whether there is anything to send back.
|
|
84
|
+
function Dispatch.request (of: Wiring, id: number, ...: any): (boolean, any)
|
|
85
|
+
local fn = of.answering[id]
|
|
86
|
+
if fn == nil then return false, nil end
|
|
87
|
+
|
|
88
|
+
return Log.caught (fn, ...)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
return table.freeze (Dispatch)
|