generated from tipragot/rust
This commit is contained in:
parent
27e0aad29d
commit
95c1890a1e
|
@ -1,42 +0,0 @@
|
||||||
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(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,182 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
use dashmap::DashMap;
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use server::SlotDashMap;
|
|
||||||
use slotmap::SlotMap;
|
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
server::new_key_type! {
|
|
||||||
struct GameId;
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref GAMES: SlotDashMap<GameId, Game> = SlotDashMap::new();
|
|
||||||
}
|
|
||||||
|
|
||||||
slotmap::new_key_type! {
|
|
||||||
struct PlayerId;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Game {
|
|
||||||
players: SlotMap<PlayerId, Player>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Player {
|
|
||||||
connection_secret: Uuid,
|
|
||||||
}
|
|
|
@ -1,68 +0,0 @@
|
||||||
use std::collections::{HashSet, LinkedList};
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use rand::Rng;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use slotmap::SlotMap;
|
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
use crate::{receive_message, send_message};
|
|
||||||
|
|
||||||
slotmap::new_key_type! {
|
|
||||||
struct ActionId;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
struct Action {
|
|
||||||
title: String,
|
|
||||||
description: String,
|
|
||||||
next_actions: SlotMap<ActionId, Self>,
|
|
||||||
tile_position: Option<(i32, i32)>,
|
|
||||||
image_id: Uuid,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Player {
|
|
||||||
connection: Option<Uuid>,
|
|
||||||
secret: Uuid,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Player {
|
|
||||||
fn get_random_action(actions: &SlotMap<ActionId, Action>) -> LinkedList<ActionId> {
|
|
||||||
if actions.len() == 0 {
|
|
||||||
return LinkedList::new();
|
|
||||||
}
|
|
||||||
let random_index = rand::thread_rng().gen_range(0..actions.len());
|
|
||||||
let action = actions.iter().nth(random_index).unwrap();
|
|
||||||
let mut result = LinkedList::new();
|
|
||||||
result.push_back(action.0);
|
|
||||||
result.append(&mut Self::get_random_action(&action.1.next_actions));
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn make_action(
|
|
||||||
&mut self,
|
|
||||||
actions: &SlotMap<ActionId, Action>,
|
|
||||||
timeout: Duration,
|
|
||||||
) -> LinkedList<ActionId> {
|
|
||||||
let Some(connection) = self.connection else {
|
|
||||||
return Self::get_random_action(actions);
|
|
||||||
};
|
|
||||||
|
|
||||||
let Ok(encoded_message) = bincode::serialize(actions) else {
|
|
||||||
return Self::get_random_action(actions);
|
|
||||||
};
|
|
||||||
|
|
||||||
if !send_message(connection, encoded_message).await {
|
|
||||||
return Self::get_random_action(actions);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the client response
|
|
||||||
match tokio::time::timeout(timeout, receive_message(connection)).await {
|
|
||||||
Ok(Some(response)) => match bincode::deserialize(&response) {
|
|
||||||
Ok(response) => response,
|
|
||||||
Err(_) => Self::get_random_action(actions),
|
|
||||||
},
|
|
||||||
_ => Self::get_random_action(actions),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,97 +0,0 @@
|
||||||
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>,
|
|
||||||
}
|
|
|
@ -1,62 +0,0 @@
|
||||||
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)*);
|
|
||||||
};
|
|
||||||
|
|
||||||
() => {}
|
|
||||||
}
|
|
|
@ -1,64 +0,0 @@
|
||||||
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;
|
|
|
@ -1,20 +0,0 @@
|
||||||
use anyhow::{bail, Context};
|
|
||||||
use axum::extract::ws::{Message, WebSocket};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub enum LoginRequest {
|
|
||||||
Create { username: String },
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn handle_client_login(socket: &mut 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::Create { username } => todo!(),
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,127 +0,0 @@
|
||||||
use std::collections::LinkedList;
|
|
||||||
|
|
||||||
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 futures::{SinkExt, StreamExt};
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use serde::de::DeserializeOwned;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use slotmap::SlotMap;
|
|
||||||
use tokio::sync::mpsc::{channel, Receiver, Sender};
|
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
slotmap::new_key_type! {
|
|
||||||
struct PlayerId;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum WorldModification {
|
|
||||||
SetTile { r: i32, s: i32, image_id: Uuid },
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Player {
|
|
||||||
connection_secret: Uuid,
|
|
||||||
unsent_modifications: LinkedList<WorldModification>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Game {
|
|
||||||
players: SlotMap<PlayerId, Player>,
|
|
||||||
}
|
|
||||||
|
|
||||||
trait Request: DeserializeOwned + Serialize {
|
|
||||||
type Response: Response;
|
|
||||||
}
|
|
||||||
|
|
||||||
trait Response: DeserializeOwned + Serialize {}
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref CLIENTS: DashMap<Uuid, Sender<Vec<u8>>> = DashMap::new();
|
|
||||||
static ref GAMES: DashMap<Uuid, Game> = DashMap::new();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub enum LoginRequest {
|
|
||||||
Create { pseudo: String },
|
|
||||||
JoinRandom { pseudo: String },
|
|
||||||
Join { game_id: Uuid, pseudo: String },
|
|
||||||
Rejoin { game_id: Uuid },
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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(data) = socket
|
|
||||||
.recv()
|
|
||||||
.await
|
|
||||||
.context("client disconnected before login")??
|
|
||||||
else {
|
|
||||||
bail!("expected login request");
|
|
||||||
};
|
|
||||||
|
|
||||||
let login_request = bincode::deserialize(&data)?;
|
|
||||||
match login_request {
|
|
||||||
LoginRequest::Create { pseudo } => todo!(),
|
|
||||||
LoginRequest::JoinRandom { pseudo } => todo!(),
|
|
||||||
LoginRequest::Join { game_id, pseudo } => todo!(),
|
|
||||||
LoginRequest::Rejoin { game_id } => todo!(),
|
|
||||||
}
|
|
||||||
|
|
||||||
let (mut writer, mut reader) = socket.split();
|
|
||||||
|
|
||||||
let (global_sender, mut receiver) = channel(128);
|
|
||||||
let client_id = loop {
|
|
||||||
let id = Uuid::new_v4();
|
|
||||||
let Entry::Vacant(entry) = CLIENTS.entry(id) else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
entry.insert(global_sender);
|
|
||||||
break id;
|
|
||||||
};
|
|
||||||
|
|
||||||
tokio::spawn(async move {
|
|
||||||
while let Some(message) = receiver.recv().await {
|
|
||||||
writer.send(Message::Binary(message)).await?;
|
|
||||||
}
|
|
||||||
Ok::<(), axum::Error>(())
|
|
||||||
});
|
|
||||||
|
|
||||||
while let Some(Ok(message)) = reader.next().await {
|
|
||||||
let Message::Binary(data) = message else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
CLIENTS.remove(&client_id);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn send_message(id: Uuid, message: Vec<u8>) -> bool {
|
|
||||||
if let Some(sender) = CLIENTS.get(&id) {
|
|
||||||
sender.send(message).await.is_ok()
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,62 +0,0 @@
|
||||||
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(())
|
|
||||||
}
|
|
|
@ -1,73 +0,0 @@
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -25,13 +25,13 @@ pub enum ServerPacket {
|
||||||
LobbyUpdated(Lobby),
|
LobbyUpdated(Lobby),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
pub struct Lobby {
|
pub struct Lobby {
|
||||||
pub public: bool,
|
pub public: bool,
|
||||||
pub players: HashMap<Uuid, LobbyPlayer>,
|
pub players: HashMap<Uuid, LobbyPlayer>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
pub struct LobbyPlayer {
|
pub struct LobbyPlayer {
|
||||||
pub username: String,
|
pub username: String,
|
||||||
pub ready: bool,
|
pub ready: bool,
|
||||||
|
|
|
@ -5,7 +5,6 @@ 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 futures::{SinkExt, StreamExt};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use log::warn;
|
use log::warn;
|
||||||
|
@ -117,6 +116,8 @@ async fn handle_unauthenticated(client_id: Uuid, packet: ClientPacket) {
|
||||||
},
|
},
|
||||||
)]),
|
)]),
|
||||||
};
|
};
|
||||||
|
let message = bincode::serialize(&ServerPacket::LobbyUpdated(lobby.clone()))
|
||||||
|
.expect("failed to serialize lobby");
|
||||||
let mut lobbies = LOBBIES.write().await;
|
let mut lobbies = LOBBIES.write().await;
|
||||||
lobbies.insert(lobby_id, lobby);
|
lobbies.insert(lobby_id, lobby);
|
||||||
CLIENTS
|
CLIENTS
|
||||||
|
@ -126,6 +127,7 @@ async fn handle_unauthenticated(client_id: Uuid, packet: ClientPacket) {
|
||||||
.expect("client not found")
|
.expect("client not found")
|
||||||
.status = ClientStatus::InLobby(lobby_id);
|
.status = ClientStatus::InLobby(lobby_id);
|
||||||
send_packet(client_id, ServerPacket::LobbyJoined(lobby_id)).await;
|
send_packet(client_id, ServerPacket::LobbyJoined(lobby_id)).await;
|
||||||
|
send_message(client_id, message).await;
|
||||||
}
|
}
|
||||||
ClientPacket::JoinLobby { lobby_id, username } => {
|
ClientPacket::JoinLobby { lobby_id, username } => {
|
||||||
let mut lobbies = LOBBIES.write().await;
|
let mut lobbies = LOBBIES.write().await;
|
||||||
|
@ -178,7 +180,8 @@ async fn handle_unauthenticated(client_id: Uuid, packet: ClientPacket) {
|
||||||
.status = ClientStatus::InLobby(lobby_id);
|
.status = ClientStatus::InLobby(lobby_id);
|
||||||
send_packet(client_id, ServerPacket::LobbyJoined(lobby_id)).await;
|
send_packet(client_id, ServerPacket::LobbyJoined(lobby_id)).await;
|
||||||
|
|
||||||
let message = bincode::serialize(&lobby).expect("failed to serialize lobby");
|
let message = bincode::serialize(&ServerPacket::LobbyUpdated(lobby.clone()))
|
||||||
|
.expect("failed to serialize lobby"); // PAS BON
|
||||||
for player_id in lobby.players.keys() {
|
for player_id in lobby.players.keys() {
|
||||||
send_message(*player_id, &message).await;
|
send_message(*player_id, &message).await;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue