Implement get_node

- Reorganize the MapVessel
- Introduce MapInterfaces with cache size control
- New error : EmptyMapblockError
- MapVessel now reads a mapblock if it is not cached but asked for loading
- MapBlock now organizes all nodes in a single dict
- Fix param0 reading and AssertionError in MapBlock.explode
- Add get_node to MapBlock, used by MapInterface to read a node on the relevant MapBlock after loading it from its vessel
This commit is contained in:
LeMagnesium 2016-01-25 20:02:25 +01:00
parent 4f103d19fd
commit bf22f10fe6
7 changed files with 149 additions and 23 deletions

7
README.md Normal file
View File

@ -0,0 +1,7 @@
Python Minetest
=================
A library to use on Minetest's files
ßÿ Mg/LeMagnesium
License : WTFPL (so far)

View File

@ -25,6 +25,9 @@ class UnknownMetadataTypeIDError(MapError):
class InvalidParamLengthError(MapError):
pass
class EmptyMapBlockError(MapError):
pass
##=========================================##
# 2. Containers Errors
class ContainerError(MinetestException):

106
map.py
View File

@ -9,12 +9,13 @@
import sqlite3 as _sql
import zlib
from io import BytesIO
import math
from errors import MapError, EmptyMapVesselError, UnknownMetadataTypeIDError, InvalidParamLengthError
from utils import readU8, readU16, readU32, readS32, Pos, posFromInt
from errors import MapError, EmptyMapVesselError, UnknownMetadataTypeIDError, InvalidParamLengthError, EmptyMapBlockError, OutOfBordersCoordinates
from utils import readU8, readU16, readU32, readS32, Pos, posFromInt, getMapBlockPos
from metadata import NodeMetaRef
from inventory import getSerializedInventory
from nodes import NodeTimerRef
from nodes import NodeTimerRef, Node
# Bitmask constants
IS_UNDERGROUND = 1
@ -22,16 +23,25 @@ DAY_NIGHT_DIFFERS = 2
LIGHTING_EXPIRED = 4
GENERATED = 8
def determineMapBlock(pos):
posx = math.floor(pos.x / 16)
posy = math.floor(pos.y / 16)
posz = math.floor(pos.z / 16)
return Pos({'x': posx, 'y': posy, 'z': posz})
class MapBlock:
def __init__(self, data = None):
if data:
self.explode(data)
else:
self.nodes = dict()
self.version = 0
self.mapblocksize = 16 # Normally
self.bitmask = b"08"
self.content_width = 2
self.param_width = 2
self.node_data = dict()
node_data = dict()
self.node_meta = dict()
self.node_timers = dict()
self.static_object_version = 0 #u8
@ -44,6 +54,16 @@ class MapBlock:
self.single_timer_data_length = 10 #u8
self.timer_counts = 0 #u16
self.timers = dict() #u16, s32, s32
self.loaded = False
def get_node(self, mapblockpos):
if not self.loaded:
raise EmptyMapBlockError
if mapblockpos < 0 or mapblockpos >= 4096:
raise OutOfBordersCoordinates
return self.nodes[mapblockpos]
def explode(self, bytelist):
data = BytesIO(bytelist)
@ -54,7 +74,8 @@ class MapBlock:
self.content_width = readU8(data)
self.param_width = readU8(data)
self.node_data = dict()
self.nodes = dict()
node_data = dict()
k = b""
while True:
@ -69,15 +90,23 @@ class MapBlock:
else:
break
self.node_data["param0"] = [ int(b) for b in c_width_data.read(4096 * self.content_width) ]
self.node_data["param1"] = [ int(b) for b in c_width_data.read(4096) ]
self.node_data["param2"] = [ int(b) for b in c_width_data.read(4096) ]
node_data["param0"] = []
for _ in range(4096):
if self.content_width == 1:
b = readU8(c_width_data)
else:
b = readU16(c_width_data)
node_data["param0"].append(int(b))
node_data["param1"] = [ int(b) for b in c_width_data.read(4096) ]
node_data["param2"] = [ int(b) for b in c_width_data.read(4096) ]
try:
assert(len(self.node_data["param0"]) == 4096 * self.content_width)
assert(len(self.node_data["param1"]) == 4096)
assert(len(self.node_data["param2"]) == 4096)
except AssertError:
assert(len(node_data["param0"]) == 4096)
assert(len(node_data["param1"]) == 4096)
assert(len(node_data["param2"]) == 4096)
except AssertionError:
raise InvalidParamLengthError()
k = b""
@ -245,7 +274,7 @@ class MapBlock:
for _ in range(self.num_name_id_mappings):
# u16 id, u8 [u16 name_len] name
id = readU16(data)
name = [ readU8(data) for _ in range(readU16(data)) ]
name = "".join([ chr(readU8(data)) for _ in range(readU16(data)) ])
self.name_id_mappings[id] = name
if self.version == 25:
@ -262,7 +291,14 @@ class MapBlock:
elapsed = readS32(data) / 1000
self.timers[pos] = NodeTimerRef(pos, timeout, elapsed)
for id in range(4096):
itemstring = self.name_id_mappings[node_data["param0"][id]]
param1 = node_data["param1"][id]
param2 = node_data["param2"][id]
self.nodes[id] = Node(posFromInt(id, self.mapblocksize), itemstring, param1 = param1, param2 = param2)
# EOF!
self.loaded = True
class MapVessel:
def __init__(self, mapfile, backend = "sqlite3"):
@ -289,6 +325,7 @@ class MapVessel:
def close(self):
self.conn.close()
self.cache = None
self.mapblocks = None
self.mapfile = None
def read(self, blockID):
@ -336,6 +373,47 @@ class MapVessel:
raise EmptyMapVesselError()
if not self.cache.get(blockID):
return False, "notread"
#return False, "notread"
res, code = self.read(blockID)
if not res and code == "notfound":
return
elif not res:
return res, code
return MapBlock(self.cache[blockID])
class MapInterface:
def __init__(self, datafile, backend = "sqlite3"):
self.datafile = datafile
self.interface = MapVessel(datafile, backend)
self.mapblocks = dict()
self.cache_history = []
self.max_cache_size = 100
def unloadMapBlock(self, blockID):
self.mapblocks[blockID] = None
del self.cache_history[self.cache_history.index(blockID)]
self.interface.uncache(blockID)
def loadMapBlock(self, blockID):
self.mapblocks[blockID] = self.interface.load(blockID)
if not blockID in self.cache_history:
self.cache_history.append(blockID)
if len(self.cache_history) > self.max_cache_size:
self.interface.uncache(self.cache_history[0])
del self.mapblocks[self.cache_history[0]]
del self.cache_history[0]
def get_node(self, pos):
mapblock = determineMapBlock(pos)
mapblockpos = getMapBlockPos(mapblock)
if not self.mapblocks.get(mapblockpos):
self.loadMapBlock(mapblockpos)
if not self.mapblocks.get(mapblockpos):
self.unloadMapBlock(mapblockpos)
return Node(pos, "ignore")
return self.mapblocks[mapblockpos].get_node((pos.x % 16) + (pos.y % 16) * 16 + (pos.z % 16) * 16 * 16)

