Skip to content

The LuaUserdata Object

davidsiaw edited this page Sep 29, 2014 · 1 revision

The LuaUserdata Object is the way you provide C++ objects to Lua. It encapsulates your object in much the same way a std::shared_ptr does, except the reference counting is done on the Lua registry, and you can pass the object into your Lua script and back out and the garbage collector will keep an eye on it.

For the purposes of this document, let us say we have a class:

#include <string>
#include <luacppinterface.h>

class Person
{
  std::string name;
  int age;
  bool hired;
public:
  Person(std::string name, int age) : name(name), age(age), hired(false) {}
  void hire() { hired = true; }
  void fire() { hired = false; }
  bool isHired() const { return hired; }
  int getAge() const { return age; }
  std::string getName() const { return name; }
};

int main()
{
  Lua lua;
  lua.LoadStandardLibraries();
  LuaUserdata<Person> billy = lua.CreateUserdata<Person>(new Person("Billy", 21));
  lua.GetGlobalEnvironment().Set("billy", billy);
  lua.RunScript("print(billy)");
  return 0;
}

#Creation

Like an std::shared_ptr, you can create a LuaUserdata object like this:

LuaUserData<Person> billy = lua.CreateUserdata(new Person("billy", 21));

That was easy.

If you put a LuaUserdata object on the global table it is accessible by all.

lua.GetGlobalEnvironment().Set("billy", billy);

In a Lua script, if you run this script:

lua.LoadStandardLibraries();
lua.RunScript("print(billy)");

you will get output that looks like: userdata: 0x7fc3d2c0de98

#Binding members

Now, our LuaUserdata object is still fairly useless in the script because besides passing it around, we cannot really do very much with it, so we need to expose the functionality of the object to Lua too. in order to do so, let us revisit our class. We have a few members

  void hire();
  void fire();
  bool isHired() const;
  int getAge() const;
  std::string getName() const;

Since all the functions deal with the Primitive Types, we can easily bind them using the Bind() function as follows:

  billy.Bind("hire", &Person::hire);
  billy.Bind("fire", &Person::fire);
  billy.Bind("isHired", &Person::isHired);
  billy.Bind("getAge", &Person::getAge);
  billy.Bind("getName", &Person::getName);

Now we can try these scripts:

  lua.RunScript("print (billy.getName())");

This should give you Billy. Now let us try get his age:

  lua.RunScript("print (billy.getAge())");

This should give us his age, which is 21. What about if he is hired?

  lua.RunScript("print (billy.isHired())");

Obviously he won't be hired without anyone hiring him.

Feel free to play around with Billy. Try hire him.

  lua.RunScript("billy.hire()");

Now let us check his state:

  lua.RunScript("print (billy.isHired())");

He is hired! What if we fire him now? Unions might disapprove of us, but he doesn't exist.

  lua.RunScript("billy.fire()");

Now let us see his status:

  lua.RunScript("print (billy.isHired())");

#Making the class constructible in Lua

Obviously, we can't just have one person, our Lua script should be able to make lots and lots of minions for us to hire! In order to do so, we must provide a constructor to Lua. This is quite simple.

We will create a table and then provide a function on the table that returns a new person.

  LuaTable personClass = lua.CreateTable();
  auto makePerson = lua.CreateFunction< LuaUserdata<Person>(std::string, int) >(
    [&](std::string name, int age)
    {
      // Create the person
      LuaUserdata<Person> newPerson = lua.CreateUserdata<Person>(new Person(name, age));
      
      // Bind the members as done above.
      newPerson.Bind("hire", &Person::hire);
      newPerson.Bind("fire", &Person::fire);
      newPerson.Bind("isHired", &Person::isHired);
      newPerson.Bind("getAge", &Person::getAge);
      newPerson.Bind("getName", &Person::getName);

      // A star is born!
      return newPerson;
    });
  personClass.Set("new", makePerson);
  lua.GetGlobalEnvironment().Set("Person", personClass);

Now try this:

lua.RunScript("john = Person.new('John', 25)");
lua.RunScript("print (john.getName())");

You will get John! The circle is complete. We can now create user data in Lua itself!

#Accessing members

Now, we have a LuaUserdata that we can pass around in Lua and in C++, but we are still missing one thing, if the object is wrapped in the LuaUserdata how do we access the object?

The answer is quite simple. I said before that the LuaUserdata acts very much like a std::shared_ptr. You simply use the -> operator to do what you always do to the object. For example:

LuaUserdata<Person> billy = lua.CreateUserdata<Person>(new Person("Billy", 21));
std::cout << billy->getName() << std::endl;

Should give you billy's name.

That is all for now. Have fun!

Clone this wiki locally