use std::{cell::RefCell, rc::Rc, sync::Arc}; use pollster::FutureExt; use wgpu::{ naga::FastHashMap, Backends, CommandEncoder, Device, DeviceDescriptor, Features, Instance, InstanceDescriptor, InstanceFlags, Queue, RequestAdapterOptions, Surface, SurfaceConfiguration, Texture, TextureFormat, TextureUsages }; use winit::{ dpi::PhysicalSize, event::{ElementState, KeyEvent, WindowEvent}, keyboard::{KeyCode, PhysicalKey}, window::Window }; use crate::game::Game; pub struct Engine<'a> { surface: Surface<'a>, pub device: Device, pub queue: Queue, pub config: SurfaceConfiguration, pub size: PhysicalSize, pub window: Arc, keys: Keys, focused: bool, state: Rc>, } #[derive(Debug, Default)] pub enum KeyState { #[default] Released, Pressed, Held, } impl KeyState { fn is_down(&self) -> bool { match self { Self::Released => false, Self::Pressed | Self::Held => true, } } } #[derive(Default)] pub struct Keys { values: FastHashMap, } impl Keys { fn update_key(&mut self, key: KeyCode, state: ElementState) { let key_state = self.values.entry(key).or_default(); if key_state.is_down() != state.is_pressed() { *key_state = if state.is_pressed() { KeyState::Pressed } else { KeyState::Released } } } fn tick_keys(&mut self) { for (key, state) in &mut self.values { if state.is_down() { *state = KeyState::Held; } } } pub fn is_key_down(&self, key: KeyCode) -> bool { if let Some(key_state) = self.values.get(&key) { key_state.is_down() } else { false } } pub fn was_key_released(&self, key: KeyCode) -> bool { !self.is_key_down(key) } pub fn was_key_pressed(&self, key: KeyCode) -> bool { matches!(self.values.get(&key), Some(KeyState::Pressed)) } } impl<'a> Engine<'a> { pub fn new(window: Arc) -> Self { let instance = Instance::new(InstanceDescriptor { backends: Backends::all(), flags: InstanceFlags::advanced_debugging(), ..Default::default() }); let surface = instance.create_surface(window.clone()).unwrap(); let adapter = instance .request_adapter(&RequestAdapterOptions { compatible_surface: Some(&surface), ..Default::default() }) .block_on() .unwrap(); let (device, queue) = adapter .request_device( &DeviceDescriptor { label: Some("device"), required_features: Features::empty(), ..Default::default() }, None, ) .block_on() .unwrap(); let size = window.inner_size(); let caps = surface.get_capabilities(&adapter); println!("formats: {:#?}", caps.formats); let format = caps.formats.iter().find(|f| matches!(f, TextureFormat::Bgra8Unorm)).cloned().unwrap_or(caps.formats[0]); let config = SurfaceConfiguration { usage: TextureUsages::RENDER_ATTACHMENT, format, width: size.width, height: size.height, present_mode: caps.present_modes[0], desired_maximum_frame_latency: 2, alpha_mode: caps.alpha_modes[0], view_formats: vec![], }; surface.configure(&device, &config); let viewer = Game::new(InitContext { device: &device, queue: &queue, window: &window, config: &config, }); Self { surface, device, queue, config, size, window, keys: Keys::default(), focused: false, state: Rc::new(RefCell::new(viewer)), } } pub fn handle_event(&mut self, window_event: &WindowEvent) { match window_event { WindowEvent::KeyboardInput { event: KeyEvent { physical_key: PhysicalKey::Code(key), state, repeat, .. }, .. } => { if *repeat { return; } self.keys.update_key(*key, *state); } WindowEvent::Focused(focused) => { self.focused = *focused; } _ => {} } } pub fn resize(&mut self, new_size: PhysicalSize) { if new_size.width > 0 && new_size.height > 0 { self.size = new_size; self.config.width = new_size.width; self.config.height = new_size.height; self.surface.configure(&self.device, &self.config); } } pub fn update(&mut self) { self.state.borrow_mut().update(&UpdateContext { device: &self.device, queue: &self.queue, keys: &self.keys, window: &self.window, }); self.keys.tick_keys(); } pub fn render(&self) -> Result<(), wgpu::SurfaceError> { let output = self.surface.get_current_texture()?; let mut encoder = self.device.create_command_encoder(&Default::default()); self.state.borrow().render(self, &output.texture, &mut encoder); self.queue.submit(std::iter::once(encoder.finish())); output.present(); Ok(()) } } pub struct InitContext<'a> { pub device: &'a Device, pub queue: &'a Queue, pub window: &'a Window, pub config: &'a SurfaceConfiguration, } pub struct UpdateContext<'a> { pub device: &'a Device, pub queue: &'a Queue, pub keys: &'a Keys, pub window: &'a Window, } pub trait EngineState { fn new(context: InitContext) -> Self where Self: Sized; fn update(&mut self, device: &UpdateContext); fn render(&self, engine: &Engine, surface: &Texture, encoder: &mut CommandEncoder); }