Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions GFramework.Game/Config/YamlConfigLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ namespace GFramework.Game.Config;
/// </summary>
public sealed class YamlConfigLoader : IConfigLoader
{
private const string DefaultHotReloadUnavailableMessage =
"Hot reload is not available for the current loader configuration.";

private const string RootPathCannotBeNullOrWhiteSpaceMessage = "Root path cannot be null or whitespace.";
private const string TableNameCannotBeNullOrWhiteSpaceMessage = "Table name cannot be null or whitespace.";
private const string RelativePathCannotBeNullOrWhiteSpaceMessage = "Relative path cannot be null or whitespace.";
Expand All @@ -22,7 +25,9 @@ public sealed class YamlConfigLoader : IConfigLoader

private static readonly TimeSpan DefaultHotReloadDebounceDelay = TimeSpan.FromMilliseconds(200);

private readonly Func<bool> _canEnableHotReload;
private readonly IDeserializer _deserializer;
private readonly string _hotReloadUnavailableMessage;

private readonly Dictionary<string, IReadOnlyCollection<string>> _lastSuccessfulDependencies =
new(StringComparer.Ordinal);
Expand All @@ -36,13 +41,38 @@ public sealed class YamlConfigLoader : IConfigLoader
/// <param name="rootPath">配置根目录。</param>
/// <exception cref="ArgumentException">当 <paramref name="rootPath" /> 为空时抛出。</exception>
public YamlConfigLoader(string rootPath)
: this(rootPath, null, null)
{
}

/// <summary>
/// 使用指定配置根目录与热重载可用性守卫创建 YAML 配置加载器。
/// </summary>
/// <param name="rootPath">配置根目录。</param>
/// <param name="canEnableHotReload">
/// 用于判断当前实例是否允许启用热重载的委托。
/// 宿主适配层可借此把额外的文件系统前置条件下沉到底层加载器,避免公开实例被绕过时启用错误监听目标。
/// </param>
/// <param name="hotReloadUnavailableMessage">
/// 当 <paramref name="canEnableHotReload" /> 返回 <see langword="false" /> 时抛出的异常消息;
/// 为空时使用默认消息。
/// </param>
/// <exception cref="ArgumentException">当 <paramref name="rootPath" /> 为空时抛出。</exception>
internal YamlConfigLoader(
string rootPath,
Func<bool>? canEnableHotReload,
string? hotReloadUnavailableMessage)
{
if (string.IsNullOrWhiteSpace(rootPath))
{
throw new ArgumentException(RootPathCannotBeNullOrWhiteSpaceMessage, nameof(rootPath));
}

_rootPath = rootPath;
_canEnableHotReload = canEnableHotReload ?? (() => true);
_hotReloadUnavailableMessage = string.IsNullOrWhiteSpace(hotReloadUnavailableMessage)
? DefaultHotReloadUnavailableMessage
: hotReloadUnavailableMessage;
_deserializer = new DeserializerBuilder()
.WithNamingConvention(CamelCaseNamingConvention.Instance)
.IgnoreUnmatchedProperties()
Expand Down Expand Up @@ -136,6 +166,7 @@ public IUnRegister EnableHotReload(
{
ArgumentNullException.ThrowIfNull(registry);
options ??= new YamlConfigHotReloadOptions();
EnsureHotReloadCanBeEnabled();
if (options.DebounceDelay < TimeSpan.Zero)
{
throw new ArgumentOutOfRangeException(
Expand All @@ -154,6 +185,19 @@ public IUnRegister EnableHotReload(
options.DebounceDelay);
}

private void EnsureHotReloadCanBeEnabled()
{
if (_canEnableHotReload())
{
return;
}

// Host adapters can attach additional filesystem constraints to the loader instance.
// Enforcing the guard here prevents callers from bypassing the adapter by invoking
// EnableHotReload directly on the exposed loader reference.
throw new InvalidOperationException(_hotReloadUnavailableMessage);
}

private void UpdateLastSuccessfulDependencies(IEnumerable<YamlTableLoadResult> loadedTables)
{
_lastSuccessfulDependencies.Clear();
Expand Down
4 changes: 4 additions & 0 deletions GFramework.Game/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("GFramework.Game.Tests")]
[assembly: InternalsVisibleTo("GFramework.Godot")]
Loading
Loading