Skip to content

Commit 9d76e90

Browse files
committed
feat: WIP request/response message handler (simple version, untested)
1 parent 307bd5b commit 9d76e90

8 files changed

Lines changed: 103 additions & 106 deletions

File tree

SimpleNetworkManager.NET/Messages/BaseRequestResponseMessageHandler.cs renamed to SimpleNetworkManager.NET/Messages/BaseRequestMessageHandler.cs

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
namespace Insthync.SimpleNetworkManager.NET.Messages
66
{
7-
public abstract class BaseRequestResponseMessageHandler<TRequest, TResponse> : BaseMessageHandler<TRequest>, IResponseMessageHandler
7+
public abstract class BaseRequestMessageHandler<TRequest, TResponse> : BaseMessageHandler<TRequest>
88
where TRequest : BaseRequestMessage
99
where TResponse : BaseResponseMessage
1010
{
@@ -26,14 +26,5 @@ protected override sealed async UniTask HandleAsync(BaseClientConnection clientC
2626
}
2727

2828
protected abstract UniTask<TResponse> HandleRequestAsync(BaseClientConnection clientConnection, TRequest request);
29-
30-
public UniTask HandleResponseDataAsync(BaseClientConnection clientConnection, object? data)
31-
{
32-
if (data == null)
33-
throw new ArgumentNullException(nameof(data));
34-
return HandleResponseAsync(clientConnection, (TResponse)data);
35-
}
36-
37-
protected abstract UniTask HandleResponseAsync(BaseClientConnection clientConnection, TResponse request);
3829
}
3930
}

SimpleNetworkManager.NET/Messages/IResponseMessageHandler.cs

Lines changed: 0 additions & 11 deletions
This file was deleted.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using Cysharp.Threading.Tasks;
2+
using Insthync.SimpleNetworkManager.NET.Network;
3+
using System;
4+
5+
namespace Insthync.SimpleNetworkManager.NET.Messages
6+
{
7+
public class ResponseMessageHandler<T> : BaseMessageHandler<T>
8+
where T : BaseResponseMessage
9+
{
10+
protected override UniTask HandleAsync(BaseClientConnection clientConnection, T data)
11+
{
12+
clientConnection.Responded(data);
13+
return default;
14+
}
15+
}
16+
}

SimpleNetworkManager.NET/Network/BaseClientConnection.cs

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Collections.Concurrent;
77
using System.Net.Sockets;
88
using System.Threading;
9+
using System.Threading.Tasks;
910

