add timeout for persistent games

This commit is contained in:
Martin Michelsen
2023-12-13 20:52:35 -08:00
parent 3551b9abc3
commit 974269187b
7 changed files with 96 additions and 23 deletions
+1 -1
View File
@@ -486,7 +486,7 @@ static void server_command_persist(shared_ptr<Client> c, const std::string&) {
send_text_message(c, "$C6Games cannot be\npersistent if a\nquest has already\nbegun");
} else {
l->toggle_flag(Lobby::Flag::PERSISTENT);
send_text_message_printf(c, "Lobby persistence\n%s", l->check_flag(Lobby::Flag::PERSISTENT) ? "enabled" : "disabled");
send_text_message_printf(l, "Lobby persistence\n%s", l->check_flag(Lobby::Flag::PERSISTENT) ? "enabled" : "disabled");
}
}
+36 -1
View File
@@ -31,12 +31,21 @@ Lobby::Lobby(shared_ptr<ServerState> s, uint32_t id)
block(0),
leader_id(0),
max_clients(12),
enabled_flags(0) {
enabled_flags(0),
idle_timeout_usecs(0),
idle_timeout_event(
event_new(s->base.get(), -1, EV_TIMEOUT | EV_PERSIST, &Lobby::dispatch_on_idle_timeout, this),
event_free) {
this->log.info("Created");
for (size_t x = 0; x < 12; x++) {
this->next_item_id[x] = 0x00010000 + 0x00200000 * x;
}
}
Lobby::~Lobby() {
this->log.info("Deleted");
}
shared_ptr<ServerState> Lobby::require_server_state() const {
auto s = this->server_state.lock();
if (!s) {
@@ -369,6 +378,12 @@ void Lobby::add_client(shared_ptr<Client> c, ssize_t required_client_id) {
send_ep3_update_game_metadata(this->shared_from_this());
}
}
// There is a player in the lobby, so it is no longer idle
if (event_pending(this->idle_timeout_event.get(), EV_TIMEOUT, nullptr)) {
event_del(this->idle_timeout_event.get());
this->log.info("Idle timeout cancelled");
}
}
void Lobby::remove_client(shared_ptr<Client> c) {
@@ -409,6 +424,14 @@ void Lobby::remove_client(shared_ptr<Client> c) {
send_ep3_update_game_metadata(this->shared_from_this());
}
}
// If the lobby is persistent but has an idle timeout, make it expire after
// the specified time
if ((this->count_clients() == 0) && this->check_flag(Flag::PERSISTENT) && (this->idle_timeout_usecs > 0)) {
auto tv = usecs_to_timeval(this->idle_timeout_usecs);
event_add(this->idle_timeout_event.get(), &tv);
this->log.info("Idle timeout scheduled");
}
}
void Lobby::move_client_to_lobby(
@@ -551,3 +574,15 @@ QuestIndex::IncludeCondition Lobby::quest_include_condition() const {
return is_enabled ? QuestIndex::IncludeState::AVAILABLE : QuestIndex::IncludeState::DISABLED;
};
}
void Lobby::dispatch_on_idle_timeout(evutil_socket_t, short, void* ctx) {
auto l = reinterpret_cast<Lobby*>(ctx)->shared_from_this();
if (l->count_clients() == 0) {
l->log.info("Idle timeout expired");
auto s = l->require_server_state();
s->remove_lobby(l);
} else {
l->log.error("Idle timeout occurred, but clients are present in lobby");
event_del(l->idle_timeout_event.get());
}
}
+9
View File
@@ -1,5 +1,6 @@
#pragma once
#include <event2/event.h>
#include <inttypes.h>
#include <array>
@@ -130,9 +131,15 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
// Keys in this map are client_id
std::unordered_map<size_t, std::weak_ptr<Client>> clients_to_add;
// This is only used when the PERSISTENT flag is set and idle_timeout_usecs
// is not zero
uint64_t idle_timeout_usecs;
std::unique_ptr<struct event, void (*)(struct event*)> idle_timeout_event;
Lobby(std::shared_ptr<ServerState> s, uint32_t id);
Lobby(const Lobby&) = delete;
Lobby(Lobby&&) = delete;
~Lobby();
Lobby& operator=(const Lobby&) = delete;
Lobby& operator=(Lobby&&) = delete;
@@ -198,4 +205,6 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
static uint8_t game_event_for_lobby_event(uint8_t lobby_event);
std::unordered_map<uint32_t, std::shared_ptr<Client>> clients_by_serial_number() const;
static void dispatch_on_idle_timeout(evutil_socket_t, short, void* ctx);
};
+31 -20
View File
@@ -18,7 +18,8 @@
using namespace std;
ServerState::ServerState(shared_ptr<struct event_base> base, const string& config_filename, bool is_replay)
: config_filename(config_filename),
: base(base),
config_filename(config_filename),
is_replay(is_replay),
dns_server_port(0),
ip_stack_debug(false),
@@ -28,6 +29,7 @@ ServerState::ServerState(shared_ptr<struct event_base> base, const string& confi
item_tracking_enabled(true),
enable_drops_behavior(BehaviorSwitch::ON_BY_DEFAULT),
use_server_item_tables_behavior(BehaviorSwitch::OFF_BY_DEFAULT),
persistent_game_idle_timeout_usecs(0),
ep3_send_function_call_enabled(false),
catch_handler_exceptions(true),
ep3_infinite_meseta(false),
@@ -43,6 +45,7 @@ ServerState::ServerState(shared_ptr<struct event_base> base, const string& confi
ep3_card_auction_min_size(0),
ep3_card_auction_max_size(0),
player_files_manager(make_shared<PlayerFilesManager>(base)),
destroy_lobbies_event(event_new(base.get(), -1, EV_TIMEOUT, &ServerState::dispatch_destroy_lobbies, this), event_free),
next_lobby_id(1),
pre_lobby_event(0),
ep3_menu_song(-1),
@@ -173,12 +176,9 @@ void ServerState::add_client_to_available_lobby(shared_ptr<Client> c) {
void ServerState::remove_client_from_lobby(shared_ptr<Client> c) {
auto l = c->lobby.lock();
if (l) {
uint8_t old_client_id = c->lobby_client_id;
l->remove_client(c);
if (!l->check_flag(Lobby::Flag::PERSISTENT) && (l->count_clients() == 0)) {
this->remove_lobby(l->lobby_id);
} else {
send_player_leave_notification(l, c->lobby_client_id);
}
this->on_player_left_lobby(l, old_client_id);
}
}
@@ -201,11 +201,7 @@ bool ServerState::change_client_lobby(
}
if (current_lobby) {
if (!current_lobby->check_flag(Lobby::Flag::PERSISTENT) && (current_lobby->count_clients() == 0)) {
this->remove_lobby(current_lobby->lobby_id);
} else {
send_player_leave_notification(current_lobby, old_lobby_client_id);
}
this->on_player_left_lobby(current_lobby, old_lobby_client_id);
}
if (send_join_notification) {
this->send_lobby_join_notifications(new_lobby, c);
@@ -256,19 +252,17 @@ shared_ptr<Lobby> ServerState::create_lobby() {
}
auto l = make_shared<Lobby>(this->shared_from_this(), this->next_lobby_id++);
this->id_to_lobby.emplace(l->lobby_id, l);
l->log.info("Created lobby");
l->idle_timeout_usecs = this->persistent_game_idle_timeout_usecs;
return l;
}
void ServerState::remove_lobby(uint32_t lobby_id) {
auto lobby_it = this->id_to_lobby.find(lobby_id);
void ServerState::remove_lobby(shared_ptr<Lobby> l) {
auto lobby_it = this->id_to_lobby.find(l->lobby_id);
if (lobby_it == this->id_to_lobby.end()) {
throw logic_error("attempted to remove nonexistent lobby");
throw logic_error("lobby not registered");
}
auto l = lobby_it->second;
if (l->count_clients() != 0) {
throw logic_error("attempted to delete lobby with clients in it");
if (lobby_it->second != l) {
throw logic_error("incorrect lobby ID in registry");
}
if (l->check_flag(Lobby::Flag::IS_SPECTATOR_TEAM)) {
@@ -284,8 +278,20 @@ void ServerState::remove_lobby(uint32_t lobby_id) {
send_ep3_disband_watcher_lobbies(l);
}
l->log.info("Deleted lobby");
this->lobbies_to_destroy.emplace(l);
auto tv = usecs_to_timeval(0);
event_add(this->destroy_lobbies_event.get(), &tv);
this->id_to_lobby.erase(lobby_it);
l->log.info("Enqueued for deletion");
}
void ServerState::on_player_left_lobby(shared_ptr<Lobby> l, uint8_t leaving_client_id) {
if (l->count_clients() > 0) {
send_player_leave_notification(l, leaving_client_id);
} else if (!l->check_flag(Lobby::Flag::PERSISTENT)) {
this->remove_lobby(l);
}
}
shared_ptr<Client> ServerState::find_client(const string* identifier, uint64_t serial_number, shared_ptr<Lobby> l) {
@@ -625,6 +631,7 @@ void ServerState::parse_config(const JSON& json, bool is_reload) {
this->item_tracking_enabled = json.get_bool("EnableItemTracking", this->item_tracking_enabled);
this->enable_drops_behavior = parse_behavior_switch("ItemDropMode", this->enable_drops_behavior);
this->use_server_item_tables_behavior = parse_behavior_switch("UseServerItemTables", this->use_server_item_tables_behavior);
this->persistent_game_idle_timeout_usecs = json.get_int("PersistentGameIdleTimeout", this->persistent_game_idle_timeout_usecs);
this->cheat_mode_behavior = parse_behavior_switch("CheatModeBehavior", this->cheat_mode_behavior);
this->ep3_send_function_call_enabled = json.get_bool("EnableEpisode3SendFunctionCall", this->ep3_send_function_call_enabled);
this->catch_handler_exceptions = json.get_bool("CatchHandlerExceptions", this->catch_handler_exceptions);
@@ -1212,3 +1219,7 @@ shared_ptr<const vector<string>> ServerState::information_contents_for_client(sh
shared_ptr<const QuestIndex> ServerState::quest_index_for_version(Version version) const {
return is_ep3(version) ? this->ep3_download_quest_index : this->default_quest_index;
}
void ServerState::dispatch_destroy_lobbies(evutil_socket_t, short, void* ctx) {
reinterpret_cast<ServerState*>(ctx)->lobbies_to_destroy.clear();
}
+10 -1
View File
@@ -59,6 +59,8 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
return (b == BehaviorSwitch::OFF_BY_DEFAULT) || (b == BehaviorSwitch::ON_BY_DEFAULT);
}
std::shared_ptr<struct event_base> base;
std::string config_filename;
bool is_replay;
@@ -76,6 +78,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
bool item_tracking_enabled;
BehaviorSwitch enable_drops_behavior;
BehaviorSwitch use_server_item_tables_behavior;
uint64_t persistent_game_idle_timeout_usecs;
bool ep3_send_function_call_enabled;
bool catch_handler_exceptions;
bool ep3_infinite_meseta;
@@ -173,6 +176,8 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
std::shared_ptr<PlayerFilesManager> player_files_manager;
std::unordered_map<Channel*, std::shared_ptr<Client>> channel_to_client;
std::map<int64_t, std::shared_ptr<Lobby>> id_to_lobby;
std::unordered_set<std::shared_ptr<Lobby>> lobbies_to_destroy;
std::shared_ptr<struct event> destroy_lobbies_event;
std::vector<std::shared_ptr<Lobby>> public_lobby_search_order;
std::atomic<int32_t> next_lobby_id;
uint8_t pre_lobby_event;
@@ -210,7 +215,8 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
std::vector<std::shared_ptr<Lobby>> all_lobbies();
std::shared_ptr<Lobby> create_lobby();
void remove_lobby(uint32_t lobby_id);
void remove_lobby(std::shared_ptr<Lobby> l);
void on_player_left_lobby(std::shared_ptr<Lobby> l, uint8_t leaving_client_id);
std::shared_ptr<Client> find_client(
const std::string* identifier = nullptr,
@@ -252,4 +258,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
void load_quest_index();
void compile_functions();
void load_dol_files();
void enqueue_destroy_lobbies();
static void dispatch_destroy_lobbies(evutil_socket_t, short, void* ctx);
};
+8
View File
@@ -675,6 +675,14 @@
},
],
// Persistent game timeout. This is the amount of time a game set to be
// persistent (with the $persist command) will continue to exist with no
// players in it before being deleted. The value is in microseconds; the
// default value is 30 minutes. If this is set to zero or not specified,
// persistent games never expire; such a game can then only deleted by joining
// it, running $persist again, and leaving.
"PersistentGameIdleTimeout": 1800000000,
// Cheat mode behavior. There are three values:
// "Off": Cheat mode is disabled on the entire server. Cheat mode cannot be
// enabled in games, and the $cheat command does nothing. This also
+1
View File
@@ -10,6 +10,7 @@
"ServerName": "Alexandria",
"CatchHandlerExceptions": false,
"PersistentGameIdleTimeout": 1800000000,
"ItemDropMode": "OnByDefault",
"UseServerItemTables": "OffByDefault",