Skip to content

Commit 804d873

Browse files
🧩 Refactor(Workflow): 统一分支模型为 List<BranchArm> 并安全化 NextBlock
- 新增 BranchArm 类型(位于 Contract 层),取代 CFGStatement/FlowControlStatement 的 TrueBlockName/FalseBlockName/ToLoopCondReturnTo 三槽,改为通用 List<BranchArm> - 消灭 BP2CFGConverter 的位置映射 hack(normalArms[0]→True/[1]→False), 改为按输出 Pin 名直接写 Arm,N 路分支不再丢失 - 安全化 NextBlock:移除 BlockScriptExecutionGlobals 的 Set/Get 对 "NextBlock" 的特判,新增 internal AdvanceTo,Branch/Loop/Flip/ToLoopCond 改用它 - 适配全部转换器(BS2CFG/CFG2BS/CFG2CS/CFGConditionDuplicator/Executor)与 5 个控制流 builtin 描述符到 Arms 模型 - 新增整数索引 Switch 内置函数:Switch(selector, "default", "b0", "b1", ...), 越界走 default;Arms 布局 [Default, 0, 1, ..., N-1] - 修复 DataEdgeBuilder:ConditionPubVar 从硬编码 PinType.Boolean 改为首个 非 Exec pin,使 Switch 的 Integer selector 正确连接(BS→BP→BS 往返修复) - BlockScriptExecutor.Validate 改为遍历 Arms 校验,统一覆盖 Branch/Loop/Switch Breaking Changes: CFGStatement/FlowControlStatement 字段迁移 (TrueBlockName/FalseBlockName/ToLoopCondReturnTo → Arms),编译期暴露,无静默风险
1 parent efb4172 commit 804d873

23 files changed

Lines changed: 490 additions & 145 deletions

KitX Clients/KitX Workflow/KitX.Workflow/BlockScripting/BlockScriptExecutionGlobals.cs

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,25 @@ public partial class BlockScriptExecutionGlobals
2020
// ─── 内置属性 ───────────────────────────────────────────────
2121

2222
/// <summary>
23-
/// NextBlock 内置变量 - 设置后执行器会跳转到指定块
23+
/// NextBlock 内置变量 - 设置后执行器会跳转到指定块。
24+
/// 这是控制流的唯一载体。普通 <c>Set/Get</c> 不再识别 "NextBlock" 这个名字,
25+
/// 因此用户脚本无法通过变量赋值劫持控制流、绕过 Loop/Break 语义或 CFG 校验。
26+
/// 仅两类调用方可写此属性:(1) 生成的 <c>RunAsync</c> 主干(G.NextBlock = ...)——
27+
/// 它是可信基础设施,完全由 CFG2CSGenerator 按已校验的 CFG 产出;(2) 受信任的 flow
28+
/// 函数(Branch/Loop/Switch/Flip/ToLoopCond),应优先通过 <see cref="AdvanceTo"/> 写入。
2429
/// </summary>
2530
public string? NextBlock { get; set; }
2631

