use std::cmp;
use std::collections::HashMap;
use std::fmt;
use std::sync::atomic;
use std::sync::atomic::AtomicU32;
use std::sync::Arc;
use std::sync::OnceLock;
use std::sync::RwLock;
#[derive(Clone)]
pub struct VerLink {
inner: Arc<Inner>,
}
#[derive(Clone)]
struct Inner {
parent: Option<VerLink>,
base: u32,
gen: u32,
}
impl VerLink {
pub fn new() -> Self {
let inner = Inner {
parent: None,
base: next_id(),
gen: 0,
};
Self {
inner: Arc::new(inner),
}
}
pub fn bump(&mut self) {
match Arc::get_mut(&mut self.inner) {
Some(_inner) => {
}
None => {
let next_inner = Inner {
parent: Some(self.clone()),
base: self.inner.base,
gen: self.inner.gen + 1,
};
let next = Self {
inner: Arc::new(next_inner),
};
*self = next;
}
}
}
}
impl PartialOrd for VerLink {
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
if self.inner.base != other.inner.base {
return None;
}
if self.inner.gen < other.inner.gen {
other.partial_cmp(self).map(|o| o.reverse())
} else {
debug_assert!(self.inner.gen >= other.inner.gen);
if Arc::ptr_eq(&self.inner, &other.inner) {
return Some(cmp::Ordering::Equal);
}
let mut cur = self.inner.parent.as_ref();
while let Some(this) = cur {
if this.inner.gen < other.inner.gen {
return None;
}
if Arc::ptr_eq(&this.inner, &other.inner) {
return Some(cmp::Ordering::Greater);
}
cur = this.inner.parent.as_ref();
}
None
}
}
}
impl PartialEq for VerLink {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.inner, &other.inner)
}
}
impl fmt::Debug for VerLink {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut cur = Some(self);
while let Some(this) = cur {
write!(f, "{:p}", this.inner.as_ref())?;
cur = this.inner.parent.as_ref();
f.write_str("->")?;
if cur.is_none() {
write!(f, "{}", this.inner.base)?;
}
}
Ok(())
}
}
static CACHE: OnceLock<RwLock<HashMap<String, ((u64, u64), VerLink)>>> = OnceLock::new();
fn storage_version_cache() -> &'static RwLock<HashMap<String, ((u64, u64), VerLink)>> {
CACHE.get_or_init(Default::default)
}
impl VerLink {
pub fn clear_storage_version_cache() {
let mut cache = storage_version_cache().write().unwrap();
cache.clear();
}
pub fn from_storage_version(str_id: &str, version: (u64, u64)) -> Option<VerLink> {
let cache = storage_version_cache().read().unwrap();
let (cached_version, verlink) = cache.get(str_id)?;
if cached_version == &version {
Some(verlink.clone())
} else {
None
}
}
pub fn associate_storage_version(&self, str_id: String, version: (u64, u64)) {
let mut cache = storage_version_cache().write().unwrap();
cache.insert(str_id, (version, self.clone()));
}
pub fn from_storage_version_or_new(str_id: &str, version: (u64, u64)) -> VerLink {
match Self::from_storage_version(str_id, version) {
Some(v) => v,
None => {
let v = Self::new();
v.associate_storage_version(str_id.to_string(), version);
v
}
}
}
}
fn next_id() -> u32 {
static ID: AtomicU32 = AtomicU32::new(0);
ID.fetch_add(1, atomic::Ordering::AcqRel)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_individual_news_are_incompatible() {
let a = VerLink::new();
let b = VerLink::new();
assert!(compatible(&a, &b).is_none());
}
#[test]
fn test_clone_compatible() {
let a = VerLink::new();
let b = a.clone();
assert_eq!(&a, &b);
assert_eq!(compatible(&a, &b), Some(&b));
}
#[test]
fn test_bump_is_different_and_backwards_compatible() {
let a = VerLink::new();
let mut b = a.clone();
b.bump();
assert_ne!(&b, &a);
assert_eq!(compatible(&a, &b), Some(&b));
b.bump();
b.bump();
assert_eq!(compatible(&a, &b), Some(&b));
}
#[test]
fn test_clone_bump_twice() {
let a = VerLink::new();
let mut b = a.clone();
b.bump();
let mut c = b.clone();
c.bump();
assert_eq!(compatible(&a, &c), Some(&c));
assert_eq!(compatible(&b, &c), Some(&c));
}
#[test]
fn test_bump_independently_become_incompatible() {
let mut a = VerLink::new();
let mut b = a.clone();
b.bump();
a.bump();
assert_eq!(compatible(&a, &b), None);
}
#[test]
fn test_bump_avoid_increase_len_if_possible() {
let a = VerLink::new();
let mut b = a.clone();
assert_eq!(a.chain_len(), 1);
assert_eq!(b.chain_len(), 1);
b.bump(); assert_eq!(b.chain_len(), 2);
b.bump(); b.bump();
assert_eq!(b.chain_len(), 2);
}
#[test]
fn test_storage_version_cache() {
let a = VerLink::new();
let v = (100, 200);
assert!(VerLink::from_storage_version("x", v).is_none());
a.associate_storage_version("x".to_string(), v);
assert_eq!(VerLink::from_storage_version("x", v).unwrap(), a);
let b = VerLink::from_storage_version_or_new("y", v);
assert_ne!(&b, &a);
let c = VerLink::from_storage_version_or_new("y", v);
assert_eq!(&b, &c);
}
#[allow(clippy::neg_cmp_op_on_partial_ord)]
fn compatible<'a>(a: &'a VerLink, b: &'a VerLink) -> Option<&'a VerLink> {
if a == b {
assert!(!(a != b));
assert!(!(a < b));
assert!(!(b < a));
assert!(!(a > b));
assert!(!(b > a));
Some(a)
} else if a < b {
assert!(!(a == b));
assert!(a != b);
assert!(!(b < a));
assert!(!(a > b));
assert!(b > a);
Some(b)
} else if a > b {
assert!(!(a == b));
assert!(a != b);
assert!(!(a < b));
assert!(b < a);
assert!(!(b > a));
Some(a)
} else {
assert!(!(a == b));
assert!(a != b);
assert!(!(a < b));
assert!(!(a > b));
assert!(!(b < a));
assert!(!(b > a));
None
}
}
impl VerLink {
fn chain_len(&self) -> usize {
let mut len = 0;
let mut cur = Some(self);
while let Some(this) = cur {
len += 1;
cur = this.inner.parent.as_ref();
}
len
}
}
}