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.
//! The wasm shims for the web boundary.
//!
//! The Renderer owns the one linear memory and this module imports it, so a batch is read
//! where it was written. That leaves the addresses: every argument below is an address or a
//! length in that shared memory, and both buffers a call names belong to this side, because
//! the Renderer has no allocator that can hold memory from one frame to the next.
//!
//! Every argument is 32 bits wide. A 64 bit one would reach the forwarder as a `BigInt`,
//! which is a heap allocation on the call that happens every frame, so a frame timestamp
//! arrives as its two halves and is put back together here.

use crate::boundary::{
    MutationBatch, RendererApi, STATUS_OK, STATUS_PROTOCOL_ERROR, install_renderer_api,
};
use crate::schema::{
    WEB_BATCH_BYTES, WEB_EVENT_BUFFER_BYTES, WEB_EVENT_BUFFER_OFFSET, WEB_RUST_REGION_BASE,
};
use crate::{LaunchBuilder, LoopMode, Runtime};
use std::ffi::c_int;
use std::mem::{offset_of, size_of};

#[link(wasm_import_module = "compose_rust_renderer")]
unsafe extern "C" {
    /// Bound to the function the Renderer exports, with no JavaScript in between: the page
    /// hands the exported function object straight to this module's instantiation, which it
    /// can because this module is instantiated second.
    fn compose_rust_renderer_request_frame();
}

/// The record layout the generated Kotlin reads a reply out of.
///
/// Written down on the Kotlin side as constants and checked here against what this
/// compiler actually chose. A layout that disagreed would not fail anywhere else: the
/// Renderer would read four bytes at the wrong offset and draw whatever they happened to
/// mean.
const _: () = {
    assert!(size_of::<MutationBatch>() == WEB_BATCH_BYTES as usize);
    assert!(offset_of!(MutationBatch, ptr) == 0);
    assert!(offset_of!(MutationBatch, len) == 4);
    assert!(offset_of!(MutationBatch, result) == 8);
};

/// What the Host lends the Renderer, once, for the life of the page.
///
/// The Renderer cannot own either buffer. `kotlin.wasm.unsafe` hands out addresses that
/// are only valid inside the scope that asked for them, and opening a scope per call is
/// the steady-state allocation the frame budget does not have. So both sit here, in this
/// module's region, and the Renderer keeps the address.
#[repr(C)]
struct BoundaryBlock {
    /// Where a call writes what it did. The Renderer reads it and releases it on the same
    /// call stack.
    out: MutationBatch,
    /// Where the Renderer encodes one event before the call that carries it.
    event: [u8; WEB_EVENT_BUFFER_BYTES as usize],
}

/// A browser tab is one thread, and the Renderer needs this address to stay where it is
/// between calls, so it is a static rather than a thread local.
static mut BLOCK: BoundaryBlock = BoundaryBlock {
    out: MutationBatch {
        ptr: std::ptr::null(),
        len: 0,
        result: 0,
    },
    event: [0; WEB_EVENT_BUFFER_BYTES as usize],
};

/// Where the Renderer is told the event buffer starts, against where it really starts.
const _: () = assert!(offset_of!(BoundaryBlock, event) == WEB_EVENT_BUFFER_OFFSET as usize);

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

extern "C" fn request_frame() {
    // SAFETY: the page supplied this import from the Renderer's exports before this
    // module was instantiated, so there is a function here to call.
    unsafe { compose_rust_renderer_request_frame() };
}

/// Whether an address the Renderer passed is one this side lent it.
///
/// Every buffer a call names is in this module's region. An address below the region is
/// either the Renderer allocator's, which would mean the two allocators have grown into
/// each other, or a stray number, and neither is worth reading or writing through.
fn lent(address: u32) -> bool {
    address >= WEB_RUST_REGION_BASE
}

