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
27pub trait ExtendModule<'lua> {
32 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
51pub trait Module: Sized {
53 #[allow(unused_variables)]
55 fn add_fields<'lua, F: ModuleFields<'lua>>(fields: &mut F) -> mlua::Result<()> {
56 Ok(())
57 }
58
59 #[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
70pub trait ModuleFields<'lua> {
72 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 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 fn add_module<K, V>(&mut self, name: K) -> mlua::Result<()>
86 where
87 K: IntoLua<'lua>,
88 V: Module;
89}
90
91pub trait ModuleMethods<'lua> {
93 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 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 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 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
126pub 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}