generated from tipragot/rust
Co-authored-by: CoCoSol <CoCoSol007@users.noreply.github.com>
This commit is contained in:
parent
63afb174cf
commit
01924cdcb8
13
Cargo.lock
generated
13
Cargo.lock
generated
|
@ -750,7 +750,9 @@ dependencies = [
|
|||
"dashmap",
|
||||
"futures",
|
||||
"lazy_static",
|
||||
"rand",
|
||||
"serde",
|
||||
"slotmap",
|
||||
"tokio",
|
||||
"uuid",
|
||||
]
|
||||
|
@ -775,6 +777,16 @@ dependencies = [
|
|||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slotmap"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.13.1"
|
||||
|
@ -1007,6 +1019,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -13,9 +13,11 @@ bincode = "1.3.3"
|
|||
dashmap = "5.5.3"
|
||||
futures = "0.3.30"
|
||||
lazy_static = "1.4.0"
|
||||
rand = "0.8.5"
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
slotmap = { version = "1.0.7", features = ["serde"] }
|
||||
tokio = { version = "1.37.0", features = ["macros", "rt-multi-thread"] }
|
||||
uuid = { version = "1.8.0", features = ["v4"] }
|
||||
uuid = { version = "1.8.0", features = ["v4", "serde"] }
|
||||
|
||||
# [lints]
|
||||
# workspace = true
|
||||
|
|
25
crates/server/src/game.rs
Normal file
25
crates/server/src/game.rs
Normal file
|
@ -0,0 +1,25 @@
|
|||
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,
|
||||
}
|
68
crates/server/src/game_save.rs
Normal file
68
crates/server/src/game_save.rs
Normal file
|
@ -0,0 +1,68 @@
|
|||
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,26 +1,62 @@
|
|||
use std::collections::HashSet;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use dashmap::mapref::entry::Entry;
|
||||
use dashmap::DashMap;
|
||||
use uuid::Uuid;
|
||||
|
||||
struct Action {
|
||||
title: String,
|
||||
description: String,
|
||||
tile_id: Uuid,
|
||||
image:
|
||||
pub trait UuidKey: From<Uuid> {
|
||||
fn to_uuid(&self) -> Uuid;
|
||||
}
|
||||
|
||||
struct ActionType {}
|
||||
pub struct SlotDashMap<K: UuidKey, V>(DashMap<Uuid, V>, PhantomData<K>);
|
||||
|
||||
struct Player {}
|
||||
impl<K: UuidKey, V> SlotDashMap<K, V> {
|
||||
pub fn new() -> Self {
|
||||
Self(DashMap::new(), PhantomData)
|
||||
}
|
||||
|
||||
impl Player {
|
||||
async fn make_action(&mut self, actions: HashSet<Action>) -> Action {
|
||||
todo!()
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn border_wars_classic(players: HashSet<Player>) {
|
||||
let player1 = players.iter().next().unwrap();
|
||||
|
||||
let action = player1.make_action(HashSet::new()).await;
|
||||
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)*);
|
||||
};
|
||||
|
||||
() => {}
|
||||
}
|
||||
|
|
20
crates/server/src/login.rs
Normal file
20
crates/server/src/login.rs
Normal file
|
@ -0,0 +1,20 @@
|
|||
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!(),
|
||||
}
|
||||
}
|
127
crates/server/src/main save.rs
Normal file
127
crates/server/src/main save.rs
Normal file
|
@ -0,0 +1,127 @@
|
|||
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,31 +1,22 @@
|
|||
use axum::extract::ws::{Message, WebSocket};
|
||||
use axum::extract::ws::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::Serialize;
|
||||
use tokio::sync::mpsc::{channel, Sender};
|
||||
use uuid::Uuid;
|
||||
|
||||
trait Request: DeserializeOwned + Serialize {
|
||||
type Response: Response;
|
||||
}
|
||||
|
||||
trait Response: DeserializeOwned + Serialize {}
|
||||
|
||||
lazy_static! {
|
||||
static ref CLIENTS: DashMap<Uuid, Sender<Vec<u8>>> = DashMap::new();
|
||||
}
|
||||
mod game;
|
||||
mod login;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let app = Router::new().route(
|
||||
"/",
|
||||
get(|ws: WebSocketUpgrade| async { ws.on_upgrade(|socket| handle(socket)) }),
|
||||
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
|
||||
|
@ -33,46 +24,6 @@ async fn main() {
|
|||
axum::serve(listener, app).await.expect("failed to serve");
|
||||
}
|
||||
|
||||
async fn handle(mut socket: WebSocket) {
|
||||
let (mut writer, mut reader) = socket.split();
|
||||
|
||||
let (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(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;
|
||||
};
|
||||
if let Err(error) = message_received(client_id, data).await {
|
||||
println!("Error: {}", error);
|
||||
break;
|
||||
};
|
||||
}
|
||||
|
||||
CLIENTS.remove(&client_id);
|
||||
}
|
||||
|
||||
async fn send_message(id: Uuid, message: Vec<u8>) -> anyhow::Result<()> {
|
||||
if let Some(sender) = CLIENTS.get(&id) {
|
||||
sender.send(message).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn message_received(id: Uuid, message: Vec<u8>) -> anyhow::Result<()> {
|
||||
async fn handle(mut socket: WebSocket) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue