use std::{cell::RefCell, fmt, sync::Mutex};
use rustc_hash::{FxBuildHasher, FxHashMap};
use crate::{
lua::{self, ffi},
next_tick,
sync::XRc,
};
static FREE_SLOTS: Mutex<FxHashMap<i32, bool>> = Mutex::new(FxHashMap::with_hasher(FxBuildHasher));
struct RefThread {
state: lua::State,
stack_top: i32,
}
thread_local! {
static REF_THREAD: RefCell<RefThread> = const { RefCell::new(RefThread {
state: lua::State(std::ptr::null_mut()),
stack_top: 0,
}) };
}
#[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| {
let free = {
let mut free_slots = FREE_SLOTS.lock().unwrap();
if let Some((&value, _)) = free_slots.iter().next() {
free_slots.remove(&value);
Some(value)
} else {
None
}
};
if let Some(free) = free {
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;
{
let mut free_slots = FREE_SLOTS.lock().unwrap();
free_slots.insert(index, true);
}
next_tick(move |_| {
with_ref_thread(|thread| {
debug_assert!(
ffi::lua_gettop(thread.state.0) >= index,
"GC finalizer is not allowed in ref_thread"
);
let mut free_slots = FREE_SLOTS.lock().unwrap();
if let Some(should_nil) = free_slots.get(&index)
&& *should_nil
{
ffi::lua_pushnil(thread.state.0);
ffi::lua_replace(thread.state.0, index);
free_slots.insert(index, false);
}
});
});
}
}
}
impl fmt::Debug for ValueRef {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "ValueRef({})", self.index)
}
}
fn set_ref_thread(state: lua::State) {
REF_THREAD.with(|c| {
let mut ref_thread = c.borrow_mut();
ref_thread.state = state;
ref_thread.stack_top = 0;
FREE_SLOTS.lock().unwrap().clear();
});
}
fn with_ref_thread<R>(f: impl FnOnce(&mut RefThread) -> R) -> R {
REF_THREAD.with(|c| {
let mut b = c.borrow_mut();
if b.state.0.is_null() {
panic!("RefThread not initialized!");
}
f(&mut b)
})
}
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(lua::State(thread));
},
|_| {
set_ref_thread(lua::State(std::ptr::null_mut()));
},
)
}