StackNode.js 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  1. import Node from './Node.js';
  2. import StackTrace from '../core/StackTrace.js';
  3. import { select } from '../math/ConditionalNode.js';
  4. import { ShaderNode, nodeProxy, getCurrentStack, setCurrentStack, nodeObject } from '../tsl/TSLBase.js';
  5. import { error } from '../../utils.js';
  6. /**
  7. * Stack is a helper for Nodes that need to produce stack-based code instead of continuous flow.
  8. * They are usually needed in cases like `If`, `Else`.
  9. *
  10. * @augments Node
  11. */
  12. class StackNode extends Node {
  13. static get type() {
  14. return 'StackNode';
  15. }
  16. /**
  17. * Constructs a new stack node.
  18. *
  19. * @param {?StackNode} [parent=null] - The parent stack node.
  20. */
  21. constructor( parent = null ) {
  22. super();
  23. /**
  24. * List of nodes.
  25. *
  26. * @type {Array<Node>}
  27. */
  28. this.nodes = [];
  29. /**
  30. * The output node.
  31. *
  32. * @type {?Node}
  33. * @default null
  34. */
  35. this.outputNode = null;
  36. /**
  37. * The parent stack node.
  38. *
  39. * @type {?StackNode}
  40. * @default null
  41. */
  42. this.parent = parent;
  43. /**
  44. * The current conditional node.
  45. *
  46. * @private
  47. * @type {ConditionalNode}
  48. * @default null
  49. */
  50. this._currentCond = null;
  51. /**
  52. * The expression node. Only
  53. * relevant for Switch/Case.
  54. *
  55. * @private
  56. * @type {Node}
  57. * @default null
  58. */
  59. this._expressionNode = null;
  60. /**
  61. * The current node being processed.
  62. *
  63. * @private
  64. * @type {Node}
  65. * @default null
  66. */
  67. this._currentNode = null;
  68. /**
  69. * Stores additional data for nodes that are added to the stack.
  70. *
  71. * @private
  72. * @type {Map<Node, {delta: number}>}
  73. */
  74. this._nodeDataLibrary = new Map();
  75. /**
  76. * This flag can be used for type testing.
  77. *
  78. * @type {boolean}
  79. * @readonly
  80. * @default true
  81. */
  82. this.isStackNode = true;
  83. }
  84. getElementType( builder ) {
  85. return this.outputNode ? this.outputNode.getElementType( builder ) : 'void';
  86. }
  87. generateNodeType( builder ) {
  88. return this.outputNode ? this.outputNode.getNodeType( builder ) : 'void';
  89. }
  90. getMemberType( builder, name ) {
  91. return this.outputNode ? this.outputNode.getMemberType( builder, name ) : 'void';
  92. }
  93. /**
  94. * Adds a node to this stack.
  95. *
  96. * @param {Node} node - The node to add.
  97. * @param {number} [index=-1] - The index of the node. If not specified, the node will be added to the end of the stack.
  98. * @return {StackNode} A reference to this stack node.
  99. */
  100. addToStack( node, index = - 1 ) {
  101. if ( node.isNode !== true ) {
  102. error( 'TSL: Invalid node added to stack.', new StackTrace() );
  103. return this;
  104. }
  105. if ( index === - 1 ) {
  106. if ( this._currentNode ) {
  107. let nodeData = this._nodeDataLibrary.get( this._currentNode );
  108. if ( nodeData === undefined ) {
  109. nodeData = {
  110. delta: 0
  111. };
  112. this._nodeDataLibrary.set( this._currentNode, nodeData );
  113. }
  114. nodeData.delta ++;
  115. index = this.nodes.indexOf( this._currentNode ) + nodeData.delta;
  116. } else {
  117. index = this.nodes.length;
  118. }
  119. }
  120. this.nodes.splice( index, 0, node );
  121. return this;
  122. }
  123. /**
  124. * Adds a node to the stack before the current node.
  125. *
  126. * @param {Node} node - The node to add.
  127. * @return {StackNode} A reference to this stack node.
  128. */
  129. addToStackBefore( node ) {
  130. const index = this._currentNode !== null ? this.nodes.indexOf( this._currentNode ) : - 1;
  131. return this.addToStack( node, index );
  132. }
  133. /**
  134. * Represent an `if` statement in TSL.
  135. *
  136. * @param {Node} boolNode - Represents the condition.
  137. * @param {Function} method - TSL code which is executed if the condition evaluates to `true`.
  138. * @return {StackNode} A reference to this stack node.
  139. */
  140. If( boolNode, method ) {
  141. const methodNode = new ShaderNode( method );
  142. this._currentCond = select( boolNode, methodNode );
  143. return this.addToStack( this._currentCond );
  144. }
  145. /**
  146. * Represent an `elseif` statement in TSL.
  147. *
  148. * @param {Node} boolNode - Represents the condition.
  149. * @param {Function} method - TSL code which is executed if the condition evaluates to `true`.
  150. * @return {StackNode} A reference to this stack node.
  151. */
  152. ElseIf( boolNode, method ) {
  153. const methodNode = new ShaderNode( method );
  154. const ifNode = select( boolNode, methodNode );
  155. this._currentCond.elseNode = ifNode;
  156. this._currentCond = ifNode;
  157. return this;
  158. }
  159. /**
  160. * Represent an `else` statement in TSL.
  161. *
  162. * @param {Function} method - TSL code which is executed in the `else` case.
  163. * @return {StackNode} A reference to this stack node.
  164. */
  165. Else( method ) {
  166. this._currentCond.elseNode = new ShaderNode( method );
  167. return this;
  168. }
  169. /**
  170. * Represents a `switch` statement in TSL.
  171. *
  172. * @param {any} expression - Represents the expression.
  173. * @param {Function} method - TSL code which is executed if the condition evaluates to `true`.
  174. * @return {StackNode} A reference to this stack node.
  175. */
  176. Switch( expression ) {
  177. this._expressionNode = nodeObject( expression );
  178. return this;
  179. }
  180. /**
  181. * Represents a `case` statement in TSL. The TSL version accepts an arbitrary numbers of values.
  182. * The last parameter must be the callback method that should be executed in the `true` case.
  183. *
  184. * @param {...any} params - The values of the `Case()` statement as well as the callback method.
  185. * @return {StackNode} A reference to this stack node.
  186. */
  187. Case( ...params ) {
  188. const caseNodes = [];
  189. // extract case nodes from the parameter list
  190. if ( params.length >= 2 ) {
  191. for ( let i = 0; i < params.length - 1; i ++ ) {
  192. caseNodes.push( this._expressionNode.equal( nodeObject( params[ i ] ) ) );
  193. }
  194. } else {
  195. error( 'TSL: Invalid parameter length. Case() requires at least two parameters.', new StackTrace() );
  196. }
  197. // extract method
  198. const method = params[ params.length - 1 ];
  199. const methodNode = new ShaderNode( method );
  200. // chain multiple cases when using Case( 1, 2, 3, () => {} )
  201. let caseNode = caseNodes[ 0 ];
  202. for ( let i = 1; i < caseNodes.length; i ++ ) {
  203. caseNode = caseNode.or( caseNodes[ i ] );
  204. }
  205. // build condition
  206. const condNode = select( caseNode, methodNode );
  207. if ( this._currentCond === null ) {
  208. this._currentCond = condNode;
  209. return this.addToStack( this._currentCond );
  210. } else {
  211. this._currentCond.elseNode = condNode;
  212. this._currentCond = condNode;
  213. return this;
  214. }
  215. }
  216. /**
  217. * Represents the default code block of a Switch/Case statement.
  218. *
  219. * @param {Function} method - TSL code which is executed in the `else` case.
  220. * @return {StackNode} A reference to this stack node.
  221. */
  222. Default( method ) {
  223. this.Else( method );
  224. return this;
  225. }
  226. setup( builder ) {
  227. const nodeProperties = builder.getNodeProperties( this );
  228. let index = 0;
  229. for ( const childNode of this.getChildren() ) {
  230. if ( childNode.isVarNode && childNode.isIntent( builder ) ) {
  231. if ( childNode.isAssign( builder ) !== true ) {
  232. continue;
  233. }
  234. }
  235. nodeProperties[ 'node' + index ++ ] = childNode;
  236. }
  237. // return a outputNode if exists or null
  238. return nodeProperties.outputNode || null;
  239. }
  240. build( builder, ...params ) {
  241. const previousStack = getCurrentStack();
  242. const buildStage = builder.buildStage;
  243. setCurrentStack( this );
  244. builder.setActiveStack( this );
  245. //
  246. for ( let i = 0; i < this.nodes.length; i ++ ) {
  247. const node = this.nodes[ i ];
  248. const previousNode = this._currentNode;
  249. this._currentNode = node;
  250. if ( node.isVarNode && node.isIntent( builder ) ) {
  251. if ( node.isAssign( builder ) !== true ) {
  252. continue;
  253. }
  254. }
  255. if ( buildStage === 'setup' ) {
  256. node.build( builder );
  257. } else if ( buildStage === 'analyze' ) {
  258. node.build( builder, this );
  259. } else if ( buildStage === 'generate' ) {
  260. const stages = builder.getDataFromNode( node, 'any' ).stages;
  261. const parents = stages && stages[ builder.shaderStage ];
  262. if ( node.isVarNode && parents && parents.length === 1 && parents[ 0 ] && parents[ 0 ].isStackNode ) {
  263. continue; // skip var nodes that are only used in .toVarying()
  264. }
  265. node.build( builder, 'void' );
  266. }
  267. this._currentNode = previousNode;
  268. }
  269. //
  270. let result;
  271. if ( this.outputNode ) {
  272. const buildResult = this.outputNode.build( builder, ...params );
  273. if ( builder.buildStage !== 'generate' || this.outputNode.getNodeType( builder ) !== 'void' ) {
  274. result = buildResult;
  275. }
  276. } else {
  277. result = super.build( builder, ...params );
  278. }
  279. setCurrentStack( previousStack );
  280. builder.removeActiveStack( this );
  281. return result;
  282. }
  283. }
  284. export default StackNode;
  285. /**
  286. * TSL function for creating a stack node.
  287. *
  288. * @tsl
  289. * @function
  290. * @param {?StackNode} [parent=null] - The parent stack node.
  291. * @returns {StackNode}
  292. */
  293. export const stack = /*@__PURE__*/ nodeProxy( StackNode ).setParameterLength( 0, 1 );
粤ICP备19079148号