@field/cascade 0.2.22 → 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/AGENTS.md +47 -7
- package/ARCHITECTURE.md +30 -2
- package/CHANGELOG.md +89 -0
- package/DESIGN.md +10 -5
- package/README.md +103 -7
- package/dist/assets/CodeEditor-BADFbTJS.js +623 -0
- package/dist/assets/CodePanel-faK6PRfz.js +2 -0
- package/dist/assets/CustomNodeDialog-DhlKuviy.js +1 -0
- package/dist/assets/{ExportDialog-DWN4nhXI.js → ExportDialog-BJu2Bpvv.js} +2 -2
- package/dist/assets/ProjectSettings-D6a6kjqR.js +2 -0
- package/dist/assets/SettingsDialog-DJZdIvLC.js +1 -0
- package/dist/assets/VersionHistoryDialog-C1X3HOhc.js +3 -0
- package/dist/assets/{core-Bji4aZUU.js → core-CiuiuWZi.js} +29 -38
- package/dist/assets/{cssMode-B6Z_Q1Kh.js → cssMode-D-NwZSMJ.js} +1 -1
- package/dist/assets/{editor.api-jMoW_43X.js → editor.api-DT3WEvBd.js} +1 -1
- package/dist/assets/{freemarker2-DvvPV6m5.js → freemarker2-eKNYQaiz.js} +1 -1
- package/dist/assets/geo-DUvVbLnx.js +1 -0
- package/dist/assets/{handlebars-B8rfuSIa.js → handlebars-C2w1ggK2.js} +1 -1
- package/dist/assets/{html-DyHm6JCp.js → html-DQb41jyo.js} +1 -1
- package/dist/assets/{htmlMode-E9XLWE5u.js → htmlMode-IhNlh6Lv.js} +1 -1
- package/dist/assets/{image-BUcQC7PG.js → image-DMCXBY-_.js} +26 -40
- package/dist/assets/index-BDV5xGBZ.css +1 -0
- package/dist/assets/index-HTCN44kj.js +212 -0
- package/dist/assets/index-client-BoJgsN9M.js +1 -0
- package/dist/assets/{javascript-C_bM5PCN.js → javascript-BIT6jMLC.js} +1 -1
- package/dist/assets/{jsonMode-BdyVS7G4.js → jsonMode-CNfyB1ro.js} +1 -1
- package/dist/assets/{legacy-DIu3XRwl.js → legacy-CAj2_7QE.js} +2 -2
- package/dist/assets/{liquid-CDkn3spA.js → liquid-CmSmJmOG.js} +1 -1
- package/dist/assets/{lspLanguageFeatures-AB5DMAsr.js → lspLanguageFeatures-BqMm3uMP.js} +1 -1
- package/dist/assets/{mdx-BvH_NjDN.js → mdx-6r6YgOFw.js} +1 -1
- package/dist/assets/{nodeTypeUtils-CweA3NHd.js → nodeTypeUtils-BBA1JqHx.js} +1 -1
- package/dist/assets/{python-B7uii4fi.js → python-x1H0TQuI.js} +1 -1
- package/dist/assets/{razor-Z48fWdJc.js → razor-Dzl1vZFm.js} +1 -1
- package/dist/assets/{tsMode-BDgacVG4.js → tsMode-n6DMMe3a.js} +1 -1
- package/dist/assets/{typescript-Derb6Oqa.js → typescript-ziEz65xN.js} +1 -1
- package/dist/assets/{workers-BtoMX_pm.js → workers-D-qUdp-r.js} +1 -1
- package/dist/assets/{xml-B1CaFSrG.js → xml-DMZjt5lE.js} +1 -1
- package/dist/assets/{yaml-HmIeA0KH.js → yaml-DFe5qZSO.js} +1 -1
- package/dist/cli/index.js +16194 -10195
- package/dist/cli/runtime/io.ts +93 -7
- package/dist/contracts/animation.d.ts +40 -0
- package/dist/contracts/animation.js +14 -0
- package/dist/contracts/definition.d.ts +39 -1
- package/dist/contracts/document.d.ts +15 -0
- package/dist/contracts/index.d.ts +1 -0
- package/dist/contracts/index.js +1 -0
- package/dist/contracts/validate.d.ts +10 -1
- package/dist/contracts/validate.js +9 -1
- package/dist/graphs/default.cascade +1 -1
- package/dist/index.html +5 -5
- package/dist/io.d.ts +196 -0
- package/dist/io.js +303 -0
- package/dist/net.d.ts +1 -0
- package/dist/net.js +28 -0
- package/dist/runtime/animation/channel.d.ts +108 -0
- package/dist/runtime/animation/channel.js +204 -0
- package/dist/runtime/animation/frameRange.d.ts +78 -0
- package/dist/runtime/animation/frameRange.js +76 -0
- package/dist/runtime/animation/index.d.ts +10 -0
- package/dist/runtime/animation/index.js +8 -0
- package/dist/runtime/builtins/core/random.d.ts +1 -1
- package/dist/runtime/builtins/core/remap.d.ts +1 -1
- package/dist/runtime/builtins/geo/copyToPoints.d.ts +1 -1
- package/dist/runtime/builtins/geo/copyToPoints.js +1 -1
- package/dist/runtime/builtins/geo/generators.d.ts +1 -1
- package/dist/runtime/builtins/geo/merge.d.ts +1 -1
- package/dist/runtime/builtins/geo/svgExport.d.ts +1 -1
- package/dist/runtime/builtins/geo/svgExport.js +1 -1
- package/dist/runtime/builtins/geo/transform.d.ts +1 -1
- package/dist/runtime/builtins/geo/transform.js +1 -1
- package/dist/runtime/definition/architecture.d.ts +1 -1
- package/dist/runtime/definition/extract.d.ts +1 -1
- package/dist/runtime/definition/extract.js +1 -1
- package/dist/runtime/error.d.ts +1 -1
- package/dist/runtime/expressions/evaluator.d.ts +53 -0
- package/dist/runtime/expressions/evaluator.js +193 -0
- package/dist/runtime/expressions/index.d.ts +15 -0
- package/dist/runtime/expressions/index.js +12 -0
- package/dist/runtime/expressions/math.d.ts +75 -0
- package/dist/runtime/expressions/math.js +140 -0
- package/dist/runtime/expressions/preprocess.d.ts +33 -0
- package/dist/runtime/expressions/preprocess.js +102 -0
- package/dist/runtime/expressions/scope.d.ts +27 -0
- package/dist/runtime/expressions/scope.js +10 -0
- package/dist/runtime/expressions/time.d.ts +54 -0
- package/dist/runtime/expressions/time.js +71 -0
- package/dist/runtime/expressions/types.d.ts +66 -0
- package/dist/runtime/expressions/types.js +9 -0
- package/dist/runtime/geometry/attributes.d.ts +2 -2
- package/dist/runtime/geometry/attributes.js +1 -1
- package/dist/runtime/geometry/builder.d.ts +1 -1
- package/dist/runtime/geometry/builder.js +1 -1
- package/dist/runtime/geometry/copy.d.ts +1 -1
- package/dist/runtime/geometry/copy.js +1 -1
- package/dist/runtime/geometry/generate.d.ts +1 -1
- package/dist/runtime/geometry/groups.d.ts +1 -1
- package/dist/runtime/geometry/groups.js +1 -1
- package/dist/runtime/geometry/matrix.d.ts +1 -1
- package/dist/runtime/geometry/merge.d.ts +1 -1
- package/dist/runtime/geometry/merge.js +1 -1
- package/dist/runtime/geometry/primitives.d.ts +1 -1
- package/dist/runtime/geometry/primitives.js +1 -1
- package/dist/runtime/geometry/promote.d.ts +1 -1
- package/dist/runtime/geometry/promote.js +1 -1
- package/dist/runtime/geometry/resample.d.ts +1 -1
- package/dist/runtime/geometry/resample.js +1 -1
- package/dist/runtime/geometry/svg.d.ts +1 -1
- package/dist/runtime/geometry/svg.js +1 -1
- package/dist/runtime/geometry/transform.d.ts +1 -1
- package/dist/runtime/geometry/transform.js +1 -1
- package/dist/runtime/index.d.ts +4 -1
- package/dist/runtime/index.js +4 -1
- package/dist/runtime/params/animator.d.ts +74 -0
- package/dist/runtime/params/animator.js +137 -0
- package/dist/runtime/params/index.d.ts +11 -0
- package/dist/runtime/params/index.js +9 -0
- package/dist/runtime/params/resolve.d.ts +73 -0
- package/dist/runtime/params/resolve.js +62 -0
- package/dist/runtime/runtime.d.ts +22 -0
- package/dist/runtime/runtime.js +242 -14
- package/dist/runtime/types.d.ts +30 -4
- package/dist/shell.d.ts +3 -3
- package/doc/NODE_REFERENCE.md +395 -0
- package/doc/PROJECT_AUTHORING.md +148 -6
- package/package.json +26 -3
- package/dist/assets/CodeEditor-DoaNEUaM.js +0 -641
- package/dist/assets/CodePanel-B0_2XJ-t.js +0 -2
- package/dist/assets/CustomNodeDialog-026lzecD.js +0 -1
- package/dist/assets/ProjectSettings-BzT164Yx.js +0 -2
- package/dist/assets/SettingsDialog-DmHb1V0r.js +0 -1
- package/dist/assets/VersionHistoryDialog-hH6VsXLX.js +0 -3
- package/dist/assets/geo-CXmCtzMM.js +0 -2
- package/dist/assets/index-BXYXSdgh.js +0 -224
- package/dist/assets/index-CrNke7SW.css +0 -1
- package/dist/assets/index-client-DN_FvjN8.js +0 -1
- package/dist/assets/quill-DjZ69wL_.js +0 -121
- package/dist/cli/index.js.map +0 -7
- package/dist/contracts/capabilities.d.ts.map +0 -1
- package/dist/contracts/capabilities.js.map +0 -1
- package/dist/contracts/color.d.ts.map +0 -1
- package/dist/contracts/color.js.map +0 -1
- package/dist/contracts/definition.d.ts.map +0 -1
- package/dist/contracts/definition.js.map +0 -1
- package/dist/contracts/document.d.ts.map +0 -1
- package/dist/contracts/document.js.map +0 -1
- package/dist/contracts/geometry.d.ts.map +0 -1
- package/dist/contracts/geometry.js.map +0 -1
- package/dist/contracts/index.d.ts.map +0 -1
- package/dist/contracts/index.js.map +0 -1
- package/dist/contracts/schema.d.ts.map +0 -1
- package/dist/contracts/schema.js.map +0 -1
- package/dist/contracts/validate.d.ts.map +0 -1
- package/dist/contracts/validate.js.map +0 -1
- package/dist/contracts/values.d.ts.map +0 -1
- package/dist/contracts/values.js.map +0 -1
- package/dist/graphs/default-orig.cascade +0 -128
- package/dist/runtime/browser.d.ts.map +0 -1
- package/dist/runtime/browser.js.map +0 -1
- package/dist/runtime/builtins/core/index.d.ts.map +0 -1
- package/dist/runtime/builtins/core/index.js.map +0 -1
- package/dist/runtime/builtins/core/random.d.ts.map +0 -1
- package/dist/runtime/builtins/core/random.js.map +0 -1
- package/dist/runtime/builtins/core/remap.d.ts.map +0 -1
- package/dist/runtime/builtins/core/remap.js.map +0 -1
- package/dist/runtime/builtins/core/routing.d.ts.map +0 -1
- package/dist/runtime/builtins/core/routing.js.map +0 -1
- package/dist/runtime/builtins/core/structural.d.ts.map +0 -1
- package/dist/runtime/builtins/core/structural.js.map +0 -1
- package/dist/runtime/builtins/geo/copyToPoints.d.ts.map +0 -1
- package/dist/runtime/builtins/geo/copyToPoints.js.map +0 -1
- package/dist/runtime/builtins/geo/definitions.d.ts.map +0 -1
- package/dist/runtime/builtins/geo/definitions.js.map +0 -1
- package/dist/runtime/builtins/geo/generators.d.ts.map +0 -1
- package/dist/runtime/builtins/geo/generators.js.map +0 -1
- package/dist/runtime/builtins/geo/index.d.ts.map +0 -1
- package/dist/runtime/builtins/geo/index.js.map +0 -1
- package/dist/runtime/builtins/geo/merge.d.ts.map +0 -1
- package/dist/runtime/builtins/geo/merge.js.map +0 -1
- package/dist/runtime/builtins/geo/namespace.d.ts.map +0 -1
- package/dist/runtime/builtins/geo/namespace.js.map +0 -1
- package/dist/runtime/builtins/geo/svgExport.d.ts.map +0 -1
- package/dist/runtime/builtins/geo/svgExport.js.map +0 -1
- package/dist/runtime/builtins/geo/transform.d.ts.map +0 -1
- package/dist/runtime/builtins/geo/transform.js.map +0 -1
- package/dist/runtime/builtins/index.d.ts.map +0 -1
- package/dist/runtime/builtins/index.js.map +0 -1
- package/dist/runtime/definition/architecture.d.ts.map +0 -1
- package/dist/runtime/definition/architecture.js.map +0 -1
- package/dist/runtime/definition/extract.d.ts.map +0 -1
- package/dist/runtime/definition/extract.js.map +0 -1
- package/dist/runtime/error.d.ts.map +0 -1
- package/dist/runtime/error.js.map +0 -1
- package/dist/runtime/geometry/attributes.d.ts.map +0 -1
- package/dist/runtime/geometry/attributes.js.map +0 -1
- package/dist/runtime/geometry/builder.d.ts.map +0 -1
- package/dist/runtime/geometry/builder.js.map +0 -1
- package/dist/runtime/geometry/copy.d.ts.map +0 -1
- package/dist/runtime/geometry/copy.js.map +0 -1
- package/dist/runtime/geometry/generate.d.ts.map +0 -1
- package/dist/runtime/geometry/generate.js.map +0 -1
- package/dist/runtime/geometry/groups.d.ts.map +0 -1
- package/dist/runtime/geometry/groups.js.map +0 -1
- package/dist/runtime/geometry/index.d.ts.map +0 -1
- package/dist/runtime/geometry/index.js.map +0 -1
- package/dist/runtime/geometry/matrix.d.ts.map +0 -1
- package/dist/runtime/geometry/matrix.js.map +0 -1
- package/dist/runtime/geometry/merge.d.ts.map +0 -1
- package/dist/runtime/geometry/merge.js.map +0 -1
- package/dist/runtime/geometry/primitives.d.ts.map +0 -1
- package/dist/runtime/geometry/primitives.js.map +0 -1
- package/dist/runtime/geometry/promote.d.ts.map +0 -1
- package/dist/runtime/geometry/promote.js.map +0 -1
- package/dist/runtime/geometry/resample.d.ts.map +0 -1
- package/dist/runtime/geometry/resample.js.map +0 -1
- package/dist/runtime/geometry/svg.d.ts.map +0 -1
- package/dist/runtime/geometry/svg.js.map +0 -1
- package/dist/runtime/geometry/transform.d.ts.map +0 -1
- package/dist/runtime/geometry/transform.js.map +0 -1
- package/dist/runtime/index.d.ts.map +0 -1
- package/dist/runtime/index.js.map +0 -1
- package/dist/runtime/node.d.ts.map +0 -1
- package/dist/runtime/node.js.map +0 -1
- package/dist/runtime/runtime.d.ts.map +0 -1
- package/dist/runtime/runtime.js.map +0 -1
- package/dist/runtime/types.d.ts.map +0 -1
- package/dist/runtime/types.js.map +0 -1
- package/dist/shell.js.map +0 -7
- package/dist/stage.js.map +0 -7
package/dist/runtime/runtime.js
CHANGED
|
@@ -1,6 +1,34 @@
|
|
|
1
|
-
import { canConnectTypes, validateNodeDefinition, } from "cascade/contracts";
|
|
1
|
+
import { canConnectTypes, validateNodeDefinition, } from "@field/cascade/contracts";
|
|
2
2
|
import { CascadeRuntimeError } from "./error.js";
|
|
3
3
|
import { builtinNodeRegistrations } from "./builtins/index.js";
|
|
4
|
+
import { deserializeChannel, isEmptyChannel } from "./animation/channel.js";
|
|
5
|
+
import { PropAnimator, isBoundProp } from "./params/index.js";
|
|
6
|
+
import { DEFAULT_FPS, MIN_FRAME } from "./expressions/time.js";
|
|
7
|
+
/**
|
|
8
|
+
* Preflight codes that describe something other than "this graph cannot run
|
|
9
|
+
* here", so `run` proceeds and a checker reports them as warnings.
|
|
10
|
+
*
|
|
11
|
+
* False positives are worse than blindness: a validator that cries wolf gets
|
|
12
|
+
* ignored, and then it is no better than the one that saw nothing. So the line
|
|
13
|
+
* is drawn at what the diagnostic is about.
|
|
14
|
+
*
|
|
15
|
+
* `preflight-environment` is about the checker's reach — a node saying
|
|
16
|
+
* `runsOn: 'browser'` claims nothing about the CLI and is not defective
|
|
17
|
+
* because somebody validated it from a terminal.
|
|
18
|
+
*
|
|
19
|
+
* `stray-params` is about the document — values were lost on the way in, which
|
|
20
|
+
* is worth saying loudly and is not a reason to refuse to run: a graph with a
|
|
21
|
+
* leftover `params` array runs perfectly well on its defaults, which is
|
|
22
|
+
* precisely the problem.
|
|
23
|
+
*
|
|
24
|
+
* This set lives here rather than in the CLI because the run path and the
|
|
25
|
+
* check path both need it, and it was a duplicated rule that let `validate`
|
|
26
|
+
* and `check` pass graphs `run` then rejected.
|
|
27
|
+
*/
|
|
28
|
+
export const PREFLIGHT_WARNING_CODES = new Set([
|
|
29
|
+
"runtime/preflight-environment",
|
|
30
|
+
"runtime/stray-params",
|
|
31
|
+
]);
|
|
4
32
|
class MutableAbortSignal {
|
|
5
33
|
report;
|
|
6
34
|
aborted = false;
|
|
@@ -129,6 +157,16 @@ class Graph {
|
|
|
129
157
|
authoredAnnotations;
|
|
130
158
|
graphInputs;
|
|
131
159
|
graphOutputs;
|
|
160
|
+
/**
|
|
161
|
+
* The graph's animated parameters, and the clock they resolve against.
|
|
162
|
+
*
|
|
163
|
+
* The clock is a value the caller sets per run (`run({ frame })`), never one
|
|
164
|
+
* this graph reads: same graph plus same frame gives the same output, which
|
|
165
|
+
* is the only reason a headless render can be trusted.
|
|
166
|
+
*/
|
|
167
|
+
animator;
|
|
168
|
+
frame = MIN_FRAME;
|
|
169
|
+
fps = DEFAULT_FPS;
|
|
132
170
|
constructor(host, nodes, connections, onDispose, annotations = []) {
|
|
133
171
|
this.host = host;
|
|
134
172
|
this.nodes = nodes;
|
|
@@ -138,10 +176,47 @@ class Graph {
|
|
|
138
176
|
this.graphInputs = graphBoundaries(nodes, "cascade.core.Input", "inputName");
|
|
139
177
|
this.graphOutputs = graphBoundaries(nodes, "cascade.core.Output", "outputName");
|
|
140
178
|
this.authoredAnnotations = Object.freeze(annotations.map((annotation) => snapshot(annotation)));
|
|
179
|
+
this.animator = new PropAnimator({
|
|
180
|
+
nodes,
|
|
181
|
+
node: (id) => this.byId.get(id),
|
|
182
|
+
inputNode: (node, index) => {
|
|
183
|
+
const feeding = this.connections
|
|
184
|
+
.filter((connection) => connection.kind === "data" &&
|
|
185
|
+
connection.target.nodeId === node.id)
|
|
186
|
+
.sort((a, b) => a.order - b.order);
|
|
187
|
+
const source = feeding[index]?.source.nodeId;
|
|
188
|
+
return (source ? this.byId.get(source) : undefined) ?? null;
|
|
189
|
+
},
|
|
190
|
+
// A failed expression has already fallen back to the stored value, so it
|
|
191
|
+
// is a diagnostic rather than a stopped run — but it is never silent: a
|
|
192
|
+
// parameter that quietly reverts is the hardest fault to see in a
|
|
193
|
+
// hundred rendered frames.
|
|
194
|
+
report: (nodeId, propName, error) => this.reportDiagnostic({
|
|
195
|
+
phase: "run",
|
|
196
|
+
code: "runtime/expression-failed",
|
|
197
|
+
message: `${nodeId}.${propName}: ${error}`,
|
|
198
|
+
path: `${nodeId}.${propName}`,
|
|
199
|
+
}),
|
|
200
|
+
});
|
|
201
|
+
this.animator.setFps(this.fps);
|
|
202
|
+
this.animator.setFrame(this.frame);
|
|
203
|
+
// Resolve once at load, so `inspect()` and a preset applied before any run
|
|
204
|
+
// both see the parameter at the graph's own first frame rather than the
|
|
205
|
+
// number that was last saved.
|
|
206
|
+
this.animator.resolveAll();
|
|
141
207
|
}
|
|
142
208
|
get state() {
|
|
143
209
|
return this.graphState;
|
|
144
210
|
}
|
|
211
|
+
/**
|
|
212
|
+
* Everything that would stop this host running the graph, without running
|
|
213
|
+
* it: a node whose environment this host is not, a bridged node that cannot
|
|
214
|
+
* be bridged, a declared capability this host does not install. Empty means
|
|
215
|
+
* the graph clears the same gate `run` puts it through.
|
|
216
|
+
*/
|
|
217
|
+
preflight() {
|
|
218
|
+
return this.preflightDiagnostics(this.nodes);
|
|
219
|
+
}
|
|
145
220
|
inspect() {
|
|
146
221
|
const nodes = this.nodes.map((node) => ({
|
|
147
222
|
id: node.id,
|
|
@@ -212,7 +287,26 @@ class Graph {
|
|
|
212
287
|
misuse("runtime/structural-prop", `${nodeId}.${propName} is structural and requires a graph reload`);
|
|
213
288
|
if (!node.definition.props?.[propName])
|
|
214
289
|
misuse("runtime/invalid-prop", `${nodeId}.${propName} is not a prop`);
|
|
215
|
-
|
|
290
|
+
const resolved = runtimeValue(value, node.definition.props[propName].type, false, `${nodeId}.${propName}`);
|
|
291
|
+
node.props[propName] = resolved;
|
|
292
|
+
this.rebind(node, propName, resolved);
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Keep a bound prop's STORED value in step with a direct write.
|
|
296
|
+
*
|
|
297
|
+
* A channel or an expression still wins on the next resolve — setting a
|
|
298
|
+
* keyed parameter's value does not delete its keys, in Studio or here — but
|
|
299
|
+
* the value written is the one the parameter falls back to, so dropping it
|
|
300
|
+
* would make a later `clearChannel` reinstate a number nobody chose.
|
|
301
|
+
*/
|
|
302
|
+
rebind(node, propName, value) {
|
|
303
|
+
const binding = node.bindings[propName];
|
|
304
|
+
if (binding)
|
|
305
|
+
node.bindings[propName] = { ...binding, value };
|
|
306
|
+
}
|
|
307
|
+
reportDiagnostic(diagnostic) {
|
|
308
|
+
this.diagnostics.push(diagnostic);
|
|
309
|
+
this.emit({ type: "diagnostic", diagnostic });
|
|
216
310
|
}
|
|
217
311
|
async applyPreset(preset) {
|
|
218
312
|
this.requireReady();
|
|
@@ -247,10 +341,50 @@ class Graph {
|
|
|
247
341
|
}
|
|
248
342
|
for (const [node, name, value] of inputDrafts)
|
|
249
343
|
node.inputs[name] = value;
|
|
250
|
-
for (const [node, name, value] of propDrafts)
|
|
344
|
+
for (const [node, name, value] of propDrafts) {
|
|
251
345
|
node.props[name] = value;
|
|
346
|
+
this.rebind(node, name, value);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* The frame this graph is cooking at, and the rate frames are counted in.
|
|
351
|
+
*
|
|
352
|
+
* Separate from `run` so a caller can scrub without cooking — and so that
|
|
353
|
+
* `setFrame` then `inspect()` reports a keyed parameter at that frame, which
|
|
354
|
+
* is what a panel needs and what `run` alone cannot give it.
|
|
355
|
+
*/
|
|
356
|
+
setFrame(frame) {
|
|
357
|
+
if (!Number.isFinite(frame))
|
|
358
|
+
return;
|
|
359
|
+
this.frame = Math.max(MIN_FRAME, frame);
|
|
360
|
+
this.animator.setFrame(this.frame);
|
|
361
|
+
this.animator.resolveAll();
|
|
362
|
+
}
|
|
363
|
+
setFps(fps) {
|
|
364
|
+
if (!Number.isFinite(fps))
|
|
365
|
+
return;
|
|
366
|
+
this.fps = Math.max(1, fps);
|
|
367
|
+
this.animator.setFps(this.fps);
|
|
368
|
+
this.animator.resolveAll();
|
|
369
|
+
}
|
|
370
|
+
getFrame() {
|
|
371
|
+
return this.frame;
|
|
372
|
+
}
|
|
373
|
+
getFps() {
|
|
374
|
+
return this.fps;
|
|
252
375
|
}
|
|
253
376
|
run(options = {}) {
|
|
377
|
+
// The frame belongs to the run, not to the graph's history: a render loop
|
|
378
|
+
// states which frame it wants and gets exactly that, with no dependence on
|
|
379
|
+
// what the previous run left behind.
|
|
380
|
+
if (options.fps !== undefined)
|
|
381
|
+
this.setFps(options.fps);
|
|
382
|
+
if (options.frame !== undefined)
|
|
383
|
+
this.setFrame(options.frame);
|
|
384
|
+
// Every bound parameter, resolved before any node executes — so a node
|
|
385
|
+
// still receives a plain number and never reads a clock.
|
|
386
|
+
else
|
|
387
|
+
this.animator.resolveAll();
|
|
254
388
|
return this.trackRun(options.target ?? { kind: "all" }, options.signal, options.runId);
|
|
255
389
|
}
|
|
256
390
|
trigger(nodeId, inputName, payload, options = {}) {
|
|
@@ -339,7 +473,7 @@ class Graph {
|
|
|
339
473
|
}
|
|
340
474
|
: this.runSelection(target);
|
|
341
475
|
const { reachable, initial } = selection;
|
|
342
|
-
this.
|
|
476
|
+
this.assertPreflight(reachable);
|
|
343
477
|
if (!signal.aborted)
|
|
344
478
|
await Promise.all(reachable
|
|
345
479
|
.filter((node) => !this.isRemote(node))
|
|
@@ -504,6 +638,7 @@ class Graph {
|
|
|
504
638
|
report: (event) => this.emit({ type: "node:progress", runId, nodeId: node.id, ...event }),
|
|
505
639
|
};
|
|
506
640
|
await node.execute({
|
|
641
|
+
nodeId: node.id,
|
|
507
642
|
inputs,
|
|
508
643
|
outputs,
|
|
509
644
|
props,
|
|
@@ -544,33 +679,70 @@ class Graph {
|
|
|
544
679
|
}
|
|
545
680
|
this.emit({ type: "node:complete", runId, nodeId: node.id });
|
|
546
681
|
}
|
|
547
|
-
|
|
682
|
+
/**
|
|
683
|
+
* Every reason this host cannot run these nodes, collected rather than
|
|
684
|
+
* thrown. `assertPreflight` is the run path and raises the first of them;
|
|
685
|
+
* `preflight()` is the static path and hands the whole list to a caller that
|
|
686
|
+
* is not executing anything.
|
|
687
|
+
*
|
|
688
|
+
* There is deliberately one implementation. `validate` and `check` used to
|
|
689
|
+
* answer "will this graph run?" without ever consulting a host, so a node
|
|
690
|
+
* declaring a capability nobody installs passed both and then failed here —
|
|
691
|
+
* the two commands could not see the fault they exist to report. Any second
|
|
692
|
+
* copy of these rules would drift back into that.
|
|
693
|
+
*/
|
|
694
|
+
preflightDiagnostics(nodes) {
|
|
695
|
+
const diagnostics = [];
|
|
696
|
+
const push = (code, nodeId, message) => diagnostics.push({ phase: "preflight", code, path: nodeId, message });
|
|
548
697
|
for (const node of nodes) {
|
|
698
|
+
if (node.strayParams.length)
|
|
699
|
+
push("runtime/stray-params", node.id, `${node.moduleId} stores ${node.strayParams.join(", ")} under "params", which this runtime does not read — move ${node.strayParams.length === 1 ? "it" : "them"} to "props" or "inputs", whichever the definition declares, or the ${node.strayParams.length === 1 ? "value is" : "values are"} silently the default`);
|
|
549
700
|
const remote = this.isRemote(node);
|
|
550
701
|
const locationWorks = node.definition.runsOn === "portable" ||
|
|
551
702
|
node.definition.runsOn === this.host.environment ||
|
|
552
703
|
remote;
|
|
553
|
-
if (!locationWorks)
|
|
554
|
-
|
|
704
|
+
if (!locationWorks) {
|
|
705
|
+
push("runtime/preflight-environment", node.id, `${node.moduleId} requires ${node.definition.runsOn}`);
|
|
706
|
+
continue;
|
|
707
|
+
}
|
|
555
708
|
if (remote) {
|
|
556
709
|
const hasTriggers = [
|
|
557
710
|
...Object.values(node.definition.inputs ?? {}),
|
|
558
711
|
...Object.values(node.definition.outputs ?? {}),
|
|
559
712
|
].some((port) => port.kind === "trigger");
|
|
560
713
|
if (hasTriggers)
|
|
561
|
-
|
|
714
|
+
push("runtime/preflight-bridge-trigger", node.id, `${node.moduleId} cannot send triggers over the server bridge`);
|
|
562
715
|
const hasTexture = [
|
|
563
716
|
...Object.values(node.definition.inputs ?? {}),
|
|
564
717
|
...Object.values(node.definition.outputs ?? {}),
|
|
565
718
|
].some((port) => port.kind === "data" && port.type === "texture");
|
|
566
719
|
if (hasTexture)
|
|
567
|
-
|
|
720
|
+
push("runtime/preflight-bridge-texture", node.id, `${node.moduleId} cannot send textures over the server bridge`);
|
|
568
721
|
}
|
|
569
722
|
else
|
|
570
723
|
for (const capability of node.definition.capabilities ?? [])
|
|
571
724
|
if (!this.host.capabilities[capability])
|
|
572
|
-
|
|
725
|
+
push("runtime/missing-capability", node.id, `Missing capability ${capability} for ${node.moduleId} (node ${node.id}) — the ${this.host.environment} host ${this.hostProvides()}`);
|
|
573
726
|
}
|
|
727
|
+
return diagnostics;
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* What the host actually installed, asked of the host rather than written
|
|
731
|
+
* down. `serverBridge` is left out: it is how a node runs somewhere else,
|
|
732
|
+
* not a capability a definition can declare.
|
|
733
|
+
*/
|
|
734
|
+
hostProvides() {
|
|
735
|
+
const supplied = Object.keys(this.host.capabilities)
|
|
736
|
+
.filter((name) => name !== "serverBridge" && !!this.host.capabilities[name])
|
|
737
|
+
.sort();
|
|
738
|
+
return supplied.length
|
|
739
|
+
? `provides ${supplied.join(", ")}`
|
|
740
|
+
: `provides no capabilities`;
|
|
741
|
+
}
|
|
742
|
+
assertPreflight(nodes) {
|
|
743
|
+
const [first] = this.preflightDiagnostics(nodes).filter((diagnostic) => !PREFLIGHT_WARNING_CODES.has(diagnostic.code));
|
|
744
|
+
if (first)
|
|
745
|
+
throw new CascadeRuntimeError(first.code, [first]);
|
|
574
746
|
}
|
|
575
747
|
isRemote(node) {
|
|
576
748
|
return (this.host.environment === "browser" &&
|
|
@@ -776,12 +948,34 @@ function materialize(authored, registration) {
|
|
|
776
948
|
}
|
|
777
949
|
for (const [name, definition] of Object.entries(registration.definition.props ?? {}))
|
|
778
950
|
props[name] = runtimeValue(definition.default, definition.type, false, `${authored.id}.${name}`);
|
|
951
|
+
const bindings = {};
|
|
779
952
|
for (const [name, saved] of Object.entries(authored.props ?? {})) {
|
|
780
953
|
const definition = registration.definition.props?.[name];
|
|
781
|
-
if (definition)
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
954
|
+
if (!definition)
|
|
955
|
+
continue;
|
|
956
|
+
// `{ value, expression?, channel? }` — the object form declared in
|
|
957
|
+
// contracts/animation.ts. A bare value is the same prop with nothing more
|
|
958
|
+
// to say, which is what every older file on disk contains.
|
|
959
|
+
const object = saved && typeof saved === "object" && "value" in saved
|
|
960
|
+
? saved
|
|
961
|
+
: null;
|
|
962
|
+
const value = runtimeValue(object ? object.value : saved, definition.type, false, `${authored.id}.${name}`);
|
|
963
|
+
props[name] = value;
|
|
964
|
+
if (!object)
|
|
965
|
+
continue;
|
|
966
|
+
const channel = deserializeChannel(object.channel);
|
|
967
|
+
const expression = typeof object.expression === "string" && object.expression
|
|
968
|
+
? object.expression
|
|
969
|
+
: undefined;
|
|
970
|
+
const binding = {
|
|
971
|
+
value,
|
|
972
|
+
...(expression ? { expression } : {}),
|
|
973
|
+
...(isEmptyChannel(channel) ? {} : { channel }),
|
|
974
|
+
};
|
|
975
|
+
// Only a prop that actually carries something, so `animated` stays a real
|
|
976
|
+
// question and an unanimated graph pays nothing for this.
|
|
977
|
+
if (isBoundProp(binding))
|
|
978
|
+
bindings[name] = binding;
|
|
785
979
|
}
|
|
786
980
|
return {
|
|
787
981
|
id: authored.id,
|
|
@@ -791,9 +985,43 @@ function materialize(authored, registration) {
|
|
|
791
985
|
definition: registration.definition,
|
|
792
986
|
inputs,
|
|
793
987
|
props,
|
|
988
|
+
bindings,
|
|
989
|
+
strayParams: strayParams(authored, registration),
|
|
794
990
|
outputs: new Map(),
|
|
795
991
|
};
|
|
796
992
|
}
|
|
993
|
+
/**
|
|
994
|
+
* The names a document stored under `params` that this runtime will not read.
|
|
995
|
+
*
|
|
996
|
+
* Nothing above consults `params`: the loops that fill `inputs` and `props`
|
|
997
|
+
* read the definition and then the document's own `inputs` and `props`, so a
|
|
998
|
+
* value left in `params` is dropped without a word and the parameter keeps its
|
|
999
|
+
* default. That is not a hypothetical — on `cascade-logo` it put a seed back to
|
|
1000
|
+
* 7 and two rasterizers back to 1024, so three nodes wrote one file and the
|
|
1001
|
+
* drawing changed, with every command still reporting success.
|
|
1002
|
+
*
|
|
1003
|
+
* **Inputs count, not just props.** The first version of this checked props
|
|
1004
|
+
* alone and so was blind to exactly the case the conversions produce most: a
|
|
1005
|
+
* parameter another node drives has to be declared as an `inputs` entry,
|
|
1006
|
+
* because a v1 prop cannot be promoted to a pin. `field-logo`'s `angle` is one,
|
|
1007
|
+
* and a negative control over the pre-migration document named five nodes and
|
|
1008
|
+
* missed that one.
|
|
1009
|
+
*
|
|
1010
|
+
* Only names the definition actually declares are reported. A leftover key
|
|
1011
|
+
* naming nothing is a different fault, and reporting it would make this fire on
|
|
1012
|
+
* documents where no value was lost.
|
|
1013
|
+
*/
|
|
1014
|
+
function strayParams(authored, registration) {
|
|
1015
|
+
const definition = registration.definition;
|
|
1016
|
+
const declared = new Set([
|
|
1017
|
+
...Object.keys(definition.props ?? {}),
|
|
1018
|
+
...Object.keys(definition.inputs ?? {}),
|
|
1019
|
+
]);
|
|
1020
|
+
const names = Array.isArray(authored.params)
|
|
1021
|
+
? authored.params.map((saved) => saved?.name)
|
|
1022
|
+
: Object.keys(authored.params ?? {});
|
|
1023
|
+
return names.filter((name) => typeof name === "string" && declared.has(name));
|
|
1024
|
+
}
|
|
797
1025
|
function specializeCoreRegistration(registration, authored, nodes) {
|
|
798
1026
|
if (registration.moduleId === "cascade.core.Input" ||
|
|
799
1027
|
registration.moduleId === "cascade.core.Output") {
|
package/dist/runtime/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { AssetCapability, AssetRef, CascadeAbortSignal, CascadeDocument, CascadePreset, Diagnostic, FileCapability, ImageRef, JsonValue, MediaCapability, NodeDefinition, NodeExecute, ProgressReporter, PythonCapability, SerializableValue, ShellCapability, TextureHandle, WebGLCapability } from "cascade/contracts";
|
|
2
|
-
export type { CascadeDocument, CascadeDocumentAnnotation, CascadeDocumentConnection, CascadeDocumentNode, CascadePreset, } from "cascade/contracts";
|
|
1
|
+
import type { AssetCapability, AssetRef, CascadeAbortSignal, CascadeDocument, CascadePreset, Diagnostic, FileCapability, ImageRef, JsonValue, MediaCapability, NodeDefinition, NodeExecute, ProgressReporter, PythonCapability, SerializableValue, ShellCapability, TextureHandle, WebGLCapability } from "@field/cascade/contracts";
|
|
2
|
+
export type { CascadeDocument, CascadeDocumentAnnotation, CascadeDocumentConnection, CascadeDocumentNode, CascadePreset, } from "@field/cascade/contracts";
|
|
3
3
|
export interface DefinitionNodeRegistration<D extends NodeDefinition = NodeDefinition> {
|
|
4
4
|
readonly kind: "definition-v1";
|
|
5
5
|
readonly moduleId: string;
|
|
@@ -88,6 +88,18 @@ export interface RunOptions {
|
|
|
88
88
|
readonly target?: RunTarget;
|
|
89
89
|
readonly signal?: CascadeAbortSignal;
|
|
90
90
|
readonly runId?: string;
|
|
91
|
+
/**
|
|
92
|
+
* The frame to resolve animated parameters at — a keyframe channel is
|
|
93
|
+
* sampled here and `$F`/`$FF`/`$T` are derived from it. Fractional frames
|
|
94
|
+
* are kept. Omitted leaves the graph on the frame it is already on.
|
|
95
|
+
*
|
|
96
|
+
* A value the caller states rather than a clock the runtime reads: same
|
|
97
|
+
* graph plus same frame gives the same output, which is what makes an
|
|
98
|
+
* offline render reproducible.
|
|
99
|
+
*/
|
|
100
|
+
readonly frame?: number;
|
|
101
|
+
/** Frames per second, for `$FPS` and for `$T`. Default 30. */
|
|
102
|
+
readonly fps?: number;
|
|
91
103
|
}
|
|
92
104
|
export interface RunResult {
|
|
93
105
|
readonly runId: string;
|
|
@@ -163,13 +175,13 @@ export type RuntimeEvent = {
|
|
|
163
175
|
readonly runId: string;
|
|
164
176
|
readonly nodeId: string;
|
|
165
177
|
readonly inputName: string;
|
|
166
|
-
readonly event: import("cascade/contracts").TriggerEvent;
|
|
178
|
+
readonly event: import("@field/cascade/contracts").TriggerEvent;
|
|
167
179
|
} | {
|
|
168
180
|
readonly type: "trigger:delivered";
|
|
169
181
|
readonly runId: string;
|
|
170
182
|
readonly nodeId: string;
|
|
171
183
|
readonly inputName: string;
|
|
172
|
-
readonly event: import("cascade/contracts").TriggerEvent;
|
|
184
|
+
readonly event: import("@field/cascade/contracts").TriggerEvent;
|
|
173
185
|
} | {
|
|
174
186
|
readonly type: "node:output";
|
|
175
187
|
readonly runId: string;
|
|
@@ -203,6 +215,12 @@ export interface CascadeRuntime {
|
|
|
203
215
|
}
|
|
204
216
|
export interface LoadedCascadeGraph {
|
|
205
217
|
readonly state: GraphState;
|
|
218
|
+
/**
|
|
219
|
+
* Host-aware static gate: the diagnostics `run` would raise before executing
|
|
220
|
+
* anything, collected instead of thrown. This is what lets a validator see a
|
|
221
|
+
* missing capability, which is a fault it was previously blind to.
|
|
222
|
+
*/
|
|
223
|
+
preflight(): readonly Diagnostic[];
|
|
206
224
|
inspect(): GraphInspection;
|
|
207
225
|
setInput(nodeId: string, inputName: string, value: unknown): Promise<void>;
|
|
208
226
|
setGraphInput(name: string, value: unknown): Promise<void>;
|
|
@@ -213,6 +231,14 @@ export interface LoadedCascadeGraph {
|
|
|
213
231
|
runId?: string;
|
|
214
232
|
}>): Promise<RunResult>;
|
|
215
233
|
run(options?: RunOptions): Promise<RunResult>;
|
|
234
|
+
/**
|
|
235
|
+
* Move the graph's clock without cooking. Animated parameters re-resolve
|
|
236
|
+
* immediately, so `inspect()` reports them at this frame.
|
|
237
|
+
*/
|
|
238
|
+
setFrame(frame: number): void;
|
|
239
|
+
setFps(fps: number): void;
|
|
240
|
+
getFrame(): number;
|
|
241
|
+
getFps(): number;
|
|
216
242
|
cancel(reason?: string): void;
|
|
217
243
|
subscribe(listener: (event: RuntimeEvent) => void): () => void;
|
|
218
244
|
getOutput(nodeId: string, outputName: string): unknown;
|
package/dist/shell.d.ts
CHANGED
|
@@ -3,6 +3,6 @@ export {
|
|
|
3
3
|
type ShellProcessErrorKind,
|
|
4
4
|
type ShellRunOptions,
|
|
5
5
|
type ShellRunResult,
|
|
6
|
-
} from 'cascade/contracts';
|
|
7
|
-
export declare function run(command: string, args?: readonly string[], options?: import('cascade/contracts').ShellRunOptions): Promise<import('cascade/contracts').ShellRunResult>;
|
|
8
|
-
export declare function runJson<T = unknown>(command: string, args?: readonly string[], options?: import('cascade/contracts').ShellRunOptions): Promise<T>;
|
|
6
|
+
} from '@field/cascade/contracts';
|
|
7
|
+
export declare function run(command: string, args?: readonly string[], options?: import('@field/cascade/contracts').ShellRunOptions): Promise<import('@field/cascade/contracts').ShellRunResult>;
|
|
8
|
+
export declare function runJson<T = unknown>(command: string, args?: readonly string[], options?: import('@field/cascade/contracts').ShellRunOptions): Promise<T>;
|