~starkingdoms/starkingdoms

b558c59903754c1c88984f8faf53b47125061434 — core 8 months ago 93b350f
start render refactor
M crates/client/src/assets/shaders/fragment.glsl => crates/client/src/assets/shaders/fragment.glsl +2 -0
@@ 1,3 1,5 @@
#version 300 es

precision mediump float;

in vec2 v_pos;

M crates/client/src/assets/shaders/vertex.glsl => crates/client/src/assets/shaders/vertex.glsl +2 -0
@@ 1,3 1,5 @@
#version 300 es

precision mediump float;

in vec2 pos;

M crates/client/src/rendering/mod.rs => crates/client/src/rendering/mod.rs +67 -294
@@ 1,4 1,6 @@
use std::collections::HashMap;
use std::mem;
use std::mem::swap;
use std::num::NonZeroU32;
use std::sync::Arc;



@@ 43,42 45,31 @@ use crate::platform::websocket::Ws;
use crate::ui::{draw_ui, init_ui};
use assets::AssetLoader;
use crate::networking::websocket::Websocket;
use crate::rendering::MaybeRenderer::{Initialized, Initializing};
use crate::rendering::renderer::{RenderCreateContext, Renderer};

pub mod assets;
mod renderer;

enum MaybeRenderer {
    Uninitialized(RenderCreateContext),
    Initializing,
    Initialized(Renderer)
}

#[derive(Default)]
pub struct App {
    window: Option<Window>,
    world: World,
    renderer: MaybeRenderer,

    program: Option<glow::Program>,
    vertex_array: Option<glow::VertexArray>,
    vertex_buffer: Option<glow::Buffer>,
    element_buffer: Option<glow::Buffer>,
    #[cfg(not(target_arch = "wasm32"))]
    gl_surface: Option<Surface<WindowSurface>>,
    #[cfg(not(target_arch = "wasm32"))]
    gl_context: Option<PossiblyCurrentContext>,
    egui_glow: Option<EguiGlow>,
    gl: Option<Arc<glow::Context>>,
    textures: HashMap<String, glow::Texture>,

    send_packet_events: Events<SendPacket>,
    recv_packet_events: Events<RecvPacket>,
    planet_types: HashMap<PlanetType, (Entity, u32)>, // (world entity, server id)

    up: bool,
    down: bool,
    left: bool,
    right: bool,

    mouse_pos: PhysicalPosition<f64>,
}

const VERTICES: [f32; 16] = [
    -1.0, -1.0, 0.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, -1.0, 1.0, 0.0, 0.0,
];
const INDICES: [u32; 6] = [0, 1, 2, 2, 3, 0];


impl App {
    pub fn new(


@@ 87,10 78,18 @@ impl App {
        recv_packet_events: Events<RecvPacket>,
    ) -> Self {
        Self {
            window: None,
            world,
            send_packet_events,
            recv_packet_events,
            ..Default::default()
            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,
        }
    }
}


@@ 210,87 209,7 @@ impl ApplicationHandler for App {

            (Arc::new(gl), "#version 300 es")
        };
        unsafe {
            let shaders = [
                (
                    "vertex",
                    include_str!("../assets/shaders/vertex.glsl"),
                    glow::VERTEX_SHADER,
                ),
                (
                    "fragment",
                    include_str!("../assets/shaders/fragment.glsl"),
                    glow::FRAGMENT_SHADER,
                ),
            ];
            let program = gl.create_program().expect("Failed to create program");

            for (name, source, shader_type) in shaders {
                let shader = gl
                    .create_shader(shader_type)
                    .expect("Failed to create vertex shader");
                gl.shader_source(shader, &format!("{}\n{}", shader_version, source));
                gl.compile_shader(shader);
                if !gl.get_shader_compile_status(shader) {
                    tracing::error!(
                        "error in {} shader: {}",
                        name,
                        gl.get_shader_info_log(shader)
                    );
                }
                gl.attach_shader(program, shader);
                gl.delete_shader(shader);
            }
            gl.link_program(program);

            gl.use_program(Some(program));

            let vertex_array = gl
                .create_vertex_array()
                .expect("Failed to create vertex array");
            gl.bind_vertex_array(Some(vertex_array));
            let vertex_buffer = gl.create_buffer().expect("Failed to create vertex buffer");
            gl.bind_buffer(glow::ARRAY_BUFFER, Some(vertex_buffer));
            let element_buffer = gl.create_buffer().expect("Failed to create element buffer");
            gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(element_buffer));
            gl.buffer_data_u8_slice(
                glow::ARRAY_BUFFER,
                std::slice::from_raw_parts(VERTICES.as_ptr() as *const u8, size_of_val(&VERTICES)),
                glow::STATIC_DRAW,
            );
            gl.buffer_data_u8_slice(
                glow::ELEMENT_ARRAY_BUFFER,
                std::slice::from_raw_parts(INDICES.as_ptr() as *const u8, size_of_val(&INDICES)),
                glow::STATIC_DRAW,
            );

