Thursday 24 January 2013

Lua & Lanarts

So lately I have been making some significant progress on the Lua side of Lanarts. Here's some summary!

LuaWrap, my pet Lua binder

After much frustration with existing Lua binding solutions (manual bindings, LuaBind, SimpleLuaBinder) I decided like any arrogant programmer to simply write my own. Of course, since I wrote it from the ground up, it suits my use-case quite well.

It is fairly small (24 short source files), flexible, and does not try to control your Lua environment in any way like SLB does. As well, although some performance was traded for ease-of-use, it provides an easy to use interface to eg Lua tables that suffers no performance penalties (assuming sufficient inlining). Here's an example of binding a global variable that should compile to the same code as if written by hand:

void bind_globals(lua_State* L) {
     LuaSpecialValue globals = luawrap::globals(L);

     globals["foo"] = "bar";
     globals["baz"] = 1;
}

LuaWrap is located in the lanarts source-tree under src/luawrap/. It does not depend on any libraries other than Lua.

Rewriting the main loop

So I wanted to move more towards having Lanarts as a game engine controlled by Lua. Now I definitely treat this as the compromise that it is -- it is perfectly possible to write a game in Lua, and that is not the goal. The plan is to allow people to modify the game very flexible in Lua without worrying about the networking aspect (except possibly for very heavy modification). The hope is that will inspire experimentation with the game mechanics.

So, as a start to this direction, I rewrote the main loop in Lua. Here's a look at the inside of the loop!

function main()
    net.sync_message_consume()

    local timer = timer_create()

    if not steponly then
        game.draw()
    end

    if not game.step() then 
        return false 
    end

    if not game.input_handle() then 
        return false 
    end

    local surplus = settings.time_per_step - timer:get_milliseconds()

    if surplus > 0 then 
        game.wait(surplus) 
    end

    return true
end

Object-Oriented Lua!

Figuring I'd continue this top-down approach, I have begun working on porting some of the menu code in Lua. For the first time, this actually meant I wanted to write something object-oriented on the Lua side! Now, OOP isn't a silver bullet, and I always like to weigh the pros & cons of other solutions -- but for graphical interfaces I think object orientation makes a lot of sense.

The first task I had was to decide on an object model. Now I think this is a really cool part of Lua -- it does not impose an object model on you. I settled on a very simple syntax, a single function 'newtype' returns a table suitable for use a 'type'. After which you simply add methods to the type. A special method, the 'init' method, is used to form a 'create' function. Here's what this looks like in action!

import "utils.lua" -- imports 'do_nothing'

TextLabel = newtype()

function TextLabel:init(font, options, text)
    self.font = font
    self.options = options
    self.text = text
end

TextLabel.step = do_nothing

function TextLabel:draw(xy)
    self.font:draw( self.options, xy, self.text )
end

-- Example usage
mylabel = TextLabel.create(font, options, text)

Some things notable here that highlight features of Lua:
  1. For people coming from a Python background, 'import' seems like a Lua keyword. Actually, it's a function that is being called with a string literal. Lua allows this syntax for one-argument table & string literal functions.
  2. 'function TextLabel:draw(xy)' is equivalent to 'function TextLabel.draw(self, xy)'. The colon operator is nothing more than syntax sugar for 'pass self as first parameter'.
  3. Lua silently (for better, or for worse!) ignores extra parameters, allowing a generic 'do_nothing' function like the one used.
 Anyway, that's all for today! Just wanted to give an update as I take a break from gameplay-oriented updates, and the dreaded mess of implementing client-side prediction. Stay tuned.