77using Dynamo . Library ;
88using Newtonsoft . Json ;
99using ProtoCore . AST . AssociativeAST ;
10+ using ProtoCore . Utils ;
1011
1112namespace Dynamo . Graph . Nodes . ZeroTouch
1213{
@@ -99,25 +100,6 @@ internal override bool HandleModelEventCore(string eventName, int value, UndoRed
99100 [ JsonIgnore ]
100101 public VariableInputNodeController VarInputController { get ; private set ; }
101102
102- /// <summary>
103- /// Scopes <see cref="NodeModel.UseLevelAndReplicationGuide"/> to the non-variadic
104- /// prefix of this node's inputs.
105- /// </summary>
106- /// <remarks>
107- /// Variadic zero-touch functions pack their tail inputs into a single
108- /// <c>ExprListNode</c> argument before <see cref="NodeModel.UseLevelAndReplicationGuide"/>
109- /// runs. AtLevel / replication-guide annotations on an <c>ExprListNode</c> are inert
110- /// at runtime, so per-port Use Levels and lacing on the variadic slots must be applied
111- /// pre-pack inside <c>ZeroTouchVarArgNodeController.BuildOutputAst</c>. This override
112- /// limits the post-pack pass to the non-variadic prefix so the packed variadic slot
113- /// is not touched twice.
114- /// </remarks>
115- protected override int LevelAndReplicationGuideInputCount ( int rawInputCount )
116- {
117- int prefix = Controller . Definition . Parameters . Count ( ) - 1 ;
118- return prefix < 0 ? 0 : prefix ;
119- }
120-
121103 #region VarInput Controller
122104 private sealed class ZeroTouchVarInputController : VariableInputNodeController
123105 {
@@ -185,99 +167,109 @@ protected override void InitializeFunctionParameters(NodeModel model, IEnumerabl
185167
186168 protected override void BuildOutputAst ( NodeModel model , List < AssociativeNode > inputAstNodes , List < AssociativeNode > resultAst )
187169 {
188- // All inputs are provided, then we should pack all inputs that
189- // belong to variable input parameter into a single array.
170+ // A variadic zero-touch function foo(x1, ..., xn, params y) is invoked at
171+ // runtime as foo(x1, ..., xn, {y1, ..., ym}) -- every variadic input is
172+ // packed into a single array argument. Suppose paramCount == n + 1 and the
173+ // node inputs are i1, ..., in, y1, ..., ym.
190174 if ( ! model . IsPartiallyApplied )
191175 {
192- var paramCount = Definition . Parameters . Count ( ) ;
176+ int variadicStart = Definition . Parameters . Count ( ) - 1 ;
193177
194- // Suppose a function foo() with var args, its signature is:
195- //
196- // foo(x1, x2, ..., xn, params y)
197- //
198- // so paramCount == n + 1 here, and suppose inputs are
199- //
200- // i1, i2, ...., in, y1, y2, ..., ym
201- //
202- // Why: per-port Use Levels and lacing-driven replication guides
203- // on the variadic inputs (y1..ym) must be applied *before* we
204- // pack them into an ExprListNode. AtLevel / replication-guide
205- // annotations carried inside an ExprListNode are inert at runtime,
206- // so packing first would silently drop per-port settings on every
207- // variadic port (the original DYN-10572 defect). The non-variadic
208- // prefix (i1..in) is handled by the post-pack
209- // NodeModel.UseLevelAndReplicationGuide pass, which DSVarArgFunction
210- // scopes to indices [0, paramCount - 1) via the
211- // LevelAndReplicationGuideInputCount override -- preventing the
212- // packed slot from being wrapped twice.
213- int variadicStart = paramCount - 1 ;
214- if ( variadicStart >= 0 && variadicStart < inputAstNodes . Count )
178+ // When any port uses levels, per-port replication must be honored. AtLevel /
179+ // replication-guide annotations are only consumed at function-call argument
180+ // positions and are inert inside the packed ExprList literal, and DesignScript
181+ // cannot call a params method with separate arguments. So we route through a
182+ // generated wrapper whose formal parameters are the individual ports: the
183+ // per-port AtLevel and the node's lacing replication guides land on the
184+ // wrapper-call arguments (replicating exactly like a non-variadic node), and
185+ // the wrapper body packs the already-replicated values per invocation. See
186+ // DYN-10572. Graphs with no Use Levels keep the original pack-then-call path,
187+ // so their behavior (including their lacing) is unchanged.
188+ if ( variadicStart >= 0 && variadicStart < inputAstNodes . Count
189+ && AnyPortUsesLevels ( model , inputAstNodes . Count )
190+ && TryBuildReplicatingOutputAst ( model , inputAstNodes , resultAst , variadicStart ) )
215191 {
216- for ( int i = variadicStart ; i < inputAstNodes . Count ; i ++ )
217- {
218- if ( model . InPorts [ i ] . UseLevels )
219- {
220- inputAstNodes [ i ] = AstFactory . AddAtLevel (
221- inputAstNodes [ i ] ,
222- - model . InPorts [ i ] . Level ,
223- model . InPorts [ i ] . KeepListStructure ) ;
224- }
225- }
192+ return ;
193+ }
226194
227- switch ( model . ArgumentLacing )
228- {
229- case LacingStrategy . Auto :
230- for ( int i = variadicStart ; i < inputAstNodes . Count ; i ++ )
231- {
232- if ( model . InPorts [ i ] . UseLevels )
233- {
234- inputAstNodes [ i ] = AstFactory . AddReplicationGuide (
235- inputAstNodes [ i ] , new List < int > { 1 } , false ) ;
236- }
237- }
238- break ;
195+ // Default path: pack all var arguments into an array {y1, ..., ym}
196+ // (skipping the first n == paramCount - 1 inputs).
197+ var argPack = AstFactory . BuildExprList ( inputAstNodes . Skip ( variadicStart ) . ToList ( ) ) ;
198+ inputAstNodes = inputAstNodes . Take ( variadicStart ) . ToList ( ) ;
199+ inputAstNodes . Add ( argPack ) ;
200+ }
239201
240- case LacingStrategy . Shortest :
241- for ( int i = variadicStart ; i < inputAstNodes . Count ; i ++ )
242- {
243- inputAstNodes [ i ] = AstFactory . AddReplicationGuide (
244- inputAstNodes [ i ] , new List < int > { 1 } , false ) ;
245- }
246- break ;
202+ base . BuildOutputAst ( model , inputAstNodes , resultAst ) ;
203+ }
247204
248- case LacingStrategy . Longest :
249- for ( int i = variadicStart ; i < inputAstNodes . Count ; i ++ )
250- {
251- inputAstNodes [ i ] = AstFactory . AddReplicationGuide (
252- inputAstNodes [ i ] , new List < int > { 1 } , true ) ;
253- }
254- break ;
205+ /// <summary>
206+ /// Returns true when any port has Use Levels enabled. Those settings cannot survive
207+ /// the default pack-then-call path (they are inert inside the packed ExprList), so the
208+ /// node must route through the per-port replicating wrapper instead.
209+ /// </summary>
210+ private static bool AnyPortUsesLevels ( NodeModel model , int inputCount )
211+ {
212+ for ( int i = 0 ; i < inputCount ; i ++ )
213+ {
214+ if ( model . InPorts [ i ] . UseLevels )
215+ return true ;
216+ }
217+ return false ;
218+ }
255219
256- case LacingStrategy . CrossProduct :
257- // Continue cross-product indices from where the prefix
258- // pass would have stopped: the prefix uses guides
259- // 1..paramCount-1, so the first variadic port starts at
260- // paramCount and each subsequent variadic port gets a
261- // distinct rank.
262- int guide = paramCount ;
263- for ( int i = variadicStart ; i < inputAstNodes . Count ; i ++ )
264- {
265- inputAstNodes [ i ] = AstFactory . AddReplicationGuide (
266- inputAstNodes [ i ] , new List < int > { guide } , false ) ;
267- guide ++ ;
268- }
269- break ;
270- }
271- }
220+ /// <summary>
221+ /// Emits a generated wrapper function that exposes every port as a separate formal
222+ /// parameter and packs the variadic tail per invocation, then calls it with per-port
223+ /// Use Levels and lacing replication guides applied to the individual arguments.
224+ /// Returns false (leaving <paramref name="resultAst"/> untouched) if the wrapper
225+ /// source cannot be parsed, so the caller falls back to the default pack path.
226+ /// </summary>
227+ private bool TryBuildReplicatingOutputAst (
228+ NodeModel model , List < AssociativeNode > inputAstNodes , List < AssociativeNode > resultAst , int variadicStart )
229+ {
230+ // Build the call to the underlying function exactly as GetFunctionApplication
231+ // would: Class.Function for member functions, the bare name for globals.
232+ string callName = Definition . Type == FunctionType . GenericFunction
233+ ? Definition . FunctionName
234+ : Definition . ClassName + "." + Definition . FunctionName ;
272235
273- // Here we pack all var arguments in an array {y1, y2, ..., ym}
274- // (skipping the first n == paramCount - 1 inputs)
275- var argPack = AstFactory . BuildExprList ( inputAstNodes . Skip ( paramCount - 1 ) . ToList ( ) ) ;
276- inputAstNodes = inputAstNodes . Take ( paramCount - 1 ) . ToList ( ) ;
277- inputAstNodes . Add ( argPack ) ;
236+ // Unique, deterministic per node + arity so repeated runs redefine the same
237+ // wrapper (cache-stable) and distinct nodes never collide.
238+ string wrapperName = "__vararg_" + model . GUID . ToString ( "N" ) + "_" + inputAstNodes . Count ;
239+
240+ var paramNames = Enumerable . Range ( 0 , inputAstNodes . Count ) . Select ( i => "p" + i ) . ToList ( ) ;
241+ var prefixArgs = paramNames . Take ( variadicStart ) ;
242+ var packedArg = "[" + string . Join ( ", " , paramNames . Skip ( variadicStart ) ) + "]" ;
243+ var innerArgs = string . Join ( ", " , prefixArgs . Append ( packedArg ) ) ;
244+
245+ string wrapperSource =
246+ "def " + wrapperName + "(" + string . Join ( ", " , paramNames ) + ")" +
247+ " { return = " + callName + "(" + innerArgs + "); }" ;
248+
249+ FunctionDefinitionNode wrapperDef ;
250+ try
251+ {
252+ wrapperDef = ParserUtils . Parse ( wrapperSource ) . Body
253+ . OfType < FunctionDefinitionNode > ( )
254+ . FirstOrDefault ( ) ;
255+ }
256+ catch
257+ {
258+ return false ;
278259 }
279260
280- base . BuildOutputAst ( model , inputAstNodes , resultAst ) ;
261+ if ( wrapperDef == null )
262+ return false ;
263+
264+ resultAst . Add ( wrapperDef ) ;
265+
266+ // Per-port AtLevel / lacing replication guides land on the wrapper-call
267+ // arguments, where they are honored just as on a non-variadic node.
268+ model . UseLevelAndReplicationGuide ( inputAstNodes ) ;
269+
270+ var rhs = AstFactory . BuildFunctionCall ( wrapperName , inputAstNodes ) ;
271+ AssignIdentifiersForFunctionCall ( model , rhs , resultAst ) ;
272+ return true ;
281273 }
282274 }
283275}
0 commit comments