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}