Skip to main content

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}