1011
namespace Insthync.SimpleNetworkManager.NET.Network
1112
{
@@ -15,6 +16,7 @@ public abstract class BaseClientConnection : IDisposable
1516
private static ConcurrentQueue<uint> s_unassignedConnectionIds = new ConcurrentQueue<uint>();
1617

1718
protected readonly ILogger<BaseClientConnection> _logger;
19+
protected readonly ConcurrentDictionary<Guid, BaseResponseMessage> _pendingResponses = new ConcurrentDictionary<Guid, BaseResponseMessage>();
1820
protected bool _disposed;
1921

2022
public uint ConnectionId { get; protected set; }
@@ -64,12 +66,55 @@ public void OnDisconnected()
6466
/// <summary>
6567
/// Sends a message asynchronously to the connected client
6668
/// </summary>
67-
public abstract UniTask SendMessageAsync(BaseMessage message);
69+
internal abstract UniTask SendMessageAsync(BaseMessage message);
6870

6971
/// <summary>
7072
/// Disconnects the client gracefully
7173
/// </summary>
72-
public abstract UniTask DisconnectAsync();
74+
internal abstract UniTask DisconnectAsync();
75+
76+
internal async UniTask<TResponse> SendRequestAsync<TResponse>(BaseRequestMessage request)
77+
where TResponse : BaseResponseMessage
78+
{
79+
Guid requestId = Guid.NewGuid();
80+
request.RequestId = requestId;
81+
await SendMessageAsync(request);
82+
83+
// Waiting for the response
84+
BaseResponseMessage? response;
85+
// 10 seconds timeout
86+
int timeoutCountDown = 10_000;
87+
do
88+
{
89+
if (timeoutCountDown <= 0)
90+
{
91+
response = null;
92+
break;
93+
}
94+
await Task.Delay(100);
95+
timeoutCountDown -= 100;
96+
}
97+
while (!_pendingResponses.TryRemove(requestId, out response));
98+
99+
if (response == null)
100+
{
101+
throw new TimeoutException($"Request timed out after 10 seconds (RequestId: {requestId}).");
102+
}
103+
104+
if (response is not TResponse castedResponse)
105+
{
106+
throw new InvalidOperationException($"Response type mismatch. Expected {typeof(TResponse).Name}, got {response.GetType().Name}");
107+
}
108+
109+
return castedResponse;
110+
}
111+
112+
internal void Responded(BaseResponseMessage? response)
113+
{
114+
if (response == null)
115+
return;
116+
_pendingResponses.TryAdd(response.RequestId, response);
117+
}
73118

74119
/// <summary>
75120
/// Sends a serialization error message to the client

SimpleNetworkManager.NET/Network/BaseNetworkClient.cs

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,26 @@ public BaseNetworkClient(ILoggerFactory loggerFactory)
2626

2727
public async UniTask SendMessageAsync(BaseMessage message)
2828
{
29-
if (ClientConnection == null)
30-
return;
29+
if (ClientConnection == null || !ClientConnection.IsConnected)
30+
throw new InvalidOperationException("Cannot send message: client is not connected.");
3131
await ClientConnection.SendMessageAsync(message);
3232
}
3333

3434
public async UniTask DisconnectAsync()
3535
{
3636
if (ClientConnection == null || !ClientConnection.IsConnected)
37-
{
38-
_logger.LogWarning("Client is not connecting");
39-
return;
40-
}
37+
throw new InvalidOperationException("Cannot disconnect: client is not connected.");
4138
await ClientConnection.DisconnectAsync();
42-
ClientConnection?.Dispose();
39+
ClientConnection.Dispose();
40+
}
41+
42+
public async UniTask<TResponse?> SendRequestAsync<TResponse>(BaseRequestMessage request)
43+
where TResponse : BaseResponseMessage
44+
{
45+
if (ClientConnection == null || !ClientConnection.IsConnected)
46+
throw new InvalidOperationException("Cannot send request: client is not connected.");
47+
_messageRouterService.RegisterHandler(new ResponseMessageHandler<TResponse>(), true);
48+
return await ClientConnection.SendRequestAsync<TResponse>(request);
4349
}
4450

4551
public void RegisterHandler<T>(BaseMessageHandler<T> handler)
@@ -48,7 +54,7 @@ public void RegisterHandler<T>(BaseMessageHandler<T> handler)
4854
_messageRouterService.RegisterHandler(handler);
4955
}
5056

