using System.Threading; using GFramework.Core.Abstractions.Events; using GFramework.Game.Abstractions.Config; namespace GFramework.Game.Config; /// /// 提供官方的 C# 配置启动帮助器。 /// 该类型负责把配置注册表、YAML 加载器与开发期热重载句柄收敛到一个长生命周期对象中, /// 让消费者项目可以通过一个稳定入口完成配置启动,而不是在多个脚本里重复拼装运行时细节。 /// /// /// 生命周期转换会串行化执行,因此并发调用只会观察到已经提交完成的加载器与热重载句柄。 /// 如果初始化或热重载启动在中途失败,当前实例会保留失败前的稳定状态,而不会公开半初始化对象。 /// public sealed class GameConfigBootstrap : IDisposable { private const string ConfigureLoaderCannotBeNullMessage = "ConfigureLoader must be provided."; private const string RootPathCannotBeNullOrWhiteSpaceMessage = "Root path cannot be null or whitespace."; // All lifecycle transitions share one gate so initialization, hot-reload startup, // stop, and disposal never publish half-finished state to concurrent callers. private readonly object _stateGate = new(); private readonly GameConfigBootstrapOptions _options; private IUnRegister? _hotReload; private YamlConfigLoader? _loader; private bool _disposed; private bool _isInitializing; private bool _isStartingHotReload; private bool _stopHotReloadAfterStart; /// /// 使用指定选项创建配置启动帮助器。 /// /// 配置启动约定。 /// 为空时抛出。 /// /// 当 为空, /// 或 未提供时抛出。 /// public GameConfigBootstrap(GameConfigBootstrapOptions options) { ArgumentNullException.ThrowIfNull(options); if (string.IsNullOrWhiteSpace(options.RootPath)) { throw new ArgumentException( RootPathCannotBeNullOrWhiteSpaceMessage, nameof(options)); } if (options.ConfigureLoader == null) { throw new ArgumentException( ConfigureLoaderCannotBeNullMessage, nameof(options)); } _options = options; RootPath = options.RootPath; Registry = options.Registry ?? new ConfigRegistry(); } /// /// 获取配置根目录。 /// public string RootPath { get; } /// /// 获取当前配置生命周期共享的注册表。 /// 默认情况下该实例由启动帮助器创建;如调用方传入自定义注册表,则返回同一个对象。 /// public IConfigRegistry Registry { get; } /// /// 获取一个值,指示启动帮助器是否已经成功完成初次加载。 /// 该状态只会在完整初始化链路(包括可选热重载启动)成功后才对外可见, /// 避免并发调用观察到半初始化生命周期。 /// public bool IsInitialized { get { lock (_stateGate) { return _loader != null; } } } /// /// 获取一个值,指示开发期热重载是否已启用。 /// 只有当监听句柄已经成功创建并提交到当前生命周期后,该属性才会返回 。 /// public bool IsHotReloadEnabled { get { lock (_stateGate) { return _hotReload != null; } } } /// /// 获取当前生效的 YAML 配置加载器。 /// 只有在 成功返回后该属性才可访问。 /// /// 当当前实例已释放时抛出。 /// 当启动帮助器尚未初始化成功时抛出。 public YamlConfigLoader Loader { get { lock (_stateGate) { ThrowIfDisposedCore(); return _loader ?? throw new InvalidOperationException( "The config bootstrap has not been initialized yet."); } } } /// /// 执行初次配置加载,并在需要时启动开发期热重载。 /// 该方法只能成功调用一次,避免同一个生命周期对象在运行中被重新拼装为另一套加载约定。 /// /// 取消令牌。 /// /// 该入口会被 的同步生命周期钩子桥接调用, /// 因此内部所有异步等待都必须使用 ConfigureAwait(false),避免在 Unity 主线程、 /// UI Dispatcher 或带 的测试线程上发生同步阻塞死锁。 /// /// 表示异步初始化流程的任务。 /// 当当前实例已释放时抛出。 /// 当当前实例已经初始化成功时抛出。 /// 当配置加载失败时抛出。 public async Task InitializeAsync(CancellationToken cancellationToken = default) { lock (_stateGate) { ThrowIfDisposedCore(); if (_isInitializing || _loader != null) { throw new InvalidOperationException( "The config bootstrap can only be initialized once per instance."); } _isInitializing = true; } IUnRegister? hotReload = null; try { var loader = new YamlConfigLoader(RootPath); _options.ConfigureLoader!(loader); await loader.LoadAsync(Registry, cancellationToken).ConfigureAwait(false); if (_options.EnableHotReload) { hotReload = loader.EnableHotReload(Registry, _options.HotReloadOptions); } lock (_stateGate) { try { ThrowIfDisposedCore(); // 仅在初次加载与可选热重载都完整成功后才提交结果, // 避免 IsInitialized / Loader 暴露半初始化生命周期。 _loader = loader; _hotReload = hotReload; hotReload = null; } finally { _isInitializing = false; } } } catch { lock (_stateGate) { _isInitializing = false; } hotReload?.UnRegister(); throw; } } /// /// 启用开发期热重载。 /// 该入口让调用方可以先完成一次确定性的初始加载,再按环境决定是否追加文件监听。 /// /// 热重载选项;为空时使用 的默认行为。 /// 当当前实例已释放时抛出。 /// /// 当初始加载尚未完成,或热重载已经处于启用状态时抛出。 /// /// /// 当 小于 /// 时抛出。 /// public void StartHotReload(YamlConfigHotReloadOptions? options = null) { var loader = BeginHotReloadStart(); IUnRegister? hotReload = null; try { hotReload = loader.EnableHotReload(Registry, options); hotReload = CompleteHotReloadStart(hotReload); } catch { ResetHotReloadStartAfterFailure(); hotReload?.UnRegister(); throw; } } /// /// 停止开发期热重载并释放监听资源。 /// 该方法是幂等的,允许启动层在销毁阶段无条件调用。 /// public void StopHotReload() { IUnRegister? hotReload; lock (_stateGate) { if (_isStartingHotReload && _hotReload == null) { _stopHotReloadAfterStart = true; return; } hotReload = _hotReload; _hotReload = null; } hotReload?.UnRegister(); } /// /// 停止热重载并释放当前帮助器持有的资源。 /// public void Dispose() { IUnRegister? hotReload; lock (_stateGate) { if (_disposed) { return; } _disposed = true; if (_isStartingHotReload && _hotReload == null) { _stopHotReloadAfterStart = true; } hotReload = _hotReload; _hotReload = null; } hotReload?.UnRegister(); } private void ThrowIfDisposedCore() { if (_disposed) { throw new ObjectDisposedException(nameof(GameConfigBootstrap)); } } private YamlConfigLoader BeginHotReloadStart() { lock (_stateGate) { ThrowIfDisposedCore(); var loader = _loader ?? throw new InvalidOperationException( "Hot reload can only be started after the initial config load succeeds."); if (_isStartingHotReload || _hotReload != null) { throw new InvalidOperationException("Hot reload is already enabled."); } _isStartingHotReload = true; _stopHotReloadAfterStart = false; return loader; } } private IUnRegister? CompleteHotReloadStart(IUnRegister? hotReload) { var shouldStop = false; lock (_stateGate) { try { ThrowIfDisposedCore(); // Stop/Dispose may arrive while the watcher is being created. In that // case, release the new handle immediately instead of publishing it. if (_stopHotReloadAfterStart) { shouldStop = true; _stopHotReloadAfterStart = false; } else { _hotReload = hotReload; hotReload = null; } } finally { _isStartingHotReload = false; } } if (shouldStop) { hotReload?.UnRegister(); return null; } return hotReload; } private void ResetHotReloadStartAfterFailure() { lock (_stateGate) { _isStartingHotReload = false; _stopHotReloadAfterStart = false; } } }