handling differenk kinds of server messages, and matchmaking basics
This commit is contained in:
169
server/src/connection.rs
Normal file
169
server/src/connection.rs
Normal file
@@ -0,0 +1,169 @@
|
||||
use futures_util::{SinkExt, StreamExt};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::sync::Arc;
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::sync::Mutex;
|
||||
use tokio_tungstenite::{WebSocketStream, tungstenite::Message};
|
||||
use uuid::Uuid;
|
||||
|
||||
// Type definitions
|
||||
pub type Tx = futures_util::stream::SplitSink<WebSocketStream<TcpStream>, Message>;
|
||||
pub type ConnectionMap = Arc<Mutex<HashMap<Uuid, PlayerConnection>>>;
|
||||
pub type MatchMap = Arc<Mutex<HashMap<Uuid, GameMatch>>>;
|
||||
pub type WaitingQueue = Arc<Mutex<VecDeque<Uuid>>>;
|
||||
|
||||
// Helper functions to create new instances
|
||||
pub fn new_connection_map() -> ConnectionMap {
|
||||
Arc::new(Mutex::new(HashMap::new()))
|
||||
}
|
||||
|
||||
pub fn new_match_map() -> MatchMap {
|
||||
Arc::new(Mutex::new(HashMap::new()))
|
||||
}
|
||||
|
||||
pub fn new_waiting_queue() -> WaitingQueue {
|
||||
Arc::new(Mutex::new(VecDeque::new()))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PlayerConnection {
|
||||
pub id: Uuid,
|
||||
pub username: Option<String>,
|
||||
pub tx: Tx,
|
||||
pub current_match: Option<Uuid>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GameMatch {
|
||||
pub id: Uuid,
|
||||
pub player_white: Uuid,
|
||||
pub player_black: Uuid,
|
||||
pub board_state: String,
|
||||
pub move_history: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Step {
|
||||
pub from: String,
|
||||
pub to: String,
|
||||
}
|
||||
|
||||
// Message sending utilities
|
||||
pub async fn send_message_to_player(
|
||||
connections: &ConnectionMap,
|
||||
player_id: Uuid,
|
||||
message: &str,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut connections_lock = connections.lock().await;
|
||||
if let Some(connection) = connections_lock.get_mut(&player_id) {
|
||||
connection
|
||||
.tx
|
||||
.send(Message::Text(message.to_string()))
|
||||
.await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn broadcast_to_all(connections: &ConnectionMap, message: &str) {
|
||||
let mut connections_lock = connections.lock().await;
|
||||
let mut dead_connections = Vec::new();
|
||||
|
||||
for (id, connection) in connections_lock.iter_mut() {
|
||||
if let Err(e) = connection.tx.send(Message::Text(message.to_string())).await {
|
||||
eprintln!("Failed to send to {}: {}", id, e);
|
||||
dead_connections.push(*id);
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up dead connections
|
||||
for dead_id in dead_connections {
|
||||
connections_lock.remove(&dead_id);
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn broadcast_to_match(
|
||||
connections: &ConnectionMap,
|
||||
matches: &MatchMap,
|
||||
match_id: Uuid,
|
||||
message: &str,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let matches_lock = matches.lock().await;
|
||||
if let Some(game_match) = matches_lock.get(&match_id) {
|
||||
send_message_to_player(connections, game_match.player_white, message).await?;
|
||||
send_message_to_player(connections, game_match.player_black, message).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Connection handler
|
||||
pub async fn handle_connection(
|
||||
stream: TcpStream,
|
||||
connections: ConnectionMap,
|
||||
matches: MatchMap,
|
||||
waiting_queue: WaitingQueue,
|
||||
event_system: crate::events::EventSystem,
|
||||
) -> anyhow::Result<()> {
|
||||
use tokio_tungstenite::accept_async;
|
||||
|
||||
let ws_stream = accept_async(stream).await?;
|
||||
let (write, mut read) = ws_stream.split();
|
||||
|
||||
let player_id = Uuid::new_v4();
|
||||
|
||||
// Store the connection
|
||||
{
|
||||
let mut conn_map = connections.lock().await;
|
||||
conn_map.insert(
|
||||
player_id,
|
||||
PlayerConnection {
|
||||
id: player_id,
|
||||
username: None,
|
||||
tx: write,
|
||||
current_match: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
println!("New connection: {}", player_id);
|
||||
|
||||
// Send welcome message
|
||||
let _ = send_message_to_player(
|
||||
&connections,
|
||||
player_id,
|
||||
&format!(r#"{{"type": "welcome", "player_id": "{}"}}"#, player_id),
|
||||
)
|
||||
.await;
|
||||
|
||||
// Message processing loop
|
||||
while let Some(Ok(message)) = read.next().await {
|
||||
if message.is_text() {
|
||||
let text = message.to_text()?;
|
||||
println!("Received from {}: {}", player_id, text);
|
||||
|
||||
// TODO: Parse and handle message with event system
|
||||
// This will be implemented when we integrate the event system
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup on disconnect
|
||||
cleanup_player(player_id, &connections, &matches, &waiting_queue).await;
|
||||
println!("Connection {} closed", player_id);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn cleanup_player(
|
||||
player_id: Uuid,
|
||||
connections: &ConnectionMap,
|
||||
_matches: &MatchMap,
|
||||
waiting_queue: &WaitingQueue,
|
||||
) {
|
||||
// Remove from waiting queue
|
||||
waiting_queue.lock().await.retain(|&id| id != player_id);
|
||||
|
||||
// Remove from connections
|
||||
connections.lock().await.remove(&player_id);
|
||||
|
||||
println!("Cleaned up player {}", player_id);
|
||||
}
|
||||
@@ -1,44 +1,55 @@
|
||||
use futures_util::{SinkExt, StreamExt};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
mod connection;
|
||||
mod events;
|
||||
mod matchmaking;
|
||||
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::sync::Mutex;
|
||||
use tokio_tungstenite::accept_async;
|
||||
use uuid::Uuid;
|
||||
|
||||
mod broadcast_message;
|
||||
mod handle_connection;
|
||||
mod server_event;
|
||||
|
||||
use handle_connection::handle_connection;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct MessageData {
|
||||
username: String,
|
||||
userid: u32,
|
||||
text: String,
|
||||
}
|
||||
|
||||
type Tx = futures_util::stream::SplitSink<
|
||||
tokio_tungstenite::WebSocketStream<tokio::net::TcpStream>,
|
||||
tokio_tungstenite::tungstenite::Message,
|
||||
>;
|
||||
|
||||
type ConnectionMap = Arc<Mutex<HashMap<Uuid, Tx>>>;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let address = "0.0.0.0:9001"; //address to connect to
|
||||
let listener = TcpListener::bind(address).await.unwrap();
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
let address = "0.0.0.0:9001";
|
||||
let listener = TcpListener::bind(address).await?;
|
||||
println!("Server running on ws://{}", address);
|
||||
|
||||
let connections: ConnectionMap = Arc::new(Mutex::new(HashMap::new()));
|
||||
// Shared state initialization using the new helper functions
|
||||
let connections = connection::new_connection_map();
|
||||
let matches = connection::new_match_map();
|
||||
let waiting_queue = connection::new_waiting_queue();
|
||||
|
||||
// Event system for communication between components
|
||||
let event_system = events::EventSystem::new();
|
||||
|
||||
// Start matchmaking background task
|
||||
let matchmaker = matchmaking::MatchmakingSystem::new(
|
||||
connections.clone(),
|
||||
matches.clone(),
|
||||
waiting_queue.clone(),
|
||||
event_system.clone(),
|
||||
);
|
||||
tokio::spawn(async move {
|
||||
matchmaker.run().await;
|
||||
});
|
||||
|
||||
// Main connection loop
|
||||
while let Ok((stream, _)) = listener.accept().await {
|
||||
let connections = connections.clone();
|
||||
let matches = matches.clone();
|
||||
let waiting_queue = waiting_queue.clone();
|
||||
let event_system = event_system.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
handle_connection(stream, connections).await;
|
||||
if let Err(e) = connection::handle_connection(
|
||||
stream,
|
||||
connections,
|
||||
matches,
|
||||
waiting_queue,
|
||||
event_system,
|
||||
)
|
||||
.await
|
||||
{
|
||||
eprintln!("Connection error: {}", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
112
server/src/matchmaking.rs
Normal file
112
server/src/matchmaking.rs
Normal file
@@ -0,0 +1,112 @@
|
||||
use crate::connection::{ConnectionMap, GameMatch, MatchMap, WaitingQueue};
|
||||
use crate::events::EventSystem;
|
||||
use rand::random;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub struct MatchmakingSystem {
|
||||
connections: ConnectionMap,
|
||||
matches: MatchMap,
|
||||
waiting_queue: WaitingQueue,
|
||||
event_system: EventSystem,
|
||||
}
|
||||
|
||||
impl MatchmakingSystem {
|
||||
pub fn new(
|
||||
connections: ConnectionMap,
|
||||
matches: MatchMap,
|
||||
waiting_queue: WaitingQueue,
|
||||
event_system: EventSystem,
|
||||
) -> Self {
|
||||
Self {
|
||||
connections,
|
||||
matches,
|
||||
waiting_queue,
|
||||
event_system,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run(&self) {
|
||||
loop {
|
||||
self.try_create_match().await;
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn try_create_match(&self) {
|
||||
let mut queue = self.waiting_queue.lock().await;
|
||||
|
||||
while queue.len() >= 2 {
|
||||
let player1 = queue.pop_front().unwrap();
|
||||
let player2 = queue.pop_front().unwrap();
|
||||
|
||||
let match_id = Uuid::new_v4();
|
||||
let (white_player, black_player) = if random::<bool>() {
|
||||
(player1, player2)
|
||||
} else {
|
||||
(player2, player1)
|
||||
};
|
||||
|
||||
let game_match = GameMatch {
|
||||
id: match_id,
|
||||
player_white: white_player,
|
||||
player_black: black_player,
|
||||
board_state: "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1".to_string(),
|
||||
move_history: Vec::new(),
|
||||
};
|
||||
|
||||
// Store the match
|
||||
self.matches.lock().await.insert(match_id, game_match);
|
||||
|
||||
// Update player connections
|
||||
{
|
||||
let mut conn_map = self.connections.lock().await;
|
||||
if let Some(player) = conn_map.get_mut(&white_player) {
|
||||
player.current_match = Some(match_id);
|
||||
}
|
||||
if let Some(player) = conn_map.get_mut(&black_player) {
|
||||
player.current_match = Some(match_id);
|
||||
}
|
||||
}
|
||||
|
||||
// Notify players
|
||||
self.notify_players(white_player, black_player, match_id)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn notify_players(&self, white: Uuid, black: Uuid, match_id: Uuid) {
|
||||
let conn_map = self.connections.lock().await;
|
||||
|
||||
// Get opponent names
|
||||
let white_name = conn_map
|
||||
.get(&black)
|
||||
.and_then(|c| c.username.as_deref())
|
||||
.unwrap_or("Opponent");
|
||||
let black_name = conn_map
|
||||
.get(&white)
|
||||
.and_then(|c| c.username.as_deref())
|
||||
.unwrap_or("Opponent");
|
||||
|
||||
// Notify white player
|
||||
if let Some(_) = conn_map.get(&white) {
|
||||
let message = format!(
|
||||
r#"{{"type": "match_found", "match_id": "{}", "opponent": "{}", "color": "white"}}"#,
|
||||
match_id, black_name
|
||||
);
|
||||
let _ =
|
||||
crate::connection::send_message_to_player(&self.connections, white, &message).await;
|
||||
}
|
||||
|
||||
// Notify black player
|
||||
if let Some(_) = conn_map.get(&black) {
|
||||
let message = format!(
|
||||
r#"{{"type": "match_found", "match_id": "{}", "opponent": "{}", "color": "black"}}"#,
|
||||
match_id, white_name
|
||||
);
|
||||
let _ =
|
||||
crate::connection::send_message_to_player(&self.connections, black, &message).await;
|
||||
}
|
||||
|
||||
println!("Match created: {} (white) vs {} (black)", white, black);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user