Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/flo-bit/shapecraft. flo-bit.dev/shapecraft
Something went wrong. Try again.
8.1 kB · 505 lines
JavaScript
at commit da69385e
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506import Node from '../core/Node.js';import { scriptableValue } from './ScriptableValueNode.js';import { nodeProxy, float } from '../tsl/TSLBase.js';import { hashArray, hashString } from '../core/NodeUtils.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 ScriptableNodeResources = new Resources();
class ScriptableNode extends Node {
static get type() {
return 'ScriptableNode';
}
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 = ScriptableNodeResources.get( 'THREE' ); const TSL = ScriptableNodeResources.get( 'TSL' );
const method = this.getMethod( this.codeNode ); 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 );
}
}
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 = {};
}
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;
}
_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 = /*@__PURE__*/ nodeProxy( ScriptableNode );