- initial commit
This commit is contained in:
Leslie Krause 2020-01-24 11:51:11 -05:00
commit 4aabcfa393
2 changed files with 932 additions and 0 deletions

313
README.txt Normal file
View File

@ -0,0 +1,313 @@
RocketLib Toolkit v1.1
By Leslie E. Krause
RocketLib Toolkit is a purely Lua-driven SQLite3 map reader with an extensive API for
analysis of map databases. The library is intended primarily for use by server operators,
but anybody with Lua programming experience can develop their own command-line tools.
Just to showcase how easy it is to get started examining your map database, it takes only
15 lines of Lua code to search for all mapblocks that have dropped items:
require( "maplib" )
local map_db = MapDatabase( "~/.minetest/worlds/world/map.sqlite", false )
for index, block in map_db.iterate( ) do
local count = 0
for i, v in ipairs( block.object_list ) do
if v.name == "__builtin:item" then
count = count + 1
end
end
if count > 0 then
print( string.format( "%d dropped items in mapblock (%s)",
count, index_to_string( index )
) )
end
end
Important: If your map database exceeds 1GB in size, then a RAM-disk (tmpfs on Linux) is
strongly recommended for optimal performance. Based on personal experience, there can be
upwards of a ten-fold improvement in speed, particularly for intensive queries.
The map reader library provides a fully object-oriented API, so it is straightfoward to
examine mapblocks and their contents without having to worry about the underlying
database architecture.
The available class constructors are as follows:
BlobReader( input )
Provides an interface to serially parse a BLOB with a variety of known datatypes.
* input is the BLOB to parse, typically the raw mapblock data obtained from the
"data" field of the "blocks" table
The BlobReader class defines the following public methods:
BlobReader::read_u8( )
Read an 8-bit unsigned integer and advance the pointer by one byte.
BlobReader::read_u16( )
Read a 16-bit unsigned integer and advance the pointer by two bytes.
BlobReader::read_u32( )
Read a 32-bit unsigned integer and advance the pointer by four bytes.
BlobReader::read_s16( )
Read a 16-bit signed integer and advance the pointer by two bytes.
BlobReader::read_s32( )
Read a 32-bit signed integer and advance the pointer by four bytes.
BlobReader::read_f1000( )
Read a floating point and advance the pointer by four bytes.
BlobReader::read_v3f1000( )
Read a 3-dimensional floating point array and advance the pointer by 12 bytes.
BlobReader::read_zlip( )
Slurp a zlib compressed data stream and advance the pointer accordingly.
BlobReader::read_string( len )
Read a non-terminated text string of len bytes and then advance the pointer
accordingly to len. If len is not provided, slurp a multiline terminated text
string and advance the pointer accordingly.
MapArea( pos1, pos2 )
Delineates a mapblock area to be examined, while also providing various area
calculation methods.
* pos1 is lowest boundary mapblock coordinate to iterate
* pos2 is the highest boundary mapblock coordinate to iterate
The MapArea class provides the following public methods:
MapArea::get_min_pos( )
Return the lowest boundary mapblock position of the area as a table {x,y,z}
MapArea::get_max_pos( )
Return the highest boundary mapblock position of the area as a table {x,y,z}
MapArea::get_volume( )
Calculate the volume of the area in cubic mapblocks and return the result
MapArea::has_index( idx )
Returns true if the specified mapblock hashed position, idx, exists within the area
MapArea::has_pos( pos )
Returns true if the specified mapblock position, pos, exists within the area
MapArea::iterate( )
Returns an iterator for looping through the area
MapBlock( blob, is_preview, get_checksum )
Parses the mapblock data and calculates the associated checksum. For efficiency, the
nodemeta list and the node list are not parsed automatically, but they can be obtained
using the corresponding methods.
* blob is the raw mapblock data obtained from "data" field of the "blocks" table
* is_preview is a boolean indicating whether to parse the BLOB (optional).
* get_checksum is the checksum function to calculate the checksum and length of the
BLOB (optional).
The MapBlock class defines the following public methods:
MapBlock::get_node_list( )
Parses the raw node list of the mapblock and returns a node_list table.
The node_list table is an array of exactly 4096 elements, corresponding to the
16x16x16 matrix of nodes comprising the mapblock. The coordinates of a node can be
obtained using the decode_node_pos( ) helper function. Each entry of the node_list
table contains a subtable with three fields: id, param1, and param2.
Note that the id refers to the content ID which varies between map blocks. You must
cross-reference the content ID to determine the actual registered node name.
MapBlock::get_nodemeta_map( )
Parses the raw nodemeta list and returns a nodemata_list table.
The nodemeta_map table is an associative array indexed by the position hash of
the corresponding node from the node_list table. Each entry of the nodemeta_list
table is a subtable containing the following fields:e
* fields is a subtable containing the user-defined metadata for the node, as
ordinary key-value pairs.
* is_private is a boolean specifying whether the metadata of the node is private
* inventory is a subtable containing the inventory of the node as an array of
tables, with two fields for each inventory slot: item_name and item_count
The MapBlock class defines the following public read-only properties:
MapBlock::version
The version of the mapblock.
MapBlock::flags
The flags of the mapblock.
MapBlock::content_width
The size of the content_ids in bytes. This is either 1 or 2, based on the version.
MapBlock::params_width
The size of param1 and param2 in bytes. This is always 2.
MapBlock::object_list
An array of objects stored in the mapblock. Each entry contains a subtable with
seven fields: type, pos, version, name, staticdata, hp, velocity, and yaw.
MapBlock::nodename_map
An associative array of registered node names indexed by content IDs.
MapBlock::timestamp
The timetamp when the mapblock was last modified by the engine. Note that this
value is not necessarily a reliable means to determine if a mapblock was changed or
not. For that you should perform a checksum comparison.
MapDatabase( path, is_preview, summary )
Opens an existing map.sqlite database from disk and prepares the necessary SQL statements.
* path is the path to the sqlite3 map database to be opened in read-only mode
* is_preview is a boolean indicating whether mapblocks are to be parsed by default
(optional)
* is_summary is a boolean indicating whether checksums apply to all mapblocks by
default (optional)
The MapDatabase class defines the following public methods:
MapDatabase::enable_preview( )
Enable parsing of mapblocks by default
MapDatabase::disable_preview( )
Disable parsing of mapblocks by default, only calculate checksum and length
MapDatabase::enable_summary( )
Enable cumulative checksum calculations by default
MapDatabase::disable_summary( )
Disable cumulative checksum calculations by default
MapDatabase::change_algorithm( algorithm )
Switches to a different checksum algorithm, either 'adler32' or 'crc32'.
MapDatabase::create_cache( use_memory, on_step )
Create a cache database storing cross-references of mapblock position hashes,
thereby speeding up successive queries. If use_memory is true, the cache database
will be memory resident. Otherwise a file named "map.sqlite-cache" will be created
in the same directory as the map database. The optional on_step hook can be used to
update a progress bar for lengthy operations.
MapDatabase::get_length( )
Returns the total number of mapblocks. If the cache is available, then it will be
used.
MapDatabase::get_area_length( area )
Returns the total number of mapblocks inside the given area. The cache is required
for this operation.
MapDatabase::iterate( on_step )
Returns an iterator function, for looping over all existing mapblocks. The optional
on_step hook can be used to update a progress bar for length operations
MapDatabase::iterate_area( area, on_step )
Returns an iterator function, for looping over all existing mapblocks inside the
given area. The optional on_step hook can be used to update a progress bar for
lengthy operations. The cache is required for this operation.
MapDatabase::select( on_step )
Returns an array of all hashed positions for all mapblocks. The optional on_step
hook can be used to update a progress bar for lengthy operations. The cache is not
used for this operation (but I will consider making it optional)
MapDatabase::select_area( area, on_step )
Returns an array of hashed positions for all mapblocks inside the given area. The
optional on_step hook can be used to update a progress bar for lengthy operations.
The cache is required for this operation.
MapDatabase::has_index( index )
Returns a boolean indicating whether a mapblock exists at the given hashed
position.
MapDatabase::get_mapblock( index, get_checksum )
Returns the mapblock at the given hashed position as a MapBlock object. The
optional get_checksum function will be used to calculate the checksum and length
of the BLOB.
MapDatabase::get_mapblock_raw( index, get_checksum )
Returns the mapblock as a BLOB, without calculating the checksum.
MapDatabase::close( index, get_checksum )
Closes the map database (but it doesn't close the cache database, which is a known
unresolved bug).
Several helper functions are also available for debugging and conversion purposes.
decode_pos( index )
Converts the given mapblock hashed position to a vector position.
encode_pos( pos )
Converts the given mapblock vector position to a hashed position.
decode_node_pos( node_index, index )
Converts the given node index within the given mapblock to a vector position in world
coordinates. If the index parameter is not provided, then the result will be relative
to a mapblock at {0,0,0}. Note: For consistency with Lua conventions, node indexes
are always 1-based.
encode_node_pos( node_pos )
Converts the given node vector position into a both a node index and mapblock hashed
position.
encode_pos( pos )
Converts the given mapblock vector position to a hashed position.
pos_to_string( pos )
Returns a string representing the given vector position as "x,y,z".
dump( buffer )
Returns a string representing a memory dump of the given buffer
Repository
----------------------
Browse source code...
https://bitbucket.org/sorcerykid/rocketlib
Download archive...
https://bitbucket.org/sorcerykid/rocketlib/get/master.zip
https://bitbucket.org/sorcerykid/rocketlib/get/master.tar.gz
Installation
----------------------
RocketLib Toolkit depends on the Lua modules lsqlite3 and zblip, which can be installed
using Luarocks.
Luarocks itself can be obtained from
https://github.com/luarocks/luarocks/wiki/Download
License of source code
----------------------------------------------------------
The MIT License (MIT)
Copyright (c) 2020, Leslie Krause (leslie@searstower.org)
Permission is hereby granted, free of charge, to any person obtaining a copy of this
software and associated documentation files (the "Software"), to deal in the Software
without restriction, including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software, and to permit
persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
For more details:
https://opensource.org/licenses/MIT

