waterui-ffi 0.3.1

FFI bindings for the WaterUI cross-platform UI framework
//! JNI bridge functions for `WaterUI` Android.
//!
//! This module provides the JNI function implementations that Kotlin expects
//! for functions NOT generated by macros (`ffi_view!`, `ffi_binding!`, `opaque!`, etc.).
//!
//! Most component-specific functions are auto-generated by macros when
//! the `android-jni` feature is enabled.

extern crate alloc;
extern crate std;

use core::ffi::c_void;
use jni::objects::{Global, JClass, JObject, JValue};
use jni::sys::{jint, jlong, jobject};
use jni::{Env, EnvUnowned, jni_sig, jni_str};

use super::type_id_to_java;
use crate::{IntoFFI, IntoRust, WuiAnyView, WuiTypeId, app::WuiAndroidAppHandles};

// These functions are generated by the export!() macro in the user's app
// and linked at runtime via the same shared library.
unsafe extern "C" {
    fn waterui_android_init() -> *mut c_void;
    fn waterui_android_app(env: *mut c_void) -> WuiAndroidAppHandles;
}

// ============================================================================
// Core Functions (not generated by macros)
// ============================================================================

/// Initialize the `WaterUI` runtime and return an environment pointer.
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_init<'local>(
    _env: EnvUnowned<'local>,
    _class: JClass<'local>,
) -> jlong {
    // SAFETY: `waterui_android_init` is the `export!`-generated entry point linked
    // into the same shared library, and the Android runtime calls this once before
    // any other FFI entry point.
    unsafe { waterui_android_init() as jlong }
}

/// Create the application from the environment.
/// Returns an `AppStruct` `jobject`.
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_app<'local>(
    mut env: EnvUnowned<'local>,
    _class: JClass<'local>,
    env_ptr: jlong,
) -> jobject {
    // SAFETY: `env_ptr` is the environment handle `init` above returned, which the
    // Android runtime holds for the life of the process.
    let app = unsafe { waterui_android_app(env_ptr as *mut c_void) };
    super::with_env(&mut env, |env| app_to_java(env, &app).into_raw())
}

/// Convert the two transferred Android app handles to Java `AppStruct`.
fn app_to_java<'local>(env: &mut Env<'local>, app: &WuiAndroidAppHandles) -> JObject<'local> {
    let app_struct_class = env
        .find_class(jni_str!("dev/waterui/android/runtime/AppStruct"))
        .expect("AppStruct class not found");
    env.new_object(
        &app_struct_class,
        jni_sig!("(JJ)V"),
        &[
            JValue::Long(app.content as jlong),
            JValue::Long(app.env as jlong),
        ],
    )
    .expect("Failed to create AppStruct")
}

/// Get the type ID of a view.
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_viewId<'local>(
    mut env: EnvUnowned<'local>,
    _class: JClass<'local>,
    view_ptr: jlong,
) -> jobject {
    // SAFETY: Kotlin passes back a view handle the renderer still owns, so it is live
    // for this call, and `waterui_view_id` only reads it.
    let type_id = unsafe { crate::waterui_view_id(view_ptr as *const crate::WuiAnyView) };
    super::with_env(&mut env, |env| type_id_to_java(env, type_id).into_raw())
}

/// Get the body of a composite view.
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_viewBody<'local>(
    _env: EnvUnowned<'local>,
    _class: JClass<'local>,
    view_ptr: jlong,
    env_ptr: jlong,
) -> jlong {
    // SAFETY: Kotlin passes back an owning view handle and the live environment it is
    // rendered in; `waterui_view_body` consumes the view and returns a new handle.
    unsafe {
        crate::waterui_view_body(
            view_ptr as *mut crate::WuiAnyView,
            env_ptr as *mut crate::WuiEnv,
        ) as jlong
    }
}

/// Get the stretch axis of a view.
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_viewStretchAxis<'local>(
    _env: EnvUnowned<'local>,
    _class: JClass<'local>,
    view_ptr: jlong,
) -> jint {
    // SAFETY: Kotlin passes back a view handle the renderer still owns, so it is live
    // for this call, which only reads it.
    unsafe { crate::waterui_view_stretch_axis(view_ptr as *const crate::WuiAnyView) as jint }
}

/// Clone an environment.
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_cloneEnv<'local>(
    _env: EnvUnowned<'local>,
    _class: JClass<'local>,
    env_ptr: jlong,
) -> jlong {
    // SAFETY: Kotlin passes back a live environment handle, which this only reads to
    // produce an independently owned clone.
    unsafe { crate::waterui_clone_env(env_ptr as *const crate::WuiEnv) as jlong }
}

// dropEnv is generated by opaque!; AnyView ownership transfers directly.

// ============================================================================
// Type ID Functions (not generated by ffi_view! macro)
// ============================================================================

/// Get the empty type ID.
/// This is special - `()` doesn't use the `ffi_view!` macro.
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_emptyId<'local>(
    mut env: EnvUnowned<'local>,
    _class: JClass<'local>,
) -> jobject {
    let type_id = WuiTypeId::of::<()>();
    super::with_env(&mut env, |env| type_id_to_java(env, type_id).into_raw())
}

/// Get the spacer type ID.
/// `Spacer` is defined specially in `layout.rs`.
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_spacerId<'local>(
    mut env: EnvUnowned<'local>,
    _class: JClass<'local>,
) -> jobject {
    let type_id = WuiTypeId::of::<waterui_core::Native<waterui::component::spacer::Spacer>>();
    super::with_env(&mut env, |env| type_id_to_java(env, type_id).into_raw())
}

#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_webViewId<'local>(
    mut env: EnvUnowned<'local>,
    _class: JClass<'local>,
) -> jobject {
    let type_id = WuiTypeId::of::<waterui_core::Native<waterui_webview::WebView>>();
    super::with_env(&mut env, |env| type_id_to_java(env, type_id).into_raw())
}

