@trioforge/crucible 0.0.0-stage → 0.2.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 (183) hide show
  1. package/LICENSE +21 -0
  2. package/client/package.json +4 -0
  3. package/emit/package.json +4 -0
  4. package/out/all.d.ts +7 -0
  5. package/out/all.luau +2 -0
  6. package/out/attribute.d.ts +7 -0
  7. package/out/attribute.luau +7 -0
  8. package/out/client/index.d.ts +10 -0
  9. package/out/client/init.luau +55 -0
  10. package/out/configs.d.ts +37 -0
  11. package/out/configs.luau +46 -0
  12. package/out/crucible-behaviour.d.ts +16 -0
  13. package/out/crucible-behaviour.luau +78 -0
  14. package/out/crucible-object.d.ts +10 -0
  15. package/out/crucible-object.luau +69 -0
  16. package/out/data.d.ts +224 -0
  17. package/out/data.luau +97 -0
  18. package/out/emit/configs.d.ts +4 -0
  19. package/out/emit/configs.luau +22 -0
  20. package/out/emit/data.d.ts +6 -0
  21. package/out/emit/data.luau +19 -0
  22. package/out/emit/index.d.ts +45 -0
  23. package/out/emit/init.luau +101 -0
  24. package/out/emit/network.d.ts +6 -0
  25. package/out/emit/network.luau +36 -0
  26. package/out/emit/replicated.d.ts +4 -0
  27. package/out/emit/replicated.luau +13 -0
  28. package/out/index.d.ts +20 -0
  29. package/out/init.luau +34 -0
  30. package/out/inject.d.ts +1 -0
  31. package/out/inject.luau +7 -0
  32. package/out/internal/binding.d.ts +1 -0
  33. package/out/internal/binding.luau +615 -0
  34. package/out/internal/boot.d.ts +1 -0
  35. package/out/internal/boot.luau +562 -0
  36. package/out/internal/configs/limits.d.ts +10 -0
  37. package/out/internal/configs/limits.luau +25 -0
  38. package/out/internal/configs/port.d.ts +29 -0
  39. package/out/internal/configs/port.luau +99 -0
  40. package/out/internal/configs/runtime.d.ts +21 -0
  41. package/out/internal/configs/runtime.luau +981 -0
  42. package/out/internal/construction-claim.d.ts +1 -0
  43. package/out/internal/construction-claim.luau +26 -0
  44. package/out/internal/data/admission.d.ts +1 -0
  45. package/out/internal/data/admission.luau +673 -0
  46. package/out/internal/data/declarations.d.ts +1 -0
  47. package/out/internal/data/declarations.luau +384 -0
  48. package/out/internal/data/envelope.d.ts +1 -0
  49. package/out/internal/data/envelope.luau +318 -0
  50. package/out/internal/data/handles.d.ts +1 -0
  51. package/out/internal/data/handles.luau +255 -0
  52. package/out/internal/data/inbox-transforms.d.ts +1 -0
  53. package/out/internal/data/inbox-transforms.luau +458 -0
  54. package/out/internal/data/inbox.d.ts +1 -0
  55. package/out/internal/data/inbox.luau +437 -0
  56. package/out/internal/data/lease.d.ts +1 -0
  57. package/out/internal/data/lease.luau +304 -0
  58. package/out/internal/data/limits.d.ts +1 -0
  59. package/out/internal/data/limits.luau +234 -0
  60. package/out/internal/data/messages.d.ts +1 -0
  61. package/out/internal/data/messages.luau +69 -0
  62. package/out/internal/data/operation-id.d.ts +1 -0
  63. package/out/internal/data/operation-id.luau +61 -0
  64. package/out/internal/data/paths.d.ts +2 -0
  65. package/out/internal/data/paths.luau +323 -0
  66. package/out/internal/data/port.d.ts +1 -0
  67. package/out/internal/data/port.luau +290 -0
  68. package/out/internal/data/profiles.d.ts +1 -0
  69. package/out/internal/data/profiles.luau +862 -0
  70. package/out/internal/data/receipts.d.ts +1 -0
  71. package/out/internal/data/receipts.luau +190 -0
  72. package/out/internal/data/record-bindings.d.ts +1 -0
  73. package/out/internal/data/record-bindings.luau +92 -0
  74. package/out/internal/data/record-declarations.d.ts +1 -0
  75. package/out/internal/data/record-declarations.luau +140 -0
  76. package/out/internal/data/records.d.ts +1 -0
  77. package/out/internal/data/records.luau +400 -0
  78. package/out/internal/data/runtime.d.ts +1 -0
  79. package/out/internal/data/runtime.luau +918 -0
  80. package/out/internal/data/scheduler.d.ts +1 -0
  81. package/out/internal/data/scheduler.luau +507 -0
  82. package/out/internal/data/session.d.ts +1 -0
  83. package/out/internal/data/session.luau +234 -0
  84. package/out/internal/data/shutdown.d.ts +1 -0
  85. package/out/internal/data/shutdown.luau +91 -0
  86. package/out/internal/data/waiters.d.ts +1 -0
  87. package/out/internal/data/waiters.luau +65 -0
  88. package/out/internal/dense-list.d.ts +1 -0
  89. package/out/internal/dense-list.luau +155 -0
  90. package/out/internal/diagnostics.d.ts +1 -0
  91. package/out/internal/diagnostics.luau +29 -0
  92. package/out/internal/hooks.d.ts +1 -0
  93. package/out/internal/hooks.luau +53 -0
  94. package/out/internal/implementers.d.ts +1 -0
  95. package/out/internal/implementers.luau +119 -0
  96. package/out/internal/lifecycle.d.ts +1 -0
  97. package/out/internal/lifecycle.luau +912 -0
  98. package/out/internal/log-output.d.ts +1 -0
  99. package/out/internal/log-output.luau +106 -0
  100. package/out/internal/network/catalog.d.ts +26 -0
  101. package/out/internal/network/catalog.luau +182 -0
  102. package/out/internal/network/claims.d.ts +1 -0
  103. package/out/internal/network/claims.luau +154 -0
  104. package/out/internal/network/client-session.d.ts +1 -0
  105. package/out/internal/network/client-session.luau +403 -0
  106. package/out/internal/network/codec.d.ts +63 -0
  107. package/out/internal/network/codec.luau +1183 -0
  108. package/out/internal/network/envelope.d.ts +1 -0
  109. package/out/internal/network/envelope.luau +247 -0
  110. package/out/internal/network/limits.d.ts +26 -0
  111. package/out/internal/network/limits.luau +55 -0
  112. package/out/internal/network/port.d.ts +1 -0
  113. package/out/internal/network/port.luau +19 -0
  114. package/out/internal/network/roblox-network-port.d.ts +1 -0
  115. package/out/internal/network/roblox-network-port.luau +105 -0
  116. package/out/internal/network/schema.d.ts +72 -0
  117. package/out/internal/network/schema.luau +386 -0
  118. package/out/internal/network/server-session.d.ts +1 -0
  119. package/out/internal/network/server-session.luau +393 -0
  120. package/out/internal/network/session.d.ts +1 -0
  121. package/out/internal/network/session.luau +266 -0
  122. package/out/internal/port.d.ts +1 -0
  123. package/out/internal/port.luau +12 -0
  124. package/out/internal/reactive/context.d.ts +1 -0
  125. package/out/internal/reactive/context.luau +149 -0
  126. package/out/internal/reactive/flush.d.ts +1 -0
  127. package/out/internal/reactive/flush.luau +355 -0
  128. package/out/internal/reactive/graph.d.ts +1 -0
  129. package/out/internal/reactive/graph.luau +626 -0
  130. package/out/internal/reactive/limits.d.ts +1 -0
  131. package/out/internal/reactive/limits.luau +54 -0
  132. package/out/internal/reactive/ownership.d.ts +1 -0
  133. package/out/internal/reactive/ownership.luau +626 -0
  134. package/out/internal/registry.d.ts +1 -0
  135. package/out/internal/registry.luau +455 -0
  136. package/out/internal/roblox-port.d.ts +1 -0
  137. package/out/internal/roblox-port.luau +117 -0
  138. package/out/internal/runner.d.ts +1 -0
  139. package/out/internal/runner.luau +86 -0
  140. package/out/internal/scheduler.d.ts +1 -0
  141. package/out/internal/scheduler.luau +207 -0
  142. package/out/internal/state.d.ts +1 -0
  143. package/out/internal/state.luau +41 -0
  144. package/out/internal/sync/client-sync.d.ts +1 -0
  145. package/out/internal/sync/client-sync.luau +157 -0
  146. package/out/internal/sync/declarations.d.ts +1 -0
  147. package/out/internal/sync/declarations.luau +276 -0
  148. package/out/internal/sync/frame.d.ts +1 -0
  149. package/out/internal/sync/frame.luau +91 -0
  150. package/out/internal/sync/server-sync.d.ts +1 -0
  151. package/out/internal/sync/server-sync.luau +502 -0
  152. package/out/internal/sync/values.d.ts +1 -0
  153. package/out/internal/sync/values.luau +930 -0
  154. package/out/log-level.d.ts +7 -0
  155. package/out/log-level.luau +21 -0
  156. package/out/logging.d.ts +34 -0
  157. package/out/logging.luau +233 -0
  158. package/out/network.d.ts +66 -0
  159. package/out/network.luau +24 -0
  160. package/out/reactive.d.ts +76 -0
  161. package/out/reactive.luau +136 -0
  162. package/out/replicated.d.ts +38 -0
  163. package/out/replicated.luau +45 -0
  164. package/out/server/data.d.ts +54 -0
  165. package/out/server/data.luau +44 -0
  166. package/out/server/index.d.ts +18 -0
  167. package/out/server/init.luau +62 -0
  168. package/out/server/publish.d.ts +20 -0
  169. package/out/server/publish.luau +24 -0
  170. package/out/subsystem.d.ts +4 -0
  171. package/out/subsystem.luau +30 -0
  172. package/out/vide/index.d.ts +7 -0
  173. package/out/vide/init.luau +50 -0
  174. package/package.json +52 -5
  175. package/server/package.json +4 -0
  176. package/transformer/data.cjs +72 -0
  177. package/transformer/declarations.cjs +153 -0
  178. package/transformer/diagnostics.cjs +19 -0
  179. package/transformer/index.js +741 -0
  180. package/transformer/network.cjs +961 -0
  181. package/transformer/type-id.cjs +80 -0
  182. package/vide/package.json +4 -0
  183. package/README.md +0 -3
