sardonyx 0.0.3

Home automation platform, based on the Amethyst game engine
Documentation
//! The core engine framework.

use std::{env, marker::PhantomData, path::Path, sync::Arc, time::Duration};

use crate::shred::Resource;
use derivative::Derivative;
use log::{debug, info, log_enabled, trace, Level};
use rayon::ThreadPoolBuilder;
#[cfg(feature = "sentry")]
use sentry::integrations::panic::register_panic_handler;
use winit::Event;

#[cfg(feature = "profiler")]
use thread_profiler::{profile_scope, register_thread_with_profiler, write_profile};

use crate::{
    assets::{Loader, Source},
    callback_queue::CallbackQueue,
    core::{
        frame_limiter::{FrameLimiter, FrameRateLimitConfig, FrameRateLimitStrategy},
        shrev::{EventChannel, ReaderId},
        timing::{Stopwatch, Time},
        ArcThreadPool, EventReader, Named,
    },
    ecs::prelude::{Component, Read, World, WorldExt, Write},
    error::Error,
    game_data::{DataDispose, DataInit},
    state::{State, StateData, StateMachine, TransEvent},
    state_event::{StateEvent, StateEventReader},
    ui::UiEvent,
};

/// `CoreApplication` is the application implementation for the game engine. This is fully generic
/// over the state type and event type.
///
/// When starting out with sardonyx, use the type alias `Application`, which have sensible defaults
/// for the `Event` and `EventReader` generic types.
///
/// ### Type parameters:
///
/// - `T`: `State`
/// - `E`: `Event` type that should be sent to the states
/// - `R`: `EventReader` implementation for the given event type `E`
#[derive(Derivative)]
#[derivative(Debug)]
pub struct CoreApplication<'a, T, E = StateEvent, R = StateEventReader>
where
    T: DataDispose + 'static,
    E: 'static,
{
    /// The world
    #[derivative(Debug = "ignore")]
    world: World,
    #[derivative(Debug = "ignore")]
    reader: R,
    #[derivative(Debug = "ignore")]
    events: Vec<E>,
    event_reader_id: ReaderId<Event>,
    #[derivative(Debug = "ignore")]
    trans_reader_id: ReaderId<TransEvent<T, E>>,
    states: StateMachine<'a, T, E>,
    ignore_window_close: bool,
    data: T,
}

/// An Application is the root object of the game engine. It binds the OS
/// event loop, state machines, timers and other core components in a central place.
///
/// Since Application functions as the root of the game, sardonyx does not need
/// to use any global variables. Within this object is everything that your
/// game needs to run.
///
/// # Logging
///
/// sardonyx performs logging internally using the [log] crate. By default, `Application` will
/// initialize a global logger that simply sends logs to the console. You can take advantage of
/// this and use the logging macros in `log` once you've created your `Application` instance:
///
/// ```
/// use sardonyx::prelude::*;
/// use sardonyx::core::transform::{Parent, Transform};
/// use sardonyx::ecs::prelude::System;
///
/// use log::{info, warn};
///
/// struct NullState;
/// impl EmptyState for NullState {}
///
/// fn main() -> sardonyx::Result<()> {
///     sardonyx::start_logger(Default::default());
///
///     // Build the application instance to initialize the default logger.
///     let assets_dir = "assets/";
///     let mut game = Application::build(assets_dir, NullState)?
///         .build(())?;
///
///     // Now logging can be performed as normal.
///     info!("Using the default logger provided by sardonyx");
///     warn!("Uh-oh, something went wrong!");
///
///     Ok(())
/// }
/// ```
///
/// You can also setup your own logging system. Simply intialize any global logger that supports
/// [log], and it will be used instead of the default logger:
///
/// ```
/// use sardonyx::prelude::*;
/// use sardonyx::core::transform::{Parent, Transform};
/// use sardonyx::ecs::prelude::System;
///
/// struct NullState;
/// impl EmptyState for NullState {}
///
/// fn main() -> sardonyx::Result<()> {
///     // Initialize your custom logger (using env_logger in this case) before creating the
///     // `Application` instance.
///     env_logger::init();
///
///     // The default logger will be automatically disabled and any logging sardonyx does
///     // will go through your custom logger.
///     let assets_dir = "assets/";
///     let mut game = Application::build(assets_dir, NullState)?
///         .build(())?;
///
///     Ok(())
/// }
/// ```
///
/// [log]: https://crates.io/crates/log
pub type Application<'a, T> = CoreApplication<'a, T, StateEvent, StateEventReader>;

