tirbofish/dropbear · commit
485d155dde0a20a063767c9afe4668425e12ec2a
added keyboard input detection for kotlin and windows native building works
Signature present but could not be verified.
Unverified
@@ -1,24 +0,0 @@ -<component name="ProjectRunConfigurationManager"> - <configuration default="false" name="Build gradle" 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="build" /> - </list> - </option> - <option name="vmOptions" /> - </ExternalSystemSettings> - <ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess> - <ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess> - <DebugAllEnabled>false</DebugAllEnabled> - <RunAsTest>false</RunAsTest> - <method v="2" /> - </configuration> -</component> @@ -1,26 +0,0 @@ -<component name="ProjectRunConfigurationManager"> - <configuration default="false" name="Build literally everything" 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="build" /> - </list> - </option> - <option name="vmOptions" /> - </ExternalSystemSettings> - <ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess> - <ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess> - <DebugAllEnabled>false</DebugAllEnabled> - <RunAsTest>false</RunAsTest> - <method v="2"> - <option name="RunConfigurationTask" enabled="true" run_configuration_name="Build eucalyptus-editor" run_configuration_type="CargoCommandRunConfiguration" /> - </method> - </configuration> -</component> @@ -1,24 +0,0 @@ -<component name="ProjectRunConfigurationManager"> - <configuration default="false" name="Publish to GitHub Packages" 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="publish" /> - </list> - </option> - <option name="vmOptions" /> - </ExternalSystemSettings> - <ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess> - <ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess> - <DebugAllEnabled>false</DebugAllEnabled> - <RunAsTest>false</RunAsTest> - <method v="2" /> - </configuration> -</component> @@ -1,24 +0,0 @@ -<component name="ProjectRunConfigurationManager"> - <configuration default="false" name="Publish to Maven Local" 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> @@ -14,6 +14,7 @@ <option name="isRedirectInput" value="false" /> <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" /> </method> </configuration> @@ -0,0 +1,24 @@ +<component name="ProjectRunConfigurationManager"> + <configuration default="false" name="fatJar" 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="fatJar" /> + </list> + </option> + <option name="vmOptions" /> + </ExternalSystemSettings> + <ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess> + <ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess> + <DebugAllEnabled>false</DebugAllEnabled> + <RunAsTest>false</RunAsTest> + <method v="2" /> + </configuration> +</component> @@ -63,6 +63,8 @@ tree-sitter-kotlin = "0.3.8" libloading = "0.8" indexmap = "2.11" rand = "0.9" +num_enum = "0.7" +sha2 = "0.10" [workspace.dependencies.image] version = "0.25" @@ -1,5 +1,3 @@ -import org.jetbrains.kotlin.gradle.targets.jvm.KotlinJvmTarget - plugins { alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.kotlinxSerialization) @@ -14,59 +12,52 @@ repositories { mavenCentral() } +val hostOs = providers.systemProperty("os.name").get() +val isArm64 = providers.systemProperty("os.arch").map { it == "aarch64" }.get() +val isMingwX64 = hostOs.startsWith("Windows") +val isLinux = hostOs == "Linux" +val isMacOs = hostOs == "Mac OS X" + +val libName = when { + isMacOs -> "libeucalyptus_core.dylib" + isLinux -> "libeucalyptus_core.so" + isMingwX64 -> "eucalyptus_core.dll" + else -> throw GradleException("Host OS is not supported in Kotlin/Native.") +} + +val libPathProvider = provider { + val candidates = listOf( + layout.projectDirectory.file("target/debug/$libName").asFile, + layout.projectDirectory.file("target/release/$libName").asFile, + layout.projectDirectory.file("libs/$libName").asFile + ) + + candidates.firstOrNull { it.exists() }?.absolutePath + ?: println("No Rust library exists") +} + kotlin { jvm { withJava() } - val hostOs = System.getProperty("os.name") - val isArm64 = System.getProperty("os.arch") == "aarch64" - val isMingwX64 = hostOs.startsWith("Windows") val nativeTarget = when { - hostOs == "Mac OS X" && isArm64 -> macosArm64("nativeLib") - hostOs == "Mac OS X" && !isArm64 -> macosX64("nativeLib") - hostOs == "Linux" && isArm64 -> linuxArm64("nativeLib") - hostOs == "Linux" && !isArm64 -> linuxX64("nativeLib") + isMacOs && isArm64 -> macosArm64("nativeLib") + isMacOs && !isArm64 -> macosX64("nativeLib") + isLinux && isArm64 -> linuxArm64("nativeLib") + isLinux && !isArm64 -> linuxX64("nativeLib") isMingwX64 -> mingwX64("nativeLib") else -> throw GradleException("Host OS is not supported in Kotlin/Native.") } - val libName = when { - hostOs == "Mac OS X" -> "libeucalyptus_core.dylib" - hostOs == "Linux" -> "libeucalyptus_core.so" - isMingwX64 -> "eucalyptus_core.dll" - else -> throw GradleException("Host OS is not supported in Kotlin/Native.") - } - - val (libDir, libNameForLinking) = when { - file("${project.rootDir}/target/debug").exists() -> { - val debugLibDir = "${project.rootDir}/target/debug" - if (isMingwX64) { - Pair(debugLibDir, "eucalyptus_core") - } else { - Pair(debugLibDir, "eucalyptus_core") - } - } - file("${project.rootDir}/target/release").exists() -> { - val releaseLibDir = "${project.rootDir}/target/release" - if (isMingwX64) { - Pair(releaseLibDir, "eucalyptus_core") - } else { - Pair(releaseLibDir, "eucalyptus_core") - } - } - file("${project.rootDir}/libs").exists() -> { - val libsDir = "${project.rootDir}/libs" - if (isMingwX64) { - Pair(libsDir, "eucalyptus_core") - } else { - Pair(libsDir, "eucalyptus_core") - } - } - else -> { - println("WARNING: Rust library directory not found!") - Pair(null, null) - } + val nativeLibPath = libPathProvider.get() + val nativeLibDir = file(nativeLibPath).parentFile.absolutePath + val nativeLibFileName = file(nativeLibPath).name + val nativeLibNameForLinking = when { + isMacOs -> nativeLibFileName.removePrefix("lib").removeSuffix(".dylib") + isLinux -> nativeLibFileName.removePrefix("lib").removeSuffix(".so") + isMingwX64 -> nativeLibFileName.removeSuffix(".dll") + else -> throw GradleException("Unsupported OS for library name derivation.") } nativeTarget.apply { @@ -82,12 +73,14 @@ kotlin { sharedLib { baseName = "dropbear" - if (libDir != null && libNameForLinking != null) { - if (isMingwX64) { - linkerOpts("${libDir}/${libName}.lib") - } else { - linkerOpts("-L${libDir}", "-l${libNameForLinking}") - } + if (isLinux || isMacOs) { + linkerOpts("-L$nativeLibDir", "-l$nativeLibNameForLinking", "-Wl,-rpath,\\\$ORIGIN") + } else if (isMingwX64) { + val importLibName = "$nativeLibNameForLinking.lib" + val importLibPath = file("$nativeLibDir/$importLibName").absolutePath + linkerOpts( + importLibPath + ) } } } @@ -194,4 +187,4 @@ tasks.register<Jar>("fatJar") { } manifest {} -} +} @@ -28,10 +28,11 @@ winit.workspace = true tokio.workspace = true rayon.workspace = true jni.workspace = true -crossbeam-channel = "0.5.15" -libloading = "0.8.9" +sha2.workspace = true +libloading.workspace = true +crossbeam-channel.workspace = true app_dirs2.workspace = true -sha2 = "0.10" +num_enum.workspace = true [features] # editor only stuff @@ -3,8 +3,9 @@ use std::{ time::{Duration, Instant}, }; use winit::{event::MouseButton, keyboard::KeyCode}; +use dropbear_engine::gilrs::{Button, GamepadId}; -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct InputState { #[allow(dead_code)] pub last_key_press_times: HashMap<KeyCode, Instant>, @@ -16,6 +17,13 @@ pub struct InputState { pub mouse_delta: Option<(f64, f64)>, pub is_cursor_locked: bool, pub last_mouse_pos: Option<(f64, f64)>, + + pub connected_gamepads: HashSet<GamepadId>, + pub pressed_buttons: HashMap<GamepadId, HashSet<Button>>, + pub left_stick_position: HashMap<GamepadId, (f32, f32)>, + pub right_stick_position: HashMap<GamepadId, (f32, f32)>, + pub left_trigger: HashMap<GamepadId, f32>, + pub right_trigger: HashMap<GamepadId, f32>, } impl Default for InputState { @@ -35,6 +43,12 @@ impl InputState { mouse_delta: None, is_cursor_locked: false, last_mouse_pos: Default::default(), + connected_gamepads: Default::default(), + pressed_buttons: Default::default(), + left_stick_position: Default::default(), + right_stick_position: Default::default(), + left_trigger: Default::default(), + right_trigger: Default::default(), } } @@ -45,4 +59,29 @@ impl InputState { pub fn is_key_pressed(&self, key: KeyCode) -> bool { self.pressed_keys.contains(&key) } + + #[allow(clippy::unnecessary_map_or)] + pub fn is_button_pressed(&self, gamepad_id: GamepadId, button: Button) -> bool { + self.pressed_buttons + .get(&gamepad_id) + .map_or(false, |buttons| buttons.contains(&button)) + } + + pub fn get_left_stick(&self, gamepad_id: GamepadId) -> (f32, f32) { + self.left_stick_position + .get(&gamepad_id) + .copied() + .unwrap_or((0.0, 0.0)) + } + + pub fn get_right_stick(&self, gamepad_id: GamepadId) -> (f32, f32) { + self.right_stick_position + .get(&gamepad_id) + .copied() + .unwrap_or((0.0, 0.0)) + } + + pub fn is_gamepad_connected(&self, gamepad_id: GamepadId) -> bool { + self.connected_gamepads.contains(&gamepad_id) + } } @@ -1,3 +1,5 @@ use hecs::World; +use crate::input::InputState; pub type WorldPtr = *mut World; +pub type InputStatePtr = *mut InputState; @@ -1,5 +1,5 @@ pub mod jni; -pub mod kmp; +pub mod native; use crate::input::InputState; use crate::scripting::jni::JavaContext; @@ -11,7 +11,7 @@ use anyhow::Context; use crossbeam_channel::Sender; use tokio::io::{AsyncBufReadExt, BufReader}; use tokio::process::Command; -use crate::ptr::WorldPtr; +use crate::ptr::{InputStatePtr, WorldPtr}; pub const TEMPLATE_SCRIPT: &str = include_str!("../../resources/scripting/kotlin/Template.kt"); @@ -109,12 +109,12 @@ impl ScriptManager { pub fn load_script( &mut self, world: WorldPtr, - _input_state: &InputState, + input_state: InputStatePtr, ) -> anyhow::Result<()> { match &self.script_target { ScriptTarget::JVM { library_path: _ } => { if let Some(jvm) = &mut self.jvm { - jvm.init(world)?; + jvm.init(world, input_state)?; for tag in self.entity_tag_database.keys() { log::trace!("Loading systems for tag: {}", tag); jvm.load_systems_for_tag(tag)?; @@ -2,19 +2,17 @@ //! Deals with the Java Native Interface (JNI) with the help of the [`jni`] crate pub mod exception; +mod exports; use std::fs; -use dropbear_engine::entity::{AdoptedEntity, Transform}; -use hecs::World; -use jni::objects::{GlobalRef, JClass, JObject, JString, JValue}; -use jni::sys::{jclass, jlong}; -use jni::{InitArgsBuilder, JNIEnv, JNIVersion, JavaVM}; +use jni::objects::{GlobalRef, JClass, JValue}; +use jni::sys::{jlong}; +use jni::{InitArgsBuilder, JNIVersion, JavaVM}; use std::path::{PathBuf}; -use glam::{DQuat, DVec3}; use crate::{success, APP_INFO}; use sha2::{Digest, Sha256}; use crate::logging::{LOG_LEVEL}; -use crate::ptr::WorldPtr; +use crate::ptr::{InputStatePtr, WorldPtr}; use crate::scripting::jni::exception::get_exception_info; const LIBRARY_PATH: &[u8] = include_bytes!("../../../build/libs/dropbear-1.0-SNAPSHOT-all.jar"); @@ -103,7 +101,7 @@ impl JavaContext { }) } - pub fn init(&mut self, world: WorldPtr) -> anyhow::Result<()> { + pub fn init(&mut self, world: WorldPtr, input: InputStatePtr) -> anyhow::Result<()> { let mut env = self.jvm.attach_current_thread()?; if let Some(old_ref) = self.dropbear_engine_class.take() { @@ -123,12 +121,17 @@ impl JavaContext { if result.is_some() { return Err(anyhow::anyhow!("{}", result.unwrap())); } let world_handle = world as jlong; - log::trace!("Calling NativeEngine.init() with arg [{} as JValue::Long]", world_handle); + let input_handle = input as jlong; + + log::trace!("Calling NativeEngine.init() with arg [{} as JValue::Long, {} as JValue::Long]", + world_handle, + input_handle + ); env.call_method( &native_engine_obj, "init", - "(J)V", - &[JValue::Long(world_handle)], + "(JJ)V", + &[JValue::Long(world_handle), JValue::Long(input_handle)], )?; let result = get_exception_info(&mut env); @@ -382,202 +385,4 @@ impl Drop for JavaContext { let _ = old_ref; } } -} - -// JNIEXPORT jlong JNICALL Java_com_dropbear_ffi_JNINative_getEntity -// (JNIEnv *, jclass, jlong, jstring); -#[unsafe(no_mangle)] -pub fn Java_com_dropbear_ffi_JNINative_getEntity( - mut env: JNIEnv, - _obj: jclass, - world_handle: jlong, - label: JString, -) -> jlong { - let label_jni_result = env.get_string(&label); - let label_str = match label_jni_result { - Ok(java_string) => { - match java_string.to_str() { - Ok(rust_str) => rust_str.to_string(), - Err(e) => { - println!("[Java_com_dropbear_ffi_JNINative_getEntity] [ERROR] Failed to convert Java string to Rust string: {}", e); - return -1; - } - } - }, - Err(e) => { - println!("[Java_com_dropbear_ffi_JNINative_getEntity] [ERROR] Failed to get string from JNI: {}", e); - return -1; - } - }; - - let world = world_handle as *mut World; - - if world.is_null() { - println!("[Java_com_dropbear_ffi_JNINative_getEntity] [ERROR] World pointer is null"); - return -1; - } - - let world = unsafe { &mut *world }; - - for (id, entity) in world.query::<&AdoptedEntity>().iter() { - if entity.model.label == label_str { - return id.id() as jlong; - } - } - -1 -} - -// JNIEXPORT jobject JNICALL Java_com_dropbear_ffi_JNINative_getTransform -// (JNIEnv *, jclass, jlong, jlong); -#[unsafe(no_mangle)] -pub fn Java_com_dropbear_ffi_JNINative_getTransform( - mut env: JNIEnv, - _class: jclass, - world_handle: jlong, - entity_id: jlong, -) -> JObject { - let world = world_handle as *mut World; - - if world.is_null() { - println!("[Java_com_dropbear_ffi_JNINative_getTransform] [ERROR] World pointer is null"); - return JObject::null(); - } - - let world = unsafe { &mut *world }; - - let entity = unsafe { world.find_entity_from_id(entity_id as u32) }; - - if let Ok(mut q) = world.query_one::<(&AdoptedEntity, &Transform)>(entity) - && let Some((_, transform)) = q.get() - { - let new_transform = *transform; - - let transform_class = match env.find_class("com/dropbear/math/Transform") { - Ok(c) => c, - Err(_) => return JObject::null(), - }; - - return match env.new_object( - &transform_class, - "(DDDDDDDDDD)V", - &[ - new_transform.position.x.into(), - new_transform.position.y.into(), - new_transform.position.z.into(), - new_transform.rotation.x.into(), - new_transform.rotation.y.into(), - new_transform.rotation.z.into(), - new_transform.rotation.w.into(), - new_transform.scale.x.into(), - new_transform.scale.y.into(), - new_transform.scale.z.into(), - ], - ) { - Ok(java_transform) => java_transform, - Err(_) => { - println!("[Java_com_dropbear_ffi_JNINative_getTransform] [ERROR] Failed to create Transform object"); - JObject::null() - } - }; - } - - println!("[Java_com_dropbear_ffi_JNINative_getTransform] [ERROR] Failed to query for transform value for entity: {}", entity_id); - JObject::null() -} - -// JNIEXPORT void JNICALL Java_com_dropbear_ffi_JNINative_setTransform -// (JNIEnv *, jclass, jlong, jlong, jobject); -#[unsafe(no_mangle)] -pub fn Java_com_dropbear_ffi_JNINative_setTransform( - mut env: JNIEnv, - _class: jclass, - world_handle: jlong, - entity_id: jlong, - transform_obj: JObject, -) { - let world = world_handle as *mut World; - - if world.is_null() { - println!("[Java_com_dropbear_ffi_JNINative_setTransform] [ERROR] World pointer is null"); - return; - } - - let world = unsafe { &mut *world }; - let entity = unsafe { world.find_entity_from_id(entity_id as u32) }; - - let get_number_field = |env: &mut JNIEnv, obj: &JObject, field_name: &str| -> f64 { - match env.get_field(obj, field_name, "Ljava/lang/Number;") { - Ok(v) => match v.l() { - Ok(num_obj) => { - match env.call_method(&num_obj, "doubleValue", "()D", &[]) { - Ok(result) => result.d().unwrap_or_else(|_| { - println!("[Java_com_dropbear_ffi_JNINative_setTransform] [ERROR] Failed to extract double from {}", field_name); - 0.0 - }), - Err(_) => { - println!("[Java_com_dropbear_ffi_JNINative_setTransform] [ERROR] Failed to call doubleValue on {}", field_name); - 0.0 - } - } - } - Err(_) => { - println!("[Java_com_dropbear_ffi_JNINative_setTransform] [ERROR] Failed to extract Number object for {}", field_name); - 0.0 - } - }, - Err(_) => { - println!("[Java_com_dropbear_ffi_JNINative_setTransform] [ERROR] Failed to get field {}", field_name); - 0.0 - } - } - }; - - let position_obj: JObject = match env.get_field(&transform_obj, "position", "Lcom/dropbear/math/Vector3;") { - Ok(v) => v.l().unwrap_or_else(|_| JObject::null()), - Err(_) => JObject::null(), - }; - - let rotation_obj: JObject = match env.get_field(&transform_obj, "rotation", "Lcom/dropbear/math/Quaternion;") { - Ok(v) => v.l().unwrap_or_else(|_| JObject::null()), - Err(_) => JObject::null(), - }; - - let scale_obj: JObject = match env.get_field(&transform_obj, "scale", "Lcom/dropbear/math/Vector3;") { - Ok(v) => v.l().unwrap_or_else(|_| JObject::null()), - Err(_) => JObject::null(), - }; - - if position_obj.is_null() || rotation_obj.is_null() || scale_obj.is_null() { - println!("[Java_com_dropbear_ffi_JNINative_setTransform] [ERROR] Failed to extract position/rotation/scale objects"); - return; - } - - let px = get_number_field(&mut env, &position_obj, "x"); - let py = get_number_field(&mut env, &position_obj, "y"); - let pz = get_number_field(&mut env, &position_obj, "z"); - - let rx = get_number_field(&mut env, &rotation_obj, "x"); - let ry = get_number_field(&mut env, &rotation_obj, "y"); - let rz = get_number_field(&mut env, &rotation_obj, "z"); - let rw = get_number_field(&mut env, &rotation_obj, "w"); - - let sx = get_number_field(&mut env, &scale_obj, "x"); - let sy = get_number_field(&mut env, &scale_obj, "y"); - let sz = get_number_field(&mut env, &scale_obj, "z"); - - let new_transform = Transform { - position: DVec3::new(px, py, pz), - rotation: DQuat::from_axis_angle(DVec3::new(rx, ry, rz), rw), - scale: DVec3::new(sx, sy, sz), - }; - - if let Ok(mut q) = world.query_one::<&mut Transform>(entity) { - if let Some(transform) = q.get() { - *transform = new_transform; - } else { - println!("[Java_com_dropbear_ffi_JNINative_setTransform] [ERROR] Failed to query for transform component"); - } - } else { - println!("[Java_com_dropbear_ffi_JNINative_setTransform] [ERROR] Failed to query entity for transform component"); - } } @@ -0,0 +1,262 @@ +use glam::{DQuat, DVec3}; +use hecs::World; +use jni::JNIEnv; +use jni::objects::{JObject, JString}; +use jni::sys::{jboolean, jclass, jint, jlong}; +use winit::keyboard::{KeyCode, PhysicalKey}; +use winit::platform::scancode::PhysicalKeyExtScancode; +use dropbear_engine::entity::{AdoptedEntity, Transform}; +use crate::ptr::InputStatePtr; +use num_enum::FromPrimitive; +use crate::utils::keycode_from_ordinal; + +// JNIEXPORT jlong JNICALL Java_com_dropbear_ffi_JNINative_getEntity +// (JNIEnv *, jclass, jlong, jstring); +#[unsafe(no_mangle)] +pub fn Java_com_dropbear_ffi_JNINative_getEntity( + mut env: JNIEnv, + _obj: jclass, + world_handle: jlong, + label: JString, +) -> jlong { + let label_jni_result = env.get_string(&label); + let label_str = match label_jni_result { + Ok(java_string) => { + match java_string.to_str() { + Ok(rust_str) => rust_str.to_string(), + Err(e) => { + println!("[Java_com_dropbear_ffi_JNINative_getEntity] [ERROR] Failed to convert Java string to Rust string: {}", e); + return -1; + } + } + }, + Err(e) => { + println!("[Java_com_dropbear_ffi_JNINative_getEntity] [ERROR] Failed to get string from JNI: {}", e); + return -1; + } + }; + + let world = world_handle as *mut World; + + if world.is_null() { + println!("[Java_com_dropbear_ffi_JNINative_getEntity] [ERROR] World pointer is null"); + return -1; + } + + let world = unsafe { &mut *world }; + + for (id, entity) in world.query::<&AdoptedEntity>().iter() { + if entity.model.label == label_str { + return id.id() as jlong; + } + } + -1 +} + +// JNIEXPORT jobject JNICALL Java_com_dropbear_ffi_JNINative_getTransform +// (JNIEnv *, jclass, jlong, jlong); +#[unsafe(no_mangle)] +pub fn Java_com_dropbear_ffi_JNINative_getTransform( + mut env: JNIEnv, + _class: jclass, + world_handle: jlong, + entity_id: jlong, +) -> JObject { + let world = world_handle as *mut World; + + if world.is_null() { + println!("[Java_com_dropbear_ffi_JNINative_getTransform] [ERROR] World pointer is null"); + return JObject::null(); + } + + let world = unsafe { &mut *world }; + + let entity = unsafe { world.find_entity_from_id(entity_id as u32) }; + + if let Ok(mut q) = world.query_one::<(&AdoptedEntity, &Transform)>(entity) + && let Some((_, transform)) = q.get() + { + let new_transform = *transform; + + let transform_class = match env.find_class("com/dropbear/math/Transform") { + Ok(c) => c, + Err(_) => return JObject::null(), + }; + + return match env.new_object( + &transform_class, + "(DDDDDDDDDD)V", + &[ + new_transform.position.x.into(), + new_transform.position.y.into(), + new_transform.position.z.into(), + new_transform.rotation.x.into(), + new_transform.rotation.y.into(), + new_transform.rotation.z.into(), + new_transform.rotation.w.into(), + new_transform.scale.x.into(), + new_transform.scale.y.into(), + new_transform.scale.z.into(), + ], + ) { + Ok(java_transform) => java_transform, + Err(_) => { + println!("[Java_com_dropbear_ffi_JNINative_getTransform] [ERROR] Failed to create Transform object"); + JObject::null() + } + }; + } + + println!("[Java_com_dropbear_ffi_JNINative_getTransform] [ERROR] Failed to query for transform value for entity: {}", entity_id); + JObject::null() +} + +// JNIEXPORT void JNICALL Java_com_dropbear_ffi_JNINative_setTransform +// (JNIEnv *, jclass, jlong, jlong, jobject); +#[unsafe(no_mangle)] +pub fn Java_com_dropbear_ffi_JNINative_setTransform( + mut env: JNIEnv, + _class: jclass, + world_handle: jlong, + entity_id: jlong, + transform_obj: JObject, +) { + let world = world_handle as *mut World; + + if world.is_null() { + println!("[Java_com_dropbear_ffi_JNINative_setTransform] [ERROR] World pointer is null"); + return; + } + + let world = unsafe { &mut *world }; + let entity = unsafe { world.find_entity_from_id(entity_id as u32) }; + + let get_number_field = |env: &mut JNIEnv, obj: &JObject, field_name: &str| -> f64 { + match env.get_field(obj, field_name, "Ljava/lang/Number;") { + Ok(v) => match v.l() { + Ok(num_obj) => { + match env.call_method(&num_obj, "doubleValue", "()D", &[]) { + Ok(result) => result.d().unwrap_or_else(|_| { + println!("[Java_com_dropbear_ffi_JNINative_setTransform] [ERROR] Failed to extract double from {}", field_name); + 0.0 + }), + Err(_) => { + println!("[Java_com_dropbear_ffi_JNINative_setTransform] [ERROR] Failed to call doubleValue on {}", field_name); + 0.0 + } + } + } + Err(_) => { + println!("[Java_com_dropbear_ffi_JNINative_setTransform] [ERROR] Failed to extract Number object for {}", field_name); + 0.0 + } + }, + Err(_) => { + println!("[Java_com_dropbear_ffi_JNINative_setTransform] [ERROR] Failed to get field {}", field_name); + 0.0 + } + } + }; + + let position_obj: JObject = match env.get_field(&transform_obj, "position", "Lcom/dropbear/math/Vector3;") { + Ok(v) => v.l().unwrap_or_else(|_| JObject::null()), + Err(_) => JObject::null(), + }; + + let rotation_obj: JObject = match env.get_field(&transform_obj, "rotation", "Lcom/dropbear/math/Quaternion;") { + Ok(v) => v.l().unwrap_or_else(|_| JObject::null()), + Err(_) => JObject::null(), + }; + + let scale_obj: JObject = match env.get_field(&transform_obj, "scale", "Lcom/dropbear/math/Vector3;") { + Ok(v) => v.l().unwrap_or_else(|_| JObject::null()), + Err(_) => JObject::null(), + }; + + if position_obj.is_null() || rotation_obj.is_null() || scale_obj.is_null() { + println!("[Java_com_dropbear_ffi_JNINative_setTransform] [ERROR] Failed to extract position/rotation/scale objects"); + return; + } + + let px = get_number_field(&mut env, &position_obj, "x"); + let py = get_number_field(&mut env, &position_obj, "y"); + let pz = get_number_field(&mut env, &position_obj, "z"); + + let rx = get_number_field(&mut env, &rotation_obj, "x"); + let ry = get_number_field(&mut env, &rotation_obj, "y"); + let rz = get_number_field(&mut env, &rotation_obj, "z"); + let rw = get_number_field(&mut env, &rotation_obj, "w"); + + let sx = get_number_field(&mut env, &scale_obj, "x"); + let sy = get_number_field(&mut env, &scale_obj, "y"); + let sz = get_number_field(&mut env, &scale_obj, "z"); + + let new_transform = Transform { + position: DVec3::new(px, py, pz), + rotation: DQuat::from_axis_angle(DVec3::new(rx, ry, rz), rw), + scale: DVec3::new(sx, sy, sz), + }; + + if let Ok(mut q) = world.query_one::<&mut Transform>(entity) { + if let Some(transform) = q.get() { + *transform = new_transform; + } else { + println!("[Java_com_dropbear_ffi_JNINative_setTransform] [ERROR] Failed to query for transform component"); + } + } else { + println!("[Java_com_dropbear_ffi_JNINative_setTransform] [ERROR] Failed to query entity for transform component"); + } +} + +// JNIEXPORT void JNICALL Java_com_dropbear_ffi_JNINative_printInputState +// (JNIEnv *, jclass, jlong); +#[unsafe(no_mangle)] +pub fn Java_com_dropbear_ffi_JNINative_printInputState( + _env: JNIEnv, + _class: jclass, + input_handle: jlong, +) { + let input = input_handle as InputStatePtr; + + if input.is_null() { + println!("[Java_com_dropbear_ffi_JNINative_printInputState] [ERROR] Input state pointer is null"); + return; + } + + let input = unsafe { &*input }; + println!("{:#?}", input); +} + +// JNIEXPORT jboolean JNICALL Java_com_dropbear_ffi_JNINative_isKeyPressed +// (JNIEnv *, jclass, jlong, jint); +#[unsafe(no_mangle)] +pub fn Java_com_dropbear_ffi_JNINative_isKeyPressed( + _env: JNIEnv, + _class: jclass, + input_handle: jlong, + key: jint, +) -> jboolean { + let input = input_handle as InputStatePtr; + if input.is_null() { + println!("[Java_com_dropbear_ffi_JNINative_isKeyPressed] [ERROR] Input state pointer is null"); + return false.into(); + } + let input = unsafe { &*input }; + + println!("[Java_com_dropbear_ffi_JNINative_isKeyPressed] [DEBUG] Original code: {:?}", key); + + match keycode_from_ordinal(key) { + Some(k) => { + println!("[Java_com_dropbear_ffi_JNINative_isKeyPressed] [DEBUG] Keycode: {:?}", k); + if input.pressed_keys.contains(&k) { + true.into() + } else { + false.into() + } + } + None => { + println!("[Java_com_dropbear_ffi_JNINative_isKeyPressed] [WARN] Ordinal keycode is invalid"); + false.into() + } + } +} @@ -1,129 +0,0 @@ -//! Deals with Kotlin/Native library loading for different platforms. -#![allow(clippy::missing_safety_doc)] - -use dropbear_engine::entity::{AdoptedEntity, Transform}; -use std::ffi::{CStr, c_char}; - -/// Displays the types of errors that can be returned by the native library. -pub enum DropbearNativeError { - Success = 0, - NullPointer = -1, - QueryFailed = -2, - EntityNotFound = -3, - NoSuchComponent = -4, - NoSuchEntity = -5, - WorldInsertError = -6, - - InvalidUTF8 = -108, - /// A generic error when the library doesn't know what happened or cannot find a - /// suitable error code. - /// - /// The number `1274` comes from the total sum of the word "UnknownError" into decimal - UnknownError = -1274, -} - -#[unsafe(no_mangle)] -pub unsafe extern "C" fn dropbear_get_entity( - label: *const c_char, - world_ptr: *const hecs::World, - out_entity: *mut i64, -) -> i32 { - unsafe { - if label.is_null() || world_ptr.is_null() || out_entity.is_null() { - println!("[dropbear_get_entity] [ERROR] received null pointer"); - return -1; - } - - let world = &*world_ptr; - - let label_str = match CStr::from_ptr(label).to_str() { - Ok(s) => s, - Err(_) => { - println!("[dropbear_get_entity] [ERROR] invalid UTF-8 in label"); - return -108; - } - }; - - let mut hit: bool = false; - - for (id, entity) in world.query::<&AdoptedEntity>().iter() { - if entity.model.label == label_str { - #[allow(unused_assignments)] - { hit = true; } - *out_entity = id.id() as i64; - log::debug!("Found entity with label: {:?}", label_str); - return 0; - } - } - - if !hit { - println!("[dropbear_get_entity] [ERROR] Entity with label '{:?}' not found", label_str); - -3 - } else { - DropbearNativeError::UnknownError as i32 - } - } -} - -#[unsafe(no_mangle)] -pub unsafe extern "C" fn dropbear_get_transform( - world_ptr: *const hecs::World, - entity_id: i64, - out_transform: *mut Transform, -) -> i32 { - if world_ptr.is_null() { - eprintln!("[dropbear_get_transform] [ERROR] World pointer is null"); - return -1; - } - - if out_transform.is_null() { - eprintln!("[dropbear_get_transform] [ERROR] Output transform pointer is null"); - return -1; - } - - let world = unsafe { &*world_ptr }; - - let entity = unsafe { world.find_entity_from_id(entity_id as u32) }; - - match world.query_one::<&Transform>(entity) { - Ok(mut q) => { - if let Some(transform) = q.get() { - unsafe { *out_transform = *transform }; - 0 - } else { - eprintln!("[dropbear_get_transform] [ERROR] Entity has no Transform component"); - -4 - } - } - Err(_) => { - eprintln!("[dropbear_get_transform] [ERROR] Failed to query entity for Transform component"); - -2 - } - } -} - -pub unsafe extern "C" fn dropbear_set_transform( - world_ptr: *mut hecs::World, - entity_id: i64, - transform: Transform, -) -> i32 { - if world_ptr.is_null() { - eprintln!("[dropbear_get_transform] [ERROR] World pointer is null"); - return -1; - } - - let world = unsafe { &mut *world_ptr }; - - let entity = unsafe { world.find_entity_from_id(entity_id as u32) }; - - let result = world.insert_one(entity, transform); - - match result { - Ok(_) => 0, - Err(_) => { - eprintln!("[dropbear_set_transform] [ERROR] Failed to insert transform component"); - -6 - }, - } -} - @@ -0,0 +1,129 @@ +//! Deals with Kotlin/Native library loading for different platforms. +#![allow(clippy::missing_safety_doc)] + +pub mod sig; +mod exports; + +use crate::ptr::{InputStatePtr, WorldPtr}; +use crate::scripting::native::sig::{DestroyAll, DestroyTagged, Init, LoadTagged, UpdateAll, UpdateTagged}; +use libloading::{Library, Symbol}; +use std::ffi::CString; +use std::path::Path; + +pub struct NativeLibrary { + #[allow(dead_code)] + library: Library, + init_fn: Symbol<'static, Init>, + load_systems_fn: Symbol<'static, LoadTagged>, + update_all_fn: Symbol<'static, UpdateAll>, + update_tag_fn: Symbol<'static, UpdateTagged>, + destroy_all_fn: Symbol<'static, DestroyAll>, + destroy_tagged_fn: Symbol<'static, DestroyTagged>, +} + +impl NativeLibrary { + pub fn new(lib_path: impl AsRef<Path>) -> anyhow::Result<Self> { + let lib_path = lib_path.as_ref(); + unsafe { + let library: Library = Library::new(lib_path)?; + + let init_fn: Symbol<'static, Init> = std::mem::transmute( + library.get::<Init>(b"dropbear_init\0")? + ); + let load_systems_fn: Symbol<'static, LoadTagged> = std::mem::transmute( + library.get::<LoadTagged>(b"dropbear_load_systems\0")? + ); + let update_all_fn: Symbol<'static, UpdateAll> = std::mem::transmute( + library.get::<UpdateAll>(b"dropbear_update_all\0")? + ); + let update_tag_fn: Symbol<'static, UpdateTagged> = std::mem::transmute( + library.get::<UpdateTagged>(b"dropbear_update_tagged\0")? + ); + let destroy_all_fn: Symbol<'static, DestroyAll> = std::mem::transmute( + library.get::<DestroyAll>(b"dropbear_destroy_all\0")? + ); + let destroy_tagged_fn: Symbol<'static, DestroyTagged> = std::mem::transmute( + library.get::<DestroyTagged>(b"dropbear_destroy_tagged\0")? + ); + + Ok(Self { + library, + init_fn, + load_systems_fn, + update_all_fn, + update_tag_fn, + destroy_all_fn, + destroy_tagged_fn, + }) + } + } + + pub fn init(&mut self, world_ptr: WorldPtr, input_state_ptr: InputStatePtr) -> anyhow::Result<()> { + unsafe { + let result = (self.init_fn)(world_ptr, input_state_ptr); + if result != 0 { + anyhow::bail!("Init function failed with code: {}", result); + } + Ok(()) + } + } + + pub fn load_systems(&mut self, tag: String) -> anyhow::Result<()> { + unsafe { + let c_string: CString = CString::new(tag)?; + let result = (self.load_systems_fn)(c_string.as_ptr()); + if result != 0 { + anyhow::bail!("Load systems failed with code: {}", result); + } + Ok(()) + } + } + + pub fn update_all(&mut self, dt: f32) -> anyhow::Result<()> { + unsafe { + (self.update_all_fn)(dt); + Ok(()) + } + } + + pub fn update_tagged(&mut self, tag: String, dt: f32) -> anyhow::Result<()> { + unsafe { + let c_string: CString = CString::new(tag)?; + (self.update_tag_fn)(c_string.as_ptr(), dt); + Ok(()) + } + } + + pub fn destroy_all(&mut self) -> anyhow::Result<()> { + unsafe { + (self.destroy_all_fn)(); + Ok(()) + } + } + + pub fn destroy_tagged(&mut self, tag: String) -> anyhow::Result<()> { + unsafe { + let c_string: CString = CString::new(tag)?; + (self.destroy_tagged_fn)(c_string.as_ptr()); + Ok(()) + } + } +} + +/// Displays the types of errors that can be returned by the native library. +pub enum DropbearNativeError { + Success = 0, + NullPointer = -1, + QueryFailed = -2, + EntityNotFound = -3, + NoSuchComponent = -4, + NoSuchEntity = -5, + WorldInsertError = -6, + + InvalidUTF8 = -108, + /// A generic error when the library doesn't know what happened or cannot find a + /// suitable error code. + /// + /// The number `1274` comes from the total sum of the word "UnknownError" in decimal + UnknownError = -1274, +} @@ -0,0 +1,157 @@ +use std::ffi::{c_char, CStr}; +use winit::keyboard::{KeyCode, PhysicalKey}; +use winit::platform::scancode::PhysicalKeyExtScancode; +use dropbear_engine::entity::{AdoptedEntity, Transform}; +use crate::ptr::InputStatePtr; +use crate::scripting::native::DropbearNativeError; +use crate::utils::keycode_from_ordinal; + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn dropbear_get_entity( + label: *const c_char, + world_ptr: *const hecs::World, + out_entity: *mut i64, +) -> i32 { + unsafe { + if label.is_null() || world_ptr.is_null() || out_entity.is_null() { + println!("[dropbear_get_entity] [ERROR] received null pointer"); + return -1; + } + + let world = &*world_ptr; + + let label_str = match CStr::from_ptr(label).to_str() { + Ok(s) => s, + Err(_) => { + println!("[dropbear_get_entity] [ERROR] invalid UTF-8 in label"); + return -108; + } + }; + + let mut hit: bool = false; + + for (id, entity) in world.query::<&AdoptedEntity>().iter() { + if entity.model.label == label_str { + #[allow(unused_assignments)] + { hit = true; } + *out_entity = id.id() as i64; + log::debug!("Found entity with label: {:?}", label_str); + return 0; + } + } + + if !hit { + println!("[dropbear_get_entity] [ERROR] Entity with label '{:?}' not found", label_str); + -3 + } else { + DropbearNativeError::UnknownError as i32 + } + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn dropbear_get_transform( + world_ptr: *const hecs::World, + entity_id: i64, + out_transform: *mut Transform, +) -> i32 { + if world_ptr.is_null() { + eprintln!("[dropbear_get_transform] [ERROR] World pointer is null"); + return DropbearNativeError::NullPointer as i32; + } + + if out_transform.is_null() { + eprintln!("[dropbear_get_transform] [ERROR] Output transform pointer is null"); + return DropbearNativeError::NullPointer as i32; + } + + let world = unsafe { &*world_ptr }; + + let entity = unsafe { world.find_entity_from_id(entity_id as u32) }; + + match world.query_one::<&Transform>(entity) { + Ok(mut q) => { + if let Some(transform) = q.get() { + unsafe { *out_transform = *transform }; + DropbearNativeError::Success as i32 + } else { + eprintln!("[dropbear_get_transform] [ERROR] Entity has no Transform component"); + -4 + } + } + Err(_) => { + eprintln!("[dropbear_get_transform] [ERROR] Failed to query entity for Transform component"); + -2 + } + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn dropbear_set_transform( + world_ptr: *mut hecs::World, + entity_id: i64, + transform: Transform, +) -> i32 { + if world_ptr.is_null() { + eprintln!("[dropbear_get_transform] [ERROR] World pointer is null"); + return DropbearNativeError::NullPointer as i32; + } + + let world = unsafe { &mut *world_ptr }; + + let entity = unsafe { world.find_entity_from_id(entity_id as u32) }; + + let result = world.insert_one(entity, transform); + + match result { + Ok(_) => 0, + Err(_) => { + eprintln!("[dropbear_set_transform] [ERROR] Failed to insert transform component"); + -6 + }, + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn dropbear_print_input_state( + input_state_ptr: InputStatePtr, +) { + if input_state_ptr.is_null() { + eprintln!("[dropbear_print_inputstate] [ERROR] Input state pointer is null"); + return; + } + + let input_state = unsafe { &*input_state_ptr }; + println!("{:#?}", input_state); +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn dropbear_is_key_pressed( + input_state_ptr: InputStatePtr, + key: i32, + out_is_pressed: *mut bool, +) -> i32 { + if input_state_ptr.is_null() { + eprintln!("[dropbear_is_key_pressed] [ERROR] Input state pointer is null"); + unsafe { *out_is_pressed = false }; + return DropbearNativeError::NullPointer as i32; + } + + let input = unsafe { &*input_state_ptr }; + + match keycode_from_ordinal(key) { + Some(k) => { + println!("[dropbear_is_key_pressed] [DEBUG] Keycode: {:?}", k); + if input.pressed_keys.contains(&k) { + true.into() + } else { + false.into() + } + } + None => { + println!("[dropbear_is_key_pressed] [WARN] Ordinal keycode is invalid"); + false.into() + } + } +} + @@ -0,0 +1,16 @@ +/// Different signatures for Native implementations +use std::ffi::c_char; +use crate::ptr::{InputStatePtr, WorldPtr}; + +/// CName: `dropbear_init` +pub type Init = unsafe extern "C" fn(world: WorldPtr, input: InputStatePtr) -> i32; +/// CName: `dropbear_load_tagged` +pub type LoadTagged = unsafe extern "C" fn(tag: *const c_char) -> i32; +/// CName: `dropbear_update_all` +pub type UpdateAll = unsafe extern "C" fn(dt: f32) -> i32; +/// CName: `dropbear_update_tagged` +pub type UpdateTagged = unsafe extern "C" fn(tag: *const c_char, dt: f32) -> i32; +/// CName: `dropbear_destroy_tagged` +pub type DestroyTagged = unsafe extern "C" fn(tag: *const c_char) -> i32; +/// CName: `dropbear_destroy_all` +pub type DestroyAll = unsafe extern "C" fn() -> i32; @@ -1,3 +1,4 @@ +use winit::keyboard::KeyCode; use crate::states::Node; pub const PROTO_TEXTURE: &[u8] = include_bytes!("../../resources/textures/proto.png"); @@ -36,3 +37,203 @@ pub enum ViewportMode { CameraMove, Gizmo, } + +pub fn keycode_from_ordinal(ordinal: i32) -> Option<KeyCode> { + match ordinal { + 0 => Some(KeyCode::Backquote), + 1 => Some(KeyCode::Backslash), + 2 => Some(KeyCode::BracketLeft), + 3 => Some(KeyCode::BracketRight), + 4 => Some(KeyCode::Comma), + 5 => Some(KeyCode::Digit0), + 6 => Some(KeyCode::Digit1), + 7 => Some(KeyCode::Digit2), + 8 => Some(KeyCode::Digit3), + 9 => Some(KeyCode::Digit4), + 10 => Some(KeyCode::Digit5), + 11 => Some(KeyCode::Digit6), + 12 => Some(KeyCode::Digit7), + 13 => Some(KeyCode::Digit8), + 14 => Some(KeyCode::Digit9), + 15 => Some(KeyCode::Equal), + 16 => Some(KeyCode::IntlBackslash), + 17 => Some(KeyCode::IntlRo), + 18 => Some(KeyCode::IntlYen), + 19 => Some(KeyCode::KeyA), + 20 => Some(KeyCode::KeyB), + 21 => Some(KeyCode::KeyC), + 22 => Some(KeyCode::KeyD), + 23 => Some(KeyCode::KeyE), + 24 => Some(KeyCode::KeyF), + 25 => Some(KeyCode::KeyG), + 26 => Some(KeyCode::KeyH), + 27 => Some(KeyCode::KeyI), + 28 => Some(KeyCode::KeyJ), + 29 => Some(KeyCode::KeyK), + 30 => Some(KeyCode::KeyL), + 31 => Some(KeyCode::KeyM), + 32 => Some(KeyCode::KeyN), + 33 => Some(KeyCode::KeyO), + 34 => Some(KeyCode::KeyP), + 35 => Some(KeyCode::KeyQ), + 36 => Some(KeyCode::KeyR), + 37 => Some(KeyCode::KeyS), + 38 => Some(KeyCode::KeyT), + 39 => Some(KeyCode::KeyU), + 40 => Some(KeyCode::KeyV), + 41 => Some(KeyCode::KeyW), + 42 => Some(KeyCode::KeyX), + 43 => Some(KeyCode::KeyY), + 44 => Some(KeyCode::KeyZ), + 45 => Some(KeyCode::Minus), + 46 => Some(KeyCode::Period), + 47 => Some(KeyCode::Quote), + 48 => Some(KeyCode::Semicolon), + 49 => Some(KeyCode::Slash), + 50 => Some(KeyCode::AltLeft), + 51 => Some(KeyCode::AltRight), + 52 => Some(KeyCode::Backspace), + 53 => Some(KeyCode::CapsLock), + 54 => Some(KeyCode::ContextMenu), + 55 => Some(KeyCode::ControlLeft), + 56 => Some(KeyCode::ControlRight), + 57 => Some(KeyCode::Enter), + 58 => Some(KeyCode::SuperLeft), + 59 => Some(KeyCode::SuperRight), + 60 => Some(KeyCode::ShiftLeft), + 61 => Some(KeyCode::ShiftRight), + 62 => Some(KeyCode::Space), + 63 => Some(KeyCode::Tab), + 64 => Some(KeyCode::Convert), + 65 => Some(KeyCode::KanaMode), + 66 => Some(KeyCode::Lang1), + 67 => Some(KeyCode::Lang2), + 68 => Some(KeyCode::Lang3), + 69 => Some(KeyCode::Lang4), + 70 => Some(KeyCode::Lang5), + 71 => Some(KeyCode::NonConvert), + 72 => Some(KeyCode::Delete), + 73 => Some(KeyCode::End), + 74 => Some(KeyCode::Help), + 75 => Some(KeyCode::Home), + 76 => Some(KeyCode::Insert), + 77 => Some(KeyCode::PageDown), + 78 => Some(KeyCode::PageUp), + 79 => Some(KeyCode::ArrowDown), + 80 => Some(KeyCode::ArrowLeft), + 81 => Some(KeyCode::ArrowRight), + 82 => Some(KeyCode::ArrowUp), + 83 => Some(KeyCode::NumLock), + 84 => Some(KeyCode::Numpad0), + 85 => Some(KeyCode::Numpad1), + 86 => Some(KeyCode::Numpad2), + 87 => Some(KeyCode::Numpad3), + 88 => Some(KeyCode::Numpad4), + 89 => Some(KeyCode::Numpad5), + 90 => Some(KeyCode::Numpad6), + 91 => Some(KeyCode::Numpad7), + 92 => Some(KeyCode::Numpad8), + 93 => Some(KeyCode::Numpad9), + 94 => Some(KeyCode::NumpadAdd), + 95 => Some(KeyCode::NumpadBackspace), + 96 => Some(KeyCode::NumpadClear), + 97 => Some(KeyCode::NumpadClearEntry), + 98 => Some(KeyCode::NumpadComma), + 99 => Some(KeyCode::NumpadDecimal), + 100 => Some(KeyCode::NumpadDivide), + 101 => Some(KeyCode::NumpadEnter), + 102 => Some(KeyCode::NumpadEqual), + 103 => Some(KeyCode::NumpadHash), + 104 => Some(KeyCode::NumpadMemoryAdd), + 105 => Some(KeyCode::NumpadMemoryClear), + 106 => Some(KeyCode::NumpadMemoryRecall), + 107 => Some(KeyCode::NumpadMemoryStore), + 108 => Some(KeyCode::NumpadMemorySubtract), + 109 => Some(KeyCode::NumpadMultiply), + 110 => Some(KeyCode::NumpadParenLeft), + 111 => Some(KeyCode::NumpadParenRight), + 112 => Some(KeyCode::NumpadStar), + 113 => Some(KeyCode::NumpadSubtract), + 114 => Some(KeyCode::Escape), + 115 => Some(KeyCode::Fn), + 116 => Some(KeyCode::FnLock), + 117 => Some(KeyCode::PrintScreen), + 118 => Some(KeyCode::ScrollLock), + 119 => Some(KeyCode::Pause), + 120 => Some(KeyCode::BrowserBack), + 121 => Some(KeyCode::BrowserFavorites), + 122 => Some(KeyCode::BrowserForward), + 123 => Some(KeyCode::BrowserHome), + 124 => Some(KeyCode::BrowserRefresh), + 125 => Some(KeyCode::BrowserSearch), + 126 => Some(KeyCode::BrowserStop), + 127 => Some(KeyCode::Eject), + 128 => Some(KeyCode::LaunchApp1), + 129 => Some(KeyCode::LaunchApp2), + 130 => Some(KeyCode::LaunchMail), + 131 => Some(KeyCode::MediaPlayPause), + 132 => Some(KeyCode::MediaSelect), + 133 => Some(KeyCode::MediaStop), + 134 => Some(KeyCode::MediaTrackNext), + 135 => Some(KeyCode::MediaTrackPrevious), + 136 => Some(KeyCode::Power), + 137 => Some(KeyCode::Sleep), + 138 => Some(KeyCode::AudioVolumeDown), + 139 => Some(KeyCode::AudioVolumeMute), + 140 => Some(KeyCode::AudioVolumeUp), + 141 => Some(KeyCode::WakeUp), + 142 => Some(KeyCode::Meta), + 143 => Some(KeyCode::Hyper), + 144 => Some(KeyCode::Turbo), + 145 => Some(KeyCode::Abort), + 146 => Some(KeyCode::Resume), + 147 => Some(KeyCode::Suspend), + 148 => Some(KeyCode::Again), + 149 => Some(KeyCode::Copy), + 150 => Some(KeyCode::Cut), + 151 => Some(KeyCode::Find), + 152 => Some(KeyCode::Open), + 153 => Some(KeyCode::Paste), + 154 => Some(KeyCode::Props), + 155 => Some(KeyCode::Select), + 156 => Some(KeyCode::Undo), + 157 => Some(KeyCode::Hiragana), + 158 => Some(KeyCode::Katakana), + 159 => Some(KeyCode::F1), + 160 => Some(KeyCode::F2), + 161 => Some(KeyCode::F3), + 162 => Some(KeyCode::F4), + 163 => Some(KeyCode::F5), + 164 => Some(KeyCode::F6), + 165 => Some(KeyCode::F7), + 166 => Some(KeyCode::F8), + 167 => Some(KeyCode::F9), + 168 => Some(KeyCode::F10), + 169 => Some(KeyCode::F11), + 170 => Some(KeyCode::F12), + 171 => Some(KeyCode::F13), + 172 => Some(KeyCode::F14), + 173 => Some(KeyCode::F15), + 174 => Some(KeyCode::F16), + 175 => Some(KeyCode::F17), + 176 => Some(KeyCode::F18), + 177 => Some(KeyCode::F19), + 178 => Some(KeyCode::F20), + 179 => Some(KeyCode::F21), + 180 => Some(KeyCode::F22), + 181 => Some(KeyCode::F23), + 182 => Some(KeyCode::F24), + 183 => Some(KeyCode::F25), + 184 => Some(KeyCode::F26), + 185 => Some(KeyCode::F27), + 186 => Some(KeyCode::F28), + 187 => Some(KeyCode::F29), + 188 => Some(KeyCode::F30), + 189 => Some(KeyCode::F31), + 190 => Some(KeyCode::F32), + 191 => Some(KeyCode::F33), + 192 => Some(KeyCode::F34), + 193 => Some(KeyCode::F35), + _ => None, + } +} @@ -798,16 +798,6 @@ impl<'a> TabViewer for EditorTabViewer<'a> { ui.horizontal(|ui| { ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { if ui.button("Remove Component ❌").clicked() { - // if let Some(target) = &follow_target { - // *self.signal = Signal::RemoveComponent( - // *entity, - // Box::new(ComponentType::Camera( - // Box::new(camera.clone()), - // camera_component.clone(), - // Some((*target).to_owned()), - // )), - // ); - // } else { *self.signal = Signal::RemoveComponent( *entity, Box::new(ComponentType::Camera( @@ -839,17 +829,6 @@ impl<'a> TabViewer for EditorTabViewer<'a> { &mut camera.label.clone(), ); - // if let Some(target) = follow_target { - // target.inspect( - // entity, - // &mut cfg, - // ui, - // self.undo_stack, - // self.signal, - // &mut camera.label.clone(), - // ); - // } - ui.separator(); ui.label("Camera Controls:"); let mut active_camera = self.active_camera.lock(); @@ -313,15 +313,38 @@ impl Mouse for Editor { } impl Controller for Editor { - fn button_down(&mut self, _button: Button, _id: GamepadId) {} + fn button_down(&mut self, button: Button, id: GamepadId) { + self.input_state + .pressed_buttons + .entry(id) + .or_insert_with(HashSet::new) + .insert(button); + } - fn button_up(&mut self, _button: Button, _id: GamepadId) {} + fn button_up(&mut self, button: Button, id: GamepadId) { + if let Some(buttons) = self.input_state.pressed_buttons.get_mut(&id) { + buttons.remove(&button); + } + } - fn left_stick_changed(&mut self, _x: f32, _y: f32, _id: GamepadId) {} + fn left_stick_changed(&mut self, x: f32, y: f32, id: GamepadId) { + self.input_state.left_stick_position.insert(id, (x, y)); + } - fn right_stick_changed(&mut self, _x: f32, _y: f32, _id: GamepadId) {} + fn right_stick_changed(&mut self, x: f32, y: f32, id: GamepadId) { + self.input_state.right_stick_position.insert(id, (x, y)); + } - fn on_connect(&mut self, _id: GamepadId) {} + fn on_connect(&mut self, id: GamepadId) { + self.input_state.connected_gamepads.insert(id); + } - fn on_disconnect(&mut self, _id: GamepadId) {} -} + fn on_disconnect(&mut self, id: GamepadId) { + self.input_state.connected_gamepads.remove(&id); + self.input_state.pressed_buttons.remove(&id); + self.input_state.left_stick_position.remove(&id); + self.input_state.right_stick_position.remove(&id); + self.input_state.left_trigger.remove(&id); + self.input_state.right_trigger.remove(&id); + } +} @@ -84,7 +84,7 @@ pub struct Editor { pub play_mode_backup: Option<PlayModeBackup>, /// State of the input - pub(crate) input_state: InputState, + pub(crate) input_state: Box<InputState>, // channels /// A threadsafe Unbounded Receiver, typically used for checking the status of the world loading @@ -181,7 +181,7 @@ impl Editor { editor_state: EditorState::Editing, gizmo_mode: EnumSet::empty(), play_mode_backup: None, - input_state: InputState::new(), + input_state: Box::new(InputState::new()), light_manager: LightManager::new(), active_camera: Arc::new(Mutex::new(None)), progress_tx: None, @@ -17,6 +17,7 @@ use std::collections::HashMap; use std::path::PathBuf; use std::sync::Arc; use winit::keyboard::KeyCode; +use eucalyptus_core::input::InputState; pub trait SignalController { fn run_signal(&mut self, graphics: Arc<SharedGraphicsContext>) -> anyhow::Result<()>; @@ -257,11 +258,12 @@ impl SignalController for Editor { } let world_ptr = self.world.as_mut() as *mut World; + let input_ptr = self.input_state.as_mut() as *mut InputState; if let Err(e) = self.script_manager .load_script( world_ptr, - &self.input_state, + input_ptr, ) { fatal!( "Failed to initialise script because {}", @@ -453,11 +455,12 @@ impl SignalController for Editor { } let world_ptr = self.world.as_mut() as *mut World; + let input_ptr = self.input_state.as_mut() as *mut InputState; if let Err(e) = self.script_manager .load_script( world_ptr, - &self.input_state, + input_ptr, ) { fatal!( "Failed to initialise script because {}", @@ -1,8 +1,25 @@ +org.gradle.parallel=true +org.gradle.caching=true +org.gradle.daemon=true +org.gradle.workers.max=8 + +# windows specific +org.gradle.vfs.watch=false +org.gradle.unsafe.watch-fs=false + +# config cache +org.gradle.configuration-cache=true +org.gradle.configuration-cache.problems=warn + +# Kotlin stuff +kotlin.incremental=true +kotlin.compiler.execution.strategy=daemon kotlin.code.style=official +# other optimisations +scan=false + +# Dokka docs org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled org.jetbrains.dokka.experimental.gradle.pluginMode.noWarn=true -org.gradle.jvmargs=-Xmx2g -Xms512m -XX:MaxMetaspaceSize=512m -org.gradle.daemon=true -org.gradle.caching=true @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME @@ -4,7 +4,12 @@ #include <stddef.h> #include <stdint.h> +// =========================================== + typedef struct World World; // opaque pointer +typedef struct InputState InputState; // opaque pointer + +// =========================================== #ifdef __cplusplus extern "C" { @@ -41,6 +46,10 @@ int dropbear_set_transform( const NativeTransform transform ); +void dropbear_print_input_state(const InputState* input_state_ptr); + +int dropbear_is_key_pressed(const InputState* input_state_ptr, int keycode, int* out_value); // out_value is a boolean 0 or 1 + // =========================================== #ifdef __cplusplus @@ -19,20 +19,21 @@ impl Generator for KotlinNativeGenerator { output, "@file:OptIn(ExperimentalForeignApi::class, ExperimentalNativeApi::class)" )?; + writeln!(output, "@file:Suppress(\"UNUSED_PARAMETER\", \"unused\")")?; writeln!(output)?; writeln!(output, "package com.dropbear.decl")?; writeln!(output)?; - writeln!(output, "import com.dropbear.DropbearEngine")?; - writeln!(output, "import com.dropbear.EntityId")?; - writeln!(output, "import com.dropbear.EntityRef")?; - writeln!(output, "import com.dropbear.System")?; - writeln!(output, "import com.dropbear.ffi.NativeEngine")?; - writeln!(output, "import kotlinx.cinterop.COpaquePointer")?; - writeln!(output, "import kotlinx.cinterop.ExperimentalForeignApi")?; - writeln!(output, "import kotlin.experimental.ExperimentalNativeApi")?; - writeln!(output, "import kotlin.native.CName")?; + writeln!(output, r#" +import com.dropbear.DropbearEngine +import com.dropbear.System +import com.dropbear.ffi.NativeEngine +import com.dropbear.logging.Logger +import kotlinx.cinterop.COpaquePointer +import kotlinx.cinterop.ExperimentalForeignApi +import kotlin.experimental.ExperimentalNativeApi + "#)?; writeln!(output)?; let mut imported_classes = Vec::new(); @@ -59,78 +60,176 @@ impl Generator for KotlinNativeGenerator { } } - writeln!(output, "private fun getScriptFactories(tag: String): List<() -> System> {{")?; - writeln!(output, " return when (tag) {{")?; - - for (tag, classes) in &tag_map { - let factories: Vec<String> = classes - .iter() - .map(|cls| format!("{{ {}() }}", cls)) - .collect(); - writeln!( - output, - " \"{}\" -> listOf({})", - tag, - factories.join(", ") - )?; - } - writeln!(output, " else -> emptyList()")?; - writeln!(output, " }}")?; - writeln!(output, "}}")?; - writeln!(output)?; + writeln!(output, r#" +object ScriptManager {{ + private var dropbearEngine: DropbearEngine? = null + private val scriptsByTag: MutableMap<String, MutableList<System>> = mutableMapOf() - writeln!( - output, - "private fun getDropbearEngine(worldPointer: COpaquePointer?): DropbearEngine {{" - )?; - writeln!(output, " val nativeEngine = NativeEngine()")?; - writeln!(output, " nativeEngine.init(worldPointer)")?; - writeln!(output, " return DropbearEngine(nativeEngine)")?; - writeln!(output, "}}")?; - writeln!(output)?; + fun init(worldPtr: COpaquePointer?, inputPtr: COpaquePointer?) : Int {{ + try {{ + val nativeEngine = NativeEngine() + nativeEngine.init(worldPtr, inputPtr) + dropbearEngine = DropbearEngine(nativeEngine) - for (func_name, method_name) in [ - ("dropbear_load", "load"), - ("dropbear_update", "update"), - ("dropbear_destroy", "destroy"), - ] { - let param_extra = if method_name == "update" { - ", deltaTime: Double" - } else { - "" - }; - let call_extra = if method_name == "update" { - ", deltaTime.toFloat()" - } else { - "" - }; - - writeln!(output, "@CName(\"{}\")", func_name)?; - writeln!( - output, - "fun {}ScriptByTag(worldPointer: COpaquePointer?, currentEntity: Long?, tag: String?{}) {{", - method_name, - param_extra - )?; - writeln!(output, " if (tag == null) return")?; - writeln!(output, " val factories = getScriptFactories(tag)")?; - writeln!( - output, - " val engine = getDropbearEngine(worldPointer)" - )?; - writeln!(output, " for (factory in factories) {{")?; - writeln!(output, " val script = factory()")?; - if method_name == "update" { - writeln!(output, " script.{}(engine{})", method_name, call_extra)?; - } else { - writeln!(output, " script.{}(engine)", method_name)?; + scriptsByTag.clear() + Logger.debug("Native ScriptManager initialised") + return 0 + }} catch (e: Exception) {{ + Logger.error("Native ScriptManager failed to initialise: ${{e.message}}") + e.printStackTrace() + return -1 + }} + }} + + fun loadSystemsByTag(tag: String): Int {{ + val engine = dropbearEngine ?: return -2 + try {{ + val factories = getScriptFactories(tag) + val instances = factories.map {{ it() }} + + for (instance in instances) {{ + instance.load(engine) + }} + + scriptsByTag.getOrPut(tag) {{ mutableListOf() }}.addAll(instances) + Logger.debug("Loaded ${{instances.size}} script(s) for tag: '$tag'") + return 0 + }} catch (e: Exception) {{ + Logger.error("Error loading systems for tag '$tag': ${{e.message}}") + e.printStackTrace() + return -1 + }} + }} + + fun updateAllSystems(dt: Float): Int {{ + val engine = dropbearEngine ?: return -2 + try {{ + for (instances in scriptsByTag.values) {{ + for (instance in instances) {{ + instance.update(engine, dt) + }} + }} + return 0 + }} catch (e: Exception) {{ + Logger.error("Error updating all systems: ${{e.message}}") + e.printStackTrace() + return -1 + }} + }} + + fun updateSystemsByTag(tag: String, dt: Float): Int {{ + val engine = dropbearEngine ?: return -2 + try {{ + val instances = scriptsByTag[tag] ?: emptyList() + for (instance in instances) {{ + instance.update(engine, dt) + }} + return 0 + }} catch (e: Exception) {{ + Logger.error("Error updating systems for tag '$tag': ${{e.message}}") + e.printStackTrace() + return -1 + }} + }} + + fun destroyByTag(tag: String): Int {{ + try {{ + val engine = dropbearEngine ?: return -2 + val instances = scriptsByTag[tag] ?: emptyList() + for (instance in instances) {{ + instance.destroy(engine) + }} + scriptsByTag.remove(tag) + Logger.debug("Destroyed ${{instances.size}} script(s) for tag: '$tag'") + return 0 + }} catch (e: Exception) {{ + Logger.error("Error destroying systems for tag '$tag': ${{e.message}}") + e.printStackTrace() + return -1 + }} + }} + + fun destroyAll(): Int {{ + try {{ + val engine = dropbearEngine ?: return -2 + for (instances in scriptsByTag.values) {{ + for (instance in instances) {{ + instance.destroy(engine) + }} + }} + scriptsByTag.clear() + dropbearEngine = null + return 0 + }} catch (e: Exception) {{ + Logger.error("Error destroying scripts: ${{e.message}}") + e.printStackTrace() + return -1 + }} + }} + "#)?; + // getScriptFactories (generated) + { + writeln!(output, " private fun getScriptFactories(tag: String): List<() -> System> {{")?; + writeln!(output, " return when (tag) {{")?; + + for (tag, classes) in &tag_map { + let factories: Vec<String> = classes + .iter() + .map(|cls| format!("{{ {}() }}", cls)) + .collect(); + writeln!( + output, + " \"{}\" -> listOf({})", + tag, + factories.join(", ") + )?; } - writeln!(output, " }}")?; - writeln!(output, "}}")?; + + writeln!(output, " else -> emptyList()")?; + writeln!(output, " }}")?; + writeln!(output, " }}")?; writeln!(output)?; } + writeln!(output, "}}")?; + + writeln!(output, r#" +@CName("dropbear_init") +fun dropbear_native_init(worldPtr: COpaquePointer?, inputStatePtr: COpaquePointer?): Int {{ + return ScriptManager.init(worldPtr, inputStatePtr) +}} + + +@CName("dropbear_load_tagged") +fun dropbear_load_systems_for_tag(tag: String?): Int {{ + if (tag == null) return -1 + return ScriptManager.loadSystemsByTag(tag) +}} + +@CName("dropbear_update_all") +fun dropbear_update_all_systems(dt: Float): Int {{ + return ScriptManager.updateAllSystems(dt) +}} + +@CName("dropbear_update_tagged") +fun dropbear_update_systems_for_tag(tag: String?, dt: Float): Int {{ + if (tag == null) return -1 + return ScriptManager.updateSystemsByTag(tag, dt) +}} + +@CName("dropbear_destroy_tagged") +fun dropbear_destroy(tag: String?): Int {{ + if (tag == null) return -1 + return ScriptManager.destroyByTag(tag) +}} + +@CName("dropbear_destroy_all") +fun dropbear_destroy_all(): Int {{ + return ScriptManager.destroyAll() +}} + "#)?; + Ok(output) } } @@ -1,9 +1,13 @@ package com.dropbear import com.dropbear.ffi.NativeEngine +import com.dropbear.input.InputState +import com.dropbear.logging.Logger import com.dropbear.math.Transform class DropbearEngine(val native: NativeEngine) { + private var inputState: InputState? = null + public fun getEntity(label: String): EntityRef? { val entityId = native.getEntity(label) val entityRef = if (entityId != null) EntityRef(EntityId(entityId)) else null @@ -11,6 +15,14 @@ class DropbearEngine(val native: NativeEngine) { return entityRef } + public fun getInputState(): InputState { + if (this.inputState == null) { + Logger.trace("InputState not initialised, creating new one") + this.inputState = InputState(this) + } + return this.inputState!! + } + internal fun getTransform(entityId: EntityId): Transform? { val result = native.getTransform(entityId) return result @@ -1,10 +1,32 @@ package com.dropbear.ffi import com.dropbear.EntityId +import com.dropbear.input.KeyCode import com.dropbear.math.Transform expect class NativeEngine { + /** + * Fetches the entity from its label, returning a [Long] (the entity ID) + */ fun getEntity(label: String): Long? + + /** + * Fetches the [Transform] component of an entity by it's ID + */ fun getTransform(entityId: EntityId): Transform? + + /** + * Sets an entities [Transform] component. + */ fun setTransform(entityId: EntityId, transform: Transform) + + /** + * Prints the input state, typically used for debugging. + */ + fun printInputState() + + /** + * Checks if a Key is pressed by its KeyCode + */ + fun isKeyPressed(key: KeyCode): Boolean } @@ -0,0 +1,14 @@ +package com.dropbear.input + +import com.dropbear.DropbearEngine + +class InputState(private val engine: DropbearEngine) { + + fun printInputState() { + engine.native.printInputState() + } + + fun isKeyPressed(key: KeyCode): Boolean { + return engine.native.isKeyPressed(key) + } +} @@ -0,0 +1,970 @@ +package com.dropbear.input + +/** + * Enum values representing KeyCodes. Taken from the enum `winit::keyboard::KeyCode`. + * + * Code representing the location of a physical key. + * + * This mostly conforms to the UI Events Specification's [KeyboardEvent.code](https://w3c.github.io/uievents-code/#code-value-tables) + * with a few exceptions: + * - The keys that the specification calls "MetaLeft" and "MetaRight" are named "SuperLeft" and + * "SuperRight" here. + * - The key that the specification calls "Super" is reported as `Unidentified` here. + */ +@Suppress("unused") +public enum class KeyCode { + /** + * <kbd>`</kbd> on a US keyboard. This is also called a backtick or grave. + * This is the <kbd>半角</kbd>/<kbd>全角</kbd>/<kbd>漢字</kbd> + * (hankaku/zenkaku/kanji) key on Japanese keyboards + */ + Backquote, + + /** + * Used for both the US <kbd>\\</kbd> (on the 101-key layout) and also for the key + * located between the <kbd>"</kbd> and <kbd>Enter</kbd> keys on row C of the 102-, + * 104- and 106-key layouts. + * Labeled <kbd>#</kbd> on a UK (102) keyboard. + */ + Backslash, + + /** + * <kbd>[</kbd> on a US keyboard. + */ + BracketLeft, + + /** + * <kbd>]</kbd> on a US keyboard. + */ + BracketRight, + + /** + * <kbd>,</kbd> on a US keyboard. + */ + Comma, + + /** + * <kbd>0</kbd> on a US keyboard. + */ + Digit0, + + /** + * <kbd>1</kbd> on a US keyboard. + */ + Digit1, + + /** + * <kbd>2</kbd> on a US keyboard. + */ + Digit2, + + /** + * <kbd>3</kbd> on a US keyboard. + */ + Digit3, + + /** + * <kbd>4</kbd> on a US keyboard. + */ + Digit4, + + /** + * <kbd>5</kbd> on a US keyboard. + */ + Digit5, + + /** + * <kbd>6</kbd> on a US keyboard. + */ + Digit6, + + /** + * <kbd>7</kbd> on a US keyboard. + */ + Digit7, + + /** + * <kbd>8</kbd> on a US keyboard. + */ + Digit8, + + /** + * <kbd>9</kbd> on a US keyboard. + */ + Digit9, + + /** + * <kbd>=</kbd> on a US keyboard. + */ + Equal, + + /** + * Located between the left <kbd>Shift</kbd> and <kbd>Z</kbd> keys. + * Labeled <kbd>\\</kbd> on a UK keyboard. + */ + IntlBackslash, + + /** + * Located between the <kbd>/</kbd> and right <kbd>Shift</kbd> keys. + * Labeled <kbd>\\</kbd> (ro) on a Japanese keyboard. + */ + IntlRo, + + /** + * Located between the <kbd>=</kbd> and <kbd>Backspace</kbd> keys. + * Labeled <kbd>¥</kbd> (yen) on a Japanese keyboard. <kbd>\\</kbd> on a + * Russian keyboard. + */ + IntlYen, + + /** + * <kbd>a</kbd> on a US keyboard. + * Labeled <kbd>q</kbd> on an AZERTY (e.g., French) keyboard. + */ + KeyA, + + /** + * <kbd>b</kbd> on a US keyboard. + */ + KeyB, + + /** + * <kbd>c</kbd> on a US keyboard. + */ + KeyC, + + /** + * <kbd>d</kbd> on a US keyboard. + */ + KeyD, + + /** + * <kbd>e</kbd> on a US keyboard. + */ + KeyE, + + /** + * <kbd>f</kbd> on a US keyboard. + */ + KeyF, + + /** + * <kbd>g</kbd> on a US keyboard. + */ + KeyG, + + /** + * <kbd>h</kbd> on a US keyboard. + */ + KeyH, + + /** + * <kbd>i</kbd> on a US keyboard. + */ + KeyI, + + /** + * <kbd>j</kbd> on a US keyboard. + */ + KeyJ, + + /** + * <kbd>k</kbd> on a US keyboard. + */ + KeyK, + + /** + * <kbd>l</kbd> on a US keyboard. + */ + KeyL, + + /** + * <kbd>m</kbd> on a US keyboard. + */ + KeyM, + + /** + * <kbd>n</kbd> on a US keyboard. + */ + KeyN, + + /** + * <kbd>o</kbd> on a US keyboard. + */ + KeyO, + + /** + * <kbd>p</kbd> on a US keyboard. + */ + KeyP, + + /** + * <kbd>q</kbd> on a US keyboard. + * Labeled <kbd>a</kbd> on an AZERTY (e.g., French) keyboard. + */ + KeyQ, + + /** + * <kbd>r</kbd> on a US keyboard. + */ + KeyR, + + /** + * <kbd>s</kbd> on a US keyboard. + */ + KeyS, + + /** + * <kbd>t</kbd> on a US keyboard. + */ + KeyT, + + /** + * <kbd>u</kbd> on a US keyboard. + */ + KeyU, + + /** + * <kbd>v</kbd> on a US keyboard. + */ + KeyV, + + /** + * <kbd>w</kbd> on a US keyboard. + * Labeled <kbd>z</kbd> on an AZERTY (e.g., French) keyboard. + */ + KeyW, + + /** + * <kbd>x</kbd> on a US keyboard. + */ + KeyX, + + /** + * <kbd>y</kbd> on a US keyboard. + * Labeled <kbd>z</kbd> on a QWERTZ (e.g., German) keyboard. + */ + KeyY, + + /** + * <kbd>z</kbd> on a US keyboard. + * Labeled <kbd>w</kbd> on an AZERTY (e.g., French) keyboard, and <kbd>y</kbd> on a + * QWERTZ (e.g., German) keyboard. + */ + KeyZ, + + /** + * <kbd>-</kbd> on a US keyboard. + */ + Minus, + + /** + * <kbd>.</kbd> on a US keyboard. + */ + Period, + + /** + * <kbd>'</kbd> on a US keyboard. + */ + Quote, + + /** + * <kbd>;</kbd> on a US keyboard. + */ + Semicolon, + + /** + * <kbd>/</kbd> on a US keyboard. + */ + Slash, + + /** + * <kbd>Alt</kbd>, <kbd>Option</kbd>, or <kbd>⌥</kbd>. + */ + AltLeft, + + /** + * <kbd>Alt</kbd>, <kbd>Option</kbd>, or <kbd>⌥</kbd>. + * This is labeled <kbd>AltGr</kbd> on many keyboard layouts. + */ + AltRight, + + /** + * <kbd>Backspace</kbd> or <kbd>⌫</kbd>. + * Labeled <kbd>Delete</kbd> on Apple keyboards. + */ + Backspace, + + /** + * <kbd>CapsLock</kbd> or <kbd>⇪</kbd> + */ + CapsLock, + + /** + * The application context menu key, which is typically found between the right + * <kbd>Super</kbd> key and the right <kbd>Control</kbd> key. + */ + ContextMenu, + + /** + * <kbd>Control</kbd> or <kbd>⌃</kbd> + */ + ControlLeft, + + /** + * <kbd>Control</kbd> or <kbd>⌃</kbd> + */ + ControlRight, + + /** + * <kbd>Enter</kbd> or <kbd>↵</kbd>. Labeled <kbd>Return</kbd> on Apple keyboards. + */ + Enter, + + /** + * The Windows, <kbd>⌘</kbd>, <kbd>Command</kbd>, or other OS symbol key. + */ + SuperLeft, + + /** + * The Windows, <kbd>⌘</kbd>, <kbd>Command</kbd>, or other OS symbol key. + */ + SuperRight, + + /** + * <kbd>Shift</kbd> or <kbd>⇧</kbd> + */ + ShiftLeft, + + /** + * <kbd>Shift</kbd> or <kbd>⇧</kbd> + */ + ShiftRight, + + /** + * <kbd> </kbd> (space) + */ + Space, + + /** + * <kbd>Tab</kbd> or <kbd>⇥</kbd> + */ + Tab, + + /** + * Japanese: <kbd>変</kbd> (henkan) + */ + Convert, + + /** + * Japanese: <kbd>カタカナ</kbd>/<kbd>ひらがな</kbd>/<kbd>ローマ字</kbd> + * (katakana/hiragana/romaji) + */ + KanaMode, + + /** + * Korean: HangulMode <kbd>한/영</kbd> (han/yeong) + * + * Japanese (Mac keyboard): <kbd>か</kbd> (kana) + */ + Lang1, + + /** + * Korean: Hanja <kbd>한</kbd> (hanja) + * + * Japanese (Mac keyboard): <kbd>英</kbd> (eisu) + */ + Lang2, + + /** + * Japanese (word-processing keyboard): Katakana + */ + Lang3, + + /** + * Japanese (word-processing keyboard): Hiragana + */ + Lang4, + + /** + * Japanese (word-processing keyboard): Zenkaku/Hankaku + */ + Lang5, + + /** + * Japanese: <kbd>無変換</kbd> (muhenkan) + */ + NonConvert, + + /** + * <kbd>⌦</kbd>. The forward delete key. + * Note that on Apple keyboards, the key labelled <kbd>Delete</kbd> on the main part of + * the keyboard is encoded as [Backspace]. + */ + Delete, + + /** + * <kbd>Page Down</kbd>, <kbd>End</kbd>, or <kbd>↘</kbd> + */ + End, + + /** + * <kbd>Help</kbd>. Not present on standard PC keyboards. + */ + Help, + + /** + * <kbd>Home</kbd> or <kbd>↖</kbd> + */ + Home, + + /** + * <kbd>Insert</kbd> or <kbd>Ins</kbd>. Not present on Apple keyboards. + */ + Insert, + + /** + * <kbd>Page Down</kbd>, <kbd>PgDn</kbd>, or <kbd>⇟</kbd> + */ + PageDown, + + /** + * <kbd>Page Up</kbd>, <kbd>PgUp</kbd>, or <kbd>⇞</kbd> + */ + PageUp, + + /** + * <kbd>↓</kbd> + */ + ArrowDown, + + /** + * <kbd>←</kbd> + */ + ArrowLeft, + + /** + * <kbd>→</kbd> + */ + ArrowRight, + + /** + * <kbd>↑</kbd> + */ + ArrowUp, + + /** + * On the Mac, this is used for the numpad <kbd>Clear</kbd> key. + */ + NumLock, + + /** + * <kbd>0 Ins</kbd> on a keyboard. <kbd>0</kbd> on a phone or remote control + */ + Numpad0, + + /** + * <kbd>1 End</kbd> on a keyboard. <kbd>1</kbd> or <kbd>1 QZ</kbd> on a phone or remote + * control + */ + Numpad1, + + /** + * <kbd>2 ↓</kbd> on a keyboard. <kbd>2 ABC</kbd> on a phone or remote control + */ + Numpad2, + + /** + * <kbd>3 PgDn</kbd> on a keyboard. <kbd>3 DEF</kbd> on a phone or remote control + */ + Numpad3, + + /** + * <kbd>4 ←</kbd> on a keyboard. <kbd>4 GHI</kbd> on a phone or remote control + */ + Numpad4, + + /** + * <kbd>5</kbd> on a keyboard. <kbd>5 JKL</kbd> on a phone or remote control + */ + Numpad5, + + /** + * <kbd>6 →</kbd> on a keyboard. <kbd>6 MNO</kbd> on a phone or remote control + */ + Numpad6, + + /** + * <kbd>7 Home</kbd> on a keyboard. <kbd>7 PQRS</kbd> or <kbd>7 PRS</kbd> on a phone + * or remote control + */ + Numpad7, + + /** + * <kbd>8 ↑</kbd> on a keyboard. <kbd>8 TUV</kbd> on a phone or remote control + */ + Numpad8, + + /** + * <kbd>9 PgUp</kbd> on a keyboard. <kbd>9 WXYZ</kbd> or <kbd>9 WXY</kbd> on a phone + * or remote control + */ + Numpad9, + + /** + * <kbd>+</kbd> + */ + NumpadAdd, + + /** + * Found on the Microsoft Natural Keyboard. + */ + NumpadBackspace, + + /** + * <kbd>C</kbd> or <kbd>A</kbd> (All Clear). Also for use with numpads that have a + * <kbd>Clear</kbd> key that is separate from the <kbd>NumLock</kbd> key. On the Mac, the + * numpad <kbd>Clear</kbd> key is encoded as [NumLock]. + */ + NumpadClear, + + /** + * <kbd>C</kbd> (Clear Entry) + */ + NumpadClearEntry, + + /** + * <kbd>,</kbd> (thousands separator). For locales where the thousands separator + * is a "." (e.g., Brazil), this key may generate a <kbd>.</kbd>. + */ + NumpadComma, + + /** + * <kbd>. Del</kbd>. For locales where the decimal separator is "," (e.g., + * Brazil), this key may generate a <kbd>,</kbd>. + */ + NumpadDecimal, + + /** + * <kbd>/</kbd> + */ + NumpadDivide, + + NumpadEnter, + + /** + * <kbd>=</kbd> + */ + NumpadEqual, + + /** + * <kbd>#</kbd> on a phone or remote control device. This key is typically found + * below the <kbd>9</kbd> key and to the right of the <kbd>0</kbd> key. + */ + NumpadHash, + + /** + * <kbd>M</kbd> Add current entry to the value stored in memory. + */ + NumpadMemoryAdd, + + /** + * <kbd>M</kbd> Clear the value stored in memory. + */ + NumpadMemoryClear, + + /** + * <kbd>M</kbd> Replace the current entry with the value stored in memory. + */ + NumpadMemoryRecall, + + /** + * <kbd>M</kbd> Replace the value stored in memory with the current entry. + */ + NumpadMemoryStore, + + /** + * <kbd>M</kbd> Subtract current entry from the value stored in memory. + */ + NumpadMemorySubtract, + + /** + * <kbd>*</kbd> on a keyboard. For use with numpads that provide mathematical + * operations (<kbd>+</kbd>, <kbd>-</kbd> <kbd>*</kbd> and <kbd>/</kbd>). + * + * Use `NumpadStar` for the <kbd>*</kbd> key on phones and remote controls. + */ + NumpadMultiply, + + /** + * <kbd>(</kbd> Found on the Microsoft Natural Keyboard. + */ + NumpadParenLeft, + + /** + * <kbd>)</kbd> Found on the Microsoft Natural Keyboard. + */ + NumpadParenRight, + + /** + * <kbd>*</kbd> on a phone or remote control device. + * + * This key is typically found below the <kbd>7</kbd> key and to the left of + * the <kbd>0</kbd> key. + * + * Use <kbd>"NumpadMultiply"</kbd> for the <kbd>*</kbd> key on + * numeric keypads. + */ + NumpadStar, + + /** + * <kbd>-</kbd> + */ + NumpadSubtract, + + /** + * <kbd>Esc</kbd> or <kbd>⎋</kbd> + */ + Escape, + + /** + * <kbd>Fn</kbd> This is typically a hardware key that does not generate a separate code. + */ + Fn, + + /** + * <kbd>FLock</kbd> or <kbd>FnLock</kbd>. Function Lock key. Found on the Microsoft + * Natural Keyboard. + */ + FnLock, + + /** + * <kbd>PrtScr SysRq</kbd> or <kbd>Print Screen</kbd> + */ + PrintScreen, + + /** + * <kbd>Scroll Lock</kbd> + */ + ScrollLock, + + /** + * <kbd>Pause Break</kbd> + */ + Pause, + + /** + * Some laptops place this key to the left of the <kbd>↑</kbd> key. + * + * This also the "back" button (triangle) on Android. + */ + BrowserBack, + + BrowserFavorites, + + /** + * Some laptops place this key to the right of the <kbd>↑</kbd> key. + */ + BrowserForward, + + /** + * The "home" button on Android. + */ + BrowserHome, + + BrowserRefresh, + + BrowserSearch, + + BrowserStop, + + /** + * <kbd>Eject</kbd> or <kbd>⏏</kbd>. This key is placed in the function section on some Apple + * keyboards. + */ + Eject, + + /** + * Sometimes labelled <kbd>My Computer</kbd> on the keyboard + */ + LaunchApp1, + + /** + * Sometimes labelled <kbd>Calculator</kbd> on the keyboard + */ + LaunchApp2, + + LaunchMail, + + MediaPlayPause, + + MediaSelect, + + MediaStop, + + MediaTrackNext, + + MediaTrackPrevious, + + /** + * This key is placed in the function section on some Apple keyboards, replacing the + * <kbd>Eject</kbd> key. + */ + Power, + + Sleep, + + AudioVolumeDown, + + AudioVolumeMute, + + AudioVolumeUp, + + WakeUp, + + // Legacy modifier key. Also called "Super" in certain places. + Meta, + + // Legacy modifier key. + Hyper, + + Turbo, + + Abort, + + Resume, + + Suspend, + + /** + * Found on Sun's USB keyboard. + */ + Again, + + /** + * Found on Sun's USB keyboard. + */ + Copy, + + /** + * Found on Sun's USB keyboard. + */ + Cut, + + /** + * Found on Sun's USB keyboard. + */ + Find, + + /** + * Found on Sun's USB keyboard. + */ + Open, + + /** + * Found on Sun's USB keyboard. + */ + Paste, + + /** + * Found on Sun's USB keyboard. + */ + Props, + + /** + * Found on Sun's USB keyboard. + */ + Select, + + /** + * Found on Sun's USB keyboard. + */ + Undo, + + /** + * Use for dedicated <kbd>ひらがな</kbd> key found on some Japanese word processing keyboards. + */ + Hiragana, + + /** + * Use for dedicated <kbd>カタカナ</kbd> key found on some Japanese word processing keyboards. + */ + Katakana, + + /** + * General-purpose function key. + * Usually found at the top of the keyboard. + */ + F1, + + /** + * General-purpose function key. + * Usually found at the top of the keyboard. + */ + F2, + + /** + * General-purpose function key. + * Usually found at the top of the keyboard. + */ + F3, + + /** + * General-purpose function key. + * Usually found at the top of the keyboard. + */ + F4, + + /** + * General-purpose function key. + * Usually found at the top of the keyboard. + */ + F5, + + /** + * General-purpose function key. + * Usually found at the top of the keyboard. + */ + F6, + + /** + * General-purpose function key. + * Usually found at the top of the keyboard. + */ + F7, + + /** + * General-purpose function key. + * Usually found at the top of the keyboard. + */ + F8, + + /** + * General-purpose function key. + * Usually found at the top of the keyboard. + */ + F9, + + /** + * General-purpose function key. + * Usually found at the top of the keyboard. + */ + F10, + + /** + * General-purpose function key. + * Usually found at the top of the keyboard. + */ + F11, + + /** + * General-purpose function key. + * Usually found at the top of the keyboard. + */ + F12, + + /** + * General-purpose function key. + * Usually found at the top of the keyboard. + */ + F13, + + /** + * General-purpose function key. + * Usually found at the top of the keyboard. + */ + F14, + + /** + * General-purpose function key. + * Usually found at the top of the keyboard. + */ + F15, + + /** + * General-purpose function key. + * Usually found at the top of the keyboard. + */ + F16, + + /** + * General-purpose function key. + * Usually found at the top of the keyboard. + */ + F17, + + /** + * General-purpose function key. + * Usually found at the top of the keyboard. + */ + F18, + + /** + * General-purpose function key. + * Usually found at the top of the keyboard. + */ + F19, + + /** + * General-purpose function key. + * Usually found at the top of the keyboard. + */ + F20, + + /** + * General-purpose function key. + * Usually found at the top of the keyboard. + */ + F21, + + /** + * General-purpose function key. + * Usually found at the top of the keyboard. + */ + F22, + + /** + * General-purpose function key. + * Usually found at the top of the keyboard. + */ + F23, + + /** + * General-purpose function key. + * Usually found at the top of the keyboard. + */ + F24, + + /** General-purpose function key. */ + F25, + + /** General-purpose function key. */ + F26, + + /** General-purpose function key. */ + F27, + + /** General-purpose function key. */ + F28, + + /** General-purpose function key. */ + F29, + + /** General-purpose function key. */ + F30, + + /** General-purpose function key. */ + F31, + + /** General-purpose function key. */ + F32, + + /** General-purpose function key. */ + F33, + + /** General-purpose function key. */ + F34, + + /** General-purpose function key. */ + F35, +} @@ -7,8 +7,14 @@ public class JNINative { System.loadLibrary("eucalyptus_core"); } + // entity and ecs public static native long getEntity(long handle, String label); + // transformations public static native Transform getTransform(long handle, long entityHandle); public static native void setTransform(long worldHandle, long id, Transform transform); + + // input + public static native void printInputState(long inputHandle); + public static native boolean isKeyPressed(long inputHandle, int ordinal); } @@ -1,18 +1,21 @@ package com.dropbear.ffi import com.dropbear.EntityId +import com.dropbear.input.KeyCode import com.dropbear.math.Transform actual class NativeEngine { private var worldHandle: Long = 0L + private var inputHandle: Long = 0L actual fun getEntity(label: String): Long? { return JNINative.getEntity(worldHandle, label) } @JvmName("init") - fun init(handle: Long) { - this.worldHandle = handle + fun init(worldHandle: Long, inputHandle: Long) { + this.worldHandle = worldHandle + this.inputHandle = inputHandle if (this.worldHandle < 0L) { println("NativeEngine: Error - Invalid world handle received!") return @@ -26,4 +29,12 @@ actual class NativeEngine { actual fun setTransform(entityId: EntityId, transform: Transform) { return JNINative.setTransform(worldHandle, entityId.id, transform) } + + actual fun printInputState() { + return JNINative.printInputState(inputHandle) + } + + actual fun isKeyPressed(key: KeyCode): Boolean { + return JNINative.isKeyPressed(inputHandle, key.ordinal) + } } @@ -5,6 +5,7 @@ package com.dropbear.ffi import com.dropbear.EntityId import com.dropbear.ffi.generated.* +import com.dropbear.input.KeyCode import com.dropbear.logging.Logger import com.dropbear.math.Transform import kotlinx.cinterop.* @@ -12,12 +13,17 @@ import kotlin.experimental.ExperimentalNativeApi actual class NativeEngine { private var worldHandle: COpaquePointer? = null + private var inputHandle: COpaquePointer? = null - @Suppress("unused") // called from jni - fun init(handle: COpaquePointer?) { - this.worldHandle = handle + @Suppress("unused") + fun init(worldHandle: COpaquePointer?, inputHandle: COpaquePointer?) { + this.worldHandle = worldHandle + this.inputHandle = inputHandle if (this.worldHandle == null) { - Logger.info("NativeEngine: Error - Invalid world handle received!") + Logger.error("NativeEngine: Error - Invalid world handle received!") + } + if (this.inputHandle == null) { + Logger.error("NativeEngine: Error - Invalid input handle received!") } } @@ -93,4 +99,22 @@ actual class NativeEngine { ) } } + + actual fun printInputState() { + val input = inputHandle ?: return + dropbear_print_input_state(input_state_ptr = input.reinterpret()) + } + + actual fun isKeyPressed(key: KeyCode): Boolean { + val input = inputHandle ?: return false + memScoped { + val out = alloc<IntVar>() + dropbear_is_key_pressed( + input.reinterpret(), + key.ordinal, + out.ptr + ) + return out.value != 0 + } + } }