1use std::any::{Any, TypeId};
4use std::collections::{HashMap, HashSet};
5use std::path::PathBuf;
6use std::time::{Duration, Instant};
7
8use crate::audio::{Audio, Sound};
9use crate::error::Result;
10use crate::input::{Input, Key, KeyEvent};
11use crate::physics::{BodyId, World};
12use crate::shortcuts::Attached;
13use crate::speech::Speech;
14use crate::storage::Storage;
15use crate::time::Time;
16use crate::ui::{Ui, UiEvent};
17
18pub trait Scene: 'static {
24 fn start(&mut self, ctx: &mut Context) {
26 let _ = ctx;
27 }
28
29 fn update(&mut self, ctx: &mut Context);
31
32 fn fixed_update(&mut self, ctx: &mut Context) {
35 let _ = ctx;
36 }
37
38 fn on_ui(&mut self, event: &UiEvent, ctx: &mut Context) {
41 let _ = (event, ctx);
42 }
43
44 fn pause(&mut self, ctx: &mut Context) {
46 let _ = ctx;
47 }
48
49 fn resume(&mut self, ctx: &mut Context) {
51 let _ = ctx;
52 }
53
54 fn end(&mut self, ctx: &mut Context) {
56 let _ = ctx;
57 }
58}
59
60enum SceneChange {
61 Push(Box<dyn Scene>),
62 Pop,
63 Switch(Box<dyn Scene>),
64}
65
66pub struct Context {
68 pub time: Time,
70 pub input: Input,
72 pub audio: Audio,
74 pub speech: Speech,
76 pub ui: Ui,
78 pub physics: World,
80 pub storage: Storage,
82 pub(crate) follow: Option<BodyId>,
83 pub(crate) attached: Vec<Attached>,
84 pub(crate) engine_sounds: Vec<(BodyId, Sound)>,
85 pub(crate) once: HashSet<String>,
86 title: String,
87 quit: bool,
88 change: Option<SceneChange>,
89 globals: HashMap<TypeId, Box<dyn Any>>,
90}
91
92impl std::fmt::Debug for Context {
93 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
94 f.debug_struct("Context").field("title", &self.title).field("time", &self.time).finish_non_exhaustive()
95 }
96}
97
98impl Context {
99 pub fn quit(&mut self) {
101 self.quit = true;
102 }
103
104 pub fn is_quitting(&self) -> bool {
106 self.quit
107 }
108
109 pub fn title(&self) -> &str {
111 &self.title
112 }
113
114 pub fn switch_scene(&mut self, scene: impl Scene) {
116 self.change = Some(SceneChange::Switch(Box::new(scene)));
117 }
118
119 pub fn push_scene(&mut self, scene: impl Scene) {
121 self.change = Some(SceneChange::Push(Box::new(scene)));
122 }
123
124 pub fn pop_scene(&mut self) {
126 self.change = Some(SceneChange::Pop);
127 }
128
129 pub fn insert<T: 'static>(&mut self, value: T) {
132 self.globals.insert(TypeId::of::<T>(), Box::new(value));
133 }
134
135 pub fn get<T: 'static>(&self) -> Option<&T> {
137 self.globals.get(&TypeId::of::<T>()).and_then(|b| b.downcast_ref())
138 }
139
140 pub fn get_mut<T: 'static>(&mut self) -> Option<&mut T> {
142 self.globals.get_mut(&TypeId::of::<T>()).and_then(|b| b.downcast_mut())
143 }
144
145 pub fn get_or_default<T: Default + 'static>(&mut self) -> &mut T {
147 self.globals
148 .entry(TypeId::of::<T>())
149 .or_insert_with(|| Box::new(T::default()))
150 .downcast_mut()
151 .expect("type id matches")
152 }
153
154 pub fn remove<T: 'static>(&mut self) -> Option<T> {
156 self.globals.remove(&TypeId::of::<T>()).and_then(|b| b.downcast().ok()).map(|b| *b)
157 }
158}
159
160#[derive(Debug, Clone)]
187pub struct Engine {
188 pub(crate) title: String,
189 pub(crate) fps: u32,
190 pub(crate) physics_rate: u32,
191 pub(crate) sound_dir: PathBuf,
192 pub(crate) echo_speech: bool,
193 pub(crate) window_size: (u32, u32),
194 pub(crate) headless: bool,
195 pub(crate) gravity: glam::Vec3,
196 pub(crate) save_file: Option<PathBuf>,
197 pub(crate) save_secret: Option<String>,
198}
199
200impl Engine {
201 pub fn new(title: impl Into<String>) -> Self {
204 Self {
205 title: title.into(),
206 fps: 60,
207 physics_rate: 60,
208 sound_dir: PathBuf::from("sounds"),
209 echo_speech: false,
210 window_size: (640, 480),
211 headless: false,
212 gravity: glam::Vec3::ZERO,
213 save_file: None,
214 save_secret: None,
215 }
216 }
217
218 pub fn fps(mut self, fps: u32) -> Self {
220 self.fps = fps.clamp(1, 1000);
221 self
222 }
223
224 pub fn physics_rate(mut self, rate: u32) -> Self {
226 self.physics_rate = rate.clamp(1, 1000);
227 self
228 }
229
230 pub fn gravity(mut self, gravity: glam::Vec3) -> Self {
233 self.gravity = gravity;
234 self
235 }
236
237 pub fn save_file(mut self, path: impl Into<PathBuf>) -> Self {
239 self.save_file = Some(path.into());
240 self
241 }
242
243 pub fn encrypt_save(mut self, secret: impl Into<String>) -> Self {
248 self.save_secret = Some(secret.into());
249 self
250 }
251
252 pub fn sound_dir(mut self, dir: impl Into<PathBuf>) -> Self {
254 self.sound_dir = dir.into();
255 self
256 }
257
258 pub fn echo_speech(mut self, echo: bool) -> Self {
260 self.echo_speech = echo;
261 self
262 }
263
264 pub fn window_size(mut self, width: u32, height: u32) -> Self {
267 self.window_size = (width, height);
268 self
269 }
270
271 pub fn headless(mut self) -> Self {
274 self.headless = true;
275 self
276 }
277
278 pub fn run(self, scene: impl Scene) -> Result<()> {
280 crate::window::run(self, Box::new(scene))
281 }
282
283 pub fn run_with<S: Scene>(self, build: impl FnOnce(&mut Context) -> S + 'static) -> Result<()> {
302 self.run(Lazy::new(build))
303 }
304
305 #[cfg(feature = "wxdragon")]
308 pub fn run_wx(self, scene: impl Scene) -> Result<()> {
309 crate::wx::run(self, Box::new(scene))
310 }
311
312 #[cfg(feature = "wxdragon")]
314 pub fn run_wx_with<S: Scene>(self, build: impl FnOnce(&mut Context) -> S + 'static) -> Result<()> {
315 self.run_wx(Lazy::new(build))
316 }
317
318 pub fn run_fn(self, update: impl FnMut(&mut Context) + 'static) -> Result<()> {
321 self.run(FnScene(update))
322 }
323
324 pub fn build_fn(self, update: impl FnMut(&mut Context) + 'static) -> Runner {
326 self.build(FnScene(update))
327 }
328
329 pub fn build(self, scene: impl Scene) -> Runner {
332 Runner::new(self, Box::new(scene))
333 }
334}
335
336struct FnScene<F>(F);
338
339impl<F: FnMut(&mut Context) + 'static> Scene for FnScene<F> {
340 fn update(&mut self, ctx: &mut Context) {
341 (self.0)(ctx);
342 }
343}
344
345struct Lazy<S, F> {
347 build: Option<F>,
348 scene: Option<S>,
349}
350
351impl<S: Scene, F: FnOnce(&mut Context) -> S + 'static> Lazy<S, F> {
352 fn new(build: F) -> Self {
353 Self { build: Some(build), scene: None }
354 }
355}
356
357impl<S: Scene, F: FnOnce(&mut Context) -> S + 'static> Scene for Lazy<S, F> {
358 fn start(&mut self, ctx: &mut Context) {
359 if let Some(build) = self.build.take() {
360 let mut scene = build(ctx);
361 scene.start(ctx);
362 self.scene = Some(scene);
363 }
364 }
365 fn update(&mut self, ctx: &mut Context) {
366 if let Some(s) = &mut self.scene {
367 s.update(ctx);
368 }
369 }
370 fn fixed_update(&mut self, ctx: &mut Context) {
371 if let Some(s) = &mut self.scene {
372 s.fixed_update(ctx);
373 }
374 }
375 fn on_ui(&mut self, event: &UiEvent, ctx: &mut Context) {
376 if let Some(s) = &mut self.scene {
377 s.on_ui(event, ctx);
378 }
379 }
380 fn pause(&mut self, ctx: &mut Context) {
381 if let Some(s) = &mut self.scene {
382 s.pause(ctx);
383 }
384 }
385 fn resume(&mut self, ctx: &mut Context) {
386 if let Some(s) = &mut self.scene {
387 s.resume(ctx);
388 }
389 }
390 fn end(&mut self, ctx: &mut Context) {
391 if let Some(s) = &mut self.scene {
392 s.end(ctx);
393 }
394 }
395}
396
397pub struct Runner {
400 pub ctx: Context,
402 scenes: Vec<Box<dyn Scene>>,
403 started: bool,
404 accumulator: f32,
405 fixed_dt: f32,
406 frame_time: Duration,
407 next_frame: Instant,
408}
409
410impl std::fmt::Debug for Runner {
411 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
412 f.debug_struct("Runner").field("scenes", &self.scenes.len()).finish_non_exhaustive()
413 }
414}
415
416impl Runner {
417 pub(crate) fn new(config: Engine, scene: Box<dyn Scene>) -> Self {
418 let ctx = Context {
419 time: Time::new(),
420 input: Input::default(),
421 audio: if config.headless {
422 Audio::silent(config.sound_dir.clone())
423 } else {
424 Audio::new(config.sound_dir.clone())
425 },
426 speech: if config.headless { Speech::console() } else { Speech::new(config.echo_speech) },
427 ui: Ui::default(),
428 physics: {
429 let mut world = World::new();
430 world.set_gravity(config.gravity);
431 world
432 },
433 storage: if config.headless {
434 Storage::in_memory()
435 } else {
436 let file = config.save_file.clone().unwrap_or_else(|| crate::storage::default_file(&config.title));
437 match &config.save_secret {
438 Some(secret) => Storage::open_encrypted(file, secret),
439 None => Storage::open(file),
440 }
441 },
442 follow: None,
443 attached: Vec::new(),
444 engine_sounds: Vec::new(),
445 once: HashSet::new(),
446 title: config.title.clone(),
447 quit: false,
448 change: None,
449 globals: HashMap::new(),
450 };
451 Self {
452 ctx,
453 scenes: vec![scene],
454 started: false,
455 accumulator: 0.0,
456 fixed_dt: 1.0 / config.physics_rate as f32,
457 frame_time: Duration::from_secs_f64(1.0 / config.fps as f64),
458 next_frame: Instant::now(),
459 }
460 }
461
462 pub fn key_down(&mut self, key: Key, repeat: bool) {
464 self.ctx.input.push(KeyEvent::Down { key, repeat });
465 }
466
467 pub fn key_up(&mut self, key: Key) {
469 self.ctx.input.push(KeyEvent::Up { key });
470 }
471
472 pub fn text(&mut self, text: &str) {
474 self.ctx.input.push_text(text);
475 }
476
477 pub fn focus_lost(&mut self) {
479 self.ctx.input.release_all();
480 }
481
482 pub fn next_frame_time(&self) -> Instant {
484 self.next_frame
485 }
486
487 pub fn should_quit(&self) -> bool {
489 self.ctx.quit
490 }
491
492 pub fn frame(&mut self) -> bool {
494 let now = Instant::now();
495 self.next_frame = (self.next_frame + self.frame_time).max(now);
497
498 if !self.started {
499 self.started = true;
500 self.ctx.time.advance(now);
501 if let Some(scene) = self.scenes.last_mut() {
502 scene.start(&mut self.ctx);
503 }
504 self.apply_change();
505 return !self.ctx.quit;
506 }
507
508 let ctx = &mut self.ctx;
509 ctx.time.advance(now);
510 ctx.input.begin_frame(now);
511
512 let consumed = ctx.ui.update(&ctx.input, &mut ctx.speech, &mut ctx.audio);
513 ctx.input.set_blocked(consumed || ctx.ui.is_open());
514
515 if let Some(scene) = self.scenes.last_mut() {
516 for event in self.ctx.ui.take_events() {
517 scene.on_ui(&event, &mut self.ctx);
518 }
519
520 self.ctx.physics.clear_events();
521 self.accumulator += self.ctx.time.delta();
522 let mut steps = 0;
523 while self.accumulator >= self.fixed_dt && steps < 8 {
524 self.accumulator -= self.fixed_dt;
525 self.ctx.physics.step(self.fixed_dt);
526 scene.fixed_update(&mut self.ctx);
527 steps += 1;
528 }
529 if steps == 8 {
530 self.accumulator = 0.0;
531 }
532
533 scene.update(&mut self.ctx);
534 }
535 self.ctx.sync_world();
536 self.ctx.sync_vehicles();
537 self.apply_change();
538
539 if self.ctx.quit {
540 self.shutdown();
541 return false;
542 }
543 true
544 }
545
546 fn apply_change(&mut self) {
547 while let Some(change) = self.ctx.change.take() {
549 match change {
550 SceneChange::Push(mut scene) => {
551 if let Some(top) = self.scenes.last_mut() {
552 top.pause(&mut self.ctx);
553 }
554 scene.start(&mut self.ctx);
555 self.scenes.push(scene);
556 }
557 SceneChange::Pop => {
558 if let Some(mut top) = self.scenes.pop() {
559 top.end(&mut self.ctx);
560 }
561 match self.scenes.last_mut() {
562 Some(top) => top.resume(&mut self.ctx),
563 None => self.ctx.quit = true,
564 }
565 }
566 SceneChange::Switch(mut scene) => {
567 if let Some(mut top) = self.scenes.pop() {
568 top.end(&mut self.ctx);
569 }
570 scene.start(&mut self.ctx);
571 self.scenes.push(scene);
572 }
573 }
574 }
575 }
576
577 pub fn shutdown(&mut self) {
579 while let Some(mut scene) = self.scenes.pop() {
580 scene.end(&mut self.ctx);
581 }
582 if let Err(e) = self.ctx.storage.save() {
583 eprintln!("audio-code: could not save storage: {e}");
584 }
585 self.ctx.quit = true;
586 }
587}
588
589#[cfg(test)]
590mod tests {
591 use super::*;
592 use std::cell::RefCell;
593 use std::rc::Rc;
594
595 struct Recorder(Rc<RefCell<Vec<String>>>, &'static str);
596
597 impl Scene for Recorder {
598 fn start(&mut self, _: &mut Context) {
599 self.0.borrow_mut().push(format!("{} start", self.1));
600 }
601 fn update(&mut self, ctx: &mut Context) {
602 self.0.borrow_mut().push(format!("{} update", self.1));
603 if self.1 == "a" && ctx.input.pressed(Key::Enter) {
604 ctx.push_scene(Recorder(self.0.clone(), "b"));
605 }
606 if self.1 == "b" && ctx.input.pressed(Key::Escape) {
607 ctx.pop_scene();
608 }
609 }
610 fn pause(&mut self, _: &mut Context) {
611 self.0.borrow_mut().push(format!("{} pause", self.1));
612 }
613 fn resume(&mut self, _: &mut Context) {
614 self.0.borrow_mut().push(format!("{} resume", self.1));
615 }
616 fn end(&mut self, _: &mut Context) {
617 self.0.borrow_mut().push(format!("{} end", self.1));
618 }
619 }
620
621 #[test]
622 fn scene_stack() {
623 let log = Rc::new(RefCell::new(Vec::new()));
624 let mut runner = Engine::new("test").headless().build(Recorder(log.clone(), "a"));
625 runner.frame();
626 runner.key_down(Key::Enter, false);
627 runner.frame();
628 runner.key_up(Key::Enter);
629 runner.key_down(Key::Escape, false);
630 runner.frame();
631 runner.key_up(Key::Escape);
632 runner.key_down(Key::Escape, false);
633 runner.frame();
634 assert_eq!(
635 *log.borrow(),
636 ["a start", "a update", "a pause", "b start", "b update", "b end", "a resume", "a update"]
637 );
638 }
639
640 #[test]
641 fn globals() {
642 let mut runner = Engine::new("test").headless().build(Recorder(Rc::default(), "x"));
643 runner.ctx.insert(5u32);
644 *runner.ctx.get_mut::<u32>().unwrap() += 1;
645 assert_eq!(runner.ctx.get::<u32>(), Some(&6));
646 assert_eq!(*runner.ctx.get_or_default::<String>(), "");
647 }
648}