Skip to main content

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}