kitgit

tirbofish/dropbear

main / crates / eucalyptus-core / src / utils.rs · 28808 bytes

crates/eucalyptus-core/src/utils.rs
//! Utility functions and helpers

pub mod option;

use crate::ser::model::{EucalyptusMaterial, EucalyptusMesh, EucalyptusModel};
use crate::states::Node;
use dropbear_engine::utils::ResourceReference;
use std::collections::hash_map::DefaultHasher;
use std::fs;
use std::hash::{Hash, Hasher};
use std::path::{Path, PathBuf};
use std::time::Duration;
use winit::keyboard::KeyCode;

pub const PROTO_TEXTURE: &[u8] = include_bytes!("../../../resources/textures/proto.png");

pub fn search_nodes_recursively<'a, F>(nodes: &'a [Node], matcher: &F, results: &mut Vec<&'a Node>)
where
    F: Fn(&Node) -> bool,
{
    for node in nodes {
        if matcher(node) {
            results.push(node);
        }
        match node {
            Node::File(_) => {}
            Node::Folder(folder) => {
                search_nodes_recursively(&folder.nodes, matcher, results);
            }
        }
    }
}

/// Progress events for project creation
pub enum ProjectProgress {
    Step {
        _progress: f32,
        _message: String,
    },
    #[allow(dead_code)] // idk why its giving me this warning :(
    Error(String),
    Done,
}

#[derive(Clone)]
pub enum ViewportMode {
    None,
    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,
    }
}

pub trait ResolveReference {
    /// This function attempts to resolve the [`ResourceReference`]
    /// (specifically `ResourceReference::File`) into a [`PathBuf`].
    ///
    /// It does this by checking if the app is the `eucalyptus-editor`
    /// through the `editor` flag, or the redback-runtime.
    ///
    /// It first resolves for the project config, and if that is not available
    /// it will resolve by comparing to the executable's directory.
    fn resolve(&self) -> anyhow::Result<PathBuf>;
}

impl ResolveReference for ResourceReference {
    fn resolve(&self) -> anyhow::Result<PathBuf> {
        match self {
            ResourceReference::File(path) if !path.is_empty() => {
                let relative = path.as_str();

                #[cfg(feature = "editor")]
                {
                    let project_path = {
                        use crate::states::PROJECT;

                        let cfg = PROJECT.read();
                        cfg.project_path.clone()
                    };

                    if !project_path.as_os_str().is_empty() {
                        let root = project_path.join("resources");
                        if let Some(resolved) = try_resolve_resource_from_root(relative, &root) {
                            return Ok(resolved);
                        }

                        let fallback = project_path.join(relative);
                        if fallback.exists() {
                            return Ok(fallback);
                        }

                        return resolve_resource_from_root(relative, &root);
                    }
                }

                let root = runtime_resources_dir()?;
                resolve_resource_from_root(relative, &root)
            }
            _ => {
                anyhow::bail!("Cannot resolve a non-file ResourceReference")
            }
        }
    }
}

/// Converts a [`ResourceReference`] into a file.
pub trait AsFile {
    /// Converts a [`ResourceReference`] into a file.
    ///
    /// # Different type's behaviours
    /// - `ResourceReference::File("")` => Returns an error (empty path).
    /// - `ResourceReference::File(path)` => Returns the resolved file path.
    /// - `ResourceReference::Embedded` => Saves bytes to a `*.eucbin` in the gen/ folder.
    /// - `ResourceReference::Procedural` => Serializes geometry to a `*.eucmdl` in the gen/ folder.
    fn as_file(&self, new_ref: Option<String>) -> anyhow::Result<PathBuf>;
}

