mod document;
mod screen;
mod text;
use std::{sync::Arc, time::Duration};
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;
use async_mutex::Mutex as AsyncMutex;
use document::Project;
use winit::{
dpi,
event::*,
event_loop::{ControlFlow, EventLoop, EventLoopBuilder},
window::WindowBuilder,
};
const WINDOW_WIDTH: u32 = 900;
const WINDOW_HEIGHT: u32 = 600;
const LOGIC_DURATION: Duration = Duration::from_millis(16);
#[cfg_attr(target_arch = "wasm32", wasm_bindgen(start))]
#[cfg(feature = "web")]
pub async fn main_web() {
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
console_log::init_with_level(log::Level::Warn).expect("Couldn't initialize logger");
run().await.unwrap()
}
pub async fn run() -> color_eyre::Result<()> {
#[cfg(not(any(feature = "web", feature = "desktop")))]
compile_error!("Must compile with platform feature");
let event_loop: EventLoop<()> = EventLoopBuilder::new().build()?;
#[cfg_attr(not(target_arch = "wasm32"), allow(unused_mut))] let mut builder = WindowBuilder::new()
.with_inner_size(dpi::LogicalSize::new(WINDOW_WIDTH, WINDOW_HEIGHT))
.with_min_inner_size(dpi::PhysicalSize::new(WINDOW_WIDTH, WINDOW_HEIGHT));
#[cfg(target_arch = "wasm32")]
{
use winit::platform::web::WindowBuilderExtWebSys;
builder = builder.with_append(true);
}
let window = builder.build(&event_loop)?;
#[cfg(target_arch = "wasm32")]
{
use winit::platform::web::WindowExtWebSys;
let on_document = |doc: web_sys::Document| {
if let Some(canvas) = window.canvas() {
let dst = doc.get_element_by_id("game")?;
dst.append_child(&canvas).ok()?;
}
Some(())
};
if web_sys::window()
.and_then(|win| win.document())
.and_then(on_document)
.is_none()
{
log::warn!("Failed to find an element with 'game' id, leaving window appended");
}
}
event_loop.set_control_flow(ControlFlow::Poll);
let main_window_id = window.id();
let game = match Project::new(window).await {
Ok(game) => game,
Err(e) => {
log::error!("{}", e);
return Err(e);
}
};
let game = Arc::new(AsyncMutex::new(game));
let mut outer_cosync: cosync::Cosync<()> = cosync::Cosync::new();
let mut tick = instant::Instant::now();
let mut accum = Duration::ZERO;
event_loop.run(move |mut event, elwt| match event {
Event::WindowEvent {
window_id,
ref mut event,
} if window_id == main_window_id => {
if !game.try_lock().unwrap().input(event) {
match event {
WindowEvent::CloseRequested => elwt.exit(),
WindowEvent::RedrawRequested => {
puffin::GlobalProfiler::lock().new_frame();
let mut game = game.try_lock().unwrap();
match game.render() {
Ok(()) => {}
Err(screen::RenderError::Wgpu(wgpu::SurfaceError::Lost)) => {
game.screen().reconfigure()
}
Err(screen::RenderError::Wgpu(wgpu::SurfaceError::OutOfMemory)) => {
elwt.exit()
}
Err(e) => log::warn!("Encountered render error: {e:?}"),
}
}
WindowEvent::ScaleFactorChanged {
scale_factor,
ref mut inner_size_writer,
} => {
inner_size_writer
.request_inner_size(dpi::PhysicalSize::new(
WINDOW_WIDTH * *scale_factor as u32,
WINDOW_HEIGHT * *scale_factor as u32,
))
.unwrap();
}
WindowEvent::Resized(phys_size) => {
let mut game = game.try_lock().unwrap();
game.screen_mut().resize(*phys_size);
}
_ => {}
}
}
}
Event::Resumed => {
let game = game.clone();
outer_cosync.queue(|_input: cosync::CosyncInput<()>| async move {
game.try_lock().unwrap().create_surface().await.unwrap();
})
}
Event::AboutToWait => {
outer_cosync.run_until_stall(&mut ());
let elapsed = tick.elapsed();
tick = instant::Instant::now();
accum += elapsed;
let mut game = game.try_lock().unwrap();
while accum >= LOGIC_DURATION {
accum -= LOGIC_DURATION;
game.update(LOGIC_DURATION);
}
game.screen().window().request_redraw();
}
_ => {}
})?;
Ok(())
}