use std::collections::HashMap; use std::sync::{Arc, RwLock}; use std::time::SystemTime; use crate::common::serverstate::ClientId; // TODO: room passwords? // TODO: remove clients from areas (or upon insert, remove that id from anywhere else) pub const MAX_ROOMS: usize = 128; #[derive(Copy, Clone)] pub struct AreaClient { client_id: ClientId, time_join: SystemTime, } #[derive(Copy, Clone)] pub struct InnerClientArea { clients: [Option; N], } impl InnerClientArea<{N}> { pub fn new() -> InnerClientArea<{N}> { let mut clients: [std::mem::MaybeUninit>; N] = unsafe { std::mem::MaybeUninit::uninit().assume_init() }; for i in clients.iter_mut() { i.write(None); } InnerClientArea { clients: unsafe { (&clients as *const _ as *const [Option; N]).read()} } } fn add(&mut self, id: ClientId) -> Option { for (i, client) in self.clients.iter_mut().enumerate() { if client.is_none() { *client = Some(AreaClient{ client_id: id, time_join: SystemTime::now(), }); return Some(i); } } return None; } fn remove(&mut self, id: ClientId) -> bool { for areaclient in self.clients.iter_mut() { if let Some(client) = *areaclient { if client.client_id == id { *areaclient = None; return true; } } } false } fn contains(&self, id: ClientId) -> bool { self.clients.iter() .filter(|k| k.is_some()) .map(|k| k.unwrap() ) .fold(false, |acc, k| { if acc { acc } else if k.client_id == id { true } else { false } }) } } #[derive(Debug, Copy, Clone, PartialEq)] pub struct LobbyId(pub usize); #[derive(Debug, Copy, Clone, PartialEq)] pub struct RoomId(pub usize); pub type Lobby = InnerClientArea<12>; pub type Room = InnerClientArea<4>; trait ClientArea { fn clients(&self) -> std::slice::Iter<'_, Option>; fn remove(&mut self, id: ClientId) -> bool; } impl ClientArea for Lobby { fn clients(& self) -> std::slice::Iter<'_, Option> { self.clients.iter() } fn remove(&mut self, id: ClientId) -> bool { self.remove(id) } } impl<'a> ClientArea for Room { fn clients(& self) -> std::slice::Iter<'_, Option> { self.clients.iter() } fn remove(&mut self, id: ClientId) -> bool { self.remove(id) } } pub enum AreaType { Lobby, Room, } #[derive(Debug)] pub struct ClientAtLocation { pub client_id: ClientId, pub index: usize, } pub struct Area { pub area_type: AreaType, area: Arc>, index: usize, } impl Area { fn new(area_type: AreaType, area: Arc>, index: usize) -> Area { Area { area_type: area_type, area: area, index: index, } } pub fn clients(&self) -> Vec { self.area.read().unwrap().clients() .enumerate() .filter(|(_i, k)| k.is_some()) .map(|(i, k)| (i, k.unwrap()) ) .map(|(i, k)| ClientAtLocation { client_id: k.client_id, index: i }).collect() } // TODO: Result in cases where no one is in the area? pub fn leader(&self) -> ClientAtLocation { self.area.read().unwrap().clients() .enumerate() .filter(|(_i, k)| k.is_some()) .map(|(i, k)| (i, k.unwrap()) ) .fold((ClientAtLocation { client_id: ClientId(0), index: 0 }, SystemTime::UNIX_EPOCH), |(acc, time), (i, k)| { if time > k.time_join { (ClientAtLocation { client_id: k.client_id, index: i, }, k.time_join) } else { (acc, time) } }).0 } pub fn remove(&mut self, id: ClientId) -> bool { self.area.write().unwrap().remove(id) } pub fn id(&self) -> usize { self.index } } #[derive(Debug, PartialEq)] pub enum CreateRoomError { NoOpenSlots, ClientInAreaAlready, JoinError, } #[derive(Debug, PartialEq)] pub enum JoinRoomError { RoomDoesNotExist, RoomFull, ClientInAreaAlready, } #[derive(Debug, PartialEq)] pub enum JoinLobbyError { LobbyDoesNotExist, LobbyFull, ClientInAreaAlready, } pub struct ClientLocation { lobbies: [Arc>; 15], rooms: [Option>>; MAX_ROOMS], } impl ClientLocation { pub fn new() -> ClientLocation { ClientLocation { //lobbies: [Arc::new(RwLock::new(Lobby::new())); 15], lobbies: crate::init_array!(Arc>, 15, Arc::new(RwLock::new(Lobby::new()))), rooms: [None; MAX_ROOMS], } } fn err_if_client_is_in_area(&mut self, id: ClientId, err: E) -> Result<(), E> { let in_lobby = self.lobbies.iter() .any(|k| k.read().unwrap().contains(id)); let in_room = self.rooms.iter() .filter(|k| k.is_some()) .map(|k| k.as_ref().unwrap()) .any(|k| k.read().unwrap().contains(id)); if in_lobby || in_room { Err(err) } else { Ok(()) } } pub fn add_to_lobby(&mut self, id: ClientId, lobby: LobbyId) -> Result { self.err_if_client_is_in_area(id, JoinLobbyError::ClientInAreaAlready)?; self.lobbies.get_mut(lobby.0) .ok_or(JoinLobbyError::LobbyDoesNotExist)? .write().unwrap() .add(id) .ok_or(JoinLobbyError::LobbyFull) } pub fn new_room(&mut self, id: ClientId) -> Result { let (room_id, empty_room) = self.rooms.iter_mut() .enumerate() .filter(|(_, k)| k.is_none()) .nth(0) .ok_or(CreateRoomError::NoOpenSlots)?; let mut new_room = Room::new(); new_room.add(id); *empty_room = Some(Arc::new(RwLock::new(new_room))); self.remove_from_location(id); Ok(RoomId(room_id)) } pub fn add_to_room(&mut self, id: ClientId, room: RoomId) -> Result { self.err_if_client_is_in_area(id, JoinRoomError::ClientInAreaAlready)?; self.rooms.get_mut(room.0) .ok_or(JoinRoomError::RoomDoesNotExist)? .as_mut() .ok_or(JoinRoomError::RoomDoesNotExist)? .write().unwrap() .add(id) .ok_or(JoinRoomError::RoomFull) } pub fn get_area_by_user(&mut self, id: ClientId) -> Area { for (i, lobby) in self.lobbies.iter().enumerate() { if lobby.read().unwrap().contains(id) { return Area::new(AreaType::Lobby, lobby.clone(), i); } } for (i, room) in self.rooms.iter().enumerate() { if let Some(room) = room { if room.read().unwrap().contains(id){ return Area::new(AreaType::Room, room.clone(), i); } } } panic!("client is not in a room/lobby") } pub fn remove_from_location(&mut self, id: ClientId) { let in_lobby = self.lobbies.iter_mut() .map(|lobby| lobby.write().unwrap().remove(id)) .any(|k| k); if in_lobby { return; } self.rooms.iter_mut() .filter(|lobby| lobby.is_some()) .map(|lobby| lobby.as_ref().unwrap()) .map(|lobby| lobby.write().unwrap().remove(id)) .any(|k| k); } pub fn get_client_count_in_room(&self, room_id: RoomId) -> u8 { self.rooms[room_id.0].as_ref() .unwrap() .read() .unwrap() .clients() .filter(|k| k.is_some()) .count() as u8 } } #[derive(Debug, Copy, Clone, PartialEq)] pub struct LocalClientId(usize); #[derive(Debug, Copy, Clone, PartialEq)] struct RoomClient { client: ClientId, local_client: LocalClientId, time_join: SystemTime, } #[derive(Debug, Copy, Clone, PartialEq)] struct AreaClient2 { client: ClientId, local_client: LocalClientId, time_join: SystemTime, } #[derive(Debug, Copy, Clone, PartialEq)] struct Lobby2([Option; 12]); #[derive(Debug, Copy, Clone, PartialEq)] struct Room2([Option; 4]); #[derive(Debug, Copy, Clone, PartialEq)] enum RoomLobby { Room(RoomId), Lobby(LobbyId), } #[derive(Debug, PartialEq)] pub enum ClientRemovalError { ClientNotInArea, InvalidArea, } #[derive(Debug, PartialEq)] pub enum GetClientsError { InvalidArea, } #[derive(Debug, PartialEq)] pub enum GetNeighborError { InvalidClient, InvalidArea, } #[derive(Debug, PartialEq)] pub enum GetLeaderError { InvalidClient, InvalidArea, NoClientInArea, } pub struct ClientLocation2 { lobbies: [Lobby2; 15], rooms: [Option; MAX_ROOMS], client_location: HashMap, } impl ClientLocation2 { pub fn new() -> ClientLocation2 { ClientLocation2 { lobbies: [Lobby2([None; 12]); 15], rooms: [None; MAX_ROOMS], client_location: HashMap::new(), } } pub fn add_client_to_lobby(&mut self, id: ClientId, lobby: LobbyId) -> Result<(), JoinLobbyError> { let l = self.lobbies.get_mut(lobby.0).ok_or(JoinLobbyError::LobbyDoesNotExist)?; let (index, empty_slot) = l.0.iter_mut() .enumerate() .filter(|(_, k)| k.is_none()) .nth(0) .ok_or(JoinLobbyError::LobbyFull)?; *empty_slot = Some(AreaClient2 { client: id, local_client: LocalClientId(index), time_join: SystemTime::now(), }); self.remove_client_from_area(id); self.client_location.insert(id, RoomLobby::Lobby(lobby)); Ok(()) } pub fn add_client_to_next_available_lobby(&mut self, id: ClientId, lobby: LobbyId) -> Result { let l = (0..15) .map(|lobby_index| { let new_lobby = LobbyId((lobby.0 + lobby_index) % 15); (new_lobby, self.add_client_to_lobby(id, new_lobby)) }) .filter(|(_, lobby_option)| { lobby_option.is_ok() }) .nth(0) .ok_or(JoinLobbyError::LobbyFull)?; Ok(l.0) } fn create_new_room(&mut self, id: ClientId) -> Result { let (index, empty_slot) = self.rooms.iter_mut() .enumerate() .filter(|(_, r)| r.is_none()) .nth(0) .ok_or(CreateRoomError::NoOpenSlots)?; *empty_slot = Some(Room2([None; 4])); self.add_client_to_room(id, RoomId(index)).map_err(|err| CreateRoomError::JoinError)?; Ok(RoomId(index)) } fn add_client_to_room(&mut self, id: ClientId, room: RoomId) -> Result<(), JoinRoomError> { let r = self.rooms.get_mut(room.0) .ok_or(JoinRoomError::RoomDoesNotExist)? .as_mut() .ok_or(JoinRoomError::RoomDoesNotExist)?; let (index, empty_slot) = r.0.iter_mut() .enumerate() .filter(|(_, k)| k.is_none()) .nth(0) .ok_or(JoinRoomError::RoomFull)?; *empty_slot = Some(AreaClient2 { client: id, local_client: LocalClientId(index), time_join: SystemTime::now(), }); self.remove_client_from_area(id); self.client_location.insert(id, RoomLobby::Room(room)); Ok(()) } fn get_client_neighbors(&self, id: ClientId) -> Result, GetNeighborError> { let area = self.client_location.get(&id).ok_or(GetNeighborError::InvalidClient)?; match area { RoomLobby::Room(room) => { Ok(self.get_clients_in_room(*room).map_err(|_| GetNeighborError::InvalidArea)? .into_iter() .filter(|c| c.0 != id) .collect()) }, RoomLobby::Lobby(lobby) => { Ok(self.get_clients_in_lobby(*lobby).map_err(|_| GetNeighborError::InvalidArea)? .into_iter() .filter(|c| c.0 != id) .collect()) } } } fn get_room_leader_by_client(&self, id: ClientId) -> Result<(ClientId, LocalClientId), GetLeaderError> { let area = self.client_location.get(&id).ok_or(GetLeaderError::InvalidClient)?; match area { RoomLobby::Room(room) => { let mut r = self.rooms[room.0] .as_ref() .ok_or(GetLeaderError::InvalidArea)? .0.iter().flat_map(|k| k) .collect::>(); //r.sort_by(|a, b| a.time_join.cmp(&b.time_join)); r.sort_by_key(|k| k.time_join); let c = r.get(0).ok_or(GetLeaderError::NoClientInArea)?; Ok((c.client, c.local_client)) }, RoomLobby::Lobby(lobby) => { let mut l = self.lobbies[lobby.0] .0.iter().flat_map(|k| k) .collect::>(); l.sort_by_key(|k| k.time_join); let c = l.get(0).ok_or(GetLeaderError::NoClientInArea)?; Ok((c.client, c.local_client)) } } } pub fn get_clients_in_lobby(&self, lobby: LobbyId) -> Result, GetClientsError> { Ok(self.lobbies.get(lobby.0).ok_or(GetClientsError::InvalidArea)?.0 .iter() .filter_map(|client| { client.map(|c| { (c.client, c.local_client) }) }).collect()) } pub fn get_clients_in_room(&self, room: RoomId) -> Result, GetClientsError> { Ok(self.rooms.get(room.0) .ok_or(GetClientsError::InvalidArea)? .ok_or(GetClientsError::InvalidArea)?.0 .iter() .filter_map(|client| { client.map(|c| { (c.client, c.local_client) }) }).collect()) } pub fn remove_client_from_area(&mut self, id: ClientId) -> Result<(), ClientRemovalError> { let area = self.client_location.get_mut(&id).ok_or(ClientRemovalError::ClientNotInArea)?; let client_list = match area { RoomLobby::Room(room) => { self.rooms[room.0].as_mut().map_or(None, |r| { Some(r.0.iter_mut()) }) }, RoomLobby::Lobby(lobby) => { Some(self.lobbies[lobby.0].0.iter_mut()) } }; client_list .ok_or(ClientRemovalError::InvalidArea)? .filter(|client| { client.map_or(false, |c| { c.client == id }) }) .for_each(|client| { *client = None }); Ok(()) } } #[cfg(test)] mod test { use super::*; #[test] fn test_add_client_to_lobby() { let mut cl = ClientLocation2::new(); cl.add_client_to_lobby(ClientId(12), LobbyId(0)); cl.add_client_to_lobby(ClientId(13), LobbyId(1)); cl.add_client_to_lobby(ClientId(14), LobbyId(0)); assert!(cl.get_clients_in_lobby(LobbyId(0)) == Ok(vec![ (ClientId(12), LocalClientId(0)), (ClientId(14), LocalClientId(1)), ])); } #[test] fn test_add_client_to_full_lobby() { let mut cl = ClientLocation2::new(); (0..12).for_each(|i| { cl.add_client_to_lobby(ClientId(i), LobbyId(0)); }); assert!(cl.add_client_to_lobby(ClientId(99), LobbyId(0)) == Err(JoinLobbyError::LobbyFull)); } #[test] fn test_add_client_to_next_available_lobby() { let mut cl = ClientLocation2::new(); (1..4).for_each(|lobby| { (0..12).for_each(|i| { cl.add_client_to_lobby(ClientId(lobby*12+i), LobbyId(lobby)); }); }); assert!(cl.add_client_to_next_available_lobby(ClientId(99), LobbyId(1)) == Ok(LobbyId(4))); } #[test] fn test_add_to_lobby_when_all_are_full() { let mut cl = ClientLocation2::new(); (0..15).for_each(|lobby| { (0..12).for_each(|i| { cl.add_client_to_lobby(ClientId(lobby*12+i), LobbyId(lobby)); }); }); assert!(cl.add_client_to_next_available_lobby(ClientId(99), LobbyId(1)) == Err(JoinLobbyError::LobbyFull)); } #[test] fn test_new_room() { let mut cl = ClientLocation2::new(); assert!(cl.create_new_room(ClientId(12)) == Ok(RoomId(0))); } #[test] fn test_add_client_to_room() { let mut cl = ClientLocation2::new(); let room = cl.create_new_room(ClientId(12)).unwrap(); assert!(cl.add_client_to_room(ClientId(234), room) == Ok(())); assert!(cl.get_clients_in_room(room) == Ok(vec![ (ClientId(12), LocalClientId(0)), (ClientId(234), LocalClientId(1)), ])); } #[test] fn test_no_new_room_slots() { let mut cl = ClientLocation2::new(); for i in 0..128 { cl.create_new_room(ClientId(i)); } assert!(cl.create_new_room(ClientId(234)) == Err(CreateRoomError::NoOpenSlots)); } #[test] fn test_joining_full_room() { let mut cl = ClientLocation2::new(); let room = cl.create_new_room(ClientId(0)).unwrap(); assert!(cl.add_client_to_room(ClientId(1), room) == Ok(())); assert!(cl.add_client_to_room(ClientId(2), room) == Ok(())); assert!(cl.add_client_to_room(ClientId(3), room) == Ok(())); assert!(cl.add_client_to_room(ClientId(234), room) == Err(JoinRoomError::RoomFull)); } #[test] fn test_adding_client_to_room_removes_from_lobby() { let mut cl = ClientLocation2::new(); cl.add_client_to_lobby(ClientId(93), LobbyId(0)); cl.add_client_to_lobby(ClientId(23), LobbyId(0)); cl.add_client_to_lobby(ClientId(51), LobbyId(0)); cl.add_client_to_lobby(ClientId(12), LobbyId(0)); let room = cl.create_new_room(ClientId(51)).unwrap(); assert!(cl.add_client_to_room(ClientId(93), room) == Ok(())); assert!(cl.get_clients_in_lobby(LobbyId(0)) == Ok(vec![ (ClientId(23), LocalClientId(1)), (ClientId(12), LocalClientId(3)), ])); assert!(cl.get_clients_in_room(room) == Ok(vec![ (ClientId(51), LocalClientId(0)), (ClientId(93), LocalClientId(1)), ])); } #[test] fn test_getting_neighbors() { let mut cl = ClientLocation2::new(); cl.add_client_to_lobby(ClientId(93), LobbyId(0)); cl.add_client_to_lobby(ClientId(23), LobbyId(0)); cl.add_client_to_lobby(ClientId(51), LobbyId(0)); cl.add_client_to_lobby(ClientId(12), LobbyId(0)); assert!(cl.get_client_neighbors(ClientId(23)) == Ok(vec![ (ClientId(93), LocalClientId(0)), (ClientId(51), LocalClientId(2)), (ClientId(12), LocalClientId(3)), ])); } #[test] fn test_failing_to_join_lobby_does_not_remove_from_current_area() { let mut cl = ClientLocation2::new(); (0..12).for_each(|i| { cl.add_client_to_lobby(ClientId(i), LobbyId(0)); }); cl.add_client_to_lobby(ClientId(99), LobbyId(1)); cl.add_client_to_lobby(ClientId(99), LobbyId(0)); assert!(cl.get_clients_in_lobby(LobbyId(0)).unwrap().len() == 12); assert!(cl.get_clients_in_lobby(LobbyId(1)) == Ok(vec![ (ClientId(99), LocalClientId(0)), ])); } #[test] fn test_room_leader() { let mut cl = ClientLocation2::new(); cl.add_client_to_lobby(ClientId(93), LobbyId(0)); cl.add_client_to_lobby(ClientId(23), LobbyId(0)); cl.add_client_to_lobby(ClientId(51), LobbyId(0)); cl.add_client_to_lobby(ClientId(12), LobbyId(0)); assert!(cl.get_room_leader_by_client(ClientId(51)) == Ok((ClientId(93), LocalClientId(0)))); } #[test] fn test_remove_client_from_room() { let mut cl = ClientLocation2::new(); let room = cl.create_new_room(ClientId(51)).unwrap(); cl.add_client_to_room(ClientId(93), room); cl.add_client_to_room(ClientId(23), room); cl.remove_client_from_area(ClientId(51)); cl.add_client_to_room(ClientId(12), room); assert!(cl.get_clients_in_room(room) == Ok(vec![ (ClientId(12), LocalClientId(0)), (ClientId(93), LocalClientId(1)), (ClientId(23), LocalClientId(2)), ])); } #[test] fn test_room_leader_changes_on_leader_leaving() { let mut cl = ClientLocation2::new(); let room = cl.create_new_room(ClientId(51)).unwrap(); cl.add_client_to_room(ClientId(93), room); cl.add_client_to_room(ClientId(23), room); cl.remove_client_from_area(ClientId(51)); cl.add_client_to_room(ClientId(12), room); cl.remove_client_from_area(ClientId(23)); cl.add_client_to_room(ClientId(99), room); assert!(cl.get_room_leader_by_client(ClientId(12)) == Ok((ClientId(93), LocalClientId(1)))); } }