224 lines
8.3 KiB
C#
224 lines
8.3 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
|
|
namespace ShrinkModFramework
|
|
{
|
|
public interface IReadOnlyShrinkModRegistry<T>
|
|
{
|
|
string Name { get; }
|
|
IReadOnlyCollection<ShrinkRegistryEntry<T>> Entries { get; }
|
|
IReadOnlyCollection<ShrinkRegistryEntry<T>> BaseEntries { get; }
|
|
bool Contains(string key);
|
|
bool TryGet(string key, out T value);
|
|
bool TryGetEntry(string key, out ShrinkRegistryEntry<T> entry);
|
|
IReadOnlyList<ShrinkRegistryEntry<T>> GetOverrideCandidates(string key);
|
|
}
|
|
|
|
internal interface IShrinkOwnedRegistry
|
|
{
|
|
void RemoveOwnedEntries(string ownerModId);
|
|
}
|
|
|
|
internal sealed class ShrinkModRegistry<T> : IReadOnlyShrinkModRegistry<T>, IShrinkOwnedRegistry
|
|
{
|
|
private readonly Dictionary<string, ShrinkRegistryEntry<T>> _baseEntries =
|
|
new(StringComparer.Ordinal);
|
|
private readonly Dictionary<string, List<ShrinkRegistryEntry<T>>> _overrides =
|
|
new(StringComparer.Ordinal);
|
|
|
|
public string Name { get; }
|
|
|
|
internal ShrinkModRegistry(string name)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(name))
|
|
throw new ArgumentException("注册表名称不能为空。", nameof(name));
|
|
|
|
Name = name.Trim();
|
|
}
|
|
|
|
/// <summary>当前生效项;存在覆盖时返回最高优先级的覆盖项。</summary>
|
|
public IReadOnlyCollection<ShrinkRegistryEntry<T>> Entries => _baseEntries.Keys
|
|
.OrderBy(key => key, StringComparer.Ordinal)
|
|
.Select(ResolveEntry)
|
|
.ToArray();
|
|
|
|
/// <summary>不应用覆盖的基础注册项快照。</summary>
|
|
public IReadOnlyCollection<ShrinkRegistryEntry<T>> BaseEntries => _baseEntries.Values
|
|
.OrderBy(entry => entry.Key, StringComparer.Ordinal)
|
|
.ToArray();
|
|
|
|
private void RegisterBase(string ownerModId, string key, T value)
|
|
{
|
|
ownerModId = NormalizeOwner(ownerModId);
|
|
key = NormalizeKey(key);
|
|
if (_baseEntries.ContainsKey(key))
|
|
throw new InvalidOperationException($"注册表 '{Name}' 中已存在键 '{key}'。");
|
|
|
|
_baseEntries.Add(key, new ShrinkRegistryEntry<T>(ownerModId, key, value));
|
|
}
|
|
|
|
/// <summary>
|
|
/// 使用 owner 作为 namespace 注册内容,并返回最终键。localKey 只能是局部名称,不能包含冒号。
|
|
/// </summary>
|
|
public string RegisterNamespaced(string ownerModId, string localKey, T value)
|
|
{
|
|
ownerModId = NormalizeOwner(ownerModId);
|
|
if (ownerModId.IndexOf(':') >= 0)
|
|
throw new ArgumentException("归属模组 ID 不能包含 ':'。", nameof(ownerModId));
|
|
if (string.IsNullOrWhiteSpace(localKey))
|
|
throw new ArgumentException("局部注册键不能为空。", nameof(localKey));
|
|
|
|
localKey = localKey.Trim();
|
|
if (localKey.IndexOf(':') >= 0)
|
|
throw new ArgumentException("局部注册键不能包含 ':'。", nameof(localKey));
|
|
|
|
var key = ownerModId + ":" + localKey;
|
|
RegisterBase(ownerModId, key, value);
|
|
return key;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 为已有基础项注册显式覆盖。优先级越高越先命中;同一目标的相同优先级视为硬冲突。
|
|
/// </summary>
|
|
public void RegisterOverride(string ownerModId, string targetKey, int priority, T value)
|
|
{
|
|
ownerModId = NormalizeOwner(ownerModId);
|
|
targetKey = NormalizeKey(targetKey);
|
|
if (!_baseEntries.ContainsKey(targetKey))
|
|
throw new InvalidOperationException(
|
|
$"注册表 '{Name}' 的覆盖目标 '{targetKey}' 不存在。");
|
|
|
|
if (!_overrides.TryGetValue(targetKey, out var candidates))
|
|
{
|
|
candidates = new List<ShrinkRegistryEntry<T>>();
|
|
_overrides.Add(targetKey, candidates);
|
|
}
|
|
|
|
if (candidates.Any(candidate => candidate.Priority == priority))
|
|
throw new InvalidOperationException(
|
|
$"注册表 '{Name}' 的键 '{targetKey}' 在优先级 {priority} 存在覆盖冲突。");
|
|
|
|
candidates.Add(ShrinkRegistryEntry<T>.CreateOverride(ownerModId, targetKey, priority, value));
|
|
}
|
|
|
|
public IReadOnlyList<ShrinkRegistryEntry<T>> GetOverrideCandidates(string key)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(key) || !_overrides.TryGetValue(key.Trim(), out var candidates))
|
|
return Array.Empty<ShrinkRegistryEntry<T>>();
|
|
|
|
return candidates
|
|
.OrderByDescending(candidate => candidate.Priority)
|
|
.ThenBy(candidate => candidate.OwnerModId, StringComparer.Ordinal)
|
|
.ToArray();
|
|
}
|
|
|
|
public bool Contains(string key) =>
|
|
!string.IsNullOrWhiteSpace(key) && _baseEntries.ContainsKey(key.Trim());
|
|
|
|
public bool TryGet(string key, out T value)
|
|
{
|
|
if (TryGetEntry(key, out var entry))
|
|
{
|
|
value = entry.Value;
|
|
return true;
|
|
}
|
|
|
|
value = default;
|
|
return false;
|
|
}
|
|
|
|
public bool TryGetEntry(string key, out ShrinkRegistryEntry<T> entry)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(key) && _baseEntries.ContainsKey(key.Trim()))
|
|
{
|
|
entry = ResolveEntry(key.Trim());
|
|
return true;
|
|
}
|
|
|
|
entry = default;
|
|
return false;
|
|
}
|
|
|
|
void IShrinkOwnedRegistry.RemoveOwnedEntries(string ownerModId)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(ownerModId))
|
|
return;
|
|
|
|
ownerModId = ownerModId.Trim();
|
|
var keys = _baseEntries
|
|
.Where(pair => string.Equals(pair.Value.OwnerModId, ownerModId, StringComparison.Ordinal))
|
|
.Select(pair => pair.Key)
|
|
.ToArray();
|
|
foreach (var key in keys)
|
|
_baseEntries.Remove(key);
|
|
|
|
var emptyOverrideTargets = new List<string>();
|
|
foreach (var pair in _overrides)
|
|
{
|
|
pair.Value.RemoveAll(candidate =>
|
|
string.Equals(candidate.OwnerModId, ownerModId, StringComparison.Ordinal));
|
|
if (pair.Value.Count == 0)
|
|
emptyOverrideTargets.Add(pair.Key);
|
|
}
|
|
|
|
foreach (var targetKey in emptyOverrideTargets)
|
|
_overrides.Remove(targetKey);
|
|
}
|
|
|
|
private ShrinkRegistryEntry<T> ResolveEntry(string key)
|
|
{
|
|
if (_overrides.TryGetValue(key, out var candidates) && candidates.Count > 0)
|
|
{
|
|
return candidates
|
|
.OrderByDescending(candidate => candidate.Priority)
|
|
.ThenBy(candidate => candidate.OwnerModId, StringComparer.Ordinal)
|
|
.First();
|
|
}
|
|
|
|
return _baseEntries[key];
|
|
}
|
|
|
|
private static string NormalizeOwner(string ownerModId)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(ownerModId))
|
|
throw new ArgumentException("归属模组 ID 不能为空。", nameof(ownerModId));
|
|
return ownerModId.Trim();
|
|
}
|
|
|
|
private static string NormalizeKey(string key)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(key))
|
|
throw new ArgumentException("注册键不能为空。", nameof(key));
|
|
return key.Trim();
|
|
}
|
|
}
|
|
|
|
public readonly struct ShrinkRegistryEntry<T>
|
|
{
|
|
public string OwnerModId { get; }
|
|
public string Key { get; }
|
|
public T Value { get; }
|
|
public bool IsOverride { get; }
|
|
public int Priority { get; }
|
|
|
|
public ShrinkRegistryEntry(string ownerModId, string key, T value)
|
|
: this(ownerModId, key, value, false, 0)
|
|
{
|
|
}
|
|
|
|
private ShrinkRegistryEntry(string ownerModId, string key, T value, bool isOverride, int priority)
|
|
{
|
|
OwnerModId = ownerModId;
|
|
Key = key;
|
|
Value = value;
|
|
IsOverride = isOverride;
|
|
Priority = priority;
|
|
}
|
|
|
|
internal static ShrinkRegistryEntry<T> CreateOverride(
|
|
string ownerModId, string key, int priority, T value) =>
|
|
new(ownerModId, key, value, true, priority);
|
|
}
|
|
}
|