Book Image

Lua Game Development Cookbook

By : Mario Kasuba, Mário Kašuba
Book Image

Lua Game Development Cookbook

By: Mario Kasuba, Mário Kašuba

Overview of this book

Table of Contents (16 chapters)
Lua Game Development Cookbook
Credits
About the Author
About the Reviewer
www.PacktPub.com
Preface
Index

Using Lua with existing projects written in C/C++


The Lua language provides a set of API functions for communication between C/C++ and the Lua programming language. You can use these functions in your C code after you include the lua.h header file or lua.hpp for C++ source code. You can rely on this set of functions, but sooner or later you'll see that there are certain usage patterns which can be used to simplify your C/C++ code or just simply make it more readable and error prone. This is especially true if you want to expose C++ objects and structures to the Lua language environment.

Fortunately, there is the Lutok2 library to help you with that. It consists of a set of header files and a freely based previous project called Lutok from Julio Merino.

This chapter will cover how to use Lutok2 library as the C++ API library for the Lua language and as a C++ class wrapper so that you can easily manage making your own extensions in the future. You'll also see that this library is used in many other libraries to cover access to multimedia devices from the Lua language.

Getting ready

The first thing you need to do before starting is to get the Lua binary library and the header files. After this step, you can download the Lutok2 header files and use them in your project.

On Windows:

  1. Download the Lua binary files from https://code.google.com/p/luaforwindows/downloads/list.

  2. Download and unzip the Lutok2 source code from https://github.com/soulik/lutok2/archive/master.zip or get a clone of the repository with the git command:

    git clone https://github.com/soulik/lutok2.git
    

On Linux:

  1. Use your package manager to install the Lua developer package or build the Lua binary library from source code at http://www.lua.org/ftp/lua-5.1.5.tar.gz.

  2. Download and unzip the Lutok2 source code from https://github.com/soulik/lutok2/archive/master.zip or get a clone of the repository with the git command:

    git clone https://github.com/soulik/lutok2.git
    

In both cases, you'll need the C++ compiler that can handle C++11 version of the standard C++ language. You can use clang or gcc compiler under the Unix-like environment, or the recent version of Microsoft Visual C++.

How to do it…

In the following steps, you'll see most common scenarios of using the Lua language in your C/C++ project. Most of the Lutok2 code samples are paired with equivalent Lua C API code so that you can see what the equivalent C code looks like without using the Lutok2 library.

Initializing the Lua state

This is how you initialize a Lua state with Lutok2:

#include <lutok2/lutok2.hpp>

int main(int argc, char ** argv){
  lutok2::State state;
  return 0;
}

This is how you do the same with Lua C API:

#include <lua.hpp>

int main(int argc, char ** argv){
  lua_State * state = luaL_newstate();
  return 0;
}

Creating a Lua module in C/C++

With Lutok2:

#include <lutok2/lutok2.hpp>

using namespace lutok2;
   
   /* A function to be exposed should always use following form:
    * Input argument : A reference to Lutok2 State object
    * Output variable: An integral number of return values
    */ 
int lua_example_myfunction(State & state){
  // C/C++ code to be invoked
  return 0;
}

extern "C" LUA_API int luaopen_example(lua_State * current_state){
  State * state = State(current_state);
  Module myModule;
  /* Expose lua_example_myfunction function in
   * Lua language environment.
   * Key value represents a function name in Lua.
   * Value should always be a function pointer.
   */
  myModule["myfunction"] = lua_example_myfunction;
  
  /* This module will return a Lua table
   * that exposes all functions listed in myModule.
   */
  state->stack->newTable();
  state->registerLib(myModule);
  return 1;
}

With Lua C API:

#include <lua.hpp>

static int lua_example_myfunction(lua_State * L){
  return 0;
}

static const struct luaL_Reg module[] = {
  {"myfunction", lua_example_myfunction},
  {NULL, NULL}
};

extern "C" LUA_API int luaopen_example(lua_State * L){
  lua_newtable(L);
  luaL_register (L, NULL, module);
  return 1;
}

Passing variables from C/C++ into the Lua environment

With Lutok:

