gmodx/lua/
userdata.rs

1use std::any::{Any, TypeId, type_name};
2use std::cell::{Ref, RefCell, RefMut};
3use std::ffi::{CStr, CString, c_void};
4use std::rc::Rc;
5
6use rustc_hash::{FxBuildHasher, FxHashMap};
7
8use crate::lua::function::IntoLuaFunction;
9use crate::lua::traits::ObjectLike;
10use crate::lua::types::Callback;
11use crate::lua::{self, Result, ffi::lua_State};
12use crate::lua::{Error, FromLua, FromLuaMulti, Function, Table, ToLua, ToLuaMulti, Value, ffi};
13
14thread_local! {
15    static OBJECTS: RefCell<FxHashMap<*mut c_void, Rc<dyn Any>>> =
16        const { RefCell::new(FxHashMap::with_hasher(FxBuildHasher)) };
17}
18
19pub trait UserData: 'static {
20    fn meta_methods(_: &mut MethodsBuilder) {}
21    fn methods(_: &mut MethodsBuilder) {}
22
23    fn name() -> &'static str {
24        type_name::<Self>()
25            .rsplit("::")
26            .next()
27            .unwrap_or(type_name::<Self>())
28    }
29
30    fn unique_id() -> Rc<CStr> {
31        thread_local! {
32            static IDS: RefCell<FxHashMap<TypeId, Rc<CStr>>> =
33                const { RefCell::new(FxHashMap::with_hasher(FxBuildHasher)) };
34        }
35        IDS.with_borrow_mut(|ids| {
36            ids.entry(std::any::TypeId::of::<Self>())
37                .or_insert_with(|| {
38                    let cstring = CString::new(format!(
39                        "{}_{:?}",
40                        gmodx_macros::unique_id!(),
41                        std::any::TypeId::of::<Self>()
42                    ))
43                    .unwrap();
44                    Rc::from(cstring)
45                })
46                .clone()
47        })
48    }
49
50    /// By default we lazily initialize the methods table.
51    /// Use this function to initialize the methods table before it is used.
52    fn init_methods_table(state: &lua::State) -> Table
53    where
54        Self: Sized,
55    {
56        push_methods_table::<Self>(state);
57        Table(Value::pop_from_stack(state))
58    }
59}
60
61fn push_methods_table<T: UserData>(state: &lua::State) {
62    if ffi::luaL_newmetatable(state.0, T::unique_id().as_ptr()) {
63        let methods = {
64            let mut mb = MethodsBuilder::new();
65            T::methods(&mut mb);
66            mb.build()
67        };
68        for (name, func) in methods.into_iter() {
69            state.create_function_impl(func).push_to_stack(state);
70            ffi::lua_setfield(state.0, -2, name.as_ptr());
71        }
72    }
73}
74
75impl lua::State {
76    pub fn create_userdata<T: UserData>(&self, ud: T) -> AnyUserData {
77        // Userdata: 1
78        let ud_ptr = ffi::lua_newuserdata(self.0, 0);
79
80        OBJECTS.with_borrow_mut(|objects| {
81            let boxed: Rc<dyn Any> = Rc::new(RefCell::new(ud));
82            objects.insert(ud_ptr, boxed);
83        });
84
85        // UserData metatable: 2
86        let meta_methods = {
87            let mut mb = MethodsBuilder::new();
88            T::meta_methods(&mut mb);
89            mb.build()
90        };
91        ffi::lua_createtable(self.0, 0, meta_methods.len() as i32);
92        {
93            for (name, func) in meta_methods.into_iter() {
94                self.create_function_impl(func).push_to_stack(self);
95                ffi::lua_setfield(self.0, -2, name.as_ptr());
96            }
97
98            extern "C-unwind" fn __gc(state: *mut lua_State) -> i32 {
99                let l = lua::State(state);
100                let ud_ptr = ffi::lua_touserdata(l.0, -1);
101                OBJECTS.with_borrow_mut(|objects| {
102                    objects.remove(&ud_ptr);
103                });
104                0
105            }
106            ffi::lua_pushcclosure(self.0, Some(__gc), 0);
107            ffi::lua_setfield(self.0, -2, c"__gc".as_ptr());
108        }
109
110        // Store table: 3
111        ffi::lua_createtable(self.0, 0, 0);
112
113        // Store's metatable: 4
114        ffi::lua_createtable(self.0, 0, 1);
115
116        // Methods table: 5
117        push_methods_table::<T>(self);
118
119        // Set methods table as __index of store's metatable
120        ffi::lua_setfield(self.0, -2, c"__index".as_ptr()); // pops methods table
121
122        // Set store's metatable
123        ffi::lua_setmetatable(self.0, -2); // pops store's metatable
124
125        // Push store to have it as __index
126        ffi::lua_pushvalue(self.0, -1);
127        ffi::lua_setfield(self.0, -3, c"__index".as_ptr()); // sets on ud_meta
128
129        // Set store as __newindex
130        ffi::lua_setfield(self.0, -2, c"__newindex".as_ptr()); // pops store
131
132        // Set userdata's metatable
133        ffi::lua_setmetatable(self.0, -2);
134
135        AnyUserData(Value::pop_from_stack(self))
136    }
137}
138
139#[derive(Debug)]
140pub struct UserDataRef<T: UserData>(Rc<RefCell<T>>);
141
142impl<T: UserData> Clone for UserDataRef<T> {
143    fn clone(&self) -> Self {
144        UserDataRef(self.0.clone())
145    }
146}
147
148impl<T: UserData> UserDataRef<T> {
149    #[inline]
150    pub fn borrow(&self) -> Ref<'_, T> {
151        self.0.borrow()
152    }
153
154    #[inline]
155    pub fn borrow_mut(&self) -> RefMut<'_, T> {
156        self.0.borrow_mut()
157    }
158
159    #[inline]
160    pub fn try_borrow(&self) -> Result<Ref<'_, T>> {
161        self.0
162            .try_borrow()
163            .map_err(|err| Error::Message(format!("cannot borrow '{}': {}", T::name(), err)))
164    }
165
166    #[inline]
167    pub fn try_borrow_mut(&self) -> Result<RefMut<'_, T>> {
168        self.0.try_borrow_mut().map_err(|err| {
169            Error::Message(format!("cannot borrow '{}' mutably: {}", T::name(), err))
170        })
171    }
172}
173
174#[derive(Clone, Debug)]
175pub struct AnyUserData(pub(crate) Value);
176
177// We have to force them to pass lua::State here to ensure they are on the main thread
178// without having to check it each time nor have panics at runtime
179
180impl AnyUserData {
181    #[inline]
182    fn ptr(&self) -> *mut c_void {
183        ffi::lua_touserdata(self.0.thread().0, self.0.index())
184    }
185
186    #[inline]
187    pub fn is<T: UserData>(&self, _: &lua::State) -> bool {
188        OBJECTS.with_borrow(|objects| {
189            objects
190                .get(&self.ptr())
191                .is_some_and(|obj| obj.is::<RefCell<T>>())
192        })
193    }
194
195    pub fn downcast<T: UserData>(&self, _: &lua::State) -> Option<UserDataRef<T>> {
196        OBJECTS
197            .with_borrow(|objects| {
198                objects
199                    .get(&self.ptr())
200                    .and_then(|rc| rc.clone().downcast::<RefCell<T>>().ok())
201            })
202            .map(|rc| UserDataRef(rc))
203    }
204
205    #[inline]
206    fn from_stack_with_type(state: &lua::State, index: i32, type_name: &str) -> Result<Self> {
207        if ffi::lua_type(state.0, index) == ffi::LUA_TUSERDATA {
208            Ok(AnyUserData(Value::from_stack(state, index)))
209        } else {
210            Err(state.type_error(index, type_name))
211        }
212    }
213}
214
215impl<T: UserData> ToLua for T {
216    fn push_to_stack(self, state: &lua::State) {
217        state.create_userdata(self).push_to_stack(state);
218    }
219}
220
221impl ToLua for AnyUserData {
222    fn push_to_stack(self, state: &lua::State) {
223        self.0.push_to_stack(state);
224    }
225
226    fn to_value(self, _: &lua::State) -> Value {
227        self.0
228    }
229}
230
231impl ToLua for &AnyUserData {
232    fn push_to_stack(self, state: &lua::State) {
233        #[allow(clippy::needless_borrow)]
234        (&self.0).push_to_stack(state);
235    }
236
237    fn to_value(self, _: &lua::State) -> Value {
238        self.0.clone()
239    }
240}
241
242impl FromLua for AnyUserData {
243    #[inline]
244    fn try_from_stack(state: &lua::State, index: i32) -> Result<AnyUserData> {
245        AnyUserData::from_stack_with_type(state, index, "userdata")
246    }
247}
248
249impl<T: UserData> FromLua for UserDataRef<T> {
250    fn try_from_stack(state: &lua::State, index: i32) -> Result<Self> {
251        let any_ud = AnyUserData::from_stack_with_type(state, index, T::name())?;
252        any_ud
253            .downcast::<T>(state)
254            .ok_or_else(|| state.type_error(index, T::name()))
255    }
256}
257
258impl ObjectLike for AnyUserData {
259    fn get<V: FromLua>(&self, state: &lua::State, key: impl ToLua) -> Result<V> {
260        Table(self.0.clone()).get_protected(state, key)
261    }
262
263    fn set(&self, state: &lua::State, key: impl ToLua, value: impl ToLua) -> Result<()> {
264        Table(self.0.clone()).set_protected(state, key, value)
265    }
266
267    #[inline]
268    fn call<R: FromLuaMulti>(
269        &self,
270        state: &lua::State,
271        name: &str,
272        args: impl ToLuaMulti,
273    ) -> Result<R> {
274        let func: Function = self.get(state, name)?;
275        func.call(state, args)
276    }
277
278    #[inline]
279    fn call_method<R: FromLuaMulti>(
280        &self,
281        state: &lua::State,
282        name: &str,
283        args: impl ToLuaMulti,
284    ) -> Result<R> {
285        self.call(state, name, (self, args))
286    }
287}
288
289type Methods = Vec<(&'static CStr, Callback)>;
290
291#[derive(Default)]
292pub struct MethodsBuilder(Methods);
293
294impl MethodsBuilder {
295    fn new() -> Self {
296        Self(Vec::new())
297    }
298
299    pub fn add<F, Marker>(&mut self, name: &'static CStr, func: F)
300    where
301        F: IntoLuaFunction<Marker>,
302    {
303        let callback = func.into_callback();
304        self.0.push((name, callback));
305    }
306
307    fn build(self) -> Methods {
308        self.0
309    }
310}
311
312impl IntoIterator for MethodsBuilder {
313    type Item = (&'static CStr, Callback);
314    type IntoIter = std::vec::IntoIter<(&'static CStr, Callback)>;
315
316    fn into_iter(self) -> Self::IntoIter {
317        self.0.into_iter()
318    }
319}
320
321pub struct TypedUserData<T: UserData>(pub AnyUserData, std::marker::PhantomData<T>);
322
323impl<T: UserData> FromLua for TypedUserData<T> {
324    fn try_from_stack(state: &lua::State, index: i32) -> Result<Self> {
325        let any = AnyUserData::from_stack_with_type(state, index, T::name())?;
326        // Type check it
327        any.downcast::<T>(state)
328            .ok_or_else(|| state.type_error(index, T::name()))?;
329        Ok(TypedUserData(any, std::marker::PhantomData))
330    }
331}
332
333impl<T: UserData> std::ops::Deref for TypedUserData<T> {
334    type Target = AnyUserData;
335
336    fn deref(&self) -> &Self::Target {
337        &self.0
338    }
339}
340
341impl<T: UserData> std::ops::DerefMut for TypedUserData<T> {
342    fn deref_mut(&mut self) -> &mut Self::Target {
343        &mut self.0
344    }
345}