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