@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,149 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
local _lifecycle = TS.import(script, script.Parent.Parent, "lifecycle")
|
|
4
|
+
local closeScope = _lifecycle.closeScope
|
|
5
|
+
local drain = _lifecycle.drain
|
|
6
|
+
local openScope = _lifecycle.openScope
|
|
7
|
+
local call = TS.import(script, script.Parent.Parent, "runner").call
|
|
8
|
+
--* @internal
|
|
9
|
+
--* @internal What the running thread is doing for the graph. Only the thread that pushed a frame sees it.
|
|
10
|
+
local stacks = setmetatable({}, {
|
|
11
|
+
__mode = "k",
|
|
12
|
+
})
|
|
13
|
+
--* @internal The innermost frame on the running thread. A spawned thread starts with none.
|
|
14
|
+
local function top()
|
|
15
|
+
local _arg0 = coroutine.running()
|
|
16
|
+
local stack = stacks[_arg0]
|
|
17
|
+
return if stack == nil then nil else stack[#stack]
|
|
18
|
+
end
|
|
19
|
+
--* @internal True on the worker of a body the graph runs, where a nested evaluation runs inline.
|
|
20
|
+
local function inBody()
|
|
21
|
+
local _arg0 = coroutine.running()
|
|
22
|
+
local stack = stacks[_arg0]
|
|
23
|
+
return stack ~= nil and stack[1] ~= nil and stack[1].kind ~= 2
|
|
24
|
+
end
|
|
25
|
+
--* @internal
|
|
26
|
+
local function push(frame)
|
|
27
|
+
local thread = coroutine.running()
|
|
28
|
+
local stack = stacks[thread]
|
|
29
|
+
if stack == nil then
|
|
30
|
+
stack = {}
|
|
31
|
+
local _stack = stack
|
|
32
|
+
stacks[thread] = _stack
|
|
33
|
+
end
|
|
34
|
+
local _stack = stack
|
|
35
|
+
local _frame = frame
|
|
36
|
+
table.insert(_stack, _frame)
|
|
37
|
+
end
|
|
38
|
+
--* @internal Removes `frame` and anything left above it on the running thread, which keeps its emptied stack.
|
|
39
|
+
local function pop(frame)
|
|
40
|
+
local _arg0 = coroutine.running()
|
|
41
|
+
local stack = stacks[_arg0]
|
|
42
|
+
if stack == nil then
|
|
43
|
+
return nil
|
|
44
|
+
end
|
|
45
|
+
local _frame = frame
|
|
46
|
+
local index = (table.find(stack, _frame) or 0) - 1
|
|
47
|
+
if index < 0 then
|
|
48
|
+
return nil
|
|
49
|
+
end
|
|
50
|
+
while #stack > index do
|
|
51
|
+
stack[#stack] = nil
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
local NONE_OPEN = {}
|
|
55
|
+
--[[
|
|
56
|
+
*
|
|
57
|
+
* @internal
|
|
58
|
+
* Takes a finished worker's frames and returns the evaluations it left open, innermost first. A worker that returned
|
|
59
|
+
* keeps its emptied stack for its next body.
|
|
60
|
+
|
|
61
|
+
]]
|
|
62
|
+
local function discard(worker, returned)
|
|
63
|
+
if worker == nil then
|
|
64
|
+
return NONE_OPEN
|
|
65
|
+
end
|
|
66
|
+
local _worker = worker
|
|
67
|
+
local stack = stacks[_worker]
|
|
68
|
+
if stack == nil then
|
|
69
|
+
return NONE_OPEN
|
|
70
|
+
end
|
|
71
|
+
local open
|
|
72
|
+
for i = #stack - 1, 0, -1 do
|
|
73
|
+
local frame = stack[i + 1]
|
|
74
|
+
if frame.kind ~= 1 then
|
|
75
|
+
continue
|
|
76
|
+
end
|
|
77
|
+
if open == nil then
|
|
78
|
+
open = {}
|
|
79
|
+
end
|
|
80
|
+
table.insert(open, frame)
|
|
81
|
+
end
|
|
82
|
+
if returned then
|
|
83
|
+
table.clear(stack)
|
|
84
|
+
else
|
|
85
|
+
local _worker_1 = worker
|
|
86
|
+
stacks[_worker_1] = nil
|
|
87
|
+
end
|
|
88
|
+
return open or NONE_OPEN
|
|
89
|
+
end
|
|
90
|
+
--* @internal How a body ended, with the evaluations it left open, innermost first.
|
|
91
|
+
--* A body on its way through `boundary`. It becomes its own completion.
|
|
92
|
+
--* Starts the worker's stack at the body's frame.
|
|
93
|
+
local function inner(job)
|
|
94
|
+
local worker = coroutine.running()
|
|
95
|
+
job.worker = worker
|
|
96
|
+
local frame = job.frame
|
|
97
|
+
if frame ~= nil then
|
|
98
|
+
local stack = stacks[worker]
|
|
99
|
+
if stack == nil or #stack > 0 then
|
|
100
|
+
local _arg1 = { frame }
|
|
101
|
+
stacks[worker] = _arg1
|
|
102
|
+
else
|
|
103
|
+
table.insert(stack, frame)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
return if job.arg == nil then job.body() else job.body(job.arg)
|
|
107
|
+
end
|
|
108
|
+
local function traceback(err)
|
|
109
|
+
return debug.traceback(tostring(err), 2)
|
|
110
|
+
end
|
|
111
|
+
--[[
|
|
112
|
+
*
|
|
113
|
+
* @internal
|
|
114
|
+
* The completion boundary. The body runs under `frame` on a pooled worker inside a lifecycle scope, and `complete`
|
|
115
|
+
* runs on this thread before the scope closes, so whatever the body left behind is finished before its requests
|
|
116
|
+
* drain. `complete` runs framework code only. A failure there is an internal error and is rethrown here.
|
|
117
|
+
|
|
118
|
+
]]
|
|
119
|
+
local function boundary(owner, frame, body, arg, complete, after)
|
|
120
|
+
local job = {
|
|
121
|
+
frame = frame,
|
|
122
|
+
body = body,
|
|
123
|
+
arg = arg,
|
|
124
|
+
after = after,
|
|
125
|
+
result = 0,
|
|
126
|
+
value = nil,
|
|
127
|
+
open = NONE_OPEN,
|
|
128
|
+
}
|
|
129
|
+
openScope()
|
|
130
|
+
local result, value = call(inner, job, nil, owner)
|
|
131
|
+
job.result = result
|
|
132
|
+
job.value = value
|
|
133
|
+
job.open = discard(job.worker, result == 0)
|
|
134
|
+
local ok, err = xpcall(complete, traceback, job)
|
|
135
|
+
if closeScope() then
|
|
136
|
+
drain()
|
|
137
|
+
end
|
|
138
|
+
if not ok then
|
|
139
|
+
error(err, 0)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
return {
|
|
143
|
+
top = top,
|
|
144
|
+
inBody = inBody,
|
|
145
|
+
push = push,
|
|
146
|
+
pop = pop,
|
|
147
|
+
discard = discard,
|
|
148
|
+
boundary = boundary,
|
|
149
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
local runtimeDiagnostic = TS.import(script, script.Parent.Parent, "diagnostics").runtimeDiagnostic
|
|
4
|
+
local _graph = TS.import(script, script.Parent, "graph")
|
|
5
|
+
local createdBefore = _graph.createdBefore
|
|
6
|
+
local dependenciesMoved = _graph.dependenciesMoved
|
|
7
|
+
local guardPure = _graph.guardPure
|
|
8
|
+
local markEffect = _graph.markEffect
|
|
9
|
+
local observerFrame = _graph.observerFrame
|
|
10
|
+
local originOf = _graph.originOf
|
|
11
|
+
local readUntracked = _graph.readUntracked
|
|
12
|
+
local rewire = _graph.rewire
|
|
13
|
+
local staleAfterRun = _graph.staleAfterRun
|
|
14
|
+
local useScheduler = _graph.useScheduler
|
|
15
|
+
local nextId = _graph.nextId
|
|
16
|
+
local _limits = TS.import(script, script.Parent, "limits")
|
|
17
|
+
local MAX_EFFECT_RUNS_PER_FLUSH = _limits.MAX_EFFECT_RUNS_PER_FLUSH
|
|
18
|
+
local MAX_FLUSH_ROUNDS = _limits.MAX_FLUSH_ROUNDS
|
|
19
|
+
local MAX_RUNS_PER_EFFECT = _limits.MAX_RUNS_PER_EFFECT
|
|
20
|
+
local _ownership = TS.import(script, script.Parent, "ownership")
|
|
21
|
+
local adopt = _ownership.adopt
|
|
22
|
+
local endLifetime = _ownership.endLifetime
|
|
23
|
+
local hold = _ownership.hold
|
|
24
|
+
local newLifetime = _ownership.newLifetime
|
|
25
|
+
local NO_OWNER = _ownership.NO_OWNER
|
|
26
|
+
local ownerLabel = _ownership.ownerLabel
|
|
27
|
+
local resolveOwner = _ownership.resolveOwner
|
|
28
|
+
local retire = _ownership.retire
|
|
29
|
+
local runBody = _ownership.runBody
|
|
30
|
+
local useDescribe = _ownership.useDescribe
|
|
31
|
+
--* @internal What the flush asks of the engine.
|
|
32
|
+
local port
|
|
33
|
+
--* Every realm starts held, so effects queued while modules load and boot runs wait for the commit.
|
|
34
|
+
local held = true
|
|
35
|
+
local requested = false
|
|
36
|
+
local flushing = false
|
|
37
|
+
local queue = {}
|
|
38
|
+
--* The entries one round took from the queue, and the index of the first one not yet reached.
|
|
39
|
+
local batch = {}
|
|
40
|
+
local cursor = 0
|
|
41
|
+
--* The entry being processed, which an internal error stops.
|
|
42
|
+
local culprit
|
|
43
|
+
--* Every effect whose run count moved this flush.
|
|
44
|
+
local ran = {}
|
|
45
|
+
--* A bound was reported, and no flush has finished within its bounds since.
|
|
46
|
+
local boundReported = false
|
|
47
|
+
--* @internal Boot installs the engine's port; until then an enqueue only queues.
|
|
48
|
+
local function useReactivePort(value)
|
|
49
|
+
port = value
|
|
50
|
+
useDescribe(if value == nil then tostring else function(instance)
|
|
51
|
+
return value.describe(instance)
|
|
52
|
+
end)
|
|
53
|
+
end
|
|
54
|
+
--* @internal Core edit E2. Idempotent.
|
|
55
|
+
local function holdBarrier()
|
|
56
|
+
held = true
|
|
57
|
+
end
|
|
58
|
+
--* @internal Core edit E2. Requests a flush when something is queued.
|
|
59
|
+
local request
|
|
60
|
+
local function releaseBarrier()
|
|
61
|
+
held = false
|
|
62
|
+
if #queue > 0 then
|
|
63
|
+
request()
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
local flush
|
|
67
|
+
function request()
|
|
68
|
+
if requested or flushing or held or port == nil then
|
|
69
|
+
return nil
|
|
70
|
+
end
|
|
71
|
+
requested = true
|
|
72
|
+
local ok = pcall(port.defer, flush)
|
|
73
|
+
if not ok then
|
|
74
|
+
requested = false
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
local function enqueue(effect)
|
|
78
|
+
if effect.queued or effect.stopped then
|
|
79
|
+
return nil
|
|
80
|
+
end
|
|
81
|
+
effect.queued = true
|
|
82
|
+
local _queue = queue
|
|
83
|
+
local _effect = effect
|
|
84
|
+
table.insert(_queue, _effect)
|
|
85
|
+
request()
|
|
86
|
+
end
|
|
87
|
+
useScheduler(function(effect)
|
|
88
|
+
return enqueue(effect)
|
|
89
|
+
end)
|
|
90
|
+
local function report(level, message)
|
|
91
|
+
pcall(runtimeDiagnostic, level, message)
|
|
92
|
+
end
|
|
93
|
+
local function fail(effect, reason)
|
|
94
|
+
retire(effect)
|
|
95
|
+
report("error", `Crucible: effect at {effect.origin} ({ownerLabel(effect.parent.owner)}) stopped: {reason}`)
|
|
96
|
+
end
|
|
97
|
+
local function createIn(owner, body)
|
|
98
|
+
local effect = {
|
|
99
|
+
kind = 2,
|
|
100
|
+
id = nextId(),
|
|
101
|
+
dependencies = {},
|
|
102
|
+
mark = 2,
|
|
103
|
+
stopped = false,
|
|
104
|
+
queued = false,
|
|
105
|
+
parent = owner,
|
|
106
|
+
linked = false,
|
|
107
|
+
body = body,
|
|
108
|
+
origin = originOf(body),
|
|
109
|
+
runs = 0,
|
|
110
|
+
}
|
|
111
|
+
adopt(owner, effect)
|
|
112
|
+
enqueue(effect)
|
|
113
|
+
end
|
|
114
|
+
--* @internal
|
|
115
|
+
local function createEffect(body)
|
|
116
|
+
guardPure("create effects")
|
|
117
|
+
local owner = resolveOwner()
|
|
118
|
+
if owner == nil then
|
|
119
|
+
error(NO_OWNER, 0)
|
|
120
|
+
end
|
|
121
|
+
if owner.ended then
|
|
122
|
+
return nil
|
|
123
|
+
end
|
|
124
|
+
createIn(owner, body)
|
|
125
|
+
end
|
|
126
|
+
local function watchOwner()
|
|
127
|
+
guardPure("create effects")
|
|
128
|
+
local owner = resolveOwner()
|
|
129
|
+
if owner == nil then
|
|
130
|
+
error(NO_OWNER, 0)
|
|
131
|
+
end
|
|
132
|
+
return owner
|
|
133
|
+
end
|
|
134
|
+
--* @internal
|
|
135
|
+
local watchFrom
|
|
136
|
+
local function createWatch(source, onChange)
|
|
137
|
+
local owner = watchOwner()
|
|
138
|
+
local baseline = readUntracked(source)
|
|
139
|
+
if not owner.ended then
|
|
140
|
+
watchFrom(owner, source, baseline, onChange)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
--* @internal A watch whose baseline the caller already read untracked, so `source` is not read again for it.
|
|
144
|
+
local function createWatchFrom(source, baseline, onChange)
|
|
145
|
+
local owner = watchOwner()
|
|
146
|
+
if not owner.ended then
|
|
147
|
+
watchFrom(owner, source, baseline, onChange)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
function watchFrom(owner, source, baseline, onChange)
|
|
151
|
+
local previous = baseline
|
|
152
|
+
createIn(owner, function()
|
|
153
|
+
local value = source()
|
|
154
|
+
if rawequal(value, previous) then
|
|
155
|
+
return nil
|
|
156
|
+
end
|
|
157
|
+
local before = previous
|
|
158
|
+
previous = value
|
|
159
|
+
readUntracked(function()
|
|
160
|
+
return onChange(value, before)
|
|
161
|
+
end)
|
|
162
|
+
end)
|
|
163
|
+
end
|
|
164
|
+
--* A run's frame names its effect and its run lifetime.
|
|
165
|
+
local function complete(completion)
|
|
166
|
+
local frame = completion.frame
|
|
167
|
+
local effect = frame.observer
|
|
168
|
+
local run = frame.lifetime
|
|
169
|
+
local value = completion.value
|
|
170
|
+
local failure
|
|
171
|
+
if completion.result ~= 0 then
|
|
172
|
+
failure = tostring(value)
|
|
173
|
+
elseif value ~= nil and not (type(value) == "function") then
|
|
174
|
+
failure = `returned a {typeof(value)}; an effect returns nothing or a cleanup function`
|
|
175
|
+
end
|
|
176
|
+
if failure ~= nil then
|
|
177
|
+
fail(effect, failure)
|
|
178
|
+
return nil
|
|
179
|
+
end
|
|
180
|
+
if value ~= nil then
|
|
181
|
+
hold(run, 0, value)
|
|
182
|
+
end
|
|
183
|
+
if effect.stopped then
|
|
184
|
+
return nil
|
|
185
|
+
end
|
|
186
|
+
local reads = frame.deps
|
|
187
|
+
rewire(effect, effect.dependencies, reads)
|
|
188
|
+
effect.dependencies = reads
|
|
189
|
+
markEffect(effect, staleAfterRun(effect))
|
|
190
|
+
end
|
|
191
|
+
local function runOnce(effect)
|
|
192
|
+
local previous = effect.run
|
|
193
|
+
effect.run = nil
|
|
194
|
+
if previous ~= nil then
|
|
195
|
+
endLifetime(previous)
|
|
196
|
+
end
|
|
197
|
+
local run = newLifetime(2, effect.parent, effect.parent.owner, effect)
|
|
198
|
+
effect.run = run
|
|
199
|
+
runBody(observerFrame(0, effect, run), effect.body, nil, complete)
|
|
200
|
+
end
|
|
201
|
+
local function carry()
|
|
202
|
+
local rest = {}
|
|
203
|
+
do
|
|
204
|
+
local i = cursor
|
|
205
|
+
local _shouldIncrement = false
|
|
206
|
+
while true do
|
|
207
|
+
if _shouldIncrement then
|
|
208
|
+
i += 1
|
|
209
|
+
else
|
|
210
|
+
_shouldIncrement = true
|
|
211
|
+
end
|
|
212
|
+
if not (i < #batch) then
|
|
213
|
+
break
|
|
214
|
+
end
|
|
215
|
+
local _arg0 = batch[i + 1]
|
|
216
|
+
table.insert(rest, _arg0)
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
for _, effect in queue do
|
|
220
|
+
table.insert(rest, effect)
|
|
221
|
+
end
|
|
222
|
+
queue = rest
|
|
223
|
+
batch = {}
|
|
224
|
+
cursor = 0
|
|
225
|
+
end
|
|
226
|
+
local function reportBound(bound)
|
|
227
|
+
if boundReported then
|
|
228
|
+
return nil
|
|
229
|
+
end
|
|
230
|
+
boundReported = true
|
|
231
|
+
local _array = {}
|
|
232
|
+
local _length = #_array
|
|
233
|
+
table.move(ran, 1, #ran, _length + 1, _array)
|
|
234
|
+
local busiest = _array
|
|
235
|
+
table.sort(busiest, function(a, b)
|
|
236
|
+
return a.runs > b.runs
|
|
237
|
+
end)
|
|
238
|
+
local origins = {}
|
|
239
|
+
do
|
|
240
|
+
local i = 0
|
|
241
|
+
local _shouldIncrement = false
|
|
242
|
+
while true do
|
|
243
|
+
if _shouldIncrement then
|
|
244
|
+
i += 1
|
|
245
|
+
else
|
|
246
|
+
_shouldIncrement = true
|
|
247
|
+
end
|
|
248
|
+
if not (i < math.min(3, #busiest)) then
|
|
249
|
+
break
|
|
250
|
+
end
|
|
251
|
+
local _arg0 = `{busiest[i + 1].origin} ({busiest[i + 1].runs})`
|
|
252
|
+
table.insert(origins, _arg0)
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
report("warning", `Crucible: the reactive flush reached {bound}; the rest waits for the next flush. Most runs: {table.concat(origins, ", ")}`)
|
|
256
|
+
end
|
|
257
|
+
local function runRounds()
|
|
258
|
+
local rounds = 0
|
|
259
|
+
local total = 0
|
|
260
|
+
while #queue > 0 do
|
|
261
|
+
if rounds == MAX_FLUSH_ROUNDS then
|
|
262
|
+
reportBound(`{MAX_FLUSH_ROUNDS} rounds`)
|
|
263
|
+
return nil
|
|
264
|
+
end
|
|
265
|
+
rounds += 1
|
|
266
|
+
batch = queue
|
|
267
|
+
queue = {}
|
|
268
|
+
cursor = 0
|
|
269
|
+
table.sort(batch, createdBefore)
|
|
270
|
+
while cursor < #batch do
|
|
271
|
+
local effect = batch[cursor + 1]
|
|
272
|
+
cursor += 1
|
|
273
|
+
culprit = effect
|
|
274
|
+
effect.queued = false
|
|
275
|
+
local mark = effect.mark
|
|
276
|
+
if effect.stopped or mark == 0 then
|
|
277
|
+
continue
|
|
278
|
+
end
|
|
279
|
+
effect.mark = 0
|
|
280
|
+
if mark == 1 and not dependenciesMoved(effect) then
|
|
281
|
+
continue
|
|
282
|
+
end
|
|
283
|
+
if total == MAX_EFFECT_RUNS_PER_FLUSH then
|
|
284
|
+
effect.mark = 2
|
|
285
|
+
effect.queued = true
|
|
286
|
+
cursor -= 1
|
|
287
|
+
culprit = nil
|
|
288
|
+
carry()
|
|
289
|
+
reportBound(`{MAX_EFFECT_RUNS_PER_FLUSH} effect runs`)
|
|
290
|
+
return nil
|
|
291
|
+
end
|
|
292
|
+
if effect.runs == MAX_RUNS_PER_EFFECT then
|
|
293
|
+
fail(effect, `ran {MAX_RUNS_PER_EFFECT} times in one flush`)
|
|
294
|
+
continue
|
|
295
|
+
end
|
|
296
|
+
effect.runs += 1
|
|
297
|
+
total += 1
|
|
298
|
+
if effect.runs == 1 then
|
|
299
|
+
table.insert(ran, effect)
|
|
300
|
+
end
|
|
301
|
+
runOnce(effect)
|
|
302
|
+
end
|
|
303
|
+
culprit = nil
|
|
304
|
+
batch = {}
|
|
305
|
+
cursor = 0
|
|
306
|
+
end
|
|
307
|
+
boundReported = false
|
|
308
|
+
end
|
|
309
|
+
local function recover(err)
|
|
310
|
+
local blamed = culprit
|
|
311
|
+
carry()
|
|
312
|
+
local stopFailure = ""
|
|
313
|
+
if blamed ~= nil then
|
|
314
|
+
local ok, stopErr = pcall(retire, blamed)
|
|
315
|
+
if not ok then
|
|
316
|
+
blamed.stopped = true
|
|
317
|
+
blamed.queued = false
|
|
318
|
+
stopFailure = `; stopping it failed: {tostring(stopErr)}`
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
local where = if blamed == nil then "" else ` while processing the effect at {blamed.origin}`
|
|
322
|
+
report("error", `Crucible: internal error; the reactive flush recovered{where}: {tostring(err)}{stopFailure}`)
|
|
323
|
+
end
|
|
324
|
+
--* @internal The deferred flush. Runs every queued effect to rest under the bounds. Never throws.
|
|
325
|
+
function flush()
|
|
326
|
+
requested = false
|
|
327
|
+
if held or flushing then
|
|
328
|
+
return nil
|
|
329
|
+
end
|
|
330
|
+
flushing = true
|
|
331
|
+
local ok, err = pcall(runRounds)
|
|
332
|
+
if not ok then
|
|
333
|
+
recover(err)
|
|
334
|
+
end
|
|
335
|
+
for _, effect in ran do
|
|
336
|
+
effect.runs = 0
|
|
337
|
+
end
|
|
338
|
+
ran = {}
|
|
339
|
+
batch = {}
|
|
340
|
+
cursor = 0
|
|
341
|
+
culprit = nil
|
|
342
|
+
flushing = false
|
|
343
|
+
if #queue > 0 then
|
|
344
|
+
request()
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
return {
|
|
348
|
+
useReactivePort = useReactivePort,
|
|
349
|
+
holdBarrier = holdBarrier,
|
|
350
|
+
releaseBarrier = releaseBarrier,
|
|
351
|
+
createEffect = createEffect,
|
|
352
|
+
createWatch = createWatch,
|
|
353
|
+
createWatchFrom = createWatchFrom,
|
|
354
|
+
flush = flush,
|
|
355
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|