~starkingdoms/starkingdoms

65c596f596f5f282e407803c3fac1033b1f9d6fd — ghostly_zsh 8 months ago ba254ec
2nd client rewrite
52 files changed, 0 insertions(+), 1662 deletions(-)

R crates/client/src/{textures => assets}/autoplr_cfg.svg
R crates/client/src/{textures => assets}/autoplr_error.svg
R crates/client/src/{textures => assets}/autoplr_on.svg
R crates/client/src/{textures => assets}/cargo_off.svg
R crates/client/src/{textures => assets}/cargo_on.svg
R crates/client/src/{textures => assets}/earth.svg
R crates/client/src/{textures => assets}/earth_icon.svg
R crates/client/src/{textures => assets}/ecothruster_on.svg
R crates/client/src/{textures => assets}/editor_direction.svg
R crates/client/src/{textures => assets}/f.png
R crates/client/src/{textures => assets}/happy-tree.png
R crates/client/src/{textures => assets}/hearty.svg
R crates/client/src/{textures => assets}/hearty_ferris.svg
R crates/client/src/{textures => assets}/hearty_party.svg
R crates/client/src/{textures => assets}/hub_off.svg
R crates/client/src/{textures => assets}/hub_on.svg
R crates/client/src/{textures => assets}/landingleg.svg
R crates/client/src/{textures => assets}/landingthruster_off.svg
R crates/client/src/{textures => assets}/landingthruster_on.svg
R crates/client/src/{textures => assets}/mars.svg
R crates/client/src/{textures => assets}/mars_icon.svg
R crates/client/src/{textures => assets}/missing.svg
R crates/client/src/{textures => assets}/moon.svg
R crates/client/src/{textures => assets}/moon_icon.svg
R crates/client/src/{textures => assets}/powerhub_off.svg
R crates/client/src/{textures => assets}/powerhub_on.svg
R crates/client/src/{textures => assets}/starfield.svg
R crates/client/src/{textures => assets}/starfield_dim.svg
R crates/client/src/{textures => assets}/starfield_transp.svg
R crates/client/src/{textures => assets}/sun.svg
R crates/client/src/{textures => assets}/sun_rings.svg
R crates/client/src/{textures => assets}/superthruster_off.svg
R crates/client/src/{textures => assets}/superthruster_on.svg
R crates/client/src/{textures => assets}/thruster_off.svg
R crates/client/src/{textures => assets}/thruster_on.svg
R crates/client/src/{textures => assets}/trackindicator.svg
R crates/client/src/{textures => assets}/uv.png
D crates/client/src/ecs.rs
D crates/client/src/input.rs
M crates/client/src/lib.rs
D crates/client/src/networking/mod.rs
D crates/client/src/networking/ws_native.rs
D crates/client/src/networking/ws_wasm.rs
D crates/client/src/rendering/assets_native.rs
D crates/client/src/rendering/assets_wasm.rs
D crates/client/src/rendering/mipmap.rs
D crates/client/src/rendering/mod.rs
D crates/client/src/rendering/renderer.rs
D crates/client/src/rendering/texture.rs
D crates/client/src/rendering/ui.rs
D crates/client/src/shaders/sprite.wgsl
D crates/client/src/shaders/text_quad_mips.wgsl
R crates/client/src/textures/autoplr_cfg.svg => crates/client/src/assets/autoplr_cfg.svg +0 -0
R crates/client/src/textures/autoplr_error.svg => crates/client/src/assets/autoplr_error.svg +0 -0
R crates/client/src/textures/autoplr_on.svg => crates/client/src/assets/autoplr_on.svg +0 -0
R crates/client/src/textures/cargo_off.svg => crates/client/src/assets/cargo_off.svg +0 -0
R crates/client/src/textures/cargo_on.svg => crates/client/src/assets/cargo_on.svg +0 -0
R crates/client/src/textures/earth.svg => crates/client/src/assets/earth.svg +0 -0
R crates/client/src/textures/earth_icon.svg => crates/client/src/assets/earth_icon.svg +0 -0
R crates/client/src/textures/ecothruster_on.svg => crates/client/src/assets/ecothruster_on.svg +0 -0
R crates/client/src/textures/editor_direction.svg => crates/client/src/assets/editor_direction.svg +0 -0
R crates/client/src/textures/f.png => crates/client/src/assets/f.png +0 -0
R crates/client/src/textures/happy-tree.png => crates/client/src/assets/happy-tree.png +0 -0
R crates/client/src/textures/hearty.svg => crates/client/src/assets/hearty.svg +0 -0
R crates/client/src/textures/hearty_ferris.svg => crates/client/src/assets/hearty_ferris.svg +0 -0
R crates/client/src/textures/hearty_party.svg => crates/client/src/assets/hearty_party.svg +0 -0
R crates/client/src/textures/hub_off.svg => crates/client/src/assets/hub_off.svg +0 -0
R crates/client/src/textures/hub_on.svg => crates/client/src/assets/hub_on.svg +0 -0
R crates/client/src/textures/landingleg.svg => crates/client/src/assets/landingleg.svg +0 -0
R crates/client/src/textures/landingthruster_off.svg => crates/client/src/assets/landingthruster_off.svg +0 -0
R crates/client/src/textures/landingthruster_on.svg => crates/client/src/assets/landingthruster_on.svg +0 -0
R crates/client/src/textures/mars.svg => crates/client/src/assets/mars.svg +0 -0
R crates/client/src/textures/mars_icon.svg => crates/client/src/assets/mars_icon.svg +0 -0
R crates/client/src/textures/missing.svg => crates/client/src/assets/missing.svg +0 -0
R crates/client/src/textures/moon.svg => crates/client/src/assets/moon.svg +0 -0
R crates/client/src/textures/moon_icon.svg => crates/client/src/assets/moon_icon.svg +0 -0
R crates/client/src/textures/powerhub_off.svg => crates/client/src/assets/powerhub_off.svg +0 -0
R crates/client/src/textures/powerhub_on.svg => crates/client/src/assets/powerhub_on.svg +0 -0
R crates/client/src/textures/starfield.svg => crates/client/src/assets/starfield.svg +0 -0
R crates/client/src/textures/starfield_dim.svg => crates/client/src/assets/starfield_dim.svg +0 -0
R crates/client/src/textures/starfield_transp.svg => crates/client/src/assets/starfield_transp.svg +0 -0
R crates/client/src/textures/sun.svg => crates/client/src/assets/sun.svg +0 -0
R crates/client/src/textures/sun_rings.svg => crates/client/src/assets/sun_rings.svg +0 -0
R crates/client/src/textures/superthruster_off.svg => crates/client/src/assets/superthruster_off.svg +0 -0
R crates/client/src/textures/superthruster_on.svg => crates/client/src/assets/superthruster_on.svg +0 -0
R crates/client/src/textures/thruster_off.svg => crates/client/src/assets/thruster_off.svg +0 -0
R crates/client/src/textures/thruster_on.svg => crates/client/src/assets/thruster_on.svg +0 -0
R crates/client/src/textures/trackindicator.svg => crates/client/src/assets/trackindicator.svg +0 -0
R crates/client/src/textures/uv.png => crates/client/src/assets/uv.png +0 -0
D crates/client/src/ecs.rs => crates/client/src/ecs.rs +0 -101
@@ 1,101 0,0 @@
use bevy_ecs::bundle::Bundle;
use bevy_ecs::component::Component;
use bevy_ecs::event::Event;
use bevy_ecs::system::Resource;
use nalgebra::Matrix3;
use starkingdoms_common::packet::Packet;
use starkingdoms_common::PlanetType;

#[derive(Component, Debug, Clone, Copy)]
pub struct Translation {
    pub x: f32,
    pub y: f32,
}
impl Translation {
    pub fn as_matrix(&self) -> Matrix3<f32> {
        Matrix3::from_iterator([
            1.0, 0.0, self.x,
            0.0, 1.0, self.y,
            0.0, 0.0, 1.0,
        ])
    }
}

#[derive(Component, Debug, Clone, Copy)]
pub struct Shear {
    pub x: f32,
    pub y: f32,
}
impl Shear {
    pub fn as_matrix(&self) -> Matrix3<f32> {
        Matrix3::from_iterator([
            1.0, self.x, 0.0,
            self.y, 1.0, 0.0,
            0.0, 0.0, 1.0,
        ])
    }
}

