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}