fix subscriptions in events

This commit is contained in:
Evan Almloff 2023-08-04 17:23:57 -07:00
parent 07ed3e67dd
commit a6f611eccf
12 changed files with 445 additions and 103 deletions

View File

@ -1,26 +1,60 @@
//! Example: README.md showcase
//!
//! The example from the README.md.
#![allow(non_snake_case)]
use dioxus::prelude::*;
use dioxus_signals::use_signal;
use dioxus_signals::{use_signal, Effect, Signal};
fn main() {
dioxus_desktop::launch(app);
}
fn app(cx: Scope) -> Element {
let mut count = use_signal(cx, || 0);
let counts = use_signal(cx, || (0..100).map(Signal::new).collect::<Vec<_>>());
use_future!(cx, || async move {
loop {
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
count += 1;
println!("current: {count}");
}
cx.use_hook(|| {
Effect::new(move || {
println!("Counts: {:?}", counts);
})
});
cx.render(rsx! {
div { "High-Five counter: {count}" }
})
render! {
for count in counts {
Child {
count: count,
}
}
}
}
#[derive(Props, PartialEq)]
struct ChildProps {
count: Signal<u64>,
}
fn Child(cx: Scope<ChildProps>) -> Element {
let count = cx.props.count;
// use_future!(cx, || async move {
// loop {
// tokio::time::sleep(std::time::Duration::from_secs(count.value())).await;
// *count.write() += 1;
// }
// });
render! {
div {
"Child: {count}"
button {
onclick: move |_| {
*count.write() += 1;
},
"Increase"
}
button {
onclick: move |_| {
*count.write() -= 1;
},
"Decrease"
}
}
}
}

View File

@ -193,6 +193,17 @@ impl<T: 'static> CopyHandle<T> {
pub fn write(&self) -> RefMut<'_, T> {
self.try_write().unwrap()
}
pub fn ptr_eq(&self, other: &Self) -> bool {
#[cfg(any(debug_assertions, feature = "check_generation"))]
{
self.raw.data.as_ptr() == other.raw.data.as_ptr() && self.generation == other.generation
}
#[cfg(not(any(debug_assertions, feature = "check_generation")))]
{
self.raw.data.as_ptr() == other.raw.data.as_ptr()
}
}
}
impl<T> Copy for CopyHandle<T> {}

View File

