AyCode.Core/AyCode.Core/Serializers/Binaries/ArrayBinaryOutput.cs

416 lines
13 KiB
C#

using System;
using System.Buffers;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
namespace AyCode.Core.Serializers.Binaries;
/// <summary>
/// High-performance binary output backed by a byte[] from ArrayPool.
/// Matches the exact performance characteristics of the original BinarySerializationContext buffer code:
/// direct _buffer[_position++] indexing, Unsafe.WriteUnaligned, SIMD bulk copy.
///
/// This is the fastest output path — use when the result is needed as byte[]/Span.
/// </summary>
public sealed class ArrayBinaryOutput : BinaryOutputBase, IDisposable
{
private const int MinBufferSize = 256;
private static readonly Encoding Utf8NoBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false);
private byte[] _buffer;
private int _position;
public ArrayBinaryOutput(int initialCapacity = 4096)
{
_buffer = ArrayPool<byte>.Shared.Rent(Math.Max(initialCapacity, MinBufferSize));
}
/// <inheritdoc />
public override int Position
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _position;
}
#region Abstract Overrides Core Primitives
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void WriteByte(byte value)
{
if (_position >= _buffer.Length)
GrowBuffer(_position + 1);
_buffer[_position++] = value;
}
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void WriteTwoBytes(byte b1, byte b2)
{
EnsureCapacity(2);
_buffer[_position++] = b1;
_buffer[_position++] = b2;
}
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void WriteBytes(ReadOnlySpan<byte> data)
{
EnsureCapacity(data.Length);
data.CopyTo(_buffer.AsSpan(_position));
_position += data.Length;
}
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void WriteRaw<T>(T value)
{
var size = Unsafe.SizeOf<T>();
EnsureCapacity(size);
Unsafe.WriteUnaligned(ref _buffer[_position], value);
_position += size;
}
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void EnsureCapacity(int additionalBytes)
{
var required = _position + additionalBytes;
if (required <= _buffer.Length)
return;
GrowBuffer(required);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void GrowBuffer(int required)
{
var newSize = Math.Max(_buffer.Length * 2, required);
var newBuffer = ArrayPool<byte>.Shared.Rent(newSize);
_buffer.AsSpan(0, _position).CopyTo(newBuffer);
ArrayPool<byte>.Shared.Return(_buffer);
_buffer = newBuffer;
}
#endregion
#region Optimized Overrides Specialized Types (direct buffer access)
/// <summary>
/// Optimized: single EnsureCapacity + direct Unsafe.WriteUnaligned + indexer.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void WriteTypeCodeAndRaw<T>(byte typeCode, T value)
{
var size = 1 + Unsafe.SizeOf<T>();
EnsureCapacity(size);
_buffer[_position++] = typeCode;
Unsafe.WriteUnaligned(ref _buffer[_position], value);
_position += Unsafe.SizeOf<T>();
}
/// <summary>
/// Optimized: VarUInt with direct _buffer[_position++] access.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void WriteVarUInt(uint value)
{
if (value < 0x80)
{
if (_position >= _buffer.Length)
GrowBuffer(_position + 1);
_buffer[_position++] = (byte)value;
return;
}
EnsureCapacity(5);
while (value >= 0x80)
{
_buffer[_position++] = (byte)(value | 0x80);
value >>= 7;
}
_buffer[_position++] = (byte)value;
}
/// <summary>
/// Optimized: VarInt with direct buffer access.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void WriteVarInt(int value)
{
var encoded = (uint)((value << 1) ^ (value >> 31));
WriteVarUInt(encoded);
}
/// <summary>
/// Optimized: VarULong with direct buffer access.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void WriteVarULong(ulong value)
{
if (value < 0x80)
{
if (_position >= _buffer.Length)
GrowBuffer(_position + 1);
_buffer[_position++] = (byte)value;
return;
}
EnsureCapacity(10);
while (value >= 0x80)
{
_buffer[_position++] = (byte)(value | 0x80);
value >>= 7;
}
_buffer[_position++] = (byte)value;
}
/// <summary>
/// Optimized: VarLong with direct buffer access.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void WriteVarLong(long value)
{
var encoded = (ulong)((value << 1) ^ (value >> 63));
WriteVarULong(encoded);
}
/// <summary>
/// Optimized: direct Unsafe.WriteUnaligned for decimal bits.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void WriteDecimalBits(decimal value)
{
EnsureCapacity(16);
Span<int> bits = stackalloc int[4];
decimal.TryGetBits(value, bits, out _);
MemoryMarshal.AsBytes(bits).CopyTo(_buffer.AsSpan(_position, 16));
_position += 16;
}
/// <summary>
/// Optimized: direct Unsafe.WriteUnaligned + indexer for DateTime.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void WriteDateTimeBits(DateTime value)
{
EnsureCapacity(9);
Unsafe.WriteUnaligned(ref _buffer[_position], value.Ticks);
_buffer[_position + 8] = (byte)value.Kind;
_position += 9;
}
/// <summary>
/// Optimized: direct TryWriteBytes into buffer.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void WriteGuidBits(Guid value)
{
EnsureCapacity(16);
value.TryWriteBytes(_buffer.AsSpan(_position, 16));
_position += 16;
}
/// <summary>
/// Optimized: direct Unsafe.WriteUnaligned for DateTimeOffset.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void WriteDateTimeOffsetBits(DateTimeOffset value)
{
EnsureCapacity(10);
Unsafe.WriteUnaligned(ref _buffer[_position], value.UtcTicks);
Unsafe.WriteUnaligned(ref _buffer[_position + 8], (short)value.Offset.TotalMinutes);
_position += 10;
}
/// <summary>
/// Optimized: direct ASCII fast path into _buffer.
/// </summary>
public override void WriteStringUtf8(string value)
{
if (Ascii.IsValid(value))
{
WriteVarUInt((uint)value.Length);
EnsureCapacity(value.Length);
Ascii.FromUtf16(value.AsSpan(), _buffer.AsSpan(_position, value.Length), out _);
_position += value.Length;
return;
}
var byteCount = Utf8NoBom.GetByteCount(value);
WriteVarUInt((uint)byteCount);
EnsureCapacity(byteCount);
Utf8NoBom.GetBytes(value.AsSpan(), _buffer.AsSpan(_position, byteCount));
_position += byteCount;
}
/// <summary>
/// Optimized: FixStr with direct buffer write.
/// </summary>
public override void WriteFixStr(string value)
{
var length = value.Length;
EnsureCapacity(1 + length);
_buffer[_position++] = BinaryTypeCode.EncodeFixStr(length);
Ascii.FromUtf16(value.AsSpan(), _buffer.AsSpan(_position, length), out _);
_position += length;
}
/// <summary>
/// Optimized: FixStrDirect with SIMD try into buffer.
/// </summary>
public override void WriteFixStrDirect(string value)
{
var length = value.Length;
EnsureCapacity(1 + length);
var destSpan = _buffer.AsSpan(_position + 1, length);
var status = Ascii.FromUtf16(value.AsSpan(), destSpan, out var bytesWritten);
if (status == System.Buffers.OperationStatus.Done && bytesWritten == length)
{
_buffer[_position] = BinaryTypeCode.EncodeFixStr(length);
_position += 1 + length;
}
else
{
_buffer[_position++] = BinaryTypeCode.String;
WriteStringUtf8Internal(value);
}
}
/// <summary>
/// Optimized: FixStrBytes with direct buffer copy.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override void WriteFixStrBytes(ReadOnlySpan<byte> utf8Bytes)
{
var length = utf8Bytes.Length;
EnsureCapacity(1 + length);
_buffer[_position++] = BinaryTypeCode.EncodeFixStr(length);
utf8Bytes.CopyTo(_buffer.AsSpan(_position, length));
_position += length;
}
private void WriteStringUtf8Internal(string value)
{
var byteCount = Utf8NoBom.GetByteCount(value);
WriteVarUInt((uint)byteCount);
EnsureCapacity(byteCount);
Utf8NoBom.GetBytes(value.AsSpan(), _buffer.AsSpan(_position, byteCount));
_position += byteCount;
}
#endregion
#region Optimized Overrides Bulk Arrays (direct buffer, batched capacity)
/// <inheritdoc />
public override void WriteDoubleArrayBulk(double[] array)
{
EnsureCapacity(array.Length * 9);
for (var i = 0; i < array.Length; i++)
{
_buffer[_position++] = BinaryTypeCode.Float64;
Unsafe.WriteUnaligned(ref _buffer[_position], array[i]);
_position += 8;
}
}
/// <inheritdoc />
public override void WriteFloatArrayBulk(float[] array)
{
EnsureCapacity(array.Length * 5);
for (var i = 0; i < array.Length; i++)
{
_buffer[_position++] = BinaryTypeCode.Float32;
Unsafe.WriteUnaligned(ref _buffer[_position], array[i]);
_position += 4;
}
}
/// <inheritdoc />
public override void WriteGuidArrayBulk(Guid[] array)
{
EnsureCapacity(array.Length * 17);
for (var i = 0; i < array.Length; i++)
{
_buffer[_position++] = BinaryTypeCode.Guid;
array[i].TryWriteBytes(_buffer.AsSpan(_position, 16));
_position += 16;
}
}
/// <inheritdoc />
public override void WriteBytesSimd(ReadOnlySpan<byte> source)
{
EnsureCapacity(source.Length);
var destination = _buffer.AsSpan(_position, source.Length);
if (Vector.IsHardwareAccelerated && source.Length >= Vector<byte>.Count * 2)
{
var vectorSize = Vector<byte>.Count;
var i = 0;
var length = source.Length;
var vectorCount = length / vectorSize;
for (var v = 0; v < vectorCount; v++)
{
var vec = new Vector<byte>(source.Slice(i, vectorSize));
vec.CopyTo(destination.Slice(i, vectorSize));
i += vectorSize;
}
if (i < length)
source.Slice(i).CopyTo(destination.Slice(i));
}
else
{
source.CopyTo(destination);
}
_position += source.Length;
}
#endregion
#region Output Methods
/// <summary>Returns the written data as a ReadOnlySpan without allocation.</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ReadOnlySpan<byte> AsSpan() => _buffer.AsSpan(0, _position);
/// <summary>Copies the written data to a new exactly-sized array.</summary>
public byte[] ToArray()
{
var result = GC.AllocateUninitializedArray<byte>(_position);
_buffer.AsSpan(0, _position).CopyTo(result);
return result;
}
/// <summary>Copies the written data to an IBufferWriter (single memcpy).</summary>
public void WriteTo(IBufferWriter<byte> writer)
{
var span = writer.GetSpan(_position);
_buffer.AsSpan(0, _position).CopyTo(span);
writer.Advance(_position);
}
/// <summary>Resets position for reuse without deallocation.</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Reset() => _position = 0;
#endregion
#region IDisposable
public void Dispose()
{
if (_buffer != null)
{
ArrayPool<byte>.Shared.Return(_buffer);
_buffer = null!;
}
}
#endregion
}