#[derive(Component, Debug, Clone, Copy)]
pub struct Scale {
    pub width: f32,
    pub height: f32,
}
impl Scale {
    pub fn as_matrix(&self) -> Matrix3<f32> {
        Matrix3::from_iterator([
            self.width, 0.0, 0.0,
            0.0, self.height, 0.0,
            0.0, 0.0, 1.0
        ])
    }
}
#[derive(Component, Debug, Clone, Copy)]
pub struct Rotation {
    pub radians: f32
}
impl Rotation {
    pub fn as_matrix(&self) -> Matrix3<f32> {
        let x = self.radians.cos();
        let y = self.radians.sin();
        Matrix3::from_iterator([
            x, y, 0.0,
            -y, x, 0.0,
            0.0, 0.0, 1.0
        ])
    }
}

#[derive(Component, Debug, Clone)]
pub struct SpriteTexture {
    pub texture: String,
}

#[derive(Bundle)]
pub struct SpriteBundle {
    pub position: Translation,
    pub shear: Shear,
    pub scale: Scale,
    pub texture: SpriteTexture,
    pub rotation: Rotation
}

#[derive(Resource, Debug)]
pub struct Camera {
    pub x: f32,
    pub y: f32,
    pub shear_x: f32,
    pub shear_y: f32,
    pub zoom: f32,
}

#[derive(Component, Debug, Clone, Copy)]
pub struct Player;

#[derive(Component, Debug, Clone, Copy)]
pub struct Planet(PlanetType);

#[derive(Event, Clone, PartialEq)]
pub struct SendPacket(pub Packet);
#[derive(Event, Clone, PartialEq)]
pub struct RecvPacket(pub Packet);

D crates/client/src/input.rs => crates/client/src/input.rs +0 -7
@@ 1,7 0,0 @@
use bevy_ecs::event::Event;

#[derive(Event, Debug, Copy, Clone)]
pub enum MouseWheelEvent {
    Line { x: f64, y: f64 },
    Pixel { x: f64, y: f64 },
}

M crates/client/src/lib.rs => crates/client/src/lib.rs +0 -170
@@ 1,18 1,4 @@
use crate::ecs::{Camera, Translation, Rotation, Scale, SpriteBundle, SpriteTexture};
use crate::input::MouseWheelEvent;
use crate::rendering::ui::UiRenderable;
use crate::rendering::App;
use bevy_ecs::event::{EventReader, Events};
use bevy_ecs::schedule::Schedule;
use bevy_ecs::system::ResMut;
use bevy_ecs::world::World;
use ecs::{Player, RecvPacket, SendPacket, Shear};
use egui::{Context, DragValue};
use networking::ws::Ws;
use rendering::assets::Assets;
use starkingdoms_common::packet::Packet;
use tracing::info;
use winit::event_loop::{ControlFlow, EventLoop};

#[cfg(target_arch = "wasm32")]
#[path = "wasm/mod.rs"]


@@ 21,11 7,6 @@ pub mod platform;
#[path = "native/mod.rs"]
pub mod platform;

pub mod ecs;
pub mod input;
pub mod rendering;
pub mod networking;

