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