From be2aa76f8b075bfb8ffd6d4f12940f53750a0533 Mon Sep 17 00:00:00 2001 From: Vladislav Khorev Date: Thu, 26 Feb 2026 22:26:43 +0300 Subject: [PATCH] Working on server optimization --- server/server.cpp | 429 +++++++++++++++++------------------- src/network/ClientState.cpp | 4 +- src/network/ClientState.h | 2 +- 3 files changed, 204 insertions(+), 231 deletions(-) diff --git a/server/server.cpp b/server/server.cpp index 18d08c0..1bcd2a6 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -323,7 +323,6 @@ private: receivedState.shipType = this->shipType; timedClientStates.add_state(receivedState); - retranslateMessage(cleanMessage); } else if (type == "RESPAWN") { { @@ -433,16 +432,6 @@ private: } } - void retranslateMessage(const std::string& msg) { - std::string event_msg = "EVENT:" + std::to_string(id_) + ":" + msg; - - std::lock_guard lock(g_sessions_mutex); - for (auto& session : g_sessions) { - if (session->get_id() != id_) { - session->send_message(event_msg); - } - } - } }; void broadcastToAll(const std::string& message) { @@ -454,273 +443,257 @@ void broadcastToAll(const std::string& message) { 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(); - /*static uint64_t lastTickCount = 0; + static auto last_snapshot_time = std::chrono::system_clock::now(); - if (lastTickCount == 0) { - //lastTickCount = SDL_GetTicks64(); - lastTickCount = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch() - ).count(); + auto now = std::chrono::system_clock::now(); + uint64_t now_ms = static_cast( + std::chrono::duration_cast(now.time_since_epoch()).count()); - lastTickCount = (lastTickCount / 50) * 50; - - return; - } - - - auto newTickCount = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch() - ).count(); - - newTickCount = (newTickCount / 50) * 50; - - int64_t deltaMs = static_cast(newTickCount - lastTickCount); - - std::chrono::system_clock::time_point nowRounded = std::chrono::system_clock::time_point(std::chrono::milliseconds(newTickCount)); - */ - // For each player - // Get letest state + add time (until newTickCount) - // Calculate if collisions with boxes - - - - - // Рассылка Snapshot раз в 1000мс - /* - if (std::chrono::duration_cast(now - last_snapshot_time).count() >= 1000) { + // --- Snapshot every 500ms --- + /*if (std::chrono::duration_cast(now - last_snapshot_time).count() >= 500) { 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::string snapshot_msg = "SNAPSHOT:" + std::to_string(now_ms); std::lock_guard lock(g_sessions_mutex); - - // Формируем общую строку состояний всех игроков for (auto& session : g_sessions) { - ClientState st = session->get_latest_state(system_now); + ClientState st = session->get_latest_state(now); snapshot_msg += "|" + std::to_string(session->get_id()) + ":" + st.formPingMessageContent(); } - for (auto& session : g_sessions) { session->send_message(snapshot_msg); } }*/ - const std::chrono::milliseconds interval(50); - timer.expires_after(interval); + // --- Tick: broadcast each player's latest state to all others (20Hz) --- + // Send the raw last-known state with its original timestamp, NOT an extrapolated one. + // Extrapolating here causes snap-back: if a player stops rotating, the server would + // keep sending over-rotated positions until the new state arrives, then B snaps back. + { + std::lock_guard lock(g_sessions_mutex); + for (auto& sender : g_sessions) { + if (sender->timedClientStates.timedStates.empty()) continue; - timer.async_wait([&](const boost::system::error_code& ec) { - if (ec) return; + const ClientState& st = sender->timedClientStates.timedStates.back(); + uint64_t stateTime = static_cast( + std::chrono::duration_cast( + st.lastUpdateServerTime.time_since_epoch()).count()); - auto now = std::chrono::system_clock::now(); - uint64_t now_ms = static_cast(std::chrono::duration_cast(now.time_since_epoch()).count()); + std::string event_msg = "EVENT:" + std::to_string(sender->get_id()) + + ":UPD:" + std::to_string(stateTime) + ":" + st.formPingMessageContent(); - std::vector deathEvents; - - { - std::lock_guard pl(g_projectiles_mutex); - std::vector indicesToRemove; - - float dt = 50.0f / 1000.0f; - - for (size_t i = 0; i < g_projectiles.size(); ++i) { - auto& pr = g_projectiles[i]; - - pr.pos += pr.vel * dt; - - if (now_ms > pr.spawnMs + static_cast(pr.lifeMs)) { - indicesToRemove.push_back(static_cast(i)); - continue; - } - - bool hitDetected = false; - - { - std::lock_guard lm(g_sessions_mutex); - std::lock_guard gd(g_dead_mutex); - - for (auto& session : g_sessions) { - int targetId = session->get_id(); - - if (targetId == pr.shooterId) continue; - if (g_dead_players.find(targetId) != g_dead_players.end()) continue; - - ClientState targetState; - if (!session->fetchStateAtTime(now, targetState)) continue; - - Eigen::Vector3f diff = pr.pos - targetState.position; - const float shipRadius = 15.0f; - const float projectileRadius = 1.5f; - float combinedRadius = shipRadius + projectileRadius; - - if (diff.squaredNorm() <= combinedRadius * combinedRadius) { - DeathInfo death; - death.targetId = targetId; - death.serverTime = now_ms; - death.position = pr.pos; - death.killerId = pr.shooterId; - - deathEvents.push_back(death); - g_dead_players.insert(targetId); - indicesToRemove.push_back(static_cast(i)); - hitDetected = true; - - std::cout << "Server: *** HIT DETECTED! ***" << std::endl; - std::cout << "Server: Projectile at (" - << pr.pos.x() << ", " << pr.pos.y() << ", " << pr.pos.z() - << ") hit player " << targetId << std::endl; - break; - } - } - } - - if (hitDetected) continue; - } - - if (!indicesToRemove.empty()) { - std::sort(indicesToRemove.rbegin(), indicesToRemove.rend()); - for (int idx : indicesToRemove) { - if (idx >= 0 && idx < (int)g_projectiles.size()) { - g_projectiles.erase(g_projectiles.begin() + idx); - } + for (auto& receiver : g_sessions) { + if (receiver->get_id() != sender->get_id()) { + receiver->send_message(event_msg); } } } + } - { - std::lock_guard bm(g_boxes_mutex); - //const float projectileHitRadius = 1.5f; - const float projectileHitRadius = 5.0f; - const float boxCollisionRadius = 2.0f; + // --- Tick: projectile movement and hit detection --- + const float dt = 50.0f / 1000.0f; + std::vector deathEvents; - std::vector> boxProjectileCollisions; + { + std::lock_guard pl(g_projectiles_mutex); + std::vector indicesToRemove; - for (size_t bi = 0; bi < g_serverBoxes.size(); ++bi) { - if (g_serverBoxes[bi].destroyed) continue; + for (size_t i = 0; i < g_projectiles.size(); ++i) { + auto& pr = g_projectiles[i]; - Eigen::Vector3f boxWorld = g_serverBoxes[bi].position + Eigen::Vector3f(0.0f, 0.0f, 45000.0f); + pr.pos += pr.vel * dt; - for (size_t pi = 0; pi < g_projectiles.size(); ++pi) { - const auto& pr = g_projectiles[pi]; - Eigen::Vector3f diff = pr.pos - boxWorld; - //std::cout << "diff norm is " << diff.norm() << std::endl; - float thresh = boxCollisionRadius + projectileHitRadius; - - if (diff.squaredNorm() <= thresh * thresh) { - boxProjectileCollisions.push_back({ bi, pi }); - } - } + if (now_ms > pr.spawnMs + static_cast(pr.lifeMs)) { + indicesToRemove.push_back(static_cast(i)); + continue; } - for (const auto& [boxIdx, projIdx] : boxProjectileCollisions) { - g_serverBoxes[boxIdx].destroyed = true; + bool hitDetected = false; - Eigen::Vector3f boxWorld = g_serverBoxes[boxIdx].position + Eigen::Vector3f(0.0f, 0.0f, 45000.0f); - - BoxDestroyedInfo destruction; - destruction.boxIndex = static_cast(boxIdx); - destruction.serverTime = now_ms; - destruction.position = boxWorld; - destruction.destroyedBy = g_projectiles[projIdx].shooterId; - - { - std::lock_guard dm(g_boxDestructions_mutex); - g_boxDestructions.push_back(destruction); - } - - std::cout << "Server: Box " << boxIdx << " destroyed by projectile from player " - << g_projectiles[projIdx].shooterId << std::endl; - } - } - - { - std::lock_guard bm(g_boxes_mutex); - std::lock_guard lm(g_sessions_mutex); - - const float shipCollisionRadius = 15.0f; - const float boxCollisionRadius = 2.0f; - - for (size_t bi = 0; bi < g_serverBoxes.size(); ++bi) { - if (g_serverBoxes[bi].destroyed) continue; - - Eigen::Vector3f boxWorld = g_serverBoxes[bi].position + Eigen::Vector3f(0.0f, 0.0f, 45000.0f); + { + std::lock_guard lm(g_sessions_mutex); + std::lock_guard gd(g_dead_mutex); for (auto& session : g_sessions) { - { - std::lock_guard gd(g_dead_mutex); - if (g_dead_players.find(session->get_id()) != g_dead_players.end()) { - continue; - } - } + int targetId = session->get_id(); - ClientState shipState; - if (!session->fetchStateAtTime(now, shipState)) continue; + if (targetId == pr.shooterId) continue; + if (g_dead_players.find(targetId) != g_dead_players.end()) continue; - Eigen::Vector3f diff = shipState.position - boxWorld; - float thresh = shipCollisionRadius + boxCollisionRadius; + ClientState targetState; + if (!session->fetchStateAtTime(now, targetState)) continue; - if (diff.squaredNorm() <= thresh * thresh) { - g_serverBoxes[bi].destroyed = true; + Eigen::Vector3f diff = pr.pos - targetState.position; + const float shipRadius = 15.0f; + const float projectileRadius = 1.5f; + float combinedRadius = shipRadius + projectileRadius; - BoxDestroyedInfo destruction; - destruction.boxIndex = static_cast(bi); - destruction.serverTime = now_ms; - destruction.position = boxWorld; - destruction.destroyedBy = session->get_id(); + if (diff.squaredNorm() <= combinedRadius * combinedRadius) { + DeathInfo death; + death.targetId = targetId; + death.serverTime = now_ms; + death.position = pr.pos; + death.killerId = pr.shooterId; - { - std::lock_guard dm(g_boxDestructions_mutex); - g_boxDestructions.push_back(destruction); - } + deathEvents.push_back(death); + g_dead_players.insert(targetId); + indicesToRemove.push_back(static_cast(i)); + hitDetected = true; - std::cout << "Server: Box " << bi << " destroyed by ship collision with player " - << session->get_id() << std::endl; + std::cout << "Server: *** HIT DETECTED! ***" << std::endl; + std::cout << "Server: Projectile at (" + << pr.pos.x() << ", " << pr.pos.y() << ", " << pr.pos.z() + << ") hit player " << targetId << std::endl; break; } } } + + if (hitDetected) continue; } - if (!deathEvents.empty()) { - for (const auto& death : deathEvents) { - std::string deadMsg = "DEAD:" + - std::to_string(death.serverTime) + ":" + - std::to_string(death.targetId) + ":" + - std::to_string(death.position.x()) + ":" + - std::to_string(death.position.y()) + ":" + - std::to_string(death.position.z()) + ":" + - std::to_string(death.killerId); + if (!indicesToRemove.empty()) { + std::sort(indicesToRemove.rbegin(), indicesToRemove.rend()); + for (int idx : indicesToRemove) { + if (idx >= 0 && idx < (int)g_projectiles.size()) { + g_projectiles.erase(g_projectiles.begin() + idx); + } + } + } + } - broadcastToAll(deadMsg); + // --- Tick: box-projectile collisions --- + { + std::lock_guard bm(g_boxes_mutex); + const float projectileHitRadius = 5.0f; + const float boxCollisionRadius = 2.0f; - std::cout << "Server: Sent DEAD event - Player " << death.targetId - << " killed by " << death.killerId << std::endl; + std::vector> boxProjectileCollisions; + + for (size_t bi = 0; bi < g_serverBoxes.size(); ++bi) { + if (g_serverBoxes[bi].destroyed) continue; + + Eigen::Vector3f boxWorld = g_serverBoxes[bi].position + Eigen::Vector3f(0.0f, 0.0f, 45000.0f); + + for (size_t pi = 0; pi < g_projectiles.size(); ++pi) { + const auto& pr = g_projectiles[pi]; + Eigen::Vector3f diff = pr.pos - boxWorld; + float thresh = boxCollisionRadius + projectileHitRadius; + + if (diff.squaredNorm() <= thresh * thresh) { + boxProjectileCollisions.push_back({ bi, pi }); + } } } - { - std::lock_guard dm(g_boxDestructions_mutex); - for (const auto& destruction : g_boxDestructions) { - std::string boxMsg = "BOX_DESTROYED:" + - std::to_string(destruction.boxIndex) + ":" + - std::to_string(destruction.serverTime) + ":" + - std::to_string(destruction.position.x()) + ":" + - std::to_string(destruction.position.y()) + ":" + - std::to_string(destruction.position.z()) + ":" + - std::to_string(destruction.destroyedBy); + for (const auto& [boxIdx, projIdx] : boxProjectileCollisions) { + g_serverBoxes[boxIdx].destroyed = true; - broadcastToAll(boxMsg); - std::cout << "Server: Broadcasted BOX_DESTROYED for box " << destruction.boxIndex << std::endl; + Eigen::Vector3f boxWorld = g_serverBoxes[boxIdx].position + Eigen::Vector3f(0.0f, 0.0f, 45000.0f); + + BoxDestroyedInfo destruction; + destruction.boxIndex = static_cast(boxIdx); + destruction.serverTime = now_ms; + destruction.position = boxWorld; + destruction.destroyedBy = g_projectiles[projIdx].shooterId; + + { + std::lock_guard dm(g_boxDestructions_mutex); + g_boxDestructions.push_back(destruction); } - g_boxDestructions.clear(); - } + std::cout << "Server: Box " << boxIdx << " destroyed by projectile from player " + << g_projectiles[projIdx].shooterId << std::endl; + } + } + + // --- Tick: box-ship collisions --- + { + std::lock_guard bm(g_boxes_mutex); + std::lock_guard lm(g_sessions_mutex); + + const float shipCollisionRadius = 15.0f; + const float boxCollisionRadius = 2.0f; + + for (size_t bi = 0; bi < g_serverBoxes.size(); ++bi) { + if (g_serverBoxes[bi].destroyed) continue; + + Eigen::Vector3f boxWorld = g_serverBoxes[bi].position + Eigen::Vector3f(0.0f, 0.0f, 45000.0f); + + for (auto& session : g_sessions) { + { + std::lock_guard gd(g_dead_mutex); + if (g_dead_players.find(session->get_id()) != g_dead_players.end()) { + continue; + } + } + + ClientState shipState; + if (!session->fetchStateAtTime(now, shipState)) continue; + + Eigen::Vector3f diff = shipState.position - boxWorld; + float thresh = shipCollisionRadius + boxCollisionRadius; + + if (diff.squaredNorm() <= thresh * thresh) { + g_serverBoxes[bi].destroyed = true; + + BoxDestroyedInfo destruction; + destruction.boxIndex = static_cast(bi); + destruction.serverTime = now_ms; + destruction.position = boxWorld; + destruction.destroyedBy = session->get_id(); + + { + std::lock_guard dm(g_boxDestructions_mutex); + g_boxDestructions.push_back(destruction); + } + + std::cout << "Server: Box " << bi << " destroyed by ship collision with player " + << session->get_id() << std::endl; + break; + } + } + } + } + + // --- Broadcast deaths --- + for (const auto& death : deathEvents) { + std::string deadMsg = "DEAD:" + + std::to_string(death.serverTime) + ":" + + std::to_string(death.targetId) + ":" + + std::to_string(death.position.x()) + ":" + + std::to_string(death.position.y()) + ":" + + std::to_string(death.position.z()) + ":" + + std::to_string(death.killerId); + + broadcastToAll(deadMsg); + + std::cout << "Server: Sent DEAD event - Player " << death.targetId + << " killed by " << death.killerId << std::endl; + } + + // --- Broadcast box destructions --- + { + std::lock_guard dm(g_boxDestructions_mutex); + for (const auto& destruction : g_boxDestructions) { + std::string boxMsg = "BOX_DESTROYED:" + + std::to_string(destruction.boxIndex) + ":" + + std::to_string(destruction.serverTime) + ":" + + std::to_string(destruction.position.x()) + ":" + + std::to_string(destruction.position.y()) + ":" + + std::to_string(destruction.position.z()) + ":" + + std::to_string(destruction.destroyedBy); + + broadcastToAll(boxMsg); + std::cout << "Server: Broadcasted BOX_DESTROYED for box " << destruction.boxIndex << std::endl; + } + g_boxDestructions.clear(); + } + + // --- Schedule next tick in 50ms --- + timer.expires_after(std::chrono::milliseconds(50)); + timer.async_wait([&timer, &ioc](const boost::system::error_code& ec) { + if (ec) return; update_world(timer, ioc); }); } diff --git a/src/network/ClientState.cpp b/src/network/ClientState.cpp index 578f536..fb6b822 100644 --- a/src/network/ClientState.cpp +++ b/src/network/ClientState.cpp @@ -169,7 +169,7 @@ void ClientState::apply_lag_compensation(std::chrono::system_clock::time_point n while (deltaMsLeftover > 0) { - long long miniDelta = 50; + long long miniDelta = std::min(50LL, deltaMsLeftover); simulate_physics(miniDelta); deltaMsLeftover -= miniDelta; } @@ -207,7 +207,7 @@ void ClientState::handle_full_sync(const std::vector& parts, int st discreteAngle = std::stoi(parts[startFrom + 13]); } -std::string ClientState::formPingMessageContent() +std::string ClientState::formPingMessageContent() const { Eigen::Quaternionf q(rotation); diff --git a/src/network/ClientState.h b/src/network/ClientState.h index c9fb48f..02ab5f5 100644 --- a/src/network/ClientState.h +++ b/src/network/ClientState.h @@ -50,7 +50,7 @@ struct ClientState { void handle_full_sync(const std::vector& parts, int startFrom); - std::string formPingMessageContent(); + std::string formPingMessageContent() const; }; struct ClientStateInterval