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