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