Working on implementing server side physics on gam

This commit is contained in:
Vladislav Khorev 2026-01-16 00:01:44 +03:00
parent 1348efb5fe
commit e0453db063
8 changed files with 42 additions and 69 deletions

View File

@ -64,7 +64,7 @@ class Session : public std::enable_shared_from_this<Session> {
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::steady_clock::time_point::duration>(std::chrono::milliseconds(clientTimestamp)) };
state_.lastUpdateServerTime = uptime_timepoint;

View File

@ -568,7 +568,7 @@ namespace ZL
sparkEmitter.update(static_cast<float>(delta));
planetObject.update(static_cast<float>(delta));
updateRemotePlayers(static_cast<float>(delta));
extrapolateRemotePlayers(static_cast<float>(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<float>(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 содержат актуальные
// предсказанные данные для рендеринга в текущем кадре.
}
}

View File

@ -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<BoxCoords> boxCoordsArr;
std::vector<VertexRenderStruct> boxRenderArr;
std::unordered_map<int, RemotePlayer> latestRemotePlayers;
std::unordered_map<int, ClientState> latestRemotePlayers;
static const size_t CONST_TIMER_INTERVAL = 10;
static const size_t CONST_MAX_TIME_INTERVAL = 1000;

View File

@ -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<float>(nowTime - lastUpdateServerTime).count();
}
// 3. Защита от слишком больших скачков (Clamp)
// Если лаг более 500мс, ограничиваем его, чтобы избежать резких рывков
float final_lag_s = min(static_cast<float>(lag_ms), 0.5f);
float final_lag_s = min(lag_ms, 0.5f);
if (final_lag_s > 0.001f) {
// Доматываем симуляцию на величину задержки

View File

@ -1,7 +1,7 @@
#pragma once
#include <string>
#include <unordered_map>
#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<int, RemotePlayer> getRemotePlayers() = 0;
virtual std::unordered_map<int, ClientState> getRemotePlayers() = 0;
};
}

View File

@ -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;
};

View File

@ -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<std::string>& vals) {
void WebSocketClient::updateRemotePlayer(int id, const std::vector<std::string>& vals, uint64_t serverTime) {
// Используем мьютекс, так как этот метод вызывается из сетевого потока (TaskManager)
std::lock_guard<std::mutex> 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::steady_clock::time_point::duration>(std::chrono::milliseconds(serverTime)) };
rp.lastUpdateServerTime = uptime_timepoint;
// 4. Ñáðàñûâàåì òàéìåð èíòåðïîëÿöèè
rp.interpolationFactor = 0.0f;
auto now_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now().time_since_epoch()
).count();
rp.lastSnapshotTime = now_ms;
}

View File

@ -28,7 +28,7 @@ namespace ZL {
bool connected = false;
int clientId = -1;
std::unordered_map<int, RemotePlayer> remotePlayers;
std::unordered_map<int, ClientState> 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<std::string>& vals);
void updateRemotePlayer(int id, const std::vector<std::string>& 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<int, RemotePlayer> getRemotePlayers() override {
std::unordered_map<int, ClientState> getRemotePlayers() override {
std::lock_guard<std::mutex> lock(playersMutex);
return remotePlayers;
}