From d369118351d00bd937fa0c5e05f090bfac3c18c8 Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Mon, 20 Apr 2026 10:19:11 +0800 Subject: [PATCH] =?UTF-8?q?fix(coroutine):=20=E6=94=B6=E5=8F=A3=20Timing?= =?UTF-8?q?=20=E6=B5=8B=E8=AF=95=E5=AE=BF=E4=B8=BB=E6=B8=85=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复 Timing 共享单例仅在当前实例持有引用时才清理,避免测试宿主误伤其他实例 - 新增 TimingTests 的 NonParallelizable 约束,避免静态实例槽位并发污染 - 更新 coroutine optimization 跟踪与 trace,记录 PR #259 review 收口与验证结果 --- GFramework.Godot.Tests/Coroutine/TimingTests.cs | 1 + GFramework.Godot/Coroutine/Timing.Testing.cs | 3 ++- GFramework.Godot/Coroutine/Timing.cs | 12 ++++++++---- .../todos/coroutine-optimization-tracking.md | 11 +++++++++-- .../traces/coroutine-optimization-trace.md | 5 +++++ 5 files changed, 25 insertions(+), 7 deletions(-) diff --git a/GFramework.Godot.Tests/Coroutine/TimingTests.cs b/GFramework.Godot.Tests/Coroutine/TimingTests.cs index bbfc7561..5db137ca 100644 --- a/GFramework.Godot.Tests/Coroutine/TimingTests.cs +++ b/GFramework.Godot.Tests/Coroutine/TimingTests.cs @@ -10,6 +10,7 @@ namespace GFramework.Godot.Tests.Coroutine; /// 验证 在纯托管测试宿主下仍保持与真实 Godot 生命周期一致的阶段语义。 /// [TestFixture] +[NonParallelizable] public sealed class TimingTests { private Timing _timing = null!; diff --git a/GFramework.Godot/Coroutine/Timing.Testing.cs b/GFramework.Godot/Coroutine/Timing.Testing.cs index 3aea3a1c..ea9a24ba 100644 --- a/GFramework.Godot/Coroutine/Timing.Testing.cs +++ b/GFramework.Godot/Coroutine/Timing.Testing.cs @@ -119,6 +119,7 @@ public partial class Timing /// /// 清理测试初始化留下的实例槽位与调度器状态,避免跨测试污染静态单例表。 + /// 仅当当前测试宿主仍持有共享单例引用时才会清理 `_instance`,以免误伤同进程内的其他宿主。 /// internal void DisposeForTests() { @@ -130,7 +131,7 @@ public partial class Timing ActiveInstances[_instanceId] = null; } - CleanupInstanceIfNecessary(); + CleanupInstanceIfNecessary(this); _processScheduler = null; _processIgnorePauseScheduler = null; diff --git a/GFramework.Godot/Coroutine/Timing.cs b/GFramework.Godot/Coroutine/Timing.cs index d4d0bf51..a2094ffc 100644 --- a/GFramework.Godot/Coroutine/Timing.cs +++ b/GFramework.Godot/Coroutine/Timing.cs @@ -185,15 +185,19 @@ public partial class Timing : Node ActiveInstances[_instanceId] = null; } - CleanupInstanceIfNecessary(); + CleanupInstanceIfNecessary(this); } /// - /// 清理单例引用。 + /// 仅在当前实例仍持有共享单例引用时清理它,避免多宿主场景误清其他实例。 /// - private static void CleanupInstanceIfNecessary() + /// 正在退出生命周期的实例。 + private static void CleanupInstanceIfNecessary(Timing instance) { - _instance = null; + if (ReferenceEquals(_instance, instance)) + { + _instance = null; + } } /// diff --git a/ai-plan/public/coroutine-optimization/todos/coroutine-optimization-tracking.md b/ai-plan/public/coroutine-optimization/todos/coroutine-optimization-tracking.md index 80a91b68..8a16f54b 100644 --- a/ai-plan/public/coroutine-optimization/todos/coroutine-optimization-tracking.md +++ b/ai-plan/public/coroutine-optimization/todos/coroutine-optimization-tracking.md @@ -12,6 +12,7 @@ - 当前焦点: - 已为 `Timing` 补齐纯托管测试宿主入口,允许在 `dotnet test` 下验证 Godot 协程宿主阶段语义,而不依赖原生 `Node` 构造 - 已补充 `GFramework.Godot.Tests/Coroutine/TimingTests.cs`,锁定暂停、segment 路由和阶段型等待指令的回归覆盖 + - 已根据 PR #259 的最新 CodeRabbit review 收口测试宿主清理对称性,并将 `TimingTests` 固定为非并行执行 - 下一轮优先补“仍需真实场景树参与”的归属协程 / 退树语义,或转入文档迁移收口,不再回到“Godot 宿主没有自动化回归”的旧状态 ## 当前状态摘要 @@ -32,6 +33,9 @@ - 暂停时 `Process` / `ProcessIgnorePause` 的差异 - `Process` / `PhysicsProcess` / `DeferredProcess` 的推进边界 - `WaitForFixedUpdate` 与 `WaitForEndOfFrame` 的阶段型等待语义 +- 针对 PR #259 的最新未解决 review 线程,已补充两项收口: + - `TimingTests` 已添加 `[NonParallelizable]`,避免共享静态实例槽位在 NUnit 并行执行时互相污染 + - `Timing` 的测试清理与运行时退树清理现仅在当前实例持有共享 `_instance` 引用时才会清空单例状态 - `dotnet test GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release --no-restore` 当前通过,合计 `58` 个测试 ## 当前风险 @@ -58,9 +62,12 @@ - `dotnet test GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release --no-restore` - 结果:通过 - 备注:Godot 测试项目共 `58` 个测试全部通过 +- `dotnet test GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release --filter "FullyQualifiedName~TimingTests" --no-restore` + - 结果:通过 + - 备注:针对 PR #259 review 修复后的 `TimingTests` 共 `5` 个测试全部通过 ## 下一步 1. 若继续补验证,优先只做真实场景树相关的节点归属 / 退树 / `queue_free` 回归,不再重新设计 `Timing` 纯托管宿主 -2. 若转入文档收口,优先清理其余 `StartCoroutine()/StopCoroutine()` 残留,并补 Godot 新入口与阶段等待的迁移对照 -3. 若下一轮涉及更大范围语义调整,再单独开新恢复点,避免把测试、文档和 API 改动重新混成一次任务 +2. 当前 PR 合并前可直接回到 GitHub 处理这两条 review 线程的回复与 resolve,避免后续重复审阅同一问题 +3. 若转入文档收口,优先清理其余 `StartCoroutine()/StopCoroutine()` 残留,并补 Godot 新入口与阶段等待的迁移对照 diff --git a/ai-plan/public/coroutine-optimization/traces/coroutine-optimization-trace.md b/ai-plan/public/coroutine-optimization/traces/coroutine-optimization-trace.md index 4b1f4f93..97d5cc22 100644 --- a/ai-plan/public/coroutine-optimization/traces/coroutine-optimization-trace.md +++ b/ai-plan/public/coroutine-optimization/traces/coroutine-optimization-trace.md @@ -50,6 +50,11 @@ - 完成验证: - `dotnet test GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release --filter "FullyQualifiedName~TimingTests" --no-restore` - `dotnet test GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release --no-restore` +- 同日根据 PR #259 的最新 unresolved CodeRabbit review 继续收口: + - 在 `GFramework.Godot.Tests/Coroutine/TimingTests.cs` 为 fixture 添加 `[NonParallelizable]`,避免静态实例槽位在 NUnit 并行执行时互相污染 + - 将 `Timing` 的 `_instance` 清理改为“仅当当前实例仍持有共享单例引用时才执行”,同时覆盖运行时 `_ExitTree()` 与测试入口 `DisposeForTests()` +- 额外完成验证: + - `dotnet test GFramework.Godot.Tests/GFramework.Godot.Tests.csproj -c Release --filter "FullyQualifiedName~TimingTests" --no-restore` ### 下一步