use std::collections::HashMap;
const CHUNK_BITS: u32 = 4;
const CHUNK_SIDE: i32 = 1 << CHUNK_BITS; const CHUNK_MASK: i32 = CHUNK_SIDE - 1; const CHUNK_VOLUME: usize = 1 << (CHUNK_BITS * 3); const WORDS_PER_CHUNK: usize = CHUNK_VOLUME / 64;
type ChunkKey = (i32, i32, i32);
#[derive(Default)]
pub struct VisitedSet {
chunks: HashMap<ChunkKey, [u64; WORDS_PER_CHUNK]>,
}
impl VisitedSet {
pub fn new() -> Self {
Self {
chunks: HashMap::new(),
}
}
pub fn approx_bytes(&self) -> usize {
self.chunks.len() * (std::mem::size_of::<ChunkKey>() + WORDS_PER_CHUNK * 8)
}
pub fn chunk_count(&self) -> usize {
self.chunks.len()
}
#[inline]
pub fn contains(&self, x: i32, y: i32, z: i32) -> bool {
let key = chunk_key(x, y, z);
match self.chunks.get(&key) {
Some(bits) => {
let (word, bit) = index_in_chunk(x, y, z);
(bits[word] >> bit) & 1 != 0
}
None => false,
}
}
#[inline]
pub fn insert(&mut self, x: i32, y: i32, z: i32) -> bool {
let key = chunk_key(x, y, z);
let chunk = self
.chunks
.entry(key)
.or_insert_with(|| [0u64; WORDS_PER_CHUNK]);
let (word, bit) = index_in_chunk(x, y, z);
let mask = 1u64 << bit;
let was = chunk[word] & mask != 0;
chunk[word] |= mask;
!was
}
}
#[inline]
fn chunk_key(x: i32, y: i32, z: i32) -> ChunkKey {
(x >> CHUNK_BITS, y >> CHUNK_BITS, z >> CHUNK_BITS)
}
#[inline]
fn index_in_chunk(x: i32, y: i32, z: i32) -> (usize, u32) {
let lx = (x & CHUNK_MASK) as u32;
let ly = (y & CHUNK_MASK) as u32;
let lz = (z & CHUNK_MASK) as u32;
let linear = (ly << 8) | (lz << 4) | lx;
let word = (linear >> 6) as usize; let bit = linear & 63;
(word, bit)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn insert_returns_true_only_first_time() {
let mut s = VisitedSet::new();
assert!(s.insert(0, 0, 0));
assert!(!s.insert(0, 0, 0));
assert!(s.insert(1, 0, 0));
}
#[test]
fn contains_matches_insert() {
let mut s = VisitedSet::new();
for &p in &[(0, 0, 0), (5, 6, 7), (-1, -2, -3), (1023, 0, -512)] {
assert!(!s.contains(p.0, p.1, p.2));
s.insert(p.0, p.1, p.2);
assert!(s.contains(p.0, p.1, p.2));
}
assert!(!s.contains(1, 0, 0));
}
#[test]
fn negative_coordinates_share_correct_chunk() {
let mut s = VisitedSet::new();
s.insert(-1, -1, -1);
assert!(s.contains(-1, -1, -1));
s.insert(-16, -16, -16);
assert!(s.contains(-16, -16, -16));
assert_eq!(s.chunk_count(), 1);
s.insert(-17, -1, -1);
assert_eq!(s.chunk_count(), 2);
}
#[test]
fn distinct_chunks_dont_alias() {
let mut s = VisitedSet::new();
s.insert(0, 0, 0);
assert!(!s.contains(16, 0, 0));
assert!(!s.contains(0, 16, 0));
assert!(!s.contains(0, 0, 16));
}
#[test]
fn fills_a_chunk_completely() {
let mut s = VisitedSet::new();
for y in 0..16 {
for z in 0..16 {
for x in 0..16 {
assert!(s.insert(x, y, z));
}
}
}
assert_eq!(s.chunk_count(), 1);
for y in 0..16 {
for z in 0..16 {
for x in 0..16 {
assert!(s.contains(x, y, z));
}
}
}
for y in 0..16 {
for z in 0..16 {
for x in 0..16 {
assert!(!s.insert(x, y, z));
}
}
}
}
}