Skip to content

Commit

Permalink
Lobby: store DE Connector socket
Browse files Browse the repository at this point in the history
  • Loading branch information
Indy2222 committed Jul 16, 2023
1 parent 5d5abb8 commit f4a6b30
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 22 deletions.
58 changes: 54 additions & 4 deletions crates/lobby/src/games/db.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use std::net::SocketAddr;

use anyhow::{Context, Result};
use de_lobby_model::{
Game, GameConfig, GameListing, GameMap, GamePartial, MAP_HASH_LEN, MAX_GAME_NAME_LEN,
MAX_MAP_NAME_LEN, MAX_USERNAME_LEN,
Game, GameConfig, GameListing, GameMap, GamePartial, GameSetup, MAP_HASH_LEN,
MAX_GAME_NAME_LEN, MAX_MAP_NAME_LEN, MAX_USERNAME_LEN,
};
use futures_util::TryStreamExt;
use log::info;
Expand All @@ -16,6 +18,10 @@ use crate::{
db_error,
};

// This should correspond to the longest valid socket address. IPv6 hast up to
// 39 characters + colon + 5 characters for port number.
const SERVER_LEN: usize = 45;

#[derive(Clone)]
pub(super) struct Games {
pool: &'static Pool<Sqlite>,
Expand All @@ -33,6 +39,7 @@ impl Games {
game_name_len = MAX_GAME_NAME_LEN,
map_name_len = MAX_MAP_NAME_LEN,
map_hash_len = MAP_HASH_LEN,
server_len = SERVER_LEN,
);

info!("Initializing games...");
Expand Down Expand Up @@ -64,18 +71,50 @@ impl Games {
Ok(games)
}

/// This method retrieves complete info about a single game.
pub(super) async fn get(&self, game: &str) -> Result<Option<Game>> {
let Some(game_row) = query("SELECT * FROM games WHERE game = ?;")
.bind(game)
.fetch_optional(self.pool)
.await
.context("Failed to retrieve a game from the DB")?
else {
return Ok(None);
};

let setup = GameSetup::try_from_row(game_row)?;

let mut players = Vec::new();
let mut player_rows = query("SELECT username FROM players WHERE game = ?;")
.bind(game)
.fetch(self.pool);

while let Some(player_row) = player_rows
.try_next()
.await
.context("Failed to retrieve game players from the DB")?
{
let username: String = player_row.try_get("username")?;
players.push(username);
}

Ok(Some(Game::new(setup, players)))
}

/// This method creates a new game in the DB and places all users to it.
pub(super) async fn create(&self, game: Game) -> Result<(), CreationError> {
let game_config = game.config();
let game_setup = game.setup();
let game_config = game_setup.config();

let mut transaction = self.pool.begin().await.map_err(CreationError::Database)?;

let result =
query("INSERT INTO games (name, max_players, map_hash, map_name) VALUES(?, ?, ?, ?);")
query("INSERT INTO games (name, max_players, map_hash, map_name, server) VALUES(?, ?, ?, ?, ?);")
.bind(game_config.name())
.bind(game_config.max_players())
.bind(game_config.map().hash())
.bind(game_config.map().name())
.bind(game_setup.server().to_string())
.execute(&mut transaction)
.await;
db_error!(
Expand Down Expand Up @@ -244,6 +283,17 @@ pub(super) enum RemovalError {
Database(#[source] sqlx::Error),
}

impl FromRow for GameSetup {
type Error = anyhow::Error;

fn try_from_row(row: SqliteRow) -> Result<Self, Self::Error> {
let server: String = row.try_get("server")?;
let server: SocketAddr = server.parse()?;
let config = GameConfig::try_from_row(row)?;
Ok(Self::new(server, config))
}
}

impl FromRow for GamePartial {
type Error = anyhow::Error;

Expand Down
26 changes: 20 additions & 6 deletions crates/lobby/src/games/endpoints.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use actix_web::{get, post, put, web, HttpResponse, Responder};
use de_lobby_model::{Game, GameConfig, Validatable};
use de_lobby_model::{Game, GameSetup, Validatable};
use log::{error, warn};

use super::db::{AdditionError, CreationError, Games, RemovalError};
Expand All @@ -10,6 +10,7 @@ pub(super) fn configure(cfg: &mut web::ServiceConfig) {
cfg.service(
web::scope("/games")
.service(create)
.service(get)
.service(list)
.service(join)
.service(leave),
Expand All @@ -20,15 +21,15 @@ pub(super) fn configure(cfg: &mut web::ServiceConfig) {
async fn create(
claims: web::ReqData<Claims>,
games: web::Data<Games>,
game_config: web::Json<GameConfig>,
game_setup: web::Json<GameSetup>,
) -> impl Responder {
let game_config = game_config.into_inner();
if let Err(error) = game_config.validate() {
warn!("Invalid game configuration: {:?}", error);
let game_setup = game_setup.into_inner();
if let Err(error) = game_setup.validate() {
warn!("Invalid game setup: {:?}", error);
return HttpResponse::BadRequest().json(format!("{error}"));
}

let game = Game::new(game_config, claims.username().to_owned());
let game = Game::from_author(game_setup, claims.username().to_owned());
match games.create(game).await {
Ok(_) => HttpResponse::Ok().json(()),
Err(CreationError::NameTaken) => {
Expand All @@ -46,6 +47,19 @@ async fn create(
}
}

#[get("/{name}")]
async fn get(path: web::Path<String>, games: web::Data<Games>) -> impl Responder {
let name = path.into_inner();
match games.get(&name).await {
Ok(Some(game)) => HttpResponse::Ok().json(game),
Ok(None) => HttpResponse::NotFound().json("Game not found"),
Err(error) => {
error!("Game get error: {:?}", error);
HttpResponse::InternalServerError().finish()
}
}
}

#[get("")]
async fn list(games: web::Data<Games>) -> impl Responder {
match games.list().await {
Expand Down
3 changes: 2 additions & 1 deletion crates/lobby/src/games/init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ CREATE TABLE IF NOT EXISTS games (
name CHARACTER({game_name_len}) NOT NULL PRIMARY KEY,
max_players TINYINT NOT NULL,
map_hash CHARACTER({map_hash_len}) NOT NULL,
map_name CHARACTER({map_name_len}) NOT NULL
map_name CHARACTER({map_name_len}) NOT NULL,
server CHARACTER({server_len}) NOT NULL
);

CREATE TABLE IF NOT EXISTS players (
Expand Down
46 changes: 38 additions & 8 deletions crates/lobby_model/src/games.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::net::SocketAddr;

use serde::{Deserialize, Serialize};

use crate::{ensure, validation};
Expand All @@ -10,21 +12,22 @@ const MAX_PLAYERS: u8 = 4;
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Game {
config: GameConfig,
setup: GameSetup,
players: Vec<String>,
}

impl Game {
/// Creates a new game with the author being the only player.
pub fn new(config: GameConfig, author: String) -> Self {
Self {
config,
players: vec![author],
}
pub fn from_author(setup: GameSetup, author: String) -> Self {
Self::new(setup, vec![author])
}

pub fn config(&self) -> &GameConfig {
&self.config
pub fn new(setup: GameSetup, players: Vec<String>) -> Self {
Self { setup, players }
}

pub fn setup(&self) -> &GameSetup {
&self.setup
}

pub fn players(&self) -> &[String] {
Expand Down Expand Up @@ -74,6 +77,33 @@ impl GamePartial {
}
}

#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GameSetup {
server: SocketAddr,
config: GameConfig,
}

impl GameSetup {
pub fn new(server: SocketAddr, config: GameConfig) -> Self {
Self { server, config }
}

pub fn server(&self) -> SocketAddr {
self.server
}

pub fn config(&self) -> &GameConfig {
&self.config
}
}

impl validation::Validatable for GameSetup {
fn validate(&self) -> validation::Result {
self.config.validate()
}
}

#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GameConfig {
Expand Down
4 changes: 2 additions & 2 deletions crates/lobby_model/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ pub use auth::{
MIN_PASSWORD_LEN,
};
pub use games::{
Game, GameConfig, GameListing, GameMap, GamePartial, MAP_HASH_LEN, MAX_GAME_NAME_LEN,
MAX_MAP_NAME_LEN,
Game, GameConfig, GameListing, GameMap, GamePartial, GameSetup, MAP_HASH_LEN,
MAX_GAME_NAME_LEN, MAX_MAP_NAME_LEN,
};
pub use validation::Validatable;

Expand Down
40 changes: 39 additions & 1 deletion docs/src/multiplayer/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ paths:
content:
application/json:
schema:
$ref: "#/components/schemas/game-config"
$ref: "#/components/schemas/game-setup"
responses:
"200":
description: The game was successfully crated and the user joined it.
Expand All @@ -112,6 +112,34 @@ paths:
"409":
description: A different game with the same name already exists.

/a/games/{name}:
get:
summary: Get complete information about a game.
security:
- bearerAuth: []
parameters:
- name: name
in: path
required: true
schema:
type: string
responses:
"200":
description: Game successfully retrieved.
content:
application/json:
schema:
type: object
properties:
players:
type: array
items:
type: string
setup:
$ref: "#/components/schemas/game-setup"
"404":
description: The game does not exist.

/a/games/{name}/join:
put:
summary: Join the game.
Expand Down Expand Up @@ -189,6 +217,16 @@ components:
A unique user name. It is a non-empty Unicode string with maximum
length of 32 bytes when encoded in UTF-8. It does not start or end
with whitespace.
game-setup:
type: object
properties:
server:
type: string
description: >-
An IPv4 or IPv6 socket address. For example 127.0.0.1:8082.
config:
$ref: "#/components/schemas/game-config"

game-config:
type: object
properties:
Expand Down

0 comments on commit f4a6b30

Please sign in to comment.