Skip to content
Closed
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
22 changes: 22 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ parking_lot = { version = "0.12.1" }
swash = { version = "0.1.17" }
muda = { git = "https://github.com/tauri-apps/muda", rev = "8e986af3cea96a729413abc75c3702dec3990bd2", default-features = false }
winit = { git = "https://github.com/rust-windowing/winit", rev = "ee245c569d65fdeacf705ee5eedb564508d10ebe" }
dioxus-devtools = "=0.7.0-alpha.0"

[dependencies]
slotmap = "1.0.7"
Expand Down Expand Up @@ -81,6 +82,9 @@ im = { workspace = true }
winit = { workspace = true }
futures = { version = "0.3.30", optional = true }
crossbeam = { version = "0.8", optional = true }
tungstenite = "0.26.2"
serde_json = "1.0.140"
dioxus-devtools = {workspace = true}

[target.'cfg(any(target_os = "windows", target_os = "macos"))'.dependencies]
muda = { workspace = true }
Expand Down Expand Up @@ -148,3 +152,21 @@ rfd-tokio = ["dep:rfd", "rfd/tokio"]
futures = ["dep:futures"]

crossbeam = [ "dep:crossbeam", "floem_renderer/crossbeam" ]

[patch.crates-io]
vello = { path = "../vello/vello" }
vello_svg = {path = "../vello_svg"}
dioxus-devtools = {path = "../dioxus/packages/devtools"}


[profile]

[profile.wasm-dev]
inherits = "dev"
opt-level = 1

[profile.server-dev]
inherits = "dev"

[profile.android-dev]
inherits = "dev"
6 changes: 4 additions & 2 deletions examples/widget-gallery/src/checkbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ pub fn checkbox_view() -> impl IntoView {
form_item("Checkbox:", Checkbox::new_rw(is_checked)),
form_item(
"Custom Checkbox 1:",
Checkbox::new_rw_custom(is_checked, CUSTOM_CHECK_SVG)
.style(|s| s.color(palette::css::GREEN)),
Checkbox::new_rw_custom(is_checked, CUSTOM_CHECK_SVG).style(|s| {
dbg!("again");
s.color(palette::css::GREEN)
}),
),
form_item(
"Disabled Checkbox:",
Expand Down
1 change: 1 addition & 0 deletions reactive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ license.workspace = true

[dependencies]
smallvec = "1.10.0"
dioxus-devtools.workspace = true
74 changes: 59 additions & 15 deletions reactive/src/effect.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
use std::{any::Any, cell::RefCell, collections::HashSet, marker::PhantomData, mem, rc::Rc};
use std::{
any::Any, cell::RefCell, collections::HashSet, marker::PhantomData, mem, rc::Rc,
sync::atomic::AtomicU64,
};

use crate::{
id::Id,
runtime::RUNTIME,
runtime::{register_effect, RUNTIME},
scope::{with_scope, Scope},
signal::NotThreadSafe,
};
Expand All @@ -12,6 +15,8 @@ pub(crate) trait EffectTrait {
fn run(&self) -> bool;
fn add_observer(&self, id: Id);
fn clear_observers(&self) -> HashSet<Id>;
fn hot_fn_ptr(&self) -> u64;
fn name(&self) -> &'static str;
}

struct Effect<T, F>
Expand Down Expand Up @@ -55,6 +60,7 @@ where
observers: RefCell::new(HashSet::default()),
ts: PhantomData,
});
register_effect(effect.clone());
id.set_scope();

run_initial_effect(effect);
Expand Down Expand Up @@ -109,6 +115,7 @@ where
value: RefCell::new(None),
observers: RefCell::new(HashSet::default()),
});
register_effect(effect.clone());
id.set_scope();

run_initial_updater_effect(effect)
Expand Down Expand Up @@ -195,10 +202,13 @@ where
let effect_scope = Scope(effect_id, PhantomData);
let (result, new_value) = with_scope(effect_scope, || {
effect_scope.track();
(effect.compute)(None)
// Wrap the compute function in HotFn for hot-reloading
let compute_fn = &effect.compute;
let mut hot_fn = dioxus_devtools::subsecond::HotFn::current(|| compute_fn(None));
hot_fn.call(())
});

// set new value
// Set new value
*effect.value.borrow_mut() = Some(new_value);

