TiXL 图编辑器变量追踪:Get<Type>Var 悬停显示 Set/Get-Var 引用连线的实现解析
发布时间:2026/9/18 23:14:00 作者:尧图编辑部 阅读量:1,286

TiXL 图编辑器变量追踪Get Var 悬停显示 Set/Get-Var 引用连线的实现解析【免费下载链接】t3TiXL is an open source software to create realtime motion graphics.项目地址: https://gitcode.com/GitHub_Trending/t3/t3本文以 TiXL 编辑器中的计划文档 Plan_GetVarHoverLink.md 为主线围绕悬停 Get 变量算子时显示其与 Set 变量算子之间的引用连线这一交互特性逐层拆解其问题背景、通用绘制器实现、GUID 接线方案与潜在风险。读完本文你将掌握 TiXL 图编辑器中SetTypeVar/GetTypeVar虚拟链路的绘制原理理解DrawVariableReferences的方向无关设计并能在实际代码中定位、验证乃至扩展这 5 对变量算子的双向追踪功能。一、背景Set/Get 变量对的虚拟连线缺口TiXL 是用于实时生成动态图形realtime motion graphics的开源软件其核心交互是可视化节点图编辑器。在图编辑器中变量通过一对算子协作工作SetTypeVar算子如SetFloatVar、SetIntVar、SetBoolVar、SetStringVar、SetVec2Var、SetVec3Var负责给某个具名变量写入值GetTypeVar算子GetFloatVar、GetIntVar、GetBoolVar、GetStringVar、GetVec2Var、GetVec3Var负责从该变量读取值。二者之间并不存在真实的连线而是通过变量名VariableName这一字符串输入在逻辑上相互关联——这种关联在计划文档中被称为虚拟链路virtual link。计划文档对应 issue #1077目标里程碑 v4.2指出的问题是目前SetTypeVar与其匹配的GetTypeVar之间的虚拟链路只在悬停 Set 变量算子时才被绘制。用户无法从 Get 端反向追踪一个变量来自哪里。也就是说追踪链路是单向的鼠标悬停在SetFloatVar上会点亮所有同名的GetFloatVar但悬停在GetFloatVar上却没有任何反应。计划的诉求是让追踪在任意一端都能发生实现从任何一端都能追溯变量的完整体验。二、核心绘制器DrawVariableReferences源码逐段解读实现这条虚拟链路的关键是 OpUi.cs 中定义的静态方法DrawVariableReferences。计划文档称它是方向无关direction-agnostic的——它并不关心谁在追谁只负责以某个 source 实例为起点向同一父节点下、属于指定符号Symbol且变量名相同的兄弟算子画线。完整源码如下internal static void DrawVariableReferences(ImDrawListPtr drawList, ScalableCanvas canvas, Vector2 startCenter, Instance sourceInstance, string variableName, Guid symbolId, Guid variableNameInputId) { if (sourceInstance.Parent null) return; // An empty name would otherwise link all ops whose name input is equally empty. if (string.IsNullOrWhiteSpace(variableName)) return; foreach (var child in sourceInstance.Parent.Children.Values) { if (child.Symbol.Id ! symbolId) continue; var input child.GetInput(variableNameInputId); if (input is not InputSlotstring stringInput) continue; if (stringInput.Value ! variableName) continue; var childUi child.GetChildUi(); if (childUi null) continue; FrameStats.AddHoveredId(childUi.Id); drawList.AddLine(startCenter, canvas.TransformPosition(childUi.PosOnCanvas), UiColors.StatusAutomated); } }其执行逻辑可以拆解为五个防御与匹配步骤父节点与空名保护sourceInstance.Parent null时直接返回变量名为空或纯空白时也直接返回。注释明确解释了后者——空名字会把所有变量名为空的算子全部串起来必须阻止。符号过滤遍历sourceInstance.Parent.Children.Values中的所有兄弟实例只保留child.Symbol.Id symbolId的算子。symbolId即目标符号比如GetFloatVar或SetFloatVar的全局唯一 ID。输入槽类型检查通过child.GetInput(variableNameInputId)取目标算子的变量名输入并检查其是否为InputSlotstring。如果 GUID 指错或类型不匹配直接跳过该兄弟——这保证了即使传入错误的 GUID也只会表现为不画线而不会崩溃。值匹配只有当该兄弟算子VariableName输入槽的当前值与sourceInstance的变量名完全相等时才认为二者是同一变量的两端。绘制与悬停联动通过FrameStats.AddHoveredId(childUi.Id)把目标算子的 UI 标记为悬停状态使其呈现与真实悬停一致的视觉高亮再用drawList.AddLine从startCenter画一条直线到目标算子在画布上的位置canvas.TransformPosition(childUi.PosOnCanvas)颜色使用UiColors.StatusAutomated。从源码结构看该方法的对称性是这次计划能够顺利实施的根本前提同一份方法既可以被 Set 侧调用向 Get 侧画线也可以被 Get 侧调用向 Set 侧画线只需互换传入的符号 ID 与输入槽 ID 即可。三、既有实现Set 侧的悬停调用范式计划文档明确指出Set 侧的实现早已存在作为范本的是 SetFloatVarUi.cs// Draw reference lines on hover if (area.Contains(ImGui.GetMousePos())) { OpUi.DrawVariableReferences(drawList, canvas, area.GetCenter(), instance, data.VariableName.Value, Guid.Parse(e6072ecf-30d2-4c52-afa1-3b195d61617b), Guid.Parse(015d1ea0-ea51-4038-893a-4af2f8584631)); }这段代码位于DrawChildUi中要点如下悬停检测area.Contains(ImGui.GetMousePos())判断鼠标是否位于该算子节点的绘制区域内起点area.GetCenter()取节点区域中心作为连线起点变量名data.VariableName.Value取自当前 Set 算子的 VariableName 输入槽目标符号 IDe6072ecf-...是GetFloatVar的符号 GUID见 OpUi.cs 注册表目标输入槽 ID015d1ea0-...是 GetFloatVar 的 VariableName 输入 GUID见 GetFloatVarUi.cs 的BindInput声明。同样的模式也存在于SetStringVarUi、SetIntVarUi、SetBoolVarUi、SetVec3VarUi以及计划文档未单独提及的SetVec2VarUi中结构完全一致仅 GUID 不同。计划文档因此特别强调Get 侧的新代码块应与 Set 侧保持逐字一致Keep the block identical to the Set side for consistency以降低维护成本。四、方案实施Get 侧镜像悬停块与反向参数接线计划的思路非常直接把 Set 侧的悬停块原样复制到每个GetTypeVarUi.DrawChildUi中但传入的参数是反向的——source 仍为当前Get实例variableName仍取当前实例的VariableName.Value而symbolId换成对应的Set Var符号 GUID、variableNameInputId换成该SetVar 的变量名输入 GUID。由于绘制器是对称的这样就能点亮从被悬停的 GetVar 指向其同名 SetVar(s) 的连线。当前仓库中的落地实现计划文档写作时 Get 侧尚无连线no link today但从当前仓库源码看该方案已经落地。例如 GetFloatVarUi.cs 中的实现// Draw reference lines on hover — links to the matching Set op (mirror of the Set side). if (area.Contains(ImGui.GetMousePos())) { OpUi.DrawVariableReferences(drawList, canvas, area.GetCenter(), instance, data.VariableName.Value, Guid.Parse(2a0c932a-eb81-4a7d-aeac-836a23b0b789), Guid.Parse(6EE64D39-855A-4B20-A8F5-39B4F98E8036)); }注意其中的 GUID 正是反向接线的体现2a0c932a-...是SetFloatVar的符号 GUIDOpUi.cs6EE64D39-...是SetFloatVar的 VariableName 输入 GUIDSetFloatVarUi.cs 的[BindInput(6EE64D39-855A-4B20-A8F5-39B4F98E8036)]。两条接线形成完美闭环悬停 SetFloatVar 时用 GetFloatVar 的标识找兄弟 GetFloatVar悬停 GetFloatVar 时用 SetFloatVar 的标识找兄弟 SetFloatVar。五对变量的完整 GUID 对照表计划文档指出每个类型需要两个 GUIDSet 符号 ID、Set 变量名输入 ID需要从各SetTypeVarUi与OpUi.cs注册表中逐一收集并特别警告Set 侧的变量名输入 GUID 可能不同于 Get 侧。结合当前仓库源码整理出 6 对变量的完整接线如下source列表示悬停在哪一侧时发起连线变量类型悬停端目标符号 ID目标 VariableName 输入 IDFloatSete6072ecf-30d2-4c52-afa1-3b195d61617bGetFloatVar015d1ea0-ea51-4038-893a-4af2f8584631FloatGet2a0c932a-eb81-4a7d-aeac-836a23b0b789SetFloatVar6EE64D39-855A-4B20-A8F5-39B4F98E8036IntSet470db771-c7f2-4c52-8897-d3a9b9fc6a4eGetIntVard7662b65-f249-4887-a319-dc2cf7d192f2IntGet7953f704-ebee-498b-8bdd-a2c201dfe278SetIntVarbfd87742-aaf5-4fa8-b714-fd275de1c60dStringSet15012739-97b0-49e6-8bdc-cd45cd262560GetStringVard19b0483-cdd4-4684-852d-ef5ee8393dfcStringGet9c9274c1-3f85-4f57-9eb5-1e0a0b1cce00SetStringVar5be2939b-27f5-4af8-b78e-3ae220d6b1a4BoolSet604bfb46-fe8f-4c8b-896b-1b7bc827137bGetBoolVarb0821091-68c0-4e34-9f8b-926c0b6ebf94BoolGet9a843835-d39c-428f-b996-6334323e8106SetBoolVarBFDFCD6E-3B31-4B26-AFF4-3023A6B72810Vec3Setf21de2e1-6af8-4651-90a0-6c662bbb23afGetVec3Vard8a9d923-232f-4cd4-9e24-fadbf40fe1d1Vec3Getfdad077d-e919-4f40-a154-36e86245a585SetVec3Var0edf7837-4555-4e62-902f-930abf72e8b8Vec2Set411d3dd4-778b-4012-a19f-05d855fa1bafGetVec2Var05d0cc68-4f6b-4e03-871c-4de0e6ea0b24Vec2Get332d7496-09ec-45b7-85b4-0653bb93dd62SetVec2Var903c7fc3-dfa4-4a01-8f7d-53a94f8a93ab该表可直接用于验证或扩展其他变量类型的接线悬停端为 Set 时目标是 Get 的符号与输入 GUID悬停端为 Get 时目标是 Set 的符号与输入 GUID——切勿混用两端 GUID这正是计划文档反复强调的风险点。五、调度与路由DrawCustomUi如何找到正确的 UI理解悬停块为何会执行需要追溯到 UI 的注册与路由机制。在 OpUi.cs 中维护着一个静态字典_drawFunctionsForSymbolIds将算子符号 GUID映射到绘制函数委托{ Guid.Parse(470db771-c7f2-4c52-8897-d3a9b9fc6a4e), GetIntVarUi.DrawChildUi }, { Guid.Parse(7953f704-ebee-498b-8bdd-a2c201dfe278), SetIntVarUi.DrawChildUi }, { Guid.Parse(e6072ecf-30d2-4c52-afa1-3b195d61617b), GetFloatVarUi.DrawChildUi }, { Guid.Parse(2a0c932a-eb81-4a7d-aeac-836a23b0b789), SetFloatVarUi.DrawChildUi }, { Guid.Parse(15012739-97b0-49e6-8bdc-cd45cd262560), GetStringVarUi.DrawChildUi }, { Guid.Parse(9c9274c1-3f85-4f57-9eb5-1e0a0b1cce00), SetStringVarUi.DrawChildUi }, { Guid.Parse(9a843835-d39c-428f-b996-6334323e8106), SetBoolVarUi.DrawChildUi }, { Guid.Parse(604bfb46-fe8f-4c8b-896b-1b7bc827137b), GetBoolVarUi.DrawChildUi }, { Guid.Parse(fdad077d-e919-4f40-a154-36e86245a585), SetVec3VarUi.DrawChildUi }, { Guid.Parse(f21de2e1-6af8-4651-90a0-6c662bbb23af), GetVec3VarUi.DrawChildUi }, { Guid.Parse(332d7496-09ec-45b7-85b4-0653bb93dd62), SetVec2VarUi.DrawChildUi }, { Guid.Parse(411d3dd4-778b-4012-a19f-05d855fa1baf), GetVec2VarUi.DrawChildUi },代码注释坦言这种手动注册令人遗憾unfortunate但相比引入静态类或反射做自动注册这是最简单直接的方案。运行时OpUi.DrawCustomUiOpUi.cs以instance.Symbol.Id查表找到对应的DrawChildUi委托并执行——这也是为什么新增变量类型时必须同时维护符号 GUID 注册表与各 Ui 内的 GUID 接线两处数据。每个 Ui 内部通过OpUiBinding[BindInput(GUID)]特性完成输入槽绑定例如 GetFloatVarUi.cs[BindInput(015d1ea0-ea51-4038-893a-4af2f8584631)] internal readonly InputSlotstring VariableName null!; [BindOutput(e368ba33-827e-4e08-aa19-ba894b40906a)] internal readonly Slotfloat Result null!;绑定失败时data.IsValid为 falseDrawChildUi会提前返回PreventOpenSubGraph避免绘制半失效的 UI。六、风险与副作用静默失败的 GUID 陷阱计划文档把风险等级评定为低严重度Low severity但明确指出一个最棘手的特性错误的 GUID 是静默失败的——一个打错的 GUID 对只会让连线不显示而不会产生任何编译错误。这正是该功能必须先写计划、再实施的原因正确性只能通过在运行中的编辑器里逐一悬停 5 个 Get 算子来确认无法靠自动化构建或单元测试自动验证。因此实施时的核对要点是每个GetTypeVarUi传入的符号 GUID必须查 OpUi.cs 注册表中对应SetTypeVar的那一行而不是 Get 自己每个传入的输入槽 GUID必须查对应SetTypeVarUi的[BindInput]声明可能大写比较时注意大小写而不是 Get 侧的输入 GUID改动范围仅限 5 个实际为 6 个含 Vec2Get*VarUi文件且纯新增、不改动既有逻辑与 Set 侧保持逐字一致便于未来统一维护。七、开放问题与后续打磨方向计划文档在文末列出了两个开放问题可作为该功能后续演进的参考连线去重dedupe当 Set 与 Get 两端同时处于悬停或选中状态时双向逻辑可能各自画出一条连线导致同一对变量出现两条重叠的线。可选的打磨是检测另一端是否也在绘制避免重复绘制。选中态一致性selection-based drawing目前悬停是唯一触发条件计划文档提出的疑问是悬停 Get 时是否也应尊重既有的、基于选中selection的绘制规则使选中与悬停两种交互状态下的连线行为保持一致。这两点均属于交互打磨范畴不影响核心功能正确性但值得在后续迭代中评估。八、相关文件索引文件作用.agentic/Plans/Plan_GetVarHoverLink.md本文依据的计划文档问题、方案、风险与开放问题Editor/Gui/OpUis/OpUi.cs通用绘制器DrawVariableReferences及符号注册表Editor/Gui/OpUis/UIs/SetFloatVarUi.csSet 侧悬停范式向 Get 侧连线Editor/Gui/OpUis/UIs/GetFloatVarUi.csGet 侧镜像实现向 Set 侧连线Editor/Gui/OpUis/UIs/GetIntVarUi.csGet 侧镜像IntEditor/Gui/OpUis/UIs/GetStringVarUi.csGet 侧镜像StringEditor/Gui/OpUis/UIs/GetBoolVarUi.csGet 侧镜像BoolEditor/Gui/OpUis/UIs/GetVec3VarUi.csGet 侧镜像Vec3Editor/Gui/OpUis/UIs/GetVec2VarUi.csGet 侧镜像Vec2结语悬停 Get 变量算子显示引用连线这一看似微小的交互改动背后体现了 TiXL 图编辑器 UI 层一个值得借鉴的设计把寻找关联节点的通用逻辑收敛进一个方向无关的绘制器两侧 UI 只负责提供正确的对端标识从而让同一份代码同时服务于两个方向。理解DrawVariableReferences的符号过滤 输入槽匹配机制以及 Set/Get 两端 GUID 的镜像接线方式不仅能读懂变量追踪功能也为在 TiXL 中实现任何虚拟引用链路类交互如参数联动、事件订阅关系可视化提供了可直接复用的范式。【免费下载链接】t3TiXL is an open source software to create realtime motion graphics.项目地址: https://gitcode.com/GitHub_Trending/t3/t3创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考