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; } = true;
///
/// 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
};
}