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<Window>,
world: World,
renderer: MaybeRenderer,
#[cfg(not(target_arch = "wasm32"))]
gl_surface: Option<Surface<WindowSurface>>,
#[cfg(not(target_arch = "wasm32"))]
gl_context: Option<PossiblyCurrentContext>,
gl: Option<Arc<glow::Context>>,
}
impl App {
pub fn new(
world: World,
send_packet_events: Events<SendPacket>,
recv_packet_events: Events<RecvPacket>,
) -> 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::<web_sys::HtmlCanvasElement>()
.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::<web_sys::WebGl2RenderingContext>()
.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::<dyn Fn(Event)>::new(move |_| {
let document = web_sys::window().unwrap().document().unwrap();
let canvas = document.get_element_by_id("canvas").unwrap();
let canvas = canvas
.dyn_into::<web_sys::HtmlCanvasElement>()
.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::<Camera>().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::<Camera>().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::<Entity, With<Menu>>();
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::<Menu>();
}
}
let camera = self.world.get_resource::<Camera>().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::<Ws>()
.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::<Ws>()
.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(),
));
}
}