From cacc18dc7e97f20f9d2d5086f631062bdd83a8c6 Mon Sep 17 00:00:00 2001 From: Vlad Date: Mon, 19 Jan 2026 22:04:33 +0600 Subject: [PATCH] added boxes from server --- server/server.cpp | 344 ++++++++++++++++++++++---------- src/Game.cpp | 21 ++ src/Game.h | 1 + src/network/ClientState.h | 2 +- src/network/LocalClient.h | 4 + src/network/NetworkInterface.h | 2 + src/network/WebSocketClient.cpp | 36 ++++ src/network/WebSocketClient.h | 9 + 8 files changed, 308 insertions(+), 111 deletions(-) diff --git a/server/server.cpp b/server/server.cpp index 1853f88..96350f3 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -11,16 +11,18 @@ #define _USE_MATH_DEFINES #include #include "../src/network/ClientState.h" +#include +#include // Вспомогательный split std::vector split(const std::string& s, char delimiter) { - std::vector tokens; - std::string token; - std::istringstream tokenStream(s); - while (std::getline(tokenStream, token, delimiter)) { - tokens.push_back(token); - } - return tokens; + std::vector tokens; + std::string token; + std::istringstream tokenStream(s); + while (std::getline(tokenStream, token, delimiter)) { + tokens.push_back(token); + } + return tokens; } namespace beast = boost::beast; @@ -31,152 +33,274 @@ using tcp = net::ip::tcp; class Session; +struct ServerBox { + Eigen::Vector3f position; + Eigen::Matrix3f rotation; + float collisionRadius = 2.0f; +}; + +std::vector g_serverBoxes; +std::mutex g_boxes_mutex; + + std::vector> g_sessions; std::mutex g_sessions_mutex; class Session : public std::enable_shared_from_this { - websocket::stream ws_; - beast::flat_buffer buffer_; - int id_; - + websocket::stream ws_; + beast::flat_buffer buffer_; + int id_; + bool is_writing_ = false; + ClientStateInterval timedClientStates; - void process_message(const std::string& msg) { - auto now_server = std::chrono::system_clock::now(); + void process_message(const std::string& msg) { + auto now_server = std::chrono::system_clock::now(); - auto parts = split(msg, ':'); + auto parts = split(msg, ':'); - if (parts.size() < 16) - { - throw std::runtime_error("Unknown message type received, too small"); - } + if (parts.size() < 16) + { + throw std::runtime_error("Unknown message type received, too small"); + } - uint64_t clientTimestamp = std::stoull(parts[1]); + uint64_t clientTimestamp = std::stoull(parts[1]); - ClientState receivedState; + ClientState receivedState; - receivedState.id = id_; + receivedState.id = id_; - std::chrono::system_clock::time_point uptime_timepoint{ std::chrono::duration_cast(std::chrono::milliseconds(clientTimestamp)) }; - receivedState.lastUpdateServerTime = uptime_timepoint; + std::chrono::system_clock::time_point uptime_timepoint{ std::chrono::duration_cast(std::chrono::milliseconds(clientTimestamp)) }; + receivedState.lastUpdateServerTime = uptime_timepoint; - if (parts[0] == "UPD") { - receivedState.handle_full_sync(parts, 2); - retranslateMessage(msg); - } - else - { + if (parts[0] == "UPD") { + receivedState.handle_full_sync(parts, 2); + retranslateMessage(msg); + } + else + { throw std::runtime_error("Unknown message type received: " + parts[0]); - } + } timedClientStates.add_state(receivedState); - } + } - void retranslateMessage(const std::string& msg) - { - std::string event_msg = "EVENT:" + std::to_string(id_) + ":" + msg; + void retranslateMessage(const std::string& msg) + { + std::string event_msg = "EVENT:" + std::to_string(id_) + ":" + msg; + + std::lock_guard lock(g_sessions_mutex); + for (auto& session : g_sessions) { + if (session->get_id() != id_) { // Не шлем отправителю + session->send_message(event_msg); + } + } + } + + void sendBoxesToClient() { + std::lock_guard lock(g_boxes_mutex); + + std::string boxMsg = "BOXES:"; + for (const auto& box : g_serverBoxes) { + Eigen::Quaternionf q(box.rotation); + boxMsg += std::to_string(box.position.x()) + ":" + + std::to_string(box.position.y()) + ":" + + std::to_string(box.position.z()) + ":" + + std::to_string(q.w()) + ":" + + std::to_string(q.x()) + ":" + + std::to_string(q.y()) + ":" + + std::to_string(q.z()) + "|"; + } + + if (!boxMsg.empty() && boxMsg.back() == '|') { + boxMsg.pop_back(); + } + + send_message(boxMsg); + } + + void send_message(std::string msg) { + auto ss = std::make_shared(std::move(msg)); + + if (is_writing_) { + + ws_.async_write(net::buffer(*ss), + [self = shared_from_this(), ss](beast::error_code ec, std::size_t) { + if (ec) { + std::cerr << "Write error: " << ec.message() << std::endl; + } + }); + } + else { + is_writing_ = true; + ws_.async_write(net::buffer(*ss), + [self = shared_from_this(), ss](beast::error_code ec, std::size_t) { + self->is_writing_ = false; + if (ec) { + std::cerr << "Write error: " << ec.message() << std::endl; + } + }); + } + } - std::lock_guard lock(g_sessions_mutex); - for (auto& session : g_sessions) { - if (session->get_id() != id_) { // Не шлем отправителю - session->send_message(event_msg); - } - } - } - public: - explicit Session(tcp::socket&& socket, int id) - : ws_(std::move(socket)), id_(id) { - } + explicit Session(tcp::socket&& socket, int id) + : ws_(std::move(socket)), id_(id) { + } - void init() - { - } + void init() + { + sendBoxesToClient(); - void run() { + auto timer = std::make_shared(ws_.get_executor()); + timer->expires_after(std::chrono::milliseconds(100)); + timer->async_wait([self = shared_from_this(), timer](const boost::system::error_code& ec) { + if (!ec) { + self->send_message("ID:" + std::to_string(self->id_)); + self->do_read(); + } + }); + } - { - std::lock_guard lock(g_sessions_mutex); + void run() { + + { + std::lock_guard lock(g_sessions_mutex); g_sessions.push_back(shared_from_this()); - } + } - ws_.async_accept([self = shared_from_this()](beast::error_code ec) { - if (ec) return; - std::cout << "Client " << self->id_ << " connected\n"; - self->init(); - self->send_message("ID:" + std::to_string(self->id_)); - self->do_read(); - }); - } + ws_.async_accept([self = shared_from_this()](beast::error_code ec) { + if (ec) return; + std::cout << "Client " << self->id_ << " connected\n"; + self->init(); + // self->send_message("ID:" + std::to_string(self->id_)); + // self->do_read(); + }); + } - void send_message(std::string msg) { - auto ss = std::make_shared(std::move(msg)); - ws_.async_write(net::buffer(*ss), [ss](beast::error_code, std::size_t) {}); - } + /*void send_message(std::string msg) { + auto ss = std::make_shared(std::move(msg)); + ws_.async_write(net::buffer(*ss), [ss](beast::error_code, std::size_t) {}); + }*/ - int get_id() const { - return id_; + int get_id() const { + return id_; } private: - void do_read() { - ws_.async_read(buffer_, [self = shared_from_this()](beast::error_code ec, std::size_t) { - if (ec) - { - std::lock_guard lock(g_sessions_mutex); - g_sessions.erase(std::remove_if(g_sessions.begin(), g_sessions.end(), - [self](const std::shared_ptr& session) { - return session.get() == self.get(); + void do_read() { + ws_.async_read(buffer_, [self = shared_from_this()](beast::error_code ec, std::size_t) { + if (ec) + { + std::lock_guard lock(g_sessions_mutex); + g_sessions.erase(std::remove_if(g_sessions.begin(), g_sessions.end(), + [self](const std::shared_ptr& session) { + return session.get() == self.get(); }), g_sessions.end()); - return; - } + return; + } - std::string msg = beast::buffers_to_string(self->buffer_.data()); - self->process_message(msg); + std::string msg = beast::buffers_to_string(self->buffer_.data()); + self->process_message(msg); - self->buffer_.consume(self->buffer_.size()); - self->do_read(); - }); - } + self->buffer_.consume(self->buffer_.size()); + self->do_read(); + }); + } }; void update_world(net::steady_timer& timer, net::io_context& ioc) { - // TODO: Renew game state + // TODO: Renew game state - timer.expires_after(std::chrono::milliseconds(50)); - timer.async_wait([&](const boost::system::error_code& ec) { - if (!ec) update_world(timer, ioc); - }); + timer.expires_after(std::chrono::milliseconds(50)); + timer.async_wait([&](const boost::system::error_code& ec) { + if (!ec) update_world(timer, ioc); + }); } +std::vector generateServerBoxes(int count) { + std::vector boxes; + std::random_device rd; + std::mt19937 gen(rd()); + + const float MIN_COORD = -100.0f; + const float MAX_COORD = 100.0f; + const float MIN_DISTANCE = 3.0f; + const float MIN_DISTANCE_SQUARED = MIN_DISTANCE * MIN_DISTANCE; + const int MAX_ATTEMPTS = 1000; + + std::uniform_real_distribution<> posDistrib(MIN_COORD, MAX_COORD); + std::uniform_real_distribution<> angleDistrib(0.0, M_PI * 2.0); + + for (int i = 0; i < count; i++) { + bool accepted = false; + int attempts = 0; + + while (!accepted && attempts < MAX_ATTEMPTS) { + ServerBox box; + box.position = Eigen::Vector3f( + (float)posDistrib(gen), + (float)posDistrib(gen), + (float)posDistrib(gen) + ); + + accepted = true; + for (const auto& existingBox : boxes) { + Eigen::Vector3f diff = box.position - existingBox.position; + if (diff.squaredNorm() < MIN_DISTANCE_SQUARED) { + accepted = false; + break; + } + } + + if (accepted) { + float randomAngle = (float)angleDistrib(gen); + Eigen::Vector3f axis = Eigen::Vector3f::Random().normalized(); + box.rotation = Eigen::AngleAxisf(randomAngle, axis).toRotationMatrix(); + boxes.push_back(box); + } + + attempts++; + } + } + + return boxes; +} + + int main() { - try { - net::io_context ioc; - tcp::acceptor acceptor{ ioc, {tcp::v4(), 8080} }; - int next_id = 1000; + try { + { + std::lock_guard lock(g_boxes_mutex); + g_serverBoxes = generateServerBoxes(50); + std::cout << "Generated " << g_serverBoxes.size() << " boxes on server\n"; + } + net::io_context ioc; + tcp::acceptor acceptor{ ioc, {tcp::v4(), 8080} }; + int next_id = 1000; - std::cout << "Server started on port 8080...\n"; + std::cout << "Server started on port 8080...\n"; - auto do_accept = [&](auto& self_fn) -> void { - acceptor.async_accept([&, self_fn](beast::error_code ec, tcp::socket socket) { - if (!ec) { - std::make_shared(std::move(socket), next_id++)->run(); - } - self_fn(self_fn); - }); - }; + auto do_accept = [&](auto& self_fn) -> void { + acceptor.async_accept([&, self_fn](beast::error_code ec, tcp::socket socket) { + if (!ec) { + std::make_shared(std::move(socket), next_id++)->run(); + } + self_fn(self_fn); + }); + }; - - net::steady_timer timer(ioc); - update_world(timer, ioc); - do_accept(do_accept); - ioc.run(); - } - catch (std::exception const& e) { - std::cerr << "Error: " << e.what() << std::endl; - } - return 0; + net::steady_timer timer(ioc); + update_world(timer, ioc); + + do_accept(do_accept); + ioc.run(); + } + catch (std::exception const& e) { + std::cerr << "Error: " << e.what() << std::endl; + } + return 0; } \ No newline at end of file diff --git a/src/Game.cpp b/src/Game.cpp index 7afcadb..b0b7cb2 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -577,6 +577,27 @@ namespace ZL latestRemotePlayers = networkClient->getRemotePlayers(); + // Если сервер прислал коробки, применяем их однократно вместо локальной генерации + if (!serverBoxesApplied && networkClient) { + auto sboxes = networkClient->getServerBoxes(); + if (!sboxes.empty()) { + boxCoordsArr.clear(); + for (auto &b : sboxes) { + BoxCoords bc; + bc.pos = b.first; + bc.m = b.second; + boxCoordsArr.push_back(bc); + } + boxRenderArr.resize(boxCoordsArr.size()); + for (int i = 0; i < (int)boxCoordsArr.size(); ++i) { + boxRenderArr[i].AssignFrom(boxBase); + boxRenderArr[i].RefreshVBO(); + } + boxAlive.assign(boxCoordsArr.size(), true); + serverBoxesApplied = true; + } + } + // Итерируемся по актуальным данным из extrapolateRemotePlayers for (auto const& [id, remotePlayer] : latestRemotePlayers) { diff --git a/src/Game.h b/src/Game.h index 597c223..8c91b4b 100644 --- a/src/Game.h +++ b/src/Game.h @@ -104,6 +104,7 @@ namespace ZL { uint64_t lastExplosionTime = 0; const uint64_t explosionDurationMs = 500; + bool serverBoxesApplied = false; }; diff --git a/src/network/ClientState.h b/src/network/ClientState.h index ac28def..ca2eca2 100644 --- a/src/network/ClientState.h +++ b/src/network/ClientState.h @@ -14,7 +14,7 @@ constexpr float SHIP_ACCEL = 1.0f * 1000.0f; constexpr float ROTATION_SENSITIVITY = 0.002f; constexpr long long SERVER_DELAY = 0; //ms -constexpr long long CLIENT_DELAY = 200; //ms +constexpr long long CLIENT_DELAY = 1000; //ms constexpr long long CUTOFF_TIME = 5000; //ms struct ClientState { diff --git a/src/network/LocalClient.h b/src/network/LocalClient.h index 55994b3..8cd1cee 100644 --- a/src/network/LocalClient.h +++ b/src/network/LocalClient.h @@ -21,5 +21,9 @@ namespace ZL { std::unordered_map getRemotePlayers() override { return std::unordered_map(); } + + std::vector> getServerBoxes() override { + return {}; + } }; } \ No newline at end of file diff --git a/src/network/NetworkInterface.h b/src/network/NetworkInterface.h index 4873f07..d2c513d 100644 --- a/src/network/NetworkInterface.h +++ b/src/network/NetworkInterface.h @@ -15,5 +15,7 @@ namespace ZL { virtual bool IsConnected() const = 0; virtual void Poll() = 0; // ƒл¤ обработки вход¤щих пакетов virtual std::unordered_map getRemotePlayers() = 0; + + virtual std::vector> getServerBoxes() = 0; }; } diff --git a/src/network/WebSocketClient.cpp b/src/network/WebSocketClient.cpp index ce9dd3e..c0aa195 100644 --- a/src/network/WebSocketClient.cpp +++ b/src/network/WebSocketClient.cpp @@ -83,6 +83,42 @@ namespace ZL { std::string msg = messageQueue.front(); messageQueue.pop(); + // Обработка списка коробок от сервера + if (msg.rfind("BOXES:", 0) == 0) { + std::string payload = msg.substr(6); // после "BOXES:" + std::vector> parsedBoxes; + if (!payload.empty()) { + auto items = split(payload, '|'); + for (auto& item : items) { + if (item.empty()) continue; + auto parts = split(item, ':'); + if (parts.size() < 7) continue; + try { + float px = std::stof(parts[0]); + float py = std::stof(parts[1]); + float pz = std::stof(parts[2]); + Eigen::Quaternionf q( + std::stof(parts[3]), + std::stof(parts[4]), + std::stof(parts[5]), + std::stof(parts[6]) + ); + Eigen::Matrix3f rot = q.toRotationMatrix(); + parsedBoxes.emplace_back(Eigen::Vector3f{ px, py, pz }, rot); + } + catch (...) { + // пропускаем некорректную запись + continue; + } + } + } + { + std::lock_guard bLock(boxesMutex); + serverBoxes_ = std::move(parsedBoxes); + } + continue; + } + if (msg.rfind("EVENT:", 0) == 0) { auto parts = split(msg, ':'); if (parts.size() < 5) continue; // EVENT:ID:TYPE:TIME:DATA... diff --git a/src/network/WebSocketClient.h b/src/network/WebSocketClient.h index d8b108f..0692d67 100644 --- a/src/network/WebSocketClient.h +++ b/src/network/WebSocketClient.h @@ -34,6 +34,10 @@ namespace ZL { std::unordered_map remotePlayers; std::mutex playersMutex; + // Серверные коробки + std::vector> serverBoxes_; + std::mutex boxesMutex; + void startAsyncRead(); void processIncomingMessage(const std::string& msg); @@ -54,6 +58,11 @@ namespace ZL { std::lock_guard lock(playersMutex); return remotePlayers; } + + std::vector> getServerBoxes() override { + std::lock_guard lock(boxesMutex); + return serverBoxes_; + } }; } #endif