use alloc::vec::Vec;
use super::World;
pub struct System {
pub name: &'static str,
pub priority: i32,
pub run: fn(&mut World),
pub last_us: u32,
pub total_us: u64,
pub call_count: u32,
}
impl System {
pub const fn new(name: &'static str, priority: i32, run: fn(&mut World)) -> Self {
Self {
name,
priority,
run,
last_us: 0,
total_us: 0,
call_count: 0,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum SystemSlot {
SimInput,
DeltaTime,
InteractionState,
Animation,
Timer,
ScrollInertia,
LazyList,
TabPages,
Normal,
PreRender,
}
impl SystemSlot {
pub const fn priority(self) -> i32 {
match self {
Self::SimInput => 50,
Self::DeltaTime => 60,
Self::InteractionState => 80,
Self::Animation => 150,
Self::Timer => 150,
Self::ScrollInertia => 250,
Self::LazyList => 350,
Self::TabPages => 350,
Self::Normal => 500,
Self::PreRender => 700,
}
}
}
impl From<SystemSlot> for i32 {
fn from(slot: SystemSlot) -> i32 {
slot.priority()
}
}
pub mod run_order {
use super::SystemSlot;
pub const SIM_INPUT: i32 = SystemSlot::SimInput.priority();
pub const DELTA_TIME: i32 = SystemSlot::DeltaTime.priority();
pub const INTERACTION_STATE: i32 = SystemSlot::InteractionState.priority();
pub const ANIMATION: i32 = SystemSlot::Animation.priority();
pub const TIMER: i32 = SystemSlot::Timer.priority();
pub const SCROLL_INERTIA: i32 = SystemSlot::ScrollInertia.priority();
pub const LAZY_LIST: i32 = SystemSlot::LazyList.priority();
pub const TAB_PAGES: i32 = SystemSlot::TabPages.priority();
pub const NORMAL: i32 = SystemSlot::Normal.priority();
pub const PRE_RENDER: i32 = SystemSlot::PreRender.priority();
}
#[derive(Default)]
pub struct SystemScheduler {
systems: Vec<System>,
}
impl SystemScheduler {
pub fn new() -> Self {
Self::default()
}
pub fn add(&mut self, system: System) {
let pos = self
.systems
.iter()
.position(|s| s.priority > system.priority)
.unwrap_or(self.systems.len());
self.systems.insert(pos, system);
}
pub fn run_all(&mut self, world: &mut World) {
let clock_fn = world.resource::<super::MonoClock>().map(|c| c.clock);
for system in &mut self.systems {
let start_ns = clock_fn.map(|f| f()).unwrap_or(0);
(system.run)(world);
if let Some(f) = clock_fn {
let elapsed_ns = f().saturating_sub(start_ns);
let elapsed_us = (elapsed_ns / 1_000) as u32;
system.last_us = elapsed_us;
system.total_us = system.total_us.saturating_add(elapsed_us as u64);
system.call_count = system.call_count.saturating_add(1);
}
}
}
pub fn reset_perf(&mut self) {
for system in &mut self.systems {
system.last_us = 0;
system.total_us = 0;
system.call_count = 0;
}
}
pub fn iter(&self) -> impl Iterator<Item = &System> {
self.systems.iter()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn dummy(_: &mut World) {}
#[test]
fn run_order_orders_ascending() {
let mut s = SystemScheduler::new();
s.add(System::new("late", 300, dummy));
s.add(System::new("early", 100, dummy));
s.add(System::new("mid", 200, dummy));
let names: Vec<&str> = s.iter().map(|s| s.name).collect();
assert_eq!(names, ["early", "mid", "late"]);
}
#[test]
fn equal_run_order_preserves_insertion_order() {
let mut s = SystemScheduler::new();
s.add(System::new("a", 200, dummy));
s.add(System::new("b", 200, dummy));
s.add(System::new("c", 100, dummy));
let names: Vec<&str> = s.iter().map(|s| s.name).collect();
assert_eq!(names, ["c", "a", "b"]);
}
#[test]
fn system_slot_priorities_match_run_order_constants() {
assert_eq!(SystemSlot::SimInput.priority(), run_order::SIM_INPUT);
assert_eq!(SystemSlot::DeltaTime.priority(), run_order::DELTA_TIME);
assert_eq!(
SystemSlot::InteractionState.priority(),
run_order::INTERACTION_STATE
);
assert_eq!(SystemSlot::Animation.priority(), run_order::ANIMATION);
assert_eq!(SystemSlot::Timer.priority(), run_order::TIMER);
assert_eq!(
SystemSlot::ScrollInertia.priority(),
run_order::SCROLL_INERTIA
);
assert_eq!(SystemSlot::LazyList.priority(), run_order::LAZY_LIST);
assert_eq!(SystemSlot::TabPages.priority(), run_order::TAB_PAGES);
assert_eq!(SystemSlot::Normal.priority(), run_order::NORMAL);
assert_eq!(SystemSlot::PreRender.priority(), run_order::PRE_RENDER);
}
#[test]
fn system_slot_into_i32_returns_priority() {
let p: i32 = SystemSlot::Animation.into();
assert_eq!(p, 150);
}
#[test]
fn plugins_declare_inserts_section() {
let plugins = [
("perf_report", include_str!("../plugins/perf_report.rs")),
("fps_summary", include_str!("../plugins/fps_summary.rs")),
(
"input_feedback",
include_str!("../plugins/input_feedback.rs"),
),
#[cfg(feature = "std")]
("std_clock", include_str!("../plugins/std_clock.rs")),
];
for (name, src) in plugins {
assert!(
src.contains("**Inserts**"),
"plugin {name} missing **Inserts** doc section",
);
}
}
}