1

I have a nested table in my lua code that I want to pass to C++ so the native code can manipulate it:

-- Some persistent data in my game
local data = {
        { 44, 34, 0, 7, },
        { 4, 4, 1, 3, },
}
-- Pass it into a C++ function that can modify the input data.
TimelineEditor(data)

How do I write my C++ code to read the nested table and modify its values?

Reading Lua nested tables in C++ and lua c read nested tables both describe how I can read from nested tables, but not how to write to them.

idbrii
  • 10,975
  • 5
  • 66
  • 107
  • 1
    Does this answer your question? [Reading Lua nested tables in C++](https://stackoverflow.com/questions/16975760/reading-lua-nested-tables-in-c) – Kelvin Schoofs Aug 04 '21 at 20:21
  • That question is only about reading tables, not writing to them. – idbrii Aug 04 '21 at 20:31
  • 1
    you can consider including sol libraries to your project. it will make life easier – dixit_chandra Aug 04 '21 at 20:35
  • Correct, although I'm sure anyone that can get that far can figure out to use `lua_rawseti` instead of `lua_rawgeti`. And since there are questions on how to read nested tables (and writing to regular tables), there really isn't a need for this question? But fair I guess. – Kelvin Schoofs Aug 04 '21 at 20:37
  • There are [3 other q/a with rawseti](https://stackoverflow.com/search?q=rawseti) and they're either about non-nested tables or creating new tables. I made the connection to rawseti, but it took awhile to figure out the details. I think this question would be very helpful to anyone trying to understand how the lua C API works. – idbrii Aug 04 '21 at 20:42

1 Answers1

0

Short answer

Lua uses a stack to get values in and out of tables. To modify table values you'll need to push the table you want to modify with lua_rawgeti, push a value you want to insert with lua_pushinteger, and then set the value in the table with lua_rawseti.

When writing this, it's important to visualize the stack to ensure you use the right indexes:

lua_rawgeti()
    stack:
        table

lua_rawgeti()
    stack:
        number <-- top of the stack
        table

lua_tonumber()
    stack:
        number
        table

lua_pop()
    stack:
        table

lua_pushinteger()
    stack:
        number
        table

lua_rawseti()
    stack:
        table

Negative indexes are stack positions and positive indexes are argument positions. So we'll often pass -1 to access the table at the stack. When calling lua_rawseti to write to the table, we'll pass -2 since the table is under the value we're writing.

Example

I'll add inspect.lua to the lua code to print out the table values so we can see that the values are modified.

local inspect = require "inspect"

local data = {
        { 44, 34, 0, 7, },
        { 4, 4, 1, 3, },
}
print("BEFORE =", inspect(data, { depth = 5, }))
TimelineEditor(data)
print("AFTER =", inspect(data, { depth = 5, }))

Assuming you've figured out BindingCodeToLua, you can implement the function like so:


// Replace LOG with whatever you use for logging or use this:
#define LOG(...) printf(__VA_ARGS__); printf("\n")

// I bound with Lunar. I don't think it makes a difference for this example.
int TimelineEditor(lua_State* L)
{
    LOG("Read the values and print them out to show that it's working.");
    {
        int entries_table_idx = 1;
        luaL_checktype(L, entries_table_idx, LUA_TTABLE);
        int n_entries = static_cast<int>(lua_rawlen(L, entries_table_idx));
        LOG("%d entries", n_entries);
        for (int i = 1; i <= n_entries; ++i)
        {
            // Push inner table onto stack.
            lua_rawgeti(L, entries_table_idx, i);
            int item_table_idx = 1;
            luaL_checktype(L, -1, LUA_TTABLE);
            int n_items = static_cast<int>(lua_rawlen(L, -1));
            LOG("%d items", n_items);
            for (int i = 1; i <= n_items; ++i)
            {
                // Push value from table onto stack.
                lua_rawgeti(L, -1, i);
                int is_number = 0;
                // Read value
                int x = static_cast<int>(lua_tonumberx(L, -1, &is_number));
                if (!is_number)
                {
                    // fire an error
                    luaL_checktype(L, -1, LUA_TNUMBER);
                }
                LOG("Got: %d", x);
                // pop value off stack
                lua_pop(L, 1);
            }
            // pop table off stack
            lua_pop(L, 1);
        }
    }

    LOG("Overwrite the values");
    {
        int entries_table_idx = 1;
        luaL_checktype(L, entries_table_idx, LUA_TTABLE);
        int n_entries = static_cast<int>(lua_rawlen(L, entries_table_idx));
        LOG("%d entries", n_entries);
        for (int i = 1; i <= n_entries; ++i)
        {
            // Push inner table onto stack.
            lua_rawgeti(L, entries_table_idx, i);
            int item_table_idx = 1;
            luaL_checktype(L, -1, LUA_TTABLE);
            int n_items = static_cast<int>(lua_rawlen(L, -1));
            LOG("%d items", n_items);
            for (int j = 1; j <= n_items; ++j)
            {
                int x = j + 10;
                // Push new value onto stack.
                lua_pushinteger(L, x);
                // rawseti pops the value off. Need to go -2 to get to the
                // table because the value is on top.
                lua_rawseti(L, -2, j);
                LOG("Wrote: %d", x);
            }
            // pop table off stack
            lua_pop(L, 1);
        }
    }
    // No return values
    return 0;
}

Output:

BEFORE =    { { 44, 34, 0, 7 }, { 4, 4, 1, 3 } }
Read the values and print them out to show that it's working.
2 entries
4 items
Got: 44
Got: 34
Got: 0
Got: 7
4 items
Got: 4
Got: 4
Got: 1
Got: 3
Overwrite the values
2 entries
4 items
Wrote: 11
Wrote: 12
Wrote: 13
Wrote: 14
4 items
Wrote: 11
Wrote: 12
Wrote: 13
Wrote: 14
AFTER =     { { 11, 12, 13, 14 }, { 11, 12, 13, 14 } }
idbrii
  • 10,975
  • 5
  • 66
  • 107