123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488 |
- import Node, { addNodeClass } from '../core/Node.js';
- import { scriptableValue } from './ScriptableValueNode.js';
- import { addNodeElement, nodeProxy, float } from '../shadernode/ShaderNode.js';
- class Resources extends Map {
- get( key, callback = null, ...params ) {
- if ( this.has( key ) ) return super.get( key );
- if ( callback !== null ) {
- const value = callback( ...params );
- this.set( key, value );
- return value;
- }
- }
- }
- class Parameters {
- constructor( scriptableNode ) {
- this.scriptableNode = scriptableNode;
- }
- get parameters() {
- return this.scriptableNode.parameters;
- }
- get layout() {
- return this.scriptableNode.getLayout();
- }
- getInputLayout( id ) {
- return this.scriptableNode.getInputLayout( id );
- }
- get( name ) {
- const param = this.parameters[ name ];
- const value = param ? param.getValue() : null;
- return value;
- }
- }
- export const global = new Resources();
- class ScriptableNode extends Node {
- constructor( codeNode = null, parameters = {} ) {
- super();
- this.codeNode = codeNode;
- this.parameters = parameters;
- this._local = new Resources();
- this._output = scriptableValue();
- this._outputs = {};
- this._source = this.source;
- this._method = null;
- this._object = null;
- this._value = null;
- this._needsOutputUpdate = true;
- this.onRefresh = this.onRefresh.bind( this );
- this.isScriptableNode = true;
- }
- get source() {
- return this.codeNode ? this.codeNode.code : '';
- }
- setLocal( name, value ) {
- return this._local.set( name, value );
- }
- getLocal( name ) {
- return this._local.get( name );
- }
- onRefresh() {
- this._refresh();
- }
- getInputLayout( id ) {
- for ( const element of this.getLayout() ) {
- if ( element.inputType && ( element.id === id || element.name === id ) ) {
- return element;
- }
- }
- }
- getOutputLayout( id ) {
- for ( const element of this.getLayout() ) {
- if ( element.outputType && ( element.id === id || element.name === id ) ) {
- return element;
- }
- }
- }
- setOutput( name, value ) {
- const outputs = this._outputs;
- if ( outputs[ name ] === undefined ) {
- outputs[ name ] = scriptableValue( value );
- } else {
- outputs[ name ].value = value;
- }
- return this;
- }
- getOutput( name ) {
- return this._outputs[ name ];
- }
- getParameter( name ) {
- return this.parameters[ name ];
- }
- setParameter( name, value ) {
- const parameters = this.parameters;
- if ( value && value.isScriptableNode ) {
- this.deleteParameter( name );
- parameters[ name ] = value;
- parameters[ name ].getDefaultOutput().events.addEventListener( 'refresh', this.onRefresh );
- } else if ( value && value.isScriptableValueNode ) {
- this.deleteParameter( name );
- parameters[ name ] = value;
- parameters[ name ].events.addEventListener( 'refresh', this.onRefresh );
- } else if ( parameters[ name ] === undefined ) {
- parameters[ name ] = scriptableValue( value );
- parameters[ name ].events.addEventListener( 'refresh', this.onRefresh );
- } else {
- parameters[ name ].value = value;
- }
- return this;
- }
- getValue() {
- return this.getDefaultOutput().getValue();
- }
- deleteParameter( name ) {
- let valueNode = this.parameters[ name ];
- if ( valueNode ) {
- if ( valueNode.isScriptableNode ) valueNode = valueNode.getDefaultOutput();
- valueNode.events.removeEventListener( 'refresh', this.onRefresh );
- }
- return this;
- }
- clearParameters() {
- for ( const name of Object.keys( this.parameters ) ) {
- this.deleteParameter( name );
- }
- this.needsUpdate = true;
- return this;
- }
- call( name, ...params ) {
- const object = this.getObject();
- const method = object[ name ];
- if ( typeof method === 'function' ) {
- return method( ...params );
- }
- }
- async callAsync( name, ...params ) {
- const object = this.getObject();
- const method = object[ name ];
- if ( typeof method === 'function' ) {
- return method.constructor.name === 'AsyncFunction' ? await method( ...params ) : method( ...params );
- }
- }
- getNodeType( builder ) {
- return this.getDefaultOutputNode().getNodeType( builder );
- }
- refresh( output = null ) {
- if ( output !== null ) {
- this.getOutput( output ).refresh();
- } else {
- this._refresh();
- }
- }
- getObject() {
- if ( this.needsUpdate ) this.dispose();
- if ( this._object !== null ) return this._object;
- //
- const refresh = () => this.refresh();
- const setOutput = ( id, value ) => this.setOutput( id, value );
- const parameters = new Parameters( this );
- const THREE = global.get( 'THREE' );
- const TSL = global.get( 'TSL' );
- const method = this.getMethod( this.codeNode );
- const params = [ parameters, this._local, global, refresh, setOutput, THREE, TSL ];
- this._object = method( ...params );
- const layout = this._object.layout;
- if ( layout ) {
- if ( layout.cache === false ) {
- this._local.clear();
- }
- // default output
- this._output.outputType = layout.outputType || null;
- if ( Array.isArray( layout.elements ) ) {
- for ( const element of layout.elements ) {
- const id = element.id || element.name;
- if ( element.inputType ) {
- if ( this.getParameter( id ) === undefined ) this.setParameter( id, null );
- this.getParameter( id ).inputType = element.inputType;
- }
- if ( element.outputType ) {
- if ( this.getOutput( id ) === undefined ) this.setOutput( id, null );
- this.getOutput( id ).outputType = element.outputType;
- }
- }
- }
- }
- return this._object;
- }
- deserialize( data ) {
- super.deserialize( data );
- for ( const name in this.parameters ) {
- let valueNode = this.parameters[ name ];
- if ( valueNode.isScriptableNode ) valueNode = valueNode.getDefaultOutput();
- valueNode.events.addEventListener( 'refresh', this.onRefresh );
- }
- }
- getLayout() {
- return this.getObject().layout;
- }
- getDefaultOutputNode() {
- const output = this.getDefaultOutput().value;
- if ( output && output.isNode ) {
- return output;
- }
- return float();
- }
- getDefaultOutput() {
- return this._exec()._output;
- }
- getMethod() {
- if ( this.needsUpdate ) this.dispose();
- if ( this._method !== null ) return this._method;
- //
- const parametersProps = [ 'parameters', 'local', 'global', 'refresh', 'setOutput', 'THREE', 'TSL' ];
- const interfaceProps = [ 'layout', 'init', 'main', 'dispose' ];
- const properties = interfaceProps.join( ', ' );
- const declarations = 'var ' + properties + '; var output = {};\n';
- const returns = '\nreturn { ...output, ' + properties + ' };';
- const code = declarations + this.codeNode.code + returns;
- //
- this._method = new Function( ...parametersProps, code );
- return this._method;
- }
- dispose() {
- if ( this._method === null ) return;
- if ( this._object && typeof this._object.dispose === 'function' ) {
- this._object.dispose();
- }
- this._method = null;
- this._object = null;
- this._source = null;
- this._value = null;
- this._needsOutputUpdate = true;
- this._output.value = null;
- this._outputs = {};
- }
- construct() {
- return this.getDefaultOutputNode();
- }
- set needsUpdate( value ) {
- if ( value === true ) this.dispose();
- }
- get needsUpdate() {
- return this.source !== this._source;
- }
- _exec() {
- if ( this.codeNode === null ) return this;
- if ( this._needsOutputUpdate === true ) {
- this._value = this.call( 'main' );
- this._needsOutputUpdate = false;
- }
- this._output.value = this._value;
- return this;
- }
- _refresh() {
- this.needsUpdate = true;
- this._exec();
- this._output.refresh();
- }
- }
- export default ScriptableNode;
- export const scriptable = nodeProxy( ScriptableNode );
- addNodeElement( 'scriptable', scriptable );
- addNodeClass( ScriptableNode );
|