pub const MAX_BUCKET_ENTRIES_CEILING: u32 = 0xFFFE;
pub const DEFAULT_MAX_BUCKET_ENTRIES: u32 = 32;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BucketConfig {
pub max_bucket_entries: u32,
}
impl Default for BucketConfig {
fn default() -> Self {
Self {
max_bucket_entries: DEFAULT_MAX_BUCKET_ENTRIES,
}
}
}
impl BucketConfig {
pub fn validate(&self) -> Result<(), String> {
if self.max_bucket_entries < 1 || self.max_bucket_entries > MAX_BUCKET_ENTRIES_CEILING {
return Err(format!(
"BucketConfig.max_bucket_entries = {} is out of range (must be 1..={})",
self.max_bucket_entries, MAX_BUCKET_ENTRIES_CEILING
));
}
Ok(())
}
}
pub fn classify_mask_words(live_bucket_count: u32) -> u32 {
live_bucket_count.div_ceil(32).max(1)
}
pub fn edge_slot_bits(live_bucket_count: u32) -> u8 {
if live_bucket_count <= 254 {
8
} else {
16
}
}
pub fn edge_slot_words_per_edge(live_bucket_count: u32) -> u32 {
match edge_slot_bits(live_bucket_count) {
8 => 1,
_ => 2,
}
}
pub const EDGE_SENTINEL_SKYBOX_8: u32 = 0xFE;
pub const EDGE_SENTINEL_EMPTY_8: u32 = 0xFF;
pub const EDGE_SENTINEL_SKYBOX_16: u32 = 0xFFFE;
pub const EDGE_SENTINEL_EMPTY_16: u32 = 0xFFFF;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn edge_slot_bits_flips_at_254() {
assert_eq!(edge_slot_bits(0), 8);
assert_eq!(edge_slot_bits(16), 8);
assert_eq!(edge_slot_bits(254), 8); assert_eq!(edge_slot_bits(255), 16);
assert_eq!(edge_slot_bits(1024), 16);
assert_eq!(edge_slot_bits(65534), 16);
assert_eq!(edge_slot_words_per_edge(254), 1);
assert_eq!(edge_slot_words_per_edge(255), 2);
assert_eq!(edge_slot_words_per_edge(1024), 2);
}
#[test]
fn edge_slot_bits_can_represent_every_live_index() {
for &live in &[1u32, 16, 254, 255, 1024, 65534] {
let max_index = live - 1;
let bits = edge_slot_bits(live);
let sentinel_floor = if bits == 8 { 0xFEu32 } else { 0xFFFEu32 };
assert!(
max_index < sentinel_floor,
"live={live}: max index {max_index} collides with the {bits}-bit sentinel floor {sentinel_floor}"
);
}
}
#[test]
fn classify_mask_words_matches_today_at_small_counts() {
assert_eq!(classify_mask_words(0), 1);
assert_eq!(classify_mask_words(1), 1);
assert_eq!(classify_mask_words(16), 1);
assert_eq!(classify_mask_words(32), 1);
}
#[test]
fn bucket_config_default_is_32_and_validates() {
let cfg = BucketConfig::default();
assert_eq!(cfg.max_bucket_entries, 32);
assert!(cfg.validate().is_ok());
}
#[test]
fn bucket_config_validation_bounds() {
assert!(BucketConfig {
max_bucket_entries: 1
}
.validate()
.is_ok());
assert!(BucketConfig {
max_bucket_entries: 254
}
.validate()
.is_ok());
assert!(BucketConfig {
max_bucket_entries: 1024
}
.validate()
.is_ok());
assert!(BucketConfig {
max_bucket_entries: 65534
}
.validate()
.is_ok());
assert!(BucketConfig {
max_bucket_entries: 0
}
.validate()
.is_err());
assert!(BucketConfig {
max_bucket_entries: 65535
}
.validate()
.is_err());
}
#[test]
fn classify_mask_words_grows_one_word_per_32_buckets() {
assert_eq!(classify_mask_words(33), 2);
assert_eq!(classify_mask_words(64), 2);
assert_eq!(classify_mask_words(65), 3);
assert_eq!(classify_mask_words(254), 8); assert_eq!(classify_mask_words(255), 8);
assert_eq!(classify_mask_words(256), 8);
assert_eq!(classify_mask_words(1024), 32); assert_eq!(classify_mask_words(65534), 2048); }
}