kitgit

tirbofish/dropbear · commit

308bda5898fd46c1e04541243d52bdd1c5d1c078

attempting to optimise model FPS. got 260 with FPS with no objects and 30 fps with 322 entities. damn thats pretty good :) works on #39 jarvis, run github actions

Unverified

tk <4tkbytes@pm.me> · 2025-09-18 16:16

view full diff

diff --git a/Cargo.toml b/Cargo.toml
index 64b1587..13950d8 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -60,6 +60,7 @@ boa_engine = "0.20"
 backtrace = "0.3"
 gltf = "1"
 os_info = "3.12"
+rustc_version_runtime = "0.3"
 
 [workspace.dependencies.image]
 version = "0.25"
diff --git a/dropbear-engine/Cargo.toml b/dropbear-engine/Cargo.toml
index 0f198f4..9324376 100644
--- a/dropbear-engine/Cargo.toml
+++ b/dropbear-engine/Cargo.toml
@@ -36,6 +36,7 @@ tokio.workspace = true
 async-trait.workspace = true
 backtrace.workspace = true
 os_info.workspace = true
+rustc_version_runtime.workspace = true
 
 [target.'cfg(not(target_os = "android"))'.dependencies]
 rfd.workspace = true
diff --git a/dropbear-engine/src/entity.rs b/dropbear-engine/src/entity.rs
index 6865cc9..28792e8 100644
--- a/dropbear-engine/src/entity.rs
+++ b/dropbear-engine/src/entity.rs
@@ -9,10 +9,17 @@ use crate::{
     model::{LazyModel, LazyType, Model},
 };
 
+/// A type that represents a position, rotation and scale of an entity
+///
+/// This type is the most primitive model, as it implements most traits.
+#[repr(C)]
 #[derive(Debug, Clone, Deserialize, Serialize, Copy, PartialEq)]
 pub struct Transform {
+    /// The position of the entity as [`DVec3`]
     pub position: DVec3,
+    /// The rotation of the entity as [`DQuat`]
     pub rotation: DQuat,
+    /// The scale of the entity as [`DVec3`]
     pub scale: DVec3,
 }
 
@@ -27,30 +34,42 @@ impl Default for Transform {
 }
 
 impl Transform {
+    /// Creates a new default instance of Transform
     pub fn new() -> Self {
         Self::default()
     }
 
-    pub fn matrix(&self) -> DMat4 {
+    /// Returns the matrix of the model
+    pub(crate) fn matrix(&self) -> DMat4 {
         DMat4::from_scale_rotation_translation(self.scale, self.rotation, self.position)
     }
 
+    /// Rotates the model on its X axis by a certain angle
     pub fn rotate_x(&mut self, angle_rad: f64) {
         self.rotation *= DQuat::from_euler(glam::EulerRot::XYZ, angle_rad, 0.0, 0.0);
     }
 
+    /// Rotates the model on its Y axis by a certain value
     pub fn rotate_y(&mut self, angle_rad: f64) {
         self.rotation *= DQuat::from_euler(glam::EulerRot::XYZ, 0.0, angle_rad, 0.0);
     }
 
+    /// Rotates the model on its Z axis by a certain value
     pub fn rotate_z(&mut self, angle_rad: f64) {
         self.rotation *= DQuat::from_euler(glam::EulerRot::XYZ, 0.0, 0.0, angle_rad);
     }
 
+    /// Translates (moves) the model by a translation [`DVec3`].
+    ///
+    /// Doesn't replace the position value,
+    /// it adds the value.
     pub fn translate(&mut self, translation: DVec3) {
         self.position += translation;
     }
 
+    /// Scales the model by a scale value.
+    ///
+    /// Doesn't replace the scale value, just multiplies.
     pub fn scale(&mut self, scale: DVec3) {
         self.scale *= scale;
     }
@@ -105,12 +124,14 @@ impl LazyType for LazyAdoptedEntity {
     }
 }
 
-#[derive(Default, Clone)]
+#[derive(Clone)]
 pub struct AdoptedEntity {
-    pub model: Option<Model>,
+    pub model: Arc<Model>,
     pub previous_matrix: DMat4,
     pub instance: Instance,
     pub instance_buffer: Option<Buffer>,
+    pub dirty: bool,
+    last_frame_rendered: Option<u64>,
 }
 
 impl AdoptedEntity {
@@ -124,18 +145,6 @@ impl AdoptedEntity {
         Ok(Self::adopt(graphics, model).await)
     }
 
-    pub fn label(&self) -> &String {
-        &self.model().label
-    }
-
-    pub fn label_mut(&mut self) -> &mut String {
-        &mut self.model_mut().label
-    }
-
-    pub fn set_label(&mut self, label: &str) {
-        self.model_mut().label = label.to_string();
-    }
-
     pub async fn adopt(graphics: Arc<SharedGraphicsContext>, model: Model) -> Self {
         let label = model.label.clone();
         let instance = Instance::new(DVec3::ZERO, DQuat::IDENTITY, DVec3::ONE);
@@ -151,10 +160,12 @@ impl AdoptedEntity {
                 });
 
         Self {
-            model: Some(model),
+            model: Arc::new(model),
             instance,
             instance_buffer: Some(instance_buffer),
             previous_matrix: initial_matrix,
+            dirty: true,
+            last_frame_rendered: None,
         }
     }
 
@@ -172,12 +183,41 @@ impl AdoptedEntity {
         }
     }
 
-    pub fn model(&self) -> &Model {
-        self.model.as_ref().unwrap()
+    pub fn mark_dirty(&mut self) {
+        self.dirty = true;
+    }
+
+    pub fn is_dirty(&self) -> bool {
+        self.dirty
+    }
+
+    pub fn flush_to_gpu(&mut self, graphics: Arc<SharedGraphicsContext>) {
+        if self.dirty {
+            let instance_raw = self.instance.to_raw();
+            if let Some(buffer) = &self.instance_buffer {
+                graphics
+                    .queue
+                    .write_buffer(buffer, 0, bytemuck::cast_slice(&[instance_raw]));
+            }
+            self.dirty = false;
+        }
+    }
+
+    pub fn mark_rendered(&mut self, frame_number: u64) {
+        self.last_frame_rendered = Some(frame_number);
+    }
+
+    pub fn was_recently_rendered(&self, current_frame: u64, max_frames_ago: u64) -> bool {
+        if let Some(last_frame) = self.last_frame_rendered {
+            current_frame - last_frame <= max_frames_ago
+        } else {
+            false
+        }
     }
 
-    pub fn model_mut(&mut self) -> &mut Model {
-        self.model.as_mut().unwrap()
+    pub fn get_instance_buffer(&mut self, graphics: Arc<SharedGraphicsContext>) -> Option<&Buffer> {
+        self.flush_to_gpu(graphics);
+        self.instance_buffer.as_ref()
     }
 }
 
diff --git a/dropbear-engine/src/graphics.rs b/dropbear-engine/src/graphics.rs
index 8ce5515..d42ceab 100644
--- a/dropbear-engine/src/graphics.rs
+++ b/dropbear-engine/src/graphics.rs
@@ -213,6 +213,33 @@ impl<'a> RenderContext<'a> {
             })
             .forget_lifetime()
     }
