Play With Lua!

Raw keyboard input

without comments

I’ve wanted to do a small case study of making an extension for a long time now. I’m writing a Roguelike game in Lua, so I had a need for one, and knocked it together last night: a C extension to provide Lua with a function to read one key from the keyboard.

What we want to do

Reading one key from stdin is easy, right? io.read() pulls characters from stdin, and you can pass it a numeric argument to say how many characters you want to read, so io.read(1) returns one character. There are two problems with this: special characters and line buffering.

See, that only works for normal printable characters. Things like the arrow keys you can’t read with io.read(). Arrow keys are pretty important for a Roguelike game, or, well, any interface at all. So that’s strike one.

The bigger problem is that the console buffers input until there’s a complete line. Normally this is really useful: most programs (like, say, the Lua REPL) prefer input one line at a time, but a game probably prefers one keystroke at a time.

What we want is a function, let’s call it getch for get-character, that waits until the next key is pressed and then returns what key that was.

Curses

There’s a really common C library that does things like this, called Curses. It allows you a lot more control over the console than you normally get, with functions that let you move the cursor around on the screen, change the text color, and read keystrokes from the keyboard. So, we want to make an extension that links against Curses and exports that one function to Lua. We’ll call our extension kb, so we will call kb.getch() in Lua to read one key.

Creating the library

Extensions are loaded by Lua at runtime. What we’ll do is, make a shared library and put it in the same directory as our Lua script. require will know how to load it, at runtime, because the path to it will be in package.cpath.

So, let’s get started. We want to include the headers for Curses, as well as the Lua ones. Then, in order to load our library called “kb”, Lua will call a function called luaopen_kb, passing it a Lua state:

#include <ncurses.h>
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
 
int luaopen_kb(lua_State *L){
    return 0;
}

That function is going to be responsible for creating the kb table and putting functions in it pointing to C functions. Right now though, let’s worry about the other thing it has to do: initialize Curses.

Starting up Curses

Curses does a lot more than keyboard input; it messes with the way the console works in a profound way. So, in order for things like print to work more-or-less as expected, we want to not be in Curses most of the time. So what we’ll do is, start up Curses, save its state, and then leave curses mode. Whet we call getch we’ll re-enter curses mode, get one character, then leave it again before we return.

In order to start up Curses to read characters, we do this in luaopen_kb:

initscr(); /* Start curses */
raw(); /* Turn off line buffering */
keypad(stdscr, TRUE); /* Grab ALL kbd input, arrow-keys included */
refresh(); /* Store screen state so endwin works */
endwin(); /* Leave curses mode */

The function to export

Lua has a very easy-to-use system for talking to C. Every function it calls takes a Lua state as a parameter, which contains a stack of values. We push values on to the stack, then return the number of values from the top of the stack that are return values.

So we’re going to write this function:

int getch_wrapper(lua_State *L);

It takes a Lua state, calls Curses’ getch, pushes the character it gets on to the stack, and then returns 1:

int getch_wrapper(lua_State *L){
    reset_prog_mode(); /* Get back into curses */
    lua_pushnumber(L, getch()); /* Grab a char and push it */
    endwin(); /* Get out of curses again */
    return 1; /* Return 1 value to Lua */
}

Exporting the function

Lua’s C API has functions to export C functions, create new tables, and put values in them, which we could use to essentially do this:

kb = {}
kb.getch = [[some C function]]

But the case of making and exporting a table of C functions is so common that there’s a convenience function to do it: luaL_openlib. We make a list of structs, that map a Lua name to a C function:

luaL_Reg fns[] = {
    {"getch", getch_wrapper},
    {NULL, NULL}};

Then we call openlib to make a Lua table out of that mapping:

luaL_openlib(L, "kb", fns, 0);

That all goes inside luaopen_kb.

Building and using the extension

(I’m going to go through how to do this on the Mac; Linux and Windows/MinGW are almost the same. Detailed instructions for other OSes are here)

We want to build a shared library out of this, called kb.so. It needs to link against Curses, of course, so we could just do this:

gcc -shared -o kb.so kb.c -lncurses -llua

The problem with that is, it’s bad form to link against Lua itself. Technically, this same library could be loaded by a different version of Lua, or LuaJIT. Lua builds as a static library, so if we link against it, we’ll actually import the code from liblua.a, and have to rebuild the module to load it in any other version of Lua.

So instead we want to tell the linker, “any symbols you don’t find, just try to load them dynamically.” This will work because the library is going to be loaded with dlopen into a process that already has all those Lua API symbols defined (because it’s a copy of the Lua interpreter). On Mac, you do this:

gcc -shared -o kb.so kb.c -undefined dynamic_lookup -lncurses

The downside is that the compiler now won’t tell us until load time if we call a function that doesn’t exist. But that’s not a big deal; we can just build it normally until we’re sure it works, and then build a release version that way.

Trying it out

Now, with kb.so in the same directory, open a Lua console and run this:

require 'kb'
print(kb.getch())

It will pause for you to press a key, and then print out the ASCII code of whatever you pressed.

Doing this is really handy for adding functionality to Lua, of course (there’s a C or C++ library for pretty much everything), but it can also be a good way to make your code run faster. If you have some task that’s heavily CPU-bound, you can write it in C and use an extension module to embed it into your Lua program, without having to write the rest of the program in C.

The complete code for this is here.

Written by randrews

April 22nd, 2012 at 3:43 pm

Posted in Uncategorized