impl AsFile for ResourceReference {
    fn as_file(&self, new_ref: Option<String>) -> anyhow::Result<PathBuf> {
        match self {
            ResourceReference::File(s) if s.is_empty() => {
                anyhow::bail!("Cannot convert an empty File reference to a path")
            }
            ResourceReference::File(_) => {
                let resolved = self.resolve()?;
                if let Some(relative) = new_ref {
                    let root = resources_root_for_write()?;
                    let out_path = root.join(relative.trim_start_matches('/'));
                    if let Some(parent) = out_path.parent() {
                        fs::create_dir_all(parent)?;
                    }
                    Ok(out_path)
                } else {
                    Ok(resolved)
                }
            }
            ResourceReference::Embedded(bytes) => {
                let hash = hash_value(bytes);
                let root = resources_root_for_write()?;
                let out_path = root.join("gen").join(format!("{hash:016x}.eucbin"));

                if let Some(parent) = out_path.parent() {
                    fs::create_dir_all(parent)?;
                }
                fs::write(&out_path, bytes.as_ref())?;

                Ok(out_path)
            }
            ResourceReference::Procedural(obj) => {
                let hash = hash_value(obj);
                let out_path = if let Some(relative) = new_ref.as_ref() {
                    let root = resources_root_for_write()?;
                    root.join(relative.trim_start_matches('/'))
                } else {
                    let root = resources_root_for_write()?;
                    root.join("gen").join(format!("{hash:016x}.eucmdl"))
                };

                let label_stem = out_path
                    .file_stem()
                    .and_then(|stem| stem.to_str())
                    .unwrap_or("procedural_model")
                    .to_string();

                let mut expanded_vertices = Vec::with_capacity(obj.indices.len());
                for &index in &obj.indices {
                    let vertex = obj
                        .vertices
                        .get(index as usize)
                        .ok_or_else(|| {
                            anyhow::anyhow!(
                                "Procedural object index {} is out of bounds for {} vertices",
                                index,
                                obj.vertices.len()
                            )
                        })?
                        .clone();
                    expanded_vertices.push(vertex);
                }

                let model = EucalyptusModel {
                    label: label_stem.clone(),
                    meshes: vec![EucalyptusMesh {
                        name: format!("{label_stem}_mesh"),
                        num_elements: expanded_vertices.len() as u32,
                        material: 0,
                        vertices: expanded_vertices,
                        morph_deltas_offset: 0,
                        morph_target_count: 0,
                        morph_vertex_count: obj.indices.len() as u32,
                        morph_default_weights: Vec::new(),
                    }],
                    materials: vec![EucalyptusMaterial {
                        name: "procedural_material".to_string(),
                        diffuse_texture: None,
                        normal_texture: None,
                        emissive_texture: None,
                        metallic_roughness_texture: None,
                        occlusion_texture: None,
                        tint: [1.0, 1.0, 1.0, 1.0],
                        emissive_factor: [0.0, 0.0, 0.0],
                        metallic_factor: 1.0,
                        roughness_factor: 1.0,
                        alpha_mode: dropbear_engine::model::AlphaMode::Opaque,
                        alpha_cutoff: None,
                        occlusion_strength: 1.0,
                        normal_scale: 1.0,
                        uv_tiling: [1.0, 1.0],
                        texture_tag: Some("procedural_material".to_string()),
                        wrap_mode: dropbear_engine::texture::TextureWrapMode::Repeat,
                    }],
                    skins: Vec::new(),
                    animations: Vec::new(),
                    nodes: Vec::new(),
                    morph_deltas: Vec::new(),
                };

                if let Some(parent) = out_path.parent() {
                    fs::create_dir_all(parent)?;
                }

                let serialized = rkyv::to_bytes::<rkyv::rancor::Error>(&model).map_err(|e| {
                    anyhow::anyhow!("Failed to serialize proc object model as rkyv bytes: {e}")
                })?;
                fs::write(&out_path, serialized.as_ref())?;

                Ok(out_path)
            }
        }
    }
}

fn hash_value<T: Hash>(value: &T) -> u64 {
    let mut hasher = DefaultHasher::new();
    value.hash(&mut hasher);
    hasher.finish()
}

fn resources_root_for_write() -> anyhow::Result<PathBuf> {
    #[cfg(feature = "editor")]
    {
        use crate::states::PROJECT;

        let project_path = PROJECT.read().project_path.clone();
        if !project_path.as_os_str().is_empty() {
            return Ok(project_path.join("resources"));
        }
    }

    runtime_resources_dir()
}

fn try_resolve_resource_from_root(relative: &str, root: &Path) -> Option<PathBuf> {
    let resolved = root.join(relative);
    resolved.exists().then_some(resolved)
}

fn runtime_resources_dir() -> anyhow::Result<PathBuf> {
    let current_exe = std::env::current_exe()?;
    let dir = current_exe
        .parent()
        .ok_or_else(|| anyhow::anyhow!("Unable to locate parent folder of runtime executable"))?;
    let resources_dir = dir.join("resources");
    if !resources_dir.exists() {
        anyhow::bail!(
            "Runtime resources directory is missing at '{}'. Ensure the packaged build includes a 'resources' folder next to the executable (current exe: {}).",
            resources_dir.display(),
            current_exe.display()
        );
    }
    Ok(resources_dir)
}

fn resolve_resource_from_root(relative: &str, root: &Path) -> anyhow::Result<PathBuf> {
    let resolved = root.join(relative);
    if resolved.exists() {
        return Ok(resolved);
    }

    if !root.exists() {
        anyhow::bail!(
            "Resource '{}' could not be resolved because the base directory '{}' does not exist",
            relative,
            root.display()
        );
    }

    anyhow::bail!(
        "Resource '{}' was resolved to '{}' but the file does not exist. Ensure the asset is packaged under '{}'",
        relative,
        resolved.display(),
        root.display()
    );
}

