PrevUpHomeNext

Lua References

As we saw, userdata is a way that you can allocate C++ objects in memory owned by lua, and attach a lua interface to it.

Sometimes, you want to have objects which are exposed like this, but aren't owned exclusively by lua -- they exist in some other part of your program and play an active role there.

A common idiom for this is to make lua own a smart pointer to the object, not the object itself:

struct foo_ref {
  std::shared_ptr<foo> foo_ptr;
};

namespace primer {
namespace traits {

template <>
struct userdata<foo_ref> { ... };

} // end namespace traits
} // end namespace primer

In this case, the object only gets freed when your program is finished with it, and lua garbage collects the userdata. (In primer, if you don't specify a __gc metamethod, a default one is installed which calls the destructor, which is almost always what you want. In pure lua you generally need to do that manually. See reference page for more details.)

This allows lua to share ownership with C++ of C++ objects. However, sometimes we want to do the mirror of this -- C++ shares ownership with lua of lua objects.

lua provides a facility to do basically exactly that: luaL_ref and luaL_unref.

If you push an object on top of the stack and use luaL_ref, it basically increments lua's internal reference count for that object, and gives you a token that you can use to find it again. luaL_unref can be used to decrement the count when you don't need to hold onto the object anymore.

In C++, we can basically hold this token in an RAII object which calls luaL_unref from the destructor.

In some cases this is more elegant and convenient than using std::shared_ptr userdata. When shared_ptr is used, there are actually two reference counts taking place -- lua's internal reference counts for its garbage collection, and the one within std::shared_ptr. When we use primer::lua_ref, we effectively get a "smart pointer" to a lua value which hooks into lua's own garbage collection, so only one reference count is occuring rather than two.

Particularly, this is one of the only ways you can "hang on" to some objects within a lua state that can only really be represented within lua and not easily in C++: lua functions, and lua coroutines.

Here's an example binding a lua_ref:

// Make a table
luaL_newtable(L);
lua_pushinteger(L, 5);
lua_setfield(L, -2, "asdf");

primer::lua_ref ref{L}; // pops table from the stack

assert(ref);

ref.push(); // pushes the table back onto the stack
            // ref is still bound

lua_getfield(L, -2, "asdf");
assert(lua_isinteger(L, -1));

For extended syntax examples, see the reference pages.

A particularly useful example has to do with using primer::bound_function to bind user script functions to a C++ gui.

In many applications, people want to use lua scripts to create mods or plugins. They'd like to be able to create functions that act as callbacks, in lua. For example, for rigging up gui's using lua:

local function my_func()
  print("Hello world!")
end

bind_click(my_func)

Using primer, this can be used with a typical C++ gui by taking advantage of bound_function:

struct lua_callback_runner : my_gui::event_handler {
  primer::bound_function func_;

  void handle_event() {
    func_.call();
  }
};

primer::result bind_click(lua_State * L, primer::bound_function func) {
  my_gui::bind_click(lua_callback_runner{std::move(func)});
  return 0;
}

In this case, the bound function becomes the guts of a function object meeting your gui's requirements. It's okay for the bound_function to live past the lifetime of your lua_State, it doesn't particularly matter when the gui cleans up its event handlers in this example.


PrevUpHomeNext