added boxes from server

This commit is contained in:
Vlad 2026-01-19 22:04:33 +06:00
parent 96d879076a
commit cacc18dc7e
8 changed files with 308 additions and 111 deletions

View File

@ -11,16 +11,18 @@
#define _USE_MATH_DEFINES #define _USE_MATH_DEFINES
#include <math.h> #include <math.h>
#include "../src/network/ClientState.h" #include "../src/network/ClientState.h"
#include <random>
#include <algorithm>
// Вспомогательный 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 beast = boost::beast; namespace beast = boost::beast;
@ -31,152 +33,274 @@ using tcp = net::ip::tcp;
class Session; class Session;
struct ServerBox {
Eigen::Vector3f position;
Eigen::Matrix3f rotation;
float collisionRadius = 2.0f;
};
std::vector<ServerBox> g_serverBoxes;
std::mutex g_boxes_mutex;
std::vector<std::shared_ptr<Session>> g_sessions; std::vector<std::shared_ptr<Session>> g_sessions;
std::mutex g_sessions_mutex; std::mutex g_sessions_mutex;
class Session : public std::enable_shared_from_this<Session> { class Session : public std::enable_shared_from_this<Session> {
websocket::stream<beast::tcp_stream> ws_; websocket::stream<beast::tcp_stream> ws_;
beast::flat_buffer buffer_; beast::flat_buffer buffer_;
int id_; int id_;
bool is_writing_ = false;
ClientStateInterval timedClientStates; ClientStateInterval timedClientStates;
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.size() < 16)
{ {
throw std::runtime_error("Unknown message type received, too small"); throw std::runtime_error("Unknown message type received, too small");
} }
uint64_t clientTimestamp = std::stoull(parts[1]); uint64_t clientTimestamp = std::stoull(parts[1]);
ClientState receivedState; ClientState receivedState;
receivedState.id = id_; receivedState.id = id_;
std::chrono::system_clock::time_point uptime_timepoint{ std::chrono::duration_cast<std::chrono::system_clock::time_point::duration>(std::chrono::milliseconds(clientTimestamp)) }; 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; receivedState.lastUpdateServerTime = uptime_timepoint;
if (parts[0] == "UPD") { if (parts[0] == "UPD") {
receivedState.handle_full_sync(parts, 2); receivedState.handle_full_sync(parts, 2);
retranslateMessage(msg); retranslateMessage(msg);
} }
else else
{ {
throw std::runtime_error("Unknown message type received: " + parts[0]); throw std::runtime_error("Unknown message type received: " + parts[0]);
} }
timedClientStates.add_state(receivedState); timedClientStates.add_state(receivedState);
} }
void retranslateMessage(const std::string& msg) void retranslateMessage(const std::string& msg)
{ {
std::string event_msg = "EVENT:" + std::to_string(id_) + ":" + msg; std::string event_msg = "EVENT:" + std::to_string(id_) + ":" + msg;
std::lock_guard<std::mutex> lock(g_sessions_mutex);
for (auto& session : g_sessions) {
if (session->get_id() != id_) { // Не шлем отправителю
session->send_message(event_msg);
}
}
}
void sendBoxesToClient() {
std::lock_guard<std::mutex> lock(g_boxes_mutex);
std::string boxMsg = "BOXES:";
for (const auto& box : g_serverBoxes) {
Eigen::Quaternionf q(box.rotation);
boxMsg += std::to_string(box.position.x()) + ":" +
std::to_string(box.position.y()) + ":" +
std::to_string(box.position.z()) + ":" +
std::to_string(q.w()) + ":" +
std::to_string(q.x()) + ":" +
std::to_string(q.y()) + ":" +
std::to_string(q.z()) + "|";
}
if (!boxMsg.empty() && boxMsg.back() == '|') {
boxMsg.pop_back();
}
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;
}
});
}
}
std::lock_guard<std::mutex> lock(g_sessions_mutex);
for (auto& session : g_sessions) {
if (session->get_id() != id_) { // Не шлем отправителю
session->send_message(event_msg);
}
}
}
public: public:
explicit Session(tcp::socket&& socket, int id) explicit Session(tcp::socket&& socket, int id)
: ws_(std::move(socket)), id_(id) { : ws_(std::move(socket)), id_(id) {
} }
void init() void init()
{ {
} sendBoxesToClient();
void run() { auto timer = std::make_shared<net::steady_timer>(ws_.get_executor());
timer->expires_after(std::chrono::milliseconds(100));
timer->async_wait([self = shared_from_this(), timer](const boost::system::error_code& ec) {
if (!ec) {
self->send_message("ID:" + std::to_string(self->id_));
self->do_read();
}
});
}
{ void run() {
std::lock_guard<std::mutex> lock(g_sessions_mutex);
{
std::lock_guard<std::mutex> lock(g_sessions_mutex);
g_sessions.push_back(shared_from_this()); g_sessions.push_back(shared_from_this());
} }
ws_.async_accept([self = shared_from_this()](beast::error_code ec) { ws_.async_accept([self = shared_from_this()](beast::error_code ec) {
if (ec) return; if (ec) return;
std::cout << "Client " << self->id_ << " connected\n"; std::cout << "Client " << self->id_ << " connected\n";
self->init(); self->init();
self->send_message("ID:" + std::to_string(self->id_)); // self->send_message("ID:" + std::to_string(self->id_));
self->do_read(); // self->do_read();
}); });
} }
void send_message(std::string msg) { /*void send_message(std::string msg) {
auto ss = std::make_shared<std::string>(std::move(msg)); auto ss = std::make_shared<std::string>(std::move(msg));
ws_.async_write(net::buffer(*ss), [ss](beast::error_code, std::size_t) {}); ws_.async_write(net::buffer(*ss), [ss](beast::error_code, std::size_t) {});
} }*/
int get_id() const { int get_id() const {
return id_; return id_;
} }
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) {
if (ec) if (ec)
{ {
std::lock_guard<std::mutex> lock(g_sessions_mutex); std::lock_guard<std::mutex> lock(g_sessions_mutex);
g_sessions.erase(std::remove_if(g_sessions.begin(), g_sessions.end(), g_sessions.erase(std::remove_if(g_sessions.begin(), g_sessions.end(),
[self](const std::shared_ptr<Session>& session) { [self](const std::shared_ptr<Session>& session) {
return session.get() == self.get(); return session.get() == self.get();
}), g_sessions.end()); }), g_sessions.end());
return; return;
} }
std::string msg = beast::buffers_to_string(self->buffer_.data()); std::string msg = beast::buffers_to_string(self->buffer_.data());
self->process_message(msg); self->process_message(msg);
self->buffer_.consume(self->buffer_.size()); self->buffer_.consume(self->buffer_.size());
self->do_read(); self->do_read();
}); });
} }
}; };
void update_world(net::steady_timer& timer, net::io_context& ioc) { void update_world(net::steady_timer& timer, net::io_context& ioc) {
// TODO: Renew game state // TODO: Renew game state
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) {
if (!ec) update_world(timer, ioc); if (!ec) update_world(timer, ioc);
}); });
} }
std::vector<ServerBox> generateServerBoxes(int count) {
std::vector<ServerBox> boxes;
std::random_device rd;
std::mt19937 gen(rd());
const float MIN_COORD = -100.0f;
const float MAX_COORD = 100.0f;
const float MIN_DISTANCE = 3.0f;
const float MIN_DISTANCE_SQUARED = MIN_DISTANCE * MIN_DISTANCE;
const int MAX_ATTEMPTS = 1000;
std::uniform_real_distribution<> posDistrib(MIN_COORD, MAX_COORD);
std::uniform_real_distribution<> angleDistrib(0.0, M_PI * 2.0);
for (int i = 0; i < count; i++) {
bool accepted = false;
int attempts = 0;
while (!accepted && attempts < MAX_ATTEMPTS) {
ServerBox box;
box.position = Eigen::Vector3f(
(float)posDistrib(gen),
(float)posDistrib(gen),
(float)posDistrib(gen)
);
accepted = true;
for (const auto& existingBox : boxes) {
Eigen::Vector3f diff = box.position - existingBox.position;
if (diff.squaredNorm() < MIN_DISTANCE_SQUARED) {
accepted = false;
break;
}
}
if (accepted) {
float randomAngle = (float)angleDistrib(gen);
Eigen::Vector3f axis = Eigen::Vector3f::Random().normalized();
box.rotation = Eigen::AngleAxisf(randomAngle, axis).toRotationMatrix();
boxes.push_back(box);
}
attempts++;
}
}
return boxes;
}
int main() { int main() {
try { try {
net::io_context ioc; {
tcp::acceptor acceptor{ ioc, {tcp::v4(), 8080} }; std::lock_guard<std::mutex> lock(g_boxes_mutex);
int next_id = 1000; g_serverBoxes = generateServerBoxes(50);
std::cout << "Generated " << g_serverBoxes.size() << " boxes on server\n";
}
net::io_context ioc;
tcp::acceptor acceptor{ ioc, {tcp::v4(), 8080} };
int next_id = 1000;
std::cout << "Server started on port 8080...\n"; std::cout << "Server started on port 8080...\n";
auto do_accept = [&](auto& self_fn) -> void { auto do_accept = [&](auto& self_fn) -> void {
acceptor.async_accept([&, self_fn](beast::error_code ec, tcp::socket socket) { acceptor.async_accept([&, self_fn](beast::error_code ec, tcp::socket socket) {
if (!ec) { if (!ec) {
std::make_shared<Session>(std::move(socket), next_id++)->run(); std::make_shared<Session>(std::move(socket), next_id++)->run();
} }
self_fn(self_fn); self_fn(self_fn);
}); });
}; };
net::steady_timer timer(ioc);
update_world(timer, ioc);
do_accept(do_accept); net::steady_timer timer(ioc);
ioc.run(); update_world(timer, ioc);
}
catch (std::exception const& e) { do_accept(do_accept);
std::cerr << "Error: " << e.what() << std::endl; ioc.run();
} }
return 0; catch (std::exception const& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
return 0;
} }

View File

@ -577,6 +577,27 @@ namespace ZL
latestRemotePlayers = networkClient->getRemotePlayers(); latestRemotePlayers = networkClient->getRemotePlayers();
// Если сервер прислал коробки, применяем их однократно вместо локальной генерации
if (!serverBoxesApplied && networkClient) {
auto sboxes = networkClient->getServerBoxes();
if (!sboxes.empty()) {
boxCoordsArr.clear();
for (auto &b : sboxes) {
BoxCoords bc;
bc.pos = b.first;
bc.m = b.second;
boxCoordsArr.push_back(bc);
}
boxRenderArr.resize(boxCoordsArr.size());
for (int i = 0; i < (int)boxCoordsArr.size(); ++i) {
boxRenderArr[i].AssignFrom(boxBase);
boxRenderArr[i].RefreshVBO();
}
boxAlive.assign(boxCoordsArr.size(), true);
serverBoxesApplied = true;
}
}
// Итерируемся по актуальным данным из extrapolateRemotePlayers // Итерируемся по актуальным данным из extrapolateRemotePlayers
for (auto const& [id, remotePlayer] : latestRemotePlayers) { for (auto const& [id, remotePlayer] : latestRemotePlayers) {

View File

@ -104,6 +104,7 @@ namespace ZL {
uint64_t lastExplosionTime = 0; uint64_t lastExplosionTime = 0;
const uint64_t explosionDurationMs = 500; const uint64_t explosionDurationMs = 500;
bool serverBoxesApplied = false;
}; };

View File

@ -14,7 +14,7 @@ constexpr float SHIP_ACCEL = 1.0f * 1000.0f;
constexpr float ROTATION_SENSITIVITY = 0.002f; constexpr float ROTATION_SENSITIVITY = 0.002f;
constexpr long long SERVER_DELAY = 0; //ms constexpr long long SERVER_DELAY = 0; //ms
constexpr long long CLIENT_DELAY = 200; //ms constexpr long long CLIENT_DELAY = 1000; //ms
constexpr long long CUTOFF_TIME = 5000; //ms constexpr long long CUTOFF_TIME = 5000; //ms
struct ClientState { struct ClientState {

View File

@ -21,5 +21,9 @@ namespace ZL {
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>();
} }
std::vector<std::pair<Eigen::Vector3f, Eigen::Matrix3f>> getServerBoxes() override {
return {};
}
}; };
} }

