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; } }