feat(cordis): 接入上下文组合与模组事务热替换

This commit is contained in:
2026-08-16 23:20:40 +08:00
commit ad256f109b
676 changed files with 52168 additions and 0 deletions
@@ -0,0 +1,232 @@
#nullable enable
using System;
using System.Collections.Generic;
using Cysharp.Threading.Tasks;
namespace ShrinkContext
{
/// <summary>声明式配置条目(论文定义 74 的原型子集):</summary>
public sealed class ShrinkLoaderEntry
{
public ShrinkLoaderEntry(string id, string component, object? config = null, bool disabled = false,
IReadOnlyDictionary<string, string>? isolate = 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;
}
/// <summary>稳定协调键:条目增删与更新的 diff 依据。</summary>
public string Id { get; }
/// <summary>组件目录名(论文条目的 url 字段)。</summary>
public string Component { get; }
/// <summary>绑定给组件 apply 的配置。</summary>
public object? Config { get; }
/// <summary>管理性关闭:true 时卸载对应纤程。</summary>
public bool Disabled { get; }
/// <summary>条目级隔离域:键 → realm,叠加到该纤程自身的解析。</summary>
public IReadOnlyDictionary<string, string>? Isolate { get; }
}
/// <summary>组件目录:条目的组件名 → 组件工厂。装配期由宿主/CodeGen 注册,加载器不做全域反射扫描。</summary>
public sealed class ShrinkComponentCatalog
{
private readonly Dictionary<string, Func<IShrinkComponent>> _factories =
new(StringComparer.Ordinal);
public void Register(string name, Func<IShrinkComponent> 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<TComponent>(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;
}
}
/// <summary>加载器配置错误(重复条目 id、未知组件名等)。</summary>
public sealed class ShrinkLoaderException : Exception
{
public ShrinkLoaderException(string message) : base(message)
{
}
}
/// <summary>
/// 声明式组件加载器(论文 5.2 的原型子集):
/// 编排者把期望组合表达为条目列表,加载器将其增量协调为纤程操作——
/// 条目消失 → 退役;disabled → 卸载;组件/配置变化 → 重建;其余保持不动(幂等)。
///
/// 与完整实现的差异(后续阶段补齐):config 变化当前统一走重建而非组件自决 diff;
/// isolate 变化走重建而非领域原地重分配;intercept 尚未支持。
/// </summary>
public sealed class ShrinkContextLoader
{
private sealed class ManagedEntry
{
public string Component = string.Empty;
public object? Config;
public bool Disabled;
public IReadOnlyDictionary<string, string>? Isolate;
public ShrinkFiber? Fiber;
}
private readonly ShrinkContextRuntime _runtime;
private readonly ShrinkComponentCatalog _catalog;
private readonly Dictionary<string, ManagedEntry> _entries = new(StringComparer.Ordinal);
public ShrinkContextLoader(ShrinkContextRuntime runtime, ShrinkComponentCatalog catalog)
{
_runtime = runtime ?? throw new ArgumentNullException(nameof(runtime));
_catalog = catalog ?? throw new ArgumentNullException(nameof(catalog));
}
/// <summary>当前托管条目 id(含 disabled 但仍被管理的条目)。</summary>
public IEnumerable<string> ManagedEntryIds => _entries.Keys;
/// <summary>查询条目当前纤程(可能处于 Inactive——依赖缺失时被响应式停用但仍被管理)。</summary>
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;
}
/// <summary>
/// 增量应用期望配置:不整体重建,只对差异部分执行最小破坏性操作。
/// 协调顺序确定性:先移除消失条目,再按声明顺序处理其余条目。
/// </summary>
public async UniTask ApplyAsync(IReadOnlyList<ShrinkLoaderEntry> desired)
{
if (desired == null)
throw new ArgumentNullException(nameof(desired));
var desiredIds = new HashSet<string>(StringComparer.Ordinal);
foreach (var entry in desired)
{
if (!desiredIds.Add(entry.Id))
throw new ShrinkLoaderException($"Duplicate loader entry id: '{entry.Id}'.");
}
// 1) 消失的条目退役(其依赖者由响应式通知自动停用,条目本身仍被管理)
var removedIds = new List<string>();
foreach (var id in _entries.Keys)
{
if (!desiredIds.Contains(id))
removedIds.Add(id);
}
foreach (var id in removedIds)
{
var managed = _entries[id];
if (managed.Fiber != null)
await _runtime.RetireAsync(managed.Fiber);
_entries.Remove(id);
}
// 2) 按声明顺序逐条分派
foreach (var entry in desired)
{
_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
};
}
else
{
if (managed.Fiber != null)
await _runtime.RetireAsync(managed.Fiber);
managed.Fiber = null;
managed.Disabled = true;
}
continue;
}
if (managed != null && !managed.Disabled && managed.Fiber != null &&
managed.Component == entry.Component &&
Equals(managed.Config, entry.Config) &&
IsolateEquals(managed.Isolate, entry.Isolate))
{
continue; // 幂等:无变化不打扰(被响应式停用的纤程保持管理,等待依赖回归)
}
if (managed?.Fiber != null)
await _runtime.RetireAsync(managed.Fiber);
if (!_catalog.TryCreate(entry.Component, out var component))
throw new ShrinkLoaderException(
$"Unknown component '{entry.Component}' requested by entry '{entry.Id}'.");
var fiber = _runtime.Use(component, entry.Config, _runtime.RootContext, entry.Isolate);
_entries[entry.Id] = new ManagedEntry
{
Component = entry.Component,
Config = entry.Config,
Disabled = false,
Isolate = entry.Isolate,
Fiber = fiber
};
}
}
private static bool IsolateEquals(IReadOnlyDictionary<string, string>? a, IReadOnlyDictionary<string, string>? 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;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c236b8aa6fec940449f9303b855a3989
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: