tirbofish/dropbear · diff
wip: stupid ass bug related to not updating Components.
todo: i need to upgrade egui to 0.34.1 to get wgpu 29.0.0 working (only took like 4 months for them to make a new release smh)
Signature present but could not be verified.
Unverified
@@ -71,6 +71,7 @@ pub struct Camera { pub uniform: CameraUniform, buffer: UniformBuffer<CameraUniform>, + pub bind_group: wgpu::BindGroup, /// View matrix pub view_mat: DMat4, @@ -116,6 +117,14 @@ impl Camera { uniform.view_proj = mvp.as_mat4().to_cols_array_2d(); let buffer = UniformBuffer::new(&graphics.device, "camera uniform"); + let bind_group = graphics.device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("editor camera bind group"), + layout: &graphics.layouts.camera_bind_group_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: buffer.buffer().as_entire_binding(), + }], + }); let camera = Self { eye: builder.eye, @@ -136,6 +145,7 @@ impl Camera { }, view_mat, proj_mat, + bind_group, }; log::debug!( @@ -491,7 +491,7 @@ impl DebugDrawPipeline { let mut pass = encoder.begin_render_pass(&RenderPassDescriptor { label: Some("debug draw pass"), color_attachments: &[Some(RenderPassColorAttachment { - view: hdr.view(), + view: hdr.render_view(), depth_slice: None, resolve_target: hdr.resolve_target(), ops: Operations { @@ -22,7 +22,7 @@ use std::collections::hash_map::DefaultHasher; use std::future::Future; use std::hash::{Hash, Hasher}; use std::pin::Pin; -use std::sync::{Arc, LazyLock}; +use std::sync::{Arc, LazyLock, OnceLock}; use std::time::{SystemTime, UNIX_EPOCH}; pub use typetag::*; @@ -50,6 +50,10 @@ pub struct KotlinComponentDecl { pub static KOTLIN_COMPONENT_QUEUE: LazyLock<Mutex<Vec<KotlinComponentDecl>>> = LazyLock::new(|| Mutex::new(Vec::new())); +/// Fn signature for the Kotlin per-entity update callback stored on [`ComponentRegistry`]. +/// Arguments: `(fqcn, entity_bits, delta_time_secs)`. +type KotlinUpdateFn = Box<dyn Fn(&str, u64, f32) + Send + Sync>; + pub struct ComponentRegistry { /// Maps TypeId to ComponentDescriptor for quick lookups descriptors: HashMap<LanguageTypeId, ComponentDescriptor>, @@ -73,6 +77,10 @@ pub struct ComponentRegistry { finders: HashMap<LanguageTypeId, FindFn>, /// Allows for inspecting the component in the Resource Inspector dock. inspectors: HashMap<LanguageTypeId, InspectFn>, + /// Shared JVM update callback, set after JVM init via [`Self::set_kotlin_update_fn`]. + /// Captured by `Arc::clone` in each Kotlin updater closure so it works even when + /// the fn is set after [`Self::drain_kotlin_queue`] has already registered descriptors. + kotlin_update_fn: Arc<OnceLock<KotlinUpdateFn>>, } /// Describes a handy little future for [`Component::init`], which deals with initialising a component from its serialized form. @@ -138,9 +146,18 @@ impl ComponentRegistry { removers: HashMap::new(), finders: HashMap::new(), inspectors: HashMap::new(), + kotlin_update_fn: Arc::new(OnceLock::new()), } } + /// Sets the JVM update callback used to call `NativeComponent.updateComponent()` from Rust. + /// + /// Must be called once after the JVM is ready (after `drain_kotlin_queue`). Subsequent calls + /// are no-ops because the inner `OnceLock` can only be set once. + pub fn set_kotlin_update_fn(&mut self, f: impl Fn(&str, u64, f32) + Send + Sync + 'static) { + let _ = self.kotlin_update_fn.set(Box::new(f)); + } + /// Register a component type with the registry pub fn register<T>(&mut self) where @@ -241,6 +258,9 @@ impl ComponentRegistry { let world_ptr = world as *const hecs::World; if let Ok(mut comp) = world.get::<&mut T>(entity) { let world_ref = unsafe { &*world_ptr }; + if T::descriptor().internal { + return; + } comp.inspect(world_ref, entity, ui, graphics); } }), @@ -447,6 +467,19 @@ impl ComponentRegistry { ui.separator(); } + + let kotlin_fqcns: Vec<String> = world + .get::<&KotlinComponents>(entity) + .map(|kc| kc.fqcns.clone()) + .unwrap_or_default(); + + for fqcn in kotlin_fqcns { + let kotlin_type_id = LanguageTypeId::Kotlin(fqcn); + if let Some(inspector) = self.inspectors.get(&kotlin_type_id) { + inspector(world, entity, ui, graphics.clone()); + ui.separator(); + } + } } fn numeric_id(type_id: &LanguageTypeId) -> u64 { @@ -489,6 +522,8 @@ impl ComponentRegistry { return; } + let type_name_display = decl.type_name.clone(); + self.fqtn_to_type.insert(decl.fqcn.clone(), id.clone()); if let Some(ref cat) = decl.category { self.categories @@ -518,6 +553,46 @@ impl ComponentRegistry { }), ); + let fqcn = decl.fqcn.clone(); + self.defaults.insert( + id.clone(), + Box::new(move || { + Box::new(KotlinComponents { fqcns: vec![fqcn.clone()] }) + }), + ); + + let fqcn_for_update = decl.fqcn.clone(); + let update_slot = Arc::clone(&self.kotlin_update_fn); + self.updaters.insert( + id.clone(), + Box::new(move |world, _physics, dt, _graphics| { + let entity_bits: Vec<u64> = world + .query::<(hecs::Entity, &KotlinComponents)>() + .iter() + .filter_map(|(entity, kc)| { + if kc.has(&fqcn_for_update) { Some(entity.to_bits().get()) } else { None } + }) + .collect(); + + if let Some(update_fn) = update_slot.get() { + for bits in entity_bits { + update_fn(&fqcn_for_update, bits, dt); + } + } + }), + ); + + self.inspectors.insert( + id.clone(), + Box::new(move |_world, _entity, ui, _graphics| { + CollapsingHeader::new(type_name_display.as_str()) + .default_open(true) + .show(ui, |ui| { + ui.label("inspect() not yet wired."); + }); + }), + ); + let fqcn = decl.fqcn; self.finders.insert( id, @@ -19,7 +19,7 @@ fn label_exists_for_entity( #[dropbear_macro::export( kotlin( - class = "com.dropbear.components.EntityRefNative", + class = "com.dropbear.EntityRefNative", func = "getEntityLabel" ), c @@ -34,7 +34,7 @@ fn get_label( #[dropbear_macro::export( kotlin( - class = "com.dropbear.components.EntityRefNative", + class = "com.dropbear.EntityRefNative", func = "getChildren" ), c @@ -58,7 +58,7 @@ fn get_children( #[dropbear_macro::export( kotlin( - class = "com.dropbear.components.EntityRefNative", + class = "com.dropbear.EntityRefNative", func = "getChildByLabel" ), c @@ -70,7 +70,7 @@ fn get_child_by_label( ) -> DropbearNativeResult<Option<u64>> { if let Ok(children) = world.query_one::<&Children>(entity).get() { for child in children.children() { - if let Ok(label) = world.get::<&Label>(entity) { + if let Ok(label) = world.get::<&Label>(*child) { if label.as_str() == target { let found = child.clone(); return Ok(Some(found.to_bits().get())); @@ -87,7 +87,7 @@ fn get_child_by_label( } #[dropbear_macro::export( - kotlin(class = "com.dropbear.components.EntityRefNative", func = "getParent"), + kotlin(class = "com.dropbear.EntityRefNative", func = "getParent"), c )] fn get_parent( @@ -19,6 +19,7 @@ use once_cell::sync::OnceCell; use sha2::{Digest, Sha256}; use std::fs; use std::path::PathBuf; +use std::sync::Arc; #[cfg(feature = "jvm_debug")] use crate::scripting::AWAIT_JDB; @@ -34,6 +35,9 @@ pub enum RuntimeMode { const LIBRARY_PATH: &[u8] = include_bytes!("../../../../build/libs/dropbear-1.0-SNAPSHOT-all.jar"); pub static RUNTIME_MODE: OnceCell<RuntimeMode> = OnceCell::new(); +/// Shared handle to the active [`JavaVM`], set once during [`JavaContext::new`]. +/// Callers may clone the `Arc` to attach new threads without any unsafe code. +pub static GLOBAL_JVM: OnceCell<Arc<JavaVM>> = OnceCell::new(); #[cfg(feature = "jvm_debug")] fn is_port_available(port: u16) -> bool { @@ -48,7 +52,7 @@ fn is_port_available(port: u16) -> bool { /// Provides a context for any eucalyptus-core JNI calls and JVM hosting. pub struct JavaContext { - pub(crate) jvm: JavaVM, + pub(crate) jvm: Arc<JavaVM>, native_engine_instance: Option<GlobalRef>, dropbear_engine_class: Option<GlobalRef>, system_manager_instance: Option<GlobalRef>, @@ -277,6 +281,9 @@ impl JavaContext { log::info!("Created JVM instance"); + let jvm = Arc::new(jvm); + let _ = GLOBAL_JVM.set(jvm.clone()); + Ok(Self { jvm, native_engine_instance: None, @@ -430,6 +437,8 @@ impl JavaContext { self.system_manager_instance = Some(manager_global_ref); } + Self::call_component_manager_register_all(&mut env); + Ok(()) })(); @@ -437,6 +446,27 @@ impl JavaContext { result } + /// Registers the components within the .jar file as created in the ComponentManager. + fn call_component_manager_register_all(env: &mut JNIEnv) { + match env.find_class("com/dropbear/decl/ComponentManager") { + Err(_) => { + let _ = env.exception_clear(); + log::debug!( + "ComponentManager class not found in user JAR, no @EcsComponent types will be registered" + ); + } + Ok(class) => { + if let Err(e) = + env.call_static_method(&class, "registerAll", "()V", &[]) + { + let _ = env.exception_clear(); + log::warn!("ComponentManager.registerAll() failed: {:?}", e); + } else { + log::debug!("ComponentManager.registerAll() called successfully"); + } + } + } + } pub fn get_exception(env: &mut JNIEnv) -> anyhow::Result<()> { if let Ok(ex) = env.exception_occurred() { @@ -511,6 +541,7 @@ impl JavaContext { "(Ljava/lang/String;)V", &[JValue::Object(&jar_path_jstring)], )?; + Self::call_component_manager_register_all(&mut env); Ok(()) })(); @@ -50,6 +50,10 @@ pub struct NativeLibrary { 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>, + + update_kotlin_component_fn: Option<Symbol<'static, sig::UpdateKotlinComponent>>, + #[allow(dead_code)] + inspect_kotlin_component_fn: Option<Symbol<'static, sig::InspectKotlinComponent>>, } impl NativeLibrary { @@ -150,6 +154,21 @@ impl NativeLibrary { "dropbear_set_last_error_message", )?; + let update_kotlin_component_fn = library + .get::<sig::UpdateKotlinComponent>(b"dropbear_update_kotlin_component\0") + .ok() + .map(|s| std::mem::transmute::< + Symbol<sig::UpdateKotlinComponent>, + Symbol<'static, sig::UpdateKotlinComponent>, + >(s)); + let inspect_kotlin_component_fn = library + .get::<sig::InspectKotlinComponent>(b"dropbear_inspect_kotlin_component\0") + .ok() + .map(|s| std::mem::transmute::< + Symbol<sig::InspectKotlinComponent>, + Symbol<'static, sig::InspectKotlinComponent>, + >(s)); + Ok(Self { library, init_fn, @@ -169,6 +188,8 @@ impl NativeLibrary { contact_force_event_fn, get_last_err_msg_fn, set_last_err_msg_fn, + update_kotlin_component_fn, + inspect_kotlin_component_fn, }) } } @@ -82,3 +82,9 @@ pub type DestroyAll = unsafe extern "C" fn() -> i32; pub type GetLastErrorMessage = unsafe extern "C" fn() -> *const c_char; /// CName: `dropbear_set_last_error_message` pub type SetLastErrorMessage = unsafe extern "C" fn(msg: *const c_char); + +/// CName: `dropbear_update_kotlin_component` +pub type UpdateKotlinComponent = + unsafe extern "C" fn(fqcn: *const c_char, entity_id: u64, dt: f64) -> i32; +/// CName: `dropbear_inspect_kotlin_component` +pub type InspectKotlinComponent = unsafe extern "C" fn(fqcn: *const c_char) -> i32; @@ -91,6 +91,10 @@ impl InspectableComponent for KotlinComponents { for fqcn in &self.fqcns { ui.label(fqcn); } + + ui.separator(); + + ui.label("This should not be visible in the editor. this is considered `internal=true`"); }); } } @@ -8,10 +8,11 @@ use crate::scripting::result::DropbearNativeResult; use crate::types::{NTransform, NVector3}; use ::jni::JNIEnv; use ::jni::objects::{JObject, JValue}; +use dropbear_engine::camera::Camera; use dropbear_engine::entity::{EntityTransform, Transform}; use dropbear_engine::graphics::SharedGraphicsContext; use egui::{CollapsingHeader, ComboBox, Ui}; -use glam::{DQuat, DVec3, Vec3}; +use glam::{DMat3, DQuat, DVec3, Vec3}; use hecs::{Entity, World}; use splines::{Interpolation, Key, Spline}; use std::sync::Arc; @@ -62,8 +63,23 @@ fn project_to_path(path: &[DVec3], pos: DVec3) -> f32 { best_t } -/// Linearly samples the path at `t` in [0, 1]. Matches the `Interpolation::Linear` splines used -/// in `update_component`, so the editor gizmo stays consistent with runtime behaviour. +fn path_tangent_rotation(path: &[DVec3], t: f32) -> DQuat { + let n = path.len(); + if n < 2 { + return DQuat::IDENTITY; + } + let t_clamped = (t as f64 * (n - 1) as f64).clamp(0.0, (n - 2) as f64); + let i = t_clamped as usize; + let forward = (path[i + 1] - path[i]).normalize_or_zero(); + if forward.length_squared() < 1e-10 { + return DQuat::IDENTITY; + } + let world_up = if forward.dot(DVec3::Y).abs() > 0.99 { DVec3::Z } else { DVec3::Y }; + let right = world_up.cross(forward).normalize(); + let up = forward.cross(right).normalize(); + DQuat::from_mat3(&DMat3::from_cols(right, up, -forward)) +} + fn sample_path_linear(path: &[DVec3], t: f32) -> DVec3 { let n = path.len(); if n == 0 { @@ -152,9 +168,6 @@ impl Component for OnRails { } fn update_component(&mut self, world: &World, _physics: &mut PhysicsState, entity: Entity, dt: f32, _graphics: Arc<SharedGraphicsContext>) { - if !self.enabled { - return; - } let n = self.path.len(); if n < 2 { return; @@ -238,8 +251,16 @@ impl Component for OnRails { sy.clamped_sample(t), sz.clamped_sample(t), ) { + let new_pos = DVec3::new(x, y, z); + let rot = path_tangent_rotation(&self.path, self.progress); if let Ok(mut et) = world.get::<&mut EntityTransform>(entity) { - et.world_mut().position = DVec3::new(x, y, z); + et.world_mut().position = new_pos; + et.world_mut().rotation = rot; + } + if let Ok(mut camera) = world.get::<&mut Camera>(entity) { + camera.eye = new_pos; + let forward = rot * DVec3::NEG_Z; + camera.target = new_pos + forward; } } } @@ -255,12 +276,11 @@ impl InspectableComponent for OnRails { world: &World, entity: Entity, ui: &mut Ui, - _graphics: Arc<SharedGraphicsContext>, + graphics: Arc<SharedGraphicsContext>, ) { let n = self.path.len(); - // if the entity is dragged off the path, it will - if self.enabled && n >= 2 { + if n >= 2 { let expected = sample_path_linear(&self.path, self.progress); let current_pos = world .get::<&EntityTransform>(entity) @@ -270,8 +290,15 @@ impl InspectableComponent for OnRails { if pos.distance_squared(expected) > 1e-8 { self.progress = project_to_path(&self.path, pos); let snapped = sample_path_linear(&self.path, self.progress); + let rot = path_tangent_rotation(&self.path, self.progress); if let Ok(mut et) = world.get::<&mut EntityTransform>(entity) { et.world_mut().position = snapped; + et.world_mut().rotation = rot; + } + if let Ok(mut camera) = world.get::<&mut Camera>(entity) { + camera.eye = snapped; + let forward = rot * DVec3::NEG_Z; + camera.target = snapped + forward; } } } @@ -292,8 +319,15 @@ impl InspectableComponent for OnRails { ); if slider.changed() && n >= 2 { let pos = sample_path_linear(&self.path, self.progress); + let rot = path_tangent_rotation(&self.path, self.progress); if let Ok(mut et) = world.get::<&mut EntityTransform>(entity) { et.world_mut().position = pos; + et.world_mut().rotation = rot; + } + if let Ok(mut camera) = world.get::<&mut Camera>(entity) { + camera.eye = pos; + let forward = rot * DVec3::NEG_Z; + camera.target = pos + forward; } } @@ -389,6 +423,19 @@ impl InspectableComponent for OnRails { ui.add_space(4.0); ui.checkbox(&mut self.debug, "Debug path"); + + if self.debug && self.path.len() >= 2 { + if let Some(dd) = graphics.debug_draw.lock().as_mut() { + let red = [1.0, 0.0, 0.0, 1.0]; + dd.draw_point(self.path[0].as_vec3(), 0.1, red); + for i in 0..(self.path.len() - 1) { + let a = self.path[i].as_vec3(); + let b = self.path[i + 1].as_vec3(); + dd.draw_line(a, b, red); + dd.draw_point(b, 0.1, red); + } + } + } }); } } @@ -412,9 +459,9 @@ impl Component for EntityTransform { } fn init( - ser: &Self::SerializedForm, + ser: &'_ Self::SerializedForm, _: Arc<SharedGraphicsContext>, - ) -> crate::component::ComponentInitFuture<Self> { + ) -> crate::component::ComponentInitFuture<'_, Self> { Box::pin(async move { Ok((ser.clone(),)) }) } @@ -85,6 +85,7 @@ impl<'a> EditorTabViewer<'a> { BTreeMap::new(); for (id, desc) in registry.iter_available_components() { + if desc.internal { continue; } let category = desc .category .clone() @@ -177,11 +178,12 @@ impl<'a> EditorTabViewer<'a> { } } - let children_entities = if let Ok(children) = world.get::<&Children>(entity) { + let mut children_entities = if let Ok(children) = world.get::<&Children>(entity) { children.children().to_vec() } else { Vec::new() }; + children_entities.sort_by_key(|e| e.to_bits().get()); for child in children_entities { if let Err(e) = @@ -221,7 +223,8 @@ impl<'a> EditorTabViewer<'a> { }); { puffin::profile_scope!("entity_list.index_components"); - for (component_id, _) in self.component_registry.iter_available_components() { + for (component_id, desc) in self.component_registry.iter_available_components() { + if desc.internal { continue; } for entity in self .component_registry .find_entities_by_numeric_id(self.world, component_id) @@ -234,13 +237,14 @@ impl<'a> EditorTabViewer<'a> { } } - let root_entities: Vec<Entity> = self + let mut root_entities: Vec<Entity> = self .world .query::<Entity>() .without::<&Parent>() .iter() .map(|e| e) .collect(); + root_entities.sort_by_key(|e| e.to_bits().get()); for entity in root_entities { puffin::profile_scope!("entity_list.root_entity"); @@ -88,7 +88,6 @@ impl<'a> EditorTabViewer<'a> { }); log::debug!("Pointer down - {:?}", entity); } else { - *self.selected_entity = None; *self.viewport_drag = None; } } @@ -105,7 +105,6 @@ pub struct Editor { pub sky_pipeline: Option<SkyPipeline>, pub billboard_pipeline: Option<BillboardPipeline>, pub kino: Option<KinoState>, - pub(crate) camera_bind_group: Option<wgpu::BindGroup>, pub(crate) default_skinning_buffer: Option<wgpu::Buffer>, pub(crate) default_morph_deltas_buffer: Option<wgpu::Buffer>, pub(crate) default_morph_weights_buffer: Option<wgpu::Buffer>, @@ -329,7 +328,6 @@ impl Editor { sky_pipeline: None, billboard_pipeline: None, kino: None, - camera_bind_group: None, default_skinning_buffer: None, default_morph_deltas_buffer: None, default_morph_weights_buffer: None, @@ -481,7 +479,8 @@ impl Editor { scene.scene_name ); - let entity_ids = self.world.query::<Entity>().iter().collect::<Vec<_>>(); + let mut entity_ids = self.world.query::<Entity>().iter().collect::<Vec<_>>(); + entity_ids.sort_by_key(|e| e.to_bits().get()); for id in entity_ids { let Ok(label) = self.world.get::<&Label>(id) else { @@ -1594,6 +1593,7 @@ impl Editor { self.collider_wireframe_pipeline = Some(ColliderWireframePipeline::new(graphics.clone())); self.mipmapper = None; self.billboard_pipeline = Some(BillboardPipeline::new(graphics.clone())); + *graphics.debug_draw.lock() = Some(dropbear_engine::debug::DebugDraw::new(graphics.clone())); self.kino = Some(KinoState::new( KinoWGPURenderer::new( &graphics.device, @@ -1607,7 +1607,6 @@ impl Editor { KinoWinitWindowing::new(graphics.window.clone(), None), )); - self.camera_bind_group = None; self.default_skinning_buffer = None; self.default_morph_deltas_buffer = None; self.default_morph_weights_buffer = None; @@ -1623,15 +1622,6 @@ impl Editor { let active_camera = self.active_camera.lock().clone(); if let Some(camera_entity) = active_camera { if let Ok(camera) = self.world.query_one::<&Camera>(camera_entity).get() { - self.camera_bind_group = Some(graphics.device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("editor camera bind group"), - layout: &graphics.layouts.camera_bind_group_layout, - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: camera.buffer().as_entire_binding(), - }], - })); - let max_skinning_matrices = 256usize; let identity = vec![Mat4::IDENTITY; max_skinning_matrices]; let skinning_buffer = graphics.device.create_buffer_init(&wgpu::util::BufferInitDescriptor { @@ -13,6 +13,7 @@ use dropbear_engine::{ scene::{Scene, SceneCommand}, }; use eucalyptus_core::billboard::BillboardComponent; +use eucalyptus_core::component::KotlinComponentDecl; use eucalyptus_core::entity_status::EntityStatus; use eucalyptus_core::physics::collider::ColliderGroup; use eucalyptus_core::physics::collider::ColliderShapeKey; @@ -22,6 +23,7 @@ use eucalyptus_core::states::{Label, WorldLoadingStatus, SCENES}; use eucalyptus_core::ui::HUDComponent; use hecs::Entity; use log; +use magna_carta::ScriptManifest; use parking_lot::Mutex; use std::collections::HashMap; use std::sync::Arc; @@ -36,6 +38,41 @@ use kino_ui::rendering::KinoRenderTargetId; impl Scene for Editor { fn load(&mut self, graphics: Arc<SharedGraphicsContext>) { + { + let src_path = { + let project = PROJECT.read(); + project.project_path.join("src") + }; + if src_path.exists() { + if let Some(registry) = Arc::get_mut(&mut self.component_registry) { + let mut processor = match magna_carta::KotlinProcessor::new() { + Ok(p) => p, + Err(e) => { + log::warn!("Failed to create KotlinProcessor for component scan: {}", e); + return; + } + }; + let mut manifest = ScriptManifest::new(); + if let Err(e) = magna_carta::visit_kotlin_files(&src_path, &mut processor, &mut manifest) { + log::warn!("Kotlin component scan failed: {}", e); + } else { + let count = manifest.components().len(); + for item in manifest.components() { + registry.register_kotlin_descriptor(KotlinComponentDecl { + fqcn: item.fqcn().to_owned(), + type_name: item.simple_name().to_owned(), + category: None, + description: None, + }); + } + log::info!("Registered {} Kotlin component descriptor(s) from project sources", count); + } + } else { + log::warn!("Could not obtain exclusive access to component_registry for Kotlin component scan"); + } + } + } + self.current_scene_name = { let last_opened = { let project = PROJECT.read(); @@ -419,14 +456,10 @@ impl Scene for Editor { let Some(camera) = q else { return; }; + log_once::debug_once!("Camera ready"); log_once::debug_once!("Camera currently being viewed: {}", camera.label); - let Some(camera_bind_group) = self.camera_bind_group.as_ref() else { - log_once::warn_once!("Camera bind group not ready"); - return; - }; - // clear viewport render pass { puffin::profile_scope!("Clearing viewport"); @@ -670,7 +703,7 @@ impl Scene for Editor { if !light.component.visible { continue; } - render_pass.draw_light_model(&model, camera_bind_group, &light.bind_group); + render_pass.draw_light_model(&model, &camera.bind_group, &light.bind_group); } } } else { @@ -1034,7 +1067,7 @@ impl Scene for Editor { }); render_pass.set_pipeline(&collider_pipeline.pipeline); - render_pass.set_bind_group(0, camera_bind_group, &[]); + render_pass.set_bind_group(0, Some(&camera.bind_group), &[]); let mut instances_by_shape: HashMap< ColliderShapeKey, @@ -1242,6 +1275,12 @@ impl Scene for Editor { } } + // debug draw flush + if let Some(debug_draw) = graphics.debug_draw.lock().as_mut() { + let view_proj = Mat4::from_cols_array_2d(&camera.uniform.view_proj); + debug_draw.flush(graphics.clone(), &mut encoder, view_proj); + } + hdr.process(&mut encoder, &graphics.viewport_texture.view); if let Err(e) = encoder.submit() { @@ -7,6 +7,7 @@ use egui::Align2; use eucalyptus_core::camera::{CameraComponent, CameraType}; use eucalyptus_core::scene::SceneEntity; use eucalyptus_core::scripting::{BuildStatus, build_jvm}; +use eucalyptus_core::scripting::types::KotlinComponents; use eucalyptus_core::states::{Label, PROJECT}; use eucalyptus_core::{fatal, info, success, success_without_console, warn, warn_without_console}; use std::collections::{HashMap, HashSet}; @@ -546,6 +547,28 @@ impl SignalController for Editor { Ok(()) } Signal::AddComponent(entity, component) => { + if let Some(kc_box) = component.as_any().downcast_ref::<KotlinComponents>() { + for fqcn in kc_box.fqcns.clone() { + if let Ok(mut existing) = self.world.get::<&mut KotlinComponents>(entity) { + if existing.has(&fqcn) { + warn!("Entity {:?} already has Kotlin component '{}'", entity, fqcn); + } else { + existing.attach(&fqcn); + success!("Added Kotlin component '{}' to entity {:?}", fqcn, entity); + } + } else { + let mut new_kc = KotlinComponents::default(); + new_kc.attach(&fqcn); + if let Err(e) = self.world.insert_one(entity, new_kc) { + warn!("Failed to insert KotlinComponents for '{}': {}", fqcn, e); + } else { + success!("Added Kotlin component '{}' to entity {:?}", fqcn, entity); + } + } + } + return Ok(()); + } + let registry = self.component_registry.clone(); let Some(component_id) = registry.id_for_component(component.as_ref()) else { @@ -112,17 +112,37 @@ impl Generator for KotlinJVMGenerator { writeln!(output)?; writeln!(output, "object ComponentManager {{")?; + writeln!(output, " private val instances: MutableMap<String, com.dropbear.ecs.NativeComponent> = mutableMapOf()")?; + writeln!(output)?; + writeln!(output, " private fun registerOne(component: com.dropbear.ecs.NativeComponent) {{")?; + writeln!(output, " component.register()")?; + writeln!(output, " instances[component.fullyQualifiedTypeName] = component")?; + writeln!(output, " }}")?; + writeln!(output)?; writeln!(output, " @Synchronized")?; writeln!(output, " fun registerAll() {{")?; for component in manifest.components() { writeln!( output, - " com.dropbear.ecs.registerKotlinComponentType({fqcn}, {simple}, null, null)", - fqcn = format!("\"{}\"", component.fqcn()), - simple = format!("\"{}\"", component.simple_name()), + " registerOne({simple}())", + simple = component.simple_name(), )?; } writeln!(output, " }}")?; + writeln!(output)?; + writeln!(output, " fun updateKotlinComponent(fqcn: String, entityId: Long, dt: Double) {{")?; + writeln!(output, " val instance = instances[fqcn] ?: return")?; + writeln!(output, " val engine = com.dropbear.DropbearEngine(com.dropbear.DropbearEngine.native)")?; + writeln!(output, " instance.setCurrentEntity(entityId)")?; + writeln!(output, " instance.updateComponent(engine, dt)")?; + writeln!(output, " instance.clearCurrentEntity()")?; + writeln!(output, " }}")?; + writeln!(output)?; + writeln!(output, " fun inspectKotlinComponent(fqcn: String) {{")?; + writeln!(output, " val instance = instances[fqcn] ?: return")?; + writeln!(output, " val engine = com.dropbear.DropbearEngine(com.dropbear.DropbearEngine.native)")?; + writeln!(output, " instance.inspect(engine)")?; + writeln!(output, " }}")?; writeln!(output, "}}")?; Ok(output) @@ -556,16 +556,36 @@ object ScriptManager {{ writeln!(output)?; writeln!(output, "object ComponentManager {{")?; + writeln!(output, " private val instances: MutableMap<String, com.dropbear.ecs.NativeComponent> = mutableMapOf()")?; + writeln!(output)?; + writeln!(output, " private fun registerOne(component: com.dropbear.ecs.NativeComponent) {{")?; + writeln!(output, " component.register()")?; + writeln!(output, " instances[component.fullyQualifiedTypeName] = component")?; + writeln!(output, " }}")?; + writeln!(output)?; writeln!(output, " fun registerAll() {{")?; for component in manifest.components() { writeln!( output, - " com.dropbear.ecs.registerKotlinComponentType({fqcn}, {simple}, null, null)", - fqcn = format!("\"{}\"", component.fqcn()), - simple = format!("\"{}\"", component.simple_name()), + " registerOne({simple}())", + simple = component.simple_name(), )?; } writeln!(output, " }}")?; + writeln!(output)?; + writeln!(output, " fun updateKotlinComponent(fqcn: String, entityId: Long, dt: Double) {{")?; + writeln!(output, " val instance = instances[fqcn] ?: return")?; + writeln!(output, " val engine = com.dropbear.DropbearEngine(com.dropbear.DropbearEngine.native)")?; + writeln!(output, " instance.setCurrentEntity(entityId)")?; + writeln!(output, " instance.updateComponent(engine, dt)")?; + writeln!(output, " instance.clearCurrentEntity()")?; + writeln!(output, " }}")?; + writeln!(output)?; + writeln!(output, " fun inspectKotlinComponent(fqcn: String) {{")?; + writeln!(output, " val instance = instances[fqcn] ?: return")?; + writeln!(output, " val engine = com.dropbear.DropbearEngine(com.dropbear.DropbearEngine.native)")?; + writeln!(output, " instance.inspect(engine)")?; + writeln!(output, " }}")?; writeln!(output, "}}")?; // ADD CNAME FUNCTIONS HERE @@ -724,6 +744,20 @@ fun dropbear_destroy_all(): Int {{ return ScriptManager.destroyAll() }} +@CName("dropbear_update_kotlin_component") +fun dropbear_update_kotlin_component(fqcn: String?, entityId: ULong, dt: Double): Int {{ + if (fqcn == null) return -1 + ComponentManager.updateKotlinComponent(fqcn, entityId.toLong(), dt) + return 0 +}} + +@CName("dropbear_inspect_kotlin_component") +fun dropbear_inspect_kotlin_component(fqcn: String?): Int {{ + if (fqcn == null) return -1 + ComponentManager.inspectKotlinComponent(fqcn) + return 0 +}} + @CName("dropbear_get_last_error") fun dropbear_get_last_error(): String? {{ return com.dropbear.lastErrorMessage @@ -18,6 +18,7 @@ tokio.workspace = true chrono.workspace = true env_logger.workspace = true colored.workspace = true +jni.workspace = true parking_lot.workspace = true postcard.workspace = true ron.workspace = true @@ -35,6 +35,7 @@ use log::error; use std::collections::HashMap; use std::path::PathBuf; use std::sync::Arc; +use jni::objects::JValue; use wgpu::SurfaceConfiguration; use wgpu::util::DeviceExt; use winit::window::Fullscreen; @@ -137,7 +138,6 @@ pub struct PlayMode { instance_buffer_cache: HashMap<u64, ResizableBuffer<InstanceRaw>>, animated_instance_buffers: HashMap<Entity, ResizableBuffer<InstanceRaw>>, sky_pipeline: Option<SkyPipeline>, - camera_bind_group: Option<wgpu::BindGroup>, default_skinning_buffer: Option<wgpu::Buffer>, default_morph_deltas_buffer: Option<wgpu::Buffer>, default_morph_weights_buffer: Option<wgpu::Buffer>, @@ -221,7 +221,7 @@ impl PlayMode { event_collector, display_settings: DisplaySettings { window_mode: WindowMode::Windowed, - maintain_aspect_ratio: true, + maintain_aspect_ratio: false, vsync: false, last_window_mode: WindowMode::BorderlessFullscreen, last_vsync: true, @@ -229,7 +229,6 @@ impl PlayMode { }, kino: None, sky_pipeline: None, - camera_bind_group: None, default_skinning_buffer: None, default_morph_deltas_buffer: None, default_morph_weights_buffer: None, @@ -257,7 +256,6 @@ impl PlayMode { self.shader_globals = None; self.kino = None; self.sky_pipeline = None; - self.camera_bind_group = None; self.default_skinning_buffer = None; self.default_morph_deltas_buffer = None; self.default_morph_weights_buffer = None; @@ -279,7 +277,6 @@ impl PlayMode { Some("runtime shader globals"), )); - let mut camera_bind_group = None; let mut pending_sky_pipeline = None; if self.default_skinning_buffer.is_none() { @@ -399,17 +396,6 @@ impl PlayMode { if let Some(camera_entity) = self.active_camera { if let Ok(camera) = self.world.query_one::<&Camera>(camera_entity).get() { - camera_bind_group = Some(graphics.device.create_bind_group( - &wgpu::BindGroupDescriptor { - label: Some("runtime camera bind group"), - layout: &graphics.layouts.camera_bind_group_layout, - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: camera.buffer().as_entire_binding(), - }], - }, - )); - match sky_texture_result { Ok(sky_texture) => { pending_sky_pipeline = Some(SkyPipeline::new( @@ -442,7 +428,6 @@ impl PlayMode { error!("Unable to create bind groups without an active camera"); } - self.camera_bind_group = camera_bind_group; if let Some(sky_pipeline) = pending_sky_pipeline { self.sky_pipeline = Some(sky_pipeline); } @@ -498,6 +483,46 @@ impl PlayMode { log::debug!("Loaded scripts successfully!"); } + // todo: this wont work for native contexts. + if let Some(registry) = Arc::get_mut(&mut self.component_registry) { + registry.drain_kotlin_queue(); + + if let Some(jvm) = eucalyptus_core::scripting::jni::GLOBAL_JVM.get() { + let jvm = jvm.clone(); + registry.set_kotlin_update_fn(move |fqcn: &str, entity_id: u64, dt: f32| { + let Ok(mut env) = jvm.attach_current_thread() else { + log::warn!("kotlin_update_fn: failed to attach JVM thread"); + return; + }; + let Ok(fqcn_jstr) = env.new_string(fqcn) else { return }; + let Ok(class) = env.find_class("com/dropbear/decl/ComponentManager") else { + log::warn!( + "kotlin_update_fn: ComponentManager class not found - has the project JAR been loaded?" + ); + return; + }; + if let Err(e) = env.call_static_method( + class, + "updateKotlinComponent", + "(Ljava/lang/String;JD)V", + &[ + JValue::Object(&fqcn_jstr), + JValue::Long(entity_id as i64), + JValue::Double(dt as f64), + ], + ) { + log::warn!( + "kotlin_update_fn: updateKotlinComponent('{}', {}) failed: {:?}", + fqcn, entity_id, e + ); + let _ = env.exception_clear(); + } + }); + } + } else { + log::warn!("drain_kotlin_queue: could not get exclusive access to component_registry; Kotlin component descriptors were not registered"); + } + self.scripts_ready = true; log::debug!("Scripts reloaded successfully!"); } @@ -630,11 +630,6 @@ impl Scene for PlayMode { log_once::debug_once!("Camera ready"); log_once::debug_once!("Camera currently being viewed: {}", camera.label); - let Some(camera_bind_group) = self.camera_bind_group.as_ref() else { - log_once::warn_once!("Camera bind group not ready"); - return; - }; - // clear viewport render pass { puffin::profile_scope!("Clearing viewport"); @@ -879,7 +874,7 @@ impl Scene for PlayMode { if !light.component.visible { continue; } - render_pass.draw_light_model(&model, camera_bind_group, &light.bind_group); + render_pass.draw_light_model(&model, &camera.bind_group, &light.bind_group); } } } else { @@ -2,7 +2,6 @@ package com.dropbear import com.dropbear.ecs.Component import com.dropbear.ecs.ComponentType -import com.dropbear.physics.RigidBody /** * A reference to an ECS Entity stored inside the dropbear engine. @@ -1,10 +1,12 @@ package com.dropbear.components.camera +import com.dropbear.DropbearEngine import com.dropbear.ecs.NativeComponent import com.dropbear.math.Vector3d import com.dropbear.physics.ColliderShape import com.dropbear.physics.Physics import com.dropbear.ui.Inspect +import com.dropbear.ui.UIInstructionSet import com.dropbear.ui.WidgetType /** @@ -55,6 +57,11 @@ class SpringyCameraController: NativeComponent( return playerPos + (dir * currentDistance) } - override fun inspect() {} - override fun updateComponent() {} + override fun inspect(engine: DropbearEngine): UIInstructionSet? { + return null + } + + override fun updateComponent(engine: DropbearEngine, deltaTime: Double) { + + } } @@ -1,22 +1,6 @@ package com.dropbear.ecs -import com.dropbear.EntityId - -/** - * Marks a class as a user-defined ECS component for the dropbear engine. - * - * Annotated classes must extend [NativeComponent]. The `magna-carta` tool scans for this annotation - * at build time to generate a [ComponentManager] that registers each component type with the - * native engine's ComponentRegistry via [registerKotlinComponentType]. - * - * Retention is [AnnotationRetention.RUNTIME] so JNI reflection can discover types at startup - * without relying on build-time scanning alone. - */ -@Target(AnnotationTarget.CLASS) -@Retention(AnnotationRetention.RUNTIME) -annotation class EcsComponent - -internal expect fun registerKotlinComponentType( +expect fun registerKotlinComponentType( fqcn: String, typeName: String?, category: String?, @@ -28,87 +12,4 @@ internal expect fun registerKotlinComponentType( * * Extend [NativeComponent] or [ExternalComponent], not this class directly. */ -sealed class Component - -/** - * States a Kotlin-defined ECS component. - * - * Annotate subclasses with [@EcsComponent][EcsComponent] so that `magna-carta` can discover them - * at build time and emit a `ComponentManager` that calls [register] for every discovered type. - * - * @param fullyQualifiedTypeName The fully qualified class name used as the stable key inside the - * Rust ComponentRegistry (e.g. `"com.example.PlayerHealth"`). Prefer using the literal string - * directly so the value survives code shrinking / obfuscation. - * @param typeName Human-readable short name shown in the editor (e.g. `"PlayerHealth"`). - * @param category Optional grouping shown in the "Add Component" picker (e.g. `"Gameplay"`). - * @param description Optional one-line description shown as a tooltip in the editor. - */ -abstract class NativeComponent( - val fullyQualifiedTypeName: String, - val typeName: String, - val category: String? = null, - val description: String? = null, -): Component() { - /** - * Registers this component type with the engine's ComponentRegistry. - * - * This is called once per type at startup - */ - fun register() { - registerKotlinComponentType(fullyQualifiedTypeName, typeName, category, description) - } - - /** - * Renders the inspector UI for this component in the editor. - * - * Called by the editor for every entity that has this component type attached. - */ - abstract fun inspect() - - /** - * Updates the component for all entities that contain this component. - */ - abstract fun updateComponent() -} - -/** - * Proxy for an ECS component that is defined outside Kotlin, typically in a Rust crate or - * another native shared library, and registered with the engine's ComponentRegistry under a - * known [fullyQualifiedTypeName]. - * - * Use this when you need to query whether an entity carries a non-Kotlin component, without - * owning or duplicating its data on the Kotlin side. - * - * @param fullyQualifiedTypeName The fully qualified type name that the external component was - * registered under in the ComponentRegistry (e.g. `"eucalyptus_core::components::RigidBody"`). - */ -abstract class ExternalComponent( - val fullyQualifiedTypeName: String, -): Component() { -} - -/** - * An interface that all components must include to be queryable. - * - * Basically looks like this: - * ``` - * companion object : ComponentType<T> { - * override fun get(entityId: EntityId): T? { - * // chuck in whatever validation stuff you want idk as long as it works... - * } - * } - * ``` - * - * @param T The component that can be queried. It must extend the [Component] class. - */ -interface ComponentType<T : Component> { - /** - * Uses the FFI to check if the entity is a component. - * - * Since most components are technically just classes with a ctor containing the parentEntity's id, - * and getters and setters, it just needs to query the world. - * - * @return The components type ([T]) if it exists or `null` if not - */ - fun get(entityId: EntityId): T? -} +sealed class Component @@ -0,0 +1,29 @@ +package com.dropbear.ecs + +import com.dropbear.EntityId + +/** + * An interface that all components must include to be queryable. + * + * Basically looks like this: + * ``` + * companion object : ComponentType<T> { + * override fun get(entityId: EntityId): T? { + * // chuck in whatever validation stuff you want idk as long as it works... + * } + * } + * ``` + * + * @param T The component that can be queried. It must extend the [Component] class. + */ +interface ComponentType<T : Component> { + /** + * Uses the FFI to check if the entity is a component. + * + * Since most components are technically just classes with a ctor containing the parentEntity's id, + * and getters and setters, it just needs to query the world. + * + * @return The components type ([T]) if it exists or `null` if not + */ + fun get(entityId: EntityId): T? +} @@ -0,0 +1,15 @@ +package com.dropbear.ecs + +/** + * Marks a class as a user-defined ECS component for the dropbear engine. + * + * Annotated classes must extend [NativeComponent]. The `magna-carta` tool scans for this annotation + * at build time to generate a [ComponentManager] that registers each component type with the + * native engine's ComponentRegistry via [registerKotlinComponentType]. + * + * Retention is [AnnotationRetention.RUNTIME] so JNI reflection can discover types at startup + * without relying on build-time scanning alone. + */ +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +annotation class EcsComponent @@ -0,0 +1,17 @@ +package com.dropbear.ecs + +/** + * Proxy for an ECS component that is defined outside Kotlin, typically in a Rust crate or + * another native shared library, and registered with the engine's ComponentRegistry under a + * known [fullyQualifiedTypeName]. + * + * Use this when you need to query whether an entity carries a non-Kotlin component, without + * owning or duplicating its data on the Kotlin side. + * + * @param fullyQualifiedTypeName The fully qualified type name that the external component was + * registered under in the ComponentRegistry (e.g. `"eucalyptus_core::components::RigidBody"`). + */ +abstract class ExternalComponent( + val fullyQualifiedTypeName: String, +): Component() { +} @@ -0,0 +1,71 @@ +package com.dropbear.ecs + +import com.dropbear.DropbearEngine +import com.dropbear.EntityId +import com.dropbear.EntityRef +import com.dropbear.ui.UIInstructionSet + +/** + * States a Kotlin-defined ECS component. + * + * Annotate subclasses with [@EcsComponent][EcsComponent] so that `magna-carta` can discover them + * at build time and emit a `ComponentManager` that calls [register] for every discovered type. + * + * @param fullyQualifiedTypeName The fully qualified class name used as the stable key inside the + * Rust ComponentRegistry (e.g. `"com.example.PlayerHealth"`). Prefer using the literal string + * directly so the value survives code shrinking / obfuscation. + * @param typeName Human-readable short name shown in the editor (e.g. `"PlayerHealth"`). + * @param category Optional grouping shown in the "Add Component" picker (e.g. `"Gameplay"`). + * @param description Optional one-line description shown as a tooltip in the editor. + */ +abstract class NativeComponent( + val fullyQualifiedTypeName: String, + val typeName: String, + val category: String? = null, + val description: String? = null, +): Component() { + /** + * The entity currently being updated. + * + * Set by the engine immediately before [updateComponent] is called and cleared + * immediately after. It is `null` outside of an active update call. + */ + var currentEntity: EntityRef? = null + private set + + /** + * Registers this component type with the engine's ComponentRegistry. + * + * This is called once per type at startup by the generated [ComponentManager]. + */ + fun register() { + registerKotlinComponentType(fullyQualifiedTypeName, typeName, category, description) + } + + /** Internal: sets [currentEntity] before calling [updateComponent]. */ + fun setCurrentEntity(entity: Long) { + currentEntity = EntityRef(EntityId(entity)) + } + + /** Internal: clears [currentEntity] after [updateComponent] returns. */ + fun clearCurrentEntity() { + currentEntity = null + } + + /** + * Renders the inspector UI for this component in the editor. + * + * Called by the editor for every entity that has this component type attached. + */ + abstract fun inspect(engine: DropbearEngine): UIInstructionSet? + + /** + * Called once per frame for every entity that holds this component type. + * + * [currentEntity] is set to the entity being processed for the duration of this call. + * + * @param engine The engine facade for queries and mutations. + * @param deltaTime Seconds elapsed since the last frame. + */ + abstract fun updateComponent(engine: DropbearEngine, deltaTime: Double) +} @@ -5,8 +5,6 @@ import com.dropbear.ecs.ComponentType import com.dropbear.ecs.ExternalComponent import com.dropbear.math.Quaterniond import com.dropbear.math.Vector3d -import com.dropbear.math.degreesToRadians -import kotlin.math.cos class KinematicCharacterController( val entity: EntityId, @@ -4,7 +4,7 @@ import com.dropbear.DropbearEngine import com.dropbear.EntityId import com.dropbear.components.ComponentNative -internal actual fun registerKotlinComponentType( +actual fun registerKotlinComponentType( fqcn: String, typeName: String?, category: String?, @@ -43,7 +43,7 @@ class SystemManager( typed.clearCurrentEntity() typedInstances.add(typed) Logger.trace("Instantiated system: ${typed.javaClass.name} for tag: $tag") - } catch (ex: Exception) { + } catch (ex: Throwable) { Logger.error("Failed to instantiate system ${typed.javaClass.name}: ${ex.message}") } } @@ -64,13 +64,13 @@ class SystemManager( system.attachEngine(engine) system.clearCurrentEntity() system.destroy(engine) - } catch (ex: Exception) { + } catch (ex: Throwable) { Logger.error("Failed to destroy system ${system.javaClass.name} for tag $tag: ${ex.message}") } finally { try { system.clearCurrentEntity() - } catch (_: Exception) { - // ignore + } catch (e: Throwable) { + Logger.warn("Failed to clear current entity for system ${system.javaClass.name} for tag $tag: ${e.message}") } } } @@ -107,13 +107,13 @@ class SystemManager( system.attachEngine(engine) system.clearCurrentEntity() system.load(engine) - } catch (ex: Exception) { + } catch (ex: Throwable) { Logger.error("Failed to load system ${system.javaClass.name} for tag $tag: ${ex.message}") } finally { try { system.clearCurrentEntity() - } catch (_: Exception) { - // ignore + } catch (e: Throwable) { + Logger.warn("Failed to clear current entity for system ${system.javaClass.name} for tag $tag: ${e.message}") } } } @@ -126,13 +126,13 @@ class SystemManager( system.attachEngine(engine) system.clearCurrentEntity() system.load(engine) - } catch (ex: Exception) { + } catch (ex: Throwable) { Logger.error("Failed to load system ${system.javaClass.name} for tag $tag: ${ex.message}") } finally { try { system.clearCurrentEntity() - } catch (_: Exception) { - // ignore + } catch (e: Throwable) { + Logger.warn("Failed to clear current entity for system ${system.javaClass.name} for tag $tag: ${e.message}") } } } @@ -156,13 +156,13 @@ class SystemManager( system.attachEngine(engine) system.setCurrentEntity(entityId) system.load(engine) - } catch (ex: Exception) { + } catch (ex: Throwable) { Logger.error("Failed to load system ${system.javaClass.name} for entity $entityId: ${ex.message}") } finally { try { system.clearCurrentEntity() - } catch (_: Exception) { - // ignore + } catch (e: Throwable) { + Logger.warn("Failed to clear current entity for system ${system.javaClass.name} for tag $tag: ${e.message}") } } } @@ -214,8 +214,8 @@ class SystemManager( system.attachEngine(engine) system.setCurrentEntity(entityId) system.update(engine, deltaTime) - } catch (ex: Exception) { - Logger.error("Failed to update system ${system.javaClass.name} for entity $entityId: ${ex.message}") + } catch (e: Throwable) { + Logger.error("Failed to update system ${system.javaClass.name} for entity $entityId: ${e.message}") } } } @@ -244,8 +244,8 @@ class SystemManager( system.attachEngine(engine) system.setCurrentEntity(entityId) system.physicsUpdate(engine, deltaTime) - } catch (ex: Exception) { - Logger.error("Failed to physics update system ${system.javaClass.name} for entity $entityId: ${ex.message}") + } catch (e: Throwable) { + Logger.error("Failed to physics update system ${system.javaClass.name} for entity $entityId: ${e.message}") } } } @@ -263,7 +263,7 @@ class SystemManager( system.attachEngine(engine) system.setCurrentEntity(entityId) system.collisionEvent(engine, event) - } catch (ex: Exception) { + } catch (ex: Throwable) { Logger.error("Failed to deliver collision event to ${system.javaClass.name} for entity $entityId: ${ex.message}") } } @@ -281,7 +281,7 @@ class SystemManager( system.attachEngine(engine) system.setCurrentEntity(entityId) system.collisionForceEvent(engine, event) - } catch (ex: Exception) { + } catch (ex: Throwable) { Logger.error("Failed to deliver contact-force event to ${system.javaClass.name} for entity $entityId: ${ex.message}") } } @@ -297,7 +297,7 @@ class SystemManager( system.attachEngine(engine) system.clearCurrentEntity() system.update(engine, deltaTime) - } catch (ex: Exception) { + } catch (ex: Throwable) { Logger.error("Failed to update system ${system.javaClass.name} for tag $tag: ${ex.message}") } } @@ -309,7 +309,7 @@ class SystemManager( system.attachEngine(engine) system.clearCurrentEntity() system.physicsUpdate(engine, deltaTime) - } catch (ex: Exception) { + } catch (ex: Throwable) { Logger.error("Failed to physics update system ${system.javaClass.name} for tag $tag: ${ex.message}") } } @@ -3,7 +3,7 @@ package com.dropbear.ecs import com.dropbear.EntityId -internal actual fun registerKotlinComponentType( +actual fun registerKotlinComponentType( fqcn: String, typeName: String?, category: String?,