198 lines
7.2 KiB
C#
198 lines
7.2 KiB
C#
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>
|
|
/// <typeparam name="TOptions">The concrete options type.</typeparam>
|
|
public abstract class AcSerializerContextBase<TMetadata, TOptions>
|
|
where TMetadata : TypeMetadataBase
|
|
where TOptions : AcSerializerOptions
|
|
{
|
|
/// <summary>
|
|
/// The options used for this context. Set during Reset.
|
|
/// </summary>
|
|
public TOptions Options { get; private set; } = null!;
|
|
|
|
public byte MaxDepth => Options.MaxDepth;
|
|
public ReferenceHandlingMode ReferenceHandling => Options.ReferenceHandling;
|
|
|
|
/// <summary>
|
|
/// Pre-computed: ReferenceHandling != None. Avoids Options field chain per call.
|
|
/// </summary>
|
|
private bool _hasRefHandling;
|
|
|
|
/// <summary>
|
|
/// Pre-computed: ReferenceHandling == OnlyId (not All). When true, only IId types are tracked.
|
|
/// </summary>
|
|
private bool _hasIdHandling;
|
|
|
|
/// <summary>
|
|
/// Pre-computed: ReferenceHandling == All. When true, all reference types are tracked.
|
|
/// </summary>
|
|
private bool _hasAllRefHandling;
|
|
|
|
internal bool HasRefHandling => _hasRefHandling;
|
|
internal bool HasAllRefHandling => _hasAllRefHandling;
|
|
public bool ThrowOnCircularReference => Options.ThrowOnCircularReference;
|
|
/// <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>
|
|
/// Slot-indexed wrapper cache. Shared between SGen types (RuntimeSlotCount+) and
|
|
/// runtime polymorphic type cache (0..RuntimeSlotCount-1).
|
|
/// Avoids dictionary lookup — direct array access (~1-2ns vs ~15-25ns).
|
|
/// Not cleared on pool return: wrapper references are stable across serialization calls.
|
|
/// </summary>
|
|
private TypeMetadataWrapper<TMetadata>?[]? _wrapperSlots;
|
|
|
|
/// <summary>
|
|
/// Factory function to create metadata. Implemented by derived class.
|
|
/// </summary>
|
|
protected abstract Func<Type, TMetadata> MetadataFactory { get; }
|
|
|
|
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
//public bool UseTypeReferenceHandling(Type type)
|
|
//{
|
|
// var wrapper = GetWrapper(type);
|
|
// return UseTypeReferenceHandling(wrapper.Metadata);
|
|
//}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public bool UseTypeReferenceHandling(TMetadata metaData)
|
|
{
|
|
return _hasRefHandling && (metaData.IsIId || !_hasIdHandling);
|
|
}
|
|
|
|
#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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or creates a wrapper for the specified type using a slot index.
|
|
/// Slot checked first (array access ~1-2ns), falls back to dictionary if slot empty.
|
|
/// Used by both SGen (compile-time slot) and runtime polymorphic cache (0..RuntimeSlotCount-1).
|
|
/// </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public TypeMetadataWrapper<TMetadata> GetWrapper(Type type, int slotIndex)
|
|
{
|
|
var slots = _wrapperSlots!;
|
|
var wrapper = slots[slotIndex];
|
|
if (wrapper != null)
|
|
return wrapper;
|
|
|
|
wrapper = GetWrapper(type);
|
|
slots[slotIndex] = wrapper;
|
|
return wrapper;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
protected TypeMetadataWrapper<TMetadata>? GetWrapperSlot(int slotIndex)
|
|
=> _wrapperSlots![slotIndex];
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
protected void SetWrapperSlot(int slotIndex, TypeMetadataWrapper<TMetadata> wrapper)
|
|
=> _wrapperSlots![slotIndex] = wrapper;
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
protected void ClearWrapperSlots(int count)
|
|
=> Array.Clear(_wrapperSlots!, 0, count);
|
|
|
|
/// <summary>
|
|
/// Pre-allocates the wrapper slot array with the known total slot count.
|
|
/// Called once from derived context constructor after all AllocateWrapperSlot() calls have completed.
|
|
/// </summary>
|
|
protected void InitializeWrapperSlots(int count)
|
|
{
|
|
if (count > 0)
|
|
_wrapperSlots = new TypeMetadataWrapper<TMetadata>?[count];
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Wrapper Iteration (for footer writing)
|
|
|
|
/// <summary>
|
|
/// Iterates all wrappers and collects InternEntry data for ID tracking footer.
|
|
/// </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public Dictionary<Type, TypeMetadataWrapper<TMetadata>>.ValueCollection GetWrappers() => _wrappers.Values;
|
|
|
|
/// <summary>
|
|
/// Returns wrappers as span for direct indexed access without allocation.
|
|
/// Uses CollectionsMarshal for zero-copy access to dictionary values.
|
|
/// </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public int GetWrapperCount() => _wrappers.Count;
|
|
|
|
#endregion
|
|
|
|
#region Reset
|
|
|
|
/// <summary>
|
|
/// Resets context for new operation. Sets options only.
|
|
/// </summary>
|
|
public virtual void Reset(TOptions options)
|
|
{
|
|
Options = options;
|
|
_hasRefHandling = options.ReferenceHandling != ReferenceHandlingMode.None;
|
|
_hasIdHandling = options.ReferenceHandling == ReferenceHandlingMode.OnlyId;
|
|
_hasAllRefHandling = options.ReferenceHandling == ReferenceHandlingMode.All;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clears tracking state for pool return. Returns IdentityMap arrays to pool.
|
|
/// </summary>
|
|
public virtual void Clear()
|
|
{
|
|
//System.Diagnostics.Debug.WriteLine($"Wrappers count: {_wrappers.Count}");
|
|
//Console.WriteLine($"Wrappers count: {_wrappers.Count}");
|
|
|
|
foreach (var wrapper in _wrappers.Values)
|
|
{
|
|
wrapper.ResetTracking(Options.UseAsync);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|