gravitron_plugin 0.2.0

Gravitron Plugin
Documentation
use std::{
  any::{type_name, Any, TypeId},
  collections::HashMap,
  marker::PhantomData,
  time::{Duration, Instant},
};

use gravitron_ecs::{
  scheduler::{Scheduler, SchedulerBuilder},
  systems::{IntoSystem, System},
  world::World,
};
use log::debug;
#[cfg(feature = "debug")]
use log::trace;

use crate::{
  config::AppConfig,
  ecs::resources::{engine_commands::EngineCommands, engine_info::EngineInfo},
  stages::{CleanupSystemStage, InitSystemStage, MainSystemStage},
};

pub struct AppBuilder<S: Stage> {
  world: World,
  init_scheduler: SchedulerBuilder<InitSystemStage>,
  main_scheduler: SchedulerBuilder<MainSystemStage>,
  cleanup_scheduler: SchedulerBuilder<CleanupSystemStage>,
  config: HashMap<TypeId, Box<dyn Any>>,
  marker: PhantomData<S>,
}

pub struct App<S: Status> {
  world: World,
  init_scheduler: Scheduler,
  main_scheduler: Scheduler,
  cleanup_scheduler: Scheduler,
  config: HashMap<TypeId, Box<dyn Any>>,
  marker: PhantomData<S>,
}

impl<S: Status> App<S> {
  #[inline]
  pub fn get_resource<R: 'static>(&self) -> Option<&R> {
    self.world.get_resource()
  }

  #[inline]
  pub fn get_resource_mut<R: 'static>(&mut self) -> Option<&mut R> {
    self.world.get_resource_mut()
  }
}

impl App<Running> {
  #[inline]
  pub fn set_resource<R: 'static>(&mut self, res: R) {
    self.world.set_resource(res);
  }

  #[inline]
  pub fn run_init(&mut self) {
    self.init_scheduler.run(&mut self.world);
  }

  #[inline]
  pub fn config<C: 'static>(&self) -> Option<&C> {
    self
      .config
      .get(&TypeId::of::<C>())
      .and_then(|c| c.downcast_ref())
  }

  pub fn run_main(&mut self) {
    let fps = self.config::<AppConfig>().unwrap().fps;

    let mut last_frame = Instant::now();
    let frame_time = Duration::from_secs(1) / fps;

    loop {
      let elapsed = last_frame.elapsed();

      if elapsed > frame_time {
        self.set_resource(EngineInfo {
          delta_time: elapsed.as_secs_f32(),
        });

        last_frame = Instant::now();

        self.main_scheduler.run(&mut self.world);

        let cmds = self
          .get_resource::<EngineCommands>()
          .expect("Failed to get Engine Commands");
        if cmds.is_shutdown() {
          debug!("Exiting game loop");
          break;
        }

        self.world.next_tick();

        #[cfg(feature = "debug")]
        trace!("Frame took {:?}", last_frame.elapsed());
      }
    }
  }

  #[inline]
  pub fn run_cleanup(mut self) -> App<Cleanup> {
    self.cleanup_scheduler.run(&mut self.world);

    App {
      world: self.world,
      init_scheduler: self.init_scheduler,
      main_scheduler: self.main_scheduler,
      cleanup_scheduler: self.cleanup_scheduler,
      config: self.config,
      marker: PhantomData,
    }
  }
}

