tirbofish/dropbear · diff
implemented parent and child api, fixes #65
Signature present but could not be verified.
Unverified
@@ -1,20 +0,0 @@ -<component name="ProjectRunConfigurationManager"> - <configuration default="false" name="Build eucalyptus-editor" type="CargoCommandRunConfiguration" factoryName="Cargo Command"> - <option name="buildProfileId" value="dev" /> - <option name="command" value="build" /> - <option name="workingDirectory" value="file://$PROJECT_DIR$" /> - <envs /> - <option name="emulateTerminal" value="true" /> - <option name="channel" value="DEFAULT" /> - <option name="requiredFeatures" value="true" /> - <option name="allFeatures" value="false" /> - <option name="withSudo" value="false" /> - <option name="buildTarget" value="REMOTE" /> - <option name="backtrace" value="SHORT" /> - <option name="isRedirectInput" value="false" /> - <option name="redirectInputPath" value="" /> - <method v="2"> - <option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" /> - </method> - </configuration> -</component> @@ -1,20 +0,0 @@ -<component name="ProjectRunConfigurationManager"> - <configuration default="false" name="Build eucalyptus_core cdylib" type="CargoCommandRunConfiguration" factoryName="Cargo Command"> - <option name="buildProfileId" value="release" /> - <option name="command" value="build -p eucalyptus-core --features "editor"" /> - <option name="workingDirectory" value="file://$PROJECT_DIR$" /> - <envs /> - <option name="emulateTerminal" value="true" /> - <option name="channel" value="DEFAULT" /> - <option name="requiredFeatures" value="true" /> - <option name="allFeatures" value="false" /> - <option name="withSudo" value="false" /> - <option name="buildTarget" value="REMOTE" /> - <option name="backtrace" value="SHORT" /> - <option name="isRedirectInput" value="false" /> - <option name="redirectInputPath" value="" /> - <method v="2"> - <option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" /> - </method> - </configuration> -</component> @@ -1,20 +0,0 @@ -<component name="ProjectRunConfigurationManager"> - <configuration default="false" name="Clean literally everything" type="CargoCommandRunConfiguration" factoryName="Cargo Command"> - <option name="command" value="clean" /> - <option name="workingDirectory" value="file://$PROJECT_DIR$" /> - <envs /> - <option name="emulateTerminal" value="true" /> - <option name="channel" value="DEFAULT" /> - <option name="requiredFeatures" value="true" /> - <option name="allFeatures" value="false" /> - <option name="withSudo" value="false" /> - <option name="buildTarget" value="REMOTE" /> - <option name="backtrace" value="SHORT" /> - <option name="isRedirectInput" value="false" /> - <option name="redirectInputPath" value="" /> - <method v="2"> - <option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" /> - <option name="Gradle.BeforeRunTask" enabled="true" tasks="clean" externalProjectPath="$PROJECT_DIR$" vmOptions="" scriptParameters="" /> - </method> - </configuration> -</component> @@ -15,7 +15,7 @@ <option name="redirectInputPath" value="" /> <method v="2"> <option name="RunConfigurationTask" enabled="true" run_configuration_name="fatJar" run_configuration_type="GradleRunConfiguration" /> - <option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" /> + <option name="RunConfigurationTask" enabled="true" run_configuration_name="Build" run_configuration_type="CargoCommandRunConfiguration" run_configuration_target="RsBuildProfile:dev" /> </method> </configuration> </component> @@ -0,0 +1,24 @@ +<component name="ProjectRunConfigurationManager"> + <configuration default="false" name="generateJNIHeaders" type="GradleRunConfiguration" factoryName="Gradle"> + <ExternalSystemSettings> + <option name="executionName" /> + <option name="externalProjectPath" value="$PROJECT_DIR$" /> + <option name="externalSystemIdString" value="GRADLE" /> + <option name="scriptParameters" value="" /> + <option name="taskDescriptions"> + <list /> + </option> + <option name="taskNames"> + <list> + <option value="generateJniHeaders" /> + </list> + </option> + <option name="vmOptions" /> + </ExternalSystemSettings> + <ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess> + <ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess> + <DebugAllEnabled>false</DebugAllEnabled> + <RunAsTest>false</RunAsTest> + <method v="2" /> + </configuration> +</component> @@ -0,0 +1,24 @@ +<component name="ProjectRunConfigurationManager"> + <configuration default="false" name="publishToMavenLocal" type="GradleRunConfiguration" factoryName="Gradle"> + <ExternalSystemSettings> + <option name="executionName" /> + <option name="externalProjectPath" value="$PROJECT_DIR$" /> + <option name="externalSystemIdString" value="GRADLE" /> + <option name="scriptParameters" value="" /> + <option name="taskDescriptions"> + <list /> + </option> + <option name="taskNames"> + <list> + <option value="publishToMavenLocal" /> + </list> + </option> + <option name="vmOptions" /> + </ExternalSystemSettings> + <ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess> + <ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess> + <DebugAllEnabled>false</DebugAllEnabled> + <RunAsTest>false</RunAsTest> + <method v="2" /> + </configuration> +</component> @@ -1,5 +1,7 @@ +#![allow(non_snake_case)] + use crate::camera::{CameraComponent, CameraType}; -use crate::hierarchy::EntityTransformExt; +use crate::hierarchy::{Children, EntityTransformExt, Parent}; use crate::ptr::{AssetRegistryPtr, GraphicsPtr, InputStatePtr, WorldPtr}; use crate::scripting::jni::utils::{ create_vector3, extract_vector3, java_button_to_rust, new_float_array, @@ -18,9 +20,7 @@ use glam::{DQuat, DVec3}; use hecs::World; use jni::JNIEnv; use jni::objects::{JClass, JObject, JPrimitiveArray, JString, JValue}; -use jni::sys::{ - JNI_FALSE, jboolean, jclass, jdouble, jfloatArray, jint, jlong, jobject, jobjectArray, jstring, -}; +use jni::sys::{JNI_FALSE, jboolean, jclass, jdouble, jfloatArray, jint, jlong, jobject, jobjectArray, jstring, jlongArray}; use parking_lot::Mutex; use std::collections::{HashMap, HashSet}; use std::sync::Arc; @@ -389,7 +389,7 @@ pub fn Java_com_dropbear_ffi_JNINative_propagateTransform( } }; - return transform_obj.into_raw(); + transform_obj.into_raw() } else { println!( "[Java_com_dropbear_ffi_JNINative_propagateTransform] [ERROR] Failed to get entity transform" @@ -2477,3 +2477,98 @@ pub fn Java_com_dropbear_ffi_JNINative_getAsset( }; 0 as jlong } + +/// `JNIEXPORT jlongArray JNICALL Java_com_dropbear_ffi_JNINative_getChildren +/// (JNIEnv *, jclass, jlong, jlong);` +#[unsafe(no_mangle)] +pub fn Java_com_dropbear_ffi_JNINative_getChildren( + env: JNIEnv, + _class: JClass, + world_handle: jlong, + entity_id: jlong, +) -> jlongArray { + let world = convert_ptr!(world_handle, WorldPtr => World); + let entity = convert_jlong_to_entity!(entity_id); + + let entities = if let Ok(mut q) = world.query_one::<&Children>(entity) && let Some(children) = q.get() { + let children = children.children(); + let mut array = vec![]; + for child in children { + array.push(child.to_bits().get() as i64); + } + array + } else { + vec![] + }; + + let array = match env.new_long_array(entities.len() as i32) { + Ok(array) => array, + Err(e) => { + return crate::ffi_error_return!("Unable to create a new long array: {}", e); + } + }; + + if let Err(e) = env.set_long_array_region(&array, 0, &entities) { + return crate::ffi_error_return!("Unable to populate long array: {}", e); + } + + array.into_raw() +} + +/// `JNIEXPORT jlong JNICALL Java_com_dropbear_ffi_JNINative_getChildByLabel +/// (JNIEnv *, jclass, jlong, jlong, jstring);` +#[unsafe(no_mangle)] +pub fn Java_com_dropbear_ffi_JNINative_getChildByLabel( + mut env: JNIEnv, + _class: JClass, + world_handle: jlong, + entity_id: jlong, + label: JString, +) -> jlong { + let world = convert_ptr!(world_handle, WorldPtr => World); + let entity = convert_jlong_to_entity!(entity_id); + let target = convert_jstring!(env, label); + + if let Ok(mut q) = world.query_one::<&Children>(entity) && let Some(children) = q.get() { + for child in children.children() { + if let Ok(label) = world.get::<&Label>(entity) { + if label.as_str() == target { + return child.to_bits().get() as jlong; + } + } else { + // skip if error or no entity + continue; + } + } + + } else { + // no children exist for the entity + return -2 as jlong; + }; + + // no children exist with that label + -2 as jlong +} + +/// `JNIEXPORT jlong JNICALL Java_com_dropbear_ffi_JNINative_getParent +/// (JNIEnv *, jclass, jlong, jlong);` +#[unsafe(no_mangle)] +pub fn Java_com_dropbear_ffi_JNINative_getParent( + _env: JNIEnv, + _class: JClass, + world_handle: jlong, + entity_id: jlong, +) -> jlong { + let world = convert_ptr!(world_handle, WorldPtr => World); + let entity = convert_jlong_to_entity!(entity_id); + + if let Ok(mut q) = world.query_one::<&Parent>(entity) { + if let Some(parent) = q.get() { + parent.parent().to_bits().get() as jlong + } else { + -2 as jlong + } + } else { + crate::ffi_error_return!("No entity exists") + } +} @@ -367,6 +367,21 @@ macro_rules! convert_jstring { }}; } +/// A convenient macro for returning from a function when you cbb to add a specific return value. +/// +/// # Usage +/// ``` +/// use anyhow::anyhow; +/// +/// fn some_native_function() -> i32 { +/// let error_value = anyhow!("This is an error. Uh oh!"); +/// let Ok(val) = error_value else { +/// return eucalyptus_core::ffi_error_return!(); +/// // eucalyptus_core::ffi_error_return!("Optional message") +/// // this expands out to `return -1` +/// } +/// } +/// ``` #[macro_export] macro_rules! ffi_error_return { () => {{ @@ -411,7 +426,7 @@ macro_rules! ffi_error_return { impl ErrorValue for u8 { fn error_value() -> Self { 0 - } // most likely a char or a jboolean + } } impl ErrorValue for u16 { @@ -462,10 +477,25 @@ macro_rules! ffi_error_return { } } - // todo: implement other types - ErrorValue::error_value() }}; + + ($($arg:tt)*) => {{ + println!( + "[{}] [ERROR] {}", + { + fn type_name_of<T>(_: T) -> &'static str { + std::any::type_name::<T>() + } + type_name_of(|| {}) + .rsplit("::") + .find(|s| !s.starts_with("{{")) + .unwrap_or("unknown") + }, + format!($($arg)*) + ); + $crate::ffi_error_return!() + }}; } /// Converts a jlong to a hecs::Entity with automatic error handling. @@ -13,7 +13,8 @@ import com.dropbear.math.Transform * The ECS system the dropbear engine uses is `hecs` ECS, which is a Rust crate that has blazing fast * querying systems. The id passed is just a primitive integer value that points to the entity in the world. * - * @property id The unique identifier of the entity as set by `hecs::World` + * @property id The unique identifier of the entity as set by `hecs::World`. This value changes dynamically during different + * playthroughs, so it is recommended not to store this value. */ class EntityRef(val id: EntityId = EntityId(0L)) { lateinit var engine: DropbearEngine @@ -165,4 +166,50 @@ class EntityRef(val id: EntityId = EntityId(0L)) { fun setTextureOverride(materialName: String, textureHandle: TextureHandle) { engine.native.setTextureOverride(id.id, materialName, textureHandle) } + + /** + * Fetches all direct children available to that entity. It does not go any deeper than that level. + * + * It will return `null` if there was an error, or an empty array if no children have been found. + * + * # Example + * ``` + * |- cat + * | |- wizard_hat + * | | |- pom_pom + * ``` + * + * By running [getChildren] on `cat`, it will return `[ wizard_hat ]`, not `pom_pom`. + */ + fun getChildren(): Array<EntityRef>? { + return engine.native.getChildren(id) + } + + /** + * Fetches a direct child by a specific label. + * + * Returns `null` if an error occurred or no child exists, otherwise the entity. + */ + fun getChildByLabel(label: String): EntityRef? { + return engine.native.getChildByLabel(id, label) + } + + /** + * Fetches the parent of this entity. + * + * Returns `null` if no parent exists. If it exists, it will return the [EntityRef] of that parent. + * + * # Note + * You will see in the editor something like this: + * ``` + * Scene_name + * |- cat + * |- bat + * ``` + * + * Calling [getParent] on `cat` will return `null`, as the Scene is not an entity. + */ + fun getParent(): EntityRef? { + return engine.native.getParent(id) + } } @@ -2,6 +2,7 @@ package com.dropbear.ffi import com.dropbear.Camera import com.dropbear.EntityId +import com.dropbear.EntityRef import com.dropbear.EntityTransform import com.dropbear.asset.AssetHandle import com.dropbear.asset.ModelHandle @@ -41,6 +42,10 @@ expect class NativeEngine { fun propagateTransform(entityId: EntityId): Transform? fun setTransform(entityId: EntityId, transform: EntityTransform) + fun getChildren(entityId: EntityId): Array<EntityRef>? + fun getChildByLabel(entityId: EntityId, label: String): EntityRef? + fun getParent(entityId: EntityId): EntityRef? + // ------------------------ MODEL PROPERTIES ------------------------- fun getStringProperty(entityHandle: Long, label: String): String? @@ -41,6 +41,11 @@ public class JNINative { public static native Transform propagateTransform(long worldHandle, long id); public static native void setTransform(long worldHandle, long id, EntityTransform transform); + // hierarchy + public static native long[] getChildren(long worldHandle, long entityId); + public static native long getChildByLabel(long worldHandle, long entityId, String label); + public static native long getParent(long worldHandle, long entityId); + // properties public static native String getStringProperty(long worldHandle, long entityHandle, String label); public static native int getIntProperty(long worldHandle, long entityHandle, String label); @@ -3,6 +3,7 @@ package com.dropbear.ffi import com.dropbear.Camera import com.dropbear.DropbearEngine import com.dropbear.EntityId +import com.dropbear.EntityRef import com.dropbear.EntityTransform import com.dropbear.asset.TextureHandle import com.dropbear.exception.DropbearNativeException @@ -324,4 +325,53 @@ actual class NativeEngine { actual fun getAllTextures(entityHandle: Long): Array<String> { return JNINative.getAllTextures(worldHandle, entityHandle) ?: emptyArray() } + + actual fun getChildren(entityId: EntityId): Array<EntityRef>? { + val result = JNINative.getChildren(worldHandle, entityId.id) + // i shouldn't expect it to return null unless an error, otherwise it must + // return an empty array + if (result == null) { + if (exceptionOnError) { + throw DropbearNativeException("Unable to query for all children for entity ${entityId.id}") + } else { + return null + } + } else { + val entityRefs = mutableListOf<EntityRef>() + result.forEach { e -> + entityRefs.add(EntityRef(EntityId(e))) + } + return entityRefs.toTypedArray() // must be an array so it cannot be mutated + } + } + + actual fun getChildByLabel(entityId: EntityId, label: String): EntityRef? { + val result = JNINative.getChildByLabel(worldHandle, entityId.id, label) + return if (result == -1L) { + if (exceptionOnError) { + throw DropbearNativeException("Unable to get child by label $entityId $label") + } else { + null + } + } else if (result == -2L) { + null + } else { + EntityRef(EntityId(result)) + } + } + + actual fun getParent(entityId: EntityId): EntityRef? { + val result = JNINative.getParent(worldHandle, entityId.id) + return if (result == -1L) { + if (exceptionOnError) { + throw DropbearNativeException("Unable to get parent of entity $entityId") + } else { + null + } + } else if (result == -2L) { + null + } else { + EntityRef(EntityId(result)) + } + } } @@ -9,6 +9,7 @@ package com.dropbear.ffi import com.dropbear.Camera import com.dropbear.EntityId +import com.dropbear.EntityRef import com.dropbear.EntityTransform import com.dropbear.asset.TextureHandle import com.dropbear.exception.DropbearNativeException @@ -807,6 +808,7 @@ actual class NativeEngine { } actual fun setModel(entityHandle: Long, modelHandle: Long) { + TODO("Not yet implemented") } actual fun getTexture(entityHandle: Long, name: String): Long? { @@ -834,6 +836,7 @@ actual class NativeEngine { } actual fun setTextureOverride(entityHandle: Long, oldMaterialName: String, newTextureHandle: TextureHandle) { + TODO("Not yet implemented") } actual fun getTextureName(textureHandle: Long): String? { @@ -843,4 +846,16 @@ actual class NativeEngine { actual fun getAllTextures(entityHandle: Long): Array<String> { TODO("Not yet implemented") } + + actual fun getChildren(entityId: EntityId): Array<EntityRef>? { + TODO("Not yet implemented") + } + + actual fun getChildByLabel(entityId: EntityId, label: String): EntityRef? { + TODO("Not yet implemented") + } + + actual fun getParent(entityId: EntityId): EntityRef? { + TODO("Not yet implemented") + } }