AyCode.Core/AyCode.Services/SignalRs/ISignalParams.cs

114 lines
5.0 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
/// <summary>
/// Base interface for SignalR message parameters (metadata).
/// </summary>
public interface ISignalParams { }
/// <summary>
/// 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[].
/// </summary>
[AcBinarySerializable]
public class SignalParams : ISignalParams
{
public SignalResponseStatus Status { get; set; }
public AcSerializerType DataSerializerType { get; set; }
/// <summary>
/// Wire format: serialized byte[][] as a single byte[].
/// AcBinary handles this as byte[] fast-path (zero-copy potential).
/// Use SetParameterValues/GetParameterValues for typed access.
/// </summary>
public byte[]? Parameters { get; set; }
/// <summary>
/// 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.
/// </summary>
public bool IsRawBytesData { get; set; }
/// <summary>
/// Cached deserialized byte[][] from Parameters.
/// </summary>
private byte[][]? _parameterValues;
/// <summary>
/// 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.
/// </summary>
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<T>() 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();
}
/// <summary>
/// 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.
/// </summary>
public object[]? GetParameterValues(ParameterInfo[] paramInfos)
{
if (paramInfos is not { Length: > 0 })
return null;
// Deserialize byte[] → byte[][] (cached)
_parameterValues ??= Parameters is { Length: > 0 } ? Parameters.BinaryTo<byte[][]>() : 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;
}
}