Using Lua with C++ (Part 1)

You can view full source code of this article here

Russian version of this tutorial here

If you want to see Lua in real world practice, check out Using Lua with C++ in practice series.

 

This article is about building your own  Lua/C++ binding. If you want to use an existing library (which is a lot easier and faster), check out this article : Using Lua with C++: LuaBridge

So, why use LUA?

I’ve seen game devs using different data formats. Some of them use JSON, some use XML, some use plain .txt files etc.. As for me, I use Lua for data, because:

  • It is easy to use without any additional libraries(well, except of lua libraries, of course…)
  • You can use different formulas in your files, for example: some_variable = math.sqrt(2) * 2
  • It’s extremely lightweight and fast
  • It’s under MIT license, so you can use it in any way you want. Just download it and use it
  • It’s ported almost everywhere, because it’s written in C and compiles with almost any C compiler
  • You can use tables to categorize your data which is easy to edit and read

Let’s look at example Lua file:

player = {
    pos = {
         X = 20,
         Y = 30,
    },
    filename = "res/images/player.png",
    HP = 20,
-- you can also have comments
}

With a little class(implementation is below) you can get data in this way:

LuaScript script("player.lua");
std::string filename = script.get("player.filename");
int posX = script.get("player.pos.X");

Pretty neat.

Bindings

You can find lots of bindings here.
But I wanted to write my own, so here it is.

Note: my code is not perfect, but it works. I appreciate your suggestions on how I can improve my code. E-mail me if you find some errors or just to say thanks: eliasdaler@yandex.ru

Recommended reading to know how Lua stack works and view simple examples to know what’s going on:
http://www.lua.org/pil/24.html

Implementation

So, let’s start with a simple class

#ifndef LUASCRIPT_H
#define LUASCRIPT_H

#include <string>
#include <vector>
#include <iostream>

// Lua is written in C, so compiler needs to know how to link its libraries
extern "C" {
# include "lua.h"
# include "lauxlib.h"
# include "lualib.h"
}

class LuaScript {
public:
    LuaScript(const std::string& filename);
    ~LuaScript();
    void printError(const std::string& variableName, const std::string& reason);

    template<typename T>
    T get(const std::string& variableName) {
    	// will be implemented later in tutorial
    }
    bool lua_gettostack(const std::string& variableName) {
       // will be explained later too
    }

    // Generic get
    template<typename T>
    T lua_get(const std::string& variableName) {
      return 0;
    }

    // Generic default get
    template<typename T>
    T lua_getdefault(const std::string& variableName) {
      return 0;
    }
private:
    lua_State* L;
};

#endif

Constuctor:

LuaScript::LuaScript(const std::string& filename) {
    L = luaL_newstate();
    if (luaL_loadfile(L, filename.c_str()) || lua_pcall(L, 0, 0, 0)) {
        std::cout<<"Error: script not loaded ("<<filename<<")"<<std::endl;
        L = 0;
    }
}

Nothing special here. We create new Lua state and check if everything is okay.

Destructor:

LuaScript::~LuaScript() {
    if(L) lua_close(L);
}

printError method is simple. It prints which variable was not loaded and why.

void LuaScript::printError(const std::string& variableName, const std::string& reason) {
	std::cout<<"Error: can't get ["<<variableName<<"]. "<<reason<<std::endl;
}

lua_getdefault() is used when you need to return some null value for a variable. For numbers it will be okay to return 0, but for std::string we have to return “null”, for example. (You can add another cases for different types)
That’s why we add template specialization.(it goes in LuaScript.h)

template<>
inline std::string LuaScript::lua_getdefault() {
  return "null";
}

Now we’ll implement template get function(this will all go in LuaScript.h file)

The algorigthm for lua_gettostack is simple . Suppose you have a variable you want to get, for example named player.pos.X.

player table is global, so you need to get it via lua_getglobal method. Now player table will be on the top of your stack and you will use lua_getfield function to get pos table and then variable x. We use level variable to know which level we’re currently on and how many items are currently in stack

How get() template function works

template<typename T>
T get(const std::string& variableName) {
  if(!L) {
    printError(variableName, "Script is not loaded");
    return lua_getdefault<T>();
  }

  T result;
  if(lua_gettostack(variableName)) { // variable succesfully on top of stack
    result = lua_get<T>(variableName);
  } else {
    result = lua_getdefault<T>();
  }

  lua_pop(L, level + 1); // pop all existing elements from stack
  return result;
}

bool lua_gettostack(const std::string& variableName) {
  level = 0;
  std::string var = "";
    for(unsigned int i = 0; i < variableName.size(); i++) {
      if(variableName.at(i) == '.') {
        if(level == 0) {
          lua_getglobal(L, var.c_str());
        } else {
          lua_getfield(L, -1, var.c_str());
        }

        if(lua_isnil(L, -1)) {
          printError(variableName, var + " is not defined");
          return false;
        } else {
          var = "";
          level++;
        }
      } else {
        var += variableName.at(i);
      }
    }
    if(level == 0) {
      lua_getglobal(L, var.c_str());
    } else {
      lua_getfield(L, -1, var.c_str());
    }
    if(lua_isnil(L, -1)) {
        printError(variableName, var + " is not defined");
        return false;
    }

    return true;
}

And now for the template specializations(put them in LuaScript.h):

template <>
inline bool LuaScript::lua_get(const std::string& variableName) {
    return (bool)lua_toboolean(L, -1);
}

template <>
inline float LuaScript::lua_get(const std::string& variableName) {
    if(!lua_isnumber(L, -1)) {
      printError(variableName, "Not a number");
    }
    return (float)lua_tonumber(L, -1);
}

template <>
inline int LuaScript::lua_get(const std::string& variableName) {
    if(!lua_isnumber(L, -1)) {
      printError(variableName, "Not a number");
    }
    return (int)lua_tonumber(L, -1);
}

template <>
inline std::string LuaScript::lua_get(const std::string& variableName) {
    std::string s = "null";
    if(lua_isstring(L, -1)) {
      s = std::string(lua_tostring(L, -1));
    } else {
      printError(variableName, "Not a string");
    }
    return s;
}

That’s all! You can find complete source code with a little example here: https://github.com/EliasD/unnamed_lua_binder
(Note: you have to create Makefile or project yourself, don’t forget to add lua include/lib dirs to your project)

What to do next?

There are still lots of things you can do with Lua.
One useful feature is getting arrays of elements.

-- somefile.lua
some_array = { 1, 2, 3, 4, 5, 6}

 

// getIntVector will be implemented in part 2
std::vector v = script.getIntVector("some_array")

Another useful thing is getting list of table keys and calling Lua functions from C++. Part 2 will cover those two features. And here it is.

You can also call Lua functions from C++. And C++ functions from Lua. This will probably be in Part 3. And here it is in its full glory

Follow me on twitter @EliasDaler if you don’t want to miss it.

13 thoughts on “Using Lua with C++ (Part 1)

  1. Hi, very good job! Is there a way to get the table keys of a table that is inside an other table? E.g. here: get the table keys of “pos”?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s