+
+    pub fn continue_pass(&mut self) -> RenderPass<'static> {
+        self.frame
+            .encoder
+            .begin_render_pass(&wgpu::RenderPassDescriptor {
+                label: Some("Render Pass"),
+                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
+                    view: &self.frame.view,
+                    resolve_target: None,
+                    ops: wgpu::Operations {
+                        load: wgpu::LoadOp::Load,
+                        store: wgpu::StoreOp::Store,
+                    },
+                })],
+                depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
+                    view: &self.frame.depth_texture.view,
+                    depth_ops: Some(Operations {
+                        load: LoadOp::Load,
+                        store: wgpu::StoreOp::Store,
+                    }),
+                    stencil_ops: None,
+                }),
+                occlusion_query_set: None,
+                timestamp_writes: None,
+            })
+            .forget_lifetime()
+    }
 }
 
 // A nice little struct that stored basic information about a WGPU shader.
diff --git a/dropbear-engine/src/lib.rs b/dropbear-engine/src/lib.rs
index ba51e94..8ad56cf 100644
--- a/dropbear-engine/src/lib.rs
+++ b/dropbear-engine/src/lib.rs
@@ -510,7 +510,18 @@ impl App {
         }
 
         // log::debug!("OUT_DIR: {}", std::env!("OUT_DIR"));
-
+        log::info!("======================================================================");
+        log::info!("dropbear-engine v{} compiled with rustc {}", env!("CARGO_PKG_VERSION"),
+            rustc_version_runtime::version_meta().short_version_string);
+        log::info!("Made by tk with love at https://github.com/4tkbytes/dropbear <3");
+        log::info!("======================================================================");
+        log::info!("dropbear-engine running...");
+        let ad = app_dirs2::get_app_root(AppDataType::UserData, &config.app_info);
+        match ad {
+            Ok(path) => {log::info!("App data is stored at {}", path.display())},
+            Err(_) => {}
+        };
+        log::debug!("Additional nerdy stuff: {:#?}", rustc_version_runtime::version_meta());
         let event_loop = EventLoop::with_user_event().build()?;
         log::debug!("Created new event loop");
         let mut app = Box::new(App::new(config));
diff --git a/dropbear-engine/src/lighting.rs b/dropbear-engine/src/lighting.rs
index b11659c..1d8e95e 100644
--- a/dropbear-engine/src/lighting.rs
+++ b/dropbear-engine/src/lighting.rs
@@ -232,13 +232,13 @@ impl LazyType for LazyLight {
             cutoff: f32::cos(self.light_component.cutoff_angle.to_radians()),
         };
 
-        let cube_model = if let Some(lazy_model) = self.cube_lazy_model {
+        let cube_model = Arc::new(if let Some(lazy_model) = self.cube_lazy_model {
             lazy_model.poke(graphics.clone())?
         } else {
             anyhow::bail!(
                 "The light cube LazyModel has not been initialised yet. Use Light::new(/** params */).preload_cube_model() to preload it (which is required)"
             );
-        };
+        });
 
         let buffer = graphics.create_uniform(uniform, self.label.as_deref());
 
