tirbofish/dropbear · diff
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
Signature present but could not be verified.
Unverified
@@ -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" @@ -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 @@ -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() } } @@ -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. @@ -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)); @@ -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(); @@ -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); @@ -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) } } @@ -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 { @@ -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(); @@ -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, @@ -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()); } @@ -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, ); } @@ -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, @@ -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, @@ -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 { @@ -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 {