| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353 |
- import Node from '../core/Node.js';
- import { expression } from '../code/ExpressionNode.js';
- import { nodeObject, nodeArray, Fn } from '../tsl/TSLBase.js';
- /**
- * This module offers a variety of ways to implement loops in TSL. In it's basic form it's:
- * ```js
- * Loop( count, ( { i } ) => {
- *
- * } );
- * ```
- * However, it is also possible to define a start and end ranges, data types and loop conditions:
- * ```js
- * Loop( { start: int( 0 ), end: int( 10 ), type: 'int', condition: '<' }, ( { i } ) => {
- *
- * } );
- *```
- * Nested loops can be defined in a compacted form:
- * ```js
- * Loop( 10, 5, ( { i, j } ) => {
- *
- * } );
- * ```
- * Loops that should run backwards can be defined like so:
- * ```js
- * Loop( { start: 10 }, () => {} );
- * ```
- * It is possible to execute with boolean values, similar to the `while` syntax.
- * ```js
- * const value = float( 0 ).toVar();
- *
- * Loop( value.lessThan( 10 ), () => {
- *
- * value.addAssign( 1 );
- *
- * } );
- * ```
- * The module also provides `Break()` and `Continue()` TSL expression for loop control.
- * @augments Node
- */
- class LoopNode extends Node {
- static get type() {
- return 'LoopNode';
- }
- /**
- * Constructs a new loop node.
- *
- * @param {Array<any>} params - Depending on the loop type, array holds different parameterization values for the loop.
- */
- constructor( params = [] ) {
- super();
- this.params = params;
- }
- /**
- * Returns a loop variable name based on an index. The pattern is
- * `0` = `i`, `1`= `j`, `2`= `k` and so on.
- *
- * @param {number} index - The index.
- * @return {string} The loop variable name.
- */
- getVarName( index ) {
- return String.fromCharCode( 'i'.charCodeAt( 0 ) + index );
- }
- /**
- * Returns properties about this node.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {Object} The node properties.
- */
- getProperties( builder ) {
- const properties = builder.getNodeProperties( this );
- if ( properties.stackNode !== undefined ) return properties;
- //
- const inputs = {};
- for ( let i = 0, l = this.params.length - 1; i < l; i ++ ) {
- const param = this.params[ i ];
- const name = ( param.isNode !== true && param.name ) || this.getVarName( i );
- const type = ( param.isNode !== true && param.type ) || 'int';
- inputs[ name ] = expression( name, type );
- }
- const stack = builder.addStack(); // TODO: cache() it
- properties.returnsNode = this.params[ this.params.length - 1 ]( inputs, builder );
- properties.stackNode = stack;
- const baseParam = this.params[ 0 ];
- if ( baseParam.isNode !== true && typeof baseParam.update === 'function' ) {
- properties.updateNode = Fn( this.params[ 0 ].update )( inputs );
- }
- builder.removeStack();
- return properties;
- }
- /**
- * This method is overwritten since the node type is inferred based on the loop configuration.
- *
- * @param {NodeBuilder} builder - The current node builder.
- * @return {string} The node type.
- */
- getNodeType( builder ) {
- const { returnsNode } = this.getProperties( builder );
- return returnsNode ? returnsNode.getNodeType( builder ) : 'void';
- }
- setup( builder ) {
- // setup properties
- this.getProperties( builder );
- }
- generate( builder ) {
- const properties = this.getProperties( builder );
- const params = this.params;
- const stackNode = properties.stackNode;
- for ( let i = 0, l = params.length - 1; i < l; i ++ ) {
- const param = params[ i ];
- let isWhile = false, start = null, end = null, name = null, type = null, condition = null, update = null;
- if ( param.isNode ) {
- if ( param.getNodeType( builder ) === 'bool' ) {
- isWhile = true;
- type = 'bool';
- end = param.build( builder, type );
- } else {
- type = 'int';
- name = this.getVarName( i );
- start = '0';
- end = param.build( builder, type );
- condition = '<';
- }
- } else {
- type = param.type || 'int';
- name = param.name || this.getVarName( i );
- start = param.start;
- end = param.end;
- condition = param.condition;
- update = param.update;
- if ( typeof start === 'number' ) start = builder.generateConst( type, start );
- else if ( start && start.isNode ) start = start.build( builder, type );
- if ( typeof end === 'number' ) end = builder.generateConst( type, end );
- else if ( end && end.isNode ) end = end.build( builder, type );
- if ( start !== undefined && end === undefined ) {
- start = start + ' - 1';
- end = '0';
- condition = '>=';
- } else if ( end !== undefined && start === undefined ) {
- start = '0';
- condition = '<';
- }
- if ( condition === undefined ) {
- if ( Number( start ) > Number( end ) ) {
- condition = '>=';
- } else {
- condition = '<';
- }
- }
- }
- let loopSnippet;
- if ( isWhile ) {
- loopSnippet = `while ( ${ end } )`;
- } else {
- const internalParam = { start, end, condition };
- //
- const startSnippet = internalParam.start;
- const endSnippet = internalParam.end;
- let updateSnippet;
- const deltaOperator = () => condition.includes( '<' ) ? '+=' : '-=';
- if ( update !== undefined && update !== null ) {
- switch ( typeof update ) {
- case 'function':
- const flow = builder.flowStagesNode( properties.updateNode, 'void' );
- const snippet = flow.code.replace( /\t|;/g, '' );
- updateSnippet = snippet;
- break;
- case 'number':
- updateSnippet = name + ' ' + deltaOperator() + ' ' + builder.generateConst( type, update );
- break;
- case 'string':
- updateSnippet = name + ' ' + update;
- break;
- default:
- if ( update.isNode ) {
- updateSnippet = name + ' ' + deltaOperator() + ' ' + update.build( builder );
- } else {
- console.error( 'THREE.TSL: \'Loop( { update: ... } )\' is not a function, string or number.' );
- updateSnippet = 'break /* invalid update */';
- }
- }
- } else {
- if ( type === 'int' || type === 'uint' ) {
- update = condition.includes( '<' ) ? '++' : '--';
- } else {
- update = deltaOperator() + ' 1.';
- }
- updateSnippet = name + ' ' + update;
- }
- const declarationSnippet = builder.getVar( type, name ) + ' = ' + startSnippet;
- const conditionalSnippet = name + ' ' + condition + ' ' + endSnippet;
- loopSnippet = `for ( ${ declarationSnippet }; ${ conditionalSnippet }; ${ updateSnippet } )`;
- }
- builder.addFlowCode( ( i === 0 ? '\n' : '' ) + builder.tab + loopSnippet + ' {\n\n' ).addFlowTab();
- }
- const stackSnippet = stackNode.build( builder, 'void' );
- const returnsSnippet = properties.returnsNode ? properties.returnsNode.build( builder ) : '';
- builder.removeFlowTab().addFlowCode( '\n' + builder.tab + stackSnippet );
- for ( let i = 0, l = this.params.length - 1; i < l; i ++ ) {
- builder.addFlowCode( ( i === 0 ? '' : builder.tab ) + '}\n\n' ).removeFlowTab();
- }
- builder.addFlowTab();
- return returnsSnippet;
- }
- }
- export default LoopNode;
- /**
- * TSL function for creating a loop node.
- *
- * @tsl
- * @function
- * @param {...any} params - A list of parameters.
- * @returns {LoopNode}
- */
- export const Loop = ( ...params ) => nodeObject( new LoopNode( nodeArray( params, 'int' ) ) ).toStack();
- /**
- * TSL function for creating a `Continue()` expression.
- *
- * @tsl
- * @function
- * @returns {ExpressionNode}
- */
- export const Continue = () => expression( 'continue' ).toStack();
- /**
- * TSL function for creating a `Break()` expression.
- *
- * @tsl
- * @function
- * @returns {ExpressionNode}
- */
- export const Break = () => expression( 'break' ).toStack();
|