@ -163,7 +163,7 @@ impl VirtualDom {
let listener = unsafe { &*listener };
match &listener.value {
AttributeValue::Listener(l) => {
_ = l.take();
_ = l.callback.take();
}
AttributeValue::Any(a) => {
_ = a.take();

View File

@ -1,7 +1,9 @@
use std::{
cell::{Cell, RefCell},
rc::Rc,
};
use crate::ScopeId;
/// A wrapper around some generic data that handles the event's state
///
@ -134,13 +136,15 @@ impl<T: std::fmt::Debug> std::fmt::Debug for Event<T> {
/// }
///
/// ```
pub struct EventHandler<'bump, T = ()> {
pub struct EventHandler<'bump, T:?Sized = ()> {
pub(crate) origin: ScopeId,
pub(super) callback: RefCell<Option<ExternalListenerCallback<'bump, T>>>,
}
impl<T> Default for EventHandler<'_, T> {
fn default() -> Self {
Self {
origin: ScopeId(0),
callback: Default::default(),
}
}

View File

@ -1,4 +1,4 @@
use crate::{
use crate::prelude::EventHandler;use crate::{
any_props::AnyProps, arena::ElementId, Element, Event, LazyNodes, ScopeId, ScopeState,
};
use bumpalo::boxed::Box as BumpBox;
@ -476,7 +476,7 @@ pub enum AttributeValue<'a> {
Bool(bool),
/// A listener, like "onclick"
Listener(RefCell<Option<ListenerCb<'a>>>),
Listener(EventHandler<'a, Event<dyn Any>>),
/// An arbitrary value that implements PartialEq and is static
Any(RefCell<Option<BumpBox<'a, dyn AnyValue>>>),
@ -485,8 +485,6 @@ pub enum AttributeValue<'a> {
None,
}
pub type ListenerCb<'a> = BumpBox<'a, dyn FnMut(Event<dyn Any>) + 'a>;
/// Any of the built-in values that the Dioxus VirtualDom supports as dynamic attributes on elements that are borrowed
///
/// These varients are used to communicate what the value of an attribute is that needs to be updated

View File

@ -446,7 +446,7 @@ impl<'src> ScopeState {
let handler: &mut dyn FnMut(T) = self.bump().alloc(f);
let caller = unsafe { BumpBox::from_raw(handler as *mut dyn FnMut(T)) };
let callback = RefCell::new(Some(caller));
EventHandler { callback }
EventHandler { callback, origin: self.context().id }
}
/// Create a new [`AttributeValue`] with the listener variant from a callback
@ -456,22 +456,16 @@ impl<'src> ScopeState {
&'src self,
mut callback: impl FnMut(Event<T>) + 'src,
) -> AttributeValue<'src> {
// safety: there's no other way to create a dynamicly-dispatched bump box other than alloc + from-raw
// This is the suggested way to build a bumpbox
//
// In theory, we could just use regular boxes
let boxed: BumpBox<'src, dyn FnMut(_) + 'src> = unsafe {
BumpBox::from_raw(self.bump().alloc(move |event: Event<dyn Any>| {
if let Ok(data) = event.data.downcast::<T>() {
callback(Event {
propagates: event.propagates,
data,
});
}
}))
};
AttributeValue::Listener(RefCell::new(Some(boxed)))
AttributeValue::Listener(self.event_handler(move |event: Event<dyn Any>| {
if let Ok(data) = event.data.downcast::<T>() {
callback(Event {
propagates: event.propagates,
data,
});
}
}))
}
/// Create a new [`AttributeValue`] with a value that implements [`AnyValue`]

View File

@ -391,9 +391,12 @@ impl VirtualDom {
// We check the bubble state between each call to see if the event has been stopped from bubbling
for listener in listeners.drain(..).rev() {
if let AttributeValue::Listener(listener) = listener {
if let Some(cb) = listener.borrow_mut().as_deref_mut() {
let origin = listener.origin;
self.runtime.scope_stack.borrow_mut().push(origin);
if let Some(cb) = listener.callback.borrow_mut().as_deref_mut() {
cb(uievent.clone());
}
self.runtime.scope_stack.borrow_mut().pop();
if !uievent.propagates.get() {
return;
@ -422,9 +425,12 @@ impl VirtualDom {
// Only call the listener if this is the exact target element.
if attr.name.trim_start_matches("on") == name && target_path == this_path {
if let AttributeValue::Listener(listener) = &attr.value {
if let Some(cb) = listener.borrow_mut().as_deref_mut() {
let origin = listener.origin;
self.runtime.scope_stack.borrow_mut().push(origin);
if let Some(cb) = listener.callback.borrow_mut().as_deref_mut() {
cb(uievent.clone());
}
self.runtime.scope_stack.borrow_mut().pop();
break;
}

View File

@ -0,0 +1,52 @@
use dioxus_core::prelude::*;
use crate::CopyValue;
#[derive(Default, Clone, Copy)]
pub(crate) struct EffectStack {
pub(crate) effects: CopyValue<Vec<Effect>>,
}
pub(crate) fn get_effect_stack() -> EffectStack {
match consume_context() {
Some(rt) => rt,
None => {
let store = EffectStack::default();
provide_root_context(store).expect("in a virtual dom")
}
}
}
#[derive(Copy, Clone, PartialEq)]
pub struct Effect {
callback: CopyValue<Box<dyn FnMut()>>,
}
impl Effect {
pub(crate) fn current() -> Option<Self> {
get_effect_stack().effects.read().last().copied()
}
pub fn new(callback: impl FnMut() + 'static) -> Self {
let myself = Self {
callback: CopyValue::new(Box::new(callback)),
};
myself.try_run();
myself
}
/// Run the effect callback immediately. Returns `true` if the effect was run. Returns `false` is the effect is dead.
pub fn try_run(&self) {
if let Some(mut callback) = self.callback.try_write() {
{
get_effect_stack().effects.write().push(*self);
}
callback();
{
get_effect_stack().effects.write().pop();
}
}
}
}

View File

@ -0,0 +1,212 @@
use crate::rt::CopyValue;
use crate::Signal;
use std::cell::{Ref, RefMut};
use std::{
fmt::{Debug, Display},
ops::{Add, Div, Mul, Sub},
};
macro_rules! impls {
($ty:ident) => {
impl<T: Default + 'static> Default for $ty<T> {
fn default() -> Self {
Self::new(T::default())
}
}
impl<T> std::clone::Clone for $ty<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for $ty<T> {}
impl<T: Display + 'static> Display for $ty<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.with(|v| Display::fmt(v, f))
}
}
impl<T: Debug + 'static> Debug for $ty<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.with(|v| Debug::fmt(v, f))
}
}
impl<T: Add<Output = T> + Copy + 'static> std::ops::AddAssign<T> for $ty<T> {
fn add_assign(&mut self, rhs: T) {
self.set(self.value() + rhs);
}
}
impl<T: Sub<Output = T> + Copy + 'static> std::ops::SubAssign<T> for $ty<T> {
fn sub_assign(&mut self, rhs: T) {
self.set(self.value() - rhs);
}
}
impl<T: Mul<Output = T> + Copy + 'static> std::ops::MulAssign<T> for $ty<T> {
fn mul_assign(&mut self, rhs: T) {
self.set(self.value() * rhs);
}
}
impl<T: Div<Output = T> + Copy + 'static> std::ops::DivAssign<T> for $ty<T> {
fn div_assign(&mut self, rhs: T) {
self.set(self.value() / rhs);
}
}
impl<T: 'static> $ty<Vec<T>> {
pub fn push(&self, value: T) {
self.with_mut(|v| v.push(value))
}
pub fn pop(&self) -> Option<T> {
self.with_mut(|v| v.pop())
}
pub fn insert(&self, index: usize, value: T) {
self.with_mut(|v| v.insert(index, value))
}
pub fn remove(&self, index: usize) -> T {
self.with_mut(|v| v.remove(index))
}
pub fn clear(&self) {
self.with_mut(|v| v.clear())
}
pub fn extend(&self, iter: impl IntoIterator<Item = T>) {
self.with_mut(|v| v.extend(iter))
}
pub fn truncate(&self, len: usize) {
self.with_mut(|v| v.truncate(len))
}
pub fn swap_remove(&self, index: usize) -> T {
self.with_mut(|v| v.swap_remove(index))
}
pub fn retain(&self, f: impl FnMut(&T) -> bool) {
self.with_mut(|v| v.retain(f))
}
pub fn split_off(&self, at: usize) -> Vec<T> {
self.with_mut(|v| v.split_off(at))
}
pub fn get(&self, index: usize) -> Option<Ref<'_, T>> {
Ref::filter_map(self.read(), |v| v.get(index)).ok()
}
pub fn get_mut(&self, index: usize) -> Option<RefMut<'_, T>> {
RefMut::filter_map(self.write(), |v| v.get_mut(index)).ok()
}
}
impl<T: 'static> $ty<Option<T>> {
pub fn take(&self) -> Option<T> {
self.with_mut(|v| v.take())
}
pub fn replace(&self, value: T) -> Option<T> {
self.with_mut(|v| v.replace(value))
}
pub fn unwrap(&self) -> T
where
T: Clone,
{
self.with(|v| v.clone()).unwrap()
}
pub fn as_ref(&self) -> Option<Ref<'_, T>> {
Ref::filter_map(self.read(), |v| v.as_ref()).ok()
}
pub fn as_mut(&self) -> Option<RefMut<'_, T>> {
RefMut::filter_map(self.write(), |v| v.as_mut()).ok()
}
pub fn get_or_insert(&self, default: T) -> Ref<'_, T> {
self.get_or_insert_with(|| default)
}
pub fn get_or_insert_with(&self, default: impl FnOnce() -> T) -> Ref<'_, T> {
let borrow = self.read();
if borrow.is_none() {
drop(borrow);
self.with_mut(|v| *v = Some(default()));
Ref::map(self.read(), |v| v.as_ref().unwrap())
} else {
Ref::map(borrow, |v| v.as_ref().unwrap())
}
}
}
};
}
impls!(CopyValue);
impls!(Signal);
pub struct CopyValueIterator<T: 'static> {
index: usize,
value: CopyValue<Vec<T>>,
}
impl<T: Clone> Iterator for CopyValueIterator<T> {
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
let index = self.index;
self.index += 1;
self.value.get(index).map(|v| v.clone())
}
}
impl<T: Clone + 'static> IntoIterator for CopyValue<Vec<T>> {
type IntoIter = CopyValueIterator<T>;
type Item = T;
fn into_iter(self) -> Self::IntoIter {
CopyValueIterator {
index: 0,
value: self,
}
}
}
pub struct CopySignalIterator<T: 'static> {
index: usize,
value: Signal<Vec<T>>,
}
impl<T: Clone> Iterator for CopySignalIterator<T> {
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
let index = self.index;
self.index += 1;
self.value.get(index).map(|v| v.clone())
}
}
impl<T: Clone + 'static> IntoIterator for Signal<Vec<T>> {
type IntoIter = CopySignalIterator<T>;
type Item = T;
fn into_iter(self) -> Self::IntoIter {
CopySignalIterator {
index: 0,
value: self,
}
}
}