32+
/// <summary>
33+
/// 受信任的控制流改写入口。内置 flow 函数应通过此方法设置下一个块,
34+
/// 而非直接写 <see cref="NextBlock"/>,以保持单一改写路径。
35+
/// </summary>
36+
internal string? AdvanceTo(string? blockName)
37+
{
38+
NextBlock = blockName;
39+
return NextBlock;
40+
}
41+
2742
/// <summary>
2843
/// Number of blocks executed so far in the current run.
2944
/// </summary>
@@ -47,11 +62,11 @@ public BlockScriptExecutionGlobals(BlockScopeManager scopeManager, List<string>
4762

4863
/// <summary>
4964
/// Gets a variable value dynamically (CSharpScript path).
65+
/// "NextBlock" is no longer a recognised variable name — control flow is only mutated
66+
/// via <see cref="AdvanceTo"/> by trusted flow functions.
5067
/// </summary>
5168
public dynamic Get(string name)
5269
{
53-
if (name == "NextBlock")
54-
return NextBlock!;
5570
if (_variables.TryGetValue(name, out var value))
5671
return value!;
5772
return _scopeManager.ResolveVariable(name)!;
@@ -62,8 +77,6 @@ public dynamic Get(string name)
6277
/// </summary>
6378
public T? Get<T>(string name)
6479
{
65-
if (name == "NextBlock")
66-
return (T?)(object?)NextBlock;
6780
if (_variables.TryGetValue(name, out var value))
6881
return (T?)value;
6982
return (T?)_scopeManager.ResolveVariable(name);
@@ -74,11 +87,6 @@ public dynamic Get(string name)
7487
/// </summary>
7588
public void Set(string name, object? value)
7689
{
77-
if (name == "NextBlock")
78-
{
79-
NextBlock = value as string;
80-
return;
81-
}
8290
_variables[name] = value;
8391
_scopeManager.SetVariable(name, value, global: false);
8492
Debugger?.UpdateVariableSnapshot(GetAllVariables());
@@ -89,11 +97,6 @@ public void Set(string name, object? value)
8997
/// </summary>
9098
public void SetGlobalVariable(string name, object? value)
9199
{
92-
if (name == "NextBlock")
93-
{
94-
NextBlock = value as string;
95-
return;
96-
}
97100
_variables[name] = value;
98101
_scopeManager.SetVariable(name, value, global: true);
99102
Debugger?.UpdateVariableSnapshot(GetAllVariables());

KitX Clients/KitX Workflow/KitX.Workflow/BlockScripting/BlockScriptExecutor.cs

Lines changed: 6 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -273,38 +273,14 @@ public BlockScriptValidationResult Validate(BlockScript script)
273273
{
274274
if (statement is FlowControlStatement flow)
275275
{
276-
if (flow.ControlType == FlowControlType.Branch)
276+
// Validate every arm target exists. Works uniformly for Branch (True/False),
277+
// Loop (LoopBody/LoopEnd), ToLoopCond (Exec loopback) and Switch (Default/0/1/...).
278+
foreach (var arm in flow.Arms)
277279
{
278-
if (!string.IsNullOrEmpty(flow.TrueBlockName) &&
279-
!allBlockNames.Contains(flow.TrueBlockName))
280+
if (!string.IsNullOrEmpty(arm.TargetBlockName) &&
281+
!allBlockNames.Contains(arm.TargetBlockName))
280282
{
281-
result.AddError($"Block '{flow.TrueBlockName}' referenced in Branch at line {flow.LineNumber} does not exist");
282-
}
283-
if (!string.IsNullOrEmpty(flow.FalseBlockName) &&
284-
!allBlockNames.Contains(flow.FalseBlockName))
285-
{
286-
result.AddError($"Block '{flow.FalseBlockName}' referenced in Branch at line {flow.LineNumber} does not exist");
287-
}
288-
}
289-
else if (flow.ControlType == FlowControlType.Loop)
290-
{
291-
if (!string.IsNullOrEmpty(flow.TrueBlockName) &&
292-
!allBlockNames.Contains(flow.TrueBlockName))
293-
{
294-
result.AddError($"Block '{flow.TrueBlockName}' referenced in Loop at line {flow.LineNumber} does not exist");
295-
}
296-
if (!string.IsNullOrEmpty(flow.FalseBlockName) &&
297-
!allBlockNames.Contains(flow.FalseBlockName))
298-
{
299-
result.AddError($"Block '{flow.FalseBlockName}' referenced in Loop at line {flow.LineNumber} does not exist");
300-
}
301-
}
302-
else if (flow.ControlType == FlowControlType.ToLoopCond)
303-
{
304-
if (!string.IsNullOrEmpty(flow.ToLoopCondReturnTo) &&
305-
!allBlockNames.Contains(flow.ToLoopCondReturnTo))
306-
{
307-
result.AddError($"Block '{flow.ToLoopCondReturnTo}' referenced in ToLoopCond at line {flow.LineNumber} does not exist");
283+
result.AddError($"Block '{arm.TargetBlockName}' referenced in {flow.ControlType} (arm '{arm.PinName}') at line {flow.LineNumber} does not exist");
308284
}
309285
}
310286
}

KitX Clients/KitX Workflow/KitX.Workflow/BlockScripting/BlockScriptWellKnown.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,16 @@ public static class Pins
3131
public const string False = "False";
3232
public const string LoopBody = "LoopBody";
3333
public const string LoopEnd = "LoopEnd";
34+
public const string Default = "Default";
35+
public const string Selector = "Selector";
3436
}
3537

3638
/// <summary>Built-in flow control and action function names</summary>
3739
public static class Functions
3840
{
3941
public const string Branch = "Branch";
4042
public const string Loop = "Loop";
43+
public const string Switch = "Switch";
4144
public const string ToLoopCond = "ToLoopCond";
4245
public const string Break = "Break";
4346
public const string Print = "Print";

KitX Clients/KitX Workflow/KitX.Workflow/BlockScripting/IBuiltinFunctionDefinition.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,26 @@ public interface IBuiltinFunctionDefinition
6767
/// <summary>输出引脚描述</summary>
6868
IReadOnlyList<PinDescriptor> OutputPins { get; }
6969

70+
/// <summary>
71+
/// 输入侧变长端口配置。非 null 时,蓝图编辑器在该组最后一个输入端口被连接后,
72+
/// 自动追加一个 <see cref="VariadicPinSpec.PinType"/> 类型的新输入端口。
73+
/// 默认 null(非变长)。StringConcat 覆写为字符串变长输入。
74+
/// </summary>
75+
VariadicPinSpec? InputVariadic => null;
76+
77+
/// <summary>
78+
/// 输出侧变长端口配置。非 null 时,蓝图编辑器在该组最后一个输出端口被连接后,
79+
/// 自动追加一个新输出端口。默认 null(非变长)。Switch 覆写为执行流变长输出。
80+
/// </summary>
81+
VariadicPinSpec? OutputVariadic => null;
82+
83+
/// <summary>
84+
/// 按本语句的实际情况返回输出 Pin 描述符。默认返回固定 <see cref="OutputPins"/>(旧行为)。
85+
/// 变长输出节点(如 Switch,其输出 arm 数随语句而变)覆写此方法,按 <see cref="CFGStatement.Arms"/>
86+
/// 数量动态生成 [Default, 0, 1, ..., N-1],使 BS→BP 导入时端口数与 arm 数匹配。
87+
/// </summary>
88+
IReadOnlyList<PinDescriptor> GetOutputPinsFor(CFGStatement stmt) => OutputPins;
89+
7090
// ─── 解析(BlockScript → AST)─────────────────
7191

7292
/// <summary>

KitX Clients/KitX Workflow/KitX.Workflow/Blueprint/NodeRegistry.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,9 @@ public BlueprintNode CreateBuiltinFunctionNode(string functionName)
8989
var descriptor = new NodeDescriptor(
9090
def.NodeWidth, def.NodeHeight,
9191
def.InputPins, def.OutputPins,
92-
def.DisplayName
92+
def.DisplayName,
93+
def.InputVariadic,
94+
def.OutputVariadic
9395
);
9496
node.SetDescriptor(descriptor);
9597

KitX Clients/KitX Workflow/KitX.Workflow/BuiltinFunctions/BranchFunction.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,7 @@ public partial class BlockScriptExecutionGlobals
102102
/// </summary>
103103
public string? Branch(bool condition, string trueBlock, string falseBlock)
104104
{
105-
NextBlock = condition ? trueBlock : falseBlock;
106-
return NextBlock;
105+
return AdvanceTo(condition ? trueBlock : falseBlock);
107106
}
108107
}
109108
}

KitX Clients/KitX Workflow/KitX.Workflow/BuiltinFunctions/FlipFunction.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,7 @@ public partial class BlockScriptExecutionGlobals
108108
public string? Flip(string outputA, string outputB)
109109
{
110110
_flipCounter++;
111-
NextBlock = (_flipCounter % 2 == 1) ? outputA : outputB;
112-
return NextBlock;
111+
return AdvanceTo((_flipCounter % 2 == 1) ? outputA : outputB);
113112
}
114113

115114
/// <summary>

KitX Clients/KitX Workflow/KitX.Workflow/BuiltinFunctions/LoopFunction.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,7 @@ public partial class BlockScriptExecutionGlobals
106106
/// </summary>
107107
public string? Loop(bool condition, string trueBlock, string falseBlock)
108108
{
109-
NextBlock = condition ? trueBlock : falseBlock;
110-
return NextBlock;
109+
return AdvanceTo(condition ? trueBlock : falseBlock);
111110
}
112111
}
113112
}

KitX Clients/KitX Workflow/KitX.Workflow/BuiltinFunctions/StringConcatFunction.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@ public class StringConcatFunction : IBuiltinFunctionDefinition
3333
new("Result", PinType.String, 40),
3434
];
3535

36+
/// <summary>
37+
/// Input-side variadic growth: when the last String input is connected, the editor
38+
/// auto-appends a new "Input {N}" String pin. Declared here (on the descriptor) so the
39+
/// editor's generic variadic logic handles it instead of the former StringConcat name match.
40+
/// </summary>
41+
public VariadicPinSpec? InputVariadic => new("Input ", 3, PinType.String);
42+
3643
public BlockStatement? ExtractStatement(InvocationExpressionSyntax invoke, int lineNumber, string? exprText) => null;
3744

3845
public List<StatementSyntax> EmitStatements(CFGStatement stmt, CSEmitContext ctx)

0 commit comments

Comments
 (0)