From 4f5436f3768913ac120dd0c261893611d6dd8bb8 Mon Sep 17 00:00:00 2001 From: Ritchie Cunningham Date: Sun, 12 Oct 2025 01:05:23 +0100 Subject: [PATCH] [Refactor] Implement in-memory VFS cache. Refactors VFS to use a cetralised, in memory caching on the server. This resolves performance and state sync issues from the previous implementation. --- common/src/command_processor.cpp | 33 ++++--- common/src/command_processor.h | 14 +-- common/src/db/database_manager.cpp | 10 +- common/src/db/database_manager.h | 2 - common/src/db/machine_repository.cpp | 9 ++ common/src/db/machine_repository.h | 1 + common/src/i_network_bridge.h | 11 +++ common/src/lua_api.cpp | 95 ++++++++----------- common/src/lua_api.h | 1 + common/src/lua_processor.cpp | 22 ++--- common/src/lua_processor.h | 2 +- common/src/machine_manager.cpp | 18 +++- common/src/machine_manager.h | 11 ++- server/src/network_manager.cpp | 137 +++++++++++++++++++-------- server/src/network_manager.h | 19 +++- server/src/player.cpp | 19 ++-- server/src/player.h | 8 +- 17 files changed, 256 insertions(+), 156 deletions(-) create mode 100644 common/src/i_network_bridge.h diff --git a/common/src/command_processor.cpp b/common/src/command_processor.cpp index 5e05a64..e7174d0 100644 --- a/common/src/command_processor.cpp +++ b/common/src/command_processor.cpp @@ -3,10 +3,9 @@ #include "command_processor.h" #include "db/database_manager.h" -#include "vfs.h" +#include "i_network_bridge.h" #include "lua_api.h" #include "lua_processor.h" -#include "machine_manager.h" #include "machine.h" @@ -24,20 +23,19 @@ vfs_node* find_node_by_id(vfs_node* root, long long id) { } CommandProcessor::CommandProcessor(Machine* home_machine, - std::map& world_machines, DatabaseManager* db_manager, - MachineManager* machine_manager) : + MachineManager* machine_manager, + INetworkBridge* network_bridge) : _machine_manager(machine_manager), _db_manager(db_manager), + _network_bridge(network_bridge), _home_machine(home_machine), - _session_machine(home_machine), - _world_machines(world_machines) { - _lua = new LuaProcessor(); - _current_dir = _session_machine->vfs_root; + _session_machine(home_machine) { + + if(home_machine) _current_dir = home_machine->vfs_root; } CommandProcessor::~CommandProcessor(void) { - delete _lua; } vfs_node* CommandProcessor::get_current_dir(void) { @@ -47,11 +45,6 @@ vfs_node* CommandProcessor::get_current_dir(void) { Machine* CommandProcessor::get_home_machine(void) { return _home_machine; } Machine* CommandProcessor::get_session_machine(void) { return _session_machine; } -std::map& -CommandProcessor::get_world_machines(void) { - return _world_machines; -} - DatabaseManager* CommandProcessor::get_db_manager(void) { return _db_manager; } @@ -60,6 +53,10 @@ MachineManager* CommandProcessor::get_machine_manager(void) { return _machine_manager; } +INetworkBridge* CommandProcessor::get_network_bridge(void) { + return _network_bridge; +} + void CommandProcessor::set_current_dir(vfs_node* node) { _current_dir = node; } @@ -72,6 +69,12 @@ void CommandProcessor::set_session_machine(Machine* machine) { } std::string CommandProcessor::process_command(const std::string& command) { + /* + * Creating the Lua processor on-demand to ensure it exists on the same + * thread that will execute the script. + */ + LuaProcessor lua(*this); + /* Trim trailing whitespace. */ std::string cmd = command; size_t end = cmd.find_last_not_of(" \t\n\r"); @@ -104,7 +107,7 @@ std::string CommandProcessor::process_command(const std::string& command) { if(script_id > 0) { bool is_remote = (_session_machine != _home_machine); - sol::object result = _lua->execute(script_content, *this, args, is_remote); + sol::object result = lua.execute(script_content, *this, args, is_remote); return result.is() ? result.as() : "[Script returned an unexpected type]"; } return "Unknown command: " + command_name + "\n"; diff --git a/common/src/command_processor.h b/common/src/command_processor.h index 87f2d30..e7b611d 100644 --- a/common/src/command_processor.h +++ b/common/src/command_processor.h @@ -1,18 +1,18 @@ #pragma once -#include #include -#include #include "db/database_manager.h" +#include "i_network_bridge.h" #include "machine.h" #include "machine_manager.h" -#include "vfs.h" + +class LuaProcessor; class CommandProcessor { public: - CommandProcessor(Machine* home_machine, std::map& world_machines, - DatabaseManager* db_manager, MachineManager* machine_manager); + CommandProcessor(Machine* home_machine, DatabaseManager* db_manager, + MachineManager* machine_manager, INetworkBridge* network_bridge); ~CommandProcessor(void); std::string process_command(const std::string& command); @@ -23,9 +23,9 @@ public: vfs_node* get_current_dir(void); Machine* get_home_machine(void); Machine* get_session_machine(void); - std::map& get_world_machines(void); DatabaseManager* get_db_manager(void); MachineManager* get_machine_manager(void); + INetworkBridge* get_network_bridge(void); void set_current_dir(vfs_node* node); void set_session_machine(Machine* machine); @@ -33,9 +33,9 @@ public: private: MachineManager* _machine_manager; DatabaseManager* _db_manager; + INetworkBridge* _network_bridge; Machine* _home_machine; Machine* _session_machine; vfs_node* _current_dir; - std::map& _world_machines; LuaProcessor* _lua; }; diff --git a/common/src/db/database_manager.cpp b/common/src/db/database_manager.cpp index f5c73b2..a1db1c5 100644 --- a/common/src/db/database_manager.cpp +++ b/common/src/db/database_manager.cpp @@ -8,13 +8,7 @@ DatabaseManager::DatabaseManager(const std::string& db_path) : _machine_repository = std::make_unique(_db); _service_repository = std::make_unique(_db); _vfs_repository = std::make_unique(_db); -} -DatabaseManager::~DatabaseManager(void) { - /* db is auto closed when _db goes out of scope. */ -} - -void DatabaseManager::init(void) { _db << "CREATE TABLE IF NOT EXISTS players(" "id INTEGER PRIMARY KEY AUTOINCREMENT," "username TEXT NOT NULL UNIQUE," @@ -46,6 +40,10 @@ void DatabaseManager::init(void) { ");"; } +DatabaseManager::~DatabaseManager(void) { + /* db is auto closed when _db goes out of scope. */ +} + bool DatabaseManager::create_player(const std::string& username, const std::string& password, const std::string& hostname, vfs_node* vfs_template) { long long player_id = 0; diff --git a/common/src/db/database_manager.h b/common/src/db/database_manager.h index e483bff..33351bc 100644 --- a/common/src/db/database_manager.h +++ b/common/src/db/database_manager.h @@ -15,8 +15,6 @@ public: DatabaseManager(const std::string& db_path); ~DatabaseManager(void); - void init(void); - /* Return true on success, false if user already exists. */ bool create_player(const std::string& username, const std::string& password, const std::string& hostname, vfs_node* vfs_template); diff --git a/common/src/db/machine_repository.cpp b/common/src/db/machine_repository.cpp index 2a600eb..01b0152 100644 --- a/common/src/db/machine_repository.cpp +++ b/common/src/db/machine_repository.cpp @@ -31,6 +31,15 @@ std::vector MachineRepository::get_all_npcs(void) { return machines; } +std::vector MachineRepository::get_all(void) { + std::vector machines; + _db << "SELECT id, ip_address FROM machines;" + >> [&](long long id, std::string ip_address) { + machines.push_back({id, ip_address}); + }; + return machines; +} + std::string MachineRepository::get_hostname(long long machine_id) { std::string hostname; _db << "SELECT hostname FROM machines WHERE id = ?;" diff --git a/common/src/db/machine_repository.h b/common/src/db/machine_repository.h index ab45768..c0af20a 100644 --- a/common/src/db/machine_repository.h +++ b/common/src/db/machine_repository.h @@ -19,6 +19,7 @@ public: const std::string& ip_address); int get_npc_count(void); std::vector get_all_npcs(void); + std::vector get_all(void); std::string get_hostname(long long machine_id); private: diff --git a/common/src/i_network_bridge.h b/common/src/i_network_bridge.h new file mode 100644 index 0000000..40809ea --- /dev/null +++ b/common/src/i_network_bridge.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +class Machine; + +class INetworkBridge { +public: + virtual Machine* get_machine_by_ip(const std::string& ip) = 0; + virtual void release_machine(long long machine_id) = 0; +}; diff --git a/common/src/lua_api.cpp b/common/src/lua_api.cpp index 57cad1d..8355ab0 100644 --- a/common/src/lua_api.cpp +++ b/common/src/lua_api.cpp @@ -1,9 +1,9 @@ #include #include +#include "i_network_bridge.h" #include "lua_api.h" #include "command_processor.h" -#include "db/database_manager.h" #include "machine.h" #include "vfs.h" @@ -15,45 +15,34 @@ vfs_node* get_current_dir(CommandProcessor& context) { std::string rm(CommandProcessor& context, const std::string& filename) { vfs_node* current_dir = context.get_current_dir(); - DatabaseManager* db = context.get_db_manager(); + auto it = current_dir->children.find(filename); - /* find the file in the database. */ - long long file_id = 0; - int file_type = 0; - db->_db << "SELECT id, type FROM vfs_nodes WHERE parent_id = ? AND name = ?;" - << current_dir->id << filename - >> std::tie(file_id, file_type); - - if(file_id == 0) { + if(it == current_dir->children.end()) { return "rm: cannot remove '" + filename + "': No such file or directory."; } - if(file_type == DIR_NODE) { + if(it->second->type == DIR_NODE) { return "rm: cannot remove '" + filename + "': Is a directory."; } - db->_db << "DELETE FROM vfs_nodes WHERE id = ?;" << file_id; - /* Also remove from in-mem VFS. */ - delete current_dir->children[filename]; - current_dir->children.erase(filename); - + delete it->second; /* Free the memory for the node. */ + current_dir->children.erase(it); /* Remove from map. */ return ""; } std::string write_file(CommandProcessor& context, const std::string& filename, const std::string& content) { vfs_node* current_dir = context.get_current_dir(); - DatabaseManager* db = context.get_db_manager(); + auto it = current_dir->children.find(filename); - if(current_dir->children.count(filename)) { - vfs_node* target_node = current_dir->children[filename]; - db->_db << "UPDATE vfs_nodes SET content = ? WHERE id = ?;" - << content << target_node->id; - target_node->content = content; + if(it != current_dir->children.end()) { + /* File exists, update content. */ + if(it->second->type == DIR_NODE) { + return "write: " + filename + ": Is a directory."; + } + it->second->content = content; } else { - db->_db << "INSERT INTO vfs_nodes (machine_id, parent_id, name, type, content) VALUES (?, ?, ?, ?, ?);" - << context.get_session_machine()->id << current_dir->id << filename << FILE_NODE << content; + /* File does not exist, create it. */ vfs_node* new_file = new_node(filename, FILE_NODE, current_dir); - new_file->id = db->_db.last_insert_rowid(); new_file->content = content; current_dir->children[filename] = new_file; } @@ -62,24 +51,15 @@ std::string write_file(CommandProcessor& context, const std::string& filename, std::string cd(CommandProcessor& context, const std::string& path) { vfs_node* current_dir = context.get_current_dir(); - DatabaseManager* db = context.get_db_manager(); if(path == "..") { if(current_dir->parent) { context.set_current_dir(current_dir->parent); } } else { - long long target_id = 0; - db->_db << "SELECT id FROM vfs_nodes WHERE parent_id = ? AND name = ? AND type = 1;" - << current_dir->id << path - >> target_id; - - if(target_id > 0) { - /* Inefficient, I know. */ - Machine* reloaded_machine = - context.get_machine_manager()->load_machine(context.get_session_machine()->id, db); - vfs_node* new_dir = find_node_by_id(reloaded_machine->vfs_root, target_id); - context.set_current_dir(new_dir); + auto it = current_dir->children.find(path); + if(it != current_dir->children.end() && it->second->type == DIR_NODE) { + context.set_current_dir(it->second); } else { return "cd: no such file or directory: " + path; } @@ -89,52 +69,59 @@ std::string cd(CommandProcessor& context, const std::string& path) { std::string ls(CommandProcessor& context) { vfs_node* dir = context.get_current_dir(); - printf("DEBUG: ls command called for directory_id: %lld\n", dir->id); if(dir->type != DIR_NODE) { return "ls: not a directory"; } std::stringstream ss; - - context.get_db_manager()->_db << "SELECT name, type FROM vfs_nodes WHERE parent_id = ?;" - << dir->id - >> [&](std::string name, int type) { - ss << name; - if(type == DIR_NODE) ss << "/"; - ss << " "; - }; - + for(auto const& [name, node] : dir->children) { + ss << name; + if(node->type == DIR_NODE) { + ss << "/"; + } + ss << " "; + } return ss.str(); } std::string ssh(CommandProcessor& context, const std::string& ip) { - if(context.get_world_machines().count(ip)) { - context.set_session_machine(context.get_world_machines().at(ip)); + INetworkBridge* bridge = context.get_network_bridge(); + Machine* target_machine = bridge->get_machine_by_ip(ip); + + if(target_machine) { + context.set_session_machine(target_machine); return "Connected to " + ip; + } else { + return "ssh: Could not resolve hostname " + ip + ": Name or service not found"; } - return "ssh: Could not resolve hostname " + ip + ": Name or service not found"; } std::string nmap(CommandProcessor& context, const std::string& ip) { - if(!context.get_world_machines().count(ip)) { + long long machine_id = context.get_machine_manager()->get_machine_id_by_ip(ip); + if(machine_id == 0) { return "nmap: Could not resolve host: " + ip; } + auto services = context.get_db_manager()->services().get_for_machine(machine_id); - Machine* target_machine = context.get_world_machines().at(ip); - if(target_machine->services.empty()) { + if(services.empty()) { return "No open ports for " + ip; } std::stringstream ss; ss << "Host: " << ip << "\n"; ss << "PORT\tSTATE\tSERVICE\n"; - for(auto const& [port, service_name] : target_machine->services) { + for(auto const& [port, service_name] : services) { ss << port << "/tcp\t" << "open\t" << service_name << "\n"; } return ss.str(); } std::string disconnect(CommandProcessor& context) { + Machine* current_machine = context.get_session_machine(); + if(current_machine != context.get_home_machine()) { + INetworkBridge* bridge = context.get_network_bridge(); + bridge->release_machine(current_machine->id); + } context.set_session_machine(context.get_home_machine()); return "Connection closed."; } diff --git a/common/src/lua_api.h b/common/src/lua_api.h index ed16240..5113c7d 100644 --- a/common/src/lua_api.h +++ b/common/src/lua_api.h @@ -4,6 +4,7 @@ #include "vfs.h" class CommandProcessor; +class NetworkManager; namespace api { diff --git a/common/src/lua_processor.cpp b/common/src/lua_processor.cpp index 08d0f3e..a7e2853 100644 --- a/common/src/lua_processor.cpp +++ b/common/src/lua_processor.cpp @@ -4,11 +4,11 @@ #include #include "lua_processor.h" -#include "vfs.h" #include "lua_api.h" #include "command_processor.h" +#include "vfs.h" -LuaProcessor::LuaProcessor(void) { +LuaProcessor::LuaProcessor(CommandProcessor& context) { _lua.open_libraries(sol::lib::base, sol::lib::string, sol::lib::io, sol::lib::table); /* Expose vfs_node struct members to Lua. */ @@ -23,15 +23,15 @@ LuaProcessor::LuaProcessor(void) { /* Create the 'bettola' API table. */ sol::table bettola_api = _lua.create_named_table("bettola"); - bettola_api["rm"] = &api::rm; - bettola_api["ls"] = &api::ls; - bettola_api["write_file"] = &api::write_file; - bettola_api["get_current_dir"]= &api::get_current_dir; - bettola_api["cd"] = &api::cd; - bettola_api["ssh"] = &api::ssh; - bettola_api["nmap"] = &api::nmap; - bettola_api["disconnect"] = &api::disconnect; - bettola_api["close_terminal"] = &api::close_terminal; + bettola_api.set_function("rm", &api::rm); + bettola_api.set_function("ls", &api::ls); + bettola_api.set_function("write_file", &api::write_file); + bettola_api.set_function("get_current_dir", &api::get_current_dir); + bettola_api.set_function("cd", &api::cd); + bettola_api.set_function("close_terminal", &api::close_terminal); + bettola_api.set_function("ssh", &api::ssh); + bettola_api.set_function("nmap", &api::nmap); + bettola_api.set_function("disconnect", &api::disconnect); } LuaProcessor::~LuaProcessor(void) {} diff --git a/common/src/lua_processor.h b/common/src/lua_processor.h index 21d6fae..39a21b0 100644 --- a/common/src/lua_processor.h +++ b/common/src/lua_processor.h @@ -9,7 +9,7 @@ class CommandProcessor; class LuaProcessor { public: - LuaProcessor(void); + LuaProcessor(CommandProcessor& context); ~LuaProcessor(void); /* Executes a string of lua code and returns result as a string. */ diff --git a/common/src/machine_manager.cpp b/common/src/machine_manager.cpp index b94eea9..b7df0ed 100644 --- a/common/src/machine_manager.cpp +++ b/common/src/machine_manager.cpp @@ -96,7 +96,7 @@ void build_tree(vfs_node* parent, const std::map& nodes) { } } -Machine* MachineManager::load_machine(long long machine_id, DatabaseManager* db_manager) { +Machine* MachineManager::load_machine(long long machine_id) { printf("DEBUG: load_machine called for machine_id: %lld\n", machine_id); std::string hostname = _db_manager->machines().get_hostname(machine_id); @@ -107,7 +107,7 @@ Machine* MachineManager::load_machine(long long machine_id, DatabaseManager* db_ std::map node_map; vfs_node* root = nullptr; - auto nodes = db_manager->vfs().get_nodes_for_machine(machine_id); + auto nodes = _db_manager->vfs().get_nodes_for_machine(machine_id); for(vfs_node* node : nodes) { node_map[node->id] = node; if(node->name == "/") { @@ -123,3 +123,17 @@ Machine* MachineManager::load_machine(long long machine_id, DatabaseManager* db_ } return machine; } + +long long MachineManager::get_machine_id_by_ip(const std::string& ip) { + if(_ip_to_id_map.count(ip)) { + return _ip_to_id_map[ip]; + } + return 0; +} + +void MachineManager::init(void) { + auto all_machines = _db_manager->machines().get_all(); + for(const auto& machine_data : all_machines) { + _ip_to_id_map[machine_data.ip_address] = machine_data.id; + } +} diff --git a/common/src/machine_manager.h b/common/src/machine_manager.h index 39a878c..046a2c2 100644 --- a/common/src/machine_manager.h +++ b/common/src/machine_manager.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include "db/database_manager.h" @@ -16,12 +17,20 @@ public: Machine* create_machine(uint32_t id, const std::string& hostname, const std::string& system_type); - Machine* load_machine(long long machine_id, DatabaseManager* db_manager); + Machine* load_machine(long long machine_id); + void init(void); vfs_node* get_vfs_template(void) { return _vfs_template_root; } + /* Machine lookup. */ + long long get_machine_id_by_ip(const std::string& ip); + private: DatabaseManager* _db_manager; vfs_node* _vfs_template_root; + + /* In memory cache of all machines, loaded or not. */ + std::map _ip_to_id_map; + std::map _world_machines; }; diff --git a/server/src/network_manager.cpp b/server/src/network_manager.cpp index cd8c200..c101329 100644 --- a/server/src/network_manager.cpp +++ b/server/src/network_manager.cpp @@ -16,26 +16,18 @@ #include "vfs.h" NetworkManager::NetworkManager(const std::string& db_path) : + _io_context(), _db_manager(std::make_unique(db_path)), _acceptor(_io_context), _machine_manager(_db_manager.get()) { - _db_manager->init(); _seed_npc_machines(); - - /* Load NPC machines from the database. */ - auto npc_machines = _db_manager->machines().get_all_npcs(); - for(const auto& machine_data : npc_machines) { - _world_machines[machine_data.ip_address] = - _machine_manager.load_machine(machine_data.id, _db_manager.get()); - } - - fprintf(stderr, "Created world with %zu networks\n", _world_machines.size()); + _machine_manager.init(); } NetworkManager::~NetworkManager(void) { - for(auto const& [ip, machine] : _world_machines) { - delete machine; + for(auto& pair : _active_machines) { + _save_machine_vfs(pair.second.machine.get()); } stop(); } @@ -76,13 +68,10 @@ void NetworkManager::start_accept(void) { auto new_connection = std::make_shared(_io_context, std::move(socket)); - /* Create a new player for this connection. */ + /* Create a new player for this connection. pendnding authentication.*/ uint32_t player_id = _next_player_id++; - Machine* player_machine = _machine_manager.create_machine(player_id, "player.home", - "player"); - auto new_player = std::make_unique(player_id, player_machine, - _world_machines, _db_manager.get(), - &_machine_manager); + auto new_player = std::make_unique(player_id, nullptr, _db_manager.get(), + &_machine_manager, this); Player* new_player_ptr = new_player.get(); _players[player_id] = std::move(new_player); new_connection->set_id(player_id); @@ -123,12 +112,11 @@ void NetworkManager::on_message(std::shared_ptr connection, if(args.size() == 3) { if(_db_manager->create_player(args[0], args[1], args[2], _machine_manager.get_vfs_template())) { - long long machine_id = _db_manager->players().get_home_machine_id(args[0]); - Machine* home_machine = _machine_manager.load_machine(machine_id, _db_manager.get()); + long long home_machine_id = _db_manager->players().get_home_machine_id(args[0]); + Machine* home_machine = _get_or_load_machine(home_machine_id); delete player->cmd_processor; /* Delete old command processor. */ - player->cmd_processor = new CommandProcessor(home_machine, _world_machines, - _db_manager.get(), - &_machine_manager); + player->cmd_processor = new CommandProcessor(home_machine, _db_manager.get(), + &_machine_manager, this); player->state = PlayerState::ACTIVE; connection->send(net_protocol::build_message(net_protocol::Opcode::S2C_CREATE_ACCOUNT_SUCCESS)); /* send initial prompt. */ @@ -144,22 +132,20 @@ void NetworkManager::on_message(std::shared_ptr connection, case net_protocol::Opcode::C2S_LOGIN: if(args.size() == 2) { if(_db_manager->players().authenticate(args[0], args[1])) { - long long machine_id = _db_manager->players().get_home_machine_id(args[0]); - printf("DEBUG: Loading machine %lld for player %s\n", machine_id, args[0].c_str()); - Machine* home_machine = _machine_manager.load_machine(machine_id, _db_manager.get()); - delete player->cmd_processor; /* Delete old command processor. */ - player->cmd_processor = new CommandProcessor(home_machine, _world_machines, - _db_manager.get(), &_machine_manager); - - player->state = PlayerState::ACTIVE; + long long home_machine_id = _db_manager->players().get_home_machine_id(args[0]); + Machine* home_machine = _get_or_load_machine(home_machine_id); + delete player->cmd_processor; /* Delete old command processor. */ + player->cmd_processor = new CommandProcessor(home_machine, _db_manager.get(), + &_machine_manager, this); + player->state = PlayerState::ACTIVE; connection->send(net_protocol::build_message(net_protocol::Opcode::S2C_LOGIN_SUCCESS)); - /* Send initial prompt. */ - std::string prompt = "\n" + get_full_path(player->cmd_processor->get_current_dir()); - connection->send(net_protocol::build_message(net_protocol::Opcode::S2C_COMMAND_RESPONSE, - {prompt})); - } else { - connection->send(net_protocol::build_message(net_protocol::Opcode::S2C_LOGIN_FAIL, - {"Invalid username or password."})); + /* Send initial prompt. */ + std::string prompt = "\n" + get_full_path(player->cmd_processor->get_current_dir()); + connection->send(net_protocol::build_message(net_protocol::Opcode::S2C_COMMAND_RESPONSE, + {prompt})); + } else { + connection->send(net_protocol::build_message(net_protocol::Opcode::S2C_LOGIN_FAIL, + {"Invalid username or password."})); } } break; @@ -214,6 +200,20 @@ void NetworkManager::on_message(std::shared_ptr connection, void NetworkManager::on_disconnect(std::shared_ptr connection) { uint32_t player_id = connection->get_id(); + Player* player = _players[player_id].get(); + + if(player && player->cmd_processor) { + Machine* home_machine = player->cmd_processor->get_home_machine(); + Machine* session_machine = player->cmd_processor->get_session_machine(); + + if(home_machine) { + release_machine(home_machine->id); + } + if(session_machine && session_machine != home_machine) { + release_machine(session_machine->id); + } + } + fprintf(stderr, "[Player %u] Disconnected.\n", player_id); _connections.erase( std::remove(_connections.begin(), _connections.end(), connection), _connections.end()); @@ -221,6 +221,67 @@ void NetworkManager::on_disconnect(std::shared_ptr connectio _players.erase(player_id); } +Machine* NetworkManager::get_machine_by_ip(const std::string& ip) { + long long machine_id = _machine_manager.get_machine_id_by_ip(ip); + if(machine_id != 0) { + return _get_or_load_machine(machine_id); + } + return nullptr; +} + +Machine* NetworkManager::_get_or_load_machine(long long machine_id) { + if(_active_machines.count(machine_id)) { + _active_machines[machine_id].ref_count++; + return _active_machines[machine_id].machine.get(); + } + + /* Machine not in cache, load it. */ + fprintf(stderr, "Loading machine %lld into cache.\n", machine_id); + Machine* loaded_machine = _machine_manager.load_machine(machine_id); + _active_machines[machine_id] = { + std::unique_ptr(loaded_machine), 1 + }; + return loaded_machine; +} + +void NetworkManager::release_machine(long long machine_id) { + if(!_active_machines.count(machine_id)) { + return; + } + + _active_machines[machine_id].ref_count--; + fprintf(stderr, "Releasing machine %lld, ref count now %d.\n", machine_id, + _active_machines[machine_id].ref_count); + + if(_active_machines[machine_id].ref_count <= 0) { + fprintf(stderr, "Machine %lld has no more references, saving and evicting from cache.\n", + machine_id); + _save_machine_vfs(_active_machines[machine_id].machine.get()); + _active_machines.erase(machine_id); + } +} + +void NetworkManager::_recursive_save_vfs(long long machine_id, vfs_node* node, long long* parent_id) { + long long current_node_id = _db_manager->vfs().create_node(machine_id, parent_id, + node->name, node->type, + node->content); + + if(node->type == DIR_NODE) { + for(auto const& [name, child] : node->children) { + _recursive_save_vfs(machine_id, child, ¤t_node_id); + } + } +} + +void NetworkManager::_save_machine_vfs(Machine* machine) { + if(!machine || !machine->vfs_root) return; + + _db_manager->_db << "BEGIN;"; + _db_manager->_db << "DELETE FROM vfs_nodes WHERE machine_id = ?;" << machine->id; + _recursive_save_vfs(machine->id, machine->vfs_root, nullptr); + _db_manager->_db << "COMMIT;"; +} + void NetworkManager::_seed_npc_machines(void) { int npc_count = _db_manager->machines().get_npc_count(); diff --git a/server/src/network_manager.h b/server/src/network_manager.h index a6e1005..441fa9d 100644 --- a/server/src/network_manager.h +++ b/server/src/network_manager.h @@ -10,11 +10,17 @@ #include "asio/io_context.hpp" #include "net/tcp_connection.h" #include "player.h" +#include "i_network_bridge.h" #include "machine_manager.h" class DatabaseManager; -class NetworkManager { +struct CachedMachine { + std::unique_ptr machine; + int ref_count = 0; +}; + +class NetworkManager : public INetworkBridge { public: NetworkManager(const std::string& db_path); ~NetworkManager(void); @@ -22,11 +28,17 @@ public: void start(uint16_t port); void stop(void); + Machine* get_machine_by_ip(const std::string& ip) override; + void release_machine(long long machine_id) override; + private: void start_accept(void); void on_message(std::shared_ptr connection, const std::string& message); void on_disconnect(std::shared_ptr connection); + void _save_machine_vfs(Machine* machine); + void _recursive_save_vfs(long long machine_id, vfs_node* node, + long long* parent_id); void _seed_npc_machines(void); @@ -36,9 +48,12 @@ private: std::deque> _connections; std::unordered_map> _players; + std::map _active_machines; uint32_t _next_player_id = 1; - std::map _world_machines; /* For NPC's. */ std::unique_ptr _db_manager; MachineManager _machine_manager; + +protected: + Machine* _get_or_load_machine(long long machine_id); }; diff --git a/server/src/player.cpp b/server/src/player.cpp index a6e4514..42ef65f 100644 --- a/server/src/player.cpp +++ b/server/src/player.cpp @@ -1,25 +1,20 @@ #include "player.h" #include "command_processor.h" #include "db/database_manager.h" +#include "i_network_bridge.h" #include "machine.h" Player::Player(uint32_t new_id, Machine* home_machine, - std::map& world_machines, DatabaseManager* db_manager, - MachineManager* machine_manager) : + DatabaseManager* db_manager, + MachineManager* machine_manager, + INetworkBridge* network_bridge) : id(new_id), state(PlayerState::AUTHENTICATING) { - cmd_processor = new CommandProcessor(home_machine, world_machines, db_manager, machine_manager); + cmd_processor = new CommandProcessor(home_machine, db_manager, machine_manager, + network_bridge); } Player::~Player(void) { - if(cmd_processor) { - /* - * Player is owner of their own machine. CommandProcessor - * holds the most current pointer to it (in case of CoW). - * Delete the machine first, then the processor point to it. - */ - delete cmd_processor->get_home_machine(); - delete cmd_processor; - } + delete cmd_processor; } diff --git a/server/src/player.h b/server/src/player.h index 7215b95..37304ab 100644 --- a/server/src/player.h +++ b/server/src/player.h @@ -1,11 +1,10 @@ #pragma once #include -#include -#include #include "db/database_manager.h" #include "command_processor.h" +#include "i_network_bridge.h" #include "machine.h" #include "machine_manager.h" @@ -16,9 +15,8 @@ enum class PlayerState { class Player { public: - Player(uint32_t id, Machine* home_machine, std::map& world_machines, DatabaseManager* db_manager, - MachineManager* machine_manager); + Player(uint32_t id, Machine* home_machine, DatabaseManager* db_manager, + MachineManager* machine_manager, INetworkBridge* network_bridge); ~Player(void); uint32_t id;