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.Shared.Return(_pooledInternCache, clearArray: true); _pooledInternCache = ArrayPool.Shared.Rent(minLength); _pooledInternCacheLength = _pooledInternCache.Length; _lastInternCacheUsed = 0; return _pooledInternCache; } public void SetInternCacheUsed(int count) { _lastInternCacheUsed = count; } #endregion #region Inline Metadata public void RegisterInlineMetadata(int propNameHash, int[] propertyHashes) { if (_metadataEntries == null || _metadataEntryCount >= _metadataEntries.Length) { var newSize = Math.Max((_metadataEntries?.Length ?? 0) * 2, 8); var newArray = new MetadataEntry[newSize]; if (_metadataEntries != null) Array.Copy(_metadataEntries, newArray, _metadataEntryCount); _metadataEntries = newArray; } _metadataEntries[_metadataEntryCount++] = new MetadataEntry { PropNameHash = propNameHash, PropertyHashes = propertyHashes }; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public int[]? FindSourceHashes(int propNameHash) { for (var i = 0; i < _metadataEntryCount; i++) { if (_metadataEntries![i].PropNameHash == propNameHash) return _metadataEntries[i].PropertyHashes; } return null; } #endregion #region Polymorphic Wrapper Cache /// /// Registers a wrapper in the polymorphic cache (called by ReadObjectWithTypeName/RefFirst). /// Indices 0..RuntimeSlotCount-1 → _wrapperSlots (fast path, ~1-2ns). /// Indices RuntimeSlotCount+ → _polyOverflow (still O(1) array access). /// [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void RegisterPolymorphicWrapper(TypeMetadataWrapper wrapper) { var slot = _nextRuntimeSlot++; if (slot < AcBinarySerializer.RuntimeSlotCount) { SetWrapperSlot(slot, wrapper); } else { RegisterPolymorphicWrapperOverflow(wrapper, slot - AcBinarySerializer.RuntimeSlotCount); } } [MethodImpl(MethodImplOptions.NoInlining)] private void RegisterPolymorphicWrapperOverflow(TypeMetadataWrapper wrapper, int overflowIndex) { if (_polyOverflow == null || overflowIndex >= _polyOverflow.Length) { var newSize = _polyOverflow == null ? 4 : _polyOverflow.Length * 2; var newArray = new TypeMetadataWrapper[newSize]; if (_polyOverflow != null) Array.Copy(_polyOverflow, newArray, overflowIndex); _polyOverflow = newArray; } _polyOverflow[overflowIndex] = wrapper; } /// /// Gets a previously registered polymorphic wrapper by index (called by ReadObjectWithTypeIndex/RefFirst). /// [MethodImpl(MethodImplOptions.AggressiveInlining)] internal TypeMetadataWrapper GetPolymorphicWrapper(int index) { if (index < AcBinarySerializer.RuntimeSlotCount) return GetWrapperSlot(index)!; return _polyOverflow![index - AcBinarySerializer.RuntimeSlotCount]!; } #endregion #region Reset & Clear public override void Reset(AcBinarySerializerOptions options) { base.Reset(options); } public override void Clear() { base.Clear(); _metadataEntryCount = 0; _nextCacheIndex = 0; // Clear runtime FixObj slots to prevent stale wrapper reuse on pool return. // Slot-to-type mapping changes between sessions (slot 0 may be TestOrder in session A // but TestGenericAttribute in session B). Without clearing, ReadObjectFromSlot // would reuse the stale wrapper → wrong type → stream misalignment. if (_nextRuntimeSlot > 0) { ClearWrapperSlots(Math.Min(_nextRuntimeSlot, AcBinarySerializer.RuntimeSlotCount)); } _nextRuntimeSlot = 0; // String cache: clear content but keep dictionary allocated for reuse _stringCache?.Clear(); // Intern cache: clear GC roots, return large arrays to pool if (_pooledInternCache != null) { if (_pooledInternCacheLength > SmallArrayThreshold) { ArrayPool.Shared.Return(_pooledInternCache, clearArray: true); _pooledInternCache = null; _pooledInternCacheLength = 0; } else if (_lastInternCacheUsed > 0) { Array.Clear(_pooledInternCache, 0, _lastInternCacheUsed); } _lastInternCacheUsed = 0; } // Dup data: no GC roots (int[]), return large arrays to pool if (_pooledDupData != null && _pooledDupDataLength > SmallArrayThreshold) { ArrayPool.Shared.Return(_pooledDupData); _pooledDupData = null; _pooledDupDataLength = 0; } // Removed: _linearizedBuffer cleanup — field eliminated with InitFromSpan. } #endregion } }