state stuff
This commit is contained in:
parent
61ded634cc
commit
0a59ebe9ea
5 changed files with 89 additions and 46 deletions
|
@ -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"
|
||||||
|
|
|
@ -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>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Add table
Reference in a new issue