diff --git a/proj-windows/CMakeLists.txt b/proj-windows/CMakeLists.txt index 2355739..bc1a45f 100644 --- a/proj-windows/CMakeLists.txt +++ b/proj-windows/CMakeLists.txt @@ -81,7 +81,7 @@ target_compile_definitions(space-game001 PRIVATE PNG_ENABLED SDL_MAIN_HANDLED NETWORK - SIMPLIFIED +# SIMPLIFIED ) # Линкуем с SDL2main, если он вообще установлен diff --git a/server/server.cpp b/server/server.cpp index 75d8874..a8c978e 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -146,6 +146,7 @@ class Session : public std::enable_shared_from_this { } } + void sendBoxesToClient() { std::lock_guard lock(g_boxes_mutex); @@ -168,30 +169,6 @@ class Session : public std::enable_shared_from_this { send_message(boxMsg); } - void send_message(std::string msg) { - auto ss = std::make_shared(std::move(msg)); - - if (is_writing_) { - - ws_.async_write(net::buffer(*ss), - [self = shared_from_this(), ss](beast::error_code ec, std::size_t) { - if (ec) { - std::cerr << "Write error: " << ec.message() << std::endl; - } - }); - } - else { - is_writing_ = true; - ws_.async_write(net::buffer(*ss), - [self = shared_from_this(), ss](beast::error_code ec, std::size_t) { - self->is_writing_ = false; - if (ec) { - std::cerr << "Write error: " << ec.message() << std::endl; - } - }); - } - } - public: explicit Session(tcp::socket&& socket, int id) @@ -237,6 +214,46 @@ public: return id_; } + ClientState get_latest_state(std::chrono::system_clock::time_point now) { + if (timedClientStates.timedStates.empty()) { + return {}; + } + + // 1. Берем самое последнее известное состояние + ClientState latest = timedClientStates.timedStates.back(); + + // 3. Применяем компенсацию лага (экстраполяцию). + // Функция внутри использует simulate_physics, чтобы переместить объект + // из точки lastUpdateServerTime в точку now. + latest.apply_lag_compensation(now); + + return latest; + } + + void send_message(std::string msg) { + auto ss = std::make_shared(std::move(msg)); + + if (is_writing_) { + + ws_.async_write(net::buffer(*ss), + [self = shared_from_this(), ss](beast::error_code ec, std::size_t) { + if (ec) { + std::cerr << "Write error: " << ec.message() << std::endl; + } + }); + } + else { + is_writing_ = true; + ws_.async_write(net::buffer(*ss), + [self = shared_from_this(), ss](beast::error_code ec, std::size_t) { + self->is_writing_ = false; + if (ec) { + std::cerr << "Write error: " << ec.message() << std::endl; + } + }); + } + } + private: void do_read() { ws_.async_read(buffer_, [self = shared_from_this()](beast::error_code ec, std::size_t) { @@ -260,8 +277,33 @@ private: }; void update_world(net::steady_timer& timer, net::io_context& ioc) { + static auto last_snapshot_time = std::chrono::steady_clock::now(); + auto now = std::chrono::steady_clock::now(); - // TODO: Renew game state + // Рассылка Snapshot раз в 1000мс + /* + if (std::chrono::duration_cast(now - last_snapshot_time).count() >= 1000) { + last_snapshot_time = now; + + auto system_now = std::chrono::system_clock::now(); + + std::string snapshot_msg = "SNAPSHOT:" + std::to_string( + std::chrono::duration_cast( + system_now.time_since_epoch()).count() + ); + + std::lock_guard lock(g_sessions_mutex); + + // Формируем общую строку состояний всех игроков + for (auto& session : g_sessions) { + ClientState st = session->get_latest_state(system_now); + snapshot_msg += "|" + std::to_string(session->get_id()) + ":" + st.formPingMessageContent(); + } + + for (auto& session : g_sessions) { + session->send_message(snapshot_msg); + } + }*/ timer.expires_after(std::chrono::milliseconds(50)); timer.async_wait([&](const boost::system::error_code& ec) { diff --git a/src/Game.cpp b/src/Game.cpp index 42c843d..4848277 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -732,13 +732,13 @@ namespace ZL // Биндим текстуру корабля один раз для всех удаленных игроков (оптимизация батчинга) glBindTexture(GL_TEXTURE_2D, spaceshipTexture->getTexID()); - auto now = std::chrono::system_clock::now(); + /*auto now = std::chrono::system_clock::now(); //Apply server delay: now -= std::chrono::milliseconds(CLIENT_DELAY); latestRemotePlayers = networkClient->getRemotePlayers(); - + */ // Если сервер прислал коробки, применяем их однократно вместо локальной генерации if (!serverBoxesApplied && networkClient) { auto sboxes = networkClient->getServerBoxes(); @@ -761,15 +761,9 @@ namespace ZL } // Итерируемся по актуальным данным из extrapolateRemotePlayers - for (auto const& [id, remotePlayer] : latestRemotePlayers) { - - if (!remotePlayer.canFetchClientStateAtTime(now)) - { - continue; - } - - ClientState playerState = remotePlayer.fetchClientStateAtTime(now); + for (auto const& [id, remotePlayer] : remotePlayerStates) { + const ClientState& playerState = remotePlayer; renderer.PushMatrix(); renderer.LoadIdentity(); @@ -809,18 +803,15 @@ namespace ZL glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Берем удаленных игроков - latestRemotePlayers = networkClient->getRemotePlayers(); + //latestRemotePlayers = networkClient->getRemotePlayers(); auto now = std::chrono::system_clock::now(); now -= std::chrono::milliseconds(CLIENT_DELAY); - for (auto const& [id, remotePlayer] : latestRemotePlayers) + for (auto const& [id, remotePlayer] : remotePlayerStates) { - if (!remotePlayer.canFetchClientStateAtTime(now)) - continue; - - ClientState st = remotePlayer.fetchClientStateAtTime(now); - + + const ClientState& st = remotePlayer; // Позиция корабля в мире Vector3f shipWorld = st.position; @@ -861,6 +852,9 @@ namespace ZL lastTickCount = std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch() ).count(); + + lastTickCount = (lastTickCount / 50)*50; + return; } @@ -869,6 +863,8 @@ namespace ZL std::chrono::system_clock::now().time_since_epoch() ).count(); + newTickCount = (newTickCount / 50) * 50; + if (newTickCount - lastTickCount > CONST_TIMER_INTERVAL) { size_t delta = newTickCount - lastTickCount; @@ -882,15 +878,7 @@ namespace ZL sparkEmitter.update(static_cast(delta)); planetObject.update(static_cast(delta)); - static float pingTimer = 0.0f; - pingTimer += delta; - if (pingTimer >= 1000.0f) { - std::string pingMsg = "UPD:" + std::to_string(now_ms) + ":" + Environment::shipState.formPingMessageContent(); - networkClient->Send(pingMsg); - std::cout << "Sending: " << pingMsg << std::endl; - pingTimer = 0.0f; - } //Handle input: @@ -946,9 +934,44 @@ namespace ZL std::cout << "Sending: " << msg << std::endl; } - Environment::shipState.simulate_physics(delta); + long long leftoverDelta = delta; + while (leftoverDelta > 0) + { + long long miniDelta = 50; + Environment::shipState.simulate_physics(miniDelta); + leftoverDelta -= miniDelta; + } Environment::inverseShipMatrix = Environment::shipState.rotation.inverse(); + static float pingTimer = 0.0f; + pingTimer += delta; + if (pingTimer >= 1000.0f) { + std::string pingMsg = "UPD:" + std::to_string(now_ms) + ":" + Environment::shipState.formPingMessageContent(); + + networkClient->Send(pingMsg); + std::cout << "Sending: " << pingMsg << std::endl; + pingTimer = 0.0f; + } + + + auto latestRemotePlayers = networkClient->getRemotePlayers(); + + std::chrono::system_clock::time_point nowRounded{ std::chrono::milliseconds(newTickCount) }; + + + for (auto const& [id, remotePlayer] : latestRemotePlayers) { + + if (!remotePlayer.canFetchClientStateAtTime(nowRounded)) + { + continue; + } + + ClientState playerState = remotePlayer.fetchClientStateAtTime(nowRounded); + + remotePlayerStates[id] = playerState; + + } + 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 10177c4..b8b2ce6 100644 --- a/src/Game.h +++ b/src/Game.h @@ -73,7 +73,8 @@ namespace ZL { std::vector boxLabels; std::unique_ptr textRenderer; - std::unordered_map latestRemotePlayers; + //std::unordered_map latestRemotePlayers; + std::unordered_map remotePlayerStates; float newShipVelocity = 0; diff --git a/src/network/ClientState.cpp b/src/network/ClientState.cpp index 7de142d..8f52c0e 100644 --- a/src/network/ClientState.cpp +++ b/src/network/ClientState.cpp @@ -157,6 +157,16 @@ void ClientState::apply_lag_compensation(std::chrono::system_clock::time_point n deltaMs = std::chrono::duration_cast(nowTime - lastUpdateServerTime).count(); } + long long deltaMsLeftover = deltaMs; + + while (deltaMsLeftover > 0) + { + long long miniDelta = 50; + simulate_physics(miniDelta); + deltaMsLeftover -= miniDelta; + } + + /* // 3. Защита от слишком больших скачков (Clamp) // Если лаг более 500мс, ограничиваем его, чтобы избежать резких рывков long long final_lag_ms = deltaMs;//min(deltaMs, 500ll); @@ -165,7 +175,7 @@ void ClientState::apply_lag_compensation(std::chrono::system_clock::time_point n // Доматываем симуляцию на величину задержки // Мы предполагаем, что за это время параметры управления не менялись simulate_physics(final_lag_ms); - } + }*/ } void ClientState::handle_full_sync(const std::vector& parts, int startFrom) { diff --git a/src/network/WebSocketClient.cpp b/src/network/WebSocketClient.cpp index 6e6b7b8..91db004 100644 --- a/src/network/WebSocketClient.cpp +++ b/src/network/WebSocketClient.cpp @@ -202,6 +202,35 @@ namespace ZL { } } + if (msg.rfind("SNAPSHOT:", 0) == 0) { + auto mainParts = split(msg.substr(9), '|'); // Отсекаем "SNAPSHOT:" и делим по игрокам + if (mainParts.empty()) continue; + + uint64_t serverTimestamp = std::stoull(mainParts[0]); + std::chrono::system_clock::time_point serverTime{ std::chrono::milliseconds(serverTimestamp) }; + + for (size_t i = 1; i < mainParts.size(); ++i) { + auto playerParts = split(mainParts[i], ':'); + if (playerParts.size() < 15) continue; // ID + 14 полей ClientState + + int rId = std::stoi(playerParts[0]); + if (rId == clientId) continue; // Свое состояние игрок знает лучше всех (Client Side Prediction) + + ClientState remoteState; + remoteState.id = rId; + remoteState.lastUpdateServerTime = serverTime; + + // Используем твой handle_full_sync, начиная со 2-го индекса (пропускаем ID в playerParts) + remoteState.handle_full_sync(playerParts, 1); + + { + std::lock_guard pLock(playersMutex); + remotePlayers[rId].add_state(remoteState); + } + } + continue; + } + } } void WebSocketClient::Send(const std::string& message) {