@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,207 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local runtimeDiagnostic = TS.import(script, script.Parent, "diagnostics").runtimeDiagnostic
4
+ local DenseList = TS.import(script, script.Parent, "dense-list").DenseList
5
+ local within = TS.import(script, script.Parent, "lifecycle").within
6
+ --* @internal
7
+ --* @internal
8
+ --* @internal
9
+ --* @internal
10
+ local MAX_FIXED_STEPS = 4
11
+ --* @internal
12
+ local MAX_FRAME_SECONDS = 0.25
13
+ --* @internal
14
+ local function accumulate(carry, frameDt, step)
15
+ local total = carry + math.clamp(frameDt, 0, MAX_FRAME_SECONDS)
16
+ local whole = math.floor(total / step)
17
+ return math.min(whole, MAX_FIXED_STEPS), math.max(total - whole * step, 0)
18
+ end
19
+ local Lane
20
+ do
21
+ Lane = setmetatable({}, {
22
+ __tostring = function()
23
+ return "Lane"
24
+ end,
25
+ })
26
+ Lane.__index = Lane
27
+ function Lane.new(...)
28
+ local self = setmetatable({}, Lane)
29
+ return self:constructor(...) or self
30
+ end
31
+ function Lane:constructor(hook, frame)
32
+ self.members = DenseList.new()
33
+ self.dt = 0
34
+ local caller = function(s, dt)
35
+ return frame[hook](s.object, dt)
36
+ end
37
+ self.visit = function(s)
38
+ return within(s, s, 6, caller, s, self.dt)
39
+ end
40
+ end
41
+ function Lane:run(dt)
42
+ self.dt = dt
43
+ self.members:forEach(self.visit)
44
+ end
45
+ end
46
+ --* @internal
47
+ local Scheduler
48
+ do
49
+ Scheduler = setmetatable({}, {
50
+ __tostring = function()
51
+ return "Scheduler"
52
+ end,
53
+ })
54
+ Scheduler.__index = Scheduler
55
+ function Scheduler.new(...)
56
+ local self = setmetatable({}, Scheduler)
57
+ return self:constructor(...) or self
58
+ end
59
+ function Scheduler:constructor(port, config, frame)
60
+ self.port = port
61
+ self.config = config
62
+ self.frame = frame
63
+ self.renderLanes = {}
64
+ self.refusedClasses = {}
65
+ self.carry = 0
66
+ self.step = 1 / config.fixedRate
67
+ self.lanes = {
68
+ [0] = Lane.new(0, frame),
69
+ [1] = Lane.new(1, frame),
70
+ [2] = Lane.new(2, frame),
71
+ [3] = Lane.new(3, frame),
72
+ }
73
+ end
74
+ function Scheduler:add(s)
75
+ local record = s.record
76
+ local mask = record.hooks
77
+ if not self.config.renders and ((bit32.band(mask, (bit32.lshift(1, 0)))) ~= 0 or record.renderPriority ~= nil) then
78
+ local _refusedClasses = self.refusedClasses
79
+ local _id = record.id
80
+ if not (_refusedClasses[_id] ~= nil) then
81
+ local _refusedClasses_1 = self.refusedClasses
82
+ local _id_1 = record.id
83
+ _refusedClasses_1[_id_1] = true
84
+ runtimeDiagnostic("error", `Crucible server refuses preRender and renderPriority for {record.id}`)
85
+ end
86
+ return nil
87
+ end
88
+ if (bit32.band(mask, (bit32.lshift(1, 0)))) ~= 0 then
89
+ self:join(self:renderLaneOf(s), 0, s)
90
+ end
91
+ for _, hook in { 1, 2, 3 } do
92
+ if (bit32.band(mask, (bit32.lshift(1, hook)))) ~= 0 then
93
+ self:join(self.lanes[hook], hook, s)
94
+ end
95
+ end
96
+ end
97
+ function Scheduler:remove(s)
98
+ local priority = (s.record).renderPriority
99
+ local render = if priority == nil then self.lanes[0] else self.renderLanes[priority]
100
+ if render ~= nil and render.members:remove(s) and render.members:size() == 0 then
101
+ local _result = render.disconnect
102
+ if _result ~= nil then
103
+ _result()
104
+ end
105
+ render.disconnect = nil
106
+ end
107
+ local fixed = self.lanes[1]
108
+ if fixed.members:remove(s) and fixed.members:size() == 0 then
109
+ local _result = fixed.disconnect
110
+ if _result ~= nil then
111
+ _result()
112
+ end
113
+ fixed.disconnect = nil
114
+ self.carry = 0
115
+ end
116
+ local update = self.lanes[2]
117
+ local late = self.lanes[3]
118
+ local changed = update.members:remove(s)
119
+ if (late.members:remove(s) or changed) and update.members:size() == 0 and late.members:size() == 0 then
120
+ local _result = self.postSimulation
121
+ if _result ~= nil then
122
+ _result()
123
+ end
124
+ self.postSimulation = nil
125
+ end
126
+ end
127
+ function Scheduler:renderLaneOf(s)
128
+ local priority = (s.record).renderPriority
129
+ if priority == nil then
130
+ return self.lanes[0]
131
+ end
132
+ local lane = self.renderLanes[priority]
133
+ if lane == nil then
134
+ lane = Lane.new(0, self.frame)
135
+ local _renderLanes = self.renderLanes
136
+ local _lane = lane
137
+ _renderLanes[priority] = _lane
138
+ end
139
+ return lane
140
+ end
141
+ function Scheduler:join(lane, hook, s)
142
+ if not lane.members:add(s) then
143
+ return nil
144
+ end
145
+ local port = self.port
146
+ repeat
147
+ local _fallthrough = false
148
+ if hook == 0 then
149
+ if lane.disconnect ~= nil or not self.config.renders then
150
+ return nil
151
+ end
152
+ local priority = (s.record).renderPriority
153
+ local run = function(dt)
154
+ return lane:run(dt)
155
+ end
156
+ lane.disconnect = if priority == nil then port.onFrame(0, run) else port.bindRenderStep(`crucible:{priority}`, priority, run)
157
+ return nil
158
+ end
159
+ if hook == 1 then
160
+ if lane.disconnect ~= nil then
161
+ return nil
162
+ end
163
+ lane.disconnect = port.onFrame(1, function(dt)
164
+ local steps, carry = accumulate(self.carry, dt, self.step)
165
+ self.carry = carry
166
+ do
167
+ local i = 0
168
+ local _shouldIncrement = false
169
+ while true do
170
+ if _shouldIncrement then
171
+ i += 1
172
+ else
173
+ _shouldIncrement = true
174
+ end
175
+ if not (i < steps) then
176
+ break
177
+ end
178
+ lane:run(self.step)
179
+ end
180
+ end
181
+ end)
182
+ return nil
183
+ end
184
+ if hook == 2 then
185
+ _fallthrough = true
186
+ end
187
+ if _fallthrough or hook == 3 then
188
+ if self.postSimulation ~= nil then
189
+ return nil
190
+ end
191
+ local update = self.lanes[2]
192
+ local late = self.lanes[3]
193
+ self.postSimulation = port.onFrame(2, function(dt)
194
+ update:run(dt)
195
+ late:run(dt)
196
+ end)
197
+ return nil
198
+ end
199
+ until true
200
+ end
201
+ end
202
+ return {
203
+ accumulate = accumulate,
204
+ MAX_FIXED_STEPS = MAX_FIXED_STEPS,
205
+ MAX_FRAME_SECONDS = MAX_FRAME_SECONDS,
206
+ Scheduler = Scheduler,
207
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,41 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ --* @internal Public API rule 1: a Subsystem has `awake` and `start` but no `onEnable`, `onDisable` or `onDestroy`.
3
+ --* @internal What lifecycle reads from a class record. The registry's records extend it.
4
+ --* @internal An object the `construct` effect built. Lifecycle never reads it.
5
+ --[[
6
+ *
7
+ * @internal
8
+ * Where one generation of an object is. A phase also records which inverse hooks are owed: from Awake on,
9
+ * one `onDestroy`; in Enabled, one `onDisable`. Each step enters its phase before running its hook, so a
10
+ * hook that throws part way still owes its inverse. Faulted and Destroyed are terminal.
11
+ *
12
+ * Unconstructed --constructor--> Constructed --awake--> Awake --onEnable--> Enabled <--onDisable / onEnable--> Disabled
13
+ * | | | \--(want Disabled, no hook)---------------------------^
14
+ * +--------Drop, no hooks-------+ +--------onDestroy (from Awake or Disabled)--> Destroyed | Faulted
15
+
16
+ ]]
17
+ --* @internal `start` runs once per generation, the first time the object is Enabled with the gate at Start.
18
+ --[[
19
+ *
20
+ * @internal How far the object's batch has opened. A batch raises the gate on all its objects together, so
21
+ * every object is awake before any is enabled and enabled before any starts. After its batch an object sits at Start.
22
+
23
+ ]]
24
+ --* @internal What the outside world asked for. The latest request wins; Destroyed is absorbing.
25
+ --* @internal Where a failure happened. The first one is the object's primary diagnostic.
26
+ --* @internal One atomic move. Each runs at most one hook or changes one index.
27
+ --* @internal Discriminates the two kinds of entry on the work stack.
28
+ --* @internal The first failure, with every later failure during its cleanup attached.
29
+ --* @internal The inputs `nextStep` reads. Lifecycle owns every field; nothing else writes them.
30
+ --* @internal Runtime state of one generation.
31
+ --* @internal
32
+ --* @internal New generations going through construct, awake, enable and start together.
33
+ --[[
34
+ *
35
+ * @internal
36
+ * Everything a step does outside lifecycle state. Boot installs the real ones; specs install doubles.
37
+ * Lifecycle runs `construct` and every hook caller through `runner.call` with the object as owner, so a throw
38
+ * or a yield comes back as a failure, and `start` on its own thread.
39
+
40
+ ]]
41
+ return nil
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,157 @@
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 claim = TS.import(script, script.Parent.Parent, "network", "claims").claim
6
+ local commitPrepared = TS.import(script, script.Parent.Parent, "reactive", "graph").commitPrepared
7
+ local define = TS.import(script, script.Parent.Parent, "registry").define
8
+ local _declarations = TS.import(script, script.Parent, "declarations")
9
+ local declarationNamed = _declarations.declarationNamed
10
+ local declarations = _declarations.declarations
11
+ local SyncFrameMessage = TS.import(script, script.Parent, "frame").SyncFrameMessage
12
+ local reuse = TS.import(script, script.Parent, "values").reuse
13
+ --* @internal How ClientSync reports its last applied revision: exactly `Network.send` on the fault message.
14
+ local sendFault = function() end
15
+ local function ascending(keys)
16
+ for i = 1, #keys - 1 do
17
+ if not (keys[i] < keys[i + 1]) then
18
+ return false
19
+ end
20
+ end
21
+ return true
22
+ end
23
+ local function disjoint(values, failed)
24
+ local i = 0
25
+ local j = 0
26
+ while i < #values and j < #failed do
27
+ if values[i + 1] == failed[j + 1] then
28
+ return false
29
+ end
30
+ if values[i + 1] < failed[j + 1] then
31
+ i += 1
32
+ else
33
+ j += 1
34
+ end
35
+ end
36
+ return true
37
+ end
38
+ local ClientSync
39
+ do
40
+ local super = Subsystem
41
+ ClientSync = setmetatable({}, {
42
+ __tostring = function()
43
+ return "ClientSync"
44
+ end,
45
+ __index = super,
46
+ })
47
+ ClientSync.__index = ClientSync
48
+ function ClientSync.new(...)
49
+ local self = setmetatable({}, ClientSync)
50
+ return self:constructor(...) or self
51
+ end
52
+ function ClientSync:constructor(...)
53
+ super.constructor(self, ...)
54
+ self.revision = 0
55
+ self.snapshotted = false
56
+ self.failed = false
57
+ self.frames = claim(self, "frames", SyncFrameMessage, function(frame)
58
+ return self:apply(frame)
59
+ end)
60
+ end
61
+ function ClientSync:apply(frame)
62
+ if self.failed then
63
+ return nil
64
+ end
65
+ if frame.kind == "failed" then
66
+ return self:latch(`the server stopped this client's sync at revision {frame.revision}`, false)
67
+ end
68
+ local _exp = frame.values
69
+ -- ▼ ReadonlyArray.map ▼
70
+ local _newValue = table.create(#_exp)
71
+ local _callback = function(entry)
72
+ return entry.key
73
+ end
74
+ for _k, _v in _exp do
75
+ _newValue[_k] = _callback(_v, _k - 1, _exp)
76
+ end
77
+ -- ▲ ReadonlyArray.map ▲
78
+ local keys = _newValue
79
+ if if frame.kind == "snapshot" then self.snapshotted or frame.revision ~= 1 else not self.snapshotted then
80
+ return self:latch(`unexpected {frame.kind} at revision {frame.revision} after {self.revision}`, true)
81
+ end
82
+ if frame.kind == "update" and frame.revision ~= self.revision + 1 then
83
+ return self:latch(`revision {frame.revision} does not follow {self.revision}`, true)
84
+ end
85
+ if not ascending(keys) or not ascending(frame.failed) or not disjoint(keys, frame.failed) then
86
+ return self:latch(`revision {frame.revision} lists keys out of order or twice`, true)
87
+ end
88
+ local prepared, writes = pcall(function()
89
+ local out = {}
90
+ for _, entry in frame.values do
91
+ local declaration = declarationNamed(entry.key)
92
+ if declaration == nil then
93
+ continue
94
+ end
95
+ local reused = reuse(declaration.node.value, entry.value)
96
+ if not rawequal(reused, declaration.node.value) then
97
+ local _arg0 = { declaration.node, reused }
98
+ table.insert(out, _arg0)
99
+ end
100
+ local _arg0 = { declaration.status, "ready" }
101
+ table.insert(out, _arg0)
102
+ end
103
+ for _, key in frame.failed do
104
+ local declaration = declarationNamed(key)
105
+ if declaration ~= nil then
106
+ local _arg0 = { declaration.status, "failed" }
107
+ table.insert(out, _arg0)
108
+ end
109
+ end
110
+ return out
111
+ end)
112
+ if not prepared then
113
+ return self:latch(`internal error while preparing revision {frame.revision}: {tostring(writes)}`, true)
114
+ end
115
+ commitPrepared(writes)
116
+ self.snapshotted = true
117
+ self.revision = frame.revision
118
+ end
119
+ function ClientSync:latch(reason, fault)
120
+ self.failed = true
121
+ local _, err = pcall(function()
122
+ local _exp = declarations()
123
+ -- ▼ ReadonlyArray.map ▼
124
+ local _newValue = table.create(#_exp)
125
+ local _callback = function(declaration)
126
+ return { declaration.status, "failed" }
127
+ end
128
+ for _k, _v in _exp do
129
+ _newValue[_k] = _callback(_v, _k - 1, _exp)
130
+ end
131
+ -- ▲ ReadonlyArray.map ▲
132
+ return commitPrepared(_newValue)
133
+ end)
134
+ if fault then
135
+ pcall(sendFault, self.revision)
136
+ end
137
+ runtimeDiagnostic("error", `Crucible sync stopped: {reason}{if err == nil then "" else `; failing statuses: {tostring(err)}`}`)
138
+ end
139
+ end
140
+ local installed = false
141
+ --* @internal Rule 56: the client entry defines ClientSync once per realm, constructed before every game Subsystem.
142
+ local function installClientSync(fault)
143
+ sendFault = fault
144
+ if installed then
145
+ return nil
146
+ end
147
+ installed = true
148
+ define(ClientSync, {
149
+ kind = "subsystem",
150
+ id = "Crucible.ClientSync",
151
+ deps = {},
152
+ provides = {},
153
+ }, true)
154
+ end
155
+ return {
156
+ installClientSync = installClientSync,
157
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,276 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local readClaim = TS.import(script, script.Parent.Parent, "construction-claim").readClaim
4
+ local compile = TS.import(script, script.Parent.Parent, "network", "codec").compile
5
+ local MESSAGE_NAME_MAX_BYTES = TS.import(script, script.Parent.Parent, "network", "limits").MESSAGE_NAME_MAX_BYTES
6
+ local assertDescriptor = TS.import(script, script.Parent.Parent, "network", "schema").assertDescriptor
7
+ local _graph = TS.import(script, script.Parent.Parent, "reactive", "graph")
8
+ local createStateNode = _graph.createStateNode
9
+ local guardPure = _graph.guardPure
10
+ local readState = _graph.readState
11
+ local readUntracked = _graph.readUntracked
12
+ local write = _graph.write
13
+ local MAX_REPLICATED_DECLARATIONS = TS.import(script, script.Parent.Parent, "reactive", "limits").MAX_REPLICATED_DECLARATIONS
14
+ local _values = TS.import(script, script.Parent, "values")
15
+ local admit = _values.admit
16
+ local compilePlan = _values.compilePlan
17
+ --* @internal
18
+ --* @internal
19
+ --* @internal One declaration's server write side (rules 47 to 54).
20
+ --* @internal
21
+ local byName = {}
22
+ local handles = setmetatable({}, {
23
+ __mode = "k",
24
+ })
25
+ local ordered = {}
26
+ local sealed = false
27
+ local onRetired = function() end
28
+ local function onServer()
29
+ return game:GetService("RunService"):IsServer()
30
+ end
31
+ local function perPlayer(declaration)
32
+ error(`{declaration.name} is per player; on the server, read the sources its publisher projects from`, 0)
33
+ end
34
+ local function checkName(name, framework)
35
+ local grammar = `use 1 to {MESSAGE_NAME_MAX_BYTES} bytes of dot-separated [A-Za-z][A-Za-z0-9_]* segments`
36
+ local _name = name
37
+ local _condition = not (type(_name) == "string")
38
+ if not _condition then
39
+ _condition = #name == 0 or #name > MESSAGE_NAME_MAX_BYTES
40
+ end
41
+ if _condition then
42
+ error(`Crucible replicated name {tostring(name)} is invalid: {grammar}`, 0)
43
+ end
44
+ local segments = string.split(name, ".")
45
+ for _, segment in segments do
46
+ if (string.match(segment, "^[A-Za-z][A-Za-z0-9_]*$")) == nil then
47
+ error(`Crucible replicated name {name} is invalid: {grammar}`, 0)
48
+ end
49
+ end
50
+ if (segments[1] == "Crucible") ~= framework then
51
+ error(if framework then `Crucible replicated name {name} is invalid: a framework declaration starts with Crucible.` else `Crucible replicated name {name} is invalid: Crucible. is reserved`, 0)
52
+ end
53
+ end
54
+ local function register(audience, name, initial, descriptor)
55
+ if sealed then
56
+ error(`{name} was declared after the replicated registry sealed; declare replicated state in modules that boot loads`, 0)
57
+ end
58
+ local _name = name
59
+ if byName[_name] ~= nil then
60
+ error(`Crucible replicated name {name} is declared twice`, 0)
61
+ end
62
+ -- ▼ ReadonlyMap.size ▼
63
+ local _size = 0
64
+ for _ in byName do
65
+ _size += 1
66
+ end
67
+ -- ▲ ReadonlyMap.size ▲
68
+ if _size >= MAX_REPLICATED_DECLARATIONS then
69
+ error(`{name} would pass {MAX_REPLICATED_DECLARATIONS} replicated declarations in this realm`, 0)
70
+ end
71
+ assertDescriptor(descriptor)
72
+ compile(descriptor)
73
+ local plan = compilePlan(descriptor)
74
+ local admitted = admit(plan, name, initial)
75
+ local declaration = {
76
+ name = name,
77
+ audience = audience,
78
+ plan = plan,
79
+ initial = admitted,
80
+ node = createStateNode(admitted),
81
+ status = createStateNode("pending"),
82
+ }
83
+ local _name_1 = name
84
+ byName[_name_1] = declaration
85
+ table.insert(ordered, declaration)
86
+ table.sort(ordered, function(a, b)
87
+ return a.name < b.name
88
+ end)
89
+ local handle = if audience == "all" then function()
90
+ return readState(declaration.node)
91
+ end else function()
92
+ return if onServer() then perPlayer(declaration) else readState(declaration.node)
93
+ end
94
+ handles[handle] = declaration
95
+ return handle
96
+ end
97
+ --* @internal The emitted `replicated` and `replicatedPerPlayer` (rules 40, 41, 78).
98
+ local function declareReplicated(audience, name, initial, descriptor)
99
+ checkName(name, false)
100
+ return register(audience, name, initial, descriptor)
101
+ end
102
+ --[[
103
+ *
104
+ * @internal
105
+ * R-D1 (data rule 133): a per-player declaration only the framework makes, under the reserved `Crucible.` segment.
106
+ * It counts against MAX_REPLICATED_DECLARATIONS and the duplicate check like any other.
107
+
108
+ ]]
109
+ local function declareFrameworkReplicated(name, initial, descriptor)
110
+ checkName(name, true)
111
+ return register("player", name, initial, descriptor)
112
+ end
113
+ --* @internal How many more replicated declarations this realm admits (data rule 134).
114
+ local function replicatedCapacity()
115
+ -- ▼ ReadonlyMap.size ▼
116
+ local _size = 0
117
+ for _ in byName do
118
+ _size += 1
119
+ end
120
+ -- ▲ ReadonlyMap.size ▲
121
+ return MAX_REPLICATED_DECLARATIONS - _size
122
+ end
123
+ --* @internal
124
+ local function declarationOf(handle)
125
+ local _handle = handle
126
+ return handles[_handle]
127
+ end
128
+ --* @internal
129
+ local function declarationNamed(name)
130
+ local _name = name
131
+ return byName[_name]
132
+ end
133
+ --* @internal Every declaration in this realm, by name in byte order.
134
+ local function declarations()
135
+ return ordered
136
+ end
137
+ --* @internal `syncStatus` (rule 45): a tracked read of the status node.
138
+ local function statusOf(handle)
139
+ local declaration = declarationOf(handle) or error("syncStatus takes a replicated declaration", 0)
140
+ if declaration.audience == "player" and onServer() then
141
+ perPlayer(declaration)
142
+ end
143
+ return readState(declaration.status)
144
+ end
145
+ --* @internal ServerSync registers how a retirement reaches every peer (rule 52).
146
+ local function useRetired(listener)
147
+ onRetired = listener
148
+ end
149
+ local function retire(declaration, publication)
150
+ if not publication.live then
151
+ return nil
152
+ end
153
+ publication.live = false
154
+ if declaration.audience == "all" then
155
+ write(declaration.status, "failed")
156
+ end
157
+ onRetired(declaration)
158
+ end
159
+ local function ended(declaration, publication)
160
+ error(`{declaration.name}'s publisher {publication.label} has ended`, 0)
161
+ end
162
+ local publishedMethods = {
163
+ set = function(self, value)
164
+ guardPure("write state")
165
+ local _binding = self
166
+ local declaration = _binding.declaration
167
+ local publication = _binding.publication
168
+ if not publication.live then
169
+ ended(declaration, publication)
170
+ end
171
+ write(declaration.node, admit(declaration.plan, declaration.name, value))
172
+ end,
173
+ update = function(self, change)
174
+ guardPure("write state")
175
+ local _binding = self
176
+ local declaration = _binding.declaration
177
+ local publication = _binding.publication
178
+ if not publication.live then
179
+ ended(declaration, publication)
180
+ end
181
+ local changed = readUntracked(function()
182
+ return change(declaration.node.value)
183
+ end, "update's callback")
184
+ if not publication.live then
185
+ ended(declaration, publication)
186
+ end
187
+ write(declaration.node, admit(declaration.plan, declaration.name, changed))
188
+ end,
189
+ }
190
+ local publishedMeta = {
191
+ __index = publishedMethods,
192
+ __call = function(handle)
193
+ return readState(handle.declaration.node)
194
+ end,
195
+ }
196
+ --* @internal `publish`'s runtime half (rules 47 to 50).
197
+ local function claimPublication(target, project)
198
+ local owner = readClaim()
199
+ if owner == nil or owner.record.kind ~= 0 then
200
+ error("publish runs while a server Subsystem is constructed; make it a field initializer", 0)
201
+ end
202
+ local declaration = declarationOf(target) or error("publish takes a replicated declaration", 0)
203
+ local label = (owner.record).id
204
+ local previous = declaration.publication
205
+ if previous ~= nil then
206
+ error(`{declaration.name} is published by {previous.label} and {label}; one Subsystem publishes each value`, 0)
207
+ end
208
+ if (declaration.audience == "player") ~= (project ~= nil) then
209
+ error(if declaration.audience == "player" then `{declaration.name} is per player; publish it with a projection` else `{declaration.name} is public; publish it without a projection`, 0)
210
+ end
211
+ local _condition = project ~= nil
212
+ if _condition then
213
+ local _project = project
214
+ _condition = not (type(_project) == "function")
215
+ end
216
+ if _condition then
217
+ error(`{declaration.name}: publish needs a projection function`, 0)
218
+ end
219
+ local publication = {
220
+ label = label,
221
+ project = project,
222
+ live = true,
223
+ }
224
+ declaration.publication = publication
225
+ if owner.subscriptions == nil then
226
+ owner.subscriptions = {}
227
+ end
228
+ local _exp = owner.subscriptions
229
+ table.insert(_exp, function()
230
+ return retire(declaration, publication)
231
+ end)
232
+ if declaration.audience == "player" then
233
+ return table.freeze({})
234
+ end
235
+ write(declaration.status, "ready")
236
+ return setmetatable({
237
+ declaration = declaration,
238
+ publication = publication,
239
+ }, publishedMeta)
240
+ end
241
+ --* @internal The server's registry seals with the catalog; the client's at its network commit (rule 41).
242
+ local function sealDeclarations()
243
+ sealed = true
244
+ end
245
+ --[[
246
+ *
247
+ * @internal
248
+ * Rule 74: retire every publication silently, restore every value to its admitted `initial` and status to
249
+ * "pending", and unseal. Declarations stay registered, because their modules stay cached.
250
+
251
+ ]]
252
+ local function rollbackDeclarations()
253
+ for _, declaration in ordered do
254
+ local publication = declaration.publication
255
+ if publication ~= nil then
256
+ publication.live = false
257
+ end
258
+ declaration.publication = nil
259
+ write(declaration.node, declaration.initial)
260
+ write(declaration.status, "pending")
261
+ end
262
+ sealed = false
263
+ end
264
+ return {
265
+ declareReplicated = declareReplicated,
266
+ declareFrameworkReplicated = declareFrameworkReplicated,
267
+ replicatedCapacity = replicatedCapacity,
268
+ declarationOf = declarationOf,
269
+ declarationNamed = declarationNamed,
270
+ declarations = declarations,
271
+ statusOf = statusOf,
272
+ useRetired = useRetired,
273
+ claimPublication = claimPublication,
274
+ sealDeclarations = sealDeclarations,
275
+ rollbackDeclarations = rollbackDeclarations,
276
+ }
@@ -0,0 +1 @@
1
+ export {};