luau 0.732.0

Safe lifetime-bound Rust embedding API for the Luau runtime
use core::cell::{Cell, RefCell};
use core::ptr::{self, NonNull};
use std::rc::Rc;

use luau_vm::Thread as VmThread;
use luau_vm::state::{GlobalState, LuaCallbacks};
use luau_vm::thread::LUA_GLOBALS_INDEX;

use crate::hooks::CallbackManager;
use crate::lua::Compiler;
use crate::lua::RegistryState;
use crate::lua::app_data::AppData;
use crate::lua::memory::MemoryState;
use crate::lua::sandbox::LuaSandboxState;
use crate::userdata::PendingUserdataRegistrations;

pub(crate) struct RuntimeData {
    registry: RegistryState,
    userdata_registrations: PendingUserdataRegistrations,
    compiler: RefCell<Compiler>,
    callbacks: CallbackManager,
    memory: Rc<MemoryState>,
    app_data: AppData,
    global: GlobalState,
    current_thread: Cell<Option<NonNull<VmThread>>>,
    sandbox: Cell<LuaSandboxState>,
}

impl RuntimeData {
    pub(crate) fn new(memory: Rc<MemoryState>, main_thread: &VmThread) -> Self {
        let global = unsafe { main_thread.global() };
        Self {
            registry: RegistryState::default(),
            userdata_registrations: PendingUserdataRegistrations::default(),
            compiler: RefCell::new(Compiler::default()),
            callbacks: CallbackManager::default(),
            memory,
            app_data: AppData::default(),
            global,
            current_thread: Cell::new(None),
            sandbox: Cell::new(LuaSandboxState::Disabled),
        }
    }

    pub(crate) fn install_callbacks(&self, callbacks: &mut LuaCallbacks) {
        callbacks.userdata = ptr::from_ref(self).cast_mut().cast();
        self.callbacks.install(callbacks);
    }

    pub(crate) fn from_thread(thread: &VmThread) -> &Self {
        unsafe {
            let userdata = (*thread.callbacks()).userdata;
            debug_assert!(!userdata.is_null());
            &*userdata.cast::<Self>()
        }
    }

    pub(crate) const fn registry(&self) -> &RegistryState {
        &self.registry
    }

    pub(crate) const fn userdata_registrations(&self) -> &PendingUserdataRegistrations {
        &self.userdata_registrations
    }

    pub(crate) fn compiler(&self) -> Compiler {
        self.compiler.borrow().clone()
    }

    pub(crate) fn set_compiler(&self, compiler: Compiler) {
        *self.compiler.borrow_mut() = compiler;
    }

    pub(crate) const fn callbacks(&self) -> &CallbackManager {
        &self.callbacks
    }

    pub(crate) fn callbacks_mut(&mut self) -> &mut CallbackManager {
        &mut self.callbacks
    }

    pub(crate) fn memory(&self) -> &MemoryState {
        &self.memory
    }

    pub(crate) const fn app_data(&self) -> &AppData {
        &self.app_data
    }

    pub(super) fn is_main_thread(&self, thread: &VmThread) -> bool {
        unsafe { self.global.main_thread().eq(thread) }
    }

    pub(super) fn sandbox_state(&self) -> LuaSandboxState {
        self.sandbox.get()
    }

    pub(super) fn set_sandbox_state(&self, state: LuaSandboxState) {
        self.sandbox.set(state);
    }

    pub(crate) fn invalidate_managed_safe_env(&self) {
        if matches!(self.sandbox.get(), LuaSandboxState::Disabled) {
            return;
        }

        unsafe {
            self.global.main_thread().set_safe_env(LUA_GLOBALS_INDEX, 0);
            if let Some(current) = self.current_thread.get() {
                current.as_ref().set_safe_env(LUA_GLOBALS_INDEX, 0);
            }
        }
    }

    pub(crate) fn with_thread<R>(&self, thread: &VmThread, callback: impl FnOnce() -> R) -> R {
        let previous = self.current_thread.replace(Some(NonNull::from(thread)));
        let _scope = CurrentThreadScope {
            current: &self.current_thread,
            previous,
        };
        callback()
    }

    pub(crate) fn with_current_thread<R>(
        &self,
        callback: impl for<'thread> FnOnce(&'thread VmThread) -> R,
    ) -> R {
        match self.current_thread.get() {
            Some(current) => unsafe { callback(current.as_ref()) },
            None => {
                let main = unsafe { self.global.main_thread() };
                callback(&main)
            }
        }
    }
}

struct CurrentThreadScope<'runtime> {
    current: &'runtime Cell<Option<NonNull<VmThread>>>,
    previous: Option<NonNull<VmThread>>,
}

impl Drop for CurrentThreadScope<'_> {
    fn drop(&mut self) {
        self.current.set(self.previous);
    }
}