From 8831cb42a8e183a6831a661539ef110a0582aeb0 Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Tue, 21 Apr 2026 16:15:36 +0800 Subject: [PATCH] =?UTF-8?q?fix(core):=20=E7=BB=9F=E4=B8=80=E4=BA=8B?= =?UTF-8?q?=E4=BB=B6=E7=AD=BE=E5=90=8D=E5=B9=B6=E6=B8=85=E7=90=86MA0046?= =?UTF-8?q?=E5=91=8A=E8=AD=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hBc --- .../ArchitecturePhaseChangedEventArgs.cs | 24 ++++++++ .../AsyncLogFlushCompletedEventArgs.cs | 26 +++++++++ .../ArchitectureLifecycleBehaviorTests.cs | 35 +++++++++++- .../Architectures/TestArchitectureBase.cs | 4 +- .../Coroutine/CoroutineSchedulerTests.cs | 57 +++++++++++++++++++ .../Logging/AsyncLogAppenderTests.cs | 28 ++++++++- GFramework.Core/Architectures/Architecture.cs | 22 +++++-- .../Architectures/ArchitectureLifecycle.cs | 7 +-- .../ArchitecturePhaseCoordinator.cs | 9 +-- .../Coroutine/CoroutineExceptionEventArgs.cs | 29 ++++++++++ .../Coroutine/CoroutineFinishedEventArgs.cs | 42 ++++++++++++++ .../Coroutine/CoroutineScheduler.cs | 8 +-- .../Logging/Appenders/AsyncLogAppender.cs | 9 +-- GFramework.Godot/Coroutine/Timing.cs | 12 ++-- .../analyzer-warning-reduction-tracking.md | 31 +++++++--- .../analyzer-warning-reduction-trace.md | 32 +++++++++++ docs/zh-CN/core/architecture.md | 3 + docs/zh-CN/core/lifecycle.md | 4 +- 18 files changed, 333 insertions(+), 49 deletions(-) create mode 100644 GFramework.Core.Abstractions/Architectures/ArchitecturePhaseChangedEventArgs.cs create mode 100644 GFramework.Core.Abstractions/Logging/AsyncLogFlushCompletedEventArgs.cs create mode 100644 GFramework.Core/Coroutine/CoroutineExceptionEventArgs.cs create mode 100644 GFramework.Core/Coroutine/CoroutineFinishedEventArgs.cs diff --git a/GFramework.Core.Abstractions/Architectures/ArchitecturePhaseChangedEventArgs.cs b/GFramework.Core.Abstractions/Architectures/ArchitecturePhaseChangedEventArgs.cs new file mode 100644 index 00000000..2ccdcded --- /dev/null +++ b/GFramework.Core.Abstractions/Architectures/ArchitecturePhaseChangedEventArgs.cs @@ -0,0 +1,24 @@ +using GFramework.Core.Abstractions.Enums; + +namespace GFramework.Core.Abstractions.Architectures; + +/// +/// 表示架构阶段变化事件的数据。 +/// 该类型用于向事件订阅者传递当前已进入的阶段值。 +/// +public sealed class ArchitecturePhaseChangedEventArgs : EventArgs +{ + /// + /// 初始化 的新实例。 + /// + /// 当前已进入的架构阶段。 + public ArchitecturePhaseChangedEventArgs(ArchitecturePhase phase) + { + Phase = phase; + } + + /// + /// 获取当前已进入的架构阶段。 + /// + public ArchitecturePhase Phase { get; } +} diff --git a/GFramework.Core.Abstractions/Logging/AsyncLogFlushCompletedEventArgs.cs b/GFramework.Core.Abstractions/Logging/AsyncLogFlushCompletedEventArgs.cs new file mode 100644 index 00000000..793b35ac --- /dev/null +++ b/GFramework.Core.Abstractions/Logging/AsyncLogFlushCompletedEventArgs.cs @@ -0,0 +1,26 @@ +namespace GFramework.Core.Abstractions.Logging; + +/// +/// 表示异步日志刷新完成事件的数据。 +/// 该类型用于告知订阅者本次刷新是否在超时时间内成功完成。 +/// +public sealed class AsyncLogFlushCompletedEventArgs : EventArgs +{ + /// + /// 初始化 的新实例。 + /// + /// + /// 刷新是否成功完成。 + /// 为 表示所有待处理日志都已在超时前落地; + /// 为 表示刷新超时或输出器已不可用。 + /// + public AsyncLogFlushCompletedEventArgs(bool success) + { + Success = success; + } + + /// + /// 获取刷新是否成功完成。 + /// + public bool Success { get; } +} diff --git a/GFramework.Core.Tests/Architectures/ArchitectureLifecycleBehaviorTests.cs b/GFramework.Core.Tests/Architectures/ArchitectureLifecycleBehaviorTests.cs index 943e2bfd..05f98acc 100644 --- a/GFramework.Core.Tests/Architectures/ArchitectureLifecycleBehaviorTests.cs +++ b/GFramework.Core.Tests/Architectures/ArchitectureLifecycleBehaviorTests.cs @@ -62,6 +62,35 @@ public class ArchitectureLifecycleBehaviorTests await architecture.DestroyAsync(); } + /// + /// 验证阶段变更事件会以架构实例作为 sender,并通过事件参数暴露阶段值。 + /// + [Test] + public async Task InitializeAsync_Should_Raise_PhaseChanged_With_Sender_And_EventArgs() + { + var architecture = new PhaseTrackingArchitecture(); + var observations = new List<(object? Sender, ArchitecturePhase Phase)>(); + + architecture.PhaseChanged += (sender, eventArgs) => observations.Add((sender, eventArgs.Phase)); + + await architecture.InitializeAsync(); + + Assert.That(observations, Is.Not.Empty); + Assert.That(observations.All(item => ReferenceEquals(item.Sender, architecture)), Is.True); + Assert.That(observations.Select(static item => item.Phase), Is.EqualTo(new[] + { + ArchitecturePhase.BeforeUtilityInit, + ArchitecturePhase.AfterUtilityInit, + ArchitecturePhase.BeforeModelInit, + ArchitecturePhase.AfterModelInit, + ArchitecturePhase.BeforeSystemInit, + ArchitecturePhase.AfterSystemInit, + ArchitecturePhase.Ready + })); + + await architecture.DestroyAsync(); + } + /// /// 验证用户初始化失败时,等待 Ready 的任务会失败并进入 FailedInitialization 阶段。 /// @@ -183,7 +212,7 @@ public class ArchitectureLifecycleBehaviorTests public PhaseTrackingArchitecture(Action? onInitializeAction = null) { _onInitializeAction = onInitializeAction; - PhaseChanged += phase => PhaseHistory.Add(phase); + PhaseChanged += (_, eventArgs) => PhaseHistory.Add(eventArgs.Phase); } /// @@ -214,7 +243,7 @@ public class ArchitectureLifecycleBehaviorTests public DestroyOrderArchitecture(List destroyOrder) { _destroyOrder = destroyOrder; - PhaseChanged += phase => PhaseHistory.Add(phase); + PhaseChanged += (_, eventArgs) => PhaseHistory.Add(eventArgs.Phase); } /// @@ -247,7 +276,7 @@ public class ArchitectureLifecycleBehaviorTests public FailingInitializationArchitecture(List destroyOrder) { _destroyOrder = destroyOrder; - PhaseChanged += phase => PhaseHistory.Add(phase); + PhaseChanged += (_, eventArgs) => PhaseHistory.Add(eventArgs.Phase); } /// diff --git a/GFramework.Core.Tests/Architectures/TestArchitectureBase.cs b/GFramework.Core.Tests/Architectures/TestArchitectureBase.cs index 90f3d436..57cf9a71 100644 --- a/GFramework.Core.Tests/Architectures/TestArchitectureBase.cs +++ b/GFramework.Core.Tests/Architectures/TestArchitectureBase.cs @@ -43,6 +43,6 @@ public abstract class TestArchitectureBase : Architecture _postRegistrationHook?.Invoke(this); // 订阅阶段变更事件以记录历史 - PhaseChanged += phase => PhaseHistory.Add(phase); + PhaseChanged += (_, eventArgs) => PhaseHistory.Add(eventArgs.Phase); } -} \ No newline at end of file +} diff --git a/GFramework.Core.Tests/Coroutine/CoroutineSchedulerTests.cs b/GFramework.Core.Tests/Coroutine/CoroutineSchedulerTests.cs index b14833ad..432eddf8 100644 --- a/GFramework.Core.Tests/Coroutine/CoroutineSchedulerTests.cs +++ b/GFramework.Core.Tests/Coroutine/CoroutineSchedulerTests.cs @@ -331,6 +331,63 @@ public class CoroutineSchedulerTests Assert.That(_scheduler.ActiveCoroutineCount, Is.EqualTo(0)); } + /// + /// 验证完成事件会把调度器实例、句柄和完成结果暴露给订阅者。 + /// + [Test] + public void Run_Should_Raise_OnCoroutineFinished_With_EventArgs() + { + object? observedSender = null; + CoroutineFinishedEventArgs? observedArgs = null; + + _scheduler.OnCoroutineFinished += (sender, eventArgs) => + { + observedSender = sender; + observedArgs = eventArgs; + }; + + var handle = _scheduler.Run(CreateSimpleCoroutine()); + + _scheduler.Update(); + + Assert.Multiple(() => + { + Assert.That(observedSender, Is.SameAs(_scheduler)); + Assert.That(observedArgs, Is.Not.Null); + Assert.That(observedArgs!.Handle, Is.EqualTo(handle)); + Assert.That(observedArgs.CompletionStatus, Is.EqualTo(CoroutineCompletionStatus.Completed)); + Assert.That(observedArgs.Exception, Is.Null); + }); + } + + /// + /// 验证异常事件会把调度器实例、失败句柄和异常对象暴露给订阅者。 + /// + [Test] + public async Task Scheduler_Should_Raise_OnCoroutineException_With_EventArgs() + { + var exceptionSource = + new TaskCompletionSource<(object? Sender, CoroutineExceptionEventArgs EventArgs)>( + TaskCreationOptions.RunContinuationsAsynchronously); + _scheduler.OnCoroutineException += (sender, eventArgs) => + { + exceptionSource.TrySetResult((sender, eventArgs)); + }; + + var handle = _scheduler.Run(CreateExceptionCoroutine()); + + _scheduler.Update(); + var observation = await exceptionSource.Task; + + Assert.Multiple(() => + { + Assert.That(observation.Sender, Is.SameAs(_scheduler)); + Assert.That(observation.EventArgs.Handle, Is.EqualTo(handle)); + Assert.That(observation.EventArgs.Exception, Is.TypeOf()); + Assert.That(observation.EventArgs.Exception.Message, Is.EqualTo("Test exception")); + }); + } + /// /// 验证协程调度器应该扩展容量当槽位已满 /// diff --git a/GFramework.Core.Tests/Logging/AsyncLogAppenderTests.cs b/GFramework.Core.Tests/Logging/AsyncLogAppenderTests.cs index ae1537ca..cc7a0985 100644 --- a/GFramework.Core.Tests/Logging/AsyncLogAppenderTests.cs +++ b/GFramework.Core.Tests/Logging/AsyncLogAppenderTests.cs @@ -77,6 +77,32 @@ public class AsyncLogAppenderTests Assert.That(innerAppender.Entries.Count, Is.EqualTo(100)); } + [Test] + public void Flush_Should_Raise_OnFlushCompleted_With_Sender_And_Result() + { + var innerAppender = new TestAppender(); + using var asyncAppender = new AsyncLogAppender(innerAppender, bufferSize: 10); + object? observedSender = null; + AsyncLogFlushCompletedEventArgs? observedArgs = null; + + asyncAppender.Append(new LogEntry(DateTime.UtcNow, LogLevel.Info, "TestLogger", "Flush check", null, null)); + + asyncAppender.OnFlushCompleted += (sender, eventArgs) => + { + observedSender = sender; + observedArgs = eventArgs; + }; + + var result = asyncAppender.Flush(TimeSpan.FromSeconds(1)); + + Assert.Multiple(() => + { + Assert.That(observedSender, Is.SameAs(asyncAppender)); + Assert.That(observedArgs, Is.Not.Null); + Assert.That(observedArgs!.Success, Is.EqualTo(result)); + }); + } + [Test] public void Dispose_ShouldProcessRemainingEntries() { @@ -296,4 +322,4 @@ public class AsyncLogAppenderTests { } } -} \ No newline at end of file +} diff --git a/GFramework.Core/Architectures/Architecture.cs b/GFramework.Core/Architectures/Architecture.cs index b9e2ecf6..6a284145 100644 --- a/GFramework.Core/Architectures/Architecture.cs +++ b/GFramework.Core/Architectures/Architecture.cs @@ -49,6 +49,7 @@ public abstract class Architecture : IArchitecture // 初始化管理器 _bootstrapper = new ArchitectureBootstrapper(GetType(), resolvedEnvironment, resolvedServices, _logger); _lifecycle = new ArchitectureLifecycle(this, resolvedConfiguration, resolvedServices, _logger); + _lifecycle.PhaseChanged += HandleLifecyclePhaseChanged; _componentRegistry = new ArchitectureComponentRegistry( this, resolvedConfiguration, @@ -100,11 +101,7 @@ public abstract class Architecture : IArchitecture /// /// 阶段变更事件(用于测试和扩展) /// - public event Action? PhaseChanged - { - add => _lifecycle.PhaseChanged += value; - remove => _lifecycle.PhaseChanged -= value; - } + public event EventHandler? PhaseChanged; #endregion @@ -142,6 +139,21 @@ public abstract class Architecture : IArchitecture #endregion + #region Event Relays + + /// + /// 把生命周期协作者的阶段广播重新映射到当前架构实例, + /// 以便公开事件的 sender 始终反映真实的架构发布者。 + /// + /// 生命周期协作者实例。 + /// 阶段变化事件数据。 + private void HandleLifecyclePhaseChanged(object? sender, ArchitecturePhaseChangedEventArgs eventArgs) + { + PhaseChanged?.Invoke(this, eventArgs); + } + + #endregion + #region Module Management /// diff --git a/GFramework.Core/Architectures/ArchitectureLifecycle.cs b/GFramework.Core/Architectures/ArchitectureLifecycle.cs index c0fed21f..f70d5fe9 100644 --- a/GFramework.Core/Architectures/ArchitectureLifecycle.cs +++ b/GFramework.Core/Architectures/ArchitectureLifecycle.cs @@ -71,6 +71,7 @@ internal sealed class ArchitectureLifecycle( public void EnterPhase(ArchitecturePhase next) { _phaseCoordinator.EnterPhase(next); + PhaseChanged?.Invoke(this, new ArchitecturePhaseChangedEventArgs(next)); } #endregion @@ -127,11 +128,7 @@ internal sealed class ArchitectureLifecycle( /// /// 阶段变更事件(用于测试和扩展) /// - public event Action? PhaseChanged - { - add => _phaseCoordinator.PhaseChanged += value; - remove => _phaseCoordinator.PhaseChanged -= value; - } + public event EventHandler? PhaseChanged; #endregion diff --git a/GFramework.Core/Architectures/ArchitecturePhaseCoordinator.cs b/GFramework.Core/Architectures/ArchitecturePhaseCoordinator.cs index e1fc5f9d..51675ac9 100644 --- a/GFramework.Core/Architectures/ArchitecturePhaseCoordinator.cs +++ b/GFramework.Core/Architectures/ArchitecturePhaseCoordinator.cs @@ -22,12 +22,6 @@ internal sealed class ArchitecturePhaseCoordinator( /// public ArchitecturePhase CurrentPhase { get; private set; } - /// - /// 在架构阶段变更时触发。 - /// 该事件用于测试和扩展场景,保持现有公共行为不变。 - /// - public event Action? PhaseChanged; - /// /// 注册一个生命周期钩子。 /// 就绪后是否允许追加注册由架构配置控制,以保证阶段回调的一致性。 @@ -61,7 +55,6 @@ internal sealed class ArchitecturePhaseCoordinator( NotifyLifecycleHooks(next); NotifyPhaseListeners(next); - PhaseChanged?.Invoke(next); } /// @@ -113,4 +106,4 @@ internal sealed class ArchitecturePhaseCoordinator( listener.OnArchitecturePhase(phase); } } -} \ No newline at end of file +} diff --git a/GFramework.Core/Coroutine/CoroutineExceptionEventArgs.cs b/GFramework.Core/Coroutine/CoroutineExceptionEventArgs.cs new file mode 100644 index 00000000..cddfc054 --- /dev/null +++ b/GFramework.Core/Coroutine/CoroutineExceptionEventArgs.cs @@ -0,0 +1,29 @@ +namespace GFramework.Core.Coroutine; + +/// +/// 表示协程异常事件的数据。 +/// 该类型用于把失败协程的句柄与实际异常一起传递给订阅者。 +/// +public sealed class CoroutineExceptionEventArgs : EventArgs +{ + /// + /// 初始化 的新实例。 + /// + /// 发生异常的协程句柄。 + /// 协程执行过程中抛出的异常。 + public CoroutineExceptionEventArgs(CoroutineHandle handle, Exception exception) + { + Handle = handle; + Exception = exception ?? throw new ArgumentNullException(nameof(exception)); + } + + /// + /// 获取发生异常的协程句柄。 + /// + public CoroutineHandle Handle { get; } + + /// + /// 获取协程执行过程中抛出的异常。 + /// + public Exception Exception { get; } +} diff --git a/GFramework.Core/Coroutine/CoroutineFinishedEventArgs.cs b/GFramework.Core/Coroutine/CoroutineFinishedEventArgs.cs new file mode 100644 index 00000000..5a32ff9f --- /dev/null +++ b/GFramework.Core/Coroutine/CoroutineFinishedEventArgs.cs @@ -0,0 +1,42 @@ +using GFramework.Core.Abstractions.Coroutine; + +namespace GFramework.Core.Coroutine; + +/// +/// 表示协程结束事件的数据。 +/// 该类型统一描述协程完成、取消或失败后的最终结果。 +/// +public sealed class CoroutineFinishedEventArgs : EventArgs +{ + /// + /// 初始化 的新实例。 + /// + /// 已结束的协程句柄。 + /// 协程最终结果。 + /// 若协程以失败结束,则为对应异常;否则为 。 + public CoroutineFinishedEventArgs( + CoroutineHandle handle, + CoroutineCompletionStatus completionStatus, + Exception? exception) + { + Handle = handle; + CompletionStatus = completionStatus; + Exception = exception; + } + + /// + /// 获取已结束的协程句柄。 + /// + public CoroutineHandle Handle { get; } + + /// + /// 获取协程最终结果。 + /// + public CoroutineCompletionStatus CompletionStatus { get; } + + /// + /// 获取协程失败时对应的异常对象。 + /// 对于完成或取消结果,该值为 。 + /// + public Exception? Exception { get; } +} diff --git a/GFramework.Core/Coroutine/CoroutineScheduler.cs b/GFramework.Core/Coroutine/CoroutineScheduler.cs index ece62578..ed57b06f 100644 --- a/GFramework.Core/Coroutine/CoroutineScheduler.cs +++ b/GFramework.Core/Coroutine/CoroutineScheduler.cs @@ -91,7 +91,7 @@ public sealed class CoroutineScheduler( /// 为了避免阻塞调度器主循环,该事件会被派发到线程池回调中执行。 /// 如果调用方需要与宿主线程保持一致,请同时订阅 。 /// - public event Action? OnCoroutineException; + public event EventHandler? OnCoroutineException; /// /// 当协程以完成、取消或失败任一结果结束时触发。 @@ -99,7 +99,7 @@ public sealed class CoroutineScheduler( /// /// 该事件在调度器所在的驱动线程中同步触发,适合与宿主生命周期管理逻辑集成。 /// - public event Action? OnCoroutineFinished; + public event EventHandler? OnCoroutineFinished; /// /// 检查指定协程句柄是否仍然处于活跃状态。 @@ -622,7 +622,7 @@ public sealed class CoroutineScheduler( UpdateCompletionMetadata(handle, completionStatus); ReleaseCompletedCoroutine(slotIndex, slot, handle); CompleteCoroutineLifecycle(handle, completionStatus); - OnCoroutineFinished?.Invoke(handle, completionStatus, exception); + OnCoroutineFinished?.Invoke(this, new CoroutineFinishedEventArgs(handle, completionStatus, exception)); } /// @@ -642,7 +642,7 @@ public sealed class CoroutineScheduler( { try { - handler(handle, ex); + handler(this, new CoroutineExceptionEventArgs(handle, ex)); } catch (Exception callbackEx) { diff --git a/GFramework.Core/Logging/Appenders/AsyncLogAppender.cs b/GFramework.Core/Logging/Appenders/AsyncLogAppender.cs index 0a906f46..eda3f9a3 100644 --- a/GFramework.Core/Logging/Appenders/AsyncLogAppender.cs +++ b/GFramework.Core/Logging/Appenders/AsyncLogAppender.cs @@ -118,13 +118,14 @@ public sealed class AsyncLogAppender : ILogAppender void ILogAppender.Flush() { var success = Flush(); - OnFlushCompleted?.Invoke(success); + OnFlushCompleted?.Invoke(this, new AsyncLogFlushCompletedEventArgs(success)); } /// - /// Flush 操作完成事件,参数指示是否成功(true)或超时(false) + /// Flush 操作完成事件。 + /// 事件数据通过 提供。 /// - public event Action? OnFlushCompleted; + public event EventHandler? OnFlushCompleted; /// /// 刷新缓冲区,等待所有日志写入完成 @@ -145,7 +146,7 @@ public sealed class AsyncLogAppender : ILogAppender { // 等待处理任务发出完成信号 var success = _flushSemaphore.Wait(actualTimeout); - OnFlushCompleted?.Invoke(success); + OnFlushCompleted?.Invoke(this, new AsyncLogFlushCompletedEventArgs(success)); return success; } finally diff --git a/GFramework.Godot/Coroutine/Timing.cs b/GFramework.Godot/Coroutine/Timing.cs index a2094ffc..2bab42a2 100644 --- a/GFramework.Godot/Coroutine/Timing.cs +++ b/GFramework.Godot/Coroutine/Timing.cs @@ -781,15 +781,13 @@ public partial class Timing : Node /// /// 在协程结束时解除节点归属回调并清理索引。 /// - /// 已结束的协程句柄。 - /// 协程最终状态。 - /// 若失败则为异常对象。 + /// 触发事件的协程调度器。 + /// 协程结束事件数据。 private void HandleCoroutineFinished( - CoroutineHandle handle, - CoroutineCompletionStatus status, - Exception? exception) + object? sender, + CoroutineFinishedEventArgs eventArgs) { - CleanupOwnedCoroutineRegistration(handle); + CleanupOwnedCoroutineRegistration(eventArgs.Handle); } /// diff --git a/ai-plan/public/analyzer-warning-reduction/todos/analyzer-warning-reduction-tracking.md b/ai-plan/public/analyzer-warning-reduction/todos/analyzer-warning-reduction-tracking.md index 53f3204b..e84369d9 100644 --- a/ai-plan/public/analyzer-warning-reduction/todos/analyzer-warning-reduction-tracking.md +++ b/ai-plan/public/analyzer-warning-reduction/todos/analyzer-warning-reduction-tracking.md @@ -7,10 +7,12 @@ ## 当前恢复点 -- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-012` -- 当前阶段:`Phase 12` +- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-013` +- 当前阶段:`Phase 13` - 当前焦点: - - 当前 PR review workflow 已补强到支持 JSON 落盘与按 section/path 收窄输出;下一轮恢复到 `MA0046` 主批次 + - 当前 `MA0046` 主批次已在 `Architecture`、`AsyncLogAppender` 与 `CoroutineScheduler` 上收口完成 + - 下一轮优先从 `MA0016` 或 `MA0002` 中选择低风险批次继续推进;`MA0015` 与 `MA0077` 继续作为尾项顺手吸收 + - `GFramework.Godot` 的 `Timing.cs` 已同步适配新事件签名,但当前 worktree 的 Godot restore 资产仍受 Windows fallback package folder 干扰,独立 build 需在修复资产后补跑 - 后续继续按 warning 类型和数量批处理,而不是回退到按单文件切片推进 - 当某一轮主类型数量不足时,允许顺手合并其他低冲突 warning 类型,`MA0015` 与 `MA0077` 只是当前最明显的低数量示例,不构成限定 @@ -24,8 +26,9 @@ - 已完成当前 PR #265 review follow-up:修复 `CoroutineScheduler` 的零容量扩容边界,并补上 `Store` dispatch 作用域的异常安全回滚 - 已继续完成当前 PR #265 review follow-up:修复 `Event` 与 `Event` 监听器计数的 off-by-one,并补充回归测试 - 已增强 `gframework-pr-review` 脚本与 skill 文档,降低超长 JSON 直出导致的 review 信号漏看风险 -- 当前 `PauseStackManager`、`Store`、`CoroutineScheduler` 与 `GFramework.Core` 的 `MA0048` - 文件/类型命名冲突已从 active 入口移除;主题内剩余 warning 主要集中在 `MA0046` delegate 形状、 +- 已完成 `GFramework.Core` 当前 `MA0046` 批次:将阶段、协程与异步日志事件统一迁移到 `EventHandler` 形状, + 并同步更新 `GFramework.Godot` 订阅点、定向测试与 `docs/zh-CN` 示例 +- 当前 `GFramework.Core` `net8.0` warnings-only 基线已降到 `9` 条;剩余 warning 集中在 `MA0016` 集合抽象接口、`MA0002` comparer 重载,以及 `MA0015` / `MA0077` 两个低数量尾项 ## 当前活跃事实 @@ -51,16 +54,20 @@ 委托导致的 `GetListenerCount()` off-by-one,并以定向事件测试验证注册、注销和计数语义 - `RP-012` 为 `gframework-pr-review` 增加 `--json-output`、`--section`、`--path` 与文本截断能力,并更新 skill 推荐用法, 让“先落盘、再定向抽取”成为默认可操作路径 +- `RP-013` 已完成 `GFramework.Core` 当前 `MA0046` 批次,并以新的事件参数类型替换阶段、协程和异步日志事件的 + 非标准签名;`GFramework.Core` `net8.0` warnings-only 基线由 `15` 降至 `9` - 当前工作树分支 `fix/analyzer-warning-reduction-batch` 已在 `ai-plan/public/README.md` 建立 topic 映射 ## 当前风险 -- 公共契约兼容风险:剩余 `MA0046` / `MA0016` 若直接改公开委托或集合类型,可能波及用户代码 +- 公共契约兼容风险:剩余 `MA0016` 若直接改公开集合类型,可能波及用户代码 - 缓解措施:优先选择不改公共 API 的低风险切法;若必须触达公共契约,先补齐 XML 契约说明与定向测试 - 测试宿主稳定性风险:部分 Godot 失败路径在当前 .NET 测试宿主下仍不稳定 - 缓解措施:继续优先使用稳定的 targeted test、项目构建和相邻 smoke test 组合验证 - 多目标框架 warning 解释风险:同一源位置会在多个 target framework 下重复计数 - 缓解措施:继续以唯一源位置和 warning 家族为主要决策依据,而不是只看原始 warning 总数 +- Godot 资产文件环境风险:当前 worktree 的 `GFramework.Godot` restore/build 仍会命中 Windows fallback package folder + - 缓解措施:后续若继续触达 Godot 模块,先用 Linux 侧 restore 资产或 Windows-hosted 构建链刷新该项目,再补跑定向 build - 并行实现风险:批量收敛时若 subagent 写入边界不清晰,容易引入命名冲突或重复重构 - 缓解措施:只在 warning 类型或目录边界清晰时并行;每个 subagent 必须有独占文件 ownership,主代理负责合并验证 @@ -121,11 +128,19 @@ - 结果:通过;`--json-output`、`--section`、`--path`、`--max-description-length` 已出现在 CLI 帮助中 - `dotnet build GFramework.Core/GFramework.Core.csproj -c Release --no-restore -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -nologo` - 结果:`0 Warning(s)`,`0 Error(s)` +- `RP-013` 的定向验证结果: + - `dotnet build GFramework.Core/GFramework.Core.csproj -c Release --no-restore -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"` + - 结果:`9 Warning(s)`,`0 Error(s)`;当前 `GFramework.Core` `net8.0` warnings-only 输出中已不再出现 `MA0046` + - `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~ArchitectureLifecycleBehaviorTests|FullyQualifiedName~CoroutineSchedulerTests|FullyQualifiedName~AsyncLogAppenderTests" -m:1 -p:RestoreFallbackFolders="" -nologo` + - 结果:`50 Passed`,`0 Failed` + - `dotnet build GFramework.Godot/GFramework.Godot.csproj -c Release --no-restore -p:RestoreFallbackFolders="" -nologo` + - 结果:失败;当前 worktree 的 Godot restore 资产仍引用 Windows fallback package folder,尚未完成独立项目编译验证 - active 跟踪文件只保留当前恢复点、活跃事实、风险与下一步,不再重复保存已完成阶段的长篇历史 ## 下一步 1. 若要继续该主题,先读 active tracking,再按需展开历史归档中的 warning 热点与验证记录 -2. 下一轮优先以 `MA0046` 为主批次启动,先从 `Architecture*` 与 `CoroutineScheduler` 的低风险 delegate 形状修正中选一个切入点 -3. 若 `MA0046` 的文件 ownership 可以清晰切分,允许使用不同模型的 subagent 并行处理互不冲突的目录或类型簇 +2. 下一轮优先在 `MA0016` 与 `MA0002` 之间选择低风险批次继续推进,默认先看 `LoggingConfiguration` / + `FilterConfiguration` 与 `CollectionExtensions` +3. 若后续继续改动 `GFramework.Godot`,先修复该项目的 Linux 侧 restore 资产,再补跑独立 build 4. 若本主题确认暂缓,可保持当前归档状态,不需要再恢复 `local-plan/` diff --git a/ai-plan/public/analyzer-warning-reduction/traces/analyzer-warning-reduction-trace.md b/ai-plan/public/analyzer-warning-reduction/traces/analyzer-warning-reduction-trace.md index d0f53094..666ab845 100644 --- a/ai-plan/public/analyzer-warning-reduction/traces/analyzer-warning-reduction-trace.md +++ b/ai-plan/public/analyzer-warning-reduction/traces/analyzer-warning-reduction-trace.md @@ -1,5 +1,37 @@ # Analyzer Warning Reduction 追踪 +## 2026-04-21 — RP-013 + +### 阶段:`MA0046` 事件签名批次收口(RP-013) + +- 依据 `RP-012` 的下一步建议,本轮恢复到 `GFramework.Core` 的 `MA0046` 主批次,而不是继续停留在 PR review workflow 优化 +- 本地 warnings-only 基线确认当前 `GFramework.Core` `net8.0` 仍有 `6` 个 `MA0046`: + - `Architecture.cs` + - `ArchitectureLifecycle.cs` + - `ArchitecturePhaseCoordinator.cs` + - `AsyncLogAppender.cs` + - `CoroutineScheduler.cs` 两处事件 +- 方案选择: + - 不再保留 `Action<...>` 事件签名,统一改为标准 `EventHandler` + - 为 `Architecture`、`AsyncLogAppender` 新增放在 `GFramework.Core.Abstractions` 的事件参数类型 + - 为 `CoroutineScheduler` 新增放在 `GFramework.Core` 的事件参数类型,因为 `CoroutineHandle` 定义在 runtime 层,不适合反向放入 Abstractions + - `Architecture` 相关事件采用 `Coordinator -> Lifecycle -> Architecture` relay,而不是直接透传底层事件,确保公开事件的 sender 始终是实际发布者,并避免引入新的 `MA0091` +- 同步适配: + - 更新 `GFramework.Godot/Coroutine/Timing.cs` 的 `OnCoroutineFinished` 订阅签名 + - 更新 `ArchitectureLifecycleBehaviorTests`、`CoroutineSchedulerTests`、`AsyncLogAppenderTests` 以覆盖 sender / event args 契约 + - 更新 `docs/zh-CN/core/architecture.md` 与 `docs/zh-CN/core/lifecycle.md` 的 `PhaseChanged` 示例 +- 验证结果: + - `dotnet build GFramework.Core/GFramework.Core.csproj -c Release --no-restore -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"` + - 结果:`9 Warning(s)`,`0 Error(s)`;当前 `GFramework.Core` `net8.0` 输出中已无 `MA0046` + - `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~ArchitectureLifecycleBehaviorTests|FullyQualifiedName~CoroutineSchedulerTests|FullyQualifiedName~AsyncLogAppenderTests" -m:1 -p:RestoreFallbackFolders="" -nologo` + - 结果:`50 Passed`,`0 Failed` + - `dotnet build GFramework.Godot/GFramework.Godot.csproj -c Release --no-restore -p:RestoreFallbackFolders="" -nologo` + - 结果:失败;当前 worktree 的 `project.assets.json` 仍引用 Windows fallback package folder,尚未完成 Godot 独立编译验证 +- 当前结论: + - `MA0046` 已从 active 批次中移除 + - 剩余 `GFramework.Core` `net8.0` warning 分布更新为:`MA0016=5`、`MA0002=2`、`MA0015=1`、`MA0077=1` + - 若继续本主题,下一步默认转入 `MA0016` 批次;若继续触达 Godot,再先修复该项目 restore 资产 + ## 2026-04-21 — RP-012 ### 阶段:PR review workflow 输出收窄增强(RP-012) diff --git a/docs/zh-CN/core/architecture.md b/docs/zh-CN/core/architecture.md index e1900382..3c54e1bb 100644 --- a/docs/zh-CN/core/architecture.md +++ b/docs/zh-CN/core/architecture.md @@ -123,6 +123,9 @@ protected override void OnInitialize() - `PhaseChanged` - `RegisterLifecycleHook(...)` +其中 `PhaseChanged` 现在遵循标准 `EventHandler` 约定, +阶段值通过 `args.Phase` 读取。 + 如果你需要在 `Ready`、`Destroying` 等阶段执行横切逻辑,比起把这类逻辑塞进某个具体 `System`,更适合单独实现 `IArchitectureLifecycleHook`。 diff --git a/docs/zh-CN/core/lifecycle.md b/docs/zh-CN/core/lifecycle.md index a546070d..4cdaa8fc 100644 --- a/docs/zh-CN/core/lifecycle.md +++ b/docs/zh-CN/core/lifecycle.md @@ -139,9 +139,9 @@ architecture.RegisterLifecycleHook(new MetricsHook()); 如果你只需要观察阶段变化,也可以直接订阅: ```csharp -architecture.PhaseChanged += phase => +architecture.PhaseChanged += (_, args) => { - Console.WriteLine($"Phase changed: {phase}"); + Console.WriteLine($"Phase changed: {args.Phase}"); }; ```