From 01b83044dca0e999e5cd57af0b0d1a3964af79d0 Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Mon, 4 Dec 2023 12:38:26 -0800 Subject: [PATCH] cache loaded player files between sessions --- src/Client.cc | 1 + src/DNSServer.hh | 3 +- src/Main.cc | 2 +- src/Player.cc | 112 +++++++++++++++++++++++++++++++++++++++-- src/Player.hh | 29 ++++++++++- src/ReceiveCommands.cc | 4 +- src/ServerState.cc | 3 +- src/ServerState.hh | 5 +- 8 files changed, 148 insertions(+), 11 deletions(-) diff --git a/src/Client.cc b/src/Client.cc index 539f2822..2d3f9a0e 100644 --- a/src/Client.cc +++ b/src/Client.cc @@ -153,6 +153,7 @@ Client::Client( lobby_client_id(0), lobby_arrow_color(0), preferred_lobby_id(-1), + game_data(server->get_state()->player_files_manager), save_game_data_event( event_new( bufferevent_get_base(bev), -1, EV_TIMEOUT | EV_PERSIST, diff --git a/src/DNSServer.hh b/src/DNSServer.hh index a58b5719..37eed4af 100644 --- a/src/DNSServer.hh +++ b/src/DNSServer.hh @@ -31,7 +31,6 @@ private: uint32_t local_connect_address; uint32_t external_connect_address; - static void dispatch_on_receive_message(evutil_socket_t fd, short events, - void* ctx); + static void dispatch_on_receive_message(evutil_socket_t fd, short events, void* ctx); void on_receive_message(int fd, short event); }; diff --git a/src/Main.cc b/src/Main.cc index 19de9546..906b5d5e 100644 --- a/src/Main.cc +++ b/src/Main.cc @@ -1698,7 +1698,7 @@ Action a_run_server_replay_log( } shared_ptr base(event_base_new(), event_base_free); - auto state = make_shared(config_filename, is_replay); + auto state = make_shared(base, config_filename, is_replay); state->init(); shared_ptr dns_server; diff --git a/src/Player.cc b/src/Player.cc index 5566840b..e28bb252 100644 --- a/src/Player.cc +++ b/src/Player.cc @@ -19,10 +19,90 @@ using namespace std; -ClientGameData::ClientGameData() +PlayerFilesManager::PlayerFilesManager(std::shared_ptr base) + : base(base), + clear_expired_files_event( + event_new(this->base.get(), -1, EV_TIMEOUT | EV_PERSIST, &PlayerFilesManager::clear_expired_files, this), + event_free) { + auto tv = usecs_to_timeval(30 * 1000 * 1000); + event_add(this->clear_expired_files_event.get(), &tv); +} + +template +size_t erase_unused(std::unordered_map>& m) { + size_t ret = 0; + for (auto it = m.begin(); it != m.end();) { + if (it->second.use_count() <= 1) { + it = m.erase(it); + ret++; + } else { + it++; + } + } + return ret; +} + +std::shared_ptr PlayerFilesManager::get_system(const std::string& filename) { + try { + return this->loaded_system_files.at(filename); + } catch (const out_of_range&) { + return nullptr; + } +} + +std::shared_ptr PlayerFilesManager::get_character(const std::string& filename) { + try { + return this->loaded_character_files.at(filename); + } catch (const out_of_range&) { + return nullptr; + } +} + +std::shared_ptr PlayerFilesManager::get_guild_card(const std::string& filename) { + try { + return this->loaded_guild_card_files.at(filename); + } catch (const out_of_range&) { + return nullptr; + } +} + +void PlayerFilesManager::set_system(const std::string& filename, std::shared_ptr file) { + if (!this->loaded_system_files.emplace(filename, file).second) { + throw runtime_error("Guild Card file already loaded"); + } +} +void PlayerFilesManager::set_character(const std::string& filename, std::shared_ptr file) { + if (!this->loaded_character_files.emplace(filename, file).second) { + throw runtime_error("character file already loaded"); + } +} +void PlayerFilesManager::set_guild_card(const std::string& filename, std::shared_ptr file) { + if (!this->loaded_guild_card_files.emplace(filename, file).second) { + throw runtime_error("Guild Card file already loaded"); + } +} + +void PlayerFilesManager::clear_expired_files(evutil_socket_t, short, void* ctx) { + auto* self = reinterpret_cast(ctx); + size_t num_deleted = erase_unused(self->loaded_system_files); + if (num_deleted) { + player_data_log.info("Cleared %zu expired system file(s)", num_deleted); + } + num_deleted = erase_unused(self->loaded_character_files); + if (num_deleted) { + player_data_log.info("Cleared %zu expired character file(s)", num_deleted); + } + num_deleted = erase_unused(self->loaded_guild_card_files); + if (num_deleted) { + player_data_log.info("Cleared %zu expired Guild Card file(s)", num_deleted); + } +} + +ClientGameData::ClientGameData(std::shared_ptr files_manager) : guild_card_number(0), should_update_play_time(false), bb_character_index(-1), + files_manager(files_manager), last_play_time_update(0) { for (size_t z = 0; z < this->blocked_senders.size(); z++) { this->blocked_senders[z] = 0; @@ -247,14 +327,23 @@ void ClientGameData::load_all_files() { this->guild_card_data.reset(); string sys_filename = this->system_filename(); - if (isfile(sys_filename)) { + this->system_data = this->files_manager->get_system(sys_filename); + if (this->system_data) { + player_data_log.info("Using loaded system file %s", sys_filename.c_str()); + } else if (isfile(sys_filename)) { this->system_data = make_shared(load_object_file(sys_filename, true)); + this->files_manager->set_system(sys_filename, this->system_data); player_data_log.info("Loaded system data from %s", sys_filename.c_str()); + } else { + player_data_log.info("System file is missing: %s", sys_filename.c_str()); } if (this->bb_character_index >= 0) { string char_filename = this->character_filename(); - if (isfile(char_filename)) { + this->character_data = this->files_manager->get_character(char_filename); + if (this->character_data) { + player_data_log.info("Using loaded character file %s", char_filename.c_str()); + } else if (isfile(char_filename)) { auto f = fopen_unique(char_filename, "rb"); auto header = freadx(f.get()); if (header.size != 0x399C) { @@ -267,21 +356,31 @@ void ClientGameData::load_all_files() { throw runtime_error("incorrect flag in character file header"); } this->character_data = make_shared(freadx(f.get())); + this->files_manager->set_character(this->character_filename(), this->character_data); player_data_log.info("Loaded character data from %s", char_filename.c_str()); // If there was no .psosys file, load the system file from the .psochar // file instead if (!this->system_data) { this->system_data = make_shared(freadx(f.get())); + this->files_manager->set_system(sys_filename, this->system_data); player_data_log.info("Loaded system data from %s", char_filename.c_str()); } + } else { + player_data_log.info("Character file is missing: %s", char_filename.c_str()); } } string card_filename = this->guild_card_filename(); - if (isfile(card_filename)) { + this->guild_card_data = this->files_manager->get_guild_card(card_filename); + if (this->guild_card_data) { + player_data_log.info("Using loaded Guild Card file %s", card_filename.c_str()); + } else if (isfile(card_filename)) { this->guild_card_data = make_shared(load_object_file(card_filename)); + this->files_manager->set_guild_card(card_filename, this->guild_card_data); player_data_log.info("Loaded Guild Card data from %s", card_filename.c_str()); + } else { + player_data_log.info("Guild Card file is missing: %s", card_filename.c_str()); } // If any of the above files were missing, try to load from .nsa/.nsc files instead @@ -295,20 +394,24 @@ void ClientGameData::load_all_files() { } if (!this->system_data) { this->system_data = make_shared(nsa_data->system_file.base); + this->files_manager->set_system(sys_filename, this->system_data); player_data_log.info("Loaded legacy system data from %s", nsa_filename.c_str()); } if (!this->guild_card_data) { this->guild_card_data = make_shared(nsa_data->guild_card_file); + this->files_manager->set_guild_card(card_filename, this->guild_card_data); player_data_log.info("Loaded legacy Guild Card data from %s", nsa_filename.c_str()); } } if (!this->system_data) { this->system_data = make_shared(); + this->files_manager->set_system(sys_filename, this->system_data); player_data_log.info("Created new system data"); } if (!this->guild_card_data) { this->guild_card_data = make_shared(); + this->files_manager->set_guild_card(card_filename, this->guild_card_data); player_data_log.info("Created new Guild Card data"); } @@ -326,6 +429,7 @@ void ClientGameData::load_all_files() { } this->character_data = make_shared(); + this->files_manager->set_character(this->character_filename(), this->character_data); this->character_data->inventory = nsc_data.inventory; this->character_data->disp = nsc_data.disp; this->character_data->play_time_seconds = nsc_data.disp.play_time; diff --git a/src/Player.hh b/src/Player.hh index d0ad7705..4fd84a2e 100644 --- a/src/Player.hh +++ b/src/Player.hh @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -30,6 +31,30 @@ struct PendingCardTrade { std::vector> card_to_count; }; +class PlayerFilesManager { +public: + explicit PlayerFilesManager(std::shared_ptr base); + ~PlayerFilesManager() = default; + + std::shared_ptr get_system(const std::string& filename); + std::shared_ptr get_character(const std::string& filename); + std::shared_ptr get_guild_card(const std::string& filename); + + void set_system(const std::string& filename, std::shared_ptr file); + void set_character(const std::string& filename, std::shared_ptr file); + void set_guild_card(const std::string& filename, std::shared_ptr file); + +private: + std::shared_ptr base; + std::unique_ptr clear_expired_files_event; + + std::unordered_map> loaded_system_files; + std::unordered_map> loaded_character_files; + std::unordered_map> loaded_guild_card_files; + + static void clear_expired_files(evutil_socket_t fd, short events, void* ctx); +}; + class ClientGameData { public: uint32_t guild_card_number; @@ -55,7 +80,7 @@ public: ItemData identify_result; std::array, 3> shop_contents; - ClientGameData(); + explicit ClientGameData(std::shared_ptr files_manager); ~ClientGameData(); void create_battle_overlay(std::shared_ptr rules, std::shared_ptr level_table); @@ -88,6 +113,8 @@ public: void save_guild_card_file() const; private: + std::shared_ptr files_manager; + // The overlay character data is used in battle and challenge modes, when // character data is temporarily replaced in-game. In other play modes and in // lobbies, overlay_character_data is null. diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index e2e8068f..9df5c155 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -3113,7 +3113,9 @@ static void on_E3_BB(shared_ptr c, uint16_t, uint32_t, string& data) { return; } - ClientGameData temp_gd; + auto s = c->require_server_state(); + + ClientGameData temp_gd(s->player_files_manager); temp_gd.guild_card_number = c->license->serial_number; temp_gd.bb_username = c->license->bb_username; temp_gd.bb_character_index = cmd.character_index; diff --git a/src/ServerState.cc b/src/ServerState.cc index 9c5725ca..d7ad56c3 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -17,7 +17,7 @@ using namespace std; -ServerState::ServerState(const string& config_filename, bool is_replay) +ServerState::ServerState(shared_ptr base, const string& config_filename, bool is_replay) : config_filename(config_filename), is_replay(is_replay), dns_server_port(0), @@ -42,6 +42,7 @@ ServerState::ServerState(const string& config_filename, bool is_replay) ep3_card_auction_points(0), ep3_card_auction_min_size(0), ep3_card_auction_max_size(0), + player_files_manager(make_shared(base)), next_lobby_id(1), pre_lobby_event(0), ep3_menu_song(-1), diff --git a/src/ServerState.hh b/src/ServerState.hh index 9e7a337d..5c43341c 100644 --- a/src/ServerState.hh +++ b/src/ServerState.hh @@ -1,5 +1,7 @@ #pragma once +#include + #include #include #include @@ -167,6 +169,7 @@ struct ServerState : public std::enable_shared_from_this { std::string pc_patch_server_message; std::string bb_patch_server_message; + std::shared_ptr player_files_manager; std::unordered_map> channel_to_client; std::map> id_to_lobby; std::vector> public_lobby_search_order; @@ -184,7 +187,7 @@ struct ServerState : public std::enable_shared_from_this { std::shared_ptr proxy_server; std::shared_ptr game_server; - ServerState(const std::string& config_filename, bool is_replay); + ServerState(std::shared_ptr base, const std::string& config_filename, bool is_replay); ServerState(const ServerState&) = delete; ServerState(ServerState&&) = delete; ServerState& operator=(const ServerState&) = delete;