added sync projectiles/boxes

This commit is contained in:
Vlad 2026-01-27 19:38:18 +06:00
parent cacc18dc7e
commit e8fb14b809
7 changed files with 338 additions and 192 deletions

View File

@ -56,33 +56,76 @@ class Session : public std::enable_shared_from_this<Session> {
void process_message(const std::string& msg) { void process_message(const std::string& msg) {
auto now_server = std::chrono::system_clock::now(); auto now_server = std::chrono::system_clock::now();
auto parts = split(msg, ':'); auto parts = split(msg, ':');
if (parts.size() < 16) if (parts.empty()) {
{ std::cerr << "Empty message received\n";
throw std::runtime_error("Unknown message type received, too small"); return;
} }
uint64_t clientTimestamp = std::stoull(parts[1]); std::string type = parts[0];
ClientState receivedState; if (type == "UPD") {
if (parts.size() < 16) {
receivedState.id = id_; std::cerr << "Invalid UPD message: too few parts (" << parts.size() << ")\n";
return;
std::chrono::system_clock::time_point uptime_timepoint{ std::chrono::duration_cast<std::chrono::system_clock::time_point::duration>(std::chrono::milliseconds(clientTimestamp)) }; }
receivedState.lastUpdateServerTime = uptime_timepoint; uint64_t clientTimestamp = std::stoull(parts[1]);
ClientState receivedState;
if (parts[0] == "UPD") { receivedState.id = id_;
std::chrono::system_clock::time_point uptime_timepoint{
std::chrono::milliseconds(clientTimestamp)
};
receivedState.lastUpdateServerTime = uptime_timepoint;
receivedState.handle_full_sync(parts, 2); receivedState.handle_full_sync(parts, 2);
retranslateMessage(msg); retranslateMessage(msg);
timedClientStates.add_state(receivedState);
} }
else else if (parts[0] == "FIRE") {
{ if (parts.size() < 8) {
throw std::runtime_error("Unknown message type received: " + parts[0]); std::cerr << "Invalid FIRE: too few parts\n";
} return;
}
timedClientStates.add_state(receivedState); uint64_t clientTime = std::stoull(parts[1]);
Eigen::Vector3f pos{
std::stof(parts[2]), std::stof(parts[3]), std::stof(parts[4])
};
Eigen::Vector3f dir{
std::stof(parts[5]), std::stof(parts[6]), std::stof(parts[7])
};
int shotCount = 1;
if (parts.size() >= 9) {
try {
shotCount = std::stoi(parts[8]);
}
catch (...) {
shotCount = 1;
}
}
std::string broadcast = "PROJECTILE:" +
std::to_string(id_) + ":" +
std::to_string(clientTime) + ":" +
std::to_string(pos.x()) + ":" +
std::to_string(pos.y()) + ":" +
std::to_string(pos.z()) + ":" +
std::to_string(dir.x()) + ":" +
std::to_string(dir.y()) + ":" +
std::to_string(dir.z()) + ":" +
std::to_string(shotCount);
std::lock_guard<std::mutex> lock(g_sessions_mutex);
std::cout << "Player " << id_ << " fired " << shotCount << " shots → broadcasting\n";
for (auto& session : g_sessions) {
session->send_message(broadcast);
}
}
else {
std::cerr << "Unknown message type: " << type << "\n";
}
} }
void retranslateMessage(const std::string& msg) void retranslateMessage(const std::string& msg)

View File

@ -232,7 +232,24 @@ namespace ZL
uint64_t now = SDL_GetTicks64(); uint64_t now = SDL_GetTicks64();
if (now - lastProjectileFireTime >= static_cast<uint64_t>(projectileCooldownMs)) { if (now - lastProjectileFireTime >= static_cast<uint64_t>(projectileCooldownMs)) {
lastProjectileFireTime = now; lastProjectileFireTime = now;
fireProjectiles();
Eigen::Vector3f localForward = { 0, 0, -1 };
Eigen::Vector3f worldForward = (Environment::shipState.rotation * localForward).normalized();
Eigen::Vector3f centerPos = Environment::shipState.position +
Environment::shipState.rotation * Vector3f{ 0, 0.9f, 5.0f };
std::string fireMsg = "FIRE:" +
std::to_string(now) + ":" +
std::to_string(centerPos.x()) + ":" +
std::to_string(centerPos.y()) + ":" +
std::to_string(centerPos.z()) + ":" +
std::to_string(worldForward.x()) + ":" +
std::to_string(worldForward.y()) + ":" +
std::to_string(worldForward.z()) + ":" +
"2";
networkClient->Send(fireMsg);
} }
}); });
@ -582,7 +599,7 @@ namespace ZL
auto sboxes = networkClient->getServerBoxes(); auto sboxes = networkClient->getServerBoxes();
if (!sboxes.empty()) { if (!sboxes.empty()) {
boxCoordsArr.clear(); boxCoordsArr.clear();
for (auto &b : sboxes) { for (auto& b : sboxes) {
BoxCoords bc; BoxCoords bc;
bc.pos = b.first; bc.pos = b.first;
bc.m = b.second; bc.m = b.second;
@ -607,7 +624,7 @@ namespace ZL
} }
ClientState playerState = remotePlayer.fetchClientStateAtTime(now); ClientState playerState = remotePlayer.fetchClientStateAtTime(now);
renderer.PushMatrix(); renderer.PushMatrix();
renderer.LoadIdentity(); renderer.LoadIdentity();
@ -704,7 +721,7 @@ namespace ZL
float radians = atan2f(diffy, diffx); float radians = atan2f(diffy, diffx);
discreteAngle = static_cast<int>(radians * 180.0f / M_PI); discreteAngle = static_cast<int>(radians * 180.0f / M_PI);
if (discreteAngle < 0) discreteAngle += 360; if (discreteAngle < 0) discreteAngle += 360;
} }
else else
{ {
@ -718,7 +735,7 @@ namespace ZL
discreteMag = 0.0f; discreteMag = 0.0f;
} }
if (discreteAngle != Environment::shipState.discreteAngle || discreteMag != Environment::shipState.discreteMag) { if (discreteAngle != Environment::shipState.discreteAngle || discreteMag != Environment::shipState.discreteMag) {
Environment::shipState.discreteAngle = discreteAngle; Environment::shipState.discreteAngle = discreteAngle;
Environment::shipState.discreteMag = discreteMag; Environment::shipState.discreteMag = discreteMag;
@ -1097,7 +1114,7 @@ namespace ZL
{ {
if (event.key.keysym.sym == SDLK_i) if (event.key.keysym.sym == SDLK_i)
{ {
} }
}*/ }*/
#endif #endif
@ -1105,6 +1122,42 @@ namespace ZL
render(); render();
mainThreadHandler.processMainThreadTasks(); mainThreadHandler.processMainThreadTasks();
networkClient->Poll(); networkClient->Poll();
if (networkClient) {
auto pending = networkClient->getPendingProjectiles();
if (!pending.empty()) {
const float projectileSpeed = 60.0f;
const float lifeMs = 5000.0f;
const float size = 0.5f;
for (const auto& pi : pending) {
Eigen::Vector3f dir = pi.direction;
float len = dir.norm();
if (len <= 1e-6f) continue;
dir /= len;
Eigen::Vector3f baseVel = dir * projectileSpeed;
int shotCount = 1;
shotCount = 2;
std::vector<Eigen::Vector3f> localOffsets = {
{-1.5f, 0.9f, 5.0f},
{ 1.5f, 0.9f, 5.0f}
};
for (int i = 0; i < shotCount; ++i) {
Eigen::Vector3f shotPos = pi.position + localOffsets[i];
for (auto& p : projectiles) {
if (!p->isActive()) {
p->init(shotPos, baseVel, lifeMs, size, projectileTexture, renderer);
break;
}
}
}
}
}
}
} }
void Game::handleDown(int mx, int my) void Game::handleDown(int mx, int my)

