diff --git a/package.json b/package.json index 699941f..1339d5e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "graphfx", - "version": "0.0.3", + "version": "0.0.4", "description": "Graph image processing pipeline", "main": "src/index.js", "scripts": { diff --git a/src/graph.js b/src/graph.js index 724cfb0..6dae141 100644 --- a/src/graph.js +++ b/src/graph.js @@ -38,6 +38,12 @@ export default class Graph { return results; } + destroy() { + for(let {node} of this.nodes) { + node.destroy(); + } + } + } diff --git a/src/nodes/Canvas2d/Canvas2d.js b/src/nodes/Canvas2d/Canvas2d.js index c357476..7d5b770 100644 --- a/src/nodes/Canvas2d/Canvas2d.js +++ b/src/nodes/Canvas2d/Canvas2d.js @@ -25,6 +25,11 @@ export default class Canvas2d extends Node { this.__canvas = document.createElement('canvas'); } + destroy() { + this.__canvas = null; + super.destroy(); + } + async _update() { const values = {}; for (let name of Object.keys(this.in.variables)) { @@ -37,8 +42,12 @@ export default class Canvas2d extends Node { const result = await this.render(values, this.__canvas, ctx); if (result instanceof ImageBitmap) { this.__out.image.value = result; - this.out.width.value = this.out.image.value.width; - this.out.height.value = this.out.image.value.height; + if (this.out.width.value !== this.out.image.value.width) { + this.out.width.value = this.out.image.value.width; + } + if (this.out.height.value !== this.out.image.value.height) { + this.out.height.value = this.out.image.value.height; + } } } diff --git a/src/nodes/Canvas2d/Text.js b/src/nodes/Canvas2d/Text.js index d91cdca..3e3766a 100644 --- a/src/nodes/Canvas2d/Text.js +++ b/src/nodes/Canvas2d/Text.js @@ -65,7 +65,6 @@ export default class Resize extends Canvas2d { ctx.textAlign = textAlign; const x = textAlign === 'center' ? canvas.width /2 : textAlign === 'left' ? 0 : canvas.width; - console.log(text.split('\\n')); text.split('\\n').forEach((line, index) => { ctx.fillText(line, x, fontSize * (1 + index)); }); diff --git a/src/nodes/Node.js b/src/nodes/Node.js index 9f31ab4..32e7760 100644 --- a/src/nodes/Node.js +++ b/src/nodes/Node.js @@ -5,20 +5,28 @@ export default class Node { constructor(name, inputDefinition, outputDefiniton, options) { this.name = name; + this.uid = uuidv4(); this.id = uuidv4(); this.__in = new Inputs(inputDefinition, this); this.__out = new Outputs(outputDefiniton, this); this.__in.update = (name) => this.__update([name]); this.__scheduledUpdate = false; + this.__currentUpdate = Promise.resolve(); + this._stopped = false; } __update(changes) { - if (!this.__scheduledUpdate) { + if (!this.__scheduledUpdate && !this._stopped) { this.__scheduledUpdate = true; - Promise.resolve() - .then(() => { + this.__currentUpdate = this.__currentUpdate + .then(async () => { + const startTag =`graphfx-update-start:${this.uid}`; + const endTag = `graphfx-update-end:${this.uid}` this.__scheduledUpdate = false; - this._update(); + performance.mark(startTag) + await this._update(); + performance.mark(endTag) + performance.measure(`GraphFX<${this.name}>`, startTag, endTag) }); } } @@ -57,11 +65,9 @@ export default class Node { } reconnect({options}, outputs) { - console.log('outputs', outputs) for (let name of Object.keys(options.in)) { const {output} = options.in[name]; if (output) { - console.log('connecting', this.in[name].id, outputs[output]) this.in[name].connect(outputs[output]); } } @@ -69,9 +75,18 @@ export default class Node { this.out[name].deserialize(options.out[name]); } for (let name of Object.keys(options.in)) { - console.log(options.in[name]); this.in[name].deserialize(options.in[name]); } } + + destroy() { + this._stopped = true; + Promise.resolve() + .then(() => { + for (let input of this.in) { + input.disconnect(); + } + }) + } }; diff --git a/src/nodes/WebGL/WebGl.js b/src/nodes/WebGL/WebGl.js index bea6a37..f5c1e12 100644 --- a/src/nodes/WebGL/WebGl.js +++ b/src/nodes/WebGL/WebGl.js @@ -21,6 +21,12 @@ export default class WebGL extends Node { }); } + destroy() { + this.canvas = null; + this.program = null; + super.destroy(); + } + get vert() { return ` attribute vec2 a_position; @@ -147,6 +153,9 @@ export default class WebGL extends Node { const image = this.in.image.value; if (!image) return; const {width, height} = mediaSize(image); + if (width === 0 || height === 0) { + return; + } this.setup(); const canvas = this.canvas; canvas.width = width; @@ -222,8 +231,12 @@ export default class WebGL extends Node { const result = await createImageBitmap(canvas); this.out.image.value = result; - this.out.width.value = this.out.image.value.width; - this.out.height.value = this.out.image.value.height; + if (this.out.width.value !== this.out.image.value.width) { + this.out.width.value = this.out.image.value.width; + } + if (this.out.height.value !== this.out.image.value.height) { + this.out.height.value = this.out.image.value.height; + } } setRectangle(gl, x, y, width, height) { diff --git a/src/nodes/Webcam.js b/src/nodes/Webcam.js index 4c60f75..274724f 100644 --- a/src/nodes/Webcam.js +++ b/src/nodes/Webcam.js @@ -1,6 +1,6 @@ import Node from './Node'; -export default class ToBlob extends Node { +export default class Webcam extends Node { constructor(options) { super('Webcam', {}, { @@ -14,14 +14,14 @@ export default class ToBlob extends Node { type: 'Number', } }); - this.start(); + this.start() + .catch(console.error.bind(console)) } async start() { const devices = (await navigator.mediaDevices.enumerateDevices()).filter((d) => d.kind === 'videoinput') const last = (ary) => ary[ary.length - 1]; - console.log(last(devices)); const stream = await navigator.mediaDevices.getUserMedia({ video: { width: { min: 480, ideal: 1920, max: 1920 }, @@ -29,7 +29,6 @@ export default class ToBlob extends Node { deviceId: last(devices).deviceId }, }); - console.log(stream) /** @type {HTMLVideoElement} */ const video = document.createElement('video'); @@ -45,7 +44,11 @@ export default class ToBlob extends Node { this.out.width.value = video.videoWidth; this.out.height.value = video.videoHeight; } - requestAnimationFrame(feedLoop); + if (!this._stopped) { + requestAnimationFrame(feedLoop); + } else { + video.src = null; + } }; feedLoop(); } diff --git a/src/nodes/io/AbstractIOSet.js b/src/nodes/io/AbstractIOSet.js index 1ce7c12..0254681 100644 --- a/src/nodes/io/AbstractIOSet.js +++ b/src/nodes/io/AbstractIOSet.js @@ -21,6 +21,12 @@ export default class AbstractIOSet { } } + *[Symbol.iterator]() { + for (let name of Object.keys(this.variables)) { + yield this.__values[name]; + } + } + __createProperty(name, definition) { } diff --git a/src/utils.js b/src/utils.js index 695a66f..c028f65 100644 --- a/src/utils.js +++ b/src/utils.js @@ -9,4 +9,5 @@ export const waitForMedia = async (media) => { return media; } -export const setImmediate = (fn) => Promise.resolve().then(fn); \ No newline at end of file +export const setImmediate = (fn) => Promise.resolve().then(fn); +