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    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}