use crate::{
context::{ResourceContext, ServiceContext, SystemContext},
error::Result,
event::EventBus,
scene::{Scene, SceneDirector, SceneTransition},
ui::{input::poll_input, InputEvent, Tui},
};
use ratatui::Frame;
use std::{
future::Future,
pin::Pin,
time::{Duration, Instant},
};
pub struct GameRunner<S> {
director: SceneDirector<S>,
tick_rate: Duration,
}
impl<S: Scene> GameRunner<S> {
pub fn new(director: SceneDirector<S>) -> Self {
Self {
director,
tick_rate: Duration::from_millis(33),
}
}
pub fn with_tick_rate(mut self, tick_rate: Duration) -> Self {
self.tick_rate = tick_rate;
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<R, H>(mut self, tui: &mut Tui, mut render: R, mut on_input: H) -> Result<()>
where
R: FnMut(&mut Frame, &S, &ResourceContext),
H: for<'a> FnMut(
&'a mut S,
&'a ServiceContext,
&'a mut SystemContext,
&'a mut ResourceContext,
InputEvent,
) -> Pin<Box<dyn Future<Output = SceneTransition<S>> + 'a>>,
{
let mut last_tick = Instant::now();
loop {
tui.terminal().draw(|frame| {
if let Some(scene) = self.director.current() {
render(frame, scene, self.director.resources());
}
})?;
let timeout = self
.tick_rate
.checked_sub(last_tick.elapsed())
.unwrap_or(Duration::ZERO);
let input = poll_input(timeout)?;
if input != InputEvent::Other {
if let Some(transition) = self
.director
.with_current_async(|scene, services, systems, resources| {
on_input(scene, services, systems, resources, input)
})
.await
{
self.director.handle(transition).await?;
}
}
if last_tick.elapsed() >= self.tick_rate {
let transition = self.director.update().await;
self.director.handle(transition).await?;
self.update_systems().await;
last_tick = Instant::now();
}
if self.director.should_quit() || self.director.is_empty() {
break;
}
if let Some(mut event_bus) = self.director.resources_mut().get_mut::<EventBus>().await {
event_bus.dispatch();
}
}
Ok(())
}
}