From e3ea364b29e8744cdfa82205f13aacfe382bb91b Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Mon, 6 Apr 2026 08:33:56 +0800 Subject: [PATCH] =?UTF-8?q?docs(core):=20=E6=B7=BB=E5=8A=A0=E6=9E=B6?= =?UTF-8?q?=E6=9E=84=E8=AF=A6=E8=A7=A3=E5=92=8C=E6=A0=B8=E5=BF=83=E6=A1=86?= =?UTF-8?q?=E6=9E=B6=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 Architecture 架构详解文档,涵盖设计目标、核心组件、生命周期管理 - 添加 GFramework.Core 核心框架文档,包含五层架构、快速开始、组件联动等内容 - 新增架构组件激活器实现,支持类型注册路径的实例创建能力 - 添加架构销毁器,统一处理可销毁对象的登记与释放 - 实现架构生命周期管理器,负责阶段转换和组件初始化销毁 --- ...hitectureComponentRegistryBehaviorTests.cs | 244 ++++++++++++++++++ .../ArchitectureLifecycleBehaviorTests.cs | 210 +++++++++++++++ .../ArchitectureComponentActivator.cs | 17 +- .../Architectures/ArchitectureDisposer.cs | 5 +- .../Architectures/ArchitectureLifecycle.cs | 21 +- docs/zh-CN/core/architecture.md | 28 +- docs/zh-CN/core/index.md | 20 +- 7 files changed, 515 insertions(+), 30 deletions(-) diff --git a/GFramework.Core.Tests/Architectures/ArchitectureComponentRegistryBehaviorTests.cs b/GFramework.Core.Tests/Architectures/ArchitectureComponentRegistryBehaviorTests.cs index 6cd98bc2..583c30c8 100644 --- a/GFramework.Core.Tests/Architectures/ArchitectureComponentRegistryBehaviorTests.cs +++ b/GFramework.Core.Tests/Architectures/ArchitectureComponentRegistryBehaviorTests.cs @@ -179,6 +179,77 @@ public class ArchitectureComponentRegistryBehaviorTests await architecture.DestroyAsync(); } + /// + /// 验证预冻结阶段通过实现类型注册的单例依赖会在同一轮组件激活中复用同一个实例。 + /// 该回归测试用于保护 的共享单例缓存,避免系统和模型分别创建重复单例。 + /// + [Test] + public async Task RegisterSystem_And_Model_Type_Should_Reuse_ImplementationType_Singleton_During_Activation() + { + var counter = new DependencyConstructionCounter(); + var architecture = new RegistryTestArchitecture( + target => + { + target.RegisterSystem(); + target.RegisterModel(); + }, + services => + { + services.AddSingleton(counter); + services.AddSingleton(); + }); + + await architecture.InitializeAsync(); + + var system = architecture.Context.GetSystem(); + var model = architecture.Context.GetModel(); + + Assert.Multiple(() => + { + Assert.That(counter.CreationCount, Is.EqualTo(1)); + Assert.That(system.Dependency, Is.SameAs(model.Dependency)); + }); + + await architecture.DestroyAsync(); + } + + /// + /// 验证预冻结阶段通过工厂注册的单例依赖会在同一轮组件激活中复用同一个实例。 + /// 该回归测试覆盖 ImplementationFactory 描述符路径,避免用户工厂在初始化时被重复调用。 + /// + [Test] + public async Task RegisterSystem_And_Model_Type_Should_Reuse_Factory_Singleton_During_Activation() + { + var creationCount = 0; + var architecture = new RegistryTestArchitecture( + target => + { + target.RegisterSystem(); + target.RegisterModel(); + }, + services => + { + services.AddSingleton(_ => + { + creationCount++; + return new FactorySharedDependency(); + }); + }); + + await architecture.InitializeAsync(); + + var system = architecture.Context.GetSystem(); + var model = architecture.Context.GetModel(); + + Assert.Multiple(() => + { + Assert.That(creationCount, Is.EqualTo(1)); + Assert.That(system.Dependency, Is.SameAs(model.Dependency)); + }); + + await architecture.DestroyAsync(); + } + /// /// 验证 Ready 阶段后不允许继续注册 Utility,保持与系统和模型一致的约束。 /// @@ -470,4 +541,177 @@ public class ArchitectureComponentRegistryBehaviorTests return _context; } } + + /// + /// 统计实现类型单例在预冻结激活阶段的构造次数。 + /// + private sealed class DependencyConstructionCounter + { + /// + /// 获取共享依赖被构造的次数。 + /// + public int CreationCount { get; private set; } + + /// + /// 记录一次新的依赖构造。 + /// + public void RecordCreation() + { + CreationCount++; + } + } + + /// + /// 用于覆盖 ImplementationType 单例描述符路径的共享依赖。 + /// + private sealed class ImplementationTypeSharedDependency + { + /// + /// 创建共享依赖并记录构造次数。 + /// + /// 用于统计构造次数的计数器。 + public ImplementationTypeSharedDependency(DependencyConstructionCounter counter) + { + counter.RecordCreation(); + } + } + + /// + /// 用于覆盖 ImplementationType 单例复用路径的测试系统。 + /// + private sealed class ImplementationTypeDependencySystem(ImplementationTypeSharedDependency dependency) : ISystem + { + private IArchitectureContext _context = null!; + + /// + /// 获取构造函数注入的共享依赖。 + /// + public ImplementationTypeSharedDependency Dependency { get; } = dependency; + + public void Initialize() + { + } + + public void Destroy() + { + } + + public void OnArchitecturePhase(ArchitecturePhase phase) + { + } + + public void SetContext(IArchitectureContext context) + { + _context = context; + } + + public IArchitectureContext GetContext() + { + return _context; + } + } + + /// + /// 用于覆盖 ImplementationType 单例复用路径的测试模型。 + /// + private sealed class ImplementationTypeDependencyModel(ImplementationTypeSharedDependency dependency) : IModel + { + private IArchitectureContext _context = null!; + + /// + /// 获取构造函数注入的共享依赖。 + /// + public ImplementationTypeSharedDependency Dependency { get; } = dependency; + + public void Initialize() + { + } + + public void OnArchitecturePhase(ArchitecturePhase phase) + { + } + + public void SetContext(IArchitectureContext context) + { + _context = context; + } + + public IArchitectureContext GetContext() + { + return _context; + } + } + + /// + /// 用于覆盖 ImplementationFactory 单例描述符路径的共享依赖。 + /// + private sealed class FactorySharedDependency + { + } + + /// + /// 用于覆盖 ImplementationFactory 单例复用路径的测试系统。 + /// + private sealed class FactoryDependencySystem(FactorySharedDependency dependency) : ISystem + { + private IArchitectureContext _context = null!; + + /// + /// 获取构造函数注入的共享依赖。 + /// + public FactorySharedDependency Dependency { get; } = dependency; + + public void Initialize() + { + } + + public void Destroy() + { + } + + public void OnArchitecturePhase(ArchitecturePhase phase) + { + } + + public void SetContext(IArchitectureContext context) + { + _context = context; + } + + public IArchitectureContext GetContext() + { + return _context; + } + } + + /// + /// 用于覆盖 ImplementationFactory 单例复用路径的测试模型。 + /// + private sealed class FactoryDependencyModel(FactorySharedDependency dependency) : IModel + { + private IArchitectureContext _context = null!; + + /// + /// 获取构造函数注入的共享依赖。 + /// + public FactorySharedDependency Dependency { get; } = dependency; + + public void Initialize() + { + } + + public void OnArchitecturePhase(ArchitecturePhase phase) + { + } + + public void SetContext(IArchitectureContext context) + { + _context = context; + } + + public IArchitectureContext GetContext() + { + return _context; + } + } } \ No newline at end of file diff --git a/GFramework.Core.Tests/Architectures/ArchitectureLifecycleBehaviorTests.cs b/GFramework.Core.Tests/Architectures/ArchitectureLifecycleBehaviorTests.cs index 0bde4b5d..b0ef7262 100644 --- a/GFramework.Core.Tests/Architectures/ArchitectureLifecycleBehaviorTests.cs +++ b/GFramework.Core.Tests/Architectures/ArchitectureLifecycleBehaviorTests.cs @@ -1,3 +1,4 @@ +using System.Reflection; using GFramework.Core.Abstractions.Architectures; using GFramework.Core.Abstractions.Enums; using GFramework.Core.Abstractions.Lifecycle; @@ -6,6 +7,7 @@ using GFramework.Core.Abstractions.Systems; using GFramework.Core.Abstractions.Utility; using GFramework.Core.Architectures; using GFramework.Core.Logging; +using Microsoft.Extensions.DependencyInjection; namespace GFramework.Core.Tests.Architectures; @@ -98,6 +100,75 @@ public class ArchitectureLifecycleBehaviorTests }); } + /// + /// 验证初始化失败后仍然允许执行销毁流程。 + /// 该回归测试用于保护 FailedInitialization → Destroying 的合法迁移,避免失败路径上的组件与容器泄漏。 + /// + [Test] + public async Task DestroyAsync_After_FailedInitialization_Should_Cleanup_And_Enter_Destroyed() + { + var destroyOrder = new List(); + var architecture = new FailingInitializationArchitecture(destroyOrder); + + var exception = Assert.ThrowsAsync(async () => await architecture.InitializeAsync()); + Assert.That(exception, Is.Not.Null); + Assert.That(architecture.CurrentPhase, Is.EqualTo(ArchitecturePhase.FailedInitialization)); + + await architecture.DestroyAsync(); + + Assert.Multiple(() => + { + Assert.That(destroyOrder, Is.EqualTo(new[] { "model", "utility" })); + Assert.That(architecture.CurrentPhase, Is.EqualTo(ArchitecturePhase.Destroyed)); + Assert.That(architecture.PhaseHistory[^3..], Is.EqualTo(new[] + { + ArchitecturePhase.FailedInitialization, + ArchitecturePhase.Destroying, + ArchitecturePhase.Destroyed + })); + }); + } + + /// + /// 验证 Destroyed 阶段会在容器清空前广播给容器内的阶段监听器。 + /// 该回归测试保护销毁尾声的阶段通知,确保依赖最终阶段信号的服务仍能收到 Destroyed。 + /// + [Test] + public async Task DestroyAsync_Should_Notify_Container_Phase_Listeners_About_Destroyed_Before_Clear() + { + var listener = new TrackingPhaseListener(); + var architecture = new ListenerTrackingArchitecture(listener); + + await architecture.InitializeAsync(); + await architecture.DestroyAsync(); + + Assert.That(listener.ObservedPhases[^2..], Is.EqualTo(new[] + { + ArchitecturePhase.Destroying, + ArchitecturePhase.Destroyed + })); + } + + /// + /// 验证启用 AllowLateRegistration 时,生命周期层会立即初始化后注册的组件,而不是继续沿用初始化期的拒绝策略。 + /// 由于公共架构 API 在 Ready 之后会先触发容器限制,此回归测试直接覆盖生命周期协作者的对齐逻辑。 + /// + [Test] + public async Task + RegisterLifecycleComponent_After_Initialization_Should_Initialize_Immediately_When_LateRegistration_Is_Enabled() + { + var architecture = new AllowLateRegistrationArchitecture(); + await architecture.InitializeAsync(); + + var lateComponent = new LateRegisteredInitializableComponent(); + + architecture.RegisterLateComponentForTesting(lateComponent); + + Assert.That(lateComponent.InitializeCallCount, Is.EqualTo(1)); + + await architecture.DestroyAsync(); + } + /// /// 记录阶段流转的可配置测试架构。 /// @@ -162,6 +233,106 @@ public class ArchitectureLifecycleBehaviorTests } } + /// + /// 在初始化阶段注册可销毁组件并随后抛出异常的测试架构。 + /// + private sealed class FailingInitializationArchitecture : Architecture + { + private readonly List _destroyOrder; + + /// + /// 创建用于验证失败后销毁行为的测试架构。 + /// + /// 记录失败后清理顺序的列表。 + public FailingInitializationArchitecture(List destroyOrder) + { + _destroyOrder = destroyOrder; + PhaseChanged += phase => PhaseHistory.Add(phase); + } + + /// + /// 获取架构经历过的阶段列表。 + /// + public List PhaseHistory { get; } = []; + + /// + /// 注册可销毁组件后故意抛出异常,模拟初始化失败场景。 + /// + protected override void OnInitialize() + { + RegisterUtility(new TrackingDestroyableUtility(_destroyOrder)); + RegisterModel(new TrackingDestroyableModel(_destroyOrder)); + throw new InvalidOperationException("boom"); + } + } + + /// + /// 通过配置器把阶段监听器注册到容器中的测试架构。 + /// + private sealed class ListenerTrackingArchitecture(TrackingPhaseListener listener) : Architecture + { + /// + /// 保持对监听器的引用,以便配置器在初始化前把同一实例注册到容器。 + /// + public override Action? Configurator => + services => services.AddSingleton(listener); + + /// + /// 该测试不需要额外组件注册。 + /// + protected override void OnInitialize() + { + } + } + + /// + /// 启用 AllowLateRegistration 的测试架构。 + /// 该架构暴露生命周期协作者供回归测试验证内部注册策略对齐。 + /// + private sealed class AllowLateRegistrationArchitecture : Architecture + { + /// + /// 使用允许后注册的配置创建测试架构。 + /// + public AllowLateRegistrationArchitecture() + : base(new ArchitectureConfiguration + { + ArchitectureProperties = new() + { + AllowLateRegistration = true, + StrictPhaseValidation = true + } + }) + { + } + + /// + /// 该测试不需要初始组件。 + /// + protected override void OnInitialize() + { + } + + /// + /// 通过反射调用内部生命周期协作者的注册逻辑,以便覆盖无法通过公共 API 直接到达的后注册初始化路径。 + /// + /// 要登记到生命周期中的后注册组件。 + public void RegisterLateComponentForTesting(object component) + { + var field = typeof(Architecture).GetField( + "_lifecycle", + BindingFlags.Instance | BindingFlags.NonPublic); + var lifecycle = field?.GetValue(this) ?? + throw new InvalidOperationException("Architecture lifecycle field was not found."); + var registerMethod = lifecycle.GetType().GetMethod(nameof(RegisterLateComponentForTesting)) ?? + lifecycle.GetType().GetMethod("RegisterLifecycleComponent") ?? + throw new InvalidOperationException( + "Architecture lifecycle registration method was not found."); + + registerMethod.Invoke(lifecycle, [component]); + } + } + /// /// 用于验证逆序销毁的上下文工具。 /// @@ -189,6 +360,45 @@ public class ArchitectureLifecycleBehaviorTests } } + /// + /// 记录容器阶段通知顺序的监听器。 + /// + private sealed class TrackingPhaseListener : IArchitecturePhaseListener + { + /// + /// 获取监听到的阶段列表。 + /// + public List ObservedPhases { get; } = []; + + /// + /// 记录收到的阶段通知。 + /// + /// 当前阶段。 + public void OnArchitecturePhase(ArchitecturePhase phase) + { + ObservedPhases.Add(phase); + } + } + + /// + /// 记录即时初始化次数的后注册测试组件。 + /// + private sealed class LateRegisteredInitializableComponent : IInitializable + { + /// + /// 获取组件被即时初始化的次数。 + /// + public int InitializeCallCount { get; private set; } + + /// + /// 记录一次初始化调用。 + /// + public void Initialize() + { + InitializeCallCount++; + } + } + /// /// 用于验证逆序销毁的模型。 /// diff --git a/GFramework.Core/Architectures/ArchitectureComponentActivator.cs b/GFramework.Core/Architectures/ArchitectureComponentActivator.cs index 88afa810..0bf121e5 100644 --- a/GFramework.Core/Architectures/ArchitectureComponentActivator.cs +++ b/GFramework.Core/Architectures/ArchitectureComponentActivator.cs @@ -14,6 +14,12 @@ internal sealed class ArchitectureComponentActivator( IIocContainer container, ILogger logger) { + /// + /// 预冻结阶段的单例实例缓存。 + /// 该缓存跨越整个架构组件激活周期共享,确保多个组件在同一轮初始化中解析到同一个单例描述时不会重复创建实例。 + /// + private readonly Dictionary _singletonCache = []; + /// /// 根据当前容器状态创建组件实例。 /// 激活过程优先复用已经注册到容器中的实例,再按服务描述解析实现类型或工厂方法, @@ -23,7 +29,7 @@ internal sealed class ArchitectureComponentActivator( /// 创建完成的组件实例。 public TComponent CreateInstance() where TComponent : class { - var activationProvider = new RegistrationServiceProvider(container, logger); + var activationProvider = new RegistrationServiceProvider(container, logger, _singletonCache); return ActivatorUtilities.CreateInstance(activationProvider); } @@ -33,9 +39,14 @@ internal sealed class ArchitectureComponentActivator( /// private sealed class RegistrationServiceProvider( IIocContainer container, - ILogger logger) : IServiceProvider + ILogger logger, + Dictionary singletonCache) : IServiceProvider { - private readonly Dictionary _singletonCache = []; + /// + /// 共享的单例缓存。 + /// 该缓存由外层 activator 统一持有,从而把单例复用范围提升到整个组件注册批次,而不是单次实例创建调用。 + /// + private readonly Dictionary _singletonCache = singletonCache; /// /// 从当前服务集合中解析指定类型的服务。 diff --git a/GFramework.Core/Architectures/ArchitectureDisposer.cs b/GFramework.Core/Architectures/ArchitectureDisposer.cs index 1c1a861d..c2c7f553 100644 --- a/GFramework.Core/Architectures/ArchitectureDisposer.cs +++ b/GFramework.Core/Architectures/ArchitectureDisposer.cs @@ -50,7 +50,7 @@ internal sealed class ArchitectureDisposer( /// 用于推进架构阶段的回调。 public async ValueTask DestroyAsync(ArchitecturePhase currentPhase, Action enterPhase) { - if (currentPhase >= ArchitecturePhase.Destroying) + if (currentPhase is ArchitecturePhase.Destroying or ArchitecturePhase.Destroyed) { logger.Warn("Architecture destroy called but already in destroying/destroyed state"); return; @@ -68,9 +68,10 @@ internal sealed class ArchitectureDisposer( await CleanupComponentsAsync(); await services.ModuleManager.DestroyAllAsync(); - services.Container.Clear(); + // Destroyed 广播依赖容器中的阶段监听器,必须在清空容器前完成。 enterPhase(ArchitecturePhase.Destroyed); + services.Container.Clear(); logger.Info("Architecture destruction completed"); } diff --git a/GFramework.Core/Architectures/ArchitectureLifecycle.cs b/GFramework.Core/Architectures/ArchitectureLifecycle.cs index 85c06ed7..6cb8f3df 100644 --- a/GFramework.Core/Architectures/ArchitectureLifecycle.cs +++ b/GFramework.Core/Architectures/ArchitectureLifecycle.cs @@ -43,9 +43,14 @@ internal sealed class ArchitectureLifecycle( if (component is IInitializable initializable) { if (_initialized) - throw new InvalidOperationException("Cannot initialize component after Architecture is Ready"); + { + if (!configuration.ArchitectureProperties.AllowLateRegistration) + throw new InvalidOperationException("Cannot initialize component after Architecture is Ready"); - if (_pendingInitializableSet.Add(initializable)) + InitializeLateRegisteredComponent(initializable); + } + + else if (_pendingInitializableSet.Add(initializable)) { _pendingInitializableList.Add(initializable); logger.Trace($"Added {component.GetType().Name} to pending initialization queue"); @@ -218,6 +223,18 @@ internal sealed class ArchitectureLifecycle( component.Initialize(); } + /// + /// 立即初始化在常规初始化批次完成后新增的组件。 + /// 当启用 AllowLateRegistration 时,生命周期层需要和注册层保持一致, + /// 让新增组件在注册当下完成同步初始化,而不是停留在未初始化状态。 + /// + /// 后注册的可初始化组件。 + private void InitializeLateRegisteredComponent(IInitializable component) + { + logger.Debug($"Initializing late-registered component: {component.GetType().Name}"); + component.Initialize(); + } + #endregion #region Destruction diff --git a/docs/zh-CN/core/architecture.md b/docs/zh-CN/core/architecture.md index 12007e4d..066dc7d6 100644 --- a/docs/zh-CN/core/architecture.md +++ b/docs/zh-CN/core/architecture.md @@ -207,10 +207,10 @@ architecture.RegisterLifecycleHook(new MyLifecycleHook()); ├─> 调用 OnInitialize() (用户注册组件) ├─> 初始化所有组件 │ ├─> BeforeUtilityInit → 初始化 Utility → AfterUtilityInit - │ ├─> BeforeModelInit → 初始化 Model → AfterModelInit - │ └─> BeforeSystemInit → 初始化 System → AfterSystemInit - ├─> 冻结 IoC 容器 - └─> 进入 Ready 阶段 + │ ├─> BeforeModelInit → 初始化 Model → AfterModelInit + │ └─> BeforeSystemInit → 初始化 System → AfterSystemInit + ├─> CompleteInitialization() 冻结 IoC 容器 + └─> 进入 Ready 阶段 3. 等待就绪 (可选) └─> await architecture.WaitUntilReadyAsync() @@ -219,16 +219,16 @@ architecture.RegisterLifecycleHook(new MyLifecycleHook()); ### 销毁流程 ``` -1. 调用 DestroyAsync() 或 Destroy() - ├─> 检查当前阶段 (如果是 None 或已销毁则直接返回) - ├─> 进入 Destroying 阶段 - ├─> 逆序销毁所有组件 - │ ├─> 优先调用 IAsyncDestroyable.DestroyAsync() - │ └─> 否则调用 IDestroyable.Destroy() - ├─> 销毁服务模块 - ├─> 清空 IoC 容器 - └─> 进入 Destroyed 阶段 -``` +1. 调用 DestroyAsync() 或 Destroy() + ├─> 检查当前阶段 (如果是 None 或已销毁则直接返回) + ├─> 进入 Destroying 阶段 + ├─> 逆序销毁所有组件 + │ ├─> 优先调用 IAsyncDestroyable.DestroyAsync() + │ └─> 否则调用 IDestroyable.Destroy() + ├─> 销毁服务模块 + ├─> 进入 Destroyed 阶段 + └─> 清空 IoC 容器 +``` --- diff --git a/docs/zh-CN/core/index.md b/docs/zh-CN/core/index.md index 950c7100..48563aea 100644 --- a/docs/zh-CN/core/index.md +++ b/docs/zh-CN/core/index.md @@ -340,14 +340,15 @@ public class PlayerController : IController #### 2. ArchitectureBootstrapper (初始化基础设施编排器) -**职责**: 在用户 `OnInitialize()` 执行前准备环境、服务和上下文 +**职责**: 在用户 `OnInitialize()` 执行前准备环境、服务和上下文,并在组件初始化完成后执行初始化收尾 **核心功能**: - 初始化环境对象 - 注册内置服务模块 - 绑定架构上下文到 `GameContext` -- 执行服务钩子并冻结 IoC 容器 +- 执行服务钩子 +- 在 `InitializeAllComponentsAsync()` 完成后通过 `CompleteInitialization()` 冻结 IoC 容器 #### 3. ArchitectureLifecycle (生命周期管理器) @@ -455,13 +456,14 @@ public class PlayerController : IController ├─> OnInitialize() (用户注册组件) │ ├─> RegisterModel → Model.SetContext() │ ├─> RegisterSystem → System.SetContext() - │ └─> RegisterUtility → 注册到容器 - └─> InitializeAllComponentsAsync() - ├─> BeforeUtilityInit → Utility.Initialize() - ├─> BeforeModelInit → Model.Initialize() - ├─> BeforeSystemInit → System.Initialize() - └─> Ready -``` + │ └─> RegisterUtility → 注册到容器 + ├─> InitializeAllComponentsAsync() + │ ├─> BeforeUtilityInit → Utility.Initialize() + │ ├─> BeforeModelInit → Model.Initialize() + │ └─> BeforeSystemInit → System.Initialize() + ├─> CompleteInitialization() → 冻结 IoC 容器 + └─> 进入 Ready +``` **重要变更 (v1.1.0)**: 管理器现在在构造函数中初始化,而不是在 InitializeAsync 中。这消除了 `null!` 断言,提高了代码安全性。