kitgit

tirbofish/dropbear · commit

51bd99fb844aa18e8e4fceb86572a8f2cfa714bb

asset server

Unverified

tk <4tkbytes@pm.me> · 2025-11-01 11:44

view full diff

diff --git a/Cargo.toml b/Cargo.toml
index 52e9f71..03a7d19 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -60,6 +60,7 @@ libloading = "0.8"
 indexmap = "2.11"
 sha2 = "0.10"
 wesl = "0.2"
+dashmap = "6.1"
 
 [workspace.dependencies.image]
 version = "0.25"
diff --git a/dropbear-engine/Cargo.toml b/dropbear-engine/Cargo.toml
index 78ec532..d9dc7a5 100644
--- a/dropbear-engine/Cargo.toml
+++ b/dropbear-engine/Cargo.toml
@@ -39,6 +39,7 @@ backtrace.workspace = true
 os_info.workspace = true
 rustc_version_runtime.workspace = true
 ron.workspace = true
+dashmap.workspace = true
 
 [target.'cfg(not(target_os = "android"))'.dependencies]
 rfd.workspace = true
diff --git a/dropbear-engine/src/asset.rs b/dropbear-engine/src/asset.rs
new file mode 100644
index 0000000..d9d5912
--- /dev/null
+++ b/dropbear-engine/src/asset.rs
@@ -0,0 +1,74 @@
+use std::sync::Arc;
+
+use dashmap::DashMap;
+
+use crate::model::{Material, MaterialComponent, Mesh, MeshComponent};
+
+pub type Handle = u64; 
+
+pub struct AssetCache {
+    materials: DashMap<MaterialComponent, Arc<Material>>,
+    meshes: DashMap<MeshComponent, Arc<Mesh>>,
+}
+
+impl AssetCache {
+    pub fn new() -> Self {
+        Self {
+            materials: DashMap::new(),
+            meshes: DashMap::new(),
+        }
+    }
+
+    /// Fetches the material based off the handle. 
+    /// 
+    /// If it doesn't exist, it will run the loader as a function and store the mesh from there. 
+    pub fn get_or_load_material<F>(&self, handle: MaterialComponent, loader: F) -> anyhow::Result<Arc<Material>>
+    where
+        F: FnOnce() -> anyhow::Result<Material>,
+    {
+        if let Some(existing) = self.materials.get(&handle) {
+            return Ok(existing.clone());
+        }
+
+        let material = Arc::new(loader()?);
+
+        match self.materials.entry(handle) {
+            dashmap::mapref::entry::Entry::Occupied(entry) => Ok(entry.get().clone()),
+            dashmap::mapref::entry::Entry::Vacant(entry) => {
+                entry.insert(material.clone());
+                Ok(material)
+            }
+        }
+    }
+
+    /// Fetches the mesh based off the handle. 
+    /// 
+    /// If it doesn't exist, it will run the loader as a function and store the mesh from there. 
+    pub fn get_or_load_mesh<F>(&self, handle: MeshComponent, loader: F) -> anyhow::Result<Arc<Mesh>>
+    where
+        F: FnOnce() -> anyhow::Result<Mesh>,
+    {
+        log::debug!("Searching for mesh {:?}", handle);
+        if let Some(existing) = self.meshes.get(&handle) {
+            log::debug!("Found existing mesh");
+            return Ok(existing.clone());
+        }
+
+        let mesh = Arc::new(loader()?);
+        log::debug!("Created new mesh");
+
+        match self.meshes.entry(handle) {
+            dashmap::mapref::entry::Entry::Occupied(entry) => Ok(entry.get().clone()),
+            dashmap::mapref::entry::Entry::Vacant(entry) => {
+                entry.insert(mesh.clone());
+                Ok(mesh)
+            }
+        }
+    }
+
+    pub fn clear_everything(&mut self) {
+        self.materials.clear();
+        self.meshes.clear();
+        log::debug!("Cleared everything in the asset cache");
+    }
+}
\ No newline at end of file
diff --git a/dropbear-engine/src/entity.rs b/dropbear-engine/src/entity.rs
index df351cd..2c61857 100644
--- a/dropbear-engine/src/entity.rs
+++ b/dropbear-engine/src/entity.rs
@@ -1,15 +1,14 @@
-use futures::executor::block_on;
 use glam::{DMat4, DQuat, DVec3, Mat4};
 use serde::{Deserialize, Serialize};
 use std::{
-    path::{Path, PathBuf},
+    path::Path,
     sync::Arc,
 };
 use wgpu::{Buffer, util::DeviceExt};
 
 use crate::{
     graphics::{Instance, SharedGraphicsContext},
-    model::{LazyModel, LazyType, Model},
+    model::Model,
 };
 
 /// A type that represents a position, rotation and scale of an entity