impl<'a, T, E, R> CoreApplication<'a, T, E, R>
where
    T: DataDispose + 'static,
    E: Clone + Send + Sync + 'static,
{
    /// Creates a new Application with the given initial game state.
    /// This will create and allocate all the needed resources for
    /// the event loop of the game engine. It is a shortcut for convenience
    /// if you need more control over how the engine is configured you should
    /// be using [build](struct.Application.html#method.build) instead.
    ///
    /// # Parameters
    ///
    /// - `path`: The default path for asset loading.
    ///
    /// - `initial_state`: The initial State handler of your game See
    ///   [State](trait.State.html) for more information on what this is.
    ///
    /// # Returns
    ///
    /// Returns a `Result` type wrapping the `Application` type. See
    /// [errors](struct.Application.html#errors) for a full list of
    /// possible errors that can happen in the creation of a Application object.
    ///
    /// # Type Parameters
    ///
    /// - `P`: The path type for your standard asset path.
    ///
    /// - `S`: A type that implements the `State` trait. e.g. Your initial
    ///        game logic.
    ///
    /// # Lifetimes
    ///
    /// - `a`: The lifetime of the `State` objects.
    /// - `b`: This lifetime is inherited from `specs` and `shred`, it is
    ///        the minimum lifetime of the systems used by `Application`
    ///
    /// # Errors
    ///
    /// Application will return an error if the internal thread pool fails
    /// to initialize correctly because of systems resource limitations
    ///
    /// # Examples
    ///
    /// ~~~no_run
    /// use sardonyx::prelude::*;
    ///
    /// struct NullState;
    /// impl EmptyState for NullState {}
    ///
    /// # fn main() -> sardonyx::Result<()> {
    /// #
    /// let assets_dir = "assets/";
    /// let mut game = Application::new(assets_dir, NullState, ())?;
    /// game.run();
    ///
    /// #     Ok(())
    /// # }
    /// ~~~
    pub fn new<P, S, I>(path: P, initial_state: S, init: I) -> Result<Self, Error>
    where
        P: AsRef<Path>,
        S: State<T, E> + 'a,
        I: DataInit<T>,
        for<'b> R: EventReader<'b, Event = E>,
        R: Default,
    {
        ApplicationBuilder::new(path, initial_state)?.build(init)
    }

    /// Creates a new ApplicationBuilder with the given initial game state.
    ///
    /// This is identical in function to
    /// [ApplicationBuilder::new](struct.ApplicationBuilder.html#method.new).
    pub fn build<P, S>(path: P, initial_state: S) -> Result<ApplicationBuilder<S, T, E, R>, Error>
    where
        P: AsRef<Path>,
        S: State<T, E> + 'a,
        for<'b> R: EventReader<'b, Event = E>,
    {
        ApplicationBuilder::new(path, initial_state)
    }

    /// Run the gameloop until the game state indicates that the game is no
    /// longer running. This is done via the `State` returning `Trans::Quit` or
    /// `Trans::Pop` on the last state in from the stack. See full
    /// documentation on this in [State](trait.State.html) documentation.
    ///
    /// # Examples
    ///
    /// See the example supplied in the
    /// [`new`](struct.Application.html#examples) method.
    pub fn run(&mut self)
    where
        for<'b> R: EventReader<'b, Event = E>,
    {
        #[cfg(feature = "sentry")]
        let _sentry_guard = if let Some(dsn) = option_env!("SENTRY_DSN") {
            let guard = sentry::init(dsn);
            register_panic_handler();
            Some(guard)
        } else {
            None
        };

        self.initialize();
        self.world.write_resource::<Stopwatch>().start();
        while self.states.is_running() {
            self.advance_frame();
            {
                #[cfg(feature = "profiler")]
                profile_scope!("frame_limiter wait");
                self.world.write_resource::<FrameLimiter>().wait();
            }
            {
                let elapsed = self.world.read_resource::<Stopwatch>().elapsed();
                let mut time = self.world.write_resource::<Time>();
                time.increment_frame_number();
                time.set_delta_time(elapsed);
            }
            let mut stopwatch = self.world.write_resource::<Stopwatch>();
            stopwatch.stop();
            stopwatch.restart();
        }

        self.shutdown();
    }

    /// Sets up the application.
    fn initialize(&mut self) {
        #[cfg(feature = "profiler")]
        profile_scope!("initialize");
        self.states
            .start(StateData::new(&mut self.world, &mut self.data))
            .expect("Tried to start state machine without any states present");
    }

    // React to window close events
    fn should_close(&mut self) -> bool {
        if self.ignore_window_close {
            false
        } else {
            use crate::winit::WindowEvent;
            let world = &mut self.world;
            let reader_id = &mut self.event_reader_id;
            world.exec(|ev: Read<'_, EventChannel<Event>>| {
                ev.read(reader_id).any(|e| {
                    if cfg!(target_os = "ios") {
                        if let Event::WindowEvent {
                            event: WindowEvent::Destroyed,
                            ..
                        } = e
                        {
                            true
                        } else {
                            false
                        }
                    } else if let Event::WindowEvent {
                        event: WindowEvent::CloseRequested,
                        ..
                    } = e
                    {
                        true
                    } else {
                        false
                    }
                })
            })
        }
    }

    /// Advances the automation loop by one tick.
    fn advance_frame(&mut self)
    where
        for<'b> R: EventReader<'b, Event = E>,
    {
        trace!("Advancing frame (`Application::advance_frame`)");
        if self.should_close() {
            let world = &mut self.world;
            let states = &mut self.states;
            states.stop(StateData::new(world, &mut self.data));
        }

        // Read the Trans queue and apply changes.
        {
            let mut world = &mut self.world;
            let states = &mut self.states;
            let reader = &mut self.trans_reader_id;

            let trans = world
                .read_resource::<EventChannel<TransEvent<T, E>>>()
                .read(reader)
                .map(|e| e())
                .collect::<Vec<_>>();
            for tr in trans {
                states.transition(tr, StateData::new(&mut world, &mut self.data));
            }
        }

        {
            #[cfg(feature = "profiler")]
            profile_scope!("run_callback_queue");
            let mut world = &mut self.world;
            let receiver = world.read_resource::<CallbackQueue>().receiver.clone();
            while let Ok(func) = receiver.try_recv() {
                func(&mut world);
            }
        }

        {
            #[cfg(feature = "profiler")]
            profile_scope!("handle_event");

            {
                let events = &mut self.events;
                self.reader.read(self.world.system_data(), events);
            }

            {
                let world = &mut self.world;
                let states = &mut self.states;
                for e in self.events.drain(..) {
                    states.handle_event(StateData::new(world, &mut self.data), e);
                }
            }
        }
        {
            #[cfg(feature = "profiler")]
            profile_scope!("fixed_update");

            {
                self.world.write_resource::<Time>().start_fixed_update();
            }
            while { self.world.write_resource::<Time>().step_fixed_update() } {
                self.states
                    .fixed_update(StateData::new(&mut self.world, &mut self.data));
            }
            {
                self.world.write_resource::<Time>().finish_fixed_update();
            }
        }
        {
            #[cfg(feature = "profiler")]
            profile_scope!("update");
            self.states
                .update(StateData::new(&mut self.world, &mut self.data));
        }

        #[cfg(feature = "profiler")]
        profile_scope!("maintain");
        self.world.maintain();
    }

    /// Cleans up after the quit signal is received.
    fn shutdown(&mut self) {
        info!("Engine is shutting down");
        self.data.dispose(&mut self.world);
    }
}

