1use 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#[derive(Debug, Error)]
25pub enum ApplicationHostError<E>
26where
27 E: StdError + Send + Sync + 'static,
28{
29 #[error("renderer failed: {0}")]
31 Renderer(#[source] E),
32
33 #[error("window failed: {0}")]
35 Window(#[from] WindowError),
36
37 #[error("application host build failed: {0}")]
39 Build(#[from] ApplicationHostBuildError),
40}
41
42#[derive(Debug, Error)]
44#[non_exhaustive]
45pub enum ApplicationHostBuildError {
46 #[error("application name cannot be empty")]
48 EmptyName,
49}
50
51#[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#[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 #[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 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 pub fn builder(renderer_factory: F) -> ApplicationHostBuilder<F> {
117 ApplicationHostBuilder { name: None, renderer_factory }
118 }
119
120 pub fn name(&self) -> &str {
122 &self.name
123 }
124
125 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 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}