generated from tipragot/rust
This commit is contained in:
parent
01924cdcb8
commit
27e0aad29d
120
Cargo.lock
generated
120
Cargo.lock
generated
|
@ -17,6 +17,19 @@ version = "1.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ahash"
|
||||||
|
version = "0.8.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"const-random",
|
||||||
|
"once_cell",
|
||||||
|
"version_check",
|
||||||
|
"zerocopy",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.81"
|
version = "1.0.81"
|
||||||
|
@ -34,6 +47,12 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "atomic"
|
||||||
|
version = "0.5.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
|
@ -170,6 +189,26 @@ version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "const-random"
|
||||||
|
version = "0.1.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359"
|
||||||
|
dependencies = [
|
||||||
|
"const-random-macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "const-random-macro"
|
||||||
|
version = "0.1.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
"once_cell",
|
||||||
|
"tiny-keccak",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cpufeatures"
|
name = "cpufeatures"
|
||||||
version = "0.2.12"
|
version = "0.2.12"
|
||||||
|
@ -179,6 +218,12 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crunchy"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crypto-common"
|
name = "crypto-common"
|
||||||
version = "0.1.6"
|
version = "0.1.6"
|
||||||
|
@ -218,6 +263,18 @@ dependencies = [
|
||||||
"crypto-common",
|
"crypto-common",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "flurry"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7874ce5eeafa5e546227f7c62911e586387bf03d6c9a45ac78aa1c3bc2fedb61"
|
||||||
|
dependencies = [
|
||||||
|
"ahash",
|
||||||
|
"num_cpus",
|
||||||
|
"parking_lot",
|
||||||
|
"seize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fnv"
|
name = "fnv"
|
||||||
version = "1.0.7"
|
version = "1.0.7"
|
||||||
|
@ -482,9 +539,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.20"
|
version = "0.4.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
|
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "matchit"
|
name = "matchit"
|
||||||
|
@ -549,6 +606,16 @@ version = "1.19.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking_lot"
|
||||||
|
version = "0.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
|
||||||
|
dependencies = [
|
||||||
|
"lock_api",
|
||||||
|
"parking_lot_core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot_core"
|
name = "parking_lot_core"
|
||||||
version = "0.9.9"
|
version = "0.9.9"
|
||||||
|
@ -681,12 +748,28 @@ version = "1.0.16"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c"
|
checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scc"
|
||||||
|
version = "2.0.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e4c10d60d2fd9faf0e8b6540623f5e502343bde2ac585a7d158b3db24d93fcbe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scopeguard"
|
name = "scopeguard"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "seize"
|
||||||
|
version = "0.2.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0e5739de653b129b0a59da381599cf17caf24bc586f6a797c52d3d6147c5b85a"
|
||||||
|
dependencies = [
|
||||||
|
"num_cpus",
|
||||||
|
"once_cell",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.197"
|
version = "1.0.197"
|
||||||
|
@ -748,9 +831,12 @@ dependencies = [
|
||||||
"axum",
|
"axum",
|
||||||
"bincode",
|
"bincode",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
|
"flurry",
|
||||||
"futures",
|
"futures",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
|
"log",
|
||||||
"rand",
|
"rand",
|
||||||
|
"scc",
|
||||||
"serde",
|
"serde",
|
||||||
"slotmap",
|
"slotmap",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
@ -846,6 +932,15 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tiny-keccak"
|
||||||
|
version = "2.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
|
||||||
|
dependencies = [
|
||||||
|
"crunchy",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tinyvec"
|
name = "tinyvec"
|
||||||
version = "1.6.0"
|
version = "1.6.0"
|
||||||
|
@ -1018,6 +1113,7 @@ version = "1.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0"
|
checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"atomic",
|
||||||
"getrandom",
|
"getrandom",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
@ -1165,3 +1261,23 @@ name = "windows_x86_64_msvc"
|
||||||
version = "0.52.0"
|
version = "0.52.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
|
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy"
|
||||||
|
version = "0.7.32"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be"
|
||||||
|
dependencies = [
|
||||||
|
"zerocopy-derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy-derive"
|
||||||
|
version = "0.7.32"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
|
@ -11,13 +11,16 @@ anyhow = "1.0.81"
|
||||||
axum = { version = "0.7.5", features = ["ws"] }
|
axum = { version = "0.7.5", features = ["ws"] }
|
||||||
bincode = "1.3.3"
|
bincode = "1.3.3"
|
||||||
dashmap = "5.5.3"
|
dashmap = "5.5.3"
|
||||||
|
flurry = "0.5.0"
|
||||||
futures = "0.3.30"
|
futures = "0.3.30"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
|
log = "0.4.21"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
|
scc = "2.0.19"
|
||||||
serde = { version = "1.0.197", features = ["derive"] }
|
serde = { version = "1.0.197", features = ["derive"] }
|
||||||
slotmap = { version = "1.0.7", features = ["serde"] }
|
slotmap = { version = "1.0.7", features = ["serde"] }
|
||||||
tokio = { version = "1.37.0", features = ["macros", "rt-multi-thread"] }
|
tokio = { version = "1.37.0", features = ["macros", "rt-multi-thread"] }
|
||||||
uuid = { version = "1.8.0", features = ["v4", "serde"] }
|
uuid = { version = "1.8.0", features = ["v4", "serde", "v7"] }
|
||||||
|
|
||||||
# [lints]
|
# [lints]
|
||||||
# workspace = true
|
# workspace = true
|
||||||
|
|
42
crates/server/src dada/lib.rs
Normal file
42
crates/server/src dada/lib.rs
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
use std::hash::Hash;
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
|
use dashmap::mapref::multiple::RefMutMulti;
|
||||||
|
use dashmap::mapref::one::RefMut;
|
||||||
|
|
||||||
|
pub enum GlobalRefMut<'a, K: Eq + Hash, V> {
|
||||||
|
Single(RefMut<'a, K, V>),
|
||||||
|
Multi(RefMutMulti<'a, K, V>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, K: Eq + Hash, V> From<RefMut<'a, K, V>> for GlobalRefMut<'a, K, V> {
|
||||||
|
fn from(v: RefMut<'a, K, V>) -> Self {
|
||||||
|
Self::Single(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, K: Eq + Hash, V> From<RefMutMulti<'a, K, V>> for GlobalRefMut<'a, K, V> {
|
||||||
|
fn from(v: RefMutMulti<'a, K, V>) -> Self {
|
||||||
|
Self::Multi(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, K: Eq + Hash, V> Deref for GlobalRefMut<'a, K, V> {
|
||||||
|
type Target = V;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
match self {
|
||||||
|
Self::Single(v) => v.value(),
|
||||||
|
Self::Multi(v) => v.value(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, K: Eq + Hash, V> DerefMut for GlobalRefMut<'a, K, V> {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
match self {
|
||||||
|
Self::Single(v) => v.value_mut(),
|
||||||
|
Self::Multi(v) => v.value_mut(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
182
crates/server/src dada/main.rs
Normal file
182
crates/server/src dada/main.rs
Normal file
|
@ -0,0 +1,182 @@
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use std::hash::RandomState;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use anyhow::{bail, Context};
|
||||||
|
use axum::extract::ws::{Message, WebSocket};
|
||||||
|
use axum::extract::WebSocketUpgrade;
|
||||||
|
use axum::routing::get;
|
||||||
|
use axum::Router;
|
||||||
|
use dashmap::mapref::entry::Entry;
|
||||||
|
use dashmap::mapref::multiple::RefMutMulti;
|
||||||
|
use dashmap::mapref::one::RefMut;
|
||||||
|
use dashmap::DashMap;
|
||||||
|
use futures::stream::{SplitSink, SplitStream};
|
||||||
|
use futures::{SinkExt, StreamExt};
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use serde::de::DeserializeOwned;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use server::GlobalRefMut;
|
||||||
|
use slotmap::SlotMap;
|
||||||
|
use tokio::sync::mpsc::{channel, Receiver, Sender};
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ConnectionSender(Sender<Vec<u8>>);
|
||||||
|
|
||||||
|
impl ConnectionSender {
|
||||||
|
pub async fn send<T: Serialize>(&mut self, message: T) -> anyhow::Result<()> {
|
||||||
|
Ok(self.0.send(bincode::serialize(&message)?).await?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ConnectionReader(SplitStream<WebSocket>);
|
||||||
|
|
||||||
|
impl ConnectionReader {
|
||||||
|
pub async fn read<T: DeserializeOwned>(&mut self) -> anyhow::Result<T> {
|
||||||
|
loop {
|
||||||
|
let Message::Binary(message) = self.0.next().await.context("no message")?? else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
return Ok(bincode::deserialize(&message)?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
let app = Router::new().route(
|
||||||
|
"/",
|
||||||
|
get(|ws: WebSocketUpgrade| async {
|
||||||
|
ws.on_upgrade(|socket| async {
|
||||||
|
let (mut sender, receiver) = socket.split();
|
||||||
|
let (send_tx, mut send_rx) = channel(16);
|
||||||
|
tokio::spawn(async move {
|
||||||
|
while let Some(message) = send_rx.recv().await {
|
||||||
|
sender.send(Message::Binary(message)).await?;
|
||||||
|
}
|
||||||
|
Ok::<(), axum::Error>(())
|
||||||
|
});
|
||||||
|
if let Err(e) = handle(ConnectionSender(send_tx), ConnectionReader(receiver)).await
|
||||||
|
{
|
||||||
|
eprintln!("Error: {}", e);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
let listener = tokio::net::TcpListener::bind("0.0.0.0:80")
|
||||||
|
.await
|
||||||
|
.expect("failed to bind");
|
||||||
|
axum::serve(listener, app).await.expect("failed to serve");
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref LOBBIES: DashMap<Uuid, Lobby> = DashMap::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
enum LoginRequest {
|
||||||
|
CreateLobby {
|
||||||
|
username: String,
|
||||||
|
public: bool,
|
||||||
|
},
|
||||||
|
JoinLobby {
|
||||||
|
lobby_id: Option<Uuid>,
|
||||||
|
username: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
slotmap::new_key_type! {
|
||||||
|
struct ConnectionId;}
|
||||||
|
|
||||||
|
struct Lobby {
|
||||||
|
id: Uuid,
|
||||||
|
public: bool,
|
||||||
|
connections: SlotMap<ConnectionId, LobbyConnection>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LobbyConnection {
|
||||||
|
sender: ConnectionSender,
|
||||||
|
username: String,
|
||||||
|
ready: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct LobbyJoined(Uuid);
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct IAmReady;
|
||||||
|
|
||||||
|
async fn handle(
|
||||||
|
mut sender: ConnectionSender,
|
||||||
|
mut receiver: ConnectionReader,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
// Find or create a lobby
|
||||||
|
let login_request: LoginRequest = receiver.read().await?;
|
||||||
|
let (mut lobby, username) = match login_request {
|
||||||
|
LoginRequest::CreateLobby { username, public } => (create_lobby(public).await, username),
|
||||||
|
LoginRequest::JoinLobby { lobby_id, username } => (
|
||||||
|
match lobby_id {
|
||||||
|
Some(id) => LOBBIES.get_mut(&id).context("lobby not found")?.into(),
|
||||||
|
None => match find_random_lobby().await {
|
||||||
|
Some(lobby) => lobby,
|
||||||
|
None => create_lobby(true).await,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
username,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add the user to the lobby
|
||||||
|
let lobby_id = lobby.id;
|
||||||
|
sender.send(LobbyJoined(lobby_id)).await?;
|
||||||
|
let connection_id = lobby.connections.insert(LobbyConnection {
|
||||||
|
sender,
|
||||||
|
username,
|
||||||
|
ready: false,
|
||||||
|
});
|
||||||
|
drop(lobby);
|
||||||
|
|
||||||
|
// Wait for the user to be ready
|
||||||
|
let disconnected = receiver.read::<IAmReady>().await.is_err();
|
||||||
|
|
||||||
|
// Check to start the game
|
||||||
|
let Entry::Occupied(mut lobby) = LOBBIES.entry(lobby_id) else {
|
||||||
|
bail!("lobby not found");
|
||||||
|
};
|
||||||
|
if disconnected {
|
||||||
|
lobby.get_mut().connections.remove(connection_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if lobby.get().connections.is_empty() {
|
||||||
|
LOBBIES.remove(&lobby_id);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let should_start = lobby.connections.iter().all(|(_, c)| c.ready);
|
||||||
|
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn create_lobby(public: bool) -> GlobalRefMut<'static, Uuid, Lobby> {
|
||||||
|
loop {
|
||||||
|
let id = Uuid::new_v4();
|
||||||
|
if let Entry::Vacant(e) = LOBBIES.entry(id) {
|
||||||
|
break e
|
||||||
|
.insert(Lobby {
|
||||||
|
id,
|
||||||
|
public,
|
||||||
|
connections: SlotMap::with_key(),
|
||||||
|
})
|
||||||
|
.into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn find_random_lobby() -> Option<GlobalRefMut<'static, Uuid, Lobby>> {
|
||||||
|
LOBBIES
|
||||||
|
.iter_mut()
|
||||||
|
.filter(|lobby| lobby.public)
|
||||||
|
.min_by_key(|lobby| lobby.connections.len())
|
||||||
|
.map(GlobalRefMut::from)
|
||||||
|
}
|
97
crates/server/src save/lib dada.rs
Normal file
97
crates/server/src save/lib dada.rs
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||||
|
pub struct GameId(Uuid);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||||
|
pub struct PlayerId(Uuid);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||||
|
pub struct PlayerSecret(Uuid);
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct PlayerProfile {
|
||||||
|
pub username: String,
|
||||||
|
pub image_id: Uuid,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub enum LoginRequest {
|
||||||
|
Create {
|
||||||
|
username: String,
|
||||||
|
},
|
||||||
|
JoinRandom {
|
||||||
|
username: String,
|
||||||
|
},
|
||||||
|
Join {
|
||||||
|
game_id: GameId,
|
||||||
|
username: String,
|
||||||
|
},
|
||||||
|
Rejoin {
|
||||||
|
game_id: GameId,
|
||||||
|
player_id: PlayerId,
|
||||||
|
secret: PlayerSecret,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub enum LoginResponse<T> {
|
||||||
|
Refused(String),
|
||||||
|
Success(T),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct CreateResponse {
|
||||||
|
game_id: GameId,
|
||||||
|
player_id: PlayerId,
|
||||||
|
secret: PlayerSecret,
|
||||||
|
options: Vec<GameSettingField>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct GameSettingField {
|
||||||
|
pub name: String,
|
||||||
|
pub description: String,
|
||||||
|
pub field_type: GameSettingFieldType,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub enum GameSettingFieldType {
|
||||||
|
Integer { min: i32, max: i32 },
|
||||||
|
Decimal { min: f32, max: f32 },
|
||||||
|
String { min_len: usize, max_len: usize },
|
||||||
|
Choice { choices: HashSet<String> },
|
||||||
|
Boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub enum GameSettingFieldValue {
|
||||||
|
Integer(i32),
|
||||||
|
Decimal(f32),
|
||||||
|
String(String),
|
||||||
|
Choice(String),
|
||||||
|
Boolean(bool),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct JoinRandomResponse {
|
||||||
|
game_id: GameId,
|
||||||
|
player_id: PlayerId,
|
||||||
|
secret: PlayerSecret,
|
||||||
|
players: HashMap<PlayerId, PlayerProfile>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct JoinResponse {
|
||||||
|
player_id: PlayerId,
|
||||||
|
secret: PlayerSecret,
|
||||||
|
players: HashMap<PlayerId, PlayerProfile>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct RejoinResponse {
|
||||||
|
players: HashMap<PlayerId, PlayerProfile>,
|
||||||
|
}
|
62
crates/server/src save/lib save.rs
Normal file
62
crates/server/src save/lib save.rs
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
use dashmap::mapref::entry::Entry;
|
||||||
|
use dashmap::DashMap;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
pub trait UuidKey: From<Uuid> {
|
||||||
|
fn to_uuid(&self) -> Uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SlotDashMap<K: UuidKey, V>(DashMap<Uuid, V>, PhantomData<K>);
|
||||||
|
|
||||||
|
impl<K: UuidKey, V> SlotDashMap<K, V> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self(DashMap::new(), PhantomData)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert(&self, value: V) -> K {
|
||||||
|
loop {
|
||||||
|
let id = Uuid::new_v4();
|
||||||
|
let Entry::Vacant(entry) = self.0.entry(id) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
entry.insert(value);
|
||||||
|
return K::from(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K: UuidKey, V> Default for SlotDashMap<K, V> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! new_key_type {
|
||||||
|
( $(#[$outer:meta])* $vis:vis struct $name:ident; $($rest:tt)* ) => {
|
||||||
|
$(#[$outer])*
|
||||||
|
#[derive(Copy, Clone, Default,
|
||||||
|
Eq, PartialEq, Ord, PartialOrd,
|
||||||
|
Hash, Debug)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
$vis struct $name(::uuid::Uuid);
|
||||||
|
|
||||||
|
impl ::core::convert::From<::uuid::Uuid> for $name {
|
||||||
|
fn from(k: ::uuid::Uuid) -> Self {
|
||||||
|
$name(k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl $crate::UuidKey for $name {
|
||||||
|
fn to_uuid(&self) -> ::uuid::Uuid {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$crate::new_key_type!($($rest)*);
|
||||||
|
};
|
||||||
|
|
||||||
|
() => {}
|
||||||
|
}
|
64
crates/server/src save/lib.rs
Normal file
64
crates/server/src save/lib.rs
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||||
|
pub struct LobbyId(Uuid);
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct Lobby {
|
||||||
|
id: LobbyId,
|
||||||
|
public: bool,
|
||||||
|
players: HashMap<PlayerId, LobbyPlayer>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct LobbyPlayer {
|
||||||
|
username: String,
|
||||||
|
ready: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||||
|
pub struct PlayerId(Uuid);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||||
|
pub struct GameId(Uuid);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||||
|
pub struct RejoinToken(Uuid);
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub enum LoginRequest {
|
||||||
|
CreateLobby {
|
||||||
|
username: String,
|
||||||
|
public: bool,
|
||||||
|
},
|
||||||
|
JoinLobby {
|
||||||
|
lobby_id: Option<Uuid>,
|
||||||
|
username: String,
|
||||||
|
},
|
||||||
|
RejoinGame {
|
||||||
|
token: RejoinToken,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct LobbyJoined {
|
||||||
|
player_id: PlayerId,
|
||||||
|
lobby: Lobby,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub enum LobbyClientPacket {
|
||||||
|
Ready(bool),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub enum LobbyServerPacket {
|
||||||
|
LobbyUpdated(Lobby),
|
||||||
|
GameStarted(GameId, RejoinToken),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct RejoinResponse;
|
62
crates/server/src save/main.rs
Normal file
62
crates/server/src save/main.rs
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use anyhow::{bail, Context};
|
||||||
|
use axum::extract::ws::{Message, WebSocket};
|
||||||
|
use axum::extract::WebSocketUpgrade;
|
||||||
|
use axum::routing::get;
|
||||||
|
use axum::Router;
|
||||||
|
use dashmap::mapref::entry::Entry;
|
||||||
|
use dashmap::DashMap;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use server::{GameId, Lobby, LobbyId, LoginRequest, PlayerId};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref LOBBIES: DashMap<LobbyId, Lobby> = DashMap::new();
|
||||||
|
// static ref GAMES: DashMap<GameId, Game> = DashMap::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
let app = Router::new().route(
|
||||||
|
"/",
|
||||||
|
get(|ws: WebSocketUpgrade| async {
|
||||||
|
ws.on_upgrade(|socket| async {
|
||||||
|
if let Err(e) = handle(socket).await {
|
||||||
|
eprintln!("Error: {}", e);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
let listener = tokio::net::TcpListener::bind("0.0.0.0:80")
|
||||||
|
.await
|
||||||
|
.expect("failed to bind");
|
||||||
|
axum::serve(listener, app).await.expect("failed to serve");
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle(mut socket: WebSocket) -> anyhow::Result<()> {
|
||||||
|
let Message::Binary(login_data) = socket.recv().await.context("client disconnected")?? else {
|
||||||
|
bail!("expected login request");
|
||||||
|
};
|
||||||
|
let login_request = bincode::deserialize(&login_data)?;
|
||||||
|
match login_request {
|
||||||
|
LoginRequest::CreateLobby { username, public } => {
|
||||||
|
let lobby_id = loop {
|
||||||
|
let id = Uuid::new_v4();
|
||||||
|
let Entry::Vacant(entry) = LOBBIES.entry(LobbyId(id)) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
entry.insert()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LoginRequest::JoinLobby { lobby_id, username } => todo!(),
|
||||||
|
LoginRequest::RejoinGame { token } => todo!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_game_creation(mut socket: WebSocket, username: String) -> anyhow::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
73
crates/server/src save/utils.rs
Normal file
73
crates/server/src save/utils.rs
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
use dashmap::mapref::entry::Entry;
|
||||||
|
use dashmap::DashMap;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
pub trait UuidKey: From<Uuid> + Copy {
|
||||||
|
fn to_uuid(&self) -> Uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! new_id_type {
|
||||||
|
( $($vis:vis struct $name:ident;)* ) => {
|
||||||
|
$(
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, ::serde::Serialize, ::serde::Deserialize)]
|
||||||
|
$vis struct $name(::uuid::Uuid);
|
||||||
|
|
||||||
|
impl ::core::convert::From<::uuid::Uuid> for $name {
|
||||||
|
fn from(k: ::uuid::Uuid) -> Self {
|
||||||
|
$name(k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl $crate::utils::UuidKey for $name {
|
||||||
|
fn to_uuid(&self) -> ::uuid::Uuid {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct IdDashMap<K: UuidKey, V>(DashMap<Uuid, V>, PhantomData<K>);
|
||||||
|
|
||||||
|
impl<K: UuidKey, V> IdDashMap<K, V> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self(DashMap::new(), PhantomData)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert(&self, value: V) -> K {
|
||||||
|
loop {
|
||||||
|
let id = Uuid::new_v4();
|
||||||
|
let Entry::Vacant(entry) = self.0.entry(id) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
entry.insert(value);
|
||||||
|
return K::from(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert_with_id(&self, create_value: impl FnOnce(K) -> V) -> K {
|
||||||
|
loop {
|
||||||
|
let id = Uuid::new_v4();
|
||||||
|
let Entry::Vacant(entry) = self.0.entry(id) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let id = K::from(id);
|
||||||
|
let value = create_value(id);
|
||||||
|
entry.insert(value);
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove(&self, id: K) {
|
||||||
|
self.0.remove(&id.to_uuid());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K: UuidKey, V> Default for IdDashMap<K, V> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,62 +1,38 @@
|
||||||
use std::marker::PhantomData;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use dashmap::mapref::entry::Entry;
|
use serde::{Deserialize, Serialize};
|
||||||
use dashmap::DashMap;
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
pub trait UuidKey: From<Uuid> {
|
#[derive(Serialize, Deserialize)]
|
||||||
fn to_uuid(&self) -> Uuid;
|
pub enum ClientPacket {
|
||||||
|
Disconnect,
|
||||||
|
CreateLobby {
|
||||||
|
username: String,
|
||||||
|
public: bool,
|
||||||
|
},
|
||||||
|
JoinLobby {
|
||||||
|
lobby_id: Option<Uuid>,
|
||||||
|
username: String,
|
||||||
|
},
|
||||||
|
IAmReady,
|
||||||
|
IAmNotReady,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SlotDashMap<K: UuidKey, V>(DashMap<Uuid, V>, PhantomData<K>);
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub enum ServerPacket {
|
||||||
impl<K: UuidKey, V> SlotDashMap<K, V> {
|
Refused(String),
|
||||||
pub fn new() -> Self {
|
LobbyJoined(Uuid),
|
||||||
Self(DashMap::new(), PhantomData)
|
LobbyUpdated(Lobby),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert(&self, value: V) -> K {
|
#[derive(Serialize, Deserialize)]
|
||||||
loop {
|
pub struct Lobby {
|
||||||
let id = Uuid::new_v4();
|
pub public: bool,
|
||||||
let Entry::Vacant(entry) = self.0.entry(id) else {
|
pub players: HashMap<Uuid, LobbyPlayer>,
|
||||||
continue;
|
|
||||||
};
|
|
||||||
entry.insert(value);
|
|
||||||
return K::from(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K: UuidKey, V> Default for SlotDashMap<K, V> {
|
#[derive(Serialize, Deserialize)]
|
||||||
fn default() -> Self {
|
pub struct LobbyPlayer {
|
||||||
Self::new()
|
pub username: String,
|
||||||
}
|
pub ready: bool,
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! new_key_type {
|
|
||||||
( $(#[$outer:meta])* $vis:vis struct $name:ident; $($rest:tt)* ) => {
|
|
||||||
$(#[$outer])*
|
|
||||||
#[derive(Copy, Clone, Default,
|
|
||||||
Eq, PartialEq, Ord, PartialOrd,
|
|
||||||
Hash, Debug)]
|
|
||||||
#[repr(transparent)]
|
|
||||||
$vis struct $name(::uuid::Uuid);
|
|
||||||
|
|
||||||
impl ::core::convert::From<::uuid::Uuid> for $name {
|
|
||||||
fn from(k: ::uuid::Uuid) -> Self {
|
|
||||||
$name(k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl $crate::UuidKey for $name {
|
|
||||||
fn to_uuid(&self) -> ::uuid::Uuid {
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$crate::new_key_type!($($rest)*);
|
|
||||||
};
|
|
||||||
|
|
||||||
() => {}
|
|
||||||
}
|
}
|
||||||
|
|
18
crates/server/src/lobby.rs
Normal file
18
crates/server/src/lobby.rs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
use axum::http::header::Entry;
|
||||||
|
use dashmap::DashMap;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use scc::{HashMap, HashSet};
|
||||||
|
use server::LobbyStatus;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref LOBBIES: DashMap<Uuid, LobbyStatus> = DashMap::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn create_lobby(
|
||||||
|
client_id: Uuid,
|
||||||
|
username: String,
|
||||||
|
public: bool,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
todo!()
|
||||||
|
}
|
|
@ -1,22 +1,57 @@
|
||||||
use axum::extract::ws::WebSocket;
|
use std::borrow::{Borrow, Cow};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use axum::extract::ws::{Message, WebSocket};
|
||||||
use axum::extract::WebSocketUpgrade;
|
use axum::extract::WebSocketUpgrade;
|
||||||
use axum::routing::get;
|
use axum::routing::get;
|
||||||
use axum::Router;
|
use axum::Router;
|
||||||
|
use dashmap::DashMap;
|
||||||
|
use futures::{SinkExt, StreamExt};
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use log::warn;
|
||||||
|
use server::{ClientPacket, Lobby, LobbyPlayer, ServerPacket};
|
||||||
|
use tokio::sync::mpsc::{channel, Sender};
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
mod game;
|
struct Client {
|
||||||
mod login;
|
status: ClientStatus,
|
||||||
|
sender: Sender<Vec<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ClientStatus {
|
||||||
|
Unauthenticated,
|
||||||
|
InLobby(Uuid),
|
||||||
|
InGame(Uuid),
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref CLIENTS: RwLock<HashMap<Uuid, Client>> = RwLock::new(HashMap::new());
|
||||||
|
static ref LOBBIES: RwLock<HashMap<Uuid, Lobby>> = RwLock::new(HashMap::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send_message<'a>(client_id: Uuid, message: impl Into<Cow<'a, [u8]>>) {
|
||||||
|
if let Some(client) = CLIENTS.read().await.get(&client_id) {
|
||||||
|
client.sender.send(message.into().into_owned()).await.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send_packet(client_id: Uuid, packet: impl Borrow<ServerPacket>) {
|
||||||
|
let message = match bincode::serialize(packet.borrow()) {
|
||||||
|
Ok(message) => message,
|
||||||
|
Err(error) => {
|
||||||
|
warn!("failed to serialize packet for {}: {}", client_id, error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
send_message(client_id, message).await;
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let app = Router::new().route(
|
let app = Router::new().route(
|
||||||
"/",
|
"/",
|
||||||
get(|ws: WebSocketUpgrade| async {
|
get(|ws: WebSocketUpgrade| async { ws.on_upgrade(handle_client) }),
|
||||||
ws.on_upgrade(|socket| async {
|
|
||||||
if let Err(e) = handle(socket).await {
|
|
||||||
eprintln!("Error: {}", e);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:80")
|
let listener = tokio::net::TcpListener::bind("0.0.0.0:80")
|
||||||
.await
|
.await
|
||||||
|
@ -24,6 +59,141 @@ async fn main() {
|
||||||
axum::serve(listener, app).await.expect("failed to serve");
|
axum::serve(listener, app).await.expect("failed to serve");
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle(mut socket: WebSocket) -> anyhow::Result<()> {
|
async fn handle_client(socket: WebSocket) {
|
||||||
Ok(())
|
let client_id = Uuid::now_v7();
|
||||||
|
|
||||||
|
let (mut sender, mut receiver) = socket.split();
|
||||||
|
let (send_tx, mut send_rx) = channel(16);
|
||||||
|
tokio::spawn(async move {
|
||||||
|
while let Some(message) = send_rx.recv().await {
|
||||||
|
sender.send(Message::Binary(message)).await?;
|
||||||
}
|
}
|
||||||
|
Ok::<(), axum::Error>(())
|
||||||
|
});
|
||||||
|
|
||||||
|
CLIENTS.write().await.insert(
|
||||||
|
client_id,
|
||||||
|
Client {
|
||||||
|
status: ClientStatus::Unauthenticated,
|
||||||
|
sender: send_tx,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
while let Some(Ok(message)) = receiver.next().await {
|
||||||
|
let Message::Binary(message) = message else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let Ok(packet) = bincode::deserialize::<ClientPacket>(&message) else {
|
||||||
|
warn!("failed to deserialize packet from {}", client_id);
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
packet_received(client_id, packet).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
packet_received(client_id, ClientPacket::Disconnect).await;
|
||||||
|
CLIENTS.write().await.remove(&client_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn packet_received(client_id: Uuid, packet: ClientPacket) {
|
||||||
|
let client = &CLIENTS.read().await[&client_id];
|
||||||
|
match client.status {
|
||||||
|
ClientStatus::Unauthenticated => handle_unauthenticated(client_id, packet).await,
|
||||||
|
ClientStatus::InLobby(lobby_id) => handle_in_lobby(client_id, lobby_id, packet).await,
|
||||||
|
ClientStatus::InGame(game_id) => handle_in_game(client_id, game_id, packet).await,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_unauthenticated(client_id: Uuid, packet: ClientPacket) {
|
||||||
|
match packet {
|
||||||
|
ClientPacket::CreateLobby { username, public } => {
|
||||||
|
let lobby_id = Uuid::now_v7();
|
||||||
|
let lobby = Lobby {
|
||||||
|
public,
|
||||||
|
players: HashMap::from_iter([(
|
||||||
|
client_id,
|
||||||
|
LobbyPlayer {
|
||||||
|
username,
|
||||||
|
ready: false,
|
||||||
|
},
|
||||||
|
)]),
|
||||||
|
};
|
||||||
|
let mut lobbies = LOBBIES.write().await;
|
||||||
|
lobbies.insert(lobby_id, lobby);
|
||||||
|
CLIENTS
|
||||||
|
.write()
|
||||||
|
.await
|
||||||
|
.get_mut(&client_id)
|
||||||
|
.expect("client not found")
|
||||||
|
.status = ClientStatus::InLobby(lobby_id);
|
||||||
|
send_packet(client_id, ServerPacket::LobbyJoined(lobby_id)).await;
|
||||||
|
}
|
||||||
|
ClientPacket::JoinLobby { lobby_id, username } => {
|
||||||
|
let mut lobbies = LOBBIES.write().await;
|
||||||
|
|
||||||
|
let (lobby_id, lobby) = match lobby_id {
|
||||||
|
Some(id) => {
|
||||||
|
let Some(lobby) = lobbies.get_mut(&id) else {
|
||||||
|
return send_packet(
|
||||||
|
client_id,
|
||||||
|
ServerPacket::Refused("lobby not found".to_string()),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
};
|
||||||
|
(id, lobby)
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let random_lobby = lobbies
|
||||||
|
.iter_mut()
|
||||||
|
.filter(|(_, lobby)| lobby.public)
|
||||||
|
.min_by_key(|(_, lobby)| lobby.players.len());
|
||||||
|
match random_lobby {
|
||||||
|
Some((&id, lobby)) => (id, lobby),
|
||||||
|
None => {
|
||||||
|
let id = Uuid::now_v7();
|
||||||
|
(
|
||||||
|
id,
|
||||||
|
lobbies.entry(id).or_insert(Lobby {
|
||||||
|
public: true,
|
||||||
|
players: HashMap::new(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
lobby.players.insert(
|
||||||
|
client_id,
|
||||||
|
LobbyPlayer {
|
||||||
|
username,
|
||||||
|
ready: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
CLIENTS
|
||||||
|
.write()
|
||||||
|
.await
|
||||||
|
.get_mut(&client_id)
|
||||||
|
.expect("client not found")
|
||||||
|
.status = ClientStatus::InLobby(lobby_id);
|
||||||
|
send_packet(client_id, ServerPacket::LobbyJoined(lobby_id)).await;
|
||||||
|
|
||||||
|
let message = bincode::serialize(&lobby).expect("failed to serialize lobby");
|
||||||
|
for player_id in lobby.players.keys() {
|
||||||
|
send_message(*player_id, &message).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_in_lobby(client_id: Uuid, lobby_id: Uuid, packet: ClientPacket) {
|
||||||
|
match packet {
|
||||||
|
ClientPacket::Disconnect => todo!(),
|
||||||
|
ClientPacket::IAmReady => todo!(),
|
||||||
|
ClientPacket::IAmNotReady => todo!(),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_in_game(client_id: Uuid, game_id: Uuid, packet: ClientPacket) {}
|
||||||
|
|
Loading…
Reference in a new issue