luau 0.732.0

Safe lifetime-bound Rust embedding API for the Luau runtime
use core::marker::PhantomData;
use std::cell::RefCell;

use crate::callback::{Arguments, CallbackReturn};
use crate::error::Error;
use crate::function::Function;
use crate::function::callback::{
    CallbackFn as FunctionCallbackFn, CallbackMut as FunctionCallbackMut, ScopedCallback,
    create_scoped as create_scoped_callback,
};
use crate::lua::{Lua, LuaRef};

/// A temporary context for callbacks that borrow Rust data.
///
/// Functions created by a scope are invalidated when the scope ends.
pub struct Scope<'scope, 'env: 'scope> {
    lua: LuaRef<'env>,
    callbacks: RefCell<Vec<ScopedCallback<'env>>>,
    destructors: Destructors<'env>,
    _scope_invariant: PhantomData<&'scope mut &'scope ()>,
    _env_invariant: PhantomData<&'env mut &'env ()>,
}

struct Destructors<'env>(RefCell<Vec<Box<dyn FnOnce() + 'env>>>);

impl Lua {
    /// Runs a closure with a temporary callback scope.
    ///
    /// The scope permits callbacks to borrow non-`'static` Rust data. Its
    /// callbacks are invalidated and its destructors run before this method
    /// returns.
    #[allow(clippy::let_and_return)]
    pub fn scope<'env, R>(
        &'env self,
        run: impl for<'scope> FnOnce(&'scope Scope<'scope, 'env>) -> Result<R, Error>,
    ) -> Result<R, Error> {
        // Keep the result local so the temporary scope is invalidated before returning.
        let result = run(&Scope::new(self.lua_ref()));
        result
    }
}

impl<'scope, 'env: 'scope> Scope<'scope, 'env> {
    fn new(lua: LuaRef<'env>) -> Self {
        Self {
            lua,
            callbacks: RefCell::new(Vec::new()),
            destructors: Destructors(RefCell::new(Vec::new())),
            _scope_invariant: PhantomData,
            _env_invariant: PhantomData,
        }
    }

    /// Creates an immutable callback that remains valid for this scope.
    pub fn create_function<F>(&'scope self, function: F) -> Result<Function<'scope>, Error>
    where
        F: for<'call> Fn(LuaRef<'call>, Arguments<'call>) -> Result<CallbackReturn<'call>, Error>
            + 'scope,
    {
        self.create_callback(FunctionCallbackFn::new(function))
    }

    /// Creates a mutable callback that remains valid for this scope.
    pub fn create_function_mut<F>(&'scope self, function: F) -> Result<Function<'scope>, Error>
    where
        F: for<'call> FnMut(
                LuaRef<'call>,
                Arguments<'call>,
            ) -> Result<CallbackReturn<'call>, Error>
            + 'scope,
    {
        self.create_callback(FunctionCallbackMut::new(function))
    }

    /// Runs a destructor when this scope ends.
    pub fn add_destructor(&self, destructor: impl FnOnce() + 'env) {
        self.destructors.0.borrow_mut().push(Box::new(destructor));
    }

    fn create_callback<F>(&'scope self, callback: F) -> Result<Function<'scope>, Error>
    where
        F: crate::callback::Callback + 'scope,
    {
        let scoped = unsafe { create_scoped_callback(&self.lua, Box::new(callback))? };
        let function: Function<'scope> = scoped.function().try_clone()?;
        self.callbacks.borrow_mut().push(scoped);
        Ok(function)
    }
}

impl Drop for Destructors<'_> {
    fn drop(&mut self) {
        while let Some(destructor) = self.0.get_mut().pop() {
            destructor();
        }
    }
}