use alloc::boxed::Box;
use crate::app::App;
use crate::components::Transform;
use crate::ecs::{PipelineContext, SimTiming, StepResult, System, SystemEntry, SystemTable, World};
use crate::result::CnResult;
const STOP_AT: f32 = 3.0;
#[derive(Debug)]
struct Ticker;
impl System for Ticker {
fn step(&mut self, ctx: &mut PipelineContext) -> StepResult {
for transform in ctx.query_mut::<Transform>() {
transform.position[0] += 1.0;
}
StepResult::Continue
}
}
#[derive(Debug)]
struct Stopper;
impl System for Stopper {
fn step(&mut self, ctx: &mut PipelineContext) -> StepResult {
match ctx.query::<Transform>().next() {
Some(t) if t.position[0] >= STOP_AT => StepResult::Stop,
_ => StepResult::Continue,
}
}
}
fn ticker(world: &World) -> Option<Box<dyn System>> {
world.query::<Transform>().next()?;
Some(Box::new(Ticker))
}
fn stopper(world: &World) -> Option<Box<dyn System>> {
world.query::<Transform>().next()?;
Some(Box::new(Stopper))
}
const fn entry(name: &'static str, gate: fn(&World) -> Option<Box<dyn System>>) -> SystemEntry {
SystemEntry {
name,
present_when: "the world holds a Transform",
gate,
after: &[],
before: &[],
}
}
const fn table(entries: &'static [SystemEntry]) -> SystemTable {
SystemTable {
entries,
before_init: None,
prepare_events: None,
}
}
static TICKING: SystemTable = table(&[entry("Ticker", ticker)]);
static HALTING: SystemTable = table(&[entry("Ticker", ticker), entry("Stopper", stopper)]);
fn counting_world() -> World {
let mut world = World::new();
world.push(Transform::default());
world
}
fn count(app: &App) -> f32 {
app.world()
.query::<Transform>()
.next()
.expect("the world holds its transform")
.position[0]
}
#[test]
fn a_bounded_run_steps_the_ticks_it_was_asked_for() {
let mut app = App::with_systems(counting_world(), &TICKING);
assert_eq!(app.run_for(5), Ok(StepResult::Continue));
assert_eq!(app.ticks(), 5);
assert_eq!(count(&app), 5.0);
}
#[test]
fn bounded_runs_continue_one_another() {
let mut app = App::with_systems(counting_world(), &TICKING);
app.run_for(2).expect("the app runs");
app.run_for(3).expect("the app runs again");
assert_eq!(app.ticks(), 5);
assert_eq!(count(&app), 5.0);
}
#[test]
fn a_tick_publishes_the_fixed_virtual_timing() {
let mut app = App::with_systems(counting_world(), &TICKING);
app.run_for(1).expect("the app runs");
let timing = app
.world()
.resource::<SimTiming>()
.expect("the tick published its budget");
assert_eq!(timing.ticks, 1);
assert_eq!(timing.tick_dt, SimTiming::TICK_DT);
assert_eq!(timing.alpha, 1.0);
}
#[test]
fn a_system_stopping_the_world_ends_the_run() {
let mut app = App::with_systems(counting_world(), &HALTING);
assert_eq!(app.run(), Ok(StepResult::Stop));
assert_eq!(app.ticks(), STOP_AT as u64);
}
#[test]
fn a_bounded_run_honors_a_stop_before_its_count() {
let mut app = App::with_systems(counting_world(), &HALTING);
assert_eq!(app.run_for(100), Ok(StepResult::Stop));
assert_eq!(app.ticks(), STOP_AT as u64);
}
#[test]
fn a_world_with_no_systems_is_done_on_its_first_tick() {
let mut app = App::from_world(counting_world());
assert_eq!(app.run(), Ok(StepResult::Done));
assert_eq!(app.ticks(), 1);
assert_eq!(count(&app), 0.0, "no system ran over the world");
}
#[test]
fn a_run_starts_the_world_and_tolerates_one_already_started() {
let mut app = App::with_systems(counting_world(), &TICKING);
assert_eq!(app.start(), Ok(()));
assert_eq!(app.run_for(2), Ok(StepResult::Continue));
assert_eq!(app.ticks(), 2);
let mut unstarted = App::with_systems(counting_world(), &TICKING);
assert_eq!(unstarted.run_for(2), Ok(StepResult::Continue));
assert_eq!(count(&unstarted), 2.0);
}
#[test]
fn starting_twice_is_refused() {
let mut app = App::with_systems(counting_world(), &TICKING);
assert_eq!(app.start(), Ok(()));
assert_eq!(app.start(), Err(CnResult::InvalidState));
}
#[cfg(debug_assertions)]
#[test]
fn the_allocation_invariant_arms_and_holds_across_a_long_run() {
use crate::app::alloc_guard::{QUIET_WINDOW_TICKS, WARMUP_TICKS, armed};
let mut app = App::with_systems(counting_world(), &TICKING);
let ticks = WARMUP_TICKS + QUIET_WINDOW_TICKS + 8;
assert_eq!(app.run_for(ticks), Ok(StepResult::Continue));
assert_eq!(app.ticks(), ticks);
assert!(armed(), "the test binary installs the tracking allocator");
}
#[test]
fn the_debug_impl_reports_the_runs_progress() {
let mut app = App::with_systems(counting_world(), &TICKING);
app.run_for(2).expect("the app runs");
let text = alloc::format!("{app:?}");
assert!(text.contains("ticks: 2"), "{text}");
assert!(text.contains("Started"), "{text}");
}