use std::cmp::Reverse;
use std::collections::BinaryHeap;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) struct Item(u64);
pub(crate) enum Subject {
Node(u32),
Region(u32),
}
impl Item {
const REGION: u64 = 1 << 32;
#[allow(non_snake_case)]
pub fn Node(v: u32) -> Item {
Item(u64::from(v))
}
#[allow(non_snake_case)]
pub fn Region(r: u32) -> Item {
Item(Item::REGION | u64::from(r))
}
pub fn subject(self) -> Subject {
if self.0 & Item::REGION == 0 {
Subject::Node(self.0 as u32)
} else {
Subject::Region(self.0 as u32)
}
}
}
pub(crate) type Tracker = RadixHeap<Item>;
pub(crate) struct RadixHeap<T> {
last: i64,
current: BinaryHeap<Reverse<T>>,
buckets: [Vec<(i64, T)>; 64],
occupied: u64,
}
impl<T: Ord + Copy> Default for RadixHeap<T> {
fn default() -> Self {
RadixHeap { last: 0, current: BinaryHeap::new(), buckets: std::array::from_fn(|_| Vec::new()), occupied: 0 }
}
}
impl<T: Ord + Copy> RadixHeap<T> {
fn bucket(&self, t: i64) -> usize {
64 - ((t ^ self.last) as u64).leading_zeros() as usize
}
pub fn clear(&mut self) {
self.last = 0;
self.current.clear();
while self.occupied != 0 {
let b = self.occupied.trailing_zeros() as usize;
self.buckets[b].clear();
self.occupied &= self.occupied - 1;
}
}
pub fn push(&mut self, t: i64, item: T) {
debug_assert!(t >= self.last, "a reminder at {t} before the last time taken, {}", self.last);
if t == self.last {
self.current.push(Reverse(item));
} else {
let b = self.bucket(t);
self.buckets[b].push((t, item));
self.occupied |= 1 << b;
}
}
pub fn pop(&mut self) -> Option<(i64, T)> {
if self.current.is_empty() {
if self.occupied == 0 {
return None;
}
let b = self.occupied.trailing_zeros() as usize;
self.occupied &= !(1 << b);
let mut moving = std::mem::take(&mut self.buckets[b]);
self.last = moving.iter().map(|x| x.0).min().expect("an occupied bucket is not empty");
for &(t, item) in &moving {
if t == self.last {
self.current.push(Reverse(item));
} else {
let nb = self.bucket(t);
self.buckets[nb].push((t, item));
self.occupied |= 1 << nb;
}
}
moving.clear();
self.buckets[b] = moving;
}
self.current.pop().map(|Reverse(item)| (self.last, item))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::surface_code::Xorshift;
#[test]
fn pops_in_exactly_the_binary_heaps_order() {
let mut rng = Xorshift::new(11);
for trial in 0..300 {
let mut radix = Tracker::default();
let mut heap = std::collections::BinaryHeap::new();
let mut now = 0i64;
for _ in 0..2000 {
if !rng.next_u64().is_multiple_of(3) || heap.is_empty() {
let dt = match rng.next_u64() % 4 {
0 => 0,
1 => (rng.next_u64() % 8) as i64,
2 => (rng.next_u64() % 100_000) as i64,
_ => (rng.next_u64() % (1 << 40)) as i64,
};
let item = if rng.next_u64().is_multiple_of(2) {
Item::Node((rng.next_u64() % 50) as u32)
} else {
Item::Region((rng.next_u64() % 50) as u32)
};
radix.push(now + dt, item);
heap.push(std::cmp::Reverse((now + dt, item)));
} else {
let a = radix.pop();
let b = heap.pop().map(|std::cmp::Reverse(x)| x);
assert_eq!(a, b, "trial {trial}");
now = a.unwrap().0;
}
}
while let Some(std::cmp::Reverse(x)) = heap.pop() {
assert_eq!(radix.pop(), Some(x), "trial {trial}, draining");
}
assert_eq!(radix.pop(), None);
}
}
#[test]
fn clear_empties_and_restarts_time() {
let mut t = Tracker::default();
t.push(5, Item::Node(1));
t.push(9, Item::Region(2));
assert_eq!(t.pop(), Some((5, Item::Node(1))));
t.clear();
assert_eq!(t.pop(), None);
t.push(0, Item::Node(3));
assert_eq!(t.pop(), Some((0, Item::Node(3))));
}
}