ScriptableNode.js 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  1. import Node, { addNodeClass } from '../core/Node.js';
  2. import { scriptableValue } from './ScriptableValueNode.js';
  3. import { addNodeElement, nodeProxy, float } from '../shadernode/ShaderNode.js';
  4. class Resources extends Map {
  5. get( key, callback = null, ...params ) {
  6. if ( this.has( key ) ) return super.get( key );
  7. if ( callback !== null ) {
  8. const value = callback( ...params );
  9. this.set( key, value );
  10. return value;
  11. }
  12. }
  13. }
  14. class Parameters {
  15. constructor( scriptableNode ) {
  16. this.scriptableNode = scriptableNode;
  17. }
  18. get parameters() {
  19. return this.scriptableNode.parameters;
  20. }
  21. get layout() {
  22. return this.scriptableNode.getLayout();
  23. }
  24. getInputLayout( id ) {
  25. return this.scriptableNode.getInputLayout( id );
  26. }
  27. get( name ) {
  28. const param = this.parameters[ name ];
  29. const value = param ? param.getValue() : null;
  30. return value;
  31. }
  32. }
  33. export const global = new Resources();
  34. class ScriptableNode extends Node {
  35. constructor( codeNode = null, parameters = {} ) {
  36. super();
  37. this.codeNode = codeNode;
  38. this.parameters = parameters;
  39. this._local = new Resources();
  40. this._output = scriptableValue();
  41. this._outputs = {};
  42. this._source = this.source;
  43. this._method = null;
  44. this._object = null;
  45. this._value = null;
  46. this._needsOutputUpdate = true;
  47. this.onRefresh = this.onRefresh.bind( this );
  48. this.isScriptableNode = true;
  49. }
  50. get source() {
  51. return this.codeNode ? this.codeNode.code : '';
  52. }
  53. setLocal( name, value ) {
  54. return this._local.set( name, value );
  55. }
  56. getLocal( name ) {
  57. return this._local.get( name );
  58. }
  59. onRefresh() {
  60. this._refresh();
  61. }
  62. getInputLayout( id ) {
  63. for ( const element of this.getLayout() ) {
  64. if ( element.inputType && ( element.id === id || element.name === id ) ) {
  65. return element;
  66. }
  67. }
  68. }
  69. getOutputLayout( id ) {
  70. for ( const element of this.getLayout() ) {
  71. if ( element.outputType && ( element.id === id || element.name === id ) ) {
  72. return element;
  73. }
  74. }
  75. }
  76. setOutput( name, value ) {
  77. const outputs = this._outputs;
  78. if ( outputs[ name ] === undefined ) {
  79. outputs[ name ] = scriptableValue( value );
  80. } else {
  81. outputs[ name ].value = value;
  82. }
  83. return this;
  84. }
  85. getOutput( name ) {
  86. return this._outputs[ name ];
  87. }
  88. getParameter( name ) {
  89. return this.parameters[ name ];
  90. }
  91. setParameter( name, value ) {
  92. const parameters = this.parameters;
  93. if ( value && value.isScriptableNode ) {
  94. this.deleteParameter( name );
  95. parameters[ name ] = value;
  96. parameters[ name ].getDefaultOutput().events.addEventListener( 'refresh', this.onRefresh );
  97. } else if ( value && value.isScriptableValueNode ) {
  98. this.deleteParameter( name );
  99. parameters[ name ] = value;
  100. parameters[ name ].events.addEventListener( 'refresh', this.onRefresh );
  101. } else if ( parameters[ name ] === undefined ) {
  102. parameters[ name ] = scriptableValue( value );
  103. parameters[ name ].events.addEventListener( 'refresh', this.onRefresh );
  104. } else {
  105. parameters[ name ].value = value;
  106. }
  107. return this;
  108. }
  109. getValue() {
  110. return this.getDefaultOutput().getValue();
  111. }
  112. deleteParameter( name ) {
  113. let valueNode = this.parameters[ name ];
  114. if ( valueNode ) {
  115. if ( valueNode.isScriptableNode ) valueNode = valueNode.getDefaultOutput();
  116. valueNode.events.removeEventListener( 'refresh', this.onRefresh );
  117. }
  118. return this;
  119. }
  120. clearParameters() {
  121. for ( const name of Object.keys( this.parameters ) ) {
  122. this.deleteParameter( name );
  123. }
  124. this.needsUpdate = true;
  125. return this;
  126. }
  127. call( name, ...params ) {
  128. const object = this.getObject();
  129. const method = object[ name ];
  130. if ( typeof method === 'function' ) {
  131. return method( ...params );
  132. }
  133. }
  134. async callAsync( name, ...params ) {
  135. const object = this.getObject();
  136. const method = object[ name ];
  137. if ( typeof method === 'function' ) {
  138. return method.constructor.name === 'AsyncFunction' ? await method( ...params ) : method( ...params );
  139. }
  140. }
  141. getNodeType( builder ) {
  142. return this.getDefaultOutputNode().getNodeType( builder );
  143. }
  144. refresh( output = null ) {
  145. if ( output !== null ) {
  146. this.getOutput( output ).refresh();
  147. } else {
  148. this._refresh();
  149. }
  150. }
  151. getObject() {
  152. if ( this.needsUpdate ) this.dispose();
  153. if ( this._object !== null ) return this._object;
  154. //
  155. const refresh = () => this.refresh();
  156. const setOutput = ( id, value ) => this.setOutput( id, value );
  157. const parameters = new Parameters( this );
  158. const THREE = global.get( 'THREE' );
  159. const TSL = global.get( 'TSL' );
  160. const method = this.getMethod( this.codeNode );
  161. const params = [ parameters, this._local, global, refresh, setOutput, THREE, TSL ];
  162. this._object = method( ...params );
  163. const layout = this._object.layout;
  164. if ( layout ) {
  165. if ( layout.cache === false ) {
  166. this._local.clear();
  167. }
  168. // default output
  169. this._output.outputType = layout.outputType || null;
  170. if ( Array.isArray( layout.elements ) ) {
  171. for ( const element of layout.elements ) {
  172. const id = element.id || element.name;
  173. if ( element.inputType ) {
  174. if ( this.getParameter( id ) === undefined ) this.setParameter( id, null );
  175. this.getParameter( id ).inputType = element.inputType;
  176. }
  177. if ( element.outputType ) {
  178. if ( this.getOutput( id ) === undefined ) this.setOutput( id, null );
  179. this.getOutput( id ).outputType = element.outputType;
  180. }
  181. }
  182. }
  183. }
  184. return this._object;
  185. }
  186. deserialize( data ) {
  187. super.deserialize( data );
  188. for ( const name in this.parameters ) {
  189. let valueNode = this.parameters[ name ];
  190. if ( valueNode.isScriptableNode ) valueNode = valueNode.getDefaultOutput();
  191. valueNode.events.addEventListener( 'refresh', this.onRefresh );
  192. }
  193. }
  194. getLayout() {
  195. return this.getObject().layout;
  196. }
  197. getDefaultOutputNode() {
  198. const output = this.getDefaultOutput().value;
  199. if ( output && output.isNode ) {
  200. return output;
  201. }
  202. return float();
  203. }
  204. getDefaultOutput() {
  205. return this._exec()._output;
  206. }
  207. getMethod() {
  208. if ( this.needsUpdate ) this.dispose();
  209. if ( this._method !== null ) return this._method;
  210. //
  211. const parametersProps = [ 'parameters', 'local', 'global', 'refresh', 'setOutput', 'THREE', 'TSL' ];
  212. const interfaceProps = [ 'layout', 'init', 'main', 'dispose' ];
  213. const properties = interfaceProps.join( ', ' );
  214. const declarations = 'var ' + properties + '; var output = {};\n';
  215. const returns = '\nreturn { ...output, ' + properties + ' };';
  216. const code = declarations + this.codeNode.code + returns;
  217. //
  218. this._method = new Function( ...parametersProps, code );
  219. return this._method;
  220. }
  221. dispose() {
  222. if ( this._method === null ) return;
  223. if ( this._object && typeof this._object.dispose === 'function' ) {
  224. this._object.dispose();
  225. }
  226. this._method = null;
  227. this._object = null;
  228. this._source = null;
  229. this._value = null;
  230. this._needsOutputUpdate = true;
  231. this._output.value = null;
  232. this._outputs = {};
  233. }
  234. construct() {
  235. return this.getDefaultOutputNode();
  236. }
  237. set needsUpdate( value ) {
  238. if ( value === true ) this.dispose();
  239. }
  240. get needsUpdate() {
  241. return this.source !== this._source;
  242. }
  243. _exec() {
  244. if ( this.codeNode === null ) return this;
  245. if ( this._needsOutputUpdate === true ) {
  246. this._value = this.call( 'main' );
  247. this._needsOutputUpdate = false;
  248. }
  249. this._output.value = this._value;
  250. return this;
  251. }
  252. _refresh() {
  253. this.needsUpdate = true;
  254. this._exec();
  255. this._output.refresh();
  256. }
  257. }
  258. export default ScriptableNode;
  259. export const scriptable = nodeProxy( ScriptableNode );
  260. addNodeElement( 'scriptable', scriptable );
  261. addNodeClass( ScriptableNode );