lu 0.5.1

A library for embedding Luau
Documentation
use std::{cell::RefCell, ffi, marker::PhantomData, ptr::NonNull};

use crate::{
    Config, Library, LuauAllocator, Methods, Stack, Thread, ThreadData, ThreadMain, ThreadRef,
    Userdata,
};

pub struct State<C: Config> {
    libraries: Vec<(&'static str, Library<C>)>,
    alloc: NonNull<C::Allocator>,
    main: NonNull<RefCell<C::MainData>>,
    ptr: NonNull<sys::lua_State>,
}

impl<C: Config> Drop for State<C> {
    fn drop(&mut self) {
        unsafe {
            sys::lua_close(self.ptr.as_ptr());
            drop(Box::from_raw(self.main.as_ptr()));
            drop(Box::from_raw(self.alloc.as_ptr()));
        }
    }
}

impl<C: Config> State<C> {
    pub fn new(main_data: C::MainData, alloc: C::Allocator) -> Self {
        let main = NonNull::new(Box::into_raw(Box::new(RefCell::new(main_data)))).unwrap();
        let alloc = NonNull::new(Box::into_raw(Box::new(alloc))).unwrap();

        extern "C-unwind" fn alloc_fn<Alloc: LuauAllocator>(
            ud: *mut ffi::c_void,
            ptr: *mut ffi::c_void,
            osize: usize,
            nsize: usize,
        ) -> *mut ffi::c_void {
            let alloc = unsafe { ud.cast::<Alloc>().as_mut().unwrap_unchecked() };
            let ptr = ptr.cast::<u8>();

            if nsize == 0 {
                if !ptr.is_null() {
                    alloc.dealloc(ptr, osize);
                }

                std::ptr::null_mut()
            } else if ptr.is_null() {
                alloc.alloc(nsize).cast()
            } else {
                alloc.realloc(ptr, osize, nsize).cast()
            }
        }

        extern "C-unwind" fn userthread<C: Config>(
            parent: *mut sys::lua_State,
            thread: *mut sys::lua_State,
        ) {
            if !parent.is_null() {
                unsafe {
                    let parent = Thread::<C>(NonNull::new_unchecked(parent), PhantomData);
                    let thread = Thread::<C>(NonNull::new_unchecked(thread), PhantomData);

                    let data = C::ThreadData::new(&parent, &thread);
                    let data = Box::into_raw(Box::new(RefCell::new(data)));

                    sys::lua_setthreaddata(thread.as_ptr(), data.cast());
                }
            } else {
                unsafe {
                    drop(Box::from_raw(
                        sys::lua_getthreaddata(thread).cast::<RefCell<C::ThreadData>>(),
                    ));
                }
            }
        }

        let ptr = unsafe { sys::lua_newstate(alloc_fn::<C::Allocator>, alloc.as_ptr().cast()) };

        unsafe {
            let callbacks = sys::lua_callbacks(ptr);
            (*callbacks).userthread = Some(userthread::<C>);
        }

        Self {
            libraries: Vec::new(),
            alloc,
            main,
            ptr: NonNull::new(ptr).unwrap(),
        }
    }

    pub fn as_ptr(&self) -> *mut sys::lua_State {
        self.ptr.as_ptr()
    }

    pub fn thread(&self) -> ThreadMain<C> {
        unsafe { std::mem::transmute(self.ptr) }
    }

    pub fn stack(&self) -> &Stack<C> {
        unsafe { std::mem::transmute(&self.ptr) }
    }

    pub fn data(&self) -> &RefCell<C::MainData> {
        unsafe { self.main.as_ref() }
    }

    pub fn open_library(&mut self, name: &'static str, library: Library<C>) {
        let stack = self.stack();
        stack.reserve(3);

        stack.push_string(name);
        library.push(stack);

        stack.table_set_raw(sys::LUA_GLOBALSINDEX);

        self.libraries.push((name, library))
    }

    pub fn open_userdata<U: Userdata>(&self, methods: Methods<C>) {
        let methods = methods.methods;

        extern "C-unwind" fn dtor<U: Userdata>(_: *mut sys::lua_State, ud: *mut ffi::c_void) {
            let ud = ud.cast::<RefCell<U>>();
            unsafe { ud.drop_in_place() };
        }

        let stack = self.stack();
        stack.push_table();

        stack.push_string(U::name());
        stack.table_set_raw_field(-2, c"__type");

        stack.push_table_with(0, methods.len() as _);
        for (name, func) in methods {
            stack.push_string(name);
            stack.push_function(&func);
            stack.table_set_raw(-3);
        }

        stack.table_set_raw_field(-2, c"__index");

        unsafe {
            sys::lua_setuserdatametatable(self.as_ptr(), U::tag() as _);
            sys::lua_setuserdatadtor(self.as_ptr(), U::tag() as _, Some(dtor::<U>));
        }
    }

    pub fn open_std(&self) {
        unsafe { sys::luaL_openlibs(self.as_ptr()) };
    }

    pub fn open_base(&self) {
        unsafe { sys::luaopen_base(self.as_ptr()) };
    }

    pub fn open_coroutine(&self) {
        unsafe { sys::luaopen_coroutine(self.as_ptr()) };
    }

    pub fn open_table(&self) {
        unsafe { sys::luaopen_table(self.as_ptr()) };
    }

    pub fn open_os(&self) {
        unsafe { sys::luaopen_os(self.as_ptr()) };
    }

    pub fn open_string(&self) {
        unsafe { sys::luaopen_string(self.as_ptr()) };
    }

    pub fn open_bit(&self) {
        unsafe { sys::luaopen_bit32(self.as_ptr()) };
    }

    pub fn open_buffer(&self) {
        unsafe { sys::luaopen_buffer(self.as_ptr()) };
    }

    pub fn open_utf8(&self) {
        unsafe { sys::luaopen_utf8(self.as_ptr()) };
    }

    pub fn open_math(&self) {
        unsafe { sys::luaopen_math(self.as_ptr()) };
    }

    pub fn open_debug(&self) {
        unsafe { sys::luaopen_debug(self.as_ptr()) };
    }

    pub fn open_vector(&self) {
        unsafe { sys::luaopen_vector(self.as_ptr()) };
    }

    pub fn sandbox(&self) {
        unsafe { sys::luaL_sandbox(self.as_ptr()) }
    }

    pub fn new_thread(&self) -> ThreadRef<C> {
        let thread = self.stack().push_thread_new();
        self.stack().pop(1);

        thread
    }
}