// Hi, you've found the real main function! This is called AFTER platform-specific initialization code.
pub fn start() {
    info!(


@@ 39,155 20,4 @@ pub fn start() {
    );

    info!("Creating the ECS world...");
    let mut world = World::new();

    world.insert_resource::<Events<MouseWheelEvent>>(Events::default());
    world.insert_resource(Camera {
        x: 0.0,
        y: 0.0,
        shear_x: 0.0,
        shear_y: 0.0,
        zoom: 1.0,
    });
    world.insert_resource(Assets::new());
    world.insert_resource(Ws::new());

    let mut send_packet_events = Events::<SendPacket>::default();
    let recv_packet_events = Events::<RecvPacket>::default();

    let mut start_schedule = Schedule::default();
    // Add startup things here
    // Caution: This will run before there are things on-screen
    start_schedule.run(&mut world);

    let mut update_schedule = Schedule::default();
    // Add things to run every frame here
    // Caution: This will run once before there are things on screen
    update_schedule.add_systems(zoom_camera_on_mouse_events);

    send_packet_events.send(SendPacket(Packet::ClientLogin {
        username: String::new(),
        save: None,
        jwt: None,
    }));

    world.spawn((SpriteBundle {
        position: Translation {
            x: 100.0,
            y: 100.0
        },
        shear: Shear {
            x: 0.0,
            y: 0.0,
        },
        scale: Scale {
            width: 100.0,
            height: 100.0,
        },
        rotation: Rotation {
            radians: 0.0_f32.to_radians()
        },
        texture: SpriteTexture {
            texture: "hearty".to_string(),
        },
    }, Player));

    let event_loop = EventLoop::new().unwrap();
    event_loop.set_control_flow(ControlFlow::Poll);
    event_loop
        .run_app(&mut App::new(world, send_packet_events, recv_packet_events, update_schedule, Gui {}))
        .unwrap();
}

fn zoom_camera_on_mouse_events(mut events: EventReader<MouseWheelEvent>, mut camera: ResMut<Camera>) {
    for event in events.read() {
        let raw_delta = match event {
            MouseWheelEvent::Line { y, .. } => *y,
            MouseWheelEvent::Pixel { y, ..} => *y,
        } as f32;

        let delta = 1.1;

        if raw_delta < 0.0 {
            camera.zoom *= 1.0 / delta;
        } else {
            camera.zoom *= delta;
        }
    }
}

pub struct Gui {}
impl UiRenderable for Gui {
    fn render(&mut self, ctx: &Context, world: &mut World) {
        egui::Window::new("Main Menu")
            .resizable(false)
            .show(ctx, |ui| {
                ui.heading("StarKingdoms.TK");
                ui.label("A game about floating through space");
                ui.separator();

                let mut sprites = world.query::<(&mut Translation, &mut Shear, &mut Scale, &SpriteTexture, &mut Rotation)>();
                /*for (mut pos, mut shear, mut scale, tex, mut rot) in sprites.iter_mut(world) {
                    ui.heading(&tex.texture);
                    
                    egui::Grid::new("sprite_grid")
                        .num_columns(2)
                        .spacing([40.0, 4.0])
                        .striped(true)
                        .show(ui, |ui| {
                            ui.label("X");
                            ui.add(DragValue::new(&mut pos.x).speed(0.1));
                            ui.end_row();

                            ui.label("Y");
                            ui.add(DragValue::new(&mut pos.y).speed(0.1));
                            ui.end_row();

                            ui.label("Shear X");
                            ui.add(DragValue::new(&mut shear.x).speed(0.1));
                            ui.end_row();

                            ui.label("Shear Y");
                            ui.add(DragValue::new(&mut shear.y).speed(0.1));
                            ui.end_row();

                            ui.label("Width");
                            ui.add(DragValue::new(&mut scale.width).speed(0.1));
                            ui.end_row();

                            ui.label("Height");
                            ui.add(DragValue::new(&mut scale.height).speed(0.1));
                            ui.end_row();

                            ui.label("Rotation");
                            ui.add(DragValue::new(&mut rot.radians).speed(0.1));
                            ui.end_row();
                        });
                }
                let mut camera = world.resource_mut::<Camera>();
                egui::Grid::new("camera_grid")
                    .num_columns(2)
                    .spacing([40.0, 4.0])
                    .show(ui, |ui| {
                        ui.label("Camera X");
                        ui.add(DragValue::new(&mut camera.x).speed(0.1));
                        ui.end_row();

                        ui.label("Camera Y");
                        ui.add(DragValue::new(&mut camera.y).speed(0.1));
                        ui.end_row();

                        ui.label("Shear X");
                        ui.add(DragValue::new(&mut camera.shear_x).speed(0.1));
                        ui.end_row();
                        ui.label("Shear Y");
                        ui.add(DragValue::new(&mut camera.shear_y).speed(0.1));
                        ui.end_row();

                        ui.label("Camera Zoom");
                        ui.add(DragValue::new(&mut camera.zoom).speed(0.1));
                        ui.end_row();
                    });*/
            });
    }
}

D crates/client/src/networking/mod.rs => crates/client/src/networking/mod.rs +0 -76
@@ 1,76 0,0 @@
use std::collections::HashMap;

use bevy_ecs::{entity::Entity, event::Events, query::With, world::World};
use starkingdoms_common::{packet::Packet, PartType, PlanetType};

use crate::ecs::{Camera, Planet, Player, RecvPacket, Rotation, Scale, SendPacket, Shear, SpriteBundle, SpriteTexture, Translation};

#[cfg(target_arch = "wasm32")]
#[path = "ws_wasm.rs"]
pub mod ws;
#[cfg(not(target_arch = "wasm32"))]
#[path = "ws_native.rs"]
pub mod ws;

pub fn process_packets(
    world: &mut World,
    send_packet_events: &mut Events<SendPacket>,
    recv_packet_events: &mut Events<RecvPacket>,
    planet_types: &mut HashMap<PlanetType, (Entity, u32)>,
) {
    use Packet::*;
    let mut recv_cursor = recv_packet_events.get_cursor();
    for recv in recv_cursor.read(&recv_packet_events) {
        match &recv.0 {
            PartPositions { parts } => {
                for (id, part) in parts {
                    if part.part_type == PartType::Hearty {
                        let mut player_query = world.query_filtered::<(&mut Translation, &mut Rotation), With<Player>>();
                        let mut camera = world.resource_mut::<Camera>();
                        camera.x = part.transform.x;
                        camera.y = part.transform.y;
                        tracing::info!("camera real {:?}", camera);
                        for (mut translation, mut rotation) in player_query.iter_mut(world) {
                            translation.x = part.transform.x;
                            translation.y = part.transform.y;
                            rotation.radians = part.transform.rot;
                            tracing::info!("part real {:?}", translation);
                        }
                        tracing::info!("part {:?}", part.transform);
                    }
                }
            }
            PlanetPositions { planets } => {
                for (server_id, planet) in planets {
                    let mut planet_query = world.query_filtered::<(&mut Translation, &mut Rotation), With<Planet>>();
                    if !planet_types.contains_key(&planet.planet_type) {
                        let entity = world.spawn(SpriteBundle {
                            position: Translation { x: planet.transform.x, y: planet.transform.y },
                            shear: Shear { x: 0.0, y: 0.0 },
                            rotation: Rotation { radians: planet.transform.rot },
                            scale: Scale { width: planet.radius, height: planet.radius },
                            texture: SpriteTexture {
                                texture: match planet.planet_type {
                                    PlanetType::Sun => "sun",
                                    PlanetType::Mercury => "mercury",
                                    PlanetType::Venus => "venus",
                                    PlanetType::Earth => "earth",
                                    PlanetType::Moon => "moon",
                                    PlanetType::Mars => "mars",
                                    PlanetType::Jupiter => "jupiter",
                                    PlanetType::Saturn => "saturn",
                                    PlanetType::Uranus => "uranus",
                                    PlanetType::Neptune => "neptune",
                                    PlanetType::Pluto => "pluto",
                                }.to_string()
                            },
                        });
                        planet_types.insert(planet.planet_type, (entity.id(), *server_id));
                        tracing::info!("{:?}", planet.transform);
                    }
                }
            }
            _ => {}
        }
    }
}

D crates/client/src/networking/ws_native.rs => crates/client/src/networking/ws_native.rs +0 -61
@@ 1,61 0,0 @@
use std::{net::TcpStream, sync::{Arc, Mutex}};

use bevy_ecs::system::Resource;
use crossbeam::channel::{unbounded, Receiver, Sender};
use starkingdoms_common::packet::{MsgFromError, Packet};
use tungstenite::{connect, stream::MaybeTlsStream, Message, WebSocket};

pub trait PacketMessageConvert {
    fn from_message(value: &Message) -> Result<Packet, MsgFromError>;
    fn into_message(&self) -> Message;
}

impl PacketMessageConvert for Packet {
    fn from_message(value: &Message) -> Result<Packet, MsgFromError> {
        match value {
            Message::Text(s) => serde_json::from_str(s).map_err(MsgFromError::JSONError),
            Message::Binary(b) => serde_json::from_slice(b).map_err(MsgFromError::JSONError),
            Message::Close(_) => Ok(Packet::_SpecialDisconnect {}),
            Message::Frame(_) | Message::Pong(_) | Message::Ping(_) => {
                Err(MsgFromError::InvalidMessageType)
            }
        }
    }
    fn into_message(&self) -> Message {
        Message::Text(serde_json::to_string(self).expect("failed to serialize packet to json").into())
    }
}

#[derive(Resource, Debug)]
pub struct Ws {
    socket: Arc<Mutex<WebSocket<MaybeTlsStream<TcpStream>>>>,
    pub sender: Sender<Packet>,
    pub receiver: Receiver<Packet>,
    packet_receiver: Receiver<Packet>,
}
impl Ws {
    pub fn new() -> Self {
        let (socket, _) = connect("ws://localhost:3000").expect("Failed to connect to server");
        let socket = Arc::new(Mutex::new(socket));
        let (packet_sender, receiver) = unbounded();
        let (sender, packet_receiver) = unbounded();
        let socket_clone = socket.clone();
        std::thread::spawn(move || {
            let socket = socket_clone;
            loop {
                let message = socket.lock().unwrap().read().expect("Failed to reading message");
                let packet = Packet::from_message(&message).expect("Server sent invalid packet");
                packet_sender.send(packet).expect("Couldn't send packet to server");
            }
        });
        Ws {
            socket,
            sender,
            receiver,
            packet_receiver,
        }
    }
    pub fn send_packet(&mut self, packet: &Packet) {
        self.socket.lock().unwrap().send(packet.into_message()).expect("Couldn't send packet to server");
    }
}

D crates/client/src/networking/ws_wasm.rs => crates/client/src/networking/ws_wasm.rs +0 -73
@@ 1,73 0,0 @@
use std::thread::yield_now;

use bevy_ecs::system::Resource;
//use crossbeam::channel::{unbounded, Receiver, Sender};
use futures::{channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender}, SinkExt};
use wasm_bindgen::{prelude::Closure, JsCast, JsValue};
use wasm_bindgen_futures::spawn_local;
use web_sys::{MessageEvent, WebSocket};
use starkingdoms_common::packet::Packet;

use crate::ecs::RecvPacket;

const PORT: u16 = 3000;

#[derive(Debug)]
pub struct Socket(WebSocket);
unsafe impl Send for Socket {}
unsafe impl Sync for Socket {}

#[derive(Resource, Debug)]
pub struct Ws {
    socket: Socket,
    pub sender: UnboundedSender<Packet>,
    pub receiver: UnboundedReceiver<Packet>,
    packet_receiver: UnboundedReceiver<Packet>,
}

impl Ws {
    pub fn new() -> Self {
        let window = web_sys::window().unwrap();
        let ws = WebSocket::new(&format!("ws://{}:{}",
                window.location().hostname().unwrap(), PORT)).expect("Couldn't connect to server");
        let (packet_sender, receiver) = unbounded();
        //let packet_sender = Rc::new(RwLock::new(packet_sender));
        let (sender, packet_receiver) = unbounded();

        /*let onopen_callback = Closure::<dyn FnMut()>::new(move || {

        });
        ws.set_onopen(Some(onopen_callback.as_ref().unchecked_ref()));
        onopen_callback.forget();*/
        let onmessage_callback = Closure::<dyn FnMut(_)>::new(move |e: MessageEvent| {
            //tracing::error!("{}", ws.ready_state());
            let data = e.data().as_string().expect("Expected string, found some other type");
            let data: Packet = serde_json::from_str(&data).expect("Received invalid json from server");
            let mut sender_clone = packet_sender.clone();
            spawn_local(async move {
                sender_clone.send(data).await.expect("Couldn't transmit packet to client");
            });
        });
        ws.set_onmessage(Some(onmessage_callback.as_ref().unchecked_ref()));
        onmessage_callback.forget();
        Ws {
            socket: Socket(ws),
            sender,
            receiver,
            packet_receiver,
        }
    }
    pub fn send_all_packets_from_channel(&mut self) {
        //for packet in self.packet_receiver.iter() {
        while let Ok(Some(packet)) = self.packet_receiver.try_next() {
            self.socket.0.send_with_str(&serde_json::to_string(&packet).expect("Couldn't convert packet to json")).expect("Couldn't send packet to server");
        }
    }
    pub fn send_packet(&mut self, packet: Packet) {
        let socket = self.socket.0.clone();
        spawn_local(async move {
            while socket.ready_state() != 1 { yield_now(); }
            socket.send_with_str(&serde_json::to_string(&packet).expect("Couldn't convert packet to json")).expect("Couldn't send packet to server");
        });
    }
}

