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,
};
#[derive(Derivative)]
#[derivative(Debug)]
pub struct CoreApplication<'a, T, E = StateEvent, R = StateEventReader>
where
T: DataDispose + 'static,
E: 'static,
{
#[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,
}
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,
{
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)
}
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)
}
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();
}
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 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
}
})
})
}
}
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));
}
{
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();
}
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) {
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"));
}
}
#[allow(missing_debug_implementations)]
pub struct ApplicationBuilder<S, T, E, R> {
initial_state: S,
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,
{
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 `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"));
#[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("AMETHYST_NUM_THREADS")
.as_ref()
.map(|s| {
s.as_str()
.parse()
.expect("AMETHYST_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 Amethyst 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,
})
}
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.insert(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_default_source<O>(self, store: O) -> Self
where
O: Source,
{
{
let mut loader = self.world.write_resource::<Loader>();
loader.set_default_source(store);
}
self
}
pub fn with_frame_limit(mut self, strategy: FrameRateLimitStrategy, max_fps: u32) -> Self {
self.world.insert(FrameLimiter::new(strategy, max_fps));
self
}
pub fn with_frame_limit_config(mut self, config: FrameRateLimitConfig) -> Self {
self.world.insert(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, 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,
})
}
}