51-
public void RegisterHandler<TRequest, TResponse>(BaseRequestResponseMessageHandler<TRequest, TResponse> handler)
57+
public void RegisterHandler<TRequest, TResponse>(BaseRequestMessageHandler<TRequest, TResponse> handler)
5258
where TRequest : BaseRequestMessage
5359
where TResponse : BaseResponseMessage
5460
{

SimpleNetworkManager.NET/Network/BaseNetworkServer.cs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Insthync.SimpleNetworkManager.NET.Services;
44
using Microsoft.Extensions.Logging;
55
using System;
6+
using System.Collections.Generic;
67
using System.Threading;
78

89
namespace Insthync.SimpleNetworkManager.NET.Network
@@ -34,28 +35,39 @@ public BaseNetworkServer(ILoggerFactory loggerFactory)
3435
public async UniTask SendMessageAsync(uint connectionId, BaseMessage message)
3536
{
3637
if (!_connectionManager.TryGetConnection(connectionId, out var clientConnection))
37-
return;
38+
throw new KeyNotFoundException($"No connection found with ID {connectionId}.");
3839
if (clientConnection == null || !clientConnection.IsConnected)
39-
return;
40+
throw new InvalidOperationException($"Cannot send message: client {connectionId} is not connected.");
4041
await clientConnection.SendMessageAsync(message);
4142
}
4243

4344
public async UniTask DisconnectAsync(uint connectionId)
4445
{
4546
if (!_connectionManager.TryGetConnection(connectionId, out var clientConnection))
46-
return;
47+
throw new KeyNotFoundException($"No connection found with ID {connectionId}.");
4748
if (clientConnection == null || !clientConnection.IsConnected)
48-
return;
49+
throw new InvalidOperationException($"Cannot disconnect: client {connectionId} is not connected.");
4950
await clientConnection.DisconnectAsync();
5051
}
5152

53+
public async UniTask<TResponse?> SendRequestAsync<TResponse>(uint connectionId, BaseRequestMessage request)
54+
where TResponse : BaseResponseMessage
55+
{
56+
if (!_connectionManager.TryGetConnection(connectionId, out var clientConnection))
57+
throw new KeyNotFoundException($"No connection found with ID {connectionId}.");
58+
if (clientConnection == null || !clientConnection.IsConnected)
59+
throw new InvalidOperationException($"Cannot send request: client {connectionId} is not connected.");
60+
_messageRouterService.RegisterHandler(new ResponseMessageHandler<TResponse>(), true);
61+
return await clientConnection.SendRequestAsync<TResponse>(request);
62+
}
63+
5264
public void RegisterHandler<T>(BaseMessageHandler<T> handler)
5365
where T : BaseMessage
5466
{
5567
_messageRouterService.RegisterHandler(handler);
5668
}
5769

58-
public void RegisterHandler<TRequest, TResponse>(BaseRequestResponseMessageHandler<TRequest, TResponse> handler)
70+
public void RegisterHandler<TRequest, TResponse>(BaseRequestMessageHandler<TRequest, TResponse> handler)
5971
where TRequest : BaseRequestMessage
6072
where TResponse : BaseResponseMessage
6173
{

SimpleNetworkManager.NET/Network/TcpTransport/TcpClientConnection.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ await SendErrorMessageAsync(MessageTypes.Error,
216216
}
217217
}
218218

219-
public override async UniTask SendMessageAsync(BaseMessage message)
219+
internal override async UniTask SendMessageAsync(BaseMessage message)
220220
{
221221
if (_disposed || !_isConnected || _networkStream == null)
222222
{
@@ -309,7 +309,7 @@ public override async UniTask SendMessageAsync(BaseMessage message)
309309
}
310310
}
311311

312-
public override async UniTask DisconnectAsync()
312+
internal override async UniTask DisconnectAsync()
313313
{
314314
if (_disposed || !_isConnected)
315315
return;

SimpleNetworkManager.NET/Services/MessageRouterService.cs

Lines changed: 6 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,11 @@ public class MessageRouterService
1616
{
1717
private readonly ILogger<MessageRouterService> _logger;
1818
private readonly ConcurrentDictionary<uint, IMessageHandler> _handlers;
19-
private readonly ConcurrentDictionary<uint, IMessageHandler> _requestHandlers;
20-
private readonly ConcurrentDictionary<uint, IResponseMessageHandler> _responseHandlers;
2119

2220
public MessageRouterService(ILogger<MessageRouterService> logger)
2321
{
2422
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
2523
_handlers = new ConcurrentDictionary<uint, IMessageHandler>();
26-
_requestHandlers = new ConcurrentDictionary<uint, IMessageHandler>();
27-
_responseHandlers = new ConcurrentDictionary<uint, IResponseMessageHandler>();
2824
}
2925

3026
/// <summary>
@@ -33,7 +29,7 @@ public MessageRouterService(ILogger<MessageRouterService> logger)
3329
/// <typeparam name="T">Type of message the handler processes</typeparam>
3430
/// <param name="handler">Handler instance to register</param>
3531
/// <exception cref="ArgumentNullException">Thrown when handler is null</exception>
36-
public void RegisterHandler<T>(BaseMessageHandler<T> handler)
32+
public void RegisterHandler<T>(BaseMessageHandler<T> handler, bool dismissWarning = false)
3733
where T : BaseMessage
3834
{
3935
if (handler == null)
@@ -50,59 +46,15 @@ public void RegisterHandler<T>(BaseMessageHandler<T> handler)
5046
}
5147
else
5248
{
53-
_logger.LogWarning("Handler for message type {MessageType} ({TypeName}) already registered, replacing",
54-
messageType, typeof(T).Name);
49+
if (!dismissWarning)
50+
{
51+
_logger.LogWarning("Handler for message type {MessageType} ({TypeName}) already registered, replacing",
52+
messageType, typeof(T).Name);
53+
}
5554
_handlers[messageType] = handler;
5655
}
5756
}
5857

59-
/// <summary>
60-
/// Registers a request message handler for a specific message type
61-
/// </summary>
62-
/// <typeparam name="TRequest">Type of request message the handler processes</typeparam>
63-
/// <typeparam name="TResponse">Type of response message the handler processes</typeparam>
64-
/// <param name="handler">Handler instance to register</param>
65-
/// <exception cref="ArgumentNullException">Thrown when handler is null</exception>
66-
public void RegisterHandler<TRequest, TResponse>(BaseRequestResponseMessageHandler<TRequest, TResponse> handler)
67-
where TRequest : BaseRequestMessage
68-
where TResponse : BaseResponseMessage
69-
{
70-
if (handler == null)
71-
throw new ArgumentNullException(nameof(handler));
72-
73-
// Get the request message type from the generic type parameter
74-
var requestInstance = handler.GetMessageInstance();
75-
var requestType = requestInstance.GetMessageType();
76-
77-
if (_requestHandlers.TryAdd(requestType, handler))
78-
{
79-
_logger.LogDebug("Registered handler for message type {MessageType} ({TypeName})",
80-
requestType, typeof(TRequest).Name);
81-
}
82-
else
83-
{
84-
_logger.LogWarning("Handler for message type {MessageType} ({TypeName}) already registered, replacing",
85-
requestType, typeof(TRequest).Name);
86-
_requestHandlers[requestType] = handler;
87-
}
88-
89-
// Get the response message type from the generic type parameter
90-
var responseInstance = handler.GetResponseMessageInstance();
91-
var responseType = responseInstance.GetMessageType();
92-
93-
if (_responseHandlers.TryAdd(responseType, handler))
94-
{
95-
_logger.LogDebug("Registered handler for message type {MessageType} ({TypeName})",
96-
responseType, typeof(TResponse).Name);
97-
}
98-
else
99-
{
100-
_logger.LogWarning("Handler for message type {MessageType} ({TypeName}) already registered, replacing",
101-
responseType, typeof(TResponse).Name);
102-
_responseHandlers[responseType] = handler;
103-
}
104-
}
105-
10658
/// <summary>
10759
/// Routes a message to the appropriate handler
10860
/// </summary>
@@ -124,20 +76,6 @@ public async UniTask RouteMessageAsync(BaseClientConnection clientConnection, by
12476
var dataType = messageInstance.GetType();
12577
await handler.HandleDataAsync(clientConnection, MessagePackSerializer.Deserialize(messageInstance.GetType(), data, messageInstance.GetMessagePackOptions()));
12678
}
127-
128-
if (_requestHandlers.TryGetValue(messageType, out var requestHandler))
129-
{
130-
var messageInstance = requestHandler.GetMessageInstance();
131-
var dataType = messageInstance.GetType();
132-
await requestHandler.HandleDataAsync(clientConnection, MessagePackSerializer.Deserialize(messageInstance.GetType(), data, messageInstance.GetMessagePackOptions()));
133-
}
134-
135-
if (_responseHandlers.TryGetValue(messageType, out var responseHandler))
136-
{
137-
var messageInstance = responseHandler.GetMessageInstance();
138-
var dataType = messageInstance.GetType();
139-
await responseHandler.HandleResponseDataAsync(clientConnection, MessagePackSerializer.Deserialize(messageInstance.GetType(), data, messageInstance.GetMessagePackOptions()));
140-
}
14179
}
14280
}
14381
}

0 commit comments

Comments
 (0)