Skip to main content

elemental_object/
facade.rs

1use std::{
2    any::{Any, type_name},
3    future::Future,
4    pin::Pin,
5    sync::Arc,
6};
7
8use anyhow::{Context, Result};
9
10pub use crate::context::ObjectContext;
11use crate::pool::{ObjectPool, POOL, ShutdownFn};
12use crate::{context::CONTEXT, instant::InstantObject};
13
14/// Fast try to get a value from the pool, if not exists, return error immediately.
15pub async fn require<T: Any + Send + Sync>() -> Result<Arc<T>> {
16    if let Some(pool) = context_pool()
17        && let Ok(value) = pool.get_async::<T>().await.context(format!(
18            "Cannot get object `{}` from pool",
19            type_name::<T>()
20        ))
21    {
22        return Ok(value);
23    }
24
25    POOL.get_async::<T>().await.context(format!(
26        "Cannot get object `{}` from pool",
27        type_name::<T>()
28    ))
29}
30
31/// Provide a value to the pool, it will let the old value shutdown if exists.
32/// Provide value to a existing value means safe **HOT RELOAD**
33pub async fn provide<T>(value: T)
34where
35    T: Any + Send + Sync,
36{
37    provide_arc::<T>(Arc::new(value), None, false).await;
38}
39
40pub async fn provide_with_shutdown<T, F, Fut>(value: T, shutdown: F)
41where
42    T: Any + Send + Sync,
43    F: Fn(Arc<T>) -> Fut + Send + Sync + 'static,
44    Fut: Future<Output = ()> + Send + 'static,
45{
46    provide_arc(Arc::new(value), Some(convert_shutdown_fn(shutdown)), false).await;
47}
48
49pub async fn provide_context<T>(value: T)
50where
51    T: Any + Send + Sync,
52{
53    provide_arc(Arc::new(value), None, true).await;
54}
55
56pub async fn provide_context_with_shutdown<T, F, Fut>(value: T, shutdown: F)
57where
58    T: Any + Send + Sync,
59    F: Fn(Arc<T>) -> Fut + Send + Sync + 'static,
60    Fut: Future<Output = ()> + Send + 'static,
61{
62    provide_arc(Arc::new(value), Some(convert_shutdown_fn(shutdown)), true).await;
63}
64
65pub async fn provide_instant<T>()
66where
67    T: InstantObject,
68{
69    provide_instant_impl::<T>(false).await;
70}
71
72pub async fn provide_instant_context<T>()
73where
74    T: InstantObject,
75{
76    provide_instant_impl::<T>(true).await;
77}
78
79fn convert_shutdown_fn<T, F, Fut>(shutdown: F) -> ShutdownFn<T>
80where
81    T: Any + Send + Sync,
82    F: Fn(Arc<T>) -> Fut + Send + Sync + 'static,
83    Fut: Future<Output = ()> + Send + 'static,
84{
85    Box::new(move |value: Arc<T>| {
86        Box::pin(shutdown(value)) as Pin<Box<dyn Future<Output = ()> + Send>>
87    })
88}
89/// Provide a value to the pool, it will let the old value shutdown if exists.
90async fn provide_arc<T>(value: Arc<T>, shutdown: Option<ShutdownFn<T>>, context: bool)
91where
92    T: Any + Send + Sync,
93{
94    // Try to set value in context first, if context exists, otherwise set it in global pool.
95    if let Some(pool) = selected_context_pool(context) {
96        pool.set_value(value, shutdown).await;
97        return;
98    }
99
100    POOL.set_value(value, shutdown).await;
101}
102
103pub async fn drop_value<T: Any + Send + Sync>() {
104    POOL.remove_value::<T>().await;
105}
106
107pub async fn drop_context_value<T: Any + Send + Sync>() {
108    if let Some(pool) = context_pool() {
109        pool.remove_value::<T>().await;
110    }
111}
112
113pub async fn drop_entry<T: Any + Send + Sync>() {
114    POOL.remove_entry::<T>().await;
115}
116
117pub async fn drop_context_entry<T: Any + Send + Sync>() {
118    if let Some(pool) = context_pool() {
119        pool.remove_entry::<T>().await;
120    }
121}
122
123/// Acquire a value from the pool, if not exists, wait until it is provided.
124/// If the value is shutdown while waiting, return error immediately to avoid waiting forever.
125/// This function is designed for the scenario that comsumer want to ensure a value is provided and blocking on it.
126/// If you want to use it as a subscriber, you may got some value lost in the scenario that the value is frequently provided and comsumer is slow.
127#[cfg(feature = "notify")]
128pub async fn acquire<T: Any + Send + Sync>() -> Result<Arc<T>> {
129    // If context exists, wait value in context, otherwise wait value in global pool.
130    if let Some(pool) = context_pool() {
131        pool.wait_value::<T>().await;
132    } else {
133        POOL.wait_value::<T>().await;
134    }
135    require::<T>().await
136}
137
138// Who first provide the value, we use whose shutdown function, even if the value is overwritten later, so the shutdown function can always access the value.
139pub async fn fulfill<T: Any + Send + Sync>(value: T) {
140    fulfill_arc(Arc::new(value), None, false).await;
141}
142
143pub async fn fulfill_with_shutdown<T, F, Fut>(value: T, shutdown: F)
144where
145    T: Any + Send + Sync,
146    F: Fn(Arc<T>) -> Fut + Send + Sync + 'static,
147    Fut: Future<Output = ()> + Send + 'static,
148{
149    fulfill_arc(Arc::new(value), Some(convert_shutdown_fn(shutdown)), false).await;
150}
151
152pub async fn fulfill_context<T: Any + Send + Sync>(value: T) {
153    fulfill_arc(Arc::new(value), None, true).await;
154}
155
156pub async fn fulfill_with_context_shutdown<T, F, Fut>(value: T, shutdown: F)
157where
158    T: Any + Send + Sync,
159    F: Fn(Arc<T>) -> Fut + Send + Sync + 'static,
160    Fut: Future<Output = ()> + Send + 'static,
161{
162    fulfill_arc(Arc::new(value), Some(convert_shutdown_fn(shutdown)), true).await;
163}
164
165async fn fulfill_arc<T: Any + Send + Sync>(
166    value: Arc<T>,
167    shutdown: Option<ShutdownFn<T>>,
168    context: bool,
169) {
170    if let Some(pool) = selected_context_pool(context) {
171        pool.fulfill_value(value, shutdown).await;
172        return;
173    }
174    POOL.fulfill_value(value, shutdown).await;
175}
176
177pub async fn shutdown() {
178    POOL.shutdown().await;
179}
180
181pub async fn shutdown_local() {
182    if let Some(pool) = context_pool() {
183        pool.shutdown().await;
184    }
185}
186
187fn context_pool() -> Option<Arc<ObjectPool>> {
188    CONTEXT.try_with(|pool| pool.clone()).ok()
189}
190
191fn selected_context_pool(context: bool) -> Option<Arc<ObjectPool>> {
192    if context {
193        return context_pool();
194    }
195
196    None
197}
198
199async fn provide_instant_impl<T>(context: bool)
200where
201    T: InstantObject,
202{
203    let instant_value = T::create().await;
204    let shutdown = |value: Arc<T::Output>| async move {
205        T::destroy(value).await;
206    };
207
208    provide_arc(
209        Arc::new(instant_value),
210        Some(convert_shutdown_fn(shutdown)),
211        context,
212    )
213    .await;
214}