Refactor: unify metadata and tracking for serializer contexts
Major refactor of serialization infrastructure: - Removed AcSerializeBase; replaced with AcSerializerContextBase<TMetadata> for unified context management. - Added TypeMetadataWrapper<TMetadata> to combine metadata and per-context tracking state. - All serializer contexts now inherit from AcSerializerContextBase and use context.GetWrapper(type) for metadata and tracking. - Reference tracking for IId types is now type-safe and efficient (bitmaps for small int IDs, generic identity maps for others). - Removed generic ThreadLocal caching from TypeMetadataBase; caching now uses global ConcurrentDictionary. - Updated all type metadata classes to inherit from non-generic base. - Added IdPropertyInfo and MetadataType to TypeMetadataBase. - Added stub context base classes for JSON and Toon. This centralizes and optimizes metadata/tracking, improves performance, and prepares for future extensibility.
This commit is contained in:
parent
2ab640b375
commit
8161ddade4
|
|
@ -1,30 +0,0 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace AyCode.Core.Serializers;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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.
|
||||
}
|
||||
|
|
@ -609,6 +609,52 @@ public static class AcSerializerCommon
|
|||
Object = 255
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface for identity maps used in serialization tracking.
|
||||
/// Enables type-safe Reset() without knowing the generic type parameter.
|
||||
/// </summary>
|
||||
public interface IIdentityMap
|
||||
{
|
||||
/// <summary>
|
||||
/// Resets the identity map for reuse between serializations.
|
||||
/// </summary>
|
||||
void Reset();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generic identity map for tracking IId values during serialization.
|
||||
/// Uses JIT-optimized EqualityComparer for maximum performance with common ID types.
|
||||
/// </summary>
|
||||
/// <typeparam name="TId">The ID type (int, long, Guid)</typeparam>
|
||||
public sealed class IdentityMap<TId> : IIdentityMap where TId : notnull
|
||||
{
|
||||
private readonly HashSet<TId> _seenIds;
|
||||
|
||||
public IdentityMap()
|
||||
{
|
||||
_seenIds = new HashSet<TId>(EqualityComparer<TId>.Default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryAdd(TId id)
|
||||
{
|
||||
return _seenIds.Add(id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the identity map for reuse.
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
_seenIds.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// IId-based reference tracking for serialization.
|
||||
/// Supplements (not replaces) the ReferenceEquals-based SerializationReferenceTracker.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <typeparam name="TMetadata">The concrete metadata type.</typeparam>
|
||||
public abstract class AcSerializerContextBase<TMetadata> where TMetadata : TypeMetadataBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Global shared cache for metadata (thread-safe, shared across all contexts).
|
||||
/// Generic specialization ensures separate cache per TMetadata type.
|
||||
/// </summary>
|
||||
private static readonly ConcurrentDictionary<Type, TMetadata> GlobalMetadataCache = new();
|
||||
|
||||
/// <summary>
|
||||
/// Per-context wrappers containing metadata + tracking state.
|
||||
/// </summary>
|
||||
private readonly Dictionary<Type, TypeMetadataWrapper<TMetadata>> _wrappers = new();
|
||||
|
||||
/// <summary>
|
||||
/// Factory function to create metadata. Implemented by derived class.
|
||||
/// </summary>
|
||||
protected abstract Func<Type, TMetadata> MetadataFactory { get; }
|
||||
|
||||
private const int BitArraySize = 1024;
|
||||
private const int MaxSmallId = BitArraySize * 64;
|
||||
|
||||
#region Wrapper Access
|
||||
|
||||
/// <summary>
|
||||
/// Gets or creates a wrapper for the specified type.
|
||||
/// The wrapper contains metadata (from GlobalMetadataCache) + per-context tracking state.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public TypeMetadataWrapper<TMetadata> GetWrapper(Type type)
|
||||
{
|
||||
if (_wrappers.TryGetValue(type, out var wrapper))
|
||||
return wrapper;
|
||||
|
||||
return GetWrapperSlow(type);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private TypeMetadataWrapper<TMetadata> 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<TMetadata>(metadata);
|
||||
_wrappers[type] = wrapper;
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Tracking API - int
|
||||
|
||||
/// <summary>
|
||||
/// Tries to track an object with int RefId.
|
||||
/// Use when wrapper.Metadata.IdAccessorType == Int32.
|
||||
/// Returns true if first occurrence.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryTrack(TypeMetadataWrapper<TMetadata> wrapper, object obj, out int refId)
|
||||
{
|
||||
Debug.Assert(wrapper.Metadata.IdAccessorType == AcSerializerCommon.IdAccessorType.Int32);
|
||||
|
||||
var getter = (Func<object, int>)wrapper.RefIdGetter;
|
||||
refId = getter(obj);
|
||||
|
||||
// BitArray fast path for small positive IDs
|
||||
if (refId >= 0 && refId < MaxSmallId)
|
||||
{
|
||||
return TryTrackSmallId(wrapper, refId);
|
||||
}
|
||||
|
||||
// IdentityMap for large/negative IDs
|
||||
var map = wrapper.IdentityMap as AcSerializerCommon.IdentityMap<int>;
|
||||
if (map == null)
|
||||
{
|
||||
map = new AcSerializerCommon.IdentityMap<int>();
|
||||
wrapper.IdentityMap = map;
|
||||
}
|
||||
return map.TryAdd(refId);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool TryTrackSmallId(TypeMetadataWrapper<TMetadata> wrapper, int id)
|
||||
{
|
||||
wrapper.EnsureSmallIdBitmap();
|
||||
var bitmap = wrapper.SmallIdBitmap!;
|
||||
|
||||
uint idx = (uint)id;
|
||||
uint wordIdx = idx >> 6;
|
||||
int bitIdx = (int)(idx & 63);
|
||||
|
||||
ulong mask = 1UL << bitIdx;
|
||||
ref ulong word = ref bitmap[wordIdx];
|
||||
|
||||
if ((word & mask) == 0)
|
||||
{
|
||||
word |= mask;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Tracking API - long
|
||||
|
||||
/// <summary>
|
||||
/// Tries to track an object with long RefId.
|
||||
/// Use when wrapper.Metadata.IdAccessorType == Int64.
|
||||
/// Returns true if first occurrence.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryTrack(TypeMetadataWrapper<TMetadata> wrapper, object obj, out long refId)
|
||||
{
|
||||
Debug.Assert(wrapper.Metadata.IdAccessorType == AcSerializerCommon.IdAccessorType.Int64);
|
||||
|
||||
var getter = (Func<object, long>)wrapper.RefIdGetter;
|
||||
refId = getter(obj);
|
||||
|
||||
var map = wrapper.IdentityMap as AcSerializerCommon.IdentityMap<long>;
|
||||
if (map == null)
|
||||
{
|
||||
map = new AcSerializerCommon.IdentityMap<long>();
|
||||
wrapper.IdentityMap = map;
|
||||
}
|
||||
return map.TryAdd(refId);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Tracking API - Guid
|
||||
|
||||
/// <summary>
|
||||
/// Tries to track an object with Guid RefId.
|
||||
/// Use when wrapper.Metadata.IdAccessorType == Guid.
|
||||
/// Returns true if first occurrence.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryTrack(TypeMetadataWrapper<TMetadata> wrapper, object obj, out Guid refId)
|
||||
{
|
||||
Debug.Assert(wrapper.Metadata.IdAccessorType == AcSerializerCommon.IdAccessorType.Guid);
|
||||
|
||||
var getter = (Func<object, Guid>)wrapper.RefIdGetter;
|
||||
refId = getter(obj);
|
||||
|
||||
var map = wrapper.IdentityMap as AcSerializerCommon.IdentityMap<Guid>;
|
||||
if (map == null)
|
||||
{
|
||||
map = new AcSerializerCommon.IdentityMap<Guid>();
|
||||
wrapper.IdentityMap = map;
|
||||
}
|
||||
return map.TryAdd(refId);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Reset
|
||||
|
||||
/// <summary>
|
||||
/// Resets all wrapper tracking states for reuse.
|
||||
/// Does not remove wrappers - keeps them for next operation.
|
||||
/// </summary>
|
||||
public virtual void Reset()
|
||||
{
|
||||
foreach (var wrapper in _wrappers.Values)
|
||||
{
|
||||
wrapper.ResetTracking();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ namespace AyCode.Core.Serializers.Binaries;
|
|||
|
||||
public static partial class AcBinaryDeserializer
|
||||
{
|
||||
internal sealed class BinaryDeserializeTypeMetadata : DeserializeTypeMetadataBase<BinaryDeserializeTypeMetadata>
|
||||
internal sealed class BinaryDeserializeTypeMetadata : DeserializeTypeMetadataBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Properties array ordered alphabetically by name for index-based lookup.
|
||||
|
|
|
|||
|
|
@ -1358,13 +1358,12 @@ public static partial class AcBinaryDeserializer
|
|||
|
||||
#region Type Metadata
|
||||
|
||||
/// <summary>
|
||||
/// Gets type metadata with ThreadLocal caching for hot path optimization.
|
||||
/// Uses built-in cache from BinaryDeserializeTypeMetadata base class (zero ref parameter overhead).
|
||||
/// </summary>
|
||||
// Temporary: own cache until ref struct is removed
|
||||
private static readonly ConcurrentDictionary<Type, BinaryDeserializeTypeMetadata> MetadataCache = new();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static BinaryDeserializeTypeMetadata GetTypeMetadata(Type type)
|
||||
=> BinaryDeserializeTypeMetadata.GetOrCreateMetadata(type, static t => new BinaryDeserializeTypeMetadata(t));
|
||||
=> MetadataCache.GetOrAdd(type, static t => new BinaryDeserializeTypeMetadata(t));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static object? CreateInstance(Type type, BinaryDeserializeTypeMetadata metadata)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using System.Runtime.CompilerServices;
|
|||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Intrinsics;
|
||||
using System.Text;
|
||||
using static AyCode.Core.Helpers.JsonUtilities;
|
||||
|
||||
namespace AyCode.Core.Serializers.Binaries;
|
||||
|
||||
|
|
@ -47,7 +48,7 @@ public static partial class AcBinarySerializer
|
|||
/// <summary>
|
||||
/// Binary serialization context. Public for generated serializers.
|
||||
/// </summary>
|
||||
internal sealed class BinarySerializationContext : AcSerializeBase, IDisposable
|
||||
internal sealed class BinarySerializationContext : AcSerializerContextBase<BinarySerializeTypeMetadata>, IDisposable
|
||||
{
|
||||
private const int MinBufferSize = 256;
|
||||
private const int PropertyIndexBufferMaxCache = 512;
|
||||
|
|
@ -99,6 +100,12 @@ public static partial class AcBinarySerializer
|
|||
Reset(options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Factory for creating BinarySerializeTypeMetadata instances.
|
||||
/// </summary>
|
||||
protected override Func<Type, BinarySerializeTypeMetadata> MetadataFactory
|
||||
=> static t => new BinarySerializeTypeMetadata(t, HasJsonIgnoreAttribute);
|
||||
|
||||
public void Reset(AcBinarySerializerOptions options)
|
||||
{
|
||||
_position = 0;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ namespace AyCode.Core.Serializers.Binaries;
|
|||
|
||||
public static partial class AcBinarySerializer
|
||||
{
|
||||
internal sealed class BinarySerializeTypeMetadata : SerializeTypeMetadataBase<BinarySerializeTypeMetadata>
|
||||
internal sealed class BinarySerializeTypeMetadata : SerializeTypeMetadataBase
|
||||
{
|
||||
public BinaryPropertyAccessor[] Properties { get; }
|
||||
|
||||
|
|
|
|||
|
|
@ -203,8 +203,9 @@ public static partial class AcBinarySerializer
|
|||
var type = value.GetType();
|
||||
if (IsPrimitiveOrStringFast(type)) return;
|
||||
|
||||
// Get metadata for IId-aware tracking
|
||||
var metadata = GetTypeMetadata(type);
|
||||
// Get wrapper for IId-aware tracking
|
||||
var wrapper = context.GetWrapper(type);
|
||||
var metadata = wrapper.Metadata;
|
||||
|
||||
// OPTIMIZATION: Skip types that don't need reference tracking
|
||||
// (no IId, no complex properties that could be shared)
|
||||
|
|
@ -286,7 +287,8 @@ public static partial class AcBinarySerializer
|
|||
return;
|
||||
}
|
||||
|
||||
var metadata = GetTypeMetadata(type);
|
||||
var wrapper = context.GetWrapper(type);
|
||||
var metadata = wrapper.Metadata;
|
||||
var properties = metadata.Properties;
|
||||
|
||||
// Use index-based iteration for array access
|
||||
|
|
@ -722,7 +724,8 @@ public static partial class AcBinarySerializer
|
|||
context.WriteVarInt(-1); // No ref ID
|
||||
}
|
||||
|
||||
var metadata = GetTypeMetadata(type);
|
||||
var wrapper = context.GetWrapper(type);
|
||||
var metadata = wrapper.Metadata;
|
||||
var nextDepth = depth + 1;
|
||||
var properties = metadata.Properties;
|
||||
var propCount = properties.Length;
|
||||
|
|
@ -1248,13 +1251,6 @@ public static partial class AcBinarySerializer
|
|||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets type metadata with ThreadLocal caching for hot path optimization.
|
||||
/// Uses built-in cache from BinarySerializeTypeMetadata base class (zero ref parameter overhead).
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static BinarySerializeTypeMetadata GetTypeMetadata(Type type)
|
||||
=> BinarySerializeTypeMetadata.GetOrCreateMetadata(type, static t => new BinarySerializeTypeMetadata(t, HasJsonIgnoreAttribute));
|
||||
|
||||
// Type metadata helpers moved to AcBinarySerializer.BinarySerializeTypeMetadata.cs
|
||||
|
||||
|
|
|
|||
|
|
@ -6,11 +6,9 @@ namespace AyCode.Core.Serializers;
|
|||
/// Base class for deserializer type metadata.
|
||||
/// Extends TypeMetadataBase for deserializer-specific functionality.
|
||||
/// Used by both JSON and Binary deserializers.
|
||||
/// Generic version provides built-in ThreadLocal caching with zero ref parameter overhead.
|
||||
/// Note: IId detection (IsIId, IdType, IdGetter) is now in TypeMetadataBase for use by both serializers and deserializers.
|
||||
/// </summary>
|
||||
public abstract class DeserializeTypeMetadataBase<TMetadata> : TypeMetadataBase<TMetadata>
|
||||
where TMetadata : DeserializeTypeMetadataBase<TMetadata>
|
||||
public abstract class DeserializeTypeMetadataBase : TypeMetadataBase
|
||||
{
|
||||
protected DeserializeTypeMetadataBase(Type type, Func<PropertyInfo, bool> ignorePropertyFilter) : base(type, ignorePropertyFilter)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
public class AcJsonContextBase
|
||||
{
|
||||
|
||||
}
|
||||
|
|
@ -45,7 +45,7 @@ public static partial class AcJsonDeserializer
|
|||
public readonly int RefId = refId;
|
||||
}
|
||||
|
||||
private sealed class DeserializationContext
|
||||
private sealed class DeserializationContext : AcSerializerContextBase<JsonDeserializeTypeMetadata>
|
||||
{
|
||||
// Use shared reference tracker from AcSerializerCommon
|
||||
private readonly AcSerializerCommon.DeserializationReferenceTracker _refTracker = new();
|
||||
|
|
@ -66,12 +66,18 @@ public static partial class AcJsonDeserializer
|
|||
/// </summary>
|
||||
public bool IsChainMode => ChainTracker != null;
|
||||
|
||||
/// <summary>
|
||||
/// Factory for creating JsonDeserializeTypeMetadata instances.
|
||||
/// </summary>
|
||||
protected override Func<Type, JsonDeserializeTypeMetadata> 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;
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
||||
/// <summary>
|
||||
/// JSON deserialization type metadata.
|
||||
/// Extends DeserializeTypeMetadataBase which provides cached IId info.
|
||||
/// </summary>
|
||||
private sealed class JsonDeserializeTypeMetadata : DeserializeTypeMetadataBase<JsonDeserializeTypeMetadata>
|
||||
private sealed class JsonDeserializeTypeMetadata : DeserializeTypeMetadataBase
|
||||
{
|
||||
public FrozenDictionary<string, PropertySetterInfo> PropertySettersFrozen { get; }
|
||||
public PropertySetterInfo[] PropertiesArray { get; } // Array for fast UTF8 linear scan (small objects)
|
||||
|
|
|
|||
|
|
@ -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<Type, JsonDeserializeTypeMetadata> MetadataCache = new();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static JsonDeserializeTypeMetadata GetTypeMetadata(Type type)
|
||||
=> MetadataCache.GetOrAdd(type, static t => new JsonDeserializeTypeMetadata(t));
|
||||
|
||||
#region Public API
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ public static partial class AcJsonSerializer
|
|||
}
|
||||
}
|
||||
|
||||
private sealed class JsonSerializationContext : AcSerializeBase, IDisposable
|
||||
private sealed class JsonSerializationContext : AcSerializerContextBase<JsonSerializeTypeMetadata>, IDisposable
|
||||
{
|
||||
private readonly ArrayBufferWriter<byte> _buffer;
|
||||
public Utf8JsonWriter Writer { get; private set; }
|
||||
|
|
@ -59,6 +59,12 @@ public static partial class AcJsonSerializer
|
|||
Reset(options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Factory for creating JsonSerializeTypeMetadata instances.
|
||||
/// </summary>
|
||||
protected override Func<Type, JsonSerializeTypeMetadata> MetadataFactory
|
||||
=> static t => new JsonSerializeTypeMetadata(t);
|
||||
|
||||
public void Reset(in AcJsonSerializerOptions options)
|
||||
{
|
||||
UseReferenceHandling = options.UseReferenceHandling;
|
||||
|
|
|
|||
|
|
@ -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<JsonSerializeTypeMetadata>
|
||||
private sealed class JsonSerializeTypeMetadata : SerializeTypeMetadataBase
|
||||
{
|
||||
public PropertyAccessor[] Properties { get; }
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
/// </summary>
|
||||
/// <typeparam name="TMetadata">The concrete metadata type (must inherit from this class).</typeparam>
|
||||
public abstract class SerializeTypeMetadataBase<TMetadata> : TypeMetadataBase<TMetadata>
|
||||
where TMetadata : SerializeTypeMetadataBase<TMetadata>
|
||||
public abstract class SerializeTypeMetadataBase : TypeMetadataBase
|
||||
{
|
||||
protected SerializeTypeMetadataBase(Type type, Func<PropertyInfo, bool> ignorePropertyFilter)
|
||||
: base(type, ignorePropertyFilter)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
public class AcToonContextBase //: AcSerializerContextBase<ToonMeta>
|
||||
{
|
||||
|
||||
}
|
||||
|
|
@ -47,7 +47,7 @@ public static partial class AcToonSerializer
|
|||
/// Pooled context for Toon serialization.
|
||||
/// Handles output building, indentation, and reference tracking.
|
||||
/// </summary>
|
||||
private sealed class ToonSerializationContext : AcSerializeBase
|
||||
private sealed class ToonSerializationContext : AcSerializerContextBase<ToonSerializeTypeMetadata>
|
||||
{
|
||||
private readonly StringBuilder _builder;
|
||||
private Dictionary<object, int>? _scanOccurrences;
|
||||
|
|
@ -68,6 +68,12 @@ public static partial class AcToonSerializer
|
|||
Reset(options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Factory for creating ToonSerializeTypeMetadata instances.
|
||||
/// </summary>
|
||||
protected override Func<Type, ToonSerializeTypeMetadata> MetadataFactory
|
||||
=> static t => new ToonSerializeTypeMetadata(t);
|
||||
|
||||
public void Reset(AcToonSerializerOptions options)
|
||||
{
|
||||
Options = options;
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
/// </summary>
|
||||
private sealed class ToonSerializeTypeMetadata : SerializeTypeMetadataBase<ToonSerializeTypeMetadata>
|
||||
private sealed class ToonSerializeTypeMetadata : SerializeTypeMetadataBase
|
||||
{
|
||||
public string TypeName { get; }
|
||||
public string ShortTypeName { get; }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
/// <summary>
|
||||
/// Gets or creates ToonSerializeTypeMetadata using TypeMetadataBase infrastructure.
|
||||
/// This uses the shared GlobalMetadataCache and ThreadLocal cache for optimal performance.
|
||||
/// </summary>
|
||||
// Temporary: own cache for static methods without context
|
||||
private static readonly ConcurrentDictionary<Type, ToonSerializeTypeMetadata> 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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,6 +88,17 @@ public abstract class TypeMetadataBase
|
|||
/// </summary>
|
||||
public AcSerializerCommon.IdAccessorType IdAccessorType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The Id property info if IsIId is true, null otherwise.
|
||||
/// Used by TypeMetadataWrapper to create typed RefIdGetter.
|
||||
/// </summary>
|
||||
public PropertyInfo? IdPropertyInfo { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Public accessor for the type. Used by wrapper for typed getter creation.
|
||||
/// </summary>
|
||||
public Type MetadataType => SourceType;
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <typeparam name="TMetadata">The concrete metadata type (must inherit from this class).</typeparam>
|
||||
public abstract class TypeMetadataBase<TMetadata> : TypeMetadataBase where TMetadata : TypeMetadataBase<TMetadata>
|
||||
{
|
||||
/// <summary>
|
||||
/// ThreadLocal cache for this specific metadata type.
|
||||
/// Each TMetadata type gets its own static cache due to generic specialization.
|
||||
/// </summary>
|
||||
[ThreadStatic]
|
||||
private static Dictionary<Type, TMetadata>? t_localCache;
|
||||
|
||||
protected TypeMetadataBase(Type type, Func<PropertyInfo, bool> ignorePropertyFilter) : base(type, ignorePropertyFilter)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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)
|
||||
/// </summary>
|
||||
/// <param name="sourceType">The type to get metadata for.</param>
|
||||
/// <param name="factory">Factory function to create metadata if not cached.</param>
|
||||
/// <returns>Cached or newly created metadata instance.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static TMetadata GetOrCreateMetadata(Type sourceType, Func<Type, TMetadata> 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<Type, TMetadata> 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<Type, TMetadata>();
|
||||
|
||||
if (t_localCache.Count >= MaxLocalCacheSize)
|
||||
t_localCache.Clear();
|
||||
|
||||
t_localCache[sourceType] = metadata;
|
||||
return metadata;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,95 @@
|
|||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using AyCode.Core.Helpers;
|
||||
|
||||
namespace AyCode.Core.Serializers;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <typeparam name="TMetadata">The concrete metadata type (BinarySerializeTypeMetadata, etc.)</typeparam>
|
||||
public sealed class TypeMetadataWrapper<TMetadata> where TMetadata : TypeMetadataBase
|
||||
{
|
||||
/// <summary>
|
||||
/// The cached metadata reference (from GlobalMetadataCache).
|
||||
/// </summary>
|
||||
public readonly TMetadata Metadata;
|
||||
|
||||
/// <summary>
|
||||
/// Typed getter for reference ID. Runtime type is Func<object, int/long/Guid>.
|
||||
/// Use IdAccessorType to determine the actual type.
|
||||
/// </summary>
|
||||
internal readonly Delegate RefIdGetter;
|
||||
|
||||
/// <summary>
|
||||
/// Identity map for tracking. Runtime type is IdentityMap<int/long/Guid>.
|
||||
/// </summary>
|
||||
internal AcSerializerCommon.IIdentityMap? IdentityMap;
|
||||
|
||||
/// <summary>
|
||||
/// BitArray for tracking small int32 IDs (0-65535).
|
||||
/// Only used when IdAccessorType == Int32.
|
||||
/// </summary>
|
||||
internal ulong[]? SmallIdBitmap;
|
||||
|
||||
private const int BitArraySize = 1024; // 1024 * 64 = 65,536 IDs
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new wrapper for the given metadata.
|
||||
/// Initializes RefIdGetter based on IdAccessorType.
|
||||
/// </summary>
|
||||
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<int>(metadata.MetadataType, metadata.IdPropertyInfo),
|
||||
AcSerializerCommon.IdAccessorType.Int64 =>
|
||||
AcSerializerCommon.CreateTypedGetter<long>(metadata.MetadataType, metadata.IdPropertyInfo),
|
||||
AcSerializerCommon.IdAccessorType.Guid =>
|
||||
AcSerializerCommon.CreateTypedGetter<Guid>(metadata.MetadataType, metadata.IdPropertyInfo),
|
||||
_ => throw new NotSupportedException($"Unsupported IdAccessorType: {metadata.IdAccessorType}")
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
// Non-IId type - use RuntimeHelpers.GetHashCode
|
||||
return new Func<object, int>(RuntimeHelpers.GetHashCode);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets tracking state for reuse between serializations.
|
||||
/// Does not deallocate - just clears for reuse.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ResetTracking()
|
||||
{
|
||||
if (SmallIdBitmap != null)
|
||||
Array.Clear(SmallIdBitmap);
|
||||
|
||||
IdentityMap?.Reset();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures SmallIdBitmap is allocated (lazy allocation).
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void EnsureSmallIdBitmap()
|
||||
{
|
||||
SmallIdBitmap ??= new ulong[BitArraySize];
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue