1use std::sync::Arc;
2
3use pfx_gpu::screens::{Display, SystemSource, apply_display, detect};
4use pfx_gpu::window::{self, Attention, Pace, Size, SystemClock};
5use pfx_gpu::{Gpu, WindowTarget, wgpu};
6use pfx_input::{Backend, InputEvent, LayoutSource, SteamPrecedence};
7use pfx_load::scene::Scene;
8use pfx_play::{Cursor, Game};
9use pfx_sound::{ChannelId, Level, Output, PcmSender};
10use winit::application::ApplicationHandler;
11use winit::dpi::{PhysicalPosition, PhysicalSize};
12use winit::event::{DeviceEvent, DeviceId, WindowEvent};
13use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
14use winit::window::{CursorGrabMode, Window, WindowId};
15
16use crate::config::{Config, Pads, report};
17use crate::cursor::{Pointer, nudge};
18use crate::driver::{Driver, Event, present_preference};
19use crate::painter::{Painter, Warmup};
20use crate::start;
21
22pub const AUDIO_SECONDS: f32 = 0.25;
23
24struct Sound {
25 _output: Output,
26 sender: PcmSender,
27}
28
29struct Live {
30 window: Arc<Window>,
31 target: WindowTarget,
32 painter: Painter,
33 driver: Driver,
34 sound: Option<Sound>,
35 minimized: bool,
36 warming: Option<Warmup>,
37 pointer: Pointer,
38 locked: bool,
39 at: [f32; 2],
40}
41
42struct App {
43 pending: Option<(Box<dyn Game>, Config, Option<Scene>)>,
44 live: Option<Live>,
45 clock: SystemClock,
46 error: Option<String>,
47 outcome: Option<Result<u8, String>>,
48}
49
50pub fn run<G: Game + 'static>(game: G, mut config: Config) -> Result<u8, String> {
51 let name = config.name.clone();
52 let crash = config.crash.take();
53 let outcome = play(Box::new(game), config);
54 if let Err(error) = &outcome {
55 report(&name, crash.as_ref(), error);
56 }
57 outcome
58}
59
60fn play(game: Box<dyn Game>, config: Config) -> Result<u8, String> {
61 pfx_gpu::refuse_window("the game window")?;
62 let scene = start::scene(&config)?;
63 let event_loop = EventLoop::new().map_err(|error| format!("the event loop: {error}"))?;
64 let mut app = App {
65 pending: Some((game, config, scene)),
66 live: None,
67 clock: SystemClock::new(),
68 error: None,
69 outcome: None,
70 };
71 event_loop
72 .run_app(&mut app)
73 .map_err(|error| format!("the event loop: {error}"))?;
74 if let Some(live) = app.live.take() {
75 app.outcome = Some(live.driver.finish());
76 }
77 match (app.error, app.outcome) {
78 (Some(error), _) => Err(error),
79 (None, Some(outcome)) => outcome,
80 (None, None) => Ok(0),
81 }
82}
83
84fn pads(config: &mut Config) -> Option<Box<dyn Backend>> {
85 match std::mem::replace(&mut config.pads, Pads::None) {
86 Pads::None => None,
87 Pads::Backend(backend) => Some(backend),
88 Pads::Gilrs => pfx_input::pads::Gilrs::new().ok().map(|gilrs| {
89 Box::new(gilrs.with_steam(SteamPrecedence::from_env())) as Box<dyn Backend>
90 }),
91 }
92}
93
94#[cfg(windows)]
95fn layout(config: &mut Config) -> Option<Box<dyn LayoutSource>> {
96 Some(
97 config
98 .layout
99 .take()
100 .unwrap_or_else(|| Box::new(pfx_input::WindowsLayout::new())),
101 )
102}
103
104#[cfg(not(windows))]
105fn layout(config: &mut Config) -> Option<Box<dyn LayoutSource>> {
106 config.layout.take()
107}
108
109fn sound(config: &Config) -> Option<Sound> {
110 if !config.sound {
111 return None;
112 }
113 let output = Output::open().ok()?;
114 let (_, sender) = output.pcm_stream(
115 ChannelId::EFFECTS,
116 output.channels(),
117 AUDIO_SECONDS,
118 Level::default(),
119 );
120 Some(Sound {
121 _output: output,
122 sender,
123 })
124}
125
126fn size_of(window: &Window) -> Size {
127 let size = window.inner_size();
128 Size {
129 width: size.width,
130 height: size.height,
131 }
132}
133
134fn open(
135 event_loop: &ActiveEventLoop,
136 game: Box<dyn Game>,
137 mut config: Config,
138 scene: Option<&Scene>,
139) -> Result<Live, String> {
140 let settings = start::settings(game.as_ref(), &config);
141 let device = config
142 .device
143 .unwrap_or_else(|| detect(&SystemSource).device);
144 let display = Display::new(device, settings.display.clone());
145 let windowed = display.settings().size;
146 let attributes = Window::default_attributes()
147 .with_title(config.window_title())
148 .with_inner_size(PhysicalSize::new(windowed.width, windowed.height));
149 let window = Arc::new(
150 event_loop
151 .create_window(attributes)
152 .map_err(|error| format!("the window: {error}"))?,
153 );
154 apply_display(&window, &display).map_err(|error| format!("the display mode: {error}"))?;
155 let preference = present_preference(settings.pacing.vrr, window::host(&window));
156 let (gpu, target) = pollster::block_on(Gpu::window_present(window.clone(), preference))?;
157 let size = size_of(&window);
158 let painter = Painter::new(gpu, scene, &config, size)?;
159 let sound = sound(&config);
160 let audio = sound.as_ref().map_or((48_000, 2), |sound| {
161 (sound.sender.rate(), sound.sender.channels())
162 });
163 let pads = pads(&mut config);
164 let mut driver = start::driver(
165 scene,
166 Some(&painter),
167 game,
168 &config,
169 device,
170 size,
171 audio,
172 pads,
173 )?;
174 driver.set_layout(layout(&mut config));
175 driver.feed(Event::Refresh(
176 window
177 .current_monitor()
178 .and_then(|monitor| window::refresh_millihertz(&monitor)),
179 ));
180 let pointer = Pointer::new(window.has_focus());
181 Ok(Live {
182 window,
183 target,
184 painter,
185 driver,
186 sound,
187 minimized: false,
188 warming: Some(Warmup::new(config.warm_budget)),
189 pointer,
190 locked: false,
191 at: [0.0; 2],
192 })
193}
194
195impl Live {
196 fn warm(&mut self, now_ns: u64) -> Result<(), String> {
197 let Some(warmup) = &mut self.warming else {
198 return Ok(());
199 };
200 let format = self.target.config.format;
201 if warmup.pass(&mut self.painter, &mut self.driver, format, now_ns)? {
202 self.warming = None;
203 }
204 Ok(())
205 }
206
207 fn frame(&mut self, now_ns: u64) -> Result<(), String> {
208 if self.warming.is_some() {
209 return self.warm(now_ns);
210 }
211 self.driver.update(now_ns);
212 self.cursor();
213 if let Some(window) = self.driver.take_warp() {
214 self.warp(window);
215 }
216 for notice in self.driver.take_notices() {
217 eprintln!("{notice}");
218 }
219 if let Some(display) = self.driver.take_display() {
220 apply_display(&self.window, display)
221 .map_err(|error| format!("the display mode: {error}"))?;
222 }
223 let gpu = self.painter.renderer().gpu();
224 if let Some(vrr) = self.driver.take_present() {
225 let preference = present_preference(vrr, window::host(&self.window));
226 self.target.set_present(gpu, &preference);
227 }
228 if self.driver.exit().is_some() {
229 return Ok(());
230 }
231 let texture = match self.target.surface.get_current_texture() {
232 wgpu::CurrentSurfaceTexture::Success(texture)
233 | wgpu::CurrentSurfaceTexture::Suboptimal(texture) => texture,
234 wgpu::CurrentSurfaceTexture::Lost | wgpu::CurrentSurfaceTexture::Outdated => {
235 self.target
236 .surface
237 .configure(&gpu.device, &self.target.config);
238 return Ok(());
239 }
240 wgpu::CurrentSurfaceTexture::Timeout | wgpu::CurrentSurfaceTexture::Occluded => {
241 return Ok(());
242 }
243 wgpu::CurrentSurfaceTexture::Validation => {
244 return Err("the window's surface: validation failed".to_string());
245 }
246 };
247 let view = texture.texture.create_view(&Default::default());
248 self.painter
249 .draw(&mut self.driver, &view, self.target.config.format)?;
250 self.window.pre_present_notify();
251 self.painter.renderer().gpu().queue.present(texture);
252 let audio = self.driver.take_audio();
253 if let Some(sound) = &self.sound {
254 sound.sender.push(&audio);
255 }
256 Ok(())
257 }
258
259 fn resized(&mut self, size: Size) {
260 if size.width > 0 && size.height > 0 {
261 self.target
262 .resize(self.painter.renderer().gpu(), size.width, size.height);
263 self.driver.feed(Event::Resized(size));
264 }
265 }
266
267 fn moved(&mut self) {
268 if let Some((monitor, position)) = window::placement(&self.window) {
269 self.driver.feed(Event::Moved { monitor, position });
270 }
271 let monitor = self.window.current_monitor();
272 self.driver.feed(Event::Monitor(
273 monitor.as_ref().and_then(|monitor| monitor.name()),
274 ));
275 self.driver.feed(Event::Refresh(
276 monitor.as_ref().and_then(window::refresh_millihertz),
277 ));
278 }
279
280 fn check_minimized(&mut self) {
281 let minimized = window::minimized(&self.window);
282 if minimized != self.minimized {
283 self.minimized = minimized;
284 self.driver
285 .feed(Event::Attention(Attention::Minimized(minimized)));
286 self.pointer.minimize(minimized);
287 self.cursor();
288 }
289 }
290
291 fn cursor(&mut self) {
292 let Some(cursor) = self.pointer.change(self.driver.cursor()) else {
293 return;
294 };
295 self.locked = false;
296 let grab = match cursor {
297 Cursor::Shown | Cursor::Hidden => self.window.set_cursor_grab(CursorGrabMode::None),
298 Cursor::Captured => self
299 .window
300 .set_cursor_grab(CursorGrabMode::Confined)
301 .or_else(|_| {
302 self.window
303 .set_cursor_grab(CursorGrabMode::Locked)
304 .map(|()| self.locked = true)
305 }),
306 };
307 self.window.set_cursor_visible(cursor == Cursor::Shown);
308 if let Err(error) = grab {
309 eprintln!("the pointer stays free: {error}");
310 }
311 }
312
313 fn warp(&mut self, window: [f32; 2]) {
314 self.at = window;
315 if self.locked {
316 return;
317 }
318 let position = PhysicalPosition::new(f64::from(window[0]), f64::from(window[1]));
319 if let Err(error) = self.window.set_cursor_position(position) {
320 eprintln!("the pointer stays where it is: {error}");
321 }
322 }
323
324 fn moved_locked(&mut self, delta: (f64, f64)) {
325 let scale = if cfg!(target_os = "macos") {
326 self.window.scale_factor()
327 } else {
328 1.0
329 };
330 self.at = nudge(
331 self.at,
332 [delta.0 * scale, delta.1 * scale],
333 size_of(&self.window),
334 );
335 self.driver
336 .feed(Event::Input(InputEvent::PointerMoved { window: self.at }));
337 }
338}
339
340impl ApplicationHandler for App {
341 fn resumed(&mut self, event_loop: &ActiveEventLoop) {
342 let Some((game, config, scene)) = self.pending.take() else {
343 return;
344 };
345 match open(event_loop, game, config, scene.as_ref()) {
346 Ok(live) => self.live = Some(live),
347 Err(error) => {
348 self.error = Some(error);
349 event_loop.exit();
350 }
351 }
352 }
353
354 fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
355 let Some(live) = &mut self.live else {
356 return;
357 };
358 if let Some(attention) = window::attention(&event) {
359 live.driver.feed(Event::Attention(attention));
360 }
361 pfx_input::winit::learn(live.driver.labels_mut(), &event);
362 if let Some(input) = pfx_input::winit::event(&event) {
363 live.driver.feed(Event::Input(input));
364 }
365 match event {
366 WindowEvent::CloseRequested => event_loop.exit(),
367 WindowEvent::Resized(size) => live.resized(Size {
368 width: size.width,
369 height: size.height,
370 }),
371 WindowEvent::Moved(_) => live.moved(),
372 WindowEvent::Focused(focused) => {
373 live.pointer.focus(focused);
374 live.cursor();
375 }
376 WindowEvent::CursorMoved { position, .. } => {
377 live.at = [position.x as f32, position.y as f32];
378 }
379 WindowEvent::RedrawRequested => {
380 let now = window::Clock::now_ns(&self.clock);
381 if let Err(error) = live.frame(now) {
382 self.error = Some(error);
383 event_loop.exit();
384 } else if live.driver.exit().is_some() {
385 event_loop.exit();
386 }
387 }
388 _ => {}
389 }
390 live.check_minimized();
391 }
392
393 fn device_event(&mut self, _event_loop: &ActiveEventLoop, _id: DeviceId, event: DeviceEvent) {
394 let Some(live) = &mut self.live else {
395 return;
396 };
397 if let DeviceEvent::MouseMotion { delta } = event
398 && live.locked
399 {
400 live.moved_locked(delta);
401 }
402 }
403
404 fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
405 let Some(live) = &mut self.live else {
406 return;
407 };
408 if live.warming.is_some() {
409 live.window.request_redraw();
410 event_loop.set_control_flow(ControlFlow::Poll);
411 return;
412 }
413 let flow = match live.driver.poll(&self.clock) {
414 Pace::Start { next_ns } => {
415 live.window.request_redraw();
416 next_ns.map_or(ControlFlow::Poll, |ns| {
417 ControlFlow::WaitUntil(self.clock.instant(ns))
418 })
419 }
420 Pace::Wait { until_ns } => ControlFlow::WaitUntil(self.clock.instant(until_ns)),
421 Pace::Paused => ControlFlow::Wait,
422 };
423 event_loop.set_control_flow(flow);
424 }
425
426 fn exiting(&mut self, _event_loop: &ActiveEventLoop) {
427 if let Some(live) = self.live.take() {
428 self.outcome = Some(live.driver.finish());
429 }
430 }
431}