From 13af968a04c0423f8f8918c2182c82286c44e5b3 Mon Sep 17 00:00:00 2001 From: Vlad Date: Wed, 14 Jan 2026 16:06:32 +0600 Subject: [PATCH 01/18] added collision for stones --- src/Game.cpp | 101 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/src/Game.cpp b/src/Game.cpp index 56bd7d2..bc92e89 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -713,6 +713,10 @@ namespace ZL this->explosionEmitter.setEmissionPoints(std::vector()); Environment::shipPosition = Vector3f{ 0, 0, 45000.f }; Environment::shipVelocity = 0.0f; + Environment::shipMatrix = Eigen::Matrix3f::Identity(); + Environment::inverseShipMatrix = Eigen::Matrix3f::Identity(); + Environment::zoom = 18.f; + Environment::tapDownHold = false; uiManager.popMenu(); std::cerr << "Game restarted\n"; }); @@ -728,6 +732,103 @@ namespace ZL } } } + else { + bool stoneCollided = false; + int collidedTriIdx = -1; + Vector3f collidedStonePos = Vector3f{ 0.0f, 0.0f, 0.0f }; + + for (int triIdx : planetObject.triangleIndicesToDraw) { + if (triIdx < 0 || triIdx >= static_cast(planetObject.planetStones.allInstances.size())) + continue; + + if (planetObject.planetStones.statuses.size() <= static_cast(triIdx)) + continue; + + if (planetObject.planetStones.statuses[triIdx] != ChunkStatus::Live) + continue; + + const auto& instances = planetObject.planetStones.allInstances[triIdx]; + for (const auto& inst : instances) { + + Vector3f stoneWorld = inst.position; + Vector3f diff = Environment::shipPosition - stoneWorld; + + float maxScale = (std::max)({ inst.scale(0), inst.scale(1), inst.scale(2) }); + float stoneRadius = StoneParams::BASE_SCALE * maxScale * 0.9f; + float thresh = shipCollisionRadius + stoneRadius; + + if (diff.squaredNorm() <= thresh * thresh) { + stoneCollided = true; + collidedTriIdx = triIdx; + collidedStonePos = stoneWorld; + break; + } + } + + if (stoneCollided) break; + } + + if (stoneCollided) { + Vector3f localForward = { 0,0,-1 }; + Vector3f worldForward = (Environment::shipMatrix * localForward).normalized(); + const float backDistance = 400.0f; + Environment::shipPosition = Environment::shipPosition - worldForward * backDistance; + + shipAlive = false; + gameOver = true; + Environment::shipVelocity = 0.0f; + showExplosion = true; + + explosionEmitter.setUseWorldSpace(true); + explosionEmitter.setEmissionPoints(std::vector{ collidedStonePos }); + explosionEmitter.emit(); + + std::cerr << "GAME OVER: collision with stone on triangle " << collidedTriIdx << std::endl; + + if (collidedTriIdx >= 0 && collidedTriIdx < static_cast(planetObject.stonesToRender.size())) { + planetObject.stonesToRender[collidedTriIdx].data.PositionData.clear(); + planetObject.stonesToRender[collidedTriIdx].vao.reset(); + planetObject.stonesToRender[collidedTriIdx].positionVBO.reset(); + planetObject.stonesToRender[collidedTriIdx].normalVBO.reset(); + planetObject.stonesToRender[collidedTriIdx].tangentVBO.reset(); + planetObject.stonesToRender[collidedTriIdx].binormalVBO.reset(); + planetObject.stonesToRender[collidedTriIdx].colorVBO.reset(); + planetObject.stonesToRender[collidedTriIdx].texCoordVBO.reset(); + } + if (collidedTriIdx >= 0 && collidedTriIdx < static_cast(planetObject.planetStones.statuses.size())) { + planetObject.planetStones.statuses[collidedTriIdx] = ChunkStatus::Empty; + } + + if (!uiGameOverShown) { + if (uiManager.pushMenuFromFile("resources/config/game_over.json", this->renderer, CONST_ZIP_FILE)) { + uiManager.setButtonCallback("restartButton", [this](const std::string& name) { + this->shipAlive = true; + this->gameOver = false; + this->uiGameOverShown = false; + this->showExplosion = false; + this->explosionEmitter.setEmissionPoints(std::vector()); + Environment::shipPosition = Vector3f{ 0, 0, 45000.f }; + Environment::shipVelocity = 0.0f; + Environment::shipMatrix = Eigen::Matrix3f::Identity(); + Environment::inverseShipMatrix = Eigen::Matrix3f::Identity(); + Environment::zoom = 18.f; + Environment::tapDownHold = false; + uiManager.popMenu(); + std::cerr << "Game restarted\n"; + }); + + uiManager.setButtonCallback("gameOverExitButton", [this](const std::string& name) { + Environment::exitGameLoop = true; + }); + + uiGameOverShown = true; + } + else { + std::cerr << "Failed to load game_over.json\n"; + } + } + } + } } for (int i = 0; i < boxCoordsArr.size(); ++i) { From bdce619e68da259279e0316a5d5989edc1a656c3 Mon Sep 17 00:00:00 2001 From: Vlad Date: Wed, 14 Jan 2026 17:06:53 +0600 Subject: [PATCH 02/18] fix --- src/Game.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Game.cpp b/src/Game.cpp index bc92e89..6cb06ba 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -771,7 +771,7 @@ namespace ZL if (stoneCollided) { Vector3f localForward = { 0,0,-1 }; Vector3f worldForward = (Environment::shipMatrix * localForward).normalized(); - const float backDistance = 400.0f; + const float backDistance = 50.0f; Environment::shipPosition = Environment::shipPosition - worldForward * backDistance; shipAlive = false; From 483757f8ca338e3cfddd6350f71ad7693bb27200 Mon Sep 17 00:00:00 2001 From: Vladislav Khorev Date: Wed, 14 Jan 2026 23:17:52 +0300 Subject: [PATCH 03/18] first version --- proj-windows/CMakeLists.txt | 4 ++ server/CMakeLists.txt | 44 ++++++++++++++++++ server/main.cpp | 81 +++++++++++++++++++++++++++++++++ src/network/NetworkInterface.h | 14 ++++++ src/network/WebSocketClient.cpp | 0 src/network/WebSocketClient.h | 29 ++++++++++++ 6 files changed, 172 insertions(+) create mode 100644 server/CMakeLists.txt create mode 100644 server/main.cpp create mode 100644 src/network/NetworkInterface.h create mode 100644 src/network/WebSocketClient.cpp create mode 100644 src/network/WebSocketClient.h diff --git a/proj-windows/CMakeLists.txt b/proj-windows/CMakeLists.txt index 0f68441..942ab6e 100644 --- a/proj-windows/CMakeLists.txt +++ b/proj-windows/CMakeLists.txt @@ -50,6 +50,9 @@ add_executable(space-game001 ../src/UiManager.h ../src/Projectile.h ../src/Projectile.cpp + ../src/network/NetworkInterface.h + ../src/network/WebSocketClient.h + ../src/network/WebSocketClient.cpp ) # Установка проекта по умолчанию для Visual Studio @@ -71,6 +74,7 @@ target_compile_definitions(space-game001 PRIVATE WIN32_LEAN_AND_MEAN PNG_ENABLED SDL_MAIN_HANDLED + NETWORK # SIMPLIFIED ) diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt new file mode 100644 index 0000000..b119422 --- /dev/null +++ b/server/CMakeLists.txt @@ -0,0 +1,44 @@ +cmake_minimum_required(VERSION 3.15) +project(SpaceGameServer) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Подключаем зависимости нашего движка +include(${CMAKE_CURRENT_SOURCE_DIR}/../cmake/ThirdParty.cmake) + +# Настройка флагов для тяжелых шаблонов Boost +if (MSVC) + add_compile_options(/bigobj) +endif() + +# Добавляем скомпилированные компоненты Boost через относительные пути +# CMake сам создаст цели boost_system и др. +add_subdirectory("${BOOST_SRC_DIR}/libs/system" boost-system-build EXCLUDE_FROM_ALL) +add_subdirectory("${BOOST_SRC_DIR}/libs/assert" boost-assert-build EXCLUDE_FROM_ALL) +add_subdirectory("${BOOST_SRC_DIR}/libs/config" boost-config-build EXCLUDE_FROM_ALL) +add_subdirectory("${BOOST_SRC_DIR}/libs/throw_exception" boost-throw_exception-build EXCLUDE_FROM_ALL) +add_subdirectory("${BOOST_SRC_DIR}/libs/variant2" boost-variant2-build EXCLUDE_FROM_ALL) +add_subdirectory("${BOOST_SRC_DIR}/libs/mp11" boost-mp11-build EXCLUDE_FROM_ALL) +add_subdirectory("${BOOST_SRC_DIR}/libs/winapi" boost-winapi-build EXCLUDE_FROM_ALL) +add_subdirectory("${BOOST_SRC_DIR}/libs/predef" boost-predef-build EXCLUDE_FROM_ALL) +# EXCLUDE_FROM_ALL гарантирует, что мы собираем только то, что линкуем + +# Исполняемый файл сервера +add_executable(Server main.cpp) + +target_include_directories(Server PRIVATE ${BOOST_SRC_DIR}) + +# Линковка +target_link_libraries(Server + PRIVATE + boost_system # Скомпилированная часть для error_code + eigen_external_lib # Если планируешь использовать математику на сервере +) + +if(WIN32) + target_link_libraries(Server PRIVATE ws2_32 mswsock) +endif() + +# Дополнительный макрос, чтобы Asio знал, что мы работаем без устаревших функций +target_compile_definitions(Server PRIVATE BOOST_ASIO_NO_DEPRECATED) \ No newline at end of file diff --git a/server/main.cpp b/server/main.cpp new file mode 100644 index 0000000..c3a3195 --- /dev/null +++ b/server/main.cpp @@ -0,0 +1,81 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast = boost::beast; +namespace http = beast::http; +namespace websocket = beast::websocket; +namespace net = boost::asio; +using tcp = net::ip::tcp; + +class Session : public std::enable_shared_from_this { + websocket::stream ws_; + beast::flat_buffer buffer_; + int id_; + +public: + explicit Session(tcp::socket&& socket, int id) + : ws_(std::move(socket)), id_(id) { + } + + void run() { + ws_.async_accept([self = shared_from_this()](beast::error_code ec) { + if (ec) return; + std::cout << "Client " << self->id_ << " connected\n"; + // ID + self->send_message("ID:" + std::to_string(self->id_)); + self->do_read(); + }); + } + +private: + void do_read() { + ws_.async_read(buffer_, [self = shared_from_this()](beast::error_code ec, std::size_t) { + if (ec) return; + std::string msg = beast::buffers_to_string(self->buffer_.data()); + + if (msg == "PING") { + self->send_message("PONG"); + } + + self->buffer_.consume(self->buffer_.size()); + 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) {}); + } +}; + +int main() { + try { + net::io_context ioc; + tcp::acceptor acceptor{ ioc, {tcp::v4(), 8080} }; + int next_id = 1000; + + 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); + }); + }; + + 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/network/NetworkInterface.h b/src/network/NetworkInterface.h new file mode 100644 index 0000000..dca15c0 --- /dev/null +++ b/src/network/NetworkInterface.h @@ -0,0 +1,14 @@ +#pragma once +#include + +// NetworkInterface.h - +namespace ZL { + class INetworkClient { + public: + virtual ~INetworkClient() = default; + virtual void Connect(const std::string& host, uint16_t port) = 0; + virtual void Send(const std::string& message) = 0; + virtual bool IsConnected() const = 0; + virtual void Poll() = 0; // + }; +} diff --git a/src/network/WebSocketClient.cpp b/src/network/WebSocketClient.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/network/WebSocketClient.h b/src/network/WebSocketClient.h new file mode 100644 index 0000000..7add3fe --- /dev/null +++ b/src/network/WebSocketClient.h @@ -0,0 +1,29 @@ +#pragma once + +// WebSocketClient.h +#include "NetworkInterface.h" +#include + +namespace ZL { + class WebSocketClient : public INetworkClient { + private: + std::queue messageQueue; + bool connected = false; + int clientId = -1; + + public: + void Connect(const std::string& host, uint16_t port) override { + // Emscripten emscripten_websocket_new + // Desktop - boost::beast::websocket::stream + } + + void Poll() override { + // . + // ID: clientId = parseId(msg); connected = true; + // "PONG": . + } + + bool IsConnected() const override { return connected; } + int GetClientId() const { return clientId; } + }; +} \ No newline at end of file From 52a52ef9600567f8f01112e15e9d5d88d09a8165 Mon Sep 17 00:00:00 2001 From: Vlad Date: Thu, 15 Jan 2026 15:49:32 +0600 Subject: [PATCH 04/18] added projectile collision and some fixed --- resources/config/explosion_config.json | 2 +- src/Game.cpp | 69 ++++++++++++++++++++++---- src/Game.h | 6 ++- src/Projectile.h | 2 +- 4 files changed, 64 insertions(+), 15 deletions(-) diff --git a/resources/config/explosion_config.json b/resources/config/explosion_config.json index 2e461e8..fc0a2bc 100644 --- a/resources/config/explosion_config.json +++ b/resources/config/explosion_config.json @@ -9,7 +9,7 @@ "lifeTimeRange": [200.0, 800.0], "emissionRate": 50.0, "maxParticles": 5, - "particleSize": 0.5, + "particleSize": 2, "biasX": 0.1, "shaderProgramName": "default" } \ No newline at end of file diff --git a/src/Game.cpp b/src/Game.cpp index 8d2cca6..46af558 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -1,6 +1,7 @@ #include "Game.h" #include "AnimatedModel.h" #include "BoneAnimatedModel.h" +#include "planet/PlanetData.h" #include "utils/Utils.h" #include "render/OpenGlExtensions.h" #include @@ -314,7 +315,7 @@ namespace ZL //spaceshipTexture = std::make_unique(CreateTextureDataFromPng("resources/DefaultMaterial_BaseColor_shine.png", CONST_ZIP_FILE)); //spaceshipBase = LoadFromTextFile02("resources/spaceship006.txt", CONST_ZIP_FILE); //spaceshipBase.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(M_PI / 2.0, Eigen::Vector3f::UnitY())).toRotationMatrix());// QuatFromRotateAroundY(M_PI / 2.0).toRotationMatrix()); - + //spaceshipTexture = std::make_unique(CreateTextureDataFromPng("./resources/cap_D.png", CONST_ZIP_FILE)); //spaceshipBase = LoadFromTextFile02("./resources/spaceship006x.txt", CONST_ZIP_FILE); //spaceshipBase.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-M_PI / 2.0, Eigen::Vector3f::UnitY())).toRotationMatrix());// QuatFromRotateAroundY(M_PI / 2.0).toRotationMatrix()); @@ -695,22 +696,30 @@ namespace ZL projectileEmitter.update(static_cast(delta)); explosionEmitter.update(static_cast(delta)); + if (showExplosion) { + uint64_t now = SDL_GetTicks64(); + if (lastExplosionTime != 0 && now - lastExplosionTime >= explosionDurationMs) { + showExplosion = false; + explosionEmitter.setEmissionPoints(std::vector()); + explosionEmitter.setUseWorldSpace(false); + } + } if (shipAlive) { float distToSurface = planetObject.distanceToPlanetSurface(Environment::shipPosition); if (distToSurface <= 0.0f) { - Vector3f localForward = { 0,0,-1 }; - Vector3f worldForward = (Environment::shipMatrix * localForward).normalized(); - const float backDistance = 400.0f; - Environment::shipPosition = Environment::shipPosition - worldForward * backDistance; + Vector3f dir = (Environment::shipPosition - PlanetData::PLANET_CENTER_OFFSET).normalized(); + Vector3f collisionPoint = PlanetData::PLANET_CENTER_OFFSET + dir * PlanetData::PLANET_RADIUS; + Environment::shipPosition = PlanetData::PLANET_CENTER_OFFSET + dir * (PlanetData::PLANET_RADIUS + shipCollisionRadius + 0.1f); shipAlive = false; gameOver = true; Environment::shipVelocity = 0.0f; showExplosion = true; - explosionEmitter.setUseWorldSpace(false); - explosionEmitter.setEmissionPoints(std::vector{ Vector3f{ 0.0f,0.0f,0.0f } }); + explosionEmitter.setUseWorldSpace(true); + explosionEmitter.setEmissionPoints(std::vector{ collisionPoint }); explosionEmitter.emit(); + lastExplosionTime = SDL_GetTicks64(); std::cerr << "GAME OVER: collision with planet (moved back and exploded)\n"; @@ -747,6 +756,7 @@ namespace ZL bool stoneCollided = false; int collidedTriIdx = -1; Vector3f collidedStonePos = Vector3f{ 0.0f, 0.0f, 0.0f }; + float collidedStoneRadius = 0.0f; for (int triIdx : planetObject.triangleIndicesToDraw) { if (triIdx < 0 || triIdx >= static_cast(planetObject.planetStones.allInstances.size())) @@ -772,6 +782,7 @@ namespace ZL stoneCollided = true; collidedTriIdx = triIdx; collidedStonePos = stoneWorld; + collidedStoneRadius = stoneRadius; break; } } @@ -780,10 +791,13 @@ namespace ZL } if (stoneCollided) { - Vector3f localForward = { 0,0,-1 }; - Vector3f worldForward = (Environment::shipMatrix * localForward).normalized(); - const float backDistance = 50.0f; - Environment::shipPosition = Environment::shipPosition - worldForward * backDistance; + Vector3f away = (Environment::shipPosition - collidedStonePos); + if (away.squaredNorm() <= 1e-6f) { + away = Vector3f{ 0.0f, 1.0f, 0.0f }; + } + away.normalize(); + + Environment::shipPosition = collidedStonePos + away * (collidedStoneRadius + shipCollisionRadius + 0.1f); shipAlive = false; gameOver = true; @@ -793,6 +807,7 @@ namespace ZL explosionEmitter.setUseWorldSpace(true); explosionEmitter.setEmissionPoints(std::vector{ collidedStonePos }); explosionEmitter.emit(); + lastExplosionTime = SDL_GetTicks64(); std::cerr << "GAME OVER: collision with stone on triangle " << collidedTriIdx << std::endl; @@ -861,10 +876,42 @@ namespace ZL explosionEmitter.setUseWorldSpace(true); explosionEmitter.setEmissionPoints(std::vector{ boxWorld }); explosionEmitter.emit(); + lastExplosionTime = SDL_GetTicks64(); std::cerr << "Box destroyed at index " << i << std::endl; } } + + const float projectileHitRadius = 1.5f; + for (auto& p : projectiles) { + if (!p || !p->isActive()) continue; + Vector3f ppos = p->getPosition(); + Vector3f projInBoxSpace = Environment::inverseShipMatrix * (ppos - Environment::shipPosition); + for (int i = 0; i < boxCoordsArr.size(); ++i) { + if (!boxAlive[i]) continue; + Vector3f boxWorld = boxCoordsArr[i].pos + Vector3f{ 0.0f, 6.0f, 45000.0f }; + Vector3f dd = ppos - boxWorld; + float thresh = boxCollisionRadius + projectileHitRadius; + if (dd.squaredNorm() <= thresh * thresh) { + boxAlive[i] = false; + boxRenderArr[i].data.PositionData.clear(); + boxRenderArr[i].vao.reset(); + boxRenderArr[i].positionVBO.reset(); + boxRenderArr[i].texCoordVBO.reset(); + + showExplosion = true; + explosionEmitter.setUseWorldSpace(true); + explosionEmitter.setEmissionPoints(std::vector{ boxWorld }); + explosionEmitter.emit(); + lastExplosionTime = SDL_GetTicks64(); + + p->deactivate(); + std::cerr << "Box destroyed by projectile at index " << i << std::endl; + break; + } + } + } + uiManager.update(static_cast(delta)); //#endif lastTickCount = newTickCount; diff --git a/src/Game.h b/src/Game.h index 40cc6c8..9a9d45d 100644 --- a/src/Game.h +++ b/src/Game.h @@ -87,14 +87,16 @@ namespace ZL { int maxProjectiles = 32; std::vector shipLocalEmissionPoints; - + bool shipAlive = true; bool gameOver = false; std::vector boxAlive; - float shipCollisionRadius = 3.5f; + float shipCollisionRadius = 15.0f; float boxCollisionRadius = 2.0f; bool uiGameOverShown = false; bool showExplosion = false; + uint64_t lastExplosionTime = 0; + const uint64_t explosionDurationMs = 500; }; diff --git a/src/Projectile.h b/src/Projectile.h index 1a5a62f..872177f 100644 --- a/src/Projectile.h +++ b/src/Projectile.h @@ -18,7 +18,7 @@ namespace ZL { bool isActive() const { return active; } Vector3f getPosition() const { return pos; } - + void deactivate() { active = false; } private: Vector3f pos; Vector3f vel; From ea26d6fb23f61ff6bd80e83db4d467c1701a966f Mon Sep 17 00:00:00 2001 From: Vladislav Khorev Date: Thu, 15 Jan 2026 13:41:00 +0300 Subject: [PATCH 05/18] Working on local and websocket client --- proj-windows/CMakeLists.txt | 2 + src/Game.cpp | 52 +++++++++++------------ src/Game.h | 3 ++ src/network/LocalClient.cpp | 26 ++++++++++++ src/network/LocalClient.h | 21 ++++++++++ src/network/WebSocketClient.cpp | 73 +++++++++++++++++++++++++++++++++ src/network/WebSocketClient.h | 33 ++++++++++----- src/utils/TaskManager.h | 5 +++ 8 files changed, 178 insertions(+), 37 deletions(-) create mode 100644 src/network/LocalClient.cpp create mode 100644 src/network/LocalClient.h diff --git a/proj-windows/CMakeLists.txt b/proj-windows/CMakeLists.txt index 942ab6e..fd01681 100644 --- a/proj-windows/CMakeLists.txt +++ b/proj-windows/CMakeLists.txt @@ -51,6 +51,8 @@ add_executable(space-game001 ../src/Projectile.h ../src/Projectile.cpp ../src/network/NetworkInterface.h + ../src/network/LocalClient.h + ../src/network/LocalClient.cpp ../src/network/WebSocketClient.h ../src/network/WebSocketClient.cpp ) diff --git a/src/Game.cpp b/src/Game.cpp index 101baf2..974ffe6 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -13,6 +13,12 @@ #include #endif +#ifdef NETWORK +#include "network/WebSocketClient.h" +#else +#include "network/LocalClient.h" +#endif + namespace ZL { #ifdef EMSCRIPTEN @@ -375,6 +381,14 @@ namespace ZL //rockTexture = std::make_unique(CreateTextureDataFromPng("./resources/rock.png", CONST_ZIP_FILE)); std::cout << "Init step 8 " << std::endl; + +#ifdef NETWORK + networkClient = std::make_unique(taskManager.getIOContext()); + networkClient->Connect("127.0.0.1", 8080); +#else + networkClient = std::make_unique(); + networkClient->Connect("", 0); +#endif } void Game::drawCubemap(float skyPercent) @@ -626,6 +640,13 @@ namespace ZL sparkEmitter.update(static_cast(delta)); planetObject.update(static_cast(delta)); + static float pingTimer = 0.0f; + pingTimer += delta; + if (pingTimer >= 1000.0f) { + networkClient->Send("PING"); + pingTimer = 0.0f; + } + if (Environment::tapDownHold) { float diffx = Environment::tapDownCurrentPos(0) - Environment::tapDownStartPos(0); @@ -765,7 +786,6 @@ namespace ZL } } uiManager.update(static_cast(delta)); - //#endif lastTickCount = newTickCount; } } @@ -844,31 +864,6 @@ namespace ZL if (event.type == SDL_MOUSEBUTTONDOWN) { int mx = event.button.x; int my = event.button.y; - - uiManager.onMouseDown(mx, my); - - bool uiHandled = false; - - for (const auto& button : uiManager.findButton("") ? std::vector>{} : std::vector>{}) { - (void)button; - } - - auto pressedSlider = [&]() -> std::shared_ptr { - for (const auto& slider : uiManager.findSlider("") ? std::vector>{} : std::vector>{}) { - (void)slider; - } - return nullptr; - }(); - - if (!uiManager.isUiInteraction()) { - Environment::tapDownHold = true; - - Environment::tapDownStartPos(0) = mx; - Environment::tapDownStartPos(1) = my; - - Environment::tapDownCurrentPos(0) = mx; - Environment::tapDownCurrentPos(1) = my; - } handleDown(mx, my); } if (event.type == SDL_MOUSEBUTTONUP) { @@ -884,6 +879,7 @@ namespace ZL handleMotion(mx, my); } + /* if (event.type == SDL_MOUSEWHEEL) { static const float zoomstep = 2.0f; if (event.wheel.y > 0) { @@ -916,11 +912,12 @@ namespace ZL Environment::shipVelocity -= 50.f; //x = x - 2.0; } - } + }*/ #endif } render(); mainThreadHandler.processMainThreadTasks(); + networkClient->Poll(); } void Game::handleDown(int mx, int my) @@ -953,6 +950,7 @@ namespace ZL Environment::tapDownCurrentPos(1) = my; } } + void Game::handleUp(int mx, int my) { int uiX = mx; diff --git a/src/Game.h b/src/Game.h index 40cc6c8..b0aa524 100644 --- a/src/Game.h +++ b/src/Game.h @@ -8,6 +8,7 @@ #include "UiManager.h" #include "Projectile.h" #include "utils/TaskManager.h" +#include "network/NetworkInterface.h" #include namespace ZL { @@ -34,6 +35,8 @@ namespace ZL { Renderer renderer; TaskManager taskManager; MainThreadHandler mainThreadHandler; + + std::unique_ptr networkClient; private: void processTickCount(); void drawScene(); diff --git a/src/network/LocalClient.cpp b/src/network/LocalClient.cpp new file mode 100644 index 0000000..57d3266 --- /dev/null +++ b/src/network/LocalClient.cpp @@ -0,0 +1,26 @@ +#include "LocalClient.h" +#include + + +namespace ZL { + + void LocalClient::Connect(const std::string& host, uint16_t port) { + } + + void LocalClient::Poll() { + + if (!messageQueue.empty()) { + std::string msg = messageQueue.front(); + messageQueue.pop(); + std::cout << "LocalClient received message: " << msg << std::endl; + } + } + + void LocalClient::Send(const std::string& message) { + + if (message == "PING") + { + messageQueue.push("PONG"); + } + } +} \ No newline at end of file diff --git a/src/network/LocalClient.h b/src/network/LocalClient.h new file mode 100644 index 0000000..3e9b15e --- /dev/null +++ b/src/network/LocalClient.h @@ -0,0 +1,21 @@ +#pragma once + +// WebSocketClient.h +#include "NetworkInterface.h" +#include + +namespace ZL { + class LocalClient : public INetworkClient { + private: + std::queue messageQueue; + public: + void Connect(const std::string& host, uint16_t port) override; + + void Poll() override; + + void Send(const std::string& message) override; + + bool IsConnected() const override { return true; } + int GetClientId() const { return 1; } + }; +} \ No newline at end of file diff --git a/src/network/WebSocketClient.cpp b/src/network/WebSocketClient.cpp index e69de29..5f0784e 100644 --- a/src/network/WebSocketClient.cpp +++ b/src/network/WebSocketClient.cpp @@ -0,0 +1,73 @@ +#include "WebSocketClient.h" +#include + + +namespace ZL { + + void WebSocketClient::Connect(const std::string& host, uint16_t port) { + try { + boost::asio::ip::tcp::resolver resolver(ioc_); + auto const results = resolver.resolve(host, std::to_string(port)); + + ws_ = std::make_unique>(ioc_); + + // handshake + boost::beast::get_lowest_layer(*ws_).connect(results); + ws_->handshake(host, "/"); + + connected = true; + + // TaskManager + startAsyncRead(); + + } + catch (std::exception& e) { + std::cerr << "Network Error: " << e.what() << std::endl; + } + } + + void WebSocketClient::startAsyncRead() { + ws_->async_read(buffer_, [this](boost::beast::error_code ec, std::size_t bytes) { + if (!ec) { + std::string msg = boost::beast::buffers_to_string(buffer_.data()); + buffer_.consume(bytes); + + // ( TaskManager) + processIncomingMessage(msg); + + startAsyncRead(); + } + else { + connected = false; + } + }); + } + + void WebSocketClient::processIncomingMessage(const std::string& msg) { + // ... + if (msg.rfind("ID:", 0) == 0) { + clientId = std::stoi(msg.substr(3)); + } + + // + std::lock_guard lock(queueMutex); + messageQueue.push(msg); + } + + void WebSocketClient::Poll() { + // + std::lock_guard lock(queueMutex); + while (!messageQueue.empty()) { + std::string msg = messageQueue.front(); + messageQueue.pop(); + // + } + } + + void WebSocketClient::Send(const std::string& message) { + if (!connected) return; + // , + auto ss = std::make_shared(message); + ws_->async_write(boost::asio::buffer(*ss), [ss](boost::beast::error_code, std::size_t) {}); + } +} \ No newline at end of file diff --git a/src/network/WebSocketClient.h b/src/network/WebSocketClient.h index 7add3fe..ffaaecc 100644 --- a/src/network/WebSocketClient.h +++ b/src/network/WebSocketClient.h @@ -3,25 +3,38 @@ // WebSocketClient.h #include "NetworkInterface.h" #include +#include +#include +#include +#include namespace ZL { class WebSocketClient : public INetworkClient { private: + // io_context TaskManager + boost::asio::io_context& ioc_; + + // + std::unique_ptr> ws_; + boost::beast::flat_buffer buffer_; + std::queue messageQueue; + std::mutex queueMutex; // messageQueue + bool connected = false; int clientId = -1; - public: - void Connect(const std::string& host, uint16_t port) override { - // Emscripten emscripten_websocket_new - // Desktop - boost::beast::websocket::stream - } + void startAsyncRead(); + void processIncomingMessage(const std::string& msg); - void Poll() override { - // . - // ID: clientId = parseId(msg); connected = true; - // "PONG": . - } + public: + explicit WebSocketClient(boost::asio::io_context& ioc) : ioc_(ioc) {} + + void Connect(const std::string& host, uint16_t port) override; + + void Poll() override; + + void Send(const std::string& message) override; bool IsConnected() const override { return connected; } int GetClientId() const { return clientId; } diff --git a/src/utils/TaskManager.h b/src/utils/TaskManager.h index f1c5eb3..66423f8 100644 --- a/src/utils/TaskManager.h +++ b/src/utils/TaskManager.h @@ -24,6 +24,11 @@ namespace ZL { // Graceful shutdown ~TaskManager(); + + boost::asio::io_context& getIOContext() + { + return ioContext; + } }; From 1348efb5fe95ecb3547199a474232c510df7b0c2 Mon Sep 17 00:00:00 2001 From: Vladislav Khorev Date: Thu, 15 Jan 2026 21:54:37 +0300 Subject: [PATCH 06/18] Working on server --- proj-windows/CMakeLists.txt | 2 + server/CMakeLists.txt | 5 +- server/main.cpp | 81 --------- server/server.cpp | 237 ++++++++++++++++++++++++ src/Environment.cpp | 5 + src/Environment.h | 5 + src/Game.cpp | 307 +++++++++++++++++++++----------- src/Game.h | 5 +- src/network/ClientState.h | 152 ++++++++++++++++ src/network/NetworkInterface.h | 3 + src/network/RemotePlayer.h | 15 ++ src/network/WebSocketClient.cpp | 122 ++++++++++++- src/network/WebSocketClient.h | 15 ++ 13 files changed, 761 insertions(+), 193 deletions(-) delete mode 100644 server/main.cpp create mode 100644 server/server.cpp create mode 100644 src/network/ClientState.h create mode 100644 src/network/RemotePlayer.h diff --git a/proj-windows/CMakeLists.txt b/proj-windows/CMakeLists.txt index fd01681..774e1e0 100644 --- a/proj-windows/CMakeLists.txt +++ b/proj-windows/CMakeLists.txt @@ -53,6 +53,8 @@ add_executable(space-game001 ../src/network/NetworkInterface.h ../src/network/LocalClient.h ../src/network/LocalClient.cpp + ../src/network/ClientState.h + ../src/network/RemotePlayer.h ../src/network/WebSocketClient.h ../src/network/WebSocketClient.cpp ) diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index b119422..5e12ff8 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -25,7 +25,10 @@ add_subdirectory("${BOOST_SRC_DIR}/libs/predef" boost-predef-build EXCLUDE_FROM_ # EXCLUDE_FROM_ALL гарантирует, что мы собираем только то, что линкуем # Исполняемый файл сервера -add_executable(Server main.cpp) +add_executable(Server +server.cpp +../src/network/ClientState.h +) target_include_directories(Server PRIVATE ${BOOST_SRC_DIR}) diff --git a/server/main.cpp b/server/main.cpp deleted file mode 100644 index c3a3195..0000000 --- a/server/main.cpp +++ /dev/null @@ -1,81 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -namespace beast = boost::beast; -namespace http = beast::http; -namespace websocket = beast::websocket; -namespace net = boost::asio; -using tcp = net::ip::tcp; - -class Session : public std::enable_shared_from_this { - websocket::stream ws_; - beast::flat_buffer buffer_; - int id_; - -public: - explicit Session(tcp::socket&& socket, int id) - : ws_(std::move(socket)), id_(id) { - } - - void run() { - ws_.async_accept([self = shared_from_this()](beast::error_code ec) { - if (ec) return; - std::cout << "Client " << self->id_ << " connected\n"; - // ID - self->send_message("ID:" + std::to_string(self->id_)); - self->do_read(); - }); - } - -private: - void do_read() { - ws_.async_read(buffer_, [self = shared_from_this()](beast::error_code ec, std::size_t) { - if (ec) return; - std::string msg = beast::buffers_to_string(self->buffer_.data()); - - if (msg == "PING") { - self->send_message("PONG"); - } - - self->buffer_.consume(self->buffer_.size()); - 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) {}); - } -}; - -int main() { - try { - net::io_context ioc; - tcp::acceptor acceptor{ ioc, {tcp::v4(), 8080} }; - int next_id = 1000; - - 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); - }); - }; - - 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/server/server.cpp b/server/server.cpp new file mode 100644 index 0000000..635d3c8 --- /dev/null +++ b/server/server.cpp @@ -0,0 +1,237 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define _USE_MATH_DEFINES +#include +#include "../src/network/ClientState.h" + +// 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; +} + +namespace beast = boost::beast; +namespace http = beast::http; +namespace websocket = beast::websocket; +namespace net = boost::asio; +using tcp = net::ip::tcp; + +class Session; + +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_; + ClientState state_; + + void process_message(const std::string& msg) { + auto now_server = std::chrono::steady_clock::now(); + + // : + // ROT:ANGLE:MAG:TIMESTAMP + // VEL:SELECTED_VEL:TIMESTAMP + auto parts = split(msg, ':'); + if (parts.empty()) return; + + std::cout << msg << std::endl; + + auto now_ms = std::chrono::duration_cast( + now_server.time_since_epoch() + ).count(); + + uint64_t clientTimestamp = std::stoull(parts[1]); + + // " " + float dt_server = 0.0f; + if (state_.lastUpdateServerTime.time_since_epoch().count() > 0) { + dt_server = (clientTimestamp - now_ms) * 1000.f; + } + + + if (dt_server > 0) state_.simulate_physics(dt_server); + using time_point = std::chrono::steady_clock::time_point; + std::chrono::steady_clock::time_point uptime_timepoint{ std::chrono::duration_cast(std::chrono::milliseconds(clientTimestamp)) }; + state_.lastUpdateServerTime = uptime_timepoint; + + + // + if (parts[0] == "ROT") { + state_.discreteAngle = std::stoi(parts[2]); + state_.discreteMag = std::stof(parts[3]); + state_.apply_lag_compensation(now_server); + } + else if (parts[0] == "VEL") { + state_.selectedVelocity = std::stoi(parts[2]); + state_.apply_lag_compensation(now_server); + } + else if (parts[0] == "PING") { + state_.handle_full_sync(parts); + state_.apply_lag_compensation(now_server); + } + } + + + + +public: + explicit Session(tcp::socket&& socket, int id) + : ws_(std::move(socket)), id_(id) { + } + + void init() + { + state_.lastUpdateServerTime = std::chrono::steady_clock::now(); + } + + std::string get_state_string() { + return state_.get_state_string(id_); + } + + 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(); + }); + } + + void tick_physics_global(std::chrono::steady_clock::time_point now) { + float dt = 0.0f; + + // , + if (state_.lastUpdateServerTime.time_since_epoch().count() > 0) { + dt = std::chrono::duration(now - state_.lastUpdateServerTime).count(); + } + + if (dt > 0.0001f) { + state_.simulate_physics(dt); + state_.lastUpdateServerTime = now; // + } + } + + void tick_physics(float dt_s) { + state_.simulate_physics(dt_s); + } + + 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) {}); + } + +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(); + }), g_sessions.end()); + return; + } + + std::string msg = beast::buffers_to_string(self->buffer_.data()); + self->process_message(msg); + + self->buffer_.consume(self->buffer_.size()); + self->do_read(); + }); + } +}; + +void update_world(net::steady_timer& timer, net::io_context& ioc) { + auto now = std::chrono::steady_clock::now(); + + { + std::lock_guard lock(g_sessions_mutex); + + // 1. + for (auto& session : g_sessions) { + session->tick_physics_global(now); + } + + static auto last_broadcast = now; + if (std::chrono::duration(now - last_broadcast).count() >= 1.0f) { + last_broadcast = now; + + auto now_ms = std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch() + ).count(); + + // + std::string snapshot = "WORLD_UPDATE|"; + snapshot += std::to_string(now_ms) + "|"; + snapshot += std::to_string(g_sessions.size()) + "|"; + for (size_t i = 0; i < g_sessions.size(); ++i) { + snapshot += g_sessions[i]->get_state_string(); + if (i < g_sessions.size() - 1) snapshot += ";"; // + } + + // + for (auto& session : g_sessions) { + session->send_message(snapshot); + } + } + + } + + // : (50), (1000 ) + timer.expires_after(std::chrono::milliseconds(50)); + timer.async_wait([&](const boost::system::error_code& ec) { + if (!ec) update_world(timer, ioc); + }); +} + +int main() { + try { + net::io_context ioc; + tcp::acceptor acceptor{ ioc, {tcp::v4(), 8080} }; + int next_id = 1000; + + 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); + }); + }; + + + 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/Environment.cpp b/src/Environment.cpp index 5f2bf6a..7c6ab60 100644 --- a/src/Environment.cpp +++ b/src/Environment.cpp @@ -40,8 +40,13 @@ Eigen::Vector2f Environment::tapDownCurrentPos = { 0, 0 }; Eigen::Vector3f Environment::shipPosition = {0,0,45000.f}; float Environment::shipVelocity = 0.f; +int Environment::shipSelectedVelocity = 0; +Eigen::Vector3f Environment::currentAngularVelocity; +int Environment::lastSentAngle = -1; +float Environment::lastSentMagnitude = 0.0f; + const float Environment::CONST_Z_NEAR = 5.f; const float Environment::CONST_Z_FAR = 5000.f; diff --git a/src/Environment.h b/src/Environment.h index 0e01ee1..6c52147 100644 --- a/src/Environment.h +++ b/src/Environment.h @@ -38,6 +38,11 @@ public: static Eigen::Vector3f shipPosition; static float shipVelocity; + static int shipSelectedVelocity; + static Eigen::Vector3f currentAngularVelocity; + + static int lastSentAngle; + static float lastSentMagnitude; static const float CONST_Z_NEAR; static const float CONST_Z_FAR; diff --git a/src/Game.cpp b/src/Game.cpp index 974ffe6..447f401 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -19,6 +19,8 @@ #include "network/LocalClient.h" #endif +#include "network/RemotePlayer.h" + namespace ZL { #ifdef EMSCRIPTEN @@ -139,33 +141,6 @@ namespace ZL ZL::BindOpenGlFunctions(); ZL::CheckGlError(); -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_INFO, "Game", "Start for Android"); - - const char* testFiles[] = { - "resources/shaders/default.vertex", - "resources/shaders/default_web.fragment", - nullptr - }; - - for (int i = 0; testFiles[i] != nullptr; i++) { - SDL_RWops* file = SDL_RWFromFile(testFiles[i], "rb"); - if (file) { - Sint64 size = SDL_RWsize(file); - __android_log_print(ANDROID_LOG_INFO, "Game", "Found: %s (size: %lld)", testFiles[i], size); - SDL_RWclose(file); - } - else { - __android_log_print(ANDROID_LOG_WARN, "Game", "Not found: %s (SDL error: %s)", - testFiles[i], SDL_GetError()); - } - } - - __android_log_print(ANDROID_LOG_ERROR, "ShaderManager", - "Step 1xxxxxxx"); -#endif - // Initialize renderer - #ifndef SIMPLIFIED renderer.shaderManager.AddShaderFromFiles("defaultColor", "resources/shaders/defaultColor.vertex", "resources/shaders/defaultColor_web.fragment", CONST_ZIP_FILE); @@ -178,37 +153,11 @@ namespace ZL #else renderer.shaderManager.AddShaderFromFiles("default", "resources/shaders/default.vertex", "resources/shaders/default_web.fragment", CONST_ZIP_FILE); -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_ERROR, "ShaderManager", - "Step 2xxxxxxx"); -#endif renderer.shaderManager.AddShaderFromFiles("env_sky", "resources/shaders/default_env.vertex", "resources/shaders/default_env_web.fragment", CONST_ZIP_FILE); -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_INFO, "ShaderManager", - "Step 2"); -#endif renderer.shaderManager.AddShaderFromFiles("defaultAtmosphere", "resources/shaders/default_texture.vertex", "resources/shaders/default_texture_web.fragment", CONST_ZIP_FILE); -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_INFO, "ShaderManager", - "Step 3"); -#endif - renderer.shaderManager.AddShaderFromFiles("planetBake", "resources/shaders/default_texture.vertex", "resources/shaders/default_texture_web.fragment", CONST_ZIP_FILE); -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_INFO, "ShaderManager", - "Step 4"); -#endif renderer.shaderManager.AddShaderFromFiles("planetStone", "resources/shaders/default_texture.vertex", "resources/shaders/default_texture_web.fragment", CONST_ZIP_FILE); -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_INFO, "ShaderManager", - "Step 5"); -#endif renderer.shaderManager.AddShaderFromFiles("planetLand", "resources/shaders/default_texture.vertex", "resources/shaders/default_texture_web.fragment", CONST_ZIP_FILE); -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_INFO, "ShaderManager", - "Step 1"); -#endif - #endif bool cfgLoaded = sparkEmitter.loadFromJsonFile("resources/config/spark_config.json", renderer, CONST_ZIP_FILE); @@ -289,16 +238,17 @@ namespace ZL }); uiManager.setSliderCallback("velocitySlider", [this](const std::string& name, float value) { - Environment::shipVelocity = value * 1000.f; + int newVel = roundf(value * 10); + if (newVel != Environment::shipSelectedVelocity) { + Environment::shipSelectedVelocity = newVel; + auto now_ms = std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch() + ).count(); + std::string msg = "VEL:" + std::to_string(now_ms) + ":" + std::to_string(Environment::shipSelectedVelocity); + networkClient->Send(msg); + } }); - /*uiManager.setSliderCallback("musicVolumeSlider", [this](const std::string& name, float value) { - std::cerr << "Music volume slider changed to: " << value << std::endl; - musicVolume = value; - Environment::shipVelocity = musicVolume * 20.0f; - }); - //#endif*/ - cubemapTexture = std::make_shared( std::array{ CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE), @@ -330,10 +280,6 @@ namespace ZL spaceshipBase.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY())).toRotationMatrix());// QuatFromRotateAroundY(M_PI / 2.0).toRotationMatrix()); spaceshipBase.Move(Vector3f{ 1.2, 0, -5 }); - //spaceshipBase.Move(Vector3f{ -0.52998, -13, 0 }); - //spaceshipBase.Move(Vector3f{ -0.52998, -10, 10 }); - //spaceshipBase.Move(Vector3f{ -0.52998, 0, 10 }); - spaceship.AssignFrom(spaceshipBase); spaceship.RefreshVBO(); @@ -343,16 +289,8 @@ namespace ZL boxTexture = std::make_unique(CreateTextureDataFromPng("resources/box/box.png", CONST_ZIP_FILE)); boxBase = LoadFromTextFile02("resources/box/box.txt", CONST_ZIP_FILE); - std::cout << "Init step 1 " << std::endl; - boxCoordsArr = generateRandomBoxCoords(50); - - std::cout << "Init step 2 " << std::endl; - boxRenderArr.resize(boxCoordsArr.size()); - - std::cout << "Init step 3x " << std::endl; - for (int i = 0; i < boxCoordsArr.size(); i++) { boxRenderArr[i].AssignFrom(boxBase); @@ -362,26 +300,17 @@ namespace ZL boxAlive.resize(boxCoordsArr.size(), true); - std::cout << "Init step 4 " << std::endl; - if (!cfgLoaded) { throw std::runtime_error("Failed to load spark emitter config file!"); } - std::cout << "Init step 5 " << std::endl; renderer.InitOpenGL(); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - std::cout << "Init step 6 " << std::endl; planetObject.init(); - std::cout << "Init step 7 " << std::endl; - //rockTexture = std::make_unique(CreateTextureDataFromPng("./resources/rock.png", CONST_ZIP_FILE)); - - std::cout << "Init step 8 " << std::endl; - #ifdef NETWORK networkClient = std::make_unique(taskManager.getIOContext()); networkClient->Connect("127.0.0.1", 8080); @@ -639,14 +568,182 @@ namespace ZL sparkEmitter.update(static_cast(delta)); planetObject.update(static_cast(delta)); + updateRemotePlayers(static_cast(delta)); static float pingTimer = 0.0f; pingTimer += delta; if (pingTimer >= 1000.0f) { - networkClient->Send("PING"); + + auto now_ms = std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch() + ).count(); + + // 1. Извлекаем кватернион из матрицы поворота + Eigen::Quaternionf q(Environment::shipMatrix); + + // 2. Формируем строку PING согласно протоколу сервера + // Формат: PING:timestamp:posX:posY:posZ:qW:qX:qY:qZ:angVelX:angVelY:angVelZ:vel:selectedVel:discMag:discAngle + std::string pingMsg = "PING:" + std::to_string(now_ms) + ":" + + std::to_string(Environment::shipPosition.x()) + ":" + + std::to_string(Environment::shipPosition.y()) + ":" + + std::to_string(Environment::shipPosition.z()) + ":" + + std::to_string(q.w()) + ":" + + std::to_string(q.x()) + ":" + + std::to_string(q.y()) + ":" + + std::to_string(q.z()) + ":" + + std::to_string(Environment::currentAngularVelocity.x()) + ":" + + std::to_string(Environment::currentAngularVelocity.y()) + ":" + + std::to_string(Environment::currentAngularVelocity.z()) + ":" + + std::to_string(Environment::shipVelocity) + ":" + + std::to_string(Environment::shipSelectedVelocity) + ":" + + std::to_string(Environment::lastSentMagnitude) + ":" // Используем те же static переменные из блока ROT + + std::to_string(Environment::lastSentAngle); + + networkClient->Send(pingMsg); pingTimer = 0.0f; } + static const float CONST_ACCELERATION = 1.f; + + float shipDesiredVelocity = Environment::shipSelectedVelocity * 100.f; + + if (!gameOver) + { + if (Environment::shipVelocity < shipDesiredVelocity) + { + Environment::shipVelocity += delta * CONST_ACCELERATION; + if (Environment::shipVelocity > shipDesiredVelocity) + { + Environment::shipVelocity = shipDesiredVelocity; + } + } + else if (Environment::shipVelocity > shipDesiredVelocity) + { + Environment::shipVelocity -= delta * CONST_ACCELERATION; + if (Environment::shipVelocity < shipDesiredVelocity) + { + Environment::shipVelocity = shipDesiredVelocity; + } + } + } + + static const float ANGULAR_ACCEL = 0.005f; + + if (Environment::tapDownHold) { + float diffx = Environment::tapDownCurrentPos(0) - Environment::tapDownStartPos(0); + float diffy = Environment::tapDownCurrentPos(1) - Environment::tapDownStartPos(1); + + float rawMag = sqrtf(diffx * diffx + diffy * diffy); + float maxRadius = 200.0f; // Максимальный вынос джойстика + + if (rawMag > 10.0f) { // Мертвая зона + // 1. Дискретизируем отклонение (0.0 - 1.0 с шагом 0.1) + float normalizedMag = min(rawMag / maxRadius, 1.0f); + float discreteMag = std::round(normalizedMag * 10.0f) / 10.0f; + + // 2. Дискретизируем угол (0-359 градусов) + // atan2 возвращает радианы, переводим в градусы + float radians = atan2f(diffy, diffx); + int discreteAngle = static_cast(radians * 180.0f / M_PI); + if (discreteAngle < 0) discreteAngle += 360; + + // 3. Проверяем, изменились ли параметры значимо для отправки на сервер + if (discreteAngle != Environment::lastSentAngle || discreteMag != Environment::lastSentMagnitude) { + Environment::lastSentAngle = discreteAngle; + Environment::lastSentMagnitude = discreteMag; + auto now_ms = std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch() + ).count(); + + // Формируем сетевой пакет + // Нам нужно отправить: дискретный угол, дискретную силу и текущую матрицу/позицию для синхронизации + std::string msg = "ROT:" + std::to_string(now_ms) + ":" + std::to_string(discreteAngle) + ":" + std::to_string(discreteMag); + networkClient->Send(msg); + } + + // 4. Логика вращения (угловое ускорение) + // Теперь используем discreteAngle и discreteMag как "цель" для набора угловой скорости + // ... (код интерполяции скорости к этой цели) + + // Вычисляем целевой вектор оси вращения из дискретного угла + // В 3D пространстве экранные X и Y преобразуются в оси вращения вокруг Y и X соответственно + float rad = static_cast(discreteAngle) * static_cast(M_PI) / 180.0f; + + // Целевая угловая скорость (дискретная сила определяет модуль вектора) + // Вектор {cos, sin, 0} дает нам направление отклонения джойстика + Eigen::Vector3f targetAngularVelDir(sinf(rad), cosf(rad), 0.0f); + Eigen::Vector3f targetAngularVelocity = targetAngularVelDir * discreteMag; + + + Eigen::Vector3f diffVel = targetAngularVelocity - Environment::currentAngularVelocity; + float diffLen = diffVel.norm(); + + if (diffLen > 0.0001f) { + // Вычисляем, на сколько мы можем изменить скорость в этом кадре + float maxChange = ANGULAR_ACCEL * static_cast(delta); + + if (diffLen <= maxChange) { + // Если до цели осталось меньше, чем шаг ускорения — просто прыгаем в цель + Environment::currentAngularVelocity = targetAngularVelocity; + } + else { + // Линейно двигаемся в сторону целевого вектора + Environment::currentAngularVelocity += (diffVel / diffLen) * maxChange; + } + } + + // Применяем вращение к матрице корабля + float speedScale = Environment::currentAngularVelocity.norm(); + if (speedScale > 0.0001f) { + // Коэффициент чувствительности вращения + const float ROTATION_SENSITIVITY = 0.002f; + float deltaAlpha = speedScale * static_cast(delta) * ROTATION_SENSITIVITY; + + Eigen::Vector3f axis = Environment::currentAngularVelocity.normalized(); + Eigen::Quaternionf rotateQuat(Eigen::AngleAxisf(deltaAlpha, axis)); + + Environment::shipMatrix = Environment::shipMatrix * rotateQuat.toRotationMatrix(); + Environment::inverseShipMatrix = Environment::shipMatrix.inverse(); + } + } + } + else { + // Если джойстик не зажат — сбрасываем дискретные значения и плавно замедляем вращение + Environment::lastSentAngle = -1; + Environment::lastSentMagnitude = 0.0f; + + float currentSpeed = Environment::currentAngularVelocity.norm(); + + if (currentSpeed > 0.0001f) { + float drop = ANGULAR_ACCEL * static_cast(delta); + if (currentSpeed <= drop) { + Environment::currentAngularVelocity = Eigen::Vector3f::Zero(); + } + else { + // Уменьшаем модуль вектора, сохраняя направление + Environment::currentAngularVelocity -= (Environment::currentAngularVelocity / currentSpeed) * drop; + } + } + + // Применяем остаточное вращение (инерция) + float speedScale = Environment::currentAngularVelocity.norm(); + if (speedScale > 0.0001f) { + float deltaAlpha = speedScale * static_cast(delta) * 0.002f; + Eigen::Quaternionf rotateQuat(Eigen::AngleAxisf(deltaAlpha, Environment::currentAngularVelocity.normalized())); + Environment::shipMatrix = Environment::shipMatrix * rotateQuat.toRotationMatrix(); + Environment::inverseShipMatrix = Environment::shipMatrix.inverse(); + } + } + + // Движение вперед (существующая логика) + if (fabs(Environment::shipVelocity) > 0.01f) + { + Vector3f velocityDirection = { 0,0, -Environment::shipVelocity * delta / 1000.f }; + Vector3f velocityDirectionAdjusted = Environment::shipMatrix * velocityDirection; + Environment::shipPosition = Environment::shipPosition + velocityDirectionAdjusted; + } + + /* if (Environment::tapDownHold) { float diffx = Environment::tapDownCurrentPos(0) - Environment::tapDownStartPos(0); @@ -671,14 +768,7 @@ namespace ZL Environment::inverseShipMatrix = Environment::shipMatrix.inverse(); } - } - - if (fabs(Environment::shipVelocity) > 0.01f) - { - Vector3f velocityDirection = { 0,0, -Environment::shipVelocity * delta / 1000.f }; - Vector3f velocityDirectionAdjusted = Environment::shipMatrix * velocityDirection; - Environment::shipPosition = Environment::shipPosition + velocityDirectionAdjusted; - } + }*/ for (auto& p : projectiles) { if (p && p->isActive()) { @@ -896,21 +986,7 @@ namespace ZL { if (event.key.keysym.sym == SDLK_i) { - Environment::shipVelocity += 500.f; - } - if (event.key.keysym.sym == SDLK_k) - { - Environment::shipVelocity -= 500.f; - } - if (event.key.keysym.sym == SDLK_o) - { - Environment::shipVelocity += 50.f; - //x = x + 2.0; - } - if (event.key.keysym.sym == SDLK_l) - { - Environment::shipVelocity -= 50.f; - //x = x - 2.0; + } }*/ #endif @@ -976,5 +1052,24 @@ namespace ZL } } + void Game::updateRemotePlayers(float deltaMs) { + + latestRemotePlayers = networkClient->getRemotePlayers(); + + for (auto& [id, rp] : latestRemotePlayers) { + // Увеличиваем фактор интерполяции (базируется на частоте Snapshot = 1000мс) + rp.interpolationFactor += deltaMs / 1000.0f; + if (rp.interpolationFactor > 1.0f) rp.interpolationFactor = 1.0f; + + // Линейная интерполяция позиции + rp.state.position = rp.startPosition + (rp.targetPosition - rp.startPosition) * rp.interpolationFactor; + + // Сферическая интерполяция вращения (Slerp) + Eigen::Quaternionf currentQ = rp.startRotation.slerp(rp.interpolationFactor, rp.targetRotation); + rp.state.rotation = currentQ.toRotationMatrix(); + } + + } + } // namespace ZL diff --git a/src/Game.h b/src/Game.h index b0aa524..d66d654 100644 --- a/src/Game.h +++ b/src/Game.h @@ -44,13 +44,15 @@ namespace ZL { void drawShip(); void drawBoxes(); void drawUI(); - + void fireProjectiles(); void handleDown(int mx, int my); void handleUp(int mx, int my); void handleMotion(int mx, int my); + void updateRemotePlayers(float deltaMs); + SDL_Window* window; SDL_GLContext glContext; @@ -62,6 +64,7 @@ namespace ZL { std::vector boxCoordsArr; std::vector boxRenderArr; + std::unordered_map latestRemotePlayers; static const size_t CONST_TIMER_INTERVAL = 10; static const size_t CONST_MAX_TIME_INTERVAL = 1000; diff --git a/src/network/ClientState.h b/src/network/ClientState.h new file mode 100644 index 0000000..edf77c2 --- /dev/null +++ b/src/network/ClientState.h @@ -0,0 +1,152 @@ +#pragma once +#include +#include +#define _USE_MATH_DEFINES +#include + + + +using std::min; +using std::max; + +struct ClientState { + int id; + Eigen::Vector3f position = { 0, 0, 45000.0f }; + Eigen::Matrix3f rotation = Eigen::Matrix3f::Identity(); + Eigen::Vector3f currentAngularVelocity = Eigen::Vector3f::Zero(); + float velocity = 0.0f; + int selectedVelocity = 0; + float discreteMag = 0; + int discreteAngle = -1; + + // Для расчета лага + uint64_t lastClientTimestamp = 0; + std::chrono::steady_clock::time_point lastUpdateServerTime; + + void simulate_physics(float dt_s) { + // Константы из Game.cpp, приведенные к секундам (умножаем на 1000) + const float ANGULAR_ACCEL = 0.005f * 1000.0f; + const float ROTATION_SENSITIVITY = 0.002f * 1000.0f; + const float SHIP_ACCEL = 1.0f * 1000.0f; // CONST_ACCELERATION + + // 1. Вычисляем targetAngularVelocity на лету из дискретных значений + Eigen::Vector3f targetAngularVelocity = Eigen::Vector3f::Zero(); + + if (discreteMag > 0.001f) { + float rad = static_cast(discreteAngle) * static_cast(M_PI) / 180.0f; + // Направление из угла (как в твоем обновленном клиентском коде) + Eigen::Vector3f targetDir(sinf(rad), cosf(rad), 0.0f); + targetAngularVelocity = targetDir * discreteMag; + } + + // 2. Линейное изменение текущей угловой скорости к вычисленной цели + Eigen::Vector3f diffVel = targetAngularVelocity - currentAngularVelocity; + float diffLen = diffVel.norm(); + + if (diffLen > 0.0001f) { + float maxChange = ANGULAR_ACCEL * dt_s; + if (diffLen <= maxChange) { + currentAngularVelocity = targetAngularVelocity; + } + else { + currentAngularVelocity += (diffVel / diffLen) * maxChange; + } + } + else if (discreteMag < 0.001f && currentAngularVelocity.norm() > 0.0001f) { + // Если джойстик отпущен, используем ту же логику торможения (или ANGULAR_ACCEL) + float currentSpeed = currentAngularVelocity.norm(); + float drop = ANGULAR_ACCEL * dt_s; + if (currentSpeed <= drop) { + currentAngularVelocity = Eigen::Vector3f::Zero(); + } + else { + currentAngularVelocity -= (currentAngularVelocity / currentSpeed) * drop; + } + } + + // 3. Применение вращения к матрице (Интеграция) + float speedScale = currentAngularVelocity.norm(); + if (speedScale > 0.0001f) { + float deltaAlpha = speedScale * dt_s * ROTATION_SENSITIVITY; + Eigen::Vector3f axis = currentAngularVelocity.normalized(); + + Eigen::Quaternionf rotateQuat(Eigen::AngleAxisf(deltaAlpha, axis)); + rotation = rotation * rotateQuat.toRotationMatrix(); + } + + // 4. Линейное изменение линейной скорости + float shipDesiredVelocity = static_cast(selectedVelocity) * 100.f; + float speedDiff = shipDesiredVelocity - velocity; + if (std::abs(speedDiff) > 0.001f) { + float speedStep = SHIP_ACCEL * dt_s; + if (std::abs(speedDiff) <= speedStep) { + velocity = shipDesiredVelocity; + } + else { + velocity += (speedDiff > 0 ? 1.0f : -1.0f) * speedStep; + } + } + + // 5. Обновление позиции + if (std::abs(velocity) > 0.01f) { + // Движение вперед по локальной оси Z (в твоем коде это {0, 0, -1}) + Eigen::Vector3f localMove(0.0f, 0.0f, -velocity * dt_s); + position += rotation * localMove; + } + } + + void apply_lag_compensation(std::chrono::steady_clock::time_point nowTime) { + + // 1. Получаем текущее время сервера в той же шкале (мс) + auto now_ms = std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch() + ).count(); + + // 2. Вычисляем задержку + double lag_ms = 0.0; + if (nowTime > lastUpdateServerTime) { + lag_ms = std::chrono::duration(nowTime - lastUpdateServerTime).count(); + } + + // 3. Защита от слишком больших скачков (Clamp) + // Если лаг более 500мс, ограничиваем его, чтобы избежать резких рывков + float final_lag_s = min(static_cast(lag_ms), 0.5f); + + if (final_lag_s > 0.001f) { + // Доматываем симуляцию на величину задержки + // Мы предполагаем, что за это время параметры управления не менялись + simulate_physics(final_lag_s); + } + } + + std::string get_state_string(int id) { + // Используем кватернион для передачи вращения (4 числа вместо 9) + Eigen::Quaternionf q(rotation); + + std::string s = std::to_string(id) + "," + + std::to_string(position.x()) + "," + std::to_string(position.y()) + "," + std::to_string(position.z()) + "," + + std::to_string(q.w()) + "," + std::to_string(q.x()) + "," + std::to_string(q.y()) + "," + std::to_string(q.z()) + "," + + std::to_string(velocity) + "," + + std::to_string(currentAngularVelocity.x()) + "," + std::to_string(currentAngularVelocity.y()) + "," + std::to_string(currentAngularVelocity.z()) + "," + + std::to_string(selectedVelocity) + "," + + std::to_string(discreteMag) + "," + + std::to_string(discreteAngle); + return s; + } + + void handle_full_sync(const std::vector& parts) { + // Позиция + position = { std::stof(parts[2]), std::stof(parts[3]), std::stof(parts[4]) }; + + // Для вращения клиент должен прислать либо кватернион, либо углы Эйлера. + // Предположим, мы передаем 4 значения кватерниона для экономии: + Eigen::Quaternionf q(std::stof(parts[5]), std::stof(parts[6]), std::stof(parts[7]), std::stof(parts[8])); + rotation = q.toRotationMatrix(); + + currentAngularVelocity = Eigen::Vector3f{ std::stof(parts[9]), std::stof(parts[10]), std::stof(parts[11]) }; + velocity = std::stof(parts[12]); + selectedVelocity = std::stoi(parts[13]); + discreteMag = std::stof(parts[14]); + discreteAngle = std::stoi(parts[15]); + } +}; diff --git a/src/network/NetworkInterface.h b/src/network/NetworkInterface.h index dca15c0..2691be4 100644 --- a/src/network/NetworkInterface.h +++ b/src/network/NetworkInterface.h @@ -1,5 +1,7 @@ #pragma once #include +#include +#include "RemotePlayer.h" // NetworkInterface.h - namespace ZL { @@ -10,5 +12,6 @@ namespace ZL { virtual void Send(const std::string& message) = 0; virtual bool IsConnected() const = 0; virtual void Poll() = 0; // + virtual std::unordered_map getRemotePlayers() = 0; }; } diff --git a/src/network/RemotePlayer.h b/src/network/RemotePlayer.h new file mode 100644 index 0000000..ff9bb8d --- /dev/null +++ b/src/network/RemotePlayer.h @@ -0,0 +1,15 @@ +#pragma once +#include "ClientState.h" + +struct RemotePlayer { + ClientState state; + + // Данные для интерполяции + Eigen::Vector3f startPosition; + Eigen::Vector3f targetPosition; + Eigen::Quaternionf startRotation; + Eigen::Quaternionf targetRotation; + + float interpolationFactor = 0.0f; + uint64_t lastSnapshotTime = 0; +}; \ No newline at end of file diff --git a/src/network/WebSocketClient.cpp b/src/network/WebSocketClient.cpp index 5f0784e..276ea57 100644 --- a/src/network/WebSocketClient.cpp +++ b/src/network/WebSocketClient.cpp @@ -1,6 +1,18 @@ #include "WebSocketClient.h" #include +#include +#include "RemotePlayer.h" +// 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; +} namespace ZL { @@ -55,19 +67,121 @@ namespace ZL { } void WebSocketClient::Poll() { - // std::lock_guard lock(queueMutex); while (!messageQueue.empty()) { std::string msg = messageQueue.front(); messageQueue.pop(); - // + + if (msg.rfind("WORLD_UPDATE|", 0) == 0) { + parseWorldUpdate(msg); + std::cout << msg << std::endl; + } } } + void WebSocketClient::parseWorldUpdate(const std::string& msg) { + // : WORLD_UPDATE|server_now_ms|count|id,x,y,z,w,qx,qy,qz,v;... + auto parts = split(msg, '|'); + if (parts.size() < 4) return; + + uint64_t serverTime = std::stoull(parts[1]); + int count = std::stoi(parts[2]); + auto playersData = split(parts[3], ';'); + + for (const auto& pData : playersData) { + auto vals = split(pData, ','); + if (vals.size() < 9) continue; + + int id = std::stoi(vals[0]); + if (id == this->clientId) continue; // + + // RemotePlayer + updateRemotePlayer(id, vals); + } + } + + void WebSocketClient::updateRemotePlayer(int id, const std::vector& vals) { + // , (TaskManager) + std::lock_guard lock(playersMutex); + + auto& rp = remotePlayers[id]; + rp.state.id = id; + + // 1. + // "", 1.0 + if (rp.lastSnapshotTime == 0) { + // + rp.startPosition = { std::stof(vals[1]), std::stof(vals[2]), std::stof(vals[3]) }; + rp.startRotation = Eigen::Quaternionf(std::stof(vals[4]), std::stof(vals[5]), std::stof(vals[6]), std::stof(vals[7])); + } + else { + // , , + rp.startPosition = rp.state.position; + rp.startRotation = Eigen::Quaternionf(rp.state.rotation); + } + + // 2. + rp.targetPosition = { std::stof(vals[1]), std::stof(vals[2]), std::stof(vals[3]) }; + rp.targetRotation = Eigen::Quaternionf( + std::stof(vals[4]), // w + std::stof(vals[5]), // x + std::stof(vals[6]), // y + std::stof(vals[7]) // z + ); + + // 3. ( ) + rp.state.velocity = std::stof(vals[8]); + rp.state.currentAngularVelocity = { std::stof(vals[9]), std::stof(vals[10]), std::stof(vals[11]) }; + rp.state.selectedVelocity = std::stoi(vals[12]); + rp.state.discreteMag = std::stoi(vals[13]); + rp.state.discreteAngle = std::stoi(vals[14]); + + // 4. + rp.interpolationFactor = 0.0f; + auto now_ms = std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch() + ).count(); + rp.lastSnapshotTime = now_ms; + } + + void WebSocketClient::Send(const std::string& message) { if (!connected) return; - // , + auto ss = std::make_shared(message); - ws_->async_write(boost::asio::buffer(*ss), [ss](boost::beast::error_code, std::size_t) {}); + + std::lock_guard lock(writeMutex_); + writeQueue_.push(ss); + + // , + if (!isWriting_) { + doWrite(); + } + } + + void WebSocketClient::doWrite() { + // + if (writeQueue_.empty()) { + isWriting_ = false; + return; + } + + isWriting_ = true; + auto message = writeQueue_.front(); + + // self (shared_from_this), + ws_->async_write( + boost::asio::buffer(*message), + [this, message](boost::beast::error_code ec, std::size_t) { + if (ec) { + connected = false; + return; + } + + std::lock_guard lock(writeMutex_); + writeQueue_.pop(); // + doWrite(); // + } + ); } } \ No newline at end of file diff --git a/src/network/WebSocketClient.h b/src/network/WebSocketClient.h index ffaaecc..5f18ced 100644 --- a/src/network/WebSocketClient.h +++ b/src/network/WebSocketClient.h @@ -21,9 +21,16 @@ namespace ZL { std::queue messageQueue; std::mutex queueMutex; // messageQueue + std::queue> writeQueue_; + bool isWriting_ = false; + std::mutex writeMutex_; // + bool connected = false; int clientId = -1; + std::unordered_map remotePlayers; + std::mutex playersMutex; + void startAsyncRead(); void processIncomingMessage(const std::string& msg); @@ -33,10 +40,18 @@ namespace ZL { void Connect(const std::string& host, uint16_t port) override; void Poll() override; + void parseWorldUpdate(const std::string& msg); + void updateRemotePlayer(int id, const std::vector& vals); void Send(const std::string& message) override; + void doWrite(); bool IsConnected() const override { return connected; } int GetClientId() const { return clientId; } + + std::unordered_map getRemotePlayers() override { + std::lock_guard lock(playersMutex); + return remotePlayers; + } }; } \ No newline at end of file From 0f415b6ed6413c23380a06f138b7090e869690b8 Mon Sep 17 00:00:00 2001 From: vottozi Date: Fri, 16 Jan 2026 00:58:22 +0600 Subject: [PATCH 07/18] added SDL_ttf and FreeType libs --- cmake/FetchDependencies.cmake | 6 + cmake/ThirdParty.cmake | 212 +++++++++++++++++++++++++++++++++- proj-windows/CMakeLists.txt | 8 ++ 3 files changed, 224 insertions(+), 2 deletions(-) diff --git a/cmake/FetchDependencies.cmake b/cmake/FetchDependencies.cmake index f05529f..4dd118c 100644 --- a/cmake/FetchDependencies.cmake +++ b/cmake/FetchDependencies.cmake @@ -41,3 +41,9 @@ check_and_download("https://gitlab.com/libeigen/eigen/-/archive/5.0.0/eigen-5.0. # 6) Boost check_and_download("https://archives.boost.io/release/1.90.0/source/boost_1_90_0.zip" "boost_1_90_0.zip" "boost_1_90_0" "boost") + +# 7) FreeType +check_and_download("https://download.savannah.gnu.org/releases/freetype/freetype-2.14.1.tar.gz" "freetype-2.14.1.tar.gz" "freetype-2.14.1" "CMakeLists.txt") + +# 8) SDL_ttf +check_and_download("https://github.com/libsdl-org/SDL_ttf/archive/refs/tags/release-2.24.0.zip" "release-2.24.0.zip" "SDL_ttf-release-2.24.0" "CMakeLists.txt") diff --git a/cmake/ThirdParty.cmake b/cmake/ThirdParty.cmake index ea8ce95..99bc399 100644 --- a/cmake/ThirdParty.cmake +++ b/cmake/ThirdParty.cmake @@ -314,7 +314,215 @@ set_target_properties(libzip_external_lib PROPERTIES ) # =========================================== -# 5) Eigen (5.0.0.zip → eigen-5.0.0) - HEADER-ONLY +# 5) FreeType (2.14.1) - dependency for SDL_ttf +# =========================================== +set(FREETYPE_SRC_DIR "${THIRDPARTY_DIR}/freetype-2.14.1") +set(FREETYPE_BASE_DIR "${FREETYPE_SRC_DIR}/install") + +set(_have_freetype TRUE) +foreach(cfg IN LISTS BUILD_CONFIGS) + if(NOT EXISTS "${FREETYPE_BASE_DIR}-${cfg}/lib/freetype.lib" AND + NOT EXISTS "${FREETYPE_BASE_DIR}-${cfg}/lib/freetyped.lib") + set(_have_freetype FALSE) + endif() +endforeach() + +if(NOT _have_freetype) + foreach(cfg IN LISTS BUILD_CONFIGS) + log("Configuring FreeType (${cfg}) ...") + execute_process( + COMMAND ${CMAKE_COMMAND} + -G "${CMAKE_GENERATOR}" + -S "${FREETYPE_SRC_DIR}" + -B "${FREETYPE_SRC_DIR}/build-${cfg}" + -DCMAKE_INSTALL_PREFIX=${FREETYPE_BASE_DIR}-${cfg} + -DCMAKE_PREFIX_PATH="${ZLIB_INSTALL_DIR};${LIBPNG_INSTALL_DIR}" + -DCMAKE_DISABLE_FIND_PACKAGE_HarfBuzz=TRUE + -DCMAKE_DISABLE_FIND_PACKAGE_BZip2=TRUE + -DFT_DISABLE_BROTLI=ON + -DBUILD_SHARED_LIBS=OFF + RESULT_VARIABLE _ft_cfg_res + ) + if(NOT _ft_cfg_res EQUAL 0) + message(FATAL_ERROR "FreeType configure failed for ${cfg}") + endif() + + log("Building FreeType (${cfg}) ...") + execute_process( + COMMAND ${CMAKE_COMMAND} + --build "${FREETYPE_SRC_DIR}/build-${cfg}" --config ${cfg} + RESULT_VARIABLE _ft_build_res + ) + if(NOT _ft_build_res EQUAL 0) + message(FATAL_ERROR "FreeType build failed for ${cfg}") + endif() + + log("Installing FreeType (${cfg}) ...") + execute_process( + COMMAND ${CMAKE_COMMAND} + --install "${FREETYPE_SRC_DIR}/build-${cfg}" --config ${cfg} + RESULT_VARIABLE _ft_inst_res + ) + if(NOT _ft_inst_res EQUAL 0) + message(FATAL_ERROR "FreeType install failed for ${cfg}") + endif() + endforeach() +endif() + +set(_ft_debug_lib "") +foreach(cand + "${FREETYPE_BASE_DIR}-Debug/lib/freetyped.lib" + "${FREETYPE_BASE_DIR}-Debug/lib/freetype.lib" +) + if(EXISTS "${cand}") + set(_ft_debug_lib "${cand}") + break() + endif() +endforeach() + +set(_ft_release_lib "") +foreach(cand + "${FREETYPE_BASE_DIR}-Release/lib/freetype.lib" +) + if(EXISTS "${cand}") + set(_ft_release_lib "${cand}") + break() + endif() +endforeach() + +if(_ft_debug_lib STREQUAL "" OR _ft_release_lib STREQUAL "") + message(FATAL_ERROR "FreeType libs not found in ${FREETYPE_BASE_DIR}-Debug/Release") +endif() + +add_library(freetype_external_lib UNKNOWN IMPORTED GLOBAL) +set_target_properties(freetype_external_lib PROPERTIES + IMPORTED_LOCATION_DEBUG "${_ft_debug_lib}" + IMPORTED_LOCATION_RELEASE "${_ft_release_lib}" + INTERFACE_INCLUDE_DIRECTORIES + "$,${FREETYPE_BASE_DIR}-Debug/include,${FREETYPE_BASE_DIR}-Release/include>" +) + +# =========================================== +# 6) SDL_ttf (2.24.0) +# =========================================== +set(SDL2TTF_SRC_DIR "${THIRDPARTY_DIR}/SDL_ttf-release-2.24.0") +set(SDL2TTF_BASE_DIR "${SDL2TTF_SRC_DIR}/install") + +set(_have_sdl2ttf TRUE) +foreach(cfg IN LISTS BUILD_CONFIGS) + if(NOT EXISTS "${SDL2TTF_BASE_DIR}-${cfg}/lib/SDL2_ttf.lib" AND + NOT EXISTS "${SDL2TTF_BASE_DIR}-${cfg}/lib/SDL2_ttfd.lib") + set(_have_sdl2ttf FALSE) + endif() +endforeach() + +if(NOT _have_sdl2ttf) + foreach(cfg IN LISTS BUILD_CONFIGS) + + if(cfg STREQUAL "Debug") + set(_SDL2_LIB "${SDL2_INSTALL_DIR}/lib/SDL2d.lib") + else() + set(_SDL2_LIB "${SDL2_INSTALL_DIR}/lib/SDL2.lib") + endif() + + set(_FT_PREFIX "${FREETYPE_BASE_DIR}-${cfg}") + + set(_FT_LIB "") + foreach(cand + "${_FT_PREFIX}/lib/freetyped.lib" + "${_FT_PREFIX}/lib/freetype.lib" + ) + if(EXISTS "${cand}") + set(_FT_LIB "${cand}") + break() + endif() + endforeach() + + if(_FT_LIB STREQUAL "") + message(FATAL_ERROR "FreeType library not found for ${cfg}") + endif() + + log("Configuring SDL_ttf (${cfg}) ...") + execute_process( + COMMAND ${CMAKE_COMMAND} + -G "${CMAKE_GENERATOR}" + -S "${SDL2TTF_SRC_DIR}" + -B "${SDL2TTF_SRC_DIR}/build-${cfg}" + -DCMAKE_INSTALL_PREFIX=${SDL2TTF_BASE_DIR}-${cfg} + -DCMAKE_PREFIX_PATH=${_FT_PREFIX};${SDL2_INSTALL_DIR} + -DSDL2_LIBRARY=${_SDL2_LIB} + -DSDL2_INCLUDE_DIR=${SDL2_INSTALL_DIR}/include/SDL2 + -DFREETYPE_LIBRARY=${_FT_LIB} + -DFREETYPE_INCLUDE_DIR=${_FT_PREFIX}/include + -DFREETYPE_INCLUDE_DIRS=${_FT_PREFIX}/include + -DSDL2TTF_VENDORED=OFF + -DSDL2TTF_SAMPLES=OFF + RESULT_VARIABLE _ttf_cfg_res + ) + if(NOT _ttf_cfg_res EQUAL 0) + message(FATAL_ERROR "SDL_ttf configure failed for ${cfg}") + endif() + + log("Building SDL_ttf (${cfg}) ...") + execute_process( + COMMAND ${CMAKE_COMMAND} + --build "${SDL2TTF_SRC_DIR}/build-${cfg}" --config ${cfg} + RESULT_VARIABLE _ttf_build_res + ) + if(NOT _ttf_build_res EQUAL 0) + message(FATAL_ERROR "SDL_ttf build failed for ${cfg}") + endif() + + log("Installing SDL_ttf (${cfg}) ...") + execute_process( + COMMAND ${CMAKE_COMMAND} + --install "${SDL2TTF_SRC_DIR}/build-${cfg}" --config ${cfg} + RESULT_VARIABLE _ttf_inst_res + ) + if(NOT _ttf_inst_res EQUAL 0) + message(FATAL_ERROR "SDL_ttf install failed for ${cfg}") + endif() + + endforeach() +endif() + +set(_ttf_debug_lib "") +foreach(cand + "${SDL2TTF_BASE_DIR}-Debug/lib/SDL2_ttfd.lib" + "${SDL2TTF_BASE_DIR}-Debug/lib/SDL2_ttf.lib" +) + if(EXISTS "${cand}") + set(_ttf_debug_lib "${cand}") + break() + endif() +endforeach() + +set(_ttf_release_lib "") +foreach(cand + "${SDL2TTF_BASE_DIR}-Release/lib/SDL2_ttf.lib" +) + if(EXISTS "${cand}") + set(_ttf_release_lib "${cand}") + break() + endif() +endforeach() + +if(_ttf_debug_lib STREQUAL "" OR _ttf_release_lib STREQUAL "") + message(FATAL_ERROR "SDL_ttf libs not found in install-Debug / install-Release") +endif() + +add_library(SDL2_ttf_external_lib UNKNOWN IMPORTED GLOBAL) +set_target_properties(SDL2_ttf_external_lib PROPERTIES + IMPORTED_LOCATION_DEBUG "${_ttf_debug_lib}" + IMPORTED_LOCATION_RELEASE "${_ttf_release_lib}" + INTERFACE_INCLUDE_DIRECTORIES + "$,${SDL2TTF_BASE_DIR}-Debug/include,${SDL2TTF_BASE_DIR}-Release/include>;$,${SDL2TTF_BASE_DIR}-Debug/include/SDL2,${SDL2TTF_BASE_DIR}-Release/include/SDL2>" + INTERFACE_LINK_LIBRARIES + "SDL2_external_lib;freetype_external_lib" +) + +# =========================================== +# 7) Eigen (5.0.0.zip → eigen-5.0.0) - HEADER-ONLY # =========================================== set(EIGEN_SRC_DIR "${THIRDPARTY_DIR}/eigen-5.0.0") @@ -324,7 +532,7 @@ if(NOT TARGET eigen_external_lib) endif() # =========================================== -# 6) Boost (1.90.0) - HEADER-ONLY +# 8) Boost (1.90.0) - HEADER-ONLY # =========================================== set(BOOST_VERSION "1.90.0") set(BOOST_ARCHIVE_NAME "boost_1_90_0.zip") diff --git a/proj-windows/CMakeLists.txt b/proj-windows/CMakeLists.txt index 0f68441..a63029e 100644 --- a/proj-windows/CMakeLists.txt +++ b/proj-windows/CMakeLists.txt @@ -83,6 +83,8 @@ target_link_libraries(space-game001 PRIVATE libpng_external_lib zlib_external_lib libzip_external_lib + freetype_external_lib + SDL2_ttf_external_lib eigen_external_lib boost_external_lib ) @@ -106,6 +108,8 @@ if (WIN32) set(ZLIB_DLL_SRC "$,${ZLIB_INSTALL_DIR}/bin/zlibd.dll,${ZLIB_INSTALL_DIR}/bin/zlib.dll>") set(ZLIB_DLL_DST "$,$/zlibd.dll,$/zlib.dll>") + + set(SDL2TTF_DLL_SRC "$,${SDL2TTF_BASE_DIR}-Debug/bin/SDL2_ttfd.dll,${SDL2TTF_BASE_DIR}-Release/bin/SDL2_ttf.dll>") add_custom_command(TARGET space-game001 POST_BUILD @@ -124,6 +128,10 @@ if (WIN32) COMMAND ${CMAKE_COMMAND} -E copy_if_different "${ZLIB_DLL_SRC}" "${ZLIB_DLL_DST}" + + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${SDL2TTF_DLL_SRC}" + "$" ) endif() From e0453db063320a542dc55afb15dc1f9df6d2c797 Mon Sep 17 00:00:00 2001 From: Vladislav Khorev Date: Fri, 16 Jan 2026 00:01:44 +0300 Subject: [PATCH 08/18] Working on implementing server side physics on gam --- server/server.cpp | 2 +- src/Game.cpp | 29 +++++++++++++------- src/Game.h | 4 +-- src/network/ClientState.h | 5 ++-- src/network/NetworkInterface.h | 4 +-- src/network/RemotePlayer.h | 13 --------- src/network/WebSocketClient.cpp | 48 +++++++++------------------------ src/network/WebSocketClient.h | 6 ++--- 8 files changed, 42 insertions(+), 69 deletions(-) diff --git a/server/server.cpp b/server/server.cpp index 635d3c8..a991dee 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -64,7 +64,7 @@ class Session : public std::enable_shared_from_this { if (dt_server > 0) state_.simulate_physics(dt_server); - using time_point = std::chrono::steady_clock::time_point; + std::chrono::steady_clock::time_point uptime_timepoint{ std::chrono::duration_cast(std::chrono::milliseconds(clientTimestamp)) }; state_.lastUpdateServerTime = uptime_timepoint; diff --git a/src/Game.cpp b/src/Game.cpp index 447f401..8ab04c9 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -568,7 +568,7 @@ namespace ZL sparkEmitter.update(static_cast(delta)); planetObject.update(static_cast(delta)); - updateRemotePlayers(static_cast(delta)); + extrapolateRemotePlayers(static_cast(delta)); static float pingTimer = 0.0f; pingTimer += delta; @@ -1052,21 +1052,30 @@ namespace ZL } } - void Game::updateRemotePlayers(float deltaMs) { + void Game::extrapolateRemotePlayers(float deltaMs) { + + auto now = std::chrono::steady_clock::now(); latestRemotePlayers = networkClient->getRemotePlayers(); for (auto& [id, rp] : latestRemotePlayers) { - // Увеличиваем фактор интерполяции (базируется на частоте Snapshot = 1000мс) - rp.interpolationFactor += deltaMs / 1000.0f; - if (rp.interpolationFactor > 1.0f) rp.interpolationFactor = 1.0f; + // 1. Рассчитываем, сколько времени прошло с момента получения последнего пакета (в секундах) + float age_s = std::chrono::duration(now - rp.lastUpdateServerTime).count(); - // Линейная интерполяция позиции - rp.state.position = rp.startPosition + (rp.targetPosition - rp.startPosition) * rp.interpolationFactor; + // Ограничим экстраполяцию (например, не более 2 секунд), + // чтобы в случае лага корабли не улетали в бесконечность + if (age_s > 2.0f) age_s = 2.0f; - // Сферическая интерполяция вращения (Slerp) - Eigen::Quaternionf currentQ = rp.startRotation.slerp(rp.interpolationFactor, rp.targetRotation); - rp.state.rotation = currentQ.toRotationMatrix(); + // 2. Сбрасываем физическое состояние rp.state в значения из последнего пакета + // (Это важно: мы всегда экстраполируем от последнего достоверного серверного состояния) + // В WebSocketClient::updateRemotePlayer нужно убедиться, что rp.state обновляется данными из пакета. + + // 3. Вызываем физику, чтобы "догнать" реальное время + // Мы передаем age_s как один большой шаг симуляции + rp.simulate_physics(age_s); + + // Теперь rp.state.position и rp.state.rotation содержат актуальные + // предсказанные данные для рендеринга в текущем кадре. } } diff --git a/src/Game.h b/src/Game.h index d66d654..83d9b36 100644 --- a/src/Game.h +++ b/src/Game.h @@ -51,7 +51,7 @@ namespace ZL { void handleUp(int mx, int my); void handleMotion(int mx, int my); - void updateRemotePlayers(float deltaMs); + void extrapolateRemotePlayers(float deltaMs); SDL_Window* window; SDL_GLContext glContext; @@ -64,7 +64,7 @@ namespace ZL { std::vector boxCoordsArr; std::vector boxRenderArr; - std::unordered_map latestRemotePlayers; + std::unordered_map latestRemotePlayers; static const size_t CONST_TIMER_INTERVAL = 10; static const size_t CONST_MAX_TIME_INTERVAL = 1000; diff --git a/src/network/ClientState.h b/src/network/ClientState.h index edf77c2..267edb3 100644 --- a/src/network/ClientState.h +++ b/src/network/ClientState.h @@ -20,7 +20,6 @@ struct ClientState { int discreteAngle = -1; // Для расчета лага - uint64_t lastClientTimestamp = 0; std::chrono::steady_clock::time_point lastUpdateServerTime; void simulate_physics(float dt_s) { @@ -103,14 +102,14 @@ struct ClientState { ).count(); // 2. Вычисляем задержку - double lag_ms = 0.0; + float lag_ms = 0.0; if (nowTime > lastUpdateServerTime) { lag_ms = std::chrono::duration(nowTime - lastUpdateServerTime).count(); } // 3. Защита от слишком больших скачков (Clamp) // Если лаг более 500мс, ограничиваем его, чтобы избежать резких рывков - float final_lag_s = min(static_cast(lag_ms), 0.5f); + float final_lag_s = min(lag_ms, 0.5f); if (final_lag_s > 0.001f) { // Доматываем симуляцию на величину задержки diff --git a/src/network/NetworkInterface.h b/src/network/NetworkInterface.h index 2691be4..35080bf 100644 --- a/src/network/NetworkInterface.h +++ b/src/network/NetworkInterface.h @@ -1,7 +1,7 @@ #pragma once #include #include -#include "RemotePlayer.h" +#include "ClientState.h" // NetworkInterface.h - namespace ZL { @@ -12,6 +12,6 @@ namespace ZL { virtual void Send(const std::string& message) = 0; virtual bool IsConnected() const = 0; virtual void Poll() = 0; // - virtual std::unordered_map getRemotePlayers() = 0; + virtual std::unordered_map getRemotePlayers() = 0; }; } diff --git a/src/network/RemotePlayer.h b/src/network/RemotePlayer.h index ff9bb8d..5a1664d 100644 --- a/src/network/RemotePlayer.h +++ b/src/network/RemotePlayer.h @@ -1,15 +1,2 @@ #pragma once #include "ClientState.h" - -struct RemotePlayer { - ClientState state; - - // Данные для интерполяции - Eigen::Vector3f startPosition; - Eigen::Vector3f targetPosition; - Eigen::Quaternionf startRotation; - Eigen::Quaternionf targetRotation; - - float interpolationFactor = 0.0f; - uint64_t lastSnapshotTime = 0; -}; \ No newline at end of file diff --git a/src/network/WebSocketClient.cpp b/src/network/WebSocketClient.cpp index 276ea57..04c0b29 100644 --- a/src/network/WebSocketClient.cpp +++ b/src/network/WebSocketClient.cpp @@ -96,52 +96,30 @@ namespace ZL { if (id == this->clientId) continue; // // RemotePlayer - updateRemotePlayer(id, vals); + updateRemotePlayer(id, vals, serverTime); } } - void WebSocketClient::updateRemotePlayer(int id, const std::vector& vals) { + void WebSocketClient::updateRemotePlayer(int id, const std::vector& vals, uint64_t serverTime) { // , (TaskManager) std::lock_guard lock(playersMutex); auto& rp = remotePlayers[id]; - rp.state.id = id; + rp.id = id; - // 1. - // "", 1.0 - if (rp.lastSnapshotTime == 0) { - // - rp.startPosition = { std::stof(vals[1]), std::stof(vals[2]), std::stof(vals[3]) }; - rp.startRotation = Eigen::Quaternionf(std::stof(vals[4]), std::stof(vals[5]), std::stof(vals[6]), std::stof(vals[7])); - } - else { - // , , - rp.startPosition = rp.state.position; - rp.startRotation = Eigen::Quaternionf(rp.state.rotation); - } - - // 2. - rp.targetPosition = { std::stof(vals[1]), std::stof(vals[2]), std::stof(vals[3]) }; - rp.targetRotation = Eigen::Quaternionf( - std::stof(vals[4]), // w - std::stof(vals[5]), // x - std::stof(vals[6]), // y - std::stof(vals[7]) // z - ); + rp.position = { std::stof(vals[1]), std::stof(vals[2]), std::stof(vals[3]) }; + rp.rotation = Eigen::Quaternionf(std::stof(vals[4]), std::stof(vals[5]), std::stof(vals[6]), std::stof(vals[7])); // 3. ( ) - rp.state.velocity = std::stof(vals[8]); - rp.state.currentAngularVelocity = { std::stof(vals[9]), std::stof(vals[10]), std::stof(vals[11]) }; - rp.state.selectedVelocity = std::stoi(vals[12]); - rp.state.discreteMag = std::stoi(vals[13]); - rp.state.discreteAngle = std::stoi(vals[14]); + rp.velocity = std::stof(vals[8]); + rp.currentAngularVelocity = { std::stof(vals[9]), std::stof(vals[10]), std::stof(vals[11]) }; + rp.selectedVelocity = std::stoi(vals[12]); + rp.discreteMag = std::stoi(vals[13]); + rp.discreteAngle = std::stoi(vals[14]); + std::chrono::steady_clock::time_point uptime_timepoint{ std::chrono::duration_cast(std::chrono::milliseconds(serverTime)) }; + + rp.lastUpdateServerTime = uptime_timepoint; - // 4. - rp.interpolationFactor = 0.0f; - auto now_ms = std::chrono::duration_cast( - std::chrono::steady_clock::now().time_since_epoch() - ).count(); - rp.lastSnapshotTime = now_ms; } diff --git a/src/network/WebSocketClient.h b/src/network/WebSocketClient.h index 5f18ced..098088e 100644 --- a/src/network/WebSocketClient.h +++ b/src/network/WebSocketClient.h @@ -28,7 +28,7 @@ namespace ZL { bool connected = false; int clientId = -1; - std::unordered_map remotePlayers; + std::unordered_map remotePlayers; std::mutex playersMutex; void startAsyncRead(); @@ -41,7 +41,7 @@ namespace ZL { void Poll() override; void parseWorldUpdate(const std::string& msg); - void updateRemotePlayer(int id, const std::vector& vals); + void updateRemotePlayer(int id, const std::vector& vals, uint64_t serverTime); void Send(const std::string& message) override; void doWrite(); @@ -49,7 +49,7 @@ namespace ZL { bool IsConnected() const override { return connected; } int GetClientId() const { return clientId; } - std::unordered_map getRemotePlayers() override { + std::unordered_map getRemotePlayers() override { std::lock_guard lock(playersMutex); return remotePlayers; } From ff95163bcd263fe6c458437eb61bf9ab429eaf3b Mon Sep 17 00:00:00 2001 From: Vladislav Khorev Date: Fri, 16 Jan 2026 18:11:52 +0300 Subject: [PATCH 09/18] Yee render works --- src/Game.cpp | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/Game.h | 2 +- 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/src/Game.cpp b/src/Game.cpp index 8ab04c9..fe8cab4 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -548,12 +548,71 @@ namespace ZL glClear(GL_DEPTH_BUFFER_BIT); } drawShip(); + drawRemoteShips(); drawBoxes(); drawUI(); CheckGlError(); } + void Game::drawRemoteShips() { + // Используем те же константы имен для шейдеров, что и в drawShip + static const std::string defaultShaderName = "default"; + static const std::string vPositionName = "vPosition"; + static const std::string vTexCoordName = "vTexCoord"; + static const std::string textureUniformName = "Texture"; + + // Активируем шейдер и текстуру (предполагаем, что меш у всех одинаковый) + renderer.shaderManager.PushShader(defaultShaderName); + renderer.RenderUniform1i(textureUniformName, 0); + + renderer.EnableVertexAttribArray(vPositionName); + renderer.EnableVertexAttribArray(vTexCoordName); + + renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5, + static_cast(Environment::width) / static_cast(Environment::height), + Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR); + + // Биндим текстуру корабля один раз для всех удаленных игроков (оптимизация батчинга) + glBindTexture(GL_TEXTURE_2D, spaceshipTexture->getTexID()); + + // Итерируемся по актуальным данным из extrapolateRemotePlayers + for (auto const& [id, playerState] : latestRemotePlayers) { + //if (id == networkClient->GetClientId()) continue; // Не рисуем себя через этот цикл + + renderer.PushMatrix(); + renderer.LoadIdentity(); + + // 1. Камера и вид игрока + renderer.TranslateMatrix({ 0, 0, -1.0f * Environment::zoom }); + renderer.RotateMatrix(Environment::inverseShipMatrix); + + // 2. Относительная позиция (ОЧЕНЬ ВАЖНО) + // Мы перемещаем объект в позицию (Враг - Я) + // Если враг в 44928, а я в 45000, результат будет -72 по Z. + // Поскольку камера смотрит в -Z, корабль должен быть ПЕРЕД нами. + Eigen::Vector3f relativePos = playerState.position - Environment::shipPosition; + renderer.TranslateMatrix(relativePos); + + // 3. Поворот врага + renderer.RotateMatrix(playerState.rotation); + + // 4. Смещение самого меша (если оно есть в drawShip, оно должно быть и тут) + // В твоем drawShip() есть: renderer.TranslateMatrix({ 0, -6.f, 0 }); + renderer.TranslateMatrix({ 0, -6.f, 0 }); + + renderer.DrawVertexRenderStruct(spaceship); + renderer.PopMatrix(); + } + + renderer.PopProjectionMatrix(); + renderer.DisableVertexAttribArray(vPositionName); + renderer.DisableVertexAttribArray(vTexCoordName); + renderer.shaderManager.PopShader(); + + CheckGlError(); + } + void Game::processTickCount() { if (lastTickCount == 0) { diff --git a/src/Game.h b/src/Game.h index 83d9b36..5724a1e 100644 --- a/src/Game.h +++ b/src/Game.h @@ -44,7 +44,7 @@ namespace ZL { void drawShip(); void drawBoxes(); void drawUI(); - + void drawRemoteShips(); void fireProjectiles(); void handleDown(int mx, int my); From fbc420e8943b0a4a265aa2cdafc64f8c91161d27 Mon Sep 17 00:00:00 2001 From: Vladislav Khorev Date: Sat, 17 Jan 2026 11:05:04 +0300 Subject: [PATCH 10/18] Propagate ROT and VEL --- server/server.cpp | 16 +++++++++++ src/Game.cpp | 44 +++++++++++------------------- src/network/WebSocketClient.cpp | 47 +++++++++++++++++++++++++++++++-- 3 files changed, 76 insertions(+), 31 deletions(-) diff --git a/server/server.cpp b/server/server.cpp index a991dee..81dde92 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -74,10 +74,12 @@ class Session : public std::enable_shared_from_this { state_.discreteAngle = std::stoi(parts[2]); state_.discreteMag = std::stof(parts[3]); state_.apply_lag_compensation(now_server); + retranslateMessage(msg); } else if (parts[0] == "VEL") { state_.selectedVelocity = std::stoi(parts[2]); state_.apply_lag_compensation(now_server); + retranslateMessage(msg); } else if (parts[0] == "PING") { state_.handle_full_sync(parts); @@ -85,7 +87,17 @@ class Session : public std::enable_shared_from_this { } } + 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); + } + } + } public: @@ -141,6 +153,10 @@ public: ws_.async_write(net::buffer(*ss), [ss](beast::error_code, std::size_t) {}); } + 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) { diff --git a/src/Game.cpp b/src/Game.cpp index fe8cab4..6ff3bc4 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -768,8 +768,21 @@ namespace ZL } else { // Если джойстик не зажат — сбрасываем дискретные значения и плавно замедляем вращение - Environment::lastSentAngle = -1; - Environment::lastSentMagnitude = 0.0f; + int discreteAngle = -1; + float discreteMag = 0.0f; + + if (discreteAngle != Environment::lastSentAngle || discreteMag != Environment::lastSentMagnitude) { + Environment::lastSentAngle = discreteAngle; + Environment::lastSentMagnitude = discreteMag; + auto now_ms = std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch() + ).count(); + + // Формируем сетевой пакет + // Нам нужно отправить: дискретный угол, дискретную силу и текущую матрицу/позицию для синхронизации + std::string msg = "ROT:" + std::to_string(now_ms) + ":" + std::to_string(discreteAngle) + ":" + std::to_string(discreteMag); + networkClient->Send(msg); + } float currentSpeed = Environment::currentAngularVelocity.norm(); @@ -802,33 +815,6 @@ namespace ZL Environment::shipPosition = Environment::shipPosition + velocityDirectionAdjusted; } - /* - if (Environment::tapDownHold) { - - float diffx = Environment::tapDownCurrentPos(0) - Environment::tapDownStartPos(0); - float diffy = Environment::tapDownCurrentPos(1) - Environment::tapDownStartPos(1); - - if (abs(diffy) > 5.0 || abs(diffx) > 5.0) //threshold - { - - float rotationPower = sqrtf(diffx * diffx + diffy * diffy); - float deltaAlpha = rotationPower * delta * static_cast(M_PI) / 500000.f; - - Eigen::Vector3f rotationDirection(diffy, diffx, 0.0f); - rotationDirection.normalize(); // Eigen-way нормализация - - // Создаем кватернион через AngleAxis - // Конструктор принимает (угол_в_радианах, ось_вращения) - Eigen::Quaternionf rotateQuat(Eigen::AngleAxisf(deltaAlpha, rotationDirection)); - - Matrix3f rotateMat = rotateQuat.toRotationMatrix(); - - Environment::shipMatrix = Environment::shipMatrix * rotateMat; - Environment::inverseShipMatrix = Environment::shipMatrix.inverse(); - - } - }*/ - for (auto& p : projectiles) { if (p && p->isActive()) { p->update(static_cast(delta), renderer); diff --git a/src/network/WebSocketClient.cpp b/src/network/WebSocketClient.cpp index 04c0b29..0d8046e 100644 --- a/src/network/WebSocketClient.cpp +++ b/src/network/WebSocketClient.cpp @@ -68,13 +68,56 @@ namespace ZL { void WebSocketClient::Poll() { std::lock_guard lock(queueMutex); + + auto now_ms = std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch() + ).count(); + while (!messageQueue.empty()) { std::string msg = messageQueue.front(); messageQueue.pop(); - if (msg.rfind("WORLD_UPDATE|", 0) == 0) { + if (msg.rfind("EVENT:", 0) == 0) { + auto parts = split(msg, ':'); + if (parts.size() < 5) continue; // EVENT:ID:TYPE:TIME:DATA... + + int remoteId = std::stoi(parts[1]); + std::string subType = parts[2]; + uint64_t sentTime = std::stoull(parts[3]); + + std::lock_guard pLock(playersMutex); + if (remotePlayers.count(remoteId)) { + auto& rp = remotePlayers[remoteId]; + + if (subType == "VEL") { + rp.selectedVelocity = std::stoi(parts[4]); + } + else if (subType == "ROT") { + rp.discreteAngle = std::stoi(parts[4]); + rp.discreteMag = std::stof(parts[5]); + } + + // --- --- + // + float lag_s = static_cast(now_ms - sentTime) / 1000.0f; + + // ( ) + lag_s = std::max(0.0f, std::min(lag_s, 1.0f)); + + if (lag_s > 0.001f) { + // "" + // ClientState.h simulate_physics + // discreteAngle/Mag + rp.simulate_physics(lag_s); + } + + // , Game.cpp + // , now_ms + rp.lastUpdateServerTime = std::chrono::steady_clock::now(); + } + } + else if (msg.rfind("WORLD_UPDATE|", 0) == 0) { parseWorldUpdate(msg); - std::cout << msg << std::endl; } } } From f967807296ee2883dd131511b09415c874743596 Mon Sep 17 00:00:00 2001 From: Vladislav Khorev Date: Sat, 17 Jan 2026 12:43:41 +0300 Subject: [PATCH 11/18] Fixing bugs, unify velocity --- server/server.cpp | 23 +++--- src/Game.cpp | 27 +++++-- src/Game.h | 2 +- src/network/ClientState.h | 132 +++++++++++++++++++++++++------- src/network/NetworkInterface.h | 1 + src/network/WebSocketClient.cpp | 13 ++-- src/network/WebSocketClient.h | 9 +++ 7 files changed, 153 insertions(+), 54 deletions(-) diff --git a/server/server.cpp b/server/server.cpp index 81dde92..067076a 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -57,13 +57,13 @@ class Session : public std::enable_shared_from_this { uint64_t clientTimestamp = std::stoull(parts[1]); // " " - float dt_server = 0.0f; + long long deltaMs = 0.0f; if (state_.lastUpdateServerTime.time_since_epoch().count() > 0) { - dt_server = (clientTimestamp - now_ms) * 1000.f; + deltaMs = (clientTimestamp - now_ms); } - if (dt_server > 0) state_.simulate_physics(dt_server); + if (deltaMs > 0) state_.simulate_physics(deltaMs); std::chrono::steady_clock::time_point uptime_timepoint{ std::chrono::duration_cast(std::chrono::milliseconds(clientTimestamp)) }; state_.lastUpdateServerTime = uptime_timepoint; @@ -74,16 +74,19 @@ class Session : public std::enable_shared_from_this { state_.discreteAngle = std::stoi(parts[2]); state_.discreteMag = std::stof(parts[3]); state_.apply_lag_compensation(now_server); + state_.lastUpdateServerTime = now_server; retranslateMessage(msg); } else if (parts[0] == "VEL") { state_.selectedVelocity = std::stoi(parts[2]); state_.apply_lag_compensation(now_server); + state_.lastUpdateServerTime = now_server; retranslateMessage(msg); } else if (parts[0] == "PING") { state_.handle_full_sync(parts); state_.apply_lag_compensation(now_server); + state_.lastUpdateServerTime = now_server; } } @@ -131,23 +134,21 @@ public: } void tick_physics_global(std::chrono::steady_clock::time_point now) { - float dt = 0.0f; + long long deltaMs = 0; // , if (state_.lastUpdateServerTime.time_since_epoch().count() > 0) { - dt = std::chrono::duration(now - state_.lastUpdateServerTime).count(); + deltaMs = std::chrono::duration_cast( + now - state_.lastUpdateServerTime + ).count(); } - if (dt > 0.0001f) { - state_.simulate_physics(dt); + if (deltaMs > 0) { + state_.simulate_physics(deltaMs); state_.lastUpdateServerTime = now; // } } - void tick_physics(float dt_s) { - state_.simulate_physics(dt_s); - } - 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) {}); diff --git a/src/Game.cpp b/src/Game.cpp index 6ff3bc4..1d726cf 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -627,7 +627,7 @@ namespace ZL sparkEmitter.update(static_cast(delta)); planetObject.update(static_cast(delta)); - extrapolateRemotePlayers(static_cast(delta)); + extrapolateRemotePlayers(); static float pingTimer = 0.0f; pingTimer += delta; @@ -659,10 +659,11 @@ namespace ZL + std::to_string(Environment::lastSentAngle); networkClient->Send(pingMsg); + std::cout << "Sending: " << pingMsg << std::endl; pingTimer = 0.0f; } - static const float CONST_ACCELERATION = 1.f; + //static const float CONST_ACCELERATION = 1.f; float shipDesiredVelocity = Environment::shipSelectedVelocity * 100.f; @@ -670,7 +671,7 @@ namespace ZL { if (Environment::shipVelocity < shipDesiredVelocity) { - Environment::shipVelocity += delta * CONST_ACCELERATION; + Environment::shipVelocity += delta * SHIP_ACCEL; if (Environment::shipVelocity > shipDesiredVelocity) { Environment::shipVelocity = shipDesiredVelocity; @@ -678,7 +679,7 @@ namespace ZL } else if (Environment::shipVelocity > shipDesiredVelocity) { - Environment::shipVelocity -= delta * CONST_ACCELERATION; + Environment::shipVelocity -= delta * SHIP_ACCEL; if (Environment::shipVelocity < shipDesiredVelocity) { Environment::shipVelocity = shipDesiredVelocity; @@ -784,6 +785,7 @@ namespace ZL networkClient->Send(msg); } + float currentSpeed = Environment::currentAngularVelocity.norm(); if (currentSpeed > 0.0001f) { @@ -807,6 +809,7 @@ namespace ZL } } + std::cout << "shipVelocity=" << Environment::shipVelocity << " delta=" << delta << std::endl; // Движение вперед (существующая логика) if (fabs(Environment::shipVelocity) > 0.01f) { @@ -1097,7 +1100,7 @@ namespace ZL } } - void Game::extrapolateRemotePlayers(float deltaMs) { + void Game::extrapolateRemotePlayers() { auto now = std::chrono::steady_clock::now(); @@ -1105,11 +1108,16 @@ namespace ZL for (auto& [id, rp] : latestRemotePlayers) { // 1. Рассчитываем, сколько времени прошло с момента получения последнего пакета (в секундах) - float age_s = std::chrono::duration(now - rp.lastUpdateServerTime).count(); + + auto deltaMs = std::chrono::duration_cast( + now - rp.lastUpdateServerTime + ).count(); + + //float age_s = std::chrono::duration(now - rp.lastUpdateServerTime).count(); // Ограничим экстраполяцию (например, не более 2 секунд), // чтобы в случае лага корабли не улетали в бесконечность - if (age_s > 2.0f) age_s = 2.0f; + if (deltaMs > 2000) deltaMs = 2000; // 2. Сбрасываем физическое состояние rp.state в значения из последнего пакета // (Это важно: мы всегда экстраполируем от последнего достоверного серверного состояния) @@ -1117,11 +1125,14 @@ namespace ZL // 3. Вызываем физику, чтобы "догнать" реальное время // Мы передаем age_s как один большой шаг симуляции - rp.simulate_physics(age_s); + rp.simulate_physics(deltaMs); + rp.lastUpdateServerTime = now; // Теперь rp.state.position и rp.state.rotation содержат актуальные // предсказанные данные для рендеринга в текущем кадре. } + + networkClient->updateRemotePlayers(latestRemotePlayers); } diff --git a/src/Game.h b/src/Game.h index 5724a1e..296f654 100644 --- a/src/Game.h +++ b/src/Game.h @@ -51,7 +51,7 @@ namespace ZL { void handleUp(int mx, int my); void handleMotion(int mx, int my); - void extrapolateRemotePlayers(float deltaMs); + void extrapolateRemotePlayers(); SDL_Window* window; SDL_GLContext glContext; diff --git a/src/network/ClientState.h b/src/network/ClientState.h index 267edb3..75c9c09 100644 --- a/src/network/ClientState.h +++ b/src/network/ClientState.h @@ -3,12 +3,16 @@ #include #define _USE_MATH_DEFINES #include - +#include using std::min; using std::max; +constexpr float ANGULAR_ACCEL = 0.005f * 1000.0f; +constexpr float SHIP_ACCEL = 1.0f * 1000.0f; +constexpr float ROTATION_SENSITIVITY = 0.002f; + struct ClientState { int id; Eigen::Vector3f position = { 0, 0, 45000.0f }; @@ -22,13 +26,70 @@ struct ClientState { // Для расчета лага std::chrono::steady_clock::time_point lastUpdateServerTime; - void simulate_physics(float dt_s) { + void simulate_physics(size_t delta) { // Константы из Game.cpp, приведенные к секундам (умножаем на 1000) - const float ANGULAR_ACCEL = 0.005f * 1000.0f; - const float ROTATION_SENSITIVITY = 0.002f * 1000.0f; - const float SHIP_ACCEL = 1.0f * 1000.0f; // CONST_ACCELERATION + //const float ANGULAR_ACCEL = 0.005f * 1000.0f; + //const float ROTATION_SENSITIVITY = 0.002f; + //const float SHIP_ACCEL = 1.0f * 1000.0f; // CONST_ACCELERATION // 1. Вычисляем targetAngularVelocity на лету из дискретных значений + + if (discreteMag > 0.01f) + { + float rad = static_cast(discreteAngle) * static_cast(M_PI) / 180.0f; + + // Целевая угловая скорость (дискретная сила определяет модуль вектора) + // Вектор {cos, sin, 0} дает нам направление отклонения джойстика + Eigen::Vector3f targetAngularVelDir(sinf(rad), cosf(rad), 0.0f); + Eigen::Vector3f targetAngularVelocity = targetAngularVelDir * discreteMag; + + Eigen::Vector3f diffVel = targetAngularVelocity - currentAngularVelocity; + float diffLen = diffVel.norm(); + + if (diffLen > 0.0001f) { + // Вычисляем, на сколько мы можем изменить скорость в этом кадре + float maxChange = ANGULAR_ACCEL * static_cast(delta); + + if (diffLen <= maxChange) { + // Если до цели осталось меньше, чем шаг ускорения — просто прыгаем в цель + currentAngularVelocity = targetAngularVelocity; + } + + else { + // Линейно двигаемся в сторону целевого вектора + currentAngularVelocity += (diffVel / diffLen) * maxChange; + } + } + } + else + { + float currentSpeed = currentAngularVelocity.norm(); + + if (currentSpeed > 0.0001f) { + float drop = ANGULAR_ACCEL * static_cast(delta); + if (currentSpeed <= drop) { + currentAngularVelocity = Eigen::Vector3f::Zero(); + } + else { + // Уменьшаем модуль вектора, сохраняя направление + currentAngularVelocity -= (currentAngularVelocity / currentSpeed) * drop; + } + } + } + + float speedScale = currentAngularVelocity.norm(); + if (speedScale > 0.0001f) { + // Коэффициент чувствительности вращения + + float deltaAlpha = speedScale * static_cast(delta) * ROTATION_SENSITIVITY; + + Eigen::Vector3f axis = currentAngularVelocity.normalized(); + Eigen::Quaternionf rotateQuat(Eigen::AngleAxisf(deltaAlpha, axis)); + + rotation = rotation * rotateQuat.toRotationMatrix(); + } + + /* Eigen::Vector3f targetAngularVelocity = Eigen::Vector3f::Zero(); if (discreteMag > 0.001f) { @@ -61,8 +122,9 @@ struct ClientState { else { currentAngularVelocity -= (currentAngularVelocity / currentSpeed) * drop; } - } + }*/ + /* // 3. Применение вращения к матрице (Интеграция) float speedScale = currentAngularVelocity.norm(); if (speedScale > 0.0001f) { @@ -71,50 +133,62 @@ struct ClientState { Eigen::Quaternionf rotateQuat(Eigen::AngleAxisf(deltaAlpha, axis)); rotation = rotation * rotateQuat.toRotationMatrix(); - } + }*/ // 4. Линейное изменение линейной скорости - float shipDesiredVelocity = static_cast(selectedVelocity) * 100.f; - float speedDiff = shipDesiredVelocity - velocity; - if (std::abs(speedDiff) > 0.001f) { - float speedStep = SHIP_ACCEL * dt_s; - if (std::abs(speedDiff) <= speedStep) { - velocity = shipDesiredVelocity; - } - else { - velocity += (speedDiff > 0 ? 1.0f : -1.0f) * speedStep; - } - } + float shipDesiredVelocity = selectedVelocity * 100.f; - // 5. Обновление позиции - if (std::abs(velocity) > 0.01f) { - // Движение вперед по локальной оси Z (в твоем коде это {0, 0, -1}) - Eigen::Vector3f localMove(0.0f, 0.0f, -velocity * dt_s); - position += rotation * localMove; + if (velocity < shipDesiredVelocity) + { + velocity += delta * SHIP_ACCEL; + if (velocity > shipDesiredVelocity) + { + velocity = shipDesiredVelocity; + } + } + else if (velocity > shipDesiredVelocity) + { + velocity -= delta * SHIP_ACCEL; + if (velocity < shipDesiredVelocity) + { + velocity = shipDesiredVelocity; + } + } + std::cout << "velocity=" << velocity << " delta=" << delta << std::endl; + + if (fabs(velocity) > 0.01f) + { + Eigen::Vector3f velocityDirection = { 0,0, -velocity * delta / 1000.f }; + Eigen::Vector3f velocityDirectionAdjusted = rotation * velocityDirection; + position = position + velocityDirectionAdjusted; } } void apply_lag_compensation(std::chrono::steady_clock::time_point nowTime) { // 1. Получаем текущее время сервера в той же шкале (мс) - auto now_ms = std::chrono::duration_cast( + /*uto now_ms = std::chrono::duration_cast( std::chrono::steady_clock::now().time_since_epoch() ).count(); + + long long deltaMs = std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch() - lastUpdateServerTime + ).count();*/ // 2. Вычисляем задержку - float lag_ms = 0.0; + long long deltaMs = 0; if (nowTime > lastUpdateServerTime) { - lag_ms = std::chrono::duration(nowTime - lastUpdateServerTime).count(); + deltaMs = std::chrono::duration_cast(nowTime - lastUpdateServerTime).count(); } // 3. Защита от слишком больших скачков (Clamp) // Если лаг более 500мс, ограничиваем его, чтобы избежать резких рывков - float final_lag_s = min(lag_ms, 0.5f); + long long final_lag_ms = min(deltaMs, 500ll); - if (final_lag_s > 0.001f) { + if (final_lag_ms > 0) { // Доматываем симуляцию на величину задержки // Мы предполагаем, что за это время параметры управления не менялись - simulate_physics(final_lag_s); + simulate_physics(final_lag_ms); } } diff --git a/src/network/NetworkInterface.h b/src/network/NetworkInterface.h index 35080bf..8f1eb33 100644 --- a/src/network/NetworkInterface.h +++ b/src/network/NetworkInterface.h @@ -13,5 +13,6 @@ namespace ZL { virtual bool IsConnected() const = 0; virtual void Poll() = 0; // virtual std::unordered_map getRemotePlayers() = 0; + virtual void updateRemotePlayers(const std::unordered_map& newRemotePlayers) = 0; }; } diff --git a/src/network/WebSocketClient.cpp b/src/network/WebSocketClient.cpp index 0d8046e..3bf868a 100644 --- a/src/network/WebSocketClient.cpp +++ b/src/network/WebSocketClient.cpp @@ -69,8 +69,10 @@ namespace ZL { void WebSocketClient::Poll() { std::lock_guard lock(queueMutex); + auto nowTime = std::chrono::steady_clock::now(); + auto now_ms = std::chrono::duration_cast( - std::chrono::steady_clock::now().time_since_epoch() + nowTime.time_since_epoch() ).count(); while (!messageQueue.empty()) { @@ -99,21 +101,22 @@ namespace ZL { // --- --- // - float lag_s = static_cast(now_ms - sentTime) / 1000.0f; + long long lag_s = now_ms - sentTime; // ( ) - lag_s = std::max(0.0f, std::min(lag_s, 1.0f)); + lag_s = std::max(0ll, std::min(lag_s, 1000ll)); - if (lag_s > 0.001f) { + if (lag_s > 0) { // "" // ClientState.h simulate_physics // discreteAngle/Mag rp.simulate_physics(lag_s); + } // , Game.cpp // , now_ms - rp.lastUpdateServerTime = std::chrono::steady_clock::now(); + rp.lastUpdateServerTime = nowTime; } } else if (msg.rfind("WORLD_UPDATE|", 0) == 0) { diff --git a/src/network/WebSocketClient.h b/src/network/WebSocketClient.h index 098088e..d5472d4 100644 --- a/src/network/WebSocketClient.h +++ b/src/network/WebSocketClient.h @@ -53,5 +53,14 @@ namespace ZL { std::lock_guard lock(playersMutex); return remotePlayers; } + + void updateRemotePlayers(const std::unordered_map& newRemotePlayers) override { + std::lock_guard lock(playersMutex); + + for (const auto& [id, newPlayer] : newRemotePlayers) { + remotePlayers[id] = newPlayer; + } + + } }; } \ No newline at end of file From e0a60dcd6fa40f686d37586612bd4b0889ab24be Mon Sep 17 00:00:00 2001 From: Vladislav Khorev Date: Sat, 17 Jan 2026 13:59:02 +0300 Subject: [PATCH 12/18] Change clock to system clock, fix bugs --- server/server.cpp | 14 ++++++++------ src/Game.cpp | 14 ++++++++------ src/network/ClientState.h | 18 ++++++------------ src/network/WebSocketClient.cpp | 9 ++++++--- 4 files changed, 28 insertions(+), 27 deletions(-) diff --git a/server/server.cpp b/server/server.cpp index 067076a..7eb849d 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -40,7 +40,7 @@ class Session : public std::enable_shared_from_this { ClientState state_; void process_message(const std::string& msg) { - auto now_server = std::chrono::steady_clock::now(); + auto now_server = std::chrono::system_clock::now(); // : // ROT:ANGLE:MAG:TIMESTAMP @@ -65,7 +65,7 @@ class Session : public std::enable_shared_from_this { if (deltaMs > 0) state_.simulate_physics(deltaMs); - std::chrono::steady_clock::time_point uptime_timepoint{ std::chrono::duration_cast(std::chrono::milliseconds(clientTimestamp)) }; + std::chrono::system_clock::time_point uptime_timepoint{ std::chrono::duration_cast(std::chrono::milliseconds(clientTimestamp)) }; state_.lastUpdateServerTime = uptime_timepoint; @@ -73,6 +73,7 @@ class Session : public std::enable_shared_from_this { if (parts[0] == "ROT") { state_.discreteAngle = std::stoi(parts[2]); state_.discreteMag = std::stof(parts[3]); + std::cout << "ROT id = " << this->id_ << " discreteMag=" << state_.discreteMag << std::endl; state_.apply_lag_compensation(now_server); state_.lastUpdateServerTime = now_server; retranslateMessage(msg); @@ -85,6 +86,7 @@ class Session : public std::enable_shared_from_this { } else if (parts[0] == "PING") { state_.handle_full_sync(parts); + std::cout << "PING id = " << this->id_ <<" discreteMag=" << state_.discreteMag << std::endl; state_.apply_lag_compensation(now_server); state_.lastUpdateServerTime = now_server; } @@ -110,7 +112,7 @@ public: void init() { - state_.lastUpdateServerTime = std::chrono::steady_clock::now(); + state_.lastUpdateServerTime = std::chrono::system_clock::now(); } std::string get_state_string() { @@ -133,7 +135,7 @@ public: }); } - void tick_physics_global(std::chrono::steady_clock::time_point now) { + void tick_physics_global(std::chrono::system_clock::time_point now) { long long deltaMs = 0; // , @@ -181,7 +183,7 @@ private: }; void update_world(net::steady_timer& timer, net::io_context& ioc) { - auto now = std::chrono::steady_clock::now(); + auto now = std::chrono::system_clock::now(); { std::lock_guard lock(g_sessions_mutex); @@ -196,7 +198,7 @@ void update_world(net::steady_timer& timer, net::io_context& ioc) { last_broadcast = now; auto now_ms = std::chrono::duration_cast( - std::chrono::steady_clock::now().time_since_epoch() + std::chrono::system_clock::now().time_since_epoch() ).count(); // diff --git a/src/Game.cpp b/src/Game.cpp index 1d726cf..ad01455 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -242,7 +242,7 @@ namespace ZL if (newVel != Environment::shipSelectedVelocity) { Environment::shipSelectedVelocity = newVel; auto now_ms = std::chrono::duration_cast( - std::chrono::steady_clock::now().time_since_epoch() + std::chrono::system_clock::now().time_since_epoch() ).count(); std::string msg = "VEL:" + std::to_string(now_ms) + ":" + std::to_string(Environment::shipSelectedVelocity); networkClient->Send(msg); @@ -634,7 +634,7 @@ namespace ZL if (pingTimer >= 1000.0f) { auto now_ms = std::chrono::duration_cast( - std::chrono::steady_clock::now().time_since_epoch() + std::chrono::system_clock::now().time_since_epoch() ).count(); // 1. Извлекаем кватернион из матрицы поворота @@ -707,12 +707,14 @@ namespace ZL int discreteAngle = static_cast(radians * 180.0f / M_PI); if (discreteAngle < 0) discreteAngle += 360; + std::cout << "OUTPUT discreteAngle=" << discreteAngle << std::endl; + // 3. Проверяем, изменились ли параметры значимо для отправки на сервер if (discreteAngle != Environment::lastSentAngle || discreteMag != Environment::lastSentMagnitude) { Environment::lastSentAngle = discreteAngle; Environment::lastSentMagnitude = discreteMag; auto now_ms = std::chrono::duration_cast( - std::chrono::steady_clock::now().time_since_epoch() + std::chrono::system_clock::now().time_since_epoch() ).count(); // Формируем сетевой пакет @@ -776,7 +778,7 @@ namespace ZL Environment::lastSentAngle = discreteAngle; Environment::lastSentMagnitude = discreteMag; auto now_ms = std::chrono::duration_cast( - std::chrono::steady_clock::now().time_since_epoch() + std::chrono::system_clock::now().time_since_epoch() ).count(); // Формируем сетевой пакет @@ -809,7 +811,7 @@ namespace ZL } } - std::cout << "shipVelocity=" << Environment::shipVelocity << " delta=" << delta << std::endl; + //std::cout << "shipVelocity=" << Environment::shipVelocity << " delta=" << delta << std::endl; // Движение вперед (существующая логика) if (fabs(Environment::shipVelocity) > 0.01f) { @@ -1102,7 +1104,7 @@ namespace ZL void Game::extrapolateRemotePlayers() { - auto now = std::chrono::steady_clock::now(); + auto now = std::chrono::system_clock::now(); latestRemotePlayers = networkClient->getRemotePlayers(); diff --git a/src/network/ClientState.h b/src/network/ClientState.h index 75c9c09..4d29b8f 100644 --- a/src/network/ClientState.h +++ b/src/network/ClientState.h @@ -24,7 +24,7 @@ struct ClientState { int discreteAngle = -1; // Для расчета лага - std::chrono::steady_clock::time_point lastUpdateServerTime; + std::chrono::system_clock::time_point lastUpdateServerTime; void simulate_physics(size_t delta) { // Константы из Game.cpp, приведенные к секундам (умножаем на 1000) @@ -87,6 +87,10 @@ struct ClientState { Eigen::Quaternionf rotateQuat(Eigen::AngleAxisf(deltaAlpha, axis)); rotation = rotation * rotateQuat.toRotationMatrix(); + //std::cout << "Rotating ship. d="<< delta <<" DeltaAlpha: " << deltaAlpha << ", Axis: [" << axis.x() << ", " << axis.y() << ", " << axis.z() << "]\n"; + } + else { + //std::cout << "NOT Rotating ship. speedScale=" << speedScale << " discreteMag=" << discreteMag << "\n"; } /* @@ -154,7 +158,6 @@ struct ClientState { velocity = shipDesiredVelocity; } } - std::cout << "velocity=" << velocity << " delta=" << delta << std::endl; if (fabs(velocity) > 0.01f) { @@ -164,16 +167,7 @@ struct ClientState { } } - void apply_lag_compensation(std::chrono::steady_clock::time_point nowTime) { - - // 1. Получаем текущее время сервера в той же шкале (мс) - /*uto now_ms = std::chrono::duration_cast( - std::chrono::steady_clock::now().time_since_epoch() - ).count(); - - long long deltaMs = std::chrono::duration_cast( - std::chrono::steady_clock::now().time_since_epoch() - lastUpdateServerTime - ).count();*/ + void apply_lag_compensation(std::chrono::system_clock::time_point nowTime) { // 2. Вычисляем задержку long long deltaMs = 0; diff --git a/src/network/WebSocketClient.cpp b/src/network/WebSocketClient.cpp index 3bf868a..477e6ba 100644 --- a/src/network/WebSocketClient.cpp +++ b/src/network/WebSocketClient.cpp @@ -69,7 +69,7 @@ namespace ZL { void WebSocketClient::Poll() { std::lock_guard lock(queueMutex); - auto nowTime = std::chrono::steady_clock::now(); + auto nowTime = std::chrono::system_clock::now(); auto now_ms = std::chrono::duration_cast( nowTime.time_since_epoch() @@ -98,6 +98,7 @@ namespace ZL { rp.discreteAngle = std::stoi(parts[4]); rp.discreteMag = std::stof(parts[5]); } + std::cout << "EVENT Received discreteMag=" << rp.discreteMag << std::endl; // --- --- // @@ -160,9 +161,11 @@ namespace ZL { rp.velocity = std::stof(vals[8]); rp.currentAngularVelocity = { std::stof(vals[9]), std::stof(vals[10]), std::stof(vals[11]) }; rp.selectedVelocity = std::stoi(vals[12]); - rp.discreteMag = std::stoi(vals[13]); + rp.discreteMag = std::stof(vals[13]); rp.discreteAngle = std::stoi(vals[14]); - std::chrono::steady_clock::time_point uptime_timepoint{ std::chrono::duration_cast(std::chrono::milliseconds(serverTime)) }; + std::chrono::system_clock::time_point uptime_timepoint{ std::chrono::duration_cast(std::chrono::milliseconds(serverTime)) }; + + std::cout << "PING Received discreteMag=" << rp.discreteMag << std::endl; rp.lastUpdateServerTime = uptime_timepoint; From 3c55b59c8d4cc1b4d9f01b4e2953e740c6654440 Mon Sep 17 00:00:00 2001 From: Vladislav Khorev Date: Sat, 17 Jan 2026 15:03:37 +0300 Subject: [PATCH 13/18] Fixing bugs, more freq updated --- server/server.cpp | 7 ++- src/Game.cpp | 43 +++++++++++++++++ src/Game.h | 1 + src/network/ClientState.h | 25 ++++++---- src/network/WebSocketClient.cpp | 84 ++++++++++++++++++++++++++++----- src/network/WebSocketClient.h | 4 +- 6 files changed, 136 insertions(+), 28 deletions(-) diff --git a/server/server.cpp b/server/server.cpp index 7eb849d..b15f016 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -42,9 +42,6 @@ class Session : public std::enable_shared_from_this { void process_message(const std::string& msg) { auto now_server = std::chrono::system_clock::now(); - // : - // ROT:ANGLE:MAG:TIMESTAMP - // VEL:SELECTED_VEL:TIMESTAMP auto parts = split(msg, ':'); if (parts.empty()) return; @@ -73,6 +70,7 @@ class Session : public std::enable_shared_from_this { if (parts[0] == "ROT") { state_.discreteAngle = std::stoi(parts[2]); state_.discreteMag = std::stof(parts[3]); + state_.handle_full_sync(parts, 4); std::cout << "ROT id = " << this->id_ << " discreteMag=" << state_.discreteMag << std::endl; state_.apply_lag_compensation(now_server); state_.lastUpdateServerTime = now_server; @@ -80,12 +78,13 @@ class Session : public std::enable_shared_from_this { } else if (parts[0] == "VEL") { state_.selectedVelocity = std::stoi(parts[2]); + state_.handle_full_sync(parts, 3); state_.apply_lag_compensation(now_server); state_.lastUpdateServerTime = now_server; retranslateMessage(msg); } else if (parts[0] == "PING") { - state_.handle_full_sync(parts); + state_.handle_full_sync(parts, 2); std::cout << "PING id = " << this->id_ <<" discreteMag=" << state_.discreteMag << std::endl; state_.apply_lag_compensation(now_server); state_.lastUpdateServerTime = now_server; diff --git a/src/Game.cpp b/src/Game.cpp index ad01455..30628ef 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -245,6 +245,7 @@ namespace ZL std::chrono::system_clock::now().time_since_epoch() ).count(); std::string msg = "VEL:" + std::to_string(now_ms) + ":" + std::to_string(Environment::shipSelectedVelocity); + msg = msg + ":" + formPingMessageContent(); networkClient->Send(msg); } }); @@ -583,6 +584,18 @@ namespace ZL renderer.PushMatrix(); renderer.LoadIdentity(); + renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom }); + renderer.RotateMatrix(Environment::inverseShipMatrix); + renderer.TranslateMatrix(-Environment::shipPosition); + + Eigen::Vector3f relativePos = playerState.position;// -Environment::shipPosition; + renderer.TranslateMatrix(relativePos); + + // 3. Поворот врага + renderer.RotateMatrix(playerState.rotation); + + /* + // 1. Камера и вид игрока renderer.TranslateMatrix({ 0, 0, -1.0f * Environment::zoom }); renderer.RotateMatrix(Environment::inverseShipMatrix); @@ -601,6 +614,8 @@ namespace ZL // В твоем drawShip() есть: renderer.TranslateMatrix({ 0, -6.f, 0 }); renderer.TranslateMatrix({ 0, -6.f, 0 }); + */ + renderer.DrawVertexRenderStruct(spaceship); renderer.PopMatrix(); } @@ -638,6 +653,7 @@ namespace ZL ).count(); // 1. Извлекаем кватернион из матрицы поворота + /* Eigen::Quaternionf q(Environment::shipMatrix); // 2. Формируем строку PING согласно протоколу сервера @@ -657,6 +673,8 @@ namespace ZL + std::to_string(Environment::shipSelectedVelocity) + ":" + std::to_string(Environment::lastSentMagnitude) + ":" // Используем те же static переменные из блока ROT + std::to_string(Environment::lastSentAngle); + */ + std::string pingMsg = "PING:" + std::to_string(now_ms) + ":" + formPingMessageContent(); networkClient->Send(pingMsg); std::cout << "Sending: " << pingMsg << std::endl; @@ -720,6 +738,7 @@ namespace ZL // Формируем сетевой пакет // Нам нужно отправить: дискретный угол, дискретную силу и текущую матрицу/позицию для синхронизации std::string msg = "ROT:" + std::to_string(now_ms) + ":" + std::to_string(discreteAngle) + ":" + std::to_string(discreteMag); + msg = msg + ":" + formPingMessageContent(); networkClient->Send(msg); } @@ -784,6 +803,7 @@ namespace ZL // Формируем сетевой пакет // Нам нужно отправить: дискретный угол, дискретную силу и текущую матрицу/позицию для синхронизации std::string msg = "ROT:" + std::to_string(now_ms) + ":" + std::to_string(discreteAngle) + ":" + std::to_string(discreteMag); + msg = msg + ":" + formPingMessageContent(); networkClient->Send(msg); } @@ -1138,5 +1158,28 @@ namespace ZL } + std::string Game::formPingMessageContent() + { + Eigen::Quaternionf q(Environment::shipMatrix); + + std::string pingMsg = std::to_string(Environment::shipPosition.x()) + ":" + + std::to_string(Environment::shipPosition.y()) + ":" + + std::to_string(Environment::shipPosition.z()) + ":" + + std::to_string(q.w()) + ":" + + std::to_string(q.x()) + ":" + + std::to_string(q.y()) + ":" + + std::to_string(q.z()) + ":" + + std::to_string(Environment::currentAngularVelocity.x()) + ":" + + std::to_string(Environment::currentAngularVelocity.y()) + ":" + + std::to_string(Environment::currentAngularVelocity.z()) + ":" + + std::to_string(Environment::shipVelocity) + ":" + + std::to_string(Environment::shipSelectedVelocity) + ":" + + std::to_string(Environment::lastSentMagnitude) + ":" // Используем те же static переменные из блока ROT + + std::to_string(Environment::lastSentAngle); + + + return pingMsg; + } + } // namespace ZL diff --git a/src/Game.h b/src/Game.h index 296f654..0b916ed 100644 --- a/src/Game.h +++ b/src/Game.h @@ -52,6 +52,7 @@ namespace ZL { void handleMotion(int mx, int my); void extrapolateRemotePlayers(); + std::string formPingMessageContent(); SDL_Window* window; SDL_GLContext glContext; diff --git a/src/network/ClientState.h b/src/network/ClientState.h index 4d29b8f..0074c8a 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; struct ClientState { - int id; + int id = 0; Eigen::Vector3f position = { 0, 0, 45000.0f }; Eigen::Matrix3f rotation = Eigen::Matrix3f::Identity(); Eigen::Vector3f currentAngularVelocity = Eigen::Vector3f::Zero(); @@ -201,19 +201,26 @@ struct ClientState { return s; } - void handle_full_sync(const std::vector& parts) { + void handle_full_sync(const std::vector& parts, int startFrom) { // Позиция - position = { std::stof(parts[2]), std::stof(parts[3]), std::stof(parts[4]) }; + position = { std::stof(parts[startFrom]), std::stof(parts[startFrom+1]), std::stof(parts[startFrom+2]) }; // Для вращения клиент должен прислать либо кватернион, либо углы Эйлера. // Предположим, мы передаем 4 значения кватерниона для экономии: - Eigen::Quaternionf q(std::stof(parts[5]), std::stof(parts[6]), std::stof(parts[7]), std::stof(parts[8])); + Eigen::Quaternionf q( + std::stof(parts[startFrom+3]), + std::stof(parts[startFrom+4]), + std::stof(parts[startFrom+5]), + std::stof(parts[startFrom+6])); rotation = q.toRotationMatrix(); - currentAngularVelocity = Eigen::Vector3f{ std::stof(parts[9]), std::stof(parts[10]), std::stof(parts[11]) }; - velocity = std::stof(parts[12]); - selectedVelocity = std::stoi(parts[13]); - discreteMag = std::stof(parts[14]); - discreteAngle = std::stoi(parts[15]); + currentAngularVelocity = Eigen::Vector3f{ + std::stof(parts[startFrom+7]), + std::stof(parts[startFrom+8]), + std::stof(parts[startFrom+9]) }; + velocity = std::stof(parts[startFrom+10]); + selectedVelocity = std::stoi(parts[startFrom+11]); + discreteMag = std::stof(parts[startFrom+12]); + discreteAngle = std::stoi(parts[startFrom+13]); } }; diff --git a/src/network/WebSocketClient.cpp b/src/network/WebSocketClient.cpp index 477e6ba..7039c9b 100644 --- a/src/network/WebSocketClient.cpp +++ b/src/network/WebSocketClient.cpp @@ -69,13 +69,14 @@ namespace ZL { void WebSocketClient::Poll() { std::lock_guard lock(queueMutex); - auto nowTime = std::chrono::system_clock::now(); - - auto now_ms = std::chrono::duration_cast( - nowTime.time_since_epoch() - ).count(); - while (!messageQueue.empty()) { + + auto nowTime = std::chrono::system_clock::now(); + + auto now_ms = std::chrono::duration_cast( + nowTime.time_since_epoch() + ).count(); + std::string msg = messageQueue.front(); messageQueue.pop(); @@ -93,10 +94,65 @@ namespace ZL { if (subType == "VEL") { rp.selectedVelocity = std::stoi(parts[4]); + int startFrom = 5; + rp.position = { std::stof(parts[startFrom]), std::stof(parts[startFrom+1]), std::stof(parts[startFrom+2]) }; + Eigen::Quaternionf q( + std::stof(parts[startFrom + 3]), + std::stof(parts[startFrom + 4]), + std::stof(parts[startFrom + 5]), + std::stof(parts[startFrom + 6])); + rp.rotation = q.toRotationMatrix(); + + rp.currentAngularVelocity = Eigen::Vector3f{ + std::stof(parts[startFrom + 7]), + std::stof(parts[startFrom + 8]), + std::stof(parts[startFrom + 9]) }; + rp.velocity = std::stof(parts[startFrom + 10]); + rp.selectedVelocity = std::stoi(parts[startFrom + 11]); + rp.discreteMag = std::stof(parts[startFrom + 12]); + rp.discreteAngle = std::stoi(parts[startFrom + 13]); + /*position = { std::stof(parts[startFrom]), std::stof(parts[startFrom+1]), std::stof(parts[startFrom+2]) }; + + // , . + // , 4 : + Eigen::Quaternionf q( + std::stof(parts[startFrom+3]), + std::stof(parts[startFrom+4]), + std::stof(parts[startFrom+5]), + std::stof(parts[startFrom+6])); + rotation = q.toRotationMatrix(); + + currentAngularVelocity = Eigen::Vector3f{ + std::stof(parts[startFrom+7]), + std::stof(parts[startFrom+8]), + std::stof(parts[startFrom+9]) }; + velocity = std::stof(parts[startFrom+10]); + selectedVelocity = std::stoi(parts[startFrom+11]); + discreteMag = std::stof(parts[startFrom+12]); + discreteAngle = std::stoi(parts[15]); + */ + } else if (subType == "ROT") { rp.discreteAngle = std::stoi(parts[4]); rp.discreteMag = std::stof(parts[5]); + int startFrom = 6; + rp.position = { std::stof(parts[startFrom]), std::stof(parts[startFrom + 1]), std::stof(parts[startFrom + 2]) }; + Eigen::Quaternionf q( + std::stof(parts[startFrom + 3]), + std::stof(parts[startFrom + 4]), + std::stof(parts[startFrom + 5]), + std::stof(parts[startFrom + 6])); + rp.rotation = q.toRotationMatrix(); + + rp.currentAngularVelocity = Eigen::Vector3f{ + std::stof(parts[startFrom + 7]), + std::stof(parts[startFrom + 8]), + std::stof(parts[startFrom + 9]) }; + rp.velocity = std::stof(parts[startFrom + 10]); + rp.selectedVelocity = std::stoi(parts[startFrom + 11]); + rp.discreteMag = std::stof(parts[startFrom + 12]); + rp.discreteAngle = std::stoi(parts[startFrom + 13]); } std::cout << "EVENT Received discreteMag=" << rp.discreteMag << std::endl; @@ -121,12 +177,15 @@ namespace ZL { } } else if (msg.rfind("WORLD_UPDATE|", 0) == 0) { - parseWorldUpdate(msg); + parseWorldUpdate(msg, nowTime); } } } - void WebSocketClient::parseWorldUpdate(const std::string& msg) { + void WebSocketClient::parseWorldUpdate(const std::string& msg, std::chrono::system_clock::time_point now_ms) { + // , (TaskManager) + std::lock_guard lock(playersMutex); + // : WORLD_UPDATE|server_now_ms|count|id,x,y,z,w,qx,qy,qz,v;... auto parts = split(msg, '|'); if (parts.size() < 4) return; @@ -143,14 +202,12 @@ namespace ZL { if (id == this->clientId) continue; // // RemotePlayer - updateRemotePlayer(id, vals, serverTime); + updateRemotePlayer(id, vals, serverTime, now_ms); } } - void WebSocketClient::updateRemotePlayer(int id, const std::vector& vals, uint64_t serverTime) { - // , (TaskManager) - std::lock_guard lock(playersMutex); - + void WebSocketClient::updateRemotePlayer(int id, const std::vector& vals, uint64_t serverTime, std::chrono::system_clock::time_point now_ms) { + auto& rp = remotePlayers[id]; rp.id = id; @@ -168,6 +225,7 @@ namespace ZL { std::cout << "PING Received discreteMag=" << rp.discreteMag << std::endl; rp.lastUpdateServerTime = uptime_timepoint; + rp.apply_lag_compensation(now_ms); } diff --git a/src/network/WebSocketClient.h b/src/network/WebSocketClient.h index d5472d4..54062d2 100644 --- a/src/network/WebSocketClient.h +++ b/src/network/WebSocketClient.h @@ -40,8 +40,8 @@ namespace ZL { void Connect(const std::string& host, uint16_t port) override; void Poll() override; - void parseWorldUpdate(const std::string& msg); - void updateRemotePlayer(int id, const std::vector& vals, uint64_t serverTime); + void parseWorldUpdate(const std::string& msg, std::chrono::system_clock::time_point now_ms); + void updateRemotePlayer(int id, const std::vector& vals, uint64_t serverTime, std::chrono::system_clock::time_point now_ms); void Send(const std::string& message) override; void doWrite(); From 64385ba15c0ae75013a3c95bd6a378a1898e4feb Mon Sep 17 00:00:00 2001 From: Vladislav Khorev Date: Sat, 17 Jan 2026 19:16:06 +0300 Subject: [PATCH 14/18] Fixing bug, implementing delays, remove WORLD_UPDATE --- server/server.cpp | 136 ++++++++++++++++++++++------ src/Game.cpp | 104 ++++++++++++++++++++-- src/Game.h | 4 +- src/network/ClientState.h | 60 ++----------- src/network/NetworkInterface.h | 63 ++++++++++++- src/network/WebSocketClient.cpp | 153 +++++++++++++++++++++++++------- src/network/WebSocketClient.h | 14 +-- 7 files changed, 398 insertions(+), 136 deletions(-) diff --git a/server/server.cpp b/server/server.cpp index b15f016..c3f025e 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #define _USE_MATH_DEFINES #include @@ -37,7 +38,10 @@ class Session : public std::enable_shared_from_this { websocket::stream ws_; beast::flat_buffer buffer_; int id_; - ClientState state_; + //ClientState state_; + std::vector timedClientStates; + + void process_message(const std::string& msg) { auto now_server = std::chrono::system_clock::now(); @@ -51,8 +55,16 @@ class Session : public std::enable_shared_from_this { now_server.time_since_epoch() ).count(); + //Apply server delay: + now_ms -= SERVER_DELAY; + uint64_t clientTimestamp = std::stoull(parts[1]); + ClientState receivedState; + + receivedState.id = id_; + + /* // " " long long deltaMs = 0.0f; if (state_.lastUpdateServerTime.time_since_epoch().count() > 0) { @@ -60,34 +72,42 @@ class Session : public std::enable_shared_from_this { } - if (deltaMs > 0) state_.simulate_physics(deltaMs); + if (deltaMs > 0) state_.simulate_physics(deltaMs);*/ std::chrono::system_clock::time_point uptime_timepoint{ std::chrono::duration_cast(std::chrono::milliseconds(clientTimestamp)) }; - state_.lastUpdateServerTime = uptime_timepoint; + receivedState.lastUpdateServerTime = uptime_timepoint; // if (parts[0] == "ROT") { - state_.discreteAngle = std::stoi(parts[2]); - state_.discreteMag = std::stof(parts[3]); - state_.handle_full_sync(parts, 4); - std::cout << "ROT id = " << this->id_ << " discreteMag=" << state_.discreteMag << std::endl; - state_.apply_lag_compensation(now_server); - state_.lastUpdateServerTime = now_server; + receivedState.discreteAngle = std::stoi(parts[2]); + receivedState.discreteMag = std::stof(parts[3]); + receivedState.handle_full_sync(parts, 4); + //std::cout << "ROT id = " << this->id_ << " discreteMag=" << state_.discreteMag << std::endl; + //receivedState.apply_lag_compensation(now_server); + //receivedState.lastUpdateServerTime = now_server; retranslateMessage(msg); } else if (parts[0] == "VEL") { - state_.selectedVelocity = std::stoi(parts[2]); - state_.handle_full_sync(parts, 3); - state_.apply_lag_compensation(now_server); - state_.lastUpdateServerTime = now_server; + receivedState.selectedVelocity = std::stoi(parts[2]); + receivedState.handle_full_sync(parts, 3); + //receivedState.apply_lag_compensation(now_server); + //receivedState.lastUpdateServerTime = now_server; retranslateMessage(msg); } else if (parts[0] == "PING") { - state_.handle_full_sync(parts, 2); - std::cout << "PING id = " << this->id_ <<" discreteMag=" << state_.discreteMag << std::endl; - state_.apply_lag_compensation(now_server); - state_.lastUpdateServerTime = now_server; + receivedState.handle_full_sync(parts, 2); + retranslateMessage(msg); + //receivedState.apply_lag_compensation(now_server); + //receivedState.lastUpdateServerTime = now_server; + } + timedClientStates.push_back(receivedState); + + auto cutoff_time = now_server - std::chrono::milliseconds(CUTOFF_TIME); + + while (timedClientStates.size() > 0 && timedClientStates[0].lastUpdateServerTime < cutoff_time) + { + timedClientStates.erase(timedClientStates.begin()); } } @@ -111,12 +131,12 @@ public: void init() { - state_.lastUpdateServerTime = std::chrono::system_clock::now(); + //state_.lastUpdateServerTime = std::chrono::system_clock::now(); } - + /* std::string get_state_string() { return state_.get_state_string(id_); - } + }*/ void run() { @@ -134,9 +154,63 @@ public: }); } - void tick_physics_global(std::chrono::system_clock::time_point now) { - long long deltaMs = 0; + bool canFetchClientStateAtTime(std::chrono::system_clock::time_point targetTime) + { + if (timedClientStates.empty()) + { + return false; + } + if (timedClientStates[0].lastUpdateServerTime > targetTime) + { + return false; + } + return true; + } + + ClientState fetchClientStateAtTime(std::chrono::system_clock::time_point targetTime) { + + ClientState closestState; + + if (timedClientStates.empty()) + { + throw std::runtime_error("No timed client states available"); + return closestState; + } + if (timedClientStates[0].lastUpdateServerTime > targetTime) + { + throw std::runtime_error("Found time but it is in future"); + return closestState; + } + if (timedClientStates.size() == 1) + { + closestState = timedClientStates[0]; + closestState.apply_lag_compensation(targetTime); + return closestState; + } + + + for (size_t i = 0; i < timedClientStates.size() - 1; ++i) + { + const auto& earlierState = timedClientStates[i]; + const auto& laterState = timedClientStates[i + 1]; + if (earlierState.lastUpdateServerTime <= targetTime && laterState.lastUpdateServerTime >= targetTime) + { + closestState = earlierState; + closestState.apply_lag_compensation(targetTime); + return closestState; + } + } + + closestState = timedClientStates[timedClientStates.size() - 1]; + closestState.apply_lag_compensation(targetTime); + return closestState; + } + + void tick_physics_global(std::chrono::system_clock::time_point now) { + /*long long deltaMs = 0; + + // , if (state_.lastUpdateServerTime.time_since_epoch().count() > 0) { deltaMs = std::chrono::duration_cast( @@ -147,7 +221,7 @@ public: if (deltaMs > 0) { state_.simulate_physics(deltaMs); state_.lastUpdateServerTime = now; // - } + }*/ } void send_message(std::string msg) { @@ -184,7 +258,10 @@ private: void update_world(net::steady_timer& timer, net::io_context& ioc) { auto now = std::chrono::system_clock::now(); - { + //Apply server delay + now -= std::chrono::milliseconds(SERVER_DELAY); + + /* { std::lock_guard lock(g_sessions_mutex); // 1. @@ -197,7 +274,7 @@ void update_world(net::steady_timer& timer, net::io_context& ioc) { last_broadcast = now; auto now_ms = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch() + now.time_since_epoch() ).count(); // @@ -205,8 +282,11 @@ void update_world(net::steady_timer& timer, net::io_context& ioc) { snapshot += std::to_string(now_ms) + "|"; snapshot += std::to_string(g_sessions.size()) + "|"; for (size_t i = 0; i < g_sessions.size(); ++i) { - snapshot += g_sessions[i]->get_state_string(); - if (i < g_sessions.size() - 1) snapshot += ";"; // + if (g_sessions[i]->canFetchClientStateAtTime(now)) + { + snapshot += g_sessions[i]->fetchClientStateAtTime(now).get_state_string(); + if (i < g_sessions.size() - 1) snapshot += ";"; // + } } // @@ -215,7 +295,7 @@ void update_world(net::steady_timer& timer, net::io_context& ioc) { } } - } + }*/ // : (50), (1000 ) timer.expires_after(std::chrono::milliseconds(50)); diff --git a/src/Game.cpp b/src/Game.cpp index 30628ef..7d56b67 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -240,13 +240,14 @@ namespace ZL uiManager.setSliderCallback("velocitySlider", [this](const std::string& name, float value) { int newVel = roundf(value * 10); if (newVel != Environment::shipSelectedVelocity) { + velocityChanged = true; Environment::shipSelectedVelocity = newVel; - auto now_ms = std::chrono::duration_cast( + /*auto now_ms = std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch() ).count(); std::string msg = "VEL:" + std::to_string(now_ms) + ":" + std::to_string(Environment::shipSelectedVelocity); msg = msg + ":" + formPingMessageContent(); - networkClient->Send(msg); + networkClient->Send(msg);*/ } }); @@ -577,17 +578,34 @@ namespace ZL // Биндим текстуру корабля один раз для всех удаленных игроков (оптимизация батчинга) glBindTexture(GL_TEXTURE_2D, spaceshipTexture->getTexID()); + auto now = std::chrono::system_clock::now(); + + //Apply server delay: + now -= std::chrono::milliseconds(CLIENT_DELAY); + + latestRemotePlayers = networkClient->getRemotePlayers(); + // Итерируемся по актуальным данным из extrapolateRemotePlayers - for (auto const& [id, playerState] : latestRemotePlayers) { + for (auto const& [id, remotePlayer] : latestRemotePlayers) { //if (id == networkClient->GetClientId()) continue; // Не рисуем себя через этот цикл + if (!remotePlayer.canFetchClientStateAtTime(now)) + { + continue; + } + + ClientState playerState = remotePlayer.fetchClientStateAtTime(now); + + renderer.PushMatrix(); renderer.LoadIdentity(); renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom }); + renderer.TranslateMatrix({ 0, -6.f, 0 }); //Ship camera offset renderer.RotateMatrix(Environment::inverseShipMatrix); renderer.TranslateMatrix(-Environment::shipPosition); + Eigen::Vector3f relativePos = playerState.position;// -Environment::shipPosition; renderer.TranslateMatrix(relativePos); @@ -705,7 +723,25 @@ namespace ZL } } - static const float ANGULAR_ACCEL = 0.005f; + if (velocityChanged) + { + velocityChanged = false; + //if (newVel != Environment::shipSelectedVelocity) { + // velocityChanged = true; + //Environment::shipSelectedVelocity = newVel; + auto now_ms = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch() + ).count(); + std::string msg = "VEL:" + std::to_string(now_ms) + ":" + std::to_string(Environment::shipSelectedVelocity); + msg = msg + ":" + formPingMessageContent(); + networkClient->Send(msg); + //} + } + + + + + //static const float ANGULAR_ACCEL = 0.005f; if (Environment::tapDownHold) { float diffx = Environment::tapDownCurrentPos(0) - Environment::tapDownStartPos(0); @@ -725,13 +761,17 @@ namespace ZL int discreteAngle = static_cast(radians * 180.0f / M_PI); if (discreteAngle < 0) discreteAngle += 360; + bool sendRotation = false; + std::cout << "OUTPUT discreteAngle=" << discreteAngle << std::endl; // 3. Проверяем, изменились ли параметры значимо для отправки на сервер if (discreteAngle != Environment::lastSentAngle || discreteMag != Environment::lastSentMagnitude) { Environment::lastSentAngle = discreteAngle; Environment::lastSentMagnitude = discreteMag; - auto now_ms = std::chrono::duration_cast( + + sendRotation = true; + /*auto now_ms = std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch() ).count(); @@ -740,6 +780,7 @@ namespace ZL std::string msg = "ROT:" + std::to_string(now_ms) + ":" + std::to_string(discreteAngle) + ":" + std::to_string(discreteMag); msg = msg + ":" + formPingMessageContent(); networkClient->Send(msg); + std::cout << "Sending: " << msg << std::endl;*/ } // 4. Логика вращения (угловое ускорение) @@ -786,6 +827,20 @@ namespace ZL Environment::shipMatrix = Environment::shipMatrix * rotateQuat.toRotationMatrix(); Environment::inverseShipMatrix = Environment::shipMatrix.inverse(); } + + if (sendRotation) + { + auto now_ms = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch() + ).count(); + + // Формируем сетевой пакет + // Нам нужно отправить: дискретный угол, дискретную силу и текущую матрицу/позицию для синхронизации + std::string msg = "ROT:" + std::to_string(now_ms) + ":" + std::to_string(discreteAngle) + ":" + std::to_string(discreteMag); + msg = msg + ":" + formPingMessageContent(); + networkClient->Send(msg); + std::cout << "Sending: " << msg << std::endl; + } } } else { @@ -793,9 +848,14 @@ namespace ZL int discreteAngle = -1; float discreteMag = 0.0f; + bool sendRotation = false; + if (discreteAngle != Environment::lastSentAngle || discreteMag != Environment::lastSentMagnitude) { Environment::lastSentAngle = discreteAngle; Environment::lastSentMagnitude = discreteMag; + + sendRotation = true; + /* auto now_ms = std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch() ).count(); @@ -805,6 +865,7 @@ namespace ZL std::string msg = "ROT:" + std::to_string(now_ms) + ":" + std::to_string(discreteAngle) + ":" + std::to_string(discreteMag); msg = msg + ":" + formPingMessageContent(); networkClient->Send(msg); + std::cout << "Sending: " << msg << std::endl;*/ } @@ -829,6 +890,20 @@ namespace ZL Environment::shipMatrix = Environment::shipMatrix * rotateQuat.toRotationMatrix(); Environment::inverseShipMatrix = Environment::shipMatrix.inverse(); } + + if (sendRotation) + { + auto now_ms = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch() + ).count(); + + // Формируем сетевой пакет + // Нам нужно отправить: дискретный угол, дискретную силу и текущую матрицу/позицию для синхронизации + std::string msg = "ROT:" + std::to_string(now_ms) + ":" + std::to_string(discreteAngle) + ":" + std::to_string(discreteMag); + msg = msg + ":" + formPingMessageContent(); + networkClient->Send(msg); + std::cout << "Sending: " << msg << std::endl; + } } //std::cout << "shipVelocity=" << Environment::shipVelocity << " delta=" << delta << std::endl; @@ -1124,8 +1199,24 @@ namespace ZL void Game::extrapolateRemotePlayers() { + /*auto now = std::chrono::system_clock::now(); + + //Apply server delay: + now -= std::chrono::milliseconds(CLIENT_DELAY); + + latestRemotePlayers = networkClient->getRemotePlayers(); + + for (auto& [id, rp] : latestRemotePlayers) { + + }*/ + + /* + * auto now = std::chrono::system_clock::now(); + //Apply server delay: + now -= std::chrono::milliseconds(CLIENT_DELAY); + latestRemotePlayers = networkClient->getRemotePlayers(); for (auto& [id, rp] : latestRemotePlayers) { @@ -1139,6 +1230,7 @@ namespace ZL // Ограничим экстраполяцию (например, не более 2 секунд), // чтобы в случае лага корабли не улетали в бесконечность + if (deltaMs < 0) deltaMs = 0; if (deltaMs > 2000) deltaMs = 2000; // 2. Сбрасываем физическое состояние rp.state в значения из последнего пакета @@ -1155,7 +1247,7 @@ namespace ZL } networkClient->updateRemotePlayers(latestRemotePlayers); - + */ } std::string Game::formPingMessageContent() diff --git a/src/Game.h b/src/Game.h index 0b916ed..d76f738 100644 --- a/src/Game.h +++ b/src/Game.h @@ -65,7 +65,9 @@ namespace ZL { std::vector boxCoordsArr; std::vector boxRenderArr; - std::unordered_map latestRemotePlayers; + std::unordered_map latestRemotePlayers; + + bool velocityChanged = false; static const size_t CONST_TIMER_INTERVAL = 10; static const size_t CONST_MAX_TIME_INTERVAL = 1000; diff --git a/src/network/ClientState.h b/src/network/ClientState.h index 0074c8a..ea11745 100644 --- a/src/network/ClientState.h +++ b/src/network/ClientState.h @@ -13,6 +13,9 @@ constexpr float ANGULAR_ACCEL = 0.005f * 1000.0f; 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 = 1500; //ms +constexpr long long CUTOFF_TIME = 5000; //ms struct ClientState { int id = 0; Eigen::Vector3f position = { 0, 0, 45000.0f }; @@ -27,13 +30,6 @@ struct ClientState { std::chrono::system_clock::time_point lastUpdateServerTime; void simulate_physics(size_t delta) { - // Константы из Game.cpp, приведенные к секундам (умножаем на 1000) - //const float ANGULAR_ACCEL = 0.005f * 1000.0f; - //const float ROTATION_SENSITIVITY = 0.002f; - //const float SHIP_ACCEL = 1.0f * 1000.0f; // CONST_ACCELERATION - - // 1. Вычисляем targetAngularVelocity на лету из дискретных значений - if (discreteMag > 0.01f) { float rad = static_cast(discreteAngle) * static_cast(M_PI) / 180.0f; @@ -93,52 +89,6 @@ struct ClientState { //std::cout << "NOT Rotating ship. speedScale=" << speedScale << " discreteMag=" << discreteMag << "\n"; } - /* - Eigen::Vector3f targetAngularVelocity = Eigen::Vector3f::Zero(); - - if (discreteMag > 0.001f) { - float rad = static_cast(discreteAngle) * static_cast(M_PI) / 180.0f; - // Направление из угла (как в твоем обновленном клиентском коде) - Eigen::Vector3f targetDir(sinf(rad), cosf(rad), 0.0f); - targetAngularVelocity = targetDir * discreteMag; - } - - // 2. Линейное изменение текущей угловой скорости к вычисленной цели - Eigen::Vector3f diffVel = targetAngularVelocity - currentAngularVelocity; - float diffLen = diffVel.norm(); - - if (diffLen > 0.0001f) { - float maxChange = ANGULAR_ACCEL * dt_s; - if (diffLen <= maxChange) { - currentAngularVelocity = targetAngularVelocity; - } - else { - currentAngularVelocity += (diffVel / diffLen) * maxChange; - } - } - else if (discreteMag < 0.001f && currentAngularVelocity.norm() > 0.0001f) { - // Если джойстик отпущен, используем ту же логику торможения (или ANGULAR_ACCEL) - float currentSpeed = currentAngularVelocity.norm(); - float drop = ANGULAR_ACCEL * dt_s; - if (currentSpeed <= drop) { - currentAngularVelocity = Eigen::Vector3f::Zero(); - } - else { - currentAngularVelocity -= (currentAngularVelocity / currentSpeed) * drop; - } - }*/ - - /* - // 3. Применение вращения к матрице (Интеграция) - float speedScale = currentAngularVelocity.norm(); - if (speedScale > 0.0001f) { - float deltaAlpha = speedScale * dt_s * ROTATION_SENSITIVITY; - Eigen::Vector3f axis = currentAngularVelocity.normalized(); - - Eigen::Quaternionf rotateQuat(Eigen::AngleAxisf(deltaAlpha, axis)); - rotation = rotation * rotateQuat.toRotationMatrix(); - }*/ - // 4. Линейное изменение линейной скорости float shipDesiredVelocity = selectedVelocity * 100.f; @@ -177,7 +127,7 @@ struct ClientState { // 3. Защита от слишком больших скачков (Clamp) // Если лаг более 500мс, ограничиваем его, чтобы избежать резких рывков - long long final_lag_ms = min(deltaMs, 500ll); + long long final_lag_ms = deltaMs;//min(deltaMs, 500ll); if (final_lag_ms > 0) { // Доматываем симуляцию на величину задержки @@ -186,7 +136,7 @@ struct ClientState { } } - std::string get_state_string(int id) { + std::string get_state_string() { // Используем кватернион для передачи вращения (4 числа вместо 9) Eigen::Quaternionf q(rotation); diff --git a/src/network/NetworkInterface.h b/src/network/NetworkInterface.h index 8f1eb33..c50f0f9 100644 --- a/src/network/NetworkInterface.h +++ b/src/network/NetworkInterface.h @@ -1,10 +1,70 @@ #pragma once #include #include +#include #include "ClientState.h" // NetworkInterface.h - namespace ZL { + + struct RemotePlayer + { + std::vector timedRemoteStates; + + bool canFetchClientStateAtTime(std::chrono::system_clock::time_point targetTime) const + { + if (timedRemoteStates.empty()) + { + return false; + } + if (timedRemoteStates[0].lastUpdateServerTime > targetTime) + { + return false; + } + + return true; + } + + ClientState fetchClientStateAtTime(std::chrono::system_clock::time_point targetTime) const { + + ClientState closestState; + + if (timedRemoteStates.empty()) + { + throw std::runtime_error("No timed client states available"); + return closestState; + } + if (timedRemoteStates[0].lastUpdateServerTime > targetTime) + { + throw std::runtime_error("Found time but it is in future"); + return closestState; + } + if (timedRemoteStates.size() == 1) + { + closestState = timedRemoteStates[0]; + closestState.apply_lag_compensation(targetTime); + return closestState; + } + + + for (size_t i = 0; i < timedRemoteStates.size() - 1; ++i) + { + const auto& earlierState = timedRemoteStates[i]; + const auto& laterState = timedRemoteStates[i + 1]; + if (earlierState.lastUpdateServerTime <= targetTime && laterState.lastUpdateServerTime >= targetTime) + { + closestState = earlierState; + closestState.apply_lag_compensation(targetTime); + return closestState; + } + } + + closestState = timedRemoteStates[timedRemoteStates.size() - 1]; + closestState.apply_lag_compensation(targetTime); + return closestState; + } + }; + class INetworkClient { public: virtual ~INetworkClient() = default; @@ -12,7 +72,6 @@ namespace ZL { virtual void Send(const std::string& message) = 0; virtual bool IsConnected() const = 0; virtual void Poll() = 0; // - virtual std::unordered_map getRemotePlayers() = 0; - virtual void updateRemotePlayers(const std::unordered_map& newRemotePlayers) = 0; + virtual std::unordered_map getRemotePlayers() = 0; }; } diff --git a/src/network/WebSocketClient.cpp b/src/network/WebSocketClient.cpp index 7039c9b..8be73d9 100644 --- a/src/network/WebSocketClient.cpp +++ b/src/network/WebSocketClient.cpp @@ -73,10 +73,15 @@ namespace ZL { auto nowTime = std::chrono::system_clock::now(); + //Apply server delay: + nowTime -= std::chrono::milliseconds(CLIENT_DELAY); + auto now_ms = std::chrono::duration_cast( nowTime.time_since_epoch() ).count(); + + std::string msg = messageQueue.front(); messageQueue.pop(); @@ -88,6 +93,95 @@ namespace ZL { std::string subType = parts[2]; uint64_t sentTime = std::stoull(parts[3]); + ClientState remoteState; + remoteState.id = remoteId; + + std::chrono::system_clock::time_point uptime_timepoint{ std::chrono::duration_cast(std::chrono::milliseconds(sentTime)) }; + remoteState.lastUpdateServerTime = uptime_timepoint; + + if (subType == "VEL") { + remoteState.selectedVelocity = std::stoi(parts[4]); + int startFrom = 5; + remoteState.position = { std::stof(parts[startFrom]), std::stof(parts[startFrom + 1]), std::stof(parts[startFrom + 2]) }; + Eigen::Quaternionf q( + std::stof(parts[startFrom + 3]), + std::stof(parts[startFrom + 4]), + std::stof(parts[startFrom + 5]), + std::stof(parts[startFrom + 6])); + remoteState.rotation = q.toRotationMatrix(); + + remoteState.currentAngularVelocity = Eigen::Vector3f{ + std::stof(parts[startFrom + 7]), + std::stof(parts[startFrom + 8]), + std::stof(parts[startFrom + 9]) }; + remoteState.velocity = std::stof(parts[startFrom + 10]); + remoteState.selectedVelocity = std::stoi(parts[startFrom + 11]); + remoteState.discreteMag = std::stof(parts[startFrom + 12]); + remoteState.discreteAngle = std::stoi(parts[startFrom + 13]); + + } + else if (subType == "ROT") { + remoteState.discreteAngle = std::stoi(parts[4]); + remoteState.discreteMag = std::stof(parts[5]); + int startFrom = 6; + remoteState.position = { std::stof(parts[startFrom]), std::stof(parts[startFrom + 1]), std::stof(parts[startFrom + 2]) }; + Eigen::Quaternionf q( + std::stof(parts[startFrom + 3]), + std::stof(parts[startFrom + 4]), + std::stof(parts[startFrom + 5]), + std::stof(parts[startFrom + 6])); + remoteState.rotation = q.toRotationMatrix(); + + remoteState.currentAngularVelocity = Eigen::Vector3f{ + std::stof(parts[startFrom + 7]), + std::stof(parts[startFrom + 8]), + std::stof(parts[startFrom + 9]) }; + remoteState.velocity = std::stof(parts[startFrom + 10]); + remoteState.selectedVelocity = std::stoi(parts[startFrom + 11]); + remoteState.discreteMag = std::stof(parts[startFrom + 12]); + remoteState.discreteAngle = std::stoi(parts[startFrom + 13]); + } + else if (subType == "PING") { + //remoteState.discreteAngle = std::stoi(parts[4]); + //remoteState.discreteMag = std::stof(parts[5]); + int startFrom = 4; + remoteState.position = { std::stof(parts[startFrom]), std::stof(parts[startFrom + 1]), std::stof(parts[startFrom + 2]) }; + Eigen::Quaternionf q( + std::stof(parts[startFrom + 3]), + std::stof(parts[startFrom + 4]), + std::stof(parts[startFrom + 5]), + std::stof(parts[startFrom + 6])); + remoteState.rotation = q.toRotationMatrix(); + + remoteState.currentAngularVelocity = Eigen::Vector3f{ + std::stof(parts[startFrom + 7]), + std::stof(parts[startFrom + 8]), + std::stof(parts[startFrom + 9]) }; + remoteState.velocity = std::stof(parts[startFrom + 10]); + remoteState.selectedVelocity = std::stoi(parts[startFrom + 11]); + remoteState.discreteMag = std::stof(parts[startFrom + 12]); + remoteState.discreteAngle = std::stoi(parts[startFrom + 13]); + } + + { + std::lock_guard pLock(playersMutex); + auto& rp = remotePlayers[remoteId]; + rp.timedRemoteStates.push_back(remoteState); + + auto cutoff_time = nowTime - std::chrono::milliseconds(CUTOFF_TIME); + + while (rp.timedRemoteStates.size() > 0 && rp.timedRemoteStates[0].lastUpdateServerTime < cutoff_time) + { + rp.timedRemoteStates.erase(rp.timedRemoteStates.begin()); + } + } + + + + + + + /* std::lock_guard pLock(playersMutex); if (remotePlayers.count(remoteId)) { auto& rp = remotePlayers[remoteId]; @@ -111,26 +205,6 @@ namespace ZL { rp.selectedVelocity = std::stoi(parts[startFrom + 11]); rp.discreteMag = std::stof(parts[startFrom + 12]); rp.discreteAngle = std::stoi(parts[startFrom + 13]); - /*position = { std::stof(parts[startFrom]), std::stof(parts[startFrom+1]), std::stof(parts[startFrom+2]) }; - - // , . - // , 4 : - Eigen::Quaternionf q( - std::stof(parts[startFrom+3]), - std::stof(parts[startFrom+4]), - std::stof(parts[startFrom+5]), - std::stof(parts[startFrom+6])); - rotation = q.toRotationMatrix(); - - currentAngularVelocity = Eigen::Vector3f{ - std::stof(parts[startFrom+7]), - std::stof(parts[startFrom+8]), - std::stof(parts[startFrom+9]) }; - velocity = std::stof(parts[startFrom+10]); - selectedVelocity = std::stoi(parts[startFrom+11]); - discreteMag = std::stof(parts[startFrom+12]); - discreteAngle = std::stoi(parts[15]); - */ } else if (subType == "ROT") { @@ -174,7 +248,7 @@ namespace ZL { // , Game.cpp // , now_ms rp.lastUpdateServerTime = nowTime; - } + }*/ } else if (msg.rfind("WORLD_UPDATE|", 0) == 0) { parseWorldUpdate(msg, nowTime); @@ -208,24 +282,37 @@ namespace ZL { void WebSocketClient::updateRemotePlayer(int id, const std::vector& vals, uint64_t serverTime, std::chrono::system_clock::time_point now_ms) { - auto& rp = remotePlayers[id]; - rp.id = id; + //auto& rp = remotePlayers[id]; - rp.position = { std::stof(vals[1]), std::stof(vals[2]), std::stof(vals[3]) }; - rp.rotation = Eigen::Quaternionf(std::stof(vals[4]), std::stof(vals[5]), std::stof(vals[6]), std::stof(vals[7])); + ClientState remoteState; + + remoteState.id = id; + + remoteState.position = { std::stof(vals[1]), std::stof(vals[2]), std::stof(vals[3]) }; + remoteState.rotation = Eigen::Quaternionf(std::stof(vals[4]), std::stof(vals[5]), std::stof(vals[6]), std::stof(vals[7])); // 3. ( ) - rp.velocity = std::stof(vals[8]); - rp.currentAngularVelocity = { std::stof(vals[9]), std::stof(vals[10]), std::stof(vals[11]) }; - rp.selectedVelocity = std::stoi(vals[12]); - rp.discreteMag = std::stof(vals[13]); - rp.discreteAngle = std::stoi(vals[14]); + remoteState.velocity = std::stof(vals[8]); + remoteState.currentAngularVelocity = { std::stof(vals[9]), std::stof(vals[10]), std::stof(vals[11]) }; + remoteState.selectedVelocity = std::stoi(vals[12]); + remoteState.discreteMag = std::stof(vals[13]); + remoteState.discreteAngle = std::stoi(vals[14]); std::chrono::system_clock::time_point uptime_timepoint{ std::chrono::duration_cast(std::chrono::milliseconds(serverTime)) }; - std::cout << "PING Received discreteMag=" << rp.discreteMag << std::endl; + //std::cout << "PING Received discreteMag=" << rp.discreteMag << std::endl; - rp.lastUpdateServerTime = uptime_timepoint; - rp.apply_lag_compensation(now_ms); + remoteState.lastUpdateServerTime = uptime_timepoint; + + auto& rp = remotePlayers[id]; + rp.timedRemoteStates.push_back(remoteState); + + auto cutoff_time = now_ms - std::chrono::milliseconds(CUTOFF_TIME); + + while (rp.timedRemoteStates.size() > 0 && rp.timedRemoteStates[0].lastUpdateServerTime < cutoff_time) + { + rp.timedRemoteStates.erase(rp.timedRemoteStates.begin()); + } + //rp.apply_lag_compensation(now_ms); } diff --git a/src/network/WebSocketClient.h b/src/network/WebSocketClient.h index 54062d2..e551504 100644 --- a/src/network/WebSocketClient.h +++ b/src/network/WebSocketClient.h @@ -9,6 +9,7 @@ #include namespace ZL { + class WebSocketClient : public INetworkClient { private: // io_context TaskManager @@ -28,7 +29,7 @@ namespace ZL { bool connected = false; int clientId = -1; - std::unordered_map remotePlayers; + std::unordered_map remotePlayers; std::mutex playersMutex; void startAsyncRead(); @@ -49,18 +50,9 @@ namespace ZL { bool IsConnected() const override { return connected; } int GetClientId() const { return clientId; } - std::unordered_map getRemotePlayers() override { + std::unordered_map getRemotePlayers() override { std::lock_guard lock(playersMutex); return remotePlayers; } - - void updateRemotePlayers(const std::unordered_map& newRemotePlayers) override { - std::lock_guard lock(playersMutex); - - for (const auto& [id, newPlayer] : newRemotePlayers) { - remotePlayers[id] = newPlayer; - } - - } }; } \ No newline at end of file From 579f4886d1765137016056d96514d3f81fee49cd Mon Sep 17 00:00:00 2001 From: Vladislav Khorev Date: Sat, 17 Jan 2026 20:06:57 +0300 Subject: [PATCH 15/18] Finally fixed bugs, now need to do cleanup --- src/Game.cpp | 167 +++++++++++++------------------- src/Game.h | 2 +- src/network/WebSocketClient.cpp | 24 ++++- 3 files changed, 91 insertions(+), 102 deletions(-) diff --git a/src/Game.cpp b/src/Game.cpp index 7d56b67..280061a 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -240,8 +240,8 @@ namespace ZL uiManager.setSliderCallback("velocitySlider", [this](const std::string& name, float value) { int newVel = roundf(value * 10); if (newVel != Environment::shipSelectedVelocity) { - velocityChanged = true; - Environment::shipSelectedVelocity = newVel; + newShipVelocity = newVel; + //Environment::shipSelectedVelocity = newVel; /*auto now_ms = std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch() ).count(); @@ -649,49 +649,37 @@ namespace ZL void Game::processTickCount() { if (lastTickCount == 0) { - lastTickCount = SDL_GetTicks64(); + //lastTickCount = SDL_GetTicks64(); + lastTickCount = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch() + ).count(); return; } - newTickCount = SDL_GetTicks64(); + //newTickCount = SDL_GetTicks64(); + newTickCount = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch() + ).count(); + if (newTickCount - lastTickCount > CONST_TIMER_INTERVAL) { - size_t delta = (newTickCount - lastTickCount > CONST_MAX_TIME_INTERVAL) ? - CONST_MAX_TIME_INTERVAL : newTickCount - lastTickCount; + + size_t delta = newTickCount - lastTickCount; + if (delta > CONST_MAX_TIME_INTERVAL) + { + throw std::runtime_error("Synchronization is lost"); + } + + auto now_ms = newTickCount; sparkEmitter.update(static_cast(delta)); planetObject.update(static_cast(delta)); extrapolateRemotePlayers(); + //bool sendRotation = false; + static float pingTimer = 0.0f; pingTimer += delta; if (pingTimer >= 1000.0f) { - - auto now_ms = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch() - ).count(); - - // 1. Извлекаем кватернион из матрицы поворота - /* - Eigen::Quaternionf q(Environment::shipMatrix); - - // 2. Формируем строку PING согласно протоколу сервера - // Формат: PING:timestamp:posX:posY:posZ:qW:qX:qY:qZ:angVelX:angVelY:angVelZ:vel:selectedVel:discMag:discAngle - std::string pingMsg = "PING:" + std::to_string(now_ms) + ":" - + std::to_string(Environment::shipPosition.x()) + ":" - + std::to_string(Environment::shipPosition.y()) + ":" - + std::to_string(Environment::shipPosition.z()) + ":" - + std::to_string(q.w()) + ":" - + std::to_string(q.x()) + ":" - + std::to_string(q.y()) + ":" - + std::to_string(q.z()) + ":" - + std::to_string(Environment::currentAngularVelocity.x()) + ":" - + std::to_string(Environment::currentAngularVelocity.y()) + ":" - + std::to_string(Environment::currentAngularVelocity.z()) + ":" - + std::to_string(Environment::shipVelocity) + ":" - + std::to_string(Environment::shipSelectedVelocity) + ":" - + std::to_string(Environment::lastSentMagnitude) + ":" // Используем те же static переменные из блока ROT - + std::to_string(Environment::lastSentAngle); - */ std::string pingMsg = "PING:" + std::to_string(now_ms) + ":" + formPingMessageContent(); networkClient->Send(pingMsg); @@ -699,50 +687,17 @@ namespace ZL pingTimer = 0.0f; } - //static const float CONST_ACCELERATION = 1.f; - - float shipDesiredVelocity = Environment::shipSelectedVelocity * 100.f; - - if (!gameOver) + if (newShipVelocity != Environment::shipSelectedVelocity) { - if (Environment::shipVelocity < shipDesiredVelocity) - { - Environment::shipVelocity += delta * SHIP_ACCEL; - if (Environment::shipVelocity > shipDesiredVelocity) - { - Environment::shipVelocity = shipDesiredVelocity; - } - } - else if (Environment::shipVelocity > shipDesiredVelocity) - { - Environment::shipVelocity -= delta * SHIP_ACCEL; - if (Environment::shipVelocity < shipDesiredVelocity) - { - Environment::shipVelocity = shipDesiredVelocity; - } - } - } + Environment::shipSelectedVelocity = newShipVelocity; - if (velocityChanged) - { - velocityChanged = false; - //if (newVel != Environment::shipSelectedVelocity) { - // velocityChanged = true; - //Environment::shipSelectedVelocity = newVel; - auto now_ms = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch() - ).count(); - std::string msg = "VEL:" + std::to_string(now_ms) + ":" + std::to_string(Environment::shipSelectedVelocity); - msg = msg + ":" + formPingMessageContent(); - networkClient->Send(msg); + std::string msg = "VEL:" + std::to_string(now_ms) + ":" + std::to_string(Environment::shipSelectedVelocity); + msg = msg + ":" + formPingMessageContent(); + networkClient->Send(msg); //} } - - - //static const float ANGULAR_ACCEL = 0.005f; - if (Environment::tapDownHold) { float diffx = Environment::tapDownCurrentPos(0) - Environment::tapDownStartPos(0); float diffy = Environment::tapDownCurrentPos(1) - Environment::tapDownStartPos(1); @@ -770,7 +725,12 @@ namespace ZL Environment::lastSentAngle = discreteAngle; Environment::lastSentMagnitude = discreteMag; - sendRotation = true; + std::string msg = "ROT:" + std::to_string(now_ms) + ":" + std::to_string(Environment::lastSentAngle) + ":" + std::to_string(Environment::lastSentMagnitude); + msg = msg + ":" + formPingMessageContent(); + networkClient->Send(msg); + std::cout << "Sending: " << msg << std::endl; + + //sendRotation = true; /*auto now_ms = std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch() ).count(); @@ -828,19 +788,6 @@ namespace ZL Environment::inverseShipMatrix = Environment::shipMatrix.inverse(); } - if (sendRotation) - { - auto now_ms = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch() - ).count(); - - // Формируем сетевой пакет - // Нам нужно отправить: дискретный угол, дискретную силу и текущую матрицу/позицию для синхронизации - std::string msg = "ROT:" + std::to_string(now_ms) + ":" + std::to_string(discreteAngle) + ":" + std::to_string(discreteMag); - msg = msg + ":" + formPingMessageContent(); - networkClient->Send(msg); - std::cout << "Sending: " << msg << std::endl; - } } } else { @@ -848,13 +795,16 @@ namespace ZL int discreteAngle = -1; float discreteMag = 0.0f; - bool sendRotation = false; - if (discreteAngle != Environment::lastSentAngle || discreteMag != Environment::lastSentMagnitude) { Environment::lastSentAngle = discreteAngle; Environment::lastSentMagnitude = discreteMag; + + std::string msg = "ROT:" + std::to_string(now_ms) + ":" + std::to_string(Environment::lastSentAngle) + ":" + std::to_string(Environment::lastSentMagnitude); + msg = msg + ":" + formPingMessageContent(); + networkClient->Send(msg); + std::cout << "Sending: " << msg << std::endl; - sendRotation = true; + //sendRotation = true; /* auto now_ms = std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch() @@ -890,19 +840,24 @@ namespace ZL Environment::shipMatrix = Environment::shipMatrix * rotateQuat.toRotationMatrix(); Environment::inverseShipMatrix = Environment::shipMatrix.inverse(); } + } - if (sendRotation) + float shipDesiredVelocity = Environment::shipSelectedVelocity * 100.f; + + if (Environment::shipVelocity < shipDesiredVelocity) + { + Environment::shipVelocity += delta * SHIP_ACCEL; + if (Environment::shipVelocity > shipDesiredVelocity) { - auto now_ms = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch() - ).count(); - - // Формируем сетевой пакет - // Нам нужно отправить: дискретный угол, дискретную силу и текущую матрицу/позицию для синхронизации - std::string msg = "ROT:" + std::to_string(now_ms) + ":" + std::to_string(discreteAngle) + ":" + std::to_string(discreteMag); - msg = msg + ":" + formPingMessageContent(); - networkClient->Send(msg); - std::cout << "Sending: " << msg << std::endl; + Environment::shipVelocity = shipDesiredVelocity; + } + } + else if (Environment::shipVelocity > shipDesiredVelocity) + { + Environment::shipVelocity -= delta * SHIP_ACCEL; + if (Environment::shipVelocity < shipDesiredVelocity) + { + Environment::shipVelocity = shipDesiredVelocity; } } @@ -915,6 +870,20 @@ namespace ZL Environment::shipPosition = Environment::shipPosition + velocityDirectionAdjusted; } + /* + if (sendRotation) + { + sendRotation = false; + + // Формируем сетевой пакет + // Нам нужно отправить: дискретный угол, дискретную силу и текущую матрицу/позицию для синхронизации + std::string msg = "ROT:" + std::to_string(now_ms) + ":" + std::to_string(Environment::lastSentAngle) + ":" + std::to_string(Environment::lastSentMagnitude); + msg = msg + ":" + formPingMessageContent(); + networkClient->Send(msg); + std::cout << "Sending: " << msg << std::endl; + } + */ + for (auto& p : projectiles) { if (p && p->isActive()) { p->update(static_cast(delta), renderer); diff --git a/src/Game.h b/src/Game.h index d76f738..1b68e81 100644 --- a/src/Game.h +++ b/src/Game.h @@ -67,7 +67,7 @@ namespace ZL { std::unordered_map latestRemotePlayers; - bool velocityChanged = false; + float newShipVelocity = 0; static const size_t CONST_TIMER_INTERVAL = 10; static const size_t CONST_MAX_TIME_INTERVAL = 1000; diff --git a/src/network/WebSocketClient.cpp b/src/network/WebSocketClient.cpp index 8be73d9..d8d153c 100644 --- a/src/network/WebSocketClient.cpp +++ b/src/network/WebSocketClient.cpp @@ -166,7 +166,19 @@ namespace ZL { { std::lock_guard pLock(playersMutex); auto& rp = remotePlayers[remoteId]; - rp.timedRemoteStates.push_back(remoteState); + + + + if (rp.timedRemoteStates.size() > 0 && rp.timedRemoteStates[rp.timedRemoteStates.size() - 1].lastUpdateServerTime == remoteState.lastUpdateServerTime) + { + rp.timedRemoteStates[rp.timedRemoteStates.size() - 1] = remoteState; + } + else + { + rp.timedRemoteStates.push_back(remoteState); + } + + //rp.timedRemoteStates.push_back(remoteState); auto cutoff_time = nowTime - std::chrono::milliseconds(CUTOFF_TIME); @@ -304,7 +316,15 @@ namespace ZL { remoteState.lastUpdateServerTime = uptime_timepoint; auto& rp = remotePlayers[id]; - rp.timedRemoteStates.push_back(remoteState); + + if (rp.timedRemoteStates.size() > 0 && rp.timedRemoteStates[rp.timedRemoteStates.size() - 1].lastUpdateServerTime == remoteState.lastUpdateServerTime) + { + rp.timedRemoteStates[rp.timedRemoteStates.size() - 1] = remoteState; + } + else + { + rp.timedRemoteStates.push_back(remoteState); + } auto cutoff_time = now_ms - std::chrono::milliseconds(CUTOFF_TIME); From 6aff22ec53f43d342375f1f6dab376ba0bb84b68 Mon Sep 17 00:00:00 2001 From: Vladislav Khorev Date: Sun, 18 Jan 2026 11:29:15 +0300 Subject: [PATCH 16/18] Working on cleanup --- server/server.cpp | 179 ++----------------------- src/Game.cpp | 131 +------------------ src/Game.h | 3 +- src/network/ClientState.h | 105 +++++++++++---- src/network/NetworkInterface.h | 60 +-------- src/network/WebSocketClient.cpp | 223 ++------------------------------ src/network/WebSocketClient.h | 6 +- 7 files changed, 112 insertions(+), 595 deletions(-) diff --git a/server/server.cpp b/server/server.cpp index c3f025e..06f2331 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -38,25 +38,18 @@ class Session : public std::enable_shared_from_this { websocket::stream ws_; beast::flat_buffer buffer_; int id_; - //ClientState state_; - std::vector timedClientStates; - - + + ClientStateInterval timedClientStates; void process_message(const std::string& msg) { auto now_server = std::chrono::system_clock::now(); auto parts = split(msg, ':'); - if (parts.empty()) return; - std::cout << msg << std::endl; - - auto now_ms = std::chrono::duration_cast( - now_server.time_since_epoch() - ).count(); - - //Apply server delay: - now_ms -= SERVER_DELAY; + if (parts.size() < 16) + { + throw std::runtime_error("Unknown message type received, too small"); + } uint64_t clientTimestamp = std::stoull(parts[1]); @@ -64,51 +57,19 @@ class Session : public std::enable_shared_from_this { receivedState.id = id_; - /* - // " " - long long deltaMs = 0.0f; - if (state_.lastUpdateServerTime.time_since_epoch().count() > 0) { - deltaMs = (clientTimestamp - now_ms); - } - - - if (deltaMs > 0) state_.simulate_physics(deltaMs);*/ - std::chrono::system_clock::time_point uptime_timepoint{ std::chrono::duration_cast(std::chrono::milliseconds(clientTimestamp)) }; receivedState.lastUpdateServerTime = uptime_timepoint; - - // - if (parts[0] == "ROT") { - receivedState.discreteAngle = std::stoi(parts[2]); - receivedState.discreteMag = std::stof(parts[3]); - receivedState.handle_full_sync(parts, 4); - //std::cout << "ROT id = " << this->id_ << " discreteMag=" << state_.discreteMag << std::endl; - //receivedState.apply_lag_compensation(now_server); - //receivedState.lastUpdateServerTime = now_server; - retranslateMessage(msg); - } - else if (parts[0] == "VEL") { - receivedState.selectedVelocity = std::stoi(parts[2]); - receivedState.handle_full_sync(parts, 3); - //receivedState.apply_lag_compensation(now_server); - //receivedState.lastUpdateServerTime = now_server; - retranslateMessage(msg); - } - else if (parts[0] == "PING") { + if (parts[0] == "UPD") { receivedState.handle_full_sync(parts, 2); retranslateMessage(msg); - //receivedState.apply_lag_compensation(now_server); - //receivedState.lastUpdateServerTime = now_server; } - timedClientStates.push_back(receivedState); - - auto cutoff_time = now_server - std::chrono::milliseconds(CUTOFF_TIME); - - while (timedClientStates.size() > 0 && timedClientStates[0].lastUpdateServerTime < cutoff_time) + else { - timedClientStates.erase(timedClientStates.begin()); + throw std::runtime_error("Unknown message type received: " + parts[0]); } + + timedClientStates.add_state(receivedState); } void retranslateMessage(const std::string& msg) @@ -131,12 +92,7 @@ public: void init() { - //state_.lastUpdateServerTime = std::chrono::system_clock::now(); } - /* - std::string get_state_string() { - return state_.get_state_string(id_); - }*/ void run() { @@ -154,76 +110,6 @@ public: }); } - bool canFetchClientStateAtTime(std::chrono::system_clock::time_point targetTime) - { - if (timedClientStates.empty()) - { - return false; - } - if (timedClientStates[0].lastUpdateServerTime > targetTime) - { - return false; - } - - return true; - } - - ClientState fetchClientStateAtTime(std::chrono::system_clock::time_point targetTime) { - - ClientState closestState; - - if (timedClientStates.empty()) - { - throw std::runtime_error("No timed client states available"); - return closestState; - } - if (timedClientStates[0].lastUpdateServerTime > targetTime) - { - throw std::runtime_error("Found time but it is in future"); - return closestState; - } - if (timedClientStates.size() == 1) - { - closestState = timedClientStates[0]; - closestState.apply_lag_compensation(targetTime); - return closestState; - } - - - for (size_t i = 0; i < timedClientStates.size() - 1; ++i) - { - const auto& earlierState = timedClientStates[i]; - const auto& laterState = timedClientStates[i + 1]; - if (earlierState.lastUpdateServerTime <= targetTime && laterState.lastUpdateServerTime >= targetTime) - { - closestState = earlierState; - closestState.apply_lag_compensation(targetTime); - return closestState; - } - } - - closestState = timedClientStates[timedClientStates.size() - 1]; - closestState.apply_lag_compensation(targetTime); - return closestState; - } - - void tick_physics_global(std::chrono::system_clock::time_point now) { - /*long long deltaMs = 0; - - - // , - if (state_.lastUpdateServerTime.time_since_epoch().count() > 0) { - deltaMs = std::chrono::duration_cast( - now - state_.lastUpdateServerTime - ).count(); - } - - if (deltaMs > 0) { - state_.simulate_physics(deltaMs); - state_.lastUpdateServerTime = now; // - }*/ - } - 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) {}); @@ -256,52 +142,13 @@ private: }; void update_world(net::steady_timer& timer, net::io_context& ioc) { - auto now = std::chrono::system_clock::now(); - //Apply server delay - now -= std::chrono::milliseconds(SERVER_DELAY); + // TODO: Renew game state - /* { - std::lock_guard lock(g_sessions_mutex); - - // 1. - for (auto& session : g_sessions) { - session->tick_physics_global(now); - } - - static auto last_broadcast = now; - if (std::chrono::duration(now - last_broadcast).count() >= 1.0f) { - last_broadcast = now; - - auto now_ms = std::chrono::duration_cast( - now.time_since_epoch() - ).count(); - - // - std::string snapshot = "WORLD_UPDATE|"; - snapshot += std::to_string(now_ms) + "|"; - snapshot += std::to_string(g_sessions.size()) + "|"; - for (size_t i = 0; i < g_sessions.size(); ++i) { - if (g_sessions[i]->canFetchClientStateAtTime(now)) - { - snapshot += g_sessions[i]->fetchClientStateAtTime(now).get_state_string(); - if (i < g_sessions.size() - 1) snapshot += ";"; // - } - } - - // - for (auto& session : g_sessions) { - session->send_message(snapshot); - } - } - - }*/ - - // : (50), (1000 ) timer.expires_after(std::chrono::milliseconds(50)); timer.async_wait([&](const boost::system::error_code& ec) { if (!ec) update_world(timer, ioc); - }); + }); } int main() { diff --git a/src/Game.cpp b/src/Game.cpp index 280061a..d63b260 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -587,7 +587,6 @@ namespace ZL // Итерируемся по актуальным данным из extrapolateRemotePlayers for (auto const& [id, remotePlayer] : latestRemotePlayers) { - //if (id == networkClient->GetClientId()) continue; // Не рисуем себя через этот цикл if (!remotePlayer.canFetchClientStateAtTime(now)) { @@ -612,28 +611,6 @@ namespace ZL // 3. Поворот врага renderer.RotateMatrix(playerState.rotation); - /* - - // 1. Камера и вид игрока - renderer.TranslateMatrix({ 0, 0, -1.0f * Environment::zoom }); - renderer.RotateMatrix(Environment::inverseShipMatrix); - - // 2. Относительная позиция (ОЧЕНЬ ВАЖНО) - // Мы перемещаем объект в позицию (Враг - Я) - // Если враг в 44928, а я в 45000, результат будет -72 по Z. - // Поскольку камера смотрит в -Z, корабль должен быть ПЕРЕД нами. - Eigen::Vector3f relativePos = playerState.position - Environment::shipPosition; - renderer.TranslateMatrix(relativePos); - - // 3. Поворот врага - renderer.RotateMatrix(playerState.rotation); - - // 4. Смещение самого меша (если оно есть в drawShip, оно должно быть и тут) - // В твоем drawShip() есть: renderer.TranslateMatrix({ 0, -6.f, 0 }); - renderer.TranslateMatrix({ 0, -6.f, 0 }); - - */ - renderer.DrawVertexRenderStruct(spaceship); renderer.PopMatrix(); } @@ -673,14 +650,11 @@ namespace ZL sparkEmitter.update(static_cast(delta)); planetObject.update(static_cast(delta)); - extrapolateRemotePlayers(); - - //bool sendRotation = false; static float pingTimer = 0.0f; pingTimer += delta; if (pingTimer >= 1000.0f) { - std::string pingMsg = "PING:" + std::to_string(now_ms) + ":" + formPingMessageContent(); + std::string pingMsg = "UPD:" + std::to_string(now_ms) + ":" + formPingMessageContent(); networkClient->Send(pingMsg); std::cout << "Sending: " << pingMsg << std::endl; @@ -691,8 +665,7 @@ namespace ZL { Environment::shipSelectedVelocity = newShipVelocity; - std::string msg = "VEL:" + std::to_string(now_ms) + ":" + std::to_string(Environment::shipSelectedVelocity); - msg = msg + ":" + formPingMessageContent(); + std::string msg = "UPD:" + std::to_string(now_ms) + ":" + formPingMessageContent(); networkClient->Send(msg); //} } @@ -725,22 +698,9 @@ namespace ZL Environment::lastSentAngle = discreteAngle; Environment::lastSentMagnitude = discreteMag; - std::string msg = "ROT:" + std::to_string(now_ms) + ":" + std::to_string(Environment::lastSentAngle) + ":" + std::to_string(Environment::lastSentMagnitude); - msg = msg + ":" + formPingMessageContent(); + std::string msg = "UPD:" + std::to_string(now_ms) + ":" + formPingMessageContent(); networkClient->Send(msg); std::cout << "Sending: " << msg << std::endl; - - //sendRotation = true; - /*auto now_ms = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch() - ).count(); - - // Формируем сетевой пакет - // Нам нужно отправить: дискретный угол, дискретную силу и текущую матрицу/позицию для синхронизации - std::string msg = "ROT:" + std::to_string(now_ms) + ":" + std::to_string(discreteAngle) + ":" + std::to_string(discreteMag); - msg = msg + ":" + formPingMessageContent(); - networkClient->Send(msg); - std::cout << "Sending: " << msg << std::endl;*/ } // 4. Логика вращения (угловое ускорение) @@ -799,23 +759,9 @@ namespace ZL Environment::lastSentAngle = discreteAngle; Environment::lastSentMagnitude = discreteMag; - std::string msg = "ROT:" + std::to_string(now_ms) + ":" + std::to_string(Environment::lastSentAngle) + ":" + std::to_string(Environment::lastSentMagnitude); - msg = msg + ":" + formPingMessageContent(); + std::string msg = "UPD:" + std::to_string(now_ms) + ":" + formPingMessageContent(); networkClient->Send(msg); std::cout << "Sending: " << msg << std::endl; - - //sendRotation = true; - /* - auto now_ms = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch() - ).count(); - - // Формируем сетевой пакет - // Нам нужно отправить: дискретный угол, дискретную силу и текущую матрицу/позицию для синхронизации - std::string msg = "ROT:" + std::to_string(now_ms) + ":" + std::to_string(discreteAngle) + ":" + std::to_string(discreteMag); - msg = msg + ":" + formPingMessageContent(); - networkClient->Send(msg); - std::cout << "Sending: " << msg << std::endl;*/ } @@ -861,7 +807,6 @@ namespace ZL } } - //std::cout << "shipVelocity=" << Environment::shipVelocity << " delta=" << delta << std::endl; // Движение вперед (существующая логика) if (fabs(Environment::shipVelocity) > 0.01f) { @@ -870,20 +815,6 @@ namespace ZL Environment::shipPosition = Environment::shipPosition + velocityDirectionAdjusted; } - /* - if (sendRotation) - { - sendRotation = false; - - // Формируем сетевой пакет - // Нам нужно отправить: дискретный угол, дискретную силу и текущую матрицу/позицию для синхронизации - std::string msg = "ROT:" + std::to_string(now_ms) + ":" + std::to_string(Environment::lastSentAngle) + ":" + std::to_string(Environment::lastSentMagnitude); - msg = msg + ":" + formPingMessageContent(); - networkClient->Send(msg); - std::cout << "Sending: " << msg << std::endl; - } - */ - for (auto& p : projectiles) { if (p && p->isActive()) { p->update(static_cast(delta), renderer); @@ -1166,59 +1097,6 @@ namespace ZL } } - void Game::extrapolateRemotePlayers() { - - /*auto now = std::chrono::system_clock::now(); - - //Apply server delay: - now -= std::chrono::milliseconds(CLIENT_DELAY); - - latestRemotePlayers = networkClient->getRemotePlayers(); - - for (auto& [id, rp] : latestRemotePlayers) { - - }*/ - - /* - * - auto now = std::chrono::system_clock::now(); - - //Apply server delay: - now -= std::chrono::milliseconds(CLIENT_DELAY); - - latestRemotePlayers = networkClient->getRemotePlayers(); - - for (auto& [id, rp] : latestRemotePlayers) { - // 1. Рассчитываем, сколько времени прошло с момента получения последнего пакета (в секундах) - - auto deltaMs = std::chrono::duration_cast( - now - rp.lastUpdateServerTime - ).count(); - - //float age_s = std::chrono::duration(now - rp.lastUpdateServerTime).count(); - - // Ограничим экстраполяцию (например, не более 2 секунд), - // чтобы в случае лага корабли не улетали в бесконечность - if (deltaMs < 0) deltaMs = 0; - if (deltaMs > 2000) deltaMs = 2000; - - // 2. Сбрасываем физическое состояние rp.state в значения из последнего пакета - // (Это важно: мы всегда экстраполируем от последнего достоверного серверного состояния) - // В WebSocketClient::updateRemotePlayer нужно убедиться, что rp.state обновляется данными из пакета. - - // 3. Вызываем физику, чтобы "догнать" реальное время - // Мы передаем age_s как один большой шаг симуляции - rp.simulate_physics(deltaMs); - rp.lastUpdateServerTime = now; - - // Теперь rp.state.position и rp.state.rotation содержат актуальные - // предсказанные данные для рендеринга в текущем кадре. - } - - networkClient->updateRemotePlayers(latestRemotePlayers); - */ - } - std::string Game::formPingMessageContent() { Eigen::Quaternionf q(Environment::shipMatrix); @@ -1238,7 +1116,6 @@ namespace ZL + std::to_string(Environment::lastSentMagnitude) + ":" // Используем те же static переменные из блока ROT + std::to_string(Environment::lastSentAngle); - return pingMsg; } diff --git a/src/Game.h b/src/Game.h index 1b68e81..37ec3a1 100644 --- a/src/Game.h +++ b/src/Game.h @@ -51,7 +51,6 @@ namespace ZL { void handleUp(int mx, int my); void handleMotion(int mx, int my); - void extrapolateRemotePlayers(); std::string formPingMessageContent(); SDL_Window* window; @@ -65,7 +64,7 @@ namespace ZL { std::vector boxCoordsArr; std::vector boxRenderArr; - std::unordered_map latestRemotePlayers; + std::unordered_map latestRemotePlayers; float newShipVelocity = 0; diff --git a/src/network/ClientState.h b/src/network/ClientState.h index ea11745..711533d 100644 --- a/src/network/ClientState.h +++ b/src/network/ClientState.h @@ -14,8 +14,9 @@ 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 = 1500; //ms +constexpr long long CLIENT_DELAY = 200; //ms constexpr long long CUTOFF_TIME = 5000; //ms + struct ClientState { int id = 0; Eigen::Vector3f position = { 0, 0, 45000.0f }; @@ -83,12 +84,9 @@ struct ClientState { Eigen::Quaternionf rotateQuat(Eigen::AngleAxisf(deltaAlpha, axis)); rotation = rotation * rotateQuat.toRotationMatrix(); - //std::cout << "Rotating ship. d="<< delta <<" DeltaAlpha: " << deltaAlpha << ", Axis: [" << axis.x() << ", " << axis.y() << ", " << axis.z() << "]\n"; - } - else { - //std::cout << "NOT Rotating ship. speedScale=" << speedScale << " discreteMag=" << discreteMag << "\n"; } + // 4. Линейное изменение линейной скорости float shipDesiredVelocity = selectedVelocity * 100.f; @@ -136,27 +134,10 @@ struct ClientState { } } - std::string get_state_string() { - // Используем кватернион для передачи вращения (4 числа вместо 9) - Eigen::Quaternionf q(rotation); - - std::string s = std::to_string(id) + "," - + std::to_string(position.x()) + "," + std::to_string(position.y()) + "," + std::to_string(position.z()) + "," - + std::to_string(q.w()) + "," + std::to_string(q.x()) + "," + std::to_string(q.y()) + "," + std::to_string(q.z()) + "," - + std::to_string(velocity) + "," - + std::to_string(currentAngularVelocity.x()) + "," + std::to_string(currentAngularVelocity.y()) + "," + std::to_string(currentAngularVelocity.z()) + "," - + std::to_string(selectedVelocity) + "," - + std::to_string(discreteMag) + "," - + std::to_string(discreteAngle); - return s; - } - void handle_full_sync(const std::vector& parts, int startFrom) { // Позиция position = { std::stof(parts[startFrom]), std::stof(parts[startFrom+1]), std::stof(parts[startFrom+2]) }; - // Для вращения клиент должен прислать либо кватернион, либо углы Эйлера. - // Предположим, мы передаем 4 значения кватерниона для экономии: Eigen::Quaternionf q( std::stof(parts[startFrom+3]), std::stof(parts[startFrom+4]), @@ -174,3 +155,83 @@ struct ClientState { discreteAngle = std::stoi(parts[startFrom+13]); } }; + + +struct ClientStateInterval +{ + std::vector timedStates; + + void add_state(const ClientState& state) + { + auto nowTime = std::chrono::system_clock::now(); + + if (timedStates.size() > 0 && timedStates[timedStates.size() - 1].lastUpdateServerTime == state.lastUpdateServerTime) + { + timedStates[timedStates.size() - 1] = state; + } + else + { + timedStates.push_back(state); + } + + auto cutoff_time = nowTime - std::chrono::milliseconds(CUTOFF_TIME); + + while (timedStates.size() > 0 && timedStates[0].lastUpdateServerTime < cutoff_time) + { + timedStates.erase(timedStates.begin()); + } + } + + bool canFetchClientStateAtTime(std::chrono::system_clock::time_point targetTime) const + { + if (timedStates.empty()) + { + return false; + } + if (timedStates[0].lastUpdateServerTime > targetTime) + { + return false; + } + + return true; + } + + ClientState fetchClientStateAtTime(std::chrono::system_clock::time_point targetTime) const { + + ClientState closestState; + + if (timedStates.empty()) + { + throw std::runtime_error("No timed client states available"); + return closestState; + } + if (timedStates[0].lastUpdateServerTime > targetTime) + { + throw std::runtime_error("Found time but it is in future"); + return closestState; + } + if (timedStates.size() == 1) + { + closestState = timedStates[0]; + closestState.apply_lag_compensation(targetTime); + return closestState; + } + + + for (size_t i = 0; i < timedStates.size() - 1; ++i) + { + const auto& earlierState = timedStates[i]; + const auto& laterState = timedStates[i + 1]; + if (earlierState.lastUpdateServerTime <= targetTime && laterState.lastUpdateServerTime >= targetTime) + { + closestState = earlierState; + closestState.apply_lag_compensation(targetTime); + return closestState; + } + } + + closestState = timedStates[timedStates.size() - 1]; + closestState.apply_lag_compensation(targetTime); + return closestState; + } +}; diff --git a/src/network/NetworkInterface.h b/src/network/NetworkInterface.h index c50f0f9..03f93a5 100644 --- a/src/network/NetworkInterface.h +++ b/src/network/NetworkInterface.h @@ -7,64 +7,6 @@ // NetworkInterface.h - namespace ZL { - struct RemotePlayer - { - std::vector timedRemoteStates; - - bool canFetchClientStateAtTime(std::chrono::system_clock::time_point targetTime) const - { - if (timedRemoteStates.empty()) - { - return false; - } - if (timedRemoteStates[0].lastUpdateServerTime > targetTime) - { - return false; - } - - return true; - } - - ClientState fetchClientStateAtTime(std::chrono::system_clock::time_point targetTime) const { - - ClientState closestState; - - if (timedRemoteStates.empty()) - { - throw std::runtime_error("No timed client states available"); - return closestState; - } - if (timedRemoteStates[0].lastUpdateServerTime > targetTime) - { - throw std::runtime_error("Found time but it is in future"); - return closestState; - } - if (timedRemoteStates.size() == 1) - { - closestState = timedRemoteStates[0]; - closestState.apply_lag_compensation(targetTime); - return closestState; - } - - - for (size_t i = 0; i < timedRemoteStates.size() - 1; ++i) - { - const auto& earlierState = timedRemoteStates[i]; - const auto& laterState = timedRemoteStates[i + 1]; - if (earlierState.lastUpdateServerTime <= targetTime && laterState.lastUpdateServerTime >= targetTime) - { - closestState = earlierState; - closestState.apply_lag_compensation(targetTime); - return closestState; - } - } - - closestState = timedRemoteStates[timedRemoteStates.size() - 1]; - closestState.apply_lag_compensation(targetTime); - return closestState; - } - }; - class INetworkClient { public: virtual ~INetworkClient() = default; @@ -72,6 +14,6 @@ namespace ZL { virtual void Send(const std::string& message) = 0; virtual bool IsConnected() const = 0; virtual void Poll() = 0; // - virtual std::unordered_map getRemotePlayers() = 0; + virtual std::unordered_map getRemotePlayers() = 0; }; } diff --git a/src/network/WebSocketClient.cpp b/src/network/WebSocketClient.cpp index d8d153c..f7a0790 100644 --- a/src/network/WebSocketClient.cpp +++ b/src/network/WebSocketClient.cpp @@ -43,10 +43,7 @@ namespace ZL { if (!ec) { std::string msg = boost::beast::buffers_to_string(buffer_.data()); buffer_.consume(bytes); - - // ( TaskManager) processIncomingMessage(msg); - startAsyncRead(); } else { @@ -98,52 +95,8 @@ namespace ZL { std::chrono::system_clock::time_point uptime_timepoint{ std::chrono::duration_cast(std::chrono::milliseconds(sentTime)) }; remoteState.lastUpdateServerTime = uptime_timepoint; - - if (subType == "VEL") { - remoteState.selectedVelocity = std::stoi(parts[4]); - int startFrom = 5; - remoteState.position = { std::stof(parts[startFrom]), std::stof(parts[startFrom + 1]), std::stof(parts[startFrom + 2]) }; - Eigen::Quaternionf q( - std::stof(parts[startFrom + 3]), - std::stof(parts[startFrom + 4]), - std::stof(parts[startFrom + 5]), - std::stof(parts[startFrom + 6])); - remoteState.rotation = q.toRotationMatrix(); - - remoteState.currentAngularVelocity = Eigen::Vector3f{ - std::stof(parts[startFrom + 7]), - std::stof(parts[startFrom + 8]), - std::stof(parts[startFrom + 9]) }; - remoteState.velocity = std::stof(parts[startFrom + 10]); - remoteState.selectedVelocity = std::stoi(parts[startFrom + 11]); - remoteState.discreteMag = std::stof(parts[startFrom + 12]); - remoteState.discreteAngle = std::stoi(parts[startFrom + 13]); - - } - else if (subType == "ROT") { - remoteState.discreteAngle = std::stoi(parts[4]); - remoteState.discreteMag = std::stof(parts[5]); - int startFrom = 6; - remoteState.position = { std::stof(parts[startFrom]), std::stof(parts[startFrom + 1]), std::stof(parts[startFrom + 2]) }; - Eigen::Quaternionf q( - std::stof(parts[startFrom + 3]), - std::stof(parts[startFrom + 4]), - std::stof(parts[startFrom + 5]), - std::stof(parts[startFrom + 6])); - remoteState.rotation = q.toRotationMatrix(); - - remoteState.currentAngularVelocity = Eigen::Vector3f{ - std::stof(parts[startFrom + 7]), - std::stof(parts[startFrom + 8]), - std::stof(parts[startFrom + 9]) }; - remoteState.velocity = std::stof(parts[startFrom + 10]); - remoteState.selectedVelocity = std::stoi(parts[startFrom + 11]); - remoteState.discreteMag = std::stof(parts[startFrom + 12]); - remoteState.discreteAngle = std::stoi(parts[startFrom + 13]); - } - else if (subType == "PING") { - //remoteState.discreteAngle = std::stoi(parts[4]); - //remoteState.discreteMag = std::stof(parts[5]); + + if (subType == "UPD") { int startFrom = 4; remoteState.position = { std::stof(parts[startFrom]), std::stof(parts[startFrom + 1]), std::stof(parts[startFrom + 2]) }; Eigen::Quaternionf q( @@ -162,181 +115,21 @@ namespace ZL { remoteState.discreteMag = std::stof(parts[startFrom + 12]); remoteState.discreteAngle = std::stoi(parts[startFrom + 13]); } + else + { + throw std::runtime_error("Unknown EVENT subtype: " + subType); + } { std::lock_guard pLock(playersMutex); auto& rp = remotePlayers[remoteId]; - - - if (rp.timedRemoteStates.size() > 0 && rp.timedRemoteStates[rp.timedRemoteStates.size() - 1].lastUpdateServerTime == remoteState.lastUpdateServerTime) - { - rp.timedRemoteStates[rp.timedRemoteStates.size() - 1] = remoteState; - } - else - { - rp.timedRemoteStates.push_back(remoteState); - } - - //rp.timedRemoteStates.push_back(remoteState); - - auto cutoff_time = nowTime - std::chrono::milliseconds(CUTOFF_TIME); - - while (rp.timedRemoteStates.size() > 0 && rp.timedRemoteStates[0].lastUpdateServerTime < cutoff_time) - { - rp.timedRemoteStates.erase(rp.timedRemoteStates.begin()); - } + rp.add_state(remoteState); } - - - - - - - /* - std::lock_guard pLock(playersMutex); - if (remotePlayers.count(remoteId)) { - auto& rp = remotePlayers[remoteId]; - - if (subType == "VEL") { - rp.selectedVelocity = std::stoi(parts[4]); - int startFrom = 5; - rp.position = { std::stof(parts[startFrom]), std::stof(parts[startFrom+1]), std::stof(parts[startFrom+2]) }; - Eigen::Quaternionf q( - std::stof(parts[startFrom + 3]), - std::stof(parts[startFrom + 4]), - std::stof(parts[startFrom + 5]), - std::stof(parts[startFrom + 6])); - rp.rotation = q.toRotationMatrix(); - - rp.currentAngularVelocity = Eigen::Vector3f{ - std::stof(parts[startFrom + 7]), - std::stof(parts[startFrom + 8]), - std::stof(parts[startFrom + 9]) }; - rp.velocity = std::stof(parts[startFrom + 10]); - rp.selectedVelocity = std::stoi(parts[startFrom + 11]); - rp.discreteMag = std::stof(parts[startFrom + 12]); - rp.discreteAngle = std::stoi(parts[startFrom + 13]); - - } - else if (subType == "ROT") { - rp.discreteAngle = std::stoi(parts[4]); - rp.discreteMag = std::stof(parts[5]); - int startFrom = 6; - rp.position = { std::stof(parts[startFrom]), std::stof(parts[startFrom + 1]), std::stof(parts[startFrom + 2]) }; - Eigen::Quaternionf q( - std::stof(parts[startFrom + 3]), - std::stof(parts[startFrom + 4]), - std::stof(parts[startFrom + 5]), - std::stof(parts[startFrom + 6])); - rp.rotation = q.toRotationMatrix(); - - rp.currentAngularVelocity = Eigen::Vector3f{ - std::stof(parts[startFrom + 7]), - std::stof(parts[startFrom + 8]), - std::stof(parts[startFrom + 9]) }; - rp.velocity = std::stof(parts[startFrom + 10]); - rp.selectedVelocity = std::stoi(parts[startFrom + 11]); - rp.discreteMag = std::stof(parts[startFrom + 12]); - rp.discreteAngle = std::stoi(parts[startFrom + 13]); - } - std::cout << "EVENT Received discreteMag=" << rp.discreteMag << std::endl; - - // --- --- - // - long long lag_s = now_ms - sentTime; - - // ( ) - lag_s = std::max(0ll, std::min(lag_s, 1000ll)); - - if (lag_s > 0) { - // "" - // ClientState.h simulate_physics - // discreteAngle/Mag - rp.simulate_physics(lag_s); - - } - - // , Game.cpp - // , now_ms - rp.lastUpdateServerTime = nowTime; - }*/ - } - else if (msg.rfind("WORLD_UPDATE|", 0) == 0) { - parseWorldUpdate(msg, nowTime); } + } } - - void WebSocketClient::parseWorldUpdate(const std::string& msg, std::chrono::system_clock::time_point now_ms) { - // , (TaskManager) - std::lock_guard lock(playersMutex); - - // : WORLD_UPDATE|server_now_ms|count|id,x,y,z,w,qx,qy,qz,v;... - auto parts = split(msg, '|'); - if (parts.size() < 4) return; - - uint64_t serverTime = std::stoull(parts[1]); - int count = std::stoi(parts[2]); - auto playersData = split(parts[3], ';'); - - for (const auto& pData : playersData) { - auto vals = split(pData, ','); - if (vals.size() < 9) continue; - - int id = std::stoi(vals[0]); - if (id == this->clientId) continue; // - - // RemotePlayer - updateRemotePlayer(id, vals, serverTime, now_ms); - } - } - - void WebSocketClient::updateRemotePlayer(int id, const std::vector& vals, uint64_t serverTime, std::chrono::system_clock::time_point now_ms) { - - //auto& rp = remotePlayers[id]; - - ClientState remoteState; - - remoteState.id = id; - - remoteState.position = { std::stof(vals[1]), std::stof(vals[2]), std::stof(vals[3]) }; - remoteState.rotation = Eigen::Quaternionf(std::stof(vals[4]), std::stof(vals[5]), std::stof(vals[6]), std::stof(vals[7])); - - // 3. ( ) - remoteState.velocity = std::stof(vals[8]); - remoteState.currentAngularVelocity = { std::stof(vals[9]), std::stof(vals[10]), std::stof(vals[11]) }; - remoteState.selectedVelocity = std::stoi(vals[12]); - remoteState.discreteMag = std::stof(vals[13]); - remoteState.discreteAngle = std::stoi(vals[14]); - std::chrono::system_clock::time_point uptime_timepoint{ std::chrono::duration_cast(std::chrono::milliseconds(serverTime)) }; - - //std::cout << "PING Received discreteMag=" << rp.discreteMag << std::endl; - - remoteState.lastUpdateServerTime = uptime_timepoint; - - auto& rp = remotePlayers[id]; - - if (rp.timedRemoteStates.size() > 0 && rp.timedRemoteStates[rp.timedRemoteStates.size() - 1].lastUpdateServerTime == remoteState.lastUpdateServerTime) - { - rp.timedRemoteStates[rp.timedRemoteStates.size() - 1] = remoteState; - } - else - { - rp.timedRemoteStates.push_back(remoteState); - } - - auto cutoff_time = now_ms - std::chrono::milliseconds(CUTOFF_TIME); - - while (rp.timedRemoteStates.size() > 0 && rp.timedRemoteStates[0].lastUpdateServerTime < cutoff_time) - { - rp.timedRemoteStates.erase(rp.timedRemoteStates.begin()); - } - //rp.apply_lag_compensation(now_ms); - - } - - void WebSocketClient::Send(const std::string& message) { if (!connected) return; diff --git a/src/network/WebSocketClient.h b/src/network/WebSocketClient.h index e551504..ee7d119 100644 --- a/src/network/WebSocketClient.h +++ b/src/network/WebSocketClient.h @@ -29,7 +29,7 @@ namespace ZL { bool connected = false; int clientId = -1; - std::unordered_map remotePlayers; + std::unordered_map remotePlayers; std::mutex playersMutex; void startAsyncRead(); @@ -41,8 +41,6 @@ namespace ZL { void Connect(const std::string& host, uint16_t port) override; void Poll() override; - void parseWorldUpdate(const std::string& msg, std::chrono::system_clock::time_point now_ms); - void updateRemotePlayer(int id, const std::vector& vals, uint64_t serverTime, std::chrono::system_clock::time_point now_ms); void Send(const std::string& message) override; void doWrite(); @@ -50,7 +48,7 @@ namespace ZL { bool IsConnected() const override { return connected; } int GetClientId() const { return clientId; } - std::unordered_map getRemotePlayers() override { + std::unordered_map getRemotePlayers() override { std::lock_guard lock(playersMutex); return remotePlayers; } From 1a7e424085b1d6adc4d88071c905590df715fd1b Mon Sep 17 00:00:00 2001 From: Vladislav Khorev Date: Sun, 18 Jan 2026 13:13:25 +0300 Subject: [PATCH 17/18] Major clean up --- proj-windows/CMakeLists.txt | 2 +- src/Environment.cpp | 18 +-- src/Environment.h | 18 +-- src/Game.cpp | 202 +++++++--------------------- src/Game.h | 2 - src/SparkEmitter.cpp | 4 +- src/network/ClientState.cpp | 224 ++++++++++++++++++++++++++++++++ src/network/ClientState.h | 203 ++--------------------------- src/network/NetworkInterface.h | 6 +- src/network/RemotePlayer.h | 2 - src/network/WebSocketClient.cpp | 23 ++-- src/network/WebSocketClient.h | 10 +- src/planet/PlanetData.cpp | 92 ++++++------- src/planet/PlanetData.h | 14 +- src/planet/PlanetObject.cpp | 32 ++--- src/planet/StoneObject.h | 28 ++-- 16 files changed, 390 insertions(+), 490 deletions(-) create mode 100644 src/network/ClientState.cpp delete mode 100644 src/network/RemotePlayer.h diff --git a/proj-windows/CMakeLists.txt b/proj-windows/CMakeLists.txt index 774e1e0..dad2933 100644 --- a/proj-windows/CMakeLists.txt +++ b/proj-windows/CMakeLists.txt @@ -54,7 +54,7 @@ add_executable(space-game001 ../src/network/LocalClient.h ../src/network/LocalClient.cpp ../src/network/ClientState.h - ../src/network/RemotePlayer.h + ../src/network/ClientState.cpp ../src/network/WebSocketClient.h ../src/network/WebSocketClient.cpp ) diff --git a/src/Environment.cpp b/src/Environment.cpp index 7c6ab60..069971f 100644 --- a/src/Environment.cpp +++ b/src/Environment.cpp @@ -16,20 +16,12 @@ int Environment::height = 0; float Environment::zoom = 36.f; -bool Environment::leftPressed = false; -bool Environment::rightPressed = false; -bool Environment::upPressed = false; -bool Environment::downPressed = false; - -bool Environment::settings_inverseVertical = false; - SDL_Window* Environment::window = nullptr; bool Environment::showMouse = false; bool Environment::exitGameLoop = false; -Eigen::Matrix3f Environment::shipMatrix = Eigen::Matrix3f::Identity(); Eigen::Matrix3f Environment::inverseShipMatrix = Eigen::Matrix3f::Identity(); @@ -37,15 +29,7 @@ bool Environment::tapDownHold = false; Eigen::Vector2f Environment::tapDownStartPos = { 0, 0 }; Eigen::Vector2f Environment::tapDownCurrentPos = { 0, 0 }; -Eigen::Vector3f Environment::shipPosition = {0,0,45000.f}; - -float Environment::shipVelocity = 0.f; -int Environment::shipSelectedVelocity = 0; -Eigen::Vector3f Environment::currentAngularVelocity; - - -int Environment::lastSentAngle = -1; -float Environment::lastSentMagnitude = 0.0f; +ClientState Environment::shipState; const float Environment::CONST_Z_NEAR = 5.f; const float Environment::CONST_Z_FAR = 5000.f; diff --git a/src/Environment.h b/src/Environment.h index 6c52147..294ee8d 100644 --- a/src/Environment.h +++ b/src/Environment.h @@ -6,6 +6,7 @@ #include "render/OpenGlExtensions.h" #endif #include +#include "network/ClientState.h" namespace ZL { @@ -16,14 +17,6 @@ public: static int height; static float zoom; - static bool leftPressed; - static bool rightPressed; - static bool upPressed; - static bool downPressed; - - static bool settings_inverseVertical; - - static Eigen::Matrix3f shipMatrix; static Eigen::Matrix3f inverseShipMatrix; static SDL_Window* window; @@ -31,18 +24,11 @@ public: static bool showMouse; static bool exitGameLoop; - static bool tapDownHold; static Eigen::Vector2f tapDownStartPos; static Eigen::Vector2f tapDownCurrentPos; - static Eigen::Vector3f shipPosition; - static float shipVelocity; - static int shipSelectedVelocity; - static Eigen::Vector3f currentAngularVelocity; - - static int lastSentAngle; - static float lastSentMagnitude; + static ClientState shipState; static const float CONST_Z_NEAR; static const float CONST_Z_FAR; diff --git a/src/Game.cpp b/src/Game.cpp index d63b260..3fe95f0 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -19,8 +19,6 @@ #include "network/LocalClient.h" #endif -#include "network/RemotePlayer.h" - namespace ZL { #ifdef EMSCRIPTEN @@ -239,15 +237,8 @@ namespace ZL uiManager.setSliderCallback("velocitySlider", [this](const std::string& name, float value) { int newVel = roundf(value * 10); - if (newVel != Environment::shipSelectedVelocity) { + if (newVel != Environment::shipState.selectedVelocity) { newShipVelocity = newVel; - //Environment::shipSelectedVelocity = newVel; - /*auto now_ms = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch() - ).count(); - std::string msg = "VEL:" + std::to_string(now_ms) + ":" + std::to_string(Environment::shipSelectedVelocity); - msg = msg + ":" + formPingMessageContent(); - networkClient->Send(msg);*/ } }); @@ -358,7 +349,7 @@ namespace ZL // 1. Вектор направления от центра планеты к игроку (в мировых координатах) // Предполагаем, что планета в (0,0,0). Если нет, то (shipPosition - planetCenter) - Vector3f playerDirWorld = Environment::shipPosition.normalized(); + Vector3f playerDirWorld = Environment::shipState.position.normalized(); // 2. Направление света в мировом пространстве //Vector3f worldLightDir = Vector3f(1.0f, -1.0f, -1.0f).normalized(); @@ -470,7 +461,7 @@ namespace ZL renderer.LoadIdentity(); renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom }); renderer.RotateMatrix(Environment::inverseShipMatrix); - renderer.TranslateMatrix(-Environment::shipPosition); + renderer.TranslateMatrix(-Environment::shipState.position); renderer.TranslateMatrix({ 0.f, 0.f, 45000.f }); renderer.TranslateMatrix(boxCoordsArr[i].pos); renderer.RotateMatrix(boxCoordsArr[i].m); @@ -528,7 +519,7 @@ namespace ZL CheckGlError(); float skyPercent = 0.0; - float distance = planetObject.distanceToPlanetSurface(Environment::shipPosition); + float distance = planetObject.distanceToPlanetSurface(Environment::shipState.position); if (distance > 1500.f) { skyPercent = 0.0f; @@ -545,7 +536,7 @@ namespace ZL drawCubemap(skyPercent); planetObject.draw(renderer); - if (planetObject.distanceToPlanetSurface(Environment::shipPosition) > 100.f) + if (planetObject.distanceToPlanetSurface(Environment::shipState.position) > 100.f) { glClear(GL_DEPTH_BUFFER_BIT); } @@ -602,7 +593,7 @@ namespace ZL renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom }); renderer.TranslateMatrix({ 0, -6.f, 0 }); //Ship camera offset renderer.RotateMatrix(Environment::inverseShipMatrix); - renderer.TranslateMatrix(-Environment::shipPosition); + renderer.TranslateMatrix(-Environment::shipState.position); Eigen::Vector3f relativePos = playerState.position;// -Environment::shipPosition; @@ -654,22 +645,25 @@ namespace ZL static float pingTimer = 0.0f; pingTimer += delta; if (pingTimer >= 1000.0f) { - std::string pingMsg = "UPD:" + std::to_string(now_ms) + ":" + formPingMessageContent(); + std::string pingMsg = "UPD:" + std::to_string(now_ms) + ":" + Environment::shipState.formPingMessageContent(); networkClient->Send(pingMsg); std::cout << "Sending: " << pingMsg << std::endl; pingTimer = 0.0f; } - if (newShipVelocity != Environment::shipSelectedVelocity) - { - Environment::shipSelectedVelocity = newShipVelocity; + //Handle input: - std::string msg = "UPD:" + std::to_string(now_ms) + ":" + formPingMessageContent(); + if (newShipVelocity != Environment::shipState.selectedVelocity) + { + Environment::shipState.selectedVelocity = newShipVelocity; + + std::string msg = "UPD:" + std::to_string(now_ms) + ":" + Environment::shipState.formPingMessageContent(); networkClient->Send(msg); - //} } + float discreteMag; + int discreteAngle; if (Environment::tapDownHold) { float diffx = Environment::tapDownCurrentPos(0) - Environment::tapDownStartPos(0); @@ -681,140 +675,40 @@ namespace ZL if (rawMag > 10.0f) { // Мертвая зона // 1. Дискретизируем отклонение (0.0 - 1.0 с шагом 0.1) float normalizedMag = min(rawMag / maxRadius, 1.0f); - float discreteMag = std::round(normalizedMag * 10.0f) / 10.0f; + discreteMag = std::round(normalizedMag * 10.0f) / 10.0f; // 2. Дискретизируем угол (0-359 градусов) // atan2 возвращает радианы, переводим в градусы float radians = atan2f(diffy, diffx); - int discreteAngle = static_cast(radians * 180.0f / M_PI); + discreteAngle = static_cast(radians * 180.0f / M_PI); if (discreteAngle < 0) discreteAngle += 360; - - bool sendRotation = false; - - std::cout << "OUTPUT discreteAngle=" << discreteAngle << std::endl; - - // 3. Проверяем, изменились ли параметры значимо для отправки на сервер - if (discreteAngle != Environment::lastSentAngle || discreteMag != Environment::lastSentMagnitude) { - Environment::lastSentAngle = discreteAngle; - Environment::lastSentMagnitude = discreteMag; - - std::string msg = "UPD:" + std::to_string(now_ms) + ":" + formPingMessageContent(); - networkClient->Send(msg); - std::cout << "Sending: " << msg << std::endl; - } - - // 4. Логика вращения (угловое ускорение) - // Теперь используем discreteAngle и discreteMag как "цель" для набора угловой скорости - // ... (код интерполяции скорости к этой цели) - - // Вычисляем целевой вектор оси вращения из дискретного угла - // В 3D пространстве экранные X и Y преобразуются в оси вращения вокруг Y и X соответственно - float rad = static_cast(discreteAngle) * static_cast(M_PI) / 180.0f; - - // Целевая угловая скорость (дискретная сила определяет модуль вектора) - // Вектор {cos, sin, 0} дает нам направление отклонения джойстика - Eigen::Vector3f targetAngularVelDir(sinf(rad), cosf(rad), 0.0f); - Eigen::Vector3f targetAngularVelocity = targetAngularVelDir * discreteMag; - - Eigen::Vector3f diffVel = targetAngularVelocity - Environment::currentAngularVelocity; - float diffLen = diffVel.norm(); - - if (diffLen > 0.0001f) { - // Вычисляем, на сколько мы можем изменить скорость в этом кадре - float maxChange = ANGULAR_ACCEL * static_cast(delta); - - if (diffLen <= maxChange) { - // Если до цели осталось меньше, чем шаг ускорения — просто прыгаем в цель - Environment::currentAngularVelocity = targetAngularVelocity; - } - else { - // Линейно двигаемся в сторону целевого вектора - Environment::currentAngularVelocity += (diffVel / diffLen) * maxChange; - } - } - - // Применяем вращение к матрице корабля - float speedScale = Environment::currentAngularVelocity.norm(); - if (speedScale > 0.0001f) { - // Коэффициент чувствительности вращения - const float ROTATION_SENSITIVITY = 0.002f; - float deltaAlpha = speedScale * static_cast(delta) * ROTATION_SENSITIVITY; - - Eigen::Vector3f axis = Environment::currentAngularVelocity.normalized(); - Eigen::Quaternionf rotateQuat(Eigen::AngleAxisf(deltaAlpha, axis)); - - Environment::shipMatrix = Environment::shipMatrix * rotateQuat.toRotationMatrix(); - Environment::inverseShipMatrix = Environment::shipMatrix.inverse(); - } - } - } - else { - // Если джойстик не зажат — сбрасываем дискретные значения и плавно замедляем вращение - int discreteAngle = -1; - float discreteMag = 0.0f; - - if (discreteAngle != Environment::lastSentAngle || discreteMag != Environment::lastSentMagnitude) { - Environment::lastSentAngle = discreteAngle; - Environment::lastSentMagnitude = discreteMag; - - std::string msg = "UPD:" + std::to_string(now_ms) + ":" + formPingMessageContent(); - networkClient->Send(msg); - std::cout << "Sending: " << msg << std::endl; - } - - - float currentSpeed = Environment::currentAngularVelocity.norm(); - - if (currentSpeed > 0.0001f) { - float drop = ANGULAR_ACCEL * static_cast(delta); - if (currentSpeed <= drop) { - Environment::currentAngularVelocity = Eigen::Vector3f::Zero(); - } - else { - // Уменьшаем модуль вектора, сохраняя направление - Environment::currentAngularVelocity -= (Environment::currentAngularVelocity / currentSpeed) * drop; - } - } - - // Применяем остаточное вращение (инерция) - float speedScale = Environment::currentAngularVelocity.norm(); - if (speedScale > 0.0001f) { - float deltaAlpha = speedScale * static_cast(delta) * 0.002f; - Eigen::Quaternionf rotateQuat(Eigen::AngleAxisf(deltaAlpha, Environment::currentAngularVelocity.normalized())); - Environment::shipMatrix = Environment::shipMatrix * rotateQuat.toRotationMatrix(); - Environment::inverseShipMatrix = Environment::shipMatrix.inverse(); - } - } - - float shipDesiredVelocity = Environment::shipSelectedVelocity * 100.f; - - if (Environment::shipVelocity < shipDesiredVelocity) - { - Environment::shipVelocity += delta * SHIP_ACCEL; - if (Environment::shipVelocity > shipDesiredVelocity) + else { - Environment::shipVelocity = shipDesiredVelocity; + discreteAngle = -1; + discreteMag = 0.0f; } } - else if (Environment::shipVelocity > shipDesiredVelocity) + else { - Environment::shipVelocity -= delta * SHIP_ACCEL; - if (Environment::shipVelocity < shipDesiredVelocity) - { - Environment::shipVelocity = shipDesiredVelocity; - } + discreteAngle = -1; + discreteMag = 0.0f; } - // Движение вперед (существующая логика) - if (fabs(Environment::shipVelocity) > 0.01f) - { - Vector3f velocityDirection = { 0,0, -Environment::shipVelocity * delta / 1000.f }; - Vector3f velocityDirectionAdjusted = Environment::shipMatrix * velocityDirection; - Environment::shipPosition = Environment::shipPosition + velocityDirectionAdjusted; + + if (discreteAngle != Environment::shipState.discreteAngle || discreteMag != Environment::shipState.discreteMag) { + Environment::shipState.discreteAngle = discreteAngle; + Environment::shipState.discreteMag = discreteMag; + + std::string msg = "UPD:" + std::to_string(now_ms) + ":" + Environment::shipState.formPingMessageContent(); + networkClient->Send(msg); + std::cout << "Sending: " << msg << std::endl; } + Environment::shipState.simulate_physics(delta); + Environment::inverseShipMatrix = Environment::shipState.rotation.inverse(); + for (auto& p : projectiles) { if (p && p->isActive()) { p->update(static_cast(delta), renderer); @@ -825,7 +719,7 @@ namespace ZL for (const auto& p : projectiles) { if (p && p->isActive()) { Vector3f worldPos = p->getPosition(); - Vector3f rel = worldPos - Environment::shipPosition; + Vector3f rel = worldPos - Environment::shipState.position; Vector3f camPos = Environment::inverseShipMatrix * rel; projCameraPoints.push_back(camPos); } @@ -852,16 +746,17 @@ namespace ZL explosionEmitter.update(static_cast(delta)); if (shipAlive) { - float distToSurface = planetObject.distanceToPlanetSurface(Environment::shipPosition); + float distToSurface = planetObject.distanceToPlanetSurface(Environment::shipState.position); if (distToSurface <= 0.0f) { + Vector3f localForward = { 0,0,-1 }; - Vector3f worldForward = (Environment::shipMatrix * localForward).normalized(); + Vector3f worldForward = (Environment::shipState.rotation * localForward).normalized(); const float backDistance = 400.0f; - Environment::shipPosition = Environment::shipPosition - worldForward * backDistance; + Environment::shipState.position = Environment::shipState.position - worldForward * backDistance; shipAlive = false; gameOver = true; - Environment::shipVelocity = 0.0f; + Environment::shipState.velocity = 0.0f; showExplosion = true; explosionEmitter.setUseWorldSpace(false); @@ -878,8 +773,8 @@ namespace ZL this->uiGameOverShown = false; this->showExplosion = false; this->explosionEmitter.setEmissionPoints(std::vector()); - Environment::shipPosition = Vector3f{ 0, 0, 45000.f }; - Environment::shipVelocity = 0.0f; + Environment::shipState.position = Vector3f{ 0, 0, 45000.f }; + Environment::shipState.velocity = 0.0f; uiManager.popMenu(); std::cerr << "Game restarted\n"; }); @@ -900,7 +795,7 @@ namespace ZL for (int i = 0; i < boxCoordsArr.size(); ++i) { if (!boxAlive[i]) continue; Vector3f boxWorld = boxCoordsArr[i].pos + Vector3f{ 0.0f, 0.0f, 45000.0f }; - Vector3f diff = Environment::shipPosition - boxWorld; + Vector3f diff = Environment::shipState.position - boxWorld; float thresh = shipCollisionRadius + boxCollisionRadius; if (diff.squaredNorm() <= thresh * thresh) { boxAlive[i] = false; @@ -911,7 +806,7 @@ namespace ZL boxRenderArr[i].texCoordVBO.reset(); showExplosion = true; - Vector3f rel = boxWorld - Environment::shipPosition; + Vector3f rel = boxWorld - Environment::shipState.position; Vector3f camPos = Environment::inverseShipMatrix * rel; explosionEmitter.setUseWorldSpace(true); explosionEmitter.setEmissionPoints(std::vector{ boxWorld }); @@ -936,11 +831,11 @@ namespace ZL const float size = 0.5f; Vector3f localForward = { 0,0,-1 }; - Vector3f worldForward = (Environment::shipMatrix * localForward).normalized(); + Vector3f worldForward = (Environment::shipState.rotation * localForward).normalized(); for (const auto& lo : localOffsets) { - Vector3f worldPos = Environment::shipPosition + Environment::shipMatrix * lo; - Vector3f worldVel = worldForward * (projectileSpeed + Environment::shipVelocity); + Vector3f worldPos = Environment::shipState.position + Environment::shipState.rotation * lo; + Vector3f worldVel = worldForward * (projectileSpeed + Environment::shipState.velocity); for (auto& p : projectiles) { if (!p->isActive()) { @@ -1097,6 +992,7 @@ namespace ZL } } + /* std::string Game::formPingMessageContent() { Eigen::Quaternionf q(Environment::shipMatrix); @@ -1117,7 +1013,7 @@ namespace ZL + std::to_string(Environment::lastSentAngle); return pingMsg; - } + }*/ } // namespace ZL diff --git a/src/Game.h b/src/Game.h index 37ec3a1..0c0d3df 100644 --- a/src/Game.h +++ b/src/Game.h @@ -51,8 +51,6 @@ namespace ZL { void handleUp(int mx, int my); void handleMotion(int mx, int my); - std::string formPingMessageContent(); - SDL_Window* window; SDL_GLContext glContext; diff --git a/src/SparkEmitter.cpp b/src/SparkEmitter.cpp index d162675..7d317bb 100644 --- a/src/SparkEmitter.cpp +++ b/src/SparkEmitter.cpp @@ -75,7 +75,7 @@ namespace ZL { if (particle.active) { Vector3f posCam; if (useWorldSpace) { - Vector3f rel = particle.position - Environment::shipPosition; + Vector3f rel = particle.position - Environment::shipState.position; posCam = Environment::inverseShipMatrix * rel; } else { @@ -94,7 +94,7 @@ namespace ZL { const auto& particle = *particlePtr; Vector3f posCam; if (useWorldSpace) { - Vector3f rel = particle.position - Environment::shipPosition; + Vector3f rel = particle.position - Environment::shipState.position; posCam = Environment::inverseShipMatrix * rel; } else { diff --git a/src/network/ClientState.cpp b/src/network/ClientState.cpp new file mode 100644 index 0000000..12438a1 --- /dev/null +++ b/src/network/ClientState.cpp @@ -0,0 +1,224 @@ +#include "ClientState.h" + + +void ClientState::simulate_physics(size_t delta) { + if (discreteMag > 0.01f) + { + float rad = static_cast(discreteAngle) * static_cast(M_PI) / 180.0f; + + // Целевая угловая скорость (дискретная сила определяет модуль вектора) + // Вектор {cos, sin, 0} дает нам направление отклонения джойстика + Eigen::Vector3f targetAngularVelDir(sinf(rad), cosf(rad), 0.0f); + Eigen::Vector3f targetAngularVelocity = targetAngularVelDir * discreteMag; + + Eigen::Vector3f diffVel = targetAngularVelocity - currentAngularVelocity; + float diffLen = diffVel.norm(); + + if (diffLen > 0.0001f) { + // Вычисляем, на сколько мы можем изменить скорость в этом кадре + float maxChange = ANGULAR_ACCEL * static_cast(delta); + + if (diffLen <= maxChange) { + // Если до цели осталось меньше, чем шаг ускорения — просто прыгаем в цель + currentAngularVelocity = targetAngularVelocity; + } + + else { + // Линейно двигаемся в сторону целевого вектора + currentAngularVelocity += (diffVel / diffLen) * maxChange; + } + } + } + else + { + float currentSpeed = currentAngularVelocity.norm(); + + if (currentSpeed > 0.0001f) { + float drop = ANGULAR_ACCEL * static_cast(delta); + if (currentSpeed <= drop) { + currentAngularVelocity = Eigen::Vector3f::Zero(); + } + else { + // Уменьшаем модуль вектора, сохраняя направление + currentAngularVelocity -= (currentAngularVelocity / currentSpeed) * drop; + } + } + } + + float speedScale = currentAngularVelocity.norm(); + if (speedScale > 0.0001f) { + // Коэффициент чувствительности вращения + + float deltaAlpha = speedScale * static_cast(delta) * ROTATION_SENSITIVITY; + + Eigen::Vector3f axis = currentAngularVelocity.normalized(); + Eigen::Quaternionf rotateQuat(Eigen::AngleAxisf(deltaAlpha, axis)); + + rotation = rotation * rotateQuat.toRotationMatrix(); + } + + + // 4. Линейное изменение линейной скорости + float shipDesiredVelocity = selectedVelocity * 100.f; + + if (velocity < shipDesiredVelocity) + { + velocity += delta * SHIP_ACCEL; + if (velocity > shipDesiredVelocity) + { + velocity = shipDesiredVelocity; + } + } + else if (velocity > shipDesiredVelocity) + { + velocity -= delta * SHIP_ACCEL; + if (velocity < shipDesiredVelocity) + { + velocity = shipDesiredVelocity; + } + } + + if (fabs(velocity) > 0.01f) + { + Eigen::Vector3f velocityDirection = { 0,0, -velocity * delta / 1000.f }; + Eigen::Vector3f velocityDirectionAdjusted = rotation * velocityDirection; + position = position + velocityDirectionAdjusted; + } +} + +void ClientState::apply_lag_compensation(std::chrono::system_clock::time_point nowTime) { + + // 2. Вычисляем задержку + long long deltaMs = 0; + if (nowTime > lastUpdateServerTime) { + deltaMs = std::chrono::duration_cast(nowTime - lastUpdateServerTime).count(); + } + + // 3. Защита от слишком больших скачков (Clamp) + // Если лаг более 500мс, ограничиваем его, чтобы избежать резких рывков + long long final_lag_ms = deltaMs;//min(deltaMs, 500ll); + + if (final_lag_ms > 0) { + // Доматываем симуляцию на величину задержки + // Мы предполагаем, что за это время параметры управления не менялись + simulate_physics(final_lag_ms); + } +} + +void ClientState::handle_full_sync(const std::vector& parts, int startFrom) { + // Позиция + position = { std::stof(parts[startFrom]), std::stof(parts[startFrom + 1]), std::stof(parts[startFrom + 2]) }; + + Eigen::Quaternionf q( + std::stof(parts[startFrom + 3]), + std::stof(parts[startFrom + 4]), + std::stof(parts[startFrom + 5]), + std::stof(parts[startFrom + 6])); + rotation = q.toRotationMatrix(); + + currentAngularVelocity = Eigen::Vector3f{ + std::stof(parts[startFrom + 7]), + std::stof(parts[startFrom + 8]), + std::stof(parts[startFrom + 9]) }; + velocity = std::stof(parts[startFrom + 10]); + selectedVelocity = std::stoi(parts[startFrom + 11]); + discreteMag = std::stof(parts[startFrom + 12]); + discreteAngle = std::stoi(parts[startFrom + 13]); +} + +std::string ClientState::formPingMessageContent() +{ + Eigen::Quaternionf q(rotation); + + std::string pingMsg = std::to_string(position.x()) + ":" + + std::to_string(position.y()) + ":" + + std::to_string(position.z()) + ":" + + std::to_string(q.w()) + ":" + + std::to_string(q.x()) + ":" + + std::to_string(q.y()) + ":" + + std::to_string(q.z()) + ":" + + std::to_string(currentAngularVelocity.x()) + ":" + + std::to_string(currentAngularVelocity.y()) + ":" + + std::to_string(currentAngularVelocity.z()) + ":" + + std::to_string(velocity) + ":" + + std::to_string(selectedVelocity) + ":" + + std::to_string(discreteMag) + ":" // Используем те же static переменные из блока ROT + + std::to_string(discreteAngle); + + return pingMsg; +} + + +void ClientStateInterval::add_state(const ClientState& state) +{ + auto nowTime = std::chrono::system_clock::now(); + + if (timedStates.size() > 0 && timedStates[timedStates.size() - 1].lastUpdateServerTime == state.lastUpdateServerTime) + { + timedStates[timedStates.size() - 1] = state; + } + else + { + timedStates.push_back(state); + } + + auto cutoff_time = nowTime - std::chrono::milliseconds(CUTOFF_TIME); + + while (timedStates.size() > 0 && timedStates[0].lastUpdateServerTime < cutoff_time) + { + timedStates.erase(timedStates.begin()); + } +} + +bool ClientStateInterval::canFetchClientStateAtTime(std::chrono::system_clock::time_point targetTime) const +{ + if (timedStates.empty()) + { + return false; + } + if (timedStates[0].lastUpdateServerTime > targetTime) + { + return false; + } + + return true; +} + +ClientState ClientStateInterval::fetchClientStateAtTime(std::chrono::system_clock::time_point targetTime) const { + + ClientState closestState; + + if (timedStates.empty()) + { + throw std::runtime_error("No timed client states available"); + return closestState; + } + if (timedStates[0].lastUpdateServerTime > targetTime) + { + throw std::runtime_error("Found time but it is in future"); + return closestState; + } + if (timedStates.size() == 1) + { + closestState = timedStates[0]; + closestState.apply_lag_compensation(targetTime); + return closestState; + } + + + for (size_t i = 0; i < timedStates.size() - 1; ++i) + { + const auto& earlierState = timedStates[i]; + const auto& laterState = timedStates[i + 1]; + if (earlierState.lastUpdateServerTime <= targetTime && laterState.lastUpdateServerTime >= targetTime) + { + closestState = earlierState; + closestState.apply_lag_compensation(targetTime); + return closestState; + } + } + + closestState = timedStates[timedStates.size() - 1]; + closestState.apply_lag_compensation(targetTime); + return closestState; +} diff --git a/src/network/ClientState.h b/src/network/ClientState.h index 711533d..ac28def 100644 --- a/src/network/ClientState.h +++ b/src/network/ClientState.h @@ -25,213 +25,28 @@ struct ClientState { float velocity = 0.0f; int selectedVelocity = 0; float discreteMag = 0; - int discreteAngle = -1; + int discreteAngle = -1; // Для расчета лага std::chrono::system_clock::time_point lastUpdateServerTime; - void simulate_physics(size_t delta) { - if (discreteMag > 0.01f) - { - float rad = static_cast(discreteAngle) * static_cast(M_PI) / 180.0f; + void simulate_physics(size_t delta); - // Целевая угловая скорость (дискретная сила определяет модуль вектора) - // Вектор {cos, sin, 0} дает нам направление отклонения джойстика - Eigen::Vector3f targetAngularVelDir(sinf(rad), cosf(rad), 0.0f); - Eigen::Vector3f targetAngularVelocity = targetAngularVelDir * discreteMag; + void apply_lag_compensation(std::chrono::system_clock::time_point nowTime); - Eigen::Vector3f diffVel = targetAngularVelocity - currentAngularVelocity; - float diffLen = diffVel.norm(); + void handle_full_sync(const std::vector& parts, int startFrom); - if (diffLen > 0.0001f) { - // Вычисляем, на сколько мы можем изменить скорость в этом кадре - float maxChange = ANGULAR_ACCEL * static_cast(delta); - - if (diffLen <= maxChange) { - // Если до цели осталось меньше, чем шаг ускорения — просто прыгаем в цель - currentAngularVelocity = targetAngularVelocity; - } - - else { - // Линейно двигаемся в сторону целевого вектора - currentAngularVelocity += (diffVel / diffLen) * maxChange; - } - } - } - else - { - float currentSpeed = currentAngularVelocity.norm(); - - if (currentSpeed > 0.0001f) { - float drop = ANGULAR_ACCEL * static_cast(delta); - if (currentSpeed <= drop) { - currentAngularVelocity = Eigen::Vector3f::Zero(); - } - else { - // Уменьшаем модуль вектора, сохраняя направление - currentAngularVelocity -= (currentAngularVelocity / currentSpeed) * drop; - } - } - } - - float speedScale = currentAngularVelocity.norm(); - if (speedScale > 0.0001f) { - // Коэффициент чувствительности вращения - - float deltaAlpha = speedScale * static_cast(delta) * ROTATION_SENSITIVITY; - - Eigen::Vector3f axis = currentAngularVelocity.normalized(); - Eigen::Quaternionf rotateQuat(Eigen::AngleAxisf(deltaAlpha, axis)); - - rotation = rotation * rotateQuat.toRotationMatrix(); - } - - - // 4. Линейное изменение линейной скорости - float shipDesiredVelocity = selectedVelocity * 100.f; - - if (velocity < shipDesiredVelocity) - { - velocity += delta * SHIP_ACCEL; - if (velocity > shipDesiredVelocity) - { - velocity = shipDesiredVelocity; - } - } - else if (velocity > shipDesiredVelocity) - { - velocity -= delta * SHIP_ACCEL; - if (velocity < shipDesiredVelocity) - { - velocity = shipDesiredVelocity; - } - } - - if (fabs(velocity) > 0.01f) - { - Eigen::Vector3f velocityDirection = { 0,0, -velocity * delta / 1000.f }; - Eigen::Vector3f velocityDirectionAdjusted = rotation * velocityDirection; - position = position + velocityDirectionAdjusted; - } - } - - void apply_lag_compensation(std::chrono::system_clock::time_point nowTime) { - - // 2. Вычисляем задержку - long long deltaMs = 0; - if (nowTime > lastUpdateServerTime) { - deltaMs = std::chrono::duration_cast(nowTime - lastUpdateServerTime).count(); - } - - // 3. Защита от слишком больших скачков (Clamp) - // Если лаг более 500мс, ограничиваем его, чтобы избежать резких рывков - long long final_lag_ms = deltaMs;//min(deltaMs, 500ll); - - if (final_lag_ms > 0) { - // Доматываем симуляцию на величину задержки - // Мы предполагаем, что за это время параметры управления не менялись - simulate_physics(final_lag_ms); - } - } - - void handle_full_sync(const std::vector& parts, int startFrom) { - // Позиция - position = { std::stof(parts[startFrom]), std::stof(parts[startFrom+1]), std::stof(parts[startFrom+2]) }; - - Eigen::Quaternionf q( - std::stof(parts[startFrom+3]), - std::stof(parts[startFrom+4]), - std::stof(parts[startFrom+5]), - std::stof(parts[startFrom+6])); - rotation = q.toRotationMatrix(); - - currentAngularVelocity = Eigen::Vector3f{ - std::stof(parts[startFrom+7]), - std::stof(parts[startFrom+8]), - std::stof(parts[startFrom+9]) }; - velocity = std::stof(parts[startFrom+10]); - selectedVelocity = std::stoi(parts[startFrom+11]); - discreteMag = std::stof(parts[startFrom+12]); - discreteAngle = std::stoi(parts[startFrom+13]); - } + std::string formPingMessageContent(); }; - struct ClientStateInterval { std::vector timedStates; - void add_state(const ClientState& state) - { - auto nowTime = std::chrono::system_clock::now(); + void add_state(const ClientState& state); - if (timedStates.size() > 0 && timedStates[timedStates.size() - 1].lastUpdateServerTime == state.lastUpdateServerTime) - { - timedStates[timedStates.size() - 1] = state; - } - else - { - timedStates.push_back(state); - } + bool canFetchClientStateAtTime(std::chrono::system_clock::time_point targetTime) const; - auto cutoff_time = nowTime - std::chrono::milliseconds(CUTOFF_TIME); - - while (timedStates.size() > 0 && timedStates[0].lastUpdateServerTime < cutoff_time) - { - timedStates.erase(timedStates.begin()); - } - } - - bool canFetchClientStateAtTime(std::chrono::system_clock::time_point targetTime) const - { - if (timedStates.empty()) - { - return false; - } - if (timedStates[0].lastUpdateServerTime > targetTime) - { - return false; - } - - return true; - } - - ClientState fetchClientStateAtTime(std::chrono::system_clock::time_point targetTime) const { - - ClientState closestState; - - if (timedStates.empty()) - { - throw std::runtime_error("No timed client states available"); - return closestState; - } - if (timedStates[0].lastUpdateServerTime > targetTime) - { - throw std::runtime_error("Found time but it is in future"); - return closestState; - } - if (timedStates.size() == 1) - { - closestState = timedStates[0]; - closestState.apply_lag_compensation(targetTime); - return closestState; - } - - - for (size_t i = 0; i < timedStates.size() - 1; ++i) - { - const auto& earlierState = timedStates[i]; - const auto& laterState = timedStates[i + 1]; - if (earlierState.lastUpdateServerTime <= targetTime && laterState.lastUpdateServerTime >= targetTime) - { - closestState = earlierState; - closestState.apply_lag_compensation(targetTime); - return closestState; - } - } - - closestState = timedStates[timedStates.size() - 1]; - closestState.apply_lag_compensation(targetTime); - return closestState; - } + ClientState fetchClientStateAtTime(std::chrono::system_clock::time_point targetTime) const; }; + diff --git a/src/network/NetworkInterface.h b/src/network/NetworkInterface.h index 03f93a5..4873f07 100644 --- a/src/network/NetworkInterface.h +++ b/src/network/NetworkInterface.h @@ -1,10 +1,10 @@ -#pragma once +#pragma once #include #include #include #include "ClientState.h" -// NetworkInterface.h - +// NetworkInterface.h - »нтерфейс дл¤ разных типов соединений namespace ZL { class INetworkClient { @@ -13,7 +13,7 @@ namespace ZL { virtual void Connect(const std::string& host, uint16_t port) = 0; virtual void Send(const std::string& message) = 0; virtual bool IsConnected() const = 0; - virtual void Poll() = 0; // + virtual void Poll() = 0; // ƒл¤ обработки вход¤щих пакетов virtual std::unordered_map getRemotePlayers() = 0; }; } diff --git a/src/network/RemotePlayer.h b/src/network/RemotePlayer.h deleted file mode 100644 index 5a1664d..0000000 --- a/src/network/RemotePlayer.h +++ /dev/null @@ -1,2 +0,0 @@ -#pragma once -#include "ClientState.h" diff --git a/src/network/WebSocketClient.cpp b/src/network/WebSocketClient.cpp index f7a0790..e9d1789 100644 --- a/src/network/WebSocketClient.cpp +++ b/src/network/WebSocketClient.cpp @@ -1,9 +1,8 @@ -#include "WebSocketClient.h" +#include "WebSocketClient.h" #include #include -#include "RemotePlayer.h" -// split +// Вспомогательный split std::vector split(const std::string& s, char delimiter) { std::vector tokens; std::string token; @@ -23,13 +22,13 @@ namespace ZL { ws_ = std::make_unique>(ioc_); - // handshake + // Выполняем синхронный коннект и handshake для простоты старта boost::beast::get_lowest_layer(*ws_).connect(results); ws_->handshake(host, "/"); connected = true; - // TaskManager + // Запускаем асинхронное чтение в пуле потоков TaskManager startAsyncRead(); } @@ -53,12 +52,12 @@ namespace ZL { } void WebSocketClient::processIncomingMessage(const std::string& msg) { - // ... + // Логика парсинга... if (msg.rfind("ID:", 0) == 0) { clientId = std::stoi(msg.substr(3)); } - // + // Безопасно кладем в очередь для главного потока std::lock_guard lock(queueMutex); messageQueue.push(msg); } @@ -138,14 +137,14 @@ namespace ZL { std::lock_guard lock(writeMutex_); writeQueue_.push(ss); - // , + // Если сейчас ничего не записывается, инициируем первую запись if (!isWriting_) { doWrite(); } } void WebSocketClient::doWrite() { - // + // Эта функция всегда вызывается под мьютексом или из колбэка if (writeQueue_.empty()) { isWriting_ = false; return; @@ -154,7 +153,7 @@ namespace ZL { isWriting_ = true; auto message = writeQueue_.front(); - // self (shared_from_this), + // Захватываем self (shared_from_this), чтобы объект не удалился во время записи ws_->async_write( boost::asio::buffer(*message), [this, message](boost::beast::error_code ec, std::size_t) { @@ -164,8 +163,8 @@ namespace ZL { } std::lock_guard lock(writeMutex_); - writeQueue_.pop(); // - doWrite(); // + writeQueue_.pop(); // Удаляем отправленное сообщение + doWrite(); // Проверяем следующее } ); } diff --git a/src/network/WebSocketClient.h b/src/network/WebSocketClient.h index ee7d119..09d3616 100644 --- a/src/network/WebSocketClient.h +++ b/src/network/WebSocketClient.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once // WebSocketClient.h #include "NetworkInterface.h" @@ -12,19 +12,19 @@ namespace ZL { class WebSocketClient : public INetworkClient { private: - // io_context TaskManager + // Переиспользуем io_context из TaskManager boost::asio::io_context& ioc_; - // + // Объекты переехали в члены класса std::unique_ptr> ws_; boost::beast::flat_buffer buffer_; std::queue messageQueue; - std::mutex queueMutex; // messageQueue + std::mutex queueMutex; // Защита для messageQueue std::queue> writeQueue_; bool isWriting_ = false; - std::mutex writeMutex_; // + std::mutex writeMutex_; // Отдельный мьютекс для очереди записи bool connected = false; int clientId = -1; diff --git a/src/planet/PlanetData.cpp b/src/planet/PlanetData.cpp index d4f2f02..a1a1254 100644 --- a/src/planet/PlanetData.cpp +++ b/src/planet/PlanetData.cpp @@ -1,4 +1,4 @@ -#include "PlanetData.h" +#include "PlanetData.h" #include #include #include @@ -10,13 +10,13 @@ namespace ZL { const float PlanetData::PLANET_RADIUS = 20000.f; const Vector3f PlanetData::PLANET_CENTER_OFFSET = Vector3f{ 0.f, 0.f, 0.0f }; - // --- ( PlanetObject.cpp) --- + // --- Константы диапазонов (перенесены из PlanetObject.cpp) --- VertexID generateEdgeID(const VertexID& id1, const VertexID& id2) { return id1 < id2 ? id1 + "_" + id2 : id2 + "_" + id1; } - // () + // Вспомогательная функция для проекции (локальная) static Vector3f projectPointOnPlane(const Vector3f& P, const Vector3f& A, const Vector3f& B, const Vector3f& C) { Vector3f AB = B + A * (-1.0f); Vector3f AC = C + A * (-1.0f); @@ -138,18 +138,18 @@ namespace ZL { std::vector PlanetData::getBestTriangleUnderCamera(const Vector3f& viewerPosition) { - const LodLevel& finalLod = planetMeshLods[currentLod]; // LOD + const LodLevel& finalLod = planetMeshLods[currentLod]; // Работаем с текущим активным LOD Vector3f targetDir = (viewerPosition - PLANET_CENTER_OFFSET).normalized(); int bestTriangle = -1; float maxDot = -1.0f; - // 1: "" - // , N- - // LOD0, . - // ( 5-6 ) + // Шаг 1: Быстрый поиск ближайшего треугольника по "центроиду" + // Чтобы не проверять все, можно проверять каждый N-й или использовать + // предварительно вычисленные центры для LOD0, чтобы сузить круг. + // Но для надежности пройдемся по массиву (для 5-6 подразделений это быстро) for (int i = 0; i < (int)finalLod.triangles.size(); ++i) { - // + // Вычисляем примерное направление на треугольник Vector3f triDir = (finalLod.triangles[i].data[0] + finalLod.triangles[i].data[1] + finalLod.triangles[i].data[2]).normalized(); @@ -172,22 +172,22 @@ namespace ZL { float currentDist = shipLocal.norm(); Vector3f targetDir = shipLocal.normalized(); - // ( / ) - // , . + // Желаемый радиус покрытия на поверхности планеты (в метрах/единицах движка) + // Подбери это значение так, чтобы камни вокруг корабля всегда были видны. const float desiredCoverageRadius = 3000.0f; - // . - // (currentDist ), - // , . + // Вычисляем порог косинуса на основе желаемого радиуса и текущего расстояния. + // Чем мы дальше (currentDist больше), тем меньше должен быть угол отклонения + // от нормали, чтобы захватить ту же площадь. float angle = atan2(desiredCoverageRadius, currentDist); float searchThreshold = cos(angle); - // , + // Ограничитель, чтобы не захватить всю планету или вообще ничего searchThreshold = std::clamp(searchThreshold, 0.90f, 0.9999f); std::vector result; for (int i = 0; i < (int)finalLod.triangles.size(); ++i) { - // ( LodLevel ) + // Используем центроид (можно кэшировать в LodLevel для скорости) Vector3f triDir = (finalLod.triangles[i].data[0] + finalLod.triangles[i].data[1] + finalLod.triangles[i].data[2]).normalized(); @@ -205,7 +205,7 @@ namespace ZL { std::vector output; for (const auto& t : input) { - // ID + // Вершины и их ID const Vector3f& a = t.data[0]; const Vector3f& b = t.data[1]; const Vector3f& c = t.data[2]; @@ -213,7 +213,7 @@ namespace ZL { const VertexID& id_b = t.ids[1]; const VertexID& id_c = t.ids[2]; - // 1. () + // 1. Вычисляем середины (координаты) Vector3f m_ab = ((a + b) * 0.5f).normalized(); Vector3f m_bc = ((b + c) * 0.5f).normalized(); Vector3f m_ac = ((a + c) * 0.5f).normalized(); @@ -226,12 +226,12 @@ namespace ZL { Vector3f pm_bc = m_bc; Vector3f pm_ac = m_ac; - // 2. ID + // 2. Вычисляем ID новых вершин VertexID id_mab = generateEdgeID(id_a, id_b); VertexID id_mbc = generateEdgeID(id_b, id_c); VertexID id_mac = generateEdgeID(id_a, id_c); - // 3. 4 + // 3. Формируем 4 новых треугольника output.emplace_back(Triangle{ {a, pm_ab, pm_ac}, {id_a, id_mab, id_mac} }); // 0 output.emplace_back(Triangle{ {pm_ab, b, pm_bc}, {id_mab, id_b, id_mbc} }); // 1 output.emplace_back(Triangle{ {pm_ac, pm_bc, c}, {id_mac, id_mbc, id_c} }); // 2 @@ -282,31 +282,31 @@ namespace ZL { Vector2f(1.0f, 0.0f) }; - const Vector3f colorPinkish = { 1.0f, 0.8f, 0.82f }; // - const Vector3f colorYellowish = { 1.0f, 1.0f, 0.75f }; // + const Vector3f colorPinkish = { 1.0f, 0.8f, 0.82f }; // Слегка розоватый + const Vector3f colorYellowish = { 1.0f, 1.0f, 0.75f }; // Слегка желтоватый - const float colorFrequency = 4.0f; // + const float colorFrequency = 4.0f; // Масштаб пятен for (const auto& t : lod.triangles) { - // --- ( GetRotationForTriangle) --- + // --- Вычисляем локальный базис треугольника (как в GetRotationForTriangle) --- Vector3f vA = t.data[0]; Vector3f vB = t.data[1]; Vector3f vC = t.data[2]; - Vector3f x_axis = (vC - vB).normalized(); // U + Vector3f x_axis = (vC - vB).normalized(); // Направление U Vector3f edge1 = vB - vA; Vector3f edge2 = vC - vA; - Vector3f z_axis = edge1.cross(edge2).normalized(); // + Vector3f z_axis = edge1.cross(edge2).normalized(); // Нормаль плоскости - // ( ) + // Проверка направления нормали наружу (от центра планеты) Vector3f centerToTri = (vA + vB + vC).normalized(); if (z_axis.dot(centerToTri) < 0) { z_axis = z_axis * -1.0f; } - Vector3f y_axis = z_axis.cross(x_axis).normalized(); // V + Vector3f y_axis = z_axis.cross(x_axis).normalized(); // Направление V for (int i = 0; i < 3; ++i) { lod.vertexData.PositionData.push_back(t.data[i]); @@ -315,7 +315,7 @@ namespace ZL { lod.vertexData.TangentData.push_back(x_axis); lod.vertexData.BinormalData.push_back(y_axis); - // + // Используем один шум для выбора между розовым и желтым Vector3f dir = t.data[i].normalized(); float blendFactor = colorPerlin.noise( dir(0) * colorFrequency, @@ -323,10 +323,10 @@ namespace ZL { dir(2) * colorFrequency ); - // [-1, 1] [0, 1] + // Приводим шум из диапазона [-1, 1] в [0, 1] blendFactor = blendFactor * 0.5f + 0.5f; - // + // Линейная интерполяция между двумя цветами Vector3f finalColor; finalColor = colorPinkish + blendFactor * (colorYellowish - colorPinkish); @@ -339,17 +339,17 @@ namespace ZL { LodLevel PlanetData::generateSphere(int subdivisions, float noiseCoeff) { const float t = (1.0f + std::sqrt(5.0f)) / 2.0f; - // 12 + // 12 базовых вершин икосаэдра std::vector icosaVertices = { {-1, t, 0}, { 1, t, 0}, {-1, -t, 0}, { 1, -t, 0}, { 0, -1, t}, { 0, 1, t}, { 0, -1, -t}, { 0, 1, -t}, { t, 0, -1}, { t, 0, 1}, {-t, 0, -1}, {-t, 0, 1} }; - // + // Нормализуем вершины for (auto& v : icosaVertices) v = v.normalized(); - // 20 + // 20 граней икосаэдра struct IndexedTri { int v1, v2, v3; }; std::vector faces = { {0, 11, 5}, {0, 5, 1}, {0, 1, 7}, {0, 7, 10}, {0, 10, 11}, @@ -365,7 +365,7 @@ namespace ZL { tri.data[1] = icosaVertices[f.v2]; tri.data[2] = icosaVertices[f.v3]; - // ID ( ) + // Генерируем ID для базовых вершин (можно использовать их координаты) for (int i = 0; i < 3; ++i) { tri.ids[i] = std::to_string(tri.data[i](0)) + "_" + std::to_string(tri.data[i](1)) + "_" + @@ -374,15 +374,15 @@ namespace ZL { geometry.push_back(tri); } - // 3. N + // 3. Разбиваем N раз for (int i = 0; i < subdivisions; i++) { - geometry = subdivideTriangles(geometry, 0.0f); // + geometry = subdivideTriangles(geometry, 0.0f); // Шум пока игнорируем } - // 4. LodLevel (v2tMap) + // 4. Создаем LodLevel и заполняем топологию (v2tMap) LodLevel lodLevel = createLodLevel(geometry); - // v2tMap ( ) + // Пересобираем v2tMap (она критична для релаксации) lodLevel.v2tMap.clear(); for (size_t i = 0; i < geometry.size(); ++i) { for (int j = 0; j < 3; ++j) { @@ -390,12 +390,12 @@ namespace ZL { } } - // 5. (Lloyd-like) - // 5-10 + // 5. Применяем итеративную релаксацию (Lloyd-like) + // 5-10 итераций достаточно для отличной сетки applySphericalRelaxation(lodLevel, 8); - // 6. - // ... ( Perlin) + // 6. Накладываем шум и обновляем атрибуты + // ... (твой код наложения шума через Perlin) recalculateMeshAttributes(lodLevel); return lodLevel; @@ -410,7 +410,7 @@ namespace ZL { for (auto const& [vID, connectedTris] : lod.v2tMap) { Vector3f centroid(0, 0, 0); - // + // Находим среднюю точку среди центров всех соседних треугольников for (int triIdx : connectedTris) { const auto& tri = lod.triangles[triIdx]; Vector3f faceCenter = (tri.data[0] + tri.data[1] + tri.data[2]) * (1.0f / 3.0f); @@ -419,11 +419,11 @@ namespace ZL { centroid = centroid * (1.0f / (float)connectedTris.size()); - // + // Проецируем обратно на единичную сферу newPositions[vID] = centroid.normalized(); } - // + // Синхронизируем данные в треугольниках for (auto& tri : lod.triangles) { for (int i = 0; i < 3; ++i) { tri.data[i] = newPositions[tri.ids[i]]; diff --git a/src/planet/PlanetData.h b/src/planet/PlanetData.h index 8c875da..9430239 100644 --- a/src/planet/PlanetData.h +++ b/src/planet/PlanetData.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "utils/Perlin.h" #include "render/Renderer.h" @@ -16,7 +16,7 @@ namespace ZL { struct Vector3fComparator { bool operator()(const Eigen::Vector3f& a, const Eigen::Vector3f& b) const { - // (x, y, z) + // Лексикографическое сравнение (x, затем y, затем z) if (a.x() != b.x()) return a.x() < b.x(); if (a.y() != b.y()) return a.y() < b.y(); return a.z() < b.z(); @@ -87,11 +87,11 @@ namespace ZL { std::array planetMeshLods; LodLevel planetAtmosphereLod; - int currentLod; // + int currentLod; // Логический текущий уровень детализации //std::map initialVertexMap; - // + // Внутренние методы генерации std::vector subdivideTriangles(const std::vector& inputTriangles, float noiseCoeff); LodLevel createLodLevel(const std::vector& triangles); void recalculateMeshAttributes(LodLevel& lod); @@ -101,17 +101,17 @@ namespace ZL { void init(); - // ( ) + // Методы доступа к данным (для рендерера) const LodLevel& getLodLevel(int level) const; const LodLevel& getAtmosphereLod() const; int getCurrentLodIndex() const; int getMaxLodIndex() const; - // + // Логика std::pair calculateZRange(float distanceToSurface); float distanceToPlanetSurfaceFast(const Vector3f& viewerPosition); - // , + // Возвращает индексы треугольников, видимых камерой std::vector getBestTriangleUnderCamera(const Vector3f& viewerPosition); std::vector getTrianglesUnderCameraNew2(const Vector3f& viewerPosition); diff --git a/src/planet/PlanetObject.cpp b/src/planet/PlanetObject.cpp index 9048b6c..1216607 100644 --- a/src/planet/PlanetObject.cpp +++ b/src/planet/PlanetObject.cpp @@ -101,15 +101,15 @@ namespace ZL { // 1. Проверка порога движения (оптимизация из текущего кода) float movementThreshold = 1.0f; - if ((Environment::shipPosition - lastUpdatePos).squaredNorm() < movementThreshold * movementThreshold + if ((Environment::shipState.position - lastUpdatePos).squaredNorm() < movementThreshold * movementThreshold && !triangleIndicesToDraw.empty()) { //processMainThreadTasks(); // Все равно обрабатываем очередь OpenGL задач return; } - lastUpdatePos = Environment::shipPosition; + lastUpdatePos = Environment::shipState.position; // 2. Получаем список видимых треугольников - auto newIndices = planetData.getTrianglesUnderCameraNew2(Environment::shipPosition); + auto newIndices = planetData.getTrianglesUnderCameraNew2(Environment::shipState.position); std::sort(newIndices.begin(), newIndices.end()); // 3. Анализируем, что нужно загрузить @@ -305,7 +305,7 @@ namespace ZL { renderer.EnableVertexAttribArray(vTexCoordName); - float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipPosition); + float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipState.position); auto zRange = planetData.calculateZRange(dist); const float currentZNear = zRange.first; const float currentZFar = zRange.second; @@ -320,7 +320,7 @@ namespace ZL { renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom }); renderer.RotateMatrix(Environment::inverseShipMatrix); - renderer.TranslateMatrix(-Environment::shipPosition); + renderer.TranslateMatrix(-Environment::shipState.position); const Matrix4f viewMatrix = renderer.GetCurrentModelViewMatrix(); @@ -332,7 +332,7 @@ namespace ZL { Matrix3f mr = GetRotationForTriangle(tr); // Та же матрица, что и при запекании // Позиция камеры (корабля) в мире - renderer.RenderUniform3fv("uViewPos", Environment::shipPosition.data()); + renderer.RenderUniform3fv("uViewPos", Environment::shipState.position.data()); //renderer.RenderUniform1f("uHeightScale", 0.08f); renderer.RenderUniform1f("uHeightScale", 0.0f); @@ -345,7 +345,7 @@ namespace ZL { renderer.RenderUniform3fv("uLightDirWorld", sunDirWorld.data()); // Направление от центра планеты к игроку для расчета дня/ночи - Vector3f playerDirWorld = Environment::shipPosition.normalized(); + Vector3f playerDirWorld = Environment::shipState.position.normalized(); renderer.RenderUniform3fv("uPlayerDirWorld", playerDirWorld.data()); // Тот же фактор освещенности игрока @@ -394,7 +394,7 @@ namespace ZL { renderer.EnableVertexAttribArray(vTexCoordName); - float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipPosition); + float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipState.position); auto zRange = planetData.calculateZRange(dist); const float currentZNear = zRange.first; const float currentZFar = zRange.second; @@ -409,17 +409,17 @@ namespace ZL { renderer.LoadIdentity(); renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom }); renderer.RotateMatrix(Environment::inverseShipMatrix); - renderer.TranslateMatrix(-Environment::shipPosition); + renderer.TranslateMatrix(-Environment::shipState.position); renderer.RenderUniform1f("uDistanceToPlanetSurface", dist); renderer.RenderUniform1f("uCurrentZFar", currentZFar); - renderer.RenderUniform3fv("uViewPos", Environment::shipPosition.data()); - //std::cout << "uViewPos" << Environment::shipPosition << std::endl; + renderer.RenderUniform3fv("uViewPos", Environment::shipState.position.data()); + //std::cout << "uViewPos" << Environment::shipState.position << std::endl; // PlanetObject.cpp, метод drawStones Vector3f sunDirWorld = Vector3f(1.0f, -1.0f, -1.0f).normalized(); renderer.RenderUniform3fv("uLightDirWorld", sunDirWorld.data()); - Vector3f playerDirWorld = Environment::shipPosition.normalized(); + Vector3f playerDirWorld = Environment::shipState.position.normalized(); float playerLightFactor = max(0.0f, (playerDirWorld.dot(-sunDirWorld) + 0.2f) / 1.2f); renderer.RenderUniform1f("uPlayerLightFactor", playerLightFactor); @@ -479,7 +479,7 @@ namespace ZL { renderer.EnableVertexAttribArray(vPositionName); renderer.EnableVertexAttribArray(vNormalName); - float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipPosition); + float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipState.position); auto zRange = planetData.calculateZRange(dist); float currentZNear = zRange.first; float currentZFar = zRange.second; @@ -499,7 +499,7 @@ namespace ZL { renderer.LoadIdentity(); renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom }); renderer.RotateMatrix(Environment::inverseShipMatrix); - renderer.TranslateMatrix(-Environment::shipPosition); + renderer.TranslateMatrix(-Environment::shipState.position); const Matrix4f viewMatrix = renderer.GetCurrentModelViewMatrix(); @@ -536,7 +536,7 @@ namespace ZL { renderer.RenderUniform3fv("uWorldLightDir", worldLightDir.data()); // 1. Рассчитываем uPlayerLightFactor (как в Game.cpp) - Vector3f playerDirWorld = Environment::shipPosition.normalized(); + Vector3f playerDirWorld = Environment::shipState.position.normalized(); Vector3f sunDirWorld = Vector3f(1.0f, -1.0f, -1.0f).normalized(); // Насколько игрок на свету @@ -549,7 +549,7 @@ namespace ZL { // 3. Убедитесь, что uSkyColor тоже передан (в коде выше его не было) renderer.RenderUniform3fv("uSkyColor", color.data()); - //Vector3f playerDirWorld = Environment::shipPosition.normalized(); + //Vector3f playerDirWorld = Environment::shipState.position.normalized(); renderer.RenderUniform3fv("uPlayerDirWorld", playerDirWorld.data()); glEnable(GL_BLEND); diff --git a/src/planet/StoneObject.h b/src/planet/StoneObject.h index a2218b4..6827a74 100644 --- a/src/planet/StoneObject.h +++ b/src/planet/StoneObject.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "render/Renderer.h" #include "PlanetData.h" @@ -6,11 +6,11 @@ namespace ZL { struct StoneParams { - static const float BASE_SCALE; // - static const float MIN_AXIS_SCALE; // / - static const float MAX_AXIS_SCALE; // / - static const float MIN_PERTURBATION; // - static const float MAX_PERTURBATION; // + static const float BASE_SCALE; // Общий размер камня + static const float MIN_AXIS_SCALE; // Минимальное растяжение/сжатие по оси + static const float MAX_AXIS_SCALE; // Максимальное растяжение/сжатие по оси + static const float MIN_PERTURBATION; // Минимальное радиальное возмущение вершины + static const float MAX_PERTURBATION; // Максимальное радиальное возмущение вершины static const int STONES_PER_TRIANGLE; }; @@ -23,19 +23,19 @@ namespace ZL { }; enum class ChunkStatus { - Empty, // - Generating, // TaskManager (CPU) - ReadyToUpload, // , - Live // GPU + Empty, // Данных нет + Generating, // Задача в TaskManager (CPU) + ReadyToUpload, // Данные в памяти, ждут очереди в главный поток + Live // Загружено в GPU и готово к отрисовке }; struct StoneGroup { - // mesh.PositionData inflate() + // mesh.PositionData и прочие будут заполняться в inflate() VertexDataStruct mesh; std::vector> allInstances; - // + // Очищает старую геометрию и генерирует новую для указанных индексов std::vector inflate(int count); VertexRenderStruct inflateOne(int index, float scaleModifier); @@ -43,13 +43,13 @@ namespace ZL { std::vector statuses; - // + // Инициализация статусов при создании группы void initStatuses() { statuses.assign(allInstances.size(), ChunkStatus::Empty); } }; - // , + // Теперь возвращает заготовку со всеми параметрами, но без тяжелого меша StoneGroup CreateStoneGroupData(uint64_t globalSeed, const LodLevel& lodLevel); Triangle createLocalTriangle(const Triangle& sampleTri); From 67c0696061d87b8aa8d6a65ea67775572d254d3f Mon Sep 17 00:00:00 2001 From: Vladislav Khorev Date: Sun, 18 Jan 2026 13:18:59 +0300 Subject: [PATCH 18/18] By default make offline --- proj-windows/CMakeLists.txt | 2 +- server/server.cpp | 6 +++--- src/network/LocalClient.cpp | 12 ++---------- src/network/LocalClient.h | 4 ++++ src/network/WebSocketClient.cpp | 8 ++++++-- src/network/WebSocketClient.h | 5 ++++- 6 files changed, 20 insertions(+), 17 deletions(-) diff --git a/proj-windows/CMakeLists.txt b/proj-windows/CMakeLists.txt index dad2933..06e1ed3 100644 --- a/proj-windows/CMakeLists.txt +++ b/proj-windows/CMakeLists.txt @@ -78,7 +78,7 @@ target_compile_definitions(space-game001 PRIVATE WIN32_LEAN_AND_MEAN PNG_ENABLED SDL_MAIN_HANDLED - NETWORK +# NETWORK # SIMPLIFIED ) diff --git a/server/server.cpp b/server/server.cpp index 06f2331..1853f88 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include #include @@ -12,7 +12,7 @@ #include #include "../src/network/ClientState.h" -// split +// Вспомогательный split std::vector split(const std::string& s, char delimiter) { std::vector tokens; std::string token; @@ -78,7 +78,7 @@ class Session : public std::enable_shared_from_this { std::lock_guard lock(g_sessions_mutex); for (auto& session : g_sessions) { - if (session->get_id() != id_) { // + if (session->get_id() != id_) { // Не шлем отправителю session->send_message(event_msg); } } diff --git a/src/network/LocalClient.cpp b/src/network/LocalClient.cpp index 57d3266..264f526 100644 --- a/src/network/LocalClient.cpp +++ b/src/network/LocalClient.cpp @@ -8,19 +8,11 @@ namespace ZL { } void LocalClient::Poll() { - - if (!messageQueue.empty()) { - std::string msg = messageQueue.front(); - messageQueue.pop(); - std::cout << "LocalClient received message: " << msg << std::endl; - } } void LocalClient::Send(const std::string& message) { - if (message == "PING") - { - messageQueue.push("PONG"); - } } + + } \ No newline at end of file diff --git a/src/network/LocalClient.h b/src/network/LocalClient.h index 3e9b15e..55994b3 100644 --- a/src/network/LocalClient.h +++ b/src/network/LocalClient.h @@ -17,5 +17,9 @@ namespace ZL { bool IsConnected() const override { return true; } int GetClientId() const { return 1; } + + std::unordered_map getRemotePlayers() override { + return std::unordered_map(); + } }; } \ No newline at end of file diff --git a/src/network/WebSocketClient.cpp b/src/network/WebSocketClient.cpp index e9d1789..ce9dd3e 100644 --- a/src/network/WebSocketClient.cpp +++ b/src/network/WebSocketClient.cpp @@ -1,4 +1,6 @@ -#include "WebSocketClient.h" +#ifdef NETWORK + +#include "WebSocketClient.h" #include #include @@ -168,4 +170,6 @@ namespace ZL { } ); } -} \ No newline at end of file +} + +#endif diff --git a/src/network/WebSocketClient.h b/src/network/WebSocketClient.h index 09d3616..d8b108f 100644 --- a/src/network/WebSocketClient.h +++ b/src/network/WebSocketClient.h @@ -1,5 +1,7 @@ #pragma once +#ifdef NETWORK + // WebSocketClient.h #include "NetworkInterface.h" #include @@ -53,4 +55,5 @@ namespace ZL { return remotePlayers; } }; -} \ No newline at end of file +} +#endif