feat(cordis): 接入上下文组合与模组事务热替换
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 40e4fc95f69522e46b43fbb4dade4bb9
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,57 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
|
||||
namespace ShrinkContext
|
||||
{
|
||||
/// <summary>共享存储中的一条依赖绑定:键解析到 realm 后对应的值与提供者纤程。</summary>
|
||||
public sealed class ShrinkBinding
|
||||
{
|
||||
public ShrinkBinding(object? value, ShrinkFiber? provider, string key)
|
||||
{
|
||||
Value = value;
|
||||
Provider = provider;
|
||||
Key = key;
|
||||
}
|
||||
|
||||
public object? Value { get; }
|
||||
public ShrinkFiber? Provider { get; }
|
||||
public string Key { get; }
|
||||
}
|
||||
|
||||
/// <summary>访问了纤程链上没有任何一方声明的依赖键(论文算法 6 的 UNDECLARED_ACCESS)。</summary>
|
||||
public sealed class ShrinkUndeclaredAccessException : Exception
|
||||
{
|
||||
public ShrinkUndeclaredAccessException(string key)
|
||||
: base($"Undeclared coeffect access: '{key}'. Declare it in the component's Inject list.")
|
||||
{
|
||||
Key = key;
|
||||
}
|
||||
|
||||
public string Key { get; }
|
||||
}
|
||||
|
||||
/// <summary>纤程声明了依赖键但尚未激活就访问(论文算法 6 的 INACTIVE_ACCESS)。</summary>
|
||||
public sealed class ShrinkInactiveAccessException : Exception
|
||||
{
|
||||
public ShrinkInactiveAccessException(string key, string fiberName)
|
||||
: base($"Inactive coeffect access: '{key}' declared by fiber '{fiberName}' but not committed yet.")
|
||||
{
|
||||
Key = key;
|
||||
}
|
||||
|
||||
public string Key { get; }
|
||||
}
|
||||
|
||||
/// <summary>两个活跃纤程试图供给同一个 realm(论文定义 45 的供给不相交约束)。</summary>
|
||||
public sealed class ShrinkSupplyConflictException : Exception
|
||||
{
|
||||
public ShrinkSupplyConflictException(string key, ShrinkFiber? existing, ShrinkFiber? setter)
|
||||
: base($"Supply conflict on key '{key}': existing provider " +
|
||||
$"'{existing?.Name ?? "<ambient>"}' conflicts with '{setter?.Name ?? "<ambient>"}'.")
|
||||
{
|
||||
Key = key;
|
||||
}
|
||||
|
||||
public string Key { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2dcad9bdadb1607498dc233cfc1b5a06
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4906e85b1734b944ca870fa011dd3a98
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,197 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Cysharp.Threading.Tasks;
|
||||
|
||||
namespace ShrinkContext
|
||||
{
|
||||
/// <summary>
|
||||
/// 一等上下文(论文 Γ∞ 的运行时对应物)。
|
||||
/// - 树结构:由 <see cref="Isolate"/> 派生隔离子上下文,或由运行时为纤程派生子上下文;
|
||||
/// - 效应:本上下文注册的可逆效应按注册逆序(LIFO)在 <see cref="DisposeAsync"/> 中回滚;
|
||||
/// - 余效应:Set/Get 委托给运行时共享存储,键先经隔离表解析为 realm 再查表。
|
||||
/// </summary>
|
||||
public sealed class ShrinkCtx
|
||||
{
|
||||
private readonly List<ShrinkEffectHandle> _effects = new();
|
||||
private readonly Dictionary<string, string>? _isolateOverlay;
|
||||
|
||||
internal ShrinkCtx(ShrinkContextRuntime runtime, ShrinkCtx? parent, ShrinkFiber? ownerFiber,
|
||||
Dictionary<string, string>? isolateOverlay)
|
||||
{
|
||||
Runtime = runtime;
|
||||
Parent = parent;
|
||||
OwnerFiber = ownerFiber;
|
||||
_isolateOverlay = isolateOverlay;
|
||||
}
|
||||
|
||||
public ShrinkContextRuntime Runtime { get; }
|
||||
public ShrinkCtx? Parent { get; }
|
||||
public ShrinkFiber? OwnerFiber { get; }
|
||||
|
||||
// ---------------- 效应(论文 3.1 / 算法 1) ----------------
|
||||
|
||||
/// <summary>同步前向 + 可选同步逆操作的可逆效应;前向立即执行。</summary>
|
||||
public ShrinkEffectHandle Effect(Action forward, Action? inverse = null)
|
||||
{
|
||||
if (forward == null)
|
||||
throw new ArgumentNullException(nameof(forward));
|
||||
|
||||
forward();
|
||||
|
||||
var handle = new ShrinkEffectHandle();
|
||||
if (inverse != null)
|
||||
handle.AttachInverse(() =>
|
||||
{
|
||||
inverse();
|
||||
return UniTask.CompletedTask;
|
||||
});
|
||||
Attach(handle);
|
||||
return handle;
|
||||
}
|
||||
|
||||
/// <summary>异步前向,完成时产出异步逆操作(论文 𝔈Γ)。DisposeAsync 会等待进行中的前向。</summary>
|
||||
public async UniTask<ShrinkEffectHandle> EffectAsync(Func<UniTask<Func<UniTask>>> effectFn)
|
||||
{
|
||||
if (effectFn == null)
|
||||
throw new ArgumentNullException(nameof(effectFn));
|
||||
|
||||
var handle = new ShrinkEffectHandle();
|
||||
var gate = new UniTaskCompletionSource();
|
||||
handle.BeginForward(gate.Task);
|
||||
Attach(handle);
|
||||
|
||||
Func<UniTask>? inverse = null;
|
||||
try
|
||||
{
|
||||
inverse = await effectFn();
|
||||
handle.CompleteForward(inverse);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 先登记逆操作再放行 gate,保证等待前向的 DisposeAsync 能读到完整逆链
|
||||
gate.TrySetResult();
|
||||
}
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
internal void Attach(ShrinkEffectHandle handle) => _effects.Add(handle);
|
||||
|
||||
/// <summary>
|
||||
/// 前向已在外部执行完毕的效应:仅登记逆操作(供桥接适配器把既有 API 的"注册/注销"对包装为可逆效应)。
|
||||
/// </summary>
|
||||
public ShrinkEffectHandle EffectInverse(Func<UniTask> inverse)
|
||||
{
|
||||
if (inverse == null)
|
||||
throw new ArgumentNullException(nameof(inverse));
|
||||
|
||||
var handle = new ShrinkEffectHandle();
|
||||
handle.AttachInverse(inverse);
|
||||
Attach(handle);
|
||||
return handle;
|
||||
}
|
||||
|
||||
/// <summary>按 LIFO 顺序执行本上下文注册的全部逆操作。</summary>
|
||||
public async UniTask DisposeAsync()
|
||||
{
|
||||
for (var i = _effects.Count - 1; i >= 0; i--)
|
||||
await _effects[i].DisposeAsync();
|
||||
_effects.Clear();
|
||||
}
|
||||
|
||||
// ---------------- 余效应(论文 3.2 / 算法 2) ----------------
|
||||
|
||||
/// <summary>
|
||||
/// 提供依赖(set 是可逆效应):立即安装绑定并通知依赖者;
|
||||
/// 逆操作撤回绑定并再次通知。句柄同时挂在当前上下文上,随上下文卸载自动撤回。
|
||||
/// </summary>
|
||||
public ShrinkEffectHandle Set<T>(string key, T value) => Runtime.SetBinding(this, key, value);
|
||||
|
||||
/// <summary>
|
||||
/// 代理介导的依赖访问(论文算法 6):沿纤程链解析已提交视图;
|
||||
/// 声明了但未激活的键抛 <see cref="ShrinkInactiveAccessException"/>,
|
||||
/// 全链未声明的键抛 <see cref="ShrinkUndeclaredAccessException"/>。
|
||||
/// </summary>
|
||||
public T Get<T>(string key)
|
||||
{
|
||||
var fiber = OwnerFiber;
|
||||
while (fiber != null)
|
||||
{
|
||||
if (fiber.Committed != null && fiber.Committed.TryGetValue(key, out var binding))
|
||||
return (T)binding.Value!;
|
||||
if (fiber.InjectSet.Contains(key))
|
||||
throw new ShrinkInactiveAccessException(key, fiber.Name);
|
||||
fiber = fiber.Parent;
|
||||
}
|
||||
|
||||
throw new ShrinkUndeclaredAccessException(key);
|
||||
}
|
||||
|
||||
/// <summary>与 <see cref="Get{T}"/> 相同的介导规则;未激活返回 false 而不抛异常。</summary>
|
||||
public bool TryGet<T>(string key, out T value)
|
||||
{
|
||||
var fiber = OwnerFiber;
|
||||
while (fiber != null)
|
||||
{
|
||||
if (fiber.Committed != null && fiber.Committed.TryGetValue(key, out var binding))
|
||||
{
|
||||
value = (T)binding.Value!;
|
||||
return true;
|
||||
}
|
||||
if (fiber.InjectSet.Contains(key))
|
||||
break;
|
||||
fiber = fiber.Parent;
|
||||
}
|
||||
|
||||
value = default!;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 派生隔离子上下文(论文定义 28/29 的派生实现):
|
||||
/// 覆盖 key 的 realm 解析;父上下文不受影响;丢弃子上下文即隐式恢复。
|
||||
/// </summary>
|
||||
public ShrinkCtx Isolate(string key, string realm)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
throw new ArgumentException("Key must not be null or empty.", nameof(key));
|
||||
if (string.IsNullOrEmpty(realm))
|
||||
throw new ArgumentException("Realm must not be null or empty.", nameof(realm));
|
||||
|
||||
return new ShrinkCtx(Runtime, this, OwnerFiber, new Dictionary<string, string> { [key] = realm });
|
||||
}
|
||||
|
||||
internal ShrinkCtx CreateChildForFiber(ShrinkFiber fiber) => new(Runtime, this, fiber, null);
|
||||
|
||||
/// <summary>在派生子上下文中叠加隔离覆盖(同键后者优先),供加载器为条目配置隔离域。</summary>
|
||||
internal ShrinkCtx WithIsolate(IReadOnlyDictionary<string, string> isolate)
|
||||
{
|
||||
var overlay = new Dictionary<string, string>();
|
||||
if (_isolateOverlay != null)
|
||||
{
|
||||
foreach (var pair in _isolateOverlay)
|
||||
overlay[pair.Key] = pair.Value;
|
||||
}
|
||||
|
||||
foreach (var pair in isolate)
|
||||
overlay[pair.Key] = pair.Value;
|
||||
|
||||
return new ShrinkCtx(Runtime, this, OwnerFiber, overlay);
|
||||
}
|
||||
|
||||
/// <summary>解析 key 在本上下文中归属的 realm:沿父链找最近的隔离覆盖,默认 realm 为 key 自身。</summary>
|
||||
internal string ResolveRealm(string key)
|
||||
{
|
||||
var current = this;
|
||||
while (current != null)
|
||||
{
|
||||
if (current._isolateOverlay != null && current._isolateOverlay.TryGetValue(key, out var realm))
|
||||
return realm;
|
||||
current = current.Parent;
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3c4e5dd3f658ca14a929dd41f2466927
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,61 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using Cysharp.Threading.Tasks;
|
||||
|
||||
namespace ShrinkContext
|
||||
{
|
||||
/// <summary>
|
||||
/// 单个可逆效应的句柄(论文算法 1 的 effect 封装)。
|
||||
/// 前向执行时产出逆操作并累积在句柄内部;DisposeAsync 至多执行一次(armed 语义),
|
||||
/// 若前向仍在进行中则先等待其完成再运行累积的逆操作(dispose = await task; recover())。
|
||||
/// </summary>
|
||||
public sealed class ShrinkEffectHandle
|
||||
{
|
||||
private static readonly Func<UniTask> Nop = static () => UniTask.CompletedTask;
|
||||
|
||||
private bool _armed = true;
|
||||
private UniTask _forwardTask;
|
||||
private bool _hasForwardTask;
|
||||
private Func<UniTask> _inverseChain = Nop;
|
||||
|
||||
public bool IsArmed => _armed;
|
||||
|
||||
/// <summary>登记一个进行中的异步前向;DisposeAsync 会先等待它完成。</summary>
|
||||
internal void BeginForward(UniTask forwardTask)
|
||||
{
|
||||
_forwardTask = forwardTask;
|
||||
_hasForwardTask = true;
|
||||
}
|
||||
|
||||
/// <summary>前向完成后登记其逆操作;新逆操作在链首执行(LIFO)。</summary>
|
||||
internal void CompleteForward(Func<UniTask> inverse)
|
||||
{
|
||||
if (inverse == null)
|
||||
return;
|
||||
|
||||
var previous = _inverseChain;
|
||||
_inverseChain = async () =>
|
||||
{
|
||||
await inverse();
|
||||
await previous();
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>直接附加逆操作(等效于前向已同步完成)。</summary>
|
||||
internal void AttachInverse(Func<UniTask> inverse) => CompleteForward(inverse);
|
||||
|
||||
public async UniTask DisposeAsync()
|
||||
{
|
||||
if (!_armed)
|
||||
return;
|
||||
|
||||
_armed = false;
|
||||
if (_hasForwardTask && _forwardTask.Status == UniTaskStatus.Pending)
|
||||
await _forwardTask;
|
||||
|
||||
var chain = _inverseChain;
|
||||
_inverseChain = Nop;
|
||||
await chain();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6baa51f655b3c9a4d9dc0b1dbdce8461
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ea9777e12a1bd8e40a2afbce45d23622
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,47 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Cysharp.Threading.Tasks;
|
||||
|
||||
namespace ShrinkContext
|
||||
{
|
||||
/// <summary>
|
||||
/// 组件 = (inject 依赖键集合 d, provide 供给键集合 p, apply 效应函数 e)(论文定义 43)。
|
||||
/// apply 内部通过 ctx.Set 提供依赖、ctx.Get 消费依赖、ctx.Effect 注册可逆效应;
|
||||
/// 组件的拆卸由运行时按已追踪的逆操作自动推导,无需手写卸载路径。
|
||||
/// </summary>
|
||||
public interface IShrinkComponent
|
||||
{
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>声明的依赖键集合(论文 d):全部可用时组件才激活。</summary>
|
||||
IReadOnlyList<string> Inject { get; }
|
||||
|
||||
/// <summary>供给键集合(论文 p):apply 的效应不应写入 p 之外的键。</summary>
|
||||
IReadOnlyList<string> Provide { get; }
|
||||
|
||||
/// <summary>效应函数(论文 e):激活时执行;其中注册的效应在停用时按 LIFO 回滚。</summary>
|
||||
UniTask ApplyAsync(ShrinkCtx ctx, object? config);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 分步效应组件(论文 𝔈iterΓ):apply 以步进器逐步产出逆操作。
|
||||
/// 目标在步进边界发生变化时中断迭代并回滚已执行步骤(部分回滚,论文 4.3.2 / 定理 64)。
|
||||
/// 使用自定义 UniTask 步进协议而非 IAsyncEnumerable:
|
||||
/// Mono 的 ValueTask 迭代器恢复经同步上下文调度,会破坏同步内联的确定性语义。
|
||||
/// </summary>
|
||||
public interface IShrinkIterativeComponent : IShrinkComponent
|
||||
{
|
||||
IShrinkStepEffectEnumerator ApplySteps(ShrinkCtx ctx, object? config);
|
||||
}
|
||||
|
||||
/// <summary>分步效应步进器:MoveNextAsync 执行该步前向并使 Current 指向其逆操作。</summary>
|
||||
public interface IShrinkStepEffectEnumerator
|
||||
{
|
||||
/// <summary>推进到下一步;返回 false 表示迭代结束。前向在该调用内执行完毕。</summary>
|
||||
UniTask<bool> MoveNextAsync();
|
||||
|
||||
/// <summary>最近一次 MoveNextAsync 成功后该步的逆操作。</summary>
|
||||
Func<UniTask> Current { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cc9d2e1bcf849da42a70c6c720e1c770
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,66 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Cysharp.Threading.Tasks;
|
||||
|
||||
namespace ShrinkContext
|
||||
{
|
||||
public enum ShrinkFiberState
|
||||
{
|
||||
Inactive = 0,
|
||||
Loading = 1,
|
||||
Active = 2,
|
||||
Unloading = 3,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 纤程(论文定义 44):组件的一次运行时实例化。
|
||||
/// 生命周期由 Target(期望提供者集合,null 即 ⊥)与惯性状态机驱动:
|
||||
/// 一旦进入转换即运行至完成,之后才响应新的目标变化(算法 5)。
|
||||
/// </summary>
|
||||
public sealed class ShrinkFiber
|
||||
{
|
||||
internal ShrinkFiber(long uid, IShrinkComponent component, ShrinkFiber? parent, object? config)
|
||||
{
|
||||
Uid = uid;
|
||||
Component = component;
|
||||
Parent = parent;
|
||||
Config = config;
|
||||
InjectSet = new HashSet<string>(component.Inject ?? Array.Empty<string>(), StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
public long Uid { get; }
|
||||
|
||||
public string Name => Component.Name;
|
||||
|
||||
public IShrinkComponent Component { get; }
|
||||
|
||||
/// <summary>实例化该纤程时的父纤程(根级为 null);父纤程卸载会级联到本纤程。</summary>
|
||||
public ShrinkFiber? Parent { get; }
|
||||
|
||||
/// <summary>本纤程运行所在的派生子上下文。</summary>
|
||||
public ShrinkCtx Ctx { get; internal set; } = null!;
|
||||
|
||||
public object? Config { get; }
|
||||
|
||||
public ShrinkFiberState State { get; internal set; } = ShrinkFiberState.Inactive;
|
||||
|
||||
/// <summary>是否到达过 Active(用于断言“从未激活”)。</summary>
|
||||
public bool EverActive { get; internal set; }
|
||||
|
||||
/// <summary>最近一次 apply 抛出的异常;成功激活后清空。</summary>
|
||||
public Exception? LastError { get; internal set; }
|
||||
|
||||
/// <summary>已提交视图(算法 5 reload 提交 / 算法 6 读取):依赖键 → 绑定快照。</summary>
|
||||
public IReadOnlyDictionary<string, ShrinkBinding>? Committed => _committed;
|
||||
|
||||
/// <summary>进行中转换的句柄;任务在纤程到达静止态(Active 或 Inactive)时完成。</summary>
|
||||
public UniTask Inertia { get; internal set; }
|
||||
|
||||
internal Dictionary<string, ShrinkBinding>? _committed;
|
||||
internal long[]? Target;
|
||||
internal bool InTransition;
|
||||
internal bool Retired;
|
||||
internal HashSet<string> InjectSet { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 812996226a7accb44b00963f89ff15fd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f54de3c6db6ef7f4097d692f8ac11e38
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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:
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "ShrinkContext.Core.Runtime",
|
||||
"rootNamespace": "ShrinkContext",
|
||||
"references": [
|
||||
"UniTask"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"autoReferenced": true
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 768c7f0b5000fcc4084d4c4de00ff868
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,28 @@
|
||||
#nullable enable
|
||||
using UnityEngine;
|
||||
|
||||
namespace ShrinkContext
|
||||
{
|
||||
/// <summary>
|
||||
/// 默认运行时入口 + Domain Reload 安全重置。
|
||||
/// 原型阶段不自动启动任何组件;编排方式(配置树加载器)属于后续阶段。
|
||||
/// </summary>
|
||||
public static class ShrinkContextDefaults
|
||||
{
|
||||
private static ShrinkContextRuntime? _default;
|
||||
|
||||
public static ShrinkContextRuntime Default => _default ??= new ShrinkContextRuntime();
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
||||
private static void ResetStaticState()
|
||||
{
|
||||
_default = null;
|
||||
}
|
||||
|
||||
/// <summary>测试用:丢弃默认运行时,下次访问时重建(模拟 Domain Reload)。</summary>
|
||||
public static void ResetForTesting()
|
||||
{
|
||||
_default = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f2734645dd1546e4fa4b496fda9349d0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,365 @@
|
||||
#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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c9abfe7bff0c65d4b8dae01a0fba1c09
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user