@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.
- package/LICENSE +21 -0
- package/client/package.json +4 -0
- package/emit/package.json +4 -0
- package/out/all.d.ts +7 -0
- package/out/all.luau +2 -0
- package/out/attribute.d.ts +7 -0
- package/out/attribute.luau +7 -0
- package/out/client/index.d.ts +10 -0
- package/out/client/init.luau +55 -0
- package/out/configs.d.ts +37 -0
- package/out/configs.luau +46 -0
- package/out/crucible-behaviour.d.ts +16 -0
- package/out/crucible-behaviour.luau +78 -0
- package/out/crucible-object.d.ts +10 -0
- package/out/crucible-object.luau +69 -0
- package/out/data.d.ts +224 -0
- package/out/data.luau +97 -0
- package/out/emit/configs.d.ts +4 -0
- package/out/emit/configs.luau +22 -0
- package/out/emit/data.d.ts +6 -0
- package/out/emit/data.luau +19 -0
- package/out/emit/index.d.ts +45 -0
- package/out/emit/init.luau +101 -0
- package/out/emit/network.d.ts +6 -0
- package/out/emit/network.luau +36 -0
- package/out/emit/replicated.d.ts +4 -0
- package/out/emit/replicated.luau +13 -0
- package/out/index.d.ts +20 -0
- package/out/init.luau +34 -0
- package/out/inject.d.ts +1 -0
- package/out/inject.luau +7 -0
- package/out/internal/binding.d.ts +1 -0
- package/out/internal/binding.luau +615 -0
- package/out/internal/boot.d.ts +1 -0
- package/out/internal/boot.luau +562 -0
- package/out/internal/configs/limits.d.ts +10 -0
- package/out/internal/configs/limits.luau +25 -0
- package/out/internal/configs/port.d.ts +29 -0
- package/out/internal/configs/port.luau +99 -0
- package/out/internal/configs/runtime.d.ts +21 -0
- package/out/internal/configs/runtime.luau +981 -0
- package/out/internal/construction-claim.d.ts +1 -0
- package/out/internal/construction-claim.luau +26 -0
- package/out/internal/data/admission.d.ts +1 -0
- package/out/internal/data/admission.luau +673 -0
- package/out/internal/data/declarations.d.ts +1 -0
- package/out/internal/data/declarations.luau +384 -0
- package/out/internal/data/envelope.d.ts +1 -0
- package/out/internal/data/envelope.luau +318 -0
- package/out/internal/data/handles.d.ts +1 -0
- package/out/internal/data/handles.luau +255 -0
- package/out/internal/data/inbox-transforms.d.ts +1 -0
- package/out/internal/data/inbox-transforms.luau +458 -0
- package/out/internal/data/inbox.d.ts +1 -0
- package/out/internal/data/inbox.luau +437 -0
- package/out/internal/data/lease.d.ts +1 -0
- package/out/internal/data/lease.luau +304 -0
- package/out/internal/data/limits.d.ts +1 -0
- package/out/internal/data/limits.luau +234 -0
- package/out/internal/data/messages.d.ts +1 -0
- package/out/internal/data/messages.luau +69 -0
- package/out/internal/data/operation-id.d.ts +1 -0
- package/out/internal/data/operation-id.luau +61 -0
- package/out/internal/data/paths.d.ts +2 -0
- package/out/internal/data/paths.luau +323 -0
- package/out/internal/data/port.d.ts +1 -0
- package/out/internal/data/port.luau +290 -0
- package/out/internal/data/profiles.d.ts +1 -0
- package/out/internal/data/profiles.luau +862 -0
- package/out/internal/data/receipts.d.ts +1 -0
- package/out/internal/data/receipts.luau +190 -0
- package/out/internal/data/record-bindings.d.ts +1 -0
- package/out/internal/data/record-bindings.luau +92 -0
- package/out/internal/data/record-declarations.d.ts +1 -0
- package/out/internal/data/record-declarations.luau +140 -0
- package/out/internal/data/records.d.ts +1 -0
- package/out/internal/data/records.luau +400 -0
- package/out/internal/data/runtime.d.ts +1 -0
- package/out/internal/data/runtime.luau +918 -0
- package/out/internal/data/scheduler.d.ts +1 -0
- package/out/internal/data/scheduler.luau +507 -0
- package/out/internal/data/session.d.ts +1 -0
- package/out/internal/data/session.luau +234 -0
- package/out/internal/data/shutdown.d.ts +1 -0
- package/out/internal/data/shutdown.luau +91 -0
- package/out/internal/data/waiters.d.ts +1 -0
- package/out/internal/data/waiters.luau +65 -0
- package/out/internal/dense-list.d.ts +1 -0
- package/out/internal/dense-list.luau +155 -0
- package/out/internal/diagnostics.d.ts +1 -0
- package/out/internal/diagnostics.luau +29 -0
- package/out/internal/hooks.d.ts +1 -0
- package/out/internal/hooks.luau +53 -0
- package/out/internal/implementers.d.ts +1 -0
- package/out/internal/implementers.luau +119 -0
- package/out/internal/lifecycle.d.ts +1 -0
- package/out/internal/lifecycle.luau +912 -0
- package/out/internal/log-output.d.ts +1 -0
- package/out/internal/log-output.luau +106 -0
- package/out/internal/network/catalog.d.ts +26 -0
- package/out/internal/network/catalog.luau +182 -0
- package/out/internal/network/claims.d.ts +1 -0
- package/out/internal/network/claims.luau +154 -0
- package/out/internal/network/client-session.d.ts +1 -0
- package/out/internal/network/client-session.luau +403 -0
- package/out/internal/network/codec.d.ts +63 -0
- package/out/internal/network/codec.luau +1183 -0
- package/out/internal/network/envelope.d.ts +1 -0
- package/out/internal/network/envelope.luau +247 -0
- package/out/internal/network/limits.d.ts +26 -0
- package/out/internal/network/limits.luau +55 -0
- package/out/internal/network/port.d.ts +1 -0
- package/out/internal/network/port.luau +19 -0
- package/out/internal/network/roblox-network-port.d.ts +1 -0
- package/out/internal/network/roblox-network-port.luau +105 -0
- package/out/internal/network/schema.d.ts +72 -0
- package/out/internal/network/schema.luau +386 -0
- package/out/internal/network/server-session.d.ts +1 -0
- package/out/internal/network/server-session.luau +393 -0
- package/out/internal/network/session.d.ts +1 -0
- package/out/internal/network/session.luau +266 -0
- package/out/internal/port.d.ts +1 -0
- package/out/internal/port.luau +12 -0
- package/out/internal/reactive/context.d.ts +1 -0
- package/out/internal/reactive/context.luau +149 -0
- package/out/internal/reactive/flush.d.ts +1 -0
- package/out/internal/reactive/flush.luau +355 -0
- package/out/internal/reactive/graph.d.ts +1 -0
- package/out/internal/reactive/graph.luau +626 -0
- package/out/internal/reactive/limits.d.ts +1 -0
- package/out/internal/reactive/limits.luau +54 -0
- package/out/internal/reactive/ownership.d.ts +1 -0
- package/out/internal/reactive/ownership.luau +626 -0
- package/out/internal/registry.d.ts +1 -0
- package/out/internal/registry.luau +455 -0
- package/out/internal/roblox-port.d.ts +1 -0
- package/out/internal/roblox-port.luau +117 -0
- package/out/internal/runner.d.ts +1 -0
- package/out/internal/runner.luau +86 -0
- package/out/internal/scheduler.d.ts +1 -0
- package/out/internal/scheduler.luau +207 -0
- package/out/internal/state.d.ts +1 -0
- package/out/internal/state.luau +41 -0
- package/out/internal/sync/client-sync.d.ts +1 -0
- package/out/internal/sync/client-sync.luau +157 -0
- package/out/internal/sync/declarations.d.ts +1 -0
- package/out/internal/sync/declarations.luau +276 -0
- package/out/internal/sync/frame.d.ts +1 -0
- package/out/internal/sync/frame.luau +91 -0
- package/out/internal/sync/server-sync.d.ts +1 -0
- package/out/internal/sync/server-sync.luau +502 -0
- package/out/internal/sync/values.d.ts +1 -0
- package/out/internal/sync/values.luau +930 -0
- package/out/log-level.d.ts +7 -0
- package/out/log-level.luau +21 -0
- package/out/logging.d.ts +34 -0
- package/out/logging.luau +233 -0
- package/out/network.d.ts +66 -0
- package/out/network.luau +24 -0
- package/out/reactive.d.ts +76 -0
- package/out/reactive.luau +136 -0
- package/out/replicated.d.ts +38 -0
- package/out/replicated.luau +45 -0
- package/out/server/data.d.ts +54 -0
- package/out/server/data.luau +44 -0
- package/out/server/index.d.ts +18 -0
- package/out/server/init.luau +62 -0
- package/out/server/publish.d.ts +20 -0
- package/out/server/publish.luau +24 -0
- package/out/subsystem.d.ts +4 -0
- package/out/subsystem.luau +30 -0
- package/out/vide/index.d.ts +7 -0
- package/out/vide/init.luau +50 -0
- package/package.json +52 -5
- package/server/package.json +4 -0
- package/transformer/data.cjs +72 -0
- package/transformer/declarations.cjs +153 -0
- package/transformer/diagnostics.cjs +19 -0
- package/transformer/index.js +741 -0
- package/transformer/network.cjs +961 -0
- package/transformer/type-id.cjs +80 -0
- package/vide/package.json +4 -0
- 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 {};
|