engine.rs
1 use atomic_float::AtomicF64; 2 use crossbeam::{atomic::AtomicCell, channel::bounded}; 3 #[cfg(feature = "client")] 4 use std::collections::VecDeque; 5 use std::sync::atomic::Ordering::Relaxed; 6 7 use let_engine_core::{ 8 CustomError, 9 backend::{ 10 Backends, 11 audio::AudioInterface, 12 gpu::GpuBackend, 13 networking::{NetEvent, NetworkingBackend}, 14 }, 15 scenes::Scene, 16 }; 17 18 use parking_lot::{Condvar, Mutex}; 19 20 #[cfg(feature = "client")] 21 use crate::window::WindowBuilder; 22 use crate::{ 23 backend::DefaultBackends, 24 settings, 25 tick_system::{self, TickSystem}, 26 }; 27 #[cfg(feature = "client")] 28 use { 29 self::events::ScrollDelta, 30 crate::window::Window, 31 crate::{events, input::Input}, 32 anyhow::Result, 33 glam::{dvec2, uvec2, vec2}, 34 let_engine_core::backend::gpu::GpuInterfacer, 35 winit::application::ApplicationHandler, 36 winit::event::MouseScrollDelta, 37 }; 38 39 use std::sync::OnceLock; 40 41 use std::time::Instant; 42 use std::{sync::Arc, time::Duration}; 43 44 type Connection<B> = <B as NetworkingBackend>::Connection; 45 type ClientMessage<'a, B> = <B as NetworkingBackend>::ClientEvent<'a>; 46 type ServerMessage<'a, B> = <B as NetworkingBackend>::ServerEvent<'a>; 47 48 /// The main event trait of the game engine. 49 /// 50 /// All events emitted by backends or window updates with the client feature enabled get called in this trait. 51 /// 52 /// Every trait has a default implementation that does nothing. 53 /// 54 /// An event represents an update of the game state. 55 #[allow(unused_variables)] 56 pub trait Game<B: Backends = DefaultBackends, E: CustomError = ()>: Send + Sync + 'static { 57 /// Runs before the frame is drawn. 58 #[cfg(feature = "client")] 59 fn update(&mut self, context: EngineContext<E, B>) -> Result<(), E> { 60 Ok(()) 61 } 62 63 /// Runs before `update` method 64 #[cfg(feature = "egui")] 65 fn egui(&mut self, context: EngineContext<E, B>, egui_context: egui::Context) -> Result<(), E> { 66 Ok(()) 67 } 68 69 /// Runs based on the configured tick settings of the engine. 70 fn tick(&mut self, context: EngineContext<E, B>) -> Result<(), E> { 71 Ok(()) 72 } 73 74 /// Runs when the window is ready. 75 #[cfg(feature = "client")] 76 fn window_ready(&mut self, context: EngineContext<E, B>) -> Result<(), E> { 77 Ok(()) 78 } 79 80 /// Events emitted by the window system. 81 #[cfg(feature = "client")] 82 fn window( 83 &mut self, 84 context: EngineContext<E, B>, 85 event: events::WindowEvent, 86 ) -> Result<(), E> { 87 Ok(()) 88 } 89 90 /// Received input events. 91 #[cfg(feature = "client")] 92 fn input(&mut self, context: EngineContext<E, B>, event: events::InputEvent) -> Result<(), E> { 93 Ok(()) 94 } 95 96 /// An external networking event emitted by the set networking backends server. 97 fn server_event( 98 &mut self, 99 context: EngineContext<E, B>, 100 connection: Connection<B::Networking>, 101 message: ServerMessage<B::Networking>, 102 ) -> Result<(), E> { 103 Ok(()) 104 } 105 106 /// An external networking event emitted by the set networking backends client. 107 fn client_event( 108 &mut self, 109 context: EngineContext<E, B>, 110 message: ClientMessage<B::Networking>, 111 ) -> Result<(), E> { 112 Ok(()) 113 } 114 115 /// An error has occured within the engine context. 116 /// 117 /// Last chance to gracefully handle errors. 118 /// 119 /// If an `Err` is returned from this event, this will be the return type of the `start` method. 120 /// 121 /// The default implementation passes the error to the return type like so: 122 /// ```ignore 123 /// fn error( 124 /// &mut self, 125 /// _context: EngineContext<E, B>, 126 /// message: EngineError<E, B>, 127 /// ) -> Result<(), EngineError<E, B>> { 128 /// Err(message) 129 /// } 130 /// ``` 131 fn error( 132 &mut self, 133 context: EngineContext<E, B>, 134 message: EngineError<E, B>, 135 ) -> Result<(), EngineError<E, B>> { 136 Err(message) 137 } 138 139 /// The last event ever emitted by this trait. 140 /// 141 /// Symbolizes a halt of the game engine, which can be initiated by the contexts `exit` method 142 /// or a Ctrl-C event. 143 fn end(&mut self, context: EngineContext<E, B>) -> Result<(), E> { 144 Ok(()) 145 } 146 } 147 148 /// The initial start method of the game engine. 149 /// 150 /// Runs the game closure after the backends have started. 151 /// 152 /// If the game closure returns an error, this method returns an [`EngineError::Custom`] 153 pub fn start<G: Game<B, E>, E: CustomError, B: Backends>( 154 settings: impl Into<settings::EngineSettings<B>>, 155 game: impl FnOnce(EngineContext<E, B>) -> Result<G, E>, 156 ) -> Result<(), EngineError<E, B>> { 157 Engine::<G, E, B>::start(game, settings) 158 } 159 160 /// The struct that holds and executes all backends and the game state. 161 struct Engine<G, E, B = DefaultBackends> 162 where 163 G: Game<B, E>, 164 E: CustomError, 165 B: Backends, 166 { 167 #[cfg(feature = "client")] 168 gpu_backend: B::Gpu, 169 #[cfg(feature = "client")] 170 delta_time_window: VecDeque<(Instant, f64)>, 171 #[cfg(feature = "client")] 172 window_settings: WindowBuilder, 173 #[cfg(feature = "client")] 174 oldest_fps_sample_age: Duration, 175 176 #[allow(dead_code)] 177 game: Arc<GameWrapper<G, E, B>>, 178 } 179 180 pub use let_engine_core::EngineError; 181 182 impl<G: Game<B, E>, E: CustomError, B: Backends> Engine<G, E, B> { 183 /// Starts the game engine with the given game. 184 pub fn start( 185 game: impl FnOnce(EngineContext<E, B>) -> Result<G, E>, 186 settings: impl Into<settings::EngineSettings<B>>, 187 ) -> Result<(), EngineError<E, B>> { 188 let settings: settings::EngineSettings<B> = settings.into(); 189 190 #[cfg(feature = "client")] 191 let Ok(event_loop) = winit::event_loop::EventLoop::new() else { 192 // TODO: Handle other errors 193 return Err(EngineError::Recreation); 194 }; 195 196 // Gpu backend 197 #[cfg(feature = "client")] 198 let (gpu_backend, gpu_interface) = 199 B::Gpu::new(settings.gpu, &event_loop).map_err(EngineError::GpuBackend)?; 200 201 // Audio backend 202 let audio_interface = 203 AudioInterface::new(settings.audio).map_err(EngineError::AudioBackend)?; 204 205 // Networking backend 206 let (net_send, net_recv) = bounded(0); 207 let (game_send, game_recv) = bounded(0); 208 209 let networking_settings = settings.networking.clone(); 210 let networking_join_handle = std::thread::Builder::new() 211 .name("let-engine-networking-backend".to_owned()) 212 .spawn(move || { 213 let mut networking_backend = match B::Networking::new(networking_settings) { 214 Ok(n) => { 215 net_send 216 .send(Ok(( 217 n.server_interface().clone(), 218 n.client_interface().clone(), 219 ))) 220 .unwrap(); 221 n 222 } 223 Err(e) => { 224 net_send.send(Err(e)).unwrap(); 225 return; 226 } 227 }; 228 let game: Arc<GameWrapper<G, E, B>> = game_recv.recv().unwrap(); 229 230 while game.exit.get().is_none() { 231 if let Err(e) = networking_backend.receive(|message| { 232 match message { 233 NetEvent::Server { connection, event } => { 234 game.server_event(connection, event) 235 } 236 NetEvent::Client { event } => game.client_event(event), 237 }; 238 }) { 239 game.error(EngineError::NetworkingBackend(e)) 240 } 241 } 242 }) 243 .unwrap(); 244 245 let result = net_recv.recv().unwrap(); 246 247 let (server, client) = result.map_err(EngineError::NetworkingBackend)?; 248 249 let game = Arc::new( 250 GameWrapper::new( 251 game, 252 settings.tick_system.clone(), 253 #[cfg(feature = "client")] 254 gpu_interface, 255 audio_interface, 256 server, 257 client, 258 ) 259 .map_err(|e| EngineError::Custom(e))?, 260 ); 261 262 { 263 // Downgrade to avoid closing already closed game 264 let game = Arc::downgrade(&game); 265 ctrlc::set_handler(move || { 266 if let Some(game) = game.upgrade() { 267 let _ = game.exit.set(Ok(())); 268 } 269 }) 270 .unwrap(); 271 } 272 273 game_send.send(game.clone()).unwrap(); 274 275 #[cfg(not(feature = "client"))] 276 { 277 tick_system::run(game.clone()); 278 game.end(); 279 use let_engine_core::backend::networking::{ClientInterface, ServerInterface}; 280 let _ = game.backends.server.stop(); 281 let _ = game.backends.client.disconnect(); 282 } 283 284 #[cfg(feature = "client")] 285 { 286 let mut engine = Self { 287 #[cfg(feature = "client")] 288 gpu_backend, 289 #[cfg(feature = "client")] 290 window_settings: settings.window, 291 #[cfg(feature = "client")] 292 delta_time_window: VecDeque::new(), 293 #[cfg(feature = "client")] 294 oldest_fps_sample_age: settings.oldest_fps_sample, 295 game: game.clone(), 296 }; 297 298 let game_clone = game.clone(); 299 let tick_system_join_thread = std::thread::Builder::new() 300 .name("let-engine-tick-system".to_owned()) 301 .spawn(move || { 302 tick_system::run(game_clone); 303 }) 304 .unwrap(); 305 306 event_loop.run_app(&mut engine).unwrap(); 307 308 tick_system_join_thread.join().unwrap(); 309 } 310 311 networking_join_handle.join().unwrap(); 312 313 let game = Arc::into_inner(game).unwrap(); 314 315 game.exit.into_inner().unwrap() 316 } 317 } 318 319 #[doc(hidden)] 320 #[cfg(feature = "client")] 321 impl<G, E, B> ApplicationHandler for Engine<G, E, B> 322 where 323 G: Game<B, E>, 324 E: CustomError, 325 B: Backends, 326 { 327 fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) { 328 let window: Arc<winit::window::Window> = event_loop 329 .create_window(self.window_settings.clone().into()) 330 .unwrap() 331 .into(); 332 333 self.game.window.set(Window::new(window.clone())).unwrap(); 334 335 self.gpu_backend.init_window(event_loop, &window); 336 337 self.game.window_ready(); 338 } 339 340 fn window_event( 341 &mut self, 342 event_loop: &winit::event_loop::ActiveEventLoop, 343 _window_id: winit::window::WindowId, 344 event: winit::event::WindowEvent, 345 ) { 346 use winit::event::WindowEvent; 347 348 self.game.input.lock().update(&event); 349 350 #[cfg(feature = "egui")] 351 if self.gpu_backend.update_egui(&event) { 352 return; 353 } 354 355 let window_event = match event { 356 WindowEvent::Resized(size) => { 357 let size = uvec2(size.width, size.height); 358 self.gpu_backend.resize_event(size); 359 events::WindowEvent::Resized(size) 360 } 361 WindowEvent::CloseRequested => events::WindowEvent::CloseRequested, 362 WindowEvent::CursorEntered { .. } => events::WindowEvent::CursorEntered, 363 WindowEvent::CursorLeft { .. } => events::WindowEvent::CursorLeft, 364 WindowEvent::CursorMoved { position, .. } => { 365 events::WindowEvent::CursorMoved(dvec2(position.x, position.y)) 366 } 367 WindowEvent::Destroyed => events::WindowEvent::Destroyed, 368 WindowEvent::HoveredFile(file) => events::WindowEvent::HoveredFile(file), 369 WindowEvent::DroppedFile(file) => events::WindowEvent::DroppedFile(file), 370 WindowEvent::HoveredFileCancelled => events::WindowEvent::HoveredFileCancelled, 371 WindowEvent::Focused(focused) => events::WindowEvent::Focused(focused), 372 WindowEvent::KeyboardInput { event, .. } => { 373 self.game.input(events::InputEvent::KeyboardInput { 374 input: events::KeyboardInput { 375 physical_key: event.physical_key, 376 key: event.logical_key, 377 text: event.text, 378 key_location: event.location, 379 state: event.state, 380 repeat: event.repeat, 381 }, 382 }); 383 return; 384 } 385 WindowEvent::ModifiersChanged(_) => { 386 self.game.input(events::InputEvent::ModifiersChanged); 387 return; 388 } 389 WindowEvent::MouseInput { state, button, .. } => { 390 self.game 391 .input(events::InputEvent::MouseInput(button, state)); 392 return; 393 } 394 WindowEvent::MouseWheel { delta, .. } => events::WindowEvent::MouseWheel(match delta { 395 MouseScrollDelta::LineDelta(x, y) => ScrollDelta::LineDelta(vec2(x, y)), 396 MouseScrollDelta::PixelDelta(delta) => { 397 ScrollDelta::PixelDelta(dvec2(delta.x, delta.y)) 398 } 399 }), 400 WindowEvent::RedrawRequested => { 401 let framerate_limit = self.game.time.framerate_limit(); 402 403 if framerate_limit == Duration::ZERO { 404 event_loop.set_control_flow(winit::event_loop::ControlFlow::Poll); 405 } else { 406 event_loop.set_control_flow(winit::event_loop::ControlFlow::wait_duration( 407 framerate_limit, 408 )); 409 } 410 411 #[cfg(feature = "egui")] 412 self.game.egui(self.gpu_backend.draw_egui()); 413 414 self.game.update(); 415 416 if let Err(e) = self.gpu_backend.draw(&self.game.scene.lock(), || { 417 let window = self.game.window.get().unwrap(); 418 window.pre_present_notify(); 419 }) { 420 self.game.error(EngineError::GpuBackend(e)); 421 } 422 423 self.game 424 .time 425 .update(&mut self.delta_time_window, self.oldest_fps_sample_age); 426 427 return; 428 } 429 _ => return, 430 }; 431 432 self.game.window(window_event); 433 } 434 435 fn about_to_wait(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) { 436 if self.game.exit.get().is_some() { 437 event_loop.exit(); 438 } 439 } 440 441 fn new_events( 442 &mut self, 443 _event_loop: &winit::event_loop::ActiveEventLoop, 444 cause: winit::event::StartCause, 445 ) { 446 use winit::event::StartCause; 447 448 match cause { 449 StartCause::ResumeTimeReached { .. } | StartCause::Poll => { 450 if let Some(window) = self.game.window.get() { 451 window.request_redraw(); 452 } 453 } 454 _ => (), 455 } 456 } 457 458 #[inline] 459 fn exiting(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop) { 460 self.game.end(); 461 } 462 463 fn device_event( 464 &mut self, 465 _event_loop: &winit::event_loop::ActiveEventLoop, 466 _device_id: winit::event::DeviceId, 467 event: winit::event::DeviceEvent, 468 ) { 469 use winit::event::DeviceEvent; 470 match event { 471 DeviceEvent::MouseMotion { delta } => { 472 self.game.input(events::InputEvent::MouseMotion(glam::vec2( 473 delta.0 as f32, 474 delta.1 as f32, 475 ))); 476 } 477 DeviceEvent::MouseWheel { delta } => { 478 self.game.input(events::InputEvent::MouseWheel(match delta { 479 MouseScrollDelta::LineDelta(x, y) => ScrollDelta::LineDelta(glam::vec2(x, y)), 480 MouseScrollDelta::PixelDelta(delta) => { 481 ScrollDelta::PixelDelta(dvec2(delta.x, delta.y)) 482 } 483 })); 484 } 485 _ => (), 486 } 487 } 488 } 489 490 #[derive(Clone)] 491 pub(crate) struct BackendInterfaces<B: Backends> { 492 pub tick_system: Arc<TickSystem>, 493 #[cfg(feature = "client")] 494 pub gpu: <B::Gpu as GpuBackend>::Interface, 495 pub audio: AudioInterface<B::Kira>, 496 pub server: <B::Networking as NetworkingBackend>::ServerInterface, 497 pub client: <B::Networking as NetworkingBackend>::ClientInterface, 498 } 499 500 unsafe impl<B: Backends> Send for BackendInterfaces<B> {} 501 unsafe impl<B: Backends> Sync for BackendInterfaces<B> {} 502 503 pub(super) struct GameWrapper<G, E, B> 504 where 505 G: Game<B, E>, 506 E: CustomError, 507 B: Backends, 508 { 509 pub game: Mutex<G>, 510 pub exit: OnceLock<Result<(), EngineError<E, B>>>, 511 512 pub time: Time, 513 #[cfg(feature = "client")] 514 pub input: Mutex<Input>, 515 516 pub scene: Mutex<Scene<<B::Gpu as GpuBackend>::LoadedTypes>>, 517 518 #[cfg(feature = "client")] 519 pub window: OnceLock<Window>, 520 521 pub backends: BackendInterfaces<B>, 522 } 523 524 impl<G, E, B> GameWrapper<G, E, B> 525 where 526 G: Game<B, E>, 527 E: CustomError, 528 B: Backends, 529 { 530 // TODO: define settings inside of here. 531 pub fn new( 532 game: impl FnOnce(EngineContext<E, B>) -> Result<G, E>, 533 tick_settings: tick_system::TickSettings, 534 #[cfg(feature = "client")] gpu: <B::Gpu as GpuBackend>::Interface, 535 audio: AudioInterface<<B as Backends>::Kira>, 536 server: <B::Networking as NetworkingBackend>::ServerInterface, 537 client: <B::Networking as NetworkingBackend>::ClientInterface, 538 ) -> Result<Self, E> { 539 let exit = OnceLock::new(); 540 541 let time = Time::new(); 542 #[cfg(feature = "client")] 543 let input = Mutex::new(Input::default()); 544 let scene = Mutex::new(Scene::default()); 545 #[cfg(feature = "client")] 546 let window = OnceLock::new(); 547 let backends = BackendInterfaces::<B> { 548 tick_system: Arc::new(TickSystem::new(tick_settings)), 549 #[cfg(feature = "client")] 550 gpu, 551 audio, 552 server, 553 client, 554 }; 555 556 let game = { 557 let mut scene = scene.lock(); 558 #[cfg(feature = "client")] 559 let mut input = input.lock(); 560 561 let context = EngineContext::<E, B>::new( 562 &exit, 563 &time, 564 &mut scene, 565 #[cfg(feature = "client")] 566 &mut input, 567 #[cfg(feature = "client")] 568 &window, 569 &backends, 570 ); 571 572 Mutex::new(game(context)?) 573 }; 574 575 Ok(Self { 576 game, 577 exit, 578 time, 579 #[cfg(feature = "client")] 580 input, 581 scene, 582 #[cfg(feature = "client")] 583 window, 584 backends, 585 }) 586 } 587 588 pub fn context<'a>( 589 &'a self, 590 scene: &'a mut Scene<<B::Gpu as GpuBackend>::LoadedTypes>, 591 #[cfg(feature = "client")] input: &'a mut Input, 592 ) -> EngineContext<'a, E, B> { 593 EngineContext::new( 594 &self.exit, 595 &self.time, 596 scene, 597 #[cfg(feature = "client")] 598 input, 599 #[cfg(feature = "client")] 600 &self.window, 601 &self.backends, 602 ) 603 } 604 605 #[inline] 606 #[cfg(feature = "client")] 607 pub fn update(&self) { 608 let mut scene = self.scene.lock(); 609 let mut input = self.input.lock(); 610 let context = self.context(&mut scene, &mut input); 611 if let Err(e) = self.game.lock().update(context) { 612 self.error(EngineError::Custom(e)); 613 } 614 } 615 616 #[inline] 617 #[cfg(feature = "egui")] 618 pub fn egui(&self, egui_context: egui::Context) { 619 let mut scene = self.scene.lock(); 620 let mut input = self.input.lock(); 621 let context = self.context(&mut scene, &mut input); 622 if let Err(e) = self.game.lock().egui(context, egui_context) { 623 self.error(EngineError::Custom(e)); 624 } 625 } 626 627 #[inline] 628 pub fn tick(&self) { 629 let mut scene = self.scene.lock(); 630 #[cfg(feature = "client")] 631 let mut input = self.input.lock(); 632 let context = self.context( 633 &mut scene, 634 #[cfg(feature = "client")] 635 &mut input, 636 ); 637 if let Err(e) = self.game.lock().tick(context) { 638 self.error(EngineError::Custom(e)); 639 } 640 } 641 642 #[cfg(feature = "client")] 643 #[inline] 644 pub fn window_ready(&self) { 645 let mut scene = self.scene.lock(); 646 let mut input = self.input.lock(); 647 let context = self.context(&mut scene, &mut input); 648 if let Err(e) = self.game.lock().window_ready(context) { 649 self.error(EngineError::Custom(e)); 650 } 651 } 652 653 #[cfg(feature = "client")] 654 #[inline] 655 pub fn window(&self, event: events::WindowEvent) { 656 let mut scene = self.scene.lock(); 657 let mut input = self.input.lock(); 658 let context = self.context(&mut scene, &mut input); 659 if let Err(e) = self.game.lock().window(context, event) { 660 self.error(EngineError::Custom(e)); 661 } 662 } 663 664 #[cfg(feature = "client")] 665 #[inline] 666 pub fn input(&self, event: events::InputEvent) { 667 let mut scene = self.scene.lock(); 668 let mut input = self.input.lock(); 669 let context = self.context(&mut scene, &mut input); 670 if let Err(e) = self.game.lock().input(context, event) { 671 self.error(EngineError::Custom(e)); 672 } 673 } 674 675 #[inline] 676 pub fn server_event( 677 &self, 678 connection: <<B as Backends>::Networking as NetworkingBackend>::Connection, 679 message: <<B as Backends>::Networking as NetworkingBackend>::ServerEvent<'_>, 680 ) { 681 let mut scene = self.scene.lock(); 682 #[cfg(feature = "client")] 683 let mut input = self.input.lock(); 684 let context = self.context( 685 &mut scene, 686 #[cfg(feature = "client")] 687 &mut input, 688 ); 689 if let Err(e) = self.game.lock().server_event(context, connection, message) { 690 self.error(EngineError::Custom(e)); 691 } 692 } 693 694 #[inline] 695 pub fn client_event( 696 &self, 697 message: <<B as Backends>::Networking as NetworkingBackend>::ClientEvent<'_>, 698 ) { 699 let mut scene = self.scene.lock(); 700 #[cfg(feature = "client")] 701 let mut input = self.input.lock(); 702 let context = self.context( 703 &mut scene, 704 #[cfg(feature = "client")] 705 &mut input, 706 ); 707 if let Err(e) = self.game.lock().client_event(context, message) { 708 self.error(EngineError::Custom(e)); 709 } 710 } 711 712 pub fn error(&self, message: EngineError<E, B>) { 713 let mut scene = self.scene.lock(); 714 #[cfg(feature = "client")] 715 let mut input = self.input.lock(); 716 let context = self.context( 717 &mut scene, 718 #[cfg(feature = "client")] 719 &mut input, 720 ); 721 722 if let Err(message) = self.game.lock().error(context, message) { 723 let _ = self.exit.set(Err(message)); 724 } 725 } 726 727 #[inline] 728 pub fn end(&self) { 729 use let_engine_core::backend::networking::{ClientInterface, ServerInterface}; 730 let _ = self.backends.server.stop(); 731 let _ = self.backends.client.disconnect(); 732 733 let mut scene = self.scene.lock(); 734 #[cfg(feature = "client")] 735 let mut input = self.input.lock(); 736 let context = self.context( 737 &mut scene, 738 #[cfg(feature = "client")] 739 &mut input, 740 ); 741 if let Err(e) = self.game.lock().end(context) { 742 self.error(EngineError::Custom(e)); 743 } 744 } 745 } 746 747 #[cfg(feature = "client")] 748 type GpuInterface<'a, B> = <<<B as Backends>::Gpu as GpuBackend>::Interface as GpuInterfacer< 749 <<B as Backends>::Gpu as GpuBackend>::LoadedTypes, 750 >>::Interface<'a>; 751 752 /// The context of the game engine. It is the direct interface between the engine and game state 753 /// updates. 754 /// 755 /// It contains timing, the scene, an interface to each backend and if feature `client` is 756 /// enabled, input, the window as well as the gpu backend. 757 pub struct EngineContext<'a, E = (), B = DefaultBackends> 758 where 759 E: CustomError, 760 B: Backends, 761 B::Gpu: 'a, 762 { 763 exit: &'a OnceLock<Result<(), EngineError<E, B>>>, 764 pub time: &'a Time, 765 pub scene: &'a mut Scene<<B::Gpu as GpuBackend>::LoadedTypes>, 766 #[cfg(feature = "client")] 767 pub input: &'a mut Input, 768 #[cfg(feature = "client")] 769 pub(super) window: &'a OnceLock<Window>, 770 #[cfg(feature = "client")] 771 pub gpu: GpuInterface<'a, B>, 772 pub audio: &'a AudioInterface<B::Kira>, 773 pub server: &'a <B::Networking as NetworkingBackend>::ServerInterface, 774 pub client: &'a <B::Networking as NetworkingBackend>::ClientInterface, 775 } 776 777 impl<'a, E: CustomError, B: Backends> EngineContext<'a, E, B> { 778 fn new( 779 exit: &'a OnceLock<Result<(), EngineError<E, B>>>, 780 time: &'a Time, 781 scene: &'a mut Scene<<B::Gpu as GpuBackend>::LoadedTypes>, 782 #[cfg(feature = "client")] input: &'a mut Input, 783 #[cfg(feature = "client")] window: &'a OnceLock<Window>, 784 backends: &'a BackendInterfaces<B>, 785 ) -> Self { 786 Self { 787 exit, 788 time, 789 scene, 790 #[cfg(feature = "client")] 791 input, 792 #[cfg(feature = "client")] 793 window, 794 #[cfg(feature = "client")] 795 gpu: backends.gpu.interface(), 796 audio: &backends.audio, 797 server: &backends.server, 798 client: &backends.client, 799 } 800 } 801 802 /// Returns the window in case it is initialized. 803 #[cfg(feature = "client")] 804 pub fn window(&self) -> Option<&Window> { 805 self.window.get() 806 } 807 808 /// Stops the game and lastly runs the exit function of `Game`. 809 pub fn exit(&self) { 810 let _ = self.exit.set(Ok(())); 811 } 812 } 813 814 /// Holds the timings of the engine like runtime and delta time. 815 pub struct Time { 816 /// Time since engine start. 817 time: AtomicCell<Instant>, 818 time_scale: AtomicF64, 819 820 #[cfg(feature = "client")] 821 delta_instant: AtomicCell<Instant>, 822 /// Rendering delta time 823 #[cfg(feature = "client")] 824 delta_time: AtomicF64, 825 826 #[cfg(feature = "client")] 827 fps: AtomicF64, 828 829 #[cfg(feature = "client")] 830 framerate_limit: AtomicCell<Duration>, 831 832 /// Notification to time dependent tick systems that the time scale is not zero anymore. 833 pub(crate) zero_cvar: (Mutex<()>, Condvar), 834 } 835 836 impl Time { 837 fn new() -> Self { 838 Self { 839 time: AtomicCell::new(Instant::now()), 840 time_scale: 1.0.into(), 841 #[cfg(feature = "client")] 842 delta_instant: AtomicCell::new(Instant::now()), 843 #[cfg(feature = "client")] 844 delta_time: 0.0.into(), 845 #[cfg(feature = "client")] 846 fps: 0.0.into(), 847 #[cfg(feature = "client")] 848 framerate_limit: AtomicCell::new(Duration::ZERO), 849 zero_cvar: (Mutex::new(()), Condvar::new()), 850 } 851 } 852 853 /// Updates the time data on frame redraw. 854 #[inline] 855 #[cfg(feature = "client")] 856 fn update( 857 &self, 858 time_window: &mut VecDeque<(Instant, f64)>, 859 delta_time_window_oldest_allowed_sample_age: Duration, 860 ) { 861 let now = Instant::now(); 862 let last = self.delta_instant.swap(now); 863 let delta = now.duration_since(last).as_secs_f64(); 864 865 // Add delta to time window 866 time_window.push_back((now, delta)); 867 868 // Remove out of date samples 869 while let Some(&(timestamp, _)) = time_window.front() 870 && now.duration_since(timestamp) > delta_time_window_oldest_allowed_sample_age 871 { 872 time_window.pop_front(); 873 } 874 875 // Calculate smooth delta time from the time window 876 let smooth_delta_time = if !time_window.is_empty() { 877 time_window.iter().map(|(_, dt)| *dt).sum::<f64>() / time_window.len() as f64 878 } else { 879 0.0 880 }; 881 882 self.fps.store(1.0 / smooth_delta_time, Relaxed); 883 884 self.delta_time.store(delta, Relaxed); 885 } 886 887 /// Returns the time it took to execute last iteration. 888 #[inline] 889 #[cfg(feature = "client")] 890 pub fn delta_time(&self) -> f64 { 891 self.delta_time.load(Relaxed) * self.scale() 892 } 893 894 /// Returns the delta time of the update iteration that does not scale with the time scale. 895 #[inline] 896 #[cfg(feature = "client")] 897 pub fn unscaled_delta_time(&self) -> f64 { 898 self.delta_time.load(Relaxed) 899 } 900 901 /// Returns the frames per second. 902 #[inline] 903 #[cfg(feature = "client")] 904 pub fn fps(&self) -> f64 { 905 self.fps.load(Relaxed) 906 } 907 908 /// Returns the time since start of the engine game session. 909 #[inline] 910 pub fn time(&self) -> f64 { 911 self.time.load().elapsed().as_secs_f64() 912 } 913 914 /// Returns the time scale of the game 915 #[inline] 916 pub fn scale(&self) -> f64 { 917 self.time_scale.load(Relaxed) 918 } 919 920 /// Sets the time scale of the game. 921 /// 922 /// Panics if the given time scale is negative. 923 #[inline] 924 pub fn set_scale(&self, time_scale: f64) { 925 if time_scale.is_sign_negative() { 926 panic!("A negative time scale was given."); 927 } 928 self.time_scale.store(time_scale, Relaxed); 929 if time_scale != 0.0 { 930 self.zero_cvar.1.notify_all(); 931 } 932 } 933 934 /// Sleeps the given duration times the time scale of the game engine. 935 #[inline] 936 pub fn sleep(&self, duration: Duration) { 937 spin_sleep::sleep(duration.mul_f64(self.time_scale.load(Relaxed))); 938 } 939 940 /// Sets the framerate limit as waiting time between frames. 941 /// 942 /// This should be able to be changed by the user in case they have a device with limited power capacity like a laptop with a battery. 943 /// 944 /// Setting the duration to no wait time at all will turn off the limit. 945 #[cfg(feature = "client")] 946 #[inline] 947 pub fn set_framerate_limit(&self, limit: Duration) { 948 self.framerate_limit.store(limit); 949 } 950 951 #[cfg(feature = "client")] 952 #[inline] 953 pub fn framerate_limit(&self) -> Duration { 954 self.framerate_limit.load() 955 } 956 957 /// Sets the cap for the max frames per second the game should be able to output. 958 /// 959 /// This method is the same as setting the `set_framerate_limit` of this setting to `1.0 / cap` in seconds. 960 /// 961 /// Warns when `fps` is not normal or smaller than `0` 962 #[cfg(feature = "client")] 963 #[inline] 964 pub fn set_fps_limit(&self, fps: f64) { 965 if !fps.is_normal() || fps < 0.0 { 966 log::warn!("Invalid FPS value: {}. Not changing FPS", fps); 967 return; 968 } 969 970 self.set_framerate_limit(Duration::from_secs_f64(1.0 / fps)); 971 } 972 } 973 974 #[cfg(not(feature = "client"))] 975 #[cfg(test)] 976 mod tests { 977 use crate::prelude::*; 978 979 #[test] 980 fn start_engine() { 981 struct Game { 982 number: u32, 983 } 984 impl Game { 985 pub fn new() -> Self { 986 Self { number: 0 } 987 } 988 } 989 990 impl crate::Game for Game { 991 fn tick(&mut self, context: EngineContext) -> Result<(), ()> { 992 self.number += 1; 993 if self.number > 62 { 994 context.exit(); 995 } 996 Ok(()) 997 } 998 } 999 1000 crate::start(EngineSettings::default(), |_| Ok(Game::new())).unwrap(); 1001 } 1002 }