use super::BitMap;
use std::{
collections::VecDeque,
sync::atomic::{AtomicU64, Ordering},
};
pub struct Atomic {
words: Vec<AtomicU64>,
len: u64,
}
impl Atomic {
const WORD_BITS: u64 = u64::BITS as u64;
pub fn zeroes(len: u64) -> Self {
let words = (0..len.div_ceil(Self::WORD_BITS))
.map(|_| AtomicU64::new(0))
.collect();
Self { words, len }
}
pub fn set(&self, bit: u64) {
assert!(
bit < self.len,
"bit {} out of bounds (len: {})",
bit,
self.len
);
self.words[(bit / Self::WORD_BITS) as usize]
.fetch_or(1 << (bit % Self::WORD_BITS), Ordering::Relaxed);
}
pub fn into_bitmap(self) -> BitMap {
let chunks: VecDeque<_> = self
.words
.into_iter()
.map(|word| word.into_inner().to_le_bytes())
.collect();
BitMap::from_chunks(chunks, self.len)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_set_and_into_bitmap_matches_plain_sets() {
for len in [0u64, 1, 63, 64, 65, 128, 200] {
let atomic = Atomic::zeroes(len);
let mut expected = BitMap::zeroes(len);
for bit in (0..len).step_by(3).chain(len.checked_sub(1)) {
atomic.set(bit);
expected.set(bit, true);
}
assert_eq!(atomic.into_bitmap(), expected, "len {len}");
}
}
#[test]
fn test_shared_setters() {
let atomic = Atomic::zeroes(1000);
std::thread::scope(|s| {
for stripe in 0..4u64 {
let atomic = &atomic;
s.spawn(move || {
for bit in (stripe..1000).step_by(4) {
atomic.set(bit);
}
});
}
});
assert_eq!(atomic.into_bitmap(), BitMap::ones(1000));
}
#[test]
#[should_panic(expected = "out of bounds")]
fn test_set_past_len_panics() {
Atomic::zeroes(64).set(64);
}
#[test]
#[should_panic(expected = "out of bounds")]
fn test_set_past_len_in_tail_word_panics() {
Atomic::zeroes(65).set(70);
}
#[test]
#[should_panic(expected = "out of bounds")]
fn test_set_on_empty_panics() {
Atomic::zeroes(0).set(0);
}
}