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}