#[unsafe(no_mangle)]
/// Forces an owned view handle into its `WebView` descriptor.
///
/// # Safety
///
/// `view_ptr` must be a valid owning view pointer whose type id is `WebView`, and
/// must not be used after this call.
unsafe extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_forceAsWebView<'local>(
    mut env: EnvUnowned<'local>,
    _class: JClass<'local>,
    view_ptr: jlong,
) -> jobject {
    super::with_env(&mut env, |env| {
        // SAFETY: the caller contract above makes `view_ptr` a valid owning handle that
        // this call consumes, and guarantees its erased value is a `Native<WebView>`,
        // which is what the unchecked downcast relies on.
        unsafe {
            let view_ptr: *mut WuiAnyView = crate::jni::convert::jlong_to_ptr_mut(view_ptr);
            let any: waterui::AnyView = view_ptr.into_rust();
            let view = *any.downcast_unchecked::<waterui_core::Native<waterui_webview::WebView>>();
            let ffi = view.into_inner().into_ffi();
            crate::jni::convert::struct_to_java(env, ffi).into_raw()
        }
    })
}

#[unsafe(no_mangle)]
/// Drops an owned reactive view-collection handle.
///
/// # Safety
///
/// `ptr` must be a valid owning `WuiAnyViews` pointer and must not have been
/// dropped previously.
unsafe extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_dropAnyViews<'local>(
    _env: EnvUnowned<'local>,
    _class: JClass<'local>,
    ptr: jlong,
) {
    // SAFETY: the caller contract above makes `ptr` an owning handle that has not been
    // dropped, so reclaiming it once frees it exactly once.
    unsafe {
        drop(crate::IntoRust::into_rust(
            ptr as *mut crate::views::WuiAnyViews,
        ));
    }
}

/// Drops an owned type-erased view that was never transferred into a renderer.
///
/// # Safety
///
/// `ptr` must be a valid owning `WuiAnyView` pointer and must not have been
/// consumed or dropped previously.
#[unsafe(no_mangle)]
unsafe extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_dropAnyView<'local>(
    _env: EnvUnowned<'local>,
    _class: JClass<'local>,
    ptr: jlong,
) {
    // SAFETY: the caller contract above makes `ptr` an owning handle that has not been
    // consumed or dropped, so reclaiming it once frees it exactly once.
    unsafe {
        drop(crate::IntoRust::into_rust(ptr as *mut crate::WuiAnyView));
    }
}

// ============================================================================
// Environment Install Functions
// ============================================================================

#[cfg(feature = "gpu")]
struct AndroidGpuRuntimeCompletion {
    jvm: jni::JavaVM,
    callback: Global<JObject<'static>>,
}

#[cfg(feature = "gpu")]
impl AndroidGpuRuntimeCompletion {
    fn complete(self, runtime: waterui_graphics::shared_context::GpuRuntime) {
        let runtime = crate::IntoFFI::into_ffi(runtime);
        super::with_attached_env(&self.jvm, |env| {
            env.call_method(
                &self.callback,
                jni_str!("onReady"),
                jni_sig!("(J)V"),
                &[JValue::Long(runtime as jlong)],
            )
            .expect("GpuRuntimeReadyCallback.onReady failed");
        })
        .expect("GPU runtime completion failed to attach to JVM");
    }
}

/// Creates the process GPU runtime without blocking the Android main thread.
#[cfg(feature = "gpu")]
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_gpuRuntimeCreate<'local>(
    mut env: EnvUnowned<'local>,
    _class: JClass<'local>,
    callback: JObject<'local>,
) {
    super::with_env(&mut env, |env| {
        let completion = AndroidGpuRuntimeCompletion {
            jvm: env
                .get_java_vm()
                .expect("WatcherJni.gpuRuntimeCreate failed to access JavaVM"),
            callback: env
                .new_global_ref(callback)
                .expect("WatcherJni.gpuRuntimeCreate failed to retain callback"),
        };
        crate::components::gpu_runtime::create_gpu_runtime(move |runtime| {
            completion.complete(runtime);
        });
    });
}

/// Installs an asynchronously-created GPU runtime into the app environment.
#[cfg(feature = "gpu")]
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_envInstallGpuRuntime<'local>(
    _env: EnvUnowned<'local>,
    _class: JClass<'local>,
    env_ptr: jlong,
    runtime_ptr: jlong,
) {
    // SAFETY: Kotlin passes back the live app environment handle, borrowed only for
    // this call.
    let env = unsafe { crate::borrow_ffi_mut(env_ptr as *mut crate::WuiEnv) };
    // SAFETY: `runtime_ptr` is the owning handle `gpuRuntimeCreate` above delivered to
    // `onReady`, and installing it consumes it once.
    let runtime = unsafe {
        crate::IntoRust::into_rust(
            runtime_ptr as *mut crate::components::gpu_runtime::WuiGpuRuntime,
        )
    };
    crate::components::gpu_runtime::install_gpu_runtime(&mut env.0, runtime);
}

/// Install web view controller in the environment.
#[unsafe(no_mangle)]
extern "system" fn Java_dev_waterui_android_ffi_WatcherJni_envInstallWebViewController<'local>(
    mut env: EnvUnowned<'local>,
    _class: JClass<'local>,
    env_ptr: jlong,
    factory: JObject<'local>,
) {
    super::with_env(&mut env, |env| {
        // SAFETY: Kotlin passes back the live app environment handle, which
        // `install_android_webview_controller` borrows only for this call.
        unsafe {
            super::webview_bridge::install_android_webview_controller(
                env,
                env_ptr as *mut crate::WuiEnv,
                factory,
            );
        }
    });
}