use std::cell::Cell;
use std::sync::atomic::{AtomicU16, AtomicU32, AtomicUsize, Ordering};
pub trait Word: Copy + Default {
type Atomic: Default;
fn load_atomic(a: &Self::Atomic) -> Self;
fn store_atomic(a: &Self::Atomic, w: Self);
}
impl Word for u16 {
type Atomic = AtomicU16;
fn load_atomic(a: &AtomicU16) -> u16 {
a.load(Ordering::Relaxed)
}
fn store_atomic(a: &AtomicU16, w: u16) {
a.store(w, Ordering::Relaxed);
}
}
impl Word for u32 {
type Atomic = AtomicU32;
fn load_atomic(a: &AtomicU32) -> u32 {
a.load(Ordering::Relaxed)
}
fn store_atomic(a: &AtomicU32, w: u32) {
a.store(w, Ordering::Relaxed);
}
}
pub trait AccumCell<W: Word>: Default {
fn load(&self) -> W;
fn store(&self, w: W);
}
impl<W: Word> AccumCell<W> for Cell<W> {
fn load(&self) -> W {
self.get()
}
fn store(&self, w: W) {
self.set(w);
}
}
pub struct AtomicCell<W: Word>(W::Atomic);
impl<W: Word> Default for AtomicCell<W> {
fn default() -> Self {
Self(W::Atomic::default())
}
}
impl<W: Word> AccumCell<W> for AtomicCell<W> {
fn load(&self) -> W {
W::load_atomic(&self.0)
}
fn store(&self, w: W) {
W::store_atomic(&self.0, w);
}
}
pub trait FloatCell: Default {
fn load(&self) -> f32;
fn store(&self, v: f32);
}
pub trait CounterCell: Default {
fn load(&self) -> usize;
fn fetch_incr(&self) -> usize;
fn store(&self, v: usize);
}
pub trait StorageBackend {
type Float: FloatCell;
type Counter: CounterCell;
type Cell<W: Word>: AccumCell<W>;
}
impl FloatCell for Cell<f32> {
fn load(&self) -> f32 {
self.get()
}
fn store(&self, v: f32) {
self.set(v);
}
}
impl CounterCell for Cell<usize> {
fn load(&self) -> usize {
self.get()
}
fn fetch_incr(&self) -> usize {
let prev = self.get();
self.set(prev + 1);
prev
}
fn store(&self, v: usize) {
self.set(v);
}
}
pub struct Local;
impl StorageBackend for Local {
type Float = Cell<f32>;
type Counter = Cell<usize>;
type Cell<W: Word> = Cell<W>;
}
impl FloatCell for AtomicU32 {
fn load(&self) -> f32 {
f32::from_bits(AtomicU32::load(self, Ordering::Relaxed))
}
fn store(&self, v: f32) {
AtomicU32::store(self, v.to_bits(), Ordering::Relaxed);
}
}
impl CounterCell for AtomicUsize {
fn load(&self) -> usize {
AtomicUsize::load(self, Ordering::Relaxed)
}
fn fetch_incr(&self) -> usize {
self.fetch_add(1, Ordering::Relaxed)
}
fn store(&self, v: usize) {
AtomicUsize::store(self, v, Ordering::Relaxed);
}
}
pub struct Atomic;
impl StorageBackend for Atomic {
type Float = AtomicU32;
type Counter = AtomicUsize;
type Cell<W: Word> = AtomicCell<W>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn word_cells_round_trip_both_widths() {
fn check<B: StorageBackend>() {
let c16 = <B::Cell<u16>>::default();
assert_eq!(c16.load(), 0);
c16.store(40_000);
assert_eq!(c16.load(), 40_000);
let c32 = <B::Cell<u32>>::default();
c32.store(0xDEAD_BEEF);
assert_eq!(c32.load(), 0xDEAD_BEEF);
}
check::<Local>();
check::<Atomic>();
}
#[test]
fn counter_store_sets_value() {
fn check<C: CounterCell>() {
let c = C::default();
c.store(42);
assert_eq!(c.load(), 42);
assert_eq!(c.fetch_incr(), 42);
}
check::<<Local as StorageBackend>::Counter>();
check::<<Atomic as StorageBackend>::Counter>();
}
#[test]
fn atomic_word_cells_are_shareable() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<<Atomic as StorageBackend>::Cell<u16>>();
assert_send_sync::<<Atomic as StorageBackend>::Cell<u32>>();
}
fn float_cell_roundtrips<C: FloatCell>() {
let c = C::default();
assert_eq!(c.load(), 0.0);
for &v in &[1.0f32, -2.5, 0.0, 1e9, -1e-9] {
c.store(v);
assert_eq!(c.load(), v, "round-trip {v}");
}
}
#[test]
fn local_float_cell_roundtrips() {
float_cell_roundtrips::<<Local as StorageBackend>::Float>();
}
#[test]
fn atomic_float_cell_roundtrips_via_bits() {
float_cell_roundtrips::<<Atomic as StorageBackend>::Float>();
}
fn counter_increments<C: CounterCell>() {
let c = C::default();
assert_eq!(c.load(), 0);
assert_eq!(c.fetch_incr(), 0); assert_eq!(c.fetch_incr(), 1);
assert_eq!(c.load(), 2);
}
#[test]
fn local_counter_increments() {
counter_increments::<<Local as StorageBackend>::Counter>();
}
#[test]
fn atomic_counter_increments() {
counter_increments::<<Atomic as StorageBackend>::Counter>();
}
#[test]
fn atomic_backend_cells_are_shareable() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<<Atomic as StorageBackend>::Float>();
assert_send_sync::<<Atomic as StorageBackend>::Counter>();
}
}