Fixing lag compensation and make more smooth
This commit is contained in:
parent
82ca994fac
commit
7b1d9363a0
@ -81,7 +81,7 @@ target_compile_definitions(space-game001 PRIVATE
|
|||||||
PNG_ENABLED
|
PNG_ENABLED
|
||||||
SDL_MAIN_HANDLED
|
SDL_MAIN_HANDLED
|
||||||
NETWORK
|
NETWORK
|
||||||
SIMPLIFIED
|
# SIMPLIFIED
|
||||||
)
|
)
|
||||||
|
|
||||||
# Линкуем с SDL2main, если он вообще установлен
|
# Линкуем с SDL2main, если он вообще установлен
|
||||||
|
|||||||
@ -146,6 +146,7 @@ class Session : public std::enable_shared_from_this<Session> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void sendBoxesToClient() {
|
void sendBoxesToClient() {
|
||||||
std::lock_guard<std::mutex> lock(g_boxes_mutex);
|
std::lock_guard<std::mutex> lock(g_boxes_mutex);
|
||||||
|
|
||||||
@ -168,30 +169,6 @@ class Session : public std::enable_shared_from_this<Session> {
|
|||||||
send_message(boxMsg);
|
send_message(boxMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
void send_message(std::string msg) {
|
|
||||||
auto ss = std::make_shared<std::string>(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:
|
public:
|
||||||
explicit Session(tcp::socket&& socket, int id)
|
explicit Session(tcp::socket&& socket, int id)
|
||||||
@ -237,6 +214,46 @@ public:
|
|||||||
return id_;
|
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::string>(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:
|
private:
|
||||||
void do_read() {
|
void do_read() {
|
||||||
ws_.async_read(buffer_, [self = shared_from_this()](beast::error_code ec, std::size_t) {
|
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) {
|
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<std::chrono::milliseconds>(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<std::chrono::milliseconds>(
|
||||||
|
system_now.time_since_epoch()).count()
|
||||||
|
);
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> 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.expires_after(std::chrono::milliseconds(50));
|
||||||
timer.async_wait([&](const boost::system::error_code& ec) {
|
timer.async_wait([&](const boost::system::error_code& ec) {
|
||||||
|
|||||||
73
src/Game.cpp
73
src/Game.cpp
@ -732,13 +732,13 @@ namespace ZL
|
|||||||
// Биндим текстуру корабля один раз для всех удаленных игроков (оптимизация батчинга)
|
// Биндим текстуру корабля один раз для всех удаленных игроков (оптимизация батчинга)
|
||||||
glBindTexture(GL_TEXTURE_2D, spaceshipTexture->getTexID());
|
glBindTexture(GL_TEXTURE_2D, spaceshipTexture->getTexID());
|
||||||
|
|
||||||
auto now = std::chrono::system_clock::now();
|
/*auto now = std::chrono::system_clock::now();
|
||||||
|
|
||||||
//Apply server delay:
|
//Apply server delay:
|
||||||
now -= std::chrono::milliseconds(CLIENT_DELAY);
|
now -= std::chrono::milliseconds(CLIENT_DELAY);
|
||||||
|
|
||||||
latestRemotePlayers = networkClient->getRemotePlayers();
|
latestRemotePlayers = networkClient->getRemotePlayers();
|
||||||
|
*/
|
||||||
// Если сервер прислал коробки, применяем их однократно вместо локальной генерации
|
// Если сервер прислал коробки, применяем их однократно вместо локальной генерации
|
||||||
if (!serverBoxesApplied && networkClient) {
|
if (!serverBoxesApplied && networkClient) {
|
||||||
auto sboxes = networkClient->getServerBoxes();
|
auto sboxes = networkClient->getServerBoxes();
|
||||||
@ -761,15 +761,9 @@ namespace ZL
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Итерируемся по актуальным данным из extrapolateRemotePlayers
|
// Итерируемся по актуальным данным из extrapolateRemotePlayers
|
||||||
for (auto const& [id, remotePlayer] : latestRemotePlayers) {
|
for (auto const& [id, remotePlayer] : remotePlayerStates) {
|
||||||
|
|
||||||
if (!remotePlayer.canFetchClientStateAtTime(now))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
ClientState playerState = remotePlayer.fetchClientStateAtTime(now);
|
|
||||||
|
|
||||||
|
const ClientState& playerState = remotePlayer;
|
||||||
|
|
||||||
renderer.PushMatrix();
|
renderer.PushMatrix();
|
||||||
renderer.LoadIdentity();
|
renderer.LoadIdentity();
|
||||||
@ -809,18 +803,15 @@ namespace ZL
|
|||||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
|
|
||||||
// Берем удаленных игроков
|
// Берем удаленных игроков
|
||||||
latestRemotePlayers = networkClient->getRemotePlayers();
|
//latestRemotePlayers = networkClient->getRemotePlayers();
|
||||||
|
|
||||||
auto now = std::chrono::system_clock::now();
|
auto now = std::chrono::system_clock::now();
|
||||||
now -= std::chrono::milliseconds(CLIENT_DELAY);
|
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;
|
Vector3f shipWorld = st.position;
|
||||||
|
|
||||||
@ -861,6 +852,9 @@ namespace ZL
|
|||||||
lastTickCount = std::chrono::duration_cast<std::chrono::milliseconds>(
|
lastTickCount = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
std::chrono::system_clock::now().time_since_epoch()
|
std::chrono::system_clock::now().time_since_epoch()
|
||||||
).count();
|
).count();
|
||||||
|
|
||||||
|
lastTickCount = (lastTickCount / 50)*50;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -869,6 +863,8 @@ namespace ZL
|
|||||||
std::chrono::system_clock::now().time_since_epoch()
|
std::chrono::system_clock::now().time_since_epoch()
|
||||||
).count();
|
).count();
|
||||||
|
|
||||||
|
newTickCount = (newTickCount / 50) * 50;
|
||||||
|
|
||||||
if (newTickCount - lastTickCount > CONST_TIMER_INTERVAL) {
|
if (newTickCount - lastTickCount > CONST_TIMER_INTERVAL) {
|
||||||
|
|
||||||
size_t delta = newTickCount - lastTickCount;
|
size_t delta = newTickCount - lastTickCount;
|
||||||
@ -882,15 +878,7 @@ namespace ZL
|
|||||||
sparkEmitter.update(static_cast<float>(delta));
|
sparkEmitter.update(static_cast<float>(delta));
|
||||||
planetObject.update(static_cast<float>(delta));
|
planetObject.update(static_cast<float>(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:
|
//Handle input:
|
||||||
|
|
||||||
@ -946,9 +934,44 @@ namespace ZL
|
|||||||
std::cout << "Sending: " << msg << std::endl;
|
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();
|
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) {
|
for (auto& p : projectiles) {
|
||||||
if (p && p->isActive()) {
|
if (p && p->isActive()) {
|
||||||
p->update(static_cast<float>(delta), renderer);
|
p->update(static_cast<float>(delta), renderer);
|
||||||
|
|||||||
@ -73,7 +73,8 @@ namespace ZL {
|
|||||||
std::vector<std::string> boxLabels;
|
std::vector<std::string> boxLabels;
|
||||||
std::unique_ptr<TextRenderer> textRenderer;
|
std::unique_ptr<TextRenderer> textRenderer;
|
||||||
|
|
||||||
std::unordered_map<int, ClientStateInterval> latestRemotePlayers;
|
//std::unordered_map<int, ClientStateInterval> latestRemotePlayers;
|
||||||
|
std::unordered_map<int, ClientState> remotePlayerStates;
|
||||||
|
|
||||||
float newShipVelocity = 0;
|
float newShipVelocity = 0;
|
||||||
|
|
||||||
|
|||||||
@ -157,6 +157,16 @@ void ClientState::apply_lag_compensation(std::chrono::system_clock::time_point n
|
|||||||
deltaMs = std::chrono::duration_cast<std::chrono::milliseconds>(nowTime - lastUpdateServerTime).count();
|
deltaMs = std::chrono::duration_cast<std::chrono::milliseconds>(nowTime - lastUpdateServerTime).count();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
long long deltaMsLeftover = deltaMs;
|
||||||
|
|
||||||
|
while (deltaMsLeftover > 0)
|
||||||
|
{
|
||||||
|
long long miniDelta = 50;
|
||||||
|
simulate_physics(miniDelta);
|
||||||
|
deltaMsLeftover -= miniDelta;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
// 3. Защита от слишком больших скачков (Clamp)
|
// 3. Защита от слишком больших скачков (Clamp)
|
||||||
// Если лаг более 500мс, ограничиваем его, чтобы избежать резких рывков
|
// Если лаг более 500мс, ограничиваем его, чтобы избежать резких рывков
|
||||||
long long final_lag_ms = deltaMs;//min(deltaMs, 500ll);
|
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);
|
simulate_physics(final_lag_ms);
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClientState::handle_full_sync(const std::vector<std::string>& parts, int startFrom) {
|
void ClientState::handle_full_sync(const std::vector<std::string>& parts, int startFrom) {
|
||||||
|
|||||||
@ -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<std::mutex> pLock(playersMutex);
|
||||||
|
remotePlayers[rId].add_state(remoteState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void WebSocketClient::Send(const std::string& message) {
|
void WebSocketClient::Send(const std::string& message) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user