From 8164ff17ec81a5a7b3515a176e4635c7e9abc592 Mon Sep 17 00:00:00 2001 From: Adoo Date: Sun, 28 Apr 2024 22:16:00 +0800 Subject: [PATCH] =?UTF-8?q?wfeat(core):=20=F0=9F=8E=B8=20add=20`PartState`?= =?UTF-8?q?=20to=20partial=20state=20even=20if=20it's=20a=20value=20type;?= =?UTF-8?q?=20closes=20#521=20#507?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 3 + core/src/animation/stagger.rs | 33 +++- core/src/context/app_ctx.rs | 1 + core/src/render_helper.rs | 6 + core/src/state.rs | 283 ++++++++++++++--------------- core/src/state/map_state.rs | 215 +++++++++++++--------- core/src/state/splitted_state.rs | 168 ++++++----------- core/src/state/state_cell.rs | 213 ++++++++++++++++++++++ core/src/state/stateful.rs | 57 +++--- core/src/state/watcher.rs | 3 - core/src/widget.rs | 7 + docs/en/get_started/quick_start.md | 20 +- docs/zh/get_started/quick_start.md | 16 +- examples/todos/src/ui.rs | 15 +- macros/src/lib.rs | 19 -- macros/src/symbol_process.rs | 9 - macros/src/writer_map_macro.rs | 106 ----------- tests/rdl_macro_test.rs | 8 +- themes/material/src/lib.rs | 27 +-- themes/material/src/ripple.rs | 7 +- widgets/src/text_field.rs | 12 +- 21 files changed, 641 insertions(+), 587 deletions(-) create mode 100644 core/src/state/state_cell.rs delete mode 100644 macros/src/writer_map_macro.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c6cb39bd..739568076 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ Please only add new entries below the [Unreleased](#unreleased---releasedate) he ### Features +- **core**: The split functions in `StateReader::map_reader`, `StateWriter::map_writer`, and `StateWriter::split_writer` no longer need to return a reference. (#568 @M-Adoo) - **core**: Introduced `StateWatcher` for watching state modifies, which was previously the responsibility of `StateReader`. This results in a cleaner and more compact `StateReader` implementation. (#556, @M-Adoo) - **gpu**: Introduced `GPUBackendImpl::max_textures_per_draw` to set a limit on textures per draw phase (#562 @M-Adoo) @@ -41,6 +42,8 @@ Please only add new entries below the [Unreleased](#unreleased---releasedate) he ### Breaking +- **macros**: removed `map_writer!` and `split_writer!` macros. (#568, @M-Adoo) +- **ribir**: `StateWriter::map_writer` and `StateWriter::split_writer` now only require a writer split function, enhancing both reader and writer split operations. (#568, @M-Adoo) - **core**: The `StateReader` no longer supports watching its modifications. Use the `StateWatcher` trait instead for this functionality. (#556 @M-Adoo) - **painter**: Changes to `BackendPainter` APIs. This only affects you if you've implemented a custom painter. (#562 @M-Adoo) diff --git a/core/src/animation/stagger.rs b/core/src/animation/stagger.rs index 3b54f885f..605821970 100644 --- a/core/src/animation/stagger.rs +++ b/core/src/animation/stagger.rs @@ -1,6 +1,7 @@ -//! A stagger animation is a series of animations that run one by one. -//! The next animation will start after a delay after the previous animation -//! starts and not care about if the previous animation ends. +//! A stagger animation consists of a sequence of animations that execute +//! consecutively. Each subsequent animation commences following a delay from +//! the start of the preceding animation, regardless of whether the preceding +//! animation has concluded. //! //! # Example //! @@ -22,14 +23,21 @@ //! //! let mut first = @Text { text: "first" }; //! let mut second = @Text { text: "second" }; +//! let first_opacity = first +//! .get_opacity_widget() +//! .map_writer(|w| PartData::from_ref_mut(&mut w.opacity)); +//! let second_opacity = second +//! .get_opacity_widget() +//! .map_writer(|w| PartData::from_ref_mut(&mut w.opacity)); +//! //! //! let first_fade_in = @Animate { //! transition: transitions::EASE_IN.of(ctx!()), -//! state: map_writer!($first.opacity), +//! state: first_opacity, //! }; //! //! stagger.write().push_animation(first_fade_in); -//! stagger.write().push_state(map_writer!($second.opacity), 0., ctx!()); +//! stagger.write().push_state(second_opacity, 0., ctx!()); //! //! @Column { //! on_mounted: move |_| stagger.run(), @@ -225,15 +233,18 @@ mod tests { fn_widget! { let stagger = Stagger::new(Duration::from_millis(100), transitions::EASE_IN.of(ctx!())); let mut mock_box = @MockBox { size: Size::new(100., 100.) }; + let opacity = mock_box + .get_opacity_widget() + .map_writer(|w| PartData::from_ref_mut(&mut w.opacity)); let animate = @Animate { transition: transitions::EASE_IN.of(ctx!()), - state: map_writer!($mock_box.opacity), + state: opacity, from: 0., }; stagger.write().push_animation(animate); stagger.write().push_state( - map_writer!($mock_box.size), + mock_box.map_writer(|w| PartData::from_ref_mut(&mut w.size)), Size::new(200., 200.), ctx!() ); @@ -260,7 +271,13 @@ mod tests { let c_stagger = stagger.clone_writer().into_inner(); let w = fn_widget! { let mut mock_box = @MockBox { size: Size::new(100., 100.) }; - $stagger.write().push_state(map_writer!($mock_box.opacity), 0., ctx!()); + $stagger.write().push_state( + mock_box + .get_opacity_widget() + .map_writer(|w| PartData::from_ref_mut(&mut w.opacity)), + 0., + ctx!() + ); stagger.run(); mock_box diff --git a/core/src/context/app_ctx.rs b/core/src/context/app_ctx.rs index 42b72dd36..aa7729d81 100644 --- a/core/src/context/app_ctx.rs +++ b/core/src/context/app_ctx.rs @@ -403,6 +403,7 @@ impl Drop for AppCtxScopeGuard { fn drop(&mut self) { // Safety: this guard guarantee only one thread can access the `AppCtx`. unsafe { + AppCtx::shared_mut().windows.borrow_mut().clear(); APP_CTX = None; INIT_THREAD_ID = None; APP_CTX_INIT = Once::new(); diff --git a/core/src/render_helper.rs b/core/src/render_helper.rs index 1b824a912..73ea05332 100644 --- a/core/src/render_helper.rs +++ b/core/src/render_helper.rs @@ -1,6 +1,7 @@ use std::cell::RefCell; use ribir_algo::Sc; +use state_cell::StateCell; use crate::prelude::*; @@ -60,6 +61,11 @@ impl RenderTarget for RefCell { fn proxy(&self, f: impl FnOnce(&Self::Target) -> V) -> V { f(&*self.borrow()) } } +impl RenderTarget for StateCell { + type Target = R; + fn proxy(&self, f: impl FnOnce(&Self::Target) -> V) -> V { f(&*self.read()) } +} + impl RenderTarget for Sc { type Target = R::Target; fn proxy(&self, f: impl FnOnce(&Self::Target) -> V) -> V { self.deref().proxy(f) } diff --git a/core/src/state.rs b/core/src/state.rs index e7b21e766..189bbe8d1 100644 --- a/core/src/state.rs +++ b/core/src/state.rs @@ -4,16 +4,19 @@ mod splitted_state; mod stateful; mod watcher; use std::{ - cell::{Cell, Ref, RefCell, RefMut, UnsafeCell}, + cell::{Cell, UnsafeCell}, convert::Infallible, mem::MaybeUninit, ops::DerefMut, }; +pub mod state_cell; pub use map_state::*; pub use prior_op::*; use rxrust::ops::box_it::{BoxOp, CloneableBoxOp}; pub use splitted_state::*; +pub use state_cell::PartData; +use state_cell::{StateCell, ValueMutRef, ValueRef}; pub use stateful::*; pub use watcher::*; @@ -22,7 +25,7 @@ use crate::prelude::*; /// The `StateReader` trait allows for reading, clone and map the state. pub trait StateReader: 'static { /// The value type of this state. - type Value: ?Sized; + type Value; /// The origin state type that this state map or split from . Otherwise /// return itself. type OriginReader: StateReader; @@ -36,26 +39,21 @@ pub trait StateReader: 'static { /// value. The return reader is just a shortcut to access part of the origin /// reader. /// - /// ## Undefined Behavior - /// - /// As `MapReader` is a shortcut to access part of the origin writer, it's - /// assume its part of data is always valid. If the data is invalid, and you - /// use `MapReader` to access it, it's undefined behavior. + /// Note, `MapReader` is a shortcut to access a portion of the original + /// reader. It's assumed that the `map` function returns a part of the + /// original data, not a cloned portion. Otherwise, the returned reader will + /// not respond to state changes. #[inline] fn map_reader(&self, map: F) -> MapReader where - U: ?Sized, - F: Fn(&Self::Value) -> &U + Clone, + F: Fn(&Self::Value) -> PartData + Clone, { - MapReader { origin: self.clone_reader(), map } + MapReader { origin: self.clone_reader(), part_map: map } } /// Return the origin reader that this state map or split from . Otherwise /// return itself. fn origin_reader(&self) -> &Self::OriginReader; - /// Return the time stamp of the last modifies of this state. - fn time_stamp(&self) -> Instant; - /// try convert this state into the value, if there is no other share this /// state, otherwise return an error with self. fn try_into_value(self) -> Result @@ -115,65 +113,47 @@ pub trait StateWriter: StateWatcher { /// /// ##Notice /// - /// Your `mut_map` function will accept a mutable reference, but you should - /// not modify it, just return a mutable reference to a part of it. Because - /// Ribir assume you will not modify the origin data in it. If you modify the - /// origin data in it, no downstream will be notified. - /// - /// When you remove/invalid some data across the return writer, you should be - /// careful. Because that part may be in another state reader or writer, if - /// you invalid it, other state reader or writer will be invalid too. - /// - /// ## Panic - /// - /// 1. When the origin writer modifies, the return writer will be invalid, - /// access its value will panic. - /// 2. If modifies the split part data causing any data to be invalid, it may - /// panic. - + /// The `mut_map` function accepts a mutable reference, but it should not be + /// modified. Instead, return a mutable reference to a part of it. Ribir + /// uses this to access a portion of the original data, so a read operation + /// may also call it. Ribir assumes that the original data will not be + /// modified within this function. Therefore, if the original data is + /// modified, no downstream will be notified. #[inline] - fn split_writer(&self, map: R, mut_map: W) -> SplittedWriter + fn split_writer(&self, mut_map: W) -> SplittedWriter where - V: ?Sized, - R: Fn(&Self::Value) -> &V + Clone + 'static, - W: Fn(&mut Self::Value) -> &mut V + Clone + 'static, + W: Fn(&mut Self::Value) -> PartData + Clone + 'static, { - SplittedWriter::new(self.clone_writer(), map, mut_map) + SplittedWriter::new(self.clone_writer(), mut_map) } /// Return a new writer by applying a function to the contained value. The /// return writer is just a shortcut to access part of the origin writer. /// - /// ## Notice - /// - /// Your `mut_map` function will accept a mutable reference, but you should - /// not modify it, just return a mutable reference to a part of it. Because - /// Ribir assume you will not modify the origin data in it. If you modify the - /// origin data in it, no downstream will be notified. - /// - /// ## Undefined Behavior + /// ##Notice /// - /// As `MapWriter` is a shortcut to access part of the origin writer, it's - /// assume its part of data is always valid. If the data is invalid, and you - /// use `MapWriter` to access it, it's undefined behavior. + /// The `mut_map` function accepts a mutable reference, but it should not be + /// modified. Instead, return a mutable reference to a part of it. Ribir + /// uses this to access a portion of the original data, so a read operation + /// may also call it. Ribir assumes that the original data will not be + /// modified within this function. Therefore, if the original data is + /// modified, no downstream will be notified. #[inline] - fn map_writer(&self, map: R, mut_map: W) -> MapWriter + fn map_writer(&self, part_map: M) -> MapWriter where - V: ?Sized, - R: Fn(&Self::Value) -> &V + Clone, - W: Fn(&mut Self::Value) -> &mut V + Clone, + M: Fn(&mut Self::Value) -> PartData + Clone, { let origin = self.clone_writer(); - MapWriter { origin, map, mut_map } + MapWriter { origin, part_map } } } /// Wraps a borrowed reference to a value in a state. /// A wrapper type for an immutably borrowed value from a `StateReader`. -pub struct ReadRef<'a, V: ?Sized>(Ref<'a, V>); +pub struct ReadRef<'a, V>(ValueRef<'a, V>); -pub struct WriteRef<'a, V: ?Sized> { - value: Option>, +pub struct WriteRef<'a, V> { + value: ValueMutRef<'a, V>, control: &'a dyn WriterControl, modify_scope: ModifyScope, modified: bool, @@ -183,12 +163,11 @@ pub struct WriteRef<'a, V: ?Sized> { pub struct State(pub(crate) UnsafeCell>); pub(crate) enum InnerState { - Data(RefCell), + Data(StateCell), Stateful(Stateful), } trait WriterControl { - fn last_modified_stamp(&self) -> &Cell; fn batched_modifies(&self) -> &Cell; fn notifier(&self) -> &Notifier; fn dyn_clone(&self) -> Box; @@ -201,7 +180,7 @@ impl StateReader for State { fn read(&self) -> ReadRef { match self.inner_ref() { - InnerState::Data(w) => ReadRef::new(w.borrow()), + InnerState::Data(w) => ReadRef::new(w.read()), InnerState::Stateful(w) => w.read(), } } @@ -212,9 +191,6 @@ impl StateReader for State { #[inline] fn origin_reader(&self) -> &Self::OriginReader { self } - #[inline] - fn time_stamp(&self) -> Instant { self.as_stateful().time_stamp() } - fn try_into_value(self) -> Result { match self.0.into_inner() { InnerState::Data(w) => Ok(w.into_inner()), @@ -255,12 +231,12 @@ impl State { State(UnsafeCell::new(InnerState::Stateful(stateful))) } - pub fn value(value: W) -> Self { State(UnsafeCell::new(InnerState::Data(RefCell::new(value)))) } + pub fn value(value: W) -> Self { State(UnsafeCell::new(InnerState::Data(StateCell::new(value)))) } pub fn as_stateful(&self) -> &Stateful { match self.inner_ref() { InnerState::Data(w) => { - assert!(w.try_borrow_mut().is_ok()); + assert!(w.is_unused()); let mut uninit: MaybeUninit<_> = MaybeUninit::uninit(); // Safety: we already check there is no other reference to the state data. @@ -290,15 +266,72 @@ impl State { } } -impl<'a, V: ?Sized> ReadRef<'a, V> { - pub(crate) fn new(r: Ref<'a, V>) -> ReadRef<'a, V> { ReadRef(r) } +impl<'a, V> ReadRef<'a, V> { + pub(crate) fn new(r: ValueRef<'a, V>) -> ReadRef<'a, V> { ReadRef(r) } + + /// Make a new `ReadRef` by mapping the value of the current `ReadRef`. + pub fn map(mut r: ReadRef<'a, V>, f: impl FnOnce(&V) -> PartData) -> ReadRef<'a, U> { + ReadRef(ValueRef { value: f(&mut r.0.value), borrow: r.0.borrow }) + } + + /// Split the current `ReadRef` into two `ReadRef`s by mapping the value to + /// two parts. + pub fn map_split( + orig: ReadRef<'a, V>, f: impl FnOnce(&V) -> (PartData, PartData), + ) -> (ReadRef<'a, U>, ReadRef<'a, W>) { + let (a, b) = f(&*orig); + let borrow = orig.0.borrow.clone(); - pub(crate) fn map(r: ReadRef<'a, V>, f: impl FnOnce(&V) -> &U) -> ReadRef<'a, U> { - ReadRef(Ref::map(r.0, f)) + (ReadRef(ValueRef { value: a, borrow: borrow.clone() }), ReadRef(ValueRef { value: b, borrow })) + } + + pub(crate) fn mut_as_ref_map( + orig: ReadRef<'a, V>, f: impl FnOnce(&mut V) -> PartData, + ) -> ReadRef<'a, U> { + let ValueRef { value, borrow } = orig.0; + let value = match value { + PartData::PartRef(mut ptr) => unsafe { + // Safety: This method is used to map a state to a part of it. Although a `&mut + // T` is passed to the closure, it is the user's responsibility to + // ensure that the closure does not modify the state. + f(ptr.as_mut()) + }, + PartData::PartData(mut data) => f(&mut data), + }; + + ReadRef(ValueRef { value, borrow }) } } -impl<'a, V: ?Sized> WriteRef<'a, V> { +impl<'a, V> WriteRef<'a, V> { + pub fn map(mut orig: WriteRef<'a, V>, part_map: M) -> WriteRef<'a, U> + where + M: Fn(&mut V) -> PartData, + { + let value = part_map(&mut orig.value); + let borrow = orig.value.borrow.clone(); + let value = ValueMutRef { value, borrow }; + + WriteRef { value, modified: false, modify_scope: orig.modify_scope, control: orig.control } + } + + pub fn map_split( + mut orig: WriteRef<'a, V>, f: F, + ) -> (WriteRef<'a, U1>, WriteRef<'a, U2>) + where + F: FnOnce(&mut V) -> (PartData, PartData), + { + let WriteRef { control, modify_scope, modified, .. } = orig; + let (a, b) = f(&mut *orig.value); + let borrow = orig.value.borrow.clone(); + let a = ValueMutRef { value: a, borrow: borrow.clone() }; + let b = ValueMutRef { value: b, borrow }; + ( + WriteRef { value: a, modified, modify_scope, control }, + WriteRef { value: b, modified, modify_scope, control }, + ) + } + /// Forget all modifies of this reference. So all the modifies occurred on /// this reference before this call will not be notified. Return true if there /// is any modifies on this reference. @@ -306,7 +339,7 @@ impl<'a, V: ?Sized> WriteRef<'a, V> { pub fn forget_modifies(&mut self) -> bool { std::mem::replace(&mut self.modified, false) } } -impl<'a, W: ?Sized> Deref for ReadRef<'a, W> { +impl<'a, W> Deref for ReadRef<'a, W> { type Target = W; #[track_caller] @@ -314,31 +347,23 @@ impl<'a, W: ?Sized> Deref for ReadRef<'a, W> { fn deref(&self) -> &Self::Target { &self.0 } } -impl<'a, W: ?Sized> Deref for WriteRef<'a, W> { +impl<'a, W> Deref for WriteRef<'a, W> { type Target = W; #[track_caller] #[inline] - fn deref(&self) -> &Self::Target { - // Safety: value always exists except in drop method. - unsafe { self.value.as_ref().unwrap_unchecked().deref() } - } + fn deref(&self) -> &Self::Target { self.value.deref() } } -impl<'a, W: ?Sized> DerefMut for WriteRef<'a, W> { +impl<'a, W> DerefMut for WriteRef<'a, W> { #[track_caller] #[inline] fn deref_mut(&mut self) -> &mut Self::Target { self.modified = true; - self - .control - .last_modified_stamp() - .set(Instant::now()); - // Safety: value always exists except in drop method. - unsafe { self.value.as_mut().unwrap_unchecked().deref_mut() } + self.value.deref_mut() } } -impl<'a, W: ?Sized> Drop for WriteRef<'a, W> { +impl<'a, W> Drop for WriteRef<'a, W> { fn drop(&mut self) { let Self { control, modify_scope, modified, .. } = self; if !*modified { @@ -350,13 +375,12 @@ impl<'a, W: ?Sized> Drop for WriteRef<'a, W> { batched_modifies.set(*modify_scope); let control = control.dyn_clone(); - AppCtx::spawn_local(async move { + let _ = AppCtx::spawn_local(async move { let scope = control .batched_modifies() .replace(ModifyScope::empty()); control.notifier().next(scope); - }) - .unwrap(); + }); } else { batched_modifies.set(*modify_scope | batched_modifies.get()); } @@ -448,21 +472,19 @@ where macro_rules! impl_compose_builder { ($name:ident) => { - impl ComposeBuilder for $name + impl ComposeBuilder for $name where W: StateWriter, - RM: Fn(&W::Value) -> &V + Clone + 'static, - WM: Fn(&mut W::Value) -> &mut V + Clone + 'static, + WM: Fn(&mut W::Value) -> PartData + Clone + 'static, V: Compose + 'static, { fn build(self, ctx: &crate::context::BuildCtx) -> Widget { Compose::compose(self).build(ctx) } } - impl ComposeChildBuilder for $name + impl ComposeChildBuilder for $name where W: StateWriter, - RM: Fn(&W::Value) -> &V + Clone + 'static, - WM: Fn(&mut W::Value) -> &mut V + Clone + 'static, + WM: Fn(&mut W::Value) -> PartData + Clone + 'static, V: ComposeChild> + 'static, { #[inline] @@ -493,7 +515,7 @@ mod tests { reset_test_env!(); let origin = State::value(Origin { a: 0, b: 0 }); - let map_state = map_writer!($origin.b); + let map_state = origin.map_writer(|v| PartData::from_ref_mut(&mut v.b)); let track_origin = Sc::new(Cell::new(0)); let track_map = Sc::new(Cell::new(0)); @@ -528,7 +550,7 @@ mod tests { reset_test_env!(); let origin = State::value(Origin { a: 0, b: 0 }); - let split = split_writer!($origin.b); + let split = origin.split_writer(|v| PartData::from_ref_mut(&mut v.b)); let track_origin = Sc::new(Cell::new(0)); let track_split = Sc::new(Cell::new(0)); @@ -558,19 +580,6 @@ mod tests { assert_eq!(track_split.get(), ModifyScope::BOTH.bits()); } - #[test] - #[should_panic] - fn invalid_split_after_origin_modify() { - reset_test_env!(); - - let origin = State::value(Origin { a: 0, b: 0 }); - let split = split_writer!($origin.b); - - origin.write().b = 1; - // invalid split state - *split.write() = 1; - } - struct C; impl Compose for C { @@ -581,6 +590,8 @@ mod tests { #[test] fn state_writer_compose_builder() { + reset_test_env!(); + let _state_compose_widget = fn_widget! { State::value(C) }; @@ -594,12 +605,12 @@ mod tests { }; let _map_writer_compose_widget = fn_widget! { - let s = Stateful::new((C, 0)); - map_writer!($s.0) + Stateful::new((C, 0)) + .map_writer(|v| PartData::from_ref_mut(&mut v.0)) }; let _split_writer_compose_widget = fn_widget! { - let s = Stateful::new((C, 0)); - split_writer!($s.0) + Stateful::new((C, 0)) + .split_writer(|v| PartData::from_ref_mut(&mut v.0)) }; } @@ -613,6 +624,8 @@ mod tests { #[test] fn state_writer_compose_child_builder() { + reset_test_env!(); + let _state_with_child = fn_widget! { let cc = State::value(CC); @$cc { @{ Void } } @@ -641,30 +654,32 @@ mod tests { }; let _map_writer_with_child = fn_widget! { - let s = Stateful::new((CC, 0)); - let w = map_writer!($s.0); + let w = Stateful::new((CC, 0)) + .map_writer(|v| PartData::from_ref_mut(&mut v.0)); @$w { @{ Void } } }; let _map_writer_without_child = fn_widget! { - let s = Stateful::new((CC, 0)); - map_writer!($s.0) + Stateful::new((CC, 0)) + .map_writer(|v| PartData::from_ref_mut(&mut v.0)) }; let _split_writer_with_child = fn_widget! { - let s = Stateful::new((CC, 0)); - let w = split_writer!($s.0); + let w = Stateful::new((CC, 0)) + .split_writer(|v| PartData::from_ref_mut(&mut v.0)); @$w { @{ Void } } }; let _split_writer_without_child = fn_widget! { - let s = Stateful::new((CC, 0)); - split_writer!($s.0) + Stateful::new((CC, 0)) + .split_writer(|v| PartData::from_ref_mut(&mut v.0)) }; } #[test] fn state_reader_builder() { + reset_test_env!(); + let _state_render_widget = fn_widget! { State::value(Void) }; @@ -678,43 +693,17 @@ mod tests { }; let _map_reader_render_widget = fn_widget! { - Stateful::new((Void, 0)).map_reader(|v| &v.0) + Stateful::new((Void, 0)).map_reader(|v| PartData::from_ref(&v.0)) }; let _map_writer_render_widget = fn_widget! { - let s = Stateful::new((Void, 0)); - map_writer!($s.0) + Stateful::new((Void, 0)) + .map_writer(|v| PartData::from_ref_mut(&mut v.0)) }; let _split_writer_render_widget = fn_widget! { - let s = Stateful::new((Void, 0)); - split_writer!($s.0) + Stateful::new((Void, 0)) + .split_writer(|v| PartData::from_ref_mut(&mut v.0)) }; } - - #[test] - fn reader_from_trait() { - reset_test_env!(); - struct A {} - trait T { - fn test(&self); - fn test_mut(&mut self); - } - - impl T for A { - fn test(&self) {} - fn test_mut(&mut self) {} - } - - let a = State::value(A {}); - let split_writer = a.split_writer(|a| a as &dyn T, |a| a as &mut dyn T); - split_writer.read().test(); - split_writer.write().test_mut(); - - let map_writer = a.map_writer(|a| a as &dyn T, |a| a as &mut dyn T); - let map_reader = a.map_reader(|a| a as &dyn T); - map_reader.read().test(); - map_writer.write().test_mut(); - AppCtx::run_until_stalled(); - } } diff --git a/core/src/state/map_state.rs b/core/src/state/map_state.rs index 489909ef9..943c12d65 100644 --- a/core/src/state/map_state.rs +++ b/core/src/state/map_state.rs @@ -1,87 +1,132 @@ -use std::{cell::RefMut, convert::Infallible}; +use std::convert::Infallible; use rxrust::ops::box_it::CloneableBoxOp; -use super::{ModifyScope, ReadRef, StateReader, StateWatcher, StateWriter, WriteRef}; +use super::{ + state_cell::PartData, ModifyScope, ReadRef, StateReader, StateWatcher, StateWriter, WriteRef, +}; use crate::{ context::BuildCtx, render_helper::{RenderProxy, RenderTarget}, - ticker::Instant, widget::{Render, RenderBuilder, Widget}, }; /// A state reader that map a reader to another by applying a function on the -/// value. This reader is the same reader with the origin reader, It's also have -/// the same modifier with the origin state. +/// value. This reader is the same reader with the origin reader. pub struct MapReader { pub(super) origin: S, - pub(super) map: F, + pub(super) part_map: F, } -pub struct MapWriter { +pub struct MapWriter { pub(super) origin: W, - pub(super) map: RM, - pub(super) mut_map: WM, + pub(super) part_map: WM, } -macro_rules! impl_reader_trivial_methods { - () => { - type Value = V; - type OriginReader = S; - type Reader = MapReader; - - #[inline] - fn read(&self) -> ReadRef { ReadRef::map(self.origin.read(), &self.map) } - - #[inline] - fn clone_reader(&self) -> Self::Reader { - MapReader { origin: self.origin.clone_reader(), map: self.map.clone() } - } - - #[inline] - fn origin_reader(&self) -> &Self::OriginReader { &self.origin } - - #[inline] - fn time_stamp(&self) -> Instant { self.origin.time_stamp() } - - #[inline] - fn try_into_value(self) -> Result - where - Self::Value: Sized, - { - Err(self) - } - }; +pub struct MapWriterAsReader { + pub(super) origin: W, + pub(super) part_map: M, +} + +impl StateReader for MapReader +where + Self: 'static, + S: StateReader, + M: Fn(&S::Value) -> PartData + Clone + 'static, +{ + type Value = V; + type OriginReader = S; + type Reader = MapReader; + + #[inline] + fn read(&self) -> ReadRef { ReadRef::map(self.origin.read(), &self.part_map) } + + #[inline] + fn clone_reader(&self) -> Self::Reader { + MapReader { origin: self.origin.clone_reader(), part_map: self.part_map.clone() } + } + + #[inline] + fn origin_reader(&self) -> &Self::OriginReader { &self.origin } + + #[inline] + fn try_into_value(self) -> Result + where + Self::Value: Sized, + { + Err(self) + } } -impl StateReader for MapReader +impl StateReader for MapWriterAsReader where Self: 'static, - V: ?Sized, S: StateReader, - R: Fn(&S::Value) -> &V + Clone + 'static, + M: Fn(&mut S::Value) -> PartData + Clone, { - impl_reader_trivial_methods!(); + type Value = V; + type OriginReader = S; + type Reader = MapWriterAsReader; + + #[inline] + fn read(&self) -> ReadRef { + ReadRef::mut_as_ref_map(self.origin.read(), &self.part_map) + } + + #[inline] + fn clone_reader(&self) -> Self::Reader { + MapWriterAsReader { origin: self.origin.clone_reader(), part_map: self.part_map.clone() } + } + + #[inline] + fn origin_reader(&self) -> &Self::OriginReader { &self.origin } + + #[inline] + fn try_into_value(self) -> Result + where + Self::Value: Sized, + { + Err(self) + } } -impl StateReader for MapWriter +impl StateReader for MapWriter where Self: 'static, - V: ?Sized, S: StateWriter, - R: Fn(&S::Value) -> &V + Clone, - W: Fn(&mut S::Value) -> &mut V + Clone, + M: Fn(&mut S::Value) -> PartData + Clone, { - impl_reader_trivial_methods!(); + type Value = V; + type OriginReader = S; + type Reader = MapWriterAsReader; + + #[inline] + fn read(&self) -> ReadRef { + ReadRef::mut_as_ref_map(self.origin.read(), &self.part_map) + } + + #[inline] + fn clone_reader(&self) -> Self::Reader { + MapWriterAsReader { origin: self.origin.clone_reader(), part_map: self.part_map.clone() } + } + + #[inline] + fn origin_reader(&self) -> &Self::OriginReader { &self.origin } + + #[inline] + fn try_into_value(self) -> Result + where + Self::Value: Sized, + { + Err(self) + } } -impl StateWatcher for MapWriter +impl StateWatcher for MapWriter where Self: 'static, - V: ?Sized, W: StateWriter, - RM: Fn(&W::Value) -> &V + Clone, - WM: Fn(&mut W::Value) -> &mut V + Clone, + M: Fn(&mut W::Value) -> PartData + Clone, { #[inline] fn raw_modifies(&self) -> CloneableBoxOp<'static, ModifyScope, Infallible> { @@ -89,33 +134,29 @@ where } } -impl StateWriter for MapWriter +impl StateWriter for MapWriter where Self: 'static, - V: ?Sized, W: StateWriter, - RM: Fn(&W::Value) -> &V + Clone, - WM: Fn(&mut W::Value) -> &mut V + Clone, + M: Fn(&mut W::Value) -> PartData + Clone, { - type Writer = MapWriter; + type Writer = MapWriter; type OriginWriter = W; #[inline] - fn write(&self) -> WriteRef { self.map_ref(self.origin.write()) } + fn write(&self) -> WriteRef { WriteRef::map(self.origin.write(), &self.part_map) } #[inline] - fn silent(&self) -> WriteRef { self.map_ref(self.origin.silent()) } + fn silent(&self) -> WriteRef { WriteRef::map(self.origin.silent(), &self.part_map) } #[inline] - fn shallow(&self) -> WriteRef { self.map_ref(self.origin.shallow()) } + fn shallow(&self) -> WriteRef { + WriteRef::map(self.origin.shallow(), &self.part_map) + } #[inline] fn clone_writer(&self) -> Self::Writer { - MapWriter { - origin: self.origin.clone_writer(), - map: self.map.clone(), - mut_map: self.mut_map.clone(), - } + MapWriter { origin: self.origin.clone_writer(), part_map: self.part_map.clone() } } #[inline] @@ -125,7 +166,21 @@ where impl RenderTarget for MapReader where S: StateReader, - F: Fn(&S::Value) -> &V + Clone + 'static, + F: Fn(&S::Value) -> PartData + Clone + 'static, + V: Render, +{ + type Target = V; + + fn proxy(&self, f: impl FnOnce(&Self::Target) -> T) -> T { + let v = self.read(); + f(&*v) + } +} + +impl RenderTarget for MapWriterAsReader +where + S: StateReader, + F: Fn(&mut S::Value) -> PartData + Clone + 'static, V: Render, { type Target = V; @@ -139,42 +194,30 @@ where impl RenderBuilder for MapReader where S: StateReader, - F: Fn(&S::Value) -> &V + Clone + 'static, + F: Fn(&S::Value) -> PartData + Clone + 'static, V: Render, { #[inline] fn build(self, ctx: &BuildCtx) -> Widget { RenderProxy::new(self).build(ctx) } } -impl RenderBuilder for MapWriter +impl RenderBuilder for MapWriterAsReader where - Self: 'static, - S: StateWriter, - RM: Fn(&S::Value) -> &V + Clone, - WM: Fn(&mut S::Value) -> &mut V + Clone, + S: StateReader, + F: Fn(&mut S::Value) -> PartData + Clone + 'static, V: Render, { #[inline] - fn build(self, ctx: &BuildCtx) -> Widget { self.clone_reader().build(ctx) } + fn build(self, ctx: &BuildCtx) -> Widget { RenderProxy::new(self).build(ctx) } } -impl MapWriter +impl RenderBuilder for MapWriter where Self: 'static, - V: ?Sized, S: StateWriter, - RM: Fn(&S::Value) -> &V + Clone, - WM: Fn(&mut S::Value) -> &mut V + Clone, + WM: Fn(&mut S::Value) -> PartData + Clone, + V: Render, { - fn map_ref<'a>(&'a self, mut orig: WriteRef<'a, S::Value>) -> WriteRef<'a, V> - where - WM: Fn(&mut S::Value) -> &mut V, - { - let value = orig - .value - .take() - .map(|orig| RefMut::map(orig, &self.mut_map)); - - WriteRef { value, modified: false, modify_scope: orig.modify_scope, control: orig.control } - } + #[inline] + fn build(self, ctx: &BuildCtx) -> Widget { self.clone_reader().build(ctx) } } diff --git a/core/src/state/splitted_state.rs b/core/src/state/splitted_state.rs index 528e548f8..9381be34f 100644 --- a/core/src/state/splitted_state.rs +++ b/core/src/state/splitted_state.rs @@ -1,4 +1,4 @@ -use std::cell::{Cell, RefMut}; +use std::cell::Cell; use ribir_algo::Sc; use rxrust::{ @@ -7,21 +7,21 @@ use rxrust::{ }; use super::{ - MapReader, ModifyScope, Notifier, ReadRef, StateReader, StateWatcher, StateWriter, WriteRef, - WriterControl, + state_cell::PartData, MapWriterAsReader, ModifyScope, Notifier, ReadRef, StateReader, + StateWatcher, StateWriter, WriteRef, WriterControl, }; use crate::{ context::BuildCtx, prelude::AppCtx, + state::state_cell::ValueMutRef, ticker::Instant, widget::{Render, RenderBuilder, Widget}, }; /// A writer splitted writer from another writer, and has its own notifier. -pub struct SplittedWriter { +pub struct SplittedWriter { origin: O, - map: R, - mut_map: W, + splitter: W, notifier: Notifier, batched_modify: Sc>, create_at: Instant, @@ -29,90 +29,49 @@ pub struct SplittedWriter { ref_count: Sc>, } -impl Drop for SplittedWriter { +impl Drop for SplittedWriter { fn drop(&mut self) { if self.ref_count.get() == 1 { let mut notifier = self.notifier.clone(); // we use an async task to unsubscribe to wait the batched modifies to be // notified. - AppCtx::spawn_local(async move { + let _ = AppCtx::spawn_local(async move { notifier.unsubscribe(); - }) - .unwrap(); + }); } } } -pub struct SplittedReader { - origin: S, - map: F, - notifier: Notifier, - create_at: Instant, - last_modified: Sc>, -} - -macro_rules! splitted_reader_impl { - () => { - type Value = V; - type OriginReader = O; - type Reader = SplittedReader; - - #[track_caller] - fn read(&self) -> ReadRef { - // fixme: remove time_stamp - // assert!( - // self.create_at > self.origin.time_stamp(), - // "A splitted reader is invalid because its origin state is modified after it - // created." ); - ReadRef::map(self.origin.read(), &self.map) - } - - #[inline] - fn clone_reader(&self) -> Self::Reader { - SplittedReader { - origin: self.origin.clone_reader(), - map: self.map.clone(), - notifier: self.notifier.clone(), - create_at: self.create_at, - last_modified: self.last_modified.clone(), - } - } - - #[inline] - fn origin_reader(&self) -> &Self::OriginReader { &self.origin } - - #[inline] - fn time_stamp(&self) -> Instant { self.last_modified.get() } - - #[inline] - fn try_into_value(self) -> Result - where - Self::Value: Sized, - { - Err(self) - } - }; -} - -impl StateReader for SplittedReader +impl StateReader for SplittedWriter where Self: 'static, - V: ?Sized, - O: StateReader, - R: Fn(&O::Value) -> &V + Clone, -{ - splitted_reader_impl!(); -} - -impl StateReader for SplittedWriter -where - Self: 'static, - V: ?Sized, O: StateWriter, - R: Fn(&O::Value) -> &V + Clone, - W: Fn(&mut O::Value) -> &mut V + Clone, + W: Fn(&mut O::Value) -> PartData + Clone, { - splitted_reader_impl!(); + type Value = V; + type OriginReader = O; + type Reader = MapWriterAsReader; + + #[track_caller] + fn read(&self) -> ReadRef { + ReadRef::mut_as_ref_map(self.origin.read(), &self.splitter) + } + + #[inline] + fn clone_reader(&self) -> Self::Reader { + MapWriterAsReader { origin: self.origin.clone_reader(), part_map: self.splitter.clone() } + } + + #[inline] + fn origin_reader(&self) -> &Self::OriginReader { &self.origin } + + #[inline] + fn try_into_value(self) -> Result + where + Self::Value: Sized, + { + Err(self) + } } // fixme: we will remove the stamp check after #521 is fixed. @@ -137,13 +96,11 @@ impl Clone for Box { fn clone(&self) -> Self { self.box_clone() } } -impl StateWatcher for SplittedWriter +impl StateWatcher for SplittedWriter where Self: 'static, - V: ?Sized, O: StateWriter, - R: Fn(&O::Value) -> &V + Clone, - W: Fn(&mut O::Value) -> &mut V + Clone, + W: Fn(&mut O::Value) -> PartData + Clone, { fn raw_modifies(&self) -> CloneableBoxOp<'static, ModifyScope, std::convert::Infallible> { let origin = self.origin.clone_reader(); @@ -161,15 +118,13 @@ where } } -impl StateWriter for SplittedWriter +impl StateWriter for SplittedWriter where Self: 'static, - V: ?Sized, O: StateWriter, - R: Fn(&O::Value) -> &V + Clone, - W: Fn(&mut O::Value) -> &mut V + Clone, + W: Fn(&mut O::Value) -> PartData + Clone, { - type Writer = SplittedWriter; + type Writer = SplittedWriter; type OriginWriter = O; #[inline] @@ -184,8 +139,7 @@ where fn clone_writer(&self) -> Self::Writer { SplittedWriter { origin: self.origin.clone_writer(), - map: self.map.clone(), - mut_map: self.mut_map.clone(), + splitter: self.splitter.clone(), notifier: self.notifier.clone(), batched_modify: self.batched_modify.clone(), last_modified: self.last_modified.clone(), @@ -198,17 +152,12 @@ where fn origin_writer(&self) -> &Self::OriginWriter { &self.origin } } -impl WriterControl for SplittedWriter +impl WriterControl for SplittedWriter where Self: 'static, - V: ?Sized, O: StateWriter, - R: Fn(&O::Value) -> &V + Clone, - W: Fn(&mut O::Value) -> &mut V + Clone, + W: Fn(&mut O::Value) -> PartData + Clone, { - #[inline] - fn last_modified_stamp(&self) -> &Cell { &self.last_modified } - #[inline] fn batched_modifies(&self) -> &Cell { &self.batched_modify } @@ -219,21 +168,18 @@ where fn dyn_clone(&self) -> Box { Box::new(self.clone_writer()) } } -impl SplittedWriter +impl SplittedWriter where Self: 'static, O: StateWriter, - R: Fn(&O::Value) -> &V + Clone, - W: Fn(&mut O::Value) -> &mut V + Clone, - V: ?Sized, + W: Fn(&mut O::Value) -> PartData + Clone, { - pub(super) fn new(origin: O, map: R, mut_map: W) -> Self { + pub(super) fn new(origin: O, mut_map: W) -> Self { let create_at = Instant::now(); Self { origin, - map, - mut_map, + splitter: mut_map, notifier: Notifier::default(), batched_modify: <_>::default(), last_modified: Sc::new(Cell::new(create_at)), @@ -244,11 +190,6 @@ where #[track_caller] fn split_ref<'a>(&'a self, mut orig: WriteRef<'a, O::Value>) -> WriteRef<'a, V> { - assert!( - self.create_at > self.origin.time_stamp(), - "A splitted writer is invalid because its origin state is modified after it created." - ); - let modify_scope = orig.modify_scope; // the origin mark as a silent write, because split writer not effect the origin @@ -256,24 +197,21 @@ where assert!(!orig.modified); orig.modify_scope.remove(ModifyScope::FRAMEWORK); orig.modified = true; - - let value = orig - .value - .take() - .map(|orig| RefMut::map(orig, &self.mut_map)); + let value = + ValueMutRef { value: (self.splitter)(&mut orig.value), borrow: orig.value.borrow.clone() }; WriteRef { value, modified: false, modify_scope, control: self } } } -impl RenderBuilder for SplittedWriter +impl RenderBuilder for SplittedWriter where O: StateWriter, - R: Fn(&O::Value) -> &V + Clone + 'static, - W: Fn(&mut O::Value) -> &mut V + Clone, + W: Fn(&mut O::Value) -> PartData + Clone + 'static, V: Render, { fn build(self, ctx: &BuildCtx) -> Widget { - MapReader { origin: self.origin.clone_reader(), map: self.map.clone() }.build(ctx) + MapWriterAsReader { origin: self.origin.clone_reader(), part_map: self.splitter.clone() } + .build(ctx) } } diff --git a/core/src/state/state_cell.rs b/core/src/state/state_cell.rs new file mode 100644 index 000000000..2c2203f99 --- /dev/null +++ b/core/src/state/state_cell.rs @@ -0,0 +1,213 @@ +//! This implementation is a fork from `std::cell::RefCell`, allowing us to +//! manage the borrow flag. +use std::{ + cell::{Cell, UnsafeCell}, + ops::{Deref, DerefMut}, + ptr::NonNull, +}; + +type BorrowFlag = isize; +const UNUSED: BorrowFlag = 0; + +#[inline(always)] +fn is_reading(x: BorrowFlag) -> bool { x > UNUSED } + +#[inline(always)] +fn is_writing(x: BorrowFlag) -> bool { x < UNUSED } + +pub(crate) struct StateCell { + borrow_flag: Cell, + #[cfg(debug_assertions)] + borrowed_at: Cell>>, + data: UnsafeCell, +} + +impl StateCell { + pub(crate) fn new(data: W) -> Self { + StateCell { + borrow_flag: Cell::new(UNUSED), + #[cfg(debug_assertions)] + borrowed_at: Cell::new(None), + data: UnsafeCell::new(data), + } + } + + #[track_caller] + pub(crate) fn read(&self) -> ValueRef { + let borrow = &self.borrow_flag; + let b = borrow.get().wrapping_add(1); + borrow.set(b); + #[cfg(debug_assertions)] + { + // `borrowed_at` is always the *first* active borrow + if b == 1 { + self + .borrowed_at + .set(Some(std::panic::Location::caller())); + } + } + if !is_reading(b) { + // If a borrow occurred, then we must already have an outstanding borrow, + // so `borrowed_at` will be `Some` + panic!("already mutably borrowed: {:?}", self.borrowed_at.get().unwrap()) + } + + // SAFETY: `BorrowRef` ensures that there is only immutable access + // to the value while borrowed. + let value = PartData::PartRef(unsafe { NonNull::new_unchecked(self.data.get()) }); + ValueRef { value, borrow: BorrowRef { borrow } } + } + + pub(crate) fn write(&self) -> ValueMutRef<'_, W> { + // NOTE: Unlike BorrowRefMut::clone, new is called to create the initial + // mutable reference, and so there must currently be no existing + // references. Thus, while clone increments the mutable refcount, here + // we explicitly only allow going from UNUSED to UNUSED - 1. + let borrow = &self.borrow_flag; + if borrow.get() != UNUSED { + panic!("already borrowed: {:?}", self.borrowed_at.get().unwrap()) + } + #[cfg(debug_assertions)] + { + // If a borrow occurred, then we must already have an outstanding borrow, + // so `borrowed_at` will be `Some` + self + .borrowed_at + .set(Some(std::panic::Location::caller())); + } + + borrow.set(UNUSED - 1); + let v_ref = BorrowRefMut { borrow }; + let value = PartData::PartRef(unsafe { NonNull::new_unchecked(self.data.get()) }); + ValueMutRef { value, borrow: v_ref } + } + + pub(crate) fn is_unused(&self) -> bool { self.borrow_flag.get() == UNUSED } + + pub(super) fn into_inner(self) -> W { self.data.into_inner() } +} + +/// A partial data of a state, which should be point to the part data of the +/// state. +#[derive(Clone)] +pub enum PartData { + PartRef(NonNull), + PartData(T), +} + +impl PartData { + /// Create a `PartData` from a reference. + pub fn from_ref(v: &T) -> Self { PartData::PartRef(NonNull::from(v)) } + + /// Create a `PartData` from a mutable reference. + pub fn from_ref_mut(v: &mut T) -> Self { PartData::PartRef(NonNull::from(v)) } + + /// Create a `PartData` from a type that should be point to the part data not + /// a copy. E.g. Option<&T>, `Box`, `Arc`, `Rc`, etc. + /// + /// Caller should ensure that the data is not a copy. + pub fn from_data(ptr_data: T) -> Self { PartData::PartData(ptr_data) } +} +pub(crate) struct ValueRef<'a, T> { + pub(crate) value: PartData, + pub(crate) borrow: BorrowRef<'a>, +} + +pub(crate) struct ValueMutRef<'a, T> { + pub(crate) value: PartData, + pub(crate) borrow: BorrowRefMut<'a>, +} + +pub(crate) struct BorrowRefMut<'b> { + borrow: &'b Cell, +} + +pub(crate) struct BorrowRef<'b> { + borrow: &'b Cell, +} + +impl Deref for PartData { + type Target = T; + fn deref(&self) -> &Self::Target { + match self { + PartData::PartRef(ptr) => unsafe { ptr.as_ref() }, + PartData::PartData(data) => data, + } + } +} + +impl DerefMut for PartData { + fn deref_mut(&mut self) -> &mut Self::Target { + match self { + PartData::PartRef(ptr) => unsafe { ptr.as_mut() }, + PartData::PartData(data) => data, + } + } +} + +impl Drop for BorrowRefMut<'_> { + #[inline] + fn drop(&mut self) { + let borrow = self.borrow.get(); + debug_assert!(is_writing(borrow)); + self.borrow.set(borrow + 1); + } +} + +impl Drop for BorrowRef<'_> { + #[inline] + fn drop(&mut self) { + let borrow = self.borrow.get(); + debug_assert!(is_reading(borrow)); + self.borrow.set(borrow - 1); + } +} + +impl<'b> BorrowRefMut<'b> { + // Clones a `BorrowRefMut`. + // + // This is only valid if each `BorrowRefMut` is used to track a mutable + // reference to a distinct, nonoverlapping range of the original object. + // This isn't in a Clone impl so that code doesn't call this implicitly. + #[inline] + pub(crate) fn clone(&self) -> BorrowRefMut<'b> { + let borrow = self.borrow.get(); + debug_assert!(is_writing(borrow)); + // Prevent the borrow counter from underflowing. + assert!(borrow != BorrowFlag::MIN); + self.borrow.set(borrow - 1); + BorrowRefMut { borrow: self.borrow } + } +} + +impl BorrowRef<'_> { + #[inline] + pub(crate) fn clone(&self) -> Self { + // Since this Ref exists, we know the borrow flag + // is a reading borrow. + let borrow = self.borrow.get(); + debug_assert!(is_reading(borrow)); + // Prevent the borrow counter from overflowing into + // a writing borrow. + assert!(borrow != BorrowFlag::MAX); + self.borrow.set(borrow + 1); + BorrowRef { borrow: self.borrow } + } +} + +impl<'a, T> Deref for ValueRef<'a, T> { + type Target = T; + #[inline] + fn deref(&self) -> &Self::Target { &self.value } +} + +impl<'a, T> Deref for ValueMutRef<'a, T> { + type Target = T; + #[inline] + fn deref(&self) -> &Self::Target { &self.value } +} + +impl<'a, T> DerefMut for ValueMutRef<'a, T> { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { &mut self.value } +} diff --git a/core/src/state/stateful.rs b/core/src/state/stateful.rs index e9ca1b970..58ef803e7 100644 --- a/core/src/state/stateful.rs +++ b/core/src/state/stateful.rs @@ -1,21 +1,18 @@ -use std::{ - cell::{Cell, RefCell}, - convert::Infallible, -}; +use std::{cell::Cell, convert::Infallible}; use ribir_algo::Sc; use rxrust::{ops::box_it::CloneableBoxOp, prelude::*}; -use super::WriterControl; +use super::{state_cell::StateCell, WriterControl}; use crate::{prelude::*, render_helper::RenderProxy}; /// Stateful object use to watch the modifies of the inner data. pub struct Stateful { - data: Sc>, + data: Sc>, info: Sc, } -pub struct Reader(Sc>); +pub struct Reader(Sc>); pub struct Writer(Stateful); @@ -46,9 +43,6 @@ struct StatefulInfo { writer_count: Cell, /// The batched modifies of the `State` which will be notified. batch_modified: Cell, - /// The timestamp of the last modify of the data, not record the modify from - /// the splitted or mapped state. - last_modified: Cell, } impl StateReader for Stateful { @@ -57,7 +51,7 @@ impl StateReader for Stateful { type Reader = Reader; #[inline] - fn read(&self) -> ReadRef { ReadRef::new(self.data.borrow()) } + fn read(&self) -> ReadRef { ReadRef::new(self.data.read()) } #[inline] fn clone_reader(&self) -> Self::Reader { Reader(self.data.clone()) } @@ -65,9 +59,6 @@ impl StateReader for Stateful { #[inline] fn origin_reader(&self) -> &Self::OriginReader { self } - #[inline] - fn time_stamp(&self) -> Instant { self.info.last_modified.get() } - fn try_into_value(self) -> Result { if self.data.ref_count() == 1 { let data = self.data.clone(); @@ -114,7 +105,7 @@ impl StateReader for Reader { type Reader = Self; #[inline] - fn read(&self) -> ReadRef { ReadRef::new(self.0.borrow()) } + fn read(&self) -> ReadRef { ReadRef::new(self.0.read()) } #[inline] fn clone_reader(&self) -> Self { Reader(self.0.clone()) } @@ -122,13 +113,6 @@ impl StateReader for Reader { #[inline] fn origin_reader(&self) -> &Self::OriginReader { self } - #[inline] - fn time_stamp(&self) -> Instant { - //fixme: we should remove this method in future, temporary return incorrect - // value here. - Instant::now() - } - fn try_into_value(self) -> Result { if self.0.ref_count() == 1 { // SAFETY: `self.0.ref_count() == 1` guarantees unique access. @@ -154,9 +138,6 @@ impl StateReader for Writer { #[inline] fn origin_reader(&self) -> &Self::OriginReader { self } - #[inline] - fn time_stamp(&self) -> Instant { self.0.time_stamp() } - #[inline] fn try_into_value(self) -> Result { self.0.try_into_value().map_err(Writer) } } @@ -189,9 +170,6 @@ impl StateWriter for Writer { } impl WriterControl for Sc { - #[inline] - fn last_modified_stamp(&self) -> &Cell { &self.last_modified } - #[inline] fn batched_modifies(&self) -> &Cell { &self.batch_modified } @@ -258,12 +236,12 @@ impl RenderBuilder for Writer { impl Stateful { pub fn new(data: W) -> Self { - Self { data: Sc::new(RefCell::new(data)), info: Sc::new(StatefulInfo::new()) } + Self { data: Sc::new(StateCell::new(data)), info: Sc::new(StatefulInfo::new()) } } fn write_ref(&self, scope: ModifyScope) -> WriteRef<'_, W> { - let value = self.data.borrow_mut(); - WriteRef { value: Some(value), modified: false, modify_scope: scope, control: &self.info } + let value = self.data.write(); + WriteRef { value, modified: false, modify_scope: scope, control: &self.info } } fn writer_count(&self) -> usize { self.info.writer_count.get() } @@ -292,7 +270,6 @@ impl StatefulInfo { batch_modified: <_>::default(), writer_count: Cell::new(1), notifier: <_>::default(), - last_modified: Cell::new(Instant::now()), } } } @@ -329,14 +306,14 @@ impl Notifier { impl std::fmt::Debug for Stateful { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_tuple("Stateful") - .field(&*self.data.borrow()) + .field(&*self.data.read()) .finish() } } #[cfg(test)] mod tests { - use std::rc::Rc; + use std::{cell::RefCell, rc::Rc}; use super::*; use crate::test_helper::*; @@ -384,13 +361,21 @@ mod tests { assert_eq!(*drop_cnt.borrow(), 1); { - drop_writer_subscribe(Stateful::new(()).map_writer(|v| v, |v| v), drop_cnt.clone()); + drop_writer_subscribe( + #[allow(clippy::redundant_closure)] + Stateful::new(()).map_writer(|v| PartData::from_ref_mut(v)), + drop_cnt.clone(), + ); }; AppCtx::run_until_stalled(); assert_eq!(*drop_cnt.borrow(), 2); { - drop_writer_subscribe(Stateful::new(()).split_writer(|v| v, |v| v), drop_cnt.clone()); + drop_writer_subscribe( + #[allow(clippy::redundant_closure)] + Stateful::new(()).split_writer(|v| PartData::from_ref_mut(v)), + drop_cnt.clone(), + ); }; AppCtx::run_until_stalled(); assert_eq!(*drop_cnt.borrow(), 3); diff --git a/core/src/state/watcher.rs b/core/src/state/watcher.rs index 5092d995f..92c7ccc19 100644 --- a/core/src/state/watcher.rs +++ b/core/src/state/watcher.rs @@ -31,9 +31,6 @@ impl StateReader for Watcher { #[inline] fn origin_reader(&self) -> &Self::OriginReader { self.reader.origin_reader() } - #[inline] - fn time_stamp(&self) -> Instant { self.reader.time_stamp() } - #[inline] fn try_into_value(self) -> Result where diff --git a/core/src/widget.rs b/core/src/widget.rs index 615e9e626..b96f97987 100644 --- a/core/src/widget.rs +++ b/core/src/widget.rs @@ -392,6 +392,11 @@ impl Query for Sc { impl Query for RefCell { impl_proxy_and_self_query!(borrow()); } + +impl Query for StateCell { + impl_proxy_and_self_query!(read()); +} + impl_proxy_render!(proxy deref(), ShareResource, , where T: Render + 'static); pub(crate) fn hit_test_impl(ctx: &HitTestCtx, pos: Point) -> bool { @@ -446,6 +451,8 @@ pub(crate) use multi_build_replace_impl; pub(crate) use multi_build_replace_impl_include_self; pub(crate) use repeat_and_replace; +use self::state_cell::StateCell; + impl Drop for Widget { fn drop(&mut self) { log::warn!("widget allocated but never used: {:?}", self.id); diff --git a/docs/en/get_started/quick_start.md b/docs/en/get_started/quick_start.md index de7fe8dcf..ae66aa1f3 100644 --- a/docs/en/get_started/quick_start.md +++ b/docs/en/get_started/quick_start.md @@ -678,7 +678,7 @@ Fortunately, for state management, Ribir provides a mechanism for transformation The **map** is to transform a parent state into a sub-state, and the sub-state has the same data as the parent state. Modifying the parent state is equivalent to modifying the sub-state, and vice versa. It only reduces the visible scope of the data, making it easier for you to use or pass only part of the state. -The **split** is to separate a sub-state from a parent state. The parent and child state also share the same data. The difference is that modifying data through the sub-state will not trigger the views dependent on the parent state to update, and modifying data through the parent state will cause the split sub-state to be invalidated. +The **split** is to separate a sub-state from a parent state. The parent and child state also share the same data. The difference is that modifying data through the sub-state will not trigger the views dependent on the parent state to update. What you need to note is that whether it's **map** or **split**, the parent and child state share the same data. Therefore, their modifications to the data will affect each other, but the scope of data modifications they push may be different. @@ -692,8 +692,8 @@ struct AppData { } let state = State::value(AppData { count: 0 }); -let map_count = state.map_writer(|d| &d.count, |d| &mut d.count); -let split_count = state.split_writer(|d| &d.count, |d| &mut d.count); +let map_count = state.map_writer(|d| PartData::from_ref_mut(&mut d.count)); +let split_count = state.split_writer(|d| PartData::from_ref_mut(&mut d.count)); watch!($state.count).subscribe(|_| println!("Parent data")); watch!(*$map_count).subscribe(|_| println!("Child(map) data")); @@ -743,20 +743,10 @@ AppCtx::run_until_stalled(); Because data modification notifications are sent out asynchronously in batches, in the example, for ease of understanding, we call `AppCtx::run_until_stalled()` after each data modification to force the notifications to be sent. However, this should not appear in your actual code. - -If the reader and writer that you map or split from are on the same path, you can use `map_writer!` and `split_writer!` provided by Ribir to simplify your code: - -```rust ignore -// let map_count = state.map_writer(|d| &d.count, |d| &mut d.count) -let map_count = map_writer!($state.count); -// let split_count = state.split_writer(|d| &d.count, |d| &mut d.count); -let split_count = split_writer!($state.count); -``` - If you only want to get a read-only sub-state, you can use `map_reader` to convert: ```rust ignore -let count_reader = state.map_reader(|d| &d.count); +let count_reader = state.map_reader(|d| PartData::from_ref(&d.count)); ``` However, Ribir does not provide a `split_reader`, because splitting a read-only sub-state is equivalent to converting a read-only sub-state. @@ -774,7 +764,7 @@ struct AppData { } let state: State = State::value(AppData { count: 0 }); -let split_count = split_writer!($state.count); +let split_count = state.split_writer(|d| PartData::from_ref_mut(&mut d.count)); // the root state's origin state is itself let _: &State = state.origin_reader(); diff --git a/docs/zh/get_started/quick_start.md b/docs/zh/get_started/quick_start.md index ed77d1819..e96583a65 100644 --- a/docs/zh/get_started/quick_start.md +++ b/docs/zh/get_started/quick_start.md @@ -676,7 +676,7 @@ fn main() { **转换**是将一个父状态转换为子状态,父子状态共享同样的数据,修改父状态等同于修改子状态,反之亦然。它仅仅是缩减了数据的可见范围,方便你只想使用和传递传递部分状态。 -**分离**是从一个父状态中分离出子状态,父子状态共享同样的数据。不同的是,通过子状态修改数据不会触发父状态的依赖视图更新,而通过父状态修改数据则会导致分离子状态失效。 +**分离**是从一个父状态中分离出子状态,父子状态共享同样的数据。不同的是,通过子状态修改数据不会触发父状态的依赖视图更新。 你要注意的是,不论是转换还是分离,父子状态共享的都是同一份数据。因此,它们对数据的修改会影响到彼此,但它们所推送的数据变更的范围可能不同。 @@ -690,8 +690,8 @@ struct AppData { } let state = State::value(AppData { count: 0 }); -let map_count = state.map_writer(|d| &d.count, |d| &mut d.count); -let split_count = state.split_writer(|d| &d.count, |d| &mut d.count); +let map_count = state.map_writer(|d| PartData::from_ref(&mut d.count)); +let split_count = state.split_writer(|d| PartData::from_ref(&mut d.count)); watch!($state.count).subscribe(|_| println!("父状态数据")); watch!(*$map_count).subscribe(|_| println!("子状态(转换)数据")); @@ -741,14 +741,6 @@ AppCtx::run_until_stalled(); 因为 Ribir 的数据修改通知是异步批量发出的,所以在例子中为了方便理解,我们每次数据修改都调用了 `AppCtx::run_until_stalled()` 来强制理解发送,但这不应该出现在你真实的代码中。 -如果你的状态读写器转换或分离自同一个路径,你可以使用 Ribir 提供的 `map_writer!` 和 `split_writer!` 来简化你的代码: - -```rust ignore -// let map_count = state.map_writer(|d| &d.count, |d| &mut d.count) -let map_count = map_writer!($state.count); -// let split_count = state.split_writer(|d| &d.count, |d| &mut d.count); -let split_count = split_writer!($state.count); -``` 如果你仅是想获得一个只读的子状态,那么可以通过 `map_reader` 来转换: @@ -771,7 +763,7 @@ struct AppData { } let state: State = State::value(AppData { count: 0 }); -let split_count = split_writer!($state.count); +let split_count = state.split_writer(|d| PartData::from_ref(&mut d.count)); // 根状态的源状态是它自己 let _: &State = state.origin_reader(); diff --git a/examples/todos/src/ui.rs b/examples/todos/src/ui.rs index ded536c3e..974e758e1 100644 --- a/examples/todos/src/ui.rs +++ b/examples/todos/src/ui.rs @@ -50,10 +50,11 @@ fn task_lists(this: &impl StateWriter, cond: fn(&Task) -> bool) - for id in $this.all_tasks() { if $this.get_task(id).map_or(false, cond) { let task = this.split_writer( - move |todos| todos.get_task(id).unwrap(), - move |todos| todos.get_task_mut(id).unwrap(), + // task will always exist, if the task is removed, + // sthe widgets list will be rebuild. + move |todos| PartData::from_ref_mut(todos.get_task_mut(id).unwrap()), ); - let item = pipe!(*$editing == Some($task.id())) + let item = pipe!(*$editing == Some(id)) .value_chain(|s| s.distinct_until_changed().box_it()) .map(move |b|{ if b { @@ -133,8 +134,14 @@ where let mut stagger = $stagger.write(); if !stagger.has_ever_run() { $item.write().opacity = 0.; + let transform = item + .get_transform_widget() + .map_writer(|w| PartData::from_ref_mut(&mut w.transform)); + let opacity = item + .get_opacity_widget() + .map_writer(|w| PartData::from_ref_mut(&mut w.opacity)); let fly_in = stagger.push_state( - (map_writer!($item.transform), map_writer!($item.opacity)), + (transform, opacity), (Transform::translation(0., 64.), 0.), ctx!() ); diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 430c8ed2e..88384a227 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -16,7 +16,6 @@ mod rdl_macro; mod simple_declare_attr; pub(crate) mod variable_names; mod watch_macro; -mod writer_map_macro; pub(crate) use rdl_macro::*; pub(crate) mod declare_obj; pub(crate) mod error; @@ -256,24 +255,6 @@ pub fn watch(input: TokenStream) -> TokenStream { watch_macro::gen_code(input.into(), &mut DollarRefsCtx::top_level()) } -/// macro split a new writer from a state writer. For example, -/// `split_writer!($label.visible);` will return a writer of `bool` that is -/// partial of the `Visibility`. This macros is a convenient way for -/// `StateWriter::split_writer` -#[proc_macro] -pub fn split_writer(input: TokenStream) -> TokenStream { - writer_map_macro::gen_split_path_writer(input.into(), &mut DollarRefsCtx::top_level()) -} - -/// macro map a write to another state write. For example, -/// `map_writer!($label.visible);` will return a writer of `bool` that is -/// partial of the `Visibility`. This macros is a convenient way for -/// `StateWriter::map_writer` -#[proc_macro] -pub fn map_writer(input: TokenStream) -> TokenStream { - writer_map_macro::gen_map_path_writer(input.into(), &mut DollarRefsCtx::top_level()) -} - /// Includes an SVG file as an `Svg`. /// The file is located relative to the current crate (similar to the location diff --git a/macros/src/symbol_process.rs b/macros/src/symbol_process.rs index a0f9815b7..e7127922a 100644 --- a/macros/src/symbol_process.rs +++ b/macros/src/symbol_process.rs @@ -14,7 +14,6 @@ use crate::{ fn_widget_macro, rdl_macro::RdlMacro, variable_names::{ribir_suffix_variable, BuiltinMemberType, BUILTIN_INFOS}, - writer_map_macro::{gen_map_path_writer, gen_split_path_writer}, }; pub const KW_DOLLAR_STR: &str = "_dollar_ಠ_ಠ"; @@ -23,8 +22,6 @@ pub const KW_RDL: &str = "rdl"; pub const KW_PIPE: &str = "pipe"; pub const KW_WATCH: &str = "watch"; pub const KW_FN_WIDGET: &str = "fn_widget"; -pub const KW_SPLIT_WRITER: &str = "split_writer"; -pub const KW_MAP_WRITER: &str = "map_writer"; pub use tokens_pre_process::*; @@ -334,12 +331,6 @@ impl Fold for DollarRefsCtx { } else if mac.path.is_ident(KW_FN_WIDGET) { mac.tokens = fn_widget_macro::gen_code(mac.tokens, self).into(); mark_macro_expanded(&mut mac); - } else if mac.path.is_ident(KW_SPLIT_WRITER) { - mac.tokens = gen_split_path_writer(mac.tokens, self).into(); - mark_macro_expanded(&mut mac); - } else if mac.path.is_ident(KW_MAP_WRITER) { - mac.tokens = gen_map_path_writer(mac.tokens, self).into(); - mark_macro_expanded(&mut mac); } else if mac.path.is_ident(KW_CTX) { self.mark_used_ctx(); } else { diff --git a/macros/src/writer_map_macro.rs b/macros/src/writer_map_macro.rs deleted file mode 100644 index 7735adbe8..000000000 --- a/macros/src/writer_map_macro.rs +++ /dev/null @@ -1,106 +0,0 @@ -use proc_macro::{TokenStream as TokenStream1, TokenTree}; -use proc_macro2::{Ident, Span, TokenStream}; -use quote::{quote, quote_spanned, ToTokens}; -use syn::{ - fold::Fold, - parse::{Parse, ParseStream}, - parse_macro_input, parse_quote, - spanned::Spanned, - Expr, ExprMacro, Result, -}; - -use crate::{ - ok, - symbol_process::{symbol_to_macro, DollarRefsCtx}, -}; - -pub fn gen_map_path_writer(input: TokenStream, refs_ctx: &mut DollarRefsCtx) -> TokenStream1 { - gen_path_partial_writer(input, "map_writer", refs_ctx) -} - -pub fn gen_split_path_writer(input: TokenStream, refs_ctx: &mut DollarRefsCtx) -> TokenStream1 { - gen_path_partial_writer(input, "split_writer", refs_ctx) -} - -fn gen_path_partial_writer( - input: TokenStream, method_name: &'static str, refs_ctx: &mut DollarRefsCtx, -) -> TokenStream1 { - fn first_dollar_err(span: Span) -> TokenStream1 { - quote_spanned! { span => - compile_error!("expected `$` as the first token, and only one `$` is allowed") - } - .into() - } - - let mut input = TokenStream1::from(input).into_iter(); - let first = input.next(); - let is_first_dollar = first - .as_ref() - .map_or(false, |f| matches!(f, TokenTree::Punct(p) if p.as_char() == '$')); - if !is_first_dollar { - first_dollar_err( - first - .as_ref() - .map_or(Span::call_site(), |t| t.span().into()), - ); - } - - let input = ok!(symbol_to_macro(first.into_iter().chain(input))); - - let expr = parse_macro_input! { input as Expr }; - // Although `split_writer!` and `map_writer!` are not a capture scope, but we - // start a new capture scope to ensure found the dollar in the macro. We will - // not use the result of the `$var`, so it ok. - refs_ctx.new_dollar_scope(true); - let expr = refs_ctx.fold_expr(expr); - let refs = refs_ctx.pop_dollar_scope(true, false); - - if refs.len() != 1 { - quote_spanned! { expr.span() => - compile_error!("expected `$` as the first token, and only one `$` is allowed") - } - .into() - } else { - let dollar_ref = &refs[0]; - let host = if dollar_ref.builtin.is_some() { - refs_ctx.builtin_host_tokens(dollar_ref) - } else { - dollar_ref.name.to_token_stream() - }; - - let path: RouterPath = parse_quote!(#expr); - RouterMacro { host, path: path.0, method_name } - .to_token_stream() - .into() - } -} - -struct RouterPath(TokenStream); - -impl Parse for RouterPath { - fn parse(input: ParseStream) -> Result { - input.parse::()?; - Ok(Self(input.parse()?)) - } -} - -struct RouterMacro { - host: TokenStream, - path: TokenStream, - method_name: &'static str, -} - -impl ToTokens for RouterMacro { - fn to_tokens(&self, tokens: &mut TokenStream) { - let Self { host, path, method_name } = self; - let method = Ident::new(method_name, Span::call_site()); - - quote!( - #host.#method( - move |origin: &_| &origin #path, - move |origin: &mut _| &mut origin #path - ) - ) - .to_tokens(tokens) - } -} diff --git a/tests/rdl_macro_test.rs b/tests/rdl_macro_test.rs index eb8f83a79..2157e91e1 100644 --- a/tests/rdl_macro_test.rs +++ b/tests/rdl_macro_test.rs @@ -824,7 +824,11 @@ fn no_watch() { #[test] fn fix_direct_use_map_writer_with_builtin() { fn _x(mut host: FatObj, ctx!(): &BuildCtx) { - let _anchor = map_writer!($host.anchor); - let _anchor = split_writer!($host.anchor); + let _anchor = host + .get_relative_anchor_widget() + .map_writer(|w| PartData::from_ref_mut(&mut w.anchor)); + let _anchor = host + .get_relative_anchor_widget() + .map_writer(|w| PartData::from_ref_mut(&mut w.anchor)); } } diff --git a/themes/material/src/lib.rs b/themes/material/src/lib.rs index d2d70861a..38fa5c04b 100644 --- a/themes/material/src/lib.rs +++ b/themes/material/src/lib.rs @@ -256,10 +256,10 @@ fn override_compose_decorator(theme: &mut FullTheme) { fn_widget! { let host = scrollbar_thumb(host, EdgeInsets::vertical(1.)); let mut thumb = @ $host { anchor: pipe!($this.offset).map(Anchor::left) }; - - map_writer!($thumb.anchor) + thumb + .get_relative_anchor_widget() + .map_writer(|w| PartData::from_ref_mut(&mut w.anchor)) .transition(transitions::LINEAR.of(ctx!()), ctx!()); - thumb } .build(ctx) @@ -268,8 +268,9 @@ fn override_compose_decorator(theme: &mut FullTheme) { fn_widget! { let host = scrollbar_thumb(host, EdgeInsets::vertical(1.)); let mut thumb = @ $host { anchor: pipe!($this.offset).map(Anchor::top) }; - - map_writer!($thumb.anchor) + thumb + .get_relative_anchor_widget() + .map_writer(|w| PartData::from_ref_mut(&mut w.anchor)) .transition(transitions::LINEAR.of(ctx!()), ctx!()); thumb @@ -297,18 +298,10 @@ fn override_compose_decorator(theme: &mut FullTheme) { }, }; - let ease_in = transitions::EASE_IN.of(ctx!()); - match $style.pos { - Position::Top | Position::Bottom => { - map_writer!($indicator.anchor) - .transition(ease_in, ctx!()); - } - Position::Left | Position::Right => { - map_writer!($indicator.anchor) - .transition(ease_in, ctx!()); - } - } - + indicator + .get_relative_anchor_widget() + .map_writer(|w| PartData::from_ref_mut(&mut w.anchor)) + .transition(transitions::EASE_IN.of(ctx!()), ctx!()); indicator } .build(ctx) diff --git a/themes/material/src/ripple.rs b/themes/material/src/ripple.rs index 4533acba2..3a50c45be 100644 --- a/themes/material/src/ripple.rs +++ b/themes/material/src/ripple.rs @@ -62,7 +62,7 @@ impl ComposeChild for Ripple { let ripper_enter = @Animate { transition: transitions::LINEAR.of(ctx!()), state: LerpFnState::new( - map_writer!($ripple.path), + ripple.map_writer(|w| PartData::from_ref_mut(&mut w.path)), move |_, _, rate| { let radius = Lerp::lerp(&0., &radius, rate); Path::circle(launch_at, radius) @@ -79,8 +79,9 @@ impl ComposeChild for Ripple { $ripple_at.write().take(); }); - - let ripper_fade_out = map_writer!($ripple.opacity) + let ripper_fade_out = ripple + .get_opacity_widget() + .map_writer(|w| PartData::from_ref_mut(&mut w.opacity)) .transition(transitions::EASE_OUT.of(ctx!()), ctx!()); let bounded = $this.bounded; diff --git a/widgets/src/text_field.rs b/widgets/src/text_field.rs index c47f08198..f42a8ef43 100644 --- a/widgets/src/text_field.rs +++ b/widgets/src/text_field.rs @@ -329,9 +329,9 @@ fn build_input_area( let mut input_area = @Row { visible: pipe!(!$this.text.is_empty() || $theme.state == TextFieldState::Focused), }; - - let visible = map_writer!($input_area.visible); - visible.transition(transitions::LINEAR.of(ctx!()), ctx!()); + input_area.get_visibility_widget() + .map_writer(|w| PartData::from_ref(&w.visible)) + .transition(transitions::LINEAR.of(ctx!()), ctx!()); let mut input = @Input{ style: pipe!($theme.text.clone()) }; $input.write().set_text(&$this.text); @@ -391,7 +391,7 @@ impl Compose for TextFieldLabel { text_style: pipe!($this.style.clone()), }; - map_writer!($this.style.font_size) + this.map_writer(|w| PartData::from_ref(&w.style.font_size)) .transition(transitions::LINEAR.of(ctx!()), ctx!()); label @@ -409,7 +409,9 @@ fn build_content_area( padding: pipe!($theme.input_padding($this.text.is_empty())), }; - map_writer!($content_area.padding) + content_area + .get_padding_widget() + .map_writer(|w| PartData::from_ref(&w.padding)) .transition(transitions::LINEAR.of(ctx!()), ctx!()); @ $content_area {