[Fix] Implement independent terminal sessions.

Closes #1
This commit is contained in:
Ritchie Cunningham 2025-10-20 20:16:16 +01:00
parent b150c814fe
commit 651f7c415e
17 changed files with 306 additions and 188 deletions

View File

@ -1,6 +1,7 @@
#include <cstdio> #include <cstdio>
#include <memory> #include <memory>
#include <sstream> #include <sstream>
#include <string>
#include <thread> #include <thread>
#include <chrono> #include <chrono>
@ -25,9 +26,13 @@ void GameState::_init_desktop(void) {
_desktop = std::make_unique<Desktop>(_screen_width, _screen_height, this); _desktop = std::make_unique<Desktop>(_screen_width, _screen_height, this);
auto term = std::make_unique<Terminal>(this); auto term = std::make_unique<Terminal>(this);
term->set_session_id(_initial_session_id);
auto term_window = std::make_unique<UIWindow>("Terminal", 100, 100, 800, 500); auto term_window = std::make_unique<UIWindow>("Terminal", 100, 100, 800, 500);
UIWindow* window_ptr = term_window.get();
term_window->set_content(std::move(term)); term_window->set_content(std::move(term));
_desktop->add_window(std::move(term_window)); _desktop->add_window(std::move(term_window));
_desktop->register_session(_initial_session_id, window_ptr);
} }
void GameState::_run_server(void) { void GameState::_run_server(void) {
@ -52,7 +57,8 @@ GameState::GameState(void) :
_screen_width(0), _screen_width(0),
_screen_height(0), _screen_height(0),
_is_single_player(false), _is_single_player(false),
_show_debug_overlay(false) {_debug_overlay = std::make_unique<DebugOverlay>();} _show_debug_overlay(false),
_initial_session_id(0) {_debug_overlay = std::make_unique<DebugOverlay>();}
GameState::~GameState(void) = default; GameState::~GameState(void) = default;
@ -170,6 +176,12 @@ void GameState::update(float dt, int draw_calls, int shape_verts, int text_verts
switch(opcode) { switch(opcode) {
case net_protocol::Opcode::S2C_LOGIN_SUCCESS: case net_protocol::Opcode::S2C_LOGIN_SUCCESS:
case net_protocol::Opcode::S2C_CREATE_ACCOUNT_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; _current_screen = Screen::BOOTING;
_login_screen.reset(); /* Free mem. */ _login_screen.reset(); /* Free mem. */
_boot_sequence = std::make_unique<BootSequence>(); _boot_sequence = std::make_unique<BootSequence>();
@ -210,47 +222,67 @@ void GameState::update(float dt, int draw_calls, int shape_verts, int text_verts
net_protocol::parse_message(server_msg, opcode, args); net_protocol::parse_message(server_msg, opcode, args);
switch(opcode) { 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<Terminal*>(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: case net_protocol::Opcode::S2C_FILE_DATA:
if(args.size() == 2) { if(args.size() == 3) {
auto editor = std::make_unique<Editor>(args[0]); uint32_t session_id = std::stoul(args[0]);
editor->set_buffer_content(args[1]); auto editor = std::make_unique<Editor>(args[1]);
auto editor_window = std::make_unique<UIWindow>(args[0].c_str(), editor->set_buffer_content(args[2]);
auto editor_window = std::make_unique<UIWindow>(args[1].c_str(),
200, 200, 600, 400); 200, 200, 600, 400);
editor_window->set_content(std::move(editor)); editor_window->set_content(std::move(editor));
_desktop->add_window(std::move(editor_window)); _desktop->add_window(std::move(editor_window));
} }
break; break;
case net_protocol::Opcode::S2C_DISCONNECT: { case net_protocol::Opcode::S2C_DISCONNECT: {
IWindowContent* content = _desktop->get_focused_window() ? if(!args.empty()) {
_desktop->get_focused_window()->get_content() : nullptr; uint32_t session_id = std::stoul(args[0]);
Terminal* terminal = dynamic_cast<Terminal*>(content); UIWindow* window = _desktop->get_window_by_session_id(session_id);
if(window) {
Terminal* terminal = dynamic_cast<Terminal*>(window->get_content());
if(terminal) { if(terminal) {
terminal->add_history("Connection closed."); terminal->add_history("Connection closed.");
} }
} }
}
}
break; break;
case net_protocol::Opcode::S2C_CLOSE_WINDOW: { case net_protocol::Opcode::S2C_CLOSE_WINDOW: {
if(_desktop) { if(!args.empty()) {
UIWindow* focused_window = _desktop->get_focused_window(); uint32_t session_id = std::stoul(args[0]);
if(focused_window) { focused_window->close(); } UIWindow* window = _desktop->get_window_by_session_id(session_id);
if(window) {
window->close();
}
} }
} }
break; break;
case net_protocol::Opcode::S2C_COMMAND_RESPONSE: { case net_protocol::Opcode::S2C_COMMAND_RESPONSE: {
if(!args.empty()) { if(args.size() == 2) {
IWindowContent* content = _desktop->get_focused_window() ? uint32_t session_id = std::stoul(args[0]);
_desktop->get_focused_window()->get_content() : nullptr; UIWindow* window = _desktop->get_window_by_session_id(session_id);
Terminal* terminal = dynamic_cast<Terminal*>(content); if(window) {
if(!terminal) continue; Terminal* terminal = dynamic_cast<Terminal*>(window->get_content());
if(terminal) {
/* Server sends "output\nprompt", split them. */ /* Server sends "output\nprompt", split them. */
size_t last_newline = args[0].find_last_of('\n'); size_t last_newline = args[1].find_last_of('\n');
if(last_newline != std::string::npos) { if(last_newline != std::string::npos) {
std::string prompt = args[0].substr(last_newline+1); std::string prompt = args[1].substr(last_newline+1);
terminal->set_prompt(prompt); terminal->set_prompt(prompt);
std::string output = args[0].substr(0, last_newline); std::string output = args[1].substr(0, last_newline);
if(!output.empty()) { if(!output.empty()) {
/* Split multiline output and push each line to history. */ /* Split multiline output and push each line to history. */
std::stringstream ss(output); std::stringstream ss(output);
@ -260,7 +292,9 @@ void GameState::update(float dt, int draw_calls, int shape_verts, int text_verts
} }
} }
} else { } else {
terminal->add_history(args[0]); terminal->add_history(args[1]);
}
}
} }
} }
} }
@ -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) { void GameState::render(const RenderContext& context) {
switch(_current_screen) { switch(_current_screen) {
case Screen::MAIN_MENU: 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) { void GameState::send_network_command(uint32_t session_id, const std::string& command) {
_network->send(net_protocol::build_message(net_protocol::Opcode::C2S_WRITE_FILE, if(_network && _network->is_connected()) {
{path, content})); _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) { void GameState::send_file_write_request(uint32_t session_id, const std::string& path,
_network->send(net_protocol::build_message(net_protocol::Opcode::C2S_READ_FILE, const std::string& content) {
{path})); _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));
}
} }

View File

@ -33,9 +33,10 @@ public:
void render(const RenderContext& context); void render(const RenderContext& context);
/* Public network interface for UI components. */ /* Public network interface for UI components. */
void send_network_command(const std::string& command); void send_network_command(uint32_t session_id, const std::string& command);
void send_file_write_request(const std::string& path, const std::string& content); void send_file_write_request(uint32_t session_id, const std::string& path, const std::string& content);
void send_file_read_request(const std::string& path); void send_file_read_request(uint32_t session_id, const std::string& path);
void send_create_session_request(void);
private: private:
std::unique_ptr<ClientNetwork> _network; std::unique_ptr<ClientNetwork> _network;
@ -49,8 +50,8 @@ private:
int _screen_width; int _screen_width;
int _screen_height; int _screen_height;
bool _is_single_player; bool _is_single_player;
uint32_t _initial_session_id;
void _init_desktop(void); void _init_desktop(void);
void _send_network_command(const std::string& command);
void _run_server(void); void _run_server(void);
}; };

View File

@ -12,7 +12,8 @@
Terminal::Terminal(GameState* game_state) Terminal::Terminal(GameState* game_state)
: _game_state(game_state), _should_close(false), _command_history_index(0), : _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<TextView>(&_input_buffer, false, false); _input_view = std::make_unique<TextView>(&_input_buffer, false, false);
} }
@ -52,6 +53,14 @@ WindowAction Terminal::get_pending_action(void) {
return action; 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) { void Terminal::_on_ret_press(void) {
std::string command = _input_buffer.get_line(0); std::string command = _input_buffer.get_line(0);
if(!command.empty()) { if(!command.empty()) {
@ -79,7 +88,7 @@ void Terminal::_on_ret_press(void) {
} }
_history.push_back(_prompt + "> " + command); _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) { void Terminal::handle_input(SDL_Event* event, int window_x, int window_y, int window_gl_y) {

View File

@ -26,6 +26,8 @@ public:
void set_prompt(const std::string& prompt); void set_prompt(const std::string& prompt);
bool should_close(void) override; bool should_close(void) override;
WindowAction get_pending_action(void) override; WindowAction get_pending_action(void) override;
void set_session_id(uint32_t id);
uint32_t get_session_id(void) const;
private: private:
void _on_ret_press(void); void _on_ret_press(void);
@ -39,5 +41,6 @@ private:
GameState* _game_state; GameState* _game_state;
TextBuffer _input_buffer; TextBuffer _input_buffer;
WindowAction _pending_action; WindowAction _pending_action;
uint32_t _session_id;
std::unique_ptr<TextView> _input_view; std::unique_ptr<TextView> _input_view;
}; };

View File

@ -30,6 +30,7 @@ Desktop::Desktop(int screen_width, int screen_height, GameState* game_state) {
_taskbar = std::make_unique<Taskbar>(screen_width, screen_height); _taskbar = std::make_unique<Taskbar>(screen_width, screen_height);
_game_state= game_state; _game_state= game_state;
_focused_window = nullptr; _focused_window = nullptr;
_window_awaiting_session_id = nullptr;
_launcher_is_open = false; _launcher_is_open = false;
_launcher = std::make_unique<Launcher>(5, 0, 200); /* Tmp y-coord. */ _launcher = std::make_unique<Launcher>(5, 0, 200); /* Tmp y-coord. */
int launcher_y = screen_height - _taskbar->get_height() - _launcher->get_height(); 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") { if(app_to_launch == "Terminal") {
auto term = std::make_unique<Terminal>(_game_state); auto term = std::make_unique<Terminal>(_game_state);
auto term_window = std::make_unique<UIWindow>("Terminal", 150, 150, 800, 500); auto term_window = std::make_unique<UIWindow>("Terminal", 150, 150, 800, 500);
_window_awaiting_session_id = term_window.get();
term_window->set_content(std::move(term)); term_window->set_content(std::move(term));
add_window(std::move(term_window)); add_window(std::move(term_window));
_launcher_is_open = false; _launcher_is_open = false;
_game_state->send_create_session_request();
} else if(app_to_launch == "Editor") { } else if(app_to_launch == "Editor") {
auto editor = std::make_unique<Editor>(); auto editor = std::make_unique<Editor>();
auto editor_window = std::make_unique<UIWindow>("Editor", 200, 200, 600, 400); auto editor_window = std::make_unique<UIWindow>("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(); IWindowContent* content = _focused_window->get_content();
if(content) { if(content) {
WindowAction action = content->get_pending_action(); WindowAction action = content->get_pending_action();
uint32_t session_id = 0;
if(auto term = dynamic_cast<Terminal*>(content)) {
session_id = term->get_session_id();
}
switch(action.type) { switch(action.type) {
case ActionType::WRITE_FILE: { 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; break;
} }
case ActionType::READ_FILE: { 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; break;
} }
default: 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(), _windows.erase(std::remove_if(_windows.begin(), _windows.end(),
[this](const std::unique_ptr<UIWindow>& w) { [this](const std::unique_ptr<UIWindow>& w) {
if (w->should_close()) { 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()); _taskbar->remove_window(w.get());
if (w.get() == _focused_window) { if (w.get() == _focused_window) {
_focused_window = nullptr; _focused_window = nullptr;
@ -214,10 +235,28 @@ void Desktop::update(float dt, int screen_width, int screen_height) {
_windows.end()); _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) { UIWindow* Desktop::get_focused_window(void) {
return _focused_window; return _focused_window;
} }
UIWindow* Desktop::get_window_awaiting_session_id(void) {
return _window_awaiting_session_id;
}
void Desktop::render(const RenderContext& context) { void Desktop::render(const RenderContext& context) {
/* Pass 1: Background. */ /* Pass 1: Background. */
context.ui_renderer->begin_text(); context.ui_renderer->begin_text();

View File

@ -2,6 +2,7 @@
#include <memory> #include <memory>
#include <vector> #include <vector>
#include <map>
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include "gfx/types.h" #include "gfx/types.h"
@ -31,7 +32,10 @@ public:
void update(float dt, int screen_width, int screen_height); void update(float dt, int screen_width, int screen_height);
void render(const RenderContext& context); 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_focused_window(void);
UIWindow* get_window_awaiting_session_id(void);
private: private:
void _set_focused_window(UIWindow* window); void _set_focused_window(UIWindow* window);
@ -42,6 +46,8 @@ private:
std::unique_ptr<Taskbar> _taskbar; std::unique_ptr<Taskbar> _taskbar;
std::unique_ptr<Launcher> _launcher; std::unique_ptr<Launcher> _launcher;
UIWindow* _focused_window; UIWindow* _focused_window;
UIWindow* _window_awaiting_session_id;
std::map<uint32_t, UIWindow*> _session_windows;
GameState* _game_state; GameState* _game_state;
std::vector<ScrollingText> _background_text; std::vector<ScrollingText> _background_text;
std::vector<std::string> _snippets; std::vector<std::string> _snippets;

View File

@ -4,17 +4,17 @@
#include "i_network_bridge.h" #include "i_network_bridge.h"
#include "lua_api.h" #include "lua_api.h"
#include "command_processor.h" #include "session.h"
#include "machine.h" #include "machine.h"
#include "vfs.h" #include "vfs.h"
namespace api { namespace api {
vfs_node* get_current_dir(CommandProcessor& context) { vfs_node* get_current_dir(Session& context) {
return context.get_current_dir(); 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(); vfs_node* current_dir = context.get_current_dir();
auto it = current_dir->children.find(filename); auto it = current_dir->children.find(filename);
@ -30,7 +30,7 @@ std::string rm(CommandProcessor& context, const std::string& filename) {
return ""; 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) { const std::string& content) {
vfs_node* current_dir = context.get_current_dir(); vfs_node* current_dir = context.get_current_dir();
auto it = current_dir->children.find(filename); auto it = current_dir->children.find(filename);
@ -50,7 +50,7 @@ std::string write_file(CommandProcessor& context, const std::string& filename,
return ""; 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(); vfs_node* current_dir = context.get_current_dir();
if(path == "..") { if(path == "..") {
@ -68,7 +68,7 @@ std::string cd(CommandProcessor& context, const std::string& path) {
return ""; return "";
} }
std::string ls(CommandProcessor& context) { std::string ls(Session& context) {
vfs_node* dir = context.get_current_dir(); vfs_node* dir = context.get_current_dir();
if(dir->type != DIR_NODE) { if(dir->type != DIR_NODE) {
return "ls: not a directory"; return "ls: not a directory";
@ -85,7 +85,7 @@ std::string ls(CommandProcessor& context) {
return ss.str(); 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(); INetworkBridge* bridge = context.get_network_bridge();
Machine* target_machine = bridge->get_machine_by_ip(ip); 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); long long machine_id = context.get_machine_manager()->get_machine_id_by_ip(ip);
if(machine_id == 0) { if(machine_id == 0) {
return "nmap: Could not resolve host: " + ip; return "nmap: Could not resolve host: " + ip;
@ -117,7 +117,7 @@ std::string nmap(CommandProcessor& context, const std::string& ip) {
return ss.str(); return ss.str();
} }
std::string disconnect(CommandProcessor& context) { std::string disconnect(Session& context) {
Machine* current_machine = context.get_session_machine(); Machine* current_machine = context.get_session_machine();
if(current_machine != context.get_home_machine()) { if(current_machine != context.get_home_machine()) {
INetworkBridge* bridge = context.get_network_bridge(); INetworkBridge* bridge = context.get_network_bridge();
@ -127,7 +127,7 @@ std::string disconnect(CommandProcessor& context) {
return "Connection closed."; return "Connection closed.";
} }
std::string close_terminal(CommandProcessor& context) { std::string close_terminal(Session& context) {
return "__CLOSE_CONNECTION__"; 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) { const std::string& dest_arg) {
ScpPath source_path = parse_scp_path(source_arg); ScpPath source_path = parse_scp_path(source_arg);
ScpPath dest_path = parse_scp_path(dest_arg); ScpPath dest_path = parse_scp_path(dest_arg);

View File

@ -3,27 +3,27 @@
#include <string> #include <string>
#include "vfs.h" #include "vfs.h"
class CommandProcessor; class Session;
class NetworkManager; class NetworkManager;
namespace api { namespace api {
/* FILESYSTEM ACTIONS. */ /* FILESYSTEM ACTIONS. */
vfs_node* get_current_dir(CommandProcessor& context); vfs_node* get_current_dir(Session& context);
std::string rm(CommandProcessor& context, const std::string& filename); std::string rm(Session& context, const std::string& filename);
std::string write_file(CommandProcessor& context, const std::string& filename, std::string write_file(Session& context, const std::string& filename,
const std::string& content); const std::string& content);
std::string ls(CommandProcessor& context); std::string ls(Session& context);
std::string cd(CommandProcessor& context, const std::string& path); std::string cd(Session& context, const std::string& path);
std::string scp(CommandProcessor& context, const std::string& source, std::string scp(Session& context, const std::string& source,
const std::string& destination); const std::string& destination);
/* NETWORK ACTIONS. */ /* NETWORK ACTIONS. */
std::string ssh(CommandProcessor& context, const std::string& ip); std::string ssh(Session& context, const std::string& ip);
std::string nmap(CommandProcessor& context, const std::string& ip); std::string nmap(Session& context, const std::string& ip);
std::string disconnect(CommandProcessor& context); std::string disconnect(Session& context);
/* SYSTEM ACTIONS. */ /* SYSTEM ACTIONS. */
std::string close_terminal(CommandProcessor& context); std::string close_terminal(Session& context);
} /* namespace api */ } /* namespace api */

View File

@ -5,10 +5,10 @@
#include "lua_processor.h" #include "lua_processor.h"
#include "lua_api.h" #include "lua_api.h"
#include "command_processor.h" #include "session.h"
#include "vfs.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); _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. */
@ -19,7 +19,7 @@ LuaProcessor::LuaProcessor(CommandProcessor& context) {
"content", &vfs_node::content); "content", &vfs_node::content);
/* Expose CommandProcessor to Lua. DON'T ALLOW SCRIPTS TO CREATE IT THOUGH! */ /* Expose CommandProcessor to Lua. DON'T ALLOW SCRIPTS TO CREATE IT THOUGH! */
_lua.new_usertype<CommandProcessor>("CommandProcessor", sol::no_constructor); _lua.new_usertype<Session>("Session", sol::no_constructor);
/* 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");
@ -37,7 +37,7 @@ LuaProcessor::LuaProcessor(CommandProcessor& context) {
LuaProcessor::~LuaProcessor(void) {} 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<std::string>& args, bool is_remote) { const std::vector<std::string>& args, bool is_remote) {
try { try {
/* Pass C++ objects/points into the Lua env. */ /* Pass C++ objects/points into the Lua env. */

View File

@ -5,15 +5,15 @@
#include <vfs.h> #include <vfs.h>
#include <string> #include <string>
class CommandProcessor; class Session;
class LuaProcessor { class LuaProcessor {
public: public:
LuaProcessor(CommandProcessor& context); LuaProcessor(Session& 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. */
sol::object execute(const std::string& script, CommandProcessor& context, sol::object execute(const std::string& script, Session& context,
const std::vector<std::string>& args, bool is_remote); const std::vector<std::string>& args, bool is_remote);
private: private:
sol::state _lua; sol::state _lua;

View File

@ -16,19 +16,21 @@ enum class Opcode : uint8_t {
/* Client -> Server messages. */ /* Client -> Server messages. */
C2S_CREATE_ACCOUNT, C2S_CREATE_ACCOUNT,
C2S_LOGIN, C2S_LOGIN,
C2S_COMMAND, C2S_CREATE_SESSION,
C2S_WRITE_FILE, C2S_COMMAND, /* args: [session_id, command] */
C2S_READ_FILE, C2S_WRITE_FILE, /* args: [session_id, path, content] */
C2S_READ_FILE, /* args: [session_id, path] */
/* Server -> Client messages. */ /* Server -> Client messages. */
S2C_CREATE_ACCOUNT_SUCCESS, S2C_CREATE_ACCOUNT_SUCCESS,
S2C_CREATE_ACCOUNT_FAIL, S2C_CREATE_ACCOUNT_FAIL,
S2C_LOGIN_SUCCESS, S2C_LOGIN_SUCCESS,
S2C_LOGIN_FAIL, S2C_LOGIN_FAIL,
S2C_COMMAND_RESPONSE, S2C_SESSION_CREATED, /* args: [session_id] */
S2C_FILE_DATA, S2C_COMMAND_RESPONSE, /* args: [session_id, response] */
S2C_DISCONNECT, S2C_FILE_DATA, /* args: [session_id, path, content] */
S2C_CLOSE_WINDOW, S2C_DISCONNECT, /* args: [session_id] */
S2C_CLOSE_WINDOW, /* args: [session_id] */
}; };
/** /**

View File

@ -1,7 +1,7 @@
#include <sol/types.hpp> #include <sol/types.hpp>
#include <sstream> #include <sstream>
#include "command_processor.h" #include "session.h"
#include "db/database_manager.h" #include "db/database_manager.h"
#include "i_network_bridge.h" #include "i_network_bridge.h"
#include "lua_api.h" #include "lua_api.h"
@ -22,7 +22,7 @@ vfs_node* find_node_by_id(vfs_node* root, long long id) {
return nullptr; return nullptr;
} }
CommandProcessor::CommandProcessor(Machine* home_machine, Session::Session(Machine* home_machine,
DatabaseManager* db_manager, DatabaseManager* db_manager,
MachineManager* machine_manager, MachineManager* machine_manager,
INetworkBridge* network_bridge) : INetworkBridge* network_bridge) :
@ -35,40 +35,40 @@ CommandProcessor::CommandProcessor(Machine* home_machine,
if(home_machine) _current_dir = home_machine->vfs_root; 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; return _current_dir;
} }
Machine* CommandProcessor::get_home_machine(void) { return _home_machine; } Machine* Session::get_home_machine(void) { return _home_machine; }
Machine* CommandProcessor::get_session_machine(void) { return _session_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; return _db_manager;
} }
MachineManager* CommandProcessor::get_machine_manager(void) { MachineManager* Session::get_machine_manager(void) {
return _machine_manager; return _machine_manager;
} }
INetworkBridge* CommandProcessor::get_network_bridge(void) { INetworkBridge* Session::get_network_bridge(void) {
return _network_bridge; return _network_bridge;
} }
void CommandProcessor::set_current_dir(vfs_node* node) { void Session::set_current_dir(vfs_node* node) {
_current_dir = node; _current_dir = node;
} }
void CommandProcessor::set_session_machine(Machine* machine) { void Session::set_session_machine(Machine* machine) {
_session_machine = machine; _session_machine = machine;
if(_session_machine) { if(_session_machine) {
_current_dir = _session_machine->vfs_root; _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 * Creating the Lua processor on-demand to ensure it exists on the same
* thread that will execute the script. * 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"; 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); 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* root = get_session_machine()->vfs_root;
vfs_node* node = find_node_by_path(root, path); vfs_node* node = find_node_by_path(root, path);
if(node && node->type == FILE_NODE) { if(node && node->type == FILE_NODE) {

View File

@ -9,11 +9,11 @@
class LuaProcessor; class LuaProcessor;
class CommandProcessor { class Session {
public: public:
CommandProcessor(Machine* home_machine, DatabaseManager* db_manager, Session(Machine* home_machine, DatabaseManager* db_manager,
MachineManager* machine_manager, INetworkBridge* network_bridge); MachineManager* machine_manager, INetworkBridge* network_bridge);
~CommandProcessor(void); ~Session(void);
std::string process_command(const std::string& command); std::string process_command(const std::string& command);
std::string write_file(const std::string& path, const std::string& content); std::string write_file(const std::string& path, const std::string& content);

View File

@ -2,6 +2,7 @@
#include <exception> #include <exception>
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <string>
#include "network_manager.h" #include "network_manager.h"
#include "net/message_protocol.h" #include "net/message_protocol.h"
@ -9,7 +10,7 @@
#include "asio/error_code.hpp" #include "asio/error_code.hpp"
#include "asio/ip/tcp.hpp" #include "asio/ip/tcp.hpp"
#include "command_processor.h" #include "session.h"
#include "player.h" #include "player.h"
#include "machine.h" #include "machine.h"
#include "net/tcp_connection.h" #include "net/tcp_connection.h"
@ -114,15 +115,17 @@ void NetworkManager::on_message(std::shared_ptr<net::TcpConnection> connection,
_machine_manager.get_vfs_template())) { _machine_manager.get_vfs_template())) {
long long home_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 = _get_or_load_machine(home_machine_id); 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; player->state = PlayerState::ACTIVE;
/* Create first session for player. */
uint32_t session_id = _next_session_id++;
player->sessions[session_id] = std::make_unique<Session>(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_CREATE_ACCOUNT_SUCCESS));
/* send initial prompt. */ connection->send(net_protocol::build_message(net_protocol::Opcode::S2C_SESSION_CREATED,
std::string prompt = "\n" + get_full_path(player->cmd_processor->get_current_dir()); {std::to_string(session_id)}));
connection->send(net_protocol::build_message(net_protocol::Opcode::S2C_COMMAND_RESPONSE,
{prompt}));
} else { } else {
connection->send(net_protocol::build_message(net_protocol::Opcode::S2C_CREATE_ACCOUNT_FAIL, connection->send(net_protocol::build_message(net_protocol::Opcode::S2C_CREATE_ACCOUNT_FAIL,
{"Username already exists."})); {"Username already exists."}));
@ -134,15 +137,18 @@ void NetworkManager::on_message(std::shared_ptr<net::TcpConnection> connection,
if(_db_manager->players().authenticate(args[0], args[1])) { if(_db_manager->players().authenticate(args[0], args[1])) {
long long home_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 = _get_or_load_machine(home_machine_id); 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; player->state = PlayerState::ACTIVE;
/* Create first session for player. */
uint32_t session_id = _next_session_id++;
player->sessions[session_id] = std::make_unique<Session>(home_machine,
_db_manager.get(),
&_machine_manager, this);
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. */ connection->send(net_protocol::build_message(net_protocol::Opcode::S2C_SESSION_CREATED,
std::string prompt = "\n" + get_full_path(player->cmd_processor->get_current_dir()); {std::to_string(session_id)}));
connection->send(net_protocol::build_message(net_protocol::Opcode::S2C_COMMAND_RESPONSE,
{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."}));
@ -159,36 +165,49 @@ void NetworkManager::on_message(std::shared_ptr<net::TcpConnection> connection,
/* === PLAYER BECOMES ACTIVE HERE === */ /* === PLAYER BECOMES ACTIVE HERE === */
switch(opcode) { 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<Session>(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: case net_protocol::Opcode::C2S_WRITE_FILE:
if(args.size() == 2) { if(args.size() == 3) {
fprintf(stderr, "[Player %u] Write file: '%s'\n", player->id, args[0].c_str()); uint32_t session_id = std::stoul(args[0]);
player->cmd_processor->write_file(args[0], args[1]); if(player->sessions.count(session_id)) {
/* Response not required for a file write. */ player->sessions.at(session_id)->write_file(args[1], args[2]);
}
} }
break; break;
case net_protocol::Opcode::C2S_READ_FILE: case net_protocol::Opcode::C2S_READ_FILE:
if(!args.empty()) { if(args.size() == 2) {
fprintf(stderr, "[Player %u] Read file: '%s'\n", player->id, args[0].c_str()); uint32_t session_id = std::stoul(args[0]);
std::string content = player->cmd_processor->read_file(args[0]); if(player->sessions.count(session_id)) {
/* Send the content back to the client. */ std::string content = player->sessions.at(session_id)->read_file(args[1]);
connection->send(net_protocol::build_message(net_protocol::Opcode::S2C_FILE_DATA, connection->send(net_protocol::build_message(net_protocol::Opcode::S2C_FILE_DATA,
{args[0], content})); {args[0], args[1], content}));
}
} }
break; break;
case net_protocol::Opcode::C2S_COMMAND: case net_protocol::Opcode::C2S_COMMAND:
if(!args.empty()) { if(args.size() == 2) {
fprintf(stderr, "[Player %u] Command: '%s'\n", player->id, args[0].c_str()); uint32_t session_id = std::stoul(args[0]);
std::string response = player->cmd_processor->process_command(args[0]); if(player->sessions.count(session_id)) {
Session* session = player->sessions.at(session_id).get();
if(response == "__CLOSE_CONNECTION__") { std::string response = session->process_command(args[1]);
connection->send(net_protocol::build_message(net_protocol::Opcode::S2C_CLOSE_WINDOW)); std::string new_prompt = get_full_path(session->get_current_dir());
return;
}
std::string new_prompt = get_full_path(player->cmd_processor->get_current_dir());
response += "\n" + new_prompt; response += "\n" + new_prompt;
connection->send(net_protocol::build_message(net_protocol::Opcode::S2C_COMMAND_RESPONSE, connection->send(net_protocol::build_message(net_protocol::Opcode::S2C_COMMAND_RESPONSE,
{response})); {args[0], response}));
}
} }
break; break;
default: default:
@ -202,17 +221,16 @@ void NetworkManager::on_disconnect(std::shared_ptr<net::TcpConnection> connectio
uint32_t player_id = connection->get_id(); uint32_t player_id = connection->get_id();
Player* player = _players[player_id].get(); Player* player = _players[player_id].get();
if(player && player->cmd_processor) { if(player) {
Machine* home_machine = player->cmd_processor->get_home_machine(); for(auto const& [session_id, session] : player->sessions) {
Machine* session_machine = player->cmd_processor->get_session_machine(); Machine* home_machine = session->get_home_machine();
Machine* session_machine = session->get_session_machine();
if(home_machine) { if(home_machine) { release_machine(home_machine->id); }
release_machine(home_machine->id);
}
if(session_machine && session_machine != home_machine) { if(session_machine && session_machine != home_machine) {
release_machine(session_machine->id); 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(

View File

@ -50,6 +50,7 @@ private:
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; std::map<long long, CachedMachine> _active_machines;
uint32_t _next_player_id = 1; uint32_t _next_player_id = 1;
uint32_t _next_session_id = 1;
std::unique_ptr<DatabaseManager> _db_manager; std::unique_ptr<DatabaseManager> _db_manager;
MachineManager _machine_manager; MachineManager _machine_manager;

View File

@ -1,5 +1,5 @@
#include "player.h" #include "player.h"
#include "command_processor.h" #include "session.h"
#include "db/database_manager.h" #include "db/database_manager.h"
#include "i_network_bridge.h" #include "i_network_bridge.h"
#include "machine.h" #include "machine.h"
@ -9,12 +9,6 @@ Player::Player(uint32_t new_id, Machine* home_machine,
MachineManager* machine_manager, MachineManager* machine_manager,
INetworkBridge* network_bridge) : INetworkBridge* network_bridge) :
id(new_id), id(new_id),
state(PlayerState::AUTHENTICATING) { state(PlayerState::AUTHENTICATING) {}
cmd_processor = new CommandProcessor(home_machine, db_manager, machine_manager, Player::~Player(void) {}
network_bridge);
}
Player::~Player(void) {
delete cmd_processor;
}

View File

@ -1,9 +1,11 @@
#pragma once #pragma once
#include <cstdint> #include <cstdint>
#include <memory>
#include <unordered_map>
#include "db/database_manager.h" #include "db/database_manager.h"
#include "command_processor.h" #include "session.h"
#include "i_network_bridge.h" #include "i_network_bridge.h"
#include "machine.h" #include "machine.h"
#include "machine_manager.h" #include "machine_manager.h"
@ -21,5 +23,7 @@ public:
uint32_t id; uint32_t id;
PlayerState state; 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<uint32_t, std::unique_ptr<Session>> sessions;
}; };