hul ignennem

This commit is contained in:
Asger Juul Brunshøj 2023-06-11 14:36:21 +02:00
parent bfabcc0abc
commit fb1b863746
13 changed files with 189 additions and 43 deletions

35
Cargo.lock generated
View File

@ -232,9 +232,12 @@ dependencies = [
"futures", "futures",
"log", "log",
"reqwasm", "reqwasm",
"serde",
"serde_json",
"wasm-bindgen-futures", "wasm-bindgen-futures",
"wasm-logger", "wasm-logger",
"yew", "yew",
"yew-agent",
"yew-router", "yew-router",
] ]
@ -344,8 +347,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"js-sys",
"libc", "libc",
"wasi", "wasi",
"wasm-bindgen",
] ]
[[package]] [[package]]
@ -364,7 +369,7 @@ dependencies = [
"gloo-storage", "gloo-storage",
"gloo-timers", "gloo-timers",
"gloo-utils", "gloo-utils",
"gloo-worker", "gloo-worker 0.2.1",
] ]
[[package]] [[package]]
@ -519,6 +524,23 @@ dependencies = [
"web-sys", "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]] [[package]]
name = "gloo-worker" name = "gloo-worker"
version = "0.2.1" version = "0.2.1"
@ -1456,6 +1478,7 @@ checksum = "345444e32442451b267fc254ae85a209c64be56d2890e601a0c37ff0c3c5ecd2"
dependencies = [ dependencies = [
"getrandom", "getrandom",
"serde", "serde",
"wasm-bindgen",
] ]
[[package]] [[package]]
@ -1688,6 +1711,16 @@ dependencies = [
"yew-macro", "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]] [[package]]
name = "yew-macro" name = "yew-macro"
version = "0.20.0" version = "0.20.0"

View File

@ -9,4 +9,4 @@ edition = "2021"
[workspace.dependencies] [workspace.dependencies]
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
common = { path = "crates/common" } common = { path = "crates/common" }
uuid = { vserion = "1.3", features = ["serde", "v4"] } uuid = { vserion = "1.3", features = ["serde", "v4", "js"] }

View File

@ -129,7 +129,7 @@ async fn main() {
async fn ws_handler( async fn ws_handler(
ws: WebSocketUpgrade, ws: WebSocketUpgrade,
user_agent: Option<TypedHeader<headers::UserAgent>>, user_agent: Option<TypedHeader<headers::UserAgent>>,
Extension(app_state_watch_rx): Extension<tokio::sync::watch::Receiver<AppState>>, Extension(state_watch_rx): Extension<tokio::sync::watch::Receiver<common::State>>,
) -> impl IntoResponse { ) -> impl IntoResponse {
let user_agent = if let Some(TypedHeader(user_agent)) = user_agent { let user_agent = if let Some(TypedHeader(user_agent)) = user_agent {
user_agent.to_string() user_agent.to_string()
@ -138,15 +138,15 @@ async fn ws_handler(
}; };
tracing::debug!("{user_agent} connected websocket."); 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) /// Websocket statemachine (one will be spawned per connection)
async fn handle_socket(mut socket: WebSocket, app_state_watch_rx: tokio::sync::watch::Receiver<AppState>) { async fn handle_socket(mut socket: WebSocket, state_watch_rx: tokio::sync::watch::Receiver<common::State>) {
let mut stream = tokio_stream::wrappers::WatchStream::new(app_state_watch_rx); let mut stream = tokio_stream::wrappers::WatchStream::new(state_watch_rx);
loop { while let Some(state) = stream.next().await {
let app_state = stream.next().await; let state: common::WebSocketMessage = state;
let serialized = serde_json::to_string(&app_state).expect("Failed to serialize app state to JSON"); let serialized = serde_json::to_string(&state).expect("Failed to serialize app state to JSON");
if socket.send(Message::Text(serialized)).await.is_err() { if socket.send(Message::Text(serialized)).await.is_err() {
tracing::debug!("Websocket client disconnected"); tracing::debug!("Websocket client disconnected");
break; break;

View File

@ -1,6 +1,8 @@
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
pub type WebSocketMessage = State;
#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct State { pub struct State {
pub achievements: Vec<Achievement>, pub achievements: Vec<Achievement>,

View File

@ -13,3 +13,6 @@ common.workspace = true
futures = "0.3.28" futures = "0.3.28"
wasm-bindgen-futures = "0.4.36" wasm-bindgen-futures = "0.4.36"
reqwasm = "0.5.0" reqwasm = "0.5.0"
yew-agent = "0.2.0"
serde.workspace = true
serde_json = "1.0.96"

View File

@ -3,5 +3,7 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<title>Achievements</title> <title>Achievements</title>
<link data-trunk rel="rust" href="Cargo.toml" data-bin="app" data-type="main" />
<link data-trunk rel="rust" href="Cargo.toml" data-bin="event_bus" data-type="worker" />
</head> </head>
</html> </html>

View File

@ -0,0 +1,4 @@
fn main() {
wasm_logger::init(wasm_logger::Config::default());
yew::Renderer::<frontend::App>::new().render();
}

View File

@ -0,0 +1,6 @@
use frontend::event_bus::EventBus;
use yew_agent::PublicWorker;
fn main() {
EventBus::register();
}

View File

@ -1,26 +1,75 @@
use crate::event_bus::EventBus;
use crate::services::websocket::WebsocketService;
use crate::AppState;
use crate::Route; use crate::Route;
use common::Achievement;
use std::rc::Rc;
use yew::functional::*; use yew::functional::*;
use yew::prelude::*; use yew::prelude::*;
use yew_agent::Bridge;
use yew_agent::Bridged;
use yew_router::prelude::*; use yew_router::prelude::*;
#[function_component(Root)] pub struct Root {
pub fn root() -> Html { wss: WebsocketService,
let app_state = use_context::<crate::AppState>().expect("no context found"); _producer: Box<dyn Bridge<EventBus>>,
achievements: Vec<Achievement>,
}
let achievements = app_state pub enum Msg {
.state HandleMsg(String),
.achievements }
.iter()
.map(|a| { impl Component for Root {
html! { type Message = Msg;
<p>{format!("{}", a.goal)}</p>
type Properties = ();
fn create(ctx: &Context<Self>) -> Self {
// let ctx_link = ctx
// .link()
// .context::<AppState>(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<Self>, 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>(); }
html! { fn view(&self, ctx: &Context<Self>) -> Html {
<div> let achievements = self
{achievements} .achievements
</div> .iter()
.map(|a| {
html! {
<p>{format!("{}", a.goal)}</p>
}
})
.collect::<Html>();
html! {
<div>
{achievements}
</div>
}
} }
} }

View File

@ -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<Self>,
subscribers: HashSet<HandlerId>,
}
#[derive(Serialize, Deserialize)]
pub enum EventBusInput {
EventBusMsg(String),
}
impl yew_agent::Worker for EventBus {
type Reach = Public<Self>;
type Input = EventBusInput;
type Output = String;
type Message = ();
fn create(link: WorkerLink<Self>) -> 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"
}
}

View File

@ -7,6 +7,7 @@ use yew_router::Routable;
use yew_router::Switch; use yew_router::Switch;
mod components; mod components;
pub mod event_bus;
mod services; mod services;
#[derive(Debug, Clone, Copy, PartialEq, Routable)] #[derive(Debug, Clone, Copy, PartialEq, Routable)]
@ -35,16 +36,7 @@ struct AppStateInner {
type AppState = Rc<AppStateInner>; type AppState = Rc<AppStateInner>;
#[function_component] #[function_component]
fn App() -> Html { pub fn App() -> Html {
// let counter = use_state(|| 0);
// let onclick = {
// let counter = counter.clone();
// move |_| {
// let value = *counter + 1;
// counter.set(value);
// }
// };
let ctx = use_state(|| Rc::new(AppStateInner::default())); let ctx = use_state(|| Rc::new(AppStateInner::default()));
html! { html! {
@ -57,8 +49,3 @@ fn App() -> Html {
</ContextProvider<AppState>> </ContextProvider<AppState>>
} }
} }
fn main() {
wasm_logger::init(wasm_logger::Config::default());
yew::Renderer::<App>::new().render();
}

View File

@ -1,9 +1,12 @@
use crate::event_bus::EventBus;
use crate::event_bus::EventBusInput;
use futures::channel::mpsc::Sender; use futures::channel::mpsc::Sender;
use futures::SinkExt; use futures::SinkExt;
use futures::StreamExt; use futures::StreamExt;
use reqwasm::websocket::futures::WebSocket; use reqwasm::websocket::futures::WebSocket;
use reqwasm::websocket::Message; use reqwasm::websocket::Message;
use wasm_bindgen_futures::spawn_local; use wasm_bindgen_futures::spawn_local;
use yew_agent::Dispatched;
pub struct WebsocketService { pub struct WebsocketService {
pub tx: Sender<String>, pub tx: Sender<String>,
@ -11,11 +14,12 @@ pub struct WebsocketService {
impl WebsocketService { impl WebsocketService {
pub fn new() -> Self { 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 (mut write, mut read) = ws.split();
let (in_tx, mut in_rx) = futures::channel::mpsc::channel::<String>(1000); let (in_tx, mut in_rx) = futures::channel::mpsc::channel::<String>(1000);
let mut event_bus = EventBus::dispatcher();
spawn_local(async move { spawn_local(async move {
while let Some(s) = in_rx.next().await { while let Some(s) = in_rx.next().await {
@ -29,15 +33,17 @@ impl WebsocketService {
match msg { match msg {
Ok(Message::Text(data)) => { Ok(Message::Text(data)) => {
log::debug!("from websocket: {}", data); log::debug!("from websocket: {}", data);
event_bus.send(EventBusInput::EventBusMsg(data));
} }
Ok(Message::Bytes(b)) => { Ok(Message::Bytes(b)) => {
let decoded = std::str::from_utf8(&b); let decoded = std::str::from_utf8(&b);
if let Ok(val) = decoded { if let Ok(val) = decoded {
log::debug!("from websocket: {}", val); log::debug!("from websocket: {}", val);
event_bus.send(EventBusInput::EventBusMsg(val.into()));
} }
} }
Err(e) => { Err(e) => {
log::error!("ws: {:?}", e) log::error!("ws: {:?}", e);
} }
} }
} }

View File

@ -18,7 +18,7 @@ fmt:
taplo fmt `fd --extension=toml` taplo fmt `fd --extension=toml`
run-backend: run-backend:
RUST_LOG=debug cargo run -p backend RUST_LOG=backend=debug cargo run -p backend
# POST /create # POST /create
create-gul-bus: create-gul-bus: