[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.
This commit is contained in:
Ritchie Cunningham 2025-10-12 01:05:23 +01:00
parent 9d770ef9b2
commit 4f5436f376
17 changed files with 256 additions and 156 deletions

View File

@ -3,10 +3,9 @@
#include "command_processor.h" #include "command_processor.h"
#include "db/database_manager.h" #include "db/database_manager.h"
#include "vfs.h" #include "i_network_bridge.h"
#include "lua_api.h" #include "lua_api.h"
#include "lua_processor.h" #include "lua_processor.h"
#include "machine_manager.h"
#include "machine.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, CommandProcessor::CommandProcessor(Machine* home_machine,
std::map<std::string, Machine*>& world_machines,
DatabaseManager* db_manager, DatabaseManager* db_manager,
MachineManager* machine_manager) : MachineManager* machine_manager,
INetworkBridge* network_bridge) :
_machine_manager(machine_manager), _machine_manager(machine_manager),
_db_manager(db_manager), _db_manager(db_manager),
_network_bridge(network_bridge),
_home_machine(home_machine), _home_machine(home_machine),
_session_machine(home_machine), _session_machine(home_machine) {
_world_machines(world_machines) {
_lua = new LuaProcessor(); if(home_machine) _current_dir = home_machine->vfs_root;
_current_dir = _session_machine->vfs_root;
} }
CommandProcessor::~CommandProcessor(void) { CommandProcessor::~CommandProcessor(void) {
delete _lua;
} }
vfs_node* CommandProcessor::get_current_dir(void) { 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_home_machine(void) { return _home_machine; }
Machine* CommandProcessor::get_session_machine(void) { return _session_machine; } Machine* CommandProcessor::get_session_machine(void) { return _session_machine; }
std::map<std::string, Machine*>&
CommandProcessor::get_world_machines(void) {
return _world_machines;
}
DatabaseManager* CommandProcessor::get_db_manager(void) { DatabaseManager* CommandProcessor::get_db_manager(void) {
return _db_manager; return _db_manager;
} }
@ -60,6 +53,10 @@ MachineManager* CommandProcessor::get_machine_manager(void) {
return _machine_manager; return _machine_manager;
} }
INetworkBridge* CommandProcessor::get_network_bridge(void) {
return _network_bridge;
}
void CommandProcessor::set_current_dir(vfs_node* node) { void CommandProcessor::set_current_dir(vfs_node* node) {
_current_dir = node; _current_dir = node;
} }
@ -72,6 +69,12 @@ void CommandProcessor::set_session_machine(Machine* machine) {
} }
std::string CommandProcessor::process_command(const std::string& command) { 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. */ /* Trim trailing whitespace. */
std::string cmd = command; std::string cmd = command;
size_t end = cmd.find_last_not_of(" \t\n\r"); 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) { if(script_id > 0) {
bool is_remote = (_session_machine != _home_machine); 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<std::string>() ? result.as<std::string>() : "[Script returned an unexpected type]"; return result.is<std::string>() ? result.as<std::string>() : "[Script returned an unexpected type]";
} }
return "Unknown command: " + command_name + "\n"; return "Unknown command: " + command_name + "\n";

View File

@ -1,18 +1,18 @@
#pragma once #pragma once
#include <lua_processor.h>
#include <string> #include <string>
#include <map>
#include "db/database_manager.h" #include "db/database_manager.h"
#include "i_network_bridge.h"
#include "machine.h" #include "machine.h"
#include "machine_manager.h" #include "machine_manager.h"
#include "vfs.h"
class LuaProcessor;
class CommandProcessor { class CommandProcessor {
public: public:
CommandProcessor(Machine* home_machine, std::map<std::string, Machine*>& world_machines, CommandProcessor(Machine* home_machine, DatabaseManager* db_manager,
DatabaseManager* db_manager, MachineManager* machine_manager); MachineManager* machine_manager, INetworkBridge* network_bridge);
~CommandProcessor(void); ~CommandProcessor(void);
std::string process_command(const std::string& command); std::string process_command(const std::string& command);
@ -23,9 +23,9 @@ public:
vfs_node* get_current_dir(void); vfs_node* get_current_dir(void);
Machine* get_home_machine(void); Machine* get_home_machine(void);
Machine* get_session_machine(void); Machine* get_session_machine(void);
std::map<std::string, Machine*>& get_world_machines(void);
DatabaseManager* get_db_manager(void); DatabaseManager* get_db_manager(void);
MachineManager* get_machine_manager(void); MachineManager* get_machine_manager(void);
INetworkBridge* get_network_bridge(void);
void set_current_dir(vfs_node* node); void set_current_dir(vfs_node* node);
void set_session_machine(Machine* machine); void set_session_machine(Machine* machine);
@ -33,9 +33,9 @@ public:
private: private:
MachineManager* _machine_manager; MachineManager* _machine_manager;
DatabaseManager* _db_manager; DatabaseManager* _db_manager;
INetworkBridge* _network_bridge;
Machine* _home_machine; Machine* _home_machine;
Machine* _session_machine; Machine* _session_machine;
vfs_node* _current_dir; vfs_node* _current_dir;
std::map<std::string, Machine*>& _world_machines;
LuaProcessor* _lua; LuaProcessor* _lua;
}; };

