using AyCode.Core.Serializers.Binaries; using Microsoft.Extensions.Logging; namespace AyCode.Services.SignalRs; /// /// Configuration for and derived protocols. /// Use via services.Configure<AcBinaryHubProtocolOptions>(...) in Program.cs, /// or pass an instance directly to the protocol constructor. /// public sealed class AcBinaryHubProtocolOptions { // --- Serializer (standalone sub-group, also usable without SignalR via ToBinary/BinaryTo) --- /// Binary serializer options. Default: . public AcBinarySerializerOptions SerializerOptions { get; set; } = AcBinarySerializerOptions.Default; // --- Transport --- /// Wire format and pipeline strategy. Default: . public BinaryProtocolMode ProtocolMode { get; set; } = BinaryProtocolMode.Bytes; /// Chunk size for BufferWriterBinaryOutput / AsyncPipeWriterOutput. /// Default: 4096 (aligns with Kestrel's slab size for optimal latency-to-first-byte). public int BufferSize { get; set; } = 4096; /// /// Per-chunk flush synchronization in mode. /// /// true (default): every Grow waits for the previous FlushAsync to complete /// before starting the next chunk. Guarantees end-to-end zero-copy (no owned-buffer fallback) /// and maximum pipeline parallelism. Best for high-throughput servers with fast consumers. /// false: fire-and-forget flush per chunk; blocks only when committed bytes exceed /// the memory threshold (~60 KB). Itself an adaptive backpressure mode — a fast consumer /// never triggers a wait, a slow consumer naturally throttles through buffer pressure. /// Safer for mixed consumer speeds and memory-sensitive environments. /// /// Ignored for Bytes and Segment modes. /// public bool WaitForFlush { get; set; } = false; /// /// Maximum wait for a single synchronous FlushAsync before throwing /// . Protects against slow/stuck/disconnected consumers /// blocking the server thread indefinitely. /// /// Default: 10 seconds. Rationale: AsyncSegment chunks are max 65 KB (UINT16 size field); /// even GPRS-class connections (~60 Kbit/s) transfer 65 KB in ~9 s. If a flush exceeds 10 s, /// the consumer is effectively stuck — faster failure detection is preferable to indefinite blocking. /// /// /// Note: complementary to SignalR's connection-level timeouts (ClientTimeoutInterval, /// KeepAliveInterval). This is a per-operation guard; SignalR timeouts manage the overall /// connection lifetime. Set FlushTimeout < ClientTimeoutInterval so this guard fires first. /// /// Set to to disable. /// public TimeSpan FlushTimeout { get; set; } = TimeSpan.FromSeconds(10); // --- Identity --- /// Protocol name in the SignalR handshake. Client and server must match. Default: "acbinary". public string Name { get; set; } = "acbinary"; // --- Diagnostics --- /// Optional logger. When null, no protocol-internal logging occurs. public ILogger? Logger { get; set; } // --- Validation --- /// /// Validates option values and platform compatibility. /// Throws / on invalid values, /// or for unsupported platform/mode combinations. /// public void Validate() { // WASM + AsyncSegment send-path guard — the AsyncPipeWriterOutput sync-over-async flush // would block the browser's single UI thread. The receive side converts chunked wire to // synchronous deser automatically, so WASM clients can still *receive* AsyncSegment data // from a non-WASM server — they just cannot *send* via AsyncSegment themselves. if (OperatingSystem.IsBrowser() && ProtocolMode == BinaryProtocolMode.AsyncSegment) throw new PlatformNotSupportedException( "BinaryProtocolMode.AsyncSegment is not supported on WebAssembly. " + "Use BinaryProtocolMode.Bytes or BinaryProtocolMode.Segment instead."); if (BufferSize < 256 || BufferSize > AsyncPipeWriterOutput.MaxChunkSize) throw new ArgumentOutOfRangeException(nameof(BufferSize), BufferSize, $"BufferSize must be between 256 and {AsyncPipeWriterOutput.MaxChunkSize} (UINT16 max)."); if (FlushTimeout <= TimeSpan.Zero && FlushTimeout != System.Threading.Timeout.InfiniteTimeSpan) throw new ArgumentOutOfRangeException(nameof(FlushTimeout), FlushTimeout, "FlushTimeout must be positive, or Timeout.InfiniteTimeSpan to disable."); if (string.IsNullOrWhiteSpace(Name)) throw new ArgumentException("Name cannot be null or whitespace.", nameof(Name)); } /// /// Creates a shallow copy. Required for DI IOptions<T> integration — /// the singleton from DI must not be mutated by per-connection configure callbacks. /// public AcBinaryHubProtocolOptions Clone() => new() { SerializerOptions = SerializerOptions, ProtocolMode = ProtocolMode, BufferSize = BufferSize, WaitForFlush = WaitForFlush, FlushTimeout = FlushTimeout, Name = Name, Logger = Logger }; }