using System.Reflection; using AyCode.Core.Extensions; using AyCode.Core.Serializers; using AyCode.Core.Serializers.Attributes; using AyCode.Core.Serializers.Binaries; namespace AyCode.Services.SignalRs; /// /// Base interface for SignalR message parameters (metadata). /// public interface ISignalParams { } /// /// SignalR message metadata + optional method parameters. /// Travels as a separate hub argument (AcBinary serialized). /// Parameters and data are independent — both can be null or filled in any direction. /// /// Parameter serialization follows the PostDataJson pattern: /// - Wire format: Parameters (byte[]) — AcBinary fast-path /// - Client: SetParameterValues(object[]) packs object[] → byte[][] → byte[] /// - Server: GetParameterValues(ParameterInfo[]) unpacks byte[] → byte[][] → object[] /// The protocol never sees byte[][] — only byte[]. /// [AcBinarySerializable] public class SignalParams : ISignalParams { public SignalResponseStatus Status { get; set; } public AcSerializerType DataSerializerType { get; set; } /// /// Wire format: serialized byte[][] as a single byte[]. /// AcBinary handles this as byte[] fast-path (zero-copy potential). /// Use SetParameterValues/GetParameterValues for typed access. /// public byte[]? Parameters { get; set; } /// /// Client sets true when requesting raw byte[] (e.g. DataSource populate/merge). /// Server: reads this from client's SignalParams → serializes object → byte[] directly. /// Protocol: reads this from response SignalParams → skips deserialization, returns raw byte[]. /// Result: single serialize (server) → single deserialize (consumer). No double ser/deser. /// public bool IsRawBytesData { get; set; } /// /// Cached deserialized byte[][] from Parameters. /// private byte[][]? _parameterValues; /// /// Client-side: packs object[] into Parameters (byte[]). /// Each parameter is individually binary-serialized via ToBinary(). /// /// PERF: N× ToBinary() = N× context pool acquire/release + N× ArrayBinaryOutput allocation. /// For many small primitives (int, bool) this overhead may exceed a single bulk serialization call. /// Consider a batch fast-path (single context, write all params) if benchmarks confirm regression. /// public void SetParameterValues(object[] parameters) { // N× pool roundtrip — see PERF note in summary var paramBytes = new byte[parameters.Length][]; for (var i = 0; i < parameters.Length; i++) { // Pass explicit runtime type — parameters[i] is statically object, so the generic // ToBinary() overload would infer T = object and emit an object-typed wire payload // (instead of the concrete int/string/DTO type). Explicit null-handling avoids the // null-conditional + fallback indirection and produces the Null marker directly. var p = parameters[i]; paramBytes[i] = p == null ? [BinaryTypeCode.Null] : p.ToBinary(p.GetType()); } _parameterValues = paramBytes; Parameters = paramBytes.ToBinary(); } /// /// Server-side: unpacks Parameters (byte[]) into typed object[]. /// Each parameter is deserialized with BinaryTo(targetType) from method signature. /// Fills trailing optional parameters with defaults, throws for missing required ones. /// Caches the intermediate byte[][] for repeated access. /// /// NOTE: Currently AcBinary only. JSON parameter deserialization not supported /// (would require dispatching on serializer type + AcJsonSerializer reference). /// /// PERF: Non-generic BinaryTo(Type) uses ThreadLocal + ConcurrentDictionary type cache per call. /// N parameters = N× pool roundtrip + N× type-dispatch lookup. /// For many small primitives this may be slower than a single bulk deserialization. /// Consider a batch fast-path (single context, read all params) if benchmarks confirm regression. /// public object[]? GetParameterValues(ParameterInfo[] paramInfos) { if (paramInfos is not { Length: > 0 }) return null; // Deserialize byte[] → byte[][] (cached) _parameterValues ??= Parameters is { Length: > 0 } ? Parameters.BinaryTo() : null; if (_parameterValues is null or { Length: 0 }) return null; // N× pool roundtrip + type-dispatch — see PERF note in summary var result = new object?[paramInfos.Length]; for (var i = 0; i < _parameterValues.Length && i < paramInfos.Length; i++) result[i] = _parameterValues[i].BinaryTo(paramInfos[i].ParameterType); // Fill trailing optional parameters with defaults for (var i = _parameterValues.Length; i < paramInfos.Length; i++) result[i] = paramInfos[i].HasDefaultValue ? paramInfos[i].DefaultValue : null; return result; } }