From f40335f4e77491171fe6c2b27d70c7a2a287bb56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Duhen?= Date: Mon, 6 May 2024 16:01:04 +0200 Subject: [PATCH 01/44] feat: add Graph and basic nodes --- src/Graph/Common.ts | 36 ++++ src/Graph/Graph.ts | 250 ++++++++++++++++++++++++++++ src/Graph/Nodes/GraphNode.ts | 60 +++++++ src/Graph/Nodes/InputNode.ts | 30 ++++ src/Graph/Nodes/ProcessorNode.ts | 41 +++++ src/Graph/Nodes/ScreenShaderNode.ts | 107 ++++++++++++ 6 files changed, 524 insertions(+) create mode 100644 src/Graph/Common.ts create mode 100644 src/Graph/Graph.ts create mode 100644 src/Graph/Nodes/GraphNode.ts create mode 100644 src/Graph/Nodes/InputNode.ts create mode 100644 src/Graph/Nodes/ProcessorNode.ts create mode 100644 src/Graph/Nodes/ScreenShaderNode.ts diff --git a/src/Graph/Common.ts b/src/Graph/Common.ts new file mode 100644 index 0000000000..8c2b15417a --- /dev/null +++ b/src/Graph/Common.ts @@ -0,0 +1,36 @@ +import Graph from './Graph.ts'; +import GraphNode from './Nodes/GraphNode.ts'; +import InputNode from './Nodes/InputNode.ts'; +import ProcessorNode from './Nodes/ProcessorNode.ts'; + +enum BuiltinType { + Number = 'Number', // TODO: remove, just here for testing + GeoData = 'GeoData', // TODO: split into different data formats + Renderer = 'Renderer', + Texture = 'Texture', + CRS = 'CRS', // Coordinate Reference System +} + +export type Type = string; +export type Dependency = GraphNode | undefined | null; + +export interface DumpDotNodeStyle { + label: (name: string) => string; + attrs: { [key: string]: string }; +} + +export interface DumpDotGlobalStyle { + node: { [key: string]: string }; + edge: { [key: string]: string }; +} + +export { + // Classes + Graph, + GraphNode, + InputNode, + ProcessorNode, + + // Utils + BuiltinType, +}; diff --git a/src/Graph/Graph.ts b/src/Graph/Graph.ts new file mode 100644 index 0000000000..7ac2cdb0a0 --- /dev/null +++ b/src/Graph/Graph.ts @@ -0,0 +1,250 @@ +import GraphNode from './Nodes/GraphNode.ts'; +import { DumpDotGlobalStyle, Type } from './Common.ts'; + +/** Represents a directed graph that guarantees the absence of cycles on use. */ +export default class Graph { + public nodes: Map; + public types: Set; + private _valid: boolean; + + public constructor() { + this.nodes = new Map(); + this.types = new Set(); + this._valid = false; + } + + public get valid(): boolean { + return this._valid; + } + + /** + * Get the output of a node at a given frame. + * @throws If the graph is invalid. + * @throws If the node does not exist. + * @returns The output of the node at the given frame. + */ + public getOutput(frame: number, node: string | GraphNode): any { + this.validate(); + + const out = typeof node === 'string' ? this.nodes.get(node) : node; + + if (out == undefined) { + throw new Error(`Node ${node} does not exist`); + } + + return out.getOutput(frame); + } + + /** + * Get a node by name. + * @returns The node with the given name. + */ + public get(name: string): GraphNode | undefined { + return this.nodes.get(name); + } + + /** + * Add or update a node. If the node already exists, it will be updated. + * @throws If the node is orphaned and the graph has at least one node already. + * @returns True if the node was added or updated, false otherwise. + */ + public set(name: string, node: GraphNode): boolean { + if (this.nodes.size > 0 && node.inputs.size === 0) { + throw new Error('Orphaned node'); + } + + this._valid = false; + + this.nodes.set(name, node); + this.types.add(node.outputType); + + return true; + } + + /** + * Add or update multiple nodes at once. Check the documentation of {@link set} + * for more details. + * Using numerical object keys is not recommended, as they will be automatically sorted, + * possibly leading to unexpected behavior. + * @throws If any of the nodes are orphaned and the graph has at least one node already. + * @returns A map of the results of the set operation. + */ + public set_grouped(nodes: { + [name: string]: GraphNode; + }): Map { + const results = new Map(); + for (const [name, node] of Object.entries(nodes)) { + results.set(name, this.set(name, node)); + } + return results; + } + + /** + * Determine if the graph is valid. A graph is considered valid if it does + * not contain cycles nor dangling dependencies. + * @throws If the graph is invalid. + */ + public validate() { + if (this._valid) { + return; + } + + const visited = new Set(); + for (const [name, node] of this.nodes) { + if (visited.has(name)) { + continue; + } + + this._validation_dfs(node, new Set(), visited); + } + + this._valid = true; + } + + /** + * Depth-first search for cycles and dangling dependencies. + * @throws If a cycle is detected or a dangling dependency is found. + */ + private _validation_dfs( + node: GraphNode, + path: Set, + visited: Set, + ) { + // Cycle detection + for (const [name, _dep] of node.inputs ?? []) { + if (path.has(name)) { + throw new Error( + `Cycle detected: ${Array.from(path).join(' -> ')} -> ${name}`, + ); + } + } + + // DFS + for (const [name, dep] of node.inputs ?? []) { + // Dangling dependency check + if (dep == undefined) { + throw new Error(`Dangling dependency: ${name}`); + } + + if (visited.has(name)) { + continue; + } + + path.add(name); + this._validation_dfs(dep, path, visited); + path.delete(name); + } + } + + /** Depth-first traversal of the graph. */ + public dfs( + node: GraphNode, + { prefix, infix, postfix, undef }: DfsCallbacks, + ) { + prefix?.(node); + + const inputs = node.inputs; + + let index = 0; + for (const [name, input] of inputs.entries()) { + if (input != undefined) { + this.dfs(input, { prefix, infix, postfix, undef }); + } else { + undef?.(name); + index++; + continue; + } + + // Run the infix between every dependency + if (index < inputs.size - 1) { + infix?.(node); + } + + index++; + } + + // Run the infix at least once per node even without dependencies + if (inputs.size <= 1) { + infix?.(node); + } + + postfix?.(node); + } + + public get dumpDotStyle(): DumpDotGlobalStyle { + return { + node: { + fontname: 'Arial', + }, + edge: { + fontname: 'Arial', + }, + }; + } + + /** + * Dump the graph in the DOT format. + * @throws If a node input is not part of the graph. + * @returns The graph in the DOT format. + */ + public dumpDot(): string { + const dump: string[] = ['digraph G {']; + + if (this.nodes.size > 0) { + // Global style defaults + Object.entries(this.dumpDotStyle).forEach(([type, attrs]) => { + const formattedAttrs = Object.entries(attrs) + .map(([k, v]) => `${k}=${v}`) + .join(' '); + dump.push(`\t${type} [${formattedAttrs}]`); + }); + + // Declare nodes + dump.push('\t{'); + for (const [name, node] of this.nodes) { + dump.push(`\t\t'${name}' ${node.dumpDotAttr(name)};`); + } + dump.push('\t}'); + + // Declare edges + const entries = Array.from(this.nodes.entries()); + for (const [name, node] of this.nodes) { + for (const [_, dep] of node.inputs) { + if (dep == null) continue; + + // PERF: Inefficient but the alternative is duplicating the names + // inside the nodes and that makes the API much heavier so we'll + // live with it, this will likely never be an issue. + const inputName = entries.find( + ([_, oNode]) => oNode == dep, + )?.[0]; + if (inputName == undefined) { + throw new Error( + `An input of node ${name} is not part of the graph`, + ); + } + const attrs = node.dumpDotEdgeAttr(); + + dump.push(`\t'${inputName}' -> '${name}' ${attrs};`); + } + } + } + + dump.push('}'); + + return dump.join('\n'); + } +} + +type NodeCallback = (node: GraphNode) => void; +type StringCallback = (string: string) => void; +interface DfsCallbacks { + /** Run before the dependencies. */ + prefix?: NodeCallback; + /** Run between every dependency or once if less than 2 dependencies. */ + infix?: NodeCallback; + /** Run after the dependencies. */ + postfix?: NodeCallback; + /** Run when an undefined dependency is found. */ + undef?: StringCallback; +} diff --git a/src/Graph/Nodes/GraphNode.ts b/src/Graph/Nodes/GraphNode.ts new file mode 100644 index 0000000000..ffa3f06d25 --- /dev/null +++ b/src/Graph/Nodes/GraphNode.ts @@ -0,0 +1,60 @@ +import { Type, Dependency, DumpDotNodeStyle } from '../Common.ts'; + +/** + * Represents a node in a directed graph. + * Base class for all other types of nodes. + */ +export default abstract class GraphNode { + public inputs: Map; + public outputType: Type; + + protected _out: [number, any | undefined]; + + public constructor(inputs: Map, outputType: Type) { + this.inputs = inputs; + this.outputType = outputType; + this._out = [-1, undefined]; + } + + protected abstract _apply(_frame: number): any; + + protected abstract get _node_type(): string; + + /** + * Get the output of the node at a given frame. + * @param frame The frame to get the output for. + * @returns The output of the node at the given frame. + */ + public getOutput(frame: number): any { + const [oFrane, oValue] = this._out; + if (oValue == undefined || oFrane !== frame) { + this._out = [frame, this._apply(frame)]; + } + return this._out[1]; + } + + /** + * Get the style for the node when dumping to DOT format. + */ + public abstract get dumpDotStyle(): DumpDotNodeStyle; + + /** + * Get the DOT attribute string for the node. + * @param name The name of the node. + */ + public dumpDotAttr(name: string): string { + const { label, attrs } = this.dumpDotStyle; + const formattedAttrs = Object.entries(attrs) + .map(([k, v]) => `${k}=${v}`) + .join(' '); + + return `[label="${label(name)}" ${formattedAttrs}]`; + } + + /** + * Get the DOT attribute string for the edge between this node and its inputs. + */ + public dumpDotEdgeAttr(): string { + return `[label=" ${this.outputType}"]`; + } +} diff --git a/src/Graph/Nodes/InputNode.ts b/src/Graph/Nodes/InputNode.ts new file mode 100644 index 0000000000..90f08543c5 --- /dev/null +++ b/src/Graph/Nodes/InputNode.ts @@ -0,0 +1,30 @@ +import GraphNode from './GraphNode.ts'; +import { Type, DumpDotNodeStyle } from '../Common.ts'; + +/** Represents a node that outputs a constant value. */ +export default class InputNode extends GraphNode { + public value: any; + + public constructor(value: any, type: Type) { + super(new Map(), type); + this.value = value; + } + + protected _apply(_frame: number): any { + return this.value; + } + + protected get _node_type(): string { + return 'Input'; + } + + public get dumpDotStyle(): DumpDotNodeStyle { + return { + label: (name) => `${name}: ${this.value}`, + attrs: { + shape: 'invtrapezium', + color: 'goldenrod', + }, + }; + } +} diff --git a/src/Graph/Nodes/ProcessorNode.ts b/src/Graph/Nodes/ProcessorNode.ts new file mode 100644 index 0000000000..5af10ac71c --- /dev/null +++ b/src/Graph/Nodes/ProcessorNode.ts @@ -0,0 +1,41 @@ +import GraphNode from './GraphNode.ts'; +import { Type, Dependency, DumpDotNodeStyle } from '../Common.ts'; + +/** Represents a mapping from a set of inputs to an output. */ +export default class ProcessorNode extends GraphNode { + public callback: (frame: number, args: any) => any; + + public constructor( + inputs: { [name: string]: Dependency }, + outputType: Type, + callback: (frame: number, args: any) => any, + ) { + super(new Map(Object.entries(inputs)), outputType); + this.callback = callback; + } + + protected _apply(frame: number): any { + const inputs = Array.from(this.inputs); + const args: [string, any][] = inputs.map(([name, dependency]) => [ + name, + dependency?.getOutput(frame) ?? null, + ]); + const argObj = Object.fromEntries(args); + + return this.callback(frame, argObj); + } + + protected get _node_type(): string { + return 'Processor'; + } + + public get dumpDotStyle(): DumpDotNodeStyle { + return { + label: name => `${name}`, + attrs: { + shape: 'box', + color: 'lightskyblue', + }, + }; + } +} diff --git a/src/Graph/Nodes/ScreenShaderNode.ts b/src/Graph/Nodes/ScreenShaderNode.ts new file mode 100644 index 0000000000..9a09469cad --- /dev/null +++ b/src/Graph/Nodes/ScreenShaderNode.ts @@ -0,0 +1,107 @@ +import * as THREE from 'three'; +import { BuiltinType, Dependency, DumpDotNodeStyle } from '../Common.ts'; +import ProcessorNode from './ProcessorNode.ts'; + +export default class ScreenShaderNode extends ProcessorNode { + private static get vertexShader() { + return ` + varying vec2 vUv; + + void main() { + vUv = uv; + gl_Position = vec4(position, 1.0); + } + ` + }; + + // WARN: This is a temporary hack. Essentially a scuffed singleton pack. + // PERF: Evaluate the cost of having a scene per shader node instead. + private static _scene: THREE.Scene; + private static _quad: THREE.Mesh; + private static _camera: THREE.Camera; + + // Kept for debug purposes + private _fragmentShader: string; + private _material: THREE.ShaderMaterial; + + private static _init() { + if (ScreenShaderNode._scene == undefined) { + ScreenShaderNode._scene = new THREE.Scene(); + + // Setup the quad used to render the effects + ScreenShaderNode._quad = new THREE.Mesh(new THREE.PlaneGeometry(1, 1)); + ScreenShaderNode._quad.frustumCulled = false; + + ScreenShaderNode._scene.add(ScreenShaderNode._quad); + + ScreenShaderNode._camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1); + } + } + + /** + * Create a new screen shader node. + * @param input The input {@link THREE.Texture}. + * @param renderer The {@link THREE.WebGLRenderer} to render with. + */ + public constructor( + input: Dependency, + renderer: Dependency, + uniforms?: { [name: string]: Dependency }, + fragmentShader?: string + ) { + ScreenShaderNode._init(); + + // Unpacking the uniforms object first allows us to ignore potential "input" and "renderer" fields. + super({ ...(uniforms ?? {}), input, renderer }, BuiltinType.Texture, (_frame, args) => { + const input = args.input as THREE.Texture; + const renderer = args.renderer as THREE.WebGLRenderer; + + const target: THREE.WebGLRenderTarget = this._out[1] ?? new THREE.WebGLRenderTarget( + input.image.width, + input.image.height + ); + + this._material.uniforms.uTexture.value = input; + for (const [name, value] of Object.entries(args)) { + if (name === "input" || name === "renderer") { + continue; + } + + this._material.uniforms[name] = { value }; + } + ScreenShaderNode._quad.material = this._material; + + renderer.setRenderTarget(target); + renderer.clear(); + renderer.render(ScreenShaderNode._scene, ScreenShaderNode._camera); + }); + + this._fragmentShader = ` + precision highp float; + varying vec2 vUv; + uniform sampler2D uTexture; + + ${fragmentShader ?? + `void main() { + vec4 color = texture2D(uTexture, vUv); + gl_FragColor = color; + }` + }`; + + this._material = new THREE.ShaderMaterial({ + fragmentShader: this._fragmentShader, + vertexShader: ScreenShaderNode.vertexShader, + }); + } + + public get dumpDotStyle(): DumpDotNodeStyle { + const { label, attrs } = super.dumpDotStyle; + return { + label, + attrs: { + ...attrs, + fillcolor: "lightcoral", + } + }; + } +} From a1d62e165e73ab7ad46191027b870fb88e0cee12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Duhen?= Date: Tue, 7 May 2024 11:24:06 +0200 Subject: [PATCH 02/44] build: make ts work --- .babelrc | 10 +- babel.config.json | 5 + package-lock.json | 374 ++++++++++++++++++++++++++++++++++++++------- package.json | 3 +- src/Graph/Graph.ts | 2 + src/Main.js | 4 +- src/glsl.d.ts | 4 + tsconfig.json | 1 + webpack.config.cjs | 49 ++---- 9 files changed, 347 insertions(+), 105 deletions(-) create mode 100644 babel.config.json create mode 100644 src/glsl.d.ts diff --git a/.babelrc b/.babelrc index ded830aa4d..38ae9c6b36 100644 --- a/.babelrc +++ b/.babelrc @@ -1,11 +1,6 @@ { "presets": [ - ["@babel/preset-env", { - "targets": { - "browsers": "defaults and supports webgl2" - }, - "modules": false - }] + ["@babel/preset-typescript"] ], "plugins": [ ["module-resolver", { "root": ["./src"] } ], @@ -18,9 +13,6 @@ ] }], ["module-extension-resolver"], - ["@babel/plugin-transform-runtime", { - "regenerator": false - }], ["minify-replace", { "replacements": [{ "identifierName": "__DEBUG__", diff --git a/babel.config.json b/babel.config.json new file mode 100644 index 0000000000..001ec6fbf8 --- /dev/null +++ b/babel.config.json @@ -0,0 +1,5 @@ +{ + "babelrcRoots": [ + "." + ] +} diff --git a/package-lock.json b/package-lock.json index 13c9b288e9..fcebdaa41d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "@babel/cli": "^7.22.5", "@babel/plugin-transform-runtime": "^7.22.5", "@babel/preset-env": "^7.22.5", + "@babel/preset-typescript": "^7.24.1", "@babel/register": "^7.22.5", "@types/three": "^0.159.0", "@xmldom/xmldom": "^0.8.10", @@ -260,6 +261,8 @@ }, "node_modules/@babel/helper-annotate-as-pure": { "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", + "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", "dev": true, "license": "MIT", "dependencies": { @@ -299,19 +302,20 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.22.5", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.5.tgz", + "integrity": "sha512-uRc4Cv8UQWnE4NXlYTIIdM7wfFkOqlFztcC/gVXDKohKoVB3OyonfelUBaJzSwpBntZ2KYGF/9S7asCHsXwW6g==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", - "@babel/helper-member-expression-to-functions": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-member-expression-to-functions": "^7.24.5", "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-replace-supers": "^7.22.5", + "@babel/helper-replace-supers": "^7.24.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.5", - "semver": "^6.3.0" + "@babel/helper-split-export-declaration": "^7.24.5", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -376,6 +380,8 @@ }, "node_modules/@babel/helper-hoist-variables": { "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, "license": "MIT", "dependencies": { @@ -386,43 +392,46 @@ } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.22.5", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.5.tgz", + "integrity": "sha512-4owRteeihKWKamtqg4JmWSsEZU445xpFRXPEwp44HbgbxdWlUV1b4Agg4lkA806Lil5XM/e+FJyS0vj5T6vmcA==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.22.5", + "version": "7.24.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz", + "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.22.5", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.5.tgz", + "integrity": "sha512-9GxeY8c2d2mdQUP1Dye0ks3VDyIMS98kt/llQ2nUId8IsWqTF0l1LkSX0/uP7l7MCDrzXS009Hyhe2gzTiGW8A==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-module-imports": "^7.22.5", - "@babel/helper-simple-access": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.24.3", + "@babel/helper-simple-access": "^7.24.5", + "@babel/helper-split-export-declaration": "^7.24.5", + "@babel/helper-validator-identifier": "^7.24.5" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, "node_modules/@babel/helper-optimise-call-expression": { @@ -439,9 +448,10 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.22.5", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.5.tgz", + "integrity": "sha512-xjNLDopRzW2o6ba0gKbkZq5YWEBaK3PCyTOY1K2P/O07LGMhMqlMXPxwN4S5/RhWuCobT8z0jrlKGlYmeR1OhQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -464,27 +474,29 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.22.5", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.1.tgz", + "integrity": "sha512-QCR1UqC9BzG5vZl8BMicmZ28RuUBnHhAMddD8yHFHDRH9lLTZ9uUPehX8ctVPT8l0TKblJidqcgUUKGVrePleQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-member-expression-to-functions": "^7.22.5", - "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-member-expression-to-functions": "^7.23.0", + "@babel/helper-optimise-call-expression": "^7.22.5" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, "node_modules/@babel/helper-simple-access": { - "version": "7.22.5", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.5.tgz", + "integrity": "sha512-uH3Hmf5q5n7n8mz7arjUlDOCbttY/DW4DYhE6FUsjKJ/oYC1kQQUvwEQWxRwUpX9qQKRXeqLwWxrqilMrf32sQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.5" }, "engines": { "node": ">=6.9.0" @@ -504,38 +516,40 @@ } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz", + "integrity": "sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.22.5", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", + "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz", + "integrity": "sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.22.5", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", "dev": true, - "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -697,6 +711,8 @@ }, "node_modules/@babel/plugin-proposal-private-property-in-object": { "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", "dev": true, "license": "MIT", "engines": { @@ -819,6 +835,8 @@ }, "node_modules/@babel/plugin-syntax-import-meta": { "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", "dev": true, "license": "MIT", "dependencies": { @@ -841,6 +859,21 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz", + "integrity": "sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-logical-assignment-operators": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", @@ -951,8 +984,25 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.1.tgz", + "integrity": "sha512-Yhnmvy5HZEnHUty6i++gcfH1/l68AHnItFHnaCv6hn9dNh0hQvvQJsxpi4BMBFN5DLeHBuucT/0DgzXif/OyRw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-unicode-sets-regex": { "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", "dev": true, "license": "MIT", "dependencies": { @@ -1301,12 +1351,13 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.22.5", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.1.tgz", + "integrity": "sha512-szog8fFTUxBfw0b98gEWPaEqF42ZUD/T3bkynW/wtgx2p/XCP55WEsb+VosKceRSd6njipdZvNogqdtI4Q0chw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.24.0", "@babel/helper-simple-access": "^7.22.5" }, "engines": { @@ -1350,6 +1401,8 @@ }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", + "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1650,6 +1703,24 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.24.5.tgz", + "integrity": "sha512-E0VWu/hk83BIFUWnsKZ4D81KXjN5L3MobvevOHErASk9IPwKHOkTgvqzvNo1yP/ePJWqqK2SpUR5z+KQbl6NVw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.24.5", + "@babel/helper-plugin-utils": "^7.24.5", + "@babel/plugin-syntax-typescript": "^7.24.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-transform-unicode-escapes": { "version": "7.22.5", "dev": true, @@ -1819,6 +1890,25 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/preset-typescript": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.24.1.tgz", + "integrity": "sha512-1DBaMmRDpuYQBPWD8Pf/WEwCrtgRHxsZnP4mIy9G/X+hFfbI47Q2G4t1Paakld84+qsk2fSsUPMKg71jkoOOaQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-validator-option": "^7.23.5", + "@babel/plugin-syntax-jsx": "^7.24.1", + "@babel/plugin-transform-modules-commonjs": "^7.24.1", + "@babel/plugin-transform-typescript": "^7.24.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/register": { "version": "7.22.5", "dev": true, @@ -1839,6 +1929,8 @@ }, "node_modules/@babel/regjsgen": { "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", "dev": true, "license": "MIT" }, @@ -1895,13 +1987,13 @@ } }, "node_modules/@babel/types": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", - "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.5.tgz", + "integrity": "sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-string-parser": "^7.24.1", + "@babel/helper-validator-identifier": "^7.24.5", "to-fast-properties": "^2.0.0" }, "engines": { @@ -1926,6 +2018,8 @@ }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", "dev": true, "license": "MIT", "dependencies": { @@ -2022,6 +2116,8 @@ }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -2154,6 +2250,8 @@ }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", "dev": true, "license": "MIT" }, @@ -2184,6 +2282,8 @@ }, "node_modules/@kurkle/color": { "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", + "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==", "dev": true, "license": "MIT" }, @@ -2204,6 +2304,8 @@ }, "node_modules/@mapbox/mapbox-gl-style-spec": { "version": "13.28.0", + "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-style-spec/-/mapbox-gl-style-spec-13.28.0.tgz", + "integrity": "sha512-B8xM7Fp1nh5kejfIl4SWeY0gtIeewbuRencqO3cJDrCHZpaPg7uY+V8abuR+esMeuOjRl5cLhVTP40v+1ywxbg==", "license": "ISC", "dependencies": { "@mapbox/jsonlint-lines-primitives": "~2.0.2", @@ -2253,6 +2355,8 @@ }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, "license": "MIT", "dependencies": { @@ -2265,6 +2369,8 @@ }, "node_modules/@nodelib/fs.stat": { "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, "license": "MIT", "engines": { @@ -2273,6 +2379,8 @@ }, "node_modules/@nodelib/fs.walk": { "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, "license": "MIT", "dependencies": { @@ -2722,11 +2830,15 @@ }, "node_modules/@webassemblyjs/floating-point-hex-parser": { "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", "dev": true, "license": "MIT" }, "node_modules/@webassemblyjs/helper-api-error": { "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", "dev": true, "license": "MIT" }, @@ -2737,6 +2849,8 @@ }, "node_modules/@webassemblyjs/helper-numbers": { "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", "dev": true, "license": "MIT", "dependencies": { @@ -2747,6 +2861,8 @@ }, "node_modules/@webassemblyjs/helper-wasm-bytecode": { "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", "dev": true, "license": "MIT" }, @@ -2763,6 +2879,8 @@ }, "node_modules/@webassemblyjs/ieee754": { "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", "dev": true, "license": "MIT", "dependencies": { @@ -2771,6 +2889,8 @@ }, "node_modules/@webassemblyjs/leb128": { "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -2779,6 +2899,8 @@ }, "node_modules/@webassemblyjs/utf8": { "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", "dev": true, "license": "MIT" }, @@ -2900,6 +3022,8 @@ }, "node_modules/acorn-import-assertions": { "version": "1.9.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", + "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", "dev": true, "license": "MIT", "peerDependencies": { @@ -2970,6 +3094,8 @@ }, "node_modules/ajv-formats/node_modules/ajv": { "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "dev": true, "license": "MIT", "dependencies": { @@ -3051,6 +3177,8 @@ }, "node_modules/anymatch": { "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, "license": "ISC", "dependencies": { @@ -3489,6 +3617,8 @@ }, "node_modules/babel-plugin-module-resolver/node_modules/glob": { "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", "dev": true, "license": "ISC", "dependencies": { @@ -3507,6 +3637,8 @@ }, "node_modules/babel-plugin-module-resolver/node_modules/minimatch": { "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, "license": "ISC", "dependencies": { @@ -4096,6 +4228,8 @@ }, "node_modules/colorette": { "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "dev": true, "license": "MIT" }, @@ -4215,6 +4349,8 @@ }, "node_modules/connect-history-api-fallback": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", "dev": true, "license": "MIT", "engines": { @@ -4883,6 +5019,8 @@ }, "node_modules/earcut": { "version": "2.2.4", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz", + "integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==", "license": "ISC" }, "node_modules/ee-first": { @@ -5428,6 +5566,8 @@ }, "node_modules/eslint/node_modules/glob-parent": { "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, "license": "ISC", "dependencies": { @@ -5510,6 +5650,8 @@ }, "node_modules/esquery": { "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -5766,6 +5908,8 @@ }, "node_modules/fastest-levenshtein": { "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", "dev": true, "license": "MIT", "engines": { @@ -5872,6 +6016,8 @@ }, "node_modules/find-babel-config": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/find-babel-config/-/find-babel-config-2.0.0.tgz", + "integrity": "sha512-dOKT7jvF3hGzlW60Gc3ONox/0rRZ/tz7WCil0bqA1In/3I8f1BctpXahRnEKDySZqci7u+dqq93sZST9fOJpFw==", "dev": true, "license": "MIT", "dependencies": { @@ -5943,6 +6089,8 @@ }, "node_modules/findup-sync": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-5.0.0.tgz", + "integrity": "sha512-MzwXju70AuyflbgeOhzvQWAvvQdo1XL0A9bVvlXsYcFEBM87WR4OakL4OfZq+QRmr+duJubio+UtNQCPsVESzQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6033,6 +6181,8 @@ }, "node_modules/for-each": { "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", "dev": true, "license": "MIT", "dependencies": { @@ -6318,6 +6468,8 @@ }, "node_modules/glob": { "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dev": true, "license": "ISC", "dependencies": { @@ -6412,6 +6564,8 @@ }, "node_modules/globalthis": { "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", "dev": true, "license": "MIT", "dependencies": { @@ -6426,6 +6580,8 @@ }, "node_modules/gopd": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", "dev": true, "license": "MIT", "dependencies": { @@ -6437,11 +6593,15 @@ }, "node_modules/graceful-fs": { "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true, "license": "ISC" }, "node_modules/graphemer": { "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true, "license": "MIT" }, @@ -6640,6 +6800,8 @@ }, "node_modules/grunt/node_modules/dateformat": { "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", "dev": true, "license": "MIT", "engines": { @@ -6902,6 +7064,8 @@ }, "node_modules/hpack.js/node_modules/readable-stream": { "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dev": true, "license": "MIT", "dependencies": { @@ -6971,6 +7135,8 @@ }, "node_modules/http-parser-js": { "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", "dev": true, "license": "MIT" }, @@ -7065,6 +7231,8 @@ }, "node_modules/iconv-lite": { "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dev": true, "license": "MIT", "dependencies": { @@ -7290,6 +7458,8 @@ }, "node_modules/ipaddr.js": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz", + "integrity": "sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==", "dev": true, "license": "MIT", "engines": { @@ -7375,6 +7545,8 @@ }, "node_modules/is-callable": { "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "dev": true, "license": "MIT", "engines": { @@ -7512,6 +7684,8 @@ }, "node_modules/is-path-inside": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true, "license": "MIT", "engines": { @@ -7927,6 +8101,8 @@ }, "node_modules/jsdoc": { "version": "4.0.2", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.2.tgz", + "integrity": "sha512-e8cIg2z62InH7azBBi3EsSEqrKx+nUtAS5bBcYTSpZFA+vhNPyhv8PTFZ0WsjOPDj04/dOLlm08EDcQJDqaGQg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -7965,6 +8141,8 @@ }, "node_modules/jsdoc/node_modules/marked": { "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", "dev": true, "license": "MIT", "bin": { @@ -8022,6 +8200,8 @@ }, "node_modules/json5": { "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, "license": "MIT", "bin": { @@ -8067,6 +8247,8 @@ }, "node_modules/jszip": { "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", "license": "(MIT OR GPL-3.0-or-later)", "dependencies": { "lie": "~3.3.0", @@ -8083,6 +8265,8 @@ }, "node_modules/jszip/node_modules/readable-stream": { "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "license": "MIT", "dependencies": { "core-util-is": "~1.0.0", @@ -8127,6 +8311,8 @@ }, "node_modules/klaw": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", + "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", "dev": true, "license": "MIT", "dependencies": { @@ -8324,6 +8510,8 @@ }, "node_modules/lru-cache": { "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, "license": "ISC", "dependencies": { @@ -8405,6 +8593,8 @@ }, "node_modules/markdown-it-anchor": { "version": "8.6.7", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz", + "integrity": "sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==", "dev": true, "license": "Unlicense", "peerDependencies": { @@ -8588,6 +8778,8 @@ }, "node_modules/minimist": { "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -8740,6 +8932,8 @@ }, "node_modules/multicast-dns": { "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", "dev": true, "license": "MIT", "dependencies": { @@ -9170,6 +9364,8 @@ }, "node_modules/open": { "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9885,6 +10081,8 @@ }, "node_modules/queue-microtask": { "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true, "funding": [ { @@ -10165,6 +10363,8 @@ }, "node_modules/regexpu-core": { "version": "5.3.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", + "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", "dev": true, "license": "MIT", "dependencies": { @@ -10181,6 +10381,8 @@ }, "node_modules/regjsparser": { "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -10260,6 +10462,8 @@ }, "node_modules/replace-in-file/node_modules/glob": { "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", "dev": true, "license": "ISC", "dependencies": { @@ -10278,6 +10482,8 @@ }, "node_modules/replace-in-file/node_modules/minimatch": { "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, "license": "ISC", "dependencies": { @@ -10302,6 +10508,8 @@ }, "node_modules/replace-in-file/node_modules/yargs": { "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "license": "MIT", "dependencies": { @@ -10366,6 +10574,8 @@ }, "node_modules/requizzle": { "version": "0.2.4", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", + "integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==", "dev": true, "license": "MIT", "dependencies": { @@ -10374,6 +10584,8 @@ }, "node_modules/reselect": { "version": "4.1.8", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz", + "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==", "dev": true, "license": "MIT" }, @@ -10462,6 +10674,8 @@ }, "node_modules/reusify": { "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", "dev": true, "license": "MIT", "engines": { @@ -10487,6 +10701,8 @@ }, "node_modules/run-parallel": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "dev": true, "funding": [ { @@ -10597,6 +10813,8 @@ }, "node_modules/schema-utils/node_modules/ajv": { "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "dev": true, "license": "MIT", "dependencies": { @@ -10612,6 +10830,8 @@ }, "node_modules/schema-utils/node_modules/ajv-keywords": { "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "dev": true, "license": "MIT", "dependencies": { @@ -10847,6 +11067,8 @@ }, "node_modules/setimmediate": { "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", "license": "MIT" }, "node_modules/setprototypeof": { @@ -10902,6 +11124,8 @@ }, "node_modules/shpjs": { "version": "4.0.4", + "resolved": "https://registry.npmjs.org/shpjs/-/shpjs-4.0.4.tgz", + "integrity": "sha512-+IcS2DoiTGqAONUEN46ZociKGJ2ecs1EVwJuSqnAOkMafxWC8noO3X/zI959RCbqHGvBr1RM1bdk4jc7fYONVg==", "license": "MIT", "dependencies": { "jszip": "^3.5.0", @@ -11000,6 +11224,8 @@ }, "node_modules/sockjs": { "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", "dev": true, "license": "MIT", "dependencies": { @@ -11157,6 +11383,8 @@ }, "node_modules/spdy-transport/node_modules/readable-stream": { "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "license": "MIT", "dependencies": { @@ -11469,6 +11697,8 @@ }, "node_modules/terser-webpack-plugin/node_modules/schema-utils": { "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "dev": true, "license": "MIT", "dependencies": { @@ -11572,6 +11802,8 @@ }, "node_modules/through2/node_modules/readable-stream": { "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dev": true, "license": "MIT", "dependencies": { @@ -11660,6 +11892,8 @@ }, "node_modules/tsconfig-paths/node_modules/json5": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "license": "MIT", "dependencies": { @@ -11866,6 +12100,8 @@ }, "node_modules/underscore": { "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", "dev": true, "license": "MIT" }, @@ -11909,6 +12145,8 @@ }, "node_modules/unicode-match-property-value-ecmascript": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", "dev": true, "license": "MIT", "engines": { @@ -11917,6 +12155,8 @@ }, "node_modules/unicode-property-aliases-ecmascript": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", "dev": true, "license": "MIT", "engines": { @@ -12201,6 +12441,8 @@ }, "node_modules/webpack-cli/node_modules/@webpack-cli/configtest": { "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", + "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", "dev": true, "license": "MIT", "engines": { @@ -12213,6 +12455,8 @@ }, "node_modules/webpack-cli/node_modules/@webpack-cli/info": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", + "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", "dev": true, "license": "MIT", "engines": { @@ -12225,6 +12469,8 @@ }, "node_modules/webpack-cli/node_modules/@webpack-cli/serve": { "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", + "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", "dev": true, "license": "MIT", "engines": { @@ -12242,6 +12488,8 @@ }, "node_modules/webpack-cli/node_modules/commander": { "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", "dev": true, "license": "MIT", "engines": { @@ -12250,6 +12498,8 @@ }, "node_modules/webpack-cli/node_modules/interpret": { "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", "dev": true, "license": "MIT", "engines": { @@ -12258,6 +12508,8 @@ }, "node_modules/webpack-cli/node_modules/rechoir": { "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", "dev": true, "license": "MIT", "dependencies": { @@ -12410,6 +12662,8 @@ }, "node_modules/webpack/node_modules/schema-utils": { "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", "dev": true, "license": "MIT", "dependencies": { @@ -12524,11 +12778,15 @@ }, "node_modules/wildcard": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", "dev": true, "license": "MIT" }, "node_modules/wkt-parser": { "version": "1.3.3", + "resolved": "https://registry.npmjs.org/wkt-parser/-/wkt-parser-1.3.3.tgz", + "integrity": "sha512-ZnV3yH8/k58ZPACOXeiHaMuXIiaTk1t0hSUVisbO0t4RjA5wPpUytcxeyiN2h+LZRrmuHIh/1UlrR9e7DHDvTw==", "license": "MIT" }, "node_modules/wordwrap": { @@ -12629,6 +12887,8 @@ }, "node_modules/yallist": { "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true, "license": "ISC" }, diff --git a/package.json b/package.json index 23016fba1b..213ac98792 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "base-test-unit": "cross-env BABEL_DISABLE_CACHE=1 mocha --file test/unit/bootstrap.js --loader=babel-register-esm", "build": "cross-env NODE_ENV=production webpack", "build-dev": "cross-env NODE_ENV=development webpack", - "transpile": "cross-env BABEL_DISABLE_CACHE=1 babel src --out-dir lib", + "transpile": "cross-env BABEL_DISABLE_CACHE=1 babel src --extensions '.js,.ts' --out-dir lib", "start": "cross-env NODE_ENV=development webpack serve", "start-https": "cross-env NODE_ENV=development webpack serve --https", "debug": "cross-env noInline=true npm start", @@ -75,6 +75,7 @@ "@babel/cli": "^7.22.5", "@babel/plugin-transform-runtime": "^7.22.5", "@babel/preset-env": "^7.22.5", + "@babel/preset-typescript": "^7.24.1", "@babel/register": "^7.22.5", "@types/three": "^0.159.0", "@xmldom/xmldom": "^0.8.10", diff --git a/src/Graph/Graph.ts b/src/Graph/Graph.ts index 7ac2cdb0a0..4ce19d0d61 100644 --- a/src/Graph/Graph.ts +++ b/src/Graph/Graph.ts @@ -1,6 +1,8 @@ import GraphNode from './Nodes/GraphNode.ts'; import { DumpDotGlobalStyle, Type } from './Common.ts'; +console.log("coucou"); + /** Represents a directed graph that guarantees the absence of cycles on use. */ export default class Graph { public nodes: Map; diff --git a/src/Main.js b/src/Main.js index 4969bbff5c..7e264b43ce 100644 --- a/src/Main.js +++ b/src/Main.js @@ -94,7 +94,7 @@ export { default as LASParser } from 'Parser/LASParser'; export { default as ISGParser } from 'Parser/ISGParser'; export { default as GDFParser } from 'Parser/GDFParser'; export { default as GTXParser } from 'Parser/GTXParser'; -export { default as GLTFParser, enableDracoLoader, enableKtx2Loader, glTFLoader, legacyGLTFLoader } from 'Parser/GLTFParser'; +export { default as GLTFParser, enableDracoLoader, enableKtx2Loader, glTFLoader, legacyGLTFLoader } from 'Parser/GLTFParser'; // 3D Tiles classes and extensions // Exported to allow one to implement its own 3D Tiles extension which needs to @@ -107,3 +107,5 @@ export { default as C3DTExtensions } from './Core/3DTiles/C3DTExtensions'; export { C3DTilesTypes, C3DTilesBoundingVolumeTypes } from './Core/3DTiles/C3DTilesEnums'; export { default as C3DTBatchTableHierarchyExtension } from './Core/3DTiles/C3DTBatchTableHierarchyExtension'; export { process3dTilesNode, $3dTilesCulling, $3dTilesSubdivisionControl } from 'Process/3dTilesProcessing'; + +export { default as Graph } from 'Graph/Graph.ts'; diff --git a/src/glsl.d.ts b/src/glsl.d.ts new file mode 100644 index 0000000000..235c87e9e4 --- /dev/null +++ b/src/glsl.d.ts @@ -0,0 +1,4 @@ +declare module '*.glsl' { + const content: string; + export default content; +} diff --git a/tsconfig.json b/tsconfig.json index 0035eca157..2ca5ecac2c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,6 +19,7 @@ "checkJs": false, // set to true for type checking of js files /* Interop Constraints */ "allowSyntheticDefaultImports": true, + "allowImportingTsExtensions": true, "esModuleInterop": true, "isolatedModules": true, /* Language and Environment */ diff --git a/webpack.config.cjs b/webpack.config.cjs index 1331d777c1..4e644d7209 100644 --- a/webpack.config.cjs +++ b/webpack.config.cjs @@ -1,44 +1,14 @@ -const fs = require('fs'); const path = require('path'); const ESLintPlugin = require('eslint-webpack-plugin'); const mode = process.env.NODE_ENV; -const noInline = process.env.noInline; -const debugBuild = mode === 'development'; - -/* - configuring babel: - - when babel runs alone (for `test-unit` for instance), we let him deal with - ES6 modules, because node doesn't support them yet (planned for v10 lts). - - however, webpack also has ES6 module support and these 2 don't play well - together. When running webpack (either `build` or `start` script), we prefer - to rely on webpack loaders (much more powerful and gives more possibilities), - so let's disable modules for babel here. - - we also dynamise the value of __DEBUG__ according to the env var -*/ -// Note that we don't support .babelrc in parent folders -const babelrc = fs.readFileSync(path.resolve(__dirname, '.babelrc')); -const babelConf = JSON.parse(babelrc); - -babelConf.babelrc = false; // disabel babelrc reading, as we've just done it -const replacementPluginConf = babelConf.plugins.find(plugin => Array.isArray(plugin) && plugin[0] === 'minify-replace'); -replacementPluginConf[1].replacements.find(decl => decl.identifierName === '__DEBUG__').replacement.value = debugBuild; - -const include = [ - path.resolve(__dirname, 'src'), - path.resolve(__dirname, 'test'), - path.resolve(__dirname, 'utils'), -]; module.exports = () => { - const babelLoaderOptions = []; - if (!noInline) { - babelLoaderOptions.push('babel-inline-import-loader'); - } - babelLoaderOptions.push({ - loader: 'babel-loader', - options: babelConf, - }); + const include = [ + path.resolve(__dirname, 'src'), + path.resolve(__dirname, 'test'), + path.resolve(__dirname, 'utils'), + ]; return { mode, @@ -71,9 +41,14 @@ module.exports = () => { module: { rules: [ { - test: /\.js$/, + test: /\.[jt]s$/, include, - use: babelLoaderOptions, + use: { + loader: 'babel-loader', + options: { + rootMode: 'upward', + }, + }, }, ], }, From f4a9ce1b77251b319e1f6342a801f0e82129199a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Duhen?= Date: Tue, 7 May 2024 18:07:14 +0200 Subject: [PATCH 03/44] feat: alter post-processing example to use graph --- .eslintrc.cjs | 2 +- examples/effects_postprocessing.html | 158 ++++++++++++++------------- src/Graph/Common.ts | 6 + src/Graph/Graph.ts | 28 +++-- src/Graph/Nodes/GraphNode.ts | 10 +- src/Graph/Nodes/RenderViewNode.ts | 58 ++++++++++ src/Graph/Nodes/ScreenShaderNode.ts | 40 +++++-- src/Main.js | 2 +- 8 files changed, 199 insertions(+), 105 deletions(-) create mode 100644 src/Graph/Nodes/RenderViewNode.ts diff --git a/.eslintrc.cjs b/.eslintrc.cjs index c2544517f6..171ce0be6b 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -5,7 +5,7 @@ module.exports = { 'eslint-config-airbnb-base/rules/strict', ], parserOptions: { - ecmaVersion: 13, + ecmaVersion: 2023, sourceType: 'module', ecmaFeatures: { impliedStrict: true, diff --git a/examples/effects_postprocessing.html b/examples/effects_postprocessing.html index b00f52c593..989c715240 100644 --- a/examples/effects_postprocessing.html +++ b/examples/effects_postprocessing.html @@ -1,20 +1,22 @@ - - Itowns - postprocessing - - - - - - - - -
- - - - + + + - - + - // iTowns namespace defined here - var viewerDiv = document.getElementById('viewerDiv'); - var view = new itowns.GlobeView(viewerDiv, placement); - - // Simple postprocessing setup - // - var postprocessScene = new itowns.THREE.Scene(); - var quad = new itowns.THREE.Mesh(new itowns.THREE.PlaneGeometry(2, 2), null); - var cam = new itowns.THREE.OrthographicCamera(-1, 1, 1, -1, 0, 10); - - setupLoadingScreen(viewerDiv, view); - - quad.frustumCulled = false; - quad.material = new itowns.THREE.ShaderMaterial({ - uniforms: { - tDiffuse: { value: null }, - tSize: { value: new itowns.THREE.Vector2(256, 256) }, - center: { value: new itowns.THREE.Vector2(0.5, 0.5) }, - angle: { value: 1.57 }, - scale: { value: 1.0 }, - }, - vertexShader: document.getElementById('vertexshader').textContent, - fragmentShader: document.getElementById('fragmentshader').textContent, - }); - postprocessScene.add(quad); - - view.render = function render() { - var g = view.mainLoop.gfxEngine; - var r = g.renderer; - r.setRenderTarget(g.fullSizeRenderTarget); - r.clear(); - r.render(view.scene, view.camera3D); - - quad.material.uniforms.tDiffuse.value = g.fullSizeRenderTarget.texture; - quad.material.uniforms.tSize.value.set( - g.fullSizeRenderTarget.width, g.fullSizeRenderTarget.height); - - r.setRenderTarget(null); - r.clear(); - r.render(postprocessScene, cam); - }; - - itowns.Fetcher.json('./layers/JSONLayers/Ortho.json').then(function _(config) { - config.source = new itowns.WMTSSource(config.source); - var layer = new itowns.ColorLayer('Ortho', config); - view.addLayer(layer); - }); - itowns.Fetcher.json('./layers/JSONLayers/IGN_MNT.json').then(function _(config) { - config.source = new itowns.WMTSSource(config.source); - var layer = new itowns.ElevationLayer(config.id, config); - view.addLayer(layer); - }); - - diff --git a/src/Graph/Common.ts b/src/Graph/Common.ts index 8c2b15417a..a842b3ed1f 100644 --- a/src/Graph/Common.ts +++ b/src/Graph/Common.ts @@ -2,11 +2,15 @@ import Graph from './Graph.ts'; import GraphNode from './Nodes/GraphNode.ts'; import InputNode from './Nodes/InputNode.ts'; import ProcessorNode from './Nodes/ProcessorNode.ts'; +import ScreenShaderNode from './Nodes/ScreenShaderNode.ts'; +import RenderViewNode from './Nodes/RenderViewNode.ts'; enum BuiltinType { Number = 'Number', // TODO: remove, just here for testing GeoData = 'GeoData', // TODO: split into different data formats Renderer = 'Renderer', + RenderTarget = 'RenderTarget', + View = 'View', Texture = 'Texture', CRS = 'CRS', // Coordinate Reference System } @@ -30,6 +34,8 @@ export { GraphNode, InputNode, ProcessorNode, + ScreenShaderNode, + RenderViewNode, // Utils BuiltinType, diff --git a/src/Graph/Graph.ts b/src/Graph/Graph.ts index 4ce19d0d61..689bf24730 100644 --- a/src/Graph/Graph.ts +++ b/src/Graph/Graph.ts @@ -1,8 +1,6 @@ import GraphNode from './Nodes/GraphNode.ts'; import { DumpDotGlobalStyle, Type } from './Common.ts'; -console.log("coucou"); - /** Represents a directed graph that guarantees the absence of cycles on use. */ export default class Graph { public nodes: Map; @@ -50,8 +48,8 @@ export default class Graph { * @throws If the node is orphaned and the graph has at least one node already. * @returns True if the node was added or updated, false otherwise. */ - public set(name: string, node: GraphNode): boolean { - if (this.nodes.size > 0 && node.inputs.size === 0) { + public set(name: string, node: GraphNode, isInput: boolean = false): boolean { + if (!isInput && this.nodes.size > 0 && node.inputs.size === 0) { throw new Error('Orphaned node'); } @@ -71,12 +69,12 @@ export default class Graph { * @throws If any of the nodes are orphaned and the graph has at least one node already. * @returns A map of the results of the set operation. */ - public set_grouped(nodes: { + public setGrouped(nodes: { [name: string]: GraphNode; - }): Map { + }, isInput: boolean = false): Map { const results = new Map(); for (const [name, node] of Object.entries(nodes)) { - results.set(name, this.set(name, node)); + results.set(name, this.set(name, node, isInput)); } return results; } @@ -97,7 +95,7 @@ export default class Graph { continue; } - this._validation_dfs(node, new Set(), visited); + this._validationDfs(node, new Set(), visited); } this._valid = true; @@ -107,7 +105,7 @@ export default class Graph { * Depth-first search for cycles and dangling dependencies. * @throws If a cycle is detected or a dangling dependency is found. */ - private _validation_dfs( + private _validationDfs( node: GraphNode, path: Set, visited: Set, @@ -133,7 +131,7 @@ export default class Graph { } path.add(name); - this._validation_dfs(dep, path, visited); + this._validationDfs(dep, path, visited); path.delete(name); } } @@ -204,7 +202,7 @@ export default class Graph { // Declare nodes dump.push('\t{'); for (const [name, node] of this.nodes) { - dump.push(`\t\t'${name}' ${node.dumpDotAttr(name)};`); + dump.push(`\t\t"${name}" ${node.dumpDotAttr(name)};`); } dump.push('\t}'); @@ -217,17 +215,17 @@ export default class Graph { // PERF: Inefficient but the alternative is duplicating the names // inside the nodes and that makes the API much heavier so we'll // live with it, this will likely never be an issue. - const inputName = entries.find( + const [inputName, oNode] = entries.find( ([_, oNode]) => oNode == dep, - )?.[0]; + ) ?? [undefined, undefined]; if (inputName == undefined) { throw new Error( `An input of node ${name} is not part of the graph`, ); } - const attrs = node.dumpDotEdgeAttr(); + const attrs = oNode.dumpDotEdgeAttr(); - dump.push(`\t'${inputName}' -> '${name}' ${attrs};`); + dump.push(`\t"${inputName}" -> "${name}" ${attrs};`); } } } diff --git a/src/Graph/Nodes/GraphNode.ts b/src/Graph/Nodes/GraphNode.ts index ffa3f06d25..7b8e8e4d0d 100644 --- a/src/Graph/Nodes/GraphNode.ts +++ b/src/Graph/Nodes/GraphNode.ts @@ -48,11 +48,17 @@ export default abstract class GraphNode { .map(([k, v]) => `${k}=${v}`) .join(' '); - return `[label="${label(name)}" ${formattedAttrs}]`; + const lName = label(name).trim(); + const lHtml = `< + + ${lName.length == 0 ? "" : ``} +
${this._node_type}
${lName}
>`; + + return `[label=${lHtml} ${formattedAttrs} margin=.05]`; } /** - * Get the DOT attribute string for the edge between this node and its inputs. + * Get the DOT attribute string for the outgoing edges. */ public dumpDotEdgeAttr(): string { return `[label=" ${this.outputType}"]`; diff --git a/src/Graph/Nodes/RenderViewNode.ts b/src/Graph/Nodes/RenderViewNode.ts new file mode 100644 index 0000000000..2e897f709d --- /dev/null +++ b/src/Graph/Nodes/RenderViewNode.ts @@ -0,0 +1,58 @@ +import { BuiltinType, Dependency, DumpDotNodeStyle } from "../Common.ts"; +import ProcessorNode from "./ProcessorNode.ts"; + +import View from "../../Core/View.js"; +import MainLoop from "../../Core/MainLoop.js"; +import c3DEngine from "../../Renderer/c3DEngine.js"; + +import * as THREE from "three"; + +export default class RenderViewNode extends ProcessorNode { + private _target: THREE.WebGLRenderTarget | null = null; + + public constructor(view: Dependency, toScreen: boolean = false) { + super({ view }, BuiltinType.RenderTarget, (_frame, args) => { + const view = args.view as View; + const engine = (view.mainLoop as MainLoop).gfxEngine as c3DEngine; + const renderer = engine.renderer as THREE.WebGLRenderer; + + if (!this._target) { + if (toScreen) { + this._target = null; + } else { + const fsrt = engine.fullSizeRenderTarget; + + this._target = new THREE.WebGLRenderTarget(fsrt.width, fsrt.height, { + minFilter: THREE.LinearFilter, + magFilter: THREE.LinearFilter, + format: THREE.RGBAFormat, + type: THREE.FloatType, + }); + } + } + + console.log(`Rendering view`); + + renderer.setRenderTarget(this._target); + renderer.clear(); + renderer.render(view.scene, view.camera3D); + + return this._target; + }); + } + + protected get _node_type(): string { + return 'RenderView'; + } + + public get dumpDotStyle(): DumpDotNodeStyle { + const { label: _, attrs } = super.dumpDotStyle; + return { + label: (_name: string) => '', + attrs: { + ...attrs, + shape: 'Mcircle', + }, + }; + } +} diff --git a/src/Graph/Nodes/ScreenShaderNode.ts b/src/Graph/Nodes/ScreenShaderNode.ts index 9a09469cad..849e6b747c 100644 --- a/src/Graph/Nodes/ScreenShaderNode.ts +++ b/src/Graph/Nodes/ScreenShaderNode.ts @@ -14,6 +14,15 @@ export default class ScreenShaderNode extends ProcessorNode { ` }; + private static get defaultFragmentShader() { + return ` + void main() { + vec4 color = texture2D(uTexture, vUv); + gl_FragColor = color; + } + `; + }; + // WARN: This is a temporary hack. Essentially a scuffed singleton pack. // PERF: Evaluate the cost of having a scene per shader node instead. private static _scene: THREE.Scene; @@ -29,7 +38,7 @@ export default class ScreenShaderNode extends ProcessorNode { ScreenShaderNode._scene = new THREE.Scene(); // Setup the quad used to render the effects - ScreenShaderNode._quad = new THREE.Mesh(new THREE.PlaneGeometry(1, 1)); + ScreenShaderNode._quad = new THREE.Mesh(new THREE.PlaneGeometry(2, 2)); ScreenShaderNode._quad.frustumCulled = false; ScreenShaderNode._scene.add(ScreenShaderNode._quad); @@ -46,22 +55,27 @@ export default class ScreenShaderNode extends ProcessorNode { public constructor( input: Dependency, renderer: Dependency, - uniforms?: { [name: string]: Dependency }, - fragmentShader?: string + { uniforms, fragmentShader, toScreen = false }: { + uniforms?: { [name: string]: Dependency }, + fragmentShader?: string, + toScreen?: boolean + } ) { ScreenShaderNode._init(); // Unpacking the uniforms object first allows us to ignore potential "input" and "renderer" fields. - super({ ...(uniforms ?? {}), input, renderer }, BuiltinType.Texture, (_frame, args) => { - const input = args.input as THREE.Texture; + super({ ...(uniforms ?? {}), input, renderer }, BuiltinType.RenderTarget, (_frame, args) => { + const input = args.input as THREE.WebGLRenderTarget; const renderer = args.renderer as THREE.WebGLRenderer; - const target: THREE.WebGLRenderTarget = this._out[1] ?? new THREE.WebGLRenderTarget( - input.image.width, - input.image.height - ); + const target: THREE.WebGLRenderTarget | null = toScreen + ? null + : (this._out[1] ?? new THREE.WebGLRenderTarget( + input.width, + input.height + )); - this._material.uniforms.uTexture.value = input; + this._material.uniforms['uTexture'] = { value: input.texture }; for (const [name, value] of Object.entries(args)) { if (name === "input" || name === "renderer") { continue; @@ -74,6 +88,8 @@ export default class ScreenShaderNode extends ProcessorNode { renderer.setRenderTarget(target); renderer.clear(); renderer.render(ScreenShaderNode._scene, ScreenShaderNode._camera); + + return target; }); this._fragmentShader = ` @@ -94,6 +110,10 @@ export default class ScreenShaderNode extends ProcessorNode { }); } + protected get _node_type(): string { + return 'ScreenShader'; + } + public get dumpDotStyle(): DumpDotNodeStyle { const { label, attrs } = super.dumpDotStyle; return { diff --git a/src/Main.js b/src/Main.js index 7e264b43ce..367dea581f 100644 --- a/src/Main.js +++ b/src/Main.js @@ -108,4 +108,4 @@ export { C3DTilesTypes, C3DTilesBoundingVolumeTypes } from './Core/3DTiles/C3DTi export { default as C3DTBatchTableHierarchyExtension } from './Core/3DTiles/C3DTBatchTableHierarchyExtension'; export { process3dTilesNode, $3dTilesCulling, $3dTilesSubdivisionControl } from 'Process/3dTilesProcessing'; -export { default as Graph } from 'Graph/Graph.ts'; +export { Graph, GraphNode, InputNode, RenderViewNode, ProcessorNode, ScreenShaderNode, BuiltinType } from 'Graph/Common.ts'; From ea03206407142de40800a8dbbbba5e81e750c67d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Duhen?= Date: Mon, 13 May 2024 11:42:45 +0200 Subject: [PATCH 04/44] build: configure eslint for typescript interop --- .eslintrc.cjs | 28 +- package-lock.json | 408 +++++++++++++++++++++++++--- package.json | 2 + src/Core/3DTiles/C3DTFeature.js | 2 +- src/Core/AnimationPlayer.js | 2 +- src/Core/Prefab/Globe/Atmosphere.js | 2 +- src/Graph/Common.ts | 2 +- src/Graph/Nodes/GraphNode.ts | 2 +- src/Graph/Nodes/InputNode.ts | 4 +- src/Graph/Nodes/ProcessorNode.ts | 2 +- src/Graph/Nodes/RenderViewNode.ts | 18 +- src/Graph/Nodes/ScreenShaderNode.ts | 27 +- src/Layer/C3DTilesLayer.js | 2 +- 13 files changed, 436 insertions(+), 65 deletions(-) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 171ce0be6b..58d46d6c47 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -1,8 +1,13 @@ module.exports = { root: true, + plugins: [ + '@typescript-eslint', + ], + parser: '@typescript-eslint/parser', extends: [ 'eslint-config-airbnb-base', 'eslint-config-airbnb-base/rules/strict', + 'plugin:@typescript-eslint/recommended', ], parserOptions: { ecmaVersion: 2023, @@ -25,9 +30,22 @@ module.exports = { commonjs: true, }, rules: { + 'import/extensions': [ + 'off', + ], 'no-trailing-spaces': 'warn', 'padded-blocks': 'warn', - 'no-unused-vars': 'warn', + 'no-unused-vars': 'off', + '@typescript-eslint/no-unused-vars': [ + 'warn', + { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + caughtErrorsIgnorePattern: '^_', + }, + ], + '@typescript-eslint/no-this-alias': 'off', + '@typescript-eslint/no-explicit-any': 'off', 'no-plusplus': 'off', // this option sets a specific tab width for your code // http://eslint.org/docs/rules/indent @@ -108,6 +126,14 @@ module.exports = { // change default-param-last to on, but there are several breaking changes or default params to add. 'default-param-last': 'off', }, + overrides: [ + { + files: ['*.ts'], + rules: { + 'valid-jsdoc': 'off', + }, + }, + ], globals: { __DEBUG__: false, }, diff --git a/package-lock.json b/package-lock.json index fcebdaa41d..37aeca2028 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,8 @@ "@babel/preset-typescript": "^7.24.1", "@babel/register": "^7.22.5", "@types/three": "^0.159.0", + "@typescript-eslint/eslint-plugin": "^7.8.0", + "@typescript-eslint/parser": "^7.8.0", "@xmldom/xmldom": "^0.8.10", "babel-inline-import-loader": "^1.0.1", "babel-loader": "^9.1.3", @@ -2092,22 +2094,22 @@ } }, "node_modules/@eslint/js": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.55.0.tgz", - "integrity": "sha512-qQfo2mxH5yVom1kacMtZZJFVdW+E70mqHMJvVg6WTLo+VBuQJ4TojZlfWBjK0ve5BdEeNAVxOsl/nvNMpJOaJA==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.13", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", - "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^2.0.1", - "debug": "^4.1.1", + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", "minimatch": "^3.0.5" }, "engines": { @@ -2129,9 +2131,9 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", - "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", "dev": true }, "node_modules/@hutson/parse-repository-url": { @@ -2645,9 +2647,10 @@ } }, "node_modules/@types/json-schema": { - "version": "7.0.12", - "dev": true, - "license": "MIT" + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true }, "node_modules/@types/json5": { "version": "0.0.29", @@ -2720,6 +2723,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/semver": { + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", + "dev": true + }, "node_modules/@types/send": { "version": "0.17.1", "dev": true, @@ -2813,6 +2822,256 @@ "@types/node": "*" } }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.8.0.tgz", + "integrity": "sha512-gFTT+ezJmkwutUPmB0skOj3GZJtlEGnlssems4AjkVweUPGj7jRwwqg0Hhg7++kPGJqKtTYx+R05Ftww372aIg==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.8.0", + "@typescript-eslint/type-utils": "7.8.0", + "@typescript-eslint/utils": "7.8.0", + "@typescript-eslint/visitor-keys": "7.8.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.8.0.tgz", + "integrity": "sha512-KgKQly1pv0l4ltcftP59uQZCi4HUYswCLbTqVZEJu7uLX8CTLyswqMLqLN+2QFz4jCptqWVV4SB7vdxcH2+0kQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "7.8.0", + "@typescript-eslint/types": "7.8.0", + "@typescript-eslint/typescript-estree": "7.8.0", + "@typescript-eslint/visitor-keys": "7.8.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.8.0.tgz", + "integrity": "sha512-viEmZ1LmwsGcnr85gIq+FCYI7nO90DVbE37/ll51hjv9aG+YZMb4WDE2fyWpUR4O/UrhGRpYXK/XajcGTk2B8g==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.8.0", + "@typescript-eslint/visitor-keys": "7.8.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.8.0.tgz", + "integrity": "sha512-H70R3AefQDQpz9mGv13Uhi121FNMh+WEaRqcXTX09YEDky21km4dV1ZXJIp8QjXc4ZaVkXVdohvWDzbnbHDS+A==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "7.8.0", + "@typescript-eslint/utils": "7.8.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.8.0.tgz", + "integrity": "sha512-wf0peJ+ZGlcH+2ZS23aJbOv+ztjeeP8uQ9GgwMJGVLx/Nj9CJt17GWgWWoSmoRVKAX2X+7fzEnAjxdvK2gqCLw==", + "dev": true, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.8.0.tgz", + "integrity": "sha512-5pfUCOwK5yjPaJQNy44prjCwtr981dO8Qo9J9PwYXZ0MosgAbfEMB008dJ5sNo3+/BN6ytBPuSvXUg9SAqB0dg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.8.0", + "@typescript-eslint/visitor-keys": "7.8.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.1.tgz", + "integrity": "sha512-f/vbBsu+fOiYt+lmwZV0rVwJScl46HppnOA1ZvIuBWKOTlllpyJ3bfVax76/OrhCH38dyxoDIA8K7uB963IYgA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.8.0.tgz", + "integrity": "sha512-L0yFqOCflVqXxiZyXrDr80lnahQfSOfc9ELAAZ75sqicqp2i36kEZZGuUymHNFoYOqxRT05up760b4iGsl02nQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.15", + "@types/semver": "^7.5.8", + "@typescript-eslint/scope-manager": "7.8.0", + "@typescript-eslint/types": "7.8.0", + "@typescript-eslint/typescript-estree": "7.8.0", + "semver": "^7.6.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.8.0.tgz", + "integrity": "sha512-q4/gibTNBQNA0lGyYQCmWRS5D15n8rXh4QjK3KV+MBPlTYHpfBUT3D3PaPR/HeNiI9W6R7FvlkcGhNyAoP+caA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.8.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", @@ -3261,6 +3520,15 @@ "node": ">=0.10.0" } }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/array.prototype.find": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.2.2.tgz", @@ -4750,15 +5018,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cosmiconfig/node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/cross-env": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", @@ -4973,6 +5232,18 @@ "node": ">=0.3.1" } }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/dns-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", @@ -5258,16 +5529,16 @@ } }, "node_modules/eslint": { - "version": "8.55.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.55.0.tgz", - "integrity": "sha512-iyUUAM0PCKj5QpwGfmCAG9XXbZCWsqP/eWAWrG/W0umvjuLRBECwSFdt+rCntju0xEH7teIABPwXpahftIaTdA==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.55.0", - "@humanwhocodes/config-array": "^0.11.13", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", @@ -5893,6 +6164,22 @@ "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", "dev": true }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -6578,6 +6865,35 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -7263,9 +7579,9 @@ "license": "BSD-3-Clause" }, "node_modules/ignore": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", - "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", "dev": true, "engines": { "node": ">= 4" @@ -8674,6 +8990,15 @@ "dev": true, "license": "MIT" }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, "node_modules/meshoptimizer": { "version": "0.18.1", "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-0.18.1.tgz", @@ -9716,6 +10041,15 @@ "dev": true, "license": "MIT" }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/pbf": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/pbf/-/pbf-3.2.1.tgz", @@ -11879,6 +12213,18 @@ "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", "license": "MIT" }, + "node_modules/ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "dev": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, "node_modules/tsconfig-paths": { "version": "3.14.2", "dev": true, diff --git a/package.json b/package.json index 213ac98792..b6bf63496b 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,8 @@ "@babel/preset-typescript": "^7.24.1", "@babel/register": "^7.22.5", "@types/three": "^0.159.0", + "@typescript-eslint/eslint-plugin": "^7.8.0", + "@typescript-eslint/parser": "^7.8.0", "@xmldom/xmldom": "^0.8.10", "babel-inline-import-loader": "^1.0.1", "babel-loader": "^9.1.3", diff --git a/src/Core/3DTiles/C3DTFeature.js b/src/Core/3DTiles/C3DTFeature.js index 1e58cd777c..98b79cd0d9 100644 --- a/src/Core/3DTiles/C3DTFeature.js +++ b/src/Core/3DTiles/C3DTFeature.js @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars import { Object3D, Box3 } from 'three'; /** diff --git a/src/Core/AnimationPlayer.js b/src/Core/AnimationPlayer.js index 3575a10c22..1d03574f8f 100644 --- a/src/Core/AnimationPlayer.js +++ b/src/Core/AnimationPlayer.js @@ -55,7 +55,7 @@ class AnimationPlayer extends THREE.EventDispatcher { this.duration = 0; this.state = PLAYER_STATE.STOP; this.waitTimer = null; - this.callback = () => {}; + this.callback = () => { }; } isPlaying() { diff --git a/src/Core/Prefab/Globe/Atmosphere.js b/src/Core/Prefab/Globe/Atmosphere.js index 347b3a494d..87f77cebc0 100644 --- a/src/Core/Prefab/Globe/Atmosphere.js +++ b/src/Core/Prefab/Globe/Atmosphere.js @@ -145,7 +145,7 @@ class Atmosphere extends GeometryLayer { node.material.lightPosition = this.realisticLightingPosition; } - // eslint-disable-next-line no-unused-vars + // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars preUpdate(context, srcs) { const cameraPosition = context.view.camera3D.position; if (this.fog.enable) { diff --git a/src/Graph/Common.ts b/src/Graph/Common.ts index a842b3ed1f..73153d9e3f 100644 --- a/src/Graph/Common.ts +++ b/src/Graph/Common.ts @@ -6,7 +6,7 @@ import ScreenShaderNode from './Nodes/ScreenShaderNode.ts'; import RenderViewNode from './Nodes/RenderViewNode.ts'; enum BuiltinType { - Number = 'Number', // TODO: remove, just here for testing + Number = 'Number', GeoData = 'GeoData', // TODO: split into different data formats Renderer = 'Renderer', RenderTarget = 'RenderTarget', diff --git a/src/Graph/Nodes/GraphNode.ts b/src/Graph/Nodes/GraphNode.ts index 7b8e8e4d0d..f9730aee34 100644 --- a/src/Graph/Nodes/GraphNode.ts +++ b/src/Graph/Nodes/GraphNode.ts @@ -51,7 +51,7 @@ export default abstract class GraphNode { const lName = label(name).trim(); const lHtml = `< - ${lName.length == 0 ? "" : ``} + ${lName.length == 0 ? '' : ``}
${this._node_type}
${lName}
${lName}
>`; return `[label=${lHtml} ${formattedAttrs} margin=.05]`; diff --git a/src/Graph/Nodes/InputNode.ts b/src/Graph/Nodes/InputNode.ts index 90f08543c5..3139805a8c 100644 --- a/src/Graph/Nodes/InputNode.ts +++ b/src/Graph/Nodes/InputNode.ts @@ -15,12 +15,12 @@ export default class InputNode extends GraphNode { } protected get _node_type(): string { - return 'Input'; + return InputNode.name.replace('Node', ''); } public get dumpDotStyle(): DumpDotNodeStyle { return { - label: (name) => `${name}: ${this.value}`, + label: name => `${name}: ${this.value}`, attrs: { shape: 'invtrapezium', color: 'goldenrod', diff --git a/src/Graph/Nodes/ProcessorNode.ts b/src/Graph/Nodes/ProcessorNode.ts index 5af10ac71c..8d7844a8c5 100644 --- a/src/Graph/Nodes/ProcessorNode.ts +++ b/src/Graph/Nodes/ProcessorNode.ts @@ -26,7 +26,7 @@ export default class ProcessorNode extends GraphNode { } protected get _node_type(): string { - return 'Processor'; + return ProcessorNode.name.replace('Node', ''); } public get dumpDotStyle(): DumpDotNodeStyle { diff --git a/src/Graph/Nodes/RenderViewNode.ts b/src/Graph/Nodes/RenderViewNode.ts index 2e897f709d..0a75a776a3 100644 --- a/src/Graph/Nodes/RenderViewNode.ts +++ b/src/Graph/Nodes/RenderViewNode.ts @@ -1,11 +1,11 @@ -import { BuiltinType, Dependency, DumpDotNodeStyle } from "../Common.ts"; -import ProcessorNode from "./ProcessorNode.ts"; +import * as THREE from 'three'; -import View from "../../Core/View.js"; -import MainLoop from "../../Core/MainLoop.js"; -import c3DEngine from "../../Renderer/c3DEngine.js"; +import { BuiltinType, Dependency, DumpDotNodeStyle } from '../Common.ts'; +import ProcessorNode from './ProcessorNode.ts'; -import * as THREE from "three"; +import View from '../../Core/View.js'; +import MainLoop from '../../Core/MainLoop.js'; +import c3DEngine from '../../Renderer/c3DEngine.js'; export default class RenderViewNode extends ProcessorNode { private _target: THREE.WebGLRenderTarget | null = null; @@ -31,8 +31,6 @@ export default class RenderViewNode extends ProcessorNode { } } - console.log(`Rendering view`); - renderer.setRenderTarget(this._target); renderer.clear(); renderer.render(view.scene, view.camera3D); @@ -41,8 +39,8 @@ export default class RenderViewNode extends ProcessorNode { }); } - protected get _node_type(): string { - return 'RenderView'; + protected get _nodeType(): string { + return RenderViewNode.name.replace('Node', ''); } public get dumpDotStyle(): DumpDotNodeStyle { diff --git a/src/Graph/Nodes/ScreenShaderNode.ts b/src/Graph/Nodes/ScreenShaderNode.ts index 849e6b747c..8845ac05bc 100644 --- a/src/Graph/Nodes/ScreenShaderNode.ts +++ b/src/Graph/Nodes/ScreenShaderNode.ts @@ -11,8 +11,8 @@ export default class ScreenShaderNode extends ProcessorNode { vUv = uv; gl_Position = vec4(position, 1.0); } - ` - }; + `; + } private static get defaultFragmentShader() { return ` @@ -21,7 +21,7 @@ export default class ScreenShaderNode extends ProcessorNode { gl_FragColor = color; } `; - }; + } // WARN: This is a temporary hack. Essentially a scuffed singleton pack. // PERF: Evaluate the cost of having a scene per shader node instead. @@ -59,11 +59,11 @@ export default class ScreenShaderNode extends ProcessorNode { uniforms?: { [name: string]: Dependency }, fragmentShader?: string, toScreen?: boolean - } + }, ) { ScreenShaderNode._init(); - // Unpacking the uniforms object first allows us to ignore potential "input" and "renderer" fields. + // Unpacking the uniforms object first allows us to ignore potential 'input' and 'renderer' fields. super({ ...(uniforms ?? {}), input, renderer }, BuiltinType.RenderTarget, (_frame, args) => { const input = args.input as THREE.WebGLRenderTarget; const renderer = args.renderer as THREE.WebGLRenderer; @@ -72,12 +72,12 @@ export default class ScreenShaderNode extends ProcessorNode { ? null : (this._out[1] ?? new THREE.WebGLRenderTarget( input.width, - input.height + input.height, )); - this._material.uniforms['uTexture'] = { value: input.texture }; + this._material.uniforms.uTexture = { value: input.texture }; for (const [name, value] of Object.entries(args)) { - if (name === "input" || name === "renderer") { + if (name === 'input' || name === 'renderer') { continue; } @@ -101,8 +101,7 @@ export default class ScreenShaderNode extends ProcessorNode { `void main() { vec4 color = texture2D(uTexture, vUv); gl_FragColor = color; - }` - }`; + }`}`; this._material = new THREE.ShaderMaterial({ fragmentShader: this._fragmentShader, @@ -110,8 +109,8 @@ export default class ScreenShaderNode extends ProcessorNode { }); } - protected get _node_type(): string { - return 'ScreenShader'; + protected get _nodeType(): string { + return ScreenShaderNode.name.replace('Node', ''); } public get dumpDotStyle(): DumpDotNodeStyle { @@ -120,8 +119,8 @@ export default class ScreenShaderNode extends ProcessorNode { label, attrs: { ...attrs, - fillcolor: "lightcoral", - } + fillcolor: 'lightcoral', + }, }; } } diff --git a/src/Layer/C3DTilesLayer.js b/src/Layer/C3DTilesLayer.js index ee48c5d62d..147cad31a8 100644 --- a/src/Layer/C3DTilesLayer.js +++ b/src/Layer/C3DTilesLayer.js @@ -4,7 +4,7 @@ import { init3dTilesLayer, pre3dTilesUpdate, process3dTilesNode } from 'Process/ import C3DTileset from 'Core/3DTiles/C3DTileset'; import C3DTExtensions from 'Core/3DTiles/C3DTExtensions'; import { PNTS_MODE, PNTS_SHAPE, PNTS_SIZE_MODE } from 'Renderer/PointsMaterial'; -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars import Style from 'Core/Style'; import C3DTFeature from 'Core/3DTiles/C3DTFeature'; import { optimizeGeometryGroups } from 'Utils/ThreeUtils'; From e4630574765f547185c37d5f14935733a6ae889d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Duhen?= Date: Mon, 13 May 2024 17:00:05 +0200 Subject: [PATCH 05/44] feat: add type-checking, update usage example --- examples/effects_postprocessing.html | 73 ++++++++---------- src/Graph/Common.ts | 2 + src/Graph/Graph.ts | 110 ++++++++++++++++----------- src/Graph/Nodes/GraphNode.ts | 8 +- src/Graph/Nodes/InputNode.ts | 4 +- src/Graph/Nodes/ProcessorNode.ts | 6 +- src/Graph/Nodes/RenderViewNode.ts | 2 +- src/Graph/Nodes/ScreenShaderNode.ts | 85 +++++++++++++-------- 8 files changed, 165 insertions(+), 125 deletions(-) diff --git a/examples/effects_postprocessing.html b/examples/effects_postprocessing.html index 989c715240..877cc50c05 100644 --- a/examples/effects_postprocessing.html +++ b/examples/effects_postprocessing.html @@ -15,41 +15,30 @@
- - + gl_FragColor = color + vec4(coasts, coasts, coasts, 1.0); + } + @@ -54,21 +54,17 @@ // Simple postprocessing setup const g = new itowns.Graph(); - const inputView = new itowns.InputNode(view, itowns.BuiltinType.View); - const renderer = new itowns.InputNode( - view.mainLoop.gfxEngine.renderer, - itowns.BuiltinType.Renderer - ); + const inputView = new itowns.InputNode(view); + const renderer = new itowns.InputNode(view.mainLoop.gfxEngine.renderer); g.setGrouped({inputView, renderer}, true); const render = new itowns.RenderViewNode(inputView); g.setGrouped({render}); - // Sobel sharpen // Uniforms - const sharpenWeight = new itowns.InputNode(5, itowns.BuiltinType.Number); - const sharpenOffset = new itowns.InputNode(new itowns.THREE.Vector2(0.0025, 0.), itowns.BuiltinType.Number); + const sharpenWeight = new itowns.InputNode(5); + const sharpenOffset = new itowns.InputNode(new itowns.THREE.Vector2(0.0025, 0.)); g.setGrouped({sharpenWeight, sharpenOffset}, true); // Filter const coastSharpen = new itowns.ScreenShaderNode(render, renderer, { diff --git a/src/Graph/Common.ts b/src/Graph/Common.ts index e8d150f33e..acdd5b1a3f 100644 --- a/src/Graph/Common.ts +++ b/src/Graph/Common.ts @@ -1,3 +1,5 @@ +import * as THREE from 'three'; +import View from 'Core/View.js'; import Graph from './Graph.ts'; import GraphNode from './Nodes/GraphNode.ts'; import InputNode from './Nodes/InputNode.ts'; @@ -6,14 +8,41 @@ import ScreenShaderNode from './Nodes/ScreenShaderNode.ts'; import RenderViewNode from './Nodes/RenderViewNode.ts'; enum BuiltinType { + // Primitives Number = 'Number', - GeoData = 'GeoData', // TODO: split into different data formats + + // iTowns types + View = 'View', + + // Three.js types Renderer = 'Renderer', RenderTarget = 'RenderTarget', - View = 'View', - Texture = 'Texture', Vector2 = 'Vector2', - CRS = 'CRS', // Coordinate Reference System + Vector3 = 'Vector3', + Vector4 = 'Vector4', +} + +const mapping: [(v: any) => boolean, BuiltinType][] = [ + // Primitives + [v => typeof v === 'number', BuiltinType.Number], + // iTowns types + [v => v instanceof View, BuiltinType.View], + // Three.js types + [v => v instanceof THREE.WebGLRenderer, BuiltinType.Renderer], + [v => v instanceof THREE.WebGLRenderTarget, BuiltinType.RenderTarget], + [v => v instanceof THREE.Vector2, BuiltinType.Vector2], + [v => v instanceof THREE.Vector3, BuiltinType.Vector3], + [v => v instanceof THREE.Vector4, BuiltinType.Vector4], +]; + +function getBuiltinType(v: any): BuiltinType { + for (const [check, ty] of mapping) { + if (check(v)) { + return ty; + } + } + + throw new Error('No builtin cast available for this type'); } export type Type = string; @@ -41,4 +70,5 @@ export { // Utils BuiltinType, + getBuiltinType, }; diff --git a/src/Graph/Nodes/InputNode.ts b/src/Graph/Nodes/InputNode.ts index ae565d38bd..5165bc993d 100644 --- a/src/Graph/Nodes/InputNode.ts +++ b/src/Graph/Nodes/InputNode.ts @@ -1,12 +1,12 @@ import GraphNode from './GraphNode.ts'; -import { Type, DumpDotNodeStyle } from '../Common.ts'; +import { Type, DumpDotNodeStyle, getBuiltinType } from '../Common.ts'; /** Represents a node that outputs a constant value. */ export default class InputNode extends GraphNode { public value: any; - public constructor(value: any, type: Type) { - super(new Map(), type); + public constructor(value: any, type?: Type) { + super(new Map(), type ?? getBuiltinType(value)); this.value = value; } From bc39d04833c133e3d40f4369d561c2fb553db836 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Duhen?= Date: Tue, 14 May 2024 16:42:41 +0200 Subject: [PATCH 07/44] feat: add type inference and improve dot graph --- examples/effects_postprocessing.html | 2 +- src/Graph/Common.ts | 58 ++++++++++++++++++++++++---- src/Graph/Graph.ts | 18 +++++---- src/Graph/Nodes/GraphNode.ts | 15 +++++-- src/Graph/Nodes/InputNode.ts | 11 ++++-- src/Graph/Nodes/RenderViewNode.ts | 7 +--- src/Graph/Nodes/ScreenShaderNode.ts | 8 +--- 7 files changed, 86 insertions(+), 33 deletions(-) diff --git a/examples/effects_postprocessing.html b/examples/effects_postprocessing.html index e4662b9f61..2afce050ab 100644 --- a/examples/effects_postprocessing.html +++ b/examples/effects_postprocessing.html @@ -90,7 +90,7 @@ })(dumpDot); console.log(dumpDot); - // window.open(dumpDotLink, '_blank'); + window.open(dumpDotLink, '_blank'); itowns.Fetcher.json('./layers/JSONLayers/Ortho.json').then(function _(config) { config.source = new itowns.WMTSSource(config.source); diff --git a/src/Graph/Common.ts b/src/Graph/Common.ts index acdd5b1a3f..e392ca71e5 100644 --- a/src/Graph/Common.ts +++ b/src/Graph/Common.ts @@ -7,6 +7,9 @@ import ProcessorNode from './Nodes/ProcessorNode.ts'; import ScreenShaderNode from './Nodes/ScreenShaderNode.ts'; import RenderViewNode from './Nodes/RenderViewNode.ts'; +export type Type = string; +export type Dependency = GraphNode | undefined | null; + enum BuiltinType { // Primitives Number = 'Number', @@ -22,7 +25,8 @@ enum BuiltinType { Vector4 = 'Vector4', } -const mapping: [(v: any) => boolean, BuiltinType][] = [ +// HACK: all of these mappings and functions should go into a style state object. +const typeMapping: [(v: any) => boolean, BuiltinType][] = [ // Primitives [v => typeof v === 'number', BuiltinType.Number], // iTowns types @@ -35,18 +39,56 @@ const mapping: [(v: any) => boolean, BuiltinType][] = [ [v => v instanceof THREE.Vector4, BuiltinType.Vector4], ]; -function getBuiltinType(v: any): BuiltinType { - for (const [check, ty] of mapping) { - if (check(v)) { +function getBuiltinType(value: any): BuiltinType | undefined { + for (const [check, ty] of typeMapping) { + if (check(value)) { return ty; } } + return undefined; +} + +// eslint-disable-next-line +const stringMapping = new Map string>([ + [BuiltinType.Vector2, v => `(${v.x}, ${v.y})`], +]); + +function stringify(value: any, type?: Type): string { + const auto = value.toString(); + if (auto !== '[object Object]') { + return auto; + } + + const ty = type ?? getBuiltinType(value); + if (ty != undefined) { + return stringMapping.get(ty)?.(value) ?? `[${ty}]`; + } - throw new Error('No builtin cast available for this type'); + return '...'; } -export type Type = string; -export type Dependency = GraphNode | undefined | null; +type ColorStyle = { + color?: string, + fillcolor?: string, +}; + +const typeColor = new Map([ + [BuiltinType.Number, { color: 'chocolate', fillcolor: 'orange' }], + [BuiltinType.View, { color: 'cornflowerblue', fillcolor: 'deepskyblue' }], + [BuiltinType.Vector2, { color: 'indigo', fillcolor: 'violet' }], + [BuiltinType.Vector3, { color: 'indigo', fillcolor: 'violet' }], + [BuiltinType.Vector4, { color: 'indigo', fillcolor: 'violet' }], + [BuiltinType.RenderTarget, { color: 'darkolivegreen', fillcolor: 'limegreen' }], + [BuiltinType.Renderer, { color: 'darkolivegreen', fillcolor: 'limegreen' }], +]); + +function getColor(value: any, type?: Type): ColorStyle { + const ty = type ?? getBuiltinType(value); + if (ty != undefined) { + return typeColor.get(ty) ?? {}; + } + return {}; +} export interface DumpDotNodeStyle { label: (name: string) => string; @@ -71,4 +113,6 @@ export { // Utils BuiltinType, getBuiltinType, + stringify, + getColor, }; diff --git a/src/Graph/Graph.ts b/src/Graph/Graph.ts index 9a1a9445a3..0885d345fb 100644 --- a/src/Graph/Graph.ts +++ b/src/Graph/Graph.ts @@ -1,5 +1,5 @@ import GraphNode from './Nodes/GraphNode.ts'; -import { DumpDotGlobalStyle, Type } from './Common.ts'; +import { DumpDotGlobalStyle, Type, getColor } from './Common.ts'; type NodeCallback = (node: GraphNode) => void; type StringCallback = (string: string) => void; @@ -82,9 +82,10 @@ export default class Graph { * @throws If any of the nodes are orphaned and the graph has at least one node already. * @returns A map of the results of the set operation. */ - public setGrouped(nodes: { - [name: string]: GraphNode; - }, isInput: boolean = false): Map { + public setGrouped( + nodes: { [name: string]: GraphNode; }, + isInput: boolean = false, + ): Map { const results = new Map(); for (const [name, node] of Object.entries(nodes)) { results.set(name, this.set(name, node, isInput)); @@ -208,9 +209,12 @@ export default class Graph { rankdir: 'LR', node: { fontname: 'Arial', + style: 'filled', + fillcolor: 'whitesmoke', }, edge: { fontname: 'Arial', + arrowhead: 'dot', }, }; } @@ -245,7 +249,7 @@ export default class Graph { // Declare edges for (const [name, node] of this.nodes) { - for (const [_, [dep, _ty]] of node.inputs) { + for (const [depName, [dep, _ty]] of node.inputs) { if (dep == null) { continue; } // PERF: Inefficient but the alternative is duplicating the names @@ -258,9 +262,9 @@ export default class Graph { ); } - const attrs = nodeEntry.node.dumpDotEdgeAttr(); + const attrs = nodeEntry.node.dumpDotEdgeAttr({ ...getColor(null, dep.outputType) }); - dump.push(`\t"${nodeEntry.name}" -> "${name}" ${attrs};`); + dump.push(`\t"${nodeEntry.name}" -> "${name}":${depName} ${attrs};`); } } } diff --git a/src/Graph/Nodes/GraphNode.ts b/src/Graph/Nodes/GraphNode.ts index 3c46b039c8..c5361a9a27 100644 --- a/src/Graph/Nodes/GraphNode.ts +++ b/src/Graph/Nodes/GraphNode.ts @@ -49,9 +49,14 @@ export default abstract class GraphNode { .join(' '); const lName = label(name).trim(); + const lPorts = Array.from(this.inputs) + .map(([name, [dep, _ty]]) => + `${name}`) + .join('\n'); const lHtml = `< - ${lName.length == 0 ? '' : ``} + ${lName.length == 0 ? '' : `
`} + ${lPorts}
${this._nodeType}
${lName}
${lName}
>`; return `[label=${lHtml} ${formattedAttrs} margin=.05]`; @@ -60,7 +65,11 @@ export default abstract class GraphNode { /** * Get the DOT attribute string for the outgoing edges. */ - public dumpDotEdgeAttr(): string { - return `[label=" ${this.outputType}"]`; + public dumpDotEdgeAttr(extra?: { [attr: string]: string }): string { + const attrs = Object.entries(extra ?? {}) + .map(([name, value]) => `${name}="${value}"`) + .join(' '); + + return `[label=" ${this.outputType}" ${attrs}]`; } } diff --git a/src/Graph/Nodes/InputNode.ts b/src/Graph/Nodes/InputNode.ts index 5165bc993d..88e440f2cd 100644 --- a/src/Graph/Nodes/InputNode.ts +++ b/src/Graph/Nodes/InputNode.ts @@ -1,12 +1,17 @@ import GraphNode from './GraphNode.ts'; -import { Type, DumpDotNodeStyle, getBuiltinType } from '../Common.ts'; +import { Type, DumpDotNodeStyle, getBuiltinType, stringify } from '../Common.ts'; /** Represents a node that outputs a constant value. */ export default class InputNode extends GraphNode { public value: any; public constructor(value: any, type?: Type) { - super(new Map(), type ?? getBuiltinType(value)); + const ty = type ?? getBuiltinType(value); + if (ty == undefined) { + throw new Error('Input node type could not be inferred'); + } + + super(new Map(), ty); this.value = value; } @@ -20,7 +25,7 @@ export default class InputNode extends GraphNode { public get dumpDotStyle(): DumpDotNodeStyle { return { - label: name => `${name}: ${this.value}`, + label: name => `${name}: ${stringify(this.value)}`, attrs: { shape: 'rectangle', color: 'goldenrod', diff --git a/src/Graph/Nodes/RenderViewNode.ts b/src/Graph/Nodes/RenderViewNode.ts index 221262580f..93f48a3ac4 100644 --- a/src/Graph/Nodes/RenderViewNode.ts +++ b/src/Graph/Nodes/RenderViewNode.ts @@ -46,11 +46,8 @@ export default class RenderViewNode extends ProcessorNode { public get dumpDotStyle(): DumpDotNodeStyle { const { label: _, attrs } = super.dumpDotStyle; return { - label: (_name: string) => '', - attrs: { - ...attrs, - shape: 'Mcircle', - }, + label: (name: string) => name, + attrs, }; } } diff --git a/src/Graph/Nodes/ScreenShaderNode.ts b/src/Graph/Nodes/ScreenShaderNode.ts index 5eb4081acf..aae56fbaac 100644 --- a/src/Graph/Nodes/ScreenShaderNode.ts +++ b/src/Graph/Nodes/ScreenShaderNode.ts @@ -29,7 +29,6 @@ export default class ScreenShaderNode extends ProcessorNode { `; } - // WARN: This is a temporary hack. Essentially a scuffed singleton pack. // PERF: Evaluate the cost of having a scene per shader node instead. private static _scene: THREE.Scene; @@ -81,8 +80,6 @@ export default class ScreenShaderNode extends ProcessorNode { }), ); - console.log('== FullUniforms ==', fullUniforms); - super( { // Unpacking the uniforms object first allows us to ignore @@ -140,10 +137,7 @@ export default class ScreenShaderNode extends ProcessorNode { const { label, attrs } = super.dumpDotStyle; return { label, - attrs: { - ...attrs, - fillcolor: 'lightcoral', - }, + attrs, }; } } From b81de505dc7cdf20a12a02b44cb537452117c12d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Duhen?= Date: Wed, 15 May 2024 11:15:02 +0200 Subject: [PATCH 08/44] feat: minor dumpDot style fix --- examples/effects_postprocessing.html | 14 ++------------ src/Graph/Graph.ts | 16 ++++++++++++++++ src/Graph/Nodes/GraphNode.ts | 14 +++++++------- 3 files changed, 25 insertions(+), 19 deletions(-) diff --git a/examples/effects_postprocessing.html b/examples/effects_postprocessing.html index 2afce050ab..4fe5dec403 100644 --- a/examples/effects_postprocessing.html +++ b/examples/effects_postprocessing.html @@ -79,18 +79,8 @@ g.getOutput(frame++, coastSharpen); }; - const dumpDot = g.dumpDot(); - const dumpDotLink = (dot => { - const escaped = dot - .replaceAll(/\s+/g, ' ') - .replaceAll(':', '%3A') - .replaceAll(';', '%3B') - .replaceAll('=', '%3D'); - return `https://dreampuf.github.io/GraphvizOnline/#${escaped}`; - })(dumpDot); - - console.log(dumpDot); - window.open(dumpDotLink, '_blank'); + console.log(g.dumpDot()); + window.open(g.dumpDotGraphvizLink(), '_blank'); itowns.Fetcher.json('./layers/JSONLayers/Ortho.json').then(function _(config) { config.source = new itowns.WMTSSource(config.source); diff --git a/src/Graph/Graph.ts b/src/Graph/Graph.ts index 0885d345fb..6795d807ee 100644 --- a/src/Graph/Graph.ts +++ b/src/Graph/Graph.ts @@ -273,4 +273,20 @@ export default class Graph { return dump.join('\n'); } + + /** + * Dump the graph in the DOT format and convert it to a graphviz link. + * @throws If a node input is not part of the graph. + * @returns The GraphvizOnline URL to view the graph. + */ + public dumpDotGraphvizLink(): string { + const dot = this.dumpDot(); + const escaped = dot + .replaceAll('\n', '%0A') + .replaceAll('\t', '%20%20') + .replaceAll(':', '%3A') + .replaceAll(';', '%3B') + .replaceAll('=', '%3D'); + return `https://dreampuf.github.io/GraphvizOnline/#${escaped}`; + } } diff --git a/src/Graph/Nodes/GraphNode.ts b/src/Graph/Nodes/GraphNode.ts index c5361a9a27..0e42eebbfa 100644 --- a/src/Graph/Nodes/GraphNode.ts +++ b/src/Graph/Nodes/GraphNode.ts @@ -48,16 +48,16 @@ export default abstract class GraphNode { .map(([k, v]) => `${k}=${v}`) .join(' '); + const lType = `${this._nodeType}`; + const lName = label(name).trim(); + const lNameFormatted = lName.length == 0 ? [] : [`
${lName}`]; + const lPorts = Array.from(this.inputs) .map(([name, [dep, _ty]]) => - `${name}`) - .join('\n'); - const lHtml = `< - - ${lName.length == 0 ? '' : `
`} - ${lPorts} -
${this._nodeType}
${lName}
>`; + `${name}`); + + const lHtml = ['<', lType, ...lNameFormatted, ...lPorts, '
>'].join('\n'); return `[label=${lHtml} ${formattedAttrs} margin=.05]`; } From 197875929fab41a9cd52dbb93e0ebd4bcea508a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Duhen?= Date: Wed, 15 May 2024 16:49:27 +0200 Subject: [PATCH 09/44] feat: add junction node --- examples/effects_postprocessing.html | 7 ++-- src/Graph/Common.ts | 2 + src/Graph/Graph.ts | 16 ++++---- src/Graph/Nodes/GraphNode.ts | 6 ++- src/Graph/Nodes/JunctionNode.ts | 56 ++++++++++++++++++++++++++++ src/Main.js | 2 +- 6 files changed, 76 insertions(+), 13 deletions(-) create mode 100644 src/Graph/Nodes/JunctionNode.ts diff --git a/examples/effects_postprocessing.html b/examples/effects_postprocessing.html index 4fe5dec403..50370bf46c 100644 --- a/examples/effects_postprocessing.html +++ b/examples/effects_postprocessing.html @@ -51,14 +51,15 @@ setupLoadingScreen(viewerDiv, view); - // Simple postprocessing setup + // Graph showcase const g = new itowns.Graph(); const inputView = new itowns.InputNode(view); + const viewJunction = new itowns.JunctionNode(inputView) const renderer = new itowns.InputNode(view.mainLoop.gfxEngine.renderer); - g.setGrouped({inputView, renderer}, true); + g.setGrouped({inputView, viewJunction, renderer}, true); - const render = new itowns.RenderViewNode(inputView); + const render = new itowns.RenderViewNode(viewJunction); g.setGrouped({render}); // Sobel sharpen diff --git a/src/Graph/Common.ts b/src/Graph/Common.ts index e392ca71e5..f1d3e024d3 100644 --- a/src/Graph/Common.ts +++ b/src/Graph/Common.ts @@ -6,6 +6,7 @@ import InputNode from './Nodes/InputNode.ts'; import ProcessorNode from './Nodes/ProcessorNode.ts'; import ScreenShaderNode from './Nodes/ScreenShaderNode.ts'; import RenderViewNode from './Nodes/RenderViewNode.ts'; +import JunctionNode from './Nodes/JunctionNode.ts'; export type Type = string; export type Dependency = GraphNode | undefined | null; @@ -109,6 +110,7 @@ export { ProcessorNode, ScreenShaderNode, RenderViewNode, + JunctionNode, // Utils BuiltinType, diff --git a/src/Graph/Graph.ts b/src/Graph/Graph.ts index 6795d807ee..9f74fffcdc 100644 --- a/src/Graph/Graph.ts +++ b/src/Graph/Graph.ts @@ -1,5 +1,4 @@ -import GraphNode from './Nodes/GraphNode.ts'; -import { DumpDotGlobalStyle, Type, getColor } from './Common.ts'; +import { GraphNode, DumpDotGlobalStyle, Type, getColor, JunctionNode } from './Common.ts'; type NodeCallback = (node: GraphNode) => void; type StringCallback = (string: string) => void; @@ -225,7 +224,8 @@ export default class Graph { * @returns The graph in the DOT format. */ public dumpDot(): string { - const dump: string[] = ['digraph G {']; + const dump: string[] = []; + dump.push('digraph G {'); if (this.nodes.size > 0) { // Global style defaults @@ -243,7 +243,7 @@ export default class Graph { // Declare nodes dump.push('\t{'); for (const [name, node] of this.nodes) { - dump.push(`\t\t"${name}" ${node.dumpDotAttr(name)};`); + dump.push(`\t\t${node.dumpDot(name)}`); } dump.push('\t}'); @@ -258,13 +258,15 @@ export default class Graph { const nodeEntry = this.findNodeEntry(dep); if (nodeEntry == undefined) { throw new Error( - `An input of node ${name} is not part of the graph`, + `Input "${depName}" of node "${name}" is not part of the graph`, ); } - const attrs = nodeEntry.node.dumpDotEdgeAttr({ ...getColor(null, dep.outputType) }); + const colorStyle = getColor(null, dep.outputType); + const attrs = nodeEntry.node.dumpDotEdgeAttr({ ...(node instanceof JunctionNode ? { arrowhead: 'none' } : {}), ...colorStyle }); + const port = node instanceof JunctionNode ? '' : `:${depName}`; - dump.push(`\t"${nodeEntry.name}" -> "${name}":${depName} ${attrs};`); + dump.push(`\t"${nodeEntry.name}" -> "${name}"${port} ${attrs};`); } } } diff --git a/src/Graph/Nodes/GraphNode.ts b/src/Graph/Nodes/GraphNode.ts index 0e42eebbfa..105ca0481d 100644 --- a/src/Graph/Nodes/GraphNode.ts +++ b/src/Graph/Nodes/GraphNode.ts @@ -42,7 +42,7 @@ export default abstract class GraphNode { * Get the DOT attribute string for the node. * @param name The name of the node. */ - public dumpDotAttr(name: string): string { + public dumpDot(name: string): string { const { label, attrs } = this.dumpDotStyle; const formattedAttrs = Object.entries(attrs) .map(([k, v]) => `${k}=${v}`) @@ -59,11 +59,13 @@ export default abstract class GraphNode { const lHtml = ['<', lType, ...lNameFormatted, ...lPorts, '
>'].join('\n'); - return `[label=${lHtml} ${formattedAttrs} margin=.05]`; + return `"${name}" [label=${lHtml} ${formattedAttrs} margin=.05]`; } /** * Get the DOT attribute string for the outgoing edges. + * + * Example output: [label="Node" shape="box"] */ public dumpDotEdgeAttr(extra?: { [attr: string]: string }): string { const attrs = Object.entries(extra ?? {}) diff --git a/src/Graph/Nodes/JunctionNode.ts b/src/Graph/Nodes/JunctionNode.ts new file mode 100644 index 0000000000..141ae63f40 --- /dev/null +++ b/src/Graph/Nodes/JunctionNode.ts @@ -0,0 +1,56 @@ +import { Dependency, DumpDotNodeStyle, Type, getColor } from '../Common.ts'; +import GraphNode from './GraphNode.ts'; + +export default class JunctionNode extends GraphNode { + private static inputName = 'value'; + + public constructor(input: GraphNode | Type) { + if (input instanceof GraphNode) { + super(new Map([[JunctionNode.inputName, [input, input.outputType]]]), input.outputType); + } else { + super(new Map([[JunctionNode.inputName, [undefined, input]]]), input); + } + } + + protected _apply(frame: number): any { + const node = this.inputs.entries().next().value[1][0] as Dependency; + return node?.getOutput(frame) ?? null; + } + + public set input(node: Dependency) { + const [_oldValue, type] = this.inputs.get(JunctionNode.inputName)!; + this.inputs.set(JunctionNode.inputName, [node, type]); + } + + protected get _nodeType(): string { + const name = JunctionNode.name; + return name.slice(0, name.length - 4); + } + + public get dumpDotStyle(): DumpDotNodeStyle { + return { + label: name => `${name}`, + attrs: { + shape: 'insulator', + ...getColor(null, this.outputType), + }, + }; + } + + public dumpDot(name: string): string { + const { label: _, attrs } = this.dumpDotStyle; + const formattedAttrs = Object.entries(attrs) + .map(([k, v]) => `${k}=${v}`) + .join(' '); + + return `"${name}" [label="" ${formattedAttrs} margin=.05]`; + } + + public dumpDotEdgeAttr(extra?: { [attr: string]: string; } | undefined): string { + const attrs = Object.entries(extra ?? {}) + .map(([name, value]) => `${name}="${value}"`) + .join(' '); + + return `[${attrs}]`; + } +} diff --git a/src/Main.js b/src/Main.js index 367dea581f..d868dc53eb 100644 --- a/src/Main.js +++ b/src/Main.js @@ -108,4 +108,4 @@ export { C3DTilesTypes, C3DTilesBoundingVolumeTypes } from './Core/3DTiles/C3DTi export { default as C3DTBatchTableHierarchyExtension } from './Core/3DTiles/C3DTBatchTableHierarchyExtension'; export { process3dTilesNode, $3dTilesCulling, $3dTilesSubdivisionControl } from 'Process/3dTilesProcessing'; -export { Graph, GraphNode, InputNode, RenderViewNode, ProcessorNode, ScreenShaderNode, BuiltinType } from 'Graph/Common.ts'; +export { Graph, GraphNode, InputNode, RenderViewNode, ProcessorNode, ScreenShaderNode, BuiltinType, JunctionNode } from 'Graph/Common.ts'; From 275abe55785eb5a0ba5ed97f02d25784815a2b20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Duhen?= Date: Wed, 15 May 2024 19:08:41 +0200 Subject: [PATCH 10/44] feat: add working subgraphs, dotDump is WIP --- examples/effects_postprocessing.html | 33 ++++++++++---- src/Graph/Common.ts | 4 ++ src/Graph/Graph.ts | 60 ++++++++++++++++++------ src/Graph/Nodes/GraphInputNode.ts | 25 ++++++++++ src/Graph/Nodes/GraphNode.ts | 8 ++-- src/Graph/Nodes/InputNode.ts | 4 +- src/Graph/Nodes/JunctionNode.ts | 23 +++++++--- src/Graph/Nodes/ProcessorNode.ts | 6 +-- src/Graph/Nodes/ScreenShaderNode.ts | 2 +- src/Graph/Nodes/SubGraphNode.ts | 68 ++++++++++++++++++++++++++++ src/Main.js | 2 +- 11 files changed, 194 insertions(+), 41 deletions(-) create mode 100644 src/Graph/Nodes/GraphInputNode.ts create mode 100644 src/Graph/Nodes/SubGraphNode.ts diff --git a/examples/effects_postprocessing.html b/examples/effects_postprocessing.html index 50370bf46c..cda3571c9c 100644 --- a/examples/effects_postprocessing.html +++ b/examples/effects_postprocessing.html @@ -16,7 +16,7 @@ - + @@ -62,12 +62,9 @@ const outerGraph = new itowns.Graph(); const inputView = new itowns.InputNode(view); - const viewJunction = new itowns.JunctionNode(inputView) const renderer = new itowns.InputNode(view.mainLoop.gfxEngine.renderer); - outerGraph.setGrouped({inputView, viewJunction, renderer}); - - const render = new itowns.RenderViewNode(viewJunction); - outerGraph.setGrouped({render}); + const render = new itowns.RenderViewNode(inputView); + outerGraph.setGrouped({inputView, renderer, render}); // Subgraphs const postProcessingGraph = new itowns.Graph(); @@ -76,26 +73,32 @@ const sharpenWeight = new itowns.InputNode(5); const sharpenOffset = new itowns.InputNode(new itowns.THREE.Vector2(0.0025, 0.)); postProcessingGraph.setGrouped({sharpenWeight, sharpenOffset}); - // Filter + // Screen shaders const coastSharpen = new itowns.ScreenShaderNode(render, renderer, { fragmentShader: document.getElementById('coastSharpenFragmentShader').textContent, toScreen: true, uniforms: {uWeight: sharpenWeight, uOffset: sharpenOffset}, }); - postProcessingGraph.setGrouped({coastSharpen}); + const greyscale = new itowns.ScreenShaderNode(coastSharpen, renderer, { + fragmentShader: document.getElementById('greyscaleFragmentShader').textContent, + toScreen: true, + }); + postProcessingGraph.setGrouped({coastSharpen, greyscale}); - const postProcessingGraphNode = new itowns.SubGraphNode(outerGraph, postProcessingGraph, coastSharpen, 'PostProcessing'); + const postProcessingGraphNode = new itowns.SubGraphNode(outerGraph, postProcessingGraph, greyscale, 'PostProcessing'); outerGraph.setGrouped({postProcessingGraphNode}); - let frame = 0; - view.render = function render() { - outerGraph.getOutput(frame++, postProcessingGraphNode); - }; + console.log(coastSharpen.inputs.get('input')) console.dir(outerGraph); console.log(outerGraph.dumpDot()); window.open(outerGraph.dumpDotGraphvizLink(), '_blank'); + let frame = 0; + view.render = function render() { + outerGraph.getOutput(frame++, postProcessingGraphNode); + }; + itowns.Fetcher.json('./layers/JSONLayers/Ortho.json').then(function _(config) { config.source = new itowns.WMTSSource(config.source); var layer = new itowns.ColorLayer('Ortho', config); diff --git a/package-lock.json b/package-lock.json index 37aeca2028..feaa3145cb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45,6 +45,7 @@ "copyfiles": "^2.4.1", "core-js": "^3.34.0", "cross-env": "^7.0.3", + "deep-object-diff": "^1.1.9", "eslint": "^8.55.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-import-resolver-webpack": "^0.13.8", @@ -5112,6 +5113,12 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "node_modules/deep-object-diff": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/deep-object-diff/-/deep-object-diff-1.1.9.tgz", + "integrity": "sha512-Rn+RuwkmkDwCi2/oXOFS9Gsr5lJZu/yTGpK7wAaAIE75CC+LCGEZHpY6VQJa/RoJcrmaA/docWJZvYohlNkWPA==", + "dev": true + }, "node_modules/default-gateway": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", diff --git a/package.json b/package.json index b6bf63496b..111e2cf06c 100644 --- a/package.json +++ b/package.json @@ -97,6 +97,7 @@ "copyfiles": "^2.4.1", "core-js": "^3.34.0", "cross-env": "^7.0.3", + "deep-object-diff": "^1.1.9", "eslint": "^8.55.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-import-resolver-webpack": "^0.13.8", diff --git a/src/Graph/Graph.ts b/src/Graph/Graph.ts index 20b20323ac..3edf40386e 100644 --- a/src/Graph/Graph.ts +++ b/src/Graph/Graph.ts @@ -1,3 +1,4 @@ +import { diff as objectDiff } from 'deep-object-diff'; import { GraphNode, DumpDotGlobalStyle, Type, getColor, JunctionNode, SubGraphNode, InputNode, GraphInputNode } from './Common.ts'; type NodeCallback = (node: GraphNode) => void; @@ -28,7 +29,7 @@ export default class Graph { this._valid = false; } - public get valid(): boolean { + public get isValid(): boolean { return this._valid; } @@ -131,7 +132,7 @@ export default class Graph { const nodeName = this.findNodeEntry(node)?.name ?? this.findInputEntry(node)?.name ?? undefined; if (nodeName == undefined) { console.error(node); - throw new Error('AAAAAAA'); + throw new Error(`Node not found in nodes or inputs after following this path: ${path}`); } if (visited.has(nodeName)) { @@ -163,10 +164,7 @@ export default class Graph { path.delete(nodeName); } - /** - * Find a node's entry in the graph. O(n) time complexity. - * Returns the whole entry to be easier to expand later on if needed. - */ + /** Find a node's entry in the graph. O(n) time complexity. */ public findNodeEntry(node: GraphNode): { name: string, node: GraphNode } | null { for (const [name, oNode] of this.nodes.entries()) { if (node == oNode) { @@ -176,10 +174,7 @@ export default class Graph { return null; } - /** - * Find a node's entry in the graph. O(n) time complexity. - * Returns the whole entry to be easier to expand later on if needed. - */ + /** Find a node's entry in the inputs. O(n) time complexity. */ public findInputEntry(node: GraphNode): { name: string, node: GraphInputNode } | null { for (const [name, oNode] of this.inputs.entries()) { if (node == oNode) { @@ -228,6 +223,7 @@ export default class Graph { return { rankdir: 'LR', node: { + shape: 'mbox', fontname: 'Arial', style: 'filled', fillcolor: 'whitesmoke', @@ -283,20 +279,21 @@ export default class Graph { for (const [depName, [dep, _ty]] of node.inputs) { if (dep == null) { continue; } + // Lookup the node in the graph nodes and inputs // PERF: Inefficient but the alternative is duplicating the names // inside the nodes and that makes the API much heavier so we'll // have to live with it as it will likely never be an issue. const nodeEntry = this.findNodeEntry(dep) ?? (isSubgraph ? this.findInputEntry(dep) : undefined); if (nodeEntry == undefined) { throw new Error( - `Input "${depName}" of node "${nodeName}" is not part of the graph`, + `Input "${depName}" of node "${nodeName}" is not part of the ${isSubgraph ? `subgraph "${subgraphName}"` : 'graph'}`, ); } const { name: entryName, node: entryNode } = nodeEntry; const colorStyle = getColor(null, dep.outputType); const attrs = nodeEntry.node.dumpDotEdgeAttr({ - ...(node instanceof JunctionNode || entryNode instanceof GraphInputNode ? { arrowhead: 'none' } : {}), + ...(node instanceof JunctionNode ? { arrowhead: 'none' } : {}), ...colorStyle, }); const port = node instanceof JunctionNode ? '' : `:${depName}`; diff --git a/src/Graph/Nodes/GraphNode.ts b/src/Graph/Nodes/GraphNode.ts index 8b158b30f1..1ea77e8e4c 100644 --- a/src/Graph/Nodes/GraphNode.ts +++ b/src/Graph/Nodes/GraphNode.ts @@ -18,7 +18,7 @@ export default abstract class GraphNode { protected abstract _apply(graph: Graph, frame: number): any; - protected abstract get _nodeType(): string; + public abstract get nodeType(): string; /** * Get the output of the node at a given frame. @@ -48,7 +48,7 @@ export default abstract class GraphNode { .map(([k, v]) => `${k}=${v}`) .join(' '); - const lType = `${this._nodeType}`; + const lType = `${this.nodeType}`; const lName = label(name).trim(); const lNameFormatted = lName.length == 0 ? [] : [`
${lName}`]; diff --git a/src/Graph/Nodes/InputNode.ts b/src/Graph/Nodes/InputNode.ts index c352d02ce6..e06e15e102 100644 --- a/src/Graph/Nodes/InputNode.ts +++ b/src/Graph/Nodes/InputNode.ts @@ -19,7 +19,7 @@ export default class InputNode extends GraphNode { return this.value; } - protected get _nodeType(): string { + public get nodeType(): string { return InputNode.name.replace('Node', ''); } @@ -27,7 +27,6 @@ export default class InputNode extends GraphNode { return { label: name => `${name}: ${stringify(this.value)}`, attrs: { - shape: 'rectangle', color: 'goldenrod', }, }; diff --git a/src/Graph/Nodes/JunctionNode.ts b/src/Graph/Nodes/JunctionNode.ts index 400752dba4..4a3bcc7fce 100644 --- a/src/Graph/Nodes/JunctionNode.ts +++ b/src/Graph/Nodes/JunctionNode.ts @@ -28,7 +28,7 @@ export default class JunctionNode extends GraphNode { this.inputs.set(JunctionNode.inputName, [node, type]); } - protected get _nodeType(): string { + public get nodeType(): string { const name = JunctionNode.name; return name.slice(0, name.length - 4); } diff --git a/src/Graph/Nodes/ProcessorNode.ts b/src/Graph/Nodes/ProcessorNode.ts index 502ad9a342..e3d0444eeb 100644 --- a/src/Graph/Nodes/ProcessorNode.ts +++ b/src/Graph/Nodes/ProcessorNode.ts @@ -25,7 +25,7 @@ export default class ProcessorNode extends GraphNode { return this.callback(frame, argObj); } - protected get _nodeType(): string { + public get nodeType(): string { return ProcessorNode.name.replace('Node', ''); } @@ -33,7 +33,6 @@ export default class ProcessorNode extends GraphNode { return { label: name => `${name}`, attrs: { - shape: 'box', color: 'lightskyblue', }, }; diff --git a/src/Graph/Nodes/RenderViewNode.ts b/src/Graph/Nodes/RenderViewNode.ts index 93f48a3ac4..7c1d97a7b9 100644 --- a/src/Graph/Nodes/RenderViewNode.ts +++ b/src/Graph/Nodes/RenderViewNode.ts @@ -39,7 +39,7 @@ export default class RenderViewNode extends ProcessorNode { }); } - protected get _nodeType(): string { + public get nodeType(): string { return RenderViewNode.name.replace('Node', ''); } diff --git a/src/Graph/Nodes/ScreenShaderNode.ts b/src/Graph/Nodes/ScreenShaderNode.ts index 8f08ed140f..223f95d366 100644 --- a/src/Graph/Nodes/ScreenShaderNode.ts +++ b/src/Graph/Nodes/ScreenShaderNode.ts @@ -129,7 +129,7 @@ export default class ScreenShaderNode extends ProcessorNode { }); } - protected get _nodeType(): string { + public get nodeType(): string { return ScreenShaderNode.name.replace('Node', ''); } diff --git a/src/Graph/Nodes/SubGraphNode.ts b/src/Graph/Nodes/SubGraphNode.ts index a1800a0dd3..38540f8bd1 100644 --- a/src/Graph/Nodes/SubGraphNode.ts +++ b/src/Graph/Nodes/SubGraphNode.ts @@ -27,11 +27,19 @@ export default class SubGraphNode extends GraphNode { for (const [_nodeName, node] of this.graph.nodes) { for (const [depName, [dep, depType]] of node.inputs) { if (dep != undefined && Array.from(this.graph.nodes.values()).find(oNode => oNode == dep) == undefined) { + // Try to find an already created graph input for this dependency + const findInput = Array.from(this.graph.inputs).find(([name, _input]) => name == depName); + if (findInput != undefined) { + const [_name, input] = findInput; + node.inputs.set(depName, [input, depType]); + continue; + } + // TODO: only works for one level of nesting, we might need a resolve function but // I'm not sure the case where it'd be needed will ever occur. const newInput = new GraphInputNode(Object.fromEntries([[outerGraph.findNodeEntry(dep)!.name, dep]])); - this.graph.inputs.set(depName, newInput); - node.inputs.set(depName, [newInput, depType]); + const addedInput = this.graph.inputs.set(depName, newInput).get(depName)!; + node.inputs.set(depName, [addedInput, depType]); } } } @@ -45,7 +53,7 @@ export default class SubGraphNode extends GraphNode { return this._label; } - protected get _nodeType(): string { + public get nodeType(): string { return SubGraphNode.name.replace('Node', ''); } From 7ec6ed899f36ceaed2e9c543dcd9b5f1703ec818 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Duhen?= Date: Thu, 16 May 2024 16:34:57 +0200 Subject: [PATCH 14/44] fix: correct render target mistake in example --- examples/effects_postprocessing.html | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/examples/effects_postprocessing.html b/examples/effects_postprocessing.html index 9bbafc44ad..11d0b21e68 100644 --- a/examples/effects_postprocessing.html +++ b/examples/effects_postprocessing.html @@ -76,7 +76,6 @@ // Screen shaders const coastSharpen = new itowns.ScreenShaderNode(render, renderer, { fragmentShader: document.getElementById('coastSharpenFragmentShader').textContent, - toScreen: true, uniforms: {uWeight: sharpenWeight, uOffset: sharpenOffset}, }); const greyscale = new itowns.ScreenShaderNode(coastSharpen, renderer, { @@ -85,14 +84,14 @@ }); postProcessingGraph.setGrouped({coastSharpen, greyscale}); - const postProcessingGraphNode = new itowns.SubGraphNode(outerGraph, postProcessingGraph, greyscale, 'PostProcessing'); + const postProcessingGraphNode = new itowns.SubGraphNode(outerGraph, postProcessingGraph, greyscale, 'Post-processing'); outerGraph.setGrouped({postProcessingGraphNode}); console.log(coastSharpen.inputs.get('input')) console.dir(outerGraph); console.log(outerGraph.dumpDot()); - window.open(outerGraph.dumpDotGraphvizLink(), '_blank'); + // window.open(outerGraph.dumpDotGraphvizLink(), '_blank'); let frame = 0; view.render = function render() { From e5311d593cc5bd4121a336e2b6698552a7650570 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Duhen?= Date: Wed, 22 May 2024 15:29:16 +0200 Subject: [PATCH 15/44] feat!: add View nodes --- examples/effects_postprocessing.html | 51 ++++++++++++++-------------- examples/js/GUI/LoadingScreen.js | 2 +- src/Graph/Common.ts | 41 ++++++++++++++++++---- src/Graph/Graph.ts | 4 +-- src/Graph/Nodes/FieldGetterNode.ts | 25 ++++++++++++++ src/Graph/Nodes/GlobeViewNode.ts | 31 +++++++++++++++++ src/Graph/Nodes/GraphNode.ts | 15 ++++---- src/Graph/Nodes/InputNode.ts | 7 ++-- src/Graph/Nodes/JunctionNode.ts | 2 +- src/Graph/Nodes/LazyStaticNode.ts | 27 +++++++++++++++ src/Graph/Nodes/ProcessorNode.ts | 7 ++-- src/Graph/Nodes/SubGraphNode.ts | 2 +- src/Graph/Nodes/ViewNode.ts | 13 +++++++ src/Main.js | 2 +- 14 files changed, 173 insertions(+), 56 deletions(-) create mode 100644 src/Graph/Nodes/FieldGetterNode.ts create mode 100644 src/Graph/Nodes/GlobeViewNode.ts create mode 100644 src/Graph/Nodes/LazyStaticNode.ts create mode 100644 src/Graph/Nodes/ViewNode.ts diff --git a/examples/effects_postprocessing.html b/examples/effects_postprocessing.html index 11d0b21e68..88c19d1f36 100644 --- a/examples/effects_postprocessing.html +++ b/examples/effects_postprocessing.html @@ -48,50 +48,51 @@ - - - - - -
+ + - - - - - - + + - + + + + + - + // ---------- DEBUG TOOLS : ---------- + + debug.createTileDebugUI(debugMenu.gui, view); + + + + diff --git a/src/Graph/Graph.ts b/src/Graph/Graph.ts index 7e6b800092..f5602635a8 100644 --- a/src/Graph/Graph.ts +++ b/src/Graph/Graph.ts @@ -34,7 +34,7 @@ export default class Graph { * @throws If the node does not exist. * @returns The output of the node at the given frame. */ - public getOutput(frame: number, out: Dependency | [GraphNode, string] | [string, string] | GraphNode | string): any { + public getOutput(frame: number, out: Dependency | [GraphNode, string] | [string, string] | GraphNode | string): unknown { this.validate(); if (out instanceof GraphNode) { diff --git a/src/Graph/Nodes/CameraDataNode.ts b/src/Graph/Nodes/CameraDataNode.ts index 818167114d..5c45312277 100644 --- a/src/Graph/Nodes/CameraDataNode.ts +++ b/src/Graph/Nodes/CameraDataNode.ts @@ -14,8 +14,7 @@ export default class CameraDataNode extends ProcessorNode { })), (_frame, args) => { const camera = args.camera as CameraLike; - this._out.outputs.set('cameraNear', [camera.near, BuiltinType.Number]); - this._out.outputs.set('cameraFar', [camera.far, BuiltinType.Number]); + this.updateOutputs({ cameraNear: camera.near, cameraFar: camera.far }); }, ); } diff --git a/src/Graph/Nodes/DepthGetterNode.ts b/src/Graph/Nodes/DepthGetterNode.ts index c8caa8226d..9f1dd76c49 100644 --- a/src/Graph/Nodes/DepthGetterNode.ts +++ b/src/Graph/Nodes/DepthGetterNode.ts @@ -7,10 +7,7 @@ export default class DepthGetterNode extends ProcessorNode { { target: [target, BuiltinType.RenderTarget] }, BuiltinType.Texture, (_frame, args) => - this._out.outputs.set(DepthGetterNode.defaultIoName, [ - (args.target as WebGLRenderTarget).depthTexture, - BuiltinType.Texture, - ]), + this.updateOutputs({ [DepthGetterNode.defaultIoName]: (args.target as WebGLRenderTarget).depthTexture }), ); } } diff --git a/src/Graph/Nodes/GlobeViewNode.ts b/src/Graph/Nodes/GlobeViewNode.ts index 22140785cd..f54a125be9 100644 --- a/src/Graph/Nodes/GlobeViewNode.ts +++ b/src/Graph/Nodes/GlobeViewNode.ts @@ -11,9 +11,11 @@ export default class GlobeViewNode extends ViewNode { } const view = new GlobeView(args.viewerDiv as HTMLDivElement, args.placement as Extent); - this.outputs.set('view', [view, BuiltinType.View]); - this.outputs.set('renderer', [view.mainLoop.gfxEngine.renderer, BuiltinType.Renderer]); - this.outputs.set('camera', [view.camera, BuiltinType.Camera]); + this.updateOutputs({ + view, + renderer: view.mainLoop.gfxEngine.renderer, + camera: view.camera, + }); }, { placement: [placement, BuiltinType.Placement] }, ); diff --git a/src/Graph/Nodes/GraphNode.ts b/src/Graph/Nodes/GraphNode.ts index 0d2acc35a7..b95244072d 100644 --- a/src/Graph/Nodes/GraphNode.ts +++ b/src/Graph/Nodes/GraphNode.ts @@ -13,7 +13,7 @@ export default abstract class GraphNode { // protected _out: [number, any | undefined]; protected _out: { frame: number, - outputs: Map, + outputs: Map, /** Stored in ms. */ timeTaken?: number, }; @@ -70,6 +70,23 @@ export default abstract class GraphNode { return { node: this, output: output ?? GraphNode.defaultIoName }; } + protected updateOutputs(updates: { [name: string]: unknown }): void { + const errors = []; + + for (const [name, value] of Object.entries(updates)) { + const output = this._out.outputs.get(name); + if (output == undefined) { + errors.push(`Provided ${this.nodeType} node does not have an output named '${name}' to update`); + continue; + } + this._out.outputs.set(name, [value, output[1]]); + } + + if (errors.length > 0) { + throw new Error(errors.join('\n')); + } + } + /** * Get the output of the node at a given frame. * @param name The name of the output to get. @@ -77,7 +94,7 @@ export default abstract class GraphNode { * @param frame The frame to get the output for. * @returns The output of the node at the given frame. */ - public getOutput(name: string = GraphNode.defaultIoName, graph?: Graph, frame: number = 0): [any, Type] { + public getOutput(name: string = GraphNode.defaultIoName, graph?: Graph, frame: number = 0): unknown { const { frame: oFrane, outputs } = this._out; if (!outputs.has(name)) { diff --git a/src/Graph/Nodes/InputNode.ts b/src/Graph/Nodes/InputNode.ts index 815fd6e9a1..04b8e7f5b9 100644 --- a/src/Graph/Nodes/InputNode.ts +++ b/src/Graph/Nodes/InputNode.ts @@ -20,7 +20,7 @@ export default class InputNode extends GraphNode { } protected override _apply(_graph?: Graph, _frame?: number): void { - this.outputs.set(GraphNode.defaultIoName, [this.value, this._type]); + this.updateOutputs({ [GraphNode.defaultIoName]: this.value }); } public override get nodeType(): string { diff --git a/src/Graph/Nodes/JunctionNode.ts b/src/Graph/Nodes/JunctionNode.ts index 04c3ecc7ce..f989498e66 100644 --- a/src/Graph/Nodes/JunctionNode.ts +++ b/src/Graph/Nodes/JunctionNode.ts @@ -19,15 +19,14 @@ export default class JunctionNode extends GraphNode { protected override _apply(graph?: Graph, frame: number = 0): void { const dep = this.inputs.get(GraphNode.defaultIoName)![0]; - const [_oValue, oType] = this.outputs.get(GraphNode.defaultIoName)!; if (dep == null) { - this.outputs.set(GraphNode.defaultIoName, [null, oType]); + this.updateOutputs({ [GraphNode.defaultIoName]: null }); return; } const { node, output } = dep; - this.outputs.set(GraphNode.defaultIoName, [node.getOutput(output, graph, frame) ?? null, oType]); + this.updateOutputs({ [GraphNode.defaultIoName]: node.getOutput(output, graph, frame) ?? null }); } public get input(): [Dependency | null, Type] { diff --git a/src/Graph/Nodes/PlanarViewNode.ts b/src/Graph/Nodes/PlanarViewNode.ts index e35205e729..b672355dc3 100644 --- a/src/Graph/Nodes/PlanarViewNode.ts +++ b/src/Graph/Nodes/PlanarViewNode.ts @@ -11,9 +11,11 @@ export default class PlanarViewNode extends ViewNode { } const view = new PlanarView(args.viewerDiv as HTMLDivElement, args.placement as Extent); - this.outputs.set('view', [view, BuiltinType.View]); - this.outputs.set('renderer', [view.mainLoop.gfxEngine.renderer, BuiltinType.Renderer]); - this.outputs.set('camera', [view.camera, BuiltinType.Camera]); + this.updateOutputs({ + view, + renderer: view.mainLoop.gfxEngine.renderer, + camera: view.camera, + }); }, { placement: [placement, BuiltinType.Placement] }, ); diff --git a/src/Graph/Nodes/RenderViewNode.ts b/src/Graph/Nodes/RenderViewNode.ts index 43f8c66a12..3be22020b5 100644 --- a/src/Graph/Nodes/RenderViewNode.ts +++ b/src/Graph/Nodes/RenderViewNode.ts @@ -38,7 +38,7 @@ export default class RenderViewNode extends ProcessorNode { renderer.clear(); renderer.render(view.scene, view.camera3D); - this._out.outputs.set(RenderViewNode.defaultIoName, [this._target, BuiltinType.RenderTarget]); + this.updateOutputs({ [RenderViewNode.defaultIoName]: this._target }); }); } diff --git a/src/Graph/Nodes/ScreenShaderNode.ts b/src/Graph/Nodes/ScreenShaderNode.ts index 91d8ed4ecc..b97a963b88 100644 --- a/src/Graph/Nodes/ScreenShaderNode.ts +++ b/src/Graph/Nodes/ScreenShaderNode.ts @@ -137,7 +137,7 @@ void main() { renderer.clear(); renderer.render(ScreenShaderNode._scene, ScreenShaderNode._camera); - this._out.outputs.set(ScreenShaderNode.defaultIoName, [target, BuiltinType.RenderTarget]); + this.updateOutputs({ [ScreenShaderNode.defaultIoName]: target }); }); this._fragmentShaderParts = fragmentShaderParts; diff --git a/src/Graph/Nodes/SubGraphNode.ts b/src/Graph/Nodes/SubGraphNode.ts index d847cc9631..4e03142f36 100644 --- a/src/Graph/Nodes/SubGraphNode.ts +++ b/src/Graph/Nodes/SubGraphNode.ts @@ -75,9 +75,9 @@ export default class SubGraphNode extends GraphNode { } protected override _apply(_graph?: Graph, frame: number = 0): void { - for (const [name, [dep, ty]] of this.graphOutputs) { - this.outputs.set(name, [dep.node.getOutput(dep.output, this.graph, frame), ty]); - } + const updates = Array.from(this.graphOutputs.entries()) + .map(([name, [dep, _ty]]) => [name, dep.node.getOutput(dep.output, this.graph, frame)]); + this.updateOutputs(Object.fromEntries(updates)); } public get label(): string | undefined { From 30ea8d5ba9e2cf891193de07154dd3a58909929c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Duhen?= Date: Mon, 24 Jun 2024 14:13:46 +0200 Subject: [PATCH 36/44] feat(wip): make outputs objects, add dependants --- src/Graph/Graph.ts | 6 ++-- src/Graph/Nodes/GraphNode.ts | 55 +++++++++++++---------------- src/Graph/Nodes/GraphOutputNode.ts | 2 +- src/Graph/Nodes/JunctionNode.ts | 4 +-- src/Graph/Nodes/ScreenShaderNode.ts | 10 +++--- src/Graph/Nodes/SubGraphNode.ts | 2 +- src/Graph/Nodes/ViewNode.ts | 10 +++--- src/Graph/Prelude.ts | 2 ++ src/Graph/Types.ts | 4 +++ 9 files changed, 47 insertions(+), 48 deletions(-) diff --git a/src/Graph/Graph.ts b/src/Graph/Graph.ts index f5602635a8..0cf2b57eab 100644 --- a/src/Graph/Graph.ts +++ b/src/Graph/Graph.ts @@ -83,8 +83,8 @@ export default class Graph { for (const [_name, [_, ty]] of node.inputs) { this.types.add(ty); } - for (const [_name, [_, ty]] of node.outputs) { - this.types.add(ty); + for (const [_name, output] of node.outputs) { + this.types.add(output.type); } return true; @@ -170,7 +170,7 @@ export default class Graph { throw new Error(`Dangling dependency: '${nodeName}.${name}'; ` + `${dep.node.nodeType} dependency does not have an output named '${dep.output}'`); } - const [_outputValue, outputType] = output; + const outputType = output.type; if (outputType != type && type != BuiltinType.Any) { throw new Error(`Invalid type for dependency ${nodeName}.${name}` diff --git a/src/Graph/Nodes/GraphNode.ts b/src/Graph/Nodes/GraphNode.ts index b95244072d..2b7c874168 100644 --- a/src/Graph/Nodes/GraphNode.ts +++ b/src/Graph/Nodes/GraphNode.ts @@ -1,9 +1,15 @@ -import { Type, Dependency, DumpDotNodeStyle, Graph, LazyStaticNode } from '../Prelude'; +import { Type, Dependency, Dependant, DumpDotNodeStyle, Graph, LazyStaticNode } from '../Prelude'; function lerp(a: number, b: number, t: number): number { return a + (b - a) * t; } +export interface Output { + value: unknown, + type: Type, + dependants: Set, +} + /** * Represents a node in a directed graph. * Base class for all other types of nodes. @@ -13,7 +19,7 @@ export default abstract class GraphNode { // protected _out: [number, any | undefined]; protected _out: { frame: number, - outputs: Map, + outputs: Map, /** Stored in ms. */ timeTaken?: number, }; @@ -39,17 +45,18 @@ export default abstract class GraphNode { ], ])); - let normalizedOutputs: Map; - - if (outputs == undefined) { - normalizedOutputs = new Map(); - } else if (outputs instanceof Map) { - normalizedOutputs = new Map(Array.from(outputs.entries()).map(([name, ty]) => [name, [undefined, ty]])); - } else if (typeof outputs == 'string') { - normalizedOutputs = new Map([[GraphNode.defaultIoName, [undefined, outputs]]]); - } else { - throw new Error('Unrecognized type when constructing node outputs'); - } + const normalizedOutputs: Map = new Map((() => { + if (outputs == undefined) { + return []; + } else if (outputs instanceof Map) { + return Array.from(outputs.entries()) + .map(([name, ty]) => [name, { value: undefined, type: ty, dependants: new Set() }]); + } else if (typeof outputs == 'string') { + return [[GraphNode.defaultIoName, { value: undefined, type: outputs, dependants: new Set() }]]; + } else { + throw new Error('Unrecognized type when constructing node outputs'); + } + })()); this._out = { frame: -1, outputs: normalizedOutputs }; this._id = GraphNode.idCounter++; @@ -62,7 +69,7 @@ export default abstract class GraphNode { return this._id; } - public get outputs(): Map { + public get outputs(): Map { return this._out.outputs; } @@ -79,7 +86,7 @@ export default abstract class GraphNode { errors.push(`Provided ${this.nodeType} node does not have an output named '${name}' to update`); continue; } - this._out.outputs.set(name, [value, output[1]]); + output.value = value; } if (errors.length > 0) { @@ -97,21 +104,11 @@ export default abstract class GraphNode { public getOutput(name: string = GraphNode.defaultIoName, graph?: Graph, frame: number = 0): unknown { const { frame: oFrane, outputs } = this._out; - if (!outputs.has(name)) { - throw new Error(`Provided ${this.nodeType} node does not have an output named '${name}'`); - } - const getOutput = outputs.get(name); if (getOutput == undefined) { throw new Error(`Provided ${this.nodeType} node does not have an output named '${name}'`); } - const [oValue, _oType] = getOutput; - - // const thisName = graph?.findNode(this)?.name; - // const debugName = `${thisName == undefined ? '' : `${thisName}: `}${this.nodeType}`; - - // GraphNode.depth++; - // const tab = '| '.repeat(GraphNode.depth - 1); + const oValue = getOutput.value; if (oValue == undefined || oFrane !== frame) { // console.log(`${tab}[${debugName}] calling _apply`); @@ -119,11 +116,7 @@ export default abstract class GraphNode { this._out.frame = frame; } - const output = this._out.outputs.get(name); - // console.log(`${tab}[${debugName}] getOutput(${name}): `, output); - // GraphNode.depth--; - - return output![0]; + return getOutput.value; } /** diff --git a/src/Graph/Nodes/GraphOutputNode.ts b/src/Graph/Nodes/GraphOutputNode.ts index f07cdf1301..4e9950b055 100644 --- a/src/Graph/Nodes/GraphOutputNode.ts +++ b/src/Graph/Nodes/GraphOutputNode.ts @@ -21,7 +21,7 @@ export default class GraphOutputNode extends JunctionNode { if (socket == undefined) { throw new Error(`Provided ${node.nodeType} node does not have an output named '${output}'`); } - this.inputs.set(GraphNode.defaultIoName, [{ node, output }, socket[1]]); + this.inputs.set(GraphNode.defaultIoName, [{ node, output }, socket.type]); } public dumpDot(name: string): string { diff --git a/src/Graph/Nodes/JunctionNode.ts b/src/Graph/Nodes/JunctionNode.ts index f989498e66..3177b88313 100644 --- a/src/Graph/Nodes/JunctionNode.ts +++ b/src/Graph/Nodes/JunctionNode.ts @@ -9,7 +9,7 @@ export default class JunctionNode extends GraphNode { if (nodeOutput == undefined) { throw new Error(`Provided ${node.nodeType} node does not have an output named '${output}'`); } - const ty = nodeOutput[1]; + const ty = nodeOutput.type; super(new Map([[GraphNode.defaultIoName, [input, ty]]]), ty); } else { @@ -49,7 +49,7 @@ export default class JunctionNode extends GraphNode { shape: 'doublecircle', width: '.1', height: '.1', - ...Mappings.colorize(null, this.outputs.get(GraphNode.defaultIoName)![1]), + ...Mappings.colorize(null, this.outputs.get(GraphNode.defaultIoName)!.type), }, }; } diff --git a/src/Graph/Nodes/ScreenShaderNode.ts b/src/Graph/Nodes/ScreenShaderNode.ts index b97a963b88..a7e02739bd 100644 --- a/src/Graph/Nodes/ScreenShaderNode.ts +++ b/src/Graph/Nodes/ScreenShaderNode.ts @@ -90,9 +90,9 @@ void main() { if (typeof uniform == 'string') { val = [null, uniform]; } else if (uniform instanceof GraphNode) { - val = [{ node: uniform, output: GraphNode.defaultIoName }, uniform.outputs.get(GraphNode.defaultIoName)![1]]; + val = [{ node: uniform, output: GraphNode.defaultIoName }, uniform.outputs.get(GraphNode.defaultIoName)!.type]; } else { - val = [uniform, uniform.node.outputs.get(uniform.output)![1]]; + val = [uniform, uniform.node.outputs.get(uniform.output)!.type]; } return [name, val]; @@ -131,7 +131,7 @@ void main() { const target: THREE.WebGLRenderTarget | null = toScreen ? null - : (this.outputs.get(GraphNode.defaultIoName)![0] ?? createRenderTarget(input)); + : ((this.outputs.get(GraphNode.defaultIoName)!.value as THREE.WebGLRenderTarget | null) ?? createRenderTarget(input)); renderer.setRenderTarget(target); renderer.clear(); @@ -158,9 +158,9 @@ void main() { if (typeof uniform == 'string') { ty = uniform; } else if (uniform instanceof GraphNode) { - ty = uniform.outputs.get(GraphNode.defaultIoName)![1]; + ty = uniform.outputs.get(GraphNode.defaultIoName)!.type; } else { - ty = uniform.node.outputs.get(uniform.output)![1]; + ty = uniform.node.outputs.get(uniform.output)!.type; } // TODO: Create a way to mark types as non-automatic uniforms diff --git a/src/Graph/Nodes/SubGraphNode.ts b/src/Graph/Nodes/SubGraphNode.ts index 4e03142f36..89e3d719a6 100644 --- a/src/Graph/Nodes/SubGraphNode.ts +++ b/src/Graph/Nodes/SubGraphNode.ts @@ -31,7 +31,7 @@ export default class SubGraphNode extends GraphNode { return [name, dep]; } }) - .map(([name, dep]): [string, [Dependency, Type]] => [name, [dep, dep.node.outputs.get(dep.output)![1]]]); + .map(([name, dep]): [string, [Dependency, Type]] => [name, [dep, dep.node.outputs.get(dep.output)!.type]]); const missingOutputDeps = outputs .filter(([_name, [dep, _ty]]) => graph.findGraphNode(dep.node) == undefined) diff --git a/src/Graph/Nodes/ViewNode.ts b/src/Graph/Nodes/ViewNode.ts index ebc7dabbbe..15eea56da5 100644 --- a/src/Graph/Nodes/ViewNode.ts +++ b/src/Graph/Nodes/ViewNode.ts @@ -9,11 +9,11 @@ export default abstract class ViewNode extends LazyStaticNode { ) { super( { viewerDiv: [viewerDiv, BuiltinType.HtmlDivElement], ...extraDependencies }, - new Map([ - ['view', BuiltinType.View], - ['renderer', BuiltinType.Renderer], - ['camera', BuiltinType.Camera], - ]), + new Map(Object.entries({ + view: BuiltinType.View, + renderer: BuiltinType.Renderer, + camera: BuiltinType.Camera, + })), generator, ); } diff --git a/src/Graph/Prelude.ts b/src/Graph/Prelude.ts index 8dcfe22474..6e638f9ab4 100644 --- a/src/Graph/Prelude.ts +++ b/src/Graph/Prelude.ts @@ -24,6 +24,7 @@ import { BuiltinType, Type, Dependency, + Dependant, ColorStyle, DumpDotNodeStyle, DumpDotGlobalStyle, @@ -70,6 +71,7 @@ export { export type { Type, Dependency, + Dependant, ColorStyle, DumpDotNodeStyle, DumpDotGlobalStyle, diff --git a/src/Graph/Types.ts b/src/Graph/Types.ts index 526e8a822e..490036b994 100644 --- a/src/Graph/Types.ts +++ b/src/Graph/Types.ts @@ -9,6 +9,10 @@ export type Dependency = { node: GraphNode, output: string }; +export type Dependant = { + node: GraphNode, + input: string, +}; // TODO: Refactor type enum variants into separate types and discriminated union types // can still have a `Custom`-like variant From 78253e60099a70affdb663ee8cf859066777d314 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Duhen?= Date: Wed, 26 Jun 2024 13:52:04 +0200 Subject: [PATCH 37/44] feat: add auto-managed forward links --- examples/effects_postprocessing.html | 93 +++++++------- src/Graph/Graph.ts | 82 +++++++++++++ src/Graph/LinearSet.ts | 43 +++++++ src/Graph/Nodes/GraphInputNode.ts | 9 +- src/Graph/Nodes/GraphNode.ts | 121 ++++++++++++++++--- src/Graph/Nodes/JunctionNode.ts | 3 +- src/Graph/Nodes/ScreenShaderNode.ts | 5 +- src/Graph/Nodes/SubGraphNode.ts | 31 ++--- src/Graph/Optimizations/ScreenShaderMerge.ts | 34 ++++-- src/Graph/SubGraph.ts | 19 ++- 10 files changed, 343 insertions(+), 97 deletions(-) create mode 100644 src/Graph/LinearSet.ts diff --git a/examples/effects_postprocessing.html b/examples/effects_postprocessing.html index 891f299aab..beb93f2108 100644 --- a/examples/effects_postprocessing.html +++ b/examples/effects_postprocessing.html @@ -52,24 +52,22 @@ fragmentShaderParts: { auxCode: /* glsl */` uniform vec2 resolution; +`, + main: /* glsl */` +float frequency = 50.; +float strength = 5.; -vec4 warp(vec4 color) { - float frequency = 50.; - float strength = 5.; - - vec2 uv = sqrt(2. * abs(vUv - 0.5)); - if (resolution.x > resolution.y) { - uv.x *= resolution.x / resolution.y; - } - vec2 squv = uv * uv; - vec2 uvOffset = vec2( - sin(floor(frequency * squv.x)), - cos(floor(frequency * squv.y)) - ); - return texture2D(tDiffuse, vUv + uvOffset * vec2(strength, strength) / resolution); +vec2 uv = sqrt(2. * abs(vUv - 0.5)); +if (resolution.x > resolution.y) { + uv.x *= resolution.x / resolution.y; } -`, - main: /* glsl */'return warp(tex);' +vec2 squv = uv * uv; +vec2 uvOffset = vec2( + sin(floor(frequency * squv.x)), + cos(floor(frequency * squv.y)) +); +return texture2D(tDiffuse, vUv + uvOffset * vec2(strength, strength) / resolution); +` }, }); @@ -82,46 +80,37 @@ float getAvg(vec4 color) { return (color.r + color.g + color.b) / 3.0; } +`, + main: /* glsl */` +float mid = getAvg(color); +float left = getAvg(texture2D(tDiffuse, vUv - uOffset / resolution)); -vec4 coastHighlight(vec4 color) { - float mid = getAvg(color); - float left = getAvg(texture2D(tDiffuse, vUv - uOffset / resolution)); - - float diff = uWeight * (mid - left); - float avg = (mid + left) / 2.0; +float diff = uWeight * (mid - left); +float avg = (mid + left) / 2.0; - float smoothed = max(0., diff - avg); - float blueness = color.b / (color.r + color.g + color.b); +float smoothed = max(0., diff - avg); +float blueness = color.b / (color.r + color.g + color.b); - return color + blueness * vec4(smoothed, smoothed, smoothed, 1.0); -} -`, - main: /* glsl */'return coastHighlight(tex);' +return color + blueness * vec4(smoothed, smoothed, smoothed, 1.0); +` }, }); const greyscale = new graph.ScreenShaderNode(coastHighlight, renderer, { fragmentShaderParts: { - auxCode: /* glsl */` -vec4 greyscale(vec4 color) { - float avg = (color.r + color.g + color.b) / 3.; - return vec4(avg, avg, avg, 1.0); -} -`, - main: /* glsl */'return greyscale(tex);' + main: /* glsl */` +float avg = (color.r + color.g + color.b) / 3.; +return vec4(avg, avg, avg, 1.0);` }, }); const uvcolor = new graph.ScreenShaderNode(greyscale, renderer, { fragmentShaderParts: { - auxCode: /* glsl */` -vec4 uvcolor(vec4 color) { - float r = min(color.r * vUv.x, 1.); - float g = min(color.g * vUv.y, 1.); - return vec4(r, g, color.b, 1.0); -} -`, - main: /* glsl */'return uvcolor(tex);', + main: /* glsl */` +float r = min(color.r * vUv.x, 1.); +float g = min(color.g * vUv.y, 1.); +return vec4(r, g, color.b, 1.0); + `, }, }); @@ -137,17 +126,29 @@ const renderFBO = new graph.ScreenShaderNode({node: postProcessingGraphNode, output: 'uvcolor'}, renderer, {toScreen: true}); outerGraph.set({postProcessingGraphNode, renderFBO}); + // window.open(outerGraph.dumpDotGraphvizLink(), '_blank', 'popup'); // We expect coastHighlight, greyscale and uvcolor to be merged - // warp will not be merged as it violates the no-offset access invariant - postProcessingSubGraph.optimize(uvcolor, true); + // warp will not be merged as the merged node from the previous merges violates the no-offset access invariant + // console.log(postProcessingGraphNode); + postProcessingSubGraph.optimize(uvcolor); + + console.log(viewNode.outputs.get('renderer')); + // console.log(postProcessingGraphNode); + + const graphNodeIds = graph => Array.from(graph.nodes.entries()).map(([name, n]) => `${name}: ${n.id}`).join('\n'); + + // console.log(graphNodeIds(outerGraph)); + // console.log(graphNodeIds(postProcessingSubGraph)); + + console.log(outerGraph.dumpAdjacencyMatrix()); let frame = 0; view.render = function render() { outerGraph.getOutput(frame++, renderFBO); // Reach cruising speed before dumping so timing information is accurate if (frame == 10) { - // window.open(outerGraph.dumpDotGraphvizLink(), '_blank', 'popup'); + window.open(outerGraph.dumpDotGraphvizLink(), '_blank', 'popup'); } }; diff --git a/src/Graph/Graph.ts b/src/Graph/Graph.ts index 0cf2b57eab..692e2f89fc 100644 --- a/src/Graph/Graph.ts +++ b/src/Graph/Graph.ts @@ -10,6 +10,7 @@ import { Dependency, GraphOptimizer, GraphInputNode, + SubGraph, } from './Prelude'; /** Represents a directed graph that guarantees the absence of cycles on use. */ @@ -189,6 +190,33 @@ export default class Graph { path.delete(nodeName); } + /** + * Remove a node from the graph. + * @throws If the node does not exist. + * @returns true if the node was removed, false otherwise. + */ + public remove(name: string): void { + console.log(`Removing node '${name}' from ${this instanceof SubGraph ? `${this.name} graph` : 'graph'}`); + + const node = this.nodes.get(name); + if (node == undefined) { + throw new Error(`Node "${name}" does not exist in the graph`); + } + + this._valid = false; + + Array.from(node.inputs).filter(([_input, [dep, _ty]]) => dep != null).forEach(([input, [dependency, _ty]]) => { + const { node, output } = dependency!; + const out = node.outputs.get(output); + if (out == undefined) { + throw new Error(`Dependency ${node.nodeType} (id: ${node.id}) does not have an output named '${output}'`); + } + out.dependants.delete({ node, input }); + }); + + this.nodes.delete(name); + } + public findDependants(node: GraphNode): GraphNode[] { const dependants: GraphNode[] = []; for (const [_name, n] of this.nodes) { @@ -360,4 +388,58 @@ export default class Graph { .replaceAll('=', '%3D'); return `https://dreampuf.github.io/GraphvizOnline/#${escaped}`; } + + public dumpAdjacencyMatrix(dependants: boolean = false): string { + const nodeCount = GraphNode.totalNodesCreated; + + type Cell = { dependencies: number, dependants: number }; + + const matrix: Cell[][] = Array.from({ length: nodeCount }, () => Array.from({ length: nodeCount }, () => ({ dependencies: 0, dependants: 0 }))); + for (const node of this.nodes.values()) { + const nodeIndex = node.id; + + // Dependencies + for (const [dep, _ty] of node.inputs.values()) { + if (dep != null) { + const depIndex = dep.node.id; + matrix[nodeIndex][depIndex].dependencies += 1; + } + } + + // Dependants + for (const output of node.outputs.values()) { + for (const dep of output.dependants) { + const depIndex = dep.node.id; + matrix[nodeIndex][depIndex].dependants += 1; + } + } + } + + const dump: string[] = []; + const padding = nodeCount > 0 ? Math.floor(Math.log10(nodeCount)) + 1 : 1; + const cellPadding = nodeCount > 0 ? Math.floor(Math.log10(Math.max(nodeCount, ...matrix.map((row): number => + Math.max(...row.map((v): number => + Math.max(v.dependencies, v.dependants))))))) + 1 : 1; + + // Header + dump.push(`${' '.repeat(padding + 2)}${Array.from({ length: nodeCount }, (_, i) => i.toString().padStart(cellPadding)).join(' ')}`); + dump.push(`${' '.repeat(padding + 1)}┌${'─'.repeat((cellPadding + 1) * nodeCount - 1)}`); + + // Rows + for (const [index, row] of matrix.entries()) { + const provider = (v: Cell): number => (dependants ? v.dependants : v.dependencies); + const strRow = row.map(provider).map(v => (v > 0 ? v.toString() : ' ').padStart(padding)).join(' '); + + dump.push(`${index.toString().padStart(padding)} │${strRow}`); + } + + for (const [name, node] of this.nodes.entries()) { + dump.push(`${node.id.toString().padStart(padding)}: ${name}`); + if (node instanceof SubGraphNode) { + dump.push(node.graph.dumpAdjacencyMatrix()); + } + } + + return dump.join('\n'); + } } diff --git a/src/Graph/LinearSet.ts b/src/Graph/LinearSet.ts new file mode 100644 index 0000000000..eb8c5cd2ac --- /dev/null +++ b/src/Graph/LinearSet.ts @@ -0,0 +1,43 @@ +export default class LinearSet { + private content: T[]; + private comparison: (a: T, b: T) => boolean; + + public constructor(init?: Iterable, comparison?: (a: T, b: T) => boolean) { + this.content = init != undefined ? Array.from(init) : []; + this.comparison = comparison ?? ((a, b) => a == b); + } + + public get size(): number { + return this.content.length; + } + + public add(value: T): void { + if (this.content.find(v => this.comparison(v, value)) == undefined) { + this.content.push(value); + } + } + + public has(value: T): boolean { + return this.get(value) != undefined; + } + + public get(value: T): T | undefined { + return this.content.find(v => this.comparison(v, value)); + } + + public delete(value: T): void { + this.content = this.content.filter(v => !this.comparison(v, value)); + } + + public clear(): void { + this.content = []; + } + + public [Symbol.iterator](): IterableIterator { + return this.values(); + } + + public values(): IterableIterator { + return this.content[Symbol.iterator](); + } +} diff --git a/src/Graph/Nodes/GraphInputNode.ts b/src/Graph/Nodes/GraphInputNode.ts index a6b61ebeb7..7c8bd7fe5f 100644 --- a/src/Graph/Nodes/GraphInputNode.ts +++ b/src/Graph/Nodes/GraphInputNode.ts @@ -3,10 +3,9 @@ import { Dependency, GraphNode, JunctionNode, Type } from '../Prelude'; export default class GraphInputNode extends JunctionNode { private inputNodeName: string | undefined; - public constructor(input: { [name: string]: [GraphNode, string] } | Type) { + public constructor([name, input]: [string, Dependency | Type]) { if (typeof input != 'string') { - const [name, [node, output]] = Object.entries(input)[0]!; - super({ node, output }); + super(input); this.inputNodeName = name; } else { super(input); @@ -18,8 +17,8 @@ export default class GraphInputNode extends JunctionNode { } public set graphInput([name, node]: [string, Dependency]) { - const [_oldValue, type] = this.inputs.get(GraphNode.defaultIoName)!; - this.inputs.set(GraphNode.defaultIoName, [node, type]); + const input = this.inputs.get(GraphNode.defaultIoName)!; + input[0] = node; this.inputNodeName = name; } } diff --git a/src/Graph/Nodes/GraphNode.ts b/src/Graph/Nodes/GraphNode.ts index 2b7c874168..69e5e6eab3 100644 --- a/src/Graph/Nodes/GraphNode.ts +++ b/src/Graph/Nodes/GraphNode.ts @@ -1,13 +1,20 @@ import { Type, Dependency, Dependant, DumpDotNodeStyle, Graph, LazyStaticNode } from '../Prelude'; +import LinearSet from '../LinearSet'; function lerp(a: number, b: number, t: number): number { return a + (b - a) * t; } -export interface Output { - value: unknown, - type: Type, - dependants: Set, +export class Output { + public value: unknown; + public type: Type; + public dependants: LinearSet; + + constructor(value: unknown, type: Type) { + this.value = value; + this.type = type; + this.dependants = new LinearSet([], (a, b) => a.node == b.node && a.input == b.input); + } } /** @@ -34,32 +41,43 @@ export default abstract class GraphNode { // Optional to allow for clean side-effect-only nodes outputs?: Map | Type, ) { - this.inputs = new Map(Array.from(inputs.entries()) - .map(([name, [dep, ty]]) => [ + this._id = GraphNode.idCounter++; + + this.inputs = new Map(); + const inputDependencies = Object.fromEntries(Array.from(inputs.entries()) + .filter(([_name, [dep, _ty]]) => dep != null) + .map(([name, [dep, ty]]): [string, Dependency | Type] => [ name, - [ - dep instanceof GraphNode - ? { node: dep, output: GraphNode.defaultIoName } - : dep, - ty, - ], + dep instanceof GraphNode + ? { node: dep, output: GraphNode.defaultIoName } + : dep ?? ty, ])); + this.updateInputs(inputDependencies, true); + + for (const input of this.inputs) { + const [name, [dep, ty]] = input; + if (dep != null && dep instanceof GraphNode) { + const has = dep.outputs.get(GraphNode.defaultIoName)!.dependants.has({ node: this, input: name }); + if (!has) { + throw new Error(`Dependency ${dep.nodeType} (id: ${dep.id}) does not have GraphNode (id: ${this.id}) as a dependant`); + } + } + } const normalizedOutputs: Map = new Map((() => { if (outputs == undefined) { return []; } else if (outputs instanceof Map) { return Array.from(outputs.entries()) - .map(([name, ty]) => [name, { value: undefined, type: ty, dependants: new Set() }]); + .map(([name, ty]) => [name, new Output(undefined, ty)]); } else if (typeof outputs == 'string') { - return [[GraphNode.defaultIoName, { value: undefined, type: outputs, dependants: new Set() }]]; + return [[GraphNode.defaultIoName, new Output(undefined, outputs)]]; } else { throw new Error('Unrecognized type when constructing node outputs'); } })()); this._out = { frame: -1, outputs: normalizedOutputs }; - this._id = GraphNode.idCounter++; } protected abstract _apply(graph?: Graph, frame?: number): void; @@ -68,6 +86,77 @@ export default abstract class GraphNode { public get id(): number { return this._id; } + public static get totalNodesCreated(): number { + return GraphNode.idCounter; + } + + private addAsDependantTo(target: Dependency, input: string): void { + const { node, output: outputName } = target; + const output = node.outputs.get(outputName); + if (output == undefined) { + throw new Error(`Provided dependency does not exist: ${node.nodeType} (id: ${node.id}) node does not have an output named '${outputName}'`); + } + console.log(`${node.nodeType}(${node.id}):${outputName} -> ${this.nodeType}(${this.id}):${input}`); + output.dependants.add({ node: this, input }); + } + + public deleteInput(name: string): void { + const input = this.inputs.get(name); + if (input == undefined) { + throw new Error(`Provided ${this.nodeType} (id: ${this.id}) node does not have an input named '${name}' to delete`); + } + + const dep = input[0]; + if (dep != null) { + dep.node.outputs.get(dep.output)!.dependants.delete({ node: this, input: name }); + } + + this.inputs.delete(name); + } + + private updateInput(name: string, dep: Dependency | Type): void { + const input = this.inputs.get(name); + if (input == undefined) { + throw new Error(`Provided ${this.nodeType} (id: ${this.id}) node does not have an input named '${name}' to update`); + } + + if (typeof dep != 'string') { + // Removing a link + if (input[0] != null) { + const oDep = input[0]; + oDep.node.outputs.get(oDep.output)!.dependants.delete({ node: this, input: name }); + } else { // Adding a link + this.addAsDependantTo(dep, name); + } + input[0] = dep; + } else { + input[0] = null; + input[1] = dep; + } + } + + private addInput(name: string, dep: Dependency | Type): void { + if (this.inputs.has(name)) { + throw new Error(`Provided ${this.nodeType} node already has an input named '${name}'`); + } + + if (typeof dep != 'string') { + this.addAsDependantTo(dep!, name); + this.inputs.set(name, [dep, dep.node.outputs.get(dep.output)!.type]); + } else { + this.inputs.set(name, [null, dep]); + } + } + + public updateInputs(updates: { [name: string]: Dependency | Type }, adding: boolean = false): void { + for (const [name, dep] of Object.entries(updates)) { + if (adding) { + this.addInput(name, dep); + } else { + this.updateInput(name, dep); + } + } + } public get outputs(): Map { return this._out.outputs; @@ -77,7 +166,7 @@ export default abstract class GraphNode { return { node: this, output: output ?? GraphNode.defaultIoName }; } - protected updateOutputs(updates: { [name: string]: unknown }): void { + public updateOutputs(updates: { [name: string]: unknown }): void { const errors = []; for (const [name, value] of Object.entries(updates)) { diff --git a/src/Graph/Nodes/JunctionNode.ts b/src/Graph/Nodes/JunctionNode.ts index 3177b88313..11f2048424 100644 --- a/src/Graph/Nodes/JunctionNode.ts +++ b/src/Graph/Nodes/JunctionNode.ts @@ -34,8 +34,7 @@ export default class JunctionNode extends GraphNode { } public set input(node: Dependency) { - const [_oldValue, type] = this.inputs.get(GraphNode.defaultIoName)!; - this.inputs.set(GraphNode.defaultIoName, [node, type]); + this.updateInputs({ [GraphNode.defaultIoName]: node }); } public override get nodeType(): string { diff --git a/src/Graph/Nodes/ScreenShaderNode.ts b/src/Graph/Nodes/ScreenShaderNode.ts index a7e02739bd..c558978b68 100644 --- a/src/Graph/Nodes/ScreenShaderNode.ts +++ b/src/Graph/Nodes/ScreenShaderNode.ts @@ -42,7 +42,7 @@ void main() { protected static get defaultFragmentShader(): FragmentShaderParts { return { - main: /* glsl */'return tex;', + main: /* glsl */'return color;', }; } @@ -142,6 +142,7 @@ void main() { this._fragmentShaderParts = fragmentShaderParts; const frag = ScreenShaderNode.buildFragmentShader(this._fragmentShaderParts); + console.log(frag); this.material = ScreenShaderNode.buildMaterial(frag); } @@ -188,7 +189,7 @@ void main() { ...(uniformDeclarations.length > 0 ? [uniformDeclarations.join('\n')] : []), // User code ...(auxCode != undefined ? [auxCode] : []), - 'vec4 shader(in vec4 tex) {', + 'vec4 shader(in vec4 color) {', ...main.split('\n').map(s => ` ${s}`), '}', '', diff --git a/src/Graph/Nodes/SubGraphNode.ts b/src/Graph/Nodes/SubGraphNode.ts index 89e3d719a6..a57225b21b 100644 --- a/src/Graph/Nodes/SubGraphNode.ts +++ b/src/Graph/Nodes/SubGraphNode.ts @@ -54,21 +54,24 @@ export default class SubGraphNode extends GraphNode { for (const [_nodeName, node] of this.graph.nodes) { // Replace dependencies outside the graph with graph inputs for (const [depName, [dep, depType]] of node.inputs) { - if (dep != undefined && Array.from(this.graph.nodes.values()).find(oNode => oNode == dep.node) == undefined) { - // Try to find an already created graph input for this dependency - const inputs = Array.from(this.graph.inputs); - const findInput = inputs.find(([name, _input]) => name == depName); - if (findInput != undefined) { - const [_name, input] = findInput; - node.inputs.set(depName, [{ node: input, output: GraphNode.defaultIoName }, depType]); - continue; - } + if (dep != undefined) { + dep.node.outputs.get(dep.output)!.dependants.delete({ node, input: depName }); + if (Array.from(this.graph.nodes.values()).find(oNode => oNode == dep.node) == undefined) { + // Try to find an already created graph input for this dependency + const inputs = Array.from(this.graph.inputs); + const findInput = inputs.find(([name, _input]) => name == depName); + if (findInput != undefined) { + const [_name, input] = findInput; + node.inputs.set(depName, [{ node: input, output: GraphNode.defaultIoName }, depType]); + continue; + } - // NOTE: only works for one level of nesting, we might need a resolve function but - // I'm not sure the case where it'd be needed will ever occur. - const newInput = new GraphInputNode(Object.fromEntries([[outerGraph.findGraphNode(dep.node)!.name, [dep.node, dep.output]]])); - const addedInput = this.graph.inputs.set(depName, newInput).get(depName)!; - node.inputs.set(depName, [{ node: addedInput, output: GraphNode.defaultIoName }, depType]); + // NOTE: only works for one level of nesting, we might need a resolve function but + // I'm not sure the case where it'd be needed will ever occur. + const newInput = new GraphInputNode([outerGraph.findGraphNode(dep.node)!.name, dep]); + const addedInput = this.graph.inputs.set(depName, newInput).get(depName)!; + node.inputs.set(depName, [{ node: addedInput, output: GraphNode.defaultIoName }, depType]); + } } } } diff --git a/src/Graph/Optimizations/ScreenShaderMerge.ts b/src/Graph/Optimizations/ScreenShaderMerge.ts index 6e84a1c475..c44e043fcb 100644 --- a/src/Graph/Optimizations/ScreenShaderMerge.ts +++ b/src/Graph/Optimizations/ScreenShaderMerge.ts @@ -100,32 +100,44 @@ export default { cParts.auxCode = [ `// ${pName}`, pParts.auxCode, - `vec4 _${parent.id}_shader(vec4 tex) {`, + `vec4 _${parent.id}_shader(vec4 color) {`, `\t${pParts.main.replace('\n', '\n\t')}`, '}', `// ${cName}`, cParts.auxCode, - `vec4 ${childMergedId}_shader(vec4 tex) {`, + `vec4 ${childMergedId}_shader(vec4 color) {`, `\t${cParts.main.replace('\n', '\n\t')}`, '}', ].join('\n'); cParts.main = [ - `return ${childMergedId}_shader(_${parent.id}_shader(tex));`, + `return ${childMergedId}_shader(_${parent.id}_shader(color));`, ].join('\n'); const fragmentShader = ScreenShaderNode.buildFragmentShader(cParts); - - console.log(fragmentShader); + console.log('Merge\n', fragmentShader); child.material = ScreenShaderNode.buildMaterial(fragmentShader); - child.inputs.delete('renderer'); - child.inputs.delete('target'); - for (const [inputName, [dep, ty]] of parent.inputs.entries()) { - child.inputs.set(inputName, [dep, ty]); + child.deleteInput('renderer'); + child.deleteInput('target'); + const newInputs = Object.fromEntries(Array.from(parent.inputs.entries()).map(([name, [dep, ty]]) => [name, dep ?? ty])); + child.updateInputs(newInputs, true); + + // Delete dependant entries for parent's inputs + for (const [name, [dep, _ty]] of parent.inputs.entries()) { + if (dep == null) { continue; } + + const output = dep.node.outputs.get(dep.output); + if (output == undefined) { + throw new Error(`Output ${dep.output} of ${dep.node.nodeType}(${dep.node.id}) does not exist`); + } + + output.dependants.delete({ node: parent, input: name }); } - graph.nodes.delete(pName); - graph.nodes.delete(cName); + graph.remove(pName); + graph.remove(cName); + // graph.nodes.delete(pName); + // graph.nodes.delete(cName); if (mergedIds != undefined) { graph.nodes.set(`_${parent.id}_${pName}${cName}`, child); diff --git a/src/Graph/SubGraph.ts b/src/Graph/SubGraph.ts index 4b7f66a86c..217b986ded 100644 --- a/src/Graph/SubGraph.ts +++ b/src/Graph/SubGraph.ts @@ -112,7 +112,7 @@ export default class SubGraph extends Graph { for (const [name, node] of this.outputs) { const dep = node.input[0]!; const { name: gName, node: gNode } = this.findGraphNode(dep.node)!; - const ty = gNode.outputs.get(dep.output)![1]; + const ty = gNode.outputs.get(dep.output)!.type; const colorStyle = Mappings.colorize(null, ty); const attrs = gNode.dumpDotEdgeAttr(ty, { @@ -126,4 +126,21 @@ export default class SubGraph extends Graph { return dump.join('\n'); } + + override dumpAdjacencyMatrix(): string { + const dump = [super.dumpAdjacencyMatrix()]; + + const nodeCount = GraphNode.totalNodesCreated; + const padding = nodeCount > 0 ? Math.floor(Math.log10(nodeCount)) + 1 : 1; + + for (const [name, node] of this.inputs.entries()) { + dump.push(`${node.id.toString().padStart(padding)}: (in) ${name}`); + } + + for (const [name, node] of this.outputs.entries()) { + dump.push(`${node.id.toString().padStart(padding)}: (out) ${name}`); + } + + return dump.join('\n'); + } } From 935ada32951faf433f9037f6768d89369b7c46c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Duhen?= Date: Wed, 26 Jun 2024 15:26:46 +0200 Subject: [PATCH 38/44] fix: remove console debug logging --- examples/effects_postprocessing.html | 12 +----------- src/Graph/Graph.ts | 3 --- src/Graph/Nodes/GraphNode.ts | 5 ++--- src/Graph/Nodes/ScreenShaderNode.ts | 1 - src/Graph/Optimizations/ScreenShaderMerge.ts | 1 - 5 files changed, 3 insertions(+), 19 deletions(-) diff --git a/examples/effects_postprocessing.html b/examples/effects_postprocessing.html index beb93f2108..678ce59d4f 100644 --- a/examples/effects_postprocessing.html +++ b/examples/effects_postprocessing.html @@ -39,7 +39,7 @@ const render = new graph.RenderViewNode({node: viewNode, output: 'view'}); outerGraph.set({render}); - // Subgraph + // Post-processing subgraph const postProcessingGraph = new graph.Graph(); // Coast highlight uniforms @@ -126,23 +126,13 @@ const renderFBO = new graph.ScreenShaderNode({node: postProcessingGraphNode, output: 'uvcolor'}, renderer, {toScreen: true}); outerGraph.set({postProcessingGraphNode, renderFBO}); - // window.open(outerGraph.dumpDotGraphvizLink(), '_blank', 'popup'); // We expect coastHighlight, greyscale and uvcolor to be merged // warp will not be merged as the merged node from the previous merges violates the no-offset access invariant - // console.log(postProcessingGraphNode); postProcessingSubGraph.optimize(uvcolor); - console.log(viewNode.outputs.get('renderer')); - // console.log(postProcessingGraphNode); - const graphNodeIds = graph => Array.from(graph.nodes.entries()).map(([name, n]) => `${name}: ${n.id}`).join('\n'); - // console.log(graphNodeIds(outerGraph)); - // console.log(graphNodeIds(postProcessingSubGraph)); - - console.log(outerGraph.dumpAdjacencyMatrix()); - let frame = 0; view.render = function render() { outerGraph.getOutput(frame++, renderFBO); diff --git a/src/Graph/Graph.ts b/src/Graph/Graph.ts index 692e2f89fc..eea93aa2b4 100644 --- a/src/Graph/Graph.ts +++ b/src/Graph/Graph.ts @@ -10,7 +10,6 @@ import { Dependency, GraphOptimizer, GraphInputNode, - SubGraph, } from './Prelude'; /** Represents a directed graph that guarantees the absence of cycles on use. */ @@ -196,8 +195,6 @@ export default class Graph { * @returns true if the node was removed, false otherwise. */ public remove(name: string): void { - console.log(`Removing node '${name}' from ${this instanceof SubGraph ? `${this.name} graph` : 'graph'}`); - const node = this.nodes.get(name); if (node == undefined) { throw new Error(`Node "${name}" does not exist in the graph`); diff --git a/src/Graph/Nodes/GraphNode.ts b/src/Graph/Nodes/GraphNode.ts index 69e5e6eab3..92bba9f9f9 100644 --- a/src/Graph/Nodes/GraphNode.ts +++ b/src/Graph/Nodes/GraphNode.ts @@ -55,7 +55,7 @@ export default abstract class GraphNode { this.updateInputs(inputDependencies, true); for (const input of this.inputs) { - const [name, [dep, ty]] = input; + const [name, [dep, _ty]] = input; if (dep != null && dep instanceof GraphNode) { const has = dep.outputs.get(GraphNode.defaultIoName)!.dependants.has({ node: this, input: name }); if (!has) { @@ -96,7 +96,6 @@ export default abstract class GraphNode { if (output == undefined) { throw new Error(`Provided dependency does not exist: ${node.nodeType} (id: ${node.id}) node does not have an output named '${outputName}'`); } - console.log(`${node.nodeType}(${node.id}):${outputName} -> ${this.nodeType}(${this.id}):${input}`); output.dependants.add({ node: this, input }); } @@ -250,7 +249,7 @@ export default abstract class GraphNode { ? [] : [ '
', - `\t${labelName}`, + `\t[${this.id}] ${labelName}`, '', ]; diff --git a/src/Graph/Nodes/ScreenShaderNode.ts b/src/Graph/Nodes/ScreenShaderNode.ts index c558978b68..b18acb7505 100644 --- a/src/Graph/Nodes/ScreenShaderNode.ts +++ b/src/Graph/Nodes/ScreenShaderNode.ts @@ -142,7 +142,6 @@ void main() { this._fragmentShaderParts = fragmentShaderParts; const frag = ScreenShaderNode.buildFragmentShader(this._fragmentShaderParts); - console.log(frag); this.material = ScreenShaderNode.buildMaterial(frag); } diff --git a/src/Graph/Optimizations/ScreenShaderMerge.ts b/src/Graph/Optimizations/ScreenShaderMerge.ts index c44e043fcb..2f0d87200f 100644 --- a/src/Graph/Optimizations/ScreenShaderMerge.ts +++ b/src/Graph/Optimizations/ScreenShaderMerge.ts @@ -113,7 +113,6 @@ export default { ].join('\n'); const fragmentShader = ScreenShaderNode.buildFragmentShader(cParts); - console.log('Merge\n', fragmentShader); child.material = ScreenShaderNode.buildMaterial(fragmentShader); From 4b756459c9805cacbf76e3c0f2d3354a543c11af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Duhen?= Date: Wed, 26 Jun 2024 16:50:45 +0200 Subject: [PATCH 39/44] feat: replace lazystaticnode with property Add needsUpdate forward propagation --- examples/effects_postprocessing.html | 1 + src/Graph/Nodes/GraphNode.ts | 39 +++++++++++++++++++++++----- src/Graph/Nodes/InputNode.ts | 6 ++--- src/Graph/Nodes/JunctionNode.ts | 4 +-- src/Graph/Nodes/LazyStaticNode.ts | 27 ------------------- src/Graph/Nodes/ProcessorNode.ts | 3 ++- src/Graph/Nodes/SubGraphNode.ts | 2 +- src/Graph/Nodes/ViewNode.ts | 6 ++--- src/Graph/Prelude.ts | 2 -- 9 files changed, 44 insertions(+), 46 deletions(-) delete mode 100644 src/Graph/Nodes/LazyStaticNode.ts diff --git a/examples/effects_postprocessing.html b/examples/effects_postprocessing.html index 678ce59d4f..af08632c4d 100644 --- a/examples/effects_postprocessing.html +++ b/examples/effects_postprocessing.html @@ -27,6 +27,7 @@ coord: new itowns.Coordinates('EPSG:4326', 2.351323, 48.856712), range: 25_000_000, }, graph.BuiltinType.Placement); + const viewerDiv = new graph.InputNode(document.getElementById('viewerDiv')); const viewNode = new graph.GlobeViewNode(viewerDiv, placement); outerGraph.set({placement, viewerDiv, viewNode}); diff --git a/src/Graph/Nodes/GraphNode.ts b/src/Graph/Nodes/GraphNode.ts index 92bba9f9f9..d88e813b8f 100644 --- a/src/Graph/Nodes/GraphNode.ts +++ b/src/Graph/Nodes/GraphNode.ts @@ -1,4 +1,4 @@ -import { Type, Dependency, Dependant, DumpDotNodeStyle, Graph, LazyStaticNode } from '../Prelude'; +import { Type, Dependency, Dependant, DumpDotNodeStyle, Graph } from '../Prelude'; import LinearSet from '../LinearSet'; function lerp(a: number, b: number, t: number): number { @@ -36,10 +36,14 @@ export default abstract class GraphNode { public inputs: Map; + private _isStatic: boolean; + private _needsUpdate: boolean; + public constructor( inputs: Map, // Optional to allow for clean side-effect-only nodes outputs?: Map | Type, + isStatic: boolean = false, ) { this._id = GraphNode.idCounter++; @@ -78,6 +82,8 @@ export default abstract class GraphNode { })()); this._out = { frame: -1, outputs: normalizedOutputs }; + this._isStatic = isStatic; + this._needsUpdate = this._isStatic; } protected abstract _apply(graph?: Graph, frame?: number): void; @@ -165,7 +171,7 @@ export default abstract class GraphNode { return { node: this, output: output ?? GraphNode.defaultIoName }; } - public updateOutputs(updates: { [name: string]: unknown }): void { + public updateOutputs(updates: { [name: string]: unknown }, frame?: number): void { const errors = []; for (const [name, value] of Object.entries(updates)) { @@ -177,11 +183,25 @@ export default abstract class GraphNode { output.value = value; } + if (frame != undefined) { + this._out.frame = frame; + } + if (errors.length > 0) { throw new Error(errors.join('\n')); } } + public needsUpdate(): void { + this._needsUpdate = true; + + for (const output of this.outputs.values()) { + for (const dep of output.dependants) { + dep.node._needsUpdate = true; + } + } + } + /** * Get the output of the node at a given frame. * @param name The name of the output to get. @@ -190,7 +210,7 @@ export default abstract class GraphNode { * @returns The output of the node at the given frame. */ public getOutput(name: string = GraphNode.defaultIoName, graph?: Graph, frame: number = 0): unknown { - const { frame: oFrane, outputs } = this._out; + const { frame: oFrame, outputs } = this._out; const getOutput = outputs.get(name); if (getOutput == undefined) { @@ -198,12 +218,17 @@ export default abstract class GraphNode { } const oValue = getOutput.value; - if (oValue == undefined || oFrane !== frame) { - // console.log(`${tab}[${debugName}] calling _apply`); + if (!this._isStatic && (oValue == undefined || oFrame !== frame) || this._needsUpdate) { this._apply(graph, frame); this._out.frame = frame; + + if (this._needsUpdate) { + this.needsUpdate(); + } } + this._needsUpdate = false; + return getOutput.value; } @@ -230,7 +255,7 @@ export default abstract class GraphNode { const generateTiming = (): string => { const lerpChannel = (): number => Math.floor(lerp(0, 255, Math.min(1, this._out.timeTaken! / 20))); const mapChannel = (op: (x: number) => number): string => op(lerpChannel()).toString(16).padStart(2, '0'); - const timingColor = this instanceof LazyStaticNode + const timingColor = this._isStatic ? '#000000' : `#${mapChannel(x => x)}${mapChannel(x => 255 - x)}00`; @@ -248,7 +273,7 @@ export default abstract class GraphNode { const formattedName = labelName.length == 0 ? [] : [ - '
', + `
${this._isStatic ? '
' : ''}`, `\t[${this.id}] ${labelName}`, '', ]; diff --git a/src/Graph/Nodes/InputNode.ts b/src/Graph/Nodes/InputNode.ts index 04b8e7f5b9..7fb3a9da52 100644 --- a/src/Graph/Nodes/InputNode.ts +++ b/src/Graph/Nodes/InputNode.ts @@ -11,7 +11,7 @@ export default class InputNode extends GraphNode { throw new Error('Input node type could not be inferred'); } - super(new Map(), ty); + super(new Map(), ty, true); this._type = ty; } @@ -19,8 +19,8 @@ export default class InputNode extends GraphNode { return this._type; } - protected override _apply(_graph?: Graph, _frame?: number): void { - this.updateOutputs({ [GraphNode.defaultIoName]: this.value }); + protected override _apply(_graph?: Graph, frame: number = 0): void { + this.updateOutputs({ [GraphNode.defaultIoName]: this.value }, frame); } public override get nodeType(): string { diff --git a/src/Graph/Nodes/JunctionNode.ts b/src/Graph/Nodes/JunctionNode.ts index 11f2048424..464696b220 100644 --- a/src/Graph/Nodes/JunctionNode.ts +++ b/src/Graph/Nodes/JunctionNode.ts @@ -21,12 +21,12 @@ export default class JunctionNode extends GraphNode { const dep = this.inputs.get(GraphNode.defaultIoName)![0]; if (dep == null) { - this.updateOutputs({ [GraphNode.defaultIoName]: null }); + this.updateOutputs({ [GraphNode.defaultIoName]: null }, frame); return; } const { node, output } = dep; - this.updateOutputs({ [GraphNode.defaultIoName]: node.getOutput(output, graph, frame) ?? null }); + this.updateOutputs({ [GraphNode.defaultIoName]: node.getOutput(output, graph, frame) ?? null }, frame); } public get input(): [Dependency | null, Type] { diff --git a/src/Graph/Nodes/LazyStaticNode.ts b/src/Graph/Nodes/LazyStaticNode.ts deleted file mode 100644 index f6db74997f..0000000000 --- a/src/Graph/Nodes/LazyStaticNode.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { DumpDotNodeStyle, Graph, ProcessorNode } from '../Prelude'; - -/** - * A lazy static node only gets re-evaluated when the frame is less than the frame it was first evaluated at. - */ -export default class LazyStaticNode extends ProcessorNode { - protected override _apply(graph?: Graph, frame: number = 0): void { - if (this._out.frame == -1 || frame < this._out.frame) { - // console.log(`[${this.nodeType}][LazyStatic] _applying`); - // this._out.outputs.set(GraphNode.defaultIoName, [super._apply(graph, frame), oType]); - super._apply(graph, frame); - } - } - - public override get nodeType(): string { - return LazyStaticNode.name; - } - - public override get dumpDotStyle(): DumpDotNodeStyle { - return { - label: name => `${name}`, - attrs: { - color: 'deepskyblue', - }, - }; - } -} diff --git a/src/Graph/Nodes/ProcessorNode.ts b/src/Graph/Nodes/ProcessorNode.ts index b62b388f3e..c8d0d8efba 100644 --- a/src/Graph/Nodes/ProcessorNode.ts +++ b/src/Graph/Nodes/ProcessorNode.ts @@ -7,8 +7,9 @@ export default class ProcessorNode extends GraphNode { inputs: { [name: string]: [Dependency, Type] }, outputs: Map | Type, public callback: (frame: number, args: { [arg: string]: unknown }) => void, + isStatic: boolean = false, ) { - super(new Map(Object.entries(inputs)), outputs); + super(new Map(Object.entries(inputs)), outputs, isStatic); } protected override _apply(graph?: Graph, frame: number = 0): void { diff --git a/src/Graph/Nodes/SubGraphNode.ts b/src/Graph/Nodes/SubGraphNode.ts index a57225b21b..22c7f03a82 100644 --- a/src/Graph/Nodes/SubGraphNode.ts +++ b/src/Graph/Nodes/SubGraphNode.ts @@ -80,7 +80,7 @@ export default class SubGraphNode extends GraphNode { protected override _apply(_graph?: Graph, frame: number = 0): void { const updates = Array.from(this.graphOutputs.entries()) .map(([name, [dep, _ty]]) => [name, dep.node.getOutput(dep.output, this.graph, frame)]); - this.updateOutputs(Object.fromEntries(updates)); + this.updateOutputs(Object.fromEntries(updates), frame); } public get label(): string | undefined { diff --git a/src/Graph/Nodes/ViewNode.ts b/src/Graph/Nodes/ViewNode.ts index 15eea56da5..8e7d3d8e49 100644 --- a/src/Graph/Nodes/ViewNode.ts +++ b/src/Graph/Nodes/ViewNode.ts @@ -1,7 +1,6 @@ -import { BuiltinType, Dependency, Type } from '../Prelude'; -import LazyStaticNode from './LazyStaticNode'; +import { BuiltinType, Dependency, ProcessorNode, Type } from '../Prelude'; -export default abstract class ViewNode extends LazyStaticNode { +export default abstract class ViewNode extends ProcessorNode { public constructor( viewerDiv: Dependency, generator: (frame: number, args: any) => void, @@ -15,6 +14,7 @@ export default abstract class ViewNode extends LazyStaticNode { camera: BuiltinType.Camera, })), generator, + true, ); } } diff --git a/src/Graph/Prelude.ts b/src/Graph/Prelude.ts index 6e638f9ab4..8dd03cd0fb 100644 --- a/src/Graph/Prelude.ts +++ b/src/Graph/Prelude.ts @@ -8,7 +8,6 @@ import ProcessorNode from './Nodes/ProcessorNode'; import ScreenShaderNode from './Nodes/ScreenShaderNode'; import RenderViewNode from './Nodes/RenderViewNode'; import SubGraphNode from './Nodes/SubGraphNode'; -import LazyStaticNode from './Nodes/LazyStaticNode'; import JunctionNode from './Nodes/JunctionNode'; import ViewNode from './Nodes/ViewNode'; import GraphInputNode from './Nodes/GraphInputNode'; @@ -46,7 +45,6 @@ export { GraphInputNode, GraphOutputNode, FieldGetterNode, - LazyStaticNode, // View ViewNode, From 14a2431c121d3d56b91ee19b8e929f5dfcd688f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Duhen?= Date: Wed, 26 Jun 2024 16:55:24 +0200 Subject: [PATCH 40/44] feat: adapt InputNode to the new static mode --- src/Graph/Nodes/InputNode.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/Graph/Nodes/InputNode.ts b/src/Graph/Nodes/InputNode.ts index 7fb3a9da52..f4a63eb6f5 100644 --- a/src/Graph/Nodes/InputNode.ts +++ b/src/Graph/Nodes/InputNode.ts @@ -4,14 +4,16 @@ import { Type, DumpDotNodeStyle, Mappings, Graph } from '../Prelude'; /** Represents a node that outputs a constant value. */ export default class InputNode extends GraphNode { private _type: Type; + private _value: unknown; - public constructor(public value: any, type?: Type) { + public constructor(value: unknown, type?: Type) { const ty = type ?? Mappings.typeOf(value); if (ty == undefined) { throw new Error('Input node type could not be inferred'); } super(new Map(), ty, true); + this._value = value; this._type = ty; } @@ -20,16 +22,21 @@ export default class InputNode extends GraphNode { } protected override _apply(_graph?: Graph, frame: number = 0): void { - this.updateOutputs({ [GraphNode.defaultIoName]: this.value }, frame); + this.updateOutputs({ [GraphNode.defaultIoName]: this._value }, frame); } public override get nodeType(): string { return InputNode.name; } + public set value(value: unknown) { + this._value = value; + this.needsUpdate(); + } + public override get dumpDotStyle(): DumpDotNodeStyle { return { - label: name => `${name}: ${Mappings.stringify(this.value)}`, + label: name => `${name}: ${Mappings.stringify(this._value)}`, attrs: { color: 'goldenrod', }, From 8fe966c0bd3f748c5a9a2c9a23c1bbd92251fdfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Duhen?= Date: Tue, 2 Jul 2024 15:04:28 +0200 Subject: [PATCH 41/44] feat: add graph-wide state, minor renaming --- .gitignore | 1 + src/Graph/Graph.ts | 15 +++++++++++---- src/Graph/{GraphOptimizer.ts => Optimizer.ts} | 10 +++++----- src/Graph/Prelude.ts | 4 ++-- src/Graph/SubGraph.ts | 9 ++++++--- 5 files changed, 25 insertions(+), 14 deletions(-) rename src/Graph/{GraphOptimizer.ts => Optimizer.ts} (83%) diff --git a/.gitignore b/.gitignore index 5ebc710462..0de7c2761b 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ potree coverage .nyc_output/ /src/ThreeExtended/ +types/ diff --git a/src/Graph/Graph.ts b/src/Graph/Graph.ts index eea93aa2b4..da915ea69e 100644 --- a/src/Graph/Graph.ts +++ b/src/Graph/Graph.ts @@ -8,7 +8,7 @@ import { InputNode, BuiltinType, Dependency, - GraphOptimizer, + Optimizer, GraphInputNode, } from './Prelude'; @@ -16,18 +16,25 @@ import { export default class Graph { public nodes: Map; public types: Set; - private _valid: boolean; + protected _state: unknown; + protected _valid: boolean; - public constructor() { + public constructor(state: unknown) { this.nodes = new Map(); this.types = new Set(); this._valid = false; + this._state = state; } public get isValid(): boolean { return this._valid; } + // Encapsulated to prevent setting the state to a new reference + public get state(): unknown { + return this._state; + } + /** * Get the output of a node at a given frame. * @throws If the graph is invalid. @@ -245,7 +252,7 @@ export default class Graph { } public optimize(start: GraphNode | string, debug: boolean = false): void { - GraphOptimizer.optimize(this, start, debug); + Optimizer.optimize(this, start, debug); } /** Find a node's entry in the graph. O(n) time complexity. */ diff --git a/src/Graph/GraphOptimizer.ts b/src/Graph/Optimizer.ts similarity index 83% rename from src/Graph/GraphOptimizer.ts rename to src/Graph/Optimizer.ts index e3f19ce214..a52e656d57 100644 --- a/src/Graph/GraphOptimizer.ts +++ b/src/Graph/Optimizer.ts @@ -1,6 +1,6 @@ import { Graph, GraphInputNode, GraphNode, GraphOptimization, opti } from './Prelude'; -export default class GraphOptimizer { +export default class Optimizer { public static patterns: Map = new Map(Object.entries(opti)); public static optimize(graph: Graph, start: GraphNode | string, debug: boolean = false): Graph { @@ -18,10 +18,10 @@ export default class GraphOptimizer { if (debug) { // eslint-disable-next-line no-console - console.info(`[${GraphOptimizer.name}] path:`, path.map(n => graph.findNode(n)?.name ?? n.nodeType)); + console.info(`[${Optimizer.name}] path:`, path.map(n => graph.findNode(n)?.name ?? n.nodeType)); } - for (const [name, { pattern, operation }] of GraphOptimizer.patterns) { + for (const [name, { pattern, operation }] of Optimizer.patterns) { // Compare the last n nodes in the path to the pattern const isDifferent = path.length < pattern.length || path.slice(-pattern.length) .map((v, i) => v.nodeType == pattern[i]) @@ -30,7 +30,7 @@ export default class GraphOptimizer { try { if (debug) { // eslint-disable-next-line no-console - console.info(`[${GraphOptimizer.name}] Trying optimization ${name}`); + console.info(`[${Optimizer.name}] Trying optimization ${name}`); } const newStart = operation(path.slice(-pattern.length), graph); path.splice(-pattern.length); @@ -39,7 +39,7 @@ export default class GraphOptimizer { } catch (e) { if (debug) { // eslint-disable-next-line no-console - console.info(`[${GraphOptimizer.name}] ${name}:`, e); + console.info(`[${Optimizer.name}] ${name}:`, e); } } } diff --git a/src/Graph/Prelude.ts b/src/Graph/Prelude.ts index 8dd03cd0fb..343966f325 100644 --- a/src/Graph/Prelude.ts +++ b/src/Graph/Prelude.ts @@ -31,7 +31,7 @@ import { } from './Types'; import opti from './Optimizations/Prelude'; -import GraphOptimizer from './GraphOptimizer'; +import Optimizer from './Optimizer'; export { Graph, @@ -62,7 +62,7 @@ export { Mappings, KernelType, BuiltinType, - GraphOptimizer, + Optimizer, opti, }; diff --git a/src/Graph/SubGraph.ts b/src/Graph/SubGraph.ts index 217b986ded..cf39075418 100644 --- a/src/Graph/SubGraph.ts +++ b/src/Graph/SubGraph.ts @@ -3,13 +3,16 @@ import { Graph, GraphInputNode, GraphNode, GraphOutputNode, JunctionNode, SubGra export default class SubGraph extends Graph { public inputs: Map = new Map(); public outputs: Map = new Map(); + public name: string; - constructor(public name: string) { - super(); + private constructor(name: string, state: unknown) { + super(state); + this.name = name; } public static from(graph: Graph, name: string): SubGraph { - const subGraph = new SubGraph(name); + // State is shared between subgraphs and parent graphs + const subGraph = new SubGraph(name, graph.state); subGraph.nodes = graph.nodes; subGraph.types = graph.types; return subGraph; From fab8b3fc04a1423a22413c279022c22fbac99905 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Duhen?= Date: Wed, 24 Jul 2024 00:24:05 +0200 Subject: [PATCH 42/44] wip(feat): sources --- examples/effects_postprocessing.html | 7 +- src/Graph/Mappings.ts | 1 + .../Nodes/Source/OrientedImageSourceNode.ts | 140 ++++++++++++++++++ src/Graph/Nodes/Source/Prelude.ts | 7 + src/Graph/Nodes/Source/SourceNode.ts | 23 +++ src/Graph/Nodes/Source/TMSSourceNode.ts | 31 ++++ src/Graph/Prelude.ts | 7 + src/Graph/Types.ts | 8 + src/Source/Source.js | 4 +- 9 files changed, 223 insertions(+), 5 deletions(-) create mode 100644 src/Graph/Nodes/Source/OrientedImageSourceNode.ts create mode 100644 src/Graph/Nodes/Source/Prelude.ts create mode 100644 src/Graph/Nodes/Source/SourceNode.ts create mode 100644 src/Graph/Nodes/Source/TMSSourceNode.ts diff --git a/examples/effects_postprocessing.html b/examples/effects_postprocessing.html index af08632c4d..346c760bc5 100644 --- a/examples/effects_postprocessing.html +++ b/examples/effects_postprocessing.html @@ -138,9 +138,9 @@ view.render = function render() { outerGraph.getOutput(frame++, renderFBO); // Reach cruising speed before dumping so timing information is accurate - if (frame == 10) { - window.open(outerGraph.dumpDotGraphvizLink(), '_blank', 'popup'); - } + // if (frame == 10) { + // window.open(outerGraph.dumpDotGraphvizLink(), '_blank', 'popup'); + // } }; itowns.Fetcher.json('./layers/JSONLayers/Ortho.json').then(function _(config) { @@ -150,6 +150,7 @@ }); itowns.Fetcher.json('./layers/JSONLayers/IGN_MNT.json').then(function _(config) { config.source = new itowns.WMTSSource(config.source); + console.dir(config.source); var layer = new itowns.ElevationLayer(config.id, config); view.addLayer(layer); }); diff --git a/src/Graph/Mappings.ts b/src/Graph/Mappings.ts index 56c0e23244..4f4dccd742 100644 --- a/src/Graph/Mappings.ts +++ b/src/Graph/Mappings.ts @@ -52,6 +52,7 @@ export default class Mappings { public static colorMapping = new Map([ [BuiltinType.Number, { color: 'chocolate', fillcolor: 'orange' }], + [BuiltinType.Source, { color: 'cornflowerblue', fillcolor: 'deepskyblue' }], [BuiltinType.View, { color: 'cornflowerblue', fillcolor: 'deepskyblue' }], [BuiltinType.Placement, { color: 'cornflowerblue', fillcolor: 'deepskyblue' }], [BuiltinType.Vector2, { color: 'indigo', fillcolor: 'violet' }], diff --git a/src/Graph/Nodes/Source/OrientedImageSourceNode.ts b/src/Graph/Nodes/Source/OrientedImageSourceNode.ts new file mode 100644 index 0000000000..c91d2fceb4 --- /dev/null +++ b/src/Graph/Nodes/Source/OrientedImageSourceNode.ts @@ -0,0 +1,140 @@ +import { Matrix3Tuple, Vector2Tuple, Vector3Tuple } from 'three'; +import { BuiltinType, Dependency, type Source } from 'Graph/Types'; +import { Extent, Fetcher } from 'Main'; +import Cache from 'Core/Scheduler/Cache'; +import SourceNode from './SourceNode'; + +type OISDescriptor = { + url: string, + crs: string, + orientationsUrl?: string, + calibrationUrl?: string, +} + +type CameraCalibrationData = { + /** Camera ID */ + id: number, + // TODO: Add documentation + mask?: string, + /** Rotation matrix */ + rotation: Matrix3Tuple, + /** Translation vector */ + position: Vector3Tuple, + /** Camera intrinsic matrix */ + projection: Matrix3Tuple, + /** Width and height */ + size: Vector2Tuple, + distortion: { + /** Principal Point of Symmetry */ + pps: Vector2Tuple, + // TODO: Add documentation + poly357: Vector3Tuple, + limit: number, + }, + // TODO: Find spec for calibration data and remove this catch-all +} & { [name: string]: unknown }; + +type OrientationsData = { + type: string, + features: { + type: string, + geometry: { + type: string, + coordinates: Vector3Tuple, + }, + properties: { + id: number, + easting: number, + northing: number, + altitude: number, + heading: number, + roll: number, + pitch: number, + date: Date, + } + }[], + properties: CameraCalibrationData[], + crs: { + type: string, + properties: { + code: number, + }, + }, +}; + +type OISConfig = { calibration: CameraCalibrationData[], orientations: OrientationsData }; + +type OISDataLoadInput = { crs: string }; + +// FIXME: replace unknown with the actual types +export class OrientedImageSource implements Source { + private url: string; + private crs: string; + private whenReady: Promise; + private cache: { [crs: string]: Cache }; + + constructor(descriptor: OISDescriptor) { + this.url = descriptor.url; + this.crs = descriptor.crs; + + const promises = [ + descriptor.calibrationUrl ? Fetcher.json(descriptor.calibrationUrl) : Promise.resolve(), + descriptor.orientationsUrl ? Fetcher.json(descriptor.orientationsUrl) : Promise.resolve(), + ]; + + this.whenReady = Promise.all(promises).then(([calibration, orientations]) => ({ + calibration: calibration as CameraCalibrationData[], + orientations: orientations as OrientationsData, + })); + + this.cache = {}; + } + + public loadData(extent: Extent, input: OISDataLoadInput): Promise { + const cache = this.getCache(input.crs); + + const key = this.requestToKey(extent); + + // TODO: + throw new Error('Method not implemented.'); + } + + private imageUrl(cameraId: string, panoId: string): string { + return this.url.replace('{cameraId}', cameraId).replace('{panoId}', panoId); + } + + // TODO: + private requestToKey(extent: Extent): unknown { + throw new Error('Method not implemented.'); + } + + private getCache(crs: string): Cache { + if (this.cache[crs] === undefined) { + this.cache[crs] = new Cache(); + } + return this.cache[crs]; + } + + public removeCache(crs: string): void { + delete this.cache[crs]; + } +} + +export default class OrientedImageSourceNode extends SourceNode { + constructor( + url: Dependency, + crs: Dependency, + orientationsUrl?: Dependency, + calibrationUrl?: Dependency, + ) { + super( + args => new OrientedImageSource(args as OISDescriptor), + url, + crs, + { + ...(orientationsUrl ? { orientationsUrl: [orientationsUrl, BuiltinType.String] } : {}), + ...(calibrationUrl ? { calibrationUrl: [calibrationUrl, BuiltinType.String] } : {}), + }, + ); + } +} diff --git a/src/Graph/Nodes/Source/Prelude.ts b/src/Graph/Nodes/Source/Prelude.ts new file mode 100644 index 0000000000..125776af13 --- /dev/null +++ b/src/Graph/Nodes/Source/Prelude.ts @@ -0,0 +1,7 @@ +import SourceNode from './SourceNode'; +import TMSSourceNode from './TMSSourceNode'; + +export default { + SourceNode, + TMSSourceNode, +}; diff --git a/src/Graph/Nodes/Source/SourceNode.ts b/src/Graph/Nodes/Source/SourceNode.ts new file mode 100644 index 0000000000..e9b8689f55 --- /dev/null +++ b/src/Graph/Nodes/Source/SourceNode.ts @@ -0,0 +1,23 @@ +import { BuiltinType, Dependency, ProcessorNode, Source, Type } from 'Graph/Prelude'; + +export type ConstructorArgs = { url: string, crs: string } & { [name: string]: unknown }; + +export default abstract class SourceNode extends ProcessorNode { + public constructor( + constructor: (args: ConstructorArgs) => Source, + url: Dependency, + crs: Dependency, + extraDependencies?: { [name: string]: [Dependency, Type] }, + ) { + super( + { + url: [url, BuiltinType.String], + crs: [crs, BuiltinType.CRS], + ...extraDependencies, + }, + BuiltinType.Source, + (_frame, args) => constructor(args as ConstructorArgs), + true, + ); + } +} diff --git a/src/Graph/Nodes/Source/TMSSourceNode.ts b/src/Graph/Nodes/Source/TMSSourceNode.ts new file mode 100644 index 0000000000..8d41ae5bae --- /dev/null +++ b/src/Graph/Nodes/Source/TMSSourceNode.ts @@ -0,0 +1,31 @@ +import Extent from 'Core/Geographic/Extent'; +import { BuiltinType } from 'Graph/Types'; +import ProcessorNode from '../ProcessorNode'; + +export type TMSSourceDescriptor = { + url: string, + crs?: string, + format?: string, + extent?: Extent, + zoom?: { + min: number, + max: number, + }, + tileMatrixSetLimits?: Record, + tileMatrixCallback?: (zoomLevel: number) => string, +}; + +export default class TMSSourceNode extends ProcessorNode { + protected extentSetLimits?: Record>; + + constructor(public descriptor: TMSSourceDescriptor) { + super({}, BuiltinType.Source, (_frame, args) => { + // TODO: Generate a TMSSource + }, true); + } +} diff --git a/src/Graph/Prelude.ts b/src/Graph/Prelude.ts index 343966f325..e595c8d46c 100644 --- a/src/Graph/Prelude.ts +++ b/src/Graph/Prelude.ts @@ -17,6 +17,8 @@ import PlanarViewNode from './Nodes/PlanarViewNode'; import FieldGetterNode from './Nodes/FieldGetterNode'; import DepthGetterNode from './Nodes/DepthGetterNode'; import CameraDataNode from './Nodes/CameraDataNode'; +import SourceNode from './Nodes/Source/SourceNode'; +import sources from './Nodes/Source/Prelude'; import { KernelType, @@ -28,6 +30,7 @@ import { DumpDotNodeStyle, DumpDotGlobalStyle, GraphOptimization, + Source, } from './Types'; import opti from './Optimizations/Prelude'; @@ -51,6 +54,9 @@ export { GlobeViewNode, PlanarViewNode, + // Sources + sources, + // Processors ProcessorNode, ScreenShaderNode, @@ -74,4 +80,5 @@ export type { DumpDotNodeStyle, DumpDotGlobalStyle, GraphOptimization, + Source, }; diff --git a/src/Graph/Types.ts b/src/Graph/Types.ts index 490036b994..03672b5d05 100644 --- a/src/Graph/Types.ts +++ b/src/Graph/Types.ts @@ -1,6 +1,7 @@ // Stored in a separate file to avoid circular dependencies import { OrthographicCamera, PerspectiveCamera } from 'three'; +import { Extent } from 'Main'; import Graph from './Graph'; import GraphNode from './Nodes/GraphNode'; @@ -29,11 +30,14 @@ export enum BuiltinType { // Primitives Number = 'Number', + String = 'String', Float32Array = 'Float32Array', // iTowns types + Source = 'Source', View = 'View', Placement = 'Placement', + CRS = 'CRS', // Three.js /// Types @@ -76,3 +80,7 @@ export type GraphOptimization = { pattern: string[], operation: (nodes: GraphNode[], graph: Graph) => GraphNode }; + +export interface Source { + loadData(extent: Extent, input: Input): Promise; +} diff --git a/src/Source/Source.js b/src/Source/Source.js index feadfa8c5c..af84eca42f 100644 --- a/src/Source/Source.js +++ b/src/Source/Source.js @@ -21,7 +21,7 @@ export const supportedParsers = new Map([ ['application/gdf', GDFParser.parse], ]); -const noCache = { getByArray: () => {}, setByArray: a => a, clear: () => {} }; +const noCache = { getByArray: () => { }, setByArray: a => a, clear: () => { } }; /** * @property {string} crs - data crs projection. @@ -52,7 +52,7 @@ class InformationsData { * @property {FeatureBuildingOptions|Layer} out - options indicates how the features should be built. */ // eslint-disable-next-line -class /* istanbul ignore next */ ParsingOptions {} +class /* istanbul ignore next */ ParsingOptions { } let uid = 0; From 9cc58ce808539d0fd76cb2da6aacaedc47fd29f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Duhen?= Date: Mon, 12 Aug 2024 00:27:28 +0200 Subject: [PATCH 43/44] wip(feat): base design choices for source nodes --- examples/layers/JSONLayers/OPENSM.json | 2 +- src/Graph/Nodes/GraphNode.ts | 23 +++++++++++++-- src/Graph/Nodes/Source/Prelude.ts | 2 +- src/Graph/Nodes/Source/SourceNode.ts | 33 ++++++++++++--------- src/Graph/Nodes/Source/TMSSourceNode.ts | 38 ++++++++++++++++++++----- src/Graph/Types.ts | 4 +-- src/Parser/GeoJsonParser.js | 2 ++ 7 files changed, 78 insertions(+), 26 deletions(-) diff --git a/examples/layers/JSONLayers/OPENSM.json b/examples/layers/JSONLayers/OPENSM.json index d9377913c3..bd05e500b8 100644 --- a/examples/layers/JSONLayers/OPENSM.json +++ b/examples/layers/JSONLayers/OPENSM.json @@ -4,7 +4,7 @@ "crs": "EPSG:3857", "isInverted": true, "format": "image/png", - "url": "http://osm.oslandia.io/styles/klokantech-basic/${z}/${x}/${y}.png", + "url": "https://maps.pole-emploi.fr/styles/klokantech-basic/${z}/${x}/${y}.png", "attribution": { "name":"OpenStreetMap", "url": "http://www.openstreetmap.org/" diff --git a/src/Graph/Nodes/GraphNode.ts b/src/Graph/Nodes/GraphNode.ts index d88e813b8f..0932af6960 100644 --- a/src/Graph/Nodes/GraphNode.ts +++ b/src/Graph/Nodes/GraphNode.ts @@ -163,14 +163,26 @@ export default abstract class GraphNode { } } + /** + * Returns the map of all of the node's outputs. + */ public get outputs(): Map { return this._out.outputs; } + /** + * Returns a dependency object for the desired output. + */ public toDep(output?: string): Dependency { + return { node: this, output: output ?? GraphNode.defaultIoName }; } + /** + * Updates desire node outputs with the new values, updating the frame if it is provided. + * + * @throws If an output does not exist. + */ public updateOutputs(updates: { [name: string]: unknown }, frame?: number): void { const errors = []; @@ -192,18 +204,25 @@ export default abstract class GraphNode { } } + /** + * Marks the node as needing to be updated on the next frame and propagates the update to its dependant nodes + * recursively (depth-first). + */ public needsUpdate(): void { this._needsUpdate = true; for (const output of this.outputs.values()) { for (const dep of output.dependants) { - dep.node._needsUpdate = true; + if (!dep.node._needsUpdate) { + dep.node.needsUpdate(); + } } } } /** - * Get the output of the node at a given frame. + * Get the output of the node for a given frame. + * * @param name The name of the output to get. * @param graph The graph the node is a part of. * @param frame The frame to get the output for. diff --git a/src/Graph/Nodes/Source/Prelude.ts b/src/Graph/Nodes/Source/Prelude.ts index 125776af13..de04498892 100644 --- a/src/Graph/Nodes/Source/Prelude.ts +++ b/src/Graph/Nodes/Source/Prelude.ts @@ -1,5 +1,5 @@ import SourceNode from './SourceNode'; -import TMSSourceNode from './TMSSourceNode'; +import { TMSSourceNode } from './TMSSourceNode'; export default { SourceNode, diff --git a/src/Graph/Nodes/Source/SourceNode.ts b/src/Graph/Nodes/Source/SourceNode.ts index e9b8689f55..03dfacea55 100644 --- a/src/Graph/Nodes/Source/SourceNode.ts +++ b/src/Graph/Nodes/Source/SourceNode.ts @@ -1,23 +1,30 @@ -import { BuiltinType, Dependency, ProcessorNode, Source, Type } from 'Graph/Prelude'; +import { BuiltinType, Dependency, Graph, GraphNode, ProcessorNode, Source, Type } from 'Graph/Prelude'; -export type ConstructorArgs = { url: string, crs: string } & { [name: string]: unknown }; +export type BaseConstructorArgs = { url: string, crs: string }; + +export default abstract class SourceNode extends GraphNode { + private _constructor: (args: ConstructorArgs) => Source; + private _config: ConstructorArgs; -export default abstract class SourceNode extends ProcessorNode { public constructor( - constructor: (args: ConstructorArgs) => Source, - url: Dependency, - crs: Dependency, - extraDependencies?: { [name: string]: [Dependency, Type] }, + constructor: (args: ConstructorArgs) => Source, + constructorArgs: ConstructorArgs, ) { super( - { - url: [url, BuiltinType.String], - crs: [crs, BuiltinType.CRS], - ...extraDependencies, - }, + new Map(), BuiltinType.Source, - (_frame, args) => constructor(args as ConstructorArgs), true, ); + + this._constructor = constructor; + this._config = constructorArgs; + } + + protected _apply(_graph?: Graph, frame: number = 0): void { + this._out.frame = frame; + + const start = Date.now(); + this.updateOutputs({ [SourceNode.defaultIoName]: this._constructor(this._config) }) + this._out.timeTaken = Date.now() - start; } } diff --git a/src/Graph/Nodes/Source/TMSSourceNode.ts b/src/Graph/Nodes/Source/TMSSourceNode.ts index 8d41ae5bae..b50bbb14f8 100644 --- a/src/Graph/Nodes/Source/TMSSourceNode.ts +++ b/src/Graph/Nodes/Source/TMSSourceNode.ts @@ -1,10 +1,10 @@ import Extent from 'Core/Geographic/Extent'; -import { BuiltinType } from 'Graph/Types'; -import ProcessorNode from '../ProcessorNode'; +import { BuiltinType, DumpDotNodeStyle, Source } from 'Graph/Types'; +import SourceNode from './SourceNode'; export type TMSSourceDescriptor = { url: string, - crs?: string, + crs: string, format?: string, extent?: Extent, zoom?: { @@ -20,12 +20,36 @@ export type TMSSourceDescriptor = { tileMatrixCallback?: (zoomLevel: number) => string, }; -export default class TMSSourceNode extends ProcessorNode { +export class TMSSourceNode extends SourceNode { protected extentSetLimits?: Record>; constructor(public descriptor: TMSSourceDescriptor) { - super({}, BuiltinType.Source, (_frame, args) => { - // TODO: Generate a TMSSource - }, true); + super((args) => new TMSSource(args), descriptor) + } + + public get nodeType(): string { + return "TMSSource"; + } + + public get dumpDotStyle(): DumpDotNodeStyle { + return { + label: name => name, + attrs: { + color: 'lightskyblue', + } + } + } +} + +export type TMSSourceInput = { + crs: string, + +} + +export class TMSSource implements Source { + public constructor(config: TMSSourceDescriptor) { } + + public loadData(extent: Extent, input: TMSSourceDescriptor): Promise { + throw new Error('Method not implemented.'); } } diff --git a/src/Graph/Types.ts b/src/Graph/Types.ts index 03672b5d05..5f03746f73 100644 --- a/src/Graph/Types.ts +++ b/src/Graph/Types.ts @@ -81,6 +81,6 @@ export type GraphOptimization = { operation: (nodes: GraphNode[], graph: Graph) => GraphNode }; -export interface Source { - loadData(extent: Extent, input: Input): Promise; +export interface Source { + loadData(extent: Extent, input: Input): Promise; } diff --git a/src/Parser/GeoJsonParser.js b/src/Parser/GeoJsonParser.js index 3c31645c5b..b3453362ce 100644 --- a/src/Parser/GeoJsonParser.js +++ b/src/Parser/GeoJsonParser.js @@ -206,6 +206,8 @@ export default { options = deprecatedParsingOptionsToNewOne(options); options.in = options.in || {}; + console.log(options) + const out = options.out; const _in = options.in; From 378bf4040f2729fc5274adf4de2a265cfa3e9fdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Duhen?= Date: Tue, 1 Oct 2024 13:28:39 +0200 Subject: [PATCH 44/44] feat: improve side-effect-only ergonomics --- examples/effects_postprocessing.html | 8 ++++---- src/Graph/Graph.ts | 18 ++++++++++++++++++ src/Graph/Nodes/GraphNode.ts | 28 +++++++++++++++++++--------- src/Graph/Nodes/ProcessorNode.ts | 2 +- src/Graph/Nodes/ScreenShaderNode.ts | 6 ++++-- 5 files changed, 46 insertions(+), 16 deletions(-) diff --git a/examples/effects_postprocessing.html b/examples/effects_postprocessing.html index 346c760bc5..41827ae382 100644 --- a/examples/effects_postprocessing.html +++ b/examples/effects_postprocessing.html @@ -136,11 +136,11 @@ let frame = 0; view.render = function render() { - outerGraph.getOutput(frame++, renderFBO); + outerGraph.run(frame++, renderFBO); // Reach cruising speed before dumping so timing information is accurate - // if (frame == 10) { - // window.open(outerGraph.dumpDotGraphvizLink(), '_blank', 'popup'); - // } + if (frame == 10) { + window.open(outerGraph.dumpDotGraphvizLink(), '_blank', 'popup'); + } }; itowns.Fetcher.json('./layers/JSONLayers/Ortho.json').then(function _(config) { diff --git a/src/Graph/Graph.ts b/src/Graph/Graph.ts index da915ea69e..aa709de663 100644 --- a/src/Graph/Graph.ts +++ b/src/Graph/Graph.ts @@ -64,6 +64,24 @@ export default class Graph { return out.node.getOutput(out.output, this, frame); } + public run(frame: number, end: GraphNode | string): void { + this.validate(); + + let node: GraphNode; + + if (typeof end == 'string') { + const opt = this.nodes.get(end); + if (opt == undefined) { + throw new Error(`Node "${end}" does not exist in the graph`); + } + node = opt; + } else { + node = end; + } + + node.run(this, frame); + } + /** * Get a node by name. * @returns The node with the given name. diff --git a/src/Graph/Nodes/GraphNode.ts b/src/Graph/Nodes/GraphNode.ts index 0932af6960..7b6d0f08f6 100644 --- a/src/Graph/Nodes/GraphNode.ts +++ b/src/Graph/Nodes/GraphNode.ts @@ -42,7 +42,7 @@ export default abstract class GraphNode { public constructor( inputs: Map, // Optional to allow for clean side-effect-only nodes - outputs?: Map | Type, + outputs: Map | Type | null, isStatic: boolean = false, ) { this._id = GraphNode.idCounter++; @@ -174,7 +174,6 @@ export default abstract class GraphNode { * Returns a dependency object for the desired output. */ public toDep(output?: string): Dependency { - return { node: this, output: output ?? GraphNode.defaultIoName }; } @@ -229,15 +228,28 @@ export default abstract class GraphNode { * @returns The output of the node at the given frame. */ public getOutput(name: string = GraphNode.defaultIoName, graph?: Graph, frame: number = 0): unknown { - const { frame: oFrame, outputs } = this._out; - - const getOutput = outputs.get(name); + const getOutput = this._out.outputs.get(name); if (getOutput == undefined) { throw new Error(`Provided ${this.nodeType} node does not have an output named '${name}'`); } - const oValue = getOutput.value; - if (!this._isStatic && (oValue == undefined || oFrame !== frame) || this._needsUpdate) { + this._needsUpdate ||= getOutput.value == undefined; + + this.run(graph, frame); + + return getOutput.value; + } + + /** + * Run the node for a given frame. + * + * @param graph The graph the node is a part of. + * @param frame The frame to get the output for. + */ + public run(graph?: Graph, frame: number = 0): void { + const oFrame = this._out.frame; + + if (!this._isStatic && oFrame !== frame || this._needsUpdate) { this._apply(graph, frame); this._out.frame = frame; @@ -247,8 +259,6 @@ export default abstract class GraphNode { } this._needsUpdate = false; - - return getOutput.value; } /** diff --git a/src/Graph/Nodes/ProcessorNode.ts b/src/Graph/Nodes/ProcessorNode.ts index c8d0d8efba..64152c2352 100644 --- a/src/Graph/Nodes/ProcessorNode.ts +++ b/src/Graph/Nodes/ProcessorNode.ts @@ -5,7 +5,7 @@ import { Type, Dependency, DumpDotNodeStyle, Graph } from '../Prelude'; export default class ProcessorNode extends GraphNode { public constructor( inputs: { [name: string]: [Dependency, Type] }, - outputs: Map | Type, + outputs: Map | Type | null, public callback: (frame: number, args: { [arg: string]: unknown }) => void, isStatic: boolean = false, ) { diff --git a/src/Graph/Nodes/ScreenShaderNode.ts b/src/Graph/Nodes/ScreenShaderNode.ts index b18acb7505..b519141868 100644 --- a/src/Graph/Nodes/ScreenShaderNode.ts +++ b/src/Graph/Nodes/ScreenShaderNode.ts @@ -107,7 +107,7 @@ void main() { target: [target, BuiltinType.RenderTarget], renderer: [renderer, BuiltinType.Renderer], }, - BuiltinType.RenderTarget, + toScreen ? null : BuiltinType.RenderTarget, (_frame, args) => { const { target: input, renderer, ...rest } = args as CallbackArgs; @@ -137,7 +137,9 @@ void main() { renderer.clear(); renderer.render(ScreenShaderNode._scene, ScreenShaderNode._camera); - this.updateOutputs({ [ScreenShaderNode.defaultIoName]: target }); + if (target != null) { + this.updateOutputs({ [ScreenShaderNode.defaultIoName]: target }); + } }); this._fragmentShaderParts = fragmentShaderParts;