mlua_extras/extras/
module.rs

1use std::{any::type_name, marker::PhantomData};
2
3use mlua::{FromLuaMulti, IntoLua, IntoLuaMulti};
4
5use crate::MaybeSend;
6
7#[derive(Default)]
8pub struct LuaModule<M>(PhantomData<M>);
9impl<'lua, M: Module> IntoLua<'lua> for LuaModule<M> {
10    fn into_lua(
11        self,
12        lua: &'lua mlua::prelude::Lua,
13    ) -> mlua::prelude::LuaResult<mlua::prelude::LuaValue<'lua>> {
14        let mut builder: ModuleBuilder<'lua> = ModuleBuilder {
15            table: lua.create_table()?,
16            lua,
17            parents: Vec::new(),
18        };
19
20        M::add_fields(&mut builder)?;
21        M::add_methods(&mut builder)?;
22
23        Ok(mlua::Value::Table(builder.table))
24    }
25}
26
27/// Sepecify a lua module (table) with fields and methods
28pub trait Module: Sized {
29    /// Add fields to the module
30    #[allow(unused_variables)]
31    fn add_fields<'lua, F: ModuleFields<'lua>>(fields: &mut F) -> mlua::Result<()> {
32        Ok(())
33    }
34    
35    /// Add methods/functions to the module
36    #[allow(unused_variables)]
37    fn add_methods<'lua, M: ModuleMethods<'lua>>(methods: &mut M) -> mlua::Result<()> {
38        Ok(())
39    }
40
41    fn module() -> LuaModule<Self> {
42        LuaModule(PhantomData)
43    }
44}
45
46/// Add table fields for a module
47pub trait ModuleFields<'lua> {
48    /// Add a field to the module's table
49    fn add_field<K, V>(&mut self, name: K, value: V) -> mlua::Result<()>
50    where
51        K: IntoLua<'lua>,
52        V: IntoLua<'lua>;
53
54    /// Add a field to the module's metatable
55    fn add_meta_field<K, V>(&mut self, name: K, value: V) -> mlua::Result<()>
56    where
57        K: IntoLua<'lua>,
58        V: IntoLua<'lua>;
59
60    /// Add a nested module as a table in this module
61    fn add_module<K, V>(&mut self, name: K) -> mlua::Result<()>
62    where
63        K: IntoLua<'lua>,
64        V: Module;
65}
66
67/// Add table functions and methods for a module
68pub trait ModuleMethods<'lua> {
69    /// Add a function to this module's table
70    fn add_function<K, F, A, R>(&mut self, name: K, function: F) -> mlua::Result<()>
71    where
72        K: IntoLua<'lua>,
73        F: Fn(&mlua::Lua, A) -> mlua::Result<R> + MaybeSend + 'static,
74        A: FromLuaMulti<'lua>,
75        R: IntoLuaMulti<'lua>;
76
77    /// Add a function to this module's metatable
78    fn add_meta_function<K, F, A, R>(&mut self, name: K, function: F) -> mlua::Result<()>
79    where
80        K: IntoLua<'lua>,
81        F: Fn(&mlua::Lua, A) -> mlua::Result<R> + MaybeSend + 'static,
82        A: FromLuaMulti<'lua>,
83        R: IntoLuaMulti<'lua>;
84
85    /// Add a method to this module's table
86    fn add_method<K, F, A, R>(&mut self, name: K, function: F) -> mlua::Result<()>
87    where
88        K: IntoLua<'lua>,
89        F: Fn(&mlua::Lua, mlua::Table<'_>, A) -> mlua::Result<R> + MaybeSend + 'static,
90        A: FromLuaMulti<'lua>,
91        R: IntoLuaMulti<'lua>;
92
93    /// Add a method to this module's metatable
94    fn add_meta_method<K, F, A, R>(&mut self, name: K, function: F) -> mlua::Result<()>
95    where
96        K: IntoLua<'lua>,
97        F: Fn(&mlua::Lua, mlua::Table<'_>, A) -> mlua::Result<R> + MaybeSend + 'static,
98        A: FromLuaMulti<'lua>,
99        R: IntoLuaMulti<'lua>;
100}
101
102/// Builder that construct a module based on the [`Module`] trait
103pub struct ModuleBuilder<'lua> {
104    lua: &'lua mlua::Lua,
105    table: mlua::Table<'lua>,
106    parents: Vec<&'static str>,
107}
108
109impl<'lua> ModuleFields<'lua> for ModuleBuilder<'lua> {
110    fn add_field<K, V>(&mut self, name: K, value: V) -> mlua::Result<()>
111    where
112        K: IntoLua<'lua>,
113        V: IntoLua<'lua>,
114    {
115        self.table.set(name, value)
116    }
117
118    fn add_meta_field<K, V>(&mut self, name: K, value: V) -> mlua::Result<()>
119    where
120        K: IntoLua<'lua>,
121        V: IntoLua<'lua>,
122    {
123        let meta = match self.table.get_metatable() {
124            Some(meta) => meta,
125            None => {
126                let meta = self.lua.create_table()?;
127                self.table.set_metatable(Some(meta.clone()));
128                meta
129            }
130        };
131
132        meta.set(name, value)
133    }
134
135    fn add_module<K, V>(&mut self, name: K) -> mlua::Result<()>
136    where
137        K: IntoLua<'lua>,
138        V: Module,
139    {
140        if self.parents.contains(&type_name::<V>()) {
141            return Err(mlua::Error::runtime(format!(
142                "infinite nested modules using: '{}'",
143                type_name::<V>()
144            )));
145        }
146
147        let mut builder: ModuleBuilder<'lua> = ModuleBuilder {
148            table: self.lua.create_table()?,
149            lua: self.lua,
150            parents: self
151                .parents
152                .iter()
153                .map(|v| *v)
154                .chain([type_name::<V>()])
155                .collect(),
156        };
157
158        V::add_fields(&mut builder)?;
159        V::add_methods(&mut builder)?;
160
161        self.table.set(name, builder.table)
162    }
163}
164
165impl<'lua> ModuleMethods<'lua> for ModuleBuilder<'lua> {
166    fn add_function<K, F, A, R>(&mut self, name: K, function: F) -> mlua::Result<()>
167    where
168        K: IntoLua<'lua>,
169        F: Fn(&mlua::Lua, A) -> mlua::Result<R> + MaybeSend + 'static,
170        A: FromLuaMulti<'lua>,
171        R: IntoLuaMulti<'lua>,
172    {
173        self.table.set(name, self.lua.create_function(function)?)
174    }
175
176    fn add_meta_function<K, F, A, R>(&mut self, name: K, function: F) -> mlua::Result<()>
177    where
178        K: IntoLua<'lua>,
179        F: Fn(&mlua::Lua, A) -> mlua::Result<R> + MaybeSend + 'static,
180        A: FromLuaMulti<'lua>,
181        R: IntoLuaMulti<'lua>,
182    {
183        let meta = match self.table.get_metatable() {
184            Some(meta) => meta,
185            None => {
186                let meta = self.lua.create_table()?;
187                self.table.set_metatable(Some(meta.clone()));
188                meta
189            }
190        };
191
192        meta.set(name, self.lua.create_function(function)?)
193    }
194
195    fn add_method<K, F, A, R>(&mut self, name: K, function: F) -> mlua::Result<()>
196    where
197        K: IntoLua<'lua>,
198        F: Fn(&mlua::Lua, mlua::Table<'_>, A) -> mlua::Result<R> + MaybeSend + 'static,
199        A: FromLuaMulti<'lua>,
200        R: IntoLuaMulti<'lua>,
201    {
202        self.table.set(
203            name,
204            self.lua
205                .create_function(move |lua, args: mlua::MultiValue| {
206                    let this = mlua::Table::from_lua_multi(args.clone(), lua)?;
207                    let rest = A::from_lua_multi(args, lua)?;
208                    function(lua, this, rest)
209                })?,
210        )
211    }
212
213    fn add_meta_method<K, F, A, R>(&mut self, name: K, function: F) -> mlua::Result<()>
214    where
215        K: IntoLua<'lua>,
216        F: Fn(&mlua::Lua, mlua::Table<'_>, A) -> mlua::Result<R> + MaybeSend + 'static,
217        A: FromLuaMulti<'lua>,
218        R: IntoLuaMulti<'lua>,
219    {
220        let meta = match self.table.get_metatable() {
221            Some(meta) => meta,
222            None => {
223                let meta = self.lua.create_table()?;
224                self.table.set_metatable(Some(meta.clone()));
225                meta
226            }
227        };
228
229        meta.set(
230            name,
231            self.lua
232                .create_function(move |lua, args: mlua::MultiValue| {
233                    let this = mlua::Table::from_lua_multi(args.clone(), lua)?;
234                    let rest = A::from_lua_multi(args, lua)?;
235                    function(lua, this, rest)
236                })?,
237        )
238    }
239}