D crates/client/src/rendering/assets_native.rs => crates/client/src/rendering/assets_native.rs +0 -14
@@ 1,14 0,0 @@
use bevy_ecs::system::Resource;

#[derive(Resource)]
pub struct Assets {

}
impl Assets {
    pub fn new() -> Self {
        Assets {  }
    }
    pub fn get(&self, local_path: impl Into<String>) -> Option<Vec<u8>> {
        std::fs::read(format!("src/textures/{}", local_path.into())).ok()
    }
}

D crates/client/src/rendering/assets_wasm.rs => crates/client/src/rendering/assets_wasm.rs +0 -123
@@ 1,123 0,0 @@
use std::{collections::HashMap, fmt::Display, sync::{Arc, Mutex}, time::Duration};

use bevy_ecs::system::Resource;
use futures::channel::oneshot::Receiver;
use image::EncodableLayout;
use poll_promise::Promise;
use resvg::{tiny_skia, usvg};
use wasm_bindgen_futures::spawn_local;

#[derive(Debug, Clone)]
pub enum AssetError {
    AssetNotFound,
    ResponseNotOk(u16),
}
impl Display for AssetError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            AssetError::AssetNotFound => write!(f, "Asset not found"),
            AssetError::ResponseNotOk(code) => write!(f, "Server response was not ok {}", code),
        }
    }
}
impl std::error::Error for AssetError {}

#[derive(Debug, Clone)]
pub struct ImgData {
    pub bytes: Vec<u8>,
    pub width: u32,
    pub height: u32,
}

#[derive(Resource)]
pub struct Assets {
    texture_promises: Arc<Mutex<HashMap<String, Promise<ImgData>>>>,
    textures: Arc<Mutex<HashMap<String, ImgData>>>,
}

impl Assets {
    pub fn new() -> Self {
        Assets {
            textures: Arc::new(Mutex::new(HashMap::new())),
            texture_promises: Arc::new(Mutex::new(HashMap::new())),
        }
    }
    pub fn get(&self, local_path: impl Into<String>) -> Option<ImgData> {
        let local_path = local_path.into();
        tracing::info!("contains texture");
        let contains_texture = {
            self.textures.lock().unwrap().contains_key(&local_path)
        };
        tracing::info!("contains texture promise");
        let contains_texture_promise = {
            self.texture_promises.lock().unwrap().contains_key(&local_path)
        };
        if !contains_texture && !contains_texture_promise {
            tracing::info!("insert receiver");
            let local_path_clone = local_path.clone();
            let request_promise = poll_promise::Promise::spawn_local(async move {
                tracing::info!("in spawn local");
                let window = web_sys::window().unwrap();
                tracing::info!("after window");
                let request = ehttp::Request::get(format!("{}/src/textures/{}", window.location().origin().unwrap(), local_path_clone));
                let response = match ehttp::fetch_async(request).await {
                    Ok(resp) => resp,
                    Err(e) => {
                        panic!("{}", e);
                    },
                };
                tracing::info!("after response");
                tracing::info!("texture received");
                if local_path_clone.ends_with(".svg") {
                    let opt = usvg::Options {
                        default_size: usvg::Size::from_wh(20.0, 20.0).unwrap(),
                        ..Default::default()
                    };
                    let tree = usvg::Tree::from_data(&response.bytes, &opt).expect(&format!("Couldn't parse svg {}", local_path_clone));
                    let tree_size = tree.size().to_int_size();
                    let size = usvg::Size::from_wh(100.0, 100.0).unwrap().to_int_size();
                    assert!(size.width() > 0 && size.height() > 0);
                    let mut pixmap = tiny_skia::Pixmap::new(size.width(), size.height()).expect("Failed to construct pixmap");
                    resvg::render(&tree, tiny_skia::Transform::from_scale((size.width() as f32)/(tree_size.height() as f32), (size.height() as f32)/(tree_size.height() as f32)), &mut pixmap.as_mut());
                    let data = ImgData {
                        bytes: pixmap.data().to_vec(),
                        width: size.width(),
                        height: size.height(),
                    };
                    
                    data
                } else if local_path_clone.ends_with(".png") {
                    let img = image::load_from_memory(&response.bytes).unwrap();
                    let rgba = img.to_rgba8();
                    let data = ImgData {
                        bytes: rgba.as_bytes().to_vec(),
                        width: rgba.width(),
                        height: rgba.height(),
                    };
                    data
                } else {
                    panic!("Unsupported sprite type");
                }
            });
            {
                self.texture_promises.lock().unwrap().insert(local_path.clone(), request_promise);
            }
            None
        } else if !contains_texture {
            tracing::info!("in contains texture");
            let mut texture_promises = self.texture_promises.lock().unwrap();
            tracing::info!("lock received");
            let promise = texture_promises.get_mut(&local_path).unwrap();
            tracing::info!("texture receiver gotten");
            let mut returned_value = None;
            if let Some(texture) = promise.ready() {
                self.textures.lock().unwrap().insert(local_path.clone(), texture.clone());
                returned_value = Some(texture.clone());
                texture_promises.remove(&local_path);
            }
            return returned_value
        } else {
            self.textures.lock().unwrap().get(&local_path).cloned()
        }
    }
}

D crates/client/src/rendering/mipmap.rs => crates/client/src/rendering/mipmap.rs +0 -134
@@ 1,134 0,0 @@
use crate::rendering::texture::Texture;
use std::collections::HashMap;
use tracing::debug;
use wgpu::{
    include_wgsl, BindGroupDescriptor, BindGroupEntry, BindingResource, Color, ColorTargetState,
    ColorWrites, CommandEncoderDescriptor, Device, FilterMode, FragmentState, LoadOp, Operations,
    Queue, RenderPassColorAttachment, RenderPassDescriptor, RenderPipeline,
    RenderPipelineDescriptor, Sampler, SamplerDescriptor, ShaderModule, StoreOp, TextureFormat,
    TextureViewDescriptor, VertexState,
};

pub struct MipGenerator {
    shader: ShaderModule,
    sampler: Sampler,
    pipelines: HashMap<TextureFormat, RenderPipeline>,
}
impl MipGenerator {
    pub fn new(device: &Device) -> Self {
        debug!("initializing MipGenerator, compiling shader module");
        let shader = device.create_shader_module(include_wgsl!("../shaders/text_quad_mips.wgsl"));
        Self {
            shader,
            sampler: device.create_sampler(&SamplerDescriptor {
                min_filter: FilterMode::Linear,
                label: Some("MipGenerator sampler"),
                ..Default::default()
            }),
            pipelines: HashMap::new(),
        }
    }

