gmodx/lua/
table.rs

1use crate::lua::{
2    self, FromLuaMulti, Function, Result, ToLuaMulti, Value, ffi,
3    traits::{FromLua, ObjectLike, ToLua},
4};
5
6#[derive(Clone, Debug)]
7pub struct Table(pub(crate) Value);
8
9impl Table {
10    pub fn set(&self, state: &lua::State, key: impl ToLua, value: impl ToLua) -> Result<()> {
11        if !self.has_metatable(state) {
12            // If the table has no metatable, we can use rawset directly
13            // this is because rawset cannot fail
14            self.raw_set(state, key, value);
15            return Ok(());
16        }
17
18        // Otherwise, we use the protected version because lua can longjmp if __newindex errors
19        self.set_protected(state, key, value)
20    }
21
22    pub fn get<V: FromLua>(&self, state: &lua::State, key: impl ToLua) -> Result<V> {
23        if !self.has_metatable(state) {
24            return self.raw_get(state, key);
25        }
26
27        self.get_protected(state, key)
28    }
29
30    // TODO: should make it call __len, lua 5.1 does not invoke __len, has to be implemented manually
31    pub fn len(&self, state: &lua::State) -> Result<usize> {
32        Ok(self.raw_len(state))
33    }
34
35    pub fn raw_set(&self, state: &lua::State, key: impl ToLua, value: impl ToLua) {
36        let _sg = state.stack_guard();
37
38        self.push_to_stack(state); // push the table
39        key.push_to_stack(state); // push the key
40        value.push_to_stack(state); // push the value
41        ffi::lua_rawset(state.0, -3);
42    }
43
44    pub fn raw_get<V: FromLua>(&self, state: &lua::State, key: impl ToLua) -> Result<V> {
45        let _sg = state.stack_guard();
46
47        self.push_to_stack(state); // push the table
48        key.push_to_stack(state); // push the key
49        ffi::lua_rawget(state.0, -2);
50
51        V::try_from_stack(state, -1)
52    }
53
54    // the lua state is only used to ensure we are on main thread
55    pub fn raw_len(&self, _: &lua::State) -> usize {
56        ffi::lua_rawlen(self.0.thread().0, self.0.index())
57    }
58
59    // the lua state is only used to ensure we are on main thread
60    pub fn has_metatable(&self, _: &lua::State) -> bool {
61        let thread = self.0.thread();
62        if ffi::lua_getmetatable(thread.0, self.0.index()) == 0 {
63            false
64        } else {
65            ffi::lua_pop(thread.0, 1); // pop the metatable
66            true
67        }
68    }
69
70    #[inline]
71    pub fn ipairs<V: FromLua>(&self, state: &lua::State) -> IPairsIter<V> {
72        IPairsIter {
73            table: self.clone(),
74            state: state.clone(),
75            index: 0,
76            len: self.raw_len(state),
77            _phantom: std::marker::PhantomData,
78        }
79    }
80
81    pub fn set_metatable(&self, _: &lua::State, metatable: Option<Table>) {
82        let ref_thread = self.0.thread().0;
83        if let Some(metatable) = &metatable {
84            ffi::lua_pushvalue(ref_thread, metatable.0.index());
85        } else {
86            ffi::lua_pushnil(ref_thread);
87        }
88        ffi::lua_setmetatable(ref_thread, self.0.index());
89    }
90
91    pub(crate) fn set_protected(
92        &self,
93        state: &lua::State,
94        key: impl ToLua,
95        value: impl ToLua,
96    ) -> Result<()> {
97        let _sg = state.stack_guard();
98
99        unsafe extern "C-unwind" fn safe_settable(state: *mut ffi::lua_State) -> i32 {
100            // stack: table, key, value
101            ffi::lua_settable(state, -3);
102            0
103        }
104
105        ffi::lua_pushcfunction(state.0, Some(safe_settable));
106        self.push_to_stack(state); // push the table
107        key.push_to_stack(state); // push the key
108        value.push_to_stack(state); // push the value
109        state.protect_lua_call(3, 0)?;
110
111        Ok(())
112    }
113
114    pub(crate) fn get_protected<V: FromLua>(
115        &self,
116        state: &lua::State,
117        key: impl ToLua,
118    ) -> Result<V> {
119        let _sg = state.stack_guard();
120
121        unsafe extern "C-unwind" fn safe_gettable(state: *mut ffi::lua_State) -> i32 {
122            // stack: table, key
123            ffi::lua_gettable(state, -2);
124            1
125        }
126
127        ffi::lua_pushcfunction(state.0, Some(safe_gettable));
128        self.push_to_stack(state); // push the table
129        key.push_to_stack(state); // push the key
130        state.protect_lua_call(2, 1)?;
131
132        V::try_from_stack(state, -1)
133    }
134}
135
136impl lua::State {
137    pub fn create_table(&self) -> Table {
138        self.create_table_with_capacity(0, 0)
139    }
140
141    pub fn create_table_with_capacity(&self, narr: i32, nrec: i32) -> Table {
142        lua::ffi::lua_createtable(self.0, narr, nrec);
143        Table(Value::pop_from_stack(self))
144    }
145}
146
147impl ToLua for Table {
148    fn push_to_stack(self, state: &lua::State) {
149        self.0.push_to_stack(state);
150    }
151
152    fn to_value(self, _: &lua::State) -> Value {
153        self.0
154    }
155}
156
157impl ToLua for &Table {
158    fn push_to_stack(self, state: &lua::State) {
159        #[allow(clippy::needless_borrow)]
160        (&self.0).push_to_stack(state);
161    }
162
163    fn to_value(self, _: &lua::State) -> Value {
164        self.0.clone()
165    }
166}
167
168impl FromLua for Table {
169    fn try_from_stack(state: &lua::State, index: i32) -> Result<Self> {
170        match lua::ffi::lua_type(state.0, index) {
171            lua::ffi::LUA_TTABLE => Ok(Table(Value::from_stack(state, index))),
172            _ => Err(state.type_error(index, "table")),
173        }
174    }
175}
176
177impl ObjectLike for Table {
178    #[inline]
179    fn get<V: FromLua>(&self, state: &lua::State, key: impl ToLua) -> Result<V> {
180        self.get(state, key)
181    }
182
183    #[inline]
184    fn set(&self, state: &lua::State, key: impl ToLua, value: impl ToLua) -> Result<()> {
185        self.set(state, key, value)
186    }
187
188    #[inline]
189    fn call<R: FromLuaMulti>(
190        &self,
191        state: &lua::State,
192        name: &str,
193        args: impl ToLuaMulti,
194    ) -> lua::Result<R> {
195        let func: Function = self.get(state, name)?;
196        func.call(state, args)
197    }
198
199    #[inline]
200    fn call_method<R: FromLuaMulti>(
201        &self,
202        state: &lua::State,
203        name: &str,
204        args: impl ToLuaMulti,
205    ) -> lua::Result<R> {
206        self.call(state, name, (self, args))
207    }
208}
209
210pub struct IPairsIter<V> {
211    table: Table,
212    state: lua::State,
213    index: usize,
214    len: usize,
215    _phantom: std::marker::PhantomData<V>,
216}
217
218impl<V: FromLua> Iterator for IPairsIter<V> {
219    type Item = (usize, V);
220
221    fn next(&mut self) -> Option<Self::Item> {
222        if self.index >= self.len {
223            return None;
224        }
225        self.index += 1;
226
227        let _sg = self.state.stack_guard();
228
229        (&self.table).push_to_stack(&self.state);
230        ffi::lua_rawgeti(self.state.0, -1, self.index as i32);
231
232        V::try_from_stack(&self.state, -1)
233            .ok()
234            .map(|value| (self.index, value))
235    }
236}