Skip to main content

lattice_host/
versioned.rs

1//! `Versioned<T>` — a tiny newtype wrapper that bumps a monotonic
2//! `u64` version on every `&mut` access via `DerefMut`.
3//!
4//! ## Why this exists
5//!
6//! Perf plan B.4: identity-preserving Arc publish for unchanged
7//! `RenderState` sub-states. Every `Editor::build_render_state`
8//! tick today freshly `Arc::new`s every sub-state struct even when
9//! the backing data hasn't moved. Most keystrokes don't touch
10//! `pane_tree` / `active_modes` / `buffer_locals` / `pane_highlights` /
11//! `lsp_progress`, so reusing the prior Arc when nothing changed
12//! drops both the outer allocation and the inner map / tree clones
13//! they would otherwise produce.
14//!
15//! The cache lookup needs a cheap-to-compare key. The naive option
16//! ("content-hash on every publish") makes the hash cost the
17//! rebuild cost — wash. The alternative ("dirty-flag set by every
18//! mutator") is bulletproof in spec but easy to forget at one of
19//! the ~30 mutation sites and fails silently when missed.
20//!
21//! `Versioned<T>` takes the third path: any code that obtains a
22//! `&mut` to the inner data goes through `DerefMut`, which
23//! increments the counter atomically with the access. There is no
24//! way to mutate through a `Versioned<T>` without bumping. Reads
25//! (`Deref`) don't bump. The cost is one `u64` add per `&mut`
26//! borrow — sub-nanosecond, dwarfed by whatever mutation follows.
27//!
28//! ## Trade-offs
29//!
30//! - Over-bumps on read-then-no-op-mutate (e.g. `.iter_mut()` that
31//!   the caller never actually writes through) cause a spurious
32//!   cache miss the next publish. Safe; just costs one extra
33//!   rebuild of that sub-state. Bounded.
34//! - Field-assignment (`self.field = ...`) is not a `DerefMut` —
35//!   it replaces the wrapper entirely. The `From<T>` /
36//!   `Versioned::new` constructors zero the version, so the next
37//!   `build_render_state` will rebuild correctly. Use [`Self::replace`]
38//!   when you want to bump rather than reset.
39//! - Single-threaded only by design. The wrapper isn't atomic; it
40//!   relies on `Editor` being mutated from one thread (the actor).
41//!   If you need cross-thread mutation, you have other problems —
42//!   talk to the actor instead of mutating Editor directly.
43
44use std::ops::{Deref, DerefMut};
45
46/// Newtype wrapper that bumps a `u64` version counter on every
47/// `DerefMut` access. See module docs for the rationale.
48#[derive(Debug, Default, Clone)]
49pub struct Versioned<T> {
50    inner: T,
51    version: u64,
52}
53
54impl<T> Versioned<T> {
55    /// Wrap a value at version 0.
56    pub fn new(inner: T) -> Self {
57        Self { inner, version: 0 }
58    }
59
60    /// Current version. Cache consumers compare against the version
61    /// stored alongside the cached output to decide whether to
62    /// reuse.
63    pub fn version(&self) -> u64 {
64        self.version
65    }
66
67    /// Replace the inner value AND bump the version (whereas a
68    /// plain `*v = Versioned::new(x)` would reset to 0). Use when
69    /// you need the swap to invalidate downstream caches.
70    pub fn replace(&mut self, value: T) -> T {
71        // Bump first so a panic in `mem::replace` doesn't leave us
72        // with stale (data, version) pairing.
73        self.version = self.version.wrapping_add(1);
74        std::mem::replace(&mut self.inner, value)
75    }
76
77    /// Unwrap to the underlying value, discarding the version.
78    pub fn into_inner(self) -> T {
79        self.inner
80    }
81}
82
83impl<T> Deref for Versioned<T> {
84    type Target = T;
85    fn deref(&self) -> &T {
86        &self.inner
87    }
88}
89
90impl<T> DerefMut for Versioned<T> {
91    fn deref_mut(&mut self) -> &mut T {
92        self.version = self.version.wrapping_add(1);
93        &mut self.inner
94    }
95}
96
97impl<T> From<T> for Versioned<T> {
98    fn from(inner: T) -> Self {
99        Self::new(inner)
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106    use std::collections::HashMap;
107
108    #[test]
109    fn new_starts_at_version_zero() {
110        let v: Versioned<i32> = Versioned::new(42);
111        assert_eq!(v.version(), 0);
112        assert_eq!(*v, 42);
113    }
114
115    #[test]
116    fn deref_does_not_bump() {
117        let v: Versioned<i32> = Versioned::new(7);
118        let _ = *v;
119        let _ = *v;
120        let _ = v.clone();
121        assert_eq!(v.version(), 0);
122    }
123
124    #[test]
125    fn deref_mut_bumps_each_access() {
126        let mut v: Versioned<i32> = Versioned::new(7);
127        *v += 1;
128        assert_eq!(v.version(), 1);
129        *v += 1;
130        assert_eq!(v.version(), 2);
131    }
132
133    #[test]
134    fn replace_bumps_and_returns_old() {
135        let mut v: Versioned<String> = Versioned::new("a".into());
136        let old = v.replace("b".into());
137        assert_eq!(old, "a");
138        assert_eq!(*v, "b");
139        assert_eq!(v.version(), 1);
140    }
141
142    #[test]
143    fn into_inner_discards_version() {
144        let mut v: Versioned<i32> = Versioned::new(1);
145        *v = 2;
146        assert_eq!(v.version(), 1);
147        let inner = v.into_inner();
148        assert_eq!(inner, 2);
149    }
150
151    #[test]
152    fn hashmap_mutator_through_autoref_bumps() {
153        // The whole point of the wrapper: an existing call like
154        // `self.field.insert(k, v)` autorefs `&mut self.field`,
155        // which fires DerefMut and bumps the version. No call-site
156        // change needed.
157        let mut v: Versioned<HashMap<u32, u32>> = Versioned::new(HashMap::new());
158        v.insert(1, 10);
159        assert_eq!(v.version(), 1);
160        v.insert(2, 20);
161        assert_eq!(v.version(), 2);
162        let _len = v.len();
163        let _val = v.get(&1);
164        assert_eq!(v.version(), 2);
165    }
166
167    #[test]
168    fn from_t_constructs_at_version_zero() {
169        let v: Versioned<i32> = 5.into();
170        assert_eq!(v.version(), 0);
171        assert_eq!(*v, 5);
172    }
173}