/// `Init`, which calls `compose_rust_host_init`.
#[unsafe(no_mangle)]
pub extern "C" fn compose_rust_host_web_init(
    handshake: u32,
    handshake_length: u32,
    out: u32,
) -> i32 {
    if !((handshake_length == 0 || lent(handshake)) && lent(out)) {
        return STATUS_PROTOCOL_ERROR;
    }
    // SAFETY: every address above was checked to be inside this module's region,
    // which is where the only buffers the Renderer can name live.
    unsafe {
        crate::boundary::compose_rust_host_init(
            handshake as *const u8,
            handshake_length,
            out as *mut MutationBatch,
        )
    }
}

/// `DispatchEvent`, which calls `compose_rust_host_dispatch_event`.
#[unsafe(no_mangle)]
pub extern "C" fn compose_rust_host_web_dispatch_event(
    event: u32,
    event_length: u32,
    out: u32,
) -> i32 {
    if !((event_length == 0 || lent(event)) && lent(out)) {
        return STATUS_PROTOCOL_ERROR;
    }
    // SAFETY: every address above was checked to be inside this module's region,
    // which is where the only buffers the Renderer can name live.
    unsafe {
        crate::boundary::compose_rust_host_dispatch_event(
            event as *const u8,
            event_length,
            out as *mut MutationBatch,
        )
    }
}

/// `RenderFrame`, which calls `compose_rust_host_render_frame`.
#[unsafe(no_mangle)]
pub extern "C" fn compose_rust_host_web_render_frame(
    frame_time_nanos_low: u32,
    frame_time_nanos_high: u32,
    out: u32,
) -> i32 {
    if !(lent(out)) {
        return STATUS_PROTOCOL_ERROR;
    }
    let frame_time_nanos =
        (u64::from(frame_time_nanos_high) << 32) | u64::from(frame_time_nanos_low);
    // SAFETY: every address above was checked to be inside this module's region,
    // which is where the only buffers the Renderer can name live.
    unsafe {
        crate::boundary::compose_rust_host_render_frame(frame_time_nanos, out as *mut MutationBatch)
    }
}

/// `ReleaseBatch`, which calls `compose_rust_host_release_batch`.
#[unsafe(no_mangle)]
pub extern "C" fn compose_rust_host_web_release_batch(out: u32) -> i32 {
    if !(lent(out)) {
        return STATUS_PROTOCOL_ERROR;
    }
    // SAFETY: every address above was checked to be inside this module's region,
    // which is where the only buffers the Renderer can name live.
    unsafe { crate::boundary::compose_rust_host_release_batch(out as *mut MutationBatch) };
    STATUS_OK
}

/// `Shutdown`, which calls `compose_rust_host_shutdown`.
#[unsafe(no_mangle)]
pub extern "C" fn compose_rust_host_web_shutdown() -> i32 {
    crate::boundary::compose_rust_host_shutdown();
    STATUS_OK
}

/// Starts the Host and reports where the block it lends the Renderer sits.
///
/// A page has no library loader, so this is where the work `JNI_OnLoad` does on Android
/// goes: install the renderer API, then register the application's runtime. Zero means the
/// block is not somewhere the Renderer may read, and the Renderer makes no boundary call at
/// all in that case.
///
/// The application exports this as `compose_rust_host_web_start` through its authoring layer's
/// entry macro (`web_main!` in the Dioxus adapter), and the export lives there rather than
/// here because a wasm module cannot be linked with an undefined symbol the way an ELF
/// shared library can: an import nobody satisfies stops the module from being
/// instantiated, so this crate's own module must not name a function only an application
/// can define.
///
/// The builder comes from the application rather than being made here, because
/// everything an application settles before it launches, its theme above all, is settled
/// on a builder. Making one here would mean a page ignored the theme its own desktop
/// binary uses and drew the same screens in a different design system.
pub fn web_start(
    builder: LaunchBuilder,
    runtime: impl Fn() -> Box<dyn Runtime> + Send + Sync + 'static,
) -> u32 {
    let _ = install_renderer_api(RendererApi {
        run: platform_run,
        request_frame,
    });
    let status = builder
        .with_mode(LoopMode::Platform)
        .try_launch_runtime(runtime);
    if status != STATUS_OK {
        return 0;
    }
    let address = (&raw const BLOCK) as usize as u32;
    if lent(address) { address } else { 0 }
}