    pub fn generate_mips(&mut self, texture: &Texture, device: &Device, queue: &Queue) {
        let pipeline = self
            .pipelines
            .entry(texture.texture.format())
            .or_insert_with(|| {
                device.create_render_pipeline(&RenderPipelineDescriptor {
                    label: Some("MipGenerator format pipeline"),
                    layout: None,
                    vertex: VertexState {
                        module: &self.shader,
                        entry_point: Some("vs"),
                        compilation_options: Default::default(),
                        buffers: &[],
                    },
                    primitive: Default::default(),
                    depth_stencil: None,
                    multisample: Default::default(),
                    fragment: Some(FragmentState {
                        module: &self.shader,
                        entry_point: Some("fs"),
                        compilation_options: Default::default(),
                        targets: &[Some(ColorTargetState {
                            format: texture.texture.format(),
                            blend: None,
                            write_mask: ColorWrites::default(),
                        })],
                    }),
                    multiview: None,
                    cache: None,
                })
            });

        let mut encoder = device.create_command_encoder(&CommandEncoderDescriptor {
            label: Some("MipGenerator command encoder"),
        });

        let mut width = texture.texture.width();
        let mut height = texture.texture.height();
        let mut base_mip_level = 0;

        while width > 1 || height > 1 {
            width = 1.max(width / 2);
            height = 1.max(height / 2);

            let bind_group = device.create_bind_group(&BindGroupDescriptor {
                label: Some("MipGenerator bind group"),
                layout: &pipeline.get_bind_group_layout(0),
                entries: &[
                    BindGroupEntry {
                        binding: 0,
                        resource: BindingResource::Sampler(&self.sampler),
                    },
                    BindGroupEntry {
                        binding: 1,
                        resource: BindingResource::TextureView(&texture.texture.create_view(
                            &TextureViewDescriptor {
                                base_mip_level,
                                mip_level_count: Some(1),
                                ..Default::default()
                            },
                        )),
                    },
                ],
            });

            base_mip_level += 1;

            let texture_view = texture.texture.create_view(&TextureViewDescriptor {
                base_mip_level,
                mip_level_count: Some(1),
                ..Default::default()
            });

            let render_pass_descriptor = RenderPassDescriptor {
                label: Some("MipGenerator render pass"),
                color_attachments: &[Some(RenderPassColorAttachment {
                    view: &texture_view,
                    resolve_target: None,
                    ops: Operations {
                        load: LoadOp::Clear(Color {
                            r: 0.0,
                            g: 0.0,
                            b: 0.0,
                            a: 1.0,
                        }),
                        store: StoreOp::Store,
                    },
                })],
                depth_stencil_attachment: None,
                timestamp_writes: None,
                occlusion_query_set: None,
            };

            let mut pass = encoder.begin_render_pass(&render_pass_descriptor);
            pass.set_pipeline(pipeline);
            pass.set_bind_group(0, Some(&bind_group), &[]);
            pass.draw(0..6, 0..1);
        }

        let command_buffer = encoder.finish();
        queue.submit(std::iter::once(command_buffer));
    }
}

D crates/client/src/rendering/mod.rs => crates/client/src/rendering/mod.rs +0 -253
@@ 1,253 0,0 @@
mod mipmap;
mod renderer;
mod texture;
#[cfg(not(target_arch="wasm32"))]
#[path = "assets_native.rs"]
pub mod assets;
#[cfg(target_arch="wasm32")]
#[path = "assets_wasm.rs"]
pub mod assets;
pub mod ui;

use crate::ecs::{RecvPacket, SendPacket};
use crate::input::MouseWheelEvent;
use crate::rendering::renderer::RenderInitRes::{Initialized, NotReadyYet};
#[allow(unused_imports)]
use crate::rendering::renderer::{Renderer, RenderInitRes};
use crate::rendering::ui::UiRenderable;
use bevy_ecs::event::Events;
use bevy_ecs::schedule::Schedule;
use bevy_ecs::world::World;
use std::ops::Add;
use std::process::exit;
use std::sync::Arc;
use std::time::Duration;
use tracing::info;
use web_time::Instant;
use winit::application::ApplicationHandler;
use winit::dpi::LogicalSize;
use winit::event::{MouseScrollDelta, WindowEvent};
use winit::event_loop::{ActiveEventLoop, ControlFlow};
use winit::window::{Window, WindowId};

pub struct App<T: UiRenderable> {
    window: Option<Arc<Window>>,
    renderer: Option<Renderer<T>>,

    #[cfg(target_arch = "wasm32")]
    renderer_rx: Option<futures::channel::oneshot::Receiver<RenderInitRes<T>>>,

    world: Option<World>,
    update_schedule: Option<Schedule>,
    ui_renderable: Option<T>,
    send_packet_events: Option<Events<SendPacket>>,
    recv_packet_events: Option<Events<RecvPacket>>,
}

impl<T: UiRenderable> App<T> {
    pub fn new(world: World, send_packet_events: Events<SendPacket>,
            recv_packet_events: Events<RecvPacket>, update_schedule: Schedule, ui_renderable: T) -> Self {
        Self {
            window: None,
            renderer: None,
            #[cfg(target_arch = "wasm32")]
            renderer_rx: None,
            world: Some(world),
            update_schedule: Some(update_schedule),
            ui_renderable: Some(ui_renderable),
            send_packet_events: Some(send_packet_events),
            recv_packet_events: Some(recv_packet_events),
        }
    }
}
impl<T: UiRenderable + 'static> ApplicationHandler for App<T> {
    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
        if self.window.is_none() {
            let attributes = Window::default_attributes().with_title("StarKingdoms.TK");

            let window = Arc::new(event_loop.create_window(attributes).unwrap());
            self.window = Some(window.clone());

            let world = self.world.take().unwrap();
            let update_schedule = self.update_schedule.take().unwrap();
            let ui_renderable = self.ui_renderable.take().unwrap();
            let send_packet_events = self.send_packet_events.take().unwrap();
            let recv_packet_events = self.recv_packet_events.take().unwrap();

            #[cfg(not(target_arch = "wasm32"))]
            {
                let renderer = pollster::block_on(async move {
                    Renderer::try_init(window.clone(), world,
                        update_schedule, ui_renderable, send_packet_events, recv_packet_events).await
                });
                match renderer {
                    Initialized(r) => {
                        self.renderer = Some(r);
                    }
                    NotReadyYet(w, u, t, send, recv) => {
                        self.world = Some(w);
                        self.update_schedule = Some(u);
                        self.ui_renderable = Some(t);
                        self.send_packet_events = Some(send);
                        self.recv_packet_events = Some(recv);
                    }
                }
            }
            #[cfg(target_arch = "wasm32")]
            {
                use winit::platform::web::WindowExtWebSys;
                // Add it to the DOM
                web_sys::window()
                    .unwrap()
                    .document()
                    .unwrap()
                    .body()
                    .unwrap()
                    .append_child(&window.canvas().unwrap())
                    .unwrap();

                let (tx, rx) = futures::channel::oneshot::channel();
                self.renderer_rx = Some(rx);
                wasm_bindgen_futures::spawn_local(async move {
                    let renderer =
                        Renderer::try_init(window.clone(), world,
                            update_schedule, ui_renderable, send_packet_events, recv_packet_events).await;
                    tx.send(renderer).unwrap();
                });
            }
        }
    }

    fn window_event(
        &mut self,
        event_loop: &ActiveEventLoop,
        _window_id: WindowId,
        event: WindowEvent,
    ) {
        if event == WindowEvent::CloseRequested {
            info!("Close requested by underlying event system");
            event_loop.exit();
            exit(1);
        }
        if let Some(renderer) = &mut self.renderer {
            let egui_response = renderer.gui_winit.on_window_event(&renderer.window, &event);
            if egui_response.consumed {
                return;
            }
        }

        match event {
            WindowEvent::Resized(new) => {
                if let Some(renderer) = &mut self.renderer {
                    if new.width > 0 && new.height > 0 {
                        renderer.size = new;
                        renderer.scale_factor = renderer.window.scale_factor();
                        renderer.logical_size =
                            LogicalSize::from_physical(renderer.size, renderer.scale_factor);

                        renderer.surface_configuration.width = new.width;
                        renderer.surface_configuration.height = new.height;
                        renderer
                            .surface
                            .configure(&renderer.device, &renderer.surface_configuration);
                    }
                }
            }
            WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
                if let Some(renderer) = &mut self.renderer {
                    renderer.scale_factor = scale_factor;
                    renderer.logical_size =
                        LogicalSize::from_physical(renderer.size, renderer.scale_factor);
                }
            }
            WindowEvent::MouseWheel { delta, .. } => {
                if let Some(renderer) = &mut self.renderer {
                    renderer.world.send_event(match delta {
                        MouseScrollDelta::PixelDelta(pos) => {
                            MouseWheelEvent::Pixel { x: pos.x, y: pos.y }
                        }
                        MouseScrollDelta::LineDelta(x, y) => MouseWheelEvent::Line {
                            x: x as f64,
                            y: y as f64,
                        },
                    });
                }
            }
            _ => {}
        }
    }

    fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
        #[allow(unused_variables)]
        if let Some(window) = self.window.clone() {
            #[cfg(target_arch = "wasm32")]
            {
                let mut renderer_rxd = false;
                if let Some(rx) = self.renderer_rx.as_mut() {
                    if let Ok(Some(renderer)) = rx.try_recv() {
                        match renderer {
                            Initialized(r) => {
                                self.renderer = Some(r);
                                renderer_rxd = true;
                            }
                            NotReadyYet(w, u, t, send, recv) => {
                                let (tx, rx) = futures::channel::oneshot::channel();
                                self.renderer_rx = Some(rx);
                                wasm_bindgen_futures::spawn_local(async move {
                                    let renderer = Renderer::try_init(window.clone(), w, u, t, send, recv).await;
                                    tx.send(renderer).unwrap();
                                });
                            }
                        }
                    }
                }
                if renderer_rxd {
                    self.renderer_rx = None;
                }
            }

            #[cfg(not(target_arch = "wasm32"))]
            {
                if self.renderer.is_none() {
                    if let Some(window) = self.window.clone() {
                        let world = self.world.take().unwrap();
                        let update_schedule = self.update_schedule.take().unwrap();
                        let ui_renderable = self.ui_renderable.take().unwrap();
                        let send_packet_events = self.send_packet_events.take().unwrap();
                        let recv_packet_events = self.recv_packet_events.take().unwrap();

                        let renderer = pollster::block_on(async move {
                            Renderer::try_init(window.clone(), world, update_schedule,
                                ui_renderable, send_packet_events, recv_packet_events).await
                        });

                        match renderer {
                            Initialized(r) => {
                                self.renderer = Some(r);
                            }
                            NotReadyYet(w, u, t, send, recv) => {
                                self.world = Some(w);
                                self.update_schedule = Some(u);
                                self.ui_renderable = Some(t);
                                self.send_packet_events = Some(send);
                                self.recv_packet_events = Some(recv);
                            }
                        }

                        return;
                    }
                }
            }

            let Some(renderer) = &mut self.renderer else {
                return;
            };

            renderer.render();

            event_loop.set_control_flow(ControlFlow::WaitUntil(
                Instant::now().add(Duration::from_secs_f64(1.0 / 60.0)),
            ));
        }
    }
}

