tlua/cdata.rs
1use crate::ffi;
2use crate::lua_functions::LuaFunction;
3use crate::object::{FromObject, Object};
4use crate::{AsLua, LuaRead, LuaState, Push, PushInto, PushOneInto, ReadResult, WrongType};
5use std::cell::UnsafeCell;
6use std::convert::TryFrom;
7use std::num::NonZeroI32;
8use std::os::raw::{c_char, c_void};
9
10////////////////////////////////////////////////////////////////////////////////
11// CDataOnStack
12////////////////////////////////////////////////////////////////////////////////
13
14/// Represents a reference to the underlying cdata value corresponding to a
15/// given cdata object.
16#[derive(Debug, Clone, Copy)]
17enum CDataRef<'l> {
18 Ptr(*mut c_void),
19 Slice(&'l [u8]),
20}
21
22/// A cdata value stored on lua stack. Can be used to check type of cdata,
23/// access the raw bytes of the data, downcast it to a rust type or passed as
24/// an argument into a lua function.
25/// # Examples:
26/// ```no_run
27/// use tlua::{CDataOnStack, Lua, ffi};
28/// let lua = Lua::new();
29/// let cdata: CDataOnStack<_> = lua.eval("
30/// ffi = require 'ffi';
31/// return ffi.new('uint8_t', 69)
32/// ").unwrap();
33///
34/// // check CTypeID
35/// assert_eq!(cdata.ctypeid(), ffi::CTID_UINT8);
36///
37/// // check raw bytes
38/// assert_eq!(cdata.data(), [69]);
39///
40/// // pass to a lua function
41/// let n: u8 = lua.eval_with("return ... + 1", &cdata).unwrap();
42/// assert_eq!(n, 70);
43/// ```
44#[derive(Debug)]
45pub struct CDataOnStack<'l, L> {
46 inner: Object<L>,
47 data: UnsafeCell<CDataRef<'l>>,
48 ctypeid: ffi::CTypeID,
49}
50
51impl<L> CDataOnStack<'_, L>
52where
53 L: AsLua,
54{
55 /// Return pointer to data. Maybe use [`CDataOnStack::data`] instead.
56 #[inline(always)]
57 pub fn as_ptr(&self) -> *const c_void {
58 match unsafe { *self.data.get() } {
59 CDataRef::Ptr(ptr) => ptr,
60 CDataRef::Slice(slice) => slice.as_ptr().cast(),
61 }
62 }
63
64 /// Updates the CDataRef inplace replacing the pointer with a slice of known
65 /// size. This function is only executed once because it performs an
66 /// expensnive call to luajit runtime to figure out the size of the data.
67 fn update_data(&self, ptr: *const c_void) -> &[u8] {
68 let f = LuaFunction::load(self, "return require('ffi').sizeof(...)").unwrap();
69 let size: usize = f.into_call_with_args(self).unwrap();
70 unsafe {
71 let slice = std::slice::from_raw_parts(ptr.cast(), size);
72 std::ptr::write(self.data.get(), CDataRef::Slice(slice));
73 slice
74 }
75 }
76
77 /// Return a slice of bytes covering the data if the data's size was already
78 /// retrieved before. Otherwise return `None`.
79 ///
80 /// See also [`CDataOnStack::data`].
81 pub fn try_as_bytes(&self) -> Option<&[u8]> {
82 match unsafe { *self.data.get() } {
83 CDataRef::Slice(slice) => Some(slice),
84 CDataRef::Ptr(_) => None,
85 }
86 }
87
88 /// Return a mutable slice of bytes covering the data if the data's size was
89 /// already retrieved before. Otherwise return `None`.
90 ///
91 /// See also [`CDataOnStack::data_mut`].
92 pub fn try_as_bytes_mut(&mut self) -> Option<&mut [u8]> {
93 match unsafe { *self.data.get() } {
94 CDataRef::Slice(slice) => unsafe {
95 Some(std::slice::from_raw_parts_mut(
96 slice.as_ptr() as *mut _,
97 slice.len(),
98 ))
99 },
100 CDataRef::Ptr(_) => None,
101 }
102 }
103
104 /// Return a slice of bytes covering the data. Calling this function the
105 /// first time around will perform an expensive operation of retrieving the
106 /// data's size. So if for some reason you just need the pointer to the
107 /// data, use the [`CDataOnStack::as_ptr`]. But if you actually need the
108 /// bytes, use this function.
109 #[inline(always)]
110 pub fn data(&self) -> &[u8] {
111 match unsafe { *self.data.get() } {
112 CDataRef::Ptr(ptr) => self.update_data(ptr),
113 CDataRef::Slice(slice) => slice,
114 }
115 }
116
117 /// Return a mutable slice of bytes covering the data. Calling this function the
118 /// first time around will perform an expensive operation of retrieving the
119 /// data's size.
120 #[inline(always)]
121 pub fn data_mut(&mut self) -> &mut [u8] {
122 let data = self.data();
123 unsafe { std::slice::from_raw_parts_mut(data.as_ptr() as *mut _, data.len()) }
124 }
125
126 /// Return the ctypeid of the cdata.
127 #[inline(always)]
128 pub fn ctypeid(&self) -> ffi::CTypeID {
129 self.ctypeid
130 }
131
132 /// Return a reference to the underlying value if
133 /// `self.`[`ctypeid`]`() ==
134 /// <T as `[`AsCData`]`>::`[`ctypeid`](AsCData::ctypeid)`()`,
135 /// otherwise return `None`.
136 ///
137 /// **This function may panic, if
138 /// `self.`[`data`]`().len() != std::mem::size_of::<T>()`**
139 ///
140 /// [`data`]: CDataOnStack::data
141 /// [`ctypeid`]: CDataOnStack::ctypeid
142 #[inline(always)]
143 pub fn try_downcast<T>(&self) -> Option<&T>
144 where
145 T: AsCData,
146 {
147 self.check_ctypeid::<T>()
148 .then(|| unsafe { &*self.as_ptr().cast::<T>() })
149 }
150
151 /// Return a mutable reference to the underlying value if
152 /// `self.`[`ctypeid`]`() ==
153 /// <T as `[`AsCData`]`>::`[`ctypeid`](AsCData::ctypeid)`()`,
154 /// otherwise return `None`.
155 ///
156 /// **This function may panic, if
157 /// `self.`[`data`]`().len() != std::mem::size_of::<T>()`**
158 ///
159 /// [`data`]: CDataOnStack::data
160 /// [`ctypeid`]: CDataOnStack::ctypeid
161 #[inline(always)]
162 pub fn try_downcast_mut<T>(&self) -> Option<&mut T>
163 where
164 T: AsCData,
165 {
166 self.check_ctypeid::<T>()
167 .then(|| unsafe { &mut *(self.as_ptr().cast::<T>() as *mut _) })
168 }
169
170 /// Return the underlying value consuming `self` if
171 /// `self.`[`ctypeid`]`() ==
172 /// <T as `[`AsCData`]`>::`[`ctypeid`](AsCData::ctypeid)`()`,
173 /// otherwise return `Err(self)`.
174 ///
175 /// **This function may panic, if
176 /// `self.`[`data`]`().len() != std::mem::size_of::<T>()`**
177 ///
178 /// [`data`]: CDataOnStack::data
179 /// [`ctypeid`]: CDataOnStack::ctypeid
180 #[inline(always)]
181 pub fn try_downcast_into<T>(self) -> Result<T, Self>
182 where
183 T: AsCData,
184 {
185 self.check_ctypeid::<T>()
186 .then(|| unsafe { std::ptr::read(self.as_ptr().cast::<T>()) })
187 .ok_or(self)
188 }
189
190 #[inline(always)]
191 #[allow(clippy::nonminimal_bool)]
192 fn check_ctypeid<T: AsCData>(&self) -> bool {
193 self.ctypeid == T::ctypeid() && {
194 if cfg!(debug_assertions) {
195 assert_eq!(self.data().len(), std::mem::size_of::<T>());
196 }
197 true
198 }
199 }
200}
201
202impl<L> FromObject<L> for CDataOnStack<'_, L>
203where
204 L: AsLua,
205{
206 unsafe fn check(lua: impl AsLua, index: NonZeroI32) -> bool {
207 ffi::lua_type(lua.as_lua(), index.into()) == ffi::LUA_TCDATA
208 }
209
210 unsafe fn from_obj(inner: Object<L>) -> Self {
211 let mut ctypeid = 0;
212 let cdata = ffi::luaL_checkcdata(inner.as_lua(), inner.index().into(), &mut ctypeid);
213 Self {
214 inner,
215 data: UnsafeCell::new(CDataRef::Ptr(cdata)),
216 ctypeid,
217 }
218 }
219}
220
221impl<L> TryFrom<Object<L>> for CDataOnStack<'_, L>
222where
223 L: AsLua,
224{
225 type Error = Object<L>;
226
227 #[inline(always)]
228 fn try_from(o: Object<L>) -> Result<Self, Self::Error> {
229 Self::try_from_obj(o)
230 }
231}
232
233impl<'a, L> From<CDataOnStack<'a, L>> for Object<L> {
234 fn from(ud: CDataOnStack<'a, L>) -> Self {
235 ud.inner
236 }
237}
238
239impl<L> LuaRead<L> for CDataOnStack<'_, L>
240where
241 L: AsLua,
242{
243 #[inline]
244 fn lua_read_at_position(lua: L, index: NonZeroI32) -> ReadResult<Self, L> {
245 Self::try_from_obj(Object::new(lua, index)).map_err(|l| {
246 let g = Object::into_guard(l);
247 let e = WrongType::info("reading cdata")
248 .expected_type::<Self>()
249 .actual_single_lua(&g, index);
250 (g, e)
251 })
252 }
253}
254
255impl<L, O> Push<L> for CDataOnStack<'_, O>
256where
257 L: AsLua,
258 O: AsLua,
259{
260 type Err = crate::Void;
261 #[inline]
262 fn push_to_lua(&self, lua: L) -> Result<crate::PushGuard<L>, (Self::Err, L)> {
263 unsafe {
264 crate::ffi::lua_pushvalue(lua.as_lua(), self.inner.index().into());
265 Ok(crate::PushGuard::new(lua, 1))
266 }
267 }
268}
269
270impl<L> AsLua for CDataOnStack<'_, L>
271where
272 L: AsLua,
273{
274 #[inline]
275 fn as_lua(&self) -> LuaState {
276 self.inner.as_lua()
277 }
278}
279
280////////////////////////////////////////////////////////////////////////////////
281// AsCData
282////////////////////////////////////////////////////////////////////////////////
283
284/// Types implementing this trait can be represented as luajit's cdata.
285///
286/// # Safety
287/// The implementor must make sure that the type can actually be used as cdata,
288/// for example the CTypeID must be correct and the size of the type must be the
289/// same as `ffi.sizeof(<typeid>)`
290pub unsafe trait AsCData: Sized + 'static {
291 /// This function will be called every time the ctypeid is needed, so
292 /// implement your own caching if it's required.
293 fn ctypeid() -> ffi::CTypeID;
294}
295
296macro_rules! impl_builtin_as_cdata {
297 ($($t:ty: $ctid:expr),* $(,)?) => {
298 $(
299 unsafe impl AsCData for $t {
300 fn ctypeid() -> ffi::CTypeID {
301 $ctid
302 }
303 }
304 )*
305 };
306}
307
308impl_builtin_as_cdata! {
309 i8 : ffi::CTID_INT8,
310 i16: ffi::CTID_INT16,
311 i32: ffi::CTID_INT32,
312 i64: ffi::CTID_INT64,
313 u8 : ffi::CTID_UINT8,
314 u16: ffi::CTID_UINT16,
315 u32: ffi::CTID_UINT32,
316 u64: ffi::CTID_UINT64,
317 f32: ffi::CTID_FLOAT,
318 f64: ffi::CTID_DOUBLE,
319
320 bool: ffi::CTID_BOOL,
321
322 *mut c_void: ffi::CTID_P_VOID,
323 *const c_void: ffi::CTID_P_CVOID,
324 *const c_char: ffi::CTID_P_CCHAR,
325
326 isize:
327 match std::mem::size_of::<isize>() {
328 4 => ffi::CTID_INT32,
329 8 => ffi::CTID_INT64,
330 _ => unimplemented!("only 32 & 64 bit pointers are supported"),
331 },
332 usize:
333 match std::mem::size_of::<usize>() {
334 4 => ffi::CTID_UINT32,
335 8 => ffi::CTID_UINT64,
336 _ => unimplemented!("only 32 & 64 bit pointers are supported"),
337 },
338}
339
340////////////////////////////////////////////////////////////////////////////////
341// CData
342////////////////////////////////////////////////////////////////////////////////
343
344/// A wrapper type for reading/writing rust values as luajit cdata.
345/// ```no_run
346/// use tlua::{Lua, CData};
347/// let lua = Lua::new();
348/// lua.set("cdata", CData(1337_i16));
349/// let ty: String = lua.eval("return require('ffi').typeof(cdata)").unwrap();
350/// assert_eq!(ty, "ctype<short>");
351///
352/// let CData(num): CData<i16> = lua.get("cdata").unwrap();
353/// assert_eq!(num, 1337);
354/// ```
355///
356/// For this to work the type must implement [`AsCData`] which is true for
357/// builtin numbers and some pointers but can also be implemented for user
358/// defined types:
359/// ```no_run
360/// use tlua::{AsCData, CData};
361/// use tlua::{Lua, AsLua, ffi, c_ptr};
362/// # let lua = Lua::new();
363///
364/// #[repr(C)]
365/// #[derive(Debug, PartialEq, Clone, Copy)]
366/// struct S { i: i32, f: f32 }
367///
368/// // let luajit know about our struct
369/// lua.exec("ffi.cdef[[ struct S { int i; float f; }; ]]").unwrap();
370///
371/// // save the CTypeID of our struct
372/// static mut CTID_STRUCT_S: Option<ffi::CTypeID> = None;
373/// let ctid = unsafe { ffi::luaL_ctypeid(lua.as_lua(), c_ptr!("struct S")) };
374/// unsafe { CTID_STRUCT_S = Some(ctid) }
375///
376/// // implement AsCData for our struct so that it can be wrapped with CData
377/// unsafe impl AsCData for S {
378/// fn ctypeid() -> ffi::CTypeID {
379/// unsafe { CTID_STRUCT_S.unwrap() }
380/// }
381/// }
382///
383/// // wirte our struct into a lua variable as cdata
384/// lua.set("tmp", CData(S { i: 69, f: 420.0 }));
385///
386/// // check it's type
387/// let ty: String = lua.eval("return type(tmp)").unwrap();
388/// assert_eq!(ty, "cdata");
389///
390/// // read the value back
391/// let CData(res): CData<S> = lua.get("tmp").unwrap();
392/// assert_eq!(res, S { i: 69, f: 420.0 });
393/// ```
394#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
395pub struct CData<T>(pub T)
396where
397 T: AsCData;
398
399impl<L, T> PushInto<L> for CData<T>
400where
401 L: AsLua,
402 T: AsCData,
403{
404 type Err = crate::Void;
405 fn push_into_lua(self, lua: L) -> Result<crate::PushGuard<L>, (Self::Err, L)> {
406 let Self(value) = self;
407 unsafe {
408 let ptr = ffi::luaL_pushcdata(lua.as_lua(), T::ctypeid());
409 std::ptr::write(ptr.cast::<T>(), value);
410 Ok(crate::PushGuard::new(lua, 1))
411 }
412 }
413}
414impl<L, T> PushOneInto<L> for CData<T>
415where
416 L: AsLua,
417 T: AsCData,
418 T: Copy,
419{
420}
421
422impl<L, T> LuaRead<L> for CData<T>
423where
424 L: AsLua,
425 T: AsCData,
426{
427 fn lua_read_at_position(lua: L, index: NonZeroI32) -> ReadResult<Self, L> {
428 CDataOnStack::lua_read_at_position(lua, index).and_then(|data| {
429 match data.try_downcast_into() {
430 Ok(value) => Ok(CData(value)),
431 Err(data) => {
432 let g = data.inner.into_guard();
433 let e = WrongType::info("reading cdata")
434 .expected_type::<T>()
435 .actual_single_lua(&g, index);
436 Err((g, e))
437 }
438 }
439 })
440 }
441}