Make EventHandler copy (#2112)

* implement Copy for EventHandler
* implement from closure for event handler and remove special on prefix
* fix props implementation of EventHandler
This commit is contained in:
Evan Almloff authored and GitHub committed 2024-03-19 19:48:53 -07:00
1 parent 4bb807a3ce
commit 58f7efafea
19 files changed
+755 -364

No files matched your search

+78 -27
View File
@@ -1,8 +1,7 @@
use crate::{global_context::current_scope_id, Runtime, ScopeId};
use std::{
cell::{Cell, RefCell},
rc::Rc,
};
use generational_box::{GenerationalBox, UnsyncStorage};
use crate::{generational_box::current_owner, global_context::current_scope_id, Runtime, ScopeId};
use std::{cell::Cell, rc::Rc};
/// A wrapper around some generic data that handles the event's state
///
@@ -165,41 +164,45 @@ impl<T: std::fmt::Debug> std::fmt::Debug for Event<T> {
/// ```
pub struct EventHandler<T = ()> {
pub(crate) origin: ScopeId,
pub(super) callback: Rc<RefCell<Option<ExternalListenerCallback<T>>>>,
pub(super) callback: GenerationalBox<Option<ExternalListenerCallback<T>>>,
}
impl<T: 'static> Default for EventHandler<T> {
fn default() -> Self {
EventHandler::new(|_| {})
}
}
impl<F: FnMut(T) + 'static, T: 'static> From<F> for EventHandler<T> {
fn from(f: F) -> Self {
EventHandler::new(f)
}
}
impl<T> Copy for EventHandler<T> {}
impl<T> Clone for EventHandler<T> {
fn clone(&self) -> Self {
Self {
origin: self.origin,
callback: self.callback.clone(),
}
*self
}
}
impl<T> PartialEq for EventHandler<T> {
fn eq(&self, other: &Self) -> bool {
Rc::ptr_eq(&self.callback, &other.callback)
}
}
impl<T> Default for EventHandler<T> {
fn default() -> Self {
Self {
origin: ScopeId::ROOT,
callback: Default::default(),
}
impl<T: 'static> PartialEq for EventHandler<T> {
fn eq(&self, _: &Self) -> bool {
true
}
}
type ExternalListenerCallback<T> = Box<dyn FnMut(T)>;
impl<T> EventHandler<T> {
impl<T: 'static> EventHandler<T> {
/// Create a new [`EventHandler`] from an [`FnMut`]
#[track_caller]
pub fn new(mut f: impl FnMut(T) + 'static) -> EventHandler<T> {
let callback = Rc::new(RefCell::new(Some(Box::new(move |event: T| {
let owner = current_owner::<UnsyncStorage>();
let callback = owner.insert(Some(Box::new(move |event: T| {
f(event);
}) as Box<dyn FnMut(T)>)));
}) as Box<dyn FnMut(T)>));
EventHandler {
callback,
origin: current_scope_id().expect("to be in a dioxus runtime"),
@@ -210,7 +213,7 @@ impl<T> EventHandler<T> {
///
/// This borrows the event using a RefCell. Recursively calling a listener will cause a panic.
pub fn call(&self, event: T) {
if let Some(callback) = self.callback.borrow_mut().as_mut() {
if let Some(callback) = self.callback.write().as_mut() {
Runtime::with(|rt| rt.scope_stack.borrow_mut().push(self.origin));
callback(event);
Runtime::with(|rt| rt.scope_stack.borrow_mut().pop());
@@ -221,6 +224,54 @@ impl<T> EventHandler<T> {
///
/// This will force any future calls to "call" to not doing anything
pub fn release(&self) {
self.callback.replace(None);
self.callback.set(None);
}
#[doc(hidden)]
/// This should only be used by the `rsx!` macro.
pub fn __set(&mut self, value: impl FnMut(T) + 'static) {
self.callback.set(Some(Box::new(value)));
}
#[doc(hidden)]
/// This should only be used by the `rsx!` macro.
pub fn __take(&self) -> ExternalListenerCallback<T> {
self.callback
.manually_drop()
.expect("Signal has already been dropped")
.expect("EventHandler was manually dropped")
}
}
impl<T: 'static> std::ops::Deref for EventHandler<T> {
type Target = dyn Fn(T) + 'static;
fn deref(&self) -> &Self::Target {
// https://github.com/dtolnay/case-studies/tree/master/callable-types
// First we create a closure that captures something with the Same in memory layout as Self (MaybeUninit<Self>).
let uninit_callable = std::mem::MaybeUninit::<Self>::uninit();
// Then move that value into the closure. We assume that the closure now has a in memory layout of Self.
let uninit_closure = move |t| Self::call(unsafe { &*uninit_callable.as_ptr() }, t);
// Check that the size of the closure is the same as the size of Self in case the compiler changed the layout of the closure.
let size_of_closure = std::mem::size_of_val(&uninit_closure);
assert_eq!(size_of_closure, std::mem::size_of::<Self>());
// Then cast the lifetime of the closure to the lifetime of &self.
fn cast_lifetime<'a, T>(_a: &T, b: &'a T) -> &'a T {
b
}
let reference_to_closure = cast_lifetime(
{
// The real closure that we will never use.
&uninit_closure
},
// We transmute self into a reference to the closure. This is safe because we know that the closure has the same memory layout as Self so &Closure == &Self.
unsafe { std::mem::transmute(self) },
);
// Cast the closure to a trait object.
reference_to_closure as &_
}
}
+102
View File
@@ -0,0 +1,102 @@
//! Integration with the generational-box crate for copy state management.
//!
//! Each scope in dioxus has a single [Owner]
use std::{
any::{Any, TypeId},
cell::RefCell,
};
use generational_box::{AnyStorage, Owner, SyncStorage, UnsyncStorage};
use crate::{innerlude::current_scope_id, ScopeId};
/// Run a closure with the given owner.
///
/// This will override the default owner for the current component.
pub fn with_owner<S: AnyStorage, F: FnOnce() -> R, R>(owner: Owner<S>, f: F) -> R {
let old_owner = set_owner(Some(owner));
let result = f();
set_owner(old_owner);
result
}
/// Set the owner for the current thread.
fn set_owner<S: AnyStorage>(owner: Option<Owner<S>>) -> Option<Owner<S>> {
let id = TypeId::of::<S>();
if id == TypeId::of::<SyncStorage>() {
SYNC_OWNER.with(|cell| {
std::mem::replace(
&mut *cell.borrow_mut(),
owner.map(|owner| {
*(Box::new(owner) as Box<dyn Any>)
.downcast::<Owner<SyncStorage>>()
.unwrap()
}),
)
.map(|owner| *(Box::new(owner) as Box<dyn Any>).downcast().unwrap())
})
} else {
UNSYNC_OWNER.with(|cell| {
std::mem::replace(
&mut *cell.borrow_mut(),
owner.map(|owner| {
*(Box::new(owner) as Box<dyn Any>)
.downcast::<Owner<UnsyncStorage>>()
.unwrap()
}),
)
.map(|owner| *(Box::new(owner) as Box<dyn Any>).downcast().unwrap())
})
}
}
thread_local! {
static SYNC_OWNER: RefCell<Option<Owner<SyncStorage>>> = const { RefCell::new(None) };
static UNSYNC_OWNER: RefCell<Option<Owner<UnsyncStorage>>> = const { RefCell::new(None) };
}
/// Returns the current owner. This owner will be used to drop any `Copy` state that is created by the `generational-box` crate.
///
/// If an owner has been set with `with_owner`, that owner will be returned. Otherwise, the owner from the current scope will be returned.
pub fn current_owner<S: AnyStorage>() -> Owner<S> {
let id = TypeId::of::<S>();
let override_owner = if id == TypeId::of::<SyncStorage>() {
SYNC_OWNER.with(|cell| {
let owner = cell.borrow();
owner.clone().map(|owner| {
*(Box::new(owner) as Box<dyn Any>)
.downcast::<Owner<S>>()
.unwrap()
})
})
} else {
UNSYNC_OWNER.with(|cell| {
cell.borrow().clone().map(|owner| {
*(Box::new(owner) as Box<dyn Any>)
.downcast::<Owner<S>>()
.unwrap()
})
})
};
if let Some(owner) = override_owner {
return owner;
}
// Otherwise get the owner from the current scope
current_scope_id().expect("in a virtual dom").owner()
}
impl ScopeId {
/// Get the owner for the current scope.
pub fn owner<S: AnyStorage>(self) -> Owner<S> {
match self.has_context() {
Some(rt) => rt,
None => {
let owner = S::owner();
self.provide_context(owner)
}
}
}
}
+8 -6
View File
@@ -10,6 +10,7 @@ mod dirty_scope;
mod error_boundary;
mod events;
mod fragment;
mod generational_box;
mod global_context;
mod mutations;
mod nodes;
@@ -29,6 +30,7 @@ pub(crate) mod innerlude {
pub use crate::error_boundary::*;
pub use crate::events::*;
pub use crate::fragment::*;
pub use crate::generational_box::*;
pub use crate::global_context::*;
pub use crate::mutations::*;
pub use crate::nodes::*;
@@ -88,13 +90,13 @@ pub use crate::innerlude::{
/// This includes types like [`Element`], and [`Component`].
pub mod prelude {
pub use crate::innerlude::{
consume_context, consume_context_from_scope, current_scope_id, fc_to_builder, generation,
has_context, needs_update, needs_update_any, parent_scope, provide_context,
provide_root_context, remove_future, schedule_update, schedule_update_any, spawn,
spawn_forever, spawn_isomorphic, suspend, try_consume_context, use_after_render,
consume_context, consume_context_from_scope, current_owner, current_scope_id,
fc_to_builder, generation, has_context, needs_update, needs_update_any, parent_scope,
provide_context, provide_root_context, remove_future, schedule_update, schedule_update_any,
spawn, spawn_forever, spawn_isomorphic, suspend, try_consume_context, use_after_render,
use_before_render, use_drop, use_error_boundary, use_hook, use_hook_with_cleanup,
wait_for_next_render, AnyValue, Attribute, Component, ComponentFunction, Element,
ErrorBoundary, Event, EventHandler, Fragment, HasAttributes, IntoAttributeValue,
wait_for_next_render, with_owner, AnyValue, Attribute, Component, ComponentFunction,
Element, ErrorBoundary, Event, EventHandler, Fragment, HasAttributes, IntoAttributeValue,
IntoDynNode, OptionStringFromMarker, Properties, Runtime, RuntimeGuard, ScopeId,
ScopeState, SuperFrom, SuperInto, Task, Template, TemplateAttribute, TemplateNode, Throw,
VNode, VNodeInner, VirtualDom,