Skip to content

Commit

Permalink
Hot reloading support (#67)
Browse files Browse the repository at this point in the history
  • Loading branch information
matthunz authored Jul 2, 2024
1 parent 8138b65 commit 3e58462
Show file tree
Hide file tree
Showing 8 changed files with 101 additions and 23 deletions.
7 changes: 2 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,16 @@ edition = "2021"
description = "Top level crate for Blitz"
license = "MIT OR Apache-2.0"
keywords = ["dom", "ui", "gui", "react", "wasm"]
rust-version = "1.60.0"
rust-version = "1.70.0"
publish = false

[profile.dev.package."*"]
opt-level = 2

# Need to force specific versions of these dependencies
[dependencies]
[dev-dependencies]
# webrender = "0.61.0"
euclid = { version = "0.22", features = ["serde"] }
# mozbuild = "0.1.0"

[dev-dependencies]
blitz = { path = "./packages/blitz" }
blitz-dom = { path = "./packages/dom" }
comrak = { version = "0.21.0", default-features = false }
Expand Down
5 changes: 4 additions & 1 deletion packages/dioxus-blitz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@ version = "0.0.0"
edition = "2021"

[features]
default = ["hot-reload", "menu"]
hot-reload = ["dep:dioxus-cli-config", "dep:dioxus-hot-reload"]
menu = ["dep:muda"]
default = ["menu"]

[dependencies]
winit = { version = "0.30.2", features = ["rwh_06"] }
muda = { version = "0.11.5", features = ["serde"], optional = true }
tokio = { workspace = true, features = ["full"] }
dioxus = { workspace = true }
dioxus-cli-config = { git = "https://github.com/dioxuslabs/dioxus", rev = "a3aa6ae771a2d0a4d8cb6055c41efc0193b817ef", optional = true }
dioxus-hot-reload = { git = "https://github.com/dioxuslabs/dioxus", rev = "a3aa6ae771a2d0a4d8cb6055c41efc0193b817ef", optional = true }
futures-util = "0.3.30"
vello = { workspace = true }
wgpu = { workspace = true }
Expand Down
3 changes: 2 additions & 1 deletion packages/dioxus-blitz/src/documents/dioxus_document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ fn qual_name(local_name: &str, namespace: Option<&str>) -> QualName {
}

pub struct DioxusDocument {
vdom: VirtualDom,
pub(crate) vdom: VirtualDom,
vdom_state: DioxusState,
inner: Document,
}
Expand Down Expand Up @@ -230,6 +230,7 @@ impl MutationWriter<'_> {
}
// If element_id is already mapping to a node, remove that node from the document
else if let Some(mapped_node_id) = self.state.node_id_mapping[element_id] {
// todo: we should mark these as needing garbage collection?
self.doc.remove_node(mapped_node_id);
}

Expand Down
69 changes: 64 additions & 5 deletions packages/dioxus-blitz/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ mod documents;
mod waker;
mod window;

use crate::waker::{EventData, UserWindowEvent};
use crate::waker::{EventData, UserEvent};
use crate::{documents::HtmlDocument, window::View};

use blitz::RenderState;
Expand All @@ -20,6 +20,10 @@ use winit::{
event_loop::ControlFlow,
};

pub mod exports {
pub use dioxus;
}

#[derive(Default)]
pub struct Config {
pub stylesheets: Vec<String>,
Expand Down Expand Up @@ -94,7 +98,7 @@ fn launch_with_window<Doc: DocumentLike + 'static>(window: View<'static, Doc>) {
let _guard = rt.enter();

// Build an event loop for the application
let mut builder = EventLoop::<UserWindowEvent>::with_user_event();
let mut builder = EventLoop::<UserEvent>::with_user_event();

#[cfg(target_os = "android")]
{
Expand All @@ -117,6 +121,24 @@ fn launch_with_window<Doc: DocumentLike + 'static>(window: View<'static, Doc>) {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
let mut initial = true;

// Setup hot-reloading if enabled.
#[cfg(all(
feature = "hot-reload",
debug_assertions,
not(target_os = "android"),
not(target_os = "ios")
))]
{
if let Ok(cfg) = dioxus_cli_config::CURRENT_CONFIG.as_ref() {
dioxus_hot_reload::connect_at(cfg.target_dir.join("dioxusin"), {
let proxy = proxy.clone();
move |template| {
let _ = proxy.send_event(UserEvent::HotReloadEvent(template));
}
})
}
}

// the move to winit wants us to use a struct with a run method instead of the callback approach
// we want to just keep the callback approach for now
#[allow(deprecated)]
Expand Down Expand Up @@ -165,21 +187,58 @@ fn launch_with_window<Doc: DocumentLike + 'static>(window: View<'static, Doc>) {
};
}