*runtime.current_effect.borrow_mut() = None;
Expand Down Expand Up @@ -227,18 +237,21 @@ where
T: 'static,
F: Fn(Option<T>) -> T,
{
fn name(&self) -> &'static str {
"Effect"
}

fn id(&self) -> Id {
self.id
}

fn run(&self) -> bool {
// Wrap the effect's function in HotFn for hot-reloading
let compute_fn = &self.f;
let mut hot_fn = dioxus_devtools::subsecond::HotFn::current(compute_fn);
let curr_value = self.value.borrow_mut().take();

// run the effect
let new_value = (self.f)(curr_value);

let new_value = hot_fn.call((curr_value,)); // Execute the hot-reloadable function
*self.value.borrow_mut() = Some(new_value);

true
}

Expand All @@ -249,6 +262,12 @@ where
fn clear_observers(&self) -> HashSet<Id> {
mem::take(&mut *self.observers.borrow_mut())
}

fn hot_fn_ptr(&self) -> u64 {
let compute_fn = &self.f;
let hot_fn = dioxus_devtools::subsecond::HotFn::current(compute_fn);
hot_fn.ptr_address()
}
}

impl<T, I, C, U> EffectTrait for UpdaterEffect<T, I, C, U>
Expand All @@ -257,17 +276,21 @@ where
C: Fn(Option<T>) -> (I, T),
U: Fn(I, T) -> T,
{
fn name(&self) -> &'static str {
"UpdaterEffect"
}

fn id(&self) -> Id {
self.id
}

fn run(&self) -> bool {
// Wrap the compute function in HotFn for hot-reloading
let compute_fn = &self.compute;
let mut hot_fn = dioxus_devtools::subsecond::HotFn::current(compute_fn);
let curr_value = self.value.borrow_mut().take();

// run the effect
let (i, t) = (self.compute)(curr_value);
let new_value = (self.on_change)(i, t);

let (i, t) = hot_fn.call((curr_value,)); // Execute the hot-reloadable function
let new_value = (self.on_change)(i, t); // Apply the update
*self.value.borrow_mut() = Some(new_value);
true
}
Expand All @@ -279,6 +302,14 @@ where
fn clear_observers(&self) -> HashSet<Id> {
mem::take(&mut *self.observers.borrow_mut())
}

fn hot_fn_ptr(&self) -> u64 {
let compute_fn = &self.compute;
let hot_fn = dioxus_devtools::subsecond::HotFn::current(compute_fn);
hot_fn.ptr_address()
// static COUNT: AtomicU64 = AtomicU64::new(0);
// COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
}
}

pub struct SignalTracker {
Expand Down Expand Up @@ -313,6 +344,7 @@ impl SignalTracker {
observers: RefCell::new(HashSet::default()),
on_change: self.on_change.clone(),
});
register_effect(tracking_effect.clone());

RUNTIME.with(|runtime| {
*runtime.current_effect.borrow_mut() = Some(tracking_effect.clone());
Expand All @@ -339,12 +371,19 @@ struct TrackingEffect {
}

impl EffectTrait for TrackingEffect {
fn name(&self) -> &'static str {
"TrackingEffect"
}

fn id(&self) -> Id {
self.id
}

fn run(&self) -> bool {
(self.on_change)();
// Wrap the on_change function in HotFn for hot-reloading
let on_change_fn = &self.on_change;
let mut hot_fn = dioxus_devtools::subsecond::HotFn::current(|| on_change_fn());
hot_fn.call(());
true
}

Expand All @@ -355,4 +394,9 @@ impl EffectTrait for TrackingEffect {
fn clear_observers(&self) -> HashSet<Id> {
mem::take(&mut *self.observers.borrow_mut())
}

fn hot_fn_ptr(&self) -> u64 {
let on_change_fn = &self.on_change;
dioxus_devtools::subsecond::HotFn::current(|| on_change_fn()).ptr_address()
}
}
1 change: 1 addition & 0 deletions reactive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub use effect::{
};
pub use memo::{create_memo, Memo};
pub use read::{ReadSignalValue, SignalGet, SignalRead, SignalTrack, SignalWith};
pub use runtime::hotpatch;
pub use scope::{as_child_of_current_scope, with_scope, Scope};
pub use signal::{create_rw_signal, create_signal, ReadSignal, RwSignal, WriteSignal};
pub use trigger::{create_trigger, Trigger};
Expand Down
45 changes: 45 additions & 0 deletions reactive/src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub(crate) struct Runtime {
pub(crate) contexts: RefCell<HashMap<TypeId, Box<dyn Any>>>,
pub(crate) batching: Cell<bool>,
pub(crate) pending_effects: RefCell<SmallVec<[Rc<dyn EffectTrait>; 10]>>,
pub(crate) hot_patched_effects: RefCell<HashMap<Id, u64>>,
}

impl Default for Runtime {
Expand All @@ -45,6 +46,7 @@ impl Runtime {
contexts: Default::default(),
batching: Cell::new(false),
pending_effects: RefCell::new(SmallVec::new()),
hot_patched_effects: RefCell::new(HashMap::new()),
}
}

Expand All @@ -66,3 +68,46 @@ impl Runtime {
}
}
}

