use std::collections::HashSet;
use std::sync::{Arc, Mutex};
use rand::rngs::StdRng;
use rand::{RngCore, SeedableRng};
#[derive(Clone)]
pub struct IndexTable<Rng = StdRng>(Arc<Mutex<(Rng, HashSet<u32>)>>);
pub struct Index<Rng = StdRng> {
value: u32,
table: IndexTable<Rng>,
}
impl<Rng> IndexTable<Rng>
where
Rng: RngCore,
{
pub fn new_index(&self) -> Index<Rng> {
let mut g = self.0.lock().unwrap();
loop {
let idx = Self::next_id(&mut g.0);
if g.1.insert(idx) {
return Index {
value: idx,
table: Self(Arc::clone(&self.0)),
};
}
}
}
pub(crate) fn next_id(rng: &mut Rng) -> u32 {
rng.next_u32()
}
pub fn from_rng(rng: Rng) -> Self {
IndexTable(Arc::new(Mutex::new((rng, HashSet::new()))))
}
}
impl<Rng> IndexTable<Rng>
where
Rng: SeedableRng + RngCore,
{
pub fn from_os_rng() -> Self {
Self::from_rng(Rng::from_os_rng())
}
}
impl<Rng> IndexTable<Rng> {
fn free_index(&self, index: u32) {
let mut g = self.0.lock().unwrap();
g.1.remove(&index);
}
}
impl Index {
pub fn value(&self) -> u32 {
self.value
}
}
impl<Rng> Drop for Index<Rng> {
fn drop(&mut self) {
self.table.free_index(self.value);
}
}
impl std::fmt::Debug for Index {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.value.fmt(f)
}
}
impl std::fmt::Display for Index {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.value.fmt(f)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Default)]
struct ModCounter {
dividend: u32,
divisor: u32,
}
impl RngCore for ModCounter {
fn next_u32(&mut self) -> u32 {
let v = self.dividend % self.divisor;
self.dividend += 1;
v
}
fn next_u64(&mut self) -> u64 {
unimplemented!()
}
fn fill_bytes(&mut self, _: &mut [u8]) {
unimplemented!()
}
}
#[test]
fn test_reuse_on_drop() {
let table = IndexTable::from_rng(ModCounter {
dividend: 0,
divisor: 3,
});
let a = table.new_index();
let b = table.new_index();
let c = table.new_index();
assert_eq!(a.value, 0);
assert_eq!(b.value, 1);
assert_eq!(c.value, 2);
let recycled = b.value;
drop(b);
for _ in 0..10 {
assert_eq!(table.new_index().value, recycled);
}
}
}