Skip to main content

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    /// Create an empty cache with no cached graph.
31    pub fn new() -> Self {
32        GenerationCache {
33            inner: RwLock::new(None),
34        }
35    }
36
37    /// Return cached graph if `generation` matches, else call `build` and cache result.
38    ///
39    /// `build` is called only on a miss or stale entry. On error from `build`,
40    /// the existing cache entry is left unchanged.
41    ///
42    /// Concurrent callers that both observe a miss may each call `build`
43    /// independently. The last writer wins the cache slot. `build` must be
44    /// idempotent — callers that need to prevent redundant work should
45    /// serialize `get_or_build` at a higher level.
46    ///
47    /// # Examples
48    ///
49    /// ```
50    /// use petgraph_live::cache::GenerationCache;
51    /// use std::sync::Arc;
52    ///
53    /// let cache: GenerationCache<Vec<u32>> = GenerationCache::new();
54    /// let g1: Arc<Vec<u32>> = cache.get_or_build(1, || Ok::<_, ()>(vec![1, 2, 3])).unwrap();
55    /// assert_eq!(*g1, vec![1, 2, 3]);
56    ///
57    /// // Same generation — cached Arc returned, build closure not called.
58    /// let g2 = cache.get_or_build(1, || Ok::<_, ()>(vec![9, 9])).unwrap();
59    /// assert!(Arc::ptr_eq(&g1, &g2));
60    /// ```
61    pub fn get_or_build<F, E>(&self, generation: u64, build: F) -> Result<Arc<G>, E>
62    where
63        F: FnOnce() -> Result<G, E>,
64    {
65        {
66            let guard = self.inner.read().unwrap();
67            if let Some(entry) = guard.as_ref()
68                && entry.generation == generation
69            {
70                return Ok(Arc::clone(&entry.graph));
71            }
72        }
73        let graph = Arc::new(build()?);
74        *self.inner.write().unwrap() = Some(CacheEntry {
75            graph: Arc::clone(&graph),
76            generation,
77        });
78        Ok(graph)
79    }
80
81    /// Force cache invalidation. Next `get_or_build` call always rebuilds.
82    pub fn invalidate(&self) {
83        *self.inner.write().unwrap() = None;
84    }
85
86    /// Generation of the currently cached graph, or `None` if cache is empty.
87    pub fn current_generation(&self) -> Option<u64> {
88        self.inner.read().unwrap().as_ref().map(|e| e.generation)
89    }
90}
91
92impl<G> Default for GenerationCache<G> {
93    fn default() -> Self {
94        Self::new()
95    }
96}