compose-rust 0.0.1

Declarative UI in Rust, rendered by an AOT-compiled Compose Multiplatform engine.
Documentation
// Generated by `cargo run -p compose-rust --bin codegen`. DO NOT EDIT.
//! JNI shims for the Android boundary.
//!
//! Kotlin owns the process and the frame loop, so every call here starts on the Renderer
//! UI thread and returns on the same call stack. Nothing is copied: a call reports where
//! its batch sits inside the Host's arena, and the Renderer reads the arena through a
//! direct byte buffer it made once.

#![allow(non_snake_case)]

use crate::boundary::{
    MutationBatch, RendererApi, STATUS_OK, STATUS_PROTOCOL_ERROR, current_arena,
    install_renderer_api,
};
use jni::JNIEnv;
use jni::JavaVM;
use jni::objects::{GlobalRef, JByteBuffer, JClass, JLongArray, JStaticMethodID};
use jni::signature::{Primitive, ReturnType};
use jni::sys::jobject;
use jni::sys::{JNI_ERR, JNI_VERSION_1_6, jint, jlong};
use std::ffi::c_int;
use std::os::raw::c_void;
use std::sync::OnceLock;

unsafe extern "C" {
    /// Defined by the application's cdylib through `compose_rust::android_main!`. It
    /// registers the root component before the Renderer's first init call.
    fn compose_rust_android_main();
}

/// The number of `out` slots a batch-returning call writes.
pub const OUT_SLOTS: usize = 5;

struct FrameRequestUpcall {
    class: GlobalRef,
    method: JStaticMethodID,
}

static VM: OnceLock<JavaVM> = OnceLock::new();
static UPCALL: OnceLock<FrameRequestUpcall> = OnceLock::new();

/// The Activity owns the loop, so the Host never runs one.
extern "C" fn platform_run() -> c_int {
    STATUS_OK as c_int
}

/// Called from a Host worker thread, never from the UI thread.
///
/// The worker attaches to the JavaVM once and stays attached for the life of the process:
/// attaching and detaching around every call would cost more than the frame it asks for.
extern "C" fn request_frame() {
    let (Some(vm), Some(upcall)) = (VM.get(), UPCALL.get()) else {
        return;
    };
    let Ok(mut env) = vm.attach_current_thread_permanently() else {
        return;
    };
    let class = <&JClass>::from(upcall.class.as_obj());
    // SAFETY: the method id was resolved on this class for the signature `()V`, and the
    // argument list matches it.
    let result = unsafe {
        env.call_static_method_unchecked(
            class,
            upcall.method,
            ReturnType::Primitive(Primitive::Void),
            &[],
        )
    };
    if result.is_err() {
        // A failed upcall is not worth aborting the process for. Clearing the pending
        // exception leaves the VM usable.
        let _ = env.exception_clear();
    }
}

/// Resolves a direct byte buffer to the address the Host may read `length` bytes from.
fn direct_address(env: &JNIEnv<'_>, buffer: &JByteBuffer<'_>, length: jint) -> Option<*const u8> {
    if length < 0 {
        return None;
    }
    let address = env.get_direct_buffer_address(buffer).ok()?;
    let capacity = env.get_direct_buffer_capacity(buffer).ok()?;
    if capacity < length as usize {
        return None;
    }
    Some(address.cast_const())
}

/// Reports where the batch sits, without copying a byte of it.
fn report(env: &mut JNIEnv<'_>, out: &JLongArray<'_>, batch: &MutationBatch) {
    let (base, capacity) = current_arena();
    let offset = if batch.ptr.is_null() || base.is_null() {
        0
    } else {
        (batch.ptr as usize).wrapping_sub(base as usize)
    };
    let values: [jlong; OUT_SLOTS] = [
        offset as jlong,
        jlong::from(batch.len),
        batch.result,
        base as usize as jlong,
        capacity as jlong,
    ];
    let _ = env.set_long_array_region(out, 0, &values);
}

/// `Init`, which calls `compose_rust_host_init`.
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_darkpyonix_composerust_ui_platform_HostBridge_nativeInit(
    mut env: JNIEnv<'_>,
    _class: JClass<'_>,
    handshake: JByteBuffer<'_>,
    handshake_length: jint,
    out: JLongArray<'_>,
) -> jint {
    let Some(address) = direct_address(&env, &handshake, handshake_length) else {
        return STATUS_PROTOCOL_ERROR;
    };
    let mut batch = MutationBatch::default();
    // SAFETY: every pointer above addresses the number of readable bytes the
    // boundary contract promises, and the batch is local to this call.
    let status = unsafe {
        crate::boundary::compose_rust_host_init(address, handshake_length as u32, &raw mut batch)
    };
    report(&mut env, &out, &batch);
    status
}

