AyCode.Core/AyCode.Core/docs/BINARY_OPTIONS.md

9.7 KiB
Raw Blame History

AcBinary Configuration

Configuration options, presets, and option interactions for AcBinarySerializerOptions. For wire format see BINARY_FORMAT.md. For features (interning, ref tracking, property ordering) see BINARY_FEATURES.md.

WireMode

Value Integers Strings Output size Speed
Compact (default) VarInt/VarUInt (15 bytes) UTF-8 with speculative ASCII fast path Smaller Slightly slower
Fast Fixed-width raw bytes (4/8 bytes) UTF-16 memcpy (charCount * 2 bytes) Larger Fastest encode/decode

Format difference for strings:

  • Compact: [VarUInt byteLength] [UTF-8 bytes] — speculative ASCII (1 pass if all ASCII, rewind+UTF-8 fallback otherwise)
  • Fast: [VarUInt charCount] [raw UTF-16 bytes] — zero-encoding memcpy

Code branch: context.FastWire flag set at context.Reset(). Checked in WriteStringUtf8() and integer write methods. FixStr optimization is skipped in Fast mode (UTF-8 specific).

ReferenceHandling

Value Tracked objects Scan pass Header flags Wire markers
None Nothing Skipped 0x00 Standard object markers only
OnlyId IId objects only (by ID value) Partial 0x02 ObjectRefFirst(70) + ObjectRef(65)
All (default) All reference types Full graph walk 0x06 ObjectRefFirst(70) + ObjectRef(65)

Format impact: When enabled, multi-referenced objects are written once with ObjectRefFirst(70) + VarUInt(refCacheIndex) on first encounter, then replaced by ObjectRef(65) + VarUInt(refCacheIndex) on subsequent encounters. Header HasCacheCount flag is set and cache count written.

Interaction with ThrowOnCircularReference (default: true):

  • true + ref handling enabled: all objects tracked for cycle detection, throws InvalidOperationException on circular reference
  • false + ref handling enabled: only IId types tracked for deduplication, non-IId circular refs silently truncated at MaxDepth

UseMetadata

Value Wire markers Property matching Overhead
false (default) FixObj/Object Positional index only — types must match None
true ObjectWithMetadata(69) / ObjectWithMetadataRefFirst(71) FNV-1a property name hashes 4 bytes per property per type

Format impact: When enabled, each type's first occurrence writes [VarUInt hashCount] [FNV-1a hash × N] before properties. Deserializer uses hashes to build source→destination index mapping, enabling cross-type deserialization (different property sets/ordering).

Code branch: context.UseMetadata controls whether ObjectWithMetadata(69) or plain Object(64) markers are used. When false, IsDirectObjectWrite=true allows source-generated writers to bypass WriteObject entirely and inline property writes.

Related: CheckDuplicatePropName (default: true) — throws if FNV-1a hash collision detected between property names of the same type. Disable in production for performance.

UseStringInterning

Value Eligible strings Scan overhead Wire markers
None Nothing None String(91) / FixStr only
Attribute (default) Properties with [AcStringIntern(true)] Scans marked properties StringInternFirst(94) + StringInterned(92)
All All strings within length limits Scans all strings StringInternFirst(94) + StringInterned(92)

Length limits: MinStringInternLength (default: 4) and MaxStringInternLength (default: 64, 0=unlimited). Strings outside this range are always written inline.

Format impact: Interned strings on first occurrence: [StringInternFirst(94)] [VarUInt cacheIndex] [string data]. Subsequent: [StringInterned(92)] [VarUInt cacheIndex] (12 bytes vs full string). Single-occurrence strings are never interned — no overhead for unique strings.

Code branch: context.StringInternEligible flag set per-property before WriteString. Scan pass builds a WriteDuplicateEntry[] plan; write pass consumes it via cursor.

MaxDepth

Value Behavior
255 (default) Effectively unlimited nesting
0 Root level only — nested objects/collections written as Null(76)
N Objects deeper than N levels written as Null(76)

Format impact: Depth-exceeded values appear as Null(76) in the stream — indistinguishable from actual null values. No special marker.

Code branch: Checked at entry of every object/collection write: if (depth > MaxDepth) { WriteByte(Null); return; }.

