tirbofish/dropbear · commit
0e5688b8b82c6becaf0a2eb99965ae6bd59f7604
wip: component should support kotlin
wip: debug drawing for camera splines
Signature present but could not be verified.
Unverified
@@ -0,0 +1,59 @@ +use std::sync::Arc; +use wgpu::{RenderPipeline, RenderPipelineDescriptor, VertexState}; +use crate::graphics::SharedGraphicsContext; + +pub struct DebugLine { + +} + +pub struct DebugDraw { + pipeline: Arc<DebugDrawPipeline>, +} + +impl DebugDraw { + pub fn draw_line(&self) { + + } +} + +pub struct DebugDrawPipeline { + pipeline: RenderPipeline, +} + +impl DebugDrawPipeline { + pub fn new(graphics: Arc<SharedGraphicsContext>) -> Self { + let pipeline_layout = graphics.device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("debug draw pipeline layout"), + bind_group_layouts: &[], + push_constant_ranges: &[], + }); + + let pipeline = graphics.device.create_render_pipeline(&RenderPipelineDescriptor { + label: Some("debug draw render pipeline"), + layout: Some(&pipeline_layout), + vertex: VertexState { + module: &(), + entry_point: None, + compilation_options: Default::default(), + buffers: &[], + }, + primitive: Default::default(), + depth_stencil: None, + multisample: Default::default(), + fragment: None, + multiview: None, + cache: None, + }); + } + + pub fn draw(&self, graphics: Arc<SharedGraphicsContext>) { + + } +} + +#[repr(C)] +#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] +pub struct DebugVertex { + pub position: [f32; 3], + pub color: [f32; 4], +} @@ -24,6 +24,7 @@ pub mod texture; pub mod utils; pub mod multisampling; pub mod billboarding; +mod debug; features! { pub mod feature_list { @@ -0,0 +1,29 @@ +// basic.wgsl - shader used for drawing debug stuff like lines and shii + +struct CameraUniform { + view_proj: mat4x4<f32>, +} +@group(0) @binding(0) var<uniform> camera: CameraUniform; + +struct VertexIn { + @location(0) position: vec3<f32>, + @location(1) color: vec4<f32>, +} + +struct VertexOut { + @builtin(position) clip_position: vec4<f32>, + @location(0) color: vec4<f32>, +} + +@vertex +fn vs_main(in: VertexIn) -> VertexOut { + var out: VertexOut; + out.clip_position = camera.view_proj * vec4<f32>(in.position, 1.0); + out.color = in.color; + return out; +} + +@fragment +fn fs_main(in: VertexOut) -> @location(0) vec4<f32> { + return in.color; +} @@ -29,27 +29,27 @@ pub const DRAGGED_ASSET_ID: &str = "dragged_asset_reference"; pub struct ComponentRegistry { /// Maps TypeId to ComponentDescriptor for quick lookups - descriptors: HashMap<TypeId, ComponentDescriptor>, + descriptors: HashMap<LanguageTypeId, ComponentDescriptor>, /// Maps fully qualified type name to TypeId for lookups by string - fqtn_to_type: HashMap<String, TypeId>, + fqtn_to_type: HashMap<String, LanguageTypeId>, /// Maps category name to list of TypeIds in that category - categories: HashMap<String, Vec<TypeId>>, + categories: HashMap<String, Vec<LanguageTypeId>>, /// Maps serialized TypeId to component TypeId - serialized_to_component: HashMap<TypeId, TypeId>, + serialized_to_component: HashMap<LanguageTypeId, LanguageTypeId>, /// Functions that extract and serialize components from entities - extractors: HashMap<TypeId, ExtractorFn>, + extractors: HashMap<LanguageTypeId, ExtractorFn>, /// Functions that allow for the entity to load. - loaders: HashMap<TypeId, LoaderFn>, + loaders: HashMap<LanguageTypeId, LoaderFn>, /// Functions that update the contents of the component. - updaters: HashMap<TypeId, UpdateFn>, + updaters: HashMap<LanguageTypeId, UpdateFn>, /// Functions that create default serialized components. - defaults: HashMap<TypeId, DefaultFn>, + defaults: HashMap<LanguageTypeId, DefaultFn>, /// Functions that remove components by type. - removers: HashMap<TypeId, RemoveFn>, + removers: HashMap<LanguageTypeId, RemoveFn>, /// Functions that find entities with a component. - finders: HashMap<TypeId, FindFn>, + finders: HashMap<LanguageTypeId, FindFn>, /// Allows for inspecting the component in the Resource Inspector dock. - inspectors: HashMap<TypeId, InspectFn>, + inspectors: HashMap<LanguageTypeId, InspectFn>, } /// Describes a handy little future for [`Component::init`], which deals with initialising a component from its serialized form. @@ -93,6 +93,12 @@ type InspectFn = Box< // fn inspect(&mut self, world: &hecs::World, entity: hecs::Entity, ui: &mut egui::Ui, graphics: Arc<SharedGraphicsContext>); +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum LanguageTypeId { + Rust(TypeId), + Kotlin(String), +} + impl ComponentRegistry { pub fn new() -> Self { Self { @@ -117,23 +123,23 @@ impl ComponentRegistry { T::SerializedForm: 'static + Default, T::RequiredComponentTypes: Send + Sync, { - let type_id = TypeId::of::<T>(); - let serialized_type_id = TypeId::of::<T::SerializedForm>(); + let type_id = LanguageTypeId::Rust(TypeId::of::<T>()); + let serialized_type_id = LanguageTypeId::Rust(TypeId::of::<T::SerializedForm>()); let desc = T::descriptor(); - self.fqtn_to_type.insert(desc.fqtn.clone(), type_id); + self.fqtn_to_type.insert(desc.fqtn.clone(), type_id.clone()); if let Some(ref cat) = desc.category { self.categories .entry(cat.clone()) .or_default() - .push(type_id); + .push(type_id.clone()); } - self.descriptors.insert(type_id, desc); + self.descriptors.insert(type_id.clone(), desc); self.serialized_to_component - .insert(serialized_type_id, type_id); + .insert(serialized_type_id.clone(), type_id.clone()); self.extractors.insert( - type_id, + type_id.clone(), Box::new(|world, entity| { let Ok(c) = world.get::<&T>(entity) else { return None; @@ -162,17 +168,17 @@ impl ComponentRegistry { ); self.defaults - .insert(type_id, Box::new(|| Box::new(T::SerializedForm::default()))); + .insert(type_id.clone(), Box::new(|| Box::new(T::SerializedForm::default()))); self.removers.insert( - type_id, + type_id.clone(), Box::new(|world, entity| { let _ = world.remove_one::<T>(entity); }), ); self.finders.insert( - type_id, + type_id.clone(), Box::new(|world| { world .query::<(hecs::Entity, &T)>() @@ -184,7 +190,7 @@ impl ComponentRegistry { let disabled_flags = T::descriptor().disabled_flags; self.updaters.insert( - type_id, + type_id.clone(), Box::new(move |world, physics, dt, graphics| { let world_ptr = world as *mut hecs::World; // safe assuming world is kept at the DropbearAppBuilder application level (lifetime) let mut query = world.query::<(hecs::Entity, &mut T)>(); @@ -220,7 +226,7 @@ impl ComponentRegistry { /// Get descriptor for a specific component type pub fn get_descriptor<T: Component + 'static>(&self) -> Option<&ComponentDescriptor> { - self.descriptors.get(&TypeId::of::<T>()) + self.descriptors.get(&LanguageTypeId::Rust(TypeId::of::<T>())) } /// Get descriptor by fully qualified type name @@ -255,12 +261,12 @@ impl ComponentRegistry { /// Check if a component type is registered pub fn is_registered<T: Component + 'static>(&self) -> bool { - self.descriptors.contains_key(&TypeId::of::<T>()) + self.descriptors.contains_key(&LanguageTypeId::Rust(TypeId::of::<T>())) } - /// Get the TypeId for a component by its fully qualified type name - pub fn get_type_id(&self, fqtn: &str) -> Option<TypeId> { - self.fqtn_to_type.get(fqtn).copied() + /// Get the LanguageTypeId for a component by its fully qualified type name + pub fn get_type_id(&self, fqtn: &str) -> Option<LanguageTypeId> { + self.fqtn_to_type.get(fqtn).cloned() } /// Get count of registered components @@ -272,7 +278,7 @@ impl ComponentRegistry { pub fn iter_available_components(&self) -> impl Iterator<Item = (u64, &ComponentDescriptor)> { self.descriptors .iter() - .map(|(type_id, desc)| (Self::numeric_id(*type_id), desc)) + .map(|(type_id, desc)| (Self::numeric_id(type_id), desc)) } /// Extract all registered components from an entity @@ -293,7 +299,7 @@ impl ComponentRegistry { world: &hecs::World, entity: hecs::Entity, ) -> Option<Box<dyn SerializedComponent>> { - let type_id = TypeId::of::<T>(); + let type_id = LanguageTypeId::Rust(TypeId::of::<T>()); self.extractors .get(&type_id) .and_then(|extractor| extractor(world, entity)) @@ -323,10 +329,9 @@ impl ComponentRegistry { /// Gets the numeric id for a serialized component instance. pub fn id_for_component(&self, component: &dyn SerializedComponent) -> Option<u64> { - let serialized_type_id = component.as_any().type_id(); + let serialized_type_id = LanguageTypeId::Rust(component.as_any().type_id()); self.serialized_to_component .get(&serialized_type_id) - .copied() .map(Self::numeric_id) } @@ -366,7 +371,7 @@ impl ComponentRegistry { serialized: &'a dyn SerializedComponent, graphics: Arc<SharedGraphicsContext>, ) -> Option<LoaderFuture<'a>> { - let serialized_type_id = serialized.as_any().type_id(); + let serialized_type_id = LanguageTypeId::Rust(serialized.as_any().type_id()); self.loaders .get(&serialized_type_id) .map(|loader| loader(serialized, graphics)) @@ -403,7 +408,7 @@ impl ComponentRegistry { let type_ids = world .entity(entity) - .map(|e| e.component_types().collect::<Vec<_>>()) + .map(|e| e.component_types().map(LanguageTypeId::Rust).collect::<Vec<_>>()) .unwrap_or_default(); for type_id in type_ids { @@ -421,22 +426,18 @@ impl ComponentRegistry { } } - fn numeric_id(type_id: TypeId) -> u64 { - use std::hash::{Hash, Hasher}; + fn numeric_id(type_id: &LanguageTypeId) -> u64 { let mut hasher = std::collections::hash_map::DefaultHasher::new(); type_id.hash(&mut hasher); - let mut id = hasher.finish(); - if id == 0 { - id = 1; - } - id + let id = hasher.finish(); + if id == 0 { 1 } else { id } } - fn type_id_from_numeric_id(&self, id: u64) -> Option<TypeId> { + fn type_id_from_numeric_id(&self, id: u64) -> Option<LanguageTypeId> { self.descriptors .keys() - .copied() - .find(|type_id| Self::numeric_id(*type_id) == id) + .find(|&type_id| Self::numeric_id(type_id) == id) + .cloned() } } @@ -1,5 +1,3 @@ -extern crate core; - pub mod animation; pub mod asset; pub mod billboard; @@ -30,6 +28,7 @@ pub mod ser; pub mod metadata; pub mod resource; pub mod uuid; +pub mod plugin; pub use dropbear_macro as macros; @@ -62,6 +62,27 @@ pub struct ManifestItem { file_path: PathBuf, } +/// Represents a single user-defined Kotlin component discovered via `@EcsComponent`. +#[derive(Debug, Clone)] +pub struct ComponentManifestItem { + /// Fully qualified class name, e.g. `com.game.PlayerHealth` + fqcn: String, + /// Simple name of the class, e.g. `PlayerHealth` + simple_name: String, + /// Path to the source file + file_path: PathBuf, +} + +impl ComponentManifestItem { + pub fn new(fqcn: String, simple_name: String, file_path: PathBuf) -> Self { + Self { fqcn, simple_name, file_path } + } + + pub fn fqcn(&self) -> &str { &self.fqcn } + pub fn simple_name(&self) -> &str { &self.simple_name } + pub fn file_path(&self) -> &PathBuf { &self.file_path } +} + impl ManifestItem { /// Creates a new manifest item from an fqcn (fully qualified class name), simple name, tags /// and file_path. @@ -10,7 +10,10 @@ import com.dropbear.math.Vector3d */ class Camera( internal val entity: EntityId, -): Component(entity, "Camera3D") { +): Component( + fullyQualifiedTypeName = "dropbear_engine::camera::Camera", + typeName = "Camera3D", +) { /** * The eye/position of the camera. */ @@ -12,7 +12,10 @@ import com.dropbear.math.Vector3i * * The entity must include the `CustomProperties` component in the editor. */ -class CustomProperties(val id: EntityId): Component(id, "CustomProperties") { +class CustomProperties(val id: EntityId): Component( + fullyQualifiedTypeName = "eucalyptus_core::properties::CustomProperties", + typeName = "CustomProperties", +) { /** * Fetches the property of the ModelProperty component on the entity. * @@ -10,7 +10,10 @@ import com.dropbear.math.Transform * * This entity must contain the `EntityTransform` component to be queryable. */ -class EntityTransform(val id: EntityId): Component(id, "EntityTransform") { +class EntityTransform(val id: EntityId): Component( + fullyQualifiedTypeName = "dropbear_engine::entity::EntityTransform", + typeName = "EntityTransform", +) { /** * The local transform. * @@ -15,7 +15,10 @@ import com.dropbear.getEntityLabel */ class Label( internal val entity: EntityId -): Component(entity, "Label") { +): Component( + fullyQualifiedTypeName = "eucalyptus_core::states::Label", + typeName = "Label", +) { val name: String get() = EntityRef.getEntityLabel(entity) @@ -11,7 +11,10 @@ import com.dropbear.ecs.ComponentType * * It must require the `MeshRenderer` component in the editor to be queryable. */ -class MeshRenderer(val id: EntityId) : Component(id, "MeshRenderer") { +class MeshRenderer(val id: EntityId) : Component( + fullyQualifiedTypeName = "dropbear_engine::entity::MeshRenderer", + typeName = "MeshRenderer", +) { /** * The active model currently assigned to this entity. @@ -3,24 +3,47 @@ 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 [Component]. The magna-carta tool scans for this annotation at + * build time to generate a [ComponentManager] that registers the component type with the native + * engine and dispatches lifecycle callbacks from Rust. + * + * 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 +/** * A custom property that *can* be attached to an entity in the dropbear ECS system. * * # Note * A [ComponentType] `companion object` is required to be implemented to be queryable. Take a look at * the documentation for the object. * - * @property parentEntity The [EntityId] to which this component is attached. - * @property typeName The string name of the component type as defined in Rust. The name is - * found as the type name, such as `component_registry.register_with_default::<Camera3D>();`, - * where the type name would be `"Camera3D"`. + * */ abstract class Component( - private val parentEntity: EntityId, - internal val typeName: String, + val fullyQualifiedTypeName: String?, + val typeName: String?, + val category: String?, + val description: String?, ) { - override fun toString(): String { - return "Component(parentEntity: ${this.parentEntity}, typeName: $typeName)" - } + /** + * Ran on a component being attached to an entity. + */ + abstract fun onAttach() + + /** + * Ran on a component being updated. + */ + abstract fun update() + + /** + * Ran on a component being detached from an entity. + */ + abstract fun onDetach() } /** @@ -0,0 +1,40 @@ +package com.dropbear.components; + +import com.dropbear.EucalyptusCoreLoader; + +/** + * JNI bridge for Kotlin-defined ECS components. + * + * <p>The Rust side implements each {@code native} method. The component lifecycle + * (onAttach / update / onDetach) is driven by Rust calling back into the JVM's + * ComponentManager (generated by magna-carta for JVM targets); this class covers + * the Kotlin → Rust direction only. + */ +public class ComponentNative { + static { + new EucalyptusCoreLoader().ensureLoaded(); + } + + /** + * Returns {@code true} if the entity identified by {@code entityId} has a + * {@code KotlinComponent} entry keyed by {@code fqcn} in the Rust world. + */ + public static native boolean hasKotlinComponent(long worldPtr, long entityId, String fqcn); + + /** + * Registers a Kotlin component type with Rust's {@code ComponentRegistry} so that it is + * serialisable, inspectable in the editor, and dispatchable. + * + * <p>Call this once per type at startup, typically from the generated + * {@code ComponentManager.registerAll()}. + * + * @param category may be null or empty + * @param description may be null or empty + */ + public static native void registerKotlinComponent( + long worldPtr, + String fqcn, + String typeName, + String category, + String description); +} @@ -0,0 +1,21 @@ +package com.dropbear.ecs + +import com.dropbear.DropbearEngine +import com.dropbear.EntityId +import com.dropbear.components.ComponentNative + +internal actual fun hasKotlinComponent(entityId: EntityId, fqcn: String): Boolean = + ComponentNative.hasKotlinComponent(DropbearEngine.native.worldHandle, entityId.raw, fqcn) + +internal actual fun registerKotlinComponentType( + fqcn: String, + typeName: String?, + category: String?, + description: String?, +) = ComponentNative.registerKotlinComponent( + DropbearEngine.native.worldHandle, + fqcn, + typeName ?: "", + category ?: "", + description ?: "", +) @@ -0,0 +1,22 @@ +@file:OptIn(ExperimentalForeignApi::class) + +package com.dropbear.ecs + +import com.dropbear.DropbearEngine +import com.dropbear.EntityId +import com.dropbear.ffi.generated.* +import kotlinx.cinterop.* + +internal actual fun hasKotlinComponent(entityId: EntityId, fqcn: String): Boolean = memScoped { + val world = DropbearEngine.native.worldHandle ?: return@memScoped false + dropbear_kotlin_component_exists(world, entityId.raw.toULong(), fqcn) +} + +internal actual fun registerKotlinComponentType( + fqcn: String, + typeName: String?, + category: String?, + description: String?, +) { + dropbear_register_kotlin_component(fqcn, typeName, category, description) +}