mlua_extras/extras/
module.rs

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