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
8/// The identity of one `provides` call: the local's own salt and the call
9/// site. Same-local providers at different sites must not adopt each other's
10/// entries when a neighbor leaves. Deliberately composer-free: a provider may
11/// be constructed anywhere, including inside a slot initializer that already
12/// holds the writer.
13fn provider_entry_source(key: &LocalKey, caller: crate::Key) -> crate::Key {
14    (key.entry_source() ^ caller).wrapping_mul(0x0000_0100_0000_01b3)
15}
16
17pub struct ProvidedValue {
18    key: LocalKey,
19    #[allow(clippy::type_complexity)] // Closure returns trait object for flexible local values
20    apply: Box<dyn Fn(&Composer, crate::Key) -> Rc<dyn Any>>,
21}
22
23impl ProvidedValue {
24    pub(crate) fn key(&self) -> &LocalKey {
25        &self.key
26    }
27
28    /// `site` is the provider call this value is applied under: two sibling
29    /// provider scopes fed from one construction site must not share entries.
30    pub(crate) fn into_entry(
31        self,
32        composer: &Composer,
33        site: crate::Key,
34    ) -> (LocalKey, Rc<dyn Any>) {
35        let ProvidedValue { key, apply } = self;
36        let entry = apply(composer, site);
37        (key, entry)
38    }
39}
40
41#[allow(non_snake_case)]
42#[track_caller]
43pub fn CompositionLocalProvider(
44    values: impl IntoIterator<Item = ProvidedValue>,
45    content: impl FnOnce(),
46) {
47    let site = crate::caller_location_key();
48    composer_context::with_composer(|composer| {
49        let provided: Vec<ProvidedValue> = values.into_iter().collect();
50        composer.with_composition_locals(provided, site, |_composer| content());
51    })
52}
53
54pub(crate) struct LocalStateEntry<T: Clone + 'static> {
55    state: OwnedMutableState<T>,
56}
57
58type LocalEquivalentFn<T> = dyn Fn(&T, &T) -> bool + Send + Sync + 'static;
59
60struct LocalValuePolicy<T: Clone + 'static> {
61    equivalent: Arc<LocalEquivalentFn<T>>,
62}
63
64impl<T: Clone + 'static> MutationPolicy<T> for LocalValuePolicy<T> {
65    fn equivalent(&self, a: &T, b: &T) -> bool {
66        (self.equivalent)(a, b)
67    }
68}
69
70impl<T: Clone + 'static> LocalStateEntry<T> {
71    fn new(initial: T, runtime: RuntimeHandle, equivalent: Arc<LocalEquivalentFn<T>>) -> Self {
72        Self {
73            state: OwnedMutableState::with_runtime_and_policy(
74                initial,
75                runtime,
76                Arc::new(LocalValuePolicy { equivalent }),
77            ),
78        }
79    }
80
81    fn set(&self, value: T) {
82        self.state.replace(value);
83    }
84
85    pub(crate) fn value(&self) -> T {
86        self.state.value()
87    }
88}
89
90pub(crate) struct StaticLocalEntry<T: Clone + 'static> {
91    value: RefCell<T>,
92}
93
94impl<T: Clone + 'static> StaticLocalEntry<T> {
95    fn new(value: T) -> Self {
96        Self {
97            value: RefCell::new(value),
98        }
99    }
100
101    fn set(&self, value: T) {
102        *self.value.borrow_mut() = value;
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>)
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)]
159pub(crate) fn malformed_composition_local_for_test<T: Clone + 'static>(
160    local: &CompositionLocal<T>,
161    entry: Rc<dyn Any>,
162) -> ProvidedValue {
163    let key = local.key.clone();
164    ProvidedValue {
165        key,
166        apply: Box::new(move |_, _| entry.clone()),
167    }
168}
169
170#[allow(non_snake_case)]
171pub fn compositionLocalOf<T: Clone + PartialEq + 'static>(
172    default: impl Fn() -> T + 'static,
173) -> CompositionLocal<T> {
174    compositionLocalOfWithPolicy(default, |current, next| current == next)
175}
176
177#[allow(non_snake_case)]
178pub fn compositionLocalOfWithPolicy<T: Clone + 'static>(
179    default: impl Fn() -> T + 'static,
180    equivalent: impl Fn(&T, &T) -> bool + Send + Sync + 'static,
181) -> CompositionLocal<T> {
182    CompositionLocal {
183        key: LocalKey::new(),
184        default: Rc::new(default),
185        equivalent: Arc::new(equivalent),
186    }
187}
188
189/// A `StaticCompositionLocal` is a CompositionLocal that is optimized for values that are
190/// unlikely to change. Unlike `CompositionLocal`, reads of a `StaticCompositionLocal` are not
191/// tracked by the recomposition system, which means:
192/// - Reading `.current()` does NOT establish a subscription
193/// - Changing the provided value does NOT automatically invalidate readers
194/// - This makes it more efficient for truly static values
195///
196/// This matches the API of Jetpack Compose's `staticCompositionLocalOf` but with simplified
197/// semantics. Use this for values that are guaranteed to never change during the lifetime of
198/// the CompositionLocalProvider scope (e.g., application-wide constants, configuration)
199#[derive(Clone)]
200pub struct StaticCompositionLocal<T: Clone + 'static> {
201    pub(crate) key: LocalKey,
202    default: Rc<dyn Fn() -> T>,
203}
204
205impl<T: Clone + 'static> PartialEq for StaticCompositionLocal<T> {
206    fn eq(&self, other: &Self) -> bool {
207        self.key == other.key
208    }
209}
210
211impl<T: Clone + 'static> Eq for StaticCompositionLocal<T> {}
212
213impl<T: Clone + 'static> StaticCompositionLocal<T> {
214    #[track_caller]
215    pub fn provides(&self, value: T) -> ProvidedValue {
216        let key = self.key.clone();
217        let entry_source = provider_entry_source(&key, crate::caller_location_key());
218        ProvidedValue {
219            key,
220            apply: Box::new(move |composer: &Composer, site: crate::Key| {
221                let source = (entry_source ^ site).wrapping_mul(0x0000_0100_0000_01b3);
222                let entry_ref = composer
223                    .remember_internal(source, || Rc::new(StaticLocalEntry::new(value.clone())));
224                entry_ref.update(|entry| entry.set(value.clone()));
225                entry_ref.with(|entry| entry.clone() as Rc<dyn Any>)
226            }),
227        }
228    }
229
230    pub fn current(&self) -> T {
231        composer_context::with_composer(|composer| composer.read_static_composition_local(self))
232    }
233
234    pub fn default_value(&self) -> T {
235        (self.default)()
236    }
237}
238
239#[cfg(test)]
240pub(crate) fn malformed_static_composition_local_for_test<T: Clone + 'static>(
241    local: &StaticCompositionLocal<T>,
242    entry: Rc<dyn Any>,
243) -> ProvidedValue {
244    let key = local.key.clone();
245    ProvidedValue {
246        key,
247        apply: Box::new(move |_, _| entry.clone()),
248    }
249}
250
251#[allow(non_snake_case)]
252pub fn staticCompositionLocalOf<T: Clone + 'static>(
253    default: impl Fn() -> T + 'static,
254) -> StaticCompositionLocal<T> {
255    StaticCompositionLocal {
256        key: LocalKey::new(),
257        default: Rc::new(default),
258    }
259}