use std::{num::NonZeroU32, sync::Arc, time::Instant};
use anyhow::Context;
use tracing_unwrap::ResultExt;
use wgpu::rwh::{HasDisplayHandle, HasWindowHandle};
use winit::{
application::ApplicationHandler,
dpi::PhysicalPosition,
event::{ElementState, KeyEvent, MouseButton, MouseScrollDelta, TouchPhase, WindowEvent},
event_loop::ActiveEventLoop,
window::Window,
};
use crate::{camera::Camera, game::Game, render::RenderingResource, scene::frame::Frame};
pub struct App<'window, G: Game> {
game: G,
resource: Option<AppResource<'window>>,
last_update: Instant,
key_events: Vec<KeyEvent>,
mouse_clicks: Vec<(ElementState, MouseButton, PhysicalPosition<f64>)>,
mouse_wheels: Vec<(MouseScrollDelta, TouchPhase, PhysicalPosition<f64>)>,
last_mouse_pos: PhysicalPosition<f64>,
}
impl<G: Game> App<'_, G> {
pub fn new(game: G) -> Self {
Self {
game,
resource: None,
last_update: Instant::now(),
key_events: Vec::new(),
mouse_clicks: Vec::new(),
mouse_wheels: Vec::new(),
last_mouse_pos: PhysicalPosition::new(0.0, 0.0),
}
}
#[tracing::instrument(level = "trace", skip(self))]
fn setup(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
if self.resource.is_none() {
self.game.init();
let scene = self.game.get_scene_mut_for_rendering();
let r = AppResource::new(event_loop, &scene.camera).unwrap_or_log();
scene.setup(&r.render);
self.resource = Some(r);
}
}
fn update(&mut self) {
if let Some(r) = self.resource.as_mut() {
let now = Instant::now();
let frame = Frame {
delta_time: now - self.last_update,
now,
key_events: self.key_events.as_slice(),
mouse_clicks: self.mouse_clicks.as_slice(),
mouse_wheels: self.mouse_wheels.as_slice(),
mouse_position: self.last_mouse_pos,
};
self.game.update(&frame);
let scene = self.game.get_scene_mut_for_rendering();
scene.update(&frame, &r.render);
self.last_update = now;
self.key_events.clear();
self.mouse_clicks.clear();
self.mouse_wheels.clear();
r.render.render(scene);
r.window.0.request_redraw();
}
}
}
impl<G: Game> ApplicationHandler for App<'_, G> {
fn new_events(
&mut self,
_event_loop: &winit::event_loop::ActiveEventLoop,
cause: winit::event::StartCause,
) {
if cause == winit::event::StartCause::Poll
&& let Some(r) = self.resource.as_ref()
{
r.window.0.request_redraw();
}
}
fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
self.setup(event_loop);
}
fn window_event(
&mut self,
_event_loop: &winit::event_loop::ActiveEventLoop,
_window_id: winit::window::WindowId,
event: winit::event::WindowEvent,
) {
match event {
WindowEvent::CloseRequested => {
self.resource = None;
}
WindowEvent::Resized(size) => {
if let Some(r) = self.resource.as_mut()
&& let (Some(width), Some(height)) =
(NonZeroU32::new(size.width), NonZeroU32::new(size.height))
{
r.render
.resize(width, height, &self.game.get_scene_for_rendering().camera);
r.window.0.request_redraw();
}
}
WindowEvent::RedrawRequested => self.update(),
WindowEvent::KeyboardInput { event, .. } => {
if self.resource.is_some() {
self.key_events.push(event);
}
}
WindowEvent::CursorMoved { position, .. } => {
self.last_mouse_pos = position;
}
WindowEvent::MouseInput { state, button, .. } => {
self.mouse_clicks.push((state, button, self.last_mouse_pos));
}
WindowEvent::MouseWheel { delta, phase, .. } => {
self.mouse_wheels.push((delta, phase, self.last_mouse_pos));
}
_ => {}
}
}
fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
if self.resource.is_none() {
event_loop.exit();
}
}
}
pub struct AppResource<'window> {
pub window: ArcWindow,
pub render: RenderingResource<'window>,
}
impl AppResource<'_> {
pub fn new(event_loop: &ActiveEventLoop, camera: &Camera) -> anyhow::Result<Self> {
let window = event_loop
.create_window(Window::default_attributes())
.unwrap_or_log();
let window = ArcWindow(Arc::new(window));
let size = window.0.inner_size();
let width = size
.width
.try_into()
.context("error: window inner width is zero")?;
let height = size
.height
.try_into()
.context("error: window inner height is zero")?;
let render = pollster::block_on(RenderingResource::setup(
window.clone(),
width,
height,
camera,
))
.context("failed: setup wgpu")?;
Ok(Self { window, render })
}
}
#[derive(Clone)]
pub struct ArcWindow(pub Arc<winit::window::Window>);
impl HasWindowHandle for ArcWindow {
fn window_handle(&self) -> Result<wgpu::rwh::WindowHandle<'_>, wgpu::rwh::HandleError> {
self.0.window_handle()
}
}
impl HasDisplayHandle for ArcWindow {
fn display_handle(&self) -> Result<wgpu::rwh::DisplayHandle<'_>, wgpu::rwh::HandleError> {
self.0.display_handle()
}
}