pub mod component;
pub mod config;
pub mod event;
mod metrics;
use log::debug;
use winit::{
application::ApplicationHandler,
dpi::PhysicalSize,
error::EventLoopError,
event::{
DeviceEvent,
DeviceId,
ElementState,
KeyEvent,
MouseButton,
WindowEvent,
},
event_loop::{ActiveEventLoop, ControlFlow, EventLoop},
window::WindowId,
};
use pkecs::{
app::Plugin,
ecs::{
event::{handler::into::IntoEventHandler, Events},
world::UnsafeWorldCell,
system::{
handler::into::IntoSystemHandler,
schedule::{Startup, Update, FixedUpdate, Shutdown},
Systems,
},
},
};
use component::{lifecycle::ApplicationLifecycle, time::GameTime, window::WindowContext};
use config::GraphicalApplicationOptions;
use event::{resize::Resized, key::KeyPress, mouse::MouseMotion};
use metrics::Metrics;
#[derive(Default)]
pub struct GraphicalApplication {
events: Events,
systems: Systems,
world: UnsafeWorldCell,
metrics: Metrics,
options: GraphicalApplicationOptions,
}
impl GraphicalApplication {
pub fn from_config(options: impl Into<GraphicalApplicationOptions>) -> Self {
let events = Events::default();
let systems = Systems::default();
let world = UnsafeWorldCell::default();
let metrics = Metrics::default();
let options = options.into();
Self { events, systems, world, metrics, options }
}
pub fn run(&mut self) {
debug!("Running application...");
self.run_inner()
.expect("Application failed unexpectedly!");
}
fn run_inner(&mut self) -> Result<(), EventLoopError> {
let event_loop = EventLoop::new()?;
event_loop.set_control_flow(ControlFlow::Poll);
event_loop.run_app(self)?;
Ok(())
}
fn close(&mut self, event_loop: &ActiveEventLoop) {
event_loop.exit();
self.systems.run(Shutdown, &self.world);
}
fn redraw(&mut self) {
self.systems.run(Update, &self.world);
self.metrics.measure();
if self.metrics.elapsed() > self.options.tick_rate {
self.systems.run(FixedUpdate, &self.world);
self.metrics.reset_elapsed();
}
self.time_measure();
self.window_redraw();
}
fn time_measure(&mut self) {
let world = self.world.get_mut();
let components = world.components_mut();
let mut time_query = components.query_mut::<GameTime>();
let time = time_query
.first_mut()
.expect("Failed to retrieve a temporal device while redrawing.");
time.delta = self.metrics.frame_time() as f32;
}
fn window_redraw(&self) {
let world = self.world.get();
let components = world.components();
let context_query = components.query::<WindowContext>();
let context = context_query
.first()
.expect("Failed to retrieve a window while redrawing.");
context.redraw();
}
fn resize(&mut self, width: u32, height: u32) {
self.options.with_size(width, height);
let world = self.world.get_mut();
let events = world.events_mut();
events.queue(Resized::from_size(width, height));
}
fn keyboard_input(&mut self, event: &KeyEvent) {
let Ok(key) = KeyPress::try_from(event) else {
return;
};
let world = self.world.get_mut();
let events = world.events_mut();
events.queue(key);
}
fn mouse_input(&mut self, state: &ElementState, button: &MouseButton) {
if !state.is_pressed() {
return;
}
let world = self.world.get_mut();
let events = world.events_mut();
events.queue(Into::<MouseButton>::into(*button));
}
fn mouse_motion(&mut self, delta_x: f64, delta_y: f64) {
let world = self.world.get_mut();
let events = world.events_mut();
events.queue(MouseMotion::from_delta(delta_x, delta_y));
}
pub fn add_system<S, F, P>(&mut self, schedule: S, system: F) -> &mut Self
where
S: 'static,
F: IntoSystemHandler<P> + 'static,
P: 'static,
{
self.systems.add(schedule, system);
self
}
pub fn handle_event<E, H, P>(&mut self, handler: H) -> &mut Self
where
E: 'static,
H: IntoEventHandler<E, P> + 'static,
P: 'static,
{
self.events.register(handler);
self
}
pub fn add_plugin<TPlugin>(&mut self, plugin: TPlugin) -> &mut Self
where
TPlugin: Plugin,
{
plugin.attach(&mut self.events, &mut self.systems);
self
}
}
impl ApplicationHandler for GraphicalApplication {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
debug!("Window resumed...");
let Ok(window) = event_loop.create_window(self.options.to_attr()) else {
panic!("Failed to initialize the window!");
};
let world = self.world.get_mut();
let components = world.components_mut();
components.spawn(WindowContext::from_window(window));
components.spawn(ApplicationLifecycle::default());
components.spawn(GameTime::default());
self.systems.run(Startup, &self.world);
let events = world.events_mut();
events.queue(self.options.to_resized());
self.events.consume(&self.world);
}
fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
match event {
WindowEvent::CloseRequested => self.close(event_loop),
WindowEvent::RedrawRequested => self.redraw(),
WindowEvent::Resized(PhysicalSize { width, height }) => self.resize(width, height),
WindowEvent::KeyboardInput { event, .. } => self.keyboard_input(&event),
WindowEvent::MouseInput { state, button, .. } => self.mouse_input(&state, &button),
_ => { },
};
let world = self.world.get();
let components = world.components();
let lifecycle = components.query::<ApplicationLifecycle>();
if lifecycle.iter().any(|l| l.is_exiting()) {
self.close(event_loop);
}
self.events.consume(&self.world);
}
fn device_event(&mut self, event_loop: &ActiveEventLoop, _id: DeviceId, event: DeviceEvent) {
match event {
DeviceEvent::MouseMotion { delta: (delta_x, delta_y) } => self.mouse_motion(delta_x, delta_y),
_ => { }
};
let world = self.world.get();
let components = world.components();
let lifecycle = components.query::<ApplicationLifecycle>();
if lifecycle.iter().any(|l| l.is_exiting()) {
self.close(event_loop);
}
self.events.consume(&self.world);
}
}