use std::collections::HashMap;
use std::hash::Hash;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::RwLock;
fn read_lock<T>(lock: &RwLock<T>) -> std::sync::RwLockReadGuard<'_, T> {
lock.read().unwrap_or_else(|p| p.into_inner())
}
fn write_lock<T>(lock: &RwLock<T>) -> std::sync::RwLockWriteGuard<'_, T> {
lock.write().unwrap_or_else(|p| p.into_inner())
}
pub struct BufferRing<K, V>
where
K: Clone + Eq + Hash,
V: Clone,
{
capacity: usize,
slots: RwLock<Vec<Option<(K, V)>>>,
hand: AtomicUsize,
map: RwLock<HashMap<K, usize>>,
}
impl<K, V> BufferRing<K, V>
where
K: Clone + Eq + Hash,
V: Clone,
{
pub fn new(capacity: usize) -> Self {
let capacity = capacity.max(1);
Self {
capacity,
slots: RwLock::new(vec![None; capacity]),
hand: AtomicUsize::new(0),
map: RwLock::new(HashMap::with_capacity(capacity)),
}
}
pub fn capacity(&self) -> usize {
self.capacity
}
pub fn len(&self) -> usize {
read_lock(&self.map).len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn get(&self, key: &K) -> Option<V> {
let map = read_lock(&self.map);
let idx = *map.get(key)?;
drop(map);
let slots = read_lock(&self.slots);
slots
.get(idx)
.and_then(|s| s.as_ref().map(|(_, v)| v.clone()))
}
pub fn insert(&self, key: K, value: V) -> Option<(K, V)> {
{
let map = read_lock(&self.map);
if let Some(&idx) = map.get(&key) {
drop(map);
let mut slots = write_lock(&self.slots);
if let Some(slot) = slots.get_mut(idx) {
*slot = Some((key, value));
}
return None;
}
}
let mut map = write_lock(&self.map);
let mut slots = write_lock(&self.slots);
if map.len() < self.capacity {
let start = self.hand.load(Ordering::Relaxed) % self.capacity;
for offset in 0..self.capacity {
let idx = (start + offset) % self.capacity;
if slots[idx].is_none() {
slots[idx] = Some((key.clone(), value));
map.insert(key, idx);
self.hand
.store((idx + 1) % self.capacity, Ordering::Relaxed);
return None;
}
}
}
let victim_idx = self.hand.load(Ordering::Relaxed) % self.capacity;
let evicted = slots[victim_idx].take();
if let Some((ref evicted_key, _)) = evicted {
map.remove(evicted_key);
}
slots[victim_idx] = Some((key.clone(), value));
map.insert(key, victim_idx);
self.hand
.store((victim_idx + 1) % self.capacity, Ordering::Relaxed);
evicted
}
pub fn clear(&self) {
let mut slots = write_lock(&self.slots);
let mut map = write_lock(&self.map);
for slot in slots.iter_mut() {
*slot = None;
}
map.clear();
self.hand.store(0, Ordering::Relaxed);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_ring_returns_none() {
let ring: BufferRing<u32, &str> = BufferRing::new(4);
assert!(ring.is_empty());
assert_eq!(ring.len(), 0);
assert!(ring.get(&1).is_none());
}
#[test]
fn insert_into_empty_slot_no_eviction() {
let ring: BufferRing<u32, String> = BufferRing::new(4);
assert!(ring.insert(1, "a".to_string()).is_none());
assert!(ring.insert(2, "b".to_string()).is_none());
assert_eq!(ring.get(&1), Some("a".to_string()));
assert_eq!(ring.get(&2), Some("b".to_string()));
assert_eq!(ring.len(), 2);
}
#[test]
fn ring_recycles_at_capacity() {
let ring: BufferRing<u32, u32> = BufferRing::new(16);
for i in 0..20 {
let _ = ring.insert(i, i * 10);
}
assert_eq!(ring.len(), 16);
for i in 0..4 {
assert!(ring.get(&i).is_none(), "key {i} should be evicted");
}
for i in 4..20 {
assert_eq!(ring.get(&i), Some(i * 10), "key {i} should be present");
}
}
#[test]
fn insert_returns_evicted_pair_when_full() {
let ring: BufferRing<u32, &str> = BufferRing::new(2);
assert!(ring.insert(1, "a").is_none());
assert!(ring.insert(2, "b").is_none());
let evicted = ring.insert(3, "c");
assert!(evicted.is_some());
let (ek, ev) = evicted.unwrap();
assert_eq!(ek, 1);
assert_eq!(ev, "a");
}
#[test]
fn update_in_place_does_not_evict() {
let ring: BufferRing<u32, &str> = BufferRing::new(2);
ring.insert(1, "a");
ring.insert(2, "b");
let evicted = ring.insert(1, "A");
assert!(evicted.is_none());
assert_eq!(ring.get(&1), Some("A"));
assert_eq!(ring.get(&2), Some("b"));
assert_eq!(ring.len(), 2);
}
#[test]
fn clear_drops_all_entries() {
let ring: BufferRing<u32, &str> = BufferRing::new(4);
ring.insert(1, "a");
ring.insert(2, "b");
ring.clear();
assert!(ring.is_empty());
assert!(ring.get(&1).is_none());
}
#[test]
fn capacity_clamped_to_at_least_one() {
let ring: BufferRing<u32, &str> = BufferRing::new(0);
assert_eq!(ring.capacity(), 1);
ring.insert(1, "a");
let ev = ring.insert(2, "b");
assert_eq!(ev, Some((1, "a")));
}
}