kitgit

tirbofish/dropbear · commit

2a0f08a88f896659e57a758b7dd2decf9d4ed541

feature: automatic component inspection

Unverified

Thribhu K <4tkbytes@pm.me> · 2026-02-17 11:43

view full diff

diff --git a/crates/dropbear-engine/src/pipelines/light_cube.rs b/crates/dropbear-engine/src/pipelines/light_cube.rs
index 53773b3..f772ebe 100644
--- a/crates/dropbear-engine/src/pipelines/light_cube.rs
+++ b/crates/dropbear-engine/src/pipelines/light_cube.rs
@@ -3,7 +3,7 @@ use std::mem::size_of;
 use glam::DMat4;
 use slank::include_slang;
 use wgpu::{BufferAddress, CompareFunction, DepthBiasState, StencilState, VertexAttribute, VertexFormat};
-use crate::buffer::{StorageBuffer, UniformBuffer};
+use crate::buffer::{StorageBuffer};
 use crate::entity::{EntityTransform, Transform};
 use crate::graphics::SharedGraphicsContext;
 use crate::lighting::{Light, LightArrayUniform, LightComponent, MAX_LIGHTS};
@@ -87,18 +87,12 @@ impl DropbearShaderPipeline for LightCubePipeline {
             cache: None,
         });
 
-        let mut storage_buffer = None;
-
-        storage_buffer = Some(StorageBuffer::new(
+        let storage_buffer = StorageBuffer::new(
             &graphics.device,
             "light cube pipeline storage buffer",
-        ));
+        );
 
