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}