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