#![cfg(debug_assertions)]
use std::cell::RefCell;
use std::rc::{Rc, Weak};
#[allow(clippy::len_without_is_empty)]
pub trait ModelDebug: 'static {
fn kind(&self) -> &'static str;
fn len(&self) -> usize;
fn debug_dump(&self, out: &mut dyn std::fmt::Write);
}
thread_local! {
static REGISTRY: RefCell<Vec<Entry>> = const { RefCell::new(Vec::new()) };
}
struct Entry {
name: String,
weak: Weak<dyn ModelDebug>,
}
pub fn register(name: impl Into<String>, adapter: Weak<dyn ModelDebug>) {
REGISTRY.with(|cell| {
let mut registry = cell.borrow_mut();
registry.retain(|entry| entry.weak.strong_count() > 0);
registry.push(Entry {
name: name.into(),
weak: adapter,
});
});
}
pub fn snapshot() -> Vec<(String, Rc<dyn ModelDebug>)> {
REGISTRY.with(|cell| {
let mut out: Vec<(String, Rc<dyn ModelDebug>)> = Vec::new();
cell.borrow_mut().retain(|entry| {
if let Some(strong) = entry.weak.upgrade() {
out.push((entry.name.clone(), strong));
true
} else {
false
}
});
out
})
}
#[cfg(test)]
mod tests {
use super::*;
use std::cell::Cell;
struct DummyModel {
len: Cell<usize>,
}
impl ModelDebug for DummyModel {
fn kind(&self) -> &'static str {
"DummyModel"
}
fn len(&self) -> usize {
self.len.get()
}
fn debug_dump(&self, out: &mut dyn std::fmt::Write) {
let _ = write!(out, "len={}", self.len.get());
}
}
#[test]
fn register_and_snapshot_round_trip() {
let snap_before = snapshot();
let initial = snap_before.len();
let m: Rc<dyn ModelDebug> = Rc::new(DummyModel { len: Cell::new(3) });
register("alpha", Rc::downgrade(&m));
let snap = snapshot();
assert_eq!(snap.len(), initial + 1);
let added = snap.iter().find(|(n, _)| n == "alpha").unwrap();
assert_eq!(added.1.len(), 3);
}
#[test]
fn dead_weaks_are_pruned() {
let snap_before = snapshot();
let initial = snap_before.len();
{
let m: Rc<dyn ModelDebug> = Rc::new(DummyModel { len: Cell::new(0) });
register("ephemeral", Rc::downgrade(&m));
}
let snap = snapshot();
assert_eq!(snap.len(), initial);
assert!(snap.iter().all(|(n, _)| n != "ephemeral"));
}
#[test]
fn register_prunes_dead_entries_without_a_snapshot_call() {
{
let m: Rc<dyn ModelDebug> = Rc::new(DummyModel { len: Cell::new(0) });
register("ephemeral", Rc::downgrade(&m));
}
let m2: Rc<dyn ModelDebug> = Rc::new(DummyModel { len: Cell::new(1) });
register("still-alive", Rc::downgrade(&m2));
let raw_len = REGISTRY.with(|cell| cell.borrow().len());
assert_eq!(raw_len, 1);
}
}