-        let light_buffer: &wgpu::Buffer = if let Some(buf) = &storage_buffer {
-            buf.buffer()
-        } else {
-            panic!("A storage buffer should have been created");
-        };
+        let light_buffer: &wgpu::Buffer = storage_buffer.buffer();
 
         let light_bind_group = graphics.device.create_bind_group(&wgpu::BindGroupDescriptor {
             layout: &graphics.layouts.light_array_bind_group_layout,
@@ -113,7 +107,7 @@ impl DropbearShaderPipeline for LightCubePipeline {
             shader,
             pipeline_layout,
             pipeline,
-            storage_buffer,
+            storage_buffer: Some(storage_buffer),
             light_bind_group,
         }
     }
diff --git a/crates/dropbear-engine/src/pipelines/shader.rs b/crates/dropbear-engine/src/pipelines/shader.rs
index c1fd9cb..b397c16 100644
--- a/crates/dropbear-engine/src/pipelines/shader.rs
+++ b/crates/dropbear-engine/src/pipelines/shader.rs
@@ -1,6 +1,5 @@
 use std::sync::Arc;
 use wgpu::{CompareFunction, DepthBiasState, StencilState};
-use crate::buffer::{StorageBuffer, UniformBuffer};
 use crate::graphics::{InstanceRaw, SharedGraphicsContext};
 use crate::model;
 use crate::model::Vertex;
diff --git a/crates/dropbear-macro/src/lib.rs b/crates/dropbear-macro/src/lib.rs
index f840604..89f2379 100644
--- a/crates/dropbear-macro/src/lib.rs
+++ b/crates/dropbear-macro/src/lib.rs
@@ -4,7 +4,7 @@ use syn::{
     parse::{Parse, ParseStream},
     parse_macro_input, parse_quote,
     spanned::Spanned,
-    DeriveInput, FnArg, GenericArgument, Ident, Item, ItemFn, ItemMod, ItemEnum, LitStr, PathArguments,
+    FnArg, GenericArgument, Ident, ItemFn, ItemEnum, LitStr, PathArguments,
     ReturnType, Token, Type,
 };
 use std::path::Path;
@@ -1235,98 +1235,6 @@ fn is_double_type(ty: &Type) -> bool {
     matches!(ty, Type::Path(path) if path.path.segments.last().map(|s| s.ident == "f64").unwrap_or(false))
 }
 
-fn transform_function(mut func: ItemFn) -> ItemFn {
-    let inputs = func.sig.inputs.clone();
-    let output = func.sig.output.clone();
-    let block = func.block;
-
-    let inner_type = extract_inner_type(&output);
-    let is_void = is_unit_type(&inner_type);
-
-    let mut new_inputs = inputs.clone();
-
-    if !is_void {
-        let out_ptr_type: Type = parse_quote! { *mut #inner_type };
-
-        new_inputs.push(FnArg::Typed(syn::PatType {
-            attrs: vec![],
-            pat: Box::new(parse_quote! { out_result }),
-            colon_token: Default::default(),
-            ty: Box::new(out_ptr_type),
-        }));
-    }
-    
-    let pointer_check = if !is_void {
-        quote! {
-            if out_result.is_null() {
-                return crate::scripting::native::DropbearNativeError::NullPointer.code();
-            }
-        }
-    } else {
-        quote! {}
-    };
-
-    let success_handling = if !is_void {
-        quote! {
-            unsafe { *out_result = val; }
-            crate::scripting::native::DropbearNativeError::Success.code()
-        }
-    } else {
-        quote! {
-            crate::scripting::native::DropbearNativeError::Success.code()
-        }
-    };
-
-    let new_body = quote! {
-        {
-            #pointer_check
-            
-            let logic = || #output {
-                #block
-            };
-            
-            match logic() {
-                DropbearNativeResult::Ok(val) => {
-                    #success_handling
-                }
-                DropbearNativeResult::Err(e) => {
-                    e.code()
-                }
-            }
-        }
-    };
-
-    func.sig.inputs = new_inputs;
-    func.sig.output = parse_quote! { -> i32 };
-    func.sig.abi = Some(parse_quote! { extern "C" });
-    func.sig.unsafety = Some(parse_quote! { unsafe });
-
-    func.attrs.push(parse_quote! { #[unsafe(no_mangle)] });
-
-    func.block = Box::new(syn::parse2(new_body).expect("Failed to parse new body"));
-
-    func
-}
-
-/// Helper to dig into Result<T, E> and get T
-fn extract_inner_type(output: &ReturnType) -> Type {
-    match output {
-        ReturnType::Type(_, ty) => {
-            if let Type::Path(type_path) = &**ty {
-                if let Some(segment) = type_path.path.segments.last() {
-                    if let PathArguments::AngleBracketed(args) = &segment.arguments {
-                        if let Some(GenericArgument::Type(inner)) = args.args.first() {
-                            return inner.clone();
-                        }
-                    }
-                }
-            }
-            parse_quote! { () }
-        }
-        ReturnType::Default => parse_quote! { () },
-    }
-}
-
 /// Helper to check if type is ()
 fn is_unit_type(ty: &Type) -> bool {
     if let Type::Tuple(tuple) = ty {
diff --git a/crates/eucalyptus-core/src/animation/mod.rs b/crates/eucalyptus-core/src/animation/mod.rs
index 1868057..b1fec32 100644
--- a/crates/eucalyptus-core/src/animation/mod.rs
+++ b/crates/eucalyptus-core/src/animation/mod.rs
@@ -5,7 +5,7 @@ use dropbear_engine::animation::AnimationComponent;
 use dropbear_engine::asset::ASSET_REGISTRY;
 use dropbear_engine::entity::MeshRenderer;
 use dropbear_engine::graphics::SharedGraphicsContext;
-use crate::component::{Component, ComponentDescriptor, SerializedComponent};
+use crate::component::{Component, ComponentDescriptor, InspectableComponent, SerializedComponent};
 
 #[typetag::serde]
 impl SerializedComponent for AnimationComponent {}
@@ -60,7 +60,9 @@ impl Component for AnimationComponent {
     fn save(&self, _world: &World, _entity: Entity) -> Box<dyn SerializedComponent> {
         Box::new(self.clone())
     }
+}
 
+impl InspectableComponent for AnimationComponent {
     fn inspect(&mut self, ui: &mut Ui) {
         CollapsingHeader::new("Animation").default_open(true).show(ui, |ui| {
             ui.label("Active animation:");
diff --git a/crates/eucalyptus-core/src/camera.rs b/crates/eucalyptus-core/src/camera.rs
index 4bc055b..b568e3b 100644
--- a/crates/eucalyptus-core/src/camera.rs
+++ b/crates/eucalyptus-core/src/camera.rs
@@ -8,7 +8,7 @@ use std::sync::Arc;
 use egui::{CollapsingHeader, Ui};
 use hecs::{Entity, World};
 use dropbear_engine::graphics::SharedGraphicsContext;
-use crate::component::{Component, ComponentDescriptor, ComponentInitFuture, SerializedComponent};
+use crate::component::{Component, ComponentDescriptor, ComponentInitFuture, InspectableComponent, SerializedComponent};
 use crate::ptr::WorldPtr;
 use crate::scripting::result::DropbearNativeResult;
 use crate::types::NVector3;
@@ -70,7 +70,9 @@ impl Component for Camera {
             Box::new(SerializableCamera::default())
         }
     }
+}
 
+impl InspectableComponent for Camera {
     fn inspect(&mut self, ui: &mut Ui) {
         CollapsingHeader::new("Camera3D").show(ui, |ui| {
             ui.label("Not implemented yet!");
diff --git a/crates/eucalyptus-core/src/component.rs b/crates/eucalyptus-core/src/component.rs
index 6706a44..641ce18 100644
--- a/crates/eucalyptus-core/src/component.rs
+++ b/crates/eucalyptus-core/src/component.rs
@@ -41,6 +41,8 @@ pub struct ComponentRegistry {
     removers: HashMap<TypeId, RemoveFn>,
     /// Functions that find entities with a component.
     finders: HashMap<TypeId, FindFn>,
+    /// Allows for inspecting the component in the Resource Inspector dock. 
+    inspectors: HashMap<TypeId, InspectFn>,
 }
 
 /// Describes a handy little future for [`Component::init`], which deals with initialising a component from its serialized form. 
@@ -61,6 +63,9 @@ type UpdateFn = Box<dyn Fn(&mut hecs::World, f32, Arc<SharedGraphicsContext>) + 
 type DefaultFn = Box<dyn Fn() -> Box<dyn SerializedComponent> + Send + Sync>;
 type RemoveFn = Box<dyn Fn(&mut hecs::World, hecs::Entity) + Send + Sync>;
 type FindFn = Box<dyn Fn(&hecs::World) -> Vec<hecs::Entity> + Send + Sync>;
+type InspectFn = Box<dyn Fn(&mut hecs::World, hecs::Entity, &mut egui::Ui) + Send + Sync>;
+
+// fn inspect(&mut self, ui: &mut egui::Ui);
 
 impl ComponentRegistry {
     pub fn new() -> Self {
@@ -75,13 +80,14 @@ impl ComponentRegistry {
             defaults: HashMap::new(),
             removers: HashMap::new(),
             finders: HashMap::new(),
+            inspectors: HashMap::new(),
         }
     }
 
     /// Register a component type with the registry
     pub fn register<T>(&mut self)
     where
-        T: Component + Send + Sync + 'static,
+        T: Component + InspectableComponent + Send + Sync + 'static,
         T::SerializedForm: 'static + Default,
         T::RequiredComponentTypes: Send + Sync,
     {
@@ -142,6 +148,12 @@ impl ComponentRegistry {
                 component.update_component(world_ref, entity, dt, graphics.clone());
             }
         }));
+
+        self.inspectors.insert(type_id, Box::new(|world, entity, ui| {
+            if let Ok(mut comp) = world.get::<&mut T>(entity) {
+                comp.inspect(ui);
+            }
+        }));
     }
 
     /// Get descriptor for a specific component type
@@ -344,60 +356,10 @@ impl ComponentRegistry {
                 continue;
             };
 
-            match desc.fqtn.as_str() {
-                "dropbear_engine::entity::EntityTransform" => {
-                    if let Ok(mut comp) = world.get::<&mut EntityTransform>(entity) {
-                        comp.inspect(ui);
-                    }
-                }
-                "eucalyptus_core::properties::CustomProperties" => {
-                    if let Ok(mut comp) = world.get::<&mut crate::properties::CustomProperties>(entity) {
-                        comp.inspect(ui);
-                    }
-                }
-                "dropbear_engine::lighting::Light" => {
-                    if let Ok(mut comp) = world.get::<&mut dropbear_engine::lighting::Light>(entity) {
-                        comp.inspect(ui);
-                    }
-                }
-                "eucalyptus_core::states::Script" => {
-                    if let Ok(mut comp) = world.get::<&mut crate::states::Script>(entity) {
-                        comp.inspect(ui);
-                    }
-                }
-                "dropbear_engine::entity::MeshRenderer" => {
-                    if let Ok(mut comp) = world.get::<&mut MeshRenderer>(entity) {
-                        comp.inspect(ui);
-                    }
-                }
-                "dropbear_engine::camera::Camera" => {
-                    if let Ok(mut comp) = world.get::<&mut dropbear_engine::camera::Camera>(entity) {
-                        comp.inspect(ui);
-                    }
-                }
-                "eucalyptus_core::physics::rigidbody::RigidBody" => {
-                    if let Ok(mut comp) = world.get::<&mut crate::physics::rigidbody::RigidBody>(entity) {
-                        comp.inspect(ui);
-                    }
-                }
-                "eucalyptus_core::physics::collider::ColliderGroup" => {
-                    if let Ok(mut comp) = world.get::<&mut crate::physics::collider::ColliderGroup>(entity) {
-                        comp.inspect(ui);
-                    }
-                }
-                "eucalyptus_core::physics::kcc::KCC" => {
-                    if let Ok(mut comp) = world.get::<&mut crate::physics::kcc::KCC>(entity) {
-                        comp.inspect(ui);
-                    }
-                }
-                "dropbear_engine::animation::AnimationComponent" => {
-                    if let Ok(mut comp) = world.get::<&mut dropbear_engine::animation::AnimationComponent>(entity) {
-                        comp.inspect(ui);
-                    }
-                }
-                _ => {
-                    ui.label(format!("{} (no inspector)", desc.type_name));
-                }
+            if let Some(inspector) = self.inspectors.get(&type_id) {
+                inspector(world, entity, ui);
+            } else {
+                ui.label(format!("{} (no inspector)", desc.type_name));
             }
         }
     }
@@ -447,7 +409,7 @@ pub struct ComponentDescriptor {
 }
 
 /// Defines a type that can be considered a component of an entity.
-pub trait Component: Sized + Sync + Send {
+pub trait Component: Sync + Send {
     /// A custom format of the component for saving the state of the component to disk.
     ///
     /// To have your type available, you must include this blanket trait:
@@ -481,14 +443,16 @@ pub trait Component: Sized + Sync + Send {
     /// Called when saving the scene to disk. Returns the [`Self::SerializedForm`] of the component that can be
     /// saved to disk.
     fn save(&self, world: &hecs::World, entity: hecs::Entity) -> Box<dyn SerializedComponent>;
-
-    /// In the editor, how the component will be represented in the `Resource Viewer` dock.
-    fn inspect(&mut self, ui: &mut egui::Ui);
 }
 
 #[typetag::serde]
 impl SerializedComponent for SerializedMeshRenderer {}
 
+pub trait InspectableComponent: Send + Sync {
+    /// In the editor, how the component will be represented in the `Resource Viewer` dock.
+    fn inspect(&mut self, ui: &mut egui::Ui);
+}
+
 // sample for MeshRenderer
 impl Component for MeshRenderer {
     type SerializedForm = SerializedMeshRenderer;
@@ -738,8 +702,10 @@ impl Component for MeshRenderer {
             texture_override,
         })
     }
+}
 
-    fn inspect(&mut self, ui: &mut Ui) {
+impl InspectableComponent for MeshRenderer {
+    fn inspect(&mut self, ui: &mut egui::Ui) {
         CollapsingHeader::new("Mesh Renderer").show(ui, |ui| {
             ui.label("Not implemented yet (MeshRenderer)");
         });
diff --git a/crates/eucalyptus-core/src/config.rs b/crates/eucalyptus-core/src/config.rs
index 631d5c1..b4de8e8 100644
--- a/crates/eucalyptus-core/src/config.rs
+++ b/crates/eucalyptus-core/src/config.rs
@@ -251,7 +251,7 @@ impl ProjectConfig {
             let scene_entry = scene_entry?;
             let path = scene_entry.path();
 
-            if path.is_file() && path.extension().and_then(|s| s.to_str()) == Some("eucs") {
+            if path.is_file() && path.extension().and_then(|s| s.to_str()) == Some("eucs") && path.extension().and_then(|s| s.to_str()) != Some("bak") {
                 match SceneConfig::read_from(&path) {
                     Ok(scene) => {
                         log::debug!("Loaded scene config: {}", scene.scene_name);
@@ -262,13 +262,21 @@ impl ProjectConfig {
                             if io_err.kind() == std::io::ErrorKind::NotFound {
                                 log::warn!("Scene file {:?} not found", path);
                             } else {
-                                if let Some(scene) = deal_with_bad_scene(&path, &e, &project_root) {
-                                    scene_configs.push(scene);
+                                if let Some(first) = scene_configs.first() {
+                                    log::warn!("Unable to load scene {}: [{:?}], loading the first available scene [{}]", path.display(), &e, first.scene_name);
+                                } else {
+                                    if let Some(scene) = deal_with_bad_scene(&path, &e, &project_root) {
+                                        scene_configs.push(scene);
+                                    }
                                 }
                             }
                         } else {
-                            if let Some(scene) = deal_with_bad_scene(&path, &e, &project_root) {
-                                scene_configs.push(scene);
+                            if let Some(first) = scene_configs.first() {
+                                log::warn!("Unable to load scene {}: [{:?}], loading the first available scene [{}]", path.display(), &e, first.scene_name);
+                            } else {
+                                if let Some(scene) = deal_with_bad_scene(&path, &e, &project_root) {
+                                    scene_configs.push(scene);
+                                }
                             }
                         }
                     }
diff --git a/crates/eucalyptus-core/src/lighting.rs b/crates/eucalyptus-core/src/lighting.rs
index fe8738c..43dcf98 100644
--- a/crates/eucalyptus-core/src/lighting.rs
+++ b/crates/eucalyptus-core/src/lighting.rs
@@ -12,7 +12,7 @@ use hecs::{Entity, World};
 use jni::objects::{JObject, JValue};
 use jni::JNIEnv;
 use dropbear_engine::graphics::SharedGraphicsContext;
-use crate::component::{Component, ComponentDescriptor, ComponentInitFuture, SerializedComponent};
+use crate::component::{Component, ComponentDescriptor, ComponentInitFuture, InspectableComponent, SerializedComponent};
 use crate::states::SerializedLight;
 
 #[typetag::serde]
@@ -73,7 +73,9 @@ impl Component for Light {
             Box::new(SerializedLight::default())
         }
     }
+}
 
+impl InspectableComponent for Light {
     fn inspect(&mut self, ui: &mut Ui) {
         CollapsingHeader::new("Light").default_open(true).show(ui, |ui| {
             ui.label("Not implemented yet"); 
diff --git a/crates/eucalyptus-core/src/physics/collider.rs b/crates/eucalyptus-core/src/physics/collider.rs
index 0d46a40..a1e5488 100644
--- a/crates/eucalyptus-core/src/physics/collider.rs
+++ b/crates/eucalyptus-core/src/physics/collider.rs
@@ -36,7 +36,7 @@ use glam::DQuat;
 use hecs::{Entity, World};
 use rapier3d::prelude::{Rotation, SharedShape, TypedShape, Vector};
 use dropbear_engine::animation::AnimationComponent;
-use crate::component::{Component, ComponentDescriptor, SerializedComponent};
+use crate::component::{Component, ComponentDescriptor, InspectableComponent, SerializedComponent};
 use crate::physics::PhysicsState;
 use crate::ptr::PhysicsStatePtr;
 
@@ -72,7 +72,7 @@ impl Component for ColliderGroup {
         }
     }
 
-    async fn first_time(graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<Self::RequiredComponentTypes>
+    async fn first_time(_: Arc<SharedGraphicsContext>) -> anyhow::Result<Self::RequiredComponentTypes>
     where
         Self: Sized
     {
@@ -91,7 +91,9 @@ impl Component for ColliderGroup {
     fn save(&self, _world: &World, _entity: Entity) -> Box<dyn SerializedComponent> {
         Box::new(self.clone())
     }
+}
 
+impl InspectableComponent for ColliderGroup {
     fn inspect(&mut self, ui: &mut Ui) {
         CollapsingHeader::new("Colliders").default_open(true).show(ui, |ui| {
             ui.label("Not implemented yet!");
diff --git a/crates/eucalyptus-core/src/physics/kcc.rs b/crates/eucalyptus-core/src/physics/kcc.rs
index a6abe0f..4cf20f1 100644
--- a/crates/eucalyptus-core/src/physics/kcc.rs
+++ b/crates/eucalyptus-core/src/physics/kcc.rs
@@ -22,7 +22,7 @@ use rapier3d::math::Rotation;
 use rapier3d::prelude::QueryFilter;
 use dropbear_engine::animation::AnimationComponent;
 use dropbear_engine::graphics::SharedGraphicsContext;
-use crate::component::{Component, ComponentDescriptor, SerializedComponent};
+use crate::component::{Component, ComponentDescriptor, InspectableComponent, SerializedComponent};
 use crate::ptr::WorldPtr;
 
 /// The kinematic character controller (kcc) component.
@@ -69,7 +69,9 @@ impl Component for KCC {
     fn save(&self, _world: &World, _entity: Entity) -> Box<dyn SerializedComponent> {
         Box::new(self.clone())
     }
+}
 
+impl InspectableComponent for KCC {
     fn inspect(&mut self, ui: &mut Ui) {
         egui::CollapsingHeader::new("Kinematic Character Controller").default_open(true).show(ui, |ui| {
             ui.label("Not implemented yet!")
diff --git a/crates/eucalyptus-core/src/physics/rigidbody.rs b/crates/eucalyptus-core/src/physics/rigidbody.rs
index 7aee1e1..eba8c0a 100644
--- a/crates/eucalyptus-core/src/physics/rigidbody.rs
+++ b/crates/eucalyptus-core/src/physics/rigidbody.rs
@@ -221,7 +221,9 @@ impl Component for RigidBody {
 	fn save(&self, _world: &World, _entity: Entity) -> Box<dyn SerializedComponent> {
 		Box::new(self.clone())
 	}
+}
 
+impl crate::component::InspectableComponent for RigidBody {
 	fn inspect(&mut self, ui: &mut Ui) {
 		CollapsingHeader::new("RigidBody").default_open(true).show(ui, |ui| {
 			ui.label("Not implemented yet!");
diff --git a/crates/eucalyptus-core/src/properties.rs b/crates/eucalyptus-core/src/properties.rs
index 89ad566..c2bb0fd 100644
--- a/crates/eucalyptus-core/src/properties.rs
+++ b/crates/eucalyptus-core/src/properties.rs
@@ -6,7 +6,7 @@ use std::sync::Arc;
 use egui::{CollapsingHeader, ComboBox, DragValue, Grid, RichText, TextEdit, Ui};
 use hecs::{Entity, World};
 use dropbear_engine::graphics::SharedGraphicsContext;
-use crate::component::{Component, ComponentDescriptor, ComponentInitFuture, SerializedComponent};
+use crate::component::{Component, ComponentDescriptor, ComponentInitFuture, InspectableComponent, SerializedComponent};
 use crate::ptr::WorldPtr;
 use crate::scripting::native::DropbearNativeError;
 use crate::scripting::result::DropbearNativeResult;
@@ -56,7 +56,9 @@ impl Component for CustomProperties {
     fn save(&self, _world: &World, _entity: Entity) -> Box<dyn SerializedComponent> {
         Box::new(self.clone())
     }
+}
 
+impl InspectableComponent for CustomProperties {
     fn inspect(&mut self, ui: &mut Ui) {
         CollapsingHeader::new("Custom Properties").default_open(true).show(ui, |ui| {
             ui.vertical(|ui| {
diff --git a/crates/eucalyptus-core/src/states.rs b/crates/eucalyptus-core/src/states.rs
index 74bd6c8..c60e60f 100644
--- a/crates/eucalyptus-core/src/states.rs
+++ b/crates/eucalyptus-core/src/states.rs
@@ -27,7 +27,7 @@ use egui::{CollapsingHeader, TextEdit, Ui};
 use hecs::{Entity, World};
 use dropbear_engine::model::AlphaMode;
 use dropbear_engine::texture::TextureWrapMode;
-use crate::component::{Component, ComponentDescriptor, ComponentInitFuture, SerializedComponent};
+use crate::component::{Component, ComponentDescriptor, ComponentInitFuture, InspectableComponent, SerializedComponent};
 use crate::properties::Value;
 
 /// A global "singleton" that contains the configuration of a project.
@@ -193,7 +193,9 @@ impl Component for Script {
     fn save(&self, _world: &World, _entity: Entity) -> Box<dyn SerializedComponent> {
         Box::new(self.clone())
     }
+}
 
+impl InspectableComponent for Script {
     fn inspect(&mut self, ui: &mut Ui) {
         CollapsingHeader::new("Logic")
             .default_open(true)
diff --git a/crates/eucalyptus-core/src/transform.rs b/crates/eucalyptus-core/src/transform.rs
index 4b18466..2bc8500 100644
--- a/crates/eucalyptus-core/src/transform.rs
+++ b/crates/eucalyptus-core/src/transform.rs
@@ -11,7 +11,7 @@ use ::jni::JNIEnv;
 use egui::{CollapsingHeader, Ui};
 use hecs::{Entity, World};
 use dropbear_engine::graphics::SharedGraphicsContext;
-use crate::component::{Component, ComponentDescriptor, SerializedComponent};
+use crate::component::{Component, ComponentDescriptor, InspectableComponent, SerializedComponent};
 use crate::types::{NTransform, NVector3};
 
 #[typetag::serde]
@@ -46,7 +46,9 @@ impl Component for EntityTransform {
     fn save(&self, _: &World, _: Entity) -> Box<dyn SerializedComponent> {
         Box::new(self.clone())
     }
+}
 
+impl InspectableComponent for EntityTransform {
     fn inspect(&mut self, ui: &mut Ui) {
         CollapsingHeader::new("Entity Transform").show(ui, |ui| {
             CollapsingHeader::new("Local").show(ui, |ui| {
diff --git a/include/dropbear.h b/include/dropbear.h
index caaf0ab..2197270 100644
--- a/include/dropbear.h
+++ b/include/dropbear.h
@@ -179,31 +179,7 @@ typedef struct NChannelValuesFfi {
 
 typedef NChannelValuesFfi NChannelValues;
 
-typedef struct NRange {
-    float start;
-    float end;
-} NRange;
-
-typedef void* SceneLoaderPtr;
-
-typedef struct NAttenuation {
-    float constant;
-    float linear;
-    float quadratic;
-} NAttenuation;
-
-typedef struct NColour {
-    uint8_t r;
-    uint8_t g;
-    uint8_t b;
-    uint8_t a;
-} NColour;
-
-typedef struct AxisLock {
-    bool x;
-    bool y;
-    bool z;
-} AxisLock;
+typedef void* InputStatePtr;
 
 typedef struct IndexNative {
     uint32_t index;
@@ -216,83 +192,19 @@ typedef struct NCollider {
     uint32_t id;
 } NCollider;
 
-typedef struct u64Array {
-    uint64_t* values;
-    size_t length;
-    size_t capacity;
-} u64Array;
-
-typedef void* InputStatePtr;
-
-typedef void* AssetRegistryPtr;
-
 typedef struct RayHit {
     NCollider collider;
     double distance;
 } RayHit;
 
-typedef struct NShapeCastHit {
-    NCollider collider;
-    double distance;
-    NVector3 witness1;
-    NVector3 witness2;
-    NVector3 normal1;
-    NVector3 normal2;
-    NShapeCastStatus status;
-} NShapeCastHit;
+typedef void* SceneLoaderPtr;
 
 typedef struct RigidBodyContext {
     IndexNative index;
     uint64_t entity_id;
 } RigidBodyContext;
 
-typedef struct ConnectedGamepadIds {
-    u64Array ids;
-} ConnectedGamepadIds;
-
-typedef void* PhysicsStatePtr;
-
-typedef struct i32Array {
-    int32_t* values;
-    size_t length;
-    size_t capacity;
-} i32Array;
-
-typedef struct f64ArrayArray {
-    double* values;
-    size_t length;
-    size_t capacity;
-} f64ArrayArray;
-
-typedef struct NSkin {
-    const char* name;
-    i32Array joints;
-    f64ArrayArray inverse_bind_matrices;
-    const int32_t* skeleton_root;
-} NSkin;
-
-typedef struct NSkinArray {
-    NSkin* values;
-    size_t length;
-    size_t capacity;
-} NSkinArray;
-
-typedef struct NColliderArray {
-    NCollider* values;
-    size_t length;
-    size_t capacity;
-} NColliderArray;
-
-typedef struct IndexNativeArray {
-    IndexNative* values;
-    size_t length;
-    size_t capacity;
-} IndexNativeArray;
-
-typedef struct CharacterCollisionArray {
-    uint64_t entity_id;
-    IndexNativeArray collisions;
-} CharacterCollisionArray;
+typedef void* CommandBufferPtr;
 
 typedef struct NVector4 {
     double x;
@@ -306,6 +218,12 @@ typedef struct NVector2 {
     double y;
 } NVector2;
 
+typedef struct i32Array {
+    int32_t* values;
+    size_t length;
+    size_t capacity;
+} i32Array;
+
 typedef struct NModelVertex {
     NVector3 position;
     NVector3 normal;
@@ -336,38 +254,60 @@ typedef struct NMeshArray {
     size_t capacity;
 } NMeshArray;
 
-typedef struct NMaterial {
+typedef struct f64ArrayArray {
+    double* values;
+    size_t length;
+    size_t capacity;
+} f64ArrayArray;
+
+typedef struct NSkin {
     const char* name;
-    uint64_t diffuse_texture;
-    uint64_t normal_texture;
-    NVector4 tint;
-    NVector3 emissive_factor;
-    float metallic_factor;
-    float roughness_factor;
-    const float* alpha_cutoff;
-    bool double_sided;
-    float occlusion_strength;
-    float normal_scale;
-    NVector2 uv_tiling;
-    const uint64_t* emissive_texture;
-    const uint64_t* metallic_roughness_texture;
-    const uint64_t* occlusion_texture;
-} NMaterial;
+    i32Array joints;
+    f64ArrayArray inverse_bind_matrices;
+    const int32_t* skeleton_root;
+} NSkin;
 
-typedef struct NMaterialArray {
-    NMaterial* values;
+typedef struct NSkinArray {
+    NSkin* values;
     size_t length;
     size_t capacity;
-} NMaterialArray;
+} NSkinArray;
 
-typedef struct NTransform {
-    NVector3 position;
+typedef struct NNodeTransform {
+    NVector3 translation;
     NQuaternion rotation;
     NVector3 scale;
-} NTransform;
+} NNodeTransform;
+
+typedef struct NNode {
+    const char* name;
+    const int32_t* parent;
+    i32Array children;
+    NNodeTransform transform;
+} NNode;
+
+typedef struct NNodeArray {
+    NNode* values;
+    size_t length;
+    size_t capacity;
+} NNodeArray;
+
+typedef void* AssetRegistryPtr;
 
 typedef void* GraphicsContextPtr;
 
+typedef struct u64Array {
+    uint64_t* values;
+    size_t length;
+    size_t capacity;
+} u64Array;
+
+typedef struct ConnectedGamepadIds {
+    u64Array ids;
+} ConnectedGamepadIds;
+
+typedef void* WorldPtr;
+
 typedef struct f64Array {
     double* values;
     size_t length;
@@ -399,34 +339,94 @@ typedef struct NAnimationArray {
     size_t capacity;
 } NAnimationArray;
 
-typedef struct Progress {
-    size_t current;
-    size_t total;
-    const char* message;
-} Progress;
+typedef struct NColliderArray {
+    NCollider* values;
+    size_t length;
+    size_t capacity;
+} NColliderArray;
 
-typedef struct NNodeTransform {
-    NVector3 translation;
+typedef struct IndexNativeArray {
+    IndexNative* values;
+    size_t length;
+    size_t capacity;
+} IndexNativeArray;
+
+typedef struct CharacterCollisionArray {
+    uint64_t entity_id;
+    IndexNativeArray collisions;
+} CharacterCollisionArray;
+
+typedef void* PhysicsStatePtr;
+
+typedef struct AxisLock {
+    bool x;
+    bool y;
+    bool z;
+} AxisLock;
+
+typedef struct NRange {
+    float start;
+    float end;
+} NRange;
+
+typedef struct NTransform {
+    NVector3 position;
     NQuaternion rotation;
     NVector3 scale;
-} NNodeTransform;
+} NTransform;
 
-typedef struct NNode {
+typedef struct NColour {
+    uint8_t r;
+    uint8_t g;
+    uint8_t b;
+    uint8_t a;
+} NColour;
+
+typedef struct NShapeCastHit {
+    NCollider collider;
+    double distance;
+    NVector3 witness1;
+    NVector3 witness2;
+    NVector3 normal1;
+    NVector3 normal2;
+    NShapeCastStatus status;
+} NShapeCastHit;
+
+typedef struct NMaterial {
     const char* name;
-    const int32_t* parent;
-    i32Array children;
-    NNodeTransform transform;
-} NNode;
+    uint64_t diffuse_texture;
+    uint64_t normal_texture;
+    NVector4 tint;
+    NVector3 emissive_factor;
+    float metallic_factor;
+    float roughness_factor;
+    const float* alpha_cutoff;
+    bool double_sided;
+    float occlusion_strength;
+    float normal_scale;
+    NVector2 uv_tiling;
+    const uint64_t* emissive_texture;
+    const uint64_t* metallic_roughness_texture;
+    const uint64_t* occlusion_texture;
+} NMaterial;
 
-typedef struct NNodeArray {
-    NNode* values;
+typedef struct NMaterialArray {
+    NMaterial* values;
     size_t length;
     size_t capacity;
-} NNodeArray;
+} NMaterialArray;
 
-typedef void* CommandBufferPtr;
+typedef struct NAttenuation {
+    float constant;
+    float linear;
+    float quadratic;
+} NAttenuation;
 
-typedef void* WorldPtr;
+typedef struct Progress {
+    size_t current;
+    size_t total;
+    const char* message;
+} Progress;
 
 int32_t dropbear_gamepad_is_button_pressed(InputStatePtr input, uint64_t gamepad_id, int32_t button_ordinal, bool* out0);
 int32_t dropbear_gamepad_get_left_stick_position(InputStatePtr input, uint64_t gamepad_id, NVector2* out0);