Add ART loader for the LuaJIT BUILD struct loader module, 2 more examples.

The map iterator now has init/finish capability, making it possible to
write scripts that aggregate data over multiple map files.  One such example
calculates some statistics, the other loads art metadata and looks for
red walls with non-pow2 ysize tiles.

git-svn-id: https://svn.eduke32.com/eduke32@2814 1a8010ca-5511-0410-912e-c29ae57300e0
This commit is contained in:
helixhorned 2012-07-08 21:47:11 +00:00
parent 6b0f6176f6
commit 3697842bc5
5 changed files with 370 additions and 16 deletions

View file

@ -1,14 +1,14 @@
-- Loaders for various BUILD structures for LuaJIT -- Loaders for various BUILD structures for LuaJIT
-- TODO: bound-checking, load ART
local ffi = require "ffi" local ffi = require "ffi"
local io = require "io" local io = require "io"
local error = error
local assert = assert local assert = assert
local print = print local print = print
local setmetatable = setmetatable
module(...) module(...)
@ -62,6 +62,8 @@ local MAX =
SECTORS = { [7]=1024, [8]=4096, [9]=4096 }, SECTORS = { [7]=1024, [8]=4096, [9]=4096 },
WALLS = { [7]=8192, [8]=16384, [9]=16384 }, WALLS = { [7]=8192, [8]=16384, [9]=16384 },
SPRITES = { [7]=4096, [8]=16384, [9]=16384 }, SPRITES = { [7]=4096, [8]=16384, [9]=16384 },
TILES = 30720,
} }
local function doread(fh, basectype, numelts) local function doread(fh, basectype, numelts)
@ -85,6 +87,23 @@ local function doread(fh, basectype, numelts)
return cd return cd
end end
local function set_secwalspr_mt(structar, maxidx)
local mt = {
__index = function(tab, idx)
if (idx < 0 or idx >= maxidx) then
error("Invalid structure array read access", 2)
end
return structar[idx]
end,
__newindex = function(tab, idx, newval)
error('cannot write directly to structure array', 2)
end,
}
return setmetatable({}, mt)
end
--== LOADBOARD ==-- --== LOADBOARD ==--
-- returns: -- returns:
-- on failure, nil, errmsg -- on failure, nil, errmsg
@ -133,6 +152,9 @@ function loadboard(filename)
-- sectors -- sectors
map.numsectors = cd[2] map.numsectors = cd[2]
if (map.numsectors == nil) then
return nil, "Couldn't read number of sectors"
end
if (map.numsectors <= 0 or map.numsectors > MAX.SECTORS[map.version]) then if (map.numsectors <= 0 or map.numsectors > MAX.SECTORS[map.version]) then
fh:close() fh:close()
return nil, "Invalid number of sectors" return nil, "Invalid number of sectors"
@ -145,6 +167,9 @@ function loadboard(filename)
-- walls -- walls
cd = doread(fh, "int16_t", 1) cd = doread(fh, "int16_t", 1)
if (cd == nil) then
return nil, "Couldn't read number of walls"
end
map.numwalls = cd[0] map.numwalls = cd[0]
if (map.numwalls <= 0 or map.numwalls > MAX.WALLS[map.version]) then if (map.numwalls <= 0 or map.numwalls > MAX.WALLS[map.version]) then
fh:close() fh:close()
@ -158,6 +183,9 @@ function loadboard(filename)
-- sprites -- sprites
cd = doread(fh, "int16_t", 1) cd = doread(fh, "int16_t", 1)
if (cd == nil) then
return nil, "Couldn't read number of sprites"
end
map.numsprites = cd[0] map.numsprites = cd[0]
if (map.numsprites < 0 or map.numsprites > MAX.SPRITES[map.version]) then if (map.numsprites < 0 or map.numsprites > MAX.SPRITES[map.version]) then
fh:close() fh:close()
@ -169,7 +197,100 @@ function loadboard(filename)
return nil, "Couldn't read sprites" return nil, "Couldn't read sprites"
end end
map.sector = set_secwalspr_mt(map.sector, map.numsectors)
map.wall = set_secwalspr_mt(map.wall, map.numwalls)
map.sprite = set_secwalspr_mt(map.sprite, map.numsprites)
-- done -- done
fh:close() fh:close()
return map return map
end end
local function set_sizarray_mt(sizar)
local mt = {
__index = function(tab, idx)
if (idx < 0 or idx >= MAX.TILES) then
error("Invalid tile size array read access", 2)
end
return sizar[idx]
end,
__newindex = function(tab, idx, newval)
if (idx < 0 or idx >= MAX.TILES) then
error("Invalid tile size array write access", 2)
end
sizar[idx] = newval
end,
}
return setmetatable({}, mt)
end
--== LOADARTS (currently tilesizx and tilesizy only) ==--
-- filenames: a table with ART file names
-- returns:
-- on failure, nil, errmsg
-- on success, a table
-- {
-- sizx = <cdata (array of length MAXTILES)>
-- sizy = <cdata (array of length MAXTILES)>
-- }
function loadarts(filenames)
local tile = {
sizx = ffi.new("int16_t [?]", MAX.TILES),
sizy = ffi.new("int16_t [?]", MAX.TILES),
}
for fni=1,#filenames do
local fn = filenames[fni]
local fh, errmsg = io.open(fn)
if (fh==nil) then
return nil, errmsg
end
local cd = doread(fh, "int32_t", 4)
-- artversion, numtiles, localtilestart, localtileend
if (cd==nil) then
fh:close()
return nil, fn..": Couldn't read header"
end
local localtilestart = cd[2]
local numtileshere = cd[3]-localtilestart
if (numtileshere < 0 or localtilestart+numtileshere >= MAX.TILES) then
fh:close()
return nil, fn..": Invalid tile start/end"
end
if (numtileshere==0) then
fh:close()
else
-- X sizes
cd = doread(fh, "int16_t", numtileshere)
if (cd == nil) then
fh:close()
return nil, fn..": Couldn't read tilesizx array"
end
ffi.copy(tile.sizx+localtilestart, cd, numtileshere*2)
-- Y sizes
cd = doread(fh, "int16_t", numtileshere)
if (cd == nil) then
fh:close()
return nil, fn..": Couldn't read tilesizy array"
end
ffi.copy(tile.sizy+localtilestart, cd, numtileshere*2)
end
end
tile.sizx = set_sizarray_mt(tile.sizx)
tile.sizy = set_sizarray_mt(tile.sizy)
return tile
end

