@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,981 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local Subsystem = TS.import(script, script.Parent.Parent.Parent, "subsystem").Subsystem
4
+ local runtimeDiagnostic = TS.import(script, script.Parent.Parent, "diagnostics").runtimeDiagnostic
5
+ local _lifecycle = TS.import(script, script.Parent.Parent, "lifecycle")
6
+ local isLive = _lifecycle.isLive
7
+ local stateOf = _lifecycle.stateOf
8
+ local _schema = TS.import(script, script.Parent.Parent, "network", "schema")
9
+ local admitsNil = _schema.admitsNil
10
+ local assertDescriptor = _schema.assertDescriptor
11
+ local runtimeKind = _schema.runtimeKind
12
+ local _graph = TS.import(script, script.Parent.Parent, "reactive", "graph")
13
+ local createStateNode = _graph.createStateNode
14
+ local readState = _graph.readState
15
+ local write = _graph.write
16
+ local define = TS.import(script, script.Parent.Parent, "registry").define
17
+ local L = TS.import(script, script.Parent, "limits")
18
+ local configPort = TS.import(script, script.Parent, "port").configPort
19
+ --* One value a reader sees. Commit is its only writer.
20
+ --* A config value as reactive state, so a read is tracked (rule 93) and a commit wakes its readers once.
21
+ local KEEP = table.freeze({})
22
+ local declarations = {}
23
+ local names = {}
24
+ local sealer
25
+ local function newScope(player)
26
+ return {
27
+ player = player,
28
+ phase = 0,
29
+ diagnosed = {},
30
+ ready = createStateNode(false),
31
+ values = {},
32
+ }
33
+ end
34
+ local global = newScope()
35
+ local slots = {}
36
+ --* Bumped at every join, so a read for a player with no scope yet reruns once their scope exists (rule 93).
37
+ local roster = createStateNode(0)
38
+ local owner
39
+ local retired = false
40
+ local listeners = {}
41
+ local flight
42
+ local active = 0
43
+ local queue = {}
44
+ local function fail(path, reason)
45
+ error({
46
+ configFailure = true,
47
+ path = path,
48
+ reason = reason,
49
+ })
50
+ end
51
+ local F32_MAX = 3.4028234663852886e38
52
+ local UINT_MAX = {
53
+ u8 = 255,
54
+ u16 = 65_535,
55
+ u32 = 4_294_967_295,
56
+ }
57
+ local function admit(node, value, root)
58
+ local visits = 0
59
+ local open = {}
60
+ local function enter(value, path, depth)
61
+ visits += 1
62
+ if visits > L.CONFIG_ADMISSION_VISITS then
63
+ fail(path, `more than {L.CONFIG_ADMISSION_VISITS} values`)
64
+ end
65
+ if depth > L.CONFIG_VALUE_DEPTH then
66
+ fail(path, `deeper than {L.CONFIG_VALUE_DEPTH}`)
67
+ end
68
+ if getmetatable(value) ~= nil then
69
+ fail(path, "a table with a metatable")
70
+ end
71
+ local _value = value
72
+ if open[_value] ~= nil then
73
+ fail(path, "a cycle")
74
+ end
75
+ local _value_1 = value
76
+ open[_value_1] = true
77
+ end
78
+ local function finish(source, copy)
79
+ local _source = source
80
+ open[_source] = nil
81
+ return table.freeze(copy)
82
+ end
83
+ local function scalar(value, path)
84
+ visits += 1
85
+ if visits > L.CONFIG_ADMISSION_VISITS then
86
+ fail(path, `more than {L.CONFIG_ADMISSION_VISITS} values`)
87
+ end
88
+ local _value = value
89
+ if type(_value) == "number" then
90
+ if value ~= value or math.abs(value) == math.huge then
91
+ fail(path, "expected a finite number")
92
+ end
93
+ else
94
+ local _value_1 = value
95
+ if type(_value_1) == "string" then
96
+ if #value > L.CONFIG_STRING_MAX_BYTES then
97
+ fail(path, `a string over {L.CONFIG_STRING_MAX_BYTES} bytes`)
98
+ end
99
+ if (utf8.len(value)) == nil then
100
+ fail(path, "invalid UTF-8")
101
+ end
102
+ else
103
+ local _value_2 = value
104
+ if not (type(_value_2) == "boolean") then
105
+ local _exp = path
106
+ local _value_3 = value
107
+ fail(_exp, `expected JSON, got {typeof(_value_3)}`)
108
+ end
109
+ end
110
+ end
111
+ return value
112
+ end
113
+ local function isArray(value)
114
+ local array = value
115
+ local count = 0
116
+ for key in pairs(array) do
117
+ if not (type(key) == "number") or key % 1 ~= 0 or key < 1 or key > #array then
118
+ return false
119
+ end
120
+ count += 1
121
+ end
122
+ return count == #array
123
+ end
124
+ local function json(value, path, depth)
125
+ local _value = value
126
+ if not (type(_value) == "table") then
127
+ return scalar(value, path)
128
+ end
129
+ enter(value, path, depth)
130
+ local copy = {}
131
+ if isArray(value) then
132
+ local array = value
133
+ for i = 0, #array - 1 do
134
+ local _arg0 = i + 1
135
+ local _arg1 = json(array[i + 1], `{path}[{i}]`, depth + 1)
136
+ copy[_arg0] = _arg1
137
+ end
138
+ else
139
+ for key, child in pairs(value) do
140
+ if not (type(key) == "string") then
141
+ fail(path, "a table mixing array and object keys")
142
+ end
143
+ local _arg1 = json(child, `{path}.{key}`, depth + 1)
144
+ copy[key] = _arg1
145
+ end
146
+ end
147
+ return finish(value, copy)
148
+ end
149
+ local function tableAt(value, path, depth)
150
+ local _value = value
151
+ if not (type(_value) == "table") then
152
+ local _exp = path
153
+ local _value_1 = value
154
+ fail(_exp, `expected a table, got {typeof(_value_1)}`)
155
+ end
156
+ enter(value, path, depth)
157
+ return value
158
+ end
159
+ local function dense(value, path, depth)
160
+ local t = tableAt(value, path, depth)
161
+ if not isArray(t) then
162
+ fail(path, "expected an array")
163
+ end
164
+ return t
165
+ end
166
+ local function walk(node, value, path, depth)
167
+ local _exp = node.kind
168
+ repeat
169
+ local _fallthrough = false
170
+ if _exp == "nil" then
171
+ if value ~= nil then
172
+ fail(path, "expected nothing")
173
+ end
174
+ return value
175
+ end
176
+ if _exp == "literal" then
177
+ if value ~= node.value then
178
+ fail(path, `expected the literal {tostring(node.value)}`)
179
+ end
180
+ return scalar(value, path)
181
+ end
182
+ if _exp == "bool" then
183
+ local _value = value
184
+ if not (type(_value) == "boolean") then
185
+ local _exp_1 = path
186
+ local _value_1 = value
187
+ fail(_exp_1, `expected a boolean, got {typeof(_value_1)}`)
188
+ end
189
+ return scalar(value, path)
190
+ end
191
+ if _exp == "string" then
192
+ local _value = value
193
+ if not (type(_value) == "string") then
194
+ local _exp_1 = path
195
+ local _value_1 = value
196
+ fail(_exp_1, `expected a string, got {typeof(_value_1)}`)
197
+ end
198
+ return scalar(value, path)
199
+ end
200
+ if _exp == "f64" then
201
+ _fallthrough = true
202
+ end
203
+ if _fallthrough or _exp == "f32" then
204
+ _fallthrough = true
205
+ end
206
+ if _fallthrough or _exp == "u8" then
207
+ _fallthrough = true
208
+ end
209
+ if _fallthrough or _exp == "u16" then
210
+ _fallthrough = true
211
+ end
212
+ if _fallthrough or _exp == "u32" then
213
+ local _value = value
214
+ if not (type(_value) == "number") then
215
+ local _exp_1 = path
216
+ local _value_1 = value
217
+ fail(_exp_1, `expected a number, got {typeof(_value_1)}`)
218
+ end
219
+ scalar(value, path)
220
+ if node.kind == "f32" and math.abs(value) > F32_MAX then
221
+ fail(path, "out of f32 range")
222
+ end
223
+ if node.kind ~= "f64" and node.kind ~= "f32" then
224
+ local maximum = UINT_MAX[node.kind]
225
+ if value % 1 ~= 0 or value < 0 or value > maximum then
226
+ fail(path, `expected an integer in [0, {maximum}]`)
227
+ end
228
+ end
229
+ return value
230
+ end
231
+ if _exp == "object" then
232
+ local source = tableAt(value, path, depth)
233
+ local copy = {}
234
+ local declared = {}
235
+ for _, _binding in node.fields do
236
+ local name = _binding[1]
237
+ local child = _binding[2]
238
+ declared[name] = true
239
+ local field = source[name]
240
+ if field ~= nil then
241
+ local _arg1 = walk(child, field, `{path}.{name}`, depth + 1)
242
+ copy[name] = _arg1
243
+ elseif not admitsNil(child) then
244
+ fail(`{path}.{name}`, "a required field is missing")
245
+ end
246
+ end
247
+ for key, child in pairs(source) do
248
+ if not (type(key) == "string") then
249
+ fail(path, "expected an object")
250
+ end
251
+ if not (declared[key] ~= nil) then
252
+ local _arg1 = json(child, `{path}.{key}`, depth + 1)
253
+ copy[key] = _arg1
254
+ end
255
+ end
256
+ return finish(source, copy)
257
+ end
258
+ if _exp == "array" then
259
+ local source = dense(value, path, depth)
260
+ -- ▼ ReadonlyArray.map ▼
261
+ local _newValue = table.create(#source)
262
+ local _callback = function(element, i)
263
+ return walk(node.element, element, `{path}[{i}]`, depth + 1)
264
+ end
265
+ for _k, _v in source do
266
+ _newValue[_k] = _callback(_v, _k - 1, source)
267
+ end
268
+ -- ▲ ReadonlyArray.map ▲
269
+ local copy = _newValue
270
+ return finish(source, copy)
271
+ end
272
+ if _exp == "tuple" then
273
+ local source = dense(value, path, depth)
274
+ if #source ~= #node.elements then
275
+ fail(path, `expected {#node.elements} elements`)
276
+ end
277
+ local _exp_1 = node.elements
278
+ -- ▼ ReadonlyArray.map ▼
279
+ local _newValue = table.create(#_exp_1)
280
+ local _callback = function(element, i)
281
+ return walk(element, source[i + 1], `{path}[{i}]`, depth + 1)
282
+ end
283
+ for _k, _v in _exp_1 do
284
+ _newValue[_k] = _callback(_v, _k - 1, _exp_1)
285
+ end
286
+ -- ▲ ReadonlyArray.map ▲
287
+ local copy = _newValue
288
+ return finish(source, copy)
289
+ end
290
+ if _exp == "map" then
291
+ local source = tableAt(value, path, depth)
292
+ local copy = {}
293
+ for key, child in pairs(source) do
294
+ local at = `{path}.{tostring(key)}`
295
+ walk(node.key, key, at, depth + 1)
296
+ local _arg1 = walk(node.value, child, at, depth + 1)
297
+ copy[key] = _arg1
298
+ end
299
+ return finish(source, copy)
300
+ end
301
+ if _exp == "union" then
302
+ for _, branch in node.branches do
303
+ if branch.kind == "variant" then
304
+ local _value = value
305
+ local _condition = type(_value) == "table"
306
+ if _condition then
307
+ _condition = value[branch.key] == branch.value
308
+ end
309
+ if _condition then
310
+ return walk(branch.body, value, path, depth)
311
+ end
312
+ else
313
+ local _result
314
+ if branch.kind == "literal" then
315
+ _result = value == branch.value
316
+ else
317
+ local _result_1
318
+ if branch.kind == "nil" then
319
+ _result_1 = value == nil
320
+ else
321
+ local _value = value
322
+ _result_1 = typeof(_value) == runtimeKind(branch)
323
+ end
324
+ _result = _result_1
325
+ end
326
+ if _result then
327
+ return walk(branch, value, path, depth)
328
+ end
329
+ end
330
+ end
331
+ local _exp_1 = path
332
+ local _value = value
333
+ return fail(_exp_1, `matches no member of the union, got {typeof(_value)}`)
334
+ end
335
+ fail(path, `a {node.kind} cannot be a config value`)
336
+ until true
337
+ end
338
+ return walk(node, value, root, 0)
339
+ end
340
+ local function acceptsString(node)
341
+ if node.kind == "union" then
342
+ local _exp = node.branches
343
+ -- ▼ ReadonlyArray.some ▼
344
+ local _result = false
345
+ for _k, _v in _exp do
346
+ if acceptsString(_v, _k - 1, _exp) then
347
+ _result = true
348
+ break
349
+ end
350
+ end
351
+ -- ▲ ReadonlyArray.some ▲
352
+ return _result
353
+ end
354
+ local _condition = node.kind == "string"
355
+ if not _condition then
356
+ local _condition_1 = node.kind == "literal"
357
+ if _condition_1 then
358
+ local _value = node.value
359
+ _condition_1 = type(_value) == "string"
360
+ end
361
+ _condition = _condition_1
362
+ end
363
+ return _condition
364
+ end
365
+ local function admitRaw(declaration, raw)
366
+ local port = configPort()
367
+ local value = raw
368
+ local _raw = raw
369
+ if type(_raw) == "string" then
370
+ if #raw > L.CONFIG_VALUE_MAX_BYTES then
371
+ fail(declaration.name, `over {L.CONFIG_VALUE_MAX_BYTES} bytes`)
372
+ end
373
+ if not acceptsString(declaration.node) then
374
+ local ok, decoded = pcall(function()
375
+ return port.decodeJson(raw)
376
+ end)
377
+ if not ok then
378
+ fail(declaration.name, "invalid JSON")
379
+ end
380
+ value = decoded
381
+ end
382
+ end
383
+ local _value = value
384
+ if type(_value) == "table" then
385
+ local ok, bytes = pcall(function()
386
+ return port.jsonBytes(value)
387
+ end)
388
+ if not ok then
389
+ fail(declaration.name, "not a JSON value")
390
+ end
391
+ if bytes > L.CONFIG_VALUE_MAX_BYTES then
392
+ fail(declaration.name, `over {L.CONFIG_VALUE_MAX_BYTES} bytes`)
393
+ end
394
+ end
395
+ return admit(declaration.node, value, declaration.name)
396
+ end
397
+ local function failure(err)
398
+ local _err = err
399
+ local _condition = type(_err) == "table"
400
+ if _condition then
401
+ _condition = err.configFailure == true
402
+ end
403
+ if _condition then
404
+ return err
405
+ end
406
+ return {
407
+ configFailure = true,
408
+ path = "",
409
+ reason = "admission failed",
410
+ }
411
+ end
412
+ local function deepEqual(a, b)
413
+ if a == b then
414
+ return true
415
+ end
416
+ local _a = a
417
+ local _condition = not (type(_a) == "table")
418
+ if not _condition then
419
+ local _b = b
420
+ _condition = not (type(_b) == "table")
421
+ end
422
+ if _condition then
423
+ return false
424
+ end
425
+ for key, value in pairs(a) do
426
+ if not deepEqual(value, b[key]) then
427
+ return false
428
+ end
429
+ end
430
+ for key in pairs(b) do
431
+ if a[key] == nil then
432
+ return false
433
+ end
434
+ end
435
+ return true
436
+ end
437
+ local function validName(name)
438
+ local _name = name
439
+ local _condition = not (type(_name) == "string")
440
+ if not _condition then
441
+ _condition = #name == 0
442
+ end
443
+ if _condition then
444
+ return false
445
+ end
446
+ local segments = string.split(name, ".")
447
+ local _condition_1 = segments[1] ~= "Crucible"
448
+ if _condition_1 then
449
+ -- ▼ ReadonlyArray.every ▼
450
+ local _result = true
451
+ local _callback = function(s)
452
+ return (string.match(s, "^[A-Za-z][A-Za-z0-9_]*$")) ~= nil
453
+ end
454
+ for _k, _v in segments do
455
+ if not _callback(_v, _k - 1, segments) then
456
+ _result = false
457
+ break
458
+ end
459
+ end
460
+ -- ▲ ReadonlyArray.every ▲
461
+ _condition_1 = _result
462
+ end
463
+ return _condition_1
464
+ end
465
+ local ended
466
+ local function declare(name, kind, fallback, node)
467
+ if not validName(name) then
468
+ error(`Config name {tostring(name)} is invalid: use dot-separated [A-Za-z][A-Za-z0-9_]* segments, not Crucible.*`, 3)
469
+ end
470
+ if #name > L.CONFIG_NAME_MAX_BYTES then
471
+ error(`Config name {name} is {#name} bytes; the limit is {L.CONFIG_NAME_MAX_BYTES}`, 3)
472
+ end
473
+ local _name = name
474
+ if names[_name] ~= nil then
475
+ error(`Config {name} is declared twice`, 3)
476
+ end
477
+ if #declarations >= L.MAX_CONFIG_DECLARATIONS then
478
+ error(`Config {name} passes the limit of {L.MAX_CONFIG_DECLARATIONS} configs`, 3)
479
+ end
480
+ if sealer ~= nil and not ended(sealer) then
481
+ error(`{name} was declared after the config registry sealed; declare configs in modules that boot loads`, 3)
482
+ end
483
+ assertDescriptor(node)
484
+ local ok, admitted = pcall(function()
485
+ return admit(node, fallback, name)
486
+ end)
487
+ if not ok then
488
+ local f = failure(admitted)
489
+ error(`Config {name} has an invalid fallback at {f.path}: {f.reason}`, 3)
490
+ end
491
+ local declaration = {
492
+ name = name,
493
+ kind = kind,
494
+ node = node,
495
+ fallback = admitted,
496
+ global = createStateNode(admitted),
497
+ }
498
+ local _name_1 = name
499
+ names[_name_1] = true
500
+ table.insert(declarations, declaration)
501
+ return declaration
502
+ end
503
+ local function readGlobal(declaration)
504
+ return readState(declaration.global)
505
+ end
506
+ local function readPlayer(declaration, player)
507
+ local _player = player
508
+ local scope = slots[_player]
509
+ if scope == nil then
510
+ readState(roster)
511
+ return declaration.fallback
512
+ end
513
+ if readState(scope.ready) ~= true then
514
+ return readState(declaration.global)
515
+ end
516
+ local _values = scope.values
517
+ local _declaration = declaration
518
+ return readState(_values[_declaration])
519
+ end
520
+ local function ready(player)
521
+ if player == nil then
522
+ return readState(global.ready)
523
+ end
524
+ local _player = player
525
+ local scope = slots[_player]
526
+ if scope == nil then
527
+ readState(roster)
528
+ return false
529
+ end
530
+ return readState(scope.ready) == true
531
+ end
532
+ local function seal(runtime)
533
+ sealer = runtime
534
+ end
535
+ function ended(runtime)
536
+ local s = stateOf(runtime)
537
+ return s == nil or not isLive(s)
538
+ end
539
+ local function retire()
540
+ retired = true
541
+ for _, disconnect in listeners do
542
+ disconnect()
543
+ end
544
+ listeners = {}
545
+ local _result = global.snapshot
546
+ if _result ~= nil then
547
+ _result.disconnect()
548
+ end
549
+ for _, scope in slots do
550
+ local _result_1 = scope.snapshot
551
+ if _result_1 ~= nil then
552
+ _result_1.disconnect()
553
+ end
554
+ end
555
+ end
556
+ local function live()
557
+ return owner ~= nil and not retired
558
+ end
559
+ --* `subject` is a config name, or undefined for the snapshot request itself.
560
+ local function diagnose(scope, subject, reason)
561
+ local _condition = subject
562
+ if _condition == nil then
563
+ _condition = ""
564
+ end
565
+ local key = _condition
566
+ if scope.diagnosed[key] ~= nil then
567
+ return nil
568
+ end
569
+ scope.diagnosed[key] = true
570
+ local where = if scope.player == nil then "global snapshot" else `snapshot of player {scope.player.UserId}`
571
+ local _condition_1 = subject
572
+ if _condition_1 == nil then
573
+ _condition_1 = "request"
574
+ end
575
+ runtimeDiagnostic("warning", `Config {_condition_1} ({where}): {reason}`)
576
+ end
577
+ local function acquire()
578
+ if active < L.CONFIG_REQUEST_CONCURRENCY then
579
+ active += 1
580
+ return nil
581
+ end
582
+ local _arg0 = coroutine.running()
583
+ table.insert(queue, _arg0)
584
+ coroutine.yield()
585
+ end
586
+ local function release()
587
+ local waiting = table.remove(queue, 1)
588
+ if waiting ~= nil then
589
+ task.spawn(waiting)
590
+ else
591
+ active -= 1
592
+ end
593
+ end
594
+ local function abandoned(scope)
595
+ return scope.phase == 2 or not live()
596
+ end
597
+ --* One ConfigService request through the shared limit. Undefined when the scope is gone or configs retired.
598
+ local function request(scope, call)
599
+ acquire()
600
+ if abandoned(scope) then
601
+ release()
602
+ return nil
603
+ end
604
+ local ok, value = pcall(call)
605
+ release()
606
+ if abandoned(scope) then
607
+ return nil
608
+ end
609
+ return {
610
+ ok = ok,
611
+ value = value,
612
+ }
613
+ end
614
+ local function stage(scope, snapshot, first, updates)
615
+ scope.diagnosed = {}
616
+ local values = {}
617
+ for _, declaration in declarations do
618
+ if scope.player ~= nil and declaration.kind ~= "player" then
619
+ continue
620
+ end
621
+ local found, raw = pcall(function()
622
+ return snapshot.port.get(declaration.name)
623
+ end)
624
+ if not found then
625
+ diagnose(scope, declaration.name, "GetValue failed")
626
+ values[declaration] = KEEP
627
+ elseif raw == nil then
628
+ local _fallback = declaration.fallback
629
+ values[declaration] = _fallback
630
+ else
631
+ local ok, admitted = pcall(function()
632
+ return admitRaw(declaration, raw)
633
+ end)
634
+ if ok then
635
+ values[declaration] = admitted
636
+ else
637
+ local f = failure(admitted)
638
+ diagnose(scope, declaration.name, `{f.path}: {f.reason}; keeping the last good value`)
639
+ values[declaration] = KEEP
640
+ end
641
+ end
642
+ end
643
+ return {
644
+ scope = scope,
645
+ snapshot = snapshot,
646
+ first = first,
647
+ updates = updates,
648
+ values = values,
649
+ }
650
+ end
651
+ local function failed(snapshot)
652
+ local ok, value = pcall(function()
653
+ return snapshot.port.failed()
654
+ end)
655
+ return not ok or value == true
656
+ end
657
+ local function stale(snapshot)
658
+ if snapshot.updates ~= snapshot.applied or snapshot.dropped or failed(snapshot) then
659
+ return true
660
+ end
661
+ local ok, outdated = pcall(function()
662
+ return snapshot.port.outdated()
663
+ end)
664
+ return not ok or outdated == true
665
+ end
666
+ local function fetch(scope)
667
+ local player = scope.player
668
+ local port = configPort()
669
+ local result = request(scope, function()
670
+ return if player == nil then port.global() else port.forPlayer(player)
671
+ end)
672
+ if result == nil then
673
+ return nil
674
+ end
675
+ if not result.ok then
676
+ diagnose(scope, nil, `the fetch failed: {tostring(result.value)}`)
677
+ return nil
678
+ end
679
+ local snapshotPort = result.value
680
+ local snapshot
681
+ snapshot = {
682
+ port = snapshotPort,
683
+ updates = 0,
684
+ applied = 0,
685
+ dropped = false,
686
+ disconnect = snapshotPort.onUpdateAvailable(function()
687
+ if live() then
688
+ snapshot.updates += 1
689
+ end
690
+ end),
691
+ }
692
+ if failed(snapshot) then
693
+ snapshot.disconnect()
694
+ diagnose(scope, nil, "the snapshot failed to load")
695
+ return nil
696
+ end
697
+ return stage(scope, snapshot, true, 0)
698
+ end
699
+ local function refreshScope(scope)
700
+ if scope.phase == 0 then
701
+ return fetch(scope)
702
+ end
703
+ local snapshot = scope.snapshot
704
+ if scope.phase ~= 1 or snapshot == nil or not stale(snapshot) then
705
+ return nil
706
+ end
707
+ local updates = snapshot.updates
708
+ local result = request(scope, function()
709
+ return snapshot.port.refresh()
710
+ end)
711
+ if result == nil then
712
+ return nil
713
+ end
714
+ if not result.ok or failed(snapshot) then
715
+ diagnose(scope, nil, if result.ok then "the snapshot failed to load" else `the refresh failed: {tostring(result.value)}`)
716
+ return nil
717
+ end
718
+ return stage(scope, snapshot, false, updates)
719
+ end
720
+ --* Writes every changed value of every result in one pass that never yields.
721
+ local function commit(results)
722
+ local apply = live()
723
+ for _, result in results do
724
+ local scope = result.scope
725
+ if not apply or scope.phase == 2 or (result.first and scope.phase == 1) then
726
+ if result.first then
727
+ result.snapshot.disconnect()
728
+ end
729
+ continue
730
+ end
731
+ if result.first then
732
+ scope.snapshot = result.snapshot
733
+ scope.phase = 1
734
+ end
735
+ result.snapshot.applied = result.updates
736
+ result.snapshot.dropped = false
737
+ for declaration, value in result.values do
738
+ local cell
739
+ if scope.player == nil then
740
+ cell = declaration.global
741
+ else
742
+ cell = scope.values[declaration] or createStateNode(declaration.global.value)
743
+ local _values = scope.values
744
+ local _cell = cell
745
+ _values[declaration] = _cell
746
+ end
747
+ if value ~= KEEP and not deepEqual(cell.value, value) then
748
+ write(cell, value)
749
+ end
750
+ end
751
+ write(scope.ready, true)
752
+ end
753
+ end
754
+ local function fetchLoop(scope)
755
+ local port = configPort()
756
+ do
757
+ local attempt = 0
758
+ local _shouldIncrement = false
759
+ while true do
760
+ if _shouldIncrement then
761
+ attempt += 1
762
+ else
763
+ _shouldIncrement = true
764
+ end
765
+ if attempt > 0 then
766
+ port.wait(port.random() * math.min(L.CONFIG_FETCH_RETRY_CAP_SECONDS, L.CONFIG_FETCH_RETRY_BASE_SECONDS * 2 ^ (attempt - 1)))
767
+ end
768
+ if scope.phase ~= 0 or not live() then
769
+ return nil
770
+ end
771
+ local result = fetch(scope)
772
+ if result ~= nil then
773
+ commit({ result })
774
+ return nil
775
+ end
776
+ end
777
+ end
778
+ end
779
+ local function refresh()
780
+ if not live() then
781
+ return false
782
+ end
783
+ if flight ~= nil then
784
+ local _waiters = flight.waiters
785
+ local _arg0 = coroutine.running()
786
+ table.insert(_waiters, _arg0)
787
+ return (coroutine.yield())
788
+ end
789
+ local current = {
790
+ waiters = {},
791
+ }
792
+ flight = current
793
+ local scopes = { global }
794
+ for _, scope in slots do
795
+ table.insert(scopes, scope)
796
+ end
797
+ local results = {}
798
+ local caller = coroutine.running()
799
+ local pending = #scopes
800
+ local closed = false
801
+ local parked = false
802
+ local close = function()
803
+ if closed then
804
+ return nil
805
+ end
806
+ closed = true
807
+ if parked then
808
+ task.spawn(caller)
809
+ end
810
+ end
811
+ for _, scope in scopes do
812
+ task.spawn(function()
813
+ local result = refreshScope(scope)
814
+ if closed then
815
+ local _result = result
816
+ if _result ~= nil then
817
+ _result = _result.first
818
+ end
819
+ if _result then
820
+ result.snapshot.disconnect()
821
+ elseif result ~= nil then
822
+ result.snapshot.dropped = true
823
+ end
824
+ return nil
825
+ end
826
+ if result ~= nil then
827
+ table.insert(results, result)
828
+ end
829
+ pending -= 1
830
+ if pending == 0 then
831
+ close()
832
+ end
833
+ end)
834
+ end
835
+ if not closed then
836
+ task.spawn(function()
837
+ configPort().wait(L.CONFIG_REFRESH_TIMEOUT_SECONDS)
838
+ close()
839
+ end)
840
+ parked = true
841
+ coroutine.yield()
842
+ end
843
+ commit(results)
844
+ local _condition = live()
845
+ if _condition then
846
+ -- ▼ ReadonlyArray.every ▼
847
+ local _result = true
848
+ local _callback = function(scope)
849
+ return scope.phase == 2 or (scope.phase == 1 and not stale(scope.snapshot))
850
+ end
851
+ for _k, _v in scopes do
852
+ if not _callback(_v, _k - 1, scopes) then
853
+ _result = false
854
+ break
855
+ end
856
+ end
857
+ -- ▲ ReadonlyArray.every ▲
858
+ _condition = _result
859
+ end
860
+ local ok = _condition
861
+ flight = nil
862
+ for _, waiter in current.waiters do
863
+ task.spawn(waiter, ok)
864
+ end
865
+ return ok
866
+ end
867
+ local function join(player)
868
+ local _condition = not live()
869
+ if not _condition then
870
+ local _player = player
871
+ _condition = slots[_player] ~= nil
872
+ end
873
+ if _condition then
874
+ return nil
875
+ end
876
+ local scope = newScope(player)
877
+ local _player = player
878
+ slots[_player] = scope
879
+ write(roster, (roster.value) + 1)
880
+ task.spawn(fetchLoop, scope)
881
+ end
882
+ local function leave(player)
883
+ if not live() then
884
+ return nil
885
+ end
886
+ local _player = player
887
+ local scope = slots[_player]
888
+ if scope == nil then
889
+ return nil
890
+ end
891
+ local _player_1 = player
892
+ slots[_player_1] = nil
893
+ scope.phase = 2
894
+ write(scope.ready, false)
895
+ local _result = scope.snapshot
896
+ if _result ~= nil then
897
+ _result.disconnect()
898
+ end
899
+ end
900
+ local function activate(runtime)
901
+ owner = runtime
902
+ retired = false
903
+ local _exp = stateOf(runtime)
904
+ if _exp.subscriptions == nil then
905
+ _exp.subscriptions = {}
906
+ end
907
+ local _exp_1 = (_exp.subscriptions)
908
+ table.insert(_exp_1, retire)
909
+ task.spawn(fetchLoop, global)
910
+ -- ▼ ReadonlyArray.some ▼
911
+ local _result = false
912
+ local _callback = function(d)
913
+ return d.kind == "player"
914
+ end
915
+ for _k, _v in declarations do
916
+ if _callback(_v, _k - 1, declarations) then
917
+ _result = true
918
+ break
919
+ end
920
+ end
921
+ -- ▲ ReadonlyArray.some ▲
922
+ if not _result then
923
+ return nil
924
+ end
925
+ local port = configPort()
926
+ local _listeners = listeners
927
+ local _arg0 = port.onPlayerAdded(join)
928
+ local _arg1 = port.onPlayerRemoving(leave)
929
+ -- ▼ Array.push ▼
930
+ table.insert(_listeners, _arg0)
931
+ table.insert(_listeners, _arg1)
932
+ -- ▲ Array.push ▲
933
+ for _, player in port.players() do
934
+ join(player)
935
+ end
936
+ end
937
+ local ConfigRuntime
938
+ do
939
+ local super = Subsystem
940
+ ConfigRuntime = setmetatable({}, {
941
+ __tostring = function()
942
+ return "ConfigRuntime"
943
+ end,
944
+ __index = super,
945
+ })
946
+ ConfigRuntime.__index = ConfigRuntime
947
+ function ConfigRuntime.new(...)
948
+ local self = setmetatable({}, ConfigRuntime)
949
+ return self:constructor(...) or self
950
+ end
951
+ function ConfigRuntime:constructor(...)
952
+ super.constructor(self, ...)
953
+ end
954
+ function ConfigRuntime:start()
955
+ seal(self)
956
+ if #declarations == 0 then
957
+ return nil
958
+ end
959
+ configPort().wait(0)
960
+ if ended(self) then
961
+ return nil
962
+ end
963
+ activate(self)
964
+ end
965
+ end
966
+ local function installConfigRuntime()
967
+ define(ConfigRuntime, {
968
+ kind = "subsystem",
969
+ id = "Crucible.ConfigRuntime",
970
+ deps = {},
971
+ provides = {},
972
+ })
973
+ end
974
+ return {
975
+ declare = declare,
976
+ readGlobal = readGlobal,
977
+ readPlayer = readPlayer,
978
+ ready = ready,
979
+ refresh = refresh,
980
+ installConfigRuntime = installConfigRuntime,
981
+ }