using AyCode.Core.Extensions; using AyCode.Core.Interfaces; using System.Buffers; using System.Runtime.CompilerServices; using AyCode.Core.Serializers.Jsons; using JsonIgnoreAttribute = Newtonsoft.Json.JsonIgnoreAttribute; using STJIgnore = System.Text.Json.Serialization.JsonIgnoreAttribute; namespace AyCode.Services.SignalRs; /// /// Message container for serialized parameter IDs. /// Optimized for common primitive types to avoid full JSON overhead. /// public class IdMessage { public List Ids { get; private set; } public IdMessage() { Ids = []; } /// /// Creates IdMessage with multiple parameters serialized directly as JSON. /// public IdMessage(object[] ids) { Ids = new List(ids.Length); for (var i = 0; i < ids.Length; i++) { Ids.Add(SignalRSerializationHelper.SerializePrimitiveToJson(ids[i])); } } /// /// Creates IdMessage with a single parameter serialized as JSON. /// public IdMessage(object id) { Ids = [SignalRSerializationHelper.SerializePrimitiveToJson(id)]; } /// /// Creates IdMessage with multiple Guid parameters. /// public IdMessage(IEnumerable ids) { var idsArray = ids as Guid[] ?? ids.ToArray(); Ids = new List(idsArray.Length); for (var i = 0; i < idsArray.Length; i++) { Ids.Add(SignalRSerializationHelper.SerializeGuidToJson(idsArray[i])); } } public override string ToString() => string.Join("; ", Ids); } /// /// Message containing JSON-serialized post data. /// public class SignalPostJsonMessage { public string PostDataJson { get; set; } = ""; public SignalPostJsonMessage() { } protected SignalPostJsonMessage(string postDataJson) => PostDataJson = postDataJson; } /// /// Generic message containing JSON-serialized post data with typed access. /// public class SignalPostJsonDataMessage : SignalPostJsonMessage, ISignalPostMessage { [JsonIgnore] [STJIgnore] private TPostDataType? _postData; [JsonIgnore] [STJIgnore] public TPostDataType PostData { get => _postData ??= PostDataJson.JsonTo()!; private init { _postData = value; PostDataJson = _postData.ToJson(); } } public SignalPostJsonDataMessage() : base() { } public SignalPostJsonDataMessage(TPostDataType postData) => PostData = postData; public SignalPostJsonDataMessage(string postDataJson) : base(postDataJson) { } } /// /// Simple message containing post data. /// public class SignalPostMessage(TPostData postData) : ISignalPostMessage { public TPostData? PostData { get; set; } = postData; } public interface ISignalPostMessage : ISignalRMessage { TPostData? PostData { get; } } /// /// Message for requesting by Guid ID. /// public class SignalRequestByIdMessage(Guid id) : ISignalRequestMessage, IId { public Guid Id { get; set; } = id; } public interface ISignalRequestMessage : ISignalRMessage { TRequestId Id { get; set; } } public interface ISignalRMessage { } public interface ISignalResponseMessage : ISignalRMessage { int MessageTag { get; set; } SignalResponseStatus Status { get; set; } } public enum SignalResponseStatus : byte { Error = 0, Success = 5 } /// /// Unified signal response message that supports both JSON and Binary serialization. /// JSON mode uses Brotli compression for reduced payload size. /// Optimized: uses pooled buffers for decompression, zero-copy deserialization path. /// public sealed class SignalResponseDataMessage : ISignalResponseMessage, IDisposable { public int MessageTag { get; set; } public SignalResponseStatus Status { get; set; } public AcSerializerType DataSerializerType { get; set; } public byte[]? ResponseData { get; set; } [JsonIgnore] [STJIgnore] private object? _cachedResponseData; [JsonIgnore] [STJIgnore] private byte[]? _rentedDecompressedBuffer; [JsonIgnore] [STJIgnore] private int _decompressedLength; public SignalResponseDataMessage() { } public SignalResponseDataMessage(int messageTag, SignalResponseStatus status) { MessageTag = messageTag; Status = status; } public SignalResponseDataMessage(int messageTag, SignalResponseStatus status, object? responseData, AcSerializerOptions serializerOptions) : this(messageTag, status) { DataSerializerType = serializerOptions.SerializerType; ResponseData = SignalRSerializationHelper.CreateResponseData(responseData, serializerOptions); } /// /// Deserializes the ResponseData to the specified type. /// Uses cached result for repeated calls. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public T? GetResponseData() { if (_cachedResponseData != null) return (T)_cachedResponseData; if (ResponseData == null) return default; if (DataSerializerType == AcSerializerType.Binary) return (T)(_cachedResponseData = ResponseData.BinaryTo()!); // Decompress Brotli to pooled buffer and deserialize directly EnsureDecompressed(); var result = AcJsonDeserializer.Deserialize(new ReadOnlySpan(_rentedDecompressedBuffer, 0, _decompressedLength)); _cachedResponseData = result; return result; } /// /// Gets the decompressed JSON bytes as a ReadOnlySpan for direct processing. /// public ReadOnlySpan GetDecompressedJsonSpan() { if (ResponseData == null) return ReadOnlySpan.Empty; if (DataSerializerType == AcSerializerType.Binary) return ReadOnlySpan.Empty; EnsureDecompressed(); return _rentedDecompressedBuffer.AsSpan(0, _decompressedLength); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void EnsureDecompressed() { if (_rentedDecompressedBuffer != null) return; (_rentedDecompressedBuffer, _decompressedLength) = SignalRSerializationHelper.DecompressToRentedBuffer(ResponseData!); } public void Dispose() { } } public interface IAcSignalRHubClient : IAcSignalRHubBase { Task SendMessageToServerAsync(int messageTag, ISignalRMessage? message, int? requestId); }