mirror of
https://github.com/Xevion/Pac-Man.git
synced 2025-12-07 20:07:46 -06:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8ce2af89c8 | |||
| 5f0ee87dd9 | |||
| b88895e82f | |||
| 2f0c734d13 |
14
Cargo.lock
generated
14
Cargo.lock
generated
@@ -528,6 +528,12 @@ version = "2.7.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
|
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "micromap"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "18c087666f377f857b49564f8791b481260c67825d6b337e1e38ddf54a985a88"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nonmax"
|
name = "nonmax"
|
||||||
version = "0.5.5"
|
version = "0.5.5"
|
||||||
@@ -575,6 +581,7 @@ dependencies = [
|
|||||||
"glam 0.30.5",
|
"glam 0.30.5",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"libc",
|
"libc",
|
||||||
|
"micromap",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"pathfinding",
|
"pathfinding",
|
||||||
@@ -588,6 +595,7 @@ dependencies = [
|
|||||||
"strum",
|
"strum",
|
||||||
"strum_macros",
|
"strum_macros",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
"thousands",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-error",
|
"tracing-error",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
@@ -992,6 +1000,12 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thousands"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thread_local"
|
name = "thread_local"
|
||||||
version = "1.1.7"
|
version = "1.1.7"
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ phf = { version = "0.12.1", features = ["macros"] }
|
|||||||
bevy_ecs = "0.16.1"
|
bevy_ecs = "0.16.1"
|
||||||
bitflags = "2.9.1"
|
bitflags = "2.9.1"
|
||||||
parking_lot = "0.12.3"
|
parking_lot = "0.12.3"
|
||||||
|
micromap = "0.1.0"
|
||||||
|
thousands = "0.2.0"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
|
|||||||
30
src/app.rs
30
src/app.rs
@@ -5,7 +5,8 @@ use sdl2::render::TextureCreator;
|
|||||||
use sdl2::ttf::Sdl2TtfContext;
|
use sdl2::ttf::Sdl2TtfContext;
|
||||||
use sdl2::video::WindowContext;
|
use sdl2::video::WindowContext;
|
||||||
use sdl2::{AudioSubsystem, EventPump, Sdl, VideoSubsystem};
|
use sdl2::{AudioSubsystem, EventPump, Sdl, VideoSubsystem};
|
||||||
use tracing::{field, info, warn};
|
use thousands::Separable;
|
||||||
|
use tracing::info;
|
||||||
|
|
||||||
use crate::error::{GameError, GameResult};
|
use crate::error::{GameError, GameResult};
|
||||||
|
|
||||||
@@ -16,6 +17,7 @@ use crate::systems::profiling::SystemTimings;
|
|||||||
|
|
||||||
pub struct App {
|
pub struct App {
|
||||||
pub game: Game,
|
pub game: Game,
|
||||||
|
last_timings: Instant,
|
||||||
last_tick: Instant,
|
last_tick: Instant,
|
||||||
focused: bool,
|
focused: bool,
|
||||||
_cursor_pos: Vec2,
|
_cursor_pos: Vec2,
|
||||||
@@ -68,6 +70,7 @@ impl App {
|
|||||||
game,
|
game,
|
||||||
focused: true,
|
focused: true,
|
||||||
last_tick: Instant::now(),
|
last_tick: Instant::now(),
|
||||||
|
last_timings: Instant::now() - Duration::from_secs_f32(0.5),
|
||||||
_cursor_pos: Vec2::ZERO,
|
_cursor_pos: Vec2::ZERO,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -110,15 +113,26 @@ impl App {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show timings if the loop took more than 25% of the loop time
|
if self.last_timings.elapsed() > Duration::from_secs(1) {
|
||||||
let show_timings = start.elapsed() > (LOOP_TIME / 4);
|
// Show timing statistics over the last 90 frames
|
||||||
if show_timings || true {
|
|
||||||
if let Some(timings) = self.game.world.get_resource::<SystemTimings>() {
|
if let Some(timings) = self.game.world.get_resource::<SystemTimings>() {
|
||||||
let mut timings = timings.timings.lock();
|
let stats = timings.get_stats();
|
||||||
let total = timings.values().sum::<Duration>();
|
let (total_avg, total_std) = timings.get_total_stats();
|
||||||
info!("Total: {:?}, Timings: {:?}", total, field::debug(&timings));
|
|
||||||
timings.clear();
|
let mut individual_timings = String::new();
|
||||||
|
for (name, (avg, std_dev)) in stats.iter() {
|
||||||
|
individual_timings.push_str(&format!("{}={:?} ± {:?} ", name, avg, std_dev));
|
||||||
|
}
|
||||||
|
|
||||||
|
let effective_fps = match 1.0 / total_avg.as_secs_f64() {
|
||||||
|
f if f > 100.0 => (f as u32).separate_with_commas(),
|
||||||
|
f if f < 10.0 => format!("{:.1} FPS", f),
|
||||||
|
f => format!("{:.0} FPS", f),
|
||||||
|
};
|
||||||
|
|
||||||
|
info!("({effective_fps}) {total_avg:?} ± {total_std:?} ({individual_timings})");
|
||||||
}
|
}
|
||||||
|
self.last_timings = Instant::now();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sleep if we still have time left
|
// Sleep if we still have time left
|
||||||
|
|||||||
@@ -14,13 +14,13 @@ use crate::systems::{
|
|||||||
collision::collision_system,
|
collision::collision_system,
|
||||||
components::{
|
components::{
|
||||||
Collider, CollisionLayer, DeltaTime, DirectionalAnimated, EntityType, GlobalState, ItemBundle, ItemCollider,
|
Collider, CollisionLayer, DeltaTime, DirectionalAnimated, EntityType, GlobalState, ItemBundle, ItemCollider,
|
||||||
PacmanCollider, PlayerBundle, PlayerControlled, Renderable, Score, ScoreResource,
|
PacmanCollider, PlayerBundle, PlayerControlled, RenderDirty, Renderable, Score, ScoreResource,
|
||||||
},
|
},
|
||||||
control::player_system,
|
control::player_system,
|
||||||
input::input_system,
|
input::input_system,
|
||||||
movement::movement_system,
|
movement::movement_system,
|
||||||
profiling::{profile, SystemTimings},
|
profiling::{profile, SystemTimings},
|
||||||
render::{directional_render_system, render_system, BackbufferResource, MapTextureResource},
|
render::{directional_render_system, dirty_render_system, render_system, BackbufferResource, MapTextureResource},
|
||||||
};
|
};
|
||||||
use crate::texture::animated::AnimatedTexture;
|
use crate::texture::animated::AnimatedTexture;
|
||||||
use bevy_ecs::schedule::IntoScheduleConfigs;
|
use bevy_ecs::schedule::IntoScheduleConfigs;
|
||||||
@@ -100,7 +100,7 @@ impl Game {
|
|||||||
// Render map to texture
|
// Render map to texture
|
||||||
canvas
|
canvas
|
||||||
.with_texture_canvas(&mut map_texture, |map_canvas| {
|
.with_texture_canvas(&mut map_texture, |map_canvas| {
|
||||||
MapRenderer::render_map(map_canvas, &mut atlas, &mut map_tiles);
|
MapRenderer::render_map(map_canvas, &mut atlas, &map_tiles);
|
||||||
})
|
})
|
||||||
.map_err(|e| GameError::Sdl(e.to_string()))?;
|
.map_err(|e| GameError::Sdl(e.to_string()))?;
|
||||||
|
|
||||||
@@ -175,6 +175,7 @@ impl Game {
|
|||||||
world.insert_resource(SystemTimings::default());
|
world.insert_resource(SystemTimings::default());
|
||||||
world.insert_resource(Bindings::default());
|
world.insert_resource(Bindings::default());
|
||||||
world.insert_resource(DeltaTime(0f32));
|
world.insert_resource(DeltaTime(0f32));
|
||||||
|
world.insert_resource(RenderDirty::default());
|
||||||
|
|
||||||
world.add_observer(
|
world.add_observer(
|
||||||
|event: Trigger<GameEvent>, mut state: ResMut<GlobalState>, _score: ResMut<ScoreResource>| match *event {
|
|event: Trigger<GameEvent>, mut state: ResMut<GlobalState>, _score: ResMut<ScoreResource>| match *event {
|
||||||
@@ -196,10 +197,10 @@ impl Game {
|
|||||||
profile("collision", collision_system),
|
profile("collision", collision_system),
|
||||||
profile("blinking", blinking_system),
|
profile("blinking", blinking_system),
|
||||||
profile("directional_render", directional_render_system),
|
profile("directional_render", directional_render_system),
|
||||||
profile("render", render_system),
|
|
||||||
)
|
)
|
||||||
.chain(),
|
.chain(),
|
||||||
);
|
);
|
||||||
|
schedule.add_systems((profile("dirty_render", dirty_render_system), profile("render", render_system)).chain());
|
||||||
|
|
||||||
// Spawn player
|
// Spawn player
|
||||||
world.spawn(player);
|
world.spawn(player);
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ impl MapRenderer {
|
|||||||
///
|
///
|
||||||
/// This function draws the static map texture to the screen at the correct
|
/// This function draws the static map texture to the screen at the correct
|
||||||
/// position and scale.
|
/// position and scale.
|
||||||
pub fn render_map<T: RenderTarget>(canvas: &mut Canvas<T>, atlas: &mut SpriteAtlas, map_tiles: &mut [AtlasTile]) {
|
pub fn render_map<T: RenderTarget>(canvas: &mut Canvas<T>, atlas: &mut SpriteAtlas, map_tiles: &[AtlasTile]) {
|
||||||
for (y, row) in TILE_MAP.iter().enumerate() {
|
for (y, row) in TILE_MAP.iter().enumerate() {
|
||||||
for (x, &tile_index) in row.iter().enumerate() {
|
for (x, &tile_index) in row.iter().enumerate() {
|
||||||
let mut tile = map_tiles[tile_index];
|
let mut tile = map_tiles[tile_index];
|
||||||
|
|||||||
@@ -109,3 +109,6 @@ pub struct ScoreResource(pub u32);
|
|||||||
|
|
||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
pub struct DeltaTime(pub f32);
|
pub struct DeltaTime(pub f32);
|
||||||
|
|
||||||
|
#[derive(Resource, Default)]
|
||||||
|
pub struct RenderDirty(pub bool);
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ pub fn input_system(bindings: Res<Bindings>, mut writer: EventWriter<GameEvent>,
|
|||||||
Event::KeyDown { keycode: Some(key), .. } => {
|
Event::KeyDown { keycode: Some(key), .. } => {
|
||||||
let command = bindings.key_bindings.get(&key).copied();
|
let command = bindings.key_bindings.get(&key).copied();
|
||||||
if let Some(command) = command {
|
if let Some(command) = command {
|
||||||
tracing::info!("triggering command: {:?}", command);
|
|
||||||
writer.write(GameEvent::Command(command));
|
writer.write(GameEvent::Command(command));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,82 @@
|
|||||||
use bevy_ecs::prelude::Resource;
|
use bevy_ecs::prelude::Resource;
|
||||||
use bevy_ecs::system::{IntoSystem, System};
|
use bevy_ecs::system::{IntoSystem, System};
|
||||||
|
use micromap::Map;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use std::collections::HashMap;
|
use std::collections::VecDeque;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
const TIMING_WINDOW_SIZE: usize = 90; // 1.5 seconds at 60 FPS
|
||||||
|
|
||||||
#[derive(Resource, Default, Debug)]
|
#[derive(Resource, Default, Debug)]
|
||||||
pub struct SystemTimings {
|
pub struct SystemTimings {
|
||||||
pub timings: Mutex<HashMap<&'static str, Duration>>,
|
pub timings: Mutex<Map<&'static str, VecDeque<Duration>, 15>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
queue.push_back(duration);
|
||||||
|
if queue.len() > TIMING_WINDOW_SIZE {
|
||||||
|
queue.pop_front();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_stats(&self) -> Map<&'static str, (Duration, Duration), 10> {
|
||||||
|
let timings = self.timings.lock();
|
||||||
|
let mut stats = Map::new();
|
||||||
|
|
||||||
|
for (name, queue) in timings.iter() {
|
||||||
|
if queue.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let durations: Vec<f64> = queue.iter().map(|d| d.as_secs_f64() * 1000.0).collect();
|
||||||
|
let count = durations.len() as f64;
|
||||||
|
|
||||||
|
let sum: f64 = durations.iter().sum();
|
||||||
|
let mean = sum / count;
|
||||||
|
|
||||||
|
let variance = durations.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / count;
|
||||||
|
let std_dev = variance.sqrt();
|
||||||
|
|
||||||
|
stats.insert(
|
||||||
|
*name,
|
||||||
|
(
|
||||||
|
Duration::from_secs_f64(mean / 1000.0),
|
||||||
|
Duration::from_secs_f64(std_dev / 1000.0),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
stats
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_total_stats(&self) -> (Duration, Duration) {
|
||||||
|
let timings = self.timings.lock();
|
||||||
|
let mut all_durations = Vec::new();
|
||||||
|
|
||||||
|
for queue in timings.values() {
|
||||||
|
all_durations.extend(queue.iter().map(|d| d.as_secs_f64() * 1000.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
if all_durations.is_empty() {
|
||||||
|
return (Duration::ZERO, Duration::ZERO);
|
||||||
|
}
|
||||||
|
|
||||||
|
let count = all_durations.len() as f64;
|
||||||
|
let sum: f64 = all_durations.iter().sum();
|
||||||
|
let mean = sum / count;
|
||||||
|
|
||||||
|
let variance = all_durations.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / count;
|
||||||
|
let std_dev = variance.sqrt();
|
||||||
|
|
||||||
|
(
|
||||||
|
Duration::from_secs_f64(mean / 1000.0),
|
||||||
|
Duration::from_secs_f64(std_dev / 1000.0),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn profile<S, M>(name: &'static str, system: S) -> impl FnMut(&mut bevy_ecs::world::World)
|
pub fn profile<S, M>(name: &'static str, system: S) -> impl FnMut(&mut bevy_ecs::world::World)
|
||||||
@@ -25,8 +95,8 @@ where
|
|||||||
system.run((), world);
|
system.run((), world);
|
||||||
let duration = start.elapsed();
|
let duration = start.elapsed();
|
||||||
|
|
||||||
if let Some(mut timings) = world.get_resource_mut::<SystemTimings>() {
|
if let Some(timings) = world.get_resource::<SystemTimings>() {
|
||||||
timings.timings.lock().insert(name, duration);
|
timings.add_timing(name, duration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,25 @@
|
|||||||
use crate::error::{GameError, TextureError};
|
use crate::error::{GameError, TextureError};
|
||||||
use crate::map::builder::Map;
|
use crate::map::builder::Map;
|
||||||
use crate::systems::components::{DeltaTime, DirectionalAnimated, Renderable};
|
use crate::systems::components::{DeltaTime, DirectionalAnimated, RenderDirty, Renderable};
|
||||||
use crate::systems::movement::{Movable, MovementState, Position};
|
use crate::systems::movement::{Movable, MovementState, Position};
|
||||||
use crate::texture::sprite::SpriteAtlas;
|
use crate::texture::sprite::SpriteAtlas;
|
||||||
use bevy_ecs::entity::Entity;
|
use bevy_ecs::entity::Entity;
|
||||||
use bevy_ecs::event::EventWriter;
|
use bevy_ecs::event::EventWriter;
|
||||||
use bevy_ecs::system::{NonSendMut, Query, Res};
|
use bevy_ecs::prelude::{Changed, Or, RemovedComponents};
|
||||||
|
use bevy_ecs::system::{NonSendMut, Query, Res, ResMut};
|
||||||
use sdl2::render::{Canvas, Texture};
|
use sdl2::render::{Canvas, Texture};
|
||||||
use sdl2::video::Window;
|
use sdl2::video::Window;
|
||||||
|
|
||||||
|
pub fn dirty_render_system(
|
||||||
|
mut dirty: ResMut<RenderDirty>,
|
||||||
|
changed_renderables: Query<(), Or<(Changed<Renderable>, Changed<Position>)>>,
|
||||||
|
removed_renderables: RemovedComponents<Renderable>,
|
||||||
|
) {
|
||||||
|
if !changed_renderables.is_empty() || !removed_renderables.is_empty() {
|
||||||
|
dirty.0 = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Updates the directional animated texture of an entity.
|
/// Updates the directional animated texture of an entity.
|
||||||
///
|
///
|
||||||
/// This runs before the render system so it can update the sprite based on the current direction of travel, as well as whether the entity is moving.
|
/// This runs before the render system so it can update the sprite based on the current direction of travel, as well as whether the entity is moving.
|
||||||
@@ -31,7 +42,10 @@ pub fn directional_render_system(
|
|||||||
if !stopped {
|
if !stopped {
|
||||||
texture.tick(dt.0);
|
texture.tick(dt.0);
|
||||||
}
|
}
|
||||||
renderable.sprite = *texture.current_tile();
|
let new_tile = *texture.current_tile();
|
||||||
|
if renderable.sprite != new_tile {
|
||||||
|
renderable.sprite = new_tile;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
errors.write(TextureError::RenderFailed(format!("Entity has no texture")).into());
|
errors.write(TextureError::RenderFailed(format!("Entity has no texture")).into());
|
||||||
continue;
|
continue;
|
||||||
@@ -51,9 +65,13 @@ pub fn render_system(
|
|||||||
mut backbuffer: NonSendMut<BackbufferResource>,
|
mut backbuffer: NonSendMut<BackbufferResource>,
|
||||||
mut atlas: NonSendMut<SpriteAtlas>,
|
mut atlas: NonSendMut<SpriteAtlas>,
|
||||||
map: Res<Map>,
|
map: Res<Map>,
|
||||||
renderables: Query<(Entity, &mut Renderable, &Position)>,
|
mut dirty: ResMut<RenderDirty>,
|
||||||
|
renderables: Query<(Entity, &Renderable, &Position)>,
|
||||||
mut errors: EventWriter<GameError>,
|
mut errors: EventWriter<GameError>,
|
||||||
) {
|
) {
|
||||||
|
if !dirty.0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Clear the main canvas first
|
// Clear the main canvas first
|
||||||
canvas.set_draw_color(sdl2::pixels::Color::BLACK);
|
canvas.set_draw_color(sdl2::pixels::Color::BLACK);
|
||||||
canvas.clear();
|
canvas.clear();
|
||||||
@@ -66,17 +84,16 @@ pub fn render_system(
|
|||||||
backbuffer_canvas.clear();
|
backbuffer_canvas.clear();
|
||||||
|
|
||||||
// Copy the pre-rendered map texture to the backbuffer
|
// Copy the pre-rendered map texture to the backbuffer
|
||||||
backbuffer_canvas
|
if let Err(e) = backbuffer_canvas.copy(&map_texture.0, None, None) {
|
||||||
.copy(&map_texture.0, None, None)
|
errors.write(TextureError::RenderFailed(e.to_string()).into());
|
||||||
.err()
|
}
|
||||||
.map(|e| errors.write(TextureError::RenderFailed(e.to_string()).into()));
|
|
||||||
|
|
||||||
// Render all entities to the backbuffer
|
// Render all entities to the backbuffer
|
||||||
for (_, mut renderable, position) in renderables
|
for (_, renderable, position) in renderables.iter() {
|
||||||
// .iter_mut()
|
if !renderable.visible {
|
||||||
// .sort_by_key::<&mut Renderable, _, _>(|(renderable, renderable, renderable)| renderable.layer)
|
continue;
|
||||||
// .collect()
|
}
|
||||||
{
|
|
||||||
let pos = position.get_pixel_pos(&map.graph);
|
let pos = position.get_pixel_pos(&map.graph);
|
||||||
match pos {
|
match pos {
|
||||||
Ok(pos) => {
|
Ok(pos) => {
|
||||||
@@ -107,4 +124,6 @@ pub fn render_system(
|
|||||||
.map(|e| errors.write(TextureError::RenderFailed(e.to_string()).into()));
|
.map(|e| errors.write(TextureError::RenderFailed(e.to_string()).into()));
|
||||||
|
|
||||||
canvas.present();
|
canvas.present();
|
||||||
|
|
||||||
|
dirty.0 = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ pub struct MapperFrame {
|
|||||||
pub height: u16,
|
pub height: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
pub struct AtlasTile {
|
pub struct AtlasTile {
|
||||||
pub pos: U16Vec2,
|
pub pos: U16Vec2,
|
||||||
pub size: U16Vec2,
|
pub size: U16Vec2,
|
||||||
@@ -30,7 +30,7 @@ pub struct AtlasTile {
|
|||||||
|
|
||||||
impl AtlasTile {
|
impl AtlasTile {
|
||||||
pub fn render<C: RenderTarget>(
|
pub fn render<C: RenderTarget>(
|
||||||
&mut self,
|
&self,
|
||||||
canvas: &mut Canvas<C>,
|
canvas: &mut Canvas<C>,
|
||||||
atlas: &mut SpriteAtlas,
|
atlas: &mut SpriteAtlas,
|
||||||
dest: Rect,
|
dest: Rect,
|
||||||
@@ -41,7 +41,7 @@ impl AtlasTile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_with_color<C: RenderTarget>(
|
pub fn render_with_color<C: RenderTarget>(
|
||||||
&mut self,
|
&self,
|
||||||
canvas: &mut Canvas<C>,
|
canvas: &mut Canvas<C>,
|
||||||
atlas: &mut SpriteAtlas,
|
atlas: &mut SpriteAtlas,
|
||||||
dest: Rect,
|
dest: Rect,
|
||||||
|
|||||||
@@ -103,9 +103,9 @@ impl TextTexture {
|
|||||||
&self.char_map
|
&self.char_map
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_tile(&mut self, c: char, atlas: &mut SpriteAtlas) -> Result<Option<&mut AtlasTile>> {
|
pub fn get_tile(&mut self, c: char, atlas: &mut SpriteAtlas) -> Result<Option<&AtlasTile>> {
|
||||||
if self.char_map.contains_key(&c) {
|
if self.char_map.contains_key(&c) {
|
||||||
return Ok(self.char_map.get_mut(&c));
|
return Ok(self.char_map.get(&c));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(tile_name) = char_to_tile_name(c) {
|
if let Some(tile_name) = char_to_tile_name(c) {
|
||||||
@@ -113,7 +113,7 @@ impl TextTexture {
|
|||||||
.get_tile(&tile_name)
|
.get_tile(&tile_name)
|
||||||
.ok_or(GameError::Texture(TextureError::AtlasTileNotFound(tile_name)))?;
|
.ok_or(GameError::Texture(TextureError::AtlasTileNotFound(tile_name)))?;
|
||||||
self.char_map.insert(c, tile);
|
self.char_map.insert(c, tile);
|
||||||
Ok(self.char_map.get_mut(&c))
|
Ok(self.char_map.get(&c))
|
||||||
} else {
|
} else {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|||||||
40
tests/profiling.rs
Normal file
40
tests/profiling.rs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
use pacman::systems::profiling::SystemTimings;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_timing_statistics() {
|
||||||
|
let timings = SystemTimings::default();
|
||||||
|
|
||||||
|
// Add some test data
|
||||||
|
timings.add_timing("test_system", Duration::from_millis(10));
|
||||||
|
timings.add_timing("test_system", Duration::from_millis(12));
|
||||||
|
timings.add_timing("test_system", Duration::from_millis(8));
|
||||||
|
|
||||||
|
let stats = timings.get_stats();
|
||||||
|
let (avg, std_dev) = stats.get("test_system").unwrap();
|
||||||
|
|
||||||
|
// Average should be 10ms, standard deviation should be small
|
||||||
|
assert!((avg.as_millis() as f64 - 10.0).abs() < 1.0);
|
||||||
|
assert!(std_dev.as_millis() > 0);
|
||||||
|
|
||||||
|
let (total_avg, total_std) = timings.get_total_stats();
|
||||||
|
assert!((total_avg.as_millis() as f64 - 10.0).abs() < 1.0);
|
||||||
|
assert!(total_std.as_millis() > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user