Added force disconnect from server
This commit is contained in:
parent
8528b4dc90
commit
ac551122d9
@ -1,9 +1,14 @@
|
||||
# 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
|
||||
```
|
||||
|
||||
Then activate the environment:
|
||||
```
|
||||
C:\Work\Projects\emsdk\emsdk.bat activate latest
|
||||
C:\Work\Projects\emsdk\emsdk_env.bat
|
||||
```
|
||||
|
||||
@ -20,7 +20,19 @@ std::vector<std::string> split(const std::string& s, char delimiter) {
|
||||
Session::Session(Server& server, tcp::socket&& socket, int id)
|
||||
: server_(server)
|
||||
, 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_; }
|
||||
@ -166,12 +178,16 @@ void Session::doWrite() {
|
||||
void Session::do_read() {
|
||||
ws_.async_read(buffer_, [self = shared_from_this()](beast::error_code ec, std::size_t) {
|
||||
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);
|
||||
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) {
|
||||
return session.get() == self.get();
|
||||
}), self->server_.g_sessions.end());
|
||||
std::cout << "Client " << self->id_ << " disconnected\n";
|
||||
std::cout << "Client " << self->id_ << " removed from session list\n";
|
||||
return;
|
||||
}
|
||||
|
||||
@ -188,6 +204,7 @@ void Session::process_message(const std::string& msg) {
|
||||
std::cout << "[Security] Invalid packet hash. Dropping message: " << msg << std::endl;
|
||||
return;
|
||||
}
|
||||
lastReceivedTime_ = std::chrono::system_clock::now();
|
||||
std::string cleanMessage = msg.substr(0, msg.find("#hash:"));
|
||||
|
||||
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>(
|
||||
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);
|
||||
for (auto& sender : g_sessions) {
|
||||
|
||||
@ -72,6 +72,7 @@ class Session : public std::enable_shared_from_this<Session> {
|
||||
public:
|
||||
ClientStateInterval timedClientStates;
|
||||
bool joined_ = false;
|
||||
std::chrono::system_clock::time_point lastReceivedTime_;
|
||||
|
||||
bool hasReservedSpawn_ = false;
|
||||
Eigen::Vector3f reservedSpawn_ = Eigen::Vector3f(0.0f, 0.0f, kWorldZOffset);
|
||||
@ -87,6 +88,8 @@ public:
|
||||
void send_message(const std::string& msg);
|
||||
void run();
|
||||
bool IsMessageValid(const std::string& fullMessage);
|
||||
bool is_timed_out(std::chrono::system_clock::time_point now) const;
|
||||
void force_disconnect();
|
||||
|
||||
private:
|
||||
void sendBoxesToClient();
|
||||
|
||||
@ -1822,6 +1822,18 @@ namespace ZL
|
||||
|
||||
void Space::update() {
|
||||
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();
|
||||
if (!pending.empty()) {
|
||||
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();
|
||||
if (!boxDestructions.empty()) {
|
||||
std::cout << "Game: Received " << boxDestructions.size() << " box destruction events" << std::endl;
|
||||
|
||||
@ -124,6 +124,7 @@ namespace ZL {
|
||||
|
||||
std::unordered_set<int> deadRemotePlayers;
|
||||
int playerScore = 0;
|
||||
bool wasConnectedToServer = false;
|
||||
|
||||
static constexpr float TARGET_MAX_DIST = 50000.0f;
|
||||
static constexpr float TARGET_MAX_DIST_SQ = TARGET_MAX_DIST * TARGET_MAX_DIST;
|
||||
|
||||
@ -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 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
|
||||
|
||||
@ -50,6 +50,7 @@ namespace ZL {
|
||||
virtual int GetClientId() const { return -1; }
|
||||
virtual std::vector<BoxDestroyedInfo> getPendingBoxDestructions() = 0;
|
||||
virtual int64_t getTimeOffset() const { return 0; }
|
||||
virtual std::vector<int> getPendingDisconnects() { return {}; }
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@ -103,6 +103,19 @@ namespace ZL {
|
||||
|
||||
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) {
|
||||
@ -356,6 +369,12 @@ namespace ZL {
|
||||
return copy;
|
||||
}
|
||||
|
||||
std::vector<int> WebSocketClientBase::getPendingDisconnects() {
|
||||
std::vector<int> copy;
|
||||
copy.swap(pendingDisconnects_);
|
||||
return copy;
|
||||
}
|
||||
|
||||
std::vector<ClientState> WebSocketClientBase::getPendingSpawns() {
|
||||
std::vector<ClientState> copy;
|
||||
copy.swap(pendingSpawns_);
|
||||
|
||||
@ -20,6 +20,7 @@ namespace ZL {
|
||||
std::vector<DeathInfo> pendingDeaths_;
|
||||
std::vector<int> pendingRespawns_;
|
||||
std::vector<BoxDestroyedInfo> pendingBoxDestructions_;
|
||||
std::vector<int> pendingDisconnects_;
|
||||
int clientId = -1;
|
||||
int64_t timeOffset = 0;
|
||||
std::vector<ClientState> pendingSpawns_;
|
||||
@ -49,6 +50,7 @@ namespace ZL {
|
||||
std::vector<DeathInfo> getPendingDeaths() override;
|
||||
std::vector<int> getPendingRespawns() override;
|
||||
std::vector<BoxDestroyedInfo> getPendingBoxDestructions() override;
|
||||
std::vector<int> getPendingDisconnects() override;
|
||||
std::vector<ClientState> getPendingSpawns();
|
||||
int getClientId() const { return clientId; }
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user