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, +}