swiftui-native 0.0.1

SwiftUI from pure Rust — no Swift bridge, no shim, only swift-runtime calls
Documentation
//! `NSViewRepresentable` written in Rust — an AppKit view inside a SwiftUI tree.
//!
//! The thing a SwiftUI app reaches for when SwiftUI has no control for the job:
//! an `NSTextView`, a `WKWebView`, an `MTKView`. A Swift program writes a
//! `struct` with `makeNSView` and `updateNSView`; here the two are Rust closures
//! and the conformance is synthesised.
//!
//! ```no_run
//! # use swiftui_native::{objc, views};
//! # use core::ffi::c_void;
//! // Any `NSView` subclass, made and configured through the ObjC runtime.
//! let rep = views::ns_view_fn(
//!     || unsafe {
//!         let cls = objc::class(c"OBJC_CLASS_$_NSTextField");
//!         let v = objc::send(objc::send(cls, objc::sel(c"alloc")), objc::sel(c"init"));
//!         objc::send_id(v, objc::sel(c"setStringValue:"), objc::nsstring("AppKit"));
//!         v
//!     },
//!     |_view: *mut c_void| {},
//! );
//! let v = views::ns_view_representable(&rep);
//! ```
//!
//! # The knot, again
//!
//! `NSViewRepresentable: View where Body == Never`, and the `View` witnesses come
//! from SwiftUI's own extension on `NSViewRepresentable` — which wants
//! `Self: NSViewRepresentable` where a `View` witness is handed `Self: View`. So
//! each goes through a thunk that swaps the table, exactly as
//! [`crate::views::customshape`]'s `body` does. One turn simpler than `Shape`,
//! though: `Body` is `Never`, so there is no type that has to be instantiated
//! before the conformance exists.
//!
//! # What is not offered
//!
//! A coordinator. `Coordinator` defaults to `Void` and that is what this
//! supplies: the Rust closures own whatever state they need, so there is nothing
//! for a coordinator to hold that a captured `Arc` does not.

use super::{wrap_owned, ViewHandle};
use crate::abi;
use crate::conformance::{synthesize, Conformance, Protocol, Witness};
use crate::resolve;
use core::ffi::c_void;
use std::sync::{Mutex, OnceLock};

/// How an AppKit view is made and kept up to date.
pub trait Representing: Send + Sync + 'static {
    /// Make the view. Called once.
    fn make(&self) -> *mut c_void;
    /// Bring the view in line with the current state. Called on every update.
    fn update(&self, view: *mut c_void);
}

/// A registered representable, as the one word SwiftUI stores.
pub struct NSViewRep {
    index: i64,
}

/// A representable from two closures.
struct Pair<M, U> {
    make: M,
    update: U,
}

impl<M, U> Representing for Pair<M, U>
where
    M: Fn() -> *mut c_void + Send + Sync + 'static,
    U: Fn(*mut c_void) + Send + Sync + 'static,
{
    fn make(&self) -> *mut c_void {
        (self.make)()
    }
    fn update(&self, view: *mut c_void) {
        (self.update)(view)
    }
}

fn registry() -> &'static Mutex<Vec<&'static dyn Representing>> {
    static R: OnceLock<Mutex<Vec<&'static dyn Representing>>> = OnceLock::new();
    R.get_or_init(|| Mutex::new(Vec::new()))
}

/// Register a representable from a maker and an updater.
///
/// `make` must return a `+1` `NSView` subclass — SwiftUI takes ownership of it.
pub fn ns_view_fn(
    make: impl Fn() -> *mut c_void + Send + Sync + 'static,
    update: impl Fn(*mut c_void) + Send + Sync + 'static,
) -> NSViewRep {
    let leaked: &'static dyn Representing = Box::leak(Box::new(Pair { make, update }));
    let mut r = registry().lock().expect("representable registry");
    r.push(leaked);
    NSViewRep { index: r.len() as i64 - 1 }
}

