From febf9480770b78ec86c1e4d46ae6f53ecda4f1ce Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Thu, 9 Apr 2026 16:33:02 +0800 Subject: [PATCH] =?UTF-8?q?feat(config):=20=E6=B7=BB=E5=8A=A0=E6=9E=B6?= =?UTF-8?q?=E6=9E=84=E9=85=8D=E7=BD=AE=E9=9B=86=E6=88=90=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E5=92=8C=E6=A8=A1=E5=9D=97=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 实现了 ArchitectureConfigIntegrationTests 测试类,验证配置模块在架构场景下的完整链路 - 添加了 GameConfigModule 类,提供基于 Architecture 的配置模块接入入口 - 实现了配置模块的生命周期管理,包括首次加载和热重载支持 - 集成了 BootstrapInitializationHook 确保配置在 utility 初始化前完成加载 - 添加了模块复用限制和安装窗口验证机制 - 实现了架构销毁时的资源清理和生命周期钩子注册 --- .../ArchitectureConfigIntegrationTests.cs | 37 +++++++++---------- GFramework.Game/Config/GameConfigModule.cs | 19 +++++++--- 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/GFramework.Game.Tests/Config/ArchitectureConfigIntegrationTests.cs b/GFramework.Game.Tests/Config/ArchitectureConfigIntegrationTests.cs index 49e99af9..f9a2492f 100644 --- a/GFramework.Game.Tests/Config/ArchitectureConfigIntegrationTests.cs +++ b/GFramework.Game.Tests/Config/ArchitectureConfigIntegrationTests.cs @@ -1,15 +1,11 @@ -using System; using System.IO; -using System.Linq; using System.Threading; -using System.Threading.Tasks; using GFramework.Core.Architectures; using GFramework.Core.Extensions; using GFramework.Core.Utility; using GFramework.Game.Abstractions.Config; using GFramework.Game.Config; using GFramework.Game.Config.Generated; -using NUnit.Framework; namespace GFramework.Game.Tests.Config; @@ -153,33 +149,39 @@ public class ArchitectureConfigIntegrationTests } /// - /// 验证配置启动帮助器在同步阻塞且存在 的线程上仍可完成初始化, - /// 避免架构生命周期钩子的同步桥接因为 await 捕获上下文而死锁。 + /// 验证配置模块在同步阻塞且存在 的线程上仍可完成架构初始化, + /// 直接覆盖 通过生命周期钩子执行同步桥接的真实路径。 /// [Test] - public void GameConfigBootstrapShouldSupportSynchronousBridgeOnBlockingSynchronizationContext() + public void GameConfigModuleShouldSupportSynchronousBridgeOnBlockingSynchronizationContext() { var rootPath = CreateTempConfigRoot(); - GameConfigBootstrap? bootstrap = null; + ConsumerArchitecture? architecture = null; + var initialized = false; try { - bootstrap = CreateBootstrap(rootPath); + architecture = new ConsumerArchitecture(rootPath); RunBlockingOnSynchronizationContext( - () => bootstrap.InitializeAsync(), + () => architecture.InitializeAsync(), TimeSpan.FromSeconds(5)); + initialized = true; - var monsterTable = bootstrap.Registry.GetMonsterTable(); + var monsterTable = architecture.Registry.GetMonsterTable(); Assert.Multiple(() => { - Assert.That(bootstrap.IsInitialized, Is.True); + Assert.That(architecture.ConfigModule.IsInitialized, Is.True); Assert.That(monsterTable.Get(1).Name, Is.EqualTo("Slime")); Assert.That(monsterTable.FindByFaction("dungeon").Count(), Is.EqualTo(2)); }); } finally { - bootstrap?.Dispose(); + if (architecture is not null && initialized) + { + architecture.DestroyAsync().GetAwaiter().GetResult(); + } + DeleteDirectoryIfExists(rootPath); } } @@ -322,14 +324,9 @@ public class ArchitectureConfigIntegrationTests } } - private static GameConfigBootstrap CreateBootstrap(string configRoot) - { - return new GameConfigBootstrap(CreateBootstrapOptions(configRoot)); - } - /// - /// 创建一个使用配置模块的模块实例。 - /// + /// 创建一个使用配置模块的模块实例。 + /// /// 测试配置根目录。 /// 已配置的模块实例。 private static GameConfigModule CreateModule(string configRoot) diff --git a/GFramework.Game/Config/GameConfigModule.cs b/GFramework.Game/Config/GameConfigModule.cs index 4b726877..1f964e04 100644 --- a/GFramework.Game/Config/GameConfigModule.cs +++ b/GFramework.Game/Config/GameConfigModule.cs @@ -22,7 +22,7 @@ public sealed class GameConfigModule : IArchitectureModule { private const int InstallStateNotInstalled = 0; private const int InstallStateInstalling = 1; - private const int InstallStateInstalled = 2; + private const int InstallStateConsumed = 2; private readonly GameConfigBootstrap _bootstrap; private readonly ModuleBootstrapLifetimeUtility _lifetimeUtility; @@ -76,6 +76,11 @@ public sealed class GameConfigModule : IArchitectureModule /// 目标架构实例。 /// 为空时抛出。 /// 当同一个模块实例被重复安装时抛出。 + /// + /// 生命周期阶段校验会在任何注册动作前执行,因此错过安装窗口的调用不会消耗当前模块实例。 + /// 一旦开始向架构注册 utility 或生命周期钩子,就不存在回滚 API;因此后续任何失败都会把该模块实例视为已消耗, + /// 调用方必须创建新的 再重试。 + /// public void Install(IArchitecture architecture) { ArgumentNullException.ThrowIfNull(architecture); @@ -93,16 +98,18 @@ public sealed class GameConfigModule : IArchitectureModule try { - // 先注册生命周期钩子,确保任何“已错过 BeforeUtilityInit”的安装都会在暴露注册表之前失败, - // 避免架构看到永远不会完成首次加载的半安装配置入口。 - architecture.RegisterLifecycleHook(new BootstrapInitializationHook(_bootstrap)); + // 阶段窗口已经在前面做过无副作用校验,因此这里优先注册 utility, + // 让常见的容器/上下文接线失败在不可回滚的 hook 注册之前暴露出来。 architecture.RegisterUtility(Registry); architecture.RegisterUtility(_lifetimeUtility); - Volatile.Write(ref _installState, InstallStateInstalled); + architecture.RegisterLifecycleHook(new BootstrapInitializationHook(_bootstrap)); + Volatile.Write(ref _installState, InstallStateConsumed); } catch { - Volatile.Write(ref _installState, InstallStateNotInstalled); + // 架构对 utility / hook 注册都不提供回滚入口,因此一旦进入注册阶段, + // 即使安装失败也必须禁止复用同一个模块实例,避免重复暴露共享注册表或挂上第二个 hook。 + Volatile.Write(ref _installState, InstallStateConsumed); throw; } }