1use crate::{
2 Direction, GlobalState, PlayState, PlayStateResult,
3 menu::main::MainMenuState,
4 settings::get_fps,
5 ui,
6 window::{Event, EventLoop},
7};
8use common_base::{prof_span, span};
9use std::{mem, time::Duration};
10use tracing::debug;
11use winit::event_loop::ActiveEventLoop;
12
13pub fn run(
14 mut global_state: GlobalState,
15 event_loop: EventLoop,
16) -> Result<(), winit::error::EventLoopError> {
17 let mut states: Vec<Box<dyn PlayState>> = vec![Box::new(MainMenuState::new(&mut global_state))];
19 states.last_mut().map(|current_state| {
20 current_state.enter(&mut global_state, Direction::Forwards);
21 let current_state = current_state.name();
22 debug!(?current_state, "Started game with state");
23 });
24
25 let mut polled_twice = false;
30
31 let mut poll_span = None;
32 let mut event_span = None;
33
34 #[expect(deprecated)]
35 event_loop.run(move |event, event_loop| {
36 event_loop.set_control_flow(winit::event_loop::ControlFlow::Poll);
38
39 #[cfg(feature = "egui-ui")]
40 if let winit::event::Event::WindowEvent { event, .. } = &event {
41 let enabled_for_current_state = states.last().is_some_and(|state| state.egui_enabled());
42
43 if enabled_for_current_state && global_state.settings.interface.egui_enabled() {
45 let response = global_state
46 .egui_state
47 .winit_state
48 .on_window_event(global_state.window.window(), event);
49 if response.consumed {
50 return;
51 }
52 }
53 }
54
55 if !matches!(&event, winit::event::Event::WindowEvent {
60 event: winit::event::WindowEvent::Resized(_),
61 ..
62 }) {
63 let window = &mut global_state.window;
64 if let Some(event) = ui::Event::try_from(&event, window.window(), window.modifiers()) {
66 window.send_event(Event::Ui(event));
67 }
68 if let winit::event::Event::WindowEvent { event, .. } = &event
70 && let Some(event) =
71 ui::ice::window_event(event, window.scale_factor(), window.modifiers())
72 {
73 window.send_event(Event::IcedUi(event));
74 }
75 }
76
77 match event {
78 winit::event::Event::NewEvents(_) => {
79 event_span.take();
80 prof_span!(span, "Process Events");
81 event_span = Some(span);
82 },
83 winit::event::Event::AboutToWait => {
84 event_span.take();
85 poll_span.take();
86 if polled_twice {
87 handle_main_events_cleared(&mut states, event_loop, &mut global_state);
88 }
89 prof_span!(span, "Poll Winit");
90 poll_span = Some(span);
91 polled_twice = !polled_twice;
92 },
93 winit::event::Event::WindowEvent { event, .. } => {
94 span!(_guard, "Handle WindowEvent");
95
96 if let winit::event::WindowEvent::Focused(focused) = event {
97 global_state.audio.set_master_volume(if focused {
98 global_state.settings.audio.master_volume.get_checked()
99 } else {
100 global_state
101 .settings
102 .audio
103 .inactive_master_volume_perc
104 .get_checked()
105 * global_state.settings.audio.master_volume.get_checked()
106 });
107 }
108
109 global_state
110 .window
111 .handle_window_event(event, &mut global_state.settings)
112 },
113 winit::event::Event::DeviceEvent { event, .. } => {
114 span!(_guard, "Handle DeviceEvent");
115 global_state.window.handle_device_event(event)
116 },
117 winit::event::Event::LoopExiting => {
118 global_state
120 .settings
121 .save_to_file_warn(&global_state.config_dir);
122 global_state
123 .profile
124 .save_to_file_warn(&global_state.config_dir);
125 },
126 _ => {},
127 }
128 })
129}
130
131fn handle_main_events_cleared(
132 states: &mut Vec<Box<dyn PlayState>>,
133 event_loop: &ActiveEventLoop,
134 global_state: &mut GlobalState,
135) {
136 span!(guard, "Handle MainEventsCleared");
137 global_state
139 .window
140 .resolve_deduplicated_events(&mut global_state.settings, &global_state.config_dir);
141 let mut exit = true;
153 while let Some(state_result) = states.last_mut().map(|last| {
154 let events = global_state.window.fetch_events(&mut global_state.settings);
155 last.tick(global_state, events)
156 }) {
157 match state_result {
159 PlayStateResult::Continue => {
160 exit = false;
161 break;
162 },
163 PlayStateResult::Shutdown => {
164 #[cfg(feature = "discord")]
166 global_state.discord.clear_activity();
167
168 debug!("Shutting down all states...");
169 while states.last().is_some() {
170 states.pop().map(|old_state| {
171 debug!("Popped state '{}'.", old_state.name());
172 global_state.on_play_state_changed();
173 });
174 }
175 },
176 PlayStateResult::Pop => {
177 states.pop().map(|old_state| {
178 debug!("Popped state '{}'.", old_state.name());
179 global_state.on_play_state_changed();
180 });
181 states.last_mut().map(|new_state| {
182 new_state.enter(global_state, Direction::Backwards);
183 });
184 },
185 PlayStateResult::Push(mut new_state) => {
186 new_state.enter(global_state, Direction::Forwards);
187 debug!("Pushed state '{}'.", new_state.name());
188 states.push(new_state);
189 global_state.on_play_state_changed();
190 },
191 PlayStateResult::Switch(mut new_state) => {
192 new_state.enter(global_state, Direction::Forwards);
193 states.last_mut().map(|old_state| {
194 debug!(
195 "Switching to state '{}' from state '{}'.",
196 new_state.name(),
197 old_state.name()
198 );
199 mem::swap(old_state, &mut new_state);
200 global_state.on_play_state_changed();
201 });
202 },
203 }
204 }
205
206 if exit {
207 event_loop.exit();
208 }
209
210 let mut capped_fps = false;
211
212 drop(guard);
213
214 #[cfg(feature = "egui-ui")]
215 let scale_factor = global_state.window.scale_factor() as f32;
216
217 if let Some(last) = states.last_mut() {
218 capped_fps = last.capped_fps();
219
220 span!(guard, "Render");
221
222 #[cfg(feature = "egui-ui")]
223 let mut platform_output = None;
224
225 if let Some(mut drawer) = global_state
227 .window
228 .renderer_mut()
229 .start_recording_frame(last.globals_bind_group())
230 .expect("Unrecoverable render error when starting a new frame!")
231 {
232 if global_state.clear_shadows_next_frame {
233 drawer.clear_shadows();
234 }
235
236 last.render(&mut drawer, &global_state.settings);
237
238 #[cfg(feature = "egui-ui")]
239 if last.egui_enabled() && global_state.settings.interface.egui_enabled() {
240 platform_output =
241 Some(drawer.draw_egui(&mut global_state.egui_state.winit_state, scale_factor));
242 }
243 };
244
245 #[cfg(feature = "egui-ui")]
246 if let Some(output) = platform_output {
247 global_state
248 .egui_state
249 .winit_state
250 .handle_platform_output(global_state.window.window(), output);
251 }
252
253 if global_state.clear_shadows_next_frame {
254 global_state.clear_shadows_next_frame = false;
255 }
256
257 drop(guard);
258 }
259
260 if !exit {
261 span!(guard, "Main thread sleep");
263
264 let max_fps = get_fps(global_state.settings.graphics.max_fps);
268 let max_background_fps = u32::min(
269 max_fps,
270 get_fps(global_state.settings.graphics.max_background_fps),
271 );
272 let max_fps_focus_adjusted = if global_state.window.focused {
273 max_fps
274 } else {
275 max_background_fps
276 };
277
278 const TITLE_SCREEN_FPS_CAP: u32 = 60;
279
280 let target_fps = if capped_fps {
281 u32::min(TITLE_SCREEN_FPS_CAP, max_fps_focus_adjusted)
282 } else {
283 max_fps_focus_adjusted
284 };
285
286 global_state
287 .clock
288 .set_target_dt(Duration::from_secs_f64(1.0 / target_fps as f64));
289 global_state.clock.tick();
290 drop(guard);
291 #[cfg(feature = "tracy")]
292 common_base::tracy_client::frame_mark();
293
294 global_state.maintain();
296 }
297}