Skip to content

Commit

Permalink
Fix early drop of eventhandlers (#2126)
Browse files Browse the repository at this point in the history
* fix early drop of eventhandlers

* add a test for stale props that are memorized in place

* fix clippy
  • Loading branch information
ealmloff authored Mar 22, 2024
1 parent acbf7df commit 0662033
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 15 deletions.
1 change: 1 addition & 0 deletions packages/check/src/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ pub fn check_file(path: PathBuf, file_content: &str) -> IssueReport {
)
}

#[allow(unused)]
#[derive(Debug, Clone)]
enum Node {
If(IfInfo),
Expand Down
25 changes: 16 additions & 9 deletions packages/core-macro/src/props/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -648,38 +648,45 @@ mod struct_info {
)*
};

// If there are signals, we automatically try to memoize the signals
if !signal_fields.is_empty() {
Ok(quote! {
// First check if the fields are equal
// First check if the fields are equal. This will compare the signal fields by pointer
let exactly_equal = self == new;
if exactly_equal {
// If they are return early, they can be memoized without any changes
return true;
}

// If they are not, move over the signal fields and check if they are equal
// If they are not, move the signal fields into self and check if they are equal now that the signal fields are equal
#(
let mut #signal_fields = self.#signal_fields;
self.#signal_fields = new.#signal_fields;
)*

// Then check if the fields are equal again
// Then check if the fields are equal now that we know the signal fields are equal
// NOTE: we don't compare other fields individually because we want to let users opt-out of memoization for certain fields by implementing PartialEq themselves
let non_signal_fields_equal = self == new;

// If they are not equal, we still need to rerun the component
// If they are not equal, we need to move over all the fields to self
if !non_signal_fields_equal {
return false;
*self = new.clone();
}

// Move any signal and event fields into their old container.
// We update signals and event handlers in place so that they are always up to date even if they were moved into a future in a previous render
#move_signal_fields
#move_event_handlers

true
non_signal_fields_equal
})
} else {
Ok(quote! {
let equal = self == new;
if equal {
#move_event_handlers
// Move any signal and event fields into their old container.
#move_event_handlers
// If they are not equal, we need to move over all the fields to self
if !equal {
*self = new.clone();
}
equal
})
Expand Down
75 changes: 75 additions & 0 deletions packages/core-macro/tests/values_memoize_in_place.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
use dioxus::prelude::*;

thread_local! {
static DROP_COUNT: std::cell::RefCell<usize> = const { std::cell::RefCell::new(0) };
}

#[test]
fn values_memoize_in_place() {
let mut dom = VirtualDom::new(app);

dom.rebuild_in_place();
dom.mark_dirty(ScopeId::ROOT);
for _ in 0..20 {
dom.render_immediate(&mut dioxus_core::NoOpMutations);
}
dom.render_immediate(&mut dioxus_core::NoOpMutations);
// As we rerun the app, the drop count should be 15 one for each render of the app component
let drop_count = DROP_COUNT.with(|c| *c.borrow());
assert_eq!(drop_count, 15);
}

struct CountsDrop;

impl Drop for CountsDrop {
fn drop(&mut self) {
DROP_COUNT.with(|c| *c.borrow_mut() += 1);
}
}

fn app() -> Element {
let mut count = use_signal(|| 0);
let x = CountsDrop;

if generation() < 15 {
count += 1;
}

rsx! {
TakesEventHandler {
click: move |num| {
// Force the closure to own the drop counter
let _ = &x;
println!("num is {num}");
},
children: count() / 2
}
TakesSignal { sig: count(), children: count() / 2 }
}
}

#[component]
fn TakesEventHandler(click: EventHandler<usize>, children: usize) -> Element {
let first_render_click = use_hook(move || click);
if generation() > 0 {
// Make sure the event handler is memoized in place and never gets dropped
first_render_click(children);
}

rsx! {
button { "{children}" }
}
}

#[component]
fn TakesSignal(sig: ReadOnlySignal<usize>, children: usize) -> Element {
let first_render_sig = use_hook(move || sig);
if generation() > 0 {
// Make sure the signal is memoized in place and never gets dropped
println!("{first_render_sig}");
}

rsx! {
button { "{children}" }
}
}
2 changes: 1 addition & 1 deletion packages/core/src/any_props.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ pub(crate) type BoxedAnyProps = Box<dyn AnyProps>;
pub(crate) trait AnyProps: 'static {
/// Render the component with the internal props.
fn render(&self) -> RenderReturn;
/// Check if the props are the same as the type erased props of another component.
/// Make the old props equal to the new type erased props. Return if the props were equal and should be memoized.
fn memoize(&mut self, other: &dyn Any) -> bool;
/// Get the props as a type erased `dyn Any`.
fn props(&self) -> &dyn Any;
Expand Down
3 changes: 0 additions & 3 deletions packages/core/src/diff/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,6 @@ impl VNode {
return;
}

// First, move over the props from the old to the new, dropping old props in the process
dom.scopes[scope_id.0].props = new.props.duplicate();

// Now run the component and diff it
let new = dom.run_scope(scope_id);
dom.diff_scope(to, scope_id, new);
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/properties.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ pub trait Properties: Clone + Sized + 'static {
/// Create a builder for this component.
fn builder() -> Self::Builder;

/// Compare two props to see if they are memoizable.
/// Make the old props equal to the new props. Return if the props were equal and should be memoized.
fn memoize(&mut self, other: &Self) -> bool;

/// Create a component from the props.
Expand Down
2 changes: 1 addition & 1 deletion packages/hooks/tests/memo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ async fn memo_updates() {
use dioxus::prelude::*;

thread_local! {
static VEC_SIGNAL: RefCell<Option<Signal<Vec<usize>, SyncStorage>>> = RefCell::new(None);
static VEC_SIGNAL: RefCell<Option<Signal<Vec<usize>, SyncStorage>>> = const { RefCell::new(None) };
}

fn app() -> Element {
Expand Down

0 comments on commit 0662033

Please sign in to comment.