mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-23 04:33:06 +00:00
create read only signal
This commit is contained in:
parent
6ca170453b
commit
646c161c7d
7 changed files with 287 additions and 236 deletions
|
@ -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" }
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(¤t_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::*;
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
244
packages/signals/src/signal.rs
Normal file
244
packages/signals/src/signal.rs
Normal 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(¤t_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
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue