using System;
using System.Buffers;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
namespace AyCode.Core.Serializers.Binaries;
public static partial class AcBinaryDeserializer
{
///
/// Pool for BinaryDeserializationContext instances.
/// Eliminates per-call heap allocation — mirrors BinarySerializationContextPool pattern.
///
private static class DeserializationContextPool where TInput : struct, IBinaryInputBase
{
private static readonly ConcurrentQueue> Pool = new();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static BinaryDeserializationContext Get(AcBinarySerializerOptions options)
{
if (Pool.TryDequeue(out var ctx))
{
ctx.Reset(options);
return ctx;
}
var newCtx = new BinaryDeserializationContext();
newCtx.Reset(options);
return newCtx;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Return(BinaryDeserializationContext ctx)
{
if (Pool.Count < ctx.Options.MaxContextPoolSize)
{
ctx.Clear();
Pool.Enqueue(ctx);
}
}
}
///
/// Binary deserialization context. Sealed class for pool reuse.
/// Holds all state: buffer, position, caches, options, metadata, interning.
/// Buffer state and read methods are directly in the context (via partial Read.cs)
/// for zero-indirection hot-path access — mirrors the serializer pattern.
/// TInput handles buffer lifecycle (Initialize/AdvanceSegment) — mirrors BinarySerializationContext<TOutput>.
///
internal sealed partial class BinaryDeserializationContext
: AcSerializerContextBase
where TInput : struct, IBinaryInputBase
{
///
/// Input target — handles only Initialize (startup) and AdvanceSegment (cold path).
///
public TInput Input;
// Marker-based interning: sequential cache (no footer needed)
private object?[]? _internCache;
public bool HasMetadata;
public bool IsMergeMode;
public bool RemoveOrphanedItems;
public bool FastWire;
// Options-derived properties
public byte MinStringInternLength => Options.MinStringInternLength;
///
/// Chain reference tracker for maintaining object identity across chain operations.
/// Only set when in chain mode (CreateDeserializeChain).
///
public AcSerializerCommon.ChainReferenceTracker? ChainTracker;
///
/// Returns true if in chain mode (ChainTracker is set).
///
public bool IsChainMode => ChainTracker != null;
// Pooled arrays - reused across deserializations, zero steady-state allocation
private int[]? _pooledDupData;
private object?[]? _pooledInternCache;
private int _pooledDupDataLength;
private int _pooledInternCacheLength;
private int _lastInternCacheUsed; // how many slots were used (for targeted Clear)
// Small arrays: keep across calls. Large arrays: return to pool in Clear().
private const int SmallArrayThreshold = 256;
// String cache - for WASM optimization
private Dictionary? _stringCache;
// Intern cache index counter
private int _nextCacheIndex;
// Removed: _linearizedBuffer was used by InitFromSpan (eliminated — all paths now use byte[] directly)
///
/// Inline metadata entries flat array.
///
private MetadataEntry[]? _metadataEntries;
private int _metadataEntryCount;
///
/// Runtime polymorphic slot counter. Indices 0..RuntimeSlotCount-1 stored in _wrapperSlots.
/// Overflow (index >= RuntimeSlotCount) stored in _polyOverflow.
///
internal int _nextRuntimeSlot;
///
/// Overflow array for polymorphic types beyond RuntimeSlotCount.
/// Only allocated when >RuntimeSlotCount distinct poly types (very rare).
/// Indexed by (polyIndex - RuntimeSlotCount).
///
private TypeMetadataWrapper[]? _polyOverflow;
///
/// A metadata entry for the deserializer.
///
internal struct MetadataEntry
{
public int PropNameHash;
public int[] PropertyHashes;
}
///
/// Factory for creating BinaryDeserializeTypeMetadata instances.
///
///
/// The lambda's t parameter explicitly declares DAMs to match the
/// signature — DAMs do not auto-propagate from the
/// delegate type to lambda parameters.
///
protected override MetadataFactoryDelegate MetadataFactory
=> static ([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(TypeMetadataBase.RequiredMembers)] t)
=> new BinaryDeserializeTypeMetadata(t);
public BinaryDeserializationContext()
{
InitializeWrapperSlots(Volatile.Read(ref AcBinarySerializer.s_nextWrapperSlot));
}
///
/// Initializes the context with the given TInput.
/// Calls Input.Initialize to set up buffer/position/length.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void InitInput(TInput input)
{
Input = input;
Input.Initialize(out _buffer, out _position, out _bufferLength);
_useStringCaching = Options.UseStringCaching;
_maxCachedStringLength = Options.MaxCachedStringLength;
if (_useStringCaching) GetOrCreateStringCache();
_internCache = null;
HasMetadata = false;
IsMergeMode = false;
RemoveOrphanedItems = false;
FastWire = Options.WireMode == WireMode.Fast;
ChainTracker = null;
}
// Removed: InitFromSpan — all deserialization now goes through byte[] directly (zero-copy).
#region Header
public void ReadHeader()
{
if (_position == 0 && IsAtEnd)
{
throw new AcBinaryDeserializationException("Binary payload is too short to contain a header.");
}
var version = ReadByte();
if (version != AcBinarySerializerOptions.FormatVersion)
{
throw new AcBinaryDeserializationException(
$"Unsupported binary format version '{version}'. Expected '{AcBinarySerializerOptions.FormatVersion}'.",
_position - 1);
}
var marker = ReadByte();
var hasPropertyTable = false;
if (marker == BinaryTypeCode.MetadataHeader)
{
hasPropertyTable = true;
Options.ReferenceHandling = ReferenceHandlingMode.OnlyId; // Legacy: assume OnlyId
}
else if (marker == BinaryTypeCode.NoMetadataHeader)
{
Options.ReferenceHandling = ReferenceHandlingMode.OnlyId; // Legacy: assume OnlyId
}
else if ((marker & 0xF0) == BinaryTypeCode.HeaderFlagsBase)
{
var flags = (byte)(marker & 0x0F);
hasPropertyTable = (flags & BinaryTypeCode.HeaderFlag_Metadata) != 0;
var hasOnlyId = (flags & BinaryTypeCode.HeaderFlag_RefHandling_OnlyId) != 0;
var hasAll = (flags & BinaryTypeCode.HeaderFlag_RefHandling_All) != 0;
Options.ReferenceHandling = hasAll ? ReferenceHandlingMode.All
: hasOnlyId ? ReferenceHandlingMode.OnlyId
: ReferenceHandlingMode.None;
var hasCacheCount = (flags & BinaryTypeCode.HeaderFlag_HasCacheCount) != 0;
if (hasCacheCount)
{
var cacheCount = (int)ReadVarUInt();
if (cacheCount > 0)
{
_internCache = RentInternCache(cacheCount);
SetInternCacheUsed(cacheCount);
}
}
}
else
{
throw new AcBinaryDeserializationException(
$"Unsupported binary header marker '{marker}'.",
_position - 1);
}
HasMetadata = hasPropertyTable;
}
#endregion
#region String Interning
///
/// Next intern cache index to assign when registering interned values.
///
internal ref int NextCacheIndexRef
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ref _nextCacheIndex;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RegisterNextInternedValue(object value)
{
_internCache![_nextCacheIndex++] = value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RegisterInternedValueAt(int cacheIndex, object value)
{
_internCache![cacheIndex] = value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string GetInternedString(int cacheIndex)
{
var result = _internCache![cacheIndex];
if (result == null)
{
throw new AcBinaryDeserializationException(
$"Interned string at cache index '{cacheIndex}' was not populated.",
_position);
}
return (string)result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public object GetInternedObject(int cacheIndex)
{
var result = _internCache![cacheIndex];
if (result == null)
{
throw new AcBinaryDeserializationException(
$"Interned object at cache index '{cacheIndex}' was not populated.",
_position);
}
return result;
}
#endregion
#region Pooled Arrays & Caches (merged from ContextClass)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Dictionary GetOrCreateStringCache()
{
return _stringCache ??= new Dictionary(128);
}
// Removed: RentLinearizedBuffer — was only used by InitFromSpan (eliminated).
public int[] RentDupData(int minLength)
{
if (_pooledDupData != null && _pooledDupDataLength >= minLength)
return _pooledDupData;
if (_pooledDupData != null)
ArrayPool.Shared.Return(_pooledDupData);
_pooledDupData = ArrayPool.Shared.Rent(minLength);
_pooledDupDataLength = _pooledDupData.Length;
return _pooledDupData;
}
public object?[] RentInternCache(int minLength)
{
if (_pooledInternCache != null && _pooledInternCacheLength >= minLength)
{
if (_lastInternCacheUsed > 0)
{
Array.Clear(_pooledInternCache, 0, _lastInternCacheUsed);
_lastInternCacheUsed = 0;
}
return _pooledInternCache;
}
if (_pooledInternCache != null)
ArrayPool