feat(codegen): validate contracts and unify Unity weaving
Publish UPM package / publish (push) Successful in 2s
Publish NuGet packages / publish (push) Successful in 2m44s

This commit is contained in:
2026-09-29 10:14:49 +08:00
parent ab6617eda4
commit c669ee5956
11 changed files with 148 additions and 18 deletions
+18 -7
View File
@@ -57,6 +57,14 @@ public static class ShrinkAssemblyWeaver
var module = assembly.MainModule;
var marker = FindType(module, "ShrinkSDK.Runtime.ShrinkCodeGenWovenAttribute", "ShrinkRuntime.Abstractions");
// Unity passes transitive reference paths even when C# emitted no direct reference.
// All Unity postprocessors must see the same marker before another pass runs.
if (marker == null)
{
var runtime = resolver.Resolve(new AssemblyNameReference("ShrinkRuntime.Abstractions", new System.Version(0, 0, 0, 0)));
var definition = runtime?.MainModule.GetType("ShrinkSDK.Runtime.ShrinkCodeGenWovenAttribute");
if (definition != null) marker = module.ImportReference(definition);
}
var existingMarker = marker == null ? null : assembly.CustomAttributes.FirstOrDefault(a => a.AttributeType.FullName == marker.FullName);
if (existingMarker != null)
{
@@ -140,14 +148,13 @@ public static class ShrinkAssemblyWeaver
if (type.Fields.Any(field => field.Name == bindingFieldName))
throw new InvalidOperationException($"Reserved generated field already exists on {type.FullName}: {bindingFieldName}.");
var disposableType = module.ImportReference(typeof(IDisposable));
var disposableType = new TypeReference("System", "IDisposable", module, module.TypeSystem.CoreLibrary);
var bindingField = new FieldDefinition(bindingFieldName, FieldAttributes.Private, disposableType);
type.Fields.Add(bindingField);
var eventBusType = RequireType(module, "ShrinkEventBus.EventBus", EventRuntime);
var attachMethod = ImportMethod(module, eventBusType,
method => method.Name == "Attach" && method.IsStatic && method.Parameters.Count == 2);
var disposeMethod = module.ImportReference(typeof(IDisposable).GetMethod(nameof(IDisposable.Dispose))
?? throw new InvalidOperationException("IDisposable.Dispose was not found."));
var disposeMethod = new MethodReference("Dispose", module.TypeSystem.Void, disposableType) { HasThis = true };
InjectAwake(type, module, bindingField, attachMethod);
InjectOnDestroy(type, module, bindingField, disposeMethod);
}
@@ -638,10 +645,14 @@ public static class ShrinkAssemblyWeaver
{
var opcode = Convert.ToInt32(attribute.ConstructorArguments[0].Value);
var route = attribute.ConstructorArguments.Count > 1 ? attribute.ConstructorArguments[1].Value as string ?? string.Empty : string.Empty;
var key = $"{opcode}:{route}";
if (seen.TryGetValue(key, out var previous))
throw new InvalidOperationException($"Duplicate network opcode/route {key}: {previous} and {type.FullName}.");
seen[key] = type.FullName;
Check("opcode:" + opcode);
if (!string.IsNullOrWhiteSpace(route)) Check("route:" + route.Trim());
void Check(string key)
{
if (seen.TryGetValue(key, out var previous))
throw new InvalidOperationException($"SHRINK002 Duplicate {key}: {previous} and {type.FullName}. Assign unique opcodes and nonempty routes.");
seen[key] = type.FullName;
}
}
}