View File

@ -1,15 +1,18 @@
use std::{
cell::{Ref, RefMut},
fmt::{Debug, Display},
ops::{Add, Div, Mul, Sub},
cell::{Ref, RefCell, RefMut},
rc::Rc,
sync::Arc,
};
mod rt;
pub use rt::*;
mod effect;
pub use effect::*;
#[macro_use]
mod impls;
use dioxus_core::{
prelude::{current_scope_id, schedule_update_any},
prelude::{current_scope_id, has_context, provide_context, schedule_update_any},
ScopeId, ScopeState,
};
@ -17,8 +20,36 @@ pub fn use_signal<T: 'static>(cx: &ScopeState, f: impl FnOnce() -> T) -> Signal<
*cx.use_hook(|| Signal::new(f()))
}
#[derive(Clone)]
struct Unsubscriber {
scope: ScopeId,
subscribers: Rc<RefCell<Vec<Rc<RefCell<Vec<ScopeId>>>>>>,
}
impl Drop for Unsubscriber {
fn drop(&mut self) {
for subscribers in self.subscribers.borrow().iter() {
subscribers.borrow_mut().retain(|s| *s != self.scope);
}
}
}
fn current_unsubscriber() -> Unsubscriber {
match has_context() {
Some(rt) => rt,
None => {
let owner = Unsubscriber {
scope: current_scope_id().expect("in a virtual dom"),
subscribers: Default::default(),
};
provide_context(owner).expect("in a virtual dom")
}
}
}
struct SignalData<T> {
subscribers: Vec<ScopeId>,
subscribers: Rc<RefCell<Vec<ScopeId>>>,
effect_subscribers: Rc<RefCell<Vec<Effect>>>,
update_any: Arc<dyn Fn(ScopeId)>,
value: T,
}
@ -31,7 +62,8 @@ impl<T: 'static> Signal<T> {
pub fn new(value: T) -> Self {
Self {
inner: CopyValue::new(SignalData {
subscribers: Vec::new(),
subscribers: Default::default(),
effect_subscribers: Default::default(),
update_any: schedule_update_any().expect("in a virtual dom"),
value,
}),
@ -39,21 +71,40 @@ impl<T: 'static> Signal<T> {
}
pub fn read(&self) -> Ref<T> {
let inner = self.inner.read();
if let Some(current_scope_id) = current_scope_id() {
let mut inner = self.inner.write();
if !inner.subscribers.contains(&current_scope_id) {
inner.subscribers.push(current_scope_id);
let mut subscribers = inner.subscribers.borrow_mut();
if !subscribers.contains(&current_scope_id) {
subscribers.push(current_scope_id);
drop(subscribers);
let unsubscriber = current_unsubscriber();
inner.subscribers.borrow_mut().push(unsubscriber.scope);
}
}
Ref::map(self.inner.read(), |v| &v.value)
if let Some(effect) = Effect::current() {
let mut effect_subscribers = inner.effect_subscribers.borrow_mut();
if !effect_subscribers.contains(&effect) {
effect_subscribers.push(effect);
}
}
Ref::map(inner, |v| &v.value)
}
pub fn write(&self) -> RefMut<T> {
let inner = self.inner.write();
for &scope_id in &inner.subscribers {
(inner.update_any)(scope_id);
{
let inner = self.inner.read();
for &scope_id in &*inner.subscribers.borrow() {
(inner.update_any)(scope_id);
}
}
let subscribers =
{ std::mem::take(&mut *self.inner.read().effect_subscribers.borrow_mut()) };
for effect in subscribers {
effect.try_run();
}
let inner = self.inner.write();
RefMut::map(inner, |v| &mut v.value)
}
@ -66,58 +117,20 @@ impl<T: 'static> Signal<T> {
f(&*write)
}
pub fn update<O>(&self, f: impl FnOnce(&mut T) -> O) -> O {
pub fn with_mut<O>(&self, f: impl FnOnce(&mut T) -> O) -> O {
let mut write = self.write();
f(&mut *write)
}
}
impl<T: Clone + 'static> Signal<T> {
pub fn get(&self) -> T {
pub fn value(&self) -> T {
self.read().clone()
}
}
impl<T> std::clone::Clone for Signal<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for Signal<T> {}
impl<T: Display + 'static> Display for Signal<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.with(|v| Display::fmt(v, f))
}
}
impl<T: Debug + 'static> Debug for Signal<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.with(|v| Debug::fmt(v, f))
}
}
impl<T: Add<Output = T> + Copy + 'static> std::ops::AddAssign<T> for Signal<T> {
fn add_assign(&mut self, rhs: T) {
self.set(self.get() + rhs);
}
}
impl<T: Sub<Output = T> + Copy + 'static> std::ops::SubAssign<T> for Signal<T> {
fn sub_assign(&mut self, rhs: T) {
self.set(self.get() - rhs);
}
}
impl<T: Mul<Output = T> + Copy + 'static> std::ops::MulAssign<T> for Signal<T> {
fn mul_assign(&mut self, rhs: T) {
self.set(self.get() * rhs);
}
}
impl<T: Div<Output = T> + Copy + 'static> std::ops::DivAssign<T> for Signal<T> {
fn div_assign(&mut self, rhs: T) {
self.set(self.get() / rhs);
impl<T: 'static> PartialEq for Signal<T> {
fn eq(&self, other: &Self) -> bool {
self.inner == other.inner
}
}

View File

@ -26,14 +26,6 @@ fn current_owner() -> Rc<Owner> {
}
}
impl<T> Copy for CopyValue<T> {}
impl<T> Clone for CopyValue<T> {
fn clone(&self) -> Self {
*self
}
}
pub struct CopyValue<T: 'static> {
pub value: CopyHandle<T>,
}
@ -62,4 +54,30 @@ impl<T: 'static> CopyValue<T> {
pub fn write(&self) -> RefMut<'_, T> {
self.value.write()
}
pub fn set(&mut self, value: T) {
*self.write() = value;
}
pub fn with<O>(&self, f: impl FnOnce(&T) -> O) -> O {
let write = self.read();
f(&*write)
}
pub fn with_mut<O>(&self, f: impl FnOnce(&mut T) -> O) -> O {
let mut write = self.write();
f(&mut *write)
}
}
impl<T: Clone + 'static> CopyValue<T> {
pub fn value(&self) -> T {
self.read().clone()
}
}
impl<T: 'static> PartialEq for CopyValue<T> {
fn eq(&self, other: &Self) -> bool {
self.value.ptr_eq(&other.value)
}
}