use std::cell::RefCell;
use std::thread::LocalKey;
use crate::util::finalizer::Finalizer;
pub trait Events {
fn new() -> Self;
fn take(&mut self) -> Self;
}
pub type Stack<E> = RefCell<Vec<E>>;
pub fn new_stack<E: Events>() -> Stack<E> {
RefCell::new(Vec::new())
}
pub type Local<E> = LocalKey<Stack<E>>;
pub fn collect<E: Events, R>(local: &'static Local<E>, f: impl FnOnce() -> R) -> (R, E) {
local.with(move |cell| {
{
let events = E::new();
let mut events_stack = cell.borrow_mut();
events_stack.push(events);
}
let finalizer = Finalizer::new(|| {
local.with(move |cell| {
let mut events_stack = cell.borrow_mut();
events_stack.pop();
})
});
let result = f();
let events = {
let mut events_stack = cell.borrow_mut();
let events = events_stack.last_mut().unwrap();
events.take()
};
drop(finalizer);
(result, events)
})
}
fn enabled_with_cell<E: Events>(cell: &RefCell<Vec<E>>) -> bool {
!cell.borrow().is_empty()
}
pub fn enabled<E: Events>(local: &'static Local<E>) -> bool {
local.with(move |cell| enabled_with_cell(cell))
}
pub fn modify<E: Events>(local: &'static Local<E>, f: impl FnOnce(&mut Vec<E>)) {
local.with(move |cell| {
if enabled_with_cell(cell) {
let mut events_stack = cell.borrow_mut();
f(&mut events_stack);
}
});
}
#[cfg(test)]
mod tests {
use std::panic::catch_unwind;
use crate::util::events::{self, Events, Stack};
struct Counter(u32);
impl Events for Counter {
fn new() -> Self {
Counter(0)
}
fn take(&mut self) -> Self {
Counter(self.0)
}
}
thread_local! {
static LOCAL: Stack<Counter> = events::new_stack();
}
fn enabled() -> bool {
events::enabled(&LOCAL)
}
fn collect(f: impl FnOnce()) -> u32 {
let ((), Counter(n)) = events::collect(&LOCAL, f);
n
}
fn inc() {
events::modify(&LOCAL, |stack| stack.iter_mut().for_each(|c| c.0 += 1));
}
#[test]
fn collect_and_modify() {
let n = collect(|| {
inc();
inc();
let m = collect(inc);
assert_eq!(1, m);
});
assert_eq!(3, n);
}
#[test]
fn is_only_enabled_during_collection() {
assert!(!enabled());
collect(|| {
assert!(enabled());
});
assert!(!enabled());
}
#[test]
fn logger_is_not_enabled_after_panic() {
let _ = catch_unwind(|| {
collect(|| {
panic!();
})
});
assert!(!enabled());
}
}