C++: Storing A shared_ptr As Lua Userdata
Recently I’ve been working on a game engine in C++. I’ve always wanted to participate in a Ludum Dare compo, and this work is in preparation for the December 1st compo. A big piece of the design of my engine is using Lua for scripting and providing novel Lua objects from C++ for this purpose.
Yesterday I was attempting to embed provide a C++ object to Lua scripts. This object’s lifetime and ownership were managed using a reference counted shared_ptr, and I didn’t want to abandon the shared_ptr if I didn’t have to in order to offer access to this object in Lua. Along the way I discovered the following, and to spare others a few core dumps and some frustration I figured I’d write it up here.
It all starts with a resource, in our case a Texture, but any resource you want to expose within your Lua scripting environment will do. So we’ll make an example object:
class Resource { public: Resource(const std::string& name) : name_(name) { std::cout << "Resource(" << name_ << ")" << std::endl; } ~Resource() { std::cout << "~Resource(" << name_ << ")" << std::endl; } const std::string& Name() const { return name_; } private: std::string name_; };
In order to inject the object into Lua’s scripting environment you need to define some methods that will go on its metatable:
static const struct luaL_reg ResourceTableDefinition[] = { {"Create", ResourceTable::Create}, {"__gc", ResourceTable::Destroy}, {"Name", ResourceTable::Name}, {NULL, NULL} // Sentinel value }; // Bind a new table, "Resource", within our Lua environment. void BindResource(lua_State* state) { const char* id = "Resource"; lua_newtable(state); luaL_newmetatable(state, id); luaL_register(state, NULL, ResourceTableDefinition); lua_pushliteral(state, "__index"); lua_pushvalue(state, -2); lua_rawset(state, -3); lua_setglobal(state, id); }
Accessor methods, like Name, are pretty easy if we assume that we’ve correctly initialized our user data with a shared_ptr object:
namespace ResourceTable { int Name(lua_State* state) { void* resourcePtr = luaL_checkudate(state, 1, "Resource"); if (resourcePtr) { auto resource = static_cast<std::shared_ptr<Resource>*>(resourcePtr); lua_pushstring(state, (*resource)->Name().c_str()); return 1; } return 0; } }
The tricky part appears when you go to define your Create and Destroy methods. Almost immediately the question of what you should store in the allocated userdata arises. The secret is to allocated space for an entire shared_ptr object, and then use the placement syntax for the new expression to initialize that allocated space with a new shared_ptr:
We can see this in action in the Create method below:
namespace ResourceTable { int Create(lua_State* state) { // We will take one argument which will be our Resource name. if(!lua_isstring(state, 1)) { return luaL_typerror(state, 1, "string"); } // Initialize a new resource shared pointer const char* resourceName = lua_tostring(state, 1); auto resource = std::make_shared<Resource>(resourceName); // Next allocate enough space for the shared_tr void *userData = lua_newuserdata( state, sizeof(std::shared_ptr<Resource>)); // Handle allocation failure if (!userData) { return 0; } // Use the placement parameters of the new operator to allocate // shared_ptr within the userdata provided by Lua. Copy our other // shared_ptr into it, increasing the reference count new(userData) std::shared_ptr<Resource>(resource); // Now just set the metatable on this new object luaL_getmetatable(state, "Resource"); lua_setmetatable(state, -2); return 1; } }
Now that we have our object successfully allocated, initialized, and accessible from our accessor method, we next need to figure out the twilight of its lifecycle. In order to ensure the allocated shared_ptr is properly released, it turns out that you will need t o write your own cleanup method for when the Lua garbage collector finally reclaims that space.
Luckily, for shared_ptr, cleanup is as simple as calling its reset method:
namespace ResourceTable { int Destroy(lua_State* state) { void* resourcePtr = luaL_checkudata(state, 1, "Resource"); if (resourcePtr) { auto resource = static_cast<std::shared_ptr<Resource>*>(resourcePtr); resource->reset(); } return 0; } }
If you now write some Lua code to test this new resource:
resource = Resource.Create("Test") io.write("%s\n", resource.Name())
Executing this from within our C++ environment, we see what we expect:
Resource(Test) Test ~Resource(Test)
And that’s it. It took several hours of tinkering, core dumps, and digging around on Google and within various wrapper libraries before I finally fit all the pieces together. Since I couldn’t find a good single resource for explaining this technique, I figured I’d write it.
You can see the implementation within my game engine here