Skip to content

⬅️ Back to Table of Contents

📄 ScriptableNode.js

📊 Analysis Summary

Metric Count
🔧 Functions 32
🧱 Classes 3
📦 Imports 6
📊 Variables & Constants 20

📚 Table of Contents

🛠️ File Location:

📂 src/nodes/code/ScriptableNode.js

📦 Imports

Name Source
Node ../core/Node.js
scriptableValue ./ScriptableValueNode.js
nodeProxy ../tsl/TSLBase.js
float ../tsl/TSLBase.js
hashArray ../core/NodeUtils.js
hashString ../core/NodeUtils.js

Variables & Constants

Name Type Kind Value Exported
param any let/var this.parameters[ name ]
value any let/var param ? param.getValue() : null
ScriptableNodeResources Resources let/var new Resources()
outputs {} let/var this._outputs
parameters any let/var this.parameters
valueNode any let/var this.parameters[ name ]
method any let/var object[ name ]
method any let/var object[ name ]
parameters Parameters let/var new Parameters( this )
params any[] let/var [ parameters, this._local, ScriptableNodeResources, refresh, setOutput, THREE...
layout any let/var this._object.layout
id any let/var element.id \|\| element.name
valueNode any let/var this.parameters[ name ]
output any let/var this.getDefaultOutput().value
parametersProps string[] let/var [ 'parameters', 'local', 'global', 'refresh', 'setOutput', 'THREE', 'TSL' ]
interfaceProps string[] let/var [ 'layout', 'init', 'main', 'dispose' ]
declarations string let/var 'var ' + properties + '; var output = {};\n'
returns string let/var '\nreturn { ...output, ' + properties + ' };'
code string let/var declarations + this.codeNode.code + returns
values number[] let/var [ hashString( this.source ), this.getDefaultOutputNode().getCacheKey( force ) ]

Functions

Resources.get(key: any, callback: any, params: any[]): any

Parameters:

  • key any
  • callback any
  • params any[]

Returns: any

Calls:

  • this.has
  • super.get
  • callback
  • this.set
Code
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;

        }

    }

Parameters.getInputLayout(id: any): any

Parameters:

  • id any

Returns: any

Calls:

  • this.scriptableNode.getInputLayout
Code
getInputLayout( id ) {

        return this.scriptableNode.getInputLayout( id );

    }

Parameters.get(name: any): any

Parameters:

  • name any

Returns: any

Calls:

  • param.getValue
Code
get( name ) {

        const param = this.parameters[ name ];
        const value = param ? param.getValue() : null;

        return value;

    }

ScriptableNode.setLocal(name: string, value: any): Resources

JSDoc:

/**
     * Sets the reference of a local script variable.
     *
     * @param {string} name - The variable name.
     * @param {Object} value - The reference to set.
     * @return {Resources} The resource map
     */

Parameters:

  • name string
  • value any

Returns: Resources

Calls:

  • this._local.set
Code
setLocal( name, value ) {

        return this._local.set( name, value );

    }

ScriptableNode.getLocal(name: string): any

JSDoc:

/**
     * Gets the value of a local script variable.
     *
     * @param {string} name - The variable name.
     * @return {Object} The value.
     */

Parameters:

  • name string

Returns: any

Calls:

  • this._local.get
Code
getLocal( name ) {

        return this._local.get( name );

    }

ScriptableNode.onRefresh(): void

JSDoc:

/**
     * Event listener for the `refresh` event.
     */

Returns: void

Calls:

  • this._refresh
Code
onRefresh() {

        this._refresh();

    }

ScriptableNode.getInputLayout(id: string): any

JSDoc:

/**
     * Returns an input from the layout with the given id/name.
     *
     * @param {string} id - The id/name of the input.
     * @return {Object} The element entry.
     */

Parameters:

  • id string

Returns: any

Calls:

  • this.getLayout
Code
getInputLayout( id ) {

        for ( const element of this.getLayout() ) {

            if ( element.inputType && ( element.id === id || element.name === id ) ) {

                return element;

            }

        }

    }

ScriptableNode.getOutputLayout(id: string): any

JSDoc:

/**
     * Returns an output from the layout with the given id/name.
     *
     * @param {string} id - The id/name of the output.
     * @return {Object} The element entry.
     */

Parameters:

  • id string

Returns: any

