Skip to main content

cranpose_services/durable_save/
local_saves.rs

1//! The saves of this thread's compositions: what `rememberSaveable` and
2//! saveable scopes register, the last values of saves that left the
3//! composition, and the saved keys waiting to be removed.
4
5use std::{collections::BTreeMap, rc::Rc, sync::Arc};
6
7use super::{NEXT_SAVE, Ordering, SaveBatch};
8use crate::preferences::PreferencesRef;
9
10struct LocalSave {
11    /// `None` keeps the value only in memory.
12    store: Option<PreferencesRef>,
13    key: String,
14    read: Box<dyn Fn() -> Option<String>>,
15}
16
17#[derive(Default)]
18pub(crate) struct LocalSaves {
19    live: BTreeMap<u64, Rc<LocalSave>>,
20    /// The last values of saves whose composables left the composition
21    /// before their store held them, by key. A composable that comes back
22    /// restores what it had. A completed save boundary writes the durable
23    /// ones and forgets them.
24    retired: BTreeMap<String, RetiredSave>,
25    /// Saved keys to remove from their stores at the next save boundary.
26    forgotten: Vec<(PreferencesRef, Arc<Forget>)>,
27}
28
29struct RetiredSave {
30    store: Option<PreferencesRef>,
31    value: String,
32}
33
34/// The keys under `prefix`, apart from those under one of `keep`.
35pub(super) struct Forget {
36    prefix: String,
37    keep: Vec<String>,
38}
39
40impl Forget {
41    pub(super) fn matches(&self, key: &str) -> bool {
42        key.starts_with(&self.prefix) && !self.keep.iter().any(|kept| key.starts_with(kept))
43    }
44}
45
46fn same_store(a: Option<&PreferencesRef>, b: Option<&PreferencesRef>) -> bool {
47    match (a, b) {
48        (Some(a), Some(b)) => Arc::ptr_eq(a, b),
49        (None, None) => true,
50        _ => false,
51    }
52}
53
54impl LocalSaves {
55    fn forgotten(&self, store: &PreferencesRef, key: &str) -> bool {
56        self.forgotten
57            .iter()
58            .any(|(forgotten, keys)| Arc::ptr_eq(forgotten, store) && keys.matches(key))
59    }
60}
61
62/// Keeps a composable's value saved while it lives, and its last value
63/// once it is dropped. It belongs to the thread that registered it.
64pub struct SaveableRegistration {
65    id: u64,
66    thread: std::marker::PhantomData<Rc<()>>,
67}
68
69impl Drop for SaveableRegistration {
70    fn drop(&mut self) {
71        crate::thread_state::try_with(|state| {
72            let removed = state.local_saves.borrow_mut().live.remove(&self.id);
73            let Some(save) = removed else {
74                return;
75            };
76            let Some(value) = (save.read)() else {
77                return;
78            };
79            let stored = save.store.as_ref().is_some_and(|store| {
80                !state.local_saves.borrow().forgotten(store, &save.key)
81                    && store.get(&save.key).as_deref() == Some(value.as_str())
82            });
83            if !stored {
84                state.local_saves.borrow_mut().retired.insert(
85                    save.key.clone(),
86                    RetiredSave {
87                        store: save.store.clone(),
88                        value,
89                    },
90                );
91            }
92        });
93    }
94}
95
96/// Saves what `read` returns under `key` in `store`, or only in memory when
97/// `store` is `None`, while the registration lives.
98pub(crate) fn register(
99    store: Option<PreferencesRef>,
100    key: String,
101    read: impl Fn() -> Option<String> + 'static,
102) -> SaveableRegistration {
103    let id = NEXT_SAVE.fetch_add(1, Ordering::Relaxed);
104    crate::thread_state::with(|state| {
105        let mut saves = state.local_saves.borrow_mut();
106        saves.retired.remove(&key);
107        saves.live.insert(
108            id,
109            Rc::new(LocalSave {
110                store,
111                key,
112                read: Box::new(read),
113            }),
114        )
115    });
116    SaveableRegistration {
117        id,
118        thread: std::marker::PhantomData,
119    }
120}
121
122/// The value saved under `key`: the last value a save left when it left the
123/// composition, else the stored one unless it waits to be removed.
124pub(crate) fn saved(store: Option<&PreferencesRef>, key: &str) -> Option<String> {
125    let (retired, forgotten) = crate::thread_state::with(|state| {
126        let saves = state.local_saves.borrow();
127        (
128            saves
129                .retired
130                .get(key)
131                .filter(|retired| same_store(retired.store.as_ref(), store))
132                .map(|retired| retired.value.clone()),
133            store.is_some_and(|store| saves.forgotten(store, key)),
134        )
135    });
136    if retired.is_some() {
137        return retired;
138    }
139    store
140        .filter(|_| !forgotten)
141        .and_then(|store| store.get(key))
142}
143
144/// Forgets the saves under `prefix`, apart from those under one of `keep`:
145/// live ones stop saving, the last values of dropped ones are discarded and
146/// stored ones are removed from `store` at the next save boundary. The
147/// values `retain` keeps under those keys are dropped.
148pub(crate) fn forget(store: Option<PreferencesRef>, prefix: String, keep: Vec<String>) {
149    let keys = Arc::new(Forget { prefix, keep });
150    if let Some(store) = &store {
151        super::stop_preference_saves(store, &keys);
152    }
153    let (stopped, retired): (Vec<_>, _) = crate::thread_state::with(|state| {
154        let mut saves = state.local_saves.borrow_mut();
155        saves.retired.retain(|key, _| !keys.matches(key));
156        if let Some(store) = store {
157            saves.forgotten.push((store, Arc::clone(&keys)));
158        }
159        let retired = state.retained.borrow_mut().forget(|key| keys.matches(key));
160        let stopped = saves
161            .live
162            .extract_if(.., |_, save| keys.matches(&save.key))
163            .collect();
164        (stopped, retired)
165    });
166    drop(stopped);
167    drop(retired);
168}
169
170/// What a save boundary took from this thread's saves, so that a completed
171/// boundary forgets what it made obsolete.
172pub(crate) struct Sampled {
173    retired: Vec<(String, String)>,
174    forgotten: usize,
175}
176
177/// Adds this thread's durable saves to `batch`.
178pub(super) fn sample(batch: &mut SaveBatch) -> Sampled {
179    let (live, retired, forgotten) = crate::thread_state::with(|state| {
180        let saves = state.local_saves.borrow();
181        (
182            saves
183                .live
184                .values()
185                .filter(|save| save.store.is_some())
186                .cloned()
187                .collect::<Vec<_>>(),
188            saves
189                .retired
190                .iter()
191                .filter_map(|(key, retired)| {
192                    let store = retired.store.as_ref()?;
193                    Some((Arc::clone(store), key.clone(), retired.value.clone()))
194                })
195                .collect::<Vec<_>>(),
196            saves.forgotten.clone(),
197        )
198    });
199    let sampled_forgotten = forgotten.len();
200    for (store, keys) in forgotten {
201        batch.forget(&store, keys);
202    }
203    let mut written = Vec::with_capacity(retired.len());
204    for (store, key, value) in retired {
205        batch.push(&store, key.clone(), value.clone());
206        written.push((key, value));
207    }
208    for save in live {
209        if let (Some(store), Some(value)) = (&save.store, (save.read)()) {
210            batch.push(store, save.key.clone(), value);
211        }
212    }
213    Sampled {
214        retired: written,
215        forgotten: sampled_forgotten,
216    }
217}
218
219impl Sampled {
220    /// Forgets the removals and the last values that a completed boundary
221    /// wrote, unless a newer value replaced one meanwhile.
222    pub(super) fn completed(self) {
223        crate::thread_state::with(|state| {
224            let mut saves = state.local_saves.borrow_mut();
225            let forgotten = self.forgotten.min(saves.forgotten.len());
226            saves.forgotten.drain(..forgotten);
227            for (key, value) in self.retired {
228                if saves
229                    .retired
230                    .get(&key)
231                    .is_some_and(|retired| retired.value == value)
232                {
233                    saves.retired.remove(&key);
234                }
235            }
236        });
237    }
238}