From 4f9d67fb45f67456096e8e3eb7cf8d4664416399 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sun, 1 Jan 2023 19:57:33 -0500 Subject: [PATCH 1/4] feat: simple signals implementation --- Cargo.toml | 2 + examples/signals.rs | 41 +++++++++++ packages/signals/Cargo.toml | 10 +++ packages/signals/src/lib.rs | 131 ++++++++++++++++++++++++++++++++++++ packages/signals/src/rt.rs | 73 ++++++++++++++++++++ 5 files changed, 257 insertions(+) create mode 100644 examples/signals.rs create mode 100644 packages/signals/Cargo.toml create mode 100644 packages/signals/src/lib.rs create mode 100644 packages/signals/src/rt.rs diff --git a/Cargo.toml b/Cargo.toml index af5e8a733..7d8cc6871 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ members = [ "packages/native-core", "packages/native-core-macro", "packages/rsx-rosetta", + "packages/signals", "docs/guide", ] @@ -43,6 +44,7 @@ dioxus = { path = "./packages/dioxus" } dioxus-desktop = { path = "./packages/desktop", features = ["transparent"] } dioxus-ssr = { path = "./packages/ssr" } dioxus-router = { path = "./packages/router" } +dioxus-signals = { path = "./packages/signals" } fermi = { path = "./packages/fermi" } futures-util = "0.3.21" log = "0.4.14" diff --git a/examples/signals.rs b/examples/signals.rs new file mode 100644 index 000000000..17f21f28d --- /dev/null +++ b/examples/signals.rs @@ -0,0 +1,41 @@ +//! Example: README.md showcase +//! +//! The example from the README.md. + +use dioxus::prelude::*; +use dioxus_signals::{use_init_signal_rt, use_signal}; +use std::time::Duration; + +fn main() { + dioxus_desktop::launch(app); +} + +fn app(cx: Scope) -> Element { + use_init_signal_rt(cx); + + let mut count = use_signal(cx, || 0); + let mut running = use_signal(cx, || false); + + use_coroutine(cx, |_: UnboundedReceiver<()>| async move { + loop { + if running.get() { + count += 1; + } + tokio::time::sleep(Duration::from_millis(100)).await; + } + }); + + cx.render(rsx! { + h1 { "High-Five counter: {count}" } + + button { onclick: move |_| count += 1, "Up high!" } + button { onclick: move |_| count -= 1, "Down low!" } + + button { onclick: move |_| running.set(!running.get()), "Start counting" } + + + if count.get() > 3 { + rsx! ( h2 { "Nice!" } ) + } + }) +} diff --git a/packages/signals/Cargo.toml b/packages/signals/Cargo.toml new file mode 100644 index 000000000..9a12c787d --- /dev/null +++ b/packages/signals/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "dioxus-signals" +version = "0.0.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +dioxus-core = { path = "../core" } +slab = "0.4.7" diff --git a/packages/signals/src/lib.rs b/packages/signals/src/lib.rs new file mode 100644 index 000000000..9845372fe --- /dev/null +++ b/packages/signals/src/lib.rs @@ -0,0 +1,131 @@ +use std::{ + fmt::Display, + marker::PhantomData, + ops::{Add, Div, Mul, Sub}, +}; + +mod rt; + +use dioxus_core::ScopeState; +pub use rt::*; + +pub fn use_init_signal_rt(cx: &ScopeState) { + cx.use_hook(|| { + let rt = crate::rt::claim_rt(cx.schedule_update_any()); + cx.provide_context(rt); + }); +} + +pub fn use_signal(cx: &ScopeState, f: impl FnOnce() -> T) -> Signal { + *cx.use_hook(|| { + let rt: &'static SignalRt = cx.consume_context().unwrap(); + let id = rt.init(f()); + rt.subscribe(id, cx.scope_id()); + + Signal { + rt, + id, + t: PhantomData, + } + }) +} + +pub struct Signal { + id: usize, + rt: &'static SignalRt, + t: PhantomData, +} + +impl Signal { + pub fn set(&mut self, value: T) { + self.rt.set(self.id, value); + } + + pub fn map(&self, _f: impl FnOnce(T) -> U) -> Signal { + todo!() + } + + pub fn update(&self, _f: impl FnOnce(&mut T) -> O) { + todo!() + } +} + +impl Signal { + pub fn get(&self) -> T { + self.rt.get(self.id) + } +} + +// impl std::ops::Deref for Signal { +// type Target = dyn Fn() -> T; + +// fn deref(&self) -> &Self::Target { +// todo!() +// } +// } + +impl std::clone::Clone for Signal { + fn clone(&self) -> Self { + Self { + t: PhantomData, + id: self.id, + rt: self.rt, + } + } +} + +impl Copy for Signal {} + +impl Display for Signal { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.rt.with::(self.id, |v| T::fmt(v, f)) + } +} + +impl + Copy + 'static> std::ops::AddAssign for Signal { + fn add_assign(&mut self, rhs: T) { + self.set(self.get() + rhs); + } +} + +impl + Copy + 'static> std::ops::SubAssign for Signal { + fn sub_assign(&mut self, rhs: T) { + self.set(self.get() - rhs); + } +} + +impl + Copy + 'static> std::ops::MulAssign for Signal { + fn mul_assign(&mut self, rhs: T) { + self.set(self.get() * rhs); + } +} + +impl + Copy + 'static> std::ops::DivAssign for Signal { + fn div_assign(&mut self, rhs: T) { + self.set(self.get() / rhs); + } +} + +// impl + Copy> std::ops::AddAssign for Signal { +// fn add_assign(&mut self, rhs: T) { +// self.set((*self.current()) + rhs); +// } +// } + +// impl + Copy> std::ops::SubAssign for Signal { +// fn sub_assign(&mut self, rhs: T) { +// self.set((*self.current()) - rhs); +// } +// } + +// impl + Copy> std::ops::MulAssign for Signal { +// fn mul_assign(&mut self, rhs: T) { +// self.set((*self.current()) * rhs); +// } +// } + +// impl + Copy> std::ops::DivAssign for Signal { +// fn div_assign(&mut self, rhs: T) { +// self.set((*self.current()) / rhs); +// } +// } diff --git a/packages/signals/src/rt.rs b/packages/signals/src/rt.rs new file mode 100644 index 000000000..bea3c6f98 --- /dev/null +++ b/packages/signals/src/rt.rs @@ -0,0 +1,73 @@ +use std::{ + any::Any, + cell::RefCell, + rc::Rc, + sync::{Arc, Mutex}, +}; + +use dioxus_core::ScopeId; +use slab::Slab; + +/// Provide the runtime for signals +/// +/// This will reuse dead runtimes +pub fn claim_rt(update_any: Arc) -> &'static SignalRt { + Box::leak(Box::new(SignalRt { + signals: RefCell::new(Slab::new()), + update_any, + })) +} + +pub fn reclam_rt(rt: usize) {} + +struct GlobalRt { + signals: Slab, +} + +pub struct SignalRt { + signals: RefCell>, + update_any: Arc, +} + +impl SignalRt { + pub fn init(&self, val: T) -> usize { + self.signals.borrow_mut().insert(Inner { + value: Box::new(val), + subscribers: Vec::new(), + }) + } + + pub fn subscribe(&self, id: usize, subscriber: ScopeId) { + self.signals.borrow_mut()[id].subscribers.push(subscriber); + } + + pub fn get(&self, id: usize) -> T { + self.signals.borrow()[id] + .value + .downcast_ref::() + .cloned() + .unwrap() + } + + pub fn set(&self, id: usize, value: T) { + let mut signals = self.signals.borrow_mut(); + let inner = &mut signals[id]; + inner.value = Box::new(value); + + for subscriber in inner.subscribers.iter() { + (self.update_any)(*subscriber); + } + } + + pub fn with(&self, id: usize, f: impl FnOnce(&T) -> O) -> O { + let signals = self.signals.borrow(); + let inner = &signals[id]; + let inner = inner.value.downcast_ref::().unwrap(); + f(&*inner) + } +} + +struct Inner { + value: Box, + subscribers: Vec, +} From c0315e55f47ce03a29722cae15f21cce6d549dc3 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sun, 1 Jan 2023 22:02:49 -0500 Subject: [PATCH 2/4] chore: more signals methods --- examples/signals.rs | 13 +----------- packages/signals/src/lib.rs | 40 +++++++++++++++++++++++++++---------- packages/signals/src/rt.rs | 18 +++++++++++++++++ 3 files changed, 48 insertions(+), 23 deletions(-) diff --git a/examples/signals.rs b/examples/signals.rs index 17f21f28d..17ae40b73 100644 --- a/examples/signals.rs +++ b/examples/signals.rs @@ -14,28 +14,17 @@ fn app(cx: Scope) -> Element { use_init_signal_rt(cx); let mut count = use_signal(cx, || 0); - let mut running = use_signal(cx, || false); use_coroutine(cx, |_: UnboundedReceiver<()>| async move { loop { - if running.get() { - count += 1; - } + count += 1; tokio::time::sleep(Duration::from_millis(100)).await; } }); cx.render(rsx! { h1 { "High-Five counter: {count}" } - button { onclick: move |_| count += 1, "Up high!" } button { onclick: move |_| count -= 1, "Down low!" } - - button { onclick: move |_| running.set(!running.get()), "Start counting" } - - - if count.get() > 3 { - rsx! ( h2 { "Nice!" } ) - } }) } diff --git a/packages/signals/src/lib.rs b/packages/signals/src/lib.rs index 9845372fe..2fcbdcdb2 100644 --- a/packages/signals/src/lib.rs +++ b/packages/signals/src/lib.rs @@ -1,4 +1,5 @@ use std::{ + cell::{Ref, RefMut}, fmt::Display, marker::PhantomData, ops::{Add, Div, Mul, Sub}, @@ -17,17 +18,29 @@ pub fn use_init_signal_rt(cx: &ScopeState) { } pub fn use_signal(cx: &ScopeState, f: impl FnOnce() -> T) -> Signal { - *cx.use_hook(|| { + cx.use_hook(|| { let rt: &'static SignalRt = cx.consume_context().unwrap(); let id = rt.init(f()); rt.subscribe(id, cx.scope_id()); - Signal { - rt, - id, - t: PhantomData, + struct SignalHook { + signal: Signal, + } + impl Drop for SignalHook { + fn drop(&mut self) { + self.signal.rt.remove(self.signal.id); + } + } + + SignalHook { + signal: Signal { + id, + rt, + t: PhantomData, + }, } }) + .signal } pub struct Signal { @@ -37,16 +50,21 @@ pub struct Signal { } impl Signal { + pub fn read(&self) -> Ref { + self.rt.read(self.id) + } + + pub fn write(&self) -> RefMut { + self.rt.write(self.id) + } + pub fn set(&mut self, value: T) { self.rt.set(self.id, value); } - pub fn map(&self, _f: impl FnOnce(T) -> U) -> Signal { - todo!() - } - - pub fn update(&self, _f: impl FnOnce(&mut T) -> O) { - todo!() + pub fn update(&self, _f: impl FnOnce(&mut T) -> O) -> O { + let mut write = self.write(); + _f(&mut *write) } } diff --git a/packages/signals/src/rt.rs b/packages/signals/src/rt.rs index bea3c6f98..c24e336f9 100644 --- a/packages/signals/src/rt.rs +++ b/packages/signals/src/rt.rs @@ -59,12 +59,30 @@ impl SignalRt { } } + pub fn remove(&self, id: usize) { + self.signals.borrow_mut().remove(id); + } + pub fn with(&self, id: usize, f: impl FnOnce(&T) -> O) -> O { let signals = self.signals.borrow(); let inner = &signals[id]; let inner = inner.value.downcast_ref::().unwrap(); f(&*inner) } + + pub(crate) fn read(&self, id: usize) -> std::cell::Ref { + let signals = self.signals.borrow(); + std::cell::Ref::map(signals, |signals| { + signals[id].value.downcast_ref::().unwrap() + }) + } + + pub(crate) fn write(&self, id: usize) -> std::cell::RefMut { + let mut signals = self.signals.borrow_mut(); + std::cell::RefMut::map(signals, |signals| { + signals[id].value.downcast_mut::().unwrap() + }) + } } struct Inner { From fd92079eb3d33b18b4edd38529cace5ff446a992 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sun, 1 Jan 2023 22:09:08 -0500 Subject: [PATCH 3/4] feat: add recycling of rts in signals --- examples/signals.rs | 6 +----- packages/signals/src/rt.rs | 36 +++++++++++++++++++++--------------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/examples/signals.rs b/examples/signals.rs index 17ae40b73..e5e0935b5 100644 --- a/examples/signals.rs +++ b/examples/signals.rs @@ -1,7 +1,3 @@ -//! Example: README.md showcase -//! -//! The example from the README.md. - use dioxus::prelude::*; use dioxus_signals::{use_init_signal_rt, use_signal}; use std::time::Duration; @@ -15,7 +11,7 @@ fn app(cx: Scope) -> Element { let mut count = use_signal(cx, || 0); - use_coroutine(cx, |_: UnboundedReceiver<()>| async move { + use_future!(cx, || async move { loop { count += 1; tokio::time::sleep(Duration::from_millis(100)).await; diff --git a/packages/signals/src/rt.rs b/packages/signals/src/rt.rs index c24e336f9..d516b9ff0 100644 --- a/packages/signals/src/rt.rs +++ b/packages/signals/src/rt.rs @@ -1,27 +1,33 @@ -use std::{ - any::Any, - cell::RefCell, - rc::Rc, - sync::{Arc, Mutex}, -}; +use std::{any::Any, cell::RefCell, sync::Arc}; use dioxus_core::ScopeId; use slab::Slab; +thread_local! { + static RUNTIMES: RefCell> = RefCell::new(Vec::new()); +} + /// Provide the runtime for signals /// /// This will reuse dead runtimes pub fn claim_rt(update_any: Arc) -> &'static SignalRt { - Box::leak(Box::new(SignalRt { - signals: RefCell::new(Slab::new()), - update_any, - })) + RUNTIMES.with(|runtimes| { + if let Some(rt) = runtimes.borrow_mut().pop() { + return rt; + } + + Box::leak(Box::new(SignalRt { + signals: RefCell::new(Slab::new()), + update_any, + })) + }) } -pub fn reclam_rt(rt: usize) {} - -struct GlobalRt { - signals: Slab, +/// Push this runtime into the global runtime list +pub fn reclam_rt(_rt: &'static SignalRt) { + RUNTIMES.with(|runtimes| { + runtimes.borrow_mut().push(_rt); + }); } pub struct SignalRt { @@ -78,7 +84,7 @@ impl SignalRt { } pub(crate) fn write(&self, id: usize) -> std::cell::RefMut { - let mut signals = self.signals.borrow_mut(); + let signals = self.signals.borrow_mut(); std::cell::RefMut::map(signals, |signals| { signals[id].value.downcast_mut::().unwrap() }) From 14a1e4145e9a79f58f8a2c39ab9f636d350c130d Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sun, 1 Jan 2023 22:57:16 -0500 Subject: [PATCH 4/4] feat: implement function accessor --- examples/signals.rs | 6 +++++- packages/signals/src/lib.rs | 42 +++++++++++-------------------------- packages/signals/src/rt.rs | 38 ++++++++++++++++++++++++++------- 3 files changed, 48 insertions(+), 38 deletions(-) diff --git a/examples/signals.rs b/examples/signals.rs index e5e0935b5..988b9af41 100644 --- a/examples/signals.rs +++ b/examples/signals.rs @@ -14,7 +14,7 @@ fn app(cx: Scope) -> Element { use_future!(cx, || async move { loop { count += 1; - tokio::time::sleep(Duration::from_millis(100)).await; + tokio::time::sleep(Duration::from_millis(400)).await; } }); @@ -22,5 +22,9 @@ fn app(cx: Scope) -> Element { h1 { "High-Five counter: {count}" } button { onclick: move |_| count += 1, "Up high!" } button { onclick: move |_| count -= 1, "Down low!" } + + if count() > 5 { + rsx!{ h2 { "High five!" } } + } }) } diff --git a/packages/signals/src/lib.rs b/packages/signals/src/lib.rs index 2fcbdcdb2..e4b499ed8 100644 --- a/packages/signals/src/lib.rs +++ b/packages/signals/src/lib.rs @@ -26,6 +26,7 @@ pub fn use_signal(cx: &ScopeState, f: impl FnOnce() -> T) -> Signal< struct SignalHook { signal: Signal, } + impl Drop for SignalHook { fn drop(&mut self) { self.signal.rt.remove(self.signal.id); @@ -62,6 +63,11 @@ impl Signal { self.rt.set(self.id, value); } + pub fn with(&self, f: impl FnOnce(&T) -> O) -> O { + let write = self.read(); + f(&*write) + } + pub fn update(&self, _f: impl FnOnce(&mut T) -> O) -> O { let mut write = self.write(); _f(&mut *write) @@ -74,13 +80,13 @@ impl Signal { } } -// impl std::ops::Deref for Signal { -// type Target = dyn Fn() -> T; +impl std::ops::Deref for Signal { + type Target = dyn Fn() -> T; -// fn deref(&self) -> &Self::Target { -// todo!() -// } -// } + fn deref(&self) -> &Self::Target { + self.rt.getter(self.id) + } +} impl std::clone::Clone for Signal { fn clone(&self) -> Self { @@ -123,27 +129,3 @@ impl + Copy + 'static> std::ops::DivAssign for Signal { self.set(self.get() / rhs); } } - -// impl + Copy> std::ops::AddAssign for Signal { -// fn add_assign(&mut self, rhs: T) { -// self.set((*self.current()) + rhs); -// } -// } - -// impl + Copy> std::ops::SubAssign for Signal { -// fn sub_assign(&mut self, rhs: T) { -// self.set((*self.current()) - rhs); -// } -// } - -// impl + Copy> std::ops::MulAssign for Signal { -// fn mul_assign(&mut self, rhs: T) { -// self.set((*self.current()) * rhs); -// } -// } - -// impl + Copy> std::ops::DivAssign for Signal { -// fn div_assign(&mut self, rhs: T) { -// self.set((*self.current()) / rhs); -// } -// } diff --git a/packages/signals/src/rt.rs b/packages/signals/src/rt.rs index d516b9ff0..e849f9008 100644 --- a/packages/signals/src/rt.rs +++ b/packages/signals/src/rt.rs @@ -4,6 +4,7 @@ use dioxus_core::ScopeId; use slab::Slab; thread_local! { + // we cannot drop these since any future might be using them static RUNTIMES: RefCell> = RefCell::new(Vec::new()); } @@ -31,15 +32,16 @@ pub fn reclam_rt(_rt: &'static SignalRt) { } pub struct SignalRt { - signals: RefCell>, - update_any: Arc, + pub(crate) signals: RefCell>, + pub(crate) update_any: Arc, } impl SignalRt { - pub fn init(&self, val: T) -> usize { + pub fn init(&'static self, val: T) -> usize { self.signals.borrow_mut().insert(Inner { value: Box::new(val), subscribers: Vec::new(), + getter: None, }) } @@ -73,7 +75,7 @@ impl SignalRt { let signals = self.signals.borrow(); let inner = &signals[id]; let inner = inner.value.downcast_ref::().unwrap(); - f(&*inner) + f(inner) } pub(crate) fn read(&self, id: usize) -> std::cell::Ref { @@ -89,9 +91,31 @@ impl SignalRt { signals[id].value.downcast_mut::().unwrap() }) } + + pub(crate) fn getter(&self, id: usize) -> &dyn Fn() -> T { + let mut signals = self.signals.borrow_mut(); + let inner = &mut signals[id]; + let r = inner.getter.as_mut(); + + if r.is_none() { + let rt = self; + let r = move || rt.get::(id); + let getter: Box T> = Box::new(r); + let getter: Box = unsafe { std::mem::transmute(getter) }; + + inner.getter = Some(getter); + } + + let r = inner.getter.as_ref().unwrap(); + + unsafe { std::mem::transmute::<&dyn Fn(), &dyn Fn() -> T>(r) } + } } -struct Inner { - value: Box, - subscribers: Vec, +pub(crate) struct Inner { + pub value: Box, + pub subscribers: Vec, + + // todo: this has a soundness hole in it that you might not run into + pub getter: Option>, }