AyCode.Core/AyCode.Core/Extensions/AcBinarySerializerOptions.cs

193 lines
7.2 KiB
C#

using System.Runtime.CompilerServices;
namespace AyCode.Core.Extensions;
/// <summary>
/// Options for AcBinarySerializer and AcBinaryDeserializer.
/// Optimized for speed and memory efficiency over raw size.
/// </summary>
public sealed class AcBinarySerializerOptions : AcSerializerOptions
{
public override AcSerializerType SerializerType { get; init; } = AcSerializerType.Binary;
/// <summary>
/// Current binary format version. Incremented when breaking changes are made.
/// </summary>
public const byte FormatVersion = 1;
/// <summary>
/// Default options instance with metadata and string interning enabled.
/// </summary>
public static readonly AcBinarySerializerOptions Default = new();
/// <summary>
/// Options optimized for maximum speed (no metadata, no interning).
/// Use when deserializer knows the exact type structure.
/// </summary>
public static readonly AcBinarySerializerOptions FastMode = new()
{
UseMetadata = false,
UseStringInterning = false,
UseReferenceHandling = false
};
/// <summary>
/// Options for shallow serialization (root level only).
/// </summary>
public static readonly AcBinarySerializerOptions ShallowCopy = new()
{
MaxDepth = 0,
UseReferenceHandling = false
};
/// <summary>
/// Whether to include metadata header with property names.
/// When enabled, property names are stored once and referenced by index.
/// Improves deserialization speed and allows schema evolution.
/// Default: true
/// </summary>
public bool UseMetadata { get; init; } = true;
/// <summary>
/// Whether to intern repeated strings.
/// When enabled, duplicate strings are stored once and referenced by index.
/// Reduces size and memory for objects with many repeated string values.
/// Default: true
/// </summary>
public bool UseStringInterning { get; init; } = true;
/// <summary>
/// Minimum string length to consider for interning.
/// Shorter strings are written inline to avoid overhead.
/// Default: 4 (strings shorter than 4 chars are not interned)
/// </summary>
public byte MinStringInternLength { get; init; } = 4;
/// <summary>
/// Initial capacity for serialization buffer.
/// Default: 4096 bytes
/// </summary>
public int InitialBufferCapacity { get; init; } = 4096;
/// <summary>
/// Creates options with specified max depth.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static AcBinarySerializerOptions WithMaxDepth(byte maxDepth) => new() { MaxDepth = maxDepth };
/// <summary>
/// Creates options without reference handling.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static AcBinarySerializerOptions WithoutReferenceHandling() => new() { UseReferenceHandling = false };
/// <summary>
/// Creates options without metadata (faster but less flexible).
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static AcBinarySerializerOptions WithoutMetadata() => new() { UseMetadata = false };
}
/// <summary>
/// Binary type codes for serialization.
/// Designed for fast switch dispatch and compact storage.
/// Lower 5 bits = type code (0-31)
/// Upper 3 bits = flags (interned, reference, has-type-info)
/// </summary>
internal static class BinaryTypeCode
{
// Primitive types (0-15)
public const byte Null = 0;
public const byte True = 1;
public const byte False = 2;
public const byte Int8 = 3;
public const byte UInt8 = 4;
public const byte Int16 = 5;
public const byte UInt16 = 6;
public const byte Int32 = 7;
public const byte UInt32 = 8;
public const byte Int64 = 9;
public const byte UInt64 = 10;
public const byte Float32 = 11;
public const byte Float64 = 12;
public const byte Decimal = 13;
public const byte Char = 14;
// String types (16-19)
public const byte String = 16; // Inline UTF8 string
public const byte StringInterned = 17; // Reference to interned string by index
public const byte StringEmpty = 18; // Empty string marker
// Date/Time types (20-23)
public const byte DateTime = 20;
public const byte DateTimeOffset = 21;
public const byte TimeSpan = 22;
public const byte Guid = 23;
// Enum (24)
public const byte Enum = 24;
// Complex types (25-31)
public const byte Object = 25; // Start of object
public const byte ObjectEnd = 26; // End of object marker
public const byte ObjectRef = 27; // Reference to previously serialized object
public const byte Array = 28; // Start of array/list
public const byte Dictionary = 29; // Start of dictionary
public const byte ByteArray = 30; // Optimized byte[] storage
// Special markers (32+, for header/meta)
// Header flags byte structure (for values >= 64):
// Bit 0 (0x01): HasMetadata
// Bit 1 (0x02): HasReferenceHandling
// Values 32, 33 are legacy for backward compatibility
public const byte MetadataHeader = 32; // Binary has metadata section (legacy, implies HasReferenceHandling=true)
public const byte NoMetadataHeader = 33; // Binary has no metadata (legacy, implies HasReferenceHandling=true)
// New flag-based header markers (48+)
// Base value 48 (0x30 = 00110000) chosen to:
// - Be distinguishable from legacy values (32, 33)
// - Not conflict with flag bits in lower nibble
// - Leave room below Int32Tiny (64)
public const byte HeaderFlagsBase = 48; // Base value for flag-based headers (0x30)
public const byte HeaderFlag_Metadata = 0x01;
public const byte HeaderFlag_ReferenceHandling = 0x02;
// Compact integer variants (for VarInt optimization)
public const byte Int32Tiny = 64; // -16 to 111 stored in single byte (value = code - 64 - 16)
public const byte Int32TinyMax = 191; // Upper bound for tiny int
/// <summary>
/// Check if type code represents a reference (string or object).
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsReference(byte code) => code == StringInterned || code == ObjectRef;
/// <summary>
/// Check if type code is a tiny int (single byte int32 encoding).
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsTinyInt(byte code) => code >= Int32Tiny && code <= Int32TinyMax;
/// <summary>
/// Decode tiny int value from type code.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int DecodeTinyInt(byte code) => code - Int32Tiny - 16;
/// <summary>
/// Encode small int value (-16 to 111) as type code.
/// Returns true if value fits in tiny encoding.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryEncodeTinyInt(int value, out byte code)
{
if (value >= -16 && value <= 111)
{
code = (byte)(value + 16 + Int32Tiny);
return true;
}
code = 0;
return false;
}
}