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",
|
"dashmap",
|
||||||
"futures",
|
"futures",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
|
"rand",
|
||||||
"serde",
|
"serde",
|
||||||
|
"slotmap",
|
||||||
"tokio",
|
"tokio",
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
@ -775,6 +777,16 @@ dependencies = [
|
||||||
"autocfg",
|
"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]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "smallvec"
|
||||||
version = "1.13.1"
|
version = "1.13.1"
|
||||||
|
@ -1007,6 +1019,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0"
|
checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom",
|
"getrandom",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -13,9 +13,11 @@ bincode = "1.3.3"
|
||||||
dashmap = "5.5.3"
|
dashmap = "5.5.3"
|
||||||
futures = "0.3.30"
|
futures = "0.3.30"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
|
rand = "0.8.5"
|
||||||
serde = { version = "1.0.197", features = ["derive"] }
|
serde = { version = "1.0.197", features = ["derive"] }
|
||||||
|
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"] }
|
uuid = { version = "1.8.0", features = ["v4", "serde"] }
|
||||||
|
|
||||||
# [lints]
|
# [lints]
|
||||||
# workspace = true
|
# 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;
|
use uuid::Uuid;
|
||||||
|
|
||||||
struct Action {
|
pub trait UuidKey: From<Uuid> {
|
||||||
title: String,
|
fn to_uuid(&self) -> Uuid;
|
||||||
description: String,
|
|
||||||
tile_id: Uuid,
|
|
||||||
image:
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
pub fn insert(&self, value: V) -> K {
|
||||||
async fn make_action(&mut self, actions: HashSet<Action>) -> Action {
|
loop {
|
||||||
todo!()
|
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>) {
|
impl<K: UuidKey, V> Default for SlotDashMap<K, V> {
|
||||||
let player1 = players.iter().next().unwrap();
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
let action = player1.make_action(HashSet::new()).await;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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::extract::WebSocketUpgrade;
|
||||||
use axum::routing::get;
|
use axum::routing::get;
|
||||||
use axum::Router;
|
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 {
|
mod game;
|
||||||
type Response: Response;
|
mod login;
|
||||||
}
|
|
||||||
|
|
||||||
trait Response: DeserializeOwned + Serialize {}
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref CLIENTS: DashMap<Uuid, Sender<Vec<u8>>> = DashMap::new();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let app = Router::new().route(
|
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")
|
let listener = tokio::net::TcpListener::bind("0.0.0.0:80")
|
||||||
.await
|
.await
|
||||||
|
@ -33,46 +24,6 @@ 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) {
|
async fn handle(mut socket: WebSocket) -> anyhow::Result<()> {
|
||||||
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<()> {
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue