diff --git a/AyCode.Core/Serializers/AcSerializerOptions.cs b/AyCode.Core/Serializers/AcSerializerOptions.cs
index 745868a..16adedb 100644
--- a/AyCode.Core/Serializers/AcSerializerOptions.cs
+++ b/AyCode.Core/Serializers/AcSerializerOptions.cs
@@ -114,6 +114,22 @@ public enum ReferenceHandlingMode : byte
All = 2
}
+///
+/// Wire encoding mode for binary serialization.
+///
+public enum WireMode : byte
+{
+ ///
+ /// Compact encoding: VarInt for integers, UTF-8 for strings. Smaller output.
+ ///
+ Compact = 0,
+
+ ///
+ /// Fast encoding: fixed-width integers, UTF-16 for strings. Larger output, faster encode/decode.
+ ///
+ Fast = 1
+}
+
///
/// Delegate for custom property mapping during cross-type deserialization/population.
/// Enables mapping between different class hierarchies or renamed properties.
diff --git a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.Read.cs b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.Read.cs
index f224a6d..ccf5b10 100644
--- a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.Read.cs
+++ b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.Read.cs
@@ -12,7 +12,7 @@ public static partial class AcBinaryDeserializer
{
private static readonly Encoding Utf8NoBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false);
- #region Buffer State — owned by context for zero virtual dispatch
+ #region Buffer State � owned by context for zero virtual dispatch
internal byte[] _buffer = null!;
internal int _bufferLength;
@@ -20,7 +20,7 @@ public static partial class AcBinaryDeserializer
#endregion
- // String caching state — needed for WASM optimization
+ // String caching state � needed for WASM optimization
// The cache dictionary is owned by context (pooled), passed in at init time.
private bool _useStringCaching;
private int _maxCachedStringLength;
@@ -172,6 +172,16 @@ public static partial class AcBinaryDeserializer
return value;
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public T ReadRaw() where T : unmanaged
+ {
+ var size = Unsafe.SizeOf();
+ EnsureAvailable(size);
+ var value = Unsafe.ReadUnaligned(ref _buffer[_position]);
+ _position += size;
+ return value;
+ }
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int ReadInt32Raw()
{
@@ -188,6 +198,7 @@ public static partial class AcBinaryDeserializer
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int ReadVarInt()
{
+ if (FastWire) { return ReadRaw(); }
var raw = ReadVarUInt();
var value = (int)(raw >> 1) ^ -(int)(raw & 1);
return value;
@@ -196,6 +207,7 @@ public static partial class AcBinaryDeserializer
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public uint ReadVarUInt()
{
+ if (FastWire) { return ReadRaw(); }
// Fast path: single byte (0-127) - ~70% of cases
var b0 = _buffer[_position];
if ((b0 & 0x80) == 0)
@@ -245,6 +257,7 @@ public static partial class AcBinaryDeserializer
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public long ReadVarLong()
{
+ if (FastWire) { return ReadRaw(); }
var raw = ReadVarULong();
var value = (long)(raw >> 1) ^ -((long)raw & 1);
return value;
@@ -253,6 +266,7 @@ public static partial class AcBinaryDeserializer
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ulong ReadVarULong()
{
+ if (FastWire) { return ReadRaw(); }
ulong value = 0;
var shift = 0;
while (true)
@@ -277,7 +291,7 @@ public static partial class AcBinaryDeserializer
#endregion
///
- /// Called on first StringInternFirst marker — disables _stringCache because
+ /// Called on first StringInternFirst marker � disables _stringCache because
/// interned strings are resolved via _internCache and plain strings appear only once.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -311,6 +325,17 @@ public static partial class AcBinaryDeserializer
return string.Empty;
}
+ // FastWire: length is char count, data is UTF-16 (2 bytes per char)
+ if (FastWire)
+ {
+ var byteLen = length * 2;
+ EnsureAvailable(byteLen);
+ var chars = MemoryMarshal.Cast(_buffer.AsSpan(_position, byteLen));
+ var value = new string(chars);
+ _position += byteLen;
+ return value;
+ }
+
EnsureAvailable(length);
// WASM optimization: cache short strings to reduce allocations
@@ -319,8 +344,8 @@ public static partial class AcBinaryDeserializer
return ReadStringUtf8Cached(length);
}
- // ASCII fast path: short strings (?128 bytes) with all ASCII bytes
- // use string.Create + direct byte?char widening, avoiding UTF8Encoding overhead.
+ // ASCII fast path: short strings (≤128 bytes) with all ASCII bytes
+ // use string.Create + direct byte→char widening, avoiding UTF8Encoding overhead.
if (length <= 128 && System.Text.Ascii.IsValid(_buffer.AsSpan(_position, length)))
{
var pos = _position;
@@ -333,9 +358,9 @@ public static partial class AcBinaryDeserializer
});
}
- var value = Utf8NoBom.GetString(_buffer, _position, length);
+ var value2 = Utf8NoBom.GetString(_buffer, _position, length);
_position += length;
- return value;
+ return value2;
}
private string ReadStringUtf8Cached(int length)
@@ -366,7 +391,7 @@ public static partial class AcBinaryDeserializer
///
/// Full-content hash for string caching.
- /// CRITICAL: DO NOT SIMPLIFY — prevents hash collisions for similar property names.
+ /// CRITICAL: DO NOT SIMPLIFY � prevents hash collisions for similar property names.
/// See BinaryDeserializationContext for full history.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
diff --git a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.cs b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.cs
index efc200b..4e1c736 100644
--- a/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.cs
+++ b/AyCode.Core/Serializers/Binaries/AcBinaryDeserializer.BinaryDeserializationContext.cs
@@ -63,6 +63,7 @@ public static partial class AcBinaryDeserializer
public bool HasMetadata;
public bool IsMergeMode;
public bool RemoveOrphanedItems;
+ public bool FastWire;
// Options-derived properties
public byte MinStringInternLength => Options.MinStringInternLength;
@@ -138,6 +139,7 @@ public static partial class AcBinaryDeserializer
HasMetadata = false;
IsMergeMode = false;
RemoveOrphanedItems = false;
+ FastWire = Options.WireMode == WireMode.Fast;
ChainTracker = null;
}
diff --git a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs
index 8cfe96e..92759f1 100644
--- a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs
+++ b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.BinarySerializationContext.cs
@@ -227,6 +227,7 @@ public static partial class AcBinarySerializer
/// Reference handling is safe because generated code inlines TryConsumeWritePlanEntry for IId types.
///
public bool IsDirectObjectWrite => !UseMetadata && !HasPropertyFilter;
+ public bool FastWire { get; private set; }
public byte MinStringInternLength => Options.MinStringInternLength;
public byte MaxStringInternLength => Options.MaxStringInternLength;
public BinaryPropertyFilter? PropertyFilter => Options.PropertyFilter;
@@ -249,6 +250,7 @@ public static partial class AcBinarySerializer
public BinarySerializationContext(AcBinarySerializerOptions options)
{
Reset(options);
+ FastWire = options.WireMode == WireMode.Fast;
}
///
@@ -262,6 +264,7 @@ public static partial class AcBinarySerializer
// IMPORTANT: base.Reset sets Options first, so derived code can use Options-derived properties
base.Reset(options);
HasPropertyFilter = Options.PropertyFilter != null;
+ FastWire = Options.WireMode == WireMode.Fast;
}
public override void Clear()
@@ -386,6 +389,7 @@ public static partial class AcBinarySerializer
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteVarUInt(uint value)
{
+ if (FastWire) { WriteRaw(value); return; }
if (value < 0x80)
{
if (_position >= _bufferEnd)
@@ -405,6 +409,7 @@ public static partial class AcBinarySerializer
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteVarInt(int value)
{
+ if (FastWire) { WriteRaw(value); return; }
var encoded = (uint)((value << 1) ^ (value >> 31));
WriteVarUInt(encoded);
}
@@ -412,6 +417,7 @@ public static partial class AcBinarySerializer
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteVarULong(ulong value)
{
+ if (FastWire) { WriteRaw(value); return; }
if (value < 0x80)
{
if (_position >= _bufferEnd)
@@ -431,6 +437,7 @@ public static partial class AcBinarySerializer
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteVarLong(long value)
{
+ if (FastWire) { WriteRaw(value); return; }
var encoded = (ulong)((value << 1) ^ (value >> 63));
WriteVarULong(encoded);
}
@@ -481,6 +488,18 @@ public static partial class AcBinarySerializer
public void WriteStringUtf8(string value)
{
+ if (FastWire)
+ {
+ // UTF-16: char count (fixed uint) + raw char data (zero-encoding memcopy)
+ var charLen = value.Length;
+ var byteLen = charLen * 2;
+ WriteRaw(charLen);
+ EnsureCapacity(byteLen);
+ MemoryMarshal.AsBytes(value.AsSpan()).CopyTo(_buffer.AsSpan(_position, byteLen));
+ _position += byteLen;
+ return;
+ }
+
var charLength = value.Length;
// Speculative ASCII fast path: assume byteCount == charLength
diff --git a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs
index 22041b8..3e2de6a 100644
--- a/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs
+++ b/AyCode.Core/Serializers/Binaries/AcBinarySerializer.cs
@@ -970,6 +970,14 @@ public static partial class AcBinarySerializer
#endif
}
+ // FastWire: skip FixStr optimization (UTF-8 specific), write String marker + UTF-16 data
+ if (context.FastWire)
+ {
+ context.WriteByte(BinaryTypeCode.String);
+ context.WriteStringUtf8(value);
+ return;
+ }
+
// Fast path for short strings: check length first (cheap), then ASCII
// FixStr encodes type+length in single byte for strings <= 31 chars
var length = value.Length;
diff --git a/AyCode.Core/Serializers/Binaries/AcBinarySerializerOptions.cs b/AyCode.Core/Serializers/Binaries/AcBinarySerializerOptions.cs
index 654d25e..a96eb42 100644
--- a/AyCode.Core/Serializers/Binaries/AcBinarySerializerOptions.cs
+++ b/AyCode.Core/Serializers/Binaries/AcBinarySerializerOptions.cs
@@ -95,6 +95,13 @@ public sealed class AcBinarySerializerOptions : AcSerializerOptions
///
public StringInterningMode UseStringInterning { get; set; } = StringInterningMode.Attribute;
+ ///
+ /// Wire encoding mode.
+ /// Compact: VarInt + UTF-8 (default, smaller output).
+ /// Fast: Fixed-width integers + UTF-16 (larger output, faster encode/decode).
+ ///
+ public WireMode WireMode { get; set; } = WireMode.Fast;
+
///
/// 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.