diff --git a/protobuf/game.proto b/protobuf/game.proto index 3db2c20..640b2a8 100644 --- a/protobuf/game.proto +++ b/protobuf/game.proto @@ -2,13 +2,14 @@ syntax = "proto3"; package game; -// Interface exported by the server. +// Connection utilities to get a client_id service Connection { rpc connect(Username) returns(UserID); rpc joinLobbyWithCode(LobbyCode) returns(Null); rpc joinLobbyWithoutCode(Null) returns(LobbyCode); } +// Lobby functionality (client_id required for most of them) service Lobby { rpc getGames(Null) returns(stream Game); rpc vote(Vote) returns(Null); diff --git a/server/.gitignore b/server/.gitignore index b755a01..416be0b 100644 --- a/server/.gitignore +++ b/server/.gitignore @@ -1,4 +1,5 @@ /target Cargo.lock db.sqlite* -/games \ No newline at end of file +/games +output.log \ No newline at end of file diff --git a/server/Cargo.toml b/server/Cargo.toml index d586d82..ece1b01 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -26,6 +26,11 @@ prost = "0.6" # Database (SQLite) sqlx = { version = "0.3", default-features = false, features = [ "runtime-tokio", "macros", "sqlite" ] } +# TUI +crossterm = "0.18.2" +log = "0.4.11" +fern = "0.6.0" +chrono = "0.4.19" [build-dependencies] # Grpc codegen tonic-build = "0.3" \ No newline at end of file diff --git a/server/src/games/config.rs b/server/src/games/config.rs index 9ffb3d3..36853b3 100644 --- a/server/src/games/config.rs +++ b/server/src/games/config.rs @@ -64,11 +64,11 @@ impl Config { pub fn load + std::fmt::Debug>(file: P) -> Self { serde_json::from_reader(std::fs::File::open(&file).unwrap()) .map_err(|e| { - eprintln!( + log::error!( "Malformed game defintion file @ {}", file.as_ref().display() ); - eprintln!("JSON Error: {}", e); + log::error!("JSON Error: {}", e); panic!() }) .unwrap() diff --git a/server/src/games/mod.rs b/server/src/games/mod.rs index e4b0010..000db64 100644 --- a/server/src/games/mod.rs +++ b/server/src/games/mod.rs @@ -1,4 +1,5 @@ use rhai::AST; +use log::info; use std::fs::read_dir; mod config; @@ -21,7 +22,7 @@ impl Game { let conf = config::Config::load(folder.as_ref().join("game.json")); let ast = rhai::Engine::new().compile_file(folder.as_ref().join(&conf.script)).unwrap(); - println!("AST: {:?}", ast); + info!("AST: {:?}", ast); Self { conf: conf.clone(), name: conf.name, version: conf.version, authors: conf.authors, ast } } diff --git a/server/src/games/run.rs b/server/src/games/run.rs index bf1fbfa..4ebc977 100644 --- a/server/src/games/run.rs +++ b/server/src/games/run.rs @@ -33,6 +33,7 @@ impl RunningGame { pub fn new(ast: AST, conf: &Config, players: u32) -> Self { let mut engine = Engine::new(); engine.register_result_fn("shuffle", shuffle_pile); + engine.on_print(|x| log::info!(target: "Rhai", "{}", x)).on_debug(|x|log::debug!(target: "Rhai", "{}", x)); let setup = Func::<(Dynamic,), Dynamic>::create_from_ast(engine, ast, "setup"); let piles = conf.piles.clone(); diff --git a/server/src/grpc.rs b/server/src/grpc.rs index 8e12e45..84ba754 100644 --- a/server/src/grpc.rs +++ b/server/src/grpc.rs @@ -1,5 +1,7 @@ use tonic::{transport::Server, Request, Response, Status}; +use log::info; + use std::sync::Arc; mod game; @@ -19,7 +21,7 @@ impl Connection for ConnectionService { let name = request.into_inner().name; let mut conn = self.conn.acquire().await; let uuid = conn.add_user(&name).await; - println!("Connected {}[{}]", name, uuid); + info!("Connected {}[{}]", name, uuid); conn.close().await; Ok(Response::new(UserId { id: uuid.to_hyphenated().to_string(), diff --git a/server/src/grpc/game.rs b/server/src/grpc/game.rs index e42b3dd..9f56eb5 100644 --- a/server/src/grpc/game.rs +++ b/server/src/grpc/game.rs @@ -67,7 +67,7 @@ pub mod connection_server { request: tonic::Request, ) -> Result, tonic::Status>; } - #[doc = " Interface exported by the server."] + #[doc = " Connection utilities to get a client_id"] #[derive(Debug)] pub struct ConnectionServer { inner: _Inner, @@ -250,6 +250,7 @@ pub mod lobby_server { request: tonic::Request, ) -> Result, tonic::Status>; } + #[doc = " Lobby functionality (client_id required for most of them)"] #[derive(Debug)] pub struct LobbyServer { inner: _Inner, diff --git a/server/src/logger.rs b/server/src/logger.rs new file mode 100644 index 0000000..b361888 --- /dev/null +++ b/server/src/logger.rs @@ -0,0 +1,257 @@ +use std::sync::mpsc; + +use std::io::Write; + +use crossterm::{ + cursor::{MoveTo, MoveToNextLine}, + event::{Event, KeyCode, KeyModifiers, MouseEvent}, + queue, + style::Print, + terminal::{Clear, ClearType}, +}; + +pub fn setup() -> Result<(Close, Stdin), fern::InitError> { + let (stdout, stdin, close, join_handle) = TerminalHandler::new(|x| { + if x == "sv_cheats" { + log::info!("CHEATS ENABLED") + } + x + }); + fern::Dispatch::new() + .format(|out, message, record| { + out.finish(format_args!( + "{}[{}][{}] {}", + chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"), + record.target(), + record.level(), + message + )) + }) + .level(log::LevelFilter::Debug) + .chain(stdout) + .chain( + std::fs::OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open("output.log")?, + ) + .apply()?; + log::info!("TEST1"); + log::info!("TEST2"); + // std::process::exit(1); + Ok((Close{close, handle: join_handle}, stdin)) +} + +pub type Stdout = mpsc::Sender; +pub type Stdin = mpsc::Receiver; + +struct TerminalHandler +where + T: Send + 'static, + F: Fn(String) -> T + Send + 'static, +{ + stdout: mpsc::Receiver, + stdin: mpsc::Sender, + close: mpsc::Receiver<()>, + handler: F, + lines: LimitedVec, + command: String, +} + +impl TerminalHandler +where + T: Send + 'static, + F: Fn(String) -> T + Send + 'static, +{ + fn new( + handler: F, + ) -> ( + Stdout, + Stdin, + mpsc::Sender<()>, + std::thread::JoinHandle<()>, + ) { + crossterm::terminal::enable_raw_mode().unwrap(); + let mut stdout = std::io::stdout(); + queue!(stdout, crossterm::event::EnableMouseCapture).unwrap(); + let (stdout_send, stdout_recv) = mpsc::channel(); + let (stdin_send, stdin_recv) = mpsc::channel(); + let (close_send, close_recv) = mpsc::channel(); + let join_handle = std::thread::spawn(move || { + let mut s = Self { + stdout: stdout_recv, + stdin: stdin_send, + close: close_recv, + handler, + lines: LimitedVec::new(crossterm::terminal::size().unwrap().1 as usize - 1), + command: String::new(), + }; + loop { + if s.iterate() { + break; + } + } + queue!(stdout, crossterm::event::DisableMouseCapture).unwrap(); + crossterm::terminal::disable_raw_mode().ok(); + println!("Closing printing thread"); + }); + (stdout_send, stdin_recv, close_send, join_handle) + } + + fn iterate(&mut self) -> bool { + let mut updated = false; + match self.close.try_recv() { + Err(mpsc::TryRecvError::Empty) => (), + _ => return true, + } + while is_event_available() { + match crossterm::event::read().unwrap() { + Event::Resize(_cols, rows) => self.lines.size = rows as usize - 1, + Event::Key(k) => match (k.code, k.modifiers) { + (KeyCode::Char('c'), KeyModifiers::CONTROL) => { + let mut stdout = std::io::stdout(); + queue!(stdout, crossterm::event::DisableMouseCapture).unwrap(); + crossterm::terminal::disable_raw_mode().unwrap(); + stdout.flush().unwrap(); + println!("Exiting server"); + std::process::exit(0) + } + (k, _m) => { + match k { + KeyCode::Char(c) => self.command.push(c), + KeyCode::Backspace => {self.command.pop();}, + KeyCode::Enter => { + match self.stdin.send((self.handler)(self.command.clone())) { + Ok(_) => (), + Err(e) => log::error!("{}", e), + }; + self.lines.push(format!(" >> {}", self.command)); + self.command = String::new(); + } + _ => () + } + }, + }, + Event::Mouse(m) => match m { + MouseEvent::ScrollUp(_, _, _) => self.lines.scroll_up(), + MouseEvent::ScrollDown(_, _, _) => self.lines.scroll_down(), + _ => (), + }, + } + + updated = true; + } + while let Some(x) = match self.stdout.try_recv() { + Err(mpsc::TryRecvError::Empty) => None, + Err(mpsc::TryRecvError::Disconnected) => return true, + Ok(v) => Some(v), + } { + for line in x.split('\n') { + if !line.is_empty() { + self.lines.push(line.trim_end().to_string()); + } + } + + updated = true; + } + let mut stdout = std::io::stdout(); + // println!("Got stdout"); + if updated { + queue!(stdout, Clear(ClearType::All), MoveTo(0, 0)).unwrap(); + for v in self.lines.iter() { + queue!(stdout, Print(v), MoveToNextLine(1)).unwrap(); + // println!("{}", v); + } + let size = crossterm::terminal::size().unwrap(); + queue!(stdout, MoveTo(0, size.1), Clear(ClearType::CurrentLine), Print(&self.command)).unwrap(); + stdout.flush().unwrap(); + } + false + } +} + +impl Drop for TerminalHandler +where + T: Send + 'static, + F: Fn(String) -> T + Send + 'static, +{ + fn drop(&mut self) { + queue!(std::io::stdout(), crossterm::event::DisableMouseCapture).unwrap(); + } +} + +fn is_event_available() -> bool { + crossterm::event::poll(std::time::Duration::from_millis(10)) + .expect("Error polling for terminal events") +} + +pub struct Close { + close: mpsc::Sender<()>, + handle: std::thread::JoinHandle<()>, +} + +impl Close { + pub fn close(self) { + self.close.send(()).unwrap(); + self.handle.join().unwrap(); + println!("CLOSING"); + queue!(std::io::stdout(), crossterm::event::DisableMouseCapture).unwrap(); + } +} + +struct LimitedVec { + inner: Vec, + size: usize, + pos: usize, +} + +impl LimitedVec { + fn new(size: usize) -> Self { + Self { + inner: Vec::with_capacity(size), + size, + pos: 0, + } + } + + fn push(&mut self, v: T) { + // while self.inner.len() >= self.size { + // self.inner.remove(0); + // } + if self.pos != 0 { + self.pos += 1; + } + self.inner.push(v); + // assert!(self.inner.len() <= self.size) + } + + fn iter(&self) -> std::slice::Iter { + let start = if self.inner.len() < self.size { + 0 + } else { + self.inner + .len() + .checked_sub(self.size) + .and_then(|x| x.checked_sub(self.pos)) + .unwrap_or(0) + }; + let mut end = self.inner.len().checked_sub(self.pos).unwrap_or(0); + if end < self.inner.len() { + end = self.inner.len() + } + self.inner[start..end].iter() + } + + fn scroll_up(&mut self) { + if self.pos + 1 < self.inner.len() { + self.pos += 1; + } + } + + fn scroll_down(&mut self) { + if self.pos > 0 { + self.pos -= 1; + } + } +} diff --git a/server/src/main.rs b/server/src/main.rs index ee90a10..679ddf2 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -1,12 +1,25 @@ mod grpc; mod db; mod games; +mod logger; #[tokio::main] async fn main() { + let (close, _stdin) = logger::setup().unwrap(); // protobuf::route_guide_grpc::RouteGuid + for i in 0..100 { + std::thread::sleep(std::time::Duration::from_secs_f32(0.1)); + + if i == 69 { + log::info!("69 Nice"); + std::thread::sleep(std::time::Duration::from_secs_f32(0.2)); + }else{ + log::info!("{}", i); + } + } + let games = games::load_games(); - println!("{:?}", games); + log::info!("{:?}", games); games[0].run(4); // let pool = db::DbPool::new().await; // let mut conn = pool.acquire().await; @@ -14,4 +27,8 @@ async fn main() { // println!("{:?}", conn.users().await); // conn.close().await; // grpc::start(pool).await; + // crossterm::terminal::disable_raw_mode().unwrap(); + // loop {} + + close.close(); }