This commit is contained in:
@@ -0,0 +1,300 @@
|
||||
#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>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3c4e5dd3f658ca14a929dd41f2466927
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,61 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using Cysharp.Threading.Tasks;
|
||||
|
||||
namespace ShrinkContext
|
||||
{
|
||||
/// <summary>
|
||||
/// 单个可逆效应的句柄(论文算法 1 的 effect 封装)。
|
||||
/// 前向执行时产出逆操作并累积在句柄内部;DisposeAsync 至多执行一次(armed 语义),
|
||||
/// 若前向仍在进行中则先等待其完成再运行累积的逆操作(dispose = await task; recover())。
|
||||
/// </summary>
|
||||
public sealed class ShrinkEffectHandle
|
||||
{
|
||||
private static readonly Func<UniTask> Nop = static () => UniTask.CompletedTask;
|
||||
|
||||
private bool _armed = true;
|
||||
private UniTask _forwardTask;
|
||||
private bool _hasForwardTask;
|
||||
private Func<UniTask> _inverseChain = Nop;
|
||||
|
||||
public bool IsArmed => _armed;
|
||||
|
||||
/// <summary>登记一个进行中的异步前向;DisposeAsync 会先等待它完成。</summary>
|
||||
internal void BeginForward(UniTask forwardTask)
|
||||
{
|
||||
_forwardTask = forwardTask;
|
||||
_hasForwardTask = true;
|
||||
}
|
||||
|
||||
/// <summary>前向完成后登记其逆操作;新逆操作在链首执行(LIFO)。</summary>
|
||||
internal void CompleteForward(Func<UniTask> inverse)
|
||||
{
|
||||
if (inverse == null)
|
||||
return;
|
||||
|
||||
var previous = _inverseChain;
|
||||
_inverseChain = async () =>
|
||||
{
|
||||
await inverse();
|
||||
await previous();
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>直接附加逆操作(等效于前向已同步完成)。</summary>
|
||||
internal void AttachInverse(Func<UniTask> inverse) => CompleteForward(inverse);
|
||||
|
||||
public async UniTask DisposeAsync()
|
||||
{
|
||||
if (!_armed)
|
||||
return;
|
||||
|
||||
_armed = false;
|
||||
if (_hasForwardTask && _forwardTask.Status == UniTaskStatus.Pending)
|
||||
await _forwardTask;
|
||||
|
||||
var chain = _inverseChain;
|
||||
_inverseChain = Nop;
|
||||
await chain();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6baa51f655b3c9a4d9dc0b1dbdce8461
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user