#[allow(dead_code)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum MemoryCategory {
Meshes,
Textures,
Physics,
Audio,
Other,
}
#[allow(dead_code)]
#[derive(Clone, Debug)]
pub struct AllocationRecord {
pub label: String,
pub size: u64,
pub category: MemoryCategory,
pub is_alloc: bool,
}
#[allow(dead_code)]
#[derive(Clone, Debug)]
pub struct MemoryTracker {
current: u64,
peak: u64,
by_category: Vec<(MemoryCategory, u64)>,
budget: u64,
alloc_count: u64,
free_count: u64,
}
#[allow(dead_code)]
fn cat_index(tracker: &mut MemoryTracker, cat: &MemoryCategory) -> usize {
if let Some(idx) = tracker.by_category.iter().position(|(c, _)| c == cat) {
idx
} else {
tracker.by_category.push((cat.clone(), 0));
tracker.by_category.len() - 1
}
}
#[allow(dead_code)]
fn cat_usage(tracker: &MemoryTracker, cat: &MemoryCategory) -> u64 {
tracker
.by_category
.iter()
.find(|(c, _)| c == cat)
.map_or(0, |(_, v)| *v)
}
#[allow(dead_code)]
pub fn new_memory_tracker() -> MemoryTracker {
MemoryTracker {
current: 0,
peak: 0,
by_category: Vec::new(),
budget: 0,
alloc_count: 0,
free_count: 0,
}
}
#[allow(dead_code)]
pub fn track_alloc(tracker: &mut MemoryTracker, size: u64, category: MemoryCategory) {
tracker.current += size;
tracker.alloc_count += 1;
if tracker.current > tracker.peak {
tracker.peak = tracker.current;
}
let idx = cat_index(tracker, &category);
tracker.by_category[idx].1 += size;
}
#[allow(dead_code)]
pub fn track_free(tracker: &mut MemoryTracker, size: u64, category: MemoryCategory) {
tracker.current = tracker.current.saturating_sub(size);
tracker.free_count += 1;
let idx = cat_index(tracker, &category);
tracker.by_category[idx].1 = tracker.by_category[idx].1.saturating_sub(size);
}
#[allow(dead_code)]
pub fn current_usage(tracker: &MemoryTracker) -> u64 {
tracker.current
}
#[allow(dead_code)]
pub fn usage_by_category(tracker: &MemoryTracker, category: &MemoryCategory) -> u64 {
cat_usage(tracker, category)
}
#[allow(dead_code)]
pub fn peak_usage(tracker: &MemoryTracker) -> u64 {
tracker.peak
}
#[allow(dead_code)]
pub fn budget_remaining(tracker: &MemoryTracker) -> u64 {
if tracker.budget == 0 {
return 0;
}
tracker.budget.saturating_sub(tracker.current)
}
#[allow(dead_code)]
pub fn set_budget(tracker: &mut MemoryTracker, budget: u64) {
tracker.budget = budget;
}
#[allow(dead_code)]
pub fn over_budget(tracker: &MemoryTracker) -> bool {
tracker.budget > 0 && tracker.current > tracker.budget
}
#[allow(dead_code)]
pub fn allocation_count(tracker: &MemoryTracker) -> u64 {
tracker.alloc_count
}
#[allow(dead_code)]
pub fn free_count(tracker: &MemoryTracker) -> u64 {
tracker.free_count
}
#[allow(dead_code)]
pub fn largest_category(tracker: &MemoryTracker) -> Option<MemoryCategory> {
tracker
.by_category
.iter()
.max_by_key(|(_, v)| *v)
.map(|(c, _)| c.clone())
}
#[allow(dead_code)]
pub fn reset_tracker(tracker: &mut MemoryTracker) {
tracker.current = 0;
tracker.peak = 0;
tracker.by_category.clear();
tracker.budget = 0;
tracker.alloc_count = 0;
tracker.free_count = 0;
}
#[allow(dead_code)]
pub fn memory_tracker_to_json(tracker: &MemoryTracker) -> String {
let mut s = String::from("{");
s.push_str(&format!("\"current\":{}", tracker.current));
s.push_str(&format!(",\"peak\":{}", tracker.peak));
s.push_str(&format!(",\"budget\":{}", tracker.budget));
s.push_str(&format!(",\"alloc_count\":{}", tracker.alloc_count));
s.push_str(&format!(",\"free_count\":{}", tracker.free_count));
s.push_str(",\"categories\":{");
for (i, (cat, val)) in tracker.by_category.iter().enumerate() {
if i > 0 {
s.push(',');
}
s.push_str(&format!("\"{:?}\":{}", cat, val));
}
s.push_str("}}");
s
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_memory_tracker() {
let t = new_memory_tracker();
assert_eq!(current_usage(&t), 0);
assert_eq!(peak_usage(&t), 0);
}
#[test]
fn test_track_alloc() {
let mut t = new_memory_tracker();
track_alloc(&mut t, 1024, MemoryCategory::Meshes);
assert_eq!(current_usage(&t), 1024);
assert_eq!(allocation_count(&t), 1);
}
#[test]
fn test_track_free() {
let mut t = new_memory_tracker();
track_alloc(&mut t, 1000, MemoryCategory::Textures);
track_free(&mut t, 400, MemoryCategory::Textures);
assert_eq!(current_usage(&t), 600);
assert_eq!(free_count(&t), 1);
}
#[test]
fn test_track_free_saturates() {
let mut t = new_memory_tracker();
track_alloc(&mut t, 100, MemoryCategory::Audio);
track_free(&mut t, 999, MemoryCategory::Audio);
assert_eq!(current_usage(&t), 0);
}
#[test]
fn test_usage_by_category() {
let mut t = new_memory_tracker();
track_alloc(&mut t, 500, MemoryCategory::Meshes);
track_alloc(&mut t, 300, MemoryCategory::Textures);
assert_eq!(usage_by_category(&t, &MemoryCategory::Meshes), 500);
assert_eq!(usage_by_category(&t, &MemoryCategory::Textures), 300);
assert_eq!(usage_by_category(&t, &MemoryCategory::Physics), 0);
}
#[test]
fn test_peak_usage() {
let mut t = new_memory_tracker();
track_alloc(&mut t, 1000, MemoryCategory::Meshes);
track_alloc(&mut t, 500, MemoryCategory::Textures);
track_free(&mut t, 1000, MemoryCategory::Meshes);
assert_eq!(peak_usage(&t), 1500);
assert_eq!(current_usage(&t), 500);
}
#[test]
fn test_set_budget_and_remaining() {
let mut t = new_memory_tracker();
set_budget(&mut t, 2000);
track_alloc(&mut t, 800, MemoryCategory::Audio);
assert_eq!(budget_remaining(&t), 1200);
}
#[test]
fn test_budget_remaining_no_budget() {
let t = new_memory_tracker();
assert_eq!(budget_remaining(&t), 0);
}
#[test]
fn test_over_budget() {
let mut t = new_memory_tracker();
set_budget(&mut t, 100);
track_alloc(&mut t, 200, MemoryCategory::Physics);
assert!(over_budget(&t));
}
#[test]
fn test_not_over_budget() {
let mut t = new_memory_tracker();
set_budget(&mut t, 1000);
track_alloc(&mut t, 500, MemoryCategory::Meshes);
assert!(!over_budget(&t));
}
#[test]
fn test_allocation_and_free_counts() {
let mut t = new_memory_tracker();
track_alloc(&mut t, 100, MemoryCategory::Meshes);
track_alloc(&mut t, 200, MemoryCategory::Textures);
track_free(&mut t, 50, MemoryCategory::Meshes);
assert_eq!(allocation_count(&t), 2);
assert_eq!(free_count(&t), 1);
}
#[test]
fn test_largest_category() {
let mut t = new_memory_tracker();
track_alloc(&mut t, 100, MemoryCategory::Meshes);
track_alloc(&mut t, 500, MemoryCategory::Textures);
track_alloc(&mut t, 200, MemoryCategory::Audio);
assert_eq!(largest_category(&t), Some(MemoryCategory::Textures));
}
#[test]
fn test_largest_category_empty() {
let t = new_memory_tracker();
assert!(largest_category(&t).is_none());
}
#[test]
fn test_reset_tracker() {
let mut t = new_memory_tracker();
track_alloc(&mut t, 1000, MemoryCategory::Meshes);
set_budget(&mut t, 5000);
reset_tracker(&mut t);
assert_eq!(current_usage(&t), 0);
assert_eq!(peak_usage(&t), 0);
assert_eq!(allocation_count(&t), 0);
assert_eq!(free_count(&t), 0);
}
#[test]
fn test_memory_tracker_to_json() {
let mut t = new_memory_tracker();
track_alloc(&mut t, 256, MemoryCategory::Meshes);
let json = memory_tracker_to_json(&t);
assert!(json.contains("\"current\":256"));
assert!(json.contains("\"peak\":256"));
assert!(json.contains("\"alloc_count\":1"));
}
#[test]
fn test_multiple_categories_independent() {
let mut t = new_memory_tracker();
track_alloc(&mut t, 100, MemoryCategory::Meshes);
track_alloc(&mut t, 200, MemoryCategory::Audio);
track_free(&mut t, 100, MemoryCategory::Meshes);
assert_eq!(usage_by_category(&t, &MemoryCategory::Meshes), 0);
assert_eq!(usage_by_category(&t, &MemoryCategory::Audio), 200);
assert_eq!(current_usage(&t), 200);
}
}