Dowon Cha
Dowon Cha

Reputation: 84

How to retrieve lua table from context in Rust?

I'm trying to retrieve tables from lua files in Rust. I'm registering a register_entity function in the lua global context for data files to register their tables. When the lua file is executed and it calls the register_entity function, the registered callback is called in Rust. The callback should add the passed table into a HashMap to maintain a collection of all entities.

Here's an example lua file that is being read.

Goblin = {
  glyph: "2"
}

register_entity("Goblin", Goblin)

Rust Code

fn load_lua_globals(&self) {
    let entities = Arc::new(Mutex::new(HashMap::new()));

        self.lua.context(|lua_ctx| {
            let register_entity = {
                let entities = Arc::clone(&entities);

                let register_entity = lua_ctx.create_function(
                    move |_, (name, table): (String, Table)| {
                        entities.lock().unwrap().insert(name, table);

                    Ok(())
                }).unwrap();
            };

            lua_ctx.globals().set("register_entity", register_entity).unwrap();
        });
    }
}

This is the error.

error[E0277]: `*mut rlua::ffi::lua_State` cannot be sent between threads safely
   --> src/bin/main.rs:106:47
    |
106 |                 let register_entity = lua_ctx.create_function(
    |                                               ^^^^^^^^^^^^^^^ `*mut rlua::ffi::lua_State` cannot be sent between threads safely
    |
    = help: within `(std::string::String, rlua::Table<'_>)`, the trait `std::marker::Send` is not implemented for `*mut rlua::ffi::lua_State`
    = note: required because it appears within the type `rlua::Context<'_>`
    = note: required because it appears within the type `rlua::types::LuaRef<'_>`
    = note: required because it appears within the type `rlua::Table<'_>`
    = note: required because it appears within the type `(std::string::String, rlua::Table<'_>)`
    = note: required because of the requirements on the impl of `std::marker::Send` for `hashbrown::raw::RawTable<(std::string::String, rlua::Table<'_>)>`
    = note: required because it appears within the type `hashbrown::map::HashMap<std::string::String, rlua::Table<'_>, std::collections::hash_map::RandomState>`
    = note: required because it appears within the type `std::collections::HashMap<std::string::String, rlua::Table<'_>>`
    = note: required because of the requirements on the impl of `std::marker::Send` for `std::sync::Mutex<std::collections::HashMap<std::string::String, rlua::Table<'_>>>`
    = note: required because of the requirements on the impl of `std::marker::Send` for `std::sync::Arc<std::sync::Mutex<std::collections::HashMap<std::string::String, rlua::Table<'_>>>>`
    = note: required because it appears within the type `[closure@src/bin/main.rs:107:21: 112:18 entities:std::sync::Arc<std::sync::Mutex<std::collections::HashMap<std::string::String, rlua::Table<'_>>>>]

Upvotes: 1

Views: 783

Answers (1)

Hugal31
Hugal31

Reputation: 1768

You cannot store reference to lua_ctx outside the call to Lua::context. The rlua::Table you store in entities is bound to the lifetime of lua_ctx.

rlua provides a way to store reference to Lua objects outside of context calls, with Context::create_registry_value. It returns a Regsitry key that you can safely store in your HashMap, and use it afterwards in another call to Lua::context. Check the following code for an example :

use std::collections::HashMap;
use std::sync::{Arc, Mutex};

use rlua::{Lua, RegistryKey, Table};

const LUA_SOURCE: &str = r#"
Goblin = {
  glyph = "2"
}

register_entity("Goblin", Goblin)
"#;

fn setup_register_entity(lua: &Lua) -> Arc<Mutex<HashMap<String, RegistryKey>>> {
    let entities = Arc::new(Mutex::new(HashMap::new()));

    lua.context(|lua_ctx| {
        let entities = entities.clone();

        let register_entity = lua_ctx.create_function(
            move |ctx, (name, table): (String, Table)| {
                // Store a refenrence to the object as a RegistryKey
                let key = ctx.create_registry_value(table)
                    .expect("should have inserted in registry");
                entities.lock().unwrap().insert(name, key);

                Ok(())
            })
            .unwrap();

        lua_ctx.globals().set("register_entity", register_entity).unwrap();
    });

    entities
}

fn main() {
    let lua = Lua::new();

    let entities = setup_register_entity(&lua);

    lua.context(|lua_ctx| {
        // Load lua code
        lua_ctx.load(LUA_SOURCE)
            .exec()
            .expect("should load");

        for (name, key) in entities.lock().unwrap().iter() {
            // Retreive table
            let table: Table = lua_ctx.registry_value(key).unwrap();

            // Use it
            let glyph: String = table.get("glyph").unwrap();
            println!("{}.glyph = {}", name, glyph);
        }
    });
}

Upvotes: 2

Related Questions