feat(packages): 内置 SDK 包并完善 ContextLoader 集成

- 将 ShrinkEventBus、ShrinkDataSaver 及其 EventBus 集成从 gitlink 转为仓库直接维护的完整 UPM 包,补齐运行时、编辑器工具、测试与文档
- 新增 Command 和 Network 的 App 集成组件,支持 ContextLoader 服务发布、可逆注销及 Network Loopback 生命周期管理
- 更新 Starter 与演示组合逻辑,缺失模块时可注册、已有兼容安装器时可覆盖,并补充宿主启动断言
- 升级内部包依赖与 Shared CodeGen 包定义,放宽 Integration.App 包的 Git 忽略规则
- 将独立服务器生成器改为基于已编译程序集的语义扫描,支持 partial、复杂泛型、命名冲突检测及模板 SHA-256 覆写保护
- 新增 Network 语义扫描、模板保护和 App 组件生命周期测试
- 新增真实 UPM 消费工程验证脚本,校验内部版本一致性、程序集加载及 EditMode 测试
- 重构当前架构文档并归档已完成的 Cordis 迁移与旧代码地图
This commit is contained in:
2026-08-18 18:06:34 +08:00
parent 517c4cf46e
commit d74c2f08ca
240 changed files with 13647 additions and 545 deletions
@@ -0,0 +1,23 @@
{
"name": "ShrinkNetwork.Editor.Tests",
"rootNamespace": "ShrinkNetwork.Editor.Tests",
"references": [
"ShrinkNetwork.Runtime",
"ShrinkNetwork.Editor"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"autoReferenced": false,
"defineConstraints": [
"UNITY_INCLUDE_TESTS"
],
"versionDefines": [],
"noEngineReferences": false,
"optionalUnityReferences": [
"TestAssemblies"
]
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: ff014bde3ee6e184cb3bb7ffbc42d917
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,138 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NUnit.Framework;
using ShrinkNetwork;
namespace ShrinkNetwork.Editor.Tests
{
public sealed class ShrinkNetworkSemanticScannerTests
{
[Test]
public void NamespaceCollisionIsRejectedInsteadOfSilentlyDroppingAContract()
{
var exception = Assert.Throws<InvalidOperationException>(() =>
ShrinkNetworkSemanticScanner.ScanTypesForTests(
typeof(CollisionA.SharedMessage),
typeof(CollisionB.SharedMessage)));
StringAssert.Contains("SharedMessage", exception!.Message);
StringAssert.Contains(typeof(CollisionA.SharedMessage).FullName, exception.Message);
StringAssert.Contains(typeof(CollisionB.SharedMessage).FullName, exception.Message);
}
[Test]
public void PartialMessageUsesTheCompiledTypeAndIncludesEveryPart()
{
var result = ShrinkNetworkSemanticScanner.ScanTypesForTests(typeof(PartialMessage));
CollectionAssert.AreEqual(
new[] { "First", "Second" },
result.Messages.Single().Properties.Select(property => property.Name));
}
[Test]
public void ComplexGenericPropertyKeepsItsSemanticTypeShape()
{
var result = ShrinkNetworkSemanticScanner.ScanTypesForTests(
typeof(ComplexGenericMessage),
typeof(SemanticPayload));
var property = result.Messages.Single().Properties.Single();
Assert.AreEqual("Dictionary<string, List<SemanticPayload[]>>", property.Type);
Assert.AreEqual("Payloads", property.Name);
Assert.AreEqual("SemanticPayload", result.DataTypes.Single().Name);
}
}
public sealed class ShrinkDedicatedServerTemplateProtectionTests
{
private string _root = string.Empty;
private string _templateRoot = string.Empty;
private string _outputRoot = string.Empty;
[SetUp]
public void SetUp()
{
_root = Path.Combine(Path.GetTempPath(), "ShrinkNetworkGeneratorTests", Guid.NewGuid().ToString("N"));
_templateRoot = Path.Combine(_root, "Template");
_outputRoot = Path.Combine(_root, "Output");
Directory.CreateDirectory(_templateRoot);
}
[TearDown]
public void TearDown()
{
if (Directory.Exists(_root))
Directory.Delete(_root, true);
}
[Test]
public void ModifiedManagedFileBlocksRegenerationBeforeAnyFileIsWritten()
{
File.WriteAllText(Path.Combine(_templateRoot, "README.md.txt"), "generated-v1");
File.WriteAllText(Path.Combine(_templateRoot, "settings.json.txt"), "settings-v1");
ShrinkDedicatedServerScaffoldGenerator.CopyTemplateProject(_templateRoot, _outputRoot);
File.WriteAllText(Path.Combine(_outputRoot, "README.md"), "manual-change");
File.WriteAllText(Path.Combine(_templateRoot, "settings.json.txt"), "settings-v2");
Assert.Throws<InvalidOperationException>(() =>
ShrinkDedicatedServerScaffoldGenerator.CopyTemplateProject(_templateRoot, _outputRoot));
Assert.AreEqual("manual-change", File.ReadAllText(Path.Combine(_outputRoot, "README.md")));
Assert.AreEqual("settings-v1", File.ReadAllText(Path.Combine(_outputRoot, "settings.json")));
}
[Test]
public void UnmodifiedManagedFilesCanBeRegeneratedFromANewerTemplate()
{
File.WriteAllText(Path.Combine(_templateRoot, "README.md.txt"), "generated-v1");
ShrinkDedicatedServerScaffoldGenerator.CopyTemplateProject(_templateRoot, _outputRoot);
File.WriteAllText(Path.Combine(_templateRoot, "README.md.txt"), "generated-v2");
ShrinkDedicatedServerScaffoldGenerator.CopyTemplateProject(_templateRoot, _outputRoot);
Assert.AreEqual("generated-v2", File.ReadAllText(Path.Combine(_outputRoot, "README.md")));
}
}
[ShrinkNetworkMessage(9101, "tests/partial")]
internal sealed partial class PartialMessage : IShrinkNetworkMessage
{
public int First { get; set; }
}
internal sealed partial class PartialMessage
{
public string Second { get; set; } = string.Empty;
}
[ShrinkNetworkMessage(9102, "tests/complex-generic")]
internal sealed class ComplexGenericMessage : IShrinkNetworkMessage
{
public Dictionary<string, List<SemanticPayload[]>> Payloads { get; set; } = new();
}
internal sealed class SemanticPayload
{
public Guid Id { get; set; }
}
}
namespace ShrinkNetwork.Editor.Tests.CollisionA
{
[ShrinkNetworkMessage(9110, "tests/collision-a")]
internal sealed class SharedMessage : IShrinkNetworkMessage
{
}
}
namespace ShrinkNetwork.Editor.Tests.CollisionB
{
[ShrinkNetworkMessage(9111, "tests/collision-b")]
internal sealed class SharedMessage : IShrinkNetworkMessage
{
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: dac93fdc7d35e3c4db7bdbeb9cc25f62
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: