Refactor: generic binary input for deserialization
Refactored deserialization to use IBinaryInputBase abstraction, supporting both ArrayBinaryInput (byte[]) and SequenceBinaryInput (ReadOnlySequence<byte>). All context and methods are now generic over TInput, enabling zero-copy for arrays and true streaming for multi-segment sources. Internal logic (ReadValue, Populate, Skip, etc.) is specialized per input type, improving performance and flexibility. This enables future extensibility for other input sources and optimizes handling of large or segmented payloads.
This commit is contained in:
parent
4c6342aa2b
commit
96409fe321
|
|
@ -8,7 +8,7 @@ namespace AyCode.Core.Serializers.Binaries;
|
||||||
|
|
||||||
public static partial class AcBinaryDeserializer
|
public static partial class AcBinaryDeserializer
|
||||||
{
|
{
|
||||||
internal sealed partial class BinaryDeserializationContext
|
internal sealed partial class BinaryDeserializationContext<TInput>
|
||||||
{
|
{
|
||||||
private static readonly Encoding Utf8NoBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false);
|
private static readonly Encoding Utf8NoBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false);
|
||||||
|
|
||||||
|
|
@ -370,7 +370,8 @@ public static partial class AcBinaryDeserializer
|
||||||
{
|
{
|
||||||
if (_position > _bufferLength - length)
|
if (_position > _bufferLength - length)
|
||||||
{
|
{
|
||||||
throw new AcBinaryDeserializationException("Unexpected end of binary payload.", _position);
|
if (!Input.TryAdvanceSegment(ref _buffer, ref _position, ref _bufferLength, length))
|
||||||
|
throw new AcBinaryDeserializationException("Unexpected end of binary payload.", _position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,12 @@ public static partial class AcBinaryDeserializer
|
||||||
/// Pool for BinaryDeserializationContext instances.
|
/// Pool for BinaryDeserializationContext instances.
|
||||||
/// Eliminates per-call heap allocation — mirrors BinarySerializationContextPool pattern.
|
/// Eliminates per-call heap allocation — mirrors BinarySerializationContextPool pattern.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static class DeserializationContextPool
|
private static class DeserializationContextPool<TInput> where TInput : struct, IBinaryInputBase
|
||||||
{
|
{
|
||||||
private static readonly ConcurrentQueue<BinaryDeserializationContext> Pool = new();
|
private static readonly ConcurrentQueue<BinaryDeserializationContext<TInput>> Pool = new();
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static BinaryDeserializationContext Get(AcBinarySerializerOptions options)
|
public static BinaryDeserializationContext<TInput> Get(AcBinarySerializerOptions options)
|
||||||
{
|
{
|
||||||
if (Pool.TryDequeue(out var ctx))
|
if (Pool.TryDequeue(out var ctx))
|
||||||
{
|
{
|
||||||
|
|
@ -25,13 +25,13 @@ public static partial class AcBinaryDeserializer
|
||||||
return ctx;
|
return ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
var newCtx = new BinaryDeserializationContext();
|
var newCtx = new BinaryDeserializationContext<TInput>();
|
||||||
newCtx.Reset(options);
|
newCtx.Reset(options);
|
||||||
return newCtx;
|
return newCtx;
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static void Return(BinaryDeserializationContext ctx)
|
public static void Return(BinaryDeserializationContext<TInput> ctx)
|
||||||
{
|
{
|
||||||
if (Pool.Count < ctx.Options.MaxContextPoolSize)
|
if (Pool.Count < ctx.Options.MaxContextPoolSize)
|
||||||
{
|
{
|
||||||
|
|
@ -46,10 +46,17 @@ public static partial class AcBinaryDeserializer
|
||||||
/// Holds all state: buffer, position, caches, options, metadata, interning.
|
/// Holds all state: buffer, position, caches, options, metadata, interning.
|
||||||
/// Buffer state and read methods are directly in the context (via partial Read.cs)
|
/// Buffer state and read methods are directly in the context (via partial Read.cs)
|
||||||
/// for zero-indirection hot-path access — mirrors the serializer pattern.
|
/// for zero-indirection hot-path access — mirrors the serializer pattern.
|
||||||
|
/// TInput handles buffer lifecycle (Initialize/AdvanceSegment) — mirrors BinarySerializationContext<TOutput>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal sealed partial class BinaryDeserializationContext
|
internal sealed partial class BinaryDeserializationContext<TInput>
|
||||||
: AcSerializerContextBase<BinaryDeserializeTypeMetadata, AcBinarySerializerOptions>
|
: AcSerializerContextBase<BinaryDeserializeTypeMetadata, AcBinarySerializerOptions>
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Input target — handles only Initialize (startup) and AdvanceSegment (cold path).
|
||||||
|
/// </summary>
|
||||||
|
public TInput Input;
|
||||||
|
|
||||||
// Marker-based interning: sequential cache (no footer needed)
|
// Marker-based interning: sequential cache (no footer needed)
|
||||||
private object?[]? _internCache;
|
private object?[]? _internCache;
|
||||||
|
|
||||||
|
|
@ -116,15 +123,14 @@ public static partial class AcBinaryDeserializer
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the buffer for a new deserialization operation from a byte array.
|
/// Initializes the context with the given TInput.
|
||||||
/// Zero-copy: context references the byte[] directly.
|
/// Calls Input.Initialize to set up buffer/position/length.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void InitBuffer(byte[] data, int length)
|
public void InitInput(TInput input)
|
||||||
{
|
{
|
||||||
_buffer = data;
|
Input = input;
|
||||||
_bufferLength = length;
|
Input.Initialize(out _buffer, out _position, out _bufferLength);
|
||||||
_position = 0;
|
|
||||||
_useStringCaching = Options.UseStringCaching;
|
_useStringCaching = Options.UseStringCaching;
|
||||||
_maxCachedStringLength = Options.MaxCachedStringLength;
|
_maxCachedStringLength = Options.MaxCachedStringLength;
|
||||||
if (_useStringCaching) GetOrCreateStringCache();
|
if (_useStringCaching) GetOrCreateStringCache();
|
||||||
|
|
@ -136,14 +142,14 @@ public static partial class AcBinaryDeserializer
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the buffer from a ReadOnlySpan by copying to a linearized buffer.
|
/// Initializes the context from a ReadOnlySpan by copying to a pooled linearized buffer,
|
||||||
/// Used when the source is not a byte[] (e.g. stackalloc, pinned).
|
/// then creating an ArrayBinaryInput. Used when TInput is ArrayBinaryInput.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void InitBufferFromSpan(ReadOnlySpan<byte> data)
|
public void InitFromSpan(ReadOnlySpan<byte> data)
|
||||||
{
|
{
|
||||||
var buffer = RentLinearizedBuffer(data.Length);
|
var buffer = RentLinearizedBuffer(data.Length);
|
||||||
data.CopyTo(buffer);
|
data.CopyTo(buffer);
|
||||||
InitBuffer(buffer, data.Length);
|
InitInput((TInput)(object)new ArrayBinaryInput(buffer, data.Length));
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Header
|
#region Header
|
||||||
|
|
|
||||||
|
|
@ -55,8 +55,8 @@ public static partial class AcBinaryDeserializer
|
||||||
|
|
||||||
// Cross-type path: use index mapping
|
// Cross-type path: use index mapping
|
||||||
var indexMapping = GetIndexMapping(sourceType, destType, options.PropertyMapper);
|
var indexMapping = GetIndexMapping(sourceType, destType, options.PropertyMapper);
|
||||||
var context = DeserializationContextPool.Get(options);
|
var context = DeserializationContextPool<ArrayBinaryInput>.Get(options);
|
||||||
context.InitBufferFromSpan(data);
|
context.InitFromSpan(data);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -76,7 +76,7 @@ public static partial class AcBinaryDeserializer
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
DeserializationContextPool.Return(context);
|
DeserializationContextPool<ArrayBinaryInput>.Return(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -141,8 +141,8 @@ public static partial class AcBinaryDeserializer
|
||||||
|
|
||||||
// Cross-type path: use index mapping
|
// Cross-type path: use index mapping
|
||||||
var indexMapping = GetIndexMapping(sourceType, destType, options.PropertyMapper);
|
var indexMapping = GetIndexMapping(sourceType, destType, options.PropertyMapper);
|
||||||
var context = DeserializationContextPool.Get(options);
|
var context = DeserializationContextPool<ArrayBinaryInput>.Get(options);
|
||||||
context.InitBufferFromSpan(data);
|
context.InitFromSpan(data);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -179,7 +179,7 @@ public static partial class AcBinaryDeserializer
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
DeserializationContextPool.Return(context);
|
DeserializationContextPool<ArrayBinaryInput>.Return(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -207,7 +207,8 @@ public static partial class AcBinaryDeserializer
|
||||||
/// Reads a value with index mapping applied.
|
/// Reads a value with index mapping applied.
|
||||||
/// Maps source PropertyIndex to destination PropertyIndex using the provided mapping.
|
/// Maps source PropertyIndex to destination PropertyIndex using the provided mapping.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static object? ReadValueWithMapping(BinaryDeserializationContext context, Type destType, int[] indexMapping, int depth)
|
private static object? ReadValueWithMapping<TInput>(BinaryDeserializationContext<TInput> context, Type destType, int[] indexMapping, int depth)
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
{
|
{
|
||||||
var typeCode = context.ReadByte();
|
var typeCode = context.ReadByte();
|
||||||
|
|
||||||
|
|
@ -224,7 +225,8 @@ public static partial class AcBinaryDeserializer
|
||||||
/// Reads an object using index mapping for property resolution.
|
/// Reads an object using index mapping for property resolution.
|
||||||
/// Note: Object marker already consumed by caller.
|
/// Note: Object marker already consumed by caller.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static object? ReadObjectWithMapping(BinaryDeserializationContext context, Type destType, int[] indexMapping, int depth, bool registerInCache)
|
private static object? ReadObjectWithMapping<TInput>(BinaryDeserializationContext<TInput> context, Type destType, int[] indexMapping, int depth, bool registerInCache)
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
{
|
{
|
||||||
var wrapper = context.GetWrapper(destType);
|
var wrapper = context.GetWrapper(destType);
|
||||||
var metadata = wrapper.Metadata;
|
var metadata = wrapper.Metadata;
|
||||||
|
|
@ -246,12 +248,13 @@ public static partial class AcBinaryDeserializer
|
||||||
/// Populates an object using index mapping.
|
/// Populates an object using index mapping.
|
||||||
/// Source property indices are remapped to destination indices.
|
/// Source property indices are remapped to destination indices.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static void PopulateObjectWithMapping(
|
private static void PopulateObjectWithMapping<TInput>(
|
||||||
BinaryDeserializationContext context,
|
BinaryDeserializationContext<TInput> context,
|
||||||
object target,
|
object target,
|
||||||
Type destType,
|
Type destType,
|
||||||
int[] indexMapping,
|
int[] indexMapping,
|
||||||
int depth)
|
int depth)
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
{
|
{
|
||||||
var wrapper = context.GetWrapper(destType);
|
var wrapper = context.GetWrapper(destType);
|
||||||
var metadata = wrapper.Metadata;
|
var metadata = wrapper.Metadata;
|
||||||
|
|
@ -303,8 +306,8 @@ public static partial class AcBinaryDeserializer
|
||||||
/// Common logic for populating a single property value.
|
/// Common logic for populating a single property value.
|
||||||
/// Shared between normal populate and cross-type populate.
|
/// Shared between normal populate and cross-type populate.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static void PopulatePropertyValue(
|
private static void PopulatePropertyValue<TInput>(
|
||||||
BinaryDeserializationContext context,
|
BinaryDeserializationContext<TInput> context,
|
||||||
object target,
|
object target,
|
||||||
BinaryPropertySetterInfo propInfo,
|
BinaryPropertySetterInfo propInfo,
|
||||||
TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper,
|
TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper,
|
||||||
|
|
@ -314,6 +317,7 @@ public static partial class AcBinaryDeserializer
|
||||||
int currentIndex,
|
int currentIndex,
|
||||||
int totalCount,
|
int totalCount,
|
||||||
int depth)
|
int depth)
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
{
|
{
|
||||||
var peekCode = context.PeekByte();
|
var peekCode = context.PeekByte();
|
||||||
|
|
||||||
|
|
@ -387,4 +391,4 @@ public static partial class AcBinaryDeserializer
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
@ -32,7 +32,8 @@ public static partial class AcBinaryDeserializer
|
||||||
/// - Non-IId + All: [Object][hashcode][props 0-t<>l...] - hashcode prefix
|
/// - Non-IId + All: [Object][hashcode][props 0-t<>l...] - hashcode prefix
|
||||||
/// - Ref=Off: [Object][props 0-t<>l...] - no prefix
|
/// - Ref=Off: [Object][props 0-t<>l...] - no prefix
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static void PopulateObject(BinaryDeserializationContext context, object target, Type targetType, int depth)
|
private static void PopulateObject<TInput>(BinaryDeserializationContext<TInput> context, object target, Type targetType, int depth)
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
{
|
{
|
||||||
var wrapper = context.GetWrapper(targetType);
|
var wrapper = context.GetWrapper(targetType);
|
||||||
|
|
||||||
|
|
@ -45,12 +46,13 @@ public static partial class AcBinaryDeserializer
|
||||||
/// Wire format: All properties are written WITH type markers (including Id for IId types).
|
/// Wire format: All properties are written WITH type markers (including Id for IId types).
|
||||||
/// No hashcode prefix - position-based footer handles reference tracking.
|
/// No hashcode prefix - position-based footer handles reference tracking.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static void PopulateObjectCore(
|
private static void PopulateObjectCore<TInput>(
|
||||||
BinaryDeserializationContext context,
|
BinaryDeserializationContext<TInput> context,
|
||||||
object target,
|
object target,
|
||||||
TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper,
|
TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper,
|
||||||
int depth,
|
int depth,
|
||||||
bool skipDefaultWrite)
|
bool skipDefaultWrite)
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
{
|
{
|
||||||
PopulateObjectPropertiesIndexed(context, target, wrapper, depth, skipDefaultWrite);
|
PopulateObjectPropertiesIndexed(context, target, wrapper, depth, skipDefaultWrite);
|
||||||
}
|
}
|
||||||
|
|
@ -60,12 +62,13 @@ public static partial class AcBinaryDeserializer
|
||||||
/// UseMetadata=true: cacheMap[i] gives the setter (null → skip).
|
/// UseMetadata=true: cacheMap[i] gives the setter (null → skip).
|
||||||
/// UseMetadata=false: properties[i] gives the setter directly.
|
/// UseMetadata=false: properties[i] gives the setter directly.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static void PopulateObjectPropertiesIndexed(
|
private static void PopulateObjectPropertiesIndexed<TInput>(
|
||||||
BinaryDeserializationContext context,
|
BinaryDeserializationContext<TInput> context,
|
||||||
object target,
|
object target,
|
||||||
TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper,
|
TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper,
|
||||||
int depth,
|
int depth,
|
||||||
bool skipDefaultWrite)
|
bool skipDefaultWrite)
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
{
|
{
|
||||||
var metadata = wrapper.Metadata;
|
var metadata = wrapper.Metadata;
|
||||||
var properties = metadata.PropertiesArray;
|
var properties = metadata.PropertiesArray;
|
||||||
|
|
@ -110,8 +113,8 @@ public static partial class AcBinaryDeserializer
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Standard marker-based property read. Extracted to avoid duplicating logic in both loops.
|
/// Standard marker-based property read. Extracted to avoid duplicating logic in both loops.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static void PopulatePropertyWithMarker(
|
private static void PopulatePropertyWithMarker<TInput>(
|
||||||
BinaryDeserializationContext context,
|
BinaryDeserializationContext<TInput> context,
|
||||||
object target,
|
object target,
|
||||||
BinaryPropertySetterBase? propInfo,
|
BinaryPropertySetterBase? propInfo,
|
||||||
BinaryDeserializeTypeMetadata metadata,
|
BinaryDeserializeTypeMetadata metadata,
|
||||||
|
|
@ -120,6 +123,7 @@ public static partial class AcBinaryDeserializer
|
||||||
bool skipDefaultWrite,
|
bool skipDefaultWrite,
|
||||||
int propertyIndex,
|
int propertyIndex,
|
||||||
int depth)
|
int depth)
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
{
|
{
|
||||||
var peekCode = context.PeekByte();
|
var peekCode = context.PeekByte();
|
||||||
|
|
||||||
|
|
@ -227,7 +231,8 @@ public static partial class AcBinaryDeserializer
|
||||||
/// Only called for non-nullable value types with ExpectedTypeCode set.
|
/// Only called for non-nullable value types with ExpectedTypeCode set.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static void ReadAndSetMarkerlessValue(BinaryDeserializationContext context, object target, BinaryPropertySetterBase propInfo)
|
private static void ReadAndSetMarkerlessValue<TInput>(BinaryDeserializationContext<TInput> context, object target, BinaryPropertySetterBase propInfo)
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
{
|
{
|
||||||
switch (propInfo.AccessorType)
|
switch (propInfo.AccessorType)
|
||||||
{
|
{
|
||||||
|
|
@ -274,7 +279,8 @@ public static partial class AcBinaryDeserializer
|
||||||
/// Called from ReadObject/ReadObjectWithMetadata for new instances.
|
/// Called from ReadObject/ReadObjectWithMetadata for new instances.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static void PopulateObject(BinaryDeserializationContext context, object target, TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper, int depth, bool skipDefaultWrite)
|
private static void PopulateObject<TInput>(BinaryDeserializationContext<TInput> context, object target, TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper, int depth, bool skipDefaultWrite)
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
{
|
{
|
||||||
PopulateObjectPropertiesIndexed(context, target, wrapper, depth, skipDefaultWrite);
|
PopulateObjectPropertiesIndexed(context, target, wrapper, depth, skipDefaultWrite);
|
||||||
}
|
}
|
||||||
|
|
@ -283,7 +289,8 @@ public static partial class AcBinaryDeserializer
|
||||||
|
|
||||||
#region Populate List Methods
|
#region Populate List Methods
|
||||||
|
|
||||||
private static void PopulateList(BinaryDeserializationContext context, IList targetList, Type listType, int depth)
|
private static void PopulateList<TInput>(BinaryDeserializationContext<TInput> context, IList targetList, Type listType, int depth)
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
{
|
{
|
||||||
var elementType = GetCollectionElementType(listType) ?? typeof(object);
|
var elementType = GetCollectionElementType(listType) ?? typeof(object);
|
||||||
|
|
||||||
|
|
@ -313,7 +320,8 @@ public static partial class AcBinaryDeserializer
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Optimized list populate that reuses existing items when possible.
|
/// Optimized list populate that reuses existing items when possible.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static void PopulateListOptimized(BinaryDeserializationContext context, IList existingList, BinaryPropertySetterBase propInfo, int depth)
|
private static void PopulateListOptimized<TInput>(BinaryDeserializationContext<TInput> context, IList existingList, BinaryPropertySetterBase propInfo, int depth)
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
{
|
{
|
||||||
var elementType = propInfo.ElementType ?? typeof(object);
|
var elementType = propInfo.ElementType ?? typeof(object);
|
||||||
var count = (int)context.ReadVarUInt();
|
var count = (int)context.ReadVarUInt();
|
||||||
|
|
@ -385,7 +393,8 @@ public static partial class AcBinaryDeserializer
|
||||||
/// IId collection merge using cached property info.
|
/// IId collection merge using cached property info.
|
||||||
/// Matches items by Id, updates existing, adds new, optionally removes orphans.
|
/// Matches items by Id, updates existing, adds new, optionally removes orphans.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static void MergeIIdCollection(BinaryDeserializationContext context, IList existingList, BinaryPropertySetterBase propInfo, int depth)
|
private static void MergeIIdCollection<TInput>(BinaryDeserializationContext<TInput> context, IList existingList, BinaryPropertySetterBase propInfo, int depth)
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
{
|
{
|
||||||
var elementType = propInfo.ElementType!;
|
var elementType = propInfo.ElementType!;
|
||||||
var idGetter = propInfo.ElementIdGetter!;
|
var idGetter = propInfo.ElementIdGetter!;
|
||||||
|
|
@ -486,12 +495,13 @@ public static partial class AcBinaryDeserializer
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// IId collection merge using type metadata (for top-level list merge).
|
/// IId collection merge using type metadata (for top-level list merge).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static void MergeIIdCollectionWithMetadata(
|
private static void MergeIIdCollectionWithMetadata<TInput>(
|
||||||
BinaryDeserializationContext context,
|
BinaryDeserializationContext<TInput> context,
|
||||||
IList existingList,
|
IList existingList,
|
||||||
Type elementType,
|
Type elementType,
|
||||||
TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper,
|
TypeMetadataWrapper<BinaryDeserializeTypeMetadata> wrapper,
|
||||||
int depth)
|
int depth)
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
{
|
{
|
||||||
var elementMetadata = wrapper.Metadata;
|
var elementMetadata = wrapper.Metadata;
|
||||||
var idGetter = elementMetadata.IdGetter!;
|
var idGetter = elementMetadata.IdGetter!;
|
||||||
|
|
@ -603,7 +613,8 @@ public static partial class AcBinaryDeserializer
|
||||||
/// Reads Id value without type marker. The serializer didn't write a marker for IId types.
|
/// Reads Id value without type marker. The serializer didn't write a marker for IId types.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static void ReadIdValueWithoutMarker(BinaryDeserializationContext context, object target, BinaryPropertySetterInfo propInfo, IdAccessorType idType)
|
private static void ReadIdValueWithoutMarker<TInput>(BinaryDeserializationContext<TInput> context, object target, BinaryPropertySetterInfo propInfo, IdAccessorType idType)
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
{
|
{
|
||||||
switch (idType)
|
switch (idType)
|
||||||
{
|
{
|
||||||
|
|
@ -646,4 +657,3 @@ public static partial class AcBinaryDeserializer
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,71 +35,76 @@ public static partial class AcBinaryDeserializer
|
||||||
[ThreadStatic]
|
[ThreadStatic]
|
||||||
private static Dictionary<Type, TypeConversionInfo>? t_typeConversionLocalCache;
|
private static Dictionary<Type, TypeConversionInfo>? t_typeConversionLocalCache;
|
||||||
|
|
||||||
// Type dispatch table for fast ReadValue
|
// Type dispatch table for fast ReadValue — generic per TInput, JIT specializes each
|
||||||
private delegate object? TypeReader(BinaryDeserializationContext context, Type targetType, int depth);
|
private delegate object? TypeReader<TInput>(BinaryDeserializationContext<TInput> context, Type targetType, int depth)
|
||||||
|
where TInput : struct, IBinaryInputBase;
|
||||||
|
|
||||||
private static readonly TypeReader?[] TypeReaders = new TypeReader[byte.MaxValue + 1];
|
private static class TypeReaderTable<TInput> where TInput : struct, IBinaryInputBase
|
||||||
private static readonly Encoding Utf8NoBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
|
|
||||||
|
|
||||||
static AcBinaryDeserializer()
|
|
||||||
{
|
{
|
||||||
RegisterReader(BinaryTypeCode.Null, static (BinaryDeserializationContext _, Type _, int _) => null);
|
public static readonly TypeReader<TInput>?[] Readers = InitReaders();
|
||||||
RegisterReader(BinaryTypeCode.True, static (BinaryDeserializationContext _, Type _, int _) => true);
|
|
||||||
RegisterReader(BinaryTypeCode.False, static (BinaryDeserializationContext _, Type _, int _) => false);
|
private static TypeReader<TInput>?[] InitReaders()
|
||||||
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 (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);
|
|
||||||
RegisterReader(BinaryTypeCode.ObjectWithMetadataRefFirst, ReadObjectWithMetadataRefFirst);
|
|
||||||
RegisterReader(BinaryTypeCode.ObjectRef, ReadObjectRef);
|
|
||||||
RegisterReader(BinaryTypeCode.Array, ReadArray);
|
|
||||||
RegisterReader(BinaryTypeCode.Dictionary, ReadDictionary);
|
|
||||||
RegisterReader(BinaryTypeCode.ByteArray, static (BinaryDeserializationContext ctx, Type _, int _) => ReadByteArray(ctx));
|
|
||||||
|
|
||||||
// Register FixStr readers (34-65)
|
|
||||||
for (byte code = BinaryTypeCode.FixStrBase; code <= BinaryTypeCode.FixStrMax; code++)
|
|
||||||
{
|
{
|
||||||
var length = BinaryTypeCode.DecodeFixStrLength(code);
|
var readers = new TypeReader<TInput>?[byte.MaxValue + 1];
|
||||||
RegisterReader(code, CreateFixStrReader(length));
|
readers[BinaryTypeCode.Null] = static (_, _, _) => null;
|
||||||
|
readers[BinaryTypeCode.True] = static (_, _, _) => true;
|
||||||
|
readers[BinaryTypeCode.False] = static (_, _, _) => false;
|
||||||
|
readers[BinaryTypeCode.Int8] = static (ctx, _, _) => (sbyte)ctx.ReadByte();
|
||||||
|
readers[BinaryTypeCode.UInt8] = static (ctx, _, _) => ctx.ReadByte();
|
||||||
|
readers[BinaryTypeCode.Int16] = static (ctx, _, _) => ctx.ReadInt16Unsafe();
|
||||||
|
readers[BinaryTypeCode.UInt16] = static (ctx, _, _) => ctx.ReadUInt16Unsafe();
|
||||||
|
readers[BinaryTypeCode.Int32] = static (ctx, type, _) => ReadInt32Value(ctx, type);
|
||||||
|
readers[BinaryTypeCode.UInt32] = static (ctx, _, _) => ctx.ReadVarUInt();
|
||||||
|
readers[BinaryTypeCode.Int64] = static (ctx, _, _) => ctx.ReadVarLong();
|
||||||
|
readers[BinaryTypeCode.UInt64] = static (ctx, _, _) => ctx.ReadVarULong();
|
||||||
|
readers[BinaryTypeCode.Float32] = static (ctx, _, _) => ctx.ReadSingleUnsafe();
|
||||||
|
readers[BinaryTypeCode.Float64] = static (ctx, _, _) => ctx.ReadDoubleUnsafe();
|
||||||
|
readers[BinaryTypeCode.Decimal] = static (ctx, _, _) => ctx.ReadDecimalUnsafe();
|
||||||
|
readers[BinaryTypeCode.Char] = static (ctx, _, _) => ctx.ReadCharUnsafe();
|
||||||
|
readers[BinaryTypeCode.String] = static (ctx, _, _) => ReadPlainString(ctx);
|
||||||
|
readers[BinaryTypeCode.StringInterned] = static (ctx, _, _) => ctx.GetInternedString((int)ctx.ReadVarUInt());
|
||||||
|
readers[BinaryTypeCode.StringEmpty] = static (_, _, _) => string.Empty;
|
||||||
|
readers[BinaryTypeCode.StringInternFirst] = static (ctx, _, _) => ReadAndRegisterInternedString(ctx);
|
||||||
|
readers[BinaryTypeCode.DateTime] = static (ctx, _, _) => ctx.ReadDateTimeUnsafe();
|
||||||
|
readers[BinaryTypeCode.DateTimeOffset] = static (ctx, _, _) => ctx.ReadDateTimeOffsetUnsafe();
|
||||||
|
readers[BinaryTypeCode.TimeSpan] = static (ctx, _, _) => ctx.ReadTimeSpanUnsafe();
|
||||||
|
readers[BinaryTypeCode.Guid] = static (ctx, _, _) => ctx.ReadGuidUnsafe();
|
||||||
|
readers[BinaryTypeCode.Enum] = static (ctx, type, _) => ReadEnumValue(ctx, type);
|
||||||
|
readers[BinaryTypeCode.Object] = ReadObject;
|
||||||
|
readers[BinaryTypeCode.ObjectRefFirst] = ReadObjectRefFirst;
|
||||||
|
readers[BinaryTypeCode.ObjectWithMetadata] = ReadObjectWithMetadata;
|
||||||
|
readers[BinaryTypeCode.ObjectWithMetadataRefFirst] = ReadObjectWithMetadataRefFirst;
|
||||||
|
readers[BinaryTypeCode.ObjectRef] = ReadObjectRef;
|
||||||
|
readers[BinaryTypeCode.Array] = ReadArray;
|
||||||
|
readers[BinaryTypeCode.Dictionary] = ReadDictionary;
|
||||||
|
readers[BinaryTypeCode.ByteArray] = static (ctx, _, _) => ReadByteArray(ctx);
|
||||||
|
|
||||||
|
// Register FixStr readers (34-65)
|
||||||
|
for (byte code = BinaryTypeCode.FixStrBase; code <= BinaryTypeCode.FixStrMax; code++)
|
||||||
|
{
|
||||||
|
var length = BinaryTypeCode.DecodeFixStrLength(code);
|
||||||
|
readers[code] = CreateFixStrReader<TInput>(length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return readers;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a reader for FixStr with the given length.
|
/// Creates a reader for FixStr with the given length.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static TypeReader CreateFixStrReader(int length)
|
private static TypeReader<TInput> CreateFixStrReader<TInput>(int length)
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
{
|
{
|
||||||
if (length == 0)
|
if (length == 0)
|
||||||
return static (BinaryDeserializationContext _, Type _, int _) => string.Empty;
|
return static (_, _, _) => string.Empty;
|
||||||
|
|
||||||
return (BinaryDeserializationContext ctx, Type _, int _) => ctx.ReadStringUtf8(length);
|
return (ctx, _, _) => ctx.ReadStringUtf8(length);
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
private static readonly Encoding Utf8NoBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
|
||||||
private static void RegisterReader(byte typeCode, TypeReader reader) => TypeReaders[typeCode] = reader;
|
|
||||||
|
|
||||||
#region Public API
|
#region Public API
|
||||||
|
|
||||||
|
|
@ -107,13 +112,26 @@ public static partial class AcBinaryDeserializer
|
||||||
/// Deserialize binary data to object of type T.
|
/// Deserialize binary data to object of type T.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static T? Deserialize<T>(byte[] data) => Deserialize<T>(data.AsSpan(), AcBinarySerializerOptions.Default);
|
public static T? Deserialize<T>(byte[] data) => Deserialize<T>(data, AcBinarySerializerOptions.Default);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Deserialize binary data to object of type T with options.
|
/// Deserialize binary data to object of type T with options.
|
||||||
|
/// Zero-copy: ArrayBinaryInput references the byte[] directly.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
public static T? Deserialize<T>(byte[] data, AcBinarySerializerOptions options)
|
||||||
public static T? Deserialize<T>(byte[] data, AcBinarySerializerOptions options) => Deserialize<T>(data.AsSpan(), options);
|
{
|
||||||
|
if (data.Length == 0) return default;
|
||||||
|
if (data.Length == 1 && data[0] == BinaryTypeCode.Null) return default;
|
||||||
|
|
||||||
|
var targetType = typeof(T);
|
||||||
|
if (AcSerializerCommon.IsExpressionType(targetType))
|
||||||
|
return (T?)(object?)DeserializeExpression(data, targetType, options);
|
||||||
|
|
||||||
|
var context = DeserializationContextPool<ArrayBinaryInput>.Get(options);
|
||||||
|
context.InitInput(new ArrayBinaryInput(data));
|
||||||
|
try { return (T?)DeserializeCore(context, targetType); }
|
||||||
|
finally { DeserializationContextPool<ArrayBinaryInput>.Return(context); }
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Deserialize binary data to object of type T.
|
/// Deserialize binary data to object of type T.
|
||||||
|
|
@ -129,37 +147,13 @@ public static partial class AcBinaryDeserializer
|
||||||
if (data.Length == 1 && data[0] == BinaryTypeCode.Null) return default;
|
if (data.Length == 1 && data[0] == BinaryTypeCode.Null) return default;
|
||||||
|
|
||||||
var targetType = typeof(T);
|
var targetType = typeof(T);
|
||||||
|
|
||||||
// Handle Expression types - deserialize as AcExpressionNode and rebuild
|
|
||||||
if (AcSerializerCommon.IsExpressionType(targetType))
|
if (AcSerializerCommon.IsExpressionType(targetType))
|
||||||
{
|
|
||||||
return (T?)(object?)DeserializeExpression(data, targetType, options);
|
return (T?)(object?)DeserializeExpression(data, targetType, options);
|
||||||
}
|
|
||||||
|
|
||||||
var context = DeserializationContextPool.Get(options);
|
var context = DeserializationContextPool<ArrayBinaryInput>.Get(options);
|
||||||
context.InitBufferFromSpan(data);
|
context.InitFromSpan(data);
|
||||||
|
try { return (T?)DeserializeCore(context, targetType); }
|
||||||
try
|
finally { DeserializationContextPool<ArrayBinaryInput>.Return(context); }
|
||||||
{
|
|
||||||
context.ReadHeader();
|
|
||||||
var result = ReadValue(context, targetType, 0);
|
|
||||||
// Position-based string interning - no validation needed
|
|
||||||
return (T?)result;
|
|
||||||
}
|
|
||||||
catch (AcBinaryDeserializationException)
|
|
||||||
{
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
throw new AcBinaryDeserializationException(
|
|
||||||
$"Failed to deserialize binary data to type '{targetType.Name}': {ex.Message}",
|
|
||||||
context.Position, targetType, ex);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
DeserializationContextPool.Return(context);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -176,36 +170,13 @@ public static partial class AcBinaryDeserializer
|
||||||
if (data.Length == 0) return null;
|
if (data.Length == 0) return null;
|
||||||
if (data.Length == 1 && data[0] == BinaryTypeCode.Null) return null;
|
if (data.Length == 1 && data[0] == BinaryTypeCode.Null) return null;
|
||||||
|
|
||||||
// Handle Expression types - deserialize as AcExpressionNode and rebuild
|
|
||||||
if (AcSerializerCommon.IsExpressionType(targetType))
|
if (AcSerializerCommon.IsExpressionType(targetType))
|
||||||
{
|
|
||||||
return DeserializeExpression(data, targetType, options);
|
return DeserializeExpression(data, targetType, options);
|
||||||
}
|
|
||||||
|
|
||||||
var context = DeserializationContextPool.Get(options);
|
var context = DeserializationContextPool<ArrayBinaryInput>.Get(options);
|
||||||
context.InitBufferFromSpan(data);
|
context.InitFromSpan(data);
|
||||||
|
try { return DeserializeCore(context, targetType); }
|
||||||
try
|
finally { DeserializationContextPool<ArrayBinaryInput>.Return(context); }
|
||||||
{
|
|
||||||
context.ReadHeader();
|
|
||||||
var result = ReadValue(context, targetType, 0);
|
|
||||||
// Position-based string interning - no validation needed
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
catch (AcBinaryDeserializationException)
|
|
||||||
{
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
throw new AcBinaryDeserializationException(
|
|
||||||
$"Failed to deserialize binary data to type '{targetType.Name}': {ex.Message}",
|
|
||||||
context.Position, targetType, ex);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
DeserializationContextPool.Return(context);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -217,6 +188,7 @@ public static partial class AcBinaryDeserializer
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Deserialize binary data from a ReadOnlySequence with options.
|
/// Deserialize binary data from a ReadOnlySequence with options.
|
||||||
|
/// Single-segment: zero-copy via FirstSpan. Multi-segment: uses SequenceBinaryInput for true streaming.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static T? Deserialize<T>(ReadOnlySequence<byte> data, AcBinarySerializerOptions options)
|
public static T? Deserialize<T>(ReadOnlySequence<byte> data, AcBinarySerializerOptions options)
|
||||||
{
|
{
|
||||||
|
|
@ -225,17 +197,7 @@ public static partial class AcBinaryDeserializer
|
||||||
if (data.IsSingleSegment)
|
if (data.IsSingleSegment)
|
||||||
return Deserialize<T>(data.FirstSpan, options);
|
return Deserialize<T>(data.FirstSpan, options);
|
||||||
|
|
||||||
var context = DeserializationContextPool.Get(options);
|
return DeserializeSequence<T, SequenceBinaryInput>(new SequenceBinaryInput(data), typeof(T), options);
|
||||||
try
|
|
||||||
{
|
|
||||||
var buffer = context.RentLinearizedBuffer((int)data.Length);
|
|
||||||
data.CopyTo(buffer);
|
|
||||||
return Deserialize<T>(buffer.AsSpan(0, (int)data.Length), context);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
DeserializationContextPool.Return(context);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -254,56 +216,56 @@ public static partial class AcBinaryDeserializer
|
||||||
if (data.IsSingleSegment)
|
if (data.IsSingleSegment)
|
||||||
return Deserialize(data.FirstSpan, targetType, options);
|
return Deserialize(data.FirstSpan, targetType, options);
|
||||||
|
|
||||||
var context = DeserializationContextPool.Get(options);
|
return DeserializeSequence<SequenceBinaryInput>(new SequenceBinaryInput(data), targetType, options);
|
||||||
try
|
|
||||||
{
|
|
||||||
var buffer = context.RentLinearizedBuffer((int)data.Length);
|
|
||||||
data.CopyTo(buffer);
|
|
||||||
return Deserialize(buffer.AsSpan(0, (int)data.Length), targetType, context);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
DeserializationContextPool.Return(context);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Internal: Deserialize with pre-pooled context (used by ReadOnlySequence multi-segment path).
|
/// Internal: Deserialize with any TInput (multi-segment or other future input types).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static T? Deserialize<T>(ReadOnlySpan<byte> data, BinaryDeserializationContext context)
|
private static T? DeserializeSequence<T, TInput>(TInput input, Type targetType, AcBinarySerializerOptions options)
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
{
|
{
|
||||||
if (data.Length == 1 && data[0] == BinaryTypeCode.Null) return default;
|
|
||||||
|
|
||||||
var targetType = typeof(T);
|
|
||||||
if (AcSerializerCommon.IsExpressionType(targetType))
|
if (AcSerializerCommon.IsExpressionType(targetType))
|
||||||
return (T?)(object?)DeserializeExpression(data, targetType, context);
|
return Deserialize<T>(LinearizeSequence(input), options);
|
||||||
|
|
||||||
context.InitBufferFromSpan(data);
|
var context = DeserializationContextPool<TInput>.Get(options);
|
||||||
try
|
context.InitInput(input);
|
||||||
{
|
try { return (T?)DeserializeCore(context, targetType); }
|
||||||
context.ReadHeader();
|
finally { DeserializationContextPool<TInput>.Return(context); }
|
||||||
return (T?)ReadValue(context, targetType, 0);
|
|
||||||
}
|
|
||||||
catch (AcBinaryDeserializationException) { throw; }
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
throw new AcBinaryDeserializationException(
|
|
||||||
$"Failed to deserialize binary data to type '{targetType.Name}': {ex.Message}",
|
|
||||||
context.Position, targetType, ex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Internal: Deserialize with pre-pooled context (used by ReadOnlySequence multi-segment path).
|
/// Internal: Deserialize with any TInput (multi-segment, non-generic result).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static object? Deserialize(ReadOnlySpan<byte> data, Type targetType, BinaryDeserializationContext context)
|
private static object? DeserializeSequence<TInput>(TInput input, Type targetType, AcBinarySerializerOptions options)
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
{
|
{
|
||||||
if (data.Length == 1 && data[0] == BinaryTypeCode.Null) return null;
|
|
||||||
|
|
||||||
if (AcSerializerCommon.IsExpressionType(targetType))
|
if (AcSerializerCommon.IsExpressionType(targetType))
|
||||||
return DeserializeExpression(data, targetType, context);
|
return Deserialize(LinearizeSequence(input), targetType, options);
|
||||||
|
|
||||||
context.InitBufferFromSpan(data);
|
var context = DeserializationContextPool<TInput>.Get(options);
|
||||||
|
context.InitInput(input);
|
||||||
|
try { return DeserializeCore(context, targetType); }
|
||||||
|
finally { DeserializationContextPool<TInput>.Return(context); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fallback: linearize a TInput into a contiguous ReadOnlySpan (for Expression deserialization).
|
||||||
|
/// </summary>
|
||||||
|
private static ReadOnlySpan<byte> LinearizeSequence<TInput>(TInput input)
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
|
{
|
||||||
|
input.Initialize(out var buffer, out var position, out var bufferLength);
|
||||||
|
return buffer.AsSpan(position, bufferLength - position);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Core deserialization: ReadHeader + ReadValue with unified error handling.
|
||||||
|
/// All public Deserialize overloads delegate here after pool/init setup.
|
||||||
|
/// </summary>
|
||||||
|
private static object? DeserializeCore<TInput>(BinaryDeserializationContext<TInput> context, Type targetType)
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
context.ReadHeader();
|
context.ReadHeader();
|
||||||
|
|
@ -319,61 +281,67 @@ public static partial class AcBinaryDeserializer
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Internal: DeserializeExpression with pre-pooled context.
|
/// Deserialize Expression from binary data.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static Expression? DeserializeExpression(ReadOnlySpan<byte> data, Type targetExpressionType, BinaryDeserializationContext context)
|
private static Expression? DeserializeExpression(ReadOnlySpan<byte> data, Type targetExpressionType, AcBinarySerializerOptions options)
|
||||||
{
|
{
|
||||||
context.InitBufferFromSpan(data);
|
var context = DeserializationContextPool<ArrayBinaryInput>.Get(options);
|
||||||
|
context.InitFromSpan(data);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
context.ReadHeader();
|
var node = (AcExpressionNode?)DeserializeCore(context, typeof(AcExpressionNode));
|
||||||
var node = (AcExpressionNode?)ReadValue(context, typeof(AcExpressionNode), 0);
|
|
||||||
if (node == null) return null;
|
if (node == null) return null;
|
||||||
|
|
||||||
var entityType = AcSerializerCommon.GetExpressionEntityType(targetExpressionType);
|
var entityType = AcSerializerCommon.GetExpressionEntityType(targetExpressionType);
|
||||||
return AcExpressionRebuilder.FromNode(node, entityType);
|
return AcExpressionRebuilder.FromNode(node, entityType);
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
DeserializationContextPool<ArrayBinaryInput>.Return(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Core populate: ReadHeader + dispatch by typeCode. Used by Populate, PopulateMerge, ThenPopulate.
|
||||||
|
/// </summary>
|
||||||
|
private static void PopulateCore<TInput>(BinaryDeserializationContext<TInput> context, object target)
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
|
{
|
||||||
|
var targetType = target.GetType();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
context.ReadHeader();
|
||||||
|
var typeCode = context.PeekByte();
|
||||||
|
|
||||||
|
if (typeCode == BinaryTypeCode.Object)
|
||||||
|
{
|
||||||
|
context.ReadByte();
|
||||||
|
PopulateObject(context, target, targetType, 0);
|
||||||
|
}
|
||||||
|
else if (typeCode == BinaryTypeCode.ObjectWithMetadata)
|
||||||
|
{
|
||||||
|
context.ReadByte();
|
||||||
|
ReadInlineMetadataForPopulate(context, targetType);
|
||||||
|
PopulateObject(context, target, targetType, 0);
|
||||||
|
}
|
||||||
|
else if (typeCode == BinaryTypeCode.Array && target is IList targetList)
|
||||||
|
{
|
||||||
|
context.ReadByte();
|
||||||
|
PopulateList(context, targetList, targetType, 0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new AcBinaryDeserializationException(
|
||||||
|
$"Cannot populate type '{targetType.Name}' from binary type code {typeCode}",
|
||||||
|
context.Position, targetType);
|
||||||
|
}
|
||||||
|
}
|
||||||
catch (AcBinaryDeserializationException) { throw; }
|
catch (AcBinaryDeserializationException) { throw; }
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
throw new AcBinaryDeserializationException(
|
throw new AcBinaryDeserializationException(
|
||||||
$"Failed to deserialize Expression from binary data: {ex.Message}",
|
$"Failed to populate object of type '{targetType.Name}': {ex.Message}",
|
||||||
context.Position, targetExpressionType, ex);
|
context.Position, targetType, ex);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Deserialize Expression from binary data.
|
|
||||||
/// First deserializes as AcExpressionNode, then rebuilds the Expression tree.
|
|
||||||
/// </summary>
|
|
||||||
private static Expression? DeserializeExpression(ReadOnlySpan<byte> data, Type targetExpressionType, AcBinarySerializerOptions options)
|
|
||||||
{
|
|
||||||
var context = DeserializationContextPool.Get(options);
|
|
||||||
context.InitBufferFromSpan(data);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
context.ReadHeader();
|
|
||||||
var node = (AcExpressionNode?)ReadValue(context, typeof(AcExpressionNode), 0);
|
|
||||||
// Position-based string interning - no validation needed
|
|
||||||
if (node == null) return null;
|
|
||||||
|
|
||||||
var entityType = AcSerializerCommon.GetExpressionEntityType(targetExpressionType);
|
|
||||||
return AcExpressionRebuilder.FromNode(node, entityType);
|
|
||||||
}
|
|
||||||
catch (AcBinaryDeserializationException)
|
|
||||||
{
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
throw new AcBinaryDeserializationException(
|
|
||||||
$"Failed to deserialize Expression from binary data: {ex.Message}",
|
|
||||||
context.Position, targetExpressionType, ex);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
DeserializationContextPool.Return(context);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -404,54 +372,10 @@ public static partial class AcBinaryDeserializer
|
||||||
if (data.Length == 0) return;
|
if (data.Length == 0) return;
|
||||||
if (data.Length == 1 && data[0] == BinaryTypeCode.Null) return;
|
if (data.Length == 1 && data[0] == BinaryTypeCode.Null) return;
|
||||||
|
|
||||||
var targetType = target.GetType();
|
var context = DeserializationContextPool<ArrayBinaryInput>.Get(options);
|
||||||
var context = DeserializationContextPool.Get(options);
|
context.InitFromSpan(data);
|
||||||
context.InitBufferFromSpan(data);
|
try { PopulateCore(context, target); }
|
||||||
|
finally { DeserializationContextPool<ArrayBinaryInput>.Return(context); }
|
||||||
try
|
|
||||||
{
|
|
||||||
context.ReadHeader();
|
|
||||||
var typeCode = context.PeekByte();
|
|
||||||
|
|
||||||
if (typeCode == BinaryTypeCode.Object)
|
|
||||||
{
|
|
||||||
context.ReadByte(); // consume Object marker
|
|
||||||
PopulateObject(context, target, targetType, 0);
|
|
||||||
}
|
|
||||||
else if (typeCode == BinaryTypeCode.ObjectWithMetadata)
|
|
||||||
{
|
|
||||||
context.ReadByte(); // consume ObjectWithMetadata marker
|
|
||||||
ReadInlineMetadataForPopulate(context, targetType);
|
|
||||||
PopulateObject(context, target, targetType, 0);
|
|
||||||
}
|
|
||||||
else if (typeCode == BinaryTypeCode.Array && target is IList targetList)
|
|
||||||
{
|
|
||||||
context.ReadByte(); // consume Array marker
|
|
||||||
PopulateList(context, targetList, targetType, 0);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new AcBinaryDeserializationException(
|
|
||||||
$"Cannot populate type '{targetType.Name}' from binary type code {typeCode}",
|
|
||||||
context.Position, targetType);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Position-based string interning - no validation needed
|
|
||||||
}
|
|
||||||
catch (AcBinaryDeserializationException)
|
|
||||||
{
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
throw new AcBinaryDeserializationException(
|
|
||||||
$"Failed to populate object of type '{targetType.Name}': {ex.Message}",
|
|
||||||
context.Position, targetType, ex);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
DeserializationContextPool.Return(context);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -474,12 +398,28 @@ public static partial class AcBinaryDeserializer
|
||||||
if (data.Length == 1 && data[0] == BinaryTypeCode.Null) return;
|
if (data.Length == 1 && data[0] == BinaryTypeCode.Null) return;
|
||||||
|
|
||||||
var opts = options ?? AcBinarySerializerOptions.Default;
|
var opts = options ?? AcBinarySerializerOptions.Default;
|
||||||
var targetType = target.GetType();
|
var context = DeserializationContextPool<ArrayBinaryInput>.Get(opts);
|
||||||
var context = DeserializationContextPool.Get(opts);
|
context.InitFromSpan(data);
|
||||||
context.InitBufferFromSpan(data);
|
|
||||||
context.IsMergeMode = true;
|
context.IsMergeMode = true;
|
||||||
context.RemoveOrphanedItems = opts.RemoveOrphanedItems;
|
context.RemoveOrphanedItems = opts.RemoveOrphanedItems;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
PopulateMergeCore(context, target);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
DeserializationContextPool<ArrayBinaryInput>.Return(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Core merge populate: handles IId collection merge for top-level arrays.
|
||||||
|
/// </summary>
|
||||||
|
private static void PopulateMergeCore<TInput>(BinaryDeserializationContext<TInput> context, object target)
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
|
{
|
||||||
|
var targetType = target.GetType();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
context.ReadHeader();
|
context.ReadHeader();
|
||||||
|
|
@ -499,7 +439,6 @@ public static partial class AcBinaryDeserializer
|
||||||
else if (typeCode == BinaryTypeCode.Array && target is IList targetList)
|
else if (typeCode == BinaryTypeCode.Array && target is IList targetList)
|
||||||
{
|
{
|
||||||
context.ReadByte();
|
context.ReadByte();
|
||||||
// For top-level list merge, check if it's an IId collection
|
|
||||||
var elementType = GetCollectionElementType(targetType);
|
var elementType = GetCollectionElementType(targetType);
|
||||||
if (elementType != null)
|
if (elementType != null)
|
||||||
{
|
{
|
||||||
|
|
@ -509,12 +448,10 @@ public static partial class AcBinaryDeserializer
|
||||||
if (elementMetadata.IsComplexType && elementMetadata.IsIId && elementMetadata.IdGetter != null)
|
if (elementMetadata.IsComplexType && elementMetadata.IsIId && elementMetadata.IdGetter != null)
|
||||||
{
|
{
|
||||||
MergeIIdCollectionWithMetadata(context, targetList, elementType, wrapper, 0);
|
MergeIIdCollectionWithMetadata(context, targetList, elementType, wrapper, 0);
|
||||||
// Position-based string interning - no validation needed
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Non-IId collection, just populate
|
|
||||||
PopulateList(context, targetList, targetType, 0);
|
PopulateList(context, targetList, targetType, 0);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -523,23 +460,14 @@ public static partial class AcBinaryDeserializer
|
||||||
$"Cannot populate type '{targetType.Name}' from binary type code {typeCode}",
|
$"Cannot populate type '{targetType.Name}' from binary type code {typeCode}",
|
||||||
context.Position, targetType);
|
context.Position, targetType);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Position-based string interning - no validation needed
|
|
||||||
}
|
|
||||||
catch (AcBinaryDeserializationException)
|
|
||||||
{
|
|
||||||
throw;
|
|
||||||
}
|
}
|
||||||
|
catch (AcBinaryDeserializationException) { throw; }
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
throw new AcBinaryDeserializationException(
|
throw new AcBinaryDeserializationException(
|
||||||
$"Failed to populate/merge object of type '{targetType.Name}': {ex.Message}",
|
$"Failed to populate/merge object of type '{targetType.Name}': {ex.Message}",
|
||||||
context.Position, targetType, ex);
|
context.Position, targetType, ex);
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
DeserializationContextPool.Return(context);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
@ -561,25 +489,20 @@ public static partial class AcBinaryDeserializer
|
||||||
if (data.Length == 0 || (data.Length == 1 && data[0] == BinaryTypeCode.Null))
|
if (data.Length == 0 || (data.Length == 1 && data[0] == BinaryTypeCode.Null))
|
||||||
return EmptyDeserializeChain<T>.Instance;
|
return EmptyDeserializeChain<T>.Instance;
|
||||||
|
|
||||||
var targetType = typeof(T);
|
|
||||||
|
|
||||||
// Copy data to array for chain storage
|
|
||||||
var dataArray = data.ToArray();
|
var dataArray = data.ToArray();
|
||||||
var chainTracker = new AcSerializerCommon.ChainReferenceTracker();
|
var chainTracker = new AcSerializerCommon.ChainReferenceTracker();
|
||||||
var context = DeserializationContextPool.Get(options);
|
var context = DeserializationContextPool<ArrayBinaryInput>.Get(options);
|
||||||
context.InitBuffer(dataArray, dataArray.Length);
|
context.InitInput(new ArrayBinaryInput(dataArray));
|
||||||
context.ChainTracker = chainTracker;
|
context.ChainTracker = chainTracker;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
context.ReadHeader();
|
var result = (T?)DeserializeCore(context, typeof(T));
|
||||||
var result = ReadValue(context, targetType, 0);
|
return new BinaryDeserializeChain<T>(dataArray, options, chainTracker, result);
|
||||||
// Position-based string interning - no validation needed
|
|
||||||
return new BinaryDeserializeChain<T>(dataArray, options, chainTracker, (T?)result);
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
DeserializationContextPool.Return(context);
|
DeserializationContextPool<ArrayBinaryInput>.Return(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -612,29 +535,11 @@ public static partial class AcBinaryDeserializer
|
||||||
{
|
{
|
||||||
ThrowIfDisposed();
|
ThrowIfDisposed();
|
||||||
|
|
||||||
var targetType = typeof(TResult);
|
var context = DeserializationContextPool<ArrayBinaryInput>.Get(_options);
|
||||||
var context = DeserializationContextPool.Get(_options);
|
context.InitInput(new ArrayBinaryInput(_data));
|
||||||
context.InitBuffer(_data, _data.Length);
|
|
||||||
context.ChainTracker = _chainTracker;
|
context.ChainTracker = _chainTracker;
|
||||||
|
try { return (TResult?)DeserializeCore(context, typeof(TResult)); }
|
||||||
try
|
finally { DeserializationContextPool<ArrayBinaryInput>.Return(context); }
|
||||||
{
|
|
||||||
context.ReadHeader();
|
|
||||||
var result = ReadValue(context, targetType, 0);
|
|
||||||
// Position-based string interning - no validation needed
|
|
||||||
return (TResult?)result;
|
|
||||||
}
|
|
||||||
catch (AcBinaryDeserializationException) { throw; }
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
throw new AcBinaryDeserializationException(
|
|
||||||
$"Failed to deserialize to type '{targetType.Name}' in chain: {ex.Message}",
|
|
||||||
0, targetType, ex);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
DeserializationContextPool.Return(context);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IDeserializeChain<T> ThenPopulate(object target)
|
public IDeserializeChain<T> ThenPopulate(object target)
|
||||||
|
|
@ -642,53 +547,15 @@ public static partial class AcBinaryDeserializer
|
||||||
ArgumentNullException.ThrowIfNull(target);
|
ArgumentNullException.ThrowIfNull(target);
|
||||||
ThrowIfDisposed();
|
ThrowIfDisposed();
|
||||||
|
|
||||||
var targetType = target.GetType();
|
var context = DeserializationContextPool<ArrayBinaryInput>.Get(_options);
|
||||||
var context = DeserializationContextPool.Get(_options);
|
context.InitInput(new ArrayBinaryInput(_data));
|
||||||
context.InitBuffer(_data, _data.Length);
|
|
||||||
context.ChainTracker = _chainTracker;
|
context.ChainTracker = _chainTracker;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
context.ReadHeader();
|
PopulateCore(context, target);
|
||||||
var typeCode = context.PeekByte();
|
|
||||||
|
|
||||||
if (typeCode == BinaryTypeCode.Object)
|
|
||||||
{
|
|
||||||
context.ReadByte();
|
|
||||||
PopulateObject(context, target, targetType, 0);
|
|
||||||
}
|
|
||||||
else if (typeCode == BinaryTypeCode.ObjectWithMetadata)
|
|
||||||
{
|
|
||||||
context.ReadByte();
|
|
||||||
ReadInlineMetadataForPopulate(context, targetType);
|
|
||||||
PopulateObject(context, target, targetType, 0);
|
|
||||||
}
|
|
||||||
else if (typeCode == BinaryTypeCode.Array && target is IList targetList)
|
|
||||||
{
|
|
||||||
context.ReadByte();
|
|
||||||
PopulateList(context, targetList, targetType, 0);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new AcBinaryDeserializationException(
|
|
||||||
$"Cannot populate type '{targetType.Name}' from binary type code {typeCode}",
|
|
||||||
context.Position, targetType);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Position-based string interning - no validation needed
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
catch (AcBinaryDeserializationException) { throw; }
|
finally { DeserializationContextPool<ArrayBinaryInput>.Return(context); }
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
throw new AcBinaryDeserializationException(
|
|
||||||
$"Failed to populate object of type '{targetType.Name}' in chain: {ex.Message}",
|
|
||||||
0, targetType, ex);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
DeserializationContextPool.Return(context);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ThrowIfDisposed()
|
private void ThrowIfDisposed()
|
||||||
|
|
@ -713,7 +580,8 @@ public static partial class AcBinaryDeserializer
|
||||||
/// Returns true if handled, false if should fall back to generic path.
|
/// Returns true if handled, false if should fall back to generic path.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static bool TryReadAndSetTypedValue(BinaryDeserializationContext context, object target, BinaryPropertySetterBase propInfo, byte peekCode)
|
private static bool TryReadAndSetTypedValue<TInput>(BinaryDeserializationContext<TInput> context, object target, BinaryPropertySetterBase propInfo, byte peekCode)
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
{
|
{
|
||||||
// Only handle if we have a typed setter
|
// Only handle if we have a typed setter
|
||||||
if (propInfo.AccessorType == PropertyAccessorType.Object)
|
if (propInfo.AccessorType == PropertyAccessorType.Object)
|
||||||
|
|
@ -958,7 +826,8 @@ public static partial class AcBinaryDeserializer
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Optimized value reader using FrozenDictionary dispatch table.
|
/// Optimized value reader using FrozenDictionary dispatch table.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static object? ReadValue(BinaryDeserializationContext context, Type targetType, int depth)
|
private static object? ReadValue<TInput>(BinaryDeserializationContext<TInput> context, Type targetType, int depth)
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
{
|
{
|
||||||
if (context.IsAtEnd) return null;
|
if (context.IsAtEnd) return null;
|
||||||
|
|
||||||
|
|
@ -981,7 +850,7 @@ public static partial class AcBinaryDeserializer
|
||||||
return length == 0 ? string.Empty : context.ReadStringUtf8(length);
|
return length == 0 ? string.Empty : context.ReadStringUtf8(length);
|
||||||
}
|
}
|
||||||
|
|
||||||
var reader = TypeReaders[typeCode];
|
var reader = TypeReaderTable<TInput>.Readers[typeCode];
|
||||||
if (reader != null)
|
if (reader != null)
|
||||||
{
|
{
|
||||||
return reader(context, targetType, depth);
|
return reader(context, targetType, depth);
|
||||||
|
|
@ -996,7 +865,8 @@ public static partial class AcBinaryDeserializer
|
||||||
/// Sima string olvas<61>sa - NEM regisztr<74>l az intern t<>bl<62>ba.
|
/// Sima string olvas<61>sa - NEM regisztr<74>l az intern t<>bl<62>ba.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static string ReadPlainString(BinaryDeserializationContext context)
|
private static string ReadPlainString<TInput>(BinaryDeserializationContext<TInput> context)
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
{
|
{
|
||||||
var length = (int)context.ReadVarUInt();
|
var length = (int)context.ReadVarUInt();
|
||||||
if (length == 0) return string.Empty;
|
if (length == 0) return string.Empty;
|
||||||
|
|
@ -1008,7 +878,8 @@ public static partial class AcBinaryDeserializer
|
||||||
/// Wire format: [StringInternFirst][VarUInt cacheIndex][VarUInt length][UTF8 bytes]
|
/// Wire format: [StringInternFirst][VarUInt cacheIndex][VarUInt length][UTF8 bytes]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static string ReadAndRegisterInternedString(BinaryDeserializationContext context)
|
private static string ReadAndRegisterInternedString<TInput>(BinaryDeserializationContext<TInput> context)
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
{
|
{
|
||||||
var cacheIndex = (int)context.ReadVarUInt();
|
var cacheIndex = (int)context.ReadVarUInt();
|
||||||
var length = (int)context.ReadVarUInt();
|
var length = (int)context.ReadVarUInt();
|
||||||
|
|
@ -1022,7 +893,7 @@ public static partial class AcBinaryDeserializer
|
||||||
///// Read a string and register it in the intern table for future references.
|
///// Read a string and register it in the intern table for future references.
|
||||||
///// </summary>
|
///// </summary>
|
||||||
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
//private static string ReadAndInternString(BinaryDeserializationContext context, int streamPosition)
|
//private static string ReadAndInternString<TInput>(BinaryDeserializationContext<TInput> context, int streamPosition)
|
||||||
//{
|
//{
|
||||||
// var length = (int)context.ReadVarUInt();
|
// var length = (int)context.ReadVarUInt();
|
||||||
// if (length == 0) return string.Empty;
|
// if (length == 0) return string.Empty;
|
||||||
|
|
@ -1037,7 +908,8 @@ public static partial class AcBinaryDeserializer
|
||||||
//}
|
//}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static object ReadInt32Value(BinaryDeserializationContext context, Type targetType)
|
private static object ReadInt32Value<TInput>(BinaryDeserializationContext<TInput> context, Type targetType)
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
{
|
{
|
||||||
var value = context.ReadVarInt();
|
var value = context.ReadVarInt();
|
||||||
return ConvertToTargetType(value, targetType);
|
return ConvertToTargetType(value, targetType);
|
||||||
|
|
@ -1107,7 +979,8 @@ public static partial class AcBinaryDeserializer
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static object ReadEnumValue(BinaryDeserializationContext context, Type targetType)
|
private static object ReadEnumValue<TInput>(BinaryDeserializationContext<TInput> context, Type targetType)
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
{
|
{
|
||||||
var info = GetConversionInfo(targetType);
|
var info = GetConversionInfo(targetType);
|
||||||
var nextByte = context.ReadByte();
|
var nextByte = context.ReadByte();
|
||||||
|
|
@ -1132,7 +1005,8 @@ public static partial class AcBinaryDeserializer
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static byte[] ReadByteArray(BinaryDeserializationContext context)
|
private static byte[] ReadByteArray<TInput>(BinaryDeserializationContext<TInput> context)
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
{
|
{
|
||||||
var length = (int)context.ReadVarUInt();
|
var length = (int)context.ReadVarUInt();
|
||||||
if (length == 0) return [];
|
if (length == 0) return [];
|
||||||
|
|
@ -1165,7 +1039,8 @@ public static partial class AcBinaryDeserializer
|
||||||
/// ami ritka eset (2+ referencia), tehát nem lassítja a hot path-ot.
|
/// ami ritka eset (2+ referencia), tehát nem lassítja a hot path-ot.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static object? ReadObjectRef(BinaryDeserializationContext context, Type targetType, int depth)
|
private static object? ReadObjectRef<TInput>(BinaryDeserializationContext<TInput> context, Type targetType, int depth)
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
{
|
{
|
||||||
var cacheIndex = (int)context.ReadVarUInt();
|
var cacheIndex = (int)context.ReadVarUInt();
|
||||||
return context.GetInternedObject(cacheIndex);
|
return context.GetInternedObject(cacheIndex);
|
||||||
|
|
@ -1175,7 +1050,8 @@ public static partial class AcBinaryDeserializer
|
||||||
/// Object olvasása (nem tracked, vagy UseMetadata nélkül).
|
/// Object olvasása (nem tracked, vagy UseMetadata nélkül).
|
||||||
/// Wire format: [Object][props...]
|
/// Wire format: [Object][props...]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static object? ReadObject(BinaryDeserializationContext context, Type targetType, int depth)
|
private static object? ReadObject<TInput>(BinaryDeserializationContext<TInput> context, Type targetType, int depth)
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
{
|
{
|
||||||
return ReadObjectCore(context, targetType, depth, cacheIndex: -1);
|
return ReadObjectCore(context, targetType, depth, cacheIndex: -1);
|
||||||
}
|
}
|
||||||
|
|
@ -1185,7 +1061,8 @@ public static partial class AcBinaryDeserializer
|
||||||
/// Wire format: [ObjectRefFirst][VarUInt cacheIndex][props...]
|
/// Wire format: [ObjectRefFirst][VarUInt cacheIndex][props...]
|
||||||
/// Az objektumot regisztráljuk a cache-be a megadott index-re.
|
/// Az objektumot regisztráljuk a cache-be a megadott index-re.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static object? ReadObjectRefFirst(BinaryDeserializationContext context, Type targetType, int depth)
|
private static object? ReadObjectRefFirst<TInput>(BinaryDeserializationContext<TInput> context, Type targetType, int depth)
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
{
|
{
|
||||||
var cacheIndex = (int)context.ReadVarUInt();
|
var cacheIndex = (int)context.ReadVarUInt();
|
||||||
return ReadObjectCore(context, targetType, depth, cacheIndex: cacheIndex);
|
return ReadObjectCore(context, targetType, depth, cacheIndex: cacheIndex);
|
||||||
|
|
@ -1195,7 +1072,8 @@ public static partial class AcBinaryDeserializer
|
||||||
/// Object olvasás core implementáció.
|
/// Object olvasás core implementáció.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="cacheIndex">-1 = not cached, 0+ = register at this cache index</param>
|
/// <param name="cacheIndex">-1 = not cached, 0+ = register at this cache index</param>
|
||||||
private static object? ReadObjectCore(BinaryDeserializationContext context, Type targetType, int depth, int cacheIndex)
|
private static object? ReadObjectCore<TInput>(BinaryDeserializationContext<TInput> context, Type targetType, int depth, int cacheIndex)
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
{
|
{
|
||||||
// Handle dictionary types
|
// Handle dictionary types
|
||||||
if (IsDictionaryType(targetType, out var keyType, out var valueType))
|
if (IsDictionaryType(targetType, out var keyType, out var valueType))
|
||||||
|
|
@ -1240,7 +1118,8 @@ public static partial class AcBinaryDeserializer
|
||||||
/// Első előfordulás: [ObjectWithMetadata][propNameHash (4b)][propCount (VarUInt)][hash0..N][props...]
|
/// Első előfordulás: [ObjectWithMetadata][propNameHash (4b)][propCount (VarUInt)][hash0..N][props...]
|
||||||
/// Ismételt: [ObjectWithMetadata][propNameHash (4b)][props...]
|
/// Ismételt: [ObjectWithMetadata][propNameHash (4b)][props...]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static object? ReadObjectWithMetadata(BinaryDeserializationContext context, Type targetType, int depth)
|
private static object? ReadObjectWithMetadata<TInput>(BinaryDeserializationContext<TInput> context, Type targetType, int depth)
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
{
|
{
|
||||||
return ReadObjectWithMetadataCore(context, targetType, depth, cacheIndex: -1);
|
return ReadObjectWithMetadataCore(context, targetType, depth, cacheIndex: -1);
|
||||||
}
|
}
|
||||||
|
|
@ -1250,7 +1129,8 @@ public static partial class AcBinaryDeserializer
|
||||||
/// Wire format: [ObjectWithMetadataRefFirst][VarUInt cacheIndex][propNameHash (4b)][...][props...]
|
/// Wire format: [ObjectWithMetadataRefFirst][VarUInt cacheIndex][propNameHash (4b)][...][props...]
|
||||||
/// Az objektumot regisztráljuk a cache-be a megadott index-re.
|
/// Az objektumot regisztráljuk a cache-be a megadott index-re.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static object? ReadObjectWithMetadataRefFirst(BinaryDeserializationContext context, Type targetType, int depth)
|
private static object? ReadObjectWithMetadataRefFirst<TInput>(BinaryDeserializationContext<TInput> context, Type targetType, int depth)
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
{
|
{
|
||||||
var cacheIndex = (int)context.ReadVarUInt();
|
var cacheIndex = (int)context.ReadVarUInt();
|
||||||
return ReadObjectWithMetadataCore(context, targetType, depth, cacheIndex: cacheIndex);
|
return ReadObjectWithMetadataCore(context, targetType, depth, cacheIndex: cacheIndex);
|
||||||
|
|
@ -1260,7 +1140,8 @@ public static partial class AcBinaryDeserializer
|
||||||
/// ObjectWithMetadata olvasás core implementáció.
|
/// ObjectWithMetadata olvasás core implementáció.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="cacheIndex">-1 = not cached, 0+ = register at this cache index</param>
|
/// <param name="cacheIndex">-1 = not cached, 0+ = register at this cache index</param>
|
||||||
private static object? ReadObjectWithMetadataCore(BinaryDeserializationContext context, Type targetType, int depth, int cacheIndex)
|
private static object? ReadObjectWithMetadataCore<TInput>(BinaryDeserializationContext<TInput> context, Type targetType, int depth, int cacheIndex)
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
{
|
{
|
||||||
// Inline metadata: propNameHash mindig jön
|
// Inline metadata: propNameHash mindig jön
|
||||||
var propNameHash = context.ReadInt32Raw();
|
var propNameHash = context.ReadInt32Raw();
|
||||||
|
|
@ -1324,7 +1205,8 @@ public static partial class AcBinaryDeserializer
|
||||||
/// Az ObjectWithMetadata marker már consume-álva van.
|
/// 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.
|
/// Beolvassa a propNameHash-t + hash-eket (első előfordulásnál), és felépíti a cacheMap-et.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static void ReadInlineMetadataForPopulate(BinaryDeserializationContext context, Type targetType)
|
private static void ReadInlineMetadataForPopulate<TInput>(BinaryDeserializationContext<TInput> context, Type targetType)
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
{
|
{
|
||||||
var propNameHash = context.ReadInt32Raw();
|
var propNameHash = context.ReadInt32Raw();
|
||||||
|
|
||||||
|
|
@ -1383,7 +1265,8 @@ public static partial class AcBinaryDeserializer
|
||||||
|
|
||||||
#region Array Reading
|
#region Array Reading
|
||||||
|
|
||||||
private static object? ReadArray(BinaryDeserializationContext context, Type targetType, int depth)
|
private static object? ReadArray<TInput>(BinaryDeserializationContext<TInput> context, Type targetType, int depth)
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
{
|
{
|
||||||
var elementType = GetCollectionElementType(targetType);
|
var elementType = GetCollectionElementType(targetType);
|
||||||
if (elementType == null) elementType = typeof(object);
|
if (elementType == null) elementType = typeof(object);
|
||||||
|
|
@ -1446,7 +1329,8 @@ public static partial class AcBinaryDeserializer
|
||||||
/// Optimized primitive array reader using bulk operations.
|
/// Optimized primitive array reader using bulk operations.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static Array? TryReadPrimitiveArray(BinaryDeserializationContext context, Type elementType, int count)
|
private static Array? TryReadPrimitiveArray<TInput>(BinaryDeserializationContext<TInput> context, Type elementType, int count)
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
{
|
{
|
||||||
// Int32 array
|
// Int32 array
|
||||||
if (ReferenceEquals(elementType, IntType))
|
if (ReferenceEquals(elementType, IntType))
|
||||||
|
|
@ -1564,7 +1448,8 @@ public static partial class AcBinaryDeserializer
|
||||||
|
|
||||||
#region Dictionary Reading
|
#region Dictionary Reading
|
||||||
|
|
||||||
private static object? ReadDictionary(BinaryDeserializationContext context, Type targetType, int depth)
|
private static object? ReadDictionary<TInput>(BinaryDeserializationContext<TInput> context, Type targetType, int depth)
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
{
|
{
|
||||||
if (!IsDictionaryType(targetType, out var keyType, out var valueType))
|
if (!IsDictionaryType(targetType, out var keyType, out var valueType))
|
||||||
{
|
{
|
||||||
|
|
@ -1575,7 +1460,8 @@ public static partial class AcBinaryDeserializer
|
||||||
return ReadDictionaryAsObject(context, keyType!, valueType!, depth);
|
return ReadDictionaryAsObject(context, keyType!, valueType!, depth);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static object ReadDictionaryAsObject(BinaryDeserializationContext context, Type keyType, Type valueType, int depth)
|
private static object ReadDictionaryAsObject<TInput>(BinaryDeserializationContext<TInput> context, Type keyType, Type valueType, int depth)
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
{
|
{
|
||||||
var dictType = DictionaryGenericType.MakeGenericType(keyType, valueType);
|
var dictType = DictionaryGenericType.MakeGenericType(keyType, valueType);
|
||||||
var count = (int)context.ReadVarUInt();
|
var count = (int)context.ReadVarUInt();
|
||||||
|
|
@ -1597,7 +1483,8 @@ public static partial class AcBinaryDeserializer
|
||||||
|
|
||||||
#region Skip Value
|
#region Skip Value
|
||||||
|
|
||||||
private static void SkipValue(BinaryDeserializationContext context, BinaryDeserializeTypeMetadata metaData)
|
private static void SkipValue<TInput>(BinaryDeserializationContext<TInput> context, BinaryDeserializeTypeMetadata metaData)
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
{
|
{
|
||||||
var typeCode = context.ReadByte();
|
var typeCode = context.ReadByte();
|
||||||
|
|
||||||
|
|
@ -1708,7 +1595,8 @@ public static partial class AcBinaryDeserializer
|
||||||
/// Sima string kihagy<67>sa - NEM regisztr<74>l.
|
/// Sima string kihagy<67>sa - NEM regisztr<74>l.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static void SkipPlainString(BinaryDeserializationContext context)
|
private static void SkipPlainString<TInput>(BinaryDeserializationContext<TInput> context)
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
{
|
{
|
||||||
var byteLen = (int)context.ReadVarUInt();
|
var byteLen = (int)context.ReadVarUInt();
|
||||||
if (byteLen > 0)
|
if (byteLen > 0)
|
||||||
|
|
@ -1722,7 +1610,8 @@ public static partial class AcBinaryDeserializer
|
||||||
/// Wire format: [StringInternFirst][VarUInt cacheIndex][VarUInt length][UTF8 bytes]
|
/// Wire format: [StringInternFirst][VarUInt cacheIndex][VarUInt length][UTF8 bytes]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static void SkipAndRegisterInternedString(BinaryDeserializationContext context)
|
private static void SkipAndRegisterInternedString<TInput>(BinaryDeserializationContext<TInput> context)
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
{
|
{
|
||||||
var cacheIndex = (int)context.ReadVarUInt();
|
var cacheIndex = (int)context.ReadVarUInt();
|
||||||
var byteLen = (int)context.ReadVarUInt();
|
var byteLen = (int)context.ReadVarUInt();
|
||||||
|
|
@ -1735,7 +1624,8 @@ public static partial class AcBinaryDeserializer
|
||||||
/// Skip ObjectRefFirst - must read cacheIndex and register placeholder in cache.
|
/// Skip ObjectRefFirst - must read cacheIndex and register placeholder in cache.
|
||||||
/// Wire format: [ObjectRefFirst][VarUInt cacheIndex][props...]
|
/// Wire format: [ObjectRefFirst][VarUInt cacheIndex][props...]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static void SkipObjectRefFirst(BinaryDeserializationContext context, BinaryDeserializeTypeMetadata metaData)
|
private static void SkipObjectRefFirst<TInput>(BinaryDeserializationContext<TInput> context, BinaryDeserializeTypeMetadata metaData)
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
{
|
{
|
||||||
var cacheIndex = (int)context.ReadVarUInt();
|
var cacheIndex = (int)context.ReadVarUInt();
|
||||||
// Register placeholder (stream position as boxed int for potential lazy load)
|
// Register placeholder (stream position as boxed int for potential lazy load)
|
||||||
|
|
@ -1749,7 +1639,7 @@ public static partial class AcBinaryDeserializer
|
||||||
///// <param name="context">Deserialization context</param>
|
///// <param name="context">Deserialization context</param>
|
||||||
///// <param name="streamPosition">Position before the type code was read</param>
|
///// <param name="streamPosition">Position before the type code was read</param>
|
||||||
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
//private static void SkipAndInternString(BinaryDeserializationContext context, int streamPosition)
|
//private static void SkipAndInternString<TInput>(BinaryDeserializationContext<TInput> context, int streamPosition)
|
||||||
//{
|
//{
|
||||||
// var byteLen = (int)context.ReadVarUInt();
|
// var byteLen = (int)context.ReadVarUInt();
|
||||||
// if (byteLen == 0) return;
|
// if (byteLen == 0) return;
|
||||||
|
|
@ -1764,7 +1654,8 @@ public static partial class AcBinaryDeserializer
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Object kihagyása metadata nélkül — nem támogatott, mert nem tudjuk a property számot.
|
/// Object kihagyása metadata nélkül — nem támogatott, mert nem tudjuk a property számot.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static void SkipObject(BinaryDeserializationContext context, BinaryDeserializeTypeMetadata metaData)
|
private static void SkipObject<TInput>(BinaryDeserializationContext<TInput> context, BinaryDeserializeTypeMetadata metaData)
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
{
|
{
|
||||||
throw new NotSupportedException(
|
throw new NotSupportedException(
|
||||||
"SkipObject nem támogatott metadata nélkül. " +
|
"SkipObject nem támogatott metadata nélkül. " +
|
||||||
|
|
@ -1775,7 +1666,8 @@ public static partial class AcBinaryDeserializer
|
||||||
/// Skip ObjectWithMetadata/ObjectWithMetadataRefFirst.
|
/// Skip ObjectWithMetadata/ObjectWithMetadataRefFirst.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="cacheIndex">-1 = not cached, 0+ = register at this cache index</param>
|
/// <param name="cacheIndex">-1 = not cached, 0+ = register at this cache index</param>
|
||||||
private static void SkipObjectWithMetadata(BinaryDeserializationContext context, BinaryDeserializeTypeMetadata metaData, int cacheIndex)
|
private static void SkipObjectWithMetadata<TInput>(BinaryDeserializationContext<TInput> context, BinaryDeserializeTypeMetadata metaData, int cacheIndex)
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
{
|
{
|
||||||
if (cacheIndex >= 0)
|
if (cacheIndex >= 0)
|
||||||
{
|
{
|
||||||
|
|
@ -1804,7 +1696,8 @@ public static partial class AcBinaryDeserializer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void SkipArray(BinaryDeserializationContext context, BinaryDeserializeTypeMetadata metaData)
|
private static void SkipArray<TInput>(BinaryDeserializationContext<TInput> context, BinaryDeserializeTypeMetadata metaData)
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
{
|
{
|
||||||
var count = (int)context.ReadVarUInt();
|
var count = (int)context.ReadVarUInt();
|
||||||
for (int i = 0; i < count; i++)
|
for (int i = 0; i < count; i++)
|
||||||
|
|
@ -1813,7 +1706,8 @@ public static partial class AcBinaryDeserializer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void SkipDictionary(BinaryDeserializationContext context, BinaryDeserializeTypeMetadata metaData)
|
private static void SkipDictionary<TInput>(BinaryDeserializationContext<TInput> context, BinaryDeserializeTypeMetadata metaData)
|
||||||
|
where TInput : struct, IBinaryInputBase
|
||||||
{
|
{
|
||||||
var count = (int)context.ReadVarUInt();
|
var count = (int)context.ReadVarUInt();
|
||||||
for (int i = 0; i < count; i++)
|
for (int i = 0; i < count; i++)
|
||||||
|
|
@ -1877,4 +1771,3 @@ public static partial class AcBinaryDeserializer
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implementation moved to AcBinaryDeserializer.TypeConversionInfo.cs
|
// Implementation moved to AcBinaryDeserializer.TypeConversionInfo.cs
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
using System;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace AyCode.Core.Serializers.Binaries;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Binary input backed by a byte[] — the fastest input path.
|
||||||
|
/// Zero-copy: context references the byte[] directly, no linearization needed.
|
||||||
|
/// TryAdvanceSegment always returns false (single contiguous buffer).
|
||||||
|
///
|
||||||
|
/// Mirrors ArrayBinaryOutput pattern from the serializer side.
|
||||||
|
/// </summary>
|
||||||
|
public struct ArrayBinaryInput : IBinaryInputBase
|
||||||
|
{
|
||||||
|
private readonly byte[] _data;
|
||||||
|
private readonly int _length;
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public ArrayBinaryInput(byte[] data, int length)
|
||||||
|
{
|
||||||
|
_data = data;
|
||||||
|
_length = length;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public ArrayBinaryInput(byte[] data) : this(data, data.Length) { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides the buffer directly — zero copy.
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void Initialize(out byte[] buffer, out int position, out int bufferLength)
|
||||||
|
{
|
||||||
|
buffer = _data;
|
||||||
|
position = 0;
|
||||||
|
bufferLength = _length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Single-buffer input — no more segments available.
|
||||||
|
/// The JIT sees this always returns false and eliminates the call at the callsite.
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
|
public bool TryAdvanceSegment(ref byte[] buffer, ref int position, ref int bufferLength, int needed)
|
||||||
|
=> false;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace AyCode.Core.Serializers.Binaries;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Abstract base interface for binary input implementations.
|
||||||
|
/// Provides only the buffer management strategy (initialize/advance) — all read methods live in
|
||||||
|
/// BinaryDeserializationContext which owns the _buffer/_position state for zero virtual dispatch.
|
||||||
|
///
|
||||||
|
/// Mirrors IBinaryOutputBase pattern from the serializer side.
|
||||||
|
///
|
||||||
|
/// Derived structs implement:
|
||||||
|
/// - Initialize: provide the initial buffer, position, and length
|
||||||
|
/// - TryAdvanceSegment: handle buffer exhaustion (return false for single-buffer, advance for multi-segment)
|
||||||
|
/// </summary>
|
||||||
|
public interface IBinaryInputBase
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides the initial buffer, starting position, and buffer length.
|
||||||
|
/// Called once before deserialization begins.
|
||||||
|
/// For ArrayBinaryInput: sets buffer = byte[], position = 0, bufferLength = data.Length.
|
||||||
|
/// For SequenceBinaryInput: sets buffer to first segment's array, position/length to segment bounds.
|
||||||
|
/// </summary>
|
||||||
|
void Initialize(out byte[] buffer, out int position, out int bufferLength);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when the context's buffer cannot satisfy 'needed' more bytes. Cold path only.
|
||||||
|
/// Returns true if more data is available (segment advanced), false if end of input.
|
||||||
|
/// For ArrayBinaryInput: always returns false (single contiguous buffer).
|
||||||
|
/// For SequenceBinaryInput: advances to next segment, handles cross-boundary reads.
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
|
bool TryAdvanceSegment(ref byte[] buffer, ref int position, ref int bufferLength, int needed);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,143 @@
|
||||||
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace AyCode.Core.Serializers.Binaries;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Binary input that reads directly from a ReadOnlySequence (e.g. SignalR pipe, network stream).
|
||||||
|
/// Processes segments one-by-one without linearizing the entire payload.
|
||||||
|
///
|
||||||
|
/// For values that span segment boundaries (e.g. a 4-byte int split across 2 segments),
|
||||||
|
/// copies the overlapping bytes into a small scratch buffer and reads from there.
|
||||||
|
///
|
||||||
|
/// Mirrors BufferWriterBinaryOutput pattern from the serializer side.
|
||||||
|
/// </summary>
|
||||||
|
public struct SequenceBinaryInput : IBinaryInputBase
|
||||||
|
{
|
||||||
|
// Pre-extracted segments from the ReadOnlySequence.
|
||||||
|
// Using ArraySegment avoids holding onto ReadOnlyMemory (which can't get byte[] without TryGetArray).
|
||||||
|
private readonly ArraySegment<byte>[] _segments;
|
||||||
|
private int _currentSegment;
|
||||||
|
|
||||||
|
// Scratch buffer for cross-boundary reads (max 16 bytes for Guid/Decimal)
|
||||||
|
private byte[]? _scratchBuffer;
|
||||||
|
private int _scratchLength;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a SequenceBinaryInput from a multi-segment ReadOnlySequence.
|
||||||
|
/// Pre-extracts all segments as ArraySegment for fast iteration.
|
||||||
|
/// </summary>
|
||||||
|
public SequenceBinaryInput(ReadOnlySequence<byte> sequence)
|
||||||
|
{
|
||||||
|
var segmentCount = 0;
|
||||||
|
foreach (var _ in sequence)
|
||||||
|
segmentCount++;
|
||||||
|
|
||||||
|
_segments = new ArraySegment<byte>[segmentCount];
|
||||||
|
var i = 0;
|
||||||
|
foreach (var memory in sequence)
|
||||||
|
{
|
||||||
|
if (MemoryMarshal.TryGetArray(memory, out var segment))
|
||||||
|
{
|
||||||
|
_segments[i++] = segment;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Non-array-backed memory: copy to a temp array
|
||||||
|
var temp = new byte[memory.Length];
|
||||||
|
memory.Span.CopyTo(temp);
|
||||||
|
_segments[i++] = new ArraySegment<byte>(temp, 0, temp.Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_currentSegment = 0;
|
||||||
|
_scratchBuffer = null;
|
||||||
|
_scratchLength = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides the first segment's buffer.
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void Initialize(out byte[] buffer, out int position, out int bufferLength)
|
||||||
|
{
|
||||||
|
if (_segments.Length == 0)
|
||||||
|
throw new AcBinaryDeserializationException("Empty sequence — no segments to read.");
|
||||||
|
|
||||||
|
var seg = _segments[0];
|
||||||
|
buffer = seg.Array!;
|
||||||
|
position = seg.Offset;
|
||||||
|
bufferLength = seg.Offset + seg.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Advances to the next segment when the current one is exhausted.
|
||||||
|
/// Handles cross-boundary reads by copying overlapping bytes to a scratch buffer.
|
||||||
|
/// </summary>
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
|
public bool TryAdvanceSegment(ref byte[] buffer, ref int position, ref int bufferLength, int needed)
|
||||||
|
{
|
||||||
|
// Calculate remaining bytes in current segment
|
||||||
|
var remaining = bufferLength - position;
|
||||||
|
|
||||||
|
if (remaining > 0 && remaining < needed)
|
||||||
|
{
|
||||||
|
// Cross-boundary read: value spans two segments
|
||||||
|
return TryReadCrossBoundary(ref buffer, ref position, ref bufferLength, needed, remaining);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Current segment fully consumed — advance to next
|
||||||
|
_currentSegment++;
|
||||||
|
if (_currentSegment >= _segments.Length)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var seg = _segments[_currentSegment];
|
||||||
|
buffer = seg.Array!;
|
||||||
|
position = seg.Offset;
|
||||||
|
bufferLength = seg.Offset + seg.Count;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles a read that spans two segments by copying the overlapping bytes
|
||||||
|
/// into a scratch buffer, then setting up the context to read from it.
|
||||||
|
/// After this read, the next EnsureAvailable will advance to the remainder of the new segment.
|
||||||
|
/// </summary>
|
||||||
|
private bool TryReadCrossBoundary(ref byte[] buffer, ref int position, ref int bufferLength, int needed, int remaining)
|
||||||
|
{
|
||||||
|
_currentSegment++;
|
||||||
|
if (_currentSegment >= _segments.Length)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Ensure scratch buffer is large enough (max 16 bytes for Guid/Decimal)
|
||||||
|
_scratchBuffer ??= new byte[32];
|
||||||
|
|
||||||
|
// Copy tail of current segment
|
||||||
|
Buffer.BlockCopy(buffer, position, _scratchBuffer, 0, remaining);
|
||||||
|
|
||||||
|
// Copy head of next segment
|
||||||
|
var nextSeg = _segments[_currentSegment];
|
||||||
|
var fromNext = Math.Min(needed - remaining, nextSeg.Count);
|
||||||
|
Buffer.BlockCopy(nextSeg.Array!, nextSeg.Offset, _scratchBuffer, remaining, fromNext);
|
||||||
|
|
||||||
|
_scratchLength = remaining + fromNext;
|
||||||
|
|
||||||
|
// Set up context to read from scratch buffer
|
||||||
|
buffer = _scratchBuffer;
|
||||||
|
position = 0;
|
||||||
|
bufferLength = _scratchLength;
|
||||||
|
|
||||||
|
// After the read completes, the position will be at 'needed' in scratch.
|
||||||
|
// The next EnsureAvailable will fail (scratch is consumed) and call TryAdvanceSegment again,
|
||||||
|
// which will set up the remainder of the current segment.
|
||||||
|
// We need to adjust the current segment's offset to skip the bytes we already copied.
|
||||||
|
_segments[_currentSegment] = new ArraySegment<byte>(
|
||||||
|
nextSeg.Array!,
|
||||||
|
nextSeg.Offset + fromNext,
|
||||||
|
nextSeg.Count - fromNext);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue