Modules and Namespaces in Lua

Lua does a lot of work with globals. I suspect some of this comes down to performance (it’s more efficient to tweak global state than copy values in and out of function calls) some to the language’s obsession with minimalism (which also might come down to performance) and its nature as a hosted language.

Regardless, if I had a bunch of utility functions for manipulating rectangles, the most natural way to do this in Lua would probably be to define the all at the top level of my program. In LÖVE, that means in main.lua:

-- main.lua
function expand(by, x, y, width, height)
  return x-by, y-by, width+(by*2), height+(by*2)
end

function contract(by, x, y, width, height)
  return expand(-by, x, y, width, height)
end

function love.draw()
    local screen = {0,0,400,240}
    love.graphics.setColor({0,0,0})
    love.graphics.rectangle(
      "fill", 
      contract(20, unpack(screen))
    )
end

In a way, this all feels very “capital-F” Functional, and a great many are happy with this solution and should stop reading here. But it drives me nuts for a couple of reasons:

  1. Being global, these functions are polluting my namespace. What if I wanted to have a variable to represent a legal document my character signed. I couldn’t call it contract without shadowing my global function — a recipe for future heisenbugs.
  2. I’m loading code I may not use. Sure, insetting a rectangle by a given amount seems useful now, but what if requirements change and I don’t need it anymore. Will I ever audit my gigantic list of utility functions and prune the dead code? Will it just take up RAM and bloat my footprint? 1

We can address the first problem pretty easily. Just create an empty table and assign our functions to that:2

-- main.lua
Util = {}
Util.rect = {}
function Util.rect.expand(by, x, y, width, height)
  return x-by, y-by, width+(by*2), height+(by*2)
end

function Util.rect.contract(by, x, y, width, height)
  return expand(-by, x, y, width, height)
end

function love.draw()
  local screen = {0,0,400,240}
  love.graphics.setColor({0,0,0})
  love.graphics.rectangle(
    "fill", 
    Util.rect.contract(20, unpack(screen))
  )
end

But how do we make sure we only load code we need?

So far as I know, there’s no sort of dynamic “autoload” linking or anything in Lua. But it does have the ability to pars and eval other Lua files on demand via the built-in dofile function. So we could imagine putting all our rectangle-related utilities in a file called rect.lua that just returned a table mapping names to functions:

-- rect.lua
return {
  expand = function(by, x, y, width, height)
    return x-by, y-by, width+(by*2), height+(by*2)
  end,
  contract = function(by, x, y, width, height)
    return x+by, y+by, width-(by*2), height-(by*2)
  end
}

We could then load it in main like so:

-- main.lua
Util = {}
Util.rect = dofile("rect.lua")

Which helps us keep things organized… but still doesn’t fix “load it if we need it” problem. We’re still loading this globally in main every time the program is run.

We could instead load it into a local variable of the files that need it:

-- myFileUsingRects.lua
local MyRectUtils = doFile("rect.lua")

function insetFromScreen() 
  local screen = {0,0,400,240}
  return MyRectUtils.contract(10, unpack(screen))
end

But if more than one file uses rect.lua we’ve actually compounded the problem, loading multiple copies of these functions into RAM.3

To solve this, we’re going to take a tip from C’s preprocessor and do the simplest thing that can work:

-- main.lua
Util = {}
function Util.import(name) 
  if Util[name] == nil then 
    Util[name] = dofile(name)
  end
end
-- myFileUsingRects.lua
Util.import "rect"

function insetFromScreen() 
  local screen = {0,0,400,240}
  return Util.rect.contract(10, unpack(screen))
end

This is a good tradeoff between concerns, IMHO. A file is never loaded unless explicitly imported, and even if imported many times, it’s only ever loaded once.

While we’re adding base functions to our Util namespace, here’s a little modification I’ve found usefule:

-- main.lua
function Util.define(f)
  local t = {}
  f(t)
  return t
end

This lets us define our utilities by modifying a table passed to a function rather than returning the table itself. This way we have access to anything defined in the function — local variables and functions that we can use “private” helper methods, for example. We also have access to other “public” values we’ve already defined — letting us go back to defining contract in terms of expand:

-- rect.lua
return Util.define{function(export) 
  local function secret() 
    print("only callable within this file")
  end  

  function export.expand(by, x, y, width, height)
    return x-by, y-by, width+(by*2), height+(by*2)
  end

  function export.contract(by, x, y, width, height)
    return export.expand(-by, x, y, width, height)
  end
end}

Next time we’ll see how we can also use this mechanism to pass initialization parameters to our module. Very useful when using this strategy to create stand-alone objects like views…