int lua_example_myfunction(State & state){
  void * userData = (void*)123456789;
  Stack * stack = state->stack;

  stack->push<bool>(true);
  stack->push<int>(12345);
  stack->push<LUA_NUMBER>(12345.6789);
  stack->push<const std::string &>("A text");
  stack->push<void*>(userData);
  stack->newTable();
    stack.setField<bool>("boolean", false);
    stack.setField<int>("integer", (int)12345);
    stack.setField<LUA_NUMBER>("number", (lua_Number)12345.6789);
    stack.setField<const std::string &>("string", "A text");
    stack.setfield<void *>("userData", userData);
  return 6;
}

With Lua C API:

static int lua_example_myfunction(lua_State * L){
  void * userData = (void*)123456789;
  lua_pushboolean(L, (int)true);
  lua_pushinteger(L, 12345);
  lua_pushnumber(L, 12345.6789);
  lua_pushstring(L, "A text");
  lua_pushlightuserdata(L, userData);
  lua_newtable(L);
    lua_pushboolean(L, (int)false);
    lua_setfield(L, -2, "boolean");
    lua_pushinteger(L, 12345);
    lua_setfield(L, -2, "integer");
    lua_pushnumber(L, 12345.6789);
    lua_setfield(L, -2, "number");
    lua_pushstring(L, "A text");
    lua_setfield(L, -2, "A text");
    lua_pushlightuserdata(L, userData);
    lua_setfield(L, -2, "userData");
  return 6;
}

Passing variables from the Lua environment to C/C++

To get a variable from the Lua environment, you need to call the corresponding Lua C API function lua_to*, for example, lua_tointeger(L, index). You can use the corresponding version of the luaL_check*(L, index) function to obtain a value with additional checks for the correct data type.

Lutok2 provides a similar mechanism where you can use a template form of the to function:

state.stack->to<DATA_TYPE_NAME>(index);

The part DATA_TYPE_NAME presents a name of the target data type and the index value is a position of the variable in the registry. The first function parameter is at index 1. The second one is at index 2, and so on.

The example code for Lutok2 C++ API is as follows:

int integerValue = state.stack->to<int>(1);

The example code for the plain Lua C API is as follows:

int integerValue = luaL_checkinteger(L, 1);

Making the C++ class accessible from Lua with Lutok2

Let's assume that your class is defined in the ExampleObject.hpp header file. In this minimal case, the class contains one numerical property and one member function that returns a string value. The header file will contain the following lines of code:

#ifndef EXAMPLE_OBJECT_H
#define EXAMPLE_OBJECT_H

#include <string>

class ExampleObject {
public:
  int x;
  inline const std::string helloWorld(){
    return "Hello world";
  }
};
#endif

Now, you'll need to create a class declaration and implementation of the C++ class wrapper. You should always use a reasonable name for the wrapper class so that it is clear what class is actually handled. This example uses the name LuaExampleObject.

The header file will be called LuaExampleObject.hpp and it will contain a declaration for the LuaExampleObject class. This header file contains the following lines:

#ifndef LUA_EXAMPLE_OBJECT_H
#define LUA_EXAMPLE_OBJECT_H

#include "ExampleObject.hpp"

class LuaExampleObject : public Object<ExampleObject> {
public:
  explicit LuaExampleObject(State * state) :
    Object<ExampleObject>(state){
    /* Properties handle access to member variables
     * with getter and setter functions
     */
    LUTOK_PROPERTY("x", &LuaExampleObject::getX, &LuaExampleObject::setX);
    // Methods allow you to call member functions
    LUTOK_METHOD("helloWorld",
      &LuaExampleObject::helloWorld);
  }
  
  ExampleObject * constructor(State & state, bool & managed);
  void destructor(State & state, ExampleObject * object);
  int getX(State & state, ExampleObject * object);
  int setX(State & state, ExampleObject * object);
  int helloWorld(State & state, ExampleObject * object);
};
#endif

The implementation file will be called LuaExampleObject.cpp and it will consist of the main code that handles access to member variables and functions:

#include <lutok2/lutok2.hpp>
using namespace lutok2;

#include "LuaExampleObject.hpp"

ExampleObject * LuaExampleObject::constructor(State & state, bool & managed){
  return new ExampleObject;
}