View File

@ -8,13 +8,7 @@ DatabaseManager::DatabaseManager(const std::string& db_path) :
_machine_repository = std::make_unique<MachineRepository>(_db); _machine_repository = std::make_unique<MachineRepository>(_db);
_service_repository = std::make_unique<ServiceRepository>(_db); _service_repository = std::make_unique<ServiceRepository>(_db);
_vfs_repository = std::make_unique<VFSRepository>(_db); _vfs_repository = std::make_unique<VFSRepository>(_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(" _db << "CREATE TABLE IF NOT EXISTS players("
"id INTEGER PRIMARY KEY AUTOINCREMENT," "id INTEGER PRIMARY KEY AUTOINCREMENT,"
"username TEXT NOT NULL UNIQUE," "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, bool DatabaseManager::create_player(const std::string& username, const std::string& password,
const std::string& hostname, vfs_node* vfs_template) { const std::string& hostname, vfs_node* vfs_template) {
long long player_id = 0; long long player_id = 0;

View File

@ -15,8 +15,6 @@ public:
DatabaseManager(const std::string& db_path); DatabaseManager(const std::string& db_path);
~DatabaseManager(void); ~DatabaseManager(void);
void init(void);
/* Return true on success, false if user already exists. */ /* Return true on success, false if user already exists. */
bool create_player(const std::string& username, const std::string& password, bool create_player(const std::string& username, const std::string& password,
const std::string& hostname, vfs_node* vfs_template); const std::string& hostname, vfs_node* vfs_template);

View File

@ -31,6 +31,15 @@ std::vector<MachineData> MachineRepository::get_all_npcs(void) {
return machines; return machines;
} }
std::vector<MachineData> MachineRepository::get_all(void) {
std::vector<MachineData> 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 MachineRepository::get_hostname(long long machine_id) {
std::string hostname; std::string hostname;
_db << "SELECT hostname FROM machines WHERE id = ?;" _db << "SELECT hostname FROM machines WHERE id = ?;"

View File

@ -19,6 +19,7 @@ public:
const std::string& ip_address); const std::string& ip_address);
int get_npc_count(void); int get_npc_count(void);
std::vector<MachineData> get_all_npcs(void); std::vector<MachineData> get_all_npcs(void);
std::vector<MachineData> get_all(void);
std::string get_hostname(long long machine_id); std::string get_hostname(long long machine_id);
private: private:

View File

@ -0,0 +1,11 @@
#pragma once
#include <string>
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;
};

View File

@ -1,9 +1,9 @@
#include <sol/types.hpp> #include <sol/types.hpp>
#include <sstream> #include <sstream>
#include "i_network_bridge.h"
#include "lua_api.h" #include "lua_api.h"
#include "command_processor.h" #include "command_processor.h"
#include "db/database_manager.h"
#include "machine.h" #include "machine.h"
#include "vfs.h" #include "vfs.h"
@ -15,45 +15,34 @@ vfs_node* get_current_dir(CommandProcessor& context) {
std::string rm(CommandProcessor& context, const std::string& filename) { std::string rm(CommandProcessor& context, const std::string& filename) {
vfs_node* current_dir = context.get_current_dir(); 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. */ if(it == current_dir->children.end()) {
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) {
return "rm: cannot remove '" + filename + "': No such file or directory."; 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."; return "rm: cannot remove '" + filename + "': Is a directory.";
} }
db->_db << "DELETE FROM vfs_nodes WHERE id = ?;" << file_id; delete it->second; /* Free the memory for the node. */
/* Also remove from in-mem VFS. */ current_dir->children.erase(it); /* Remove from map. */
delete current_dir->children[filename];
current_dir->children.erase(filename);
return ""; return "";
} }
std::string write_file(CommandProcessor& context, const std::string& filename, std::string write_file(CommandProcessor& context, const std::string& filename,
const std::string& content) { const std::string& content) {
vfs_node* current_dir = context.get_current_dir(); 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)) { if(it != current_dir->children.end()) {
vfs_node* target_node = current_dir->children[filename]; /* File exists, update content. */
db->_db << "UPDATE vfs_nodes SET content = ? WHERE id = ?;" if(it->second->type == DIR_NODE) {
<< content << target_node->id; return "write: " + filename + ": Is a directory.";
target_node->content = content; }
it->second->content = content;
} else { } else {
db->_db << "INSERT INTO vfs_nodes (machine_id, parent_id, name, type, content) VALUES (?, ?, ?, ?, ?);" /* File does not exist, create it. */
<< context.get_session_machine()->id << current_dir->id << filename << FILE_NODE << content;
vfs_node* new_file = new_node(filename, FILE_NODE, current_dir); vfs_node* new_file = new_node(filename, FILE_NODE, current_dir);
new_file->id = db->_db.last_insert_rowid();
new_file->content = content; new_file->content = content;
current_dir->children[filename] = new_file; 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) { std::string cd(CommandProcessor& context, const std::string& path) {
vfs_node* current_dir = context.get_current_dir(); vfs_node* current_dir = context.get_current_dir();
DatabaseManager* db = context.get_db_manager();
if(path == "..") { if(path == "..") {
if(current_dir->parent) { if(current_dir->parent) {
context.set_current_dir(current_dir->parent); context.set_current_dir(current_dir->parent);
} }
} else { } else {
long long target_id = 0; auto it = current_dir->children.find(path);
db->_db << "SELECT id FROM vfs_nodes WHERE parent_id = ? AND name = ? AND type = 1;" if(it != current_dir->children.end() && it->second->type == DIR_NODE) {
<< current_dir->id << path context.set_current_dir(it->second);
>> 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);
} else { } else {
return "cd: no such file or directory: " + path; 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) { std::string ls(CommandProcessor& context) {
vfs_node* dir = context.get_current_dir(); vfs_node* dir = context.get_current_dir();
printf("DEBUG: ls command called for directory_id: %lld\n", dir->id);
if(dir->type != DIR_NODE) { if(dir->type != DIR_NODE) {
return "ls: not a directory"; return "ls: not a directory";
} }
std::stringstream ss; std::stringstream ss;
for(auto const& [name, node] : dir->children) {
context.get_db_manager()->_db << "SELECT name, type FROM vfs_nodes WHERE parent_id = ?;" ss << name;
<< dir->id if(node->type == DIR_NODE) {
>> [&](std::string name, int type) { ss << "/";
ss << name; }
if(type == DIR_NODE) ss << "/"; ss << " ";
ss << " "; }
};
return ss.str(); return ss.str();
} }
std::string ssh(CommandProcessor& context, const std::string& ip) { std::string ssh(CommandProcessor& context, const std::string& ip) {
if(context.get_world_machines().count(ip)) { INetworkBridge* bridge = context.get_network_bridge();
context.set_session_machine(context.get_world_machines().at(ip)); Machine* target_machine = bridge->get_machine_by_ip(ip);
if(target_machine) {
context.set_session_machine(target_machine);
return "Connected to " + ip; 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) { 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; 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(services.empty()) {
if(target_machine->services.empty()) {
return "No open ports for " + ip; return "No open ports for " + ip;
} }
std::stringstream ss; std::stringstream ss;
ss << "Host: " << ip << "\n"; ss << "Host: " << ip << "\n";
ss << "PORT\tSTATE\tSERVICE\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"; ss << port << "/tcp\t" << "open\t" << service_name << "\n";
} }
return ss.str(); return ss.str();
} }
std::string disconnect(CommandProcessor& context) { 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()); context.set_session_machine(context.get_home_machine());
return "Connection closed."; return "Connection closed.";
} }

View File

@ -4,6 +4,7 @@
#include "vfs.h" #include "vfs.h"
class CommandProcessor; class CommandProcessor;
class NetworkManager;
namespace api { namespace api {

View File

@ -4,11 +4,11 @@
#include <sol/raii.hpp> #include <sol/raii.hpp>
#include "lua_processor.h" #include "lua_processor.h"
#include "vfs.h"
#include "lua_api.h" #include "lua_api.h"
#include "command_processor.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); _lua.open_libraries(sol::lib::base, sol::lib::string, sol::lib::io, sol::lib::table);
/* Expose vfs_node struct members to Lua. */ /* Expose vfs_node struct members to Lua. */
@ -23,15 +23,15 @@ LuaProcessor::LuaProcessor(void) {
/* Create the 'bettola' API table. */ /* Create the 'bettola' API table. */
sol::table bettola_api = _lua.create_named_table("bettola"); sol::table bettola_api = _lua.create_named_table("bettola");
bettola_api["rm"] = &api::rm; bettola_api.set_function("rm", &api::rm);
bettola_api["ls"] = &api::ls; bettola_api.set_function("ls", &api::ls);
bettola_api["write_file"] = &api::write_file; bettola_api.set_function("write_file", &api::write_file);
bettola_api["get_current_dir"]= &api::get_current_dir; bettola_api.set_function("get_current_dir", &api::get_current_dir);
bettola_api["cd"] = &api::cd; bettola_api.set_function("cd", &api::cd);
bettola_api["ssh"] = &api::ssh; bettola_api.set_function("close_terminal", &api::close_terminal);
bettola_api["nmap"] = &api::nmap; bettola_api.set_function("ssh", &api::ssh);
bettola_api["disconnect"] = &api::disconnect; bettola_api.set_function("nmap", &api::nmap);
bettola_api["close_terminal"] = &api::close_terminal; bettola_api.set_function("disconnect", &api::disconnect);
} }
LuaProcessor::~LuaProcessor(void) {} LuaProcessor::~LuaProcessor(void) {}

View File

@ -9,7 +9,7 @@ class CommandProcessor;
class LuaProcessor { class LuaProcessor {
public: public:
LuaProcessor(void); LuaProcessor(CommandProcessor& context);
~LuaProcessor(void); ~LuaProcessor(void);
/* Executes a string of lua code and returns result as a string. */ /* Executes a string of lua code and returns result as a string. */

View File

@ -96,7 +96,7 @@ void build_tree(vfs_node* parent, const std::map<long long, vfs_node*>& 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); printf("DEBUG: load_machine called for machine_id: %lld\n", machine_id);
std::string hostname = _db_manager->machines().get_hostname(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<long long, vfs_node*> node_map; std::map<long long, vfs_node*> node_map;
vfs_node* root = nullptr; 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) { for(vfs_node* node : nodes) {
node_map[node->id] = node; node_map[node->id] = node;
if(node->name == "/") { if(node->name == "/") {
@ -123,3 +123,17 @@ Machine* MachineManager::load_machine(long long machine_id, DatabaseManager* db_
} }
return machine; 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;
}
}

View File

@ -1,5 +1,6 @@
#pragma once #pragma once
#include <map>
#include <string> #include <string>
#include "db/database_manager.h" #include "db/database_manager.h"
@ -16,12 +17,20 @@ public:
Machine* create_machine(uint32_t id, const std::string& hostname, Machine* create_machine(uint32_t id, const std::string& hostname,
const std::string& system_type); 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; } vfs_node* get_vfs_template(void) { return _vfs_template_root; }
/* Machine lookup. */
long long get_machine_id_by_ip(const std::string& ip);
private: private:
DatabaseManager* _db_manager; DatabaseManager* _db_manager;
vfs_node* _vfs_template_root; vfs_node* _vfs_template_root;
/* In memory cache of all machines, loaded or not. */
std::map<std::string, long long> _ip_to_id_map;
std::map<long long, Machine*> _world_machines;
}; };

View File

@ -16,26 +16,18 @@
#include "vfs.h" #include "vfs.h"
NetworkManager::NetworkManager(const std::string& db_path) : NetworkManager::NetworkManager(const std::string& db_path) :
_io_context(),
_db_manager(std::make_unique<DatabaseManager>(db_path)), _db_manager(std::make_unique<DatabaseManager>(db_path)),
_acceptor(_io_context), _acceptor(_io_context),
_machine_manager(_db_manager.get()) { _machine_manager(_db_manager.get()) {
_db_manager->init();
_seed_npc_machines(); _seed_npc_machines();
_machine_manager.init();
/* 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());
} }
NetworkManager::~NetworkManager(void) { NetworkManager::~NetworkManager(void) {
for(auto const& [ip, machine] : _world_machines) { for(auto& pair : _active_machines) {
delete machine; _save_machine_vfs(pair.second.machine.get());
} }
stop(); stop();
} }
@ -76,13 +68,10 @@ void NetworkManager::start_accept(void) {
auto new_connection = auto new_connection =
std::make_shared<net::TcpConnection>(_io_context, std::move(socket)); std::make_shared<net::TcpConnection>(_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++; uint32_t player_id = _next_player_id++;
Machine* player_machine = _machine_manager.create_machine(player_id, "player.home", auto new_player = std::make_unique<Player>(player_id, nullptr, _db_manager.get(),
"player"); &_machine_manager, this);
auto new_player = std::make_unique<Player>(player_id, player_machine,
_world_machines, _db_manager.get(),
&_machine_manager);
Player* new_player_ptr = new_player.get(); Player* new_player_ptr = new_player.get();
_players[player_id] = std::move(new_player); _players[player_id] = std::move(new_player);
new_connection->set_id(player_id); new_connection->set_id(player_id);
@ -123,12 +112,11 @@ void NetworkManager::on_message(std::shared_ptr<net::TcpConnection> connection,
if(args.size() == 3) { if(args.size() == 3) {
if(_db_manager->create_player(args[0], args[1], args[2], if(_db_manager->create_player(args[0], args[1], args[2],
_machine_manager.get_vfs_template())) { _machine_manager.get_vfs_template())) {
long long machine_id = _db_manager->players().get_home_machine_id(args[0]); long long home_machine_id = _db_manager->players().get_home_machine_id(args[0]);
Machine* home_machine = _machine_manager.load_machine(machine_id, _db_manager.get()); Machine* home_machine = _get_or_load_machine(home_machine_id);
delete player->cmd_processor; /* Delete old command processor. */ delete player->cmd_processor; /* Delete old command processor. */
player->cmd_processor = new CommandProcessor(home_machine, _world_machines, player->cmd_processor = new CommandProcessor(home_machine, _db_manager.get(),
_db_manager.get(), &_machine_manager, this);
&_machine_manager);
player->state = PlayerState::ACTIVE; player->state = PlayerState::ACTIVE;
connection->send(net_protocol::build_message(net_protocol::Opcode::S2C_CREATE_ACCOUNT_SUCCESS)); connection->send(net_protocol::build_message(net_protocol::Opcode::S2C_CREATE_ACCOUNT_SUCCESS));
/* send initial prompt. */ /* send initial prompt. */
@ -144,22 +132,20 @@ void NetworkManager::on_message(std::shared_ptr<net::TcpConnection> connection,
case net_protocol::Opcode::C2S_LOGIN: case net_protocol::Opcode::C2S_LOGIN:
if(args.size() == 2) { if(args.size() == 2) {
if(_db_manager->players().authenticate(args[0], args[1])) { if(_db_manager->players().authenticate(args[0], args[1])) {
long long machine_id = _db_manager->players().get_home_machine_id(args[0]); long long home_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 = _get_or_load_machine(home_machine_id);
Machine* home_machine = _machine_manager.load_machine(machine_id, _db_manager.get()); delete player->cmd_processor; /* Delete old command processor. */
delete player->cmd_processor; /* Delete old command processor. */ player->cmd_processor = new CommandProcessor(home_machine, _db_manager.get(),
player->cmd_processor = new CommandProcessor(home_machine, _world_machines, &_machine_manager, this);
_db_manager.get(), &_machine_manager); player->state = PlayerState::ACTIVE;
player->state = PlayerState::ACTIVE;
connection->send(net_protocol::build_message(net_protocol::Opcode::S2C_LOGIN_SUCCESS)); connection->send(net_protocol::build_message(net_protocol::Opcode::S2C_LOGIN_SUCCESS));
/* Send initial prompt. */ /* Send initial prompt. */
std::string prompt = "\n" + get_full_path(player->cmd_processor->get_current_dir()); 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, connection->send(net_protocol::build_message(net_protocol::Opcode::S2C_COMMAND_RESPONSE,
{prompt})); {prompt}));
} else { } else {
connection->send(net_protocol::build_message(net_protocol::Opcode::S2C_LOGIN_FAIL, connection->send(net_protocol::build_message(net_protocol::Opcode::S2C_LOGIN_FAIL,
{"Invalid username or password."})); {"Invalid username or password."}));
} }
} }
break; break;
@ -214,6 +200,20 @@ void NetworkManager::on_message(std::shared_ptr<net::TcpConnection> connection,
void NetworkManager::on_disconnect(std::shared_ptr<net::TcpConnection> connection) { void NetworkManager::on_disconnect(std::shared_ptr<net::TcpConnection> connection) {
uint32_t player_id = connection->get_id(); 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); fprintf(stderr, "[Player %u] Disconnected.\n", player_id);
_connections.erase( _connections.erase(
std::remove(_connections.begin(), _connections.end(), connection), _connections.end()); std::remove(_connections.begin(), _connections.end(), connection), _connections.end());
@ -221,6 +221,67 @@ void NetworkManager::on_disconnect(std::shared_ptr<net::TcpConnection> connectio
_players.erase(player_id); _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<Machine>(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, &current_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) { void NetworkManager::_seed_npc_machines(void) {
int npc_count = _db_manager->machines().get_npc_count(); int npc_count = _db_manager->machines().get_npc_count();

View File

@ -10,11 +10,17 @@
#include "asio/io_context.hpp" #include "asio/io_context.hpp"
#include "net/tcp_connection.h" #include "net/tcp_connection.h"
#include "player.h" #include "player.h"
#include "i_network_bridge.h"
#include "machine_manager.h" #include "machine_manager.h"
class DatabaseManager; class DatabaseManager;
class NetworkManager { struct CachedMachine {
std::unique_ptr<Machine> machine;
int ref_count = 0;
};
class NetworkManager : public INetworkBridge {
public: public:
NetworkManager(const std::string& db_path); NetworkManager(const std::string& db_path);
~NetworkManager(void); ~NetworkManager(void);
@ -22,11 +28,17 @@ public:
void start(uint16_t port); void start(uint16_t port);
void stop(void); void stop(void);
Machine* get_machine_by_ip(const std::string& ip) override;
void release_machine(long long machine_id) override;
private: private:
void start_accept(void); void start_accept(void);
void on_message(std::shared_ptr<net::TcpConnection> connection, void on_message(std::shared_ptr<net::TcpConnection> connection,
const std::string& message); const std::string& message);
void on_disconnect(std::shared_ptr<net::TcpConnection> connection); void on_disconnect(std::shared_ptr<net::TcpConnection> 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); void _seed_npc_machines(void);
@ -36,9 +48,12 @@ private:
std::deque<std::shared_ptr<net::TcpConnection>> _connections; std::deque<std::shared_ptr<net::TcpConnection>> _connections;
std::unordered_map<uint32_t, std::unique_ptr<Player>> _players; std::unordered_map<uint32_t, std::unique_ptr<Player>> _players;
std::map<long long, CachedMachine> _active_machines;
uint32_t _next_player_id = 1; uint32_t _next_player_id = 1;
std::map<std::string, Machine*> _world_machines; /* For NPC's. */
std::unique_ptr<DatabaseManager> _db_manager; std::unique_ptr<DatabaseManager> _db_manager;
MachineManager _machine_manager; MachineManager _machine_manager;
protected:
Machine* _get_or_load_machine(long long machine_id);
}; };

View File

@ -1,25 +1,20 @@
#include "player.h" #include "player.h"
#include "command_processor.h" #include "command_processor.h"
#include "db/database_manager.h" #include "db/database_manager.h"
#include "i_network_bridge.h"
#include "machine.h" #include "machine.h"
Player::Player(uint32_t new_id, Machine* home_machine, Player::Player(uint32_t new_id, Machine* home_machine,
std::map<std::string, Machine*>& world_machines, DatabaseManager* db_manager, DatabaseManager* db_manager,
MachineManager* machine_manager) : MachineManager* machine_manager,
INetworkBridge* network_bridge) :
id(new_id), id(new_id),
state(PlayerState::AUTHENTICATING) { 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) { Player::~Player(void) {
if(cmd_processor) { delete 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;
}
} }

View File

@ -1,11 +1,10 @@
#pragma once #pragma once
#include <cstdint> #include <cstdint>
#include <map>
#include <string>
#include "db/database_manager.h" #include "db/database_manager.h"
#include "command_processor.h" #include "command_processor.h"
#include "i_network_bridge.h"
#include "machine.h" #include "machine.h"
#include "machine_manager.h" #include "machine_manager.h"
@ -16,9 +15,8 @@ enum class PlayerState {
class Player { class Player {
public: public:
Player(uint32_t id, Machine* home_machine, std::map<std::string, Player(uint32_t id, Machine* home_machine, DatabaseManager* db_manager,
Machine*>& world_machines, DatabaseManager* db_manager, MachineManager* machine_manager, INetworkBridge* network_bridge);
MachineManager* machine_manager);
~Player(void); ~Player(void);
uint32_t id; uint32_t id;