1: I don’t actually know how Lua handles function lookup. But given its minimalism and reliance on tables, I assume there’s a “global table” that holds every global variable or function we define. So even unused functions, if assigned to this table, would take up space. ↩︎

2: I’ve seen a lot of disagreement as to whether global namespaces like this ought to be capitalized or not. Lua namespaces like table are not. And LÖVE’s love namespace is not. But many linters warn of all-lower-case globals.

In most other languages I’m familiar with, these would be capitalized. And it feels like there’s semantic value in distinguishing global containers like this. So I’m going with capitalized. ↩︎

3: Assuming Lua doesn’t do any sort of caching in this regard. ↩︎

Game Plan

So for all sorts of reasons, Four Star has long been kicking around the idea of making a video game. We have a good cross-section of the necessary skills and it's probably the one genre outside of comics we’re all equally obsessed with. But we’re also commonly tied up with our individual (and more conventional) projects. Also, when our schedules align, we tend to fill the gap with… playing video games. Research, I guess?

Anyway, Sean and I got moderately serious about it a year or two ago, actually putting down ideas about plots and mechanics. He had some sketches, I had a draft of an engine.1

But things never really gelled. It’s probably no surprise that anything Four Star does in the game space would have to have to be very story driven, and to us that meant an RPG. But two forces were kind of fighting against us:

First, RPGs were trending large. No one wanted to play a game that was less than 20 hours of content, and we couldn’t see investing in the amount of writing, illustrating, programming, and scripting necessary to create an RPG of that scope. Especially in our first outing!

Second, the best platform for the tight, polished, “casual”-type game we were interested in making was also the one with the worst controls for an RPG (mobile phones and touch screens, respectively). I think Sean and I were both feeling our Chrono Trigger roots, and adapting, say, “walking around the map” to tap-to-move controls just felt bad.2

And then everyone was busy again, and once again our game plans got put on the shelf.

A Date for Play

I’ve been really excited since Day 1 of the Playdate announcement. I'm a huge Teenage Engineering fan (my poor, abused OP-1 ranks high on my “list of things I’d save first in a fire”), and I’ve owned every application Panic has ever made over the last… wow, it’s been 20 years?!3

But it wasn’t until Steven Frank’s little developer stream on Twitch4 that I thought about making a game for it. Once I did a few things became clear:

  1. The whole M.O. of the Playdate is to present small, polished games delivered weekly. The scope we had in mind for our old RPG would fit really well.
  2. The Playdate has a D-pad! And buttons! (and a crank, of course, but that’s more a frosting-on-the-cake-type situation)
  3. The 400x240 1-bit display both keeps things simple and offers exciting design challenges for Sean.

All of a sudden, I’m really excited about making games again. And as a way to keep myself excited (and motivated) I’m going to blog about it.

LÖVE and the Moon over the C

Looking into it, the Playdate SDK is C at its core. I feel good about that. I have decades of C experience. But it also has a Lua overlay, and Frank suggests in his live stream that developers give it a serious shot before dropping down to a lower abstraction.

I know a lot less about Lua. So I want to dive in to start developing an intuitive sense for its idioms and whatnot. Step one: the really excellent books written by the language’s creator, Roberto Ierusalimschy. The fourth edition of Programming in Lua is really something. Two thumbs way up 👍 👍

Step two: start writing some simple game constructs in Lua to get a feel for how these things work. What constructs? Another favorite book is Bob Nystrom’s Game Programming Patterns. It provides plenty of inspiration along with explanation.

But the Playdate SDK hasn’t been released, yet. The documentation isn’t even available. How can I start building game logic without having to reinvent a wheel I’ll just throw away once the Playdate hits the street?

All I need is LÖVE, a funky little 2D game host for Lua that seems like it provides a lot of the same primitives a Playdate SDK must — at least superficially.

So that’s the plan. Write two-color, 400x240 pixel games in LÖVE to develop a set of patterns for small, portable game development in Lua. Then when the Playdate drops, I’ll be able to hit the ground running.

Wish me luck!


1: Codename: “Highschool”, for reason I don’t remember now. ↩︎

2: I actually made a really cool variation of the engine that kept the character centered in the window and let you drag to scroll the map. When the character hit a wall or obstacle, it had that elastic snap, like scrolling past the end of a list. Which was really cool and I might use for something different later, but still felt really unRPG like. ↩︎

3: Nova? Soon? Please? I need it! ↩︎

4: This is gone now :'( It had a lot of good information in there and now I wish I’d saved it. ↩︎