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