mirror of
https://github.com/Xevion/Pac-Man.git
synced 2025-12-07 09:15:49 -06:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6af25af5f3 | |||
| f1935ad016 | |||
| 4d397bba5f | |||
| 80930ddd35 | |||
| 0133dd5329 | |||
| 635418a4da |
7
Cargo.lock
generated
7
Cargo.lock
generated
@@ -252,6 +252,12 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "circular-buffer"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23bdce1da528cadbac4654b5632bfcd8c6c63e25b1d42cea919a95958790b51d"
|
||||
|
||||
[[package]]
|
||||
name = "concurrent-queue"
|
||||
version = "2.5.0"
|
||||
@@ -590,6 +596,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"bevy_ecs",
|
||||
"bitflags 2.9.1",
|
||||
"circular-buffer",
|
||||
"glam 0.30.5",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
|
||||
@@ -31,6 +31,7 @@ micromap = "0.1.0"
|
||||
thousands = "0.2.0"
|
||||
pretty_assertions = "1.4.1"
|
||||
num-width = "0.1.0"
|
||||
circular-buffer = "1.1.0"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
|
||||
@@ -19,6 +19,7 @@ use crate::systems::{
|
||||
control::player_system,
|
||||
debug::{debug_render_system, DebugState, DebugTextureResource},
|
||||
input::input_system,
|
||||
item::item_system,
|
||||
movement::movement_system,
|
||||
profiling::{profile, SystemTimings},
|
||||
render::{directional_render_system, dirty_render_system, render_system, BackbufferResource, MapTextureResource},
|
||||
@@ -211,6 +212,7 @@ impl Game {
|
||||
profile("player", player_system),
|
||||
profile("movement", movement_system),
|
||||
profile("collision", collision_system),
|
||||
profile("item", item_system),
|
||||
profile("blinking", blinking_system),
|
||||
profile("directional_render", directional_render_system),
|
||||
profile("dirty_render", dirty_render_system),
|
||||
|
||||
@@ -80,9 +80,37 @@ fn render_timing_display(
|
||||
|
||||
// Split text by newlines and render each line separately
|
||||
let lines: Vec<&str> = timing_text.lines().collect();
|
||||
if lines.is_empty() {
|
||||
return;
|
||||
}
|
||||
let line_height = 14; // Approximate line height for 12pt font
|
||||
let padding = 10;
|
||||
|
||||
// Calculate background dimensions
|
||||
let max_width = lines
|
||||
.iter()
|
||||
.filter(|&&l| !l.is_empty()) // Don't consider empty lines for width
|
||||
.map(|line| font.size_of(line).unwrap().0)
|
||||
.max()
|
||||
.unwrap_or(0);
|
||||
|
||||
// Only draw background if there is text to display
|
||||
if max_width > 0 {
|
||||
let total_height = (lines.len() as u32) * line_height as u32;
|
||||
let bg_padding = 5;
|
||||
|
||||
// Draw background
|
||||
let bg_rect = Rect::new(
|
||||
padding - bg_padding,
|
||||
padding - bg_padding,
|
||||
max_width + (bg_padding * 2) as u32,
|
||||
total_height + bg_padding as u32,
|
||||
);
|
||||
canvas.set_blend_mode(sdl2::render::BlendMode::Blend);
|
||||
canvas.set_draw_color(Color::RGBA(40, 40, 40, 180));
|
||||
canvas.fill_rect(bg_rect).unwrap();
|
||||
}
|
||||
|
||||
for (i, line) in lines.iter().enumerate() {
|
||||
if line.is_empty() {
|
||||
continue;
|
||||
|
||||
@@ -1,41 +1,43 @@
|
||||
use num_width::NumberWidth;
|
||||
use std::time::Duration;
|
||||
use smallvec::SmallVec;
|
||||
use std::{iter, time::Duration};
|
||||
|
||||
// Helper to split a duration into a integer, decimal, and unit
|
||||
fn get_value(duration: &Duration) -> (u64, u32, &'static str) {
|
||||
let (int, decimal, unit) = match duration {
|
||||
// if greater than 1 second, return as seconds
|
||||
n if n >= &Duration::from_secs(1) => {
|
||||
let secs = n.as_secs();
|
||||
let decimal = n.as_millis() as u64 % 1000;
|
||||
(secs, decimal as u32, "s")
|
||||
}
|
||||
// if greater than 1 millisecond, return as milliseconds
|
||||
n if n >= &Duration::from_millis(1) => {
|
||||
let ms = n.as_millis() as u64;
|
||||
let decimal = n.as_micros() as u64 % 1000;
|
||||
(ms, decimal as u32, "ms")
|
||||
}
|
||||
// if greater than 1 microsecond, return as microseconds
|
||||
n if n >= &Duration::from_micros(1) => {
|
||||
let us = n.as_micros() as u64;
|
||||
let decimal = n.as_nanos() as u64 % 1000;
|
||||
(us, decimal as u32, "µs")
|
||||
}
|
||||
// otherwise, return as nanoseconds
|
||||
n => {
|
||||
let ns = n.as_nanos() as u64;
|
||||
(ns, 0, "ns")
|
||||
}
|
||||
};
|
||||
|
||||
(int, decimal, unit)
|
||||
}
|
||||
|
||||
/// Formats timing data into a vector of strings with proper alignment
|
||||
pub fn format_timing_display(timing_data: Vec<(String, Duration, Duration)>) -> String {
|
||||
if timing_data.is_empty() {
|
||||
return String::new();
|
||||
}
|
||||
|
||||
// Helper to split a duration into a integer, decimal, and unit
|
||||
fn get_value(duration: &Duration) -> (u64, u32, &'static str) {
|
||||
let (int, decimal, unit) = match duration {
|
||||
// if greater than 1 second, return as seconds
|
||||
n if n >= &Duration::from_secs(1) => {
|
||||
let secs = n.as_secs();
|
||||
let decimal = n.as_millis() as u64 % 1000;
|
||||
(secs, decimal as u32, "s")
|
||||
}
|
||||
// if greater than 1 millisecond, return as milliseconds
|
||||
n if n >= &Duration::from_millis(1) => {
|
||||
let ms = n.as_millis() as u64;
|
||||
let decimal = n.as_micros() as u64 % 1000;
|
||||
(ms, decimal as u32, "ms")
|
||||
}
|
||||
// if greater than 1 microsecond, return as microseconds
|
||||
n if n >= &Duration::from_micros(1) => {
|
||||
let us = n.as_micros() as u64;
|
||||
let decimal = n.as_nanos() as u64 % 1000;
|
||||
(us, decimal as u32, "µs")
|
||||
}
|
||||
// otherwise, return as nanoseconds
|
||||
n => {
|
||||
let ns = n.as_nanos() as u64;
|
||||
(ns, 0, "ns")
|
||||
}
|
||||
};
|
||||
|
||||
(int, decimal, unit)
|
||||
pub fn format_timing_display(timing_data: impl IntoIterator<Item = (String, Duration, Duration)>) -> SmallVec<[String; 12]> {
|
||||
let mut iter = timing_data.into_iter().peekable();
|
||||
if iter.peek().is_none() {
|
||||
return SmallVec::new();
|
||||
}
|
||||
|
||||
struct Entry {
|
||||
@@ -48,8 +50,7 @@ pub fn format_timing_display(timing_data: Vec<(String, Duration, Duration)>) ->
|
||||
std_unit: &'static str,
|
||||
}
|
||||
|
||||
let entries = timing_data
|
||||
.iter()
|
||||
let mut entries = iter
|
||||
.map(|(name, avg, std_dev)| {
|
||||
let (avg_int, avg_decimal, avg_unit) = get_value(&avg);
|
||||
let (std_int, std_decimal, std_unit) = get_value(&std_dev);
|
||||
@@ -64,88 +65,38 @@ pub fn format_timing_display(timing_data: Vec<(String, Duration, Duration)>) ->
|
||||
std_unit,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
.collect::<SmallVec<[Entry; 12]>>();
|
||||
|
||||
let max_name_width = entries.iter().map(|e| e.name.len() as usize).max().unwrap_or(0);
|
||||
let max_avg_int_width = entries.iter().map(|e| e.avg_int.width() as usize).max().unwrap_or(0);
|
||||
let max_avg_decimal_width = entries
|
||||
let (max_name_width, max_avg_int_width, max_avg_decimal_width, max_std_int_width, max_std_decimal_width) = entries
|
||||
.iter()
|
||||
.map(|e| e.avg_decimal.width() as usize)
|
||||
.max()
|
||||
.unwrap_or(0)
|
||||
.max(3);
|
||||
let max_std_int_width = entries.iter().map(|e| e.std_int.width() as usize).max().unwrap_or(0);
|
||||
let max_std_decimal_width = entries
|
||||
.iter()
|
||||
.map(|e| e.std_decimal.width() as usize)
|
||||
.max()
|
||||
.unwrap_or(0)
|
||||
.max(3);
|
||||
.fold((0, 0, 3, 0, 3), |(name_w, avg_int_w, avg_dec_w, std_int_w, std_dec_w), e| {
|
||||
(
|
||||
name_w.max(e.name.len()),
|
||||
avg_int_w.max(e.avg_int.width() as usize),
|
||||
avg_dec_w.max(e.avg_decimal.width() as usize),
|
||||
std_int_w.max(e.std_int.width() as usize),
|
||||
std_dec_w.max(e.std_decimal.width() as usize),
|
||||
)
|
||||
});
|
||||
|
||||
let mut output_lines = Vec::new();
|
||||
|
||||
// Format each line using the calculated max widths for alignment
|
||||
for Entry {
|
||||
name,
|
||||
avg_int,
|
||||
avg_decimal,
|
||||
avg_unit,
|
||||
std_int,
|
||||
std_decimal,
|
||||
std_unit,
|
||||
} in entries.iter()
|
||||
{
|
||||
// Add exactly 4 spaces of padding before each number
|
||||
let avg_padding = " ".repeat(4);
|
||||
let std_padding = " ".repeat(4);
|
||||
|
||||
output_lines.push(format!(
|
||||
"{name:max_name_width$} : {avg_int:max_avg_int_width$}.{avg_decimal:<max_avg_decimal_width$}{avg_unit} ± {std_int:max_std_int_width$}.{std_decimal:<max_std_decimal_width$}{std_unit}"
|
||||
));
|
||||
}
|
||||
|
||||
output_lines.join("\n")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::time::Duration;
|
||||
|
||||
#[test]
|
||||
fn test_format_timing_display() {
|
||||
let timing_data = vec![
|
||||
("total".to_string(), Duration::from_micros(1234), Duration::from_micros(570)),
|
||||
("input".to_string(), Duration::from_micros(120), Duration::from_micros(45)),
|
||||
("player".to_string(), Duration::from_micros(456), Duration::from_micros(123)),
|
||||
("movement".to_string(), Duration::from_micros(789), Duration::from_micros(234)),
|
||||
("render".to_string(), Duration::from_micros(12), Duration::from_micros(3)),
|
||||
("debug".to_string(), Duration::from_nanos(460), Duration::from_nanos(557)),
|
||||
];
|
||||
|
||||
let result = format_timing_display(timing_data);
|
||||
let lines: Vec<&str> = result.lines().collect();
|
||||
|
||||
// Verify we have the expected number of lines
|
||||
assert_eq!(lines.len(), 6);
|
||||
|
||||
let expected = r#"
|
||||
total : 1.234ms ± 570.0 µs
|
||||
input : 120.0 µs ± 45.0 µs
|
||||
player : 456.0 µs ± 123.0 µs
|
||||
movement : 789.0 µs ± 234.0 µs
|
||||
render : 12.0 µs ± 3.0 µs
|
||||
debug : 460.0 ns ± 557.0 ns
|
||||
"#
|
||||
.trim();
|
||||
|
||||
for (line, expected_line) in lines.iter().zip(expected.lines()) {
|
||||
assert_eq!(*line, expected_line);
|
||||
}
|
||||
|
||||
// Print the result for manual inspection
|
||||
println!("Formatted output:");
|
||||
println!("{}", result);
|
||||
}
|
||||
entries.iter().map(|e| {
|
||||
format!(
|
||||
"{name:max_name_width$} : {avg_int:max_avg_int_width$}.{avg_decimal:<max_avg_decimal_width$}{avg_unit} ± {std_int:max_std_int_width$}.{std_decimal:<max_std_decimal_width$}{std_unit}",
|
||||
// Content
|
||||
name = e.name,
|
||||
avg_int = e.avg_int,
|
||||
avg_decimal = e.avg_decimal,
|
||||
std_int = e.std_int,
|
||||
std_decimal = e.std_decimal,
|
||||
// Units
|
||||
avg_unit = e.avg_unit,
|
||||
std_unit = e.std_unit,
|
||||
// Padding
|
||||
max_name_width = max_name_width,
|
||||
max_avg_int_width = max_avg_int_width,
|
||||
max_avg_decimal_width = max_avg_decimal_width,
|
||||
max_std_int_width = max_std_int_width,
|
||||
max_std_decimal_width = max_std_decimal_width
|
||||
)
|
||||
}).collect::<SmallVec<[String; 12]>>()
|
||||
}
|
||||
|
||||
43
src/systems/item.rs
Normal file
43
src/systems/item.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
use bevy_ecs::{event::EventReader, prelude::*, query::With, system::Query};
|
||||
|
||||
use crate::{
|
||||
events::GameEvent,
|
||||
systems::components::{EntityType, ItemCollider, PacmanCollider, ScoreResource},
|
||||
};
|
||||
|
||||
pub fn item_system(
|
||||
mut commands: Commands,
|
||||
mut collision_events: EventReader<GameEvent>,
|
||||
mut score: ResMut<ScoreResource>,
|
||||
pacman_query: Query<Entity, With<PacmanCollider>>,
|
||||
item_query: Query<(Entity, &EntityType), With<ItemCollider>>,
|
||||
) {
|
||||
for event in collision_events.read() {
|
||||
if let GameEvent::Collision(entity1, entity2) = event {
|
||||
// Check if one is Pacman and the other is an item
|
||||
let (_pacman_entity, item_entity) = if pacman_query.get(*entity1).is_ok() && item_query.get(*entity2).is_ok() {
|
||||
(*entity1, *entity2)
|
||||
} else if pacman_query.get(*entity2).is_ok() && item_query.get(*entity1).is_ok() {
|
||||
(*entity2, *entity1)
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// Get the item type and update score
|
||||
if let Ok((item_ent, entity_type)) = item_query.get(item_entity) {
|
||||
match entity_type {
|
||||
EntityType::Pellet => {
|
||||
score.0 += 10;
|
||||
}
|
||||
EntityType::PowerPellet => {
|
||||
score.0 += 50;
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
|
||||
// Remove the collected item
|
||||
commands.entity(item_ent).despawn();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ pub mod control;
|
||||
pub mod debug;
|
||||
pub mod formatting;
|
||||
pub mod input;
|
||||
pub mod item;
|
||||
pub mod movement;
|
||||
pub mod profiling;
|
||||
pub mod render;
|
||||
|
||||
@@ -1,39 +1,61 @@
|
||||
use bevy_ecs::prelude::Resource;
|
||||
use bevy_ecs::system::{IntoSystem, System};
|
||||
use circular_buffer::CircularBuffer;
|
||||
use micromap::Map;
|
||||
use parking_lot::Mutex;
|
||||
use std::collections::VecDeque;
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use std::time::Duration;
|
||||
use thousands::Separable;
|
||||
|
||||
/// The maximum number of systems that can be profiled. Must not be exceeded, or it will panic.
|
||||
const MAX_SYSTEMS: usize = 11;
|
||||
/// The number of durations to keep in the circular buffer.
|
||||
const TIMING_WINDOW_SIZE: usize = 30;
|
||||
|
||||
#[derive(Resource, Default, Debug)]
|
||||
pub struct SystemTimings {
|
||||
pub timings: Mutex<Map<&'static str, VecDeque<Duration>, 15>>,
|
||||
/// Map of system names to a queue of durations, using a circular buffer.
|
||||
///
|
||||
/// Uses a RwLock to allow multiple readers for the HashMap, and a Mutex on the circular buffer for exclusive access.
|
||||
/// This is probably overkill, but it's fun to play with.
|
||||
///
|
||||
/// Also, we use a micromap::Map as the number of systems is generally quite small.
|
||||
/// Just make sure to set the capacity appropriately, or it will panic.
|
||||
pub timings: RwLock<Map<&'static str, Mutex<CircularBuffer<TIMING_WINDOW_SIZE, Duration>>, MAX_SYSTEMS>>,
|
||||
}
|
||||
|
||||
impl SystemTimings {
|
||||
pub fn add_timing(&self, name: &'static str, duration: Duration) {
|
||||
let mut timings = self.timings.lock();
|
||||
let queue = timings.entry(name).or_insert_with(VecDeque::new);
|
||||
// acquire a upgradable read lock
|
||||
let mut timings = self.timings.upgradable_read();
|
||||
|
||||
queue.push_back(duration);
|
||||
if queue.len() > TIMING_WINDOW_SIZE {
|
||||
queue.pop_front();
|
||||
// happy path, the name is already in the map (no need to mutate the hashmap)
|
||||
if timings.contains_key(name) {
|
||||
let queue = timings
|
||||
.get(name)
|
||||
.expect("System name not found in map after contains_key check");
|
||||
let mut queue = queue.lock();
|
||||
|
||||
queue.push_back(duration);
|
||||
return;
|
||||
}
|
||||
|
||||
// otherwise, acquire a write lock and insert a new queue
|
||||
timings.with_upgraded(|timings| {
|
||||
let queue = timings.entry(name).or_insert_with(|| Mutex::new(CircularBuffer::new()));
|
||||
queue.lock().push_back(duration);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn get_stats(&self) -> Map<&'static str, (Duration, Duration), 10> {
|
||||
let timings = self.timings.lock();
|
||||
pub fn get_stats(&self) -> Map<&'static str, (Duration, Duration), MAX_SYSTEMS> {
|
||||
let timings = self.timings.read();
|
||||
let mut stats = Map::new();
|
||||
|
||||
for (name, queue) in timings.iter() {
|
||||
if queue.is_empty() {
|
||||
if queue.lock().is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let durations: Vec<f64> = queue.iter().map(|d| d.as_secs_f64() * 1000.0).collect();
|
||||
let durations: Vec<f64> = queue.lock().iter().map(|d| d.as_secs_f64() * 1000.0).collect();
|
||||
let count = durations.len() as f64;
|
||||
|
||||
let sum: f64 = durations.iter().sum();
|
||||
@@ -55,11 +77,11 @@ impl SystemTimings {
|
||||
}
|
||||
|
||||
pub fn get_total_stats(&self) -> (Duration, Duration) {
|
||||
let timings = self.timings.lock();
|
||||
let timings = self.timings.read();
|
||||
let mut all_durations = Vec::new();
|
||||
|
||||
for queue in timings.values() {
|
||||
all_durations.extend(queue.iter().map(|d| d.as_secs_f64() * 1000.0));
|
||||
all_durations.extend(queue.lock().iter().map(|d| d.as_secs_f64() * 1000.0));
|
||||
}
|
||||
|
||||
if all_durations.is_empty() {
|
||||
@@ -104,7 +126,7 @@ impl SystemTimings {
|
||||
}
|
||||
|
||||
// Use the formatting module to format the data
|
||||
crate::systems::formatting::format_timing_display(timing_data)
|
||||
crate::systems::formatting::format_timing_display(timing_data).join("\n")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,135 +1,97 @@
|
||||
use itertools::izip;
|
||||
use pacman::systems::formatting::format_timing_display;
|
||||
use smallvec::SmallVec;
|
||||
use std::time::Duration;
|
||||
|
||||
#[test]
|
||||
fn test_basic_formatting() {
|
||||
let timing_data = vec![
|
||||
("60 FPS".to_string(), Duration::from_micros(1234), Duration::from_micros(567)),
|
||||
("input".to_string(), Duration::from_micros(123), Duration::from_micros(45)),
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
fn get_timing_data() -> Vec<(String, Duration, Duration)> {
|
||||
vec![
|
||||
("total".to_string(), Duration::from_micros(1234), Duration::from_micros(570)),
|
||||
("input".to_string(), Duration::from_micros(120), Duration::from_micros(45)),
|
||||
("player".to_string(), Duration::from_micros(456), Duration::from_micros(123)),
|
||||
("movement".to_string(), Duration::from_micros(789), Duration::from_micros(234)),
|
||||
("render".to_string(), Duration::from_micros(12), Duration::from_micros(3)),
|
||||
("debug".to_string(), Duration::from_nanos(1000000), Duration::from_nanos(1000)),
|
||||
];
|
||||
("debug".to_string(), Duration::from_nanos(460), Duration::from_nanos(557)),
|
||||
]
|
||||
}
|
||||
|
||||
let result = format_timing_display(timing_data);
|
||||
println!("Basic formatting test:");
|
||||
println!("{}", result);
|
||||
println!();
|
||||
fn get_formatted_output() -> impl IntoIterator<Item = String> {
|
||||
format_timing_display(get_timing_data())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_desired_format() {
|
||||
// This test represents the exact format you want to achieve
|
||||
let timing_data = vec![
|
||||
("total".to_string(), Duration::from_micros(1230), Duration::from_micros(570)),
|
||||
("input".to_string(), Duration::from_micros(120), Duration::from_micros(50)),
|
||||
("player".to_string(), Duration::from_micros(460), Duration::from_micros(120)),
|
||||
("movement".to_string(), Duration::from_micros(790), Duration::from_micros(230)),
|
||||
("render".to_string(), Duration::from_micros(10), Duration::from_micros(3)),
|
||||
("debug".to_string(), Duration::from_nanos(1000000), Duration::from_nanos(1000)),
|
||||
];
|
||||
fn test_formatting_alignment() {
|
||||
let mut colon_positions = vec![];
|
||||
let mut first_decimal_positions = vec![];
|
||||
let mut second_decimal_positions = vec![];
|
||||
let mut first_unit_positions = vec![];
|
||||
let mut second_unit_positions = vec![];
|
||||
|
||||
let result = format_timing_display(timing_data);
|
||||
println!("Desired format test:");
|
||||
println!("{}", result);
|
||||
println!();
|
||||
get_formatted_output().into_iter().for_each(|line| {
|
||||
let (mut got_decimal, mut got_unit) = (false, false);
|
||||
for (i, char) in line.chars().enumerate() {
|
||||
match char {
|
||||
':' => colon_positions.push(i),
|
||||
'.' => {
|
||||
if got_decimal {
|
||||
second_decimal_positions.push(i);
|
||||
} else {
|
||||
first_decimal_positions.push(i);
|
||||
}
|
||||
got_decimal = true;
|
||||
}
|
||||
's' => {
|
||||
if got_unit {
|
||||
first_unit_positions.push(i);
|
||||
} else {
|
||||
second_unit_positions.push(i);
|
||||
got_unit = true;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Expected output should look like:
|
||||
// total : 1.23 ms ± 0.57 ms
|
||||
// input : 0.12 ms ± 0.05 ms
|
||||
// player : 0.46 ms ± 0.12 ms
|
||||
// movement : 0.79 ms ± 0.23 ms
|
||||
// render : 0.01 ms ± 0.003ms
|
||||
// debug : 0.001ms ± 0.000ms
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mixed_units() {
|
||||
let timing_data = vec![
|
||||
("60 FPS".to_string(), Duration::from_millis(16), Duration::from_micros(500)),
|
||||
(
|
||||
"fast_system".to_string(),
|
||||
Duration::from_nanos(500000),
|
||||
Duration::from_nanos(100000),
|
||||
),
|
||||
(
|
||||
"medium_system".to_string(),
|
||||
Duration::from_micros(2500),
|
||||
Duration::from_micros(500),
|
||||
),
|
||||
("slow_system".to_string(), Duration::from_millis(5), Duration::from_millis(1)),
|
||||
];
|
||||
|
||||
let result = format_timing_display(timing_data);
|
||||
println!("Mixed units test:");
|
||||
println!("{}", result);
|
||||
println!();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_trailing_zeros() {
|
||||
let timing_data = vec![
|
||||
("60 FPS".to_string(), Duration::from_micros(1000), Duration::from_micros(500)),
|
||||
("exact_ms".to_string(), Duration::from_millis(1), Duration::from_micros(100)),
|
||||
("exact_us".to_string(), Duration::from_micros(1), Duration::from_nanos(100000)),
|
||||
("exact_ns".to_string(), Duration::from_nanos(1000), Duration::from_nanos(100)),
|
||||
];
|
||||
|
||||
let result = format_timing_display(timing_data);
|
||||
println!("Trailing zeros test:");
|
||||
println!("{}", result);
|
||||
println!();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edge_cases() {
|
||||
let timing_data = vec![
|
||||
("60 FPS".to_string(), Duration::from_nanos(1), Duration::from_nanos(1)),
|
||||
("very_small".to_string(), Duration::from_nanos(100), Duration::from_nanos(50)),
|
||||
("very_large".to_string(), Duration::from_secs(1), Duration::from_millis(100)),
|
||||
("zero_time".to_string(), Duration::ZERO, Duration::ZERO),
|
||||
];
|
||||
|
||||
let result = format_timing_display(timing_data);
|
||||
println!("Edge cases test:");
|
||||
println!("{}", result);
|
||||
println!();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_variable_name_lengths() {
|
||||
let timing_data = vec![
|
||||
("60 FPS".to_string(), Duration::from_micros(1234), Duration::from_micros(567)),
|
||||
("a".to_string(), Duration::from_micros(123), Duration::from_micros(45)),
|
||||
(
|
||||
"very_long_system_name".to_string(),
|
||||
Duration::from_micros(456),
|
||||
Duration::from_micros(123),
|
||||
),
|
||||
("medium".to_string(), Duration::from_micros(789), Duration::from_micros(234)),
|
||||
];
|
||||
|
||||
let result = format_timing_display(timing_data);
|
||||
println!("Variable name lengths test:");
|
||||
println!("{}", result);
|
||||
println!();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_input() {
|
||||
let timing_data = vec![];
|
||||
let result = format_timing_display(timing_data);
|
||||
assert_eq!(result, "");
|
||||
println!("Empty input test: PASS");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_single_entry() {
|
||||
let timing_data = vec![("60 FPS".to_string(), Duration::from_micros(1234), Duration::from_micros(567))];
|
||||
|
||||
let result = format_timing_display(timing_data);
|
||||
println!("Single entry test:");
|
||||
println!("{}", result);
|
||||
println!();
|
||||
// Assert that all positions were found
|
||||
assert_eq!(
|
||||
vec![
|
||||
&colon_positions,
|
||||
&first_decimal_positions,
|
||||
&second_decimal_positions,
|
||||
&first_unit_positions,
|
||||
&second_unit_positions
|
||||
]
|
||||
.iter()
|
||||
.all(|p| p.len() == 6),
|
||||
true
|
||||
);
|
||||
|
||||
// Assert that all positions are the same
|
||||
assert!(
|
||||
colon_positions.iter().all(|&p| p == colon_positions[0]),
|
||||
"colon positions are not the same {:?}",
|
||||
colon_positions
|
||||
);
|
||||
assert!(
|
||||
first_decimal_positions.iter().all(|&p| p == first_decimal_positions[0]),
|
||||
"first decimal positions are not the same {:?}",
|
||||
first_decimal_positions
|
||||
);
|
||||
assert!(
|
||||
second_decimal_positions.iter().all(|&p| p == second_decimal_positions[0]),
|
||||
"second decimal positions are not the same {:?}",
|
||||
second_decimal_positions
|
||||
);
|
||||
assert!(
|
||||
first_unit_positions.iter().all(|&p| p == first_unit_positions[0]),
|
||||
"first unit positions are not the same {:?}",
|
||||
first_unit_positions
|
||||
);
|
||||
assert!(
|
||||
second_unit_positions.iter().all(|&p| p == second_unit_positions[0]),
|
||||
"second unit positions are not the same {:?}",
|
||||
second_unit_positions
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user