use super::{Scene, SceneTransition};
use crate::context::{ResourceContext, ServiceContext, SystemContext};
use crate::error::Result;
use std::{future::Future, pin::Pin};
pub struct SceneDirector<S> {
stack: Vec<S>,
should_quit: bool,
services: ServiceContext,
systems: SystemContext,
resources: ResourceContext,
}
impl<S: Scene> SceneDirector<S> {
pub async fn new(
mut initial_scene: S,
services: ServiceContext,
mut systems: SystemContext,
mut resources: ResourceContext,
) -> Self {
initial_scene
.on_enter(&services, &mut systems, &mut resources)
.await;
Self {
stack: vec![initial_scene],
should_quit: false,
services,
systems,
resources,
}
}
pub async fn update(&mut self) -> SceneTransition<S> {
if let Some(current) = self.stack.last_mut() {
current
.on_update(&self.services, &mut self.systems, &mut self.resources)
.await
} else {
SceneTransition::Quit }
}
pub async fn switch_to(&mut self, mut next: S) {
if let Some(mut current) = self.stack.pop() {
current
.on_exit(&self.services, &mut self.systems, &mut self.resources)
.await;
}
next.on_enter(&self.services, &mut self.systems, &mut self.resources)
.await;
self.stack.push(next);
}
pub async fn push(&mut self, mut next: S) {
if let Some(current) = self.stack.last_mut() {
current
.on_suspend(&self.services, &mut self.systems, &mut self.resources)
.await;
}
next.on_enter(&self.services, &mut self.systems, &mut self.resources)
.await;
self.stack.push(next);
}
pub async fn pop(&mut self) -> bool {
if let Some(mut popped) = self.stack.pop() {
popped
.on_exit(&self.services, &mut self.systems, &mut self.resources)
.await;
if let Some(current) = self.stack.last_mut() {
current
.on_resume(&self.services, &mut self.systems, &mut self.resources)
.await;
}
true
} else {
false
}
}
pub async fn transition_to(&mut self, next: S) {
self.switch_to(next).await;
}
pub async fn quit(&mut self) {
while let Some(mut scene) = self.stack.pop() {
scene
.on_exit(&self.services, &mut self.systems, &mut self.resources)
.await;
}
self.should_quit = true;
}
pub fn should_quit(&self) -> bool {
self.should_quit
}
pub fn current(&self) -> Option<&S> {
self.stack.last()
}
pub fn current_mut(&mut self) -> Option<&mut S> {
self.stack.last_mut()
}
pub fn with_current_mut<R>(
&mut self,
f: impl FnOnce(&mut S, &ServiceContext, &mut SystemContext, &mut ResourceContext) -> R,
) -> Option<R> {
if let Some(scene) = self.stack.last_mut() {
let services = &self.services;
let systems = &mut self.systems;
let resources = &mut self.resources;
Some(f(scene, services, systems, resources))
} else {
None
}
}
pub async fn with_current_async<R>(
&mut self,
mut handler: impl for<'a> FnMut(
&'a mut S,
&'a ServiceContext,
&'a mut SystemContext,
&'a mut ResourceContext,
) -> Pin<Box<dyn Future<Output = R> + 'a>>,
) -> Option<R> {
if let Some(scene) = self.stack.last_mut() {
Some(
handler(
scene,
&self.services,
&mut self.systems,
&mut self.resources,
)
.await,
)
} else {
None
}
}
pub fn depth(&self) -> usize {
self.stack.len()
}
pub fn is_empty(&self) -> bool {
self.stack.is_empty()
}
pub fn iter_scenes(&self) -> impl Iterator<Item = &S> {
self.stack.iter()
}
pub fn iter_scenes_rev(&self) -> impl Iterator<Item = &S> {
self.stack.iter().rev()
}
pub fn iter_with_depth(&self) -> impl Iterator<Item = (usize, &S)> {
self.stack.iter().enumerate()
}
pub fn services(&self) -> &ServiceContext {
&self.services
}
pub fn systems(&self) -> &SystemContext {
&self.systems
}
pub fn systems_mut(&mut self) -> &mut SystemContext {
&mut self.systems
}
pub fn resources(&self) -> &ResourceContext {
&self.resources
}
pub fn resources_mut(&mut self) -> &mut ResourceContext {
&mut self.resources
}
pub async fn handle(&mut self, transition: SceneTransition<S>) -> Result<()> {
match transition {
SceneTransition::Stay => {
}
SceneTransition::Switch(next) => {
self.switch_to(next).await;
}
SceneTransition::Push(next) => {
self.push(next).await;
}
SceneTransition::Pop => {
self.pop().await;
}
SceneTransition::Quit => {
self.quit().await;
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::context::{ResourceContext, ServiceContext, SystemContext};
use async_trait::async_trait;
#[derive(Debug, Clone, PartialEq)]
struct TestScene {
name: String,
enter_count: usize,
update_count: usize,
exit_count: usize,
suspend_count: usize,
resume_count: usize,
should_transition: bool,
should_quit: bool,
}
impl TestScene {
fn new(name: &str) -> Self {
Self {
name: name.to_string(),
enter_count: 0,
update_count: 0,
exit_count: 0,
suspend_count: 0,
resume_count: 0,
should_transition: false,
should_quit: false,
}
}
#[allow(dead_code)]
fn with_transition(mut self) -> Self {
self.should_transition = true;
self
}
fn with_quit(mut self) -> Self {
self.should_quit = true;
self
}
}
#[async_trait]
impl Scene for TestScene {
async fn on_enter(
&mut self,
_services: &ServiceContext,
_systems: &mut SystemContext,
_resources: &mut ResourceContext,
) {
self.enter_count += 1;
}
async fn on_update(
&mut self,
_services: &ServiceContext,
_systems: &mut SystemContext,
_resources: &mut ResourceContext,
) -> SceneTransition<Self> {
self.update_count += 1;
if self.should_quit {
SceneTransition::Quit
} else if self.should_transition {
SceneTransition::Switch(TestScene::new("next"))
} else {
SceneTransition::Stay
}
}
async fn on_exit(
&mut self,
_services: &ServiceContext,
_systems: &mut SystemContext,
_resources: &mut ResourceContext,
) {
self.exit_count += 1;
}
async fn on_suspend(
&mut self,
_services: &ServiceContext,
_systems: &mut SystemContext,
_resources: &mut ResourceContext,
) {
self.suspend_count += 1;
}
async fn on_resume(
&mut self,
_services: &ServiceContext,
_systems: &mut SystemContext,
_resources: &mut ResourceContext,
) {
self.resume_count += 1;
}
}
async fn director_with_scene(scene: TestScene) -> SceneDirector<TestScene> {
SceneDirector::new(
scene,
ServiceContext::new(),
SystemContext::new(),
ResourceContext::new(),
)
.await
}
#[tokio::test]
async fn test_new_calls_on_enter() {
let scene = TestScene::new("test");
let director = director_with_scene(scene).await;
assert_eq!(director.depth(), 1);
assert_eq!(director.current().unwrap().enter_count, 1);
assert_eq!(director.current().unwrap().update_count, 0);
assert_eq!(director.current().unwrap().exit_count, 0);
}
#[tokio::test]
async fn test_update_calls_on_update() {
let scene = TestScene::new("test");
let mut director = director_with_scene(scene).await;
let transition = director.update().await;
assert_eq!(transition, SceneTransition::Stay);
assert_eq!(director.current().unwrap().update_count, 1);
}
#[tokio::test]
async fn test_switch_to_calls_lifecycle() {
let scene1 = TestScene::new("scene1");
let mut director = director_with_scene(scene1).await;
let scene2 = TestScene::new("scene2");
director.switch_to(scene2).await;
assert_eq!(director.depth(), 1);
assert_eq!(director.current().unwrap().name, "scene2");
assert_eq!(director.current().unwrap().enter_count, 1);
assert_eq!(director.current().unwrap().exit_count, 0);
}
#[tokio::test]
async fn test_push_calls_suspend_and_enter() {
let scene1 = TestScene::new("scene1");
let mut director = director_with_scene(scene1).await;
let scene2 = TestScene::new("scene2");
director.push(scene2).await;
assert_eq!(director.depth(), 2);
assert_eq!(director.current().unwrap().name, "scene2");
assert_eq!(director.current().unwrap().enter_count, 1);
}
#[tokio::test]
async fn test_pop_calls_exit_and_resume() {
let scene1 = TestScene::new("scene1");
let mut director = director_with_scene(scene1).await;
let scene2 = TestScene::new("scene2");
director.push(scene2).await;
assert_eq!(director.depth(), 2);
let popped = director.pop().await;
assert!(popped);
assert_eq!(director.depth(), 1);
assert_eq!(director.current().unwrap().name, "scene1");
assert_eq!(director.current().unwrap().resume_count, 1);
}
#[tokio::test]
async fn test_quit_calls_on_exit_for_all() {
let scene1 = TestScene::new("scene1");
let mut director = director_with_scene(scene1).await;
let scene2 = TestScene::new("scene2");
director.push(scene2).await;
assert_eq!(director.depth(), 2);
assert!(!director.should_quit());
director.quit().await;
assert!(director.should_quit());
assert_eq!(director.depth(), 0);
assert!(director.current().is_none());
}
#[tokio::test]
async fn test_should_quit_returns_transition() {
let scene = TestScene::new("test").with_quit();
let mut director = director_with_scene(scene).await;
let transition = director.update().await;
assert!(matches!(transition, SceneTransition::Quit));
}
#[tokio::test]
async fn test_handle_switch() {
let scene1 = TestScene::new("scene1");
let mut director = director_with_scene(scene1).await;
let transition = SceneTransition::Switch(TestScene::new("scene2"));
director.handle(transition).await.unwrap();
assert_eq!(director.depth(), 1);
assert_eq!(director.current().unwrap().name, "scene2");
}
#[tokio::test]
async fn test_handle_push_pop() {
let scene1 = TestScene::new("scene1");
let mut director = director_with_scene(scene1).await;
let transition = SceneTransition::Push(TestScene::new("scene2"));
director.handle(transition).await.unwrap();
assert_eq!(director.depth(), 2);
assert_eq!(director.current().unwrap().name, "scene2");
let transition = SceneTransition::Pop;
director.handle(transition).await.unwrap();
assert_eq!(director.depth(), 1);
assert_eq!(director.current().unwrap().name, "scene1");
}
#[tokio::test]
async fn test_handle_quit() {
let scene1 = TestScene::new("scene1");
let mut director = director_with_scene(scene1).await;
let transition = SceneTransition::Quit;
director.handle(transition).await.unwrap();
assert!(director.should_quit());
assert_eq!(director.depth(), 0);
}
#[tokio::test]
async fn test_multiple_pushes_and_pops() {
let scene1 = TestScene::new("scene1");
let mut director = director_with_scene(scene1).await;
director.push(TestScene::new("scene2")).await;
director.push(TestScene::new("scene3")).await;
assert_eq!(director.depth(), 3);
assert_eq!(director.current().unwrap().name, "scene3");
director.pop().await;
assert_eq!(director.depth(), 2);
assert_eq!(director.current().unwrap().name, "scene2");
director.pop().await;
assert_eq!(director.depth(), 1);
assert_eq!(director.current().unwrap().name, "scene1");
}
#[tokio::test]
async fn test_empty_stack_after_pop() {
let scene1 = TestScene::new("scene1");
let mut director = director_with_scene(scene1).await;
director.pop().await;
assert_eq!(director.depth(), 0);
assert!(director.current().is_none());
}
#[tokio::test]
async fn test_backward_compatibility_transition_to() {
let scene1 = TestScene::new("scene1");
let mut director = director_with_scene(scene1).await;
director.transition_to(TestScene::new("scene2")).await;
assert_eq!(director.depth(), 1);
assert_eq!(director.current().unwrap().name, "scene2");
}
#[tokio::test]
async fn test_iter_scenes() {
let scene1 = TestScene::new("scene1");
let mut director = director_with_scene(scene1).await;
director.push(TestScene::new("scene2")).await;
director.push(TestScene::new("scene3")).await;
let names: Vec<_> = director.iter_scenes().map(|s| s.name.as_str()).collect();
assert_eq!(names, vec!["scene1", "scene2", "scene3"]);
}
#[tokio::test]
async fn test_iter_scenes_rev() {
let scene1 = TestScene::new("scene1");
let mut director = director_with_scene(scene1).await;
director.push(TestScene::new("scene2")).await;
director.push(TestScene::new("scene3")).await;
let names: Vec<_> = director
.iter_scenes_rev()
.map(|s| s.name.as_str())
.collect();
assert_eq!(names, vec!["scene3", "scene2", "scene1"]);
}
#[tokio::test]
async fn test_iter_with_depth() {
let scene1 = TestScene::new("scene1");
let mut director = director_with_scene(scene1).await;
director.push(TestScene::new("scene2")).await;
let items: Vec<_> = director
.iter_with_depth()
.map(|(depth, scene)| (depth, scene.name.as_str()))
.collect();
assert_eq!(items, vec![(0, "scene1"), (1, "scene2")]);
}
}