LoopNode.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. import Node from '../core/Node.js';
  2. import { expression } from '../code/ExpressionNode.js';
  3. import { nodeObject, nodeArray, Fn } from '../tsl/TSLBase.js';
  4. /**
  5. * This module offers a variety of ways to implement loops in TSL. In it's basic form it's:
  6. * ```js
  7. * Loop( count, ( { i } ) => {
  8. *
  9. * } );
  10. * ```
  11. * However, it is also possible to define a start and end ranges, data types and loop conditions:
  12. * ```js
  13. * Loop( { start: int( 0 ), end: int( 10 ), type: 'int', condition: '<' }, ( { i } ) => {
  14. *
  15. * } );
  16. *```
  17. * Nested loops can be defined in a compacted form:
  18. * ```js
  19. * Loop( 10, 5, ( { i, j } ) => {
  20. *
  21. * } );
  22. * ```
  23. * Loops that should run backwards can be defined like so:
  24. * ```js
  25. * Loop( { start: 10 }, () => {} );
  26. * ```
  27. * It is possible to execute with boolean values, similar to the `while` syntax.
  28. * ```js
  29. * const value = float( 0 ).toVar();
  30. *
  31. * Loop( value.lessThan( 10 ), () => {
  32. *
  33. * value.addAssign( 1 );
  34. *
  35. * } );
  36. * ```
  37. * The module also provides `Break()` and `Continue()` TSL expression for loop control.
  38. * @augments Node
  39. */
  40. class LoopNode extends Node {
  41. static get type() {
  42. return 'LoopNode';
  43. }
  44. /**
  45. * Constructs a new loop node.
  46. *
  47. * @param {Array<any>} params - Depending on the loop type, array holds different parameterization values for the loop.
  48. */
  49. constructor( params = [] ) {
  50. super();
  51. this.params = params;
  52. }
  53. /**
  54. * Returns a loop variable name based on an index. The pattern is
  55. * `0` = `i`, `1`= `j`, `2`= `k` and so on.
  56. *
  57. * @param {number} index - The index.
  58. * @return {string} The loop variable name.
  59. */
  60. getVarName( index ) {
  61. return String.fromCharCode( 'i'.charCodeAt( 0 ) + index );
  62. }
  63. /**
  64. * Returns properties about this node.
  65. *
  66. * @param {NodeBuilder} builder - The current node builder.
  67. * @return {Object} The node properties.
  68. */
  69. getProperties( builder ) {
  70. const properties = builder.getNodeProperties( this );
  71. if ( properties.stackNode !== undefined ) return properties;
  72. //
  73. const inputs = {};
  74. for ( let i = 0, l = this.params.length - 1; i < l; i ++ ) {
  75. const param = this.params[ i ];
  76. const name = ( param.isNode !== true && param.name ) || this.getVarName( i );
  77. const type = ( param.isNode !== true && param.type ) || 'int';
  78. inputs[ name ] = expression( name, type );
  79. }
  80. const stack = builder.addStack(); // TODO: cache() it
  81. properties.returnsNode = this.params[ this.params.length - 1 ]( inputs, builder );
  82. properties.stackNode = stack;
  83. const baseParam = this.params[ 0 ];
  84. if ( baseParam.isNode !== true && typeof baseParam.update === 'function' ) {
  85. properties.updateNode = Fn( this.params[ 0 ].update )( inputs );
  86. }
  87. builder.removeStack();
  88. return properties;
  89. }
  90. /**
  91. * This method is overwritten since the node type is inferred based on the loop configuration.
  92. *
  93. * @param {NodeBuilder} builder - The current node builder.
  94. * @return {string} The node type.
  95. */
  96. getNodeType( builder ) {
  97. const { returnsNode } = this.getProperties( builder );
  98. return returnsNode ? returnsNode.getNodeType( builder ) : 'void';
  99. }
  100. setup( builder ) {
  101. // setup properties
  102. this.getProperties( builder );
  103. }
  104. generate( builder ) {
  105. const properties = this.getProperties( builder );
  106. const params = this.params;
  107. const stackNode = properties.stackNode;
  108. for ( let i = 0, l = params.length - 1; i < l; i ++ ) {
  109. const param = params[ i ];
  110. let isWhile = false, start = null, end = null, name = null, type = null, condition = null, update = null;
  111. if ( param.isNode ) {
  112. if ( param.getNodeType( builder ) === 'bool' ) {
  113. isWhile = true;
  114. type = 'bool';
  115. end = param.build( builder, type );
  116. } else {
  117. type = 'int';
  118. name = this.getVarName( i );
  119. start = '0';
  120. end = param.build( builder, type );
  121. condition = '<';
  122. }
  123. } else {
  124. type = param.type || 'int';
  125. name = param.name || this.getVarName( i );
  126. start = param.start;
  127. end = param.end;
  128. condition = param.condition;
  129. update = param.update;
  130. if ( typeof start === 'number' ) start = builder.generateConst( type, start );
  131. else if ( start && start.isNode ) start = start.build( builder, type );
  132. if ( typeof end === 'number' ) end = builder.generateConst( type, end );
  133. else if ( end && end.isNode ) end = end.build( builder, type );
  134. if ( start !== undefined && end === undefined ) {
  135. start = start + ' - 1';
  136. end = '0';
  137. condition = '>=';
  138. } else if ( end !== undefined && start === undefined ) {
  139. start = '0';
  140. condition = '<';
  141. }
  142. if ( condition === undefined ) {
  143. if ( Number( start ) > Number( end ) ) {
  144. condition = '>=';
  145. } else {
  146. condition = '<';
  147. }
  148. }
  149. }
  150. let loopSnippet;
  151. if ( isWhile ) {
  152. loopSnippet = `while ( ${ end } )`;
  153. } else {
  154. const internalParam = { start, end, condition };
  155. //
  156. const startSnippet = internalParam.start;
  157. const endSnippet = internalParam.end;
  158. let updateSnippet;
  159. const deltaOperator = () => condition.includes( '<' ) ? '+=' : '-=';
  160. if ( update !== undefined && update !== null ) {
  161. switch ( typeof update ) {
  162. case 'function':
  163. const flow = builder.flowStagesNode( properties.updateNode, 'void' );
  164. const snippet = flow.code.replace( /\t|;/g, '' );
  165. updateSnippet = snippet;
  166. break;
  167. case 'number':
  168. updateSnippet = name + ' ' + deltaOperator() + ' ' + builder.generateConst( type, update );
  169. break;
  170. case 'string':
  171. updateSnippet = name + ' ' + update;
  172. break;
  173. default:
  174. if ( update.isNode ) {
  175. updateSnippet = name + ' ' + deltaOperator() + ' ' + update.build( builder );
  176. } else {
  177. console.error( 'THREE.TSL: \'Loop( { update: ... } )\' is not a function, string or number.' );
  178. updateSnippet = 'break /* invalid update */';
  179. }
  180. }
  181. } else {
  182. if ( type === 'int' || type === 'uint' ) {
  183. update = condition.includes( '<' ) ? '++' : '--';
  184. } else {
  185. update = deltaOperator() + ' 1.';
  186. }
  187. updateSnippet = name + ' ' + update;
  188. }
  189. const declarationSnippet = builder.getVar( type, name ) + ' = ' + startSnippet;
  190. const conditionalSnippet = name + ' ' + condition + ' ' + endSnippet;
  191. loopSnippet = `for ( ${ declarationSnippet }; ${ conditionalSnippet }; ${ updateSnippet } )`;
  192. }
  193. builder.addFlowCode( ( i === 0 ? '\n' : '' ) + builder.tab + loopSnippet + ' {\n\n' ).addFlowTab();
  194. }
  195. const stackSnippet = stackNode.build( builder, 'void' );
  196. const returnsSnippet = properties.returnsNode ? properties.returnsNode.build( builder ) : '';
  197. builder.removeFlowTab().addFlowCode( '\n' + builder.tab + stackSnippet );
  198. for ( let i = 0, l = this.params.length - 1; i < l; i ++ ) {
  199. builder.addFlowCode( ( i === 0 ? '' : builder.tab ) + '}\n\n' ).removeFlowTab();
  200. }
  201. builder.addFlowTab();
  202. return returnsSnippet;
  203. }
  204. }
  205. export default LoopNode;
  206. /**
  207. * TSL function for creating a loop node.
  208. *
  209. * @tsl
  210. * @function
  211. * @param {...any} params - A list of parameters.
  212. * @returns {LoopNode}
  213. */
  214. export const Loop = ( ...params ) => nodeObject( new LoopNode( nodeArray( params, 'int' ) ) ).toStack();
  215. /**
  216. * TSL function for creating a `Continue()` expression.
  217. *
  218. * @tsl
  219. * @function
  220. * @returns {ExpressionNode}
  221. */
  222. export const Continue = () => expression( 'continue' ).toStack();
  223. /**
  224. * TSL function for creating a `Break()` expression.
  225. *
  226. * @tsl
  227. * @function
  228. * @returns {ExpressionNode}
  229. */
  230. export const Break = () => expression( 'break' ).toStack();
粤ICP备19079148号