luau 0.732.0

Safe lifetime-bound Rust embedding API for the Luau runtime
use core::fmt;

use luau_common::ByteSlice;
use luau_vm::Thread as VmThread;
use luau_vm::VmErrorResult;
use luau_vm::internal::RawHandle;
use luau_vm::thread::StackGuard;

use crate::error::Error;
use crate::lua::runtime::RuntimeData;
use crate::lua::{LuaRef, RegistryState};
use crate::value::ValueRef;

mod conversion;
mod execution;

pub use execution::ThreadStatus;

/// A handle to a Luau thread or coroutine.
pub struct Thread<'lua> {
    handle: ThreadHandle<'lua>,
}

enum ThreadHandle<'lua> {
    Borrowed {
        thread: &'lua VmThread,
        runtime: &'lua RuntimeData,
    },
    Rooted {
        reference: ValueRef<'lua>,
        thread: VmThread,
    },
}

impl<'lua> Thread<'lua> {
    pub(crate) const fn new(thread: &'lua VmThread, runtime: &'lua RuntimeData) -> Self {
        Self {
            handle: ThreadHandle::Borrowed { thread, runtime },
        }
    }

    pub(crate) fn from_ref(
        reference_thread: &'lua VmThread,
        runtime: &'lua RuntimeData,
        thread: VmThread,
        reference: i32,
        pointer: *const (),
    ) -> Self {
        Self {
            handle: ThreadHandle::Rooted {
                reference: ValueRef::new(reference_thread, runtime, reference, pointer),
                thread,
            },
        }
    }

    pub(crate) unsafe fn from_stack(thread: &Thread<'lua>, index: i32) -> VmErrorResult<Self> {
        unsafe {
            let vm_thread = thread.as_vm();
            debug_assert_ne!(vm_thread.is_thread(index), 0);
            let value = vm_thread
                .to_thread(index)
                .expect("thread stack slot should contain a thread");
            let reference = ValueRef::from_stack(thread, index)?;
            Ok(Self {
                handle: ThreadHandle::Rooted {
                    reference,
                    thread: value,
                },
            })
        }
    }

    pub(crate) fn as_vm(&self) -> &VmThread {
        self.vm_thread()
    }

    fn vm_thread(&self) -> &VmThread {
        match &self.handle {
            ThreadHandle::Borrowed { thread, .. } => thread,
            ThreadHandle::Rooted { thread, .. } => thread,
        }
    }

    pub(crate) fn registry(&self) -> &'lua RegistryState {
        self.runtime().registry()
    }

    pub(crate) fn runtime(&self) -> &'lua RuntimeData {
        match &self.handle {
            ThreadHandle::Borrowed { runtime, .. } => runtime,
            ThreadHandle::Rooted { reference, .. } => reference.runtime(),
        }
    }

    pub(crate) fn lua_ref(&self) -> LuaRef<'lua> {
        LuaRef::new(self.reference_thread(), self.runtime())
    }

    pub(crate) fn reference_thread(&self) -> &'lua VmThread {
        match &self.handle {
            ThreadHandle::Borrowed { thread, .. } => thread,
            ThreadHandle::Rooted { reference, .. } => reference.reference_thread(),
        }
    }

    pub(crate) fn pointer(&self) -> *const () {
        match &self.handle {
            ThreadHandle::Borrowed { thread, .. } => thread.as_ptr().cast(),
            ThreadHandle::Rooted { reference, .. } => reference.pointer(),
        }
    }

    pub(crate) fn same_vm(&self, other: impl AsRef<VmThread>) -> bool {
        unsafe { self.as_vm().same_vm(other) }
    }

    pub(crate) fn stack_type_name(&self, index: i32) -> String {
        unsafe {
            let tag = self.as_vm().type_of(index);
            let name = self.as_vm().type_name(tag);
            name.as_bytes().to_str_lossy().into_owned()
        }
    }

    pub(crate) fn reserve_stack(&self, amount: usize) -> Result<(), Error> {
        let amount = i32::try_from(amount).map_err(|_| Error::StackError)?;
        if amount > 0 && unsafe { self.as_vm().check_stack(amount) } == 0 {
            return Err(Error::StackError);
        }
        Ok(())
    }

    /// Creates another handle to this thread.
    pub fn try_clone(&self) -> Result<Self, Error> {
        match &self.handle {
            ThreadHandle::Borrowed { thread, runtime } => Ok(Self::new(thread, runtime)),
            ThreadHandle::Rooted { reference, .. } => unsafe {
                let reference_thread = reference.reference_thread();
                let _stack = StackGuard::new(reference_thread);
                reference
                    .push()
                    .map_err(|exit| Error::from_thread_exit(reference_thread, exit))?;
                let borrowed = Self::new(reference_thread, reference.runtime());
                Self::from_stack(&borrowed, -1)
                    .map_err(|exit| Error::from_thread_exit(reference_thread, exit))
            },
        }
    }

    pub(crate) fn push_to(&self, target: impl AsRef<VmThread>) -> Result<(), Error> {
        match &self.handle {
            ThreadHandle::Rooted { reference, .. } => reference.push_to(target),
            ThreadHandle::Borrowed { thread, .. } => unsafe {
                let target = target.as_ref();
                if !thread.same_vm(target) {
                    return Err(Error::foreign_lua_handle());
                }

                if *thread == target {
                    thread
                        .push_thread()
                        .map_err(|exit| Error::from_thread_exit(thread, exit))?;
                } else {
                    let _stack = StackGuard::new(thread);
                    thread
                        .push_thread()
                        .map_err(|exit| Error::from_thread_exit(thread, exit))?;
                    thread
                        .x_move(target, 1)
                        .map_err(|exit| Error::from_thread_exit(thread, exit))?;
                }
                Ok(())
            },
        }
    }
}

impl PartialEq for Thread<'_> {
    fn eq(&self, other: &Self) -> bool {
        self.same_vm(other.as_vm()) && self.as_vm() == other.as_vm()
    }
}

impl Eq for Thread<'_> {}

impl fmt::Debug for Thread<'_> {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        let mut tuple = formatter.debug_tuple("Thread");
        match &self.handle {
            ThreadHandle::Borrowed { thread, .. } => tuple.field(&thread.as_ptr().cast::<()>()),
            ThreadHandle::Rooted { reference, .. } => tuple.field(reference),
        };
        tuple.finish()
    }
}