#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, }; } } }