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 ***************************************/
43macro_rules! cache_unwrap {
44    ( $k:expr ) => {{
45        |d| {
46            d.get($k).expect("Item not found in cache despite having inserted it previously within this scope! This is completely unexpected and a fatal bug!")
47        }
48    }};
49    ( $k:expr, $default:expr ) => {{
50        |d| {
51            match d.get($k) {
52                Some(val) => val,
53                None => $default
54            }
55        }
56    }};
57}
58pub const fn new_cache<K, V>() -> LazyRUMCache<K, V> {
59    LazyRUMCache::new(|| {
60        Arc::new(RwLock::new(RUMCache::with_capacity(
61            DEFAULT_CACHE_PAGE_SIZE,
62        )))
63    })
64}
65
66pub unsafe fn get_or_set_from_cache<K, V, F>(
67    cache: *mut LazyRUMCache<K, V>,
68    expr: &K,
69    new_fn: F,
70) -> LazyRUMCacheValue<V>
71where
72    K: Hash + Eq + Clone + 'static,
73    V: Clone,
74    F: Fn(&K) -> V,
75{
76    let cache_entity = &mut *cache;
77    if !cache_entity.read().unwrap().contains_key(&expr) {
78        let cache_ref = Arc::get_mut(cache_entity).unwrap();
79        cache_ref
80            .write()
81            .unwrap()
82            .insert(expr.clone(), new_fn(&expr).clone());
83    }
84    RwLockReadGuard::map(cache_entity.read().unwrap(), cache_unwrap!(expr))
85}
86
87pub unsafe fn cache_push<K, V>(
88    cache: *mut LazyRUMCache<K, V>,
89    expr: &K,
90    val: &V,
91) -> LazyRUMCacheValue<V>
92where
93    K: Hash + Eq + Clone + 'static,
94    V: Clone,
95{
96    let cache_entity = &mut *cache;
97    cache_entity
98        .write()
99        .unwrap()
100        .insert(expr.clone(), val.clone());
101    RwLockReadGuard::map(cache_entity.read().unwrap(), cache_unwrap!(expr))
102}
103
104pub unsafe fn cache_get<K, V>(
105    cache: *mut LazyRUMCache<K, V>,
106    expr: &K,
107    default: &'static V,
108) -> LazyRUMCacheValue<V>
109where
110    K: Hash + Eq + Clone + 'static,
111    V: Clone,
112{
113    let cache_entity = &mut *cache;
114    let cache_ref = cache_entity.read().unwrap();
115    RwLockReadGuard::map(cache_ref, cache_unwrap!(expr, default))
116}
117
118pub mod cache_macros {
119    ///
120    /// Searches for item in global cache. If global cache lacks item, create item using factory
121    /// function passed to this macro.
122    ///
123    /// ```
124    /// use rumtk_core::rumtk_cache_fetch;
125    /// use rumtk_core::cache::{new_cache, LazyRUMCache};
126    /// use std::sync::Arc;
127    ///
128    /// type StringCache = LazyRUMCache<String, String>;
129    ///
130    /// fn init_cache(k: &String) -> String {
131    ///    String::from(k)
132    /// }
133    ///
134    /// let mut cache: StringCache = new_cache();
135    ///
136    /// let test_key: String = String::from("Hello World");
137    /// let v = rumtk_cache_fetch!(
138    ///     &raw mut cache,
139    ///     &test_key,
140    ///     init_cache
141    /// );
142    ///
143    /// assert_eq!(test_key.as_str(), v.as_str(), "The inserted key is not the same to what was passed as input!");
144    ///
145    ///
146    /// ```
147    ///
148    #[macro_export]
149    macro_rules! rumtk_cache_fetch {
150        ( $cache:expr, $key:expr, $func:expr ) => {{
151            use $crate::cache::get_or_set_from_cache;
152            // Do not remove the clippy disable decorator here since we do intend to expand within
153            // the unsafe block. Expanding elsewhere prevents us from getting access to the cache's
154            // internal references due to compiler error
155            #[allow(clippy::macro_metavars_in_unsafe)]
156            unsafe {
157                get_or_set_from_cache($cache, $key, $func)
158            }
159        }};
160    }
161
162    ///
163    /// Overrides contents in cache at key ```K```. No checks are performed for the existence of the
164    /// key in the cache. Be careful not to override necessary data.
165    ///
166    /// ```
167    /// use rumtk_core::{rumtk_cache_fetch, rumtk_cache_push};
168    /// use rumtk_core::cache::{new_cache, LazyRUMCache, cache_push};
169    /// use std::sync::Arc;
170    ///
171    /// type StringCache = LazyRUMCache<String, Vec<String>>;
172    ///
173    /// fn init_cache(k: &String) -> Vec<String> {
174    ///    vec![]
175    /// }
176    ///
177    /// let mut cache: StringCache = new_cache();
178    ///
179    /// let test_key: String = String::from("Hello World");
180    /// let test_value: String = String::from("?????");
181    ///
182    /// rumtk_cache_fetch!(
183    ///     &raw mut cache,
184    ///     &test_key,
185    ///     init_cache
186    /// );
187    ///
188    /// let v = rumtk_cache_push!(
189    ///     &raw mut cache,
190    ///     &test_key,
191    ///     &vec![test_value.clone()]
192    /// );
193    ///
194    /// 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!");
195    ///
196    ///
197    /// ```
198    ///
199    #[macro_export]
200    macro_rules! rumtk_cache_push {
201        ( $cache:expr, $key:expr, $val:expr ) => {{
202            use $crate::cache::cache_push;
203            // Do not remove the clippy disable decorator here since we do intend to expand within
204            // the unsafe block. Expanding elsewhere prevents us from getting access to the cache's
205            // internal references due to compiler error
206            #[allow(clippy::macro_metavars_in_unsafe)]
207            unsafe {
208                cache_push($cache, $key, $val)
209            }
210        }};
211    }
212
213    ///
214    /// Overrides contents in cache at key ```K```. No checks are performed for the existence of the
215    /// key in the cache. Be careful not to override necessary data.
216    ///
217    /// ```
218    /// use rumtk_core::{rumtk_cache_fetch, rumtk_cache_push, rumtk_cache_get};
219    /// use rumtk_core::cache::{new_cache, LazyRUMCache, cache_push, cache_get};
220    /// use std::sync::Arc;
221    ///
222    /// type StringCache = LazyRUMCache<String, Vec<String>>;
223    ///
224    /// fn init_cache(k: &String) -> Vec<String> {
225    ///    vec![]
226    /// }
227    ///
228    /// let mut cache: StringCache = new_cache();
229    /// static DEFAULT_VEC: Vec<String> = vec![];
230    ///
231    /// let test_key: String = String::from("Hello World");
232    /// let test_value: String = String::from("?????");
233    ///
234    /// rumtk_cache_fetch!(
235    ///     &raw mut cache,
236    ///     &test_key,
237    ///     init_cache
238    /// );
239    ///
240    /// rumtk_cache_push!(
241    ///     &raw mut cache,
242    ///     &test_key,
243    ///     &vec![test_value.clone()]
244    /// );
245    ///
246    /// let v = rumtk_cache_get!(
247    ///     &raw mut cache,
248    ///     &test_key,
249    ///     &DEFAULT_VEC
250    /// );
251    ///
252    /// 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!");
253    ///
254    ///
255    /// ```
256    ///
257    #[macro_export]
258    macro_rules! rumtk_cache_get {
259        ( $cache:expr, $key:expr, $default_function:expr ) => {{
260            use $crate::cache::cache_get;
261            // Do not remove the clippy disable decorator here since we do intend to expand within
262            // the unsafe block. Expanding elsewhere prevents us from getting access to the cache's
263            // internal references due to compiler error
264            #[allow(clippy::macro_metavars_in_unsafe)]
265            unsafe {
266                cache_get($cache, $key, $default_function)
267            }
268        }};
269    }
270}