Added force disconnect from server

This commit is contained in:
Vladislav Khorev 2026-03-06 20:05:55 +03:00
parent 8528b4dc90
commit ac551122d9
9 changed files with 93 additions and 3 deletions

View File

@ -1,9 +1,14 @@
# how to build # how to build
If emsdk is not installed, you need to clone it from here: https://github.com/emscripten-core/emsdk
Activate the environment: and install:
``` ```
C:\Work\Projects\emsdk\emsdk.bat install latest C:\Work\Projects\emsdk\emsdk.bat install latest
```
Then activate the environment:
```
C:\Work\Projects\emsdk\emsdk.bat activate latest C:\Work\Projects\emsdk\emsdk.bat activate latest
C:\Work\Projects\emsdk\emsdk_env.bat C:\Work\Projects\emsdk\emsdk_env.bat
``` ```

View File

@ -20,7 +20,19 @@ std::vector<std::string> split(const std::string& s, char delimiter) {
Session::Session(Server& server, tcp::socket&& socket, int id) Session::Session(Server& server, tcp::socket&& socket, int id)
: server_(server) : server_(server)
, ws_(std::move(socket)) , ws_(std::move(socket))
, id_(id) { , id_(id)
, lastReceivedTime_(std::chrono::system_clock::now()) {
}
bool Session::is_timed_out(std::chrono::system_clock::time_point now) const {
if (!joined_) return false;
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - lastReceivedTime_).count();
return elapsed > PLAYER_TIMEOUT_MS;
}
void Session::force_disconnect() {
ws_.async_close(websocket::close_code::normal,
[self = shared_from_this()](beast::error_code) {});
} }
int Session::get_id() const { return id_; } int Session::get_id() const { return id_; }
@ -166,12 +178,16 @@ void Session::doWrite() {
void Session::do_read() { void Session::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) {
if (self->joined_) {
self->server_.broadcastToAllExceptId("PLAYER_LEFT:" + std::to_string(self->id_), self->id_);
std::cout << "Client " << self->id_ << " disconnected, broadcasting PLAYER_LEFT\n";
}
std::lock_guard<std::mutex> lock(self->server_.g_sessions_mutex); std::lock_guard<std::mutex> lock(self->server_.g_sessions_mutex);
self->server_.g_sessions.erase(std::remove_if(self->server_.g_sessions.begin(), self->server_.g_sessions.end(), self->server_.g_sessions.erase(std::remove_if(self->server_.g_sessions.begin(), self->server_.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();
}), self->server_.g_sessions.end()); }), self->server_.g_sessions.end());
std::cout << "Client " << self->id_ << " disconnected\n"; std::cout << "Client " << self->id_ << " removed from session list\n";
return; return;
} }
@ -188,6 +204,7 @@ void Session::process_message(const std::string& msg) {
std::cout << "[Security] Invalid packet hash. Dropping message: " << msg << std::endl; std::cout << "[Security] Invalid packet hash. Dropping message: " << msg << std::endl;
return; return;
} }
lastReceivedTime_ = std::chrono::system_clock::now();
std::string cleanMessage = msg.substr(0, msg.find("#hash:")); std::string cleanMessage = msg.substr(0, msg.find("#hash:"));
std::cout << "Received from player " << id_ << ": " << cleanMessage << std::endl; std::cout << "Received from player " << id_ << ": " << cleanMessage << std::endl;
@ -480,6 +497,24 @@ void Server::update_world() {
uint64_t now_ms = static_cast<uint64_t>( uint64_t now_ms = static_cast<uint64_t>(
std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count()); std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count());
// --- Detect and force-disconnect timed-out players ---
{
std::vector<std::shared_ptr<Session>> timedOut;
{
std::lock_guard<std::mutex> lock(g_sessions_mutex);
for (auto& session : g_sessions) {
if (session->is_timed_out(now)) {
timedOut.push_back(session);
}
}
}
for (auto& session : timedOut) {
std::cout << "Server: Player " << session->get_id()
<< " timed out after " << PLAYER_TIMEOUT_MS << "ms, forcing disconnect\n";
session->force_disconnect();
}
}
{ {
std::lock_guard<std::mutex> lock(g_sessions_mutex); std::lock_guard<std::mutex> lock(g_sessions_mutex);
for (auto& sender : g_sessions) { for (auto& sender : g_sessions) {

View File

@ -72,6 +72,7 @@ class Session : public std::enable_shared_from_this<Session> {
public: public:
ClientStateInterval timedClientStates; ClientStateInterval timedClientStates;
bool joined_ = false; bool joined_ = false;
std::chrono::system_clock::time_point lastReceivedTime_;
bool hasReservedSpawn_ = false; bool hasReservedSpawn_ = false;
Eigen::Vector3f reservedSpawn_ = Eigen::Vector3f(0.0f, 0.0f, kWorldZOffset); Eigen::Vector3f reservedSpawn_ = Eigen::Vector3f(0.0f, 0.0f, kWorldZOffset);
@ -87,6 +88,8 @@ public:
void send_message(const std::string& msg); void send_message(const std::string& msg);
void run(); void run();
bool IsMessageValid(const std::string& fullMessage); bool IsMessageValid(const std::string& fullMessage);
bool is_timed_out(std::chrono::system_clock::time_point now) const;
void force_disconnect();
private: private:
void sendBoxesToClient(); void sendBoxesToClient();

View File

@ -1822,6 +1822,18 @@ namespace ZL
void Space::update() { void Space::update() {
if (networkClient) { if (networkClient) {
if (networkClient->IsConnected()) {
wasConnectedToServer = true;
}
else if (wasConnectedToServer && shipAlive && !gameOver) {
wasConnectedToServer = false;
shipAlive = false;
gameOver = true;
Environment::shipState.velocity = 0.0f;
std::cout << "Client: Lost connection to server\n";
menuManager.showGameOver(this->playerScore);
}
auto pending = networkClient->getPendingProjectiles(); auto pending = networkClient->getPendingProjectiles();
if (!pending.empty()) { if (!pending.empty()) {
const float projectileSpeed = PROJECTILE_VELOCITY; const float projectileSpeed = PROJECTILE_VELOCITY;
@ -1911,6 +1923,17 @@ namespace ZL
} }
} }
auto disconnects = networkClient->getPendingDisconnects();
for (int pid : disconnects) {
remotePlayerStates.erase(pid);
deadRemotePlayers.erase(pid);
if (trackedTargetId == pid) {
trackedTargetId = -1;
targetAcquireAnim = 0.f;
}
std::cout << "Client: Remote player " << pid << " left the game, removed from scene\n";
}
auto boxDestructions = networkClient->getPendingBoxDestructions(); auto boxDestructions = networkClient->getPendingBoxDestructions();
if (!boxDestructions.empty()) { if (!boxDestructions.empty()) {
std::cout << "Game: Received " << boxDestructions.size() << " box destruction events" << std::endl; std::cout << "Game: Received " << boxDestructions.size() << " box destruction events" << std::endl;

View File

@ -124,6 +124,7 @@ namespace ZL {
std::unordered_set<int> deadRemotePlayers; std::unordered_set<int> deadRemotePlayers;
int playerScore = 0; int playerScore = 0;
bool wasConnectedToServer = false;
static constexpr float TARGET_MAX_DIST = 50000.0f; static constexpr float TARGET_MAX_DIST = 50000.0f;
static constexpr float TARGET_MAX_DIST_SQ = TARGET_MAX_DIST * TARGET_MAX_DIST; static constexpr float TARGET_MAX_DIST_SQ = TARGET_MAX_DIST * TARGET_MAX_DIST;

View File

@ -26,6 +26,7 @@ constexpr float PITCH_LIMIT = static_cast<float>(M_PI) / 9.f;//18.0f;
constexpr long long SERVER_DELAY = 0; //ms constexpr long long SERVER_DELAY = 0; //ms
constexpr long long CLIENT_DELAY = 500; //ms constexpr long long CLIENT_DELAY = 500; //ms
constexpr long long CUTOFF_TIME = 5000; //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_VELOCITY = 600.f;
constexpr float PROJECTILE_LIFE = 15000.f; //ms constexpr float PROJECTILE_LIFE = 15000.f; //ms

View File

@ -50,6 +50,7 @@ namespace ZL {
virtual int GetClientId() const { return -1; } virtual int GetClientId() const { return -1; }
virtual std::vector<BoxDestroyedInfo> getPendingBoxDestructions() = 0; virtual std::vector<BoxDestroyedInfo> getPendingBoxDestructions() = 0;
virtual int64_t getTimeOffset() const { return 0; } virtual int64_t getTimeOffset() const { return 0; }
virtual std::vector<int> getPendingDisconnects() { return {}; }
}; };
} }

View File

@ -103,6 +103,19 @@ namespace ZL {
return; 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) { if (msg.rfind("RESPAWN_ACK:", 0) == 0) {
//auto parts = split(msg, ':'); //auto parts = split(msg, ':');
if (parts.size() >= 2) { if (parts.size() >= 2) {
@ -356,6 +369,12 @@ namespace ZL {
return copy; return copy;
} }
std::vector<int> WebSocketClientBase::getPendingDisconnects() {
std::vector<int> copy;
copy.swap(pendingDisconnects_);
return copy;
}
std::vector<ClientState> WebSocketClientBase::getPendingSpawns() { std::vector<ClientState> WebSocketClientBase::getPendingSpawns() {
std::vector<ClientState> copy; std::vector<ClientState> copy;
copy.swap(pendingSpawns_); copy.swap(pendingSpawns_);

View File

@ -20,6 +20,7 @@ namespace ZL {
std::vector<DeathInfo> pendingDeaths_; std::vector<DeathInfo> pendingDeaths_;
std::vector<int> pendingRespawns_; std::vector<int> pendingRespawns_;
std::vector<BoxDestroyedInfo> pendingBoxDestructions_; std::vector<BoxDestroyedInfo> pendingBoxDestructions_;
std::vector<int> pendingDisconnects_;
int clientId = -1; int clientId = -1;
int64_t timeOffset = 0; int64_t timeOffset = 0;
std::vector<ClientState> pendingSpawns_; std::vector<ClientState> pendingSpawns_;
@ -49,6 +50,7 @@ namespace ZL {
std::vector<DeathInfo> getPendingDeaths() override; std::vector<DeathInfo> getPendingDeaths() override;
std::vector<int> getPendingRespawns() override; std::vector<int> getPendingRespawns() override;
std::vector<BoxDestroyedInfo> getPendingBoxDestructions() override; std::vector<BoxDestroyedInfo> getPendingBoxDestructions() override;
std::vector<int> getPendingDisconnects() override;
std::vector<ClientState> getPendingSpawns(); std::vector<ClientState> getPendingSpawns();
int getClientId() const { return clientId; } int getClientId() const { return clientId; }
}; };