r-type  0.0.0
R-Type main
Loading...
Searching...
No Matches
asioClient.cpp
Go to the documentation of this file.
1///
2/// @file AsioClient.cpp
3/// @brief Asio-based implementation of INetworkClient interface
4/// @namespace eng
5///
6
11#include "Utils/Event.hpp"
12#include "Utils/Logger.hpp"
13
14#include <chrono>
15#include <cstdint>
16#include <random>
17
18namespace eng
19{
20
22 : m_ioContext(std::make_unique<asio::io_context>()),
23 m_socket(std::make_unique<asio::ip::udp::socket>(*m_ioContext)), m_serverPort(0),
24 m_connectionState(ConnectionState::DISCONNECTED), m_sessionId(0), m_serverTickRate(60), m_clientCaps(0),
25 m_running(false), m_packetHandler(std::make_unique<rnp::HandlerPacket>()), m_lastPingNonce(0), m_latency(0),
26 m_pingInterval(std::chrono::seconds(5)), m_connectionTimeout(std::chrono::seconds(30)), m_currentLobbyId(0),
27 m_eventBus(utl::EventBus::getInstance()), m_componentId(utl::NETWORK_CLIENT)
28 {
29 m_stats.connectionTime = std::chrono::steady_clock::now();
31 utl::Logger::log("AsioClient: Initialized", utl::LogLevel::INFO);
32
42 // Note: LOBBY_UPDATE and GAME_START are published by this client (not consumed)
43 // so no subscription is needed here
44 }
45
51
52 void AsioClient::connect(const std::string &host, std::uint16_t port)
53 {
55 {
56 utl::Logger::log("AsioClient: Already connected or connecting", utl::LogLevel::WARNING);
57 return;
58 }
59
60 m_serverHost = host;
61 m_serverPort = port;
63
64 try
65 {
66 // Resolve server address
67 asio::ip::udp::resolver resolver(*m_ioContext);
68 auto endpoints = resolver.resolve(host, std::to_string(port));
69 m_serverEndpoint = *endpoints.begin();
70
71 // Open socket
72 m_socket->open(asio::ip::udp::v4());
73
74 m_running.store(true);
75
76 // Start network thread
77 m_networkThread = std::make_unique<std::thread>(&AsioClient::networkThreadLoop, this);
78
79 // Start receiving
81
82 // Send connect packet
84
85 m_lastServerResponse = std::chrono::steady_clock::now();
86
87 utl::Logger::log("AsioClient: Connecting to " + host + ":" + std::to_string(port), utl::LogLevel::INFO);
88 }
89 catch (const std::exception &e)
90 {
92 m_running.store(false);
93 utl::Logger::log("AsioClient: Failed to connect - " + std::string(e.what()), utl::LogLevel::WARNING);
94 throw;
95 }
96 }
97
99 {
101 {
102 return;
103 }
104
106
107 // Send disconnect packet if connected
108 if (m_sessionId != 0)
109 {
111 std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Allow time for packet to be sent
112 }
113
114 m_running.store(false);
115
116 // Stop io_context
117 if (m_ioContext)
118 {
119 m_ioContext->stop();
120 }
121
122 // Wait for network thread
123 if (m_networkThread && m_networkThread->joinable())
124 {
125 m_networkThread->join();
126 }
127
128 // Close socket
129 if (m_socket && m_socket->is_open())
130 {
131 m_socket->close();
132 }
133
134 // Reset state
136 m_sessionId = 0;
137 m_serverTickRate = 60;
138 m_latency = 0;
139
140 // Clear send queue
141 {
142 std::lock_guard<std::mutex> lock(m_sendQueueMutex);
143 while (!m_sendQueue.empty())
144 {
145 m_sendQueue.pop();
146 }
147 }
148
149 utl::Logger::log("AsioClient: Disconnected", utl::LogLevel::INFO);
150 }
151
153 {
154 // Process EventBus events
156
158 {
159 // utl::Logger::log("AsioClient: Update skipped - not connected (state: " +
160 // std::to_string(static_cast<int>(m_connectionState.load())) + ")",
161 // utl::LogLevel::WARNING);
162 return;
163 }
164
165 // Process send queue
167
168 // Update connection management
170 }
171
173
175
176 std::uint32_t AsioClient::getSessionId() const { return m_sessionId; }
177
178 std::uint16_t AsioClient::getServerTickRate() const { return m_serverTickRate; }
179
180 std::uint32_t AsioClient::getLatency() const { return m_latency; }
181
182 void AsioClient::setPlayerName(const std::string &playerName)
183 {
184 m_playerName = playerName;
185 utl::Logger::log("AsioClient: Player name set to " + playerName, utl::LogLevel::INFO);
186 }
187
188 void AsioClient::setClientCapabilities(std::uint32_t caps)
189 {
190 m_clientCaps = caps;
191 utl::Logger::log("AsioClient: Client capabilities set to " + std::to_string(caps), utl::LogLevel::INFO);
192 }
193
194 void AsioClient::sendToServer(const std::vector<std::uint8_t> &data, bool reliable)
195 {
197 {
198 utl::Logger::log("AsioClient: Attempted to send while not connected", utl::LogLevel::WARNING);
199 return;
200 }
201
202 std::lock_guard<std::mutex> lock(m_sendQueueMutex);
203 m_sendQueue.emplace(data, reliable);
204 }
205
207 {
208 // CONNECT_ACCEPT handler
210 [this](const rnp::PacketConnectAccept &packet, const rnp::PacketContext &context)
211 { return handleConnectAccept(packet, context); });
212
213 // DISCONNECT handler
214 m_packetHandler->onDisconnect([this](const rnp::PacketDisconnect &packet, const rnp::PacketContext &context)
215 { return handleDisconnect(packet, context); });
216
217 // PONG handler
218 m_packetHandler->onPong([this](const rnp::PacketPingPong &packet, const rnp::PacketContext &context)
219 { return handlePong(packet, context); });
220
221 // PING handler (server can ping client too)
222 m_packetHandler->onPing([this](const rnp::PacketPingPong &packet, const rnp::PacketContext &context)
223 { return handlePing(packet, context); });
224
225 // ERROR handler
226 m_packetHandler->onError([](const rnp::PacketError &packet, const rnp::PacketContext &context)
227 { return handleError(packet, context); });
228
229 // WORLD_STATE handler
230 m_packetHandler->onWorldState([this](const rnp::PacketWorldState &packet, const rnp::PacketContext &context)
231 { return handleWorldState(packet, context); });
232
233 // ENTITY_EVENT handler (for server events)
235 [this](const std::vector<rnp::EventRecord> &events, const rnp::PacketContext &context)
236 { return handleEntityEvent(events, context); });
237
238 // LOBBY_LIST_RESPONSE handler
240 [this](const rnp::PacketLobbyListResponse &packet, const rnp::PacketContext &context)
241 { return handleLobbyListResponse(packet, context); });
242
243 // LOBBY_CREATE_RESPONSE handler
245 [this](const rnp::PacketLobbyCreateResponse &packet, const rnp::PacketContext &context)
246 { return handleLobbyCreateResponse(packet, context); });
247
248 // LOBBY_JOIN_RESPONSE handler
250 [this](const rnp::PacketLobbyJoinResponse &packet, const rnp::PacketContext &context)
251 { return handleLobbyJoinResponse(packet, context); });
252
253 // LOBBY_UPDATE handler
254 m_packetHandler->onLobbyUpdate([this](const rnp::PacketLobbyUpdate &packet, const rnp::PacketContext &context)
255 { return handleLobbyUpdate(packet, context); });
256
257 // GAME_START handler
258 m_packetHandler->onGameStart([this](const rnp::PacketGameStart &packet, const rnp::PacketContext &context)
259 { return handleGameStart(packet, context); });
260
261 utl::Logger::log("AsioClient: Packet handlers initialized", utl::LogLevel::INFO);
262 }
263
265 {
266 m_socket->async_receive_from(asio::buffer(m_recvBuffer), m_senderEndpoint,
267 [this](std::error_code ec, std::size_t bytesReceived)
268 {
269 if (!ec && m_running.load())
270 {
271 handleReceive(bytesReceived);
272 startReceive(); // Continue receiving
273 }
274 else if (m_running.load())
275 {
276 utl::Logger::log("AsioClient: Receive error - " + ec.message(),
277 utl::LogLevel::WARNING);
278 startReceive(); // Try to continue
279 }
280 });
281 }
282
283 void AsioClient::handleReceive(std::size_t bytesReceived)
284 {
285 // PacketHeader serialized size: 1 (type) + 2 (length) + 4 (sessionId) = 7 bytes
286 constexpr std::size_t PACKET_HEADER_SIZE = 7;
287 if (bytesReceived < PACKET_HEADER_SIZE)
288 {
289 utl::Logger::log("AsioClient: Received packet too small (need " + std::to_string(PACKET_HEADER_SIZE) +
290 " bytes, got " + std::to_string(bytesReceived) + ")",
292 return;
293 }
294
295 m_lastServerResponse = std::chrono::steady_clock::now();
297 m_stats.bytesTransferred += static_cast<std::uint32_t>(bytesReceived);
298
299 // Create packet context
300 rnp::PacketContext context;
301 context.receiveTime = std::chrono::steady_clock::now();
302 context.senderAddress = m_senderEndpoint.address().to_string();
303 context.senderPort = m_senderEndpoint.port();
304
305 // Extract session ID from header for context
306 std::vector<std::uint8_t> data(m_recvBuffer.begin(), m_recvBuffer.begin() + bytesReceived);
307 if (data.size() >= PACKET_HEADER_SIZE)
308 {
309 rnp::Serializer headerSerializer(data);
310 rnp::PacketHeader header = headerSerializer.deserializeHeader();
311 context.sessionId = header.sessionId;
312 }
313
314 // Process packet
315 rnp::HandlerResult result = m_packetHandler->processPacket(data, context);
316 if (result != rnp::HandlerResult::SUCCESS)
317 {
318 utl::Logger::log("AsioClient: Packet processing failed with result " +
319 std::to_string(static_cast<int>(result)),
321 }
322 }
323
325 {
326 utl::Logger::log("AsioClient: Network thread started", utl::LogLevel::INFO);
327
328 while (m_running.load())
329 {
330 try
331 {
332 m_ioContext->run_for(std::chrono::milliseconds(100));
333 if (!m_running.load())
334 {
335 break;
336 }
337 m_ioContext->restart();
338 }
339 catch (const std::exception &e)
340 {
341 utl::Logger::log("AsioClient: Network thread exception - " + std::string(e.what()),
343 std::this_thread::sleep_for(std::chrono::milliseconds(100));
344 }
345 }
346
347 utl::Logger::log("AsioClient: Network thread stopped", utl::LogLevel::INFO);
348 }
349
350 void AsioClient::sendPacketImmediate(const std::vector<std::uint8_t> &data)
351 {
352 if (!m_socket || !m_socket->is_open())
353 {
354 utl::Logger::log("AsioClient: Cannot send packet - socket not open", utl::LogLevel::WARNING);
355 return;
356 }
357
358 utl::Logger::log("AsioClient: Sending " + std::to_string(data.size()) + " bytes to " +
359 m_serverEndpoint.address().to_string() + ":" + std::to_string(m_serverEndpoint.port()),
361
362 try
363 {
364 size_t bytesSent = m_socket->send_to(asio::buffer(data), m_serverEndpoint);
366 m_stats.bytesTransferred += static_cast<std::uint32_t>(data.size());
367 utl::Logger::log("AsioClient: Successfully sent " + std::to_string(bytesSent) + " bytes",
369 }
370 catch (const std::exception &e)
371 {
372 utl::Logger::log("AsioClient: Failed to send packet - " + std::string(e.what()), utl::LogLevel::WARNING);
373 }
374 }
375
377 const rnp::PacketContext &context)
378 {
379 m_sessionId = packet.sessionId;
382
383 utl::Logger::log("AsioClient: Connection accepted - Session ID: " + std::to_string(m_sessionId) +
384 ", Tick rate: " + std::to_string(m_serverTickRate) + "Hz",
386
387 rnp::Serializer serializer;
389 utl::RENDERING_ENGINE); // GameClient ID
391 }
392
394 const rnp::PacketContext &context)
395 {
396 auto reason = static_cast<rnp::DisconnectReason>(packet.reasonCode);
397 std::string reasonStr = "Unknown";
398
399 switch (reason)
400 {
402 reasonStr = "Client request";
403 break;
405 reasonStr = "Timeout";
406 break;
408 reasonStr = "Protocol error";
409 break;
411 reasonStr = "Server shutdown";
412 break;
414 reasonStr = "Server full";
415 break;
417 reasonStr = "Banned";
418 break;
420 reasonStr = "Unspecified";
421 break;
422 }
423
424 utl::Logger::log("AsioClient: Disconnected by server - Reason: " + reasonStr, utl::LogLevel::INFO);
425
427 m_sessionId = 0;
428
430 }
431
433 {
434 if (packet.nonce == m_lastPingNonce)
435 {
436 auto now = std::chrono::steady_clock::now();
437 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(now - m_lastPingTime);
438 m_latency = static_cast<std::uint32_t>(duration.count());
439
440 utl::Logger::log("AsioClient: Ping response received - Latency: " + std::to_string(m_latency) + "ms",
442 }
443
445 }
446
448 {
449 // Server is pinging us, send pong response
450 sendPong(packet.nonce);
452 }
453
455 {
456 const auto errorCode = static_cast<rnp::ErrorCode>(packet.errorCode);
457 std::string errorStr = "Unknown error";
458
459 switch (errorCode)
460 {
462 errorStr = "Invalid payload";
463 break;
465 errorStr = "Unauthorized session";
466 break;
468 errorStr = "Rate limited";
469 break;
471 errorStr = "Internal server error";
472 break;
473 default:
474 errorStr = "Unknown error";
475 break;
476 }
477
478 utl::Logger::log("AsioClient: Server error - " + errorStr + ": " + packet.description, utl::LogLevel::WARNING);
479
481 }
482
484 const rnp::PacketContext &context) const
485 {
486 // Handle world state update
487 utl::Logger::log("AsioClient: World state received - Tick: " + std::to_string(packet.serverTick) +
488 ", Entities: " + std::to_string(packet.entityCount),
490
491 // Forward to all interested components via EventBus (broadcast)
493
495 }
496
498 {
499 rnp::Serializer serializer;
500 rnp::PacketHeader header{};
501 header.type = static_cast<std::uint8_t>(rnp::PacketType::CONNECT);
502 header.length = sizeof(rnp::PacketConnect);
503 header.sessionId = 0; // No session ID yet
504
506 connect.nameLen = static_cast<std::uint8_t>(
507 std::min(m_playerName.length(), static_cast<std::size_t>(connect.playerName.size() - 1)));
508 std::memset(connect.playerName.data(), 0, connect.playerName.size());
509 if (!m_playerName.empty())
510 {
511 std::memcpy(connect.playerName.data(), m_playerName.data(), connect.nameLen);
512 }
513 connect.clientCaps = m_clientCaps;
514
515 serializer.serializeHeader(header);
516 serializer.serializeConnect(connect);
517
518 sendPacketImmediate(serializer.getData());
519 utl::Logger::log("AsioClient: CONNECT packet sent", utl::LogLevel::INFO);
520 }
521
523 {
524 if (m_sessionId == 0)
525 {
526 return;
527 }
528
529 rnp::Serializer serializer;
530 rnp::PacketHeader header{};
531 header.type = static_cast<std::uint8_t>(rnp::PacketType::DISCONNECT);
532 header.length = sizeof(rnp::PacketDisconnect);
533 header.sessionId = m_sessionId;
534
536 disconnect.reasonCode = static_cast<std::uint16_t>(rnp::DisconnectReason::CLIENT_REQUEST);
537
538 serializer.serializeHeader(header);
540
541 sendPacketImmediate(serializer.getData());
542 utl::Logger::log("AsioClient: DISCONNECT packet sent", utl::LogLevel::INFO);
543 }
544
546 {
547 if (m_sessionId == 0)
548 {
549 return;
550 }
551
553 m_lastPingTime = std::chrono::steady_clock::now();
554
555 rnp::Serializer serializer;
556 rnp::PacketHeader header{};
557 header.type = static_cast<std::uint8_t>(rnp::PacketType::PING);
558 header.length = sizeof(rnp::PacketPingPong);
559 header.sessionId = m_sessionId;
560
561 rnp::PacketPingPong ping{};
562 ping.nonce = m_lastPingNonce;
563 ping.sendTimeMs = static_cast<std::uint32_t>(
564 std::chrono::duration_cast<std::chrono::milliseconds>(m_lastPingTime.time_since_epoch()).count());
565
566 serializer.serializeHeader(header);
567 serializer.serializePingPong(ping);
568
569 sendPacketImmediate(serializer.getData());
570 }
571
572 void AsioClient::sendPong(std::uint32_t nonce)
573 {
574 if (m_sessionId == 0)
575 {
576 return;
577 }
578
579 rnp::Serializer serializer;
580 rnp::PacketHeader header{};
581 header.type = static_cast<std::uint8_t>(rnp::PacketType::PONG);
582 header.length = sizeof(rnp::PacketPingPong);
583 header.sessionId = m_sessionId;
584
585 rnp::PacketPingPong pong{};
586 pong.nonce = nonce;
587 pong.sendTimeMs = static_cast<std::uint32_t>(
588 std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now().time_since_epoch())
589 .count());
590
591 serializer.serializeHeader(header);
592 serializer.serializePingPong(pong);
593
594 sendPacketImmediate(serializer.getData());
595 }
596
598 {
599 std::lock_guard<std::mutex> lock(m_sendQueueMutex);
600 if (!m_sendQueue.empty())
601 {
602 utl::Logger::log("AsioClient: Processing send queue with " + std::to_string(m_sendQueue.size()) +
603 " packets",
605 }
606 while (!m_sendQueue.empty())
607 {
608 const QueuedPacket &packet = m_sendQueue.front();
610 m_sendQueue.pop();
611 }
612 }
613
615 {
616 auto now = std::chrono::steady_clock::now();
617
618 // Send periodic pings when connected
620 {
621 if (now - m_lastPingTime > m_pingInterval)
622 {
623 sendPing();
624 }
625 }
626
627 // Check for connection timeout
630 {
632 {
633 utl::Logger::log("AsioClient: Connection timed out", utl::LogLevel::WARNING);
635 m_sessionId = 0;
636 }
637 }
638
639 // Retry connection if in CONNECTING state and enough time has passed
641 {
642 static auto lastConnectAttempt = std::chrono::steady_clock::now();
643 if (now - lastConnectAttempt > std::chrono::seconds(2))
644 {
645 sendConnect();
646 lastConnectAttempt = now;
647 }
648 }
649 }
650
652 {
653 static std::random_device rd;
654 static std::mt19937 gen(rd());
655 static std::uniform_int_distribution<std::uint32_t> dis(1, UINT32_MAX);
656 return dis(gen);
657 }
658
660 {
661 const auto events = m_eventBus.consumeForTarget(m_componentId);
662 for (const auto &e : events)
663 {
664 utl::Logger::log("AsioClient: Processing EventBus event type " +
665 std::to_string(static_cast<std::uint32_t>(e.type)),
667 switch (e.type)
668 {
670 {
671 sendToServer(e.data);
672 break;
673 }
675 {
676 rnp::Serializer serializer(e.data);
677 std::string playerName = serializer.readString(32);
678 std::string serverIP = serializer.readString(15);
679 unsigned short serverPortStr = std::stoi(serializer.readString(5).c_str());
680 utl::Logger::log("AsioClient: Received REQUEST_CONNECT event - Player: " + playerName +
681 ", Server: " + serverIP + ":" + std::to_string(serverPortStr),
684 {
685 setPlayerName(playerName);
686 connect(serverIP, serverPortStr);
687 }
688 break;
689 }
691 {
693 {
694 disconnect();
695 }
696 break;
697 }
699 {
700 // Create full packet with header and payload
701 rnp::Serializer packetSerializer;
702
703 // Create header for ENTITY_EVENT packet
704 rnp::PacketHeader header;
705 header.type = static_cast<std::uint8_t>(rnp::PacketType::ENTITY_EVENT);
706 header.length = static_cast<std::uint16_t>(e.data.size());
707 header.sessionId = m_sessionId;
708
709 // Serialize header and payload
710 packetSerializer.serializeHeader(header);
711 packetSerializer.writeBytes(e.data.data(), e.data.size());
712
713 // Send to server
714 sendToServer(packetSerializer.getData());
715 break;
716 }
718 {
720 break;
721 }
723 {
724 // Check if this is actually a START_GAME_REQUEST (just uint32_t lobbyId)
725 if (e.data.size() == sizeof(std::uint32_t))
726 {
727 // This is a START_GAME_REQUEST disguised as LOBBY_CREATE
728 std::uint32_t lobbyId;
729 std::memcpy(&lobbyId, e.data.data(), sizeof(std::uint32_t));
730 requestStartGame(lobbyId);
731 }
732 else
733 {
734 // Normal LOBBY_CREATE
735 rnp::Serializer serializer(e.data);
736 rnp::PacketLobbyCreate lobbyCreate = serializer.deserializeLobbyCreate();
737 std::string lobbyName(lobbyCreate.lobbyName.data(), lobbyCreate.nameLen);
738 std::uint8_t maxPlayers = lobbyCreate.maxPlayers;
739 std::uint8_t gameMode = lobbyCreate.gameMode;
740 utl::Logger::log("AsioClient: Received LOBBY_CREATE event - Name: " + lobbyName +
741 ", Max Players: " + std::to_string(maxPlayers) +
742 ", Game Mode: " + std::to_string(gameMode),
744 createLobby(lobbyCreate);
745 }
746 break;
747 }
749 {
750 rnp::Serializer serializer(e.data);
751 std::uint32_t lobbyId = serializer.readUInt32();
752 joinLobby(lobbyId);
753 break;
754 }
756 {
757 utl::Logger::log("AsioClient: Received LOBBY_LEAVE event", utl::LogLevel::INFO);
758 leaveLobby();
759 break;
760 }
762 {
763 // This event is for other components (GameMulti), not AsioClient
764 // Just ignore it here
765 break;
766 }
767 default:
768 {
769 utl::Logger::log("AsioClient: Unhandled event type: " + std::to_string(static_cast<int>(e.type)),
771 break;
772 }
773 }
774 }
775 }
776
777 rnp::HandlerResult AsioClient::handleEntityEvent(const std::vector<rnp::EventRecord> &events,
778 const rnp::PacketContext &context) const
779 {
780 utl::Logger::log("AsioClient: Received " + std::to_string(events.size()) + " entity events from server",
782
784
786 }
787
788 // Lobby System Implementation
789
791 {
793 {
794 utl::Logger::log("AsioClient: Cannot request lobby list - not connected", utl::LogLevel::WARNING);
795 return;
796 }
797
798 utl::Logger::log("AsioClient: Requesting lobby list from server", utl::LogLevel::INFO);
799
800 rnp::PacketHeader header;
801 header.type = static_cast<std::uint8_t>(rnp::PacketType::LOBBY_LIST_REQUEST);
802 header.length = 0;
803 header.sessionId = m_sessionId;
804
805 std::vector<std::uint8_t> packet;
806 rnp::Serializer serializer(packet);
807 serializer.serializeHeader(header);
808
809 std::cout << "Sending lobby list request packet of size " << serializer.getSize() << std::endl;
810 sendToServer(serializer.getData(), true);
811 }
812
814 {
816 {
817 utl::Logger::log("AsioClient: Cannot create lobby - not connected", utl::LogLevel::WARNING);
818 return;
819 }
820
821 if (m_currentLobbyId != 0)
822 {
823 utl::Logger::log("AsioClient: Cannot create lobby - already in lobby", utl::LogLevel::WARNING);
824 return;
825 }
826
827 std::string name(lobbyPacket.lobbyName.data(), lobbyPacket.nameLen);
828 utl::Logger::log("AsioClient: Creating lobby '" + name + "'", utl::LogLevel::INFO);
829
830 utl::Logger::log("AsioClient: Lobby create packet - Name: " + name +
831 ", Max Players: " + std::to_string(lobbyPacket.maxPlayers) +
832 ", Game Mode: " + std::to_string(lobbyPacket.gameMode),
834
835 std::vector<std::uint8_t> data;
836 rnp::Serializer dataSerializer(data);
837
838 rnp::PacketHeader header;
839 header.type = static_cast<std::uint8_t>(rnp::PacketType::LOBBY_CREATE);
840 header.length = static_cast<std::uint16_t>(sizeof(rnp::PacketLobbyCreate));
841 header.sessionId = m_sessionId;
842 utl::Logger::log("AsioClient: Lobby create header - Type: " + std::to_string(header.type) + ", Length: " +
843 std::to_string(header.length) + ", Session ID: " + std::to_string(header.sessionId),
845
846 dataSerializer.serializeHeader(header);
847 dataSerializer.serializeLobbyCreate(lobbyPacket);
848 std::cout << "Sending lobby create packet of size " << dataSerializer.getSize() << std::endl;
849 sendToServer(dataSerializer.getData(), true);
850 }
851
852 void AsioClient::joinLobby(std::uint32_t lobbyId)
853 {
855 {
856 utl::Logger::log("AsioClient: Cannot join lobby - not connected", utl::LogLevel::WARNING);
857 return;
858 }
859
860 if (m_currentLobbyId != 0)
861 {
862 utl::Logger::log("AsioClient: Cannot join lobby - already in lobby", utl::LogLevel::WARNING);
863 return;
864 }
865
866 utl::Logger::log("AsioClient: Joining (lobby " + std::to_string(lobbyId), utl::LogLevel::INFO);
867
868 rnp::PacketLobbyJoin joinPacket;
869 joinPacket.lobbyId = lobbyId;
870
871 rnp::PacketHeader header;
872 header.type = static_cast<std::uint8_t>(rnp::PacketType::LOBBY_JOIN);
873 header.length = static_cast<std::uint16_t>(sizeof(rnp::PacketLobbyJoin));
874 header.sessionId = m_sessionId;
875
876 std::vector<std::uint8_t> packet;
877 rnp::Serializer packetSerializer(packet);
878 packetSerializer.serializeHeader(header);
879 packetSerializer.serializeLobbyJoin(joinPacket);
880
881 sendToServer(packetSerializer.getData(), true);
882 }
883
885 {
887 {
888 utl::Logger::log("AsioClient: Cannot leave lobby - not connected", utl::LogLevel::WARNING);
889 return;
890 }
891
892 if (m_currentLobbyId == 0)
893 {
894 utl::Logger::log("AsioClient: Cannot leave lobby - not in lobby", utl::LogLevel::WARNING);
895 return;
896 }
897
898 utl::Logger::log("AsioClient: Leaving lobby " + std::to_string(m_currentLobbyId), utl::LogLevel::INFO);
899
900 rnp::PacketHeader header;
901 header.type = static_cast<std::uint8_t>(rnp::PacketType::LOBBY_LEAVE);
902 header.length = 0;
903 header.sessionId = m_sessionId;
904
905 std::vector<std::uint8_t> packet;
906 rnp::Serializer serializer(packet);
907 serializer.serializeHeader(header);
908
909 sendToServer(serializer.getData(), true);
910
912 }
913
914 void AsioClient::requestStartGame(std::uint32_t lobbyId)
915 {
917 {
918 utl::Logger::log("AsioClient: Cannot request start game - not connected", utl::LogLevel::WARNING);
919 return;
920 }
921
922 utl::Logger::log("AsioClient: Requesting to start game for lobby " + std::to_string(lobbyId),
924
926 request.lobbyId = lobbyId;
927
928 rnp::PacketHeader header;
929 header.type = static_cast<std::uint8_t>(rnp::PacketType::START_GAME_REQUEST);
930 header.length = static_cast<std::uint16_t>(sizeof(rnp::PacketStartGameRequest));
931 header.sessionId = m_sessionId;
932
933 std::vector<std::uint8_t> packet;
934 rnp::Serializer serializer(packet);
935 serializer.serializeHeader(header);
936 serializer.serializeStartGameRequest(request);
937
938 sendToServer(serializer.getData(), true);
939 utl::Logger::log("AsioClient: START_GAME_REQUEST packet sent", utl::LogLevel::INFO);
940 }
941
942 void AsioClient::setOnLobbyListReceived(std::function<void(const std::vector<rnp::LobbyInfo> &)> callback)
943 {
944 m_onLobbyListReceived = std::move(callback);
945 }
946
947 void AsioClient::setOnLobbyCreated(std::function<void(std::uint32_t, bool, rnp::ErrorCode)> callback)
948 {
949 m_onLobbyCreated = std::move(callback);
950 }
951
953 std::function<void(std::uint32_t, bool, rnp::ErrorCode, const rnp::LobbyInfo *)> callback)
954 {
955 m_onLobbyJoined = std::move(callback);
956 }
957
958 void AsioClient::setOnLobbyUpdated(std::function<void(const rnp::LobbyInfo &)> callback)
959 {
960 m_onLobbyUpdated = std::move(callback);
961 }
962
963 void AsioClient::setOnGameStart(std::function<void(std::uint32_t, std::uint32_t)> callback)
964 {
965 m_onGameStart = std::move(callback);
966 }
967
969 const rnp::PacketContext &context) const
970 {
971 utl::Logger::log("AsioClient: Received lobby list with " + std::to_string(packet.lobbyCount) + " lobbies",
973
975 {
977 }
979 7); // JoinRoomScene ID
981 }
982
984 const rnp::PacketContext &context)
985 {
987 "AsioClient: Received lobby create response - success: " + std::string(packet.success ? "true" : "false") +
988 ", lobbyId: " + std::to_string(packet.lobbyId),
990
991 if (packet.success)
992 {
993 m_currentLobbyId = packet.lobbyId;
994 }
995
997 {
998 m_onLobbyCreated(packet.lobbyId, packet.success != 0, static_cast<rnp::ErrorCode>(packet.errorCode));
999 }
1001 6); // CreateRoomScene ID
1003 }
1004
1006 const rnp::PacketContext &context)
1007 {
1009 "AsioClient: Received lobby join response - success: " + std::string(packet.success ? "true" : "false") +
1010 ", lobbyId: " + std::to_string(packet.lobbyId),
1012
1013 if (packet.success)
1014 {
1015 m_currentLobbyId = packet.lobbyId;
1016 }
1017
1018 if (m_onLobbyJoined)
1019 {
1020 const rnp::LobbyInfo *lobbyInfo = packet.success ? &packet.lobbyInfo : nullptr;
1021 m_onLobbyJoined(packet.lobbyId, packet.success != 0, static_cast<rnp::ErrorCode>(packet.errorCode),
1022 lobbyInfo);
1023 }
1025
1027 }
1028
1030 const rnp::PacketContext &context) const
1031 {
1032 utl::Logger::log("AsioClient: Received lobby update for lobby " + std::to_string(packet.lobbyInfo.lobbyId),
1034
1035 if (m_onLobbyUpdated)
1036 {
1038 }
1039
1040 // Publish to event bus so scenes can receive the update
1041 m_eventBus.publish(utl::EventType::LOBBY_UPDATE, packet, m_componentId, 8); // WaitingRoomScene ID
1042
1044 }
1045
1047 const rnp::PacketContext &context) const
1048 {
1049 utl::Logger::log("AsioClient: Game starting for lobby " + std::to_string(packet.lobbyId), utl::LogLevel::INFO);
1050
1051 if (m_onGameStart)
1052 {
1053 m_onGameStart(packet.lobbyId, context.sessionId);
1054 }
1055
1056 // Publish to event bus so WaitingRoomScene can receive the game start event
1057 utl::Event gameStartEvent(utl::EventType::GAME_START, m_componentId, 8); // target = WaitingRoomScene ID
1058 gameStartEvent.data =
1059 std::vector<std::uint8_t>(reinterpret_cast<const std::uint8_t *>(&packet),
1060 reinterpret_cast<const std::uint8_t *>(&packet) + sizeof(rnp::PacketGameStart));
1061 m_eventBus.publish(gameStartEvent);
1062
1064 }
1065
1066} // namespace eng
Asio-based UDP client implementation for R-Type multiplayer game networking.
Event structures and types for event-driven communication.
Network packet handler for RNP protocol.
This file contains the Logger class.
This file contains the network protocol.
Network packet serializer for RNP protocol.
void sendDisconnect()
Send DISCONNECT packet to terminate connection.
std::chrono::milliseconds m_connectionTimeout
Timeout duration (15000ms)
std::string m_serverHost
Server hostname or IP address.
void setupPacketHandlers()
Setup packet handlers for all RNP packet types.
rnp::HandlerResult handleLobbyJoinResponse(const rnp::PacketLobbyJoinResponse &packet, const rnp::PacketContext &context)
Handle LOBBY_JOIN_RESPONSE packet.
void joinLobby(std::uint32_t lobbyId)
Join an existing lobby.
void setPlayerName(const std::string &playerName) override
Set player name for connection.
void sendPacketImmediate(const std::vector< std::uint8_t > &data)
Send packet immediately via UDP socket.
rnp::HandlerResult handlePing(const rnp::PacketPingPong &packet, const rnp::PacketContext &context)
Handle PING packet.
std::uint16_t m_serverTickRate
Server tick rate in Hz.
void setOnLobbyJoined(std::function< void(std::uint32_t, bool, rnp::ErrorCode, const rnp::LobbyInfo *)> callback)
Set callback for lobby join response.
std::unique_ptr< asio::ip::udp::socket > m_socket
UDP socket for network communication.
std::uint32_t getSessionId() const override
Get assigned session ID.
std::uint32_t m_clientCaps
Client capability flags.
std::array< std::uint8_t, 1024 > m_recvBuffer
Buffer for receiving UDP packets.
std::uint32_t m_sessionId
Assigned session ID from server.
rnp::HandlerResult handleDisconnect(const rnp::PacketDisconnect &packet, const rnp::PacketContext &context)
Handle DISCONNECT packet.
void networkThreadLoop() const
Main network thread loop.
std::uint32_t m_componentId
Component ID for event bus.
void disconnect() override
Disconnect from server.
std::atomic< bool > m_running
Network thread running state (atomic)
std::function< void(std::uint32_t, bool, rnp::ErrorCode, const rnp::LobbyInfo *)> m_onLobbyJoined
Lobby joined callback.
static rnp::HandlerResult handleError(const rnp::PacketError &packet, const rnp::PacketContext &context)
Handle ERROR packet.
rnp::HandlerResult handlePong(const rnp::PacketPingPong &packet, const rnp::PacketContext &context)
Handle PONG packet.
void sendPing()
Send PING packet for latency measurement.
rnp::HandlerResult handleLobbyUpdate(const rnp::PacketLobbyUpdate &packet, const rnp::PacketContext &context) const
Handle LOBBY_UPDATE packet.
std::function< void(std::uint32_t, bool, rnp::ErrorCode)> m_onLobbyCreated
Lobby created callback.
rnp::HandlerResult handleWorldState(const rnp::PacketWorldState &packet, const rnp::PacketContext &context) const
Handle WORLD_STATE packet.
rnp::HandlerResult handleConnectAccept(const rnp::PacketConnectAccept &packet, const rnp::PacketContext &context)
Handle CONNECT_ACCEPT packet.
void processSendQueue()
Process outgoing packet queue.
bool isConnected() const override
Check if client is connected to server.
rnp::HandlerResult handleLobbyCreateResponse(const rnp::PacketLobbyCreateResponse &packet, const rnp::PacketContext &context)
Handle LOBBY_CREATE_RESPONSE packet.
std::uint32_t m_latency
Round-trip time in milliseconds.
asio::ip::udp::endpoint m_senderEndpoint
Endpoint of last packet sender.
std::uint32_t m_currentLobbyId
Current lobby ID, 0 if not in lobby.
void setOnGameStart(std::function< void(std::uint32_t, std::uint32_t)> callback)
Set callback for game start notification.
std::uint16_t getServerTickRate() const override
Get server tick rate.
~AsioClient() override
Destructor.
std::atomic< ConnectionState > m_connectionState
Current connection state (atomic)
void update() override
Update client state (called each frame)
void setOnLobbyCreated(std::function< void(std::uint32_t, bool, rnp::ErrorCode)> callback)
Set callback for lobby creation response.
AsioClient()
Constructor.
void sendPong(std::uint32_t nonce)
Send PONG response to server PING.
void sendToServer(const std::vector< std::uint8_t > &data, bool reliable=false) override
Send custom packet to server.
void leaveLobby()
Leave current lobby.
ConnectionState getConnectionState() const override
Get current connection state.
std::chrono::steady_clock::time_point m_lastServerResponse
Timestamp of last server packet.
void setOnLobbyListReceived(std::function< void(const std::vector< rnp::LobbyInfo > &)> callback)
Set callback for lobby list received.
void requestStartGame(std::uint32_t lobbyId)
Request to start the game (host only)
void sendConnect()
Send CONNECT packet to initiate connection.
std::function< void(std::uint32_t, std::uint32_t)> m_onGameStart
Game start callback.
rnp::HandlerResult handleLobbyListResponse(const rnp::PacketLobbyListResponse &packet, const rnp::PacketContext &context) const
Handle LOBBY_LIST_RESPONSE packet.
rnp::HandlerResult handleGameStart(const rnp::PacketGameStart &packet, const rnp::PacketContext &context) const
Handle GAME_START packet.
static std::uint32_t generatePingNonce()
Generate random ping nonce.
void requestLobbyList()
Request list of available lobbies from server.
std::chrono::steady_clock::time_point m_lastPingTime
Timestamp of last PING.
std::uint16_t m_serverPort
Server port number.
std::function< void(const rnp::LobbyInfo &)> m_onLobbyUpdated
Lobby updated callback.
void processBusEvent()
Process events from event bus.
std::unique_ptr< std::thread > m_networkThread
Dedicated network thread.
std::unique_ptr< rnp::HandlerPacket > m_packetHandler
RNP packet handler instance.
std::uint32_t m_lastPingNonce
Nonce of last PING sent.
void setOnLobbyUpdated(std::function< void(const rnp::LobbyInfo &)> callback)
Set callback for lobby updates.
std::string m_playerName
Player display name.
std::function< void(const std::vector< rnp::LobbyInfo > &)> m_onLobbyListReceived
Lobby list callback.
void connect(const std::string &host, std::uint16_t port) override
Connect to game server.
asio::ip::udp::endpoint m_serverEndpoint
Resolved server endpoint.
rnp::HandlerResult handleEntityEvent(const std::vector< rnp::EventRecord > &events, const rnp::PacketContext &context) const
Handle ENTITY_EVENT packet.
std::chrono::milliseconds m_pingInterval
Interval between pings (5000ms)
std::queue< QueuedPacket > m_sendQueue
Queue of packets waiting to be sent.
void handleReceive(std::size_t bytesReceived)
Handle received packet data.
ConnectionStats m_stats
Connection statistics.
void updateConnectionManagement()
Update connection management state.
std::unique_ptr< asio::io_context > m_ioContext
ASIO I/O context for async operations.
void startReceive()
Start asynchronous receive operation.
utl::EventBus & m_eventBus
Event bus reference.
std::mutex m_sendQueueMutex
Mutex for send queue access.
std::uint32_t getLatency() const override
Get current latency to server.
void setClientCapabilities(std::uint32_t caps) override
Set client capability flags.
void createLobby(rnp::PacketLobbyCreate lobbyCreate)
Create a new lobby on server.
void onLobbyListResponse(LobbyListResponseHandler handler)
Register LOBBY_LIST_RESPONSE packet handler.
void onPing(PingHandler handler)
Register PING packet handler.
void onPong(PongHandler handler)
Register PONG packet handler.
HandlerResult processPacket(const std::vector< std::uint8_t > &data, const PacketContext &context)
Process a received packet.
void onWorldState(WorldStateHandler handler)
Register WORLD_STATE packet handler.
void onConnectAccept(ConnectAcceptHandler handler)
Register CONNECT_ACCEPT packet handler.
void onError(ErrorHandler handler)
Register ERROR packet handler.
void onLobbyUpdate(LobbyUpdateHandler handler)
Register LOBBY_UPDATE packet handler.
void onEntityEvent(EntityEventHandler handler)
Register ENTITY_EVENT packet handler.
void onLobbyJoinResponse(LobbyJoinResponseHandler handler)
Register LOBBY_JOIN_RESPONSE packet handler.
void onLobbyCreateResponse(LobbyCreateResponseHandler handler)
Register LOBBY_CREATE_RESPONSE packet handler.
void onGameStart(GameStartHandler handler)
Register GAME_START packet handler.
void onDisconnect(DisconnectHandler handler)
Register DISCONNECT packet handler.
Binary serializer for RNP protocol packets.
void serializePingPong(const PacketPingPong &packet)
Serialize PING/PONG packet.
void writeBytes(const void *data, std::size_t size)
Write raw bytes.
PacketHeader deserializeHeader()
Deserialize packet header.
void serializeStartGameRequest(const PacketStartGameRequest &packet)
Serialize START_GAME_REQUEST packet.
void serializeHeader(const PacketHeader &header)
Serialize packet header.
PacketLobbyCreate deserializeLobbyCreate()
Deserialize LOBBY_CREATE packet.
void serializeLobbyCreate(const PacketLobbyCreate &packet)
Serialize LOBBY_CREATE packet.
void serializeConnect(const PacketConnect &packet)
Serialize CONNECT packet.
void serializeDisconnect(const PacketDisconnect &packet)
Serialize DISCONNECT packet.
std::uint32_t readUInt32()
Read a 32-bit integer (network byte order)
std::string readString(std::size_t maxLength)
Read a string with length prefix.
const std::vector< std::uint8_t > & getData() const
Get the serialized data.
std::size_t getSize() const
Get the current size of serialized data.
void serializeLobbyJoin(const PacketLobbyJoin &packet)
Serialize LOBBY_JOIN packet.
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
ConnectionState
Connection state enumeration.
ErrorCode
Error codes.
Definition Protocol.hpp:75
HandlerResult
Packet processing result.
DisconnectReason
Disconnect reason codes.
Definition Protocol.hpp:61
static constexpr std::uint32_t RENDERING_ENGINE
Definition Event.hpp:20
std::uint32_t packetsSent
Total number of packets sent.
std::uint32_t packetsReceived
Total number of packets received.
std::chrono::steady_clock::time_point connectionTime
Timestamp when connection was established.
std::uint32_t bytesTransferred
Total bytes transferred (sent + received)
Represents a packet queued for asynchronous transmission to server.
std::vector< std::uint8_t > data
Serialized packet data.
Lobby information structure.
Definition Protocol.hpp:237
std::uint32_t lobbyId
Definition Protocol.hpp:238
CONNECT_ACCEPT packet payload.
Definition Protocol.hpp:148
std::uint32_t sessionId
Definition Protocol.hpp:149
std::uint16_t tickRateHz
Definition Protocol.hpp:150
CONNECT packet payload.
Definition Protocol.hpp:138
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
std::string description
Definition Protocol.hpp:220
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 nonce
Definition Protocol.hpp:209
START_GAME_REQUEST packet payload (client requests to start game)
Definition Protocol.hpp:317
WORLD_STATE packet payload.
Definition Protocol.hpp:198
std::uint16_t entityCount
Definition Protocol.hpp:200
std::uint32_t serverTick
Definition Protocol.hpp:199