state stuff

This commit is contained in:
Adam 2024-07-22 01:32:09 -04:00
parent 61ded634cc
commit 0a59ebe9ea
5 changed files with 89 additions and 46 deletions

View file

@ -15,23 +15,17 @@ pub fn Websocket() -> impl IntoView {
.. ..
} = use_websocket("ws://0.0.0.0:3030/websocket"); } = use_websocket("ws://0.0.0.0:3030/websocket");
// Signals
let (online_users, set_online_users) = create_signal(0);
let (active_games, set_active_games) = create_signal(0);
let (chat_history, set_chat_history) = create_signal::<Vec<String>>(vec![]);
// Websocket stuff // Websocket stuff
// let send_message = move |_| {
// send("Hello, world!");
// };
let status = move || ready_state.get().to_string(); let status = move || ready_state.get().to_string();
let connected = move || ready_state.get() == ConnectionReadyState::Open; let connected = move || ready_state.get() == ConnectionReadyState::Open;
let open_connection = move |_| { let open_connection = move |_| {
open(); open();
}; };
let close_connection = move |_| {
close();
};
let fake_new_game_request = NewGameRequest { let fake_new_game_request = NewGameRequest {
name: String::from("Ligma"), name: String::from("Ligma"),
host: Player { host: Player {
@ -43,15 +37,20 @@ pub fn Websocket() -> impl IntoView {
packs: vec![0], packs: vec![0],
}; };
// Game stuff
let new_game_test = move |_| { let new_game_test = move |_| {
send(&to_string(&fake_new_game_request).unwrap()); send(&to_string(&fake_new_game_request).unwrap());
}; };
let close_connection = move |_| {
close();
set_online_users(0);
set_active_games(0);
update_chat_history(&set_chat_history, format!("Disconnected.\n"));
};
// Chat stuff // Chat stuff
let chat_history_ref = create_node_ref::<Textarea>(); let chat_history_ref = create_node_ref::<Textarea>();
let (chat_history, set_chat_history) = create_signal::<Vec<String>>(vec![]);
fn update_chat_history(&history: &WriteSignal<Vec<String>>, message: String) { fn update_chat_history(&history: &WriteSignal<Vec<String>>, message: String) {
let _ = &history.update(|history: &mut Vec<_>| history.push(message)); let _ = &history.update(|history: &mut Vec<_>| history.push(message));
} }
@ -64,18 +63,33 @@ pub fn Websocket() -> impl IntoView {
if let Some(message) = message_raw { if let Some(message) = message_raw {
if let Ok(game) = serde_json::from_str::<Game>(message) { if let Ok(game) = serde_json::from_str::<Game>(message) {
logging::log!("{:#}", serde_json::json!(game)); logging::log!("Game object received.");
} else if let Ok(state_summary) =
serde_json::from_str::<ServerStateSummary>(message)
{
logging::log!(
"Users: {}\nGames: {}",
state_summary.online_users,
state_summary.active_games
);
set_online_users(state_summary.online_users);
set_active_games(state_summary.active_games);
} else { } else {
update_chat_history(&set_chat_history, format!("{}\n", message)); update_chat_history(&set_chat_history, format!("{}\n", message));
// Scroll chat textarea to bottom
if let Some(hist) = chat_history_ref.get() {
hist.set_scroll_top(hist.scroll_height());
}
} }
} }
}) })
}); });
create_effect(move |_| {
chat_history.with(move |_| {
// Scroll chat textarea to bottom
if let Some(hist) = chat_history_ref.get() {
hist.set_scroll_top(hist.scroll_height());
}
})
});
// Login stuff // Login stuff
let (username, _set_username) = create_signal("ligma"); let (username, _set_username) = create_signal("ligma");
@ -83,7 +97,10 @@ pub fn Websocket() -> impl IntoView {
<div class="w-auto bg-slate-500"> <div class="w-auto bg-slate-500">
<hr/> <hr/>
<h2 class="text-2xl">Connection</h2> <h2 class="text-2xl">Connection</h2>
<p class="p-1">"status: " {status}</p>
<p class="p-1">"Users Online: " {online_users}</p>
<p class="p-1">"Active Games: " {active_games}</p>
<p class="p-1">"Connection Status: " {status}</p>
<div class="p-1"> <div class="p-1">
<button on:click=open_connection disabled=connected> <button on:click=open_connection disabled=connected>
"Connect" "Connect"

View file

@ -1,7 +1,15 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::net::SocketAddr; use std::net::SocketAddr;
/// Server state summary
#[derive(Serialize, Deserialize, Debug)]
pub struct ServerStateSummary {
pub online_users: usize,
pub active_games: usize,
}
/// User /// User
#[derive(Clone, Eq, PartialEq, Hash)]
pub struct User { pub struct User {
pub name: String, pub name: String,
pub addr: SocketAddr, pub addr: SocketAddr,
@ -109,5 +117,3 @@ pub struct Game {
/// Black card for the current round /// Black card for the current round
pub current_black: Option<CardBlack>, pub current_black: Option<CardBlack>,
} }

View file

@ -13,29 +13,44 @@ use std::{net::SocketAddr, sync::Arc};
pub mod message_handler; pub mod message_handler;
use crate::message_handler::*; use crate::message_handler::*;
fn greeting(state: &Arc<AppState>) -> String { fn motd() -> String {
format!( format!(
"{:#?} Card packs loaded\n\ "Welcome!"
{:#?} Current active games",
state.all_cards.lock().unwrap().len(),
state.games.lock().unwrap().len(),
) )
} }
pub async fn websocket(stream: WebSocket, state: Arc<AppState>, who: User) { fn server_sum_update(state: &Arc<AppState>) -> ServerStateSummary {
ServerStateSummary {
online_users: state.users.lock().unwrap().len(),
active_games: state.games.lock().unwrap().len(),
}
}
pub async fn on_websocket_connection(stream: WebSocket, state: Arc<AppState>, who: User) {
// Add user to users
//if doesn't exist
let _true_if_not_exist = &state.users.lock().unwrap().insert(who.clone());
//etc
// By splitting, we can send and receive at the same time. // By splitting, we can send and receive at the same time.
let (mut sender, mut receiver) = stream.split(); let (mut sender, mut receiver) = stream.split();
// sup // hydrate user
let _greeting = sender.send(Message::Text(greeting(&state))).await; let _ = &sender.send(Message::Text(motd())).await;
let _ = &sender.send(Message::Text(serde_json::to_string(&server_sum_update(&state)).unwrap())).await;
// subscribe to broadcast channel
let mut rx = state.tx.subscribe();
// ANNOUNCE THY PRESENCE // ANNOUNCE THY PRESENCE
let msg = format!("{} joined.", who.name); let msg = format!("{} joined.", who.name);
tracing::debug!("{msg}"); tracing::debug!("{msg}");
let _ = state.tx.send(msg); let _ = &state.tx.send(msg);
// Broadcast server state summary update
let _ = &state
.tx
.send(serde_json::to_string(&server_sum_update(&state)).unwrap());
// subscribe to broadcast channel
let mut rx = state.tx.subscribe();
// handle broadcasting further awesome messages // handle broadcasting further awesome messages
let mut send_task = tokio::spawn(async move { let mut send_task = tokio::spawn(async move {
@ -60,7 +75,7 @@ pub async fn websocket(stream: WebSocket, state: Arc<AppState>, who: User) {
}; };
} }
pub async fn websocket_handler( pub async fn websocket_connection_handler(
ws: WebSocketUpgrade, ws: WebSocketUpgrade,
// user_agent: Option<TypedHeader<headers::UserAgent>>, // user_agent: Option<TypedHeader<headers::UserAgent>>,
ConnectInfo(addr): ConnectInfo<SocketAddr>, ConnectInfo(addr): ConnectInfo<SocketAddr>,
@ -68,7 +83,7 @@ pub async fn websocket_handler(
) -> impl IntoResponse { ) -> impl IntoResponse {
tracing::debug!("New connection from {addr}"); tracing::debug!("New connection from {addr}");
ws.on_upgrade(move |socket| { ws.on_upgrade(move |socket| {
websocket( on_websocket_connection(
socket, socket,
state, state,
User { User {

View file

@ -1,7 +1,6 @@
use crate::api::{greeting, Message, User}; use crate::api::*;
use crate::AppState; use crate::AppState;
use crate::Arc; use crate::Arc;
use lib::models::*;
pub async fn message_handler(message: Message, state: &Arc<AppState>, who: &User) { pub async fn message_handler(message: Message, state: &Arc<AppState>, who: &User) {
let tx = &state.tx; let tx = &state.tx;
@ -20,7 +19,8 @@ pub async fn message_handler(message: Message, state: &Arc<AppState>, who: &User
tracing::error!("Failed to convert Game object to JSON.") tracing::error!("Failed to convert Game object to JSON.")
} }
state.games.lock().unwrap().push(new_game_object); state.games.lock().unwrap().push(new_game_object);
let _update = tx.send(greeting(state)); let _ = tx.send(serde_json::to_string(&server_sum_update(state)).unwrap());
// let _update = tx.send(motd());
} else { } else {
let _res = tx.send(String::from("error creating game")); let _res = tx.send(String::from("error creating game"));
} }
@ -50,6 +50,8 @@ pub async fn message_handler(message: Message, state: &Arc<AppState>, who: &User
let msg = format!("{0} left.", who.name); let msg = format!("{0} left.", who.name);
tracing::debug!("{msg}"); tracing::debug!("{msg}");
let _ = tx.send(msg); let _ = tx.send(msg);
let _ = state.users.lock().unwrap().remove(who);
let _ = tx.send(serde_json::to_string(&server_sum_update(state)).unwrap());
} }
Message::Pong(ping) => { Message::Pong(ping) => {

View file

@ -2,6 +2,7 @@ use anyhow::{Context, Result};
use axum::{response::Html, routing::get, Router}; use axum::{response::Html, routing::get, Router};
use axum_extra::response::Css; use axum_extra::response::Css;
use lib::models::*; use lib::models::*;
use std::collections::HashSet;
use std::{ use std::{
// collections::HashSet, // collections::HashSet,
fs, fs,
@ -25,7 +26,8 @@ fn load_json(path: &str) -> Result<Vec<CardSet>> {
} }
// this is still around just for reference // this is still around just for reference
fn _test() -> Result<()> { #[allow(dead_code)]
fn test() -> Result<()> {
// choose decks // choose decks
let cards_input_path: &str = "../data/cah-cards-full.json"; let cards_input_path: &str = "../data/cah-cards-full.json";
@ -78,11 +80,11 @@ fn _test() -> Result<()> {
// Our shared state // Our shared state
pub struct AppState { pub struct AppState {
// We require unique usernames. This tracks which usernames have been taken. // We require unique usernames. This tracks which usernames have been taken.
// user_set: Mutex<HashSet<String>>, users: Mutex<HashSet<User>>,
// Channel used to send messages to all connected clients. // Channel used to send messages to all connected clients.
tx: broadcast::Sender<String>, tx: broadcast::Sender<String>,
// Master card decks // Master card decks
all_cards: Mutex<Vec<CardSet>>, // all_cards: Mutex<Vec<CardSet>>,
// Games list // Games list
games: Mutex<Vec<Game>>, games: Mutex<Vec<Game>>,
} }
@ -115,13 +117,14 @@ async fn main() -> Result<()> {
// Set up application state for use with with_state(). // Set up application state for use with with_state().
// let user_set = Mutex::new(HashSet::new()); // let user_set = Mutex::new(HashSet::new());
let (tx, _rx) = broadcast::channel(100); let (tx, _rx) = broadcast::channel(100);
let cards_input_path: &str = "data/cah-cards-full.json"; // let cards_input_path: &str = "data/cah-cards-full.json";
let all_cards = Mutex::new(load_json(cards_input_path)?); let users = Mutex::new(HashSet::<User>::new());
// let all_cards = Mutex::new(load_json(cards_input_path)?);
let games = Mutex::new(vec![]); let games = Mutex::new(vec![]);
let app_state = Arc::new(AppState { let app_state = Arc::new(AppState {
// user_set, users,
tx, tx,
all_cards, // all_cards,
games, games,
}); });
@ -130,7 +133,7 @@ async fn main() -> Result<()> {
.route("/spawn_clients", get(spawn_clients)) .route("/spawn_clients", get(spawn_clients))
.route("/test_client", get(test_client)) .route("/test_client", get(test_client))
.route("/reference_client", get(reference_client)) .route("/reference_client", get(reference_client))
.route("/websocket", get(websocket_handler)) .route("/websocket", get(websocket_connection_handler))
.route("/css", get(css)) .route("/css", get(css))
.nest_service("/", ServeDir::new("dist")) .nest_service("/", ServeDir::new("dist"))
.with_state(app_state); .with_state(app_state);