@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,918 @@
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 readClaim = TS.import(script, script.Parent.Parent, "construction-claim").readClaim
5
+ local _lifecycle = TS.import(script, script.Parent.Parent, "lifecycle")
6
+ local isLive = _lifecycle.isLive
7
+ local stateOf = _lifecycle.stateOf
8
+ local _graph = TS.import(script, script.Parent.Parent, "reactive", "graph")
9
+ local createComputedNode = _graph.createComputedNode
10
+ local createStateNode = _graph.createStateNode
11
+ local guardPure = _graph.guardPure
12
+ local readComputed = _graph.readComputed
13
+ local readState = _graph.readState
14
+ local write = _graph.write
15
+ local define = TS.import(script, script.Parent.Parent, "registry").define
16
+ local claimPublication = TS.import(script, script.Parent.Parent, "sync", "declarations").claimPublication
17
+ local _values = TS.import(script, script.Parent.Parent, "sync", "values")
18
+ local admitReplicated = _values.admit
19
+ local costOf = _values.costOf
20
+ local bootClock = TS.import(script, script.Parent.Parent, "boot").bootClock
21
+ local _declarations = TS.import(script, script.Parent, "declarations")
22
+ local installServerSide = _declarations.installServerSide
23
+ local profileDeclarations = _declarations.profileDeclarations
24
+ local rootHandle = TS.import(script, script.Parent, "handles").rootHandle
25
+ local _limits = TS.import(script, script.Parent, "limits")
26
+ local CALL_DEADLINE_SECONDS = _limits.CALL_DEADLINE_SECONDS
27
+ local LOAD_DEADLINE_SECONDS = _limits.LOAD_DEADLINE_SECONDS
28
+ local OWNER_VIEWS_MAX_BYTES = _limits.OWNER_VIEWS_MAX_BYTES
29
+ local OWNER_VIEWS_MAX_ENTRIES = _limits.OWNER_VIEWS_MAX_ENTRIES
30
+ local OWNER_VIEWS_MAX_VISITS = _limits.OWNER_VIEWS_MAX_VISITS
31
+ local SAVE_TIMEOUT_DEFAULT_SECONDS = _limits.SAVE_TIMEOUT_DEFAULT_SECONDS
32
+ local SAVE_TIMEOUT_MAX_SECONDS = _limits.SAVE_TIMEOUT_MAX_SECONDS
33
+ local messageOf = TS.import(script, script.Parent, "messages").messageOf
34
+ local getPath = TS.import(script, script.Parent, "paths").getPath
35
+ local dataPort = TS.import(script, script.Parent, "port").dataPort
36
+ local DataEngine = TS.import(script, script.Parent, "profiles").DataEngine
37
+ local _record_bindings = TS.import(script, script.Parent, "record-bindings")
38
+ local bindRecords = _record_bindings.bindRecords
39
+ local unbindRecords = _record_bindings.unbindRecords
40
+ local _record_declarations = TS.import(script, script.Parent, "record-declarations")
41
+ local declaredRecords = _record_declarations.declaredRecords
42
+ local sealDeclarations = _record_declarations.sealDeclarations
43
+ local _session = TS.import(script, script.Parent, "session")
44
+ local assertOutsideCallback = _session.assertOutsideCallback
45
+ local insideDataCallback = _session.insideDataCallback
46
+ local shutdownData = TS.import(script, script.Parent, "shutdown").shutdownData
47
+ local Waiters = TS.import(script, script.Parent, "waiters").Waiters
48
+ --* Rule 127's three measures.
49
+ --* Server state for every profile store in this realm: one engine, one activation, one claim index (rules 6, 104).
50
+ local server = {
51
+ engine = nil,
52
+ owner = nil,
53
+ activated = false,
54
+ activation = nil,
55
+ disconnect = {},
56
+ receivers = {},
57
+ grants = {},
58
+ stores = 0,
59
+ }
60
+ local bindings = {}
61
+ local function port()
62
+ local _result = server.engine
63
+ if _result ~= nil then
64
+ _result = _result.port
65
+ end
66
+ local _condition = _result
67
+ if _condition == nil then
68
+ _condition = dataPort()
69
+ end
70
+ return _condition
71
+ end
72
+ local function varuintSize(value)
73
+ local size = 1
74
+ while value >= 128 do
75
+ value = math.floor(value / 128)
76
+ size += 1
77
+ end
78
+ return size
79
+ end
80
+ --* A player's slot: one state holding the admitted value while writable, else undefined (rule 9).
81
+ local Slot
82
+ do
83
+ Slot = setmetatable({}, {
84
+ __tostring = function()
85
+ return "Slot"
86
+ end,
87
+ })
88
+ Slot.__index = Slot
89
+ function Slot.new(...)
90
+ local self = setmetatable({}, Slot)
91
+ return self:constructor(...) or self
92
+ end
93
+ function Slot:constructor(binding, player)
94
+ self.binding = binding
95
+ self.player = player
96
+ self.node = createStateNode(nil)
97
+ self.live = createComputedNode(function()
98
+ return readState(self.node) ~= nil
99
+ end)
100
+ self.client = false
101
+ self.name = binding.declaration.name
102
+ self.root = rootHandle(self, binding.declaration.persist)
103
+ end
104
+ function Slot:read(path)
105
+ local _condition = readState(self.node)
106
+ if _condition == nil then
107
+ _condition = self.binding.declaration.schema.defaults
108
+ end
109
+ return getPath(_condition, path)
110
+ end
111
+ function Slot:beforeRead()
112
+ self.binding:beforeRead(self.player)
113
+ end
114
+ function Slot:write(change)
115
+ return self.binding:write(self.player, change)
116
+ end
117
+ end
118
+ --* Rule 115: a player who is not present reads the template and writes nothing, and gets no slot.
119
+ local function inertSource(binding)
120
+ return {
121
+ name = binding.declaration.name,
122
+ client = false,
123
+ read = function(self, path)
124
+ return getPath(binding.declaration.schema.defaults, path)
125
+ end,
126
+ beforeRead = function(self) end,
127
+ write = function(self)
128
+ binding:guard(nil)
129
+ return false
130
+ end,
131
+ }
132
+ end
133
+ --* One profile store's server side: slots, the gate, claims and the owner's views (rules 6, 9 to 12, 115 to 127).
134
+ local ProfileBinding
135
+ do
136
+ ProfileBinding = setmetatable({}, {
137
+ __tostring = function()
138
+ return "ProfileBinding"
139
+ end,
140
+ })
141
+ ProfileBinding.__index = ProfileBinding
142
+ function ProfileBinding.new(...)
143
+ local self = setmetatable({}, ProfileBinding)
144
+ return self:constructor(...) or self
145
+ end
146
+ function ProfileBinding:constructor(declaration)
147
+ self.declaration = declaration
148
+ self.slots = {}
149
+ self.playersNode = createStateNode(table.freeze({}))
150
+ self.claims = {}
151
+ self.claiming = {}
152
+ self.costs = {}
153
+ for i = 0, #declaration.fields - 1 do
154
+ local _costs = self.costs
155
+ local _arg0 = setmetatable({}, {
156
+ __mode = "k",
157
+ })
158
+ table.insert(_costs, _arg0)
159
+ end
160
+ end
161
+ function ProfileBinding:slot(player)
162
+ local _slots = self.slots
163
+ local _player = player
164
+ local slot = _slots[_player]
165
+ if slot == nil and port().players.present(player) then
166
+ slot = Slot.new(self, player)
167
+ local _slots_1 = self.slots
168
+ local _player_1 = player
169
+ local _slot = slot
170
+ _slots_1[_player_1] = _slot
171
+ end
172
+ return slot
173
+ end
174
+ function ProfileBinding:root(player)
175
+ local _result = self:slot(player)
176
+ if _result ~= nil then
177
+ _result = _result.root
178
+ end
179
+ local _condition = _result
180
+ if _condition == nil then
181
+ _condition = rootHandle(inertSource(self), self.declaration.persist)
182
+ end
183
+ return _condition
184
+ end
185
+ function ProfileBinding:guard(session)
186
+ local _result = session
187
+ if _result ~= nil then
188
+ _result:assertIdle()
189
+ end
190
+ assertOutsideCallback()
191
+ if insideDataCallback() then
192
+ error("a write inside a data callback; return the data instead", 0)
193
+ end
194
+ guardPure("write state")
195
+ end
196
+ function ProfileBinding:write(player, change)
197
+ local runtime = self.runtime
198
+ local _self = self
199
+ local _result = runtime
200
+ if _result ~= nil then
201
+ local _sessions = _result.sessions
202
+ local _player = player
203
+ _result = _sessions[_player]
204
+ end
205
+ _self:guard(_result)
206
+ if runtime == nil then
207
+ return false
208
+ end
209
+ return runtime:write(player, change)
210
+ end
211
+ function ProfileBinding:beforeRead(player)
212
+ local _claiming = self.claiming
213
+ local _player = player
214
+ local claim = _claiming[_player]
215
+ if claim ~= nil then
216
+ error(`{self.declaration.name}: {player.UserId}'s handles are read inside its {claim} claim; read the claim's data argument`, 3)
217
+ end
218
+ end
219
+ function ProfileBinding:publish(player, value)
220
+ local _result
221
+ if value == nil then
222
+ local _slots = self.slots
223
+ local _player = player
224
+ _result = _slots[_player]
225
+ else
226
+ _result = self:slot(player)
227
+ end
228
+ local slot = _result
229
+ if slot == nil then
230
+ return nil
231
+ end
232
+ local before = slot.node.value ~= nil
233
+ write(slot.node, value)
234
+ if before ~= (value ~= nil) then
235
+ self:refreshPlayers()
236
+ end
237
+ end
238
+ function ProfileBinding:refreshPlayers()
239
+ local _exp = self.playersNode
240
+ local _array = {}
241
+ local _length = #_array
242
+ local _result = self.runtime
243
+ if _result ~= nil then
244
+ _result = _result:players()
245
+ end
246
+ local _condition = _result
247
+ if _condition == nil then
248
+ _condition = {}
249
+ end
250
+ table.move(_condition, 1, #_condition, _length + 1, _array)
251
+ write(_exp, table.freeze(_array))
252
+ end
253
+ function ProfileBinding:drop(player)
254
+ local _slots = self.slots
255
+ local _player = player
256
+ local slot = _slots[_player]
257
+ if slot == nil then
258
+ return nil
259
+ end
260
+ write(slot.node, nil)
261
+ local _slots_1 = self.slots
262
+ local _player_1 = player
263
+ _slots_1[_player_1] = nil
264
+ self:refreshPlayers()
265
+ end
266
+ function ProfileBinding:loaded(player)
267
+ local slot = self:slot(player)
268
+ if slot == nil or readComputed(slot.live) ~= true then
269
+ return false
270
+ end
271
+ local _result = self.runtime
272
+ if _result ~= nil then
273
+ _result = _result:writable(player)
274
+ end
275
+ return _result == true
276
+ end
277
+ function ProfileBinding:checkViews(value, committed)
278
+ local declaration = self.declaration
279
+ local record = value
280
+ local before = committed
281
+ local live = declaration.liveDeclaration.name
282
+ local bytes = varuintSize(#live) + #live + costOf(true).bytes
283
+ local entries = 1 + costOf(true).entries
284
+ local visits = 2 + costOf(true).visits
285
+ local _exp = declaration.fields
286
+ -- ▼ ReadonlyArray.forEach ▼
287
+ local _callback = function(field, index)
288
+ local current = record[field.name]
289
+ local cache = self.costs[index + 1]
290
+ local cost = if type(current) == "table" and before ~= nil then cache[current] else nil
291
+ local _condition = cost == nil
292
+ if not _condition then
293
+ local _result = before
294
+ if _result ~= nil then
295
+ _result = _result[field.name]
296
+ end
297
+ _condition = not rawequal(current, _result)
298
+ end
299
+ if _condition then
300
+ local admitted = admitReplicated(field.declaration.plan, `{declaration.name}.{field.name}`, current)
301
+ cost = costOf(admitted)
302
+ if type(current) == "table" then
303
+ local _cost = cost
304
+ cache[current] = _cost
305
+ end
306
+ end
307
+ local key = field.declaration.name
308
+ bytes += varuintSize(#key) + #key + cost.bytes
309
+ entries += 1 + cost.entries
310
+ visits += 2 + cost.visits
311
+ end
312
+ for _k, _v in _exp do
313
+ _callback(_v, _k - 1, _exp)
314
+ end
315
+ -- ▲ ReadonlyArray.forEach ▲
316
+ local share = self.share
317
+ if share == nil then
318
+ return nil
319
+ end
320
+ local over = function(measure, cost, limit)
321
+ return error(`{declaration.name}: the owner's views would cost {cost} {measure}, over this store's share of {limit} ({server.stores} profile stores)`, 0)
322
+ end
323
+ if bytes > share.bytes then
324
+ over("bytes", bytes, share.bytes)
325
+ end
326
+ if entries > share.entries then
327
+ over("entries", entries, share.entries)
328
+ end
329
+ if visits > share.visits then
330
+ over("visits", visits, share.visits)
331
+ end
332
+ end
333
+ function ProfileBinding:onLoad(session, value, reason)
334
+ local name = self.declaration.name
335
+ local fits, why = pcall(function()
336
+ return self:checkViews(value, nil)
337
+ end)
338
+ if not fits then
339
+ server.engine:diagnose(`views:{name}:{session.token}`, `{name}: {session.userId}'s profile does not fit the owner's views: {tostring(why)}`)
340
+ error(why, 0)
341
+ end
342
+ self:runClaims(session, "onLoad", function(claim, data)
343
+ return claim.callback(session.player, data, reason)
344
+ end, true, reason)
345
+ return session.value
346
+ end
347
+ function ProfileBinding:onRelease(session, _value, reason)
348
+ self:runClaims(session, "onRelease", function(claim, data)
349
+ return claim.callback(session.player, data, reason)
350
+ end, false, reason)
351
+ return session.value
352
+ end
353
+ function ProfileBinding:runClaims(session, kind, call, fatal, reason)
354
+ local name = self.declaration.name
355
+ local _claiming = self.claiming
356
+ local _player = session.player
357
+ local _kind = kind
358
+ _claiming[_player] = _kind
359
+ for _, claim in self.claims do
360
+ if claim.kind ~= kind or not claim.live then
361
+ continue
362
+ end
363
+ local current = session.value
364
+ local ok, err = pcall(function()
365
+ local result = session:transact(function()
366
+ return session:callback(function(data)
367
+ return call(claim, data)
368
+ end, current, 0)
369
+ end)
370
+ if not rawequal(result, current) then
371
+ session:commit(session:prepare(result))
372
+ end
373
+ end)
374
+ if ok then
375
+ continue
376
+ end
377
+ server.engine:diagnose(`{kind}:{name}:{session.token}:{claim.owner}`, if fatal then `{name}: {session.userId}'s onLoad claim of {claim.owner} failed ({reason}); the load failed` else `{name}: {session.userId}'s onRelease claim of {claim.owner} was skipped ({reason})`)
378
+ if fatal then
379
+ local _claiming_1 = self.claiming
380
+ local _player_1 = session.player
381
+ _claiming_1[_player_1] = nil
382
+ error(err, 0)
383
+ end
384
+ end
385
+ local _claiming_1 = self.claiming
386
+ local _player_1 = session.player
387
+ _claiming_1[_player_1] = nil
388
+ end
389
+ function ProfileBinding:claim(kind, callback, extra)
390
+ local name = self.declaration.name
391
+ local owner = readClaim()
392
+ if owner == nil or owner.record.kind ~= 0 then
393
+ error(`{name}.{kind} runs while a server Subsystem is constructed; make it a field initializer`, 3)
394
+ end
395
+ if server.activated then
396
+ error(`{name}.{kind}: claims are frozen once the data runtime has started`, 3)
397
+ end
398
+ local _callback = callback
399
+ if not (type(_callback) == "function") then
400
+ error(`{name}.{kind} takes a function`, 3)
401
+ end
402
+ local label = (owner.record).id
403
+ local _object = {
404
+ kind = kind,
405
+ owner = label,
406
+ callback = callback,
407
+ }
408
+ local _left = "message"
409
+ local _result = extra
410
+ if _result ~= nil then
411
+ _result = _result.message
412
+ end
413
+ _object[_left] = _result
414
+ local _left_1 = "productId"
415
+ local _result_1 = extra
416
+ if _result_1 ~= nil then
417
+ _result_1 = _result_1.productId
418
+ end
419
+ _object[_left_1] = _result_1
420
+ _object.live = true
421
+ local claim = _object
422
+ if claim.message ~= nil then
423
+ local _receivers = server.receivers
424
+ local _name = claim.message.name
425
+ local other = _receivers[_name]
426
+ if other ~= nil then
427
+ error(`{claim.message.name} is received by {other} and {label}; one Subsystem receives each message`, 3)
428
+ end
429
+ local _receivers_1 = server.receivers
430
+ local _name_1 = claim.message.name
431
+ _receivers_1[_name_1] = label
432
+ end
433
+ if claim.productId ~= nil then
434
+ local _grants = server.grants
435
+ local _productId = claim.productId
436
+ local other = _grants[_productId]
437
+ if other ~= nil then
438
+ error(`product {claim.productId} is granted by {other} and {label}; one grant handles each product`, 3)
439
+ end
440
+ local _grants_1 = server.grants
441
+ local _productId_1 = claim.productId
442
+ _grants_1[_productId_1] = label
443
+ end
444
+ local _exp = self.claims
445
+ table.insert(_exp, claim)
446
+ if owner.subscriptions == nil then
447
+ owner.subscriptions = {}
448
+ end
449
+ local _exp_1 = (owner.subscriptions)
450
+ table.insert(_exp_1, function()
451
+ return self:retireClaim(claim)
452
+ end)
453
+ return table.freeze({})
454
+ end
455
+ function ProfileBinding:retireClaim(claim)
456
+ if not claim.live then
457
+ return nil
458
+ end
459
+ claim.live = false
460
+ local engine = server.engine
461
+ if claim.message ~= nil then
462
+ local _receivers = server.receivers
463
+ local _name = claim.message.name
464
+ _receivers[_name] = nil
465
+ local _result = self.runtime
466
+ if _result ~= nil then
467
+ _result = _result.inbox:unreceive(claim.message.name)
468
+ end
469
+ end
470
+ if claim.productId ~= nil then
471
+ local _grants = server.grants
472
+ local _productId = claim.productId
473
+ _grants[_productId] = nil
474
+ local _result = engine
475
+ if _result ~= nil then
476
+ _result = _result.receipts:unroute(claim.productId)
477
+ end
478
+ end
479
+ end
480
+ function ProfileBinding:route(engine)
481
+ local runtime = self.runtime
482
+ local granted = false
483
+ for _, claim in self.claims do
484
+ if not claim.live then
485
+ continue
486
+ end
487
+ if claim.kind == "receive" then
488
+ runtime.inbox:receive(claim.message, function(player, data, payload)
489
+ return claim.callback(player, data, payload)
490
+ end)
491
+ elseif claim.kind == "grant" then
492
+ granted = true
493
+ engine.receipts:route(claim.productId, {
494
+ runtime = runtime,
495
+ grant = function(player, data, purchase, afterGrant)
496
+ return claim.callback(player, data, purchase, afterGrant)
497
+ end,
498
+ })
499
+ end
500
+ end
501
+ return granted
502
+ end
503
+ end
504
+ local function bindingOf(declaration)
505
+ local _declaration = declaration
506
+ local binding = bindings[_declaration]
507
+ if binding == nil then
508
+ binding = ProfileBinding.new(declaration)
509
+ local _declaration_1 = declaration
510
+ local _binding = binding
511
+ bindings[_declaration_1] = _binding
512
+ end
513
+ return binding
514
+ end
515
+ --* Rule 4: a yielding call before activation waits for it within its own deadline.
516
+ local function activated(deadline)
517
+ while not server.activated and bootClock() < deadline do
518
+ if server.activation == nil then
519
+ server.activation = Waiters.new(port())
520
+ end
521
+ server.activation:park(deadline)
522
+ end
523
+ return server.activated
524
+ end
525
+ local function saveResult(result)
526
+ return if result == "ok" then {
527
+ ok = true,
528
+ } else {
529
+ ok = false,
530
+ reason = result,
531
+ }
532
+ end
533
+ local function storeResult(result)
534
+ return if result.kind == "ok" then {
535
+ ok = true,
536
+ value = result.value,
537
+ } else {
538
+ ok = false,
539
+ reason = result.kind,
540
+ }
541
+ end
542
+ local function player(name, member, value)
543
+ if value == nil then
544
+ error(`{name}.{member} needs its player on the server`, 3)
545
+ end
546
+ return value
547
+ end
548
+ --* Rule 13: send and clearQuarantined are data writes too.
549
+ local function outsideCallbacks()
550
+ assertOutsideCallback()
551
+ if insideDataCallback() then
552
+ error("a write inside a data callback; return the data instead", 0)
553
+ end
554
+ end
555
+ local function member(declaration, key)
556
+ local binding = bindingOf(declaration)
557
+ local name = declaration.name
558
+ repeat
559
+ local _fallthrough = false
560
+ if key == "loaded" then
561
+ return function(_, who)
562
+ return binding:loaded(player(name, "loaded", who))
563
+ end
564
+ end
565
+ if key == "players" then
566
+ return function()
567
+ return readState(binding.playersNode)
568
+ end
569
+ end
570
+ if key == "await" then
571
+ return function(_, who)
572
+ local target = player(name, "await", who)
573
+ if not activated(bootClock() + LOAD_DEADLINE_SECONDS) then
574
+ return false
575
+ end
576
+ return binding.runtime:await(target)
577
+ end
578
+ end
579
+ if key == "save" then
580
+ return function(_, who, timeoutSeconds)
581
+ local target = player(name, "save", who)
582
+ local _condition = timeoutSeconds
583
+ if _condition == nil then
584
+ _condition = SAVE_TIMEOUT_DEFAULT_SECONDS
585
+ end
586
+ local timeout = math.clamp(_condition, 0, SAVE_TIMEOUT_MAX_SECONDS)
587
+ outsideCallbacks()
588
+ if not activated(bootClock() + timeout) then
589
+ return saveResult("unavailable")
590
+ end
591
+ return saveResult(binding.runtime:save(target, timeout))
592
+ end
593
+ end
594
+ if key == "handoff" then
595
+ return function(_, who)
596
+ local target = player(name, "handoff", who)
597
+ local _result = binding.runtime
598
+ if _result ~= nil then
599
+ _result = _result.sessions[target]
600
+ end
601
+ binding:guard(_result)
602
+ if binding.runtime == nil then
603
+ return saveResult("unavailable")
604
+ end
605
+ return saveResult(binding.runtime:handoff(target))
606
+ end
607
+ end
608
+ if key == "resume" then
609
+ return function(_, who)
610
+ local target = player(name, "resume", who)
611
+ local _result = binding.runtime
612
+ if _result ~= nil then
613
+ _result = _result.sessions[target]
614
+ end
615
+ binding:guard(_result)
616
+ if not activated(bootClock() + LOAD_DEADLINE_SECONDS) then
617
+ return false
618
+ end
619
+ return binding.runtime:resume(target)
620
+ end
621
+ end
622
+ if key == "inspect" then
623
+ return function(_, userId)
624
+ if not activated(bootClock() + CALL_DEADLINE_SECONDS) then
625
+ return storeResult({
626
+ kind = "timeout",
627
+ })
628
+ end
629
+ return storeResult(binding.runtime:inspect(userId))
630
+ end
631
+ end
632
+ if key == "send" then
633
+ return function(_, userId, message, payload, id)
634
+ local decl = messageOf(message) or error(`{name}.send takes a profile message`, 2)
635
+ outsideCallbacks()
636
+ if not activated(bootClock() + CALL_DEADLINE_SECONDS) then
637
+ return storeResult({
638
+ kind = "timeout",
639
+ })
640
+ end
641
+ return storeResult(binding.runtime.inbox:send(userId, decl, payload, id))
642
+ end
643
+ end
644
+ if key == "quarantined" then
645
+ return function(_, userId)
646
+ if not activated(bootClock() + CALL_DEADLINE_SECONDS) then
647
+ return storeResult({
648
+ kind = "timeout",
649
+ })
650
+ end
651
+ return storeResult(binding.runtime.inbox:quarantined(userId))
652
+ end
653
+ end
654
+ if key == "clearQuarantined" then
655
+ return function(_, userId, ids, action)
656
+ outsideCallbacks()
657
+ if not activated(bootClock() + CALL_DEADLINE_SECONDS) then
658
+ return storeResult({
659
+ kind = "timeout",
660
+ })
661
+ end
662
+ return storeResult(binding.runtime.inbox:clearQuarantined(userId, ids, action))
663
+ end
664
+ end
665
+ if key == "onLoad" then
666
+ _fallthrough = true
667
+ end
668
+ if _fallthrough or key == "onRelease" then
669
+ return function(_, callback)
670
+ return binding:claim(key, callback)
671
+ end
672
+ end
673
+ if key == "receive" then
674
+ return function(_, message, callback)
675
+ return binding:claim("receive", callback, {
676
+ message = messageOf(message) or error(`{name}.receive takes a profile message`, 2),
677
+ })
678
+ end
679
+ end
680
+ if key == "grant" then
681
+ return function(_, productId, callback)
682
+ local _productId = productId
683
+ if not (type(_productId) == "number") then
684
+ error(`{name}.grant takes a product id`, 2)
685
+ end
686
+ return binding:claim("grant", callback, {
687
+ productId = productId,
688
+ })
689
+ end
690
+ end
691
+ until true
692
+ error(`{name} has no member {key}`, 2)
693
+ end
694
+ installServerSide({
695
+ root = function(declaration, who)
696
+ return bindingOf(declaration):root(who)
697
+ end,
698
+ member = member,
699
+ })
700
+ local function ended(object)
701
+ local s = stateOf(object)
702
+ return s == nil or not isLive(s)
703
+ end
704
+ --* Rule 107, and a failed boot's rollback, which unseals the registry (rule 3).
705
+ local function retire()
706
+ local engine = server.engine
707
+ for _, disconnect in server.disconnect do
708
+ disconnect()
709
+ end
710
+ table.clear(server.disconnect)
711
+ if not server.activated then
712
+ sealDeclarations(false)
713
+ server.engine = nil
714
+ server.owner = nil
715
+ for _, binding in bindings do
716
+ binding.runtime = nil
717
+ end
718
+ return nil
719
+ end
720
+ unbindRecords()
721
+ local _result = engine
722
+ if _result ~= nil then
723
+ _result:retire()
724
+ end
725
+ end
726
+ --[[
727
+ *
728
+ * Rules 3, 104, 125 and 127 at construction: the registry seals, each store gets its engine, the shares are divided
729
+ * and every template must fit its share, and the views are published under this construction claim.
730
+
731
+ ]]
732
+ local function construct()
733
+ local state = readClaim()
734
+ local declarations = profileDeclarations()
735
+ sealDeclarations(true)
736
+ if state.subscriptions == nil then
737
+ state.subscriptions = {}
738
+ end
739
+ local _exp = (state.subscriptions)
740
+ table.insert(_exp, retire)
741
+ local _condition = #declarations == 0
742
+ if _condition then
743
+ -- ▼ ReadonlyMap.size ▼
744
+ local _size = 0
745
+ for _ in declaredRecords() do
746
+ _size += 1
747
+ end
748
+ -- ▲ ReadonlyMap.size ▲
749
+ _condition = _size == 0
750
+ end
751
+ if _condition then
752
+ return nil
753
+ end
754
+ local engine = DataEngine.new(dataPort())
755
+ server.engine = engine
756
+ server.owner = state
757
+ server.stores = #declarations
758
+ local count = math.max(#declarations, 1)
759
+ local share = {
760
+ bytes = math.floor(OWNER_VIEWS_MAX_BYTES / count),
761
+ entries = math.floor(OWNER_VIEWS_MAX_ENTRIES / count),
762
+ visits = math.floor(OWNER_VIEWS_MAX_VISITS / count),
763
+ }
764
+ for _, declaration in declarations do
765
+ local binding = bindingOf(declaration)
766
+ binding.share = share
767
+ binding:checkViews(declaration.schema.defaults, nil)
768
+ local _object = table.clone(declaration.schema)
769
+ setmetatable(_object, nil)
770
+ _object.check = function(value, committed)
771
+ return binding:checkViews(value, committed)
772
+ end
773
+ binding.runtime = engine:profile(_object, {
774
+ onLoad = function(session, value, reason)
775
+ return binding:onLoad(session, value, reason)
776
+ end,
777
+ onRelease = function(session, value, reason)
778
+ return binding:onRelease(session, value, reason)
779
+ end,
780
+ publish = function(who, value)
781
+ return binding:publish(who, value)
782
+ end,
783
+ delivered = function() end,
784
+ })
785
+ claimPublication(declaration.live, function(who)
786
+ local slot = binding:slot(who)
787
+ return slot ~= nil and readComputed(slot.live) == true
788
+ end)
789
+ for _1, field in declaration.fields do
790
+ claimPublication(field.handle, function(who)
791
+ local slot = binding:slot(who)
792
+ local value = if slot == nil then nil else readState(slot.node)
793
+ return if value == nil then nil else value[field.name]
794
+ end)
795
+ end
796
+ end
797
+ end
798
+ --* Rule 104: after one wait, a live generation means boot committed.
799
+ local function activate(owner)
800
+ local engine = server.engine
801
+ if engine == nil then
802
+ return nil
803
+ end
804
+ engine.port.wait(0)
805
+ if ended(owner) then
806
+ return nil
807
+ end
808
+ server.activated = true
809
+ bindRecords(engine)
810
+ local granted = false
811
+ for _, binding in bindings do
812
+ if binding.runtime ~= nil and binding:route(engine) then
813
+ granted = true
814
+ end
815
+ end
816
+ if granted then
817
+ engine.receipts:install(engine.port.marketplace())
818
+ end
819
+ local players = engine.port.players
820
+ local live = {}
821
+ for _, binding in bindings do
822
+ if binding.runtime ~= nil then
823
+ table.insert(live, binding)
824
+ end
825
+ end
826
+ if #live > 0 then
827
+ local join = function(who)
828
+ for _, binding in live do
829
+ engine.port.spawn(function()
830
+ return binding.runtime:load(who, "join")
831
+ end)
832
+ end
833
+ end
834
+ local leave = function(who)
835
+ for _, binding in live do
836
+ local runtime = binding.runtime
837
+ engine.port.spawn(function()
838
+ runtime:leave(who)
839
+ while true do
840
+ local _sessions = runtime.sessions
841
+ local _who = who
842
+ if not (_sessions[_who] ~= nil) then
843
+ break
844
+ end
845
+ runtime.changed:park()
846
+ end
847
+ binding:drop(who)
848
+ end)
849
+ end
850
+ end
851
+ local _disconnect = server.disconnect
852
+ local _arg0 = players.onAdded(join)
853
+ local _arg1 = players.onRemoving(leave)
854
+ -- ▼ Array.push ▼
855
+ table.insert(_disconnect, _arg0)
856
+ table.insert(_disconnect, _arg1)
857
+ -- ▲ Array.push ▲
858
+ for _, binding in live do
859
+ for who in binding.slots do
860
+ if not players.present(who) then
861
+ binding:drop(who)
862
+ end
863
+ end
864
+ end
865
+ for _, who in players.all() do
866
+ join(who)
867
+ end
868
+ end
869
+ local _result = server.activation
870
+ if _result ~= nil then
871
+ _result:wake()
872
+ end
873
+ end
874
+ --* @internal The framework's data Subsystem (rules 50 to 56, 104, 107). No injection identity; nothing depends on it.
875
+ local DataRuntime
876
+ do
877
+ local super = Subsystem
878
+ DataRuntime = setmetatable({}, {
879
+ __tostring = function()
880
+ return "DataRuntime"
881
+ end,
882
+ __index = super,
883
+ })
884
+ DataRuntime.__index = DataRuntime
885
+ function DataRuntime.new(...)
886
+ local self = setmetatable({}, DataRuntime)
887
+ return self:constructor(...) or self
888
+ end
889
+ function DataRuntime:constructor()
890
+ super.constructor(self)
891
+ construct()
892
+ end
893
+ function DataRuntime:start()
894
+ activate(self)
895
+ end
896
+ function DataRuntime:onShutdown()
897
+ if server.engine ~= nil then
898
+ shutdownData(server.engine, self)
899
+ end
900
+ end
901
+ end
902
+ local installed = false
903
+ --* @internal The server entry defines DataRuntime once per realm, after ServerSync (rule 104).
904
+ local function installDataRuntime()
905
+ if installed then
906
+ return nil
907
+ end
908
+ installed = true
909
+ define(DataRuntime, {
910
+ kind = "subsystem",
911
+ id = "Crucible.DataRuntime",
912
+ deps = {},
913
+ provides = {},
914
+ })
915
+ end
916
+ return {
917
+ installDataRuntime = installDataRuntime,
918
+ }