pub(crate) fn register_effect<T: EffectTrait + 'static>(effect: Rc<T>) {
RUNTIME.with(|runtime| {
let Ok(mut hot_patched_effects) = runtime.hot_patched_effects.try_borrow_mut() else {
return;
};
let effect_id = effect.id();
let current_ptr = effect.hot_fn_ptr();
hot_patched_effects.insert(effect_id, current_ptr);
});
}

pub fn hotpatch() {
RUNTIME.with(|runtime| {
let Ok(mut hot_patched_effects) = runtime.hot_patched_effects.try_borrow_mut() else {
return;
};
println!("Starting hotpatch");
let mut effects = HashMap::new();
for signal in runtime.signals.borrow().values() {
for effect in signal.subscribers.borrow().values() {
if Some(effect.id()) != runtime.current_effect.borrow().as_ref().map(|v| v.id()) {
effects.insert(effect.id(), effect.clone());
}
}
}
for effect in effects.values() {
let effect_id = effect.id();
let new_ptr = effect.hot_fn_ptr();
if let Some(current_ptr) = hot_patched_effects.get(&effect_id) {
if new_ptr != *current_ptr {
dbg!("different pointer");
hot_patched_effects.insert(effect_id, new_ptr);
effect.run();
} else {
}
} else {
hot_patched_effects.insert(effect_id, new_ptr);
}
}
println!("Finished hotpatch");
});
}
38 changes: 37 additions & 1 deletion src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crossbeam::channel::{unbounded as channel, Receiver, Sender};
#[cfg(not(feature = "crossbeam"))]
use std::sync::mpsc::{channel, Receiver, Sender};

use floem_reactive::WriteSignal;
use floem_reactive::{hotpatch, SignalTrack, WriteSignal};
use parking_lot::Mutex;
use raw_window_handle::HasDisplayHandle;
use winit::{
Expand All @@ -19,6 +19,7 @@ use crate::{
action::{Timer, TimerToken},
app_handle::ApplicationHandle,
clipboard::Clipboard,
ext_event::create_nonblocking_signal_from_channel,
inspector::Capture,
profiler::Profile,
view::IntoView,
Expand Down Expand Up @@ -160,6 +161,7 @@ impl ApplicationHandler for Application {
impl Application {
pub fn new() -> Self {
let event_loop = EventLoop::new().expect("can't start the event loop");
initialize_hot_reload();

#[cfg(target_os = "macos")]
crate::app_delegate::set_app_delegate();
Expand Down Expand Up @@ -232,3 +234,37 @@ impl Application {
pub fn quit_app() {
Application::send_proxy_event(UserEvent::QuitApp);
}

static INIT: std::sync::Once = std::sync::Once::new();
pub fn initialize_hot_reload() {
use crate::reactive::create_updater;
use dioxus_devtools::{connect, subsecond::apply_patch, DevserverMsg};
INIT.call_once(|| {
let (tx, rx) = channel::<()>();
let update = create_nonblocking_signal_from_channel(rx);

connect(move |msg| {
if let DevserverMsg::HotReload(hot_reload_msg) = msg {
if let Some(jumptable) = hot_reload_msg.jump_table {
unsafe {
if let Err(e) = apply_patch(jumptable) {
eprintln!("Hot patch application failed: {:?}", e);
return;
}
}
println!("Hot patch applied successfully!");
tx.send(()).unwrap();
}
} else {
dbg!(msg);
}
});

create_updater(
move || update.track(),
move |_| {
hotpatch();
},
);
});
}
Loading
Loading