@field/cascade 0.3.0 → 0.3.1
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/CHANGELOG.md +18 -0
- package/dist/assets/{CodeEditor-BADFbTJS.js → CodeEditor-D22fVi-4.js} +4 -4
- package/dist/assets/{CodePanel-faK6PRfz.js → CodePanel-DPNEYfcR.js} +2 -2
- package/dist/assets/{CustomNodeDialog-DhlKuviy.js → CustomNodeDialog-DeHCAvgb.js} +1 -1
- package/dist/assets/{ProjectSettings-D6a6kjqR.js → ProjectSettings-DWpVtWEx.js} +1 -1
- package/dist/assets/{SettingsDialog-DJZdIvLC.js → SettingsDialog-za00IS3L.js} +1 -1
- package/dist/assets/{VersionHistoryDialog-C1X3HOhc.js → VersionHistoryDialog-DTMGhI0_.js} +1 -1
- package/dist/assets/{core-CiuiuWZi.js → core-JsyKMO1k.js} +1 -1
- package/dist/assets/{cssMode-D-NwZSMJ.js → cssMode-emdRheFK.js} +1 -1
- package/dist/assets/{editor.api-DT3WEvBd.js → editor.api-xHX-fMSo.js} +1 -1
- package/dist/assets/{freemarker2-eKNYQaiz.js → freemarker2-C3M8_Gs3.js} +1 -1
- package/dist/assets/geo-BAR6lQp5.js +1 -0
- package/dist/assets/{handlebars-C2w1ggK2.js → handlebars-CBMurtFk.js} +1 -1
- package/dist/assets/{html-DQb41jyo.js → html-xbLQwknw.js} +1 -1
- package/dist/assets/{htmlMode-IhNlh6Lv.js → htmlMode-2wKdDTon.js} +1 -1
- package/dist/assets/{image-DMCXBY-_.js → image-i1j6nWKe.js} +1 -1
- package/dist/assets/{index-HTCN44kj.js → index-CMcH8AgP.js} +35 -35
- package/dist/assets/{javascript-BIT6jMLC.js → javascript-DJaOo_2I.js} +1 -1
- package/dist/assets/{jsonMode-CNfyB1ro.js → jsonMode-Bheas8_0.js} +1 -1
- package/dist/assets/{liquid-CmSmJmOG.js → liquid-BCj_BGbM.js} +1 -1
- package/dist/assets/{lspLanguageFeatures-BqMm3uMP.js → lspLanguageFeatures-BfIsx_xz.js} +1 -1
- package/dist/assets/{mdx-6r6YgOFw.js → mdx-DwiQDiVO.js} +1 -1
- package/dist/assets/{python-x1H0TQuI.js → python-BjrB5nqj.js} +1 -1
- package/dist/assets/{razor-Dzl1vZFm.js → razor-CfoBmGDs.js} +1 -1
- package/dist/assets/{tsMode-n6DMMe3a.js → tsMode-BcEoMSxp.js} +1 -1
- package/dist/assets/{typescript-ziEz65xN.js → typescript-DIg7TEmQ.js} +1 -1
- package/dist/assets/{workers-D-qUdp-r.js → workers-vXYuwtPw.js} +1 -1
- package/dist/assets/{xml-DMZjt5lE.js → xml-DexLQIQy.js} +1 -1
- package/dist/assets/{yaml-DFe5qZSO.js → yaml-DUnD_1dE.js} +1 -1
- package/dist/cli/index.js +317 -49
- package/dist/cli/runtime/gpu.ts +76 -0
- package/dist/contracts/capabilities.d.ts +69 -6
- package/dist/contracts/definition.d.ts +21 -1
- package/dist/contracts/schema.js +8 -5
- package/dist/contracts/validate.js +11 -2
- package/dist/gpu.d.ts +71 -0
- package/dist/gpu.js +53 -0
- package/dist/index.html +1 -1
- package/dist/runtime/runtime.d.ts +23 -3
- package/dist/runtime/runtime.js +118 -24
- package/dist/runtime/types.d.ts +3 -3
- package/package.json +5 -1
- package/dist/assets/geo-DUvVbLnx.js +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AssetRef, ImageRef, JsonValue
|
|
1
|
+
import type { AssetRef, ImageRef, JsonValue } from "./values.js";
|
|
2
2
|
export interface CascadeAbortSignal {
|
|
3
3
|
readonly aborted: boolean;
|
|
4
4
|
readonly reason?: unknown;
|
|
@@ -66,10 +66,73 @@ export interface PythonCapability {
|
|
|
66
66
|
progress: ProgressReporter;
|
|
67
67
|
}): Promise<JsonValue | AssetRef | ImageRef>;
|
|
68
68
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
/**
|
|
70
|
+
* A GPU a node can compute on, and the state that has to outlive one cook.
|
|
71
|
+
*
|
|
72
|
+
* This replaces `WebGLCapability`, which nothing implemented, nothing declared
|
|
73
|
+
* and no host installed — its one method was WebGL2-shaped, and keeping both
|
|
74
|
+
* names would have meant two devices in one page. Two devices cannot exchange
|
|
75
|
+
* a texture: an allocation made on one is not bindable, sampleable or copyable
|
|
76
|
+
* on the other, and the failure is a validation error at bind time rather than
|
|
77
|
+
* anything a graph author would recognise. So a shared device is not a
|
|
78
|
+
* preference, it is the precondition for a chain of GPU nodes at all.
|
|
79
|
+
*
|
|
80
|
+
* **Stage 0 deliberately carries three members and no textures.** Ports still
|
|
81
|
+
* pass `image`, so every picture is byte-identical to before; what this buys is
|
|
82
|
+
* one device instead of one per node module, an owner for `device.lost`, and
|
|
83
|
+
* somewhere legitimate to keep a pipeline — which is what lets the
|
|
84
|
+
* `architecture/ambient-state` rule stay strict. The texture store, the
|
|
85
|
+
* generation-stamped handles and the readback are Stage 1, and the shape of
|
|
86
|
+
* this interface is meant to be lived with for a few days first.
|
|
87
|
+
*
|
|
88
|
+
* The `GPUDevice` type comes from `@webgpu/types`, added to this package's
|
|
89
|
+
* `tsconfig.json` as its only `types` entry. Contracts compiles with
|
|
90
|
+
* `lib: ["ES2022"]` and no DOM on purpose — it is the vocabulary both hosts
|
|
91
|
+
* share, so pulling in the whole browser would let a browser-only type reach a
|
|
92
|
+
* portable definition by accident. A hand-written stub was the alternative and
|
|
93
|
+
* would have been worse than a dependency: it would shadow the genuine
|
|
94
|
+
* `GPUDevice` at the node's own call site, so every real method call would
|
|
95
|
+
* stop typechecking.
|
|
96
|
+
*
|
|
97
|
+
* The device is raw on purpose. A wrapped pipeline API would be a second thing
|
|
98
|
+
* to learn, would lag the spec, and would fight the reason to reach for WebGPU
|
|
99
|
+
* at all, which is that you write WGSL and encode passes. The capability wraps
|
|
100
|
+
* only what carries lifetime or identity, because those are the parts a host
|
|
101
|
+
* has to be able to account for.
|
|
102
|
+
*/
|
|
103
|
+
export interface GpuCapability {
|
|
104
|
+
/**
|
|
105
|
+
* The one device for this page. Shared by every node declaring `gpu`, which
|
|
106
|
+
* is the whole point — see the note above on why two would be useless.
|
|
107
|
+
*/
|
|
108
|
+
readonly device: GPUDevice;
|
|
109
|
+
/**
|
|
110
|
+
* Who is actually rendering, recorded so a difference between two runs of
|
|
111
|
+
* the same graph is attributable rather than a mystery. WebGPU output can
|
|
112
|
+
* differ across adapters, and today there is no way to answer "which one".
|
|
113
|
+
*/
|
|
114
|
+
readonly adapterInfo: Readonly<{
|
|
115
|
+
vendor: string;
|
|
116
|
+
architecture: string;
|
|
117
|
+
device: string;
|
|
118
|
+
description: string;
|
|
119
|
+
}>;
|
|
120
|
+
/** What this adapter will actually allow — `maxTextureDimension3D` and the
|
|
121
|
+
* rest. Read at runtime rather than assumed; the 3D default is far below
|
|
122
|
+
* the 2D one and a hard-coded guess would be wrong on some machines. */
|
|
123
|
+
readonly limits: Readonly<Record<string, number>>;
|
|
124
|
+
/**
|
|
125
|
+
* A resource that outlives one cook — a compiled pipeline, a sampler, a
|
|
126
|
+
* bind group layout. Keyed per node by the host, so two instances of one
|
|
127
|
+
* module do not share a cache entry and a module-level `Map` is never the
|
|
128
|
+
* right answer again.
|
|
129
|
+
*
|
|
130
|
+
* `create` runs once per key. `destroy` is called when the entry is dropped:
|
|
131
|
+
* when the device is lost, or when the graph disposes. This is the member
|
|
132
|
+
* that makes `architecture/module-state` keepable — it was previously
|
|
133
|
+
* forbidding the only available way to hold a pipeline.
|
|
134
|
+
*/
|
|
135
|
+
cache<T>(key: string, create: (device: GPUDevice) => T, destroy?: (value: T) => void): T;
|
|
73
136
|
}
|
|
74
137
|
export interface ShellRunResult {
|
|
75
138
|
readonly stdout: string;
|
|
@@ -102,7 +165,7 @@ export interface CapabilityMap {
|
|
|
102
165
|
readonly assets: AssetCapability;
|
|
103
166
|
readonly media: MediaCapability;
|
|
104
167
|
readonly python: PythonCapability;
|
|
105
|
-
readonly
|
|
168
|
+
readonly gpu: GpuCapability;
|
|
106
169
|
readonly shell: ShellCapability;
|
|
107
170
|
}
|
|
108
171
|
export type NodeCapabilityName = keyof CapabilityMap;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { CapabilityMap, CascadeAbortSignal, NodeCapabilityName, ProgressReporter } from "./capabilities.js";
|
|
2
2
|
import type { CascadeType, CoreType, JsonValue, NamespacedType, SerializableValueForType, ValueForType } from "./values.js";
|
|
3
3
|
export type RuntimeEnvironment = "portable" | "browser" | "server";
|
|
4
|
-
export type CapabilityForEnvironment<E extends RuntimeEnvironment> = E extends "portable" ? "assets" | "media" : E extends "browser" ? "assets" | "media" | "
|
|
4
|
+
export type CapabilityForEnvironment<E extends RuntimeEnvironment> = E extends "portable" ? "assets" | "media" : E extends "browser" ? "assets" | "media" | "gpu" : "files" | "assets" | "media" | "python" | "shell";
|
|
5
5
|
export type DataTypeForEnvironment<E extends RuntimeEnvironment> = E extends "browser" ? CascadeType : Exclude<CascadeType, "texture">;
|
|
6
6
|
type ControlFor<T extends CascadeType> = T extends "float" ? "number" | "slider" | "range" : T extends "int" ? "int" | "slider" | "range" : T extends "bool" ? "boolean" : T extends "string" ? "text" | "textarea" | "select" : T extends "vec2" | "vec3" | "vec4" | "vec2i" | "vec3i" | "vec4i" ? "vector" : T extends "mat2" | "mat3" | "mat4" ? "matrix" : T extends "color" ? "color" : T extends "image" ? "image" : T extends "asset" ? "asset" | "select" : T extends "texture" ? never : "select";
|
|
7
7
|
/**
|
|
@@ -80,6 +80,26 @@ export type PropDefinition<T extends PropDataType> = {
|
|
|
80
80
|
readonly default: SerializableValueForType<T>;
|
|
81
81
|
readonly label?: string;
|
|
82
82
|
readonly description?: string;
|
|
83
|
+
/**
|
|
84
|
+
* An expression the prop starts with, so a node can come out of the box
|
|
85
|
+
* already moving — `sine-oscillator` and `ripple` baked in `$T` as dynamic
|
|
86
|
+
* nodes and shipped a static 0 once converted, which is what this restores.
|
|
87
|
+
* Marcus's decision, 2026-09-08, asked as a taste question about his own
|
|
88
|
+
* tool: a node like that should self-animate when dropped.
|
|
89
|
+
*
|
|
90
|
+
* **It is a default, so it loses to a stored value like any other.** A
|
|
91
|
+
* document that saved a plain number for this prop keeps that number and
|
|
92
|
+
* does not silently start animating on load. That is the mirror of the
|
|
93
|
+
* dropped-`params` fault: whatever the author set has to win, and the test
|
|
94
|
+
* for this asserts the losing case rather than the winning one.
|
|
95
|
+
*
|
|
96
|
+
* Applied only when the document stored nothing at all for the prop. It is a
|
|
97
|
+
* real expression once applied, not a hidden fallback — visible in the
|
|
98
|
+
* Inspector, editable, and deletable, which is how a stored expression
|
|
99
|
+
* already reads and the only version that does not lie about where the
|
|
100
|
+
* number came from.
|
|
101
|
+
*/
|
|
102
|
+
readonly expression?: string;
|
|
83
103
|
} & NumericMetadata<T> & AcceptMetadata<T> & SelectMetadata<T>;
|
|
84
104
|
export type AnyPropDefinition = {
|
|
85
105
|
[T in PropDataType]: PropDefinition<T>;
|
package/dist/contracts/schema.js
CHANGED
|
@@ -41,10 +41,13 @@ const dataProperties = {
|
|
|
41
41
|
const data = object(dataProperties, ["kind", "type"]);
|
|
42
42
|
const output = object({ kind: dataProperties.kind, type: dataProperties.type, description }, ["kind", "type"]);
|
|
43
43
|
const { kind: _kind, variadic: _variadic, ...propProperties } = dataProperties;
|
|
44
|
-
const prop = object({
|
|
45
|
-
|
|
46
|
-
"
|
|
47
|
-
|
|
44
|
+
const prop = object({
|
|
45
|
+
...propProperties,
|
|
46
|
+
label: { type: "string" },
|
|
47
|
+
// Props only, not inputs: an input's value comes from whatever is wired to
|
|
48
|
+
// it, so a default expression there would be describing the wrong thing.
|
|
49
|
+
expression: { type: "string", minLength: 1 },
|
|
50
|
+
}, ["type", "default"]);
|
|
48
51
|
function environmentCapabilities(runsOn, capabilities) {
|
|
49
52
|
return {
|
|
50
53
|
if: { properties: { runsOn: { const: runsOn } } },
|
|
@@ -72,7 +75,7 @@ export const nodeDefinitionSchema = {
|
|
|
72
75
|
}, ["apiVersion", "runsOn"]),
|
|
73
76
|
allOf: [
|
|
74
77
|
environmentCapabilities("portable", ["assets", "media"]),
|
|
75
|
-
environmentCapabilities("browser", ["assets", "media", "
|
|
78
|
+
environmentCapabilities("browser", ["assets", "media", "gpu"]),
|
|
76
79
|
environmentCapabilities("server", [
|
|
77
80
|
"files",
|
|
78
81
|
"assets",
|
|
@@ -14,7 +14,7 @@ const CONTROLS = new Set(["number", "slider", "range", "int", "boolean", "text",
|
|
|
14
14
|
*/
|
|
15
15
|
export const CAPABILITIES_BY_ENVIRONMENT = {
|
|
16
16
|
portable: ["assets", "media"],
|
|
17
|
-
browser: ["assets", "media", "
|
|
17
|
+
browser: ["assets", "media", "gpu"],
|
|
18
18
|
server: ["files", "assets", "media", "python", "shell"],
|
|
19
19
|
};
|
|
20
20
|
const ALLOWED = CAPABILITIES_BY_ENVIRONMENT;
|
|
@@ -22,7 +22,7 @@ const fields = (names) => new Set(names.split(" "));
|
|
|
22
22
|
const TOP_FIELDS = fields("apiVersion runsOn container capabilities label description icon inputs outputs props");
|
|
23
23
|
const DATA_FIELDS = fields("kind type default description variadic min max step accept control options");
|
|
24
24
|
const OUTPUT_FIELDS = fields("kind type description");
|
|
25
|
-
const PROP_FIELDS = fields("type default label description min max step accept control options");
|
|
25
|
+
const PROP_FIELDS = fields("type default label description min max step accept control options expression");
|
|
26
26
|
function diagnostic(code, message, path) {
|
|
27
27
|
return { phase: "definition", code: `definition/${code}`, message, path };
|
|
28
28
|
}
|
|
@@ -194,6 +194,15 @@ export function validateNodeDefinition(value) {
|
|
|
194
194
|
!Number.isFinite(raw.step) ||
|
|
195
195
|
raw.step <= 0))
|
|
196
196
|
add("invalid-step", "step must be greater than zero", `${path}.step`);
|
|
197
|
+
if (section === "props" &&
|
|
198
|
+
"expression" in raw &&
|
|
199
|
+
(typeof raw.expression !== "string" || raw.expression.length === 0))
|
|
200
|
+
add("invalid-expression", "expression must be a non-empty string", `${path}.expression`);
|
|
201
|
+
for (const field of ["min", "max"]) {
|
|
202
|
+
if (field in raw &&
|
|
203
|
+
(typeof raw[field] !== "number" || !Number.isFinite(raw[field])))
|
|
204
|
+
add(`invalid-${field}`, `${field} must be a finite number`, `${path}.${field}`);
|
|
205
|
+
}
|
|
197
206
|
if (typeof raw.min === "number" &&
|
|
198
207
|
typeof raw.max === "number" &&
|
|
199
208
|
raw.min > raw.max)
|
package/dist/gpu.d.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cascade/gpu` — WebGPU's flag namespaces as plain values.
|
|
3
|
+
*
|
|
4
|
+
* Small, and it was load-bearing. TypeScript's DOM library declares the WebGPU
|
|
5
|
+
* *interfaces* but not the constant objects, so the only way to reach
|
|
6
|
+
* `GPUTextureUsage.RENDER_ATTACHMENT` was off `globalThis` — which
|
|
7
|
+
* `architecture/ambient-state` correctly forbids inside a node module, because
|
|
8
|
+
* an ambient read makes a node's inputs invisible to the static reader that
|
|
9
|
+
* lets `cascade check` say anything without cooking.
|
|
10
|
+
*
|
|
11
|
+
* So a sketch had to choose between a rule and a working shader, and it chose
|
|
12
|
+
* a project library file to hide the read in. Both stay honest now: the rule
|
|
13
|
+
* keeps its teeth and a node imports these instead. That is the whole reason
|
|
14
|
+
* this file exists, and it is why it had to land with the capability rather
|
|
15
|
+
* than after it — a rule whose only workaround is removed at the same moment
|
|
16
|
+
* is a rule; one enforced a release earlier is a nuisance.
|
|
17
|
+
*
|
|
18
|
+
* The values are fixed by the WebGPU specification, not by an implementation,
|
|
19
|
+
* which is what makes writing them down safe rather than a guess. They are
|
|
20
|
+
* checked against a real browser's own globals in `tests/gpu-usage-flags.test.ts`
|
|
21
|
+
* — the numbers below were verified against Chrome rather than recalled.
|
|
22
|
+
*/
|
|
23
|
+
/** `GPUBufferUsage`. */
|
|
24
|
+
export declare const BufferUsage: {
|
|
25
|
+
readonly MAP_READ: 1;
|
|
26
|
+
readonly MAP_WRITE: 2;
|
|
27
|
+
readonly COPY_SRC: 4;
|
|
28
|
+
readonly COPY_DST: 8;
|
|
29
|
+
readonly INDEX: 16;
|
|
30
|
+
readonly VERTEX: 32;
|
|
31
|
+
readonly UNIFORM: 64;
|
|
32
|
+
readonly STORAGE: 128;
|
|
33
|
+
readonly INDIRECT: 256;
|
|
34
|
+
readonly QUERY_RESOLVE: 512;
|
|
35
|
+
};
|
|
36
|
+
/** `GPUTextureUsage`. */
|
|
37
|
+
export declare const TextureUsage: {
|
|
38
|
+
readonly COPY_SRC: 1;
|
|
39
|
+
readonly COPY_DST: 2;
|
|
40
|
+
readonly TEXTURE_BINDING: 4;
|
|
41
|
+
readonly STORAGE_BINDING: 8;
|
|
42
|
+
readonly RENDER_ATTACHMENT: 16;
|
|
43
|
+
/**
|
|
44
|
+
* Chrome 147 has this and it is newer than the rest — verified present
|
|
45
|
+
* 2026-09-08 alongside the others, but do not assume every implementation
|
|
46
|
+
* carries it. Included rather than omitted deliberately: leaving it out
|
|
47
|
+
* would send a node that wants it back to `globalThis`, which is the exact
|
|
48
|
+
* ambient read this file exists to remove, so an honest constant with a
|
|
49
|
+
* caveat beats an omission that forces a rule violation.
|
|
50
|
+
*/
|
|
51
|
+
readonly TRANSIENT_ATTACHMENT: 32;
|
|
52
|
+
};
|
|
53
|
+
/** `GPUShaderStage`, for a bind group layout entry's `visibility`. */
|
|
54
|
+
export declare const ShaderStage: {
|
|
55
|
+
readonly VERTEX: 1;
|
|
56
|
+
readonly FRAGMENT: 2;
|
|
57
|
+
readonly COMPUTE: 4;
|
|
58
|
+
};
|
|
59
|
+
/** `GPUMapMode`, for `buffer.mapAsync`. */
|
|
60
|
+
export declare const MapMode: {
|
|
61
|
+
readonly READ: 1;
|
|
62
|
+
readonly WRITE: 2;
|
|
63
|
+
};
|
|
64
|
+
/** `GPUColorWrite`, for a colour target's `writeMask`. */
|
|
65
|
+
export declare const ColorWrite: {
|
|
66
|
+
readonly RED: 1;
|
|
67
|
+
readonly GREEN: 2;
|
|
68
|
+
readonly BLUE: 4;
|
|
69
|
+
readonly ALPHA: 8;
|
|
70
|
+
readonly ALL: 15;
|
|
71
|
+
};
|
package/dist/gpu.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// server/src/runtime/gpu.ts
|
|
2
|
+
var BufferUsage = {
|
|
3
|
+
MAP_READ: 1,
|
|
4
|
+
MAP_WRITE: 2,
|
|
5
|
+
COPY_SRC: 4,
|
|
6
|
+
COPY_DST: 8,
|
|
7
|
+
INDEX: 16,
|
|
8
|
+
VERTEX: 32,
|
|
9
|
+
UNIFORM: 64,
|
|
10
|
+
STORAGE: 128,
|
|
11
|
+
INDIRECT: 256,
|
|
12
|
+
QUERY_RESOLVE: 512
|
|
13
|
+
};
|
|
14
|
+
var TextureUsage = {
|
|
15
|
+
COPY_SRC: 1,
|
|
16
|
+
COPY_DST: 2,
|
|
17
|
+
TEXTURE_BINDING: 4,
|
|
18
|
+
STORAGE_BINDING: 8,
|
|
19
|
+
RENDER_ATTACHMENT: 16,
|
|
20
|
+
/**
|
|
21
|
+
* Chrome 147 has this and it is newer than the rest — verified present
|
|
22
|
+
* 2026-09-08 alongside the others, but do not assume every implementation
|
|
23
|
+
* carries it. Included rather than omitted deliberately: leaving it out
|
|
24
|
+
* would send a node that wants it back to `globalThis`, which is the exact
|
|
25
|
+
* ambient read this file exists to remove, so an honest constant with a
|
|
26
|
+
* caveat beats an omission that forces a rule violation.
|
|
27
|
+
*/
|
|
28
|
+
TRANSIENT_ATTACHMENT: 32
|
|
29
|
+
};
|
|
30
|
+
var ShaderStage = {
|
|
31
|
+
VERTEX: 1,
|
|
32
|
+
FRAGMENT: 2,
|
|
33
|
+
COMPUTE: 4
|
|
34
|
+
};
|
|
35
|
+
var MapMode = {
|
|
36
|
+
READ: 1,
|
|
37
|
+
WRITE: 2
|
|
38
|
+
};
|
|
39
|
+
var ColorWrite = {
|
|
40
|
+
RED: 1,
|
|
41
|
+
GREEN: 2,
|
|
42
|
+
BLUE: 4,
|
|
43
|
+
ALPHA: 8,
|
|
44
|
+
ALL: 15
|
|
45
|
+
};
|
|
46
|
+
export {
|
|
47
|
+
BufferUsage,
|
|
48
|
+
ColorWrite,
|
|
49
|
+
MapMode,
|
|
50
|
+
ShaderStage,
|
|
51
|
+
TextureUsage
|
|
52
|
+
};
|
|
53
|
+
//# sourceMappingURL=gpu.js.map
|
package/dist/index.html
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>Cascade</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-CMcH8AgP.js"></script>
|
|
8
8
|
<link rel="modulepreload" crossorigin href="/assets/legacy-CAj2_7QE.js">
|
|
9
9
|
<link rel="modulepreload" crossorigin href="/assets/index-client-BoJgsN9M.js">
|
|
10
10
|
<link rel="modulepreload" crossorigin href="/assets/nodeTypeUtils-BBA1JqHx.js">
|
|
@@ -16,9 +16,29 @@ import type { CascadeRuntime, CreateRuntimeOptions } from "./types.js";
|
|
|
16
16
|
* leftover `params` array runs perfectly well on its defaults, which is
|
|
17
17
|
* precisely the problem.
|
|
18
18
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
19
|
+
* It was briefly an error, and Marcus reverted that on 2026-09-08 with the
|
|
20
|
+
* evidence that decides it: while converting sixty-two nodes across four
|
|
21
|
+
* sketches, every one of them was a mixed graph for most of the day, and as an
|
|
22
|
+
* error an unconverted or half-converted sketch **stops validating at all** —
|
|
23
|
+
* which is the one thing you need while converting it. The warning is the
|
|
24
|
+
* conversion checklist; as an error it is a locked door on the room you are
|
|
25
|
+
* working in.
|
|
26
|
+
*
|
|
27
|
+
* The fair case for the error was that the warning never fired, and that was
|
|
28
|
+
* true: `check` bailed on dynamic modules before preflight ran. The fix for
|
|
29
|
+
* that was the reporting *order*, not the severity.
|
|
30
|
+
*
|
|
31
|
+
* This set lives here rather than in the CLI because both paths read it, and a
|
|
32
|
+
* duplicated copy is what let `validate` and `check` pass graphs `run` then
|
|
33
|
+
* rejected.
|
|
34
|
+
*
|
|
35
|
+
* **It classifies a static check, and it is not the run gate.** Those are
|
|
36
|
+
* different questions and conflating them was a real bug of mine, caught by
|
|
37
|
+
* Codex's own comment on 2026-09-08: *execution is stricter.* A server run must
|
|
38
|
+
* still refuse a browser node — you cannot cook WebGPU headlessly, so tolerating
|
|
39
|
+
* it means executing and failing on an undefined capability instead of saying
|
|
40
|
+
* why. What a static check downgrades is the *checker's reach*; what a run
|
|
41
|
+
* tolerates is much narrower. See `RUN_TOLERATED_CODES`.
|
|
22
42
|
*/
|
|
23
43
|
export declare const PREFLIGHT_WARNING_CODES: ReadonlySet<string>;
|
|
24
44
|
export declare function createRuntime(options: CreateRuntimeOptions): CascadeRuntime;
|
package/dist/runtime/runtime.js
CHANGED
|
@@ -21,14 +21,46 @@ import { DEFAULT_FPS, MIN_FRAME } from "./expressions/time.js";
|
|
|
21
21
|
* leftover `params` array runs perfectly well on its defaults, which is
|
|
22
22
|
* precisely the problem.
|
|
23
23
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
24
|
+
* It was briefly an error, and Marcus reverted that on 2026-09-08 with the
|
|
25
|
+
* evidence that decides it: while converting sixty-two nodes across four
|
|
26
|
+
* sketches, every one of them was a mixed graph for most of the day, and as an
|
|
27
|
+
* error an unconverted or half-converted sketch **stops validating at all** —
|
|
28
|
+
* which is the one thing you need while converting it. The warning is the
|
|
29
|
+
* conversion checklist; as an error it is a locked door on the room you are
|
|
30
|
+
* working in.
|
|
31
|
+
*
|
|
32
|
+
* The fair case for the error was that the warning never fired, and that was
|
|
33
|
+
* true: `check` bailed on dynamic modules before preflight ran. The fix for
|
|
34
|
+
* that was the reporting *order*, not the severity.
|
|
35
|
+
*
|
|
36
|
+
* This set lives here rather than in the CLI because both paths read it, and a
|
|
37
|
+
* duplicated copy is what let `validate` and `check` pass graphs `run` then
|
|
38
|
+
* rejected.
|
|
39
|
+
*
|
|
40
|
+
* **It classifies a static check, and it is not the run gate.** Those are
|
|
41
|
+
* different questions and conflating them was a real bug of mine, caught by
|
|
42
|
+
* Codex's own comment on 2026-09-08: *execution is stricter.* A server run must
|
|
43
|
+
* still refuse a browser node — you cannot cook WebGPU headlessly, so tolerating
|
|
44
|
+
* it means executing and failing on an undefined capability instead of saying
|
|
45
|
+
* why. What a static check downgrades is the *checker's reach*; what a run
|
|
46
|
+
* tolerates is much narrower. See `RUN_TOLERATED_CODES`.
|
|
27
47
|
*/
|
|
28
48
|
export const PREFLIGHT_WARNING_CODES = new Set([
|
|
29
49
|
"runtime/preflight-environment",
|
|
30
50
|
"runtime/stray-params",
|
|
31
51
|
]);
|
|
52
|
+
/**
|
|
53
|
+
* The only preflight code that does not stop a run.
|
|
54
|
+
*
|
|
55
|
+
* A stranded `params` value is a fact about the document: the graph cooks
|
|
56
|
+
* perfectly well on its defaults, which is exactly the problem, and refusing to
|
|
57
|
+
* run is the worse answer. Everything else in the preflight is a reason this
|
|
58
|
+
* host cannot execute this graph — an environment mismatch and a missing
|
|
59
|
+
* capability both mean the cook would fail partway with a worse message.
|
|
60
|
+
*/
|
|
61
|
+
const RUN_TOLERATED_CODES = new Set([
|
|
62
|
+
"runtime/stray-params",
|
|
63
|
+
]);
|
|
32
64
|
class MutableAbortSignal {
|
|
33
65
|
report;
|
|
34
66
|
aborted = false;
|
|
@@ -148,6 +180,10 @@ class Graph {
|
|
|
148
180
|
onDispose;
|
|
149
181
|
graphState = "ready";
|
|
150
182
|
byId;
|
|
183
|
+
dataConnectionsByInput;
|
|
184
|
+
dataConnectionsByNode;
|
|
185
|
+
connectedInputs;
|
|
186
|
+
connectedOutputs;
|
|
151
187
|
listeners = new Set();
|
|
152
188
|
diagnostics = [];
|
|
153
189
|
active;
|
|
@@ -173,6 +209,34 @@ class Graph {
|
|
|
173
209
|
this.connections = connections;
|
|
174
210
|
this.onDispose = onDispose;
|
|
175
211
|
this.byId = new Map(nodes.map((node) => [node.id, node]));
|
|
212
|
+
const byInput = new Map();
|
|
213
|
+
const byNode = new Map();
|
|
214
|
+
const connectedInputs = new Set();
|
|
215
|
+
const connectedOutputs = new Set();
|
|
216
|
+
for (const connection of connections) {
|
|
217
|
+
connectedInputs.add(`${connection.target.nodeId}\0${connection.target.inputName}`);
|
|
218
|
+
connectedOutputs.add(`${connection.source.nodeId}\0${connection.source.outputName}`);
|
|
219
|
+
if (connection.kind !== "data")
|
|
220
|
+
continue;
|
|
221
|
+
const inputKey = `${connection.target.nodeId}\0${connection.target.inputName}`;
|
|
222
|
+
const inputConnections = byInput.get(inputKey) ?? [];
|
|
223
|
+
inputConnections.push(connection);
|
|
224
|
+
byInput.set(inputKey, inputConnections);
|
|
225
|
+
const nodeConnections = byNode.get(connection.target.nodeId) ?? [];
|
|
226
|
+
nodeConnections.push(connection);
|
|
227
|
+
byNode.set(connection.target.nodeId, nodeConnections);
|
|
228
|
+
}
|
|
229
|
+
this.dataConnectionsByInput = new Map([...byInput].map(([key, inputConnections]) => [
|
|
230
|
+
key,
|
|
231
|
+
Object.freeze(inputConnections.sort((first, second) => (first.variadicIndex ?? first.order) -
|
|
232
|
+
(second.variadicIndex ?? second.order))),
|
|
233
|
+
]));
|
|
234
|
+
this.dataConnectionsByNode = new Map([...byNode].map(([key, nodeConnections]) => [
|
|
235
|
+
key,
|
|
236
|
+
Object.freeze(nodeConnections.sort((first, second) => first.order - second.order)),
|
|
237
|
+
]));
|
|
238
|
+
this.connectedInputs = connectedInputs;
|
|
239
|
+
this.connectedOutputs = connectedOutputs;
|
|
176
240
|
this.graphInputs = graphBoundaries(nodes, "cascade.core.Input", "inputName");
|
|
177
241
|
this.graphOutputs = graphBoundaries(nodes, "cascade.core.Output", "outputName");
|
|
178
242
|
this.authoredAnnotations = Object.freeze(annotations.map((annotation) => snapshot(annotation)));
|
|
@@ -180,10 +244,7 @@ class Graph {
|
|
|
180
244
|
nodes,
|
|
181
245
|
node: (id) => this.byId.get(id),
|
|
182
246
|
inputNode: (node, index) => {
|
|
183
|
-
const feeding = this.
|
|
184
|
-
.filter((connection) => connection.kind === "data" &&
|
|
185
|
-
connection.target.nodeId === node.id)
|
|
186
|
-
.sort((a, b) => a.order - b.order);
|
|
247
|
+
const feeding = this.dataConnectionsByNode.get(node.id) ?? [];
|
|
187
248
|
const source = feeding[index]?.source.nodeId;
|
|
188
249
|
return (source ? this.byId.get(source) : undefined) ?? null;
|
|
189
250
|
},
|
|
@@ -228,8 +289,7 @@ class Graph {
|
|
|
228
289
|
{
|
|
229
290
|
kind: definition.kind,
|
|
230
291
|
type: definition.kind === "trigger" ? "trigger" : definition.type,
|
|
231
|
-
connected: this.
|
|
232
|
-
connection.target.inputName === name),
|
|
292
|
+
connected: this.connectedInputs.has(`${node.id}\0${name}`),
|
|
233
293
|
...(definition.kind === "data"
|
|
234
294
|
? { value: snapshot(node.inputs[name]) }
|
|
235
295
|
: {}),
|
|
@@ -240,8 +300,7 @@ class Graph {
|
|
|
240
300
|
{
|
|
241
301
|
kind: definition.kind,
|
|
242
302
|
type: definition.kind === "trigger" ? "trigger" : definition.type,
|
|
243
|
-
connected: this.
|
|
244
|
-
connection.source.outputName === name),
|
|
303
|
+
connected: this.connectedOutputs.has(`${node.id}\0${name}`),
|
|
245
304
|
...(definition.kind === "data" && node.outputs.has(name)
|
|
246
305
|
? { value: snapshot(node.outputs.get(name)) }
|
|
247
306
|
: {}),
|
|
@@ -549,10 +608,7 @@ class Graph {
|
|
|
549
608
|
const index = integerProp(node.props.inputIndex);
|
|
550
609
|
return [name, this.dataInputValue(parent, `input_${index}`)];
|
|
551
610
|
}
|
|
552
|
-
const connections = this.
|
|
553
|
-
item.target.nodeId === node.id &&
|
|
554
|
-
item.target.inputName === name).sort((first, second) => (first.variadicIndex ?? first.order) -
|
|
555
|
-
(second.variadicIndex ?? second.order));
|
|
611
|
+
const connections = this.dataConnectionsByInput.get(`${node.id}\0${name}`) ?? [];
|
|
556
612
|
if (definition.variadic) {
|
|
557
613
|
const values = connections.map((connection) => this.requireNode(connection.source.nodeId).outputs.get(connection.source.outputName));
|
|
558
614
|
if (!values.length && node.inputs[name] !== undefined)
|
|
@@ -740,7 +796,7 @@ class Graph {
|
|
|
740
796
|
: `provides no capabilities`;
|
|
741
797
|
}
|
|
742
798
|
assertPreflight(nodes) {
|
|
743
|
-
const [first] = this.preflightDiagnostics(nodes).filter((diagnostic) => !
|
|
799
|
+
const [first] = this.preflightDiagnostics(nodes).filter((diagnostic) => !RUN_TOLERATED_CODES.has(diagnostic.code));
|
|
744
800
|
if (first)
|
|
745
801
|
throw new CascadeRuntimeError(first.code, [first]);
|
|
746
802
|
}
|
|
@@ -754,7 +810,8 @@ class Graph {
|
|
|
754
810
|
}
|
|
755
811
|
runSelection(target) {
|
|
756
812
|
if (target.kind === "all") {
|
|
757
|
-
const
|
|
813
|
+
const gatedDataDescendants = this.gatedDataDescendants();
|
|
814
|
+
const initial = this.nodes.filter((node) => !isGated(node) && !gatedDataDescendants.has(node.id));
|
|
758
815
|
const ids = new Set(initial.map((node) => node.id));
|
|
759
816
|
let changed = true;
|
|
760
817
|
while (changed) {
|
|
@@ -799,8 +856,25 @@ class Graph {
|
|
|
799
856
|
}
|
|
800
857
|
return this.nodes.filter((node) => ids.has(node.id));
|
|
801
858
|
}
|
|
802
|
-
|
|
803
|
-
|
|
859
|
+
gatedDataDescendants() {
|
|
860
|
+
const outgoing = new Map();
|
|
861
|
+
for (const dependency of dataDependencies(this.nodes, this.connections)) {
|
|
862
|
+
const targets = outgoing.get(dependency.sourceId) ?? [];
|
|
863
|
+
targets.push(dependency.targetId);
|
|
864
|
+
outgoing.set(dependency.sourceId, targets);
|
|
865
|
+
}
|
|
866
|
+
const queue = this.nodes.filter(isGated).map((node) => node.id);
|
|
867
|
+
const descendants = new Set(queue);
|
|
868
|
+
let queueIndex = 0;
|
|
869
|
+
while (queueIndex < queue.length) {
|
|
870
|
+
for (const targetId of outgoing.get(queue[queueIndex++]) ?? []) {
|
|
871
|
+
if (descendants.has(targetId))
|
|
872
|
+
continue;
|
|
873
|
+
descendants.add(targetId);
|
|
874
|
+
queue.push(targetId);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
return descendants;
|
|
804
878
|
}
|
|
805
879
|
triggerDataAncestors(nodeId) {
|
|
806
880
|
const dependencies = this.dataClosure(nodeId).filter((node) => node.id !== nodeId);
|
|
@@ -949,7 +1023,21 @@ function materialize(authored, registration) {
|
|
|
949
1023
|
for (const [name, definition] of Object.entries(registration.definition.props ?? {}))
|
|
950
1024
|
props[name] = runtimeValue(definition.default, definition.type, false, `${authored.id}.${name}`);
|
|
951
1025
|
const bindings = {};
|
|
952
|
-
|
|
1026
|
+
// A declared default expression, applied only where the document stored
|
|
1027
|
+
// nothing at all for the prop. The loop below then runs over what *was*
|
|
1028
|
+
// stored, so an authored value or an authored expression overwrites this —
|
|
1029
|
+
// which is the whole contract: a default expression is a default, and a file
|
|
1030
|
+
// that saved a plain number keeps its number rather than quietly animating.
|
|
1031
|
+
const storedProps = authored.props ?? {};
|
|
1032
|
+
for (const [name, definition] of Object.entries(registration.definition.props ?? {})) {
|
|
1033
|
+
if (!definition.expression || name in storedProps)
|
|
1034
|
+
continue;
|
|
1035
|
+
bindings[name] = {
|
|
1036
|
+
value: props[name],
|
|
1037
|
+
expression: definition.expression,
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
for (const [name, saved] of Object.entries(storedProps)) {
|
|
953
1041
|
const definition = registration.definition.props?.[name];
|
|
954
1042
|
if (!definition)
|
|
955
1043
|
continue;
|
|
@@ -961,8 +1049,12 @@ function materialize(authored, registration) {
|
|
|
961
1049
|
: null;
|
|
962
1050
|
const value = runtimeValue(object ? object.value : saved, definition.type, false, `${authored.id}.${name}`);
|
|
963
1051
|
props[name] = value;
|
|
964
|
-
|
|
1052
|
+
// A stored plain value is an author's decision and beats the declared
|
|
1053
|
+
// default expression, so any binding seeded above has to go.
|
|
1054
|
+
if (!object) {
|
|
1055
|
+
delete bindings[name];
|
|
965
1056
|
continue;
|
|
1057
|
+
}
|
|
966
1058
|
const channel = deserializeChannel(object.channel);
|
|
967
1059
|
const expression = typeof object.expression === "string" && object.expression
|
|
968
1060
|
? object.expression
|
|
@@ -1265,6 +1357,7 @@ function validateTopology(nodes, connections) {
|
|
|
1265
1357
|
topological(nodes, connections);
|
|
1266
1358
|
}
|
|
1267
1359
|
function topological(nodes, connections) {
|
|
1360
|
+
const byId = new Map(nodes.map((node) => [node.id, node]));
|
|
1268
1361
|
const indegree = new Map(nodes.map((node) => [node.id, 0]));
|
|
1269
1362
|
const outgoing = new Map(nodes.map((node) => [node.id, []]));
|
|
1270
1363
|
for (const dependency of graphDependencies(nodes, connections)) {
|
|
@@ -1273,13 +1366,14 @@ function topological(nodes, connections) {
|
|
|
1273
1366
|
}
|
|
1274
1367
|
const queue = nodes.filter((node) => indegree.get(node.id) === 0);
|
|
1275
1368
|
const result = [];
|
|
1276
|
-
|
|
1277
|
-
|
|
1369
|
+
let queueIndex = 0;
|
|
1370
|
+
while (queueIndex < queue.length) {
|
|
1371
|
+
const node = queue[queueIndex++];
|
|
1278
1372
|
result.push(node);
|
|
1279
1373
|
for (const id of outgoing.get(node.id)) {
|
|
1280
1374
|
indegree.set(id, indegree.get(id) - 1);
|
|
1281
1375
|
if (indegree.get(id) === 0)
|
|
1282
|
-
queue.push(
|
|
1376
|
+
queue.push(byId.get(id));
|
|
1283
1377
|
}
|
|
1284
1378
|
}
|
|
1285
1379
|
if (result.length !== nodes.length)
|
package/dist/runtime/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AssetCapability, AssetRef, CascadeAbortSignal, CascadeDocument, CascadePreset, Diagnostic, FileCapability, ImageRef, JsonValue, MediaCapability, NodeDefinition, NodeExecute, ProgressReporter, PythonCapability, SerializableValue, ShellCapability, TextureHandle,
|
|
1
|
+
import type { AssetCapability, AssetRef, CascadeAbortSignal, CascadeDocument, CascadePreset, Diagnostic, FileCapability, ImageRef, JsonValue, MediaCapability, NodeDefinition, NodeExecute, ProgressReporter, PythonCapability, SerializableValue, ShellCapability, TextureHandle, GpuCapability } from "@field/cascade/contracts";
|
|
2
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";
|
|
@@ -31,7 +31,7 @@ export interface RuntimeCapabilities {
|
|
|
31
31
|
readonly assets?: AssetCapability;
|
|
32
32
|
readonly media?: MediaCapability;
|
|
33
33
|
readonly python?: PythonCapability;
|
|
34
|
-
readonly
|
|
34
|
+
readonly gpu?: GpuCapability;
|
|
35
35
|
readonly shell?: ShellCapability;
|
|
36
36
|
readonly serverBridge?: ServerBridgeCapability;
|
|
37
37
|
}
|
|
@@ -246,7 +246,7 @@ export interface LoadedCascadeGraph {
|
|
|
246
246
|
getOutputs(nodeId: string): ReadonlyMap<string, unknown>;
|
|
247
247
|
dispose(): Promise<void>;
|
|
248
248
|
}
|
|
249
|
-
export interface NodeRuntimeHostOptions extends Omit<RuntimeCapabilities, "
|
|
249
|
+
export interface NodeRuntimeHostOptions extends Omit<RuntimeCapabilities, "gpu"> {
|
|
250
250
|
readonly modules: RuntimeHost["modules"];
|
|
251
251
|
readonly legacyShell?: LegacyShellBindingFactory & Readonly<{
|
|
252
252
|
kind: "server-direct";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@field/cascade",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Visual programming for generative art",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "FIELD.IO <marcus@field.io>",
|
|
@@ -83,6 +83,10 @@
|
|
|
83
83
|
"types": "./dist/io.d.ts",
|
|
84
84
|
"default": "./dist/io.js"
|
|
85
85
|
},
|
|
86
|
+
"./gpu": {
|
|
87
|
+
"types": "./dist/gpu.d.ts",
|
|
88
|
+
"default": "./dist/gpu.js"
|
|
89
|
+
},
|
|
86
90
|
"./net": {
|
|
87
91
|
"types": "./dist/net.d.ts",
|
|
88
92
|
"default": "./dist/net.js"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{b as e,f as t,s as n,x as r}from"./index-HTCN44kj.js";r(n,{assets:e});export{t as geoLibrary};
|