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 {
14 (key.entry_source() ^ caller).wrapping_mul(0x0000_0100_0000_01b3)
15}
16
17pub struct ProvidedValue {
18 key: LocalKey,
19 #[allow(clippy::type_complexity)] 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 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#[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}