View File

@ -15,5 +15,7 @@ namespace ZL {
virtual bool IsConnected() const = 0; virtual bool IsConnected() const = 0;
virtual void Poll() = 0; // ƒл¤ обработки вход¤щих пакетов virtual void Poll() = 0; // ƒл¤ обработки вход¤щих пакетов
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;
}; };
} }

View File

@ -83,6 +83,42 @@ namespace ZL {
std::string msg = messageQueue.front(); std::string msg = messageQueue.front();
messageQueue.pop(); messageQueue.pop();
// Обработка списка коробок от сервера
if (msg.rfind("BOXES:", 0) == 0) {
std::string payload = msg.substr(6); // после "BOXES:"
std::vector<std::pair<Eigen::Vector3f, Eigen::Matrix3f>> parsedBoxes;
if (!payload.empty()) {
auto items = split(payload, '|');
for (auto& item : items) {
if (item.empty()) continue;
auto parts = split(item, ':');
if (parts.size() < 7) continue;
try {
float px = std::stof(parts[0]);
float py = std::stof(parts[1]);
float pz = std::stof(parts[2]);
Eigen::Quaternionf q(
std::stof(parts[3]),
std::stof(parts[4]),
std::stof(parts[5]),
std::stof(parts[6])
);
Eigen::Matrix3f rot = q.toRotationMatrix();
parsedBoxes.emplace_back(Eigen::Vector3f{ px, py, pz }, rot);
}
catch (...) {
// пропускаем некорректную запись
continue;
}
}
}
{
std::lock_guard<std::mutex> bLock(boxesMutex);
serverBoxes_ = std::move(parsedBoxes);
}
continue;
}
if (msg.rfind("EVENT:", 0) == 0) { if (msg.rfind("EVENT:", 0) == 0) {
auto parts = split(msg, ':'); auto parts = split(msg, ':');
if (parts.size() < 5) continue; // EVENT:ID:TYPE:TIME:DATA... if (parts.size() < 5) continue; // EVENT:ID:TYPE:TIME:DATA...

View File

@ -34,6 +34,10 @@ namespace ZL {
std::unordered_map<int, ClientStateInterval> remotePlayers; std::unordered_map<int, ClientStateInterval> remotePlayers;
std::mutex playersMutex; std::mutex playersMutex;
// Серверные коробки
std::vector<std::pair<Eigen::Vector3f, Eigen::Matrix3f>> serverBoxes_;
std::mutex boxesMutex;
void startAsyncRead(); void startAsyncRead();
void processIncomingMessage(const std::string& msg); void processIncomingMessage(const std::string& msg);
@ -54,6 +58,11 @@ namespace ZL {
std::lock_guard<std::mutex> lock(playersMutex); std::lock_guard<std::mutex> lock(playersMutex);
return remotePlayers; return remotePlayers;
} }
std::vector<std::pair<Eigen::Vector3f, Eigen::Matrix3f>> getServerBoxes() override {
std::lock_guard<std::mutex> lock(boxesMutex);
return serverBoxes_;
}
}; };
} }
#endif #endif