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:
@@ -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:
|
||||
Reference in New Issue
Block a user