AyCode.Core/AyCode.Core/Serializers/AcSerializerContextBase.cs

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
}