tauri-plugin-bare-kit 0.2.1

Effortless JavaScript backends for Tauri apps - Android 🤖 iOS 📱Linux 🐧 Macos 🖥️ Windows 💩
use std::ptr::null_mut;
use std::sync::Arc;

use tauri::async_runtime::block_on;
use tauri::{AppHandle, Listener, Runtime, WebviewWindow};
use tokio::sync::Notify;

use crate::runtime::bare_kit::ffi::{
    bare_ipc_poll_t, bare_ipc_readable, bare_ipc_t, bare_ipc_writable, bare_worklet_t,
};
use crate::runtime::bare_kit::ipc::*;
use crate::runtime::bare_kit::worklet::*;

#[cfg(target_os = "android")]
use crate::runtime::bare_kit::ffi::{ALooper, ALooper_acquire, ALooper_release};

#[cfg(target_os = "android")]
pub(crate) struct Looper(*mut ALooper);

#[cfg(target_os = "android")]
unsafe impl Send for Looper {}

#[cfg(target_os = "android")]
unsafe impl Sync for Looper {}

pub(crate) struct BareKitWorklet<R: Runtime> {
    id: u8,
    app: AppHandle<R>,
    window: WebviewWindow<R>,

    worklet: *mut bare_worklet_t,
    ipc: *mut bare_ipc_t,
    poll: *mut bare_ipc_poll_t,

    started: bool,
    terminated: bool,

    on_poll: u32,
}

unsafe impl<R: Runtime> Send for BareKitWorklet<R> {}

unsafe impl<R: Runtime> Sync for BareKitWorklet<R> {}

impl<R: Runtime> Clone for BareKitWorklet<R> {
    fn clone(&self) -> Self {
        Self {
            id: self.id,
            app: self.app.clone(),
            window: self.window.clone(),
            worklet: self.worklet,
            ipc: self.ipc,
            poll: self.poll,
            started: self.started,
            terminated: self.terminated,
            on_poll: self.on_poll,
        }
    }
}

impl<R: Runtime> BareKitWorklet<R> {
    pub(crate) fn new(
        id: u8,
        app: AppHandle<R>,
        window: WebviewWindow<R>,
        memory_limit: usize,
        assets: Option<String>,
        on_poll: u32,
        on_suspend: u32,
        on_wakeup: u32,
        on_idle: u32,
        on_resume: u32,
    ) -> Self {
        let worklet = worklet_new(memory_limit, assets);

        let suspend_window = window.clone();
        worklet_on_suspend(worklet, move |linger| {
            suspend_window
                .eval(format!(
                    "window.__TAURI_INTERNALS__.runCallback({}, {{ linger: {} }})",
                    on_suspend, linger,
                ))
                .unwrap();
        });

        let wakeup_window = window.clone();
        worklet_on_wakeup(worklet, move |deadline| {
            wakeup_window
                .eval(format!(
                    "window.__TAURI_INTERNALS__.runCallback({}, {{ deadline: {} }})",
                    on_wakeup, deadline,
                ))
                .unwrap();
        });

        let idle_window = window.clone();
        worklet_on_idle(worklet, move || {
            idle_window
                .eval(format!(
                    "window.__TAURI_INTERNALS__.runCallback({})",
                    on_idle,
                ))
                .unwrap();
        });

        let resume_window = window.clone();
        worklet_on_resume(worklet, move || {
            resume_window
                .eval(format!(
                    "window.__TAURI_INTERNALS__.runCallback({})",
                    on_resume,
                ))
                .unwrap();
        });

        Self {
            id,
            app,
            window,
            worklet,
            ipc: null_mut(),
            poll: null_mut(),
            started: false,
            terminated: false,
            on_poll,
        }
    }

    pub(crate) fn optimize_for_memory(enabled: bool) {
        worklet_optimize_for_memory(enabled);
    }

    pub(crate) fn start(&mut self, filename: String, source: Option<Vec<u8>>, args: Vec<String>) {
        if self.started || self.terminated {
            return;
        }

        self.started = true;

        worklet_start(self.worklet, filename, source, args);
        self.ipc = ipc_new(self.worklet);
        self.poll = ipc_poll_new(self.ipc);
    }

    #[cfg(target_os = "android")]
    pub(crate) fn set_looper(&mut self, looper: &Looper) {
        unsafe {
            let mut poll = *self.poll;

            ALooper_release(poll.looper);
            poll.looper = looper.0;
            ALooper_acquire(poll.looper);
        }
    }

    pub(crate) fn read(&mut self) -> Option<Vec<u8>> {
        if !self.started || self.terminated {
            return None;
        }

        ipc_read(self.ipc)
    }

    pub(crate) fn write(&mut self, data: Option<Vec<u8>>) -> i32 {
        ipc_write(self.ipc, data)
    }

    pub(crate) fn update(&mut self, readable: bool, writable: bool) {
        if self.terminated {
            return;
        }

        let mut events = 0;

        if readable {
            events |= bare_ipc_readable;
        }

        if writable {
            events |= bare_ipc_writable;
        }

        if events > 0 {
            let worklet = self.clone();

            #[cfg(not(target_os = "android"))]
            ipc_poll_start(self.poll, events as i32, move |readable, writable| {
                let notify = Arc::new(Notify::new());
                let notifier = notify.clone();
                let notified = notify.notified();

                worklet
                    .app
                    .once(format!("bare-kit:worklet:{}", worklet.id), move |_| {
                        notifier.notify_waiters()
                    });

                worklet.window
                    .eval(format!(
                        "window.__TAURI_INTERNALS__.runCallback({}, {{ readable: {}, writable: {} }})",
                        worklet.on_poll, readable, writable,
                    ))
                    .unwrap();

                block_on(async move {
                    notified.await;
                });
            });

            #[cfg(target_os = "android")]
            ipc_poll_start(self.poll, events as i32, move |readable, writable| {
                worklet.window
                    .eval(format!(
                        "window.__TAURI_INTERNALS__.runCallback({}, {{ readable: {}, writable: {} }})",
                        worklet.on_poll, readable, writable,
                    ))
                    .unwrap();
            });
        } else {
            ipc_poll_stop(self.poll);
        }
    }

    pub(crate) fn suspend(&mut self, linger: i32) {
        worklet_suspend(self.worklet, linger);
    }

    pub(crate) fn resume(&mut self) {
        worklet_resume(self.worklet);
    }

    pub(crate) fn wakeup(&mut self, deadline: i32) {
        worklet_wakeup(self.worklet, deadline);
    }

    pub(crate) fn terminate(&mut self) {
        if self.terminated {
            return;
        }

        self.terminated = true;

        if self.started {
            worklet_terminate(self.worklet);
            ipc_poll_destroy(self.poll);
            ipc_destroy(self.ipc);
        }

        worklet_destroy(self.worklet);

        self.worklet = null_mut();
        self.ipc = null_mut();
        self.poll = null_mut();
    }
}