1use std::collections::HashMap;
2pub use winit::error::OsError;
3use winit::event_loop::ActiveEventLoop;
4use winit::window::WindowId;
5
6use astrelis_core::profiling::{profile_function, profile_scope};
7
8use crate::{
9 event::{Event, EventBatch, EventQueue, HandleStatus},
10 time::{FrameTime, TimeTracker},
11 window::{Window, WindowDescriptor},
12};
13
14pub use crate::time::FrameTime as Time;
16
17struct WindowResources {
18 events: EventQueue,
19 scale_factor: f64,
20}
21
22pub struct AppCtx<'a> {
23 event_loop: &'a ActiveEventLoop,
24 windows: &'a mut HashMap<WindowId, WindowResources>,
25}
26
27impl AppCtx<'_> {
28 pub fn create_window(&mut self, descriptor: WindowDescriptor) -> Result<Window, OsError> {
29 let window = Window::new(self.event_loop, descriptor)?;
30
31 self.windows.insert(
32 window.id(),
33 WindowResources {
34 events: EventQueue::new(),
35 scale_factor: window.scale_factor_f64(),
36 },
37 );
38
39 Ok(window)
40 }
41
42 pub fn exit(&self) {
43 self.event_loop.exit();
44 }
45}
46
47pub trait App {
48 #[allow(unused_variables)]
52 fn on_start(&mut self, ctx: &mut AppCtx) {}
53
54 #[allow(unused_variables)]
58 fn begin_frame(&mut self, ctx: &mut AppCtx, time: &FrameTime) {}
59
60 #[allow(unused_variables)]
65 fn update(&mut self, ctx: &mut AppCtx, time: &FrameTime) {}
66
67 #[allow(unused_variables)]
76 fn fixed_update(&mut self, ctx: &mut AppCtx, fixed_dt: f32) {}
77
78 #[allow(unused_variables)]
82 fn end_frame(&mut self, ctx: &mut AppCtx, time: &FrameTime) {}
83
84 fn render(&mut self, ctx: &mut AppCtx, window_id: WindowId, events: &mut EventBatch);
86
87 #[allow(unused_variables)]
91 fn on_exit(&mut self, ctx: &mut AppCtx) {}
92}
93
94pub type AppFactory = fn(ctx: &mut AppCtx) -> Box<dyn App>;
95
96struct AppProxy {
97 factory: AppFactory,
98 app: Option<Box<dyn App>>,
99 update_called_this_frame: bool,
100 windows: HashMap<WindowId, WindowResources>,
101 time_tracker: TimeTracker,
102 started: bool,
103 current_frame_time: Option<FrameTime>,
104}
105
106impl winit::application::ApplicationHandler for AppProxy {
107 fn resumed(&mut self, _event_loop: &ActiveEventLoop) {
108 profile_function!();
109 if self.app.is_none() {
110 let mut ctx = AppCtx {
111 event_loop: _event_loop,
112 windows: &mut self.windows,
113 };
114 let mut app = (self.factory)(&mut ctx);
115
116 app.on_start(&mut ctx);
118 self.started = true;
119
120 self.app = Some(app);
121 }
122 }
123
124 fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
125 profile_function!();
126 if let (Some(app), Some(frame_time)) = (&mut self.app, &self.current_frame_time) {
128 let mut ctx = AppCtx {
129 event_loop,
130 windows: &mut self.windows,
131 };
132 app.end_frame(&mut ctx, frame_time);
133 }
134
135 self.update_called_this_frame = false;
137 self.current_frame_time = None;
138 }
139
140 fn window_event(
141 &mut self,
142 event_loop: &ActiveEventLoop,
143 window_id: winit::window::WindowId,
144 event: winit::event::WindowEvent,
145 ) {
146 profile_function!();
147 use winit::event::WindowEvent;
148
149 let _app = match &mut self.app {
150 Some(app) => app,
151 None => return,
152 };
153
154 let mut ctx = AppCtx {
155 event_loop,
156 windows: &mut self.windows,
157 };
158
159 match event {
160 WindowEvent::RedrawRequested => {
161 let app = self.app.as_mut().unwrap();
162
163 if !self.update_called_this_frame {
165 let frame_time = self.time_tracker.tick();
167
168 {
170 profile_scope!("update");
171 app.begin_frame(&mut ctx, &frame_time);
172 app.update(&mut ctx, &frame_time);
173 }
174 self.current_frame_time = Some(frame_time);
178 self.update_called_this_frame = true;
179 }
180
181 let window = ctx.windows.get_mut(&window_id).unwrap();
183 let mut events = window.events.drain();
184
185 {
186 profile_scope!("render");
187 app.render(&mut ctx, window_id, &mut events);
188 }
189
190 events.dispatch(|event| match event {
192 Event::CloseRequested => {
193 tracing::info!("Close requested for window {:?}", window_id);
194
195 if let Some(app) = self.app.as_mut() {
197 app.on_exit(&mut ctx);
198 }
199
200 ctx.event_loop.exit();
201 HandleStatus::consumed()
202 }
203 _ => HandleStatus::ignored(),
204 });
205 }
206 event => {
207 let window = self.windows.get_mut(&window_id).unwrap();
208 if let WindowEvent::ScaleFactorChanged { scale_factor, .. } = event {
209 window.scale_factor = scale_factor;
210 }
211 let astrelis_event = match Event::from_winit(event, window.scale_factor) {
212 Some(event) => event,
213 None => return,
214 };
215
216 window.events.push(astrelis_event);
217 }
218 }
219 }
220}
221
222pub fn run_app(factory: AppFactory) {
224 use winit::event_loop::{ControlFlow, EventLoop};
225 let event_loop = EventLoop::new().expect("failed to create event loop");
226 event_loop.set_control_flow(ControlFlow::Wait);
227 let mut app_proxy = AppProxy {
228 factory,
229 app: None,
230 update_called_this_frame: false,
231 windows: HashMap::new(),
232 time_tracker: TimeTracker::new(),
233 started: false,
234 current_frame_time: None,
235 };
236 event_loop
237 .run_app(&mut app_proxy)
238 .expect("failed to run app");
239}