From d9e0761e07db37f8a4a676d7b137eb03a988798c Mon Sep 17 00:00:00 2001 From: Luuk van Oijen Date: Tue, 21 Nov 2023 17:24:14 +0100 Subject: [PATCH] wip new console --- Cargo.lock | 245 ++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 3 + src/heartbeat.rs | 4 +- src/logger.rs | 38 +++++++ src/main.rs | 37 +++++-- src/server/mod.rs | 4 +- src/tui.rs | 145 +++++++++++++++++++++++++++ 7 files changed, 465 insertions(+), 11 deletions(-) create mode 100644 src/logger.rs create mode 100644 src/tui.rs diff --git a/Cargo.lock b/Cargo.lock index 72a1889..6ebf10e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,18 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "ahash" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.2" @@ -26,6 +38,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -111,6 +129,7 @@ version = "0.1.0" dependencies = [ "anyhow", "async-trait", + "crossterm", "flate2", "futures", "lazy_static", @@ -119,6 +138,7 @@ dependencies = [ "nalgebra", "num_enum", "pretty_env_logger", + "ratatui", "reqwest", "serde", "serde-aux", @@ -167,6 +187,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +[[package]] +name = "cassowary" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" + [[package]] name = "cc" version = "1.0.83" @@ -219,6 +245,31 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossterm" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" +dependencies = [ + "bitflags 2.4.1", + "crossterm_winapi", + "libc", + "mio", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + [[package]] name = "either" version = "1.9.0" @@ -434,6 +485,16 @@ name = "hashbrown" version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" @@ -592,12 +653,27 @@ dependencies = [ "hashbrown 0.14.2", ] +[[package]] +name = "indoc" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" + [[package]] name = "ipnet" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.9" @@ -631,12 +707,31 @@ version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +[[package]] +name = "lru" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efa59af2ddfad1854ae27d75009d538d0998b4b2fd47083e743ac1a10e46c60" +dependencies = [ + "hashbrown 0.14.2", +] + [[package]] name = "lua-src" version = "546.0.1" @@ -694,6 +789,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" dependencies = [ "libc", + "log", "wasi", "windows-sys", ] @@ -898,6 +994,29 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + [[package]] name = "paste" version = "1.0.14" @@ -972,6 +1091,24 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "ratatui" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ebc917cfb527a566c37ecb94c7e3fd098353516fb4eb6bea17015ade0182425" +dependencies = [ + "bitflags 2.4.1", + "cassowary", + "crossterm", + "indoc", + "itertools", + "lru", + "paste", + "strum", + "unicode-segmentation", + "unicode-width", +] + [[package]] name = "rawpointer" version = "0.2.1" @@ -1079,6 +1216,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + [[package]] name = "ryu" version = "1.0.15" @@ -1103,6 +1246,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "security-framework" version = "2.9.2" @@ -1180,6 +1329,36 @@ dependencies = [ "serde", ] +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + [[package]] name = "simba" version = "0.7.3" @@ -1202,6 +1381,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "smallvec" +version = "1.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" + [[package]] name = "socket2" version = "0.4.10" @@ -1222,6 +1407,28 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "strum" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.39", +] + [[package]] name = "syn" version = "1.0.109" @@ -1438,6 +1645,18 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + [[package]] name = "url" version = "2.4.1" @@ -1455,6 +1674,12 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + [[package]] name = "want" version = "0.3.1" @@ -1692,3 +1917,23 @@ dependencies = [ "cfg-if", "windows-sys", ] + +[[package]] +name = "zerocopy" +version = "0.7.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e97e415490559a91254a2979b4829267a57d2fcd741a98eee8b722fb57289aa0" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd7e48ccf166952882ca8bd778a43502c64f33bf94c12ebe2a7f08e5a0f6689f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] diff --git a/Cargo.toml b/Cargo.toml index 1aa93a1..1049b64 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,3 +28,6 @@ toml = "0.5" flate2 = "1.0" mlua = { version = "0.9.1", features = ["lua54", "vendored", "send"] } + +ratatui = "0.24.0" +crossterm = "0.27.0" diff --git a/src/heartbeat.rs b/src/heartbeat.rs index f8cfbd4..daa2e83 100644 --- a/src/heartbeat.rs +++ b/src/heartbeat.rs @@ -47,7 +47,7 @@ pub async fn backend_heartbeat(config: std::sync::Arc, mu if let Some(status) = status { trace!("status update: {:?}", status); info.players = status.player_count; - info.playerslist = status.player_list.clone(); + info.playerslist = status.player_list.iter().map(|(_id, name)| format!("{};", name)).collect(); } } } @@ -64,7 +64,7 @@ async fn heartbeat_post(heartbeat_info: &HeartbeatInfo) { .await { Ok(resp) => { - debug!("heartbeat response:\n{:?}", resp.text().await); + trace!("heartbeat response:\n{:?}", resp.text().await); }, Err(e) => error!("Heartbeat error occured: {e}"), } diff --git a/src/logger.rs b/src/logger.rs new file mode 100644 index 0000000..fd8d7f1 --- /dev/null +++ b/src/logger.rs @@ -0,0 +1,38 @@ +use log::{Record, Level, Metadata, SetLoggerError, LevelFilter}; +use std::sync::Mutex; + +lazy_static! { + static ref LOG_BUFFER: Mutex> = Mutex::new(Vec::new()); +} + +struct BufferedLogger; + +impl log::Log for BufferedLogger { + fn enabled(&self, _metadata: &Metadata) -> bool { + true + } + + fn log(&self, record: &Record) { + if self.enabled(record.metadata()) { + let msg = format!("{}", record.args()); + let mut lock = LOG_BUFFER.lock().expect("Logging lock poisoned!"); + lock.push((record.level(), msg)); + } + } + + fn flush(&self) {} +} + +static LOGGER: BufferedLogger = BufferedLogger; + +pub fn init(filter: LevelFilter) -> Result<(), SetLoggerError> { + log::set_logger(&LOGGER) + .map(|()| log::set_max_level(filter)) +} + +pub async fn drain_log_buffer() -> Vec<(Level, String)> { + let mut lock = LOG_BUFFER.lock().expect("Logging lock poisoned!"); + let v = lock.drain(..).collect(); + drop(lock); + v +} diff --git a/src/main.rs b/src/main.rs index 89d008e..054a192 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,8 @@ use std::sync::Arc; use tokio::sync::mpsc; +mod logger; +mod tui; mod server; mod config; mod heartbeat; @@ -12,12 +14,13 @@ mod heartbeat; #[tokio::main] async fn main() { // pretty_env_logger::formatted_timed_builder().filter_level(log::LevelFilter::max()).init(); - pretty_env_logger::formatted_timed_builder().filter_level(log::LevelFilter::Debug).init(); + // pretty_env_logger::formatted_timed_builder().filter_level(log::LevelFilter::Debug).init(); + logger::init(log::LevelFilter::max()).expect("Failed to enable logger!"); let mut user_config: config::Config = toml::from_str( - &std::fs::read_to_string("ServerConfig.toml") - .map_err(|_| error!("Failed to read config file!")) - .expect("Failed to read config file!") + &std::fs::read_to_string("ServerConfig.toml") + .map_err(|_| error!("Failed to read config file!")) + .expect("Failed to read config file!") ) .map_err(|_| error!("Failed to parse config file!")) .expect("Failed to parse config file!"); @@ -43,10 +46,14 @@ async fn main() { let user_config = Arc::new(user_config); - server_main(user_config).await; + let (cmd_tx, cmd_rx) = mpsc::channel(100); + + tokio::spawn(tui::tui_main(user_config.clone(), cmd_tx)); + + server_main(user_config, cmd_rx).await; } -async fn server_main(user_config: Arc) { +async fn server_main(user_config: Arc, mut cmd_rx: mpsc::Receiver>) { let (hb_tx, hb_rx) = mpsc::channel(100); tokio::spawn(heartbeat::backend_heartbeat(user_config.clone(), hb_rx)); @@ -89,9 +96,25 @@ async fn server_main(user_config: Arc) { let new_status = server.get_server_status(); if status != new_status { - trace!("WHAT"); status = new_status; hb_tx.send(status.clone()).await; } + + // Process commands + match cmd_rx.try_recv() { + Ok(cmd) => if cmd.len() > 0 { + match cmd[0].as_str() { + "exit" => break, + _ => info!("Unknown command!"), + } + } else { + // what! + }, + Err(mpsc::error::TryRecvError::Empty) => {}, + Err(e) => { + error!("Error: {e}"); + break; + }, + } } } diff --git a/src/server/mod.rs b/src/server/mod.rs index 6601642..ac911c5 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -74,7 +74,7 @@ fn load_plugins() -> Vec { #[derive(PartialEq, Eq, Clone, Debug)] pub struct ServerStatus { pub player_count: usize, - pub player_list: String, + pub player_list: Vec<(u8, String)>, pub max_players: usize, } @@ -320,7 +320,7 @@ impl Server { ServerStatus { player_count: self.clients.len(), player_list: self.clients.iter().map(|client| { - format!("{};", &client.get_name()) + (client.id, client.get_name().to_string()) }).collect(), // max_players: self.max_players, // TODO: Support this max_players: self.config.general.max_players, diff --git a/src/tui.rs b/src/tui.rs new file mode 100644 index 0000000..3a3b66f --- /dev/null +++ b/src/tui.rs @@ -0,0 +1,145 @@ +use std::sync::Arc; +use std::io::{stdout, Result}; +use std::collections::VecDeque; +use log::Level; + +use crossterm::{ + event::{self, KeyCode, KeyEventKind}, + terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, + ExecutableCommand, +}; +use ratatui::{ + prelude::*, + widgets::{Paragraph, Block, Borders}, +}; + +use tokio::sync::mpsc; + +use crate::config::Config; + +pub async fn tui_main(config: Arc, cmd_tx: mpsc::Sender>) { + let mut tui = Tui::new().expect("Failed to initialize tui!"); + + 'app: loop { + if tui.update().await.expect("Failed to run tui.update!") { + cmd_tx.send(vec!["exit".to_string()]).await.unwrap(); + break 'app; + } + tui.draw().expect("Failed to run tui.draw!"); + } +} + +struct Tui { + terminal: Terminal>, + + log_buffer: VecDeque<(Level, String)>, + + input: String, +} + +impl Drop for Tui { + fn drop(&mut self) { + if let Err(e) = self.cleanup() { + eprintln!("Error occured related to the TUI: {}", e); + } + } +} + +impl Tui { + fn new() -> Result { + stdout().execute(EnterAlternateScreen)?; + enable_raw_mode()?; + + let terminal = Terminal::new(CrosstermBackend::new(stdout()))?; + + Ok(Self { + terminal, + + log_buffer: VecDeque::new(), + + input: String::new(), + }) + } + + fn cleanup(&self) -> Result<()> { + stdout().execute(LeaveAlternateScreen)?; + disable_raw_mode()?; + Ok(()) + } + + async fn update(&mut self) -> Result { + for (level, msg) in crate::logger::drain_log_buffer().await { + self.log_buffer.push_front((level, msg)); + if self.log_buffer.len() > 100 { + self.log_buffer.pop_back(); + } + } + + if event::poll(std::time::Duration::from_millis(16))? { + if let event::Event::Key(key) = event::read()? { + if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') { + return Ok(true); + } + } + } + + Ok(false) + } + + fn draw(&mut self) -> Result<()> { + self.terminal.draw(|frame| { + let area = frame.size(); + + let vert_layout = Layout::default() + .direction(Direction::Vertical) + .constraints(vec![ + Constraint::Length(area.height - 3), + Constraint::Length(3), + ]) + .split(area); + + let horiz_layout = Layout::default() + .direction(Direction::Horizontal) + .constraints(vec![ + Constraint::Percentage(70), + Constraint::Percentage(30), + ]) + .split(vert_layout[0]); + + let mut lines = Vec::new(); + for (i, (level, msg)) in self.log_buffer.iter().enumerate() { + if i >= (area.height as usize - 5) { break; } + let level_style = match level { + Level::Info => Style::default().green(), + Level::Warn => Style::default().yellow(), + Level::Error => Style::default().red(), + Level::Debug => Style::default().gray(), + Level::Trace => Style::default().cyan(), + }; + lines.push(Line::from(vec![ + Span::styled("[", Style::default()), + Span::styled(format!("{}", level), level_style), + Span::styled(format!("] {}", msg), Style::default()), + ])); + } + lines.reverse(); + frame.render_widget( + // Paragraph::new("[INFO] info!!!\n[DEBUG] debug!!!!").block(Block::new().borders(Borders::ALL)), + Paragraph::new(Text::from(lines)).block(Block::new().borders(Borders::ALL)), + horiz_layout[0], + ); + + frame.render_widget( + Paragraph::new("0 - luuk-bepis\n1 - lion guy\n2 - the_racc").block(Block::new().borders(Borders::ALL)), + horiz_layout[1], + ); + + frame.render_widget( + Paragraph::new(format!(" > {}", self.input)).block(Block::new().borders(Borders::ALL)), + vert_layout[1], + ); + })?; + + Ok(()) + } +}