Calls:

  • this.getLayout
Code
getOutputLayout( id ) {

        for ( const element of this.getLayout() ) {

            if ( element.outputType && ( element.id === id || element.name === id ) ) {

                return element;

            }

        }

    }

ScriptableNode.setOutput(name: string, value: Node): ScriptableNode

JSDoc:

/**
     * Defines a script output for the given name and value.
     *
     * @param {string} name - The name of the output.
     * @param {Node} value - The node value.
     * @return {ScriptableNode} A reference to this node.
     */

Parameters:

  • name string
  • value Node

Returns: ScriptableNode

Calls:

  • scriptableValue (from ./ScriptableValueNode.js)
Code
setOutput( name, value ) {

        const outputs = this._outputs;

        if ( outputs[ name ] === undefined ) {

            outputs[ name ] = scriptableValue( value );

        } else {

            outputs[ name ].value = value;

        }

        return this;

    }

ScriptableNode.getOutput(name: string): ScriptableValueNode

JSDoc:

/**
     * Returns a script output for the given name.
     *
     * @param {string} name - The name of the output.
     * @return {ScriptableValueNode} The node value.
     */

Parameters:

  • name string

Returns: ScriptableValueNode

Code
getOutput( name ) {

        return this._outputs[ name ];

    }

ScriptableNode.getParameter(name: string): ScriptableValueNode

JSDoc:

/**
     * Returns a parameter for the given name
     *
     * @param {string} name - The name of the parameter.
     * @return {ScriptableValueNode} The node value.
     */

Parameters:

  • name string

Returns: ScriptableValueNode

Code
getParameter( name ) {

        return this.parameters[ name ];

    }

ScriptableNode.setParameter(name: string, value: any): ScriptableNode

JSDoc:

/**
     * Sets a value for the given parameter name.
     *
     * @param {string} name - The parameter name.
     * @param {any} value - The parameter value.
     * @return {ScriptableNode} A reference to this node.
     */

Parameters:

  • name string
  • value any

Returns: ScriptableNode

Calls:

  • this.deleteParameter
  • parameters[ name ].getDefaultOutput().events.addEventListener
  • parameters[ name ].events.addEventListener
  • scriptableValue (from ./ScriptableValueNode.js)
Code
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;

    }

ScriptableNode.getValue(): Node

JSDoc:

/**
     * Returns the value of this node which is the value of
     * the default output.
     *
     * @return {Node} The value.
     */

Returns: Node

Calls:

  • this.getDefaultOutput().getValue
Code
getValue() {

        return this.getDefaultOutput().getValue();

    }

ScriptableNode.deleteParameter(name: string): ScriptableNode

JSDoc:

/**
     * Deletes a parameter from the script.
     *
     * @param {string} name - The parameter to remove.
     * @return {ScriptableNode} A reference to this node.
     */

Parameters:

  • name string

Returns: ScriptableNode

Calls:

  • valueNode.getDefaultOutput
  • valueNode.events.removeEventListener
Code
deleteParameter( name ) {

        let valueNode = this.parameters[ name ];

        if ( valueNode ) {

            if ( valueNode.isScriptableNode ) valueNode = valueNode.getDefaultOutput();

            valueNode.events.removeEventListener( 'refresh', this.onRefresh );

        }

        return this;

    }

ScriptableNode.clearParameters(): ScriptableNode

JSDoc:

/**
     * Deletes all parameters from the script.
     *
     * @return {ScriptableNode} A reference to this node.
     */

Returns: ScriptableNode

Calls:

  • Object.keys
  • this.deleteParameter
Code
clearParameters() {

        for ( const name of Object.keys( this.parameters ) ) {

            this.deleteParameter( name );

        }

        this.needsUpdate = true;

        return this;

    }

ScriptableNode.call(name: string, params: any[]): any

JSDoc:

/**
     * Calls a function from the script.
     *
     * @param {string} name - The function name.
     * @param {...any} params - A list of parameters.
     * @return {any} The result of the function call.
     */

Parameters:

  • name string
  • params any[]

Returns: any

Calls:

  • this.getObject
  • method
Code
call( name, ...params ) {

        const object = this.getObject();
        const method = object[ name ];

        if ( typeof method === 'function' ) {

            return method( ...params );

        }

    }

ScriptableNode.callAsync(name: string, params: any[]): Promise<any>

