tirbofish/dropbear · commit
cc363a72bc363968029537112f7326997cb5a536
fix: terrible performance within the GameEditor
fix: saving didnt work properly and hiked up CPU usage to 100%. fix: attempted to make ResourceReferenceType::Bytes be Arc<[u8]> for cloning (memory opt).
Signature present but could not be verified.
Unverified
@@ -4,6 +4,7 @@ use crate::procedural::ProcedurallyGeneratedObject; use serde::{Deserialize, Serialize}; use std::fmt::{Display, Formatter}; use std::path::Path; +use std::sync::Arc; pub const EUCA_SCHEME: &str = "euca://"; @@ -97,7 +98,7 @@ pub enum ResourceReferenceType { /// The content in bytes. Sometimes, there is a model that is loaded into memory through the /// [`include_bytes!`] macro, this type stores it. - Bytes(Vec<u8>), + Bytes(Arc<[u8]>), /// An object that can be generated at runtime with the usage of vertices and indices, as well /// as a solid grey mesh. @@ -145,10 +146,15 @@ impl ResourceReference { /// Creates a new `ResourceReference` from bytes pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Self { Self { - ref_type: ResourceReferenceType::Bytes(bytes.as_ref().to_vec()), + ref_type: ResourceReferenceType::Bytes(Arc::<[u8]>::from(bytes.as_ref())), } } + /// Returns true when this reference points to a file path rather than embedded bytes. + pub fn is_file_backed(&self) -> bool { + matches!(self.ref_type, ResourceReferenceType::File(_)) + } + pub fn from_reference(ref_type: ResourceReferenceType) -> Self { match ref_type { ResourceReferenceType::File(reference) => { @@ -240,7 +246,7 @@ impl ResourceReference { pub fn as_bytes(&self) -> Option<&[u8]> { match &self.ref_type { - ResourceReferenceType::Bytes(bytes) => Some(bytes), + ResourceReferenceType::Bytes(bytes) => Some(bytes.as_ref()), _ => None, } } @@ -24,7 +24,33 @@ use std::time::{SystemTime, UNIX_EPOCH}; pub use typetag::*; +// todo: fix up this --------------- pub const DRAGGED_ASSET_ID: &str = "dragged_asset_reference"; +fn sanitize_resource_reference_for_scene_save( + reference: ResourceReference, + context: &str, +) -> ResourceReference { + match reference.ref_type { + ResourceReferenceType::Bytes(bytes) => { + log::warn!( + "Dropping in-memory byte resource for {} during scene save ({} bytes); use a file-backed asset for persistence", + context, + bytes.len() + ); + ResourceReference::default() + } + _ => reference, + } +} + +fn sanitize_optional_resource_reference_for_scene_save( + reference: Option<ResourceReference>, + context: &str, +) -> Option<ResourceReference> { + reference.map(|resource| sanitize_resource_reference_for_scene_save(resource, context)) +} + +// ------------------------ pub struct ComponentRegistry { /// Maps TypeId to ComponentDescriptor for quick lookups @@ -589,10 +615,11 @@ impl Component for MeshRenderer { ResourceReferenceType::File(_) => { let path = dif.resolve().ok()?; let bytes = std::fs::read(&path).ok()?; - let texture = TextureBuilder::new(&graphics.device) + let mut texture = TextureBuilder::new(&graphics.device) .from_bytes(graphics.clone(), bytes.as_slice()) .label(label.as_str()) .build(); + texture.reference = Some(dif.clone()); let mut registry = ASSET_REGISTRY.write(); Some(Ok(registry.add_texture_with_label(label.clone(), texture))) } @@ -662,7 +689,13 @@ impl Component for MeshRenderer { let asset = ASSET_REGISTRY.read(); let model = asset.get_model(self.model()); let (label, handle) = if let Some(model) = model.as_ref() { - (model.label.clone(), model.path.clone()) + ( + model.label.clone(), + sanitize_resource_reference_for_scene_save( + model.path.clone(), + "mesh renderer model handle", + ), + ) } else { if !self.model().is_null() { log::warn!( @@ -689,6 +722,10 @@ impl Component for MeshRenderer { .get_texture(mat.diffuse_texture) .and_then(|t| t.reference.clone()) }; + let diffuse_texture = sanitize_optional_resource_reference_for_scene_save( + diffuse_texture, + "mesh renderer diffuse texture", + ); let normal_texture = if default_material.map(|default| default.normal_texture) == Some(mat.normal_texture) @@ -698,6 +735,10 @@ impl Component for MeshRenderer { mat.normal_texture .and_then(|h| asset.get_texture(h).and_then(|t| t.reference.clone())) }; + let normal_texture = sanitize_optional_resource_reference_for_scene_save( + normal_texture, + "mesh renderer normal texture", + ); let emissive_texture = if default_material.map(|default| default.emissive_texture) == Some(mat.emissive_texture) @@ -707,6 +748,10 @@ impl Component for MeshRenderer { mat.emissive_texture .and_then(|h| asset.get_texture(h).and_then(|t| t.reference.clone())) }; + let emissive_texture = sanitize_optional_resource_reference_for_scene_save( + emissive_texture, + "mesh renderer emissive texture", + ); let occlusion_texture = if default_material.map(|default| default.occlusion_texture) == Some(mat.occlusion_texture) @@ -716,6 +761,10 @@ impl Component for MeshRenderer { mat.occlusion_texture .and_then(|h| asset.get_texture(h).and_then(|t| t.reference.clone())) }; + let occlusion_texture = sanitize_optional_resource_reference_for_scene_save( + occlusion_texture, + "mesh renderer occlusion texture", + ); let metallic_roughness_texture = if default_material .map(|default| default.metallic_roughness_texture) @@ -726,6 +775,10 @@ impl Component for MeshRenderer { mat.metallic_roughness_texture .and_then(|h| asset.get_texture(h).and_then(|t| t.reference.clone())) }; + let metallic_roughness_texture = sanitize_optional_resource_reference_for_scene_save( + metallic_roughness_texture, + "mesh renderer metallic-roughness texture", + ); texture_override.insert( label.to_string(), @@ -80,6 +80,7 @@ impl ProjectConfig { /// reload or write scene/resource/source configs. This is intended for small editor-facing /// settings updates (like per-model import scales) where reloading configs would be disruptive. pub fn write_project_only(&mut self) -> anyhow::Result<()> { + log::debug!("Writing the project config"); self.date_last_accessed = format!("{}", Utc::now().format("%Y-%m-%d %H:%M:%S")); let ron_str = ron::ser::to_string_pretty(&self, PrettyConfig::default()) @@ -90,6 +91,7 @@ impl ProjectConfig { .join(format!("{}.eucp", self.project_name.clone().to_lowercase())); fs::write(&config_path, ron_str).map_err(|e| anyhow::anyhow!(e.to_string()))?; + log::debug!("Written to {}", config_path.display()); Ok(()) } @@ -141,14 +141,18 @@ impl SceneConfig { /// Write the scene config to a .eucs file pub fn write_to(&self, project_path: impl AsRef<Path>) -> anyhow::Result<()> { + log::debug!("Writing scene config"); let ron_str = ron::ser::to_string_pretty(&self, PrettyConfig::default()) .map_err(|e| anyhow::anyhow!("RON serialization error: {}", e))?; let scenes_dir = project_path.as_ref().join("scenes"); + log::debug!("Creating scene dir at {}", scenes_dir.display()); fs::create_dir_all(&scenes_dir)?; let config_path = scenes_dir.join(format!("{}.eucs", self.scene_name)); - fs::write(&config_path, ron_str).map_err(|e| anyhow::anyhow!(e.to_string()))?; + log::debug!("Writing scene info to {}", config_path.display()); + fs::write(&config_path, ron_str)?; + log::debug!("Wrote scene config to {}", config_path.display()); Ok(()) } @@ -11,7 +11,7 @@ readme = "README.md" eucalyptus-core = { path = "../eucalyptus-core", features = ["editor"] } magna-carta = { path = "../magna-carta" } redback-runtime = { path = "../redback-runtime", features = ["debug"]} -kino-ui = { path = "../kino-ui" } +kino-ui = { path = "../kino-ui", features = ["trace"]} anyhow.workspace = true app_dirs2.workspace = true @@ -6,16 +6,19 @@ use eucalyptus_core::{ states::{Label, PROJECT}, }; use hecs::{Entity, World}; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, HashMap}; use crate::editor::{Editor, EditorTabDock, EditorTabDockDescriptor, EditorTabViewer, Signal, StaticallyKept, TABS_GLOBAL}; use crate::editor::page::EditorTabVisibility; impl<'a> EditorTabViewer<'a> { pub(crate) fn entity_list(&mut self, ui: &mut egui::Ui) { + puffin::profile_function!(); let mut cfg = TABS_GLOBAL.lock(); - let (_response, action) = egui_ltreeview::TreeView::new(egui::Id::new( + let (_response, action) = { + puffin::profile_scope!("entity_list.tree_build"); + egui_ltreeview::TreeView::new(egui::Id::new( "model_entity_list", )) .show(ui, |builder| { @@ -44,9 +47,12 @@ impl<'a> EditorTabViewer<'a> { entity: Entity, world: &mut World, registry: &ComponentRegistry, + component_ids_by_entity: &HashMap<Entity, Vec<u64>>, + rigidbody_component_id: Option<u64>, cfg: &mut StaticallyKept, signal: &mut Signal, ) -> anyhow::Result<()> { + puffin::profile_scope!("entity_list.add_entity_to_tree"); let entity_id = entity.to_bits().get(); let label = if let Ok(label) = world.query_one::<&Label>(entity).get() { @@ -112,51 +118,57 @@ impl<'a> EditorTabViewer<'a> { }), ); - let components = registry.extract_all_components(world, entity); - - for component in components.iter() { - let Some(component_type_id) = - registry.id_for_component(component.as_ref()) - else { - log_once::warn_once!( - "Component missing registry id, skipping tree entry" - ); - continue; - }; + if let Some(component_ids) = component_ids_by_entity.get(&entity) { let display_id = crate::features::is_enabled(crate::features::ShowComponentTypeIDInEditor); - - let component_node_id = - cfg.component_node_id(entity, component_type_id as u64); - let display = registry - .get_descriptor_by_numeric_id(component_type_id) - .map(|desc| if display_id { format!("{} (id #{component_type_id})", desc.type_name) } else { desc.type_name.clone() }) - .unwrap_or_else(|| if display_id { format!("Unknown (id #{component_type_id})") } else { String::from("Unknown")}); - let has_rigidbody = world.get::<&RigidBody>(entity).is_ok(); let has_collider = world.get::<&ColliderGroup>(entity).is_ok(); - let node = NodeBuilder::leaf(component_node_id) - .label_ui(|ui| { - ui.label(display.clone()); + for component_type_id in component_ids { + let component_node_id = + cfg.component_node_id(entity, *component_type_id); + let display = registry + .get_descriptor_by_numeric_id(*component_type_id) + .map(|desc| { + if display_id { + format!("{} (id #{component_type_id})", desc.type_name) + } else { + desc.type_name.clone() + } + }) + .unwrap_or_else(|| { + if display_id { + format!("Unknown (id #{component_type_id})") + } else { + String::from("Unknown") + } + }); - if has_rigidbody && !has_collider && component.typetag_name().contains("RigidBody") { - ui.add_space(4.0); - ui.small_button("⚠") - .on_hover_text("RigidBody has no colliders! Add the ColliderGroup component"); - } - }) - .context_menu(|ui| { - if ui.button("Remove Component").clicked() { - registry.remove_component_by_id( - world, - entity, - component_type_id, - ); - ui.close(); - } - }); - - builder.node(node); + let node = NodeBuilder::leaf(component_node_id) + .label_ui(|ui| { + ui.label(display.clone()); + + if has_rigidbody + && !has_collider + && Some(*component_type_id) == rigidbody_component_id + { + ui.add_space(4.0); + ui.small_button("⚠") + .on_hover_text("RigidBody has no colliders! Add the ColliderGroup component"); + } + }) + .context_menu(|ui| { + if ui.button("Remove Component").clicked() { + registry.remove_component_by_id( + world, + entity, + *component_type_id, + ); + ui.close(); + } + }); + + builder.node(node); + } } let children_entities = if let Ok(children) = world.get::<&Children>(entity) { @@ -167,7 +179,16 @@ impl<'a> EditorTabViewer<'a> { for child in children_entities { if let Err(e) = - add_entity_to_tree(builder, child, world, registry, cfg, signal) + add_entity_to_tree( + builder, + child, + world, + registry, + component_ids_by_entity, + rigidbody_component_id, + cfg, + signal, + ) { log_once::error_once!( "Failed to add child entity to tree, skipping: {}", @@ -181,6 +202,32 @@ impl<'a> EditorTabViewer<'a> { Ok(()) } + let mut component_ids_by_entity: HashMap<Entity, Vec<u64>> = HashMap::new(); + let rigidbody_component_id = self + .component_registry + .iter_available_components() + .find_map(|(id, desc)| { + if desc.fqtn == "eucalyptus_core::physics::rigidbody::RigidBody" { + Some(id) + } else { + None + } + }); + { + puffin::profile_scope!("entity_list.index_components"); + for (component_id, _) in self.component_registry.iter_available_components() { + for entity in self + .component_registry + .find_entities_by_numeric_id(self.world, component_id) + { + component_ids_by_entity + .entry(entity) + .or_default() + .push(component_id); + } + } + } + let root_entities: Vec<Entity> = self .world .query::<Entity>() @@ -190,11 +237,14 @@ impl<'a> EditorTabViewer<'a> { .collect(); for entity in root_entities { + puffin::profile_scope!("entity_list.root_entity"); if let Err(e) = add_entity_to_tree( builder, entity, &mut self.world, &self.component_registry, + &component_ids_by_entity, + rigidbody_component_id, &mut cfg, self.signal, ) { @@ -206,8 +256,10 @@ impl<'a> EditorTabViewer<'a> { } builder.close_dir(); - }); + }) + }; + puffin::profile_scope!("entity_list.actions"); for i in action { match i { egui_ltreeview::Action::SetSelected(items) => { @@ -32,6 +32,7 @@ impl Keyboard for Editor { || self.input_state.pressed_keys.contains(&KeyCode::ShiftRight); let is_double_press = self.double_key_pressed(key); + let is_key_repeat = self.input_state.pressed_keys.contains(&key); let is_playing = matches!(self.editor_state, EditorState::Playing); @@ -112,7 +113,7 @@ impl Keyboard for Editor { } } KeyCode::KeyQ => { - if ctrl_pressed && !is_playing { + if ctrl_pressed && !is_playing && !is_key_repeat { match self.save_project_config() { Ok(_) => {} Err(e) => { @@ -212,7 +213,7 @@ impl Keyboard for Editor { } } KeyCode::KeyS => { - if ctrl_pressed { + if ctrl_pressed && !is_key_repeat { if !is_playing { match self.save_project_config() { Ok(_) => { @@ -406,6 +406,7 @@ impl Editor { /// Save the current world state to the active scene pub fn save_current_scene(&mut self) -> anyhow::Result<()> { + log::debug!("Saving current scene"); let mut scenes = SCENES.write(); if scenes.is_empty() { @@ -430,15 +431,14 @@ impl Editor { scene.scene_name ); - let labels = self - .world - .query::<(Entity, &Label)>() - .iter() - .map(|(e, l)| (e, l.clone())) - .collect::<Vec<_>>(); + let entity_ids = self.world.query::<Entity>().iter().collect::<Vec<_>>(); - for (id, label) in labels { - let entity_label = label.clone(); + for id in entity_ids { + let Ok(label) = self.world.get::<&Label>(id) else { + log::warn!("Skipping entity {:?} without Label during save", id); + continue; + }; + let entity_label = (*label).clone(); let components = self .component_registry @@ -461,13 +461,13 @@ impl Editor { } let scene_entity = SceneEntity { - label: entity_label.clone(), + label: entity_label, components, entity_id: Some(id), }; + log::debug!("Saved entity: {}", scene_entity.label); scene.entities.push(scene_entity); - log::debug!("Saved entity: {}", entity_label); } log::info!( @@ -476,49 +476,24 @@ impl Editor { scene.scene_name ); - Ok(()) - } - - fn persist_active_scene_to_disk(&self) -> anyhow::Result<()> { - let target_scene_name = self.current_scene_name.clone().or_else(|| { - let scenes = SCENES.read(); - scenes.first().map(|scene| scene.scene_name.clone()) - }); - - let Some(scene_name) = target_scene_name else { - return Ok(()); - }; - - let scene_clone = { - let scenes = SCENES.read(); - scenes - .iter() - .find(|scene| scene.scene_name == scene_name) - .cloned() - }; - - let Some(scene_clone) = scene_clone else { - log::warn!( - "Attempted to persist scene '{}' but it is not loaded", - scene_name - ); - return Ok(()); - }; - let project_path = { let project = PROJECT.read(); project.project_path.clone() }; - scene_clone.write_to(&project_path)?; + log::debug!("Writing scene to {}", project_path.display()); + scene.write_to(&project_path)?; + log::debug!("Saved active scene '{}' to disk", scene.scene_name); + Ok(()) } pub fn save_project_config(&mut self) -> anyhow::Result<()> { + log::debug!("starting save of project config"); self.save_current_scene()?; - self.persist_active_scene_to_disk()?; - + { + log::debug!("Writing to editor settings"); let mut config = EDITOR_SETTINGS.write(); config.game_editor_dock_state = Some(self.game_editor_dock_state.clone()); config.ui_editor_dock_state = Some(self.ui_editor_dock_state.clone()); @@ -526,8 +501,9 @@ impl Editor { } { + log::debug!("Writing to project"); let mut config = PROJECT.write(); - config.write_to_all()?; + config.write_project_only()?; } Ok(()) @@ -723,7 +699,6 @@ impl Editor { if should_persist_current { self.save_current_scene()?; - self.persist_active_scene_to_disk()?; } if let Some(current) = self.current_scene_name.as_deref() { @@ -913,15 +888,21 @@ impl Editor { } pub fn show_ui(&mut self, ctx: &Context, graphics: Arc<SharedGraphicsContext>) { - if let Some(scene_name) = self.pending_scene_creation.take() { - let result = self.create_new_scene(scene_name.as_str()); - self.new_scene_name.clear(); - if let Err(e) = result { - fatal!("Failed to create scene '{}': {}", scene_name, e); + puffin::profile_function!(); + + { + puffin::profile_scope!("show_ui.pending_scene_creation"); + if let Some(scene_name) = self.pending_scene_creation.take() { + let result = self.create_new_scene(scene_name.as_str()); + self.new_scene_name.clear(); + if let Err(e) = result { + fatal!("Failed to create scene '{}': {}", scene_name, e); + } } } egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| { + puffin::profile_scope!("show_ui.top_bar"); let top_bar_rect = ui.max_rect(); ui.horizontal(|ui| { @@ -1226,6 +1207,7 @@ impl Editor { ); ui.scope_builder(egui::UiBuilder::new().max_rect(center_rect), |ui| { + puffin::profile_scope!("show_ui.page_switcher"); ui.with_layout( egui::Layout::centered_and_justified(egui::Direction::LeftToRight), |ui| { @@ -1254,6 +1236,7 @@ impl Editor { ); ui.scope_builder(egui::UiBuilder::new().max_rect(right_rect), |ui| { + puffin::profile_scope!("show_ui.play_controls"); ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { let can_play = matches!(self.editor_state, EditorState::Playing); ui.group(|ui| { @@ -1293,6 +1276,7 @@ impl Editor { egui::TopBottomPanel::bottom("status bar") .resizable(false) .show(ctx, |ui| { + puffin::profile_scope!("show_ui.status_bar"); let active_camera = *self.active_camera.lock(); let (label, is_debug, active_entity) = if let Some(entity) = active_camera { let camera_label = self @@ -1363,6 +1347,7 @@ impl Editor { let Some(view) = self.texture_id else { egui::CentralPanel::default().show(ctx, |ui| { + puffin::profile_scope!("show_ui.viewport_initialising"); ui.centered_and_justified(|ui| { ui.label("Viewport is still initialising..."); }); @@ -1371,6 +1356,7 @@ impl Editor { }; egui::CentralPanel::default().show(ctx, |ui| { + puffin::profile_scope!("show_ui.dock_area"); DockArea::new(if self.current_page.contains(EditorTabVisibility::GameEditor) { &mut self.game_editor_dock_state } else if self.current_page.contains(EditorTabVisibility::UIEditor) { @@ -492,6 +492,7 @@ impl Scene for Editor { .query::<(Entity, &MeshRenderer, Option<&mut AnimationComponent>)>(); for (entity, renderer, animation) in query.iter() { + puffin::profile_scope!(format!("locating {:?}", entity)); let handle = renderer.model(); if handle.is_null() { continue; @@ -610,6 +611,7 @@ impl Scene for Editor { render_pass.set_pipeline(light_pipeline.pipeline()); for light in &lights { + puffin::profile_scope!("rendering light", &light.label); render_pass.set_vertex_buffer(1, light.instance_buffer.buffer().slice(..)); if !light.component.visible { continue; @@ -763,6 +765,7 @@ impl Scene for Editor { morph_weight_count, ) in animated_instances { + puffin::profile_scope!("rendering animated model", format!("{:?}", entity)); let Some(model) = registry.get_model(Handle::new(handle)) else { log_once::error_once!("Missing model handle {} in registry", handle); continue; @@ -1021,6 +1024,7 @@ impl Scene for Editor { // kino billboard renderer (late stage, runtime parity) { + puffin::profile_scope!("rendering billboard targets"); if let Some(kino) = &mut self.kino { let mut kino_encoder = CommandEncoder::new(graphics.clone(), Some("kino billboard encoder")); kino.render_billboard_targets( @@ -1055,6 +1059,7 @@ impl Scene for Editor { .query::<(Entity, &BillboardComponent, Option<&EntityTransform>)>(); for (entity, billboard, entity_transform) in query.iter() { + puffin::profile_scope!("rendering billboard", format!("{:?}", entity)); if !billboard.enabled { continue; } @@ -1100,6 +1105,7 @@ impl Scene for Editor { } if !billboards.is_empty() { + puffin::profile_scope!("billboard render pass"); let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("editor billboard render pass"), color_attachments: &[Some(wgpu::RenderPassColorAttachment { @@ -67,6 +67,7 @@ impl EditorSettings { /// Saves the current EditorSettings configuration (as shown in [EDITOR_SETTINGS]) into `{app_dir}/editor.eucc`. pub fn save(&self) -> anyhow::Result<()> { + log::debug!("Saving the current EditorSettings configuration"); let app_data = app_dirs2::app_root(AppDataType::UserData, &APP_INFO)?; let serialized = ron::ser::to_string_pretty(&self, ron::ser::PrettyConfig::default())?; std::fs::write(app_data.join("editor.eucc"), serialized)?; @@ -21,6 +21,8 @@ winit.workspace = true glyphon.workspace = true serde = { workspace = true, optional = true} typetag = { workspace = true, optional = true} +puffin = { workspace = true, optional = true} [features] ser = ["serde", "typetag"] +trace = ["puffin"] @@ -256,6 +256,7 @@ impl KinoState { queue: &wgpu::Queue, encoder: &mut wgpu::CommandEncoder, ) { + puffin::profile_function!(); self.render_target_cache.tick(); let targets = self @@ -269,6 +270,7 @@ impl KinoState { .collect::<Vec<_>>(); for target in targets { + puffin::profile_scope!("rendering target", format!("{:?}", target)); let mut geometry = self .batches .remove(&target)