#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using Cysharp.Threading.Tasks;
namespace ShrinkContext
{
///
/// Cordis 核心原型运行时:纤程注册表 + 共享余效应存储 + 惯性生命周期状态机
/// (论文算法 2-5 的 C#/UniTask 移植)。
///
/// 约定:
/// - 仅在主线程使用;
/// - 异步转换在同步可完成路径上内联跑完(无需 PlayerLoop 泵即可确定性测试);
/// - 目标(Target)按 inject 声明顺序记录 provider uid 视图,uid 永不复用,
/// 因此“同值不同提供者”的替换及多键 provider 映射变化都能触发重载。
///
public sealed class ShrinkContextRuntime
{
private readonly ShrinkCtx _rootCtx;
private readonly List _fibers = new();
private readonly Dictionary _store = new();
private readonly Dictionary> _injectIndex =
new(StringComparer.Ordinal);
private long _uidCounter;
private long _notificationDispatchCount;
private long _notificationCandidateVisitCount;
private int _lastNotificationCandidateCount;
public ShrinkContextRuntime()
{
_rootCtx = new ShrinkCtx(this, null, null, null);
}
public ShrinkCtx RootContext => _rootCtx;
public IReadOnlyList Fibers => _fibers;
// ---------------- 实例化(论文算法 4) ----------------
///
/// 实例化组件为纤程:在父上下文(默认根上下文)上注册一个可逆效应,
/// 前向 = 启动子纤程生命周期,逆 = 强制子纤程目标为 ⊥ 并卸载——父卸载自动级联子卸载。
/// isolate 为该纤程自身的键解析叠加隔离域(不影响父上下文)。
///
public ShrinkFiber Use(IShrinkComponent component, object? config = null, ShrinkCtx? parentCtx = null,
IReadOnlyDictionary? isolate = null,
IReadOnlyDictionary>? intercept = null)
{
if (component == null)
throw new ArgumentNullException(nameof(component));
var parent = parentCtx ?? _rootCtx;
var fiber = new ShrinkFiber(++_uidCounter, component, parent.OwnerFiber, config);
var fiberCtx = parent.CreateChildForFiber(fiber);
if (isolate != null && isolate.Count > 0)
fiberCtx = fiberCtx.WithIsolate(isolate);
if (intercept != null && intercept.Count > 0)
fiberCtx = fiberCtx.WithIntercept(intercept);
fiber.Ctx = fiberCtx;
_fibers.Add(fiber);
IndexFiber(fiber);
var instantiationHandle = new ShrinkEffectHandle();
instantiationHandle.AttachInverse(() => RetireCoreAsync(fiber));
parent.Attach(instantiationHandle);
Refresh(fiber);
return fiber;
}
///
/// 退役并卸载纤程(论文 O-Retire):目标强制 ⊥,等待纤程到达静止态。
/// 退役是永久的:之后不再参与依赖通知。
///
public async UniTask RetireAsync(ShrinkFiber fiber)
{
if (fiber.Retired)
return;
UnindexFiber(fiber);
if (fiber.State == ShrinkFiberState.Inactive && !fiber.InTransition)
{
fiber.Retired = true;
return;
}
fiber.Retired = true;
fiber.Target = null;
if (!fiber.InTransition)
{
fiber.InTransition = true;
fiber.Inertia = RunTransitionAsync(fiber);
}
if (fiber.InTransition)
await fiber.Inertia;
}
/// 卸载全部纤程并回滚根上下文效应。
public async UniTask ShutdownAsync()
{
foreach (var fiber in _fibers.ToArray())
await RetireAsync(fiber);
await _rootCtx.DisposeAsync();
}
// ---------------- 生命周期(论文算法 5) ----------------
///
/// 重算目标:任一依赖键不可解析 → ⊥;否则为按 inject 键顺序记录的 provider uid 视图。
/// 目标变化即启动(或在惯性边界链式触发)reload / unload 转换。
///
internal bool Refresh(ShrinkFiber fiber)
{
if (fiber.Retired)
return false;
var target = ComputeTarget(fiber);
if (TargetEquals(fiber.Target, target))
return false;
fiber.Target = target;
if (!fiber.InTransition)
{
fiber.InTransition = true;
fiber.Inertia = RunTransitionAsync(fiber);
}
return true;
}
private async UniTask RunTransitionAsync(ShrinkFiber fiber)
{
try
{
while (true)
{
if (fiber.Target is null)
{
// L-Leave:先停止供给(状态置 Unloading),再调度任何逆操作
fiber.State = ShrinkFiberState.Unloading;
await UnloadCoreAsync(fiber);
if (fiber.Target is null)
{
fiber.State = ShrinkFiberState.Inactive;
return;
}
// 卸载中途目标恢复 → 链式重载(惯性)
continue;
}
fiber.State = ShrinkFiberState.Loading;
var stable = await ReloadCoreAsync(fiber);
if (stable)
{
fiber.State = ShrinkFiberState.Active;
fiber.EverActive = true;
fiber.LastError = null;
Notify(fiber.Ctx, fiber.Component.Provide);
return;
}
// 装载中途目标变化(⊥ 或换提供者)→ 链式卸载(惯性)
}
}
finally
{
fiber.InTransition = false;
}
}
private async UniTask ReloadCoreAsync(ShrinkFiber fiber)
{
var target0 = fiber.Target!;
// 提交视图:装载期间(含自身卸载过程)读取同一份绑定(定理 63)
fiber._committed = ResolveView(fiber);
try
{
if (fiber.Component is IShrinkIterativeComponent iterative)
{
var stepper = iterative.ApplySteps(fiber.Ctx, fiber.Config);
// 守卫在每步步进边界检查目标稳定性(算法 1 的 guard 循环)
while (TargetEquals(fiber.Target, target0))
{
if (!await stepper.MoveNextAsync())
break;
var handle = new ShrinkEffectHandle();
handle.AttachInverse(stepper.Current);
fiber.Ctx.Attach(handle);
}
}
else
{
await fiber.Component.ApplyAsync(fiber.Ctx, fiber.Config);
}
}
catch (Exception ex)
{
// L-Raise:记录错误并强制走卸载路径,回收已执行的部分效应
fiber.LastError = ex;
fiber.Target = null;
return false;
}
return TargetEquals(fiber.Target, target0);
}
private async UniTask UnloadCoreAsync(ShrinkFiber fiber)
{
// 先通知依赖者并等待它们到达静止态(drain),再运行自身的逆操作(L-Unload 守卫)
var affected = Notify(fiber.Ctx, fiber.Component.Provide);
foreach (var dependent in affected)
{
if (dependent.InTransition)
await dependent.Inertia;
}
await fiber.Ctx.DisposeAsync();
// 卸载完成后才丢弃提交视图,保证自身拆除期间仍可读取依赖
fiber._committed = null;
}
// ---------------- 通知(论文算法 3) ----------------
///
/// 将键变更传播给声明了该键、且 realm 解析一致的依赖纤程;
/// 返回本次被触发刷新的纤程(供卸载方等待)。
///
internal IReadOnlyList Notify(ShrinkCtx sourceCtx, IReadOnlyCollection keys)
{
var affected = new List();
if (keys.Count == 0)
return affected;
var candidates = new HashSet();
foreach (var key in keys)
{
if (_injectIndex.TryGetValue(key, out var indexed))
candidates.UnionWith(indexed);
}
_notificationDispatchCount++;
_lastNotificationCandidateCount = candidates.Count;
_notificationCandidateVisitCount += candidates.Count;
foreach (var fiber in candidates.OrderBy(candidate => candidate.Uid))
{
if (fiber.Retired)
continue;
var realmMatches = false;
foreach (var key in keys)
{
if (!fiber.InjectSet.Contains(key))
continue;
if (fiber.Ctx.ResolveRealm(key) != sourceCtx.ResolveRealm(key))
continue;
realmMatches = true;
break;
}
if (realmMatches && Refresh(fiber))
affected.Add(fiber);
}
return affected;
}
// ---------------- 余效应存储(论文算法 2) ----------------
internal ShrinkEffectHandle SetBinding(ShrinkCtx ctx, string key, object? value,
IShrinkCoeffectAccessPolicy? accessPolicy)
{
if (string.IsNullOrWhiteSpace(key))
throw new ArgumentException("Key must not be null or empty.", nameof(key));
var realm = ctx.ResolveRealm(key);
var setter = ctx.OwnerFiber;
if (setter != null && !setter.ProvideSet.Contains(key))
throw new ShrinkUndeclaredSupplyException(key, setter.Name);
InstallBinding(realm, key, value, setter, ctx, accessPolicy);
var handle = new ShrinkEffectHandle();
handle.AttachInverse(() =>
{
RemoveBinding(realm, key, setter, ctx);
return UniTask.CompletedTask;
});
ctx.Attach(handle);
return handle;
}
private void InstallBinding(string realm, string key, object? value, ShrinkFiber? setter, ShrinkCtx ctx,
IShrinkCoeffectAccessPolicy? accessPolicy)
{
if (_store.TryGetValue(realm, out var existing) && !IsTakeoverAllowed(existing, setter))
throw new ShrinkSupplyConflictException(key, existing.Provider, setter);
_store[realm] = new ShrinkBinding(value, setter, key, accessPolicy);
Notify(ctx, new[] { key });
}
private void RemoveBinding(string realm, string key, ShrinkFiber? setter, ShrinkCtx ctx)
{
// 只撤回仍归属本设置者的绑定,不误删后继提供者
if (_store.TryGetValue(realm, out var current) && current.Provider == setter)
_store.Remove(realm);
Notify(ctx, new[] { key });
}
private static bool IsTakeoverAllowed(ShrinkBinding existing, ShrinkFiber? setter)
{
if (existing.Provider == null || existing.Provider == setter)
return true;
var state = existing.Provider.State;
return state == ShrinkFiberState.Unloading || state == ShrinkFiberState.Inactive;
}
/// 解析绑定:realm 不存在、或提供者非 Active(正在装载/卸载/失活)时视为不可用。
internal ShrinkBinding? ResolveBinding(ShrinkCtx ctx, string key)
{
var realm = ctx.ResolveRealm(key);
if (!_store.TryGetValue(realm, out var binding))
return null;
if (binding.Provider != null && binding.Provider.State != ShrinkFiberState.Active)
return null;
return binding;
}
/// 绕过介导规则的原始查找(编排层/测试用):active 提供者或无主绑定才命中。
public bool TryGetRaw(ShrinkCtx ctx, string key, out T value)
{
value = default!;
var binding = ResolveBinding(ctx, key);
if (binding == null)
return false;
value = (T)binding.Value!;
return true;
}
public bool TryGetRaw(ShrinkCtx ctx, ShrinkKey key, out T value) => TryGetRaw(
ctx, (key ?? throw new ArgumentNullException(nameof(key))).Id, out value);
/// 执行 provider 绑定的访问策略;无策略时直接返回原始值。
internal T ResolveAccess(ShrinkCtx ctx, string key, ShrinkBinding binding)
{
object? value = binding.Value;
if (binding.AccessPolicy != null)
{
var access = new ShrinkCoeffectAccessContext(
key,
typeof(T),
ctx.OwnerFiber,
ctx.ResolveInterceptMetadata(key));
value = binding.AccessPolicy.Resolve(access, value);
}
if (value is T typed)
return typed;
if (value == null && (!typeof(T).IsValueType || Nullable.GetUnderlyingType(typeof(T)) != null))
return default!;
throw new InvalidCastException(
$"Coeffect '{key}' resolved value of type '{value?.GetType().FullName ?? ""}' " +
$"but consumer requested '{typeof(T).FullName}'.");
}
// ---------------- 目标计算 ----------------
private long[]? ComputeTarget(ShrinkFiber fiber)
{
var uids = new List();
foreach (var key in fiber.Component.Inject)
{
var binding = ResolveBinding(fiber.Ctx, key);
if (binding == null)
return null;
uids.Add(binding.Provider?.Uid ?? 0);
}
// target 是 key -> provider uid 的视图,不是 uid 集合。保留 inject 声明顺序与重复 uid,
// 才能观察两个键在相同提供者集合之间互换的变化。
return uids.ToArray();
}
private Dictionary ResolveView(ShrinkFiber fiber)
{
var view = new Dictionary();
foreach (var key in fiber.Component.Inject)
{
var binding = ResolveBinding(fiber.Ctx, key);
if (binding != null)
view[key] = binding;
}
return view;
}
private static bool TargetEquals(long[]? a, long[]? b)
{
if (a == null || b == null)
return a == null && b == null;
if (a.Length != b.Length)
return false;
for (var i = 0; i < a.Length; i++)
{
if (a[i] != b[i])
return false;
}
return true;
}
private async UniTask RetireCoreAsync(ShrinkFiber fiber)
{
if (fiber.Retired)
return;
await RetireAsync(fiber);
}
private void IndexFiber(ShrinkFiber fiber)
{
foreach (var key in fiber.InjectSet)
{
if (!_injectIndex.TryGetValue(key, out var fibers))
{
fibers = new HashSet();
_injectIndex.Add(key, fibers);
}
fibers.Add(fiber);
}
}
private void UnindexFiber(ShrinkFiber fiber)
{
foreach (var key in fiber.InjectSet)
{
if (!_injectIndex.TryGetValue(key, out var fibers))
continue;
fibers.Remove(fiber);
if (fibers.Count == 0)
_injectIndex.Remove(key);
}
}
/// 捕获不含可变运行时引用的诊断快照。
public ShrinkContextRuntimeDiagnostic CaptureDiagnostic()
{
var diagnostics = new List(_fibers.Count);
foreach (var fiber in _fibers.OrderBy(item => item.Uid))
{
var dependencies = new List();
foreach (var key in fiber.Component.Inject ?? Array.Empty())
{
var realm = fiber.Ctx.ResolveRealm(key);
var current = ResolveBinding(fiber.Ctx, key);
ShrinkBinding? committed = null;
fiber.Committed?.TryGetValue(key, out committed);
var potential = _fibers
.Where(candidate => !candidate.Retired && candidate.ProvideSet.Contains(key) &&
candidate.Ctx.ResolveRealm(key) == realm)
.Select(candidate => candidate.Uid)
.OrderBy(uid => uid)
.ToArray();
dependencies.Add(new ShrinkDependencyDiagnostic(
key,
realm,
current?.Provider?.Uid,
committed?.Provider?.Uid,
potential));
}
diagnostics.Add(new ShrinkFiberDiagnostic(
fiber.Uid,
fiber.Name,
GetDiagnosticStatus(fiber),
fiber.State,
fiber.Retired,
fiber.InTransition,
(fiber.Component.Inject ?? Array.Empty()).ToArray(),
(fiber.Component.Provide ?? Array.Empty()).ToArray(),
fiber.Target?.ToArray() ?? Array.Empty(),
dependencies,
fiber.Ctx.GetLocalInterceptKeys().OrderBy(key => key, StringComparer.Ordinal).ToArray(),
fiber.LastError?.GetType().FullName,
fiber.LastError?.Message));
}
return new ShrinkContextRuntimeDiagnostic(
diagnostics,
_store.Count,
new ShrinkNotificationDiagnostic(
_notificationDispatchCount,
_notificationCandidateVisitCount,
_lastNotificationCandidateCount,
_injectIndex.Count));
}
private static ShrinkFiberDiagnosticStatus GetDiagnosticStatus(ShrinkFiber fiber)
{
if (fiber.Retired)
return ShrinkFiberDiagnosticStatus.Retired;
if (fiber.LastError != null)
return ShrinkFiberDiagnosticStatus.Failed;
return fiber.State switch
{
ShrinkFiberState.Loading => ShrinkFiberDiagnosticStatus.Loading,
ShrinkFiberState.Active => ShrinkFiberDiagnosticStatus.Active,
ShrinkFiberState.Unloading => ShrinkFiberDiagnosticStatus.Unloading,
ShrinkFiberState.Inactive when fiber.InjectSet.Count > 0 => ShrinkFiberDiagnosticStatus.Waiting,
_ => ShrinkFiberDiagnosticStatus.Inactive,
};
}
}
}