366 lines
13 KiB
C#
366 lines
13 KiB
C#
#nullable enable
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using Cysharp.Threading.Tasks;
|
||
|
||
namespace ShrinkContext
|
||
{
|
||
/// <summary>
|
||
/// Cordis 核心原型运行时:纤程注册表 + 共享余效应存储 + 惯性生命周期状态机
|
||
/// (论文算法 2-5 的 C#/UniTask 移植)。
|
||
///
|
||
/// 约定:
|
||
/// - 仅在主线程使用;
|
||
/// - 异步转换在同步可完成路径上内联跑完(无需 PlayerLoop 泵即可确定性测试);
|
||
/// - 目标(Target)以提供者 uid 集合表示,uid 永不复用,因此“同值不同提供者”的替换也会触发重载。
|
||
/// </summary>
|
||
public sealed class ShrinkContextRuntime
|
||
{
|
||
private readonly ShrinkCtx _rootCtx;
|
||
private readonly List<ShrinkFiber> _fibers = new();
|
||
private readonly Dictionary<string, ShrinkBinding> _store = new();
|
||
private long _uidCounter;
|
||
|
||
public ShrinkContextRuntime()
|
||
{
|
||
_rootCtx = new ShrinkCtx(this, null, null, null);
|
||
}
|
||
|
||
public ShrinkCtx RootContext => _rootCtx;
|
||
|
||
public IReadOnlyList<ShrinkFiber> Fibers => _fibers;
|
||
|
||
// ---------------- 实例化(论文算法 4) ----------------
|
||
|
||
/// <summary>
|
||
/// 实例化组件为纤程:在父上下文(默认根上下文)上注册一个可逆效应,
|
||
/// 前向 = 启动子纤程生命周期,逆 = 强制子纤程目标为 ⊥ 并卸载——父卸载自动级联子卸载。
|
||
/// isolate 为该纤程自身的键解析叠加隔离域(不影响父上下文)。
|
||
/// </summary>
|
||
public ShrinkFiber Use(IShrinkComponent component, object? config = null, ShrinkCtx? parentCtx = null,
|
||
IReadOnlyDictionary<string, string>? isolate = 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);
|
||
fiber.Ctx = fiberCtx;
|
||
_fibers.Add(fiber);
|
||
|
||
var instantiationHandle = new ShrinkEffectHandle();
|
||
instantiationHandle.AttachInverse(() => RetireCoreAsync(fiber));
|
||
parent.Attach(instantiationHandle);
|
||
|
||
Refresh(fiber);
|
||
return fiber;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 退役并卸载纤程(论文 O-Retire):目标强制 ⊥,等待纤程到达静止态。
|
||
/// 退役是永久的:之后不再参与依赖通知。
|
||
/// </summary>
|
||
public async UniTask RetireAsync(ShrinkFiber fiber)
|
||
{
|
||
if (fiber.Retired)
|
||
return;
|
||
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;
|
||
}
|
||
|
||
/// <summary>卸载全部纤程并回滚根上下文效应。</summary>
|
||
public async UniTask ShutdownAsync()
|
||
{
|
||
foreach (var fiber in _fibers.ToArray())
|
||
await RetireAsync(fiber);
|
||
await _rootCtx.DisposeAsync();
|
||
}
|
||
|
||
// ---------------- 生命周期(论文算法 5) ----------------
|
||
|
||
/// <summary>
|
||
/// 重算目标:任一依赖键不可解析 → ⊥;否则为全部提供者 uid 的有序集合。
|
||
/// 目标变化即启动(或在惯性边界链式触发)reload / unload 转换。
|
||
/// </summary>
|
||
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<bool> 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) ----------------
|
||
|
||
/// <summary>
|
||
/// 将键变更传播给声明了该键、且 realm 解析一致的依赖纤程;
|
||
/// 返回本次被触发刷新的纤程(供卸载方等待)。
|
||
/// </summary>
|
||
internal IReadOnlyList<ShrinkFiber> Notify(ShrinkCtx sourceCtx, IReadOnlyCollection<string> keys)
|
||
{
|
||
var affected = new List<ShrinkFiber>();
|
||
foreach (var fiber in _fibers.ToArray())
|
||
{
|
||
foreach (var key in keys)
|
||
{
|
||
if (!fiber.InjectSet.Contains(key))
|
||
continue;
|
||
if (fiber.Ctx.ResolveRealm(key) != sourceCtx.ResolveRealm(key))
|
||
continue;
|
||
|
||
if (Refresh(fiber))
|
||
affected.Add(fiber);
|
||
break;
|
||
}
|
||
}
|
||
|
||
return affected;
|
||
}
|
||
|
||
// ---------------- 余效应存储(论文算法 2) ----------------
|
||
|
||
internal ShrinkEffectHandle SetBinding(ShrinkCtx ctx, string key, object? value)
|
||
{
|
||
var realm = ctx.ResolveRealm(key);
|
||
var setter = ctx.OwnerFiber;
|
||
|
||
InstallBinding(realm, key, value, setter, ctx);
|
||
|
||
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)
|
||
{
|
||
if (_store.TryGetValue(realm, out var existing) && !IsTakeoverAllowed(existing, setter))
|
||
throw new ShrinkSupplyConflictException(key, existing.Provider, setter);
|
||
|
||
_store[realm] = new ShrinkBinding(value, setter, key);
|
||
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;
|
||
}
|
||
|
||
/// <summary>解析绑定:realm 不存在、或提供者非 Active(正在装载/卸载/失活)时视为不可用。</summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>绕过介导规则的原始查找(编排层/测试用):active 提供者或无主绑定才命中。</summary>
|
||
public bool TryGetRaw<T>(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;
|
||
}
|
||
|
||
// ---------------- 目标计算 ----------------
|
||
|
||
private long[]? ComputeTarget(ShrinkFiber fiber)
|
||
{
|
||
var uids = new List<long>();
|
||
foreach (var key in fiber.Component.Inject)
|
||
{
|
||
var binding = ResolveBinding(fiber.Ctx, key);
|
||
if (binding == null)
|
||
return null;
|
||
uids.Add(binding.Provider?.Uid ?? 0);
|
||
}
|
||
|
||
return uids.Distinct().OrderBy(uid => uid).ToArray();
|
||
}
|
||
|
||
private Dictionary<string, ShrinkBinding> ResolveView(ShrinkFiber fiber)
|
||
{
|
||
var view = new Dictionary<string, ShrinkBinding>();
|
||
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);
|
||
}
|
||
}
|
||
}
|