Skip to main content

pfx_game/
host.rs

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}