repr_rs/cache/
eager.rs

1use std::fmt::Debug;
2use crate::cache::{Cache, CacheableRepr};
3use std::future::Future;
4use std::sync::Arc;
5use std::sync::RwLock;
6use tokio::task::{spawn_blocking, JoinHandle};
7
8pub(crate) struct CacheableRead<T, R: Clone + Sync + Send> {
9	read_fn: fn(&T) -> R,
10	cache: Arc<RwLock<Option<R>>>,
11}
12impl<T: Clone + Sync + Send + 'static, R: Clone + Sync + Send + 'static> CacheableRead<T, R> {
13	pub(crate) fn new(read_fn: fn(&T) -> R) -> Self {
14		Self {
15			read_fn,
16			cache: Default::default(),
17		}
18	}
19	pub(crate) fn read(&self, arg: &T) -> R {
20		let res = self.cache.read().unwrap();
21		if let Some(cached) = res.as_ref() {
22			return cached.clone();
23		}
24		(self.read_fn)(arg)
25	}
26	
27	pub(crate) fn update(&self, value: &T) -> JoinHandle<()> {
28		let mut writer = self.cache.write().unwrap();
29		*writer = None;
30		let cell = self.cache.clone();
31		let read_fn = self.read_fn;
32		let value = value.clone();
33		spawn_blocking(move || {
34			let value = value;
35			let mut writer = cell.write().unwrap();
36			*writer = Some(read_fn(&value));
37		})
38	}
39}
40impl<T: 'static + Sync + Send + Clone, R: Clone + 'static + Send + Sync> Cache<T> for CacheableRead<T, R> {
41	fn notify(&self, value: &T) {
42		self.update(value);
43	}
44}
45
46#[cfg(feature = "eager")]
47pub trait EagerCacheLookup<T: Clone + Sync + Send + 'static, I: Fn(&T) -> bool> {
48	fn eager<R: Clone + Clone + Sync + Send + 'static>(&mut self, read_fn: fn(&T) -> R) -> impl Future<Output=R>;
49	fn unregister<R: Clone + Clone + Sync + Send + 'static>(&mut self, read_fn: fn(&T) -> R) -> bool;
50}
51#[cfg(feature = "eager")]
52impl<T: Debug + Clone + Sync + Send + 'static, I: Fn(&T) -> bool> EagerCacheLookup<T, I> for CacheableRepr<T, I> {
53	/// Borrows a read-only view of the value in the representation invariant and caches the
54	/// result of the read function. The cache is keyed by the read function's address, so in general
55	/// you should use function references instead of closures. It is a bug to perform any side effects
56	/// in the read function (i.e. reading from a file). This cache is updated eagerly, so whenever
57	/// the value is mutated, all eager caches will be updated in parallel. See [`CacheableRepr::lazy`] for
58	/// a lazy version of this function.
59	///
60	/// ```rust
61	/// # tokio::runtime::Runtime::new().unwrap().block_on(async move {
62	/// use std::time::Duration;
63	/// use std::sync::atomic::{AtomicU32, Ordering};
64	/// use repr_rs::{CacheableRepr, EagerCacheLookup};
65	/// #[derive(Debug, Clone)]
66	/// struct Person { name: String }
67	/// let mut repr = CacheableRepr::new(Person { name: "Alice and Bob together at last".into() }, |p| !p.name.is_empty());
68	/// static READ_SPY: AtomicU32 = AtomicU32::new(0);
69	/// fn expensive_read(p: &Person) -> usize {
70	///   // Just for demonstration purposes.
71	///   // Do not do side effects in your read functions!
72	///   READ_SPY.fetch_add(1, Ordering::Relaxed);
73	///   fib(p.name.len())
74	/// }
75	/// let fib_of_name_len = repr.eager(expensive_read).await;
76	/// assert_eq!(832040, fib_of_name_len);
77	/// // this does not recompute the fibonacci number, it just gets it from the cache!
78	/// let fib_of_name_len2 = repr.eager(expensive_read).await;
79	/// assert_eq!(832040, fib_of_name_len2);
80	/// assert_eq!(1, READ_SPY.load(Ordering::Relaxed));
81	/// repr.write().name = "Alice".into();
82	/// // if we wait a bit we can see that a new value has been computed
83	/// tokio::time::sleep(Duration::from_millis(100)).await;
84	/// assert_eq!(2, READ_SPY.load(Ordering::Relaxed));
85	/// // Now when we fetch it again, we should see the new value without needing to recompute it
86	/// let fib_of_name_len3 = repr.eager(expensive_read).await;
87	/// assert_eq!(5, fib_of_name_len3);
88	/// assert_eq!(2, READ_SPY.load(Ordering::Relaxed));
89	/// # fn fib(n: usize) -> usize {
90	/// #   if n <= 1 { n } else { fib(n - 1) + fib(n - 2) }
91	/// # }
92	/// # })
93	#[allow(clippy::await_holding_refcell_ref)] // safe because the &mut self on this fn prevents other borrows
94	async fn eager<R: Clone + Sync + Send + 'static>(&mut self, read_fn: fn(&T) -> R) -> R {
95		let fn_identity = read_fn as *const fn(&T) -> R as usize;
96		let is_empty = !self.eager_caches.contains_key(&fn_identity);
97		let entry = self.eager_caches.entry(fn_identity);
98
99		let cache = entry.or_insert_with(|| Box::new(CacheableRead::<T, R>::new(read_fn)));
100		let cache = cache.downcast_mut::<CacheableRead<T, R>>().unwrap();
101		let data = self.inner.inner.get_mut();
102		if is_empty {
103			cache.update(data).await.unwrap();
104		}
105		cache.read(data)
106	}
107	/// Unregisters an eager cache. Returns true if the cache was found and removed.
108	fn unregister<R: Clone + Clone + Sync + Send + 'static>(&mut self, read_fn: fn(&T) -> R) -> bool {
109		let fn_identity = read_fn as *const fn(&T) -> R as usize;
110		self.eager_caches.remove(&fn_identity).is_some()
111	}
112}