mirror of
https://github.com/Xevion/Pac-Man.git
synced 2025-12-06 11:15:46 -06:00
refactor: use stack allocated circular buffer, use RwLock+Mutex for concurrent system timing access
This commit is contained in:
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
|
||||||
|
|||||||
@@ -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}"
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
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;
|
||||||
|
|
||||||
@@ -10,30 +10,49 @@ 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>>, 10>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
||||||
|
|
||||||
queue.push_back(duration);
|
// happy path, the name is already in the map (no need to mutate the hashmap)
|
||||||
if queue.len() > TIMING_WINDOW_SIZE {
|
if timings.contains_key(name) {
|
||||||
queue.pop_front();
|
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> {
|
pub fn get_stats(&self) -> Map<&'static str, (Duration, Duration), 10> {
|
||||||
let timings = self.timings.lock();
|
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 +74,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