@@ -0,0 +1,912 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local _runner = TS.import(script, script.Parent, "runner")
4
+ local call = _runner.call
5
+ local disown = _runner.disown
6
+ local own = _runner.own
7
+ local ownerOf = _runner.ownerOf
8
+ --[[
9
+ *
10
+ * @internal
11
+ * Pure transition function. The drain calls it until it returns undefined. It reads only the current state,
12
+ * so re-running it after a request converges on the same end state, and asking twice for the state an object
13
+ * already has finds no step.
14
+
15
+ ]]
16
+ local function nextStep(s)
17
+ local ending = s.fault ~= nil or s.want == 2
18
+ local _exp = s.phase
19
+ repeat
20
+ if _exp == 0 then
21
+ if ending then
22
+ return 10
23
+ end
24
+ return if s.gate >= 1 then 0 else nil
25
+ end
26
+ if _exp == 1 then
27
+ if ending then
28
+ return 10
29
+ end
30
+ return if s.gate >= 2 then 1 else nil
31
+ end
32
+ if _exp == 2 then
33
+ if ending then
34
+ return 9
35
+ end
36
+ if s.gate < 3 then
37
+ return nil
38
+ end
39
+ return if s.want == 0 then 2 else 3
40
+ end
41
+ if _exp == 3 then
42
+ if s.fault ~= nil or s.want ~= 0 then
43
+ return if s.joined or s.ticking then 7 else 8
44
+ end
45
+ if not s.joined then
46
+ return 4
47
+ end
48
+ if s.start == 0 then
49
+ return if s.gate >= 4 then 5 else nil
50
+ end
51
+ return if s.start == 2 and not s.ticking then 6 else nil
52
+ end
53
+ if _exp == 4 then
54
+ if ending then
55
+ return 9
56
+ end
57
+ return if s.want == 0 then 2 else nil
58
+ end
59
+ until true
60
+ return nil
61
+ end
62
+ local installed
63
+ local states = {}
64
+ --* @internal
65
+ local function useEffects(effects)
66
+ installed = effects
67
+ end
68
+ local function effects()
69
+ return installed or error("Crucible lifecycle used before boot")
70
+ end
71
+ --* @internal
72
+ local function stateOf(object)
73
+ local _object = object
74
+ return states[_object]
75
+ end
76
+ local enablingNow
77
+ --* @internal The generation whose Enable step is running. What it creates belongs to its enable interval.
78
+ local function enabling()
79
+ return enablingNow
80
+ end
81
+ --* @internal Awake, not faulted, and not asked to go. `get` and queries see only live objects.
82
+ local function isLive(s)
83
+ return s.fault == nil and s.want ~= 2 and (s.phase == 2 or s.phase == 3 or s.phase == 4)
84
+ end
85
+ --* @internal Faulted or Destroyed.
86
+ local function hasEnded(s)
87
+ return s.phase == 5 or s.phase == 6
88
+ end
89
+ --* @internal A new generation, before its constructor runs. `admit` gives it a batch.
90
+ local function track(record, instance, attributes)
91
+ return {
92
+ work = 0,
93
+ phase = 0,
94
+ start = 0,
95
+ gate = 0,
96
+ want = 0,
97
+ joined = false,
98
+ ticking = false,
99
+ record = record,
100
+ instance = instance,
101
+ attributes = attributes,
102
+ queued = false,
103
+ transitions = 0,
104
+ }
105
+ end
106
+ --[[
107
+ *
108
+ * @internal
109
+ * The object whose code is running: the owner of a runner worker during a hook or callback, or of a start
110
+ * thread for its whole life. A thread made with `task.spawn` inside a hook has no owner.
111
+
112
+ ]]
113
+ local function current()
114
+ return ownerOf(coroutine.running())
115
+ end
116
+ local function note(s, hook, err)
117
+ if s.fault == nil then
118
+ s.fault = {
119
+ hook = hook,
120
+ error = err,
121
+ cleanup = {},
122
+ }
123
+ else
124
+ local _cleanup = s.fault.cleanup
125
+ local _arg0 = {
126
+ hook = hook,
127
+ error = err,
128
+ }
129
+ table.insert(_cleanup, _arg0)
130
+ end
131
+ end
132
+ --* A teardown failure attaches to the fault, or is diagnosed without faulting a clean generation.
133
+ local function cleanup(s, hook, err)
134
+ if s.fault ~= nil then
135
+ local _cleanup = s.fault.cleanup
136
+ local _arg0 = {
137
+ hook = hook,
138
+ error = err,
139
+ }
140
+ table.insert(_cleanup, _arg0)
141
+ else
142
+ effects().diagnose(s, hook, err)
143
+ end
144
+ end
145
+ local subsystemLacks = {
146
+ [2] = true,
147
+ [4] = true,
148
+ [5] = true,
149
+ }
150
+ local function run(s, hook)
151
+ local _condition = s.record.kind == 0
152
+ if _condition then
153
+ local _hook = hook
154
+ _condition = subsystemLacks[_hook] ~= nil
155
+ end
156
+ if _condition then
157
+ return nil
158
+ end
159
+ local caller = effects().hook(s, hook)
160
+ if caller == nil then
161
+ return nil
162
+ end
163
+ local result, value = call(caller, s, nil, s)
164
+ if result ~= 0 then
165
+ note(s, hook, value)
166
+ end
167
+ end
168
+ local function construct(s)
169
+ return effects().construct(s)
170
+ end
171
+ local function release(s)
172
+ if s.object ~= nil then
173
+ local _object = s.object
174
+ states[_object] = nil
175
+ end
176
+ local disabled = effects().disabled
177
+ if disabled ~= nil then
178
+ pcall(disabled, s)
179
+ end
180
+ local subscriptions = s.subscriptions
181
+ s.subscriptions = nil
182
+ if subscriptions == nil then
183
+ return nil
184
+ end
185
+ for _, disconnect in subscriptions do
186
+ local ok, err = pcall(disconnect)
187
+ if not ok then
188
+ cleanup(s, 11, err)
189
+ end
190
+ end
191
+ end
192
+ local function finish(s)
193
+ s.phase = if s.fault == nil then 6 else 5
194
+ release(s)
195
+ effects().ended(s)
196
+ end
197
+ local function traceback(err)
198
+ return debug.traceback(tostring(err), 2)
199
+ end
200
+ local finishStart
201
+ local function runStart(s)
202
+ local ok, err = xpcall(effects().start, traceback, s)
203
+ disown(coroutine.running())
204
+ finishStart(s, ok, err)
205
+ end
206
+ local function cancelSuspended(thread)
207
+ disown(thread)
208
+ return pcall(effects().cancel, thread)
209
+ end
210
+ --[[
211
+ *
212
+ * Only a suspended thread can be closed. A start thread that is running, or normal because it resumed the
213
+ * thread now draining, keeps its owner, so `current()` sees an ended object and `observe` subscribes nothing.
214
+ * A deferred check cancels it at its next yield, and `finishStart` ignores it if it returns first.
215
+
216
+ ]]
217
+ local function stopStart(s, thread)
218
+ if coroutine.status(thread) == "suspended" then
219
+ local ok, err = cancelSuspended(thread)
220
+ if not ok then
221
+ cleanup(s, 10, err)
222
+ end
223
+ return nil
224
+ end
225
+ local fx = effects()
226
+ fx.defer(function()
227
+ if coroutine.status(thread) ~= "suspended" then
228
+ return nil
229
+ end
230
+ local ok, err = cancelSuspended(thread)
231
+ if not ok then
232
+ fx.diagnose(s, 10, err)
233
+ end
234
+ end)
235
+ end
236
+ local function perform(s, step)
237
+ local fx = effects()
238
+ repeat
239
+ if step == 0 then
240
+ local result, value = call(construct, s, nil, s)
241
+ if result ~= 0 then
242
+ note(s, 0, value)
243
+ else
244
+ local object = value
245
+ s.object = object
246
+ local _s = s
247
+ states[object] = _s
248
+ s.phase = 1
249
+ end
250
+ return nil
251
+ end
252
+ if step == 1 then
253
+ s.phase = 2
254
+ run(s, 1)
255
+ return nil
256
+ end
257
+ if step == 2 then
258
+ s.phase = 3
259
+ local previous = enablingNow
260
+ enablingNow = s
261
+ run(s, 2)
262
+ enablingNow = previous
263
+ return nil
264
+ end
265
+ if step == 3 then
266
+ s.phase = 4
267
+ return nil
268
+ end
269
+ if step == 4 then
270
+ s.joined = true
271
+ fx.join(s)
272
+ return nil
273
+ end
274
+ if step == 5 then
275
+ s.start = 1
276
+ local thread = coroutine.create(runStart)
277
+ s.startThread = thread
278
+ own(thread, s)
279
+ local ok, err = coroutine.resume(thread, s)
280
+ if not ok then
281
+ note(s, 3, err)
282
+ end
283
+ return nil
284
+ end
285
+ if step == 6 then
286
+ s.ticking = true
287
+ fx.tick(s)
288
+ return nil
289
+ end
290
+ if step == 7 then
291
+ if s.ticking then
292
+ s.ticking = false
293
+ fx.untick(s)
294
+ end
295
+ if s.joined then
296
+ s.joined = false
297
+ fx.leave(s)
298
+ end
299
+ return nil
300
+ end
301
+ if step == 8 then
302
+ s.phase = 4
303
+ run(s, 4)
304
+ local _result = fx.disabled
305
+ if _result ~= nil then
306
+ _result(s)
307
+ end
308
+ return nil
309
+ end
310
+ if step == 9 then
311
+ local thread = s.startThread
312
+ s.startThread = nil
313
+ if s.start == 1 and thread ~= nil then
314
+ stopStart(s, thread)
315
+ end
316
+ run(s, 5)
317
+ finish(s)
318
+ return nil
319
+ end
320
+ if step == 10 then
321
+ finish(s)
322
+ return nil
323
+ end
324
+ until true
325
+ end
326
+ --* Ends a generation with no user hooks, after its own teardown failed again. Every effect is protected.
327
+ local function forceEnd(s)
328
+ local fx = effects()
329
+ local thread = s.startThread
330
+ s.startThread = nil
331
+ if s.start == 1 and thread ~= nil then
332
+ pcall(stopStart, s, thread)
333
+ end
334
+ if s.ticking then
335
+ s.ticking = false
336
+ pcall(fx.untick, s)
337
+ end
338
+ if s.joined then
339
+ s.joined = false
340
+ pcall(fx.leave, s)
341
+ end
342
+ s.phase = if s.fault == nil then 6 else 5
343
+ pcall(release, s)
344
+ pcall(fx.ended, s)
345
+ end
346
+ --* @internal Steps one causal chain may take on one object before it faults. Fault cleanup runs past it.
347
+ local MAX_TRANSITIONS = 64
348
+ --* @internal Creation that never settles is a failure after this many awake waves in one batch.
349
+ local MAX_AWAKE_WAVES = 1024
350
+ --* @internal Batches one drain may start. A chain of objects creating objects in `start` stops here.
351
+ local MAX_BATCHES = 1024
352
+ --[[
353
+ *
354
+ * @internal
355
+ * Requests that steps may make in one drain. C5 bounds an object that undoes itself, not objects that undo each
356
+ * other; this cap ends such a cycle. Like the batch cap it is a failure, and its value waits on a Studio measurement.
357
+
358
+ ]]
359
+ local MAX_CHAINED_REQUESTS = 65_536
360
+ --* @internal Reconcile rounds one drain may run. Binding that queues pairs on every round stops here.
361
+ local MAX_RECONCILE_ROUNDS = 1024
362
+ --* @internal Consecutive internal errors that fault or force-end nothing before the drain gives up on its work.
363
+ local MAX_RECOVERIES = 1
364
+ local function awaking(batch)
365
+ return batch.sweep == 0 or batch.sweep == 1
366
+ end
367
+ local function settle(reconciler)
368
+ reconciler:settle()
369
+ end
370
+ --[[
371
+ *
372
+ * The single reconciler. Every lifecycle and binding change happens in a drain, and one drain runs at a time.
373
+ * A drain starts at an outermost entry point: an engine signal, boot, a frame dispatch, or a public call made
374
+ * outside a drain. A request made inside a drain only enqueues, so no hook ever runs while another object's
375
+ * hook is on the stack. The stack holds objects to settle and batches to advance. What one step asks for
376
+ * collects in `fresh` and moves onto the stack when the step ends, first request on top. So requests run in
377
+ * the order they were made, after the hook that made them returns and before its object takes its next step.
378
+
379
+ ]]
380
+ local request, fault
381
+ local Reconciler
382
+ do
383
+ Reconciler = setmetatable({}, {
384
+ __tostring = function()
385
+ return "Reconciler"
386
+ end,
387
+ })
388
+ Reconciler.__index = Reconciler
389
+ function Reconciler.new(...)
390
+ local self = setmetatable({}, Reconciler)
391
+ return self:constructor(...) or self
392
+ end
393
+ function Reconciler:constructor()
394
+ self.stack = {}
395
+ self.fresh = {}
396
+ self.depth = 0
397
+ self.pairsQueued = false
398
+ self.rounds = 0
399
+ self.batches = 0
400
+ self.requests = 0
401
+ end
402
+ function Reconciler:isDraining()
403
+ return self.depth > 0
404
+ end
405
+ function Reconciler:open()
406
+ self.depth += 1
407
+ end
408
+ function Reconciler:leave()
409
+ self.depth -= 1
410
+ return self.depth == 0 and (#self.stack > 0 or #self.fresh > 0 or self.pairsQueued)
411
+ end
412
+ function Reconciler:enter(callback, a, b, owner)
413
+ self.depth += 1
414
+ local result, value = call(callback, a, b, owner)
415
+ self.depth -= 1
416
+ return result, value
417
+ end
418
+ function Reconciler:fail(message)
419
+ if self.failure == nil then
420
+ self.failure = message
421
+ end
422
+ end
423
+ function Reconciler:notePairs()
424
+ self.pairsQueued = true
425
+ end
426
+ function Reconciler:push(s)
427
+ if s.queued then
428
+ return nil
429
+ end
430
+ s.queued = true
431
+ local _fresh = self.fresh
432
+ local _s = s
433
+ table.insert(_fresh, _s)
434
+ end
435
+ function Reconciler:chain(s)
436
+ local stepping = self.stepping
437
+ if stepping ~= s then
438
+ s.transitions = 0
439
+ end
440
+ if stepping == nil then
441
+ return nil
442
+ end
443
+ self.requests += 1
444
+ if self.requests <= MAX_CHAINED_REQUESTS or stepping.fault ~= nil then
445
+ return nil
446
+ end
447
+ -- A cycle's members keep requesting while a passive target never does, so the requester is the one to end.
448
+ self.requests = 0
449
+ local message = `Crucible: objects kept changing each other through {MAX_CHAINED_REQUESTS} requests in one drain`
450
+ self:fail(message)
451
+ note(stepping, 9, message)
452
+ end
453
+ function Reconciler:raise(s, gate)
454
+ if s.gate < gate then
455
+ s.gate = gate
456
+ end
457
+ self:push(s)
458
+ end
459
+ function Reconciler:innermostBatch()
460
+ for i = #self.fresh - 1, 0, -1 do
461
+ local entry = self.fresh[i + 1]
462
+ if entry.work == 1 then
463
+ return entry
464
+ end
465
+ end
466
+ for i = #self.stack - 1, 0, -1 do
467
+ local entry = self.stack[i + 1]
468
+ if entry.work == 1 then
469
+ return entry
470
+ end
471
+ end
472
+ return nil
473
+ end
474
+ function Reconciler:admit(created)
475
+ if #created == 0 then
476
+ return nil
477
+ end
478
+ local open = self:innermostBatch()
479
+ if open ~= nil and awaking(open) then
480
+ for _, s in created do
481
+ local _exp = open.pending
482
+ table.insert(_exp, s)
483
+ end
484
+ return nil
485
+ end
486
+ if self.batches == MAX_BATCHES then
487
+ for _, s in created do
488
+ request(s, 2)
489
+ end
490
+ self:fail(`Crucible: objects kept creating objects for {MAX_BATCHES} batches in one drain; the rest were dropped`)
491
+ return nil
492
+ end
493
+ self.batches += 1
494
+ local _fresh = self.fresh
495
+ local _object = {
496
+ work = 1,
497
+ members = {},
498
+ wave = {},
499
+ }
500
+ local _left = "pending"
501
+ local _array = {}
502
+ local _length = #_array
503
+ table.move(created, 1, #created, _length + 1, _array)
504
+ _object[_left] = _array
505
+ _object.sweep = 1
506
+ _object.cursor = 0
507
+ _object.waves = 0
508
+ table.insert(_fresh, _object)
509
+ self:drain()
510
+ end
511
+ function Reconciler:abandon()
512
+ local _array = {}
513
+ local _length = #_array
514
+ local _array_1 = self.stack
515
+ table.move(_array_1, 1, #_array_1, _length + 1, _array)
516
+ local _array_2 = {}
517
+ local _length_1 = #_array_2
518
+ local _array_3 = self.fresh
519
+ table.move(_array_3, 1, #_array_3, _length_1 + 1, _array_2)
520
+ for _, list in { _array, _array_2 } do
521
+ for _1, entry in list do
522
+ if entry.work ~= 1 then
523
+ continue
524
+ end
525
+ for _2, s in entry.members do
526
+ request(s, 2)
527
+ end
528
+ for _2, s in entry.pending do
529
+ request(s, 2)
530
+ end
531
+ end
532
+ end
533
+ end
534
+ function Reconciler:advance(batch)
535
+ local _exp = batch.sweep
536
+ repeat
537
+ local _fallthrough = false
538
+ if _exp == 0 then
539
+ _fallthrough = true
540
+ end
541
+ if _fallthrough or _exp == 1 then
542
+ if batch.cursor < #batch.wave then
543
+ local s = batch.wave[batch.cursor + 1]
544
+ batch.cursor += 1
545
+ self:raise(s, if batch.sweep == 0 then 1 else 2)
546
+ return true
547
+ end
548
+ batch.cursor = 0
549
+ if batch.sweep == 0 then
550
+ batch.sweep = 1
551
+ return true
552
+ end
553
+ local wave = batch.pending
554
+ batch.pending = {}
555
+ if #wave == 0 then
556
+ batch.sweep = 2
557
+ elseif batch.waves == MAX_AWAKE_WAVES then
558
+ for _, s in wave do
559
+ request(s, 2)
560
+ end
561
+ self:fail(`Crucible: objects kept creating objects during awake for {MAX_AWAKE_WAVES} waves; the last wave was dropped`)
562
+ batch.sweep = 2
563
+ else
564
+ batch.waves += 1
565
+ batch.wave = wave
566
+ for _, s in wave do
567
+ local _exp_1 = batch.members
568
+ table.insert(_exp_1, s)
569
+ end
570
+ batch.sweep = 0
571
+ end
572
+ return true
573
+ end
574
+ if _exp == 2 then
575
+ _fallthrough = true
576
+ end
577
+ if _fallthrough or _exp == 3 then
578
+ if batch.cursor < #batch.members then
579
+ local s = batch.members[batch.cursor + 1]
580
+ batch.cursor += 1
581
+ self:raise(s, if batch.sweep == 2 then 3 else 4)
582
+ return true
583
+ end
584
+ if batch.sweep == 3 then
585
+ return false
586
+ end
587
+ batch.cursor = 0
588
+ batch.sweep = 3
589
+ return true
590
+ end
591
+ until true
592
+ return false
593
+ end
594
+ function Reconciler:settleStep(s)
595
+ local step = nextStep(s)
596
+ if step == nil then
597
+ return false
598
+ end
599
+ s.transitions += 1
600
+ if s.fault == nil then
601
+ if s.transitions > MAX_TRANSITIONS then
602
+ note(s, 9, `did not settle after {MAX_TRANSITIONS} transitions since a request from outside it`)
603
+ return true
604
+ end
605
+ end
606
+ self.stepping = s
607
+ perform(s, step)
608
+ self.stepping = nil
609
+ return true
610
+ end
611
+ function Reconciler:settle()
612
+ while true do
613
+ while #self.fresh > 0 do
614
+ local _stack = self.stack
615
+ local _exp = self.fresh
616
+ -- ▼ Array.pop ▼
617
+ local _length = #_exp
618
+ local _result = _exp[_length]
619
+ _exp[_length] = nil
620
+ -- ▲ Array.pop ▲
621
+ table.insert(_stack, _result)
622
+ end
623
+ local top = self.stack[#self.stack]
624
+ if top == nil or top.work == 1 then
625
+ if self.pairsQueued then
626
+ self.pairsQueued = false
627
+ if self.rounds == MAX_RECONCILE_ROUNDS then
628
+ self:fail(`Crucible: binding kept queueing pairs for {MAX_RECONCILE_ROUNDS} reconcile rounds in one drain; the rest wait for the next drain`)
629
+ else
630
+ self.rounds += 1
631
+ self:admit(effects().reconcile())
632
+ end
633
+ continue
634
+ end
635
+ if top == nil then
636
+ return nil
637
+ end
638
+ if not self:advance(top) then
639
+ local _exp = self.stack
640
+ _exp[#_exp] = nil
641
+ end
642
+ continue
643
+ end
644
+ if not self:settleStep(top) then
645
+ local _exp = self.stack
646
+ _exp[#_exp] = nil
647
+ top.queued = false
648
+ end
649
+ end
650
+ end
651
+ function Reconciler:unwind()
652
+ local stranded = {}
653
+ local rest = {}
654
+ local _array = {}
655
+ local _length = #_array
656
+ local _array_1 = self.fresh
657
+ table.move(_array_1, 1, #_array_1, _length + 1, _array)
658
+ local order = _array
659
+ for i = #self.stack - 1, 0, -1 do
660
+ local _arg0 = self.stack[i + 1]
661
+ table.insert(order, _arg0)
662
+ end
663
+ table.clear(self.fresh)
664
+ table.clear(self.stack)
665
+ for _, entry in order do
666
+ if entry.work == 0 then
667
+ entry.queued = false
668
+ table.insert(rest, entry)
669
+ continue
670
+ end
671
+ for _1, s in entry.members do
672
+ if s.gate < 4 then
673
+ table.insert(stranded, s)
674
+ end
675
+ end
676
+ for _1, s in entry.pending do
677
+ if s.gate < 4 then
678
+ table.insert(stranded, s)
679
+ end
680
+ end
681
+ end
682
+ return stranded, rest
683
+ end
684
+ function Reconciler:drain()
685
+ if self.depth > 0 or (#self.stack == 0 and #self.fresh == 0 and not self.pairsQueued) then
686
+ return nil
687
+ end
688
+ local errors = {}
689
+ local faulted = {}
690
+ local idle = 0
691
+ local result, err = self:enter(settle, self, nil)
692
+ while result ~= 0 do
693
+ local _arg0 = tostring(err)
694
+ table.insert(errors, _arg0)
695
+ local culprit = self.stepping
696
+ self.stepping = nil
697
+ local stranded, rest = self:unwind()
698
+ local progressed = false
699
+ if culprit ~= nil and not hasEnded(culprit) then
700
+ if faulted[culprit] ~= nil then
701
+ forceEnd(culprit)
702
+ else
703
+ faulted[culprit] = true
704
+ note(culprit, 11, err)
705
+ end
706
+ progressed = true
707
+ end
708
+ for _, s in stranded do
709
+ if hasEnded(s) or faulted[s] ~= nil then
710
+ continue
711
+ end
712
+ faulted[s] = true
713
+ note(s, 11, err)
714
+ progressed = true
715
+ end
716
+ idle = if progressed then 0 else idle + 1
717
+ local _array = {}
718
+ local _length = #_array
719
+ local _strandedLength = #stranded
720
+ table.move(stranded, 1, _strandedLength, _length + 1, _array)
721
+ _length += _strandedLength
722
+ table.move(rest, 1, #rest, _length + 1, _array)
723
+ -- ▼ ReadonlyArray.filter ▼
724
+ local _newValue = {}
725
+ local _callback = function(s)
726
+ return not hasEnded(s)
727
+ end
728
+ local _length_1 = 0
729
+ for _k, _v in _array do
730
+ if _callback(_v, _k - 1, _array) == true then
731
+ _length_1 += 1
732
+ _newValue[_length_1] = _v
733
+ end
734
+ end
735
+ -- ▲ ReadonlyArray.filter ▲
736
+ local left = _newValue
737
+ if idle > MAX_RECOVERIES then
738
+ self.pairsQueued = false
739
+ for _, s in left do
740
+ if hasEnded(s) then
741
+ continue
742
+ end
743
+ if not (faulted[s] ~= nil) then
744
+ note(s, 11, err)
745
+ end
746
+ forceEnd(s)
747
+ end
748
+ break
749
+ end
750
+ for _, s in left do
751
+ self:push(s)
752
+ end
753
+ result, err = self:enter(settle, self, nil)
754
+ end
755
+ self.rounds = 0
756
+ self.batches = 0
757
+ self.requests = 0
758
+ self.stepping = nil
759
+ local failure = self.failure
760
+ self.failure = nil
761
+ local fx = effects()
762
+ for _, e in errors do
763
+ fx.failed(`Crucible: internal error; the reconciler recovered: {e}`)
764
+ end
765
+ if failure ~= nil then
766
+ fx.failed(failure)
767
+ end
768
+ end
769
+ function Reconciler:within(owner, blame, hook, callback, a, b)
770
+ local outer = self.depth > 0
771
+ local result, value = self:enter(callback, a, b, owner)
772
+ if result ~= 0 and blame ~= nil then
773
+ fault(blame, hook, value)
774
+ end
775
+ if not outer then
776
+ self:drain()
777
+ end
778
+ return result, value
779
+ end
780
+ end
781
+ local reconciler = Reconciler.new()
782
+ --* @internal True while a drain runs, including inside every hook and callback it calls.
783
+ local function isDraining()
784
+ return reconciler:isDraining()
785
+ end
786
+ --[[
787
+ *
788
+ * @internal
789
+ * Opens a scope like `within` for a caller that runs its own worker: requests made until `closeScope` wait for it.
790
+ * Pair the two, with nothing between them that can throw.
791
+
792
+ ]]
793
+ local function openScope()
794
+ reconciler:open()
795
+ end
796
+ --* @internal Closes the scope `openScope` opened. True when it was the outermost and requests wait: call `drain`.
797
+ local function closeScope()
798
+ return reconciler:leave()
799
+ end
800
+ --[[
801
+ *
802
+ * @internal
803
+ * Runs every queued change to rest. Returns at once when a drain is already running: that drain processes the
804
+ * queue before it returns. Never throws. A cap or an internal error goes to `effects.failed` after recovery.
805
+
806
+ ]]
807
+ local function drain()
808
+ reconciler:drain()
809
+ end
810
+ --[[
811
+ *
812
+ * @internal
813
+ * Binding has queued pairs. The drain reconciles them when its top is a batch or it is empty. Past the round
814
+ * cap the drain drops the note, so binding calls this again whenever it queues.
815
+
816
+ ]]
817
+ local function notePairs()
818
+ reconciler:notePairs()
819
+ end
820
+ --[[
821
+ *
822
+ * @internal
823
+ * Runs one callback as its own scope: a frame hook, an `All<T>` visit, or an observer callback. Requests it
824
+ * makes apply after it returns. A failure faults `blame`; with no blame the caller logs the returned failure.
825
+ * Outside a drain, the scope drains when it ends, so the next frame hook in a lane sees the effects of the one
826
+ * before.
827
+
828
+ ]]
829
+ local function within(owner, blame, hook, callback, a, b)
830
+ return reconciler:within(owner, blame, hook, callback, a, b)
831
+ end
832
+ --* @internal Updates `want` and queues the object. Called by `setEnabled`, `remove()` and binding. Ignored once Destroyed is wanted.
833
+ function request(s, want)
834
+ if s.want == 2 then
835
+ return nil
836
+ end
837
+ s.want = want
838
+ reconciler:chain(s)
839
+ reconciler:push(s)
840
+ reconciler:drain()
841
+ end
842
+ --* @internal Faults the object from outside a lifecycle step: a frame hook, `accepts`, or an `All<T>` callback.
843
+ function fault(s, hook, err)
844
+ if hasEnded(s) then
845
+ return nil
846
+ end
847
+ note(s, hook, err)
848
+ reconciler:chain(s)
849
+ reconciler:push(s)
850
+ reconciler:drain()
851
+ end
852
+ --* @internal Called by the start thread when `start` returns or throws. A start that outlived its generation is ignored.
853
+ function finishStart(s, ok, err)
854
+ if hasEnded(s) then
855
+ return nil
856
+ end
857
+ s.startThread = nil
858
+ s.start = 2
859
+ if not ok then
860
+ note(s, 3, err)
861
+ end
862
+ reconciler:chain(s)
863
+ reconciler:push(s)
864
+ reconciler:drain()
865
+ end
866
+ --[[
867
+ *
868
+ * @internal
869
+ * Gives new generations a batch. Inside the awake closure of the innermost batch they join its next wave, so
870
+ * every object a batch creates during awake is awake before any of the batch is enabled. Otherwise they form
871
+ * a new batch, which runs to rest before the code that created them continues.
872
+
873
+ ]]
874
+ local function admit(created)
875
+ reconciler:admit(created)
876
+ end
877
+ --* @internal Asks every generation in an unfinished batch to end. Boot calls it when a Subsystem faults.
878
+ local function abandon()
879
+ reconciler:abandon()
880
+ end
881
+ --* @internal Forwards to the installed effects' `defer`, so networking defers ClientReady cleanups as lifecycle does.
882
+ local function defer(callback)
883
+ effects().defer(callback)
884
+ end
885
+ return {
886
+ nextStep = nextStep,
887
+ useEffects = useEffects,
888
+ stateOf = stateOf,
889
+ enabling = enabling,
890
+ isLive = isLive,
891
+ hasEnded = hasEnded,
892
+ track = track,
893
+ current = current,
894
+ isDraining = isDraining,
895
+ openScope = openScope,
896
+ closeScope = closeScope,
897
+ drain = drain,
898
+ notePairs = notePairs,
899
+ within = within,
900
+ request = request,
901
+ fault = fault,
902
+ finishStart = finishStart,
903
+ admit = admit,
904
+ abandon = abandon,
905
+ defer = defer,
906
+ MAX_TRANSITIONS = MAX_TRANSITIONS,
907
+ MAX_AWAKE_WAVES = MAX_AWAKE_WAVES,
908
+ MAX_BATCHES = MAX_BATCHES,
909
+ MAX_CHAINED_REQUESTS = MAX_CHAINED_REQUESTS,
910
+ MAX_RECONCILE_ROUNDS = MAX_RECONCILE_ROUNDS,
911
+ MAX_RECOVERIES = MAX_RECOVERIES,
912
+ }