NodeScript+ 导入了个 UI Extend
Signed-off-by: TRADER_FOER <lhf190@outlook.com>
This commit is contained in:
455
Assets/Scripts/Editor Tools/NodeScript/Node重构大纲.md
Normal file
455
Assets/Scripts/Editor Tools/NodeScript/Node重构大纲.md
Normal file
@@ -0,0 +1,455 @@
|
||||
# NodeScript 重构大纲
|
||||
|
||||
## 一、重构目标
|
||||
|
||||
将节点系统从**推送式(push-based)求值**改为**Manager 集中控制的拉取式(pull-based)生命周期循环**。不改 UI 层,聚焦于 NodeManager 对节点的调度逻辑和节点自身的行为表现。
|
||||
|
||||
---
|
||||
|
||||
## 二、核心架构变更
|
||||
|
||||
### 2.1 节点生命周期状态
|
||||
|
||||
为 `NodeBase` 新增状态枚举:
|
||||
|
||||
```csharp
|
||||
enum NodeStatus { Ready, Hang, Complete }
|
||||
```
|
||||
|
||||
- **Ready** — 节点等待被触发执行
|
||||
- **Hang** — 节点因前置节点未完成而挂起,保留在运行时表中等待下一周期
|
||||
- **Complete** — 节点已完成本轮运算
|
||||
|
||||
变量节点的特殊规则:不存在 Hang 状态,启动后立即返回值/引用并 Complete。
|
||||
|
||||
### 2.2 触发表 + 运行时表(Manager 侧)
|
||||
|
||||
Manager 维护两张表:
|
||||
|
||||
| 表 | 作用 |
|
||||
|---|---|
|
||||
| **触发表** `triggerTable` | 收集本周期要加入运行的节点,周期开始前并入运行时表 |
|
||||
| **运行时表** `runtimeTable` | 当前周期正在遍历的节点集合 |
|
||||
|
||||
周期流程:
|
||||
1. `triggerTable` → 并入 `runtimeTable`,清空 `triggerTable`
|
||||
2. 遍历 `runtimeTable`,调用每个节点的 `Loop()` 方法
|
||||
3. `Loop()` 返回:下一轮要触发的节点(加入 `triggerTable`)+ 是否从 `runtimeTable` 移除自己
|
||||
|
||||
### 2.3 拉取式取值
|
||||
|
||||
节点不再通过 Output → Input 推送数据。改为:
|
||||
- 每个周期节点从前置节点的 Output 中**主动拉取**值或引用
|
||||
- 如果前置节点不处于 `Complete`,本节点 `Status = Hang`,将前置节点加入 `triggerTable`,自己保留在 `runtimeTable`
|
||||
- 取值条件满足后,执行运算,返回 `Complete`
|
||||
|
||||
### 2.4 最短距离 L(单向逻辑保证)
|
||||
|
||||
每个节点计算到 Start 节点的最短距离 `L`,用于约束连线方向:
|
||||
- L=0 的节点的输出可以连接到 L=7 的节点的输入
|
||||
- L=7 的节点的输出**不能**连接到 L=2 的节点的输入
|
||||
- 确保逻辑单向流动
|
||||
|
||||
---
|
||||
|
||||
## 三、文件级重构计划
|
||||
|
||||
### 3.1 NodeCore.cs — 核心类型重定义
|
||||
|
||||
| 变更项 | 说明 |
|
||||
|---|---|
|
||||
| 移除 `Input<T>.Notify()` / `Output<T>.SetValue()` 推送链 | 不再需要推送机制 |
|
||||
| `Input<T>` 改为存储对上游 `Output<T>` 的引用,通过 `Pull()` 取值 | 拉取模式 |
|
||||
| `NodeBase` 新增 `NodeStatus Status` 属性 | 生命周期状态 |
|
||||
| `NodeBase` 新增抽象方法 `Loop()` | 替代 `Evaluate()`,返回 `(List<NodeBase> triggers, bool removeFromRuntime)` |
|
||||
| `NodeBase` 新增 `int L` 属性 | 到 Start 节点的最短距离 |
|
||||
| `NodeBase` 新增 `List<NodeBase> GetPrecedingNodes()` | 获取所有前置依赖节点 |
|
||||
| 保留 `IInput` / `IOutput` 接口 | UI 层依赖不变 |
|
||||
| 保留 `Signal` 结构体 | 仅用于触发操作 |
|
||||
| 新增 `InputAny` 概念(见 3.4) | 动态类型支持 |
|
||||
|
||||
### 3.2 NodeManager.cs — 生命周期调度
|
||||
|
||||
| 变更项 | 说明 |
|
||||
|---|---|
|
||||
| 新增 `HashSet<NodeBase> triggerTable` | 触发表 |
|
||||
| 新增 `HashSet<NodeBase> runtimeTable` | 运行时表 |
|
||||
| 新增 `void RunCycle()` | 单周期执行逻辑 |
|
||||
| 重写 `RunGraph()` | 初始化触发表为 Start/Entry 节点,循环调用 `RunCycle()` 直到运行时表为空 |
|
||||
| 新增 `void ComputeLValues()` | 在连线变更后重新计算所有节点的最短距离 L |
|
||||
| 新增 `bool ValidateConnection(NodeBase from, NodeBase to)` | 连线前验证 L 约束 |
|
||||
| 新增子控制器管理 | 用于循环节点和子函数节点的内部运行监控 |
|
||||
| 新增 `void RegisterSubFunction(NodeBase definition)` | 扫描注册子函数定义 |
|
||||
| `TryConnect()` 中增加 L 约束检查 | 阻止反向连线 |
|
||||
| `SaveToFile()` / `LoadFromFile()` 适配新状态 | 保存/加载兼容 |
|
||||
| UI 部分(拖线、复制粘贴等)保持不变 | — |
|
||||
|
||||
### 3.3 NodeObject.cs / ConnectorSlot.cs / NodeUIBuilder.cs — UI 层
|
||||
|
||||
**原则上不修改**,仅可能的微调:
|
||||
- `NodeObject.Init()` 中调用 `nodeBase.InitConnectors()` 后触发 L 值计算
|
||||
- 循环/子函数节点的 Rect 容器支持(见 3.6)
|
||||
|
||||
### 3.4 动态类型 — InputAny / OutputAny 机制(多类型统一节点的基石)
|
||||
|
||||
#### 3.4.1 问题
|
||||
|
||||
当前同一功能、不同类型的节点大量重复:
|
||||
|
||||
| 功能 | 现有节点 | 覆盖类型 |
|
||||
|---|---|---|
|
||||
| 常量 | `NodeConstFloat`, `NodeConstVector2`, `NodeConstVector3`, `NodeConstColor` | float, Vector2, Vector3, Color |
|
||||
| 拆分 | `NodeSplitV2`, `NodeSplitV3` | Vector2, Vector3 |
|
||||
| 合并 | `NodeCombineV2`, `NodeCombineV3` | Vector2, Vector3 |
|
||||
| 数学 | `NodeMath`(仅 float) | float |
|
||||
|
||||
这些节点的**逻辑完全一致**,仅类型不同。引入 `InputAny` / `OutputAny` 后,一个节点覆盖所有类型。
|
||||
|
||||
#### 3.4.2 类型列表
|
||||
|
||||
```csharp
|
||||
// 系统支持的全部可连线类型
|
||||
static readonly HashSet<Type> SupportedTypes = new()
|
||||
{
|
||||
typeof(float), typeof(int), typeof(bool), typeof(string),
|
||||
typeof(Vector2), typeof(Vector3), typeof(Color),
|
||||
typeof(GameElement), typeof(List<GameElement>),
|
||||
typeof(Signal),
|
||||
};
|
||||
```
|
||||
|
||||
#### 3.4.3 InputAny / OutputAny 设计
|
||||
|
||||
```csharp
|
||||
/// <summary>未锁定的输入端口,连线后类型自动锁定</summary>
|
||||
public class InputAny : IInput
|
||||
{
|
||||
object _sourceOutput; // 连到的 Output(可能是 Output<T> 或 OutputAny)
|
||||
|
||||
public string Name { get; set; }
|
||||
public Type DataType { get; private set; } // null 表示未锁定,连线后锁定
|
||||
public bool IsConnected => _sourceOutput != null;
|
||||
public bool HasReceived { get; set; }
|
||||
public Color ConnectorColor => DataType != null ? NodeColors.Get(DataType) : Color.grey;
|
||||
|
||||
/// <summary>直接取已连接的上游值(泛型方式)</summary>
|
||||
public T GetValue<T>() { ... }
|
||||
|
||||
/// <summary>取值为 object</summary>
|
||||
public object GetValue() { ... }
|
||||
|
||||
/// <summary>连线时由 Manager 调用,锁定端口类型</summary>
|
||||
internal void LockType(Type t) { DataType = t; }
|
||||
}
|
||||
|
||||
/// <summary>未锁定的输出端口,类型由同节点的 InputAny 传播决定</summary>
|
||||
public class OutputAny : IOutput
|
||||
{
|
||||
object _value;
|
||||
|
||||
public string Name { get; set; }
|
||||
public Type DataType { get; private set; } // null 表示未锁定
|
||||
public bool IsConnected => _targets.Count > 0;
|
||||
public Color ConnectorColor => DataType != null ? NodeColors.Get(DataType) : Color.grey;
|
||||
|
||||
public T GetValue<T>() { ... }
|
||||
public void SetValue<T>(T v) { _value = v; }
|
||||
|
||||
/// <summary>由节点的某个 InputAny 锁定后传播过来</summary>
|
||||
internal void LockType(Type t) { DataType = t; }
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.4.4 类型传播规则
|
||||
|
||||
一个节点上存在多个 `InputAny` / `OutputAny` 时,类型按以下优先级传播:
|
||||
|
||||
```
|
||||
规则 1(外部优先): 任一 InputAny 被连线 → 锁定该端口类型 → 传播到同节点所有未锁定的 InputAny / OutputAny
|
||||
规则 2(冲突检测): 两个已连线的 InputAny 类型不一致 → Manager 阻止连线,报 "Type mismatch"
|
||||
规则 3(OutputAny): 总是跟随同节点的首个已锁定 InputAny 的类型
|
||||
规则 4(无输入节点): 常量类节点无 InputAny,OutputAny 类型由节点内置字段/UI 选择决定
|
||||
```
|
||||
|
||||
#### 3.4.5 ConnectorSlot UI 适配
|
||||
|
||||
- 未锁定的 `InputAny` / `OutputAny` 连接点显示**灰色**
|
||||
- 连线锁定后,动态更新连接点的颜色以匹配锁定类型
|
||||
- `ConnectorSlot` 需要监听 `DataType` 变化并刷新颜色
|
||||
- Manager 在 `TryConnect` 成功后调用 `Slot.RefreshAppearance()`
|
||||
|
||||
---
|
||||
|
||||
### 3.5 多类型统一节点设计
|
||||
|
||||
以下节点取代现有的同功能多类型节点。
|
||||
|
||||
#### 3.5.1 NodeConst — 通用常量(取代 NodeConstFloat/V2/V3/Color)
|
||||
|
||||
```
|
||||
NodeConst:
|
||||
[UI] Dropdown: Type (float / int / bool / Vector2 / Vector3 / Color)
|
||||
[UI] 根据所选类型动态显示对应输入控件
|
||||
OutputAny value ← 类型由 UI 选择锁定
|
||||
```
|
||||
|
||||
逻辑:无输入,Loop 中直接 Complete。输出值被下游拉取。
|
||||
|
||||
#### 3.5.2 NodeMath — 通用数学运算(扩展覆盖类型)
|
||||
|
||||
```
|
||||
NodeMath:
|
||||
[UI] Dropdown: Op (Add / Sub / Mul / Div)
|
||||
InputAny a ← 连线后锁定类型
|
||||
InputAny b ← 跟随 a 的类型(或相反,谁先连跟谁)
|
||||
OutputAny result ← 类型来源同 InputAny 的锁定类型
|
||||
```
|
||||
|
||||
支持的运算映射(LUT 注册):
|
||||
|
||||
| 类型 | Add | Sub | Mul | Div |
|
||||
|---|---|---|---|---|
|
||||
| float, int | `+` | `-` | `*` | `/` |
|
||||
| Vector2, Vector3 | `+` | `-` | `* float` | `/ float` |
|
||||
| string | 拼接 | — | — | — |
|
||||
| Color | `+` (叠加) | `-` | `* float` | — |
|
||||
|
||||
> 实现:用 `Dictionary<(Type, Op), Func<object, object, object>>` 查表分派。
|
||||
|
||||
#### 3.5.3 NodeSplit — 通用拆分(取代 NodeSplitV2/V3)
|
||||
|
||||
```
|
||||
NodeSplit:
|
||||
InputAny input ← Vector2 → 输出 X(float), Y(float)
|
||||
← Vector3 → 输出 X(float), Y(float), Z(float)
|
||||
OutputAny x, y, z ← z 仅在 Vector3 时激活
|
||||
```
|
||||
|
||||
- 默认所有 OutputAny 端口可见但灰掉,`input` 锁定类型后按需激活
|
||||
- 对于 Color:输出 R, G, B, A (float)
|
||||
|
||||
#### 3.5.4 NodeCombine — 通用合并(取代 NodeCombineV2/V3)
|
||||
|
||||
```
|
||||
NodeCombine:
|
||||
[UI] Dropdown: Type (Vector2 / Vector3 / Color)
|
||||
InputAny x, y, z, w ← 数量按类型动态显示
|
||||
OutputAny output ← 类型跟随 UI 选择
|
||||
```
|
||||
|
||||
#### 3.5.5 NodeLerp — 线性插值(新节点)
|
||||
|
||||
```
|
||||
NodeLerp:
|
||||
InputAny a, b ← 连线锁定类型
|
||||
InputAny t ← 预期 float(不受 a/b 锁定影响,标记为 fixed-type)
|
||||
OutputAny result ← 跟随 a/b 类型
|
||||
```
|
||||
|
||||
支持 float, int, Vector2, Vector3, Color。
|
||||
|
||||
#### 3.5.6 NodeCompare — 比较运算(新节点)
|
||||
|
||||
```
|
||||
NodeCompare:
|
||||
[UI] Dropdown: Op (==, !=, >, <, >=, <=)
|
||||
InputAny a, b ← 连线锁定类型(支持 float, int)
|
||||
OutputAny result ← 固定 bool
|
||||
```
|
||||
|
||||
> 注意:> \ < 仅在数值类型有效,== / != 可扩展至 string。
|
||||
|
||||
#### 3.5.7 NodeSelect — 二选一(新节点)
|
||||
|
||||
```
|
||||
NodeSelect:
|
||||
InputAny condition ← 如果未连线,用 [UI] Toggle(bool);如果连线则类型锁定 bool
|
||||
InputAny trueValue, falseValue ← 类型互相跟随
|
||||
OutputAny result ← 跟随 trueValue/falseValue 类型
|
||||
```
|
||||
|
||||
#### 3.5.8 NodeSet — 万能赋值(新节点,引用语义)
|
||||
|
||||
```
|
||||
NodeSet:
|
||||
InputAny targetRef ← 连接到变量节点的 get 输出(锁定为目标类型)
|
||||
InputAny value ← 跟随 targetRef 类型
|
||||
// 无 OutputAny,纯副作用节点
|
||||
```
|
||||
|
||||
> 关键:targetRef 不仅是取值,还要修改其引用的 Variable 内部值。需要 InputAny 能够"反向写入"。
|
||||
|
||||
#### 3.5.9 节点对比总结
|
||||
|
||||
| 统一节点 | 取代旧节点 | 覆盖类型数 |
|
||||
|---|---|---|
|
||||
| `NodeConst` | `NodeConstFloat`, `NodeConstVector2`, `NodeConstVector3`, `NodeConstColor` | 6+ |
|
||||
| `NodeMath` | `NodeMath`(扩展) | 5 (float, int, Vector2, Vector3, Color) |
|
||||
| `NodeSplit` | `NodeSplitV2`, `NodeSplitV3` | 3 (Vector2, Vector3, Color) |
|
||||
| `NodeCombine` | `NodeCombineV2`, `NodeCombineV3` | 3 (Vector2, Vector3, Color) |
|
||||
| `NodeLerp` | (新) | 5 |
|
||||
| `NodeCompare` | (新) | 3 |
|
||||
| `NodeSelect` | (新) | 任意 |
|
||||
| `NodeSet` | (新) | 任意 |
|
||||
|
||||
---
|
||||
|
||||
### 3.6 InputAny 类型锁定流程(Manager 侧)
|
||||
|
||||
```
|
||||
TryConnect(OutputAny/Output<T> src, InputAny dst):
|
||||
1. 获取 src 的实际 DataType → T
|
||||
2. 如果 dst.DataType == null → dst.LockType(T) → 传播到同节点其他端口
|
||||
3. 如果 dst.DataType == T → OK
|
||||
4. 如果 dst.DataType != T → 拒绝,类型不匹配
|
||||
5. 调用 dst.ownerNode.OnTypePropagated() 通知节点刷新 UI
|
||||
```
|
||||
|
||||
传播方法(在 `NodeBase` 上):
|
||||
```csharp
|
||||
/// <summary>当某个 InputAny 或 OutputAny 锁定了类型后,通知节点刷新其他端口</summary>
|
||||
public virtual void OnTypePropagated(ConnectorSlot lockedSlot, Type lockedType)
|
||||
{
|
||||
// 默认:遍历所有未锁定端口,LockType(lockedType)
|
||||
foreach (var slot in GetAnySlots())
|
||||
if (slot.DataType == null)
|
||||
slot.LockType(lockedType);
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.6.1 fixed-type 端口标记
|
||||
|
||||
某些 InputAny 不接受类型传播(如 `NodeLerp.t` 必须是 float)。新增标记:
|
||||
|
||||
```csharp
|
||||
public class InputAny : IInput
|
||||
{
|
||||
public bool IsFixedType { get; init; } // true = 不被传播覆盖,始终保持初始类型
|
||||
}
|
||||
```
|
||||
|
||||
`NodeLerp.t` → `new InputAny { IsFixedType = true, Name = "t" }`(已指定 float 意图时 LockType(float))
|
||||
|
||||
---
|
||||
|
||||
### 3.7 特殊节点:变量节点
|
||||
|
||||
```csharp
|
||||
class NodeVariable<T> : NodeBase
|
||||
{
|
||||
Input<Signal> signal; // 通常不连,仅在循环内使用
|
||||
Input<T> set;
|
||||
Output<T> get;
|
||||
}
|
||||
```
|
||||
|
||||
- `Loop()` 立即返回当前值,不存在 Hang 状态
|
||||
- 有 Signal 输入时(循环体内),等待 Signal 触发才更新
|
||||
- 变量节点不参与 InputAny 类型传播——类型由泛型参数 T 固定
|
||||
|
||||
### 3.8 循环节点 & 子函数节点(新 UI:Rect 容器)
|
||||
|
||||
**UI 变更(NodeUIBuilder 扩展):**
|
||||
- 新增一个可手动拖拽缩放大小的 Rect 区域
|
||||
- 将其他节点拖入此 Rect 即表示"在节点内部"
|
||||
- 循环节点的 Index 和 Signal 输出点移到第一列(输入列),可拉线到 Rect 内部
|
||||
|
||||
**节点结构:**
|
||||
```
|
||||
NodeForLoop:
|
||||
外部输入: exec(Signal), count(InputAny) ← count 支持 int/float
|
||||
外部输出: completed(Signal)
|
||||
内部 Rect 中:
|
||||
子节点...(由主 Manager 调度,子控制器监控)
|
||||
Index 输出(int) — 在第一列,拉入 Rect 内
|
||||
LoopBody 输出(Signal) — 在第一列,拉入 Rect 内
|
||||
```
|
||||
|
||||
**子控制器职责:**
|
||||
- 检查循环体内所有节点是否 Complete
|
||||
- 重置内部节点,开启下一步循环
|
||||
- 向主 Manager 报告循环是否全部完成
|
||||
|
||||
**子函数定义节点:**
|
||||
```
|
||||
NodeSubFunctionDef:
|
||||
输入: name(string)
|
||||
Rect 外→Rect 内的变量节点 → 代表函数输入参数(变量类型即参数类型)
|
||||
Rect 内的 set 节点 → Rect 外 → 代表函数输出
|
||||
```
|
||||
|
||||
**子函数执行节点(配合 InputAny):**
|
||||
```
|
||||
NodeSubFunctionCall:
|
||||
— 根据已注册的子函数定义,动态生成 InputAny/OutputAny 端口
|
||||
— 端口类型由子函数定义中变量节点的泛型参数决定
|
||||
— Manager 扫描所有文件注册子函数定义
|
||||
```
|
||||
|
||||
### 3.9 现有节点迁移计划
|
||||
|
||||
| 旧节点 | 处理方式 | 说明 |
|
||||
|---|---|---|
|
||||
| `NodeStart` | 迁移:`Evaluate()` → `Loop()` | 启动后 Complete |
|
||||
| `NodeEntry` | 同上 | — |
|
||||
| `NodeMath` | **扩展为多类型版本**(3.5.2) | 旧版删除 |
|
||||
| `NodeConstFloat/V2/V3/Color` | **合并为 `NodeConst`**(3.5.1) | 四个节点合一,旧版全部删除 |
|
||||
| `NodeSplitV2/V3` | **合并为 `NodeSplit`**(3.5.3) | 两个节点合一 |
|
||||
| `NodeCombineV2/V3` | **合并为 `NodeCombine`**(3.5.4) | 两个节点合一 |
|
||||
| `NodeForLoop` | 大改:Rect 容器 + 子控制器 | count 改用 InputAny |
|
||||
| `NodeForEach<T>` | 类似 ForLoop 改造 | — |
|
||||
| `NodeBranch` | 迁移到 Loop(),condition 改用 InputAny | 支持 float/int 条件 |
|
||||
| `NodeGameElement` / `NodeSetTransform` / `NodeClone` 等 | 生命周期适配,Signal 等待逻辑不变 | — |
|
||||
| `NodeVariable<T>` | 特殊处理:立即 Complete,不经过 Hang | 保持泛型不变 |
|
||||
| `NodeDebugLog` / `NodeLog` | 迁移,InputAny 支持任意显示类型 | — |
|
||||
| `NodeList<T>` / `NodeListAdd<T>` / `NodeListGet<T>` | 保持泛型,Loop() 适配 | — |
|
||||
| `NodePositionStepper` | 迁移,参数改用 InputAny | — |
|
||||
| **新增** `NodeLerp` | 全新 | — |
|
||||
| **新增** `NodeCompare` | 全新 | — |
|
||||
| **新增** `NodeSelect` | 全新 | — |
|
||||
| **新增** `NodeSet` | 全新(需要反向写入能力) | — |
|
||||
|
||||
---
|
||||
|
||||
## 四、实施步骤建议
|
||||
|
||||
1. **Phase 1: 核心类型层** — 修改 `NodeCore.cs`
|
||||
- 新增 `NodeStatus` 枚举
|
||||
- `NodeBase` 新增 `Loop()`、`L`、`Status`
|
||||
- `Input<T>` / `Output<T>` 改为拉取模式
|
||||
- 实现 `InputAny`、`OutputAny`、`OnTypePropagated`、`IsFixedType`
|
||||
|
||||
2. **Phase 2: Manager 调度层** — 修改 `NodeManager.cs`
|
||||
- 实现触发表/运行时表
|
||||
- 实现 `RunCycle()` 循环调度
|
||||
- 实现 L 值计算 + 连线验证
|
||||
- 实现 `TryConnect` 中的 InputAny 类型锁定传播流程
|
||||
|
||||
3. **Phase 3: 统一节点实现** — 删除旧重复节点,实现新版
|
||||
- 先实现 `NodeConst`、`NodeMath`(验证 InputAny 机制可用)
|
||||
- 再实现 `NodeSplit`、`NodeCombine`、`NodeLerp`
|
||||
- `NodeCompare`、`NodeSelect`、`NodeSet`(反向写入)
|
||||
- 迁移保留的节点(`NodeStart`、`NodeBranch`、`NodeVariable<T>` 等)
|
||||
|
||||
4. **Phase 4: 循环/子函数** — Rect 容器 + 子控制器
|
||||
- `NodeUIBuilder` 增加 Rect 容器支持
|
||||
- 循环节点子控制器实现
|
||||
- 子函数定义/执行节点 + Manager 注册扫描
|
||||
|
||||
5. **Phase 5: 测试 & 清理**
|
||||
- 验证保存/加载兼容(含新旧类型映射)
|
||||
- 删除旧版备份文件 `NodeBase.cs.bak`
|
||||
- 删除旧节点类(`NodeConstFloat` 等)
|
||||
- 验证所有节点类型覆盖无遗漏
|
||||
|
||||
---
|
||||
|
||||
## 五、风险点 & 注意事项
|
||||
|
||||
- **UI 不变原则**:`NodeObject`、`ConnectorSlot`、`NodeUIBuilder` 的接口保持稳定,节点层改动不应破坏 UI 渲染(ConnectorSlot 仅新增 `RefreshAppearance()`)
|
||||
- **InputAny 类型冲突**:同一节点两个已连线的 InputAny 类型不一致时,Manager 拒绝并报错
|
||||
- **反向写入**:`NodeSet` 的 targetRef 需要能修改上游 Variable 内部值,这是 InputAny 设计的关键难点
|
||||
- **向后兼容**:旧 JSON 中的 `NodeConstFloat` 等类型名加载时需映射到新的 `NodeConst`
|
||||
- **循环嵌套**:子控制器设计需考虑循环内嵌套循环的递归情况
|
||||
- **Performance**:大图时每帧遍历运行时表的开销,类型检查用 `Type` 引用比较(非字符串)
|
||||
- **Signal 类型**:保留但不参与 InputAny 的类型传播
|
||||
Reference in New Issue
Block a user