Skip to main content

diene_engine_core/
app.rs

1//! Application lifecycle and frame coordination.
2
3use std::error::Error as StdError;
4
5use common::{
6    logging::macros::{debug, error, info},
7    timer::Stopwatch,
8};
9use engine_renderer_api::{BoxedRenderer, RenderExtent, RendererFactory};
10use thiserror::Error;
11use winit::{
12    application::ApplicationHandler,
13    event::WindowEvent,
14    event_loop::{ActiveEventLoop, EventLoop},
15    window::WindowId,
16};
17
18mod windowing;
19
20use self::windowing::Window;
21pub use self::windowing::WindowError;
22
23/// Errors returned by application host lifecycle operations.
24#[derive(Debug, Error)]
25pub enum ApplicationHostError<E>
26where
27    E: StdError + Send + Sync + 'static,
28{
29    /// Renderer operation failed.
30    #[error("renderer failed: {0}")]
31    Renderer(#[source] E),
32
33    /// Window or event loop operation failed.
34    #[error("window failed: {0}")]
35    Window(#[from] WindowError),
36
37    /// Application host configuration is invalid.
38    #[error("application host build failed: {0}")]
39    Build(#[from] ApplicationHostBuildError),
40}
41
42/// Errors returned while building an [`ApplicationHost`].
43#[derive(Debug, Error)]
44#[non_exhaustive]
45pub enum ApplicationHostBuildError {
46    /// Application name cannot be empty or whitespace-only.
47    #[error("application name cannot be empty")]
48    EmptyName,
49}
50
51/// Drives the native window, event loop, and renderer for an application.
52#[derive(Debug)]
53pub struct ApplicationHost<F>
54where
55    F: RendererFactory,
56{
57    name: String,
58    renderer_factory: F,
59    renderer: Option<BoxedRenderer<F::Error>>,
60    window: Option<Window>,
61    error: Option<ApplicationHostError<F::Error>>,
62
63    #[allow(dead_code)]
64    stopwatch: common::timer::Stopwatch,
65}
66
67/// Configures an [`ApplicationHost`].
68#[derive(Debug)]
69pub struct ApplicationHostBuilder<F>
70where
71    F: RendererFactory,
72{
73    name: Option<String>,
74    renderer_factory: F,
75}
76
77impl<F> ApplicationHostBuilder<F>
78where
79    F: RendererFactory,
80{
81    /// Sets the human-readable application name.
82    #[must_use]
83    pub fn with_name(mut self, name: impl Into<String>) -> Self {
84        self.name = Some(name.into());
85        self
86    }
87
88    /// Builds the application host.
89    pub fn build(self) -> Result<ApplicationHost<F>, ApplicationHostError<F::Error>> {
90        let name = self.name.unwrap_or_else(|| "Untitled Application".to_owned());
91
92        if name.trim().is_empty() {
93            return Err(ApplicationHostBuildError::EmptyName.into());
94        }
95
96        debug!("[{}] building application host", name);
97
98        let timer = Stopwatch::new();
99
100        Ok(ApplicationHost {
101            name,
102            renderer_factory: self.renderer_factory,
103            renderer: None,
104            window: None,
105            error: None,
106            stopwatch: timer,
107        })
108    }
109}
110
111impl<F> ApplicationHost<F>
112where
113    F: RendererFactory,
114{
115    /// Creates a builder for configuring an [`ApplicationHost`].
116    pub fn builder(renderer_factory: F) -> ApplicationHostBuilder<F> {
117        ApplicationHostBuilder { name: None, renderer_factory }
118    }
119
120    /// Returns the human-readable application name.
121    pub fn name(&self) -> &str {
122        &self.name
123    }
124
125    /// Runs the application until the event loop exits.
126    pub fn run(mut self) -> Result<(), ApplicationHostError<F::Error>> {
127        info!("[{}] running application", self.name);
128
129        let event_loop = EventLoop::new().map_err(WindowError::from)?;
130
131        event_loop.run_app(&mut self).map_err(WindowError::from)?;
132
133        if let Some(error) = self.error.take() {
134            error!("[{}] application event loop exited with error: {}", self.name, error);
135            return Err(error);
136        }
137
138        info!("[{}] application event loop exited gracefully", self.name);
139
140        Ok(())
141    }
142
143    fn fail(&mut self, event_loop: &ActiveEventLoop, error: ApplicationHostError<F::Error>) {
144        self.error = Some(error);
145        event_loop.exit();
146    }
147
148    fn is_main_window(&self, window_id: WindowId) -> bool {
149        self.window.as_ref().is_some_and(|window| window.id() == window_id)
150    }
151
152    fn render_frame(&mut self, event_loop: &ActiveEventLoop) {
153        // WARN: This is only done for debug purposes. Be sure to remove it later!
154        if self.stopwatch.is_stopped() {
155            self.stopwatch.start();
156        }
157
158        let Some(renderer) = self.renderer.as_mut() else {
159            return;
160        };
161
162        if let Err(error) = renderer.prepare_frame().and_then(|()| renderer.render()) {
163            self.fail(event_loop, ApplicationHostError::Renderer(error));
164            return;
165        }
166
167        if let Some(window) = &self.window {
168            window.request_redraw();
169        }
170
171        let elapsed = self.stopwatch.elapsed().as_secs_f32();
172
173        if elapsed > 2.0 {
174            info!("{:.0} seconds elapsed, exitting gracefully...", elapsed);
175            event_loop.exit();
176        }
177    }
178
179    fn resize_renderer(&mut self, event_loop: &ActiveEventLoop, size: winit::dpi::PhysicalSize<u32>) {
180        let Some(renderer) = self.renderer.as_mut() else {
181            return;
182        };
183
184        let extent = RenderExtent::new(size.width, size.height);
185
186        if let Err(error) = renderer.resize(extent) {
187            self.fail(event_loop, ApplicationHostError::Renderer(error));
188        }
189    }
190}
191
192impl<F> ApplicationHandler for ApplicationHost<F>
193where
194    F: RendererFactory,
195{
196    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
197        if self.window.is_some() {
198            return;
199        }
200
201        let window = match Window::create(event_loop, &self.name) {
202            Ok(window) => {
203                info!("[{}] created application window (Id: {:?})", self.name, window.id());
204                window
205            }
206            Err(error) => {
207                self.fail(event_loop, ApplicationHostError::Window(error));
208                return;
209            }
210        };
211
212        let renderer = match self.renderer_factory.create_renderer(&window) {
213            Ok(renderer) => {
214                info!("[{}] created renderer", self.name);
215                renderer
216            }
217            Err(error) => {
218                self.fail(event_loop, ApplicationHostError::Renderer(error));
219                return;
220            }
221        };
222
223        window.request_redraw();
224
225        self.renderer = Some(renderer);
226        self.window = Some(window);
227    }
228
229    fn window_event(&mut self, event_loop: &ActiveEventLoop, window_id: WindowId, event: WindowEvent) {
230        if !self.is_main_window(window_id) {
231            return;
232        }
233
234        match event {
235            WindowEvent::CloseRequested => {
236                info!("[{}] close requested for window {:?}", self.name, window_id);
237                event_loop.exit();
238            }
239            WindowEvent::Resized(size) => {
240                self.resize_renderer(event_loop, size);
241            }
242            WindowEvent::RedrawRequested => {
243                self.render_frame(event_loop);
244            }
245            _ => {}
246        }
247    }
248}