View file

@ -4,23 +4,35 @@
-- The first cmdline arg is a name of a lua file (may be sans .lua) which must -- The first cmdline arg is a name of a lua file (may be sans .lua) which must
-- be a module and is `require'd into this script, e.g. "stats" or "stats.lua". -- be a module and is `require'd into this script, e.g. "stats" or "stats.lua".
-- Then, for each 2nd and following argument, if map loading succeeds, -- First, a key named .init is looked up in the loaded module and if it exists,
-- .success(map, filename) is run, otherwise .failure(filename, errmsg) -- -- it is run like .init(arg) (thus allowing it to parse the command-line
-- these must be functions in the loaded module. -- arguments and then potentially remove the ones it used).
-- If .init returns non-nil, this script aborts.
-- Otherwise, for each 2nd and following argument, if map loading succeeds,
-- .success(map, filename) is run, otherwise
-- .failure(filename, errmsg) is run if that key exists, or a standard error
-- message is printed to stderr.
-- Finally, if there is a .finish field in the module, it is run with no args.
-- TODO: aggregate batch mode
if (#arg < 1) then
error("Usage: luajit ./foreachmap <module[.lua]> <filename.map> ...")
end
local B = require "build" local B = require "build"
local string = require "string" local string = require "string"
local io = require "io"
local os = require "os"
if (#arg < 1) then
io.stdout:write("Usage: luajit ./foreachmap <module[.lua]> [init args...] <filename.map> ...\n\n")
return
end
local modname = string.gsub(arg[1], "%.lua$", "") local modname = string.gsub(arg[1], "%.lua$", "")
local mod = require(modname) local mod = require(modname)
if (mod.init) then
if (mod.init(arg) ~= nil) then
os.exit(1)
end
end
for i=2,#arg do for i=2,#arg do
local fn = arg[i] local fn = arg[i]
@ -29,6 +41,14 @@ for i=2,#arg do
if (map ~= nil) then if (map ~= nil) then
mod.success(map, fn) mod.success(map, fn)
else else
if (mod.failure) then
mod.failure(fn, errmsg) mod.failure(fn, errmsg)
else
io.stderr:write(string.format("--- %s: %s\n", fn, errmsg))
end end
end end
end
if (mod.finish) then
mod.finish()
end

View file

@ -0,0 +1,50 @@
-- Print out some aggregate statistics for passed BUILD maps,
-- foreachmap module.
local string = require "string"
local math = require "math"
local print = print
module(...)
local function printf(fmt, ...)
print(string.format(fmt, ...))
end
local N = 0
local sumnumsectors = 0
local sumnumwalls = 0
local sumratio = 0
local sumsqratio = 0
function success(map, fn)
local ns = map.numsectors
local nw = map.numwalls
N = N+1
sumratio = sumratio + nw/ns
sumsqratio = sumsqratio + (nw/ns)^2
sumnumsectors = sumnumsectors+ns
sumnumwalls = sumnumwalls+nw
end
function finish()
printf("%d maps\n", N)
printf("total sectors: %d", sumnumsectors)
printf("total walls: %d", sumnumwalls)
printf("total walls / total sectors: %.02f", sumnumwalls/sumnumsectors)
printf("")
printf("Walls/sector")
local meanwpers = sumratio/N
printf(" mean: %.02f", meanwpers)
printf(" stdev: %.02f", math.sqrt(sumsqratio/N - meanwpers^2))
end

View file

@ -19,8 +19,7 @@ function success(map, fn)
printf(" version: %d", map.version) printf(" version: %d", map.version)
printf(" numsectors: %d\n numwalls: %d\n numsprites: %d", printf(" numsectors: %d\n numwalls: %d\n numsprites: %d",
map.numsectors, map.numwalls, map.numsprites) map.numsectors, map.numwalls, map.numsprites)
end printf(" walls/sector: %.02f\n sprites/sector: %.02f",
map.numwalls/map.numsectors, map.numsprites/map.numsectors)
function failure(fn, errmsg) printf("")
printf("--- %s: %s", fn, errmsg)
end end

View file

@ -0,0 +1,164 @@
-- Display information about problematic wall ypannings,
-- foreachmap module.
local string = require "string"
local table = require "table"
local bit = require "bit"
local print = print
local pairs = pairs
local rawget = rawget
local setmetatable = setmetatable
local B = require "build"
module(...)
local function printf(fmt, ...)
print(string.format(fmt, ...))
end
local tile
function init(arg)
local artargend = nil
for i=2,#arg do
if (arg[i]=="--") then
artargend = i
break
end
end
if (artargend==nil or artargend==0) then
printf("Usage: luajit ./foreachmap.lua <tilesXXX.ART> [, ...] -- <filename1.map> ...\n")
return 1
end
local artfns = {}
for i=2,artargend-1 do
artfns[#artfns+1] = arg[i]
end
local i = 2
local j = artargend+1
while (arg[j]) do
arg[i] = arg[j]
arg[j] = nil
i = i+1
j = j+1
end
local tile_, errmsg = B.loadarts(artfns)
if (tile_ == nil) then
printf("%s", errmsg)
return 2
end
tile = tile_ -- set 'tile' file-scope var
end
local table_default0_mt = {
__index = function(tab, idx)
if (rawget(tab, idx)==nil) then
return 0
end
return rawget(tab, idx)
end
}
local sector, wall, sprite
function success(map, fn)
-- set file-scope vars for convenience
sector = map.sector
wall = map.wall
sprite = map.sprite
-- counts of non-pow2 tiles, per tilenum
local np2tile = setmetatable({}, table_default0_mt)
-- walls/overwall indices with non-pow2 tiles, [walidx]=true
local np2wall = {}
-- [i]=wallidx
local np2walls = {}
local badoverpicnum = false
for i=0,map.numsectors-1 do
local startwall = sector[i].wallptr
local endwall = startwall+sector[i].wallnum-1
for w=startwall,endwall do
for n=1,2 do
local pic, ysiz
if (wall[w].nextwall < 0) then
-- We don't care for white walls
elseif (n==1) then
pic = wall[w].picnum
ysiz = tile.sizy[pic]
else
pic = wall[w].overpicnum
if (pic < 0 or pic > 30720) then -- MAXTILES
badoverpicnum = true
else
ysiz = tile.sizy[pic]
if (bit.band(wall[w].cstat, 16+32)==0) then
-- we don't care about non-masked/1-way walls
ysiz = 0
end
end
end
if (ysiz~=nil and ysiz > 0 and bit.band(ysiz, bit.bnot(ysiz-1))~=ysiz) then
-- non-pow2 ysize
np2tile[pic] = np2tile[pic]+1
if (not np2wall[w]) then
np2wall[w] = true
np2walls[#np2walls+1] = w
end
end
end
end
end
-- report our findings
-- sort in wall index order
table.sort(np2walls)
printf("--- %s:", fn)
--[[
printf(" Walls:")
for i=1,#np2walls do
printf(" %d", np2walls[i])
end
printf("")
--]]
printf(" %d red walls with non-pow2 ysize tiles", #np2walls)
if (badoverpicnum) then
printf(" (some red walls have out-of-bounds overpicnums)")
end
local np2tiles = {}
for tilenum,_ in pairs(np2tile) do
np2tiles[#np2tiles+1] = tilenum
end
table.sort(np2tiles)
printf(" Tiles:")
for i=1,#np2tiles do
printf(" %d", np2tiles[i])
end
printf("")
end