@field/cascade 0.10.0 → 0.12.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/AGENTS.md +25 -5
- package/ARCHITECTURE.md +5 -4
- package/CHANGELOG.md +382 -0
- package/README.md +1 -2
- package/dist/assets/AgentPanel-1JugFjE4.css +1 -0
- package/dist/assets/AgentPanel-BDL5MddA.js +5 -0
- package/dist/assets/{CodeEditor-DSZBpzdy.js → CodeEditor-C-xYG-Wn.js} +120 -120
- package/dist/assets/CodePanel-CtXd50m6.js +2 -0
- package/dist/assets/CookInfoPanel-B3NMhKJF.css +1 -0
- package/dist/assets/CookInfoPanel-DG4_TRsX.js +2 -0
- package/dist/assets/CustomNodeDialog-CHfZUs-r.js +1 -0
- package/dist/assets/DefinitionNode-CaRTXQ_b.js +1 -0
- package/dist/assets/DefinitionPanel-C3CxZY9r.js +1 -0
- package/dist/assets/DefinitionPanel-VNLnAiQD.css +1 -0
- package/dist/assets/OpenGraphDialog-BLdr6k_q.js +1 -0
- package/dist/assets/ProjectSettings-ZnvmIAir.js +2 -0
- package/dist/assets/{SettingsDialog-p25h3tj0.js → SettingsDialog-CBX3Wa87.js} +1 -1
- package/dist/assets/TimelinePanel-CDeR0arY.css +1 -0
- package/dist/assets/TimelinePanel-CWgnFH1e.js +1 -0
- package/dist/assets/{VersionHistoryDialog-DzFLUotB.js → VersionHistoryDialog-BOSoW1R-.js} +1 -1
- package/dist/assets/assert-BO2_erPD.js +1 -0
- package/dist/assets/assert-hvykGCnP.js +1 -0
- package/dist/assets/builder-BmBKcDix.js +1 -0
- package/dist/assets/camera-DFF7YA3F.js +1 -0
- package/dist/assets/checks-DAvyO9Gf.js +1 -0
- package/dist/assets/contactSheet-N1NcaWxw.js +2 -0
- package/dist/assets/{core-BICrEt0y.js → core-DB-MkGjj.js} +3 -4
- package/dist/assets/coreTypes-DtnDATZ7.js +1 -0
- package/dist/assets/{cssMode-D7TXw57I.js → cssMode-DPVckSOc.js} +1 -1
- package/dist/assets/definitions-B227e7f_.js +1 -0
- package/dist/assets/definitions-D0WOUJd7.js +1 -0
- package/dist/assets/{editor.api-DvkCO3ko.js → editor.api-Dkutg_Z1.js} +1 -1
- package/dist/assets/expressions-X-Z-GrgW.js +5 -0
- package/dist/assets/file-DAL6IfjV.js +1 -0
- package/dist/assets/filters-D3k1LRMa.js +1 -0
- package/dist/assets/{freemarker2-BA2lxsF1.js → freemarker2-CLiLKk3B.js} +1 -1
- package/dist/assets/generators-BhDxRAFC.js +1 -0
- package/dist/assets/geo-BsMImJIJ.js +1 -0
- package/dist/assets/geo-C4YngDob.js +2 -0
- package/dist/assets/{handlebars-BcDiyPd8.js → handlebars-CcD3aMeL.js} +1 -1
- package/dist/assets/{html-C7O4TyhX.js → html-DvZWGfqb.js} +1 -1
- package/dist/assets/{htmlMode-WaDGF3Mj.js → htmlMode-DUD3VaoN.js} +1 -1
- package/dist/assets/image-0SfH-snh.js +1 -0
- package/dist/assets/image-Ck-RdGU4.js +2 -0
- package/dist/assets/index-BWnrmIB9.js +137 -0
- package/dist/assets/index-DIaxEONI.css +1 -0
- package/dist/assets/io-BGmt2g0D.js +1 -0
- package/dist/assets/{javascript-CpIEK7tz.js → javascript-Cap2ahgh.js} +1 -1
- package/dist/assets/{jsonMode-B2FltW0f.js → jsonMode-B0uLjp9C.js} +1 -1
- package/dist/assets/{liquid-CN6FFHZE.js → liquid-CnxwhfLn.js} +1 -1
- package/dist/assets/{lspLanguageFeatures-QhV9XtLp.js → lspLanguageFeatures-Dn9hlKX6.js} +1 -1
- package/dist/assets/math-GeULS-sk.js +1 -0
- package/dist/assets/{mdx-DE89pafV.js → mdx-BV3jbrVa.js} +1 -1
- package/dist/assets/pop-KMF_qloi.js +1 -0
- package/dist/assets/preload-helper-Czpn1I53.js +1 -0
- package/dist/assets/{python-ByTllTB9.js → python-CGyFk4XK.js} +1 -1
- package/dist/assets/{razor-USYlRRCE.js → razor-CQfzX3-N.js} +1 -1
- package/dist/assets/remap-5SjXQU-q.js +1 -0
- package/dist/assets/renderMarkdown-ork0Oir7.js +67 -0
- package/dist/assets/studioImages-CAyH-0Pl.js +1 -0
- package/dist/assets/text-ZpX7qNkx.js +2 -0
- package/dist/assets/time-P_OiBhcf.js +1 -0
- package/dist/assets/{tsMode-BXpN8iUz.js → tsMode-BOeXzO9H.js} +1 -1
- package/dist/assets/{typescript-DInBjGQQ.js → typescript-J1SnE_Tu.js} +1 -1
- package/dist/assets/{workers-C7Ye5B7D.js → workers-CYwwTiVF.js} +1 -1
- package/dist/assets/{xml-U0Uocqyo.js → xml-DnPRNzK9.js} +1 -1
- package/dist/assets/{yaml-CBw_PVYf.js → yaml-BmmYBNa9.js} +1 -1
- package/dist/cli/DefinitionNode-P7AA4TAU.js +15 -0
- package/dist/cli/Graph-OB6EWSGL.js +2975 -0
- package/dist/cli/assert-GROGQNCQ.js +25 -0
- package/dist/cli/checks-3WB44XBL.js +189 -0
- package/dist/cli/chunk-2JEU3MGB.js +1125 -0
- package/dist/cli/chunk-2KXB47FP.js +115 -0
- package/dist/cli/chunk-3RXL5GHB.js +816 -0
- package/dist/cli/chunk-53LGFPWX.js +138 -0
- package/dist/cli/chunk-5ORUQ6ES.js +267 -0
- package/dist/cli/chunk-73S4JZXK.js +336 -0
- package/dist/cli/chunk-7QE2ZNSN.js +121 -0
- package/dist/cli/chunk-BP7UKF36.js +376 -0
- package/dist/cli/chunk-BPODRH52.js +201 -0
- package/dist/cli/chunk-BZ63XQ3I.js +1301 -0
- package/dist/cli/chunk-CKS2FQWR.js +89 -0
- package/dist/cli/chunk-CO22XHNG.js +593 -0
- package/dist/cli/chunk-CTPLRSWF.js +2197 -0
- package/dist/cli/chunk-DEQSEFFH.js +153 -0
- package/dist/cli/chunk-EU4ZFZ7Y.js +122 -0
- package/dist/cli/chunk-EZRWJ4G2.js +104 -0
- package/dist/cli/chunk-FWMSGKEH.js +71 -0
- package/dist/cli/chunk-G23A2BZZ.js +705 -0
- package/dist/cli/chunk-GRIN2DDP.js +1465 -0
- package/dist/cli/chunk-HFHWYL55.js +139 -0
- package/dist/cli/chunk-HSNOBIVT.js +263 -0
- package/dist/cli/chunk-ICH7N5PN.js +26 -0
- package/dist/cli/chunk-J7VK7MNF.js +418 -0
- package/dist/cli/chunk-JVOCK5W4.js +83 -0
- package/dist/cli/chunk-KBGUO27Q.js +15 -0
- package/dist/cli/chunk-KH4MUBKF.js +1551 -0
- package/dist/cli/chunk-L6B6FUQP.js +330 -0
- package/dist/cli/chunk-LTXWBXOM.js +438 -0
- package/dist/cli/chunk-MI5JLQYA.js +220 -0
- package/dist/cli/chunk-MP653A5O.js +3766 -0
- package/dist/cli/chunk-NJFMDXHR.js +320 -0
- package/dist/cli/chunk-OM6BEME6.js +267 -0
- package/dist/cli/chunk-OWWBYLHC.js +136 -0
- package/dist/cli/chunk-QQPA2ZHH.js +273 -0
- package/dist/cli/chunk-RUU3YWXU.js +99 -0
- package/dist/cli/chunk-RXDIMXN5.js +98 -0
- package/dist/cli/chunk-ST6LROUG.js +267 -0
- package/dist/cli/chunk-TJW67QSI.js +225 -0
- package/dist/cli/chunk-WD62JIP6.js +118 -0
- package/dist/cli/chunk-WGKPJZDY.js +364 -0
- package/dist/cli/chunk-X4WKAOQP.js +634 -0
- package/dist/cli/chunk-X566FWBV.js +156 -0
- package/dist/cli/chunk-YH637PDL.js +317 -0
- package/dist/cli/chunk-ZL4JTG5Y.js +37 -0
- package/dist/cli/chunk-ZSCUZE6I.js +112 -0
- package/dist/cli/cliCommands-JJN5Z3ZC.js +3470 -0
- package/dist/cli/commands-PHYFYA3F.js +1374 -0
- package/dist/cli/contactSheet-2PQVRUFW.js +11 -0
- package/dist/cli/definitions-BPOPW52L.js +32 -0
- package/dist/cli/doctorCommand-ORWQRNYP.js +106 -0
- package/dist/cli/edit-S5KEQ5RA.js +28 -0
- package/dist/cli/editorCommands-NFR32JOY.js +41 -0
- package/dist/cli/encode-ICPEPMH5.js +17 -0
- package/dist/cli/file-3HTQHPYQ.js +41 -0
- package/dist/cli/filters-6ODFWYK4.js +379 -0
- package/dist/cli/frames-LSAUBZ7P.js +16 -0
- package/dist/cli/generators-YU7GB4MD.js +210 -0
- package/dist/cli/geo-6ZPGKZRU.js +43 -0
- package/dist/cli/graphCommands-EOUOBOIJ.js +184 -0
- package/dist/cli/graphFile-YTGJB5F5.js +292 -0
- package/dist/cli/headlessCanvas-ENRHC7GS.js +16 -0
- package/dist/cli/helpCommand-PULH6ZYB.js +157 -0
- package/dist/cli/image-TXELZZK5.js +55 -0
- package/dist/cli/index.js +100 -30756
- package/dist/cli/math-RSYRQ3C6.js +16 -0
- package/dist/cli/mcp-JVZFQOEX.js +164 -0
- package/dist/cli/parity-X6DEH2DV.js +334 -0
- package/dist/cli/pop-B7SIIYKN.js +39 -0
- package/dist/cli/project-VYGIA7JX.js +12 -0
- package/dist/cli/projectDefinition-F3D7UIHJ.js +27 -0
- package/dist/cli/projectRuntime-2Q4JKEH3.js +55 -0
- package/dist/cli/registerStandardNodes-BXJCB6E4.js +744 -0
- package/dist/cli/render-PT4MWI6U.js +438 -0
- package/dist/cli/runCommand-HNPQVZMS.js +198 -0
- package/dist/cli/runner-5XPXVW7J.js +43 -0
- package/dist/cli/runtime/image.ts +302 -0
- package/dist/cli/runtime/io.ts +78 -0
- package/dist/cli/runtime/stage.ts +4 -1
- package/dist/cli/share-MHF6TNRK.js +166 -0
- package/dist/cli/stageCache-TY2BFZJR.js +15 -0
- package/dist/cli/stageRunner-KY2EJP7X.js +10 -0
- package/dist/cli/text-MB3EKR4H.js +126 -0
- package/dist/cli/verbs-PI2JH5FI.js +19 -0
- package/dist/cli/webBuild-WPPMGGW7.js +44 -0
- package/dist/contracts/capabilities.d.ts +18 -1
- package/dist/contracts/color.d.ts +8 -0
- package/dist/contracts/color.js +23 -0
- package/dist/contracts/definition.d.ts +10 -0
- package/dist/contracts/documentFormat.d.ts +18 -0
- package/dist/contracts/documentFormat.js +195 -0
- package/dist/contracts/editor.d.ts +17 -0
- package/dist/contracts/index.d.ts +1 -0
- package/dist/contracts/index.js +1 -0
- package/dist/contracts/schema.d.ts +1 -1
- package/dist/contracts/schema.js +1 -0
- package/dist/contracts/validate.js +11 -1
- package/dist/contracts/values.d.ts +24 -0
- package/dist/image.d.ts +156 -0
- package/dist/image.js +161 -0
- package/dist/index.html +8 -2
- package/dist/io.d.ts +29 -0
- package/dist/io.js +40 -0
- package/dist/player/app.js +3 -2
- package/dist/player/editor.js +35 -9
- package/dist/player/editorEngine.js +3 -2
- package/dist/runtime/builtins/assert/checks.d.ts +18 -0
- package/dist/runtime/builtins/assert/checks.js +207 -0
- package/dist/runtime/builtins/assert/definitions.d.ts +130 -0
- package/dist/runtime/builtins/assert/definitions.js +54 -0
- package/dist/runtime/builtins/assert/index.d.ts +152 -0
- package/dist/runtime/builtins/assert/index.js +36 -0
- package/dist/runtime/builtins/image/contactSheet.d.ts +64 -0
- package/dist/runtime/builtins/image/contactSheet.js +150 -0
- package/dist/runtime/builtins/image/definitions.d.ts +950 -0
- package/dist/runtime/builtins/image/definitions.js +343 -0
- package/dist/runtime/builtins/image/file.d.ts +8 -0
- package/dist/runtime/builtins/image/file.js +37 -0
- package/dist/runtime/builtins/image/filters.d.ts +8 -0
- package/dist/runtime/builtins/image/filters.js +394 -0
- package/dist/runtime/builtins/image/generators.d.ts +7 -0
- package/dist/runtime/builtins/image/generators.js +144 -0
- package/dist/runtime/builtins/image/index.d.ts +952 -0
- package/dist/runtime/builtins/image/index.js +44 -0
- package/dist/runtime/builtins/image/io.d.ts +100 -0
- package/dist/runtime/builtins/image/io.js +142 -0
- package/dist/runtime/builtins/image/planar.d.ts +55 -0
- package/dist/runtime/builtins/image/planar.js +274 -0
- package/dist/runtime/builtins/image/simplex.d.ts +22 -0
- package/dist/runtime/builtins/image/simplex.js +92 -0
- package/dist/runtime/builtins/image/text.d.ts +4 -0
- package/dist/runtime/builtins/image/text.js +141 -0
- package/dist/runtime/builtins/index.d.ts +3 -0
- package/dist/runtime/builtins/index.js +7 -0
- package/dist/runtime/gpu.d.ts +25 -0
- package/dist/runtime/gpu.js +40 -0
- package/dist/runtime/runtime.d.ts +2 -0
- package/dist/runtime/runtime.js +268 -44
- package/dist/runtime/types.d.ts +30 -0
- package/dist/stage.js +4 -1
- package/doc/CLI_REFERENCE.md +91 -2
- package/doc/EDITOR_EXPORT.md +37 -2
- package/doc/MCP.md +18 -1
- package/doc/NODE_REFERENCE.md +424 -10
- package/doc/PROJECT_AUTHORING.md +222 -7
- package/package.json +19 -4
- package/dist/assets/CodePanel-sY6sZa7B.js +0 -2
- package/dist/assets/CustomNodeDialog-BRKnqZhp.js +0 -1
- package/dist/assets/ExportDialog-C7dVGua4.js +0 -358
- package/dist/assets/ExportDialog-DOq0flMV.css +0 -1
- package/dist/assets/OpenGraphDialog-ZsgL-iwr.js +0 -1
- package/dist/assets/ProjectSettings-Cw17nNe3.js +0 -2
- package/dist/assets/definitions-DUww2Rt-.js +0 -1
- package/dist/assets/geo-C2RHOVHC.js +0 -1
- package/dist/assets/image-DR4oeyJB.js +0 -2062
- package/dist/assets/index-7vae6YFC.css +0 -1
- package/dist/assets/index-DyvDC6pS.js +0 -219
- package/dist/assets/math-DaDEgWKt.js +0 -1
- package/dist/assets/pop-pLH7L0PW.js +0 -1
package/dist/runtime/runtime.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { bundleFieldType, bundleSchemasCompatible, bundleValueProblem, canConnectTypes, isBundleValue, selectBundleFields, suggestName, unknownFieldMessage, validateNodeDefinition, } from "@field/cascade/contracts";
|
|
1
|
+
import { bundleFieldType, bundleSchemasCompatible, bundleValueProblem, canConnectTypes, Color, isBundleValue, selectBundleFields, suggestName, unknownFieldMessage, validateNodeDefinition, } from "@field/cascade/contracts";
|
|
2
2
|
import { CascadeRuntimeError } from "./error.js";
|
|
3
3
|
import { builtinNodeRegistrations } from "./builtins/index.js";
|
|
4
4
|
import { deserializeChannel, isEmptyChannel } from "./animation/channel.js";
|
|
5
5
|
import { PropAnimator, isBoundProp } from "./params/index.js";
|
|
6
6
|
import { bundleFieldReferences, parseBundleFieldPath, remoteBundlePath, } from "./expressions/bundlePaths.js";
|
|
7
7
|
import { DEFAULT_FPS, MIN_FRAME } from "./expressions/time.js";
|
|
8
|
-
import { isGpuHost } from "./gpu.js";
|
|
8
|
+
import { gpuLimitShortfall, isGpuHost } from "./gpu.js";
|
|
9
9
|
import { DEFAULT_GRAPH_SEED, graphSeed, nodeSeed, seededRandom } from "./seed.js";
|
|
10
10
|
/**
|
|
11
11
|
* Preflight codes that describe something other than "this graph cannot run
|
|
@@ -113,16 +113,23 @@ class MutableAbortSignal {
|
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
115
|
export function createRuntime(options) {
|
|
116
|
-
return new Runtime(options.host, [...builtinNodeRegistrations, ...(options.nodes ?? [])]
|
|
116
|
+
return new Runtime(options.host, [...builtinNodeRegistrations, ...(options.nodes ?? [])], {
|
|
117
|
+
values: options.values === "trust" ? "trust" : "freeze",
|
|
118
|
+
concurrency: Math.max(1, Math.floor(Number(options.concurrency ?? 1)) || 1),
|
|
119
|
+
...(options.nodeScope ? { nodeScope: options.nodeScope } : {}),
|
|
120
|
+
});
|
|
117
121
|
}
|
|
122
|
+
const SERIAL_EXECUTION = { values: "freeze", concurrency: 1 };
|
|
118
123
|
class Runtime {
|
|
119
124
|
host;
|
|
125
|
+
execution;
|
|
120
126
|
registrations = new Map();
|
|
121
127
|
graphs = new Set();
|
|
122
128
|
sealed = false;
|
|
123
129
|
disposed = false;
|
|
124
|
-
constructor(host, registrations) {
|
|
130
|
+
constructor(host, registrations, execution = SERIAL_EXECUTION) {
|
|
125
131
|
this.host = host;
|
|
132
|
+
this.execution = execution;
|
|
126
133
|
for (const registration of registrations)
|
|
127
134
|
this.registerNode(registration);
|
|
128
135
|
}
|
|
@@ -166,7 +173,7 @@ class Runtime {
|
|
|
166
173
|
const connections = (document.connections ?? []).map((connection, index) => materializeConnection(connection, index, byId));
|
|
167
174
|
validateCardinality(byId, connections);
|
|
168
175
|
validateTopology(nodes, connections);
|
|
169
|
-
const graph = new Graph(this.host, nodes, connections, () => this.graphs.delete(graph), document.annotations, graphSeed(document));
|
|
176
|
+
const graph = new Graph(this.host, nodes, connections, () => this.graphs.delete(graph), document.annotations, graphSeed(document), this.execution);
|
|
170
177
|
// The graph's own frame rate, saved with it; a run's `fps` still overrides.
|
|
171
178
|
const fps = document.timeline?.fps;
|
|
172
179
|
if (typeof fps === "number" && Number.isFinite(fps) && fps > 0)
|
|
@@ -188,6 +195,7 @@ class Graph {
|
|
|
188
195
|
connections;
|
|
189
196
|
onDispose;
|
|
190
197
|
seed;
|
|
198
|
+
execution;
|
|
191
199
|
graphState = "ready";
|
|
192
200
|
byId;
|
|
193
201
|
dataConnectionsByInput;
|
|
@@ -213,14 +221,20 @@ class Graph {
|
|
|
213
221
|
animator;
|
|
214
222
|
frame = MIN_FRAME;
|
|
215
223
|
fps = DEFAULT_FPS;
|
|
224
|
+
/** What happens to a value a node hands over; see `RuntimeValuePolicy`. */
|
|
225
|
+
values;
|
|
216
226
|
constructor(host, nodes, connections, onDispose, annotations = [],
|
|
217
227
|
/** The document's seed; each node's `context.seed` derives from it. */
|
|
218
|
-
seed = DEFAULT_GRAPH_SEED
|
|
228
|
+
seed = DEFAULT_GRAPH_SEED,
|
|
229
|
+
/** Value policy, concurrency, node scope; see `CreateRuntimeOptions`. */
|
|
230
|
+
execution = SERIAL_EXECUTION) {
|
|
219
231
|
this.host = host;
|
|
220
232
|
this.nodes = nodes;
|
|
221
233
|
this.connections = connections;
|
|
222
234
|
this.onDispose = onDispose;
|
|
223
235
|
this.seed = seed;
|
|
236
|
+
this.execution = execution;
|
|
237
|
+
this.values = execution.values;
|
|
224
238
|
this.byId = new Map(nodes.map((node) => [node.id, node]));
|
|
225
239
|
const byInput = new Map();
|
|
226
240
|
const byNode = new Map();
|
|
@@ -316,7 +330,7 @@ class Graph {
|
|
|
316
330
|
type: definition.kind === "trigger" ? "trigger" : definition.type,
|
|
317
331
|
connected: this.connectedOutputs.has(`${node.id}\0${name}`),
|
|
318
332
|
...(definition.kind === "data" && node.outputs.has(name)
|
|
319
|
-
? { value:
|
|
333
|
+
? { value: this.expose(node.outputs.get(name)) }
|
|
320
334
|
: {}),
|
|
321
335
|
},
|
|
322
336
|
])),
|
|
@@ -479,7 +493,15 @@ class Graph {
|
|
|
479
493
|
return () => this.listeners.delete(listener);
|
|
480
494
|
}
|
|
481
495
|
getOutput(nodeId, outputName) {
|
|
482
|
-
return
|
|
496
|
+
return this.expose(this.requireNode(nodeId).outputs.get(outputName));
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* An output value as callers see it. Under `"freeze"` every output was
|
|
500
|
+
* deep-frozen when it was set, so this is the value itself (the snapshot is
|
|
501
|
+
* a lookup); under `"trust"` it is handed out as the producer made it.
|
|
502
|
+
*/
|
|
503
|
+
expose(value) {
|
|
504
|
+
return this.values === "trust" ? value : snapshot(value);
|
|
483
505
|
}
|
|
484
506
|
getGraphOutput(name) {
|
|
485
507
|
const node = this.graphOutputs.get(name);
|
|
@@ -490,7 +512,7 @@ class Graph {
|
|
|
490
512
|
getOutputs(nodeId) {
|
|
491
513
|
return immutableMap([...this.requireNode(nodeId).outputs].map(([name, value]) => [
|
|
492
514
|
name,
|
|
493
|
-
|
|
515
|
+
this.expose(value),
|
|
494
516
|
]));
|
|
495
517
|
}
|
|
496
518
|
dispose() {
|
|
@@ -578,19 +600,23 @@ class Graph {
|
|
|
578
600
|
const ordered = topological(this.nodes, this.connections).filter((node) => initial.includes(node) && !insideFeedback.has(node.id));
|
|
579
601
|
/** Nodes that cooked in this run, for `reuse: "strict"`. */
|
|
580
602
|
const cooked = new Set();
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
603
|
+
const skip = (node) => !!reuse && this.reusable(node) && !(reuse === "strict" && this.feedsFrom(node, cooked));
|
|
604
|
+
if (this.execution.concurrency > 1)
|
|
605
|
+
await this.cookConcurrently(ordered, runId, signal, enqueue, skip, cooked);
|
|
606
|
+
else
|
|
607
|
+
for (const node of ordered) {
|
|
608
|
+
if (signal.aborted)
|
|
609
|
+
break;
|
|
610
|
+
if (skip(node)) {
|
|
611
|
+
this.emit({ type: "node:start", runId, nodeId: node.id });
|
|
612
|
+
this.emit({ type: "node:complete", runId, nodeId: node.id });
|
|
613
|
+
continue;
|
|
614
|
+
}
|
|
615
|
+
await this.executeNode(node, runId, signal, {}, enqueue);
|
|
616
|
+
cooked.add(node.id);
|
|
617
|
+
if (!signal.aborted)
|
|
618
|
+
node.lastCook = this.cookKey(node);
|
|
588
619
|
}
|
|
589
|
-
await this.executeNode(node, runId, signal, {}, enqueue);
|
|
590
|
-
cooked.add(node.id);
|
|
591
|
-
if (!signal.aborted)
|
|
592
|
-
node.lastCook = this.cookKey(node);
|
|
593
|
-
}
|
|
594
620
|
if (injection && !signal.aborted)
|
|
595
621
|
enqueue(injection.nodeId, injection.inputName, injection.payload, {
|
|
596
622
|
kind: "external",
|
|
@@ -628,7 +654,90 @@ class Graph {
|
|
|
628
654
|
this.active = undefined;
|
|
629
655
|
}
|
|
630
656
|
}
|
|
657
|
+
/**
|
|
658
|
+
* The run's main order with up to `concurrency` nodes cooking at once.
|
|
659
|
+
*
|
|
660
|
+
* Nodes START strictly in topological order: the next node starts once
|
|
661
|
+
* everything it reads has completed and a slot is free, and nothing later
|
|
662
|
+
* in the order overtakes it. So the `node:start` events — and a run trace
|
|
663
|
+
* built from them — come in the same order as a serial run on every run;
|
|
664
|
+
* only completions interleave. A Kahn order puts siblings side by side (the
|
|
665
|
+
* morph's four renders are adjacent), which is where the overlap is.
|
|
666
|
+
*
|
|
667
|
+
* A feedback container, a node with trigger ports and a GPU node cook
|
|
668
|
+
* alone: their state (the carried step, the trigger queue's sequence, one
|
|
669
|
+
* device) is ordered by the serial loop and not worth sharing.
|
|
670
|
+
*
|
|
671
|
+
* On a failure nothing new starts, the nodes already cooking finish, and
|
|
672
|
+
* the failure earliest in the order is the run's, so the message does not
|
|
673
|
+
* depend on which stage happened to answer first.
|
|
674
|
+
*/
|
|
675
|
+
async cookConcurrently(ordered, runId, signal, enqueue, skip, cooked) {
|
|
676
|
+
const position = new Map(ordered.map((node, index) => [node.id, index]));
|
|
677
|
+
const needs = new Map();
|
|
678
|
+
for (const dependency of graphDependencies(this.nodes, this.connections)) {
|
|
679
|
+
if (!position.has(dependency.sourceId) || !position.has(dependency.targetId))
|
|
680
|
+
continue;
|
|
681
|
+
const list = needs.get(dependency.targetId) ?? [];
|
|
682
|
+
list.push(dependency.sourceId);
|
|
683
|
+
needs.set(dependency.targetId, list);
|
|
684
|
+
}
|
|
685
|
+
const done = new Set();
|
|
686
|
+
const running = new Map();
|
|
687
|
+
let exclusiveRunning = false;
|
|
688
|
+
let failure;
|
|
689
|
+
let next = 0;
|
|
690
|
+
for (;;) {
|
|
691
|
+
while (!failure &&
|
|
692
|
+
!signal.aborted &&
|
|
693
|
+
!exclusiveRunning &&
|
|
694
|
+
next < ordered.length &&
|
|
695
|
+
running.size < this.execution.concurrency) {
|
|
696
|
+
const node = ordered[next];
|
|
697
|
+
if (!(needs.get(node.id) ?? []).every((id) => done.has(id)))
|
|
698
|
+
break;
|
|
699
|
+
const alone = cooksAlone(node);
|
|
700
|
+
if (alone && running.size)
|
|
701
|
+
break;
|
|
702
|
+
next++;
|
|
703
|
+
if (skip(node)) {
|
|
704
|
+
this.emit({ type: "node:start", runId, nodeId: node.id });
|
|
705
|
+
this.emit({ type: "node:complete", runId, nodeId: node.id });
|
|
706
|
+
done.add(node.id);
|
|
707
|
+
continue;
|
|
708
|
+
}
|
|
709
|
+
if (alone)
|
|
710
|
+
exclusiveRunning = true;
|
|
711
|
+
const index = position.get(node.id);
|
|
712
|
+
const task = this.executeNode(node, runId, signal, {}, enqueue).then(() => {
|
|
713
|
+
cooked.add(node.id);
|
|
714
|
+
if (!signal.aborted)
|
|
715
|
+
node.lastCook = this.cookKey(node);
|
|
716
|
+
done.add(node.id);
|
|
717
|
+
}, (error) => {
|
|
718
|
+
if (!failure || index < failure.index)
|
|
719
|
+
failure = { index, error };
|
|
720
|
+
}).finally(() => {
|
|
721
|
+
running.delete(node.id);
|
|
722
|
+
if (alone)
|
|
723
|
+
exclusiveRunning = false;
|
|
724
|
+
});
|
|
725
|
+
running.set(node.id, task);
|
|
726
|
+
}
|
|
727
|
+
if (!running.size)
|
|
728
|
+
break;
|
|
729
|
+
await Promise.race(running.values());
|
|
730
|
+
}
|
|
731
|
+
if (failure)
|
|
732
|
+
throw failure.error;
|
|
733
|
+
}
|
|
631
734
|
async executeNode(node, runId, signal, triggers, enqueue) {
|
|
735
|
+
const scope = this.execution.nodeScope;
|
|
736
|
+
if (!scope)
|
|
737
|
+
return this.cookNode(node, runId, signal, triggers, enqueue);
|
|
738
|
+
return scope(node.id, () => this.cookNode(node, runId, signal, triggers, enqueue));
|
|
739
|
+
}
|
|
740
|
+
async cookNode(node, runId, signal, triggers, enqueue) {
|
|
632
741
|
this.emit({ type: "node:start", runId, nodeId: node.id });
|
|
633
742
|
/**
|
|
634
743
|
* `cascade.core.Previous` reads the carried state off its container, the
|
|
@@ -710,7 +819,7 @@ class Graph {
|
|
|
710
819
|
const definition = node.definition.outputs?.[name];
|
|
711
820
|
if (!definition || definition.kind !== "data")
|
|
712
821
|
continue;
|
|
713
|
-
const value = outputValue(response.outputs[name], definition, `${node.id}.${name}
|
|
822
|
+
const value = outputValue(response.outputs[name], definition, `${node.id}.${name}`, this.values);
|
|
714
823
|
node.outputs.set(name, value);
|
|
715
824
|
this.emit({
|
|
716
825
|
type: "node:output",
|
|
@@ -739,7 +848,7 @@ class Graph {
|
|
|
739
848
|
}),
|
|
740
849
|
}
|
|
741
850
|
: {
|
|
742
|
-
set: (value) => pendingData.set(name, outputValue(value, definition, `${node.id}.${name}
|
|
851
|
+
set: (value) => pendingData.set(name, outputValue(value, definition, `${node.id}.${name}`, this.values)),
|
|
743
852
|
},
|
|
744
853
|
]));
|
|
745
854
|
const capabilityEntries = [];
|
|
@@ -747,7 +856,11 @@ class Graph {
|
|
|
747
856
|
const installed = this.host.capabilities[name];
|
|
748
857
|
if (name === "gpu" && isGpuHost(installed)) {
|
|
749
858
|
await installed.ensure();
|
|
750
|
-
|
|
859
|
+
const gpu = installed.forNode(node.id);
|
|
860
|
+
const shortfall = gpuLimitShortfall(node.id, node.definition.gpuLimits, gpu);
|
|
861
|
+
if (shortfall)
|
|
862
|
+
throw new Error(shortfall);
|
|
863
|
+
capabilityEntries.push([name, gpu]);
|
|
751
864
|
}
|
|
752
865
|
else {
|
|
753
866
|
capabilityEntries.push([
|
|
@@ -801,12 +914,14 @@ class Graph {
|
|
|
801
914
|
if (pendingData.has(name)) {
|
|
802
915
|
const value = pendingData.get(name);
|
|
803
916
|
node.outputs.set(name, value);
|
|
917
|
+
// The stored value is already frozen (or trusted), so the event
|
|
918
|
+
// carries it as is rather than a fresh copy nobody may read.
|
|
804
919
|
this.emit({
|
|
805
920
|
type: "node:output",
|
|
806
921
|
runId,
|
|
807
922
|
nodeId: node.id,
|
|
808
923
|
outputName: name,
|
|
809
|
-
value:
|
|
924
|
+
value: this.expose(value),
|
|
810
925
|
});
|
|
811
926
|
}
|
|
812
927
|
for (const emitted of pendingTriggers)
|
|
@@ -1262,7 +1377,8 @@ class Graph {
|
|
|
1262
1377
|
}
|
|
1263
1378
|
return Object.freeze({
|
|
1264
1379
|
result: carried,
|
|
1265
|
-
history
|
|
1380
|
+
// Recorded as settled, so reading `history` back is not a copy.
|
|
1381
|
+
history: settle(history),
|
|
1266
1382
|
});
|
|
1267
1383
|
}
|
|
1268
1384
|
finally {
|
|
@@ -1406,7 +1522,10 @@ function materialize(authored, registration) {
|
|
|
1406
1522
|
const object = saved && typeof saved === "object" && "value" in saved
|
|
1407
1523
|
? saved
|
|
1408
1524
|
: null;
|
|
1409
|
-
const
|
|
1525
|
+
const stored = object ? object.value : saved;
|
|
1526
|
+
const value = runtimeValue(
|
|
1527
|
+
// A colour saved as `{ r, g, b }` by the class image nodes reads as one.
|
|
1528
|
+
definition.type === "color" ? Color.upgrade(stored) : stored, definition.type, false, `${authored.id}.${name}`);
|
|
1410
1529
|
props[name] = value;
|
|
1411
1530
|
// A stored plain value is an author's decision and beats the declared
|
|
1412
1531
|
// default expression, so any binding seeded above has to go.
|
|
@@ -1702,6 +1821,18 @@ function snapshotRegistration(registration) {
|
|
|
1702
1821
|
const definition = snapshot(registration.definition);
|
|
1703
1822
|
return Object.freeze({ ...registration, definition });
|
|
1704
1823
|
}
|
|
1824
|
+
/** A node `cookConcurrently` never overlaps with another; see there. */
|
|
1825
|
+
function cooksAlone(node) {
|
|
1826
|
+
if (node.definition.container === "feedback")
|
|
1827
|
+
return true;
|
|
1828
|
+
if (node.definition.capabilities?.includes("gpu"))
|
|
1829
|
+
return true;
|
|
1830
|
+
const ports = [
|
|
1831
|
+
...Object.values(node.definition.inputs ?? {}),
|
|
1832
|
+
...Object.values(node.definition.outputs ?? {}),
|
|
1833
|
+
];
|
|
1834
|
+
return ports.some((port) => port.kind === "trigger");
|
|
1835
|
+
}
|
|
1705
1836
|
function isGated(node) {
|
|
1706
1837
|
return Object.values(node.definition.inputs ?? {}).some((input) => input.kind === "trigger");
|
|
1707
1838
|
}
|
|
@@ -1882,7 +2013,103 @@ function misuse(code, message) {
|
|
|
1882
2013
|
throw runtimeError(code, message);
|
|
1883
2014
|
}
|
|
1884
2015
|
function snapshot(value) {
|
|
1885
|
-
|
|
2016
|
+
const clone = cloneValue(value, new WeakSet());
|
|
2017
|
+
if (clone !== null && typeof clone === "object")
|
|
2018
|
+
settled.add(clone);
|
|
2019
|
+
return clone;
|
|
2020
|
+
}
|
|
2021
|
+
/**
|
|
2022
|
+
* Plain arrays and objects already checked and deep-frozen — by `cloneValue`
|
|
2023
|
+
* or `settle` — so a second snapshot of the same value is a lookup, not a
|
|
2024
|
+
* copy. A runtime value is read far more often than it is made: the producer
|
|
2025
|
+
* sets it once, then the event, `getOutputs`, `finish` and `inspect` each
|
|
2026
|
+
* used to clone it again (a 1M-point array four times a frame).
|
|
2027
|
+
*
|
|
2028
|
+
* Only roots and containers of objects are recorded; a leaf of primitives
|
|
2029
|
+
* (a `[x, y]` point) is cheaper to re-walk than to record a million times.
|
|
2030
|
+
*/
|
|
2031
|
+
const settled = new WeakSet();
|
|
2032
|
+
/** Plain containers copied by `cloneValue`, for the copy-count tests. */
|
|
2033
|
+
let copiedContainers = 0;
|
|
2034
|
+
/** How many plain arrays/objects the runtime has copied in this process. */
|
|
2035
|
+
export function runtimeValueCopies() {
|
|
2036
|
+
return copiedContainers;
|
|
2037
|
+
}
|
|
2038
|
+
function isPlainContainer(value) {
|
|
2039
|
+
if (Array.isArray(value))
|
|
2040
|
+
return true;
|
|
2041
|
+
const prototype = Object.getPrototypeOf(value);
|
|
2042
|
+
return prototype === Object.prototype || prototype === null;
|
|
2043
|
+
}
|
|
2044
|
+
function checkScalar(value) {
|
|
2045
|
+
if (typeof value === "number" && !Number.isFinite(value))
|
|
2046
|
+
misuse("runtime/non-serializable-value", "Runtime numbers must be finite");
|
|
2047
|
+
if (typeof value === "function" ||
|
|
2048
|
+
typeof value === "symbol" ||
|
|
2049
|
+
typeof value === "bigint")
|
|
2050
|
+
misuse("runtime/non-serializable-value", `Runtime values cannot contain ${typeof value}`);
|
|
2051
|
+
}
|
|
2052
|
+
/**
|
|
2053
|
+
* Check a value a node handed over and deep-freeze it where it stands: the
|
|
2054
|
+
* same rules as `cloneValue` (finite numbers, no functions, no cycles, class
|
|
2055
|
+
* instances and typed arrays passed through untouched), without allocating.
|
|
2056
|
+
* The producer gave it away with `set()`; freezing is what keeps the
|
|
2057
|
+
* consumers' view of it from changing under them.
|
|
2058
|
+
*/
|
|
2059
|
+
function settle(value) {
|
|
2060
|
+
if (value === null || typeof value !== "object") {
|
|
2061
|
+
checkScalar(value);
|
|
2062
|
+
return value;
|
|
2063
|
+
}
|
|
2064
|
+
if (!settled.has(value) && isPlainContainer(value)) {
|
|
2065
|
+
settleContainer(value, new WeakSet());
|
|
2066
|
+
settled.add(value);
|
|
2067
|
+
}
|
|
2068
|
+
return value;
|
|
2069
|
+
}
|
|
2070
|
+
/** Returns whether `value` held any plain container (and so was recorded). */
|
|
2071
|
+
function settleContainer(value, ancestors) {
|
|
2072
|
+
let nested = false;
|
|
2073
|
+
const visit = (item) => {
|
|
2074
|
+
if (item === null || typeof item !== "object") {
|
|
2075
|
+
checkScalar(item);
|
|
2076
|
+
return;
|
|
2077
|
+
}
|
|
2078
|
+
if (settled.has(item) || !isPlainContainer(item))
|
|
2079
|
+
return;
|
|
2080
|
+
if (ancestors.has(item))
|
|
2081
|
+
misuse("runtime/non-serializable-value", "Runtime values cannot contain cycles");
|
|
2082
|
+
if (!nested) {
|
|
2083
|
+
nested = true;
|
|
2084
|
+
ancestors.add(value);
|
|
2085
|
+
}
|
|
2086
|
+
if (settleContainer(item, ancestors))
|
|
2087
|
+
settled.add(item);
|
|
2088
|
+
};
|
|
2089
|
+
if (Array.isArray(value))
|
|
2090
|
+
for (let index = 0; index < value.length; index++)
|
|
2091
|
+
visit(value[index]);
|
|
2092
|
+
else
|
|
2093
|
+
for (const key of Object.keys(value))
|
|
2094
|
+
visit(value[key]);
|
|
2095
|
+
if (nested)
|
|
2096
|
+
ancestors.delete(value);
|
|
2097
|
+
Object.freeze(value);
|
|
2098
|
+
return nested;
|
|
2099
|
+
}
|
|
2100
|
+
/**
|
|
2101
|
+
* `"trust"`: the top level is checked by `runtimeValue`'s type test and
|
|
2102
|
+
* frozen shallowly, so `push` or `sort` on a handed-over array still throws;
|
|
2103
|
+
* nested contents are not walked.
|
|
2104
|
+
*/
|
|
2105
|
+
function trust(value) {
|
|
2106
|
+
if (value === null || typeof value !== "object") {
|
|
2107
|
+
checkScalar(value);
|
|
2108
|
+
return value;
|
|
2109
|
+
}
|
|
2110
|
+
if (isPlainContainer(value))
|
|
2111
|
+
Object.freeze(value);
|
|
2112
|
+
return value;
|
|
1886
2113
|
}
|
|
1887
2114
|
function immutableMap(entries) {
|
|
1888
2115
|
const source = new Map(entries);
|
|
@@ -1902,22 +2129,15 @@ function immutableMap(entries) {
|
|
|
1902
2129
|
return view;
|
|
1903
2130
|
}
|
|
1904
2131
|
function cloneValue(value, ancestors) {
|
|
1905
|
-
|
|
1906
|
-
misuse("runtime/non-serializable-value", "Runtime numbers must be finite");
|
|
1907
|
-
if (typeof value === "function" ||
|
|
1908
|
-
typeof value === "symbol" ||
|
|
1909
|
-
typeof value === "bigint")
|
|
1910
|
-
misuse("runtime/non-serializable-value", `Runtime values cannot contain ${typeof value}`);
|
|
2132
|
+
checkScalar(value);
|
|
1911
2133
|
if (value === undefined || value === null || typeof value !== "object")
|
|
1912
2134
|
return value;
|
|
1913
|
-
|
|
1914
|
-
if (!Array.isArray(value) &&
|
|
1915
|
-
prototype !== Object.prototype &&
|
|
1916
|
-
prototype !== null)
|
|
2135
|
+
if (settled.has(value) || !isPlainContainer(value))
|
|
1917
2136
|
return value;
|
|
1918
2137
|
if (ancestors.has(value))
|
|
1919
2138
|
misuse("runtime/non-serializable-value", "Runtime values cannot contain cycles");
|
|
1920
2139
|
ancestors.add(value);
|
|
2140
|
+
copiedContainers++;
|
|
1921
2141
|
const clone = Array.isArray(value)
|
|
1922
2142
|
? value.map((item) => cloneValue(item, ancestors))
|
|
1923
2143
|
: Object.fromEntries(Object.entries(value).map(([key, item]) => [
|
|
@@ -1943,17 +2163,19 @@ const TUPLE_LENGTHS = {
|
|
|
1943
2163
|
* An output value, checked against its declaration. A bundle with declared
|
|
1944
2164
|
* fields may only set those, and each one must be a valid value of its type.
|
|
1945
2165
|
*/
|
|
1946
|
-
function outputValue(value, definition, path) {
|
|
2166
|
+
function outputValue(value, definition, path, policy) {
|
|
2167
|
+
// Handed over by the producer, so adopted (frozen where it stands), not copied.
|
|
2168
|
+
const adopt = policy === "trust" ? trust : settle;
|
|
1947
2169
|
if (definition.type !== "bundle" || !definition.fields)
|
|
1948
|
-
return runtimeValue(value, definition.type, false, path);
|
|
2170
|
+
return runtimeValue(value, definition.type, false, path, adopt);
|
|
1949
2171
|
const fields = definition.fields;
|
|
1950
2172
|
const problem = bundleValueProblem(value, fields);
|
|
1951
2173
|
if (problem)
|
|
1952
2174
|
misuse("runtime/value-type", `${path} ${problem}`);
|
|
1953
2175
|
if (isBundleValue(value))
|
|
1954
2176
|
for (const [field, item] of Object.entries(value))
|
|
1955
|
-
runtimeValue(item, bundleFieldType(fields[field]), true, `${path}.${field}
|
|
1956
|
-
return runtimeValue(value, "bundle", false, path);
|
|
2177
|
+
runtimeValue(item, bundleFieldType(fields[field]), true, `${path}.${field}`, (checked) => checked);
|
|
2178
|
+
return runtimeValue(value, "bundle", false, path, adopt);
|
|
1957
2179
|
}
|
|
1958
2180
|
/** Every path an expression names through ch/chs/chv. */
|
|
1959
2181
|
function expressionPaths(expression) {
|
|
@@ -1975,7 +2197,9 @@ function readsBundleFields(node) {
|
|
|
1975
2197
|
}
|
|
1976
2198
|
return false;
|
|
1977
2199
|
}
|
|
1978
|
-
function runtimeValue(value, type, allowUndefined, path
|
|
2200
|
+
function runtimeValue(value, type, allowUndefined, path,
|
|
2201
|
+
/** How a checked value is kept: copied (a caller's value) by default. */
|
|
2202
|
+
keep = snapshot) {
|
|
1979
2203
|
if (value === undefined) {
|
|
1980
2204
|
if (allowUndefined)
|
|
1981
2205
|
return undefined;
|
|
@@ -2001,7 +2225,7 @@ function runtimeValue(value, type, allowUndefined, path) {
|
|
|
2001
2225
|
type !== "string");
|
|
2002
2226
|
if (!valid)
|
|
2003
2227
|
misuse("runtime/value-type", `${path} is not a valid ${type}`);
|
|
2004
|
-
return
|
|
2228
|
+
return keep(value);
|
|
2005
2229
|
}
|
|
2006
2230
|
function sameValues(a, b, equal) {
|
|
2007
2231
|
return a.length === b.length && a.every((value, index) => equal(value, b[index]));
|
package/dist/runtime/types.d.ts
CHANGED
|
@@ -216,9 +216,39 @@ export type RuntimeEvent = {
|
|
|
216
216
|
readonly type: "run:complete";
|
|
217
217
|
readonly result: RunResult;
|
|
218
218
|
};
|
|
219
|
+
/**
|
|
220
|
+
* What the runtime does with a value a node hands to `outputs.x.set()`.
|
|
221
|
+
*
|
|
222
|
+
* - `"freeze"` (default): checked and deep-frozen in place, never copied. A
|
|
223
|
+
* node that mutates its output after handing it over, or mutates an input,
|
|
224
|
+
* throws instead of corrupting another node's view.
|
|
225
|
+
* - `"trust"`: checked at the top level only and shallow-frozen; nested
|
|
226
|
+
* contents are the producer's word. For headless runs (`cascade run`), where
|
|
227
|
+
* a 1M-point array walked every frame was most of the frame.
|
|
228
|
+
*
|
|
229
|
+
* Either way nothing is cloned on the way through: reading an output back
|
|
230
|
+
* (`getOutputs`, `inspect`, `node:output`) hands out the same frozen value.
|
|
231
|
+
*/
|
|
232
|
+
export type RuntimeValuePolicy = "freeze" | "trust";
|
|
219
233
|
export interface CreateRuntimeOptions {
|
|
220
234
|
readonly host: RuntimeHost;
|
|
221
235
|
readonly nodes?: readonly NodeRegistration[];
|
|
236
|
+
/** See `RuntimeValuePolicy`. Default `"freeze"`. */
|
|
237
|
+
readonly values?: RuntimeValuePolicy;
|
|
238
|
+
/**
|
|
239
|
+
* How many nodes one run may cook at once (default 1: one after another).
|
|
240
|
+
* Nodes still START in topological order, a node only once everything it
|
|
241
|
+
* reads has completed, so the `node:start` sequence and the outputs are the
|
|
242
|
+
* same as a serial run; only completions interleave. Feedback containers,
|
|
243
|
+
* trigger-wired and GPU nodes always cook alone.
|
|
244
|
+
*/
|
|
245
|
+
readonly concurrency?: number;
|
|
246
|
+
/**
|
|
247
|
+
* Wraps each node's execute. A Node host uses it to carry the node id
|
|
248
|
+
* through the cook's async work (AsyncLocalStorage), so a stage call or a
|
|
249
|
+
* log line is attributed to its node when several cook at once.
|
|
250
|
+
*/
|
|
251
|
+
readonly nodeScope?: <T>(nodeId: string, run: () => T) => T;
|
|
222
252
|
}
|
|
223
253
|
export interface CascadeRuntime {
|
|
224
254
|
registerNode(registration: NodeRegistration): void;
|
package/dist/stage.js
CHANGED
|
@@ -53,7 +53,10 @@ async function overHttp(stage, args, signal) {
|
|
|
53
53
|
const postStage = async () => fetch("/api/exec", {
|
|
54
54
|
method: "POST",
|
|
55
55
|
headers: { "Content-Type": "application/json", "X-Cascade-Exec-Capability": await capability() },
|
|
56
|
-
|
|
56
|
+
// `cook`: a node's cook is a pure function of its inputs, so the server's
|
|
57
|
+
// default stage cache may serve it (see StageCachePolicy). A panel posts
|
|
58
|
+
// through its own api.runStage, without this, and is never cached unasked.
|
|
59
|
+
body: JSON.stringify({ stage, args, caller: "cook" }),
|
|
57
60
|
signal
|
|
58
61
|
});
|
|
59
62
|
let response;
|