cranpose_core/
composition_locals.rs1use 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 #[allow(clippy::type_complexity)]
15 apply: Box<dyn Fn(&Composer, crate::Key) -> Rc<dyn Any>>,
16}
17
18impl ProvidedValue {
19 pub(crate) fn key(&self) -> &LocalKey {
20 &self.key
21 }
22
23 pub(crate) fn into_entry(
24 self,
25 composer: &Composer,
26 site: crate::Key,
27 ) -> (LocalKey, Rc<dyn Any>) {
28 let ProvidedValue { key, apply } = self;
29 let entry = apply(composer, site);
30 (key, entry)
31 }
32}
33
34#[allow(non_snake_case)]
35#[track_caller]
36pub fn CompositionLocalProvider(
37 values: impl IntoIterator<Item = ProvidedValue>,
38 content: impl FnOnce(),
39) {
40 let site = crate::caller_location_key();
41 composer_context::with_composer(|composer| {
42 let provided: Vec<ProvidedValue> = values.into_iter().collect();
43 composer.with_composition_locals(provided, site, |_composer| content());
44 })
45}
46
47pub(crate) struct LocalStateEntry<T: Clone + 'static> {
48 state: OwnedMutableState<T>,
49}
50
51type LocalEquivalentFn<T> = dyn Fn(&T, &T) -> bool + Send + Sync + 'static;
52
53struct LocalValuePolicy<T: Clone + 'static> {
54 equivalent: Arc<LocalEquivalentFn<T>>,
55}
56
57impl<T: Clone + 'static> MutationPolicy<T> for LocalValuePolicy<T> {
58 fn equivalent(&self, a: &T, b: &T) -> bool {
59 (self.equivalent)(a, b)
60 }
61}
62
63impl<T: Clone + 'static> LocalStateEntry<T> {
64 fn new(initial: T, runtime: RuntimeHandle, equivalent: Arc<LocalEquivalentFn<T>>) -> Self {
65 Self {
66 state: OwnedMutableState::with_runtime_and_policy(
67 initial,
68 runtime,
69 Arc::new(LocalValuePolicy { equivalent }),
70 ),
71 }
72 }
73
74 fn set(&self, value: T) {
75 self.state.replace(value);
76 }
77
78 pub(crate) fn value(&self) -> T {
79 self.state.value()
80 }
81}
82
83pub(crate) struct StaticLocalEntry<T: Clone + 'static> {
84 value: RefCell<T>,
85}
86
87impl<T: Clone + 'static> StaticLocalEntry<T> {
88 fn new(value: T) -> Self {
89 Self {
90 value: RefCell::new(value),
91 }
92 }
93
94 fn set(&self, value: T) {
95 *self.value.borrow_mut() = value;
96 }
97
98 pub(crate) fn value(&self) -> T {
99 self.value.borrow().clone()
100 }
101}
102
103#[derive(Clone)]
104pub struct CompositionLocal<T: Clone + 'static> {
105 pub(crate) key: LocalKey,
106 default: Rc<dyn Fn() -> T>,
107 equivalent: Arc<LocalEquivalentFn<T>>,
108}
109
110impl<T: Clone + 'static> PartialEq for CompositionLocal<T> {
111 fn eq(&self, other: &Self) -> bool {
112 self.key == other.key
113 }
114}
115
116impl<T: Clone + 'static> Eq for CompositionLocal<T> {}
117
118impl<T: Clone + 'static> CompositionLocal<T> {
119 #[track_caller]
120 pub fn provides(&self, value: T) -> ProvidedValue {
121 let key = self.key.clone();
122 let entry_source = provider_entry_source(&key, crate::caller_location_key());
123 let equivalent = Arc::clone(&self.equivalent);
124 ProvidedValue {
125 key,
126 apply: Box::new(move |composer: &Composer, site: crate::Key| {
127 let runtime = composer.runtime_handle();
128 let source = (entry_source ^ site).wrapping_mul(0x0000_0100_0000_01b3);
129 let entry_ref = composer.remember_internal(source, || {
130 Rc::new(LocalStateEntry::new(
131 value.clone(),
132 runtime.clone(),
133 Arc::clone(&equivalent),
134 ))
135 });
136 entry_ref.update(|entry| entry.set(value.clone()));
137 entry_ref.with(|entry| entry.clone() as Rc<dyn Any>)
138 }),
139 }
140 }
141
142 pub fn current(&self) -> T {
143 composer_context::with_composer(|composer| composer.read_composition_local(self))
144 }
145
146 pub fn default_value(&self) -> T {
147 (self.default)()
148 }
149}
150
151#[cfg(test)]
152pub(crate) fn malformed_composition_local_for_test<T: Clone + 'static>(
153 local: &CompositionLocal<T>,
154 entry: Rc<dyn Any>,
155) -> ProvidedValue {
156 let key = local.key.clone();
157 ProvidedValue {
158 key,
159 apply: Box::new(move |_, _| entry.clone()),
160 }
161}
162
163#[allow(non_snake_case)]
164pub fn compositionLocalOf<T: Clone + PartialEq + 'static>(
165 default: impl Fn() -> T + 'static,
166) -> CompositionLocal<T> {
167 compositionLocalOfWithPolicy(default, |current, next| current == next)
168}
169
170#[allow(non_snake_case)]
171pub fn compositionLocalOfWithPolicy<T: Clone + 'static>(
172 default: impl Fn() -> T + 'static,
173 equivalent: impl Fn(&T, &T) -> bool + Send + Sync + 'static,
174) -> CompositionLocal<T> {
175 CompositionLocal {
176 key: LocalKey::new(),
177 default: Rc::new(default),
178 equivalent: Arc::new(equivalent),
179 }
180}
181
182#[derive(Clone)]
193pub struct StaticCompositionLocal<T: Clone + 'static> {
194 pub(crate) key: LocalKey,
195 default: Rc<dyn Fn() -> T>,
196}
197
198impl<T: Clone + 'static> PartialEq for StaticCompositionLocal<T> {
199 fn eq(&self, other: &Self) -> bool {
200 self.key == other.key
201 }
202}
203
204impl<T: Clone + 'static> Eq for StaticCompositionLocal<T> {}
205
206impl<T: Clone + 'static> StaticCompositionLocal<T> {
207 #[track_caller]
208 pub fn provides(&self, value: T) -> ProvidedValue {
209 let key = self.key.clone();
210 let entry_source = provider_entry_source(&key, crate::caller_location_key());
211 ProvidedValue {
212 key,
213 apply: Box::new(move |composer: &Composer, site: crate::Key| {
214 let source = (entry_source ^ site).wrapping_mul(0x0000_0100_0000_01b3);
215 let entry_ref = composer
216 .remember_internal(source, || Rc::new(StaticLocalEntry::new(value.clone())));
217 entry_ref.update(|entry| entry.set(value.clone()));
218 entry_ref.with(|entry| entry.clone() as Rc<dyn Any>)
219 }),
220 }
221 }
222
223 pub fn current(&self) -> T {
224 composer_context::with_composer(|composer| composer.read_static_composition_local(self))
225 }
226
227 pub fn default_value(&self) -> T {
228 (self.default)()
229 }
230}
231
232#[cfg(test)]
233pub(crate) fn malformed_static_composition_local_for_test<T: Clone + 'static>(
234 local: &StaticCompositionLocal<T>,
235 entry: Rc<dyn Any>,
236) -> ProvidedValue {
237 let key = local.key.clone();
238 ProvidedValue {
239 key,
240 apply: Box::new(move |_, _| entry.clone()),
241 }
242}
243
244#[allow(non_snake_case)]
245pub fn staticCompositionLocalOf<T: Clone + 'static>(
246 default: impl Fn() -> T + 'static,
247) -> StaticCompositionLocal<T> {
248 StaticCompositionLocal {
249 key: LocalKey::new(),
250 default: Rc::new(default),
251 }
252}