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 9 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
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ 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."*"]
Expand Down
6 changes: 5 additions & 1 deletion packages/dioxus-blitz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@ name = "dioxus-blitz"
version = "0.0.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
hot-reload = ["dep:dioxus-cli-config", "dep:dioxus-hot-reload"]
default = []

[dependencies]
winit = { version = "0.30.2", features = ["rwh_06"] }
muda = { version = "0.11.5", features = ["serde"] }
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
2 changes: 1 addition & 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
72 changes: 65 additions & 7 deletions packages/dioxus-blitz/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,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 Down Expand Up @@ -93,9 +93,7 @@ fn launch_with_window<Doc: DocumentLike + 'static>(window: View<'static, Doc>) {
let _guard = rt.enter();

// Build an event loop for the application
let event_loop = EventLoop::<UserWindowEvent>::with_user_event()
.build()
.unwrap();
let event_loop = EventLoop::<UserEvent>::with_user_event().build().unwrap();
let proxy = event_loop.create_proxy();

// Multiwindow ftw
Expand All @@ -108,6 +106,29 @@ 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")
))]
{
let Ok(cfg) = dioxus_cli_config::CURRENT_CONFIG.as_ref() else {
return;
};

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)]
// 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 @@ -156,21 +177,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 @@ -271,7 +271,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
11 changes: 9 additions & 2 deletions 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 @@ -217,7 +222,9 @@ impl Document {

let node = &self.nodes[node_id];
let node_child_idx = node.child_idx;
let parent_id = node.parent.unwrap();

// Get this node's parent, or the root node if it has none.
let parent_id = node.parent.unwrap_or_default();
Copy link
Collaborator

Choose a reason for hiding this comment

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

This doesn't seem right. Only the root node should have no parent. And we wouldn't want to insert a child of the root node instead of a sibling. What is the use case here? Replacing the root node?

Copy link
Collaborator

Choose a reason for hiding this comment

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

@matthunz This is the only thing blocking this PR as far as I'm concerned. I'd like to understand why this is required. It seems wrong that WriteMutations::insert_nodes_after would be called on a node without a parent. @ealmloff Any thoughts?

Copy link
Member

Choose a reason for hiding this comment

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

I don't think this should be necessary. The root node should be ElementId(0) and every node except that node should have a parent in dioxus mutations. Dioxus will never insert a node after or before ElementId(0)

let parent = &mut self.nodes[parent_id];

let mut children = std::mem::take(&mut parent.children);
Expand Down