impl<S: Stage> AppBuilder<S> {
  #[inline]
  pub fn add_init_system<I, Sy: System + 'static>(
    &mut self,
    system: impl IntoSystem<I, System = Sy>,
  ) {
    self.init_scheduler.add_system(system);
  }

  #[inline]
  pub fn add_init_system_at_stage<I, Sy: System + 'static>(
    &mut self,
    system: impl IntoSystem<I, System = Sy>,
    stage: InitSystemStage,
  ) {
    self.init_scheduler.add_system_at_stage(system, stage);
  }

  #[inline]
  pub fn add_main_system<I, Sy: System + 'static>(
    &mut self,
    system: impl IntoSystem<I, System = Sy>,
  ) {
    self.main_scheduler.add_system(system);
  }

  #[inline]
  pub fn add_main_system_at_stage<I, Sy: System + 'static>(
    &mut self,
    system: impl IntoSystem<I, System = Sy>,
    stage: MainSystemStage,
  ) {
    self.main_scheduler.add_system_at_stage(system, stage);
  }

  #[inline]
  pub fn add_cleanup_system<I, Sy: System + 'static>(
    &mut self,
    system: impl IntoSystem<I, System = Sy>,
  ) {
    self.cleanup_scheduler.add_system(system);
  }

  #[inline]
  pub fn add_cleanup_system_at_stage<I, Sy: System + 'static>(
    &mut self,
    system: impl IntoSystem<I, System = Sy>,
    stage: CleanupSystemStage,
  ) {
    self.cleanup_scheduler.add_system_at_stage(system, stage);
  }

  #[inline]
  pub fn add_resource<R: 'static>(&mut self, res: R) {
    self.world.add_resource(res);
  }

  #[inline]
  pub fn config<C: 'static>(&self) -> Option<&C> {
    self
      .config
      .get(&TypeId::of::<C>())
      .and_then(|c| c.downcast_ref())
  }

  pub(crate) fn build(mut self) -> App<Running> {
    self.world.add_resource(EngineCommands::default());
    let parallel = self.config::<AppConfig>().unwrap().parallel_systems;

    App {
      world: self.world,
      init_scheduler: self.init_scheduler.build(parallel),
      main_scheduler: self.main_scheduler.build(parallel),
      cleanup_scheduler: self.cleanup_scheduler.build(parallel),
      config: self.config,
      marker: PhantomData,
    }
  }
}

impl AppBuilder<Build> {
  #[inline]
  pub(crate) fn new() -> Self {
    Self::default()
  }

  #[inline]
  pub fn config_mut<C: 'static>(&mut self) -> Option<&mut C> {
    self
      .config
      .get_mut(&TypeId::of::<C>())
      .and_then(|c| c.downcast_mut())
  }

  #[inline]
  pub fn add_config<C: 'static>(&mut self, config: C) {
    debug!("Adding Config {}", type_name::<C>());
    self.config.insert(TypeId::of::<C>(), Box::new(config));
  }

  pub(crate) fn finalize(self) -> AppBuilder<Finalize> {
    AppBuilder {
      world: self.world,
      init_scheduler: self.init_scheduler,
      main_scheduler: self.main_scheduler,
      cleanup_scheduler: self.cleanup_scheduler,
      config: self.config,
      marker: PhantomData,
    }
  }
}

impl AppBuilder<Finalize> {
  #[inline]
  pub fn get_resource<R: 'static>(&self) -> Option<&R> {
    self.world.get_resource()
  }

  #[inline]
  pub fn get_resource_mut<R: 'static>(&mut self) -> Option<&mut R> {
    self.world.get_resource_mut()
  }
}

impl Default for AppBuilder<Build> {
  fn default() -> Self {
    let orig_hook = std::panic::take_hook();
    std::panic::set_hook(Box::new(move |panic_info| {
      orig_hook(panic_info);
      std::process::exit(1);
    }));

    let mut builder = Self {
      world: Default::default(),
      init_scheduler: Default::default(),
      main_scheduler: Default::default(),
      cleanup_scheduler: Default::default(),
      config: Default::default(),
      marker: PhantomData,
    };

    builder.add_config(AppConfig::default());

    builder
  }
}

pub trait Stage {}

pub struct Build {}
impl Stage for Build {}

pub struct Finalize {}
impl Stage for Finalize {}

pub trait Status {}

pub struct Running {}
impl Status for Running {}

pub struct Cleanup {}
impl Status for Cleanup {}