View File

@ -4,15 +4,17 @@
namespace ZL { namespace ZL {
void LocalClient::Connect(const std::string& host, uint16_t port) { void LocalClient::Connect(const std::string& host, uint16_t port) {
} }
void LocalClient::Poll() { void LocalClient::Poll() {
} }
void LocalClient::Send(const std::string& message) { void LocalClient::Send(const std::string& message) {
}
}
std::vector<ProjectileInfo> LocalClient::getPendingProjectiles() {
return {};
}
} }

View File

@ -17,6 +17,7 @@ namespace ZL {
bool IsConnected() const override { return true; } bool IsConnected() const override { return true; }
int GetClientId() const { return 1; } int GetClientId() const { return 1; }
std::vector<ProjectileInfo> getPendingProjectiles();
std::unordered_map<int, ClientStateInterval> getRemotePlayers() override { std::unordered_map<int, ClientStateInterval> getRemotePlayers() override {
return std::unordered_map<int, ClientStateInterval>(); return std::unordered_map<int, ClientStateInterval>();

View File

@ -7,6 +7,13 @@
// NetworkInterface.h - »нтерфейс дл¤ разных типов соединений // NetworkInterface.h - »нтерфейс дл¤ разных типов соединений
namespace ZL { namespace ZL {
struct ProjectileInfo {
int shooterId = -1;
uint64_t clientTime = 0;
Eigen::Vector3f position = Eigen::Vector3f::Zero();
Eigen::Vector3f direction = Eigen::Vector3f::Zero();
};
class INetworkClient { class INetworkClient {
public: public:
virtual ~INetworkClient() = default; virtual ~INetworkClient() = default;
@ -17,5 +24,7 @@ namespace ZL {
virtual std::unordered_map<int, ClientStateInterval> getRemotePlayers() = 0; virtual std::unordered_map<int, ClientStateInterval> getRemotePlayers() = 0;
virtual std::vector<std::pair<Eigen::Vector3f, Eigen::Matrix3f>> getServerBoxes() = 0; virtual std::vector<std::pair<Eigen::Vector3f, Eigen::Matrix3f>> getServerBoxes() = 0;
virtual std::vector<ProjectileInfo> getPendingProjectiles() = 0;
}; };
} }

View File

@ -6,206 +6,239 @@
// Вспомогательный split // Вспомогательный split
std::vector<std::string> split(const std::string& s, char delimiter) { std::vector<std::string> split(const std::string& s, char delimiter) {
std::vector<std::string> tokens; std::vector<std::string> tokens;
std::string token; std::string token;
std::istringstream tokenStream(s); std::istringstream tokenStream(s);
while (std::getline(tokenStream, token, delimiter)) { while (std::getline(tokenStream, token, delimiter)) {
tokens.push_back(token); tokens.push_back(token);
} }
return tokens; return tokens;
} }
namespace ZL { namespace ZL {
void WebSocketClient::Connect(const std::string& host, uint16_t port) { void WebSocketClient::Connect(const std::string& host, uint16_t port) {
try { try {
boost::asio::ip::tcp::resolver resolver(ioc_); boost::asio::ip::tcp::resolver resolver(ioc_);
auto const results = resolver.resolve(host, std::to_string(port)); auto const results = resolver.resolve(host, std::to_string(port));
ws_ = std::make_unique<boost::beast::websocket::stream<boost::beast::tcp_stream>>(ioc_); ws_ = std::make_unique<boost::beast::websocket::stream<boost::beast::tcp_stream>>(ioc_);
// Выполняем синхронный коннект и handshake для простоты старта // Выполняем синхронный коннект и handshake для простоты старта
boost::beast::get_lowest_layer(*ws_).connect(results); boost::beast::get_lowest_layer(*ws_).connect(results);
ws_->handshake(host, "/"); ws_->handshake(host, "/");
connected = true; connected = true;
// Запускаем асинхронное чтение в пуле потоков TaskManager // Запускаем асинхронное чтение в пуле потоков TaskManager
startAsyncRead(); startAsyncRead();
} }
catch (std::exception& e) { catch (std::exception& e) {
std::cerr << "Network Error: " << e.what() << std::endl; std::cerr << "Network Error: " << e.what() << std::endl;
} }
} }
void WebSocketClient::startAsyncRead() { void WebSocketClient::startAsyncRead() {
ws_->async_read(buffer_, [this](boost::beast::error_code ec, std::size_t bytes) { ws_->async_read(buffer_, [this](boost::beast::error_code ec, std::size_t bytes) {
if (!ec) { if (!ec) {
std::string msg = boost::beast::buffers_to_string(buffer_.data()); std::string msg = boost::beast::buffers_to_string(buffer_.data());
buffer_.consume(bytes); buffer_.consume(bytes);
processIncomingMessage(msg); processIncomingMessage(msg);
startAsyncRead(); startAsyncRead();
} }
else { else {
connected = false; connected = false;
} }
}); });
} }
void WebSocketClient::processIncomingMessage(const std::string& msg) { void WebSocketClient::processIncomingMessage(const std::string& msg) {
// Логика парсинга... // Логика парсинга...
if (msg.rfind("ID:", 0) == 0) { if (msg.rfind("ID:", 0) == 0) {
clientId = std::stoi(msg.substr(3)); clientId = std::stoi(msg.substr(3));
} }
// Безопасно кладем в очередь для главного потока // Безопасно кладем в очередь для главного потока
std::lock_guard<std::mutex> lock(queueMutex); std::lock_guard<std::mutex> lock(queueMutex);
messageQueue.push(msg); messageQueue.push(msg);
} }
void WebSocketClient::Poll() { std::vector<ProjectileInfo> WebSocketClient::getPendingProjectiles() {
std::lock_guard<std::mutex> lock(queueMutex); std::lock_guard<std::mutex> lock(projMutex_);
auto copy = pendingProjectiles_;
pendingProjectiles_.clear();
return copy;
}
while (!messageQueue.empty()) { void WebSocketClient::Poll() {
std::lock_guard<std::mutex> lock(queueMutex);
auto nowTime = std::chrono::system_clock::now(); while (!messageQueue.empty()) {
//Apply server delay: auto nowTime = std::chrono::system_clock::now();
nowTime -= std::chrono::milliseconds(CLIENT_DELAY);
auto now_ms = std::chrono::duration_cast<std::chrono::milliseconds>( //Apply server delay:
nowTime.time_since_epoch() nowTime -= std::chrono::milliseconds(CLIENT_DELAY);
).count();
auto now_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
nowTime.time_since_epoch()
).count();
std::string msg = messageQueue.front(); std::string msg = messageQueue.front();
messageQueue.pop(); messageQueue.pop();
// Обработка списка коробок от сервера // Обработка списка коробок от сервера
if (msg.rfind("BOXES:", 0) == 0) { if (msg.rfind("BOXES:", 0) == 0) {
std::string payload = msg.substr(6); // после "BOXES:" std::string payload = msg.substr(6); // после "BOXES:"
std::vector<std::pair<Eigen::Vector3f, Eigen::Matrix3f>> parsedBoxes; std::vector<std::pair<Eigen::Vector3f, Eigen::Matrix3f>> parsedBoxes;
if (!payload.empty()) { if (!payload.empty()) {
auto items = split(payload, '|'); auto items = split(payload, '|');
for (auto& item : items) { for (auto& item : items) {
if (item.empty()) continue; if (item.empty()) continue;
auto parts = split(item, ':'); auto parts = split(item, ':');
if (parts.size() < 7) continue; if (parts.size() < 7) continue;
try { try {
float px = std::stof(parts[0]); float px = std::stof(parts[0]);
float py = std::stof(parts[1]); float py = std::stof(parts[1]);
float pz = std::stof(parts[2]); float pz = std::stof(parts[2]);
Eigen::Quaternionf q( Eigen::Quaternionf q(
std::stof(parts[3]), std::stof(parts[3]),
std::stof(parts[4]), std::stof(parts[4]),
std::stof(parts[5]), std::stof(parts[5]),
std::stof(parts[6]) std::stof(parts[6])
); );
Eigen::Matrix3f rot = q.toRotationMatrix(); Eigen::Matrix3f rot = q.toRotationMatrix();
parsedBoxes.emplace_back(Eigen::Vector3f{ px, py, pz }, rot); parsedBoxes.emplace_back(Eigen::Vector3f{ px, py, pz }, rot);
} }
catch (...) { catch (...) {
// пропускаем некорректную запись // пропускаем некорректную запись
continue; continue;
} }
} }
} }
{ {
std::lock_guard<std::mutex> bLock(boxesMutex); std::lock_guard<std::mutex> bLock(boxesMutex);
serverBoxes_ = std::move(parsedBoxes); serverBoxes_ = std::move(parsedBoxes);
} }
continue; continue;
} }
if (msg.rfind("EVENT:", 0) == 0) { if (msg.rfind("PROJECTILE:", 0) == 0) {
auto parts = split(msg, ':'); auto parts = split(msg, ':');
if (parts.size() < 5) continue; // EVENT:ID:TYPE:TIME:DATA... if (parts.size() >= 9) {
try {
ProjectileInfo pi;
pi.shooterId = std::stoi(parts[1]);
pi.clientTime = std::stoull(parts[2]);
pi.position = Eigen::Vector3f(
std::stof(parts[3]),
std::stof(parts[4]),
std::stof(parts[5])
);
pi.direction = Eigen::Vector3f(
std::stof(parts[6]),
std::stof(parts[7]),
std::stof(parts[8])
);
std::lock_guard<std::mutex> pl(projMutex_);
pendingProjectiles_.push_back(pi);
}
catch (...) {
}
}
continue;
}
int remoteId = std::stoi(parts[1]); if (msg.rfind("EVENT:", 0) == 0) {
std::string subType = parts[2]; auto parts = split(msg, ':');
uint64_t sentTime = std::stoull(parts[3]); if (parts.size() < 5) continue; // EVENT:ID:TYPE:TIME:DATA...
ClientState remoteState; int remoteId = std::stoi(parts[1]);
std::string subType = parts[2];
uint64_t sentTime = std::stoull(parts[3]);
ClientState remoteState;
remoteState.id = remoteId; remoteState.id = remoteId;
std::chrono::system_clock::time_point uptime_timepoint{ std::chrono::duration_cast<std::chrono::system_clock::time_point::duration>(std::chrono::milliseconds(sentTime)) }; std::chrono::system_clock::time_point uptime_timepoint{ std::chrono::duration_cast<std::chrono::system_clock::time_point::duration>(std::chrono::milliseconds(sentTime)) };
remoteState.lastUpdateServerTime = uptime_timepoint; remoteState.lastUpdateServerTime = uptime_timepoint;
if (subType == "UPD") {
int startFrom = 4;
remoteState.position = { std::stof(parts[startFrom]), std::stof(parts[startFrom + 1]), std::stof(parts[startFrom + 2]) };
Eigen::Quaternionf q(
std::stof(parts[startFrom + 3]),
std::stof(parts[startFrom + 4]),
std::stof(parts[startFrom + 5]),
std::stof(parts[startFrom + 6]));
remoteState.rotation = q.toRotationMatrix();
remoteState.currentAngularVelocity = Eigen::Vector3f{ if (subType == "UPD") {
std::stof(parts[startFrom + 7]), int startFrom = 4;
std::stof(parts[startFrom + 8]), remoteState.position = { std::stof(parts[startFrom]), std::stof(parts[startFrom + 1]), std::stof(parts[startFrom + 2]) };
std::stof(parts[startFrom + 9]) }; Eigen::Quaternionf q(
remoteState.velocity = std::stof(parts[startFrom + 10]); std::stof(parts[startFrom + 3]),
remoteState.selectedVelocity = std::stoi(parts[startFrom + 11]); std::stof(parts[startFrom + 4]),
remoteState.discreteMag = std::stof(parts[startFrom + 12]); std::stof(parts[startFrom + 5]),
remoteState.discreteAngle = std::stoi(parts[startFrom + 13]); std::stof(parts[startFrom + 6]));
} remoteState.rotation = q.toRotationMatrix();
else
{ remoteState.currentAngularVelocity = Eigen::Vector3f{
std::stof(parts[startFrom + 7]),
std::stof(parts[startFrom + 8]),
std::stof(parts[startFrom + 9]) };
remoteState.velocity = std::stof(parts[startFrom + 10]);
remoteState.selectedVelocity = std::stoi(parts[startFrom + 11]);
remoteState.discreteMag = std::stof(parts[startFrom + 12]);
remoteState.discreteAngle = std::stoi(parts[startFrom + 13]);
}
else
{
throw std::runtime_error("Unknown EVENT subtype: " + subType); throw std::runtime_error("Unknown EVENT subtype: " + subType);
} }
{ {
std::lock_guard<std::mutex> pLock(playersMutex); std::lock_guard<std::mutex> pLock(playersMutex);
auto& rp = remotePlayers[remoteId]; auto& rp = remotePlayers[remoteId];
rp.add_state(remoteState); rp.add_state(remoteState);
} }
} }
} }
} }
void WebSocketClient::Send(const std::string& message) { void WebSocketClient::Send(const std::string& message) {
if (!connected) return; if (!connected) return;
auto ss = std::make_shared<std::string>(message); auto ss = std::make_shared<std::string>(message);
std::lock_guard<std::mutex> lock(writeMutex_); std::lock_guard<std::mutex> lock(writeMutex_);
writeQueue_.push(ss); writeQueue_.push(ss);
// Если сейчас ничего не записывается, инициируем первую запись // Если сейчас ничего не записывается, инициируем первую запись
if (!isWriting_) { if (!isWriting_) {
doWrite(); doWrite();
} }
} }
void WebSocketClient::doWrite() { void WebSocketClient::doWrite() {
// Эта функция всегда вызывается под мьютексом или из колбэка // Эта функция всегда вызывается под мьютексом или из колбэка
if (writeQueue_.empty()) { if (writeQueue_.empty()) {
isWriting_ = false; isWriting_ = false;
return; return;
} }
isWriting_ = true; isWriting_ = true;
auto message = writeQueue_.front(); auto message = writeQueue_.front();
// Захватываем self (shared_from_this), чтобы объект не удалился во время записи // Захватываем self (shared_from_this), чтобы объект не удалился во время записи
ws_->async_write( ws_->async_write(
boost::asio::buffer(*message), boost::asio::buffer(*message),
[this, message](boost::beast::error_code ec, std::size_t) { [this, message](boost::beast::error_code ec, std::size_t) {
if (ec) { if (ec) {
connected = false; connected = false;
return; return;
} }
std::lock_guard<std::mutex> lock(writeMutex_); std::lock_guard<std::mutex> lock(writeMutex_);
writeQueue_.pop(); // Удаляем отправленное сообщение writeQueue_.pop(); // Удаляем отправленное сообщение
doWrite(); // Проверяем следующее doWrite(); // Проверяем следующее
} }
); );
} }
} }
#endif #endif

View File

@ -38,6 +38,9 @@ namespace ZL {
std::vector<std::pair<Eigen::Vector3f, Eigen::Matrix3f>> serverBoxes_; std::vector<std::pair<Eigen::Vector3f, Eigen::Matrix3f>> serverBoxes_;
std::mutex boxesMutex; std::mutex boxesMutex;
std::vector<ProjectileInfo> pendingProjectiles_;
std::mutex projMutex_;
void startAsyncRead(); void startAsyncRead();
void processIncomingMessage(const std::string& msg); void processIncomingMessage(const std::string& msg);
@ -63,6 +66,8 @@ namespace ZL {
std::lock_guard<std::mutex> lock(boxesMutex); std::lock_guard<std::mutex> lock(boxesMutex);
return serverBoxes_; return serverBoxes_;
} }
std::vector<ProjectileInfo> getPendingProjectiles() override;
}; };
} }
#endif #endif