mod part_state;
mod prior_op;
mod stateful;
mod watcher;
use std::{convert::Infallible, ops::DerefMut};
pub mod state_cell;
pub use part_state::*;
pub use prior_op::*;
use rxrust::observable::boxed::LocalBoxedObservableClone;
use smallvec::SmallVec;
pub use state_cell::*;
pub use stateful::*;
pub use watcher::*;
use crate::prelude::*;
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct PartialId(Option<CowArc<str>>);
pub trait StateReader: 'static {
type Value: ?Sized;
type Reader: StateReader<Value = Self::Value>
where
Self: Sized;
fn read(&self) -> ReadRef<'_, Self::Value>;
fn clone_boxed_reader(&self) -> Box<dyn StateReader<Value = Self::Value>>;
fn clone_reader(&self) -> Self::Reader
where
Self: Sized;
#[inline]
fn part_reader<U: ?Sized, F>(&self, map: F) -> PartReader<Self::Reader, F>
where
F: Fn(&Self::Value) -> PartRef<U> + Clone,
Self: Sized,
{
PartReader { origin: self.clone_reader(), part_map: map }
}
fn try_into_value(self) -> Result<Self::Value, Self>
where
Self: Sized,
Self::Value: Sized,
{
Err(self)
}
}
pub trait StateWatcher: StateReader {
type Watcher: StateWatcher<Value = Self::Value>
where
Self: Sized;
fn into_reader(self) -> Result<Self::Reader, Self>
where
Self: Sized;
fn modifies(&self) -> LocalBoxedObservableClone<'static, ModifyInfo, Infallible> {
self
.raw_modifies()
.filter(|s| s.contains(ModifyEffect::DATA))
.box_it_clone()
}
fn raw_modifies(&self) -> LocalBoxedObservableClone<'static, ModifyInfo, Infallible>;
fn clone_boxed_watcher(&self) -> Box<dyn StateWatcher<Value = Self::Value>>;
fn clone_watcher(&self) -> Self::Watcher
where
Self: Sized;
fn part_watcher<U: ?Sized, F>(&self, map: F) -> Watcher<PartReader<Self::Reader, F>>
where
F: Fn(&Self::Value) -> PartRef<U> + Clone,
Self: Sized,
{
let reader = self.part_reader(map);
Watcher::new(reader, self.raw_modifies())
}
}
pub trait StateWriter: StateWatcher {
fn write(&self) -> WriteRef<'_, Self::Value>;
fn silent(&self) -> WriteRef<'_, Self::Value>;
fn shallow(&self) -> WriteRef<'_, Self::Value>;
fn clone_boxed_writer(&self) -> Box<dyn StateWriter<Value = Self::Value>>;
fn clone_writer(&self) -> Self
where
Self: Sized;
fn part_writer<V: ?Sized + 'static, M>(&self, id: PartialId, part_map: M) -> PartWriter<Self, M>
where
M: Fn(&mut Self::Value) -> PartMut<V> + Clone + 'static,
Self: Sized,
{
PartWriter { origin: self.clone_writer(), part_map, id, include_partial: false }
}
fn map_writer<V: ?Sized + 'static, M>(&self, part_map: M) -> PartWriter<Self, M>
where
M: Fn(&mut Self::Value) -> PartMut<V> + Clone + 'static,
Self: Sized,
{
self.part_writer(PartialId::any(), part_map)
}
fn include_partial_writers(&mut self, include: bool);
fn scope_path(&self) -> SmallVec<[PartialId; 1]>;
}
pub struct WriteRef<'a, V: ?Sized> {
value: ValueMutRef<'a, V>,
notify_guard: WriteRefNotifyGuard<'a>,
}
struct WriteRefNotifyGuard<'a> {
info: &'a Rc<WriterInfo>,
modify_effect: ModifyEffect,
modified: bool,
path: SmallVec<[PartialId; 1]>,
}
impl PartialId {
const ANY: PartialId = PartialId(None);
pub fn new(str_id: CowArc<str>) -> Self { Self::from(str_id) }
}
impl<'a, V: ?Sized + 'a> WriteRef<'a, V> {
fn new(
value: ValueMutRef<'a, V>, info: &'a Rc<WriterInfo>, path: SmallVec<[PartialId; 1]>,
modify_effect: ModifyEffect,
) -> Self {
let notify_guard = WriteRefNotifyGuard { info, modify_effect, path, modified: false };
WriteRef { value, notify_guard }
}
pub fn silent(self) -> WriteRef<'a, V> { self.with_modify_effect(ModifyEffect::DATA) }
pub fn shallow(self) -> WriteRef<'a, V> { self.with_modify_effect(ModifyEffect::FRAMEWORK) }
pub fn map<U: ?Sized, M>(orig: WriteRef<'a, V>, part_map: M) -> WriteRef<'a, U>
where
M: Fn(&mut V) -> PartMut<U>,
{
let WriteRef { value, mut notify_guard } = orig;
notify_guard.notify();
let value = ValueMutRef::map(value, part_map);
WriteRef { value, notify_guard }
}
pub fn filter_map<U: ?Sized, M>(
mut orig: WriteRef<'a, V>, part_map: M,
) -> Result<WriteRef<'a, U>, Self>
where
M: Fn(&mut V) -> Option<PartMut<U>>,
{
match part_map(&mut orig.value).map(|v| v.inner) {
Some(part) => {
let WriteRef { value, mut notify_guard } = orig;
notify_guard.notify();
let ValueMutRef { inner, borrow, mut origin_store } = value;
origin_store.add(inner);
Ok(WriteRef { value: ValueMutRef { origin_store, inner: part, borrow }, notify_guard })
}
None => Err(orig),
}
}
#[inline]
pub fn forget_modifies(&mut self) -> bool {
std::mem::replace(&mut self.notify_guard.modified, false)
}
fn with_modify_effect(mut self, modify_effect: ModifyEffect) -> WriteRef<'a, V> {
self.notify_guard.notify();
self.notify_guard.modify_effect = modify_effect;
self
}
}
impl<'a> WriteRefNotifyGuard<'a> {
fn notify(&mut self) {
let Self { info, modify_effect, modified, path } = self;
if !*modified {
return;
}
let batched_modifies = &info.batched_modifies;
if batched_modifies.get().is_empty() && !modify_effect.is_empty() {
batched_modifies.set(*modify_effect);
AppCtx::data_changed(path.clone(), info.clone());
} else {
batched_modifies.set(*modify_effect | batched_modifies.get());
}
*modified = false;
}
}
impl PartialId {
pub fn any() -> Self { Self(None) }
}
impl<'a, W: ?Sized> Deref for WriteRef<'a, W> {
type Target = W;
#[track_caller]
#[inline]
fn deref(&self) -> &Self::Target { self.value.deref() }
}
impl<'a, W: ?Sized> DerefMut for WriteRef<'a, W> {
#[track_caller]
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
self.notify_guard.modified = true;
self.value.deref_mut()
}
}
impl<'a> Drop for WriteRefNotifyGuard<'a> {
fn drop(&mut self) { self.notify(); }
}
impl<V: ?Sized + 'static> StateReader for Box<dyn StateReader<Value = V>> {
type Value = V;
type Reader = Self;
#[inline]
fn read(&self) -> ReadRef<'_, V> { (**self).read() }
#[inline]
fn clone_boxed_reader(&self) -> Box<dyn StateReader<Value = Self::Value>> {
(**self).clone_boxed_reader()
}
fn clone_reader(&self) -> Self::Reader { self.clone_boxed_reader() }
}
impl<V: ?Sized + 'static> StateReader for Box<dyn StateWatcher<Value = V>> {
type Value = V;
type Reader = Box<dyn StateReader<Value = V>>;
#[inline]
fn read(&self) -> ReadRef<'_, V> { (**self).read() }
#[inline]
fn clone_boxed_reader(&self) -> Box<dyn StateReader<Value = Self::Value>> {
(**self).clone_boxed_reader()
}
#[inline]
fn clone_reader(&self) -> Self::Reader { self.clone_boxed_reader() }
}
impl<V: ?Sized + 'static> StateWatcher for Box<dyn StateWatcher<Value = V>> {
type Watcher = Box<dyn StateWatcher<Value = V>>;
#[inline]
fn into_reader(self) -> Result<Self::Reader, Self> { Err(self) }
#[inline]
fn raw_modifies(&self) -> LocalBoxedObservableClone<'static, ModifyInfo, Infallible> {
(**self).raw_modifies()
}
#[inline]
fn clone_boxed_watcher(&self) -> Box<dyn StateWatcher<Value = Self::Value>> {
(**self).clone_boxed_watcher()
}
#[inline]
fn clone_watcher(&self) -> Self::Watcher { self.clone_boxed_watcher() }
}
impl<V: ?Sized + 'static> StateReader for Box<dyn StateWriter<Value = V>> {
type Value = V;
type Reader = Box<dyn StateReader<Value = V>>;
#[inline]
fn read(&self) -> ReadRef<'_, V> { (**self).read() }
#[inline]
fn clone_boxed_reader(&self) -> Box<dyn StateReader<Value = Self::Value>> {
(**self).clone_boxed_reader()
}
#[inline]
fn clone_reader(&self) -> Self::Reader { self.clone_boxed_reader() }
}
impl<V: ?Sized + 'static> StateWatcher for Box<dyn StateWriter<Value = V>> {
type Watcher = Box<dyn StateWatcher<Value = Self::Value>>;
#[inline]
fn into_reader(self) -> Result<Self::Reader, Self> { Err(self) }
#[inline]
fn raw_modifies(&self) -> LocalBoxedObservableClone<'static, ModifyInfo, Infallible> {
(**self).raw_modifies()
}
#[inline]
fn clone_boxed_watcher(&self) -> Box<dyn StateWatcher<Value = Self::Value>> {
(**self).clone_boxed_watcher()
}
#[inline]
fn clone_watcher(&self) -> Self::Watcher { self.clone_boxed_watcher() }
}
impl<V: ?Sized + 'static> StateWriter for Box<dyn StateWriter<Value = V>> {
#[inline]
fn write(&self) -> WriteRef<'_, Self::Value> { (**self).write() }
#[inline]
fn silent(&self) -> WriteRef<'_, Self::Value> { (**self).silent() }
#[inline]
fn shallow(&self) -> WriteRef<'_, Self::Value> { (**self).shallow() }
#[inline]
fn clone_boxed_writer(&self) -> Box<dyn StateWriter<Value = Self::Value>> {
(**self).clone_boxed_writer()
}
#[inline]
fn clone_writer(&self) -> Self { self.clone_boxed_writer() }
#[inline]
fn include_partial_writers(&mut self, include: bool) { (**self).include_partial_writers(include) }
fn scope_path(&self) -> SmallVec<[PartialId; 1]> { (**self).scope_path() }
}
impl<T: Into<CowArc<str>>> From<T> for PartialId {
#[inline]
fn from(v: T) -> Self { Self(Some(v.into())) }
}
#[cfg(test)]
mod tests {
use std::cell::Cell;
use super::*;
use crate::reset_test_env;
#[cfg(target_arch = "wasm32")]
use crate::test_helper::wasm_bindgen_test;
struct Origin {
a: i32,
b: i32,
}
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
#[test]
fn map_same_with_origin() {
reset_test_env!();
let origin = Stateful::new(Origin { a: 0, b: 0 });
let map_state = origin.part_writer(PartialId::any(), |v| PartMut::new(&mut v.b));
let track_origin = Rc::new(Cell::new(0));
let track_map = Rc::new(Cell::new(0));
let c_origin = track_origin.clone();
origin.modifies().subscribe(move |_| {
c_origin.set(c_origin.get() + 1);
});
let c_map = track_map.clone();
map_state.modifies().subscribe(move |_| {
c_map.set(c_map.get() + 1);
});
origin.write().a = 1;
AppCtx::run_until_stalled();
assert_eq!(track_origin.get(), 1);
assert_eq!(track_map.get(), 1);
*map_state.write() = 1;
AppCtx::run_until_stalled();
assert_eq!(track_origin.get(), 2);
assert_eq!(track_map.get(), 2);
}
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
#[test]
fn split_notify() {
reset_test_env!();
let mut origin = Stateful::new(Origin { a: 0, b: 0 });
origin.include_partial_writers(true);
let split_a = origin.part_writer("a".into(), |v| PartMut::new(&mut v.a));
let split_b = origin.part_writer("b".into(), |v| PartMut::new(&mut v.b));
let track_origin = Rc::new(Cell::new(0));
let track_split_a = Rc::new(Cell::new(0));
let track_split_b = Rc::new(Cell::new(0));
let c_origin = track_origin.clone();
origin.modifies().subscribe(move |s| {
c_origin.set(c_origin.get() + s.effect.bits());
});
let c_split_a = track_split_a.clone();
split_a.modifies().subscribe(move |s| {
c_split_a.set(c_split_a.get() + s.effect.bits());
});
let c_split_b = track_split_b.clone();
split_b.modifies().subscribe(move |s| {
c_split_b.set(c_split_b.get() + s.effect.bits());
});
*split_a.write() = 0;
AppCtx::run_until_stalled();
assert_eq!(track_origin.get(), ModifyEffect::BOTH.bits());
assert_eq!(track_split_a.get(), ModifyEffect::BOTH.bits());
assert_eq!(track_split_b.get(), 0);
track_origin.set(0);
track_split_a.set(0);
*split_b.write() = 0;
AppCtx::run_until_stalled();
assert_eq!(track_origin.get(), ModifyEffect::BOTH.bits());
assert_eq!(track_split_b.get(), ModifyEffect::BOTH.bits());
assert_eq!(track_split_a.get(), 0);
track_origin.set(0);
track_split_b.set(0);
origin.write().a = 0;
AppCtx::run_until_stalled();
assert_eq!(track_origin.get(), ModifyEffect::BOTH.bits());
assert_eq!(track_split_b.get(), 0);
assert_eq!(track_split_a.get(), 0);
}
struct C;
impl Compose for C {
fn compose(_: impl StateWriter<Value = Self>) -> Widget<'static> { Void.into_widget() }
}
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
#[test]
fn state_writer_compose_builder() {
reset_test_env!();
let _state_compose_widget = fn_widget! {
Stateful::new(C)
};
let _sateful_compose_widget = fn_widget! {
Stateful::new(C)
};
let _writer_compose_widget = fn_widget! {
Stateful::new(C).clone_writer()
};
let _part_writer_compose_widget = fn_widget! {
Stateful::new((C, 0))
.part_writer(PartialId::any(), |v| PartMut::new(&mut v.0))
};
let _part_writer_compose_widget = fn_widget! {
Stateful::new((C, 0))
.part_writer("C".into(), |v| PartMut::new(&mut v.0))
};
}
struct CC;
impl<'c> ComposeChild<'c> for CC {
type Child = Option<Widget<'c>>;
fn compose_child(_: impl StateWriter<Value = Self>, _: Self::Child) -> Widget<'c> {
Void.into_widget()
}
}
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
#[test]
fn state_writer_compose_child_builder() {
reset_test_env!();
let _state_with_child = fn_widget! {
let cc = Stateful::new(CC);
@(cc) { @{ Void } }
};
let _state_without_child = fn_widget! {
Stateful::new(CC)
};
let _stateful_with_child = fn_widget! {
let cc = Stateful::new(CC);
@(cc) { @{ Void } }
};
let _stateful_without_child = fn_widget! {
Stateful::new(CC)
};
let _writer_with_child = fn_widget! {
let cc = Stateful::new(CC).clone_writer();
@(cc) { @{ Void } }
};
let _writer_without_child = fn_widget! {
Stateful::new(CC).clone_writer()
};
let _part_writer_with_child = fn_widget! {
let w = Stateful::new((CC, 0))
.part_writer(PartialId::any(), |v| PartMut::new(&mut v.0));
@(w) { @{ Void } }
};
let _part_writer_without_child = fn_widget! {
Stateful::new((CC, 0))
.part_writer(PartialId::any(), |v| PartMut::new(&mut v.0))
};
let _part_writer_with_child = fn_widget! {
let w = Stateful::new((CC, 0))
.part_writer("".into(), |v| PartMut::new(&mut v.0));
@(w) { @{ Void } }
};
let _part_writer_without_child = fn_widget! {
Stateful::new((CC, 0))
.part_writer("".into(), |v| PartMut::new(&mut v.0))
};
}
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
#[test]
fn state_reader_builder() {
reset_test_env!();
let _stateful_render_widget = fn_widget! {
Stateful::new(Void)
};
let _writer_render_widget = fn_widget! {
Stateful::new(Void).clone_writer()
};
let _part_reader_render_widget = fn_widget! {
Stateful::new((Void, 0)).part_reader(|v| PartRef::new(&v.0))
};
let _part_writer_render_widget = fn_widget! {
Stateful::new((Void, 0))
.part_writer(PartialId::any(), |v| PartMut::new(&mut v.0))
};
let _part_writer_render_widget = fn_widget! {
Stateful::new((Void, 0))
.part_writer("".into(), |v| PartMut::new(&mut v.0))
};
}
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
#[test]
fn trait_object_part_data() {
reset_test_env!();
let s = Stateful::new(0);
let m = s.part_writer("0".into(), |v| PartMut::new(v as &mut dyn Any));
let v: ReadRef<dyn Any> = m.read();
assert_eq!(*v.downcast_ref::<i32>().unwrap(), 0);
let s = s.part_writer(PartialId::any(), |v| PartMut::new(v as &mut dyn Any));
let v: ReadRef<dyn Any> = s.read();
assert_eq!(*v.downcast_ref::<i32>().unwrap(), 0);
}
}