use std::ops::Deref;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
static NEXT: AtomicU64 = AtomicU64::new(1);
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct Revision(pub u64);
impl Revision {
pub const NONE: Revision = Revision(0);
pub fn next() -> Revision {
Revision(NEXT.fetch_add(1, Ordering::Relaxed))
}
}
#[derive(Clone, Debug, Default)]
pub struct Tracked<T> {
value: T,
rev: Revision,
}
pub type Versioned<T> = Tracked<T>;
impl<T> Tracked<T> {
pub fn new(value: T) -> Tracked<T> {
Tracked { value, rev: Revision::next() }
}
pub fn get(&self) -> &T {
&self.value
}
pub fn get_mut(&mut self) -> &mut T {
self.rev = Revision::next();
&mut self.value
}
pub fn set(&mut self, value: T) {
self.value = value;
self.rev = Revision::next();
}
pub fn revision(&self) -> Revision {
self.rev
}
pub fn into_inner(self) -> T {
self.value
}
}
impl<T: Clone> Tracked<Arc<T>> {
pub fn make_mut(&mut self) -> &mut T {
self.rev = Revision::next();
Arc::make_mut(&mut self.value)
}
}
impl<T> Deref for Tracked<T> {
type Target = T;
fn deref(&self) -> &T {
&self.value
}
}
impl<T: PartialEq> PartialEq for Tracked<T> {
fn eq(&self, other: &Self) -> bool {
self.value == other.value
}
}
impl<T: Serialize> Serialize for Tracked<T> {
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
self.value.serialize(s)
}
}
impl<'de, T: Deserialize<'de>> Deserialize<'de> for Tracked<T> {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
T::deserialize(d).map(Tracked::new)
}
}
#[derive(Clone, Debug)]
pub struct Memo<K, V> {
slots: Vec<(K, V, u64)>,
cap: usize,
clock: u64,
}
impl<K: PartialEq, V> Memo<K, V> {
pub fn new(cap: usize) -> Memo<K, V> {
Memo { slots: Vec::new(), cap: cap.max(1), clock: 0 }
}
pub fn get(&mut self, key: &K) -> Option<&V> {
self.clock += 1;
let clock = self.clock;
let slot = self.slots.iter_mut().find(|s| s.0 == *key)?;
slot.2 = clock;
Some(&slot.1)
}
pub fn get_or_insert_with(&mut self, key: K, compute: impl FnOnce() -> V) -> &V {
self.clock += 1;
let clock = self.clock;
let i = match self.slots.iter().position(|s| s.0 == key) {
Some(i) => i,
None if self.slots.len() < self.cap => {
self.slots.push((key, compute(), clock));
self.slots.len() - 1
}
None => {
let lru = self.slots.iter().enumerate().min_by_key(|(_, s)| s.2).map(|(i, _)| i).unwrap_or(0);
self.slots[lru] = (key, compute(), clock);
lru
}
};
let slot = &mut self.slots[i];
slot.2 = clock;
&slot.1
}
pub fn retain(&mut self, mut keep: impl FnMut(&K) -> bool) {
self.slots.retain(|s| keep(&s.0));
}
pub fn clear(&mut self) {
self.slots.clear();
}
pub fn len(&self) -> usize {
self.slots.len()
}
pub fn is_empty(&self) -> bool {
self.slots.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn revisions_are_unique() {
let a = Revision::next();
let b = Revision::next();
assert!(b > a);
assert_ne!(a, Revision::NONE);
}
#[test]
fn tracked_bumps_on_mutation_only() {
let mut t = Tracked::new(vec![1]);
let r0 = t.revision();
assert_eq!(t.len(), 1, "deref reads");
assert_eq!(t.revision(), r0);
let c = t.clone();
assert_eq!(c.revision(), r0, "clone keeps the revision");
t.get_mut().push(2);
assert_ne!(t.revision(), r0);
assert_eq!(c.revision(), r0);
let r1 = t.revision();
t.set(vec![]);
assert_ne!(t.revision(), r1);
assert_eq!(Tracked::new(1), Tracked::new(1), "equality ignores revisions");
}
#[test]
fn tracked_arc_copy_on_write() {
let mut t = Tracked::new(Arc::new(vec![0u8; 4]));
let snapshot = t.clone();
t.make_mut()[0] = 9;
assert_eq!(snapshot[0], 0, "the snapshot is untouched");
assert_eq!(t[0], 9);
assert!(!Arc::ptr_eq(&t, &snapshot));
}
#[test]
fn tracked_serde_is_transparent() {
let t = Tracked::new(5);
assert_eq!(serde_json::to_string(&t).unwrap(), "5");
let back: Tracked<i32> = serde_json::from_str("5").unwrap();
assert_eq!(*back, 5);
assert_ne!(back.revision(), t.revision());
}
#[test]
fn memo_hits_misses_and_evicts_lru() {
let mut m: Memo<(u32, Revision), String> = Memo::new(2);
let r = Revision::next();
let mut computed = 0;
for _ in 0..3 {
m.get_or_insert_with((1, r), || {
computed += 1;
"one".into()
});
}
assert_eq!(computed, 1);
m.get_or_insert_with((2, r), || "two".into());
assert!(m.get(&(1, r)).is_some()); m.get_or_insert_with((3, r), || "three".into());
assert_eq!(m.len(), 2);
assert!(m.get(&(2, r)).is_none(), "least recently used evicted");
assert!(m.get(&(1, r)).is_some());
let r2 = Revision::next();
assert_eq!(m.get_or_insert_with((1, r2), || "one v2".into()), "one v2", "a new revision misses");
m.retain(|k| k.1 == r2);
assert_eq!(m.len(), 1);
m.clear();
assert!(m.is_empty());
assert_eq!(Memo::<u8, u8>::new(0).cap, 1);
}
}