use crate::{
error::Result,
event::EventBus,
scene::{Scene, SceneDirector},
};
use std::time::Duration;
use tokio::time;
pub struct HeadlessRunner<S> {
director: SceneDirector<S>,
tick_rate: Duration,
max_ticks: Option<u64>,
}
impl<S: Scene> HeadlessRunner<S> {
pub fn new(director: SceneDirector<S>) -> Self {
Self {
director,
tick_rate: Duration::from_millis(100),
max_ticks: None,
}
}
pub fn with_tick_rate(mut self, tick_rate: Duration) -> Self {
self.tick_rate = tick_rate;
self
}
pub fn with_max_ticks(mut self, max_ticks: u64) -> Self {
self.max_ticks = Some(max_ticks);
self
}
pub fn director(&self) -> &SceneDirector<S> {
&self.director
}
pub fn director_mut(&mut self) -> &mut SceneDirector<S> {
&mut self.director
}
async fn update_systems(&mut self) {
use crate::plugin::action::ActionResetSystem;
use crate::plugin::time::TimerSystem;
self.director
.with_current_async(|_, services, systems, resources| {
Box::pin(async move {
if let Some(timer_system) = systems.get_mut::<TimerSystem>() {
timer_system.update(services, resources).await;
}
})
})
.await;
self.director
.with_current_async(|_, services, systems, resources| {
Box::pin(async move {
if let Some(action_reset) = systems.get_mut::<ActionResetSystem>() {
action_reset.update(services, resources).await;
}
})
})
.await;
}
pub async fn run(mut self) -> Result<()> {
let mut interval = time::interval(self.tick_rate);
let mut tick_count = 0u64;
loop {
interval.tick().await;
let transition = self.director.update().await;
self.director.handle(transition).await?;
self.update_systems().await;
if let Some(mut event_bus) = self.director.resources_mut().get_mut::<EventBus>().await {
event_bus.dispatch();
}
tick_count += 1;
if self.director.should_quit() || self.director.is_empty() {
break;
}
if let Some(max) = self.max_ticks {
if tick_count >= max {
break;
}
}
}
Ok(())
}
}
pub struct ChannelHeadlessRunner<S, Cmd> {
director: SceneDirector<S>,
tick_rate: Duration,
max_ticks: Option<u64>,
command_rx: tokio::sync::mpsc::Receiver<Cmd>,
}
impl<S: Scene> HeadlessRunner<S> {
pub fn with_command_channel<Cmd: crate::event::Event + Clone + serde::Serialize>(
self,
command_rx: tokio::sync::mpsc::Receiver<Cmd>,
) -> ChannelHeadlessRunner<S, Cmd> {
ChannelHeadlessRunner {
director: self.director,
tick_rate: self.tick_rate,
max_ticks: self.max_ticks,
command_rx,
}
}
}
impl<S: Scene, Cmd: crate::event::Event + Clone + serde::Serialize> ChannelHeadlessRunner<S, Cmd> {
pub fn director(&self) -> &SceneDirector<S> {
&self.director
}
pub fn director_mut(&mut self) -> &mut SceneDirector<S> {
&mut self.director
}
async fn update_systems(&mut self) {
use crate::plugin::action::ActionResetSystem;
use crate::plugin::time::TimerSystem;
self.director
.with_current_async(|_, services, systems, resources| {
Box::pin(async move {
if let Some(timer_system) = systems.get_mut::<TimerSystem>() {
timer_system.update(services, resources).await;
}
})
})
.await;
self.director
.with_current_async(|_, services, systems, resources| {
Box::pin(async move {
if let Some(action_reset) = systems.get_mut::<ActionResetSystem>() {
action_reset.update(services, resources).await;
}
})
})
.await;
}
pub async fn run(mut self) -> Result<()> {
let mut interval = time::interval(self.tick_rate);
let mut tick_count = 0u64;
loop {
tokio::select! {
_ = interval.tick() => {
let transition = self.director.update().await;
self.director.handle(transition).await?;
self.update_systems().await;
if let Some(mut event_bus) = self.director.resources_mut().get_mut::<EventBus>().await {
event_bus.dispatch();
}
tick_count += 1;
if self.director.should_quit() || self.director.is_empty() {
break;
}
if let Some(max) = self.max_ticks {
if tick_count >= max {
break;
}
}
}
Some(cmd) = self.command_rx.recv() => {
if let Some(mut event_bus) = self.director.resources_mut().get_mut::<EventBus>().await {
event_bus.publish(cmd);
event_bus.dispatch();
}
}
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
builder::GameBuilder,
context::{ResourceContext, ServiceContext, SystemContext},
event::Event,
scene::{Scene, SceneTransition},
};
use tokio::sync::mpsc;
struct TestScene {
update_count: u32,
}
impl TestScene {
fn new() -> Self {
Self { update_count: 0 }
}
}
#[async_trait::async_trait]
impl Scene for TestScene {
async fn on_update(
&mut self,
_services: &ServiceContext,
_systems: &mut SystemContext,
_resources: &mut ResourceContext,
) -> SceneTransition<Self> {
self.update_count += 1;
SceneTransition::Stay
}
}
#[tokio::test]
async fn test_headless_runner_stops_at_max_ticks() {
let builder = GameBuilder::new();
let game = builder.build().await.unwrap();
let director = SceneDirector::new(
TestScene::new(),
game.services,
game.systems,
game.resources,
)
.await;
let runner = HeadlessRunner::new(director)
.with_tick_rate(Duration::from_millis(1))
.with_max_ticks(10);
runner.run().await.unwrap();
}
#[tokio::test]
async fn test_headless_runner_updates_correctly() {
let builder = GameBuilder::new();
let game = builder.build().await.unwrap();
let director = SceneDirector::new(
TestScene::new(),
game.services,
game.systems,
game.resources,
)
.await;
let runner = HeadlessRunner::new(director)
.with_tick_rate(Duration::from_millis(1))
.with_max_ticks(5);
runner.run().await.unwrap();
}
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
struct TestCommand {
value: u32,
}
impl Event for TestCommand {}
struct CommandProcessingScene {
received_commands: Vec<u32>,
}
impl CommandProcessingScene {
fn new() -> Self {
Self {
received_commands: Vec::new(),
}
}
}
#[async_trait::async_trait]
impl Scene for CommandProcessingScene {
async fn on_update(
&mut self,
_services: &ServiceContext,
_systems: &mut SystemContext,
resources: &mut ResourceContext,
) -> SceneTransition<Self> {
if let Some(mut event_bus) = resources.get_mut::<EventBus>().await {
let reader = event_bus.reader::<TestCommand>();
for event in reader.iter() {
self.received_commands.push(event.value);
}
}
SceneTransition::Stay
}
}
#[tokio::test]
async fn test_channel_headless_runner_receives_commands() {
let (tx, rx) = mpsc::channel(100);
let builder = GameBuilder::new();
let mut game = builder.build().await.unwrap();
game.resources.insert(EventBus::new());
let director = SceneDirector::new(
CommandProcessingScene::new(),
game.services,
game.systems,
game.resources,
)
.await;
let runner = HeadlessRunner::new(director)
.with_tick_rate(Duration::from_millis(10))
.with_max_ticks(5)
.with_command_channel(rx);
tx.send(TestCommand { value: 42 }).await.unwrap();
tx.send(TestCommand { value: 100 }).await.unwrap();
drop(tx);
runner.run().await.unwrap();
}
#[tokio::test]
async fn test_channel_headless_runner_with_no_commands() {
let (_tx, rx) = mpsc::channel(100);
let builder = GameBuilder::new();
let mut game = builder.build().await.unwrap();
game.resources.insert(EventBus::new());
let director = SceneDirector::new(
TestScene::new(),
game.services,
game.systems,
game.resources,
)
.await;
let runner = HeadlessRunner::new(director)
.with_tick_rate(Duration::from_millis(1))
.with_max_ticks(3)
.with_command_channel::<TestCommand>(rx);
runner.run().await.unwrap();
}
}