1use std::marker::PhantomData;
4use std::sync::Arc;
5
6use winit::{
7 application::ApplicationHandler,
8 dpi::PhysicalPosition,
9 event::{ElementState, WindowEvent},
10 event_loop::{ActiveEventLoop, EventLoop},
11 window::WindowId,
12};
13
14use crate::asset::{AssetServerResource, GltfSceneAssets, MaterialAssets};
15use crate::ecs::{CommandQueue, IntoSystem, System, Time, WindowResource, World};
16use crate::event::{window_event_to_engine, EngineEvent};
17use crate::input::{KeyboardInput, MouseInput};
18use crate::render::RenderFrame;
19use crate::scene::{gltf_scene_spawn_system, PendingGltfSceneSpawns, SpawnedGltfScenes, transform_propagate_system};
20use crate::ui::{handle_egui_event, EguiManager};
21use crate::window::Window;
22use oxide_renderer::Renderer;
23
24#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
25pub struct PreUpdate;
26
27#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
28pub struct Update;
29
30#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
31pub struct PostUpdate;
32
33#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
34pub struct Render;
35
36#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
37pub enum AppStage {
38 PreUpdate,
39 Update,
40 PostUpdate,
41 Extract,
42 Prepare,
43}
44
45pub type StartupSystemFn = fn(&mut World, &Window);
46
47#[derive(Default)]
48struct RunnerSystems {
49 startup: Vec<StartupSystemFn>,
50 pre_update: Vec<System>,
51 update: Vec<System>,
52 post_update: Vec<System>,
53 extract: Vec<System>,
54 prepare: Vec<System>,
55}
56
57impl RunnerSystems {
58 fn run(stage_systems: &mut [System], world: &mut World) {
59 let mut commands = CommandQueue::new();
60 for system in stage_systems {
61 system.run(world, &mut commands);
62 }
63 commands.apply(world);
64 }
65}
66
67pub trait App: 'static {
68 fn configure(world: &mut World);
69 fn init(window: &Window, renderer: Renderer) -> Self;
70 fn world(&self) -> &World;
71 fn world_mut(&mut self) -> &mut World;
72
73 fn update(&mut self);
74 fn extract(&mut self) {}
75 fn prepare(&mut self) {}
76 fn queue(&mut self, _frame: &mut RenderFrame) {}
77
78 fn on_event(&mut self, event: EngineEvent);
79
80 fn egui_manager_mut(&mut self) -> Option<&mut EguiManager> {
82 None
83 }
84}
85
86pub trait Plugin<T: App> {
87 fn build(&self, app: &mut AppBuilder<T>);
88}
89
90pub trait PluginGroup<T: App> {
91 fn build(self, app: &mut AppBuilder<T>);
92}
93
94pub struct InputPlugin;
95
96impl<T: App> Plugin<T> for InputPlugin {
97 fn build(&self, app: &mut AppBuilder<T>) {
98 app.add_startup_system_mut(initialize_input_resources);
99 }
100}
101
102fn initialize_input_resources(world: &mut World, _window: &Window) {
103 if !world.contains_resource::<Time>() {
104 world.init_resource::<Time>();
105 }
106 if !world.contains_resource::<KeyboardInput>() {
107 world.init_resource::<KeyboardInput>();
108 }
109 if !world.contains_resource::<MouseInput>() {
110 world.init_resource::<MouseInput>();
111 }
112}
113
114pub struct TransformPlugin;
115
116impl<T: App> Plugin<T> for TransformPlugin {
117 fn build(&self, app: &mut AppBuilder<T>) {
118 app.add_system_mut(AppStage::PostUpdate, transform_propagate_system);
119 }
120}
121
122pub struct RenderPlugin;
123
124impl<T: App> Plugin<T> for RenderPlugin {
125 fn build(&self, app: &mut AppBuilder<T>) {
126 app.add_startup_system_mut(initialize_window_resource);
127 app.add_startup_system_mut(initialize_asset_resources);
128 app.add_system_mut(AppStage::PreUpdate, gltf_scene_spawn_system);
129 }
130}
131
132fn initialize_window_resource(world: &mut World, window: &Window) {
133 if !world.contains_resource::<WindowResource>() {
134 let size = window.size();
135 world.insert_resource(WindowResource::new(size.width, size.height));
136 }
137}
138
139fn initialize_asset_resources(world: &mut World, _window: &Window) {
140 if !world.contains_resource::<AssetServerResource>() {
141 world.insert_resource(AssetServerResource::default());
142 }
143 if !world.contains_resource::<MaterialAssets>() {
144 world.insert_resource(MaterialAssets::default());
145 }
146 if !world.contains_resource::<GltfSceneAssets>() {
147 world.insert_resource(GltfSceneAssets::default());
148 }
149 if !world.contains_resource::<PendingGltfSceneSpawns>() {
150 world.insert_resource(PendingGltfSceneSpawns::default());
151 }
152 if !world.contains_resource::<SpawnedGltfScenes>() {
153 world.insert_resource(SpawnedGltfScenes::default());
154 }
155}
156
157pub struct DefaultPlugins;
158
159impl<T: App> PluginGroup<T> for DefaultPlugins {
160 fn build(self, app: &mut AppBuilder<T>) {
161 app.add_plugin_mut(InputPlugin);
162 app.add_plugin_mut(TransformPlugin);
163 app.add_plugin_mut(RenderPlugin);
164 }
165}
166
167pub struct AppBuilder<T: App> {
168 systems: RunnerSystems,
169 _marker: PhantomData<T>,
170}
171
172impl<T: App> Default for AppBuilder<T> {
173 fn default() -> Self {
174 Self::new()
175 }
176}
177
178impl<T: App> AppBuilder<T> {
179 pub fn new() -> Self {
180 Self {
181 systems: RunnerSystems::default(),
182 _marker: PhantomData,
183 }
184 }
185
186 pub fn add_system<S, Marker>(mut self, stage: AppStage, system: S) -> Self
187 where
188 S: IntoSystem<Marker>,
189 {
190 self.add_system_mut(stage, system);
191 self
192 }
193
194 pub fn add_system_mut<S, Marker>(&mut self, stage: AppStage, system: S) -> &mut Self
195 where
196 S: IntoSystem<Marker>,
197 {
198 let system = system.into_system();
199 match stage {
200 AppStage::PreUpdate => self.systems.pre_update.push(system),
201 AppStage::Update => self.systems.update.push(system),
202 AppStage::PostUpdate => self.systems.post_update.push(system),
203 AppStage::Extract => self.systems.extract.push(system),
204 AppStage::Prepare => self.systems.prepare.push(system),
205 }
206 self
207 }
208
209 pub fn add_startup_system(mut self, system: StartupSystemFn) -> Self {
210 self.add_startup_system_mut(system);
211 self
212 }
213
214 pub fn add_startup_system_mut(&mut self, system: StartupSystemFn) -> &mut Self {
215 self.systems.startup.push(system);
216 self
217 }
218
219 pub fn add_plugin<P>(mut self, plugin: P) -> Self
220 where
221 P: Plugin<T>,
222 {
223 self.add_plugin_mut(plugin);
224 self
225 }
226
227 pub fn add_plugin_mut<P>(&mut self, plugin: P) -> &mut Self
228 where
229 P: Plugin<T>,
230 {
231 plugin.build(self);
232 self
233 }
234
235 pub fn add_plugins<G>(mut self, plugins: G) -> Self
236 where
237 G: PluginGroup<T>,
238 {
239 plugins.build(&mut self);
240 self
241 }
242
243 pub fn run(self) {
244 let runner = AppRunner::<T>::with_systems(self.systems);
245 runner.run();
246 }
247}
248
249pub struct AppRunner<T: App> {
250 app: Option<T>,
251 window: Option<Window>,
252 systems: RunnerSystems,
253 startup_ran: bool,
254}
255
256impl<T: App> Default for AppRunner<T> {
257 fn default() -> Self {
258 Self::new()
259 }
260}
261
262impl<T: App> AppRunner<T> {
263 pub fn new() -> Self {
264 Self::with_systems(RunnerSystems::default())
265 }
266
267 fn with_systems(systems: RunnerSystems) -> Self {
268 Self {
269 app: None,
270 window: None,
271 systems,
272 startup_ran: false,
273 }
274 }
275
276 pub fn run(mut self) {
277 let event_loop = EventLoop::new().expect("Failed to create event loop");
278 event_loop
279 .run_app(&mut self)
280 .expect("Failed to run event loop");
281 }
282
283 fn run_startup_systems(&mut self) {
284 if self.startup_ran {
285 return;
286 }
287
288 if let (Some(app), Some(window)) = (self.app.as_mut(), self.window.as_ref()) {
289 for startup in &self.systems.startup {
290 startup(app.world_mut(), window);
291 }
292 self.startup_ran = true;
293 }
294 }
295}
296
297impl<T: App> ApplicationHandler for AppRunner<T> {
298 fn resumed(&mut self, event_loop: &ActiveEventLoop) {
299 if self.window.is_none() {
300 let window = Window::new(event_loop, "Oxide Core", 1280, 720);
301 let renderer = pollster::block_on(create_renderer(&window));
302 let app = T::init(&window, renderer);
303
304 self.app = Some(app);
305 self.window = Some(window);
306 self.run_startup_systems();
307 }
308 }
309
310 fn window_event(
311 &mut self,
312 event_loop: &ActiveEventLoop,
313 _window_id: WindowId,
314 event: WindowEvent,
315 ) {
316 let mut ui_consumed = false;
317 let mut ui_blocks_game_input = false;
318
319 if let (Some(app), Some(window)) = (self.app.as_mut(), self.window.as_ref()) {
320 if let Some(egui_manager) = app.egui_manager_mut() {
321 ui_consumed = handle_egui_event(egui_manager, window.winit_window(), &event);
322 ui_blocks_game_input =
323 egui_manager.wants_pointer_input() || egui_manager.wants_keyboard_input();
324 }
325 }
326
327 if ui_consumed {
328 return;
329 }
330
331 if let Some(app) = self.app.as_mut() {
332 if let Some(engine_event) = window_event_to_engine(&event) {
333 app.on_event(engine_event);
334 }
335 }
336
337 match event {
338 WindowEvent::CloseRequested => {
339 event_loop.exit();
340 }
341 WindowEvent::RedrawRequested => {
342 if let Some(app) = self.app.as_mut() {
343 {
344 let time = app.world_mut().resource_mut::<Time>();
345 time.update();
346 }
347 {
348 let keyboard = app.world_mut().resource_mut::<KeyboardInput>();
349 keyboard.update();
350 }
351
352 RunnerSystems::run(&mut self.systems.pre_update, app.world_mut());
353 app.update();
354 RunnerSystems::run(&mut self.systems.update, app.world_mut());
355 RunnerSystems::run(&mut self.systems.post_update, app.world_mut());
356
357 app.extract();
358 RunnerSystems::run(&mut self.systems.extract, app.world_mut());
359
360 app.prepare();
361 RunnerSystems::run(&mut self.systems.prepare, app.world_mut());
362
363 let frame_parts = {
364 let renderer = &app
365 .world()
366 .resource::<crate::ecs::RendererResource>()
367 .renderer;
368 match renderer.begin_frame() {
369 Ok(surface_texture) => Some((
370 surface_texture,
371 Arc::clone(&renderer.device),
372 Arc::clone(&renderer.queue),
373 )),
374 Err(err) => {
375 tracing::warn!("Skipping render frame: {err}");
376 None
377 }
378 }
379 };
380
381 if let Some((surface_texture, device, queue)) = frame_parts {
382 let mut frame = RenderFrame::new(&device, surface_texture);
383 app.queue(&mut frame);
384 frame.present(&queue);
385 }
386
387 {
388 let mouse = app.world_mut().resource_mut::<MouseInput>();
389 mouse.update();
390 }
391 }
392 }
393 WindowEvent::KeyboardInput { event, .. } => {
394 if ui_blocks_game_input {
395 return;
396 }
397
398 if let Some(app) = self.app.as_mut() {
399 let keyboard = app.world_mut().resource_mut::<KeyboardInput>();
400 let pressed = event.state == ElementState::Pressed;
401 keyboard.process_event(event.physical_key, pressed);
402 }
403 }
404 WindowEvent::MouseInput { state, button, .. } => {
405 if ui_blocks_game_input {
406 return;
407 }
408
409 if let Some(app) = self.app.as_mut() {
410 let mouse = app.world_mut().resource_mut::<MouseInput>();
411 let pressed = state == ElementState::Pressed;
412 mouse.process_button(button.into(), pressed);
413 }
414 }
415 WindowEvent::CursorEntered { .. } => {
416 if ui_blocks_game_input {
417 return;
418 }
419
420 if let (Some(app), Some(window)) = (self.app.as_mut(), self.window.as_ref()) {
421 let size = window.size();
422 let center =
423 PhysicalPosition::new(size.width as f64 * 0.5, size.height as f64 * 0.5);
424
425 if let Err(err) = window.set_cursor_position(center) {
426 tracing::warn!("Failed to recenter cursor: {err}");
427 }
428
429 let mouse = app.world_mut().resource_mut::<MouseInput>();
430 mouse.set_position(center);
431 }
432 }
433 WindowEvent::CursorMoved { position, .. } => {
434 if ui_blocks_game_input {
435 return;
436 }
437
438 if let Some(app) = self.app.as_mut() {
439 let mouse = app.world_mut().resource_mut::<MouseInput>();
440 mouse.process_move(position);
441 }
442 }
443 _ => {}
444 }
445 }
446
447 fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
448 if let Some(window) = &self.window {
449 window.request_redraw();
450 }
451 }
452}
453
454pub fn app<T: App>() -> AppBuilder<T> {
455 AppBuilder::new()
456}
457
458pub fn run_app<T: App>() {
459 AppBuilder::<T>::new().run();
460}
461
462pub async fn create_renderer(window: &Window) -> Renderer {
463 Renderer::new(window.winit_window().clone())
464 .await
465 .expect("Failed to create renderer")
466}