WebGPU Voxel Game
at main 9.0 kB view raw
1use crate::world::map::WorldMap; 2use crate::{ 3 gfx::{Gfx, GfxBuilder, MaybeGfx}, 4 gui::EguiRenderer, 5 world::World, 6 ConnectionOnlyOnNative, 7}; 8use glam::dvec2; 9use rusqlite::Connection; 10use std::sync::{Arc, Mutex}; 11use winit::{ 12 application::ApplicationHandler, 13 event::{DeviceEvent, DeviceId, ElementState, KeyEvent, WindowEvent}, 14 event_loop::{ActiveEventLoop, EventLoop}, 15 keyboard::{KeyCode, PhysicalKey}, 16 window::{Fullscreen, Window, WindowAttributes, WindowId}, 17}; 18 19pub(crate) const WASM_WIN_SIZE: (u32, u32) = (640 * 2, 480 * 2); 20 21// TODO citation: 22// https://github.com/Bentebent/rita/ for winit 29.0->30.0 migration 23// https://github.com/erer1243/wgpu-0.20-winit-0.30-web-example/blob/master/src/lib.rs For better winit 30.0 impl 24// thanks everyone. the migration is really counter-intuitive 25 26pub struct Application { 27 window_attributes: WindowAttributes, 28 gfx_state: MaybeGfx, 29 window: Option<Arc<Window>>, 30 egui: Option<EguiRenderer>, 31 world: Arc<Mutex<World>>, 32 last_render_time: instant::Instant, 33 conn: ConnectionOnlyOnNative, 34} 35 36impl Application { 37 pub fn new(event_loop: &EventLoop<Gfx>, title: &str, conn: Connection) -> Self { 38 let world = Arc::new(Mutex::new(World { 39 map: WorldMap::new(), 40 dirty: false, 41 })); 42 Self { 43 window_attributes: Window::default_attributes().with_title(title), 44 gfx_state: MaybeGfx::Builder(GfxBuilder::new(event_loop.create_proxy())), 45 window: None, 46 egui: None, 47 world: world.clone(), 48 last_render_time: instant::Instant::now(), 49 conn, 50 } 51 } 52} 53 54impl ApplicationHandler<Gfx> for Application { 55 fn resumed(&mut self, event_loop: &ActiveEventLoop) { 56 event_loop.set_control_flow(winit::event_loop::ControlFlow::Poll); 57 let window = Arc::new( 58 event_loop 59 .create_window(self.window_attributes.clone()) 60 .unwrap(), 61 ); 62 63 if let MaybeGfx::Builder(builder) = &mut self.gfx_state { 64 builder.build_and_send(window.clone()); 65 } 66 67 #[cfg(target_arch = "wasm32")] 68 { 69 // Winit prevents sizing with CSS, so we have to set 70 // the size manually when on web. 71 use winit::dpi::PhysicalSize; 72 let _ = window.request_inner_size(PhysicalSize::new(WASM_WIN_SIZE.0, WASM_WIN_SIZE.1)); 73 74 use winit::platform::web::WindowExtWebSys; 75 web_sys::window() 76 .and_then(|win| win.document()) 77 .and_then(|doc| { 78 let dst = doc.get_element_by_id("wasm-example")?; 79 let canvas = web_sys::Element::from(window.canvas()?); 80 dst.append_child(&canvas).ok()?; 81 Some(()) 82 }) 83 .expect("Couldn't append canvas to document body."); 84 } 85 86 window.request_redraw(); 87 88 self.window = Some(window); 89 } 90 91 fn device_event(&mut self, _: &ActiveEventLoop, _: DeviceId, event: DeviceEvent) { 92 if let (MaybeGfx::Graphics(gfx), DeviceEvent::MouseMotion { delta }) = 93 (&mut self.gfx_state, event) 94 { 95 if gfx.camera.mouse_focused { 96 gfx.camera 97 .controller 98 .process_mouse(dvec2(delta.0, delta.1).as_vec2()) 99 } 100 } 101 } 102 fn user_event(&mut self, _event_loop: &ActiveEventLoop, gfx: Gfx) { 103 if let Some(window) = &self.window { 104 let egui = EguiRenderer::new(&gfx.device, gfx.surface_config.format, None, 1, window); 105 self.egui = Some(egui); 106 } 107 self.gfx_state = MaybeGfx::Graphics(gfx); 108 } 109 110 fn window_event( 111 &mut self, 112 event_loop: &ActiveEventLoop, 113 _window_id: WindowId, 114 event: WindowEvent, 115 ) { 116 let MaybeGfx::Graphics(gfx) = &mut self.gfx_state else { 117 if let (WindowEvent::RedrawRequested, Some(ref window)) = (event, &self.window) { 118 window.request_redraw(); 119 } 120 return; 121 }; 122 123 if let Some(ref window) = &self.window { 124 // Returns true if EGUI consumes the input. 125 if self 126 .egui 127 .as_mut() 128 .map(|egui| egui.handle_input(window, &event)) 129 .is_some_and(std::convert::identity) 130 { 131 return; 132 } 133 134 gfx.input(&event, window.inner_size()); 135 } 136 137 match event { 138 WindowEvent::CloseRequested => event_loop.exit(), 139 140 WindowEvent::MouseInput { 141 button: winit::event::MouseButton::Left, 142 .. 143 } => { 144 if !gfx.camera.mouse_focused { 145 gfx.camera.mouse_focused = true; 146 if let Some(ref window) = &self.window { 147 match window.set_cursor_grab(winit::window::CursorGrabMode::Locked) { 148 Ok(()) => { 149 window.set_cursor_visible(false); 150 } 151 Err(e) => { 152 log::error!("{e}"); 153 } 154 } 155 } 156 } 157 } 158 159 WindowEvent::KeyboardInput { event, .. } => match event { 160 KeyEvent { 161 state: ElementState::Pressed, 162 physical_key: PhysicalKey::Code(KeyCode::KeyX), 163 .. 164 } => event_loop.exit(), 165 166 KeyEvent { 167 state: ElementState::Pressed, 168 physical_key: PhysicalKey::Code(KeyCode::Escape), 169 .. 170 } => { 171 if let Some(ref window) = &self.window { 172 if gfx.camera.mouse_focused { 173 gfx.camera.mouse_focused = false; 174 175 match window.set_cursor_grab(winit::window::CursorGrabMode::None) { 176 Ok(()) => { 177 window.set_cursor_visible(true); 178 } 179 Err(e) => { 180 log::error!("{e}"); 181 } 182 } 183 } 184 } 185 } 186 187 KeyEvent { 188 state: ElementState::Pressed, 189 physical_key: PhysicalKey::Code(KeyCode::KeyF), 190 .. 191 } => { 192 if let Some(ref window) = &self.window { 193 let fullscreen = if window.fullscreen().is_some() { 194 None 195 } else { 196 Some(Fullscreen::Borderless(None)) 197 }; 198 199 window.set_fullscreen(fullscreen); 200 } 201 } 202 _ => {} 203 }, 204 WindowEvent::Resized(physical_size) => { 205 gfx.resize(physical_size); 206 } 207 WindowEvent::RedrawRequested => { 208 // Some horrible nesting here! Don't tell Linus... 209 if let Some(ref window) = &self.window { 210 let now = instant::Instant::now(); 211 let dt = now - self.last_render_time; 212 self.last_render_time = now; 213 214 // let mut world = self.world.lock().unwrap(); 215 216 window.request_redraw(); 217 match gfx.render( 218 &mut self.egui, 219 window.clone(), 220 self.world.clone(), 221 &mut self.conn, 222 dt, 223 ) { 224 Ok(_) => { 225 // TODO CITE https://github.com/kaphula/winit-egui-wgpu-template/blob/master/src/app.rs#L3 226 gfx.update(self.world.clone(), &mut self.conn, dt); 227 } 228 Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => { 229 gfx.resize(window.inner_size()); 230 } 231 Err(wgpu::SurfaceError::OutOfMemory) => { 232 log::error!("Out of memory!"); 233 event_loop.exit(); 234 } 235 Err(wgpu::SurfaceError::Timeout) => { 236 log::warn!("Surface timeout!"); 237 } 238 Err(wgpu::SurfaceError::Other) => { 239 log::error!("Other surface-error!"); 240 event_loop.exit(); 241 } 242 } 243 } 244 } 245 _ => {} 246 } 247 } 248}