D crates/client/src/rendering/renderer.rs => crates/client/src/rendering/renderer.rs +0 -487
@@ 1,487 0,0 @@
use crate::ecs::{Camera, RecvPacket, Rotation, Scale, SendPacket, Shear, SpriteTexture, Translation};
use crate::networking::process_packets;
use crate::networking::ws::Ws;
use crate::rendering::assets::Assets;
use crate::rendering::mipmap::MipGenerator;
use crate::rendering::renderer::RenderInitRes::{Initialized, NotReadyYet};
use crate::rendering::texture;
use crate::rendering::ui::UiRenderable;
use bevy_ecs::entity::Entity;
use bevy_ecs::event::Events;
use bevy_ecs::schedule::Schedule;
use bevy_ecs::world::World;
use egui::ViewportId;
use starkingdoms_common::PlanetType;
use std::collections::hash_map::Entry;
use std::collections::{HashMap, HashSet};
use std::fmt::{Debug, Formatter};
use std::sync::Arc;
use tracing::info;
use web_time::Instant;
use wgpu::{BlendState, SurfaceConfiguration};
use wgpu::{
    include_wgsl, Adapter, Backends, BindGroupDescriptor, BindGroupEntry, BindingResource, Buffer,
    BufferDescriptor, BufferUsages, Color, ColorTargetState, CommandEncoderDescriptor, Device,
    DeviceDescriptor, Features, FragmentState, Instance, InstanceDescriptor, Limits, LoadOp,
    Operations, Queue, RenderPassColorAttachment, RenderPassDescriptor, RenderPipeline,
    RenderPipelineDescriptor, RequestAdapterOptions, ShaderModule, StoreOp, Surface,
    TextureViewDescriptor, VertexState,
};
use winit::dpi::{LogicalSize, PhysicalSize};
use winit::window::Window;

#[allow(unused_attributes, dead_code)]
pub struct Renderer<T: UiRenderable> {
    pub last_frame_time: Instant,

    pub surface: Surface<'static>,
    pub surface_configuration: SurfaceConfiguration,
    pub device: Device,
    pub queue: Queue,
    pub adapter: Adapter,

    pub sprite_shader_module: ShaderModule,
    pub sprite_pipeline: RenderPipeline,

    pub world: World,
    pub update_schedule: Schedule,
    pub send_packet_events: Events<SendPacket>,
    pub recv_packet_events: Events<RecvPacket>,

    pub gui_ctx: egui::Context,
    pub gui_winit: egui_winit::State,
    pub gui_renderer: egui_wgpu::Renderer,
    pub gui_renderable: T,

    pub textures: HashMap<String, texture::Texture>,
    pub mip_generator: MipGenerator,

    pub frame_uniform: Buffer,
    pub local_uniform: Buffer,
    pub scale_factor: f64,

    pub window: Arc<Window>,

    pub size: PhysicalSize<u32>,
    pub logical_size: LogicalSize<u32>,

    pub planet_types: HashMap<PlanetType, (Entity, u32)>, // (world entity, server id)
}

#[allow(clippy::large_enum_variant)]
pub enum RenderInitRes<T: UiRenderable> {
    Initialized(Renderer<T>),
    NotReadyYet(World, Schedule, T, Events<SendPacket>, Events<RecvPacket>),
}
impl<T: UiRenderable> Debug for RenderInitRes<T> {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            Initialized(..) => write!(f, "[initialized renderer]"),
            NotReadyYet(..) => write!(f, "[pending initialization]"),
        }
    }
}

impl<T: UiRenderable> Renderer<T> {
    pub async fn try_init(
        window: Arc<Window>,
        world: World,
        update_schedule: Schedule,
        gui_renderable: T,
        send_packet_events: Events<SendPacket>,
        recv_packet_events: Events<RecvPacket>,
    ) -> RenderInitRes<T> {
        let size = window.inner_size();
        if size.width == 0 || size.height == 0 {
            return NotReadyYet(world, update_schedule, gui_renderable,
                send_packet_events, recv_packet_events);
        }

        // First, create an instance. This is our handle to wgpu, and is the equivalent of navigator.gpu in WebGPU
        let instance = Instance::new(InstanceDescriptor {
            backends: Backends::all(), // Select the appropriate backend for the HAL to use. Defined in the platform module, as it's platform-specific
            ..Default::default()       // Other fields aren't relevant here
        });

        // Next, get our render surface
        let surface = instance.create_surface(window.clone()).unwrap();

        // Next, request out adapter
        let adapter = instance
            .request_adapter(&RequestAdapterOptions {
                power_preference: Default::default(), // Don't care
                force_fallback_adapter: false,        // We want a real GPU
                compatible_surface: Some(&surface), // Find an adapter that is able to render to our window
            })
            .await
            .unwrap();

        let mut limits = Limits::downlevel_webgl2_defaults();
        limits.max_texture_dimension_1d = 8192;
        limits.max_texture_dimension_2d = 8192;
        limits.max_texture_dimension_3d = 2048;
        let (device, queue) = adapter
            .request_device(
                &DeviceDescriptor {
                    label: Some("Basic render device"),
                    required_features: Features::default(),
                    required_limits: limits,
                    memory_hints: Default::default(),
                },
                None,
            )
            .await
            .unwrap();

        let format = surface
            .get_default_config(&adapter, size.width, size.height)
            .unwrap();

        surface.configure(&device, &format);

        let sprite_shader_module =
            device.create_shader_module(include_wgsl!("../shaders/sprite.wgsl"));
        let sprite_pipeline = device.create_render_pipeline(&RenderPipelineDescriptor {
            label: Some("Sprite pipeline"),
            layout: None,
            vertex: VertexState {
                module: &sprite_shader_module,
                entry_point: Some("vs"),
                compilation_options: Default::default(),
                buffers: &[],
            },
            primitive: Default::default(),
            depth_stencil: None,
            multisample: Default::default(),
            fragment: Some(FragmentState {
                module: &sprite_shader_module,
                entry_point: Some("fs"),
                compilation_options: Default::default(),
                targets: &[Some(ColorTargetState {
                    format: format.format,
                    blend: Some(BlendState::PREMULTIPLIED_ALPHA_BLENDING),
                    write_mask: Default::default(),
                })],
            }),
            multiview: None,
            cache: None,
        });

        let gui_context = egui::Context::default();
        let gui_winit = egui_winit::State::new(
            gui_context.clone(),
            ViewportId::ROOT,
            &window,
            Some(window.scale_factor() as f32),
            None,
            Some(device.limits().max_texture_dimension_2d as usize),
        );
        let gui_renderer = egui_wgpu::Renderer::new(&device, format.format, None, 1, false);

        let frame_uniform = device.create_buffer(&BufferDescriptor {
            label: Some("frame uniforms"),
            size: 48 + 16, // mat3x3f, vec2f
            usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });
        let local_uniform = device.create_buffer(&BufferDescriptor {
            label: Some("local uniforms"),
            size: 48, // mat3x3f
            usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
            mapped_at_creation: false,
        });

        Initialized(Self {
            mip_generator: MipGenerator::new(&device),
            textures: HashMap::new(),
            last_frame_time: Instant::now(),
            surface,
            device,
            queue,
            adapter,
            sprite_shader_module,
            sprite_pipeline,
            world,
            update_schedule,
            send_packet_events,
            recv_packet_events,
            gui_ctx: gui_context,
            gui_winit,
            gui_renderer,
            gui_renderable,
            logical_size: LogicalSize::from_physical(size, window.scale_factor()),
            scale_factor: window.scale_factor(),
            window,
            frame_uniform,
            local_uniform,
            size,
            surface_configuration: format,
            planet_types: HashMap::new(),
        })
    }

