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");
// 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
// let send_message = move |_| {
// send("Hello, world!");
// };
let status = move || ready_state.get().to_string();
let connected = move || ready_state.get() == ConnectionReadyState::Open;
let open_connection = move |_| {
open();
};
let close_connection = move |_| {
close();
};
let fake_new_game_request = NewGameRequest {
name: String::from("Ligma"),
host: Player {
@ -43,15 +37,20 @@ pub fn Websocket() -> impl IntoView {
packs: vec![0],
};
// Game stuff
let new_game_test = move |_| {
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
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) {
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 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 {
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
let (username, _set_username) = create_signal("ligma");
@ -83,7 +97,10 @@ pub fn Websocket() -> impl IntoView {
<div class="w-auto bg-slate-500">
<hr/>
<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">
<button on:click=open_connection disabled=connected>
"Connect"

View file

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

View file

@ -13,29 +13,44 @@ use std::{net::SocketAddr, sync::Arc};
pub mod message_handler;
use crate::message_handler::*;
fn greeting(state: &Arc<AppState>) -> String {
fn motd() -> String {
format!(
"{:#?} Card packs loaded\n\
{:#?} Current active games",
state.all_cards.lock().unwrap().len(),
state.games.lock().unwrap().len(),
"Welcome!"
)
}
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.
let (mut sender, mut receiver) = stream.split();
// sup
let _greeting = sender.send(Message::Text(greeting(&state))).await;
// subscribe to broadcast channel
let mut rx = state.tx.subscribe();
// hydrate user
let _ = &sender.send(Message::Text(motd())).await;
let _ = &sender.send(Message::Text(serde_json::to_string(&server_sum_update(&state)).unwrap())).await;
// ANNOUNCE THY PRESENCE
let msg = format!("{} joined.", who.name);
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
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,
// user_agent: Option<TypedHeader<headers::UserAgent>>,
ConnectInfo(addr): ConnectInfo<SocketAddr>,
@ -68,7 +83,7 @@ pub async fn websocket_handler(
) -> impl IntoResponse {
tracing::debug!("New connection from {addr}");
ws.on_upgrade(move |socket| {
websocket(
on_websocket_connection(
socket,
state,
User {

View file

@ -1,7 +1,6 @@
use crate::api::{greeting, Message, User};
use crate::api::*;
use crate::AppState;
use crate::Arc;
use lib::models::*;
pub async fn message_handler(message: Message, state: &Arc<AppState>, who: &User) {
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.")
}
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 {
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);
tracing::debug!("{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) => {

View file

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