A usual pattern in 2D games is the use of tilemaps. In Löve you can implement those in a variety of ways. In this article I want to compare tilemap implementations in Löve 0.9.1 using spritebatches.
I will use this to create an ascii display.

Timing
I will use the following code to measure the performance. It is an extended example from kikitos time function. It uses os.clock to measure the time before the execution and again after the execution.
time.lua:
local last
return function(title, f, n)
if title == "clear" then last = nil; return end
collectgarbage()
local n = n or 100000
local startTime = os.clock()
for i = 1, n do
f()
end
local endTime = os.clock()
local delta = endTime - startTime
if not last then last = delta end
print(title, delta, (delta) - last)
last = delta
end
Spritebatch
We will generate a spritebatch with a tilesheet image(aka texture atlas) and use the spritebatches add/set method to change the content. The spritebatch will have NxM size. I used Alloy_curses_12x12 from the dwarf fortress tileset repository.
local img = love.graphics.newImage("Alloy_curses_12x12.png")
local spritebatch = love.graphics.newSpriteBatch(img, 87*25, "stream")
local quads = {}
for x = 0, 255 do
local i,j = 1+(x%16), math.floor(x/16)
quads[x] = love.graphics.newQuad(i*12, j*12, 12, 12, img:getWidth(), img:getHeight())
end
We can also use the bind and unbind function on the spritebatch to bind the spritebatch into memory before we loop through the whole display grid.
Luajit Tables
We need two arrays: one to store the id and one to store the ascii value of the cell. Map is our display grid and idmap will store the id returned by adding the cells.
local map = {}
local idmap = {}
spritebatch:bind()
for i=0,86 do
map[i] = {}
idmap[i] = {}
for j=0,24 do
map[i][j] = 68
spritebatch:setColor(255,255,255)
idmap[i][j] = spritebatch:add(quads[map[i][j]], i*12, j*12)
end
end
spritebatch:unbind()
local random = math.random
time("spritebatch, lua table", function()
spritebatch:bind()
for i=0,86 do
for j=0,24 do
map[i][j] = random(255)
spritebatch:setColor(0,255,255)
spritebatch:set(idmap[i][j], quads[map[i][j]], i*12, j*12)
end
end
spritebatch:unbind()
love.graphics.setColor(255,255,255,255)
love.graphics.draw(spritebatch)
end, 1000)
FFI Arrays
We use the ffi.new functions to generate a 2 Dimensional uint8_t array for the character display grid and uint16_t for the id grid.
local ffi = require "ffi"
local map = ffi.new("uint8_t[87][25]")
local idmap = ffi.new("uint16_t[87][25]")
spritebatch:bind()
for i=0,86 do
for j=0,24 do
map[i][j] = 68
spritebatch:setColor(255,255,255)
idmap[i][j] = spritebatch:add(quads[map[i][j]], i*12, j*12)
end
end
spritebatch:unbind()
local time = require "time"
time("spritebatch, ffi array", function()
local random = math.random
spritebatch:bind()
for i=0,86 do
for j=0,24 do
map[i][j] = random(255)
spritebatch:setColor(0,255,255)
spritebatch:set(idmap[i][j], quads[map[i][j]], i*12, j*12)
end
end
spritebatch:unbind()
love.graphics.setColor(255,255,255,255)
love.graphics.draw(spritebatch)
end, 1000)
Performance
Using Luajit tables is a lot faster than using FFI arrays:
spritebatch, ffi array 2.13035 0 spritebatch, lua table 0.577013 -1.553337

FFI Arrays are a lot slower, so you should stick to tables if you want to draw tiles fast.
Slime explains: “The FFI array is probably slower because the loop it’s used in can’t be compiled by the JIT (since C API functions – all of LÖVE’s API, for example – prevent JIT compilation for the block of code they’re called in.)”
One more thing for spritebatch
It’s probably a good idea to only use spritebatch:set when something has changed. You can do this by having two map tables. Check if the map table is different than the cache table and only then set the spritebatch tile.
spritebatch:bind()
for i=0,86 do
for j=0,24 do
if map[i][j] ~= old[i][j] then
old[i][j] = map[i][j]
spritebatch:setColor(255,255,255)
spritebatch:set(smap[i][j], quads[map[i][j]], i*12, j*12)
end
end
end
spritebatch:unbind()
I created an example lua file that can be used as a very simple base to render roguelike ascii games:gist. But of course this can be used for any other tile based game too.