@trioforge/crucible 0.1.0 → 0.3.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 (145) hide show
  1. package/maths/spline/package.json +4 -0
  2. package/out/client/index.d.ts +7 -0
  3. package/out/client/init.luau +36 -1
  4. package/out/configs.d.ts +37 -0
  5. package/out/configs.luau +46 -0
  6. package/out/data.d.ts +224 -0
  7. package/out/data.luau +97 -0
  8. package/out/emit/configs.d.ts +4 -0
  9. package/out/emit/configs.luau +22 -0
  10. package/out/emit/data.d.ts +6 -0
  11. package/out/emit/data.luau +19 -0
  12. package/out/emit/index.d.ts +5 -1
  13. package/out/emit/init.luau +23 -7
  14. package/out/emit/network.d.ts +6 -0
  15. package/out/emit/network.luau +36 -0
  16. package/out/emit/replicated.d.ts +4 -0
  17. package/out/emit/replicated.luau +13 -0
  18. package/out/index.d.ts +8 -0
  19. package/out/init.luau +23 -0
  20. package/out/internal/binding.luau +5 -0
  21. package/out/internal/boot.luau +112 -8
  22. package/out/internal/configs/limits.d.ts +10 -0
  23. package/out/internal/configs/limits.luau +25 -0
  24. package/out/internal/configs/port.d.ts +29 -0
  25. package/out/internal/configs/port.luau +99 -0
  26. package/out/internal/configs/runtime.d.ts +21 -0
  27. package/out/internal/configs/runtime.luau +981 -0
  28. package/out/internal/data/admission.d.ts +1 -0
  29. package/out/internal/data/admission.luau +673 -0
  30. package/out/internal/data/declarations.d.ts +1 -0
  31. package/out/internal/data/declarations.luau +384 -0
  32. package/out/internal/data/envelope.d.ts +1 -0
  33. package/out/internal/data/envelope.luau +318 -0
  34. package/out/internal/data/handles.d.ts +1 -0
  35. package/out/internal/data/handles.luau +255 -0
  36. package/out/internal/data/inbox-transforms.d.ts +1 -0
  37. package/out/internal/data/inbox-transforms.luau +458 -0
  38. package/out/internal/data/inbox.d.ts +1 -0
  39. package/out/internal/data/inbox.luau +437 -0
  40. package/out/internal/data/lease.d.ts +1 -0
  41. package/out/internal/data/lease.luau +304 -0
  42. package/out/internal/data/limits.d.ts +1 -0
  43. package/out/internal/data/limits.luau +234 -0
  44. package/out/internal/data/messages.d.ts +1 -0
  45. package/out/internal/data/messages.luau +69 -0
  46. package/out/internal/data/operation-id.d.ts +1 -0
  47. package/out/internal/data/operation-id.luau +61 -0
  48. package/out/internal/data/paths.d.ts +2 -0
  49. package/out/internal/data/paths.luau +323 -0
  50. package/out/internal/data/port.d.ts +1 -0
  51. package/out/internal/data/port.luau +290 -0
  52. package/out/internal/data/profiles.d.ts +1 -0
  53. package/out/internal/data/profiles.luau +862 -0
  54. package/out/internal/data/receipts.d.ts +1 -0
  55. package/out/internal/data/receipts.luau +190 -0
  56. package/out/internal/data/record-bindings.d.ts +1 -0
  57. package/out/internal/data/record-bindings.luau +92 -0
  58. package/out/internal/data/record-declarations.d.ts +1 -0
  59. package/out/internal/data/record-declarations.luau +140 -0
  60. package/out/internal/data/records.d.ts +1 -0
  61. package/out/internal/data/records.luau +400 -0
  62. package/out/internal/data/runtime.d.ts +1 -0
  63. package/out/internal/data/runtime.luau +918 -0
  64. package/out/internal/data/scheduler.d.ts +1 -0
  65. package/out/internal/data/scheduler.luau +507 -0
  66. package/out/internal/data/session.d.ts +1 -0
  67. package/out/internal/data/session.luau +234 -0
  68. package/out/internal/data/shutdown.d.ts +1 -0
  69. package/out/internal/data/shutdown.luau +91 -0
  70. package/out/internal/data/waiters.d.ts +1 -0
  71. package/out/internal/data/waiters.luau +65 -0
  72. package/out/internal/lifecycle.luau +45 -0
  73. package/out/internal/network/catalog.d.ts +26 -0
  74. package/out/internal/network/catalog.luau +182 -0
  75. package/out/internal/network/claims.d.ts +1 -0
  76. package/out/internal/network/claims.luau +146 -0
  77. package/out/internal/network/client-session.d.ts +1 -0
  78. package/out/internal/network/client-session.luau +405 -0
  79. package/out/internal/network/codec.d.ts +63 -0
  80. package/out/internal/network/codec.luau +1183 -0
  81. package/out/internal/network/envelope.d.ts +1 -0
  82. package/out/internal/network/envelope.luau +247 -0
  83. package/out/internal/network/limits.d.ts +26 -0
  84. package/out/internal/network/limits.luau +55 -0
  85. package/out/internal/network/port.d.ts +1 -0
  86. package/out/internal/network/port.luau +19 -0
  87. package/out/internal/network/roblox-network-port.d.ts +1 -0
  88. package/out/internal/network/roblox-network-port.luau +105 -0
  89. package/out/internal/network/schema.d.ts +72 -0
  90. package/out/internal/network/schema.luau +386 -0
  91. package/out/internal/network/server-session.d.ts +1 -0
  92. package/out/internal/network/server-session.luau +395 -0
  93. package/out/internal/network/session.d.ts +1 -0
  94. package/out/internal/network/session.luau +266 -0
  95. package/out/internal/reactive/context.d.ts +1 -0
  96. package/out/internal/reactive/context.luau +149 -0
  97. package/out/internal/reactive/flush.d.ts +1 -0
  98. package/out/internal/reactive/flush.luau +355 -0
  99. package/out/internal/reactive/graph.d.ts +1 -0
  100. package/out/internal/reactive/graph.luau +626 -0
  101. package/out/internal/reactive/limits.d.ts +1 -0
  102. package/out/internal/reactive/limits.luau +54 -0
  103. package/out/internal/reactive/ownership.d.ts +1 -0
  104. package/out/internal/reactive/ownership.luau +626 -0
  105. package/out/internal/registry.luau +10 -1
  106. package/out/internal/sync/client-sync.d.ts +1 -0
  107. package/out/internal/sync/client-sync.luau +157 -0
  108. package/out/internal/sync/declarations.d.ts +1 -0
  109. package/out/internal/sync/declarations.luau +276 -0
  110. package/out/internal/sync/frame.d.ts +1 -0
  111. package/out/internal/sync/frame.luau +91 -0
  112. package/out/internal/sync/server-sync.d.ts +1 -0
  113. package/out/internal/sync/server-sync.luau +502 -0
  114. package/out/internal/sync/values.d.ts +1 -0
  115. package/out/internal/sync/values.luau +930 -0
  116. package/out/maths/spline/curve.d.ts +35 -0
  117. package/out/maths/spline/curve.luau +1122 -0
  118. package/out/maths/spline/index.d.ts +2 -0
  119. package/out/maths/spline/init.luau +8 -0
  120. package/out/network.d.ts +66 -0
  121. package/out/network.luau +24 -0
  122. package/out/reactive.d.ts +76 -0
  123. package/out/reactive.luau +136 -0
  124. package/out/replicated.d.ts +38 -0
  125. package/out/replicated.luau +45 -0
  126. package/out/server/data.d.ts +54 -0
  127. package/out/server/data.luau +44 -0
  128. package/out/server/index.d.ts +17 -0
  129. package/out/server/init.luau +58 -4
  130. package/out/server/publish.d.ts +20 -0
  131. package/out/server/publish.luau +24 -0
  132. package/out/testing/index.d.ts +38 -0
  133. package/out/testing/init.luau +189 -0
  134. package/out/util/random/index.d.ts +24 -0
  135. package/out/util/random/init.luau +301 -0
  136. package/out/vide/index.d.ts +7 -0
  137. package/out/vide/init.luau +50 -0
  138. package/package.json +28 -2
  139. package/testing/package.json +4 -0
  140. package/transformer/data.cjs +72 -0
  141. package/transformer/declarations.cjs +153 -0
  142. package/transformer/index.js +87 -23
  143. package/transformer/network.cjs +961 -0
  144. package/util/random/package.json +4 -0
  145. package/vide/package.json +4 -0
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,146 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local ClientReady = TS.import(script, script.Parent.Parent.Parent, "network").ClientReady
4
+ local readClaim = TS.import(script, script.Parent.Parent, "construction-claim").readClaim
5
+ local definitionOf = TS.import(script, script.Parent, "catalog").definitionOf
6
+ --* @internal
7
+ --* @internal
8
+ local session
9
+ local frozen = false
10
+ local receivers = {}
11
+ local ready = {}
12
+ local live = {}
13
+ local function retire(claim)
14
+ local _claim = claim
15
+ -- ▼ Set.delete ▼
16
+ local _valueExisted = live[_claim] ~= nil
17
+ live[_claim] = nil
18
+ -- ▲ Set.delete ▲
19
+ if not _valueExisted then
20
+ return nil
21
+ end
22
+ if claim.target == ClientReady then
23
+ local _claim_1 = claim
24
+ local _arg0 = (table.find(ready, _claim_1) or 0) - 1
25
+ table.remove(ready, _arg0 + 1)
26
+ else
27
+ local _target = claim.target
28
+ receivers[_target] = nil
29
+ end
30
+ local _result = session
31
+ if _result ~= nil then
32
+ _result.retired(claim)
33
+ end
34
+ end
35
+ --[[
36
+ *
37
+ * @internal
38
+ * Re-checks what the transformer checked, because untransformed or cast code skips it. A violation throws from the
39
+ * constructor and fails boot.
40
+
41
+ ]]
42
+ local function claim(owner, field, target, callback)
43
+ local state = readClaim()
44
+ if state == nil or state.object ~= owner then
45
+ error("receive() must initialize a readonly field of the Subsystem being constructed")
46
+ end
47
+ local record = state.record
48
+ local label = `{record.id}.{field}`
49
+ if record.kind ~= 0 then
50
+ error(`{label}: receive() must initialize a readonly field of a Subsystem; behaviours do not receive`)
51
+ end
52
+ if frozen then
53
+ error(`{label}: receive() after the network commit`)
54
+ end
55
+ local side = (session or error(`{label}: receive() before the network session opened`)).side()
56
+ local _callback = callback
57
+ if not (type(_callback) == "function") then
58
+ error(`{label}: receive() needs a callback`)
59
+ end
60
+ if target == ClientReady then
61
+ if side ~= "server" then
62
+ error(`{label}: ClientReady is received in a server Subsystem.`)
63
+ end
64
+ for _, other in ready do
65
+ if other.owner == state then
66
+ error(`{label}: {other.label} already receives ClientReady; a Subsystem holds one`)
67
+ end
68
+ end
69
+ else
70
+ local definition = definitionOf(target) or error(`{label}: receive() needs a message declared with toServer, toClient or askServer`)
71
+ local serverSide = definition.kind ~= "to-client"
72
+ if serverSide ~= (side == "server") then
73
+ error(if serverSide then `{label}: {definition.name} is client-to-server; receive it in a server Subsystem.` else `{label}: {definition.name} is server-to-client; receive it in a client Subsystem.`)
74
+ end
75
+ local other = receivers[definition]
76
+ if other ~= nil then
77
+ error(`{definition.name} has two receivers: {other.label} and {label}`)
78
+ end
79
+ end
80
+ local entry = {
81
+ owner = state,
82
+ label = label,
83
+ target = target,
84
+ callback = callback,
85
+ }
86
+ if target == ClientReady then
87
+ table.insert(ready, entry)
88
+ else
89
+ local _target = target
90
+ receivers[_target] = entry
91
+ end
92
+ live[entry] = true
93
+ if state.subscriptions == nil then
94
+ state.subscriptions = {}
95
+ end
96
+ local _exp = state.subscriptions
97
+ table.insert(_exp, function()
98
+ return retire(entry)
99
+ end)
100
+ return table.freeze({})
101
+ end
102
+ --* @internal
103
+ local function isLive(claim)
104
+ local _claim = claim
105
+ return live[_claim] ~= nil
106
+ end
107
+ --* @internal
108
+ local function receiverOf(definition)
109
+ local _definition = definition
110
+ return receivers[_definition]
111
+ end
112
+ --* @internal
113
+ local function readyClaims()
114
+ local _array = {}
115
+ local _length = #_array
116
+ table.move(ready, 1, #ready, _length + 1, _array)
117
+ return _array
118
+ end
119
+ --* @internal
120
+ local function openClaims(value)
121
+ session = value
122
+ end
123
+ --* @internal
124
+ local function freezeClaims()
125
+ frozen = true
126
+ end
127
+ --* @internal
128
+ local function closeClaims(discard)
129
+ session = nil
130
+ if not discard then
131
+ return nil
132
+ end
133
+ frozen = false
134
+ table.clear(receivers)
135
+ table.clear(ready)
136
+ table.clear(live)
137
+ end
138
+ return {
139
+ claim = claim,
140
+ isLive = isLive,
141
+ receiverOf = receiverOf,
142
+ readyClaims = readyClaims,
143
+ openClaims = openClaims,
144
+ freezeClaims = freezeClaims,
145
+ closeClaims = closeClaims,
146
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,405 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local runtimeDiagnostic = TS.import(script, script.Parent.Parent, "diagnostics").runtimeDiagnostic
4
+ local _catalog = TS.import(script, script.Parent, "catalog")
5
+ local adopt = _catalog.adopt
6
+ local catalogSize = _catalog.catalogSize
7
+ local definitionAt = _catalog.definitionAt
8
+ local definitionOf = _catalog.definitionOf
9
+ local discardIndices = _catalog.discardIndices
10
+ local unpublished = _catalog.unpublished
11
+ local _claims = TS.import(script, script.Parent, "claims")
12
+ local closeClaims = _claims.closeClaims
13
+ local freezeClaims = _claims.freezeClaims
14
+ local openClaims = _claims.openClaims
15
+ local receiverOf = _claims.receiverOf
16
+ local _envelope = TS.import(script, script.Parent, "envelope")
17
+ local argumentCount = _envelope.argumentCount
18
+ local controlPacket = _envelope.controlPacket
19
+ local decodeBody = _envelope.decodeBody
20
+ local encodeBody = _envelope.encodeBody
21
+ local failureReason = _envelope.failureReason
22
+ local frame = _envelope.frame
23
+ local missingInstance = _envelope.missingInstance
24
+ local openBody = _envelope.openBody
25
+ local readHeader = _envelope.readHeader
26
+ local L = TS.import(script, script.Parent, "limits")
27
+ local _session = TS.import(script, script.Parent, "session")
28
+ local newPeer = _session.newPeer
29
+ local Session = _session.Session
30
+ local FAIL_NAMES = { "overflow", "overloaded", "closing", "malformed" }
31
+ local ERROR_REASONS = { "unavailable", "handler-error", "overloaded", "invalid" }
32
+ local function failed(reason)
33
+ return table.freeze({
34
+ ok = false,
35
+ reason = reason,
36
+ })
37
+ end
38
+ --* @internal
39
+ local toServer
40
+ local ClientSession
41
+ do
42
+ local super = Session
43
+ ClientSession = setmetatable({}, {
44
+ __tostring = function()
45
+ return "ClientSession"
46
+ end,
47
+ __index = super,
48
+ })
49
+ ClientSession.__index = ClientSession
50
+ function ClientSession.new(...)
51
+ local self = setmetatable({}, ClientSession)
52
+ return self:constructor(...) or self
53
+ end
54
+ function ClientSession:constructor(...)
55
+ super.constructor(self, ...)
56
+ self.peer = newPeer()
57
+ self.nextNonce = 1
58
+ self.pending = {}
59
+ self.fired = false
60
+ self.unpublishedReported = {}
61
+ self.undeclared = {}
62
+ self.unmatched = {}
63
+ self.undecoded = {}
64
+ self.missingPlayer = {}
65
+ end
66
+ function ClientSession:open()
67
+ self.stage = 1
68
+ openClaims({
69
+ side = function()
70
+ return "client"
71
+ end,
72
+ retired = function() end,
73
+ })
74
+ local where = `ReplicatedStorage.{L.REMOTE_NAME}`
75
+ local found = self.port.discover(L.CLIENT_DISCOVERY_TIMEOUT_SECONDS, function(...)
76
+ local args = { ... }
77
+ return self:receive(argumentCount(args), args[1], args[2])
78
+ end)
79
+ if found == nil then
80
+ error(`Crucible found no {where} within {L.CLIENT_DISCOVERY_TIMEOUT_SECONDS} seconds`, 0)
81
+ end
82
+ if found.className ~= "RemoteEvent" then
83
+ error(`Crucible found {where} as a {found.className}`, 0)
84
+ end
85
+ if found.protocol ~= L.PROTOCOL_VERSION then
86
+ error(`Crucible {where} has Protocol {tostring(found.protocol)}; this client needs {L.PROTOCOL_VERSION}`, 0)
87
+ end
88
+ local adopted, err = pcall(adopt, found.catalog)
89
+ if not adopted then
90
+ error(`Crucible {where} has an invalid Catalog: {tostring(err)}`, 0)
91
+ end
92
+ end
93
+ function ClientSession:loaded()
94
+ self.stage = 2
95
+ end
96
+ function ClientSession:commit()
97
+ freezeClaims()
98
+ for _, definition in unpublished() do
99
+ if receiverOf(definition) ~= nil then
100
+ self:unpublished(definition)
101
+ end
102
+ end
103
+ if self.peer.state == 0 then
104
+ self:fire(self.peer, controlPacket(4, {}))
105
+ self.fired = true
106
+ self.peer.state = 1
107
+ self:flush(self.peer)
108
+ end
109
+ self.stage = 3
110
+ end
111
+ function ClientSession:close()
112
+ if self.stage == 4 then
113
+ return nil
114
+ end
115
+ local committed = self.stage == 3
116
+ self.stage = 4
117
+ if self.fired and self.peer.state ~= 2 then
118
+ pcall(function()
119
+ return self:fire(self.peer, controlPacket(5, {}, 2))
120
+ end)
121
+ end
122
+ pcall(function()
123
+ return self["end"](self, self.peer)
124
+ end)
125
+ pcall(function()
126
+ return self.port.disconnect()
127
+ end)
128
+ closeClaims(not committed)
129
+ if not committed then
130
+ discardIndices()
131
+ end
132
+ end
133
+ function ClientSession:send(token, value)
134
+ local sendable = self:sendable()
135
+ local definition = toServer(token, "to-server", "Network.send needs a message declared with toServer")
136
+ local body = encodeBody(definition.name, definition.input, "payload", value)
137
+ if not sendable or self.peer.state == 2 then
138
+ return false
139
+ end
140
+ local index = definition.index
141
+ if index == nil then
142
+ self:unpublished(definition)
143
+ return false
144
+ end
145
+ return self:deliver(self.peer, frame(body, 0, { index }))
146
+ end
147
+ function ClientSession:ask(token, value)
148
+ local sendable = self:sendable()
149
+ local definition = toServer(token, "ask-server", "Network.ask needs a message declared with askServer")
150
+ local body = encodeBody(definition.name, definition.input, "request", value)
151
+ if not sendable or self.peer.state == 2 then
152
+ return failed("disconnected")
153
+ end
154
+ local index = definition.index
155
+ if index == nil then
156
+ self:unpublished(definition)
157
+ return failed("unavailable")
158
+ end
159
+ -- ▼ ReadonlyMap.size ▼
160
+ local _size = 0
161
+ for _ in self.pending do
162
+ _size += 1
163
+ end
164
+ -- ▲ ReadonlyMap.size ▲
165
+ if _size >= L.MAX_PENDING_ASKS then
166
+ return failed("overloaded")
167
+ end
168
+ local nonce = self.nextNonce
169
+ local packet = frame(body, 1, { index, nonce })
170
+ self.nextNonce = nonce + 1
171
+ local entry = {
172
+ definition = definition,
173
+ thread = coroutine.running(),
174
+ packet = packet,
175
+ }
176
+ self.pending[nonce] = entry
177
+ entry.deadline = self.port.delay(definition.timeout, function()
178
+ return self:expire(nonce)
179
+ end)
180
+ self:deliver(self.peer, packet)
181
+ if entry.result ~= nil then
182
+ return entry.result
183
+ end
184
+ return (coroutine.yield())
185
+ end
186
+ function ClientSession:fire(_peer, packet)
187
+ self.port.fireServer(packet.bytes, packet.sidecar)
188
+ end
189
+ ClientSession["end"] = function(self, peer)
190
+ if not super["end"](self, peer) then
191
+ return false
192
+ end
193
+ local nonces = {}
194
+ for nonce in self.pending do
195
+ table.insert(nonces, nonce)
196
+ end
197
+ table.sort(nonces)
198
+ for _, nonce in nonces do
199
+ self:settle(nonce, failed("disconnected"))
200
+ end
201
+ return true
202
+ end
203
+ function ClientSession:fail(peer, reason, detail)
204
+ if peer.state == 2 then
205
+ return nil
206
+ end
207
+ runtimeDiagnostic("error", `Crucible network session failed ({FAIL_NAMES[reason + 1]}){if detail == nil then "" else `: {detail}`}`)
208
+ pcall(function()
209
+ return self:fire(peer, controlPacket(5, {}, reason))
210
+ end)
211
+ self["end"](self, peer)
212
+ end
213
+ function ClientSession:unpublished(definition)
214
+ local _unpublishedReported = self.unpublishedReported
215
+ local _definition = definition
216
+ if _unpublishedReported[_definition] ~= nil then
217
+ return nil
218
+ end
219
+ local _unpublishedReported_1 = self.unpublishedReported
220
+ local _definition_1 = definition
221
+ _unpublishedReported_1[_definition_1] = true
222
+ runtimeDiagnostic("warning", `Crucible {definition.name} is not in the server's network catalog`)
223
+ end
224
+ function ClientSession:once(seen, definition, message)
225
+ local _seen = seen
226
+ local _definition = definition
227
+ if _seen[_definition] ~= nil then
228
+ return nil
229
+ end
230
+ local _seen_1 = seen
231
+ local _definition_1 = definition
232
+ _seen_1[_definition_1] = true
233
+ runtimeDiagnostic("warning", message)
234
+ end
235
+ function ClientSession:expire(nonce)
236
+ local _pending = self.pending
237
+ local _nonce = nonce
238
+ local entry = _pending[_nonce]
239
+ if entry == nil then
240
+ return nil
241
+ end
242
+ local _queue = self.peer.queue
243
+ local _packet = entry.packet
244
+ local index = (table.find(_queue, _packet) or 0) - 1
245
+ if index ~= -1 then
246
+ table.remove(self.peer.queue, index + 1)
247
+ self.peer.queuedBytes -= entry.packet.charge
248
+ end
249
+ self:settle(nonce, failed("timeout"))
250
+ end
251
+ function ClientSession:settle(nonce, result)
252
+ local _pending = self.pending
253
+ local _nonce = nonce
254
+ local entry = _pending[_nonce]
255
+ if entry == nil then
256
+ return nil
257
+ end
258
+ local _pending_1 = self.pending
259
+ local _nonce_1 = nonce
260
+ _pending_1[_nonce_1] = nil
261
+ local _result = entry.deadline
262
+ if _result ~= nil then
263
+ _result()
264
+ end
265
+ entry.result = result
266
+ if self.later ~= nil then
267
+ local _exp = self.later
268
+ table.insert(_exp, entry)
269
+ else
270
+ self:resume(entry)
271
+ end
272
+ end
273
+ function ClientSession:resume(entry)
274
+ if coroutine.status(entry.thread) == "suspended" then
275
+ self.port.resume(entry.thread, entry.result)
276
+ end
277
+ end
278
+ function ClientSession:receive(count, packet, sidecar)
279
+ local later = {}
280
+ self.later = later
281
+ local ok, err = pcall(function()
282
+ return self:dispatch(count, packet, sidecar)
283
+ end)
284
+ self.later = nil
285
+ if not ok then
286
+ self:fail(self.peer, 3, `receive failed: {tostring(err)}`)
287
+ end
288
+ for _, entry in later do
289
+ self:resume(entry)
290
+ end
291
+ end
292
+ function ClientSession:violation(detail)
293
+ self:fail(self.peer, 3, detail)
294
+ end
295
+ function ClientSession:dispatch(count, packet, sidecar)
296
+ if self.peer.state == 2 then
297
+ return nil
298
+ end
299
+ local header = readHeader(count, packet, sidecar)
300
+ if header == nil then
301
+ return self:violation("a malformed packet")
302
+ end
303
+ if header.opcode == 5 then
304
+ local _condition = FAIL_NAMES[header.reason + 1]
305
+ if _condition == nil then
306
+ _condition = `unknown reason {header.reason}`
307
+ end
308
+ local reason = _condition
309
+ runtimeDiagnostic("error", `Crucible network session ended: the server sent FAIL ({reason})`)
310
+ self["end"](self, self.peer)
311
+ return nil
312
+ end
313
+ if not self.fired then
314
+ return self:violation("a game packet before READY")
315
+ end
316
+ if header.opcode == 0 then
317
+ return self:event(header, packet, sidecar)
318
+ end
319
+ if header.opcode == 2 or header.opcode == 3 then
320
+ return self:answer(header, packet, sidecar)
321
+ end
322
+ self:violation("a client-to-server packet")
323
+ end
324
+ function ClientSession:event(header, packet, sidecar)
325
+ if header.index >= catalogSize() then
326
+ return self:violation("an unknown message index")
327
+ end
328
+ local definition = definitionAt(header.index)
329
+ if definition == nil then
330
+ local _undeclared = self.undeclared
331
+ local _index = header.index
332
+ if _undeclared[_index] ~= nil then
333
+ return nil
334
+ end
335
+ local _undeclared_1 = self.undeclared
336
+ local _index_1 = header.index
337
+ _undeclared_1[_index_1] = true
338
+ runtimeDiagnostic("warning", `Crucible dropped catalog message {header.index}: this client never declared it`)
339
+ return nil
340
+ end
341
+ if definition.kind ~= "to-client" then
342
+ return self:violation(`{definition.name} sent as an event`)
343
+ end
344
+ local decoded, value = pcall(function()
345
+ return decodeBody(definition.input, "payload", openBody(packet, sidecar, header))
346
+ end)
347
+ if not decoded then
348
+ if missingInstance(value) then
349
+ return self:once(self.missingPlayer, definition, `Crucible dropped {definition.name}: a Player left`)
350
+ end
351
+ return self:violation(`{definition.name} {failureReason(value)}`)
352
+ end
353
+ local receiver = receiverOf(definition)
354
+ if receiver == nil then
355
+ return self:unreceived(definition)
356
+ end
357
+ if self.peer.counted >= L.MAX_ACTIVE_HANDLERS then
358
+ return self:fail(self.peer, 1)
359
+ end
360
+ self:start(self.peer, receiver, { value })
361
+ end
362
+ function ClientSession:answer(header, packet, sidecar)
363
+ local definition = definitionAt(header.index)
364
+ if definition == nil or definition.kind ~= "ask-server" then
365
+ return self:violation("a reply naming a message that is not an ask")
366
+ end
367
+ local _pending = self.pending
368
+ local _nonce = header.nonce
369
+ local entry = _pending[_nonce]
370
+ if entry == nil then
371
+ return self:once(self.unmatched, definition, `Crucible ignored a late or unknown reply to {definition.name}`)
372
+ end
373
+ if entry.definition ~= definition then
374
+ return self:violation(`a reply to {entry.definition.name} naming {definition.name}`)
375
+ end
376
+ if header.opcode == 3 then
377
+ local reason = ERROR_REASONS[header.reason + 1]
378
+ if reason == nil then
379
+ return self:violation(`an unknown ERROR reason {header.reason}`)
380
+ end
381
+ return self:settle(header.nonce, failed(reason))
382
+ end
383
+ local decoded, value = pcall(function()
384
+ return decodeBody(definition.output, "response", openBody(packet, sidecar, header))
385
+ end)
386
+ if not decoded then
387
+ self:once(self.undecoded, definition, `Crucible could not decode a reply to {definition.name}: {failureReason(value)}`)
388
+ return self:settle(header.nonce, failed("invalid"))
389
+ end
390
+ self:settle(header.nonce, table.freeze({
391
+ ok = true,
392
+ value = value,
393
+ }))
394
+ end
395
+ end
396
+ function toServer(token, kind, message)
397
+ local definition = definitionOf(token)
398
+ if definition == nil or definition.kind ~= kind then
399
+ error(message, 0)
400
+ end
401
+ return definition
402
+ end
403
+ return {
404
+ ClientSession = ClientSession,
405
+ }
@@ -0,0 +1,63 @@
1
+ import { type Branch, type Node } from "./schema";
2
+ export interface CodecFailure {
3
+ readonly codecFailure: true;
4
+ readonly path: string;
5
+ readonly reason: string;
6
+ readonly missing?: true;
7
+ }
8
+ export declare class Budget {
9
+ visits: number;
10
+ entries: number;
11
+ depth: number;
12
+ chargeEntries(count: number, path: string): void;
13
+ visit(path: string): void;
14
+ enter(path: string): void;
15
+ leave(): void;
16
+ }
17
+ export declare class Writer {
18
+ readonly budget: Budget;
19
+ readonly slots: Instance[];
20
+ private data;
21
+ private cursor;
22
+ private reserve;
23
+ u8(value: number): void;
24
+ u16(value: number): void;
25
+ u32(value: number): void;
26
+ varuint(value: number): void;
27
+ f32(value: number): void;
28
+ f64(value: number): void;
29
+ bytes(text: string): void;
30
+ slot(instance: Instance): void;
31
+ finish(header: buffer): LuaTuple<[buffer, Readonly<Record<string, Instance>>, number]>;
32
+ }
33
+ export declare class Reader {
34
+ private readonly packet;
35
+ private cursor;
36
+ private readonly sidecar;
37
+ private readonly slotCount;
38
+ readonly budget: Budget;
39
+ private nextSlot;
40
+ constructor(packet: buffer, cursor: number, sidecar: Readonly<Record<string, Instance>>, slotCount: number);
41
+ private take;
42
+ u8(): number;
43
+ u16(): number;
44
+ u32(): number;
45
+ varuint(): number;
46
+ f32(): number;
47
+ f64(): number;
48
+ string(): string;
49
+ count(minElementBytes: number, path: string): number;
50
+ slot(path: string): Instance | undefined;
51
+ finished(): boolean;
52
+ }
53
+ export interface Codec {
54
+ readonly node: Branch;
55
+ readonly minBytes: number;
56
+ readonly encode: (writer: Writer, value: unknown, path: string) => void;
57
+ readonly decode: (reader: Reader, path: string) => unknown;
58
+ }
59
+ export declare function compile(node: Node, tally?: {
60
+ nodes: number;
61
+ }): Codec;
62
+ export declare function encodeDynamic(w: Writer, value: unknown, path: string, active: Set<object>, topLevel?: boolean, arrayElement?: boolean): void;
63
+ export declare function decodeDynamic(r: Reader, path: string, topLevel: boolean, arrayElement?: boolean): unknown;