Compare commits

..

2 Commits

7 changed files with 82 additions and 69 deletions

View File

@@ -89,7 +89,7 @@ impl App {
let start = Instant::now(); let start = Instant::now();
let dt = self.last_tick.elapsed().as_secs_f32(); let dt = self.last_tick.elapsed().as_secs_f32();
self.last_tick = Instant::now(); self.last_tick = start;
let exit = self.game.tick(dt); let exit = self.game.tick(dt);

View File

@@ -8,8 +8,8 @@ use glam::UVec2;
/// ///
/// Calculated as 1/60th of a second (≈16.67ms). /// Calculated as 1/60th of a second (≈16.67ms).
/// ///
/// Written out explicitly to satisfy const-eval constraints. /// Uses integer arithmetic to avoid floating-point precision loss.
pub const LOOP_TIME: Duration = Duration::from_nanos((1_000_000_000.0 / 60.0) as u64); pub const LOOP_TIME: Duration = Duration::from_nanos(1_000_000_000 / 60);
/// The size of each cell, in pixels. /// The size of each cell, in pixels.
pub const CELL_SIZE: u32 = 8; pub const CELL_SIZE: u32 = 8;

View File

@@ -126,7 +126,7 @@ impl Game {
EventType::Display, EventType::Display,
EventType::Window, EventType::Window,
EventType::MouseWheel, EventType::MouseWheel,
EventType::MouseMotion, // EventType::MouseMotion,
EventType::MouseButtonDown, EventType::MouseButtonDown,
EventType::MouseButtonUp, EventType::MouseButtonUp,
EventType::MouseButtonDown, EventType::MouseButtonDown,

View File

@@ -12,6 +12,8 @@ use sdl2::pixels::Color;
use sdl2::rect::{Point, Rect}; use sdl2::rect::{Point, Rect};
use sdl2::render::{Canvas, Texture}; use sdl2::render::{Canvas, Texture};
use sdl2::video::Window; use sdl2::video::Window;
use smallvec::SmallVec;
use tracing::warn;
#[derive(Resource, Default, Debug, Copy, Clone)] #[derive(Resource, Default, Debug, Copy, Clone)]
pub struct DebugState { pub struct DebugState {
@@ -131,15 +133,27 @@ pub fn debug_render_system(
}; };
debug_canvas.set_draw_color(Color::GREEN); debug_canvas.set_draw_color(Color::GREEN);
for (collider, position) in colliders.iter() { {
let rects = colliders
.iter()
.map(|(collider, position)| {
let pos = position.get_pixel_position(&map.graph).unwrap(); let pos = position.get_pixel_position(&map.graph).unwrap();
// Transform position and size using common methods // Transform position and size using common methods
let pos = (pos * scale).as_ivec2(); let pos = (pos * scale).as_ivec2();
let size = (collider.size * scale) as u32; let size = (collider.size * scale) as u32;
let rect = Rect::from_center(Point::from((pos.x, pos.y)), size, size); Rect::from_center(Point::from((pos.x, pos.y)), size, size)
debug_canvas.draw_rect(rect).unwrap(); })
.collect::<SmallVec<[Rect; 100]>>();
if rects.len() > rects.capacity() {
warn!(
capacity = rects.capacity(),
count = rects.len(),
"Collider rects capacity exceeded"
);
}
debug_canvas.draw_rects(&rects).unwrap();
} }
debug_canvas.set_draw_color(Color { debug_canvas.set_draw_color(Color {
@@ -160,26 +174,38 @@ pub fn debug_render_system(
.unwrap(); .unwrap();
} }
for (id, node) in map.graph.nodes().enumerate() { {
let pos = node.position; let rects: Vec<_> = map
.graph
// Set color based on whether the node is the closest to the cursor .nodes()
debug_canvas.set_draw_color(Color { .enumerate()
a: f32_to_u8(if Some(id) == closest_node { 0.75 } else { 0.6 }), .filter_map(|(id, node)| {
..(if Some(id) == closest_node { let pos = transform_position_with_offset(node.position, scale);
Color::YELLOW
} else {
Color::BLUE
})
});
// Transform position using common method
let pos = transform_position_with_offset(pos, scale);
let size = (2.0 * scale) as u32; let size = (2.0 * scale) as u32;
let rect = Rect::new(pos.x - (size as i32 / 2), pos.y - (size as i32 / 2), size, size);
debug_canvas // If the node is the one closest to the cursor, draw it immediately
.fill_rect(Rect::new(pos.x - (size as i32 / 2), pos.y - (size as i32 / 2), size, size)) if closest_node == Some(id) {
.unwrap(); debug_canvas.set_draw_color(Color::YELLOW);
debug_canvas.fill_rect(rect).unwrap();
return None;
}
Some(rect)
})
.collect();
if rects.len() > rects.capacity() {
warn!(
capacity = rects.capacity(),
count = rects.len(),
"Node rects capacity exceeded"
);
}
// Draw the non-closest nodes all at once in blue
debug_canvas.set_draw_color(Color::BLUE);
debug_canvas.fill_rects(&rects).unwrap();
} }
// Render node ID if a node is highlighted // Render node ID if a node is highlighted

View File

@@ -138,7 +138,6 @@ pub fn input_system(
// Warn if the smallvec was heap allocated due to exceeding stack capacity // Warn if the smallvec was heap allocated due to exceeding stack capacity
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
{
if frame_events.len() > frame_events.capacity() { if frame_events.len() > frame_events.capacity() {
tracing::warn!( tracing::warn!(
"More than {} events in a frame, consider adjusting stack capacity: {:?}", "More than {} events in a frame, consider adjusting stack capacity: {:?}",
@@ -146,7 +145,6 @@ pub fn input_system(
frame_events frame_events
); );
} }
}
// Handle non-keyboard events inline and build a simplified keyboard event stream. // Handle non-keyboard events inline and build a simplified keyboard event stream.
let mut simple_key_events: SmallVec<[SimpleKeyEvent; 3]> = smallvec![]; let mut simple_key_events: SmallVec<[SimpleKeyEvent; 3]> = smallvec![];

View File

@@ -7,8 +7,8 @@ use parking_lot::{Mutex, RwLock};
use smallvec::SmallVec; use smallvec::SmallVec;
use std::fmt::Display; use std::fmt::Display;
use std::time::Duration; use std::time::Duration;
use strum::EnumCount; use strum::{EnumCount, IntoEnumIterator};
use strum_macros::{EnumCount, IntoStaticStr}; use strum_macros::{EnumCount, EnumIter, IntoStaticStr};
use thousands::Separable; use thousands::Separable;
/// The maximum number of systems that can be profiled. Must not be exceeded, or it will panic. /// The maximum number of systems that can be profiled. Must not be exceeded, or it will panic.
@@ -16,7 +16,7 @@ const MAX_SYSTEMS: usize = SystemId::COUNT;
/// The number of durations to keep in the circular buffer. /// The number of durations to keep in the circular buffer.
const TIMING_WINDOW_SIZE: usize = 30; const TIMING_WINDOW_SIZE: usize = 30;
#[derive(EnumCount, IntoStaticStr, Debug, PartialEq, Eq, Hash, Copy, Clone)] #[derive(EnumCount, EnumIter, IntoStaticStr, Debug, PartialEq, Eq, Hash, Copy, Clone)]
pub enum SystemId { pub enum SystemId {
Input, Input,
PlayerControls, PlayerControls,
@@ -96,7 +96,7 @@ impl SystemTimings {
let sum: f64 = durations.iter().sum(); let sum: f64 = durations.iter().sum();
let mean = sum / count; let mean = sum / count;
let variance = durations.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / count; let variance = durations.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / (count - 1.0).max(1.0);
let std_dev = variance.sqrt(); let std_dev = variance.sqrt();
stats.insert( stats.insert(
@@ -128,7 +128,7 @@ impl SystemTimings {
diff_secs * diff_secs diff_secs * diff_secs
}) })
.sum::<f64>() .sum::<f64>()
/ duration_sums.len() as f64; / (duration_sums.len() - 1).max(1) as f64;
let std_dev_secs = variance.sqrt(); let std_dev_secs = variance.sqrt();
(mean, Duration::from_secs_f64(std_dev_secs)) (mean, Duration::from_secs_f64(std_dev_secs))
@@ -250,11 +250,11 @@ pub fn format_timing_display(
}) })
.collect::<SmallVec<[Entry; 12]>>(); .collect::<SmallVec<[Entry; 12]>>();
let (max_name_width, max_avg_int_width, max_avg_decimal_width, max_std_int_width, max_std_decimal_width) = entries let (max_avg_int_width, max_avg_decimal_width, max_std_int_width, max_std_decimal_width) =
entries
.iter() .iter()
.fold((0, 0, 3, 0, 3), |(name_w, avg_int_w, avg_dec_w, std_int_w, std_dec_w), e| { .fold((0, 3, 0, 3), |(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_int_w.max(e.avg_int.width() as usize),
avg_dec_w.max(e.avg_decimal.width() as usize), avg_dec_w.max(e.avg_decimal.width() as usize),
std_int_w.max(e.std_int.width() as usize), std_int_w.max(e.std_int.width() as usize),
@@ -262,6 +262,11 @@ pub fn format_timing_display(
) )
}); });
let max_name_width = SystemId::iter()
.map(|id| id.to_string().len())
.max()
.expect("SystemId::iter() returned an empty iterator");
entries.iter().map(|e| { entries.iter().map(|e| {
format!( 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}", "{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}",

View File

@@ -14,6 +14,7 @@ fn test_timing_statistics() {
timings.add_timing(SystemId::Blinking, Duration::from_millis(3)); timings.add_timing(SystemId::Blinking, Duration::from_millis(3));
timings.add_timing(SystemId::Blinking, Duration::from_millis(2)); timings.add_timing(SystemId::Blinking, Duration::from_millis(2));
timings.add_timing(SystemId::Blinking, Duration::from_millis(1)); timings.add_timing(SystemId::Blinking, Duration::from_millis(1));
fn close_enough(a: Duration, b: Duration) -> bool { fn close_enough(a: Duration, b: Duration) -> bool {
if a > b { if a > b {
a - b < Duration::from_micros(500) // 0.1ms a - b < Duration::from_micros(500) // 0.1ms
@@ -36,25 +37,8 @@ fn test_timing_statistics() {
total_avg total_avg
); );
assert!( assert!(
close_enough(total_std, Duration::from_millis(12)), close_enough(total_std, Duration::from_millis(17)),
"total_std: {:?}", "total_std: {:?}",
total_std total_std
); );
} }
// #[test]
// fn test_window_size_limit() {
// let timings = SystemTimings::default();
// // Add more than 90 timings to test window size limit
// for i in 0..100 {
// timings.add_timing("test_system", Duration::from_millis(i));
// }
// let stats = timings.get_stats();
// let (avg, _) = stats.get("test_system").unwrap();
// // Should only keep the last 90 values, so average should be around 55ms
// // (average of 10-99)
// assert!((avg.as_millis() as f64 - 55.0).abs() < 5.0);
// }