JSDoc:

/**
     * Asynchronously calls a function from the script.
     *
     * @param {string} name - The function name.
     * @param {...any} params - A list of parameters.
     * @return {Promise<any>} The result of the function call.
     */

Parameters:

  • name string
  • params any[]

Returns: Promise<any>

Calls:

  • this.getObject
  • method
Code
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 );

        }

    }

ScriptableNode.getNodeType(builder: NodeBuilder): string

JSDoc:

/**
     * Overwritten since the node types is inferred from the script's output.
     *
     * @param {NodeBuilder} builder - The current node builder
     * @return {string} The node type.
     */

Parameters:

  • builder NodeBuilder

Returns: string

Calls:

  • this.getDefaultOutputNode().getNodeType
Code
getNodeType( builder ) {

        return this.getDefaultOutputNode().getNodeType( builder );

    }

ScriptableNode.refresh(output: string): void

JSDoc:

/**
     * Refreshes the script node.
     *
     * @param {?string} [output=null] - An optional output.
     */

Parameters:

  • output string

Returns: void

Calls:

  • this.getOutput( output ).refresh
  • this._refresh
Code
refresh( output = null ) {

        if ( output !== null ) {

            this.getOutput( output ).refresh();

        } else {

            this._refresh();

        }

    }

ScriptableNode.getObject(): any

JSDoc:

/**
     * Returns an object representation of the script.
     *
     * @return {Object} The result object.
     */

Returns: any

Calls:

  • this.dispose
  • this.refresh
  • this.setOutput
  • ScriptableNodeResources.get
  • this.getMethod
  • method
  • this._local.clear
  • Array.isArray
  • this.getParameter
  • this.setParameter
  • this.getOutput

Internal Comments:

// (x2)
// default output (x5)

Code
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 = ScriptableNodeResources.get( 'THREE' );
        const TSL = ScriptableNodeResources.get( 'TSL' );

        const method = this.getMethod();
        const params = [ parameters, this._local, ScriptableNodeResources, 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;

    }

ScriptableNode.deserialize(data: any): void

Parameters:

  • data any

Returns: void

Calls:

  • super.deserialize
  • valueNode.getDefaultOutput
  • valueNode.events.addEventListener
Code
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 );

        }

    }

ScriptableNode.getLayout(): any

JSDoc:

/**
     * Returns the layout of the script.
     *
     * @return {Object} The script's layout.
     */

Returns: any

Calls:

  • this.getObject
Code
getLayout() {

        return this.getObject().layout;

    }

ScriptableNode.getDefaultOutputNode(): Node

JSDoc:

/**
     * Returns default node output of the script.
     *
     * @return {Node} The default node output.
     */

Returns: Node

Calls:

  • this.getDefaultOutput
  • float (from ../tsl/TSLBase.js)
Code
getDefaultOutputNode() {

        const output = this.getDefaultOutput().value;

        if ( output && output.isNode ) {

            return output;

        }

        return float();

    }

ScriptableNode.getDefaultOutput(): ScriptableValueNode

JSDoc:

/**
     * Returns default output of the script.
     *
     * @return {ScriptableValueNode} The default output.
     */

Returns: ScriptableValueNode

Calls:

  • this._exec
Code
getDefaultOutput()  {

        return this._exec()._output;

    }

ScriptableNode.getMethod(): Function

JSDoc:

/**
     * Returns a function created from the node's script.
     *
     * @return {Function} The function representing the node's code.
     */

Returns: Function

Calls:

  • this.dispose
  • interfaceProps.join

Internal Comments:

// (x6)

Code
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;

    }

ScriptableNode.dispose(): void

JSDoc:

/**
     * Frees all internal resources.
     */

Returns: void

Calls:

  • this._object.dispose
Code
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 = {};

    }

ScriptableNode.setup(): Node

Returns: Node

Calls:

  • this.getDefaultOutputNode
Code
setup() {

        return this.getDefaultOutputNode();

    }

ScriptableNode.getCacheKey(force: any): number

Parameters:

  • force any

Returns: number

Calls:

  • hashString (from ../core/NodeUtils.js)
  • this.getDefaultOutputNode().getCacheKey
  • values.push
  • this.parameters[ param ].getCacheKey
  • hashArray (from ../core/NodeUtils.js)