// ── The witnesses SwiftUI calls ──
//
// `makeNSView(context:)` takes the context in x0 and `self` in x20, and returns
// the view in x0 — a class reference, so no indirect result.
core::arch::global_asm!(
    ".p2align 2",
    ".globl _rswift_nsvr_make",
    "_rswift_nsvr_make:",
    "mov x0, x20",
    "b _rswift_nsvr_make_body",
    // `updateNSView(_:context:)` — the view in x0, the context in x1, `self` in
    // x20. Only `self` has to move.
    ".p2align 2",
    ".globl _rswift_nsvr_update",
    "_rswift_nsvr_update:",
    "mov x1, x20",
    "b _rswift_nsvr_update_body",
    // `makeCoordinator()` with `Coordinator == Void`: an indirect result of zero
    // bytes, so there is nothing to write and nothing to read.
    ".p2align 2",
    ".globl _rswift_nsvr_coordinator",
    "_rswift_nsvr_coordinator:",
    "ret",
);

unsafe extern "C" {
    #[link_name = "rswift_nsvr_make"]
    fn nsvr_make_thunk();
    #[link_name = "rswift_nsvr_update"]
    fn nsvr_update_thunk();
    #[link_name = "rswift_nsvr_coordinator"]
    fn nsvr_coordinator_thunk();
}

/// The registered representable at `index`.
fn representing(index: usize) -> &'static dyn Representing {
    let r = registry().lock().expect("representable registry");
    r.get(index).copied().expect("representable index out of range")
}

/// `makeNSView(context:) -> NSViewType`.
///
/// # Safety
/// Reached through `rswift_nsvr_make`, which puts `self` in x0.
#[unsafe(no_mangle)]
unsafe extern "C" fn rswift_nsvr_make_body(this: *const u8) -> *mut c_void {
    let index = unsafe { *(this as *const i64) } as usize;
    let view = representing(index).make();
    assert!(!view.is_null(), "the representable's `make` returned nothing");
    view
}

/// `updateNSView(_:context:)`.
///
/// # Safety
/// Reached through `rswift_nsvr_update`, which puts `self` in x1.
#[unsafe(no_mangle)]
unsafe extern "C" fn rswift_nsvr_update_body(view: *mut c_void, this: *const u8) {
    let index = unsafe { *(this as *const i64) } as usize;
    representing(index).update(view);
}

// The three `View` witnesses come from SwiftUI's `NSViewRepresentable`
// extension, which wants `Self: NSViewRepresentable` in the register a `View`
// witness fills with `Self: View`. Each thunk swaps that one register and tail
// calls; the table to swap in is written once, when the conformance is built.
#[unsafe(no_mangle)]
static mut RSWIFT_NSVR_WT: *const c_void = core::ptr::null();

crate::thunk::runtime_thunk! {
    /// `_makeView(view:inputs:)` — x0/x1 the arguments, x2 `Self`, x3 the table.
    fn nsvr_make_view_thunk = "rswift_nsvr_make_view" via RSWIFT_NSVR_MAKE_VIEW {
        prologue: [
            "adrp x3, _RSWIFT_NSVR_WT@PAGE",
            "add x3, x3, _RSWIFT_NSVR_WT@PAGEOFF",
            "ldr x3, [x3]",
        ],
        branch: "br",
        epilogue: [],
    }
    /// `_makeViewList(view:inputs:)` — the same shape.
    fn nsvr_make_view_list_thunk = "rswift_nsvr_make_list" via RSWIFT_NSVR_MAKE_LIST {
        prologue: [
            "adrp x3, _RSWIFT_NSVR_WT@PAGE",
            "add x3, x3, _RSWIFT_NSVR_WT@PAGEOFF",
            "ldr x3, [x3]",
        ],
        branch: "br",
        epilogue: [],
    }
    /// `body` — a getter, so `Self` is in x0 and the table in x1.
    fn nsvr_body_thunk = "rswift_nsvr_body" via RSWIFT_NSVR_BODY {
        prologue: [
            "adrp x1, _RSWIFT_NSVR_WT@PAGE",
            "add x1, x1, _RSWIFT_NSVR_WT@PAGEOFF",
            "ldr x1, [x1]",
        ],
        branch: "br",
        epilogue: [],
    }
}

