use crate::ffi;
use crate::lua_functions::LuaFunction;
use crate::object::{FromObject, Object};
use crate::{AsLua, LuaRead, LuaState, Push, PushInto, PushOneInto};
use std::cell::UnsafeCell;
use std::convert::TryFrom;
use std::num::NonZeroI32;
use std::os::raw::{c_char, c_void};
#[derive(Debug, Clone, Copy)]
enum CDataRef<'l> {
Ptr(*mut c_void),
Slice(&'l [u8]),
}
#[derive(Debug)]
pub struct CDataOnStack<'l, L> {
inner: Object<L>,
data: UnsafeCell<CDataRef<'l>>,
ctypeid: ffi::CTypeID,
}
impl<L> CDataOnStack<'_, L>
where
L: AsLua,
{
#[inline(always)]
pub fn as_ptr(&self) -> *const c_void {
match unsafe { *self.data.get() } {
CDataRef::Ptr(ptr) => ptr,
CDataRef::Slice(slice) => slice.as_ptr().cast(),
}
}
fn update_data(&self, ptr: *const c_void) -> &[u8] {
let f = LuaFunction::load(self, "return require('ffi').sizeof(...)").unwrap();
let size: usize = f.into_call_with_args(self).unwrap();
unsafe {
let slice = std::slice::from_raw_parts(ptr.cast(), size);
std::ptr::write(self.data.get(), CDataRef::Slice(slice));
slice
}
}
pub fn try_as_bytes(&self) -> Option<&[u8]> {
match unsafe { *self.data.get() } {
CDataRef::Slice(slice) => Some(slice),
CDataRef::Ptr(_) => None,
}
}
pub fn try_as_bytes_mut(&mut self) -> Option<&mut [u8]> {
match unsafe { *self.data.get() } {
CDataRef::Slice(slice) => unsafe {
Some(std::slice::from_raw_parts_mut(
slice.as_ptr() as *mut _,
slice.len(),
))
},
CDataRef::Ptr(_) => None,
}
}
#[inline(always)]
pub fn data(&self) -> &[u8] {
match unsafe { *self.data.get() } {
CDataRef::Ptr(ptr) => self.update_data(ptr),
CDataRef::Slice(slice) => slice,
}
}
#[inline(always)]
pub fn data_mut(&mut self) -> &mut [u8] {
let data = self.data();
unsafe { std::slice::from_raw_parts_mut(data.as_ptr() as *mut _, data.len()) }
}
#[inline(always)]
pub fn ctypeid(&self) -> ffi::CTypeID {
self.ctypeid
}
#[inline(always)]
pub fn try_downcast<T>(&self) -> Option<&T>
where
T: AsCData,
{
self.check_ctypeid::<T>()
.then(|| unsafe { &*self.as_ptr().cast::<T>() })
}
#[inline(always)]
pub fn try_downcast_mut<T>(&self) -> Option<&mut T>
where
T: AsCData,
{
self.check_ctypeid::<T>()
.then(|| unsafe { &mut *(self.as_ptr().cast::<T>() as *mut _) })
}
#[inline(always)]
pub fn try_downcast_into<T>(self) -> Result<T, Self>
where
T: AsCData,
{
self.check_ctypeid::<T>()
.then(|| unsafe { std::ptr::read(self.as_ptr().cast::<T>()) })
.ok_or(self)
}
#[inline(always)]
#[allow(clippy::nonminimal_bool)]
fn check_ctypeid<T: AsCData>(&self) -> bool {
self.ctypeid == T::ctypeid() && {
if cfg!(debug_assertions) {
assert_eq!(self.data().len(), std::mem::size_of::<T>());
}
true
}
}
}
impl<L> FromObject<L> for CDataOnStack<'_, L>
where
L: AsLua,
{
unsafe fn check(lua: impl AsLua, index: NonZeroI32) -> bool {
ffi::lua_type(lua.as_lua(), index.into()) == ffi::LUA_TCDATA
}
unsafe fn from_obj(inner: Object<L>) -> Self {
let mut ctypeid = 0;
let cdata = ffi::luaL_checkcdata(inner.as_lua(), inner.index().into(), &mut ctypeid);
Self {
inner,
data: UnsafeCell::new(CDataRef::Ptr(cdata)),
ctypeid,
}
}
}
impl<L> TryFrom<Object<L>> for CDataOnStack<'_, L>
where
L: AsLua,
{
type Error = Object<L>;
#[inline(always)]
fn try_from(o: Object<L>) -> Result<Self, Self::Error> {
Self::try_from_obj(o)
}
}
impl<'a, L> From<CDataOnStack<'a, L>> for Object<L> {
fn from(ud: CDataOnStack<'a, L>) -> Self {
ud.inner
}
}
impl<L> LuaRead<L> for CDataOnStack<'_, L>
where
L: AsLua,
{
#[inline]
fn lua_read_at_position(lua: L, index: NonZeroI32) -> Result<Self, L> {
Self::try_from_obj(Object::new(lua, index)).map_err(Object::into_guard)
}
}
impl<L, O> Push<L> for CDataOnStack<'_, O>
where
L: AsLua,
{
type Err = crate::Void;
#[inline]
fn push_to_lua(&self, lua: L) -> Result<crate::PushGuard<L>, (Self::Err, L)> {
unsafe {
crate::ffi::lua_pushvalue(lua.as_lua(), self.inner.index().into());
Ok(crate::PushGuard::new(lua, 1))
}
}
}
impl<L> AsLua for CDataOnStack<'_, L>
where
L: AsLua,
{
#[inline]
fn as_lua(&self) -> LuaState {
self.inner.as_lua()
}
}
pub unsafe trait AsCData: Sized + 'static {
fn ctypeid() -> ffi::CTypeID;
}
macro_rules! impl_builtin_as_cdata {
($($t:ty: $ctid:expr),* $(,)?) => {
$(
unsafe impl AsCData for $t {
fn ctypeid() -> ffi::CTypeID {
$ctid
}
}
)*
};
}
impl_builtin_as_cdata! {
i8 : ffi::CTID_INT8,
i16: ffi::CTID_INT16,
i32: ffi::CTID_INT32,
i64: ffi::CTID_INT64,
u8 : ffi::CTID_UINT8,
u16: ffi::CTID_UINT16,
u32: ffi::CTID_UINT32,
u64: ffi::CTID_UINT64,
f32: ffi::CTID_FLOAT,
f64: ffi::CTID_DOUBLE,
bool: ffi::CTID_BOOL,
*mut c_void: ffi::CTID_P_VOID,
*const c_void: ffi::CTID_P_CVOID,
*const c_char: ffi::CTID_P_CCHAR,
isize:
match std::mem::size_of::<isize>() {
4 => ffi::CTID_INT32,
8 => ffi::CTID_INT64,
_ => unimplemented!("only 32 & 64 bit pointers are supported"),
},
usize:
match std::mem::size_of::<usize>() {
4 => ffi::CTID_UINT32,
8 => ffi::CTID_UINT64,
_ => unimplemented!("only 32 & 64 bit pointers are supported"),
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct CData<T>(pub T)
where
T: AsCData;
impl<L, T> PushInto<L> for CData<T>
where
L: AsLua,
T: AsCData,
{
type Err = crate::Void;
fn push_into_lua(self, lua: L) -> Result<crate::PushGuard<L>, (Self::Err, L)> {
let Self(value) = self;
unsafe {
let ptr = ffi::luaL_pushcdata(lua.as_lua(), T::ctypeid());
std::ptr::write(ptr.cast::<T>(), value);
Ok(crate::PushGuard::new(lua, 1))
}
}
}
impl<L, T> PushOneInto<L> for CData<T>
where
L: AsLua,
T: AsCData,
T: Copy,
{
}
impl<L, T> LuaRead<L> for CData<T>
where
L: AsLua,
T: AsCData,
{
fn lua_read_at_position(lua: L, index: NonZeroI32) -> Result<Self, L> {
CDataOnStack::lua_read_at_position(lua, index).and_then(|data| {
match data.try_downcast_into() {
Ok(value) => Ok(CData(value)),
Err(data) => Err(data.inner.into_guard()),
}
})
}
}