View File

@ -3,8 +3,11 @@
##################################
## Python Library to manipulate
## Minetest's files
## By Mg, license : WTFPL
##
#
import utils
from utils import Pos
from map import MapVessel
from map import MapInterface
import map

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python3
# -*- encoding: utf8 -*-
###########################
## Maps for Python-MT
## Nodes for Python-MT
##
##
#
@ -33,3 +33,18 @@ class NodeTimerRef:
def is_started(self):
return self.active
class Node:
def __init__(self, pos, itemstring, param1 = 0, param2 = 0):
self.pos = pos
self.itemstring = itemstring
self.param1, self.param2 = param1, param2
def __repr__(self):
return self.__str__()
def __str__(self):
return self.itemstring
def get_name(self):
return self.itemstring

23
test.py
View File

@ -1,21 +1,21 @@
#!/usr/bin/env python3
# -*- encoding: utf-8 -*-
##########################
############################
## Tests ran for Python-MT
##
import minetest
import random
from utils import readS8, readS16
from utils import readS8, readS16, Pos
from io import BytesIO
file = minetest.MapVessel("./map.sqlite")
#i = 2+12*4096+(-9)*4096*4096
#i = 0
def testSignedEndians():
print(readS16(BytesIO(b"\x9f\xff")))
def testMapBlockLoad():
file = minetest.map.MapVessel("./map.sqlite")
#i = 2+12*4096+(-9)*4096*4096
#i = 0
for i in range(-4096, 4096):
res, code = file.read(i)
if not res:
@ -29,6 +29,15 @@ def testMapBlockLoad():
print(len(file.cache))
def testGetNode():
db = minetest.MapInterface("./map.sqlite")
for _ in range(1000):
pos = Pos({'x': random.randint(-300, 300), 'y': random.randint(-300, 300), 'z': random.randint(-300, 300)})
print("{0}: {1}".format(pos, db.get_node(pos).get_name()))
print("Cache: {0}".format(len(db.interface.cache)))
if __name__ == "__main__":
testMapBlockLoad()
#testSignedEndians()
testSignedEndians()
testGetNode()

View File

@ -21,6 +21,13 @@ def posFromInt(pos, blocksize):
return Pos({'x': posx, 'y': posy, 'z': pos})
def intFromPos(pos, blocksize):
posint = 0
posint += pos.x
posint += pos.y * blocksize
posint += pos.z * blocksize * blocksize
return posint
def int64(u):
while u >= 2**63:
u -= 2**64
@ -28,6 +35,9 @@ def int64(u):
u += 2**64
return u
def getMapBlockPos(pos):
return pos.z * 4096 * 4096 + pos.y * 4096 + pos.x
def getIntegerAsBlock(i):
x = unsignedToSigned(i % 4096, 2048)
i = int((i - x) / 4096)
@ -62,6 +72,7 @@ class Pos:
def getAsTuple(self):
return (self.x, self.y, self.z)
# Thanks to @gravgun for those
# Big-endian!!!
def readU8(strm):
return (ord(strm.read(1)))