@@ -8,6 +8,20 @@ import {
88 createStrandsShaderNameState ,
99 getOrCreateInternalShaderName
1010} from './strands_names' ;
11+
12+ // Strands functions opt out of array-to-vector conversion by declaring
13+ // `argTypes` on themselves (see strands_api.js's `paletteLerp` for an
14+ // example), rather than being special-cased here. A descriptor of
15+ // `{ type: 'Array', subtype: { type: 'any' } }` marks an argument as an
16+ // opaque array: the ArrayExpression visitor leaves it (and anything
17+ // nested inside it, to any depth) untouched instead of converting it to
18+ // a vector/strandsNode call. This lets any current or future function —
19+ // including ones added by addons — take raw array arguments without
20+ // touching this file again.
21+ function getArgTypes ( fnName , p5 ) {
22+ return p5 ?. prototype ?. [ fnName ] ?. argTypes ;
23+ }
24+
1125let blockVarCounter = 0 ;
1226let loopVarCounter = 0 ;
1327function replaceBinaryOperator ( codeSource ) {
@@ -660,8 +674,11 @@ const ASTCallbacks = {
660674 node . arguments = [ ] ;
661675 }
662676 } ,
677+
663678 // The callbacks for AssignmentExpression and BinaryExpression handle
664679 // operator overloading including +=, *= assignment expressions
680+
681+
665682 ArrayExpression ( node , state , ancestors ) {
666683 if (
667684 ancestors . some (
@@ -672,6 +689,52 @@ const ASTCallbacks = {
672689 ) {
673690 return ;
674691 }
692+ // Don't wrap arrays that are (or are nested inside) an argument a
693+ // function has declared as opaque via argTypes. We look this up
694+ // structurally — via the nearest enclosing CallExpression and which
695+ // of its arguments node descends from — rather than depending on
696+ // that CallExpression's own visitor having already run, since
697+ // acorn-walk's ancestor() calls child callbacks before parent ones.
698+ //
699+ // Opacity is depth-scoped, not blanket: argType descriptors nest via
700+ // `.subtype`, one level per ArrayExpression descended through from
701+ // the top-level argument down to this node. We walk that chain in
702+ // lockstep with the descriptor so a function can mark the outer
703+ // array and some number of inner levels as opaque (`type: 'Array'`)
704+ // while still letting arrays beyond that depth resume normal
705+ // vectorization — signaled by the descriptor bottoming out at the
706+ // bare string `'any'` (or running out) before reaching this node's
707+ // depth.
708+ for ( let i = ancestors . length - 1 ; i >= 0 ; i -- ) {
709+ const a = ancestors [ i ] ;
710+ if ( a . type === 'CallExpression' ) {
711+ const fnName = a . callee ?. type === 'Identifier'
712+ ? a . callee . name
713+ : a . callee ?. type === 'MemberExpression' && a . callee . property ?. type === 'Identifier'
714+ ? a . callee . property . name
715+ : undefined ;
716+ const argTypes = fnName && getArgTypes ( fnName , state . p5 ) ;
717+ if ( argTypes ) {
718+ // The chain of nodes from the top-level argument down to (and
719+ // including) this array.
720+ // ancestors already ends with `node` itself (acorn-walk, in
721+ // this codebase, includes the current node as its own last
722+ // ancestor entry) — so slicing from i + 1 already gives the
723+ // full chain from the top-level argument down to and
724+ // including `node`, with no need to append it again.
725+ const path = ancestors . slice ( i + 1 ) ;
726+ const argIndex = a . arguments . indexOf ( path [ 0 ] ) ;
727+ let argType = argIndex !== - 1 ? argTypes [ argIndex ] : undefined ;
728+ for ( let depth = 1 ; depth < path . length && argType ; depth ++ ) {
729+ argType = argType . subtype ;
730+ }
731+ if ( argType ?. type === 'Array' ) {
732+ return ;
733+ }
734+ }
735+ break ; // only consider the nearest enclosing call
736+ }
737+ }
675738
676739 if ( node . elements . length < 2 || node . elements . length > 4 ) {
677740 FES . userError (
@@ -1919,7 +1982,8 @@ function makeGuardedCallbacks(callbacks) {
19191982function runNonControlFlowPass (
19201983 ast ,
19211984 uniformCallbackNames ,
1922- initialShaderNameState
1985+ initialShaderNameState ,
1986+ p5
19231987) {
19241988 const nonControlFlowCallbacks = { ...ASTCallbacks } ;
19251989 delete nonControlFlowCallbacks . IfStatement ;
@@ -1930,7 +1994,8 @@ function runNonControlFlowPass(
19301994 shaderNameMap : createStrandsShaderNameMap ( ) ,
19311995 shaderNameState : createStrandsShaderNameState (
19321996 initialShaderNameState ?. nextSuffix || 0
1933- )
1997+ ) ,
1998+ p5
19341999 } ;
19352000 ancestor ( ast , nonControlFlowCallbacks , undefined , state ) ;
19362001 return {
@@ -2065,7 +2130,8 @@ export function transpileStrandsToJS(
20652130 const { shaderNameMap, shaderNameState } = runNonControlFlowPass (
20662131 ast ,
20672132 uniformCallbackNames ,
2068- initialShaderNameState
2133+ initialShaderNameState ,
2134+ p5
20692135 ) ;
20702136
20712137 // Pass 3: transform helper functions with early returns to use __returnValue pattern
@@ -2080,4 +2146,4 @@ export function transpileStrandsToJS(
20802146 shaderNameMap,
20812147 shaderNameState
20822148 } ;
2083- }
2149+ }
0 commit comments