Browse Source

Add TUI

new_protocol
ThePerkinrex 5 years ago
parent
commit
f26f902d37
No known key found for this signature in database GPG Key ID: 1F45A7C4BFB41607
  1. 3
      protobuf/game.proto
  2. 3
      server/.gitignore
  3. 5
      server/Cargo.toml
  4. 4
      server/src/games/config.rs
  5. 3
      server/src/games/mod.rs
  6. 1
      server/src/games/run.rs
  7. 4
      server/src/grpc.rs
  8. 3
      server/src/grpc/game.rs
  9. 257
      server/src/logger.rs
  10. 19
      server/src/main.rs

3
protobuf/game.proto

@ -2,13 +2,14 @@ syntax = "proto3";
package game; package game;
// Interface exported by the server. // Connection utilities to get a client_id
service Connection { service Connection {
rpc connect(Username) returns(UserID); rpc connect(Username) returns(UserID);
rpc joinLobbyWithCode(LobbyCode) returns(Null); rpc joinLobbyWithCode(LobbyCode) returns(Null);
rpc joinLobbyWithoutCode(Null) returns(LobbyCode); rpc joinLobbyWithoutCode(Null) returns(LobbyCode);
} }
// Lobby functionality (client_id required for most of them)
service Lobby { service Lobby {
rpc getGames(Null) returns(stream Game); rpc getGames(Null) returns(stream Game);
rpc vote(Vote) returns(Null); rpc vote(Vote) returns(Null);

3
server/.gitignore

@ -1,4 +1,5 @@
/target /target
Cargo.lock Cargo.lock
db.sqlite* db.sqlite*
/games /games
output.log

5
server/Cargo.toml

@ -26,6 +26,11 @@ prost = "0.6"
# Database (SQLite) # Database (SQLite)
sqlx = { version = "0.3", default-features = false, features = [ "runtime-tokio", "macros", "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] [build-dependencies]
# Grpc codegen # Grpc codegen
tonic-build = "0.3" tonic-build = "0.3"

4
server/src/games/config.rs

@ -64,11 +64,11 @@ impl Config {
pub fn load<P: AsRef<std::path::Path> + std::fmt::Debug>(file: P) -> Self { pub fn load<P: AsRef<std::path::Path> + std::fmt::Debug>(file: P) -> Self {
serde_json::from_reader(std::fs::File::open(&file).unwrap()) serde_json::from_reader(std::fs::File::open(&file).unwrap())
.map_err(|e| { .map_err(|e| {
eprintln!( log::error!(
"Malformed game defintion file @ {}", "Malformed game defintion file @ {}",
file.as_ref().display() file.as_ref().display()
); );
eprintln!("JSON Error: {}", e); log::error!("JSON Error: {}", e);
panic!() panic!()
}) })
.unwrap() .unwrap()

3
server/src/games/mod.rs

@ -1,4 +1,5 @@
use rhai::AST; use rhai::AST;
use log::info;
use std::fs::read_dir; use std::fs::read_dir;
mod config; mod config;
@ -21,7 +22,7 @@ impl Game {
let conf = config::Config::load(folder.as_ref().join("game.json")); 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(); 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 } Self { conf: conf.clone(), name: conf.name, version: conf.version, authors: conf.authors, ast }
} }

1
server/src/games/run.rs

@ -33,6 +33,7 @@ impl RunningGame {
pub fn new(ast: AST, conf: &Config, players: u32) -> Self { pub fn new(ast: AST, conf: &Config, players: u32) -> Self {
let mut engine = Engine::new(); let mut engine = Engine::new();
engine.register_result_fn("shuffle", shuffle_pile); 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 setup = Func::<(Dynamic,), Dynamic>::create_from_ast(engine, ast, "setup");
let piles = conf.piles.clone(); let piles = conf.piles.clone();

4
server/src/grpc.rs

@ -1,5 +1,7 @@
use tonic::{transport::Server, Request, Response, Status}; use tonic::{transport::Server, Request, Response, Status};
use log::info;
use std::sync::Arc; use std::sync::Arc;
mod game; mod game;
@ -19,7 +21,7 @@ impl Connection for ConnectionService {
let name = request.into_inner().name; let name = request.into_inner().name;
let mut conn = self.conn.acquire().await; let mut conn = self.conn.acquire().await;
let uuid = conn.add_user(&name).await; let uuid = conn.add_user(&name).await;
println!("Connected {}[{}]", name, uuid); info!("Connected {}[{}]", name, uuid);
conn.close().await; conn.close().await;
Ok(Response::new(UserId { Ok(Response::new(UserId {
id: uuid.to_hyphenated().to_string(), id: uuid.to_hyphenated().to_string(),

3
server/src/grpc/game.rs

@ -67,7 +67,7 @@ pub mod connection_server {
request: tonic::Request<super::Null>, request: tonic::Request<super::Null>,
) -> Result<tonic::Response<super::LobbyCode>, tonic::Status>; ) -> Result<tonic::Response<super::LobbyCode>, tonic::Status>;
} }
#[doc = " Interface exported by the server."] #[doc = " Connection utilities to get a client_id"]
#[derive(Debug)] #[derive(Debug)]
pub struct ConnectionServer<T: Connection> { pub struct ConnectionServer<T: Connection> {
inner: _Inner<T>, inner: _Inner<T>,
@ -250,6 +250,7 @@ pub mod lobby_server {
request: tonic::Request<super::Null>, request: tonic::Request<super::Null>,
) -> Result<tonic::Response<super::LobbyStatus>, tonic::Status>; ) -> Result<tonic::Response<super::LobbyStatus>, tonic::Status>;
} }
#[doc = " Lobby functionality (client_id required for most of them)"]
#[derive(Debug)] #[derive(Debug)]
pub struct LobbyServer<T: Lobby> { pub struct LobbyServer<T: Lobby> {
inner: _Inner<T>, inner: _Inner<T>,

257
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<String>), 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<String>;
pub type Stdin<T> = mpsc::Receiver<T>;
struct TerminalHandler<T, F>
where
T: Send + 'static,
F: Fn(String) -> T + Send + 'static,
{
stdout: mpsc::Receiver<String>,
stdin: mpsc::Sender<T>,
close: mpsc::Receiver<()>,
handler: F,
lines: LimitedVec<String>,
command: String,
}
impl<T, F> TerminalHandler<T, F>
where
T: Send + 'static,
F: Fn(String) -> T + Send + 'static,
{
fn new(
handler: F,
) -> (
Stdout,
Stdin<T>,
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<T, F> Drop for TerminalHandler<T, F>
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<T> {
inner: Vec<T>,
size: usize,
pos: usize,
}
impl<T> LimitedVec<T> {
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<T> {
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;
}
}
}

19
server/src/main.rs

@ -1,12 +1,25 @@
mod grpc; mod grpc;
mod db; mod db;
mod games; mod games;
mod logger;
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
let (close, _stdin) = logger::setup().unwrap();
// protobuf::route_guide_grpc::RouteGuid // 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(); let games = games::load_games();
println!("{:?}", games); log::info!("{:?}", games);
games[0].run(4); games[0].run(4);
// let pool = db::DbPool::new().await; // let pool = db::DbPool::new().await;
// let mut conn = pool.acquire().await; // let mut conn = pool.acquire().await;
@ -14,4 +27,8 @@ async fn main() {
// println!("{:?}", conn.users().await); // println!("{:?}", conn.users().await);
// conn.close().await; // conn.close().await;
// grpc::start(pool).await; // grpc::start(pool).await;
// crossterm::terminal::disable_raw_mode().unwrap();
// loop {}
close.close();
} }

Loading…
Cancel
Save