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 let scale = global_state.window.window().scale_factor();
57 let size = global_state.window.window().inner_size().to_logical(scale);
58 global_state.settings.graphics.window.size = [size.width, size.height];
59 global_state.settings.graphics.window.maximised =
60 global_state.window.window().is_maximized();
61 if let Some(event) = ui::Event::try_from(&event, global_state.window.window()) {
63 global_state.window.send_event(Event::Ui(event));
64 }
65 if let winit::event::Event::WindowEvent { event, .. } = &event {
68 let window = &mut global_state.window;
69 if let Some(event) =
70 ui::ice::window_event(event, window.scale_factor(), window.modifiers())
71 {
72 window.send_event(Event::IcedUi(event));
73 }
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::MainEventsCleared => {
84 event_span.take();
85 poll_span.take();
86 if polled_twice {
87 handle_main_events_cleared(&mut states, control_flow, &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::LoopDestroyed => {
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 control_flow: &mut winit::event_loop::ControlFlow,
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();
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 *control_flow = winit::event_loop::ControlFlow::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.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 if let Some(mut drawer) = global_state
224 .window
225 .renderer_mut()
226 .start_recording_frame(last.globals_bind_group())
227 .expect("Unrecoverable render error when starting a new frame!")
228 {
229 if global_state.clear_shadows_next_frame {
230 drawer.clear_shadows();
231 }
232
233 last.render(&mut drawer, &global_state.settings);
234
235 #[cfg(feature = "egui-ui")]
236 if last.egui_enabled() && global_state.settings.interface.egui_enabled() {
237 drawer.draw_egui(&mut global_state.egui_state.platform, scale_factor);
238 }
239 };
240 if global_state.clear_shadows_next_frame {
241 global_state.clear_shadows_next_frame = false;
242 }
243
244 drop(guard);
245 }
246
247 if !exit {
248 span!(guard, "Main thread sleep");
250
251 let max_fps = get_fps(global_state.settings.graphics.max_fps);
255 let max_background_fps = u32::min(
256 max_fps,
257 get_fps(global_state.settings.graphics.max_background_fps),
258 );
259 let max_fps_focus_adjusted = if global_state.window.focused {
260 max_fps
261 } else {
262 max_background_fps
263 };
264
265 const TITLE_SCREEN_FPS_CAP: u32 = 60;
266
267 let target_fps = if capped_fps {
268 u32::min(TITLE_SCREEN_FPS_CAP, max_fps_focus_adjusted)
269 } else {
270 max_fps_focus_adjusted
271 };
272
273 global_state
274 .clock
275 .set_target_dt(Duration::from_secs_f64(1.0 / target_fps as f64));
276 global_state.clock.tick();
277 drop(guard);
278 #[cfg(feature = "tracy")]
279 common_base::tracy_client::frame_mark();
280
281 global_state.maintain();
283 }
284}