AyCode.Core/AyCode.Core/Serializers/Binaries/AcBinarySerializerOptions.cs

205 lines
9.1 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Runtime.CompilerServices;
using AyCode.Core.Compression;
namespace AyCode.Core.Serializers.Binaries;
/// <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.
/// Returns a new instance each time to prevent shared state corruption.
/// </summary>
public static AcBinarySerializerOptions Default => new();
/// <summary>
/// Options optimized for maximum speed (no interning, no references).
/// Use when deserializer knows the exact type structure.
/// Returns a new instance each time to prevent shared state corruption.
/// </summary>
public static AcBinarySerializerOptions FastMode => new()
{
UseStringInterning = StringInterningMode.None,
ReferenceHandling = ReferenceHandlingMode.None
};
/// <summary>
/// Options for shallow serialization (root level only).
/// Returns a new instance each time to prevent shared state corruption.
/// </summary>
public static AcBinarySerializerOptions ShallowCopy => new()
{
MaxDepth = 0,
// Truncate preserves the original "root + Null nested" semantic; under default Throw the preset
// would throw on the first nested object, defeating its purpose.
MaxDepthBehavior = MaxDepthBehavior.Truncate,
UseStringInterning = StringInterningMode.None,
ReferenceHandling = ReferenceHandlingMode.None
};
/// <summary>
/// Options optimized for WASM environment with string caching enabled.
/// Returns a new instance each time to prevent shared state corruption.
/// </summary>
public static AcBinarySerializerOptions WasmOptimized => new()
{
IsWasm = true,
UseStringCaching = true
};
/// <summary>
/// Whether running in WebAssembly/Browser environment.
/// When true, enables WASM-specific optimizations like string caching.
/// Default: auto-detected via OperatingSystem.IsBrowser()
/// </summary>
public bool IsWasm { get; init; } = DetectedIsWasm;
/// <summary>
/// Whether to cache short strings during deserialization to reduce allocations.
/// Most beneficial in WASM where GC is expensive.
/// Auto-enabled when IsWasm is true, can be overridden.
/// Default: follows IsWasm setting
/// </summary>
public bool UseStringCaching { get; private init; } = DetectedIsWasm;
/// <summary>
/// Maximum string length to cache when UseStringCaching is enabled.
/// Longer strings are not cached to avoid memory bloat.
/// Default: 64 characters
/// </summary>
public int MaxCachedStringLength { get; init; } = 64;
/// <summary>
/// Whether to include property hash metadata in footer for cross-type deserialization.
/// When enabled, property name hashes (FNV-1a) are written per type in the footer,
/// allowing the deserializer to match properties by name between different types.
/// Default: false (no overhead)
/// </summary>
public bool UseMetadata { get; set; } = false;
public bool UseGeneratedCode { get; set; } = true;
/// <summary>
/// Wire encoding mode.
/// Compact: VarInt + UTF-8 (default, smaller output).
/// Fast: Fixed-width integers + UTF-16 (larger output, faster encode/decode).
/// </summary>
public WireMode WireMode { get; set; } = WireMode.Compact;
/// <summary>
/// Controls how string interning is applied during serialization.
/// None: No interning, all strings written inline.
/// Attribute: Only properties with [AcStringIntern(true)] are interned.
/// All: All strings within length limits are interned (legacy behavior).
/// Default: All
/// </summary>
public StringInterningMode UseStringInterning { get; set; } = StringInterningMode.All;
/// <summary>
/// When true, checks for duplicate property name hashes during serialization (UseMetadata mode).
/// Throws exception if FNV-1a hash collision is detected between property names of the same type.
/// Should be enabled during development/testing, can be disabled in production for performance.
/// Default: true (safety first)
/// </summary>
public bool CheckDuplicatePropName { 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>
/// Maximum string length to consider for interning.
/// Longer strings (descriptions, notes, etc.) are usually unique and not worth interning.
/// Set to 0 to disable max limit.
/// Default: 64 (strings longer than 64 chars are not interned)
/// </summary>
public byte MaxStringInternLength { get; init; } = 64;
/// <summary>
/// Initial capacity for serialization buffer.
/// Default: 16384 bytes (16 KB)
/// </summary>
public int InitialBufferCapacity { get; init; } = 16384;
/// <summary>
/// Chunk size (in bytes) used by <see cref="BufferWriterBinaryOutput"/> when writing to an <see cref="System.Buffers.IBufferWriter{T}"/>.
/// Controls how much data is accumulated before committing (Advance + GetMemory) to the underlying writer.
///
/// <para><b>How it works:</b> The serializer writes into a chunk buffer. When the chunk fills up,
/// it commits the written bytes to the IBufferWriter and acquires a new chunk. Larger chunks mean
/// fewer Grow() calls (less overhead), but consume more memory per chunk. Smaller chunks reduce
/// memory footprint and latency-to-first-byte for streaming, but increase Grow() call frequency.</para>
///
/// <para><b>Choosing a value:</b></para>
/// <list type="bullet">
/// <item><b>Memory-backed writers</b> (ArrayPooledBufferWriter, file/DB blob): use 65536 (64 KB, the default).
/// Stays below the .NET LOH threshold (85 KB), minimizes Grow() overhead for large payloads.
/// An 8 MB payload triggers ~128 Grow() calls.</item>
/// <item><b>Network streaming</b> (Kestrel PipeWriter, SignalR): use 4096 (4 KB).
/// Aligns with Kestrel's default memory pool slab size and TCP segment sizes (~1500 byte MTU × 3).
/// Reduces latency-to-first-byte by flushing data to the wire sooner.</item>
/// </list>
///
/// <para><b>Impact of wrong value:</b> Using 64 KB on a network stream adds minor latency for the first chunk.
/// Using 4 KB for a memory-backed writer causes ~16× more Grow() calls than necessary (2048 vs 128 for 8 MB).
/// The default (64 KB) is the safe choice — suboptimal on network streams but never catastrophic.</para>
///
/// Default: 65535 (UINT16 max — wire-format invariant for AsyncSegment chunked framing).
/// </summary>
public int BufferWriterChunkSize { get; set; } = 65535;
/// <summary>
/// Optional property-level filter invoked before metadata registration and serialization.
/// Return false to exclude the property from the payload.
/// </summary>
public BinaryPropertyFilter? PropertyFilter { get; init; }
/// <summary>
/// When true, PopulateMerge will remove items from destination collections
/// that have no matching Id in the source data.
/// Only applies to IId collections during merge operations.
/// Default: false (orphaned items are kept)
/// </summary>
public bool RemoveOrphanedItems { get; init; } = false;
/// <summary>
/// Controls LZ4 compression for serialized data.
/// None: No compression (default, fastest).
/// Block: Compresses entire payload as single block (better compression ratio).
/// BlockArray: Compresses in 64KB chunks (streaming-friendly, lower memory).
/// Note: Both modes are WASM-compatible (pure managed implementation).
/// Default: None
/// </summary>
public Lz4CompressionMode UseCompression { get; set; } = Lz4CompressionMode.None;
/// <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 (and string interning disabled for speed).
/// </summary>
public static AcBinarySerializerOptions WithoutReferenceHandling => new()
{
ReferenceHandling = ReferenceHandlingMode.None,
};
/// <summary>
/// Creates options without metadata (faster but less flexible).
/// </summary>
public static AcBinarySerializerOptions WithoutMetadata => new() { UseMetadata = false };
}