@@ -78,55 +77,6 @@ impl Transform {
     }
 }
 
-/// Creates a new adopted entity in a lazy method. It fetches the data first (which can be done on a separate
-/// thread). After, the [`LazyAdoptedEntity::poke()`] function can be called to convert the Lazy to a Real adopted entity.
-#[derive(Default)]
-pub struct LazyAdoptedEntity {
-    lazy_model: LazyModel,
-    #[allow(dead_code)]
-    label: String,
-}
-
-impl LazyAdoptedEntity {
-    /// Create a LazyAdoptedEntity from a file path (can be run on background thread)
-    pub async fn from_file(path: &PathBuf, label: Option<&str>) -> anyhow::Result<Self> {
-        let buffer = std::fs::read(path)?;
-        Self::from_memory(buffer, label).await
-    }
-
-    /// Create a LazyAdoptedEntity from memory buffer (can be run on background thread)
-    pub async fn from_memory(
-        buffer: impl AsRef<[u8]>,
-        label: Option<&str>,
-    ) -> anyhow::Result<Self> {
-        let lazy_model = Model::lazy_load(buffer, label).await?;
-        let label_str = label.unwrap_or("LazyAdoptedEntity").to_string();
-
-        Ok(Self {
-            lazy_model,
-            label: label_str,
-        })
-    }
-
-    /// Create a LazyAdoptedEntity from an existing LazyModel
-    pub fn from_lazy_model(lazy_model: LazyModel, label: Option<&str>) -> Self {
-        let label_str = label.unwrap_or("LazyAdoptedEntity").to_string();
-        Self {
-            lazy_model,
-            label: label_str,
-        }
-    }
-}
-
-impl LazyType for LazyAdoptedEntity {
-    type T = AdoptedEntity;
-
-    fn poke(self, graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self::T> {
-        let model = self.lazy_model.poke(graphics.clone())?;
-        Ok(block_on(AdoptedEntity::adopt(graphics, model)))
-    }
-}
-
 #[derive(Clone)]
 pub struct AdoptedEntity {
     pub model: Arc<Model>,
@@ -145,11 +95,11 @@ impl AdoptedEntity {
         label: Option<&str>,
     ) -> anyhow::Result<Self> {
         let path = path.as_ref().to_path_buf();
-        let model = Model::load(graphics.clone(), &path, label).await?;
-        Ok(Self::adopt(graphics, model).await)
+        let model_handle = Model::load(graphics.clone(), &path, label).await?;
+        Ok(Self::adopt(graphics, model_handle.get()))
     }
 
-    pub async fn adopt(graphics: Arc<SharedGraphicsContext>, model: Model) -> Self {
+    pub fn adopt(graphics: Arc<SharedGraphicsContext>, model: Arc<Model>) -> Self {
         let label = model.label.clone();
         let instance = Instance::new(DVec3::ZERO, DQuat::IDENTITY, DVec3::ONE);
         let initial_matrix = DMat4::IDENTITY; // Default; update in new() if transform provided
@@ -164,7 +114,7 @@ impl AdoptedEntity {
                 });
 
         Self {
-            model: Arc::new(model),
+            model,
             instance,
             instance_buffer: Some(instance_buffer),
             previous_matrix: initial_matrix,
diff --git a/dropbear-engine/src/lib.rs b/dropbear-engine/src/lib.rs
index 08865a1..dd9506f 100644
--- a/dropbear-engine/src/lib.rs
+++ b/dropbear-engine/src/lib.rs
@@ -1,3 +1,4 @@
+pub mod asset;
 pub mod attenuation;
 pub mod buffer;
 pub mod camera;
diff --git a/dropbear-engine/src/lighting.rs b/dropbear-engine/src/lighting.rs
index 2057965..f431e74 100644
--- a/dropbear-engine/src/lighting.rs
+++ b/dropbear-engine/src/lighting.rs
@@ -8,7 +8,6 @@ use wgpu::{
 
 use crate::attenuation::{Attenuation, RANGE_50};
 use crate::graphics::SharedGraphicsContext;
-use crate::model::{LazyModel, LazyType};
 use crate::shader::Shader;
 use crate::{
     camera::Camera,
@@ -200,104 +199,6 @@ impl LightComponent {
     }
 }
 
-pub struct LazyLight {
-    light_component: LightComponent,
-    transform: Transform,
-    label: Option<String>,
-    cube_lazy_model: Option<LazyModel>,
-}
-
-impl LazyType for LazyLight {
-    type T = Light;
-
-    fn poke(self, graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self::T> {
-        let label_str = self.label.clone().unwrap_or_else(|| "Light".to_string());
-
-        let forward = DVec3::new(0.0, 0.0, -1.0);
-        let direction = self.transform.rotation * forward;
-
-        let uniform = LightUniform {
-            position: dvec3_to_uniform_array(self.transform.position),
-            direction: dvec3_direction_to_uniform_array(
-                direction,
-                self.light_component.outer_cutoff_angle,
-            ),
-            colour: dvec3_colour_to_uniform_array(
-                self.light_component.colour * self.light_component.intensity as f64,
-                self.light_component.light_type,
-            ),
-            constant: self.light_component.attenuation.constant,
-            linear: self.light_component.attenuation.linear,
-            quadratic: self.light_component.attenuation.quadratic,
-            cutoff: f32::cos(self.light_component.cutoff_angle.to_radians()),
-        };
-
-        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());
-
-        let layout = graphics
-            .device
-            .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
-                entries: &[wgpu::BindGroupLayoutEntry {
-                    binding: 0,
-                    visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
-                    ty: wgpu::BindingType::Buffer {
-                        ty: wgpu::BufferBindingType::Uniform,
-                        has_dynamic_offset: false,
-                        min_binding_size: None,
-                    },
-                    count: None,
-                }],
-                label: self.label.as_deref(),
-            });
-
-        let bind_group = graphics
-            .device
-            .create_bind_group(&wgpu::BindGroupDescriptor {
-                layout: &layout,
-                entries: &[wgpu::BindGroupEntry {
-                    binding: 0,
-                    resource: buffer.as_entire_binding(),
-                }],
-                label: self.label.as_deref(),
-            });
-
-        let instance = Instance::new(
-            self.transform.position,
-            self.transform.rotation,
-            DVec3::new(0.25, 0.25, 0.25),
-        );
-
-        let instance_buffer =
-            graphics
-                .device
-                .create_buffer_init(&wgpu::util::BufferInitDescriptor {
-                    label: self.label.as_deref().or(Some("instance buffer")),
-                    contents: bytemuck::cast_slice(&[instance.to_raw()]),
-                    usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
-                });
-
-        log::debug!("Created new light [{}]", label_str);
-
-        Ok(Light {
-            uniform,
-            cube_model,
-            label: label_str,
-            buffer: Some(buffer),
-            layout: Some(layout),
-            bind_group: Some(bind_group),
-            instance_buffer: Some(instance_buffer),
-        })
-    }
-}
-
 #[derive(Clone)]
 pub struct Light {
     pub uniform: LightUniform,
@@ -310,28 +211,6 @@ pub struct Light {
 }
 
 impl Light {
-    pub async fn lazy_new(
-        light_component: LightComponent,
-        transform: Transform,
-        label: Option<&str>,
-    ) -> anyhow::Result<LazyLight> {
-        let mut result = LazyLight {
-            light_component,
-            transform,
-            label: label.map(|s| s.to_string()),
-            cube_lazy_model: None,
-        };
-        if result.cube_lazy_model.is_none() {
-            let lazy_model = Model::lazy_load(
-                include_bytes!("../../resources/models/cube.glb").to_vec(),
-                result.label.as_deref(),
-            )
-            .await?;
-            result.cube_lazy_model = Some(lazy_model);
-        }
-        Ok(result)
-    }
-
     pub async fn new(
         graphics: Arc<SharedGraphicsContext>,
         light: LightComponent,
@@ -356,15 +235,14 @@ impl Light {
 
         log::trace!("Created new light uniform");
 
-        let cube_model = Arc::new(
-            Model::load_from_memory(
-                graphics.clone(),
-                include_bytes!("../../resources/models/cube.glb").to_vec(),
-                label,
-            )
-            .await
-            .unwrap(),
-        );
+        let cube_model = Model::load_from_memory(
+            graphics.clone(),
+            include_bytes!("../../resources/models/cube.glb").to_vec(),
+            label,
+        )
+        .await
+        .expect("failed to load light cube model")
+        .get();
 
         let label_str = label.unwrap_or("Light").to_string();
 
diff --git a/dropbear-engine/src/model.rs b/dropbear-engine/src/model.rs
index 1901dda..74a3a50 100644
--- a/dropbear-engine/src/model.rs
+++ b/dropbear-engine/src/model.rs
@@ -3,11 +3,6 @@ use crate::utils::ResourceReference;
 use image::GenericImageView;
 use lazy_static::lazy_static;
 use parking_lot::Mutex;
-// use russimp_ng::{
-//     Vector3D,
-//     material::{DataContent, TextureType},
-//     scene::{PostProcess, Scene},
-// };
 use rayon::prelude::*;
 use std::collections::HashMap;
 use std::hash::{DefaultHasher, Hash, Hasher};
@@ -19,12 +14,18 @@ use wgpu::{BufferAddress, VertexAttribute, VertexBufferLayout, util::DeviceExt};
 pub const GREY_TEXTURE_BYTES: &[u8] = include_bytes!("../../resources/textures/grey.png");
 
 lazy_static! {
-    pub static ref MODEL_CACHE: Mutex<HashMap<String, Model>> = Mutex::new(HashMap::new());
+    pub static ref MODEL_CACHE: Mutex<HashMap<String, Arc<Model>>> = Mutex::new(HashMap::new());
 }
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
 pub struct ModelId(pub u64);
 
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct MaterialComponent(pub u64);
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct MeshComponent(pub u64);
+
 #[derive(Clone)]
 pub struct Model {
     pub id: ModelId,
@@ -35,13 +36,42 @@ pub struct Model {
 }
 
 #[derive(Clone)]
+pub struct LoadedModel {
+    inner: Arc<Model>,
+}
+
+impl LoadedModel {
+    pub fn new(inner: Arc<Model>) -> Self {
+        Self { inner }
+    }
+
+    /// Returns the unique identifier of the underlying model asset.
+    pub fn id(&self) -> ModelId {
+        self.inner.id
+    }
+
+    /// Provides shared access to the underlying model.
+    pub fn get(&self) -> Arc<Model> {
+        Arc::clone(&self.inner)
+    }
+}
+
+impl std::ops::Deref for LoadedModel {
+    type Target = Model;
+
+    fn deref(&self) -> &Self::Target {
+        &self.inner
+    }
+}
+
+#[derive(Clone)]
 pub struct Material {
     pub name: String,
     pub diffuse_texture: Texture,
     pub bind_group: wgpu::BindGroup,
 }
 
-#[derive(Clone, Hash)]
+#[derive(Clone)]
 pub struct Mesh {
     pub name: String,
     pub vertex_buffer: wgpu::Buffer,
@@ -50,311 +80,12 @@ pub struct Mesh {
     pub material: usize,
 }
 
-#[derive(Default, Clone)]
-pub struct ParsedModelData {
-    pub label: String,
-    pub path: ResourceReference,
-    pub mesh_data: Vec<ParsedMeshData>,
-    pub material_data: Vec<ParsedMaterialData>,
-}
-
-#[derive(Default, Clone)]
-pub struct ParsedMeshData {
-    pub name: String,
-    pub vertices: Vec<ModelVertex>,
-    pub indices: Vec<u32>,
-    pub material_index: usize,
-}
-
-#[derive(Default, Clone)]
-pub struct ParsedMaterialData {
-    pub name: String,
-    pub rgba_data: Vec<u8>,
-    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>;
-}
-
-/// Loads the model into memory but graphics functions are defined after the creation
-/// of the model
-#[derive(Default)]
-pub struct LazyModel {
-    parsed_data: ParsedModelData,
-}
-
-impl LazyType for LazyModel {
-    type T = Model;
-
-    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) = MODEL_CACHE.lock().get(&cache_key) {
-            log::debug!("Model loaded from cache during poke: {:?}", cache_key);
-            return Ok(cached_model.clone());
-        }
-
-        log::debug!(
-            "Creating GPU resources for model: {:?}",
-            self.parsed_data.label
-        );
-
-        let mut materials = Vec::new();
-        for material_data in &self.parsed_data.material_data {
-            let texture_start = Instant::now();
-
-            let diffuse_texture = Texture::from_rgba_buffer(
-                graphics.clone(),
-                &material_data.rgba_data,
-                material_data.dimensions,
-            );
-
-            let bind_group = diffuse_texture.bind_group().to_owned();
-
-            materials.push(Material {
-                name: material_data.name.clone(),
-                diffuse_texture,
-                bind_group,
-            });
-
-            log::debug!(
-                "Created GPU texture for material '{}' in {:?}",
-                material_data.name,
-                texture_start.elapsed()
-            );
-        }
-
-        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 =
-                graphics
-                    .clone()
-                    .device
-                    .create_buffer_init(&wgpu::util::BufferInitDescriptor {
-                        label: Some(&format!("{} Vertex Buffer", mesh_data.name)),
-                        contents: bytemuck::cast_slice(&mesh_data.vertices),
-                        usage: wgpu::BufferUsages::VERTEX,
-                    });
-
-            let index_buffer =
-                graphics
-                    .clone()
-                    .device
-                    .create_buffer_init(&wgpu::util::BufferInitDescriptor {
-                        label: Some(&format!("{} Index Buffer", mesh_data.name)),
-                        contents: bytemuck::cast_slice(&mesh_data.indices),
-                        usage: wgpu::BufferUsages::INDEX,
-                    });
-
-            meshes.push(Mesh {
-                name: mesh_data.name.clone(),
-                vertex_buffer,
-                index_buffer,
-                num_elements: mesh_data.indices.len() as u32,
-                material: mesh_data.material_index,
-            });
-
-            log::debug!(
-                "Created GPU buffers for mesh '{}' in {:?}",
-                mesh_data.name,
-                buffer_start.elapsed()
-            );
-        }
-
-        let model = Model {
-            meshes,
-            materials,
-            label: self.parsed_data.label.clone(),
-            path: self.parsed_data.path.clone(),
-            id: ModelId(hasher.finish()),
-        };
-
-        MODEL_CACHE.lock().insert(cache_key, model.clone());
-        log::debug!(
-            "Model GPU resource creation completed in {:?}",
-            start.elapsed()
-        );
-
-        Ok(model)
-    }
-}
-
 impl Model {
-    /// Creates a [`LazyModel`].
-    pub async fn lazy_load(
-        buffer: impl AsRef<[u8]>,
-        label: Option<&str>,
-    ) -> anyhow::Result<LazyModel> {
-        let start = Instant::now();
-        let label_str = label.unwrap_or("default");
-
-        log::debug!("Starting lazy load for model: {:?}", label_str);
-
-        let res_ref = ResourceReference::from_bytes(buffer.as_ref());
-        let (gltf, buffers, _images) = gltf::import_slice(buffer.as_ref())?;
-
-        let mut texture_data = Vec::new();
-        for material in gltf.materials() {
-            log::debug!("Processing material: {:?}", material.name());
-            let material_name = material.name().unwrap_or("Unnamed Material").to_string();
-
-            let image_data =
-                if let Some(pbr) = material.pbr_metallic_roughness().base_color_texture() {
-                    let texture_info = pbr.texture();
-                    let image = texture_info.source();
-                    match image.source() {
-                        gltf::image::Source::View { view, mime_type: _ } => {
-                            let buffer_data = &buffers[view.buffer().index()];
-                            let start = view.offset();
-                            let end = start + view.length();
-                            buffer_data[start..end].to_vec()
-                        }
-                        gltf::image::Source::Uri { uri, mime_type: _ } => {
-                            log::warn!("External URI textures not supported: {}", uri);
-                            GREY_TEXTURE_BYTES.to_vec()
-                        }
-                    }
-                } else {
-                    GREY_TEXTURE_BYTES.to_vec()
-                };
-
-            texture_data.push((material_name, image_data));
-        }
-
-        if texture_data.is_empty() {
-            texture_data.push(("Default".to_string(), GREY_TEXTURE_BYTES.to_vec()));
-        }
-
-        let parallel_start = Instant::now();
-        let processed_materials: Vec<_> = texture_data
-            .into_par_iter()
-            .map(|(material_name, image_data)| {
-                let material_start = Instant::now();
-
-                let diffuse_image =
-                    image::load_from_memory(&image_data).expect("Failed to load image from memory");
-                let diffuse_rgba = diffuse_image.to_rgba8();
-                let dimensions = diffuse_image.dimensions();
-
-                log::debug!(
-                    "Processed material '{}' in {:?}",
-                    material_name,
-                    material_start.elapsed()
-                );
-
-                ParsedMaterialData {
-                    name: material_name,
-                    rgba_data: diffuse_rgba.into_raw(),
-                    dimensions,
-                }
-            })
-            .collect();
-
-        log::debug!(
-            "Parallel material processing took: {:?}",
-            parallel_start.elapsed()
-        );
-
-        let mut mesh_data = Vec::new();
-        for mesh in gltf.meshes() {
-            log::debug!("Processing mesh: {:?}", mesh.name());
-            for primitive in mesh.primitives() {
-                let reader = primitive.reader(|buffer| Some(&buffers[buffer.index()]));
-
-                let positions: Vec<[f32; 3]> = reader
-                    .read_positions()
-                    .ok_or_else(|| anyhow::anyhow!("Mesh missing positions"))?
-                    .collect();
-
-                let normals: Vec<[f32; 3]> = reader
-                    .read_normals()
-                    .map(|iter| iter.collect())
-                    .unwrap_or_else(|| vec![[0.0, 1.0, 0.0]; positions.len()]);
-
-                let tex_coords: Vec<[f32; 2]> = reader
-                    .read_tex_coords(0)
-                    .map(|iter| iter.into_f32().collect())
-                    .unwrap_or_else(|| vec![[0.0, 0.0]; positions.len()]);
-
-                let vertices: Vec<ModelVertex> = positions
-                    .iter()
-                    .zip(normals.iter())
-                    .zip(tex_coords.iter())
-                    .map(|((pos, norm), tex)| ModelVertex {
-                        position: *pos,
-                        normal: *norm,
-                        tex_coords: *tex,
-                    })
-                    .collect();
-
-                let indices: Vec<u32> = reader
-                    .read_indices()
-                    .ok_or_else(|| anyhow::anyhow!("Mesh missing indices"))?
-                    .into_u32()
-                    .collect();
-
-                let material_index = primitive.material().index().unwrap_or(0);
-
-                mesh_data.push(ParsedMeshData {
-                    name: mesh.name().unwrap_or("Unnamed Mesh").to_string(),
-                    vertices,
-                    indices,
-                    material_index,
-                });
-            }
-        }
-
-        let parsed_data = ParsedModelData {
-            label: label_str.to_string(),
-            path: res_ref,
-            mesh_data,
-            material_data: processed_materials,
-        };
-
-        log::debug!(
-            "Lazy load completed for model: {:?} in {:?}",
-            label_str,
-            start.elapsed()
-        );
-
-        Ok(LazyModel { parsed_data })
-    }
-
     pub async fn load_from_memory(
         graphics: Arc<SharedGraphicsContext>,
         buffer: impl AsRef<[u8]>,
         label: Option<&str>,
-    ) -> anyhow::Result<Model> {
+    ) -> anyhow::Result<LoadedModel> {
         let start = Instant::now();
         let mut hasher = DefaultHasher::new();
 
@@ -362,7 +93,7 @@ impl Model {
 
         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());
+            return Ok(LoadedModel::new(cached_model.clone()));
         }
 
         log::trace!(
@@ -532,27 +263,29 @@ impl Model {
         }
 
         log::debug!("Successfully loaded model [{:?}]", label);
-        let model = Model {
+        let model = Arc::new(Model {
             meshes,
             materials,
             label: label.unwrap_or("No named model").to_string(),
             path: res_ref,
             id: ModelId(hasher.finish()),
-        };
+        });
 
-        MODEL_CACHE.lock().insert(cache_key, model.clone());
+        MODEL_CACHE
+            .lock()
+            .insert(cache_key.clone(), Arc::clone(&model));
         log::trace!("==================== DONE ====================");
         log::debug!("Model cached from memory: {:?}", label);
         log::debug!("Took {:?} to load model: {:?}", start.elapsed(), label);
         log::trace!("==============================================");
-        Ok(model)
+        Ok(LoadedModel::new(model))
     }
 
     pub async fn load(
         graphics: Arc<SharedGraphicsContext>,
         path: &PathBuf,
         label: Option<&str>,
-    ) -> anyhow::Result<Model> {
+    ) -> anyhow::Result<LoadedModel> {
         let file_name = path.file_name();
         log::debug!("Loading model [{:?}]", file_name);
 
@@ -561,19 +294,34 @@ impl Model {
         log::debug!("Checking if model exists in cache");
         if let Some(cached_model) = MODEL_CACHE.lock().get(&path_str) {
             log::debug!("Model loaded from cache: {:?}", path_str);
-            return Ok(cached_model.clone());
+            return Ok(LoadedModel::new(cached_model.clone()));
         }
         log::debug!("Model does not exist in cache, loading memory...");
 
         log::debug!("Path of model: {}", path.display());
 
         let buffer = std::fs::read(path)?;
-        let mut model = Self::load_from_memory(graphics, buffer, label).await?;
-        model.path = ResourceReference::from_path(path)?;
+        let loaded = Self::load_from_memory(graphics, buffer, label).await?;
+
+        let mut model_clone: Model = (*loaded).clone();
+        if let Ok(reference) = ResourceReference::from_path(path) {
+            model_clone.path = reference;
+        }
+        if let Some(custom_label) = label {
+            model_clone.label = custom_label.to_string();
+        }
+
+        let updated = Arc::new(model_clone);
+        {
+            let mut cache = MODEL_CACHE.lock();
+            cache.insert(path_str, Arc::clone(&updated));
+            if let Some(custom_label) = label {
+                cache.insert(custom_label.to_string(), Arc::clone(&updated));
+            }
+        }
 
-        MODEL_CACHE.lock().insert(path_str, model.clone());
         log::debug!("Model cached and loaded: {:?}", file_name);
-        Ok(model)
+        Ok(LoadedModel::new(updated))
     }
 }
 
diff --git a/dropbear-engine/src/procedural/plane.rs b/dropbear-engine/src/procedural/plane.rs
index a019353..0c2ab28 100644
--- a/dropbear-engine/src/procedural/plane.rs
+++ b/dropbear-engine/src/procedural/plane.rs
@@ -1,9 +1,7 @@
 use crate::entity::AdoptedEntity;
 use crate::graphics::{SharedGraphicsContext, Texture};
-use crate::model::{LazyType, MODEL_CACHE, Material, Mesh, Model, ModelId, ModelVertex};
+use crate::model::{MODEL_CACHE, Material, Mesh, Model, ModelId, ModelVertex};
 use crate::utils::{ResourceReference, ResourceReferenceType};
-use futures::executor::block_on;
-use image::GenericImageView;
 use std::hash::{DefaultHasher, Hash, Hasher};
 /// A straight plane (and some components). Thats it.
 ///
@@ -12,104 +10,6 @@ use std::hash::{DefaultHasher, Hash, Hasher};
 use std::sync::Arc;
 use wgpu::{AddressMode, util::DeviceExt};
 
-/// Lazily creates a new Plane. This can only be accessed through the Default trait (which you shouldn't use),
-/// or the [`PlaneBuilder::lazy_build()`] (also taken from [`PlaneBuilder::new()`]).
-#[derive(Default)]
-pub struct LazyPlaneBuilder {
-    rgba_data: Vec<u8>,
-    dimensions: (u32, u32),
-    width: f32,
-    height: f32,
-    tiles_x: u32,
-    tiles_z: u32,
-    label: Option<String>,
-}
-
-impl LazyType for LazyPlaneBuilder {
-    type T = AdoptedEntity;
-
-    fn poke(self, graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self::T> {
-        let mut hasher = DefaultHasher::new();
-
-        let mut vertices = Vec::new();
-        let mut indices = Vec::new();
-
-        for z in 0..=1 {
-            for x in 0..=1 {
-                let position = [
-                    (x as f32 - 0.5) * self.width,
-                    0.0,
-                    (z as f32 - 0.5) * self.height,
-                ];
-                let normal = [0.0, 1.0, 0.0];
-                let tex_coords = [
-                    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,
-                    normal,
-                });
-            }
-        }
-
-        indices.extend_from_slice(&[0, 2, 1, 1, 2, 3]);
-        indices.hash(&mut hasher);
-
-        let vertex_buffer = graphics
-            .device
-            .create_buffer_init(&wgpu::util::BufferInitDescriptor {
-                label: Some(&format!("{:?} Vertex Buffer", self.label.as_deref())),
-                contents: bytemuck::cast_slice(&vertices),
-                usage: wgpu::BufferUsages::VERTEX,
-            });
-        let index_buffer = graphics
-            .device
-            .create_buffer_init(&wgpu::util::BufferInitDescriptor {
-                label: Some(&format!("{:?} Index Buffer", self.label.as_deref())),
-                contents: bytemuck::cast_slice(&indices),
-                usage: wgpu::BufferUsages::INDEX,
-            });
-
-        let mesh = Mesh {
-            name: "plane".to_string(),
-            vertex_buffer,
-            index_buffer,
-            num_elements: indices.len() as u32,
-            material: 0,
-        };
-
-        let diffuse_texture = Texture::new_with_sampler_with_rgba_buffer(
-            graphics.clone(),
-            &self.rgba_data,
-            self.dimensions,
-            AddressMode::Repeat,
-        );
-        let bind_group = diffuse_texture.bind_group().clone();
-        let material = Material {
-            name: "plane_material".to_string(),
-            diffuse_texture,
-            bind_group,
-        };
-
-        let model = Model {
-            label: self.label.as_deref().unwrap_or("Plane").to_string(),
-            path: ResourceReference::from_reference(ResourceReferenceType::Plane),
-            meshes: vec![mesh],
-            materials: vec![material],
-            id: ModelId(hasher.finish()),
-        };
-
-        Ok(block_on(AdoptedEntity::adopt(graphics, model)))
-    }
-}
-
 /// Creates a plane in the form of an AdoptedEntity.
 pub struct PlaneBuilder {
     width: f32,
@@ -146,31 +46,6 @@ impl PlaneBuilder {
         self
     }
 
-    pub async fn lazy_build(
-        mut self,
-        texture_bytes: &[u8],
-        label: Option<&str>,
-    ) -> anyhow::Result<LazyPlaneBuilder> {
-        if self.tiles_x == 0 && self.tiles_z == 0 {
-            self.tiles_x = self.width as u32;
-            self.tiles_z = self.height as u32;
-        }
-
-        let img = image::load_from_memory(texture_bytes)?;
-        let rgba = img.to_rgba8();
-        let dimensions = img.dimensions();
-
-        Ok(LazyPlaneBuilder {
-            rgba_data: rgba.into_raw(),
-            dimensions,
-            width: self.width,
-            height: self.height,
-            tiles_x: self.tiles_x,
-            tiles_z: self.tiles_z,
-            label: label.map(|s| s.to_string()),
-        })
-    }
-
     pub async fn build(
         mut self,
         graphics: Arc<SharedGraphicsContext>,
@@ -222,12 +97,10 @@ impl PlaneBuilder {
 
         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
-        };
+        if let Some(cached_model) = MODEL_CACHE.lock().get(&label) {
+            log::debug!("Model loaded from cache: {:?}", label);
+            return Ok(AdoptedEntity::adopt(graphics, Arc::clone(cached_model)));
+        }
 
         let vertex_buffer = graphics
             .device
@@ -261,20 +134,18 @@ impl PlaneBuilder {
             bind_group,
         };
 
-        let model = if let Some(m) = model {
-            m
-        } else {
-            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
-        };
+        let model = Arc::new(Model {
+            label: label.clone(),
+            path: ResourceReference::from_reference(ResourceReferenceType::Plane),
+            meshes: vec![mesh],
+            materials: vec![material],
+            id: ModelId(hash),
+        });
+
+        MODEL_CACHE
+            .lock()
+            .insert(label, Arc::clone(&model));
 
-        Ok(AdoptedEntity::adopt(graphics, model).await)
+        Ok(AdoptedEntity::adopt(graphics, model))
     }
 }
diff --git a/eucalyptus-core/src/states.rs b/eucalyptus-core/src/states.rs
index 06691cf..d49f79f 100644
--- a/eucalyptus-core/src/states.rs
+++ b/eucalyptus-core/src/states.rs
@@ -1088,7 +1088,7 @@ impl SceneConfig {
                         Some(&entity_config.label),
                     )
                     .await?;
-                    let adopted = AdoptedEntity::adopt(graphics.clone(), model).await;
+                    let adopted = AdoptedEntity::adopt(graphics.clone(), model.get());
 
                     let transform = entity_config.transform;
 
diff --git a/eucalyptus-editor/src/spawn.rs b/eucalyptus-editor/src/spawn.rs
index dd9d19d..df6531d 100644
--- a/eucalyptus-editor/src/spawn.rs
+++ b/eucalyptus-editor/src/spawn.rs
@@ -55,7 +55,7 @@ impl PendingSpawnController for Editor {
                                 Some(&asset_name),
                             )
                             .await?;
-                            Ok(AdoptedEntity::adopt(graphics_clone, model).await)
+                            Ok(AdoptedEntity::adopt(graphics_clone, model.get()))
                         }
                         ResourceReferenceType::Plane => {
                             let get_float = |key: &str| -> anyhow::Result<f32> {
diff --git a/redback-runtime/build.rs b/redback-runtime/build.rs
index cf74f49..f328e4d 100644
--- a/redback-runtime/build.rs
+++ b/redback-runtime/build.rs
@@ -1,3 +1 @@
-fn main() {
-
-}
\ No newline at end of file
+fn main() {}
diff --git a/redback-runtime/src/main.rs b/redback-runtime/src/main.rs
index 5a2b998..7f755fb 100644
--- a/redback-runtime/src/main.rs
+++ b/redback-runtime/src/main.rs
@@ -1,4 +1,2 @@
 #[tokio::main]
-async fn main() {
-    
-}
\ No newline at end of file
+async fn main() {}