Skip to main content

pfx_play/
session.rs

1use std::collections::BTreeMap;
2use std::sync::Arc;
3
4use pfx_core::clock::Tick;
5use pfx_gpu::window::Viewport;
6use pfx_input::{Actions, BindingMap, Input, InputEvent, MotorCommand, Recording};
7use pfx_live::renderer::Renderer;
8use pfx_live::scene::{Applied, Override, Staged};
9use pfx_live::stats::FrameStats;
10use pfx_load::scene::{Reload, Scene, SceneDiff, SceneError};
11
12use crate::PlayError;
13use crate::game::{FrameTime, Game};
14use crate::hooks::{NoHooks, PlayHooks};
15use crate::live::{Edit, Pending, Stamped};
16use crate::present::{self, Presenter, Touched};
17use crate::rigs::Rigs;
18use crate::tunables::Tunables;
19use crate::ui::{Ui, UiFrame};
20use crate::world::{Exit, RumbleRequest, World};
21
22pub const MOTORS_KEPT: usize = 256;
23
24#[derive(Clone, Debug, PartialEq)]
25pub struct Options {
26    pub seed: u64,
27    pub audio_rate: u32,
28    pub audio_channels: u16,
29    pub max_ticks: u32,
30    pub record: bool,
31    pub replay: Option<Recording>,
32    pub edits: Vec<Stamped>,
33    pub tunables: Option<Tunables>,
34    pub rigs: Option<Rigs>,
35}
36
37impl Default for Options {
38    fn default() -> Self {
39        Self {
40            seed: 0,
41            audio_rate: 48_000,
42            audio_channels: 2,
43            max_ticks: 8,
44            record: false,
45            replay: None,
46            edits: Vec::new(),
47            tunables: None,
48            rigs: None,
49        }
50    }
51}
52
53pub struct PlaySession {
54    snapshot: Arc<Scene>,
55    overrides: BTreeMap<String, Override>,
56    game: Box<dyn Game>,
57    hooks: Box<dyn PlayHooks>,
58    world: World,
59    input: Input,
60    pending: Vec<InputEvent>,
61    replay: Option<(Recording, usize)>,
62    edits: Vec<Stamped>,
63    replay_edits: Option<(Vec<Stamped>, usize)>,
64    held: Pending,
65    latest: Option<Scene>,
66    errors: Vec<SceneError>,
67    max_ticks: u32,
68    presenter: Presenter,
69    motors: Vec<MotorCommand>,
70    stats: FrameStats,
71    #[cfg(feature = "dev-hud")]
72    hud: pfx_live::hud::Hud,
73}
74
75pub struct Stopped {
76    pub game: Box<dyn Game>,
77    pub hooks: Box<dyn PlayHooks>,
78    pub snapshot: Arc<Scene>,
79    pub scene: Scene,
80    pub diff: SceneDiff,
81    pub errors: Vec<SceneError>,
82    pub ticks: u64,
83    pub pending: Pending,
84    pub edits: Vec<Stamped>,
85    overrides: BTreeMap<String, Override>,
86    touched: Touched,
87}
88
89impl PlaySession {
90    pub fn play(scene: &Scene, staged: &Staged, game: Box<dyn Game>) -> Result<Self, PlayError> {
91        Self::play_with(scene, Some(staged), game, Options::default())
92    }
93
94    pub fn play_with(
95        scene: &Scene,
96        staged: Option<&Staged>,
97        game: Box<dyn Game>,
98        options: Options,
99    ) -> Result<Self, PlayError> {
100        Self::play_hooked(scene, staged, game, options, Box::new(NoHooks))
101    }
102
103    pub fn play_hooked(
104        scene: &Scene,
105        staged: Option<&Staged>,
106        mut game: Box<dyn Game>,
107        options: Options,
108        mut hooks: Box<dyn PlayHooks>,
109    ) -> Result<Self, PlayError> {
110        let snapshot = Arc::new(scene.clone());
111        let overrides = staged
112            .map(|staged| staged.overrides().clone())
113            .unwrap_or_default();
114        let tunables = match options.tunables {
115            Some(tunables) => tunables,
116            None => Tunables::of_scene(&snapshot.path).map_err(PlayError::Tunables)?,
117        };
118        let rigs = match options.rigs {
119            Some(rigs) => rigs,
120            None => Rigs::of_scene(&snapshot.path).map_err(PlayError::Rigs)?,
121        };
122        let mut world = World::new(
123            snapshot.clone(),
124            options.seed,
125            (options.audio_rate, options.audio_channels),
126            tunables,
127            rigs,
128        )?;
129        let actions = Actions::new(game.actions()).map_err(PlayError::Actions)?;
130        let step_us = (1_000_000.0 / world.clock().tick_rate()).round() as u32;
131        let mut input = Input::new(actions, step_us);
132        let replay = options.replay.map(|recording| {
133            input.begin_replay(&recording);
134            (recording, 0)
135        });
136        if options.record && replay.is_none() {
137            input.start_recording();
138        }
139        hooks.on_play(&snapshot);
140        game.start(&mut world);
141        world.render_audio();
142        Ok(Self {
143            snapshot,
144            overrides,
145            game,
146            hooks,
147            world,
148            input,
149            pending: Vec::new(),
150            replay,
151            edits: Vec::new(),
152            replay_edits: (!options.edits.is_empty()).then_some((options.edits, 0)),
153            held: Pending::default(),
154            latest: None,
155            errors: Vec::new(),
156            max_ticks: options.max_ticks.max(1),
157            stats: FrameStats::off(),
158            presenter: Presenter::default(),
159            motors: Vec::new(),
160            #[cfg(feature = "dev-hud")]
161            hud: pfx_live::hud::Hud::new(),
162        })
163    }
164
165    pub fn set_frame_stats(&mut self, stats: FrameStats) {
166        self.stats = stats;
167    }
168
169    pub fn advance(&mut self, real_seconds: f32) -> Tick {
170        self.stats.sim_begin();
171        let tick = self.advance_ticks(real_seconds);
172        self.stats.sim_end();
173        tick
174    }
175
176    fn advance_ticks(&mut self, real_seconds: f32) -> Tick {
177        if self.world.exit().is_some() {
178            return Tick::default();
179        }
180        let mut tick = self.world.clock_mut().advance(real_seconds);
181        tick.ticks = tick.ticks.min(self.max_ticks);
182        for ran in 0..tick.ticks {
183            if self.world.exit().is_some() {
184                tick.ticks = ran;
185                return tick;
186            }
187            self.run(&tick);
188        }
189        if self.world.exit().is_some() {
190            return tick;
191        }
192        self.world.fx.shake.step(tick.paced, 1.0);
193        self.world.apply_rig(tick.alpha);
194        self.frame(FrameTime {
195            alpha: tick.alpha,
196            seconds: tick.real,
197            tick,
198        });
199        tick
200    }
201
202    fn frame(&mut self, time: FrameTime) {
203        if let Err(error) = self.game.try_frame(&self.world, time) {
204            self.world.fail(error);
205        }
206    }
207
208    pub fn step(&mut self) -> bool {
209        if !self.paused() || self.world.exit().is_some() {
210            return false;
211        }
212        let dt = self.world.clock().tick_dt() as f32;
213        let tick = Tick {
214            ticks: 1,
215            dt,
216            paced: dt,
217            real: 0.0,
218            scale: 0.0,
219            alpha: 0.0,
220        };
221        self.stats.sim_begin();
222        self.run(&tick);
223        self.stats.sim_end();
224        if self.world.exit().is_none() {
225            self.world.apply_rig(1.0);
226            self.frame(FrameTime {
227                tick,
228                ..FrameTime::default()
229            });
230        }
231        true
232    }
233
234    fn run(&mut self, tick: &Tick) {
235        self.replay_due();
236        self.world.begin_tick();
237        match &mut self.replay {
238            Some((recording, at)) => {
239                if let Some(events) = recording.steps.get(*at) {
240                    for event in events {
241                        self.input.feed(event.clone());
242                    }
243                }
244                *at += 1;
245            }
246            None => {
247                for event in self.pending.drain(..) {
248                    self.input.feed(event);
249                }
250            }
251        }
252        let step = self.input.step();
253        if self.replay.is_none() {
254            self.motors.extend(step.motors);
255            let extra = self.motors.len().saturating_sub(MOTORS_KEPT);
256            self.motors.drain(..extra);
257        }
258        if let Err(error) = self.game.try_tick(&mut self.world, tick, &self.input) {
259            self.world.fail(error);
260        }
261        if self.world.exit().is_some() {
262            return;
263        }
264        for request in self.world.take_rumble() {
265            match request {
266                RumbleRequest::Active(rumble) => self.input.rumble(rumble),
267                RumbleRequest::Pad(pad, rumble) => self.input.rumble_pad(pad, rumble),
268            };
269        }
270        self.world.step_physics();
271        if !self.world.events().is_empty() {
272            let events = self.world.events().to_vec();
273            self.game.events(&mut self.world, &events);
274        }
275        self.world.step_animations();
276        if !self.world.animation_events().is_empty() {
277            let events = self.world.animation_events().to_vec();
278            self.game.animated(&mut self.world, &events);
279        }
280        self.world.step_rigs(&self.input);
281        self.world.render_audio();
282    }
283
284    fn replay_due(&mut self) {
285        let Some((edits, at)) = &mut self.replay_edits else {
286            return;
287        };
288        let now = self.world.ticks();
289        let mut due = Vec::new();
290        while let Some(stamped) = edits.get(*at) {
291            if stamped.tick > now {
292                break;
293            }
294            if stamped.tick == now {
295                due.push(stamped.edit.clone());
296            }
297            *at += 1;
298        }
299        for edit in due {
300            let _ = self.apply(edit);
301        }
302    }
303
304    fn apply(&mut self, edit: Edit) -> Result<(), String> {
305        let kept = self.world.apply(&edit)?;
306        self.edits.push(Stamped {
307            tick: self.world.ticks(),
308            edit,
309        });
310        self.held.hold(kept);
311        Ok(())
312    }
313
314    pub fn edit(&mut self, edit: Edit) -> Result<(), String> {
315        if self.replay_edits.is_some() {
316            return Err(format!(
317                "{}: this session replays recorded edits",
318                edit.key()
319            ));
320        }
321        self.apply(edit)
322    }
323
324    pub fn edits(&self) -> &[Stamped] {
325        &self.edits
326    }
327
328    pub fn pending(&self) -> &Pending {
329        &self.held
330    }
331
332    pub fn pause(&mut self) {
333        if !self.paused() {
334            self.world.clock_mut().pause();
335            self.hooks.on_pause();
336        }
337    }
338
339    pub fn resume(&mut self) {
340        if self.paused() {
341            self.world.clock_mut().resume();
342            self.hooks.on_resume();
343        }
344    }
345
346    pub fn paused(&self) -> bool {
347        self.world.clock().paused()
348    }
349
350    pub fn feed(&mut self, event: InputEvent) {
351        #[cfg(feature = "dev-hud")]
352        if self.hud.input(&event) {
353            return;
354        }
355        if self.replay.is_none() {
356            self.pending.push(event);
357        }
358    }
359
360    pub fn input(&self) -> &Input {
361        &self.input
362    }
363
364    pub fn recording(&self) -> Option<&Recording> {
365        self.input.recording()
366    }
367
368    pub fn take_motors(&mut self) -> Vec<MotorCommand> {
369        std::mem::take(&mut self.motors)
370    }
371
372    pub fn set_rumble_intensity(&mut self, percent: u8) {
373        self.input.set_rumble_intensity(percent);
374    }
375
376    pub fn set_bindings(&mut self, map: BindingMap) {
377        self.input.set_bindings(map);
378    }
379
380    pub fn set_viewport(&mut self, viewport: Option<Viewport>) {
381        self.input.set_viewport(viewport);
382    }
383
384    pub fn game(&self) -> &dyn Game {
385        self.game.as_ref()
386    }
387
388    pub fn game_mut(&mut self) -> &mut dyn Game {
389        self.game.as_mut()
390    }
391
392    pub fn ui(&mut self, frame: &UiFrame) -> Ui {
393        if self.world.exit().is_some() {
394            return Ui::default();
395        }
396        match self.game.try_ui(&self.world, frame) {
397            Ok(ui) => ui,
398            Err(error) => {
399                self.world.fail(error);
400                Ui::default()
401            }
402        }
403    }
404
405    pub fn exit(&self) -> Option<&Exit> {
406        self.world.exit()
407    }
408
409    pub fn set_display(&mut self, display: &pfx_gpu::screens::DisplaySettings) {
410        self.world.set_display(display);
411    }
412
413    pub fn take_recording(&mut self) -> Option<Recording> {
414        self.input.stop_recording()
415    }
416
417    pub fn queue(&mut self, reload: Result<Reload, SceneError>) {
418        match reload {
419            Ok(reload) => {
420                self.latest = Some(reload.scene);
421                self.errors.clear();
422            }
423            Err(error) => self.errors.push(error),
424        }
425    }
426
427    pub fn queued(&self) -> Option<&Scene> {
428        self.latest.as_ref()
429    }
430
431    pub fn snapshot(&self) -> &Scene {
432        &self.snapshot
433    }
434
435    pub fn world(&self) -> &World {
436        &self.world
437    }
438
439    pub fn world_mut(&mut self) -> &mut World {
440        &mut self.world
441    }
442
443    pub fn take_audio(&mut self) -> Vec<f32> {
444        self.world.sounds.take_audio()
445    }
446
447    pub fn time(&self) -> f32 {
448        let rate = self.world.clock().tick_rate();
449        let alpha = if self.paused() {
450            0.0
451        } else {
452            f64::from(self.world.clock().alpha())
453        };
454        ((self.world.ticks() as f64 + alpha) / rate) as f32
455    }
456
457    pub fn present<'a>(
458        &'a mut self,
459        staged: &'a Staged,
460        renderer: &mut Renderer,
461        aspect: f32,
462        seed: u32,
463    ) -> Result<present::PlayFrame<'a>, PlayError> {
464        let time = self.time();
465        self.world.set_view_aspect(aspect);
466        self.presenter
467            .present(&self.world, staged, renderer, aspect, time, seed)
468    }
469
470    #[cfg(feature = "dev-hud")]
471    pub fn hud(&self) -> &pfx_live::hud::Hud {
472        &self.hud
473    }
474
475    #[cfg(feature = "dev-hud")]
476    pub fn hud_mut(&mut self) -> &mut pfx_live::hud::Hud {
477        &mut self.hud
478    }
479
480    #[cfg(feature = "dev-hud")]
481    pub fn draw_hud_on(
482        &mut self,
483        renderer: &mut Renderer,
484        output: &pfx_gpu::wgpu::TextureView,
485        format: pfx_gpu::wgpu::TextureFormat,
486        size: [u32; 2],
487    ) {
488        let stats = pfx_live::hud::Hud::attach(renderer);
489        if !self.stats.enabled() {
490            self.stats = stats;
491        }
492        self.hud.draw_on(renderer, output, format, size);
493    }
494
495    #[cfg(not(feature = "dev-hud"))]
496    pub fn draw_hud_on(
497        &mut self,
498        _renderer: &mut Renderer,
499        _output: &pfx_gpu::wgpu::TextureView,
500        _format: pfx_gpu::wgpu::TextureFormat,
501        _size: [u32; 2],
502    ) {
503    }
504
505    pub fn stop(mut self) -> Stopped {
506        self.game.stop();
507        self.hooks.on_stop();
508        let ticks = self.world.ticks();
509        drop(self.world);
510        let scene = self
511            .latest
512            .take()
513            .unwrap_or_else(|| (*self.snapshot).clone());
514        Stopped {
515            diff: self.snapshot.diff(&scene),
516            game: self.game,
517            hooks: self.hooks,
518            snapshot: self.snapshot,
519            scene,
520            errors: self.errors,
521            ticks,
522            pending: self.held,
523            edits: self.edits,
524            overrides: self.overrides,
525            touched: self.presenter.touched(),
526        }
527    }
528}
529
530impl Stopped {
531    pub fn restore(
532        &self,
533        staged: &mut Staged,
534        renderer: &mut Renderer,
535    ) -> Result<Applied, PlayError> {
536        staged.clear_overrides();
537        for (object, change) in &self.overrides {
538            staged.set_override(object, *change);
539        }
540        let (width, height) = renderer.size();
541        renderer.set_lens(staged.lens(width as f32 / height.max(1) as f32));
542        present::untouch(&self.snapshot, self.touched, renderer)?;
543        if self.touched.posed {
544            renderer.set_poses(&[]).map_err(PlayError::Render)?;
545        }
546        if self.touched.sprites {
547            renderer
548                .set_surfaces(staged.surfaces())
549                .map_err(PlayError::Render)?;
550        }
551        present::cut(renderer)?;
552        staged
553            .apply(renderer, &self.scene, &self.diff)
554            .map_err(PlayError::Render)
555    }
556}