#[cfg(feature = "profiler")]
impl<'a, T, E, R> Drop for CoreApplication<'a, T, E, R>
where
    T: DataDispose,
{
    fn drop(&mut self) {
        // TODO: Specify filename in config.
        use crate::utils::application_root_dir;
        let app_root = application_root_dir().expect("application root dir to exist");
        let path = app_root.join("thread_profile.json");
        write_profile(path.to_str().expect("application root dir to be a string"));
    }
}

/// `ApplicationBuilder` is an interface that allows for creation of an
/// [`Application`](struct.Application.html)
/// using a custom set of configuration. This is the normal way an
/// [`Application`](struct.Application.html)
/// object is created.
#[allow(missing_debug_implementations)]
pub struct ApplicationBuilder<S, T, E, R> {
    // config: Config,
    initial_state: S,
    /// Used by bundles to access the world directly
    pub world: World,
    ignore_window_close: bool,
    phantom: PhantomData<(T, E, R)>,
}

impl<S, T, E, X> ApplicationBuilder<S, T, E, X>
where
    T: DataDispose + 'static,
{
    /// Creates a new [ApplicationBuilder](struct.ApplicationBuilder.html) instance
    /// that wraps the initial_state. This is the more verbose way of initializing
    /// your application if you require specific configuration details to be changed
    /// away from the default.
    ///
    /// # Parameters
    /// - `initial_state`: The initial State handler of your game. See
    ///   [State](trait.State.html) for more information on what this is.
    ///
    /// # Returns
    ///
    /// Returns a `Result` type wrapping the `Application` type. See
    /// [errors](struct.Application.html#errors) for a full list of
    /// possible errors that can happen in the creation of a Application object.
    ///
    /// # Type parameters
    ///
    /// - `S`: A type that implements the `State` trait. e.g. Your initial
    ///        game logic.
    ///
    /// # Lifetimes
    ///
    /// - `a`: The lifetime of the `State` objects.
    /// - `b`: This lifetime is inherited from `specs` and `shred`, it is
    ///        the minimum lifetime of the systems used by `Application`
    ///
    /// # Errors
    ///
    /// Application will return an error if the internal threadpool fails
    /// to initialize correctly because of systems resource limitations
    ///
    /// # Examples
    ///
    /// ~~~no_run
    /// use sardonyx::prelude::*;
    /// use sardonyx::core::transform::{Parent, Transform};
    /// use sardonyx::ecs::prelude::System;
    ///
    /// struct NullState;
    /// impl EmptyState for NullState {}
    ///
    /// # fn main() -> sardonyx::Result<()> {
    /// #
    /// // initialize the builder, the `ApplicationBuilder` object
    /// // follows the use pattern of most builder objects found
    /// // in the rust ecosystem. Each function modifies the object
    /// // returning a new object with the modified configuration.
    /// let assets_dir = "assets/";
    /// let mut game = Application::build(assets_dir, NullState)?
    ///
    /// // components can be registered at this stage
    ///     .register::<Parent>()
    ///     .register::<Transform>()
    ///
    /// // lastly we can build the Application object
    /// // the `build` function takes the user defined game data initializer as input
    ///     .build(())?;
    ///
    /// // the game instance can now be run, this exits only when the game is done
    /// game.run();
    ///
    /// # Ok(())
    /// # }
    /// ~~~
    pub fn new<P: AsRef<Path>>(path: P, initial_state: S) -> Result<Self, Error> {
        if !log_enabled!(Level::Error) {
            eprintln!(
                "WARNING: No logger detected! Did you forget to call `sardonyx::start_logger()`?"
            );
        }

        info!("Initializing sardonyx...");
        info!("Version: {}", env!("CARGO_PKG_VERSION"));
        info!("Platform: {}", env!("VERGEN_TARGET_TRIPLE"));
        info!("sardonyx git commit: {}", env!("VERGEN_SHA"));
        #[cfg(feature = "sentry")]
        {
            if let Some(sentry) = option_env!("SENTRY_DSN") {
                info!("Sentry DSN: {}", sentry);
            }
        }

        let rustc_meta = rustc_version_runtime::version_meta();
        info!(
            "Rustc version: {} {:?}",
            rustc_meta.semver, rustc_meta.channel
        );
        if let Some(hash) = rustc_meta.commit_hash {
            info!("Rustc git commit: {}", hash);
        }

        let thread_count: Option<usize> = env::var("sardonyx_NUM_THREADS")
            .as_ref()
            .map(|s| {
                s.as_str()
                    .parse()
                    .expect("sardonyx_NUM_THREADS was provided but is not a valid number!")
            })
            .ok();

        let mut world = World::new();

        let thread_pool_builder = ThreadPoolBuilder::new();
        #[cfg(feature = "profiler")]
        let thread_pool_builder = thread_pool_builder.start_handler(|_index| {
            register_thread_with_profiler();
        });
        let pool: ArcThreadPool;
        if let Some(thread_count) = thread_count {
            debug!("Running sardonyx with fixed thread pool: {}", thread_count);
            pool = thread_pool_builder
                .num_threads(thread_count)
                .build()
                .map(Arc::new)?;
        } else {
            pool = thread_pool_builder.build().map(Arc::new)?;
        }
        world.insert(Loader::new(path.as_ref().to_owned(), pool.clone()));
        world.insert(pool);
        world.insert(EventChannel::<Event>::with_capacity(2000));
        world.insert(EventChannel::<UiEvent>::with_capacity(40));
        world.insert(EventChannel::<TransEvent<T, StateEvent>>::with_capacity(2));
        world.insert(FrameLimiter::default());
        world.insert(Stopwatch::default());
        world.insert(Time::default());
        world.insert(CallbackQueue::default());

        world.register::<Named>();

        Ok(Self {
            initial_state,
            world,
            ignore_window_close: false,
            phantom: PhantomData,
        })
    }

    /// Registers a component into the entity-component-system. This method
    /// takes no options other than the component type which is defined
    /// using a 'turbofish'. See the example for what this looks like.
    ///
    /// You must register a component type before it can be used. If
    /// code accesses a component that has not previously been registered
    /// it will `panic`.
    ///
    /// # Type Parameters
    ///
    /// - `C`: The Component type that you are registering. This must
    ///        implement the `Component` trait to be registered.
    ///
    /// # Returns
    ///
    /// This function returns ApplicationBuilder after it has modified it
    ///
    /// # Examples
    ///
    /// ~~~no_run
    /// use sardonyx::prelude::*;
    /// use sardonyx::ecs::prelude::Component;
    /// use sardonyx::ecs::storage::HashMapStorage;
    ///
    /// struct NullState;
    /// impl EmptyState for NullState {}
    ///
    /// // define your custom type for the ECS
    /// struct Velocity([f32; 3]);
    ///
    /// // the compiler must be told how to store every component, `Velocity`
    /// // in this case. This is done via The `sardonyx::ecs::Component` trait.
    /// impl Component for Velocity {
    ///     // To do this the `Component` trait has an associated type
    ///     // which is used to associate the type back to the container type.
    ///     // There are a few common containers, VecStorage and HashMapStorage
    ///     // are the most common used.
    ///     //
    ///     // See the documentation on the ecs::Storage trait for more information.
    ///     // https://docs.rs/specs/0.9.5/specs/struct.Storage.html
    ///     type Storage = HashMapStorage<Velocity>;
    /// }
    ///
    /// // After creating a builder, we can add any number of components
    /// // using the register method.
    /// # fn main() -> sardonyx::Result<()> {
    /// let assets_dir = "assets/";
    /// let mut game = Application::build(assets_dir, NullState)?
    ///     .register::<Velocity>();
    /// #     Ok(())
    /// # }
    /// ~~~
    ///
    pub fn register<C>(mut self) -> Self
    where
        C: Component,
        C::Storage: Default,
    {
        self.world.register::<C>();
        self
    }

    /// Adds the supplied ECS resource which can be accessed from game systems.
    ///
    /// Resources are common data that is shared with one or more game system.
    ///
    /// If a resource is added with the identical type as an existing resource,
    /// the new resource will replace the old one and the old resource will
    /// be dropped.
    ///
    /// # Parameters
    /// - `resource`: The initialized resource you wish to register
    ///
    /// # Type Parameters
    ///
    /// - `R`: `resource` must implement the `Resource` trait. This trait will
    ///      be automatically implemented if `Any` + `Send` + `Sync` traits
    ///      exist for type `R`.
    ///
    /// # Returns
    ///
    /// This function returns ApplicationBuilder after it has modified it.
    ///
    /// # Examples
    ///
    /// ~~~no_run
    /// use sardonyx::prelude::*;
    ///
    /// struct NullState;
    /// impl EmptyState for NullState {}
    ///
    /// // your resource can be anything that can be safely stored in a `Arc`
    /// // in this example, it is a vector of scores with a user name
    /// struct HighScores(Vec<Score>);
    ///
    /// struct Score {
    ///     score: u32,
    ///     user: String
    /// }
    ///
    /// # fn main() -> sardonyx::Result<()> {
    /// let score_board = HighScores(Vec::new());
    /// let assets_dir = "assets/";
    /// let mut game = Application::build(assets_dir, NullState)?
    ///     .with_resource(score_board);
    /// #     Ok(())
    /// # }
    ///
    /// ~~~
    pub fn with_resource<R>(mut self, resource: R) -> Self
    where
        R: Resource,
    {
        self.world.insert(resource);
        self
    }

    /// Register an asset store with the loader logic of the Application.
    ///
    /// If the asset store exists, that shares a name with the new store the net
    /// effect will be a replacement of the older store with the new one.
    /// No warning or panic will result from this action.
    ///
    /// # Parameters
    ///
    /// - `name`: A unique name or key to identify the asset storage location. `name`
    ///           is used later to specify where the asset should be loaded from.
    /// - `store`: The asset store being registered.
    ///
    /// # Type Parameters
    ///
    /// - `I`: A `String`, or a type that can be converted into a`String`.
    /// - `S`: A `Store` asset loader. Typically this is a [`Directory`](../sardonyx_assets/struct.Directory.html).
    ///
    /// # Returns
    ///
    /// This function returns ApplicationBuilder after it has modified it.
    ///
    /// # Examples
    ///
    /// ~~~no_run
    /// use sardonyx::prelude::*;
    /// use sardonyx::assets::{Directory, Loader, Handle};
    /// use sardonyx::renderer::{Mesh, formats::mesh::ObjFormat};
    /// use sardonyx::ecs::prelude::World;
    ///
    /// # fn main() -> sardonyx::Result<()> {
    /// let assets_dir = "assets/";
    /// let mut game = Application::build(assets_dir, LoadingState)?
    ///     // Register the directory "custom_directory" under the name "resources".
    ///     .with_source("custom_store", Directory::new("custom_directory"))
    ///     .build(GameDataBuilder::default())?
    ///     .run();
    /// #     Ok(())
    /// # }
    ///
    /// struct LoadingState;
    /// impl SimpleState for LoadingState {
    ///     fn on_start(&mut self, data: StateData<'_, GameData<'_, '_>>) {
    ///         let storage = data.world.read_resource();
    ///
    ///         let loader = data.world.read_resource::<Loader>();
    ///         // Load a teapot mesh from the directory that registered above.
    ///         let mesh: Handle<Mesh> =
    ///             loader.load_from("teapot", ObjFormat, "custom_directory", (), &storage);
    ///     }
    /// }
    /// ~~~
    pub fn with_source<I, O>(self, name: I, store: O) -> Self
    where
        I: Into<String>,
        O: Source,
    {
        {
            let mut loader = self.world.write_resource::<Loader>();
            loader.add_source(name, store);
        }
        self
    }

    /// Registers the default asset store with the loader logic of the Application.
    ///
    /// # Parameters
    ///
    /// - `store`: The asset store being registered.
    ///
    /// # Type Parameters
    ///
    /// - `S`: A `Store` asset loader. Typically this is a [`Directory`](../sardonyx_assets/struct.Directory.html).
    ///
    /// # Returns
    ///
    /// This function returns ApplicationBuilder after it has modified it.
    ///
    /// # Examples
    ///
    /// ~~~no_run
    /// use sardonyx::prelude::*;
    /// use sardonyx::assets::{Directory, Loader, Handle};
    /// use sardonyx::renderer::{Mesh, formats::mesh::ObjFormat};
    /// use sardonyx::ecs::prelude::World;
    ///
    /// # fn main() -> sardonyx::Result<()> {
    /// let assets_dir = "assets/";
    /// let mut game = Application::build(assets_dir, LoadingState)?
    ///     // Register the directory "custom_directory" as default source for the loader.
    ///     .with_default_source(Directory::new("custom_directory"))
    ///     .build(GameDataBuilder::default())?
    ///     .run();
    /// #     Ok(())
    /// # }
    ///
    /// struct LoadingState;
    /// impl SimpleState for LoadingState {
    ///     fn on_start(&mut self, data: StateData<'_, GameData<'_, '_>>) {
    ///         let storage = data.world.read_resource();
    ///
    ///         let loader = data.world.read_resource::<Loader>();
    ///         // Load a teapot mesh from the directory that registered above.
    ///         let mesh: Handle<Mesh> = loader.load("teapot", ObjFormat, (), &storage);
    ///     }
    /// }
    /// ~~~
    pub fn with_default_source<O>(self, store: O) -> Self
    where
        O: Source,
    {
        {
            let mut loader = self.world.write_resource::<Loader>();
            loader.set_default_source(store);
        }
        self
    }

    /// Sets the maximum frames per second of this game.
    ///
    /// # Parameters
    ///
    /// `strategy`: the frame limit strategy to use
    /// `max_fps`: the maximum frames per second this game will run at.
    ///
    /// # Returns
    ///
    /// This function returns the ApplicationBuilder after modifying it.
    pub fn with_frame_limit(mut self, strategy: FrameRateLimitStrategy, max_fps: u32) -> Self {
        self.world.insert(FrameLimiter::new(strategy, max_fps));
        self
    }

    /// Sets the maximum frames per second of this game, based on the given config.
    ///
    /// # Parameters
    ///
    /// `config`: the frame limiter config
    ///
    /// # Returns
    ///
    /// This function returns the ApplicationBuilder after modifying it.
    pub fn with_frame_limit_config(mut self, config: FrameRateLimitConfig) -> Self {
        self.world.insert(FrameLimiter::from_config(config));
        self
    }

    /// Sets the duration between fixed updates, defaults to one sixtieth of a second.
    ///
    /// # Parameters
    ///
    /// `duration`: The duration between fixed updates.
    ///
    /// # Returns
    ///
    /// This function returns the ApplicationBuilder after modifying it.
    pub fn with_fixed_step_length(self, duration: Duration) -> Self {
        self.world.write_resource::<Time>().set_fixed_time(duration);
        self
    }

    /// Tells the resulting application window to ignore close events if ignore is true.
    /// This will make your game window unresponsive to operating system close commands.
    /// Use with caution.
    ///
    /// # Parameters
    ///
    /// `ignore`: Whether or not the window should ignore these events.  False by default.
    ///
    /// # Returns
    ///
    /// This function returns the ApplicationBuilder after modifying it.
    pub fn ignore_window_close(mut self, ignore: bool) -> Self {
        self.ignore_window_close = ignore;
        self
    }

    /// Build an `Application` object using the `ApplicationBuilder` as configured.
    ///
    /// # Returns
    ///
    /// This function returns an Application object wrapped in the Result type.
    ///
    /// # Errors
    ///
    /// This function currently will not produce an error, returning a result
    /// type was strictly for future possibilities.
    ///
    /// # Notes
    ///
    /// If the "profiler" feature is used, this function will register the thread
    /// that executed this function as the "Main" thread.
    ///
    /// # Examples
    ///
    /// See the [example show for `ApplicationBuilder::new()`](struct.ApplicationBuilder.html#examples)
    /// for an example on how this method is used.
    pub fn build<'a, I>(mut self, init: I) -> Result<CoreApplication<'a, T, E, X>, Error>
    where
        S: State<T, E> + 'a,
        I: DataInit<T>,
        E: Clone + Send + Sync + 'static,
        X: Default,
        for<'b> X: EventReader<'b, Event = E>,
    {
        trace!("Entering `ApplicationBuilder::build`");

        #[cfg(feature = "profiler")]
        register_thread_with_profiler();
        #[cfg(feature = "profiler")]
        profile_scope!("new");

        let mut reader = X::default();
        reader.setup(&mut self.world);
        let data = init.build(&mut self.world);
        let event_reader_id = self
            .world
            .exec(|mut ev: Write<'_, EventChannel<Event>>| ev.register_reader());

        let trans_reader_id = self
            .world
            .exec(|mut ev: Write<'_, EventChannel<TransEvent<T, E>>>| ev.register_reader());

        Ok(CoreApplication {
            world: self.world,
            states: StateMachine::new(self.initial_state),
            reader,
            events: Vec::new(),
            ignore_window_close: self.ignore_window_close,
            data,
            event_reader_id,
            trans_reader_id,
        })
    }
}