use amethyst_ui::UiEvent;
use assets::{Loader, Source};
use core::frame_limiter::{FrameLimiter, FrameRateLimitConfig, FrameRateLimitStrategy};
use core::shrev::{EventChannel, ReaderId};
use core::timing::{Stopwatch, Time};
use core::{EventReader, Named};
use ecs::common::Errors;
use ecs::prelude::{Component, World, Write, Read};
use error::{Error, Result};
use game_data::DataInit;
use log::Level;
use rayon::ThreadPoolBuilder;
use shred::Resource;
use state::{State, StateData, StateMachine};
use state_event::StateEvent;
use state_event::StateEventReader;
use std::error::Error as StdError;
use std::marker::PhantomData;
use std::path::Path;
use std::sync::Arc;
use std::time::Duration;
#[cfg(feature = "profiler")]
use thread_profiler::{register_thread_with_profiler, write_profile};
use winit::Event;
#[derive(Derivative)]
#[derivative(Debug)]
pub struct CoreApplication<'a, T, E = StateEvent, R = StateEventReader> {
#[derivative(Debug = "ignore")]
world: World,
#[derivative(Debug = "ignore")]
reader: R,
#[derivative(Debug = "ignore")]
events: Vec<E>,
event_reader_id: ReaderId<Event>,
states: StateMachine<'a, T, E>,
ignore_window_close: bool,
data: T,
}
pub type Application<'a, T> = CoreApplication<'a, T, StateEvent, StateEventReader>;
impl<'a, T, E, R> CoreApplication<'a, T, E, R>
where
E: Clone + Send + Sync + 'static,
{
pub fn new<P, S, I>(path: P, initial_state: S, init: I) -> Result<Self>
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)
}
pub fn build<P, S>(path: P, initial_state: S) -> Result<ApplicationBuilder<S, E, R>>
where
P: AsRef<Path>,
S: State<T, E> + 'a,
for<'b> R: EventReader<'b, Event = E>,
{
ApplicationBuilder::new(path, initial_state)
}
pub fn run(&mut self)
where
for<'b> R: EventReader<'b, Event = E>,
{
self.initialize();
self.world.write_resource::<Stopwatch>().start();
while self.states.is_running() {
self.advance_frame();
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();
}
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");
}
fn should_close(&mut self) -> bool {
if self.ignore_window_close {
false
} else {
use renderer::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
}
}
})
})
}
}
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));
}
{
#[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);
}
}
}
{
let do_fixed = {
let time = self.world.write_resource::<Time>();
time.last_fixed_update().elapsed() >= time.fixed_time()
};
#[cfg(feature = "profiler")]
profile_scope!("fixed_update");
if do_fixed {
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();
self.world.write_resource::<Errors>().print_and_exit();
}
fn shutdown(&mut self) {
info!("Engine is shutting down");
}
}
#[cfg(feature = "profiler")]
impl<'a, T, E, R> Drop for CoreApplication<'a, T, E, R> {
fn drop(&mut self) {
use utils::application_root_dir;
let path = format!("{}/thread_profile.json", application_root_dir());
write_profile(path.as_str());
}
}
pub struct ApplicationBuilder<S, E, R> {
initial_state: S,
pub world: World,
ignore_window_close: bool,
phantom: PhantomData<(E, R)>,
}
impl<S, E, X> ApplicationBuilder<S, E, X> {
pub fn new<'a, P: AsRef<Path>>(path: P, initial_state: S) -> Result<Self> {
use rustc_version_runtime;
if !log_enabled!(Level::Error) {
eprintln!(
"WARNING: No logger detected! Did you forget to call `amethyst::start_logger()`?"
);
}
info!("Initializing Amethyst...");
info!("Version: {}", env!("CARGO_PKG_VERSION"));
info!("Platform: {}", env!("VERGEN_TARGET_TRIPLE"));
info!("Amethyst git commit: {}", env!("VERGEN_SHA"));
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 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 = thread_pool_builder
.build()
.map(|p| Arc::new(p))
.map_err(|err| Error::Core(err.description().to_string().into()))?;
world.add_resource(Loader::new(path.as_ref().to_owned(), pool.clone()));
world.add_resource(pool);
world.add_resource(EventChannel::<Event>::with_capacity(2000));
world.add_resource(EventChannel::<UiEvent>::with_capacity(40));
world.add_resource(Errors::default());
world.add_resource(FrameLimiter::default());
world.add_resource(Stopwatch::default());
world.add_resource(Time::default());
world.register::<Named>();
Ok(ApplicationBuilder {
initial_state,
world,
ignore_window_close: false,
phantom: PhantomData,
})
}
pub fn register<C>(mut self) -> Self
where
C: Component,
C::Storage: Default,
{
self.world.register::<C>();
self
}
pub fn with_resource<R>(mut self, resource: R) -> Self
where
R: Resource,
{
self.world.add_resource(resource);
self
}
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
}
pub fn with_frame_limit(mut self, strategy: FrameRateLimitStrategy, max_fps: u32) -> Self {
self.world
.add_resource(FrameLimiter::new(strategy, max_fps));
self
}
pub fn with_frame_limit_config(mut self, config: FrameRateLimitConfig) -> Self {
self.world.add_resource(FrameLimiter::from_config(config));
self
}
pub fn with_fixed_step_length(self, duration: Duration) -> Self {
self.world.write_resource::<Time>().set_fixed_time(duration);
self
}
pub fn ignore_window_close(mut self, ignore: bool) -> Self {
self.ignore_window_close = ignore;
self
}
pub fn build<'a, T, I>(mut self, init: I) -> Result<CoreApplication<'a, T, E, X>>
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.res);
let data = init.build(&mut self.world);
let event_reader_id = self.world.exec(|mut ev : Write<EventChannel<Event>>| 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,
})
}
}