#![allow(static_mut_refs)]
use std::{cell::RefCell, fmt};
use crate::{
lua::{self, ffi},
next_tick,
sync::XRc,
};
struct RefThread {
state: lua::State,
stack_top: i32,
free_slots: Vec<i32>,
}
thread_local! {
static REF_THREAD: RefCell<Option<RefThread>> = const { RefCell::new(None) };
}
#[derive(Clone)]
pub struct ValueRef {
pub(crate) index: i32,
pub(crate) xrc_index: Option<ValueRefIndex>,
}
#[derive(Clone)]
pub(crate) struct ValueRefIndex(pub(crate) XRc<i32>);
fn stack_pop() -> i32 {
with_ref_thread(|thread| {
if let Some(free) = thread.free_slots.pop() {
ffi::lua_replace(thread.state.0, free);
free
} else {
thread.stack_top += 1;
thread.stack_top
}
})
}
impl ValueRef {
#[inline]
pub(crate) fn new(index: i32) -> Self {
let xrc_index = Some(ValueRefIndex(XRc::new(index)));
Self { index, xrc_index }
}
pub(crate) fn push(&self, to: &lua::State) {
with_ref_thread(|thread| {
ffi::lua_xpush(thread.state.0, to.0, self.index);
});
}
pub(crate) fn pop() -> Self {
let index = stack_pop();
Self::new(index)
}
pub(crate) fn pop_from(from: &lua::State) -> Self {
with_ref_thread(|thread| {
ffi::lua_xmove(from.0, thread.state.0, 1);
});
Self::pop()
}
pub(crate) fn thread(&self) -> lua::State {
with_ref_thread(|thread| thread.state.clone())
}
}
impl Drop for ValueRef {
fn drop(&mut self) {
if let Some(ValueRefIndex(xrc)) = self.xrc_index.take()
&& XRc::into_inner(xrc).is_some()
{
let index = self.index;
next_tick(move |_| {
with_ref_thread(|thread| {
debug_assert!(
ffi::lua_gettop(thread.state.0) >= index,
"GC finalizer is not allowed in ref_thread"
);
ffi::lua_pushnil(thread.state.0);
ffi::lua_replace(thread.state.0, index);
thread.free_slots.push(index);
});
});
}
}
}
impl fmt::Debug for ValueRef {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "ValueRef({})", self.index)
}
}
fn set_ref_thread(state: RefThread) {
REF_THREAD.with(|c| *c.borrow_mut() = Some(state));
}
fn unset_ref_thread() {
REF_THREAD.with(|c| *c.borrow_mut() = None);
}
fn with_ref_thread<R>(f: impl FnOnce(&mut RefThread) -> R) -> R {
REF_THREAD.with(|c| {
let mut b = c.borrow_mut();
let st = b.as_mut().expect("RAW_STATE not initialized");
f(st)
})
}
inventory::submit! {
crate::open_close::new(
0,
"lua/reference",
|l| {
let thread = ffi::new_thread(l.0);
ffi::luaL_ref(l.0, ffi::LUA_REGISTRYINDEX);
set_ref_thread(RefThread {
state: lua::State(thread),
stack_top: 0,
free_slots: Vec::new(),
});
},
|_| {
unset_ref_thread();
},
)
}