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 global_state.egui_state.platform.handle_event(event);
46 if global_state.egui_state.platform.captures_event(event) {
47 return;
48 }
49 }
50 }
51
52 if !matches!(&event, winit::event::Event::WindowEvent {
57 event: winit::event::WindowEvent::Resized(_),
58 ..
59 }) {
60 let window = &mut global_state.window;
61 if let Some(event) = ui::Event::try_from(&event, window.window(), window.modifiers()) {
63 window.send_event(Event::Ui(event));
64 }
65 if let winit::event::Event::WindowEvent { event, .. } = &event {
67 if let Some(event) =
68 ui::ice::window_event(event, window.scale_factor(), window.modifiers())
69 {
70 window.send_event(Event::IcedUi(event));
71 }
72 }
73 }
74
75 match event {
76 winit::event::Event::NewEvents(_) => {
77 event_span.take();
78 prof_span!(span, "Process Events");
79 event_span = Some(span);
80 },
81 winit::event::Event::AboutToWait => {
82 event_span.take();
83 poll_span.take();
84 if polled_twice {
85 handle_main_events_cleared(&mut states, event_loop, &mut global_state);
86 }
87 prof_span!(span, "Poll Winit");
88 poll_span = Some(span);
89 polled_twice = !polled_twice;
90 },
91 winit::event::Event::WindowEvent { event, .. } => {
92 span!(_guard, "Handle WindowEvent");
93
94 if let winit::event::WindowEvent::Focused(focused) = event {
95 global_state.audio.set_master_volume(if focused {
96 global_state.settings.audio.master_volume.get_checked()
97 } else {
98 global_state
99 .settings
100 .audio
101 .inactive_master_volume_perc
102 .get_checked()
103 * global_state.settings.audio.master_volume.get_checked()
104 });
105 }
106
107 global_state
108 .window
109 .handle_window_event(event, &mut global_state.settings)
110 },
111 winit::event::Event::DeviceEvent { event, .. } => {
112 span!(_guard, "Handle DeviceEvent");
113 global_state.window.handle_device_event(event)
114 },
115 winit::event::Event::LoopExiting => {
116 global_state
118 .settings
119 .save_to_file_warn(&global_state.config_dir);
120 global_state
121 .profile
122 .save_to_file_warn(&global_state.config_dir);
123 },
124 _ => {},
125 }
126 })
127}
128
129fn handle_main_events_cleared(
130 states: &mut Vec<Box<dyn PlayState>>,
131 event_loop: &ActiveEventLoop,
132 global_state: &mut GlobalState,
133) {
134 span!(guard, "Handle MainEventsCleared");
135 global_state
137 .window
138 .resolve_deduplicated_events(&mut global_state.settings, &global_state.config_dir);
139 let mut exit = true;
151 while let Some(state_result) = states.last_mut().map(|last| {
152 let events = global_state.window.fetch_events(&mut global_state.settings);
153 last.tick(global_state, events)
154 }) {
155 match state_result {
157 PlayStateResult::Continue => {
158 exit = false;
159 break;
160 },
161 PlayStateResult::Shutdown => {
162 #[cfg(feature = "discord")]
164 global_state.discord.clear_activity();
165
166 debug!("Shutting down all states...");
167 while states.last().is_some() {
168 states.pop().map(|old_state| {
169 debug!("Popped state '{}'.", old_state.name());
170 global_state.on_play_state_changed();
171 });
172 }
173 },
174 PlayStateResult::Pop => {
175 states.pop().map(|old_state| {
176 debug!("Popped state '{}'.", old_state.name());
177 global_state.on_play_state_changed();
178 });
179 states.last_mut().map(|new_state| {
180 new_state.enter(global_state, Direction::Backwards);
181 });
182 },
183 PlayStateResult::Push(mut new_state) => {
184 new_state.enter(global_state, Direction::Forwards);
185 debug!("Pushed state '{}'.", new_state.name());
186 states.push(new_state);
187 global_state.on_play_state_changed();
188 },
189 PlayStateResult::Switch(mut new_state) => {
190 new_state.enter(global_state, Direction::Forwards);
191 states.last_mut().map(|old_state| {
192 debug!(
193 "Switching to state '{}' from state '{}'.",
194 new_state.name(),
195 old_state.name()
196 );
197 mem::swap(old_state, &mut new_state);
198 global_state.on_play_state_changed();
199 });
200 },
201 }
202 }
203
204 if exit {
205 event_loop.exit();
206 }
207
208 let mut capped_fps = false;
209
210 drop(guard);
211
212 #[cfg(feature = "egui-ui")]
213 let scale_factor = global_state.window.scale_factor() as f32;
214
215 if let Some(last) = states.last_mut() {
216 capped_fps = last.capped_fps();
217
218 span!(guard, "Render");
219
220 if let Some(mut drawer) = global_state
222 .window
223 .renderer_mut()
224 .start_recording_frame(last.globals_bind_group())
225 .expect("Unrecoverable render error when starting a new frame!")
226 {
227 if global_state.clear_shadows_next_frame {
228 drawer.clear_shadows();
229 }
230
231 last.render(&mut drawer, &global_state.settings);
232
233 #[cfg(feature = "egui-ui")]
234 if last.egui_enabled() && global_state.settings.interface.egui_enabled() {
235 drawer.draw_egui(&mut global_state.egui_state.platform, scale_factor);
236 }
237 };
238 if global_state.clear_shadows_next_frame {
239 global_state.clear_shadows_next_frame = false;
240 }
241
242 drop(guard);
243 }
244
245 if !exit {
246 span!(guard, "Main thread sleep");
248
249 let max_fps = get_fps(global_state.settings.graphics.max_fps);
253 let max_background_fps = u32::min(
254 max_fps,
255 get_fps(global_state.settings.graphics.max_background_fps),
256 );
257 let max_fps_focus_adjusted = if global_state.window.focused {
258 max_fps
259 } else {
260 max_background_fps
261 };
262
263 const TITLE_SCREEN_FPS_CAP: u32 = 60;
264
265 let target_fps = if capped_fps {
266 u32::min(TITLE_SCREEN_FPS_CAP, max_fps_focus_adjusted)
267 } else {
268 max_fps_focus_adjusted
269 };
270
271 global_state
272 .clock
273 .set_target_dt(Duration::from_secs_f64(1.0 / target_fps as f64));
274 global_state.clock.tick();
275 drop(guard);
276 #[cfg(feature = "tracy")]
277 common_base::tracy_client::frame_mark();
278
279 global_state.maintain();
281 }
282}