/// `DispatchEvent`, which calls `compose_rust_host_dispatch_event`.
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_darkpyonix_composerust_ui_platform_HostBridge_nativeDispatchEvent(
    mut env: JNIEnv<'_>,
    _class: JClass<'_>,
    event: JByteBuffer<'_>,
    event_length: jint,
    out: JLongArray<'_>,
) -> jint {
    let Some(address) = direct_address(&env, &event, event_length) else {
        return STATUS_PROTOCOL_ERROR;
    };
    let mut batch = MutationBatch::default();
    // SAFETY: every pointer above addresses the number of readable bytes the
    // boundary contract promises, and the batch is local to this call.
    let status = unsafe {
        crate::boundary::compose_rust_host_dispatch_event(
            address,
            event_length as u32,
            &raw mut batch,
        )
    };
    report(&mut env, &out, &batch);
    status
}

/// `RenderFrame`, which calls `compose_rust_host_render_frame`.
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_darkpyonix_composerust_ui_platform_HostBridge_nativeRenderFrame(
    mut env: JNIEnv<'_>,
    _class: JClass<'_>,
    frame_time_nanos: jlong,
    out: JLongArray<'_>,
) -> jint {
    let mut batch = MutationBatch::default();
    // SAFETY: every pointer above addresses the number of readable bytes the
    // boundary contract promises, and the batch is local to this call.
    let status = unsafe {
        crate::boundary::compose_rust_host_render_frame(frame_time_nanos as u64, &raw mut batch)
    };
    report(&mut env, &out, &batch);
    status
}

/// `ReleaseBatch`, which calls `compose_rust_host_release_batch`.
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_darkpyonix_composerust_ui_platform_HostBridge_nativeReleaseBatch(
    _env: JNIEnv<'_>,
    _class: JClass<'_>,
) -> jint {
    let mut batch = MutationBatch::default();
    // SAFETY: every pointer above addresses the number of readable bytes the
    // boundary contract promises, and the batch is local to this call.
    unsafe { crate::boundary::compose_rust_host_release_batch(&raw mut batch) };
    STATUS_OK
}

/// `Shutdown`, which calls `compose_rust_host_shutdown`.
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_darkpyonix_composerust_ui_platform_HostBridge_nativeShutdown(
    _env: JNIEnv<'_>,
    _class: JClass<'_>,
) -> jint {
    crate::boundary::compose_rust_host_shutdown();
    STATUS_OK
}

/// Hands the Renderer a view of the Host's arena, not a copy of it.
///
/// Not a boundary operation: it exposes no Host behaviour, it only tells the JVM where
/// memory it may already read lives. The Renderer asks for it once, and again whenever a
/// call reports a different arena address, which is what happens when the arena grows and
/// moves.
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_darkpyonix_composerust_ui_platform_HostBridge_nativeArenaBuffer(
    mut env: JNIEnv<'_>,
    _class: JClass<'_>,
) -> jobject {
    let (base, capacity) = current_arena();
    if base.is_null() || capacity == 0 {
        return std::ptr::null_mut();
    }
    // SAFETY: the arena belongs to this thread's Host and stays where it is until it
    // grows, and every call reports the address so the Renderer can notice that.
    match unsafe { env.new_direct_byte_buffer(base.cast_mut(), capacity) } {
        Ok(buffer) => buffer.into_raw(),
        Err(_) => std::ptr::null_mut(),
    }
}

/// Runs on `System.loadLibrary`, before any boundary call.
///
/// # Safety
/// The JavaVM calls this with its own `JavaVM` pointer.
#[unsafe(no_mangle)]
pub unsafe extern "system" fn JNI_OnLoad(
    vm: *mut jni::sys::JavaVM,
    _reserved: *mut c_void,
) -> jint {
    // SAFETY: the pointer comes from the JavaVM itself.
    let Ok(vm) = (unsafe { JavaVM::from_raw(vm) }) else {
        return JNI_ERR;
    };
    let Ok(mut env) = vm.attach_current_thread_permanently() else {
        return JNI_ERR;
    };
    // The class is resolved here, on a Java-created thread, and kept as a global
    // reference. A Host worker thread's class loader cannot see application classes, so
    // looking it up later would fail.
    let Ok(class) = env.find_class(BRIDGE_CLASS) else {
        return JNI_ERR;
    };
    let Ok(method) = env.get_static_method_id(&class, "onFrameRequested", "()V") else {
        return JNI_ERR;
    };
    let Ok(global) = env.new_global_ref(&class) else {
        return JNI_ERR;
    };
    let _ = UPCALL.set(FrameRequestUpcall {
        class: global,
        method,
    });
    let _ = VM.set(vm);
    let _ = install_renderer_api(RendererApi {
        run: platform_run,
        request_frame,
    });
    // SAFETY: the application's cdylib defines this symbol.
    unsafe { compose_rust_android_main() };
    JNI_VERSION_1_6
}

const BRIDGE_CLASS: &str = "dev/darkpyonix/composerust/ui/platform/HostBridge";