use std::any::{Any, TypeId};
use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use std::ops::{Deref, DerefMut};
use std::rc::Rc;
use crate::error::{Error, Result};
use crate::state::Lua;
use crate::sys::lua_State;
type Entry = Rc<RefCell<Box<dyn Any>>>;
#[derive(Default)]
struct Store {
entries: HashMap<TypeId, Entry>,
borrow: Rc<Cell<usize>>,
}
thread_local! {
static APP_DATA: RefCell<HashMap<*mut core::ffi::c_void, Store>> =
RefCell::new(HashMap::new());
}
unsafe fn vm_key(state: *mut lua_State) -> *mut core::ffi::c_void {
unsafe { (*state).global as *mut core::ffi::c_void }
}
pub struct AppDataRef<T: 'static> {
_owner: Entry,
guard: std::cell::Ref<'static, Box<dyn Any>>,
borrow: Rc<Cell<usize>>,
_marker: std::marker::PhantomData<T>,
}
impl<T: 'static> Drop for AppDataRef<T> {
fn drop(&mut self) {
self.borrow.set(self.borrow.get().saturating_sub(1));
}
}
impl<T: 'static> Deref for AppDataRef<T> {
type Target = T;
fn deref(&self) -> &T {
self.guard
.downcast_ref::<T>()
.expect("app data type mismatch")
}
}
impl<T: std::fmt::Debug + 'static> std::fmt::Debug for AppDataRef<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
(**self).fmt(f)
}
}
impl<T: std::fmt::Display + 'static> std::fmt::Display for AppDataRef<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
(**self).fmt(f)
}
}
impl<T: PartialEq + 'static> PartialEq<T> for AppDataRef<T> {
fn eq(&self, other: &T) -> bool {
(**self) == *other
}
}
pub struct AppDataRefMut<T: 'static> {
_owner: Entry,
guard: std::cell::RefMut<'static, Box<dyn Any>>,
borrow: Rc<Cell<usize>>,
_marker: std::marker::PhantomData<T>,
}
impl<T: 'static> Drop for AppDataRefMut<T> {
fn drop(&mut self) {
self.borrow.set(self.borrow.get().saturating_sub(1));
}
}
impl<T: 'static> Deref for AppDataRefMut<T> {
type Target = T;
fn deref(&self) -> &T {
self.guard
.downcast_ref::<T>()
.expect("app data type mismatch")
}
}
impl<T: 'static> DerefMut for AppDataRefMut<T> {
fn deref_mut(&mut self) -> &mut T {
self.guard
.downcast_mut::<T>()
.expect("app data type mismatch")
}
}
impl<T: std::fmt::Debug + 'static> std::fmt::Debug for AppDataRefMut<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
(**self).fmt(f)
}
}
impl<T: PartialEq + 'static> PartialEq<T> for AppDataRefMut<T> {
fn eq(&self, other: &T) -> bool {
(**self) == *other
}
}
impl Lua {
pub fn set_app_data<T: 'static>(&self, data: T) {
self.try_set_app_data(data)
.expect("cannot mutably borrow app data container");
}
pub fn try_set_app_data<T: 'static>(&self, data: T) -> Result<Option<T>> {
let key = unsafe { vm_key(self.state()) };
APP_DATA.with(|m| {
let mut outer = m.borrow_mut();
let store = outer.entry(key).or_default();
if store.borrow.get() != 0 {
return Err(Error::runtime("cannot mutably borrow app data container"));
}
let old = store
.entries
.insert(TypeId::of::<T>(), Rc::new(RefCell::new(Box::new(data))));
Ok(old.and_then(|e| {
Rc::try_unwrap(e)
.ok()
.and_then(|cell| cell.into_inner().downcast::<T>().ok().map(|b| *b))
}))
})
}
pub fn app_data_ref<T: 'static>(&self) -> Option<AppDataRef<T>> {
match self.try_app_data_ref::<T>() {
Ok(opt) => opt,
Err(_) => panic!("already mutably borrowed"),
}
}
pub fn try_app_data_ref<T: 'static>(&self) -> Result<Option<AppDataRef<T>>> {
let key = unsafe { vm_key(self.state()) };
let (entry, borrow) = match APP_DATA.with(|m| {
let outer = m.borrow();
outer.get(&key).and_then(|store| {
store
.entries
.get(&TypeId::of::<T>())
.map(|e| (e.clone(), store.borrow.clone()))
})
}) {
Some(pair) => pair,
None => return Ok(None),
};
let guard = entry
.try_borrow()
.map_err(|_| Error::runtime("app data is currently mutably borrowed"))?;
let guard: std::cell::Ref<'static, Box<dyn Any>> = unsafe { std::mem::transmute(guard) };
borrow.set(borrow.get() + 1);
Ok(Some(AppDataRef {
_owner: entry,
guard,
borrow,
_marker: std::marker::PhantomData,
}))
}
pub fn app_data_mut<T: 'static>(&self) -> Option<AppDataRefMut<T>> {
match self.try_app_data_mut::<T>() {
Ok(opt) => opt,
Err(_) => panic!("already borrowed"),
}
}
pub fn try_app_data_mut<T: 'static>(&self) -> Result<Option<AppDataRefMut<T>>> {
let key = unsafe { vm_key(self.state()) };
let (entry, borrow) = match APP_DATA.with(|m| {
let outer = m.borrow();
outer.get(&key).and_then(|store| {
store
.entries
.get(&TypeId::of::<T>())
.map(|e| (e.clone(), store.borrow.clone()))
})
}) {
Some(pair) => pair,
None => return Ok(None),
};
let guard = entry
.try_borrow_mut()
.map_err(|_| Error::runtime("app data is currently borrowed"))?;
let guard: std::cell::RefMut<'static, Box<dyn Any>> = unsafe { std::mem::transmute(guard) };
borrow.set(borrow.get() + 1);
Ok(Some(AppDataRefMut {
_owner: entry,
guard,
borrow,
_marker: std::marker::PhantomData,
}))
}
pub fn remove_app_data<T: 'static>(&self) -> Option<T> {
let key = unsafe { vm_key(self.state()) };
APP_DATA.with(|m| {
let mut outer = m.borrow_mut();
let store = outer.get_mut(&key)?;
if store.borrow.get() != 0 {
panic!("cannot mutably borrow app data container");
}
let entry = store.entries.remove(&TypeId::of::<T>())?;
Rc::try_unwrap(entry)
.ok()
.and_then(|cell| cell.into_inner().downcast::<T>().ok().map(|b| *b))
})
}
}
pub(crate) fn clear_app_data(state: *mut lua_State) {
let key = unsafe { vm_key(state) };
APP_DATA.with(|m| {
m.borrow_mut().remove(&key);
});
}