mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-07 08:44:29 +08:00
- 实现 GameConfigBootstrap 启动帮助器,统一管理配置注册表、YAML加载器与热重载句柄 - 创建 GameConfigModule 配置模块,集成到 Architecture 生命周期中完成自动加载与资源回收 - 实现 YamlConfigLoader 基于文件目录的YAML配置加载器,支持批量加载与热重载功能 - 添加 ArchitectureConfigIntegrationTests 集成测试,验证模块安装、加载顺序与表访问 - 实现热重载防抖机制,支持开发期配置变更监听与增量更新 - 提供同步上下文桥接支持,避免Unity主线程或UI线程上的死锁问题
200 lines
8.7 KiB
C#
200 lines
8.7 KiB
C#
using System;
|
||
using System.Threading;
|
||
using GFramework.Core.Abstractions.Architectures;
|
||
using GFramework.Core.Abstractions.Enums;
|
||
using GFramework.Core.Architectures;
|
||
using GFramework.Core.Utility;
|
||
using GFramework.Game.Abstractions.Config;
|
||
|
||
namespace GFramework.Game.Config;
|
||
|
||
/// <summary>
|
||
/// 提供基于 <see cref="Architecture" /> 的官方配置模块接入入口。
|
||
/// 该模块负责把 <see cref="GameConfigBootstrap" /> 挂接到架构生命周期中,统一完成注册表暴露、
|
||
/// 首次加载以及架构销毁时的资源回收。
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// 使用该模块时,推荐在 <c>Architecture.OnInitialize()</c> 的较早位置调用 <see cref="IArchitecture.InstallModule" />,
|
||
/// 以便其他 utility、model 和 system 在各自初始化阶段都能读取到已经完成首次加载的配置表。
|
||
/// 如果消费项目不基于 <see cref="Architecture" />,则继续直接使用 <see cref="GameConfigBootstrap" /> 更合适。
|
||
/// </remarks>
|
||
public sealed class GameConfigModule : IArchitectureModule
|
||
{
|
||
private const int InstallStateNotInstalled = 0;
|
||
private const int InstallStateInstalling = 1;
|
||
private const int InstallStateInstalled = 2;
|
||
|
||
private readonly GameConfigBootstrap _bootstrap;
|
||
private readonly ModuleBootstrapLifetimeUtility _lifetimeUtility;
|
||
private int _installState;
|
||
|
||
/// <summary>
|
||
/// 使用指定的启动选项创建配置模块。
|
||
/// </summary>
|
||
/// <param name="options">配置启动帮助器选项。</param>
|
||
/// <exception cref="ArgumentNullException">当 <paramref name="options" /> 为空时抛出。</exception>
|
||
/// <exception cref="ArgumentException">
|
||
/// 当 <paramref name="options" /> 不满足 <see cref="GameConfigBootstrap" /> 的构造约束时抛出。
|
||
/// </exception>
|
||
public GameConfigModule(GameConfigBootstrapOptions options)
|
||
{
|
||
ArgumentNullException.ThrowIfNull(options);
|
||
|
||
_bootstrap = new GameConfigBootstrap(options);
|
||
_lifetimeUtility = new ModuleBootstrapLifetimeUtility(_bootstrap);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取当前模块复用的配置注册表。
|
||
/// 该实例会在模块安装时注册为架构 utility,供其他组件通过上下文直接读取。
|
||
/// </summary>
|
||
public IConfigRegistry Registry => _bootstrap.Registry;
|
||
|
||
/// <summary>
|
||
/// 获取一个值,指示模块绑定的配置启动器是否已经完成首次加载。
|
||
/// </summary>
|
||
public bool IsInitialized => _bootstrap.IsInitialized;
|
||
|
||
/// <summary>
|
||
/// 获取一个值,指示模块绑定的开发期热重载是否已启用。
|
||
/// </summary>
|
||
public bool IsHotReloadEnabled => _bootstrap.IsHotReloadEnabled;
|
||
|
||
/// <summary>
|
||
/// 获取当前生效的 YAML 配置加载器。
|
||
/// 只有在模块完成首次加载后该属性才可访问。
|
||
/// </summary>
|
||
/// <exception cref="ObjectDisposedException">当模块所属架构已销毁时抛出。</exception>
|
||
/// <exception cref="InvalidOperationException">当首次加载尚未成功完成时抛出。</exception>
|
||
public YamlConfigLoader Loader => _bootstrap.Loader;
|
||
|
||
/// <summary>
|
||
/// 将配置模块安装到指定架构中。
|
||
/// 安装后会立即暴露 <see cref="Registry" />,并注册一个生命周期钩子,
|
||
/// 以便在 utility 初始化之前完成一次确定性的配置加载。
|
||
/// </summary>
|
||
/// <param name="architecture">目标架构实例。</param>
|
||
/// <exception cref="ArgumentNullException">当 <paramref name="architecture" /> 为空时抛出。</exception>
|
||
/// <exception cref="InvalidOperationException">当同一个模块实例被重复安装时抛出。</exception>
|
||
public void Install(IArchitecture architecture)
|
||
{
|
||
ArgumentNullException.ThrowIfNull(architecture);
|
||
|
||
ValidateInstallationPhase(architecture);
|
||
|
||
if (Interlocked.CompareExchange(
|
||
ref _installState,
|
||
InstallStateInstalling,
|
||
InstallStateNotInstalled) != InstallStateNotInstalled)
|
||
{
|
||
throw new InvalidOperationException(
|
||
"The same GameConfigModule instance cannot be installed more than once.");
|
||
}
|
||
|
||
try
|
||
{
|
||
// 先注册生命周期钩子,确保任何“已错过 BeforeUtilityInit”的安装都会在暴露注册表之前失败,
|
||
// 避免架构看到永远不会完成首次加载的半安装配置入口。
|
||
architecture.RegisterLifecycleHook(new BootstrapInitializationHook(_bootstrap));
|
||
architecture.RegisterUtility(Registry);
|
||
architecture.RegisterUtility(_lifetimeUtility);
|
||
Volatile.Write(ref _installState, InstallStateInstalled);
|
||
}
|
||
catch
|
||
{
|
||
Volatile.Write(ref _installState, InstallStateNotInstalled);
|
||
throw;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 在首次加载成功后显式启用开发期热重载。
|
||
/// 该入口与 <see cref="GameConfigBootstrap.StartHotReload" /> 的语义保持一致,
|
||
/// 供已经保留模块实例引用的架构启动层按环境决定是否追加监听。
|
||
/// </summary>
|
||
/// <param name="options">热重载选项;为空时使用默认行为。</param>
|
||
public void StartHotReload(YamlConfigHotReloadOptions? options = null)
|
||
{
|
||
_bootstrap.StartHotReload(options);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 停止开发期热重载并释放监听资源。
|
||
/// 该方法是幂等的,允许架构外部的开发期开关无条件调用。
|
||
/// </summary>
|
||
public void StopHotReload()
|
||
{
|
||
_bootstrap.StopHotReload();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 在 utility 初始化之前完成首次配置加载的生命周期钩子。
|
||
/// 这样后续 utility、model 和 system 在各自初始化阶段就能直接依赖已加载的注册表。
|
||
/// </summary>
|
||
private sealed class BootstrapInitializationHook(GameConfigBootstrap bootstrap) : IArchitectureLifecycleHook
|
||
{
|
||
/// <summary>
|
||
/// 在目标阶段触发配置加载。
|
||
/// </summary>
|
||
/// <param name="phase">当前架构阶段。</param>
|
||
/// <param name="architecture">相关架构实例;当前实现不直接使用,但保留用于接口契约一致性。</param>
|
||
public void OnPhase(ArchitecturePhase phase, IArchitecture architecture)
|
||
{
|
||
if (phase != ArchitecturePhase.BeforeUtilityInit)
|
||
{
|
||
return;
|
||
}
|
||
|
||
// 架构生命周期钩子当前是同步接口,因此这里显式桥接到统一的 bootstrap 异步实现,
|
||
// 让 Architecture 模式和独立运行时模式保持同一套加载、诊断和热重载启动语义。
|
||
bootstrap.InitializeAsync().ConfigureAwait(false).GetAwaiter().GetResult();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证模块仍处于允许接入的生命周期窗口。
|
||
/// 该模块依赖 <see cref="ArchitecturePhase.BeforeUtilityInit" /> 钩子完成首次加载,
|
||
/// 因此一旦架构已经离开 <see cref="ArchitecturePhase.None" />,继续安装只会错过首载时机。
|
||
/// </summary>
|
||
/// <param name="architecture">目标架构实例。</param>
|
||
/// <exception cref="InvalidOperationException">
|
||
/// 当目标架构已经开始组件初始化阶段时抛出。
|
||
/// </exception>
|
||
private static void ValidateInstallationPhase(IArchitecture architecture)
|
||
{
|
||
if (architecture is not Architecture concreteArchitecture)
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (concreteArchitecture.CurrentPhase != ArchitecturePhase.None)
|
||
{
|
||
throw new InvalidOperationException(
|
||
"GameConfigModule must be installed before the architecture enters BeforeUtilityInit.");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 跟随架构 utility 生命周期释放底层 bootstrap 资源的薄封装。
|
||
/// 该 utility 本身不承担加载职责,只负责在架构销毁时停止热重载并释放监听句柄。
|
||
/// </summary>
|
||
private sealed class ModuleBootstrapLifetimeUtility(GameConfigBootstrap bootstrap) : AbstractContextUtility
|
||
{
|
||
/// <summary>
|
||
/// 该 utility 不需要在初始化阶段执行额外逻辑。
|
||
/// 首次加载已经由 <see cref="BootstrapInitializationHook" /> 在 utility 初始化前完成。
|
||
/// </summary>
|
||
protected override void OnInit()
|
||
{
|
||
}
|
||
|
||
/// <summary>
|
||
/// 架构销毁时释放 bootstrap 资源,确保热重载监听句柄不会泄漏到架构生命周期之外。
|
||
/// </summary>
|
||
protected override void OnDestroy()
|
||
{
|
||
bootstrap.Dispose();
|
||
}
|
||
}
|
||
}
|