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.
5 * Copyright (C) 2025  MedicalMasses L.L.C.
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * This library 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 GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
20 */
21
22pub use ahash::AHashMap;
23use core::hash::Hash;
24pub use once_cell::unsync::Lazy;
25use std::sync::{Arc, MappedRwLockReadGuard, RwLock, RwLockReadGuard};
26/**************************** Constants**************************************/
27/// I don't think most scenarios will need more than 10 items worth of memory pre-allocated at a time.
28pub const DEFAULT_CACHE_PAGE_SIZE: usize = 10;
29/**************************** Caches ****************************************/
30
31/**************************** Types *****************************************/
32///
33/// Generic Cache store object. One use case will be to use a search string as the key and store
34/// the search parsing object here.
35///
36pub type RUMCache<K, V> = AHashMap<K, V>;
37pub type LazyRUMCache<K, V> = Lazy<Arc<RwLock<RUMCache<K, V>>>>;
38pub type LazyRUMCacheValue<V> = MappedRwLockReadGuard<'static, V>;
39
40/**************************** Traits ****************************************/
41
42/**************************** Helpers ***************************************/
43pub const fn new_cache<K, V>() -> LazyRUMCache<K, V> {
44    LazyRUMCache::new(|| {
45        Arc::new(RwLock::new(RUMCache::with_capacity(
46            DEFAULT_CACHE_PAGE_SIZE,
47        )))
48    })
49}
50
51pub unsafe fn get_or_set_from_cache<K, V, F>(
52    cache: *mut LazyRUMCache<K, V>,
53    expr: &K,
54    new_fn: F,
55) -> LazyRUMCacheValue<V>
56where
57    K: Hash + Eq + Clone + 'static,
58    V: Clone,
59    F: Fn(&K) -> V,
60{
61    let cache_entity = &mut *cache;
62    if !cache_entity.read().unwrap().contains_key(&expr) {
63        let cache_ref = Arc::get_mut(cache_entity).unwrap();
64        cache_ref
65            .write()
66            .unwrap()
67            .insert(expr.clone(), new_fn(&expr).clone());
68    }
69    RwLockReadGuard::map(cache_entity.read().unwrap(), |d| d.get(expr).unwrap())
70}
71
72pub unsafe fn cache_push<K, V>(
73    cache: *mut LazyRUMCache<K, V>,
74    expr: &K,
75    val: &V,
76) -> LazyRUMCacheValue<V>
77where
78    K: Hash + Eq + Clone + 'static,
79    V: Clone,
80{
81    let cache_entity = &mut *cache;
82    cache_entity
83        .write()
84        .unwrap()
85        .insert(expr.clone(), val.clone());
86    RwLockReadGuard::map(cache_entity.read().unwrap(), |d| d.get(expr).unwrap())
87}
88
89pub unsafe fn cache_get<K, V>(cache: *mut LazyRUMCache<K, V>, expr: &K) -> LazyRUMCacheValue<V>
90where
91    K: Hash + Eq + Clone + 'static,
92    V: Clone,
93{
94    let cache_entity = &mut *cache;
95    let cache_ref = cache_entity.read().unwrap();
96    RwLockReadGuard::map(cache_ref, |d| d.get(expr).unwrap())
97}
98
99pub mod cache_macros {
100    ///
101    /// Searches for item in global cache. If global cache lacks item, create item using factory
102    /// function passed to this macro.
103    ///
104    /// ```
105    /// use rumtk_core::rumtk_cache_fetch;
106    /// use rumtk_core::cache::{new_cache, LazyRUMCache};
107    /// use std::sync::Arc;
108    ///
109    /// type StringCache = LazyRUMCache<String, String>;
110    ///
111    /// fn init_cache(k: &String) -> String {
112    ///    String::from(k)
113    /// }
114    ///
115    /// let mut cache: StringCache = new_cache();
116    ///
117    /// let test_key: String = String::from("Hello World");
118    /// let v = rumtk_cache_fetch!(
119    ///     &raw mut cache,
120    ///     &test_key,
121    ///     init_cache
122    /// );
123    ///
124    /// assert_eq!(test_key.as_str(), v.as_str(), "The inserted key is not the same to what was passed as input!");
125    ///
126    ///
127    /// ```
128    ///
129    #[macro_export]
130    macro_rules! rumtk_cache_fetch {
131        ( $cache:expr, $key:expr, $func:expr ) => {{
132            use $crate::cache::get_or_set_from_cache;
133            // Do not remove the clippy disable decorator here since we do intend to expand within
134            // the unsafe block. Expanding elsewhere prevents us from getting access to the cache's
135            // internal references due to compiler error
136            #[allow(clippy::macro_metavars_in_unsafe)]
137            unsafe {
138                get_or_set_from_cache($cache, $key, $func)
139            }
140        }};
141    }
142
143    ///
144    /// Overrides contents in cache at key ```K```. No checks are performed for the existence of the
145    /// key in the cache. Be careful not to override necessary data.
146    ///
147    /// ```
148    /// use rumtk_core::{rumtk_cache_fetch, rumtk_cache_push};
149    /// use rumtk_core::cache::{new_cache, LazyRUMCache, cache_push};
150    /// use std::sync::Arc;
151    ///
152    /// type StringCache = LazyRUMCache<String, Vec<String>>;
153    ///
154    /// fn init_cache(k: &String) -> Vec<String> {
155    ///    vec![]
156    /// }
157    ///
158    /// let mut cache: StringCache = new_cache();
159    ///
160    /// let test_key: String = String::from("Hello World");
161    /// let test_value: String = String::from("?????");
162    ///
163    /// rumtk_cache_fetch!(
164    ///     &raw mut cache,
165    ///     &test_key,
166    ///     init_cache
167    /// );
168    ///
169    /// let v = rumtk_cache_push!(
170    ///     &raw mut cache,
171    ///     &test_key,
172    ///     &vec![test_value.clone()]
173    /// );
174    ///
175    /// assert_eq!(test_value.as_str(), v.get(0).unwrap().as_str(), "The inserted key is not the same to what was passed as input!");
176    ///
177    ///
178    /// ```
179    ///
180    #[macro_export]
181    macro_rules! rumtk_cache_push {
182        ( $cache:expr, $key:expr, $val:expr ) => {{
183            use $crate::cache::cache_push;
184            // Do not remove the clippy disable decorator here since we do intend to expand within
185            // the unsafe block. Expanding elsewhere prevents us from getting access to the cache's
186            // internal references due to compiler error
187            #[allow(clippy::macro_metavars_in_unsafe)]
188            unsafe {
189                cache_push($cache, $key, $val)
190            }
191        }};
192    }
193
194    ///
195    /// Overrides contents in cache at key ```K```. No checks are performed for the existence of the
196    /// key in the cache. Be careful not to override necessary data.
197    ///
198    /// ```
199    /// use rumtk_core::{rumtk_cache_fetch, rumtk_cache_push, rumtk_cache_get};
200    /// use rumtk_core::cache::{new_cache, LazyRUMCache, cache_push, cache_get};
201    /// use std::sync::Arc;
202    ///
203    /// type StringCache = LazyRUMCache<String, Vec<String>>;
204    ///
205    /// fn init_cache(k: &String) -> Vec<String> {
206    ///    vec![]
207    /// }
208    ///
209    /// let mut cache: StringCache = new_cache();
210    ///
211    /// let test_key: String = String::from("Hello World");
212    /// let test_value: String = String::from("?????");
213    ///
214    /// rumtk_cache_fetch!(
215    ///     &raw mut cache,
216    ///     &test_key,
217    ///     init_cache
218    /// );
219    ///
220    /// rumtk_cache_push!(
221    ///     &raw mut cache,
222    ///     &test_key,
223    ///     &vec![test_value.clone()]
224    /// );
225    ///
226    /// let v = rumtk_cache_get!(
227    ///     &raw mut cache,
228    ///     &test_key
229    /// );
230    ///
231    /// assert_eq!(test_value.as_str(), v.get(0).unwrap().as_str(), "The inserted key is not the same to what was passed as input!");
232    ///
233    ///
234    /// ```
235    ///
236    #[macro_export]
237    macro_rules! rumtk_cache_get {
238        ( $cache:expr, $key:expr ) => {{
239            use $crate::cache::cache_get;
240            // Do not remove the clippy disable decorator here since we do intend to expand within
241            // the unsafe block. Expanding elsewhere prevents us from getting access to the cache's
242            // internal references due to compiler error
243            #[allow(clippy::macro_metavars_in_unsafe)]
244            unsafe {
245                cache_get($cache, $key)
246            }
247        }};
248    }
249}