Working on server

This commit is contained in:
Vladislav Khorev committed 2026-01-15 21:54:37 +03:00
1 parent ea26d6fb23
commit 1348efb5fe
13 files changed
+761 -193

No files matched your search

+152
View File
@@ -0,0 +1,152 @@
#pragma once
#include <chrono>
#include <Eigen/Dense>
#define _USE_MATH_DEFINES
#include <math.h>
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<float>(discreteAngle) * static_cast<float>(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<float>(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::milliseconds>(
std::chrono::steady_clock::now().time_since_epoch()
).count();
// 2. Вычисляем задержку
double 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);
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<std::string>& 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]);
}
};
+3
View File
@@ -1,5 +1,7 @@
#pragma once
#include <string>
#include <unordered_map>
#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<int, RemotePlayer> getRemotePlayers() = 0;
};
}
+15
View File
@@ -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;
};
+118 -4
View File
@@ -1,6 +1,18 @@
#include "WebSocketClient.h"
#include <iostream>
#include <SDL2/SDL.h>
#include "RemotePlayer.h"
// Âñïîìîãàòåëüíûé split
std::vector<std::string> split(const std::string& s, char delimiter) {
std::vector<std::string> 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<std::mutex> 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<std::string>& vals) {
// Èñïîëüçóåì ìüþòåêñ, òàê êàê ýòîò ìåòîä âûçûâàåòñÿ èç ñåòåâîãî ïîòîêà (TaskManager)
std::lock_guard<std::mutex> 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::milliseconds>(
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<std::string>(message);
ws_->async_write(boost::asio::buffer(*ss), [ss](boost::beast::error_code, std::size_t) {});
std::lock_guard<std::mutex> 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<std::mutex> lock(writeMutex_);
writeQueue_.pop(); // Óäàëÿåì îòïðàâëåííîå ñîîáùåíèå
doWrite(); // Ïðîâåðÿåì ñëåäóþùåå
}
);
}
}
+15
View File
@@ -21,9 +21,16 @@ namespace ZL {
std::queue<std::string> messageQueue;
std::mutex queueMutex; // Çàùèòà äëÿ messageQueue
std::queue<std::shared_ptr<std::string>> writeQueue_;
bool isWriting_ = false;
std::mutex writeMutex_; // Îòäåëüíûé ìüþòåêñ äëÿ î÷åðåäè çàïèñè
bool connected = false;
int clientId = -1;
std::unordered_map<int, RemotePlayer> 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<std::string>& vals);
void Send(const std::string& message) override;
void doWrite();
bool IsConnected() const override { return connected; }
int GetClientId() const { return clientId; }
std::unordered_map<int, RemotePlayer> getRemotePlayers() override {
std::lock_guard<std::mutex> lock(playersMutex);
return remotePlayers;
}
};
}