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.
///
/// - : commit → flush → await each chunk before
/// acquiring the next. Strictly bounded peak memory (~chunk × 1). No producer/flush
/// parallelism. Best for memory-sensitive servers and unpredictable payload sizes.
/// - : fire-and-forget previous flush; next chunk
/// waits only if previous flush hasn't completed. Peak memory ~chunk × 2 with maximum
/// producer/flush parallelism — the recommended balanced choice for typical streaming.
/// - (default for SignalR): no per-chunk wait; the
/// underlying Pipe coalesces flushes adaptively up to its PauseWriterThreshold (~64 KB).
/// Highest throughput on bounded payloads — fast consumer never triggers a wait, slow
/// consumer naturally throttles through buffer pressure.
///
/// Ignored for Bytes and Segment modes.
///
public FlushPolicy FlushPolicy { get; set; } = FlushPolicy.Coalesced;
///
/// 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,
FlushPolicy = FlushPolicy,
FlushTimeout = FlushTimeout,
Name = Name,
Logger = Logger
};
}