diff --git a/werewolves-server/src/communication/connect.rs b/werewolves-server/src/communication/connect.rs new file mode 100644 index 0000000..3d97241 --- /dev/null +++ b/werewolves-server/src/communication/connect.rs @@ -0,0 +1,60 @@ +use core::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; + +use tokio::sync::Mutex; +use werewolves_proto::player::PlayerId; + +#[derive(Debug, Clone)] +pub struct ConnectUpdate { + updated: Arc, + connected: Arc>>, +} + +impl ConnectUpdate { + pub fn new() -> Self { + Self { + updated: Arc::new(AtomicBool::new(false)), + connected: Arc::new(Mutex::new(Vec::new())), + } + } + + pub async fn connect(&self, pid: PlayerId) { + let mut connected = self.connected.lock().await; + if connected.iter().any(|c| c == &pid) { + return; + } + connected.push(pid); + self.updated.store(true, Ordering::SeqCst); + } + + pub async fn disconnect(&self, pid: PlayerId) { + let mut connected = self.connected.lock().await; + if let Some(idx) = connected + .iter() + .enumerate() + .find_map(|(idx, c)| (c == &pid).then_some(idx)) + { + connected.swap_remove(idx); + self.updated.store(true, Ordering::SeqCst); + } + } +} + +impl Future for ConnectUpdate { + type Output = Arc<[PlayerId]>; + + fn poll( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll { + if self.updated.load(Ordering::SeqCst) + && let Ok(connected) = self.connected.try_lock() + { + self.updated.store(false, Ordering::SeqCst); + std::task::Poll::Ready(connected.clone().into()) + } else { + cx.waker().wake_by_ref(); + std::task::Poll::Pending + } + } +} diff --git a/werewolves-server/src/communication/lobby.rs b/werewolves-server/src/communication/lobby.rs index abe3108..36b81f3 100644 --- a/werewolves-server/src/communication/lobby.rs +++ b/werewolves-server/src/communication/lobby.rs @@ -1,25 +1,28 @@ use tokio::sync::broadcast::Receiver; use werewolves_proto::{error::GameError, player::PlayerId}; -use crate::{communication::Comms, runner::Message}; +use crate::{ + communication::{Comms, connect::ConnectUpdate}, + runner::Message, +}; use super::{HostComms, player::PlayerIdComms}; pub struct LobbyComms { comms: Comms, // TODO: move this to not use a receiver - connect_recv: Receiver<(PlayerId, bool)>, + connect_recv: ConnectUpdate, } impl LobbyComms { - pub fn new(comms: Comms, connect_recv: Receiver<(PlayerId, bool)>) -> Self { + pub fn new(comms: Comms, connect_recv: ConnectUpdate) -> Self { Self { comms, connect_recv, } } - pub fn into_inner(self) -> (Comms, Receiver<(PlayerId, bool)>) { + pub fn into_inner(self) -> (Comms, ConnectUpdate) { (self.comms, self.connect_recv) } @@ -41,12 +44,8 @@ impl LobbyComms { Err(err) => Err(err), } } - r = self.connect_recv.recv() => { - match r { - Ok((player_id, true)) => Ok(Message::Connect(player_id)), - Ok((player_id, false)) => Ok(Message::Disconnect(player_id)), - Err(err) => Err(GameError::GenericError(format!("connect recv: {err}"))), - } + r = self.connect_recv.clone() => { + Ok(Message::ConnectedList(r)) } } } diff --git a/werewolves-server/src/communication/mod.rs b/werewolves-server/src/communication/mod.rs index 7d57d55..64fd0f3 100644 --- a/werewolves-server/src/communication/mod.rs +++ b/werewolves-server/src/communication/mod.rs @@ -5,6 +5,7 @@ use crate::{ runner::Message, }; +pub mod connect; pub mod host; pub mod lobby; pub mod player; diff --git a/werewolves-server/src/connection.rs b/werewolves-server/src/connection.rs index 8cdddf8..e4c9c86 100644 --- a/werewolves-server/src/connection.rs +++ b/werewolves-server/src/connection.rs @@ -13,7 +13,7 @@ use werewolves_proto::{ player::PlayerId, }; -use crate::LogError; +use crate::{LogError, communication::connect::ConnectUpdate}; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ConnectionId(PlayerId, Instant); @@ -66,13 +66,13 @@ impl JoinedPlayer { #[derive(Debug, Clone)] pub struct JoinedPlayers { players: Arc>>, - connect_state_sender: Sender<(PlayerId, bool)>, + connect_state: ConnectUpdate, } impl JoinedPlayers { - pub fn new(connect_state_sender: Sender<(PlayerId, bool)>) -> Self { + pub fn new(connect_state: ConnectUpdate) -> Self { Self { - connect_state_sender, + connect_state, players: Arc::new(Mutex::new(HashMap::new())), } } @@ -126,9 +126,7 @@ impl JoinedPlayers { 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(); + self.connect_state.disconnect(connection.0.clone()).await; if map .get(connection.player_id()) @@ -162,9 +160,8 @@ impl JoinedPlayers { old.receiver } else { - self.connect_state_sender - .send((player_id.clone(), true)) - .log_warn(); + self.connect_state.connect(player_id.clone()).await; + unsafe { map.get(&player_id).unwrap_unchecked() } .receiver .resubscribe() diff --git a/werewolves-server/src/game.rs b/werewolves-server/src/game.rs index de4743a..c47c243 100644 --- a/werewolves-server/src/game.rs +++ b/werewolves-server/src/game.rs @@ -1,8 +1,9 @@ use core::ops::Not; +use std::sync::Arc; use crate::{ LogError, - communication::{Comms, lobby::LobbyComms}, + communication::{Comms, connect::ConnectUpdate, lobby::LobbyComms}, connection::JoinedPlayers, lobby::{Lobby, LobbyPlayers}, runner::{IdentifiedClientMessage, Message}, @@ -23,7 +24,7 @@ type Result = core::result::Result; pub struct GameRunner { game: Game, comms: Comms, - connect_recv: Receiver<(PlayerId, bool)>, + connect_recv: ConnectUpdate, player_sender: LobbyPlayers, roles_revealed: bool, joined_players: JoinedPlayers, @@ -34,7 +35,7 @@ impl GameRunner { game: Game, comms: Comms, player_sender: LobbyPlayers, - connect_recv: Receiver<(PlayerId, bool)>, + connect_recv: ConnectUpdate, joined_players: JoinedPlayers, ) -> Self { Self { @@ -117,6 +118,7 @@ impl GameRunner { }; let mut last_err_log = tokio::time::Instant::now() - tokio::time::Duration::from_secs(60); + let mut connect_list: Arc<[PlayerId]> = Arc::new([]); while acks.iter().any(|(_, ackd)| !*ackd) { let msg = match self.comms.message().await { Ok(msg) => msg, @@ -204,11 +206,14 @@ impl GameRunner { public: _, }, message: _, - }) - | Message::Connect(player_id) => { - (notify_of_role)(&player_id, self.game.village(), &self.player_sender) + }) => (notify_of_role)(&player_id, self.game.village(), &self.player_sender), + Message::ConnectedList(c) => { + let newly_connected = c.iter().filter(|c| connect_list.contains(*c)); + for connected in newly_connected { + (notify_of_role)(connected, self.game.village(), &self.player_sender) + } + connect_list = c; } - Message::Disconnect(_) => {} } } @@ -330,7 +335,7 @@ impl GameEnd { .send_if_present(&identity.player_id, ServerMessage::GameOver(result)) .log_debug(); } - Message::Connect(_) | Message::Disconnect(_) => {} + Message::ConnectedList(_) => {} } None } diff --git a/werewolves-server/src/lobby.rs b/werewolves-server/src/lobby.rs index 69a00f8..094f79a 100644 --- a/werewolves-server/src/lobby.rs +++ b/werewolves-server/src/lobby.rs @@ -142,7 +142,7 @@ impl Lobby { .players_in_lobby .send_if_present(&player_id, ServerMessage::Reset); } - Err((Message::Connect(_), _)) | Err((Message::Disconnect(_), _)) => {} + Err((Message::ConnectedList(_), _)) => {} } None } @@ -299,7 +299,7 @@ impl Lobby { self.send_lobby_info_to_clients().await; self.send_lobby_info_to_host().await.log_debug(); } - Message::Connect(_) | Message::Disconnect(_) => self.send_lobby_info_to_host().await?, + Message::ConnectedList(_) => self.send_lobby_info_to_host().await?, Message::Host(HostMessage::Echo(msg)) => { self.comms()?.host().send(msg).log_warn(); } diff --git a/werewolves-server/src/main.rs b/werewolves-server/src/main.rs index c374216..abe58dd 100644 --- a/werewolves-server/src/main.rs +++ b/werewolves-server/src/main.rs @@ -22,7 +22,7 @@ use std::{env, io::Write, path::Path}; use tokio::sync::{broadcast, mpsc}; use crate::{ - communication::{Comms, host::HostComms, player::PlayerIdComms}, + communication::{Comms, connect::ConnectUpdate, host::HostComms, player::PlayerIdComms}, saver::FileSaver, }; @@ -92,18 +92,14 @@ async fn main() { let (send, recv) = broadcast::channel(100); let (server_send, host_recv) = broadcast::channel(100); let (host_send, server_recv) = mpsc::channel(100); - let (connect_send, connect_recv) = broadcast::channel(100); - let joined_players = JoinedPlayers::new(connect_send); + let conn_update = ConnectUpdate::new(); + let joined_players = JoinedPlayers::new(conn_update.clone()); let lobby_comms = LobbyComms::new( Comms::new( HostComms::new(server_send, server_recv), - PlayerIdComms::new( - //joined_players.clone(), - recv, - // connect_recv.resubscribe() - ), + PlayerIdComms::new(recv), ), - connect_recv, + conn_update, ); let jp_clone = joined_players.clone(); diff --git a/werewolves-server/src/runner.rs b/werewolves-server/src/runner.rs index 10d1a68..788a8de 100644 --- a/werewolves-server/src/runner.rs +++ b/werewolves-server/src/runner.rs @@ -1,4 +1,5 @@ use core::time::Duration; +use std::sync::Arc; use werewolves_proto::{ message::{ClientMessage, Identification, host::HostMessage}, @@ -79,8 +80,7 @@ pub async fn run_game(joined_players: JoinedPlayers, comms: LobbyComms, mut save pub enum Message { Host(HostMessage), Client(IdentifiedClientMessage), - Connect(PlayerId), - Disconnect(PlayerId), + ConnectedList(Arc<[PlayerId]>), } pub enum RunningState { diff --git a/werewolves/index.scss b/werewolves/index.scss index 48ee25d..fac019b 100644 --- a/werewolves/index.scss +++ b/werewolves/index.scss @@ -105,12 +105,24 @@ nav.debug-nav { } } +.player-list { + padding-bottom: 80px; + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 10px; + justify-items: center; +} .player { + flex-grow: 0; + display: flex; + justify-content: stretch; + margin: 0px; min-width: 10rem; max-width: 10vw; - max-height: 4rem; + height: 4rem; text-align: center; justify-content: center; @@ -120,13 +132,35 @@ nav.debug-nav { filter: hue-rotate(90deg); } - &.connected {} + block-size: max-content; - &.disconnected { - // background-color: $disconnected_color; - // border: 3px solid darken($disconnected_color, 20%); + &>button { + width: 100%; + height: 100%; + border: 1px solid $disconnected_color; + background-color: color.change($disconnected_color, $alpha: 0.15); + color: $disconnected_color; + + &:hover { + filter: brightness(150%); + background-color: color.change($disconnected_color, $alpha: 0.15); + color: $disconnected_color; + } } + &.connected { + &>button { + background-color: color.change($connected_color, $alpha: 0.15); + border: 1px solid $connected_color; + color: $connected_color; + + &:hover { + filter: brightness(150%); + } + } + } + + &.dead { filter: grayscale(100%); } @@ -228,7 +262,7 @@ button { content: attr(reason); position: absolute; margin-top: 10px; - // top: 90%; + top: 90%; // left: 0; font: 'Cute Font'; // color: #000; @@ -464,18 +498,6 @@ bool_role { } -player { - background-color: rgba(255, 107, 255, 0.7); - width: fit-content; - padding-left: 10px; - padding-right: 10px; - padding-top: 5px; - padding-bottom: 5px; - margin: 10px; - color: rgba(255, 255, 255, 0.9); -} - - client { list-style: none; @@ -738,16 +760,6 @@ error { } } -.player-list { - padding-bottom: 80px; - display: flex; - flex-direction: row; - flex-wrap: wrap; - gap: 10px; - // align-items: center; - justify-content: space-evenly; -} - .binary { .button-container { background-color: $village_color; @@ -911,3 +923,11 @@ input { } } } + +.big-screen { + align-content: center; + align-items: center; + justify-content: center; + height: 100vh; + position: fixed; +} diff --git a/werewolves/src/clients/host/host.rs b/werewolves/src/clients/host/host.rs index 947161e..722d9ca 100644 --- a/werewolves/src/clients/host/host.rs +++ b/werewolves/src/clients/host/host.rs @@ -580,10 +580,9 @@ impl Component for Host { HostEvent::SetBigScreenState(state) => { self.big_screen = state; if self.big_screen - && let Some(root) = gloo::utils::document().document_element() - && let Err(err) = root.set_attribute("style", "font-size: 3rem;") + && let Ok(Some(root)) = gloo::utils::document().query_selector(".content") { - log::error!("setting zoom: {err:?}"); + root.set_class_name("content big-screen") } if state { diff --git a/werewolves/src/components/lobby.rs b/werewolves/src/components/lobby.rs index 6ffabe3..bbbdad6 100644 --- a/werewolves/src/components/lobby.rs +++ b/werewolves/src/components/lobby.rs @@ -1,4 +1,3 @@ -use core::num::NonZeroU8; use std::rc::Rc; use werewolves_proto::{message::PlayerState, player::PlayerId}; @@ -21,7 +20,7 @@ pub fn Lobby(LobbyProps { players, on_action }: &LobbyProps) -> Html {
{ players - .into_iter() + .iter() .map(|p| html! {}) .collect::() } diff --git a/werewolves/src/components/lobby_player.rs b/werewolves/src/components/lobby_player.rs index 2eb83a1..e044520 100644 --- a/werewolves/src/components/lobby_player.rs +++ b/werewolves/src/components/lobby_player.rs @@ -21,22 +21,24 @@ pub enum LobbyPlayerAction { #[function_component] pub fn LobbyPlayer(LobbyPlayerProps { player, on_action }: &LobbyPlayerProps) -> Html { - let class = if player.connected { - "connected" - } else { - "disconnected" - }; + let open = use_state(|| false); + let class = player.connected.then_some("connected"); let pid = player.identification.player_id.clone(); + let action_open = open.clone(); let action = |action: LobbyPlayerAction| { let pid = pid.clone(); on_action .as_ref() .cloned() - .map(|on_action| Callback::from(move |_| on_action.emit((pid.clone(), action)))) + .map(|on_action| { + Callback::from(move |_| { + on_action.emit((pid.clone(), action)); + action_open.set(false); + }) + }) .unwrap_or_default() }; let number = use_state(String::new); - let open = use_state(|| false); let number_open = use_state(|| false); let submenu_open = open.clone(); let submenu = on_action.clone().map(|on_action| { diff --git a/werewolves/src/components/notification.rs b/werewolves/src/components/notification.rs deleted file mode 100644 index 7f32558..0000000 --- a/werewolves/src/components/notification.rs +++ /dev/null @@ -1,19 +0,0 @@ -use yew::prelude::*; - -// #[derive(Debug, PartialEq, Properties)] -// pub struct NotificationProps { -// pub text: String, -// pub callback: Callback<()>, -// } - -// #[function_component] -// pub fn Notification(props: &NotificationProps) -> Html { -// let cb = props.callback.clone(); -// let on_click = Callback::from(move |_| cb.clone().emit(())); -// html! { -// -//

{props.text.clone()}

-// -//
-// } -// }