tirbofish/dropbear · diff
spewed something that resembled a component tree. i mean, it sorta works?
a lot of changed need to be made, such as a proper ID, and implementing SerializableComponent for everything else + some proper plugin work. otherwise, it is pretty nice.
Signature present but could not be verified.
Unverified
@@ -77,6 +77,7 @@ typetag = "0.2" syn = { version = "2.0", features = ["full"] } quote = "1.0" egui_ltreeview = { version = "0.6", features = ["doc"] } +dyn-hash = "1.0" [workspace.dependencies.image] version = "0.25" @@ -8,3 +8,7 @@ readme = "README.md" [dependencies] typetag.workspace = true +hecs.workspace = true +log.workspace = true +dyn-hash.workspace = true +anyhow.workspace = true @@ -0,0 +1,99 @@ +use std::any::TypeId; +use std::collections::HashMap; +use anyhow::Result; +use hecs::{Entity, EntityBuilder, World}; +use crate::{ + ComponentConverter, + ComponentDeserializer, + CustomConverter, + CustomDeserializer, + DirectConverter, + DirectDeserializer, + SerializableComponent, +}; + +#[derive(Default)] +pub struct ComponentRegistry { + converters: HashMap<TypeId, Box<dyn ComponentConverter>>, + deserializers: HashMap<TypeId, Box<dyn ComponentDeserializer>>, +} + +impl ComponentRegistry { + pub fn new() -> Self { + Self::default() + } + + // Register a component that's already SerializableComponent + pub fn register<T>(&mut self) + where + T: SerializableComponent + hecs::Component + Clone + 'static, + { + let type_id = TypeId::of::<T>(); + self.converters + .insert(type_id, Box::new(DirectConverter::<T>::new())); + self.deserializers + .insert(type_id, Box::new(DirectDeserializer::<T>::new())); + } + + // Register a custom converter for special cases + pub fn register_converter<From, To, F>(&mut self, converter_fn: F) + where + From: hecs::Component + 'static, + To: SerializableComponent + 'static, + F: Fn(&From) -> To + Send + Sync + 'static, + { + let type_id = TypeId::of::<From>(); + self.converters.insert( + type_id, + Box::new(CustomConverter::new(converter_fn)) + ); + } + + pub fn register_deserializer<From, To, F>(&mut self, converter_fn: F) + where + From: SerializableComponent + 'static, + To: hecs::Component + Clone + 'static, + F: Fn(&From) -> To + Send + Sync + 'static, + { + let type_id = TypeId::of::<From>(); + self.deserializers.insert( + type_id, + Box::new(CustomDeserializer::new(converter_fn)), + ); + } + + // Extract all serializable components from an entity + pub fn extract_all_components( + &self, + world: &World, + entity: Entity, + ) -> Vec<Box<dyn SerializableComponent>> { + let mut vec = vec![]; + for (k, v) in &self.converters + { + match v.extract_serializable(world, entity) { + Some(v) => vec.push(v), + None => {log::error!("Unable to extract the serializable value for entity [typeid: {:?}]", k); continue;} + } + } + return vec; + } + + /// Attempts to deserialize a [`SerializableComponent`] back into an + /// ECS component and insert it into the provided [`EntityBuilder`]. + /// Returns `Ok(true)` if the component was handled, `Ok(false)` if no + /// deserializer was registered, and `Err` if deserialization failed. + pub fn deserialize_into_builder( + &self, + component: &dyn SerializableComponent, + builder: &mut EntityBuilder, + ) -> Result<bool> { + let type_id = component.as_any().type_id(); + if let Some(deserializer) = self.deserializers.get(&type_id) { + deserializer.insert_into_builder(component, builder)?; + Ok(true) + } else { + Ok(false) + } + } +} @@ -1,5 +1,9 @@ -use std::any::Any; +pub mod component_registry; + +use std::any::{Any, TypeId}; use std::fmt::Debug; +use anyhow::{anyhow, Result}; +use hecs::{Entity, EntityBuilder, World}; /// A type of component that gets serialized and deserialized into a scene config file. #[typetag::serde(tag = "type")] @@ -14,8 +18,194 @@ pub trait SerializableComponent: Send + Sync + Debug { fn clone_boxed(&self) -> Box<dyn SerializableComponent>; } + impl Clone for Box<dyn SerializableComponent> { fn clone(&self) -> Self { self.clone_boxed() } + + fn clone_from(&mut self, source: &Self) + { + *self = source.clone_boxed(); + } +} + +pub trait ComponentConverter: Send + Sync { + fn type_id(&self) -> TypeId; + fn type_name(&self) -> &'static str; + + fn extract_serializable( + &self, + world: &World, + entity: Entity + ) -> Option<Box<dyn SerializableComponent>>; +} + +pub trait ComponentDeserializer: Send + Sync { + fn serializable_type_id(&self) -> TypeId; + fn serializable_type_name(&self) -> &'static str; + + fn insert_into_builder( + &self, + component: &dyn SerializableComponent, + builder: &mut EntityBuilder, + ) -> Result<()>; +} + +struct DirectConverter<T: SerializableComponent + hecs::Component + Clone> { + _phantom: std::marker::PhantomData<T>, +} + +impl<T: SerializableComponent + hecs::Component + Clone + 'static> DirectConverter<T> { + fn new() -> Self { + Self { _phantom: std::marker::PhantomData } + } +} + +impl<T: SerializableComponent + hecs::Component + Clone + 'static> ComponentConverter for DirectConverter<T> { + fn type_id(&self) -> TypeId { + TypeId::of::<T>() + } + + fn type_name(&self) -> &'static str { + std::any::type_name::<T>() + } + + fn extract_serializable(&self, world: &World, entity: Entity) -> Option<Box<dyn SerializableComponent>> { + if let Ok(mut q) = world.query_one::<&T>(entity) && let Some(ty) = q.get() { + return Some(ty.clone_boxed()); + } + None + } +} + +/// Custom converter that has special logic for converting `T` to a [`SerializableComponent`] +struct CustomConverter<From, To, F> +where + From: hecs::Component, + To: SerializableComponent, + F: Fn(&From) -> To + Send + Sync, +{ + converter_fn: F, + _phantom: std::marker::PhantomData<(From, To)>, +} + +impl<From, To, F> CustomConverter<From, To, F> +where + From: hecs::Component + 'static, + To: SerializableComponent + 'static, + F: Fn(&From) -> To + Send + Sync, +{ + fn new(converter_fn: F) -> Self { + Self { + converter_fn, + _phantom: std::marker::PhantomData, + } + } +} + +impl<From, To, F> ComponentConverter for CustomConverter<From, To, F> +where + From: hecs::Component + 'static, + To: SerializableComponent + 'static, + F: Fn(&From) -> To + Send + Sync, +{ + fn type_id(&self) -> TypeId { + TypeId::of::<From>() + } + + fn type_name(&self) -> &'static str { + std::any::type_name::<From>() + } + + fn extract_serializable(&self, world: &World, entity: Entity) -> Option<Box<dyn SerializableComponent>> { + world.get::<&From>(entity).ok() + .as_ref().map(|component| Box::new((self.converter_fn)(component)) as Box<dyn SerializableComponent>) + } +} + +struct DirectDeserializer<T: SerializableComponent + hecs::Component + Clone> { + _phantom: std::marker::PhantomData<T>, +} + +impl<T: SerializableComponent + hecs::Component + Clone + 'static> DirectDeserializer<T> { + pub fn new() -> Self { + Self { _phantom: std::marker::PhantomData } + } +} + +impl<T: SerializableComponent + hecs::Component + Clone + 'static> ComponentDeserializer for DirectDeserializer<T> { + fn serializable_type_id(&self) -> TypeId { + TypeId::of::<T>() + } + + fn serializable_type_name(&self) -> &'static str { + std::any::type_name::<T>() + } + + fn insert_into_builder(&self, component: &dyn SerializableComponent, builder: &mut EntityBuilder) -> Result<()> { + let typed = component + .as_any() + .downcast_ref::<T>() + .ok_or_else(|| anyhow!( + "Component '{}' does not match registered type '{}'", + component.type_name(), + std::any::type_name::<T>() + ))?; + builder.add(typed.clone()); + Ok(()) + } +} + +struct CustomDeserializer<From, To, F> +where + From: SerializableComponent, + To: hecs::Component, + F: Fn(&From) -> To + Send + Sync, +{ + converter_fn: F, + _phantom: std::marker::PhantomData<(From, To)>, +} + +impl<From, To, F> CustomDeserializer<From, To, F> +where + From: SerializableComponent + 'static, + To: hecs::Component + Clone + 'static, + F: Fn(&From) -> To + Send + Sync + 'static, +{ + pub fn new(converter_fn: F) -> Self { + Self { + converter_fn, + _phantom: std::marker::PhantomData, + } + } +} + +impl<From, To, F> ComponentDeserializer for CustomDeserializer<From, To, F> +where + From: SerializableComponent + 'static, + To: hecs::Component + Clone + 'static, + F: Fn(&From) -> To + Send + Sync + 'static, +{ + fn serializable_type_id(&self) -> TypeId { + TypeId::of::<From>() + } + + fn serializable_type_name(&self) -> &'static str { + std::any::type_name::<From>() + } + + fn insert_into_builder(&self, component: &dyn SerializableComponent, builder: &mut EntityBuilder) -> Result<()> { + let typed = component + .as_any() + .downcast_ref::<From>() + .ok_or_else(|| anyhow!( + "Component '{}' cannot be deserialized by '{}'", + component.type_name(), + std::any::type_name::<From>() + ))?; + let rebuild = (self.converter_fn)(typed); + builder.add(rebuild); + Ok(()) + } } @@ -3,12 +3,12 @@ use serde::{Deserialize, Serialize}; use dropbear_engine::entity::{EntityTransform, Transform}; use crate::states::Label; -/// A component that is added to all entities to show all child entities +/// A component that tracks all child entities of a parent entity #[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct Parent(Vec<hecs::Entity>); +pub struct Children(Vec<hecs::Entity>); -impl Parent { - /// Creates a new parent component with the provided child entities. +impl Children { + /// Creates a new children component with the provided child entities. pub fn new(children: Vec<hecs::Entity>) -> Self { Self(children) } @@ -23,12 +23,19 @@ impl Parent { &mut self.0 } - /// Adds a new child entity to this parent component. + /// Adds a new child entity to this component. pub fn push(&mut self, child: hecs::Entity) { - self.0.push(child); + if !self.0.contains(&child) { + self.0.push(child); + } + } + + /// Removes a specific child entity. + pub fn remove(&mut self, child: hecs::Entity) { + self.0.retain(|&e| e != child); } - /// Removes all children from this parent component. + /// Removes all children from this component. pub fn clear(&mut self) { self.0.clear(); } @@ -41,20 +48,109 @@ impl Parent { /// A component that points to the parent entity of an entity. #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, PartialEq, Eq)] -pub struct Child(hecs::Entity); +pub struct Parent(hecs::Entity); -impl Child { - /// Creates a new child component with the provided parent entity. +impl Parent { + /// Creates a new parent component with the provided parent entity. pub fn new(parent: hecs::Entity) -> Self { Self(parent) } - /// Returns the parent entity of this child component. + /// Returns the parent entity of this component. pub fn parent(&self) -> hecs::Entity { self.0 } } +/// Helper functions for managing entity hierarchies +pub struct Hierarchy; + +impl Hierarchy { + /// Set the parent of a child entity, updating both Parent and Children components + pub fn set_parent(world: &mut hecs::World, child: hecs::Entity, parent: hecs::Entity) { + // Remove old parent relationship if it exists + if let Ok(old_parent) = world.get::<&Parent>(child) { + let old_parent_entity = old_parent.parent(); + if let Ok(mut children) = world.get::<&mut Children>(old_parent_entity) { + children.remove(child); + } + } + + // Add Parent component to child + let _ = world.insert_one(child, Parent::new(parent)); + + // Add child to parent's Children component + if let Ok(mut children) = world.get::<&mut Children>(parent) { + children.push(child); + } else { + let _ = world.insert_one(parent, Children::new(vec![child])); + } + } + + /// Remove parent relationship from a child entity + pub fn remove_parent(world: &mut hecs::World, child: hecs::Entity) { + let mut local_remove_signal = false; + if let Ok(parent) = world.get::<&Parent>(child) { + let parent_entity = parent.parent(); + + local_remove_signal = true; + + if let Ok(mut children) = world.get::<&mut Children>(parent_entity) { + children.remove(child); + } + } + + if local_remove_signal { + let _ = world.remove_one::<Parent>(child); + } + } + + /// Get all children of an entity + pub fn get_children(world: &hecs::World, entity: hecs::Entity) -> Vec<hecs::Entity> { + world.get::<&Children>(entity) + .map(|c| c.children().to_vec()) + .unwrap_or_default() + } + + /// Get the parent of an entity + pub fn get_parent(world: &hecs::World, entity: hecs::Entity) -> Option<hecs::Entity> { + world.get::<&Parent>(entity) + .ok() + .map(|p| p.parent()) + } + + /// Get all ancestors of an entity (parent, grandparent, etc.) + pub fn get_ancestors(world: &hecs::World, entity: hecs::Entity) -> Vec<hecs::Entity> { + let mut ancestors = Vec::new(); + let mut current = entity; + + while let Some(parent) = Self::get_parent(world, current) { + ancestors.push(parent); + current = parent; + } + + ancestors + } + + /// Check if an entity is a descendant of another + pub fn is_descendant_of( + world: &hecs::World, + entity: hecs::Entity, + potential_ancestor: hecs::Entity, + ) -> bool { + let mut current = entity; + + while let Some(parent) = Self::get_parent(world, current) { + if parent == potential_ancestor { + return true; + } + current = parent; + } + + false + } +} + /// An extension trait for [EntityTransform] that allows for propagation of entities into a target transform. pub trait EntityTransformExt { /// Walks up the [`hecs::World`] and calculates the final [Transform] for the entity based off its parents. @@ -66,8 +162,8 @@ impl EntityTransformExt for EntityTransform { let mut result = self.local().clone(); let mut current = target_entity; - while let Ok(child) = world.get::<&Child>(current) { - let parent_entity = child.parent(); + while let Ok(parent_comp) = world.get::<&Parent>(current) { + let parent_entity = parent_comp.parent(); if let Ok(parent_transform) = world.get::<&EntityTransform>(parent_entity) { let parent_world = parent_transform.world(); @@ -86,6 +182,7 @@ impl EntityTransformExt for EntityTransform { } } +/// A serializable scene hierarchy based on entity labels #[derive(Default, Serialize, Deserialize, Clone, Debug)] pub struct SceneHierarchy { /// Maps entity labels to their parent label @@ -102,6 +199,31 @@ impl SceneHierarchy { } } + /// Build hierarchy from world entities + pub fn from_world(world: &hecs::World) -> Self { + let mut hierarchy = Self::new(); + + for (_entity, (label, parent)) in world.query::<(&Label, &Parent)>().iter() { + if let Ok(parent_label) = world.get::<&Label>(parent.parent()) { + hierarchy.set_parent(label.clone(), Label::new(parent_label.as_str())); + } + } + + hierarchy + } + + /// Apply this hierarchy to a world + pub fn apply_to_world(&self, world: &mut hecs::World, label_to_entity: &HashMap<Label, hecs::Entity>) { + for (child_label, parent_label) in &self.parent_map { + if let (Some(&child_entity), Some(&parent_entity)) = ( + label_to_entity.get(child_label), + label_to_entity.get(parent_label), + ) { + Hierarchy::set_parent(world, child_entity, parent_entity); + } + } + } + /// Set the parent of an entity pub fn set_parent(&mut self, child: Label, parent: Label) { if let Some(old_parent) = self.parent_map.get(&child) { @@ -14,8 +14,10 @@ use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterato use dropbear_engine::asset::ASSET_REGISTRY; use dropbear_engine::model::Model; use dropbear_engine::utils::ResourceReferenceType; +use dropbear_traits::component_registry::ComponentRegistry; +use dropbear_traits::SerializableComponent; use crate::camera::{CameraComponent, CameraType}; -use crate::hierarchy::{Parent, SceneHierarchy}; +use crate::hierarchy::{Children, Parent, SceneHierarchy}; use crate::states::{CameraConfig, Label, LightConfig, ModelProperties, ScriptComponent, SerializedMeshRenderer, WorldLoadingStatus, PROJECT}; use crate::utils::ResolveReference; @@ -23,13 +25,36 @@ use crate::utils::ResolveReference; pub struct SceneEntity { #[serde(default)] pub label: Label, + #[serde(default)] - pub components: Vec<Box<dyn dropbear_traits::SerializableComponent>>, + pub components: Vec<Box<dyn SerializableComponent>>, #[serde(skip)] pub entity_id: Option<hecs::Entity>, } +impl SceneEntity { + pub fn from_world( + world: &hecs::World, + entity: hecs::Entity, + registry: &ComponentRegistry, + ) -> Option<Self> { + let label= if let Ok(mut q) = world.query_one::<&Label>(entity) && let Some(label) = q.get() { + label.clone() + } else { + return None; + }; + + let components = registry.extract_all_components(world, entity); + + Some(Self { + label, + components, + entity_id: Some(entity), + }) + } +} + #[derive(Default, Debug, Serialize, Deserialize, Clone)] pub struct SceneSettings { /* *crickets* */ } @@ -74,6 +99,7 @@ impl SceneConfig { component: Box<dyn dropbear_traits::SerializableComponent>, builder: &mut hecs::EntityBuilder, graphics: Arc<SharedGraphicsContext>, + registry: Option<&ComponentRegistry>, label: &str, ) -> anyhow::Result<()> { if let Some(transform) = component.as_any().downcast_ref::<EntityTransform>() { @@ -181,6 +207,14 @@ impl SceneConfig { builder.add(script.clone()); } else if component.as_any().downcast_ref::<Parent>().is_some() { log::debug!("Skipping Parent component for '{}' - will be rebuilt from hierarchy_map", label); + } else if let Some(registry) = registry { + if !registry.deserialize_into_builder(component.as_ref(), builder)? { + log::warn!( + "Unknown component type '{}' for entity '{}' - skipping", + component.type_name(), + label + ); + } } else { log::warn!( "Unknown component type '{}' for entity '{}' - skipping", @@ -219,6 +253,7 @@ impl SceneConfig { &self, world: &mut hecs::World, graphics: Arc<SharedGraphicsContext>, + registry: Option<&ComponentRegistry>, progress_sender: Option<UnboundedSender<WorldLoadingStatus>>, ) -> anyhow::Result<hecs::Entity> { if let Some(ref s) = progress_sender { @@ -285,7 +320,14 @@ impl SceneConfig { has_entity_transform = true; } - Self::load_component(component, &mut builder, graphics.clone(), &label_for_logs).await?; + Self::load_component( + component, + &mut builder, + graphics.clone(), + registry, + &label_for_logs, + ) + .await?; } let entity = world.spawn(builder.build()); @@ -356,11 +398,11 @@ impl SceneConfig { let mut local_insert_one: Option<hecs::Entity> = None; - match world.query_one::<&mut Parent>(parent_entity) { + match world.query_one::<&mut Children>(parent_entity) { Ok(mut parent_query) => { - if let Some(parent_component) = parent_query.get() { - parent_component.clear(); - parent_component + if let Some(child_component) = parent_query.get() { + child_component.clear(); + child_component .children_mut() .extend(resolved_children.iter().copied()); } else { @@ -378,7 +420,7 @@ impl SceneConfig { } if let Some(parent_entity) = local_insert_one - && let Err(e) = world.insert_one(parent_entity, Parent::new(resolved_children)) + && let Err(e) = world.insert_one(parent_entity, Children::new(resolved_children)) { log::error!( "Failed to attach Parent component to entity {:?}: {}", @@ -401,6 +443,34 @@ impl SceneConfig { if !has_light { log::info!("No lights in scene, spawning default light"); + + let legacy_lights: Vec<hecs::Entity> = world + .query::<&Label>() + .iter() + .filter_map(|(entity, label)| { + if label.as_str() == "Default Light" { + Some(entity) + } else { + None + } + }) + .collect(); + + for entity in legacy_lights { + if let Err(err) = world.despawn(entity) { + log::warn!( + "Failed to remove legacy 'Default Light' entity {:?}: {}", + entity, + err + ); + } else { + log::debug!( + "Removed legacy 'Default Light' placeholder entity {:?}", + entity + ); + } + } + if let Some(ref s) = progress_sender { let _ = s.send(WorldLoadingStatus::LoadingEntity { index: 0, @@ -458,6 +528,34 @@ impl SceneConfig { Ok(camera_entity) } else { log::info!("No debug camera found, creating viewport camera for editor"); + + let legacy_cameras: Vec<hecs::Entity> = world + .query::<&Label>() + .iter() + .filter_map(|(entity, label)| { + if label.as_str() == "Viewport Camera" { + Some(entity) + } else { + None + } + }) + .collect(); + + for entity in legacy_cameras { + if let Err(err) = world.despawn(entity) { + log::warn!( + "Failed to remove legacy 'Viewport Camera' entity {:?}: {}", + entity, + err + ); + } else { + log::debug!( + "Removed legacy 'Viewport Camera' placeholder entity {:?}", + entity + ); + } + } + if let Some(ref s) = progress_sender { let _ = s.send(WorldLoadingStatus::LoadingEntity { index: 0, @@ -467,7 +565,8 @@ impl SceneConfig { } let camera = Camera::predetermined(graphics.clone(), Some("Viewport Camera")); let component = crate::camera::DebugCamera::new(); - let camera_entity = { world.spawn((camera, component)) }; + let label = Label::new("Viewport Camera"); + let camera_entity = { world.spawn((label, camera, component)) }; Ok(camera_entity) } } @@ -3,6 +3,7 @@ use crate::scripting::ScriptTarget; /// A trait implemented by the different script types ([ScriptTarget::JVM], [ScriptTarget::Native]) which allow /// for populating the last error and getting the contents of the last error. +#[allow(dead_code)] pub trait LastErrorMessage { /// Fetches the last error message. /// @@ -26,7 +26,9 @@ pub struct NativeLibrary { destroy_tagged_fn: Symbol<'static, DestroyTagged>, // err msg + #[allow(dead_code)] pub(crate) get_last_err_msg_fn: Symbol<'static, sig::GetLastErrorMessage>, + #[allow(dead_code)] pub(crate) set_last_err_msg_fn: Symbol<'static, sig::SetLastErrorMessage>, } @@ -18,6 +18,7 @@ use dropbear_engine::{ }; use egui::{self, CollapsingHeader, Margin, RichText}; use egui_dock::TabViewer; +use egui_ltreeview::{NodeBuilder, TreeViewBuilder}; use indexmap::Equivalent; use eucalyptus_core::states::{Label}; use log; @@ -37,7 +38,9 @@ pub struct EditorTabViewer<'a> { pub editor_mode: &'a mut EditorState, pub active_camera: &'a mut Arc<Mutex<Option<Entity>>>, pub plugin_registry: &'a mut PluginRegistry, + pub component_registry: &'a ComponentRegistry, pub build_logs: &'a mut Vec<String>, + // "wah wah its unsafe, its using raw pointers" shut the fuck up if it breaks i will know pub editor: *mut Editor, } @@ -253,12 +256,99 @@ impl<'a> TabViewer for EditorTabViewer<'a> { } } EditorTab::ModelEntityList => { - // todo: use egui_ltreeview to create a component graph - // egui_ltreeview::TreeView::new(egui::Id::new("model_entity_list")).show(ui, |builder| { - // builder.dir() - // // entity is entity.id() - // // component is entity.id() * -100 * - // }); + let (_response, action) = egui_ltreeview::TreeView::new(egui::Id::new("model_entity_list")) + .show(ui, |builder| { + let current_scene_name = {PROJECT.read().last_opened_scene.clone().unwrap_or("Scene".to_string())}; + builder.node( + NodeBuilder::dir(u64::MAX) + .label(format!("Scene: {}", current_scene_name)) + .context_menu(|ui| { + if ui.button("New Empty Entity").clicked() { + self.world.spawn((Label::new("Blank Entity"), )); + } + }) + ); + // the root scene must be the biggest number possible to remove any ambiguity + + fn add_entity_to_tree( + builder: &mut TreeViewBuilder<u64>, + entity: Entity, + world: &World, + registry: &ComponentRegistry, + ) -> anyhow::Result<()> { + let entity_id = entity.id() as u64; + let label = if let Ok(mut q) = world.query_one::<&Label>(entity) && let Some(label) = q.get() { + label.clone() + } else { + anyhow::bail!("This entity [{}] is expected to contain Label", entity_id); + }; + + builder.node( + NodeBuilder::dir(entity_id) + .label(label.as_str()) + .context_menu(|ui| { + ui.menu_button("New", |ui| { + if ui.button("Child").clicked() { + // todo + } + }); + }) + ); + + let components = registry.extract_all_components(world, entity); + + for (i, component) in components.iter().enumerate() { + // todo: make this a uuid instead + let component_id = entity_id.wrapping_mul(1_000_000).wrapping_add(i as u64); + + let component_name = component.type_name(); + + builder.node( + NodeBuilder::leaf(component_id) + .label(component_name) + .context_menu(|ui| { + if ui.button("Remove Component").clicked() { + // complete this + } + }) + ); + } + + if let Ok(children) = world.get::<&Children>(entity) { + for &child in children.children() { + if let Err(e) = add_entity_to_tree(builder, child, world, registry) { + log_once::error_once!("Failed to add child entity to tree, skipping: {}", e); + continue; + } + } + } + + builder.close_dir(); + Ok(()) + } + + for (entity, ()) in self.world.query::<()>() + .without::<&Parent>() + .iter() + { + if let Err(e) = add_entity_to_tree(builder, entity, &self.world, &self.component_registry) { + log_once::error_once!("Failed to add child entity to tree, skipping: {}", e); + } + } + + builder.close_dir(); + }); + + for i in action { + match i { + egui_ltreeview::Action::SetSelected(_items) => {}, + egui_ltreeview::Action::Move(_drag_and_drop) => {}, + egui_ltreeview::Action::Drag(_drag_and_drop) => {}, + egui_ltreeview::Action::Activate(_activate) => {}, + egui_ltreeview::Action::DragExternal(_drag_and_drop_external) => {}, + egui_ltreeview::Action::MoveExternal(_drag_and_drop_external) => {}, + } + } } EditorTab::AssetViewer => { // // todo: use egui_ltreeview to create a asset viewer @@ -592,83 +682,6 @@ impl<'a> TabViewer for EditorTabViewer<'a> { } } -pub(crate) fn import_object() -> anyhow::Result<()> { - let model_ext = vec!["glb", "fbx", "obj"]; - let texture_ext = vec!["png"]; - - let files = rfd::FileDialog::new() - .add_filter("All Files", &["*"]) - .add_filter("Model", &model_ext) - .add_filter("Texture", &texture_ext) - .pick_files(); - if let Some(files) = files { - for file in files { - let ext = file.extension().unwrap().to_str().unwrap(); - let mut copied = false; - for mde in model_ext.iter() { - if ext.contains(mde) { - // copy over to models folder - { - let project = PROJECT.read(); - let models_dir = project - .project_path - .clone() - .join("resources") - .join("models"); - if !models_dir.exists() { - std::fs::create_dir_all(&models_dir)?; - } - let dest = models_dir.join(file.file_name().unwrap()); - std::fs::copy(&file, &dest)?; - log::info!("Copied model file to {:?}", dest); - copied = true; - } - } - } - for tex in texture_ext.iter() { - if ext.contains(tex) { - // copy over to textures folder - { - let project = PROJECT.read(); - let textures_dir = project - .project_path - .clone() - .join("resources") - .join("textures"); - if !textures_dir.exists() { - std::fs::create_dir_all(&textures_dir)?; - } - let dest = textures_dir.join(file.file_name().unwrap()); - std::fs::copy(&file, &dest)?; - log::info!("Copied texture file to {:?}", dest); - copied = true; - } - } - } - - if !copied { - { - let project = PROJECT.read(); - // everything else copies over to resources root dir - let resources_dir = project.project_path.clone().join("resources"); - if !resources_dir.exists() { - std::fs::create_dir_all(&resources_dir)?; - } - let dest = resources_dir.join(file.file_name().unwrap()); - std::fs::copy(&file, &dest)?; - log::info!("Copied other resource file to {:?}", dest); - } - } - } - // save it all to ensure the eucc recognises it - let mut proj = PROJECT.write(); - proj.write_to_all()?; - Ok(()) - } else { - Err(anyhow::anyhow!("File dialogue returned None")) - } -} - #[derive(Debug, Clone, Copy)] #[allow(dead_code)] // todo: provide a purpose to RemoveComponent @@ -19,14 +19,14 @@ use dropbear_engine::{ entity::{MeshRenderer, Transform}, future::FutureHandle, graphics::{RenderContext, SharedGraphicsContext}, - lighting::{Light, LightManager}, + lighting::{LightManager}, model::{ModelId, MODEL_CACHE}, scene::SceneCommand, }; use egui::{self, Context}; use egui_dock::{DockArea, DockState, NodeIndex, Style}; use eucalyptus_core::APP_INFO; -use eucalyptus_core::hierarchy::{Parent, SceneHierarchy}; +use eucalyptus_core::hierarchy::{Children, Parent, SceneHierarchy}; use eucalyptus_core::states::{Label, SerializedMeshRenderer}; use eucalyptus_core::{ camera::{CameraComponent, CameraType, DebugCamera}, @@ -62,8 +62,8 @@ use wgpu::{Color, Extent3d, RenderPipeline}; use winit::window::CursorGrabMode; use winit::{keyboard::KeyCode, window::Window}; use dropbear_engine::entity::EntityTransform; -use dropbear_engine::lighting::LightComponent; use eucalyptus_core::scene::{SceneConfig, SceneEntity}; +use eucalyptus_core::traits::component_registry::ComponentRegistry; use eucalyptus_core::traits::SerializableComponent; pub struct Editor { @@ -144,6 +144,9 @@ pub struct Editor { // about show_about: bool, nerd_stats: NerdStats, + + // component registry + component_registry: Arc<ComponentRegistry>, } impl Editor { @@ -182,7 +185,47 @@ impl Editor { } }); - let plugin_registry = PluginRegistry::new(); + let mut plugin_registry = PluginRegistry::new(); + let mut component_registry = ComponentRegistry::new(); + + fn register_components(plugin_registry: &mut PluginRegistry, component_registry: &mut ComponentRegistry) { + component_registry.register::<EntityTransform>(); + component_registry.register::<ModelProperties>(); + component_registry.register::<CameraConfig>(); + component_registry.register::<LightConfig>(); + component_registry.register::<ScriptComponent>(); + + component_registry.register_converter::<MeshRenderer, SerializedMeshRenderer, _>( + |renderer| { + SerializedMeshRenderer { + handle: renderer.handle().path.clone(), + material_override: renderer.material_overrides().to_vec(), + } + } + ); + + // register plugin defined structs + if let Err(e) = plugin_registry.load_plugins() { + fatal!("Failed to load plugins: {}", e); + return; + } + + for p in plugin_registry.list_plugins() { + log::info!("Plugin {} has been loaded", p.display_name); + } + + log::info!("Total plugins added: {}", plugin_registry.plugins.len()); + + for plugin in plugin_registry.list_plugins() { + if let Some(p) = plugin_registry.get_mut(&plugin.display_name) { + p.register_component(component_registry); + log::info!("Components for plugin [{}] has been registered to component registry", plugin.display_name); + } + } + } + + register_components(&mut plugin_registry, &mut component_registry); + let component_registry = Arc::new(component_registry); Ok(Self { scene_command: SceneCommand::None, @@ -235,6 +278,7 @@ impl Editor { pending_scene_creation: None, show_about: false, nerd_stats: NerdStats::default(), + component_registry, }) } @@ -272,66 +316,24 @@ impl Editor { .iter_mut() .find(|scene| scene.scene_name == target_scene_name) .ok_or_else(|| anyhow::anyhow!("Active scene '{}' is not loaded", target_scene_name))?; - + scene.entities.clear(); scene.hierarchy_map = SceneHierarchy::new(); + log::debug!("Reset internal hierarchy map for scene {}", scene.scene_name); - let labels = self.world.query::<&Label>().iter().map(|(e, l)| (e, l.clone())).collect::<Vec<_>>(); + let labels = self.world.query::<&Label>().iter() + .map(|(e, l)| (e, l.clone())) + .collect::<Vec<_>>(); for (id, label) in labels { let entity_label = label.clone(); - let mut components: Vec<Box<dyn SerializableComponent>> = Vec::new(); - if let Ok(transform) = self.world.get::<&EntityTransform>(id) { - components.push(Box::new(*transform)); - } + let components = self.component_registry.extract_all_components(&self.world, id); - if let Ok(renderer) = self.world.get::<&MeshRenderer>(id) { - let serialized = SerializedMeshRenderer { - handle: renderer.handle().path.clone(), - material_override: renderer.material_overrides().to_vec(), - }; - components.push(Box::new(serialized)); - } - - if let Ok(mut q) = self.world.query_one::<&ModelProperties>(id) && let Some(properties) = q.get() { - components.push(Box::new(properties.clone())); - } - - if let Ok(mut camera_query) = self.world.query_one::<(&Camera, &CameraComponent)>(id) - && let Some((camera, component)) = camera_query.get() - { - let camera_config = CameraConfig::from_ecs_camera(camera, component); - components.push(Box::new(camera_config)); - } - - if let Ok(mut light_query) = self.world.query_one::<(&Light, &LightComponent, &EntityTransform)>(id) { - if let Some((light, light_component, entity_transform)) = light_query.get() { - let light_config = LightConfig { - label: light.cube_model.label.to_string(), - transform: entity_transform.sync(), - light_component: light_component.clone(), - enabled: light_component.enabled, - entity_id: Some(id), - }; - components.push(Box::new(light_config)); - } - } - - if let Ok(mut q) = self.world.query_one::<&ScriptComponent>(id) && let Some(script) = q.get() { - components.push(Box::new(script.clone())); - } - - let mut parent_component = None; - - if let Ok(comp) = self.world.query_one_mut::<&Parent>(id) { - parent_component = Some(comp.clone()); - } - - if let Some(parent_component) = parent_component { - for &child_entity in parent_component.children() { - if let Ok(child_label) = self.world.query_one_mut::<&Label>(child_entity) { - scene.hierarchy_map.set_parent(child_label.clone(), entity_label.clone()); + if let Ok(children_comp) = self.world.get::<&Children>(id) { + for &child_entity in children_comp.children() { + if let Ok(child_label) = self.world.get::<&Label>(child_entity) { + scene.hierarchy_map.set_parent(Label::new(child_label.as_str()), entity_label.clone()); } else { log::warn!( "Unable to resolve child entity {:?} for parent '{}' when saving scene", @@ -480,7 +482,6 @@ impl Editor { /// It uses an unbounded sender to send messages back to the receiver so it can /// be used within threads. pub async fn load_project_config( - // &mut self, graphics: Arc<SharedGraphicsContext>, sender: Option<UnboundedSender<WorldLoadingStatus>>, world: &mut World, @@ -488,6 +489,7 @@ impl Editor { active_camera: Arc<Mutex<Option<hecs::Entity>>>, project_path: Arc<Mutex<Option<PathBuf>>>, dock_state: Arc<Mutex<DockState<EditorTab>>>, + component_registry: Arc<ComponentRegistry>, ) -> anyhow::Result<()> { { let config = PROJECT.read(); @@ -521,7 +523,12 @@ impl Editor { { if let Some(first_scene) = first_scene_opt { let cam = first_scene - .load_into_world(world, graphics, sender.clone()) + .load_into_world( + world, + graphics, + Some(component_registry.as_ref()), + sender.clone(), + ) .await?; let mut a_c = active_camera.lock(); *a_c = Some(cam); @@ -661,6 +668,7 @@ impl Editor { let graphics_shared = graphics.shared.clone(); let active_camera = self.active_camera.clone(); let scene_name = scene.scene_name.clone(); + let component_registry_clone = self.component_registry.clone(); let handle = graphics.shared.future_queue.push(async move { let mut temp_world = World::new(); @@ -669,6 +677,7 @@ impl Editor { .load_into_world( &mut temp_world, graphics_shared.clone(), + Some(component_registry_clone.as_ref()), Some(progress_sender.clone()), ) .await; @@ -1031,6 +1040,7 @@ impl Editor { plugin_registry: &mut self.plugin_registry, editor: editor_ptr, build_logs: &mut self.build_logs, + component_registry: &self.component_registry }, ); }); @@ -48,6 +48,8 @@ impl Scene for Editor { let dock_state_shared = Arc::new(Mutex::new(self.dock_state.clone())); let dock_state_for_loading = dock_state_shared.clone(); + let component_registry = self.component_registry.clone(); + let handle = graphics.shared.future_queue.push(async move { let mut temp_world = World::new(); if let Err(e) = Self::load_project_config( @@ -58,6 +60,7 @@ impl Scene for Editor { active_camera_clone, project_path_clone, dock_state_for_loading, + component_registry ) .await { @@ -163,14 +166,6 @@ impl Scene for Editor { } } - if !self.plugin_registry.plugins_loaded { - if let Err(e) = self.plugin_registry.load_plugins() { - fatal!("Failed to load plugins: {}", e); - } else { - log::info!("Plugins loaded"); - } - } - let cache_mutex_ptr = std::sync::LazyLock::force(&MODEL_CACHE) as *const _; ASSET_REGISTRY.add_pointer(PointerKind::Const("model_cache"), cache_mutex_ptr as usize); @@ -93,6 +93,7 @@ async fn main() -> anyhow::Result<()> { LevelFilter::Debug, ) .filter(Some("eucalyptus_core"), LevelFilter::Debug) + .filter(Some("dropbear_traits"), LevelFilter::Debug) .init(); log::info!("Initialised logger"); } @@ -3,6 +3,7 @@ use app_dirs2::AppDataType; use egui::Ui; use eucalyptus_core::APP_INFO; use eucalyptus_core::states::PluginInfo; +use eucalyptus_core::traits::component_registry::ComponentRegistry; use indexmap::IndexMap; use libloading as lib; use std::fs::ReadDir; @@ -14,8 +15,10 @@ pub trait EditorPlugin: Send + Sync { fn display_name(&self) -> &str; fn ui(&mut self, ui: &mut Ui, editor: &mut Editor); fn tab_title(&self) -> &str; - fn context_menu(&mut self, _ui: &mut Ui, _editor: &mut Editor) { /* No context */ - } + + /// Allows you to register the struct [Self] as a component to the component registry. + /// This will then be listed as an option for a potential component a user could add. + fn register_component(&mut self, registry: &mut ComponentRegistry); } pub type PluginConstructor = fn() -> Box<dyn EditorPlugin>;