Using Lua with C++ (Part 1)

You can view full source code of this article here

This is VERY outdated, please refer to this version as the latest and up-to-date guide:

https://edw.is/using-lua-with-cpp/

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

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.

20 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”?

  2. There is an bug with the formatting. Rather than having the “Greater than” and “Less than” signs displayed, “&>” is displayed for the “Greater than” sign and “&<” is displayed for the “Less than” sign. It is fine on the source code on GitHub so it isn’t an issue on my end. I have seen a few website like this recently. Not all websites though. Just a few of them. Anyhow, you should check out that freaky bug…

      • yeah it’s super annoying. I complained to wordpress devs about this stuff. I think they have since fixed some bugs in encoding, but it still has problem.
        These days I usually write my blog posts in a gist and copy/paste the generated HTML into WordPress, or write the post in a plain .txt file in OneDrive. Basically, I avoid WordPress’ actual editor.

Leave a comment