Code
getCacheKey( force ) {

        const values = [ hashString( this.source ), this.getDefaultOutputNode().getCacheKey( force ) ];

        for ( const param in this.parameters ) {

            values.push( this.parameters[ param ].getCacheKey( force ) );

        }

        return hashArray( values );

    }

ScriptableNode._exec(): ScriptableNode

JSDoc:

/**
     * Executes the `main` function of the script.
     *
     * @private
     * @return {ScriptableNode} A reference to this node.
     */

Returns: ScriptableNode

Calls:

  • this.call
Code
_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;

    }

ScriptableNode._refresh(): void

JSDoc:

/**
     * Executes the refresh.
     *
     * @private
     */

Returns: void

Calls:

  • this._exec
  • this._output.refresh
Code
_refresh() {

        this.needsUpdate = true;

        this._exec();

        this._output.refresh();

    }

refresh(): void

Returns: void

Calls:

  • this.refresh
Code
() => this.refresh()

setOutput(id: any, value: any): ScriptableNode

Parameters:

  • id any
  • value any

Returns: ScriptableNode

Calls:

  • this.setOutput
Code
( id, value ) => this.setOutput( id, value )

Classes

Resources

Class Code
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;

        }

    }

}

Methods

get(key: any, callback: any, params: any[]): any
Code
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;

        }

    }

Parameters

Class Code
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;

    }

}

Methods

getInputLayout(id: any): any
Code
getInputLayout( id ) {

        return this.scriptableNode.getInputLayout( id );

    }
get(name: any): any
Code
get( name ) {

        const param = this.parameters[ name ];
        const value = param ? param.getValue() : null;

        return value;

    }

ScriptableNode

Class Code
class ScriptableNode extends Node {

    static get type() {

        return 'ScriptableNode';

    }

    /**
     * Constructs a new scriptable node.
     *
     * @param {?CodeNode} [codeNode=null] - The code node.
     * @param {Object} [parameters={}] - The parameters definition.
     */
    constructor( codeNode = null, parameters = {} ) {

        super();

        /**
         * The code node.
         *
         * @type {?CodeNode}
         * @default null
         */
        this.codeNode = codeNode;

        /**
         * The parameters definition.
         *
         * @type {Object}
         * @default {}
         */
        this.parameters = parameters;

        this._local = new Resources();
        this._output = scriptableValue( null );
        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 flag can be used for type testing.
         *
         * @type {boolean}
         * @readonly
         * @default true
         */
        this.isScriptableNode = true;

    }

    /**
     * The source code of the scriptable node.
     *
     * @type {string}
     */
    get source() {

        return this.codeNode ? this.codeNode.code : '';

    }

    /**
     * Sets the reference of a local script variable.
     *
     * @param {string} name - The variable name.
     * @param {Object} value - The reference to set.
     * @return {Resources} The resource map
     */
    setLocal( name, value ) {

        return this._local.set( name, value );

    }

    /**
     * Gets the value of a local script variable.
     *
     * @param {string} name - The variable name.
     * @return {Object} The value.
     */
    getLocal( name ) {

        return this._local.get( name );

    }

    /**
     * Event listener for the `refresh` event.
     */
    onRefresh() {

        this._refresh();

    }

    /**
     * Returns an input from the layout with the given id/name.
     *
     * @param {string} id - The id/name of the input.
     * @return {Object} The element entry.
     */
    getInputLayout( id ) {

        for ( const element of this.getLayout() ) {

            if ( element.inputType && ( element.id === id || element.name === id ) ) {

                return element;

            }

        }

    }

    /**
     * Returns an output from the layout with the given id/name.
     *
     * @param {string} id - The id/name of the output.
     * @return {Object} The element entry.
     */
    getOutputLayout( id ) {

        for ( const element of this.getLayout() ) {

            if ( element.outputType && ( element.id === id || element.name === id ) ) {

                return element;

            }

        }

    }

    /**
     * Defines a script output for the given name and value.
     *
     * @param {string} name - The name of the output.
     * @param {Node} value - The node value.
     * @return {ScriptableNode} A reference to this node.
     */
    setOutput( name, value ) {

        const outputs = this._outputs;

        if ( outputs[ name ] === undefined ) {

            outputs[ name ] = scriptableValue( value );

        } else {

            outputs[ name ].value = value;

        }

        return this;

    }

