diff --git a/AyCode.Core.Serializers.SourceGenerator/AcBinarySourceGenerator.cs b/AyCode.Core.Serializers.SourceGenerator/AcBinarySourceGenerator.cs index dcae6fe..f0402ae 100644 --- a/AyCode.Core.Serializers.SourceGenerator/AcBinarySourceGenerator.cs +++ b/AyCode.Core.Serializers.SourceGenerator/AcBinarySourceGenerator.cs @@ -248,9 +248,18 @@ public class AcBinarySourceGenerator : IIncrementalGenerator // 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) 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; } @@ -366,8 +375,9 @@ public class AcBinarySourceGenerator : IIncrementalGenerator /// /// Emits direct object write — bypasses GetWrapper + WriteObject entirely. /// Writes marker bytes + calls child GeneratedWriter.WriteProperties inline. - /// For IId types: inlines TryConsumeWritePlanEntry for ref tracking cursor alignment. - /// Falls back to WriteObjectGenerated when context.IsDirectObjectWrite is false (UseMetadata/PropertyFilter). + /// IId types: guard ReferenceHandling != None (tracked in OnlyId + All). + /// Non-IId types: guard ReferenceHandling == All (tracked only in All mode). + /// Falls back to WriteObjectGenerated when context.IsDirectObjectWrite is false (UseMetadata). /// private static void EmitDirectObjectWrite(StringBuilder sb, PropInfo p, string a, string i) { @@ -391,50 +401,32 @@ public class AcBinarySourceGenerator : IIncrementalGenerator sb.AppendLine($"{i} else"); sb.AppendLine($"{i} {{"); - if (p.IsIId) - { - // IId type: inline ref tracking from WriteObject - // context.UseTypeReferenceHandling gate: ReferenceHandling != None && IsIId - // For IId types, IsIId is always true, so: ReferenceHandling != None - sb.AppendLine($"{i} if (context.ReferenceHandling != ReferenceHandlingMode.None && context.TryConsumeWritePlanEntry(out var pe_{p.Name}))"); - sb.AppendLine($"{i} {{"); - sb.AppendLine($"{i} if (!pe_{p.Name}.IsFirst)"); - sb.AppendLine($"{i} {{"); - // 2+ occurrence → ObjectRef + cacheIndex (no properties written) - 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} {{"); - // First occurrence → ObjectRefFirst + cacheIndex + properties - 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} {{"); - // No ref tracking entry at this visit → plain Object marker - sb.AppendLine($"{i} context.WriteByte(BinaryTypeCode.Object);"); - sb.AppendLine($"{i} {writer}.Instance.WriteProperties({a}, context, {nextDepth});"); - 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} }}"); - } + // Inline ref tracking: guard depends on IId vs non-IId to match scan pass behavior. + // IId types: tracked in OnlyId + All → guard: ReferenceHandling != None + // Non-IId types: tracked only in All → guard: ReferenceHandling == All + // This matches UseTypeReferenceHandling: (IsIId || ReferenceHandling == All) && ReferenceHandling != None + 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} }}"); sb.AppendLine($"{i} }}"); sb.AppendLine($"{i}}}"); @@ -508,41 +500,31 @@ 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; }}"); - if (p.ElementIsIId) - { - sb.AppendLine($"{i} if (context.ReferenceHandling != ReferenceHandlingMode.None && 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 - { - // 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} }}"); - } + // 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 + // 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} 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} }}"); sb.AppendLine($"{i} }}"); sb.AppendLine($"{i}}}"); diff --git a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs index 223ea11..d9d1755 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs @@ -1089,7 +1089,7 @@ public static partial class AcBinarySerializer if (context.UseGeneratedCode) { var generatedWriter = wrapper.GeneratedWriter; - if (generatedWriter != null && !context.UseMetadata) + if (generatedWriter != null) { generatedWriter.WriteProperties(value, context, nextDepth); return; diff --git a/AyCode.Core/Serializers/Binaries/AcBinarySerializerOptions.cs b/AyCode.Core/Serializers/Binaries/AcBinarySerializerOptions.cs index 6b01f75..e0fff73 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinarySerializerOptions.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinarySerializerOptions.cs @@ -82,7 +82,7 @@ public sealed class AcBinarySerializerOptions : AcSerializerOptions /// allowing the deserializer to match properties by name between different types. /// Default: false (no overhead) /// - public bool UseMetadata { get; set; } = false; + public bool UseMetadata { get; set; } = true; public bool UseGeneratedCode { get; set; } = true;