From 71690d71ff39853922e2c7c39d10470ece9d1649 Mon Sep 17 00:00:00 2001 From: Martin Indra Date: Wed, 16 Nov 2022 10:44:27 +0100 Subject: [PATCH] Kick of game server Relates to #255. --- Cargo.toml | 2 + crates/server/Cargo.toml | 17 +++++++++ crates/server/src/game.rs | 78 +++++++++++++++++++++++++++++++++++++++ crates/server/src/main.rs | 59 +++++++++++++++++++++++++++++ crates/server/src/user.rs | 32 ++++++++++++++++ 5 files changed, 188 insertions(+) create mode 100644 crates/server/Cargo.toml create mode 100644 crates/server/src/game.rs create mode 100644 crates/server/src/main.rs create mode 100644 crates/server/src/user.rs diff --git a/Cargo.toml b/Cargo.toml index 56dbda548..2463d8140 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -83,6 +83,7 @@ de_ui = { path = "crates/ui", version = "0.1.0-dev" } de_uom = { path = "crates/uom", version = "0.1.0-dev" } # Other +actix-web = "4.2.1" ahash = "0.7.6" anyhow = "1.0" approx = "0.5.1" @@ -98,6 +99,7 @@ gltf = "1.0" itertools = "0.10.5" iyes_loopless = "0.9.1" iyes_progress = { version = "0.7.1", features = [ "iyes_loopless" ] } +jsonwebtoken = "8.1.1" nalgebra = { version = "0.31.0", features = ["convert-glam022"] } ntest = "0.9.0" parry2d = "0.11.0" diff --git a/crates/server/Cargo.toml b/crates/server/Cargo.toml new file mode 100644 index 000000000..fd90531bb --- /dev/null +++ b/crates/server/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "de_server" +description = "Digital Extinction lobby server." + +version.workspace = true +edition.workspace = true +authors.workspace = true +repository.workspace = true +keywords.workspace = true +homepage.workspace = true +license.workspace = true +categories.workspace = true + +[dependencies] +actix-web.workspace = true +jsonwebtoken.workspace = true +serde.workspace = true diff --git a/crates/server/src/game.rs b/crates/server/src/game.rs new file mode 100644 index 000000000..f35de309c --- /dev/null +++ b/crates/server/src/game.rs @@ -0,0 +1,78 @@ +use std::{ + collections::HashMap, + sync::{Mutex, MutexGuard}, +}; + +use serde::{Deserialize, Serialize}; + +#[derive(Default)] +pub struct Games(Mutex); + +impl Games { + pub fn inner(&self) -> MutexGuard<'_, GamesInner> { + self.0.lock().unwrap() + } +} + +#[derive(Default)] +pub struct GamesInner { + max_id: u16, + games: HashMap, +} + +impl GamesInner { + pub fn games(&self) -> &HashMap { + &self.games + } + + pub fn new_game(&mut self, config: GameConfig) { + self.games.insert(self.max_id, Game::empty(config)); + self.max_id = self.max_id.wrapping_add(1); + } +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Game { + config: GameConfig, + players: Vec, +} + +impl Game { + pub fn empty(config: GameConfig) -> Self { + Self { + config, + players: Vec::new(), + } + } + + /// Adds the player into the game. + /// + /// # Panics + /// + /// May panic if the player is already part of the game. + pub fn join(&mut self, player: &str) -> Result<(), JoinError> { + if self.config.max_players as usize <= self.players.len() { + return Err(JoinError::GameIsFull); + } + + debug_assert!(self.players.iter().all(|p| p != player)); + self.players.push(player.into()); + Ok(()) + } +} + +pub enum JoinError { + GameIsFull, +} + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GameConfig { + name: String, + max_players: u8, +} + +impl GameConfig { + // TODO +} diff --git a/crates/server/src/main.rs b/crates/server/src/main.rs new file mode 100644 index 000000000..cea27d16c --- /dev/null +++ b/crates/server/src/main.rs @@ -0,0 +1,59 @@ +use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder}; +use game::{Game, GameConfig, Games}; +use user::Users; + +mod game; +mod user; + +#[actix_web::main] +async fn main() -> std::io::Result<()> { + let users_data = web::Data::new(Users::default()); + let games_data = web::Data::new(Games::default()); + + HttpServer::new(move || { + let json_cfg = web::JsonConfig::default() + // TODO a constant + .limit(10 * 1024) + .content_type_required(true); + // TODO + //.content_type(|mime| mime == mime::APPLICATION_JSON); + + let users_scope = web::scope("/users"); + let games_scope = web::scope("/games") + .app_data(games_data.clone()) + .service(list_games) + .service(create_game); + + App::new() + .app_data(users_data.clone()) + .service(users_scope) + .service(games_scope) + }) + .bind(("127.0.0.1", 8080))? + .run() + .await +} + +// TODO sing-up / sign-in request +// TODO validate JWT in every other request +// TODO join game +// TODO leave game game +// TODO remove game if everybody abandons it +// TODO start game (remove it and create rendezvous) + +#[get("/")] +async fn list_games(games: web::Data) -> impl Responder { + HttpResponse::Ok().json(games.inner().games()) +} + +#[post("/")] +async fn create_game(games: web::Data, game: web::Json) -> impl Responder { + // TODO require user ID header + // TODO disconnect user from other games (including this one -> re-connect) + // TODO place the user into the game + + // TODO validate game parameters + // * 2 <= max_players <= 4 + games.inner().new_game(game.0); + HttpResponse::Ok().json(()) +} diff --git a/crates/server/src/user.rs b/crates/server/src/user.rs new file mode 100644 index 000000000..d7112ddc9 --- /dev/null +++ b/crates/server/src/user.rs @@ -0,0 +1,32 @@ +use std::{ + collections::HashMap, + sync::{Mutex, MutexGuard}, +}; + +use serde::{Deserialize, Serialize}; + +#[derive(Default)] +pub struct Users(Mutex); + +impl Users { + pub fn inner(&self) -> MutexGuard<'_, UsersInner> { + self.0.lock().unwrap() + } +} + +#[derive(Default)] +pub struct UsersInner { + users: HashMap, +} + +impl UsersInner { + pub fn get(&self, id: &str) -> Option<&User> { + self.users.get(id) + } +} + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct User { + name: String, +}