/ let-engine / src / engine.rs
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  }