reactive_stores/
subfield.rs

1use crate::{
2    path::{StorePath, StorePathSegment},
3    store_field::StoreField,
4    KeyMap, StoreFieldTrigger,
5};
6use reactive_graph::{
7    signal::{
8        guards::{Mapped, MappedMut, WriteGuard},
9        ArcTrigger,
10    },
11    traits::{
12        DefinedAt, Get as _, IsDisposed, Notify, ReadUntracked, Track,
13        UntrackableGuard, Write,
14    },
15    wrappers::read::Signal,
16};
17use std::{iter, marker::PhantomData, ops::DerefMut, panic::Location};
18
19/// Accesses a single field of a reactive structure.
20#[derive(Debug)]
21pub struct Subfield<Inner, Prev, T> {
22    #[cfg(any(debug_assertions, leptos_debuginfo))]
23    defined_at: &'static Location<'static>,
24    path_segment: StorePathSegment,
25    inner: Inner,
26    read: fn(&Prev) -> &T,
27    write: fn(&mut Prev) -> &mut T,
28    ty: PhantomData<T>,
29}
30
31impl<Inner, Prev, T> Clone for Subfield<Inner, Prev, T>
32where
33    Inner: Clone,
34{
35    fn clone(&self) -> Self {
36        Self {
37            #[cfg(any(debug_assertions, leptos_debuginfo))]
38            defined_at: self.defined_at,
39            path_segment: self.path_segment,
40            inner: self.inner.clone(),
41            read: self.read,
42            write: self.write,
43            ty: self.ty,
44        }
45    }
46}
47
48impl<Inner, Prev, T> Copy for Subfield<Inner, Prev, T> where Inner: Copy {}
49
50impl<Inner, Prev, T> Subfield<Inner, Prev, T> {
51    /// Creates an accessor for a single field of the inner structure.
52    #[track_caller]
53    pub fn new(
54        inner: Inner,
55        path_segment: StorePathSegment,
56        read: fn(&Prev) -> &T,
57        write: fn(&mut Prev) -> &mut T,
58    ) -> Self {
59        Self {
60            #[cfg(any(debug_assertions, leptos_debuginfo))]
61            defined_at: Location::caller(),
62            inner,
63            path_segment,
64            read,
65            write,
66            ty: PhantomData,
67        }
68    }
69}
70
71impl<Inner, Prev, T> StoreField for Subfield<Inner, Prev, T>
72where
73    Inner: StoreField<Value = Prev>,
74    Prev: 'static,
75{
76    type Value = T;
77    type Reader = Mapped<Inner::Reader, T>;
78    type Writer = MappedMut<WriteGuard<Vec<ArcTrigger>, Inner::Writer>, T>;
79
80    fn path(&self) -> impl IntoIterator<Item = StorePathSegment> {
81        self.inner
82            .path()
83            .into_iter()
84            .chain(iter::once(self.path_segment))
85    }
86
87    fn get_trigger(&self, path: StorePath) -> StoreFieldTrigger {
88        self.inner.get_trigger(path)
89    }
90
91    fn reader(&self) -> Option<Self::Reader> {
92        let inner = self.inner.reader()?;
93        Some(Mapped::new_with_guard(inner, self.read))
94    }
95
96    fn writer(&self) -> Option<Self::Writer> {
97        let trigger = self.get_trigger(self.path().into_iter().collect());
98        let mut parent = self.inner.writer()?;
99        parent.untrack();
100
101        let mut full_path = self.path().into_iter().collect::<StorePath>();
102        full_path.pop();
103
104        // build a list of triggers, starting with the full path to this node and ending with the root
105        // this will mean that the root is the final item, and this path is first
106        let mut triggers = Vec::with_capacity(full_path.len());
107        triggers.push(trigger.this.clone());
108        loop {
109            let inner = self.get_trigger(full_path.clone());
110            triggers.push(inner.children.clone());
111            if full_path.is_empty() {
112                break;
113            }
114            full_path.pop();
115        }
116
117        // when the WriteGuard is dropped, each trigger will be notified, in order
118        // reversing the list will cause the triggers to be notified starting from the root,
119        // then to each child down to this one
120        //
121        // notifying from the root down is important for things like OptionStoreExt::map()/unwrap(),
122        // where it's really important that any effects that subscribe to .is_some() run before effects
123        // that subscribe to the inner value, so that the inner effect can be canceled if the outer switches to `None`
124        // (see https://github.com/leptos-rs/leptos/issues/3704)
125        triggers.reverse();
126
127        let guard = WriteGuard::new(triggers, parent);
128
129        Some(MappedMut::new(guard, self.read, self.write))
130    }
131
132    #[inline(always)]
133    fn keys(&self) -> Option<KeyMap> {
134        self.inner.keys()
135    }
136
137    #[track_caller]
138    fn track_field(&self) {
139        let mut full_path = self.path().into_iter().collect::<StorePath>();
140        // tracks `this` for all ancestors: i.e., it will track any change that is made
141        // directly to one of its ancestors, but not a change made to a *child* of an ancestor
142        // (which would end up with every subfield tracking its own siblings, because they are
143        // children of its parent)
144        loop {
145            let inner = self.get_trigger(full_path.clone());
146            inner.this.track();
147            if full_path.is_empty() {
148                break;
149            }
150            full_path.pop();
151        }
152        let trigger = self.get_trigger(self.path().into_iter().collect());
153        trigger.this.track();
154        trigger.children.track();
155    }
156}
157
158impl<Inner, Prev, T> DefinedAt for Subfield<Inner, Prev, T>
159where
160    Inner: StoreField<Value = Prev>,
161{
162    fn defined_at(&self) -> Option<&'static Location<'static>> {
163        #[cfg(any(debug_assertions, leptos_debuginfo))]
164        {
165            Some(self.defined_at)
166        }
167        #[cfg(not(any(debug_assertions, leptos_debuginfo)))]
168        {
169            None
170        }
171    }
172}
173
174impl<Inner, Prev, T> IsDisposed for Subfield<Inner, Prev, T>
175where
176    Inner: IsDisposed,
177{
178    fn is_disposed(&self) -> bool {
179        self.inner.is_disposed()
180    }
181}
182
183impl<Inner, Prev, T> Notify for Subfield<Inner, Prev, T>
184where
185    Inner: StoreField<Value = Prev>,
186    Prev: 'static,
187{
188    #[track_caller]
189    fn notify(&self) {
190        let trigger = self.get_trigger(self.path().into_iter().collect());
191        trigger.this.notify();
192        trigger.children.notify();
193    }
194}
195
196impl<Inner, Prev, T> Track for Subfield<Inner, Prev, T>
197where
198    Inner: StoreField<Value = Prev> + Track + 'static,
199    Prev: 'static,
200    T: 'static,
201{
202    #[track_caller]
203    fn track(&self) {
204        self.track_field();
205    }
206}
207
208impl<Inner, Prev, T> ReadUntracked for Subfield<Inner, Prev, T>
209where
210    Inner: StoreField<Value = Prev>,
211    Prev: 'static,
212{
213    type Value = <Self as StoreField>::Reader;
214
215    fn try_read_untracked(&self) -> Option<Self::Value> {
216        self.reader()
217    }
218}
219
220impl<Inner, Prev, T> Write for Subfield<Inner, Prev, T>
221where
222    T: 'static,
223    Inner: StoreField<Value = Prev>,
224    Prev: 'static,
225{
226    type Value = T;
227
228    fn try_write(&self) -> Option<impl UntrackableGuard<Target = Self::Value>> {
229        self.writer()
230    }
231
232    fn try_write_untracked(
233        &self,
234    ) -> Option<impl DerefMut<Target = Self::Value>> {
235        self.writer().map(|mut writer| {
236            writer.untrack();
237            writer
238        })
239    }
240}
241
242impl<Inner, Prev, T> From<Subfield<Inner, Prev, T>> for Signal<T>
243where
244    Inner: StoreField<Value = Prev> + Track + Send + Sync + 'static,
245    Prev: 'static,
246    T: Send + Sync + Clone + 'static,
247{
248    fn from(subfield: Subfield<Inner, Prev, T>) -> Self {
249        Signal::derive(move || subfield.get())
250    }
251}