Skip to main content

rumtk_core/
cache.rs

1/*
2 * rumtk attempts to implement HL7 and medical protocols for interoperability in medicine.
3 * This toolkit aims to be reliable, simple, performant, and standards compliant.
4 * Copyright (C) 2024  Luis M. Santos, M.D. <lsantos@medicalmasses.com>
5 * Copyright (C) 2025  MedicalMasses L.L.C. <contact@medicalmasses.com>
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
19 */
20
21use crate::core::RUMResult;
22use crate::net::tcp::{AsyncOwnedRwLockReadGuard, AsyncOwnedRwLockWriteGuard};
23use crate::threading::thread_primitives::SafeLock;
24use crate::threading::threading_functions::{lock_read, new_lock, process_write_critical_section};
25use crate::types::RUMHashMap;
26use crate::{rumtk_critical_section_write, rumtk_lock_read, rumtk_new_lock};
27use clap::builder::TypedValueParser;
28use core::hash::Hash;
29pub use once_cell::unsync::Lazy;
30use std::fmt::Debug;
31use std::sync::LazyLock;
32/**************************** Constants**************************************/
33/// I don't think most scenarios will need more than 10 items worth of memory pre-allocated at a time.
34pub const DEFAULT_CACHE_PAGE_SIZE: usize = 10;
35/**************************** Caches ****************************************/
36
37/**************************** Types *****************************************/
38///
39/// Generic Cache store object. One use case will be to use a search string as the key and store
40/// the search parsing object here.
41///
42pub type RUMCache<K, V> = RUMHashMap<K, V>;
43pub type LockedCache<K, V> = SafeLock<RUMCache<K, V>>;
44pub type LazyRUMCache<K, V> = LazyLock<LockedCache<K, V>>;
45pub type RUMCacheValue<V> = AsyncOwnedRwLockReadGuard<V>;
46pub type RUMCacheMutValue<V> = AsyncOwnedRwLockWriteGuard<V>;
47pub type RUMCacheWriteGuard<K,V> = AsyncOwnedRwLockWriteGuard<RUMCache<K, V>>;
48
49/**************************** Traits ****************************************/
50
51/**************************** Helpers ***************************************/
52
53pub const fn new_cache<K, V>() -> LazyRUMCache<K, V>
54where
55    K: Debug,
56    V: Debug,
57{
58    LazyLock::new(|| {
59        let cache = RUMCache::<K, V>::with_capacity(DEFAULT_CACHE_PAGE_SIZE);
60        rumtk_new_lock!(cache)
61    })
62}
63
64pub unsafe fn cache_push<K, V>(
65    cache: *mut LazyRUMCache<K, V>,
66    key: &K,
67    val: V,
68)
69where
70    K: Hash + Eq + Clone + Send + Sync + 'static,
71    V: Clone + Send + Sync + 'static,
72{
73    rumtk_critical_section_write!((**cache).clone(), |mut guard: RUMCacheWriteGuard<K,V>| {
74        guard.insert(key.clone(), val.clone());
75    })
76}
77
78pub unsafe fn cache_get<K, V>(
79    cache: *mut LazyRUMCache<K, V>,
80    key: &K,
81) -> Option<V>
82where
83    K: Hash + Eq + Clone + Send + Sync + 'static,
84    V: Clone + Send + Sync + 'static,
85{
86    let guard = rumtk_lock_read!(*cache);
87    match (*guard).get(key) {
88        Some(val) => Some(val.clone()),
89        None => None
90    }
91}
92
93pub unsafe fn cache_get_or_set<K, V, F>(
94    cache: *mut LazyRUMCache<K, V>,
95    key: &K,
96    default_func: F
97) -> RUMResult<V>
98where
99    K: Hash + Eq + Clone + Send + Sync + 'static,
100    V: Clone + Send + Sync + 'static,
101    F: Fn() -> RUMResult<V>
102{
103    let val = match cache_get(cache, key) {
104        Some(val) => val,
105        None => {
106            cache_push(cache, key, default_func()?);
107            cache_get(cache, key).unwrap()
108        }
109    };
110    Ok(val)
111}
112
113pub mod cache_macros {
114    ///
115    /// Searches for item in global cache. If global cache lacks item, create item using factory
116    /// function passed to this macro.
117    ///
118    /// ```
119    /// use rumtk_core::rumtk_cache_fetch;
120    /// use rumtk_core::cache::{new_cache, LazyRUMCache};
121    /// use std::sync::Arc;
122    /// use rumtk_core::core::RUMResult;
123    ///
124    /// type StringCache = LazyRUMCache<String, String>;
125    ///
126    /// fn init_cache(k: &String) -> RUMResult<String> {
127    ///    Ok(String::from(k))
128    /// }
129    ///
130    /// let mut cache: StringCache = new_cache();
131    ///
132    /// let test_key: String = String::from("Hello World");
133    /// let v = rumtk_cache_fetch!(
134    ///     &raw mut cache,
135    ///     &test_key,
136    ///     || {init_cache(&test_key)}
137    /// ).unwrap();
138    ///
139    /// assert_eq!(test_key.as_str(), v.as_str(), "The inserted key is not the same to what was passed as input!");
140    ///
141    ///
142    /// ```
143    ///
144    #[macro_export]
145    macro_rules! rumtk_cache_fetch {
146        ( $cache:expr, $key:expr, $default_func:expr ) => {{
147            use $crate::cache::{cache_get_or_set};
148            // Do not remove the clippy disable decorator here since we do intend to expand within
149            // the unsafe block. Expanding elsewhere prevents us from getting access to the cache's
150            // internal references due to compiler error
151            #[allow(clippy::macro_metavars_in_unsafe)]
152            unsafe {
153                cache_get_or_set($cache, $key, $default_func)
154            }
155        }};
156    }
157
158    ///
159    /// Overrides contents in cache at key ```K```. No checks are performed for the existence of the
160    /// key in the cache. Be careful not to override necessary data.
161    ///
162    /// ```
163    /// use rumtk_core::{rumtk_cache_fetch, rumtk_cache_get, rumtk_cache_push};
164    /// use rumtk_core::cache::{new_cache, LazyRUMCache, cache_push};
165    /// use std::sync::Arc;
166    /// use rumtk_core::core::RUMResult;
167    ///
168    /// type StringCache = LazyRUMCache<String, Vec<String>>;
169    ///
170    /// fn init_cache(k: &String) -> RUMResult<Vec<String>> {
171    ///    Ok(vec![])
172    /// }
173    ///
174    /// let mut cache: StringCache = new_cache();
175    ///
176    /// let test_key: String = String::from("Hello World");
177    /// let test_value: Vec<String> = vec![String::from("?????")];
178    ///
179    /// rumtk_cache_fetch!(
180    ///     &raw mut cache,
181    ///     &test_key,
182    ///     || {init_cache(&test_key)}
183    /// );
184    ///
185    /// rumtk_cache_push!(
186    ///     &raw mut cache,
187    ///     &test_key,
188    ///     test_value.clone()
189    /// );
190    ///
191    /// let v = rumtk_cache_get!(
192    ///     &raw mut cache,
193    ///     &test_key
194    /// ).unwrap();
195    ///
196    /// assert_eq!(test_value.get(0).unwrap(), v.get(0).unwrap(), "The inserted key is not the same to what was passed as input!");
197    ///
198    ///
199    /// ```
200    ///
201    #[macro_export]
202    macro_rules! rumtk_cache_push {
203        ( $cache:expr, $key:expr, $val:expr ) => {{
204            use $crate::cache::cache_push;
205            // Do not remove the clippy disable decorator here since we do intend to expand within
206            // the unsafe block. Expanding elsewhere prevents us from getting access to the cache's
207            // internal references due to compiler error
208            #[allow(clippy::macro_metavars_in_unsafe)]
209            unsafe {
210                cache_push($cache, $key, $val)
211            }
212        }};
213    }
214
215    ///
216    /// Retrieves the cached item in immutable mode.
217    ///
218    /// ```
219    /// use rumtk_core::{rumtk_cache_fetch, rumtk_cache_push, rumtk_cache_get};
220    /// use rumtk_core::cache::{new_cache, LazyRUMCache, cache_push, cache_get};
221    /// use std::sync::Arc;
222    /// use rumtk_core::core::RUMResult;
223    ///
224    /// type StringCache = LazyRUMCache<String, Vec<String>>;
225    ///
226    /// fn init_cache(k: &String) -> RUMResult<Vec<String>> {
227    ///    Ok(vec![])
228    /// }
229    ///
230    /// let mut cache: StringCache = new_cache();
231    /// static DEFAULT_VEC: Vec<String> = vec![];
232    ///
233    /// let test_key: String = String::from("Hello World");
234    /// let test_value: Vec<String> = vec![String::from("?????")];
235    ///
236    /// rumtk_cache_fetch!(
237    ///     &raw mut cache,
238    ///     &test_key,
239    ///     || {init_cache(&test_key)}
240    /// );
241    ///
242    /// rumtk_cache_push!(
243    ///     &raw mut cache,
244    ///     &test_key,
245    ///     test_value.clone()
246    /// );
247    ///
248    /// let v = rumtk_cache_get!(
249    ///     &raw mut cache,
250    ///     &test_key
251    /// ).unwrap();
252    ///
253    /// assert_eq!(test_value.get(0).unwrap(), v.get(0).unwrap(), "The inserted key is not the same to what was passed as input!");
254    ///
255    ///
256    /// ```
257    ///
258    #[macro_export]
259    macro_rules! rumtk_cache_get {
260        ( $cache:expr, $key:expr ) => {{
261            use $crate::cache::cache_get;
262            // Do not remove the clippy disable decorator here since we do intend to expand within
263            // the unsafe block. Expanding elsewhere prevents us from getting access to the cache's
264            // internal references due to compiler error
265            #[allow(clippy::macro_metavars_in_unsafe)]
266            unsafe {
267                cache_get($cache, $key)
268            }
269        }};
270    }
271}