/// Validates and converts a raw pointer to a reference.
/// Returns early if the pointer is null.
///
/// # Example
/// ```rust
/// use eucalyptus_core::{convert_ptr, ptr::AssetRegistryPtr};
/// use dropbear_engine::asset::AssetRegistry;
///
/// let asset_handle = 0x12345678; // pointer
///
/// let asset: &AssetRegistry = convert_ptr!(asset_handle, AssetRegistryPtr => AssetRegistry);
/// let asset: &mut AssetRegistry = convert_ptr!(mut asset_handle, AssetRegistryPtr => AssetRegistry);
/// let asset: &AssetRegistry = convert_ptr!(asset_handle => AssetRegistry);
/// ```
#[macro_export]
macro_rules! convert_ptr {
    (mut $ptr:expr, $ptr_ty:ty => $target_ty:ty) => {{
        let ptr = $ptr as $ptr_ty;

        if ptr.is_null() {
            let message = format!(
                "[{}] {} pointer is null",
                std::any::type_name::<$target_ty>(),
                stringify!($ptr)
            );
            return $crate::ffi_error_return!("{}", message);
        }

        let addr = ptr as usize;
        let align = std::mem::align_of::<$target_ty>();
        if addr % align != 0 {
            let message = format!(
                "[{}] [ERROR] {} pointer is misaligned: addr=0x{:x}, align=0x{:x}",
                std::any::type_name::<$target_ty>(),
                stringify!($ptr),
                addr,
                align
            );
            return $crate::ffi_error_return!("{}", message);
        }

        unsafe { &mut *(ptr as *mut $target_ty) }
    }};

    (mut $ptr:expr => $target_ty:ty) => {{
        let ptr = $ptr as *mut $target_ty;
        if ptr.is_null() {
            let message = format!(
                "[{}] [ERROR] {} pointer is null",
                std::any::type_name::<$target_ty>(),
                stringify!($ptr)
            );
            return $crate::ffi_error_return!("{}", message);
        }

        let addr = ptr as usize;
        let align = std::mem::align_of::<$target_ty>();
        if addr % align != 0 {
            let message = format!(
                "[{}] [ERROR] {} pointer is misaligned: addr=0x{:x}, align=0x{:x}",
                std::any::type_name::<$target_ty>(),
                stringify!($ptr),
                addr,
                align
            );
            return $crate::ffi_error_return!("{}", message);
        }
        unsafe { &mut *ptr }
    }};

    ($ptr:expr, $ptr_ty:ty => $target_ty:ty) => {{
        let ptr = $ptr as $ptr_ty;
        if ptr.is_null() {
            let message = format!(
                "[{}] [ERROR] {} pointer is null",
                std::any::type_name::<$target_ty>(),
                stringify!($ptr)
            );
            return $crate::ffi_error_return!("{}", message);
        }

        let addr = ptr as usize;
        let align = std::mem::align_of::<$target_ty>();
        if addr % align != 0 {
            let message = format!(
                "[{}] [ERROR] {} pointer is misaligned: addr=0x{:x}, align=0x{:x}",
                std::any::type_name::<$target_ty>(),
                stringify!($ptr),
                addr,
                align
            );
            return $crate::ffi_error_return!("{}", message);
        }
        unsafe { &*(ptr as *const $target_ty) }
    }};

    ($ptr:expr => $target_ty:ty) => {{
        let ptr = $ptr as *const $target_ty;
        if ptr.is_null() {
            let message = format!(
                "[{}] [ERROR] {} pointer is null",
                std::any::type_name::<$target_ty>(),
                stringify!($ptr)
            );
            return $crate::ffi_error_return!("{}", message);
        }

        let addr = ptr as usize;
        let align = std::mem::align_of::<$target_ty>();
        if addr % align != 0 {
            let message = format!(
                "[{}] [ERROR] {} pointer is misaligned: addr=0x{:x}, align=0x{:x}",
                std::any::type_name::<$target_ty>(),
                stringify!($ptr),
                addr,
                align
            );
            return $crate::ffi_error_return!("{}", message);
        }
        unsafe { &*ptr }
    }};
}

/// Converts a JString to a Rust String with automatic error handling.
/// Automatically infers the appropriate error return value based on the function's return type.
///
/// # Usage
/// ```rust
/// convert_jstring!(env, jstring);
/// ```
#[macro_export]
macro_rules! convert_jstring {
    ($env:expr, $jstring:expr) => {{
        match $jstring.mutf8_chars(&mut $env) {
            Ok(chars) => chars.to_str().into_owned(),
            Err(e) => {
                let message = format!(
                    "[{}] [ERROR] Failed to get string from JNI: {}",
                    stringify!($jstring),
                    e
                );
                println!("{}", message);
                return $crate::ffi_error_return!();
            }
        }
    }};
}

