diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 350631e..2b08fdb 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -36,7 +36,8 @@ "Bash(del \"H:\\\\Applications\\\\Aycode\\\\Source\\\\AyCode.Core\\\\AyCode.Core\\\\Serializers\\\\Binaries\\\\IBinaryOutput.cs\")", "Bash(sort:*)", "WebFetch(domain:neuecc.medium.com)", - "WebFetch(domain:raw.githubusercontent.com)" + "WebFetch(domain:raw.githubusercontent.com)", + "Bash(xargs cat)" ] } } diff --git a/AyCode.Core.Serializers.Console/Program.cs b/AyCode.Core.Serializers.Console/Program.cs index a645910..3ec0eca 100644 --- a/AyCode.Core.Serializers.Console/Program.cs +++ b/AyCode.Core.Serializers.Console/Program.cs @@ -289,7 +289,16 @@ public static class Program } [MethodImpl(MethodImplOptions.NoInlining)] - public void Serialize() => AcBinarySerializer.Serialize(_order, _options); + public void Serialize() + { + AcBinarySerializer.Serialize(_order, _options); + + //if (_options.ReferenceHandling != ReferenceHandlingMode.None || _options.UseStringInterning != StringInterningMode.None) + //{ + // AcBinarySerializer.ScanOnly(_order, _options); + //} + //else AcBinarySerializer.Serialize(_order, _options); + } [MethodImpl(MethodImplOptions.NoInlining)] public void Deserialize() => AcBinaryDeserializer.Deserialize(_serialized, _options); diff --git a/AyCode.Core.Serializers.SourceGenerator/AcBinarySourceGenerator.cs b/AyCode.Core.Serializers.SourceGenerator/AcBinarySourceGenerator.cs index 46a4e92..1538ff0 100644 --- a/AyCode.Core.Serializers.SourceGenerator/AcBinarySourceGenerator.cs +++ b/AyCode.Core.Serializers.SourceGenerator/AcBinarySourceGenerator.cs @@ -498,7 +498,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator sb.AppendLine("using System.Runtime.CompilerServices;"); sb.AppendLine("using System.Runtime.InteropServices;"); sb.AppendLine("using AyCode.Core.Serializers.Binaries;"); - // ReferenceHandlingMode is needed for ScanObject self ref tracking and direct object write/scan + // IGeneratedBinaryWriter and other serializer types sb.AppendLine("using AyCode.Core.Serializers;"); sb.AppendLine(); if (!string.IsNullOrEmpty(ci.Namespace)) @@ -566,11 +566,11 @@ public class AcBinarySourceGenerator : IIncrementalGenerator if (!ci.NeedsIdScan) { if (ci.NeedsAllRefScan && ci.NeedsInternScan) - sb.AppendLine(" if (context.ReferenceHandling != ReferenceHandlingMode.All && !context.UseStringInterning) return;"); + sb.AppendLine(" if (!context.HasAllRefHandling && !context.HasStringInterning) return;"); else if (ci.NeedsAllRefScan) - sb.AppendLine(" if (context.ReferenceHandling != ReferenceHandlingMode.All) return;"); + sb.AppendLine(" if (!context.HasAllRefHandling) return;"); else if (ci.NeedsInternScan) - sb.AppendLine(" if (!context.UseStringInterning) return;"); + sb.AppendLine(" if (!context.HasStringInterning) return;"); } // Null/depth guard — matches runtime ScanValue entry @@ -590,7 +590,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator _ => "TryTrackInt32" }; sb.AppendLine(); - sb.AppendLine(" if (context.ReferenceHandling != ReferenceHandlingMode.None)"); + sb.AppendLine(" if (context.HasRefHandling)"); sb.AppendLine(" {"); sb.AppendLine($" var wrapper = context.GetWrapperBySlot(s_wrapperSlot, typeof({ci.FullTypeName}));"); sb.AppendLine(" var visitIndex = context.ScanVisitIndex++;"); @@ -607,7 +607,7 @@ public class AcBinarySourceGenerator : IIncrementalGenerator { // Non-IId type: track via wrapper.TryTrackInt32 with RuntimeHelpers.GetHashCode sb.AppendLine(); - sb.AppendLine(" if (context.ReferenceHandling == ReferenceHandlingMode.All)"); + sb.AppendLine(" if (context.HasAllRefHandling)"); sb.AppendLine(" {"); sb.AppendLine($" var wrapper = context.GetWrapperBySlot(s_wrapperSlot, typeof({ci.FullTypeName}));"); sb.AppendLine(" var visitIndex = context.ScanVisitIndex++;"); @@ -914,11 +914,11 @@ public class AcBinarySourceGenerator : IIncrementalGenerator if (!p.ChildNeedsIdScan) { if (p.ChildNeedsAllRefScan && p.ChildNeedsInternScan) - guard = "context.ReferenceHandling == ReferenceHandlingMode.All || context.UseStringInterning"; + guard = "context.HasAllRefHandling || context.HasStringInterning"; else if (p.ChildNeedsAllRefScan) - guard = "context.ReferenceHandling == ReferenceHandlingMode.All"; + guard = "context.HasAllRefHandling"; else if (p.ChildNeedsInternScan) - guard = "context.UseStringInterning"; + guard = "context.HasStringInterning"; } if (guard != null) @@ -1018,11 +1018,11 @@ public class AcBinarySourceGenerator : IIncrementalGenerator if (!p.ElementNeedsIdScan) { if (p.ElementNeedsAllRefScan && p.ElementNeedsInternScan) - elemGuard = "context.ReferenceHandling == ReferenceHandlingMode.All || context.UseStringInterning"; + elemGuard = "context.HasAllRefHandling || context.HasStringInterning"; else if (p.ElementNeedsAllRefScan) - elemGuard = "context.ReferenceHandling == ReferenceHandlingMode.All"; + elemGuard = "context.HasAllRefHandling"; else if (p.ElementNeedsInternScan) - elemGuard = "context.UseStringInterning"; + elemGuard = "context.HasStringInterning"; } // Guard entire collection scan with runtime check when no IId in element subtree @@ -1107,11 +1107,11 @@ public class AcBinarySourceGenerator : IIncrementalGenerator if (hasComplexValues && p.DictValueNeedsScan && !p.DictValueNeedsIdScan) { if (p.DictValueNeedsAllRefScan && p.DictValueNeedsInternScan) - complexGuard = "context.ReferenceHandling == ReferenceHandlingMode.All || context.UseStringInterning"; + complexGuard = "context.HasAllRefHandling || context.HasStringInterning"; else if (p.DictValueNeedsAllRefScan) - complexGuard = "context.ReferenceHandling == ReferenceHandlingMode.All"; + complexGuard = "context.HasAllRefHandling"; else if (p.DictValueNeedsInternScan) - complexGuard = "context.UseStringInterning"; + complexGuard = "context.HasStringInterning"; } // For string-only scan (no complex values), use simple interning loop @@ -1257,8 +1257,8 @@ public class AcBinarySourceGenerator : IIncrementalGenerator { // Ref tracking possible, no metadata — Object or ObjectRefFirst/ObjectRef var refGuard = p.IsIId - ? "context.ReferenceHandling != ReferenceHandlingMode.None" - : "context.ReferenceHandling == ReferenceHandlingMode.All"; + ? "context.HasRefHandling" + : "context.HasAllRefHandling"; sb.AppendLine($"{i} if ({refGuard} && context.TryConsumeWritePlanEntry(out var pe_{p.Name}))"); sb.AppendLine($"{i} {{"); sb.AppendLine($"{i} if (!pe_{p.Name}.IsFirst)"); @@ -1284,8 +1284,8 @@ public class AcBinarySourceGenerator : IIncrementalGenerator // Full path: ref tracking + metadata sb.AppendLine($"{i} var isFirstMeta_{p.Name} = context.UseMetadata && AcBinarySerializer.BinarySerializationContext.RegisterMetadataType(context.GetWrapperBySlot({writer}.s_wrapperSlot, typeof({p.TypeNameForTypeof})));"); var refGuard = p.IsIId - ? "context.ReferenceHandling != ReferenceHandlingMode.None" - : "context.ReferenceHandling == ReferenceHandlingMode.All"; + ? "context.HasRefHandling" + : "context.HasAllRefHandling"; sb.AppendLine($"{i} if ({refGuard} && context.TryConsumeWritePlanEntry(out var pe_{p.Name}))"); sb.AppendLine($"{i} {{"); sb.AppendLine($"{i} if (!pe_{p.Name}.IsFirst)"); @@ -1436,8 +1436,8 @@ public class AcBinarySourceGenerator : IIncrementalGenerator { // Inline ref tracking var elemRefGuard = p.ElementIsIId - ? "context.ReferenceHandling != ReferenceHandlingMode.None" - : "context.ReferenceHandling == ReferenceHandlingMode.All"; + ? "context.HasRefHandling" + : "context.HasAllRefHandling"; if (!p.ElementEnableMetadata) { @@ -1700,8 +1700,8 @@ public class AcBinarySourceGenerator : IIncrementalGenerator else { var dvRefGuard = p.DictValueIsIId - ? "context.ReferenceHandling != ReferenceHandlingMode.None" - : "context.ReferenceHandling == ReferenceHandlingMode.All"; + ? "context.HasRefHandling" + : "context.HasAllRefHandling"; if (!p.DictValueEnableMetadata) { diff --git a/AyCode.Core.Tests/Serialization/AcBinarySerializerIIdReferenceTests.cs b/AyCode.Core.Tests/Serialization/AcBinarySerializerIIdReferenceTests.cs index 490287c..33a33cb 100644 --- a/AyCode.Core.Tests/Serialization/AcBinarySerializerIIdReferenceTests.cs +++ b/AyCode.Core.Tests/Serialization/AcBinarySerializerIIdReferenceTests.cs @@ -127,7 +127,8 @@ public class AcBinarySerializerIIdReferenceTests { ReferenceHandling = mode, UseGeneratedCode = useSgen, - UseMetadata = useMeta + UseMetadata = useMeta, + MaxDepth = 10 }; Console.WriteLine($"\n========== ReferenceHandling: {options.ReferenceHandling}, UseSgen: {options.UseGeneratedCode}, UseMeta: {options.UseMetadata} =========="); diff --git a/AyCode.Core/Serializers/AcSerializerContextBase.cs b/AyCode.Core/Serializers/AcSerializerContextBase.cs index 71d1528..8a5c84d 100644 --- a/AyCode.Core/Serializers/AcSerializerContextBase.cs +++ b/AyCode.Core/Serializers/AcSerializerContextBase.cs @@ -31,9 +31,17 @@ public abstract class AcSerializerContextBase private bool _hasRefHandling; /// - /// Pre-computed: ReferenceHandling == IId (not All). When true, only IId types are tracked. + /// Pre-computed: ReferenceHandling == OnlyId (not All). When true, only IId types are tracked. /// private bool _hasIdHandling; + + /// + /// Pre-computed: ReferenceHandling == All. When true, all reference types are tracked. + /// + private bool _hasAllRefHandling; + + internal bool HasRefHandling => _hasRefHandling; + internal bool HasAllRefHandling => _hasAllRefHandling; public bool ThrowOnCircularReference => Options.ThrowOnCircularReference; /// /// Global shared cache for metadata (thread-safe, shared across all contexts). @@ -165,6 +173,7 @@ public abstract class AcSerializerContextBase Options = options; _hasRefHandling = options.ReferenceHandling != ReferenceHandlingMode.None; _hasIdHandling = options.ReferenceHandling == ReferenceHandlingMode.OnlyId; + _hasAllRefHandling = options.ReferenceHandling == ReferenceHandlingMode.All; } /// diff --git a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs index ee8c387..576ebea 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs @@ -212,7 +212,8 @@ public static partial class AcBinarySerializer #endif // These properties delegate to Options for convenience - internal bool UseStringInterning => Options.UseStringInterning != StringInterningMode.None; + internal bool HasStringInterning { get; private set; } + internal bool UseStringInterning => HasStringInterning; /// /// Pre-computed 1 << (int)Options.UseStringInterning. @@ -230,7 +231,7 @@ public static partial class AcBinarySerializer /// /// True if we have interning/ref tracking (cache count needed in header). /// - public bool HasCaching => UseStringInterning || ReferenceHandling != ReferenceHandlingMode.None; + public bool HasCaching => HasStringInterning || HasRefHandling; public bool UseMetadata => Options.UseMetadata; public bool UseGeneratedCode => Options.UseGeneratedCode; @@ -279,6 +280,7 @@ public static partial class AcBinarySerializer base.Reset(options); HasPropertyFilter = Options.PropertyFilter != null; InternBit = 1 << (int)Options.UseStringInterning; + HasStringInterning = Options.UseStringInterning != StringInterningMode.None; //FastWire = Options.WireMode == WireMode.Fast; } diff --git a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs index d6ab191..c34167d 100644 --- a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs +++ b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs @@ -342,6 +342,30 @@ public static partial class AcBinarySerializer } } + /// + /// Runs only the scan pass (ScanForDuplicates) without writing. + /// For benchmarking scan pass overhead in isolation. + /// + internal static void ScanOnly(T value, AcBinarySerializerOptions options) + { + if (value == null) return; + var runtimeType = value.GetType(); + var context = BinarySerializationContextPool.Get(options); + if (!context.OutputInitialized) + { + context.Output = new ArrayBinaryOutput(options.InitialBufferCapacity); + context.OutputInitialized = true; + } + try + { + ScanForDuplicates(value, runtimeType, context); + } + finally + { + BinarySerializationContextPool.Return(context); + } + } + /// /// Serialize object to an IBufferWriter for zero-copy scenarios. /// Uses BufferWriterBinaryOutput — writes directly to the caller's buffer.