    /**
     * Returns a script output for the given name.
     *
     * @param {string} name - The name of the output.
     * @return {ScriptableValueNode} The node value.
     */
    getOutput( name ) {

        return this._outputs[ name ];

    }

    /**
     * Returns a parameter for the given name
     *
     * @param {string} name - The name of the parameter.
     * @return {ScriptableValueNode} The node value.
     */
    getParameter( name ) {

        return this.parameters[ name ];

    }

    /**
     * Sets a value for the given parameter name.
     *
     * @param {string} name - The parameter name.
     * @param {any} value - The parameter value.
     * @return {ScriptableNode} A reference to this node.
     */
    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;

    }

    /**
     * Returns the value of this node which is the value of
     * the default output.
     *
     * @return {Node} The value.
     */
    getValue() {

        return this.getDefaultOutput().getValue();

    }

    /**
     * Deletes a parameter from the script.
     *
     * @param {string} name - The parameter to remove.
     * @return {ScriptableNode} A reference to this node.
     */
    deleteParameter( name ) {

        let valueNode = this.parameters[ name ];

        if ( valueNode ) {

            if ( valueNode.isScriptableNode ) valueNode = valueNode.getDefaultOutput();

            valueNode.events.removeEventListener( 'refresh', this.onRefresh );

        }

        return this;

    }

    /**
     * Deletes all parameters from the script.
     *
     * @return {ScriptableNode} A reference to this node.
     */
    clearParameters() {

        for ( const name of Object.keys( this.parameters ) ) {

            this.deleteParameter( name );

        }

        this.needsUpdate = true;

        return this;

    }

    /**
     * Calls a function from the script.
     *
     * @param {string} name - The function name.
     * @param {...any} params - A list of parameters.
     * @return {any} The result of the function call.
     */
    call( name, ...params ) {

        const object = this.getObject();
        const method = object[ name ];

        if ( typeof method === 'function' ) {

            return method( ...params );

        }

    }

    /**
     * Asynchronously calls a function from the script.
     *
     * @param {string} name - The function name.
     * @param {...any} params - A list of parameters.
     * @return {Promise<any>} The result of the function call.
     */
    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 );

        }

    }

    /**
     * Overwritten since the node types is inferred from the script's output.
     *
     * @param {NodeBuilder} builder - The current node builder
     * @return {string} The node type.
     */
    getNodeType( builder ) {

        return this.getDefaultOutputNode().getNodeType( builder );

    }

    /**
     * Refreshes the script node.
     *
     * @param {?string} [output=null] - An optional output.
     */
    refresh( output = null ) {

        if ( output !== null ) {

            this.getOutput( output ).refresh();

        } else {

            this._refresh();

        }

    }

    /**
     * Returns an object representation of the script.
     *
     * @return {Object} The result object.
     */
    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 = ScriptableNodeResources.get( 'THREE' );
        const TSL = ScriptableNodeResources.get( 'TSL' );

        const method = this.getMethod();
        const params = [ parameters, this._local, ScriptableNodeResources, 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 );

        }

    }

    /**
     * Returns the layout of the script.
     *
     * @return {Object} The script's layout.
     */
    getLayout() {

        return this.getObject().layout;

    }

    /**
     * Returns default node output of the script.
     *
     * @return {Node} The default node output.
     */
    getDefaultOutputNode() {

        const output = this.getDefaultOutput().value;

        if ( output && output.isNode ) {

            return output;

        }

        return float();

    }

    /**
     * Returns default output of the script.
     *
     * @return {ScriptableValueNode} The default output.
     */
    getDefaultOutput()  {

        return this._exec()._output;

    }

    /**
     * Returns a function created from the node's script.
     *
     * @return {Function} The function representing the node's code.
     */
    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;

    }

    /**
     * Frees all internal resources.
     */
    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 = {};

    }

    setup() {

        return this.getDefaultOutputNode();

    }

    getCacheKey( force ) {

        const values = [ hashString( this.source ), this.getDefaultOutputNode().getCacheKey( force ) ];

        for ( const param in this.parameters ) {

            values.push( this.parameters[ param ].getCacheKey( force ) );

        }

        return hashArray( values );

    }

    set needsUpdate( value ) {

        if ( value === true ) this.dispose();

    }

    get needsUpdate() {

        return this.source !== this._source;

    }

    /**
     * Executes the `main` function of the script.
     *
     * @private
     * @return {ScriptableNode} A reference to this node.
     */
    _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;

    }

    /**
     * Executes the refresh.
     *
     * @private
     */
    _refresh() {

        this.needsUpdate = true;

        this._exec();

        this._output.refresh();

    }

}

