From 3fde5208f9d8df4cf88634feca3fd0cff8929e05 Mon Sep 17 00:00:00 2001 From: fir <29286243+usermicrodevices@users.noreply.github.com> Date: Fri, 15 May 2026 00:09:12 +0300 Subject: [PATCH 01/15] CMAKE_CXX_STANDARD 20 --- CMakeLists.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3300b4a..92fc0d3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.15) project(GameServer) -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(fmt REQUIRED) @@ -196,6 +196,10 @@ add_executable(gameserver ${DATABASE_SOURCES} ) +if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + target_compile_options(gameserver PRIVATE -fcoroutines) +endif() + if(ENABLE_ASAN) message(STATUS "Enabling AddressSanitizer and UndefinedBehaviorSanitizer") if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") From 61ae152a619e1004f2a60e6d023bfb230425e327 Mon Sep 17 00:00:00 2001 From: fir <29286243+usermicrodevices@users.noreply.github.com> Date: Fri, 15 May 2026 00:10:27 +0300 Subject: [PATCH 02/15] added check installation dependencies --- build.sh | 69 +++++++++++++++++++++++++++++++++----------------------- 1 file changed, 41 insertions(+), 28 deletions(-) diff --git a/build.sh b/build.sh index 3c641ad..2799999 100755 --- a/build.sh +++ b/build.sh @@ -50,52 +50,31 @@ fi cd .. -# Install system dependencies (NO Boost packages) -sudo apt-get update -sudo apt-get install -y \ - build-essential \ - cmake \ - libpq-dev \ - python3-dev \ - libssl-dev \ - zlib1g-dev \ - postgresql \ - libglm-dev \ - libasio-dev \ - libspdlog-dev \ - nlohmann-json3-dev \ - libgl1-mesa-dev \ - libglu1-mesa-dev \ - libx11-dev \ - libxi-dev \ - libxrandr-dev \ - mesa-common-dev \ - uuid-dev \ - libcrypt-dev \ - libfmt-dev - # Parse command line arguments USE_CITUS=OFF USE_SQLITE=OFF ENABLE_ASAN=OFF CLEAR_PREVIOUS=OFF +APT_UPDATE=OFF for arg in "$@"; do case $arg in --with-citus) echo "Installing Citus extension..." - sudo apt-get install -y postgresql-citus USE_CITUS=ON ;; --with-sqlite) echo "Installing SQLite3 development libraries..." - sudo apt-get install -y libsqlite3-dev USE_SQLITE=ON ;; --with-asan) echo "Enabling AddressSanitizer and UndefinedBehaviorSanitizer" ENABLE_ASAN=ON ;; + --apt-update) + echo "Enabling linux apt update" + APT_UPDATE=ON + ;; --clear) echo "Enabling clear previous compilations" CLEAR_PREVIOUS=ON @@ -106,7 +85,41 @@ for arg in "$@"; do done # Build configuration -echo "Building with Citus: $USE_CITUS, SQLite: $USE_SQLITE, ASan: $ENABLE_ASAN" +echo "Building with Citus: $USE_CITUS, SQLite: $USE_SQLITE, ASan: $ENABLE_ASAN, apt update: $APT_UPDATE" + +# Install system dependencies +if [ $APT_UPDATE == ON ]; then +sudo apt update +sudo apt install -y \ + build-essential \ + cmake \ + libpq-dev \ + python3-dev \ + libssl-dev \ + zlib1g-dev \ + postgresql \ + libglm-dev \ + libasio-dev \ + libspdlog-dev \ + nlohmann-json3-dev \ + libgl1-mesa-dev \ + libglu1-mesa-dev \ + libx11-dev \ + libxi-dev \ + libxrandr-dev \ + mesa-common-dev \ + uuid-dev \ + libcrypt-dev \ + libfmt-dev + if [ $USE_CITUS == ON ]; then + sudo apt install -y postgresql-citus + fi + + if [ $USE_SQLITE == ON ]; then + sudo apt install -y libsqlite3-dev + fi +fi + # Clean previous build artifacts rm -f CMakeCache.txt Makefile cmake_install.cmake @@ -115,7 +128,7 @@ rm -rf CMakeFiles # Create build directory and copy related folders mkdir -p build cd build -if [ "$CLEAR_PREVIOUS" = true ]; then +if [ "$CLEAR_PREVIOUS" == ON ]; then echo "Clearing previous compilations..." find . -mindepth 1 -maxdepth 1 ! -name "certs" -exec rm -rf {} + fi From 93467af207dfa0d6396ef425a06575de2ab62b0d Mon Sep 17 00:00:00 2001 From: fir <29286243+usermicrodevices@users.noreply.github.com> Date: Fri, 15 May 2026 00:11:36 +0300 Subject: [PATCH 03/15] refactor cross-process architecture --- include/network/BinaryProtocol.hpp | 31 +- include/network/BinarySession.hpp | 2 +- include/network/ClientListener.hpp | 3 +- include/network/ConnectionManager.hpp | 5 +- include/network/IConnection.hpp | 11 - include/network/MasterServer.hpp | 6 +- include/network/WebSocketProtocol.hpp | 2 +- include/network/WebSocketSession.hpp | 7 +- include/process/ProcessPool.hpp | 35 +- src/database/SQLiteClient.cpp | 8 +- src/game/GameLogic.cpp | 19 +- src/network/BinaryProtocol.cpp | 66 ++-- src/network/BinarySession.cpp | 480 +++++++------------------- src/network/ClientListener.cpp | 90 +++-- src/network/ConnectionManager.cpp | 118 ++++++- src/network/MasterServer.cpp | 122 ++++--- src/network/WebSocketProtocol.cpp | 280 +++++++++------ src/network/WebSocketSession.cpp | 20 +- src/process/ProcessPool.cpp | 175 +++++++--- 19 files changed, 757 insertions(+), 723 deletions(-) diff --git a/include/network/BinaryProtocol.hpp b/include/network/BinaryProtocol.hpp index ae16726..f56a5af 100644 --- a/include/network/BinaryProtocol.hpp +++ b/include/network/BinaryProtocol.hpp @@ -13,7 +13,6 @@ namespace BinaryProtocol { - // Message types enum MessageType : uint16_t { MESSAGE_TYPE_INVALID = 0, @@ -101,28 +100,23 @@ namespace BinaryProtocol { uint32_t timestamp; uint32_t length; uint32_t checksum; - - NetworkHeader(uint16_t type = 0, uint32_t seq = 0, uint8_t ver = 1, uint8_t flgs = 0) - : version(ver), flags(flgs), message_type(type), - sequence(seq), timestamp(0), length(0), checksum(0) {} + NetworkHeader(uint16_t type = 0, uint32_t seq = 0, uint8_t ver = 1, uint8_t flgs = 0); }; struct BinaryMessage { NetworkHeader header; std::vector data; - std::vector Serialize() const; static BinaryMessage Deserialize(const uint8_t* buffer, size_t length); - - bool IsCompressed() const { return (header.flags & FLAG_COMPRESSED) != 0; } - bool IsEncrypted() const { return (header.flags & FLAG_ENCRYPTED) != 0; } - bool IsReliable() const { return (header.flags & FLAG_RELIABLE) != 0; } + bool IsCompressed() const; + bool IsEncrypted() const; + bool IsReliable() const; }; class BinaryWriter { public: BinaryWriter(); - + void WriteRaw(const uint8_t* data, size_t length); void WriteUInt8(uint8_t value); void WriteUInt16(uint16_t value); void WriteUInt32(uint32_t value); @@ -136,10 +130,8 @@ namespace BinaryProtocol { void WriteVector3(const glm::vec3& vec); void WriteQuaternion(const glm::quat& quaternion); void WriteJson(const nlohmann::json& json); - - const std::vector& GetBuffer() const { return buffer_; } - size_t GetSize() const { return buffer_.size(); } - + const std::vector& GetBuffer() const; + size_t GetSize() const; void Clear(); private: @@ -149,7 +141,6 @@ namespace BinaryProtocol { class BinaryReader { public: BinaryReader(const uint8_t* data, size_t length); - uint8_t ReadUInt8(); uint16_t ReadUInt16(); uint32_t ReadUInt32(); @@ -163,11 +154,9 @@ namespace BinaryProtocol { glm::vec3 ReadVector3(); glm::quat ReadQuaternion(); nlohmann::json ReadJson(); - - size_t Remaining() const { return length_ - position_; } - bool CanRead(size_t size) const { return position_ + size <= length_; } - - size_t GetPosition() const { return position_; } + size_t Remaining() const; + bool CanRead(size_t size) const; + size_t GetPosition() const; private: const uint8_t* data_; diff --git a/include/network/BinarySession.hpp b/include/network/BinarySession.hpp index 2968103..60b89b9 100644 --- a/include/network/BinarySession.hpp +++ b/include/network/BinarySession.hpp @@ -142,7 +142,7 @@ class BinarySession : public IConnection, public std::enable_shared_from_this; explicit BinarySession(asio::ip::tcp::socket socket, - std::shared_ptr ssl_context = nullptr); + std::shared_ptr ssl_context=nullptr, uint64_t sessionId=0); ~BinarySession(); ProtocolMode GetProtocolMode() const override; diff --git a/include/network/ClientListener.hpp b/include/network/ClientListener.hpp index d70bc64..3419795 100644 --- a/include/network/ClientListener.hpp +++ b/include/network/ClientListener.hpp @@ -12,11 +12,12 @@ class ClientListener { public: - ClientListener(const WorkerGroupConfig& groupConfig, int masterFd); + ClientListener(const WorkerGroupConfig& groupConfig, int masterFd, int workerId); ~ClientListener(); void Start(); void Shutdown(); private: + int workerId_; asio::io_context io_; asio::posix::stream_descriptor pipe_; std::thread ioThread_; diff --git a/include/network/ConnectionManager.hpp b/include/network/ConnectionManager.hpp index 1b47932..a263f4f 100644 --- a/include/network/ConnectionManager.hpp +++ b/include/network/ConnectionManager.hpp @@ -23,11 +23,12 @@ class ConnectionManager { public: using MasterSender = std::function& body)>; - ConnectionManager(const WorkerGroupConfig& groupConfig, MasterSender masterSender); + ConnectionManager(const WorkerGroupConfig& groupConfig, MasterSender masterSender, int workerId); ~ConnectionManager(); bool Start(); void Shutdown(); void OnMasterReply(uint32_t correlationId, const std::vector& reply); + void OnMasterPush(uint64_t sessionId, const std::vector& data); private: asio::io_context ioContext_; @@ -50,6 +51,8 @@ class ConnectionManager { std::string host_; uint16_t port_; bool reuse_; + int workerId_; + uint64_t nextLocalSid_ = 1; void initSessionFactory(); void doAccept(); void onClientMessage(uint64_t sessionId, uint16_t type, const std::vector& data); diff --git a/include/network/IConnection.hpp b/include/network/IConnection.hpp index bd4b71f..7ad614e 100644 --- a/include/network/IConnection.hpp +++ b/include/network/IConnection.hpp @@ -12,17 +12,6 @@ enum class ProtocolMode { Binary, Json, Unknown }; -static const std::unordered_map IPCMessageTypes = { - {"welcome", 1}, - {"heartbeat", 2}, - {"broadcast", 3}, - {"shutdown", 4}, - {"reload_config", 5}, - {"player_spawn_relay", 201}, - {"player_despawn_relay", 202}, - {"player_position_relay", 206} -}; - class IConnection { public: virtual ~IConnection() = default; diff --git a/include/network/MasterServer.hpp b/include/network/MasterServer.hpp index b725fb3..d08b0ea 100644 --- a/include/network/MasterServer.hpp +++ b/include/network/MasterServer.hpp @@ -22,7 +22,10 @@ #include "network/ClientListener.hpp" #include "game/GameLogic.hpp" -class DatabaseService; +struct PendingRequest { + int workerId; + uint32_t correlationId; +}; class MasterServer { public: @@ -53,6 +56,7 @@ class MasterServer { std::function&)> sendReplyCb_; std::function assignVirtualId_; std::atomic nextPersistentId_{1}; + std::unordered_map pendingReplies_; void start_signal_read(); void WireCallbacks(); diff --git a/include/network/WebSocketProtocol.hpp b/include/network/WebSocketProtocol.hpp index ce5b315..d8418ff 100644 --- a/include/network/WebSocketProtocol.hpp +++ b/include/network/WebSocketProtocol.hpp @@ -239,7 +239,7 @@ namespace WebSocketProtocol { void ReadFrame(); void HandleFrame(const WebSocketFrame& frame); void SendFrame(const WebSocketFrame& frame); - void SendFrameAsync(const WebSocketFrame& frame); + //void SendFrameAsync(const WebSocketFrame& frame); // Message assembly void ProcessMessageData(const WebSocketFrame& frame); diff --git a/include/network/WebSocketSession.hpp b/include/network/WebSocketSession.hpp index 59dd89f..be791bf 100644 --- a/include/network/WebSocketSession.hpp +++ b/include/network/WebSocketSession.hpp @@ -15,13 +15,12 @@ class WebSocketSession : public IConnection, public std::enable_shared_from_this { public: - WebSocketSession(WebSocketProtocol::WebSocketConnection::Pointer wsConn); + WebSocketSession(WebSocketProtocol::WebSocketConnection::Pointer wsConn, uint64_t sessionId=0); ~WebSocketSession(); void SetProtocolMode(ProtocolMode mode) { protocolMode_ = mode; } ProtocolMode GetProtocolMode() const override { return protocolMode_; } - // IConnection implementation void Start() override; void Stop() override; void Disconnect() override; @@ -79,22 +78,18 @@ class WebSocketSession : public IConnection, public std::enable_shared_from_this int64_t playerId_{0}; std::string authToken_; - // Groups mutable std::mutex groupsMutex_; std::set groups_; - // Data mutable std::mutex dataMutex_; std::map data_; - // Properties mutable std::mutex propertiesMutex_; std::map properties_; BinaryMessageHandler binary_handler_; BinaryMessageHandler default_binary_handler_; - // Internal helpers void OnMessage(const WebSocketProtocol::WebSocketMessage& msg); void OnClose(uint16_t code, const std::string& reason); }; diff --git a/include/process/ProcessPool.hpp b/include/process/ProcessPool.hpp index 44c9f1b..e28f417 100644 --- a/include/process/ProcessPool.hpp +++ b/include/process/ProcessPool.hpp @@ -12,7 +12,26 @@ #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include + +using asio::ip::tcp; +using asio::awaitable; +using asio::co_spawn; +using asio::detached; +using asio::redirect_error; +using asio::use_awaitable; + #include #include "logging/Logger.hpp" @@ -46,6 +65,9 @@ class ProcessWorker : public std::enable_shared_from_this { )>; asio::posix::stream_descriptor& GetMasterStream(); void SendAsync(const std::vector& binaryData); + void StartWriterThread(); + void StopWriter(); + void JoinWriterThread(); private: int workerId_; @@ -59,7 +81,11 @@ class ProcessWorker : public std::enable_shared_from_this { std::mutex writeMutex_; std::deque> sendQueue_; std::mutex sendMutex_; + std::condition_variable sendCv_; bool writing_ = false; + std::thread writerThread_; + bool writerRunning_{true}; + void writerLoop(); void doWrite(); }; @@ -71,14 +97,15 @@ class ProcessPool : public std::enable_shared_from_this { void Shutdown(); void SetWorker(std::function func); bool SendToWorker(int workerId, const std::vector& message); - void BroadcastToOtherWorkers(const nlohmann::json& msg, int senderId); - void BroadcastToAllWorkers(const nlohmann::json& msg); + void BroadcastToOtherWorkers(const std::vector& msg, int owner_id); + void BroadcastToAllWorkers(const std::vector& msg); size_t GetTotalWorkerCount() const; bool IsWorkerAlive(int workerId) const; bool IsWorkersReady() const; void WaitForWorkers(); void SetMasterMessageHandler(ProcessWorker::MasterMessageHandler handler); bool SendReplyToWorker(int workerId, uint32_t correlationId, const std::vector& binaryData); + bool PushToWorker(int workerId, uint64_t sessionId, const std::vector& binaryData); private: asio::io_context& io_; @@ -86,9 +113,11 @@ class ProcessPool : public std::enable_shared_from_this { std::vector> workers_; std::function worker_; int workerId_ = -1; - bool running_ = false; + std::atomic running_{false}; std::atomic ready_{false}; ProcessWorker::MasterMessageHandler masterHandler_; + std::vector readerThreads_; + std::unordered_map>> readerRunningFlags_; void doSpawnWorkers(); void StartReadingFromWorker(std::shared_ptr worker); }; diff --git a/src/database/SQLiteClient.cpp b/src/database/SQLiteClient.cpp index 6d3b70b..a02c97e 100644 --- a/src/database/SQLiteClient.cpp +++ b/src/database/SQLiteClient.cpp @@ -135,9 +135,9 @@ size_t SQLiteClient::GetIdleConnections() const { bool SQLiteClient::ExecuteSql(const std::string& sql, std::vector>* results) { std::lock_guard lock(dbMutex_); - Logger::Trace("SQLite ExecuteSql start: {}", sql.substr(0, 100)); + //Logger::Trace("SQLiteClient::ExecuteSql start: {}", sql.substr(0, 100)); if (!db_) { - Logger::Error("ExecuteSql: database not connected"); + Logger::Error("SQLiteClient::ExecuteSql: database not connected"); stats_.failedQueries++; stats_.connectionErrors++; return false; @@ -170,7 +170,7 @@ bool SQLiteClient::ExecuteSql(const std::string& sql, std::vector data(jsonStr.begin(), jsonStr.end()); - sendReplyCb_(req.session_id, data); - } else { - Logger::Error("No sendReplyCb_ set in GameLogic"); - } } void GameLogic::OnPlayerState(const PlayerStateData& data) { @@ -1070,6 +1054,7 @@ void GameLogic::SaveLoop() { std::unique_lock lock(saveMutex_); saveCV_.wait_for(lock, std::chrono::seconds(GetWorldConfig().save_interval), [this] { return !running_.load(); }); + Logger::Trace("GameLogic::SaveLoop tick, running_ = {}", running_.load()); if (!running_.load()) break; SaveGameState(); if (!running_.load()) break; @@ -1079,7 +1064,7 @@ void GameLogic::SaveLoop() { } void GameLogic::GameLoop() { - Logger::Info("Game loop started"); + Logger::Trace("GameLogic::GameLoop started"); auto lastUpdate = std::chrono::steady_clock::now(); //while (!instanceMutex_.try_lock()) { while (running_.load()) { diff --git a/src/network/BinaryProtocol.cpp b/src/network/BinaryProtocol.cpp index 2690506..ce10a1d 100644 --- a/src/network/BinaryProtocol.cpp +++ b/src/network/BinaryProtocol.cpp @@ -2,54 +2,46 @@ namespace BinaryProtocol { - // BinaryMessage implementation + NetworkHeader::NetworkHeader(uint16_t type, uint32_t seq, uint8_t ver, uint8_t flgs) + : version(ver), flags(flgs), message_type(type), + sequence(seq), timestamp(0), length(0), checksum(0) {} + std::vector BinaryMessage::Serialize() const { std::vector buffer(sizeof(NetworkHeader) + data.size()); - - // Copy header memcpy(buffer.data(), &header, sizeof(NetworkHeader)); - - // Copy data if (!data.empty()) { memcpy(buffer.data() + sizeof(NetworkHeader), data.data(), data.size()); } - return buffer; } - BinaryMessage BinaryMessage::Deserialize(const uint8_t* buffer, size_t length) { if (length < sizeof(NetworkHeader)) { throw std::runtime_error("Buffer too small for message header"); } - BinaryMessage msg; - - // Read header memcpy(&msg.header, buffer, sizeof(NetworkHeader)); - - // Validate length if (length != sizeof(NetworkHeader) + msg.header.length) { throw std::runtime_error("Message length mismatch"); } - - // Copy data if (msg.header.length > 0) { msg.data.resize(msg.header.length); memcpy(msg.data.data(), buffer + sizeof(NetworkHeader), msg.header.length); } - return msg; } + bool BinaryMessage::IsCompressed() const { return (header.flags & FLAG_COMPRESSED) != 0; } + bool BinaryMessage::IsEncrypted() const { return (header.flags & FLAG_ENCRYPTED) != 0; } + bool BinaryMessage::IsReliable() const { return (header.flags & FLAG_RELIABLE) != 0; } - // BinaryWriter implementation - BinaryWriter::BinaryWriter() { - buffer_.reserve(1024); // Initial capacity - } - void BinaryWriter::WriteUInt8(uint8_t value) { - buffer_.push_back(value); + BinaryWriter::BinaryWriter() { buffer_.reserve(1024); } + + void BinaryWriter::WriteRaw(const uint8_t* data, size_t length) { + buffer_.insert(buffer_.end(), data, data + length); } + void BinaryWriter::WriteUInt8(uint8_t value) { buffer_.push_back(value); } + void BinaryWriter::WriteUInt16(uint16_t value) { buffer_.push_back(static_cast(value >> 8)); buffer_.push_back(static_cast(value & 0xFF)); @@ -116,11 +108,11 @@ namespace BinaryProtocol { WriteString(json_str); } - void BinaryWriter::Clear() { - buffer_.clear(); - } + const std::vector& BinaryWriter::GetBuffer() const { return buffer_; } + size_t BinaryWriter::GetSize() const { return buffer_.size(); } + + void BinaryWriter::Clear() { buffer_.clear(); } - // BinaryReader implementation BinaryReader::BinaryReader(const uint8_t* data, size_t length) : data_(data), length_(length) {} @@ -213,11 +205,13 @@ namespace BinaryProtocol { return nlohmann::json::parse(json_str); } - // Utility functions + size_t BinaryReader::Remaining() const { return length_ - position_; } + bool BinaryReader::CanRead(size_t size) const { return position_ + size <= length_; } + size_t BinaryReader::GetPosition() const { return position_; } + uint32_t CalculateCRC32(const void* data, size_t length) { uint32_t crc = 0xFFFFFFFF; const uint8_t* bytes = static_cast(data); - for (size_t i = 0; i < length; ++i) { crc ^= bytes[i]; for (int j = 0; j < 8; ++j) { @@ -225,46 +219,36 @@ namespace BinaryProtocol { crc = (crc >> 1) ^ (0xEDB88320 & mask); } } - return ~crc; } std::vector CompressData(const std::vector& data, int level) { if (data.empty()) return {}; - uLongf compressed_size = compressBound(data.size()); std::vector compressed(compressed_size); - - if (compress2(compressed.data(), &compressed_size, + if (compress2(compressed.data(), &compressed_size, data.data(), data.size(), level) != Z_OK) { throw std::runtime_error("Compression failed"); } - compressed.resize(compressed_size); return compressed; } std::vector DecompressData(const std::vector& compressed) { if (compressed.empty()) return {}; - - // For simplicity, assume original size is stored in first 4 bytes if (compressed.size() < 4) { throw std::runtime_error("Invalid compressed data"); } - uint32_t original_size = *reinterpret_cast(compressed.data()); std::vector decompressed(original_size); uLongf decompressed_size = original_size; - if (uncompress(decompressed.data(), &decompressed_size, compressed.data() + 4, compressed.size() - 4) != Z_OK) { throw std::runtime_error("Decompression failed"); } - return decompressed; } - // ProtocolCapabilities implementation std::vector ProtocolCapabilities::Serialize() const { BinaryWriter writer; writer.WriteUInt8(version); @@ -272,30 +256,24 @@ namespace BinaryProtocol { writer.WriteUInt8(supports_encryption ? 1 : 0); writer.WriteUInt32(max_message_size); writer.WriteUInt16(static_cast(supported_message_types.size())); - for (uint16_t type : supported_message_types) { writer.WriteUInt16(type); } - return writer.GetBuffer(); } ProtocolCapabilities ProtocolCapabilities::Deserialize(const uint8_t* data, size_t length) { BinaryReader reader(data, length); ProtocolCapabilities caps; - caps.version = reader.ReadUInt8(); caps.supports_compression = reader.ReadUInt8() != 0; caps.supports_encryption = reader.ReadUInt8() != 0; caps.max_message_size = reader.ReadUInt32(); - uint16_t type_count = reader.ReadUInt16(); caps.supported_message_types.reserve(type_count); - for (uint16_t i = 0; i < type_count; ++i) { caps.supported_message_types.push_back(reader.ReadUInt16()); } - return caps; } diff --git a/src/network/BinarySession.cpp b/src/network/BinarySession.cpp index 66cbcbe..5d60e54 100644 --- a/src/network/BinarySession.cpp +++ b/src/network/BinarySession.cpp @@ -1,57 +1,42 @@ #include "game/GameLogic.hpp" #include "network/BinarySession.hpp" -// Static member initialization std::atomic BinarySession::nextSessionId_{1}; -// =============== Constructor and Destructor =============== - BinarySession::BinarySession(asio::ip::tcp::socket socket, - std::shared_ptr ssl_context) + std::shared_ptr ssl_context, uint64_t sessionId) : socket_(std::move(socket)) , ssl_context_(ssl_context) - , sessionId_(nextSessionId_.fetch_add(1)) + , sessionId_(sessionId > 0 ? sessionId : nextSessionId_.fetch_add(1)) , connected_(true) , closing_(false) , heartbeat_timer_(socket_.get_executor()) , shutdown_timer_(socket_.get_executor()) - , network_adaptation_timer_(socket_.get_executor()) { - - // Create SSL stream if context is provided + , network_adaptation_timer_(socket_.get_executor()) +{ if (ssl_context_) { ssl_stream_ = std::make_unique>( std::move(socket_), *ssl_context_); } - - // Set socket options for better performance try { asio::ip::tcp::no_delay no_delay_option(true); GetSocket().set_option(no_delay_option); - asio::socket_base::keep_alive keep_alive_option(true); GetSocket().set_option(keep_alive_option); - asio::socket_base::receive_buffer_size recv_buffer_option(65536); GetSocket().set_option(recv_buffer_option); - asio::socket_base::send_buffer_size send_buffer_option(65536); GetSocket().set_option(send_buffer_option); - asio::socket_base::linger linger_option(true, 30); GetSocket().set_option(linger_option); - - } catch (const std::exception& e) { + } catch (const std::exception& err) { Logger::Warn("Failed to set socket options for session {}: {}", - sessionId_, e.what()); + sessionId_, err.what()); } - connected_time_ = std::chrono::steady_clock::now(); last_heartbeat_ = connected_time_; - - // Initialize rate limiting rate_limit_.tokens = rate_limit_.burst_size; rate_limit_.last_refill = std::chrono::steady_clock::now(); - Logger::Info("BinarySession {} created for {}", sessionId_, GetRemoteEndpoint().address().to_string()); } @@ -78,28 +63,21 @@ std::string BinarySession::GetRemoteAddress() const { NetworkQualityMonitor& BinarySession::GetNetworkMonitor() { return network_monitor_; } -// =============== Core Session Management =============== - void BinarySession::Start() { if (!connected_) { Logger::Warn("Session {} already closed", sessionId_); return; } - Logger::Debug("Starting BinarySession {}", sessionId_); - if (ssl_stream_) { - // Start TLS handshake for encrypted connections StartTLSHandshake(); } else { - // Start protocol negotiation for plain connections StartProtocolNegotiation(); } } void BinarySession::StartTLSHandshake() { if (!ssl_stream_) return; - auto self = shared_from_this(); ssl_stream_->async_handshake(asio::ssl::stream_base::server, [self](std::error_code ec) { @@ -109,28 +87,17 @@ void BinarySession::StartTLSHandshake() { self->Stop(); return; } - Logger::Debug("TLS handshake completed for session {}", self->sessionId_); self->StartProtocolNegotiation(); }); } void BinarySession::StartProtocolNegotiation() { - // Send protocol capabilities to client SendProtocolCapabilities(); - - // Setup default binary handlers SetupDefaultHandlers(); - - // Start reading messages DoBinaryRead(); - - // Start heartbeat monitoring StartHeartbeat(); - - // Start network adaptation monitoring StartNetworkAdaptation(); - Logger::Info("BinarySession {} started", sessionId_); } @@ -211,120 +178,91 @@ asio::ip::tcp::endpoint BinarySession::GetRemoteEndpoint() const { return asio::ip::tcp::endpoint(); } -// =============== Binary Protocol Implementation =============== void BinarySession::DoBinaryRead() { if (!connected_ || closing_) return; - auto self = shared_from_this(); - - // Read message header (fixed size) BinaryProtocol::NetworkHeader header; - asio::async_read(GetSocket(), - asio::buffer(&header, sizeof(BinaryProtocol::NetworkHeader)), - [self, header](std::error_code ec, std::size_t length) mutable { - Logger::Debug("BinarySession::DoBinaryRead asio::async_read length = {}", length); + asio::buffer(&header, sizeof(BinaryProtocol::NetworkHeader)), + [self, header](std::error_code ec, std::size_t length) mutable { + Logger::Debug("BinarySession::DoBinaryRead asio::async_read length = {}", length); + if (ec) { + self->HandleNetworkError(ec); + return; + } + if (header.version > BinaryProtocol::CURRENT_PROTOCOL_VERSION) { + Logger::Warn("Session {}: incompatible protocol version {}", + self->sessionId_, header.version); + self->SendError(BinaryProtocol::MESSAGE_TYPE_ERROR, + "Incompatible protocol version", 400); + self->DoBinaryRead(); + return; + } + if (header.length > BinaryProtocol::MAX_MESSAGE_SIZE) { + Logger::Error("Session {}: message too large: {} bytes", + self->sessionId_, header.length); + self->Stop(); + return; + } + if (header.length == 0) { + BinaryProtocol::BinaryMessage message; + message.header = header; + self->HandleBinaryMessage(message); + self->DoBinaryRead(); + return; + } + auto deadline = std::make_shared(self->GetSocket().get_executor()); + deadline->expires_after(std::chrono::seconds(10)); + deadline->async_wait([self, deadline](std::error_code ec) { + if (!ec && self->connected_) { + Logger::Warn("Session {}: payload read timeout", self->sessionId_); + self->Stop(); + } + }); + std::vector body(header.length); + asio::async_read(self->GetSocket(), + asio::buffer(body), + [self, header, body, deadline](std::error_code ec, std::size_t length) mutable { + deadline->cancel(); if (ec) { self->HandleNetworkError(ec); return; } - - // Validate header - if (header.version > BinaryProtocol::CURRENT_PROTOCOL_VERSION) { - Logger::Warn("Session {}: incompatible protocol version {}", - self->sessionId_, header.version); + Logger::Debug("BinarySession::DoBinaryRead asio::async_read length = {}", length); + uint32_t calculated = BinaryProtocol::CalculateCRC32(body.data(), body.size()); + if (calculated != header.checksum) { + Logger::Error("Session {}: checksum mismatch", self->sessionId_); self->SendError(BinaryProtocol::MESSAGE_TYPE_ERROR, - "Incompatible protocol version", 400); + "Checksum error", 400); self->DoBinaryRead(); return; } - - if (header.length > BinaryProtocol::MAX_MESSAGE_SIZE) { - Logger::Error("Session {}: message too large: {} bytes", - self->sessionId_, header.length); - self->Stop(); - return; + std::vector processed_body = body; + if (header.flags & BinaryProtocol::FLAG_COMPRESSED) { + try { + processed_body = BinaryProtocol::DecompressData(body); + } catch (const std::exception& e) { + Logger::Error("Session {}: decompression failed: {}", + self->sessionId_, e.what()); + self->SendError(BinaryProtocol::MESSAGE_TYPE_ERROR, + "Decompression failed", 400); + self->DoBinaryRead(); + return; + } } - - if (header.length == 0) { - // Empty message, just process header - BinaryProtocol::BinaryMessage message; - message.header = header; - self->HandleBinaryMessage(message); + self->network_monitor_.RecordPacketReceived(header.sequence, processed_body.size()); + BinaryProtocol::BinaryMessage message; + message.header = header; + message.data = processed_body; + self->HandleBinaryMessage(message); + if (header.flags & BinaryProtocol::FLAG_RELIABLE) { + self->SendAcknowledgment(header.sequence); + } + if (self->connected_ && !self->closing_) { self->DoBinaryRead(); - return; } - - // Create deadline timer for payload read - auto deadline = std::make_shared(self->GetSocket().get_executor()); - deadline->expires_after(std::chrono::seconds(10)); - deadline->async_wait([self, deadline](std::error_code ec) { - if (!ec && self->connected_) { - Logger::Warn("Session {}: payload read timeout", self->sessionId_); - self->Stop(); - } - }); - - // Read message body - std::vector body(header.length); - - asio::async_read(self->GetSocket(), - asio::buffer(body), - [self, header, body, deadline](std::error_code ec, std::size_t length) mutable { - deadline->cancel(); // Cancel timeout on successful read or error - if (ec) { - self->HandleNetworkError(ec); - return; - } - - Logger::Debug("BinarySession::DoBinaryRead asio::async_read length = {}", length); - - // Verify checksum - uint32_t calculated = BinaryProtocol::CalculateCRC32(body.data(), body.size()); - if (calculated != header.checksum) { - Logger::Error("Session {}: checksum mismatch", self->sessionId_); - self->SendError(BinaryProtocol::MESSAGE_TYPE_ERROR, - "Checksum error", 400); - self->DoBinaryRead(); - return; - } - - // Decompress if needed - std::vector processed_body = body; - if (header.flags & BinaryProtocol::FLAG_COMPRESSED) { - try { - processed_body = BinaryProtocol::DecompressData(body); - } catch (const std::exception& e) { - Logger::Error("Session {}: decompression failed: {}", - self->sessionId_, e.what()); - self->SendError(BinaryProtocol::MESSAGE_TYPE_ERROR, - "Decompression failed", 400); - self->DoBinaryRead(); - return; - } - } - - // Record for network monitoring - self->network_monitor_.RecordPacketReceived(header.sequence, processed_body.size()); - - // Handle the message - BinaryProtocol::BinaryMessage message; - message.header = header; - message.data = processed_body; - - self->HandleBinaryMessage(message); - - // Send acknowledgment for reliable messages - if (header.flags & BinaryProtocol::FLAG_RELIABLE) { - self->SendAcknowledgment(header.sequence); - } - - // Continue reading - if (self->connected_ && !self->closing_) { - self->DoBinaryRead(); - } - }); }); + }); } void BinarySession::HandleBinaryMessage(const BinaryProtocol::BinaryMessage& message) { @@ -505,44 +443,33 @@ void BinarySession::SendPong() { void BinarySession::DoBinaryWrite() { if (!connected_ || closing_ || write_queue_.empty()) return; - std::lock_guard lock(write_mutex_); if (write_queue_.empty()) return; - const auto& data = write_queue_.front(); - auto self = shared_from_this(); asio::async_write(GetSocket(), - asio::buffer(data), - [self](std::error_code ec, std::size_t length) { - std::lock_guard lock(self->write_mutex_); - - if (ec) { - Logger::Error("Session {} write error: {}", - self->sessionId_, ec.message()); - self->Stop(); - return; - } - - // Record for statistics - self->RecordMessageSent(length); - - // Remove sent message - if (!self->write_queue_.empty()) { - self->write_queue_.pop(); - } - - // Continue writing if more messages - if (!self->write_queue_.empty()) { - self->DoBinaryWrite(); - } - }); + asio::buffer(data), + [self](std::error_code ec, std::size_t length) { + std::lock_guard lock(self->write_mutex_); + if (ec) { + Logger::Error("Session {} write error: {}", + self->sessionId_, ec.message()); + self->Stop(); + return; + } + self->RecordMessageSent(length); + if (!self->write_queue_.empty()) { + self->write_queue_.pop(); + } + if (!self->write_queue_.empty()) { + self->DoBinaryWrite(); + } + }); } void BinarySession::SendAcknowledgment(uint32_t sequence) { BinaryProtocol::BinaryWriter writer; writer.WriteUInt32(sequence); - BinaryProtocol::BinaryMessage ack; ack.header.message_type = BinaryProtocol::MESSAGE_TYPE_SUCCESS; ack.header.sequence = next_sequence_++; @@ -551,12 +478,9 @@ void BinarySession::SendAcknowledgment(uint32_t sequence) { ack.data = writer.GetBuffer(); ack.header.length = static_cast(ack.data.size()); ack.header.checksum = BinaryProtocol::CalculateCRC32(ack.data.data(), ack.data.size()); - auto serialized = ack.Serialize(); - std::lock_guard lock(write_mutex_); write_queue_.push(serialized); - if (write_queue_.size() == 1) { DoBinaryWrite(); } @@ -564,35 +488,25 @@ void BinarySession::SendAcknowledgment(uint32_t sequence) { void BinarySession::ProcessAcknowledgment(uint32_t sequence) { std::lock_guard lock(ack_mutex_); - auto it = pending_acks_.find(sequence); if (it != pending_acks_.end()) { auto now = std::chrono::steady_clock::now(); auto rtt = std::chrono::duration_cast( now - it->second).count(); - - // Record RTT for network monitoring network_monitor_.RecordAcknowledgment(sequence, static_cast(rtt)); - - // Remove from pending acks pending_acks_.erase(it); } } -// =============== Protocol Negotiation =============== - void BinarySession::SendProtocolCapabilities() { BinaryProtocol::ProtocolCapabilities caps; caps.version = BinaryProtocol::CURRENT_PROTOCOL_VERSION; caps.supports_compression = true; caps.supports_encryption = IsEncrypted(); caps.max_message_size = BinaryProtocol::MAX_MESSAGE_SIZE; - - // Add all supported message types for (int type = 1; type <= 1000; ++type) { caps.supported_message_types.push_back(static_cast(type)); } - auto caps_data = caps.Serialize(); Send(BinaryProtocol::MESSAGE_TYPE_PROTOCOL_NEGOTIATION, caps_data); } @@ -601,27 +515,19 @@ void BinarySession::HandleProtocolNegotiation(const std::vector& data) try { auto client_caps = BinaryProtocol::ProtocolCapabilities::Deserialize( data.data(), data.size()); - Logger::Debug("Session {}: client protocol capabilities: version={}, compression={}, encryption={}", sessionId_, client_caps.version, client_caps.supports_compression, client_caps.supports_encryption); - - // Enable compression if both support it if (client_caps.supports_compression) { SetCompressionEnabled(true); Logger::Debug("Session {}: compression enabled", sessionId_); } - protocol_negotiated_ = true; - - // Send negotiation success BinaryProtocol::BinaryWriter writer; - writer.WriteUInt8(1); // success + writer.WriteUInt8(1); writer.WriteString("Protocol negotiation successful"); - Send(BinaryProtocol::MESSAGE_TYPE_SUCCESS, writer.GetBuffer()); - } catch (const std::exception& e) { Logger::Error("Session {}: protocol negotiation failed: {}", sessionId_, e.what()); @@ -630,92 +536,65 @@ void BinarySession::HandleProtocolNegotiation(const std::vector& data) } } -// =============== Heartbeat Management =============== - void BinarySession::StartHeartbeat() { if (!connected_ || closing_) return; - - // Set initial heartbeat time last_heartbeat_ = std::chrono::steady_clock::now(); - - // Start heartbeat check CheckHeartbeat(); } void BinarySession::CheckHeartbeat() { if (!connected_ || closing_) return; - heartbeat_timer_.expires_after(std::chrono::seconds(30)); heartbeat_timer_.async_wait( - [self = shared_from_this()](std::error_code ec) { - if (ec == asio::error::operation_aborted) { - return; - } - - if (!self->connected_ || self->closing_) { - return; - } - - // Check time since last heartbeat - auto now = std::chrono::steady_clock::now(); - auto elapsed = std::chrono::duration_cast( - now - self->last_heartbeat_); - - if (elapsed.count() > 60) { // 60 second timeout - Logger::Warn("Session {} heartbeat timeout ({} seconds)", - self->sessionId_, elapsed.count()); - self->Stop(); - return; - } - - // Send heartbeat ping if no activity for 30 seconds - if (elapsed.count() > 30) { - self->SendPing(); - } - - // Schedule next heartbeat check - self->CheckHeartbeat(); - }); + [self = shared_from_this()](std::error_code ec) { + if (ec == asio::error::operation_aborted) { + return; + } + if (!self->connected_ || self->closing_) { + return; + } + auto now = std::chrono::steady_clock::now(); + auto elapsed = std::chrono::duration_cast( + now - self->last_heartbeat_); + if (elapsed.count() > 60) { // 60 second timeout + Logger::Warn("Session {} heartbeat timeout ({} seconds)", + self->sessionId_, elapsed.count()); + self->Stop(); + return; + } + if (elapsed.count() > 30) { + self->SendPing(); + } + self->CheckHeartbeat(); + }); } void BinarySession::UpdateHeartbeat() { last_heartbeat_ = std::chrono::steady_clock::now(); } -// =============== Network Quality Monitoring =============== - void BinarySession::StartNetworkAdaptation() { auto self = shared_from_this(); - network_adaptation_timer_.expires_after(std::chrono::seconds(10)); network_adaptation_timer_.async_wait([self](std::error_code ec) { if (ec) return; - self->CheckNetworkConditions(); self->StartNetworkAdaptation(); }); } void BinarySession::CheckNetworkConditions() { - // Update network monitor statistics network_monitor_.Update(); - auto metrics = network_monitor_.GetMetrics(); auto quality = network_monitor_.GetConnectionQuality(); - - // Adapt compression bool should_compress = network_monitor_.ShouldEnableCompression(); if (should_compress != compression_enabled_) { SetCompressionEnabled(should_compress); Logger::Debug("Session {}: compression {}", sessionId_, should_compress ? "enabled" : "disabled"); } - - // Adapt update rate based on connection quality uint32_t optimal_rate = network_monitor_.CalculateOptimalUpdateRate(); Logger::Debug("Session optimal_rate: {}",optimal_rate); - - // Log connection quality changes static auto last_quality = NetworkQualityMonitor::ConnectionQuality::EXCELLENT; if (quality != last_quality) { const char* quality_names[] = {"EXCELLENT", "GOOD", "FAIR", "POOR", "UNSTABLE"}; @@ -725,12 +604,8 @@ void BinarySession::CheckNetworkConditions() { quality_names[static_cast(quality)]); last_quality = quality; } - - // Send network metrics to client for adaptation if (quality == NetworkQualityMonitor::ConnectionQuality::UNSTABLE) { Logger::Warn("Session {}: unstable connection detected", sessionId_); - - // Send warning to client nlohmann::json warning = { {"msg", "network_warning"}, {"desc", "Unstable network connection detected"}, @@ -750,8 +625,6 @@ void BinarySession::AdaptToNetworkConditions() { CheckNetworkConditions(); } -// =============== Binary Message Handlers =============== - void BinarySession::SetBinaryMessageHandler(uint16_t message_type, BinaryMessageHandler handler) { std::lock_guard lock(binary_handlers_mutex_); binary_handlers_[message_type] = std::move(handler); @@ -770,8 +643,6 @@ void BinarySession::SetCloseHandler(std::function handler) { close_handler_ = std::move(handler); } -// =============== Session Statistics =============== - SessionStats BinarySession::GetStats() const { std::lock_guard lock(stats_mutex_); return stats_; @@ -797,13 +668,9 @@ void BinarySession::RecordMessageSent(size_t size) { stats_.messages_sent++; stats_.bytes_sent += size; stats_.last_message_sent = std::chrono::steady_clock::now(); - - // Also record for network monitoring network_monitor_.RecordBytesSent(size); } -// =============== Compression =============== - void BinarySession::SetCompressionEnabled(bool enabled) { compression_enabled_ = enabled; } @@ -812,8 +679,6 @@ bool BinarySession::IsCompressionEnabled() const { return compression_enabled_; } -// =============== Rate Limiting =============== - void BinarySession::SetRateLimit(int messages_per_second, int burst_size) { std::lock_guard lock(rate_limit_mutex_); rate_limit_.messages_per_second = messages_per_second; @@ -826,33 +691,24 @@ bool BinarySession::CheckRateLimit() { if (!rate_limit_enabled_) { return true; } - std::lock_guard lock(rate_limit_mutex_); - auto now = std::chrono::steady_clock::now(); auto elapsed = std::chrono::duration_cast( now - rate_limit_.last_refill); - - // Refill tokens based on elapsed time int refill = (elapsed.count() * rate_limit_.messages_per_second) / 1000; if (refill > 0) { rate_limit_.tokens = std::min(rate_limit_.burst_size, rate_limit_.tokens + refill); rate_limit_.last_refill = now; } - - // Check if we have tokens if (rate_limit_.tokens > 0) { rate_limit_.tokens--; return true; } - - // Rate limit exceeded { std::lock_guard stats_lock(stats_mutex_); stats_.rate_limit_exceeded++; } - return false; } @@ -860,8 +716,6 @@ void BinarySession::SetRateLimitEnabled(bool enabled) { rate_limit_enabled_ = enabled; } -// =============== Session Groups =============== - void BinarySession::JoinGroup(const std::string& groupId) { std::lock_guard lock(groups_mutex_); joined_groups_.insert(groupId); @@ -887,14 +741,11 @@ bool BinarySession::IsInGroup(const std::string& groupId) const { return joined_groups_.find(groupId) != joined_groups_.end(); } -// =============== Authentication and Security =============== - void BinarySession::Authenticate(const std::string& authToken) { std::lock_guard lock(auth_mutex_); auth_token_ = authToken; authenticated_ = true; authentication_time_ = std::chrono::steady_clock::now(); - Logger::Info("Session {} authenticated", sessionId_); } @@ -903,7 +754,6 @@ void BinarySession::Deauthenticate() { auth_token_.clear(); authenticated_ = false; player_id_ = 0; - Logger::Info("Session {} deauthenticated", sessionId_); } @@ -929,8 +779,6 @@ int64_t BinarySession::GetPlayerId() const { return player_id_; } -// =============== Session Data Storage =============== - void BinarySession::SetData(const std::string& key, const nlohmann::json& value) { std::lock_guard lock(data_mutex_); session_data_[key] = value; @@ -969,8 +817,6 @@ nlohmann::json BinarySession::GetAllData() const { return result; } -// =============== Session Properties =============== - void BinarySession::SetProperty(const std::string& key, const std::string& value) { std::lock_guard lock(properties_mutex_); properties_[key] = value; @@ -990,16 +836,11 @@ std::map BinarySession::GetAllProperties() const { return properties_; } -// =============== Metrics and Monitoring =============== - SessionMetrics BinarySession::GetMetrics() const { auto now = std::chrono::steady_clock::now(); auto connected_time = std::chrono::duration_cast( now - connected_time_); - SessionMetrics metrics; - - // Get stats with lock { std::lock_guard lock(stats_mutex_); metrics.messages_received = stats_.messages_received; @@ -1008,13 +849,11 @@ SessionMetrics BinarySession::GetMetrics() const { metrics.bytes_sent = stats_.bytes_sent; metrics.rate_limit_exceeded = stats_.rate_limit_exceeded; } - metrics.session_id = sessionId_; metrics.connected_time_seconds = connected_time.count(); metrics.is_connected = IsConnected(); metrics.is_authenticated = IsAuthenticated(); metrics.player_id = GetPlayerId(); - try { auto endpoint = GetRemoteEndpoint(); metrics.remote_endpoint = endpoint.address().to_string() + ":" + @@ -1022,27 +861,19 @@ SessionMetrics BinarySession::GetMetrics() const { } catch (...) { metrics.remote_endpoint = "unknown"; } - - // Calculate message rates if (connected_time.count() > 0) { metrics.receive_rate = static_cast(metrics.messages_received) / connected_time.count(); metrics.send_rate = static_cast(metrics.messages_sent) / connected_time.count(); } - - // Group membership metrics.joined_groups = GetJoinedGroups().size(); - - // Network quality metrics auto network_metrics = network_monitor_.GetMetrics(); metrics.average_latency = network_metrics.average_latency_ms; metrics.packet_loss = network_metrics.packet_loss_percent; - return metrics; } void BinarySession::PrintMetrics() const { auto metrics = GetMetrics(); - Logger::Info("Session {} Metrics:", metrics.session_id); Logger::Info(" Remote Endpoint: {}", metrics.remote_endpoint); Logger::Info(" Connected Time: {} seconds", metrics.connected_time_seconds); @@ -1062,27 +893,21 @@ void BinarySession::PrintMetrics() const { metrics.average_latency, metrics.packet_loss); } -// =============== Utility Methods =============== - std::string BinarySession::ToString() const { std::stringstream ss; ss << "BinarySession[" << sessionId_ << "] "; - try { auto endpoint = GetRemoteEndpoint(); ss << endpoint.address().to_string() << ":" << endpoint.port(); } catch (...) { ss << "unknown endpoint"; } - if (IsAuthenticated()) { ss << " (Player: " << GetPlayerId() << ")"; } - if (ssl_stream_) { ss << " [TLS]"; } - return ss.str(); } @@ -1090,16 +915,12 @@ uint64_t BinarySession::GetUptimeSeconds() const { if (!connected_) { return 0; } - auto now = std::chrono::steady_clock::now(); auto uptime = std::chrono::duration_cast( now - connected_time_); - return uptime.count(); } -// =============== Connection Quality Monitoring =============== - void BinarySession::RecordLatency(uint64_t latencyMs) { network_monitor_.RecordLatencySample(latencyMs); } @@ -1120,13 +941,9 @@ uint64_t BinarySession::GetMaxLatency() const { } std::vector BinarySession::GetLatencySamples() const { - // Note: NetworkQualityMonitor doesn't expose samples directly - // In a full implementation, we would add this method return {}; } -// =============== Custom Event Handlers =============== - void BinarySession::SetCustomEventHandler(const std::string& eventName, std::function handler) { std::lock_guard lock(event_handlers_mutex_); @@ -1144,14 +961,12 @@ void BinarySession::HandleCustomEvent(const std::string& eventName, const nlohma if (it != custom_event_handlers_.end()) { try { it->second(data); - } catch (const std::exception& e) { - Logger::Error("Error in custom event handler '{}': {}", eventName, e.what()); + } catch (const std::exception& err) { + Logger::Error("Error in custom event handler '{}': {}", eventName, err.what()); } } } -// =============== Message Queue Management =============== - size_t BinarySession::GetPendingMessageCount() const { std::lock_guard lock(write_mutex_); return write_queue_.size(); @@ -1172,86 +987,64 @@ void BinarySession::SetMaxWriteQueueSize(size_t maxSize) { max_write_queue_size_ = maxSize; } -// =============== World and Entity Methods =============== - void BinarySession::SendWorldChunkBinary(int chunkX, int chunkZ, const std::vector& chunkData) { BinaryProtocol::BinaryWriter writer; writer.WriteInt32(chunkX); writer.WriteInt32(chunkZ); - - // Combine metadata with chunk data auto metadata = writer.GetBuffer(); std::vector combined_data(metadata.size() + chunkData.size()); - std::copy(metadata.begin(), metadata.end(), combined_data.begin()); std::copy(chunkData.begin(), chunkData.end(), combined_data.begin() + metadata.size()); - Send(BinaryProtocol::MESSAGE_TYPE_CHUNK_DATA, combined_data); } void BinarySession::SendEntityUpdateBinary(uint64_t entityId, const std::vector& entityData) { BinaryProtocol::BinaryWriter writer; writer.WriteUInt64(entityId); - - // Combine entity ID with entity data auto id_data = writer.GetBuffer(); std::vector combined_data(id_data.size() + entityData.size()); - std::copy(id_data.begin(), id_data.end(), combined_data.begin()); std::copy(entityData.begin(), entityData.end(), combined_data.begin() + id_data.size()); - Send(BinaryProtocol::MESSAGE_TYPE_ENTITY_UPDATE, combined_data); } void BinarySession::SendEntitySpawnBinary(uint64_t entityId, const std::vector& spawnData) { BinaryProtocol::BinaryWriter writer; writer.WriteUInt64(entityId); - auto id_data = writer.GetBuffer(); std::vector combined_data(id_data.size() + spawnData.size()); - std::copy(id_data.begin(), id_data.end(), combined_data.begin()); std::copy(spawnData.begin(), spawnData.end(), combined_data.begin() + id_data.size()); - Send(BinaryProtocol::MESSAGE_TYPE_ENTITY_SPAWN, combined_data); } void BinarySession::SendEntityDespawnBinary(uint64_t entityId) { BinaryProtocol::BinaryWriter writer; writer.WriteUInt64(entityId); - Send(BinaryProtocol::MESSAGE_TYPE_ENTITY_DESPAWN, writer.GetBuffer()); } -// =============== Player State Synchronization =============== - void BinarySession::SyncPlayerStateBinary(const glm::vec3& position, const glm::vec3& rotation, const glm::vec3& velocity, uint32_t last_input_id) { BinaryProtocol::BinaryWriter writer; - writer.WriteVector3(position); writer.WriteVector3(rotation); writer.WriteVector3(velocity); writer.WriteUInt32(last_input_id); writer.WriteUInt64(std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch()).count()); - Send(BinaryProtocol::MESSAGE_TYPE_PLAYER_STATE, writer.GetBuffer()); } void BinarySession::SendPositionCorrection(const glm::vec3& position, const glm::vec3& velocity) { BinaryProtocol::BinaryWriter writer; - writer.WriteVector3(position); writer.WriteVector3(velocity); writer.WriteUInt64(std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch()).count()); - Send(BinaryProtocol::MESSAGE_TYPE_PLAYER_POSITION_CORRECTION, writer.GetBuffer()); } -// =============== Private Helper Methods =============== - asio::ip::tcp::socket& BinarySession::GetSocket() { if (ssl_stream_) { return ssl_stream_->next_layer(); @@ -1281,31 +1074,20 @@ void BinarySession::HandleMessage(const std::string& message) { if (message.empty()) { return; } - try { - // Parse JSON message auto json_message = nlohmann::json::parse(message); - - // Update heartbeat on any valid message UpdateHeartbeat(); - - // Handle the message if (message_handler_) { message_handler_(json_message); } else { Logger::Warn("No message handler set for session {}", sessionId_); } - - } catch (const nlohmann::json::parse_error& e) { + } catch (const nlohmann::json::parse_error& err) { Logger::Error("Session {} JSON parse error: {} - Message: {}", - sessionId_, e.what(), message); - - // Send error response for malformed JSON + sessionId_, err.what(), message); SendError(BinaryProtocol::MESSAGE_TYPE_ERROR, "Invalid JSON format", 400); - - } catch (const std::exception& e) { - Logger::Error("Session {} message handling error: {}", - sessionId_, e.what()); + } catch (const std::exception& err) { + Logger::Error("Session {} message handling error: {}", sessionId_, err.what()); } } @@ -1319,17 +1101,17 @@ void BinarySession::SetPlayerStateHandler(std::function& data) { - (void)type; - try { - ClientInput input = ClientInput::Deserialize(data.data(), data.size()); - if (player_state_handler_) { - player_state_handler_(input); - } else { - Logger::Warn("Session {}: no player state handler registered", sessionId_); - } - } catch (const std::exception& e) { - Logger::Error("Session {}: failed to deserialize player state: {}", sessionId_, e.what()); + [this](uint16_t type, const std::vector& data) { + (void)type; + try { + ClientInput input = ClientInput::Deserialize(data.data(), data.size()); + if (player_state_handler_) { + player_state_handler_(input); + } else { + Logger::Warn("Session {}: no player state handler registered", sessionId_); } - }); + } catch (const std::exception& err) { + Logger::Error("Session {}: failed to deserialize player state: {}", sessionId_, err.what()); + } + }); } diff --git a/src/network/ClientListener.cpp b/src/network/ClientListener.cpp index e246fd6..ef88cc7 100644 --- a/src/network/ClientListener.cpp +++ b/src/network/ClientListener.cpp @@ -1,12 +1,13 @@ #include "network/ClientListener.hpp" -ClientListener::ClientListener(const WorkerGroupConfig& groupConfig, int masterFd) - : io_() +ClientListener::ClientListener(const WorkerGroupConfig& groupConfig, int masterFd, int workerId) + : workerId_(workerId) + , io_() , pipe_(io_) , manager_(std::make_shared(groupConfig, [this](uint32_t corrId, uint64_t sessId, uint16_t msgType, const std::vector& body) { sendToMaster(corrId, sessId, msgType, body); - })) + }, workerId)) { if (masterFd != -1) pipe_.assign(masterFd); } @@ -16,13 +17,18 @@ ClientListener::~ClientListener() { Shutdown(); } void ClientListener::Start() { std::thread([this]() { manager_->Start(); }).detach(); doRead(); - ioThread_ = std::thread([this]() { io_.run(); }); + ioThread_ = std::thread([this]() { + Logger::Trace("ClientListener::Start: worker {} entering io_context::run()", workerId_); + io_.run(); + Logger::Trace("ClientListener::Start: worker {} exiting io_context::run()", workerId_); + }); } void ClientListener::Shutdown() { if (stopping_.exchange(true)) return; manager_->Shutdown(); io_.stop(); + io_.poll(); if (ioThread_.joinable()) ioThread_.join(); } @@ -31,12 +37,19 @@ void ClientListener::sendToMaster(uint32_t correlationId, uint64_t sessionId, ui w.WriteUInt32(correlationId); w.WriteUInt64(sessionId); w.WriteUInt16(messageType); - w.WriteUInt32(static_cast(body.size())); - w.WriteBytes(body.data(), body.size()); + uint32_t len = static_cast(body.size()); + w.WriteUInt32(len); + w.WriteRaw(body.data(), len); auto frame = w.GetBuffer(); + uint32_t frameLen = htonl(static_cast(frame.size())); + std::vector packet(sizeof(frameLen) + frame.size()); + memcpy(packet.data(), &frameLen, sizeof(frameLen)); + memcpy(packet.data() + sizeof(frameLen), frame.data(), frame.size()); + Logger::Trace("Worker sending to master: corrId={}, sessId={}, msgType={}, bodySize={}", + correlationId, sessionId, messageType, body.size()); { std::lock_guard lock(writeMutex_); - writeQueue_.push_back(std::move(frame)); + writeQueue_.push_back(std::move(packet)); if (!writing_) { writing_ = true; startWrite(); @@ -60,31 +73,56 @@ void ClientListener::doWrite() { writeQueue_.pop_front(); } asio::async_write(pipe_, asio::buffer(data), - [this](std::error_code ec, size_t) { - if (!ec) doWrite(); - }); + [this](std::error_code ec, size_t) { + if (ec) + Logger::Error("Worker write to master failed: {}", ec.message()); + else + doWrite(); + }); } void ClientListener::doRead() { - auto header = std::make_shared>(14); - asio::async_read(pipe_, asio::buffer(*header), - [this, header](std::error_code ec, size_t) { + auto lengthBuf = std::make_shared(0); + asio::async_read(pipe_, asio::buffer(lengthBuf.get(), sizeof(uint32_t)), + [this, lengthBuf](std::error_code ec, size_t) { + if (ec) { + Logger::Error("ClientListener::doRead asio::async_read: {}", ec.message()); + if (!stopping_) doRead(); + return; + } + uint32_t msgLen = ntohl(*lengthBuf); + if (msgLen == 0 || msgLen > 10 * 1024 * 1024) { + doRead(); + return; + } + auto msgBuffer = std::make_shared>(msgLen); + asio::async_read(pipe_, asio::buffer(*msgBuffer), + [this, msgBuffer](std::error_code ec, size_t) { if (ec) { + Logger::Error("ClientListener::doRead asio::async_read(msgBuffer): {}", ec.message()); if (!stopping_) doRead(); return; } - BinaryProtocol::BinaryReader r(header->data(), header->size()); - uint32_t corrId = r.ReadUInt32(); - uint64_t sessionId = r.ReadUInt64(); - uint16_t msgType = r.ReadUInt16(); - uint32_t bodyLen = r.ReadUInt32(); - auto body = std::make_shared>(bodyLen); - asio::async_read(pipe_, asio::buffer(*body), - [this, corrId, sessionId, msgType, body](std::error_code ec, size_t) { - if (!ec) { - manager_->OnMasterReply(corrId, *body); - } - doRead(); - }); + if (msgBuffer->size() < 18) { + doRead(); + return; + } + BinaryProtocol::BinaryReader r(msgBuffer->data(), msgBuffer->size()); + uint32_t corrId = r.ReadUInt32(); + uint64_t sessionId = r.ReadUInt64();(void)sessionId; + uint16_t msgType = r.ReadUInt16();(void)msgType; + uint32_t bodyLen = r.ReadUInt32(); + std::vector body; + if (bodyLen > 0 && r.Remaining() >= bodyLen) { + body = r.ReadBytes(bodyLen); + } + if (corrId == 0) { + manager_->OnMasterPush(sessionId, body); + doRead(); + return; + } + manager_->OnMasterReply(corrId, body); + doRead(); }); + }); } diff --git a/src/network/ConnectionManager.cpp b/src/network/ConnectionManager.cpp index f3dac7a..0de35a7 100644 --- a/src/network/ConnectionManager.cpp +++ b/src/network/ConnectionManager.cpp @@ -1,6 +1,6 @@ #include "network/ConnectionManager.hpp" -ConnectionManager::ConnectionManager(const WorkerGroupConfig& groupConfig, MasterSender masterSender) +ConnectionManager::ConnectionManager(const WorkerGroupConfig& groupConfig, MasterSender masterSender, int workerId) : ioContext_(groupConfig.threads) , acceptor_(ioContext_) , masterSender_(std::move(masterSender)) @@ -8,6 +8,7 @@ ConnectionManager::ConnectionManager(const WorkerGroupConfig& groupConfig, Maste , host_(groupConfig.host) , port_(groupConfig.port) , reuse_(groupConfig.reuse) + , workerId_(workerId) { if (groupConfig.ssl.has_value()) { sslContext_ = std::make_shared(asio::ssl::context::tls_server); @@ -31,22 +32,25 @@ bool ConnectionManager::Start() { } acceptor_.bind(endpoint); acceptor_.listen(1000); - running_ = true; + running_.store(true); doAccept(); for (int i = 0; i < groupConfig_.threads - 1; ++i) workerThreads_.emplace_back([this]() { ioContext_.run(); }); Logger::Info("ConnectionManager started on {}:{}", host_, port_); ioContext_.run(); - } catch (const std::exception& e) { - Logger::Error("ConnectionManager start failed: {}", e.what()); + } catch (const std::exception& err) { + Logger::Error("ConnectionManager start failed: {}", err.what()); return false; } return true; } void ConnectionManager::Shutdown() { - if (!running_) return; - running_ = false; + if (!running_.exchange(false)) + { + Logger::Trace("ConnectionManager::Shutdown already executed, wait finish..."); + return; + } acceptor_.close(); std::vector> sessions; { @@ -54,16 +58,20 @@ void ConnectionManager::Shutdown() { for (auto& [id, s] : sessions_) sessions.push_back(s); sessions_.clear(); } - for (auto& s : sessions) s->Stop(); + for (auto& s : sessions) + s->Stop(); ioContext_.stop(); + ioContext_.poll(); for (auto& t : workerThreads_) if (t.joinable()) t.join(); - Logger::Info("ConnectionManager shut down"); + Logger::Trace("ConnectionManager::Shutdown complete"); } -void ConnectionManager::initSessionFactory() { +void ConnectionManager::initSessionFactory() +{ if (groupConfig_.protocol == "binary") { sessionFactory_ = [this](asio::ip::tcp::socket socket, std::shared_ptr sslCtx) -> std::shared_ptr { - auto session = std::make_shared(std::move(socket), sslCtx); + uint64_t unique_session_id = (static_cast(workerId_) << 48) | nextLocalSid_++; + auto session = std::make_shared(std::move(socket), sslCtx, unique_session_id); uint64_t id = session->GetSessionId(); { std::unique_lock lock(sessionsMutex_); @@ -77,8 +85,9 @@ void ConnectionManager::initSessionFactory() { }; } else if (groupConfig_.protocol == "websocket") { webSocketFactory_ = [this](asio::ip::tcp::socket socket, std::shared_ptr) -> std::shared_ptr { + uint64_t unique_session_id = (static_cast(workerId_) << 48) | nextLocalSid_++; auto wsConn = std::make_shared(std::move(socket)); - auto session = std::make_shared(wsConn); + auto session = std::make_shared(wsConn, unique_session_id); uint64_t id = session->GetSessionId(); { std::unique_lock lock(sessionsMutex_); @@ -96,6 +105,14 @@ void ConnectionManager::initSessionFactory() { body = w.GetBuffer(); break; } + case BinaryProtocol::MESSAGE_TYPE_CHAT_MESSAGE: { + BinaryProtocol::BinaryWriter w; + w.WriteString(msg.value("sender", "")); + w.WriteString(msg.value("message", "")); + w.WriteUInt64(msg.value("timestamp", 0ULL)); + body = w.GetBuffer(); + break; + } case BinaryProtocol::MESSAGE_TYPE_CHUNK_DATA: { BinaryProtocol::BinaryWriter w; w.WriteInt32(msg.value("x",0)); @@ -180,7 +197,14 @@ void ConnectionManager::initSessionFactory() { void ConnectionManager::doAccept() { acceptor_.async_accept([this](std::error_code ec, asio::ip::tcp::socket socket) { - if (!ec) { + if (ec == asio::error::operation_aborted){ + Logger::Warn("ConnectionManager::doAccept: {}", ec.message()); + return; + } + else if (ec) + Logger::Error("ConnectionManager::doAccept: {}", ec.message()); + else + { if (groupConfig_.tcp_nodelay) { asio::ip::tcp::no_delay option(true); socket.set_option(option); @@ -193,12 +217,17 @@ void ConnectionManager::doAccept() { asio::socket_base::receive_buffer_size option(groupConfig_.receive_buffer_size); socket.set_option(option); } + std::shared_ptr session; if (groupConfig_.protocol == "binary" && sessionFactory_) - sessionFactory_(std::move(socket), sslContext_); + session = sessionFactory_(std::move(socket), sslContext_); else if (groupConfig_.protocol == "websocket" && webSocketFactory_) - webSocketFactory_(std::move(socket), sslContext_); + session = webSocketFactory_(std::move(socket), sslContext_); + if (session) { + session->Start(); + } } - if (running_) doAccept(); + if (running_.load()) + doAccept(); }); } @@ -229,7 +258,7 @@ void ConnectionManager::OnMasterReply(uint32_t correlationId, const std::vector< if (sIt != sessions_.end()) session = sIt->second; } if (!session) return; - if (groupConfig_.protocol == "binary") { + if (session->GetProtocolMode() == ProtocolMode::Binary) { session->SendRaw(std::string(reply.begin(), reply.end())); if (entry.messageType == BinaryProtocol::MESSAGE_TYPE_AUTHENTICATION && reply.size() >= 16) { BinaryProtocol::BinaryReader r(reply.data(), reply.size()); @@ -261,6 +290,7 @@ void ConnectionManager::OnMasterReply(uint32_t correlationId, const std::vector< uint16_t ConnectionManager::jsonMsgType(const std::string& msg) { static const std::unordered_map map = { {"authentication", BinaryProtocol::MESSAGE_TYPE_AUTHENTICATION}, + {"chat_message", BinaryProtocol::MESSAGE_TYPE_CHAT_MESSAGE}, {"chunk_params", BinaryProtocol::MESSAGE_TYPE_CHUNK_PARAMS}, {"get_chunk", BinaryProtocol::MESSAGE_TYPE_CHUNK_DATA}, {"collision", BinaryProtocol::MESSAGE_TYPE_COLLISION_CHECK}, @@ -285,6 +315,12 @@ nlohmann::json ConnectionManager::binaryToJson(uint16_t type, const std::vector< std::string message = r.ReadString(); return {{"msg","authentication"},{"timestamp",timestamp},{"desc",message},{"player_id",player_id}}; } + case BinaryProtocol::MESSAGE_TYPE_CHAT_MESSAGE: { + std::string sender = r.ReadString(); + std::string message = r.ReadString(); + uint64_t timestamp = r.ReadUInt64(); + return {{"msg","chat_message"},{"sender",sender},{"message",message},{"timestamp",timestamp}}; + } case BinaryProtocol::MESSAGE_TYPE_CHUNK_PARAMS: { uint64_t timestamp = r.ReadUInt64(); uint32_t size = r.ReadUInt32(); @@ -389,6 +425,54 @@ nlohmann::json ConnectionManager::binaryToJson(uint16_t type, const std::vector< return {{"msg","inventory"},{"timestamp",timestamp},{"loot_id",loot_id},{"target_id",target_id},{"move_type",move_type},{"inv_slot_id",inv_slot},{"use_slot_id",use_slot},{"quantity",quantity}}; } default: - return {{"msg","error"},{"desc","Unknown binary message type"}}; + return {{"msg","error"},{"desc","Unknown binary message type " + std::to_string(type)}};//0x7b22=31522 + } +} + +void ConnectionManager::OnMasterPush(uint64_t sessionId, const std::vector& data) { + Logger::Trace("ConnectionManager::OnMasterPush: sessionId={}, dataSize={}", sessionId, data.size()); + if (data.size() >= 4) { + Logger::Trace("First 4 bytes: {:02x} {:02x} {:02x} {:02x}", + data[0], data[1], data[2], data[3]); + } + if (sessionId == 0) {// Broadcast to all sessions on this worker + std::shared_lock lock(sessionsMutex_); + for (auto& [id, session] : sessions_) { + if (session->GetProtocolMode() == ProtocolMode::Binary) { + session->SendRaw(std::string(data.begin(), data.end())); + } else { + if (data.size() >= 2) { + BinaryProtocol::BinaryReader r(data.data(), data.size()); + uint16_t type = r.ReadUInt16(); + Logger::Trace("Parsed binary message type: {}", type); + std::vector body(data.begin() + 2, data.end()); + session->SendJson(binaryToJson(type, body)); + } + } + } + } else { + std::shared_ptr session; + { + std::shared_lock lock(sessionsMutex_); + auto it = sessions_.find(sessionId); + if (it != sessions_.end()) + session = it->second; + } + if (!session) + Logger::Warn("ConnectionManager::OnMasterPush: session for ID {} not exists", sessionId); + else + { + if (session->GetProtocolMode() == ProtocolMode::Binary) { + session->SendRaw(std::string(data.begin(), data.end())); + } else { + if (data.size() >= 2) { + BinaryProtocol::BinaryReader r(data.data(), data.size()); + uint16_t type = r.ReadUInt16(); + std::vector body(data.begin() + 2, data.end()); + session->SendJson(binaryToJson(type, body)); + } + } + } + Logger::Trace("ConnectionManager::OnMasterPush: Sending to session {}", sessionId); } } diff --git a/src/network/MasterServer.cpp b/src/network/MasterServer.cpp index 736a807..16c8cd5 100644 --- a/src/network/MasterServer.cpp +++ b/src/network/MasterServer.cpp @@ -12,25 +12,8 @@ MasterServer::MasterServer(asio::io_context& io, const std::vector uint64_t { - auto it = sessionToVirtual_.find(sessionId); - if (it == sessionToVirtual_.end()) { - uint64_t newId = (static_cast(workerId) << 32) | (nextPersistentId_++); - sessionToVirtual_[sessionId] = newId; - return newId; - } - return it->second; - }; - sendReplyCb_ = [this](uint64_t clientSessionId, const std::vector& data) { - auto it = sessionToVirtual_.find(clientSessionId); - if (it != sessionToVirtual_.end()) { - uint64_t virtualId = it->second; - int workerId = static_cast(virtualId >> 32); - uint32_t corrId = static_cast(virtualId & 0xFFFFFFFF); - processPool_.SendReplyToWorker(workerId, corrId, data); - } else { - Logger::Error("No virtual mapping for session {}", clientSessionId); - } + sendReplyCb_ = [this](uint64_t sessionId, const std::vector& data) { + processPool_.PushToWorker(static_cast(sessionId >> 48), sessionId, data); }; gameLogic_.SetSendReplyCallback(sendReplyCb_); gameLogic_.SetGetSessionIdsInRadiusCallback([](const glm::vec3& pos, float radius) -> std::vector { @@ -43,11 +26,8 @@ void MasterServer::Initialize() } return sids; }); - processPool_.SetMasterMessageHandler([this](int workerId, uint32_t /*correlationId*/, - uint64_t sessionId, uint16_t messageType, - const std::vector& body) { - uint64_t virtualId = assignVirtualId_(sessionId, workerId); - sessionToVirtual_[sessionId] = virtualId; + processPool_.SetMasterMessageHandler([this](int /*workerId*/, uint32_t /*correlationId*/, + uint64_t sessionId, uint16_t messageType, const std::vector& body) { switch (messageType) { case BinaryProtocol::MESSAGE_TYPE_AUTHENTICATION: { BinaryProtocol::BinaryReader r(body.data(), body.size()); @@ -58,6 +38,21 @@ void MasterServer::Initialize() gameLogic_.OnAuthentication(auth); break; } + case BinaryProtocol::MESSAGE_TYPE_CHAT_MESSAGE: { + BinaryProtocol::BinaryReader r(body.data(), body.size()); + std::string sender = r.ReadString(); + std::string message = r.ReadString(); + uint64_t timestamp = r.ReadUInt64(); + Logger::Trace("Chat from {} (session {}): {}", sender, sessionId, message); + BinaryProtocol::BinaryWriter w; + w.WriteUInt16(BinaryProtocol::MESSAGE_TYPE_CHAT_MESSAGE); + w.WriteString(sender); + w.WriteString(message); + w.WriteUInt64(timestamp); + std::vector response = w.GetBuffer(); + processPool_.BroadcastToAllWorkers(response); + break; + } case BinaryProtocol::MESSAGE_TYPE_CHUNK_PARAMS: { ChunkParams req; req.session_id = sessionId; @@ -188,27 +183,32 @@ void MasterServer::Initialize() void MasterServer::start_signal_read() { signal_pipe_.async_read_some(asio::buffer(signal_buffer_), - [this](std::error_code ec, size_t /*bytes*/) { - if (!ec) { - Logger::Trace("Master shutdown signal received"); - Shutdown(); - } - start_signal_read(); - }); + [this](std::error_code ec, size_t /*bytes*/) { + if (!ec) { + Logger::Trace("MasterServer::start_signal_read shutdown signal received"); + Shutdown(); + } + start_signal_read(); + Logger::Trace("MasterServer::start_signal_read completed, ec = {}", ec.message()); + }); } void MasterServer::Run() { io_.run(); + Logger::Trace("MasterServer::Run: io_context::run() returned"); } void MasterServer::Shutdown() { static std::once_flag once; std::call_once(once, [this]{ - Logger::Info("Master shutdown initiated"); + Logger::Info("MasterServer::Shutdown initiated"); + signal_pipe_.cancel(); processPool_.Shutdown(); + //Logger::Trace("MasterServer::Shutdown: about to call gameLogic_.Shutdown()"); gameLogic_.Shutdown(); io_.stop(); + Logger::Info("MasterServer::Shutdown finished"); }); } @@ -229,7 +229,7 @@ void MasterServer::WorkerClient(int workerId, const WorkerGroupConfig& groupConf Logger::AddSink(logSink); Logger::GetLogger()->set_pattern(config.GetString("logging.pattern", "[%Y-%m-%d %H:%M:%S.%e] [%P] [%l] [%n] %v")); Logger::Info("Worker {} starting for group: {} ({}:{})", workerId, groupConfig.protocol, groupConfig.host, groupConfig.port); - ClientListener listener(groupConfig, masterReadFd); + ClientListener listener(groupConfig, masterReadFd, workerId); listener.Start(); asio::io_context signalIo; asio::signal_set signals(signalIo, SIGINT, SIGTERM); @@ -243,20 +243,25 @@ void MasterServer::WireCallbacks() { gameLogic_.SetSendAuthenticationResponseCallback([this](uint64_t session_id, const std::string& message, uint64_t player_id) { BinaryProtocol::BinaryWriter w; + w.WriteUInt16(BinaryProtocol::MESSAGE_TYPE_AUTHENTICATION); w.WriteUInt64(gameLogic_.GetCurrentTimestamp()); w.WriteUInt64(player_id); w.WriteString(message); - SendResponse(session_id, w.GetBuffer()); + auto buf = w.GetBuffer(); if (!buf.empty()) Logger::Trace("Pushing from callback: first byte = 0x{:02x}", buf[0]); + processPool_.PushToWorker(static_cast(session_id >> 48), session_id, buf); }); gameLogic_.SetSendChunkParamsCallback([this](uint64_t session_id, const ChunkParams& data) { BinaryProtocol::BinaryWriter w; + w.WriteUInt16(BinaryProtocol::MESSAGE_TYPE_CHUNK_PARAMS); w.WriteUInt64(data.timestamp); w.WriteUInt32(static_cast(data.size)); w.WriteFloat(data.spacing); - SendResponse(session_id, w.GetBuffer()); + auto buf = w.GetBuffer(); if (!buf.empty()) Logger::Trace("Pushing from callback: first byte = 0x{:02x}", buf[0]); + processPool_.PushToWorker(static_cast(session_id >> 48), session_id, buf); }); gameLogic_.SetSendChunkCallback([this](uint64_t session_id, const ChunkData& data) { BinaryProtocol::BinaryWriter w; + w.WriteUInt16(BinaryProtocol::MESSAGE_TYPE_CHUNK_DATA); w.WriteUInt64(data.timestamp); w.WriteInt32(data.x); w.WriteInt32(data.z); @@ -267,19 +272,22 @@ void MasterServer::WireCallbacks() w.WriteUInt32(idxSize); w.WriteBytes(reinterpret_cast(data.indices.data()), idxSize); w.WriteUInt32(data.lod); - SendResponse(session_id, w.GetBuffer()); + auto buf = w.GetBuffer(); if (!buf.empty()) Logger::Trace("Pushing from callback: first byte = 0x{:02x}", buf[0]); + processPool_.PushToWorker(static_cast(session_id >> 48), session_id, buf); }); gameLogic_.SetSendCollisionResponseCallback([this](uint64_t session_id, const CollisionResult& result) { BinaryProtocol::BinaryWriter w; + w.WriteUInt16(BinaryProtocol::MESSAGE_TYPE_COLLISION_CHECK); w.WriteUInt64(gameLogic_.GetCurrentTimestamp()); w.WriteUInt8(result.collided ? 1 : 0); w.WriteUInt64(result.collided_id); w.WriteFloat(result.penetration); w.WriteVector3(result.resolution); - SendResponse(session_id, w.GetBuffer()); + processPool_.PushToWorker(static_cast(session_id >> 48), session_id, w.GetBuffer()); }); - gameLogic_.SetPlayerStateCallback([this](const PlayerStateData& state) { + gameLogic_.SetPlayerStateCallback([this](const PlayerStateData& state) {//broadcast BinaryProtocol::BinaryWriter w; + w.WriteUInt16(BinaryProtocol::MESSAGE_TYPE_PLAYER_STATE); w.WriteUInt64(state.timestamp); w.WriteUInt64(state.player_id); w.WriteVector3(state.position); @@ -289,11 +297,12 @@ void MasterServer::WireCallbacks() auto sids = gameLogic_.GetSessionsInRadius(state.position); for (auto sid : sids) { if (sid == state.session_id) continue; - SendResponse(sid, w.GetBuffer()); + processPool_.PushToWorker(static_cast(sid >> 48), sid, w.GetBuffer()); } }); - gameLogic_.SetBroadcastPlayerPositionCallback([this](const PlayerPositionData& data, float /*radius*/) { + gameLogic_.SetBroadcastPlayerPositionCallback([this](const PlayerPositionData& data, float /*radius*/) {//broadcast BinaryProtocol::BinaryWriter w; + w.WriteUInt16(BinaryProtocol::MESSAGE_TYPE_PLAYER_POSITION); w.WriteUInt64(data.timestamp); w.WriteUInt64(data.player_id); w.WriteVector3(data.position); @@ -301,28 +310,32 @@ void MasterServer::WireCallbacks() auto sids = gameLogic_.GetSessionsInRadius(data.position); for (auto sid : sids) { if (sid == data.session_id) continue; - SendResponse(sid, w.GetBuffer()); + processPool_.PushToWorker(static_cast(sid >> 48), sid, w.GetBuffer()); } }); gameLogic_.SetSendPlayerSpawnCallback([this](uint64_t session_id, const PlayerSpawnData& data) { BinaryProtocol::BinaryWriter w; - w.WriteUInt64(data.timestamp); + w.WriteUInt16(BinaryProtocol::MESSAGE_TYPE_PLAYER_SPAWN); + w.WriteUInt64(gameLogic_.GetCurrentTimestamp()); w.WriteUInt64(data.player_id); w.WriteString(data.name); w.WriteVector3(data.position); w.WriteFloat(data.yaw); w.WriteFloat(data.health); w.WriteFloat(data.max_health); - SendResponse(session_id, w.GetBuffer()); + int workerId = static_cast(session_id >> 48); + processPool_.PushToWorker(workerId, session_id, w.GetBuffer()); }); gameLogic_.SetSendPlayerDespawnCallback([this](uint64_t session_id, const PlayerDespawnData& data) { BinaryProtocol::BinaryWriter w; + w.WriteUInt16(BinaryProtocol::MESSAGE_TYPE_PLAYER_DESPAWN); w.WriteUInt64(data.timestamp); w.WriteUInt64(data.player_id); - SendResponse(session_id, w.GetBuffer()); + processPool_.PushToWorker(static_cast(session_id >> 48), session_id, w.GetBuffer()); }); gameLogic_.SetSendPlayerUpdateCallback([this](uint64_t session_id, const PlayerUpdateData& data) { BinaryProtocol::BinaryWriter w; + w.WriteUInt16(BinaryProtocol::MESSAGE_TYPE_PLAYER_UPDATE); w.WriteUInt64(data.timestamp); w.WriteUInt64(data.player_id); w.WriteVector3(data.position); @@ -330,10 +343,11 @@ void MasterServer::WireCallbacks() w.WriteFloat(data.health); w.WriteFloat(data.max_health); w.WriteString(data.name); - SendResponse(session_id, w.GetBuffer()); + processPool_.PushToWorker(static_cast(session_id >> 48), session_id, w.GetBuffer()); }); - gameLogic_.SetSendPlayersUpdateCallback([this](uint64_t session_id, const PlayerUpdateData& data) { + gameLogic_.SetSendPlayersUpdateCallback([this](uint64_t session_id, const PlayerUpdateData& data) {//broadcast BinaryProtocol::BinaryWriter w; + w.WriteUInt16(BinaryProtocol::MESSAGE_TYPE_PLAYERS_UPDATE); w.WriteUInt64(data.timestamp); w.WriteUInt64(data.player_id); w.WriteVector3(data.position); @@ -344,7 +358,7 @@ void MasterServer::WireCallbacks() auto sids = gameLogic_.GetSessionsInRadius(data.position); for (auto sid : sids) { if (sid != session_id) { - SendResponse(sid, w.GetBuffer()); + processPool_.PushToWorker(static_cast(sid >> 48), sid, w.GetBuffer()); } } }); @@ -357,16 +371,16 @@ void MasterServer::WireCallbacks() w.WriteFloat(response.damage); w.WriteFloat(response.health); w.WriteUInt8(response.is_dead ? 1 : 0); - SendResponse(session_id, w.GetBuffer()); + processPool_.PushToWorker(static_cast(session_id >> 48), session_id, w.GetBuffer()); } else if (response.type == "dialogue") { w.WriteUInt64(response.npc_id); w.WriteJson(response.quests); w.WriteUInt64(response.timestamp); - SendResponse(session_id, w.GetBuffer()); + processPool_.PushToWorker(static_cast(session_id >> 48), session_id, w.GetBuffer()); } else { w.WriteUInt64(0); w.WriteString("NPC interaction failed"); - SendResponse(session_id, w.GetBuffer()); + processPool_.PushToWorker(static_cast(session_id >> 48), session_id, w.GetBuffer()); } }); gameLogic_.SetSendFamiliarCommandResponseCallback([this](uint64_t session_id, const FamiliarData& response) { @@ -375,7 +389,7 @@ void MasterServer::WireCallbacks() w.WriteUInt64(response.familiar_id); w.WriteUInt64(response.target_id); w.WriteString(response.command); - SendResponse(session_id, w.GetBuffer()); + processPool_.PushToWorker(static_cast(session_id >> 48), session_id, w.GetBuffer()); }); gameLogic_.SetSendEntitySpawnResponseCallback([this](uint64_t session_id, const EntitySpawnData& response) { BinaryProtocol::BinaryWriter w; @@ -383,14 +397,14 @@ void MasterServer::WireCallbacks() w.WriteUInt64(response.entity_id); w.WriteInt32(response.type); w.WriteVector3(response.position); - SendResponse(session_id, w.GetBuffer()); + processPool_.PushToWorker(static_cast(session_id >> 48), session_id, w.GetBuffer()); }); gameLogic_.SetSendLootPickupResponseCallback([this](uint64_t session_id, const LootPickupData& response) { BinaryProtocol::BinaryWriter w; w.WriteUInt64(response.timestamp); w.WriteUInt64(response.loot_id); w.WriteUInt16(response.quantity); - SendResponse(session_id, w.GetBuffer()); + processPool_.PushToWorker(static_cast(session_id >> 48), session_id, w.GetBuffer()); }); gameLogic_.SetSendInventoryResponseCallback([this](uint64_t session_id, const InventoryData& response) { BinaryProtocol::BinaryWriter w; @@ -401,7 +415,7 @@ void MasterServer::WireCallbacks() w.WriteInt32(response.inv_slot_id); w.WriteInt32(response.use_slot_id); w.WriteUInt16(response.quantity); - SendResponse(session_id, w.GetBuffer()); + processPool_.PushToWorker(static_cast(session_id >> 48), session_id, w.GetBuffer()); }); } diff --git a/src/network/WebSocketProtocol.cpp b/src/network/WebSocketProtocol.cpp index 3763d49..b081f19 100644 --- a/src/network/WebSocketProtocol.cpp +++ b/src/network/WebSocketProtocol.cpp @@ -187,8 +187,6 @@ WebSocketFrame WebSocketFrame::CreateCloseFrame(uint16_t code, const std::string return frame; } -// =============== HandshakeRequest Implementation =============== - std::string HandshakeRequest::Serialize() const { std::stringstream ss; ss << method << " " << path << " " << http_version << "\r\n"; @@ -257,8 +255,6 @@ void HandshakeRequest::SetHeader(const std::string& name, const std::string& val headers[name] = value; } -// =============== HandshakeResponse Implementation =============== - std::string HandshakeResponse::Serialize() const { std::stringstream ss; ss << http_version << " " << status_code << " " << status_text << "\r\n"; @@ -389,11 +385,15 @@ std::atomic WebSocketConnection::next_connection_id_{1}; WebSocketConnection::WebSocketConnection(asio::ip::tcp::socket socket) : socket_(std::move(socket)) , connection_id_(next_connection_id_++) { - Logger::Debug("WebSocketConnection {} created", connection_id_); + Logger::Debug("WebSocketConnection created ID {}", connection_id_); + asio::ip::tcp::no_delay no_delay_option(true); + socket_.set_option(no_delay_option); + asio::socket_base::linger option(true, 2); + socket_.set_option(option); } WebSocketConnection::~WebSocketConnection() { - Logger::Debug("WebSocketConnection {} destroyed", connection_id_); + Logger::Debug("WebSocketConnection destroyed ID {} (socket_.is_open={})", connection_id_, socket_.is_open()); } void WebSocketConnection::Start() { @@ -407,6 +407,24 @@ void WebSocketConnection::HandleHandshake() { ReadHandshake(); } +void WebSocketConnection::HandleError(const std::error_code& ec) { + if (state_ == State::CLOSED || state_ == State::CLOSING) { + return; + } + if (ec == asio::error::eof || ec == asio::error::connection_reset || ec == asio::error::broken_pipe) { + Logger::Trace("WebSocketConnection {} disconnected by client", connection_id_); + } else if (ec == asio::error::operation_aborted) { + Logger::Trace("WebSocketConnection {} operation aborted", connection_id_); + } else if (ec == asio::error::bad_descriptor) { + Logger::Trace("WebSocketConnection {} bad descriptor - already closed", connection_id_); + } else { + Logger::Error("WebSocketConnection {} error: {}", connection_id_, ec.message()); + } + if (state_ == State::OPEN) { + Close(1006, "Connection error"); + } +} + void WebSocketConnection::ReadHandshake() { auto self = shared_from_this(); asio::async_read_until(socket_, read_buffer_, "\r\n\r\n", @@ -634,33 +652,47 @@ void WebSocketConnection::CompleteCurrentMessage() { } void WebSocketConnection::SendFrame(const WebSocketFrame& frame) { - std::vector frame_data = frame.Serialize(); - { - std::lock_guard lock(write_mutex_); - write_buffer_.insert(write_buffer_.end(), frame_data.begin(), frame_data.end()); - stats_.messages_sent++; - stats_.bytes_sent += frame.payload_data.size(); - } - DoWrite(); -} - -void WebSocketConnection::SendFrameAsync(const WebSocketFrame& frame) { auto self = shared_from_this(); - std::vector frame_data = frame.Serialize(); - asio::async_write(socket_, asio::buffer(frame_data), - [self, frame_data](std::error_code ec, size_t /*bytes_transferred*/) { - //Logger::Debug("WebSocketConnection::SendFrameAsync asio::async_write {}", bytes_transferred); - if (ec) { - self->HandleError(ec); - } else { - std::lock_guard lock(self->stats_mutex_); - self->stats_.messages_sent++; - self->stats_.bytes_sent += frame_data.size(); - } - }); + auto data = std::make_shared>(frame.Serialize()); + Logger::Trace("WebSocketConnection::SendFrame: starting write of {} bytes, socket open: {}", + data->size(), socket_.is_open()); + uint8_t opcode = frame.opcode; + asio::async_write(socket_, asio::buffer(*data), + [self, data, opcode](std::error_code ec, size_t bytes) { + Logger::Trace("WebSocketConnection::SendFrame: opcode={}, ec={}, bytes={}", opcode, ec.message(), bytes); + if (ec) { + Logger::Error("WebSocketConnection::SendFrame failed: {}", ec.message()); + self->HandleError(ec); + return; + } + if (bytes != data->size()) { + Logger::Warn("WebSocketConnection::SendFrame Partial write: {} / {}", bytes, data->size()); + } + { + std::lock_guard lock(self->stats_mutex_); + self->stats_.messages_sent++; + self->stats_.bytes_sent += bytes; + } + Logger::Trace("WebSocketConnection::SendFrame completion: ec = {}, bytes = {}", ec.message(), bytes); + }); } -// void WebSocketConnection::DoWrite() { +// void WebSocketConnection::SendFrameAsync(const WebSocketFrame& frame) {//ATTENTION: it is duplicate now +// auto self = shared_from_this(); +// auto data = std::make_shared>(frame.Serialize()); +// asio::async_write(socket_, asio::buffer(*data), +// [self, data](std::error_code ec, size_t bytes) { +// if (ec) { +// self->HandleError(ec); +// } else { +// std::lock_guard lock(self->stats_mutex_); +// self->stats_.messages_sent++; +// self->stats_.bytes_sent += bytes; +// } +// }); +// } + +// void WebSocketConnection::DoWrite() {//ATTENTION: this work fine, but freeze server shutdown // if (write_in_progress_.exchange(true)) { // return; // } @@ -670,7 +702,7 @@ void WebSocketConnection::SendFrameAsync(const WebSocketFrame& frame) { // return; // } // auto self = shared_from_this(); -// Logger::Trace("WebSocketConnection {} DoWrite: starting async_write", connection_id_); +// Logger::Trace("WebSocketConnection::DoWrite: {} writing {} bytes", connection_id_, write_buffer_.size()); // asio::async_write(socket_, asio::buffer(write_buffer_), // [self](std::error_code ec, size_t bytes) { // Logger::Trace("WebSocketConnection::DoWrite asio::async_write {}", bytes); @@ -697,139 +729,158 @@ void WebSocketConnection::SendFrameAsync(const WebSocketFrame& frame) { // }); // } -void WebSocketConnection::DoWrite() { - if (write_in_progress_.exchange(true)) return; - std::vector data; - { - std::lock_guard lock(write_mutex_); - if (write_buffer_.empty() || state_ != State::OPEN) { - write_in_progress_ = false; - return; - } - data = std::move(write_buffer_); - write_buffer_.clear(); +void WebSocketConnection::DoWrite() {//ATTENTION: this work fine only for small packets like auth + if (write_in_progress_.exchange(true)) { + return; + } + std::lock_guard lock(write_mutex_); + if (write_buffer_.empty() || state_ != State::OPEN) { + write_in_progress_ = false; + return; } auto self = shared_from_this(); - Logger::Trace("WebSocketConnection {} DoWrite: starting async_write", connection_id_); - asio::async_write(socket_, asio::buffer(data), - [self, data = std::move(data)](std::error_code ec, size_t bytes) mutable { + Logger::Trace("WebSocketConnection {} DoWrite: writing {} bytes", connection_id_, write_buffer_.size()); + asio::async_write(socket_, asio::buffer(write_buffer_), + [self](std::error_code ec, size_t bytes) { Logger::Trace("WebSocketConnection::DoWrite asio::async_write {}", bytes); if (ec) { self->HandleError(ec); - self->write_in_progress_ = false; - return; - } - { + } else { std::lock_guard lock(self->write_mutex_); + self->write_buffer_.erase(self->write_buffer_.begin(), self->write_buffer_.begin() + bytes); self->stats_.bytes_sent += bytes; } + bool more = false; { std::lock_guard lock(self->write_mutex_); - if (!self->write_buffer_.empty()) { - self->DoWrite(); - return; - } + more = !self->write_buffer_.empty(); + } + if (more) { + self->DoWrite(); + } else { + self->write_in_progress_ = false; } - self->write_in_progress_ = false; }); } void WebSocketConnection::SendText(const std::string& text) { constexpr size_t MAX_FRAME_SIZE = 16384; if (text.size() <= MAX_FRAME_SIZE) { - WebSocketFrame frame = WebSocketFrame::CreateTextFrame(text); - SendFrame(frame); + SendFrame(WebSocketFrame::CreateTextFrame(text)); return; } MessageFragmenter fragmenter(MAX_FRAME_SIZE); auto fragments = fragmenter.FragmentText(text); - std::vector combined; - for (auto& f : fragments) { - auto ser = f.Serialize(); - combined.insert(combined.end(), ser.begin(), ser.end()); - } - { - std::lock_guard lock(write_mutex_); - write_buffer_.insert(write_buffer_.end(), combined.begin(), combined.end()); - stats_.messages_sent += fragments.size(); - stats_.bytes_sent += combined.size(); + for (auto& frame : fragments) { + SendFrame(frame); } - DoWrite(); } +// void WebSocketConnection::SendText(const std::string& text) { +// constexpr size_t MAX_FRAME_SIZE = 16384; +// if (text.size() <= MAX_FRAME_SIZE) { +// SendFrame(WebSocketFrame::CreateTextFrame(text)); +// return; +// } +// MessageFragmenter fragmenter(MAX_FRAME_SIZE); +// auto fragments = fragmenter.FragmentText(text); +// std::vector combined; +// for (auto& frame : fragments) { +// auto ser = frame.Serialize(); +// combined.insert(combined.end(), ser.begin(), ser.end()); +// } +// { +// std::lock_guard lock(write_mutex_); +// write_buffer_.insert(write_buffer_.end(), combined.begin(), combined.end()); +// stats_.messages_sent += fragments.size(); +// stats_.bytes_sent += combined.size(); +// } +// DoWrite(); +// } + void WebSocketConnection::SendBinary(const std::vector& data) { WebSocketFrame frame = WebSocketFrame::CreateBinaryFrame(data); SendFrame(frame); } void WebSocketConnection::SendJson(const nlohmann::json& json) { - SendText(json.dump()); + std::string text = json.dump(); + Logger::Trace("WebSocketConnection::SendJson: sending text frame, size={}, first 30 chars={}", + text.size(), text.substr(0, 30)); + SendText(text); } void WebSocketConnection::SendPing(const std::vector& data) { WebSocketFrame frame = WebSocketFrame::CreatePingFrame(data); - SendFrameAsync(frame); + SendFrame(frame); } void WebSocketConnection::SendPong(const std::vector& data) { WebSocketFrame frame = WebSocketFrame::CreatePongFrame(data); - SendFrameAsync(frame); + SendFrame(frame); } +// void WebSocketConnection::Close(uint16_t code, const std::string& reason) { +// if (state_ == State::CLOSED || state_ == State::CLOSING) return; +// Logger::Trace("WebSocketConnection {} Close code {}, reason {}", connection_id_, code, reason); +// state_ = State::CLOSING; +// std::error_code ec; +// socket_.cancel(ec); +// if (ec) Logger::Debug("Cancel error: {}", ec.message()); +// { +// std::lock_guard lock(write_mutex_); +// write_buffer_.clear(); +// write_in_progress_ = false; +// } +// socket_.shutdown(asio::ip::tcp::socket::shutdown_both, ec); +// socket_.close(ec); +// state_ = State::CLOSED; +// if (close_handler_) close_handler_(code, reason.empty() ? "Connection closed" : reason); +// Logger::Trace("WebSocketConnection {} closed", connection_id_); +// } + void WebSocketConnection::Close(uint16_t code, const std::string& reason) { + Logger::Trace("WebSocketConnection::Close: about to close socket, pending writes?"); if (state_ == State::CLOSED) { - Logger::Trace("WebSocketConnection {} already closed", connection_id_); + Logger::Trace("WebSocketConnection::Close {} already closed", connection_id_); return; } if (state_ == State::CLOSING) { - Logger::Trace("WebSocketConnection {} already closing", connection_id_); + Logger::Trace("WebSocketConnection::Close {} already closing", connection_id_); return; } - Logger::Trace("WebSocketConnection {} Close code {}, reason {}, state={}", connection_id_, code, reason, (int)state_); + Logger::Trace("WebSocketConnection::Close {} Close code {}, reason {}, state={}", connection_id_, code, reason, (int)state_); + { + std::lock_guard lock(write_mutex_); + write_buffer_.clear(); + write_in_progress_ = false; + } state_ = State::CLOSING; std::error_code ec; if (socket_.is_open()) { socket_.cancel(ec); if (ec) { - Logger::Debug("WebSocketConnection {} cancel error: {}", connection_id_, ec.message()); + Logger::Debug("WebSocketConnection::Close {} cancel error: {}", connection_id_, ec.message()); } socket_.shutdown(asio::ip::tcp::socket::shutdown_both, ec); if (ec && ec != asio::error::not_connected) { - Logger::Debug("WebSocketConnection {} shutdown error: {}", connection_id_, ec.message()); + Logger::Debug("WebSocketConnection::Close {} shutdown error: {}", connection_id_, ec.message()); } socket_.close(ec); if (ec && ec != asio::error::not_connected) { - Logger::Debug("WebSocketConnection {} close error: {}", connection_id_, ec.message()); + Logger::Debug("WebSocketConnection::Close {} close error: {}", connection_id_, ec.message()); } } state_ = State::CLOSED; if (close_handler_) { close_handler_(code, reason.empty() ? "Connection closed" : reason); } - Logger::Trace("WebSocketConnection {} closed synchronously", connection_id_); -} - -void WebSocketConnection::HandleError(const std::error_code& ec) { - if (state_ == State::CLOSED || state_ == State::CLOSING) { - return; - } - if (ec == asio::error::eof || ec == asio::error::connection_reset || ec == asio::error::broken_pipe) { - Logger::Trace("WebSocketConnection {} disconnected by client", connection_id_); - } else if (ec == asio::error::operation_aborted) { - Logger::Debug("WebSocketConnection {} operation aborted", connection_id_); - } else if (ec == asio::error::bad_descriptor) { - Logger::Debug("WebSocketConnection {} bad descriptor - already closed", connection_id_); - } else { - Logger::Error("WebSocketConnection {} error: {}", connection_id_, ec.message()); - } - if (state_ == State::OPEN) { - Close(1006, "Connection error"); - } + Logger::Trace("WebSocketConnection::Close {} closed synchronously", connection_id_); } void WebSocketConnection::HandleClose(uint16_t code, const std::string& reason) { if (state_ == State::OPEN) { - SendFrameAsync(WebSocketFrame::CreateCloseFrame(code, reason)); + SendFrame(WebSocketFrame::CreateCloseFrame(code, reason)); } Close(code, reason); } @@ -839,8 +890,8 @@ asio::ip::tcp::endpoint WebSocketConnection::GetRemoteEndpoint() const { if (socket_.is_open()) { return socket_.remote_endpoint(); } - } catch (const std::exception& e) { - Logger::Debug("Failed to get remote endpoint: {}", e.what()); + } catch (const std::exception& err) { + Logger::Error("WebSocketConnection::GetRemoteEndpoint failed to get remote endpoint: {}", err.what()); } return asio::ip::tcp::endpoint(); } @@ -862,19 +913,18 @@ WebSocketServer::~WebSocketServer() { } void WebSocketServer::Start() { - if (running_) { + if (running_.load()) { return; } - running_ = true; + running_.store(true); DoAccept(); Logger::Info("WebSocketServer started on port {}", port_); } void WebSocketServer::Stop() { - if (!running_) { + if (!running_.exchange(false)) { return; } - running_ = false; std::error_code ec; acceptor_.close(ec); std::lock_guard lock(connections_mutex_); @@ -882,14 +932,18 @@ void WebSocketServer::Stop() { connection->Close(1001, "Server going away"); } connections_.clear(); - Logger::Info("WebSocketServer stopped"); + Logger::Info("WebSocketServer::Stop finished"); } void WebSocketServer::DoAccept() { - if (!running_) { + if (!running_.load()) { return; } acceptor_.async_accept([this](std::error_code ec, asio::ip::tcp::socket socket) { + if (ec == asio::error::operation_aborted){ + Logger::Warn("WebSocketServer::doAccept: {}", ec.message()); + return; + } if (!ec) { WebSocketConnection::Pointer connection; if (connection_factory_) { @@ -899,9 +953,9 @@ void WebSocketServer::DoAccept() { } AddConnection(connection); connection->Start(); - Logger::Debug("WebSocket connection accepted"); + Logger::Trace("WebSocketServer::DoAccept accepted"); } - if (running_) { + if (running_.load()) { DoAccept(); } }); @@ -912,7 +966,7 @@ void WebSocketServer::AddConnection(WebSocketConnection::Pointer connection) { connections_.push_back(connection); auto weak_connection = std::weak_ptr(connection); connection->SetCloseHandler([this, weak_connection](uint16_t code, const std::string& reason) { - Logger::Debug("WebSocketConnection::AddConnection WebSocketConnection::SetCloseHandler({}, {})", code, reason); + Logger::Debug("WebSocketServer::AddConnection WebSocketConnection::SetCloseHandler({}, {})", code, reason); auto connection = weak_connection.lock(); if (connection) { RemoveConnection(connection); @@ -925,7 +979,7 @@ void WebSocketServer::RemoveConnection(WebSocketConnection::Pointer connection) auto it = std::find(connections_.begin(), connections_.end(), connection); if (it != connections_.end()) { connections_.erase(it); - Logger::Debug("WebSocket connection removed"); + Logger::Debug("WebSocketServer::RemoveConnection completed"); } } @@ -1059,14 +1113,14 @@ void WebSocketClient::SendHandshakeRequest() { std::string request_str = request.Serialize(); auto self = std::static_pointer_cast(shared_from_this()); auto write_handler = [self, request](std::error_code ec, size_t bytes_transferred) { - Logger::Debug("WebSocketConnection::SendHandshakeRequest write_handler {}", bytes_transferred); + Logger::Debug("WebSocketClient::SendHandshakeRequest write_handler {}", bytes_transferred); if (ec) { self->HandleError(ec); return; } asio::async_read_until(self->socket_, self->read_buffer_, "\r\n\r\n", [self, request](std::error_code ec, size_t bytes_transferred) { - Logger::Debug("WebSocketConnection::SendHandshakeRequest asio::async_read_until {}", bytes_transferred); + Logger::Debug("WebSocketClient::SendHandshakeRequest asio::async_read_until {}", bytes_transferred); if (ec) { self->HandleError(ec); return; @@ -1080,10 +1134,10 @@ void WebSocketClient::SendHandshakeRequest() { throw std::runtime_error("Invalid handshake response"); } self->state_ = State::OPEN; - Logger::Info("WebSocketClient handshake complete"); + Logger::Info("WebSocketClient::SendHandshakeRequest complete"); self->ReadFrame(); - } catch (const std::exception& e) { - Logger::Error("WebSocket handshake error: {}", e.what()); + } catch (const std::exception& err) { + Logger::Error("WebSocketClient::SendHandshakeRequest error: {}", err.what()); self->Close(1002, "Protocol error"); } }); diff --git a/src/network/WebSocketSession.cpp b/src/network/WebSocketSession.cpp index 0d5a17a..49417f8 100644 --- a/src/network/WebSocketSession.cpp +++ b/src/network/WebSocketSession.cpp @@ -3,8 +3,10 @@ std::atomic WebSocketSession::nextSessionId_{1}; -WebSocketSession::WebSocketSession(WebSocketProtocol::WebSocketConnection::Pointer wsConn) - : protocolMode_(ProtocolMode::Json), wsConn_(std::move(wsConn)), sessionId_(nextSessionId_++) { +WebSocketSession::WebSocketSession(WebSocketProtocol::WebSocketConnection::Pointer wsConn, uint64_t sessionId) + : protocolMode_(ProtocolMode::Json), wsConn_(std::move(wsConn)) + , sessionId_(sessionId > 0 ? sessionId : nextSessionId_.fetch_add(1)) +{ wsConn_->SetMessageHandler([this](const WebSocketProtocol::WebSocketMessage& msg) { OnMessage(msg); }); @@ -40,6 +42,9 @@ void WebSocketSession::SendRaw(const std::string& data) { } void WebSocketSession::SendJson(const nlohmann::json& message) { + std::string msg_type = message.value("msg", "UNKNOWN"); + if (msg_type == "get_chunk") Logger::Trace("WebSocketSession::SendJson: msg={}", msg_type); + else Logger::Trace("WebSocketSession::SendJson: {}", message.dump()); wsConn_->SendJson(message); } @@ -183,14 +188,15 @@ void WebSocketSession::OnMessage(const WebSocketProtocol::WebSocketMessage& msg) Logger::Trace("WebSocketSession {} parsed JSON: {}", sessionId_, json.dump()); std::string msgType = json.value("msg", ""); if (msgType == "protocol_negotiation") { - protocolMode_ = ProtocolMode::Binary; - std::string protocol = json.value("protocol", ""); int version = json.value("version", 0); - Logger::Info("WebSocketSession {} client negotiated protocol: {} v{}", sessionId_, protocol, version); + std::string protocol = json.value("protocol", ""); + if (protocol == "binary") + protocolMode_ = ProtocolMode::Binary; + Logger::Trace("WebSocketSession {} client negotiated protocol: {} v{}", sessionId_, protocol, version); nlohmann::json response = { {"msg", "protocol_negotiation"}, - {"protocol", "binary"}, - {"version", 1}, + {"protocol", protocol}, + {"version", version}, {"status", "ok"} }; wsConn_->SendJson(response); diff --git a/src/process/ProcessPool.cpp b/src/process/ProcessPool.cpp index 240b397..9cf1f6b 100644 --- a/src/process/ProcessPool.cpp +++ b/src/process/ProcessPool.cpp @@ -51,18 +51,11 @@ void ProcessWorker::SendAsync(const std::vector& binaryData) { std::vector frame(sizeof(len) + binaryData.size()); memcpy(frame.data(), &len, sizeof(len)); memcpy(frame.data() + sizeof(len), binaryData.data(), binaryData.size()); - bool startWriting = false; { std::lock_guard lock(sendMutex_); sendQueue_.push_back(std::move(frame)); - if (!writing_) { - writing_ = true; - startWriting = true; - } - } - if (startWriting) { - doWrite(); } + sendCv_.notify_one(); } void ProcessWorker::doWrite() { @@ -109,6 +102,42 @@ void ProcessWorker::Shutdown() { pid_ = -1; } +void ProcessWorker::writerLoop() { + while (true) { + Logger::Trace("ProcessWorker::writerLoop ID {}", workerId_); + std::vector data; + { + std::unique_lock lock(sendMutex_); + sendCv_.wait(lock, [this] {return !writerRunning_ || !sendQueue_.empty();}); + if (!writerRunning_ && sendQueue_.empty()) return; + data = std::move(sendQueue_.front()); + sendQueue_.pop_front(); + } + Logger::Trace("ProcessWorker::writerLoop {} writing {} bytes", workerId_, data.size()); + std::error_code ec; + asio::write(masterStream_, asio::buffer(data), ec); + Logger::Trace("ProcessWorker::writerLoop {} write completed: ec = {}", workerId_, ec.message()); + if (ec) { + Logger::Error("ProcessWorker::writerLoop for worker {} failed: {}", workerId_, ec.message()); + break; + } + } +} + +void ProcessWorker::StartWriterThread() { + writerThread_ = std::thread(&ProcessWorker::writerLoop, this); +} + +void ProcessWorker::StopWriter() { + writerRunning_ = false; + sendCv_.notify_all(); +} + +void ProcessWorker::JoinWriterThread() { + if (writerThread_.joinable()) + writerThread_.join(); +} + ProcessPool::ProcessPool(asio::io_context& io, const std::vector& groups) : io_(io), groups_(groups) {} @@ -117,16 +146,25 @@ void ProcessPool::Initialize() { for (auto& w : workers_) { StartReadingFromWorker(w); } - ready_ = true; - running_ = true; + ready_.store(true); + running_.store(true); } void ProcessPool::Run() { io_.run(); } void ProcessPool::Shutdown() { - if (!running_) return; - running_ = false; + if (!running_.exchange(false)) return; + Logger::Trace("ProcessPool::Shutdown: running..."); for (auto& w : workers_) w->Shutdown(); + Logger::Trace("ProcessPool::Shutdown: workers_->Shutdown() finished"); + for (auto& w : workers_) w->StopWriter(); + Logger::Trace("ProcessPool::Shutdown: workers_->StopWriter() finished"); + for (auto& pair : readerRunningFlags_) *pair.second = false; + Logger::Trace("ProcessPool::Shutdown: readerRunningFlags_ finished"); + io_.stop(); + Logger::Trace("ProcessPool::Shutdown: io_.stop finished"); + for (auto& w : workers_) w->JoinWriterThread(); + Logger::Trace("ProcessPool::Shutdown: workers_->JoinWriterThread() finished"); } void ProcessPool::SetWorker(std::function func) { @@ -138,32 +176,47 @@ void ProcessPool::SetMasterMessageHandler(ProcessWorker::MasterMessageHandler ha } void ProcessPool::StartReadingFromWorker(std::shared_ptr worker) { - auto& stream = worker->GetMasterStream(); - auto header = std::make_shared>(14); - asio::async_read(stream, asio::buffer(*header), [this, worker, header, &stream](std::error_code ec, size_t) { - if (!ec) { - BinaryProtocol::BinaryReader r(header->data(), header->size()); - uint32_t corrId = r.ReadUInt32(); - uint64_t sessionId = r.ReadUInt64(); - uint16_t msgType = r.ReadUInt16(); - uint32_t bodyLen = r.ReadUInt32(); - if (bodyLen > 0) { - auto body = std::make_shared>(bodyLen); - asio::async_read(stream, asio::buffer(*body), [this, worker, corrId, sessionId, msgType, body, &stream](std::error_code ec2, size_t) { - if (!ec2 && masterHandler_) - masterHandler_(worker->GetId(), corrId, sessionId, msgType, std::move(*body)); - StartReadingFromWorker(worker); - }); - } else { - if (masterHandler_) - masterHandler_(worker->GetId(), corrId, sessionId, msgType, {}); - StartReadingFromWorker(worker); + auto running = std::make_shared>(true); + readerRunningFlags_[worker->GetId()] = running; + asio::co_spawn(io_, [this, worker, running]() -> asio::awaitable { + auto& stream = worker->GetMasterStream(); + while (running->load()) + { + uint32_t netLen = 0; + std::error_code ec; + co_await asio::async_read(stream, + asio::buffer(&netLen, sizeof(netLen)), + asio::redirect_error(asio::use_awaitable, ec)); + if (ec == asio::error::eof || ec == asio::error::connection_reset) + break; + if (ec) { + Logger::Error("ProcessPool::StartReadingFromWorker: ID {} read error: {}", worker->GetId(), ec.message()); + break; } - } else { - if (ec != asio::error::operation_aborted) - Logger::Error("Error reading from worker {}: {}", worker->GetId(), ec.message()); + uint32_t msgLen = ntohl(netLen); + if (msgLen == 0 || msgLen > BinaryProtocol::MAX_MESSAGE_SIZE) + continue; + std::vector msg(msgLen); + co_await asio::async_read(stream, + asio::buffer(msg), + asio::redirect_error(asio::use_awaitable, ec)); + if (ec) { + Logger::Error("ProcessPool::StartReadingFromWorker: ID {} message read error: {}", worker->GetId(), ec.message()); + break; + } + BinaryProtocol::BinaryReader r(msg.data(), msg.size()); + uint32_t corrId = r.ReadUInt32(); + uint64_t sessionId = r.ReadUInt64(); + uint16_t msgType = r.ReadUInt16(); + uint32_t bodyLen = r.ReadUInt32(); + std::vector body; + if (bodyLen > 0 && r.Remaining() >= bodyLen) + body = r.ReadBytes(bodyLen); + if (masterHandler_) + masterHandler_(worker->GetId(), corrId, sessionId, msgType, std::move(body)); } - }); + Logger::Trace("ProcessPool::StartReadingFromWorker coroutine finished ID {}", worker->GetId()); + }, asio::detached); } bool ProcessPool::SendToWorker(int workerId, const std::vector& message) { @@ -174,17 +227,30 @@ bool ProcessPool::SendToWorker(int workerId, const std::vector& message return false; } -void ProcessPool::BroadcastToOtherWorkers(const nlohmann::json& msg, int senderId) { - std::string serialized = msg.dump(); - std::vector data(serialized.begin(), serialized.end()); +void ProcessPool::BroadcastToOtherWorkers(const std::vector& msg, int owner_id) { + BinaryProtocol::BinaryWriter w; + w.WriteUInt32(0); // correlationId = 0 (broadcast) + w.WriteUInt64(0); // sessionId = 0 → worker‑side broadcast + w.WriteUInt16(0); + w.WriteUInt32(static_cast(msg.size())); + w.WriteRaw(msg.data(), msg.size()); + auto frame = w.GetBuffer(); for (auto& w : workers_) - if (w->GetId() != senderId) w->SendAsync(data); + if (w->GetId() != owner_id) + w->SendAsync(frame); } -void ProcessPool::BroadcastToAllWorkers(const nlohmann::json& msg) { - std::string serialized = msg.dump(); - std::vector data(serialized.begin(), serialized.end()); - for (auto& w : workers_) w->SendAsync(data); +void ProcessPool::BroadcastToAllWorkers(const std::vector& msg) { + BinaryProtocol::BinaryWriter w; + w.WriteUInt32(0); // correlationId = 0 (broadcast) + w.WriteUInt64(0); // sessionId = 0 → worker‑side broadcast + w.WriteUInt16(0); + w.WriteUInt32(static_cast(msg.size())); + w.WriteRaw(msg.data(), msg.size()); + auto frame = w.GetBuffer(); + for (auto& worker : workers_) { + worker->SendAsync(frame); + } } void ProcessPool::doSpawnWorkers() { @@ -199,9 +265,10 @@ void ProcessPool::doSpawnWorkers() { if (std::string(err.what()) == "worker_function") { int fd = worker->GetMasterReadFd(); worker_(globalId, groups_[gi], fd); + Logger::Trace("ProcessPool::doSpawnWorkers: worker {} exiting cleanly", globalId); _exit(0); } else { - Logger::Error("Worker start failed: {}", err.what()); + Logger::Error("ProcessPool::doSpawnWorkers: worker start failed: {}", err.what()); } } } @@ -221,6 +288,22 @@ void ProcessPool::WaitForWorkers() {} bool ProcessPool::SendReplyToWorker(int workerId, uint32_t correlationId, const std::vector& binaryData) { BinaryProtocol::BinaryWriter w; w.WriteUInt32(correlationId); - w.WriteBytes(binaryData.data(), binaryData.size()); + w.WriteUInt64(0); + w.WriteUInt16(0); + w.WriteUInt32(static_cast(binaryData.size())); + w.WriteRaw(binaryData.data(), binaryData.size()); return SendToWorker(workerId, w.GetBuffer()); } + +bool ProcessPool::PushToWorker(int workerId, uint64_t sessionId, const std::vector& binaryData) { + if (workerId < 0 || workerId >= static_cast(workers_.size())) + return false; + BinaryProtocol::BinaryWriter w; + w.WriteUInt32(0); // corrId = 0 → push marker + w.WriteUInt64(sessionId); // target session + w.WriteUInt16(0); // unused + w.WriteUInt32(static_cast(binaryData.size())); + w.WriteRaw(binaryData.data(), binaryData.size()); + workers_[workerId]->SendAsync(w.GetBuffer()); + return true; +} From 6ba85266ea7a302288a48333d3a550bee7eec90c Mon Sep 17 00:00:00 2001 From: fir <29286243+usermicrodevices@users.noreply.github.com> Date: Fri, 15 May 2026 00:40:47 +0300 Subject: [PATCH 04/15] refactor logger service --- config/core.json | 2 +- include/logging/Logger.hpp | 24 +++++++++--------------- src/logging/Logger.cpp | 24 ++++++++++++++++-------- 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/config/core.json b/config/core.json index 1532859..5894af6 100644 --- a/config/core.json +++ b/config/core.json @@ -144,7 +144,7 @@ "file": "logs/server.log", "max_file_size": 1048576, "max_files": 10, - "pattern": "[%Y-%m-%d %H:%M:%S.%e] [%^%l%$] [%P] [%n] %v" + "pattern": "[%Y-%m-%d %H:%M:%S.%e] [%P] [%^%l%$] [%n] %v" }, "scripting": { diff --git a/include/logging/Logger.hpp b/include/logging/Logger.hpp index aad9f6a..b712682 100644 --- a/include/logging/Logger.hpp +++ b/include/logging/Logger.hpp @@ -13,17 +13,17 @@ #include #include -#include #include +#include #include #include #include #include #include +#include + #include -//#include "config/ConfigManager.hpp" -//#include "network/BinaryProtocol.hpp" template class null_sink : public spdlog::sinks::base_sink { @@ -103,34 +103,28 @@ class Logger { static std::shared_ptr GetLogger(const std::string& name="WorkerMain"); static void AddSink(spdlog::sink_ptr sink); - // template static void Trace(const std::string& fmt, Args... args); - // template static void Debug(const std::string& fmt, Args... args); - // template static void Info(const std::string& fmt, Args... args); - // template static void Warn(const std::string& fmt, Args... args); - // template static void Error(const std::string& fmt, Args... args); - // template static void Critical(const std::string& fmt, Args... args); template static void Trace(const std::string& fmt, Args... args) { - GetLogger()->trace(fmt, args...); + GetLogger()->trace(fmt::runtime(fmt), args...); } template static void Debug(const std::string& fmt, Args... args) { - GetLogger()->debug(fmt, args...); + GetLogger()->debug(fmt::runtime(fmt), args...); } template static void Info(const std::string& fmt, Args... args) { - GetLogger()->info(fmt, args...); + GetLogger()->info(fmt::runtime(fmt), args...); } template static void Warn(const std::string& fmt, Args... args) { - GetLogger()->warn(fmt, args...); + GetLogger()->warn(fmt::runtime(fmt), args...); } template static void Error(const std::string& fmt, Args... args) { - GetLogger()->error(fmt, args...); + GetLogger()->error(fmt::runtime(fmt), args...); } template static void Critical(const std::string& fmt, Args... args) { - GetLogger()->critical(fmt, args...); + GetLogger()->critical(fmt::runtime(fmt), args...); } static void Flush(); diff --git a/src/logging/Logger.cpp b/src/logging/Logger.cpp index 22932a8..003130a 100644 --- a/src/logging/Logger.cpp +++ b/src/logging/Logger.cpp @@ -16,13 +16,16 @@ void LogSink::sink_it_(const spdlog::details::log_msg& msg) { formatter_->format(msg, formatted); std::string line(formatted.data(), formatted.size()); if (!line.empty() && line.back() == '\n') line.pop_back(); - if(service_) + if (service_) { service_->EnqueueLog(std::move(line)); - else - { + } else if (socket_ && socket_->is_open()) { line += '\n'; - std::lock_guard lock(writeMutex_); - asio::write(*socket_, asio::buffer(line)); + try { + std::lock_guard lock(writeMutex_); + asio::write(*socket_, asio::buffer(line)); + } catch (const std::exception& err) { + socket_->close(); + } } } @@ -156,9 +159,13 @@ void LogService::rotateFileIfNeeded() { for (int i = static_cast(maxFiles_) - 1; i > 0; --i) { auto oldName = logFilePath_ + "." + std::to_string(i); auto newName = logFilePath_ + "." + std::to_string(i + 1); - std::filesystem::rename(oldName, newName); + if (std::filesystem::exists(oldName)) { + std::filesystem::rename(oldName, newName); + } + } + if (std::filesystem::exists(logFilePath_)) { + std::filesystem::rename(logFilePath_, logFilePath_ + ".1"); } - std::filesystem::rename(logFilePath_, logFilePath_ + ".1"); } logFile_.open(logFilePath_, std::ios::out | std::ios::trunc); currentFileSize_ = 0; @@ -210,7 +217,8 @@ void Logger::SetupLogger(const std::string& loggerName, nlohmann::json config) { spdlog::level::level_enum logLevel = spdlog::level::from_str(logLevelStr); std::string pattern = config.at("pattern").get(); if (pattern.empty()) {pattern = "[%Y-%m-%d %H:%M:%S.%e] [%P] [%^%l%$] [%n] %v";} - if (sinks.empty() && !g_logService) { + //if (sinks.empty() && !g_logService) { + if (config.at("console").get()) { auto console_sink = std::make_shared(); console_sink->set_level(spdlog::level::info); console_sink->set_pattern(pattern); From 72123db0b68fc623aa5d47ff7459767615751f66 Mon Sep 17 00:00:00 2001 From: fir <29286243+usermicrodevices@users.noreply.github.com> Date: Thu, 4 Jun 2026 14:43:20 +0300 Subject: [PATCH 05/15] changed usage to latest versions --- CMakeLists.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 92fc0d3..acf9e84 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.15) +cmake_minimum_required(VERSION 3.21) project(GameServer) set(CMAKE_CXX_STANDARD 20) @@ -6,6 +6,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(fmt REQUIRED) +find_package(asio::asio QUIET) if(NOT TARGET asio::asio) message(STATUS "ASIO not found via find_package, using manual configuration") set(ASIO_DIR "${CMAKE_SOURCE_DIR}/thirdparty/asio") @@ -23,21 +24,20 @@ set(GLM_DIR "${CMAKE_SOURCE_DIR}/thirdparty/glm") set(GLM_INCLUDE_DIRS "${GLM_DIR}") include_directories(${GLM_INCLUDE_DIRS}) -find_package(nlohmann_json 3.2.0 QUIET) +find_package(nlohmann_json QUIET) if(NOT nlohmann_json_FOUND) message(WARNING "nlohmann_json not found, using FetchContent...") include(FetchContent) FetchContent_Declare( nlohmann_json GIT_REPOSITORY https://github.com/nlohmann/json.git - GIT_TAG v3.11.2 + GIT_TAG develop ) FetchContent_MakeAvailable(nlohmann_json) endif() -find_package(spdlog 1.13.0 QUIET) +find_package(spdlog QUIET) if(NOT spdlog_FOUND) - # Fall back to cloned version message(STATUS "System spdlog not found, using local clone") set(SPDLOG_DIR "${CMAKE_SOURCE_DIR}/thirdparty/spdlog") add_subdirectory(${SPDLOG_DIR} EXCLUDE_FROM_ALL) From 4e0e3819a6831cf017dc5b2a2f98685a489d3fee Mon Sep 17 00:00:00 2001 From: fir <29286243+usermicrodevices@users.noreply.github.com> Date: Thu, 4 Jun 2026 14:44:09 +0300 Subject: [PATCH 06/15] refactor cross-process ipc communications --- include/process/ProcessPool.hpp | 18 +++--- src/network/BinaryProtocol.cpp | 8 ++- src/network/BinarySession.cpp | 42 ++++++------ src/network/ClientListener.cpp | 2 + src/network/MasterServer.cpp | 10 ++- src/process/ProcessPool.cpp | 109 +++++++++++++++++--------------- 6 files changed, 105 insertions(+), 84 deletions(-) diff --git a/include/process/ProcessPool.hpp b/include/process/ProcessPool.hpp index e28f417..73d02d2 100644 --- a/include/process/ProcessPool.hpp +++ b/include/process/ProcessPool.hpp @@ -65,9 +65,10 @@ class ProcessWorker : public std::enable_shared_from_this { )>; asio::posix::stream_descriptor& GetMasterStream(); void SendAsync(const std::vector& binaryData); - void StartWriterThread(); - void StopWriter(); - void JoinWriterThread(); + // void StartWriterThread(); + // void StopWriter(); + // void JoinWriterThread(); + //Delete writerLoop(), StartWriterThread(), StopWriter(), JoinWriterThread(), and the writerRunning_ flag private: int workerId_; @@ -78,14 +79,13 @@ class ProcessWorker : public std::enable_shared_from_this { int masterWriteFd_; int masterFd_; asio::posix::stream_descriptor masterStream_; - std::mutex writeMutex_; - std::deque> sendQueue_; - std::mutex sendMutex_; + std::deque> write_queue_; + std::mutex write_mutex_; std::condition_variable sendCv_; bool writing_ = false; - std::thread writerThread_; - bool writerRunning_{true}; - void writerLoop(); + //std::thread writerThread_; + //bool writerRunning_{true}; + //void writerLoop(); void doWrite(); }; diff --git a/src/network/BinaryProtocol.cpp b/src/network/BinaryProtocol.cpp index ce10a1d..c4312b9 100644 --- a/src/network/BinaryProtocol.cpp +++ b/src/network/BinaryProtocol.cpp @@ -225,12 +225,14 @@ namespace BinaryProtocol { std::vector CompressData(const std::vector& data, int level) { if (data.empty()) return {}; uLongf compressed_size = compressBound(data.size()); - std::vector compressed(compressed_size); - if (compress2(compressed.data(), &compressed_size, + std::vector compressed(4 + compressed_size); + uint32_t original_size = static_cast(data.size()); + memcpy(compressed.data(), &original_size, sizeof(original_size)); + if (compress2(compressed.data() + 4, &compressed_size, data.data(), data.size(), level) != Z_OK) { throw std::runtime_error("Compression failed"); } - compressed.resize(compressed_size); + compressed.resize(4 + compressed_size); return compressed; } diff --git a/src/network/BinarySession.cpp b/src/network/BinarySession.cpp index 5d60e54..4418966 100644 --- a/src/network/BinarySession.cpp +++ b/src/network/BinarySession.cpp @@ -181,32 +181,32 @@ asio::ip::tcp::endpoint BinarySession::GetRemoteEndpoint() const { void BinarySession::DoBinaryRead() { if (!connected_ || closing_) return; auto self = shared_from_this(); - BinaryProtocol::NetworkHeader header; + auto header = std::make_shared(); asio::async_read(GetSocket(), - asio::buffer(&header, sizeof(BinaryProtocol::NetworkHeader)), + asio::buffer(header.get(), sizeof(BinaryProtocol::NetworkHeader)), [self, header](std::error_code ec, std::size_t length) mutable { Logger::Debug("BinarySession::DoBinaryRead asio::async_read length = {}", length); if (ec) { self->HandleNetworkError(ec); return; } - if (header.version > BinaryProtocol::CURRENT_PROTOCOL_VERSION) { + if (header->version > BinaryProtocol::CURRENT_PROTOCOL_VERSION) { Logger::Warn("Session {}: incompatible protocol version {}", - self->sessionId_, header.version); + self->sessionId_, header->version); self->SendError(BinaryProtocol::MESSAGE_TYPE_ERROR, "Incompatible protocol version", 400); self->DoBinaryRead(); return; } - if (header.length > BinaryProtocol::MAX_MESSAGE_SIZE) { + if (header->length > BinaryProtocol::MAX_MESSAGE_SIZE) { Logger::Error("Session {}: message too large: {} bytes", - self->sessionId_, header.length); + self->sessionId_, header->length); self->Stop(); return; } - if (header.length == 0) { + if (header->length == 0) { BinaryProtocol::BinaryMessage message; - message.header = header; + message.header = *header; self->HandleBinaryMessage(message); self->DoBinaryRead(); return; @@ -219,7 +219,7 @@ void BinarySession::DoBinaryRead() { self->Stop(); } }); - std::vector body(header.length); + std::vector body(header->length); asio::async_read(self->GetSocket(), asio::buffer(body), [self, header, body, deadline](std::error_code ec, std::size_t length) mutable { @@ -230,7 +230,7 @@ void BinarySession::DoBinaryRead() { } Logger::Debug("BinarySession::DoBinaryRead asio::async_read length = {}", length); uint32_t calculated = BinaryProtocol::CalculateCRC32(body.data(), body.size()); - if (calculated != header.checksum) { + if (calculated != header->checksum) { Logger::Error("Session {}: checksum mismatch", self->sessionId_); self->SendError(BinaryProtocol::MESSAGE_TYPE_ERROR, "Checksum error", 400); @@ -238,7 +238,7 @@ void BinarySession::DoBinaryRead() { return; } std::vector processed_body = body; - if (header.flags & BinaryProtocol::FLAG_COMPRESSED) { + if (header->flags & BinaryProtocol::FLAG_COMPRESSED) { try { processed_body = BinaryProtocol::DecompressData(body); } catch (const std::exception& e) { @@ -250,13 +250,13 @@ void BinarySession::DoBinaryRead() { return; } } - self->network_monitor_.RecordPacketReceived(header.sequence, processed_body.size()); + self->network_monitor_.RecordPacketReceived(header->sequence, processed_body.size()); BinaryProtocol::BinaryMessage message; - message.header = header; + message.header = *header; message.data = processed_body; self->HandleBinaryMessage(message); - if (header.flags & BinaryProtocol::FLAG_RELIABLE) { - self->SendAcknowledgment(header.sequence); + if (header->flags & BinaryProtocol::FLAG_RELIABLE) { + self->SendAcknowledgment(header->sequence); } if (self->connected_ && !self->closing_) { self->DoBinaryRead(); @@ -450,7 +450,6 @@ void BinarySession::DoBinaryWrite() { asio::async_write(GetSocket(), asio::buffer(data), [self](std::error_code ec, std::size_t length) { - std::lock_guard lock(self->write_mutex_); if (ec) { Logger::Error("Session {} write error: {}", self->sessionId_, ec.message()); @@ -458,10 +457,15 @@ void BinarySession::DoBinaryWrite() { return; } self->RecordMessageSent(length); - if (!self->write_queue_.empty()) { - self->write_queue_.pop(); + bool needs_write = false; + { + std::lock_guard lock(self->write_mutex_); + if (!self->write_queue_.empty()) { + self->write_queue_.pop(); + } + needs_write = !self->write_queue_.empty(); } - if (!self->write_queue_.empty()) { + if (needs_write) { self->DoBinaryWrite(); } }); diff --git a/src/network/ClientListener.cpp b/src/network/ClientListener.cpp index ef88cc7..03d622c 100644 --- a/src/network/ClientListener.cpp +++ b/src/network/ClientListener.cpp @@ -112,6 +112,8 @@ void ClientListener::doRead() { uint64_t sessionId = r.ReadUInt64();(void)sessionId; uint16_t msgType = r.ReadUInt16();(void)msgType; uint32_t bodyLen = r.ReadUInt32(); + Logger::Trace("ClientListener::doRead: worker received message from master corrId={}, sessionId={}, msgType={}, bodySize={}", + corrId, sessionId, msgType, bodyLen); std::vector body; if (bodyLen > 0 && r.Remaining() >= bodyLen) { body = r.ReadBytes(bodyLen); diff --git a/src/network/MasterServer.cpp b/src/network/MasterServer.cpp index 16c8cd5..8ba9dd8 100644 --- a/src/network/MasterServer.cpp +++ b/src/network/MasterServer.cpp @@ -50,7 +50,9 @@ void MasterServer::Initialize() w.WriteString(message); w.WriteUInt64(timestamp); std::vector response = w.GetBuffer(); + processPool_.PushToWorker(static_cast(sessionId >> 48), sessionId, response); processPool_.BroadcastToAllWorkers(response); + //processPool_.BroadcastToOtherWorkers(response, static_cast(sessionId >> 48)); break; } case BinaryProtocol::MESSAGE_TYPE_CHUNK_PARAMS: { @@ -242,12 +244,14 @@ void MasterServer::WorkerClient(int workerId, const WorkerGroupConfig& groupConf void MasterServer::WireCallbacks() { gameLogic_.SetSendAuthenticationResponseCallback([this](uint64_t session_id, const std::string& message, uint64_t player_id) { + Logger::Trace("MasterServer::WireCallbacks auth response: session={}, player={}, message={}", session_id, player_id, message); BinaryProtocol::BinaryWriter w; w.WriteUInt16(BinaryProtocol::MESSAGE_TYPE_AUTHENTICATION); w.WriteUInt64(gameLogic_.GetCurrentTimestamp()); w.WriteUInt64(player_id); w.WriteString(message); - auto buf = w.GetBuffer(); if (!buf.empty()) Logger::Trace("Pushing from callback: first byte = 0x{:02x}", buf[0]); + auto buf = w.GetBuffer(); + Logger::Trace("MasterServer::WireCallbacks auth response buffer size: {} bytes", buf.size()); processPool_.PushToWorker(static_cast(session_id >> 48), session_id, buf); }); gameLogic_.SetSendChunkParamsCallback([this](uint64_t session_id, const ChunkParams& data) { @@ -256,7 +260,7 @@ void MasterServer::WireCallbacks() w.WriteUInt64(data.timestamp); w.WriteUInt32(static_cast(data.size)); w.WriteFloat(data.spacing); - auto buf = w.GetBuffer(); if (!buf.empty()) Logger::Trace("Pushing from callback: first byte = 0x{:02x}", buf[0]); + auto buf = w.GetBuffer(); processPool_.PushToWorker(static_cast(session_id >> 48), session_id, buf); }); gameLogic_.SetSendChunkCallback([this](uint64_t session_id, const ChunkData& data) { @@ -272,7 +276,7 @@ void MasterServer::WireCallbacks() w.WriteUInt32(idxSize); w.WriteBytes(reinterpret_cast(data.indices.data()), idxSize); w.WriteUInt32(data.lod); - auto buf = w.GetBuffer(); if (!buf.empty()) Logger::Trace("Pushing from callback: first byte = 0x{:02x}", buf[0]); + auto buf = w.GetBuffer(); processPool_.PushToWorker(static_cast(session_id >> 48), session_id, buf); }); gameLogic_.SetSendCollisionResponseCallback([this](uint64_t session_id, const CollisionResult& result) { diff --git a/src/process/ProcessPool.cpp b/src/process/ProcessPool.cpp index 9cf1f6b..a0ca90f 100644 --- a/src/process/ProcessPool.cpp +++ b/src/process/ProcessPool.cpp @@ -1,7 +1,5 @@ #include "process/ProcessPool.hpp" -//extern std::atomic g_shutdown; - ProcessWorker::ProcessWorker(asio::io_context& io, int globalId, const WorkerGroupConfig& cfg) : workerId_(globalId), config_(cfg), io_(io), pid_(-1), masterReadFd_(-1), masterWriteFd_(-1), masterStream_(io_) {} @@ -51,32 +49,41 @@ void ProcessWorker::SendAsync(const std::vector& binaryData) { std::vector frame(sizeof(len) + binaryData.size()); memcpy(frame.data(), &len, sizeof(len)); memcpy(frame.data() + sizeof(len), binaryData.data(), binaryData.size()); - { - std::lock_guard lock(sendMutex_); - sendQueue_.push_back(std::move(frame)); - } - sendCv_.notify_one(); + asio::post(io_, [this, frame = std::move(frame)]() { + bool start_write; + { + std::lock_guard lock(write_mutex_); + write_queue_.push_back(std::move(frame)); + start_write = !writing_; + if (start_write) writing_ = true; + } + if (start_write) { + doWrite(); + } + }); } void ProcessWorker::doWrite() { - auto self = shared_from_this(); std::vector data; { - std::lock_guard lock(sendMutex_); - if (sendQueue_.empty()) { + std::lock_guard lock(write_mutex_); + if (write_queue_.empty()) { writing_ = false; return; } - data = std::move(sendQueue_.front()); - sendQueue_.pop_front(); + data = std::move(write_queue_.front()); + write_queue_.pop_front(); + writing_ = true; } + auto self = shared_from_this(); asio::async_write(masterStream_, asio::buffer(data), [self, data](std::error_code ec, size_t /*bytes*/) { if (ec) { - Logger::Error("Master async_write to worker {} failed: {}", + Logger::Error("ProcessWorker::doWrite: master async_write to worker {} failed: {}", self->workerId_, ec.message()); } - self->doWrite(); + else + self->doWrite(); }); } @@ -102,41 +109,42 @@ void ProcessWorker::Shutdown() { pid_ = -1; } -void ProcessWorker::writerLoop() { - while (true) { - Logger::Trace("ProcessWorker::writerLoop ID {}", workerId_); - std::vector data; - { - std::unique_lock lock(sendMutex_); - sendCv_.wait(lock, [this] {return !writerRunning_ || !sendQueue_.empty();}); - if (!writerRunning_ && sendQueue_.empty()) return; - data = std::move(sendQueue_.front()); - sendQueue_.pop_front(); - } - Logger::Trace("ProcessWorker::writerLoop {} writing {} bytes", workerId_, data.size()); - std::error_code ec; - asio::write(masterStream_, asio::buffer(data), ec); - Logger::Trace("ProcessWorker::writerLoop {} write completed: ec = {}", workerId_, ec.message()); - if (ec) { - Logger::Error("ProcessWorker::writerLoop for worker {} failed: {}", workerId_, ec.message()); - break; - } - } -} +// void ProcessWorker::writerLoop() { +// while (true) { +// Logger::Trace("ProcessWorker::writerLoop ID {}", workerId_); +// std::vector data; +// { +// std::unique_lock lock(sendMutex_); +// sendCv_.wait(lock, [this] {return !writerRunning_ || !sendQueue_.empty();}); +// if (!writerRunning_ && sendQueue_.empty()) return; +// data = std::move(sendQueue_.front()); +// sendQueue_.pop_front(); +// } +// Logger::Trace("ProcessWorker::writerLoop {} writing {} bytes", workerId_, data.size()); +// std::error_code ec; +// asio::write(masterStream_, asio::buffer(data), ec); +// Logger::Trace("ProcessWorker::writerLoop {} write completed: ec = {}", workerId_, ec.message()); +// if (ec) { +// Logger::Error("ProcessWorker::writerLoop for worker {} failed: {}", workerId_, ec.message()); +// break; +// } +// } +// } -void ProcessWorker::StartWriterThread() { - writerThread_ = std::thread(&ProcessWorker::writerLoop, this); -} +// void ProcessWorker::StartWriterThread() { +// writerRunning_ = true; +// writerThread_ = std::thread(&ProcessWorker::writerLoop, this); +// } -void ProcessWorker::StopWriter() { - writerRunning_ = false; - sendCv_.notify_all(); -} +// void ProcessWorker::StopWriter() { +// writerRunning_ = false; +// sendCv_.notify_all(); +// } -void ProcessWorker::JoinWriterThread() { - if (writerThread_.joinable()) - writerThread_.join(); -} +// void ProcessWorker::JoinWriterThread() { +// if (writerThread_.joinable()) +// writerThread_.join(); +// } ProcessPool::ProcessPool(asio::io_context& io, const std::vector& groups) : io_(io), groups_(groups) {} @@ -144,6 +152,7 @@ ProcessPool::ProcessPool(asio::io_context& io, const std::vectorStartWriterThread(); StartReadingFromWorker(w); } ready_.store(true); @@ -157,14 +166,14 @@ void ProcessPool::Shutdown() { Logger::Trace("ProcessPool::Shutdown: running..."); for (auto& w : workers_) w->Shutdown(); Logger::Trace("ProcessPool::Shutdown: workers_->Shutdown() finished"); - for (auto& w : workers_) w->StopWriter(); - Logger::Trace("ProcessPool::Shutdown: workers_->StopWriter() finished"); + // for (auto& w : workers_) w->StopWriter(); + // Logger::Trace("ProcessPool::Shutdown: workers_->StopWriter() finished"); for (auto& pair : readerRunningFlags_) *pair.second = false; Logger::Trace("ProcessPool::Shutdown: readerRunningFlags_ finished"); io_.stop(); Logger::Trace("ProcessPool::Shutdown: io_.stop finished"); - for (auto& w : workers_) w->JoinWriterThread(); - Logger::Trace("ProcessPool::Shutdown: workers_->JoinWriterThread() finished"); + // for (auto& w : workers_) w->JoinWriterThread(); + // Logger::Trace("ProcessPool::Shutdown: workers_->JoinWriterThread() finished"); } void ProcessPool::SetWorker(std::function func) { From 5b9c70affe49a3c532dfad9af3a772221c3ad3e2 Mon Sep 17 00:00:00 2001 From: fir <29286243+usermicrodevices@users.noreply.github.com> Date: Thu, 4 Jun 2026 21:01:43 +0300 Subject: [PATCH 07/15] refactor workers config subgroup --- config/core.json | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/config/core.json b/config/core.json index 5894af6..4000272 100644 --- a/config/core.json +++ b/config/core.json @@ -1,8 +1,11 @@ { - "process": { + "workers": { "max_message_size": 1048576, - "receive_timeout_ms": 1000, - "workers": [ + "game_logic": { + "check_interval_ms": 100, + "max_restart_attempts": 5 + }, + "clients": [ { "protocol": "binary", "host": "127.0.0.1", From 2adc1f4fa2e53c590e6580d0dd59b29847abf36a Mon Sep 17 00:00:00 2001 From: fir <29286243+usermicrodevices@users.noreply.github.com> Date: Thu, 4 Jun 2026 21:02:25 +0300 Subject: [PATCH 08/15] added new sources --- CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index acf9e84..76be2c6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -95,6 +95,7 @@ set(LOGGING_SOURCES # Process system set(PROCESS_SOURCES + src/process/IPCChannel.cpp src/process/ProcessPool.cpp ) @@ -112,6 +113,7 @@ set(NETWORK_SOURCES src/network/BinarySession.cpp src/network/ConnectionManager.cpp src/network/ClientListener.cpp + src/network/MasterRouter.cpp src/network/MasterServer.cpp src/network/NetworkQualityMonitor.cpp src/network/PredictionSystem.cpp From e81def37fb6a3396557c21dba9da09f0fe6497a6 Mon Sep 17 00:00:00 2001 From: fir <29286243+usermicrodevices@users.noreply.github.com> Date: Thu, 4 Jun 2026 21:02:59 +0300 Subject: [PATCH 09/15] refactor cross-process architecture --- include/game/GameLogic.hpp | 2 +- include/network/ClientListener.hpp | 15 +- include/network/MasterRouter.hpp | 45 ++++ include/network/MasterServer.hpp | 17 +- include/process/IPCChannel.hpp | 54 ++++ include/process/ProcessPool.hpp | 57 ++-- src/config/ConfigManager.cpp | 14 +- src/network/BinarySession.cpp | 14 - src/network/ClientListener.cpp | 110 ++------ src/network/MasterRouter.cpp | 368 ++++++++++++++++++++++++++ src/network/MasterServer.cpp | 403 ++++++++++++++++------------- src/process/IPCChannel.cpp | 137 ++++++++++ src/process/ProcessPool.cpp | 351 +++++++++++-------------- 13 files changed, 1044 insertions(+), 543 deletions(-) create mode 100644 include/network/MasterRouter.hpp create mode 100644 include/process/IPCChannel.hpp create mode 100644 src/network/MasterRouter.cpp create mode 100644 src/process/IPCChannel.cpp diff --git a/include/game/GameLogic.hpp b/include/game/GameLogic.hpp index eec758b..e923acd 100644 --- a/include/game/GameLogic.hpp +++ b/include/game/GameLogic.hpp @@ -127,7 +127,7 @@ class GameLogic : public LogicCore, public std::enable_shared_from_this backend); DatabaseBackend* GetDatabaseBackend() const; -private: +public: GameLogic(); ~GameLogic(); diff --git a/include/network/ClientListener.hpp b/include/network/ClientListener.hpp index 3419795..6ed7fc8 100644 --- a/include/network/ClientListener.hpp +++ b/include/network/ClientListener.hpp @@ -3,12 +3,13 @@ #include #include #include -#include -#include + #include + #include "config/ConfigManager.hpp" #include "network/BinaryProtocol.hpp" #include "network/ConnectionManager.hpp" +#include "process/IPCChannel.hpp" class ClientListener { public: @@ -19,15 +20,11 @@ class ClientListener { private: int workerId_; asio::io_context io_; - asio::posix::stream_descriptor pipe_; + std::unique_ptr channel_; std::thread ioThread_; std::atomic stopping_{false}; std::shared_ptr manager_; - std::deque> writeQueue_; - std::mutex writeMutex_; - bool writing_ = false; + + void onMasterMessage(const IPCEnvelope& env); void sendToMaster(uint32_t correlationId, uint64_t sessionId, uint16_t messageType, const std::vector& body); - void startWrite(); - void doWrite(); - void doRead(); }; diff --git a/include/network/MasterRouter.hpp b/include/network/MasterRouter.hpp new file mode 100644 index 0000000..95e02a1 --- /dev/null +++ b/include/network/MasterRouter.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "logging/Logger.hpp" +#include "network/BinaryProtocol.hpp" +#include "process/IPCChannel.hpp" +#include "game/GameData.hpp" + +class ProcessPool; +class GameLogic; + +struct PendingRequest { + int workerId; + uint32_t correlationId; +}; + +class MasterRouter { +public: + MasterRouter(ProcessPool& pool, GameLogic& gameLogic); + void Initialize(); + + void OnChildWorkerMessage(int workerId, uint32_t corrId, + uint64_t sessionId, uint16_t msgType, + const std::vector& body); + + void OnGameLogicResponse(const IPCEnvelope& env); + + void SendToChildWorker(int workerId, uint64_t sessionId, const std::vector& data); + void BroadcastToChildWorkers(const std::vector& data); + +private: + ProcessPool& pool_; + GameLogic& gameLogic_; + + std::function&)> sendReplyCb_; + + void WireCallbacks(); + void RouteToGameLogic(uint64_t sessionId, uint16_t msgType, const std::vector& body); +}; diff --git a/include/network/MasterServer.hpp b/include/network/MasterServer.hpp index d08b0ea..b3436e0 100644 --- a/include/network/MasterServer.hpp +++ b/include/network/MasterServer.hpp @@ -20,13 +20,9 @@ #include "process/ProcessPool.hpp" #include "network/BinaryProtocol.hpp" #include "network/ClientListener.hpp" +#include "network/MasterRouter.hpp" #include "game/GameLogic.hpp" -struct PendingRequest { - int workerId; - uint32_t correlationId; -}; - class MasterServer { public: MasterServer(asio::io_context& io, @@ -42,6 +38,8 @@ class MasterServer { static void WorkerClient(int workerId, const WorkerGroupConfig& groupConfig, int masterReadFd, const std::string& configPath); + static void GameLogicClient(int workerId, int masterFd, const std::string& configPath); + private: asio::io_context& io_; asio::posix::stream_descriptor signal_pipe_; @@ -49,16 +47,9 @@ class MasterServer { GameLogic& gameLogic_; ProcessPool processPool_; + MasterRouter router_; const ConfigManager& config_; std::string configPath_; - std::unordered_map sessionToVirtual_; - std::function&)> sendReplyCb_; - std::function assignVirtualId_; - std::atomic nextPersistentId_{1}; - std::unordered_map pendingReplies_; - void start_signal_read(); - void WireCallbacks(); - void SendResponse(uint64_t sessionId, const std::vector& buffer); }; diff --git a/include/process/IPCChannel.hpp b/include/process/IPCChannel.hpp new file mode 100644 index 0000000..5769dc1 --- /dev/null +++ b/include/process/IPCChannel.hpp @@ -0,0 +1,54 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "network/BinaryProtocol.hpp" + +struct IPCEnvelope { + uint32_t correlationId = 0; + uint64_t sessionId = 0; + uint16_t messageType = 0; + std::vector payload; +}; + +class IPCChannel : public std::enable_shared_from_this { +public: + IPCChannel(asio::io_context& io, int fd); + ~IPCChannel(); + + IPCChannel(const IPCChannel&) = delete; + IPCChannel& operator=(const IPCChannel&) = delete; + + void Start(std::function onMessage); + void Stop(); + + void SendAsync(const IPCEnvelope& envelope); + void SendSync(const IPCEnvelope& envelope); + + bool IsOpen() const; + int GetFd(); + +private: + asio::io_context& io_; + asio::posix::stream_descriptor stream_; + std::function onMessage_; + std::atomic stopped_{false}; + + std::deque> writeQueue_; + std::mutex writeMutex_; + bool writing_ = false; + + void doRead(); + void doWrite(); + static std::vector encodeFrame(const IPCEnvelope& env); + static IPCEnvelope decodeFrame(const uint8_t* data, size_t len); +}; diff --git a/include/process/ProcessPool.hpp b/include/process/ProcessPool.hpp index 73d02d2..ef26fb5 100644 --- a/include/process/ProcessPool.hpp +++ b/include/process/ProcessPool.hpp @@ -37,25 +37,22 @@ using asio::use_awaitable; #include "logging/Logger.hpp" #include "config/ConfigManager.hpp" #include "network/BinaryProtocol.hpp" +#include "process/IPCChannel.hpp" - -struct IPCEnvelope { - uint32_t correlationId; - uint64_t sessionId; - uint16_t messageType; - std::vector payload; -}; +enum class WorkerType { Child, GameLogic }; class ProcessWorker : public std::enable_shared_from_this { public: - ProcessWorker(asio::io_context& io, int globalId, const WorkerGroupConfig& cfg); + ProcessWorker(asio::io_context& io, int globalId, const WorkerGroupConfig& cfg, WorkerType type = WorkerType::Child); ~ProcessWorker(); void Start(); - void Send(const std::vector& binaryData); void Shutdown(); int GetId() const; pid_t GetPid() const; - int GetMasterReadFd() const; + WorkerType GetType() const; + + std::shared_ptr GetChannel() { return channel_; } + using MasterMessageHandler = std::function { uint16_t messageType, const std::vector& body )>; - asio::posix::stream_descriptor& GetMasterStream(); - void SendAsync(const std::vector& binaryData); - // void StartWriterThread(); - // void StopWriter(); - // void JoinWriterThread(); - //Delete writerLoop(), StartWriterThread(), StopWriter(), JoinWriterThread(), and the writerRunning_ flag private: int workerId_; WorkerGroupConfig config_; + WorkerType type_; asio::io_context& io_; pid_t pid_; - int masterReadFd_; - int masterWriteFd_; - int masterFd_; - asio::posix::stream_descriptor masterStream_; - std::deque> write_queue_; - std::mutex write_mutex_; - std::condition_variable sendCv_; - bool writing_ = false; - //std::thread writerThread_; - //bool writerRunning_{true}; - //void writerLoop(); - void doWrite(); + int masterFd_ = -1; + std::shared_ptr channel_; }; class ProcessPool : public std::enable_shared_from_this { @@ -95,29 +77,40 @@ class ProcessPool : public std::enable_shared_from_this { void Initialize(); void Run(); void Shutdown(); + void SetWorker(std::function func); + void SetGameLogicWorker(std::function func); + bool SendToWorker(int workerId, const std::vector& message); void BroadcastToOtherWorkers(const std::vector& msg, int owner_id); void BroadcastToAllWorkers(const std::vector& msg); + size_t GetTotalWorkerCount() const; bool IsWorkerAlive(int workerId) const; bool IsWorkersReady() const; void WaitForWorkers(); + void SetMasterMessageHandler(ProcessWorker::MasterMessageHandler handler); bool SendReplyToWorker(int workerId, uint32_t correlationId, const std::vector& binaryData); bool PushToWorker(int workerId, uint64_t sessionId, const std::vector& binaryData); + int SpawnGameLogicWorker(); + bool RespawnGameLogicWorker(); + bool IsGameLogicWorkerAlive() const; + std::shared_ptr GetGameLogicChannel() const; + private: asio::io_context& io_; std::vector groups_; std::vector> workers_; std::function worker_; - int workerId_ = -1; + std::function gameLogicWorkerFunc_; + int gameLogicWorkerId_ = -1; std::atomic running_{false}; std::atomic ready_{false}; ProcessWorker::MasterMessageHandler masterHandler_; - std::vector readerThreads_; - std::unordered_map>> readerRunningFlags_; + void doSpawnWorkers(); - void StartReadingFromWorker(std::shared_ptr worker); + void onWorkerMessage(int workerId, const IPCEnvelope& env); + void onGameLogicMessage(const IPCEnvelope& env); }; diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index a7c6340..229265f 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -55,8 +55,8 @@ const std::string& ConfigManager::GetConfigPath() const { return configPath_; } bool ConfigManager::HasProcessConfig() const { //ATTENTION: RECURSIVELY CALL MUTEX LOCK, DO NOT USE LINE BELOW //std::lock_guard lock(configMutex_); - return config_.contains("process") && config_["process"].contains("workers") && - config_["process"]["workers"].is_array() && !config_["process"]["workers"].empty(); + return config_.contains("workers") && config_["workers"].contains("clients") && + config_["workers"]["clients"].is_array() && !config_["workers"]["clients"].empty(); } std::vector ConfigManager::GetWorkerGroups() const { @@ -66,7 +66,7 @@ std::vector ConfigManager::GetWorkerGroups() const { if (!HasProcessConfig())//ATTENTION: RECURSIVELY CALL MUTEX LOCK return groups; - for (const auto& w : config_["process"]["workers"]) { + for (const auto& w : config_["workers"]["clients"]) { WorkerGroupConfig g; g.protocol = w.value("protocol", "binary"); g.host = w.value("host", "0.0.0.0"); @@ -114,12 +114,12 @@ int ConfigManager::GetTotalThreadCount() const { bool ConfigManager::ValidateConfig(const nlohmann::json& config) const { Logger::Info("Validate config started..."); try { - if (!config.contains("process") || !config["process"].contains("workers") || - !config["process"]["workers"].is_array() || config["process"]["workers"].empty()) { - throw std::runtime_error("Missing 'process.workers' array section"); + if (!config.contains("workers") || !config["workers"].contains("clients") || + !config["workers"]["clients"].is_array() || config["workers"]["clients"].empty()) { + throw std::runtime_error("Missing 'workers.clients' array section"); } - const auto& workers = config["process"]["workers"]; + const auto& workers = config["workers"]["clients"]; for (size_t i = 0; i < workers.size(); ++i) { const auto& w = workers[i]; std::string proto = w.value("protocol", "binary"); diff --git a/src/network/BinarySession.cpp b/src/network/BinarySession.cpp index 4418966..65bd2b9 100644 --- a/src/network/BinarySession.cpp +++ b/src/network/BinarySession.cpp @@ -1,4 +1,3 @@ -#include "game/GameLogic.hpp" #include "network/BinarySession.hpp" std::atomic BinarySession::nextSessionId_{1}; @@ -279,19 +278,6 @@ void BinarySession::HandleBinaryMessage(const BinaryProtocol::BinaryMessage& mes case BinaryProtocol::MESSAGE_TYPE_PROTOCOL_NEGOTIATION: HandleProtocolNegotiation(message.data); return; - case BinaryProtocol::MESSAGE_TYPE_CHUNK_DATA: { - BinaryProtocol::BinaryReader reader(message.data.data(), message.data.size()); - ChunkData req; - req.x = reader.ReadInt32(); - req.z = reader.ReadInt32(); - req.lod = reader.ReadUInt8(); - req.player_x = reader.ReadFloat(); - req.player_y = reader.ReadFloat(); - req.player_z = reader.ReadFloat(); - req.session_id = sessionId_; - GameLogic::GetInstance().OnChunkData(req); - break; - } case BinaryProtocol::MESSAGE_TYPE_ERROR: Logger::Warn("Session {} received error from client", sessionId_); return; diff --git a/src/network/ClientListener.cpp b/src/network/ClientListener.cpp index 03d622c..1d77eba 100644 --- a/src/network/ClientListener.cpp +++ b/src/network/ClientListener.cpp @@ -3,20 +3,19 @@ ClientListener::ClientListener(const WorkerGroupConfig& groupConfig, int masterFd, int workerId) : workerId_(workerId) , io_() - , pipe_(io_) + , channel_(std::make_unique(io_, masterFd)) , manager_(std::make_shared(groupConfig, [this](uint32_t corrId, uint64_t sessId, uint16_t msgType, const std::vector& body) { sendToMaster(corrId, sessId, msgType, body); }, workerId)) { - if (masterFd != -1) pipe_.assign(masterFd); } ClientListener::~ClientListener() { Shutdown(); } void ClientListener::Start() { std::thread([this]() { manager_->Start(); }).detach(); - doRead(); + channel_->Start([this](const IPCEnvelope& env) { onMasterMessage(env); }); ioThread_ = std::thread([this]() { Logger::Trace("ClientListener::Start: worker {} entering io_context::run()", workerId_); io_.run(); @@ -27,104 +26,25 @@ void ClientListener::Start() { void ClientListener::Shutdown() { if (stopping_.exchange(true)) return; manager_->Shutdown(); + channel_->Stop(); io_.stop(); io_.poll(); if (ioThread_.joinable()) ioThread_.join(); } -void ClientListener::sendToMaster(uint32_t correlationId, uint64_t sessionId, uint16_t messageType, const std::vector& body) { - BinaryProtocol::BinaryWriter w; - w.WriteUInt32(correlationId); - w.WriteUInt64(sessionId); - w.WriteUInt16(messageType); - uint32_t len = static_cast(body.size()); - w.WriteUInt32(len); - w.WriteRaw(body.data(), len); - auto frame = w.GetBuffer(); - uint32_t frameLen = htonl(static_cast(frame.size())); - std::vector packet(sizeof(frameLen) + frame.size()); - memcpy(packet.data(), &frameLen, sizeof(frameLen)); - memcpy(packet.data() + sizeof(frameLen), frame.data(), frame.size()); - Logger::Trace("Worker sending to master: corrId={}, sessId={}, msgType={}, bodySize={}", - correlationId, sessionId, messageType, body.size()); - { - std::lock_guard lock(writeMutex_); - writeQueue_.push_back(std::move(packet)); - if (!writing_) { - writing_ = true; - startWrite(); - } +void ClientListener::onMasterMessage(const IPCEnvelope& env) { + if (env.correlationId == 0) { + manager_->OnMasterPush(env.sessionId, env.payload); + } else { + manager_->OnMasterReply(env.correlationId, env.payload); } } -void ClientListener::startWrite() { - asio::post(io_, [this]() { doWrite(); }); -} - -void ClientListener::doWrite() { - std::vector data; - { - std::lock_guard lock(writeMutex_); - if (writeQueue_.empty()) { - writing_ = false; - return; - } - data = std::move(writeQueue_.front()); - writeQueue_.pop_front(); - } - asio::async_write(pipe_, asio::buffer(data), - [this](std::error_code ec, size_t) { - if (ec) - Logger::Error("Worker write to master failed: {}", ec.message()); - else - doWrite(); - }); -} - -void ClientListener::doRead() { - auto lengthBuf = std::make_shared(0); - asio::async_read(pipe_, asio::buffer(lengthBuf.get(), sizeof(uint32_t)), - [this, lengthBuf](std::error_code ec, size_t) { - if (ec) { - Logger::Error("ClientListener::doRead asio::async_read: {}", ec.message()); - if (!stopping_) doRead(); - return; - } - uint32_t msgLen = ntohl(*lengthBuf); - if (msgLen == 0 || msgLen > 10 * 1024 * 1024) { - doRead(); - return; - } - auto msgBuffer = std::make_shared>(msgLen); - asio::async_read(pipe_, asio::buffer(*msgBuffer), - [this, msgBuffer](std::error_code ec, size_t) { - if (ec) { - Logger::Error("ClientListener::doRead asio::async_read(msgBuffer): {}", ec.message()); - if (!stopping_) doRead(); - return; - } - if (msgBuffer->size() < 18) { - doRead(); - return; - } - BinaryProtocol::BinaryReader r(msgBuffer->data(), msgBuffer->size()); - uint32_t corrId = r.ReadUInt32(); - uint64_t sessionId = r.ReadUInt64();(void)sessionId; - uint16_t msgType = r.ReadUInt16();(void)msgType; - uint32_t bodyLen = r.ReadUInt32(); - Logger::Trace("ClientListener::doRead: worker received message from master corrId={}, sessionId={}, msgType={}, bodySize={}", - corrId, sessionId, msgType, bodyLen); - std::vector body; - if (bodyLen > 0 && r.Remaining() >= bodyLen) { - body = r.ReadBytes(bodyLen); - } - if (corrId == 0) { - manager_->OnMasterPush(sessionId, body); - doRead(); - return; - } - manager_->OnMasterReply(corrId, body); - doRead(); - }); - }); +void ClientListener::sendToMaster(uint32_t correlationId, uint64_t sessionId, uint16_t messageType, const std::vector& body) { + IPCEnvelope env; + env.correlationId = correlationId; + env.sessionId = sessionId; + env.messageType = messageType; + env.payload = body; + channel_->SendAsync(env); } diff --git a/src/network/MasterRouter.cpp b/src/network/MasterRouter.cpp new file mode 100644 index 0000000..de11e11 --- /dev/null +++ b/src/network/MasterRouter.cpp @@ -0,0 +1,368 @@ +#include "network/MasterRouter.hpp" +#include "process/ProcessPool.hpp" +#include "game/GameLogic.hpp" + +MasterRouter::MasterRouter(ProcessPool& pool, GameLogic& gameLogic) + : pool_(pool), gameLogic_(gameLogic) {} + +void MasterRouter::Initialize() { + sendReplyCb_ = [this](uint64_t sessionId, const std::vector& data) { + int workerId = static_cast(sessionId >> 48); + pool_.PushToWorker(workerId, sessionId, data); + }; + gameLogic_.SetSendReplyCallback(sendReplyCb_); + gameLogic_.SetGetSessionIdsInRadiusCallback([](const glm::vec3& pos, float radius) -> std::vector { + std::vector sids; + auto& pm = PlayerManager::GetInstance(); + auto players = pm.GetPlayersInRadius(pos, radius); + for (auto& p : players) { + uint64_t sid = pm.GetSessionIdByPlayerId(p->GetId()); + if (sid != 0) sids.push_back(sid); + } + return sids; + }); + WireCallbacks(); +} + +void MasterRouter::OnChildWorkerMessage(int workerId, uint32_t /*corrId*/, + uint64_t sessionId, uint16_t msgType, + const std::vector& body) { + RouteToGameLogic(sessionId, msgType, body); +} + +void MasterRouter::OnGameLogicResponse(const IPCEnvelope& env) { + int workerId = static_cast(env.sessionId >> 48); + pool_.PushToWorker(workerId, env.sessionId, env.payload); +} + +void MasterRouter::RouteToGameLogic(uint64_t sessionId, uint16_t msgType, const std::vector& body) { + switch (msgType) { + case BinaryProtocol::MESSAGE_TYPE_AUTHENTICATION: { + BinaryProtocol::BinaryReader r(body.data(), body.size()); + AuthenticationData auth; + auth.username = r.ReadString(); + auth.password = r.ReadString(); + auth.session_id = sessionId; + gameLogic_.OnAuthentication(auth); + break; + } + case BinaryProtocol::MESSAGE_TYPE_CHAT_MESSAGE: { + BinaryProtocol::BinaryReader r(body.data(), body.size()); + std::string sender = r.ReadString(); + std::string message = r.ReadString(); + uint64_t timestamp = r.ReadUInt64(); + Logger::Trace("Chat from {} (session {}): {}", sender, sessionId, message); + BinaryProtocol::BinaryWriter w; + w.WriteUInt16(BinaryProtocol::MESSAGE_TYPE_CHAT_MESSAGE); + w.WriteString(sender); + w.WriteString(message); + w.WriteUInt64(timestamp); + std::vector response = w.GetBuffer(); + pool_.PushToWorker(static_cast(sessionId >> 48), sessionId, response); + pool_.BroadcastToAllWorkers(response); + break; + } + case BinaryProtocol::MESSAGE_TYPE_CHUNK_PARAMS: { + ChunkParams req; + req.session_id = sessionId; + gameLogic_.OnChunkParams(req); + break; + } + case BinaryProtocol::MESSAGE_TYPE_CHUNK_DATA: { + BinaryProtocol::BinaryReader r(body.data(), body.size()); + ChunkData req; + req.x = r.ReadInt32(); + req.z = r.ReadInt32(); + req.lod = r.ReadUInt8(); + req.player_x = r.ReadFloat(); + req.player_y = r.ReadFloat(); + req.player_z = r.ReadFloat(); + req.session_id = sessionId; + gameLogic_.OnChunkData(req); + break; + } + case BinaryProtocol::MESSAGE_TYPE_COLLISION_CHECK: { + BinaryProtocol::BinaryReader r(body.data(), body.size()); + CollisionData req; + req.position = r.ReadVector3(); + req.radius = r.ReadFloat(); + req.session_id = sessionId; + gameLogic_.OnCollisionCheck(req); + break; + } + case BinaryProtocol::MESSAGE_TYPE_PLAYER_STATE: { + BinaryProtocol::BinaryReader r(body.data(), body.size()); + PlayerStateData state; + state.player_id = gameLogic_.GetPlayerIdBySession(sessionId); + state.input_id = r.ReadUInt32(); + state.position = r.ReadVector3(); + state.velocity = r.ReadVector3(); + state.rotation = r.ReadVector3(); + state.on_ground = r.ReadUInt8() != 0; + state.timestamp = r.ReadUInt64(); + state.session_id = sessionId; + gameLogic_.OnPlayerState(state); + break; + } + case BinaryProtocol::MESSAGE_TYPE_PLAYER_POSITION: { + BinaryProtocol::BinaryReader r(body.data(), body.size()); + uint64_t player_id = r.ReadUInt64(); + glm::vec3 position = r.ReadVector3(); + glm::vec3 velocity = r.ReadVector3(); + uint64_t timestamp = r.ReadUInt64(); + PlayerPositionData pos; + pos.player_id = player_id; + pos.position = position; + pos.velocity = velocity; + pos.timestamp = timestamp; + pos.session_id = sessionId; + gameLogic_.OnPlayerPosition(pos); + break; + } + case BinaryProtocol::MESSAGE_TYPE_NPC_INTERACTION: { + BinaryProtocol::BinaryReader r(body.data(), body.size()); + NpcData req; + req.npc_id = r.ReadUInt64(); + req.type = r.ReadString(); + req.session_id = sessionId; + gameLogic_.OnNPCInteraction(req); + break; + } + case BinaryProtocol::MESSAGE_TYPE_FAMILIAR_COMMAND: { + BinaryProtocol::BinaryReader r(body.data(), body.size()); + FamiliarData req; + req.session_id = sessionId; + req.familiar_id = r.ReadUInt64(); + req.target_id = r.ReadUInt64(); + req.command = r.ReadString(); + gameLogic_.OnFamiliarCommand(req); + break; + } + case BinaryProtocol::MESSAGE_TYPE_ENTITY_SPAWN: { + BinaryProtocol::BinaryReader r(body.data(), body.size()); + EntitySpawnData req; + req.session_id = sessionId; + req.entity_id = r.ReadUInt64(); + req.type = r.ReadInt32(); + req.position = r.ReadVector3(); + gameLogic_.OnEntitySpawnRequest(req); + break; + } + case BinaryProtocol::MESSAGE_TYPE_LOOT_PICKUP: { + BinaryProtocol::BinaryReader r(body.data(), body.size()); + LootPickupData req; + req.loot_id = r.ReadUInt64(); + req.quantity = r.ReadUInt16(); + req.session_id = sessionId; + gameLogic_.OnLootPickup(req); + break; + } + case BinaryProtocol::MESSAGE_TYPE_INVENTORY_MOVE: { + BinaryProtocol::BinaryReader r(body.data(), body.size()); + InventoryData req; + req.loot_id = r.ReadUInt64(); + req.target_id = r.ReadUInt64(); + req.move_type = static_cast(r.ReadUInt8()); + req.inv_slot_id = r.ReadInt32(); + req.use_slot_id = r.ReadInt32(); + req.quantity = r.ReadUInt16(); + req.session_id = sessionId; + gameLogic_.OnInventory(req); + break; + } + case BinaryProtocol::MESSAGE_TYPE_PLAYER_DISCONNECT: { + gameLogic_.OnPlayerDisconnected(sessionId); + break; + } + default: + Logger::Warn("Unhandled message type from child worker: {}", msgType); + break; + } +} + +void MasterRouter::SendToChildWorker(int workerId, uint64_t sessionId, const std::vector& data) { + pool_.PushToWorker(workerId, sessionId, data); +} + +void MasterRouter::BroadcastToChildWorkers(const std::vector& data) { + pool_.BroadcastToAllWorkers(data); +} + +void MasterRouter::WireCallbacks() { + gameLogic_.SetSendAuthenticationResponseCallback([this](uint64_t session_id, const std::string& message, uint64_t player_id) { + Logger::Trace("MasterRouter auth response: session={}, player={}, message={}", session_id, player_id, message); + BinaryProtocol::BinaryWriter w; + w.WriteUInt16(BinaryProtocol::MESSAGE_TYPE_AUTHENTICATION); + w.WriteUInt64(gameLogic_.GetCurrentTimestamp()); + w.WriteUInt64(player_id); + w.WriteString(message); + auto buf = w.GetBuffer(); + pool_.PushToWorker(static_cast(session_id >> 48), session_id, buf); + }); + gameLogic_.SetSendChunkParamsCallback([this](uint64_t session_id, const ChunkParams& data) { + BinaryProtocol::BinaryWriter w; + w.WriteUInt16(BinaryProtocol::MESSAGE_TYPE_CHUNK_PARAMS); + w.WriteUInt64(data.timestamp); + w.WriteUInt32(static_cast(data.size)); + w.WriteFloat(data.spacing); + auto buf = w.GetBuffer(); + pool_.PushToWorker(static_cast(session_id >> 48), session_id, buf); + }); + gameLogic_.SetSendChunkCallback([this](uint64_t session_id, const ChunkData& data) { + BinaryProtocol::BinaryWriter w; + w.WriteUInt16(BinaryProtocol::MESSAGE_TYPE_CHUNK_DATA); + w.WriteUInt64(data.timestamp); + w.WriteInt32(data.x); + w.WriteInt32(data.z); + uint32_t vertSize = static_cast(data.vertices.size() * sizeof(float)); + w.WriteUInt32(vertSize); + w.WriteBytes(reinterpret_cast(data.vertices.data()), vertSize); + uint32_t idxSize = static_cast(data.indices.size() * sizeof(uint32_t)); + w.WriteUInt32(idxSize); + w.WriteBytes(reinterpret_cast(data.indices.data()), idxSize); + w.WriteUInt32(data.lod); + auto buf = w.GetBuffer(); + pool_.PushToWorker(static_cast(session_id >> 48), session_id, buf); + }); + gameLogic_.SetSendCollisionResponseCallback([this](uint64_t session_id, const CollisionResult& result) { + BinaryProtocol::BinaryWriter w; + w.WriteUInt16(BinaryProtocol::MESSAGE_TYPE_COLLISION_CHECK); + w.WriteUInt64(gameLogic_.GetCurrentTimestamp()); + w.WriteUInt8(result.collided ? 1 : 0); + w.WriteUInt64(result.collided_id); + w.WriteFloat(result.penetration); + w.WriteVector3(result.resolution); + pool_.PushToWorker(static_cast(session_id >> 48), session_id, w.GetBuffer()); + }); + gameLogic_.SetPlayerStateCallback([this](const PlayerStateData& state) { + BinaryProtocol::BinaryWriter w; + w.WriteUInt16(BinaryProtocol::MESSAGE_TYPE_PLAYER_STATE); + w.WriteUInt64(state.timestamp); + w.WriteUInt64(state.player_id); + w.WriteVector3(state.position); + w.WriteVector3(state.velocity); + w.WriteVector3(state.rotation); + w.WriteUInt8(state.on_ground ? 1 : 0); + auto sids = gameLogic_.GetSessionsInRadius(state.position); + for (auto sid : sids) { + if (sid == state.session_id) continue; + pool_.PushToWorker(static_cast(sid >> 48), sid, w.GetBuffer()); + } + }); + gameLogic_.SetBroadcastPlayerPositionCallback([this](const PlayerPositionData& data, float /*radius*/) { + BinaryProtocol::BinaryWriter w; + w.WriteUInt16(BinaryProtocol::MESSAGE_TYPE_PLAYER_POSITION); + w.WriteUInt64(data.timestamp); + w.WriteUInt64(data.player_id); + w.WriteVector3(data.position); + w.WriteVector3(data.velocity); + auto sids = gameLogic_.GetSessionsInRadius(data.position); + for (auto sid : sids) { + if (sid == data.session_id) continue; + pool_.PushToWorker(static_cast(sid >> 48), sid, w.GetBuffer()); + } + }); + gameLogic_.SetSendPlayerSpawnCallback([this](uint64_t session_id, const PlayerSpawnData& data) { + BinaryProtocol::BinaryWriter w; + w.WriteUInt16(BinaryProtocol::MESSAGE_TYPE_PLAYER_SPAWN); + w.WriteUInt64(gameLogic_.GetCurrentTimestamp()); + w.WriteUInt64(data.player_id); + w.WriteString(data.name); + w.WriteVector3(data.position); + w.WriteFloat(data.yaw); + w.WriteFloat(data.health); + w.WriteFloat(data.max_health); + pool_.PushToWorker(static_cast(session_id >> 48), session_id, w.GetBuffer()); + }); + gameLogic_.SetSendPlayerDespawnCallback([this](uint64_t session_id, const PlayerDespawnData& data) { + BinaryProtocol::BinaryWriter w; + w.WriteUInt16(BinaryProtocol::MESSAGE_TYPE_PLAYER_DESPAWN); + w.WriteUInt64(data.timestamp); + w.WriteUInt64(data.player_id); + pool_.PushToWorker(static_cast(session_id >> 48), session_id, w.GetBuffer()); + }); + gameLogic_.SetSendPlayerUpdateCallback([this](uint64_t session_id, const PlayerUpdateData& data) { + BinaryProtocol::BinaryWriter w; + w.WriteUInt16(BinaryProtocol::MESSAGE_TYPE_PLAYER_UPDATE); + w.WriteUInt64(data.timestamp); + w.WriteUInt64(data.player_id); + w.WriteVector3(data.position); + w.WriteFloat(data.yaw); + w.WriteFloat(data.health); + w.WriteFloat(data.max_health); + w.WriteString(data.name); + pool_.PushToWorker(static_cast(session_id >> 48), session_id, w.GetBuffer()); + }); + gameLogic_.SetSendPlayersUpdateCallback([this](uint64_t session_id, const PlayerUpdateData& data) { + BinaryProtocol::BinaryWriter w; + w.WriteUInt16(BinaryProtocol::MESSAGE_TYPE_PLAYERS_UPDATE); + w.WriteUInt64(data.timestamp); + w.WriteUInt64(data.player_id); + w.WriteVector3(data.position); + w.WriteFloat(data.yaw); + w.WriteFloat(data.health); + w.WriteFloat(data.max_health); + w.WriteString(data.name); + auto sids = gameLogic_.GetSessionsInRadius(data.position); + for (auto sid : sids) { + if (sid != session_id) { + pool_.PushToWorker(static_cast(sid >> 48), sid, w.GetBuffer()); + } + } + }); + gameLogic_.SetSendNPCInteractionResponseCallback([this](uint64_t session_id, const NpcData& response) { + BinaryProtocol::BinaryWriter w; + if (response.type == "combat") { + w.WriteUInt64(response.timestamp); + w.WriteUInt64(response.player_id); + w.WriteUInt64(response.npc_id); + w.WriteFloat(response.damage); + w.WriteFloat(response.health); + w.WriteUInt8(response.is_dead ? 1 : 0); + pool_.PushToWorker(static_cast(session_id >> 48), session_id, w.GetBuffer()); + } else if (response.type == "dialogue") { + w.WriteUInt64(response.npc_id); + w.WriteJson(response.quests); + w.WriteUInt64(response.timestamp); + pool_.PushToWorker(static_cast(session_id >> 48), session_id, w.GetBuffer()); + } else { + w.WriteUInt64(0); + w.WriteString("NPC interaction failed"); + pool_.PushToWorker(static_cast(session_id >> 48), session_id, w.GetBuffer()); + } + }); + gameLogic_.SetSendFamiliarCommandResponseCallback([this](uint64_t session_id, const FamiliarData& response) { + BinaryProtocol::BinaryWriter w; + w.WriteUInt64(response.timestamp); + w.WriteUInt64(response.familiar_id); + w.WriteUInt64(response.target_id); + w.WriteString(response.command); + pool_.PushToWorker(static_cast(session_id >> 48), session_id, w.GetBuffer()); + }); + gameLogic_.SetSendEntitySpawnResponseCallback([this](uint64_t session_id, const EntitySpawnData& response) { + BinaryProtocol::BinaryWriter w; + w.WriteUInt64(response.timestamp); + w.WriteUInt64(response.entity_id); + w.WriteInt32(response.type); + w.WriteVector3(response.position); + pool_.PushToWorker(static_cast(session_id >> 48), session_id, w.GetBuffer()); + }); + gameLogic_.SetSendLootPickupResponseCallback([this](uint64_t session_id, const LootPickupData& response) { + BinaryProtocol::BinaryWriter w; + w.WriteUInt64(response.timestamp); + w.WriteUInt64(response.loot_id); + w.WriteUInt16(response.quantity); + pool_.PushToWorker(static_cast(session_id >> 48), session_id, w.GetBuffer()); + }); + gameLogic_.SetSendInventoryResponseCallback([this](uint64_t session_id, const InventoryData& response) { + BinaryProtocol::BinaryWriter w; + w.WriteUInt64(response.timestamp); + w.WriteUInt64(response.loot_id); + w.WriteUInt64(response.target_id); + w.WriteUInt8(static_cast(response.move_type)); + w.WriteInt32(response.inv_slot_id); + w.WriteInt32(response.use_slot_id); + w.WriteUInt16(response.quantity); + pool_.PushToWorker(static_cast(session_id >> 48), session_id, w.GetBuffer()); + }); +} diff --git a/src/network/MasterServer.cpp b/src/network/MasterServer.cpp index 8ba9dd8..b70b12c 100644 --- a/src/network/MasterServer.cpp +++ b/src/network/MasterServer.cpp @@ -5,64 +5,150 @@ extern std::atomic g_shutdown; MasterServer::MasterServer(asio::io_context& io, const std::vector& workerGroups, const ConfigManager& config, GameLogic& gameLogic, DatabaseService& dbService, const std::string& configPath) : io_(io), signal_pipe_(io), - gameLogic_(gameLogic), processPool_(io, workerGroups), config_(config), configPath_(configPath) + gameLogic_(gameLogic), processPool_(io, workerGroups), router_(processPool_, gameLogic), + config_(config), configPath_(configPath) { gameLogic_.SetDatabaseService(&dbService); } void MasterServer::Initialize() { - sendReplyCb_ = [this](uint64_t sessionId, const std::vector& data) { - processPool_.PushToWorker(static_cast(sessionId >> 48), sessionId, data); - }; - gameLogic_.SetSendReplyCallback(sendReplyCb_); - gameLogic_.SetGetSessionIdsInRadiusCallback([](const glm::vec3& pos, float radius) -> std::vector { - std::vector sids; - auto& pm = PlayerManager::GetInstance(); - auto players = pm.GetPlayersInRadius(pos, radius); - for (auto& p : players) { - uint64_t sid = pm.GetSessionIdByPlayerId(p->GetId()); - if (sid != 0) sids.push_back(sid); + router_.Initialize(); + processPool_.SetMasterMessageHandler([this](int workerId, uint32_t corrId, + uint64_t sessionId, uint16_t msgType, const std::vector& body) { + router_.OnChildWorkerMessage(workerId, corrId, sessionId, msgType, body); + }); + processPool_.SetWorker([this](int workerId, const WorkerGroupConfig& groupConfig, int masterReadFd) { + WorkerClient(workerId, groupConfig, masterReadFd, configPath_); + }); + processPool_.SetGameLogicWorker([this](int workerId, int masterFd) { + GameLogicClient(workerId, masterFd, configPath_); + }); + processPool_.Initialize(); + gameLogic_.Initialize(); + extern int g_signal_pipe[2]; + signal_pipe_.assign(g_signal_pipe[0]); + start_signal_read(); +} + +void MasterServer::start_signal_read() { + signal_pipe_.async_read_some(asio::buffer(signal_buffer_), + [this](std::error_code ec, size_t /*bytes*/) { + if (!ec) { + Logger::Trace("MasterServer::start_signal_read shutdown signal received"); + Shutdown(); } - return sids; + start_signal_read(); + Logger::Trace("MasterServer::start_signal_read completed, ec = {}", ec.message()); }); - processPool_.SetMasterMessageHandler([this](int /*workerId*/, uint32_t /*correlationId*/, - uint64_t sessionId, uint16_t messageType, const std::vector& body) { - switch (messageType) { +} + +void MasterServer::Run() +{ + io_.run(); + Logger::Trace("MasterServer::Run: io_context::run() returned"); +} + +void MasterServer::Shutdown() { + static std::once_flag once; + std::call_once(once, [this]{ + Logger::Info("MasterServer::Shutdown initiated"); + signal_pipe_.cancel(); + processPool_.Shutdown(); + gameLogic_.Shutdown(); + io_.stop(); + Logger::Info("MasterServer::Shutdown finished"); + }); +} + +void MasterServer::WorkerClient(int workerId, const WorkerGroupConfig& groupConfig, + int masterReadFd, const std::string& configPath) +{ + auto& config = ConfigManager::GetInstance(); + if (!config.LoadConfig(configPath)) { + Logger::Critical("Worker {} failed to load configuration", workerId); + return; + } + Logger::InitializeWithWorkerId(workerId, config.GetJson("logging")); + uint16_t logPort = config.GetInt("logging.log_port", 15555); + asio::io_context logIo; + auto logSocket = std::make_shared(logIo); + logSocket->connect(asio::ip::tcp::endpoint(asio::ip::address_v4::loopback(), logPort)); + auto logSink = std::make_shared(logSocket); + Logger::AddSink(logSink); + Logger::GetLogger()->set_pattern(config.GetString("logging.pattern", "[%Y-%m-%d %H:%M:%S.%e] [%P] [%l] [%n] %v")); + Logger::Info("Worker {} starting for group: {} ({}:{})", workerId, groupConfig.protocol, groupConfig.host, groupConfig.port); + ClientListener listener(groupConfig, masterReadFd, workerId); + listener.Start(); + asio::io_context signalIo; + asio::signal_set signals(signalIo, SIGINT, SIGTERM); + signals.async_wait([&listener](const std::error_code&, int) { listener.Shutdown(); }); + std::thread signalThread([&]() { signalIo.run(); }); + signalThread.join(); + listener.Shutdown(); +} + +void MasterServer::GameLogicClient(int workerId, int masterFd, const std::string& configPath) +{ + auto& config = ConfigManager::GetInstance(); + if (!config.LoadConfig(configPath)) { + Logger::Critical("GameLogicWorker {} failed to load configuration", workerId); + return; + } + Logger::InitializeWithWorkerId(workerId, config.GetJson("logging")); + uint16_t logPort = config.GetInt("logging.log_port", 15555); + asio::io_context logIo; + auto logSocket = std::make_shared(logIo); + logSocket->connect(asio::ip::tcp::endpoint(asio::ip::address_v4::loopback(), logPort)); + auto logSink = std::make_shared(logSocket); + Logger::AddSink(logSink); + Logger::GetLogger()->set_pattern(config.GetString("logging.pattern", "[%Y-%m-%d %H:%M:%S.%e] [%P] [%l] [%n] %v")); + Logger::Info("GameLogicWorker {} starting", workerId); + + asio::io_context workerIo; + auto channel = std::make_shared(workerIo, masterFd); + + DatabaseService dbService(workerIo, config.GetInt("database.pool.threads", 2)); + auto gameLogic = std::make_unique(); + gameLogic->SetDatabaseService(&dbService); + + channel->Start([&gameLogic, &channel](const IPCEnvelope& env) { + switch (env.messageType) { case BinaryProtocol::MESSAGE_TYPE_AUTHENTICATION: { - BinaryProtocol::BinaryReader r(body.data(), body.size()); + BinaryProtocol::BinaryReader r(env.payload.data(), env.payload.size()); AuthenticationData auth; auth.username = r.ReadString(); auth.password = r.ReadString(); - auth.session_id = sessionId; - gameLogic_.OnAuthentication(auth); + auth.session_id = env.sessionId; + gameLogic->OnAuthentication(auth); break; } case BinaryProtocol::MESSAGE_TYPE_CHAT_MESSAGE: { - BinaryProtocol::BinaryReader r(body.data(), body.size()); + BinaryProtocol::BinaryReader r(env.payload.data(), env.payload.size()); std::string sender = r.ReadString(); std::string message = r.ReadString(); uint64_t timestamp = r.ReadUInt64(); - Logger::Trace("Chat from {} (session {}): {}", sender, sessionId, message); BinaryProtocol::BinaryWriter w; w.WriteUInt16(BinaryProtocol::MESSAGE_TYPE_CHAT_MESSAGE); w.WriteString(sender); w.WriteString(message); w.WriteUInt64(timestamp); - std::vector response = w.GetBuffer(); - processPool_.PushToWorker(static_cast(sessionId >> 48), sessionId, response); - processPool_.BroadcastToAllWorkers(response); - //processPool_.BroadcastToOtherWorkers(response, static_cast(sessionId >> 48)); + IPCEnvelope resp; + resp.correlationId = 0; + resp.sessionId = env.sessionId; + resp.messageType = BinaryProtocol::MESSAGE_TYPE_CHAT_MESSAGE; + resp.payload = w.GetBuffer(); + channel->SendAsync(resp); break; } case BinaryProtocol::MESSAGE_TYPE_CHUNK_PARAMS: { ChunkParams req; - req.session_id = sessionId; - gameLogic_.OnChunkParams(req); + req.session_id = env.sessionId; + gameLogic->OnChunkParams(req); break; } case BinaryProtocol::MESSAGE_TYPE_CHUNK_DATA: { - BinaryProtocol::BinaryReader r(body.data(), body.size()); + BinaryProtocol::BinaryReader r(env.payload.data(), env.payload.size()); ChunkData req; req.x = r.ReadInt32(); req.z = r.ReadInt32(); @@ -70,35 +156,35 @@ void MasterServer::Initialize() req.player_x = r.ReadFloat(); req.player_y = r.ReadFloat(); req.player_z = r.ReadFloat(); - req.session_id = sessionId; - gameLogic_.OnChunkData(req); + req.session_id = env.sessionId; + gameLogic->OnChunkData(req); break; } case BinaryProtocol::MESSAGE_TYPE_COLLISION_CHECK: { - BinaryProtocol::BinaryReader r(body.data(), body.size()); + BinaryProtocol::BinaryReader r(env.payload.data(), env.payload.size()); CollisionData req; req.position = r.ReadVector3(); req.radius = r.ReadFloat(); - req.session_id = sessionId; - gameLogic_.OnCollisionCheck(req); + req.session_id = env.sessionId; + gameLogic->OnCollisionCheck(req); break; } case BinaryProtocol::MESSAGE_TYPE_PLAYER_STATE: { - BinaryProtocol::BinaryReader r(body.data(), body.size()); + BinaryProtocol::BinaryReader r(env.payload.data(), env.payload.size()); PlayerStateData state; - state.player_id = gameLogic_.GetPlayerIdBySession(sessionId); + state.player_id = gameLogic->GetPlayerIdBySession(env.sessionId); state.input_id = r.ReadUInt32(); state.position = r.ReadVector3(); state.velocity = r.ReadVector3(); state.rotation = r.ReadVector3(); state.on_ground = r.ReadUInt8() != 0; state.timestamp = r.ReadUInt64(); - state.session_id = sessionId; - gameLogic_.OnPlayerState(state); + state.session_id = env.sessionId; + gameLogic->OnPlayerState(state); break; } case BinaryProtocol::MESSAGE_TYPE_PLAYER_POSITION: { - BinaryProtocol::BinaryReader r(body.data(), body.size()); + BinaryProtocol::BinaryReader r(env.payload.data(), env.payload.size()); uint64_t player_id = r.ReadUInt64(); glm::vec3 position = r.ReadVector3(); glm::vec3 velocity = r.ReadVector3(); @@ -108,50 +194,50 @@ void MasterServer::Initialize() pos.position = position; pos.velocity = velocity; pos.timestamp = timestamp; - pos.session_id = sessionId; - gameLogic_.OnPlayerPosition(pos); + pos.session_id = env.sessionId; + gameLogic->OnPlayerPosition(pos); break; } case BinaryProtocol::MESSAGE_TYPE_NPC_INTERACTION: { - BinaryProtocol::BinaryReader r(body.data(), body.size()); + BinaryProtocol::BinaryReader r(env.payload.data(), env.payload.size()); NpcData req; req.npc_id = r.ReadUInt64(); req.type = r.ReadString(); - req.session_id = sessionId; - gameLogic_.OnNPCInteraction(req); + req.session_id = env.sessionId; + gameLogic->OnNPCInteraction(req); break; } case BinaryProtocol::MESSAGE_TYPE_FAMILIAR_COMMAND: { - BinaryProtocol::BinaryReader r(body.data(), body.size()); + BinaryProtocol::BinaryReader r(env.payload.data(), env.payload.size()); FamiliarData req; - req.session_id = sessionId; + req.session_id = env.sessionId; req.familiar_id = r.ReadUInt64(); req.target_id = r.ReadUInt64(); req.command = r.ReadString(); - gameLogic_.OnFamiliarCommand(req); + gameLogic->OnFamiliarCommand(req); break; } case BinaryProtocol::MESSAGE_TYPE_ENTITY_SPAWN: { - BinaryProtocol::BinaryReader r(body.data(), body.size()); + BinaryProtocol::BinaryReader r(env.payload.data(), env.payload.size()); EntitySpawnData req; - req.session_id = sessionId; + req.session_id = env.sessionId; req.entity_id = r.ReadUInt64(); req.type = r.ReadInt32(); req.position = r.ReadVector3(); - gameLogic_.OnEntitySpawnRequest(req); + gameLogic->OnEntitySpawnRequest(req); break; } case BinaryProtocol::MESSAGE_TYPE_LOOT_PICKUP: { - BinaryProtocol::BinaryReader r(body.data(), body.size()); + BinaryProtocol::BinaryReader r(env.payload.data(), env.payload.size()); LootPickupData req; req.loot_id = r.ReadUInt64(); req.quantity = r.ReadUInt16(); - req.session_id = sessionId; - gameLogic_.OnLootPickup(req); + req.session_id = env.sessionId; + gameLogic->OnLootPickup(req); break; } case BinaryProtocol::MESSAGE_TYPE_INVENTORY_MOVE: { - BinaryProtocol::BinaryReader r(body.data(), body.size()); + BinaryProtocol::BinaryReader r(env.payload.data(), env.payload.size()); InventoryData req; req.loot_id = r.ReadUInt64(); req.target_id = r.ReadUInt64(); @@ -159,111 +245,62 @@ void MasterServer::Initialize() req.inv_slot_id = r.ReadInt32(); req.use_slot_id = r.ReadInt32(); req.quantity = r.ReadUInt16(); - req.session_id = sessionId; - gameLogic_.OnInventory(req); + req.session_id = env.sessionId; + gameLogic->OnInventory(req); break; } - case BinaryProtocol::MESSAGE_TYPE_PLAYER_DISCONNECT: { - gameLogic_.OnPlayerDisconnected(sessionId); + case BinaryProtocol::MESSAGE_TYPE_PLAYER_DISCONNECT: + gameLogic->OnPlayerDisconnected(env.sessionId); break; - } default: - Logger::Warn("Unhandled master message type: {}", messageType); + Logger::Warn("GameLogicWorker: unhandled message type {}", env.messageType); break; } }); - WireCallbacks(); - processPool_.SetWorker([this](int workerId, const WorkerGroupConfig& groupConfig, int masterReadFd) { - WorkerClient(workerId, groupConfig, masterReadFd, configPath_); - }); - processPool_.Initialize(); - gameLogic_.Initialize(); - extern int g_signal_pipe[2]; - signal_pipe_.assign(g_signal_pipe[0]); - start_signal_read(); -} - -void MasterServer::start_signal_read() { - signal_pipe_.async_read_some(asio::buffer(signal_buffer_), - [this](std::error_code ec, size_t /*bytes*/) { - if (!ec) { - Logger::Trace("MasterServer::start_signal_read shutdown signal received"); - Shutdown(); - } - start_signal_read(); - Logger::Trace("MasterServer::start_signal_read completed, ec = {}", ec.message()); - }); -} -void MasterServer::Run() -{ - io_.run(); - Logger::Trace("MasterServer::Run: io_context::run() returned"); -} + auto sendToMaster = [&channel](uint64_t sessionId, uint16_t msgType, const std::vector& data) { + IPCEnvelope resp; + resp.correlationId = 0; + resp.sessionId = sessionId; + resp.messageType = msgType; + resp.payload = data; + channel->SendAsync(resp); + }; -void MasterServer::Shutdown() { - static std::once_flag once; - std::call_once(once, [this]{ - Logger::Info("MasterServer::Shutdown initiated"); - signal_pipe_.cancel(); - processPool_.Shutdown(); - //Logger::Trace("MasterServer::Shutdown: about to call gameLogic_.Shutdown()"); - gameLogic_.Shutdown(); - io_.stop(); - Logger::Info("MasterServer::Shutdown finished"); + gameLogic->SetSendReplyCallback([&sendToMaster](uint64_t sessionId, const std::vector& data) { + sendToMaster(sessionId, 0, data); }); -} -void MasterServer::WorkerClient(int workerId, const WorkerGroupConfig& groupConfig, - int masterReadFd, const std::string& configPath) -{ - auto& config = ConfigManager::GetInstance(); - if (!config.LoadConfig(configPath)) { - Logger::Critical("Worker {} failed to load configuration", workerId); - return; - } - Logger::InitializeWithWorkerId(workerId, config.GetJson("logging")); - uint16_t logPort = config.GetInt("logging.log_port", 15555); - asio::io_context logIo; - auto logSocket = std::make_shared(logIo); - logSocket->connect(asio::ip::tcp::endpoint(asio::ip::address_v4::loopback(), logPort)); - auto logSink = std::make_shared(logSocket); - Logger::AddSink(logSink); - Logger::GetLogger()->set_pattern(config.GetString("logging.pattern", "[%Y-%m-%d %H:%M:%S.%e] [%P] [%l] [%n] %v")); - Logger::Info("Worker {} starting for group: {} ({}:{})", workerId, groupConfig.protocol, groupConfig.host, groupConfig.port); - ClientListener listener(groupConfig, masterReadFd, workerId); - listener.Start(); - asio::io_context signalIo; - asio::signal_set signals(signalIo, SIGINT, SIGTERM); - signals.async_wait([&listener](const std::error_code&, int) { listener.Shutdown(); }); - std::thread signalThread([&]() { signalIo.run(); }); - signalThread.join(); - listener.Shutdown(); -} + gameLogic->SetGetSessionIdsInRadiusCallback([](const glm::vec3& pos, float radius) -> std::vector { + std::vector sids; + auto& pm = PlayerManager::GetInstance(); + auto players = pm.GetPlayersInRadius(pos, radius); + for (auto& p : players) { + uint64_t sid = pm.GetSessionIdByPlayerId(p->GetId()); + if (sid != 0) sids.push_back(sid); + } + return sids; + }); -void MasterServer::WireCallbacks() -{ - gameLogic_.SetSendAuthenticationResponseCallback([this](uint64_t session_id, const std::string& message, uint64_t player_id) { - Logger::Trace("MasterServer::WireCallbacks auth response: session={}, player={}, message={}", session_id, player_id, message); + gameLogic->SetSendAuthenticationResponseCallback([&sendToMaster, &gameLogic](uint64_t session_id, const std::string& message, uint64_t player_id) { BinaryProtocol::BinaryWriter w; w.WriteUInt16(BinaryProtocol::MESSAGE_TYPE_AUTHENTICATION); - w.WriteUInt64(gameLogic_.GetCurrentTimestamp()); + w.WriteUInt64(gameLogic->GetCurrentTimestamp()); w.WriteUInt64(player_id); w.WriteString(message); - auto buf = w.GetBuffer(); - Logger::Trace("MasterServer::WireCallbacks auth response buffer size: {} bytes", buf.size()); - processPool_.PushToWorker(static_cast(session_id >> 48), session_id, buf); + sendToMaster(session_id, BinaryProtocol::MESSAGE_TYPE_AUTHENTICATION, w.GetBuffer()); }); - gameLogic_.SetSendChunkParamsCallback([this](uint64_t session_id, const ChunkParams& data) { + + gameLogic->SetSendChunkParamsCallback([&sendToMaster](uint64_t session_id, const ChunkParams& data) { BinaryProtocol::BinaryWriter w; w.WriteUInt16(BinaryProtocol::MESSAGE_TYPE_CHUNK_PARAMS); w.WriteUInt64(data.timestamp); w.WriteUInt32(static_cast(data.size)); w.WriteFloat(data.spacing); - auto buf = w.GetBuffer(); - processPool_.PushToWorker(static_cast(session_id >> 48), session_id, buf); + sendToMaster(session_id, BinaryProtocol::MESSAGE_TYPE_CHUNK_PARAMS, w.GetBuffer()); }); - gameLogic_.SetSendChunkCallback([this](uint64_t session_id, const ChunkData& data) { + + gameLogic->SetSendChunkCallback([&sendToMaster](uint64_t session_id, const ChunkData& data) { BinaryProtocol::BinaryWriter w; w.WriteUInt16(BinaryProtocol::MESSAGE_TYPE_CHUNK_DATA); w.WriteUInt64(data.timestamp); @@ -276,20 +313,21 @@ void MasterServer::WireCallbacks() w.WriteUInt32(idxSize); w.WriteBytes(reinterpret_cast(data.indices.data()), idxSize); w.WriteUInt32(data.lod); - auto buf = w.GetBuffer(); - processPool_.PushToWorker(static_cast(session_id >> 48), session_id, buf); + sendToMaster(session_id, BinaryProtocol::MESSAGE_TYPE_CHUNK_DATA, w.GetBuffer()); }); - gameLogic_.SetSendCollisionResponseCallback([this](uint64_t session_id, const CollisionResult& result) { + + gameLogic->SetSendCollisionResponseCallback([&sendToMaster, &gameLogic](uint64_t session_id, const CollisionResult& result) { BinaryProtocol::BinaryWriter w; w.WriteUInt16(BinaryProtocol::MESSAGE_TYPE_COLLISION_CHECK); - w.WriteUInt64(gameLogic_.GetCurrentTimestamp()); + w.WriteUInt64(gameLogic->GetCurrentTimestamp()); w.WriteUInt8(result.collided ? 1 : 0); w.WriteUInt64(result.collided_id); w.WriteFloat(result.penetration); w.WriteVector3(result.resolution); - processPool_.PushToWorker(static_cast(session_id >> 48), session_id, w.GetBuffer()); + sendToMaster(session_id, BinaryProtocol::MESSAGE_TYPE_COLLISION_CHECK, w.GetBuffer()); }); - gameLogic_.SetPlayerStateCallback([this](const PlayerStateData& state) {//broadcast + + gameLogic->SetPlayerStateCallback([&sendToMaster, &gameLogic](const PlayerStateData& state) { BinaryProtocol::BinaryWriter w; w.WriteUInt16(BinaryProtocol::MESSAGE_TYPE_PLAYER_STATE); w.WriteUInt64(state.timestamp); @@ -298,46 +336,49 @@ void MasterServer::WireCallbacks() w.WriteVector3(state.velocity); w.WriteVector3(state.rotation); w.WriteUInt8(state.on_ground ? 1 : 0); - auto sids = gameLogic_.GetSessionsInRadius(state.position); + auto sids = gameLogic->GetSessionsInRadius(state.position); for (auto sid : sids) { if (sid == state.session_id) continue; - processPool_.PushToWorker(static_cast(sid >> 48), sid, w.GetBuffer()); + sendToMaster(sid, BinaryProtocol::MESSAGE_TYPE_PLAYER_STATE, w.GetBuffer()); } }); - gameLogic_.SetBroadcastPlayerPositionCallback([this](const PlayerPositionData& data, float /*radius*/) {//broadcast + + gameLogic->SetBroadcastPlayerPositionCallback([&sendToMaster, &gameLogic](const PlayerPositionData& data, float /*radius*/) { BinaryProtocol::BinaryWriter w; w.WriteUInt16(BinaryProtocol::MESSAGE_TYPE_PLAYER_POSITION); w.WriteUInt64(data.timestamp); w.WriteUInt64(data.player_id); w.WriteVector3(data.position); w.WriteVector3(data.velocity); - auto sids = gameLogic_.GetSessionsInRadius(data.position); + auto sids = gameLogic->GetSessionsInRadius(data.position); for (auto sid : sids) { if (sid == data.session_id) continue; - processPool_.PushToWorker(static_cast(sid >> 48), sid, w.GetBuffer()); + sendToMaster(sid, BinaryProtocol::MESSAGE_TYPE_PLAYER_POSITION, w.GetBuffer()); } }); - gameLogic_.SetSendPlayerSpawnCallback([this](uint64_t session_id, const PlayerSpawnData& data) { + + gameLogic->SetSendPlayerSpawnCallback([&sendToMaster, &gameLogic](uint64_t session_id, const PlayerSpawnData& data) { BinaryProtocol::BinaryWriter w; w.WriteUInt16(BinaryProtocol::MESSAGE_TYPE_PLAYER_SPAWN); - w.WriteUInt64(gameLogic_.GetCurrentTimestamp()); + w.WriteUInt64(gameLogic->GetCurrentTimestamp()); w.WriteUInt64(data.player_id); w.WriteString(data.name); w.WriteVector3(data.position); w.WriteFloat(data.yaw); w.WriteFloat(data.health); w.WriteFloat(data.max_health); - int workerId = static_cast(session_id >> 48); - processPool_.PushToWorker(workerId, session_id, w.GetBuffer()); + sendToMaster(session_id, BinaryProtocol::MESSAGE_TYPE_PLAYER_SPAWN, w.GetBuffer()); }); - gameLogic_.SetSendPlayerDespawnCallback([this](uint64_t session_id, const PlayerDespawnData& data) { + + gameLogic->SetSendPlayerDespawnCallback([&sendToMaster](uint64_t session_id, const PlayerDespawnData& data) { BinaryProtocol::BinaryWriter w; w.WriteUInt16(BinaryProtocol::MESSAGE_TYPE_PLAYER_DESPAWN); w.WriteUInt64(data.timestamp); w.WriteUInt64(data.player_id); - processPool_.PushToWorker(static_cast(session_id >> 48), session_id, w.GetBuffer()); + sendToMaster(session_id, BinaryProtocol::MESSAGE_TYPE_PLAYER_DESPAWN, w.GetBuffer()); }); - gameLogic_.SetSendPlayerUpdateCallback([this](uint64_t session_id, const PlayerUpdateData& data) { + + gameLogic->SetSendPlayerUpdateCallback([&sendToMaster](uint64_t session_id, const PlayerUpdateData& data) { BinaryProtocol::BinaryWriter w; w.WriteUInt16(BinaryProtocol::MESSAGE_TYPE_PLAYER_UPDATE); w.WriteUInt64(data.timestamp); @@ -347,9 +388,10 @@ void MasterServer::WireCallbacks() w.WriteFloat(data.health); w.WriteFloat(data.max_health); w.WriteString(data.name); - processPool_.PushToWorker(static_cast(session_id >> 48), session_id, w.GetBuffer()); + sendToMaster(session_id, BinaryProtocol::MESSAGE_TYPE_PLAYER_UPDATE, w.GetBuffer()); }); - gameLogic_.SetSendPlayersUpdateCallback([this](uint64_t session_id, const PlayerUpdateData& data) {//broadcast + + gameLogic->SetSendPlayersUpdateCallback([&sendToMaster, &gameLogic](uint64_t session_id, const PlayerUpdateData& data) { BinaryProtocol::BinaryWriter w; w.WriteUInt16(BinaryProtocol::MESSAGE_TYPE_PLAYERS_UPDATE); w.WriteUInt64(data.timestamp); @@ -359,14 +401,15 @@ void MasterServer::WireCallbacks() w.WriteFloat(data.health); w.WriteFloat(data.max_health); w.WriteString(data.name); - auto sids = gameLogic_.GetSessionsInRadius(data.position); + auto sids = gameLogic->GetSessionsInRadius(data.position); for (auto sid : sids) { if (sid != session_id) { - processPool_.PushToWorker(static_cast(sid >> 48), sid, w.GetBuffer()); + sendToMaster(sid, BinaryProtocol::MESSAGE_TYPE_PLAYERS_UPDATE, w.GetBuffer()); } } }); - gameLogic_.SetSendNPCInteractionResponseCallback([this](uint64_t session_id, const NpcData& response) { + + gameLogic->SetSendNPCInteractionResponseCallback([&sendToMaster](uint64_t session_id, const NpcData& response) { BinaryProtocol::BinaryWriter w; if (response.type == "combat") { w.WriteUInt64(response.timestamp); @@ -375,42 +418,46 @@ void MasterServer::WireCallbacks() w.WriteFloat(response.damage); w.WriteFloat(response.health); w.WriteUInt8(response.is_dead ? 1 : 0); - processPool_.PushToWorker(static_cast(session_id >> 48), session_id, w.GetBuffer()); + sendToMaster(session_id, BinaryProtocol::MESSAGE_TYPE_NPC_INTERACTION, w.GetBuffer()); } else if (response.type == "dialogue") { w.WriteUInt64(response.npc_id); w.WriteJson(response.quests); w.WriteUInt64(response.timestamp); - processPool_.PushToWorker(static_cast(session_id >> 48), session_id, w.GetBuffer()); + sendToMaster(session_id, BinaryProtocol::MESSAGE_TYPE_NPC_INTERACTION, w.GetBuffer()); } else { w.WriteUInt64(0); w.WriteString("NPC interaction failed"); - processPool_.PushToWorker(static_cast(session_id >> 48), session_id, w.GetBuffer()); + sendToMaster(session_id, BinaryProtocol::MESSAGE_TYPE_NPC_INTERACTION, w.GetBuffer()); } }); - gameLogic_.SetSendFamiliarCommandResponseCallback([this](uint64_t session_id, const FamiliarData& response) { + + gameLogic->SetSendFamiliarCommandResponseCallback([&sendToMaster](uint64_t session_id, const FamiliarData& response) { BinaryProtocol::BinaryWriter w; w.WriteUInt64(response.timestamp); w.WriteUInt64(response.familiar_id); w.WriteUInt64(response.target_id); w.WriteString(response.command); - processPool_.PushToWorker(static_cast(session_id >> 48), session_id, w.GetBuffer()); + sendToMaster(session_id, BinaryProtocol::MESSAGE_TYPE_FAMILIAR_COMMAND, w.GetBuffer()); }); - gameLogic_.SetSendEntitySpawnResponseCallback([this](uint64_t session_id, const EntitySpawnData& response) { + + gameLogic->SetSendEntitySpawnResponseCallback([&sendToMaster](uint64_t session_id, const EntitySpawnData& response) { BinaryProtocol::BinaryWriter w; w.WriteUInt64(response.timestamp); w.WriteUInt64(response.entity_id); w.WriteInt32(response.type); w.WriteVector3(response.position); - processPool_.PushToWorker(static_cast(session_id >> 48), session_id, w.GetBuffer()); + sendToMaster(session_id, BinaryProtocol::MESSAGE_TYPE_ENTITY_SPAWN, w.GetBuffer()); }); - gameLogic_.SetSendLootPickupResponseCallback([this](uint64_t session_id, const LootPickupData& response) { + + gameLogic->SetSendLootPickupResponseCallback([&sendToMaster](uint64_t session_id, const LootPickupData& response) { BinaryProtocol::BinaryWriter w; w.WriteUInt64(response.timestamp); w.WriteUInt64(response.loot_id); w.WriteUInt16(response.quantity); - processPool_.PushToWorker(static_cast(session_id >> 48), session_id, w.GetBuffer()); + sendToMaster(session_id, BinaryProtocol::MESSAGE_TYPE_LOOT_PICKUP, w.GetBuffer()); }); - gameLogic_.SetSendInventoryResponseCallback([this](uint64_t session_id, const InventoryData& response) { + + gameLogic->SetSendInventoryResponseCallback([&sendToMaster](uint64_t session_id, const InventoryData& response) { BinaryProtocol::BinaryWriter w; w.WriteUInt64(response.timestamp); w.WriteUInt64(response.loot_id); @@ -419,11 +466,21 @@ void MasterServer::WireCallbacks() w.WriteInt32(response.inv_slot_id); w.WriteInt32(response.use_slot_id); w.WriteUInt16(response.quantity); - processPool_.PushToWorker(static_cast(session_id >> 48), session_id, w.GetBuffer()); + sendToMaster(session_id, BinaryProtocol::MESSAGE_TYPE_INVENTORY_MOVE, w.GetBuffer()); }); -} -void MasterServer::SendResponse(uint64_t sessionId, const std::vector& buffer) -{ - sendReplyCb_(sessionId, buffer); + gameLogic->Initialize(); + + asio::io_context signalIo; + asio::signal_set signals(signalIo, SIGINT, SIGTERM); + signals.async_wait([&](const std::error_code&, int) { + channel->Stop(); + gameLogic->Shutdown(); + workerIo.stop(); + }); + std::thread signalThread([&]() { signalIo.run(); }); + + workerIo.run(); + signalThread.join(); + Logger::Info("GameLogicWorker {} shutting down", workerId); } diff --git a/src/process/IPCChannel.cpp b/src/process/IPCChannel.cpp new file mode 100644 index 0000000..a1edb6e --- /dev/null +++ b/src/process/IPCChannel.cpp @@ -0,0 +1,137 @@ +#include "process/IPCChannel.hpp" +#include "logging/Logger.hpp" + +IPCChannel::IPCChannel(asio::io_context& io, int fd) + : io_(io), stream_(io) { + if (fd >= 0) { + stream_.assign(fd); + } +} + +IPCChannel::~IPCChannel() { + Stop(); +} + +void IPCChannel::Start(std::function onMessage) { + onMessage_ = std::move(onMessage); + stopped_ = false; + doRead(); +} + +void IPCChannel::Stop() { + if (stopped_.exchange(true)) return; + std::error_code ec; + stream_.cancel(ec); + stream_.close(ec); +} + +bool IPCChannel::IsOpen() const { + return stream_.is_open(); +} + +int IPCChannel::GetFd() { + return stream_.native_handle(); +} + +std::vector IPCChannel::encodeFrame(const IPCEnvelope& env) { + uint32_t innerLen = 4 + 8 + 2 + 4 + static_cast(env.payload.size()); + uint32_t totalLen = innerLen; + std::vector frame(4 + innerLen); + uint32_t netTotal = htonl(totalLen); + memcpy(frame.data(), &netTotal, sizeof(netTotal)); + BinaryProtocol::BinaryWriter w; + w.WriteUInt32(env.correlationId); + w.WriteUInt64(env.sessionId); + w.WriteUInt16(env.messageType); + w.WriteUInt32(static_cast(env.payload.size())); + w.WriteRaw(env.payload.data(), env.payload.size()); + auto inner = w.GetBuffer(); + memcpy(frame.data() + 4, inner.data(), inner.size()); + return frame; +} + +IPCEnvelope IPCChannel::decodeFrame(const uint8_t* data, size_t len) { + IPCEnvelope env; + if (len < 18) return env; + BinaryProtocol::BinaryReader r(data, len); + env.correlationId = r.ReadUInt32(); + env.sessionId = r.ReadUInt64(); + env.messageType = r.ReadUInt16(); + uint32_t bodyLen = r.ReadUInt32(); + if (bodyLen > 0 && r.Remaining() >= bodyLen) { + env.payload = r.ReadBytes(bodyLen); + } + return env; +} + +void IPCChannel::doRead() { + if (stopped_) return; + auto self = shared_from_this(); + auto lengthBuf = std::make_shared(0); + asio::async_read(stream_, asio::buffer(lengthBuf.get(), sizeof(uint32_t)), + [self, lengthBuf](std::error_code ec, std::size_t) { + if (ec || self->stopped_) return; + uint32_t msgLen = ntohl(*lengthBuf); + if (msgLen == 0 || msgLen > BinaryProtocol::MAX_MESSAGE_SIZE) { + self->doRead(); + return; + } + auto msgBuffer = std::make_shared>(msgLen); + asio::async_read(self->stream_, asio::buffer(*msgBuffer), + [self, msgBuffer](std::error_code ec, std::size_t) { + if (ec || self->stopped_) return; + IPCEnvelope env = decodeFrame(msgBuffer->data(), msgBuffer->size()); + if (self->onMessage_) { + self->onMessage_(env); + } + self->doRead(); + }); + }); +} + +void IPCChannel::doWrite() { + std::vector data; + { + std::lock_guard lock(writeMutex_); + if (writeQueue_.empty()) { + writing_ = false; + return; + } + data = std::move(writeQueue_.front()); + writeQueue_.pop_front(); + } + auto self = shared_from_this(); + asio::async_write(stream_, asio::buffer(data), + [self, data](std::error_code ec, std::size_t) { + if (ec) { + Logger::Error("IPCChannel::doWrite failed: {}", ec.message()); + return; + } + self->doWrite(); + }); +} + +void IPCChannel::SendAsync(const IPCEnvelope& envelope) { + auto frame = encodeFrame(envelope); + { + std::lock_guard lock(writeMutex_); + writeQueue_.push_back(std::move(frame)); + if (!writing_) { + writing_ = true; + } else { + return; + } + } + asio::post(io_, [self = shared_from_this()]() { + self->doWrite(); + }); +} + +void IPCChannel::SendSync(const IPCEnvelope& envelope) { + auto frame = encodeFrame(envelope); + std::error_code ec; + asio::write(stream_, asio::buffer(frame), ec); + if (ec) { + Logger::Error("IPCChannel::SendSync failed: {}", ec.message()); + } +} diff --git a/src/process/ProcessPool.cpp b/src/process/ProcessPool.cpp index a0ca90f..23d7b5d 100644 --- a/src/process/ProcessPool.cpp +++ b/src/process/ProcessPool.cpp @@ -1,15 +1,13 @@ #include "process/ProcessPool.hpp" -ProcessWorker::ProcessWorker(asio::io_context& io, int globalId, const WorkerGroupConfig& cfg) - : workerId_(globalId), config_(cfg), io_(io), pid_(-1), - masterReadFd_(-1), masterWriteFd_(-1), masterStream_(io_) {} +ProcessWorker::ProcessWorker(asio::io_context& io, int globalId, const WorkerGroupConfig& cfg, WorkerType type) + : workerId_(globalId), config_(cfg), type_(type), io_(io), pid_(-1) {} ProcessWorker::~ProcessWorker() { Shutdown(); } int ProcessWorker::GetId() const { return workerId_; } pid_t ProcessWorker::GetPid() const { return pid_; } -int ProcessWorker::GetMasterReadFd() const { return masterReadFd_; } -asio::posix::stream_descriptor& ProcessWorker::GetMasterStream() { return masterStream_; } +WorkerType ProcessWorker::GetType() const { return type_; } void ProcessWorker::Start() { asio::local::stream_protocol::socket socket0(io_); @@ -20,14 +18,13 @@ void ProcessWorker::Start() { socket0.close(); masterFd_ = socket1.native_handle(); socket1.release(); - masterReadFd_ = masterFd_; io_.notify_fork(asio::execution_context::fork_event::fork_child); throw std::runtime_error("worker_function"); } else if (pid_ > 0) { socket1.close(); masterFd_ = socket0.native_handle(); socket0.release(); - masterStream_.assign(masterFd_); + channel_ = std::make_shared(io_, masterFd_); io_.notify_fork(asio::execution_context::fork_event::fork_parent); } else { socket0.close(); @@ -36,57 +33,6 @@ void ProcessWorker::Start() { } } -void ProcessWorker::Send(const std::vector& binaryData) { - uint32_t len = htonl(static_cast(binaryData.size())); - std::vector buffers; - buffers.emplace_back(&len, sizeof(len)); - buffers.emplace_back(binaryData.data(), binaryData.size()); - asio::write(masterStream_, buffers); -} - -void ProcessWorker::SendAsync(const std::vector& binaryData) { - uint32_t len = htonl(static_cast(binaryData.size())); - std::vector frame(sizeof(len) + binaryData.size()); - memcpy(frame.data(), &len, sizeof(len)); - memcpy(frame.data() + sizeof(len), binaryData.data(), binaryData.size()); - asio::post(io_, [this, frame = std::move(frame)]() { - bool start_write; - { - std::lock_guard lock(write_mutex_); - write_queue_.push_back(std::move(frame)); - start_write = !writing_; - if (start_write) writing_ = true; - } - if (start_write) { - doWrite(); - } - }); -} - -void ProcessWorker::doWrite() { - std::vector data; - { - std::lock_guard lock(write_mutex_); - if (write_queue_.empty()) { - writing_ = false; - return; - } - data = std::move(write_queue_.front()); - write_queue_.pop_front(); - writing_ = true; - } - auto self = shared_from_this(); - asio::async_write(masterStream_, asio::buffer(data), - [self, data](std::error_code ec, size_t /*bytes*/) { - if (ec) { - Logger::Error("ProcessWorker::doWrite: master async_write to worker {} failed: {}", - self->workerId_, ec.message()); - } - else - self->doWrite(); - }); -} - void ProcessWorker::Shutdown() { if (pid_ > 0) { kill(pid_, SIGTERM); @@ -103,58 +49,17 @@ void ProcessWorker::Shutdown() { waitpid(pid_, nullptr, 0); } } - if (masterReadFd_ != -1) close(masterReadFd_); - if (masterWriteFd_ != -1) close(masterWriteFd_); - masterReadFd_ = masterWriteFd_ = -1; + if (channel_) channel_->Stop(); + if (masterFd_ != -1) close(masterFd_); + masterFd_ = -1; pid_ = -1; } -// void ProcessWorker::writerLoop() { -// while (true) { -// Logger::Trace("ProcessWorker::writerLoop ID {}", workerId_); -// std::vector data; -// { -// std::unique_lock lock(sendMutex_); -// sendCv_.wait(lock, [this] {return !writerRunning_ || !sendQueue_.empty();}); -// if (!writerRunning_ && sendQueue_.empty()) return; -// data = std::move(sendQueue_.front()); -// sendQueue_.pop_front(); -// } -// Logger::Trace("ProcessWorker::writerLoop {} writing {} bytes", workerId_, data.size()); -// std::error_code ec; -// asio::write(masterStream_, asio::buffer(data), ec); -// Logger::Trace("ProcessWorker::writerLoop {} write completed: ec = {}", workerId_, ec.message()); -// if (ec) { -// Logger::Error("ProcessWorker::writerLoop for worker {} failed: {}", workerId_, ec.message()); -// break; -// } -// } -// } - -// void ProcessWorker::StartWriterThread() { -// writerRunning_ = true; -// writerThread_ = std::thread(&ProcessWorker::writerLoop, this); -// } - -// void ProcessWorker::StopWriter() { -// writerRunning_ = false; -// sendCv_.notify_all(); -// } - -// void ProcessWorker::JoinWriterThread() { -// if (writerThread_.joinable()) -// writerThread_.join(); -// } - ProcessPool::ProcessPool(asio::io_context& io, const std::vector& groups) : io_(io), groups_(groups) {} void ProcessPool::Initialize() { doSpawnWorkers(); - for (auto& w : workers_) { - //w->StartWriterThread(); - StartReadingFromWorker(w); - } ready_.store(true); running_.store(true); } @@ -165,100 +70,32 @@ void ProcessPool::Shutdown() { if (!running_.exchange(false)) return; Logger::Trace("ProcessPool::Shutdown: running..."); for (auto& w : workers_) w->Shutdown(); - Logger::Trace("ProcessPool::Shutdown: workers_->Shutdown() finished"); - // for (auto& w : workers_) w->StopWriter(); - // Logger::Trace("ProcessPool::Shutdown: workers_->StopWriter() finished"); - for (auto& pair : readerRunningFlags_) *pair.second = false; - Logger::Trace("ProcessPool::Shutdown: readerRunningFlags_ finished"); + Logger::Trace("ProcessPool::Shutdown: workers shutdown finished"); io_.stop(); Logger::Trace("ProcessPool::Shutdown: io_.stop finished"); - // for (auto& w : workers_) w->JoinWriterThread(); - // Logger::Trace("ProcessPool::Shutdown: workers_->JoinWriterThread() finished"); } void ProcessPool::SetWorker(std::function func) { worker_ = std::move(func); } -void ProcessPool::SetMasterMessageHandler(ProcessWorker::MasterMessageHandler handler) { - masterHandler_ = std::move(handler); +void ProcessPool::SetGameLogicWorker(std::function func) { + gameLogicWorkerFunc_ = std::move(func); } -void ProcessPool::StartReadingFromWorker(std::shared_ptr worker) { - auto running = std::make_shared>(true); - readerRunningFlags_[worker->GetId()] = running; - asio::co_spawn(io_, [this, worker, running]() -> asio::awaitable { - auto& stream = worker->GetMasterStream(); - while (running->load()) - { - uint32_t netLen = 0; - std::error_code ec; - co_await asio::async_read(stream, - asio::buffer(&netLen, sizeof(netLen)), - asio::redirect_error(asio::use_awaitable, ec)); - if (ec == asio::error::eof || ec == asio::error::connection_reset) - break; - if (ec) { - Logger::Error("ProcessPool::StartReadingFromWorker: ID {} read error: {}", worker->GetId(), ec.message()); - break; - } - uint32_t msgLen = ntohl(netLen); - if (msgLen == 0 || msgLen > BinaryProtocol::MAX_MESSAGE_SIZE) - continue; - std::vector msg(msgLen); - co_await asio::async_read(stream, - asio::buffer(msg), - asio::redirect_error(asio::use_awaitable, ec)); - if (ec) { - Logger::Error("ProcessPool::StartReadingFromWorker: ID {} message read error: {}", worker->GetId(), ec.message()); - break; - } - BinaryProtocol::BinaryReader r(msg.data(), msg.size()); - uint32_t corrId = r.ReadUInt32(); - uint64_t sessionId = r.ReadUInt64(); - uint16_t msgType = r.ReadUInt16(); - uint32_t bodyLen = r.ReadUInt32(); - std::vector body; - if (bodyLen > 0 && r.Remaining() >= bodyLen) - body = r.ReadBytes(bodyLen); - if (masterHandler_) - masterHandler_(worker->GetId(), corrId, sessionId, msgType, std::move(body)); - } - Logger::Trace("ProcessPool::StartReadingFromWorker coroutine finished ID {}", worker->GetId()); - }, asio::detached); +void ProcessPool::SetMasterMessageHandler(ProcessWorker::MasterMessageHandler handler) { + masterHandler_ = std::move(handler); } -bool ProcessPool::SendToWorker(int workerId, const std::vector& message) { - if (workerId >= 0 && workerId < static_cast(workers_.size())) { - workers_[workerId]->SendAsync(message); - return true; +void ProcessPool::onWorkerMessage(int workerId, const IPCEnvelope& env) { + if (masterHandler_) { + masterHandler_(workerId, env.correlationId, env.sessionId, env.messageType, env.payload); } - return false; -} - -void ProcessPool::BroadcastToOtherWorkers(const std::vector& msg, int owner_id) { - BinaryProtocol::BinaryWriter w; - w.WriteUInt32(0); // correlationId = 0 (broadcast) - w.WriteUInt64(0); // sessionId = 0 → worker‑side broadcast - w.WriteUInt16(0); - w.WriteUInt32(static_cast(msg.size())); - w.WriteRaw(msg.data(), msg.size()); - auto frame = w.GetBuffer(); - for (auto& w : workers_) - if (w->GetId() != owner_id) - w->SendAsync(frame); } -void ProcessPool::BroadcastToAllWorkers(const std::vector& msg) { - BinaryProtocol::BinaryWriter w; - w.WriteUInt32(0); // correlationId = 0 (broadcast) - w.WriteUInt64(0); // sessionId = 0 → worker‑side broadcast - w.WriteUInt16(0); - w.WriteUInt32(static_cast(msg.size())); - w.WriteRaw(msg.data(), msg.size()); - auto frame = w.GetBuffer(); - for (auto& worker : workers_) { - worker->SendAsync(frame); +void ProcessPool::onGameLogicMessage(const IPCEnvelope& env) { + if (masterHandler_) { + masterHandler_(gameLogicWorkerId_, env.correlationId, env.sessionId, env.messageType, env.payload); } } @@ -266,13 +103,19 @@ void ProcessPool::doSpawnWorkers() { int globalId = 0; for (size_t gi = 0; gi < groups_.size(); ++gi) { for (int i = 0; i < groups_[gi].count; ++i, ++globalId) { - auto worker = std::make_shared(io_, globalId, groups_[gi]); + auto worker = std::make_shared(io_, globalId, groups_[gi], WorkerType::Child); try { worker->Start(); workers_.push_back(worker); + worker->GetChannel()->Start([this, globalId](const IPCEnvelope& env) { + onWorkerMessage(globalId, env); + }); } catch (const std::exception& err) { if (std::string(err.what()) == "worker_function") { - int fd = worker->GetMasterReadFd(); + int fd = worker->GetChannel() ? worker->GetChannel()->GetFd() : -1; + if (fd == -1) { + fd = worker->GetId(); + } worker_(globalId, groups_[gi], fd); Logger::Trace("ProcessPool::doSpawnWorkers: worker {} exiting cleanly", globalId); _exit(0); @@ -284,35 +127,145 @@ void ProcessPool::doSpawnWorkers() { } } +int ProcessPool::SpawnGameLogicWorker() { + int globalId = static_cast(workers_.size()); + WorkerGroupConfig emptyCfg; + emptyCfg.protocol = "internal"; + emptyCfg.host = "127.0.0.1"; + emptyCfg.port = 0; + emptyCfg.count = 1; + emptyCfg.threads = 1; + auto worker = std::make_shared(io_, globalId, emptyCfg, WorkerType::GameLogic); + try { + worker->Start(); + workers_.push_back(worker); + gameLogicWorkerId_ = globalId; + worker->GetChannel()->Start([this](const IPCEnvelope& env) { + onGameLogicMessage(env); + }); + return globalId; + } catch (const std::exception& err) { + if (std::string(err.what()) == "worker_function") { + int fd = worker->GetChannel() ? worker->GetChannel()->GetFd() : -1; + if (fd == -1) { + fd = worker->GetId(); + } + if (gameLogicWorkerFunc_) { + gameLogicWorkerFunc_(globalId, fd); + } + _exit(0); + } else { + Logger::Error("ProcessPool::SpawnGameLogicWorker failed: {}", err.what()); + } + } + return -1; +} + +bool ProcessPool::RespawnGameLogicWorker() { + if (gameLogicWorkerId_ >= 0 && gameLogicWorkerId_ < static_cast(workers_.size())) { + auto& old = workers_[gameLogicWorkerId_]; + old->Shutdown(); + } + int newId = SpawnGameLogicWorker(); + return newId >= 0; +} + +bool ProcessPool::IsGameLogicWorkerAlive() const { + if (gameLogicWorkerId_ < 0 || gameLogicWorkerId_ >= static_cast(workers_.size())) return false; + pid_t pid = workers_[gameLogicWorkerId_]->GetPid(); + if (pid <= 0) return false; + return (kill(pid, 0) == 0); +} + +std::shared_ptr ProcessPool::GetGameLogicChannel() const { + if (gameLogicWorkerId_ >= 0 && gameLogicWorkerId_ < static_cast(workers_.size())) { + return workers_[gameLogicWorkerId_]->GetChannel(); + } + return nullptr; +} + +bool ProcessPool::SendToWorker(int workerId, const std::vector& message) { + if (workerId >= 0 && workerId < static_cast(workers_.size())) { + auto channel = workers_[workerId]->GetChannel(); + if (channel && channel->IsOpen()) { + IPCEnvelope env; + env.correlationId = 0; + env.sessionId = 0; + env.messageType = 0; + env.payload = message; + channel->SendAsync(env); + return true; + } + } + return false; +} + +void ProcessPool::BroadcastToOtherWorkers(const std::vector& msg, int owner_id) { + IPCEnvelope env; + env.correlationId = 0; + env.sessionId = 0; + env.messageType = 0; + env.payload = msg; + for (auto& w : workers_) { + if (w->GetId() != owner_id && w->GetType() == WorkerType::Child) { + auto channel = w->GetChannel(); + if (channel && channel->IsOpen()) { + channel->SendAsync(env); + } + } + } +} + +void ProcessPool::BroadcastToAllWorkers(const std::vector& msg) { + IPCEnvelope env; + env.correlationId = 0; + env.sessionId = 0; + env.messageType = 0; + env.payload = msg; + for (auto& w : workers_) { + if (w->GetType() == WorkerType::Child) { + auto channel = w->GetChannel(); + if (channel && channel->IsOpen()) { + channel->SendAsync(env); + } + } + } +} + size_t ProcessPool::GetTotalWorkerCount() const { return workers_.size(); } + bool ProcessPool::IsWorkerAlive(int workerId) const { if (workerId < 0 || workerId >= static_cast(workers_.size())) return false; pid_t pid = workers_[workerId]->GetPid(); if (pid <= 0) return false; return (kill(pid, 0) == 0); } + bool ProcessPool::IsWorkersReady() const { return ready_; } void ProcessPool::WaitForWorkers() {} bool ProcessPool::SendReplyToWorker(int workerId, uint32_t correlationId, const std::vector& binaryData) { - BinaryProtocol::BinaryWriter w; - w.WriteUInt32(correlationId); - w.WriteUInt64(0); - w.WriteUInt16(0); - w.WriteUInt32(static_cast(binaryData.size())); - w.WriteRaw(binaryData.data(), binaryData.size()); - return SendToWorker(workerId, w.GetBuffer()); + if (workerId < 0 || workerId >= static_cast(workers_.size())) return false; + auto channel = workers_[workerId]->GetChannel(); + if (!channel || !channel->IsOpen()) return false; + IPCEnvelope env; + env.correlationId = correlationId; + env.sessionId = 0; + env.messageType = 0; + env.payload = binaryData; + channel->SendAsync(env); + return true; } bool ProcessPool::PushToWorker(int workerId, uint64_t sessionId, const std::vector& binaryData) { - if (workerId < 0 || workerId >= static_cast(workers_.size())) - return false; - BinaryProtocol::BinaryWriter w; - w.WriteUInt32(0); // corrId = 0 → push marker - w.WriteUInt64(sessionId); // target session - w.WriteUInt16(0); // unused - w.WriteUInt32(static_cast(binaryData.size())); - w.WriteRaw(binaryData.data(), binaryData.size()); - workers_[workerId]->SendAsync(w.GetBuffer()); + if (workerId < 0 || workerId >= static_cast(workers_.size())) return false; + auto channel = workers_[workerId]->GetChannel(); + if (!channel || !channel->IsOpen()) return false; + IPCEnvelope env; + env.correlationId = 0; + env.sessionId = sessionId; + env.messageType = 0; + env.payload = binaryData; + channel->SendAsync(env); return true; } From 89e8761246328e8b71a9aefb21aef01258c9545c Mon Sep 17 00:00:00 2001 From: fir <29286243+usermicrodevices@users.noreply.github.com> Date: Thu, 4 Jun 2026 21:27:45 +0300 Subject: [PATCH 10/15] added first tests as python+bash scripts --- tests/client_binary.py | 209 +++++++++++++++++++++++++++++++++++ tests/client_websocket.py | 118 ++++++++++++++++++++ tests/tests.sh | 223 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 550 insertions(+) create mode 100755 tests/client_binary.py create mode 100755 tests/client_websocket.py create mode 100755 tests/tests.sh diff --git a/tests/client_binary.py b/tests/client_binary.py new file mode 100755 index 0000000..9258e74 --- /dev/null +++ b/tests/client_binary.py @@ -0,0 +1,209 @@ +#!/usr/bin/env python3 +""" +Binary protocol broadcast chat test client. + +Connects to the gameserver on port 9999 using the custom binary protocol, +sends chat messages, and prints broadcast responses from all connected clients. + +Usage: + python3 client_binary.py [host] [port] [num_clients] [messages] +""" + +import socket +import struct +import threading +import time +import sys +import binascii + +PROTOCOL_VERSION = 1 +MSG_TYPE_CHAT = 800 +MSG_TYPE_HEARTBEAT = 100 +MSG_TYPE_PROTOCOL_NEGOTIATION = 2 +MSG_TYPE_SUCCESS = 3 + +HEADER_SIZE = 20 # 1+1+2+4+4+4+4 + + +def crc32(data: bytes) -> int: + return binascii.crc32(data) & 0xFFFFFFFF + + +def encode_string(s: str) -> bytes: + encoded = s.encode("utf-8") + return struct.pack("!H", len(encoded)) + encoded + + +def decode_string(data: bytes, offset: int) -> tuple: + length = struct.unpack_from("!H", data, offset)[0] + offset += 2 + s = data[offset:offset + length].decode("utf-8") + return s, offset + length + + +def build_header(msg_type: int, seq: int, payload_len: int, checksum: int) -> bytes: + return struct.pack( + "!BBHIII I", + PROTOCOL_VERSION, # version + 0, # flags + msg_type, # message_type + seq, # sequence + int(time.time() * 1000) & 0xFFFFFFFF, # timestamp + payload_len, # length + checksum, # checksum + ) + + +def build_chat_message(sender: str, message: str, seq: int) -> bytes: + body = encode_string(sender) + encode_string(message) + struct.pack("!Q", int(time.time() * 1000)) + checksum = crc32(body) + header = build_header(MSG_TYPE_CHAT, seq, len(body), checksum) + return header + body + + +def build_protocol_negotiation(seq: int) -> bytes: + body = b"\x01" # version=1 + body += b"\x01" # supports_compression=true + body += b"\x00" # supports_encryption=false + body += struct.pack("!I", 10 * 1024 * 1024) # max_message_size + body += struct.pack("!H", 10) # supported message types count + for i in range(1, 11): + body += struct.pack("!H", i * 100) + checksum = crc32(body) + header = build_header(MSG_TYPE_PROTOCOL_NEGOTIATION, seq, len(body), checksum) + return header + body + + +def recv_exact(sock: socket.socket, n: int) -> bytes: + buf = b"" + while len(buf) < n: + chunk = sock.recv(n - len(buf)) + if not chunk: + raise ConnectionError("Connection closed") + buf += chunk + return buf + + +def recv_message(sock: socket.socket) -> tuple: + raw_len = recv_exact(sock, 4) + msg_len = struct.unpack("!I", raw_len)[0] + if msg_len == 0 or msg_len > 10 * 1024 * 1024: + return None, None + data = recv_exact(sock, msg_len) + if len(data) < HEADER_SIZE: + return None, None + version = data[0] + flags = data[1] + msg_type = struct.unpack_from("!H", data, 2)[0] + seq = struct.unpack_from("!I", data, 4)[0] + ts = struct.unpack_from("!I", data, 8)[0] + length = struct.unpack_from("!I", data, 12)[0] + checksum = struct.unpack_from("!I", data, 16)[0] + body = data[HEADER_SIZE:] + return msg_type, body + + +def client_worker(host: str, port: int, client_id: int, messages: list, ready_event: threading.Event, all_ready: threading.Event): + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + sock.connect((host, port)) + print(f"[Client {client_id}] Connected to {host}:{port}") + + seq = 1 + + # Protocol negotiation + neg = build_protocol_negotiation(seq) + sock.sendall(struct.pack("!I", len(neg)) + neg) + seq += 1 + + # Read negotiation response + try: + msg_type, body = recv_message(sock) + if msg_type is not None: + print(f"[Client {client_id}] Protocol negotiation response: type={msg_type}") + except Exception: + pass + + ready_event.set() + all_ready.wait() + + time.sleep(0.1 * client_id) + + for msg_text in messages: + full_msg = f"Client{client_id}: {msg_text}" + packet = build_chat_message(f"User{client_id}", full_msg, seq) + sock.sendall(struct.pack("!I", len(packet)) + packet) + print(f"[Client {client_id}] Sent: {full_msg}") + seq += 1 + time.sleep(0.3) + + print(f"[Client {client_id}] All messages sent, listening for broadcasts...") + while True: + try: + msg_type, body = recv_message(sock) + if msg_type is None: + break + if msg_type == MSG_TYPE_CHAT: + sender, off = decode_string(body, 0) + message, off = decode_string(body, off) + print(f"[Client {client_id}] Broadcast received: <{sender}> {message}") + elif msg_type == MSG_TYPE_HEARTBEAT: + pass + except (ConnectionError, OSError): + break + + except Exception as e: + print(f"[Client {client_id}] Error: {e}") + finally: + try: + sock.close() + except Exception: + pass + print(f"[Client {client_id}] Disconnected") + + +def main(): + host = sys.argv[1] if len(sys.argv) > 1 else "127.0.0.1" + port = int(sys.argv[2]) if len(sys.argv) > 2 else 9999 + num_clients = int(sys.argv[3]) if len(sys.argv) > 3 else 3 + num_messages = int(sys.argv[4]) if len(sys.argv) > 4 else 2 + + all_messages = [f"Hello from everyone! Round {r+1}" for r in range(num_messages)] + + print(f"Binary chat test: {num_clients} clients, {num_messages} messages each") + print(f"Connecting to {host}:{port}") + print("-" * 60) + + ready_events = [threading.Event() for _ in range(num_clients)] + all_ready = threading.Event() + + threads = [] + for i in range(num_clients): + t = threading.Thread( + target=client_worker, + args=(host, port, i, all_messages, ready_events[i], all_ready), + daemon=True, + ) + t.start() + threads.append(t) + + for e in ready_events: + e.wait(timeout=5) + + print("-" * 60) + print("All clients connected. Starting broadcast test...") + print("-" * 60) + all_ready.set() + + try: + time.sleep(3 + num_messages * 0.5) + except KeyboardInterrupt: + pass + + print("-" * 60) + print("Test complete.") + + +if __name__ == "__main__": + main() diff --git a/tests/client_websocket.py b/tests/client_websocket.py new file mode 100755 index 0000000..bc7e3ac --- /dev/null +++ b/tests/client_websocket.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python3 +""" +WebSocket protocol broadcast chat test client. + +Connects to the gameserver on port 8080 using WebSocket, +sends chat messages as JSON, and prints broadcast responses. + +Usage: + python3 client_websocket.py [host] [port] [num_clients] [messages] +""" + +import asyncio +import json +import sys +import time + +try: + import websockets +except ImportError: + print("ERROR: 'websockets' package required. Install with:") + print(" pip install websockets") + sys.exit(1) + + +async def client_worker(host: str, port: int, client_id: int, messages: list, ready_event: asyncio.Event, all_ready: asyncio.Event): + uri = f"ws://{host}:{port}/game" + try: + async with websockets.connect(uri) as ws: + print(f"[Client {client_id}] Connected to {uri}") + + ready_event.set() + await all_ready.wait() + + await asyncio.sleep(0.1 * client_id) + + for msg_text in messages: + full_msg = f"Client{client_id}: {msg_text}" + chat_msg = { + "msg": "chat_message", + "sender": f"User{client_id}", + "message": full_msg, + "timestamp": int(time.time() * 1000), + } + await ws.send(json.dumps(chat_msg)) + print(f"[Client {client_id}] Sent: {full_msg}") + await asyncio.sleep(0.3) + + print(f"[Client {client_id}] All messages sent, listening for broadcasts...") + + async for raw in ws: + try: + data = json.loads(raw) + msg_type = data.get("msg", "") + if msg_type == "chat_message": + sender = data.get("sender", "?") + message = data.get("message", "?") + print(f"[Client {client_id}] Broadcast received: <{sender}> {message}") + elif msg_type == "authentication": + print(f"[Client {client_id}] Auth response: {data.get('desc', '')}") + except json.JSONDecodeError: + pass + + except websockets.exceptions.ConnectionClosed: + print(f"[Client {client_id}] Connection closed") + except Exception as e: + print(f"[Client {client_id}] Error: {e}") + finally: + print(f"[Client {client_id}] Disconnected") + + +async def main(): + host = sys.argv[1] if len(sys.argv) > 1 else "127.0.0.1" + port = int(sys.argv[2]) if len(sys.argv) > 2 else 8080 + num_clients = int(sys.argv[3]) if len(sys.argv) > 3 else 3 + num_messages = int(sys.argv[4]) if len(sys.argv) > 4 else 2 + + all_messages = [f"Hello from everyone! Round {r+1}" for r in range(num_messages)] + + print(f"WebSocket chat test: {num_clients} clients, {num_messages} messages each") + print(f"Connecting to ws://{host}:{port}/game") + print("-" * 60) + + ready_events = [asyncio.Event() for _ in range(num_clients)] + all_ready = asyncio.Event() + + tasks = [ + asyncio.create_task( + client_worker(host, port, i, all_messages, ready_events[i], all_ready) + ) + for i in range(num_clients) + ] + + for e in ready_events: + try: + await asyncio.wait_for(e.wait(), timeout=5) + except asyncio.TimeoutError: + print("WARNING: Not all clients connected in time") + + print("-" * 60) + print("All clients connected. Starting broadcast test...") + print("-" * 60) + all_ready.set() + + try: + await asyncio.sleep(3 + num_messages * 0.5) + except KeyboardInterrupt: + pass + + for t in tasks: + t.cancel() + await asyncio.gather(*tasks, return_exceptions=True) + + print("-" * 60) + print("Test complete.") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/tests/tests.sh b/tests/tests.sh new file mode 100755 index 0000000..4a948ad --- /dev/null +++ b/tests/tests.sh @@ -0,0 +1,223 @@ +#!/usr/bin/env bash +# +# tests.sh - Build, start server, run all chat broadcast tests, collect results. +# +# Usage: +# ./tests.sh # full run +# ./tests.sh --build-only # build only, skip tests +# ./tests.sh --skip-build # skip build, run tests only +# ./tests.sh --clients 5 # custom client count (default 3) +# ./tests.sh --messages 4 # custom message count (default 2) +# + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +BUILD_DIR="$ROOT_DIR/build" +TESTS_DIR="$ROOT_DIR/tests" +SERVER_BIN="$BUILD_DIR/gameserver" +LOG_DIR="$ROOT_DIR/logs" +SERVER_LOG="$LOG_DIR/server_test.log" +RESULTS_DIR="$TESTS_DIR" + +HOST="127.0.0.1" +BINARY_PORT=9999 +WEBSOCKET_PORT=8080 +NUM_CLIENTS=3 +NUM_MESSAGES=2 +SERVER_PID=0 +BUILD_ONLY=false +SKIP_BUILD=false +SERVER_TIMEOUT=10 +PASS=0 +FAIL=0 +TESTS_RUN=0 + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +CYAN='\033[0;36m' +NC='\033[0m' + +usage() { + sed -n '2,/^$/s/^#//p' "$0" + exit 0 +} + +log() { echo -e "${CYAN}[TEST]${NC} $*"; } +pass() { echo -e "${GREEN}[PASS]${NC} $*"; PASS=$((PASS+1)); TESTS_RUN=$((TESTS_RUN+1)); } +fail() { echo -e "${RED}[FAIL]${NC} $*"; FAIL=$((FAIL+1)); TESTS_RUN=$((TESTS_RUN+1)); } +warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } + +cleanup() { + if [ "$SERVER_PID" -gt 0 ] && kill -0 "$SERVER_PID" 2>/dev/null; then + log "Stopping server (PID $SERVER_PID)..." + kill -TERM "$SERVER_PID" 2>/dev/null || true + wait "$SERVER_PID" 2>/dev/null || true + sleep 1 + if kill -0 "$SERVER_PID" 2>/dev/null; then + kill -9 "$SERVER_PID" 2>/dev/null || true + fi + SERVER_PID=0 + fi +} + +trap cleanup EXIT INT TERM + +parse_args() { + while [ $# -gt 0 ]; do + case "$1" in + --build-only) BUILD_ONLY=true; shift ;; + --skip-build) SKIP_BUILD=true; shift ;; + --clients) NUM_CLIENTS="$2"; shift 2 ;; + --messages) NUM_MESSAGES="$2"; shift 2 ;; + -h|--help) usage ;; + *) echo "Unknown option: $1"; usage ;; + esac + done +} + +do_build() { + log "Building gameserver..." + mkdir -p "$BUILD_DIR" + cmake -S "$ROOT_DIR" -B "$BUILD_DIR" -DCMAKE_BUILD_TYPE=Release 2>&1 | tail -3 + cmake --build "$BUILD_DIR" --target gameserver -- -j"$(nproc)" 2>&1 | tail -3 + if [ ! -x "$SERVER_BIN" ]; then + fail "Build failed: $SERVER_BIN not found" + exit 1 + fi + pass "Build succeeded" +} + +start_server() { + log "Starting gameserver..." + mkdir -p "$LOG_DIR" + "$SERVER_BIN" > "$SERVER_LOG" 2>&1 & + SERVER_PID=$! + + for i in $(seq 1 "$SERVER_TIMEOUT"); do + if ! kill -0 "$SERVER_PID" 2>/dev/null; then + fail "Server crashed on startup (see $SERVER_LOG)" + cat "$SERVER_LOG" | tail -20 + exit 1 + fi + if ss -tlnp 2>/dev/null | grep -q ":${BINARY_PORT} " && \ + ss -tlnp 2>/dev/null | grep -q ":${WEBSOCKET_PORT} "; then + pass "Server started (PID $SERVER_PID)" + return 0 + fi + sleep 1 + done + + fail "Server did not start within ${SERVER_TIMEOUT}s" + cat "$SERVER_LOG" | tail -20 + exit 1 +} + +wait_for_server_ready() { + log "Waiting for server readiness..." + for i in $(seq 1 5); do + if nc -z "$HOST" "$BINARY_PORT" 2>/dev/null; then + return 0 + fi + sleep 0.5 + done + warn "Port check failed, proceeding anyway..." +} + +run_binary_test() { + log "Running binary protocol chat test (${NUM_CLIENTS} clients, ${NUM_MESSAGES} msgs)..." + local output + output=$(python3 "$TESTS_DIR/client_binary.py" "$HOST" "$BINARY_PORT" "$NUM_CLIENTS" "$NUM_MESSAGES" 2>&1) || true + echo "$output" + + local sent_count recv_count + sent_count=$(echo "$output" | grep -c "\[Client.*Sent:" || true) + recv_count=$(echo "$output" | grep -c "\[Client.*Broadcast received:" || true) + + if [ "$sent_count" -gt 0 ] && [ "$recv_count" -gt 0 ]; then + pass "Binary chat: sent=$sent_count received=$recv_count" + elif [ "$sent_count" -gt 0 ]; then + warn "Binary chat: sent=$sent_count but no broadcasts received (server may not route in single-process mode)" + else + fail "Binary chat: no messages sent" + fi +} + +run_websocket_test() { + log "Running WebSocket protocol chat test (${NUM_CLIENTS} clients, ${NUM_MESSAGES} msgs)..." + local output + output=$(python3 "$TESTS_DIR/client_websocket.py" "$HOST" "$WEBSOCKET_PORT" "$NUM_CLIENTS" "$NUM_MESSAGES" 2>&1) || true + echo "$output" + + local sent_count recv_count + sent_count=$(echo "$output" | grep -c "\[Client.*Sent:" || true) + recv_count=$(echo "$output" | grep -c "\[Client.*Broadcast received:" || true) + + if [ "$sent_count" -gt 0 ] && [ "$recv_count" -gt 0 ]; then + pass "WebSocket chat: sent=$sent_count received=$recv_count" + elif [ "$sent_count" -gt 0 ]; then + warn "WebSocket chat: sent=$sent_count but no broadcasts received (server may not route in single-process mode)" + else + fail "WebSocket chat: no messages sent" + fi +} + +print_summary() { + echo "" + echo "=========================================" + echo " TEST SUMMARY" + echo "=========================================" + echo -e " Total: ${TESTS_RUN}" + echo -e " ${GREEN}Passed: ${PASS}${NC}" + echo -e " ${RED}Failed: ${FAIL}${NC}" + echo "=========================================" + if [ "$FAIL" -gt 0 ]; then + echo -e " ${RED}SOME TESTS FAILED${NC}" + return 1 + else + echo -e " ${GREEN}ALL TESTS PASSED${NC}" + return 0 + fi +} + +# --- Main --- + +parse_args "$@" + +echo "" +echo "=========================================" +echo " GAMESERVER TEST SUITE" +echo "=========================================" +echo " Host: $HOST" +echo " Binary: port $BINARY_PORT" +echo " WebSocket: port $WEBSOCKET_PORT" +echo " Clients: $NUM_CLIENTS" +echo " Messages: $NUM_MESSAGES" +echo "=========================================" +echo "" + +if [ "$SKIP_BUILD" = false ]; then + do_build +else + log "Skipping build (--skip-build)" +fi + +if [ "$BUILD_ONLY" = true ]; then + log "Build-only mode, skipping tests" + exit 0 +fi + +start_server +wait_for_server_ready +sleep 1 + +run_binary_test +echo "" +run_websocket_test + +echo "" +log "Server log: $SERVER_LOG" + +print_summary From a38b234d2644bc0ea55076b85a751652396083be Mon Sep 17 00:00:00 2001 From: fir <29286243+usermicrodevices@users.noreply.github.com> Date: Thu, 4 Jun 2026 22:59:44 +0300 Subject: [PATCH 11/15] added logs directory --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index daa2e88..d215ed5 100644 --- a/.gitignore +++ b/.gitignore @@ -41,9 +41,10 @@ *.dwo .github/workflow -build/ CMakeFiles/ thirdparty/ +build/ +logs/ CMakeCache.txt add-*-repo.sh From 4b2cf147632a7482059240b927d77af49f2b9840 Mon Sep 17 00:00:00 2001 From: fir <29286243+usermicrodevices@users.noreply.github.com> Date: Thu, 4 Jun 2026 23:00:41 +0300 Subject: [PATCH 12/15] changed building logics --- tests/tests.sh | 57 +++++++++++++++++++++++--------------------------- 1 file changed, 26 insertions(+), 31 deletions(-) diff --git a/tests/tests.sh b/tests/tests.sh index 4a948ad..e2dd450 100755 --- a/tests/tests.sh +++ b/tests/tests.sh @@ -1,11 +1,10 @@ #!/usr/bin/env bash # -# tests.sh - Build, start server, run all chat broadcast tests, collect results. +# tests.sh - Start server and run all chat broadcast tests. # # Usage: -# ./tests.sh # full run -# ./tests.sh --build-only # build only, skip tests -# ./tests.sh --skip-build # skip build, run tests only +# ./tests.sh # run tests (no build) +# ./tests.sh --rebuild # rebuild via build.sh, then run tests # ./tests.sh --clients 5 # custom client count (default 3) # ./tests.sh --messages 4 # custom message count (default 2) # @@ -19,7 +18,6 @@ TESTS_DIR="$ROOT_DIR/tests" SERVER_BIN="$BUILD_DIR/gameserver" LOG_DIR="$ROOT_DIR/logs" SERVER_LOG="$LOG_DIR/server_test.log" -RESULTS_DIR="$TESTS_DIR" HOST="127.0.0.1" BINARY_PORT=9999 @@ -27,8 +25,7 @@ WEBSOCKET_PORT=8080 NUM_CLIENTS=3 NUM_MESSAGES=2 SERVER_PID=0 -BUILD_ONLY=false -SKIP_BUILD=false +DO_REBUILD=false SERVER_TIMEOUT=10 PASS=0 FAIL=0 @@ -68,21 +65,18 @@ trap cleanup EXIT INT TERM parse_args() { while [ $# -gt 0 ]; do case "$1" in - --build-only) BUILD_ONLY=true; shift ;; - --skip-build) SKIP_BUILD=true; shift ;; - --clients) NUM_CLIENTS="$2"; shift 2 ;; - --messages) NUM_MESSAGES="$2"; shift 2 ;; - -h|--help) usage ;; - *) echo "Unknown option: $1"; usage ;; + --rebuild) DO_REBUILD=true; shift ;; + --clients) NUM_CLIENTS="$2"; shift 2 ;; + --messages) NUM_MESSAGES="$2"; shift 2 ;; + -h|--help) usage ;; + *) echo "Unknown option: $1"; usage ;; esac done } -do_build() { - log "Building gameserver..." - mkdir -p "$BUILD_DIR" - cmake -S "$ROOT_DIR" -B "$BUILD_DIR" -DCMAKE_BUILD_TYPE=Release 2>&1 | tail -3 - cmake --build "$BUILD_DIR" --target gameserver -- -j"$(nproc)" 2>&1 | tail -3 +do_rebuild() { + log "Rebuilding gameserver via build.sh..." + "$ROOT_DIR/build.sh" --with-sqlite if [ ! -x "$SERVER_BIN" ]; then fail "Build failed: $SERVER_BIN not found" exit 1 @@ -91,15 +85,16 @@ do_build() { } start_server() { - log "Starting gameserver..." + log "Starting gameserver from $BUILD_DIR..." mkdir -p "$LOG_DIR" - "$SERVER_BIN" > "$SERVER_LOG" 2>&1 & + rsync -a --delete "$ROOT_DIR/config/" "$BUILD_DIR/config/" + (cd "$BUILD_DIR" && "./gameserver") > "$SERVER_LOG" 2>&1 & SERVER_PID=$! for i in $(seq 1 "$SERVER_TIMEOUT"); do if ! kill -0 "$SERVER_PID" 2>/dev/null; then fail "Server crashed on startup (see $SERVER_LOG)" - cat "$SERVER_LOG" | tail -20 + tail -20 "$SERVER_LOG" exit 1 fi if ss -tlnp 2>/dev/null | grep -q ":${BINARY_PORT} " && \ @@ -111,7 +106,7 @@ start_server() { done fail "Server did not start within ${SERVER_TIMEOUT}s" - cat "$SERVER_LOG" | tail -20 + tail -20 "$SERVER_LOG" exit 1 } @@ -139,7 +134,7 @@ run_binary_test() { if [ "$sent_count" -gt 0 ] && [ "$recv_count" -gt 0 ]; then pass "Binary chat: sent=$sent_count received=$recv_count" elif [ "$sent_count" -gt 0 ]; then - warn "Binary chat: sent=$sent_count but no broadcasts received (server may not route in single-process mode)" + warn "Binary chat: sent=$sent_count but no broadcasts received" else fail "Binary chat: no messages sent" fi @@ -158,7 +153,7 @@ run_websocket_test() { if [ "$sent_count" -gt 0 ] && [ "$recv_count" -gt 0 ]; then pass "WebSocket chat: sent=$sent_count received=$recv_count" elif [ "$sent_count" -gt 0 ]; then - warn "WebSocket chat: sent=$sent_count but no broadcasts received (server may not route in single-process mode)" + warn "WebSocket chat: sent=$sent_count but no broadcasts received" else fail "WebSocket chat: no messages sent" fi @@ -195,18 +190,18 @@ echo " Binary: port $BINARY_PORT" echo " WebSocket: port $WEBSOCKET_PORT" echo " Clients: $NUM_CLIENTS" echo " Messages: $NUM_MESSAGES" +echo " Rebuild: $DO_REBUILD" echo "=========================================" echo "" -if [ "$SKIP_BUILD" = false ]; then - do_build -else - log "Skipping build (--skip-build)" +if [ "$DO_REBUILD" = true ]; then + do_rebuild fi -if [ "$BUILD_ONLY" = true ]; then - log "Build-only mode, skipping tests" - exit 0 +if [ ! -x "$SERVER_BIN" ]; then + fail "Server binary not found: $SERVER_BIN" + echo " Run with --rebuild to build, or run build.sh first" + exit 1 fi start_server From 88113cdf84d666587f9a75a768c50b42a5461f67 Mon Sep 17 00:00:00 2001 From: fir <29286243+usermicrodevices@users.noreply.github.com> Date: Thu, 4 Jun 2026 23:01:11 +0300 Subject: [PATCH 13/15] fix IPC bugs --- include/network/ClientListener.hpp | 2 +- include/process/ProcessPool.hpp | 3 ++- src/network/ClientListener.cpp | 3 ++- src/network/ConnectionManager.cpp | 1 + src/process/IPCChannel.cpp | 9 ++++++++- src/process/ProcessPool.cpp | 10 ++-------- tests/client_binary.py | 0 tests/client_websocket.py | 0 8 files changed, 16 insertions(+), 12 deletions(-) mode change 100755 => 100644 tests/client_binary.py mode change 100755 => 100644 tests/client_websocket.py diff --git a/include/network/ClientListener.hpp b/include/network/ClientListener.hpp index 6ed7fc8..340374b 100644 --- a/include/network/ClientListener.hpp +++ b/include/network/ClientListener.hpp @@ -20,7 +20,7 @@ class ClientListener { private: int workerId_; asio::io_context io_; - std::unique_ptr channel_; + std::shared_ptr channel_; std::thread ioThread_; std::atomic stopping_{false}; std::shared_ptr manager_; diff --git a/include/process/ProcessPool.hpp b/include/process/ProcessPool.hpp index ef26fb5..c801183 100644 --- a/include/process/ProcessPool.hpp +++ b/include/process/ProcessPool.hpp @@ -52,6 +52,7 @@ class ProcessWorker : public std::enable_shared_from_this { WorkerType GetType() const; std::shared_ptr GetChannel() { return channel_; } + int GetMasterFd() const { return masterFd_; } using MasterMessageHandler = std::function { std::shared_ptr channel_; }; -class ProcessPool : public std::enable_shared_from_this { +class ProcessPool { public: ProcessPool(asio::io_context& io, const std::vector& groups); void Initialize(); diff --git a/src/network/ClientListener.cpp b/src/network/ClientListener.cpp index 1d77eba..b56a02a 100644 --- a/src/network/ClientListener.cpp +++ b/src/network/ClientListener.cpp @@ -3,7 +3,7 @@ ClientListener::ClientListener(const WorkerGroupConfig& groupConfig, int masterFd, int workerId) : workerId_(workerId) , io_() - , channel_(std::make_unique(io_, masterFd)) + , channel_(std::make_shared(io_, masterFd)) , manager_(std::make_shared(groupConfig, [this](uint32_t corrId, uint64_t sessId, uint16_t msgType, const std::vector& body) { sendToMaster(corrId, sessId, msgType, body); @@ -41,6 +41,7 @@ void ClientListener::onMasterMessage(const IPCEnvelope& env) { } void ClientListener::sendToMaster(uint32_t correlationId, uint64_t sessionId, uint16_t messageType, const std::vector& body) { + Logger::Trace("ClientListener::sendToMaster: corrId={}, session={}, type={}, bodySize={}", correlationId, sessionId, messageType, body.size()); IPCEnvelope env; env.correlationId = correlationId; env.sessionId = sessionId; diff --git a/src/network/ConnectionManager.cpp b/src/network/ConnectionManager.cpp index 0de35a7..d99f50a 100644 --- a/src/network/ConnectionManager.cpp +++ b/src/network/ConnectionManager.cpp @@ -234,6 +234,7 @@ void ConnectionManager::doAccept() { void ConnectionManager::onClientMessage(uint64_t sessionId, uint16_t type, const std::vector& data) { uint32_t corrId = nextCorrelationId_++; pendingReplies_[corrId] = {sessionId, type}; + Logger::Trace("ConnectionManager::onClientMessage: sessionId={}, type={}, corrId={}, dataSize={}", sessionId, type, corrId, data.size()); masterSender_(corrId, sessionId, type, data); } diff --git a/src/process/IPCChannel.cpp b/src/process/IPCChannel.cpp index a1edb6e..10a3690 100644 --- a/src/process/IPCChannel.cpp +++ b/src/process/IPCChannel.cpp @@ -68,9 +68,13 @@ void IPCChannel::doRead() { if (stopped_) return; auto self = shared_from_this(); auto lengthBuf = std::make_shared(0); + Logger::Trace("IPCChannel::doRead: starting async_read on fd={}, stopped={}", stream_.native_handle(), stopped_.load()); asio::async_read(stream_, asio::buffer(lengthBuf.get(), sizeof(uint32_t)), [self, lengthBuf](std::error_code ec, std::size_t) { - if (ec || self->stopped_) return; + if (ec || self->stopped_) { + Logger::Trace("IPCChannel::doRead: async_read error ec={}, stopped={}", ec.value(), self->stopped_.load()); + return; + } uint32_t msgLen = ntohl(*lengthBuf); if (msgLen == 0 || msgLen > BinaryProtocol::MAX_MESSAGE_SIZE) { self->doRead(); @@ -81,6 +85,7 @@ void IPCChannel::doRead() { [self, msgBuffer](std::error_code ec, std::size_t) { if (ec || self->stopped_) return; IPCEnvelope env = decodeFrame(msgBuffer->data(), msgBuffer->size()); + Logger::Trace("IPCChannel::doRead: received envelope, corrId={}, session={}, type={}, payloadSize={}", env.correlationId, env.sessionId, env.messageType, env.payload.size()); if (self->onMessage_) { self->onMessage_(env); } @@ -107,6 +112,7 @@ void IPCChannel::doWrite() { Logger::Error("IPCChannel::doWrite failed: {}", ec.message()); return; } + Logger::Trace("IPCChannel::doWrite: wrote {} bytes", data.size()); self->doWrite(); }); } @@ -122,6 +128,7 @@ void IPCChannel::SendAsync(const IPCEnvelope& envelope) { return; } } + Logger::Trace("IPCChannel::SendAsync: posting doWrite, frameSize={}, fd={}", frame.size(), stream_.native_handle()); asio::post(io_, [self = shared_from_this()]() { self->doWrite(); }); diff --git a/src/process/ProcessPool.cpp b/src/process/ProcessPool.cpp index 23d7b5d..e47f6a0 100644 --- a/src/process/ProcessPool.cpp +++ b/src/process/ProcessPool.cpp @@ -112,10 +112,7 @@ void ProcessPool::doSpawnWorkers() { }); } catch (const std::exception& err) { if (std::string(err.what()) == "worker_function") { - int fd = worker->GetChannel() ? worker->GetChannel()->GetFd() : -1; - if (fd == -1) { - fd = worker->GetId(); - } + int fd = worker->GetMasterFd(); worker_(globalId, groups_[gi], fd); Logger::Trace("ProcessPool::doSpawnWorkers: worker {} exiting cleanly", globalId); _exit(0); @@ -146,10 +143,7 @@ int ProcessPool::SpawnGameLogicWorker() { return globalId; } catch (const std::exception& err) { if (std::string(err.what()) == "worker_function") { - int fd = worker->GetChannel() ? worker->GetChannel()->GetFd() : -1; - if (fd == -1) { - fd = worker->GetId(); - } + int fd = worker->GetMasterFd(); if (gameLogicWorkerFunc_) { gameLogicWorkerFunc_(globalId, fd); } diff --git a/tests/client_binary.py b/tests/client_binary.py old mode 100755 new mode 100644 diff --git a/tests/client_websocket.py b/tests/client_websocket.py old mode 100755 new mode 100644 From 4f9abd277e55068f9694f90af34f187feee61dc7 Mon Sep 17 00:00:00 2001 From: fir <29286243+usermicrodevices@users.noreply.github.com> Date: Fri, 5 Jun 2026 01:01:16 +0300 Subject: [PATCH 14/15] fix tests --- tests/client_binary.py | 38 +++++++++++++++++++------------------- tests/tests.sh | 13 ++++++++++--- 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/tests/client_binary.py b/tests/client_binary.py index 9258e74..9cb493d 100644 --- a/tests/client_binary.py +++ b/tests/client_binary.py @@ -43,7 +43,7 @@ def decode_string(data: bytes, offset: int) -> tuple: def build_header(msg_type: int, seq: int, payload_len: int, checksum: int) -> bytes: return struct.pack( - "!BBHIII I", + " bytes: def recv_message(sock: socket.socket) -> tuple: - raw_len = recv_exact(sock, 4) - msg_len = struct.unpack("!I", raw_len)[0] - if msg_len == 0 or msg_len > 10 * 1024 * 1024: + header_data = recv_exact(sock, HEADER_SIZE) + version = header_data[0] + flags = header_data[1] + msg_type = struct.unpack_from(" 10 * 1024 * 1024: return None, None - data = recv_exact(sock, msg_len) - if len(data) < HEADER_SIZE: - return None, None - version = data[0] - flags = data[1] - msg_type = struct.unpack_from("!H", data, 2)[0] - seq = struct.unpack_from("!I", data, 4)[0] - ts = struct.unpack_from("!I", data, 8)[0] - length = struct.unpack_from("!I", data, 12)[0] - checksum = struct.unpack_from("!I", data, 16)[0] - body = data[HEADER_SIZE:] + body = recv_exact(sock, length) return msg_type, body @@ -114,16 +112,18 @@ def client_worker(host: str, port: int, client_id: int, messages: list, ready_ev # Protocol negotiation neg = build_protocol_negotiation(seq) - sock.sendall(struct.pack("!I", len(neg)) + neg) + sock.sendall(neg) seq += 1 - # Read negotiation response + # Read negotiation response (non-blocking, server may not respond) + sock.settimeout(1.0) try: msg_type, body = recv_message(sock) if msg_type is not None: print(f"[Client {client_id}] Protocol negotiation response: type={msg_type}") - except Exception: + except (socket.timeout, Exception): pass + sock.settimeout(None) ready_event.set() all_ready.wait() @@ -133,7 +133,7 @@ def client_worker(host: str, port: int, client_id: int, messages: list, ready_ev for msg_text in messages: full_msg = f"Client{client_id}: {msg_text}" packet = build_chat_message(f"User{client_id}", full_msg, seq) - sock.sendall(struct.pack("!I", len(packet)) + packet) + sock.sendall(packet) print(f"[Client {client_id}] Sent: {full_msg}") seq += 1 time.sleep(0.3) diff --git a/tests/tests.sh b/tests/tests.sh index e2dd450..6033075 100755 --- a/tests/tests.sh +++ b/tests/tests.sh @@ -16,7 +16,7 @@ ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" BUILD_DIR="$ROOT_DIR/build" TESTS_DIR="$ROOT_DIR/tests" SERVER_BIN="$BUILD_DIR/gameserver" -LOG_DIR="$ROOT_DIR/logs" +LOG_DIR="$BUILD_DIR/logs" SERVER_LOG="$LOG_DIR/server_test.log" HOST="127.0.0.1" @@ -84,6 +84,12 @@ do_rebuild() { pass "Build succeeded" } +copy_tests_to_build() { + mkdir -p "$BUILD_DIR" + cp "$TESTS_DIR"/client_binary.py "$BUILD_DIR/" + cp "$TESTS_DIR"/client_websocket.py "$BUILD_DIR/" +} + start_server() { log "Starting gameserver from $BUILD_DIR..." mkdir -p "$LOG_DIR" @@ -124,7 +130,7 @@ wait_for_server_ready() { run_binary_test() { log "Running binary protocol chat test (${NUM_CLIENTS} clients, ${NUM_MESSAGES} msgs)..." local output - output=$(python3 "$TESTS_DIR/client_binary.py" "$HOST" "$BINARY_PORT" "$NUM_CLIENTS" "$NUM_MESSAGES" 2>&1) || true + output=$(cd "$BUILD_DIR" && python3 client_binary.py "$HOST" "$BINARY_PORT" "$NUM_CLIENTS" "$NUM_MESSAGES" 2>&1) || true echo "$output" local sent_count recv_count @@ -143,7 +149,7 @@ run_binary_test() { run_websocket_test() { log "Running WebSocket protocol chat test (${NUM_CLIENTS} clients, ${NUM_MESSAGES} msgs)..." local output - output=$(python3 "$TESTS_DIR/client_websocket.py" "$HOST" "$WEBSOCKET_PORT" "$NUM_CLIENTS" "$NUM_MESSAGES" 2>&1) || true + output=$(cd "$BUILD_DIR" && python3 client_websocket.py "$HOST" "$WEBSOCKET_PORT" "$NUM_CLIENTS" "$NUM_MESSAGES" 2>&1) || true echo "$output" local sent_count recv_count @@ -204,6 +210,7 @@ if [ ! -x "$SERVER_BIN" ]; then exit 1 fi +copy_tests_to_build start_server wait_for_server_ready sleep 1 From 99c0d9ae5264e226f368c81b76e1cdea7aad9b94 Mon Sep 17 00:00:00 2001 From: fir <29286243+usermicrodevices@users.noreply.github.com> Date: Fri, 5 Jun 2026 01:01:38 +0300 Subject: [PATCH 15/15] fixed IPC bugs --- include/process/ProcessPool.hpp | 1 + src/network/BinarySession.cpp | 40 +++++++++++++++++-------- src/network/ConnectionManager.cpp | 49 ++++++++++++++++--------------- src/network/MasterRouter.cpp | 1 - src/process/ProcessPool.cpp | 23 +++++++++++---- 5 files changed, 71 insertions(+), 43 deletions(-) diff --git a/include/process/ProcessPool.hpp b/include/process/ProcessPool.hpp index c801183..4d02ef5 100644 --- a/include/process/ProcessPool.hpp +++ b/include/process/ProcessPool.hpp @@ -52,6 +52,7 @@ class ProcessWorker : public std::enable_shared_from_this { WorkerType GetType() const; std::shared_ptr GetChannel() { return channel_; } + void SetChannel(std::shared_ptr ch) { channel_ = std::move(ch); } int GetMasterFd() const { return masterFd_; } using MasterMessageHandler = std::functionStop(); } }); - std::vector body(header->length); + auto body = std::make_shared>(header->length); asio::async_read(self->GetSocket(), - asio::buffer(body), + asio::buffer(*body), [self, header, body, deadline](std::error_code ec, std::size_t length) mutable { deadline->cancel(); if (ec) { @@ -228,7 +240,7 @@ void BinarySession::DoBinaryRead() { return; } Logger::Debug("BinarySession::DoBinaryRead asio::async_read length = {}", length); - uint32_t calculated = BinaryProtocol::CalculateCRC32(body.data(), body.size()); + uint32_t calculated = BinaryProtocol::CalculateCRC32(body->data(), body->size()); if (calculated != header->checksum) { Logger::Error("Session {}: checksum mismatch", self->sessionId_); self->SendError(BinaryProtocol::MESSAGE_TYPE_ERROR, @@ -236,10 +248,10 @@ void BinarySession::DoBinaryRead() { self->DoBinaryRead(); return; } - std::vector processed_body = body; + std::vector processed_body = *body; if (header->flags & BinaryProtocol::FLAG_COMPRESSED) { try { - processed_body = BinaryProtocol::DecompressData(body); + processed_body = BinaryProtocol::DecompressData(*body); } catch (const std::exception& e) { Logger::Error("Session {}: decompression failed: {}", self->sessionId_, e.what()); @@ -343,9 +355,13 @@ void BinarySession::Send(uint16_t message_type, const std::vector& data message.data.data(), message.data.size()); auto serialized = message.Serialize(); network_monitor_.RecordPacketSent(message.header.sequence, serialized.size()); - std::lock_guard lock(write_mutex_); - write_queue_.push(serialized); - if (write_queue_.size() == 1) { + bool should_write = false; + { + std::lock_guard lock(write_mutex_); + write_queue_.push(serialized); + should_write = (write_queue_.size() == 1); + } + if (should_write) { DoBinaryWrite(); } } diff --git a/src/network/ConnectionManager.cpp b/src/network/ConnectionManager.cpp index d99f50a..fff1a16 100644 --- a/src/network/ConnectionManager.cpp +++ b/src/network/ConnectionManager.cpp @@ -260,7 +260,12 @@ void ConnectionManager::OnMasterReply(uint32_t correlationId, const std::vector< } if (!session) return; if (session->GetProtocolMode() == ProtocolMode::Binary) { - session->SendRaw(std::string(reply.begin(), reply.end())); + if (reply.size() >= 2) { + BinaryProtocol::BinaryReader r(reply.data(), reply.size()); + uint16_t type = r.ReadUInt16(); + std::vector body(reply.begin() + 2, reply.end()); + session->Send(type, body); + } if (entry.messageType == BinaryProtocol::MESSAGE_TYPE_AUTHENTICATION && reply.size() >= 16) { BinaryProtocol::BinaryReader r(reply.data(), reply.size()); r.ReadUInt64(); @@ -436,20 +441,27 @@ void ConnectionManager::OnMasterPush(uint64_t sessionId, const std::vector& session, const std::vector& payload) { + if (session->GetProtocolMode() == ProtocolMode::Binary) { + if (payload.size() >= 2) { + BinaryProtocol::BinaryReader r(payload.data(), payload.size()); + uint16_t type = r.ReadUInt16(); + std::vector body(payload.begin() + 2, payload.end()); + session->Send(type, body); + } + } else { + if (payload.size() >= 2) { + BinaryProtocol::BinaryReader r(payload.data(), payload.size()); + uint16_t type = r.ReadUInt16(); + std::vector body(payload.begin() + 2, payload.end()); + session->SendJson(binaryToJson(type, body)); + } + } + }; if (sessionId == 0) {// Broadcast to all sessions on this worker std::shared_lock lock(sessionsMutex_); for (auto& [id, session] : sessions_) { - if (session->GetProtocolMode() == ProtocolMode::Binary) { - session->SendRaw(std::string(data.begin(), data.end())); - } else { - if (data.size() >= 2) { - BinaryProtocol::BinaryReader r(data.data(), data.size()); - uint16_t type = r.ReadUInt16(); - Logger::Trace("Parsed binary message type: {}", type); - std::vector body(data.begin() + 2, data.end()); - session->SendJson(binaryToJson(type, body)); - } - } + sendToSession(session, data); } } else { std::shared_ptr session; @@ -462,18 +474,7 @@ void ConnectionManager::OnMasterPush(uint64_t sessionId, const std::vectorGetProtocolMode() == ProtocolMode::Binary) { - session->SendRaw(std::string(data.begin(), data.end())); - } else { - if (data.size() >= 2) { - BinaryProtocol::BinaryReader r(data.data(), data.size()); - uint16_t type = r.ReadUInt16(); - std::vector body(data.begin() + 2, data.end()); - session->SendJson(binaryToJson(type, body)); - } - } - } + sendToSession(session, data); Logger::Trace("ConnectionManager::OnMasterPush: Sending to session {}", sessionId); } } diff --git a/src/network/MasterRouter.cpp b/src/network/MasterRouter.cpp index de11e11..31e1b14 100644 --- a/src/network/MasterRouter.cpp +++ b/src/network/MasterRouter.cpp @@ -58,7 +58,6 @@ void MasterRouter::RouteToGameLogic(uint64_t sessionId, uint16_t msgType, const w.WriteString(message); w.WriteUInt64(timestamp); std::vector response = w.GetBuffer(); - pool_.PushToWorker(static_cast(sessionId >> 48), sessionId, response); pool_.BroadcastToAllWorkers(response); break; } diff --git a/src/process/ProcessPool.cpp b/src/process/ProcessPool.cpp index e47f6a0..698db80 100644 --- a/src/process/ProcessPool.cpp +++ b/src/process/ProcessPool.cpp @@ -24,8 +24,6 @@ void ProcessWorker::Start() { socket1.close(); masterFd_ = socket0.native_handle(); socket0.release(); - channel_ = std::make_shared(io_, masterFd_); - io_.notify_fork(asio::execution_context::fork_event::fork_parent); } else { socket0.close(); socket1.close(); @@ -107,9 +105,6 @@ void ProcessPool::doSpawnWorkers() { try { worker->Start(); workers_.push_back(worker); - worker->GetChannel()->Start([this, globalId](const IPCEnvelope& env) { - onWorkerMessage(globalId, env); - }); } catch (const std::exception& err) { if (std::string(err.what()) == "worker_function") { int fd = worker->GetMasterFd(); @@ -122,6 +117,18 @@ void ProcessPool::doSpawnWorkers() { } } } + io_.notify_fork(asio::execution_context::fork_event::fork_parent); + for (auto& w : workers_) { + if (w->GetType() == WorkerType::Child) { + int fd = w->GetMasterFd(); + auto channel = std::make_shared(io_, fd); + int wid = w->GetId(); + channel->Start([this, wid](const IPCEnvelope& env) { + onWorkerMessage(wid, env); + }); + w->SetChannel(channel); + } + } } int ProcessPool::SpawnGameLogicWorker() { @@ -137,9 +144,13 @@ int ProcessPool::SpawnGameLogicWorker() { worker->Start(); workers_.push_back(worker); gameLogicWorkerId_ = globalId; - worker->GetChannel()->Start([this](const IPCEnvelope& env) { + io_.notify_fork(asio::execution_context::fork_event::fork_parent); + int fd = worker->GetMasterFd(); + auto channel = std::make_shared(io_, fd); + channel->Start([this](const IPCEnvelope& env) { onGameLogicMessage(env); }); + worker->SetChannel(channel); return globalId; } catch (const std::exception& err) { if (std::string(err.what()) == "worker_function") {