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:
parent
4f103d19fd
commit
bf22f10fe6
7
README.md
Normal file
7
README.md
Normal file
@ -0,0 +1,7 @@
|
||||
Python Minetest
|
||||
=================
|
||||
|
||||
A library to use on Minetest's files
|
||||
ßÿ Mg/LeMagnesium
|
||||
|
||||
License : WTFPL (so far)
|
@ -25,6 +25,9 @@ class UnknownMetadataTypeIDError(MapError):
|
||||
class InvalidParamLengthError(MapError):
|
||||
pass
|
||||
|
||||
class EmptyMapBlockError(MapError):
|
||||
pass
|
||||
|
||||
##=========================================##
|
||||
# 2. Containers Errors
|
||||
class ContainerError(MinetestException):
|
||||
|
106
map.py
106
map.py
@ -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)
|
||||
|
@ -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
|
||||
|
17
nodes.py
17
nodes.py
@ -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
23
test.py
@ -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()
|
||||
|
11
utils.py
11
utils.py
@ -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)))
|
||||
|
Loading…
x
Reference in New Issue
Block a user