#nullable enable using System; using System.Collections.Generic; using System.Linq; using Cysharp.Threading.Tasks; namespace ShrinkContext { /// 声明式配置条目(论文定义 74 的原型子集): public sealed class ShrinkLoaderEntry { public ShrinkLoaderEntry(string id, string component, object? config = null, bool disabled = false, IReadOnlyDictionary? isolate = null, IReadOnlyDictionary>? intercept = null) { if (string.IsNullOrEmpty(id)) throw new ArgumentException("Entry id must not be null or empty.", nameof(id)); if (string.IsNullOrEmpty(component)) throw new ArgumentException("Entry component must not be null or empty.", nameof(component)); Id = id; Component = component; Config = config; Disabled = disabled; Isolate = isolate; Intercept = intercept; } /// 稳定协调键:条目增删与更新的 diff 依据。 public string Id { get; } /// 组件目录名(论文条目的 url 字段)。 public string Component { get; } /// 绑定给组件 apply 的配置。 public object? Config { get; } /// 管理性关闭:true 时卸载对应纤程。 public bool Disabled { get; } /// 条目级隔离域:键 → realm,叠加到该纤程自身的解析。 public IReadOnlyDictionary? Isolate { get; } /// 条目级访问元数据:依赖键 → metadata。更新时不改变 target,也不重载 fiber。 public IReadOnlyDictionary>? Intercept { get; } } /// 组件目录:条目的组件名 → 组件工厂。装配期由宿主/CodeGen 注册,加载器不做全域反射扫描。 public sealed class ShrinkComponentCatalog { private readonly Dictionary> _factories = new(StringComparer.Ordinal); public void Register(string name, Func factory) { if (string.IsNullOrEmpty(name)) throw new ArgumentException("Component name must not be null or empty.", nameof(name)); _factories[name] = factory ?? throw new ArgumentNullException(nameof(factory)); } public void Register(string name) where TComponent : IShrinkComponent, new() { Register(name, static () => new TComponent()); } public bool TryCreate(string name, out IShrinkComponent component) { if (_factories.TryGetValue(name, out var factory)) { component = factory(); return component != null; } component = null!; return false; } public bool Contains(string name) => _factories.ContainsKey(name); } /// 加载器配置错误(重复条目 id、未知组件名等)。 public class ShrinkLoaderException : Exception { public ShrinkLoaderException(string message) : base(message) { } } public enum ShrinkLoaderTransactionPhase { Idle = 0, Validating = 1, Removing = 2, Applying = 3, Restoring = 4, Completed = 5, Failed = 6, } /// 最近一次声明式协调的稳定诊断结果。 public sealed class ShrinkLoaderTransactionDiagnostic { internal ShrinkLoaderTransactionDiagnostic(long generation) { Generation = generation; } public long Generation { get; } public ShrinkLoaderTransactionPhase Phase { get; internal set; } public string? CurrentEntryId { get; internal set; } public string? ErrorType { get; internal set; } public string? ErrorMessage { get; internal set; } public bool RestoreAttempted { get; internal set; } public bool PreviousCompositionRestored { get; internal set; } public string? RestoreErrorType { get; internal set; } public string? RestoreErrorMessage { get; internal set; } } public sealed class ShrinkLoaderRestoreException : ShrinkLoaderException { public ShrinkLoaderRestoreException(Exception applyError, Exception restoreError) : base("Loader apply failed and restoring the previous composition also failed.") { ApplyError = applyError; RestoreError = restoreError; } public Exception ApplyError { get; } public Exception RestoreError { get; } } /// /// 声明式组件加载器(论文 5.2 的原型子集): /// 编排者把期望组合表达为条目列表,加载器将其增量协调为纤程操作—— /// 条目消失 → 退役;disabled → 卸载;组件/配置变化 → 重建;其余保持不动(幂等)。 /// /// 与完整实现的差异:config 变化当前统一走重建而非组件自决 diff; /// isolate 变化仍走重建而非领域原地重分配。intercept metadata 可原地更新,不触发重载。 /// public sealed class ShrinkContextLoader { private sealed class ManagedEntry { public string Component = string.Empty; public object? Config; public bool Disabled; public IReadOnlyDictionary? Isolate; public IReadOnlyDictionary>? Intercept; public ShrinkFiber? Fiber; } private readonly ShrinkContextRuntime _runtime; private readonly ShrinkComponentCatalog _catalog; private readonly Dictionary _entries = new(StringComparer.Ordinal); private long _transactionGeneration; private bool _applying; public ShrinkContextLoader(ShrinkContextRuntime runtime, ShrinkComponentCatalog catalog) { _runtime = runtime ?? throw new ArgumentNullException(nameof(runtime)); _catalog = catalog ?? throw new ArgumentNullException(nameof(catalog)); } /// 当前托管条目 id(含 disabled 但仍被管理的条目)。 public IEnumerable ManagedEntryIds => _entries.Keys; public ShrinkLoaderTransactionDiagnostic? LastTransaction { get; private set; } /// 查询条目当前纤程(可能处于 Inactive——依赖缺失时被响应式停用但仍被管理)。 public bool TryGetFiber(string entryId, out ShrinkFiber fiber) { if (_entries.TryGetValue(entryId, out var managed) && managed.Fiber != null) { fiber = managed.Fiber; return true; } fiber = null!; return false; } /// /// 增量应用期望配置:不整体重建,只对差异部分执行最小破坏性操作。 /// 协调顺序确定性:先移除消失条目,再按声明顺序处理其余条目。 /// public async UniTask ApplyAsync(IReadOnlyList desired) { if (desired == null) throw new ArgumentNullException(nameof(desired)); if (_applying) throw new InvalidOperationException("A loader composition transaction is already running."); var previous = CaptureEntries(); var transaction = new ShrinkLoaderTransactionDiagnostic(++_transactionGeneration); LastTransaction = transaction; _applying = true; try { await ApplyCoreAsync(desired, transaction, restoring: false); transaction.Phase = ShrinkLoaderTransactionPhase.Completed; transaction.CurrentEntryId = null; } catch (Exception applyError) { transaction.ErrorType = applyError.GetType().FullName; transaction.ErrorMessage = applyError.Message; transaction.RestoreAttempted = true; transaction.Phase = ShrinkLoaderTransactionPhase.Restoring; transaction.CurrentEntryId = null; try { await ApplyCoreAsync(previous, transaction, restoring: true); transaction.PreviousCompositionRestored = true; } catch (Exception restoreError) { transaction.RestoreErrorType = restoreError.GetType().FullName; transaction.RestoreErrorMessage = restoreError.Message; transaction.Phase = ShrinkLoaderTransactionPhase.Failed; transaction.CurrentEntryId = null; throw new ShrinkLoaderRestoreException(applyError, restoreError); } transaction.Phase = ShrinkLoaderTransactionPhase.Failed; transaction.CurrentEntryId = null; throw; } finally { _applying = false; } } private async UniTask ApplyCoreAsync(IReadOnlyList desired, ShrinkLoaderTransactionDiagnostic transaction, bool restoring) { transaction.Phase = restoring ? ShrinkLoaderTransactionPhase.Restoring : ShrinkLoaderTransactionPhase.Validating; var desiredIds = new HashSet(StringComparer.Ordinal); foreach (var entry in desired) { if (!desiredIds.Add(entry.Id)) throw new ShrinkLoaderException($"Duplicate loader entry id: '{entry.Id}'."); if (!entry.Disabled && !_catalog.Contains(entry.Component)) throw new ShrinkLoaderException( $"Unknown component '{entry.Component}' requested by entry '{entry.Id}'."); } // 1) 消失的条目退役(其依赖者由响应式通知自动停用,条目本身仍被管理) if (!restoring) transaction.Phase = ShrinkLoaderTransactionPhase.Removing; var removedIds = new List(); foreach (var id in _entries.Keys) { if (!desiredIds.Contains(id)) removedIds.Add(id); } foreach (var id in removedIds) { transaction.CurrentEntryId = id; var managed = _entries[id]; if (managed.Fiber != null) await _runtime.RetireAsync(managed.Fiber); _entries.Remove(id); } // 2) 按声明顺序逐条分派 if (!restoring) transaction.Phase = ShrinkLoaderTransactionPhase.Applying; foreach (var entry in desired) { transaction.CurrentEntryId = entry.Id; _entries.TryGetValue(entry.Id, out var managed); if (entry.Disabled) { if (managed == null) { _entries[entry.Id] = new ManagedEntry { Component = entry.Component, Config = entry.Config, Disabled = true, Isolate = entry.Isolate, Intercept = entry.Intercept }; } else { if (managed.Fiber != null) await _runtime.RetireAsync(managed.Fiber); managed.Fiber = null; managed.Disabled = true; managed.Component = entry.Component; managed.Config = entry.Config; managed.Isolate = entry.Isolate; managed.Intercept = entry.Intercept; } continue; } if (managed != null && !managed.Disabled && managed.Fiber != null && managed.Component == entry.Component && Equals(managed.Config, entry.Config) && IsolateEquals(managed.Isolate, entry.Isolate)) { if (!InterceptEquals(managed.Intercept, entry.Intercept)) { managed.Fiber.Ctx.ReplaceIntercept(entry.Intercept); managed.Intercept = entry.Intercept; } continue; // 幂等:无变化不打扰(被响应式停用的纤程保持管理,等待依赖回归) } if (managed?.Fiber != null) { await _runtime.RetireAsync(managed.Fiber); managed.Fiber = null; } if (!_catalog.TryCreate(entry.Component, out var component)) throw new ShrinkLoaderException($"Component factory '{entry.Component}' returned no component."); var fiber = _runtime.Use(component, entry.Config, _runtime.RootContext, entry.Isolate, entry.Intercept); if (fiber.LastError != null) { var failure = fiber.LastError; await _runtime.RetireAsync(fiber); throw new ShrinkLoaderException( $"Component '{entry.Component}' for entry '{entry.Id}' failed during apply: {failure.Message}"); } _entries[entry.Id] = new ManagedEntry { Component = entry.Component, Config = entry.Config, Disabled = false, Isolate = entry.Isolate, Intercept = entry.Intercept, Fiber = fiber }; } } private IReadOnlyList CaptureEntries() { var entries = new List(_entries.Count); foreach (var pair in _entries.OrderBy(item => item.Key, StringComparer.Ordinal)) { var managed = pair.Value; entries.Add(new ShrinkLoaderEntry( pair.Key, managed.Component, managed.Config, managed.Disabled, managed.Isolate, managed.Intercept)); } return entries; } private static bool IsolateEquals(IReadOnlyDictionary? a, IReadOnlyDictionary? b) { if (a == null && b == null) return true; if (a == null || b == null) return false; if (a.Count != b.Count) return false; foreach (var pair in a) { if (!b.TryGetValue(pair.Key, out var value) || value != pair.Value) return false; } return true; } private static bool InterceptEquals( IReadOnlyDictionary>? a, IReadOnlyDictionary>? b) { if (a == null && b == null) return true; if (a == null || b == null || a.Count != b.Count) return false; foreach (var keyPair in a) { if (!b.TryGetValue(keyPair.Key, out var otherMetadata) || keyPair.Value.Count != otherMetadata.Count) return false; foreach (var metadataPair in keyPair.Value) { if (!otherMetadata.TryGetValue(metadataPair.Key, out var otherValue) || !Equals(metadataPair.Value, otherValue)) return false; } } return true; } } }