use core::ptr::NonNull;
use crate::VmErrorResult;
use crate::call::ThreadStack;
use crate::function::FunctionRuntime;
use crate::gc::{GcBarrier, GcRuntime};
use crate::handle::RawHandle;
use crate::state::{
BASIC_CI_SIZE, BASIC_STACK_SIZE, GlobalState, INITIAL_STACK_SIZE, LuaCallbacks, RawLuaState,
THREAD_STATUS_BREAK, THREAD_STATUS_OK, THREAD_STATUS_YIELD, ThreadLifecycle, ThreadState,
};
use crate::value::nil_object;
#[allow(
clippy::missing_safety_doc,
reason = "Thread's shared unsafe auxiliary API contract is documented on Thread"
)]
mod auxiliary;
#[allow(
clippy::missing_safety_doc,
reason = "Thread's shared unsafe call API contract is documented on Thread"
)]
mod call;
#[allow(
clippy::missing_safety_doc,
reason = "Thread's shared unsafe debug API contract is documented on Thread"
)]
mod debug;
#[allow(
clippy::missing_safety_doc,
reason = "Thread's shared unsafe GC API contract is documented on Thread"
)]
mod gc;
#[allow(
clippy::missing_safety_doc,
reason = "Thread's shared unsafe stack API contract is documented on Thread"
)]
pub(crate) mod stack;
#[allow(
clippy::missing_safety_doc,
reason = "Thread's shared unsafe string builder contract is documented on Thread"
)]
mod string_builder;
#[allow(
clippy::missing_safety_doc,
reason = "Thread's shared unsafe table API contract is documented on Thread"
)]
mod table;
#[allow(
clippy::missing_safety_doc,
reason = "Thread's shared unsafe userdata API contract is documented on Thread"
)]
mod userdata;
pub use crate::types::{
LUA_TBOOLEAN, LUA_TBUFFER, LUA_TCLASS, LUA_TFUNCTION, LUA_TINTEGER, LUA_TLIGHTUSERDATA,
LUA_TNIL, LUA_TNUMBER, LUA_TOBJECT, LUA_TSTRING, LUA_TTABLE, LUA_TTHREAD, LUA_TUSERDATA,
LUA_TVECTOR,
};
pub use string_builder::{LuaStringBuilder, LuaStringBuilderStorage};
#[derive(PartialEq, Eq)]
#[repr(transparent)]
pub struct Thread {
pub(crate) raw: NonNull<RawLuaState>,
}
impl crate::handle::sealed::Sealed for Thread {}
impl RawHandle for Thread {
type Raw = RawLuaState;
fn as_ptr(&self) -> *mut RawLuaState {
self.raw.as_ptr()
}
}
impl AsRef<Thread> for Thread {
fn as_ref(&self) -> &Thread {
self
}
}
impl Thread {
pub unsafe fn global(&self) -> GlobalState {
unsafe {
GlobalState::from_raw(NonNull::new_unchecked(
self.as_ptr().as_ref().unwrap_unchecked().global,
))
}
}
}
#[must_use = "dropping the guard restores the captured stack height"]
pub struct StackGuard<'thread> {
thread: &'thread Thread,
top: i32,
restore_on_drop: bool,
}
impl<'thread> StackGuard<'thread> {
pub unsafe fn new(thread: &'thread Thread) -> Self {
Self {
thread,
top: unsafe { thread.get_top() },
restore_on_drop: true,
}
}
pub const fn top(&self) -> i32 {
self.top
}
pub fn keep(&mut self, count: i32) {
debug_assert!(count >= 0);
self.top += count;
}
pub fn into_top(mut self) -> i32 {
self.restore_on_drop = false;
self.top
}
pub fn dismiss(mut self) {
self.restore_on_drop = false;
}
}
impl Drop for StackGuard<'_> {
fn drop(&mut self) {
if self.restore_on_drop {
unsafe { self.thread.restore_top(self.top) };
}
}
}
pub const LUA_MIN_STACK: usize = 20;
pub const LUAI_MAX_C_STACK: i32 = 8000;
pub const LUAI_MAX_CALLS: usize = 20_000;
pub const LUAI_MAX_NATIVE_CALLS: u16 = 200;
pub const LUA_BUFFER_SIZE: usize = 512;
pub const LUA_MULTRET: i32 = -1;
pub const LUA_TNONE: i32 = -1;
pub const LUA_REGISTRY_INDEX: i32 = -LUAI_MAX_C_STACK - 2000;
pub const LUA_ENVIRON_INDEX: i32 = -LUAI_MAX_C_STACK - 2001;
pub const LUA_GLOBALS_INDEX: i32 = -LUAI_MAX_C_STACK - 2002;
pub const LUA_NOREF: i32 = -1;
pub const LUA_REFNIL: i32 = 0;
pub const LUA_CORUN: i32 = 0;
pub const LUA_COSUS: i32 = 1;
pub const LUA_CONOR: i32 = 2;
pub const LUA_COFIN: i32 = 3;
pub const LUA_COERR: i32 = 4;
pub const fn upvalue_index(index: i32) -> i32 {
LUA_GLOBALS_INDEX - index
}
pub const fn is_pseudo(index: i32) -> bool {
index <= LUA_REGISTRY_INDEX
}
#[allow(
clippy::missing_safety_doc,
reason = "Thread's shared unsafe lifecycle API contract is documented on Thread"
)]
impl Thread {
pub unsafe fn same_vm(&self, other: impl AsRef<Thread>) -> bool {
unsafe { self.global() == other.as_ref().global() }
}
pub unsafe fn encode_pointer(&self, pointer: usize) -> usize {
unsafe { self.global().encode_pointer(pointer) }
}
pub unsafe fn set_pointer_encode_key(&self, a: u64, b: u64, c: u64, d: u64) {
unsafe {
self.global()
.as_ptr()
.as_mut()
.unwrap_unchecked()
.ptr_enc_key = [a & !1, b | 1, c, d];
}
}
pub unsafe fn reset(&self) -> VmErrorResult {
unsafe {
debug_assert!(!self.as_ptr().as_ref().unwrap_unchecked().is_active);
debug_assert!(
self.as_ptr().as_ref().unwrap_unchecked().status != THREAD_STATUS_OK
|| self.as_ptr().as_ref().unwrap_unchecked().ci
== self.as_ptr().as_ref().unwrap_unchecked().base_ci
);
self.close(self.base_call_info().function().value_unchecked());
let function = self.stack();
let ci = self.base_call_info();
ci.function().value_unchecked().set_nil();
ci.init_call(function, function.add(1 + LUA_MIN_STACK), 0, None);
let raw = self.as_ptr().as_mut().unwrap_unchecked();
raw.status = THREAD_STATUS_OK;
raw.native_call_depth = 0;
raw.base_native_call_depth = 0;
self.set_current_call_info(self.base_call_info_cursor());
self.set_stack_base(ci.base());
self.set_stack_top(ci.base());
if self.as_ptr().as_ref().unwrap_unchecked().size_ci as usize != BASIC_CI_SIZE {
self.realloc_ci(BASIC_CI_SIZE as i32)?;
}
let target_stack_size = INITIAL_STACK_SIZE as i32;
if self.as_ptr().as_ref().unwrap_unchecked().stack_size != target_stack_size {
self.realloc_stack(BASIC_STACK_SIZE as i32, false)?;
}
let stack = self.stack();
let stack_size = self.as_ptr().as_ref().unwrap_unchecked().stack_size as usize;
for index in 0..stack_size {
stack.add(index).value_unchecked().set_nil();
}
}
Ok(())
}
pub unsafe fn is_reset(&self) -> bool {
unsafe {
self.current_call_info() == self.base_call_info()
&& self.as_ptr().as_ref().unwrap_unchecked().base
== self.as_ptr().as_ref().unwrap_unchecked().top
&& self.as_ptr().as_ref().unwrap_unchecked().status == THREAD_STATUS_OK
}
}
pub unsafe fn status(&self) -> i32 {
unsafe { self.as_ptr().as_ref().unwrap_unchecked().status as i32 }
}
pub unsafe fn is_yieldable(&self) -> i32 {
i32::from(unsafe {
self.as_ptr().as_ref().unwrap_unchecked().native_call_depth
<= self
.as_ptr()
.as_ref()
.unwrap_unchecked()
.base_native_call_depth
})
}
pub unsafe fn thread_data(&self) -> *mut () {
unsafe { self.as_ptr().as_ref().unwrap_unchecked().userdata }
}
pub unsafe fn set_thread_data(&self, data: *mut ()) {
unsafe {
self.as_ptr().as_mut().unwrap_unchecked().userdata = data;
}
}
pub unsafe fn callbacks(&self) -> *mut LuaCallbacks {
unsafe { self.global().callbacks() }
}
}
#[allow(
clippy::missing_safety_doc,
reason = "Thread's shared unsafe thread-value API contract is documented on Thread"
)]
impl Thread {
pub unsafe fn push_thread(&self) -> VmErrorResult<i32> {
unsafe {
self.thread_barrier();
self.ensure_stack(self, 1)?;
let top = self.stack_top();
top.value_unchecked().set_thread_value(self);
debug_assert!(top < self.current_call_info().top());
self.set_stack_top(top.add(1));
}
Ok(i32::from(unsafe { self.global().main_thread() == *self }))
}
pub unsafe fn to_thread(&self, index: i32) -> Option<Thread> {
let object = unsafe { self.index_to_addr(index) };
if object == nil_object() || !object.is_thread() {
None
} else {
Some(object.thread_value())
}
}
pub unsafe fn new_thread(&self) -> VmErrorResult<Thread> {
unsafe {
self.check_gc()?;
self.thread_barrier();
self.ensure_stack(self, 1)?;
let thread = self.new_thread_internal()?;
let top = self.stack_top();
top.value_unchecked().set_thread_value(&thread);
debug_assert!(top < self.current_call_info().top());
self.set_stack_top(top.add(1));
if let Some(user_thread) = self.global().user_thread_callback() {
user_thread(Some(self), &thread);
}
Ok(thread)
}
}
pub unsafe fn co_status(&self, thread: &Thread) -> i32 {
unsafe {
debug_assert!(self.global() == thread.global());
if *thread == *self {
LUA_CORUN
} else if thread.as_ptr().as_ref().unwrap_unchecked().status == THREAD_STATUS_YIELD {
LUA_COSUS
} else if thread.as_ptr().as_ref().unwrap_unchecked().status == THREAD_STATUS_BREAK {
LUA_CONOR
} else if thread.as_ptr().as_ref().unwrap_unchecked().status != THREAD_STATUS_OK {
LUA_COERR
} else if thread.current_call_info() != thread.base_call_info() {
LUA_CONOR
} else if thread.stack_top() == thread.stack_base() {
LUA_COFIN
} else {
LUA_COSUS
}
}
}
}