create read only signal

This commit is contained in:
Evan Almloff 2023-08-07 16:04:49 -07:00
parent 6ca170453b
commit 646c161c7d
7 changed files with 287 additions and 236 deletions

View file

@ -25,7 +25,7 @@ members = [
"packages/native-core",
"packages/native-core-macro",
"packages/rsx-rosetta",
"packages/generational_box",
"packages/generational-box",
"packages/signals",
"packages/hot-reload",
"packages/fullstack",
@ -77,7 +77,7 @@ dioxus-native-core = { path = "packages/native-core", version = "0.4.0" }
dioxus-native-core-macro = { path = "packages/native-core-macro", version = "0.4.0" }
rsx-rosetta = { path = "packages/rsx-rosetta", version = "0.4.0" }
dioxus-signals = { path = "packages/signals" }
generational-box = { path = "packages/generational_box" }
generational-box = { path = "packages/generational-box" }
dioxus-hot-reload = { path = "packages/hot-reload", version = "0.4.0" }
dioxus-fullstack = { path = "packages/fullstack", version = "0.4.1" }
dioxus_server_macro = { path = "packages/server-macro", version = "0.4.1" }

View file

@ -1,5 +1,5 @@
use crate::rt::CopyValue;
use crate::{Signal, Write};
use crate::signal::{ReadOnlySignal, Signal, Write};
use std::cell::{Ref, RefMut};
@ -8,11 +8,11 @@ use std::{
ops::{Add, Div, Mul, Sub},
};
macro_rules! impls {
macro_rules! read_impls {
($ty:ident) => {
impl<T: Default + 'static> Default for $ty<T> {
fn default() -> Self {
Self::new(T::default())
Self::new(Default::default())
}
}
@ -36,6 +36,29 @@ macro_rules! impls {
}
}
impl<T: 'static> $ty<Vec<T>> {
pub fn get(&self, index: usize) -> Option<Ref<'_, T>> {
Ref::filter_map(self.read(), |v| v.get(index)).ok()
}
}
impl<T: 'static> $ty<Option<T>> {
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()
}
}
};
}
macro_rules! write_impls {
($ty:ident) => {
impl<T: Add<Output = T> + Copy + 'static> std::ops::AddAssign<T> for $ty<T> {
fn add_assign(&mut self, rhs: T) {
self.with_mut(|v| *v = *v + rhs)
@ -100,10 +123,6 @@ macro_rules! impls {
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()
}
}
impl<T: 'static> $ty<Option<T>> {
@ -115,17 +134,6 @@ macro_rules! impls {
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 get_or_insert(&self, default: T) -> Ref<'_, T> {
self.get_or_insert_with(|| default)
}
@ -144,8 +152,11 @@ macro_rules! impls {
};
}
impls!(CopyValue);
impls!(Signal);
read_impls!(CopyValue);
write_impls!(CopyValue);
read_impls!(Signal);
write_impls!(Signal);
read_impls!(ReadOnlySignal);
pub struct CopyValueIterator<T: 'static> {
index: usize,

View file

@ -1,10 +1,3 @@
use std::{
cell::{Ref, RefCell, RefMut},
ops::{Deref, DerefMut},
rc::Rc,
sync::Arc,
};
mod rt;
pub use rt::*;
mod effect;
@ -12,205 +5,5 @@ pub use effect::*;
mod impls;
mod memo;
pub use memo::*;
use dioxus_core::{
prelude::{current_scope_id, has_context, provide_context, schedule_update_any},
ScopeId, ScopeState,
};
pub fn use_signal<T: 'static>(cx: &ScopeState, f: impl FnOnce() -> T) -> Signal<T> {
*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: Rc<RefCell<Vec<ScopeId>>>,
effect_subscribers: Rc<RefCell<Vec<Effect>>>,
update_any: Arc<dyn Fn(ScopeId)>,
pub(crate) value: T,
}
pub struct Signal<T: 'static> {
pub(crate) inner: CopyValue<SignalData<T>>,
}
impl<T: 'static> Signal<T> {
pub fn new(value: T) -> Self {
Self {
inner: CopyValue::new(SignalData {
subscribers: Default::default(),
effect_subscribers: Default::default(),
update_any: schedule_update_any().expect("in a virtual dom"),
value,
}),
}
}
pub fn origin_scope(&self) -> ScopeId {
self.inner.origin_scope()
}
pub fn read(&self) -> Ref<T> {
let inner = self.inner.read();
if let Some(effect) = Effect::current() {
let mut effect_subscribers = inner.effect_subscribers.borrow_mut();
if !effect_subscribers.contains(&effect) {
effect_subscribers.push(effect);
}
} else if let Some(current_scope_id) = current_scope_id() {
log::trace!(
"{:?} subscribed to {:?}",
self.inner.value,
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(inner, |v| &v.value)
}
pub fn write(&self) -> Write<'_, T> {
let inner = self.inner.write();
let borrow = RefMut::map(inner, |v| &mut v.value);
Write {
write: borrow,
signal: SignalSubscriberDrop { signal: *self },
}
}
fn update_subscribers(&self) {
{
let inner = self.inner.read();
for &scope_id in &*inner.subscribers.borrow() {
log::trace!(
"Write on {:?} triggered update on {:?}",
self.inner.value,
scope_id
);
(inner.update_any)(scope_id);
}
}
let subscribers = {
let self_read = self.inner.read();
let mut effects = self_read.effect_subscribers.borrow_mut();
std::mem::take(&mut *effects)
};
for effect in subscribers {
log::trace!(
"Write on {:?} triggered effect {:?}",
self.inner.value,
effect
);
effect.try_run();
}
}
pub fn set(&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> Signal<T> {
pub fn value(&self) -> T {
self.read().clone()
}
}
impl<T: 'static> PartialEq for Signal<T> {
fn eq(&self, other: &Self) -> bool {
self.inner == other.inner
}
}
struct SignalSubscriberDrop<T: 'static> {
signal: Signal<T>,
}
impl<T: 'static> Drop for SignalSubscriberDrop<T> {
fn drop(&mut self) {
self.signal.update_subscribers();
}
}
pub struct Write<'a, T: 'static, I: 'static = T> {
write: RefMut<'a, T>,
signal: SignalSubscriberDrop<I>,
}
impl<'a, T: 'static, I: 'static> Write<'a, T, I> {
pub fn map<O>(myself: Self, f: impl FnOnce(&mut T) -> &mut O) -> Write<'a, O, I> {
let Self { write, signal } = myself;
Write {
write: RefMut::map(write, f),
signal,
}
}
pub fn filter_map<O>(
myself: Self,
f: impl FnOnce(&mut T) -> Option<&mut O>,
) -> Option<Write<'a, O, I>> {
let Self { write, signal } = myself;
let write = RefMut::filter_map(write, f).ok();
write.map(|write| Write {
write,
signal: signal,
})
}
}
impl<'a, T: 'static> Deref for Write<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&*self.write
}
}
impl<T> DerefMut for Write<'_, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut *self.write
}
}
pub(crate) mod signal;
pub use signal::*;

