1pub mod component;
4pub mod config;
5pub mod event;
6mod metrics;
7pub mod schedule;
8
9use log::debug;
10use winit::{
11 application::ApplicationHandler,
12 dpi::PhysicalSize,
13 error::EventLoopError,
14 event::{DeviceEvent, DeviceId, ElementState, KeyEvent, MouseButton, WindowEvent},
15 event_loop::{ActiveEventLoop, ControlFlow, EventLoop},
16 window::WindowId,
17};
18use pkecs::{
19 app::{Application, plugin::Plugin},
20 core::{
21 entity::{Entities},
22 event::{Events, EventHandler},
23 schedule::{Schedule, System},
24 },
25};
26use component::{lifecycle::ApplicationLifecycle, time::GameTime, window::WindowContext};
27use config::GraphicalApplicationOptions;
28use event::{resize::Resized, key::KeyPress, mouse::MouseMotion};
29use metrics::Metrics;
30use schedule::{Startup, Update, FixedUpdate, Shutdown};
31
32#[derive(Default)]
34pub struct GraphicalApplication {
35 entities: Entities,
36 events: Events,
37 schedule: Schedule,
38 options: GraphicalApplicationOptions,
39 metrics: Metrics,
40}
41
42impl GraphicalApplication {
43 pub fn from_config(options: impl Into<GraphicalApplicationOptions>) -> Self {
45 let entities = Entities::default();
46 let events = Events::default();
47 let schedule = Schedule::default();
48 let options = options.into();
49 let metrics = Metrics::default();
50
51 Self { schedule, entities, events, options, metrics }
52 }
53
54 pub fn run(&mut self) {
59 debug!("Running application...");
60
61 self.run_inner()
62 .expect("Application failed unexpectedly!");
63 }
64
65 fn run_inner(&mut self) -> Result<(), EventLoopError> {
69 let event_loop = EventLoop::new()?;
70 event_loop.set_control_flow(ControlFlow::Poll);
71 event_loop.run_app(self)?;
72
73 Ok(())
74 }
75
76 fn close(&mut self, event_loop: &ActiveEventLoop) {
80 event_loop.exit();
81
82 self.schedule.run_schedule(Shutdown, &mut self.entities, &mut self.events);
84 }
85
86 fn redraw(&mut self) {
92 self.schedule.run_schedule(Update, &mut self.entities, &mut self.events);
93
94 self.metrics.measure();
95 if self.metrics.elapsed() > self.options.tick_rate {
96 self.schedule.run_schedule(FixedUpdate, &mut self.entities, &mut self.events);
97 self.metrics.reset_elapsed();
98 }
99
100 self.time_measure();
101 self.window_redraw();
102 }
103
104 fn time_measure(&mut self) {
106 let mut time_query = self.entities.query_mut::<GameTime>();
107 let time = time_query
108 .first_mut()
109 .expect("Failed to retrieve a temporal device while redrawing.");
110
111 time.delta = self.metrics.frame_time() as f32;
112 }
113
114 fn window_redraw(&self) {
116 let context_query = self.entities.query::<WindowContext>();
117 let context = context_query
118 .first()
119 .expect("Failed to retrieve a window while redrawing.");
120
121 context.redraw();
122 }
123
124 fn resize(&mut self, width: u32, height: u32) {
126 self.options.with_size(width, height);
127
128 let resized = Resized::from_size(width, height);
129 self.events.trigger(&resized, &mut self.entities);
130 }
131
132 fn keyboard_input(&mut self, event: &KeyEvent) {
134 let Ok(key) = KeyPress::try_from(event) else {
135 return;
136 };
137
138 self.events.trigger(&key, &mut self.entities);
139 }
140
141 fn mouse_input(&mut self, state: &ElementState, button: &MouseButton) {
143 if !state.is_pressed() {
144 return;
145 }
146
147 let mouse = Into::<MouseButton>::into(*button);
148 self.events.trigger(&mouse, &mut self.entities);
149 }
150
151 fn mouse_motion(&mut self, delta_x: f64, delta_y: f64) {
153 let motion = MouseMotion::from_delta(delta_x, delta_y);
154 self.events.trigger(&motion, &mut self.entities)
155 }
156}
157
158impl ApplicationHandler for GraphicalApplication {
159 fn resumed(&mut self, event_loop: &ActiveEventLoop) {
160 debug!("Window resumed...");
161
162 let Ok(window) = event_loop.create_window(self.options.to_attr()) else {
163 panic!("Failed to initialize the window!");
164 };
165
166 let context = WindowContext::from_window(window);
167 self.entities.spawn(context);
168
169 let lifecycle = ApplicationLifecycle::default();
170 self.entities.spawn(lifecycle);
171
172 let time = GameTime::default();
173 self.entities.spawn(time);
174
175 self.schedule.run_schedule(Startup, &mut self.entities, &mut self.events);
176
177 self.events.trigger(&self.options.to_resized(), &mut self.entities);
179 }
180
181 fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
182 match event {
183 WindowEvent::CloseRequested => self.close(event_loop),
184 WindowEvent::RedrawRequested => self.redraw(),
185 WindowEvent::Resized(PhysicalSize { width, height }) => self.resize(width, height),
186 WindowEvent::KeyboardInput { event, .. } => self.keyboard_input(&event),
187 WindowEvent::MouseInput { state, button, .. } => self.mouse_input(&state, &button),
188 _ => { },
189 };
190
191 let lifecycle = self.entities.query::<ApplicationLifecycle>();
192 if lifecycle.iter().any(|l| l.is_exiting()) {
193 self.close(event_loop);
194 }
195 }
196
197 fn device_event(&mut self, event_loop: &ActiveEventLoop, _id: DeviceId, event: DeviceEvent) {
198 match event {
199 DeviceEvent::MouseMotion { delta: (delta_x, delta_y) } => self.mouse_motion(delta_x, delta_y),
200 _ => { }
201 };
202
203 let lifecycle = self.entities.query::<ApplicationLifecycle>();
204 if lifecycle.iter().any(|l| l.is_exiting()) {
205 self.close(event_loop);
206 }
207 }
208}
209
210impl Application for GraphicalApplication {
211 fn add_system<TSchedule, TSystem>(&mut self, schedule: TSchedule, system: TSystem) -> &mut Self
213 where
214 TSchedule: 'static,
215 TSystem: System + 'static,
216 {
217 self.schedule.add_system(schedule, system);
218
219 self
220 }
221
222 fn handle_event<TEvent, TEventHandler>(&mut self, handler: TEventHandler) -> &mut Self
223 where
224 TEvent: 'static,
225 TEventHandler: EventHandler<TEvent> + 'static,
226 {
227 self.events.register(handler);
228
229 self
230 }
231
232 fn add_plugin<TPlugin>(&mut self) -> &mut Self
233 where
234 TPlugin: Plugin,
235 {
236 TPlugin::attach(&mut self.events, &mut self.schedule);
237
238 self
239 }
240}