@@ -301,7 +301,7 @@ impl LazyType for LazyLight {
 #[derive(Clone)]
 pub struct Light {
     pub uniform: LightUniform,
-    cube_model: Model,
+    pub cube_model: Arc<Model>,
     pub label: String,
     buffer: Option<Buffer>,
     layout: Option<BindGroupLayout>,
@@ -354,13 +354,13 @@ impl Light {
             cutoff: f32::cos(light.cutoff_angle.to_radians()),
         };
 
-        let cube_model = Model::load_from_memory(
+        let cube_model = Arc::new(Model::load_from_memory(
             graphics.clone(),
             include_bytes!("../../resources/cube.glb").to_vec(),
             label.clone(),
         )
         .await
-        .unwrap();
+        .unwrap());
 
         let label_str = label.clone().unwrap_or("Light").to_string();
 
diff --git a/dropbear-engine/src/model.rs b/dropbear-engine/src/model.rs
index 87ef2fe..c8689f6 100644
--- a/dropbear-engine/src/model.rs
+++ b/dropbear-engine/src/model.rs
@@ -13,17 +13,21 @@ use std::collections::HashMap;
 use std::sync::Arc;
 use std::time::Instant;
 use std::{mem, ops::Range, path::PathBuf};
+use std::hash::{DefaultHasher, Hash, Hasher};
 use wgpu::{BufferAddress, VertexAttribute, VertexBufferLayout, util::DeviceExt};
 
 pub const GREY_TEXTURE_BYTES: &'static [u8] = include_bytes!("../../resources/grey.png");
 
 lazy_static! {
-    static ref MODEL_CACHE: Mutex<HashMap<String, Model>> = Mutex::new(HashMap::new());
-    static ref MEMORY_MODEL_CACHE: Mutex<HashMap<String, Model>> = Mutex::new(HashMap::new());
+    pub static ref MODEL_CACHE: Mutex<HashMap<String, Model>> = Mutex::new(HashMap::new());
 }
 
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct ModelId(pub u64);
+
 #[derive(Clone)]
 pub struct Model {
+    pub id: ModelId,
     pub label: String,
     pub path: ResourceReference,
     pub meshes: Vec<Mesh>,
@@ -37,7 +41,7 @@ pub struct Material {
     pub bind_group: wgpu::BindGroup,
 }
 
-#[derive(Clone)]
+#[derive(Clone, Hash)]
 pub struct Mesh {
     pub name: String,
     pub vertex_buffer: wgpu::Buffer,
@@ -69,8 +73,24 @@ pub struct ParsedMaterialData {
     pub dimensions: (u32, u32),
 }
 
+/// A type that is used to lazily load an object/struct.
+///
+/// It allows for separate threading loading.
 pub trait LazyType {
+    /// The data type of the lazy item
+    ///
+    /// For example, a [`LazyModel`] would have a [`Model`]
     type T;
+
+    /// A function used to load the wgpu graphics items.
+    ///
+    /// This can be ran after the initial thread loading is completed.
+    /// # Parameters
+    /// * [`Arc<SharedGraphicsContext>`] - The graphics context. It can be shared between threads
+    /// if required.
+    ///
+    /// # Returns
+    /// * [`Self::T`] - The data type of the item
     fn poke(self, graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self::T>;
 }
 
@@ -87,8 +107,10 @@ impl LazyType for LazyModel {
     fn poke(self, graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self::T> {
         let start = Instant::now();
 
+        let mut hasher = DefaultHasher::new();
+
         let cache_key = self.parsed_data.label.clone();
-        if let Some(cached_model) = MEMORY_MODEL_CACHE.lock().get(&cache_key) {
+        if let Some(cached_model) = MODEL_CACHE.lock().get(&cache_key) {
             log::debug!("Model loaded from cache during poke: {:?}", cache_key);
             return Ok(cached_model.clone());
         }
@@ -125,6 +147,14 @@ impl LazyType for LazyModel {
 
         let mut meshes = Vec::new();
         for mesh_data in &self.parsed_data.mesh_data {
+
+            for v in &mesh_data.vertices {
+                let _ = v.position.iter().map(|v| (*v as i32).hash(&mut hasher));
+                let _ = v.tex_coords.iter().map(|v| (*v as i32).hash(&mut hasher));
+                let _ = v.normal.iter().map(|v| (*v as i32).hash(&mut hasher));
+            }
+            mesh_data.indices.hash(&mut hasher);
+
             let buffer_start = Instant::now();
 
             let vertex_buffer =
@@ -167,9 +197,10 @@ impl LazyType for LazyModel {
             materials,
             label: self.parsed_data.label.clone(),
             path: self.parsed_data.path.clone(),
+            id: ModelId(hasher.finish()),
         };
 
-        MEMORY_MODEL_CACHE.lock().insert(cache_key, model.clone());
+        MODEL_CACHE.lock().insert(cache_key, model.clone());
         log::debug!(
             "Model GPU resource creation completed in {:?}",
             start.elapsed()
@@ -326,9 +357,11 @@ impl Model {
         label: Option<&str>,
     ) -> anyhow::Result<Model> {
         let start = Instant::now();
+        let mut hasher = DefaultHasher::new();
+
         let cache_key = label.unwrap_or("default").to_string();
 
-        if let Some(cached_model) = MEMORY_MODEL_CACHE.lock().get(&cache_key) {
+        if let Some(cached_model) = MODEL_CACHE.lock().get(&cache_key) {
             log::debug!("Model loaded from memory cache: {:?}", cache_key);
             return Ok(cached_model.clone());
         }
@@ -456,12 +489,18 @@ impl Model {
                         tex_coords: *tex,
                     })
                     .collect();
+                for v in &vertices {
+                    let _ = v.position.iter().map(|v| (*v as i32).hash(&mut hasher));
+                    let _ = v.normal.iter().map(|v| (*v as i32).hash(&mut hasher));
+                    let _ = v.tex_coords.iter().map(|v| (*v as i32).hash(&mut hasher));
+                }
 
                 let indices: Vec<u32> = reader
                     .read_indices()
                     .ok_or_else(|| anyhow::anyhow!("Mesh missing indices"))?
                     .into_u32()
                     .collect();
+                indices.hash(&mut hasher);
 
                 let vertex_buffer =
                     graphics
@@ -499,9 +538,10 @@ impl Model {
             materials,
             label: label.unwrap_or("No named model").to_string(),
             path: res_ref,
+            id: ModelId(hasher.finish()),
         };
 
-        MEMORY_MODEL_CACHE.lock().insert(cache_key, model.clone());
+        MODEL_CACHE.lock().insert(cache_key, model.clone());
         println!("==================== DONE ====================");
         log::debug!("Model cached from memory: {:?}", label);
         log::debug!("Took {:?} to load model: {:?}", start.elapsed(), label);
diff --git a/dropbear-engine/src/starter/plane.rs b/dropbear-engine/src/starter/plane.rs
index b759a1c..823e038 100644
--- a/dropbear-engine/src/starter/plane.rs
+++ b/dropbear-engine/src/starter/plane.rs
@@ -1,6 +1,7 @@
+use std::hash::{DefaultHasher, Hash, Hasher};
 use crate::entity::AdoptedEntity;
 use crate::graphics::{SharedGraphicsContext, Texture};
-use crate::model::{Material, Mesh, Model, ModelVertex};
+use crate::model::{Material, Mesh, Model, ModelId, ModelVertex, MODEL_CACHE};
 use crate::utils::{ResourceReference, ResourceReferenceType};
 use futures::executor::block_on;
 use image::GenericImageView;
@@ -26,6 +27,8 @@ pub struct LazyPlaneBuilder {
 
 impl LazyPlaneBuilder {
     pub fn poke(self, graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<AdoptedEntity> {
+        let mut hasher = DefaultHasher::new();
+
         let mut vertices = Vec::new();
         let mut indices = Vec::new();
 
@@ -41,6 +44,11 @@ impl LazyPlaneBuilder {
                     x as f32 * self.tiles_x as f32,
                     z as f32 * self.tiles_z as f32,
                 ];
+
+                let _ = position.iter().map(|v| (*v as i32).hash(&mut hasher));
+                let _ = normal.iter().map(|v| (*v as i32).hash(&mut hasher));
+                let _ = tex_coords.iter().map(|v| (*v as i32).hash(&mut hasher));
+
                 vertices.push(ModelVertex {
                     position,
                     tex_coords,
@@ -50,6 +58,7 @@ impl LazyPlaneBuilder {
         }
 
         indices.extend_from_slice(&[0, 2, 1, 1, 2, 3]);
+        indices.hash(&mut hasher);
 
         let vertex_buffer = graphics
             .device
@@ -92,6 +101,7 @@ impl LazyPlaneBuilder {
             path: ResourceReference::from_reference(ResourceReferenceType::Plane),
             meshes: vec![mesh],
             materials: vec![material],
+            id: ModelId(hasher.finish())
         };
 
         Ok(block_on(AdoptedEntity::adopt(graphics, model)))
@@ -159,6 +169,8 @@ impl PlaneBuilder {
         texture_bytes: &[u8],
         label: Option<&str>,
     ) -> anyhow::Result<AdoptedEntity> {
+        let label = if let Some(label) = label {label.to_string()} else {format!("{}*{}_tx{}xtz{}_plane", self.width, self.height, self.tiles_x, self.tiles_z)};
+        let mut hasher = DefaultHasher::new();
         if self.tiles_x == 0 && self.tiles_z == 0 {
             self.tiles_x = self.width as u32;
             self.tiles_z = self.height as u32;
@@ -178,6 +190,10 @@ impl PlaneBuilder {
                     x as f32 * self.tiles_x as f32,
                     z as f32 * self.tiles_z as f32,
                 ];
+                let _ = position.iter().map(|v| (*v as i32).hash(&mut hasher));
+                let _ = normal.iter().map(|v| (*v as i32).hash(&mut hasher));
+                let _ = tex_coords.iter().map(|v| (*v as i32).hash(&mut hasher));
+
                 vertices.push(ModelVertex {
                     position,
                     tex_coords,
@@ -187,11 +203,19 @@ impl PlaneBuilder {
         }
 
         indices.extend_from_slice(&[0, 2, 1, 1, 2, 3]);
+        indices.hash(&mut hasher);
+
+        let hash = hasher.finish();
+
+        let model = if let Some(cached_model) = MODEL_CACHE.lock().get(&label.clone()) {
+            log::debug!("Model loaded from cache: {:?}", label.clone());
+            Some(cached_model.clone())
+        } else {None};
 
         let vertex_buffer = graphics
             .device
             .create_buffer_init(&wgpu::util::BufferInitDescriptor {
-                label: Some(&format!("{:?} Vertex Buffer", label)),
+                label: Some(&format!("{:?} Vertex Buffer", label.clone())),
                 contents: bytemuck::cast_slice(&vertices),
                 usage: wgpu::BufferUsages::VERTEX,
             });
@@ -220,13 +244,22 @@ impl PlaneBuilder {
             bind_group,
         };
 
-        let model = Model {
-            label: label.unwrap_or("Plane").to_string(),
-            path: ResourceReference::from_reference(ResourceReferenceType::Plane),
-            meshes: vec![mesh],
-            materials: vec![material],
+        let model = if model.is_none() {
+            let m = Model {
+                label: label.to_string(),
+                path: ResourceReference::from_reference(ResourceReferenceType::Plane),
+                meshes: vec![mesh],
+                materials: vec![material],
+                id: ModelId(hash)
+            };
+            MODEL_CACHE.lock().insert(label, m.clone());
+            m
+        } else {
+            // safe to do
+            model.unwrap()
         };
 
+
         Ok(AdoptedEntity::adopt(graphics, model).await)
     }
 }
diff --git a/eucalyptus-core/src/scripting.rs b/eucalyptus-core/src/scripting.rs
index 7978e4f..b87fade 100644
--- a/eucalyptus-core/src/scripting.rs
+++ b/eucalyptus-core/src/scripting.rs
@@ -305,7 +305,7 @@ pub fn convert_entity_to_group(
 ) -> anyhow::Result<EntityNode> {
     if let Ok(mut query) = world.read().query_one::<(&AdoptedEntity, &Transform)>(entity_id) {
         if let Some((adopted, _transform)) = query.get() {
-            let entity_name = adopted.model().label.clone();
+            let entity_name = adopted.model.label.clone();
 
             let script_node = if let Ok(script) = world.read().get::<&ScriptComponent>(entity_id) {
                 Some(EntityNode::Script {
diff --git a/eucalyptus-core/src/states.rs b/eucalyptus-core/src/states.rs
index ad9ca23..25fc43a 100644
--- a/eucalyptus-core/src/states.rs
+++ b/eucalyptus-core/src/states.rs
@@ -482,7 +482,7 @@ impl EntityNode {
             )>()
             .iter()
         {
-            let name = adopted.model().label.clone();
+            let name = adopted.model.label.clone();
 
             // grouped entity (entity and script)
             nodes.push(EntityNode::Group {
@@ -513,7 +513,7 @@ impl EntityNode {
             if handled.contains(&id) {
                 continue;
             }
-            let name = adopted.model().label.clone();
+            let name = adopted.model.label.clone();
 
             nodes.push(EntityNode::Entity { id, name });
         }
@@ -745,163 +745,160 @@ impl SceneConfig {
 
         log::info!("World cleared, now has {} entities", world.read().len());
 
-        for (_i, entity_config) in self.entities.iter().cloned().enumerate() {
-            log::debug!("Loading entity: {}", entity_config.label);
+        let entity_configs: Vec<(usize, SceneEntity)> = {
+            let cloned = self.entities.clone();
+            cloned.into_par_iter().enumerate().map(|(i, e)| (i, e)).collect()
+        };
 
-            let entity_configs: Vec<(usize, SceneEntity)> = {
-                let cloned = self.entities.clone();
-                cloned.into_par_iter().enumerate().map(|(i, e)| (i, e)).collect()
-            };
+        for (index, entity_config) in entity_configs {
+            log::debug!("Loading entity: {}", entity_config.label);
 
-            let total = entity_configs.len();
+            let total = self.entities.len();
 
-            for (index, entity_config) in entity_configs {
-                log::debug!("Loading entity: {}", entity_config.label);
-                if let Some(ref s) = progress_sender {
-                    let _ = s.send(WorldLoadingStatus::LoadingEntity { index, name: entity_config.label.clone(), total });
-                }
+            if let Some(ref s) = progress_sender {
+                let _ = s.send(WorldLoadingStatus::LoadingEntity { index, name: entity_config.label.clone(), total });
+            }
 
-                let result = match &entity_config.model_path.ref_type {
-                    ResourceReferenceType::File(reference) => {
-                        let path: PathBuf = {
-                            if cfg!(feature = "editor") {
-                                log::debug!("Using feature editor");
-                                entity_config
-                                    .model_path
-                                    .to_project_path(project_config.clone())
-                                    .ok_or_else(|| {
-                                        anyhow::anyhow!(
+            let result = match &entity_config.model_path.ref_type {
+                ResourceReferenceType::File(reference) => {
+                    let path: PathBuf = {
+                        if cfg!(feature = "editor") {
+                            log::debug!("Using feature editor");
+                            entity_config
+                                .model_path
+                                .to_project_path(project_config.clone())
+                                .ok_or_else(|| {
+                                    anyhow::anyhow!(
                                             "Unable to convert resource reference [{}] to project path",
                                             reference
                                         )
-                                    })?
-                            } else {
-                                log::debug!("Using feature data-only");
-                                entity_config.model_path.to_executable_path()?
-                            }
-                        };
-                        log::debug!(
+                                })?
+                        } else {
+                            log::debug!("Using feature data-only");
+                            entity_config.model_path.to_executable_path()?
+                        }
+                    };
+                    log::debug!(
                             "Path for entity {} is {} from reference {}",
                             entity_config.label,
                             path.display(),
                             reference
                         );
 
-                        let adopted =
-                            AdoptedEntity::new(graphics.clone(), &path, Some(&entity_config.label))
-                                .await;
-                        let transform = entity_config.transform;
-
-                        if let Some(script_config) = &entity_config.script {
-                            let script = ScriptComponent {
-                                name: script_config.name.clone(),
-                                path: script_config.path.clone(),
-                            };
-                            { world.write().spawn((adopted, transform, script, entity_config.properties.clone())); }
-                        } else {
-                            { world.write().spawn((adopted, transform, entity_config.properties.clone())); }
-                        }
+                    let adopted =
+                        AdoptedEntity::new(graphics.clone(), &path, Some(&entity_config.label))
+                            .await;
+                    let transform = entity_config.transform;
 
-                        Ok(())
+                    if let Some(script_config) = &entity_config.script {
+                        let script = ScriptComponent {
+                            name: script_config.name.clone(),
+                            path: script_config.path.clone(),
+                        };
+                        { world.write().spawn((adopted, transform, script, entity_config.properties.clone())); }
+                    } else {
+                        { world.write().spawn((adopted, transform, entity_config.properties.clone())); }
                     }
-                    ResourceReferenceType::Bytes(bytes) => {
-                        log::info!("Loading entity from bytes [Len: {}]", bytes.len());
-                        let bytes = bytes.to_owned();
-
-                        let model = Model::load_from_memory(
-                            graphics.clone(),
-                            bytes,
-                            Some(&entity_config.label),
-                        )
-                        .await?;
-                        let adopted = AdoptedEntity::adopt(graphics.clone(), model).await;
 
-                        let transform = entity_config.transform;
+                    Ok(())
+                }
+                ResourceReferenceType::Bytes(bytes) => {
+                    log::info!("Loading entity from bytes [Len: {}]", bytes.len());
+                    let bytes = bytes.to_owned();
+
+                    let model = Model::load_from_memory(
+                        graphics.clone(),
+                        bytes,
+                        Some(&entity_config.label),
+                    )
+                        .await?;
+                    let adopted = AdoptedEntity::adopt(graphics.clone(), model).await;
 
-                        if let Some(script_config) = &entity_config.script {
-                            let script = ScriptComponent {
-                                name: script_config.name.clone(),
-                                path: script_config.path.clone(),
-                            };
-                            { world.write().spawn((adopted, transform, script, entity_config.properties.clone())); }
-                        } else {
-                            { world.write().spawn((adopted, transform, entity_config.properties.clone())); }
-                        }
+                    let transform = entity_config.transform;
 
-                        Ok(())
-                    }
-                    ResourceReferenceType::Plane => {
-                        let width = entity_config
-                            .properties
-                            .custom_properties
-                            .get("width")
-                            .ok_or_else(|| anyhow::anyhow!("Entity has no width property"))?;
-                        let width = match width {
-                            Value::Float(width) => width,
-                            _ => panic!("Entity has a width property that is not a float"),
-                        };
-                        let height = entity_config
-                            .properties
-                            .custom_properties
-                            .get("height")
-                            .ok_or_else(|| anyhow::anyhow!("Entity has no height property"))?;
-                        let height = match height {
-                            Value::Float(height) => height,
-                            _ => panic!("Entity has a height property that is not a float"),
-                        };
-                        let tiles_x = entity_config
-                            .properties
-                            .custom_properties
-                            .get("tiles_x")
-                            .ok_or_else(|| anyhow::anyhow!("Entity has no tiles_x property"))?;
-                        let tiles_x = match tiles_x {
-                            Value::Int(tiles_x) => tiles_x,
-                            _ => panic!("Entity has a tiles_x property that is not an int"),
-                        };
-                        let tiles_z = entity_config
-                            .properties
-                            .custom_properties
-                            .get("tiles_z")
-                            .ok_or_else(|| anyhow::anyhow!("Entity has no tiles_z property"))?;
-                        let tiles_z = match tiles_z {
-                            Value::Int(tiles_z) => tiles_z,
-                            _ => panic!("Entity has a tiles_z property that is not an int"),
+                    if let Some(script_config) = &entity_config.script {
+                        let script = ScriptComponent {
+                            name: script_config.name.clone(),
+                            path: script_config.path.clone(),
                         };
+                        { world.write().spawn((adopted, transform, script, entity_config.properties.clone())); }
+                    } else {
+                        { world.write().spawn((adopted, transform, entity_config.properties.clone())); }
+                    }
 
-                        let label_clone = entity_config.label.clone();
-                        let width_val = *width as f32;
-                        let height_val = *height as f32;
-                        let tiles_x_val = *tiles_x as u32;
-                        let tiles_z_val = *tiles_z as u32;
-
-                        let plane = PlaneBuilder::new()
-                            .with_size(width_val, height_val)
-                            .with_tiles(tiles_x_val, tiles_z_val)
-                            .build(graphics.clone(), PROTO_TEXTURE, Some(&label_clone))
-                            .await?;
-
-                        let transform = entity_config.transform;
-
-                        if let Some(script_config) = &entity_config.script {
-                            let script = ScriptComponent {
-                                name: script_config.name.clone(),
-                                path: script_config.path.clone(),
-                            };
-                            { world.write().spawn((plane, transform, script, entity_config.properties.clone())); }
-                        } else {
-                            { world.write().spawn((plane, transform, entity_config.properties.clone())); }
-                        }
+                    Ok(())
+                }
+                ResourceReferenceType::Plane => {
+                    let width = entity_config
+                        .properties
+                        .custom_properties
+                        .get("width")
+                        .ok_or_else(|| anyhow::anyhow!("Entity has no width property"))?;
+                    let width = match width {
+                        Value::Float(width) => width,
+                        _ => panic!("Entity has a width property that is not a float"),
+                    };
+                    let height = entity_config
+                        .properties
+                        .custom_properties
+                        .get("height")
+                        .ok_or_else(|| anyhow::anyhow!("Entity has no height property"))?;
+                    let height = match height {
+                        Value::Float(height) => height,
+                        _ => panic!("Entity has a height property that is not a float"),
+                    };
+                    let tiles_x = entity_config
+                        .properties
+                        .custom_properties
+                        .get("tiles_x")
+                        .ok_or_else(|| anyhow::anyhow!("Entity has no tiles_x property"))?;
+                    let tiles_x = match tiles_x {
+                        Value::Int(tiles_x) => tiles_x,
+                        _ => panic!("Entity has a tiles_x property that is not an int"),
+                    };
+                    let tiles_z = entity_config
+                        .properties
+                        .custom_properties
+                        .get("tiles_z")
+                        .ok_or_else(|| anyhow::anyhow!("Entity has no tiles_z property"))?;
+                    let tiles_z = match tiles_z {
+                        Value::Int(tiles_z) => tiles_z,
+                        _ => panic!("Entity has a tiles_z property that is not an int"),
+                    };
+
+                    let label_clone = entity_config.label.clone();
+                    let width_val = *width as f32;
+                    let height_val = *height as f32;
+                    let tiles_x_val = *tiles_x as u32;
+                    let tiles_z_val = *tiles_z as u32;
+
+                    let plane = PlaneBuilder::new()
+                        .with_size(width_val, height_val)
+                        .with_tiles(tiles_x_val, tiles_z_val)
+                        .build(graphics.clone(), PROTO_TEXTURE, Some(&label_clone))
+                        .await?;
+
+                    let transform = entity_config.transform;
 
-                        Ok(())
+                    if let Some(script_config) = &entity_config.script {
+                        let script = ScriptComponent {
+                            name: script_config.name.clone(),
+                            path: script_config.path.clone(),
+                        };
+                        { world.write().spawn((plane, transform, script, entity_config.properties.clone())); }
+                    } else {
+                        { world.write().spawn((plane, transform, entity_config.properties.clone())); }
                     }
-                    ResourceReferenceType::None => Err(anyhow::anyhow!(
+
+                    Ok(())
+                }
+                ResourceReferenceType::None => Err(anyhow::anyhow!(
                         "Entity has a resource reference of None, which cannot be loaded or referenced"
                     )),
-                };
+            };
 
-                result?;
-                log::debug!("Loaded!");
-            }
+            result?;
+            log::debug!("Loaded!");
         }
 
         let total = self.lights.len();
diff --git a/eucalyptus-editor/src/build/gleam.rs b/eucalyptus-editor/src/build/gleam.rs
index f718f4a..b6c6814 100644
--- a/eucalyptus-editor/src/build/gleam.rs
+++ b/eucalyptus-editor/src/build/gleam.rs
@@ -1,20 +1,16 @@
-use app_dirs2::{AppDataType, AppInfo, app_dir};
+use app_dirs2::{AppDataType, app_dir};
 use futures_util::StreamExt;
 use std::path::PathBuf;
 use std::process::Command;
 use tokio::fs;
 use tokio::io::AsyncWriteExt;
 use tokio::sync::mpsc::UnboundedSender;
+use crate::APP_INFO;
 
 const GLEAM_VERSION: &'static str = "1.12.0";
 const BUN_VERSION: &'static str = "1.2.22";
 const JAVY_VERSION: &'static str = "6.0.0";
 
-pub const APP_INFO: AppInfo = AppInfo {
-    name: "Eucalyptus",
-    author: "4tkbytes",
-};
-
 #[derive(Clone)]
 pub enum InstallStatus {
     NotStarted,
@@ -27,7 +23,7 @@ pub enum InstallStatus {
     Failed(String),
 }
 
-/// Compiles a gleam project into WASM through a pipeline.
+/// Compiles a gleam project into runnable JS using a pipeline
 pub struct GleamScriptCompiler {
     #[allow(dead_code)]
     project_location: PathBuf,
diff --git a/eucalyptus-editor/src/editor/component.rs b/eucalyptus-editor/src/editor/component.rs
index 1318763..0523a14 100644
--- a/eucalyptus-editor/src/editor/component.rs
+++ b/eucalyptus-editor/src/editor/component.rs
@@ -1,5 +1,6 @@
 //! This module should describe the different components that are editable in the resource inspector.
 
+use std::sync::Arc;
 use crate::editor::{EntityType, Signal, StaticallyKept, UndoableAction};
 use dropbear_engine::attenuation::ATTENUATION_PRESETS;
 use dropbear_engine::entity::{AdoptedEntity, Transform};
@@ -521,12 +522,12 @@ impl InspectableComponent for AdoptedEntity {
             ui.horizontal(|ui| {
                 ui.label("Name: ");
 
-                let resp = ui.text_edit_singleline(self.label_mut());
+                let resp = ui.text_edit_singleline(&mut Arc::make_mut(&mut self.model).label);
 
                 if resp.changed() {
                     if cfg.old_label_entity.is_none() {
                         cfg.old_label_entity = Some(entity.clone());
-                        cfg.label_original = Some(self.label().clone());
+                        cfg.label_original = Some(self.model.label.clone());
                     }
                     cfg.label_last_edit = Some(Instant::now());
                 }
diff --git a/eucalyptus-editor/src/editor/dock.rs b/eucalyptus-editor/src/editor/dock.rs
index 28a68db..66004d1 100644
--- a/eucalyptus-editor/src/editor/dock.rs
+++ b/eucalyptus-editor/src/editor/dock.rs
@@ -712,14 +712,14 @@ impl<'a> TabViewer for EditorTabViewer<'a> {
                             )>(*entity)
                             {
                                 if let Some((
-                                                e,
-                                                transform,
-                                                _props,
-                                                script,
-                                                camera,
-                                                camera_component,
-                                                follow_target,
-                                            )) = q.get() {
+                                    e,
+                                    transform,
+                                    _props,
+                                    script,
+                                    camera,
+                                    camera_component,
+                                    follow_target,
+                                )) = q.get() {
                                     e.inspect(
                                         entity,
                                         &mut cfg,
@@ -735,7 +735,7 @@ impl<'a> TabViewer for EditorTabViewer<'a> {
                                             ui,
                                             self.undo_stack,
                                             self.signal,
-                                            e.label_mut(),
+                                            &mut Arc::make_mut(&mut e.model).label,
                                         );
                                     }
 
@@ -772,7 +772,7 @@ impl<'a> TabViewer for EditorTabViewer<'a> {
                                     }
 
                                     // if let Some(props) = _props {
-                                    //     props.inspect(entity, &mut cfg, ui, self.undo_stack, self.signal, e.label_mut());
+                                    //     props.inspect(entity, &mut cfg, ui, self.undo_stack, self.signal, &mut Arc::make_mut(&mut e.model).label);
                                     // }
 
                                     if let Some(script) = script {
@@ -782,7 +782,7 @@ impl<'a> TabViewer for EditorTabViewer<'a> {
                                             ui,
                                             self.undo_stack,
                                             self.signal,
-                                            e.label_mut(),
+                                            &mut Arc::make_mut(&mut e.model).label,
                                         );
                                     }
 
diff --git a/eucalyptus-editor/src/editor/input.rs b/eucalyptus-editor/src/editor/input.rs
index f6bc958..4b0653e 100644
--- a/eucalyptus-editor/src/editor/input.rs
+++ b/eucalyptus-editor/src/editor/input.rs
@@ -120,8 +120,8 @@ impl Keyboard for Editor {
                         if let Ok(mut q) = query {
                             if let Some((e, t, props)) = q.get() {
                                 let s_entity = SceneEntity {
-                                    model_path: e.model().path.clone(),
-                                    label: e.model().label.clone(),
+                                    model_path: e.model.path.clone(),
+                                    label: e.model.label.clone(),
                                     transform: *t,
                                     properties: props.clone(),
                                     script: None,
diff --git a/eucalyptus-editor/src/editor/mod.rs b/eucalyptus-editor/src/editor/mod.rs
index 806f2fe..abe7b63 100644
--- a/eucalyptus-editor/src/editor/mod.rs
+++ b/eucalyptus-editor/src/editor/mod.rs
@@ -43,6 +43,7 @@ use transform_gizmo_egui::{EnumSet, Gizmo, GizmoMode};
 use wgpu::{Color, Extent3d, RenderPipeline};
 use winit::{keyboard::KeyCode, window::Window};
 use dropbear_engine::graphics::{RenderContext, Shader};
+use dropbear_engine::model::{ModelId};
 
 pub struct Editor {
     scene_command: SceneCommand,
@@ -211,8 +212,8 @@ impl Editor {
             let transform = transform.unwrap_or(&Transform::default()).clone();
 
             let scene_entity = SceneEntity {
-                model_path: adopted.model().path.clone(),
-                label: adopted.model().label.clone(),
+                model_path: adopted.model.path.clone(),
+                label: adopted.model.label.clone(),
                 transform,
                 properties: properties.clone(),
                 script: script.cloned(),
@@ -220,7 +221,7 @@ impl Editor {
             };
 
             scene.entities.push(scene_entity);
-            log::debug!("Pushed entity: {}", adopted.label());
+            log::debug!("Pushed entity: {}", adopted.model.label);
         }
 
         for (id, (light_component, transform, light)) in self
@@ -233,7 +234,7 @@ impl Editor {
             .iter()
         {
             let light_config = LightConfig {
-                label: light.label().to_string(),
+                label: light.cube_model.label.to_string(),
                 transform: *transform,
                 light_component: light_component.clone(),
                 enabled: light_component.enabled,
@@ -241,7 +242,7 @@ impl Editor {
             };
 
             scene.lights.push(light_config);
-            log::debug!("Pushed light into lights: {}", light.label());
+            log::debug!("Pushed light into lights: {}", light.cube_model.label);
         }
 
         for (_id, (camera, component, follow_target)) in self
@@ -311,7 +312,7 @@ impl Editor {
                             self.is_world_loaded.mark_project_loaded();
                             self.current_state = WorldLoadingStatus::Completed;
                             self.progress_tx = None;
-                            println!("Returning back");
+                            log::debug!("Returning back");
                             return;
                         }
                         WorldLoadingStatus::Idle => {
@@ -500,8 +501,8 @@ impl Editor {
                             if let Ok(mut q) = query {
                                 if let Some((e, t, props)) = q.get() {
                                     let s_entity = states::SceneEntity {
-                                        model_path: e.model().path.clone(),
-                                        label: e.model().label.clone(),
+                                        model_path: e.model.path.clone(),
+                                        label: e.model.label.clone(),
                                         transform: *t,
                                         properties: props.clone(),
                                         script: None,
@@ -1004,7 +1005,7 @@ impl UndoableAction {
                     {
                         if let Ok(mut q) = world.write().query_one::<&mut AdoptedEntity>(*entity) {
                             if let Some(adopted) = q.get() {
-                                adopted.model_mut().label = original_label.clone();
+                                Arc::make_mut(&mut adopted.model).label = original_label.clone();
                                 log::debug!(
                                 "Reverted label for entity {:?} to '{}'",
                                 entity,
diff --git a/eucalyptus-editor/src/editor/scene.rs b/eucalyptus-editor/src/editor/scene.rs
index 0ab84d8..a9b755d 100644
--- a/eucalyptus-editor/src/editor/scene.rs
+++ b/eucalyptus-editor/src/editor/scene.rs
@@ -1,7 +1,7 @@
 
 use super::*;
 use dropbear_engine::graphics::{InstanceRaw, RenderContext};
-use dropbear_engine::model::Model;
+use dropbear_engine::model::{Model, MODEL_CACHE};
 use dropbear_engine::starter::plane::PlaneBuilder;
 use dropbear_engine::{
     entity::{AdoptedEntity, Transform},
@@ -724,7 +724,7 @@ impl Scene for Editor {
                                 follow_target = (
                                     true,
                                     CameraFollowTarget {
-                                        follow_target: adopted.label().to_string(),
+                                        follow_target: adopted.model.label.to_string(),
                                         offset: *offset,
                                     },
                                 );
@@ -772,7 +772,7 @@ impl Scene for Editor {
                         {
                             if let Some(e) = q.get() {
                                 let mut local_signal: Option<Signal> = None;
-                                let label = e.label().clone();
+                                let label = e.model.label.clone();
                                 let mut show = true;
                                 egui::Window::new(format!("Add component for {}", label))
                                     .title_bar(true)
@@ -1063,11 +1063,13 @@ impl Scene for Editor {
                 let mut counter = 0;
                 for entity in self.world.read().iter() {
                     if let Some(entity) = entity.get::<&AdoptedEntity>() {
-                        log::info!("Model: {:?}", entity.label());
+                        log::info!("Model: {:?}", entity.model.label);
+                        log::info!("  |-> Using model: {:?}", entity.model.id);
                     }
 
                     if let Some(entity) = entity.get::<&Light>() {
-                        log::info!("Light: {:?}", entity.label());
+                        log::info!("Light: {:?}", entity.cube_model.label);
+                        log::info!("  |-> Using model: {:?}", entity.cube_model.id);
                     }
 
                     if let Some(_) = entity.get::<&Camera>() {
@@ -1210,7 +1212,7 @@ impl Scene for Editor {
                     .query::<(&AdoptedEntity, &Transform)>()
                     .iter()
                     .find_map(|(_, (adopted, transform))| {
-                        if adopted.label() == &target_label {
+                        if adopted.model.label == target_label {
                             Some(transform.position)
                         } else {
                             None
@@ -1298,6 +1300,7 @@ impl Scene for Editor {
         self.window = Some(graphics.shared.window.clone());
         logging::render(&graphics.shared.get_egui_context());
         if let Some(pipeline) = &self.render_pipeline {
+            log_once::debug_once!("Found render pipeline");
             if let Some(active_camera) = *self.active_camera.lock() {
                 let cam = {
                     let world = self.world.read();
@@ -1311,7 +1314,6 @@ impl Scene for Editor {
                         None
                     }
                 };
-                
 
                 if let Some(camera) = cam {
                     let lights = {
@@ -1323,18 +1325,18 @@ impl Scene for Editor {
                         }
                         lights
                     };
-                    
+
 
                     let entities = {
                         let world = self.world.read();
                         let mut entities = Vec::new();
-                        let mut entity_query = world.query::<(&AdoptedEntity, &Transform)>();
-                        for (_, (entity, transform)) in entity_query.iter() {
-                            entities.push((entity.clone(), transform.clone()));
+                        let mut entity_query = world.query::<&AdoptedEntity>();
+                        for (_, entity) in entity_query.iter() {
+                            entities.push(entity.clone());
                         }
                         entities
                     };
-                    
+
 
                     {
                         let mut render_pass = graphics.clear_colour(color);
@@ -1346,42 +1348,56 @@ impl Scene for Editor {
                                     light.instance_buffer.as_ref().unwrap().slice(..),
                                 );
                                 render_pass.draw_light_model(
-                                    light.model(),
+                                    &light.cube_model,
                                     camera.bind_group(),
                                     light.bind_group(),
                                 );
                             }
                         }
-                        
-
-                        let mut model_batches: HashMap<*const Model, Vec<InstanceRaw>> =
-                            HashMap::new();
-                        for (entity, _) in &entities {
-                            let model_ptr = entity.model() as *const Model;
-                            let instance_raw = entity.instance.to_raw();
-                            model_batches
-                                .entry(model_ptr)
-                                .or_insert(Vec::new())
-                                .push(instance_raw);
-                        }
+                    }
 
-                        render_pass.set_pipeline(pipeline);
-                        for (model_ptr, instances) in model_batches {
-                            let model = unsafe { &*model_ptr };
-                            let instance_buffer = graphics.shared.device.create_buffer_init(
-                                &wgpu::util::BufferInitDescriptor {
-                                    label: Some("Batched Instance Buffer"),
-                                    contents: bytemuck::cast_slice(&instances),
-                                    usage: wgpu::BufferUsages::VERTEX,
-                                },
-                            );
-                            render_pass.set_vertex_buffer(1, instance_buffer.slice(..));
-                            render_pass.draw_model_instanced(
-                                model,
-                                0..instances.len() as u32,
-                                camera.bind_group(),
-                                self.light_manager.bind_group(),
-                            );
+                    let mut model_batches: HashMap<ModelId, Vec<InstanceRaw>> =
+                        HashMap::new();
+                    for entity in &entities {
+                        let model_ptr = entity.model.id;
+                        let instance_raw = entity.instance.to_raw();
+                        model_batches
+                            .entry(model_ptr)
+                            .or_insert(Vec::new())
+                            .push(instance_raw);
+                    }
+
+                    for (model_ptr, instances) in model_batches {
+                        {
+                            let model_opt = {
+                                let cache = MODEL_CACHE.lock();
+                                cache.values().find(|m| m.id == model_ptr).cloned()
+                            };
+
+                            if let Some(model) = model_opt {
+                                {
+                                    let mut render_pass = graphics.continue_pass();
+                                    render_pass.set_pipeline(pipeline);
+
+                                    let instance_buffer = graphics.shared.device.create_buffer_init(
+                                        &wgpu::util::BufferInitDescriptor {
+                                            label: Some("Batched Instance Buffer"),
+                                            contents: bytemuck::cast_slice(&instances),
+                                            usage: wgpu::BufferUsages::VERTEX,
+                                        },
+                                    );
+                                    render_pass.set_vertex_buffer(1, instance_buffer.slice(..));
+                                    render_pass.draw_model_instanced(
+                                        &model,
+                                        0..instances.len() as u32,
+                                        camera.bind_group(),
+                                        self.light_manager.bind_group(),
+                                    );
+                                }
+                                log_once::debug_once!("Rendered {:?}", model_ptr);
+                            } else {
+                                log_once::error_once!("No such MODEL as {:?}", model_ptr);
+                            }
                         }
                     }
                 } else {
diff --git a/resources/shaders/shader.wgsl b/resources/shaders/shader.wgsl
index 37903ae..91402fa 100644
--- a/resources/shaders/shader.wgsl
+++ b/resources/shaders/shader.wgsl
@@ -19,7 +19,7 @@ struct Light {
 }
 
 struct LightArray {
-    lights: array<Light, MAX_LIGHTS>,
+    _lights: array<Light, MAX_LIGHTS>,
     light_count: u32,
     ambient_strength: f32,
 }
@@ -181,12 +181,12 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
 
     var total_ambient = vec3<f32>(0.0);
     for (var i: u32 = 0u; i < light_array.light_count; i = i + 1u) {
-        let light = light_array.lights[i];
+        let light = light_array._lights[i];
         total_ambient += light.color.xyz * light_array.ambient_strength;
     }
 
     for (var i: u32 = 0u; i < light_array.light_count; i = i + 1u) {
-        let light = light_array.lights[i];
+        let light = light_array._lights[i];
 
         // light type is color.w
         if light.color.w == 0.0 {