use core::num::NonZeroU8; use std::{collections::HashMap, sync::Arc}; use tokio::{ sync::{ Mutex, broadcast::{Receiver, Sender}, }, time::Instant, }; use werewolves_proto::{ error::GameError, message::{PublicIdentity, ServerMessage}, player::PlayerId, }; use crate::LogError; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ConnectionId(PlayerId, Instant); impl ConnectionId { pub fn new(player_id: PlayerId) -> Self { Self(player_id, Instant::now()) } pub const fn player_id(&self) -> &PlayerId { &self.0 } } #[derive(Debug)] pub struct JoinedPlayer { sender: Sender, receiver: Receiver, active_connection: ConnectionId, in_game: bool, pub name: String, pub number: Option, pub pronouns: Option, } impl JoinedPlayer { pub const fn new( sender: Sender, receiver: Receiver, active_connection: ConnectionId, name: String, number: Option, pronouns: Option, ) -> Self { Self { name, number, sender, pronouns, receiver, active_connection, in_game: false, } } pub fn resubscribe_reciever(&self) -> Receiver { self.receiver.resubscribe() } } #[derive(Debug, Clone)] pub struct JoinedPlayers { players: Arc>>, connect_state_sender: Sender<(PlayerId, bool)>, } impl JoinedPlayers { pub fn new(connect_state_sender: Sender<(PlayerId, bool)>) -> Self { Self { connect_state_sender, players: Arc::new(Mutex::new(HashMap::new())), } } pub async fn send_all_lobby(&self, in_lobby: Box<[PublicIdentity]>, in_lobby_ids: &[PlayerId]) { let players: tokio::sync::MutexGuard<'_, HashMap> = self.players.lock().await; let senders = players .iter() .map(|(pid, p)| (pid.clone(), p.sender.clone())) .collect::>(); core::mem::drop(players); for (pid, send) in senders { send.send(ServerMessage::LobbyInfo { joined: in_lobby_ids.contains(&pid), players: in_lobby.clone(), }) .log_debug(); } } pub async fn is_connected(&self, player_id: &PlayerId) -> bool { self.players.lock().await.contains_key(player_id) } pub async fn update(&self, player_id: &PlayerId, f: impl FnOnce(&mut JoinedPlayer)) { if let Some(p) = self .players .lock() .await .iter_mut() .find_map(|(pid, p)| (player_id == pid).then_some(p)) { f(p) } } pub async fn get_player_identity(&self, player_id: &PlayerId) -> Option { self.players.lock().await.iter().find_map(|(id, p)| { (id == player_id).then(|| PublicIdentity { name: p.name.clone(), pronouns: p.pronouns.clone(), number: p.number, }) }) } /// Disconnect the player /// /// Will not disconnect if the player is currently in a game, allowing them to reconnect pub async fn disconnect(&self, connection: &ConnectionId) -> Option { let mut map = self.players.lock().await; self.connect_state_sender .send((connection.0.clone(), false)) .log_warn(); if map .get(connection.player_id()) .map(|p| p.active_connection == *connection && !p.in_game) .unwrap_or_default() { return map.remove(connection.player_id()); } None } pub async fn start_game_with(&self, players: &[PlayerId]) -> Result { let mut map = self.players.lock().await; for player in players { if let Some(player) = map.get_mut(player) { player.in_game = true; }; } Ok(InGameToken::new( self.clone(), players.iter().cloned().collect(), )) } pub async fn release_from_game(&self, players: &[PlayerId]) { self.players .lock() .await .iter_mut() .filter(|(p, _)| players.contains(*p)) .for_each(|(_, p)| p.in_game = false) } pub async fn get_sender(&self, player_id: &PlayerId) -> Option> { self.players .lock() .await .get(player_id) .map(|c| c.sender.clone()) } pub async fn insert_or_replace( &self, player_id: PlayerId, player: JoinedPlayer, ) -> Receiver { let mut map = self.players.lock().await; if let Some(old) = map.insert(player_id.clone(), player) { let old_map_entry = unsafe { map.get_mut(&player_id).unwrap_unchecked() }; old_map_entry.receiver = old.resubscribe_reciever(); old.receiver } else { self.connect_state_sender .send((player_id.clone(), true)) .log_warn(); unsafe { map.get(&player_id).unwrap_unchecked() } .receiver .resubscribe() } } } pub struct InGameToken { joined_players: JoinedPlayers, players_in_game: Option>, } impl InGameToken { const fn new(joined_players: JoinedPlayers, players_in_game: Box<[PlayerId]>) -> Self { Self { joined_players, players_in_game: Some(players_in_game), } } } impl Drop for InGameToken { fn drop(&mut self) { let joined_players = self.joined_players.clone(); if let Some(players) = self.players_in_game.take() { tokio::spawn(async move { let players_in_game = players; joined_players.release_from_game(&players_in_game).await; }); } } }