use crate::context::Context;
use crate::errors::CreateEncoderErrorKind;
use crate::input::UserInput;
use crate::internal::graphics::GraphicsState;
use crate::internal::menu::InitView;
use crate::internal::menu::MenuManager;
use crate::setup_context::SetupContext;
use colored::*;
use failure::Error;
use futures::lazy;
use futures::Future;
use futures::IntoFuture;
use gfx_hal::device::Device;
use gfx_hal::Backend;
use gfx_hal::Instance;
use std::fmt;
use std::fmt::Debug;
use std::fmt::Formatter;
use std::sync::Arc;
use std::thread;
use std::time::Instant;
use winit::EventsLoop;
use winit::Window;
use winit::WindowBuilder;
use std::sync::mpsc::channel;
use crate::allocator::GpuAllocator;
use glyph_brush::Layout;
const BANNER: &str = "
$$$$$$\\ $$\\ $$\\ $$\\
$$ __$$\\ $$ | $$ | $$ |
$$ / \\__|$$$$$$\\ $$$$$$\\ $$$$$$\\ $$$$$$$\\ $$$$$$\\ $$$$$$\\ $$\\ $$\\ $$$$$$$\\ $$ | $$\\
\\$$$$$$\\ \\_$$ _| \\____$$\\ $$ __$$\\ $$ _____|\\_$$ _| $$ __$$\\ $$ | $$ |$$ _____|$$ | $$ |
\\____$$\\ $$ | $$$$$$$ |$$ | \\__|\\$$$$$$\\ $$ | $$ | \\__|$$ | $$ |$$ / $$$$$$ /
$$\\ $$ | $$ |$$\\ $$ __$$ |$$ | \\____$$\\ $$ |$$\\ $$ | $$ | $$ |$$ | $$ _$$<
\\$$$$$$ | \\$$$$ |\\$$$$$$$ |$$ | $$$$$$$ | \\$$$$ |$$ | \\$$$$$$ |\\$$$$$$$\\ $$ | \\$$\\
\\______/ \\____/ \\_______|\\__| \\_______/ \\____/ \\__| \\______/ \\_______|\\__| \\__|
";
pub trait State: Send + Sync + 'static {}
impl<T: Send + Sync + 'static> State for T {}
#[allow(clippy::type_complexity)]
pub struct Starstruck<S: State, A: GpuAllocator<B, D>, B: Backend = backend::Backend, D: Device<B> = backend::Device, I: Instance<Backend = B> = backend::Instance> {
title: String,
window: Window,
events_loop: EventsLoop,
graphics_state: Arc<GraphicsState<A, B, D, I>>,
setup_context: Arc<SetupContext<A, B, D, I>>,
setup_callback: Option<Box<Future<Item = S, Error = Error> + Send>>,
render_callback: Box<FnMut(
(
&mut S,
&mut Context<A, B, D, I>,
),
) -> Result<(), Error>>
}
impl<'a, S: State, A: GpuAllocator>
Starstruck<S, A, backend::Backend, backend::Device, backend::Instance>
{
#[allow(clippy::type_complexity)]
pub(crate) fn init<R: Future<Item = S, Error = Error> + Send + 'static, I: IntoFuture<Future = R, Item = S, Error = Error> + 'static>(
title: &str,
mut setup_callback: Box<FnMut(Arc<SetupContext<A>>) -> I + Send>,
render_callback: Box<FnMut(
(
&mut S,
&mut Context<A, backend::Backend, backend::Device, backend::Instance>,
),
) -> Result<(), Error>>,
allocator: A
) -> Result<Self, Error>
{
Self::print_banner();
info!("Initializing starstruck engine");
info!("Creating new window");
let events_loop = EventsLoop::new();
let window = WindowBuilder::new().with_title(title).build(&events_loop)?;
let graphics_state = Arc::new(GraphicsState::new(title, &window, allocator)?);
let context = Arc::new(SetupContext::new(Arc::clone(&graphics_state)));
let s_callback = {
let cloned_context = Arc::clone(&context);
let future = Box::new(lazy(move || setup_callback(cloned_context)))
as Box<Future<Item = S, Error = Error> + Send>;
Some(future)
};
Ok(Self {
title: title.to_string(),
window,
events_loop,
graphics_state,
setup_context: context,
setup_callback: s_callback,
render_callback,
})
}
pub fn run(mut self) -> Result<(), Error> {
let events_loop = &mut self.events_loop;
let graphics_state = &mut self.graphics_state;
let setup = self.setup_callback.take().unwrap();
let s_context = &self.setup_context;
let render_callback = &mut self.render_callback;
let initial_view = Arc::new(InitView::new(Arc::clone(s_context)).wait()?);
let mut menu_manager =
MenuManager::new(Arc::clone(&s_context), initial_view).wait()?;
let mut user_input = UserInput::new();
let (sender, receiver) = channel();
thread::spawn(move || {
let now = Instant::now();
let r: S = setup.wait().unwrap();
info!(
"{}",
format!("Setup took {:?} to complete", now.elapsed()).magenta()
);
sender.send(r).unwrap()
});
let mut recreate_swapchain = false;
let mut end_requested = false;
let mut state = None;
let mut old_render_area = graphics_state.render_area();
let mut last_time = Instant::now();
let mut fps = 0.0;
let mut fps_show_counter = 0;
info!("Entering render loop");
loop {
let render_area = graphics_state.render_area();
if state.is_none() {
state = receiver.try_recv().ok();
if state.is_some() {
menu_manager.hide_loading_view();
}
}
if render_area != old_render_area {
menu_manager.resize(render_area);
old_render_area = render_area;
}
if recreate_swapchain {
graphics_state.recreate_swapchain(&self.window)?;
recreate_swapchain = false;
}
user_input.reset_and_poll_events(events_loop);
menu_manager.draw_text(format!("Fps: {}", fps).as_str(), 16.0, (0.0, 0.0), Layout::default());
let user_input_clone = user_input.clone();
{
if let Err(error) = graphics_state.next_encoder(|encoder| {
let mut context =
Context::new(user_input_clone, Arc::clone(&s_context), encoder, render_area);
let draw = menu_manager.should_draw_content();
if draw {
if let Some(d) = state.as_mut() {
render_callback((d, &mut context))?;
}
menu_manager.draw(&mut context)?;
}
if context.should_stop_starstruck() {
end_requested = true;
}
Ok(())
}) {
match error.kind() {
CreateEncoderErrorKind::RecreateSwapchain => {
recreate_swapchain = true;
user_input.flush();
continue;
}
CreateEncoderErrorKind::Timeout => continue,
_ => bail!(error),
}
};
};
graphics_state.present_swapchain()?;
if user_input.resized {
recreate_swapchain = true;
}
if user_input.end_requested || end_requested {
info!("Stopping starstruck");
break;
}
user_input.flush();
let now = Instant::now();
let elapsed = now.duration_since(last_time);
let e_time = elapsed.as_secs() as f64 + f64::from(elapsed.subsec_nanos()) * 1e-9;
last_time = now;
if fps_show_counter > 20 {
fps = (1.0 / e_time).round();
fps_show_counter = 0;
} else {
fps_show_counter += 1;
}
}
Ok(())
}
fn print_banner() {
println!("{}", BANNER.green());
}
}
impl<S: State, A: GpuAllocator<B, D>, B: Backend, D: Device<B>, I: Instance<Backend = B>> Debug
for Starstruck<S, A, B, D, I>
{
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self.title)?;
write!(f, "Window: {:?}", self.window)?;
write!(f, "EventsLoop: {:?}", self.events_loop)
}
}