@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.
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 -319
  77. package/src/api/Query.luau +0 -338
  78. package/src/api/Scope.luau +0 -90
  79. package/src/api/Signal.luau +0 -188
  80. package/src/codec/Base.luau +0 -136
  81. package/src/codec/composite/Array.luau +0 -456
  82. package/src/codec/composite/Map.luau +0 -469
  83. package/src/codec/composite/Optional.luau +0 -54
  84. package/src/codec/composite/Shared.luau +0 -291
  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 -147
  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 -390
  120. package/src/internal/Middleware.luau +0 -100
  121. package/src/internal/Pool.luau +0 -65
  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 -211
  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,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": "2.3.2",
4
- "description": "Buffer networking for Roblox. Delta compression, XOR framing, built-in security",
5
- "main": "src/init.luau",
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
- "url": "https://github.com/Axp3cter/Lync/issues"
17
- },
18
- "publishConfig": {
19
- "access": "public"
20
- },
21
- "keywords": [
22
- "roblox",
23
- "networking",
24
- "buffer",
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.0",
37
- "typescript": "^5.6.0"
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)