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
22use crate::types::RUMHashMap;
23use core::hash::Hash;
24pub use once_cell::unsync::Lazy;
25use std::sync::{
26    Arc, MappedRwLockReadGuard, MappedRwLockWriteGuard, RwLock, RwLockReadGuard, RwLockWriteGuard,
27};
28/**************************** Constants**************************************/
29/// I don't think most scenarios will need more than 10 items worth of memory pre-allocated at a time.
30pub const DEFAULT_CACHE_PAGE_SIZE: usize = 10;
31/**************************** Caches ****************************************/
32
33/**************************** Types *****************************************/
34///
35/// Generic Cache store object. One use case will be to use a search string as the key and store
36/// the search parsing object here.
37///
38pub type RUMCache<K, V> = RUMHashMap<K, V>;
39pub type LazyRUMCache<K, V> = Lazy<Arc<RwLock<RUMCache<K, V>>>>;
40pub type LazyRUMCacheValue<V> = MappedRwLockReadGuard<'static, V>;
41pub type LazyRUMCacheMutValue<V> = MappedRwLockWriteGuard<'static, V>;
42
43/**************************** Traits ****************************************/
44
45/**************************** Helpers ***************************************/
46macro_rules! cache_unwrap {
47    ( $k:expr ) => {{
48        |d| {
49            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!")
50        }
51    }};
52    ( $k:expr, $default:expr ) => {{
53        |d| {
54            match d.get($k) {
55                Some(val) => val,
56                None => $default
57            }
58        }
59    }};
60}
61
62macro_rules! cache_mut_unwrap {
63    ( $k:expr ) => {{
64        |d| {
65            d.get_mut($k).expect("Item not found in cache despite having inserted it previously within this scope! This is completely unexpected and a fatal bug!")
66        }
67    }};
68    ( $k:expr, $default:expr ) => {{
69        |d| {
70            match d.get_mut($k) {
71                Some(val) => val,
72                None => $default
73            }
74        }
75    }};
76}
77
78pub const fn new_cache<K, V>() -> LazyRUMCache<K, V> {
79    LazyRUMCache::new(|| {
80        Arc::new(RwLock::new(RUMCache::with_capacity(
81            DEFAULT_CACHE_PAGE_SIZE,
82        )))
83    })
84}
85
86pub unsafe fn get_or_set_from_cache<K, V, F>(
87    cache: *mut LazyRUMCache<K, V>,
88    expr: &K,
89    new_fn: F,
90) -> LazyRUMCacheValue<V>
91where
92    K: Hash + Eq + Clone + 'static,
93    V: Clone,
94    F: Fn(&K) -> V,
95{
96    let cache_entity = &mut *cache;
97    if !cache_entity.read().unwrap().contains_key(&expr) {
98        let cache_ref = Arc::get_mut(cache_entity).unwrap();
99        cache_ref
100            .write()
101            .unwrap()
102            .insert(expr.clone(), new_fn(&expr).clone());
103    }
104    RwLockReadGuard::map(cache_entity.read().unwrap(), cache_unwrap!(expr))
105}
106
107pub unsafe fn cache_push<K, V>(
108    cache: *mut LazyRUMCache<K, V>,
109    expr: &K,
110    val: &V,
111) -> LazyRUMCacheValue<V>
112where
113    K: Hash + Eq + Clone + 'static,
114    V: Clone,
115{
116    let cache_entity = &mut *cache;
117    cache_entity
118        .write()
119        .unwrap()
120        .insert(expr.clone(), val.clone());
121    RwLockReadGuard::map(cache_entity.read().unwrap(), cache_unwrap!(expr))
122}
123
124pub unsafe fn cache_get<K, V>(
125    cache: *mut LazyRUMCache<K, V>,
126    expr: &K,
127    default: &'static V,
128) -> LazyRUMCacheValue<V>
129where
130    K: Hash + Eq + Clone + 'static,
131    V: Clone,
132{
133    let cache_entity = &mut *cache;
134    let cache_ref = cache_entity.read().unwrap();
135    RwLockReadGuard::map(cache_ref, cache_unwrap!(expr, default))
136}
137
138pub unsafe fn cache_get_mut<K, V>(
139    cache: *mut LazyRUMCache<K, V>,
140    expr: &K,
141    default: &'static mut V,
142) -> LazyRUMCacheMutValue<V>
143where
144    K: Hash + Eq + Clone + 'static,
145    V: Clone,
146{
147    let cache_entity = &mut *cache;
148    let cache_ref = cache_entity.write().unwrap();
149    RwLockWriteGuard::map(cache_ref, cache_mut_unwrap!(expr, default))
150}
151
152pub mod cache_macros {
153    ///
154    /// Searches for item in global cache. If global cache lacks item, create item using factory
155    /// function passed to this macro.
156    ///
157    /// ```
158    /// use rumtk_core::rumtk_cache_fetch;
159    /// use rumtk_core::cache::{new_cache, LazyRUMCache};
160    /// use std::sync::Arc;
161    ///
162    /// type StringCache = LazyRUMCache<String, String>;
163    ///
164    /// fn init_cache(k: &String) -> String {
165    ///    String::from(k)
166    /// }
167    ///
168    /// let mut cache: StringCache = new_cache();
169    ///
170    /// let test_key: String = String::from("Hello World");
171    /// let v = rumtk_cache_fetch!(
172    ///     &raw mut cache,
173    ///     &test_key,
174    ///     init_cache
175    /// );
176    ///
177    /// assert_eq!(test_key.as_str(), v.as_str(), "The inserted key is not the same to what was passed as input!");
178    ///
179    ///
180    /// ```
181    ///
182    #[macro_export]
183    macro_rules! rumtk_cache_fetch {
184        ( $cache:expr, $key:expr, $func:expr ) => {{
185            use $crate::cache::get_or_set_from_cache;
186            // Do not remove the clippy disable decorator here since we do intend to expand within
187            // the unsafe block. Expanding elsewhere prevents us from getting access to the cache's
188            // internal references due to compiler error
189            #[allow(clippy::macro_metavars_in_unsafe)]
190            unsafe {
191                get_or_set_from_cache($cache, $key, $func)
192            }
193        }};
194    }
195
196    ///
197    /// Overrides contents in cache at key ```K```. No checks are performed for the existence of the
198    /// key in the cache. Be careful not to override necessary data.
199    ///
200    /// ```
201    /// use rumtk_core::{rumtk_cache_fetch, rumtk_cache_push};
202    /// use rumtk_core::cache::{new_cache, LazyRUMCache, cache_push};
203    /// use std::sync::Arc;
204    ///
205    /// type StringCache = LazyRUMCache<String, Vec<String>>;
206    ///
207    /// fn init_cache(k: &String) -> Vec<String> {
208    ///    vec![]
209    /// }
210    ///
211    /// let mut cache: StringCache = new_cache();
212    ///
213    /// let test_key: String = String::from("Hello World");
214    /// let test_value: String = String::from("?????");
215    ///
216    /// rumtk_cache_fetch!(
217    ///     &raw mut cache,
218    ///     &test_key,
219    ///     init_cache
220    /// );
221    ///
222    /// let v = rumtk_cache_push!(
223    ///     &raw mut cache,
224    ///     &test_key,
225    ///     &vec![test_value.clone()]
226    /// );
227    ///
228    /// 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!");
229    ///
230    ///
231    /// ```
232    ///
233    #[macro_export]
234    macro_rules! rumtk_cache_push {
235        ( $cache:expr, $key:expr, $val:expr ) => {{
236            use $crate::cache::cache_push;
237            // Do not remove the clippy disable decorator here since we do intend to expand within
238            // the unsafe block. Expanding elsewhere prevents us from getting access to the cache's
239            // internal references due to compiler error
240            #[allow(clippy::macro_metavars_in_unsafe)]
241            unsafe {
242                cache_push($cache, $key, $val)
243            }
244        }};
245    }
246
247    ///
248    /// Retrieves the cached item in immutable mode.
249    ///
250    /// ```
251    /// use rumtk_core::{rumtk_cache_fetch, rumtk_cache_push, rumtk_cache_get};
252    /// use rumtk_core::cache::{new_cache, LazyRUMCache, cache_push, cache_get};
253    /// use std::sync::Arc;
254    ///
255    /// type StringCache = LazyRUMCache<String, Vec<String>>;
256    ///
257    /// fn init_cache(k: &String) -> Vec<String> {
258    ///    vec![]
259    /// }
260    ///
261    /// let mut cache: StringCache = new_cache();
262    /// static DEFAULT_VEC: Vec<String> = vec![];
263    ///
264    /// let test_key: String = String::from("Hello World");
265    /// let test_value: String = String::from("?????");
266    ///
267    /// rumtk_cache_fetch!(
268    ///     &raw mut cache,
269    ///     &test_key,
270    ///     init_cache
271    /// );
272    ///
273    /// rumtk_cache_push!(
274    ///     &raw mut cache,
275    ///     &test_key,
276    ///     &vec![test_value.clone()]
277    /// );
278    ///
279    /// let v = rumtk_cache_get!(
280    ///     &raw mut cache,
281    ///     &test_key,
282    ///     &DEFAULT_VEC
283    /// );
284    ///
285    /// 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!");
286    ///
287    ///
288    /// ```
289    ///
290    #[macro_export]
291    macro_rules! rumtk_cache_get {
292        ( $cache:expr, $key:expr, $default_function:expr ) => {{
293            use $crate::cache::cache_get;
294            // Do not remove the clippy disable decorator here since we do intend to expand within
295            // the unsafe block. Expanding elsewhere prevents us from getting access to the cache's
296            // internal references due to compiler error
297            #[allow(clippy::macro_metavars_in_unsafe)]
298            unsafe {
299                cache_get($cache, $key, $default_function)
300            }
301        }};
302    }
303
304    ///
305    /// Retrieves the cached item in mutable mode. What this means is that the internal [Spin Lock](std::sync::RwLock)
306    /// is set to write. Therefore, this macro will block until `all read` requests are satisfied. Any further `read`
307    /// requests will be blocked until you finish making use of the item. Avoid using this macro often unless
308    /// the cache usage is limited to safe interfaces. You are essentially bypassing the no globals ethos and
309    /// thus these APIs should be used with care!!!!!!
310    ///
311    /// ```
312    /// use rumtk_core::{rumtk_cache_fetch, rumtk_cache_push, rumtk_cache_get, rumtk_cache_get_mut};
313    /// use rumtk_core::cache::{new_cache, LazyRUMCache, cache_push, cache_get};
314    /// use std::sync::Arc;
315    ///
316    /// type StringCache = LazyRUMCache<String, Vec<String>>;
317    ///
318    /// fn init_cache(k: &String) -> Vec<String> {
319    ///    vec![]
320    /// }
321    ///
322    /// let mut cache: StringCache = new_cache();
323    /// static mut DEFAULT_VEC: Vec<String> = vec![];
324    ///
325    /// let test_key: String = String::from("Hello World");
326    /// let test_value: String = String::from("?????");
327    ///
328    /// rumtk_cache_fetch!(
329    ///     &raw mut cache,
330    ///     &test_key,
331    ///     init_cache
332    /// );
333    ///
334    /// rumtk_cache_get_mut!(
335    ///     &raw mut cache,
336    ///     &test_key,
337    ///     &mut DEFAULT_VEC
338    /// ).push(test_value.clone());
339    ///
340    /// let v = rumtk_cache_get!(
341    ///     &raw mut cache,
342    ///     &test_key,
343    ///     &DEFAULT_VEC
344    /// );
345    ///
346    /// 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!");
347    ///
348    ///
349    /// ```
350    ///
351    #[macro_export]
352    macro_rules! rumtk_cache_get_mut {
353        ( $cache:expr, $key:expr, $default_function:expr ) => {{
354            use $crate::cache::cache_get_mut;
355            // Do not remove the clippy disable decorator here since we do intend to expand within
356            // the unsafe block. Expanding elsewhere prevents us from getting access to the cache's
357            // internal references due to compiler error
358            #[allow(clippy::macro_metavars_in_unsafe)]
359            unsafe {
360                cache_get_mut($cache, $key, $default_function)
361            }
362        }};
363    }
364}