use crate::asset::{AssetState, AssetStateContract};
use crate::audio::AudioState;
use crate::event::EventConverter;
use crate::graphics::{graphics, OpenGLState, OpenGLWindowContract, WindowSettings};
use crate::time::Instant;
use crate::App;
use core::sync::atomic::{AtomicBool, Ordering};
use core::time::Duration;
use log::info;
use winit::event::Event as WinitEvent;
use winit::event_loop::ControlFlow;
#[no_mangle]
static mut INITIALIZED: AtomicBool = AtomicBool::new(false);
pub struct Context<A: App> {
assets: AssetState<A>,
stop: bool,
control_flow: Option<ControlFlow>,
last_update: Instant,
wait_next: Instant,
wait_periodic: Option<Duration>,
}
pub fn start<A: App>(desc: WindowSettings) -> ! {
if unsafe { INITIALIZED.swap(true, Ordering::Relaxed) } {
panic!("Start has already been called.");
}
init_logger();
let event_loop = winit::event_loop::EventLoop::new();
OpenGLState::init(&desc, &event_loop);
AudioState::init();
let assets = AssetState::init();
let mut ctx = Context {
assets,
stop: false,
control_flow: Some(ControlFlow::Poll),
last_update: Instant::now(),
wait_next: Instant::now(),
wait_periodic: None,
};
let mut input = EventConverter::new();
let mut app = A::new(&mut ctx);
event_loop.run(move |event, _, control_flow| {
match event {
WinitEvent::DeviceEvent {
..
} => {
input.push(event, &mut ctx, &mut app);
}
WinitEvent::WindowEvent {
..
} => {
input.push(event, &mut ctx, &mut app);
}
WinitEvent::MainEventsCleared => {
while let Some(response) = ctx.assets.next() {
response.call(&mut ctx, &mut app);
}
let now = Instant::now();
if now >= ctx.wait_next {
{
profiling::scope!("storm_update");
if let Some(duration) = ctx.wait_periodic {
ctx.wait_next += duration;
if ctx.wait_next < now {
ctx.wait_next = now;
}
ctx.control_flow = Some(ControlFlow::WaitUntil(ctx.wait_next));
}
let delta = now - ctx.last_update;
ctx.last_update = now;
app.on_update(&mut ctx, delta.as_secs_f32());
}
graphics().window().swap_buffers();
}
}
WinitEvent::LoopDestroyed => {
ctx.stop = true;
}
_ => {}
}
if ctx.stop {
*control_flow = ControlFlow::Exit;
} else if let Some(next_control_flow) = ctx.control_flow {
*control_flow = next_control_flow;
ctx.control_flow = None;
}
});
}
#[cfg(not(target_arch = "wasm32"))]
fn init_logger() {
use simplelog::*;
match TermLogger::init(LevelFilter::Trace, Config::default(), TerminalMode::Stdout, ColorChoice::Auto) {
Ok(_) => info!("Using the default logger: simplelog::loggers::termlog."),
Err(_) => info!("Using the provided logger."),
}
}
#[cfg(target_arch = "wasm32")]
fn init_logger() {
console_error_panic_hook::set_once();
match console_log::init_with_level(log::Level::Trace) {
Ok(_) => info!("Using the default logger: console_log."),
Err(_) => info!("Using the provided logger."),
}
}
impl<A: App> Context<A> {
pub(crate) fn assets(&mut self) -> &mut AssetState<A> {
&mut self.assets
}
pub fn request_stop(&mut self) {
self.stop = true;
}
pub fn wait_for(&mut self, duration: Duration) {
self.wait_until(Instant::now() + duration);
}
pub fn wait_until(&mut self, instant: Instant) {
if instant > self.wait_next {
self.wait_next = instant;
self.control_flow = Some(ControlFlow::WaitUntil(self.wait_next));
}
}
pub fn wait_periodic(&mut self, duration: Option<Duration>) {
self.wait_periodic = duration;
}
}