Skip to main content

rs_mod_lua_core/
guard.rs

1use std::cell::RefCell;
2
3thread_local! {
4    static LUA: RefCell<Option<mlua::WeakLua>> = const { RefCell::new(None) };
5}
6
7struct LuaGuard<'r> {
8    prev: Option<mlua::WeakLua>,
9    store: &'r RefCell<Option<mlua::WeakLua>>,
10}
11
12impl<'r> LuaGuard<'r> {
13    fn new(lua: &mlua::Lua, store: &'r RefCell<Option<mlua::WeakLua>>) -> Self {
14        let weak = lua.weak();
15        let prev = store.replace(Some(weak));
16        Self { prev, store }
17    }
18
19    fn bind<R, F>(lua: &mlua::Lua, store: &'r RefCell<Option<mlua::WeakLua>>, f: F) -> R
20    where
21        F: FnOnce() -> R,
22    {
23        let _guard = Self::new(lua, store);
24        f()
25    }
26
27    fn with<R, F>(store: &'r RefCell<Option<mlua::WeakLua>>, f: F) -> Result<R, mlua::Error>
28    where
29        F: FnOnce(&mlua::Lua) -> Result<R, mlua::Error>,
30    {
31        // The borrow from `store` must be dropped before calling `f` to prevent panics in case `f`
32        // itself calls `bind`
33        let lua = store
34            .try_borrow()
35            .map_err(mlua::Error::runtime)?
36            .as_ref()
37            .ok_or_else(|| {
38                mlua::Error::runtime("`mlua::Lua` instance accessed outside of a render context")
39            })?
40            .try_upgrade()
41            .ok_or_else(|| mlua::Error::runtime("`mlua::Lua` instance is not available"))?;
42
43        f(&lua)
44    }
45}
46
47impl Drop for LuaGuard<'_> {
48    fn drop(&mut self) {
49        self.store.replace(self.prev.take());
50    }
51}
52
53pub fn bind_lua<R, F>(lua: &mlua::Lua, f: F) -> R
54where
55    F: FnOnce() -> R,
56{
57    LUA.with(|slot| LuaGuard::bind(lua, slot, f))
58}
59
60pub fn with_lua<R, F>(f: F) -> Result<R, mlua::Error>
61where
62    F: FnOnce(&mlua::Lua) -> Result<R, mlua::Error>,
63{
64    LUA.with(|store| LuaGuard::with(store, f))
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70
71    #[test]
72    fn with_outside_context() {
73        let err = with_lua(|_| Ok(()));
74
75        assert!(err.is_err());
76        assert!(
77            err.unwrap_err()
78                .to_string()
79                .contains("accessed outside of a render context")
80        );
81    }
82
83    #[test]
84    fn test_recursive_bind() {
85        let lua = mlua::Lua::new();
86
87        let res = bind_lua(&lua, || {
88            with_lua(|lua| bind_lua(lua, || with_lua(|lua| bind_lua(lua, || Ok(())))))
89        });
90
91        assert!(res.is_ok())
92    }
93
94    #[test]
95    fn test_drop_and_restore() {
96        let lua1 = &mlua::Lua::new();
97        let lua2 = &mlua::Lua::new();
98
99        let reg1 = lua1.create_registry_value(1).unwrap();
100        let reg2 = lua2.create_registry_value(2).unwrap();
101
102        bind_lua(lua1, || {
103            let res1 = with_lua(|lua1| Ok(lua1.owns_registry_value(&reg1))).unwrap();
104            assert!(res1);
105
106            let res2 = bind_lua(lua2, || {
107                with_lua(|lua2| Ok(lua2.owns_registry_value(&reg2)))
108            })
109            .unwrap();
110            assert!(res2);
111
112            let res1 = with_lua(|lua1| Ok(lua1.owns_registry_value(&reg1))).unwrap();
113            assert!(res1);
114        })
115    }
116
117    #[test]
118    fn test_panic_and_restore() {
119        let lua1 = &mlua::Lua::new();
120        let lua2 = &mlua::Lua::new();
121
122        let reg1 = lua1.create_registry_value(1).unwrap();
123
124        bind_lua(lua1, || {
125            let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
126                bind_lua(lua2, || {
127                    with_lua(|_| -> Result<(), mlua::Error> { panic!("pow") }).unwrap();
128                });
129            }));
130
131            let res1 = with_lua(|lua1| Ok(lua1.owns_registry_value(&reg1))).unwrap();
132            assert!(res1);
133        })
134    }
135}