low_luajit/lua/type/userdata.rs
1use core::{ffi::CStr, mem::MaybeUninit, ptr};
2use crate::{AmountReturned, LuaState, LuaType, ffi, map_ptr};
3
4#[cfg(doc)]
5pub use crate::CFunction;
6
7/// Userdata (full & light) related functions.
8impl LuaState {
9 /// Returns `true` if the value at the given acceptable `index` is userdata (either full or light), `false` otherwise.
10 #[inline]
11 #[must_use]
12 pub fn is_userdata(&self, index: i32) -> bool {
13 // SAFETY: self.as_ptr() is always valid.
14 unsafe { ffi::lua_isuserdata(self.as_ptr(), index) != 0 }
15 }
16
17 /// Returns `true` if the value at the given acceptable `index` is full userdata, `false` otherwise.
18 #[inline]
19 #[must_use]
20 pub fn is_full_userdata(&self, index: i32) -> bool {
21 self.type_of(index) == LuaType::Userdata
22 }
23
24 /// Returns `true` if the value at the given acceptable `index` is light userdata, `false` otherwise.
25 #[inline]
26 #[must_use]
27 pub fn is_light_userdata(&self, index: i32) -> bool {
28 // SAFETY: self.as_ptr() is always valid.
29 unsafe { ffi::lua_islightuserdata(self.as_ptr(), index) != 0 }
30 }
31
32 /// Converts the value at the given acceptable `index` to full userdata and returns a mutable reference reference to its block.
33 ///
34 /// # Safety
35 ///
36 /// - The type of the element in the stack must be full userdata.
37 /// - `T` must be the same type used when creating this userdata.
38 /// - The userdata's value must be valid for `T`.
39 /// - There must not be another living reference to this userdata.
40 #[inline]
41 #[must_use]
42 #[allow(clippy::mut_from_ref)]
43 pub unsafe fn to_full_userdata<T>(&self, index: i32) -> &mut T {
44 // SAFETY: self.as_ptr() is always valid and the caller must uphold the safety conditions.
45 unsafe { &mut *ffi::lua_touserdata(self.as_ptr(), index).cast() }
46 }
47
48 /// Converts the value at the given acceptable `index` to light userdata and returns it.
49 ///
50 /// Only light and full userdata are accepted, anything else will return a null pointer.
51 #[inline]
52 #[must_use]
53 pub fn to_light_userdata<T>(&self, index: i32) -> *mut T {
54 // SAFETY: self.as_ptr() is always valid.
55 unsafe { ffi::lua_touserdata(self.as_ptr(), index).cast() }
56 }
57
58 /// Allocates a new block the size of `T` containing `value`, pushes onto the stack a new full userdata with the block's address,
59 /// and then returns a mutable reference to said block.
60 ///
61 /// Like any other Lua value, this userdata is inevitably collected by the garbage collector to be freed;
62 /// but by default this is done by simply deallocating the block instead of properly dropping the value!
63 ///
64 /// *(This won't result in undefined behavior,
65 /// but could cause a memory leak for types that allocate dynamically or access some sort of external resource;
66 /// more primitive types should not need to be properly dropped.)*
67 ///
68 /// This can be changed by having the `__gc` metamethod (which is triggered before the block is freed) drop the userdata.
69 /// For a simple way to do this, see [`LuaState::set_drop_gc_metamethod`].
70 #[inline]
71 pub fn new_full_userdata<T>(&mut self, value: T) -> &mut T {
72 self.new_full_userdata_uninit().write(value)
73 }
74
75 /// Behaves exactly as [`LuaState::new_full_userdata`], but uses `T`'s default value as the block's value.
76 #[inline]
77 pub fn new_full_userdata_default<T: Default>(&mut self) -> &mut T {
78 self.new_full_userdata(T::default())
79 }
80
81 /// Behaves exactly as [`LuaState::new_full_userdata`], but without actually initializing the block.
82 #[inline]
83 pub fn new_full_userdata_uninit<T>(&mut self) -> &mut MaybeUninit<T> {
84 // SAFETY: self.as_ptr() is always valid lua_newuserdata should always return an aligned pointer that isn't accessed anywhere else.
85 unsafe { &mut *ffi::lua_newuserdata(self.as_ptr(), size_of::<T>()).cast() }
86 }
87
88 /// Pushes the given `pointer` onto the stack as *light* userdata.
89 ///
90 /// Unlike normal *full* userdata, light userdata is the raw pointer itself.
91 #[inline]
92 pub fn push_light_userdata<T>(&mut self, pointer: *mut T) {
93 // SAFETY: self.as_ptr() is always valid and pointer is simply stored, so it doesn't have to be valid.
94 unsafe { ffi::lua_pushlightuserdata(self.as_ptr(), pointer.cast()); }
95 }
96
97 /// Checks if there's a *full* userdata of type `userdata_type` at the given index `arg_n` and returns it as full userdata.
98 ///
99 /// Userdata is of type `userdata_type` when it uses the metatable stored in the registry with key `userdata_type`
100 /// (see [`LuaState::new_userdata_metatable`]).
101 ///
102 /// If the check fails, [`LuaState::type_error`] is called using `arg_n`.
103 ///
104 /// # Safety
105 ///
106 /// - `T` must be the same type used when creating this userdata.
107 /// - The userdata's value must be valid for `T`.
108 /// - There must not be another living reference to this userdata.
109 ///
110 /// # Lua Errors
111 ///
112 /// Intentionally errors if the check fails.
113 #[inline]
114 #[must_use]
115 #[allow(clippy::mut_from_ref)]
116 pub unsafe fn check_full_userdata<T>(&self, arg_n: i32, userdata_type: &CStr) -> &mut T {
117 // SAFETY: self.as_ptr() and userdata_type are always valid and the caller must uphold the safety conditions.
118 unsafe { &mut *ffi::luaL_checkudata(self.as_ptr(), arg_n, userdata_type.as_ptr()).cast() }
119 }
120
121 /// Checks if there's an userdata (either full or light) of type `userdata_type` at the given index `arg_n` and returns it as light userdata.
122 ///
123 /// Userdata is of type `userdata_type` when it uses the metatable stored in the registry with key `userdata_type`
124 /// (see [`LuaState::new_userdata_metatable`]).
125 ///
126 /// If the check fails, [`LuaState::type_error`] is called using `arg_n`.
127 ///
128 /// # Lua Errors
129 ///
130 /// Intentionally errors if the check fails.
131 ///
132 /// Additionally, light userdata that happens to be a null pointer will cause the check to erroneously fail.
133 #[inline]
134 #[must_use]
135 pub fn check_light_userdata<T>(&self, arg_n: i32, userdata_type: &CStr) -> *mut T {
136 let ptr = self.test_light_userdata::<T>(arg_n, userdata_type);
137
138 if ptr.is_null() {
139 self.type_error(arg_n, userdata_type);
140 }
141
142 ptr
143 }
144
145 /// Behaves exactly as [`LuaState::check_full_userdata`], but returns `None` instead of erroring if the check fails.
146 ///
147 /// # Safety
148 ///
149 /// - `T` must be the same type used when creating this userdata.
150 /// - The userdata's value must be valid for `T`.
151 /// - There must not be another living reference to this userdata.
152 #[inline]
153 #[must_use]
154 #[allow(clippy::mut_from_ref)]
155 pub unsafe fn test_full_userdata<T>(&self, arg_n: i32, userdata_type: &CStr) -> Option<&mut T> {
156 // SAFETY: self.as_ptr() and userdata_type are always valid and the caller must uphold the safety conditions.
157 let udata = unsafe { ffi::luaL_testudata(self.as_ptr(), arg_n, userdata_type.as_ptr()).cast::<T>() };
158 map_ptr!(udata, unsafe { &mut *udata })
159 }
160
161 /// Behaves exactly as [`LuaState::check_full_userdata`], but returns a null pointer instead of erroring if the check fails.
162 #[inline]
163 #[must_use]
164 pub fn test_light_userdata<T>(&self, arg_n: i32, userdata_type: &CStr) -> *mut T {
165 // SAFETY: No elements are popped except the two element that are pushed by this function.
166 unsafe { self.access_mut(|lua| {
167 if self.is_userdata(arg_n) && lua.get_metatable(arg_n) {
168 lua.get_userdata_metatable(userdata_type);
169 let equals = lua.raw_equal(-1, -2);
170 lua.pop_unchecked(2);
171
172 if equals {
173 return lua.to_light_userdata(arg_n)
174 }
175 }
176
177 ptr::null_mut()
178 }) }
179 }
180
181 /// Finishes the currently called [`CFunction`], returning the given `value` as full userdata to the caller.
182 ///
183 /// The `LuaState` wrapper is consumed to prevent invalidating the `AmountReturned` (the Lua state itself is left intact).
184 #[inline]
185 pub fn return_full_userdata<T>(mut self, value: T) -> AmountReturned {
186 self.new_full_userdata(value);
187
188 // SAFETY: There should always be at least one element in the stack.
189 unsafe { AmountReturned::unchecked(1) }
190 }
191
192 /// Finishes the currently called [`CFunction`], returning the given `pointer` as light userdata to the caller.
193 ///
194 /// The `LuaState` wrapper is consumed to prevent invalidating the `AmountReturned` (the Lua state itself is left intact).
195 #[inline]
196 pub fn return_light_userdata<T>(mut self, pointer: *mut T) -> AmountReturned {
197 self.push_light_userdata(pointer);
198
199 // SAFETY: There should always be at least one element in the stack.
200 unsafe { AmountReturned::unchecked(1) }
201 }
202
203 /// Pushes onto the stack the value in the registry with key `userdata_type`, returning `false`.
204 ///
205 /// If no such value exists yet, a table will be set as the value (before being pushed) and `true` will be returned instead.
206 ///
207 /// This function is used to get the metatable to be set as the metatable of the desired userdata (see [`LuaState::set_metatable`]).
208 ///
209 /// # Examples
210 ///
211 /// ```
212 /// use low_luajit::{LuaState, AmountReturned};
213 ///
214 /// extern "C-unwind" fn index_metamethod(lua: LuaState) -> AmountReturned {
215 /// // ...
216 /// # AmountReturned::NONE
217 /// }
218 ///
219 /// # let mut lua = unsafe { low_luajit::__test!() };
220 /// # let ptr = core::ptr::null_mut::<()>();
221 /// lua.push_light_userdata(ptr);
222 /// if lua.new_userdata_metatable(c"my_light_userdata") {
223 /// // Initialize the metatable.
224 /// lua.push_cfunction(index_metamethod);
225 /// lua.set_field(-2, c"__index");
226 /// // ...
227 /// }
228 /// lua.set_metatable(-2);
229 /// ```
230 #[inline]
231 pub fn new_userdata_metatable(&mut self, userdata_type: &CStr) -> bool {
232 // SAFETY: self.as_ptr() and userdata_type are always valid.
233 unsafe { ffi::luaL_newmetatable(self.as_ptr(), userdata_type.as_ptr()) != 0 }
234 }
235
236 /// Pushes onto the stack the value in the registry with key `userdata_type`, even if `nil`.
237 ///
238 /// This value is usually an userdata's metatable.
239 #[inline]
240 pub fn get_userdata_metatable(&mut self, userdata_type: &CStr) {
241 // SAFETY: self.as_ptr() and userdata_type are always valid.
242 unsafe { ffi::luaL_getmetatable(self.as_ptr(), userdata_type.as_ptr()); }
243 }
244
245 /* TODO: this would require checking the registry field and then popping it only for the function to push it again. worth it?
246 /// Sets the userdata metatable or something as the metatable of the top element
247 #[inline]
248 pub fn set_userdata_metatable(&mut self, userdata_type: &CStr) {
249 //assert!(self.get_top() > 0, "tried to set the metatable for the element at index {index} despite having an empty stack");
250 //assert!(self.is_table(-1), "cannot set the metatable for the element at index {index}, as the given metatable is not a table");
251
252 // SAFETY: self.as_ptr() and userdata_type are always valid.
253 unsafe { ffi::luaL_setmetatable(self.as_ptr(), userdata_type.as_ptr()); }
254 }*/
255
256 /// Sets the `__gc` metamethod of the metatable at the top of the stack to a special destructor metamethod.
257 ///
258 /// This metamethod will drop the full userdata (that is using the metatable) before the garbage collector frees the userdata's block.
259 ///
260 /// This function is usually only necessary when the userdata's type implements `Drop`,
261 /// see [`LuaState::new_full_userdata`] for more information.
262 ///
263 /// # Safety
264 ///
265 /// - The type of the value that uses the metatable must be full userdata.
266 /// - `T` must be the same type used when creating this userdata.
267 /// - The userdata's value must be valid for `T`.
268 ///
269 /// These conditions should always be true when [`LuaState::new_full_userdata`] and [`LuaState::new_userdata_metatable`] are used properly.
270 ///
271 /// # Lua Errors
272 ///
273 /// Indexing the top element may trigger the `__newindex` metamethod which could error.
274 ///
275 /// Additionally, this function will error if for some reason the top element is *not* a table but instead something that can't be indexed.
276 ///
277 /// # Examples
278 ///
279 /// ```
280 /// # use low_luajit::LuaState;
281 /// # let mut lua = unsafe { low_luajit::__test!() };
282 /// lua.new_full_userdata(vec![36.2, 123.456, 0.998]);
283 /// if lua.new_userdata_metatable(c"my_full_userdata") {
284 /// // SAFETY: The metatable is being used by a valid full userdata of the same type.
285 /// unsafe { lua.set_drop_gc_metamethod::<Vec<f64>>() }
286 /// }
287 /// lua.set_metatable(-2);
288 /// ```
289 ///
290 /// *(For a more complete example on how to implement metatables for userdata, see [`LuaState::new_userdata_metatable`].)*
291 #[inline]
292 pub unsafe fn set_drop_gc_metamethod<T>(&mut self) {
293 extern "C-unwind" fn drop_userdata<T>(lua: LuaState) -> AmountReturned {
294 // SAFETY: The caller must uphold the safety conditions.
295 unsafe { lua.to_light_userdata::<T>(-1).drop_in_place() };
296 AmountReturned::NONE
297 }
298
299 self.push_cfunction(drop_userdata::<T>);
300 self.set_field(-2, c"__gc");
301 }
302}