diff --git a/Cargo.lock b/Cargo.lock index ad0fd74..21288b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -232,9 +232,12 @@ dependencies = [ "futures", "log", "reqwasm", + "serde", + "serde_json", "wasm-bindgen-futures", "wasm-logger", "yew", + "yew-agent", "yew-router", ] @@ -344,8 +347,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -364,7 +369,7 @@ dependencies = [ "gloo-storage", "gloo-timers", "gloo-utils", - "gloo-worker", + "gloo-worker 0.2.1", ] [[package]] @@ -519,6 +524,23 @@ dependencies = [ "web-sys", ] +[[package]] +name = "gloo-worker" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09110b5555bcafe508cee0fb94308af9aac7a85f980d3c88b270d117c6c6911d" +dependencies = [ + "anymap2", + "bincode", + "gloo-console", + "gloo-utils", + "js-sys", + "serde", + "slab", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "gloo-worker" version = "0.2.1" @@ -1456,6 +1478,7 @@ checksum = "345444e32442451b267fc254ae85a209c64be56d2890e601a0c37ff0c3c5ecd2" dependencies = [ "getrandom", "serde", + "wasm-bindgen", ] [[package]] @@ -1688,6 +1711,16 @@ dependencies = [ "yew-macro", ] +[[package]] +name = "yew-agent" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b06f7c5ed97fff22816bb00d3d82ebc0fc1119d7bbb9e07e62c0d2853f51920a" +dependencies = [ + "gloo-worker 0.1.2", + "yew", +] + [[package]] name = "yew-macro" version = "0.20.0" diff --git a/Cargo.toml b/Cargo.toml index 45234cd..2b9b5b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,4 +9,4 @@ edition = "2021" [workspace.dependencies] serde = { version = "1", features = ["derive"] } common = { path = "crates/common" } -uuid = { vserion = "1.3", features = ["serde", "v4"] } +uuid = { vserion = "1.3", features = ["serde", "v4", "js"] } diff --git a/crates/backend/src/main.rs b/crates/backend/src/main.rs index b9b5e1f..78d85d4 100644 --- a/crates/backend/src/main.rs +++ b/crates/backend/src/main.rs @@ -129,7 +129,7 @@ async fn main() { async fn ws_handler( ws: WebSocketUpgrade, user_agent: Option>, - Extension(app_state_watch_rx): Extension>, + Extension(state_watch_rx): Extension>, ) -> impl IntoResponse { let user_agent = if let Some(TypedHeader(user_agent)) = user_agent { user_agent.to_string() @@ -138,15 +138,15 @@ async fn ws_handler( }; tracing::debug!("{user_agent} connected websocket."); - ws.on_upgrade(move |socket| handle_socket(socket, app_state_watch_rx)) + ws.on_upgrade(move |socket| handle_socket(socket, state_watch_rx)) } /// Websocket statemachine (one will be spawned per connection) -async fn handle_socket(mut socket: WebSocket, app_state_watch_rx: tokio::sync::watch::Receiver) { - let mut stream = tokio_stream::wrappers::WatchStream::new(app_state_watch_rx); - loop { - let app_state = stream.next().await; - let serialized = serde_json::to_string(&app_state).expect("Failed to serialize app state to JSON"); +async fn handle_socket(mut socket: WebSocket, state_watch_rx: tokio::sync::watch::Receiver) { + let mut stream = tokio_stream::wrappers::WatchStream::new(state_watch_rx); + while let Some(state) = stream.next().await { + let state: common::WebSocketMessage = state; + let serialized = serde_json::to_string(&state).expect("Failed to serialize app state to JSON"); if socket.send(Message::Text(serialized)).await.is_err() { tracing::debug!("Websocket client disconnected"); break; diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index 8d73fce..c767ebc 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -1,6 +1,8 @@ use serde::Deserialize; use serde::Serialize; +pub type WebSocketMessage = State; + #[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct State { pub achievements: Vec, diff --git a/crates/frontend/Cargo.toml b/crates/frontend/Cargo.toml index d09ef54..4443562 100644 --- a/crates/frontend/Cargo.toml +++ b/crates/frontend/Cargo.toml @@ -13,3 +13,6 @@ common.workspace = true futures = "0.3.28" wasm-bindgen-futures = "0.4.36" reqwasm = "0.5.0" +yew-agent = "0.2.0" +serde.workspace = true +serde_json = "1.0.96" diff --git a/crates/frontend/index.html b/crates/frontend/index.html index 5d0ae09..29e20a1 100644 --- a/crates/frontend/index.html +++ b/crates/frontend/index.html @@ -3,5 +3,7 @@ Achievements + + diff --git a/crates/frontend/src/bin/app.rs b/crates/frontend/src/bin/app.rs new file mode 100644 index 0000000..dc5ff1c --- /dev/null +++ b/crates/frontend/src/bin/app.rs @@ -0,0 +1,4 @@ +fn main() { + wasm_logger::init(wasm_logger::Config::default()); + yew::Renderer::::new().render(); +} diff --git a/crates/frontend/src/bin/event_bus.rs b/crates/frontend/src/bin/event_bus.rs new file mode 100644 index 0000000..c839061 --- /dev/null +++ b/crates/frontend/src/bin/event_bus.rs @@ -0,0 +1,6 @@ +use frontend::event_bus::EventBus; +use yew_agent::PublicWorker; + +fn main() { + EventBus::register(); +} diff --git a/crates/frontend/src/components/root.rs b/crates/frontend/src/components/root.rs index 7893c1f..7af273f 100644 --- a/crates/frontend/src/components/root.rs +++ b/crates/frontend/src/components/root.rs @@ -1,26 +1,75 @@ +use crate::event_bus::EventBus; +use crate::services::websocket::WebsocketService; +use crate::AppState; use crate::Route; +use common::Achievement; +use std::rc::Rc; use yew::functional::*; use yew::prelude::*; +use yew_agent::Bridge; +use yew_agent::Bridged; use yew_router::prelude::*; -#[function_component(Root)] -pub fn root() -> Html { - let app_state = use_context::().expect("no context found"); +pub struct Root { + wss: WebsocketService, + _producer: Box>, + achievements: Vec, +} - let achievements = app_state - .state - .achievements - .iter() - .map(|a| { - html! { -

{format!("{}", a.goal)}

+pub enum Msg { + HandleMsg(String), +} + +impl Component for Root { + type Message = Msg; + + type Properties = (); + + fn create(ctx: &Context) -> Self { + // let ctx_link = ctx + // .link() + // .context::(Callback::noop()) + // .expect("context to be set"); + + let wss = WebsocketService::new(); + + let cb = { + let link = ctx.link().clone(); + move |e| link.send_message(Msg::HandleMsg(e)) + }; + + Self { + wss, + _producer: EventBus::bridge(Rc::new(cb)), + achievements: vec![], + } + } + + fn update(&mut self, _ctx: &Context, msg: Self::Message) -> bool { + match msg { + Msg::HandleMsg(s) => { + let msg: common::WebSocketMessage = serde_json::from_str(&s).unwrap(); + self.achievements = msg.achievements; + true } - }) - .collect::(); + } + } - html! { -
- {achievements} -
+ fn view(&self, ctx: &Context) -> Html { + let achievements = self + .achievements + .iter() + .map(|a| { + html! { +

{format!("{}", a.goal)}

+ } + }) + .collect::(); + + html! { +
+ {achievements} +
+ } } } diff --git a/crates/frontend/src/event_bus.rs b/crates/frontend/src/event_bus.rs new file mode 100644 index 0000000..a7b6b66 --- /dev/null +++ b/crates/frontend/src/event_bus.rs @@ -0,0 +1,54 @@ +use serde::Deserialize; +use serde::Serialize; +use std::collections::HashSet; +use yew_agent::HandlerId; +use yew_agent::Public; +use yew_agent::WorkerLink; + +pub struct EventBus { + link: WorkerLink, + subscribers: HashSet, +} + +#[derive(Serialize, Deserialize)] +pub enum EventBusInput { + EventBusMsg(String), +} + +impl yew_agent::Worker for EventBus { + type Reach = Public; + type Input = EventBusInput; + type Output = String; + type Message = (); + + fn create(link: WorkerLink) -> Self { + Self { + link, + subscribers: HashSet::new(), + } + } + + fn update(&mut self, _msg: Self::Message) {} + + fn handle_input(&mut self, msg: Self::Input, id: HandlerId) { + match msg { + EventBusInput::EventBusMsg(s) => { + for sub in &self.subscribers { + self.link.respond(*sub, s.clone()); + } + } + } + } + + fn connected(&mut self, id: HandlerId) { + self.subscribers.insert(id); + } + + fn disconnected(&mut self, id: HandlerId) { + self.subscribers.remove(&id); + } + + fn name_of_resource() -> &'static str { + "event_bus.js" + } +} diff --git a/crates/frontend/src/main.rs b/crates/frontend/src/lib.rs similarity index 75% rename from crates/frontend/src/main.rs rename to crates/frontend/src/lib.rs index 2fed41c..949288f 100644 --- a/crates/frontend/src/main.rs +++ b/crates/frontend/src/lib.rs @@ -7,6 +7,7 @@ use yew_router::Routable; use yew_router::Switch; mod components; +pub mod event_bus; mod services; #[derive(Debug, Clone, Copy, PartialEq, Routable)] @@ -35,16 +36,7 @@ struct AppStateInner { type AppState = Rc; #[function_component] -fn App() -> Html { - // let counter = use_state(|| 0); - // let onclick = { - // let counter = counter.clone(); - // move |_| { - // let value = *counter + 1; - // counter.set(value); - // } - // }; - +pub fn App() -> Html { let ctx = use_state(|| Rc::new(AppStateInner::default())); html! { @@ -57,8 +49,3 @@ fn App() -> Html { > } } - -fn main() { - wasm_logger::init(wasm_logger::Config::default()); - yew::Renderer::::new().render(); -} diff --git a/crates/frontend/src/services/websocket.rs b/crates/frontend/src/services/websocket.rs index 92e59f4..3ff5564 100644 --- a/crates/frontend/src/services/websocket.rs +++ b/crates/frontend/src/services/websocket.rs @@ -1,9 +1,12 @@ +use crate::event_bus::EventBus; +use crate::event_bus::EventBusInput; use futures::channel::mpsc::Sender; use futures::SinkExt; use futures::StreamExt; use reqwasm::websocket::futures::WebSocket; use reqwasm::websocket::Message; use wasm_bindgen_futures::spawn_local; +use yew_agent::Dispatched; pub struct WebsocketService { pub tx: Sender, @@ -11,11 +14,12 @@ pub struct WebsocketService { impl WebsocketService { pub fn new() -> Self { - let ws = WebSocket::open("ws://127.0.0.1:4000").unwrap(); + let ws = WebSocket::open("ws://127.0.0.1:4000/ws").unwrap(); let (mut write, mut read) = ws.split(); let (in_tx, mut in_rx) = futures::channel::mpsc::channel::(1000); + let mut event_bus = EventBus::dispatcher(); spawn_local(async move { while let Some(s) = in_rx.next().await { @@ -29,15 +33,17 @@ impl WebsocketService { match msg { Ok(Message::Text(data)) => { log::debug!("from websocket: {}", data); + event_bus.send(EventBusInput::EventBusMsg(data)); } Ok(Message::Bytes(b)) => { let decoded = std::str::from_utf8(&b); if let Ok(val) = decoded { log::debug!("from websocket: {}", val); + event_bus.send(EventBusInput::EventBusMsg(val.into())); } } Err(e) => { - log::error!("ws: {:?}", e) + log::error!("ws: {:?}", e); } } } diff --git a/justfile b/justfile index 84504bf..a6d7e1c 100644 --- a/justfile +++ b/justfile @@ -18,7 +18,7 @@ fmt: taplo fmt `fd --extension=toml` run-backend: - RUST_LOG=debug cargo run -p backend + RUST_LOG=backend=debug cargo run -p backend # POST /create create-gul-bus: