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,8 @@
fileFormatVersion: 2
guid: c297548ebc9165f448111a35488d368e
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,375 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;
using Mono.Cecil.Pdb;
using Unity.CompilationPipeline.Common.Diagnostics;
using Unity.CompilationPipeline.Common.ILPostProcessing;
namespace ShrinkEventBus.CodeGen
{
public sealed class EventBusILPostProcessor : ILPostProcessor
{
private const string RuntimeAssemblyName = "ShrinkEventBus.Runtime";
private static readonly string[] SharedCoverageAssemblyNames =
{
"ShrinkCommand.Runtime",
"ShrinkNetwork.Runtime",
"ShrinkApp.Core.Runtime"
};
public override ILPostProcessor GetInstance() => this;
public override bool WillProcess(ICompiledAssembly compiledAssembly)
{
if (!ReferencesAssembly(compiledAssembly, RuntimeAssemblyName))
return false;
if (ShouldDeferToSharedCodeGen(compiledAssembly))
return false;
return true;
}
public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly)
{
var diagnostics = new List<DiagnosticMessage>();
if (!WillProcess(compiledAssembly))
return new ILPostProcessResult(compiledAssembly.InMemoryAssembly, diagnostics);
var assemblyDefinition = AssemblyDefinitionFor(compiledAssembly);
var module = assemblyDefinition.MainModule;
try
{
var subscriberType = FindType(module, "ShrinkEventBus.EventBusSubscriberAttribute", RuntimeAssemblyName);
var subscribeAttributeType = FindType(module, "ShrinkEventBus.EventSubscribeAttribute", RuntimeAssemblyName);
var eventBusType = FindType(module, "ShrinkEventBus.EventBus", RuntimeAssemblyName);
var staticRegistryCtor = FindTypeArrayConstructor(
module,
"ShrinkEventBus.EventBusStaticRegistryAttribute",
RuntimeAssemblyName);
if (subscriberType == null || subscribeAttributeType == null || eventBusType == null)
return GetResult(assemblyDefinition, diagnostics);
var eventBusTypeDef = eventBusType.Resolve();
var autoRegisterMethod = eventBusTypeDef?.Methods.FirstOrDefault(method =>
method.Name == "AutoRegister" &&
method.IsStatic &&
method.Parameters.Count == 1);
var unregisterMethod = eventBusTypeDef?.Methods.FirstOrDefault(method =>
method.Name == "UnregisterInstance" &&
method.IsStatic &&
method.Parameters.Count == 1);
if (autoRegisterMethod == null || unregisterMethod == null)
return GetResult(assemblyDefinition, diagnostics);
var autoRegisterMethodRef = module.ImportReference(autoRegisterMethod);
var unregisterMethodRef = module.ImportReference(unregisterMethod);
var subscriberTypes = module.Types
.Where(type => HasAttribute(type, subscriberType))
.Where(InheritsFromMonoBehaviour)
.Where(type => HasInstanceSubscribeMethod(type, subscribeAttributeType))
.ToArray();
foreach (var type in subscriberTypes)
{
InjectAutoRegister(type, module, autoRegisterMethodRef);
InjectAutoUnregister(type, module, unregisterMethodRef);
}
if (staticRegistryCtor != null)
{
var staticSubscriberTypes = module.Types
.Where(type => HasAttribute(type, subscriberType))
.Where(type => type.Methods.Any(method =>
method.IsStatic && HasAttribute(method, subscribeAttributeType)))
.Select(type => module.ImportReference(type))
.ToArray();
if (staticSubscriberTypes.Length > 0)
AddAssemblyTypeArrayAttribute(module, staticRegistryCtor, staticSubscriberTypes);
}
}
catch (Exception ex)
{
diagnostics.Add(new DiagnosticMessage
{
DiagnosticType = DiagnosticType.Error,
MessageData = $"[ShrinkEventBus.CodeGen] {ex.Message}"
});
}
return GetResult(assemblyDefinition, diagnostics);
}
private static bool ShouldDeferToSharedCodeGen(ICompiledAssembly compiledAssembly)
{
if (!IsSharedCodeGenAvailable())
return false;
return SharedCoverageAssemblyNames.Any(name => ReferencesAssembly(compiledAssembly, name));
}
private static bool IsSharedCodeGenAvailable()
{
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
if (string.Equals(assembly.GetName().Name, "Unity.ShrinkShared.CodeGen", StringComparison.Ordinal))
return true;
}
return Type.GetType("ShrinkShared.CodeGen.ShrinkRegistryILPostProcessor, Unity.ShrinkShared.CodeGen", false) != null;
}
private static bool ReferencesAssembly(ICompiledAssembly compiledAssembly, string assemblyName)
{
return compiledAssembly.References.Any(reference =>
string.Equals(Path.GetFileNameWithoutExtension(reference), assemblyName, StringComparison.Ordinal));
}
private static bool HasAttribute(ICustomAttributeProvider provider, TypeReference expectedAttributeType)
{
return provider.CustomAttributes.Any(attribute => attribute.AttributeType.FullName == expectedAttributeType.FullName);
}
private static bool HasInstanceSubscribeMethod(TypeDefinition type, TypeReference subscribeAttributeType)
{
var current = type;
while (current != null && current.Name != "MonoBehaviour")
{
if (current.Methods.Any(method => !method.IsStatic && HasAttribute(method, subscribeAttributeType)))
return true;
var baseTypeRef = current.BaseType;
if (baseTypeRef == null)
return false;
TypeDefinition? resolvedBase;
try
{
resolvedBase = baseTypeRef.Resolve();
}
catch
{
return true;
}
if (resolvedBase == null)
return true;
current = resolvedBase;
}
return false;
}
private static bool InheritsFromMonoBehaviour(TypeDefinition type)
{
var current = type.BaseType;
while (current != null)
{
if (current.Name == "MonoBehaviour")
return true;
try
{
current = current.Resolve()?.BaseType;
}
catch
{
break;
}
}
return false;
}
private static void InjectAutoRegister(TypeDefinition type, ModuleDefinition module, MethodReference autoRegisterMethodRef)
{
var awake = type.Methods.FirstOrDefault(method => method.Name == "Awake" && !method.IsStatic);
if (awake == null)
{
var baseAwakeRef = FindBaseMethodReference(type, "Awake", module);
if (baseAwakeRef != null)
{
awake = new MethodDefinition("Awake",
MethodAttributes.Family | MethodAttributes.HideBySig | MethodAttributes.Virtual,
module.TypeSystem.Void);
var il = awake.Body.GetILProcessor();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Call, baseAwakeRef);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Call, autoRegisterMethodRef);
il.Emit(OpCodes.Ret);
type.Methods.Add(awake);
return;
}
awake = new MethodDefinition("Awake",
MethodAttributes.Private | MethodAttributes.HideBySig,
module.TypeSystem.Void);
var retIl = awake.Body.GetILProcessor();
retIl.Emit(OpCodes.Ret);
type.Methods.Add(awake);
}
var processor = awake.Body.GetILProcessor();
var instructions = new List<Instruction>
{
processor.Create(OpCodes.Ldarg_0),
processor.Create(OpCodes.Call, autoRegisterMethodRef),
processor.Create(OpCodes.Nop)
};
instructions.Reverse();
instructions.ForEach(instruction => processor.Body.Instructions.Insert(0, instruction));
}
private static void InjectAutoUnregister(TypeDefinition type, ModuleDefinition module, MethodReference unregisterMethodRef)
{
var onDestroy = type.Methods.FirstOrDefault(method => method.Name == "OnDestroy" && !method.IsStatic);
if (onDestroy == null)
{
var baseOnDestroyRef = FindBaseMethodReference(type, "OnDestroy", module);
if (baseOnDestroyRef != null)
{
onDestroy = new MethodDefinition("OnDestroy",
MethodAttributes.Family | MethodAttributes.HideBySig | MethodAttributes.Virtual,
module.TypeSystem.Void);
var il = onDestroy.Body.GetILProcessor();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Call, unregisterMethodRef);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Call, baseOnDestroyRef);
il.Emit(OpCodes.Ret);
type.Methods.Add(onDestroy);
return;
}
onDestroy = new MethodDefinition("OnDestroy",
MethodAttributes.Private | MethodAttributes.HideBySig,
module.TypeSystem.Void);
var retIl = onDestroy.Body.GetILProcessor();
retIl.Emit(OpCodes.Ret);
type.Methods.Add(onDestroy);
}
var processor = onDestroy.Body.GetILProcessor();
var instructions = new List<Instruction>
{
processor.Create(OpCodes.Ldarg_0),
processor.Create(OpCodes.Call, unregisterMethodRef),
processor.Create(OpCodes.Nop)
};
instructions.Reverse();
instructions.ForEach(instruction => processor.Body.Instructions.Insert(0, instruction));
}
private static MethodReference? FindBaseMethodReference(TypeDefinition type, string methodName, ModuleDefinition module)
{
try
{
var baseTypeRef = type.BaseType;
while (baseTypeRef != null)
{
var baseTypeDef = baseTypeRef.Resolve();
if (baseTypeDef == null)
break;
var method = baseTypeDef.Methods.FirstOrDefault(candidate =>
candidate.Name == methodName && !candidate.IsStatic && candidate.IsVirtual);
if (method != null)
{
if (baseTypeRef is GenericInstanceType genericInstance)
{
var methodRef = new MethodReference(
method.Name,
module.ImportReference(method.ReturnType),
module.ImportReference(genericInstance))
{
HasThis = method.HasThis,
ExplicitThis = method.ExplicitThis,
CallingConvention = method.CallingConvention
};
return methodRef;
}
return module.ImportReference(method);
}
baseTypeRef = baseTypeDef.BaseType;
}
}
catch
{
}
return null;
}
private static TypeReference? FindType(ModuleDefinition module, string fullName, string assemblyName)
{
var resolved = Type.GetType($"{fullName}, {assemblyName}", false);
return resolved == null ? null : module.ImportReference(resolved);
}
private static MethodReference? FindTypeArrayConstructor(ModuleDefinition module, string fullName, string assemblyName)
{
var typeRef = FindType(module, fullName, assemblyName);
var typeDef = typeRef?.Resolve();
var ctor = typeDef?.Methods.FirstOrDefault(method =>
method.IsConstructor &&
method.Parameters.Count == 1 &&
method.Parameters[0].ParameterType.IsArray &&
method.Parameters[0].ParameterType.GetElementType().FullName == module.ImportReference(typeof(Type)).FullName);
return ctor == null ? null : module.ImportReference(ctor);
}
private static void AddAssemblyTypeArrayAttribute(ModuleDefinition module, MethodReference ctor, TypeReference[] types)
{
var attribute = new CustomAttribute(ctor);
var typeTypeRef = module.ImportReference(typeof(Type));
attribute.ConstructorArguments.Add(new CustomAttributeArgument(
module.ImportReference(typeof(Type[])),
types.Select(type => new CustomAttributeArgument(typeTypeRef, type)).ToArray()));
module.Assembly.CustomAttributes.Add(attribute);
}
private static AssemblyDefinition AssemblyDefinitionFor(ICompiledAssembly compiledAssembly)
{
var assemblyResolver = new PostProcessorAssemblyResolver(compiledAssembly);
var readerParameters = new ReaderParameters
{
SymbolStream = new MemoryStream(compiledAssembly.InMemoryAssembly.PdbData.ToArray()),
SymbolReaderProvider = new PdbReaderProvider(),
AssemblyResolver = assemblyResolver,
ReflectionImporterProvider = new PostProcessorReflectionImporterProvider(),
ReadingMode = ReadingMode.Immediate
};
var assemblyDefinition = AssemblyDefinition.ReadAssembly(
new MemoryStream(compiledAssembly.InMemoryAssembly.PeData.ToArray()),
readerParameters);
assemblyResolver.AddAssemblyDefinitionBeingOperatedOn(assemblyDefinition);
return assemblyDefinition;
}
private static ILPostProcessResult GetResult(AssemblyDefinition assemblyDefinition, List<DiagnosticMessage> diagnostics)
{
var pe = new MemoryStream();
var pdb = new MemoryStream();
assemblyDefinition.Write(pe, new WriterParameters
{
SymbolWriterProvider = new PdbWriterProvider(),
SymbolStream = pdb,
WriteSymbols = true
});
return new ILPostProcessResult(new InMemoryAssembly(pe.ToArray(), pdb.ToArray()), diagnostics);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5f611b284dae01a46bc990b87b8bca90
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,107 @@
#nullable enable
using System.Collections.Generic;
using System.IO;
using System.Threading;
using Mono.Cecil;
using Unity.CompilationPipeline.Common.ILPostProcessing;
namespace ShrinkEventBus.CodeGen
{
internal sealed class PostProcessorAssemblyResolver : IAssemblyResolver
{
private readonly string[] _references;
private readonly Dictionary<string, AssemblyDefinition> _cache = new();
private AssemblyDefinition? _self;
public PostProcessorAssemblyResolver(ICompiledAssembly compiledAssembly)
{
_references = compiledAssembly.References;
}
public void AddAssemblyDefinitionBeingOperatedOn(AssemblyDefinition assemblyDefinition)
{
_self = assemblyDefinition;
}
public AssemblyDefinition? Resolve(AssemblyNameReference name)
{
return Resolve(name, new ReaderParameters(ReadingMode.Deferred));
}
public AssemblyDefinition? Resolve(AssemblyNameReference name, ReaderParameters parameters)
{
lock (_cache)
{
if (name.Name == _self?.Name.Name)
return _self;
var path = FindPath(name);
if (path == null)
return null;
var key = $"{path}{File.GetLastWriteTime(path)}";
if (_cache.TryGetValue(key, out var cached))
return cached;
parameters.AssemblyResolver = this;
var ms = ReadFileWithRetry(path);
var pdbPath = Path.ChangeExtension(path, ".pdb");
if (File.Exists(pdbPath))
parameters.SymbolStream = ReadFileWithRetry(pdbPath);
var assembly = AssemblyDefinition.ReadAssembly(ms, parameters);
_cache[key] = assembly;
return assembly;
}
}
private string? FindPath(AssemblyNameReference name)
{
foreach (var reference in _references)
{
if (Path.GetFileNameWithoutExtension(reference) == name.Name)
return reference;
}
var dirs = new HashSet<string>();
foreach (var reference in _references)
{
var dir = Path.GetDirectoryName(reference);
if (dir != null)
dirs.Add(dir);
}
foreach (var dir in dirs)
{
var candidate = Path.Combine(dir, $"{name.Name}.dll");
if (File.Exists(candidate))
return candidate;
}
return null;
}
private static MemoryStream ReadFileWithRetry(string path, int retries = 5)
{
for (var i = 0; i < retries; i++)
{
try
{
return new MemoryStream(File.ReadAllBytes(path));
}
catch (IOException) when (i < retries - 1)
{
Thread.Sleep(100);
}
}
throw new IOException($"[ShrinkEventBus.CodeGen] 无法读取文件: {path}");
}
public void Dispose()
{
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: aeea50f82bab4ee2833c671d801c95e3
timeCreated: 1772908894
@@ -0,0 +1,37 @@
#nullable enable
using System.Linq;
using System.Reflection;
using Mono.Cecil;
namespace ShrinkEventBus.CodeGen
{
internal sealed class PostProcessorReflectionImporter : DefaultReflectionImporter
{
private const string CoreLibName = "System.Private.CoreLib";
private readonly AssemblyNameReference? _corlib;
public PostProcessorReflectionImporter(ModuleDefinition module)
: base(module)
{
_corlib = module.AssemblyReferences.FirstOrDefault(
assembly => assembly.Name is "mscorlib" or "netstandard");
}
public override AssemblyNameReference ImportReference(AssemblyName reference)
{
if (_corlib != null && reference.Name == CoreLibName)
return _corlib;
return base.ImportReference(reference);
}
}
internal sealed class PostProcessorReflectionImporterProvider : IReflectionImporterProvider
{
public IReflectionImporter GetReflectionImporter(ModuleDefinition module)
{
return new PostProcessorReflectionImporter(module);
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0a6c2401798b42f0bbcde72796d2fa9c
timeCreated: 1772908906
@@ -0,0 +1,23 @@
{
"name": "Unity.ShrinkEventBus.CodeGen",
"rootNamespace": "ShrinkEventBus.CodeGen",
"references": [
"ShrinkEventBus.Runtime"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": true,
"overrideReferences": true,
"precompiledReferences": [
"Mono.Cecil.dll",
"Mono.Cecil.Mdb.dll",
"Mono.Cecil.Pdb.dll",
"Mono.Cecil.Rocks.dll"
],
"autoReferenced": false,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 91161d4c2ae3ae74c9184ba33d9aa52e
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant: