Skip to main content

cranpose_core/
composition_locals.rs

1use std::{
2    any::Any,
3    cell::RefCell,
4    rc::{Rc, Weak},
5    sync::Arc,
6};
7
8use crate::{
9    Composer, LocalKey, RuntimeHandle, composer_context,
10    state::{MutationPolicy, OwnedMutableState},
11};
12
13fn provider_entry_source(key: &LocalKey, caller: crate::Key) -> crate::Key {
14    (key.entry_source() ^ caller).wrapping_mul(0x0000_0100_0000_01b3)
15}
16
17/// A composition local paired with the value to provide, made by `provides` and passed to
18/// `CompositionLocalProvider`.
19pub struct ProvidedValue {
20    key: LocalKey,
21    #[expect(clippy::type_complexity)]
22    apply: Box<dyn Fn(&Composer, crate::Key) -> (Rc<dyn Any>, bool)>,
23}
24
25impl ProvidedValue {
26    pub(crate) fn key(&self) -> &LocalKey {
27        &self.key
28    }
29
30    /// The provided entry, and whether it replaced a different value of a
31    /// static local that this site provided before.
32    pub(crate) fn into_entry(
33        self,
34        composer: &Composer,
35        site: crate::Key,
36    ) -> (LocalKey, Rc<dyn Any>, bool) {
37        let ProvidedValue { key, apply } = self;
38        let (entry, static_changed) = apply(composer, site);
39        (key, entry, static_changed)
40    }
41}
42
43/// Makes `values` available to `content` and everything it composes. Where two values are for the
44/// same local, the last wins. Compose's `CompositionLocalProvider`.
45#[expect(non_snake_case)]
46#[track_caller]
47pub fn CompositionLocalProvider(
48    values: impl IntoIterator<Item = ProvidedValue>,
49    content: impl FnOnce(),
50) {
51    let site = crate::caller_location_key();
52    composer_context::with_composer(|composer| {
53        composer.with_composition_locals(values, site, |_composer| content());
54    });
55}
56
57pub(crate) struct LocalStateEntry<T: Clone + 'static> {
58    state: OwnedMutableState<T>,
59}
60
61type LocalEquivalentFn<T> = dyn Fn(&T, &T) -> bool + Send + Sync + 'static;
62
63struct LocalValuePolicy<T: Clone + 'static> {
64    equivalent: Arc<LocalEquivalentFn<T>>,
65}
66
67impl<T: Clone + 'static> MutationPolicy<T> for LocalValuePolicy<T> {
68    fn equivalent(&self, a: &T, b: &T) -> bool {
69        (self.equivalent)(a, b)
70    }
71}
72
73impl<T: Clone + 'static> LocalStateEntry<T> {
74    fn new(initial: T, runtime: RuntimeHandle, equivalent: Arc<LocalEquivalentFn<T>>) -> Self {
75        Self {
76            state: OwnedMutableState::with_runtime_and_policy(
77                initial,
78                runtime,
79                Rc::new(LocalValuePolicy { equivalent }),
80            ),
81        }
82    }
83
84    fn set(&self, value: T) {
85        self.state.replace(value);
86    }
87
88    pub(crate) fn value(&self) -> T {
89        self.state.value()
90    }
91}
92
93pub(crate) struct StaticLocalEntry<T: Clone + 'static> {
94    value: RefCell<T>,
95}
96
97impl<T: Clone + 'static> StaticLocalEntry<T> {
98    fn new(value: T) -> Self {
99        Self {
100            value: RefCell::new(value),
101        }
102    }
103
104    /// Stores `value` and says whether it differs from the stored one.
105    fn set(&self, value: T, equivalent: &LocalEquivalentFn<T>) -> bool {
106        let mut current = self.value.borrow_mut();
107        if equivalent(&current, &value) {
108            return false;
109        }
110        *current = value;
111        true
112    }
113
114    pub(crate) fn value(&self) -> T {
115        self.value.borrow().clone()
116    }
117}
118
119type ComputeLocalFn<T> = dyn Fn(&CompositionLocalAccessorScope) -> T;
120
121/// The value of a local whose provider computes it where it is read, from
122/// the other locals there. A reader recomposes when the provider gives a
123/// new computation, as with Compose's lambdas that are not remembered.
124pub(crate) struct ComputedLocalEntry<T: 'static> {
125    compute: OwnedMutableState<Rc<ComputeLocalFn<T>>>,
126}
127
128impl<T: 'static> ComputedLocalEntry<T> {
129    pub(crate) fn value(&self) -> T {
130        let compute = self.compute.value();
131        compute(&CompositionLocalAccessorScope { _private: () })
132    }
133}
134
135/// A static local's computed value: see [`ComputedLocalEntry`]. A new
136/// computation recomposes all the provider's content, as a new static value
137/// does.
138pub(crate) struct StaticComputedLocalEntry<T: 'static> {
139    compute: Rc<ComputeLocalFn<T>>,
140}
141
142impl<T: 'static> StaticComputedLocalEntry<T> {
143    pub(crate) fn value(&self) -> T {
144        (self.compute)(&CompositionLocalAccessorScope { _private: () })
145    }
146}
147
148/// What a computed composition local's value reads other locals through:
149/// their values where the computed local is read, not where it was
150/// provided. Compose's `CompositionLocalAccessorScope`.
151pub struct CompositionLocalAccessorScope {
152    _private: (),
153}
154
155impl CompositionLocalAccessorScope {
156    /// The value `local` has where the computed local is read. Compose's
157    /// `CompositionLocal.currentValue` in this scope.
158    pub fn current_value<T>(&self, local: &impl ReadableCompositionLocal<T>) -> T {
159        local.read_current()
160    }
161}
162
163/// A composition local whose current value a computed local can read; see
164/// [`CompositionLocalAccessorScope`].
165pub trait ReadableCompositionLocal<T> {
166    /// The local's value where it is read, as `current` gives it.
167    fn read_current(&self) -> T;
168}
169
170impl<T: Clone + 'static> ReadableCompositionLocal<T> for CompositionLocal<T> {
171    fn read_current(&self) -> T {
172        self.current()
173    }
174}
175
176impl<T: Clone + 'static> ReadableCompositionLocal<T> for StaticCompositionLocal<T> {
177    fn read_current(&self) -> T {
178        self.current()
179    }
180}
181
182/// A value that composables read from their surroundings without passing it as a parameter, as
183/// Jetpack Compose's `compositionLocalOf`. A scope that reads `current` recomposes when the
184/// provided value changes.
185#[derive(Clone)]
186pub struct CompositionLocal<T: Clone + 'static> {
187    pub(crate) key: LocalKey,
188    default: Rc<dyn Fn() -> T>,
189    equivalent: Arc<LocalEquivalentFn<T>>,
190}
191
192impl<T: Clone + 'static> PartialEq for CompositionLocal<T> {
193    fn eq(&self, other: &Self) -> bool {
194        self.key == other.key
195    }
196}
197
198impl<T: Clone + 'static> Eq for CompositionLocal<T> {}
199
200impl<T: Clone + 'static> CompositionLocal<T> {
201    /// Pairs this local with `value` for use in `CompositionLocalProvider`. When a later
202    /// composition provides a different value, the scopes that read the local recompose.
203    #[track_caller]
204    pub fn provides(&self, value: T) -> ProvidedValue {
205        let key = self.key.clone();
206        let entry_source = provider_entry_source(&key, crate::caller_location_key());
207        let equivalent = Arc::clone(&self.equivalent);
208        ProvidedValue {
209            key,
210            apply: Box::new(move |composer: &Composer, site: crate::Key| {
211                let runtime = composer.runtime_handle();
212                let source = (entry_source ^ site).wrapping_mul(0x0000_0100_0000_01b3);
213                let entry_ref = composer.remember_internal(source, || {
214                    Rc::new(LocalStateEntry::new(
215                        value.clone(),
216                        runtime.clone(),
217                        Arc::clone(&equivalent),
218                    ))
219                });
220                entry_ref.update(|entry| entry.set(value.clone()));
221                entry_ref.with(|entry| (entry.clone() as Rc<dyn Any>, false))
222            }),
223        }
224    }
225
226    /// Pairs this local with `compute`, which runs at each read of the local
227    /// under the provider and reads other locals there through its scope.
228    /// A reader recomposes when a local the computation read changes, and
229    /// when a later composition of the provider gives a new computation.
230    /// Compose's `providesComputed`.
231    #[track_caller]
232    pub fn provides_computed(
233        &self,
234        compute: impl Fn(&CompositionLocalAccessorScope) -> T + 'static,
235    ) -> ProvidedValue {
236        let key = self.key.clone();
237        let entry_source = provider_entry_source(&key, crate::caller_location_key());
238        let compute: Rc<ComputeLocalFn<T>> = Rc::new(compute);
239        ProvidedValue {
240            key,
241            apply: Box::new(move |composer: &Composer, site: crate::Key| {
242                let runtime = composer.runtime_handle();
243                let source = (entry_source ^ site).wrapping_mul(0x0000_0100_0000_01b3);
244                let entry_ref = composer.remember_internal(source, || {
245                    Rc::new(ComputedLocalEntry {
246                        compute: OwnedMutableState::with_runtime_and_policy(
247                            Rc::clone(&compute),
248                            runtime.clone(),
249                            Rc::new(LocalValuePolicy::<Rc<ComputeLocalFn<T>>> {
250                                equivalent: Arc::new(Rc::ptr_eq),
251                            }),
252                        ),
253                    })
254                });
255                entry_ref.update(|entry| entry.compute.replace(Rc::clone(&compute)));
256                entry_ref.with(|entry| (entry.clone() as Rc<dyn Any>, false))
257            }),
258        }
259    }
260
261    /// Returns the value from the innermost enclosing provider, or the default value when none is
262    /// in scope. Must be called during composition. The reading scope recomposes when the provided
263    /// value changes.
264    pub fn current(&self) -> T {
265        composer_context::with_composer(|composer| composer.read_composition_local(self))
266    }
267
268    /// Returns the value that `current` gives where no provider is in scope.
269    pub fn default_value(&self) -> T {
270        (self.default)()
271    }
272}
273
274#[cfg(test)]
275fn malformed_provided_value_for_test(key: LocalKey, entry: Rc<dyn Any>) -> ProvidedValue {
276    ProvidedValue {
277        key,
278        apply: Box::new(move |_, _| (entry.clone(), false)),
279    }
280}
281
282#[cfg(test)]
283pub(crate) fn malformed_composition_local_for_test<T: Clone + 'static>(
284    local: &CompositionLocal<T>,
285    entry: Rc<dyn Any>,
286) -> ProvidedValue {
287    malformed_provided_value_for_test(local.key.clone(), entry)
288}
289
290/// Creates a [`CompositionLocal`] whose default is made by `default`. A provided value replaces the
291/// previous one when it differs by `==`. Compose's `compositionLocalOf`.
292#[expect(non_snake_case)]
293pub fn compositionLocalOf<T: Clone + PartialEq + 'static>(
294    default: impl Fn() -> T + 'static,
295) -> CompositionLocal<T> {
296    compositionLocalOfWithPolicy(default, |current, next| current == next)
297}
298
299/// Creates a [`CompositionLocal`] whose default is made by `default`. A provided value replaces the
300/// previous one only when `equivalent` returns `false` for the two.
301#[expect(non_snake_case)]
302pub fn compositionLocalOfWithPolicy<T: Clone + 'static>(
303    default: impl Fn() -> T + 'static,
304    equivalent: impl Fn(&T, &T) -> bool + Send + Sync + 'static,
305) -> CompositionLocal<T> {
306    CompositionLocal {
307        key: LocalKey::new(),
308        default: Rc::new(default),
309        equivalent: Arc::new(equivalent),
310    }
311}
312
313/// A composition local for values that rarely change, as Jetpack Compose's
314/// `staticCompositionLocalOf`. A read of `.current()` subscribes nothing, so
315/// it costs less than a read of a [`CompositionLocal`]. In return, a
316/// provider whose value changes recomposes everything it provides to, not
317/// only the readers: every composable call under it runs its body, and
318/// every subcomposition under it composes again the same way.
319#[derive(Clone)]
320pub struct StaticCompositionLocal<T: Clone + 'static> {
321    pub(crate) key: LocalKey,
322    default: Rc<dyn Fn() -> T>,
323    equivalent: Arc<LocalEquivalentFn<T>>,
324    /// The provider frame the last read read under and what it found there,
325    /// shared by the local's clones: the next read under the same frame
326    /// takes it without walking the providers.
327    last_read: Rc<RefCell<Option<StaticRead<T>>>>,
328}
329
330/// What a static local's read found under a provider frame.
331enum Provided<T: Clone + 'static> {
332    Value(Rc<StaticLocalEntry<T>>),
333    Computed(Rc<StaticComputedLocalEntry<T>>),
334    Default,
335}
336
337impl<T: Clone + 'static> Provided<T> {
338    fn downgrade(&self) -> StaticFound<T> {
339        match self {
340            Self::Value(entry) => StaticFound::Value(Rc::downgrade(entry)),
341            Self::Computed(entry) => StaticFound::Computed(Rc::downgrade(entry)),
342            Self::Default => StaticFound::Default,
343        }
344    }
345
346    /// The value of an entry, read each time as its provider updates the
347    /// entry in place, or what reads it once the provider stack is free.
348    fn read(self) -> Result<T, StaticDeferred<T>> {
349        match self {
350            Self::Value(entry) => Ok(entry.value()),
351            Self::Computed(entry) => Err(StaticDeferred::Computed(entry)),
352            Self::Default => Err(StaticDeferred::Default),
353        }
354    }
355}
356
357/// [`Provided`], held so the last read keeps no provider's value alive.
358enum StaticFound<T: Clone + 'static> {
359    Value(Weak<StaticLocalEntry<T>>),
360    Computed(Weak<StaticComputedLocalEntry<T>>),
361    Default,
362}
363
364impl<T: Clone + 'static> StaticFound<T> {
365    fn upgrade(&self) -> Option<Provided<T>> {
366        match self {
367            Self::Value(entry) => entry.upgrade().map(Provided::Value),
368            Self::Computed(entry) => entry.upgrade().map(Provided::Computed),
369            Self::Default => Some(Provided::Default),
370        }
371    }
372}
373
374/// A static local's value that runs once the provider stack is released,
375/// as it may read other locals.
376pub(crate) enum StaticDeferred<T: Clone + 'static> {
377    Computed(Rc<StaticComputedLocalEntry<T>>),
378    Default,
379}
380
381impl<T: Clone + 'static> StaticDeferred<T> {
382    pub(crate) fn value(self, local: &StaticCompositionLocal<T>) -> T {
383        match self {
384            Self::Computed(entry) => entry.value(),
385            Self::Default => local.default_value(),
386        }
387    }
388}
389
390/// The innermost provider frame a static local was last read under, and
391/// what it found there.
392struct StaticRead<T: Clone + 'static> {
393    frame: Option<Weak<crate::composer::LocalFrame>>,
394    found: StaticFound<T>,
395}
396
397/// Whether `frame` is the innermost frame of `stack`. A weak handle keeps
398/// its frame's allocation, so no other frame takes the address meanwhile.
399fn is_top_frame(
400    frame: &Option<Weak<crate::composer::LocalFrame>>,
401    stack: &crate::LocalStackSnapshot,
402) -> bool {
403    match (frame, stack) {
404        (Some(frame), Some(top)) => std::ptr::eq(frame.as_ptr(), Rc::as_ptr(top)),
405        (None, None) => true,
406        _ => false,
407    }
408}
409
410impl<T: Clone + 'static> PartialEq for StaticCompositionLocal<T> {
411    fn eq(&self, other: &Self) -> bool {
412        self.key == other.key
413    }
414}
415
416impl<T: Clone + 'static> Eq for StaticCompositionLocal<T> {}
417
418impl<T: Clone + 'static> StaticCompositionLocal<T> {
419    /// The value `stack` provides for this local, or what to read it from
420    /// when it is computed or the default: those may read other locals, so
421    /// they run once the caller releases the stack.
422    pub(crate) fn provided_under(
423        &self,
424        stack: &crate::LocalStackSnapshot,
425    ) -> Result<T, StaticDeferred<T>> {
426        let mut last = self.last_read.borrow_mut();
427        if let Some(read) = &*last
428            && is_top_frame(&read.frame, stack)
429            && let Some(provided) = read.found.upgrade()
430        {
431            return provided.read();
432        }
433        let provided = self.find(stack);
434        *last = Some(StaticRead {
435            frame: stack.as_ref().map(Rc::downgrade),
436            found: provided.downgrade(),
437        });
438        provided.read()
439    }
440
441    fn find(&self, stack: &crate::LocalStackSnapshot) -> Provided<T> {
442        let Some(entry) = crate::composer::provided_entry(stack, &self.key) else {
443            return Provided::Default;
444        };
445        match entry.downcast::<StaticLocalEntry<T>>() {
446            Ok(entry) => Provided::Value(entry),
447            Err(entry) => match entry.downcast::<StaticComputedLocalEntry<T>>() {
448                Ok(entry) => Provided::Computed(entry),
449                Err(_) => {
450                    log::error!(
451                        "composition local entry type mismatch for key {:?}",
452                        self.key
453                    );
454                    Provided::Default
455                }
456            },
457        }
458    }
459
460    /// Pairs this local with `value` for use in `CompositionLocalProvider`. When a later
461    /// composition provides a different value, everything under the provider recomposes.
462    #[track_caller]
463    pub fn provides(&self, value: T) -> ProvidedValue {
464        let key = self.key.clone();
465        let entry_source = provider_entry_source(&key, crate::caller_location_key());
466        let equivalent = Arc::clone(&self.equivalent);
467        ProvidedValue {
468            key,
469            apply: Box::new(move |composer: &Composer, site: crate::Key| {
470                let source = (entry_source ^ site).wrapping_mul(0x0000_0100_0000_01b3);
471                let entry_ref = composer
472                    .remember_internal(source, || Rc::new(StaticLocalEntry::new(value.clone())));
473                let changed = entry_ref.update(|entry| entry.set(value.clone(), &*equivalent));
474                entry_ref.with(|entry| (entry.clone() as Rc<dyn Any>, changed))
475            }),
476        }
477    }
478
479    /// Pairs this local with `compute`, which runs at each read of the local
480    /// under the provider and reads other locals there through its scope.
481    /// Each composition of the provider gives a new computation, which
482    /// recomposes everything under it. Compose's `providesComputed`.
483    #[track_caller]
484    pub fn provides_computed(
485        &self,
486        compute: impl Fn(&CompositionLocalAccessorScope) -> T + 'static,
487    ) -> ProvidedValue {
488        let compute: Rc<ComputeLocalFn<T>> = Rc::new(compute);
489        ProvidedValue {
490            key: self.key.clone(),
491            apply: Box::new(move |_: &Composer, _: crate::Key| {
492                let entry = Rc::new(StaticComputedLocalEntry {
493                    compute: Rc::clone(&compute),
494                });
495                (entry as Rc<dyn Any>, true)
496            }),
497        }
498    }
499
500    /// Returns the value from the innermost enclosing provider, or the default value when none is
501    /// in scope. Must be called during composition. A read subscribes nothing.
502    pub fn current(&self) -> T {
503        composer_context::with_composer(|composer| composer.read_static_composition_local(self))
504    }
505
506    /// Returns the value that `current` gives where no provider is in scope.
507    pub fn default_value(&self) -> T {
508        (self.default)()
509    }
510}
511
512#[cfg(test)]
513pub(crate) fn malformed_static_composition_local_for_test<T: Clone + 'static>(
514    local: &StaticCompositionLocal<T>,
515    entry: Rc<dyn Any>,
516) -> ProvidedValue {
517    malformed_provided_value_for_test(local.key.clone(), entry)
518}
519
520/// A [`StaticCompositionLocal`] whose provided values compare with `==`.
521#[expect(non_snake_case)]
522pub fn staticCompositionLocalOf<T: Clone + PartialEq + 'static>(
523    default: impl Fn() -> T + 'static,
524) -> StaticCompositionLocal<T> {
525    staticCompositionLocalOfWithPolicy(default, |current, next| current == next)
526}
527
528/// A [`StaticCompositionLocal`] whose provider recomposes its content when
529/// `equivalent` says a new value differs from the last one.
530#[expect(non_snake_case)]
531pub fn staticCompositionLocalOfWithPolicy<T: Clone + 'static>(
532    default: impl Fn() -> T + 'static,
533    equivalent: impl Fn(&T, &T) -> bool + Send + Sync + 'static,
534) -> StaticCompositionLocal<T> {
535    StaticCompositionLocal {
536        key: LocalKey::new(),
537        default: Rc::new(default),
538        equivalent: Arc::new(equivalent),
539        last_read: Rc::default(),
540    }
541}
542
543/// A [`CompositionLocal`] whose default is computed at each read without a
544/// provider, from the other locals there. A scope that reads it recomposes
545/// when a dynamic local the computation read changes. As the default reads
546/// locals, it is computed during composition only. Compose's
547/// `compositionLocalWithComputedDefaultOf`.
548#[expect(non_snake_case)]
549pub fn compositionLocalWithComputedDefaultOf<T: Clone + PartialEq + 'static>(
550    compute: impl Fn(&CompositionLocalAccessorScope) -> T + 'static,
551) -> CompositionLocal<T> {
552    compositionLocalOf(move || compute(&CompositionLocalAccessorScope { _private: () }))
553}
554
555/// A [`StaticCompositionLocal`] whose default is computed at each read
556/// without a provider, from the other locals there. Compose's
557/// `staticCompositionLocalWithComputedDefaultOf`.
558#[expect(non_snake_case)]
559pub fn staticCompositionLocalWithComputedDefaultOf<T: Clone + PartialEq + 'static>(
560    compute: impl Fn(&CompositionLocalAccessorScope) -> T + 'static,
561) -> StaticCompositionLocal<T> {
562    staticCompositionLocalOf(move || compute(&CompositionLocalAccessorScope { _private: () }))
563}