use std::time::{Duration, Instant};
use lotus_proc_macros::Resource;
use wgpu::SurfaceError;
use winit::event_loop::ActiveEventLoop;
use super::{
text::text::TextHolder,
context::Context,
input::Input,
camera::camera2d::Camera2d,
managers::rendering::manager::RenderState,
ecs::{world::World, resource::{ResourceRef, ResourceRefMut}}
};
#[derive(Clone, Default, Debug, PartialEq)]
pub enum GameLoopState {
#[default]
Running,
Paused
}
pub struct GameLoop {
pub delta: Duration,
pub previous_time_of_last_run: Instant,
pub setup: fn(context: &mut Context),
pub update: fn(context: &mut Context),
pub frame_count: u32,
pub last_fps_update: Instant,
pub current_fps: u32
}
impl GameLoop {
pub fn new(setup: fn(context: &mut Context), update: fn(context: &mut Context)) -> Self {
return Self {
delta: Duration::from_secs_f32(1.0 / 60.0),
previous_time_of_last_run: Instant::now(),
setup,
update,
frame_count: 0,
last_fps_update: Instant::now(),
current_fps: 0
};
}
pub fn run(&mut self, context: &mut Context, event_loop: &ActiveEventLoop) {
let now: Instant = Instant::now();
self.delta = now - self.previous_time_of_last_run;
self.previous_time_of_last_run = now;
context.delta = self.get_delta_as_seconds();
context.commands.flush_commands(&mut context.world, &mut context.render_state);
context.world.synchronize_events(&context.render_state);
context.world.synchronize_animations_of_entities(context.delta);
context.world.synchronize_gravity_with_dynamic_bodies(&mut context.render_state, context.delta);
context.world.synchronize_transformations_with_collisions();
context.world.synchronize_camera_with_target(&mut context.render_state);
(self.update)(context);
self.render(&mut context.render_state, &mut context.world, event_loop);
let mut input: ResourceRefMut<'_, Input> = context.world.get_resource_mut::<Input>().unwrap();
input.update_hashes();
self.frame_count += 1;
let elapsed: Duration = self.last_fps_update.elapsed();
if elapsed >= Duration::from_secs(1) {
self.current_fps = self.frame_count;
self.frame_count = 0;
self.last_fps_update = Instant::now();
context.game_loop_listener.current_fps = self.current_fps;
}
if let Some(fps_cap) = context.game_loop_listener.fps_cap {
let target_delta: Duration = Duration::from_secs_f32(1.0 / fps_cap as f32);
let frame_time: Duration = Instant::now() - now;
if frame_time < target_delta {
std::thread::sleep(target_delta - frame_time);
}
}
}
pub fn render(&self, render_state: &mut RenderState, world: &mut World, event_loop: &ActiveEventLoop) {
match render_state.render(world) {
Ok(_) => {}
Err(
SurfaceError::Lost | SurfaceError::Outdated
) => {
let camera2d: ResourceRef<'_, Camera2d> = world.get_resource::<Camera2d>().unwrap();
let text_holder: ResourceRef<'_, TextHolder> = world.get_resource::<TextHolder>().unwrap();
render_state.resize(
render_state.physical_size.as_ref().unwrap().clone(),
&camera2d,
&text_holder.text_renderers
);
},
Err(
SurfaceError::OutOfMemory | SurfaceError::Other
) => {
log::error!("Application OOMKilled.");
event_loop.exit();
}
Err(SurfaceError::Timeout) => {
log::warn!("Surface Timeout.")
}
}
}
pub fn get_delta(&self) -> Duration {
return self.delta;
}
pub fn get_delta_as_seconds(&self) -> f32 {
return self.delta.as_secs_f32();
}
}
#[derive(Clone, Debug, Resource)]
pub struct GameLoopListener {
pub state: GameLoopState,
pub current_fps: u32,
pub fps_cap: Option<u32>
}
impl GameLoopListener {
pub fn new() -> Self {
return Self {
state: GameLoopState::Running,
current_fps: 60,
fps_cap: None
};
}
pub(crate) fn _pause(&mut self) {
self.state = GameLoopState::Paused;
}
pub(crate) fn _resume(&mut self) {
self.state = GameLoopState::Running;
}
pub fn fps_cap(&mut self, fps_cap: u32) {
self.fps_cap = Some(fps_cap);
}
}