Methods

setLocal(name: string, value: any): Resources
Code
setLocal( name, value ) {

        return this._local.set( name, value );

    }
getLocal(name: string): any
Code
getLocal( name ) {

        return this._local.get( name );

    }
onRefresh(): void
Code
onRefresh() {

        this._refresh();

    }
getInputLayout(id: string): any
Code
getInputLayout( id ) {

        for ( const element of this.getLayout() ) {

            if ( element.inputType && ( element.id === id || element.name === id ) ) {

                return element;

            }

        }

    }
getOutputLayout(id: string): any
Code
getOutputLayout( id ) {

        for ( const element of this.getLayout() ) {

            if ( element.outputType && ( element.id === id || element.name === id ) ) {

                return element;

            }

        }

    }
setOutput(name: string, value: Node): ScriptableNode
Code
setOutput( name, value ) {

        const outputs = this._outputs;

        if ( outputs[ name ] === undefined ) {

            outputs[ name ] = scriptableValue( value );

        } else {

            outputs[ name ].value = value;

        }

        return this;

    }
getOutput(name: string): ScriptableValueNode
Code
getOutput( name ) {

        return this._outputs[ name ];

    }
getParameter(name: string): ScriptableValueNode
Code
getParameter( name ) {

        return this.parameters[ name ];

    }
setParameter(name: string, value: any): ScriptableNode
Code
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(): Node
Code
getValue() {

        return this.getDefaultOutput().getValue();

    }
deleteParameter(name: string): ScriptableNode
Code
deleteParameter( name ) {

        let valueNode = this.parameters[ name ];

        if ( valueNode ) {

            if ( valueNode.isScriptableNode ) valueNode = valueNode.getDefaultOutput();

            valueNode.events.removeEventListener( 'refresh', this.onRefresh );

        }

        return this;

    }
clearParameters(): ScriptableNode
Code
clearParameters() {

        for ( const name of Object.keys( this.parameters ) ) {

            this.deleteParameter( name );

        }

        this.needsUpdate = true;

        return this;

    }
call(name: string, params: any[]): any
Code
call( name, ...params ) {

        const object = this.getObject();
        const method = object[ name ];

        if ( typeof method === 'function' ) {

            return method( ...params );

        }

    }
callAsync(name: string, params: any[]): Promise<any>
Code
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: NodeBuilder): string
Code
getNodeType( builder ) {

        return this.getDefaultOutputNode().getNodeType( builder );

    }
refresh(output: string): void
Code
refresh( output = null ) {

        if ( output !== null ) {

            this.getOutput( output ).refresh();

        } else {

            this._refresh();

        }

    }
getObject(): any
Code
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 = ScriptableNodeResources.get( 'THREE' );
        const TSL = ScriptableNodeResources.get( 'TSL' );

        const method = this.getMethod();
        const params = [ parameters, this._local, ScriptableNodeResources, 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: any): void
Code
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(): any
Code
getLayout() {

        return this.getObject().layout;

    }
getDefaultOutputNode(): Node
Code
getDefaultOutputNode() {

        const output = this.getDefaultOutput().value;

        if ( output && output.isNode ) {

            return output;

        }

        return float();

    }
getDefaultOutput(): ScriptableValueNode
Code
getDefaultOutput()  {

        return this._exec()._output;

    }
getMethod(): Function
Code
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(): void
Code
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 = {};

    }
setup(): Node
Code
setup() {

        return this.getDefaultOutputNode();

    }
getCacheKey(force: any): number
Code
getCacheKey( force ) {

        const values = [ hashString( this.source ), this.getDefaultOutputNode().getCacheKey( force ) ];

        for ( const param in this.parameters ) {

            values.push( this.parameters[ param ].getCacheKey( force ) );

        }

        return hashArray( values );

    }
_exec(): ScriptableNode
Code
_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(): void
Code
_refresh() {

        this.needsUpdate = true;

        this._exec();

        this._output.refresh();

    }