/// The carrier type a representable value *is*: one `Int`, its index.
fn carrier() -> (*const c_void, *const c_void) {
    static C: OnceLock<(usize, usize)> = OnceLock::new();
    let (d, m) = *C.get_or_init(|| unsafe {
        let (d, m) =
            crate::state::emit_int_carrier(b"RustNSViewRep\0", b"6rswift13RustNSViewRepV\0");
        (d as usize, m as usize)
    });
    (d as *const c_void, m as *const c_void)
}

fn representable_conformance() -> &'static Conformance {
    static C: OnceLock<Conformance> = OnceLock::new();
    C.get_or_init(|| unsafe {
        let protocol = Protocol::from_symbol(c"$s7SwiftUI19NSViewRepresentableMp");
        assert_eq!(
            protocol.requirement_count(),
            15,
            "NSViewRepresentable changed shape — it used to be View, two \
             associated types, and twelve methods of which three must be supplied",
        );

        // `NSViewType` is `NSView`. An imported Objective-C class's Swift
        // metadata is *not* simply its class object — the runtime wraps it, and
        // `swift_getObjCClassMetadata` is what does the wrapping. Passing the
        // bare class here is the kind of mistake that looks right: it is a
        // pointer, it is the right class, and everything that reads it as
        // metadata walks off into the class's `isa`.
        let class = crate::objc::class(c"OBJC_CLASS_$_NSView");
        assert!(!class.is_null(), "NSView class");
        let nsview =
            swift_runtime_sys::MetadataIntrospection::swift_getObjCClassMetadata(class);
        assert!(!nsview.is_null(), "NSView metadata");

        let view_wt = abi::get_view_wt(carrier().1);
        assert!(!view_wt.is_null(), "the carrier type has no View conformance");

        let conformance = synthesize(
            &protocol,
            carrier().0,
            &[
                Witness::Metadata(view_wt),
                Witness::Metadata(nsview),
                // `Coordinator`, from the protocol's own recorded default.
                //
                // Not `Witness::Metadata(())`. An associated type with a default
                // records it as a *mangled name* the runtime resolves in context,
                // and a raw metadata pointer in that slot is read as one — which
                // faulted inside `swift_initEnumMetadataSinglePayload`, building
                // an `Optional` of whatever it thought `Coordinator` was, a long
                // way from here.
                Witness::Default,
                Witness::Function(nsvr_make_thunk as *const c_void),
                Witness::Function(nsvr_update_thunk as *const c_void),
                Witness::Default,
                Witness::Default,
                Witness::Function(nsvr_coordinator_thunk as *const c_void),
                Witness::Default,
                Witness::Default,
                Witness::Default,
                Witness::Default,
                Witness::Default,
                Witness::Default,
                Witness::Default,
            ],
        );
        RSWIFT_NSVR_WT = conformance.witness_table();

        // The carrier's `View` table, as `crate::state` laid it out, pointed at
        // SwiftUI's own representable machinery: `Body` is `Never`, and the three
        // `_make…` entries and `body` come from the extension.
        RSWIFT_NSVR_MAKE_VIEW = resolve::nsvr_make_view();
        RSWIFT_NSVR_MAKE_LIST = resolve::nsvr_make_view_list();
        RSWIFT_NSVR_BODY = resolve::nsvr_body();
        let words = view_wt as *mut *const c_void;
        *words.add(1) = resolve::never_view_wt();
        *words.add(2) = resolve::never_meta();
        *words.add(3) = nsvr_make_view_thunk as *const c_void;
        *words.add(4) = nsvr_make_view_list_thunk as *const c_void;
        *words.add(6) = nsvr_body_thunk as *const c_void;

        conformance
    })
}

/// The representable as a view.
pub fn ns_view_representable(rep: &NSViewRep) -> ViewHandle {
    unsafe {
        let _ = representable_conformance();
        let meta = carrier().1;
        let wt = abi::get_view_wt(meta);
        assert!(!wt.is_null(), "{} !: View", crate::diag::type_name(meta));
        let mut bytes = vec![0u8; abi::type_bytes(meta)];
        bytes[..8].copy_from_slice(&rep.index.to_le_bytes());
        wrap_owned(bytes, meta, wt)
    }
}