use std::cell::RefCell;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Phase {
pub name: &'static str,
pub bytes_allocated: usize,
pub allocation_count: usize,
#[cfg(feature = "diagnostics")]
pub start_time: Option<std::time::Instant>,
}
impl Phase {
pub fn new(name: &'static str) -> Self {
Self {
name,
bytes_allocated: 0,
allocation_count: 0,
#[cfg(feature = "diagnostics")]
start_time: Some(std::time::Instant::now()),
}
}
pub fn record_alloc(&mut self, size: usize) {
self.bytes_allocated += size;
self.allocation_count += 1;
}
#[cfg(feature = "diagnostics")]
pub fn duration(&self) -> Option<std::time::Duration> {
self.start_time.map(|t| t.elapsed())
}
}
pub struct PhaseTracker {
stack: Vec<Phase>,
completed: Vec<Phase>,
}
impl PhaseTracker {
pub fn new() -> Self {
Self {
stack: Vec::with_capacity(8),
completed: Vec::with_capacity(16),
}
}
pub fn begin_phase(&mut self, name: &'static str) {
self.stack.push(Phase::new(name));
}
pub fn end_phase(&mut self) -> Option<Phase> {
if let Some(phase) = self.stack.pop() {
self.completed.push(phase.clone());
Some(phase)
} else {
None
}
}
pub fn current_phase(&self) -> Option<&'static str> {
self.stack.last().map(|p| p.name)
}
pub fn record_alloc(&mut self, size: usize) {
if let Some(phase) = self.stack.last_mut() {
phase.record_alloc(size);
}
}
pub fn completed_phases(&self) -> &[Phase] {
&self.completed
}
pub fn reset(&mut self) {
self.stack.clear();
self.completed.clear();
}
pub fn is_in_phase(&self) -> bool {
!self.stack.is_empty()
}
pub fn depth(&self) -> usize {
self.stack.len()
}
}
impl Default for PhaseTracker {
fn default() -> Self {
Self::new()
}
}
thread_local! {
static PHASE_TRACKER: RefCell<PhaseTracker> = RefCell::new(PhaseTracker::new());
}
pub fn begin_phase(name: &'static str) {
PHASE_TRACKER.with(|t| t.borrow_mut().begin_phase(name));
}
pub fn end_phase() -> Option<Phase> {
PHASE_TRACKER.with(|t| t.borrow_mut().end_phase())
}
pub fn current_phase() -> Option<&'static str> {
PHASE_TRACKER.with(|t| t.borrow().current_phase())
}
pub fn record_phase_alloc(size: usize) {
PHASE_TRACKER.with(|t| t.borrow_mut().record_alloc(size));
}
pub fn reset_phases() {
PHASE_TRACKER.with(|t| t.borrow_mut().reset());
}
pub fn is_in_phase() -> bool {
PHASE_TRACKER.with(|t| t.borrow().is_in_phase())
}
pub struct PhaseGuard {
_private: (),
}
impl PhaseGuard {
pub fn new(name: &'static str) -> Self {
begin_phase(name);
Self { _private: () }
}
}
impl Drop for PhaseGuard {
fn drop(&mut self) {
end_phase();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_phase_tracking() {
reset_phases();
begin_phase("physics");
assert_eq!(current_phase(), Some("physics"));
record_phase_alloc(1024);
record_phase_alloc(512);
let phase = end_phase().unwrap();
assert_eq!(phase.name, "physics");
assert_eq!(phase.bytes_allocated, 1536);
assert_eq!(phase.allocation_count, 2);
}
#[test]
fn test_nested_phases() {
reset_phases();
begin_phase("update");
begin_phase("physics");
assert_eq!(current_phase(), Some("physics"));
end_phase();
assert_eq!(current_phase(), Some("update"));
end_phase();
assert_eq!(current_phase(), None);
}
#[test]
fn test_phase_guard() {
reset_phases();
{
let _guard = PhaseGuard::new("render");
assert_eq!(current_phase(), Some("render"));
}
assert_eq!(current_phase(), None);
}
}