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