zng_app_context/context_local.rs
1use std::sync::Arc;
2
3use parking_lot::RwLock;
4
5use crate::{
6 AppLocal, AppLocalId, AppLocalImpl, LocalContext, LocalValueKind, ReadOnlyRwLock, RwLockReadGuardOwned, RwLockWriteGuardOwned,
7};
8
9#[doc(hidden)]
10pub struct ContextLocalData<T: Send + Sync + 'static> {
11 default_init: fn() -> T,
12 default_value: Option<Arc<T>>,
13}
14impl<T: Send + Sync + 'static> ContextLocalData<T> {
15 #[doc(hidden)]
16 pub const fn new(default_init: fn() -> T) -> Self {
17 Self {
18 default_init,
19 default_value: None,
20 }
21 }
22}
23
24/// Represents an [`AppLocal<T>`] value that can be temporarily overridden in a context.
25///
26/// The *context* works across threads, as long as the threads are instrumented using [`LocalContext`].
27///
28/// Use the [`context_local!`] macro to declare a static variable in the same style as [`thread_local!`].
29///
30/// [`context_local!`]: crate::context_local!
31pub struct ContextLocal<T: Send + Sync + 'static> {
32 data: AppLocal<ContextLocalData<T>>,
33}
34impl<T: Send + Sync + 'static> ContextLocal<T> {
35 #[doc(hidden)]
36 pub const fn new(storage: fn() -> &'static dyn AppLocalImpl<ContextLocalData<T>>) -> Self {
37 Self {
38 data: AppLocal::new(storage),
39 }
40 }
41
42 /// Gets an ID for this context local instance that is valid for the lifetime of the process.
43 ///
44 /// Note that comparing two `&'static CTX_LOCAL` pointers is incorrect, because in `"hot_reload"` builds the statics
45 /// can be different and still represent the same app local. This ID identifies the actual inner pointer.
46 pub fn id(&'static self) -> AppLocalId {
47 self.data.id()
48 }
49
50 /// Calls `f` with the `value` loaded in context.
51 ///
52 /// The `value` is moved into context, `f` is called, then the value is moved back to `value`.
53 ///
54 /// # Panics
55 ///
56 /// Panics if `value` is `None`.
57 #[inline(always)]
58 pub fn with_context<R>(&'static self, value: &mut Option<Arc<T>>, f: impl FnOnce() -> R) -> R {
59 let mut r = None;
60 let f = || r = Some(f());
61
62 LocalContext::with_value_ctx(self, LocalValueKind::Local, value, f);
63
64 r.unwrap()
65 }
66
67 /// Same as [`with_context`], but `value` represents a variable.
68 ///
69 /// Values loaded with this method are captured by [`CaptureFilter::ContextVars`].
70 ///
71 /// [`with_context`]: Self::with_context
72 /// [`CaptureFilter::ContextVars`]: crate::CaptureFilter::ContextVars
73 #[inline(always)]
74 pub fn with_context_var<R>(&'static self, value: &mut Option<Arc<T>>, f: impl FnOnce() -> R) -> R {
75 let mut r = None;
76 let f = || r = Some(f());
77
78 LocalContext::with_value_ctx(self, LocalValueKind::Var, value, f);
79
80 r.unwrap()
81 }
82
83 /// Calls `f` with no value loaded in context.
84 #[inline(always)]
85 pub fn with_default<R>(&'static self, f: impl FnOnce() -> R) -> R {
86 let mut r = None;
87 let f = || r = Some(f());
88
89 LocalContext::with_default_ctx(self, f);
90
91 r.unwrap()
92 }
93
94 /// Gets if no value is set in the context.
95 pub fn is_default(&'static self) -> bool {
96 !LocalContext::contains(self.id())
97 }
98
99 /// Clone a reference to the current value in the context or the default value.
100 pub fn get(&'static self) -> Arc<T> {
101 let cl = self.data.read();
102 match LocalContext::get(self.id()) {
103 Some(c) => Arc::downcast(c.0).unwrap(),
104 None => match &cl.default_value {
105 Some(d) => d.clone(),
106 None => {
107 drop(cl);
108 let mut cl = self.data.write();
109 match &cl.default_value {
110 None => {
111 let d = Arc::new((cl.default_init)());
112 cl.default_value = Some(d.clone());
113 d
114 }
115 Some(d) => d.clone(),
116 }
117 }
118 },
119 }
120 }
121
122 /// Clone the current value in the context or the default value.
123 pub fn get_clone(&'static self) -> T
124 where
125 T: Clone,
126 {
127 let cl = self.data.read();
128 match LocalContext::get(self.id()) {
129 Some(c) => c.0.downcast_ref::<T>().unwrap().clone(),
130 None => match &cl.default_value {
131 Some(d) => d.as_ref().clone(),
132 None => {
133 drop(cl);
134 let mut cl = self.data.write();
135 match &cl.default_value {
136 None => {
137 let val = (cl.default_init)();
138 let r = val.clone();
139 cl.default_value = Some(Arc::new(val));
140 r
141 }
142 Some(d) => d.as_ref().clone(),
143 }
144 }
145 },
146 }
147 }
148}
149
150impl<T: Send + Sync + 'static> ContextLocal<RwLock<T>> {
151 /// Gets a read-only shared reference to the current context value.
152 pub fn read_only(&'static self) -> ReadOnlyRwLock<T> {
153 ReadOnlyRwLock::new(self.get())
154 }
155
156 /// Locks this `RwLock` with shared read access, blocking the current thread until it can be acquired.
157 ///
158 /// See `parking_lot::RwLock::read` for more details.
159 pub fn read(&'static self) -> RwLockReadGuardOwned<T> {
160 RwLockReadGuardOwned::lock(self.get())
161 }
162
163 /// Locks this `RwLock` with shared read access, blocking the current thread until it can be acquired.
164 ///
165 /// Unlike `read`, this method is guaranteed to succeed without blocking if
166 /// another read lock is held at the time of the call.
167 ///
168 /// See `parking_lot::RwLock::read` for more details.
169 pub fn read_recursive(&'static self) -> RwLockReadGuardOwned<T> {
170 RwLockReadGuardOwned::lock_recursive(self.get())
171 }
172
173 /// Locks this `RwLock` with exclusive write access, blocking the current
174 /// thread until it can be acquired.
175 ///
176 /// See `parking_lot::RwLock::write` for more details.
177 pub fn write(&'static self) -> RwLockWriteGuardOwned<T> {
178 RwLockWriteGuardOwned::lock(self.get())
179 }
180
181 /// Try lock this `RwLock` with shared read access, blocking the current thread until it can be acquired.
182 ///
183 /// See `parking_lot::RwLock::try_read` for more details.
184 pub fn try_read(&'static self) -> Option<RwLockReadGuardOwned<T>> {
185 RwLockReadGuardOwned::try_lock(self.get())
186 }
187
188 /// Locks this `RwLock` with shared read access, blocking the current thread until it can be acquired.
189 ///
190 /// See `parking_lot::RwLock::try_read_recursive` for more details.
191 pub fn try_read_recursive(&'static self) -> Option<RwLockReadGuardOwned<T>> {
192 RwLockReadGuardOwned::try_lock_recursive(self.get())
193 }
194
195 /// Locks this `RwLock` with exclusive write access, blocking the current
196 /// thread until it can be acquired.
197 ///
198 /// See `parking_lot::RwLock::try_write` for more details.
199 pub fn try_write(&'static self) -> Option<RwLockWriteGuardOwned<T>> {
200 RwLockWriteGuardOwned::try_lock(self.get())
201 }
202}
203
204///<span data-del-macro-root></span> Declares new app and context local variable.
205///
206/// # Examples
207///
208/// ```
209/// # use zng_app_context::*;
210/// context_local! {
211/// /// A public documented value.
212/// pub static FOO: u8 = 10u8;
213///
214/// // A private value.
215/// static BAR: String = "Into!";
216/// }
217/// ```
218///
219/// # Default Value
220///
221/// All contextual values must have a fallback value that is used when no context is loaded.
222///
223/// The default value is instantiated once per app, the expression can be any static value that converts [`Into<T>`].
224///
225/// # Usage
226///
227/// After you declare the context local you can use it by loading a contextual value for the duration of a closure call.
228///
229/// ```
230/// # use zng_app_context::*;
231/// # use std::sync::Arc;
232/// context_local! {
233/// static FOO: String = "default";
234/// }
235///
236/// fn print_value() {
237/// println!("value is {}!", FOO.get());
238/// }
239///
240/// let _scope = LocalContext::start_app(AppId::new_unique());
241///
242/// let mut value = Some(Arc::new(String::from("other")));
243/// FOO.with_context(&mut value, || {
244/// print!("in context, ");
245/// print_value();
246/// });
247///
248/// print!("out of context, ");
249/// print_value();
250/// ```
251///
252/// The example above prints:
253///
254/// ```text
255/// in context, value is other!
256/// out of context, value is default!
257/// ```
258///
259/// See [`ContextLocal<T>`] for more details.
260#[macro_export]
261macro_rules! context_local {
262 ($(
263 $(#[$meta:meta])*
264 $vis:vis static $IDENT:ident : $T:ty = $init:expr;
265 )+) => {$(
266 $crate::context_local_impl! {
267 $(#[$meta])*
268 $vis static $IDENT: $T = $init;
269 }
270 )+};
271}
272
273#[doc(hidden)]
274#[macro_export]
275macro_rules! context_local_impl_single {
276 ($(
277 $(#[$meta:meta])*
278 $vis:vis static $IDENT:ident : $T:ty = $init:expr;
279 )+) => {$(
280 $(#[$meta])*
281 $vis static $IDENT: $crate::ContextLocal<$T> = {
282 fn s() -> &'static dyn $crate::AppLocalImpl<$crate::ContextLocalData<$T>> {
283 fn init() -> $T {
284 std::convert::Into::into($init)
285 }
286 $crate::hot_static! {
287 static IMPL: $crate::AppLocalConst<$crate::ContextLocalData<$T>> =
288 $crate::AppLocalConst::new(
289 $crate::ContextLocalData::new(init)
290 );
291 }
292 $crate::hot_static_ref!(IMPL)
293 }
294 $crate::ContextLocal::new(s)
295 };
296 )+};
297}
298
299#[doc(hidden)]
300#[macro_export]
301macro_rules! context_local_impl_multi {
302 ($(
303 $(#[$meta:meta])*
304 $vis:vis static $IDENT:ident : $T:ty = $init:expr;
305 )+) => {$(
306 $(#[$meta])*
307 $vis static $IDENT: $crate::ContextLocal<$T> = {
308 fn s() -> &'static dyn $crate::AppLocalImpl<$crate::ContextLocalData<$T>> {
309 fn init() -> $T {
310 std::convert::Into::into($init)
311 }
312 $crate::hot_static! {
313 static IMPL: $crate::AppLocalVec<$crate::ContextLocalData<$T>> =
314 $crate::AppLocalVec::new(
315 || $crate::ContextLocalData::new(init)
316 );
317 }
318 $crate::hot_static_ref!(IMPL)
319 }
320 $crate::ContextLocal::new(s)
321 };
322 )+};
323}
324
325#[cfg(feature = "multi_app")]
326#[doc(hidden)]
327pub use context_local_impl_multi as context_local_impl;
328
329#[cfg(not(feature = "multi_app"))]
330#[doc(hidden)]
331pub use context_local_impl_single as context_local_impl;