diff --git a/ai-plan/public/documentation-governance-and-refresh/todos/documentation-governance-and-refresh-tracking.md b/ai-plan/public/documentation-governance-and-refresh/todos/documentation-governance-and-refresh-tracking.md index a435f696..23bd67d4 100644 --- a/ai-plan/public/documentation-governance-and-refresh/todos/documentation-governance-and-refresh-tracking.md +++ b/ai-plan/public/documentation-governance-and-refresh/todos/documentation-governance-and-refresh-tracking.md @@ -7,19 +7,26 @@ ## 当前恢复点 -- 恢复点编号:`DOCUMENTATION-GOVERNANCE-REFRESH-RP-010` +- 恢复点编号:`DOCUMENTATION-GOVERNANCE-REFRESH-RP-017` - 当前阶段:`Phase 3` - 当前焦点: - 已建立统一公开 skill:`.agents/skills/gframework-doc-refresh/` - 文档重构入口已从“按 guide/tutorial/api 类型拆 skill”收口为“按源码模块驱动文档刷新” - - PR #268 的当前未解决 review 线程已进入收口:Scene/UI 标题层级修正、共享脚本 review 修复、`gframework-pr-review` 多 AI reviewer 支持补齐 - - 下一轮需要用统一 skill 推进 Godot 相关生成器页面核对 + - `docs/zh-CN/godot/index.md` 已改成源码优先的模块 landing page,不再把 `GetNodeX`、`CreateSignalBuilder`、`InstallGodotModule(...)` 写成默认入口 + - `docs/zh-CN/godot/architecture.md` 已改成当前锚点生命周期、模块挂接顺序和接口边界说明,不再沿用旧版 `.Wait()` 叙述 + - `docs/zh-CN/godot/scene.md` 与 `docs/zh-CN/godot/ui.md` 已按当前 factory / registry / root / source-generator wiring 重写完成 + - `docs/zh-CN/godot/signal.md` 已按当前 `Signal(...)` / `SignalBuilder` / `[BindNodeSignal]` 分工重写完成 + - `docs/zh-CN/godot/extensions.md` 已按当前 `GodotPathExtensions`、`NodeExtensions`、`SignalFluentExtensions` 与 `UnRegisterExtension` 重写完成 + - `docs/zh-CN/godot/logging.md` 已按当前 provider / factory / logger 结构、Godot 控制台输出语义与 CoreGrid 架构接线重写完成 + - 下一轮高优先级工作转为评估 Godot 栏目当前 active 恢复点是否可以收口并迁入 archive ## 当前状态摘要 - 文档治理规则已收口到仓库规范,README、站点入口与采用链路不再依赖旧文档自证 -- 高优先级模块入口与 `core` 关键专题页已回到可作为默认导航入口的状态,本轮计划中的 `core` 剩余高风险页面已完成收口 -- 当前主题仍是 active topic,因为 `source-generators` 栏目下的 Godot 相关页面仍可能包含与实现漂移的旧内容,且统一 skill 还需要在该场景上继续落地使用 +- 高优先级模块入口、`core` 关键专题页与 `tutorials/godot-integration.md` 已回到“以源码 / 测试 / README 为准”的状态 +- `docs/zh-CN/godot/index.md`、`architecture.md`、`scene.md` 与 `ui.md` 已完成当前实现收口 +- 当前主题仍是 active topic,因为 Godot 栏目本轮已完成 `logging.md` 收口,但仍需确认是否可以把当前阶段历史迁入 + `archive/`,并在下一次推送后跟进 PR #268 的 review 线程收敛情况 ## 当前活跃事实 @@ -57,6 +64,41 @@ - `docs/zh-CN/source-generators/priority-generator.md` 已改成“生成 `IPrioritized`、priority-aware 检索 API、动态优先级边界与诊断”的结构, 不再把 `GetAllByPriority()` / `system.Init()` 当作所有场景的默认示例 - 本轮重写后再次执行 `cd docs && bun run build` 通过,当前 `source-generators` 栏目改动没有破坏站点构建 +- `docs/zh-CN/source-generators/godot-project-generator.md` 已改成“包关系、最小接入路径、AutoLoad / InputActions 生成语义、`project.godot` 文件约束与诊断边界”的结构, + 明确 `GFrameworkGodotProjectFile` 只能改相对路径、不能改文件名 +- `docs/zh-CN/source-generators/get-node-generator.md` 已改成“字段注入职责、路径推断、`Required` / `Lookup` 语义、`_Ready()` 自动补齐边界与冲突诊断”的结构, + 明确只有缺少 `_Ready()` 时才会生成 `OnGetNodeReadyGenerated()` +- `docs/zh-CN/source-generators/bind-node-signal-generator.md` 已改成“CLR event 绑定职责、生命周期接线要求、与 `[GetNode]` 的调用顺序、签名约束与命名冲突”的结构, + 明确当前不会自动生成 `_Ready()` / `_ExitTree()` +- `docs/zh-CN/source-generators/auto-register-exported-collections-generator.md` 已补齐 frontmatter,并改成“成员形状、registry 匹配规则、null-skip 行为、编译期诊断与 CoreGrid 真实采用路径”的结构, + 明确生成器依赖的是实例可读集合成员与可读 registry 成员,不要求成员必须带 `[Export]` +- `docs/zh-CN/tutorials/godot-integration.md` 已改成“包关系、`project.godot` 接线、`[GetNode]` / `[BindNodeSignal]` 协作顺序、运行时扩展边界、迁移提醒”的结构, + 不再把 `GetNodeX`、`CreateSignalBuilder`、`AbstractGodotModule` 默认化叙述为当前推荐路径 +- `docs/zh-CN/tutorials/index.md` 中 Godot 教程入口摘要已同步改成“项目级配置 + 生成器协作 + 生命周期边界”,不再继续宣传对象池 / 性能优化式旧范围 +- `docs/zh-CN/godot/index.md` 已改成“模块定位、包关系、最小接入路径、关键入口、当前边界”的 landing page 结构,并明确把 + `[GetNode]`、`[BindNodeSignal]`、`AutoLoads`、`InputActions` 归到 `GFramework.Godot.SourceGenerators` +- `docs/zh-CN/godot/architecture.md` 已改成“何时继承 `AbstractArchitecture`、何时使用 `InstallGodotModule(...)`、锚点生命周期、 + `IGodotModule` 契约边界”的结构,不再把 `OnPhase(...)` / `OnArchitecturePhase(...)` 写成稳定自动广播 +- 本轮再次执行 `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh` 校验 `godot/index.md` 与 + `godot/architecture.md`,并执行 `cd docs && bun run build`,站点构建继续通过 +- `docs/zh-CN/godot/scene.md` 已改成“公开入口、factory 实际行为、项目侧 router/root wiring、`[AutoScene]` 最小接入路径、 + 当前边界”的结构,明确当前没有 `GodotSceneRouter`,且 `GodotSceneFactory` 会在 provider 缺失时回退到 + `SceneBehaviorFactory` +- `docs/zh-CN/godot/ui.md` 已改成“公开入口、layer behavior 语义、项目侧 router/root wiring、`[AutoUiPage]` 最小接入路径、 + 输入与暂停边界”的结构,明确当前没有 `GodotUiRouter`,且 `GodotUiFactory` 仍强制要求 `IUiPageBehaviorProvider` +- 本轮已执行 `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/godot/scene.md` 与 + `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/godot/ui.md`,两页聚焦校验通过 +- `docs/zh-CN/godot/signal.md` 已改成“当前公开入口、动态绑定最小接入路径、与 `[BindNodeSignal]` 的分工、当前边界”的结构, + 不再沿用旧 `CreateSignalBuilder(...)` / builder-pattern 教程式长篇叙述 +- `docs/zh-CN/godot/extensions.md` 已改成“真实扩展分组、Node 辅助成员表、`UnRegisterWhenNodeExitTree(...)` 生命周期边界、 + 当前边界”的结构,不再把扩展层写成覆盖所有 Godot 开发动作的万能工具箱 +- 本轮已执行 `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/godot/signal.md` 与 + `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/godot/extensions.md`,两页聚焦校验通过 +- 本轮再次执行 `cd docs && bun run build` 通过,当前 Godot signal / extensions 页面改动没有破坏站点构建 +- `docs/zh-CN/godot/logging.md` 已改成“当前公开入口、最小接入路径、Godot 控制台输出语义、`[Log]` 协作边界、当前限制”的结构, + 不再把直接改写 `LoggerFactoryResolver.Provider`、`AbstractGodotModule` 或 Godot 专用日志 API 写成默认接入模型 +- 本轮已执行 `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/godot/logging.md` 与 + `cd docs && bun run build`,logging 页面聚焦校验与站点构建继续通过 - `.agents/skills/gframework-doc-refresh/SKILL.md` 已改成标准 YAML frontmatter skill,并明确支持模块输入、证据顺序、输出优先级与验证步骤 - `.agents/skills/gframework-doc-refresh/SKILL.md` 的 `description` 已加引号,修复 `Recommended command:` 中冒号导致的 invalid YAML skill 加载警告 @@ -74,7 +116,12 @@ - 旧专题页示例失真风险:`docs/zh-CN/game/*` 与 `source-generators/*` 中仍可能保留看似合理但与真实实现不一致的示例 - 缓解措施:`game/scene.md`、`ui.md`、`source-generators/context-aware-generator.md` 与 `priority-generator.md` 已完成收口; + `godot-project-generator.md`、`get-node-generator.md`、`bind-node-signal-generator.md` 与 `auto-register-exported-collections-generator.md` + 已完成收口; 继续按源码、测试、`*.csproj` 与 `ai-libs/` 下已验证参考实现核对剩余 Godot 相关页面,不把旧文档当事实来源 +- Godot 栏目归档过早风险:虽然 `logging.md` 已完成收口,但如果在推送前就把当前阶段过早归档,后续 review 跟进会缺少 + 清晰的 active 恢复入口 + - 缓解措施:先保留当前 topic 为 active;待确认本轮页面集与 PR #268 的 review 跟进节奏后,再决定是否迁入 `archive/` - 采用路径误导风险:根聚合包与模块边界若再次被写错,会继续误导消费者的包选择 - 缓解措施:保持“源码与包关系优先”的证据顺序,改动采用说明时同步核对包依赖与生成器 wiring - 模块映射不全风险:统一 skill 若遗漏模块别名、测试项目或 docs 栏目映射,会让后续扫描阶段直接失焦 @@ -115,10 +162,25 @@ - `python3 .agents/skills/gframework-doc-refresh/scripts/scan_module_evidence.py Core` - `python3 .agents/skills/gframework-doc-refresh/scripts/scan_module_evidence.py Godot.SourceGenerators` - `python3 .agents/skills/gframework-doc-refresh/scripts/scan_module_evidence.py Cqrs` +- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/source-generators/godot-project-generator.md` +- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/source-generators/get-node-generator.md` +- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/source-generators/bind-node-signal-generator.md` +- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/source-generators/auto-register-exported-collections-generator.md` +- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/tutorials/godot-integration.md` +- `rg -n "GetNodeX|CreateSignalBuilder|GodotGameArchitecture|AbstractGodotModule|InstallGodotModule\(|GFramework\.Godot\.Pool" docs/zh-CN/godot docs/zh-CN/tutorials -S` +- `cd docs && bun run build` +- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/godot/index.md` +- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/godot/architecture.md` +- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/godot/scene.md` +- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/godot/ui.md` +- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/godot/signal.md` +- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/godot/extensions.md` +- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/godot/logging.md` +- `rg -n "GodotSceneRouter|GodotUiRouter|CreateSignalBuilder|GetNodeX|InstallGodotModule\(" docs/zh-CN/godot -S` +- `cd docs && bun run build` ## 下一步 -1. 继续核对 Godot 相关生成器页面,优先处理 `godot-project-generator.md`、`get-node-generator.md` 与 - `bind-node-signal-generator.md`,优先用 `gframework-doc-refresh` 的模块扫描结果驱动判断 -2. 下一次推送后先重新执行 `$gframework-pr-review`,确认 PR #268 的 CodeRabbit / Greptile open thread 是否按预期收敛 -3. 再继续确认 `project.godot`、`AutoLoad` / `InputActions`、`GetNode` / `BindNodeSignal` 示例仍与当前包关系和生成器入口一致 +1. 评估当前 Godot 栏目页面集是否已足够稳定,决定是否把本阶段 active 恢复点收口并迁入 `archive/` +2. 如需继续保持 active,优先精简 tracking / trace,只保留归档决策、当前风险与下一次 PR follow-up 入口 +3. 下一次推送后重新执行 `$gframework-pr-review`,确认 PR #268 的 CodeRabbit / Greptile open thread 是否按预期收敛 diff --git a/ai-plan/public/documentation-governance-and-refresh/traces/documentation-governance-and-refresh-trace.md b/ai-plan/public/documentation-governance-and-refresh/traces/documentation-governance-and-refresh-trace.md index 729de8cb..c206fd3c 100644 --- a/ai-plan/public/documentation-governance-and-refresh/traces/documentation-governance-and-refresh-trace.md +++ b/ai-plan/public/documentation-governance-and-refresh/traces/documentation-governance-and-refresh-trace.md @@ -2,7 +2,7 @@ ## 2026-04-22 -### 当前恢复点:RP-010 +### 当前恢复点:RP-017 - 本轮从 PR #268 的最新 review 数据恢复,未发现失败检查;CTRF 报告显示 2139 个测试全部通过 - 本轮复核确认当前 PR 的 latest-head open thread 同时来自 `coderabbitai[bot]` 与 `greptile-apps[bot]` @@ -16,6 +16,37 @@ - `fetch_current_pr_review.py` 的本地函数 docstring 覆盖率已补到 `44/44` - 已闭环 RP-001 到 RP-008 的执行细节已归档到 `ai-plan/public/documentation-governance-and-refresh/archive/traces/documentation-governance-and-refresh-rp-001-through-rp-008.md` +- 本轮按 `gframework-doc-refresh` 的模块扫描结果,重写了 `Godot.SourceGenerators` 的 3 个高风险专题页: + - `godot-project-generator.md` + - `get-node-generator.md` + - `bind-node-signal-generator.md` +- 新页面统一收口到“包关系、最小接入路径、真实生成语义、生命周期边界、诊断约束”,不再沿用旧教程式长篇 API 罗列 +- 本轮额外复核了 `ai-libs/CoreGrid` 的真实采用方式,确认 `[GetNode]` / `[BindNodeSignal]` 组合使用时应先注入节点再绑定事件 +- 本轮继续收口 `auto-register-exported-collections-generator.md`,补齐 frontmatter,并把“导出集合”纠正为“实例可读集合成员 + registry 成员 + 单参数实例方法”的真实契约 +- 本轮已重写 `docs/zh-CN/tutorials/godot-integration.md`,把内容收口为“包关系、`project.godot` 接线、`[GetNode]` / + `[BindNodeSignal]` 协作顺序、运行时扩展边界、迁移提醒”,不再把旧 Godot API 列表当事实来源 +- `docs/zh-CN/tutorials/index.md` 的 Godot 教程入口摘要已同步改成当前采用路径,避免入口页继续把教程描述成对象池 / 性能优化总览 +- 本轮已重写 `docs/zh-CN/godot/index.md`,改成“模块定位、包关系、最小接入路径、关键入口、当前边界”的 landing page 结构, + 明确把 `[GetNode]`、`[BindNodeSignal]`、`AutoLoads`、`InputActions` 收口到 `GFramework.Godot.SourceGenerators` +- 本轮已重写 `docs/zh-CN/godot/architecture.md`,改成“锚点生命周期、`InstallGodotModule(...)` 执行顺序、`IGodotModule` + 契约边界”的结构,不再沿用旧版 `.Wait()` 和自动阶段广播叙述 +- 本轮已重写 `docs/zh-CN/godot/scene.md`,把内容收口为“公开入口、factory 真实行为、项目侧 router/root wiring、 + `ISceneBehaviorProvider` 与 `[AutoScene]` 的真实关系、当前边界”,不再继续虚构 `GodotSceneRouter` +- 本轮已重写 `docs/zh-CN/godot/ui.md`,把内容收口为“公开入口、layer behavior 语义、项目侧 router/root wiring、 + `IUiPageBehaviorProvider` 与 `[AutoUiPage]` 的真实关系、输入与暂停边界”,不再继续虚构 `GodotUiRouter` +- 本轮额外确认 Godot Scene / UI 的关键差异:`GodotSceneFactory` 在 provider 缺失时会回退到 `SceneBehaviorFactory`, + 而 `GodotUiFactory` 仍会在缺失 `IUiPageBehaviorProvider` 时直接抛异常;这已写入两页文档,避免继续把两者描述成同一种接入模型 +- 本轮已重写 `docs/zh-CN/godot/signal.md`,把内容收口为“当前公开入口、动态绑定最小接入路径、与 `[BindNodeSignal]` + 的分工、当前边界”,明确当前入口是 `Signal(...)` 而不是旧 `CreateSignalBuilder(...)` +- 本轮已重写 `docs/zh-CN/godot/extensions.md`,把内容收口为“真实扩展分组、`NodeExtensions` 实际成员、`UnRegisterWhenNodeExitTree(...)` + 生命周期边界、当前边界”,不再继续宣称存在覆盖所有 Godot 场景的万能扩展层 +- 本轮复核 `ai-libs/CoreGrid` 的动态绑定用法后,明确把 fluent API 定位为“动态对象 / 动态 signal 的运行时连接”,而把静态控件绑定继续归到 + `[BindNodeSignal]` 生成器链路 +- 本轮已重写 `docs/zh-CN/godot/logging.md`,把内容收口为“当前 provider / factory / logger 结构、最小接入路径、 + Godot 控制台输出语义、`[Log]` 协作边界、当前限制”,不再把直接改全局 provider 或 `AbstractGodotModule` 写成默认采用路径 +- 本轮额外复核 `GFramework.Godot/Logging/*.cs`、`GFramework.Core.Abstractions/Logging/LoggerFactoryResolver.cs`、 + `GFramework.Core/Logging/CachedLoggerFactory.cs` 与 `ai-libs/CoreGrid/global/GameEntryPoint.cs`,确认当前推荐接法应以 + `ArchitectureConfiguration.LoggerProperties.LoggerFactoryProvider` 为主,而不是先写 `LoggerFactoryResolver.Provider = ...` ### 当前决策 @@ -23,6 +54,20 @@ - `scene.md` 与 `ui.md` 的集成说明除目录布局外,也要保证标题层级能真实反映采用路径语义 - `gframework-pr-review` 继续以 latest-head unresolved thread 为主信号,同时显式声明支持的 AI reviewer 名单,避免 skill 声明与实际抓取能力再次漂移 +- `Godot.SourceGenerators` 专题页继续采用“源码 / 测试 / README 优先,`ai-libs/` 只补消费者 wiring”的证据顺序 +- `BindNodeSignal` 页面明确记录“当前不自动生成 `_Ready()` / `_ExitTree()`”,避免继续把它写成自动生命周期织入器 +- `auto-register-exported-collections` 页面明确区分“运行时 null 时跳过注册”和“配置错误时编译期报错”,避免旧文档把两类边界混为一谈 +- `godot-integration.md` 已重新成为可用的采用路径入口;后续 Godot 文档收口应优先处理 `godot/index.md` 和 `godot/architecture.md` +- `godot/index.md` 与 `godot/architecture.md` 现在都必须维持“运行时包与生成器包分边界”的写法,不能再把场景注入和项目元数据生成写回 + `GFramework.Godot` 运行时契约 +- `scene.md` 已明确记录“项目侧 router + Godot factory/registry/root”这一分工,后续不要再把 router 包装回 + `GFramework.Godot` 运行时 +- `ui.md` 已明确记录 `Page` 必须走 `PushAsync` / `ReplaceAsync`,`Show(..., UiLayer.Page)` 在当前实现中会抛异常; + 后续不要再把所有 UI 入口重新写回统一 `Show(...)` +- `signal.md` 已明确为 `Signal(...)` / `SignalBuilder` 的轻量 fluent 包装说明页,不再继续混入生成器职责 +- `extensions.md` 已明确限制在 `GodotPathExtensions`、`NodeExtensions`、`SignalFluentExtensions` 与 `UnRegisterExtension` + 这四组当前存在的扩展 +- `logging.md` 已完成收口;下一轮优先级转为评估当前 Godot 栏目恢复点是否可以迁入 `archive/`,并保留 PR review follow-up 入口 ### 验证 @@ -33,9 +78,26 @@ - `bash .agents/skills/gframework-doc-refresh/scripts/validate-code-blocks.sh docs/zh-CN/game/ui.md` - `bash -lc 'source .agents/skills/_shared/module-config.sh && get_readme_paths Core.SourceGenerators.Abstractions && if get_readme_paths Not.Real.Module; then exit 1; else echo unmapped-ok; fi'` - `cd docs && bun run build` +- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/source-generators/godot-project-generator.md` +- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/source-generators/get-node-generator.md` +- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/source-generators/bind-node-signal-generator.md` +- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/source-generators/auto-register-exported-collections-generator.md` +- `cd docs && bun run build` +- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/tutorials/godot-integration.md` +- `cd docs && bun run build` +- `rg -n "GetNodeX|CreateSignalBuilder|GodotGameArchitecture|AbstractGodotModule|InstallGodotModule\(|GFramework\\.Godot\\.Pool" docs/zh-CN/godot docs/zh-CN/tutorials -S` +- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/godot/index.md` +- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/godot/architecture.md` +- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/godot/scene.md` +- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/godot/ui.md` +- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/godot/signal.md` +- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/godot/extensions.md` +- `bash .agents/skills/gframework-doc-refresh/scripts/validate-all.sh docs/zh-CN/godot/logging.md` +- `rg -n "GodotSceneRouter|GodotUiRouter|CreateSignalBuilder|GetNodeX|InstallGodotModule\(" docs/zh-CN/godot -S` +- `cd docs && bun run build` ### 下一步 -1. 下一次推送后重新执行 `$gframework-pr-review`,确认 PR #268 的 CodeRabbit / Greptile open thread 是否关闭或减少 -2. 继续使用 `gframework-doc-refresh` 对 `Godot.SourceGenerators` 做真实模块扫描 -3. 优先刷新 `godot-project-generator.md`、`get-node-generator.md` 与 `bind-node-signal-generator.md` +1. 评估当前 Godot 栏目页面集是否已足够稳定,决定是否把当前恢复点收口并迁入 `archive/` +2. 如暂不归档,先把 active tracking / trace 进一步压缩到归档决策、当前风险与 PR 跟进入口 +3. 下一次推送后重新执行 `$gframework-pr-review`,确认 PR #268 的 CodeRabbit / Greptile open thread 是否关闭或减少 diff --git a/docs/zh-CN/godot/architecture.md b/docs/zh-CN/godot/architecture.md index b5efb31d..3261a6b3 100644 --- a/docs/zh-CN/godot/architecture.md +++ b/docs/zh-CN/godot/architecture.md @@ -1,612 +1,176 @@ --- title: Godot 架构集成 -description: Godot 架构集成提供了 GFramework 与 Godot 引擎的无缝连接,实现生命周期同步和模块化开发。 +description: 说明 AbstractArchitecture、ArchitectureAnchor 和 Godot 模块挂接的当前生命周期语义,避免继续沿用旧版 `.Wait()` 接法。 --- # Godot 架构集成 ## 概述 -Godot 架构集成是 GFramework.Godot 中连接框架与 Godot 引擎的核心组件。它提供了架构与 Godot 场景树的生命周期绑定、模块化扩展系统,以及与 -Godot 节点系统的深度集成。 +`GFramework.Godot` 当前的架构集成目标很直接:让 `Architecture` 能安全地感知 Godot `SceneTree` 生命周期,并在需要时把 +带 `Node` 的扩展模块挂到场景树上。 -通过 Godot 架构集成,你可以在 Godot 项目中使用 GFramework 的所有功能,同时保持与 Godot 引擎的完美兼容。 +当前真正参与这条链路的核心类型只有三类: -**主要特性**: +- `AbstractArchitecture`:在原有 `Architecture` 之上增加 Godot 生命周期绑定 +- `ArchitectureAnchor`:挂在 `SceneTree.Root` 下的锚点节点,负责把 `_ExitTree()` 事件转回架构销毁 +- `IGodotModule` / `AbstractGodotModule`:当模块本身需要携带 Godot `Node` 时使用 -- 架构与 Godot 生命周期自动同步 -- 模块化的 Godot 扩展系统 -- 架构锚点节点管理 -- 自动资源清理 -- 热重载支持 -- 与 Godot 场景树深度集成 +它不是另一套独立的模块系统,也不意味着所有模块都必须改成 `InstallGodotModule(...)`。 -## 核心概念 +## 什么时候该用 `AbstractArchitecture` -### 抽象架构 +当你的架构需要满足下面任一条件时,可以让它继承 `AbstractArchitecture`: -`AbstractArchitecture` 是 Godot 项目中架构的基类: +- 需要把架构生命周期绑定到 Godot `SceneTree` +- 需要在架构里安装带 `Node` 的扩展模块 +- 需要通过受保护的 `ArchitectureRoot` 访问锚点节点,继续挂接 Godot 子节点 + +如果你只是做普通的 Model / System / Utility 注册,`AbstractArchitecture` 的主要价值仍然是“让架构知道自己何时跟随 +Godot 场景树销毁”,而不是改变注册方式。 + +## 最小接入路径 + +### 常规模块仍然用 `InstallModule(...)` + +当前消费者 `ai-libs/CoreGrid` 的默认做法,是保持普通模块注册方式: ```csharp -public abstract class AbstractArchitecture : Architecture +using GFramework.Core.Abstractions.Architectures; +using GFramework.Core.Abstractions.Environment; +using GFramework.Godot.Architectures; + +namespace MyGame.Scripts.Core; + +public sealed class GameArchitecture( + IArchitectureConfiguration configuration, + IEnvironment environment) + : AbstractArchitecture(configuration, environment) { - protected Node ArchitectureRoot { get; } - protected abstract void InstallModules(); - protected Task InstallGodotModule(TModule module); -} -``` - -### 架构锚点 - -`ArchitectureAnchor` 是连接架构与 Godot 场景树的桥梁: - -```csharp -public partial class ArchitectureAnchor : Node -{ - public void Bind(Action onExit); - public override void _ExitTree(); -} -``` - -### Godot 模块 - -`IGodotModule` 定义了 Godot 特定的模块接口: - -```csharp -public interface IGodotModule : IArchitectureModule -{ - Node Node { get; } - void OnPhase(ArchitecturePhase phase, IArchitecture architecture); - void OnAttach(Architecture architecture); - void OnDetach(); -} -``` - -## 基本用法 - -### 创建 Godot 架构 - -```csharp -using GFramework.Godot.Architecture; -using GFramework.Core.Abstractions.Architecture; - -public class GameArchitecture : AbstractArchitecture -{ - // 单例实例 - public static GameArchitecture Interface { get; private set; } - - public GameArchitecture() - { - Interface = this; - } - protected override void InstallModules() { - // 注册 Model - RegisterModel(new PlayerModel()); - RegisterModel(new GameModel()); - - // 注册 System - RegisterSystem(new GameplaySystem()); - RegisterSystem(new AudioSystem()); - - // 注册 Utility - RegisterUtility(new StorageUtility()); + InstallModule(new UtilityModule()); + InstallModule(new ModelModule()); + InstallModule(new GameplayModule()); + InstallModule(new SystemModule()); } } ``` -### 在 Godot 场景中初始化架构 +这里继承 `AbstractArchitecture` 的意义,是把架构绑定到 Godot 生命周期,而不是把普通模块注册改写成 Godot 风格 API。 + +### 只有携带 `Node` 的模块才需要 `InstallGodotModule(...)` + +如果模块本身暴露一个 Godot `Node`,并且希望由架构锚点统一托管,可以这样写: ```csharp -using Godot; -using GFramework.Godot.Architecture; - -public partial class GameRoot : Node -{ - private GameArchitecture _architecture; - - public override void _Ready() - { - // 创建并初始化架构 - _architecture = new GameArchitecture(); - _architecture.InitializeAsync().AsTask().Wait(); - - GD.Print("架构已初始化"); - } -} -``` - -### 使用架构锚点 - -架构锚点会自动创建并绑定到场景树: - -```csharp -// 架构会自动创建锚点节点 -// 节点名称格式: __GFramework__GameArchitecture__[HashCode]__ArchitectureAnchor__ - -// 当场景树销毁时,锚点会自动触发架构清理 -``` - -## 高级用法 - -### 创建 Godot 模块 - -```csharp -using GFramework.Godot.Architecture; +using GFramework.Core.Abstractions.Architectures; +using GFramework.Godot.Architectures; using Godot; -public class CoroutineModule : AbstractGodotModule +namespace MyGame.Scripts.Core; + +public sealed class HudModule : AbstractGodotModule { - private Node _coroutineNode; - - public override Node Node => _coroutineNode; - - public CoroutineModule() + private readonly Control _root = new() { - _coroutineNode = new Node { Name = "CoroutineScheduler" }; - } + Name = "HudModule" + }; + + public override Node Node => _root; public override void Install(IArchitecture architecture) { - // 注册协程调度器 - var scheduler = new CoroutineScheduler(new GodotTimeSource()); - architecture.RegisterSystem(scheduler); - - GD.Print("协程模块已安装"); } - public override void OnPhase(ArchitecturePhase phase, IArchitecture architecture) + public override void OnAttach(GFramework.Core.Architectures.Architecture architecture) { - if (phase == ArchitecturePhase.Ready) - { - GD.Print("协程模块已就绪"); - } } public override void OnDetach() { - GD.Print("协程模块已分离"); - _coroutineNode?.QueueFree(); + _root.QueueFree(); } } ``` -### 安装 Godot 模块 +这类模块的关键点不是“注册更多框架能力”,而是“让模块节点跟着架构锚点进出场景树”。 +真正调用 `InstallGodotModule(...)` 时,也应该把它放在能够接受异步挂接流程的初始化路径里,而不是继续沿用旧文档里的 +`.Wait()` 叙述。 -```csharp -public class GameArchitecture : AbstractArchitecture -{ - protected override void InstallModules() - { - // 安装核心模块 - RegisterModel(new PlayerModel()); - RegisterSystem(new GameplaySystem()); +## 当前生命周期 - // 安装 Godot 模块 - InstallGodotModule(new CoroutineModule()).Wait(); - InstallGodotModule(new SceneModule()).Wait(); - InstallGodotModule(new UiModule()).Wait(); - } -} -``` +### 初始化阶段 -### 访问架构根节点 +`AbstractArchitecture.OnInitialize()` 目前会按这个顺序工作: -```csharp -public class SceneModule : AbstractGodotModule -{ - private Node _sceneRoot; +1. 生成唯一的锚点节点名称 +2. 调用 `AttachToGodotLifecycle()` +3. 在可用的 `SceneTree` 上创建并绑定 `ArchitectureAnchor` +4. 执行你重写的 `InstallModules()` - public override Node Node => _sceneRoot; +也就是说,Godot 生命周期绑定先发生,业务模块注册后发生。 - public SceneModule() - { - _sceneRoot = new Node { Name = "SceneRoot" }; - } +### `InstallGodotModule(...)` 的执行顺序 - public override void Install(IArchitecture architecture) - { - // 访问架构根节点 - if (architecture is AbstractArchitecture godotArch) - { - var root = godotArch.ArchitectureRoot; - root.AddChild(_sceneRoot); - } - } -} -``` +当前实现里,`InstallGodotModule(...)` 会: -### 监听架构阶段 +1. 检查模块参数是否为 `null` +2. 检查 `_anchor` 是否已初始化 +3. 先执行 `module.Install(this)` +4. 把模块登记进内部 `_extensions` +5. `await anchor.WaitUntilReadyAsync()` +6. 通过 `CallDeferred(AddChild, module.Node)` 把模块节点挂到锚点下 +7. 调用 `module.OnAttach(this)` -```csharp -public class AnalyticsModule : AbstractGodotModule -{ - private Node _analyticsNode; +这条顺序有两个实际意义: - public override Node Node => _analyticsNode; +- 模块会在挂接节点前先完成框架侧注册 +- 只有等锚点真正 ready 后,才进入需要访问 Godot 节点 API 的附加阶段 - public AnalyticsModule() - { - _analyticsNode = new Node { Name = "Analytics" }; - } +### 销毁阶段 - public override void Install(IArchitecture architecture) - { - // 安装分析系统 - } +`ArchitectureAnchor._ExitTree()` 会触发绑定好的退出回调,随后 `AbstractArchitecture` 会开始观察异步销毁流程: - public override void OnPhase(ArchitecturePhase phase, IArchitecture architecture) - { - switch (phase) - { - case ArchitecturePhase.Initializing: - GD.Print("架构正在初始化"); - break; +- 防止重复销毁 +- 依次调用已登记 Godot 模块的 `OnDetach()` +- 清空内部扩展列表 +- 再进入基类 `DestroyAsync()` - case ArchitecturePhase.Ready: - GD.Print("架构已就绪,开始追踪"); - StartTracking(); - break; +如果异步销毁抛异常,当前实现会把错误写到 Godot 错误输出,而不是静默吞掉。 - case ArchitecturePhase.Destroying: - GD.Prin构正在销毁,停止追踪"); - StopTracking(); - break; - } - } +## 当前边界 - private void StartTracking() { } - private void StopTracking() { } -} -``` +### 没有锚点时不会偷偷安装模块 -### 自定义架构配置 +`GFramework.Godot.Tests/Architectures/AbstractArchitectureModuleInstallationTests.cs` 已覆盖一个关键边界: -```csharp -using GFramework.Core.Abstractions.Architecture; -using GFramework.Core.Abstractions.Environment; +- 当锚点尚未初始化时,`InstallGodotModule(...)` 会直接抛 `InvalidOperationException("Anchor not initialized")` +- 失败发生在 `module.Install(...)` 之前,因此不会留下半安装副作用 -public class GameArchitecture : AbstractArchitecture -{ - public GameArchitecture() : base( - configuration: CreateConfiguration(), - environment: CreateEnvironment() - ) - { - } +这也是为什么文档不应该再把 `InstallGodotModule(...).Wait()` 写成一种随处可用的默认初始化方式。 - private static IArchitectureConfiguration CreateConfiguration() - { - return new ArchitectureConfiguration - { - EnableLogging - LogLevel = LogLevel.Debug - }; - } +### `AbstractGodotModule` 只是便捷基类,不代表自动阶段广播 - private static IEnvironment CreateEnvironment() - { - return new DefaultEnvironment - { - IsDevelopment = OS.IsDebugBuild() - }; - } +当前接口 `IGodotModule` 真正保证的成员只有: - protected override void InstallModules() - { - // 根据环境配置安装模块 - if (Environment.IsDevelopment) - { - InstallGodotModule(new DebugModule()).Wait(); - } +- `Node` +- `Install(IArchitecture architecture)` +- `OnAttach(Architecture architecture)` +- `OnDetach()` - // 安装核心模块 - RegisterModel(new PlayerModel()); - RegisterSystem(new GameplaySystem()); - } -} -``` +`AbstractGodotModule` 里虽然保留了 `OnPhase(...)` / `OnArchitecturePhase(...)` 虚方法,但它们不在当前接口契约内,也没有在 +这条挂接流程里形成稳定的自动广播语义。不要把它写成当前公开保证。 -### 热重载支持 +### `ArchitectureRoot` 只在锚点就绪后可用 -```csharp -public class GameArchitecture : AbstractArchitecture -{ - private static bool _initialized; +`ArchitectureRoot` 是受保护属性,底层直接返回 `_anchor`。如果锚点尚未准备好或架构已经失效,它会抛 +`InvalidOperationException("Architecture root not ready")`。因此它适合放在明确依赖锚点存在的挂接逻辑里,而不是拿来做 +任意时机的全局节点查找。 - protected override void OnInitialize() - { - // 防止热重载时重复初始化 - if (_initialized) - { - GD.Print("架构已初始化,跳过重复初始化"); - return; - } +## 继续阅读 - base.OnInitialize(); - _initialized = true; - } - - protected override async ValueTask OnDestroyAsync() - { - await base.OnDestroyAsync(); - _initialized = false; - } -} -``` - -### 在节点中使用架构 - -```csharp -using Godot; -using GFramework.Core.Abstractions.Controller; -using GFramework.Core.SourceGenerators.Abstractions.Rule; - -[ContextAware] -public partial class Player : CharacterBody2D, IController -{ - public override void _Ready() - { - // 使用扩展方法访问架构([ContextAware] 实现 IContextAware 接口) - var playerModel = this.GetModel(); - var gameplaySystem = this.GetSystem(); - - // 发送事件 - this.SendEvent(new PlayerSpawnedEvent()); - - // 执行命令 - this.SendCommand(new InitPlayerCommand()); - } - - public override void _Process(double delta) - { - // 在 Process 中使用架构组件 - var inputSystem = this.GetSystem(); - var movement = inputSystem.GetMovementInput(); - - Velocity = movement * 200; - MoveAndSlide(); - } -} -``` - -### 多架构支持 - -```csharp -// 游戏架构 -public class GameArchitecture : AbstractArchitecture -{ - public static GameArchitecture Interface { get; private set; } - - public GameArchitecture() - { - Interface = this; - } - - protected override void InstallModules() - { - RegisterModel(new PlayerModel()); - RegisterSystem(new GameplaySystem()); - } -} - -// UI 架构 -public class UiArchitecture : AbstractArchitecture -{ - public static UiArchitecture Interface { get; private set; } - - public UiArchitecture() - { - Interface = this; - } - - protected override void InstallModules() - { - RegisterModel(new UiModel()); - RegisterSystem(new UiSystem()); - } -} - -// 在不同节点中使用不同架构 -[ContextAware] -public partial class GameNode : Node, IController -{ - // 配置使用 GameArchitecture 的上下文提供者 - static GameNode() - { - SetContextProvider(new GameContextProvider()); - } -} - -[ContextAware] -public partial class UiNode : Control, IController -{ - // 配置使用 UiArchitecture 的上下文提供者 - static UiNode() - { - SetContextProvider(new UiContextProvider()); - } -} -``` - -## 最佳实践 - -1. **使用单例模式**:为架构提供全局访问点 - ```csharp - public class GameArchitecture : AbstractArchitecture - { - public static GameArchitecture Interface { get; private set; } - - public GameArchitecture() - { - Interface = this; - } - } - ``` - -2. **在根节点初始化架构**:确保架构在所有节点之前就绪 - ```csharp - public partial class GameRoot : Node - { - public override void _Ready() - { - new GameArchitecture().InitializeAsync().AsTask().Wait(); - } - } - ``` - -3. **使用 Godot 模块组织功能**:将相关功能封装为模块 - ```csharp - InstallGodotModule(new CoroutineModule()).Wait(); - InstallGodotModule(new SceneModule()).Wait(); - InstallGodotModule(new UiModule()).Wait(); - ``` - -4. **利用架构阶段钩子**:在适当的时机执行逻辑 - ```csharp - public override void OnPhase(ArchitecturePhase phase, IArchitecture architecture) - { - if (phase == ArchitecturePhase.Ready) - { - // 架构就绪后的初始化 - } - } - ``` - -5. **正确清理资源**:在 OnDetach 中释放 Godot 节点 - ```csharp - public override void OnDetach() - { - _node?.QueueFree(); - _node = null; - } - ``` - -6. **避免在构造函数中访问架构**:使用 _Ready 或 OnPhase - ```csharp - ✗ public Player() - { - var model = this.GetModel(); // 架构可能未就绪 - } - - ✓ public override void _Ready() - { - var model = this.GetModel(); // 安全 - } - ``` - -## 常见问题 - -### 问题:架构什么时候初始化? - -**解答**: -在根节点的 `_Ready` 方法中初始化: - -```csharp -public partial class GameRoot : Node -{ - public override void _Ready() - { - new GameArchitecture().InitializeAsync().AsTask().Wait(); - } -} -``` - -### 问题:如何在节点中访问架构? - -**解答**: -使用 `[ContextAware]` 特性或直接使用单例: - -```csharp -using GFramework.Core.SourceGenerators.Abstractions.Rule; - -// 方式 1: 使用 [ContextAware] 特性(推荐) -[ContextAware] -public partial class Player : Node, IController -{ - public override void _Ready() - { - // 使用扩展方法访问架构([ContextAware] 实现 IContextAware 接口) - var model = this.GetModel(); - var system = this.GetSystem(); - } -} - -// 方式 2: 直接使用单例 -public partial class Enemy : Node -{ - public override void _Ready() - { - var model = GameArchitecture.Interface.GetModel(); - } -} -``` - -**注意**: - -- `IController` 是标记接口,不包含任何方法 -- 架构访问能力由 `[ContextAware]` 特性提供 -- `[ContextAware]` 会自动生成 `Context` 属性和实现 `IContextAware` 接口 -- 扩展方法(如 `this.GetModel()`)基于 `IContextAware` 接口,而非 `IController` - -### 问题:架构锚点节点是什么? - -**解答**: -架构锚点是一个隐藏的节点,用于将架构绑定到 Godot 场景树。当场景树销毁时,锚点会自动触发架构清理。 - -### 问题:如何支持热重载? - -**解答**: -使用静态标志防止重复初始化: - -```csharp -private static bool _initialized; - -protected override void OnInitialize() -{ - if (_initialized) return; - base.OnInitialize(); - _initialized = true; -} -``` - -### 问题:可以有多个架构吗? - -**解答**: -可以,但通常一个游戏只需要一个主架构。如果需要多个架构,为每个架构提供独立的单例: - -```csharp -public class GameArchitecture : AbstractArchitecture -{ - public static GameArchitecture Interface { get; private set; } -} - -public class UiArchitecture : AbstractArchitecture -{ - public static UiArchitecture Interface { get; private set; } -} -``` - -### 问题:Godot 模块和普通模块有什么区别? - -**解答**: - -- **普通模块**:纯 C# 逻辑,不依赖 Godot -- **Godot 模块**:包含 Godot 节点,与场景树集成 - -```csharp -// 普通模块 -InstallModule(new CoreModule()); - -// Godot 模块 -InstallGodotModule(new SceneModule()).Wait(); -``` - -## 相关文档 - -- [架构组件](/zh-CN/core/architecture) - 核心架构系统 -- [生命周期管理](/zh-CN/core/lifecycle) - 组件生命周期 -- [Godot 场景系统](/zh-CN/godot/scene) - Godot 场景集成 -- [Godot UI 系统](/zh-CN/godot/ui) - Godot UI 集成 -- [Godot 扩展](/zh-CN/godot/extensions) - Godot 扩展方法 +1. [Godot 运行时集成](./index.md) +2. [Godot 集成教程](../tutorials/godot-integration.md) +3. [Godot 场景系统](./scene.md) +4. [Godot UI 系统](./ui.md) diff --git a/docs/zh-CN/godot/extensions.md b/docs/zh-CN/godot/extensions.md index e7426b13..8f006692 100644 --- a/docs/zh-CN/godot/extensions.md +++ b/docs/zh-CN/godot/extensions.md @@ -1,328 +1,181 @@ -# Godot 扩展方法 (Godot Extensions) +--- +title: Godot 扩展方法 +description: 以当前 GFramework.Godot.Extensions 源码为准,说明路径、Node、signal 和 unregister 扩展的真实成员与边界。 +--- -## 概述 +# Godot 扩展方法 -Godot 扩展方法模块为 Godot 引擎提供了丰富的便捷扩展方法集合。这些扩展方法简化了常见的 Godot -开发任务,提高了代码的可读性和开发效率。该模块遵循流畅接口设计原则,支持链式调用。 +`GFramework.Godot.Extensions` 当前并不是“覆盖所有 Godot 节点操作”的万能层。按源码看,它实际公开的扩展主要只有四组: -## 模块结构 +- `GodotPathExtensions` +- `NodeExtensions` +- `SignalFluentExtensions` +- `UnRegisterExtension` -```mermaid -graph TD - A[Extensions] --> B[GodotPathExtensions] - A --> C[NodeExtensions] - A --> D[SignalFluentExtensions] - A --> E[UnRegisterExtension] - D --> F[SignalBuilder] - - B --> G[路径判断扩展] - C --> H[节点生命周期] - C --> I[节点查询] - C --> J[场景树操作] - C --> K[输入控制] - C --> L[调试工具] - D --> M[信号连接系统] - E --> N[事件管理] -``` +这页的重点应该是识别这些扩展各自解决什么问题,以及哪些旧文档里的“大而全能力”现在并不存在。 -## 扩展模块详解 +## 当前公开入口 -### 1. 路径扩展 (GodotPathExtensions) +### `GodotPathExtensions` -提供 Godot 虚拟路径的判断和识别功能。 +这组扩展只负责判断 Godot 虚拟路径前缀: -**主要方法:** +- `IsUserPath(this string path)` +- `IsResPath(this string path)` +- `IsGodotPath(this string path)` -- `IsUserPath()` - 判断是否为 `user://` 路径 -- `IsResPath()` - 判断是否为 `res://` 路径 -- `IsGodotPath()` - 判断是否为 Godot 虚拟路径 - -**使用示例:** +它们不做文件访问,也不解析目录结构,只是用字符串前缀判断 `user://` 和 `res://`。 ```csharp -string savePath = "user://save.dat"; -string configPath = "res://config.json"; -string logPath = "C:/logs/debug.log"; +using GFramework.Godot.Extensions; -if (savePath.IsUserPath()) Console.WriteLine("用户数据路径"); -if (configPath.IsResPath()) Console.WriteLine("资源路径"); -if (logPath.IsGodotPath()) Console.WriteLine("Godot 虚拟路径"); -else Console.WriteLine("文件系统路径"); -``` - -### 2. 节点扩展 (NodeExtensions) - -最丰富的扩展模块,提供全面的节点操作功能。 - -#### 节点生命周期管理 - -```csharp -// 安全释放节点 -node.QueueFreeX(); // 延迟释放 -node.FreeX(); // 立即释放 - -// 等待节点就绪 -await node.WaitUntilReadyAsync(); - -// 检查节点有效性 -if (node.IsValidNode()) Console.WriteLine("节点有效"); -if (node.IsInvalidNode()) Console.WriteLine("节点无效"); -``` - -#### 节点查询操作 - -```csharp -// 查找子节点 -var sprite = node.FindChildX("Sprite"); -var parent = node.GetParentX(); - -// 获取或创建节点 -var panel = parent.GetOrCreateNode("MainPanel"); - -// 遍历子节点 -node.ForEachChild(sprite => { - sprite.Modulate = Colors.White; -}); -``` - -#### 场景树操作 - -```csharp -// 获取根节点 -var root = node.GetRootNodeX(); - -// 异步添加子节点 -await parent.AddChildXAsync(childNode); - -// 设置场景树暂停状态 -node.Paused(true); // 暂停 -node.Paused(false); // 恢复 -``` - -#### 输入控制 - -```csharp -// 标记输入事件已处理 -node.SetInputAsHandled(); - -// 禁用/启用输入 -node.DisableInput(); -node.EnableInput(); -``` - -#### 调试工具 - -```csharp -// 打印节点路径 -node.LogNodePath(); - -// 打印节点树 -node.PrintTreeX(); - -// 安全延迟调用 -node.SafeCallDeferred("UpdateUI"); -``` - -#### 类型转换 - -```csharp -// 安全的类型转换 -var button = node.OfType