void LuaExampleObject::destructor(State & state, ExampleObject * object){
  delete object;
}

int LuaExampleObject::getX(State & state, ExampleObject * object){
  state.stack->push<int>(object->x);
  return 1;
}

int LuaExampleObject::setX(State & state, ExampleObject * object){
  if (state.stack->is<LUA_TNUMBER>(1)){
    object->x = state.stack->to<int>(1);
  }
  return 0;
}

int LuaExampleObject::helloWorld(State & state, ExampleObject * object){
  state.stack->push<const std::string &>(object->helloWorld());
  return 1;
}

Furthermore, to finish while process, you'll need to register a Lua interface. This is usually done during the module initialization routine. The following code sample shows how to prepare a C++ source file with module initialization. This module will return a table that is used in the Lua script as an interface to create an instance of ExampleObject. Note that this module doesn't register itself in the global variable space. This is currently the preferred way of using modules in the Lua language.

The main module file will be called ExampleModule.cpp and will consist of the following lines:

#include <lutok2/lutok2.hpp>
using namespace lutok2;

#include "LuaExampleObject.hpp"

extern "C" LUA_API int luaopen_example(lua_State * current_state){
  // State object is freed automatically after Lua state closes!
  State * state = new State(current_state);
  Stack * stack = state->stack;

  // Prepare main module interface table
  stack->newTable();
  
  /* Object interface registration always returns Lua function
   * with object constructor.
   */
  state->registerInterface<LuaExampleObject>(
    "LuaExample_ExampleObject");
  /* A new instance of ExampleObject can be obtained by
   * calling ExampleObject function from main interface table.
   */
  stack->setField("ExampleObject");
  return 1;
}

Now, you can include all source code in your C++ project in your favorite IDE and compile them into the binary library.

Don't forget to move the resulting binary library with the module to the working directory with your Lua script or anywhere the Lua interpreter can find it.

Finally, your Lua script will look like this:

local exampleModule = require 'Example'

local example_object = exampleModule.ExampleObject()
example_object.x = 5
print(example_object.x)
print(example_object.helloWorld())

This will create a new ExampleObject instance, sets and gets content of member variable; in the final step, it calls the member function that returns a string value.

How it works…

The Lutok2 library contains most of the commonly used Lua C API functions while sanitizing access to class objects.

The core of this library is divided into two sections:

  • Functions that manage Lua states and Lua modules

  • Functions that manage Lua stack content

All Lutok2 classes are encapsulated in Lutok2 namespace, so there should be no naming conflicts with other libraries. It contains automatic management of the Lua state for these use cases:

  • Lua modules

  • A standalone application that creates a Lua state during runtime

Class wrapper objects use the template form of lutok2::Object as a generic base class that handles most of the work in order to register the class interface in the current Lua environment. The template parameter is mandatory and specifies what class will be wrapped.

lutok2::Object<CLASS_NAME>

The constructor of the class wrapper must always be present, as it defines what methods or properties will be available in the Lua environment. It's called automatically during interface registration. The body of the constructor usually consists of several macros that specify the class members. There are two macros you can use, which are as follows:

  • LUTOK_METHOD(LUA_NAME, FUNCTION)

  • LUTOK_PROPERTY(LUA_NAME, FUNCTION, FUNCTION)

The member functions are defined by their name as a string value and a function pointer. The member variables use a similar notation while using two function pointers. The first one points to the getter function that returns a value of the member variable. The second one points to the setter function that sets the value of the member variable. If you don't want to allow the member variable modification, you can use the pointer to the nullMethod function instead of your own setter function. This will effectively block any changes to the member variable from the Lua script.

Another part of the C++ class wrapper is the functions that manage object instance creation and destruction—constructor and destructor. The constructor method is called when you actually call the object constructor in the Lua environment and it gives you space to actually create a new object instance. There's a reference to the managed argument, which you can change to the false value if the object instance is managed elsewhere. This will also cause that destructor method won't be called upon garbage collection in the Lua environment:

CLASS_NAME * constructor(State & state, bool & managed);

The destructor method is called when the object is freed in the Lua environment during garbage collection. This is the place where you can clean up and free up the object's instance:

  void destructor(State & state, CLASS_NAME * object);