From 2e4a7ab46743df153387b3d2ead3ff056356d3db Mon Sep 17 00:00:00 2001 From: Vladislav Khorev Date: Thu, 2 Apr 2026 19:24:15 +0300 Subject: [PATCH] Some cleanup --- proj-windows/CMakeLists.txt | 22 +- src/Environment.cpp | 2 - src/Environment.h | 3 - src/Game.cpp | 11 +- src/Game.h | 6 - src/SparkEmitter.cpp | 48 +- src/network/ClientState.cpp | 305 ------------ src/network/ClientState.h | 76 --- src/network/LocalClient.cpp | 581 ---------------------- src/network/LocalClient.h | 93 ---- src/network/NetworkInterface.h | 70 --- src/network/WebSocketClient.cpp | 127 ----- src/network/WebSocketClient.h | 67 --- src/network/WebSocketClientBase.cpp | 436 ---------------- src/network/WebSocketClientBase.h | 63 --- src/network/WebSocketClientEmscripten.cpp | 121 ----- src/network/WebSocketClientEmscripten.h | 46 -- 17 files changed, 19 insertions(+), 2058 deletions(-) delete mode 100644 src/network/ClientState.cpp delete mode 100644 src/network/ClientState.h delete mode 100644 src/network/LocalClient.cpp delete mode 100644 src/network/LocalClient.h delete mode 100644 src/network/NetworkInterface.h delete mode 100644 src/network/WebSocketClient.cpp delete mode 100644 src/network/WebSocketClient.h delete mode 100644 src/network/WebSocketClientBase.cpp delete mode 100644 src/network/WebSocketClientBase.h delete mode 100644 src/network/WebSocketClientEmscripten.cpp delete mode 100644 src/network/WebSocketClientEmscripten.h diff --git a/proj-windows/CMakeLists.txt b/proj-windows/CMakeLists.txt index 83d4f4f..f8c12e8 100644 --- a/proj-windows/CMakeLists.txt +++ b/proj-windows/CMakeLists.txt @@ -50,17 +50,17 @@ add_executable(space-game001 ../src/UiManager.h ../src/Projectile.h ../src/Projectile.cpp - ../src/network/NetworkInterface.h - ../src/network/LocalClient.h - ../src/network/LocalClient.cpp - ../src/network/ClientState.h - ../src/network/ClientState.cpp - ../src/network/WebSocketClient.h - ../src/network/WebSocketClient.cpp - ../src/network/WebSocketClientBase.h - ../src/network/WebSocketClientBase.cpp - ../src/network/WebSocketClientEmscripten.h - ../src/network/WebSocketClientEmscripten.cpp +# ../src/network/NetworkInterface.h +# ../src/network/LocalClient.h +# ../src/network/LocalClient.cpp +# ../src/network/ClientState.h +# ../src/network/ClientState.cpp +# ../src/network/WebSocketClient.h +# ../src/network/WebSocketClient.cpp +# ../src/network/WebSocketClientBase.h +# ../src/network/WebSocketClientBase.cpp +# ../src/network/WebSocketClientEmscripten.h +# ../src/network/WebSocketClientEmscripten.cpp ../src/render/TextRenderer.h ../src/render/TextRenderer.cpp ../src/MenuManager.h diff --git a/src/Environment.cpp b/src/Environment.cpp index 057f2db..ee2ae8c 100644 --- a/src/Environment.cpp +++ b/src/Environment.cpp @@ -31,8 +31,6 @@ bool Environment::tapDownHold = false; Eigen::Vector2f Environment::tapDownStartPos = { 0, 0 }; Eigen::Vector2f Environment::tapDownCurrentPos = { 0, 0 }; -ClientState Environment::shipState; - const float Environment::CONST_Z_NEAR = 0.1f; const float Environment::CONST_Z_FAR = 100.f; diff --git a/src/Environment.h b/src/Environment.h index 8eac9ca..81cc7b9 100644 --- a/src/Environment.h +++ b/src/Environment.h @@ -6,7 +6,6 @@ #include "render/OpenGlExtensions.h" #endif #include -#include "network/ClientState.h" namespace ZL { @@ -32,8 +31,6 @@ public: static Eigen::Vector2f tapDownStartPos; static Eigen::Vector2f tapDownCurrentPos; - static ClientState shipState; - static const float CONST_Z_NEAR; static const float CONST_Z_FAR; diff --git a/src/Game.cpp b/src/Game.cpp index d5c60d3..d5f001a 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -15,21 +15,12 @@ #include #endif -#ifdef NETWORK -#include "network/WebSocketClientBase.h" -#ifdef EMSCRIPTEN -#include "network/WebSocketClientEmscripten.h" -#else -#include "network/WebSocketClient.h" -#endif -#endif + #ifdef EMSCRIPTEN #include #endif -#include "network/LocalClient.h" -#include "network/ClientState.h" #include "GameConstants.h" namespace ZL diff --git a/src/Game.h b/src/Game.h index 59e0ecc..bacc905 100644 --- a/src/Game.h +++ b/src/Game.h @@ -8,7 +8,6 @@ #include "UiManager.h" #include "Projectile.h" #include "utils/TaskManager.h" -#include "network/NetworkInterface.h" #include #include #include @@ -16,7 +15,6 @@ #include #include #include "MenuManager.h" -#include "Space.h" #include @@ -113,9 +111,6 @@ namespace ZL { static void onResourcesZipError(const char* filename); #endif - //SDL_Window* window; - //SDL_GLContext glContext; - int64_t newTickCount; int64_t lastTickCount; uint32_t connectingStartTicks = 0; @@ -125,7 +120,6 @@ namespace ZL { static const size_t CONST_MAX_TIME_INTERVAL = 1000; MenuManager menuManager; - //Space space; }; diff --git a/src/SparkEmitter.cpp b/src/SparkEmitter.cpp index abcbe33..d65de51 100644 --- a/src/SparkEmitter.cpp +++ b/src/SparkEmitter.cpp @@ -94,16 +94,9 @@ namespace ZL { for (const auto& particle : particles) { if (particle.active) { Vector3f posCam; - if (withRotation) { - Vector3f rel = particle.position - Environment::shipState.position; - posCam = Environment::inverseShipMatrix * rel; - } - else { - posCam = particle.position; - //Vector3f rel = particle.position - Environment::shipState.position; - //posCam = Environment::shipState.position + Eigen::Quaternionf(Eigen::AngleAxisf(M_PI * 150 / 180.f, Eigen::Vector3f::UnitY())).toRotationMatrix() * Environment::inverseShipMatrix * rel; - } - //sortedParticles.push_back({ &particle, particle.position(2) }); + + posCam = particle.position; + sortedParticles.push_back({ &particle, posCam(2) }); } } @@ -116,39 +109,12 @@ namespace ZL { for (const auto& [particlePtr, depth] : sortedParticles) { const auto& particle = *particlePtr; Vector3f posCam; - if (useWorldSpace) { - Vector3f rel = particle.position - Environment::shipState.position; - posCam = Environment::inverseShipMatrix * rel; - } - else { - posCam = particle.position; - } + posCam = particle.position; + float size = particleSize * particle.scale; - if (withRotation) - { - - drawPositions.push_back(Environment::shipState.rotation * Vector3f{ -size, -size, 0 } + posCam); - drawTexCoords.push_back({ 0.0f, 0.0f }); - - drawPositions.push_back(Environment::shipState.rotation * Vector3f{ -size, size,0 } + posCam); - drawTexCoords.push_back({ 0.0f, 1.0f }); - - drawPositions.push_back(Environment::shipState.rotation * Vector3f{ size,size, 0 } + posCam); - drawTexCoords.push_back({ 1.0f, 1.0f }); - - drawPositions.push_back(Environment::shipState.rotation * Vector3f{ -size, -size, 0 } + posCam); - drawTexCoords.push_back({ 0.0f, 0.0f }); - - drawPositions.push_back(Environment::shipState.rotation * Vector3f{ size, size,0 } + posCam); - drawTexCoords.push_back({ 1.0f, 1.0f }); - - drawPositions.push_back(Environment::shipState.rotation * Vector3f{ size, -size, 0 } + posCam); - drawTexCoords.push_back({ 1.0f, 0.0f }); - } - else - { + drawPositions.push_back({ posCam(0) - size, posCam(1) - size, posCam(2) }); drawTexCoords.push_back({ 0.0f, 0.0f }); @@ -167,7 +133,7 @@ namespace ZL { drawPositions.push_back({ posCam(0) + size, posCam(1) - size, posCam(2) }); drawTexCoords.push_back({ 1.0f, 0.0f }); - } + } drawDataDirty = false; diff --git a/src/network/ClientState.cpp b/src/network/ClientState.cpp deleted file mode 100644 index e33c965..0000000 --- a/src/network/ClientState.cpp +++ /dev/null @@ -1,305 +0,0 @@ -#include "ClientState.h" - -uint32_t fnv1a_hash(const std::string& data) { - uint32_t hash = 0x811c9dc5; - for (unsigned char c : data) { - hash ^= c; - hash *= 0x01000193; - } - return hash; -} - -void ClientState::simulate_physics(size_t delta) { - if (discreteMag > 0.01f) - { - float rad = static_cast(discreteAngle) * static_cast(M_PI) / 180.0f; - - // Целевая угловая скорость (дискретная сила определяет модуль вектора) - // Вектор {cos, sin, 0} дает нам направление отклонения джойстика - Eigen::Vector3f targetAngularVelDir(sinf(rad), cosf(rad), 0.0f); - Eigen::Vector3f targetAngularVelocity = targetAngularVelDir * discreteMag; - - Eigen::Vector3f diffVel = targetAngularVelocity - currentAngularVelocity; - float diffLen = diffVel.norm(); - - if (diffLen > 0.0001f) { - // Вычисляем, на сколько мы можем изменить скорость в этом кадре - float maxChange = ANGULAR_ACCEL * static_cast(delta); - - if (diffLen <= maxChange) { - // Если до цели осталось меньше, чем шаг ускорения — просто прыгаем в цель - currentAngularVelocity = targetAngularVelocity; - } - - else { - // Линейно двигаемся в сторону целевого вектора - currentAngularVelocity += (diffVel / diffLen) * maxChange; - } - } - } - else - { - float currentSpeed = currentAngularVelocity.norm(); - - if (currentSpeed > 0.0001f) { - float drop = ANGULAR_ACCEL * static_cast(delta); - if (currentSpeed <= drop) { - currentAngularVelocity = Eigen::Vector3f::Zero(); - } - else { - // Уменьшаем модуль вектора, сохраняя направление - currentAngularVelocity -= (currentAngularVelocity / currentSpeed) * drop; - } - } - } - - - float distToCenter = position.norm(); // Расстояние до {0,0,0} - float landingZone = PLANET_RADIUS * PLANET_ALIGN_ZONE; - - if (distToCenter <= landingZone) { - Eigen::Vector3f planetNormal = position.normalized(); - - // --- 1. ВЫРАВНИВАНИЕ КРЕНА (Roll - ось Z) --- - Eigen::Vector3f localX = rotation.col(0); - float rollError = localX.dot(planetNormal); - - if (std::abs(rollError) > 0.001f) { - currentAngularVelocity.z() -= rollError * PLANET_ANGULAR_ACCEL * delta; - currentAngularVelocity.z() = std::max(-PLANET_MAX_ANGULAR_VELOCITY, - std::min(currentAngularVelocity.z(), PLANET_MAX_ANGULAR_VELOCITY)); - } - - // --- 2. ОГРАНИЧЕНИЕ ТАНГАЖА (Pitch - ось X) --- - // Нос корабля в локальных координатах — это -Z (третий столбец матрицы) - Eigen::Vector3f forwardDir = -rotation.col(2); - - // В твоем случае dot < 0 означает, что нос направлен К планете - float pitchSin = forwardDir.dot(planetNormal); - float currentPitchAngle = asinf(std::clamp(pitchSin, -1.0f, 1.0f)); - - // Лимит у нас M_PI / 6.0 (примерно 0.523) - // По твоим данным: -0.89 < -0.523, значит мы превысили наклон вниз - if (currentPitchAngle < -PITCH_LIMIT) { - // Вычисляем ошибку (насколько мы ушли "ниже" лимита) - // -0.89 - (-0.52) = -0.37 - float pitchError = currentPitchAngle + PITCH_LIMIT; - - // Теперь важно: нам нужно ПОДНЯТЬ нос. - // Если pitchError отрицательный, а нам нужно уменьшить вращение, - // пробуем прибавлять или вычитать в зависимости от твоей оси X. - // Судя по стандартной логике Eigen, нам нужно ПЛЮСОВАТЬ: - currentAngularVelocity.x() -= pitchError * PLANET_ANGULAR_ACCEL * delta; - } - } - else { - // Вне зоны: тормозим Z (крен) И X (тангаж), если они были активны - float drop = ANGULAR_ACCEL * delta; - - if (std::abs(currentAngularVelocity[2]) > 0.0001f) { - if (std::abs(currentAngularVelocity[2]) <= drop) { - currentAngularVelocity[2] = 0.0f; - } - else { - currentAngularVelocity[2] -= (currentAngularVelocity[2] > 0 ? 1.0f : -1.0f) * drop; - } - } - } - - // Ограничение скорости - currentAngularVelocity.x() = std::max(-PLANET_MAX_ANGULAR_VELOCITY, - std::min(currentAngularVelocity.x(), PLANET_MAX_ANGULAR_VELOCITY)); - // Ограничение скорости - currentAngularVelocity.y() = std::max(-PLANET_MAX_ANGULAR_VELOCITY, - std::min(currentAngularVelocity.y(), PLANET_MAX_ANGULAR_VELOCITY)); - // Ограничение скорости - currentAngularVelocity.z() = std::max(-PLANET_MAX_ANGULAR_VELOCITY, - std::min(currentAngularVelocity.z(), PLANET_MAX_ANGULAR_VELOCITY)); - - float speedScale = currentAngularVelocity.norm(); - if (speedScale > 0.0001f) { - // Коэффициент чувствительности вращения - - float deltaAlpha = speedScale * static_cast(delta) * ROTATION_SENSITIVITY; - - Eigen::Vector3f axis = currentAngularVelocity.normalized(); - Eigen::Quaternionf rotateQuat(Eigen::AngleAxisf(deltaAlpha, axis)); - - rotation = rotation * rotateQuat.toRotationMatrix(); - } - - - // 4. Линейное изменение линейной скорости - float shipDesiredVelocity = selectedVelocity * 100.f; - - if (velocity < shipDesiredVelocity) - { - velocity += delta * SHIP_ACCEL; - if (velocity > shipDesiredVelocity) - { - velocity = shipDesiredVelocity; - } - } - else if (velocity > shipDesiredVelocity) - { - velocity -= delta * SHIP_ACCEL; - if (velocity < shipDesiredVelocity) - { - velocity = shipDesiredVelocity; - } - } - - if (fabs(velocity) > 0.01f) - { - Eigen::Vector3f velocityDirection = { 0,0, -velocity * delta / 1000.f }; - Eigen::Vector3f velocityDirectionAdjusted = rotation * velocityDirection; - position = position + velocityDirectionAdjusted; - } -} - -void ClientState::apply_lag_compensation(std::chrono::system_clock::time_point nowTime) { - - // 2. Вычисляем задержку - long long deltaMs = 0; - if (nowTime > lastUpdateServerTime) { - deltaMs = std::chrono::duration_cast(nowTime - lastUpdateServerTime).count(); - } - - long long deltaMsLeftover = deltaMs; - - while (deltaMsLeftover > 0) - { - long long miniDelta = std::min(50LL, deltaMsLeftover); - simulate_physics(miniDelta); - deltaMsLeftover -= miniDelta; - } - - /* - // 3. Защита от слишком больших скачков (Clamp) - // Если лаг более 500мс, ограничиваем его, чтобы избежать резких рывков - long long final_lag_ms = deltaMs;//min(deltaMs, 500ll); - - if (final_lag_ms > 0) { - // Доматываем симуляцию на величину задержки - // Мы предполагаем, что за это время параметры управления не менялись - simulate_physics(final_lag_ms); - }*/ -} - -void ClientState::handle_full_sync(const std::vector& parts, int startFrom) { - // Позиция - 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])); - rotation = q.toRotationMatrix(); - - currentAngularVelocity = Eigen::Vector3f{ - std::stof(parts[startFrom + 7]), - std::stof(parts[startFrom + 8]), - std::stof(parts[startFrom + 9]) }; - velocity = std::stof(parts[startFrom + 10]); - selectedVelocity = std::stoi(parts[startFrom + 11]); - discreteMag = std::stof(parts[startFrom + 12]); - discreteAngle = std::stoi(parts[startFrom + 13]); -} - -std::string ClientState::formPingMessageContent() const -{ - Eigen::Quaternionf q(rotation); - - std::string pingMsg = 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(currentAngularVelocity.x()) + ":" - + std::to_string(currentAngularVelocity.y()) + ":" - + std::to_string(currentAngularVelocity.z()) + ":" - + std::to_string(velocity) + ":" - + std::to_string(selectedVelocity) + ":" - + std::to_string(discreteMag) + ":" // Используем те же static переменные из блока ROT - + std::to_string(discreteAngle); - - return pingMsg; -} - - -void ClientStateInterval::add_state(const ClientState& state) -{ - auto nowTime = std::chrono::system_clock::now(); - - if (timedStates.size() > 0 && timedStates[timedStates.size() - 1].lastUpdateServerTime == state.lastUpdateServerTime) - { - timedStates[timedStates.size() - 1] = state; - } - else - { - timedStates.push_back(state); - } - - auto cutoff_time = nowTime - std::chrono::milliseconds(CUTOFF_TIME); - - while (timedStates.size() > 0 && timedStates[0].lastUpdateServerTime < cutoff_time) - { - timedStates.erase(timedStates.begin()); - } -} - -bool ClientStateInterval::canFetchClientStateAtTime(std::chrono::system_clock::time_point targetTime) const -{ - if (timedStates.empty()) - { - return false; - } - if (timedStates[0].lastUpdateServerTime > targetTime) - { - return false; - } - - return true; -} - -ClientState ClientStateInterval::fetchClientStateAtTime(std::chrono::system_clock::time_point targetTime) const { - - ClientState closestState; - - if (timedStates.empty()) - { - throw std::runtime_error("No timed client states available"); - return closestState; - } - if (timedStates[0].lastUpdateServerTime > targetTime) - { - throw std::runtime_error("Found time but it is in future"); - return closestState; - } - if (timedStates.size() == 1) - { - closestState = timedStates[0]; - closestState.apply_lag_compensation(targetTime); - return closestState; - } - - - for (size_t i = 0; i < timedStates.size() - 1; ++i) - { - const auto& earlierState = timedStates[i]; - const auto& laterState = timedStates[i + 1]; - if (earlierState.lastUpdateServerTime <= targetTime && laterState.lastUpdateServerTime >= targetTime) - { - closestState = earlierState; - closestState.apply_lag_compensation(targetTime); - return closestState; - } - } - - closestState = timedStates[timedStates.size() - 1]; - closestState.apply_lag_compensation(targetTime); - return closestState; -} diff --git a/src/network/ClientState.h b/src/network/ClientState.h deleted file mode 100644 index d537030..0000000 --- a/src/network/ClientState.h +++ /dev/null @@ -1,76 +0,0 @@ -#pragma once -#include -#include -#define _USE_MATH_DEFINES -#include -#include -#include - - -using std::min; -using std::max; - -constexpr auto NET_SECRET = "880b3713b9ff3e7a94b2712d54679e1f"; -#define ENABLE_NETWORK_CHECKSUM - -constexpr float ANGULAR_ACCEL = 0.005f * 1000.0f; -constexpr float SHIP_ACCEL = 1.0f * 200.0f; -constexpr float ROTATION_SENSITIVITY = 0.002f; - -constexpr float PLANET_RADIUS = 20000.f; -constexpr float PLANET_ALIGN_ZONE = 1.05f; -constexpr float PLANET_ANGULAR_ACCEL = 0.01f; -constexpr float PLANET_MAX_ANGULAR_VELOCITY = 10.f; -constexpr float PITCH_LIMIT = static_cast(M_PI) / 9.f;//18.0f; - -constexpr long long SERVER_DELAY = 0; //ms -constexpr long long CLIENT_DELAY = 500; //ms -constexpr long long CUTOFF_TIME = 5000; //ms -constexpr long long PLAYER_TIMEOUT_MS = 10000; //ms — disconnect if no UPD received - -constexpr float PROJECTILE_VELOCITY = 600.f; -constexpr float PROJECTILE_LIFE = 15000.f; //ms - -const float projectileHitRadius = 2.5f * 4; -const float boxCollisionRadius = 3.0f * 4; -const float shipCollisionRadius = 15.0f * 3; -const float BOX_PICKUP_RADIUS = shipCollisionRadius * 3; -const float npcCollisionRadius = 5.0f * 3; - -uint32_t fnv1a_hash(const std::string& data); - -struct ClientState { - int id = 0; - 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; - - std::string nickname = "Player"; - int shipType = 0; - - std::chrono::system_clock::time_point lastUpdateServerTime; - - void simulate_physics(size_t delta); - - void apply_lag_compensation(std::chrono::system_clock::time_point nowTime); - - void handle_full_sync(const std::vector& parts, int startFrom); - - std::string formPingMessageContent() const; -}; - -struct ClientStateInterval -{ - std::vector timedStates; - - void add_state(const ClientState& state); - - bool canFetchClientStateAtTime(std::chrono::system_clock::time_point targetTime) const; - - ClientState fetchClientStateAtTime(std::chrono::system_clock::time_point targetTime) const; -}; - diff --git a/src/network/LocalClient.cpp b/src/network/LocalClient.cpp deleted file mode 100644 index f1e9cab..0000000 --- a/src/network/LocalClient.cpp +++ /dev/null @@ -1,581 +0,0 @@ -#include "LocalClient.h" -#include -#include -#include -#include -#define _USE_MATH_DEFINES -#include - -namespace ZL { - - void LocalClient::Connect(const std::string& host, uint16_t port) { - generateBoxes(); - initializeNPCs(); - lastUpdateMs = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()).count(); - } - - void LocalClient::generateBoxes() { - serverBoxes.clear(); - - std::random_device rd; - std::mt19937 gen(rd()); - - const float MIN_COORD = -1000.0f; - const float MAX_COORD = 1000.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 < 50; i++) { - bool accepted = false; - int attempts = 0; - - while (!accepted && attempts < MAX_ATTEMPTS) { - LocalServerBox box; - box.position = Eigen::Vector3f( - (float)posDistrib(gen), - (float)posDistrib(gen), - (float)posDistrib(gen) - ); - - accepted = true; - for (const auto& existingBox : serverBoxes) { - 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(); - serverBoxes.push_back(box); - } - - attempts++; - } - } - - std::cout << "LocalClient: Generated " << serverBoxes.size() << " boxes\n"; - } - - Eigen::Vector3f LocalClient::generateRandomPosition() { - std::random_device rd; - std::mt19937 gen(rd()); - std::uniform_real_distribution<> distrib(-5000.0, 5000.0); - - return Eigen::Vector3f( - (float)distrib(gen), - (float)distrib(gen), - (float)distrib(gen) + 45000.0f - ); - } - - void LocalClient::initializeNPCs() { - npcs.clear(); - std::random_device rd; - std::mt19937 gen(rd()); - std::uniform_int_distribution typeDistrib(0, 1); // 0 = default ship, 1 = cargo - - for (int i = 0; i < 3; ++i) { - LocalNPC npc; - npc.id = 100 + i; - npc.currentState.id = npc.id; - npc.currentState.position = generateRandomPosition(); - npc.currentState.rotation = Eigen::Matrix3f::Identity(); - npc.currentState.velocity = 0.0f; - npc.currentState.selectedVelocity = 0; - npc.currentState.discreteMag = 0.0f; - npc.currentState.discreteAngle = -1; - npc.currentState.currentAngularVelocity = Eigen::Vector3f::Zero(); - -// random - int shipType = typeDistrib(gen); - npc.shipType = shipType; - npc.currentState.shipType = shipType; - - npc.targetPosition = generateRandomPosition(); - npc.lastStateUpdateMs = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()).count(); - npc.destroyed = false; - - npc.stateHistory.add_state(npc.currentState); - npcs.push_back(npc); - } - } - - void LocalClient::updateNPCs() { - auto now_ms = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()).count(); - - for (auto& npc : npcs) { - if (npc.destroyed) continue; - - uint64_t deltaMs = now_ms - npc.lastStateUpdateMs; - if (deltaMs == 0) { - npc.lastStateUpdateMs = now_ms; - continue; - } - npc.lastStateUpdateMs = now_ms; - - Eigen::Vector3f toTarget = npc.targetPosition - npc.currentState.position; - float distance = toTarget.norm(); - - const float ARRIVAL_THRESHOLD = 100.0f; - - if (distance < ARRIVAL_THRESHOLD) { - npc.targetPosition = generateRandomPosition(); - toTarget = npc.targetPosition - npc.currentState.position; - distance = toTarget.norm(); - } - - Eigen::Vector3f forwardWorld = -npc.currentState.rotation.col(2); - forwardWorld.normalize(); - - Eigen::Vector3f desiredDir = (distance > 0.001f) ? toTarget.normalized() : Eigen::Vector3f::UnitZ(); - float dot = forwardWorld.dot(desiredDir); - float angleErrorRad = std::acos(std::clamp(dot, -1.0f, 1.0f)); - - const float ALIGN_TOLERANCE = 0.15f; - - const float HYSTERESIS_FACTOR = 1.35f; - const float SOFT_THRUST_ANGLE = ALIGN_TOLERANCE * HYSTERESIS_FACTOR; - - if (angleErrorRad < ALIGN_TOLERANCE) { - npc.currentState.selectedVelocity = 1; - npc.currentState.discreteMag = 0.0f; - } - else if (angleErrorRad < SOFT_THRUST_ANGLE) { - npc.currentState.selectedVelocity = 1; - npc.currentState.discreteMag = std::min(0.50f, (angleErrorRad - ALIGN_TOLERANCE) * 10.0f); - } - else { - npc.currentState.selectedVelocity = 0; - - Eigen::Vector3f localDesired = npc.currentState.rotation.transpose() * desiredDir; - float dx = localDesired.x(); - float dy = localDesired.y(); - float dz = localDesired.z(); - - float turnX = dy; - float turnY = -dx; - float turnLen = std::sqrt(turnX * turnX + turnY * turnY); - - if (turnLen > 0.0001f) { - turnX /= turnLen; - turnY /= turnLen; - - float rad = std::atan2(turnX, turnY); - int angleDeg = static_cast(std::round(rad * 180.0f / M_PI)); - if (angleDeg < 0) angleDeg += 360; - - npc.currentState.discreteAngle = angleDeg; - npc.currentState.discreteMag = std::min(1.0f, angleErrorRad * 2.2f); - } - else if (angleErrorRad > 0.1f) { - npc.currentState.discreteAngle = 0; - npc.currentState.discreteMag = 1.0f; - } - else { - npc.currentState.discreteMag = 0.0f; - } - } - - npc.currentState.simulate_physics(static_cast(deltaMs)); - npc.currentState.lastUpdateServerTime = std::chrono::system_clock::time_point( - std::chrono::milliseconds(now_ms)); - npc.stateHistory.add_state(npc.currentState); - } - } - - void LocalClient::Poll() { - updatePhysics(); - updateNPCs(); - checkCollisions(); - } - - void LocalClient::updatePhysics() { - auto now_ms = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()).count(); - - if (lastUpdateMs == 0) { - lastUpdateMs = now_ms; - return; - } - - uint64_t deltaMs = now_ms - lastUpdateMs; - float dt = deltaMs / 1000.0f; - lastUpdateMs = now_ms; - - std::vector indicesToRemove; - - for (size_t i = 0; i < projectiles.size(); ++i) { - auto& pr = projectiles[i]; - pr.pos += pr.vel * dt; - - if (now_ms > pr.spawnMs + static_cast(pr.lifeMs)) { - indicesToRemove.push_back(static_cast(i)); - } - } - - if (!indicesToRemove.empty()) { - std::sort(indicesToRemove.rbegin(), indicesToRemove.rend()); - for (int idx : indicesToRemove) { - if (idx >= 0 && idx < (int)projectiles.size()) { - projectiles.erase(projectiles.begin() + idx); - } - } - } - } - - void LocalClient::checkCollisions() { - auto now_ms = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()).count(); - - std::vector> boxProjectileCollisions; - - for (size_t bi = 0; bi < serverBoxes.size(); ++bi) { - if (serverBoxes[bi].destroyed) continue; - - Eigen::Vector3f boxWorld = serverBoxes[bi].position + Eigen::Vector3f(0.0f, 0.0f, 45000.0f); - - for (size_t pi = 0; pi < projectiles.size(); ++pi) { - const auto& pr = projectiles[pi]; - Eigen::Vector3f diff = pr.pos - boxWorld; - float thresh = boxCollisionRadius + projectileHitRadius; - - if (diff.squaredNorm() <= thresh * thresh) { - boxProjectileCollisions.push_back({ bi, pi }); - } - } - } - - std::vector projIndicesToRemove; - for (const auto& [boxIdx, projIdx] : boxProjectileCollisions) { - if (!serverBoxes[boxIdx].destroyed) { - serverBoxes[boxIdx].destroyed = true; - - Eigen::Vector3f boxWorld = serverBoxes[boxIdx].position + Eigen::Vector3f(0.0f, 0.0f, 45000.0f); - - BoxDestroyedInfo destruction; - destruction.boxIndex = static_cast(boxIdx); - destruction.serverTime = now_ms; - destruction.position = boxWorld; - destruction.destroyedBy = projectiles[projIdx].shooterId; - - pendingBoxDestructions.push_back(destruction); - - std::cout << "LocalClient: Box " << boxIdx << " destroyed by projectile from player " - << projectiles[projIdx].shooterId << std::endl; - - // Respawn box - { - std::random_device rd2; - std::mt19937 gen2(rd2()); - std::uniform_real_distribution angleDist(0.f, static_cast(M_PI * 2.0)); - Eigen::Vector3f newPos = generateRespawnBoxPos(static_cast(boxIdx)); - Eigen::Vector3f axis = Eigen::Vector3f::Random().normalized(); - Eigen::Matrix3f newRot = Eigen::AngleAxisf(angleDist(gen2), axis).toRotationMatrix(); - serverBoxes[boxIdx].position = newPos; - serverBoxes[boxIdx].rotation = newRot; - serverBoxes[boxIdx].destroyed = false; - BoxRespawnInfo respawn; - respawn.boxIndex = static_cast(boxIdx); - respawn.position = newPos; - respawn.rotation = newRot; - pendingBoxRespawns.push_back(respawn); - } - - if (std::find(projIndicesToRemove.begin(), projIndicesToRemove.end(), (int)projIdx) - == projIndicesToRemove.end()) { - projIndicesToRemove.push_back(static_cast(projIdx)); - } - } - } - - std::vector> npcProjectileCollisions; - - for (size_t ni = 0; ni < npcs.size(); ++ni) { - if (npcs[ni].destroyed) continue; - - for (size_t pi = 0; pi < projectiles.size(); ++pi) { - const auto& pr = projectiles[pi]; - Eigen::Vector3f diff = pr.pos - npcs[ni].currentState.position; - float thresh = npcCollisionRadius + projectileHitRadius; - - if (diff.squaredNorm() <= thresh * thresh) { - npcProjectileCollisions.push_back({ ni, pi }); - } - } - } - - for (const auto& [npcIdx, projIdx] : npcProjectileCollisions) { - if (!npcs[npcIdx].destroyed) { - npcs[npcIdx].destroyed = true; - - DeathInfo death; - death.targetId = npcs[npcIdx].id; - death.serverTime = now_ms; - death.position = npcs[npcIdx].currentState.position; - death.killerId = projectiles[projIdx].shooterId; - - pendingDeaths.push_back(death); - - std::cout << "LocalClient: NPC " << npcs[npcIdx].id << " destroyed by projectile from player " - << projectiles[projIdx].shooterId << " at position (" - << npcs[npcIdx].currentState.position.x() << ", " - << npcs[npcIdx].currentState.position.y() << ", " - << npcs[npcIdx].currentState.position.z() << ")" << std::endl; - - if (std::find(projIndicesToRemove.begin(), projIndicesToRemove.end(), (int)projIdx) - == projIndicesToRemove.end()) { - projIndicesToRemove.push_back(static_cast(projIdx)); - } - } - } - - if (!projIndicesToRemove.empty()) { - std::sort(projIndicesToRemove.rbegin(), projIndicesToRemove.rend()); - for (int idx : projIndicesToRemove) { - if (idx >= 0 && idx < (int)projectiles.size()) { - projectiles.erase(projectiles.begin() + idx); - } - } - } - - if (hasLocalPlayerState) { - for (size_t bi = 0; bi < serverBoxes.size(); ++bi) { - if (serverBoxes[bi].destroyed) continue; - - Eigen::Vector3f boxWorld = serverBoxes[bi].position + Eigen::Vector3f(0.0f, 0.0f, 45000.0f); - Eigen::Vector3f diff = localPlayerState.position - boxWorld; - float thresh = shipCollisionRadius + boxCollisionRadius; - - if (diff.squaredNorm() <= thresh * thresh) { - serverBoxes[bi].destroyed = true; - - BoxDestroyedInfo destruction; - destruction.boxIndex = static_cast(bi); - destruction.serverTime = now_ms; - destruction.position = boxWorld; - destruction.destroyedBy = GetClientId(); - - pendingBoxDestructions.push_back(destruction); - - std::cout << "LocalClient: Box " << bi << " destroyed by ship collision with player " - << GetClientId() << std::endl; - - // Respawn box - { - std::random_device rd2; - std::mt19937 gen2(rd2()); - std::uniform_real_distribution angleDist(0.f, static_cast(M_PI * 2.0)); - Eigen::Vector3f newPos = generateRespawnBoxPos(static_cast(bi)); - Eigen::Vector3f axis = Eigen::Vector3f::Random().normalized(); - Eigen::Matrix3f newRot = Eigen::AngleAxisf(angleDist(gen2), axis).toRotationMatrix(); - serverBoxes[bi].position = newPos; - serverBoxes[bi].rotation = newRot; - serverBoxes[bi].destroyed = false; - BoxRespawnInfo respawn; - respawn.boxIndex = static_cast(bi); - respawn.position = newPos; - respawn.rotation = newRot; - pendingBoxRespawns.push_back(respawn); - } - } - } - } - } - - Eigen::Vector3f LocalClient::generateRespawnBoxPos(int skipIdx) { - std::random_device rd; - std::mt19937 gen(rd()); - std::uniform_real_distribution dist(-1000.f, 1000.f); - - for (int attempt = 0; attempt < 500; ++attempt) { - Eigen::Vector3f cand(dist(gen), dist(gen), dist(gen)); - bool safe = true; - for (int i = 0; i < (int)serverBoxes.size(); ++i) { - if (i == skipIdx) continue; - if (serverBoxes[i].destroyed) continue; - if ((cand - serverBoxes[i].position).squaredNorm() < 9.f) { - safe = false; - break; - } - } - if (safe) return cand; - } - return Eigen::Vector3f(dist(gen), dist(gen), dist(gen)); - } - - std::vector LocalClient::getPendingBoxPickups() { - auto result = pendingBoxPickups; - pendingBoxPickups.clear(); - return result; - } - - std::vector LocalClient::getPendingBoxRespawns() { - auto result = pendingBoxRespawns; - pendingBoxRespawns.clear(); - return result; - } - - void LocalClient::Send(const std::string& message) { - auto parts = [](const std::string& s, char delimiter) { - std::vector tokens; - std::string token; - std::istringstream tokenStream(s); - while (std::getline(tokenStream, token, delimiter)) { - tokens.push_back(token); - } - return tokens; - }(message, ':'); - - if (parts.empty()) return; - - std::string type = parts[0]; - - if (type == "BOX_PICKUP") { - if (parts.size() < 2) return; - if (!hasLocalPlayerState || localPlayerState.shipType != 1) return; - - int boxIdx = -1; - try { boxIdx = std::stoi(parts[1]); } catch (...) { return; } - - if (boxIdx < 0 || boxIdx >= (int)serverBoxes.size()) return; - if (serverBoxes[boxIdx].destroyed) return; - - Eigen::Vector3f boxWorld = serverBoxes[boxIdx].position + Eigen::Vector3f(0.f, 0.f, 45000.f); - float distSq = (localPlayerState.position - boxWorld).squaredNorm(); - if (distSq > BOX_PICKUP_RADIUS * BOX_PICKUP_RADIUS) return; - - serverBoxes[boxIdx].destroyed = true; - - BoxPickedUpInfo pickup; - pickup.boxIndex = boxIdx; - pickup.pickedUpBy = GetClientId(); - pendingBoxPickups.push_back(pickup); - - std::cout << "LocalClient: Box " << boxIdx << " picked up by player " << GetClientId() << "\n"; - - // Respawn box at new position - { - std::random_device rd2; - std::mt19937 gen2(rd2()); - std::uniform_real_distribution angleDist(0.f, static_cast(M_PI * 2.0)); - Eigen::Vector3f newPos = generateRespawnBoxPos(boxIdx); - Eigen::Vector3f axis = Eigen::Vector3f::Random().normalized(); - Eigen::Matrix3f newRot = Eigen::AngleAxisf(angleDist(gen2), axis).toRotationMatrix(); - serverBoxes[boxIdx].position = newPos; - serverBoxes[boxIdx].rotation = newRot; - serverBoxes[boxIdx].destroyed = false; - - BoxRespawnInfo respawn; - respawn.boxIndex = boxIdx; - respawn.position = newPos; - respawn.rotation = newRot; - pendingBoxRespawns.push_back(respawn); - std::cout << "LocalClient: Box " << boxIdx << " respawned after pickup\n"; - } - return; - } - - if (type == "FIRE") { - if (parts.size() < 10) return; - - uint64_t clientTime = std::stoull(parts[1]); - Eigen::Vector3f pos{ - std::stof(parts[2]), std::stof(parts[3]), std::stof(parts[4]) - }; - Eigen::Quaternionf dir( - std::stof(parts[5]), std::stof(parts[6]), std::stof(parts[7]), std::stof(parts[8]) - ); - float velocity = std::stof(parts[9]); - - int shotCount = 2; - if (parts.size() >= 11) { - try { shotCount = std::stoi(parts[10]); } - catch (...) { shotCount = 2; } - } - - const std::vector localOffsets = { - Eigen::Vector3f(-1.5f, 0.9f - 6.f, 5.0f), - Eigen::Vector3f(1.5f, 0.9f - 6.f, 5.0f) - }; - - uint64_t now_ms = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()).count(); - - for (int i = 0; i < std::min(shotCount, (int)localOffsets.size()); ++i) { - LocalProjectile pr; - pr.shooterId = GetClientId(); - pr.spawnMs = now_ms; - Eigen::Vector3f shotPos = pos + dir.toRotationMatrix() * localOffsets[i]; - pr.pos = shotPos; - Eigen::Vector3f localForward(0.0f, 0.0f, -1.0f); - Eigen::Vector3f worldForward = dir.toRotationMatrix() * localForward; - float len = worldForward.norm(); - if (len > 1e-6f) worldForward /= len; - pr.vel = worldForward * velocity; - pr.lifeMs = 5000.0f; - projectiles.push_back(pr); - - ProjectileInfo pinfo; - pinfo.shooterId = pr.shooterId; - pinfo.clientTime = clientTime; - pinfo.position = pr.pos; - pinfo.rotation = dir.toRotationMatrix(); - pinfo.velocity = velocity; - - if (pinfo.shooterId != GetClientId()) { - pendingProjectiles.push_back(pinfo); - } - std::cout << "LocalClient: Created server projectile at pos (" << shotPos.x() << ", " - << shotPos.y() << ", " << shotPos.z() << ") vel (" << pr.vel.x() << ", " - << pr.vel.y() << ", " << pr.vel.z() << ") shooter=" << pr.shooterId << std::endl; - } - } - } - - std::vector LocalClient::getPendingProjectiles() { - auto result = pendingProjectiles; - pendingProjectiles.clear(); - return result; - } - - std::vector LocalClient::getPendingDeaths() { - auto result = pendingDeaths; - pendingDeaths.clear(); - return result; - } - - std::unordered_map LocalClient::getRemotePlayers() { - std::unordered_map result; - for (const auto& npc : npcs) { - if (!npc.destroyed) { - result[npc.id] = npc.stateHistory; - } - } - return result; - } - - std::vector> LocalClient::getServerBoxes() { - std::vector> result; - for (const auto& box : serverBoxes) { - result.push_back({ box.position, box.rotation }); - } - return result; - } - - std::vector LocalClient::getPendingBoxDestructions() { - auto result = pendingBoxDestructions; - pendingBoxDestructions.clear(); - return result; - } -} \ No newline at end of file diff --git a/src/network/LocalClient.h b/src/network/LocalClient.h deleted file mode 100644 index ce683fd..0000000 --- a/src/network/LocalClient.h +++ /dev/null @@ -1,93 +0,0 @@ -#pragma once - -#include "NetworkInterface.h" -#include -#include -#include -#include -#include - -namespace ZL { - - struct LocalServerBox { - Eigen::Vector3f position; - Eigen::Matrix3f rotation; - float collisionRadius = 2.0f; - bool destroyed = false; - }; - - struct LocalProjectile { - int shooterId = -1; - uint64_t spawnMs = 0; - Eigen::Vector3f pos; - Eigen::Vector3f vel; - float lifeMs = 5000.0f; - }; - - struct LocalNPC { - int id = -1; - ClientState currentState; - ClientStateInterval stateHistory; - Eigen::Vector3f targetPosition; - uint64_t lastStateUpdateMs = 0; - bool destroyed = false; - int shipType = 0; - }; - - class LocalClient : public INetworkClient { - private: - std::queue messageQueue; - std::vector serverBoxes; - std::vector projectiles; - std::vector pendingProjectiles; - std::vector pendingDeaths; - std::vector pendingBoxDestructions; - std::vector pendingBoxPickups; - std::vector pendingBoxRespawns; - std::vector pendingRespawns; - - uint64_t lastUpdateMs = 0; - ClientState localPlayerState; - bool hasLocalPlayerState = false; - - std::vector npcs; - - void updatePhysics(); - void checkCollisions(); - void generateBoxes(); - void initializeNPCs(); - void updateNPCs(); - Eigen::Vector3f generateRandomPosition(); - Eigen::Vector3f generateRespawnBoxPos(int skipIdx); - - public: - void Connect(const std::string& host, uint16_t port) override; - - void Poll() override; - - void Send(const std::string& message) override; - - bool IsConnected() const override { return true; } - int GetClientId() const override { return 1; } - std::vector getPendingProjectiles() override; - - std::unordered_map getRemotePlayers() override; - - std::vector> getServerBoxes() override; - - std::vector getPendingDeaths() override; - - std::vector getPendingRespawns() override { - return {}; - } - - std::vector getPendingBoxDestructions() override; - std::vector getPendingBoxPickups() override; - std::vector getPendingBoxRespawns() override; - - void setLocalPlayerState(const ClientState& state) { - localPlayerState = state; - hasLocalPlayerState = true; - } - }; -} \ No newline at end of file diff --git a/src/network/NetworkInterface.h b/src/network/NetworkInterface.h deleted file mode 100644 index ba60170..0000000 --- a/src/network/NetworkInterface.h +++ /dev/null @@ -1,70 +0,0 @@ -#pragma once -#include -#include -#include -#include "ClientState.h" - -// 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; - }; - - struct DeathInfo { - int targetId = -1; - uint64_t serverTime = 0; - Eigen::Vector3f position = Eigen::Vector3f::Zero(); - int killerId = -1; - }; - - struct BoxDestroyedInfo { - int boxIndex = -1; - uint64_t serverTime = 0; - Eigen::Vector3f position = Eigen::Vector3f::Zero(); - int destroyedBy = -1; - }; - - struct BoxPickedUpInfo { - int boxIndex = -1; - int pickedUpBy = -1; - }; - - struct BoxRespawnInfo { - int boxIndex = -1; - Eigen::Vector3f position = Eigen::Vector3f::Zero(); - Eigen::Matrix3f rotation = Eigen::Matrix3f::Identity(); - }; - - class INetworkClient { - public: - virtual ~INetworkClient() = default; - virtual void Connect(const std::string& host, uint16_t port) = 0; - virtual void Disconnect() {} - virtual void Send(const std::string& message) = 0; - virtual bool IsConnected() const = 0; - virtual void Poll() = 0; // ƒл¤ обработки вход¤щих пакетов - virtual std::unordered_map getRemotePlayers() = 0; - - virtual std::vector> getServerBoxes() = 0; - - virtual std::vector getServerBoxDestroyedFlags() { return {}; } - - virtual std::vector getPendingProjectiles() = 0; - - virtual std::vector getPendingDeaths() = 0; - virtual std::vector getPendingRespawns() = 0; - virtual int GetClientId() const { return -1; } - virtual std::vector getPendingBoxDestructions() = 0; - virtual std::vector getPendingBoxPickups() { return {}; } - virtual std::vector getPendingBoxRespawns() { return {}; } - virtual int64_t getTimeOffset() const { return 0; } - virtual std::vector getPendingDisconnects() { return {}; } - - }; -} diff --git a/src/network/WebSocketClient.cpp b/src/network/WebSocketClient.cpp deleted file mode 100644 index 69301c7..0000000 --- a/src/network/WebSocketClient.cpp +++ /dev/null @@ -1,127 +0,0 @@ -#ifdef NETWORK - -#include "WebSocketClient.h" -#include -#include - -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)); - - ws_ = std::make_unique>(ioc_); - - // Выполняем синхронный коннект и handshake для простоты старта - boost::beast::get_lowest_layer(*ws_).connect(results); - ws_->handshake(host, "/"); - - connected = true; - - // Запускаем асинхронное чтение в пуле потоков TaskManager - startAsyncRead(); - - } - catch (std::exception& e) { - std::cerr << "Network Error: " << e.what() << std::endl; - } - } - - void WebSocketClient::Disconnect() { - if (!ws_ || !connected) return; - connected = false; - try { - boost::beast::get_lowest_layer(*ws_).cancel(); - } - catch (...) {} - } - - 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) { - // Lock-free push: producer (I/O thread) pushes to its buffer - readProducerBuf_.load(std::memory_order_relaxed)->push_back(msg); - } - - void WebSocketClient::Poll() { - // Lock-free drain: swap consumer buffer with producer if ours is empty, then process all - MessageBuf* c = readConsumerBuf_.load(std::memory_order_acquire); - if (c->empty()) { - MessageBuf* p = readProducerBuf_.exchange(c, std::memory_order_acq_rel); - readConsumerBuf_.store(p, std::memory_order_release); - c = p; - } - for (std::string& msg : *c) { - HandlePollMessage(msg); - } - c->clear(); - } - - - - void WebSocketClient::Send(const std::string& message) { - if (!connected) return; - - std::string finalMessage = SignMessage(message); - auto ss = std::make_shared(std::move(finalMessage)); - - // Lock-free push to write queue - writeProducerBuf_.load(std::memory_order_relaxed)->push_back(ss); - - // Start write chain if not already writing - bool expected = false; - if (isWriting_.compare_exchange_strong(expected, true, std::memory_order_acq_rel)) { - doWrite(); - } - } - - void WebSocketClient::doWrite() { - // Lock-free: take next message from consumer buffer; swap buffers if drained - WriteBuf* c = writeConsumerBuf_.load(std::memory_order_acquire); - if (currentWriteBuf_ == nullptr || currentWriteIndex_ >= currentWriteBuf_->size()) { - if (currentWriteBuf_) { - currentWriteBuf_->clear(); - } - currentWriteBuf_ = c; - if (currentWriteBuf_->empty()) { - WriteBuf* p = writeProducerBuf_.exchange(currentWriteBuf_, std::memory_order_acq_rel); - writeConsumerBuf_.store(p, std::memory_order_release); - currentWriteBuf_ = p; - } - currentWriteIndex_ = 0; - } - if (currentWriteIndex_ >= currentWriteBuf_->size()) { - isWriting_.store(false, std::memory_order_release); - return; - } - - std::shared_ptr message = (*currentWriteBuf_)[currentWriteIndex_++]; - - ws_->async_write( - boost::asio::buffer(*message), - [this, message](boost::beast::error_code ec, std::size_t) { - if (ec) { - connected = false; - isWriting_.store(false, std::memory_order_release); - return; - } - doWrite(); - } - ); - } -} - -#endif \ No newline at end of file diff --git a/src/network/WebSocketClient.h b/src/network/WebSocketClient.h deleted file mode 100644 index 956aeae..0000000 --- a/src/network/WebSocketClient.h +++ /dev/null @@ -1,67 +0,0 @@ -#pragma once - -#ifdef NETWORK - -#include "WebSocketClientBase.h" -#include -#include -#include -#include -#include -#include -#include - -namespace ZL { - - // Lock-free SPSC double-buffer: producer pushes to one buffer, consumer swaps and drains the other. - // No mutexes; avoids contention under high message load. - class WebSocketClient : public WebSocketClientBase { - private: - boost::asio::io_context& ioc_; - std::unique_ptr> ws_; - boost::beast::flat_buffer buffer_; - - // Incoming messages: I/O thread pushes, main thread drains in Poll() - using MessageBuf = std::vector; - MessageBuf readBuffer0_; - MessageBuf readBuffer1_; - std::atomic readProducerBuf_; - std::atomic readConsumerBuf_; - - // Outgoing messages: main thread pushes in Send(), doWrite()/completion drains - using WriteBuf = std::vector>; - WriteBuf writeBuffer0_; - WriteBuf writeBuffer1_; - std::atomic writeProducerBuf_; - std::atomic writeConsumerBuf_; - WriteBuf* currentWriteBuf_ = nullptr; - size_t currentWriteIndex_ = 0; - std::atomic isWriting_{ false }; - - bool connected = false; - - - void startAsyncRead(); - void processIncomingMessage(const std::string& msg); - - public: - explicit WebSocketClient(boost::asio::io_context& ioc) - : ioc_(ioc) - , readProducerBuf_(&readBuffer0_) - , readConsumerBuf_(&readBuffer1_) - , writeProducerBuf_(&writeBuffer0_) - , writeConsumerBuf_(&writeBuffer1_) - {} - - void Connect(const std::string& host, uint16_t port) override; - void Disconnect() override; - - void Poll() override; - - void Send(const std::string& message) override; - void doWrite(); - - bool IsConnected() const override { return connected; } - }; -} -#endif diff --git a/src/network/WebSocketClientBase.cpp b/src/network/WebSocketClientBase.cpp deleted file mode 100644 index 7c37d3f..0000000 --- a/src/network/WebSocketClientBase.cpp +++ /dev/null @@ -1,436 +0,0 @@ -#ifdef NETWORK - -#include "WebSocketClientBase.h" -#include -#include - -// Вспомогательный split -std::vector split(const std::string& s, char delimiter) { - std::vector tokens; - std::string token; - std::istringstream tokenStream(s); - while (std::getline(tokenStream, token, delimiter)) { - tokens.push_back(token); - } - return tokens; -} - -namespace ZL { - - void WebSocketClientBase::HandlePollMessage(const std::string& msg) { - auto parts = split(msg, ':'); - if (parts.empty()) return; - - if (parts[0] == "ID") { - std::cout << "ID Message Received:" << msg << std::endl; - clientId = std::stoi(parts[1]); - if (parts.size() >= 3) { - std::cout << "ID Message Received step 2" << std::endl; - uint64_t serverTime = std::stoull(parts[2]); - uint64_t localTime = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()).count(); - - std::cout << "ID Message Received localTime = " << localTime << std::endl; - std::cout << "ID Message Received serverTime = " << serverTime << std::endl; - - // Вычисляем смещение - timeOffset = static_cast(serverTime) - static_cast(localTime); - - std::cout << "Time synchronized. Offset: " << timeOffset << " ms" << std::endl; - } - return; - } - - if (msg.rfind("BOXES:", 0) == 0) { - std::string payload = msg.substr(6); - std::vector> parsed; - if (!payload.empty()) { - auto items = split(payload, '|'); - for (auto& item : items) { - if (item.empty()) continue; - auto parts = split(item, ':'); - if (parts.size() < 9) { - return; - } - try { - int idx = std::stoi(parts[0]); - float px = std::stof(parts[1]); - float py = std::stof(parts[2]); - float pz = std::stof(parts[3]); - Eigen::Quaternionf q( - std::stof(parts[4]), - std::stof(parts[5]), - std::stof(parts[6]), - std::stof(parts[7]) - ); - bool destroyed = (std::stoi(parts[8]) != 0); - - Eigen::Matrix3f rot = q.toRotationMatrix(); - parsed.emplace_back(idx, Eigen::Vector3f{ px, py, pz }, rot, destroyed); - } - catch (...) { - return; - } - } - } - - int maxIdx = -1; - for (auto& t : parsed) { - int idx = std::get<0>(t); - if (idx > maxIdx) maxIdx = idx; - } - if (maxIdx < 0) { - serverBoxes_.clear(); - serverBoxesDestroyed_.clear(); - return; - } - - serverBoxes_.clear(); - serverBoxes_.resize((size_t)maxIdx + 1); - serverBoxesDestroyed_.clear(); - serverBoxesDestroyed_.resize((size_t)maxIdx + 1, true); - - for (auto& t : parsed) { - int idx = std::get<0>(t); - const Eigen::Vector3f& pos = std::get<1>(t); - const Eigen::Matrix3f& rot = std::get<2>(t); - bool destroyed = std::get<3>(t); - if (idx >= 0 && idx < serverBoxes_.size()) { - serverBoxes_[idx] = { pos, rot }; - serverBoxesDestroyed_[idx] = destroyed; - } - } - - return; - } - if (msg.rfind("PLAYER_LEFT:", 0) == 0) { - if (parts.size() >= 2) { - try { - int pid = std::stoi(parts[1]); - remotePlayers.erase(pid); - pendingDisconnects_.push_back(pid); - std::cout << "Client: Player " << pid << " disconnected (PLAYER_LEFT)\n"; - } - catch (...) {} - } - return; - } - - if (msg.rfind("RESPAWN_ACK:", 0) == 0) { - //auto parts = split(msg, ':'); - if (parts.size() >= 2) { - try { - int respawnedPlayerId = std::stoi(parts[1]); - pendingRespawns_.push_back(respawnedPlayerId); - std::cout << "Client: Received RESPAWN_ACK for player " << respawnedPlayerId << std::endl; - } - catch (...) {} - } - return; - } - - if (msg.rfind("BOX_PICKED_UP:", 0) == 0) { - if (parts.size() >= 3) { - try { - BoxPickedUpInfo pickup; - pickup.boxIndex = std::stoi(parts[1]); - pickup.pickedUpBy = std::stoi(parts[2]); - pendingBoxPickups_.push_back(pickup); - std::cout << "Client: Received BOX_PICKED_UP box=" << pickup.boxIndex - << " by player " << pickup.pickedUpBy << std::endl; - } - catch (...) {} - } - return; - } - - if (msg.rfind("BOX_RESPAWN:", 0) == 0) { - if (parts.size() >= 9) { - try { - BoxRespawnInfo respawn; - respawn.boxIndex = std::stoi(parts[1]); - float px = std::stof(parts[2]); - float py = std::stof(parts[3]); - float pz = std::stof(parts[4]); - Eigen::Quaternionf q( - std::stof(parts[5]), - std::stof(parts[6]), - std::stof(parts[7]), - std::stof(parts[8]) - ); - respawn.position = Eigen::Vector3f(px, py, pz); - respawn.rotation = q.toRotationMatrix(); - pendingBoxRespawns_.push_back(respawn); - std::cout << "Client: Received BOX_RESPAWN box=" << respawn.boxIndex << std::endl; - } - catch (...) {} - } - return; - } - - if (msg.rfind("BOX_DESTROYED:", 0) == 0) { - //auto parts = split(msg, ':'); - if (parts.size() >= 7) { - try { - BoxDestroyedInfo destruction; - destruction.boxIndex = std::stoi(parts[1]); - destruction.serverTime = std::stoull(parts[2]); - destruction.position = Eigen::Vector3f( - std::stof(parts[3]), - std::stof(parts[4]), - std::stof(parts[5]) - ); - destruction.destroyedBy = std::stoi(parts[6]); - - pendingBoxDestructions_.push_back(destruction); - - std::cout << "Client: Received BOX_DESTROYED for box " << destruction.boxIndex - << " destroyed by player " << destruction.destroyedBy << std::endl; - } - catch (const std::exception& e) { - std::cerr << "Client: Error parsing BOX_DESTROYED: " << e.what() << std::endl; - } - } - return; - } - - if (msg.rfind("PROJECTILE:", 0) == 0) { - //auto parts = split(msg, ':'); - if (parts.size() >= 11) { - 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(); - - pi.velocity = std::stof(parts[10]); - pendingProjectiles_.push_back(pi); - } - catch (...) {} - } - return; - } - - if (msg.rfind("DEAD:", 0) == 0) { - //auto parts = split(msg, ':'); - if (parts.size() >= 7) { - try { - DeathInfo di; - di.serverTime = std::stoull(parts[1]); - di.targetId = std::stoi(parts[2]); - di.position = Eigen::Vector3f( - std::stof(parts[3]), - std::stof(parts[4]), - std::stof(parts[5]) - ); - di.killerId = std::stoi(parts[6]); - - pendingDeaths_.push_back(di); - } - catch (...) {} - } - return; - } - if (msg.rfind("SPAWN:", 0) == 0) { - // SPAWN:playerId:serverTime:<14 полей> - if (parts.size() >= 3 + 14) { - try { - int pid = std::stoi(parts[1]); - uint64_t serverTime = std::stoull(parts[2]); - - ClientState st; - st.id = pid; - std::chrono::system_clock::time_point tp{ std::chrono::milliseconds(serverTime) }; - st.lastUpdateServerTime = tp; - - // данные начинаются с parts[3] - st.handle_full_sync(parts, 3); - - pendingSpawns_.push_back(st); - std::cout << "Client: SPAWN received for player " << pid << std::endl; - } - catch (...) {} - } - return; - } - if (msg.rfind("EVENT:", 0) == 0) { - //auto parts = split(msg, ':'); - if (parts.size() < 5) return; // 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::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(); - - 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); - } - - { - auto& rp = remotePlayers[remoteId]; - if (!rp.timedStates.empty()) { - const ClientState& last = rp.timedStates.back(); - remoteState.nickname = last.nickname; - remoteState.shipType = last.shipType; - } - rp.add_state(remoteState); - } - } - - if (msg.rfind("SNAPSHOT:", 0) == 0) { - auto mainParts = split(msg.substr(9), '|'); // Отсекаем "SNAPSHOT:" и делим по игрокам - if (mainParts.empty()) return; - - 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) return; // ID + 14 полей ClientState - - int rId = std::stoi(playerParts[0]); - if (rId == clientId) return; // Свое состояние игрок знает лучше всех, (Client Side Prediction) - - ClientState remoteState; - remoteState.id = rId; - remoteState.lastUpdateServerTime = serverTime; - - // Используем твой handle_full_sync, начиная со 2-го индекса (пропускаем ID в playerParts) - remoteState.handle_full_sync(playerParts, 1); - - remotePlayers[rId].add_state(remoteState); - } - } - - if (msg.rfind("PLAYERINFO:", 0) == 0) { - if (parts.size() >= 4) { - try { - int pid = std::stoi(parts[1]); - if (pid == clientId) { - return; - } - - std::string nick = parts[2]; - int st = std::stoi(parts[3]); - - auto it = remotePlayers.find(pid); - if (it != remotePlayers.end() && !it->second.timedStates.empty()) { - auto& states = it->second.timedStates; - states.back().nickname = nick; - states.back().shipType = st; - } - else { - ClientState cs; - cs.id = pid; - cs.nickname = nick; - cs.shipType = st; - cs.lastUpdateServerTime = std::chrono::system_clock::now(); - remotePlayers[pid].add_state(cs); - } - - std::cout << "Client: PLAYERINFO received. id=" << pid << " nick=" << nick << " shipType=" << st << std::endl; - } - catch (...) { - } - } - return; - } - } - - std::string WebSocketClientBase::SignMessage(const std::string& msg) { -#ifdef ENABLE_NETWORK_CHECKSUM - size_t hashValue = fnv1a_hash(msg + NET_SECRET); - std::stringstream ss; - ss << msg << "#hash:" << std::hex << hashValue; - return ss.str(); -#else - return msg; -#endif - } - - std::vector WebSocketClientBase::getPendingProjectiles() { - std::vector copy; - copy.swap(pendingProjectiles_); - return copy; - } - - std::vector WebSocketClientBase::getPendingDeaths() { - std::vector copy; - copy.swap(pendingDeaths_); - return copy; - } - - std::vector WebSocketClientBase::getPendingRespawns() { - std::vector copy; - copy.swap(pendingRespawns_); - return copy; - } - - std::vector WebSocketClientBase::getPendingBoxDestructions() { - std::vector copy; - copy.swap(pendingBoxDestructions_); - return copy; - } - - std::vector WebSocketClientBase::getPendingBoxPickups() { - std::vector copy; - copy.swap(pendingBoxPickups_); - return copy; - } - - std::vector WebSocketClientBase::getPendingBoxRespawns() { - std::vector copy; - copy.swap(pendingBoxRespawns_); - return copy; - } - - std::vector WebSocketClientBase::getPendingDisconnects() { - std::vector copy; - copy.swap(pendingDisconnects_); - return copy; - } - - std::vector WebSocketClientBase::getPendingSpawns() { - std::vector copy; - copy.swap(pendingSpawns_); - return copy; - } -} - -#endif \ No newline at end of file diff --git a/src/network/WebSocketClientBase.h b/src/network/WebSocketClientBase.h deleted file mode 100644 index a324a70..0000000 --- a/src/network/WebSocketClientBase.h +++ /dev/null @@ -1,63 +0,0 @@ -#pragma once - -#include "NetworkInterface.h" -#include -#include - -namespace ZL { - - // All state in WebSocketClientBase is only accessed from the main thread: - // HandlePollMessage() runs from Poll(), and get*() are called from Game/Space on the main thread. - // No mutexes needed. - class WebSocketClientBase : public INetworkClient { - protected: - std::unordered_map remotePlayers; - - std::vector> serverBoxes_; - std::vector serverBoxesDestroyed_; - - std::vector pendingProjectiles_; - std::vector pendingDeaths_; - std::vector pendingRespawns_; - std::vector pendingBoxDestructions_; - std::vector pendingBoxPickups_; - std::vector pendingBoxRespawns_; - std::vector pendingDisconnects_; - int clientId = -1; - int64_t timeOffset = 0; - std::vector pendingSpawns_; - - public: - int GetClientId() const override { return clientId; } - - int64_t getTimeOffset() const override { return timeOffset; } - - void HandlePollMessage(const std::string& msg); - - std::string SignMessage(const std::string& msg); - - std::unordered_map getRemotePlayers() override { - return remotePlayers; - } - - std::vector> getServerBoxes() override { - return serverBoxes_; - } - - std::vector getServerBoxDestroyedFlags() override { - return serverBoxesDestroyed_; - } - - std::vector getPendingProjectiles() override; - std::vector getPendingDeaths() override; - std::vector getPendingRespawns() override; - std::vector getPendingBoxDestructions() override; - std::vector getPendingBoxPickups() override; - std::vector getPendingBoxRespawns() override; - std::vector getPendingDisconnects() override; - std::vector getPendingSpawns(); - int getClientId() const { return clientId; } - }; - -} - diff --git a/src/network/WebSocketClientEmscripten.cpp b/src/network/WebSocketClientEmscripten.cpp deleted file mode 100644 index 4f4fcbb..0000000 --- a/src/network/WebSocketClientEmscripten.cpp +++ /dev/null @@ -1,121 +0,0 @@ -#ifdef NETWORK -#ifdef EMSCRIPTEN -#include "WebSocketClientEmscripten.h" -#include -#include - -namespace ZL { - void WebSocketClientEmscripten::Connect(const std::string& host, uint16_t port) { - // Формируем URL. Обратите внимание, что в Web часто лучше использовать ws://localhost - //std::string url = "ws://" + host + ":" + std::to_string(port); - std::string url = "wss://api.spacegame.fishrungames.com"; - //std::string url = "ws://localhost:8081"; - EmscriptenWebSocketCreateAttributes attr = { - url.c_str(), - nullptr, - EM_TRUE // create_on_main_thread - }; - - socket_ = emscripten_websocket_new(&attr); - - emscripten_websocket_set_onopen_callback(socket_, this, onOpen); - emscripten_websocket_set_onmessage_callback(socket_, this, onMessage); - emscripten_websocket_set_onerror_callback(socket_, this, onError); - emscripten_websocket_set_onclose_callback(socket_, this, onClose); - - connected = false; - } - - void WebSocketClientEmscripten::Disconnect() { - if (socket_ > 0) { - emscripten_websocket_close(socket_, 1000, "User disconnected"); - emscripten_websocket_delete(socket_); - socket_ = 0; - } - connected = false; - } - - void WebSocketClientEmscripten::flushOutgoingQueue() { - std::lock_guard lock(outgoingMutex); - if (!socket_) return; - while (!outgoingQueue.empty()) { - const std::string &m = outgoingQueue.front(); - emscripten_websocket_send_utf8_text(socket_, m.c_str()); - outgoingQueue.pop(); - } - } - - void WebSocketClientEmscripten::Send(const std::string& message) { - std::string signedMsg = SignMessage(message); - - { - std::lock_guard lock(outgoingMutex); - if (connected && socket_ > 0) { - //std::cout << "[WebWS] Sending message (immediate): " << signedMsg << std::endl; - emscripten_websocket_send_utf8_text(socket_, signedMsg.c_str()); - return; - } - outgoingQueue.push(signedMsg); - //std::cout << "[WebWS] Queued outgoing message (waiting for open): " << signedMsg << std::endl; - } - } - - void WebSocketClientEmscripten::Poll() { - // Локальная очередь для минимизации времени блокировки мьютекса - std::queue localQueue; - - if (messageQueue.empty()) return; - std::swap(localQueue, messageQueue); - - while (!localQueue.empty()) { - const std::string& msg = localQueue.front(); - //std::cout << "[WebWS] Processing message: " << msg << std::endl; - - // Передаем в базовый класс для парсинга игровых событий (BOXES, UPD, и т.д.) - HandlePollMessage(msg); - - localQueue.pop(); - } - } - - // --- Колбэки --- - - EM_BOOL WebSocketClientEmscripten::onOpen(int eventType, const EmscriptenWebSocketOpenEvent* e, void* userData) { - auto* self = static_cast(userData); - self->connected = true; - //std::cout << "[WebWS] Connection opened" << std::endl; - - self->flushOutgoingQueue(); - - return EM_TRUE; - } - - EM_BOOL WebSocketClientEmscripten::onMessage(int eventType, const EmscriptenWebSocketMessageEvent* e, void* userData) { - //std::cout << "[WebWS] onMessage " << std::endl; - auto* self = static_cast(userData); - if (e->isText && e->data) { - std::string msg(reinterpret_cast(e->data), e->numBytes); - self->messageQueue.push(msg); - } - return EM_TRUE; - } - - EM_BOOL WebSocketClientEmscripten::onError(int eventType, const EmscriptenWebSocketErrorEvent* e, void* userData) { - auto* self = static_cast(userData); - self->connected = false; - //std::cerr << "[WebWS] Error detected" << std::endl; - return EM_TRUE; - } - - EM_BOOL WebSocketClientEmscripten::onClose(int eventType, const EmscriptenWebSocketCloseEvent* e, void* userData) { - auto* self = static_cast(userData); - self->connected = false; - //std::cout << "[WebWS] Connection closed" << std::endl; - return EM_TRUE; - } - -} - -#endif -#endif - diff --git a/src/network/WebSocketClientEmscripten.h b/src/network/WebSocketClientEmscripten.h deleted file mode 100644 index 5e347e6..0000000 --- a/src/network/WebSocketClientEmscripten.h +++ /dev/null @@ -1,46 +0,0 @@ -#pragma once - -#ifdef NETWORK -#ifdef EMSCRIPTEN - -#include -#include "WebSocketClientBase.h" -#include -#include -#include - -namespace ZL { - - class WebSocketClientEmscripten : public WebSocketClientBase { - private: - EMSCRIPTEN_WEBSOCKET_T socket_ = 0; - bool connected = false; - - // Очередь для хранения сырых строк от браузера - std::queue messageQueue; - - std::queue outgoingQueue; - std::mutex outgoingMutex; - void flushOutgoingQueue(); - - public: - WebSocketClientEmscripten() = default; - virtual ~WebSocketClientEmscripten() = default; - - void Connect(const std::string& host, uint16_t port) override; - void Disconnect() override; - void Send(const std::string& message) override; - void Poll() override; - - bool IsConnected() const override { return connected; } - - // Статические методы-переходники для C-API Emscripten - static EM_BOOL onOpen(int eventType, const EmscriptenWebSocketOpenEvent* e, void* userData); - static EM_BOOL onMessage(int eventType, const EmscriptenWebSocketMessageEvent* e, void* userData); - static EM_BOOL onError(int eventType, const EmscriptenWebSocketErrorEvent* e, void* userData); - static EM_BOOL onClose(int eventType, const EmscriptenWebSocketCloseEvent* e, void* userData); - }; -} -#endif -#endif -