619
rocketlib.lua Normal file
View File

@ -0,0 +1,619 @@
-----------------------------------------------------
-- Minetest :: RocketLib Toolkit (rocketlib)
--
-- See README.txt for licensing and release notes.
-- Copyright (c) 2018-2020, Leslie E. Krause
-----------------------------------------------------
package.cpath = package.cpath .. ";/usr/local/lib/lua/5.1/?.so"
local zlib = require( "zlib" ) -- https://luarocks.org/modules/brimworks/lua-zlib
local sqlite3 = require( "lsqlite3complete" ) -- https://luarocks.org/modules/dougcurrie/lsqlite3
-----------------------------
-- Conversion Routines
-----------------------------
local floor = math.floor
local ceil = math.ceil
local max = math.max
local min = math.min
local byte = string.byte
local match = string.match
local find = string.find
local sub = string.sub
local function to_signed( val )
return val < 2048 and val or val - 2 * 2048
end
function decode_pos( idx )
local x = to_signed( idx % 4096 )
idx = floor( ( idx - x ) / 4096 )
local y = to_signed( idx % 4096 )
idx = floor( ( idx - y ) / 4096 )
local z = to_signed( idx % 4096 )
return { x = x, y = y, z = z }
end
function encode_pos( pos )
return pos.x + pos.y * 4096 + pos.z * 16777216
end
function decode_node_pos( node_idx, idx )
local pos = idx and decode_pos( idx ) or { x = 0, y = 0, z = 0 }
local node_pos = { }
node_idx = node_idx - 1 -- correct for one-based indexing used in node_list
node_pos.x = ( node_idx % 16 ) + pos.x * 16
node_idx = floor( node_idx / 16 )
node_pos.y = ( node_idx % 16 ) + pos.y * 16
node_idx = floor( node_idx / 16 )
node_pos.z = ( node_idx % 16 ) + pos.z * 16
return node_pos
end
function encode_node_pos( node_pos )
local pos = {
x = floor( node_pos.x / 16 ),
y = floor( node_pos.y / 16 ),
z = floor( node_pos.z / 16 )
}
local x = node_pos.x % 16
local y = node_pos.x % 16
local z = node_pos.x % 16
return x + y * 16 + z * 256, encode_pos( pos )
end
local function is_match( text, glob )
-- use array for captures
_ = { match( text, glob ) }
return #_ > 0 and _ or nil
end
-----------------------------
-- Debugging Routines
-----------------------------
function pos_to_string( pos )
return string.format( "(%d,%d,%d)", pos.x, pos.y, pos.z )
end
function dump( buffer )
for i = 1, ceil( #buffer / 16 ) * 16 do
if ( i - 1 ) % 16 == 0 then io.write( string.format( '%08X ', i - 1 ) ) end
io.write( i > #buffer and ' ' or string.format( '%02x ', buffer:byte( i ) ) )
if i % 8 == 0 then io.write( ' ' ) end
if i % 16 == 0 then io.write( buffer:sub( i - 16 + 1, i ):gsub( '%c', '.' ), '\n' ) end
end
end
-----------------------------
-- BlobReader Class
-----------------------------
function BlobReader( input )
local idx = 1
local self = { }
-- private methods
local function u16_to_signed( val )
return val < 32767 and val or val - 2 * 32767
end
local function u32_to_signed( val )
return val < 2147483647 and val or val - 2 * 2147483647
end
-- public methods
self.read_u8 = function ( )
local output = byte( input, idx )
idx = idx + 1
return output
end
self.read_u16 = function ( )
-- 16-bit unsigned integer
local output = byte( input, idx ) * 256 + byte( input, idx + 1 )
idx = idx + 2
return output
end
self.read_u32 = function ( )
-- 32-bit unsigned integer
local output = byte( input, idx ) * 16777216 + byte( input, idx + 1 ) * 65536 + byte( input, idx + 2 ) * 256 + byte( input, idx + 3 )
idx = idx + 4
return output
end
self.read_s16 = function ( )
-- 16-bit signed integer
local output = u16_to_signed( byte( input, idx ) * 256 + byte( input, idx + 1 ) )
idx = idx + 2
return output
end
self.read_s32 = function ( )
-- 32-bit signed integer
local output = u32_to_signed( byte( input, idx ) * 16777216 + byte( input, idx + 1 ) * 65536 + byte( input, idx + 2 ) * 256 + byte( input, idx + 3 ) )
idx = idx + 4
return output
end
self.read_f1000 = function ( )
local output = self.read_s32( ) / 1000
return output
end
self.read_v3f10000 = function ( )
local output = { x = self.read_s32( ) / 10000, y = self.read_s32( ) / 10000, z = self.read_s32( ) / 10000 }
return output
end
self.read_zlib = function ( )
output, is_eof, bytes_in, bytes_out = zlib.inflate( )( sub( input, idx ) )
idx = idx + bytes_in
return output
end
self.read_string = function ( len )
if not len then
-- multiline string
local len = find( input, "\n", idx ) - idx
local output = sub( input, idx, idx + len - 1 )
idx = idx + len + 1
return output
else
-- non-terminated string
local output = sub( input, idx, idx + len - 1 )
idx = idx + len
return output
end
end
return self
end
-----------------------------
-- Deserializer Routines
-----------------------------
local function parse_node_list( blob )
local p = BlobReader( blob )
local this = { }
for idx = 1, 4096 do
this[ idx ] = { id = p.read_s16( ) }
end
for idx = 1, 4096 do
this[ idx ].param1 = p.read_u8( )
end
for idx = 1, 4096 do
this[ idx ].param2 = p.read_u8( )
end
return this
end
local function parse_nodemeta_map( blob )
local p = BlobReader( blob )
local this = { }
local version = p.read_u8( )
if version == 0 then
return this
elseif version > 3 then
error( "Unsupported node_metadata version, aborting!" )
end
local node_total = p.read_u16( )
for node_count = 1, node_total do
local pos = p.read_u16( ) + 1 -- use one-based indexing to correspond with node_list array
local var_total = p.read_u32( )
this[ pos ] = { fields = { }, inventory = { }, is_private = false }
for var_count = 1, var_total do
local key = p.read_string( p.read_u16( ) ) -- 16-bit length string
local value = p.read_string( p.read_u32( ) ) -- 32-bit length string
this[ pos ].fields[ key ] = value
end
if version >= 2 then
this[ pos ].is_private = ( p.read_u8( ) == 1 )
end
for inv_count = 1, 127 do
local text = p.read_string( )
if text == "EndInventory" then
break
end
if is_match( text, "^Item ([a-zA-Z0-9_]+:[a-zA-Z_]+)$" ) or is_match( text, "^Item ([a-zA-Z0-9_]+:[a-zA-Z0-9_]+) (%d+)" ) then
table.insert( this[ pos ].inventory, { item_name = _[ 1 ], item_count = tonumber( _[ 2 ] ) or 1 } )
elseif text == "Empty" then
table.insert( this[ pos ].inventory, { } ) -- empty item stack
end
end
end
return this
end
local function parse_object( blob )
local p = BlobReader( blob )
local this = { }
this.version = p.read_u8( )
this.name = p.read_string( p.read_u16( ) )
this.staticdata = p.read_string( p.read_u32( ) )
if this.version == 1 then
this.hp = p.read_s16( )
this.velocity = p.read_f1000( ) / 10
this.yaw = p.read_f1000( ) / 10
end
return this
end
------------------------
-- MapBlock Class
------------------------
function MapBlock( blob, is_preview, get_checksum )
local self = { }
self.checksum, self.length = get_checksum( blob )
if is_preview then return self end
----------
local p = BlobReader( blob )
self.version = p.read_u8( )
self.flags = p.read_u8( )
if self.version >= 27 then
self.lighting_complete = p.read_u16( )
end
self.content_width = p.read_u8( )
self.params_width = p.read_u8( )
if self.params_width ~= 2 then
error( "Invalid params_width, aborting!" )
end
if self.content_width ~= 1 and self.version < 24 or self.content_width ~= 2 and self.version >= 24 then
error( "Invalid params_width, aborting!" )
end
----------
local node_list_raw = p.read_zlib( )
if #node_list_raw ~= 4096 * self.content_width * self.params_width then
error( "Invalid node_list, aborting!" )
end
----------
local nodemeta_list_raw = p.read_zlib( )
----------
if self.version == 23 then
p.read_u8( ) -- unused
end
----------
self.object_list = { }
local obj_version = p.read_u8( )
local obj_total = p.read_u16( )
for obj_count = 1, obj_total do
local type = p.read_u8( )
local pos = p.read_v3f10000( )
local blob = p.read_string( p.read_u16( ) )
local object = parse_object( blob )
object.type = type
object.pos = pos
table.insert( self.object_list, object )
end
self.timestamp = p:read_u32( )
----------
self.nodename_map = { }
local map_version = p.read_u8( )
local map_total = p.read_u16( )
for map_total = 1, map_total do
local id = p.read_s16( )
local name = p.read_string( p.read_u16( ) )
self.nodename_map[ id ] = name
end
----------
-- TODO: parse timers
----------
self.get_node_list = function ( )
return parse_node_list( node_list_raw )
end
self.get_nodemeta_map = function ( )
return parse_nodemeta_map( nodemeta_list_raw )
end
return self
end
-----------------------------
-- MapArea Class
-----------------------------
function MapArea( pos1, pos2 )
local self = { }
-- presort positions and clamp to designated boundaries
local x1 = max( min( pos1.x, pos2.x ), -2048 )
local y1 = max( min( pos1.y, pos2.y ), -2048 )
local z1 = max( min( pos1.z, pos2.z ), -2048 )
local x2 = min( max( pos1.x, pos2.x ), 2048 )
local y2 = min( max( pos1.y, pos2.y ), 2048 )
local z2 = min( max( pos1.z, pos2.z ), 2048 )
self.get_min_pos = function ( )
return { x = x1, y = y1, z = z1 }
end
self.get_max_pos = function ( )
return { x = x2, y = y2, z = z2 }
end
self.get_volume = function ( )
return ( z2 - z1 + 1 ) * ( y2 - y1 + 1 ) * ( x2 - x1 + 1 )
end
self.has_index = function ( idx )
local x = to_signed( idx % 4096 )
if x < x1 or x > x2 then return false end
idx = floor( ( idx - x ) / 4096 )
local y = to_signed( idx % 4096 )
if y < y1 or y > y2 then return false end
idx = floor( ( idx - y ) / 4096 )
local z = to_signed( idx % 4096 )
if z < z1 or z > z2 then return false end
return true
end
self.has_pos = function ( pos )
if pos.x < x1 or pos.x > x2 or pos.y < y1 or pos.y > y2 or pos.z < z1 or pos.z > z2 then
return false
end
return true
end
self.iterate = function ( )
local x
local y = y1
local z = z1
return function ( )
if not x then
x = x1
elseif x < x2 then
x = x + 1
elseif y < y2 then
x = x1
y = y + 1
elseif z < z2 then
x = x1
y = y1
z = z + 1
else
return nil
end
return x + y * 4096 + z * 16777216
end
end
return self
end
-----------------------------
-- MapDatabase Class
-----------------------------
function MapDatabase( path, is_preview, is_summary )
local self = { }
local map_db = sqlite3.open( path, sqlite3.OPEN_READONLY )
local cache_db
local init_checksum = zlib.crc32
if not map_db then
error( "Cannot open map database, aborting!" )
end
local map_select_pos = map_db:prepare( "SELECT data FROM blocks WHERE pos = ?" )
local map_select = map_db:prepare( "SELECT pos, data FROM blocks" )
self.enable_preview = function ( )
is_preview = true
end
self.disable_preview = function ( )
is_preview = false
end
self.enable_summary = function ( )
is_summary = true
end
self.disable_summary = function ( )
is_summary = false
end
self.change_algorithm = function ( algorithm )
init_checksum = ( { ["crc32"] = zlib.crc32, ["adler32"] = zlib.alder32 } )[ algorithm ]
end
self.create_cache = function ( use_memory, on_step )
cache_db = use_memory and sqlite3.open_memory( ) or sqlite3.open( path .. "-cache" )
if not cache_db then
error( "Cannot open cache database, aborting!" )
end
for _ in cache_db:rows( "SELECT * FROM sqlite_master WHERE name = 'catalog' and type = 'table'" ) do return end
if cache_db:exec( "CREATE TABLE catalog (pos INTEGER PRIMARY KEY, x INTEGER, y INTEGER, z INTEGER)" ) ~= sqlite3.OK then
error( "Cannot update cache database, aborting!" )
end
local stmt = cache_db:prepare( "INSERT INTO catalog VALUES (?, ?, ?, ?)" )
cache_db:exec( "BEGIN" ) -- combine into single transaction
for index in map_db:urows( "SELECT pos FROM blocks" ) do
if on_step then on_step( ) end
local pos = decode_pos( index )
stmt:reset( )
stmt:bind_values( index, pos.x, pos.y, pos.z )
if stmt:step( ) ~= sqlite3.DONE then
error( "Cannot update cache database, aborting!" )
end
end
cache_db:exec( "END" )
end
self.get_length = function ( )
if not cache_db then
for total in map_db:urows( "SELECT count(*) FROM blocks" ) do
return total
end
else
for total in cache_db:urows( "SELECT count(*) FROM catalog" ) do
return total
end
end
end
self.get_area_length = function ( area )
if not cache_db then return end
local stmt = cache_db:prepare( "SELECT count(*) FROM catalog WHERE x >= ? AND x <= ? AND y >= ? AND y <= ? AND z >= ? AND z <= ?" )
local min_pos = area.get_min_pos( )
local max_pos = area.get_max_pos( )
stmt:bind_values( min_pos.x, max_pos.x, min_pos.y, max_pos.y, min_pos.z, max_pos.z )
for total in stmt:urows( ) do
return total
end
end
self.iterate = function ( on_step )
local get_checksum
local stmt = map_select
stmt:reset( )
if is_summary then
get_checksum = init_checksum( )
end
return function ( )
if stmt:step( ) ~= sqlite3.ROW then return end
if on_step then on_step( ) end
local index = stmt:get_value( 0 )
local block = MapBlock( stmt:get_value( 1 ), is_preview, get_checksum or init_checksum( ) )
return index, block
end
end
self.iterate_area = function ( area, on_step )
if not cache_db then return end
-- provided cache database, query indices of all mapblocks within
-- area, then return each index and parsed block
local stmt = cache_db:prepare( "SELECT pos FROM catalog WHERE x >= ? AND x <= ? AND y >= ? AND y <= ? AND z >= ? AND z <= ?" )
local min_pos = area.get_min_pos( )
local max_pos = area.get_max_pos( )
stmt:bind_values( min_pos.x, max_pos.x, min_pos.y, max_pos.y, min_pos.z, max_pos.z )
if is_summary then
get_checksum = init_checksum( )
end
return function ( )
if stmt:step( ) ~= sqlite3.ROW then
stmt:finalize( )
return
end
if on_step then on_step( ) end
local index = stmt:get_value( 0 )
local block = self.get_mapblock( index, get_checksum or init_checksum( ) )
return index, block
end
end
self.select = function ( on_step )
for index in map_db:urows( "SELECT pos FROM blocks" ) do
if on_step then on_step( ) end
table.insert( index_list, index )
end
return index_list
end
self.select_area = function ( area, on_step )
local index_list = { }
local stmt = cache_db:prepare( "SELECT pos FROM catalog WHERE x >= ? AND x <= ? AND y >= ? AND y <= ? AND z >= ? AND z <= ?" )
local min_pos = area.get_min_pos( )
local max_pos = area.get_max_pos( )
stmt:bind_values( min_pos.x, max_pos.x, min_pos.y, max_pos.y, min_pos.z, max_pos.z )
for index in stmt:urows( ) do
if on_step then on_step( ) end
table.insert( index_list, index )
end
return index_list
end
self.has_index = function ( index )
local stmt = map_select_pos
stmt:reset( )
stmt:bind_values( index )
return stmt:step( ) == sqlite3.ROW
end
self.get_mapblock = function ( index )
local stmt = map_select_pos
stmt:reset( )
stmt:bind_values( index )
if stmt:step( ) == sqlite3.ROW then
return MapBlock( stmt:get_value( 0 ), is_preview, init_checksum( ) )
end
end
self.get_mapblock_raw = function ( index )
local stmt = map_select_pos
stmt:reset( )
stmt:bind_values( index )
if stmt:step( ) == sqlite3.ROW then
return stmt:get_value( 0 )
end
end
self.close = function ( )
map_db:close( )
end
return self
end