Event::UserEvent(UserWindowEvent(EventData::Poll, id)) => {
Event::UserEvent(UserEvent::Window {
data: EventData::Poll,
window_id: id,
}) => {
if let Some(view) = windows.get_mut(&id) {
if view.poll() {
view.request_redraw();
}
};
}

#[cfg(all(
feature = "hot-reload",
debug_assertions,
not(target_os = "android"),
not(target_os = "ios")
))]
Event::UserEvent(UserEvent::HotReloadEvent(msg)) => match msg {
dioxus_hot_reload::HotReloadMsg::UpdateTemplate(template) => {
for window in windows.values_mut() {
if let Some(dx_doc) = window
.renderer
.dom
.as_any_mut()
.downcast_mut::<DioxusDocument>()
{
dx_doc.vdom.replace_template(template);

if window.poll() {
window.request_redraw();
}
}
}
}
dioxus_hot_reload::HotReloadMsg::Shutdown => event_loop.exit(),
dioxus_hot_reload::HotReloadMsg::UpdateAsset(asset) => {
// TODO dioxus-desktop seems to handle this by forcing a reload of all stylesheets.
dbg!("Update asset {:?}", asset);
}
},

// Event::UserEvent(_redraw) => {
// for (_, view) in windows.iter() {
// view.request_redraw();
// }
// }
Event::NewEvents(_) => {
for id in windows.keys() {
_ = proxy.send_event(UserWindowEvent(EventData::Poll, *id));
for window_id in windows.keys().copied() {
_ = proxy.send_event(UserEvent::Window {
data: EventData::Poll,
window_id,
});
}
}

Expand Down
26 changes: 20 additions & 6 deletions packages/dioxus-blitz/src/waker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,20 @@ use std::sync::Arc;
use winit::{event_loop::EventLoopProxy, window::WindowId};

#[derive(Debug, Clone)]
pub struct UserWindowEvent(pub EventData, pub WindowId);
pub enum UserEvent {
Window {
window_id: WindowId,
data: EventData,
},
/// Handle a hotreload event, basically telling us to update our templates
#[cfg(all(
feature = "hot-reload",
debug_assertions,
not(target_os = "android"),
not(target_os = "ios")
))]
HotReloadEvent(dioxus_hot_reload::HotReloadMsg),
}

#[derive(Debug, Clone)]
pub enum EventData {
Expand All @@ -17,9 +30,9 @@ pub enum EventData {
/// This lets the VirtualDom "come up for air" and process events while the main thread is blocked by the WebView.
///
/// All other IO lives in the Tokio runtime,
pub fn tao_waker(proxy: &EventLoopProxy<UserWindowEvent>, id: WindowId) -> std::task::Waker {
pub fn tao_waker(proxy: &EventLoopProxy<UserEvent>, id: WindowId) -> std::task::Waker {
struct DomHandle {
proxy: EventLoopProxy<UserWindowEvent>,
proxy: EventLoopProxy<UserEvent>,
id: WindowId,
}

Expand All @@ -30,9 +43,10 @@ pub fn tao_waker(proxy: &EventLoopProxy<UserWindowEvent>, id: WindowId) -> std::

impl ArcWake for DomHandle {
fn wake_by_ref(arc_self: &Arc<Self>) {
_ = arc_self
.proxy
.send_event(UserWindowEvent(EventData::Poll, arc_self.id));
_ = arc_self.proxy.send_event(UserEvent::Window {
data: EventData::Poll,
window_id: arc_self.id,
})
}
}

Expand Down
4 changes: 2 additions & 2 deletions packages/dioxus-blitz/src/window.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::waker::UserWindowEvent;
use crate::waker::UserEvent;
use blitz::{RenderState, Renderer, Viewport};
use blitz_dom::DocumentLike;
use winit::keyboard::PhysicalKey;
Expand Down Expand Up @@ -277,7 +277,7 @@ impl<'a, Doc: DocumentLike> View<'a, Doc> {
pub fn resume(
&mut self,
event_loop: &ActiveEventLoop,
proxy: &EventLoopProxy<UserWindowEvent>,
proxy: &EventLoopProxy<UserEvent>,
rt: &tokio::runtime::Runtime,
) {
let window_builder = || {
Expand Down
2 changes: 0 additions & 2 deletions packages/dom/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ name = "blitz-dom"
version = "0.0.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
style = { workspace = true, features = ["servo"] }
selectors = { workspace = true }
Expand Down
8 changes: 7 additions & 1 deletion packages/dom/src/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::{Node, NodeData, TextNodeData};
// use quadtree_rs::Quadtree;
use selectors::{matching::QuirksMode, Element};
use slab::Slab;
use std::any::Any;
use std::collections::HashMap;
use style::invalidation::element::restyle_hints::RestyleHint;
use style::selector_parser::ServoElementSnapshot;
Expand Down Expand Up @@ -37,7 +38,7 @@ impl FontMetricsProvider for DummyFontMetricsProvider {
}
}

pub trait DocumentLike: AsRef<Document> + AsMut<Document> + Into<Document> {
pub trait DocumentLike: AsRef<Document> + AsMut<Document> + Into<Document> + 'static {
fn poll(&mut self, _cx: std::task::Context) -> bool {
// Default implementation does nothing
false
Expand All @@ -47,6 +48,10 @@ pub trait DocumentLike: AsRef<Document> + AsMut<Document> + Into<Document> {
// Default implementation does nothing
false
}

fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}

impl DocumentLike for Document {}
Expand Down Expand Up @@ -218,6 +223,7 @@ impl Document {

let node = &self.nodes[node_id];
let node_child_idx = node.child_idx;

let parent_id = node.parent.unwrap();
let parent = &mut self.nodes[parent_id];

Expand Down

0 comments on commit 3e58462

Please sign in to comment.