topsoil_core/dispatch_context.rs
1// This file is part of Soil.
2
3// Copyright (C) Soil contributors.
4// Copyright (C) Parity Technologies (UK) Ltd.
5// SPDX-License-Identifier: Apache-2.0 OR GPL-3.0-or-later WITH Classpath-exception-2.0
6
7//! Provides functions to interact with the dispatch context.
8//!
9//! A Dispatch context is created by calling [`run_in_context`] and then the given closure will be
10//! executed in this dispatch context. Everything run in this `closure` will have access to the same
11//! dispatch context. This also applies to nested calls of [`run_in_context`]. The dispatch context
12//! can be used to store and retrieve information locally in this context. The dispatch context can
13//! be accessed by using [`with_context`]. This function will execute the given closure and give it
14//! access to the value stored in the dispatch context.
15//!
16//! # FRAME integration
17//!
18//! The FRAME macros implement
19//! [`UnfilteredDispatchable`](topsoil_core::traits::UnfilteredDispatchable) for each pallet `Call`
20//! enum. Part of this implementation is the call to [`run_in_context`], so that each call to
21//! [`UnfilteredDispatchable::dispatch_bypass_filter`](crate::traits::UnfilteredDispatchable::dispatch_bypass_filter)
22//! or [`Dispatchable::dispatch`](subsoil::runtime::traits::Dispatchable::dispatch) will run in a dispatch
23//! context.
24//!
25//! # Example
26//!
27//! ```
28//! use topsoil_core::dispatch_context::{with_context, run_in_context};
29//!
30//! // Not executed in a dispatch context, so it should return `None`.
31//! assert!(with_context::<(), _>(|_| println!("Hello")).is_none());
32//!
33//! // Run it in a dispatch context and `with_context` returns `Some(_)`.
34//! run_in_context(|| {
35//! assert!(with_context::<(), _>(|_| println!("Hello")).is_some());
36//! });
37//!
38//! #[derive(Default)]
39//! struct CustomContext(i32);
40//!
41//! run_in_context(|| {
42//! with_context::<CustomContext, _>(|v| {
43//! // Initialize the value to the default value.
44//! assert_eq!(0, v.or_default().0);
45//! v.or_default().0 = 10;
46//! });
47//!
48//! with_context::<CustomContext, _>(|v| {
49//! // We are still in the same context and can still access the set value.
50//! assert_eq!(10, v.or_default().0);
51//! });
52//!
53//! run_in_context(|| {
54//! with_context::<CustomContext, _>(|v| {
55//! // A nested call of `run_in_context` stays in the same dispatch context
56//! assert_eq!(10, v.or_default().0);
57//! })
58//! })
59//! });
60//!
61//! run_in_context(|| {
62//! with_context::<CustomContext, _>(|v| {
63//! // We left the other context and created a new one, so we should be back
64//! // to our default value.
65//! assert_eq!(0, v.or_default().0);
66//! });
67//! });
68//! ```
69//!
70//! In your pallet you will only have to use [`with_context`], because as described above
71//! [`run_in_context`] will be handled by FRAME for you.
72
73use alloc::{
74 boxed::Box,
75 collections::btree_map::{BTreeMap, Entry},
76};
77use core::any::{Any, TypeId};
78
79environmental::environmental!(DISPATCH_CONTEXT: BTreeMap<TypeId, Box<dyn Any>>);
80
81/// Abstraction over some optional value `T` that is stored in the dispatch context.
82pub struct Value<'a, T> {
83 value: Option<&'a mut T>,
84 new_value: Option<T>,
85}
86
87impl<T> Value<'_, T> {
88 /// Get the value as reference.
89 pub fn get(&self) -> Option<&T> {
90 self.new_value.as_ref().or_else(|| self.value.as_ref().map(|v| *v as &T))
91 }
92
93 /// Get the value as mutable reference.
94 pub fn get_mut(&mut self) -> Option<&mut T> {
95 self.new_value.as_mut().or_else(|| self.value.as_mut().map(|v| *v as &mut T))
96 }
97
98 /// Set to the given value.
99 ///
100 /// [`Self::get`] and [`Self::get_mut`] will return `new_value` afterwards.
101 pub fn set(&mut self, new_value: T) {
102 self.value = None;
103 self.new_value = Some(new_value);
104 }
105
106 /// Returns a mutable reference to the value.
107 ///
108 /// If the internal value isn't initialized, this will set it to [`Default::default()`] before
109 /// returning the mutable reference.
110 pub fn or_default(&mut self) -> &mut T
111 where
112 T: Default,
113 {
114 if let Some(v) = &mut self.value {
115 return v;
116 }
117
118 self.new_value.get_or_insert_with(|| Default::default())
119 }
120
121 /// Clear the internal value.
122 ///
123 /// [`Self::get`] and [`Self::get_mut`] will return `None` afterwards.
124 pub fn clear(&mut self) {
125 self.new_value = None;
126 self.value = None;
127 }
128}
129
130/// Runs the given `callback` in the dispatch context and gives access to some user defined value.
131///
132/// Passes a mutable reference of [`Value`] to the callback. The value will be of type `T` and
133/// is identified using the [`TypeId`] of `T`. This means that `T` should be some unique type to
134/// make the value unique. If no value is set yet [`Value::get()`] and [`Value::get_mut()`] will
135/// return `None`. It is totally valid to have some `T` that is shared between different callers to
136/// have access to the same value.
137///
138/// Returns `None` if the current context is not a dispatch context. To create a context it is
139/// required to call [`run_in_context`] with the closure to execute in this context. So, for example
140/// in tests it could be that there isn't any dispatch context or when calling a dispatchable like a
141/// normal Rust function from some FRAME hook.
142pub fn with_context<T: 'static, R>(callback: impl FnOnce(&mut Value<T>) -> R) -> Option<R> {
143 DISPATCH_CONTEXT::with(|c| match c.entry(TypeId::of::<T>()) {
144 Entry::Occupied(mut o) => {
145 let value = o.get_mut().downcast_mut::<T>();
146
147 if value.is_none() {
148 log::error!(
149 "Failed to downcast value for type {} in dispatch context!",
150 core::any::type_name::<T>(),
151 );
152 }
153
154 let mut value = Value { value, new_value: None };
155 let res = callback(&mut value);
156
157 if value.value.is_none() && value.new_value.is_none() {
158 o.remove();
159 } else if let Some(new_value) = value.new_value {
160 o.insert(Box::new(new_value) as Box<_>);
161 }
162
163 res
164 },
165 Entry::Vacant(v) => {
166 let mut value = Value { value: None, new_value: None };
167
168 let res = callback(&mut value);
169
170 if let Some(new_value) = value.new_value {
171 v.insert(Box::new(new_value) as Box<_>);
172 }
173
174 res
175 },
176 })
177}
178
179/// Run the given closure `run` in a dispatch context.
180///
181/// Nested calls to this function will execute `run` in the same dispatch context as the initial
182/// call to this function. In other words, all nested calls of this function will be done in the
183/// same dispatch context.
184pub fn run_in_context<R>(run: impl FnOnce() -> R) -> R {
185 DISPATCH_CONTEXT::using_once(&mut Default::default(), run)
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191
192 #[test]
193 fn dispatch_context_works() {
194 // No context, so we don't execute
195 assert!(with_context::<(), _>(|_| ()).is_none());
196
197 let ret = run_in_context(|| with_context::<(), _>(|_| 1).unwrap());
198 assert_eq!(1, ret);
199
200 #[derive(Default)]
201 struct Context(i32);
202
203 let res = run_in_context(|| {
204 with_context::<Context, _>(|v| {
205 assert_eq!(0, v.or_default().0);
206
207 v.or_default().0 = 100;
208 });
209
210 run_in_context(|| {
211 run_in_context(|| {
212 run_in_context(|| with_context::<Context, _>(|v| v.or_default().0).unwrap())
213 })
214 })
215 });
216
217 // Ensure that the initial value set in the context is also accessible after nesting the
218 // `run_in_context` calls.
219 assert_eq!(100, res);
220 }
221}