use std::cell::RefCell;
pub struct TagStack {
stack: Vec<&'static str>,
}
impl TagStack {
pub fn new() -> Self {
Self {
stack: Vec::with_capacity(8),
}
}
pub fn push(&mut self, tag: &'static str) {
self.stack.push(tag);
}
pub fn pop(&mut self) -> Option<&'static str> {
self.stack.pop()
}
pub fn current(&self) -> Option<&'static str> {
self.stack.last().copied()
}
pub fn is_active(&self, tag: &'static str) -> bool {
self.stack.iter().any(|&t| t == tag)
}
pub fn path(&self) -> String {
self.stack.join("::")
}
pub fn clear(&mut self) {
self.stack.clear();
}
pub fn depth(&self) -> usize {
self.stack.len()
}
}
impl Default for TagStack {
fn default() -> Self {
Self::new()
}
}
thread_local! {
static TAG_STACK: RefCell<TagStack> = RefCell::new(TagStack::new());
}
pub fn push_tag(tag: &'static str) {
TAG_STACK.with(|s| s.borrow_mut().push(tag));
}
pub fn pop_tag() -> Option<&'static str> {
TAG_STACK.with(|s| s.borrow_mut().pop())
}
pub fn current_tag() -> Option<&'static str> {
TAG_STACK.with(|s| s.borrow().current())
}
pub fn tag_path() -> String {
TAG_STACK.with(|s| s.borrow().path())
}
pub fn clear_tags() {
TAG_STACK.with(|s| s.borrow_mut().clear());
}
pub struct TagGuard {
tag: &'static str,
}
impl TagGuard {
pub fn new(tag: &'static str) -> Self {
push_tag(tag);
Self { tag }
}
pub fn tag(&self) -> &'static str {
self.tag
}
}
impl Drop for TagGuard {
fn drop(&mut self) {
pop_tag();
}
}
pub fn with_tag<F, R>(tag: &'static str, f: F) -> R
where
F: FnOnce() -> R,
{
let _guard = TagGuard::new(tag);
f()
}
#[derive(Debug, Clone, Default)]
pub struct TaggedStats {
pub tag: &'static str,
pub bytes_allocated: usize,
pub allocation_count: usize,
pub current_bytes: usize,
}
impl TaggedStats {
pub fn new(tag: &'static str) -> Self {
Self {
tag,
bytes_allocated: 0,
allocation_count: 0,
current_bytes: 0,
}
}
pub fn record_alloc(&mut self, size: usize) {
self.bytes_allocated += size;
self.allocation_count += 1;
self.current_bytes += size;
}
pub fn record_dealloc(&mut self, size: usize) {
self.current_bytes = self.current_bytes.saturating_sub(size);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tag_stack() {
clear_tags();
push_tag("rendering");
assert_eq!(current_tag(), Some("rendering"));
push_tag("shadows");
assert_eq!(current_tag(), Some("shadows"));
assert_eq!(tag_path(), "rendering::shadows");
pop_tag();
assert_eq!(current_tag(), Some("rendering"));
pop_tag();
assert_eq!(current_tag(), None);
}
#[test]
fn test_tag_guard() {
clear_tags();
{
let _guard = TagGuard::new("physics");
assert_eq!(current_tag(), Some("physics"));
{
let _inner = TagGuard::new("collision");
assert_eq!(current_tag(), Some("collision"));
}
assert_eq!(current_tag(), Some("physics"));
}
assert_eq!(current_tag(), None);
}
#[test]
fn test_with_tag() {
clear_tags();
let result = with_tag("ai", || {
assert_eq!(current_tag(), Some("ai"));
42
});
assert_eq!(result, 42);
assert_eq!(current_tag(), None);
}
}