use std::cell::RefCell;
use rustc_hash::FxHashMap;
use uuid::Uuid;
thread_local! {
static PASS: RefCell<PassState> = RefCell::new(PassState::new());
}
struct PassState {
depth: u32,
fires: FxHashMap<Uuid, u32>,
last: Option<PassReport>,
}
impl PassState {
fn new() -> Self {
Self {
depth: 0,
fires: FxHashMap::default(),
last: None,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct PassReport {
pub per_cell: Vec<(Uuid, u32)>,
}
struct PassGuard;
impl Drop for PassGuard {
fn drop(&mut self) {
PASS.with(|p| {
let mut p = p.borrow_mut();
p.depth = p.depth.saturating_sub(1);
if p.depth == 0 {
let per_cell = p.fires.drain().collect();
p.last = Some(PassReport { per_cell });
}
});
}
}
impl PassReport {
#[must_use]
pub fn total_fires(&self) -> u64 {
self.per_cell.iter().map(|&(_, n)| u64::from(n)).sum()
}
#[must_use]
pub fn total_refires(&self) -> u64 {
self.per_cell
.iter()
.map(|&(_, n)| u64::from(n.saturating_sub(1)))
.sum()
}
#[must_use]
pub const fn cells_fired(&self) -> usize {
self.per_cell.len()
}
#[must_use]
pub fn refiring_cells(&self) -> Vec<(Uuid, u32)> {
let mut v: Vec<_> = self
.per_cell
.iter()
.copied()
.filter(|&(_, n)| n > 1)
.collect();
v.sort_by(|a, b| b.1.cmp(&a.1).then(a.0.cmp(&b.0)));
v
}
#[must_use]
pub fn coalesceable_fraction(&self) -> f64 {
fn u64_to_f64(value: u64) -> f64 {
let high = u32::try_from(value >> 32).unwrap_or(u32::MAX);
let low = u32::try_from(value & u64::from(u32::MAX)).unwrap_or(u32::MAX);
f64::from(high).mul_add(4_294_967_296.0, f64::from(low))
}
let total = self.total_fires();
if total == 0 {
0.0
} else {
u64_to_f64(self.total_refires()) / u64_to_f64(total)
}
}
}
pub fn pass<R>(f: impl FnOnce() -> R) -> R {
PASS.with(|p| {
let mut p = p.borrow_mut();
p.depth = p.depth.saturating_add(1);
if p.depth == 1 {
p.fires.clear();
}
});
let _guard = PassGuard;
f()
}
#[must_use]
pub fn take_report() -> Option<PassReport> {
PASS.with(|p| p.borrow_mut().last.take())
}
#[inline]
pub(crate) fn record_fire(id: Uuid) {
PASS.with(|p| {
let mut p = p.borrow_mut();
if p.depth > 0 {
let count = p.fires.entry(id).or_insert(0);
*count = count.saturating_add(1);
}
});
}