Refine AcBinary gen: metadata, ref tracking alignment

Improve generated serialization code to match runtime behavior for metadata emission and reference tracking. Markerless types now respect UseMetadata, ensuring type markers are written when required. Ref tracking guards for IId and non-IId types are unified to match scan pass logic. Generated writers are always used when available. Default UseMetadata is now true for consistent metadata output.
This commit is contained in:
Loretta 2026-02-18 13:07:26 +01:00
parent d40e40a45a
commit e2269d3ecf
3 changed files with 66 additions and 84 deletions

View File

@ -248,9 +248,18 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
// 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)
// to match runtime WritePropertyOrSkip behavior (every property gets a type marker byte)
if (IsMarkerless(p.TypeKind)) if (IsMarkerless(p.TypeKind))
{ {
EmitMarkerless(sb, p.TypeKind, a, 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;
} }
@ -366,8 +375,9 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
/// <summary> /// <summary>
/// Emits direct object write — bypasses GetWrapper + WriteObject entirely. /// Emits direct object write — bypasses GetWrapper + WriteObject entirely.
/// Writes marker bytes + calls child GeneratedWriter.WriteProperties inline. /// Writes marker bytes + calls child GeneratedWriter.WriteProperties inline.
/// For IId types: inlines TryConsumeWritePlanEntry for ref tracking cursor alignment. /// IId types: guard ReferenceHandling != None (tracked in OnlyId + All).
/// Falls back to WriteObjectGenerated when context.IsDirectObjectWrite is false (UseMetadata/PropertyFilter). /// Non-IId types: guard ReferenceHandling == All (tracked only in All mode).
/// Falls back to WriteObjectGenerated when context.IsDirectObjectWrite is false (UseMetadata).
/// </summary> /// </summary>
private static void EmitDirectObjectWrite(StringBuilder sb, PropInfo p, string a, string i) private static void EmitDirectObjectWrite(StringBuilder sb, PropInfo p, string a, string i)
{ {
@ -391,22 +401,22 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
sb.AppendLine($"{i} else"); sb.AppendLine($"{i} else");
sb.AppendLine($"{i} {{"); sb.AppendLine($"{i} {{");
if (p.IsIId) // Inline ref tracking: guard depends on IId vs non-IId to match scan pass behavior.
{ // IId types: tracked in OnlyId + All → guard: ReferenceHandling != None
// IId type: inline ref tracking from WriteObject // Non-IId types: tracked only in All → guard: ReferenceHandling == All
// context.UseTypeReferenceHandling gate: ReferenceHandling != None && IsIId // This matches UseTypeReferenceHandling: (IsIId || ReferenceHandling == All) && ReferenceHandling != None
// For IId types, IsIId is always true, so: ReferenceHandling != None var refGuard = p.IsIId
sb.AppendLine($"{i} if (context.ReferenceHandling != ReferenceHandlingMode.None && context.TryConsumeWritePlanEntry(out var pe_{p.Name}))"); ? "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} {{");
sb.AppendLine($"{i} if (!pe_{p.Name}.IsFirst)"); sb.AppendLine($"{i} if (!pe_{p.Name}.IsFirst)");
sb.AppendLine($"{i} {{"); sb.AppendLine($"{i} {{");
// 2+ occurrence → ObjectRef + cacheIndex (no properties written)
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} {{");
// First occurrence → ObjectRefFirst + cacheIndex + properties
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} {writer}.Instance.WriteProperties({a}, context, {nextDepth});"); sb.AppendLine($"{i} {writer}.Instance.WriteProperties({a}, context, {nextDepth});");
@ -414,27 +424,9 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
sb.AppendLine($"{i} }}"); sb.AppendLine($"{i} }}");
sb.AppendLine($"{i} else"); sb.AppendLine($"{i} else");
sb.AppendLine($"{i} {{"); sb.AppendLine($"{i} {{");
// No ref tracking entry at this visit → plain Object marker
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} }}");
}
else
{
// Non-IId type: usually no ref tracking needed.
// Exception: ReferenceHandling == All tracks ALL objects → scan pass increments ScanVisitIndex,
// so write pass must also consume from WritePlan to keep cursor aligned.
// Guard: fall back to WriteObjectGenerated for All mode (rare). OnlyId mode is safe (non-IId skipped).
sb.AppendLine($"{i} if (context.ReferenceHandling == ReferenceHandlingMode.All)");
sb.AppendLine($"{i} {{");
sb.AppendLine($"{i} AcBinarySerializer.WriteObjectGenerated({a}, typeof({p.TypeNameForTypeof}), context, depth);");
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} }}");
}
sb.AppendLine($"{i} }}"); sb.AppendLine($"{i} }}");
sb.AppendLine($"{i}}}"); sb.AppendLine($"{i}}}");
@ -508,9 +500,13 @@ 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; }}");
if (p.ElementIsIId) // Inline ref tracking: guard depends on IId vs non-IId element type to match scan pass.
{ // IId elements: tracked in OnlyId + All → guard: ReferenceHandling != None
sb.AppendLine($"{i} if (context.ReferenceHandling != ReferenceHandlingMode.None && context.TryConsumeWritePlanEntry(out var epe_{p.Name}))"); // Non-IId elements: tracked only in All → guard: ReferenceHandling == All
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} {{");
sb.AppendLine($"{i} if (!epe_{p.Name}.IsFirst)"); sb.AppendLine($"{i} if (!epe_{p.Name}.IsFirst)");
sb.AppendLine($"{i} {{"); sb.AppendLine($"{i} {{");
@ -529,20 +525,6 @@ public class AcBinarySourceGenerator : IIncrementalGenerator
sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.Object);"); 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} }}");
}
else
{
// Non-IId element: guard against ReferenceHandling.All (cursor alignment)
sb.AppendLine($"{i} if (context.ReferenceHandling == ReferenceHandlingMode.All)");
sb.AppendLine($"{i} {{");
sb.AppendLine($"{i} AcBinarySerializer.WriteObjectGenerated({e}, typeof({elemType}), context, nextDepth_{p.Name} - 1);");
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} }}");
}
sb.AppendLine($"{i} }}"); sb.AppendLine($"{i} }}");
sb.AppendLine($"{i}}}"); sb.AppendLine($"{i}}}");

View File

@ -1089,7 +1089,7 @@ public static partial class AcBinarySerializer
if (context.UseGeneratedCode) if (context.UseGeneratedCode)
{ {
var generatedWriter = wrapper.GeneratedWriter; var generatedWriter = wrapper.GeneratedWriter;
if (generatedWriter != null && !context.UseMetadata) if (generatedWriter != null)
{ {
generatedWriter.WriteProperties(value, context, nextDepth); generatedWriter.WriteProperties(value, context, nextDepth);
return; return;

View File

@ -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;