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::lang::Lang;
12use crate::net::{Net, NetEvent};
13use crate::physics::{BodyId, World};
14use crate::shortcuts::Attached;
15use crate::speech::Speech;
16use crate::storage::Storage;
17use crate::time::Time;
18use crate::ui::{Ui, UiEvent};
19
20pub trait Scene: 'static {
26 fn start(&mut self, ctx: &mut Context) {
28 let _ = ctx;
29 }
30
31 fn update(&mut self, ctx: &mut Context);
33
34 fn fixed_update(&mut self, ctx: &mut Context) {
37 let _ = ctx;
38 }
39
40 fn on_ui(&mut self, event: &UiEvent, ctx: &mut Context) {
43 let _ = (event, ctx);
44 }
45
46 fn on_net(&mut self, event: &NetEvent, ctx: &mut Context) {
50 let _ = (event, ctx);
51 }
52
53 fn pause(&mut self, ctx: &mut Context) {
55 let _ = ctx;
56 }
57
58 fn resume(&mut self, ctx: &mut Context) {
60 let _ = ctx;
61 }
62
63 fn end(&mut self, ctx: &mut Context) {
65 let _ = ctx;
66 }
67}
68
69enum SceneChange {
70 Push(Box<dyn Scene>),
71 Pop,
72 Switch(Box<dyn Scene>),
73}
74
75pub struct Context {
77 pub time: Time,
79 pub input: Input,
81 pub audio: Audio,
83 pub speech: Speech,
85 pub ui: Ui,
87 pub physics: World,
89 pub storage: Storage,
91 pub lang: Lang,
93 pub net: Net,
95 pub(crate) follow: Option<BodyId>,
96 pub(crate) attached: Vec<Attached>,
97 pub(crate) engine_sounds: Vec<(BodyId, Sound)>,
98 pub(crate) once: HashSet<String>,
99 title: String,
100 quit: bool,
101 change: Option<SceneChange>,
102 globals: HashMap<TypeId, Box<dyn Any>>,
103}
104
105impl std::fmt::Debug for Context {
106 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107 f.debug_struct("Context").field("title", &self.title).field("time", &self.time).finish_non_exhaustive()
108 }
109}
110
111impl Context {
112 pub fn quit(&mut self) {
114 self.quit = true;
115 }
116
117 pub fn is_quitting(&self) -> bool {
119 self.quit
120 }
121
122 pub fn title(&self) -> &str {
124 &self.title
125 }
126
127 pub fn switch_scene(&mut self, scene: impl Scene) {
129 self.change = Some(SceneChange::Switch(Box::new(scene)));
130 }
131
132 pub fn push_scene(&mut self, scene: impl Scene) {
134 self.change = Some(SceneChange::Push(Box::new(scene)));
135 }
136
137 pub fn pop_scene(&mut self) {
139 self.change = Some(SceneChange::Pop);
140 }
141
142 pub fn insert<T: 'static>(&mut self, value: T) {
145 self.globals.insert(TypeId::of::<T>(), Box::new(value));
146 }
147
148 pub fn get<T: 'static>(&self) -> Option<&T> {
150 self.globals.get(&TypeId::of::<T>()).and_then(|b| b.downcast_ref())
151 }
152
153 pub fn get_mut<T: 'static>(&mut self) -> Option<&mut T> {
155 self.globals.get_mut(&TypeId::of::<T>()).and_then(|b| b.downcast_mut())
156 }
157
158 pub fn get_or_default<T: Default + 'static>(&mut self) -> &mut T {
160 self.globals
161 .entry(TypeId::of::<T>())
162 .or_insert_with(|| Box::new(T::default()))
163 .downcast_mut()
164 .expect("type id matches")
165 }
166
167 pub fn remove<T: 'static>(&mut self) -> Option<T> {
169 self.globals.remove(&TypeId::of::<T>()).and_then(|b| b.downcast().ok()).map(|b| *b)
170 }
171}
172
173#[derive(Debug, Clone)]
200pub struct Engine {
201 pub(crate) title: String,
202 pub(crate) fps: u32,
203 pub(crate) physics_rate: u32,
204 pub(crate) sound_dir: PathBuf,
205 pub(crate) echo_speech: bool,
206 pub(crate) window_size: (u32, u32),
207 pub(crate) headless: bool,
208 pub(crate) server: bool,
209 pub(crate) gravity: glam::Vec3,
210 pub(crate) save_file: Option<PathBuf>,
211 pub(crate) save_secret: Option<String>,
212 pub(crate) lang_dir: PathBuf,
213 pub(crate) language: Option<String>,
214}
215
216impl Engine {
217 pub fn new(title: impl Into<String>) -> Self {
220 Self {
221 title: title.into(),
222 fps: 60,
223 physics_rate: 60,
224 sound_dir: PathBuf::from("sounds"),
225 echo_speech: false,
226 window_size: (640, 480),
227 headless: false,
228 server: false,
229 gravity: glam::Vec3::ZERO,
230 save_file: None,
231 save_secret: None,
232 lang_dir: PathBuf::from("lang"),
233 language: None,
234 }
235 }
236
237 pub fn fps(mut self, fps: u32) -> Self {
239 self.fps = fps.clamp(1, 1000);
240 self
241 }
242
243 pub fn physics_rate(mut self, rate: u32) -> Self {
245 self.physics_rate = rate.clamp(1, 1000);
246 self
247 }
248
249 pub fn gravity(mut self, gravity: glam::Vec3) -> Self {
252 self.gravity = gravity;
253 self
254 }
255
256 pub fn save_file(mut self, path: impl Into<PathBuf>) -> Self {
258 self.save_file = Some(path.into());
259 self
260 }
261
262 pub fn encrypt_save(mut self, secret: impl Into<String>) -> Self {
267 self.save_secret = Some(secret.into());
268 self
269 }
270
271 pub fn sound_dir(mut self, dir: impl Into<PathBuf>) -> Self {
273 self.sound_dir = dir.into();
274 self
275 }
276
277 pub fn lang_dir(mut self, dir: impl Into<PathBuf>) -> Self {
280 self.lang_dir = dir.into();
281 self
282 }
283
284 pub fn language(mut self, code: impl Into<String>) -> Self {
288 self.language = Some(code.into());
289 self
290 }
291
292 pub fn echo_speech(mut self, echo: bool) -> Self {
294 self.echo_speech = echo;
295 self
296 }
297
298 pub fn window_size(mut self, width: u32, height: u32) -> Self {
301 self.window_size = (width, height);
302 self
303 }
304
305 pub fn headless(mut self) -> Self {
308 self.headless = true;
309 self
310 }
311
312 pub fn run_server(mut self, scene: impl Scene) -> Result<()> {
332 self.server = true;
333 let mut runner = self.build(scene);
334 while runner.frame() {
335 std::thread::sleep(runner.next_frame_time().saturating_duration_since(Instant::now()));
336 }
337 Ok(())
338 }
339
340 pub fn run_server_fn(self, update: impl FnMut(&mut Context) + 'static) -> Result<()> {
342 self.run_server(FnScene(update))
343 }
344
345 pub fn run(self, scene: impl Scene) -> Result<()> {
347 crate::window::run(self, Box::new(scene))
348 }
349
350 pub fn run_with<S: Scene>(self, build: impl FnOnce(&mut Context) -> S + 'static) -> Result<()> {
369 self.run(Lazy::new(build))
370 }
371
372 #[cfg(feature = "wxdragon")]
375 pub fn run_wx(self, scene: impl Scene) -> Result<()> {
376 crate::wx::run(self, Box::new(scene))
377 }
378
379 #[cfg(feature = "wxdragon")]
381 pub fn run_wx_with<S: Scene>(self, build: impl FnOnce(&mut Context) -> S + 'static) -> Result<()> {
382 self.run_wx(Lazy::new(build))
383 }
384
385 pub fn run_fn(self, update: impl FnMut(&mut Context) + 'static) -> Result<()> {
388 self.run(FnScene(update))
389 }
390
391 pub fn build_fn(self, update: impl FnMut(&mut Context) + 'static) -> Runner {
393 self.build(FnScene(update))
394 }
395
396 pub fn build(self, scene: impl Scene) -> Runner {
399 Runner::new(self, Box::new(scene))
400 }
401}
402
403fn load_lang(config: &Engine, storage: &Storage) -> Lang {
406 let mut lang = Lang::new();
407 if lang.load_dir(&config.lang_dir) == 0
408 && config.lang_dir.is_relative()
409 && let Some(resources) = crate::bundle::resources()
410 {
411 lang.load_dir(resources.join(&config.lang_dir));
412 }
413 let saved = storage.get::<String>(crate::shortcuts::LANGUAGE_KEY).and_then(|c| lang.resolve(&c));
414 let configured = config.language.as_deref().and_then(|c| lang.resolve(c));
415 let system = if config.headless { None } else { Lang::system().and_then(|c| lang.resolve_loaded(&c)) };
416 if let Some(code) = saved.or(configured).or(system) {
417 lang.set(&code);
418 } else if let Some(code) = &config.language {
419 lang.set(code); }
421 lang
422}
423
424struct FnScene<F>(F);
426
427impl<F: FnMut(&mut Context) + 'static> Scene for FnScene<F> {
428 fn update(&mut self, ctx: &mut Context) {
429 (self.0)(ctx);
430 }
431}
432
433struct Lazy<S, F> {
435 build: Option<F>,
436 scene: Option<S>,
437}
438
439impl<S: Scene, F: FnOnce(&mut Context) -> S + 'static> Lazy<S, F> {
440 fn new(build: F) -> Self {
441 Self { build: Some(build), scene: None }
442 }
443}
444
445impl<S: Scene, F: FnOnce(&mut Context) -> S + 'static> Scene for Lazy<S, F> {
446 fn start(&mut self, ctx: &mut Context) {
447 if let Some(build) = self.build.take() {
448 let mut scene = build(ctx);
449 scene.start(ctx);
450 self.scene = Some(scene);
451 }
452 }
453 fn update(&mut self, ctx: &mut Context) {
454 if let Some(s) = &mut self.scene {
455 s.update(ctx);
456 }
457 }
458 fn fixed_update(&mut self, ctx: &mut Context) {
459 if let Some(s) = &mut self.scene {
460 s.fixed_update(ctx);
461 }
462 }
463 fn on_ui(&mut self, event: &UiEvent, ctx: &mut Context) {
464 if let Some(s) = &mut self.scene {
465 s.on_ui(event, ctx);
466 }
467 }
468 fn on_net(&mut self, event: &NetEvent, ctx: &mut Context) {
469 if let Some(s) = &mut self.scene {
470 s.on_net(event, ctx);
471 }
472 }
473 fn pause(&mut self, ctx: &mut Context) {
474 if let Some(s) = &mut self.scene {
475 s.pause(ctx);
476 }
477 }
478 fn resume(&mut self, ctx: &mut Context) {
479 if let Some(s) = &mut self.scene {
480 s.resume(ctx);
481 }
482 }
483 fn end(&mut self, ctx: &mut Context) {
484 if let Some(s) = &mut self.scene {
485 s.end(ctx);
486 }
487 }
488}
489
490pub struct Runner {
493 pub ctx: Context,
495 scenes: Vec<Box<dyn Scene>>,
496 started: bool,
497 accumulator: f32,
498 fixed_dt: f32,
499 frame_time: Duration,
500 next_frame: Instant,
501}
502
503impl std::fmt::Debug for Runner {
504 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
505 f.debug_struct("Runner").field("scenes", &self.scenes.len()).finish_non_exhaustive()
506 }
507}
508
509impl Runner {
510 pub(crate) fn new(config: Engine, scene: Box<dyn Scene>) -> Self {
511 let storage = if config.headless {
512 Storage::in_memory()
513 } else {
514 let file = config.save_file.clone().unwrap_or_else(|| crate::storage::default_file(&config.title));
515 match &config.save_secret {
516 Some(secret) => Storage::open_encrypted(file, secret),
517 None => Storage::open(file),
518 }
519 };
520 let lang = load_lang(&config, &storage);
521 let ctx = Context {
522 time: Time::new(),
523 input: Input::default(),
524 audio: if config.headless || config.server {
525 Audio::silent(config.sound_dir.clone())
526 } else {
527 Audio::new(config.sound_dir.clone())
528 },
529 speech: if config.headless || config.server { Speech::console() } else { Speech::new(config.echo_speech) },
530 ui: Ui::default(),
531 physics: {
532 let mut world = World::new();
533 world.set_gravity(config.gravity);
534 world
535 },
536 storage,
537 lang,
538 net: Net::new(config.title.clone()),
539 follow: None,
540 attached: Vec::new(),
541 engine_sounds: Vec::new(),
542 once: HashSet::new(),
543 title: config.title.clone(),
544 quit: false,
545 change: None,
546 globals: HashMap::new(),
547 };
548 Self {
549 ctx,
550 scenes: vec![scene],
551 started: false,
552 accumulator: 0.0,
553 fixed_dt: 1.0 / config.physics_rate as f32,
554 frame_time: Duration::from_secs_f64(1.0 / config.fps as f64),
555 next_frame: Instant::now(),
556 }
557 }
558
559 pub fn key_down(&mut self, key: Key, repeat: bool) {
561 self.ctx.input.push(KeyEvent::Down { key, repeat });
562 }
563
564 pub fn key_up(&mut self, key: Key) {
566 self.ctx.input.push(KeyEvent::Up { key });
567 }
568
569 pub fn text(&mut self, text: &str) {
571 self.ctx.input.push_text(text);
572 }
573
574 pub fn focus_lost(&mut self) {
576 self.ctx.input.release_all();
577 }
578
579 pub fn next_frame_time(&self) -> Instant {
581 self.next_frame
582 }
583
584 pub fn should_quit(&self) -> bool {
586 self.ctx.quit
587 }
588
589 pub fn frame(&mut self) -> bool {
591 let now = Instant::now();
592 self.next_frame = (self.next_frame + self.frame_time).max(now);
594
595 if !self.started {
596 self.started = true;
597 self.ctx.time.advance(now);
598 if let Some(scene) = self.scenes.last_mut() {
599 scene.start(&mut self.ctx);
600 }
601 self.apply_change();
602 return !self.ctx.quit;
603 }
604
605 let ctx = &mut self.ctx;
606 ctx.time.advance(now);
607 ctx.input.begin_frame(now);
608 ctx.net.update();
609
610 let consumed = ctx.ui.update(&ctx.input, &mut ctx.speech, &mut ctx.audio, &ctx.lang);
611 ctx.input.set_blocked(consumed || ctx.ui.is_open());
612
613 if let Some(scene) = self.scenes.last_mut() {
614 for event in self.ctx.ui.take_events() {
615 scene.on_ui(&event, &mut self.ctx);
616 }
617 if !self.ctx.net.events.is_empty() {
618 let events = std::mem::take(&mut self.ctx.net.events);
620 for event in &events {
621 scene.on_net(event, &mut self.ctx);
622 }
623 let added = std::mem::replace(&mut self.ctx.net.events, events);
624 self.ctx.net.events.extend(added);
625 }
626
627 self.ctx.physics.clear_events();
628 self.accumulator += self.ctx.time.delta();
629 let mut steps = 0;
630 while self.accumulator >= self.fixed_dt && steps < 8 {
631 self.accumulator -= self.fixed_dt;
632 self.ctx.physics.step(self.fixed_dt);
633 scene.fixed_update(&mut self.ctx);
634 steps += 1;
635 }
636 if steps == 8 {
637 self.accumulator = 0.0;
638 }
639
640 scene.update(&mut self.ctx);
641 }
642 self.ctx.sync_world();
643 self.ctx.sync_vehicles();
644 self.apply_change();
645
646 if self.ctx.quit {
647 self.shutdown();
648 return false;
649 }
650 true
651 }
652
653 fn apply_change(&mut self) {
654 while let Some(change) = self.ctx.change.take() {
656 match change {
657 SceneChange::Push(mut scene) => {
658 if let Some(top) = self.scenes.last_mut() {
659 top.pause(&mut self.ctx);
660 }
661 scene.start(&mut self.ctx);
662 self.scenes.push(scene);
663 }
664 SceneChange::Pop => {
665 if let Some(mut top) = self.scenes.pop() {
666 top.end(&mut self.ctx);
667 }
668 match self.scenes.last_mut() {
669 Some(top) => top.resume(&mut self.ctx),
670 None => self.ctx.quit = true,
671 }
672 }
673 SceneChange::Switch(mut scene) => {
674 if let Some(mut top) = self.scenes.pop() {
675 top.end(&mut self.ctx);
676 }
677 scene.start(&mut self.ctx);
678 self.scenes.push(scene);
679 }
680 }
681 }
682 }
683
684 pub fn shutdown(&mut self) {
686 while let Some(mut scene) = self.scenes.pop() {
687 scene.end(&mut self.ctx);
688 }
689 self.ctx.net.close();
690 if let Err(e) = self.ctx.storage.save() {
691 eprintln!("audio-code: could not save storage: {e}");
692 }
693 self.ctx.quit = true;
694 }
695}
696
697#[cfg(test)]
698mod tests {
699 use super::*;
700 use std::cell::RefCell;
701 use std::rc::Rc;
702
703 struct Recorder(Rc<RefCell<Vec<String>>>, &'static str);
704
705 impl Scene for Recorder {
706 fn start(&mut self, _: &mut Context) {
707 self.0.borrow_mut().push(format!("{} start", self.1));
708 }
709 fn update(&mut self, ctx: &mut Context) {
710 self.0.borrow_mut().push(format!("{} update", self.1));
711 if self.1 == "a" && ctx.input.pressed(Key::Enter) {
712 ctx.push_scene(Recorder(self.0.clone(), "b"));
713 }
714 if self.1 == "b" && ctx.input.pressed(Key::Escape) {
715 ctx.pop_scene();
716 }
717 }
718 fn pause(&mut self, _: &mut Context) {
719 self.0.borrow_mut().push(format!("{} pause", self.1));
720 }
721 fn resume(&mut self, _: &mut Context) {
722 self.0.borrow_mut().push(format!("{} resume", self.1));
723 }
724 fn end(&mut self, _: &mut Context) {
725 self.0.borrow_mut().push(format!("{} end", self.1));
726 }
727 }
728
729 #[test]
730 fn scene_stack() {
731 let log = Rc::new(RefCell::new(Vec::new()));
732 let mut runner = Engine::new("test").headless().build(Recorder(log.clone(), "a"));
733 runner.frame();
734 runner.key_down(Key::Enter, false);
735 runner.frame();
736 runner.key_up(Key::Enter);
737 runner.key_down(Key::Escape, false);
738 runner.frame();
739 runner.key_up(Key::Escape);
740 runner.key_down(Key::Escape, false);
741 runner.frame();
742 assert_eq!(
743 *log.borrow(),
744 ["a start", "a update", "a pause", "b start", "b update", "b end", "a resume", "a update"]
745 );
746 }
747
748 use crate::net::Packet;
749
750 struct NetLog(Rc<RefCell<Vec<String>>>);
751
752 impl Scene for NetLog {
753 fn update(&mut self, ctx: &mut Context) {
754 for (from, packet) in ctx.net.received("hi") {
755 ctx.net.send(from, Packet::new("echo").with(packet.int(0)));
756 }
757 }
758 fn on_net(&mut self, event: &NetEvent, _: &mut Context) {
759 self.0.borrow_mut().push(format!("{event:?}"));
760 }
761 }
762
763 #[test]
764 fn network_events_reach_scenes() {
765 use crate::net::PeerId;
766 let log = Rc::new(RefCell::new(Vec::new()));
767 let mut server = Engine::new("net").headless().build(NetLog(log.clone()));
768 let mut client = Engine::new("net").headless().build_fn(|_| {});
769 server.frame();
770 client.frame();
771 server.ctx.net.host(0).unwrap();
772 client.ctx.net.join(&format!("127.0.0.1:{}", server.ctx.net.port().unwrap()));
773 let start = Instant::now();
774 let mut echoed = None;
775 while echoed.is_none() || client.ctx.net.ping(PeerId::SERVER) == 0.0 {
776 assert!(start.elapsed() < Duration::from_secs(8), "no answer");
777 server.frame();
778 client.frame();
779 if client.ctx.net.joined() == [PeerId::SERVER] {
780 client.ctx.net.send_all(Packet::new("hi").with(7));
781 }
782 if let Some((_, p)) = client.ctx.net.received("echo").first() {
783 echoed = Some(p.int(0));
784 }
785 std::thread::sleep(Duration::from_millis(5));
786 }
787 assert_eq!(echoed, Some(7));
788 let log = log.borrow();
789 assert_eq!(log[0], "Connected(PeerId(1))");
790 assert!(log[1].starts_with("Message(PeerId(1)"));
791 }
792
793 #[test]
794 fn globals() {
795 let mut runner = Engine::new("test").headless().build(Recorder(Rc::default(), "x"));
796 runner.ctx.insert(5u32);
797 *runner.ctx.get_mut::<u32>().unwrap() += 1;
798 assert_eq!(runner.ctx.get::<u32>(), Some(&6));
799 assert_eq!(*runner.ctx.get_or_default::<String>(), "");
800 }
801
802 #[test]
803 fn start_language() {
804 let runner = Engine::new("test").headless().build(Recorder(Rc::default(), "x"));
805 assert_eq!(runner.ctx.lang.current(), "en");
806 let mut runner = Engine::new("test").headless().language("de").build(Recorder(Rc::default(), "x"));
807 assert_eq!(runner.ctx.lang.current(), "de");
808 assert_eq!(runner.ctx.tr("ui.yes"), "Ja");
809 assert!(runner.ctx.set_language("en-US"));
810 assert_eq!(runner.ctx.storage.get::<String>("language").as_deref(), Some("en"));
811 assert!(!runner.ctx.set_language("xx"));
812 }
813}