Skip to main content

cranpose_core/
composition_locals.rs

1use std::{any::Any, cell::RefCell, rc::Rc, sync::Arc};
2
3use crate::{
4    Composer, LocalKey, RuntimeHandle, composer_context,
5    state::{MutationPolicy, OwnedMutableState},
6};
7
8fn provider_entry_source(key: &LocalKey, caller: crate::Key) -> crate::Key {
9    (key.entry_source() ^ caller).wrapping_mul(0x0000_0100_0000_01b3)
10}
11
12pub struct ProvidedValue {
13    key: LocalKey,
14    #[expect(clippy::type_complexity)]
15    apply: Box<dyn Fn(&Composer, crate::Key) -> (Rc<dyn Any>, bool)>,
16}
17
18impl ProvidedValue {
19    pub(crate) fn key(&self) -> &LocalKey {
20        &self.key
21    }
22
23    /// The provided entry, and whether it replaced a different value of a
24    /// static local that this site provided before.
25    pub(crate) fn into_entry(
26        self,
27        composer: &Composer,
28        site: crate::Key,
29    ) -> (LocalKey, Rc<dyn Any>, bool) {
30        let ProvidedValue { key, apply } = self;
31        let (entry, static_changed) = apply(composer, site);
32        (key, entry, static_changed)
33    }
34}
35
36#[expect(non_snake_case)]
37#[track_caller]
38pub fn CompositionLocalProvider(
39    values: impl IntoIterator<Item = ProvidedValue>,
40    content: impl FnOnce(),
41) {
42    let site = crate::caller_location_key();
43    composer_context::with_composer(|composer| {
44        composer.with_composition_locals(values, site, |_composer| content());
45    });
46}
47
48pub(crate) struct LocalStateEntry<T: Clone + 'static> {
49    state: OwnedMutableState<T>,
50}
51
52type LocalEquivalentFn<T> = dyn Fn(&T, &T) -> bool + Send + Sync + 'static;
53
54struct LocalValuePolicy<T: Clone + 'static> {
55    equivalent: Arc<LocalEquivalentFn<T>>,
56}
57
58impl<T: Clone + 'static> MutationPolicy<T> for LocalValuePolicy<T> {
59    fn equivalent(&self, a: &T, b: &T) -> bool {
60        (self.equivalent)(a, b)
61    }
62}
63
64impl<T: Clone + 'static> LocalStateEntry<T> {
65    fn new(initial: T, runtime: RuntimeHandle, equivalent: Arc<LocalEquivalentFn<T>>) -> Self {
66        Self {
67            state: OwnedMutableState::with_runtime_and_policy(
68                initial,
69                runtime,
70                Rc::new(LocalValuePolicy { equivalent }),
71            ),
72        }
73    }
74
75    fn set(&self, value: T) {
76        self.state.replace(value);
77    }
78
79    pub(crate) fn value(&self) -> T {
80        self.state.value()
81    }
82}
83
84pub(crate) struct StaticLocalEntry<T: Clone + 'static> {
85    value: RefCell<T>,
86}
87
88impl<T: Clone + 'static> StaticLocalEntry<T> {
89    fn new(value: T) -> Self {
90        Self {
91            value: RefCell::new(value),
92        }
93    }
94
95    /// Stores `value` and says whether it differs from the stored one.
96    fn set(&self, value: T, equivalent: &LocalEquivalentFn<T>) -> bool {
97        let mut current = self.value.borrow_mut();
98        if equivalent(&current, &value) {
99            return false;
100        }
101        *current = value;
102        true
103    }
104
105    pub(crate) fn value(&self) -> T {
106        self.value.borrow().clone()
107    }
108}
109
110#[derive(Clone)]
111pub struct CompositionLocal<T: Clone + 'static> {
112    pub(crate) key: LocalKey,
113    default: Rc<dyn Fn() -> T>,
114    equivalent: Arc<LocalEquivalentFn<T>>,
115}
116
117impl<T: Clone + 'static> PartialEq for CompositionLocal<T> {
118    fn eq(&self, other: &Self) -> bool {
119        self.key == other.key
120    }
121}
122
123impl<T: Clone + 'static> Eq for CompositionLocal<T> {}
124
125impl<T: Clone + 'static> CompositionLocal<T> {
126    #[track_caller]
127    pub fn provides(&self, value: T) -> ProvidedValue {
128        let key = self.key.clone();
129        let entry_source = provider_entry_source(&key, crate::caller_location_key());
130        let equivalent = Arc::clone(&self.equivalent);
131        ProvidedValue {
132            key,
133            apply: Box::new(move |composer: &Composer, site: crate::Key| {
134                let runtime = composer.runtime_handle();
135                let source = (entry_source ^ site).wrapping_mul(0x0000_0100_0000_01b3);
136                let entry_ref = composer.remember_internal(source, || {
137                    Rc::new(LocalStateEntry::new(
138                        value.clone(),
139                        runtime.clone(),
140                        Arc::clone(&equivalent),
141                    ))
142                });
143                entry_ref.update(|entry| entry.set(value.clone()));
144                entry_ref.with(|entry| (entry.clone() as Rc<dyn Any>, false))
145            }),
146        }
147    }
148
149    pub fn current(&self) -> T {
150        composer_context::with_composer(|composer| composer.read_composition_local(self))
151    }
152
153    pub fn default_value(&self) -> T {
154        (self.default)()
155    }
156}
157
158#[cfg(test)]
159fn malformed_provided_value_for_test(key: LocalKey, entry: Rc<dyn Any>) -> ProvidedValue {
160    ProvidedValue {
161        key,
162        apply: Box::new(move |_, _| (entry.clone(), false)),
163    }
164}
165
166#[cfg(test)]
167pub(crate) fn malformed_composition_local_for_test<T: Clone + 'static>(
168    local: &CompositionLocal<T>,
169    entry: Rc<dyn Any>,
170) -> ProvidedValue {
171    malformed_provided_value_for_test(local.key.clone(), entry)
172}
173
174#[expect(non_snake_case)]
175pub fn compositionLocalOf<T: Clone + PartialEq + 'static>(
176    default: impl Fn() -> T + 'static,
177) -> CompositionLocal<T> {
178    compositionLocalOfWithPolicy(default, |current, next| current == next)
179}
180
181#[expect(non_snake_case)]
182pub fn compositionLocalOfWithPolicy<T: Clone + 'static>(
183    default: impl Fn() -> T + 'static,
184    equivalent: impl Fn(&T, &T) -> bool + Send + Sync + 'static,
185) -> CompositionLocal<T> {
186    CompositionLocal {
187        key: LocalKey::new(),
188        default: Rc::new(default),
189        equivalent: Arc::new(equivalent),
190    }
191}
192
193/// A composition local for values that rarely change, as Jetpack Compose's
194/// `staticCompositionLocalOf`. A read of `.current()` subscribes nothing, so
195/// it costs less than a read of a [`CompositionLocal`]. In return, a
196/// provider whose value changes recomposes everything it provides to, not
197/// only the readers: every composable call under it runs its body, and
198/// every subcomposition under it composes again the same way.
199#[derive(Clone)]
200pub struct StaticCompositionLocal<T: Clone + 'static> {
201    pub(crate) key: LocalKey,
202    default: Rc<dyn Fn() -> T>,
203    equivalent: Arc<LocalEquivalentFn<T>>,
204}
205
206impl<T: Clone + 'static> PartialEq for StaticCompositionLocal<T> {
207    fn eq(&self, other: &Self) -> bool {
208        self.key == other.key
209    }
210}
211
212impl<T: Clone + 'static> Eq for StaticCompositionLocal<T> {}
213
214impl<T: Clone + 'static> StaticCompositionLocal<T> {
215    #[track_caller]
216    pub fn provides(&self, value: T) -> ProvidedValue {
217        let key = self.key.clone();
218        let entry_source = provider_entry_source(&key, crate::caller_location_key());
219        let equivalent = Arc::clone(&self.equivalent);
220        ProvidedValue {
221            key,
222            apply: Box::new(move |composer: &Composer, site: crate::Key| {
223                let source = (entry_source ^ site).wrapping_mul(0x0000_0100_0000_01b3);
224                let entry_ref = composer
225                    .remember_internal(source, || Rc::new(StaticLocalEntry::new(value.clone())));
226                let changed = entry_ref.update(|entry| entry.set(value.clone(), &*equivalent));
227                entry_ref.with(|entry| (entry.clone() as Rc<dyn Any>, changed))
228            }),
229        }
230    }
231
232    pub fn current(&self) -> T {
233        composer_context::with_composer(|composer| composer.read_static_composition_local(self))
234    }
235
236    pub fn default_value(&self) -> T {
237        (self.default)()
238    }
239}
240
241#[cfg(test)]
242pub(crate) fn malformed_static_composition_local_for_test<T: Clone + 'static>(
243    local: &StaticCompositionLocal<T>,
244    entry: Rc<dyn Any>,
245) -> ProvidedValue {
246    malformed_provided_value_for_test(local.key.clone(), entry)
247}
248
249/// A [`StaticCompositionLocal`] whose provided values compare with `==`.
250#[expect(non_snake_case)]
251pub fn staticCompositionLocalOf<T: Clone + PartialEq + 'static>(
252    default: impl Fn() -> T + 'static,
253) -> StaticCompositionLocal<T> {
254    staticCompositionLocalOfWithPolicy(default, |current, next| current == next)
255}
256
257/// A [`StaticCompositionLocal`] whose provider recomposes its content when
258/// `equivalent` says a new value differs from the last one.
259#[expect(non_snake_case)]
260pub fn staticCompositionLocalOfWithPolicy<T: Clone + 'static>(
261    default: impl Fn() -> T + 'static,
262    equivalent: impl Fn(&T, &T) -> bool + Send + Sync + 'static,
263) -> StaticCompositionLocal<T> {
264    StaticCompositionLocal {
265        key: LocalKey::new(),
266        default: Rc::new(default),
267        equivalent: Arc::new(equivalent),
268    }
269}