From 1faafd6692c6e098b661352a4e6349604e9f9734 Mon Sep 17 00:00:00 2001 From: Catchawink <33239314+Catchawink@users.noreply.github.com> Date: Mon, 16 Jun 2025 16:33:34 -0400 Subject: [PATCH 1/5] Added WASM support. --- Cargo.lock | 6 ++++ Cargo.toml | 14 ++++++++ macros/src/lib.rs | 27 ++++++++------- src/hot_patched_app.rs | 2 +- src/lib.rs | 75 ++++++++++++++++++++++++++++++++++++------ 5 files changed, 99 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 80200c1..c1e118d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1069,8 +1069,11 @@ dependencies = [ "bevy_simple_subsecond_system_macros", "bevy_utils", "crossbeam-channel", + "dioxus-cli-config", "dioxus-devtools", "send_wrapper", + "serde_json", + "web-sys", ] [[package]] @@ -1884,6 +1887,9 @@ name = "dioxus-cli-config" version = "0.7.0-alpha.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fd0ba83fe297b4509423853acb11bf0ef0dffa8c603b0857d8fd07d0da5107c" +dependencies = [ + "wasm-bindgen", +] [[package]] name = "dioxus-core" diff --git a/Cargo.toml b/Cargo.toml index d57491b..fc47a81 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,20 @@ bevy_simple_subsecond_system_macros = { path = "macros", version = "0.1.9" } crossbeam-channel = "0.5.15" send_wrapper = "0.6.0" +[target.'cfg(target_arch = "wasm32")'.dependencies] +dioxus-cli-config = { version = "0.7.0-alpha.1", features = ["web"] } +serde_json = { version = "1.0" } + +[target.'cfg(target_arch = "wasm32")'.dependencies.web-sys] +version = "0.3.77" +features = [ + "Window", + "Location", + "CloseEvent", + "MessageEvent", + "WebSocket" +] + [dev-dependencies] bevy = { version = "0.16.0", features = ["bevy_ui_debug", "track_location"] } diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 07e5d08..9fda64c 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -132,20 +132,19 @@ pub fn hot(attr: TokenStream, item: TokenStream) -> TokenStream { if !hot_patch_signature && !rerun_on_hot_patch { let result = quote! { - #[cfg(any(target_family = "wasm", not(debug_assertions)))] - #vis fn #original_fn_name #impl_generics(#inputs) #where_clause #original_output { - #block - } + //#[cfg(any(target_family = "wasm", not(debug_assertions)))] + //#vis fn #original_fn_name #impl_generics(#inputs) #where_clause #original_output { + // #block + //} - - #[cfg(all(not(target_family = "wasm"), debug_assertions))] + #[cfg(debug_assertions)] #[allow(unused_mut)] #vis fn #original_fn_name #impl_generics(#inputs) #where_clause #original_output { #hot_fn.call((#(#param_idents,)*)) } - #[cfg(all(not(target_family = "wasm"), debug_assertions))] + #[cfg(debug_assertions)] #vis fn #hotpatched_fn #impl_generics(#inputs) #where_clause #original_output { #block } @@ -215,12 +214,12 @@ pub fn hot(attr: TokenStream, item: TokenStream) -> TokenStream { }; let result = quote! { - #[cfg(any(target_family = "wasm", not(debug_assertions)))] - #vis fn #original_fn_name #impl_generics(#inputs) #where_clause #original_output { - #block - } + //#[cfg(any(target_family = "wasm", not(debug_assertions)))] + //#vis fn #original_fn_name #impl_generics(#inputs) #where_clause #original_output { + // #block + //} // Outer entry point: stable ABI, hot-reload safe - #[cfg(all(not(target_family = "wasm"), debug_assertions))] + #[cfg(debug_assertions)] #vis fn #original_fn_name #impl_generics(world: &mut ::bevy_simple_subsecond_system::__macros_internal::World) #where_clause #original_output { use std::any::Any as _; let type_id = #hotpatched_fn #maybe_generics.type_id(); @@ -258,11 +257,11 @@ pub fn hot(attr: TokenStream, item: TokenStream) -> TokenStream { } // Hotpatched version with stable signature - #[cfg(all(not(target_family = "wasm"), debug_assertions))] + #[cfg(debug_assertions)] #hotpatched_fn_definition // Original function body moved into a standalone fn - #[cfg(all(not(target_family = "wasm"), debug_assertions))] + #[cfg(debug_assertions)] #vis fn #original_wrapper_fn #impl_generics(#inputs) #where_clause #original_output { #block } diff --git a/src/hot_patched_app.rs b/src/hot_patched_app.rs index 5e2af21..ebb9fdb 100644 --- a/src/hot_patched_app.rs +++ b/src/hot_patched_app.rs @@ -7,7 +7,7 @@ use bevy_app::{ }; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::prelude::*; -#[cfg(all(not(target_family = "wasm"), debug_assertions))] +#[cfg(debug_assertions)] use bevy_ecs::system::{Commands, Res}; use bevy_ecs_macros::ScheduleLabel; use bevy_log::{debug, error}; diff --git a/src/lib.rs b/src/lib.rs index 8c57265..b42ef21 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,15 +4,18 @@ pub mod migration; -#[cfg(all(not(target_family = "wasm"), debug_assertions))] +#[cfg(debug_assertions)] use __macros_internal::__HotPatchedSystems as HotPatchedSystems; use bevy_app::{App, Last, Plugin, PostStartup, PreUpdate}; use bevy_ecs::prelude::*; pub use bevy_simple_subsecond_system_macros::*; pub use dioxus_devtools; -#[cfg(all(not(target_family = "wasm"), debug_assertions))] +#[cfg(debug_assertions)] use dioxus_devtools::{subsecond::apply_patch, *}; +#[cfg(target_arch = "wasm32")] +use web_sys::{window, CloseEvent, MessageEvent, WebSocket, wasm_bindgen::{closure::Closure, JsCast, JsValue}, js_sys::JsString}; + pub mod hot_patched_app; /// Everything you need to use hotpatching @@ -51,20 +54,15 @@ impl Plugin for SimpleSubsecondPlugin { ) .chain(), ); - #[cfg(target_family = "wasm")] - { - bevy_log::warn!( - "Hotpatching is not supported on Wasm yet. Disabling SimpleSubsecondPlugin." - ); - return; - } #[cfg(not(debug_assertions))] { return; } - #[cfg(all(not(target_family = "wasm"), debug_assertions))] + #[cfg(debug_assertions)] { let (sender, receiver) = crossbeam_channel::bounded::(1); + + #[cfg(not(target_arch = "wasm32"))] connect(move |msg| { if let DevserverMsg::HotReload(hot_reload_msg) = msg { if let Some(jumptable) = hot_reload_msg.jump_table { @@ -76,6 +74,63 @@ impl Plugin for SimpleSubsecondPlugin { } }); + #[cfg(target_arch = "wasm32")] + { + let location = web_sys::window().unwrap().location(); + let url = format!( + "{protocol}//{host}/_dioxus?build_id={build_id}", + protocol = match location.protocol().unwrap() { + prot if prot == "https:" => "wss:", + _ => "ws:", + }, + host = location.host().unwrap(), + build_id = dioxus_cli_config::build_id(), + ); + + let ws = WebSocket::new(&url).unwrap(); + + // Set the onmessage handler to bounce messages off to the main dioxus loop + //let tx_ = tx.clone(); + ws.set_onmessage(Some( + Closure::::new(move |e: MessageEvent| { + let Ok(text) = e.data().dyn_into::() else { + return; + }; + + // The devserver messages have some &'static strs in them, so we need to leak the source string + let string: String = text.into(); + let string = Box::leak(string.into_boxed_str()); + + match serde_json::from_str::(string) { + Ok(DevserverMsg::HotReload(hr)) => { + if let Some(jumptable) = hr.jump_table { + // SAFETY: This is not unsafe, but anything using the updated jump table is. + // The table must be built carefully + unsafe { apply_patch(jumptable).unwrap() }; + sender.send(HotPatched).unwrap(); + } + //_ = tx_.unbounded_send(hr), + }, + Ok(_) => { + // Other devserver messages not used + }, + Err(e) => web_sys::console::error_1( + &format!("Error parsing devserver message: {}", e).into(), + ), + e => { + web_sys::console::error_1( + &format!("Error parsing devserver message: {:?}", e).into(), + ); + } + } + }) + .into_js_value() + .as_ref() + .unchecked_ref() + )); + } + + app.init_resource::(); app.add_event::().add_systems( From 0372bb6cf7c3a5bf4b56c41caa441888fa694f83 Mon Sep 17 00:00:00 2001 From: Catchawink <33239314+Catchawink@users.noreply.github.com> Date: Mon, 16 Jun 2025 16:40:58 -0400 Subject: [PATCH 2/5] Cleaned up comments. --- src/lib.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index b42ef21..a49536c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -90,7 +90,6 @@ impl Plugin for SimpleSubsecondPlugin { let ws = WebSocket::new(&url).unwrap(); // Set the onmessage handler to bounce messages off to the main dioxus loop - //let tx_ = tx.clone(); ws.set_onmessage(Some( Closure::::new(move |e: MessageEvent| { let Ok(text) = e.data().dyn_into::() else { @@ -109,7 +108,6 @@ impl Plugin for SimpleSubsecondPlugin { unsafe { apply_patch(jumptable).unwrap() }; sender.send(HotPatched).unwrap(); } - //_ = tx_.unbounded_send(hr), }, Ok(_) => { // Other devserver messages not used From fe14dc14d7945a6deb045dfc7a3f69caa0d86edd Mon Sep 17 00:00:00 2001 From: Catchawink <33239314+Catchawink@users.noreply.github.com> Date: Mon, 16 Jun 2025 16:43:51 -0400 Subject: [PATCH 3/5] Cleaned up comments. --- src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index a49536c..d6fa683 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -89,7 +89,6 @@ impl Plugin for SimpleSubsecondPlugin { let ws = WebSocket::new(&url).unwrap(); - // Set the onmessage handler to bounce messages off to the main dioxus loop ws.set_onmessage(Some( Closure::::new(move |e: MessageEvent| { let Ok(text) = e.data().dyn_into::() else { From 65008f2a0dfc409fb41e31c1fceb63c2b43e37a5 Mon Sep 17 00:00:00 2001 From: Catchawink <33239314+Catchawink@users.noreply.github.com> Date: Mon, 16 Jun 2025 16:45:05 -0400 Subject: [PATCH 4/5] Update lib.rs --- macros/src/lib.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 9fda64c..dc51f53 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -132,11 +132,6 @@ pub fn hot(attr: TokenStream, item: TokenStream) -> TokenStream { if !hot_patch_signature && !rerun_on_hot_patch { let result = quote! { - //#[cfg(any(target_family = "wasm", not(debug_assertions)))] - //#vis fn #original_fn_name #impl_generics(#inputs) #where_clause #original_output { - // #block - //} - #[cfg(debug_assertions)] #[allow(unused_mut)] #vis fn #original_fn_name #impl_generics(#inputs) #where_clause #original_output { @@ -214,10 +209,6 @@ pub fn hot(attr: TokenStream, item: TokenStream) -> TokenStream { }; let result = quote! { - //#[cfg(any(target_family = "wasm", not(debug_assertions)))] - //#vis fn #original_fn_name #impl_generics(#inputs) #where_clause #original_output { - // #block - //} // Outer entry point: stable ABI, hot-reload safe #[cfg(debug_assertions)] #vis fn #original_fn_name #impl_generics(world: &mut ::bevy_simple_subsecond_system::__macros_internal::World) #where_clause #original_output { From f7cd7aee33ba1cfc80ba27e8a94b0c08f6593e80 Mon Sep 17 00:00:00 2001 From: Jan Hohenheim Date: Tue, 15 Jul 2025 22:09:55 +0200 Subject: [PATCH 5/5] Run fmt --- src/lib.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d6fa683..4aab556 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,7 +14,12 @@ pub use dioxus_devtools; use dioxus_devtools::{subsecond::apply_patch, *}; #[cfg(target_arch = "wasm32")] -use web_sys::{window, CloseEvent, MessageEvent, WebSocket, wasm_bindgen::{closure::Closure, JsCast, JsValue}, js_sys::JsString}; +use web_sys::{ + CloseEvent, MessageEvent, WebSocket, + js_sys::JsString, + wasm_bindgen::{JsCast, JsValue, closure::Closure}, + window, +}; pub mod hot_patched_app; @@ -107,10 +112,10 @@ impl Plugin for SimpleSubsecondPlugin { unsafe { apply_patch(jumptable).unwrap() }; sender.send(HotPatched).unwrap(); } - }, + } Ok(_) => { // Other devserver messages not used - }, + } Err(e) => web_sys::console::error_1( &format!("Error parsing devserver message: {}", e).into(), ), @@ -123,10 +128,9 @@ impl Plugin for SimpleSubsecondPlugin { }) .into_js_value() .as_ref() - .unchecked_ref() - )); + .unchecked_ref(), + )); } - app.init_resource::();