    pub fn render(&mut self) {
        let mut ws = self.world.get_resource_mut::<Ws>().expect("Failed to get Ws resource");
        #[cfg(target_arch = "wasm32")]
        while let Ok(Some(packet)) = ws.receiver.try_next() {
            self.recv_packet_events.send(RecvPacket(packet));
        }
        #[cfg(not(target_arch = "wasm32"))]
        for packet in ws.receiver.iter() {
            self.recv_packet_events.send(RecvPacket(packet));
        }
        // update the world
        self.update_schedule.run(&mut self.world);
        self.send_packet_events.update();
        self.recv_packet_events.update();

        process_packets(&mut self.world, &mut self.send_packet_events, &mut self.recv_packet_events, &mut self.planet_types);
        // update the UI
        let egui_output = self
            .gui_ctx
            .run(self.gui_winit.take_egui_input(&self.window), |ctx| {
                self.gui_renderable.render(ctx, &mut self.world)
            });
        self.gui_winit
            .handle_platform_output(&self.window, egui_output.platform_output);

        let output = self.surface.get_current_texture().unwrap();
        let view = output
            .texture
            .create_view(&TextureViewDescriptor::default());

        let render_pass_descriptor = RenderPassDescriptor {
            label: Some("basic render pass"),
            color_attachments: &[Some(RenderPassColorAttachment {
                view: &view,
                resolve_target: None,
                ops: Operations {
                    load: LoadOp::Clear(Color {
                        r: 0.3,
                        g: 0.3,
                        b: 0.3,
                        a: 1.0,
                    }),
                    store: StoreOp::Store,
                },
            })],
            ..Default::default()
        };
        let mut encoder = self
            .device
            .create_command_encoder(&CommandEncoderDescriptor {
                label: Some("command encoder"),
            });

        let mut sprites_to_render: Vec<(Translation, Shear, Scale, SpriteTexture, Rotation)> = vec![];

        let mut things_to_render = self.world.query::<(&Translation, &Shear, &Scale, &SpriteTexture, &Rotation)>();
        for thing in things_to_render.iter_mut(&mut self.world) {
            sprites_to_render.push((*thing.0, *thing.1, *thing.2, thing.3.clone(), *thing.4));
        }

        let cam = self.world.resource::<Camera>();

        let x = -cam.x*cam.zoom + self.logical_size.width as f32/2.0;
        let y = -cam.y*cam.zoom + self.logical_size.height as f32/2.0;

        let mut frame_uniform = vec![];
        let frame_uniform_values = [
            cam.zoom,   cam.shear_y,0.0,  0.0,
            cam.shear_x,cam.zoom,   0.0,  0.0,
            x,          y,          1.0,  0.0,
            self.logical_size.width as f32, self.logical_size.height as f32, 0.0, 0.0];
        for i in frame_uniform_values {
            let mut bytes = i.to_ne_bytes().to_vec();
            frame_uniform.append(&mut bytes);
        }

        self.queue.write_buffer(&self.frame_uniform, 0, &frame_uniform);

        for (pos, shear, scale, sprite_tex, rot) in sprites_to_render {
            let tex = match self.textures.entry(sprite_tex.texture.clone()) {
                Entry::Occupied(entry) => entry.into_mut(),
                Entry::Vacant(entry) => {
                    let assets = self.world.get_resource::<Assets>().unwrap();
                    let b = match sprite_tex.texture.as_str() {
                        "f" => match assets.get("f.png") {
                            Some(b) => b,
                            None => continue,
                        }
                        "happy-tree" => match assets.get("happy-tree.png") {
                            Some(b) => b,
                            None => continue,
                        }
                        "uv" => match assets.get("uv.png") {
                            Some(b) => b,
                            None => continue,
                        }
                        "hearty" => match assets.get("hearty.svg") {
                            Some(b) => b,
                            None => continue,
                        }
                        "sun" => match assets.get("sun.svg") {
                            Some(b) => b,
                            None => continue,
                        }
                        "mercury" => match assets.get("moon.svg") {
                            Some(b) => b,
                            None => continue,
                        }
                        "venus" => match assets.get("mars.svg") {
                            Some(b) => b,
                            None => continue,
                        }
                        "earth" => match assets.get("earth.svg") {
                            Some(b) => b,
                            None => continue,
                        }
                        "moon" => match assets.get("moon.svg") {
                            Some(b) => b,
                            None => continue,
                        }
                        "mars" => match assets.get("mars.svg") {
                            Some(b) => b,
                            None => continue,
                        }
                        "jupiter" => match assets.get("sun.svg") {
                            Some(b) => b,
                            None => continue,
                        }
                        "saturn" => match assets.get("moon.svg") {
                            Some(b) => b,
                            None => continue,
                        }
                        "uranus" => match assets.get("earth.svg") {
                            Some(b) => b,
                            None => continue,
                        }
                        "neptune" => match assets.get("moon.svg") {
                            Some(b) => b,
                            None => continue,
                        }
                        "pluto" => match assets.get("mars.svg") {
                            Some(b) => b,
                            None => continue,
                        }
                        u => panic!("unknown texture {u}, has it been added in rendering::renderer::<impl Renderer>::render()?")
                    };
                    entry.insert(texture::Texture::new(
                        b,
                        &sprite_tex.texture,
                        &self.device,
                        &self.queue,
                        &mut self.mip_generator,
                    ))
                }
            };
            tracing::info!("{} {:?} {:?}", sprite_tex.texture, pos, tex.texture);
            let xy_matrix = pos.as_matrix();
            let shear_matrix = shear.as_matrix();
            let rot_matrix = rot.as_matrix();
            let scale_matrix = scale.as_matrix();

            let transform_matrix = scale_matrix * shear_matrix * rot_matrix * xy_matrix;


            let mut local_buffer = vec![];
            let local_buffer_data = [
                transform_matrix.m11, transform_matrix.m12, transform_matrix.m13, 0.0,
                transform_matrix.m21, transform_matrix.m22, transform_matrix.m23, 0.0,
                transform_matrix.m31, transform_matrix.m32, transform_matrix.m33, 0.0,
            ];
            for i in local_buffer_data {
                let mut bytes = i.to_ne_bytes().to_vec();
                local_buffer.append(&mut bytes);
            }

            self.queue
                .write_buffer(&self.local_uniform, 0, &local_buffer);

            let bind_group = self.device.create_bind_group(&BindGroupDescriptor {
                label: Some("test_bind_group"),
                layout: &self.sprite_pipeline.get_bind_group_layout(0),
                entries: &[
                    BindGroupEntry {
                        binding: 0,
                        resource: BindingResource::Sampler(&tex.sampler),
                    },
                    BindGroupEntry {
                        binding: 1,
                        resource: BindingResource::TextureView(
                            &tex.texture.create_view(&TextureViewDescriptor::default()),
                        ),
                    },
                    BindGroupEntry {
                        binding: 2,
                        resource: BindingResource::Buffer(
                            self.frame_uniform.as_entire_buffer_binding(),
                        ),
                    },
                    BindGroupEntry {
                        binding: 3,
                        resource: BindingResource::Buffer(
                            self.local_uniform.as_entire_buffer_binding(),
                        ),
                    },
                ],
            });
            let mut pass = encoder.begin_render_pass(&render_pass_descriptor);
            pass.set_pipeline(&self.sprite_pipeline);
            pass.set_bind_group(0, Some(&bind_group), &[]);
            pass.draw(0..6, 0..1);
        }
        // main game rendering done
        // next up: egui UI rendering
        for (id, image_delta) in &egui_output.textures_delta.set {
            self.gui_renderer
                .update_texture(&self.device, &self.queue, *id, image_delta);
        }
        {
            let paint_jobs = self
                .gui_ctx
                .tessellate(egui_output.shapes, self.scale_factor as f32);
            let screen_descriptor = egui_wgpu::ScreenDescriptor {
                size_in_pixels: [self.size.width, self.size.height],
                pixels_per_point: self.scale_factor as f32,
            };
            self.gui_renderer.update_buffers(
                &self.device,
                &self.queue,
                &mut encoder,
                &paint_jobs,
                &screen_descriptor,
            );
            let render_pass = encoder.begin_render_pass(&RenderPassDescriptor {
                label: Some("ui render pass"),
                color_attachments: &[Some(RenderPassColorAttachment {
                    view: &view,
                    resolve_target: None,
                    ops: Operations {
                        load: LoadOp::Load,
                        store: StoreOp::Store,
                    },
                })],
                depth_stencil_attachment: None,
                timestamp_writes: None,
                occlusion_query_set: None,
            });
            let mut forgotten_render_pass = render_pass.forget_lifetime();
            self.gui_renderer
                .render(&mut forgotten_render_pass, &paint_jobs, &screen_descriptor);
            for id in egui_output.textures_delta.free {
                self.gui_renderer.free_texture(&id);
            }
        }
        let mut ws = self.world.get_resource_mut::<Ws>().expect("Failed to get Ws resource");
        let mut send_event_cursor = self.send_packet_events.get_cursor();
        for event in send_event_cursor.read(&self.send_packet_events) {
            ws.send_packet(event.0.clone());
        }

        let buffer = encoder.finish();
        self.queue.submit(std::iter::once(buffer));

        output.present();
    }
}

