From 651f7c415e72270f3ab878847a5907fce0bccf40 Mon Sep 17 00:00:00 2001 From: Ritchie Cunningham Date: Mon, 20 Oct 2025 20:16:16 +0100 Subject: [PATCH] [Fix] Implement independent terminal sessions. Closes #1 --- client/src/game_state.cpp | 135 ++++++++++++------ client/src/game_state.h | 9 +- client/src/terminal.cpp | 13 +- client/src/terminal.h | 3 + client/src/ui/desktop.cpp | 43 +++++- client/src/ui/desktop.h | 20 ++- common/src/lua_api.cpp | 22 +-- common/src/lua_api.h | 22 +-- common/src/lua_processor.cpp | 8 +- common/src/lua_processor.h | 6 +- common/src/net/message_protocol.h | 16 ++- .../{command_processor.cpp => session.cpp} | 34 ++--- common/src/{command_processor.h => session.h} | 6 +- server/src/network_manager.cpp | 134 +++++++++-------- server/src/network_manager.h | 3 +- server/src/player.cpp | 12 +- server/src/player.h | 8 +- 17 files changed, 306 insertions(+), 188 deletions(-) rename common/src/{command_processor.cpp => session.cpp} (71%) rename common/src/{command_processor.h => session.h} (89%) diff --git a/client/src/game_state.cpp b/client/src/game_state.cpp index 0846eff..4d7b79f 100644 --- a/client/src/game_state.cpp +++ b/client/src/game_state.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -25,9 +26,13 @@ void GameState::_init_desktop(void) { _desktop = std::make_unique(_screen_width, _screen_height, this); auto term = std::make_unique(this); + term->set_session_id(_initial_session_id); + auto term_window = std::make_unique("Terminal", 100, 100, 800, 500); + UIWindow* window_ptr = term_window.get(); term_window->set_content(std::move(term)); _desktop->add_window(std::move(term_window)); + _desktop->register_session(_initial_session_id, window_ptr); } void GameState::_run_server(void) { @@ -52,7 +57,8 @@ GameState::GameState(void) : _screen_width(0), _screen_height(0), _is_single_player(false), - _show_debug_overlay(false) {_debug_overlay = std::make_unique();} + _show_debug_overlay(false), + _initial_session_id(0) {_debug_overlay = std::make_unique();} GameState::~GameState(void) = default; @@ -170,6 +176,12 @@ void GameState::update(float dt, int draw_calls, int shape_verts, int text_verts switch(opcode) { case net_protocol::Opcode::S2C_LOGIN_SUCCESS: case net_protocol::Opcode::S2C_CREATE_ACCOUNT_SUCCESS: + /* Don't switch screen yet, wait for the session ID. */ + break; + case net_protocol::Opcode::S2C_SESSION_CREATED: + if(!args.empty()) { + _initial_session_id = std::stoul(args[0]); + } _current_screen = Screen::BOOTING; _login_screen.reset(); /* Free mem. */ _boot_sequence = std::make_unique(); @@ -210,57 +222,79 @@ void GameState::update(float dt, int draw_calls, int shape_verts, int text_verts net_protocol::parse_message(server_msg, opcode, args); switch(opcode) { + case net_protocol::Opcode::S2C_SESSION_CREATED: + if(!args.empty()) { + uint32_t session_id = std::stoul(args[0]); + UIWindow* new_term = _desktop->get_window_awaiting_session_id(); + if(new_term) { + Terminal* term = dynamic_cast(new_term->get_content()); + if(term) { + term->set_session_id(session_id); + _desktop->register_session(session_id, new_term); + } + } + } + break; case net_protocol::Opcode::S2C_FILE_DATA: - if(args.size() == 2) { - auto editor = std::make_unique(args[0]); - editor->set_buffer_content(args[1]); - auto editor_window = std::make_unique(args[0].c_str(), + if(args.size() == 3) { + uint32_t session_id = std::stoul(args[0]); + auto editor = std::make_unique(args[1]); + editor->set_buffer_content(args[2]); + auto editor_window = std::make_unique(args[1].c_str(), 200, 200, 600, 400); editor_window->set_content(std::move(editor)); _desktop->add_window(std::move(editor_window)); } break; case net_protocol::Opcode::S2C_DISCONNECT: { - IWindowContent* content = _desktop->get_focused_window() ? - _desktop->get_focused_window()->get_content() : nullptr; - Terminal* terminal = dynamic_cast(content); - - if(terminal) { - terminal->add_history("Connection closed."); + if(!args.empty()) { + uint32_t session_id = std::stoul(args[0]); + UIWindow* window = _desktop->get_window_by_session_id(session_id); + if(window) { + Terminal* terminal = dynamic_cast(window->get_content()); + if(terminal) { + terminal->add_history("Connection closed."); + } + } } } break; case net_protocol::Opcode::S2C_CLOSE_WINDOW: { - if(_desktop) { - UIWindow* focused_window = _desktop->get_focused_window(); - if(focused_window) { focused_window->close(); } + if(!args.empty()) { + uint32_t session_id = std::stoul(args[0]); + UIWindow* window = _desktop->get_window_by_session_id(session_id); + if(window) { + window->close(); + } } } break; case net_protocol::Opcode::S2C_COMMAND_RESPONSE: { - if(!args.empty()) { - IWindowContent* content = _desktop->get_focused_window() ? - _desktop->get_focused_window()->get_content() : nullptr; - Terminal* terminal = dynamic_cast(content); - if(!terminal) continue; + if(args.size() == 2) { + uint32_t session_id = std::stoul(args[0]); + UIWindow* window = _desktop->get_window_by_session_id(session_id); + if(window) { + Terminal* terminal = dynamic_cast(window->get_content()); + if(terminal) { + /* Server sends "output\nprompt", split them. */ + size_t last_newline = args[1].find_last_of('\n'); + if(last_newline != std::string::npos) { + std::string prompt = args[1].substr(last_newline+1); + terminal->set_prompt(prompt); - /* Server sends "output\nprompt", split them. */ - size_t last_newline = args[0].find_last_of('\n'); - if(last_newline != std::string::npos) { - std::string prompt = args[0].substr(last_newline+1); - terminal->set_prompt(prompt); - - std::string output = args[0].substr(0, last_newline); - if(!output.empty()) { - /* Split multiline output and push each line to history. */ - std::stringstream ss(output); - std::string line; - while(std::getline(ss, line, '\n')) { - terminal->add_history(line); + std::string output = args[1].substr(0, last_newline); + if(!output.empty()) { + /* Split multiline output and push each line to history. */ + std::stringstream ss(output); + std::string line; + while(std::getline(ss, line, '\n')) { + terminal->add_history(line); + } + } + } else { + terminal->add_history(args[1]); } } - } else { - terminal->add_history(args[0]); } } } @@ -275,13 +309,6 @@ void GameState::update(float dt, int draw_calls, int shape_verts, int text_verts } } -void GameState::send_network_command(const std::string& command) { - if(_network && _network->is_connected()) { - _network->send(net_protocol::build_message(net_protocol::Opcode::C2S_COMMAND, - {command})); - } -} - void GameState::render(const RenderContext& context) { switch(_current_screen) { case Screen::MAIN_MENU: @@ -311,12 +338,26 @@ void GameState::render(const RenderContext& context) { } } -void GameState::send_file_write_request(const std::string& path, const std::string& content) { - _network->send(net_protocol::build_message(net_protocol::Opcode::C2S_WRITE_FILE, - {path, content})); +void GameState::send_network_command(uint32_t session_id, const std::string& command) { + if(_network && _network->is_connected()) { + _network->send(net_protocol::build_message(net_protocol::Opcode::C2S_COMMAND, + {std::to_string(session_id), command})); + } } -void GameState::send_file_read_request(const std::string& path) { - _network->send(net_protocol::build_message(net_protocol::Opcode::C2S_READ_FILE, - {path})); +void GameState::send_file_write_request(uint32_t session_id, const std::string& path, + const std::string& content) { + _network->send(net_protocol::build_message(net_protocol::Opcode::C2S_WRITE_FILE, + {std::to_string(session_id), path, content})); +} + +void GameState::send_file_read_request(uint32_t session_id, const std::string& path) { + _network->send(net_protocol::build_message(net_protocol::Opcode::C2S_READ_FILE, + {std::to_string(session_id), path})); +} + +void GameState::send_create_session_request(void) { + if(_network && _network->is_connected()) { + _network->send(net_protocol::build_message(net_protocol::Opcode::C2S_CREATE_SESSION)); + } } diff --git a/client/src/game_state.h b/client/src/game_state.h index b3ac0ff..185922c 100644 --- a/client/src/game_state.h +++ b/client/src/game_state.h @@ -33,9 +33,10 @@ public: void render(const RenderContext& context); /* Public network interface for UI components. */ - void send_network_command(const std::string& command); - void send_file_write_request(const std::string& path, const std::string& content); - void send_file_read_request(const std::string& path); + void send_network_command(uint32_t session_id, const std::string& command); + void send_file_write_request(uint32_t session_id, const std::string& path, const std::string& content); + void send_file_read_request(uint32_t session_id, const std::string& path); + void send_create_session_request(void); private: std::unique_ptr _network; @@ -49,8 +50,8 @@ private: int _screen_width; int _screen_height; bool _is_single_player; + uint32_t _initial_session_id; void _init_desktop(void); - void _send_network_command(const std::string& command); void _run_server(void); }; diff --git a/client/src/terminal.cpp b/client/src/terminal.cpp index f41ff61..f072486 100644 --- a/client/src/terminal.cpp +++ b/client/src/terminal.cpp @@ -12,7 +12,8 @@ Terminal::Terminal(GameState* game_state) : _game_state(game_state), _should_close(false), _command_history_index(0), - _scroll_offset(0), _prompt(""), _pending_action({ActionType::NONE}) { + _scroll_offset(0), _prompt(""), _pending_action({ActionType::NONE}), + _session_id(0) { _input_view = std::make_unique(&_input_buffer, false, false); } @@ -52,6 +53,14 @@ WindowAction Terminal::get_pending_action(void) { return action; } +void Terminal::set_session_id(uint32_t id) { + _session_id = id; +} + +uint32_t Terminal::get_session_id(void) const { + return _session_id; +} + void Terminal::_on_ret_press(void) { std::string command = _input_buffer.get_line(0); if(!command.empty()) { @@ -79,7 +88,7 @@ void Terminal::_on_ret_press(void) { } _history.push_back(_prompt + "> " + command); - _game_state->send_network_command(command); + _game_state->send_network_command(_session_id, command); } void Terminal::handle_input(SDL_Event* event, int window_x, int window_y, int window_gl_y) { diff --git a/client/src/terminal.h b/client/src/terminal.h index 988f757..b113008 100644 --- a/client/src/terminal.h +++ b/client/src/terminal.h @@ -26,6 +26,8 @@ public: void set_prompt(const std::string& prompt); bool should_close(void) override; WindowAction get_pending_action(void) override; + void set_session_id(uint32_t id); + uint32_t get_session_id(void) const; private: void _on_ret_press(void); @@ -39,5 +41,6 @@ private: GameState* _game_state; TextBuffer _input_buffer; WindowAction _pending_action; + uint32_t _session_id; std::unique_ptr _input_view; }; diff --git a/client/src/ui/desktop.cpp b/client/src/ui/desktop.cpp index 888438a..2e66ff0 100644 --- a/client/src/ui/desktop.cpp +++ b/client/src/ui/desktop.cpp @@ -30,6 +30,7 @@ Desktop::Desktop(int screen_width, int screen_height, GameState* game_state) { _taskbar = std::make_unique(screen_width, screen_height); _game_state= game_state; _focused_window = nullptr; + _window_awaiting_session_id = nullptr; _launcher_is_open = false; _launcher = std::make_unique(5, 0, 200); /* Tmp y-coord. */ int launcher_y = screen_height - _taskbar->get_height() - _launcher->get_height(); @@ -135,9 +136,11 @@ void Desktop::handle_event(SDL_Event* event, int screen_width, int screen_height if(app_to_launch == "Terminal") { auto term = std::make_unique(_game_state); auto term_window = std::make_unique("Terminal", 150, 150, 800, 500); + _window_awaiting_session_id = term_window.get(); term_window->set_content(std::move(term)); add_window(std::move(term_window)); _launcher_is_open = false; + _game_state->send_create_session_request(); } else if(app_to_launch == "Editor") { auto editor = std::make_unique(); auto editor_window = std::make_unique("Editor", 200, 200, 600, 400); @@ -180,13 +183,21 @@ void Desktop::update(float dt, int screen_width, int screen_height) { IWindowContent* content = _focused_window->get_content(); if(content) { WindowAction action = content->get_pending_action(); + uint32_t session_id = 0; + if(auto term = dynamic_cast(content)) { + session_id = term->get_session_id(); + } switch(action.type) { case ActionType::WRITE_FILE: { - _game_state->send_file_write_request(action.payload1, action.payload2); + if(session_id != 0) { + _game_state->send_file_write_request(session_id, action.payload1, action.payload2); + } break; } case ActionType::READ_FILE: { - _game_state->send_file_read_request(action.payload1); + if(session_id != 0) { + _game_state->send_file_read_request(session_id, action.payload1); + } break; } default: @@ -203,6 +214,16 @@ void Desktop::update(float dt, int screen_width, int screen_height) { _windows.erase(std::remove_if(_windows.begin(), _windows.end(), [this](const std::unique_ptr& w) { if (w->should_close()) { + /* Also remove from session map. */ + uint32_t session_to_remove = 0; + for(auto const& [sid, win] : _session_windows) { + if(win == w.get()) { + session_to_remove = sid; + break; + } + } + if(session_to_remove != 0) + _session_windows.erase(session_to_remove); _taskbar->remove_window(w.get()); if (w.get() == _focused_window) { _focused_window = nullptr; @@ -214,10 +235,28 @@ void Desktop::update(float dt, int screen_width, int screen_height) { _windows.end()); } +void Desktop::register_session(uint32_t session_id, UIWindow* window) { + _session_windows[session_id] = window; + if(_window_awaiting_session_id == window) { + _window_awaiting_session_id = nullptr; + } +} + +UIWindow* Desktop::get_window_by_session_id(uint32_t session_id) { + if(_session_windows.count(session_id)) { + return _session_windows.at(session_id); + } + return nullptr; +} + UIWindow* Desktop::get_focused_window(void) { return _focused_window; } +UIWindow* Desktop::get_window_awaiting_session_id(void) { + return _window_awaiting_session_id; +} + void Desktop::render(const RenderContext& context) { /* Pass 1: Background. */ context.ui_renderer->begin_text(); diff --git a/client/src/ui/desktop.h b/client/src/ui/desktop.h index d930abf..fced735 100644 --- a/client/src/ui/desktop.h +++ b/client/src/ui/desktop.h @@ -2,6 +2,7 @@ #include #include +#include #include #include "gfx/types.h" @@ -31,7 +32,10 @@ public: void update(float dt, int screen_width, int screen_height); void render(const RenderContext& context); + void register_session(uint32_t session_id, UIWindow* window); + UIWindow* get_window_by_session_id(uint32_t session_id); UIWindow* get_focused_window(void); + UIWindow* get_window_awaiting_session_id(void); private: void _set_focused_window(UIWindow* window); @@ -39,11 +43,13 @@ private: void _update_wallpaper(float dt, int screen_width, int screen_height); std::vector> _windows; - std::unique_ptr _taskbar; - std::unique_ptr _launcher; - UIWindow* _focused_window; - GameState* _game_state; - std::vector _background_text; - std::vector _snippets; - bool _launcher_is_open; + std::unique_ptr _taskbar; + std::unique_ptr _launcher; + UIWindow* _focused_window; + UIWindow* _window_awaiting_session_id; + std::map _session_windows; + GameState* _game_state; + std::vector _background_text; + std::vector _snippets; + bool _launcher_is_open; }; diff --git a/common/src/lua_api.cpp b/common/src/lua_api.cpp index d546851..1ca60fd 100644 --- a/common/src/lua_api.cpp +++ b/common/src/lua_api.cpp @@ -4,17 +4,17 @@ #include "i_network_bridge.h" #include "lua_api.h" -#include "command_processor.h" +#include "session.h" #include "machine.h" #include "vfs.h" namespace api { -vfs_node* get_current_dir(CommandProcessor& context) { +vfs_node* get_current_dir(Session& context) { return context.get_current_dir(); } -std::string rm(CommandProcessor& context, const std::string& filename) { +std::string rm(Session& context, const std::string& filename) { vfs_node* current_dir = context.get_current_dir(); auto it = current_dir->children.find(filename); @@ -30,7 +30,7 @@ std::string rm(CommandProcessor& context, const std::string& filename) { return ""; } -std::string write_file(CommandProcessor& context, const std::string& filename, +std::string write_file(Session& context, const std::string& filename, const std::string& content) { vfs_node* current_dir = context.get_current_dir(); auto it = current_dir->children.find(filename); @@ -50,7 +50,7 @@ std::string write_file(CommandProcessor& context, const std::string& filename, return ""; } -std::string cd(CommandProcessor& context, const std::string& path) { +std::string cd(Session& context, const std::string& path) { vfs_node* current_dir = context.get_current_dir(); if(path == "..") { @@ -68,7 +68,7 @@ std::string cd(CommandProcessor& context, const std::string& path) { return ""; } -std::string ls(CommandProcessor& context) { +std::string ls(Session& context) { vfs_node* dir = context.get_current_dir(); if(dir->type != DIR_NODE) { return "ls: not a directory"; @@ -85,7 +85,7 @@ std::string ls(CommandProcessor& context) { return ss.str(); } -std::string ssh(CommandProcessor& context, const std::string& ip) { +std::string ssh(Session& context, const std::string& ip) { INetworkBridge* bridge = context.get_network_bridge(); Machine* target_machine = bridge->get_machine_by_ip(ip); @@ -97,7 +97,7 @@ std::string ssh(CommandProcessor& context, const std::string& ip) { } } -std::string nmap(CommandProcessor& context, const std::string& ip) { +std::string nmap(Session& context, const std::string& 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; @@ -117,7 +117,7 @@ std::string nmap(CommandProcessor& context, const std::string& ip) { return ss.str(); } -std::string disconnect(CommandProcessor& context) { +std::string disconnect(Session& context) { Machine* current_machine = context.get_session_machine(); if(current_machine != context.get_home_machine()) { INetworkBridge* bridge = context.get_network_bridge(); @@ -127,7 +127,7 @@ std::string disconnect(CommandProcessor& context) { return "Connection closed."; } -std::string close_terminal(CommandProcessor& context) { +std::string close_terminal(Session& context) { return "__CLOSE_CONNECTION__"; } @@ -145,7 +145,7 @@ ScpPath parse_scp_path(const std::string& arg) { } } -std::string scp(CommandProcessor& context, const std::string& source_arg, +std::string scp(Session& context, const std::string& source_arg, const std::string& dest_arg) { ScpPath source_path = parse_scp_path(source_arg); ScpPath dest_path = parse_scp_path(dest_arg); diff --git a/common/src/lua_api.h b/common/src/lua_api.h index d469695..57e7a9c 100644 --- a/common/src/lua_api.h +++ b/common/src/lua_api.h @@ -3,27 +3,27 @@ #include #include "vfs.h" -class CommandProcessor; +class Session; class NetworkManager; namespace api { /* FILESYSTEM ACTIONS. */ -vfs_node* get_current_dir(CommandProcessor& context); -std::string rm(CommandProcessor& context, const std::string& filename); -std::string write_file(CommandProcessor& context, const std::string& filename, +vfs_node* get_current_dir(Session& context); +std::string rm(Session& context, const std::string& filename); +std::string write_file(Session& context, const std::string& filename, const std::string& content); -std::string ls(CommandProcessor& context); -std::string cd(CommandProcessor& context, const std::string& path); -std::string scp(CommandProcessor& context, const std::string& source, +std::string ls(Session& context); +std::string cd(Session& context, const std::string& path); +std::string scp(Session& context, const std::string& source, const std::string& destination); /* NETWORK ACTIONS. */ -std::string ssh(CommandProcessor& context, const std::string& ip); -std::string nmap(CommandProcessor& context, const std::string& ip); -std::string disconnect(CommandProcessor& context); +std::string ssh(Session& context, const std::string& ip); +std::string nmap(Session& context, const std::string& ip); +std::string disconnect(Session& context); /* SYSTEM ACTIONS. */ -std::string close_terminal(CommandProcessor& context); +std::string close_terminal(Session& context); } /* namespace api */ diff --git a/common/src/lua_processor.cpp b/common/src/lua_processor.cpp index a991b2b..a1da61b 100644 --- a/common/src/lua_processor.cpp +++ b/common/src/lua_processor.cpp @@ -5,10 +5,10 @@ #include "lua_processor.h" #include "lua_api.h" -#include "command_processor.h" +#include "session.h" #include "vfs.h" -LuaProcessor::LuaProcessor(CommandProcessor& context) { +LuaProcessor::LuaProcessor(Session& context) { _lua.open_libraries(sol::lib::base, sol::lib::string, sol::lib::io, sol::lib::table); /* Expose vfs_node struct members to Lua. */ @@ -19,7 +19,7 @@ LuaProcessor::LuaProcessor(CommandProcessor& context) { "content", &vfs_node::content); /* Expose CommandProcessor to Lua. DON'T ALLOW SCRIPTS TO CREATE IT THOUGH! */ - _lua.new_usertype("CommandProcessor", sol::no_constructor); + _lua.new_usertype("Session", sol::no_constructor); /* Create the 'bettola' API table. */ sol::table bettola_api = _lua.create_named_table("bettola"); @@ -37,7 +37,7 @@ LuaProcessor::LuaProcessor(CommandProcessor& context) { LuaProcessor::~LuaProcessor(void) {} -sol::object LuaProcessor::execute(const std::string& script, CommandProcessor& context, +sol::object LuaProcessor::execute(const std::string& script, Session& context, const std::vector& args, bool is_remote) { try { /* Pass C++ objects/points into the Lua env. */ diff --git a/common/src/lua_processor.h b/common/src/lua_processor.h index 39a21b0..1b7fe94 100644 --- a/common/src/lua_processor.h +++ b/common/src/lua_processor.h @@ -5,15 +5,15 @@ #include #include -class CommandProcessor; +class Session; class LuaProcessor { public: - LuaProcessor(CommandProcessor& context); + LuaProcessor(Session& context); ~LuaProcessor(void); /* Executes a string of lua code and returns result as a string. */ - sol::object execute(const std::string& script, CommandProcessor& context, + sol::object execute(const std::string& script, Session& context, const std::vector& args, bool is_remote); private: sol::state _lua; diff --git a/common/src/net/message_protocol.h b/common/src/net/message_protocol.h index 9cd5aa7..e747be1 100644 --- a/common/src/net/message_protocol.h +++ b/common/src/net/message_protocol.h @@ -16,19 +16,21 @@ enum class Opcode : uint8_t { /* Client -> Server messages. */ C2S_CREATE_ACCOUNT, C2S_LOGIN, - C2S_COMMAND, - C2S_WRITE_FILE, - C2S_READ_FILE, + C2S_CREATE_SESSION, + C2S_COMMAND, /* args: [session_id, command] */ + C2S_WRITE_FILE, /* args: [session_id, path, content] */ + C2S_READ_FILE, /* args: [session_id, path] */ /* Server -> Client messages. */ S2C_CREATE_ACCOUNT_SUCCESS, S2C_CREATE_ACCOUNT_FAIL, S2C_LOGIN_SUCCESS, S2C_LOGIN_FAIL, - S2C_COMMAND_RESPONSE, - S2C_FILE_DATA, - S2C_DISCONNECT, - S2C_CLOSE_WINDOW, + S2C_SESSION_CREATED, /* args: [session_id] */ + S2C_COMMAND_RESPONSE, /* args: [session_id, response] */ + S2C_FILE_DATA, /* args: [session_id, path, content] */ + S2C_DISCONNECT, /* args: [session_id] */ + S2C_CLOSE_WINDOW, /* args: [session_id] */ }; /** diff --git a/common/src/command_processor.cpp b/common/src/session.cpp similarity index 71% rename from common/src/command_processor.cpp rename to common/src/session.cpp index 95a7551..306cfbb 100644 --- a/common/src/command_processor.cpp +++ b/common/src/session.cpp @@ -1,7 +1,7 @@ #include #include -#include "command_processor.h" +#include "session.h" #include "db/database_manager.h" #include "i_network_bridge.h" #include "lua_api.h" @@ -22,10 +22,10 @@ vfs_node* find_node_by_id(vfs_node* root, long long id) { return nullptr; } -CommandProcessor::CommandProcessor(Machine* home_machine, - DatabaseManager* db_manager, - MachineManager* machine_manager, - INetworkBridge* network_bridge) : +Session::Session(Machine* home_machine, + DatabaseManager* db_manager, + MachineManager* machine_manager, + INetworkBridge* network_bridge) : _machine_manager(machine_manager), _db_manager(db_manager), _network_bridge(network_bridge), @@ -35,40 +35,40 @@ CommandProcessor::CommandProcessor(Machine* home_machine, if(home_machine) _current_dir = home_machine->vfs_root; } -CommandProcessor::~CommandProcessor(void) { +Session::~Session(void) { } -vfs_node* CommandProcessor::get_current_dir(void) { +vfs_node* Session::get_current_dir(void) { return _current_dir; } -Machine* CommandProcessor::get_home_machine(void) { return _home_machine; } -Machine* CommandProcessor::get_session_machine(void) { return _session_machine; } +Machine* Session::get_home_machine(void) { return _home_machine; } +Machine* Session::get_session_machine(void) { return _session_machine; } -DatabaseManager* CommandProcessor::get_db_manager(void) { +DatabaseManager* Session::get_db_manager(void) { return _db_manager; } -MachineManager* CommandProcessor::get_machine_manager(void) { +MachineManager* Session::get_machine_manager(void) { return _machine_manager; } -INetworkBridge* CommandProcessor::get_network_bridge(void) { +INetworkBridge* Session::get_network_bridge(void) { return _network_bridge; } -void CommandProcessor::set_current_dir(vfs_node* node) { +void Session::set_current_dir(vfs_node* node) { _current_dir = node; } -void CommandProcessor::set_session_machine(Machine* machine) { +void Session::set_session_machine(Machine* machine) { _session_machine = machine; if(_session_machine) { _current_dir = _session_machine->vfs_root; } } -std::string CommandProcessor::process_command(const std::string& command) { +std::string Session::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. @@ -113,11 +113,11 @@ std::string CommandProcessor::process_command(const std::string& command) { return "Unknown command: " + command_name + "\n"; } -std::string CommandProcessor::write_file(const std::string& path, const std::string& content) { +std::string Session::write_file(const std::string& path, const std::string& content) { return api::write_file(*this, path, content); } -std::string CommandProcessor::read_file(const std::string& path) { +std::string Session::read_file(const std::string& path) { vfs_node* root = get_session_machine()->vfs_root; vfs_node* node = find_node_by_path(root, path); if(node && node->type == FILE_NODE) { diff --git a/common/src/command_processor.h b/common/src/session.h similarity index 89% rename from common/src/command_processor.h rename to common/src/session.h index e7b611d..ffa5cb7 100644 --- a/common/src/command_processor.h +++ b/common/src/session.h @@ -9,11 +9,11 @@ class LuaProcessor; -class CommandProcessor { +class Session { public: - CommandProcessor(Machine* home_machine, DatabaseManager* db_manager, + Session(Machine* home_machine, DatabaseManager* db_manager, MachineManager* machine_manager, INetworkBridge* network_bridge); - ~CommandProcessor(void); + ~Session(void); std::string process_command(const std::string& command); std::string write_file(const std::string& path, const std::string& content); diff --git a/server/src/network_manager.cpp b/server/src/network_manager.cpp index c101329..996039a 100644 --- a/server/src/network_manager.cpp +++ b/server/src/network_manager.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include "network_manager.h" #include "net/message_protocol.h" @@ -9,7 +10,7 @@ #include "asio/error_code.hpp" #include "asio/ip/tcp.hpp" -#include "command_processor.h" +#include "session.h" #include "player.h" #include "machine.h" #include "net/tcp_connection.h" @@ -112,37 +113,42 @@ 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 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_CREATE_ACCOUNT_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_CREATE_ACCOUNT_FAIL, - {"Username already exists."})); + long long home_machine_id = _db_manager->players().get_home_machine_id(args[0]); + Machine* home_machine = _get_or_load_machine(home_machine_id); + player->state = PlayerState::ACTIVE; + + /* Create first session for player. */ + uint32_t session_id = _next_session_id++; + player->sessions[session_id] = std::make_unique(home_machine, + _db_manager.get(), + &_machine_manager, this); + + connection->send(net_protocol::build_message(net_protocol::Opcode::S2C_CREATE_ACCOUNT_SUCCESS)); + connection->send(net_protocol::build_message(net_protocol::Opcode::S2C_SESSION_CREATED, + {std::to_string(session_id)})); + } else { + connection->send(net_protocol::build_message(net_protocol::Opcode::S2C_CREATE_ACCOUNT_FAIL, + {"Username already exists."})); + } } - } - break; + break; case net_protocol::Opcode::C2S_LOGIN: if(args.size() == 2) { if(_db_manager->players().authenticate(args[0], args[1])) { 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; + + /* Create first session for player. */ + uint32_t session_id = _next_session_id++; + player->sessions[session_id] = std::make_unique(home_machine, + _db_manager.get(), + &_machine_manager, this); + 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})); + connection->send(net_protocol::build_message(net_protocol::Opcode::S2C_SESSION_CREATED, + {std::to_string(session_id)})); + } else { connection->send(net_protocol::build_message(net_protocol::Opcode::S2C_LOGIN_FAIL, {"Invalid username or password."})); @@ -159,36 +165,49 @@ void NetworkManager::on_message(std::shared_ptr connection, /* === PLAYER BECOMES ACTIVE HERE === */ switch(opcode) { + case net_protocol::Opcode::C2S_CREATE_SESSION: { + uint32_t session_id = _next_session_id++; + /* Find player's home machine to init the session. */ + Machine* home_machine = nullptr; + if(!player->sessions.empty()) { + home_machine = player->sessions.begin()->second->get_home_machine(); + } + player->sessions[session_id] = std::make_unique(home_machine, + _db_manager.get(), + &_machine_manager, this); + connection->send(net_protocol::build_message(net_protocol::Opcode::S2C_SESSION_CREATED, + {std::to_string(session_id)})); + break; + } case net_protocol::Opcode::C2S_WRITE_FILE: - if(args.size() == 2) { - fprintf(stderr, "[Player %u] Write file: '%s'\n", player->id, args[0].c_str()); - player->cmd_processor->write_file(args[0], args[1]); - /* Response not required for a file write. */ - } - break; + if(args.size() == 3) { + uint32_t session_id = std::stoul(args[0]); + if(player->sessions.count(session_id)) { + player->sessions.at(session_id)->write_file(args[1], args[2]); + } + } + break; case net_protocol::Opcode::C2S_READ_FILE: - if(!args.empty()) { - fprintf(stderr, "[Player %u] Read file: '%s'\n", player->id, args[0].c_str()); - std::string content = player->cmd_processor->read_file(args[0]); - /* Send the content back to the client. */ - connection->send(net_protocol::build_message(net_protocol::Opcode::S2C_FILE_DATA, - {args[0], content})); - } + if(args.size() == 2) { + uint32_t session_id = std::stoul(args[0]); + if(player->sessions.count(session_id)) { + std::string content = player->sessions.at(session_id)->read_file(args[1]); + connection->send(net_protocol::build_message(net_protocol::Opcode::S2C_FILE_DATA, + {args[0], args[1], content})); + } + } break; case net_protocol::Opcode::C2S_COMMAND: - if(!args.empty()) { - fprintf(stderr, "[Player %u] Command: '%s'\n", player->id, args[0].c_str()); - std::string response = player->cmd_processor->process_command(args[0]); - - if(response == "__CLOSE_CONNECTION__") { - connection->send(net_protocol::build_message(net_protocol::Opcode::S2C_CLOSE_WINDOW)); - return; + if(args.size() == 2) { + uint32_t session_id = std::stoul(args[0]); + if(player->sessions.count(session_id)) { + Session* session = player->sessions.at(session_id).get(); + std::string response = session->process_command(args[1]); + std::string new_prompt = get_full_path(session->get_current_dir()); + response += "\n" + new_prompt; + connection->send(net_protocol::build_message(net_protocol::Opcode::S2C_COMMAND_RESPONSE, + {args[0], response})); } - std::string new_prompt = get_full_path(player->cmd_processor->get_current_dir()); - response += "\n" + new_prompt; - - connection->send(net_protocol::build_message(net_protocol::Opcode::S2C_COMMAND_RESPONSE, - {response})); } break; default: @@ -202,15 +221,14 @@ void NetworkManager::on_disconnect(std::shared_ptr connectio 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); + if(player) { + for(auto const& [session_id, session] : player->sessions) { + Machine* home_machine = session->get_home_machine(); + Machine* session_machine = session->get_session_machine(); + if(home_machine) { release_machine(home_machine->id); } + if(session_machine && session_machine != home_machine) { + release_machine(session_machine->id); + } } } diff --git a/server/src/network_manager.h b/server/src/network_manager.h index 441fa9d..c4fa64d 100644 --- a/server/src/network_manager.h +++ b/server/src/network_manager.h @@ -49,7 +49,8 @@ private: std::deque> _connections; std::unordered_map> _players; std::map _active_machines; - uint32_t _next_player_id = 1; + uint32_t _next_player_id = 1; + uint32_t _next_session_id = 1; std::unique_ptr _db_manager; MachineManager _machine_manager; diff --git a/server/src/player.cpp b/server/src/player.cpp index 42ef65f..bfbfdc9 100644 --- a/server/src/player.cpp +++ b/server/src/player.cpp @@ -1,5 +1,5 @@ #include "player.h" -#include "command_processor.h" +#include "session.h" #include "db/database_manager.h" #include "i_network_bridge.h" #include "machine.h" @@ -9,12 +9,6 @@ Player::Player(uint32_t new_id, Machine* home_machine, MachineManager* machine_manager, INetworkBridge* network_bridge) : id(new_id), - state(PlayerState::AUTHENTICATING) { + state(PlayerState::AUTHENTICATING) {} - cmd_processor = new CommandProcessor(home_machine, db_manager, machine_manager, - network_bridge); -} - -Player::~Player(void) { - delete cmd_processor; -} +Player::~Player(void) {} diff --git a/server/src/player.h b/server/src/player.h index 37304ab..43d3860 100644 --- a/server/src/player.h +++ b/server/src/player.h @@ -1,9 +1,11 @@ #pragma once #include +#include +#include #include "db/database_manager.h" -#include "command_processor.h" +#include "session.h" #include "i_network_bridge.h" #include "machine.h" #include "machine_manager.h" @@ -21,5 +23,7 @@ public: uint32_t id; PlayerState state; - CommandProcessor* cmd_processor; /* Manages the VFS state for the remote session. */ + + /* Map of session IDs to their respective session objects. */ + std::unordered_map> sessions; };