Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hot reloading support #67

Merged
merged 16 commits into from
Jul 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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" }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this change be causing the examples not to work correctly?

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