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