1use std::{
2 any::Any,
3 cell::RefCell,
4 hash::{Hash, Hasher},
5 rc::{Rc, Weak},
6 sync::Arc,
7};
8
9use crate::{
10 Composer, Key, RuntimeHandle, avalanche_location_key, composer, composer_context,
11 state::{MutationPolicy, OwnedMutableState},
12};
13
14fn provider_entry_source(key: &LocalKey, caller: Key) -> Key {
15 key.entry_source().mixed(caller)
16}
17
18pub struct ProvidedValue {
21 key: LocalKey,
22 #[expect(clippy::type_complexity, reason = "one boxed closure per local")]
23 apply: Box<dyn Fn(&Composer, Key) -> (Rc<dyn Any>, bool)>,
24}
25
26impl ProvidedValue {
27 pub(crate) fn key(&self) -> &LocalKey {
28 &self.key
29 }
30
31 pub(crate) fn into_entry(
34 self,
35 composer: &Composer,
36 site: Key,
37 ) -> (LocalKey, Rc<dyn Any>, bool) {
38 let Self { key, apply } = self;
39 let (entry, static_changed) = apply(composer, site);
40 (key, entry, static_changed)
41 }
42}
43
44#[expect(non_snake_case, reason = "names follow Compose's casing")]
47#[track_caller]
48pub fn CompositionLocalProvider(
49 values: impl IntoIterator<Item = ProvidedValue>,
50 content: impl FnOnce(),
51) {
52 let site = crate::caller_location_key();
53 composer_context::with_composer(|composer| {
54 composer.with_composition_locals(values, site, |_composer| content());
55 });
56}
57
58pub(crate) struct LocalStateEntry<T: Clone + 'static> {
59 state: OwnedMutableState<T>,
60}
61
62type LocalEquivalentFn<T> = dyn Fn(&T, &T) -> bool + Send + Sync + 'static;
63
64struct LocalValuePolicy<T: Clone + 'static> {
65 equivalent: Arc<LocalEquivalentFn<T>>,
66}
67
68impl<T: Clone + 'static> MutationPolicy<T> for LocalValuePolicy<T> {
69 fn equivalent(&self, a: &T, b: &T) -> bool {
70 (self.equivalent)(a, b)
71 }
72}
73
74impl<T: Clone + 'static> LocalStateEntry<T> {
75 fn new(initial: T, runtime: RuntimeHandle, equivalent: Arc<LocalEquivalentFn<T>>) -> Self {
76 Self {
77 state: OwnedMutableState::with_runtime_and_policy(
78 initial,
79 runtime,
80 Rc::new(LocalValuePolicy { equivalent }),
81 ),
82 }
83 }
84
85 fn set(&self, value: T) {
86 self.state.replace(value);
87 }
88
89 pub(crate) fn value(&self) -> T {
90 self.state.value()
91 }
92}
93
94pub(crate) struct StaticLocalEntry<T: Clone + 'static> {
95 value: RefCell<T>,
96}
97
98impl<T: Clone + 'static> StaticLocalEntry<T> {
99 fn new(value: T) -> Self {
100 Self {
101 value: RefCell::new(value),
102 }
103 }
104
105 fn set(&self, value: T, equivalent: &LocalEquivalentFn<T>) -> bool {
107 let mut current = self.value.borrow_mut();
108 if equivalent(¤t, &value) {
109 return false;
110 }
111 *current = value;
112 true
113 }
114
115 pub(crate) fn value(&self) -> T {
116 self.value.borrow().clone()
117 }
118}
119
120type ComputeLocalFn<T> = dyn Fn(&CompositionLocalAccessorScope) -> T;
121
122pub(crate) struct ComputedLocalEntry<T: 'static> {
126 compute: OwnedMutableState<Rc<ComputeLocalFn<T>>>,
127}
128
129impl<T: 'static> ComputedLocalEntry<T> {
130 pub(crate) fn value(&self) -> T {
131 let compute = self.compute.value();
132 compute(&CompositionLocalAccessorScope { _private: () })
133 }
134}
135
136pub(crate) struct StaticComputedLocalEntry<T: 'static> {
140 compute: Rc<ComputeLocalFn<T>>,
141}
142
143impl<T: 'static> StaticComputedLocalEntry<T> {
144 pub(crate) fn value(&self) -> T {
145 (self.compute)(&CompositionLocalAccessorScope { _private: () })
146 }
147}
148
149pub struct CompositionLocalAccessorScope {
153 _private: (),
154}
155
156impl CompositionLocalAccessorScope {
157 pub fn current_value<T>(&self, local: &impl ReadableCompositionLocal<T>) -> T {
160 local.read_current()
161 }
162}
163
164pub trait ReadableCompositionLocal<T> {
167 fn read_current(&self) -> T;
169}
170
171impl<T: Clone + 'static> ReadableCompositionLocal<T> for CompositionLocal<T> {
172 fn read_current(&self) -> T {
173 self.current()
174 }
175}
176
177impl<T: Clone + 'static> ReadableCompositionLocal<T> for StaticCompositionLocal<T> {
178 fn read_current(&self) -> T {
179 self.current()
180 }
181}
182
183#[derive(Clone)]
187pub struct CompositionLocal<T: Clone + 'static> {
188 pub(crate) key: LocalKey,
189 default: Rc<dyn Fn() -> T>,
190 equivalent: Arc<LocalEquivalentFn<T>>,
191}
192
193impl<T: Clone + 'static> PartialEq for CompositionLocal<T> {
194 fn eq(&self, other: &Self) -> bool {
195 self.key == other.key
196 }
197}
198
199impl<T: Clone + 'static> Eq for CompositionLocal<T> {}
200
201impl<T: Clone + 'static> CompositionLocal<T> {
202 #[track_caller]
205 pub fn provides(&self, value: T) -> ProvidedValue {
206 let key = self.key.clone();
207 let entry_source = provider_entry_source(&key, crate::caller_location_key());
208 let equivalent = Arc::clone(&self.equivalent);
209 ProvidedValue {
210 key,
211 apply: Box::new(move |composer: &Composer, site: Key| {
212 let runtime = composer.runtime_handle();
213 let source = entry_source.mixed(site);
214 let entry_ref = composer.remember_internal(source, || {
215 Rc::new(LocalStateEntry::new(
216 value.clone(),
217 runtime.clone(),
218 Arc::clone(&equivalent),
219 ))
220 });
221 entry_ref.update(|entry| entry.set(value.clone()));
222 entry_ref.with(|entry| (Rc::clone(entry) as Rc<dyn Any>, false))
223 }),
224 }
225 }
226
227 #[track_caller]
233 pub fn provides_computed(
234 &self,
235 compute: impl Fn(&CompositionLocalAccessorScope) -> T + 'static,
236 ) -> ProvidedValue {
237 let key = self.key.clone();
238 let entry_source = provider_entry_source(&key, crate::caller_location_key());
239 let compute: Rc<ComputeLocalFn<T>> = Rc::new(compute);
240 ProvidedValue {
241 key,
242 apply: Box::new(move |composer: &Composer, site: Key| {
243 let runtime = composer.runtime_handle();
244 let source = entry_source.mixed(site);
245 let entry_ref = composer.remember_internal(source, || {
246 Rc::new(ComputedLocalEntry {
247 compute: OwnedMutableState::with_runtime_and_policy(
248 Rc::clone(&compute),
249 runtime.clone(),
250 Rc::new(LocalValuePolicy::<Rc<ComputeLocalFn<T>>> {
251 equivalent: Arc::new(Rc::ptr_eq),
252 }),
253 ),
254 })
255 });
256 entry_ref.update(|entry| entry.compute.replace(Rc::clone(&compute)));
257 entry_ref.with(|entry| (Rc::clone(entry) as Rc<dyn Any>, false))
258 }),
259 }
260 }
261
262 pub fn current(&self) -> T {
266 composer_context::with_composer(|composer| composer.read_composition_local(self))
267 }
268
269 pub fn default_value(&self) -> T {
271 (self.default)()
272 }
273}
274
275#[cfg(test)]
276fn malformed_provided_value_for_test(key: LocalKey, entry: Rc<dyn Any>) -> ProvidedValue {
277 ProvidedValue {
278 key,
279 apply: Box::new(move |_, _| (Rc::clone(&entry), false)),
280 }
281}
282
283#[cfg(test)]
284pub(crate) fn malformed_composition_local_for_test<T: Clone + 'static>(
285 local: &CompositionLocal<T>,
286 entry: Rc<dyn Any>,
287) -> ProvidedValue {
288 malformed_provided_value_for_test(local.key.clone(), entry)
289}
290
291#[expect(non_snake_case, reason = "names follow Compose's casing")]
294pub fn compositionLocalOf<T: Clone + PartialEq + 'static>(
295 default: impl Fn() -> T + 'static,
296) -> CompositionLocal<T> {
297 compositionLocalOfWithPolicy(default, |current, next| current == next)
298}
299
300#[expect(non_snake_case, reason = "names follow Compose's casing")]
303pub fn compositionLocalOfWithPolicy<T: Clone + 'static>(
304 default: impl Fn() -> T + 'static,
305 equivalent: impl Fn(&T, &T) -> bool + Send + Sync + 'static,
306) -> CompositionLocal<T> {
307 CompositionLocal {
308 key: LocalKey::new(),
309 default: Rc::new(default),
310 equivalent: Arc::new(equivalent),
311 }
312}
313
314#[derive(Clone)]
321pub struct StaticCompositionLocal<T: Clone + 'static> {
322 pub(crate) key: LocalKey,
323 default: Rc<dyn Fn() -> T>,
324 equivalent: Arc<LocalEquivalentFn<T>>,
325 last_read: Rc<RefCell<Option<StaticRead<T>>>>,
329}
330
331enum Provided<T: Clone + 'static> {
333 Value(Rc<StaticLocalEntry<T>>),
334 Computed(Rc<StaticComputedLocalEntry<T>>),
335 Default,
336}
337
338impl<T: Clone + 'static> Provided<T> {
339 fn downgrade(&self) -> StaticFound<T> {
340 match self {
341 Self::Value(entry) => StaticFound::Value(Rc::downgrade(entry)),
342 Self::Computed(entry) => StaticFound::Computed(Rc::downgrade(entry)),
343 Self::Default => StaticFound::Default,
344 }
345 }
346
347 fn read(self) -> Result<T, StaticDeferred<T>> {
350 match self {
351 Self::Value(entry) => Ok(entry.value()),
352 Self::Computed(entry) => Err(StaticDeferred::Computed(entry)),
353 Self::Default => Err(StaticDeferred::Default),
354 }
355 }
356}
357
358enum StaticFound<T: Clone + 'static> {
360 Value(Weak<StaticLocalEntry<T>>),
361 Computed(Weak<StaticComputedLocalEntry<T>>),
362 Default,
363}
364
365impl<T: Clone + 'static> StaticFound<T> {
366 fn upgrade(&self) -> Option<Provided<T>> {
367 match self {
368 Self::Value(entry) => entry.upgrade().map(Provided::Value),
369 Self::Computed(entry) => entry.upgrade().map(Provided::Computed),
370 Self::Default => Some(Provided::Default),
371 }
372 }
373}
374
375pub(crate) enum StaticDeferred<T: Clone + 'static> {
378 Computed(Rc<StaticComputedLocalEntry<T>>),
379 Default,
380}
381
382impl<T: Clone + 'static> StaticDeferred<T> {
383 pub(crate) fn value(self, local: &StaticCompositionLocal<T>) -> T {
384 match self {
385 Self::Computed(entry) => entry.value(),
386 Self::Default => local.default_value(),
387 }
388 }
389}
390
391struct StaticRead<T: Clone + 'static> {
394 frame: Option<Weak<composer::LocalFrame>>,
395 found: StaticFound<T>,
396}
397
398fn is_top_frame(frame: &Option<Weak<composer::LocalFrame>>, stack: &LocalStackSnapshot) -> bool {
401 match (frame, stack) {
402 (Some(frame), Some(top)) => std::ptr::eq(frame.as_ptr(), Rc::as_ptr(top)),
403 (None, None) => true,
404 _ => false,
405 }
406}
407
408impl<T: Clone + 'static> PartialEq for StaticCompositionLocal<T> {
409 fn eq(&self, other: &Self) -> bool {
410 self.key == other.key
411 }
412}
413
414impl<T: Clone + 'static> Eq for StaticCompositionLocal<T> {}
415
416impl<T: Clone + 'static> StaticCompositionLocal<T> {
417 pub(crate) fn provided_under(
421 &self,
422 stack: &LocalStackSnapshot,
423 ) -> Result<T, StaticDeferred<T>> {
424 let mut last = self.last_read.borrow_mut();
425 if let Some(read) = &*last
426 && is_top_frame(&read.frame, stack)
427 && let Some(provided) = read.found.upgrade()
428 {
429 return provided.read();
430 }
431 let provided = self.find(stack);
432 *last = Some(StaticRead {
433 frame: stack.as_ref().map(Rc::downgrade),
434 found: provided.downgrade(),
435 });
436 provided.read()
437 }
438
439 fn find(&self, stack: &LocalStackSnapshot) -> Provided<T> {
440 let Some(entry) = composer::provided_entry(stack, &self.key) else {
441 return Provided::Default;
442 };
443 match entry.downcast::<StaticLocalEntry<T>>() {
444 Ok(entry) => Provided::Value(entry),
445 Err(entry) => match entry.downcast::<StaticComputedLocalEntry<T>>() {
446 Ok(entry) => Provided::Computed(entry),
447 Err(_) => {
448 log::error!(
449 "composition local entry type mismatch for key {:?}",
450 self.key
451 );
452 Provided::Default
453 }
454 },
455 }
456 }
457
458 #[track_caller]
461 pub fn provides(&self, value: T) -> ProvidedValue {
462 let key = self.key.clone();
463 let entry_source = provider_entry_source(&key, crate::caller_location_key());
464 let equivalent = Arc::clone(&self.equivalent);
465 ProvidedValue {
466 key,
467 apply: Box::new(move |composer: &Composer, site: Key| {
468 let source = entry_source.mixed(site);
469 let entry_ref = composer
470 .remember_internal(source, || Rc::new(StaticLocalEntry::new(value.clone())));
471 let changed = entry_ref.update(|entry| entry.set(value.clone(), &*equivalent));
472 entry_ref.with(|entry| (Rc::clone(entry) as Rc<dyn Any>, changed))
473 }),
474 }
475 }
476
477 #[track_caller]
482 pub fn provides_computed(
483 &self,
484 compute: impl Fn(&CompositionLocalAccessorScope) -> T + 'static,
485 ) -> ProvidedValue {
486 let compute: Rc<ComputeLocalFn<T>> = Rc::new(compute);
487 ProvidedValue {
488 key: self.key.clone(),
489 apply: Box::new(move |_: &Composer, _: Key| {
490 let entry = Rc::new(StaticComputedLocalEntry {
491 compute: Rc::clone(&compute),
492 });
493 (entry as Rc<dyn Any>, true)
494 }),
495 }
496 }
497
498 pub fn current(&self) -> T {
501 composer_context::with_composer(|composer| composer.read_static_composition_local(self))
502 }
503
504 pub fn default_value(&self) -> T {
506 (self.default)()
507 }
508}
509
510#[cfg(test)]
511pub(crate) fn malformed_static_composition_local_for_test<T: Clone + 'static>(
512 local: &StaticCompositionLocal<T>,
513 entry: Rc<dyn Any>,
514) -> ProvidedValue {
515 malformed_provided_value_for_test(local.key.clone(), entry)
516}
517
518#[expect(non_snake_case, reason = "names follow Compose's casing")]
520pub fn staticCompositionLocalOf<T: Clone + PartialEq + 'static>(
521 default: impl Fn() -> T + 'static,
522) -> StaticCompositionLocal<T> {
523 staticCompositionLocalOfWithPolicy(default, |current, next| current == next)
524}
525
526#[expect(non_snake_case, reason = "names follow Compose's casing")]
529pub fn staticCompositionLocalOfWithPolicy<T: Clone + 'static>(
530 default: impl Fn() -> T + 'static,
531 equivalent: impl Fn(&T, &T) -> bool + Send + Sync + 'static,
532) -> StaticCompositionLocal<T> {
533 StaticCompositionLocal {
534 key: LocalKey::new(),
535 default: Rc::new(default),
536 equivalent: Arc::new(equivalent),
537 last_read: Rc::default(),
538 }
539}
540
541#[expect(non_snake_case, reason = "names follow Compose's casing")]
547pub fn compositionLocalWithComputedDefaultOf<T: Clone + PartialEq + 'static>(
548 compute: impl Fn(&CompositionLocalAccessorScope) -> T + 'static,
549) -> CompositionLocal<T> {
550 compositionLocalOf(move || compute(&CompositionLocalAccessorScope { _private: () }))
551}
552
553#[expect(non_snake_case, reason = "names follow Compose's casing")]
557pub fn staticCompositionLocalWithComputedDefaultOf<T: Clone + PartialEq + 'static>(
558 compute: impl Fn(&CompositionLocalAccessorScope) -> T + 'static,
559) -> StaticCompositionLocal<T> {
560 staticCompositionLocalOf(move || compute(&CompositionLocalAccessorScope { _private: () }))
561}
562
563pub(crate) type LocalStackSnapshot = Option<Rc<composer::LocalFrame>>;
568
569#[derive(Clone)]
570pub(crate) struct LocalKey(Rc<()>);
571
572impl LocalKey {
573 fn new() -> Self {
574 Self(Rc::new(()))
575 }
576
577 pub(crate) fn entry_source(&self) -> Key {
578 Key(avalanche_location_key(Rc::as_ptr(&self.0) as usize as u64))
579 }
580}
581
582impl std::fmt::Debug for LocalKey {
583 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
584 f.debug_tuple("LocalKey")
585 .field(&(Rc::as_ptr(&self.0) as usize))
586 .finish()
587 }
588}
589
590impl PartialEq for LocalKey {
591 fn eq(&self, other: &Self) -> bool {
592 Rc::ptr_eq(&self.0, &other.0)
593 }
594}
595
596impl Eq for LocalKey {}
597
598impl Hash for LocalKey {
599 fn hash<H: Hasher>(&self, state: &mut H) {
600 Rc::as_ptr(&self.0).hash(state);
601 }
602}