petgraph_live/cache.rs
1use std::sync::{Arc, RwLock};
2
3struct CacheEntry<G> {
4 graph: Arc<G>,
5 generation: u64,
6}
7
8/// Hot-reload graph cache keyed on an external generation counter.
9///
10/// `G` is any graph type. `generation` is a monotonic `u64` controlled by the
11/// caller — bump it whenever the underlying data source changes (index commit,
12/// file-watch event, etc.).
13///
14/// Only one graph is cached at a time. Filtered or derived views must be
15/// computed from the cached graph by the caller.
16///
17/// # Examples
18///
19/// ```
20/// use petgraph_live::cache::GenerationCache;
21///
22/// let cache: GenerationCache<Vec<u32>> = GenerationCache::new();
23/// assert_eq!(cache.current_generation(), None);
24/// ```
25pub struct GenerationCache<G> {
26 inner: RwLock<Option<CacheEntry<G>>>,
27}
28
29impl<G> GenerationCache<G> {
30 pub fn new() -> Self {
31 GenerationCache {
32 inner: RwLock::new(None),
33 }
34 }
35
36 /// Return cached graph if `generation` matches, else call `build` and cache result.
37 ///
38 /// `build` is called only on a miss or stale entry. On error from `build`,
39 /// the existing cache entry is left unchanged.
40 ///
41 /// Concurrent callers that both observe a miss may each call `build`
42 /// independently. The last writer wins the cache slot. `build` must be
43 /// idempotent — callers that need to prevent redundant work should
44 /// serialize `get_or_build` at a higher level.
45 ///
46 /// # Examples
47 ///
48 /// ```
49 /// use petgraph_live::cache::GenerationCache;
50 /// use std::sync::Arc;
51 ///
52 /// let cache: GenerationCache<Vec<u32>> = GenerationCache::new();
53 /// let g1: Arc<Vec<u32>> = cache.get_or_build(1, || Ok::<_, ()>(vec![1, 2, 3])).unwrap();
54 /// assert_eq!(*g1, vec![1, 2, 3]);
55 ///
56 /// // Same generation — cached Arc returned, build closure not called.
57 /// let g2 = cache.get_or_build(1, || Ok::<_, ()>(vec![9, 9])).unwrap();
58 /// assert!(Arc::ptr_eq(&g1, &g2));
59 /// ```
60 pub fn get_or_build<F, E>(&self, generation: u64, build: F) -> Result<Arc<G>, E>
61 where
62 F: FnOnce() -> Result<G, E>,
63 {
64 {
65 let guard = self.inner.read().unwrap();
66 if let Some(entry) = guard.as_ref()
67 && entry.generation == generation
68 {
69 return Ok(Arc::clone(&entry.graph));
70 }
71 }
72 let graph = Arc::new(build()?);
73 *self.inner.write().unwrap() = Some(CacheEntry {
74 graph: Arc::clone(&graph),
75 generation,
76 });
77 Ok(graph)
78 }
79
80 /// Force cache invalidation. Next `get_or_build` call always rebuilds.
81 pub fn invalidate(&self) {
82 *self.inner.write().unwrap() = None;
83 }
84
85 /// Generation of the currently cached graph, or `None` if cache is empty.
86 pub fn current_generation(&self) -> Option<u64> {
87 self.inner.read().unwrap().as_ref().map(|e| e.generation)
88 }
89}
90
91impl<G> Default for GenerationCache<G> {
92 fn default() -> Self {
93 Self::new()
94 }
95}