[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 <memory>
#include <sstream>
#include <string>
#include <thread>
#include <chrono>
@ -25,9 +26,13 @@ void GameState::_init_desktop(void) {
_desktop = std::make_unique<Desktop>(_screen_width, _screen_height, 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);
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<DebugOverlay>();}
_show_debug_overlay(false),
_initial_session_id(0) {_debug_overlay = std::make_unique<DebugOverlay>();}
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<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);
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:
if(args.size() == 2) {
auto editor = std::make_unique<Editor>(args[0]);
editor->set_buffer_content(args[1]);
auto editor_window = std::make_unique<UIWindow>(args[0].c_str(),
if(args.size() == 3) {
uint32_t session_id = std::stoul(args[0]);
auto editor = std::make_unique<Editor>(args[1]);
editor->set_buffer_content(args[2]);
auto editor_window = std::make_unique<UIWindow>(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<Terminal*>(content);
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<Terminal*>(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<Terminal*>(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<Terminal*>(window->get_content());
if(terminal) {
/* 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) {
std::string prompt = args[0].substr(last_newline+1);
std::string prompt = args[1].substr(last_newline+1);
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()) {
/* Split multiline output and push each line to history. */
std::stringstream ss(output);
@ -260,7 +292,9 @@ void GameState::update(float dt, int draw_calls, int shape_verts, int text_verts
}
}
} 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) {
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));
}
}

View File

@ -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<ClientNetwork> _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);
};

View File

@ -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<TextView>(&_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) {

View File

@ -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<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);
_game_state= game_state;
_focused_window = nullptr;
_window_awaiting_session_id = nullptr;
_launcher_is_open = false;
_launcher = std::make_unique<Launcher>(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<Terminal>(_game_state);
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));
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<Editor>();
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();
if(content) {
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) {
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<UIWindow>& 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();

View File

@ -2,6 +2,7 @@
#include <memory>
#include <vector>
#include <map>
#include <SDL3/SDL.h>
#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);
@ -42,6 +46,8 @@ private:
std::unique_ptr<Taskbar> _taskbar;
std::unique_ptr<Launcher> _launcher;
UIWindow* _focused_window;
UIWindow* _window_awaiting_session_id;
std::map<uint32_t, UIWindow*> _session_windows;
GameState* _game_state;
std::vector<ScrollingText> _background_text;
std::vector<std::string> _snippets;

View File

@ -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);

View File

@ -3,27 +3,27 @@
#include <string>
#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 */

View File

@ -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>("CommandProcessor", sol::no_constructor);
_lua.new_usertype<Session>("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<std::string>& args, bool is_remote) {
try {
/* Pass C++ objects/points into the Lua env. */

View File

@ -5,15 +5,15 @@
#include <vfs.h>
#include <string>
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<std::string>& args, bool is_remote);
private:
sol::state _lua;

View File

@ -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] */
};
/**

View File

@ -1,7 +1,7 @@
#include <sol/types.hpp>
#include <sstream>
#include "command_processor.h"
#include "session.h"
#include "db/database_manager.h"
#include "i_network_bridge.h"
#include "lua_api.h"
@ -22,7 +22,7 @@ vfs_node* find_node_by_id(vfs_node* root, long long id) {
return nullptr;
}
CommandProcessor::CommandProcessor(Machine* home_machine,
Session::Session(Machine* home_machine,
DatabaseManager* db_manager,
MachineManager* machine_manager,
INetworkBridge* 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) {

View File

@ -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);

View File

@ -2,6 +2,7 @@
#include <exception>
#include <functional>
#include <memory>
#include <string>
#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"
@ -114,15 +115,17 @@ void NetworkManager::on_message(std::shared_ptr<net::TcpConnection> connection,
_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;
/* 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));
/* 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_CREATE_ACCOUNT_FAIL,
{"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])) {
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<Session>(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<net::TcpConnection> 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<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:
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. */
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. */
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], content}));
{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;
}
std::string new_prompt = get_full_path(player->cmd_processor->get_current_dir());
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,
{response}));
{args[0], response}));
}
}
break;
default:
@ -202,17 +221,16 @@ void NetworkManager::on_disconnect(std::shared_ptr<net::TcpConnection> 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(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);
}
}
}
fprintf(stderr, "[Player %u] Disconnected.\n", player_id);
_connections.erase(

View File

@ -50,6 +50,7 @@ private:
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_session_id = 1;
std::unique_ptr<DatabaseManager> _db_manager;
MachineManager _machine_manager;

View File

@ -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) {}

View File

@ -1,9 +1,11 @@
#pragma once
#include <cstdint>
#include <memory>
#include <unordered_map>
#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<uint32_t, std::unique_ptr<Session>> sessions;
};