Files
cneicy 6eebb8e8e4
Publish UPM package / publish (push) Failing after 1s
chore: initialize standalone UPM package
2026-08-26 02:49:58 +08:00

301 lines
13 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#nullable enable
using System;
using System.Collections.Generic;
using Cysharp.Threading.Tasks;
namespace ShrinkContext
{
/// <summary>
/// 一等上下文(论文 Γ∞ 的运行时对应物)。
/// - 树结构:由 <see cref="Isolate"/> 派生隔离子上下文,或由运行时为纤程派生子上下文;
/// - 效应:本上下文注册的可逆效应按注册逆序(LIFO)在 <see cref="DisposeAsync"/> 中回滚;
/// - 余效应:Set/Get 委托给运行时共享存储,键先经隔离表解析为 realm 再查表。
/// </summary>
public sealed class ShrinkCtx
{
private readonly List<ShrinkEffectHandle> _effects = new();
private readonly Dictionary<string, string>? _isolateOverlay;
private Dictionary<string, IReadOnlyDictionary<string, object?>>? _interceptOverlay;
internal ShrinkCtx(ShrinkContextRuntime runtime, ShrinkCtx? parent, ShrinkFiber? ownerFiber,
Dictionary<string, string>? isolateOverlay,
IReadOnlyDictionary<string, IReadOnlyDictionary<string, object?>>? interceptOverlay = null)
{
Runtime = runtime;
Parent = parent;
OwnerFiber = ownerFiber;
_isolateOverlay = isolateOverlay;
ReplaceIntercept(interceptOverlay);
}
public ShrinkContextRuntime Runtime { get; }
public ShrinkCtx? Parent { get; }
public ShrinkFiber? OwnerFiber { get; }
// ---------------- 效应(论文 3.1 / 算法 1 ----------------
/// <summary>同步前向 + 可选同步逆操作的可逆效应;前向立即执行。</summary>
public ShrinkEffectHandle Effect(Action forward, Action? inverse = null)
{
if (forward == null)
throw new ArgumentNullException(nameof(forward));
forward();
var handle = new ShrinkEffectHandle();
if (inverse != null)
handle.AttachInverse(() =>
{
inverse();
return UniTask.CompletedTask;
});
Attach(handle);
return handle;
}
/// <summary>异步前向,完成时产出异步逆操作(论文 𝔈Γ)。DisposeAsync 会等待进行中的前向。</summary>
public async UniTask<ShrinkEffectHandle> EffectAsync(Func<UniTask<Func<UniTask>>> effectFn)
{
if (effectFn == null)
throw new ArgumentNullException(nameof(effectFn));
var handle = new ShrinkEffectHandle();
var gate = new UniTaskCompletionSource();
handle.BeginForward(gate.Task);
Attach(handle);
Func<UniTask>? inverse = null;
try
{
inverse = await effectFn();
handle.CompleteForward(inverse);
}
finally
{
// 先登记逆操作再放行 gate,保证等待前向的 DisposeAsync 能读到完整逆链
gate.TrySetResult();
}
return handle;
}
internal void Attach(ShrinkEffectHandle handle) => _effects.Add(handle);
/// <summary>
/// 前向已在外部执行完毕的效应:仅登记逆操作(供桥接适配器把既有 API 的"注册/注销"对包装为可逆效应)。
/// </summary>
public ShrinkEffectHandle EffectInverse(Func<UniTask> inverse)
{
if (inverse == null)
throw new ArgumentNullException(nameof(inverse));
var handle = new ShrinkEffectHandle();
handle.AttachInverse(inverse);
Attach(handle);
return handle;
}
/// <summary>按 LIFO 顺序执行本上下文注册的全部逆操作。</summary>
public async UniTask DisposeAsync()
{
for (var i = _effects.Count - 1; i >= 0; i--)
await _effects[i].DisposeAsync();
_effects.Clear();
}
// ---------------- 余效应(论文 3.2 / 算法 2 ----------------
/// <summary>
/// 提供依赖(set 是可逆效应):立即安装绑定并通知依赖者;
/// 逆操作撤回绑定并再次通知。句柄同时挂在当前上下文上,随上下文卸载自动撤回。
/// </summary>
public ShrinkEffectHandle Set<T>(string key, T value) => Runtime.SetBinding(this, key, value, null);
/// <summary>提供带访问策略的依赖;策略只在介导 Get 时运行,原始编排查找不会运行策略。</summary>
public ShrinkEffectHandle Set<T>(string key, T value, IShrinkCoeffectAccessPolicy accessPolicy) =>
Runtime.SetBinding(this, key, value, accessPolicy ?? throw new ArgumentNullException(nameof(accessPolicy)));
public ShrinkEffectHandle Set<T>(ShrinkKey<T> key, T value) =>
Set((key ?? throw new ArgumentNullException(nameof(key))).Id, value);
public ShrinkEffectHandle Set<T>(ShrinkKey<T> key, T value, IShrinkCoeffectAccessPolicy accessPolicy) =>
Set((key ?? throw new ArgumentNullException(nameof(key))).Id, value, accessPolicy);
/// <summary>
/// 代理介导的依赖访问(论文算法 6):沿纤程链解析已提交视图;
/// 声明了但未激活的键抛 <see cref="ShrinkInactiveAccessException"/>
/// 全链未声明的键抛 <see cref="ShrinkUndeclaredAccessException"/>。
/// </summary>
public T Get<T>(string key)
{
var fiber = OwnerFiber;
while (fiber != null)
{
if (fiber.Committed != null && fiber.Committed.TryGetValue(key, out var binding))
return Runtime.ResolveAccess<T>(this, key, binding);
if (fiber.InjectSet.Contains(key))
throw new ShrinkInactiveAccessException(key, fiber.Name);
fiber = fiber.Parent;
}
throw new ShrinkUndeclaredAccessException(key);
}
public T Get<T>(ShrinkKey<T> key) => Get<T>(
(key ?? throw new ArgumentNullException(nameof(key))).Id);
/// <summary>与 <see cref="Get{T}"/> 相同的介导规则;未激活返回 false 而不抛异常。</summary>
public bool TryGet<T>(string key, out T value)
{
var fiber = OwnerFiber;
while (fiber != null)
{
if (fiber.Committed != null && fiber.Committed.TryGetValue(key, out var binding))
{
value = Runtime.ResolveAccess<T>(this, key, binding);
return true;
}
if (fiber.InjectSet.Contains(key))
break;
fiber = fiber.Parent;
}
value = default!;
return false;
}
public bool TryGet<T>(ShrinkKey<T> key, out T value) => TryGet(
(key ?? throw new ArgumentNullException(nameof(key))).Id, out value);
/// <summary>
/// 派生隔离子上下文(论文定义 28/29 的派生实现):
/// 覆盖 key 的 realm 解析;父上下文不受影响;丢弃子上下文即隐式恢复。
/// </summary>
public ShrinkCtx Isolate(string key, string realm)
{
if (string.IsNullOrEmpty(key))
throw new ArgumentException("Key must not be null or empty.", nameof(key));
if (string.IsNullOrEmpty(realm))
throw new ArgumentException("Realm must not be null or empty.", nameof(realm));
return new ShrinkCtx(Runtime, this, OwnerFiber, new Dictionary<string, string> { [key] = realm });
}
public ShrinkCtx Isolate<T>(ShrinkKey<T> key, string realm) => Isolate(
(key ?? throw new ArgumentNullException(nameof(key))).Id, realm);
/// <summary>
/// 派生带拦截元数据的子上下文。子级同名 metadata 覆盖父级;丢弃子上下文即恢复。
/// 该操作不改变 realm、provider 或 target,因此不会触发 fiber 重载。
/// </summary>
public ShrinkCtx Intercept(string key, IReadOnlyDictionary<string, object?> metadata)
{
if (string.IsNullOrWhiteSpace(key))
throw new ArgumentException("Key must not be null or empty.", nameof(key));
if (metadata == null)
throw new ArgumentNullException(nameof(metadata));
var metadataCopy = new Dictionary<string, object?>(StringComparer.Ordinal);
foreach (var pair in metadata)
metadataCopy[pair.Key] = pair.Value;
var overlay = new Dictionary<string, IReadOnlyDictionary<string, object?>>(StringComparer.Ordinal)
{
[key] = metadataCopy
};
return new ShrinkCtx(Runtime, this, OwnerFiber, null, overlay);
}
public ShrinkCtx Intercept<T>(ShrinkKey<T> key, IReadOnlyDictionary<string, object?> metadata) =>
Intercept((key ?? throw new ArgumentNullException(nameof(key))).Id, metadata);
internal ShrinkCtx CreateChildForFiber(ShrinkFiber fiber) => new(Runtime, this, fiber, null);
/// <summary>在派生子上下文中叠加隔离覆盖(同键后者优先),供加载器为条目配置隔离域。</summary>
internal ShrinkCtx WithIsolate(IReadOnlyDictionary<string, string> isolate)
{
var overlay = new Dictionary<string, string>();
if (_isolateOverlay != null)
{
foreach (var pair in _isolateOverlay)
overlay[pair.Key] = pair.Value;
}
foreach (var pair in isolate)
overlay[pair.Key] = pair.Value;
return new ShrinkCtx(Runtime, this, OwnerFiber, overlay);
}
/// <summary>在当前纤程上下文上叠加 loader 条目配置的 intercept。</summary>
internal ShrinkCtx WithIntercept(
IReadOnlyDictionary<string, IReadOnlyDictionary<string, object?>> intercept) =>
new(Runtime, this, OwnerFiber, null, interceptOverlay: intercept);
/// <summary>原地替换本层 intercept,供 loader 在不重载 fiber 的情况下更新访问策略。</summary>
internal void ReplaceIntercept(
IReadOnlyDictionary<string, IReadOnlyDictionary<string, object?>>? intercept)
{
if (intercept == null || intercept.Count == 0)
{
_interceptOverlay = null;
return;
}
var copy = new Dictionary<string, IReadOnlyDictionary<string, object?>>(StringComparer.Ordinal);
foreach (var pair in intercept)
{
if (string.IsNullOrWhiteSpace(pair.Key))
throw new ArgumentException("Intercept key must not be empty.", nameof(intercept));
if (pair.Value == null)
throw new ArgumentException($"Intercept metadata for '{pair.Key}' must not be null.",
nameof(intercept));
var metadataCopy = new Dictionary<string, object?>(StringComparer.Ordinal);
foreach (var metadataPair in pair.Value)
metadataCopy[metadataPair.Key] = metadataPair.Value;
copy[pair.Key] = metadataCopy;
}
_interceptOverlay = copy;
}
/// <summary>解析 key 在本上下文中归属的 realm:沿父链找最近的隔离覆盖,默认 realm 为 key 自身。</summary>
internal string ResolveRealm(string key)
{
var current = this;
while (current != null)
{
if (current._isolateOverlay != null && current._isolateOverlay.TryGetValue(key, out var realm))
return realm;
current = current.Parent;
}
return key;
}
/// <summary>按根到叶顺序合并拦截元数据;越接近访问方的上下文优先。</summary>
internal IReadOnlyDictionary<string, object?> ResolveInterceptMetadata(string key)
{
var chain = new Stack<ShrinkCtx>();
for (var current = this; current != null; current = current.Parent)
chain.Push(current);
var merged = new Dictionary<string, object?>(StringComparer.Ordinal);
while (chain.Count > 0)
{
var current = chain.Pop();
if (current._interceptOverlay == null ||
!current._interceptOverlay.TryGetValue(key, out var metadata))
continue;
foreach (var pair in metadata)
merged[pair.Key] = pair.Value;
}
return merged;
}
internal IReadOnlyCollection<string> GetLocalInterceptKeys() =>
_interceptOverlay?.Keys ?? (IReadOnlyCollection<string>)Array.Empty<string>();
}
}