r-type  0.0.0
R-Type main
Loading...
Searching...
No Matches
asioServer.cpp
Go to the documentation of this file.
1///
2/// @file AsioServer.cpp
3/// @brief Asio-based implementation of INetworkServer interface
4/// @namespace srv
5///
6
9#include "Utils/Event.hpp"
10#include "Utils/EventBus.hpp"
11#include "Utils/Logger.hpp"
12
13#include <algorithm>
14#include <chrono>
15#include <random>
16#include <ranges>
17
18namespace srv
19{
20
22 : m_ioContext(std::make_unique<asio::io_context>()),
23 m_socket(std::make_unique<asio::ip::udp::socket>(*m_ioContext)), m_port(0), m_tickRate(60), m_serverCaps(0),
24 m_running(false), m_started(false), m_nextSessionId(1), m_nextLobbyId(1),
25 m_packetHandler(std::make_unique<rnp::HandlerPacket>()), m_pingInterval(std::chrono::seconds(5)),
26 m_clientTimeout(std::chrono::seconds(30)), m_componentId(utl::NETWORK_SERVER),
27 m_eventBus(utl::EventBus::getInstance())
28 {
29 // utl::Logger::log("AsioServer: Constructor called", utl::LogLevel::INFO);
30 // utl::Logger::log("AsioServer: Creating I/O context and socket", utl::LogLevel::INFO);
32 // utl::Logger::log("AsioServer: Packet handlers setup complete", utl::LogLevel::INFO);
33
34 // EventBus integration
40
41 // utl::Logger::log("AsioServer: Initialized with EventBus integration", utl::LogLevel::INFO);
42 }
43
45 {
46 // utl::Logger::log("AsioServer: Destructor called", utl::LogLevel::INFO);
48 // utl::Logger::log("AsioServer: Destroyed", utl::LogLevel::INFO);
49 }
50
51 void AsioServer::init(const std::string &host, std::uint16_t port)
52 {
53 // utl::Logger::log("AsioServer: init() called with host=" + host + ", port=" + std::to_string(port),
54 // utl::LogLevel::INFO);
55 m_host = host;
56 m_port = port;
57 // utl::Logger::log("AsioServer: Configuration stored - ready to start", utl::LogLevel::INFO);
58 }
59
61 {
62 // utl::Logger::log("AsioServer: Starting server...", utl::LogLevel::INFO);
63
64 if (m_started.load())
65 {
66 // utl::Logger::log("AsioServer: Already started", utl::LogLevel::WARNING);
67 return;
68 }
69
70 try
71 {
72 // utl::Logger::log("AsioServer: Creating endpoint for " + m_host + ":" + std::to_string(m_port),
73 // utl::LogLevel::INFO);
74
75 // Create endpoint
76 asio::ip::udp::endpoint endpoint(asio::ip::make_address(m_host), m_port);
77 // utl::Logger::log("AsioServer: Endpoint created successfully", utl::LogLevel::INFO);
78
79 // Bind socket
80 // utl::Logger::log("AsioServer: Opening socket...", utl::LogLevel::INFO);
81 m_socket->open(endpoint.protocol());
82 // utl::Logger::log("AsioServer: Setting socket options...", utl::LogLevel::INFO);
83 m_socket->set_option(asio::ip::udp::socket::reuse_address(true));
84 // utl::Logger::log("AsioServer: Binding socket to endpoint...", utl::LogLevel::INFO);
85 m_socket->bind(endpoint);
86 // utl::Logger::log("AsioServer: Socket bound successfully", utl::LogLevel::INFO);
87
88 m_running.store(true);
89 m_started.store(true);
90
91 // Start network thread
92 // utl::Logger::log("AsioServer: Starting network thread...", utl::LogLevel::INFO);
93 m_networkThread = std::make_unique<std::thread>(&AsioServer::networkThreadLoop, this);
94
95 // Start receiving
96 // utl::Logger::log("AsioServer: Starting packet reception...", utl::LogLevel::INFO);
98
99 // utl::Logger::log("AsioServer: Started on " + m_host + ":" + std::to_string(m_port), utl::LogLevel::INFO);
100 }
101 catch (const std::exception &e)
102 {
103 m_running.store(false);
104 m_started.store(false);
105 // utl::Logger::log("AsioServer: Failed to start - " + std::string(e.what()), utl::LogLevel::WARNING);
106 throw;
107 }
108 }
109
111 {
112 if (!m_started.load())
113 {
114 return;
115 }
116
117 m_running.store(false);
118 m_started.store(false);
119
120 // Send disconnect to all clients
121 {
122 std::lock_guard<std::mutex> lock(m_clientsMutex);
123 for (auto &[sessionId, client] : m_clients)
124 {
125 if (client.isConnected)
126 {
127 rnp::Serializer serializer;
128 rnp::PacketHeader header{};
129 header.type = static_cast<std::uint8_t>(rnp::PacketType::DISCONNECT);
130 header.length = sizeof(rnp::PacketDisconnect);
131 header.sessionId = sessionId;
132
133 rnp::PacketDisconnect disconnect{};
134 disconnect.reasonCode = static_cast<std::uint16_t>(rnp::DisconnectReason::SERVER_SHUTDOWN);
135
136 serializer.serializeHeader(header);
137 serializer.serializeDisconnect(disconnect);
138
139 sendPacketImmediate(serializer.getData(), client.endpoint);
140 }
141 }
142 }
143
144 // Stop io_context safely
145 if (m_ioContext)
146 {
147 m_ioContext->stop();
148 }
149
150 // Wait for network thread with timeout
151 if (m_networkThread && m_networkThread->joinable())
152 {
153 auto joinStart = std::chrono::steady_clock::now();
154 bool joined = false;
155
156 while (
157 !joined &&
158 std::chrono::duration_cast<std::chrono::seconds>(std::chrono::steady_clock::now() - joinStart).count() <
159 5)
160 {
161 if (m_networkThread->joinable())
162 {
163 m_networkThread->join();
164 joined = true;
165 }
166 else
167 {
168 std::this_thread::sleep_for(std::chrono::milliseconds(10));
169 }
170 }
171
172 if (!joined)
173 {
174 // utl::Logger::log("AsioServer: Network thread join timeout, detaching", utl::LogLevel::WARNING);
175 m_networkThread->detach();
176 }
177 }
178
179 // Close socket safely
180 {
181 std::lock_guard<std::mutex> socketLock(m_socketMutex);
182 if (m_socket && m_socket->is_open())
183 {
184 try
185 {
186 m_socket->close();
187 }
188 catch (const std::exception &e)
189 {
190 // utl::Logger::log("AsioServer: Exception closing socket - " + std::string(e.what()),
191 // utl::LogLevel::WARNING);
192 }
193 }
194 }
195
196 // Clear clients
197 {
198 std::lock_guard<std::mutex> lock(m_clientsMutex);
199 m_clients.clear();
200 m_endpointToSession.clear();
201 }
202
203 // utl::Logger::log("AsioServer: Stopped", utl::LogLevel::INFO);
204 }
205
207 {
209
210 if (!m_running.load())
211 {
212 return;
213 }
214
215 // Process send queue
217
218 // Update client management (timeouts, pings)
220 }
221
222 void AsioServer::sendToClient(std::uint32_t sessionId, const std::vector<std::uint8_t> &data, bool reliable)
223 {
224 std::lock_guard<std::mutex> clientLock(m_clientsMutex);
225 auto it = m_clients.find(sessionId);
226 if (it == m_clients.end() || !it->second.isConnected)
227 {
228 // utl::Logger::log("AsioServer: Attempted to send to invalid session " + std::to_string(sessionId),
229 // utl::LogLevel::WARNING);
230 return;
231 }
232
233 std::lock_guard<std::mutex> queueLock(m_sendQueueMutex);
234 m_sendQueue.emplace(data, it->second.endpoint, reliable);
235 }
236
237 void AsioServer::sendToAllClients(const std::vector<std::uint8_t> &data, bool reliable)
238 {
239 std::lock_guard<std::mutex> clientLock(m_clientsMutex);
240 std::lock_guard<std::mutex> queueLock(m_sendQueueMutex);
241
242 for (const auto &client : m_clients | std::views::values)
243 {
244 if (client.isConnected)
245 {
246 m_sendQueue.emplace(data, client.endpoint, reliable);
247 }
248 }
249 }
250
251 void AsioServer::disconnectClient(std::uint32_t sessionId)
252 {
253 std::uint32_t lobbyId = 0;
254
255 // Scope for clientsLock to release it before broadcastLobbyUpdate
256 {
257 std::lock_guard<std::mutex> lock(m_clientsMutex);
258 auto it = m_clients.find(sessionId);
259 if (it == m_clients.end())
260 {
261 return;
262 }
263
264 utl::Logger::log("AsioServer: Disconnecting client " + std::to_string(sessionId), utl::LogLevel::INFO);
265
266 rnp::PacketHeader header;
267 header.type = static_cast<std::uint8_t>(rnp::PacketType::DISCONNECT);
268 header.length = static_cast<std::uint16_t>(sizeof(rnp::PacketDisconnect));
269 header.sessionId = sessionId;
270
271 std::vector<std::uint8_t> packet;
272 rnp::Serializer serializer(packet);
273 rnp::PacketDisconnect disconnect{};
274 disconnect.reasonCode = static_cast<std::uint16_t>(rnp::DisconnectReason::CLIENT_REQUEST);
275
276 serializer.serializeHeader(header);
277 serializer.serializeDisconnect(disconnect);
278
279 sendPacketImmediate(serializer.getData(), it->second.endpoint);
280
281 // Remove from lobby if in one
282 if (it->second.currentLobbyId != 0)
283 {
284 lobbyId = it->second.currentLobbyId;
285 }
286
287 // Remove from endpoint mapping
288 const std::string endpointStr = endpointToString(it->second.endpoint);
289 m_endpointToSession.erase(endpointStr);
290
291 // Remove client
292 m_clients.erase(it);
293
294 m_packetHandler->clearSession(sessionId);
295
296 // utl::Logger::log("AsioServer: Disconnected client " + std::to_string(sessionId), utl::LogLevel::INFO);
297 } // clientsLock released here
298
299 // Leave lobby AFTER releasing clientsLock to avoid nested mutex acquisitions
300 if (lobbyId != 0)
301 {
302 leaveLobby(sessionId);
303 // Broadcast lobby update to remaining players
304 broadcastLobbyUpdate(lobbyId);
305 }
306 }
307
308 std::size_t AsioServer::getClientCount() const
309 {
310 std::lock_guard clientLock(m_clientsMutex);
311 return std::ranges::count_if(m_clients, [](const auto &pair) { return pair.second.isConnected; });
312 }
313
314 std::vector<std::uint32_t> AsioServer::getConnectedSessions() const
315 {
316 std::lock_guard clientLock(m_clientsMutex);
317 std::vector<std::uint32_t> sessions;
318 for (const auto &[sessionId, client] : m_clients)
319 {
320 if (client.isConnected)
321 {
322 sessions.push_back(sessionId);
323 }
324 }
325 return sessions;
326 }
327
328 bool AsioServer::isRunning() const { return m_running.load(); }
329
330 void AsioServer::setTickRate(std::uint16_t tickRate)
331 {
332 m_tickRate = tickRate;
333 // utl::Logger::log("AsioServer: Tick rate set to " + std::to_string(tickRate), utl::LogLevel::INFO);
334 }
335
336 void AsioServer::setServerCapabilities(std::uint32_t caps)
337 {
338 m_serverCaps = caps;
339 // utl::Logger::log("AsioServer: Server capabilities set to " + std::to_string(caps), utl::LogLevel::INFO);
340 }
341
343 {
344 // CONNECT handler
345 m_packetHandler->onConnect([this](const rnp::PacketConnect &packet, const rnp::PacketContext &context)
346 { return handleConnect(packet, context); });
347
348 // DISCONNECT handler
349 m_packetHandler->onDisconnect([this](const rnp::PacketDisconnect &packet, const rnp::PacketContext &context)
350 { return handleDisconnect(packet, context); });
351
352 // PING handler
353 m_packetHandler->onPing([this](const rnp::PacketPingPong &packet, const rnp::PacketContext &context)
354 { return handlePing(packet, context); });
355
356 // PONG handler
357 m_packetHandler->onPong([this](const rnp::PacketPingPong &packet, const rnp::PacketContext &context)
358 { return handlePong(packet, context); });
359
360 // ENTITY_EVENT handler (for player inputs)
362 [this](const std::vector<rnp::EventRecord> &events, const rnp::PacketContext &context)
363 { return handleEntityEvent(events, context); });
364
365 // LOBBY_LIST_REQUEST handler
367 { return handleLobbyListRequest(context); });
368
369 // LOBBY_CREATE handler
370 m_packetHandler->onLobbyCreate([this](const rnp::PacketLobbyCreate &packet, const rnp::PacketContext &context)
371 { return handleLobbyCreate(packet, context); });
372
373 // LOBBY_JOIN handler
374 m_packetHandler->onLobbyJoin([this](const rnp::PacketLobbyJoin &packet, const rnp::PacketContext &context)
375 { return handleLobbyJoin(packet, context); });
376
377 // LOBBY_LEAVE handler
378 m_packetHandler->onLobbyLeave([this](const rnp::PacketContext &context) { return handleLobbyLeave(context); });
379
380 // START_GAME_REQUEST handler
382 [this](const rnp::PacketStartGameRequest &packet, const rnp::PacketContext &context)
383 { return handleStartGameRequest(packet, context); });
384
385 // utl::Logger::log("AsioServer: Packet handlers initialized", utl::LogLevel::INFO);
386 }
387
389 {
390 // Check if we should continue receiving
391 if (!m_running.load() || !m_socket || !m_socket->is_open())
392 {
393 utl::Logger::log("AsioServer: Cannot start receive - server not running or socket closed",
395 return;
396 }
397
398 m_socket->async_receive_from(
399 asio::buffer(m_recvBuffer), m_senderEndpoint,
400 [this](const std::error_code ec, const std::size_t bytesReceived)
401 {
402 if (!ec && m_running.load())
403 {
404 utl::Logger::log("AsioServer: Received " + std::to_string(bytesReceived) + " bytes from " +
405 m_senderEndpoint.address().to_string() + ":" +
406 std::to_string(m_senderEndpoint.port()),
407 utl::LogLevel::INFO);
408 handleReceive(bytesReceived);
409 startReceive(); // Continue receiving
410 }
411 else if (m_running.load() && ec != asio::error::operation_aborted)
412 {
413 utl::Logger::log("AsioServer: Receive error - " + ec.message(), utl::LogLevel::WARNING);
414 std::this_thread::sleep_for(std::chrono::milliseconds(100));
415 startReceive(); // Try to continue
416 }
417 else
418 {
419 utl::Logger::log("AsioServer: Server stopped or operation aborted, ending receive loop",
420 utl::LogLevel::INFO);
421 }
422 });
423 }
424
425 void AsioServer::handleReceive(std::size_t bytesReceived)
426 {
427 utl::Logger::log("AsioServer: Processing received packet of " + std::to_string(bytesReceived) + " bytes",
429
430 // PacketHeader serialized size: 1 (type) + 2 (length) + 4 (sessionId) = 7 bytes
431 constexpr std::size_t PACKET_HEADER_SIZE = 7;
432 if (bytesReceived < PACKET_HEADER_SIZE)
433 {
434 utl::Logger::log("AsioServer: Received packet too small (need " + std::to_string(PACKET_HEADER_SIZE) +
435 " bytes, got " + std::to_string(bytesReceived) + ")",
437 return;
438 }
439
440 // Create packet context
441 rnp::PacketContext context;
442 context.receiveTime = std::chrono::steady_clock::now();
443 context.senderAddress = m_senderEndpoint.address().to_string();
444 context.senderPort = m_senderEndpoint.port();
445
446 utl::Logger::log("AsioServer: Packet from " + context.senderAddress + ":" + std::to_string(context.senderPort),
448
449 // Extract session ID from header for context
450 std::vector<std::uint8_t> data(m_recvBuffer.begin(), m_recvBuffer.begin() + bytesReceived);
451 if (data.size() >= PACKET_HEADER_SIZE)
452 {
453 rnp::Serializer headerSerializer(data);
454 rnp::PacketHeader header = headerSerializer.deserializeHeader();
455 context.sessionId = header.sessionId;
456 utl::Logger::log("AsioServer: Packet type: " + std::to_string(static_cast<int>(header.type)) +
457 ", Session ID: " + std::to_string(header.sessionId),
459 }
460
461 // Process packet
462 utl::Logger::log("AsioServer: Processing packet...", utl::LogLevel::INFO);
463 rnp::HandlerResult result = m_packetHandler->processPacket(data, context);
464 if (result != rnp::HandlerResult::SUCCESS)
465 {
466 utl::Logger::log("AsioServer: Packet processing failed with result " +
467 std::to_string(static_cast<int>(result)),
469 }
470 else
471 {
472 utl::Logger::log("AsioServer: Packet processed successfully", utl::LogLevel::INFO);
473 }
474 }
475
477 {
478 // utl::Logger::log("AsioServer: Network thread started", utl::LogLevel::INFO);
479
480 while (m_running.load())
481 {
482 try
483 {
484 // Run I/O context and check if we should continue
485 if (!m_running.load())
486 {
487 break;
488 }
489
490 size_t handlersRun = m_ioContext->run_one();
491 if (handlersRun == 0)
492 {
493 // No handlers to run, sleep briefly to avoid busy waiting
494 std::this_thread::sleep_for(std::chrono::milliseconds(1));
495 }
496 }
497 catch (const std::exception &e)
498 {
499 if (m_running.load())
500 {
501 // utl::Logger::log("AsioServer: Network thread exception - " + std::string(e.what()),
502 // utl::LogLevel::WARNING);
503 std::this_thread::sleep_for(std::chrono::milliseconds(100));
504 }
505 }
506 }
507
508 // utl::Logger::log("AsioServer: Network thread stopped", utl::LogLevel::INFO);
509 }
510
511 std::uint32_t AsioServer::generateSessionId() const
512 {
513 std::random_device rd;
514 std::mt19937 gen(rd());
515 std::uniform_int_distribution<std::uint32_t> dis(1, UINT32_MAX);
516
517 std::uint32_t sessionId = 0;
518 while (sessionId == 0 || m_clients.contains(sessionId))
519 {
520 sessionId = dis(gen);
521 }
522
523 return sessionId;
524 }
525
526 std::string AsioServer::endpointToString(const asio::ip::udp::endpoint &endpoint)
527 {
528 return endpoint.address().to_string() + ":" + std::to_string(endpoint.port());
529 }
530
531 void AsioServer::sendPacketImmediate(const std::vector<std::uint8_t> &data,
532 const asio::ip::udp::endpoint &destination)
533 {
534 utl::Logger::log("AsioServer: Sending " + std::to_string(data.size()) + " bytes to " +
535 destination.address().to_string() + ":" + std::to_string(destination.port()),
537
538 // Protect socket access with mutex
539 std::lock_guard<std::mutex> socketLock(m_socketMutex);
540
541 if (!m_socket || !m_socket->is_open() || !m_running.load())
542 {
543 utl::Logger::log("AsioServer: Cannot send - socket not open or server not running", utl::LogLevel::WARNING);
544 return;
545 }
546
547 try
548 {
549 size_t bytesSent = m_socket->send_to(asio::buffer(data), destination);
550 utl::Logger::log("AsioServer: Successfully sent " + std::to_string(bytesSent) + " bytes",
552 }
553 catch (const std::exception &e)
554 {
555 if (m_running.load())
556 {
557 utl::Logger::log("AsioServer: Failed to send packet - " + std::string(e.what()),
559 }
560 }
561 }
562
564 {
565 // utl::Logger::log("AsioServer: Handling CONNECT packet from " + context.senderAddress + ":" +
566 // std::to_string(context.senderPort),
567 // utl::LogLevel::INFO);
568
569 std::lock_guard<std::mutex> lock(m_clientsMutex);
570 // Check if server is full (avoid getClientCount() to prevent deadlock)
571 const size_t currentClientCount =
572 std::ranges::count_if(m_clients, [](const auto &pair) { return pair.second.isConnected; });
573 // utl::Logger::log("AsioServer: Current client count: " + std::to_string(currentClientCount) + "/" +
574 // std::to_string(MAX_CLIENTS),
575 // utl::LogLevel::INFO);
576
577 if (currentClientCount >= MAX_CLIENTS)
578 {
579 // utl::Logger::log("AsioServer: Server full, rejecting connection", utl::LogLevel::WARNING);
582 }
583
584 // Check if client already exists
585 const std::string endpointStr = endpointToString(m_senderEndpoint);
586 const auto existingIt = m_endpointToSession.find(endpointStr);
587 if (existingIt != m_endpointToSession.end())
588 {
589 // utl::Logger::log("AsioServer: Client already connected, resending accept (Session ID: " +
590 // std::to_string(existingIt->second) + ")",
591 // utl::LogLevel::INFO);
592 // Client already connected, resend accept
593 sendConnectAccept(existingIt->second, m_senderEndpoint);
595 }
596
597 // Create new session
598 std::uint32_t sessionId = generateSessionId();
599 // utl::Logger::log("AsioServer: Creating new session with ID: " + std::to_string(sessionId),
600 // utl::LogLevel::INFO);
601
602 ClientSession &client = m_clients[sessionId];
603 client.sessionId = sessionId;
604 client.endpoint = m_senderEndpoint;
605 client.lastSeen = context.receiveTime;
606 client.playerName = std::string(packet.playerName.data(), packet.nameLen);
607 client.clientCaps = packet.clientCaps;
608 client.isConnected = true;
609
610 m_endpointToSession[endpointStr] = sessionId;
611
612 // utl::Logger::log("AsioServer: Player name: '" + client.playerName +
613 // "', Caps: " + std::to_string(client.clientCaps),
614 // utl::LogLevel::INFO);
615
616 // Send accept response
617 // utl::Logger::log("AsioServer: Sending CONNECT_ACCEPT to session " + std::to_string(sessionId),
618 // utl::LogLevel::INFO);
620
621 // utl::Logger::log("AsioServer: Client connected - " + client.playerName + " (ID: " + std::to_string(sessionId)
622 // +
623 // ")",
624 // utl::LogLevel::INFO);
625
626 // Publier vers GameServer
628 utl::GAME_LOGIC); // GameServer ID
629
630 // utl::Logger::log(
631 // "AsioServer: Nouvelle connexion publiée vers GameServer (sessionId: " + std::to_string(sessionId) + ")",
632 // utl::LogLevel::INFO);
633
635 }
636
638 const rnp::PacketContext &context)
639 {
640 // utl::Logger::log("AsioServer: Handling DISCONNECT packet from session " + std::to_string(context.sessionId) +
641 // " from " + context.senderAddress + ":" + std::to_string(context.senderPort),
642 // utl::LogLevel::INFO);
643
644 std::lock_guard<std::mutex> lock(m_clientsMutex);
645
646 auto it = m_clients.find(context.sessionId);
647 if (it != m_clients.end())
648 {
649 // utl::Logger::log("AsioServer: Client disconnected - " + it->second.playerName +
650 // " (ID: " + std::to_string(context.sessionId) + ")" +
651 // ", Reason: " + std::to_string(static_cast<int>(packet.reasonCode)),
652 // utl::LogLevel::INFO);
653
654 // Remove from endpoint mapping
655 const std::string endpointStr = endpointToString(it->second.endpoint);
656 // utl::Logger::log("AsioServer: Removing endpoint mapping: " + endpointStr, utl::LogLevel::INFO);
657 m_endpointToSession.erase(endpointStr);
658
659 // Remove client
660 // utl::Logger::log("AsioServer: Removing client from session list", utl::LogLevel::INFO);
661 m_clients.erase(it);
662 }
663 else
664 {
665 // utl::Logger::log("AsioServer: DISCONNECT from unknown session " + std::to_string(context.sessionId),
666 // utl::LogLevel::WARNING);
667 }
668
669 // Publier vers GameServer
671 utl::GAME_LOGIC); // GameServer ID
672
673 // utl::Logger::log(
674 // "AsioServer: Déconnexion publiée vers GameServer (sessionId: " + std::to_string(context.sessionId) + ")",
675 // utl::LogLevel::INFO);
676
678 }
679
681 {
682 // Update client last seen time
683 {
684 std::lock_guard<std::mutex> lock(m_clientsMutex);
685 const auto it = m_clients.find(context.sessionId);
686 if (it != m_clients.end())
687 {
688 it->second.lastSeen = context.receiveTime;
689 }
690 }
691
692 // Send pong response
693 sendPong(packet.nonce, m_senderEndpoint, context.sessionId);
695 }
696
698 {
699 // Update client latency and last seen time
700 {
701 std::lock_guard<std::mutex> lock(m_clientsMutex);
702 auto it = m_clients.find(context.sessionId);
703 if (it != m_clients.end())
704 {
705 it->second.lastSeen = context.receiveTime;
706 // Calculate latency based on ping time
707 auto now = std::chrono::steady_clock::now();
708 auto pingTime = std::chrono::milliseconds(packet.sendTimeMs);
709 auto currentTime = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch());
710 it->second.latency = static_cast<std::uint32_t>((currentTime - pingTime).count());
711 }
712 }
713
715 }
716
717 void AsioServer::sendPong(std::uint32_t nonce, const asio::ip::udp::endpoint &destination, std::uint32_t sessionId)
718 {
719 rnp::Serializer serializer;
720 rnp::PacketHeader header{};
721 header.type = static_cast<std::uint8_t>(rnp::PacketType::PONG);
722 header.length = sizeof(rnp::PacketPingPong);
723 header.sessionId = sessionId;
724
725 rnp::PacketPingPong pong{};
726 pong.nonce = nonce;
727 pong.sendTimeMs = static_cast<std::uint32_t>(
728 std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now().time_since_epoch())
729 .count());
730
731 serializer.serializeHeader(header);
732 serializer.serializePingPong(pong);
733
734 sendPacketImmediate(serializer.getData(), destination);
735 }
736
737 void AsioServer::sendConnectAccept(std::uint32_t sessionId, const asio::ip::udp::endpoint &destination)
738 {
739 // utl::Logger::log("AsioServer: Preparing CONNECT_ACCEPT packet for session " + std::to_string(sessionId) +
740 // " to " + destination.address().to_string() + ":" + std::to_string(destination.port()),
741 // utl::LogLevel::INFO);
742
743 rnp::Serializer serializer;
744 rnp::PacketHeader header{};
745 header.type = static_cast<std::uint8_t>(rnp::PacketType::CONNECT_ACCEPT);
746 header.length = sizeof(rnp::PacketConnectAccept);
747 header.sessionId = sessionId;
748
750 accept.sessionId = sessionId;
751 accept.tickRateHz = m_tickRate;
752 accept.mtuPayloadBytes = rnp::MAX_PAYLOAD;
753 accept.serverCaps = m_serverCaps;
754
755 serializer.serializeHeader(header);
756 serializer.serializeConnectAccept(accept);
757
758 // utl::Logger::log("AsioServer: Sending CONNECT_ACCEPT packet (" + std::to_string(serializer.getData().size())
759 // +
760 // " bytes)",
761 // utl::LogLevel::INFO);
762 sendPacketImmediate(serializer.getData(), destination);
763 }
764
765 void AsioServer::sendError(rnp::ErrorCode errorCode, const std::string &description,
766 const asio::ip::udp::endpoint &destination, std::uint32_t sessionId)
767 {
768 rnp::Serializer serializer;
769 rnp::PacketHeader header{};
770 header.type = static_cast<std::uint8_t>(rnp::PacketType::PACKET_ERROR);
771 header.length = static_cast<std::uint16_t>(sizeof(rnp::PacketError) + description.length());
772 header.sessionId = sessionId;
773
774 rnp::PacketError error{};
775 error.errorCode = static_cast<std::uint16_t>(errorCode);
776 error.msgLen = static_cast<std::uint16_t>(description.length());
777 error.description = description;
778
779 serializer.serializeHeader(header);
780 serializer.serializeError(error);
781
782 sendPacketImmediate(serializer.getData(), destination);
783 }
784
786 {
787 const auto now = std::chrono::steady_clock::now();
788
789 // Send pings periodically
790 if (now - m_lastPingTime > m_pingInterval)
791 {
792 std::lock_guard<std::mutex> lock(m_clientsMutex);
793 for (auto &[sessionId, client] : m_clients)
794 {
795 if (client.isConnected)
796 {
797 // Send ping
798 rnp::Serializer serializer;
799 rnp::PacketHeader header{};
800 header.type = static_cast<std::uint8_t>(rnp::PacketType::PING);
801 header.length = sizeof(rnp::PacketPingPong);
802 header.sessionId = sessionId;
803
804 rnp::PacketPingPong ping{};
805 ping.nonce = ++client.lastPingSent;
806 ping.sendTimeMs = static_cast<std::uint32_t>(
807 std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count());
808
809 serializer.serializeHeader(header);
810 serializer.serializePingPong(ping);
811
812 std::lock_guard<std::mutex> queueLock(m_sendQueueMutex);
813 m_sendQueue.emplace(serializer.getData(), client.endpoint, false);
814 }
815 }
816 m_lastPingTime = now;
817 }
818
819 // Check for timeouts
820 std::vector<std::uint32_t> timedOutClients;
821 {
822 std::lock_guard<std::mutex> lock(m_clientsMutex);
823 for (const auto &[sessionId, client] : m_clients)
824 {
825 if (client.isConnected && (now - client.lastSeen) > m_clientTimeout)
826 {
827 timedOutClients.push_back(sessionId);
828 }
829 }
830 }
831
832 // Disconnect timed out clients
833 for (std::uint32_t sessionId : timedOutClients)
834 {
835 // utl::Logger::log("AsioServer: Client " + std::to_string(sessionId) + " timed out", utl::LogLevel::INFO);
836 disconnectClient(sessionId);
837 }
838 }
839
841 {
842 std::lock_guard<std::mutex> lock(m_sendQueueMutex);
843 while (!m_sendQueue.empty())
844 {
845 const QueuedPacket &packet = m_sendQueue.front();
846 sendPacketImmediate(packet.data, packet.destination);
847 m_sendQueue.pop();
848 }
849 }
850
852 {
853 // Consommer les événements du GameServer
854 const auto events = m_eventBus.consumeForTarget(m_componentId);
855
856 for (const auto &event : events)
857 {
858 switch (event.type)
859 {
862 break;
864 handleGameOverEvent(event);
865 break;
866
869 break;
870
873 break;
874
875 default:
876 // utl::Logger::log("AsioServer: Événement non géré: " +
877 // std::to_string(static_cast<uint32_t>(event.type)),
878 // utl::LogLevel::WARNING);
879 break;
880 }
881 }
882 }
883
885 {
886 // Extraire l'ID du client cible et les données
887 if (event.data.size() >= sizeof(uint32_t))
888 {
889 uint32_t sessionId = 0;
890 std::memcpy(&sessionId, event.data.data(), sizeof(uint32_t));
891
892 std::vector<uint8_t> messageData(event.data.begin() + sizeof(uint32_t), event.data.end());
893
894 // Utiliser la méthode existante
895 sendToClient(sessionId, messageData);
896
897 // utl::Logger::log("AsioServer: Message envoye au client " + std::to_string(sessionId) +
898 // " (taille: " + std::to_string(messageData.size()) + " bytes)",
899 // utl::LogLevel::INFO);
900 }
901 }
902
904 {
905 // Utiliser la méthode existante pour broadcaster
906 sendToAllClients(event.data);
907
908 // utl::Logger::log(
909 // "AsioServer: Message diffuse a tous les clients (taille: " + std::to_string(event.data.size()) + "
910 // bytes)", utl::LogLevel::INFO);
911 }
912
914 {
915 // Forward entity events to all clients
916 sendToAllClients(event.data);
917
918 // utl::Logger::log("AsioServer: Entity events diffuses a tous les clients (taille: " +
919 // std::to_string(event.data.size()) + " bytes)",
920 // utl::LogLevel::INFO);
921 }
922
923 rnp::HandlerResult AsioServer::handleEntityEvent(const std::vector<rnp::EventRecord> &events,
924 const rnp::PacketContext &context) const
925 {
926 utl::Logger::log("AsioServer: Received " + std::to_string(events.size()) + " entity events from session " +
927 std::to_string(context.sessionId),
929
930 // Filtrer les événements d'input et les publier vers GameServer
931 for (const auto &eventRecord : events)
932 {
933 if (eventRecord.type == rnp::EventType::INPUT)
934 {
935 utl::Logger::log("AsioServer: Input event received, data size: " +
936 std::to_string(eventRecord.data.size()),
938
939 // Create event with sessionId as sourceId so RTypeServer knows which player
941 event.data = eventRecord.data;
942 m_eventBus.publish(event);
943
944 // utl::Logger::log("AsioServer: Input joueur publie vers GameServer (sessionId: " +
945 // std::to_string(context.sessionId) + ")",
946 // utl::LogLevel::INFO);
947 }
948 else
949 {
951 utl::GAME_LOGIC); // GameServer ID
952
953 // utl::Logger::log("AsioServer: Evenement entite publie vers GameServer (type: " +
954 // std::to_string(static_cast<uint8_t>(eventRecord.type)) + ")",
955 // utl::LogLevel::INFO);
956 }
957 }
958
960 }
961
962 // Lobby System Implementation
963
965 {
966 utl::Logger::log("AsioServer: Handling LOBBY_LIST_REQUEST from session " + std::to_string(context.sessionId),
968
969 // Verify session exists
970 {
971 std::lock_guard<std::mutex> clientsLock(m_clientsMutex);
972 auto clientIt = m_clients.find(context.sessionId);
973 if (clientIt == m_clients.end() || !clientIt->second.isConnected)
974 {
977 }
978 } // Release the lock before calling sendLobbyList
979
980 sendLobbyList(context.sessionId);
982 }
983
985 const rnp::PacketContext &context)
986 {
987 utl::Logger::log("AsioServer: Handling LOBBY_CREATE from session " + std::to_string(context.sessionId),
989
990 std::uint32_t lobbyId = 0;
991 bool success = false;
992
993 // Scope for clientsLock to release it before broadcastLobbyUpdate
994 {
995 std::lock_guard<std::mutex> clientsLock(m_clientsMutex);
996 auto clientIt = m_clients.find(context.sessionId);
997 if (clientIt == m_clients.end() || !clientIt->second.isConnected)
998 {
1001 }
1002
1003 utl::Logger::log("AsioServer: LOBBY_JOIN - Client current lobby ID: " +
1004 std::to_string(clientIt->second.currentLobbyId),
1006
1007 if (clientIt->second.currentLobbyId != 0)
1008 {
1011 }
1012
1013 // Extract lobby name
1014 std::string lobbyName;
1015 if (packet.nameLen > 0 && packet.nameLen <= 31)
1016 {
1017 lobbyName = std::string(packet.lobbyName.data(), packet.nameLen);
1018 }
1019 else
1020 {
1021 lobbyName = "Lobby " + std::to_string(m_nextLobbyId);
1022 }
1023
1024 // Create lobby
1025 lobbyId = createLobby(lobbyName, context.sessionId, packet.maxPlayers, packet.gameMode);
1026 if (lobbyId > 0)
1027 {
1028 clientIt->second.currentLobbyId = lobbyId;
1029 sendLobbyCreateResponse(context.sessionId, lobbyId, true);
1030 success = true;
1031 }
1032 else
1033 {
1035 }
1036 } // clientsLock released here
1037
1038 // Broadcast lobby update AFTER releasing clientsLock to avoid deadlock
1039 if (success && lobbyId > 0)
1040 {
1041 broadcastLobbyUpdate(lobbyId);
1042 }
1043
1045 }
1046
1048 const rnp::PacketContext &context)
1049 {
1050 utl::Logger::log("AsioServer: Handling LOBBY_JOIN from session " + std::to_string(context.sessionId) +
1051 " for lobby " + std::to_string(packet.lobbyId),
1053
1054 bool joinSuccess = false;
1055
1056 // Scope for clientsLock to release it before lobbyToLobbyInfo and broadcastLobbyUpdate
1057 {
1058 std::lock_guard<std::mutex> clientsLock(m_clientsMutex);
1059 utl::Logger::log("AsioServer: LOBBY_JOIN - Checking client session " + std::to_string(context.sessionId),
1061 auto clientIt = m_clients.find(context.sessionId);
1062 if (clientIt == m_clients.end() || !clientIt->second.isConnected)
1063 {
1064 utl::Logger::log("AsioServer: LOBBY_JOIN failed - unauthorized session " +
1065 std::to_string(context.sessionId),
1069 }
1070
1071 if (clientIt->second.currentLobbyId != 0)
1072 {
1073 utl::Logger::log("AsioServer: LOBBY_JOIN failed - session " + std::to_string(context.sessionId) +
1074 " already in lobby " + std::to_string(clientIt->second.currentLobbyId),
1078 }
1079
1080 // Try to join lobby
1081 LobbyStatus joinStatus = joinLobby(packet.lobbyId, context.sessionId);
1082 utl::Logger::log("AsioServer: joinLobby returned status: " + std::to_string(static_cast<int>(joinStatus)),
1084
1085 if (joinStatus == LobbyStatus::SUCCESS)
1086 {
1087 clientIt->second.currentLobbyId = packet.lobbyId;
1088 utl::Logger::log("AsioServer: Session " + std::to_string(context.sessionId) +
1089 " successfully joined lobby " + std::to_string(packet.lobbyId),
1091 joinSuccess = true;
1092 }
1093 else
1094 {
1095 utl::Logger::log("AsioServer: Session " + std::to_string(context.sessionId) + " failed to join lobby " +
1096 std::to_string(packet.lobbyId) +
1097 " - status: " + std::to_string(static_cast<int>(joinStatus)),
1100 nullptr);
1101 }
1102 } // clientsLock released here
1103
1104 // Get lobby info AFTER releasing clientsLock to avoid deadlock in lobbyToLobbyInfo
1105 if (joinSuccess)
1106 {
1107 rnp::LobbyInfo lobbyInfo;
1108 {
1109 std::lock_guard<std::mutex> lobbiesLock(m_lobbiesMutex);
1110 auto lobbyIt = m_lobbies.find(packet.lobbyId);
1111 if (lobbyIt != m_lobbies.end())
1112 {
1113 lobbyInfo = lobbyToLobbyInfo(lobbyIt->second);
1114 utl::Logger::log("AsioServer: Lobby info - currentPlayers: " +
1115 std::to_string(static_cast<int>(lobbyInfo.currentPlayers)) + "/" +
1116 std::to_string(static_cast<int>(lobbyInfo.maxPlayers)),
1118 }
1119 }
1120
1121 sendLobbyJoinResponse(context.sessionId, packet.lobbyId, true, rnp::ErrorCode::NONE, &lobbyInfo);
1122 }
1123
1124 // Broadcast lobby update AFTER releasing clientsLock to avoid deadlock
1125 if (joinSuccess)
1126 {
1128 }
1129
1131 }
1132
1134 {
1135 utl::Logger::log("AsioServer: Handling LOBBY_LEAVE from session " + std::to_string(context.sessionId),
1137
1138 std::uint32_t lobbyId = 0;
1139
1140 // Scope for clientsLock to release it before broadcastLobbyUpdate
1141 {
1142 std::lock_guard<std::mutex> clientsLock(m_clientsMutex);
1143 auto clientIt = m_clients.find(context.sessionId);
1144 if (clientIt == m_clients.end() || !clientIt->second.isConnected)
1145 {
1148 }
1149
1150 // Check if client is in a lobby
1151 if (clientIt->second.currentLobbyId == 0)
1152 {
1155 }
1156
1157 lobbyId = clientIt->second.currentLobbyId;
1158 clientIt->second.currentLobbyId = 0; // Clear lobby association before leaving
1159 } // clientsLock released here
1160
1161 // Leave lobby after releasing clientsLock to avoid nested mutex acquisitions
1162 leaveLobby(context.sessionId);
1163
1164 // Broadcast update to remaining players AFTER releasing clientsLock to avoid deadlock
1165 if (lobbyId != 0)
1166 {
1167 broadcastLobbyUpdate(lobbyId);
1168 }
1169
1171 }
1172
1173 void AsioServer::sendLobbyList(const std::uint32_t sessionId)
1174 {
1175 utl::Logger::log("AsioServer: Sending lobby list to session " + std::to_string(sessionId), utl::LogLevel::INFO);
1176
1178 {
1179 std::lock_guard<std::mutex> lobbiesLock(m_lobbiesMutex);
1180 response.lobbyCount = static_cast<std::uint16_t>(m_lobbies.size());
1181 response.lobbies.reserve(m_lobbies.size());
1182
1183 for (const auto &lobby : m_lobbies | std::views::values)
1184 {
1185 rnp::LobbyInfo lobbyInfo = lobbyToLobbyInfo(lobby);
1186 utl::Logger::log("AsioServer: Adding lobby " + std::to_string(lobbyInfo.lobbyId) + " with " +
1187 std::to_string(static_cast<int>(lobbyInfo.currentPlayers)) + "/" +
1188 std::to_string(static_cast<int>(lobbyInfo.maxPlayers)) + " players",
1190 response.lobbies.push_back(lobbyInfo);
1191 }
1192 }
1193
1194 // Serialize header and response data
1195 std::vector<std::uint8_t> packet;
1196 rnp::Serializer packetSerializer(packet);
1197
1198 rnp::PacketHeader header;
1199 header.type = static_cast<std::uint8_t>(rnp::PacketType::LOBBY_LIST_RESPONSE);
1200 header.length = static_cast<std::uint16_t>(sizeof(rnp::PacketLobbyListResponse));
1201 header.sessionId = sessionId;
1202
1203 packetSerializer.serializeHeader(header);
1204 packetSerializer.serializeLobbyListResponse(response);
1205
1206 utl::Logger::log("AsioServer: Sending lobby list with " + std::to_string(response.lobbyCount) +
1207 " lobbies, packet size: " + std::to_string(packetSerializer.getData().size()),
1209
1210 sendPacketImmediate(packetSerializer.getData(), m_senderEndpoint);
1211 }
1212
1213 std::uint32_t AsioServer::createLobby(const std::string &name, std::uint32_t hostSession, std::uint8_t maxPlayers,
1214 std::uint8_t gameMode)
1215 {
1216 std::lock_guard<std::mutex> lobbiesLock(m_lobbiesMutex);
1217
1218 std::uint32_t lobbyId = m_nextLobbyId++;
1219 Lobby lobby(lobbyId, name, hostSession, maxPlayers, gameMode);
1220 lobby.playerSessions.push_back(hostSession);
1221
1222 m_lobbies[lobbyId] = std::move(lobby);
1223
1224 utl::Logger::log("AsioServer: Created lobby " + std::to_string(lobbyId) + " '" + name + "' hosted by " +
1225 std::to_string(hostSession),
1227
1228 return lobbyId;
1229 }
1230
1231 LobbyStatus AsioServer::joinLobby(std::uint32_t lobbyId, std::uint32_t sessionId)
1232 {
1233 std::lock_guard<std::mutex> lobbiesLock(m_lobbiesMutex);
1234
1235 utl::Logger::log("AsioServer: joinLobby - Looking for lobby " + std::to_string(lobbyId), utl::LogLevel::INFO);
1236
1237 auto lobbyIt = m_lobbies.find(lobbyId);
1238 if (lobbyIt == m_lobbies.end())
1239 {
1240 utl::Logger::log("AsioServer: joinLobby - Lobby " + std::to_string(lobbyId) + " not found",
1243 }
1244
1245 Lobby &lobby = lobbyIt->second;
1247 "AsioServer: joinLobby - Lobby found, current players: " + std::to_string(lobby.playerSessions.size()) +
1248 "/" + std::to_string(lobby.maxPlayers) + ", status: " + std::to_string(static_cast<int>(lobby.status)),
1250
1251 if (lobby.playerSessions.size() >= lobby.maxPlayers)
1252 {
1253 utl::Logger::log("AsioServer: joinLobby - Lobby is full", utl::LogLevel::WARNING);
1254 return LobbyStatus::FULL;
1255 }
1256
1257 if (lobby.status != rnp::LobbyStatus::WAITING)
1258 {
1259 utl::Logger::log("AsioServer: joinLobby - Game already started", utl::LogLevel::WARNING);
1260 return LobbyStatus::IN_GAME;
1261 }
1262
1263 if (auto playerIt = std::ranges::find(lobby.playerSessions, sessionId); playerIt != lobby.playerSessions.end())
1264 {
1266 }
1267
1268 lobby.playerSessions.push_back(sessionId);
1269
1270 utl::Logger::log("AsioServer: Player " + std::to_string(sessionId) + " joined lobby " +
1271 std::to_string(lobbyId) + " (now " + std::to_string(lobby.playerSessions.size()) + "/" +
1272 std::to_string(static_cast<int>(lobby.maxPlayers)) + " players)",
1274 return LobbyStatus::SUCCESS;
1275 }
1276
1277 void AsioServer::leaveLobby(std::uint32_t sessionId)
1278 {
1279 std::lock_guard<std::mutex> lobbiesLock(m_lobbiesMutex);
1280
1281 for (auto &[lobbyId, lobby] : m_lobbies)
1282 {
1283 if (auto playerIt = std::ranges::find(lobby.playerSessions, sessionId);
1284 playerIt != lobby.playerSessions.end())
1285 {
1286 lobby.playerSessions.erase(playerIt);
1287
1288 utl::Logger::log("AsioServer: Player " + std::to_string(sessionId) + " left lobby " +
1289 std::to_string(lobbyId),
1291
1292 // Clear the client's current lobby association
1293 {
1294 std::lock_guard<std::mutex> clientsLock(m_clientsMutex);
1295 auto clientIt = m_clients.find(sessionId);
1296 if (clientIt != m_clients.end())
1297 {
1298 clientIt->second.currentLobbyId = 0;
1299 utl::Logger::log("AsioServer: Cleared lobby association for session " +
1300 std::to_string(sessionId),
1302 }
1303 }
1304
1305 // If host left, assign new host or delete lobby
1306 if (lobby.hostSessionId == sessionId)
1307 {
1308 if (!lobby.playerSessions.empty())
1309 {
1310 lobby.hostSessionId = lobby.playerSessions[0];
1311 utl::Logger::log("AsioServer: New host for lobby " + std::to_string(lobbyId) + " is " +
1312 std::to_string(lobby.hostSessionId),
1314 }
1315 else
1316 {
1317 // No players left, lobby will be cleaned up
1318 utl::Logger::log("AsioServer: Lobby " + std::to_string(lobbyId) + " is now empty",
1320 }
1321 }
1322 break;
1323 }
1324 }
1325
1327 }
1328
1330 {
1331 utl::Logger::log("AsioServer: Received GAME_OVER event - closing all active lobbies", utl::LogLevel::INFO);
1332
1333 // Broadcast GAME_OVER to all clients in lobbies
1334 std::vector<std::uint32_t> lobbyIds;
1335 {
1336 std::lock_guard<std::mutex> lobbiesLock(m_lobbiesMutex);
1337 for (const auto &[lobbyId, lobby] : m_lobbies)
1338 {
1339 lobbyIds.push_back(lobbyId);
1340 }
1341 }
1342
1343 // Send GAME_OVER packet to all clients in each lobby
1344 for (std::uint32_t lobbyId : lobbyIds)
1345 {
1346 broadcastGameOverToLobby(lobbyId, event.data);
1347 }
1348
1349 // Close all lobbies after game over
1350 {
1351 std::lock_guard<std::mutex> lobbiesLock(m_lobbiesMutex);
1352 std::lock_guard<std::mutex> clientsLock(m_clientsMutex);
1353
1354 for (auto &[sessionId, client] : m_clients)
1355 {
1356 if (client.currentLobbyId != 0)
1357 {
1358 utl::Logger::log("AsioServer: Clearing lobby association for session " + std::to_string(sessionId),
1360 client.currentLobbyId = 0;
1361 }
1362 }
1363
1364 utl::Logger::log("AsioServer: Clearing all lobbies (" + std::to_string(m_lobbies.size()) + " lobbies)",
1366 m_lobbies.clear();
1367 }
1368 }
1369
1370 void AsioServer::broadcastGameOverToLobby(std::uint32_t lobbyId, const std::vector<std::uint8_t> &gameOverData)
1371 {
1372 std::vector<std::uint32_t> playerSessions;
1373
1374 {
1375 std::lock_guard<std::mutex> lobbiesLock(m_lobbiesMutex);
1376 auto lobbyIt = m_lobbies.find(lobbyId);
1377 if (lobbyIt == m_lobbies.end())
1378 {
1379 return;
1380 }
1381 playerSessions = lobbyIt->second.playerSessions;
1382 }
1383
1384 rnp::Serializer serializer;
1385 rnp::PacketHeader header;
1386 header.type = static_cast<std::uint8_t>(rnp::PacketType::GAME_OVER);
1387 header.length = static_cast<std::uint16_t>(gameOverData.size());
1388 header.sessionId = 0; // Broadcast to all
1389
1390 serializer.serializeHeader(header);
1391 // Append game over data to serializer
1392 std::vector<std::uint8_t> packetData = serializer.getData();
1393 packetData.insert(packetData.end(), gameOverData.begin(), gameOverData.end());
1394
1395 // Send to all players in lobby
1396 std::lock_guard<std::mutex> clientsLock(m_clientsMutex);
1397 for (std::uint32_t sessionId : playerSessions)
1398 {
1399 auto clientIt = m_clients.find(sessionId);
1400 if (clientIt != m_clients.end() && clientIt->second.isConnected)
1401 {
1402 utl::Logger::log("AsioServer: Sending GAME_OVER to session " + std::to_string(sessionId),
1404 sendPacketImmediate(packetData, clientIt->second.endpoint);
1405 }
1406 }
1407 }
1408
1409 void AsioServer::broadcastLobbyUpdate(std::uint32_t lobbyId)
1410 {
1411 std::lock_guard<std::mutex> lobbiesLock(m_lobbiesMutex);
1412 auto lobbyIt = m_lobbies.find(lobbyId);
1413 if (lobbyIt == m_lobbies.end())
1414 {
1415 return;
1416 }
1417
1419 update.lobbyInfo = lobbyToLobbyInfo(lobbyIt->second);
1420
1421 // Serialize packet data
1422 std::vector<std::uint8_t> data;
1423 rnp::Serializer serializer(data);
1424 serializer.serializeLobbyUpdate(update);
1425
1426 // Get the serialized data
1427 const std::vector<std::uint8_t> &lobbyUpdateData = serializer.getData();
1428
1429 // Send to all players in lobby
1430 for (std::uint32_t sessionId : lobbyIt->second.playerSessions)
1431 {
1432 rnp::PacketHeader header;
1433 header.type = static_cast<std::uint8_t>(rnp::PacketType::LOBBY_UPDATE);
1434 header.length = static_cast<std::uint16_t>(lobbyUpdateData.size());
1435 header.sessionId = sessionId;
1436
1437 std::vector<std::uint8_t> playerPacket;
1438 rnp::Serializer playerPacketSerializer(playerPacket);
1439 playerPacketSerializer.serializeHeader(header);
1440 playerPacketSerializer.serializeLobbyUpdate(update);
1441
1442 // Lock clients mutex to safely access client data
1443 std::lock_guard<std::mutex> clientsLock(m_clientsMutex);
1444 auto clientIt = m_clients.find(sessionId);
1445 if (clientIt != m_clients.end())
1446 {
1447 sendPacketImmediate(playerPacketSerializer.getData(), clientIt->second.endpoint);
1448 }
1449 }
1450
1451 utl::Logger::log("AsioServer: Broadcasted lobby update for lobby " + std::to_string(lobbyId),
1453 }
1454
1456 {
1457 rnp::LobbyInfo info;
1458 info.lobbyId = lobby.lobbyId;
1459 info.currentPlayers = static_cast<std::uint8_t>(lobby.playerSessions.size());
1460 info.maxPlayers = lobby.maxPlayers;
1461 info.gameMode = lobby.gameMode;
1462 info.status = static_cast<std::uint8_t>(lobby.status);
1463 info.hostSessionId = lobby.hostSessionId;
1464
1465 // Copy lobby name
1466 std::size_t nameLen = std::min(lobby.lobbyName.size(), static_cast<std::size_t>(31));
1467 std::memcpy(info.lobbyName.data(), lobby.lobbyName.c_str(), nameLen);
1468 info.lobbyName[nameLen] = '\0';
1469
1470 // Copy player names
1471 std::lock_guard<std::mutex> clientsLock(m_clientsMutex);
1472 for (std::size_t i = 0; i < lobby.playerSessions.size() && i < 8; ++i)
1473 {
1474 auto clientIt = m_clients.find(lobby.playerSessions[i]);
1475 if (clientIt != m_clients.end())
1476 {
1477 std::size_t playerNameLen = std::min(clientIt->second.playerName.size(), static_cast<std::size_t>(31));
1478 std::memcpy(info.playerNames[i].data(), clientIt->second.playerName.c_str(), playerNameLen);
1479 info.playerNames[i][playerNameLen] = '\0';
1480 }
1481 }
1482
1483 return info;
1484 }
1485
1486 void AsioServer::sendLobbyCreateResponse(std::uint32_t sessionId, std::uint32_t lobbyId, bool success,
1487 rnp::ErrorCode errorCode)
1488 {
1490 response.lobbyId = lobbyId;
1491 response.success = success ? 1 : 0;
1492 response.errorCode = static_cast<std::uint16_t>(errorCode);
1493
1494 rnp::PacketHeader header;
1495 header.type = static_cast<std::uint8_t>(rnp::PacketType::LOBBY_CREATE_RESPONSE);
1496 header.length = static_cast<std::uint16_t>(sizeof(rnp::PacketLobbyCreateResponse));
1497 header.sessionId = sessionId;
1498
1499 std::vector<std::uint8_t> packet;
1500 rnp::Serializer packetSerializer(packet);
1501 packetSerializer.serializeHeader(header);
1502 packetSerializer.serializeLobbyCreateResponse(response);
1503
1504 sendPacketImmediate(packetSerializer.getData(), m_senderEndpoint);
1505
1506 utl::Logger::log("AsioServer: Sent lobby create response to session " + std::to_string(sessionId) +
1507 " (success: " + (success ? "true" : "false") + ")",
1509 }
1510
1511 void AsioServer::sendLobbyJoinResponse(std::uint32_t sessionId, std::uint32_t lobbyId, bool success,
1512 rnp::ErrorCode errorCode, const rnp::LobbyInfo *lobbyInfo)
1513 {
1515 response.lobbyId = lobbyId;
1516 response.success = success ? 1 : 0;
1517 response.errorCode = static_cast<std::uint16_t>(errorCode);
1518 if (success && lobbyInfo)
1519 {
1520 response.lobbyInfo = *lobbyInfo;
1521 }
1522
1523 rnp::PacketHeader header;
1524 header.type = static_cast<std::uint8_t>(rnp::PacketType::LOBBY_JOIN_RESPONSE);
1525 header.length = static_cast<std::uint16_t>(sizeof(rnp::PacketLobbyJoinResponse));
1526 header.sessionId = sessionId;
1527
1528 std::vector<std::uint8_t> packet;
1529 rnp::Serializer packetSerializer(packet);
1530 packetSerializer.serializeHeader(header);
1531 packetSerializer.serializeLobbyJoinResponse(response);
1532
1533 sendPacketImmediate(packetSerializer.getData(), m_senderEndpoint);
1534
1535 utl::Logger::log("AsioServer: Sent lobby join response to session " + std::to_string(sessionId) +
1536 " (success: " + (success ? "true" : "false") + ")",
1538 }
1539
1541 {
1542 // NOTE: Assumes m_lobbiesMutex is already held by caller
1543 auto it = m_lobbies.begin();
1544 while (it != m_lobbies.end())
1545 {
1546 if (it->second.playerSessions.empty())
1547 {
1548 utl::Logger::log("AsioServer: Cleaning up empty lobby " + std::to_string(it->first),
1550 it = m_lobbies.erase(it);
1551 }
1552 else
1553 {
1554 ++it;
1555 }
1556 }
1557 }
1558
1560 const rnp::PacketContext &context)
1561 {
1562 utl::Logger::log("AsioServer: Handling START_GAME_REQUEST from session " + std::to_string(context.sessionId) +
1563 " for lobby " + std::to_string(packet.lobbyId),
1565
1566 // Verify session exists and is in the lobby
1567 {
1568 std::lock_guard<std::mutex> clientsLock(m_clientsMutex);
1569 auto clientIt = m_clients.find(context.sessionId);
1570 if (clientIt == m_clients.end() || !clientIt->second.isConnected)
1571 {
1574 }
1575
1576 if (clientIt->second.currentLobbyId != packet.lobbyId)
1577 {
1578 sendError(rnp::ErrorCode::NOT_IN_LOBBY, "Not in this lobby", m_senderEndpoint, context.sessionId);
1580 }
1581 }
1582
1583 // Verify the session is the host and lobby exists
1584 {
1585 std::lock_guard<std::mutex> lobbiesLock(m_lobbiesMutex);
1586 auto lobbyIt = m_lobbies.find(packet.lobbyId);
1587 if (lobbyIt == m_lobbies.end())
1588 {
1591 }
1592
1593 // Only host can start the game
1594 if (lobbyIt->second.hostSessionId != context.sessionId)
1595 {
1597 context.sessionId);
1599 }
1600
1601 // Update lobby status to IN_GAME
1602 lobbyIt->second.status = rnp::LobbyStatus::IN_GAME;
1603 }
1604
1605 utl::Logger::log("AsioServer: Starting game for lobby " + std::to_string(packet.lobbyId), utl::LogLevel::INFO);
1606
1607 // Get sessionIds of players in lobby
1608 std::vector<std::uint32_t> playerSessions;
1609 {
1610 std::lock_guard<std::mutex> lobbiesLock(m_lobbiesMutex);
1611 auto lobbyIt = m_lobbies.find(packet.lobbyId);
1612 if (lobbyIt != m_lobbies.end())
1613 {
1614 playerSessions = lobbyIt->second.playerSessions;
1615 utl::Logger::log("AsioServer: Player sessions in lobby: " + std::to_string(playerSessions.size()),
1617 }
1618 }
1619
1620 // Notify RTypeServer about game start with player sessions
1622 std::vector<std::uint8_t> sessionsData;
1623 sessionsData.resize(sizeof(std::uint32_t) * playerSessions.size());
1624 for (size_t i = 0; i < playerSessions.size(); ++i)
1625 {
1626 std::memcpy(sessionsData.data() + i * sizeof(std::uint32_t), &playerSessions[i], sizeof(std::uint32_t));
1627 }
1628 startEvent.data = sessionsData;
1629 m_eventBus.publish(startEvent);
1630
1631 utl::Logger::log("AsioServer: Sent SERVER_START to RTypeServer with " + std::to_string(playerSessions.size()) +
1632 " players",
1634
1635 // Broadcast game start to all players
1637
1639 }
1640
1641 void AsioServer::broadcastGameStart(std::uint32_t lobbyId)
1642 {
1643 std::lock_guard<std::mutex> lobbiesLock(m_lobbiesMutex);
1644 auto lobbyIt = m_lobbies.find(lobbyId);
1645 if (lobbyIt == m_lobbies.end())
1646 {
1647 return;
1648 }
1649
1650 rnp::PacketGameStart gameStart;
1651 gameStart.lobbyId = lobbyId;
1652
1653 // Serialize packet
1654 std::vector<std::uint8_t> data;
1655 rnp::Serializer serializer(data);
1656 serializer.serializeGameStart(gameStart);
1657
1658 const std::vector<std::uint8_t> &gameStartData = serializer.getData();
1659
1660 // Send to all players in lobby
1661 for (std::uint32_t sessionId : lobbyIt->second.playerSessions)
1662 {
1663 rnp::PacketHeader header;
1664 header.type = static_cast<std::uint8_t>(rnp::PacketType::GAME_START);
1665 header.length = static_cast<std::uint16_t>(gameStartData.size());
1666 header.sessionId = sessionId;
1667
1668 std::vector<std::uint8_t> playerPacket;
1669 rnp::Serializer playerPacketSerializer(playerPacket);
1670 playerPacketSerializer.serializeHeader(header);
1671 playerPacketSerializer.serializeGameStart(gameStart);
1672
1673 // Lock clients mutex to safely access client data
1674 std::lock_guard<std::mutex> clientsLock(m_clientsMutex);
1675 auto clientIt = m_clients.find(sessionId);
1676 if (clientIt != m_clients.end())
1677 {
1678 sendPacketImmediate(playerPacketSerializer.getData(), clientIt->second.endpoint);
1679 }
1680 }
1681
1682 utl::Logger::log("AsioServer: Broadcasted game start for lobby " + std::to_string(lobbyId),
1684
1685 // Publish GAME_START event to game logic to start waves
1687 utl::Logger::log("AsioServer: Published GAME_START event to game logic", utl::LogLevel::INFO);
1688 }
1689
1690} // namespace srv
Asio-based UDP server implementation for R-Type multiplayer game networking.
Thread-safe event bus implementation for inter-component communication.
Event structures and types for event-driven communication.
This file contains the Logger class.
This file contains the network protocol.
void onPing(PingHandler handler)
Register PING packet handler.
void onPong(PongHandler handler)
Register PONG packet handler.
void onLobbyJoin(LobbyJoinHandler handler)
Register LOBBY_JOIN packet handler.
void onLobbyLeave(LobbyLeaveHandler handler)
Register LOBBY_LEAVE packet handler.
HandlerResult processPacket(const std::vector< std::uint8_t > &data, const PacketContext &context)
Process a received packet.
void onLobbyListRequest(LobbyListRequestHandler handler)
Register LOBBY_LIST_REQUEST packet handler.
void onConnect(ConnectHandler handler)
Register CONNECT packet handler.
void onStartGameRequest(StartGameRequestHandler handler)
Register START_GAME_REQUEST packet handler.
void onLobbyCreate(LobbyCreateHandler handler)
Register LOBBY_CREATE packet handler.
void onEntityEvent(EntityEventHandler handler)
Register ENTITY_EVENT packet handler.
void onDisconnect(DisconnectHandler handler)
Register DISCONNECT packet handler.
void clearSession(std::uint32_t sessionId)
Clear rate limiting data for a session.
Binary serializer for RNP protocol packets.
void serializePingPong(const PacketPingPong &packet)
Serialize PING/PONG packet.
void serializeLobbyCreateResponse(const PacketLobbyCreateResponse &packet)
Serialize LOBBY_CREATE_RESPONSE packet.
PacketHeader deserializeHeader()
Deserialize packet header.
void serializeLobbyListResponse(const PacketLobbyListResponse &packet)
Serialize LOBBY_LIST_RESPONSE packet.
void serializeHeader(const PacketHeader &header)
Serialize packet header.
void serializeConnectAccept(const PacketConnectAccept &packet)
Serialize CONNECT_ACCEPT packet.
void serializeGameStart(const PacketGameStart &packet)
Serialize GAME_START packet.
void serializeDisconnect(const PacketDisconnect &packet)
Serialize DISCONNECT packet.
void serializeLobbyJoinResponse(const PacketLobbyJoinResponse &packet)
Serialize LOBBY_JOIN_RESPONSE packet.
const std::vector< std::uint8_t > & getData() const
Get the serialized data.
void serializeError(const PacketError &packet)
Serialize ERROR packet.
void serializeLobbyUpdate(const PacketLobbyUpdate &packet)
Serialize LOBBY_UPDATE packet.
std::uint16_t m_tickRate
Server tick rate in Hz.
rnp::HandlerResult handlePing(const rnp::PacketPingPong &packet, const rnp::PacketContext &context)
Handle PING packet.
std::unique_ptr< asio::io_context > m_ioContext
ASIO I/O context for async operations.
rnp::HandlerResult handleLobbyJoin(const rnp::PacketLobbyJoin &packet, const rnp::PacketContext &context)
Handle LOBBY_JOIN packet.
void init(const std::string &host, std::uint16_t port) override
void disconnectClient(std::uint32_t sessionId) override
void setupPacketHandlers()
Initialize packet handlers.
void sendToClient(std::uint32_t sessionId, const std::vector< std::uint8_t > &data, bool reliable=false) override
rnp::HandlerResult handleEntityEvent(const std::vector< rnp::EventRecord > &events, const rnp::PacketContext &context) const
Handle entity event packet (including player inputs)
void cleanupEmptyLobbies()
Clean up empty lobbies.
std::uint32_t m_nextLobbyId
Next available lobby ID.
void update() override
rnp::HandlerResult handleConnect(const rnp::PacketConnect &packet, const rnp::PacketContext &context)
Handle CONNECT packet.
~AsioServer() override
Destructor.
void sendConnectAccept(std::uint32_t sessionId, const asio::ip::udp::endpoint &destination)
Send CONNECT_ACCEPT response.
std::string m_host
Server bind address (e.g., "0.0.0.0")
void handleReceive(std::size_t bytesReceived)
Handle received packet.
rnp::LobbyInfo lobbyToLobbyInfo(const Lobby &lobby)
Convert Lobby to LobbyInfo for network transmission.
void setServerCapabilities(std::uint32_t caps) override
void setTickRate(std::uint16_t tickRate) override
std::string endpointToString(const asio::ip::udp::endpoint &endpoint)
Get endpoint string representation.
std::vector< std::uint32_t > getConnectedSessions() const override
rnp::HandlerResult handlePong(const rnp::PacketPingPong &packet, const rnp::PacketContext &context)
Handle PONG packet.
bool isRunning() const override
std::unique_ptr< std::thread > m_networkThread
Dedicated network thread.
std::atomic< bool > m_started
Server started state.
void startReceive()
Start receiving packets asynchronously.
void handleGameOverEvent(const utl::Event &event)
Handle GAME_OVER event from game server.
asio::ip::udp::endpoint m_senderEndpoint
Endpoint of last packet sender.
void sendError(rnp::ErrorCode errorCode, const std::string &description, const asio::ip::udp::endpoint &destination, std::uint32_t sessionId)
Send ERROR packet.
LobbyStatus joinLobby(std::uint32_t lobbyId, std::uint32_t sessionId)
Join player to lobby.
void sendPong(std::uint32_t nonce, const asio::ip::udp::endpoint &destination, std::uint32_t sessionId)
Send PONG response.
std::unordered_map< std::uint32_t, ClientSession > m_clients
Map of session ID to client session.
void networkThreadLoop() const
Network thread main loop.
std::chrono::milliseconds m_clientTimeout
Client timeout duration (15000ms)
std::uint16_t m_port
Server listening port (default: 4567)
std::mutex m_sendQueueMutex
Mutex for send queue access.
std::uint32_t m_serverCaps
Server capability flags.
void sendPacketImmediate(const std::vector< std::uint8_t > &data, const asio::ip::udp::endpoint &destination)
Send packet immediately.
std::uint32_t createLobby(const std::string &lobbyName, std::uint32_t hostSessionId, std::uint8_t maxPlayers, std::uint8_t gameMode)
Create new lobby.
std::mutex m_lobbiesMutex
Mutex for thread-safe lobby access.
void broadcastGameStart(std::uint32_t lobbyId)
Broadcast game start notification to all lobby members.
void sendLobbyCreateResponse(std::uint32_t sessionId, std::uint32_t lobbyId, bool success, rnp::ErrorCode errorCode=rnp::ErrorCode::INTERNAL_ERROR)
Send lobby create response.
std::mutex m_socketMutex
Mutex for socket operations.
AsioServer()
Constructor.
void handleBroadcastEvent(const utl::Event &event)
Handle broadcast event from EventBus.
std::mutex m_clientsMutex
Mutex for thread-safe client access.
std::unordered_map< std::uint32_t, Lobby > m_lobbies
Map of lobby ID to lobby data.
std::queue< QueuedPacket > m_sendQueue
Queue of packets waiting to be sent.
rnp::HandlerResult handleLobbyCreate(const rnp::PacketLobbyCreate &packet, const rnp::PacketContext &context)
Handle LOBBY_CREATE packet.
rnp::HandlerResult handleLobbyListRequest(const rnp::PacketContext &context)
Handle LOBBY_LIST_REQUEST packet.
std::atomic< bool > m_running
Server running state (atomic for thread safety)
std::array< std::uint8_t, MAX_LEN_RECV_BUFFER > m_recvBuffer
Buffer for receiving UDP packets.
void start() override
void sendToAllClients(const std::vector< std::uint8_t > &data, bool reliable=false) override
std::uint32_t m_componentId
Component ID for event bus registration.
void broadcastLobbyUpdate(std::uint32_t lobbyId)
Broadcast lobby update to all lobby members.
void processSendQueue()
Process send queue.
std::unordered_map< std::string, std::uint32_t > m_endpointToSession
Map of endpoint string to session ID.
rnp::HandlerResult handleLobbyLeave(const rnp::PacketContext &context)
Handle LOBBY_LEAVE packet.
void updateClientManagement()
Update client timeouts and send pings.
rnp::HandlerResult handleDisconnect(const rnp::PacketDisconnect &packet, const rnp::PacketContext &context)
Handle DISCONNECT packet.
std::unique_ptr< asio::ip::udp::socket > m_socket
UDP socket for network communication.
std::chrono::milliseconds m_pingInterval
Interval between ping sweeps (5000ms)
void leaveLobby(std::uint32_t sessionId)
Remove player from lobby.
void sendLobbyJoinResponse(std::uint32_t sessionId, std::uint32_t lobbyId, bool success, rnp::ErrorCode errorCode, const rnp::LobbyInfo *lobbyInfo=nullptr)
Send lobby join response.
std::chrono::steady_clock::time_point m_lastPingTime
Timestamp of last ping sweep.
std::size_t getClientCount() const override
void stop() override
rnp::HandlerResult handleStartGameRequest(const rnp::PacketStartGameRequest &packet, const rnp::PacketContext &context)
Handle START_GAME_REQUEST packet.
void broadcastGameOverToLobby(std::uint32_t lobbyId)
Broadcast game start to all players in lobby.
void handleSendToClientEvent(const utl::Event &event)
Handle send to client event from EventBus.
std::unique_ptr< rnp::HandlerPacket > m_packetHandler
RNP packet handler instance.
void sendLobbyList(std::uint32_t sessionId)
Send lobby list to client.
void processEventBusEvents()
Process EventBus events for network operations.
void handleSendEntityEventToClients(const utl::Event &event)
Handle send entity event to clients from EventBus.
utl::EventBus & m_eventBus
std::uint32_t generateSessionId() const
Generate unique session ID.
bool publish(const Event &event)
Publish an event to the bus.
Definition EventBus.hpp:118
std::vector< Event > consumeForTarget(std::uint32_t targetId, std::uint32_t maxEvents=100)
Consume events targeted to specific component.
Definition EventBus.hpp:357
void registerComponent(std::uint32_t componentId, const std::string &name)
Register component name for better debugging.
Definition EventBus.hpp:423
void subscribe(std::uint32_t componentId, EventType type)
Subscribe component to specific event types.
Definition EventBus.hpp:384
Event structure for inter-component communication.
Definition Event.hpp:89
std::vector< std::uint8_t > data
Serialized event data.
Definition Event.hpp:96
static void log(const std::string &message, const LogLevel &logLevel)
Definition Logger.hpp:51
ErrorCode
Error codes.
Definition Protocol.hpp:75
HandlerResult
Packet processing result.
constexpr std::size_t MAX_PAYLOAD
Definition Protocol.hpp:18
constexpr size_t MAX_CLIENTS
LobbyStatus
Status codes for lobby operations.
@ IN_GAME
Game in progress.
@ NOT_FOUND
Requested lobby does not exist.
@ FULL
Lobby has reached maximum capacity.
@ SUCCESS
Operation completed successfully.
@ ALREADY_IN_LOBBY
Client is already in a lobby.
static constexpr std::uint32_t GAME_LOGIC
Definition Event.hpp:19
static constexpr std::uint32_t NETWORK_SERVER
Definition Event.hpp:18
Lobby information structure.
Definition Protocol.hpp:237
std::uint32_t hostSessionId
Definition Protocol.hpp:244
std::array< char, 32 > lobbyName
Definition Protocol.hpp:239
std::uint8_t status
Definition Protocol.hpp:243
std::array< std::array< char, 32 >, 8 > playerNames
Definition Protocol.hpp:245
std::uint32_t lobbyId
Definition Protocol.hpp:238
std::uint8_t currentPlayers
Definition Protocol.hpp:240
std::uint8_t maxPlayers
Definition Protocol.hpp:241
std::uint8_t gameMode
Definition Protocol.hpp:242
CONNECT_ACCEPT packet payload.
Definition Protocol.hpp:148
std::uint32_t sessionId
Definition Protocol.hpp:149
CONNECT packet payload.
Definition Protocol.hpp:138
std::array< char, 32 > playerName
Definition Protocol.hpp:140
std::uint32_t clientCaps
Definition Protocol.hpp:141
std::uint8_t nameLen
Definition Protocol.hpp:139
Context information for packet processing.
std::uint32_t sessionId
std::chrono::steady_clock::time_point receiveTime
std::string senderAddress
std::uint16_t senderPort
DISCONNECT packet payload.
Definition Protocol.hpp:159
std::uint16_t reasonCode
Definition Protocol.hpp:160
ERROR packet payload.
Definition Protocol.hpp:217
std::uint16_t errorCode
Definition Protocol.hpp:218
GAME_START packet payload.
Definition Protocol.hpp:309
std::uint32_t lobbyId
Definition Protocol.hpp:310
Packet header according to RNP specification (Big Endian) Total size: 7 bytes (1 + 2 + 4)
Definition Protocol.hpp:128
std::uint32_t sessionId
Definition Protocol.hpp:131
std::uint8_t type
Definition Protocol.hpp:129
std::uint16_t length
Definition Protocol.hpp:130
LOBBY_CREATE_RESPONSE packet payload.
Definition Protocol.hpp:272
LOBBY_CREATE packet payload.
Definition Protocol.hpp:261
std::array< char, 32 > lobbyName
Definition Protocol.hpp:263
std::uint8_t maxPlayers
Definition Protocol.hpp:264
std::uint8_t gameMode
Definition Protocol.hpp:265
LOBBY_JOIN_RESPONSE packet payload.
Definition Protocol.hpp:290
LOBBY_JOIN packet payload.
Definition Protocol.hpp:282
std::uint32_t lobbyId
Definition Protocol.hpp:283
LOBBY_LIST_RESPONSE packet payload.
Definition Protocol.hpp:252
std::vector< LobbyInfo > lobbies
Definition Protocol.hpp:254
LOBBY_UPDATE packet payload.
Definition Protocol.hpp:301
PING/PONG packet payload.
Definition Protocol.hpp:208
std::uint32_t sendTimeMs
Definition Protocol.hpp:210
std::uint32_t nonce
Definition Protocol.hpp:209
START_GAME_REQUEST packet payload (client requests to start game)
Definition Protocol.hpp:317
Represents an active client connection session.
bool isConnected
Connection state flag.
std::chrono::steady_clock::time_point lastSeen
Timestamp of last received packet.
std::uint32_t clientCaps
Client capability flags (reserved)
std::uint32_t sessionId
Unique session identifier assigned by server.
std::string playerName
Player's display name.
asio::ip::udp::endpoint endpoint
Client's network endpoint (IP:port)
Represents a game lobby (room) where players gather before starting a game.
std::uint32_t hostSessionId
Session ID of the lobby host.
rnp::LobbyStatus status
Current lobby status.
std::uint8_t maxPlayers
Maximum number of players allowed (1-8)
std::vector< std::uint32_t > playerSessions
List of player session IDs in this lobby.
std::uint32_t lobbyId
Unique lobby identifier.
std::string lobbyName
Human-readable lobby name.
std::uint8_t gameMode
Game mode identifier.
Represents a packet queued for asynchronous transmission.
asio::ip::udp::endpoint destination
Target endpoint.
std::vector< std::uint8_t > data
Serialized packet data.