View file

@ -1,12 +1,15 @@
use dioxus_core::prelude::*;
use crate::{get_effect_stack, CopyValue, Effect, Signal, SignalData};
use crate::{get_effect_stack, signal::SignalData, CopyValue, Effect, ReadOnlySignal, Signal};
pub fn use_memo<R: PartialEq>(cx: &ScopeState, f: impl FnMut() -> R + 'static) -> Signal<R> {
*cx.use_hook(|| memo(f))
pub fn use_selector<R: PartialEq>(
cx: &ScopeState,
f: impl FnMut() -> R + 'static,
) -> ReadOnlySignal<R> {
*cx.use_hook(|| selector(f))
}
pub fn memo<R: PartialEq>(mut f: impl FnMut() -> R + 'static) -> Signal<R> {
fn selector<R: PartialEq>(mut f: impl FnMut() -> R + 'static) -> ReadOnlySignal<R> {
let state = Signal::<R> {
inner: CopyValue::invalid(),
};
@ -38,5 +41,5 @@ pub fn memo<R: PartialEq>(mut f: impl FnMut() -> R + 'static) -> Signal<R> {
}
}));
state
ReadOnlySignal::new(state)
}

View file

@ -0,0 +1,244 @@
use std::{
cell::{Ref, RefCell, RefMut},
ops::{Deref, DerefMut},
rc::Rc,
sync::Arc,
};
use dioxus_core::{
prelude::{current_scope_id, has_context, provide_context, schedule_update_any},
ScopeId, ScopeState,
};
use crate::{CopyValue, Effect};
pub fn use_signal<T: 'static>(cx: &ScopeState, f: impl FnOnce() -> T) -> Signal<T> {
*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")
}
}
}
pub(crate) struct SignalData<T> {
pub(crate) subscribers: Rc<RefCell<Vec<ScopeId>>>,
pub(crate) effect_subscribers: Rc<RefCell<Vec<Effect>>>,
pub(crate) update_any: Arc<dyn Fn(ScopeId)>,
pub(crate) value: T,
}
pub struct Signal<T: 'static> {
pub(crate) inner: CopyValue<SignalData<T>>,
}
impl<T: 'static> Signal<T> {
pub fn new(value: T) -> Self {
Self {
inner: CopyValue::new(SignalData {
subscribers: Default::default(),
effect_subscribers: Default::default(),
update_any: schedule_update_any().expect("in a virtual dom"),
value,
}),
}
}
pub fn origin_scope(&self) -> ScopeId {
self.inner.origin_scope()
}
pub fn read(&self) -> Ref<T> {
let inner = self.inner.read();
if let Some(effect) = Effect::current() {
let mut effect_subscribers = inner.effect_subscribers.borrow_mut();
if !effect_subscribers.contains(&effect) {
effect_subscribers.push(effect);
}
} else if let Some(current_scope_id) = current_scope_id() {
log::trace!(
"{:?} subscribed to {:?}",
self.inner.value,
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(inner, |v| &v.value)
}
pub fn write(&self) -> Write<'_, T> {
let inner = self.inner.write();
let borrow = RefMut::map(inner, |v| &mut v.value);
Write {
write: borrow,
signal: SignalSubscriberDrop { signal: *self },
}
}
fn update_subscribers(&self) {
{
let inner = self.inner.read();
for &scope_id in &*inner.subscribers.borrow() {
log::trace!(
"Write on {:?} triggered update on {:?}",
self.inner.value,
scope_id
);
(inner.update_any)(scope_id);
}
}
let subscribers = {
let self_read = self.inner.read();
let mut effects = self_read.effect_subscribers.borrow_mut();
std::mem::take(&mut *effects)
};
for effect in subscribers {
log::trace!(
"Write on {:?} triggered effect {:?}",
self.inner.value,
effect
);
effect.try_run();
}
}
pub fn set(&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> Signal<T> {
pub fn value(&self) -> T {
self.read().clone()
}
}
impl<T: 'static> PartialEq for Signal<T> {
fn eq(&self, other: &Self) -> bool {
self.inner == other.inner
}
}
struct SignalSubscriberDrop<T: 'static> {
signal: Signal<T>,
}
impl<T: 'static> Drop for SignalSubscriberDrop<T> {
fn drop(&mut self) {
self.signal.update_subscribers();
}
}
pub struct Write<'a, T: 'static, I: 'static = T> {
write: RefMut<'a, T>,
signal: SignalSubscriberDrop<I>,
}
impl<'a, T: 'static, I: 'static> Write<'a, T, I> {
pub fn map<O>(myself: Self, f: impl FnOnce(&mut T) -> &mut O) -> Write<'a, O, I> {
let Self { write, signal } = myself;
Write {
write: RefMut::map(write, f),
signal,
}
}
pub fn filter_map<O>(
myself: Self,
f: impl FnOnce(&mut T) -> Option<&mut O>,
) -> Option<Write<'a, O, I>> {
let Self { write, signal } = myself;
let write = RefMut::filter_map(write, f).ok();
write.map(|write| Write {
write,
signal: signal,
})
}
}
impl<'a, T: 'static> Deref for Write<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&*self.write
}
}
impl<T> DerefMut for Write<'_, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut *self.write
}
}
pub struct ReadOnlySignal<T: 'static> {
inner: Signal<T>,
}
impl<T: 'static> ReadOnlySignal<T> {
pub fn new(signal: Signal<T>) -> Self {
Self { inner: signal }
}
pub fn origin_scope(&self) -> ScopeId {
self.inner.origin_scope()
}
pub fn read(&self) -> Ref<T> {
self.inner.read()
}
pub fn with<O>(&self, f: impl FnOnce(&T) -> O) -> O {
self.inner.with(f)
}
}
impl<T: Clone + 'static> ReadOnlySignal<T> {
pub fn value(&self) -> T {
self.read().clone()
}
}
impl<T: 'static> PartialEq for ReadOnlySignal<T> {
fn eq(&self, other: &Self) -> bool {
self.inner == other.inner
}
}