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#[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}