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;
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(())
}
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()
}
}