This commit is contained in:
Vladislav Khorev committed 2026-02-05 07:30:12 +03:00
commit 0d2f705d42
18 files changed
+901 -322

No files matched your search

+1 -1
View File
@@ -20,7 +20,7 @@ constexpr float PLANET_MAX_ANGULAR_VELOCITY = 10.f;
constexpr float PITCH_LIMIT = static_cast<float>(M_PI) / 9.f;//18.0f;
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
struct ClientState {
+9 -7
View File
@@ -4,15 +4,17 @@
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 {};
}
}
+5
View File
@@ -17,9 +17,14 @@ namespace ZL {
bool IsConnected() const override { return true; }
int GetClientId() const { return 1; }
std::vector<ProjectileInfo> getPendingProjectiles();
std::unordered_map<int, ClientStateInterval> getRemotePlayers() override {
return std::unordered_map<int, ClientStateInterval>();
}
std::vector<std::pair<Eigen::Vector3f, Eigen::Matrix3f>> getServerBoxes() override {
return {};
}
};
}
+13
View File
@@ -7,6 +7,15 @@
// NetworkInterface.h - »нтерфейс дл¤ разных типов соединений
namespace ZL {
struct ProjectileInfo {
int shooterId = -1;
uint64_t clientTime = 0;
Eigen::Vector3f position = Eigen::Vector3f::Zero();
//Eigen::Vector3f direction = Eigen::Vector3f::Zero();
Eigen::Matrix3f rotation = Eigen::Matrix3f::Identity();
float velocity = 0;
};
class INetworkClient {
public:
virtual ~INetworkClient() = default;
@@ -15,5 +24,9 @@ namespace ZL {
virtual bool IsConnected() const = 0;
virtual void Poll() = 0; // ƒл¤ обработки вход¤щих пакетов
virtual std::unordered_map<int, ClientStateInterval> getRemotePlayers() = 0;
virtual std::vector<std::pair<Eigen::Vector3f, Eigen::Matrix3f>> getServerBoxes() = 0;
virtual std::vector<ProjectileInfo> getPendingProjectiles() = 0;
};
}
+199 -126
View File
@@ -6,170 +6,243 @@
// Вспомогательный 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;
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 {
void WebSocketClient::Connect(const std::string& host, uint16_t port) {
try {
boost::asio::ip::tcp::resolver resolver(ioc_);
auto const results = resolver.resolve(host, std::to_string(port));
void WebSocketClient::Connect(const std::string& host, uint16_t port) {
try {
boost::asio::ip::tcp::resolver resolver(ioc_);
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 для простоты старта
boost::beast::get_lowest_layer(*ws_).connect(results);
ws_->handshake(host, "/");
// Выполняем синхронный коннект и handshake для простоты старта
boost::beast::get_lowest_layer(*ws_).connect(results);
ws_->handshake(host, "/");
connected = true;
connected = true;
// Запускаем асинхронное чтение в пуле потоков TaskManager
startAsyncRead();
// Запускаем асинхронное чтение в пуле потоков TaskManager
startAsyncRead();
}
catch (std::exception& e) {
std::cerr << "Network Error: " << e.what() << std::endl;
}
}
}
catch (std::exception& e) {
std::cerr << "Network Error: " << e.what() << std::endl;
}
}
void WebSocketClient::startAsyncRead() {
ws_->async_read(buffer_, [this](boost::beast::error_code ec, std::size_t bytes) {
if (!ec) {
std::string msg = boost::beast::buffers_to_string(buffer_.data());
buffer_.consume(bytes);
processIncomingMessage(msg);
startAsyncRead();
}
else {
connected = false;
}
});
}
void WebSocketClient::startAsyncRead() {
ws_->async_read(buffer_, [this](boost::beast::error_code ec, std::size_t bytes) {
if (!ec) {
std::string msg = boost::beast::buffers_to_string(buffer_.data());
buffer_.consume(bytes);
processIncomingMessage(msg);
startAsyncRead();
}
else {
connected = false;
}
});
}
void WebSocketClient::processIncomingMessage(const std::string& msg) {
// Логика парсинга...
if (msg.rfind("ID:", 0) == 0) {
clientId = std::stoi(msg.substr(3));
}
void WebSocketClient::processIncomingMessage(const std::string& msg) {
// Логика парсинга...
if (msg.rfind("ID:", 0) == 0) {
clientId = std::stoi(msg.substr(3));
}
// Безопасно кладем в очередь для главного потока
std::lock_guard<std::mutex> lock(queueMutex);
messageQueue.push(msg);
}
// Безопасно кладем в очередь для главного потока
std::lock_guard<std::mutex> lock(queueMutex);
messageQueue.push(msg);
}
void WebSocketClient::Poll() {
std::lock_guard<std::mutex> lock(queueMutex);
std::vector<ProjectileInfo> WebSocketClient::getPendingProjectiles() {
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:
nowTime -= std::chrono::milliseconds(CLIENT_DELAY);
auto nowTime = std::chrono::system_clock::now();
auto now_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
nowTime.time_since_epoch()
).count();
//Apply server delay:
nowTime -= std::chrono::milliseconds(CLIENT_DELAY);
auto now_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
nowTime.time_since_epoch()
).count();
std::string msg = messageQueue.front();
messageQueue.pop();
std::string msg = messageQueue.front();
messageQueue.pop();
if (msg.rfind("EVENT:", 0) == 0) {
auto parts = split(msg, ':');
if (parts.size() < 5) continue; // EVENT:ID:TYPE:TIME:DATA...
// Обработка списка коробок от сервера
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;
}
int remoteId = std::stoi(parts[1]);
std::string subType = parts[2];
uint64_t sentTime = std::stoull(parts[3]);
if (msg.rfind("PROJECTILE:", 0) == 0) {
auto parts = split(msg, ':');
if (parts.size() >= 10) {
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])
);
Eigen::Quaternionf q(
std::stof(parts[6]),
std::stof(parts[7]),
std::stof(parts[8]),
std::stof(parts[9])
);
pi.rotation = q.toRotationMatrix();
ClientState remoteState;
pi.velocity = std::stof(parts[10]);
std::lock_guard<std::mutex> pl(projMutex_);
pendingProjectiles_.push_back(pi);
}
catch (...) {
}
}
continue;
}
if (msg.rfind("EVENT:", 0) == 0) {
auto parts = split(msg, ':');
if (parts.size() < 5) continue; // EVENT:ID:TYPE:TIME:DATA...
int remoteId = std::stoi(parts[1]);
std::string subType = parts[2];
uint64_t sentTime = std::stoull(parts[3]);
ClientState remoteState;
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)) };
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();
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.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
{
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{
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);
}
}
{
std::lock_guard<std::mutex> pLock(playersMutex);
{
std::lock_guard<std::mutex> pLock(playersMutex);
auto& rp = remotePlayers[remoteId];
rp.add_state(remoteState);
}
}
rp.add_state(remoteState);
}
}
}
}
void WebSocketClient::Send(const std::string& message) {
if (!connected) return;
}
}
void WebSocketClient::Send(const std::string& message) {
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_);
writeQueue_.push(ss);
std::lock_guard<std::mutex> lock(writeMutex_);
writeQueue_.push(ss);
// Если сейчас ничего не записывается, инициируем первую запись
if (!isWriting_) {
doWrite();
}
}
// Если сейчас ничего не записывается, инициируем первую запись
if (!isWriting_) {
doWrite();
}
}
void WebSocketClient::doWrite() {
// Эта функция всегда вызывается под мьютексом или из колбэка
if (writeQueue_.empty()) {
isWriting_ = false;
return;
}
void WebSocketClient::doWrite() {
// Эта функция всегда вызывается под мьютексом или из колбэка
if (writeQueue_.empty()) {
isWriting_ = false;
return;
}
isWriting_ = true;
auto message = writeQueue_.front();
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;
}
// Захватываем 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(); // Проверяем следующее
}
);
}
std::lock_guard<std::mutex> lock(writeMutex_);
writeQueue_.pop(); // Удаляем отправленное сообщение
doWrite(); // Проверяем следующее
}
);
}
}
#endif
+14
View File
@@ -34,6 +34,13 @@ namespace ZL {
std::unordered_map<int, ClientStateInterval> remotePlayers;
std::mutex playersMutex;
// Серверные коробки
std::vector<std::pair<Eigen::Vector3f, Eigen::Matrix3f>> serverBoxes_;
std::mutex boxesMutex;
std::vector<ProjectileInfo> pendingProjectiles_;
std::mutex projMutex_;
void startAsyncRead();
void processIncomingMessage(const std::string& msg);
@@ -54,6 +61,13 @@ namespace ZL {
std::lock_guard<std::mutex> lock(playersMutex);
return remotePlayers;
}
std::vector<std::pair<Eigen::Vector3f, Eigen::Matrix3f>> getServerBoxes() override {
std::lock_guard<std::mutex> lock(boxesMutex);
return serverBoxes_;
}
std::vector<ProjectileInfo> getPendingProjectiles() override;
};
}
#endif