use std::mem::swap; use std::num::NonZeroU32; use std::sync::Arc; use bevy_ecs::entity::Entity; use bevy_ecs::event::Events; use bevy_ecs::query::With; use bevy_ecs::world::World; use glow::HasContext; #[cfg(not(target_arch = "wasm32"))] use glutin::surface::{GlSurface, Surface, SwapInterval, WindowSurface}; #[cfg(not(target_arch = "wasm32"))] use glutin::{ config::{ConfigTemplateBuilder, GlConfig}, context::{ContextApi, ContextAttributesBuilder, PossiblyCurrentContext}, display::GetGlDisplay, prelude::{GlDisplay, NotCurrentGlContext}, }; #[cfg(not(target_arch = "wasm32"))] use glutin_winit::{DisplayBuilder, GlWindow}; use nalgebra::Vector3; use starkingdoms_common::packet::{ButtonType, Packet, PlayerInputPacket, PlayerMouseInputPacket}; #[cfg(target_arch = "wasm32")] use wasm_bindgen::{prelude::Closure, JsCast}; #[cfg(target_arch = "wasm32")] use web_sys::{Event, HtmlCanvasElement}; use winit::event::{ElementState, MouseButton, MouseScrollDelta}; use winit::event_loop::ControlFlow; use winit::keyboard::{KeyCode, PhysicalKey}; #[cfg(target_arch = "wasm32")] use winit::platform::web::{WindowAttributesExtWebSys, WindowExtWebSys}; use winit::{ application::ApplicationHandler, dpi::LogicalSize, event::WindowEvent, event_loop::ActiveEventLoop, raw_window_handle::HasWindowHandle, window::Window, }; use crate::components::Schedule::Update; use crate::components::{Camera, Menu, RecvPacket, SendPacket}; use crate::networking::send_packet_event; use crate::networking::websocket::Websocket; use crate::platform::websocket::Ws; use crate::rendering::renderer::{RenderCreateContext, Renderer}; use crate::rendering::MaybeRenderer::{Initialized, Initializing}; use assets::AssetLoader; pub mod assets; mod renderer; enum MaybeRenderer { Uninitialized(RenderCreateContext), Initializing, Initialized(Renderer), } pub struct App { window: Option, world: World, renderer: MaybeRenderer, #[cfg(not(target_arch = "wasm32"))] gl_surface: Option>, #[cfg(not(target_arch = "wasm32"))] gl_context: Option, gl: Option>, } impl App { pub fn new( world: World, send_packet_events: Events, recv_packet_events: Events, ) -> Self { Self { window: None, world, renderer: MaybeRenderer::Uninitialized(RenderCreateContext { send_packet_events, recv_packet_events, planet_types: Default::default(), }), #[cfg(not(target_arch = "wasm32"))] gl_context: None, #[cfg(not(target_arch = "wasm32"))] gl_surface: None, gl: None, } } } impl ApplicationHandler for App { fn resumed(&mut self, event_loop: &ActiveEventLoop) { #[cfg(target_arch = "wasm32")] let attributes = { let document = web_sys::window().unwrap().document().unwrap(); let canvas = document.get_element_by_id("canvas").unwrap(); let canvas = canvas .dyn_into::() .map_err(|_| ()) .unwrap(); canvas.set_width( web_sys::window() .unwrap() .inner_width() .unwrap() .as_f64() .unwrap() as u32, ); canvas.set_height( web_sys::window() .unwrap() .inner_height() .unwrap() .as_f64() .unwrap() as u32, ); Window::default_attributes() .with_title("StarKingdoms.TK") .with_canvas(Some(canvas)) }; #[cfg(not(target_arch = "wasm32"))] let attributes = { Window::default_attributes() .with_transparent(true) .with_title("StarKingdoms.TK") .with_inner_size(LogicalSize::new(400, 300)) }; self.window = Some(event_loop.create_window(attributes.clone()).unwrap()); let window = self.window.as_ref().unwrap(); #[cfg(target_arch = "wasm32")] let context = window .canvas() .unwrap() .get_context("webgl2") .unwrap() .unwrap() .dyn_into::() .unwrap(); #[cfg(target_arch = "wasm32")] let (gl, shader_version) = ( Arc::new(glow::Context::from_webgl2_context(context)), "#version 300 es", ); #[cfg(not(target_arch = "wasm32"))] let (gl, shader_version) = unsafe { let template = ConfigTemplateBuilder::new().with_transparency(true); let display_builder = DisplayBuilder::new().with_window_attributes(Some(attributes)); let (window, gl_config) = display_builder .build(event_loop, template, |configs| { configs .reduce(|accum, config| { let supports_transparency = config.supports_transparency().unwrap_or(false) && !accum.supports_transparency().unwrap_or(false); if supports_transparency || config.num_samples() > accum.num_samples() { config } else { accum } }) .unwrap() }) .unwrap(); let raw_handle = window.as_ref().map(|window| { window .window_handle() .unwrap() .window_handle() .unwrap() .as_raw() }); let gl_display = gl_config.display(); let context_attributes = ContextAttributesBuilder::new() .with_context_api(ContextApi::OpenGl(Some(glutin::context::Version { major: 3, minor: 0, }))) .build(raw_handle); let not_current_gl_context = gl_display .create_context(&gl_config, &context_attributes) .unwrap(); let window = window.unwrap(); let surface_attributes = window.build_surface_attributes(Default::default()).unwrap(); let gl_surface = gl_display .create_window_surface(&gl_config, &surface_attributes) .unwrap(); let gl_context = not_current_gl_context.make_current(&gl_surface).unwrap(); let gl = glow::Context::from_loader_function_cstr(|s| gl_display.get_proc_address(s)); gl_surface .set_swap_interval(&gl_context, SwapInterval::Wait(NonZeroU32::new(1).unwrap())) .unwrap(); self.gl_surface = Some(gl_surface); self.gl_context = Some(gl_context); (Arc::new(gl), "#version 300 es") }; #[cfg(target_arch = "wasm32")] web_sys::window().unwrap().set_onresize(Some( Closure::::new(move |_| { let document = web_sys::window().unwrap().document().unwrap(); let canvas = document.get_element_by_id("canvas").unwrap(); let canvas = canvas .dyn_into::() .map_err(|_| ()) .unwrap(); canvas.set_width( web_sys::window() .unwrap() .inner_width() .unwrap() .as_f64() .unwrap() as u32, ); canvas.set_height( web_sys::window() .unwrap() .inner_height() .unwrap() .as_f64() .unwrap() as u32, ); }) .into_js_value() .as_ref() .unchecked_ref(), )); let mut rcc = Initializing; swap(&mut self.renderer, &mut rcc); let MaybeRenderer::Uninitialized(rcc) = rcc else { unreachable!() }; let renderer = unsafe { Renderer::new(gl.clone(), event_loop, rcc) }; self.renderer = Initialized(renderer); self.gl = Some(gl); } fn window_event( &mut self, event_loop: &ActiveEventLoop, _window_id: winit::window::WindowId, event: winit::event::WindowEvent, ) { let Initialized(renderer) = &mut self.renderer else { return; }; let Some(window) = &self.window else { return }; if renderer.egui_glow.on_window_event(window, &event).consumed { return; // egui ate it } match event { WindowEvent::CloseRequested => { event_loop.exit(); } WindowEvent::Resized(size) => { #[cfg(not(target_arch = "wasm32"))] self.gl_surface.as_ref().unwrap().resize( self.gl_context.as_ref().unwrap(), NonZeroU32::new(size.width).unwrap(), NonZeroU32::new(size.height).unwrap(), ); let mut camera = self.world.get_resource_mut::().unwrap(); camera.width = size.width; camera.height = size.height; unsafe { self.gl .as_ref() .unwrap() .viewport(0, 0, size.width as i32, size.height as i32); } } WindowEvent::MouseWheel { delta, .. } => { let mut camera = self.world.get_resource_mut::().unwrap(); let raw_delta = match delta { MouseScrollDelta::PixelDelta(pos) => pos.y as f32, MouseScrollDelta::LineDelta(y, ..) => y, }; let delta = 1.1; if raw_delta < 0.0 { camera.zoom = (camera.zoom * 1.0 / delta).max(0.13); } else { camera.zoom = (camera.zoom * delta).min(5.0); } } WindowEvent::KeyboardInput { ref event, .. } => { match event.physical_key { PhysicalKey::Code(key) => { let matched: bool; match event.state { ElementState::Pressed => match key { KeyCode::KeyW => { renderer.up = true; matched = true; } KeyCode::KeyS => { renderer.down = true; matched = true; } KeyCode::KeyA => { renderer.left = true; matched = true; } KeyCode::KeyD => { renderer.right = true; matched = true; } _ => matched = false, }, ElementState::Released => match key { KeyCode::KeyW => { renderer.up = false; matched = true; } KeyCode::KeyS => { renderer.down = false; matched = true; } KeyCode::KeyA => { renderer.left = false; matched = true; } KeyCode::KeyD => { renderer.right = false; matched = true; } _ => matched = false, }, } if matched { renderer .send_packet_events .send(SendPacket(Packet::PlayerInput(PlayerInputPacket { up: renderer.up, down: renderer.down, left: renderer.left, right: renderer.right, }))); } } PhysicalKey::Unidentified(_) => {} // unsupported } } WindowEvent::CursorMoved { position, .. } => renderer.mouse_pos = position, WindowEvent::MouseInput { state, button, .. } => { let button = match button { MouseButton::Left => ButtonType::Left, MouseButton::Middle => ButtonType::Middle, MouseButton::Right => ButtonType::Right, _ => return, }; if state.is_pressed() { let mut menu_query = self.world.query_filtered::>(); let mut menus = Vec::new(); for menu in menu_query.iter(&self.world) { menus.push(menu); } for menu in menus { self.world.entity_mut(menu).remove::(); } } let camera = self.world.get_resource::().unwrap(); let view = camera.to_cursor_matrix(); let pos = view * Vector3::new( renderer.mouse_pos.x as f32, renderer.mouse_pos.y as f32, 1.0, ); let pos = pos / pos.z; renderer .send_packet_events .send(SendPacket(Packet::PlayerMouseInput( PlayerMouseInputPacket { x: pos.x, y: pos.y, released: !state.is_pressed(), button, }, ))); } _ => {} } } fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) { let Initialized(renderer) = &mut self.renderer else { return; }; let Some(window) = &self.window else { return }; let mut ws = self .world .get_resource_mut::() .expect("Failed to get Ws resource"); let mut packets = vec![]; #[cfg(target_arch = "wasm32")] while let Ok(Some(packet)) = ws.receiver.try_next() { packets.push(packet); } #[cfg(not(target_arch = "wasm32"))] while let Ok(packet) = ws.receiver.try_recv() { packets.push(packet); } for packet in packets { send_packet_event(packet, &mut self.world); } renderer.send_packet_events.update(); renderer.recv_packet_events.update(); self.world.run_schedule(Update); let gl = self.gl.as_ref().unwrap(); unsafe { renderer.draw(gl, window, &mut self.world); } #[cfg(not(target_arch = "wasm32"))] self.gl_surface .as_ref() .unwrap() .swap_buffers(self.gl_context.as_ref().unwrap()) .unwrap(); let mut ws = self .world .get_resource_mut::() .expect("Failed to get Ws resource"); let mut send_event_cursor = renderer.send_packet_events.get_cursor(); for event in send_event_cursor.read(&renderer.send_packet_events) { ws.send_packet(event.0.clone()); } event_loop.set_control_flow(ControlFlow::WaitUntil( web_time::Instant::now() .checked_add(web_time::Duration::from_millis(16)) .unwrap(), )); } }