UseCompression

Value Method Granularity Memory
None (default) No compression
Block LZ4 single block Entire payload Full buffer in memory
BlockArray LZ4 chunked 64KB chunks Streaming-friendly, lower peak memory

Format impact: Compression is applied post-serialization as a transparent wrapper — the inner wire format is unchanged. Both modes are pure managed C# (WASM-compatible, no native dependencies).

Code branch: Applied in AcBinarySerializer.Serialize() after the serialization context produces the raw buffer: if (UseCompression != None) Lz4.Compress(buffer, mode). Decompression is automatic on deserialize.

PropertyFilter

Optional delegate BinaryPropertyFilter? (default: null). When set, invoked for each property to decide inclusion.

delegate bool BinaryPropertyFilter(in BinaryPropertyFilterContext context);

BinaryPropertyFilterContext fields: DeclaringType, PropertyName, PropertyType, Instance (null during metadata phase), IsMetadataPhase, GetValue() (lazy).

Format impact: Excluded properties are completely absent from the stream — no marker, no placeholder. The deserializer must use UseMetadata=true or identical filter to correctly match property indices.

Code branch: context.HasPropertyFilter checked in ShouldSerializeProperty(). Called twice: once during metadata registration (Instance=null), once during write phase.

PropertyMapper

Optional delegate PropertyMapperDelegate? (default: null) for cross-type deserialization property remapping.

delegate PropertyInfo? PropertyMapperDelegate(PropertyInfo sourceProperty, Type destinationType);

Purpose: Maps properties between different class hierarchies (renamed properties, external DTOs). Result is cached — zero overhead on same-type operations (Deserialize<T>).

WASM Options

Option Default Purpose
IsWasm OperatingSystem.IsBrowser() Auto-detect WASM environment
UseStringCaching follows IsWasm Cache short strings during deserialization to reduce GC pressure
MaxCachedStringLength 64 Max string length to cache

Format impact: None — these are deserialization-only optimizations. When UseStringCaching=true, the deserializer maintains an intern cache for strings ≤ MaxCachedStringLength chars. Disabled automatically when StringInternFirst marker is encountered (interning takes precedence).

Other Options

Option Type Default Purpose
UseGeneratedCode bool true Use source-generated writers/readers when available
InitialBufferCapacity int 4096 Starting buffer size (bytes) for serialization output
RemoveOrphanedItems bool false During PopulateMerge: remove destination collection items with no matching source ID
UseAsync bool false Async context pool return via ThreadPool. Auto-disabled in WASM and when ReferenceHandling=None
MaxContextPoolSize int 8 Max serialization contexts kept in pool

Presets

Preset WireMode Metadata StringInterning RefHandling MaxDepth Compression Other
Default Compact false Attribute All 255 None
FastMode Compact false None None 255 None No scan pass
ShallowCopy Compact false None None 0 None Root level only
WasmOptimized Compact false Attribute All 255 None +StringCaching
WithoutReferenceHandling Compact false Attribute None 255 None No scan pass
WithoutMetadata Compact false Attribute All 255 None

Performance implication of presets:

  • Default / WasmOptimized — two-phase (scan + serialize) due to ReferenceHandling=All
  • FastMode / ShallowCopy — single-phase (no scan pass) since both interning and refs are disabled
  • The scan pass adds ~20-30% overhead; disable it when the object graph is a simple tree

Option Interactions

Key interdependencies that affect which code branches execute:

Combination Effect
ReferenceHandling=None + UseStringInterning=None No scan pass — fastest path, single-phase serialization
ReferenceHandling=All + UseMetadata=true Uses ObjectWithMetadataRefFirst(71) marker — combined ref + metadata
UseMetadata=false + UseGeneratedCode=true IsDirectObjectWrite=true — generated code inlines property writes, bypasses WriteObject
UseMetadata=true + PropertyFilter set Filter invoked twice (metadata phase + write phase); filter results must be stable
WireMode=Fast + UseStringInterning!=None Interned strings still use the fast string path (UTF-16 for first occurrence, VarUInt index for subsequent)
UseCompression!=None + any other option Compression is orthogonal — applied post-serialization, inner format unchanged