Files
FlowScope/My project/Assets/FlowScope/Runtime/Config/JsonConfigProvider.cs
2026-05-18 11:58:49 +08:00

133 lines
4.7 KiB
C#

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using FlowScope.Data;
namespace FlowScope.Config
{
public sealed class JsonConfigProvider : IConfigProvider
{
private readonly IReadOnlyList<ConfigMapping> _mappings;
private readonly Dictionary<Type, object> _rowsByType = new();
public JsonConfigProvider(IReadOnlyList<ConfigMapping> mappings)
{
_mappings = mappings ?? throw new ArgumentNullException(nameof(mappings));
}
public static ConfigMapping Mapping<T>(string fileName, Func<CancellationToken, Task<string>> loadTextAsync)
where T : class, IConfigRow
{
return new ConfigMapping(typeof(T), fileName, loadTextAsync);
}
public async Task LoadAllAsync(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var loadedRows = new Dictionary<Type, object>();
foreach (var mapping in _mappings)
{
cancellationToken.ThrowIfCancellationRequested();
string json;
try
{
json = await mapping.LoadTextAsync(cancellationToken);
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception exception)
{
throw new InvalidOperationException($"Failed to load config file '{mapping.FileName}'.", exception);
}
try
{
loadedRows[mapping.RowType] = mapping.Parse(json);
}
catch (Exception exception) when (exception is not OperationCanceledException)
{
throw new InvalidOperationException(
$"Failed to parse config file '{mapping.FileName}': {exception.GetBaseException().Message}",
exception);
}
}
_rowsByType.Clear();
foreach (var pair in loadedRows)
{
_rowsByType[pair.Key] = pair.Value;
}
}
public T Get<T>(int id) where T : class, IConfigRow
{
return _rowsByType.TryGetValue(typeof(T), out var table) &&
table is Dictionary<int, T> rows &&
rows.TryGetValue(id, out var row)
? row
: null;
}
public IReadOnlyList<T> GetAll<T>() where T : class, IConfigRow
{
if (!_rowsByType.TryGetValue(typeof(T), out var table) || table is not Dictionary<int, T> rows)
{
return Array.Empty<T>();
}
return new List<T>(rows.Values);
}
public sealed class ConfigMapping
{
private readonly Func<string, object> _parse;
internal ConfigMapping(Type rowType, string fileName, Func<CancellationToken, Task<string>> loadTextAsync)
{
RowType = rowType ?? throw new ArgumentNullException(nameof(rowType));
FileName = string.IsNullOrWhiteSpace(fileName)
? throw new ArgumentException("Config file name is required.", nameof(fileName))
: fileName;
LoadTextAsync = loadTextAsync ?? throw new ArgumentNullException(nameof(loadTextAsync));
_parse = CreateParser(rowType);
}
public Type RowType { get; }
public string FileName { get; }
public Func<CancellationToken, Task<string>> LoadTextAsync { get; }
internal object Parse(string json) => _parse(json);
private static Func<string, object> CreateParser(Type rowType)
{
var method = typeof(ConfigMapping)
.GetMethod(nameof(ParseRows), System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)
.MakeGenericMethod(rowType);
return json => method.Invoke(null, new object[] { json });
}
private static object ParseRows<T>(string json) where T : class, IConfigRow
{
var rows = ReactivePropertyJsonConverter.DeserializeArray<T>(json);
var byId = new Dictionary<int, T>();
foreach (var row in rows)
{
if (byId.ContainsKey(row.Id))
{
throw new InvalidOperationException($"Duplicate config id {row.Id} for {typeof(T).Name}.");
}
byId.Add(row.Id, row);
}
return byId;
}
}
}
}