mirror of
https://github.com/Xevion/Pac-Man.git
synced 2025-12-10 06:07:55 -06:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "circular-buffer"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "23bdce1da528cadbac4654b5632bfcd8c6c63e25b1d42cea919a95958790b51d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "concurrent-queue"
|
name = "concurrent-queue"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
@@ -590,6 +596,7 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"bevy_ecs",
|
"bevy_ecs",
|
||||||
"bitflags 2.9.1",
|
"bitflags 2.9.1",
|
||||||
|
"circular-buffer",
|
||||||
"glam 0.30.5",
|
"glam 0.30.5",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"libc",
|
"libc",
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ micromap = "0.1.0"
|
|||||||
thousands = "0.2.0"
|
thousands = "0.2.0"
|
||||||
pretty_assertions = "1.4.1"
|
pretty_assertions = "1.4.1"
|
||||||
num-width = "0.1.0"
|
num-width = "0.1.0"
|
||||||
|
circular-buffer = "1.1.0"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ use crate::systems::{
|
|||||||
control::player_system,
|
control::player_system,
|
||||||
debug::{debug_render_system, DebugState, DebugTextureResource},
|
debug::{debug_render_system, DebugState, DebugTextureResource},
|
||||||
input::input_system,
|
input::input_system,
|
||||||
|
item::item_system,
|
||||||
movement::movement_system,
|
movement::movement_system,
|
||||||
profiling::{profile, SystemTimings},
|
profiling::{profile, SystemTimings},
|
||||||
render::{directional_render_system, dirty_render_system, render_system, BackbufferResource, MapTextureResource},
|
render::{directional_render_system, dirty_render_system, render_system, BackbufferResource, MapTextureResource},
|
||||||
@@ -211,6 +212,7 @@ impl Game {
|
|||||||
profile("player", player_system),
|
profile("player", player_system),
|
||||||
profile("movement", movement_system),
|
profile("movement", movement_system),
|
||||||
profile("collision", collision_system),
|
profile("collision", collision_system),
|
||||||
|
profile("item", item_system),
|
||||||
profile("blinking", blinking_system),
|
profile("blinking", blinking_system),
|
||||||
profile("directional_render", directional_render_system),
|
profile("directional_render", directional_render_system),
|
||||||
profile("dirty_render", dirty_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
|
// Split text by newlines and render each line separately
|
||||||
let lines: Vec<&str> = timing_text.lines().collect();
|
let lines: Vec<&str> = timing_text.lines().collect();
|
||||||
|
if lines.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let line_height = 14; // Approximate line height for 12pt font
|
let line_height = 14; // Approximate line height for 12pt font
|
||||||
let padding = 10;
|
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() {
|
for (i, line) in lines.iter().enumerate() {
|
||||||
if line.is_empty() {
|
if line.is_empty() {
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -95,10 +95,6 @@ pub fn format_timing_display(timing_data: Vec<(String, Duration, Duration)>) ->
|
|||||||
std_unit,
|
std_unit,
|
||||||
} in entries.iter()
|
} 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!(
|
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}"
|
"{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}"
|
||||||
));
|
));
|
||||||
|
|||||||
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 debug;
|
||||||
pub mod formatting;
|
pub mod formatting;
|
||||||
pub mod input;
|
pub mod input;
|
||||||
|
pub mod item;
|
||||||
pub mod movement;
|
pub mod movement;
|
||||||
pub mod profiling;
|
pub mod profiling;
|
||||||
pub mod render;
|
pub mod render;
|
||||||
|
|||||||
@@ -1,39 +1,61 @@
|
|||||||
use bevy_ecs::prelude::Resource;
|
use bevy_ecs::prelude::Resource;
|
||||||
use bevy_ecs::system::{IntoSystem, System};
|
use bevy_ecs::system::{IntoSystem, System};
|
||||||
|
use circular_buffer::CircularBuffer;
|
||||||
use micromap::Map;
|
use micromap::Map;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::{Mutex, RwLock};
|
||||||
use std::collections::VecDeque;
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use thousands::Separable;
|
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;
|
const TIMING_WINDOW_SIZE: usize = 30;
|
||||||
|
|
||||||
#[derive(Resource, Default, Debug)]
|
#[derive(Resource, Default, Debug)]
|
||||||
pub struct SystemTimings {
|
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 {
|
impl SystemTimings {
|
||||||
pub fn add_timing(&self, name: &'static str, duration: Duration) {
|
pub fn add_timing(&self, name: &'static str, duration: Duration) {
|
||||||
let mut timings = self.timings.lock();
|
// acquire a upgradable read lock
|
||||||
let queue = timings.entry(name).or_insert_with(VecDeque::new);
|
let mut timings = self.timings.upgradable_read();
|
||||||
|
|
||||||
|
// 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);
|
queue.push_back(duration);
|
||||||
if queue.len() > TIMING_WINDOW_SIZE {
|
return;
|
||||||
queue.pop_front();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_stats(&self) -> Map<&'static str, (Duration, Duration), 10> {
|
// otherwise, acquire a write lock and insert a new queue
|
||||||
let timings = self.timings.lock();
|
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), MAX_SYSTEMS> {
|
||||||
|
let timings = self.timings.read();
|
||||||
let mut stats = Map::new();
|
let mut stats = Map::new();
|
||||||
|
|
||||||
for (name, queue) in timings.iter() {
|
for (name, queue) in timings.iter() {
|
||||||
if queue.is_empty() {
|
if queue.lock().is_empty() {
|
||||||
continue;
|
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 count = durations.len() as f64;
|
||||||
|
|
||||||
let sum: f64 = durations.iter().sum();
|
let sum: f64 = durations.iter().sum();
|
||||||
@@ -55,11 +77,11 @@ impl SystemTimings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_total_stats(&self) -> (Duration, Duration) {
|
pub fn get_total_stats(&self) -> (Duration, Duration) {
|
||||||
let timings = self.timings.lock();
|
let timings = self.timings.read();
|
||||||
let mut all_durations = Vec::new();
|
let mut all_durations = Vec::new();
|
||||||
|
|
||||||
for queue in timings.values() {
|
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() {
|
if all_durations.is_empty() {
|
||||||
|
|||||||
Reference in New Issue
Block a user