Per-type metadata control for binary serialization
Adds EnableMetadataFeature to [AcBinarySerializable], allowing types to opt out of inline metadata even when global UseMetadata is enabled. Source generator, serializer, and deserializer now respect this flag for child and element types. Default UseMetadata is set to true. Enables fine-grained control over serialization overhead and compatibility.
This commit is contained in:
parent
dcd9783b3b
commit
3e935cad2f
|
|
@ -47,6 +47,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
||||||
var enableIdTracking = true;
|
var enableIdTracking = true;
|
||||||
var enableRefHandling = true;
|
var enableRefHandling = true;
|
||||||
var enableInternString = true;
|
var enableInternString = true;
|
||||||
|
var enableMetadata = true;
|
||||||
var binarySerializableAttr = typeSymbol.GetAttributes().FirstOrDefault(a =>
|
var binarySerializableAttr = typeSymbol.GetAttributes().FirstOrDefault(a =>
|
||||||
a.AttributeClass?.ToDisplayString() == AttributeName);
|
a.AttributeClass?.ToDisplayString() == AttributeName);
|
||||||
if (binarySerializableAttr != null)
|
if (binarySerializableAttr != null)
|
||||||
|
|
@ -58,10 +59,12 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
||||||
enableIdTracking = all;
|
enableIdTracking = all;
|
||||||
enableRefHandling = all;
|
enableRefHandling = all;
|
||||||
enableInternString = all;
|
enableInternString = all;
|
||||||
|
enableMetadata = all;
|
||||||
}
|
}
|
||||||
else if (binarySerializableAttr.ConstructorArguments.Length == 4)
|
else if (binarySerializableAttr.ConstructorArguments.Length == 4)
|
||||||
{
|
{
|
||||||
// Four bool ctor: (metadata, idTracking, refHandling, internString)
|
// Four bool ctor: (metadata, idTracking, refHandling, internString)
|
||||||
|
enableMetadata = (bool)binarySerializableAttr.ConstructorArguments[0].Value!;
|
||||||
enableIdTracking = (bool)binarySerializableAttr.ConstructorArguments[1].Value!;
|
enableIdTracking = (bool)binarySerializableAttr.ConstructorArguments[1].Value!;
|
||||||
enableRefHandling = (bool)binarySerializableAttr.ConstructorArguments[2].Value!;
|
enableRefHandling = (bool)binarySerializableAttr.ConstructorArguments[2].Value!;
|
||||||
enableInternString = (bool)binarySerializableAttr.ConstructorArguments[3].Value!;
|
enableInternString = (bool)binarySerializableAttr.ConstructorArguments[3].Value!;
|
||||||
|
|
@ -110,6 +113,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
||||||
var kind = GetKind(p.Type);
|
var kind = GetKind(p.Type);
|
||||||
bool hasGenWriter = false;
|
bool hasGenWriter = false;
|
||||||
bool propTypeIsIId = false;
|
bool propTypeIsIId = false;
|
||||||
|
bool propEnableMetadata = true;
|
||||||
string? writerClassName = null;
|
string? writerClassName = null;
|
||||||
string? propIdTypeName = null;
|
string? propIdTypeName = null;
|
||||||
int childTypeNameHash = 0;
|
int childTypeNameHash = 0;
|
||||||
|
|
@ -127,6 +131,8 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
||||||
|
|
||||||
if (hasGenWriter)
|
if (hasGenWriter)
|
||||||
{
|
{
|
||||||
|
// Read child type's EnableMetadataFeature
|
||||||
|
propEnableMetadata = ReadEnableMetadata(resolvedType);
|
||||||
var iidIface = resolvedType.AllInterfaces.FirstOrDefault(i =>
|
var iidIface = resolvedType.AllInterfaces.FirstOrDefault(i =>
|
||||||
i.IsGenericType &&
|
i.IsGenericType &&
|
||||||
i.OriginalDefinition.ToDisplayString() == "AyCode.Core.Interfaces.IId<T>");
|
i.OriginalDefinition.ToDisplayString() == "AyCode.Core.Interfaces.IId<T>");
|
||||||
|
|
@ -153,6 +159,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
||||||
PropertyTypeKind elemKind = PropertyTypeKind.Unknown;
|
PropertyTypeKind elemKind = PropertyTypeKind.Unknown;
|
||||||
bool elemHasGenWriter = false;
|
bool elemHasGenWriter = false;
|
||||||
bool elemIsIId = false;
|
bool elemIsIId = false;
|
||||||
|
bool elemEnableMetadata = true;
|
||||||
string? elemWriterClassName = null;
|
string? elemWriterClassName = null;
|
||||||
string? elemIdTypeName = null;
|
string? elemIdTypeName = null;
|
||||||
string? collKind = null;
|
string? collKind = null;
|
||||||
|
|
@ -197,6 +204,8 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
||||||
a.AttributeClass?.ToDisplayString() == AttributeName);
|
a.AttributeClass?.ToDisplayString() == AttributeName);
|
||||||
if (elemHasGenWriter)
|
if (elemHasGenWriter)
|
||||||
{
|
{
|
||||||
|
// Read element type's EnableMetadataFeature
|
||||||
|
elemEnableMetadata = ReadEnableMetadata(resolvedElem);
|
||||||
var elemIidIface = resolvedElem.AllInterfaces.FirstOrDefault(ifc =>
|
var elemIidIface = resolvedElem.AllInterfaces.FirstOrDefault(ifc =>
|
||||||
ifc.IsGenericType &&
|
ifc.IsGenericType &&
|
||||||
ifc.OriginalDefinition.ToDisplayString() == "AyCode.Core.Interfaces.IId<T>");
|
ifc.OriginalDefinition.ToDisplayString() == "AyCode.Core.Interfaces.IId<T>");
|
||||||
|
|
@ -228,7 +237,8 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
||||||
stringInternAttr, hasGenWriter, propTypeIsIId, writerClassName, propIdTypeName,
|
stringInternAttr, hasGenWriter, propTypeIsIId, writerClassName, propIdTypeName,
|
||||||
elemKind, elemHasGenWriter, elemIsIId, elemWriterClassName, elemIdTypeName, collKind, elemFullTypeName,
|
elemKind, elemHasGenWriter, elemIsIId, elemWriterClassName, elemIdTypeName, collKind, elemFullTypeName,
|
||||||
childTypeNameHash, childPropertyHashes,
|
childTypeNameHash, childPropertyHashes,
|
||||||
elementTypeNameHash, elementPropertyHashes));
|
elementTypeNameHash, elementPropertyHashes,
|
||||||
|
propEnableMetadata, elemEnableMetadata));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -253,7 +263,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
||||||
var className = BuildFlatName(typeSymbol);
|
var className = BuildFlatName(typeSymbol);
|
||||||
var typeNameHash = ComputeFnvHash(typeSymbol.Name);
|
var typeNameHash = ComputeFnvHash(typeSymbol.Name);
|
||||||
var propertyNameHashes = properties.Select(prop => ComputeFnvHash(prop.Name)).ToArray();
|
var propertyNameHashes = properties.Select(prop => ComputeFnvHash(prop.Name)).ToArray();
|
||||||
return new SerializableClassInfo(namespaceName, className, typeSymbol.ToDisplayString(), properties, isIId, idTypeName, enableRefHandling, typeNameHash, propertyNameHashes);
|
return new SerializableClassInfo(namespaceName, className, typeSymbol.ToDisplayString(), properties, isIId, idTypeName, enableRefHandling, typeNameHash, propertyNameHashes, enableMetadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void Execute(ImmutableArray<SerializableClassInfo?> classes, SourceProductionContext context)
|
private static void Execute(ImmutableArray<SerializableClassInfo?> classes, SourceProductionContext context)
|
||||||
|
|
@ -299,7 +309,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
||||||
foreach (var p in ci.Properties)
|
foreach (var p in ci.Properties)
|
||||||
{
|
{
|
||||||
sb.AppendLine();
|
sb.AppendLine();
|
||||||
EmitProp(sb, p, " ", ci.FullTypeName);
|
EmitProp(sb, p, " ", ci.FullTypeName, ci.EnableMetadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
sb.AppendLine(" }");
|
sb.AppendLine(" }");
|
||||||
|
|
@ -406,25 +416,33 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
||||||
sb.AppendLine(" }");
|
sb.AppendLine(" }");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void EmitProp(StringBuilder sb, PropInfo p, string i, string fullTypeName)
|
private static void EmitProp(StringBuilder sb, PropInfo p, string i, string fullTypeName, bool enableMetadata)
|
||||||
{
|
{
|
||||||
var a = $"obj.{p.Name}";
|
var a = $"obj.{p.Name}";
|
||||||
|
|
||||||
// Markerless types: write raw value only, no type marker, no PropertySkip
|
// Markerless types: write raw value only, no type marker, no PropertySkip
|
||||||
// Matches runtime WritePropertyMarkerless — these have ExpectedTypeCode
|
// Matches runtime WritePropertyMarkerless — these have ExpectedTypeCode
|
||||||
// NEVER filtered (runtime doesn't filter markerless properties either)
|
// NEVER filtered (runtime doesn't filter markerless properties either)
|
||||||
// UseMetadata=true: markerless path NOT available — must use markered path (EmitSkip)
|
// When EnableMetadataFeature=false: always markerless (no UseMetadata branch needed)
|
||||||
// to match runtime WritePropertyOrSkip behavior (every property gets a type marker byte)
|
// When EnableMetadataFeature=true: UseMetadata=true uses markered path (EmitSkip)
|
||||||
if (IsMarkerless(p.TypeKind))
|
if (IsMarkerless(p.TypeKind))
|
||||||
{
|
{
|
||||||
sb.AppendLine($"{i}if (context.UseMetadata)");
|
if (!enableMetadata)
|
||||||
sb.AppendLine($"{i}{{");
|
{
|
||||||
EmitSkip(sb, p.TypeKind, a, p.TypeNameForTypeof, i + " ");
|
// Per-type metadata disabled — always markerless, no branch
|
||||||
sb.AppendLine($"{i}}}");
|
EmitMarkerless(sb, p.TypeKind, a, i);
|
||||||
sb.AppendLine($"{i}else");
|
}
|
||||||
sb.AppendLine($"{i}{{");
|
else
|
||||||
EmitMarkerless(sb, p.TypeKind, a, i + " ");
|
{
|
||||||
sb.AppendLine($"{i}}}");
|
sb.AppendLine($"{i}if (context.UseMetadata)");
|
||||||
|
sb.AppendLine($"{i}{{");
|
||||||
|
EmitSkip(sb, p.TypeKind, a, p.TypeNameForTypeof, i + " ");
|
||||||
|
sb.AppendLine($"{i}}}");
|
||||||
|
sb.AppendLine($"{i}else");
|
||||||
|
sb.AppendLine($"{i}{{");
|
||||||
|
EmitMarkerless(sb, p.TypeKind, a, i + " ");
|
||||||
|
sb.AppendLine($"{i}}}");
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -759,49 +777,79 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
||||||
sb.AppendLine($"{i} else");
|
sb.AppendLine($"{i} else");
|
||||||
sb.AppendLine($"{i} {{");
|
sb.AppendLine($"{i} {{");
|
||||||
|
|
||||||
// UseMetadata: register type for first/repeated tracking via slot (no GetWrapper needed)
|
if (!p.ChildEnableMetadata)
|
||||||
sb.AppendLine($"{i} var isFirstMeta_{p.Name} = context.UseMetadata && context.RegisterMetadataTypeBySlot({writer}.s_metadataSlot);");
|
{
|
||||||
|
// Child type has EnableMetadataFeature=false — no metadata, always Object marker
|
||||||
|
// Inline ref tracking still needed for IId/All mode
|
||||||
|
var refGuard = p.IsIId
|
||||||
|
? "context.ReferenceHandling != ReferenceHandlingMode.None"
|
||||||
|
: "context.ReferenceHandling == ReferenceHandlingMode.All";
|
||||||
|
sb.AppendLine($"{i} if ({refGuard} && context.TryConsumeWritePlanEntry(out var pe_{p.Name}))");
|
||||||
|
sb.AppendLine($"{i} {{");
|
||||||
|
sb.AppendLine($"{i} if (!pe_{p.Name}.IsFirst)");
|
||||||
|
sb.AppendLine($"{i} {{");
|
||||||
|
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectRef);");
|
||||||
|
sb.AppendLine($"{i} context.WriteVarUInt((uint)pe_{p.Name}.CacheMapIndex);");
|
||||||
|
sb.AppendLine($"{i} }}");
|
||||||
|
sb.AppendLine($"{i} else");
|
||||||
|
sb.AppendLine($"{i} {{");
|
||||||
|
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectRefFirst);");
|
||||||
|
sb.AppendLine($"{i} context.WriteVarUInt((uint)pe_{p.Name}.CacheMapIndex);");
|
||||||
|
sb.AppendLine($"{i} {writer}.Instance.WriteProperties({a}, context, {nextDepth});");
|
||||||
|
sb.AppendLine($"{i} }}");
|
||||||
|
sb.AppendLine($"{i} }}");
|
||||||
|
sb.AppendLine($"{i} else");
|
||||||
|
sb.AppendLine($"{i} {{");
|
||||||
|
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.Object);");
|
||||||
|
sb.AppendLine($"{i} {writer}.Instance.WriteProperties({a}, context, {nextDepth});");
|
||||||
|
sb.AppendLine($"{i} }}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// UseMetadata: register type for first/repeated tracking via slot (no GetWrapper needed)
|
||||||
|
sb.AppendLine($"{i} var isFirstMeta_{p.Name} = context.UseMetadata && context.RegisterMetadataTypeBySlot({writer}.s_metadataSlot);");
|
||||||
|
|
||||||
// Inline ref tracking: guard depends on IId vs non-IId to match scan pass behavior.
|
// Inline ref tracking: guard depends on IId vs non-IId to match scan pass behavior.
|
||||||
var refGuard = p.IsIId
|
var refGuard = p.IsIId
|
||||||
? "context.ReferenceHandling != ReferenceHandlingMode.None"
|
? "context.ReferenceHandling != ReferenceHandlingMode.None"
|
||||||
: "context.ReferenceHandling == ReferenceHandlingMode.All";
|
: "context.ReferenceHandling == ReferenceHandlingMode.All";
|
||||||
sb.AppendLine($"{i} if ({refGuard} && context.TryConsumeWritePlanEntry(out var pe_{p.Name}))");
|
sb.AppendLine($"{i} if ({refGuard} && context.TryConsumeWritePlanEntry(out var pe_{p.Name}))");
|
||||||
sb.AppendLine($"{i} {{");
|
sb.AppendLine($"{i} {{");
|
||||||
sb.AppendLine($"{i} if (!pe_{p.Name}.IsFirst)");
|
sb.AppendLine($"{i} if (!pe_{p.Name}.IsFirst)");
|
||||||
sb.AppendLine($"{i} {{");
|
sb.AppendLine($"{i} {{");
|
||||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectRef);");
|
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectRef);");
|
||||||
sb.AppendLine($"{i} context.WriteVarUInt((uint)pe_{p.Name}.CacheMapIndex);");
|
sb.AppendLine($"{i} context.WriteVarUInt((uint)pe_{p.Name}.CacheMapIndex);");
|
||||||
sb.AppendLine($"{i} }}");
|
sb.AppendLine($"{i} }}");
|
||||||
sb.AppendLine($"{i} else");
|
sb.AppendLine($"{i} else");
|
||||||
sb.AppendLine($"{i} {{");
|
sb.AppendLine($"{i} {{");
|
||||||
// RefFirst: UseMetadata → ObjectWithMetadataRefFirst + metadata, else → ObjectRefFirst
|
// RefFirst: UseMetadata → ObjectWithMetadataRefFirst + metadata, else → ObjectRefFirst
|
||||||
sb.AppendLine($"{i} if (context.UseMetadata)");
|
sb.AppendLine($"{i} if (context.UseMetadata)");
|
||||||
sb.AppendLine($"{i} {{");
|
sb.AppendLine($"{i} {{");
|
||||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectWithMetadataRefFirst);");
|
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectWithMetadataRefFirst);");
|
||||||
sb.AppendLine($"{i} context.WriteVarUInt((uint)pe_{p.Name}.CacheMapIndex);");
|
sb.AppendLine($"{i} context.WriteVarUInt((uint)pe_{p.Name}.CacheMapIndex);");
|
||||||
EmitInlineMetadata(sb, p.ChildTypeNameHash, p.ChildPropertyHashes!, $"isFirstMeta_{p.Name}", i + " ");
|
EmitInlineMetadata(sb, p.ChildTypeNameHash, p.ChildPropertyHashes!, $"isFirstMeta_{p.Name}", i + " ");
|
||||||
sb.AppendLine($"{i} }}");
|
sb.AppendLine($"{i} }}");
|
||||||
sb.AppendLine($"{i} else");
|
sb.AppendLine($"{i} else");
|
||||||
sb.AppendLine($"{i} {{");
|
sb.AppendLine($"{i} {{");
|
||||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectRefFirst);");
|
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectRefFirst);");
|
||||||
sb.AppendLine($"{i} context.WriteVarUInt((uint)pe_{p.Name}.CacheMapIndex);");
|
sb.AppendLine($"{i} context.WriteVarUInt((uint)pe_{p.Name}.CacheMapIndex);");
|
||||||
sb.AppendLine($"{i} }}");
|
sb.AppendLine($"{i} }}");
|
||||||
sb.AppendLine($"{i} {writer}.Instance.WriteProperties({a}, context, {nextDepth});");
|
sb.AppendLine($"{i} {writer}.Instance.WriteProperties({a}, context, {nextDepth});");
|
||||||
sb.AppendLine($"{i} }}");
|
sb.AppendLine($"{i} }}");
|
||||||
sb.AppendLine($"{i} }}");
|
sb.AppendLine($"{i} }}");
|
||||||
sb.AppendLine($"{i} else");
|
sb.AppendLine($"{i} else");
|
||||||
sb.AppendLine($"{i} {{");
|
sb.AppendLine($"{i} {{");
|
||||||
// No ref tracking: UseMetadata → ObjectWithMetadata + metadata, else → Object
|
// No ref tracking: UseMetadata → ObjectWithMetadata + metadata, else → Object
|
||||||
sb.AppendLine($"{i} if (context.UseMetadata)");
|
sb.AppendLine($"{i} if (context.UseMetadata)");
|
||||||
sb.AppendLine($"{i} {{");
|
sb.AppendLine($"{i} {{");
|
||||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectWithMetadata);");
|
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectWithMetadata);");
|
||||||
EmitInlineMetadata(sb, p.ChildTypeNameHash, p.ChildPropertyHashes!, $"isFirstMeta_{p.Name}", i + " ");
|
EmitInlineMetadata(sb, p.ChildTypeNameHash, p.ChildPropertyHashes!, $"isFirstMeta_{p.Name}", i + " ");
|
||||||
sb.AppendLine($"{i} }}");
|
sb.AppendLine($"{i} }}");
|
||||||
sb.AppendLine($"{i} else");
|
sb.AppendLine($"{i} else");
|
||||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.Object);");
|
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.Object);");
|
||||||
sb.AppendLine($"{i} {writer}.Instance.WriteProperties({a}, context, {nextDepth});");
|
sb.AppendLine($"{i} {writer}.Instance.WriteProperties({a}, context, {nextDepth});");
|
||||||
sb.AppendLine($"{i} }}");
|
sb.AppendLine($"{i} }}");
|
||||||
|
}
|
||||||
|
|
||||||
sb.AppendLine($"{i} }}");
|
sb.AppendLine($"{i} }}");
|
||||||
sb.AppendLine($"{i}}}");
|
sb.AppendLine($"{i}}}");
|
||||||
|
|
@ -878,49 +926,76 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
||||||
sb.AppendLine($"{i} if ({e} == null) {{ context.WriteByte(BinaryTypeCode.Null); continue; }}");
|
sb.AppendLine($"{i} if ({e} == null) {{ context.WriteByte(BinaryTypeCode.Null); continue; }}");
|
||||||
sb.AppendLine($"{i} if (nextDepth_{p.Name} > context.MaxDepth) {{ context.WriteByte(BinaryTypeCode.Null); continue; }}");
|
sb.AppendLine($"{i} if (nextDepth_{p.Name} > context.MaxDepth) {{ context.WriteByte(BinaryTypeCode.Null); continue; }}");
|
||||||
|
|
||||||
// UseMetadata: register element type for first/repeated tracking
|
|
||||||
sb.AppendLine($"{i} var isFirstMeta_e_{p.Name} = context.UseMetadata && context.RegisterMetadataTypeBySlot({writer}.s_metadataSlot);");
|
|
||||||
|
|
||||||
// Inline ref tracking
|
// Inline ref tracking
|
||||||
var elemRefGuard = p.ElementIsIId
|
var elemRefGuard = p.ElementIsIId
|
||||||
? "context.ReferenceHandling != ReferenceHandlingMode.None"
|
? "context.ReferenceHandling != ReferenceHandlingMode.None"
|
||||||
: "context.ReferenceHandling == ReferenceHandlingMode.All";
|
: "context.ReferenceHandling == ReferenceHandlingMode.All";
|
||||||
sb.AppendLine($"{i} if ({elemRefGuard} && context.TryConsumeWritePlanEntry(out var epe_{p.Name}))");
|
|
||||||
sb.AppendLine($"{i} {{");
|
if (!p.ElementEnableMetadata)
|
||||||
sb.AppendLine($"{i} if (!epe_{p.Name}.IsFirst)");
|
{
|
||||||
sb.AppendLine($"{i} {{");
|
// Element type has EnableMetadataFeature=false — no metadata, always Object marker
|
||||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectRef);");
|
sb.AppendLine($"{i} if ({elemRefGuard} && context.TryConsumeWritePlanEntry(out var epe_{p.Name}))");
|
||||||
sb.AppendLine($"{i} context.WriteVarUInt((uint)epe_{p.Name}.CacheMapIndex);");
|
sb.AppendLine($"{i} {{");
|
||||||
sb.AppendLine($"{i} }}");
|
sb.AppendLine($"{i} if (!epe_{p.Name}.IsFirst)");
|
||||||
sb.AppendLine($"{i} else");
|
sb.AppendLine($"{i} {{");
|
||||||
sb.AppendLine($"{i} {{");
|
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectRef);");
|
||||||
// RefFirst: UseMetadata → ObjectWithMetadataRefFirst + metadata, else → ObjectRefFirst
|
sb.AppendLine($"{i} context.WriteVarUInt((uint)epe_{p.Name}.CacheMapIndex);");
|
||||||
sb.AppendLine($"{i} if (context.UseMetadata)");
|
sb.AppendLine($"{i} }}");
|
||||||
sb.AppendLine($"{i} {{");
|
sb.AppendLine($"{i} else");
|
||||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectWithMetadataRefFirst);");
|
sb.AppendLine($"{i} {{");
|
||||||
sb.AppendLine($"{i} context.WriteVarUInt((uint)epe_{p.Name}.CacheMapIndex);");
|
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectRefFirst);");
|
||||||
EmitInlineMetadata(sb, p.ElementTypeNameHash, p.ElementPropertyHashes!, $"isFirstMeta_e_{p.Name}", i + " ");
|
sb.AppendLine($"{i} context.WriteVarUInt((uint)epe_{p.Name}.CacheMapIndex);");
|
||||||
sb.AppendLine($"{i} }}");
|
sb.AppendLine($"{i} {writer}.Instance.WriteProperties({e}, context, nextDepth_{p.Name});");
|
||||||
sb.AppendLine($"{i} else");
|
sb.AppendLine($"{i} }}");
|
||||||
sb.AppendLine($"{i} {{");
|
sb.AppendLine($"{i} }}");
|
||||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectRefFirst);");
|
sb.AppendLine($"{i} else");
|
||||||
sb.AppendLine($"{i} context.WriteVarUInt((uint)epe_{p.Name}.CacheMapIndex);");
|
sb.AppendLine($"{i} {{");
|
||||||
sb.AppendLine($"{i} }}");
|
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.Object);");
|
||||||
sb.AppendLine($"{i} {writer}.Instance.WriteProperties({e}, context, nextDepth_{p.Name});");
|
sb.AppendLine($"{i} {writer}.Instance.WriteProperties({e}, context, nextDepth_{p.Name});");
|
||||||
sb.AppendLine($"{i} }}");
|
sb.AppendLine($"{i} }}");
|
||||||
sb.AppendLine($"{i} }}");
|
}
|
||||||
sb.AppendLine($"{i} else");
|
else
|
||||||
sb.AppendLine($"{i} {{");
|
{
|
||||||
// No ref tracking: UseMetadata → ObjectWithMetadata + metadata, else → Object
|
// UseMetadata: register element type for first/repeated tracking
|
||||||
sb.AppendLine($"{i} if (context.UseMetadata)");
|
sb.AppendLine($"{i} var isFirstMeta_e_{p.Name} = context.UseMetadata && context.RegisterMetadataTypeBySlot({writer}.s_metadataSlot);");
|
||||||
sb.AppendLine($"{i} {{");
|
|
||||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectWithMetadata);");
|
sb.AppendLine($"{i} if ({elemRefGuard} && context.TryConsumeWritePlanEntry(out var epe_{p.Name}))");
|
||||||
EmitInlineMetadata(sb, p.ElementTypeNameHash, p.ElementPropertyHashes!, $"isFirstMeta_e_{p.Name}", i + " ");
|
sb.AppendLine($"{i} {{");
|
||||||
sb.AppendLine($"{i} }}");
|
sb.AppendLine($"{i} if (!epe_{p.Name}.IsFirst)");
|
||||||
sb.AppendLine($"{i} else");
|
sb.AppendLine($"{i} {{");
|
||||||
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.Object);");
|
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectRef);");
|
||||||
sb.AppendLine($"{i} {writer}.Instance.WriteProperties({e}, context, nextDepth_{p.Name});");
|
sb.AppendLine($"{i} context.WriteVarUInt((uint)epe_{p.Name}.CacheMapIndex);");
|
||||||
sb.AppendLine($"{i} }}");
|
sb.AppendLine($"{i} }}");
|
||||||
|
sb.AppendLine($"{i} else");
|
||||||
|
sb.AppendLine($"{i} {{");
|
||||||
|
// RefFirst: UseMetadata → ObjectWithMetadataRefFirst + metadata, else → ObjectRefFirst
|
||||||
|
sb.AppendLine($"{i} if (context.UseMetadata)");
|
||||||
|
sb.AppendLine($"{i} {{");
|
||||||
|
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectWithMetadataRefFirst);");
|
||||||
|
sb.AppendLine($"{i} context.WriteVarUInt((uint)epe_{p.Name}.CacheMapIndex);");
|
||||||
|
EmitInlineMetadata(sb, p.ElementTypeNameHash, p.ElementPropertyHashes!, $"isFirstMeta_e_{p.Name}", i + " ");
|
||||||
|
sb.AppendLine($"{i} }}");
|
||||||
|
sb.AppendLine($"{i} else");
|
||||||
|
sb.AppendLine($"{i} {{");
|
||||||
|
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectRefFirst);");
|
||||||
|
sb.AppendLine($"{i} context.WriteVarUInt((uint)epe_{p.Name}.CacheMapIndex);");
|
||||||
|
sb.AppendLine($"{i} }}");
|
||||||
|
sb.AppendLine($"{i} {writer}.Instance.WriteProperties({e}, context, nextDepth_{p.Name});");
|
||||||
|
sb.AppendLine($"{i} }}");
|
||||||
|
sb.AppendLine($"{i} }}");
|
||||||
|
sb.AppendLine($"{i} else");
|
||||||
|
sb.AppendLine($"{i} {{");
|
||||||
|
// No ref tracking: UseMetadata → ObjectWithMetadata + metadata, else → Object
|
||||||
|
sb.AppendLine($"{i} if (context.UseMetadata)");
|
||||||
|
sb.AppendLine($"{i} {{");
|
||||||
|
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.ObjectWithMetadata);");
|
||||||
|
EmitInlineMetadata(sb, p.ElementTypeNameHash, p.ElementPropertyHashes!, $"isFirstMeta_e_{p.Name}", i + " ");
|
||||||
|
sb.AppendLine($"{i} }}");
|
||||||
|
sb.AppendLine($"{i} else");
|
||||||
|
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.Object);");
|
||||||
|
sb.AppendLine($"{i} {writer}.Instance.WriteProperties({e}, context, nextDepth_{p.Name});");
|
||||||
|
sb.AppendLine($"{i} }}");
|
||||||
|
}
|
||||||
|
|
||||||
sb.AppendLine($"{i} }}");
|
sb.AppendLine($"{i} }}");
|
||||||
sb.AppendLine($"{i}}}");
|
sb.AppendLine($"{i}}}");
|
||||||
|
|
@ -1077,6 +1152,22 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
|
||||||
return string.Join("_", parts);
|
return string.Join("_", parts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads EnableMetadataFeature from a type's [AcBinarySerializable] attribute.
|
||||||
|
/// Returns true (default) if no attribute or enableAllFeatures=true.
|
||||||
|
/// </summary>
|
||||||
|
private static bool ReadEnableMetadata(ITypeSymbol type)
|
||||||
|
{
|
||||||
|
var attr = type.GetAttributes().FirstOrDefault(a =>
|
||||||
|
a.AttributeClass?.ToDisplayString() == AttributeName);
|
||||||
|
if (attr == null) return true;
|
||||||
|
if (attr.ConstructorArguments.Length == 1)
|
||||||
|
return (bool)attr.ConstructorArguments[0].Value!;
|
||||||
|
if (attr.ConstructorArguments.Length == 4)
|
||||||
|
return (bool)attr.ConstructorArguments[0].Value!;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
#region FNV-1a Hash (compile-time)
|
#region FNV-1a Hash (compile-time)
|
||||||
|
|
||||||
private static int ComputeFnvHash(string value)
|
private static int ComputeFnvHash(string value)
|
||||||
|
|
@ -1222,8 +1313,10 @@ internal sealed class SerializableClassInfo
|
||||||
public int TypeNameHash { get; }
|
public int TypeNameHash { get; }
|
||||||
/// <summary>FNV-1a hash of each property name, in property order</summary>
|
/// <summary>FNV-1a hash of each property name, in property order</summary>
|
||||||
public int[] PropertyNameHashes { get; }
|
public int[] PropertyNameHashes { get; }
|
||||||
public SerializableClassInfo(string ns, string cn, string ftn, List<PropInfo> p, bool isIId, string? idTypeName, bool enableRefHandling, int typeNameHash, int[] propertyNameHashes)
|
/// <summary>When false, skip inline metadata and use markerless property write for this type.</summary>
|
||||||
{ Namespace = ns; ClassName = cn; FullTypeName = ftn; Properties = p; IsIId = isIId; IdTypeName = idTypeName; EnableRefHandling = enableRefHandling; TypeNameHash = typeNameHash; PropertyNameHashes = propertyNameHashes; }
|
public bool EnableMetadata { get; }
|
||||||
|
public SerializableClassInfo(string ns, string cn, string ftn, List<PropInfo> p, bool isIId, string? idTypeName, bool enableRefHandling, int typeNameHash, int[] propertyNameHashes, bool enableMetadata)
|
||||||
|
{ Namespace = ns; ClassName = cn; FullTypeName = ftn; Properties = p; IsIId = isIId; IdTypeName = idTypeName; EnableRefHandling = enableRefHandling; TypeNameHash = typeNameHash; PropertyNameHashes = propertyNameHashes; EnableMetadata = enableMetadata; }
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class PropInfo
|
internal sealed class PropInfo
|
||||||
|
|
@ -1278,13 +1371,18 @@ internal sealed class PropInfo
|
||||||
public int ElementTypeNameHash { get; }
|
public int ElementTypeNameHash { get; }
|
||||||
/// <summary>FNV-1a hashes of collection element type's properties. Only set when ElementHasGeneratedWriter.</summary>
|
/// <summary>FNV-1a hashes of collection element type's properties. Only set when ElementHasGeneratedWriter.</summary>
|
||||||
public int[]? ElementPropertyHashes { get; }
|
public int[]? ElementPropertyHashes { get; }
|
||||||
|
/// <summary>When false, child Complex type skips inline metadata in generated code.</summary>
|
||||||
|
public bool ChildEnableMetadata { get; }
|
||||||
|
/// <summary>When false, collection element type skips inline metadata in generated code.</summary>
|
||||||
|
public bool ElementEnableMetadata { get; }
|
||||||
|
|
||||||
public PropInfo(string n, string tn, string tnForTypeof, PropertyTypeKind tk, bool nullable,
|
public PropInfo(string n, string tn, string tnForTypeof, PropertyTypeKind tk, bool nullable,
|
||||||
bool? stringInternAttr = null, bool hasGeneratedWriter = false, bool isIId = false, string? writerClassName = null, string? idTypeName = null,
|
bool? stringInternAttr = null, bool hasGeneratedWriter = false, bool isIId = false, string? writerClassName = null, string? idTypeName = null,
|
||||||
PropertyTypeKind elementKind = PropertyTypeKind.Unknown, bool elementHasGenWriter = false, bool elementIsIId = false,
|
PropertyTypeKind elementKind = PropertyTypeKind.Unknown, bool elementHasGenWriter = false, bool elementIsIId = false,
|
||||||
string? elementWriterClassName = null, string? elementIdTypeName = null, string? collectionKind = null, string? elementFullTypeName = null,
|
string? elementWriterClassName = null, string? elementIdTypeName = null, string? collectionKind = null, string? elementFullTypeName = null,
|
||||||
int childTypeNameHash = 0, int[]? childPropertyHashes = null,
|
int childTypeNameHash = 0, int[]? childPropertyHashes = null,
|
||||||
int elementTypeNameHash = 0, int[]? elementPropertyHashes = null)
|
int elementTypeNameHash = 0, int[]? elementPropertyHashes = null,
|
||||||
|
bool childEnableMetadata = true, bool elementEnableMetadata = true)
|
||||||
{
|
{
|
||||||
Name = n;
|
Name = n;
|
||||||
TypeName = tn;
|
TypeName = tn;
|
||||||
|
|
@ -1306,6 +1404,8 @@ internal sealed class PropInfo
|
||||||
ChildPropertyHashes = childPropertyHashes;
|
ChildPropertyHashes = childPropertyHashes;
|
||||||
ElementTypeNameHash = elementTypeNameHash;
|
ElementTypeNameHash = elementTypeNameHash;
|
||||||
ElementPropertyHashes = elementPropertyHashes;
|
ElementPropertyHashes = elementPropertyHashes;
|
||||||
|
ChildEnableMetadata = childEnableMetadata;
|
||||||
|
ElementEnableMetadata = elementEnableMetadata;
|
||||||
// Mirror runtime _interningFlags computation from BinaryPropertyAccessorBase
|
// Mirror runtime _interningFlags computation from BinaryPropertyAccessorBase
|
||||||
int flags = 0;
|
int flags = 0;
|
||||||
if (stringInternAttr == true) flags |= (1 << 1); // Attribute bit
|
if (stringInternAttr == true) flags |= (1 << 1); // Attribute bit
|
||||||
|
|
|
||||||
|
|
@ -128,10 +128,11 @@ public static partial class AcBinaryDeserializer
|
||||||
SkipDefaultWrite = skipDefaultWrite
|
SkipDefaultWrite = skipDefaultWrite
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!context.HasMetadata)
|
if (!context.HasMetadata || !metadata.EnableMetadataFeature)
|
||||||
{
|
{
|
||||||
// Markerless loop: properties with ExpectedTypeCode read raw values directly.
|
// Markerless loop: properties with ExpectedTypeCode read raw values directly.
|
||||||
// Properties without ExpectedTypeCode (bool, enum, string, object) use standard marker path.
|
// Properties without ExpectedTypeCode (bool, enum, string, object) use standard marker path.
|
||||||
|
// Also used when EnableMetadataFeature=false on the type (per-type metadata opt-out).
|
||||||
for (int i = 0; i < propCount; i++)
|
for (int i = 0; i < propCount; i++)
|
||||||
{
|
{
|
||||||
var propInfo = properties[i];
|
var propInfo = properties[i];
|
||||||
|
|
|
||||||
|
|
@ -1038,15 +1038,14 @@ public static partial class AcBinarySerializer
|
||||||
{
|
{
|
||||||
var metadata = wrapper.Metadata;
|
var metadata = wrapper.Metadata;
|
||||||
|
|
||||||
// Wire format:
|
// Per-type metadata flag: when EnableMetadataFeature=false on [AcBinarySerializable],
|
||||||
// - UseMetadata=false: [Object][props...]
|
// skip inline metadata and use markerless property write — even when global UseMetadata=true.
|
||||||
// - UseMetadata=true, első: [ObjectWithMetadata][propNameHash (4b)][propCount (VarUInt)][hash0..N][props...]
|
// Deserializer must have the same attribute on the type (developer responsibility).
|
||||||
// - UseMetadata=true, ismételt: [ObjectWithMetadata][propNameHash (4b)][props...]
|
var useMetaForType = context.UseMetadata && metadata.EnableMetadataFeature;
|
||||||
// ObjectRef: [ObjectRef][cacheIndex]
|
|
||||||
|
|
||||||
// UseMetadata: típus regisztrálása (első vs ismételt előfordulás tracking)
|
// UseMetadata: típus regisztrálása (első vs ismételt előfordulás tracking)
|
||||||
var isFirstMetadataOccurrence = false;
|
var isFirstMetadataOccurrence = false;
|
||||||
if (context.UseMetadata)
|
if (useMetaForType)
|
||||||
{
|
{
|
||||||
isFirstMetadataOccurrence = BinarySerializationContext<TOutput>.RegisterMetadataType(wrapper);
|
isFirstMetadataOccurrence = BinarySerializationContext<TOutput>.RegisterMetadataType(wrapper);
|
||||||
}
|
}
|
||||||
|
|
@ -1076,7 +1075,7 @@ public static partial class AcBinarySerializer
|
||||||
// Marker kiírása:
|
// Marker kiírása:
|
||||||
// - Cached object first occurrence: ObjectRefFirst/ObjectWithMetadataRefFirst + cacheIndex
|
// - Cached object first occurrence: ObjectRefFirst/ObjectWithMetadataRefFirst + cacheIndex
|
||||||
// - Non-cached: Object/ObjectWithMetadata
|
// - Non-cached: Object/ObjectWithMetadata
|
||||||
if (context.UseMetadata)
|
if (useMetaForType)
|
||||||
{
|
{
|
||||||
if (cachedObjectCacheIndex >= 0)
|
if (cachedObjectCacheIndex >= 0)
|
||||||
{
|
{
|
||||||
|
|
@ -1123,11 +1122,12 @@ public static partial class AcBinarySerializer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!context.UseMetadata)
|
if (!useMetaForType)
|
||||||
{
|
{
|
||||||
// Markerless loop: no extra branching per property for the common case.
|
// Markerless loop: no extra branching per property for the common case.
|
||||||
// Properties with ExpectedTypeCode write raw values (no type marker, no skip).
|
// Properties with ExpectedTypeCode write raw values (no type marker, no skip).
|
||||||
// Properties without ExpectedTypeCode (bool, enum, string, object) use the standard path.
|
// Properties without ExpectedTypeCode (bool, enum, string, object) use the standard path.
|
||||||
|
// Also used when EnableMetadataFeature=false on the type (per-type metadata opt-out).
|
||||||
for (var i = 0; i < propCount; i++)
|
for (var i = 0; i < propCount; i++)
|
||||||
{
|
{
|
||||||
var prop = properties[i];
|
var prop = properties[i];
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ public sealed class AcBinarySerializerOptions : AcSerializerOptions
|
||||||
/// allowing the deserializer to match properties by name between different types.
|
/// allowing the deserializer to match properties by name between different types.
|
||||||
/// Default: false (no overhead)
|
/// Default: false (no overhead)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool UseMetadata { get; set; } = false;
|
public bool UseMetadata { get; set; } = true;
|
||||||
|
|
||||||
public bool UseGeneratedCode { get; set; } = true;
|
public bool UseGeneratedCode { get; set; } = true;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -131,6 +131,12 @@ public abstract class TypeMetadataBase
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool NeedsReferenceTracking { get; protected set; }
|
public bool NeedsReferenceTracking { get; protected set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When false, this type skips inline metadata and uses markerless property write/read
|
||||||
|
/// — even when global UseMetadata=true. Read from [AcBinarySerializable] attribute.
|
||||||
|
/// </summary>
|
||||||
|
public bool EnableMetadataFeature { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// True if this type is a primitive, string, enum, Guid, DateTime, etc.
|
/// True if this type is a primitive, string, enum, Guid, DateTime, etc.
|
||||||
/// Pre-computed to avoid IsPrimitiveOrStringFast() calls in hot path.
|
/// Pre-computed to avoid IsPrimitiveOrStringFast() calls in hot path.
|
||||||
|
|
@ -215,6 +221,7 @@ public abstract class TypeMetadataBase
|
||||||
}
|
}
|
||||||
|
|
||||||
var serializableAttr = type.GetCustomAttribute<AcBinarySerializableAttribute>(inherit: false);
|
var serializableAttr = type.GetCustomAttribute<AcBinarySerializableAttribute>(inherit: false);
|
||||||
|
EnableMetadataFeature = serializableAttr == null || serializableAttr.EnableMetadataFeature;
|
||||||
if (serializableAttr is { EnableIdTrackingFeature: false })
|
if (serializableAttr is { EnableIdTrackingFeature: false })
|
||||||
{
|
{
|
||||||
IsIId = false;
|
IsIId = false;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue