14 Commits
Author SHA1 Message Date
cneicy d3eca1f155 perf(context)!: release retired fibers and bound diagnostic history
Publish UPM package / publish (push) Successful in 2s
Publish NuGet packages / publish (push) Successful in 1m29s
2026-09-29 10:14:41 +08:00
cneicy b93f3e6122 chore: exclude .NET metadata from UPM packages
Publish UPM package / publish (push) Successful in 2s
Publish NuGet packages / publish (push) Successful in 1m56s
2026-09-05 04:06:22 +08:00
cneicy c2d86238a6 ci: trust bundled CAs during NuGet publish 2026-09-05 04:04:42 +08:00
cneicy c1592fdc75 ci: provide OpenSSL for .NET packaging 2026-09-05 04:01:15 +08:00
cneicy 8e0b06b09e ci: run .NET packaging in invariant mode 2026-09-05 03:56:56 +08:00
cneicy 0fa2f44a7f ci: bootstrap .NET SDK without system packages 2026-09-05 03:54:34 +08:00
cneicy 1b5d96d35a ci: publish NuGet without external actions 2026-09-05 03:49:37 +08:00
cneicy ab6efbd046 ci: install .NET prerequisites for package publishing 2026-09-05 03:45:12 +08:00
cneicy 63fb328bed feat: add NuGet and Godot distribution 2026-09-05 03:41:35 +08:00
cneicy a4c78ea7a7 feat: add cross-engine runtime support 2026-09-05 02:33:55 +08:00
cneicy 02a0ece826 test(host): allow Unity test runner completion 2026-08-26 08:22:46 +08:00
cneicy 341976fac6 ci: mirror Git package dependencies 2026-08-26 07:07:01 +08:00
cneicy 709e9c18a4 test: enable standalone host discovery 2026-08-26 06:56:03 +08:00
cneicy 95b82aaac9 ci: stabilize standalone Unity host 2026-08-26 06:36:33 +08:00
18 changed files with 451 additions and 21 deletions
+123
View File
@@ -0,0 +1,123 @@
name: Publish NuGet packages
on:
push:
tags:
- 'v*'
workflow_dispatch:
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
env:
NUGET_AUTH_TOKEN: ${{ secrets.SHRINKSDK_PACKAGE_TOKEN }}
DOTNET_SYSTEM_GLOBALIZATION_INVARIANT: '1'
DOTNET_CLI_TELEMETRY_OPTOUT: '1'
LD_LIBRARY_PATH: /opt/dotnet-libs/usr/lib/x86_64-linux-gnu
SSL_CERT_FILE: /opt/ca-certificates.crt
steps:
- name: Fetch exact tagged source
env:
GITEA_REF: ${{ gitea.ref }}
GITEA_REPOSITORY: ${{ gitea.repository }}
shell: bash
run: |
set -euo pipefail
tag="${GITEA_REF#refs/tags/}"
case "$tag" in
v[0-9]*) ;;
*) echo "Expected a version tag ref, got: $GITEA_REF" >&2; exit 1 ;;
esac
export SHRINKSDK_ARCHIVE_URL="https://git.crash.work/${GITEA_REPOSITORY}/archive/${tag}.tar.gz"
node --input-type=module <<'NODE'
import { writeFile } from 'node:fs/promises';
const response = await fetch(process.env.SHRINKSDK_ARCHIVE_URL);
if (!response.ok) {
throw new Error(`Release archive download failed: ${response.status} ${response.statusText}`);
}
await writeFile('release.tar.gz', new Uint8Array(await response.arrayBuffer()));
NODE
mkdir release
tar -xzf release.tar.gz --strip-components=1 -C release
rm -f release.tar.gz
printf '%s' "$tag" > release/.shrink-sdk-release-tag
- name: Install .NET 8 SDK
shell: bash
run: |
set -euo pipefail
node --input-type=module <<'NODE'
import { writeFile } from 'node:fs/promises';
import { rootCertificates } from 'node:tls';
await writeFile('/opt/ca-certificates.crt', rootCertificates.join('\n'));
const metadataResponse = await fetch('https://dotnetcli.blob.core.windows.net/dotnet/release-metadata/8.0/releases.json');
if (!metadataResponse.ok) throw new Error(`Release metadata download failed: ${metadataResponse.status}`);
const metadata = await metadataResponse.json();
const sdkVersion = metadata['latest-sdk'];
const release = metadata.releases.find(item => item.sdk?.version === sdkVersion);
const file = release?.sdk?.files?.find(item => item.rid === 'linux-x64' && item.name.endsWith('.tar.gz'));
if (!file) throw new Error(`Linux x64 SDK archive not found for ${sdkVersion}`);
const archiveResponse = await fetch(file.url);
if (!archiveResponse.ok) throw new Error(`SDK download failed: ${archiveResponse.status}`);
await writeFile('/tmp/dotnet-sdk.tar.gz', new Uint8Array(await archiveResponse.arrayBuffer()));
const poolUrl = 'https://deb.debian.org/debian-security/pool/updates/main/o/openssl/';
const poolResponse = await fetch(poolUrl);
if (!poolResponse.ok) throw new Error(`OpenSSL package index download failed: ${poolResponse.status}`);
const poolIndex = await poolResponse.text();
const packages = [...poolIndex.matchAll(/href="(libssl3_[^"]+_amd64\.deb)"/g)].map(match => match[1]).sort();
const packageName = packages.at(-1);
if (!packageName) throw new Error('Debian libssl3 package was not found');
const packageResponse = await fetch(poolUrl + packageName);
if (!packageResponse.ok) throw new Error(`OpenSSL package download failed: ${packageResponse.status}`);
await writeFile('/tmp/libssl3.deb', new Uint8Array(await packageResponse.arrayBuffer()));
NODE
mkdir -p /opt/dotnet
tar -xzf /tmp/dotnet-sdk.tar.gz -C /opt/dotnet
mkdir -p /opt/dotnet-libs
dpkg-deb -x /tmp/libssl3.deb /opt/dotnet-libs
rm -f /tmp/dotnet-sdk.tar.gz
rm -f /tmp/libssl3.deb
/opt/dotnet/dotnet --info
- name: Validate, pack and publish
shell: bash
run: |
set -euo pipefail
: "${NUGET_AUTH_TOKEN:?SHRINKSDK_PACKAGE_TOKEN is required}"
export PATH="/opt/dotnet:$PATH"
cd release
tag="$(cat .shrink-sdk-release-tag)"
projects=()
if [[ -d DotNet~ ]]; then
while IFS= read -r -d '' project; do projects+=("$project"); done < <(find DotNet~ -type f -name '*.csproj' -print0)
fi
if [[ -d Godot~ ]]; then
while IFS= read -r -d '' project; do projects+=("$project"); done < <(find Godot~ -type f -name '*.csproj' -print0)
fi
if [[ "${#projects[@]}" -eq 0 ]]; then
while IFS= read -r -d '' project; do projects+=("$project"); done < <(find . -maxdepth 1 -type f -name '*.csproj' -print0)
fi
test "${#projects[@]}" -gt 0
if [[ -f package.json ]]; then
version="$(node -p "require('./package.json').version")"
else
version="$(dotnet msbuild "${projects[0]}" -getProperty:Version -nologo)"
fi
test "$tag" = "v$version"
mkdir -p packages
for project in "${projects[@]}"; do
dotnet restore "$project" --configfile NuGet.Config
dotnet pack "$project" --configuration Release --no-restore --output "$PWD/packages" --include-symbols --include-source
done
find packages -maxdepth 1 -name '*.nupkg' -type f | grep -q .
dotnet nuget push 'packages/*.nupkg' --api-key "$NUGET_AUTH_TOKEN" --source https://git.crash.work/api/packages/ShrinkSDK/nuget/index.json --skip-duplicate
if compgen -G 'packages/*.snupkg' > /dev/null; then
dotnet nuget push 'packages/*.snupkg' --api-key "$NUGET_AUTH_TOKEN" --source https://git.crash.work/api/packages/ShrinkSDK/nuget/index.json --skip-duplicate
fi
+6 -1
View File
@@ -26,12 +26,17 @@ jobs:
shell: bash
run: |
set -eu
machine_id_file="/root/.local/share/unity3d/Unity/.machine-id"
if test -s "$machine_id_file"; then
cat "$machine_id_file" > /etc/machine-id
echo "Unity machine identity restored"
fi
git config --global url."https://ghfast.top/https://github.com/".insteadOf "https://github.com/"
unity_bin="$(command -v unity-editor || command -v unity || command -v Unity || true)"
test -n "$unity_bin"
"$unity_bin" \
-batchmode \
-nographics \
-quit \
-projectPath "$PWD/Development~/UnityProject" \
-runTests \
-testPlatform EditMode \
+8
View File
@@ -8,3 +8,11 @@
/Tools~/**/[Oo]bj/
*.user
*.DotSettings.user
/DotNet~/**/[Bb]in/
/DotNet~/**/[Oo]bj/
/Godot~/**/[Bb]in/
/Godot~/**/[Oo]bj/
/artifacts/
/packages/
!DotNet~/**/*.csproj
!Godot~/**/*.csproj
+6
View File
@@ -6,3 +6,9 @@ Tools~/
*.sln
*.user
*.DotSettings.user
DotNet~/
Godot~/
NuGet.Config
Directory.Build.props
NuGet.Config.meta
Directory.Build.props.meta
@@ -10,6 +10,10 @@
],
"dependencies": {
"com.unity.test-framework": "1.1.33",
"com.cneicy.shrink-context-core": "file:../../.."
}
"com.cneicy.shrink-context-core": "file:../../..",
"com.cysharp.unitask": "https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask#7c0f199fe0d3fc528024488ccd671e6c7b27745b"
},
"testables": [
"com.cneicy.shrink-context-core"
]
}
+13
View File
@@ -0,0 +1,13 @@
<Project>
<PropertyGroup>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<Deterministic>true</Deterministic>
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
<Authors>ShrinkSDK</Authors>
<Company>ShrinkSDK</Company>
<RepositoryUrl>https://git.crash.work/ShrinkSDK</RepositoryUrl>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
</Project>
+7
View File
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 02470ef2edd28824aa6a82e958fa4674
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+17
View File
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<AssemblyName>ShrinkContext.Core.Runtime</AssemblyName>
<RootNamespace>ShrinkContext</RootNamespace>
<PackageId>ShrinkSDK.Context.Core</PackageId>
<Version>0.3.0</Version>
<Description>ShrinkSDK reversible context and fiber runtime.</Description>
<ShrinkCodeGenEnabled>false</ShrinkCodeGenEnabled>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\Runtime\**\*.cs" />
<PackageReference Include="UniTask" Version="2.5.10" />
<PackageReference Include="ShrinkSDK.Runtime.Abstractions" Version="0.1.0" />
</ItemGroup>
</Project>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="ShrinkSDK" value="https://git.crash.work/api/packages/ShrinkSDK/nuget/index.json" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
</packageSources>
</configuration>
+32
View File
@@ -0,0 +1,32 @@
fileFormatVersion: 2
guid: aaa4ea210cab41747a7e7956707db0e4
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 1
settings: {}
userData:
assetBundleName:
assetBundleVariant:
+12
View File
@@ -2,6 +2,8 @@
《Cordis: A Programming Paradigm for Spatiotemporal Composability》核心机制的 Unity/C# 实现。当前架构与边界见仓库根 `DESIGN.md`;已完成的迁移过程归档于 `Docs/Archive/CORDIS_MIGRATION.completed.md`。
共享内核不引用 Unity 或 Godot。Godot 和普通 .NET 项目安装 `ShrinkSDK.Context.Core`;可打包源码位于 `DotNet~`。
## 定位
时空可组合性的**元框架核心库**:不规定任何领域,只提供动态组合的两个通用原语——
@@ -67,3 +69,13 @@
- 仅主线程使用;异步转换在同步可完成路径上内联跑完(测试无需 PlayerLoop 泵即可确定性断言)。
- 测试中的 `TestAwait.Run` 同步阻塞等待,仅用于保证延续同步完成的测试代码。
## 可复用能力
<!-- shrink:capabilities -->
Capability: 动态模块依赖、provider 替换和可逆生命周期;声明式组合失败恢复
Aliases: 上下文 组件 加载 卸载 装卸 依赖 热替换 isolate intercept Cordis
Limits: 不提供具体战斗、资源、背包机制;同一 runtime 的组合操作须在所属线程串行协调
Extension: IShrinkComponent.Inject/Provide/Apply;ShrinkLoaderEntry 配置;ctx.Effect/Set/Get
Evidence: [ShrinkContextRuntime](Runtime/ShrinkContextRuntime.cs); [ShrinkContextLoader](Runtime/ShrinkContextDefaults.cs); [ShrinkCtx](Runtime/Context/ShrinkCtx.cs)
<!-- /shrink:capabilities -->
+16 -3
View File
@@ -14,6 +14,7 @@ namespace ShrinkContext
public sealed class ShrinkCtx
{
private readonly List<ShrinkEffectHandle> _effects = new();
private bool _disposing;
private readonly Dictionary<string, string>? _isolateOverlay;
private Dictionary<string, IReadOnlyDictionary<string, object?>>? _interceptOverlay;
@@ -81,6 +82,13 @@ namespace ShrinkContext
internal void Attach(ShrinkEffectHandle handle) => _effects.Add(handle);
internal void Detach(ShrinkEffectHandle handle)
{
// During parent teardown the reverse traversal owns removal.
if (!_disposing)
_effects.Remove(handle);
}
/// <summary>
/// 前向已在外部执行完毕的效应:仅登记逆操作(供桥接适配器把既有 API 的"注册/注销"对包装为可逆效应)。
/// </summary>
@@ -98,9 +106,14 @@ namespace ShrinkContext
/// <summary>按 LIFO 顺序执行本上下文注册的全部逆操作。</summary>
public async UniTask DisposeAsync()
{
for (var i = _effects.Count - 1; i >= 0; i--)
await _effects[i].DisposeAsync();
_effects.Clear();
_disposing = true;
try
{
for (var i = _effects.Count - 1; i >= 0; i--)
await _effects[i].DisposeAsync();
_effects.Clear();
}
finally { _disposing = false; }
}
// ---------------- 余效应(论文 3.2 / 算法 2) ----------------
+2
View File
@@ -62,6 +62,8 @@ namespace ShrinkContext
internal long[]? Target;
internal bool InTransition;
internal bool Retired;
internal ShrinkCtx? EffectOwner;
internal ShrinkEffectHandle? Instantiation;
internal HashSet<string> InjectSet { get; }
internal HashSet<string> ProvideSet { get; }
}
+4
View File
@@ -1,5 +1,7 @@
#nullable enable
#if UNITY_5_3_OR_NEWER
using UnityEngine;
#endif
namespace ShrinkContext
{
@@ -13,7 +15,9 @@ namespace ShrinkContext
public static ShrinkContextRuntime Default => _default ??= new ShrinkContextRuntime();
#if UNITY_5_3_OR_NEWER
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
#endif
private static void ResetStaticState()
{
_default = null;
+91 -14
View File
@@ -20,16 +20,22 @@ namespace ShrinkContext
{
private readonly ShrinkCtx _rootCtx;
private readonly List<ShrinkFiber> _fibers = new();
private readonly Queue<ShrinkFiberDiagnostic> _retiredHistory = new();
private readonly int _retiredHistoryCapacity;
private readonly Dictionary<string, ShrinkBinding> _store = new();
private readonly Dictionary<string, HashSet<ShrinkFiber>> _injectIndex =
new(StringComparer.Ordinal);
private readonly Queue<Action> _transitionWork = new();
private bool _drainingTransitionWork;
private long _uidCounter;
private long _notificationDispatchCount;
private long _notificationCandidateVisitCount;
private int _lastNotificationCandidateCount;
public ShrinkContextRuntime()
public ShrinkContextRuntime(int retiredHistoryCapacity = 64)
{
if (retiredHistoryCapacity < 0) throw new ArgumentOutOfRangeException(nameof(retiredHistoryCapacity));
_retiredHistoryCapacity = retiredHistoryCapacity;
_rootCtx = new ShrinkCtx(this, null, null, null);
}
@@ -65,6 +71,8 @@ namespace ShrinkContext
var instantiationHandle = new ShrinkEffectHandle();
instantiationHandle.AttachInverse(() => RetireCoreAsync(fiber));
parent.Attach(instantiationHandle);
fiber.EffectOwner = parent;
fiber.Instantiation = instantiationHandle;
Refresh(fiber);
return fiber;
@@ -77,11 +85,16 @@ namespace ShrinkContext
public async UniTask RetireAsync(ShrinkFiber fiber)
{
if (fiber.Retired)
{
if (fiber.InTransition) await fiber.Inertia;
await ReleaseRetiredAsync(fiber);
return;
}
UnindexFiber(fiber);
if (fiber.State == ShrinkFiberState.Inactive && !fiber.InTransition)
{
fiber.Retired = true;
await ReleaseRetiredAsync(fiber);
return;
}
@@ -89,12 +102,33 @@ namespace ShrinkContext
fiber.Target = null;
if (!fiber.InTransition)
{
fiber.InTransition = true;
fiber.Inertia = RunTransitionAsync(fiber);
ScheduleTransition(fiber);
}
if (fiber.InTransition)
await fiber.Inertia;
await ReleaseRetiredAsync(fiber);
}
private async UniTask ReleaseRetiredAsync(ShrinkFiber fiber)
{
if (fiber.InTransition || fiber.State != ShrinkFiberState.Inactive || !_fibers.Remove(fiber)) return;
var owner = fiber.EffectOwner;
var handle = fiber.Instantiation;
fiber.EffectOwner = null;
fiber.Instantiation = null;
if (handle != null)
{
owner?.Detach(handle);
await handle.DisposeAsync();
}
if (_retiredHistoryCapacity == 0) return;
while (_retiredHistory.Count >= _retiredHistoryCapacity) _retiredHistory.Dequeue();
_retiredHistory.Enqueue(new ShrinkFiberDiagnostic(fiber.Uid, fiber.Name,
ShrinkFiberDiagnosticStatus.Retired, fiber.State, true, false,
fiber.InjectSet.ToArray(), fiber.ProvideSet.ToArray(), Array.Empty<long>(),
Array.Empty<ShrinkDependencyDiagnostic>(), Array.Empty<string>(),
fiber.LastError?.GetType().FullName, fiber.LastError?.Message));
}
/// <summary>卸载全部纤程并回滚根上下文效应。</summary>
@@ -123,13 +157,47 @@ namespace ShrinkContext
fiber.Target = target;
if (!fiber.InTransition)
{
fiber.InTransition = true;
fiber.Inertia = RunTransitionAsync(fiber);
ScheduleTransition(fiber);
}
return true;
}
private void ScheduleTransition(ShrinkFiber fiber)
{
var completion = new UniTaskCompletionSource();
fiber.InTransition = true;
fiber.Inertia = completion.Task.Preserve();
EnqueueTransitionWork(() => RunScheduledTransitionAsync(fiber, completion).Forget());
}
private async UniTask RunScheduledTransitionAsync(ShrinkFiber fiber, UniTaskCompletionSource completion)
{
try
{
await RunTransitionAsync(fiber);
EnqueueTransitionWork(() => completion.TrySetResult());
}
catch (Exception error)
{
EnqueueTransitionWork(() => completion.TrySetException(error));
}
}
private void EnqueueTransitionWork(Action work)
{
_transitionWork.Enqueue(work);
if (_drainingTransitionWork) return;
_drainingTransitionWork = true;
try
{
// Starting transitions and completing their awaiters both use this trampoline.
// Deep synchronous dependency chains therefore keep a bounded CLR call stack.
while (_transitionWork.Count > 0) _transitionWork.Dequeue()();
}
finally { _drainingTransitionWork = false; }
}
private async UniTask RunTransitionAsync(ShrinkFiber fiber)
{
try
@@ -457,8 +525,21 @@ namespace ShrinkContext
/// <summary>捕获不含可变运行时引用的诊断快照。</summary>
public ShrinkContextRuntimeDiagnostic CaptureDiagnostic()
{
var diagnostics = new List<ShrinkFiberDiagnostic>(_fibers.Count);
foreach (var fiber in _fibers.OrderBy(item => item.Uid))
var providers = new Dictionary<(string key, string realm), List<long>>();
foreach (var candidate in _fibers)
{
if (candidate.Retired) continue;
foreach (var key in candidate.ProvideSet)
{
if (!_injectIndex.ContainsKey(key)) continue;
var id = (key, candidate.Ctx.ResolveRealm(key));
if (!providers.TryGetValue(id, out var entries)) providers.Add(id, entries = new List<long>());
entries.Add(candidate.Uid);
}
}
var diagnostics = new List<ShrinkFiberDiagnostic>(_fibers.Count + _retiredHistory.Count);
diagnostics.AddRange(_retiredHistory);
foreach (var fiber in _fibers)
{
var dependencies = new List<ShrinkDependencyDiagnostic>();
foreach (var key in fiber.Component.Inject ?? Array.Empty<string>())
@@ -467,12 +548,8 @@ namespace ShrinkContext
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();
var potential = providers.TryGetValue((key, realm), out var candidates)
? candidates.ToArray() : Array.Empty<long>();
dependencies.Add(new ShrinkDependencyDiagnostic(
key,
@@ -499,7 +576,7 @@ namespace ShrinkContext
}
return new ShrinkContextRuntimeDiagnostic(
diagnostics,
diagnostics.OrderBy(item => item.Uid).ToArray(),
_store.Count,
new ShrinkNotificationDiagnostic(
_notificationDispatchCount,
+88
View File
@@ -0,0 +1,88 @@
#nullable enable
using System;
using System.Linq;
using System.Runtime.CompilerServices;
using Cysharp.Threading.Tasks;
using NUnit.Framework;
namespace ShrinkContext.Tests
{
public class RetirementRetentionTests
{
[Test]
public void DeepDependencyChainRetiresWithoutGrowingTheCallStack()
{
var runtime = new ShrinkContextRuntime();
ShrinkFiber first = null;
for (var i = 0; i < 1000; i++)
{
var fiber = runtime.Use(new ChainComponent(i));
if (i == 0) first = fiber;
}
TestAwait.Run(runtime.RetireAsync(first));
Assert.That(runtime.Fibers.All(f => f.State == ShrinkFiberState.Inactive));
Assert.That(runtime.CaptureDiagnostic().BindingCount, Is.Zero);
TestAwait.Run(runtime.ShutdownAsync());
Assert.That(runtime.Fibers, Is.Empty);
}
private sealed class ChainComponent : IShrinkComponent
{
public ChainComponent(int index)
{
Name = "chain-" + index;
Inject = index == 0 ? Array.Empty<string>() : new[] { "chain-" + (index - 1) };
Provide = new[] { Name };
}
public string Name { get; }
public System.Collections.Generic.IReadOnlyList<string> Inject { get; }
public System.Collections.Generic.IReadOnlyList<string> Provide { get; }
public UniTask ApplyAsync(ShrinkCtx ctx, object config) { ctx.Set(Name, this); return UniTask.CompletedTask; }
}
[Test]
public void RepeatedRetirementKeepsOnlyBoundedValueHistory()
{
var runtime = new ShrinkContextRuntime(8);
for (var i = 0; i < 10000; i++)
TestAwait.Run(runtime.RetireAsync(runtime.Use(new ProviderComponent("provider", "value", i))));
Assert.That(runtime.Fibers, Is.Empty);
var diagnostic = runtime.CaptureDiagnostic();
Assert.That(diagnostic.Fibers.Count, Is.EqualTo(8));
Assert.That(diagnostic.Fibers.All(f => f.Retired));
Assert.That(diagnostic.BindingCount, Is.Zero);
TestAwait.Run(runtime.ShutdownAsync());
}
[Test]
public void RuntimeAndParentEffectsDoNotRetainRetiredComponent()
{
var runtime = new ShrinkContextRuntime();
var weak = CreateAndRetire(runtime);
GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect();
Assert.That(weak.IsAlive, Is.False);
GC.KeepAlive(runtime);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static WeakReference CreateAndRetire(ShrinkContextRuntime runtime)
{
var component = new ProviderComponent("collectible", "key", new byte[1024]);
var weak = new WeakReference(component);
TestAwait.Run(runtime.RetireAsync(runtime.Use(component)));
return weak;
}
[Test]
public void DiagnosticProvidersRespectRealmsAndExcludeRetired()
{
var runtime = new ShrinkContextRuntime();
var p = runtime.Use(new ProviderComponent("p", "key", "value"));
var c = runtime.Use(new ConsumerComponent("c", "key"));
Assert.That(runtime.CaptureDiagnostic().Fibers.Single(f => f.Uid == c.Uid).Dependencies.Single().PotentialProviderUids, Is.EqualTo(new[] { p.Uid }));
TestAwait.Run(runtime.RetireAsync(p));
Assert.That(runtime.CaptureDiagnostic().Fibers.Single(f => f.Uid == c.Uid).Dependencies.Single().PotentialProviderUids, Is.Empty);
TestAwait.Run(runtime.ShutdownAsync());
}
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3593eb7178803dd40b5e070709a5fe8b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "com.cneicy.shrink-context-core",
"version": "0.1.0",
"version": "0.3.0",
"displayName": "ShrinkContext Core",
"description": "Cordis 时空可组合性范式的 Unity/C# 原型核心:可逆效应追踪、响应式余效应与组件纤程生命周期。",
"unity": "2022.3",