mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-10 19:56:45 +08:00
feat(config): 添加架构配置集成测试和模块实现
- 实现了 ArchitectureConfigIntegrationTests 测试类,验证配置模块在架构场景下的完整链路 - 添加了 GameConfigModule 类,提供基于 Architecture 的配置模块接入入口 - 实现了配置模块的生命周期管理,包括首次加载和热重载支持 - 集成了 BootstrapInitializationHook 确保配置在 utility 初始化前完成加载 - 添加了模块复用限制和安装窗口验证机制 - 实现了架构销毁时的资源清理和生命周期钩子注册
This commit is contained in:
parent
eb7a8c702c
commit
febf948077
@ -1,15 +1,11 @@
|
|||||||
using System;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using GFramework.Core.Architectures;
|
using GFramework.Core.Architectures;
|
||||||
using GFramework.Core.Extensions;
|
using GFramework.Core.Extensions;
|
||||||
using GFramework.Core.Utility;
|
using GFramework.Core.Utility;
|
||||||
using GFramework.Game.Abstractions.Config;
|
using GFramework.Game.Abstractions.Config;
|
||||||
using GFramework.Game.Config;
|
using GFramework.Game.Config;
|
||||||
using GFramework.Game.Config.Generated;
|
using GFramework.Game.Config.Generated;
|
||||||
using NUnit.Framework;
|
|
||||||
|
|
||||||
namespace GFramework.Game.Tests.Config;
|
namespace GFramework.Game.Tests.Config;
|
||||||
|
|
||||||
@ -153,33 +149,39 @@ public class ArchitectureConfigIntegrationTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 验证配置启动帮助器在同步阻塞且存在 <see cref="SynchronizationContext" /> 的线程上仍可完成初始化,
|
/// 验证配置模块在同步阻塞且存在 <see cref="SynchronizationContext" /> 的线程上仍可完成架构初始化,
|
||||||
/// 避免架构生命周期钩子的同步桥接因为 await 捕获上下文而死锁。
|
/// 直接覆盖 <see cref="GameConfigModule" /> 通过生命周期钩子执行同步桥接的真实路径。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Test]
|
[Test]
|
||||||
public void GameConfigBootstrapShouldSupportSynchronousBridgeOnBlockingSynchronizationContext()
|
public void GameConfigModuleShouldSupportSynchronousBridgeOnBlockingSynchronizationContext()
|
||||||
{
|
{
|
||||||
var rootPath = CreateTempConfigRoot();
|
var rootPath = CreateTempConfigRoot();
|
||||||
GameConfigBootstrap? bootstrap = null;
|
ConsumerArchitecture? architecture = null;
|
||||||
|
var initialized = false;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
bootstrap = CreateBootstrap(rootPath);
|
architecture = new ConsumerArchitecture(rootPath);
|
||||||
|
|
||||||
RunBlockingOnSynchronizationContext(
|
RunBlockingOnSynchronizationContext(
|
||||||
() => bootstrap.InitializeAsync(),
|
() => architecture.InitializeAsync(),
|
||||||
TimeSpan.FromSeconds(5));
|
TimeSpan.FromSeconds(5));
|
||||||
|
initialized = true;
|
||||||
|
|
||||||
var monsterTable = bootstrap.Registry.GetMonsterTable();
|
var monsterTable = architecture.Registry.GetMonsterTable();
|
||||||
Assert.Multiple(() =>
|
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.Get(1).Name, Is.EqualTo("Slime"));
|
||||||
Assert.That(monsterTable.FindByFaction("dungeon").Count(), Is.EqualTo(2));
|
Assert.That(monsterTable.FindByFaction("dungeon").Count(), Is.EqualTo(2));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
bootstrap?.Dispose();
|
if (architecture is not null && initialized)
|
||||||
|
{
|
||||||
|
architecture.DestroyAsync().GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
DeleteDirectoryIfExists(rootPath);
|
DeleteDirectoryIfExists(rootPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -322,14 +324,9 @@ public class ArchitectureConfigIntegrationTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static GameConfigBootstrap CreateBootstrap(string configRoot)
|
|
||||||
{
|
|
||||||
return new GameConfigBootstrap(CreateBootstrapOptions(configRoot));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 创建一个使用配置模块的模块实例。
|
/// 创建一个使用配置模块的模块实例。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="configRoot">测试配置根目录。</param>
|
/// <param name="configRoot">测试配置根目录。</param>
|
||||||
/// <returns>已配置的模块实例。</returns>
|
/// <returns>已配置的模块实例。</returns>
|
||||||
private static GameConfigModule CreateModule(string configRoot)
|
private static GameConfigModule CreateModule(string configRoot)
|
||||||
|
|||||||
@ -22,7 +22,7 @@ public sealed class GameConfigModule : IArchitectureModule
|
|||||||
{
|
{
|
||||||
private const int InstallStateNotInstalled = 0;
|
private const int InstallStateNotInstalled = 0;
|
||||||
private const int InstallStateInstalling = 1;
|
private const int InstallStateInstalling = 1;
|
||||||
private const int InstallStateInstalled = 2;
|
private const int InstallStateConsumed = 2;
|
||||||
|
|
||||||
private readonly GameConfigBootstrap _bootstrap;
|
private readonly GameConfigBootstrap _bootstrap;
|
||||||
private readonly ModuleBootstrapLifetimeUtility _lifetimeUtility;
|
private readonly ModuleBootstrapLifetimeUtility _lifetimeUtility;
|
||||||
@ -76,6 +76,11 @@ public sealed class GameConfigModule : IArchitectureModule
|
|||||||
/// <param name="architecture">目标架构实例。</param>
|
/// <param name="architecture">目标架构实例。</param>
|
||||||
/// <exception cref="ArgumentNullException">当 <paramref name="architecture" /> 为空时抛出。</exception>
|
/// <exception cref="ArgumentNullException">当 <paramref name="architecture" /> 为空时抛出。</exception>
|
||||||
/// <exception cref="InvalidOperationException">当同一个模块实例被重复安装时抛出。</exception>
|
/// <exception cref="InvalidOperationException">当同一个模块实例被重复安装时抛出。</exception>
|
||||||
|
/// <remarks>
|
||||||
|
/// 生命周期阶段校验会在任何注册动作前执行,因此错过安装窗口的调用不会消耗当前模块实例。
|
||||||
|
/// 一旦开始向架构注册 utility 或生命周期钩子,就不存在回滚 API;因此后续任何失败都会把该模块实例视为已消耗,
|
||||||
|
/// 调用方必须创建新的 <see cref="GameConfigModule" /> 再重试。
|
||||||
|
/// </remarks>
|
||||||
public void Install(IArchitecture architecture)
|
public void Install(IArchitecture architecture)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(architecture);
|
ArgumentNullException.ThrowIfNull(architecture);
|
||||||
@ -93,16 +98,18 @@ public sealed class GameConfigModule : IArchitectureModule
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 先注册生命周期钩子,确保任何“已错过 BeforeUtilityInit”的安装都会在暴露注册表之前失败,
|
// 阶段窗口已经在前面做过无副作用校验,因此这里优先注册 utility,
|
||||||
// 避免架构看到永远不会完成首次加载的半安装配置入口。
|
// 让常见的容器/上下文接线失败在不可回滚的 hook 注册之前暴露出来。
|
||||||
architecture.RegisterLifecycleHook(new BootstrapInitializationHook(_bootstrap));
|
|
||||||
architecture.RegisterUtility(Registry);
|
architecture.RegisterUtility(Registry);
|
||||||
architecture.RegisterUtility(_lifetimeUtility);
|
architecture.RegisterUtility(_lifetimeUtility);
|
||||||
Volatile.Write(ref _installState, InstallStateInstalled);
|
architecture.RegisterLifecycleHook(new BootstrapInitializationHook(_bootstrap));
|
||||||
|
Volatile.Write(ref _installState, InstallStateConsumed);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
Volatile.Write(ref _installState, InstallStateNotInstalled);
|
// 架构对 utility / hook 注册都不提供回滚入口,因此一旦进入注册阶段,
|
||||||
|
// 即使安装失败也必须禁止复用同一个模块实例,避免重复暴露共享注册表或挂上第二个 hook。
|
||||||
|
Volatile.Write(ref _installState, InstallStateConsumed);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user