Skip to main content

oxide_engine/
app.rs

1//! Application trait and engine entry points.
2
3use 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    /// Returns the egui manager if the app integrates editor/debug UI.
81    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}