NodeRegistry 注册表
一个 HashMap 的故事
`NodeRegistry` 的实现极其简洁——一个 `HashMap<String, Arc<dyn IFlowNode>>`:
#[derive(Default)]
pub struct NodeRegistry {
nodes: HashMap<String, Arc<dyn IFlowNode>>,
}
impl NodeRegistry {
pub fn new() -> Self { Self::default() }
pub fn register(&mut self, node: Arc<dyn IFlowNode>) {
let kind = node.kind().to_string();
self.nodes.insert(kind, node);
}
pub fn get(&self, kind: &str) -> Option<Arc<dyn IFlowNode>> {
self.nodes.get(kind).cloned()
}
pub fn port_specs_for(&self, kind: &str) -> Vec<PortSpec> {
self.nodes.get(kind)
.map(|n| n.schema().ports.clone())
.unwrap_or_default()
}
}
没有泛型、没有宏、没有生命周期体操——这就是 trait object 的力量。
注册时机
`FlowEditorView::new` 内部调用 `builtin::register_all`,把 8 种内置节点一次性注册:
pub fn register_all(registry: &mut NodeRegistry) {
registry.register(Arc::new(StartNode::new()));
registry.register(Arc::new(EndNode::new()));
registry.register(Arc::new(ActionNode::new()));
registry.register(Arc::new(ConditionNode::new()));
registry.register(Arc::new(LoopNode::new()));
registry.register(Arc::new(VariableNode::new()));
registry.register(Arc::new(AdapterNode::new()));
registry.register(Arc::new(AgentNode::new()));
}
注册顺序无关——查找是 O(1) HashMap 查询。`Arc` 让同一实现被多个编辑器实例共享,零拷贝。
注册表与 FlowEditorView 的关系
FlowEditorView
├── graph: FlowGraph ← 数据(节点/边)
├── registry: NodeRegistry ← 渲染策略(kind → IFlowNode)
└── render(): 每帧
└─ for node in graph.nodes():
registry.get(&node.kind) → Arc<dyn IFlowNode>
flow_node.get_view(node, ctx) → AnyElement
`graph` 存「有什么」,`registry` 存「怎么画」——两者通过 `kind` 字符串解耦。删除 graph 里的节点不影响 registry;替换 registry 不影响 graph 数据。
查找的两种用途
| 调用方 | 方法 | 用途 | |--------|------|------| | 渲染层 | `get(kind)` → `get_view`/`get_panel` | 画卡片、画面板 | | 渲染层 | `get(kind)` → `port_position` | 计算连线端点 | | 渲染层 | `get(kind)` → `content_size` | 同步节点尺寸 | | 布局层 | `get(kind)` → `ports_for_node` | 推导 Auto 端口方向 | | 面板层 | `port_specs_for(kind)` | Schema 校验、字段编辑 |
注意 `port_specs_for` 用的是 `schema().ports`(静态),而布局推导应用 `ports_for_node`(动态)——这是 Condition 节点能动态扩展端口的关键。
自定义节点的注册
自定义节点只需在 `FlowEditorView::new` 后追加注册:
let mut view = FlowEditorView::new(window, cx);
view.registry.register(Arc::new(MyCustomNode::new()));
如果 kind 与内置节点冲突,**后注册者覆盖前者**(HashMap::insert 语义)。这让你可以替换内置节点的视觉表现而不改框架代码——例如把 Action 的卡片改成带进度条的版本。
Arc 克隆的成本
`get` 返回 `Option<Arc<dyn IFlowNode>>`,每次查找都 `Arc::clone`。这是原子引用计数加一,开销约 10-20ns。每帧渲染 N 个节点就有 N 次克隆——对典型流程图(<100 节点)完全可忽略。
如果未来需要极致性能,可以改用 `&Arc<dyn IFlowNode>` 借用(需改 API 返回引用),但当前设计的简单性远比微秒级优化重要。
小结
`NodeRegistry` 用最朴素的 HashMap 实现策略分发的核心。`register_all` 集中注册 8 种内置节点,自定义节点通过 `register` 追加。registry 与 graph 通过 `kind` 字符串解耦,`port_specs_for` 提供静态端口规格查询。`Arc` 共享让多编辑器实例零成本复用实现。
下一节:[NodeViewCtx 与 NodeAction](nodeviewctx-action.md)