use std::sync::{Arc, Mutex};
use crate::ecs::{Component, Entity, Resource, With, World};
use crate::state::AppState;
pub trait Scene: Send + 'static {
fn setup(&mut self, app: &mut AppState) {
let _ = app;
}
fn update(&mut self, app: &mut AppState) {
let _ = app;
}
}
struct Pending {
countdown: f32,
scene: Box<dyn Scene>,
}
#[derive(Resource, Clone, Default)]
pub struct SceneCommands {
pending: Arc<Mutex<Option<Pending>>>,
}
impl SceneCommands {
pub fn new() -> Self {
Self::default()
}
pub fn change(&self, scene: impl Scene) {
self.change_after(0.0, scene);
}
pub fn change_after(&self, seconds: f32, scene: impl Scene) {
self.queue(seconds, Box::new(scene));
}
pub fn change_boxed(&self, scene: Box<dyn Scene>) {
self.queue(0.0, scene);
}
fn queue(&self, countdown: f32, scene: Box<dyn Scene>) {
let pending = Pending { countdown, scene };
*self.pending.lock().expect("scene queue poisoned") = Some(pending);
}
pub fn is_pending(&self) -> bool {
self.pending.lock().expect("scene queue poisoned").is_some()
}
pub(crate) fn tick(&self, delta: f32) {
if let Some(pending) = self.pending.lock().expect("scene queue poisoned").as_mut() {
pending.countdown -= delta;
}
}
pub(crate) fn take_ready(&self) -> Option<Box<dyn Scene>> {
let mut pending = self.pending.lock().expect("scene queue poisoned");
if pending.as_ref().is_some_and(|p| p.countdown <= 0.0) {
return pending.take().map(|p| p.scene);
}
None
}
}
pub trait Spawn {
type Output;
fn spawn(self, world: &mut World) -> Self::Output;
}
#[derive(Component)]
pub struct SceneEntity;
pub fn clear_scene(world: &mut World) {
let mut scene_entities = world.query_filtered::<Entity, With<SceneEntity>>();
let entities = scene_entities.iter(world).collect::<Vec<_>>();
for entity in entities {
world.despawn(entity);
}
}
#[cfg(test)]
mod tests {
use super::*;
struct Next;
impl Scene for Next {}
#[test]
fn an_immediate_change_is_ready_at_once() {
let commands = SceneCommands::new();
commands.change(Next);
assert!(commands.is_pending());
assert!(commands.take_ready().is_some());
assert!(!commands.is_pending(), "taking it clears the queue");
}
#[test]
fn a_delayed_change_waits_out_its_countdown() {
let commands = SceneCommands::new();
commands.change_after(2.0, Next);
commands.tick(1.5);
assert!(commands.take_ready().is_none(), "still counting down");
assert!(commands.is_pending(), "and still queued");
commands.tick(0.6);
assert!(commands.take_ready().is_some(), "countdown ran out");
}
#[test]
fn a_later_request_replaces_a_pending_one() {
let commands = SceneCommands::new();
commands.change_after(10.0, Next);
commands.change(Next);
assert!(
commands.take_ready().is_some(),
"the immediate request wins"
);
}
#[test]
fn nothing_is_taken_from_an_empty_queue() {
let commands = SceneCommands::new();
commands.tick(1.0);
assert!(!commands.is_pending());
assert!(commands.take_ready().is_none());
}
}