apollo_framework/platform.rs
1//! Provides a tiny DI like container to expose all components of the system.
2//!
3//! The platform is more or less a simple map which keeps all central components as
4//! **Arc<T>** around. Also this keeps the central **is_running** flag which is toggled to
5//! *false* once [Platform::terminate](Platform::terminate) is invoked.
6//!
7//! Not that in common cases [Platform::require](Platform::require) is a good way of fetching a
8//! service which is known to be there. However, be aware, that once the system shutdown is
9//! initiated, the internal map is cleared and empty (so that all Dropped handlers run). Therefore
10//! if the code might be executed after [Platform::terminate](Platform::terminate) was called, you
11//! should use [Platform::find](Platform::find) and gracefully handle the **None** case. However,
12//! in most cases the lookup of services is performed during startup and therefore **require** can
13//! be used.
14//!
15//! # Examples
16//!
17//! ```
18//! # use std::sync::Arc;
19//! # use apollo_framework::platform::Platform;
20//! struct Service {
21//! value : i32
22//! }
23//!
24//! struct UnknownService;
25//!
26//! let platform = Platform::new();
27//!
28//! // Registers a new service...
29//! platform.register::<Service>(Arc::new(Service { value: 42 }));
30//!
31//! // Obtains a reference to a previously registered service...
32//! let service = platform.require::<Service>();
33//! assert_eq!(service.value, 42);
34//!
35//! // Trying to obtain a service which hasn't been registered yet, returns an empty
36//! // optional...
37//! assert_eq!(platform.find::<UnknownService>().is_none(), true);
38//!
39//! // By default the platform is running...
40//! assert_eq!(platform.is_running(), true);
41//!
42//! // Once terminated...
43//! platform.terminate();
44//! // All services are immediately released so that their "Dropped" handlers run...
45//! assert_eq!(platform.find::<Service>().is_none(), true);
46//!
47//! // and the platform is no longer considered active...
48//! assert_eq!(platform.is_running(), false);
49//! ```
50use std::any::{Any, TypeId};
51use std::collections::HashMap;
52use std::sync::{Arc, Mutex};
53
54use std::sync::atomic::{AtomicBool, Ordering};
55
56/// Provides a container to keep all central services in a single place.
57///
58/// # Examples
59///
60/// Building and accessing components:
61/// ```
62/// # use apollo_framework::platform::Platform;
63/// # use std::sync::Arc;
64///
65/// struct Service {}
66///
67/// #[tokio::main]
68/// async fn main() {
69/// let platform = Platform::new();
70/// platform.register(Arc::new(Service {}));
71/// assert_eq!(platform.find::<Service>().is_some(), true);
72/// }
73/// ```
74///
75/// Checking the central "is running" active..
76/// ```
77/// # use apollo_framework::platform::Platform;
78/// # use std::sync::Arc;
79///
80/// struct Service {}
81///
82/// #[tokio::main]
83/// async fn main() {
84/// let platform = Platform::new();
85/// platform.register(Arc::new(Service {}));
86///
87/// // By default the platform is running...
88/// assert_eq!(platform.is_running(), true);
89///
90/// // once terminated...
91/// platform.terminate();
92///
93/// // all services are evicted so that their Dropped handlers are executed
94/// assert_eq!(platform.find::<Service>().is_some(), false);
95///
96/// // the platform is considered halted...
97/// assert_eq!(platform.is_running(), false);
98/// }
99/// ```
100pub struct Platform {
101 services: Mutex<HashMap<TypeId, Arc<dyn Any + Send + Sync>>>,
102 is_running: AtomicBool,
103}
104
105impl Platform {
106 /// Creates a new platform instance..
107 pub fn new() -> Arc<Self> {
108 Arc::new(Platform {
109 services: Mutex::new(HashMap::new()),
110 is_running: AtomicBool::new(true),
111 })
112 }
113
114 /// Registers a new component.
115 ///
116 /// # Examples
117 /// ```
118 /// # use apollo_framework::platform::Platform;
119 /// # use std::sync::Arc;
120 ///
121 /// struct Service {
122 /// value: i32
123 /// }
124 ///
125 /// let platform = Platform::new();
126 /// platform.register::<Service>(Arc::new(Service { value: 42 }));
127 /// ```
128 pub fn register<T>(&self, service: Arc<T>)
129 where
130 T: Any + Send + Sync,
131 {
132 let _ = self
133 .services
134 .lock()
135 .unwrap()
136 .insert(TypeId::of::<T>(), service);
137 }
138
139 /// Tries to resolve a previously registered service.
140 ///
141 /// Note, if one knows for certain, that a service will be present,
142 /// [Platform::require](Platform::require) can be used.
143 ///
144 /// # Examples
145 /// ```
146 /// # use apollo_framework::platform::Platform;
147 /// # use std::sync::Arc;
148 ///
149 /// struct Service {
150 /// value: i32
151 /// }
152 ///
153 /// struct UnknownService;
154 ///
155 /// let platform = Platform::new();
156 /// platform.register::<Service>(Arc::new(Service { value: 42 }));
157 ///
158 /// // A lookup for a known service yields a result..
159 /// assert_eq!(platform.find::<Service>().unwrap().value, 42);
160 ///
161 /// // A lookup for an unknown service returns None...
162 /// assert_eq!(platform.find::<UnknownService>().is_none(), true);
163 /// ```
164 pub fn find<T>(&self) -> Option<Arc<T>>
165 where
166 T: Any + Send + Sync,
167 {
168 let services = self.services.lock().unwrap();
169 services
170 .get(&TypeId::of::<T>())
171 .and_then(|entry| entry.clone().downcast::<T>().ok())
172 }
173
174 /// Resolve a previously registered service.
175 ///
176 /// Note, if the framework is already shutting down, all services are evicted. Therefor this
177 /// might panic even if it worked before [Platform::terminate](Platform::terminate) was invoked.
178 ///
179 /// # Panics
180 /// Panics if the requested service isn't available.
181 ///
182 /// # Examples
183 /// ```
184 /// # use apollo_framework::platform::Platform;
185 /// # use std::sync::Arc;
186 ///
187 /// struct Service {
188 /// value: i32
189 /// }
190 ///
191 /// let platform = Platform::new();
192 /// platform.register::<Service>(Arc::new(Service { value: 42 }));
193 ///
194 /// // A lookup for a known service yields a result..
195 /// assert_eq!(platform.require::<Service>().value, 42);
196 /// ```
197 ///
198 /// Requiring a service which is unknown will panic:
199 /// ```should_panic
200 /// # use apollo_framework::platform::Platform;
201 /// # use std::sync::Arc;
202 ///
203 /// struct UnknownService;
204 ///
205 /// let platform = Platform::new();
206 ///
207 /// // This will panic...
208 /// platform.require::<UnknownService>();
209 /// ```
210 pub fn require<T>(&self) -> Arc<T>
211 where
212 T: Any + Send + Sync,
213 {
214 if self.is_running() {
215 match self.find::<T>() {
216 Some(service) => service,
217 None => panic!(
218 "A required component ({}) was not available in the platform registry!",
219 std::any::type_name::<T>()
220 ),
221 }
222 } else {
223 panic!(
224 "A required component ({}) has been requested but the system is already shutting down!",
225 std::any::type_name::<T>()
226 )
227 }
228 }
229
230 /// Determines if the platform is still running or if [Platform::terminate](Platform::terminate)
231 /// has already been called.
232 pub fn is_running(&self) -> bool {
233 self.is_running.load(Ordering::Acquire)
234 }
235
236 /// Terminates the platform.
237 ///
238 /// This will immediately release all services (so that the Dropped handlers run eventually).
239 /// It will also toggle the [is_running()](Platform::is_running) flag to **false**.
240 pub fn terminate(&self) {
241 // Drop all services so that the Dropped handlers run (sooner or later)...
242 self.services.lock().unwrap().clear();
243
244 // Mark platform as halted...
245 self.is_running.store(false, Ordering::Release);
246 }
247}