D crates/client/src/rendering/texture.rs => crates/client/src/rendering/texture.rs +0 -76
@@ 1,76 0,0 @@
use crate::rendering::mipmap::MipGenerator;
use image::EncodableLayout;
use wgpu::{
    Device, Extent3d, FilterMode, ImageCopyTexture, ImageDataLayout, Origin3d, Queue,
    SamplerDescriptor, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages,
};

use super::assets::ImgData;

#[derive(Debug)]
pub struct Texture {
    pub texture: wgpu::Texture,
    pub sampler: wgpu::Sampler,
}
impl Texture {
    pub fn new(
        rgba: ImgData,
        label: &str,
        device: &Device,
        queue: &Queue,
        mip_generator: &mut MipGenerator,
    ) -> Self {
        let max_size = rgba.width.max(rgba.height);
        let log_factor = if max_size == 0 { 0 } else { max_size.ilog2() };
        let optimal_mip_levels = 1 + log_factor;

        let texture = device.create_texture(&TextureDescriptor {
            label: Some(label),
            size: Extent3d {
                width: rgba.width,
                height: rgba.height,
                depth_or_array_layers: 1,
            },
            mip_level_count: optimal_mip_levels,
            sample_count: 1,
            dimension: TextureDimension::D2,
            format: TextureFormat::Rgba8UnormSrgb,
            usage: TextureUsages::TEXTURE_BINDING
                | TextureUsages::COPY_DST
                | TextureUsages::RENDER_ATTACHMENT,
            view_formats: &[TextureFormat::Rgba8UnormSrgb],
        });
        queue.write_texture(
            ImageCopyTexture {
                texture: &texture,
                mip_level: 0,
                origin: Origin3d::ZERO,
                aspect: Default::default(),
            },
            &rgba.bytes,
            ImageDataLayout {
                offset: 0,
                bytes_per_row: Some(rgba.width * 4),
                rows_per_image: Some(rgba.height),
            },
            Extent3d {
                width: rgba.width,
                height: rgba.height,
                depth_or_array_layers: 1,
            },
        );

        let sampler = device.create_sampler(&SamplerDescriptor {
            label: Some("test_sampler"),
            mag_filter: FilterMode::Linear,
            min_filter: FilterMode::Linear,
            ..Default::default()
        });

        let tex = Self { texture, sampler };

        //mip_generator.generate_mips(&tex, device, queue);

        tex
    }
}

D crates/client/src/rendering/ui.rs => crates/client/src/rendering/ui.rs +0 -5
@@ 1,5 0,0 @@
use bevy_ecs::world::World;

pub trait UiRenderable {
    fn render(&mut self, ctx: &egui::Context, world: &mut World);
}

D crates/client/src/shaders/sprite.wgsl => crates/client/src/shaders/sprite.wgsl +0 -56
@@ 1,56 0,0 @@
struct VertexShaderOut {
    @builtin(position) position: vec4<f32>,
    @location(0) texcoord: vec2<f32>
}

struct FrameUniforms {
    camera_transform: mat3x3f,
    viewport_size: vec2f,
}
struct LocalUniforms {
    transform: mat3x3f,
}
@group(0) @binding(2) var<uniform> frame_uni: FrameUniforms;
@group(0) @binding(3) var<uniform> local_uni: LocalUniforms;

@vertex fn vs(
    @builtin(vertex_index) vertexIndex : u32
) -> VertexShaderOut {
    let pos = array(
            vec2<f32>(-0.5, -0.5),
            vec2<f32>(0.5, -0.5),
            vec2<f32>(-0.5, 0.5),
            vec2<f32>(-0.5, 0.5),
            vec2<f32>(0.5, -0.5),
            vec2<f32>(0.5, 0.5)
        );

    var vsOutput: VertexShaderOut;

    let homogeneous_position = frame_uni.camera_transform * local_uni.transform * vec3f(pos[vertexIndex], 1);
    let position = homogeneous_position.xy / homogeneous_position.z;
    // convert from pixels to 0.0 to 1.0
    let zeroToOne = position / frame_uni.viewport_size;
    // convert from 0 - 1 to 0 - 2
    let zeroToTwo = zeroToOne * 2.0;
    // convert from 0 - 2 to -1 - +1 (clip space)
    let flippedClipSpace = zeroToTwo - 1.0;
    // flip Y
    let clipSpace = flippedClipSpace * vec2f(1, -1);

    vsOutput.position = vec4f(clipSpace, 0.0, 1.0);
    vsOutput.texcoord = pos[vertexIndex] + vec2f(0.5, 0.5);

    return vsOutput;
}

@group(0) @binding(0) var tex_sampler: sampler;
@group(0) @binding(1) var tex: texture_2d<f32>;

@fragment fn fs(fsInput: VertexShaderOut) -> @location(0) vec4<f32> {
    let texColor = textureSample(tex, tex_sampler, fsInput.texcoord);
    if (texColor.a < 0.1) {
        discard;
    }
    return texColor;
}

D crates/client/src/shaders/text_quad_mips.wgsl => crates/client/src/shaders/text_quad_mips.wgsl +0 -26
@@ 1,26 0,0 @@
struct VertexOutput {
    @builtin(position) position: vec4<f32>,
    @location(0) texcoord: vec2<f32>
}
@vertex fn vs(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {
    let pos = array(
        vec2<f32>(0.0, 0.0),
        vec2<f32>(1.0, 0.0),
        vec2<f32>(0.0, 1.0),
        vec2<f32>(0.0, 1.0),
        vec2<f32>(1.0, 0.0),
        vec2<f32>(1.0, 1.0)
    );
    var out: VertexOutput;
    let xy = pos[vertexIndex];
    out.position = vec4<f32>(xy * 2.0 - 1.0, 0.0, 1.0);
    out.texcoord = vec2<f32>(xy.x, 1.0 - xy.y);
    return out;
}

@group(0) @binding(0) var texSampler: sampler;
@group(0) @binding(1) var tex: texture_2d<f32>;

@fragment fn fs(inp: VertexOutput) -> @location(0) vec4<f32> {
    return textureSample(tex, texSampler, inp.texcoord);
}
\ No newline at end of file