use std::hash::{Hash, Hasher};
use xxhash_rust::xxh3::Xxh3Default;
use crate::cache::page_id::PageId;
pub trait Allocator: Send + Sync {
fn allocate(&self, page_id: &PageId, num_dirs: usize) -> usize;
}
#[derive(Debug, Default, Clone, Copy)]
pub struct HashAllocator;
impl HashAllocator {
pub fn new() -> Self {
Self
}
}
impl Allocator for HashAllocator {
fn allocate(&self, page_id: &PageId, num_dirs: usize) -> usize {
if num_dirs <= 1 {
return 0;
}
let mut h = Xxh3Default::default();
page_id.file_id.hash(&mut h);
(h.finish() % num_dirs as u64) as usize
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn single_dir_always_zero() {
let a = HashAllocator::new();
assert_eq!(a.allocate(&PageId::new("f", 0), 1), 0);
assert_eq!(a.allocate(&PageId::new("f", 9), 1), 0);
}
#[test]
fn same_file_same_dir() {
let a = HashAllocator::new();
let d0 = a.allocate(&PageId::new("file-x", 0), 8);
let d1 = a.allocate(&PageId::new("file-x", 1), 8);
let d2 = a.allocate(&PageId::new("file-x", 99), 8);
assert_eq!(d0, d1);
assert_eq!(d1, d2);
assert!(d0 < 8);
}
#[test]
fn distributes_across_dirs() {
let a = HashAllocator::new();
let dirs: std::collections::HashSet<usize> = (0..200)
.map(|i| a.allocate(&PageId::new(format!("file-{i}"), 0), 8))
.collect();
assert!(dirs.len() > 1, "allocator should spread files across dirs");
}
}