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}