diff --git a/AyCode.Core/docs/BINARY_WRITERS.md b/AyCode.Core/docs/BINARY_WRITERS.md index 746f0ec..5264672 100644 --- a/AyCode.Core/docs/BINARY_WRITERS.md +++ b/AyCode.Core/docs/BINARY_WRITERS.md @@ -120,7 +120,7 @@ Same cached chunk pattern (`GetMemory` → `TryGetArray` → direct array writes ### Usage -Selected via `BinaryProtocolMode.Segment` in `AcBinaryHubProtocol`. The protocol casts `IBufferWriter output` to `PipeWriter` (safe — SignalR always provides `PipeWriter`). +Selected via `BinaryProtocolMode.AsyncSegment` in `AcBinaryHubProtocol`. The protocol casts `IBufferWriter output` to `PipeWriter` (safe — SignalR always provides `PipeWriter`). ```csharp AcBinarySerializer.Serialize(value, (PipeWriter)output, options) // AsyncPipeWriterOutput path diff --git a/AyCode.Services/SignalRs/AcBinaryHubProtocol.cs b/AyCode.Services/SignalRs/AcBinaryHubProtocol.cs index cd8b8a1..fee8746 100644 --- a/AyCode.Services/SignalRs/AcBinaryHubProtocol.cs +++ b/AyCode.Services/SignalRs/AcBinaryHubProtocol.cs @@ -412,14 +412,23 @@ public class AcBinaryHubProtocol : IHubProtocol return; } - // Flush BWO to pipe, then serialize directly to the pipe via AcBinarySerializer + // Bytes mode: serialize to byte[], write through BWO (no FlushAndReset needed) + if (_protocolMode == BinaryProtocolMode.Bytes) + { + var serialized = AcBinarySerializer.Serialize(value, _options); + bw.WriteRaw(serialized.Length); + bw.WriteBytes(serialized); + return; + } + + // Segment / AsyncSegment: serialize directly to the pipe bw.FlushAndReset(); // Reserve arg length prefix directly on the pipe var argLenSpan = output.GetSpan(LengthPrefixSize); output.Advance(LengthPrefixSize); - var argBytes = _protocolMode == BinaryProtocolMode.Segment + var argBytes = _protocolMode == BinaryProtocolMode.AsyncSegment ? AcBinarySerializer.Serialize(value, (System.IO.Pipelines.PipeWriter)output, _options) : AcBinarySerializer.Serialize(value, output, _options); @@ -482,6 +491,13 @@ public class AcBinaryHubProtocol : IHubProtocol return SequenceToByteArray(argSlice.Slice(1)); } + // Bytes mode: linearize to byte[] → ArrayBinaryInput (fastest deser, no segment overhead) + if (_protocolMode == BinaryProtocolMode.Bytes) + { + var bytes = SequenceToByteArray(argSlice); + return AcBinaryDeserializer.Deserialize(bytes, targetType, _options); + } + return DeserializeFromSequence(argSlice, targetType, _options); } diff --git a/AyCode.Services/SignalRs/AcSignalRClientBase.cs b/AyCode.Services/SignalRs/AcSignalRClientBase.cs index f963f3b..24fabbc 100644 --- a/AyCode.Services/SignalRs/AcSignalRClientBase.cs +++ b/AyCode.Services/SignalRs/AcSignalRClientBase.cs @@ -75,7 +75,7 @@ namespace AyCode.Services.SignalRs var binaryOptions = AcBinarySerializerOptions.Default; binaryOptions.BufferWriterChunkSize = 4096; - return new AyCodeBinaryHubProtocol(binaryOptions); + return new AyCodeBinaryHubProtocol(binaryOptions, BinaryProtocolMode.AsyncSegment); }); //Vagy ha az options-t is DI-ből: diff --git a/AyCode.Services/SignalRs/AyCodeBinaryHubProtocol.cs b/AyCode.Services/SignalRs/AyCodeBinaryHubProtocol.cs index f8312bf..41406bb 100644 --- a/AyCode.Services/SignalRs/AyCodeBinaryHubProtocol.cs +++ b/AyCode.Services/SignalRs/AyCodeBinaryHubProtocol.cs @@ -60,6 +60,13 @@ public class AyCodeBinaryHubProtocol : AcBinaryHubProtocol targetType = dataType; } + // Bytes mode: linearize to byte[] → ArrayBinaryInput (fastest deser, no segment overhead) + if (_protocolMode == BinaryProtocolMode.Bytes) + { + var bytes = SequenceToByteArray(argSlice); + return AcBinaryDeserializer.Deserialize(bytes, targetType, Options); + } + return DeserializeFromSequence(argSlice, targetType, Options); } } diff --git a/AyCode.Services/SignalRs/BinaryProtocolMode.cs b/AyCode.Services/SignalRs/BinaryProtocolMode.cs index a11e80e..9e30520 100644 --- a/AyCode.Services/SignalRs/BinaryProtocolMode.cs +++ b/AyCode.Services/SignalRs/BinaryProtocolMode.cs @@ -1,16 +1,44 @@ namespace AyCode.Services.SignalRs; /// -/// Controls how the binary protocol transports serialized data over the network. +/// Controls how the binary protocol serializes and transports data over the network. +/// +/// Bytes: Serialize via ArrayBinaryOutput → single contiguous byte[], +/// written to the pipe as a raw blob. Deserialize via SequenceReader.ToArray() → +/// ArrayBinaryInput (single buffer, TryAdvanceSegment always false → JIT-eliminated). +/// Fastest individual ser/deser, no zerocopy, no pipeline overlap. +/// +/// +/// Segment: Serialize via BufferWriterBinaryOutput directly to the PipeWriter, +/// chunk-by-chunk with a single Flush at the end. Deserialize via SequenceBinaryInput +/// from multi-segment ReadOnlySequence<byte> (lazy TryGet iteration, cross-boundary scratch). +/// Zerocopy write, but no pipeline overlap. +/// +/// +/// AsyncSegment: Serialize via AsyncPipeWriterOutput directly to the PipeWriter, +/// per-chunk FlushAsync sends data to the network during serialization. Deserialize via +/// PipeReaderBinaryInput with on-demand ReadAsync (processes chunks as they arrive). +/// Zerocopy write + pipeline parallelism (ser/network/deser overlap), highest roundtrip potential +/// for large payloads. +/// /// public enum BinaryProtocolMode { - /// Standard: serialize → egyben küld/fogad. + /// + /// ArrayBinaryOutput → byte[] → pipe. Deser: ToArray() → ArrayBinaryInput. + /// Fastest ser/deser, no zerocopy, no pipeline overlap. + /// Bytes = 0, - /// Szinkron segment streaming: flush Grow()-ban → chunk-onként hálózatra. + /// + /// BufferWriterBinaryOutput → PipeWriter, single Flush at end. Deser: SequenceBinaryInput (multi-segment). + /// Zerocopy write, no pipeline overlap. + /// Segment = 1, - /// Async segment streaming: async serializer + async output (jövő). + /// + /// AsyncPipeWriterOutput → PipeWriter, per-chunk FlushAsync. Deser: PipeReaderBinaryInput (on-demand ReadAsync). + /// Zerocopy write + pipeline parallelism (ser/network/deser overlap). + /// AsyncSegment = 2, } diff --git a/AyCode.Services/docs/SIGNALR.md b/AyCode.Services/docs/SIGNALR.md index 8c7aea5..4ae8f79 100644 --- a/AyCode.Services/docs/SIGNALR.md +++ b/AyCode.Services/docs/SIGNALR.md @@ -70,7 +70,7 @@ CRUD helpers (`PostAsync`, `GetByIdAsync`, `GetAllAsync`, `PostDataAsync`) are g ### AcBinaryHubProtocol / AyCodeBinaryHubProtocol -Custom `IHubProtocol` (`"acbinary"`), replaces JSON. Zero-copy write via `BufferWriterBinaryOutput` standalone mode + `AcBinarySerializer.Serialize(value, output)` directly to pipe. Zero-copy read via `SequenceReader` from pipe's `ReadOnlySequence`. `BinaryProtocolMode` constructor parameter selects transport strategy: `Bytes` (default, single flush), `Segment` (per-chunk flush via `AsyncPipeWriterOutput`), `AsyncSegment` (reserved). +Custom `IHubProtocol` (`"acbinary"`), replaces JSON. Zero-copy write via `BufferWriterBinaryOutput` standalone mode + `AcBinarySerializer.Serialize(value, output)` directly to pipe. Zero-copy read via `SequenceReader` from pipe's `ReadOnlySequence`. `BinaryProtocolMode` constructor parameter selects transport strategy: `Bytes` (default, ArrayBinaryOutput → byte[]), `Segment` (BWO zerocopy to PipeWriter, single flush), `AsyncSegment` (AsyncPipeWriterOutput, per-chunk FlushAsync + pipeline parallelism). `AcBinaryHubProtocol` is the base (unsealed) — general binary framing only. `AyCodeBinaryHubProtocol` derives from it with consumer-specific logic: `SignalParams` capture (via `OnArgumentRead` hook), `IsRawBytesData` path, `SignalDataType` type resolution. Register `AyCodeBinaryHubProtocol` in both client and server. diff --git a/AyCode.Services/docs/SIGNALR_BINARY_PROTOCOL.md b/AyCode.Services/docs/SIGNALR_BINARY_PROTOCOL.md index 803a8e4..61581c5 100644 --- a/AyCode.Services/docs/SIGNALR_BINARY_PROTOCOL.md +++ b/AyCode.Services/docs/SIGNALR_BINARY_PROTOCOL.md @@ -160,14 +160,14 @@ Typical overhead for 225KB payload with 4096-byte segments: ~224.5KB zero-copy, ## BinaryProtocolMode -`enum BinaryProtocolMode` — constructor parameter for `AcBinaryHubProtocol`, selects transport strategy: +`enum BinaryProtocolMode` — constructor parameter for `AcBinaryHubProtocol`, selects serialization + transport strategy: -| Value | Behavior | -|-------|----------| -| `Bytes` (default) | Standard: serialize to `BufferWriterBinaryOutput`, single flush at end. | -| `Segment` | Segment streaming: serialize to `AsyncPipeWriterOutput`, flush per 4096-byte chunk via `PipeWriter.FlushAsync().Forget()`. Network transfer overlaps with serialization. | -| `AsyncSegment` | Reserved for future async serializer. | +| Value | Serialize | Deserialize | Characteristics | +|-------|-----------|-------------|-----------------| +| `Bytes` (default) | `ArrayBinaryOutput` → `byte[]` → write to pipe as raw blob | `SequenceReader.ToArray()` → `ArrayBinaryInput` (single contiguous buffer, `TryAdvanceSegment` → false, JIT-eliminated) | Fastest individual ser/deser. No zerocopy. No pipeline overlap. | +| `Segment` | `BufferWriterBinaryOutput` → directly to `PipeWriter`, chunk-by-chunk, single `Flush` at end | `SequenceBinaryInput` → multi-segment `ReadOnlySequence` (lazy `TryGet` iteration, cross-boundary scratch) | Zerocopy write. No pipeline overlap. | +| `AsyncSegment` | `AsyncPipeWriterOutput` → directly to `PipeWriter`, per-chunk `FlushAsync().Forget()` with backpressure | `PipeReaderBinaryInput` → on-demand `ReadAsync`, processes chunks as they arrive from the network | Zerocopy write + pipeline parallelism (ser/network/deser overlap). Highest roundtrip potential for large payloads. | -In `Segment` mode, `WriteArgument` casts `IBufferWriter output` to `PipeWriter` and calls `AcBinarySerializer.Serialize(value, pipeWriter, options)` which uses `AsyncPipeWriterOutput` internally. The reader side currently uses the same `SequenceBinaryInput` path (SignalR delivers complete messages via `TryParseMessage`). `PipeReaderBinaryInput` is available for future direct-pipe deserialization. +In `AsyncSegment` mode, `WriteArgument` casts `IBufferWriter output` to `PipeWriter` and calls `AcBinarySerializer.Serialize(value, pipeWriter, options)` which uses `AsyncPipeWriterOutput` internally. In `Bytes` and `Segment` mode, the standard `AcBinarySerializer.Serialize(value, output, options)` path is used (BWO on `IBufferWriter`). **Source:** `AyCode.Services/SignalRs/AcBinaryHubProtocol.cs` (base), `AyCode.Services/SignalRs/AyCodeBinaryHubProtocol.cs` (consumer logic), `AyCode.Services/SignalRs/BinaryProtocolMode.cs` (enum)