Initial Commit

This commit is contained in:
Mrrp 2025-03-23 12:10:04 -07:00
commit a7a2d5e9c5
10 changed files with 1636 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
target

1426
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

2
Cargo.toml Normal file
View file

@ -0,0 +1,2 @@
[workspace]
members = ["lobby"]

18
lobby/Cargo.toml Normal file
View file

@ -0,0 +1,18 @@
[package]
name = "mad-lobby-service"
version = "0.1.0"
edition = "2024"
[dependencies]
axum = { version = "0.8" }
juniper = "0.16"
tokio = { version = "1.20", features = ["macros", "net", "rt-multi-thread", "time"] }
tokio-stream = "0.1"
tokio-tungstenite = "0.26"
anyhow = "1.0"
juniper_axum = "0.2"
rand = "0.9"
jwt = "0.16"
hmac = "0.12"
sha2 = "0.10"
uuid = { version = "1.16", features = ["v4"] }

0
lobby/LICENSE Normal file
View file

0
lobby/src/client/mod.rs Normal file
View file

61
lobby/src/main.rs Normal file
View file

@ -0,0 +1,61 @@
pub mod client;
pub mod server;
use std::{net::SocketAddr, sync::Arc};
use axum::{
Extension, Router,
response::Html,
routing::{MethodFilter, get, on},
};
use juniper::{graphql_object, EmptyMutation, EmptySubscription, RootNode};
use juniper_axum::{graphiql, graphql, playground};
use tokio::net::TcpListener;
#[derive(Clone, Copy, Debug)]
pub struct Query;
#[graphql_object]
impl Query {
/// Adds two `a` and `b` numbers.
fn add(a: i32, b: i32) -> i32 {
a + b
}
}
type Schema = RootNode<'static, Query, EmptyMutation, EmptySubscription>;
async fn homepage() -> Html<&'static str> {
"<html><h1>juniper_axum/simple example</h1>\
<div>visit <a href=\"/graphiql\">GraphiQL</a></div>\
<div>visit <a href=\"/playground\">GraphQL Playground</a></div>\
</html>"
.into()
}
#[tokio::main]
async fn main() {
let schema = Schema::new(Query, EmptyMutation::new(), EmptySubscription::new());
let app = Router::new()
.route(
"/graphql",
on(
MethodFilter::GET.or(MethodFilter::POST),
graphql::<Arc<Schema>>,
),
)
.route("/graphiql", get(graphiql("/graphql", "/subscriptions")))
.route("/playground", get(playground("/graphql", "/subscriptions")))
.route("/", get(homepage))
.layer(Extension(Arc::new(schema)));
let addr = SocketAddr::from(([127, 0, 0, 1], 8080));
let listener = TcpListener::bind(addr)
.await
.unwrap_or_else(|e| panic!("failed to listen on {addr}: {e}"));
axum::serve(listener, app)
.await
.unwrap_or_else(|e| panic!("failed to run `axum::serve`: {e}"));
}

View file

@ -0,0 +1,31 @@
use juniper::graphql_object;
/* -------------------------------------------------------------------------- */
/* Roots */
/* -------------------------------------------------------------------------- */
#[derive(Clone, Copy, Debug)]
pub struct Query;
#[graphql_object]
impl Query {
fn lobbies(&self) -> String {
"".to_string() // TODO
}
}
/* -------------------------------------------------------------------------- */
/* Objects */
/* -------------------------------------------------------------------------- */
#[derive(Clone, Debug)]
pub struct QueryLobby {
id: String
}
#[graphql_object]
impl QueryLobby {
fn id(&self) -> String {
self.id.clone()
}
}

95
lobby/src/server/logic.rs Normal file
View file

@ -0,0 +1,95 @@
use std::{
collections::BTreeMap,
time::{SystemTime, UNIX_EPOCH},
};
use hmac::{Hmac, Mac};
use rand::RngCore;
use sha2::Sha256;
fn unix_time() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
}
pub enum LobbyStatus {
Alive,
Inactive,
}
pub struct Lobby {
last_heartbeat: u64,
status: LobbyStatus,
}
impl Lobby {
fn send_heartbeat(&mut self) {
self.last_heartbeat = unix_time();
}
}
pub struct LobbyManager {
heartbeat_timeout: u64,
server_key: Hmac<Sha256>,
lobbies: BTreeMap<String, Lobby>,
}
impl LobbyManager {
fn new() -> Self {
let mut server_key_token: [u8; 64] = [0; 64];
{
rand::rng().fill_bytes(&mut server_key_token);
}
// TODO: Handle this expect better
let server_key: Hmac<Sha256> = Hmac::new_from_slice(&server_key_token).expect("bad token?");
Self {
heartbeat_timeout: 30,
server_key,
lobbies: BTreeMap::new(),
}
}
fn create_lobby(&mut self) {
let lobby = Lobby {
last_heartbeat: unix_time(),
status: LobbyStatus::Alive,
};
self.lobbies.insert(uuid::Uuid::new_v4().to_string(), lobby);
}
fn tick(&mut self) {
for lobby in &mut self.lobbies {
if lobby.1.last_heartbeat > unix_time() + self.heartbeat_timeout {
match lobby.1.status {
LobbyStatus::Alive => lobby.1.status = LobbyStatus::Inactive,
_ => {}
}
}
}
}
fn cull_lobbies(&mut self) -> Result<usize, ()> {
let mut key_list: Vec<String> = Vec::new();
{
for lobby in &self.lobbies {
match lobby.1.status {
LobbyStatus::Inactive => key_list.push(lobby.0.clone()),
_ => {}
}
}
}
{
for key in &key_list {
self.lobbies.remove(key);
}
}
Ok(key_list.len())
}
}

2
lobby/src/server/mod.rs Normal file
View file

@ -0,0 +1,2 @@
mod gql_schema;
mod logic;