diff --git a/AyCode.Core/Serializers/AcSerializeBase.cs b/AyCode.Core/Serializers/AcSerializeBase.cs
deleted file mode 100644
index f1d36c0..0000000
--- a/AyCode.Core/Serializers/AcSerializeBase.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using System.Runtime.CompilerServices;
-
-namespace AyCode.Core.Serializers;
-
-///
-/// Abstract base class for serialization operations.
-/// Contains serialize-specific logic that may need override capability.
-///
-/// Responsibilities:
-/// - Reference scanning (ScanReferences)
-/// - Reference tracking during write (TrackForScanning, ShouldWriteId, MarkAsWritten)
-/// - IId-aware serialization logic
-///
-/// Derived classes:
-/// - BinarySerializationContext (or similar) for Binary serialization
-/// - JsonSerializationContext (or similar) for JSON serialization
-/// - ToonSerializationContext for Toon serialization
-///
-/// Note: Currently SerializationReferenceTracker remains in AcSerializerCommon.
-/// As patterns emerge, serialize-specific methods can be moved here.
-///
-public abstract class AcSerializeBase
-{
- // Future: Move serialize-specific logic here
- // - SerializationReferenceTracker (or make it a protected property)
- // - Virtual ComputeHash method (for IId vs Reference distinction)
- // - Virtual TrackForScanning method
- // - Virtual ShouldWriteRef method
- // - etc.
-}
diff --git a/AyCode.Core/Serializers/AcSerializerCommon.cs b/AyCode.Core/Serializers/AcSerializerCommon.cs
index ed92ead..87ca528 100644
--- a/AyCode.Core/Serializers/AcSerializerCommon.cs
+++ b/AyCode.Core/Serializers/AcSerializerCommon.cs
@@ -609,6 +609,52 @@ public static class AcSerializerCommon
Object = 255
}
+ ///
+ /// Interface for identity maps used in serialization tracking.
+ /// Enables type-safe Reset() without knowing the generic type parameter.
+ ///
+ public interface IIdentityMap
+ {
+ ///
+ /// Resets the identity map for reuse between serializations.
+ ///
+ void Reset();
+ }
+
+ ///
+ /// Generic identity map for tracking IId values during serialization.
+ /// Uses JIT-optimized EqualityComparer for maximum performance with common ID types.
+ ///
+ /// The ID type (int, long, Guid)
+ public sealed class IdentityMap : IIdentityMap where TId : notnull
+ {
+ private readonly HashSet _seenIds;
+
+ public IdentityMap()
+ {
+ _seenIds = new HashSet(EqualityComparer.Default);
+ }
+
+ ///
+ /// Tries to add an ID to the tracking set.
+ /// Returns true if this is the first occurrence (ID was added).
+ /// Returns false if already seen.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool TryAdd(TId id)
+ {
+ return _seenIds.Add(id);
+ }
+
+ ///
+ /// Resets the identity map for reuse.
+ ///
+ public void Reset()
+ {
+ _seenIds.Clear();
+ }
+ }
+
///
/// IId-based reference tracking for serialization.
/// Supplements (not replaces) the ReferenceEquals-based SerializationReferenceTracker.
diff --git a/AyCode.Core/Serializers/AcSerializerContextBase.cs b/AyCode.Core/Serializers/AcSerializerContextBase.cs
new file mode 100644
index 0000000..051a9da
--- /dev/null
+++ b/AyCode.Core/Serializers/AcSerializerContextBase.cs
@@ -0,0 +1,187 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+
+namespace AyCode.Core.Serializers;
+
+///
+/// Base class for all serializer contexts.
+/// Provides GetWrapper for type metadata access with per-context tracking state.
+/// GlobalMetadataCache stores metadata (thread-safe), wrappers store per-context tracking.
+///
+/// The concrete metadata type.
+public abstract class AcSerializerContextBase where TMetadata : TypeMetadataBase
+{
+ ///
+ /// Global shared cache for metadata (thread-safe, shared across all contexts).
+ /// Generic specialization ensures separate cache per TMetadata type.
+ ///
+ private static readonly ConcurrentDictionary GlobalMetadataCache = new();
+
+ ///
+ /// Per-context wrappers containing metadata + tracking state.
+ ///
+ private readonly Dictionary> _wrappers = new();
+
+ ///
+ /// Factory function to create metadata. Implemented by derived class.
+ ///
+ protected abstract Func MetadataFactory { get; }
+
+ private const int BitArraySize = 1024;
+ private const int MaxSmallId = BitArraySize * 64;
+
+ #region Wrapper Access
+
+ ///
+ /// Gets or creates a wrapper for the specified type.
+ /// The wrapper contains metadata (from GlobalMetadataCache) + per-context tracking state.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public TypeMetadataWrapper GetWrapper(Type type)
+ {
+ if (_wrappers.TryGetValue(type, out var wrapper))
+ return wrapper;
+
+ return GetWrapperSlow(type);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private TypeMetadataWrapper GetWrapperSlow(Type type)
+ {
+ // Get metadata from global cache (thread-safe)
+ var metadata = GlobalMetadataCache.GetOrAdd(type, MetadataFactory);
+
+ // Create wrapper with metadata + tracking state (per-context)
+ var wrapper = new TypeMetadataWrapper(metadata);
+ _wrappers[type] = wrapper;
+ return wrapper;
+ }
+
+ #endregion
+
+ #region Tracking API - int
+
+ ///
+ /// Tries to track an object with int RefId.
+ /// Use when wrapper.Metadata.IdAccessorType == Int32.
+ /// Returns true if first occurrence.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool TryTrack(TypeMetadataWrapper wrapper, object obj, out int refId)
+ {
+ Debug.Assert(wrapper.Metadata.IdAccessorType == AcSerializerCommon.IdAccessorType.Int32);
+
+ var getter = (Func
public bool IsChainMode => ChainTracker != null;
+ ///
+ /// Factory for creating JsonDeserializeTypeMetadata instances.
+ ///
+ protected override Func MetadataFactory
+ => static t => new JsonDeserializeTypeMetadata(t);
+
public DeserializationContext(in AcJsonSerializerOptions options)
{
Reset(options);
}
- public void Reset(in AcJsonSerializerOptions options)
+ public new void Reset(in AcJsonSerializerOptions options)
{
UseReferenceHandling = options.UseReferenceHandling;
MaxDepth = options.MaxDepth;
@@ -80,8 +86,9 @@ public static partial class AcJsonDeserializer
_refTracker.Reset();
}
- public void Clear()
+ public new void Clear()
{
+ base.Reset();
_refTracker.Reset();
_propertiesToResolve?.Clear();
ChainTracker = null;
diff --git a/AyCode.Core/Serializers/Jsons/AcJsonDeserializer.JsonDeserializeTypeMetadata.cs b/AyCode.Core/Serializers/Jsons/AcJsonDeserializer.JsonDeserializeTypeMetadata.cs
index 2b00f17..b59965e 100644
--- a/AyCode.Core/Serializers/Jsons/AcJsonDeserializer.JsonDeserializeTypeMetadata.cs
+++ b/AyCode.Core/Serializers/Jsons/AcJsonDeserializer.JsonDeserializeTypeMetadata.cs
@@ -11,15 +11,11 @@ namespace AyCode.Core.Serializers.Jsons;
public static partial class AcJsonDeserializer
{
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static JsonDeserializeTypeMetadata GetTypeMetadata(in Type type)
- => JsonDeserializeTypeMetadata.GetOrCreateMetadata(type, static t => new JsonDeserializeTypeMetadata(t));
-
///
/// JSON deserialization type metadata.
/// Extends DeserializeTypeMetadataBase which provides cached IId info.
///
- private sealed class JsonDeserializeTypeMetadata : DeserializeTypeMetadataBase
+ private sealed class JsonDeserializeTypeMetadata : DeserializeTypeMetadataBase
{
public FrozenDictionary PropertySettersFrozen { get; }
public PropertySetterInfo[] PropertiesArray { get; } // Array for fast UTF8 linear scan (small objects)
diff --git a/AyCode.Core/Serializers/Jsons/AcJsonDeserializer.cs b/AyCode.Core/Serializers/Jsons/AcJsonDeserializer.cs
index 60aec2b..b909a7e 100644
--- a/AyCode.Core/Serializers/Jsons/AcJsonDeserializer.cs
+++ b/AyCode.Core/Serializers/Jsons/AcJsonDeserializer.cs
@@ -1,4 +1,5 @@
using System.Collections;
+using System.Collections.Concurrent;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using System.Text;
@@ -19,6 +20,13 @@ public static partial class AcJsonDeserializer
private static readonly byte[] RefPropertyUtf8 = "$ref"u8.ToArray();
private static readonly byte[] IdPropertyUtf8 = "$id"u8.ToArray();
+ // Global metadata cache
+ private static readonly ConcurrentDictionary MetadataCache = new();
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static JsonDeserializeTypeMetadata GetTypeMetadata(Type type)
+ => MetadataCache.GetOrAdd(type, static t => new JsonDeserializeTypeMetadata(t));
+
#region Public API
///
diff --git a/AyCode.Core/Serializers/Jsons/AcJsonSerializer.JsonSerializationContext.cs b/AyCode.Core/Serializers/Jsons/AcJsonSerializer.JsonSerializationContext.cs
index 07013f8..1e839ef 100644
--- a/AyCode.Core/Serializers/Jsons/AcJsonSerializer.JsonSerializationContext.cs
+++ b/AyCode.Core/Serializers/Jsons/AcJsonSerializer.JsonSerializationContext.cs
@@ -35,7 +35,7 @@ public static partial class AcJsonSerializer
}
}
- private sealed class JsonSerializationContext : AcSerializeBase, IDisposable
+ private sealed class JsonSerializationContext : AcSerializerContextBase, IDisposable
{
private readonly ArrayBufferWriter _buffer;
public Utf8JsonWriter Writer { get; private set; }
@@ -59,6 +59,12 @@ public static partial class AcJsonSerializer
Reset(options);
}
+ ///
+ /// Factory for creating JsonSerializeTypeMetadata instances.
+ ///
+ protected override Func MetadataFactory
+ => static t => new JsonSerializeTypeMetadata(t);
+
public void Reset(in AcJsonSerializerOptions options)
{
UseReferenceHandling = options.UseReferenceHandling;
diff --git a/AyCode.Core/Serializers/Jsons/AcJsonSerializer.JsonSerializeTypeMetadata.cs b/AyCode.Core/Serializers/Jsons/AcJsonSerializer.JsonSerializeTypeMetadata.cs
index a740f90..ddc0906 100644
--- a/AyCode.Core/Serializers/Jsons/AcJsonSerializer.JsonSerializeTypeMetadata.cs
+++ b/AyCode.Core/Serializers/Jsons/AcJsonSerializer.JsonSerializeTypeMetadata.cs
@@ -8,11 +8,7 @@ namespace AyCode.Core.Serializers.Jsons;
public static partial class AcJsonSerializer
{
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static JsonSerializeTypeMetadata GetTypeMetadata(in Type type)
- => JsonSerializeTypeMetadata.GetOrCreateMetadata(type, static t => new JsonSerializeTypeMetadata(t));
-
- private sealed class JsonSerializeTypeMetadata : SerializeTypeMetadataBase
+ private sealed class JsonSerializeTypeMetadata : SerializeTypeMetadataBase
{
public PropertyAccessor[] Properties { get; }
diff --git a/AyCode.Core/Serializers/Jsons/AcJsonSerializer.cs b/AyCode.Core/Serializers/Jsons/AcJsonSerializer.cs
index 67215bb..ed48862 100644
--- a/AyCode.Core/Serializers/Jsons/AcJsonSerializer.cs
+++ b/AyCode.Core/Serializers/Jsons/AcJsonSerializer.cs
@@ -139,7 +139,8 @@ public static partial class AcJsonSerializer
return;
}
- var metadata = GetTypeMetadata(type);
+ var wrapper = context.GetWrapper(type);
+ var metadata = wrapper.Metadata;
var props = metadata.Properties;
var propCount = props.Length;
for (var i = 0; i < propCount; i++)
@@ -190,7 +191,8 @@ public static partial class AcJsonSerializer
context.MarkAsWritten(value, id);
}
- var metadata = GetTypeMetadata(type);
+ var wrapper = context.GetWrapper(type);
+ var metadata = wrapper.Metadata;
var props = metadata.Properties;
var propCount = props.Length;
var nextDepth = depth + 1;
diff --git a/AyCode.Core/Serializers/SerializeTypeMetadataBase.cs b/AyCode.Core/Serializers/SerializeTypeMetadataBase.cs
index 0c2bf28..b40542a 100644
--- a/AyCode.Core/Serializers/SerializeTypeMetadataBase.cs
+++ b/AyCode.Core/Serializers/SerializeTypeMetadataBase.cs
@@ -6,11 +6,8 @@ namespace AyCode.Core.Serializers;
/// Base class for serializer type metadata.
/// Extends TypeMetadataBase for serializer-specific functionality.
/// Used by Binary, JSON, and Toon serializers.
-/// Generic version provides built-in ThreadLocal caching with zero ref parameter overhead.
///
-/// The concrete metadata type (must inherit from this class).
-public abstract class SerializeTypeMetadataBase : TypeMetadataBase
- where TMetadata : SerializeTypeMetadataBase
+public abstract class SerializeTypeMetadataBase : TypeMetadataBase
{
protected SerializeTypeMetadataBase(Type type, Func ignorePropertyFilter)
: base(type, ignorePropertyFilter)
diff --git a/AyCode.Core/Serializers/Toons/AcToonContextBase.cs b/AyCode.Core/Serializers/Toons/AcToonContextBase.cs
new file mode 100644
index 0000000..fed9fac
--- /dev/null
+++ b/AyCode.Core/Serializers/Toons/AcToonContextBase.cs
@@ -0,0 +1,4 @@
+public class AcToonContextBase //: AcSerializerContextBase
+{
+
+}
\ No newline at end of file
diff --git a/AyCode.Core/Serializers/Toons/AcToonSerializer.ToonSerializationContext.cs b/AyCode.Core/Serializers/Toons/AcToonSerializer.ToonSerializationContext.cs
index 4e064cd..90fc903 100644
--- a/AyCode.Core/Serializers/Toons/AcToonSerializer.ToonSerializationContext.cs
+++ b/AyCode.Core/Serializers/Toons/AcToonSerializer.ToonSerializationContext.cs
@@ -47,7 +47,7 @@ public static partial class AcToonSerializer
/// Pooled context for Toon serialization.
/// Handles output building, indentation, and reference tracking.
///
- private sealed class ToonSerializationContext : AcSerializeBase
+ private sealed class ToonSerializationContext : AcSerializerContextBase
{
private readonly StringBuilder _builder;
private Dictionary? _scanOccurrences;
@@ -68,6 +68,12 @@ public static partial class AcToonSerializer
Reset(options);
}
+ ///
+ /// Factory for creating ToonSerializeTypeMetadata instances.
+ ///
+ protected override Func MetadataFactory
+ => static t => new ToonSerializeTypeMetadata(t);
+
public void Reset(AcToonSerializerOptions options)
{
Options = options;
diff --git a/AyCode.Core/Serializers/Toons/AcToonSerializer.ToonSerializeTypeMetadata.cs b/AyCode.Core/Serializers/Toons/AcToonSerializer.ToonSerializeTypeMetadata.cs
index f2e2f14..6ddf81d 100644
--- a/AyCode.Core/Serializers/Toons/AcToonSerializer.ToonSerializeTypeMetadata.cs
+++ b/AyCode.Core/Serializers/Toons/AcToonSerializer.ToonSerializeTypeMetadata.cs
@@ -12,7 +12,7 @@ public static partial class AcToonSerializer
/// Cached metadata for a type including properties, type name, and descriptions.
/// Uses SerializeTypeMetadataBase infrastructure for shared caching across all serializers.
///
- private sealed class ToonSerializeTypeMetadata : SerializeTypeMetadataBase
+ private sealed class ToonSerializeTypeMetadata : SerializeTypeMetadataBase
{
public string TypeName { get; }
public string ShortTypeName { get; }
diff --git a/AyCode.Core/Serializers/Toons/AcToonSerializer.cs b/AyCode.Core/Serializers/Toons/AcToonSerializer.cs
index 1382ccd..406502d 100644
--- a/AyCode.Core/Serializers/Toons/AcToonSerializer.cs
+++ b/AyCode.Core/Serializers/Toons/AcToonSerializer.cs
@@ -1,4 +1,5 @@
using System.Collections;
+using System.Collections.Concurrent;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Text;
@@ -321,13 +322,12 @@ public static partial class AcToonSerializer
#region Type Metadata
- ///
- /// Gets or creates ToonSerializeTypeMetadata using TypeMetadataBase infrastructure.
- /// This uses the shared GlobalMetadataCache and ThreadLocal cache for optimal performance.
- ///
+ // Temporary: own cache for static methods without context
+ private static readonly ConcurrentDictionary MetadataCache = new();
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ToonSerializeTypeMetadata GetTypeMetadata(Type type)
- => ToonSerializeTypeMetadata.GetOrCreateMetadata(type, static t => new ToonSerializeTypeMetadata(t));
+ => MetadataCache.GetOrAdd(type, static t => new ToonSerializeTypeMetadata(t));
#endregion
}
diff --git a/AyCode.Core/Serializers/TypeMetadataBase.cs b/AyCode.Core/Serializers/TypeMetadataBase.cs
index 47433c4..e3fc007 100644
--- a/AyCode.Core/Serializers/TypeMetadataBase.cs
+++ b/AyCode.Core/Serializers/TypeMetadataBase.cs
@@ -88,6 +88,17 @@ public abstract class TypeMetadataBase
///
public AcSerializerCommon.IdAccessorType IdAccessorType { get; }
+ ///
+ /// The Id property info if IsIId is true, null otherwise.
+ /// Used by TypeMetadataWrapper to create typed RefIdGetter.
+ ///
+ public PropertyInfo? IdPropertyInfo { get; }
+
+ ///
+ /// Public accessor for the type. Used by wrapper for typed getter creation.
+ ///
+ public Type MetadataType => SourceType;
+
///
/// Typed getter delegate for IId.Id property.
/// Type depends on IdAccessorType (Func<object, int>, Func<object, long>, or Func<object, Guid>).
@@ -150,6 +161,7 @@ public abstract class TypeMetadataBase
if (IsIId && IdType != null)
{
var idProp = type.GetProperty("Id");
+ IdPropertyInfo = idProp; // Store for TypeMetadataWrapper
if (idProp != null)
{
// Create typed getter for the three common Id types to avoid boxing
@@ -170,9 +182,8 @@ public abstract class TypeMetadataBase
}
else
{
- // Fallback for exotic Id types - uses boxing
- IdAccessorType = AcSerializerCommon.IdAccessorType.Object;
- IdGetter = AcSerializerCommon.CreateCompiledGetter(type, idProp);
+ // Exotic Id types not supported - only int, long, Guid
+ throw new NotSupportedException($"Unsupported IId type: {IdType.Name}. Only int, long, and Guid are supported.");
}
}
}
@@ -227,59 +238,3 @@ public abstract class TypeMetadataBase
});
}
}
-
-///
-/// Generic base class for type metadata with built-in ThreadLocal caching.
-/// Provides better performance than TypeMetadataBase by eliminating ref parameter overhead.
-/// Each TMetadata type gets its own ThreadStatic cache instance automatically.
-///
-/// The concrete metadata type (must inherit from this class).
-public abstract class TypeMetadataBase : TypeMetadataBase where TMetadata : TypeMetadataBase
-{
- ///
- /// ThreadLocal cache for this specific metadata type.
- /// Each TMetadata type gets its own static cache due to generic specialization.
- ///
- [ThreadStatic]
- private static Dictionary? t_localCache;
-
- protected TypeMetadataBase(Type type, Func ignorePropertyFilter) : base(type, ignorePropertyFilter)
- {
- }
-
- ///
- /// Gets or creates metadata for the specified type using two-level cache:
- /// 1. ThreadLocal cache (fast path, no lock, no ref parameter)
- /// 2. Global cache (slow path, ConcurrentDictionary with lock)
- ///
- /// The type to get metadata for.
- /// Factory function to create metadata if not cached.
- /// Cached or newly created metadata instance.
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal static TMetadata GetOrCreateMetadata(Type sourceType, Func factory)
- {
- // Fast path: check ThreadLocal cache first (no ref parameter overhead!)
- if (t_localCache != null && t_localCache.TryGetValue(sourceType, out var cached))
- return cached;
-
- // Slow path: get from global cache and populate local cache
- return GetOrCreateMetadataSlow(sourceType, factory);
- }
-
- [MethodImpl(MethodImplOptions.NoInlining)]
- private static TMetadata GetOrCreateMetadataSlow(Type sourceType, Func factory)
- {
- // Get or create from global cache
- var key = (sourceType, typeof(TMetadata));
- var metadata = (TMetadata)GlobalMetadataCache.GetOrAdd(key, _ => factory(sourceType));
-
- // Populate ThreadLocal cache
- t_localCache ??= new Dictionary();
-
- if (t_localCache.Count >= MaxLocalCacheSize)
- t_localCache.Clear();
-
- t_localCache[sourceType] = metadata;
- return metadata;
- }
-}
diff --git a/AyCode.Core/Serializers/TypeMetadataWrapper.cs b/AyCode.Core/Serializers/TypeMetadataWrapper.cs
new file mode 100644
index 0000000..31d8e36
--- /dev/null
+++ b/AyCode.Core/Serializers/TypeMetadataWrapper.cs
@@ -0,0 +1,95 @@
+using System;
+using System.Runtime.CompilerServices;
+using AyCode.Core.Helpers;
+
+namespace AyCode.Core.Serializers;
+
+///
+/// Wrapper that combines metadata with tracking state.
+/// Each context has one wrapper per type - contains all type-specific info and state.
+/// Not generic on TRefId - uses runtime typed Delegate and object for flexibility.
+///
+/// The concrete metadata type (BinarySerializeTypeMetadata, etc.)
+public sealed class TypeMetadataWrapper where TMetadata : TypeMetadataBase
+{
+ ///
+ /// The cached metadata reference (from GlobalMetadataCache).
+ ///
+ public readonly TMetadata Metadata;
+
+ ///
+ /// Typed getter for reference ID. Runtime type is Func<object, int/long/Guid>.
+ /// Use IdAccessorType to determine the actual type.
+ ///
+ internal readonly Delegate RefIdGetter;
+
+ ///
+ /// Identity map for tracking. Runtime type is IdentityMap<int/long/Guid>.
+ ///
+ internal AcSerializerCommon.IIdentityMap? IdentityMap;
+
+ ///
+ /// BitArray for tracking small int32 IDs (0-65535).
+ /// Only used when IdAccessorType == Int32.
+ ///
+ internal ulong[]? SmallIdBitmap;
+
+ private const int BitArraySize = 1024; // 1024 * 64 = 65,536 IDs
+
+ ///
+ /// Creates a new wrapper for the given metadata.
+ /// Initializes RefIdGetter based on IdAccessorType.
+ ///
+ public TypeMetadataWrapper(TMetadata metadata)
+ {
+ Metadata = metadata;
+
+ // Create typed RefIdGetter based on IdAccessorType
+ RefIdGetter = CreateRefIdGetter(metadata);
+ }
+
+ private static Delegate CreateRefIdGetter(TMetadata metadata)
+ {
+ if (metadata.IsIId && metadata.IdPropertyInfo != null)
+ {
+ // IId type - create typed getter from Id property
+ return metadata.IdAccessorType switch
+ {
+ AcSerializerCommon.IdAccessorType.Int32 =>
+ AcSerializerCommon.CreateTypedGetter(metadata.MetadataType, metadata.IdPropertyInfo),
+ AcSerializerCommon.IdAccessorType.Int64 =>
+ AcSerializerCommon.CreateTypedGetter(metadata.MetadataType, metadata.IdPropertyInfo),
+ AcSerializerCommon.IdAccessorType.Guid =>
+ AcSerializerCommon.CreateTypedGetter(metadata.MetadataType, metadata.IdPropertyInfo),
+ _ => throw new NotSupportedException($"Unsupported IdAccessorType: {metadata.IdAccessorType}")
+ };
+ }
+ else
+ {
+ // Non-IId type - use RuntimeHelpers.GetHashCode
+ return new Func(RuntimeHelpers.GetHashCode);
+ }
+ }
+
+ ///
+ /// Resets tracking state for reuse between serializations.
+ /// Does not deallocate - just clears for reuse.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void ResetTracking()
+ {
+ if (SmallIdBitmap != null)
+ Array.Clear(SmallIdBitmap);
+
+ IdentityMap?.Reset();
+ }
+
+ ///
+ /// Ensures SmallIdBitmap is allocated (lazy allocation).
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal void EnsureSmallIdBitmap()
+ {
+ SmallIdBitmap ??= new ulong[BitArraySize];
+ }
+}