@trioforge/crucible 0.1.0 → 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/out/client/index.d.ts +7 -0
- package/out/client/init.luau +36 -1
- package/out/configs.d.ts +37 -0
- package/out/configs.luau +46 -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 +5 -1
- package/out/emit/init.luau +23 -7
- 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 +8 -0
- package/out/init.luau +23 -0
- package/out/internal/binding.luau +5 -0
- package/out/internal/boot.luau +107 -8
- 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/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/lifecycle.luau +45 -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/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.luau +10 -1
- 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/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 +16 -0
- package/out/server/init.luau +49 -3
- package/out/server/publish.d.ts +20 -0
- package/out/server/publish.luau +24 -0
- package/out/vide/index.d.ts +7 -0
- package/out/vide/init.luau +50 -0
- package/package.json +13 -2
- package/transformer/data.cjs +72 -0
- package/transformer/declarations.cjs +153 -0
- package/transformer/index.js +37 -11
- package/transformer/network.cjs +961 -0
- package/vide/package.json +4 -0
|
@@ -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 {};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
local defineFramework = TS.import(script, script.Parent.Parent, "network", "catalog").defineFramework
|
|
4
|
+
local n = TS.import(script, script.Parent.Parent, "network", "schema").n
|
|
5
|
+
local _limits = TS.import(script, script.Parent.Parent, "reactive", "limits")
|
|
6
|
+
local SYNC_FRAME_BYTES = _limits.SYNC_FRAME_BYTES
|
|
7
|
+
local SYNC_FRAME_COLLECTION_ENTRIES = _limits.SYNC_FRAME_COLLECTION_ENTRIES
|
|
8
|
+
local SYNC_FRAME_VALUE_VISITS = _limits.SYNC_FRAME_VALUE_VISITS
|
|
9
|
+
local costOf = TS.import(script, script.Parent, "values").costOf
|
|
10
|
+
--* @internal One declaration's whole value in a frame. A nil `value` is a real root `undefined`.
|
|
11
|
+
--* @internal One atomic observation unit for one peer. Both lists are strictly ascending by key.
|
|
12
|
+
--* @internal
|
|
13
|
+
local SyncFrameMessage = defineFramework({
|
|
14
|
+
kind = "to-client",
|
|
15
|
+
name = "Crucible.State.Frame",
|
|
16
|
+
input = n.object({ { "failed", n.array(n.string) }, { "kind", n.union({ n.literal("failed"), n.literal("snapshot"), n.literal("update") }) }, { "revision", n.u32 }, { "values", n.array(n.object({ { "key", n.string }, { "value", n.dynamic } })) } }),
|
|
17
|
+
})
|
|
18
|
+
--* @internal The last revision a client applied before its sync stopped.
|
|
19
|
+
local SyncFaultMessage = defineFramework({
|
|
20
|
+
kind = "to-server",
|
|
21
|
+
name = "Crucible.State.Fault",
|
|
22
|
+
input = n.u32,
|
|
23
|
+
})
|
|
24
|
+
local function varuintSize(value)
|
|
25
|
+
local size = 1
|
|
26
|
+
while value >= 128 do
|
|
27
|
+
value = math.floor(value / 128)
|
|
28
|
+
size += 1
|
|
29
|
+
end
|
|
30
|
+
return size
|
|
31
|
+
end
|
|
32
|
+
local function stringBytes(text)
|
|
33
|
+
return varuintSize(#text) + #text
|
|
34
|
+
end
|
|
35
|
+
--* @internal The whole EVENT packet `send` encodes for `frame`, with visits and entries as the decoder counts them.
|
|
36
|
+
--[[
|
|
37
|
+
*
|
|
38
|
+
* @internal
|
|
39
|
+
* Rule 69: the envelope (opcode, catalog index, slot count 0), then the frame object, `failed`, the kind's union
|
|
40
|
+
* selection and literal, the u32 revision, and each value entry with its value's cached admission cost.
|
|
41
|
+
|
|
42
|
+
]]
|
|
43
|
+
local function frameCost(frame, catalogIndex)
|
|
44
|
+
local bytes = 1 + varuintSize(catalogIndex) + 1
|
|
45
|
+
local entries = #frame.failed + #frame.values
|
|
46
|
+
local visits = 1
|
|
47
|
+
visits += 1
|
|
48
|
+
bytes += varuintSize(#frame.failed)
|
|
49
|
+
for _, key in frame.failed do
|
|
50
|
+
visits += 1
|
|
51
|
+
bytes += stringBytes(key)
|
|
52
|
+
end
|
|
53
|
+
visits += 2
|
|
54
|
+
bytes += 1
|
|
55
|
+
visits += 1
|
|
56
|
+
bytes += 4
|
|
57
|
+
visits += 1
|
|
58
|
+
bytes += varuintSize(#frame.values)
|
|
59
|
+
for _, entry in frame.values do
|
|
60
|
+
visits += 2
|
|
61
|
+
bytes += stringBytes(entry.key)
|
|
62
|
+
local cost = costOf(entry.value)
|
|
63
|
+
bytes += cost.bytes
|
|
64
|
+
entries += cost.entries
|
|
65
|
+
visits += cost.visits
|
|
66
|
+
end
|
|
67
|
+
return {
|
|
68
|
+
bytes = bytes,
|
|
69
|
+
entries = entries,
|
|
70
|
+
visits = visits,
|
|
71
|
+
}
|
|
72
|
+
end
|
|
73
|
+
--* @internal The first frame budget `cost` passes, or undefined when it fits.
|
|
74
|
+
local function overBudget(cost)
|
|
75
|
+
if cost.bytes > SYNC_FRAME_BYTES then
|
|
76
|
+
return "SYNC_FRAME_BYTES"
|
|
77
|
+
end
|
|
78
|
+
if cost.entries > SYNC_FRAME_COLLECTION_ENTRIES then
|
|
79
|
+
return "SYNC_FRAME_COLLECTION_ENTRIES"
|
|
80
|
+
end
|
|
81
|
+
if cost.visits > SYNC_FRAME_VALUE_VISITS then
|
|
82
|
+
return "SYNC_FRAME_VALUE_VISITS"
|
|
83
|
+
end
|
|
84
|
+
return nil
|
|
85
|
+
end
|
|
86
|
+
return {
|
|
87
|
+
frameCost = frameCost,
|
|
88
|
+
overBudget = overBudget,
|
|
89
|
+
SyncFrameMessage = SyncFrameMessage,
|
|
90
|
+
SyncFaultMessage = SyncFaultMessage,
|
|
91
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|