            gl.vertex_attrib_pointer_f32(0, 2, glow::FLOAT, false, 4 * size_of::<f32>() as i32, 0);
            gl.enable_vertex_attrib_array(0);
            gl.vertex_attrib_pointer_f32(
                1,
                2,
                glow::FLOAT,
                false,
                4 * size_of::<f32>() as i32,
                2 * size_of::<f32>() as i32,
            );
            gl.enable_vertex_attrib_array(1);

            gl.clear_color(0.1, 0.1, 0.1, 1.0);
            gl.viewport(
                0,
                0,
                window.inner_size().width as i32,
                window.inner_size().height as i32,
            );
            gl.enable(glow::BLEND);
            gl.blend_func(glow::SRC_ALPHA, glow::ONE_MINUS_SRC_ALPHA);

            self.program = Some(program);
            self.vertex_array = Some(vertex_array);
            self.vertex_buffer = Some(vertex_buffer);
            self.element_buffer = Some(element_buffer);
        }
        #[cfg(target_arch = "wasm32")]
        web_sys::window().unwrap().set_onresize(Some(
            Closure::<dyn Fn(Event)>::new(move |_| {


@@ 322,11 241,14 @@ impl ApplicationHandler for App {
            .unchecked_ref(),
        ));

        let egui_glow = egui_glow::EguiGlow::new(event_loop, gl.clone(), None, None, true);
        let mut rcc = Initializing;
        swap(&mut self.renderer, &mut rcc);
        let MaybeRenderer::Uninitialized(rcc) = rcc else { unreachable!() };

        init_ui(egui_glow.egui_ctx.clone());
        let renderer = unsafe { Renderer::new(gl.clone(), event_loop, rcc) };

        self.renderer = Initialized(renderer);

        self.egui_glow = Some(egui_glow);
        self.gl = Some(gl);
    }
    fn window_event(


@@ 335,14 257,13 @@ impl ApplicationHandler for App {
        _window_id: winit::window::WindowId,
        event: winit::event::WindowEvent,
    ) {
        let event_response = self
            .egui_glow
            .as_mut()
            .unwrap()
            .on_window_event(self.window.as_ref().unwrap(), &event);
        if event_response.consumed {
            return;
        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();


@@ 358,13 279,13 @@ impl ApplicationHandler for App {
                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();


@@ 386,57 307,57 @@ impl ApplicationHandler for App {
                        match event.state {
                            ElementState::Pressed => match key {
                                KeyCode::KeyW => {
                                    self.up = true;
                                    renderer.up = true;
                                    matched = true;
                                }
                                KeyCode::KeyS => {
                                    self.down = true;
                                    renderer.down = true;
                                    matched = true;
                                }
                                KeyCode::KeyA => {
                                    self.left = true;
                                    renderer.left = true;
                                    matched = true;
                                }
                                KeyCode::KeyD => {
                                    self.right = true;
                                    renderer.right = true;
                                    matched = true;
                                }
                                _ => matched = false,
                            },
                            ElementState::Released => match key {
                                KeyCode::KeyW => {
                                    self.up = false;
                                    renderer.up = false;
                                    matched = true;
                                }
                                KeyCode::KeyS => {
                                    self.down = false;
                                    renderer.down = false;
                                    matched = true;
                                }
                                KeyCode::KeyA => {
                                    self.left = false;
                                    renderer.left = false;
                                    matched = true;
                                }
                                KeyCode::KeyD => {
                                    self.right = false;
                                    renderer.right = false;
                                    matched = true;
                                }
                                _ => matched = false,
                            },
                        }
                        if matched {
                            self.send_packet_events
                            renderer.send_packet_events
                                .send(SendPacket(Packet::PlayerInput {
                                    up: self.up,
                                    down: self.down,
                                    left: self.left,
                                    right: self.right,
                                    up: renderer.up,
                                    down: renderer.down,
                                    left: renderer.left,
                                    right: renderer.right,
                                }));
                        }
                    }
                    PhysicalKey::Unidentified(_) => {} // unsupported
                }
            }
            WindowEvent::CursorMoved { position, .. } => self.mouse_pos = position,
            WindowEvent::CursorMoved { position, .. } => renderer.mouse_pos = position,
            WindowEvent::MouseInput { state, button, .. } => {
                let button = match button {
                    MouseButton::Left => ButtonType::Left,


@@ 459,9 380,9 @@ impl ApplicationHandler for App {
                let camera = self.world.get_resource::<Camera>().unwrap();
                let view = camera.to_cursor_matrix();
                let pos =
                    view * Vector3::new(self.mouse_pos.x as f32, self.mouse_pos.y as f32, 1.0);
                    view * Vector3::new(renderer.mouse_pos.x as f32, renderer.mouse_pos.y as f32, 1.0);
                let pos = pos / pos.z;
                self.send_packet_events
                renderer.send_packet_events
                    .send(SendPacket(Packet::PlayerMouseInput {
                        x: pos.x,
                        y: pos.y,


@@ 473,185 394,37 @@ impl ApplicationHandler for App {
        }
    }
    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");
        #[cfg(target_arch = "wasm32")]
        while let Ok(Some(packet)) = ws.receiver.try_next() {
            self.recv_packet_events.send(RecvPacket(packet));
            renderer.recv_packet_events.send(RecvPacket(packet));
        }
        #[cfg(not(target_arch = "wasm32"))]
        while let Ok(packet) = ws.receiver.try_recv() {
            self.recv_packet_events.send(RecvPacket(packet));
            renderer.recv_packet_events.send(RecvPacket(packet));
        }

        self.send_packet_events.update();
        self.recv_packet_events.update();
        renderer.send_packet_events.update();
        renderer.recv_packet_events.update();

        process_packets(
            &mut self.world,
            &mut self.send_packet_events,
            &mut self.recv_packet_events,
            &mut self.planet_types,
            &mut renderer.send_packet_events,
            &mut renderer.recv_packet_events,
            &mut renderer.planet_types,
        );

        let gl = self.gl.as_ref().unwrap();

        let player = self.world.query_filtered::<&Transform, With<Player>>();

        self.egui_glow
            .as_mut()
            .unwrap()
            .run(self.window.as_ref().unwrap(), |ctx| {
                draw_ui(ctx, &mut self.world, &mut self.send_packet_events);
            });

        let camera = self.world.get_resource::<Camera>().unwrap();
        let x_scale = camera.zoom / camera.width as f32 * 2.0;
        let y_scale = camera.zoom / camera.height as f32 * 2.0;
        let view = &[
            x_scale,
            0.0,
            0.0,
            camera.x * x_scale,
            0.0,
            y_scale,
            0.0,
            camera.y * y_scale,
            0.0,
            0.0,
            1.0,
            0.0,
            0.0,
            0.0,
            0.0,
            1.0,
        ];

        let mut sprite_query = self.world.query::<(&Transform, &mut Texture)>();

        let mut sprites = Vec::new();
        for (transform, texture) in sprite_query.iter(&self.world) {
            sprites.push((transform, texture));
        }

        unsafe {
            gl.clear(glow::COLOR_BUFFER_BIT);
            gl.use_program(self.program);
            gl.bind_vertex_array(self.vertex_array);
            gl.bind_buffer(glow::ARRAY_BUFFER, self.vertex_buffer);
            gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, self.element_buffer);
            gl.active_texture(glow::TEXTURE0);

            let view_loc = gl.get_uniform_location(self.program.unwrap(), "view");
            let model_loc = gl.get_uniform_location(self.program.unwrap(), "model");

            gl.uniform_matrix_4_f32_slice(view_loc.as_ref(), true, view);

            if !self.textures.contains_key("starfield.svg") {
                let assets = self.world.resource::<crate::platform::assets::Assets>();
                match assets.get("starfield.svg") {
                    Some(image) => {
                        let texture_object = gl
                            .create_texture()
                            .expect("Failed to create texture object");
                        gl.bind_texture(glow::TEXTURE_2D, Some(texture_object));
                        gl.tex_parameter_i32(
                            glow::TEXTURE_2D,
                            glow::TEXTURE_MIN_FILTER,
                            glow::LINEAR_MIPMAP_LINEAR as i32,
                        );
                        gl.tex_image_2d(
                            glow::TEXTURE_2D,
                            0,
                            glow::RGBA as i32,
                            image.width as i32,
                            image.height as i32,
                            0,
                            glow::RGBA,
                            glow::UNSIGNED_BYTE,
                            PixelUnpackData::Slice(Some(&image.bytes)),
                        );
                        gl.generate_mipmap(glow::TEXTURE_2D);

                        self.textures
                            .insert("starfield.svg".to_string(), texture_object);
                    }
                    None => {}
                }
            }
            if self.textures.contains_key("starfield.svg") {
                gl.bind_texture(
                    glow::TEXTURE_2D,
                    self.textures.get("starfield.svg").copied(),
                );

                let camera = self.world.get_resource::<Camera>().unwrap();
                let x = -(camera.x + camera.x.signum() * 200.0) + camera.x % 400.0;
                let y = -(camera.y + camera.y.signum() * 200.0) + camera.y % 400.0;
                let x_range = camera.width as f32 / camera.zoom / 400.0;
                let y_range = camera.height as f32 / camera.zoom / 400.0;
                for i in ((-x_range / 2.0) as i32 - 1)..=((x_range / 2.0) as i32 + 1) {
                    for j in ((-y_range / 2.0) as i32 - 1)..=((y_range / 2.0) as i32 + 1) {
                        let model =
                            Translation3::new(x + (i * 400) as f32, y + (j * 400) as f32, 0.0)
                                .to_homogeneous()
                                * Scale3::new(200.0, 200.0, 1.0).to_homogeneous();
                        gl.uniform_matrix_4_f32_slice(model_loc.as_ref(), false, model.as_slice());
                        gl.draw_elements(glow::TRIANGLES, 6, glow::UNSIGNED_INT, 0);
                    }
                }
            }

            for (transform, texture) in sprites {
                if !self.textures.contains_key(&texture.name) {
                    let assets = self.world.resource::<crate::platform::assets::Assets>();
                    let image = match assets.get(texture.name.clone()) {
                        Some(t) => t,
                        None => continue,
                    };

                    let texture_object = gl
                        .create_texture()
                        .expect("Failed to create texture object");
                    gl.bind_texture(glow::TEXTURE_2D, Some(texture_object));
                    gl.tex_parameter_i32(
                        glow::TEXTURE_2D,
                        glow::TEXTURE_MIN_FILTER,
                        glow::LINEAR_MIPMAP_LINEAR as i32,
                    );
                    gl.tex_image_2d(
                        glow::TEXTURE_2D,
                        0,
                        glow::RGBA as i32,
                        image.width as i32,
                        image.height as i32,
                        0,
                        glow::RGBA,
                        glow::UNSIGNED_BYTE,
                        PixelUnpackData::Slice(Some(&image.bytes)),
                    );
                    gl.generate_mipmap(glow::TEXTURE_2D);

                    self.textures.insert(texture.name.clone(), texture_object);
                }
                // now the texture must exist

                let model = transform.to_matrix();
                let model = model.as_slice();
                gl.uniform_matrix_4_f32_slice(model_loc.as_ref(), false, model);

                gl.bind_texture(glow::TEXTURE_2D, self.textures.get(&texture.name).copied());
                gl.draw_elements(glow::TRIANGLES, 6, glow::UNSIGNED_INT, 0);
            }
            renderer.draw(gl, window, &mut self.world);
        }

        self.egui_glow
            .as_mut()
            .unwrap()
            .paint(self.window.as_ref().unwrap());

        #[cfg(not(target_arch = "wasm32"))]
        self.gl_surface
            .as_ref()


@@ 663,8 436,8 @@ impl ApplicationHandler for App {
            .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) {
        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());
        }


A crates/client/src/rendering/renderer.rs => crates/client/src/rendering/renderer.rs +297 -0
@@ 0,0 1,297 @@
use std::collections::HashMap;
use std::sync::Arc;
use bevy_ecs::entity::Entity;
use bevy_ecs::event::Events;
use bevy_ecs::prelude::With;
use bevy_ecs::world::World;
use egui::Window;
use egui_glow::EguiGlow;
use glow::PixelUnpackData;
use nalgebra::{Scale3, Translation3};
use winit::dpi::PhysicalPosition;
use winit::event_loop::ActiveEventLoop;
use starkingdoms_common::PlanetType;
use crate::components::{Camera, Player, RecvPacket, SendPacket, Texture, Transform};
use crate::ui::draw_ui;
use crate::rendering::assets::AssetLoader;

pub struct Renderer {
    program: glow::Program,
    vertex_array: glow::VertexArray,
    vertex_buffer: glow::Buffer,
    element_buffer: glow::Buffer,
    pub egui_glow: EguiGlow,
    textures: HashMap<String, glow::Texture>,
    pub(crate) send_packet_events: Events<SendPacket>,
    pub(crate) recv_packet_events: Events<RecvPacket>,
    pub(crate) planet_types: HashMap<PlanetType, (Entity, u32)>, // (world entity, server id)

    pub(crate) up: bool,
    pub(crate) down: bool,
    pub(crate) left: bool,
    pub(crate) right: bool,

    pub(crate) mouse_pos: PhysicalPosition<f64>,
}

pub struct RenderCreateContext {
    pub send_packet_events: Events<SendPacket>,
    pub recv_packet_events: Events<RecvPacket>,
    pub planet_types: HashMap<PlanetType, (Entity, u32)>
}

const VERTICES: [f32; 16] = [
    -1.0, -1.0, 0.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, -1.0, 1.0, 0.0, 0.0,
];
const INDICES: [u32; 6] = [0, 1, 2, 2, 3, 0];

impl Renderer {
    pub unsafe fn new(gl: Arc<glow::Context>, event_loop: &ActiveEventLoop, ctx: RenderCreateContext) -> Self {
        use glow::HasContext as _;

            let shaders = [
                (
                    "vertex",
                    include_str!("../assets/shaders/vertex.glsl"),
                    glow::VERTEX_SHADER,
                ),
                (
                    "fragment",
                    include_str!("../assets/shaders/fragment.glsl"),
                    glow::FRAGMENT_SHADER,
                ),
            ];
            let program = gl.create_program().expect("Failed to create program");

            for (name, source, shader_type) in shaders {
                let shader = gl
                    .create_shader(shader_type)
                    .expect("Failed to create vertex shader");
                gl.shader_source(shader, source);
                gl.compile_shader(shader);
                if !gl.get_shader_compile_status(shader) {
                    tracing::error!(
                        "error in {} shader: {}",
                        name,
                        gl.get_shader_info_log(shader)
                    );
                }
                gl.attach_shader(program, shader);
                gl.delete_shader(shader);
            }
            gl.link_program(program);

            gl.use_program(Some(program));

            let vertex_array = gl
                .create_vertex_array()
                .expect("Failed to create vertex array");
            gl.bind_vertex_array(Some(vertex_array));
            let vertex_buffer = gl.create_buffer().expect("Failed to create vertex buffer");
            gl.bind_buffer(glow::ARRAY_BUFFER, Some(vertex_buffer));
            let element_buffer = gl.create_buffer().expect("Failed to create element buffer");
            gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(element_buffer));
            gl.buffer_data_u8_slice(
                glow::ARRAY_BUFFER,
                std::slice::from_raw_parts(VERTICES.as_ptr() as *const u8, size_of_val(&VERTICES)),
                glow::STATIC_DRAW,
            );
            gl.buffer_data_u8_slice(
                glow::ELEMENT_ARRAY_BUFFER,
                std::slice::from_raw_parts(INDICES.as_ptr() as *const u8, size_of_val(&INDICES)),
                glow::STATIC_DRAW,
            );

            gl.vertex_attrib_pointer_f32(0, 2, glow::FLOAT, false, 4 * size_of::<f32>() as i32, 0);
            gl.enable_vertex_attrib_array(0);
            gl.vertex_attrib_pointer_f32(
                1,
                2,
                glow::FLOAT,
                false,
                4 * size_of::<f32>() as i32,
                2 * size_of::<f32>() as i32,
            );
            gl.enable_vertex_attrib_array(1);

            gl.clear_color(0.1, 0.1, 0.1, 1.0);
            gl.enable(glow::BLEND);
            gl.blend_func(glow::SRC_ALPHA, glow::ONE_MINUS_SRC_ALPHA);


        let egui_glow = EguiGlow::new(event_loop, gl.clone(), None, None, true);

        crate::ui::init_ui(egui_glow.egui_ctx.clone());

        Self {
            program,
            vertex_array,
            vertex_buffer,
            element_buffer,
            egui_glow,
            textures: HashMap::new(),
            send_packet_events: ctx.send_packet_events,
            recv_packet_events: ctx.recv_packet_events,
            planet_types: ctx.planet_types,
            left: false,
            right: false,
            up: false,
            down: false,
            mouse_pos: Default::default(),
        }
    }

    pub unsafe fn draw(&mut self, gl: &glow::Context, window: &winit::window::Window, world: &mut World) {
        use glow::HasContext as _;

        let player = world.query_filtered::<&Transform, With<Player>>();

        // draw the UI
        self.egui_glow
            .run(window, |ctx| {
                draw_ui(ctx, &mut *world, &mut self.send_packet_events);
            });

        let camera = world.get_resource::<Camera>().unwrap();
        let x_scale = camera.zoom / camera.width as f32 * 2.0;
        let y_scale = camera.zoom / camera.height as f32 * 2.0;
        let view = &[
            x_scale,
            0.0,
            0.0,
            camera.x * x_scale,
            0.0,
            y_scale,
            0.0,
            camera.y * y_scale,
            0.0,
            0.0,
            1.0,
            0.0,
            0.0,
            0.0,
            0.0,
            1.0,
        ];

        let mut sprite_query = world.query::<(&Transform, &mut Texture)>();

        let mut sprites = Vec::new();
        for (transform, texture) in sprite_query.iter(&world) {
            sprites.push((transform, texture));
        }

        gl.clear(glow::COLOR_BUFFER_BIT);
        gl.use_program(Some(self.program));
        gl.bind_vertex_array(Some(self.vertex_array));
        gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vertex_buffer));
        gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.element_buffer));
        gl.active_texture(glow::TEXTURE0);

        let view_loc = gl.get_uniform_location(self.program, "view");
        let model_loc = gl.get_uniform_location(self.program, "model");

        gl.uniform_matrix_4_f32_slice(view_loc.as_ref(), true, view);

        if !self.textures.contains_key("starfield.svg") {
            let assets = world.resource::<crate::platform::assets::Assets>();
            match assets.get("starfield.svg") {
                Some(image) => {
                    let texture_object = gl
                        .create_texture()
                        .expect("Failed to create texture object");
                    gl.bind_texture(glow::TEXTURE_2D, Some(texture_object));
                    gl.tex_parameter_i32(
                        glow::TEXTURE_2D,
                        glow::TEXTURE_MIN_FILTER,
                        glow::LINEAR_MIPMAP_LINEAR as i32,
                    );
                    gl.tex_image_2d(
                        glow::TEXTURE_2D,
                        0,
                        glow::RGBA as i32,
                        image.width as i32,
                        image.height as i32,
                        0,
                        glow::RGBA,
                        glow::UNSIGNED_BYTE,
                        PixelUnpackData::Slice(Some(&image.bytes)),
                    );
                    gl.generate_mipmap(glow::TEXTURE_2D);

                    self.textures
                        .insert("starfield.svg".to_string(), texture_object);
                }
                None => {}
            }
        }
        if self.textures.contains_key("starfield.svg") {
            gl.bind_texture(
                glow::TEXTURE_2D,
                self.textures.get("starfield.svg").copied(),
            );

            let camera = world.get_resource::<Camera>().unwrap();
            let x = -(camera.x + camera.x.signum() * 200.0) + camera.x % 400.0;
            let y = -(camera.y + camera.y.signum() * 200.0) + camera.y % 400.0;
            let x_range = camera.width as f32 / camera.zoom / 400.0;
            let y_range = camera.height as f32 / camera.zoom / 400.0;
            for i in ((-x_range / 2.0) as i32 - 1)..=((x_range / 2.0) as i32 + 1) {
                for j in ((-y_range / 2.0) as i32 - 1)..=((y_range / 2.0) as i32 + 1) {
                    let model =
                        Translation3::new(x + (i * 400) as f32, y + (j * 400) as f32, 0.0)
                            .to_homogeneous()
                            * Scale3::new(200.0, 200.0, 1.0).to_homogeneous();
                    gl.uniform_matrix_4_f32_slice(model_loc.as_ref(), false, model.as_slice());
                    gl.draw_elements(glow::TRIANGLES, 6, glow::UNSIGNED_INT, 0);
                }
            }
        }

        for (transform, texture) in sprites {
            if !self.textures.contains_key(&texture.name) {
                let assets = world.resource::<crate::platform::assets::Assets>();
                let image = match assets.get(texture.name.clone()) {
                    Some(t) => t,
                    None => continue,
                };

                let texture_object = gl
                    .create_texture()
                    .expect("Failed to create texture object");
                gl.bind_texture(glow::TEXTURE_2D, Some(texture_object));
                gl.tex_parameter_i32(
                    glow::TEXTURE_2D,
                    glow::TEXTURE_MIN_FILTER,
                    glow::LINEAR_MIPMAP_LINEAR as i32,
                );
                gl.tex_image_2d(
                    glow::TEXTURE_2D,
                    0,
                    glow::RGBA as i32,
                    image.width as i32,
                    image.height as i32,
                    0,
                    glow::RGBA,
                    glow::UNSIGNED_BYTE,
                    PixelUnpackData::Slice(Some(&image.bytes)),
                );
                gl.generate_mipmap(glow::TEXTURE_2D);

                self.textures.insert(texture.name.clone(), texture_object);
            }
            // now the texture must exist

            let model = transform.to_matrix();
            let model = model.as_slice();
            gl.uniform_matrix_4_f32_slice(model_loc.as_ref(), false, model);

            gl.bind_texture(glow::TEXTURE_2D, self.textures.get(&texture.name).copied());
            gl.draw_elements(glow::TRIANGLES, 6, glow::UNSIGNED_INT, 0);
        }

        // paint UI
        self.egui_glow
            .paint(window);
    }
}
\ No newline at end of file

M crates/client/src/wasm/websocket.rs => crates/client/src/wasm/websocket.rs +1 -1
@@ 35,7 35,7 @@ impl Websocket for Ws {
            window.location().hostname().unwrap(),
            PORT
        ))*/
        let ws = WebSocket::new("ws://100.64.0.45:3000").expect("Couldn't connect to server");
        let ws = WebSocket::new("ws://localhost:3000").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();