Refactor: unify deserialization context into single class

Eliminate BinaryDeserializationContextClass and merge all buffer, pooling, and cache logic into a single sealed BinaryDeserializationContext. All pooling and state management is now handled directly by the context, reducing allocations and indirection. All deserialization entry points and internal logic are updated to use the new context class and pool. String interning, metadata, and array pooling are now managed within the unified context, improving performance and maintainability.
This commit is contained in:
Loretta 2026-02-12 16:24:58 +01:00
parent 97e4315d12
commit 4c6342aa2b
6 changed files with 880 additions and 944 deletions

View File

@ -0,0 +1,377 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
namespace AyCode.Core.Serializers.Binaries;
public static partial class AcBinaryDeserializer
{
internal sealed partial class BinaryDeserializationContext
{
private static readonly Encoding Utf8NoBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false);
#region Buffer State owned by context for zero virtual dispatch
internal byte[] _buffer = null!;
internal int _bufferLength;
internal int _position;
#endregion
// String caching state — needed for WASM optimization
// The cache dictionary is owned by context (pooled), passed in at init time.
private bool _useStringCaching;
private int _maxCachedStringLength;
public bool IsAtEnd
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _position >= _bufferLength;
}
public int Position
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _position;
}
#region Core Read Methods
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte ReadByte()
{
if (_position >= _bufferLength)
{
throw new AcBinaryDeserializationException("Unexpected end of binary payload.", _position);
}
return _buffer[_position++];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte PeekByte()
{
if (_position >= _bufferLength)
{
throw new AcBinaryDeserializationException("Unexpected end of binary payload.", _position);
}
return _buffer[_position];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Skip(int count)
{
EnsureAvailable(count);
_position += count;
}
#endregion
#region Fixed-Width Reads
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public short ReadInt16Unsafe()
{
EnsureAvailable(2);
var value = Unsafe.ReadUnaligned<short>(ref _buffer[_position]);
_position += 2;
return value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ushort ReadUInt16Unsafe()
{
EnsureAvailable(2);
var value = Unsafe.ReadUnaligned<ushort>(ref _buffer[_position]);
_position += 2;
return value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public char ReadCharUnsafe()
{
EnsureAvailable(2);
var value = (char)Unsafe.ReadUnaligned<ushort>(ref _buffer[_position]);
_position += 2;
return value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public float ReadSingleUnsafe()
{
EnsureAvailable(4);
var bits = Unsafe.ReadUnaligned<int>(ref _buffer[_position]);
_position += 4;
return BitConverter.Int32BitsToSingle(bits);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public double ReadDoubleUnsafe()
{
EnsureAvailable(8);
var bits = Unsafe.ReadUnaligned<long>(ref _buffer[_position]);
_position += 8;
return BitConverter.Int64BitsToDouble(bits);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public decimal ReadDecimalUnsafe()
{
EnsureAvailable(16);
var span = _buffer.AsSpan(_position, 16);
var ints = MemoryMarshal.Cast<byte, int>(span);
var lo = ints[0];
var mid = ints[1];
var hi = ints[2];
var flags = ints[3];
var isNegative = (flags & unchecked((int)0x80000000)) != 0;
var scale = (byte)((flags >> 16) & 0x7F);
_position += 16;
return new decimal(lo, mid, hi, isNegative, scale);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public DateTime ReadDateTimeUnsafe()
{
EnsureAvailable(9);
var ticks = Unsafe.ReadUnaligned<long>(ref _buffer[_position]);
var kind = (DateTimeKind)_buffer[_position + 8];
_position += 9;
return new DateTime(ticks, kind);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public DateTimeOffset ReadDateTimeOffsetUnsafe()
{
EnsureAvailable(10);
var utcTicks = Unsafe.ReadUnaligned<long>(ref _buffer[_position]);
var offsetMinutes = Unsafe.ReadUnaligned<short>(ref _buffer[_position + 8]);
_position += 10;
var utcValue = new DateTime(utcTicks, DateTimeKind.Utc);
return new DateTimeOffset(utcValue).ToOffset(TimeSpan.FromMinutes(offsetMinutes));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public TimeSpan ReadTimeSpanUnsafe()
{
EnsureAvailable(8);
var ticks = Unsafe.ReadUnaligned<long>(ref _buffer[_position]);
_position += 8;
return new TimeSpan(ticks);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Guid ReadGuidUnsafe()
{
EnsureAvailable(16);
var value = new Guid(_buffer.AsSpan(_position, 16));
_position += 16;
return value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int ReadInt32Raw()
{
EnsureAvailable(4);
var value = Unsafe.ReadUnaligned<int>(ref _buffer[_position]);
_position += 4;
return value;
}
#endregion
#region VarInt Reading
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int ReadVarInt()
{
var raw = ReadVarUInt();
var value = (int)(raw >> 1) ^ -(int)(raw & 1);
return value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public uint ReadVarUInt()
{
// Fast path: single byte (0-127) - ~70% of cases
var b0 = _buffer[_position];
if ((b0 & 0x80) == 0)
{
_position++;
return b0;
}
// Fast path: two bytes (128-16383) - ~25% of cases
if (_position + 1 < _bufferLength)
{
var b1 = _buffer[_position + 1];
if ((b1 & 0x80) == 0)
{
_position += 2;
return (uint)(b0 & 0x7F) | ((uint)b1 << 7);
}
}
// Slow path: 3+ bytes - ~5% of cases
return ReadVarUIntSlow();
}
private uint ReadVarUIntSlow()
{
uint value = 0;
var shift = 0;
while (true)
{
var b = ReadByte();
value |= (uint)(b & 0x7F) << shift;
if ((b & 0x80) == 0)
{
break;
}
shift += 7;
if (shift > 35)
{
throw new AcBinaryDeserializationException("Invalid VarUInt encoding.", _position);
}
}
return value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public long ReadVarLong()
{
var raw = ReadVarULong();
var value = (long)(raw >> 1) ^ -((long)raw & 1);
return value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ulong ReadVarULong()
{
ulong value = 0;
var shift = 0;
while (true)
{
var b = ReadByte();
value |= (ulong)(b & 0x7F) << shift;
if ((b & 0x80) == 0)
{
break;
}
shift += 7;
if (shift > 70)
{
throw new AcBinaryDeserializationException("Invalid VarULong encoding.", _position);
}
}
return value;
}
#endregion
#region Bytes & String Reading
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte[] ReadBytes(int length)
{
if (length == 0)
{
return Array.Empty<byte>();
}
EnsureAvailable(length);
var result = GC.AllocateUninitializedArray<byte>(length);
_buffer.AsSpan(_position, length).CopyTo(result);
_position += length;
return result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ReadStringUtf8(int length)
{
if (length == 0)
{
return string.Empty;
}
EnsureAvailable(length);
// WASM optimization: cache short strings to reduce allocations
if (_useStringCaching && length <= _maxCachedStringLength)
{
return ReadStringUtf8Cached(length);
}
var value = Utf8NoBom.GetString(_buffer, _position, length);
_position += length;
return value;
}
private string ReadStringUtf8Cached(int length)
{
var slice = _buffer.AsSpan(_position, length);
var hash = ComputeStringHashFull(slice);
if (_stringCache!.TryGetValue(hash, out var cached))
{
if (cached.Length == length && VerifyAsciiUtf8Match(cached, slice))
{
_position += length;
return cached;
}
}
var value = Utf8NoBom.GetString(slice);
_stringCache[hash] = value;
_position += length;
return value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool VerifyAsciiUtf8Match(string cached, ReadOnlySpan<byte> utf8Bytes)
{
return Ascii.Equals(utf8Bytes, cached);
}
/// <summary>
/// Full-content hash for string caching.
/// CRITICAL: DO NOT SIMPLIFY — prevents hash collisions for similar property names.
/// See BinaryDeserializationContext for full history.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int ComputeStringHashFull(ReadOnlySpan<byte> data)
{
if (data.Length <= 32)
{
var hash = new HashCode();
hash.AddBytes(data);
return hash.ToHashCode();
}
var h = new HashCode();
h.Add(data.Length);
h.AddBytes(data.Slice(0, 8));
h.AddBytes(data.Slice(data.Length - 8, 8));
h.AddBytes(data.Slice(data.Length / 2 - 4, 8));
return h.ToHashCode();
}
#endregion
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void EnsureAvailable(int length)
{
if (_position > _bufferLength - length)
{
throw new AcBinaryDeserializationException("Unexpected end of binary payload.", _position);
}
}
}
}

View File

@ -1,87 +1,161 @@
using System;
using System.Buffers.Binary;
using System.Buffers;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
namespace AyCode.Core.Serializers.Binaries;
public static partial class AcBinaryDeserializer
{
/// <summary>
/// Binary deserialization context. Public for generated serializers.
/// Uses composition with pooled BinaryDeserializationContextClass for zero-alloc deserialization.
/// String cache and intern cache index are delegated to ContextClass for pool reuse.
/// Pool for BinaryDeserializationContext instances.
/// Eliminates per-call heap allocation — mirrors BinarySerializationContextPool pattern.
/// </summary>
internal ref struct BinaryDeserializationContext
private static class DeserializationContextPool
{
private readonly ReadOnlySpan<byte> _buffer;
private int _position;
private static readonly ConcurrentQueue<BinaryDeserializationContext> 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);
}
}
}
/// <summary>
/// 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.
/// </summary>
internal sealed partial class BinaryDeserializationContext
: AcSerializerContextBase<BinaryDeserializeTypeMetadata, AcBinarySerializerOptions>
{
// Marker-based interning: sequential cache (no footer needed)
// StringInternFirst/ObjectRefFirst markers register values in order
private object?[]? _internCache; // Shared cache for interned strings AND IId objects
private object?[]? _internCache;
/// <summary>
/// Heap-allocated context class for IId-based reference tracking.
/// Pooled via DeserializationContextClassPool — holds caches, options, metadata.
/// </summary>
public readonly BinaryDeserializationContextClass ContextClass;
public bool HasMetadata;
public bool IsMergeMode;
public bool RemoveOrphanedItems;
public bool HasMetadata { get; private set; }
/// <summary>
/// Convenience property - true if any reference handling is enabled.
/// </summary>
//public readonly bool HasReferenceHandling => ContextClass.ReferenceHandling != ReferenceHandlingMode.None;
public bool IsMergeMode { readonly get; set; }
public bool RemoveOrphanedItems { readonly get; set; }
public readonly bool IsAtEnd => _position >= _buffer.Length;
public readonly int Position => _position;
// Options-derived properties - delegate to ContextClass.Options
public readonly byte MinStringInternLength => ContextClass.Options.MinStringInternLength;
public readonly bool UseStringCaching => ContextClass.Options.UseStringCaching;
public readonly int MaxCachedStringLength => ContextClass.Options.MaxCachedStringLength;
// Options-derived properties
public byte MinStringInternLength => Options.MinStringInternLength;
/// <summary>
/// Chain reference tracker for maintaining object identity across chain operations.
/// Only set when in chain mode (CreateDeserializeChain).
/// </summary>
public AcSerializerCommon.ChainReferenceTracker? ChainTracker { readonly get; set; }
public AcSerializerCommon.ChainReferenceTracker? ChainTracker;
/// <summary>
/// Returns true if in chain mode (ChainTracker is set).
/// </summary>
public readonly bool IsChainMode => ChainTracker != null;
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<int, string>? _stringCache;
// Intern cache index counter
private int _nextCacheIndex;
// Linearized buffer for ReadOnlySequence<byte> input
private byte[]? _linearizedBuffer;
/// <summary>
/// Creates a deserialization context with a pooled ContextClass.
/// ContextClass must already be Reset() with options before passing in.
/// Inline metadata entries flat array.
/// </summary>
public BinaryDeserializationContext(ReadOnlySpan<byte> data, BinaryDeserializationContextClass contextClass)
private MetadataEntry[]? _metadataEntries;
private int _metadataEntryCount;
/// <summary>
/// A metadata entry for the deserializer.
/// </summary>
internal struct MetadataEntry
{
public int PropNameHash;
public int[] PropertyHashes;
}
/// <summary>
/// Factory for creating BinaryDeserializeTypeMetadata instances.
/// </summary>
protected override Func<Type, BinaryDeserializeTypeMetadata> MetadataFactory
=> static t => new BinaryDeserializeTypeMetadata(t);
public BinaryDeserializationContext()
{
}
/// <summary>
/// Sets the buffer for a new deserialization operation from a byte array.
/// Zero-copy: context references the byte[] directly.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void InitBuffer(byte[] data, int length)
{
_buffer = data;
_bufferLength = length;
_position = 0;
// Marker-based interning fields
_useStringCaching = Options.UseStringCaching;
_maxCachedStringLength = Options.MaxCachedStringLength;
if (_useStringCaching) GetOrCreateStringCache();
_internCache = null;
HasMetadata = false;
IsMergeMode = false;
RemoveOrphanedItems = false;
ChainTracker = null;
ContextClass = contextClass;
}
/// <summary>
/// Sets the buffer from a ReadOnlySpan by copying to a linearized buffer.
/// Used when the source is not a byte[] (e.g. stackalloc, pinned).
/// </summary>
public void InitBufferFromSpan(ReadOnlySpan<byte> data)
{
var buffer = RentLinearizedBuffer(data.Length);
data.CopyTo(buffer);
InitBuffer(buffer, data.Length);
}
#region Header
public void ReadHeader()
{
if (_buffer.Length < 2)
if (_position == 0 && IsAtEnd)
{
throw new AcBinaryDeserializationException("Binary payload is too short to contain a header.");
}
var version = ReadByteInternal();
var version = ReadByte();
if (version != AcBinarySerializerOptions.FormatVersion)
{
throw new AcBinaryDeserializationException(
@ -89,38 +163,36 @@ public static partial class AcBinaryDeserializer
_position - 1);
}
var marker = ReadByteInternal();
var marker = ReadByte();
var hasPropertyTable = false;
if (marker == BinaryTypeCode.MetadataHeader)
{
hasPropertyTable = true;
ContextClass.Options.ReferenceHandling = ReferenceHandlingMode.OnlyId; // Legacy: assume OnlyId
Options.ReferenceHandling = ReferenceHandlingMode.OnlyId; // Legacy: assume OnlyId
}
else if (marker == BinaryTypeCode.NoMetadataHeader)
{
ContextClass.Options.ReferenceHandling = ReferenceHandlingMode.OnlyId; // Legacy: assume OnlyId
Options.ReferenceHandling = ReferenceHandlingMode.OnlyId; // Legacy: assume OnlyId
}
else if ((marker & 0xF0) == BinaryTypeCode.HeaderFlagsBase)
{
var flags = (byte)(marker & 0x0F);
hasPropertyTable = (flags & BinaryTypeCode.HeaderFlag_Metadata) != 0;
// Decode ReferenceHandlingMode from separate bits
var hasOnlyId = (flags & BinaryTypeCode.HeaderFlag_RefHandling_OnlyId) != 0;
var hasAll = (flags & BinaryTypeCode.HeaderFlag_RefHandling_All) != 0;
ContextClass.Options.ReferenceHandling = hasAll ? ReferenceHandlingMode.All
Options.ReferenceHandling = hasAll ? ReferenceHandlingMode.All
: hasOnlyId ? ReferenceHandlingMode.OnlyId
: ReferenceHandlingMode.None;
// Read cache count if flag is set (marker-based format)
var hasCacheCount = (flags & BinaryTypeCode.HeaderFlag_HasCacheCount) != 0;
if (hasCacheCount)
{
var cacheCount = (int)ReadVarUInt();
if (cacheCount > 0)
{
_internCache = ContextClass.RentInternCache(cacheCount);
ContextClass.SetInternCacheUsed(cacheCount);
_internCache = RentInternCache(cacheCount);
SetInternCacheUsed(cacheCount);
}
}
}
@ -134,407 +206,31 @@ public static partial class AcBinaryDeserializer
HasMetadata = hasPropertyTable;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte ReadByte() => ReadByteInternal();
#endregion
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private byte ReadByteInternal()
{
if (_position >= _buffer.Length)
{
throw new AcBinaryDeserializationException("Unexpected end of binary payload.", _position);
}
return _buffer[_position++];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte PeekByte()
{
if (_position >= _buffer.Length)
{
throw new AcBinaryDeserializationException("Unexpected end of binary payload.", _position);
}
return _buffer[_position];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public short ReadInt16Unsafe()
{
EnsureAvailable(2);
var value = BinaryPrimitives.ReadInt16LittleEndian(_buffer.Slice(_position, 2));
_position += 2;
return value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ushort ReadUInt16Unsafe()
{
EnsureAvailable(2);
var value = BinaryPrimitives.ReadUInt16LittleEndian(_buffer.Slice(_position, 2));
_position += 2;
return value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public char ReadCharUnsafe()
{
EnsureAvailable(2);
var value = (char)BinaryPrimitives.ReadUInt16LittleEndian(_buffer.Slice(_position, 2));
_position += 2;
return value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public float ReadSingleUnsafe()
{
EnsureAvailable(4);
var bits = BinaryPrimitives.ReadInt32LittleEndian(_buffer.Slice(_position, 4));
_position += 4;
return BitConverter.Int32BitsToSingle(bits);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public double ReadDoubleUnsafe()
{
EnsureAvailable(8);
var bits = BinaryPrimitives.ReadInt64LittleEndian(_buffer.Slice(_position, 8));
_position += 8;
return BitConverter.Int64BitsToDouble(bits);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public decimal ReadDecimalUnsafe()
{
EnsureAvailable(16);
var ints = MemoryMarshal.Cast<byte, int>(_buffer.Slice(_position, 16));
var lo = ints[0];
var mid = ints[1];
var hi = ints[2];
var flags = ints[3];
var isNegative = (flags & unchecked((int)0x80000000)) != 0;
var scale = (byte)((flags >> 16) & 0x7F);
_position += 16;
return new decimal(lo, mid, hi, isNegative, scale);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public DateTime ReadDateTimeUnsafe()
{
EnsureAvailable(9);
var ticks = BinaryPrimitives.ReadInt64LittleEndian(_buffer.Slice(_position, 8));
var kind = (DateTimeKind)_buffer[_position + 8];
_position += 9;
return new DateTime(ticks, kind);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public DateTimeOffset ReadDateTimeOffsetUnsafe()
{
EnsureAvailable(10);
var utcTicks = BinaryPrimitives.ReadInt64LittleEndian(_buffer.Slice(_position, 8));
var offsetMinutes = BinaryPrimitives.ReadInt16LittleEndian(_buffer.Slice(_position + 8, 2));
_position += 10;
var utcValue = new DateTime(utcTicks, DateTimeKind.Utc);
return new DateTimeOffset(utcValue).ToOffset(TimeSpan.FromMinutes(offsetMinutes));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public TimeSpan ReadTimeSpanUnsafe()
{
EnsureAvailable(8);
var ticks = BinaryPrimitives.ReadInt64LittleEndian(_buffer.Slice(_position, 8));
_position += 8;
return new TimeSpan(ticks);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Guid ReadGuidUnsafe()
{
EnsureAvailable(16);
var value = new Guid(_buffer.Slice(_position, 16));
_position += 16;
return value;
}
#region String Interning
/// <summary>
/// Optimized VarInt reader with fast path for 1-2 byte values (most common case).
/// Uses ZigZag decoding to handle signed integers.
/// Next intern cache index to assign when registering interned values.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int ReadVarInt()
internal ref int NextCacheIndexRef
{
var raw = ReadVarUInt();
// ZigZag decode: handle full uint range before casting to int
// This correctly handles values like int.MaxValue which encode to uint > int.MaxValue
var value = (int)(raw >> 1) ^ -(int)(raw & 1);
return value;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ref _nextCacheIndex;
}
/// <summary>
/// Optimized VarUInt reader with fast path for 1-2 byte values.
/// Most VarInts in real data are small (property indices, array lengths, etc.)
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public uint ReadVarUInt()
{
// Fast path: single byte (0-127) - ~70% of cases
var b0 = _buffer[_position];
if ((b0 & 0x80) == 0)
{
_position++;
return b0;
}
// Fast path: two bytes (128-16383) - ~25% of cases
if (_position + 1 < _buffer.Length)
{
var b1 = _buffer[_position + 1];
if ((b1 & 0x80) == 0)
{
_position += 2;
return (uint)(b0 & 0x7F) | ((uint)b1 << 7);
}
}
// Slow path: 3+ bytes - ~5% of cases
return ReadVarUIntSlow();
}
private uint ReadVarUIntSlow()
{
uint value = 0;
var shift = 0;
while (true)
{
var b = ReadByteInternal();
value |= (uint)(b & 0x7F) << shift;
if ((b & 0x80) == 0)
{
break;
}
shift += 7;
if (shift > 35)
{
throw new AcBinaryDeserializationException("Invalid VarUInt encoding.", _position);
}
}
return value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public long ReadVarLong()
{
var raw = ReadVarULong();
var value = (long)(raw >> 1) ^ -((long)raw & 1);
return value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ulong ReadVarULong()
{
ulong value = 0;
var shift = 0;
while (true)
{
var b = ReadByteInternal();
value |= (ulong)(b & 0x7F) << shift;
if ((b & 0x80) == 0)
{
break;
}
shift += 7;
if (shift > 70)
{
throw new AcBinaryDeserializationException("Invalid VarULong encoding.", _position);
}
}
return value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public byte[] ReadBytes(int length)
{
if (length == 0)
{
return Array.Empty<byte>();
}
EnsureAvailable(length);
var result = GC.AllocateUninitializedArray<byte>(length);
_buffer.Slice(_position, length).CopyTo(result);
_position += length;
return result;
}
/// <summary>
/// Read UTF8 string with optional caching for WASM optimization.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ReadStringUtf8(int length)
{
if (length == 0)
{
return string.Empty;
}
EnsureAvailable(length);
// WASM optimization: cache short strings to reduce allocations
if (UseStringCaching && length <= MaxCachedStringLength)
{
return ReadStringUtf8Cached(length);
}
var value = Utf8NoBom.GetString(_buffer.Slice(_position, length));
_position += length;
return value;
}
/// <summary>
/// Read string with caching - reduces allocations in WASM.
/// </summary>
private string ReadStringUtf8Cached(int length)
{
// CRITICAL FIX (2025-01-24): Use full-content hash to avoid collisions.
// BUG: Property names like "Creator" and "Created" have same length (7) and same
// first 4 bytes ("Crea"), causing hash collision with the old hash function.
// This caused WASM deserialization failures where "Creator" (int) value was
// incorrectly assigned to "Created" (DateTime) property.
// DO NOT REMOVE OR SIMPLIFY THIS HASH FUNCTION!
// See: ComputeStringHashFull() for the fix.
var slice = _buffer.Slice(_position, length);
var hash = ComputeStringHashFull(slice);
var stringCache = ContextClass.GetOrCreateStringCache();
if (stringCache.TryGetValue(hash, out var cached))
{
// CRITICAL: Verify actual content matches to prevent hash collision data corruption.
// cached.Length is char count, length is UTF-8 byte count — equal only for ASCII.
// For ASCII strings (common case): byte-by-byte comparison against UTF-8 buffer.
if (cached.Length == length && VerifyAsciiUtf8Match(cached, slice))
{
_position += length;
return cached;
}
// Hash collision or non-ASCII length mismatch - fall through to decode
}
var value = Utf8NoBom.GetString(slice);
stringCache[hash] = value;
_position += length;
return value;
}
/// <summary>
/// Verifies that a cached ASCII string matches the UTF-8 bytes exactly.
/// For ASCII, each char's low byte equals the UTF-8 byte.
/// Caller guarantees cached.Length == utf8Bytes.Length (ASCII invariant).
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool VerifyAsciiUtf8Match(string cached, ReadOnlySpan<byte> utf8Bytes)
{
for (var i = 0; i < cached.Length; i++)
{
if ((byte)cached[i] != utf8Bytes[i])
return false;
}
return true;
}
/// <summary>
/// Compute hash that includes ALL bytes for short strings to avoid collisions.
///
/// CRITICAL FIX (2025-01-24): DO NOT MODIFY THIS FUNCTION!
///
/// PROBLEM: The original hash function only used first 4 bytes + length.
/// This caused hash collisions in WASM for similar property names like:
/// - "Creator" vs "Created" (both 7 bytes, both start with "Crea")
/// - "Modifier" vs "Modified" (both 8 bytes, both start with "Modi")
///
/// SYMPTOM: In WASM, when "Created" was cached first, reading "Creator" returned
/// "Created" from cache, causing type mismatch (int value ? DateTime property).
/// Error: "Cannot set property 'Created' - PropertyType: DateTime, ValueType: Int32"
///
/// FIX: Hash ALL bytes for strings ?32 bytes (covers all typical property names).
/// This eliminates collisions between similar property names completely.
///
/// PERFORMANCE: ~5% slower hash computation, but zero-allocation cache hits.
/// This is acceptable for the reliability improvement.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int ComputeStringHashFull(ReadOnlySpan<byte> data)
{
// For strings up to 32 bytes (covers most property names), hash ALL bytes
// This completely eliminates collisions like Creator/Created
if (data.Length <= 32)
{
var hash = new HashCode();
hash.AddBytes(data);
return hash.ToHashCode();
}
// For longer strings (rare for property names), use sampling strategy:
// first 8 bytes + last 8 bytes + middle 8 bytes + length
// This provides good collision resistance with O(1) performance
var h = new HashCode();
h.Add(data.Length);
h.AddBytes(data.Slice(0, 8));
h.AddBytes(data.Slice(data.Length - 8, 8));
h.AddBytes(data.Slice(data.Length / 2 - 4, 8));
return h.ToHashCode();
}
/// <summary>
/// Reads a raw 4-byte int32 (little-endian) without type code.
/// Used for reading type name hashes in UseMetadata mode.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int ReadInt32Raw()
{
EnsureAvailable(4);
var value = Unsafe.ReadUnaligned<int>(ref Unsafe.AsRef(in _buffer[_position]));
_position += 4;
return value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Skip(int count)
{
EnsureAvailable(count);
_position += count;
}
/// <summary>
/// Registers an interned value (string or object) in the cache.
/// Called when StringInternFirst/ObjectRefFirst marker is encountered.
/// Sequential: values are registered in order (0, 1, 2, ...).
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RegisterNextInternedValue(object value)
{
_internCache![ContextClass.NextCacheIndexRef++] = value;
_internCache![_nextCacheIndex++] = value;
}
/// <summary>
/// Registers an interned value at a specific cache index.
/// Used when the serializer writes explicit cache indices (non-sequential).
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RegisterInternedValueAt(int cacheIndex, object value)
{
_internCache![cacheIndex] = value;
}
/// <summary>
/// Gets an interned string by cache index (StringInterned type code).
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string GetInternedString(int cacheIndex)
{
@ -549,9 +245,6 @@ public static partial class AcBinaryDeserializer
return (string)result;
}
/// <summary>
/// Gets an interned object by cache index (ObjectRef type code).
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public object GetInternedObject(int cacheIndex)
{
@ -566,13 +259,155 @@ public static partial class AcBinaryDeserializer
return result;
}
private void EnsureAvailable(int length)
#endregion
#region Pooled Arrays & Caches (merged from ContextClass)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Dictionary<int, string> GetOrCreateStringCache()
{
if (_position > _buffer.Length - length)
return _stringCache ??= new Dictionary<int, string>(128);
}
/// <summary>
/// Rents a linearized buffer for ReadOnlySequence multi-segment input.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal byte[] RentLinearizedBuffer(int minSize)
{
if (_linearizedBuffer != null && _linearizedBuffer.Length >= minSize)
return _linearizedBuffer;
if (_linearizedBuffer != null)
ArrayPool<byte>.Shared.Return(_linearizedBuffer);
_linearizedBuffer = ArrayPool<byte>.Shared.Rent(minSize);
return _linearizedBuffer;
}
public int[] RentDupData(int minLength)
{
if (_pooledDupData != null && _pooledDupDataLength >= minLength)
return _pooledDupData;
if (_pooledDupData != null)
ArrayPool<int>.Shared.Return(_pooledDupData);
_pooledDupData = ArrayPool<int>.Shared.Rent(minLength);
_pooledDupDataLength = _pooledDupData.Length;
return _pooledDupData;
}
public object?[] RentInternCache(int minLength)
{
if (_pooledInternCache != null && _pooledInternCacheLength >= minLength)
{
throw new AcBinaryDeserializationException("Unexpected end of binary payload.", _position);
if (_lastInternCacheUsed > 0)
{
Array.Clear(_pooledInternCache, 0, _lastInternCacheUsed);
_lastInternCacheUsed = 0;
}
return _pooledInternCache;
}
if (_pooledInternCache != null)
ArrayPool<object?>.Shared.Return(_pooledInternCache, clearArray: true);
_pooledInternCache = ArrayPool<object?>.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 Reset & Clear
public override void Reset(AcBinarySerializerOptions options)
{
base.Reset(options);
}
public override void Clear()
{
base.Clear();
_metadataEntryCount = 0;
_nextCacheIndex = 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<object?>.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<int>.Shared.Return(_pooledDupData);
_pooledDupData = null;
_pooledDupDataLength = 0;
}
// Linearized buffer: no GC roots (byte[]), keep small, return large
if (_linearizedBuffer != null && _linearizedBuffer.Length > SmallArrayThreshold)
{
ArrayPool<byte>.Shared.Return(_linearizedBuffer);
_linearizedBuffer = null;
}
}
#endregion
}
}

View File

@ -1,277 +0,0 @@
using System;
using System.Buffers;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace AyCode.Core.Serializers.Binaries;
public static partial class AcBinaryDeserializer
{
/// <summary>
/// Pool for BinaryDeserializationContextClass instances.
/// Eliminates per-call heap allocation — mirrors BinarySerializationContextPool pattern.
/// </summary>
private static class DeserializationContextClassPool
{
private static readonly ConcurrentQueue<BinaryDeserializationContextClass> Pool = new();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static BinaryDeserializationContextClass Get(AcBinarySerializerOptions options)
{
if (Pool.TryDequeue(out var ctx))
{
ctx.Reset(options);
return ctx;
}
var newCtx = new BinaryDeserializationContextClass();
newCtx.Reset(options);
return newCtx;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Return(BinaryDeserializationContextClass ctx)
{
if (Pool.Count < ctx.Options.MaxContextPoolSize)
{
ctx.Clear();
Pool.Enqueue(ctx);
}
}
}
/// <summary>
/// Heap-allocated context class for binary deserialization.
/// Inherits from AcSerializerContextBase for unified metadata caching and IId-based reference tracking.
/// Used in composition with the ref struct BinaryDeserializationContext.
/// Holds pooled arrays and caches for reuse across deserializations.
/// </summary>
internal sealed class BinaryDeserializationContextClass : AcSerializerContextBase<BinaryDeserializeTypeMetadata, AcBinarySerializerOptions>
{
/// <summary>
/// Factory for creating BinaryDeserializeTypeMetadata instances.
/// </summary>
protected override Func<Type, BinaryDeserializeTypeMetadata> MetadataFactory
=> static t => new BinaryDeserializeTypeMetadata(t);
// 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 - moved from ref struct for pool reuse (WASM optimization)
private Dictionary<int, string>? _stringCache;
// Intern cache index counter - moved from ref struct for pool reuse
private int _nextCacheIndex;
// Linearized buffer for ReadOnlySequence<byte> input
private byte[]? _linearizedBuffer;
/// <summary>
/// String cache for WASM optimization. Reused across deserializations.
/// </summary>
internal Dictionary<int, string>? StringCache => _stringCache;
/// <summary>
/// Gets or lazily creates the string cache with initial capacity.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Dictionary<int, string> GetOrCreateStringCache()
{
return _stringCache ??= new Dictionary<int, string>(128);
}
/// <summary>
/// Next intern cache index to assign when registering interned values.
/// </summary>
internal ref int NextCacheIndexRef
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ref _nextCacheIndex;
}
/// <summary>
/// Rents a linearized buffer for ReadOnlySequence multi-segment input.
/// Buffer is pooled and reused across deserializations.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal byte[] RentLinearizedBuffer(int minSize)
{
if (_linearizedBuffer != null && _linearizedBuffer.Length >= minSize)
return _linearizedBuffer;
if (_linearizedBuffer != null)
ArrayPool<byte>.Shared.Return(_linearizedBuffer);
_linearizedBuffer = ArrayPool<byte>.Shared.Rent(minSize);
return _linearizedBuffer;
}
/// <summary>
/// Inline metadata bejegyzések flat array-ben.
/// A propNameHash alapján lineárisan keresünk (kis számú típus per stream).
/// Minden entry: source típus propNameHash + source property hash-ek.
/// </summary>
private MetadataEntry[]? _metadataEntries;
private int _metadataEntryCount;
/// <summary>
/// Egy metadata bejegyzés a deserializer számára.
/// </summary>
internal struct MetadataEntry
{
public int PropNameHash; // source típus propNameHash (FNV-1a)
public int[] PropertyHashes; // source property hash-ek sorrendben
}
public BinaryDeserializationContextClass()
{
}
/// <summary>
/// Gets or allocates a pooled int[] for dup data (position/cacheIndex pairs).
/// </summary>
public int[] RentDupData(int minLength)
{
if (_pooledDupData != null && _pooledDupDataLength >= minLength)
return _pooledDupData;
// Too small - return old and rent bigger
if (_pooledDupData != null)
ArrayPool<int>.Shared.Return(_pooledDupData);
_pooledDupData = ArrayPool<int>.Shared.Rent(minLength);
_pooledDupDataLength = _pooledDupData.Length;
return _pooledDupData;
}
/// <summary>
/// Gets or allocates a pooled object?[] for intern cache.
/// </summary>
public object?[] RentInternCache(int minLength)
{
if (_pooledInternCache != null && _pooledInternCacheLength >= minLength)
{
// Reuse - clear previous content (release GC roots)
if (_lastInternCacheUsed > 0)
{
Array.Clear(_pooledInternCache, 0, _lastInternCacheUsed);
_lastInternCacheUsed = 0;
}
return _pooledInternCache;
}
// Too small - return old and rent bigger
if (_pooledInternCache != null)
ArrayPool<object?>.Shared.Return(_pooledInternCache, clearArray: true);
_pooledInternCache = ArrayPool<object?>.Shared.Rent(minLength);
_pooledInternCacheLength = _pooledInternCache.Length;
_lastInternCacheUsed = 0;
return _pooledInternCache;
}
/// <summary>
/// Tracks how many intern cache slots were used (for targeted Clear).
/// </summary>
public void SetInternCacheUsed(int count)
{
_lastInternCacheUsed = count;
}
/// <summary>
/// Inline metadata regisztrálása az első előforduláskor.
/// A stream-ből beolvasott source property hash-ek tárolása propNameHash alapján.
/// </summary>
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
};
}
/// <summary>
/// Source property hash-ek keresése propNameHash alapján.
/// Lineáris keresés — kis számú típus per stream (tipikusan &lt; 10).
/// Null ha nincs találat (első előfordulás, hash-eket olvasni kell a stream-ből).
/// </summary>
[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;
}
public override void Clear()
{
base.Clear();
_metadataEntryCount = 0;
_nextCacheIndex = 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)
{
// Large: return to pool - no pre-rent needed, ReadFooterIndices will rent on demand
ArrayPool<object?>.Shared.Return(_pooledInternCache, clearArray: true);
_pooledInternCache = null;
_pooledInternCacheLength = 0;
}
else if (_lastInternCacheUsed > 0)
{
// Small: keep array, just clear content (release GC roots)
Array.Clear(_pooledInternCache, 0, _lastInternCacheUsed);
}
_lastInternCacheUsed = 0;
}
// Dup data: no GC roots (int[]), return large arrays to pool
if (_pooledDupData != null && _pooledDupDataLength > SmallArrayThreshold)
{
ArrayPool<int>.Shared.Return(_pooledDupData);
_pooledDupData = null;
_pooledDupDataLength = 0;
}
// Linearized buffer: no GC roots (byte[]), keep small, return large
if (_linearizedBuffer != null && _linearizedBuffer.Length > SmallArrayThreshold)
{
ArrayPool<byte>.Shared.Return(_linearizedBuffer);
_linearizedBuffer = null;
}
}
public override void Reset(AcBinarySerializerOptions options)
{
Clear();
base.Reset(options);
}
}
}

View File

@ -55,13 +55,13 @@ public static partial class AcBinaryDeserializer
// Cross-type path: use index mapping
var indexMapping = GetIndexMapping(sourceType, destType, options.PropertyMapper);
var ctxClass = DeserializationContextClassPool.Get(options);
var context = new BinaryDeserializationContext(data, ctxClass);
var context = DeserializationContextPool.Get(options);
context.InitBufferFromSpan(data);
try
{
context.ReadHeader();
var result = ReadValueWithMapping(ref context, destType, indexMapping, 0);
var result = ReadValueWithMapping(context, destType, indexMapping, 0);
return (TDest?)result;
}
catch (AcBinaryDeserializationException)
@ -76,7 +76,7 @@ public static partial class AcBinaryDeserializer
}
finally
{
DeserializationContextClassPool.Return(ctxClass);
DeserializationContextPool.Return(context);
}
}
@ -141,8 +141,8 @@ public static partial class AcBinaryDeserializer
// Cross-type path: use index mapping
var indexMapping = GetIndexMapping(sourceType, destType, options.PropertyMapper);
var ctxClass = DeserializationContextClassPool.Get(options);
var context = new BinaryDeserializationContext(data, ctxClass);
var context = DeserializationContextPool.Get(options);
context.InitBufferFromSpan(data);
try
{
@ -158,7 +158,7 @@ public static partial class AcBinaryDeserializer
if (typeCode == BinaryTypeCode.Object)
{
context.ReadByte(); // consume Object marker
PopulateObjectWithMapping(ref context, target, destType, indexMapping, 0);
PopulateObjectWithMapping(context, target, destType, indexMapping, 0);
}
else
{
@ -179,7 +179,7 @@ public static partial class AcBinaryDeserializer
}
finally
{
DeserializationContextClassPool.Return(ctxClass);
DeserializationContextPool.Return(context);
}
}
@ -207,16 +207,16 @@ public static partial class AcBinaryDeserializer
/// Reads a value with index mapping applied.
/// Maps source PropertyIndex to destination PropertyIndex using the provided mapping.
/// </summary>
private static object? ReadValueWithMapping(ref BinaryDeserializationContext context, Type destType, int[] indexMapping, int depth)
private static object? ReadValueWithMapping(BinaryDeserializationContext context, Type destType, int[] indexMapping, int depth)
{
var typeCode = context.ReadByte();
return typeCode switch
{
BinaryTypeCode.Null => null,
BinaryTypeCode.Object => ReadObjectWithMapping(ref context, destType, indexMapping, depth, registerInCache: false),
BinaryTypeCode.ObjectRefFirst => ReadObjectWithMapping(ref context, destType, indexMapping, depth, registerInCache: true),
_ => ReadValue(ref context, destType, depth) // Primitives, arrays, etc. use normal path
BinaryTypeCode.Object => ReadObjectWithMapping(context, destType, indexMapping, depth, registerInCache: false),
BinaryTypeCode.ObjectRefFirst => ReadObjectWithMapping(context, destType, indexMapping, depth, registerInCache: true),
_ => ReadValue(context, destType, depth) // Primitives, arrays, etc. use normal path
};
}
@ -224,9 +224,9 @@ public static partial class AcBinaryDeserializer
/// Reads an object using index mapping for property resolution.
/// Note: Object marker already consumed by caller.
/// </summary>
private static object? ReadObjectWithMapping(ref BinaryDeserializationContext context, Type destType, int[] indexMapping, int depth, bool registerInCache)
private static object? ReadObjectWithMapping(BinaryDeserializationContext context, Type destType, int[] indexMapping, int depth, bool registerInCache)
{
var wrapper = context.ContextClass.GetWrapper(destType);
var wrapper = context.GetWrapper(destType);
var metadata = wrapper.Metadata;
var obj = CreateInstance(destType, metadata);
@ -237,7 +237,7 @@ public static partial class AcBinaryDeserializer
context.RegisterNextInternedValue(obj);
}
PopulateObjectWithMapping(ref context, obj, destType, indexMapping, depth);
PopulateObjectWithMapping(context, obj, destType, indexMapping, depth);
}
return obj;
}
@ -247,13 +247,13 @@ public static partial class AcBinaryDeserializer
/// Source property indices are remapped to destination indices.
/// </summary>
private static void PopulateObjectWithMapping(
ref BinaryDeserializationContext context,
BinaryDeserializationContext context,
object target,
Type destType,
int[] indexMapping,
int depth)
{
var wrapper = context.ContextClass.GetWrapper(destType);
var wrapper = context.GetWrapper(destType);
var metadata = wrapper.Metadata;
var propertyCount = (int)context.ReadVarUInt();
var nextDepth = depth + 1;
@ -268,7 +268,7 @@ public static partial class AcBinaryDeserializer
if (destPropIndex == -1)
{
// No mapping - skip this property
SkipValue(ref context, metadata);
SkipValue(context, metadata);
continue;
}
@ -277,12 +277,12 @@ public static partial class AcBinaryDeserializer
if (propInfo == null)
{
// Destination property not found - skip
SkipValue(ref context, metadata);
SkipValue(context, metadata);
continue;
}
// Reuse common populate logic
PopulatePropertyValue(ref context, target, propInfo, wrapper, nextDepth, sourcePropIndex, destPropIndex, i, propertyCount, depth);
PopulatePropertyValue(context, target, propInfo, wrapper, nextDepth, sourcePropIndex, destPropIndex, i, propertyCount, depth);
}
}
@ -304,7 +304,7 @@ public static partial class AcBinaryDeserializer
/// Shared between normal populate and cross-type populate.
/// </summary>
private static void PopulatePropertyValue(
ref BinaryDeserializationContext context,
BinaryDeserializationContext context,
object target,
BinaryPropertySetterInfo propInfo,
TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper,
@ -339,7 +339,7 @@ public static partial class AcBinaryDeserializer
context.RegisterNextInternedValue(existingObj);
}
PopulateObjectCore(ref context, existingObj, wrapper, nextDepth, skipDefaultWrite: false);
PopulateObjectCore(context, existingObj, wrapper, nextDepth, skipDefaultWrite: false);
return;
}
}
@ -351,7 +351,7 @@ public static partial class AcBinaryDeserializer
if (existingCollection is IList existingList)
{
context.ReadByte(); // consume Array marker
PopulateListOptimized(ref context, existingList, propInfo, nextDepth);
PopulateListOptimized(context, existingList, propInfo, nextDepth);
return;
}
}
@ -360,10 +360,10 @@ public static partial class AcBinaryDeserializer
var positionBeforeRead = context.Position;
try
{
if (TryReadAndSetTypedValue(ref context, target, propInfo, peekCode))
if (TryReadAndSetTypedValue(context, target, propInfo, peekCode))
return;
var value = ReadValue(ref context, propInfo.PropertyType, nextDepth);
var value = ReadValue(context, propInfo.PropertyType, nextDepth);
propInfo.SetValue(target, value);
}
catch (InvalidCastException ex)

View File

@ -32,12 +32,12 @@ public static partial class AcBinaryDeserializer
/// - Non-IId + All: [Object][hashcode][props 0-t<>l...] - hashcode prefix
/// - Ref=Off: [Object][props 0-t<>l...] - no prefix
/// </summary>
private static void PopulateObject(ref BinaryDeserializationContext context, object target, Type targetType, int depth)
private static void PopulateObject(BinaryDeserializationContext context, object target, Type targetType, int depth)
{
var wrapper = context.ContextClass.GetWrapper(targetType);
var wrapper = context.GetWrapper(targetType);
// UseMetadata: CacheMap is built in ReadObjectWithMetadata or ReadInlineMetadataForPopulate
PopulateObjectCore(ref context, target, wrapper, depth, skipDefaultWrite: false);
PopulateObjectCore(context, target, wrapper, depth, skipDefaultWrite: false);
}
/// <summary>
@ -46,13 +46,13 @@ public static partial class AcBinaryDeserializer
/// No hashcode prefix - position-based footer handles reference tracking.
/// </summary>
private static void PopulateObjectCore(
ref BinaryDeserializationContext context,
BinaryDeserializationContext context,
object target,
TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper,
int depth,
bool skipDefaultWrite)
{
PopulateObjectPropertiesIndexed(ref context, target, wrapper, depth, skipDefaultWrite);
PopulateObjectPropertiesIndexed(context, target, wrapper, depth, skipDefaultWrite);
}
/// <summary>
@ -61,7 +61,7 @@ public static partial class AcBinaryDeserializer
/// UseMetadata=false: properties[i] gives the setter directly.
/// </summary>
private static void PopulateObjectPropertiesIndexed(
ref BinaryDeserializationContext context,
BinaryDeserializationContext context,
object target,
TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper,
int depth,
@ -87,12 +87,12 @@ public static partial class AcBinaryDeserializer
if (propInfo.ExpectedTypeCode.HasValue)
{
ReadAndSetMarkerlessValue(ref context, target, propInfo);
ReadAndSetMarkerlessValue(context, target, propInfo);
continue;
}
// Non-markerless properties: standard marker-based read
PopulatePropertyWithMarker(ref context, target, propInfo, metadata, nextDepth, isMergeMode, skipDefaultWrite, i, depth);
PopulatePropertyWithMarker(context, target, propInfo, metadata, nextDepth, isMergeMode, skipDefaultWrite, i, depth);
}
}
else
@ -102,7 +102,7 @@ public static partial class AcBinaryDeserializer
{
var propInfo = cacheMap != null ? cacheMap[i] : properties[i];
PopulatePropertyWithMarker(ref context, target, propInfo, metadata, nextDepth, isMergeMode, skipDefaultWrite, i, depth);
PopulatePropertyWithMarker(context, target, propInfo, metadata, nextDepth, isMergeMode, skipDefaultWrite, i, depth);
}
}
}
@ -111,7 +111,7 @@ public static partial class AcBinaryDeserializer
/// Standard marker-based property read. Extracted to avoid duplicating logic in both loops.
/// </summary>
private static void PopulatePropertyWithMarker(
ref BinaryDeserializationContext context,
BinaryDeserializationContext context,
object target,
BinaryPropertySetterBase? propInfo,
BinaryDeserializeTypeMetadata metadata,
@ -126,7 +126,7 @@ public static partial class AcBinaryDeserializer
// Nincs megfelelő target property → skip
if (propInfo == null)
{
SkipValue(ref context, metadata);
SkipValue(context, metadata);
return;
}
@ -163,12 +163,12 @@ public static partial class AcBinaryDeserializer
// Merge mode with IId collection: use merge logic
if (isMergeMode && propInfo.IsIIdCollection)
{
MergeIIdCollection(ref context, existingList, propInfo, nextDepth);
MergeIIdCollection(context, existingList, propInfo, nextDepth);
}
else
{
// Normal populate: replace collection contents
PopulateListOptimized(ref context, existingList, propInfo, nextDepth);
PopulateListOptimized(context, existingList, propInfo, nextDepth);
}
return;
}
@ -181,10 +181,10 @@ public static partial class AcBinaryDeserializer
if (existingObj != null)
{
// ReadValue kezeli mindkét markert
var nestedValue = ReadValue(ref context, propInfo.PropertyType, nextDepth);
var nestedValue = ReadValue(context, propInfo.PropertyType, nextDepth);
if (nestedValue != null)
{
var nestedMeta = context.ContextClass.GetWrapper(propInfo.PropertyType).Metadata;
var nestedMeta = context.GetWrapper(propInfo.PropertyType).Metadata;
CopyProperties(nestedValue, existingObj, nestedMeta);
}
return;
@ -197,10 +197,10 @@ public static partial class AcBinaryDeserializer
{
// Use typed setters for primitives and strings to avoid ReadValue dispatch
if (propInfo.AccessorType != PropertyAccessorType.Object &&
TryReadAndSetTypedValue(ref context, target, propInfo, peekCode))
TryReadAndSetTypedValue(context, target, propInfo, peekCode))
return;
var value = ReadValue(ref context, propInfo.PropertyType, nextDepth);
var value = ReadValue(context, propInfo.PropertyType, nextDepth);
propInfo.SetValue(target, value);
}
catch (InvalidCastException ex)
@ -227,7 +227,7 @@ public static partial class AcBinaryDeserializer
/// Only called for non-nullable value types with ExpectedTypeCode set.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ReadAndSetMarkerlessValue(ref BinaryDeserializationContext context, object target, BinaryPropertySetterBase propInfo)
private static void ReadAndSetMarkerlessValue(BinaryDeserializationContext context, object target, BinaryPropertySetterBase propInfo)
{
switch (propInfo.AccessorType)
{
@ -274,16 +274,16 @@ public static partial class AcBinaryDeserializer
/// Called from ReadObject/ReadObjectWithMetadata for new instances.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void PopulateObject(ref BinaryDeserializationContext context, object target, TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper, int depth, bool skipDefaultWrite)
private static void PopulateObject(BinaryDeserializationContext context, object target, TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper, int depth, bool skipDefaultWrite)
{
PopulateObjectPropertiesIndexed(ref context, target, wrapper, depth, skipDefaultWrite);
PopulateObjectPropertiesIndexed(context, target, wrapper, depth, skipDefaultWrite);
}
#endregion
#region Populate List Methods
private static void PopulateList(ref BinaryDeserializationContext context, IList targetList, Type listType, int depth)
private static void PopulateList(BinaryDeserializationContext context, IList targetList, Type listType, int depth)
{
var elementType = GetCollectionElementType(listType) ?? typeof(object);
@ -300,7 +300,7 @@ public static partial class AcBinaryDeserializer
for (int i = 0; i < count; i++)
{
// ReadValue handles ChainMode internally (ReadObject returns cached instance)
var value = ReadValue(ref context, elementType, nextDepth);
var value = ReadValue(context, elementType, nextDepth);
targetList.Add(value);
}
}
@ -313,7 +313,7 @@ public static partial class AcBinaryDeserializer
/// <summary>
/// Optimized list populate that reuses existing items when possible.
/// </summary>
private static void PopulateListOptimized(ref BinaryDeserializationContext context, IList existingList, BinaryPropertySetterBase propInfo, int depth)
private static void PopulateListOptimized(BinaryDeserializationContext context, IList existingList, BinaryPropertySetterBase propInfo, int depth)
{
var elementType = propInfo.ElementType ?? typeof(object);
var count = (int)context.ReadVarUInt();
@ -333,7 +333,7 @@ public static partial class AcBinaryDeserializer
return;
}
var wrapper = context.ContextClass.GetWrapper(elementType);
var wrapper = context.GetWrapper(elementType);
var elementMetadata = wrapper.Metadata.IsComplexType ? wrapper.Metadata : null;
for (int i = 0; i < count; i++)
@ -347,13 +347,13 @@ public static partial class AcBinaryDeserializer
if (existingItem != null)
{
context.ReadByte(); // consume Object marker
PopulateObjectCore(ref context, existingItem, wrapper, nextDepth, skipDefaultWrite: false);
PopulateObjectCore(context, existingItem, wrapper, nextDepth, skipDefaultWrite: false);
continue;
}
}
// Read new value
var value = ReadValue(ref context, elementType, nextDepth);
var value = ReadValue(context, elementType, nextDepth);
if (i < existingCount)
{
@ -385,7 +385,7 @@ public static partial class AcBinaryDeserializer
/// IId collection merge using cached property info.
/// Matches items by Id, updates existing, adds new, optionally removes orphans.
/// </summary>
private static void MergeIIdCollection(ref BinaryDeserializationContext context, IList existingList, BinaryPropertySetterBase propInfo, int depth)
private static void MergeIIdCollection(BinaryDeserializationContext context, IList existingList, BinaryPropertySetterBase propInfo, int depth)
{
var elementType = propInfo.ElementType!;
var idGetter = propInfo.ElementIdGetter!;
@ -416,7 +416,7 @@ public static partial class AcBinaryDeserializer
var arrayCount = (int)context.ReadVarUInt();
var nextDepth = depth + 1;
var wrapper = context.ContextClass.GetWrapper(elementType);
var wrapper = context.GetWrapper(elementType);
var elementMetadata = wrapper.Metadata;
// Track which IDs we see in source (for orphan removal)
@ -429,7 +429,7 @@ public static partial class AcBinaryDeserializer
var itemCode = context.PeekByte();
if (itemCode != BinaryTypeCode.Object)
{
var value = ReadValue(ref context, elementType, nextDepth);
var value = ReadValue(context, elementType, nextDepth);
if (value != null)
existingList.Add(value);
continue;
@ -440,7 +440,7 @@ public static partial class AcBinaryDeserializer
if (newItem == null) continue;
// PopulateObjectCore handles hashcode reading for Non-IId types
PopulateObjectCore(ref context, newItem, wrapper, nextDepth, skipDefaultWrite: true);
PopulateObjectCore(context, newItem, wrapper, nextDepth, skipDefaultWrite: true);
var itemId = idGetter(newItem);
if (itemId != null && !IsDefaultValue(itemId, idType))
@ -487,7 +487,7 @@ public static partial class AcBinaryDeserializer
/// IId collection merge using type metadata (for top-level list merge).
/// </summary>
private static void MergeIIdCollectionWithMetadata(
ref BinaryDeserializationContext context,
BinaryDeserializationContext context,
IList existingList,
Type elementType,
TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper,
@ -533,7 +533,7 @@ public static partial class AcBinaryDeserializer
var itemCode = context.PeekByte();
if (itemCode != BinaryTypeCode.Object)
{
var value = ReadValue(ref context, elementType, nextDepth);
var value = ReadValue(context, elementType, nextDepth);
if (value != null)
existingList.Add(value);
continue;
@ -544,7 +544,7 @@ public static partial class AcBinaryDeserializer
if (newItem == null) continue;
// PopulateObjectCore handles hashcode reading for Non-IId types
PopulateObjectCore(ref context, newItem, wrapper, nextDepth, skipDefaultWrite: true);
PopulateObjectCore(context, newItem, wrapper, nextDepth, skipDefaultWrite: true);
var itemId = idGetter(newItem);
if (itemId != null && !IsDefaultValue(itemId, idType))
@ -603,7 +603,7 @@ public static partial class AcBinaryDeserializer
/// Reads Id value without type marker. The serializer didn't write a marker for IId types.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ReadIdValueWithoutMarker(ref BinaryDeserializationContext context, object target, BinaryPropertySetterInfo propInfo, IdAccessorType idType)
private static void ReadIdValueWithoutMarker(BinaryDeserializationContext context, object target, BinaryPropertySetterInfo propInfo, IdAccessorType idType)
{
switch (idType)
{

View File

@ -36,39 +36,39 @@ public static partial class AcBinaryDeserializer
private static Dictionary<Type, TypeConversionInfo>? t_typeConversionLocalCache;
// Type dispatch table for fast ReadValue
private delegate object? TypeReader(ref BinaryDeserializationContext context, Type targetType, int depth);
private delegate object? TypeReader(BinaryDeserializationContext context, Type targetType, int depth);
private static readonly TypeReader?[] TypeReaders = new TypeReader[byte.MaxValue + 1];
private static readonly Encoding Utf8NoBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
static AcBinaryDeserializer()
{
RegisterReader(BinaryTypeCode.Null, static (ref BinaryDeserializationContext _, Type _, int _) => null);
RegisterReader(BinaryTypeCode.True, static (ref BinaryDeserializationContext _, Type _, int _) => true);
RegisterReader(BinaryTypeCode.False, static (ref BinaryDeserializationContext _, Type _, int _) => false);
RegisterReader(BinaryTypeCode.Int8, static (ref BinaryDeserializationContext ctx, Type _, int _) => (sbyte)ctx.ReadByte());
RegisterReader(BinaryTypeCode.UInt8, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadByte());
RegisterReader(BinaryTypeCode.Int16, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadInt16Unsafe());
RegisterReader(BinaryTypeCode.UInt16, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadUInt16Unsafe());
RegisterReader(BinaryTypeCode.Int32, static (ref BinaryDeserializationContext ctx, Type type, int _) => ReadInt32Value(ref ctx, type));
RegisterReader(BinaryTypeCode.UInt32, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadVarUInt());
RegisterReader(BinaryTypeCode.Int64, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadVarLong());
RegisterReader(BinaryTypeCode.UInt64, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadVarULong());
RegisterReader(BinaryTypeCode.Float32, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadSingleUnsafe());
RegisterReader(BinaryTypeCode.Float64, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadDoubleUnsafe());
RegisterReader(BinaryTypeCode.Decimal, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadDecimalUnsafe());
RegisterReader(BinaryTypeCode.Char, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadCharUnsafe());
RegisterReader(BinaryTypeCode.String, static (ref BinaryDeserializationContext ctx, Type _, int _) => ReadPlainString(ref ctx));
RegisterReader(BinaryTypeCode.StringInterned, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.GetInternedString((int)ctx.ReadVarUInt()));
RegisterReader(BinaryTypeCode.StringEmpty, static (ref BinaryDeserializationContext _, Type _, int _) => string.Empty);
RegisterReader(BinaryTypeCode.Null, static (BinaryDeserializationContext _, Type _, int _) => null);
RegisterReader(BinaryTypeCode.True, static (BinaryDeserializationContext _, Type _, int _) => true);
RegisterReader(BinaryTypeCode.False, static (BinaryDeserializationContext _, Type _, int _) => false);
RegisterReader(BinaryTypeCode.Int8, static (BinaryDeserializationContext ctx, Type _, int _) => (sbyte)ctx.ReadByte());
RegisterReader(BinaryTypeCode.UInt8, static (BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadByte());
RegisterReader(BinaryTypeCode.Int16, static (BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadInt16Unsafe());
RegisterReader(BinaryTypeCode.UInt16, static (BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadUInt16Unsafe());
RegisterReader(BinaryTypeCode.Int32, static (BinaryDeserializationContext ctx, Type type, int _) => ReadInt32Value(ctx, type));
RegisterReader(BinaryTypeCode.UInt32, static (BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadVarUInt());
RegisterReader(BinaryTypeCode.Int64, static (BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadVarLong());
RegisterReader(BinaryTypeCode.UInt64, static (BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadVarULong());
RegisterReader(BinaryTypeCode.Float32, static (BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadSingleUnsafe());
RegisterReader(BinaryTypeCode.Float64, static (BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadDoubleUnsafe());
RegisterReader(BinaryTypeCode.Decimal, static (BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadDecimalUnsafe());
RegisterReader(BinaryTypeCode.Char, static (BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadCharUnsafe());
RegisterReader(BinaryTypeCode.String, static (BinaryDeserializationContext ctx, Type _, int _) => ReadPlainString(ctx));
RegisterReader(BinaryTypeCode.StringInterned, static (BinaryDeserializationContext ctx, Type _, int _) => ctx.GetInternedString((int)ctx.ReadVarUInt()));
RegisterReader(BinaryTypeCode.StringEmpty, static (BinaryDeserializationContext _, Type _, int _) => string.Empty);
// StringInternFirst: first occurrence of interned string - read cacheIndex + content + register in cache
RegisterReader(BinaryTypeCode.StringInternFirst, static (ref BinaryDeserializationContext ctx, Type _, int _) =>
ReadAndRegisterInternedString(ref ctx));
RegisterReader(BinaryTypeCode.DateTime, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadDateTimeUnsafe());
RegisterReader(BinaryTypeCode.DateTimeOffset, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadDateTimeOffsetUnsafe());
RegisterReader(BinaryTypeCode.TimeSpan, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadTimeSpanUnsafe());
RegisterReader(BinaryTypeCode.Guid, static (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadGuidUnsafe());
RegisterReader(BinaryTypeCode.Enum, static (ref BinaryDeserializationContext ctx, Type type, int _) => ReadEnumValue(ref ctx, type));
RegisterReader(BinaryTypeCode.StringInternFirst, static (BinaryDeserializationContext ctx, Type _, int _) =>
ReadAndRegisterInternedString(ctx));
RegisterReader(BinaryTypeCode.DateTime, static (BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadDateTimeUnsafe());
RegisterReader(BinaryTypeCode.DateTimeOffset, static (BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadDateTimeOffsetUnsafe());
RegisterReader(BinaryTypeCode.TimeSpan, static (BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadTimeSpanUnsafe());
RegisterReader(BinaryTypeCode.Guid, static (BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadGuidUnsafe());
RegisterReader(BinaryTypeCode.Enum, static (BinaryDeserializationContext ctx, Type type, int _) => ReadEnumValue(ctx, type));
RegisterReader(BinaryTypeCode.Object, ReadObject);
RegisterReader(BinaryTypeCode.ObjectRefFirst, ReadObjectRefFirst);
RegisterReader(BinaryTypeCode.ObjectWithMetadata, ReadObjectWithMetadata);
@ -76,7 +76,7 @@ public static partial class AcBinaryDeserializer
RegisterReader(BinaryTypeCode.ObjectRef, ReadObjectRef);
RegisterReader(BinaryTypeCode.Array, ReadArray);
RegisterReader(BinaryTypeCode.Dictionary, ReadDictionary);
RegisterReader(BinaryTypeCode.ByteArray, static (ref BinaryDeserializationContext ctx, Type _, int _) => ReadByteArray(ref ctx));
RegisterReader(BinaryTypeCode.ByteArray, static (BinaryDeserializationContext ctx, Type _, int _) => ReadByteArray(ctx));
// Register FixStr readers (34-65)
for (byte code = BinaryTypeCode.FixStrBase; code <= BinaryTypeCode.FixStrMax; code++)
@ -93,9 +93,9 @@ public static partial class AcBinaryDeserializer
private static TypeReader CreateFixStrReader(int length)
{
if (length == 0)
return static (ref BinaryDeserializationContext _, Type _, int _) => string.Empty;
return static (BinaryDeserializationContext _, Type _, int _) => string.Empty;
return (ref BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadStringUtf8(length);
return (BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadStringUtf8(length);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -136,13 +136,13 @@ public static partial class AcBinaryDeserializer
return (T?)(object?)DeserializeExpression(data, targetType, options);
}
var ctxClass = DeserializationContextClassPool.Get(options);
var context = new BinaryDeserializationContext(data, ctxClass);
var context = DeserializationContextPool.Get(options);
context.InitBufferFromSpan(data);
try
{
context.ReadHeader();
var result = ReadValue(ref context, targetType, 0);
var result = ReadValue(context, targetType, 0);
// Position-based string interning - no validation needed
return (T?)result;
}
@ -158,7 +158,7 @@ public static partial class AcBinaryDeserializer
}
finally
{
DeserializationContextClassPool.Return(ctxClass);
DeserializationContextPool.Return(context);
}
}
@ -182,13 +182,13 @@ public static partial class AcBinaryDeserializer
return DeserializeExpression(data, targetType, options);
}
var ctxClass = DeserializationContextClassPool.Get(options);
var context = new BinaryDeserializationContext(data, ctxClass);
var context = DeserializationContextPool.Get(options);
context.InitBufferFromSpan(data);
try
{
context.ReadHeader();
var result = ReadValue(ref context, targetType, 0);
var result = ReadValue(context, targetType, 0);
// Position-based string interning - no validation needed
return result;
}
@ -204,7 +204,7 @@ public static partial class AcBinaryDeserializer
}
finally
{
DeserializationContextClassPool.Return(ctxClass);
DeserializationContextPool.Return(context);
}
}
@ -225,16 +225,16 @@ public static partial class AcBinaryDeserializer
if (data.IsSingleSegment)
return Deserialize<T>(data.FirstSpan, options);
var ctxClass = DeserializationContextClassPool.Get(options);
var context = DeserializationContextPool.Get(options);
try
{
var buffer = ctxClass.RentLinearizedBuffer((int)data.Length);
var buffer = context.RentLinearizedBuffer((int)data.Length);
data.CopyTo(buffer);
return Deserialize<T>(buffer.AsSpan(0, (int)data.Length), ctxClass);
return Deserialize<T>(buffer.AsSpan(0, (int)data.Length), context);
}
finally
{
DeserializationContextClassPool.Return(ctxClass);
DeserializationContextPool.Return(context);
}
}
@ -254,35 +254,35 @@ public static partial class AcBinaryDeserializer
if (data.IsSingleSegment)
return Deserialize(data.FirstSpan, targetType, options);
var ctxClass = DeserializationContextClassPool.Get(options);
var context = DeserializationContextPool.Get(options);
try
{
var buffer = ctxClass.RentLinearizedBuffer((int)data.Length);
var buffer = context.RentLinearizedBuffer((int)data.Length);
data.CopyTo(buffer);
return Deserialize(buffer.AsSpan(0, (int)data.Length), targetType, ctxClass);
return Deserialize(buffer.AsSpan(0, (int)data.Length), targetType, context);
}
finally
{
DeserializationContextClassPool.Return(ctxClass);
DeserializationContextPool.Return(context);
}
}
/// <summary>
/// Internal: Deserialize with pre-pooled ContextClass (used by ReadOnlySequence multi-segment path).
/// Internal: Deserialize with pre-pooled context (used by ReadOnlySequence multi-segment path).
/// </summary>
private static T? Deserialize<T>(ReadOnlySpan<byte> data, BinaryDeserializationContextClass ctxClass)
private static T? Deserialize<T>(ReadOnlySpan<byte> data, BinaryDeserializationContext context)
{
if (data.Length == 1 && data[0] == BinaryTypeCode.Null) return default;
var targetType = typeof(T);
if (AcSerializerCommon.IsExpressionType(targetType))
return (T?)(object?)DeserializeExpression(data, targetType, ctxClass);
return (T?)(object?)DeserializeExpression(data, targetType, context);
var context = new BinaryDeserializationContext(data, ctxClass);
context.InitBufferFromSpan(data);
try
{
context.ReadHeader();
return (T?)ReadValue(ref context, targetType, 0);
return (T?)ReadValue(context, targetType, 0);
}
catch (AcBinaryDeserializationException) { throw; }
catch (Exception ex)
@ -294,20 +294,20 @@ public static partial class AcBinaryDeserializer
}
/// <summary>
/// Internal: Deserialize with pre-pooled ContextClass (used by ReadOnlySequence multi-segment path).
/// Internal: Deserialize with pre-pooled context (used by ReadOnlySequence multi-segment path).
/// </summary>
private static object? Deserialize(ReadOnlySpan<byte> data, Type targetType, BinaryDeserializationContextClass ctxClass)
private static object? Deserialize(ReadOnlySpan<byte> data, Type targetType, BinaryDeserializationContext context)
{
if (data.Length == 1 && data[0] == BinaryTypeCode.Null) return null;
if (AcSerializerCommon.IsExpressionType(targetType))
return DeserializeExpression(data, targetType, ctxClass);
return DeserializeExpression(data, targetType, context);
var context = new BinaryDeserializationContext(data, ctxClass);
context.InitBufferFromSpan(data);
try
{
context.ReadHeader();
return ReadValue(ref context, targetType, 0);
return ReadValue(context, targetType, 0);
}
catch (AcBinaryDeserializationException) { throw; }
catch (Exception ex)
@ -319,15 +319,15 @@ public static partial class AcBinaryDeserializer
}
/// <summary>
/// Internal: DeserializeExpression with pre-pooled ContextClass.
/// Internal: DeserializeExpression with pre-pooled context.
/// </summary>
private static Expression? DeserializeExpression(ReadOnlySpan<byte> data, Type targetExpressionType, BinaryDeserializationContextClass ctxClass)
private static Expression? DeserializeExpression(ReadOnlySpan<byte> data, Type targetExpressionType, BinaryDeserializationContext context)
{
var context = new BinaryDeserializationContext(data, ctxClass);
context.InitBufferFromSpan(data);
try
{
context.ReadHeader();
var node = (AcExpressionNode?)ReadValue(ref context, typeof(AcExpressionNode), 0);
var node = (AcExpressionNode?)ReadValue(context, typeof(AcExpressionNode), 0);
if (node == null) return null;
var entityType = AcSerializerCommon.GetExpressionEntityType(targetExpressionType);
@ -348,13 +348,13 @@ public static partial class AcBinaryDeserializer
/// </summary>
private static Expression? DeserializeExpression(ReadOnlySpan<byte> data, Type targetExpressionType, AcBinarySerializerOptions options)
{
var ctxClass = DeserializationContextClassPool.Get(options);
var context = new BinaryDeserializationContext(data, ctxClass);
var context = DeserializationContextPool.Get(options);
context.InitBufferFromSpan(data);
try
{
context.ReadHeader();
var node = (AcExpressionNode?)ReadValue(ref context, typeof(AcExpressionNode), 0);
var node = (AcExpressionNode?)ReadValue(context, typeof(AcExpressionNode), 0);
// Position-based string interning - no validation needed
if (node == null) return null;
@ -373,7 +373,7 @@ public static partial class AcBinaryDeserializer
}
finally
{
DeserializationContextClassPool.Return(ctxClass);
DeserializationContextPool.Return(context);
}
}
@ -405,8 +405,8 @@ public static partial class AcBinaryDeserializer
if (data.Length == 1 && data[0] == BinaryTypeCode.Null) return;
var targetType = target.GetType();
var ctxClass = DeserializationContextClassPool.Get(options);
var context = new BinaryDeserializationContext(data, ctxClass);
var context = DeserializationContextPool.Get(options);
context.InitBufferFromSpan(data);
try
{
@ -416,18 +416,18 @@ public static partial class AcBinaryDeserializer
if (typeCode == BinaryTypeCode.Object)
{
context.ReadByte(); // consume Object marker
PopulateObject(ref context, target, targetType, 0);
PopulateObject(context, target, targetType, 0);
}
else if (typeCode == BinaryTypeCode.ObjectWithMetadata)
{
context.ReadByte(); // consume ObjectWithMetadata marker
ReadInlineMetadataForPopulate(ref context, targetType);
PopulateObject(ref context, target, targetType, 0);
ReadInlineMetadataForPopulate(context, targetType);
PopulateObject(context, target, targetType, 0);
}
else if (typeCode == BinaryTypeCode.Array && target is IList targetList)
{
context.ReadByte(); // consume Array marker
PopulateList(ref context, targetList, targetType, 0);
PopulateList(context, targetList, targetType, 0);
}
else
{
@ -450,7 +450,7 @@ public static partial class AcBinaryDeserializer
}
finally
{
DeserializationContextClassPool.Return(ctxClass);
DeserializationContextPool.Return(context);
}
}
@ -475,12 +475,10 @@ public static partial class AcBinaryDeserializer
var opts = options ?? AcBinarySerializerOptions.Default;
var targetType = target.GetType();
var ctxClass = DeserializationContextClassPool.Get(opts);
var context = new BinaryDeserializationContext(data, ctxClass)
{
IsMergeMode = true,
RemoveOrphanedItems = opts.RemoveOrphanedItems
};
var context = DeserializationContextPool.Get(opts);
context.InitBufferFromSpan(data);
context.IsMergeMode = true;
context.RemoveOrphanedItems = opts.RemoveOrphanedItems;
try
{
@ -490,13 +488,13 @@ public static partial class AcBinaryDeserializer
if (typeCode == BinaryTypeCode.Object)
{
context.ReadByte();
PopulateObject(ref context, target, targetType, 0);
PopulateObject(context, target, targetType, 0);
}
else if (typeCode == BinaryTypeCode.ObjectWithMetadata)
{
context.ReadByte();
ReadInlineMetadataForPopulate(ref context, targetType);
PopulateObject(ref context, target, targetType, 0);
ReadInlineMetadataForPopulate(context, targetType);
PopulateObject(context, target, targetType, 0);
}
else if (typeCode == BinaryTypeCode.Array && target is IList targetList)
{
@ -505,19 +503,19 @@ public static partial class AcBinaryDeserializer
var elementType = GetCollectionElementType(targetType);
if (elementType != null)
{
var wrapper = context.ContextClass.GetWrapper(elementType);
var wrapper = context.GetWrapper(elementType);
var elementMetadata = wrapper.Metadata;
if (elementMetadata.IsComplexType && elementMetadata.IsIId && elementMetadata.IdGetter != null)
{
MergeIIdCollectionWithMetadata(ref context, targetList, elementType, wrapper, 0);
MergeIIdCollectionWithMetadata(context, targetList, elementType, wrapper, 0);
// Position-based string interning - no validation needed
return;
}
}
// Non-IId collection, just populate
PopulateList(ref context, targetList, targetType, 0);
PopulateList(context, targetList, targetType, 0);
}
else
{
@ -540,7 +538,7 @@ public static partial class AcBinaryDeserializer
}
finally
{
DeserializationContextClassPool.Return(ctxClass);
DeserializationContextPool.Return(context);
}
}
@ -568,19 +566,20 @@ public static partial class AcBinaryDeserializer
// Copy data to array for chain storage
var dataArray = data.ToArray();
var chainTracker = new AcSerializerCommon.ChainReferenceTracker();
var ctxClass = DeserializationContextClassPool.Get(options);
var context = new BinaryDeserializationContext(dataArray, ctxClass) { ChainTracker = chainTracker };
var context = DeserializationContextPool.Get(options);
context.InitBuffer(dataArray, dataArray.Length);
context.ChainTracker = chainTracker;
try
{
context.ReadHeader();
var result = ReadValue(ref context, targetType, 0);
var result = ReadValue(context, targetType, 0);
// Position-based string interning - no validation needed
return new BinaryDeserializeChain<T>(dataArray, options, chainTracker, (T?)result);
}
finally
{
DeserializationContextClassPool.Return(ctxClass);
DeserializationContextPool.Return(context);
}
}
@ -614,13 +613,14 @@ public static partial class AcBinaryDeserializer
ThrowIfDisposed();
var targetType = typeof(TResult);
var ctxClass = DeserializationContextClassPool.Get(_options);
var context = new BinaryDeserializationContext(_data, ctxClass) { ChainTracker = _chainTracker };
var context = DeserializationContextPool.Get(_options);
context.InitBuffer(_data, _data.Length);
context.ChainTracker = _chainTracker;
try
{
context.ReadHeader();
var result = ReadValue(ref context, targetType, 0);
var result = ReadValue(context, targetType, 0);
// Position-based string interning - no validation needed
return (TResult?)result;
}
@ -633,7 +633,7 @@ public static partial class AcBinaryDeserializer
}
finally
{
DeserializationContextClassPool.Return(ctxClass);
DeserializationContextPool.Return(context);
}
}
@ -643,8 +643,9 @@ public static partial class AcBinaryDeserializer
ThrowIfDisposed();
var targetType = target.GetType();
var ctxClass = DeserializationContextClassPool.Get(_options);
var context = new BinaryDeserializationContext(_data, ctxClass) { ChainTracker = _chainTracker };
var context = DeserializationContextPool.Get(_options);
context.InitBuffer(_data, _data.Length);
context.ChainTracker = _chainTracker;
try
{
@ -654,18 +655,18 @@ public static partial class AcBinaryDeserializer
if (typeCode == BinaryTypeCode.Object)
{
context.ReadByte();
PopulateObject(ref context, target, targetType, 0);
PopulateObject(context, target, targetType, 0);
}
else if (typeCode == BinaryTypeCode.ObjectWithMetadata)
{
context.ReadByte();
ReadInlineMetadataForPopulate(ref context, targetType);
PopulateObject(ref context, target, targetType, 0);
ReadInlineMetadataForPopulate(context, targetType);
PopulateObject(context, target, targetType, 0);
}
else if (typeCode == BinaryTypeCode.Array && target is IList targetList)
{
context.ReadByte();
PopulateList(ref context, targetList, targetType, 0);
PopulateList(context, targetList, targetType, 0);
}
else
{
@ -686,7 +687,7 @@ public static partial class AcBinaryDeserializer
}
finally
{
DeserializationContextClassPool.Return(ctxClass);
DeserializationContextPool.Return(context);
}
}
@ -712,7 +713,7 @@ public static partial class AcBinaryDeserializer
/// Returns true if handled, false if should fall back to generic path.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool TryReadAndSetTypedValue(ref BinaryDeserializationContext context, object target, BinaryPropertySetterBase propInfo, byte peekCode)
private static bool TryReadAndSetTypedValue(BinaryDeserializationContext context, object target, BinaryPropertySetterBase propInfo, byte peekCode)
{
// Only handle if we have a typed setter
if (propInfo.AccessorType == PropertyAccessorType.Object)
@ -927,7 +928,7 @@ public static partial class AcBinaryDeserializer
if (peekCode == BinaryTypeCode.String)
{
context.ReadByte();
propInfo.SetValue(target, ReadPlainString(ref context));
propInfo.SetValue(target, ReadPlainString(context));
return true;
}
if (peekCode == BinaryTypeCode.StringEmpty)
@ -945,7 +946,7 @@ public static partial class AcBinaryDeserializer
if (peekCode == BinaryTypeCode.StringInternFirst)
{
context.ReadByte();
propInfo.SetValue(target, ReadAndRegisterInternedString(ref context));
propInfo.SetValue(target, ReadAndRegisterInternedString(context));
return true;
}
break;
@ -957,7 +958,7 @@ public static partial class AcBinaryDeserializer
/// <summary>
/// Optimized value reader using FrozenDictionary dispatch table.
/// </summary>
private static object? ReadValue(ref BinaryDeserializationContext context, Type targetType, int depth)
private static object? ReadValue(BinaryDeserializationContext context, Type targetType, int depth)
{
if (context.IsAtEnd) return null;
@ -983,7 +984,7 @@ public static partial class AcBinaryDeserializer
var reader = TypeReaders[typeCode];
if (reader != null)
{
return reader(ref context, targetType, depth);
return reader(context, targetType, depth);
}
throw new AcBinaryDeserializationException(
@ -995,7 +996,7 @@ public static partial class AcBinaryDeserializer
/// Sima string olvas<61>sa - NEM regisztr<74>l az intern t<>bl<62>ba.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static string ReadPlainString(ref BinaryDeserializationContext context)
private static string ReadPlainString(BinaryDeserializationContext context)
{
var length = (int)context.ReadVarUInt();
if (length == 0) return string.Empty;
@ -1007,7 +1008,7 @@ public static partial class AcBinaryDeserializer
/// Wire format: [StringInternFirst][VarUInt cacheIndex][VarUInt length][UTF8 bytes]
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static string ReadAndRegisterInternedString(ref BinaryDeserializationContext context)
private static string ReadAndRegisterInternedString(BinaryDeserializationContext context)
{
var cacheIndex = (int)context.ReadVarUInt();
var length = (int)context.ReadVarUInt();
@ -1021,7 +1022,7 @@ public static partial class AcBinaryDeserializer
///// Read a string and register it in the intern table for future references.
///// </summary>
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
//private static string ReadAndInternString(ref BinaryDeserializationContext context, int streamPosition)
//private static string ReadAndInternString(BinaryDeserializationContext context, int streamPosition)
//{
// var length = (int)context.ReadVarUInt();
// if (length == 0) return string.Empty;
@ -1036,7 +1037,7 @@ public static partial class AcBinaryDeserializer
//}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static object ReadInt32Value(ref BinaryDeserializationContext context, Type targetType)
private static object ReadInt32Value(BinaryDeserializationContext context, Type targetType)
{
var value = context.ReadVarInt();
return ConvertToTargetType(value, targetType);
@ -1106,7 +1107,7 @@ public static partial class AcBinaryDeserializer
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static object ReadEnumValue(ref BinaryDeserializationContext context, Type targetType)
private static object ReadEnumValue(BinaryDeserializationContext context, Type targetType)
{
var info = GetConversionInfo(targetType);
var nextByte = context.ReadByte();
@ -1131,7 +1132,7 @@ public static partial class AcBinaryDeserializer
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte[] ReadByteArray(ref BinaryDeserializationContext context)
private static byte[] ReadByteArray(BinaryDeserializationContext context)
{
var length = (int)context.ReadVarUInt();
if (length == 0) return [];
@ -1164,7 +1165,7 @@ public static partial class AcBinaryDeserializer
/// ami ritka eset (2+ referencia), tehát nem lassítja a hot path-ot.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static object? ReadObjectRef(ref BinaryDeserializationContext context, Type targetType, int depth)
private static object? ReadObjectRef(BinaryDeserializationContext context, Type targetType, int depth)
{
var cacheIndex = (int)context.ReadVarUInt();
return context.GetInternedObject(cacheIndex);
@ -1174,9 +1175,9 @@ public static partial class AcBinaryDeserializer
/// Object olvasása (nem tracked, vagy UseMetadata nélkül).
/// Wire format: [Object][props...]
/// </summary>
private static object? ReadObject(ref BinaryDeserializationContext context, Type targetType, int depth)
private static object? ReadObject(BinaryDeserializationContext context, Type targetType, int depth)
{
return ReadObjectCore(ref context, targetType, depth, cacheIndex: -1);
return ReadObjectCore(context, targetType, depth, cacheIndex: -1);
}
/// <summary>
@ -1184,25 +1185,25 @@ public static partial class AcBinaryDeserializer
/// Wire format: [ObjectRefFirst][VarUInt cacheIndex][props...]
/// Az objektumot regisztráljuk a cache-be a megadott index-re.
/// </summary>
private static object? ReadObjectRefFirst(ref BinaryDeserializationContext context, Type targetType, int depth)
private static object? ReadObjectRefFirst(BinaryDeserializationContext context, Type targetType, int depth)
{
var cacheIndex = (int)context.ReadVarUInt();
return ReadObjectCore(ref context, targetType, depth, cacheIndex: cacheIndex);
return ReadObjectCore(context, targetType, depth, cacheIndex: cacheIndex);
}
/// <summary>
/// Object olvasás core implementáció.
/// </summary>
/// <param name="cacheIndex">-1 = not cached, 0+ = register at this cache index</param>
private static object? ReadObjectCore(ref BinaryDeserializationContext context, Type targetType, int depth, int cacheIndex)
private static object? ReadObjectCore(BinaryDeserializationContext context, Type targetType, int depth, int cacheIndex)
{
// Handle dictionary types
if (IsDictionaryType(targetType, out var keyType, out var valueType))
{
return ReadDictionaryAsObject(ref context, keyType!, valueType!, depth);
return ReadDictionaryAsObject(context, keyType!, valueType!, depth);
}
var wrapper = context.ContextClass.GetWrapper(targetType);
var wrapper = context.GetWrapper(targetType);
var metadata = wrapper.Metadata;
var instance = CreateInstance(targetType, metadata);
@ -1213,7 +1214,7 @@ public static partial class AcBinaryDeserializer
context.RegisterInternedValueAt(cacheIndex, instance);
}
PopulateObject(ref context, instance, wrapper, depth, skipDefaultWrite: true);
PopulateObject(context, instance, wrapper, depth, skipDefaultWrite: true);
// ChainMode kezelés
if (context.IsChainMode && metadata.IsIId && metadata.IdType != null)
@ -1239,9 +1240,9 @@ public static partial class AcBinaryDeserializer
/// Első előfordulás: [ObjectWithMetadata][propNameHash (4b)][propCount (VarUInt)][hash0..N][props...]
/// Ismételt: [ObjectWithMetadata][propNameHash (4b)][props...]
/// </summary>
private static object? ReadObjectWithMetadata(ref BinaryDeserializationContext context, Type targetType, int depth)
private static object? ReadObjectWithMetadata(BinaryDeserializationContext context, Type targetType, int depth)
{
return ReadObjectWithMetadataCore(ref context, targetType, depth, cacheIndex: -1);
return ReadObjectWithMetadataCore(context, targetType, depth, cacheIndex: -1);
}
/// <summary>
@ -1249,23 +1250,23 @@ public static partial class AcBinaryDeserializer
/// Wire format: [ObjectWithMetadataRefFirst][VarUInt cacheIndex][propNameHash (4b)][...][props...]
/// Az objektumot regisztráljuk a cache-be a megadott index-re.
/// </summary>
private static object? ReadObjectWithMetadataRefFirst(ref BinaryDeserializationContext context, Type targetType, int depth)
private static object? ReadObjectWithMetadataRefFirst(BinaryDeserializationContext context, Type targetType, int depth)
{
var cacheIndex = (int)context.ReadVarUInt();
return ReadObjectWithMetadataCore(ref context, targetType, depth, cacheIndex: cacheIndex);
return ReadObjectWithMetadataCore(context, targetType, depth, cacheIndex: cacheIndex);
}
/// <summary>
/// ObjectWithMetadata olvasás core implementáció.
/// </summary>
/// <param name="cacheIndex">-1 = not cached, 0+ = register at this cache index</param>
private static object? ReadObjectWithMetadataCore(ref BinaryDeserializationContext context, Type targetType, int depth, int cacheIndex)
private static object? ReadObjectWithMetadataCore(BinaryDeserializationContext context, Type targetType, int depth, int cacheIndex)
{
// Inline metadata: propNameHash mindig jön
var propNameHash = context.ReadInt32Raw();
// Source hash-ek keresése — ha nincs, első előfordulás → olvasás a stream-ből
var sourceHashes = context.ContextClass.FindSourceHashes(propNameHash);
var sourceHashes = context.FindSourceHashes(propNameHash);
if (sourceHashes == null)
{
var propCount = (int)context.ReadVarUInt();
@ -1274,16 +1275,16 @@ public static partial class AcBinaryDeserializer
{
sourceHashes[i] = context.ReadInt32Raw();
}
context.ContextClass.RegisterInlineMetadata(propNameHash, sourceHashes);
context.RegisterInlineMetadata(propNameHash, sourceHashes);
}
// Handle dictionary types
if (IsDictionaryType(targetType, out var keyType, out var valueType))
{
return ReadDictionaryAsObject(ref context, keyType!, valueType!, depth);
return ReadDictionaryAsObject(context, keyType!, valueType!, depth);
}
var wrapper = context.ContextClass.GetWrapper(targetType);
var wrapper = context.GetWrapper(targetType);
var metadata = wrapper.Metadata;
var instance = CreateInstance(targetType, metadata);
@ -1298,7 +1299,7 @@ public static partial class AcBinaryDeserializer
if (wrapper.CacheMap == null)
BuildCacheMap(wrapper, sourceHashes);
PopulateObject(ref context, instance, wrapper, depth, skipDefaultWrite: true);
PopulateObject(context, instance, wrapper, depth, skipDefaultWrite: true);
// ChainMode kezelés
if (context.IsChainMode && metadata.IsIId && metadata.IdType != null)
@ -1323,11 +1324,11 @@ public static partial class AcBinaryDeserializer
/// Az ObjectWithMetadata marker már consume-álva van.
/// Beolvassa a propNameHash-t + hash-eket (első előfordulásnál), és felépíti a cacheMap-et.
/// </summary>
private static void ReadInlineMetadataForPopulate(ref BinaryDeserializationContext context, Type targetType)
private static void ReadInlineMetadataForPopulate(BinaryDeserializationContext context, Type targetType)
{
var propNameHash = context.ReadInt32Raw();
var sourceHashes = context.ContextClass.FindSourceHashes(propNameHash);
var sourceHashes = context.FindSourceHashes(propNameHash);
if (sourceHashes == null)
{
var propCount = (int)context.ReadVarUInt();
@ -1336,10 +1337,10 @@ public static partial class AcBinaryDeserializer
{
sourceHashes[i] = context.ReadInt32Raw();
}
context.ContextClass.RegisterInlineMetadata(propNameHash, sourceHashes);
context.RegisterInlineMetadata(propNameHash, sourceHashes);
}
var wrapper = context.ContextClass.GetWrapper(targetType);
var wrapper = context.GetWrapper(targetType);
if (wrapper.CacheMap == null)
BuildCacheMap(wrapper, sourceHashes);
}
@ -1382,7 +1383,7 @@ public static partial class AcBinaryDeserializer
#region Array Reading
private static object? ReadArray(ref BinaryDeserializationContext context, Type targetType, int depth)
private static object? ReadArray(BinaryDeserializationContext context, Type targetType, int depth)
{
var elementType = GetCollectionElementType(targetType);
if (elementType == null) elementType = typeof(object);
@ -1393,7 +1394,7 @@ public static partial class AcBinaryDeserializer
// Optimized path for primitive arrays
if (targetType.IsArray && count > 0)
{
var result = TryReadPrimitiveArray(ref context, elementType, count);
var result = TryReadPrimitiveArray(context, elementType, count);
if (result != null) return result;
}
@ -1402,7 +1403,7 @@ public static partial class AcBinaryDeserializer
var array = Array.CreateInstance(elementType, count);
for (int i = 0; i < count; i++)
{
var value = ReadValue(ref context, elementType, nextDepth);
var value = ReadValue(context, elementType, nextDepth);
array.SetValue(value, i);
}
@ -1429,7 +1430,7 @@ public static partial class AcBinaryDeserializer
{
for (int i = 0; i < count; i++)
{
var value = ReadValue(ref context, elementType, nextDepth);
var value = ReadValue(context, elementType, nextDepth);
list.Add(value);
}
}
@ -1445,7 +1446,7 @@ public static partial class AcBinaryDeserializer
/// Optimized primitive array reader using bulk operations.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Array? TryReadPrimitiveArray(ref BinaryDeserializationContext context, Type elementType, int count)
private static Array? TryReadPrimitiveArray(BinaryDeserializationContext context, Type elementType, int count)
{
// Int32 array
if (ReferenceEquals(elementType, IntType))
@ -1563,7 +1564,7 @@ public static partial class AcBinaryDeserializer
#region Dictionary Reading
private static object? ReadDictionary(ref BinaryDeserializationContext context, Type targetType, int depth)
private static object? ReadDictionary(BinaryDeserializationContext context, Type targetType, int depth)
{
if (!IsDictionaryType(targetType, out var keyType, out var valueType))
{
@ -1571,10 +1572,10 @@ public static partial class AcBinaryDeserializer
valueType = typeof(object);
}
return ReadDictionaryAsObject(ref context, keyType!, valueType!, depth);
return ReadDictionaryAsObject(context, keyType!, valueType!, depth);
}
private static object ReadDictionaryAsObject(ref BinaryDeserializationContext context, Type keyType, Type valueType, int depth)
private static object ReadDictionaryAsObject(BinaryDeserializationContext context, Type keyType, Type valueType, int depth)
{
var dictType = DictionaryGenericType.MakeGenericType(keyType, valueType);
var count = (int)context.ReadVarUInt();
@ -1583,8 +1584,8 @@ public static partial class AcBinaryDeserializer
for (int i = 0; i < count; i++)
{
var key = ReadValue(ref context, keyType, nextDepth);
var value = ReadValue(ref context, valueType, nextDepth);
var key = ReadValue(context, keyType, nextDepth);
var value = ReadValue(context, valueType, nextDepth);
if (key != null)
dict.Add(key, value);
}
@ -1596,7 +1597,7 @@ public static partial class AcBinaryDeserializer
#region Skip Value
private static void SkipValue(ref BinaryDeserializationContext context, BinaryDeserializeTypeMetadata metaData)
private static void SkipValue(BinaryDeserializationContext context, BinaryDeserializeTypeMetadata metaData)
{
var typeCode = context.ReadByte();
@ -1658,14 +1659,14 @@ public static partial class AcBinaryDeserializer
context.Skip(16);
return;
case BinaryTypeCode.String:
SkipPlainString(ref context);
SkipPlainString(context);
return;
case BinaryTypeCode.StringInterned:
context.ReadVarUInt();
return;
case BinaryTypeCode.StringInternFirst:
// First occurrence - must register even when skipping
SkipAndRegisterInternedString(ref context);
SkipAndRegisterInternedString(context);
return;
case BinaryTypeCode.ByteArray:
var byteLen = (int)context.ReadVarUInt();
@ -1677,28 +1678,28 @@ public static partial class AcBinaryDeserializer
if (enumByte == BinaryTypeCode.Int32) context.ReadVarInt();
return;
case BinaryTypeCode.Object:
SkipObject(ref context, metaData);
SkipObject(context, metaData);
return;
case BinaryTypeCode.ObjectRefFirst:
SkipObjectRefFirst(ref context, metaData);
SkipObjectRefFirst(context, metaData);
return;
case BinaryTypeCode.ObjectWithMetadata:
SkipObjectWithMetadata(ref context, metaData, cacheIndex: -1);
SkipObjectWithMetadata(context, metaData, cacheIndex: -1);
return;
case BinaryTypeCode.ObjectWithMetadataRefFirst:
{
var cacheIdx = (int)context.ReadVarUInt();
SkipObjectWithMetadata(ref context, metaData, cacheIndex: cacheIdx);
SkipObjectWithMetadata(context, metaData, cacheIndex: cacheIdx);
return;
}
case BinaryTypeCode.ObjectRef:
context.ReadVarUInt();
return;
case BinaryTypeCode.Array:
SkipArray(ref context, metaData);
SkipArray(context, metaData);
return;
case BinaryTypeCode.Dictionary:
SkipDictionary(ref context, metaData);
SkipDictionary(context, metaData);
return;
}
}
@ -1707,7 +1708,7 @@ public static partial class AcBinaryDeserializer
/// Sima string kihagy<67>sa - NEM regisztr<74>l.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void SkipPlainString(ref BinaryDeserializationContext context)
private static void SkipPlainString(BinaryDeserializationContext context)
{
var byteLen = (int)context.ReadVarUInt();
if (byteLen > 0)
@ -1721,7 +1722,7 @@ public static partial class AcBinaryDeserializer
/// Wire format: [StringInternFirst][VarUInt cacheIndex][VarUInt length][UTF8 bytes]
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void SkipAndRegisterInternedString(ref BinaryDeserializationContext context)
private static void SkipAndRegisterInternedString(BinaryDeserializationContext context)
{
var cacheIndex = (int)context.ReadVarUInt();
var byteLen = (int)context.ReadVarUInt();
@ -1734,12 +1735,12 @@ public static partial class AcBinaryDeserializer
/// Skip ObjectRefFirst - must read cacheIndex and register placeholder in cache.
/// Wire format: [ObjectRefFirst][VarUInt cacheIndex][props...]
/// </summary>
private static void SkipObjectRefFirst(ref BinaryDeserializationContext context, BinaryDeserializeTypeMetadata metaData)
private static void SkipObjectRefFirst(BinaryDeserializationContext context, BinaryDeserializeTypeMetadata metaData)
{
var cacheIndex = (int)context.ReadVarUInt();
// Register placeholder (stream position as boxed int for potential lazy load)
context.RegisterInternedValueAt(cacheIndex, context.Position);
SkipObject(ref context, metaData);
SkipObject(context, metaData);
}
///// <summary>
@ -1748,7 +1749,7 @@ public static partial class AcBinaryDeserializer
///// <param name="context">Deserialization context</param>
///// <param name="streamPosition">Position before the type code was read</param>
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
//private static void SkipAndInternString(ref BinaryDeserializationContext context, int streamPosition)
//private static void SkipAndInternString(BinaryDeserializationContext context, int streamPosition)
//{
// var byteLen = (int)context.ReadVarUInt();
// if (byteLen == 0) return;
@ -1763,7 +1764,7 @@ public static partial class AcBinaryDeserializer
/// <summary>
/// Object kihagyása metadata nélkül — nem támogatott, mert nem tudjuk a property számot.
/// </summary>
private static void SkipObject(ref BinaryDeserializationContext context, BinaryDeserializeTypeMetadata metaData)
private static void SkipObject(BinaryDeserializationContext context, BinaryDeserializeTypeMetadata metaData)
{
throw new NotSupportedException(
"SkipObject nem támogatott metadata nélkül. " +
@ -1774,7 +1775,7 @@ public static partial class AcBinaryDeserializer
/// Skip ObjectWithMetadata/ObjectWithMetadataRefFirst.
/// </summary>
/// <param name="cacheIndex">-1 = not cached, 0+ = register at this cache index</param>
private static void SkipObjectWithMetadata(ref BinaryDeserializationContext context, BinaryDeserializeTypeMetadata metaData, int cacheIndex)
private static void SkipObjectWithMetadata(BinaryDeserializationContext context, BinaryDeserializeTypeMetadata metaData, int cacheIndex)
{
if (cacheIndex >= 0)
{
@ -1784,7 +1785,7 @@ public static partial class AcBinaryDeserializer
var propNameHash = context.ReadInt32Raw();
var sourceHashes = context.ContextClass.FindSourceHashes(propNameHash);
var sourceHashes = context.FindSourceHashes(propNameHash);
if (sourceHashes == null)
{
var propCount = (int)context.ReadVarUInt();
@ -1793,32 +1794,32 @@ public static partial class AcBinaryDeserializer
{
sourceHashes[i] = context.ReadInt32Raw();
}
context.ContextClass.RegisterInlineMetadata(propNameHash, sourceHashes);
context.RegisterInlineMetadata(propNameHash, sourceHashes);
}
// Skip all properties
for (var i = 0; i < sourceHashes.Length; i++)
{
SkipValue(ref context, metaData);
SkipValue(context, metaData);
}
}
private static void SkipArray(ref BinaryDeserializationContext context, BinaryDeserializeTypeMetadata metaData)
private static void SkipArray(BinaryDeserializationContext context, BinaryDeserializeTypeMetadata metaData)
{
var count = (int)context.ReadVarUInt();
for (int i = 0; i < count; i++)
{
SkipValue(ref context, metaData);
SkipValue(context, metaData);
}
}
private static void SkipDictionary(ref BinaryDeserializationContext context, BinaryDeserializeTypeMetadata metaData)
private static void SkipDictionary(BinaryDeserializationContext context, BinaryDeserializeTypeMetadata metaData)
{
var count = (int)context.ReadVarUInt();
for (int i = 0; i < count; i++)
{
SkipValue(ref context, metaData); // key
SkipValue(ref context, metaData); // value
SkipValue(context, metaData); // key
SkipValue(context, metaData); // value
}
}