/// A convenient macro for returning from a function when you cbb to add a specific return value.
///
/// # Note
/// This can only work in JNI related code, not native code. If this is used in native code, it will
/// always return [`DropbearNativeError::NullPointer`] (which might not even be the intended error).
/// # Usage
/// ```
/// 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 {
    () => {{
        trait ErrorValue {
            fn error_value() -> Self;
        }

        impl ErrorValue for () {
            fn error_value() -> Self {}
        }

        impl ErrorValue for i8 {
            fn error_value() -> Self {
                -1
            }
        }

        impl ErrorValue for i16 {
            fn error_value() -> Self {
                -1
            }
        }

        impl ErrorValue for i32 {
            fn error_value() -> Self {
                -1
            }
        }

        impl ErrorValue for i64 {
            fn error_value() -> Self {
                -1
            }
        }

        impl ErrorValue for isize {
            fn error_value() -> Self {
                -1
            }
        }

        impl ErrorValue for u8 {
            fn error_value() -> Self {
                0
            }
        }

        impl ErrorValue for u16 {
            fn error_value() -> Self {
                u16::MAX
            }
        }

        impl ErrorValue for u32 {
            fn error_value() -> Self {
                u32::MAX
            }
        }

        impl ErrorValue for u64 {
            fn error_value() -> Self {
                u64::MAX
            }
        }

        impl ErrorValue for usize {
            fn error_value() -> Self {
                usize::MAX
            }
        }

        impl<T> ErrorValue for *mut T {
            fn error_value() -> Self {
                std::ptr::null_mut()
            }
        }

        impl<T> ErrorValue for *const T {
            fn error_value() -> Self {
                std::ptr::null()
            }
        }

        impl<'local> ErrorValue for ::jni::objects::JObject<'local> {
            fn error_value() -> Self {
                ::jni::objects::JObject::null()
            }
        }

        impl ErrorValue for f64 {
            fn error_value() -> Self {
                f64::NAN
            }
        }

        impl ErrorValue for f32 {
            fn error_value() -> Self {
                f32::NAN
            }
        }

        impl ErrorValue for bool {
            fn error_value() -> Self {
                false
            }
        }

        impl<T: ErrorValue, E> ErrorValue for ::std::result::Result<T, E> {
            fn error_value() -> Self {
                Ok(T::error_value())
            }
        }
        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 [jni::sys::jlong] to a [hecs::Entity] with automatic error handling.
/// Returns from the function with an appropriate error value if the conversion fails.
///
/// # Usage
/// ```rust
/// let entity = convert_jlong_to_entity!(jlong_value);
/// ```
#[macro_export]
macro_rules! convert_jlong_to_entity {
    ($jlong:expr) => {{
        match hecs::Entity::from_bits($jlong as u64) {
            Some(entity) => entity,
            None => {
                let message = format!(
                    "[{}] [ERROR] Invalid bit pattern for entity provided: {}",
                    stringify!($jlong),
                    $jlong
                );
                println!("{}", message);
                return $crate::ffi_error_return!();
            }
        }
    }};
}

/// This function starts a [`parking_lot`] deadlock detector on another thread, which basically
/// checks for any [`parking_lot::Mutex`] and [`parking_lot::RwLock`] deadlocks.
///
/// Since parking_lot is more convenient to use compared to the std sync crates, they provide a deadlocking
/// api, which panics if any are detected.
pub fn start_deadlock_detector() {
    std::thread::spawn(move || {
        loop {
            std::thread::sleep(Duration::from_secs(1));
            let deadlocks = parking_lot::deadlock::check_deadlock();
            if deadlocks.is_empty() {
                continue;
            }

            for (i, threads) in deadlocks.iter().enumerate() {
                log::error!("Deadlock #{}", i);
                for t in threads {
                    log::error!("Thread Id {:#?}", t.thread_id());
                    log::error!("{:#?}", t.backtrace());
                }
            }
            panic!(
                "Fatal: {} deadlocks detected, unable to continue on normal process",
                deadlocks.len()
            );
        }
    });
}

/// Indicates the progress of an operation.
#[repr(C)]
#[derive(Clone)]
pub struct Progress {
    pub current: usize,
    pub total: usize,
    pub message: String,
}

impl Default for Progress {
    fn default() -> Self {
        Self {
            current: 0,
            total: 1,
            message: "Idle".to_string(),
        }
    }
}