0

scan_chunks() works according to preliminary tests \o/

This commit is contained in:
Andrew Brown
2011-11-06 23:22:51 -05:00
parent 6c0ab7e48c
commit b1c38a56f2
2 changed files with 91 additions and 13 deletions

View File

@@ -462,6 +462,61 @@ class QuadtreeGen(object):
if self.optimizeimg: if self.optimizeimg:
optimize_image(imgpath, self.imgformat, self.optimizeimg) optimize_image(imgpath, self.imgformat, self.optimizeimg)
def scan_chunks(self):
"""Scans the chunks of the world object and produce an iterator over
the tiles that need to be rendered.
"""
depth = self.p
dirty = DirtyTiles(depth)
# For each chunk, do this:
# For each tile that the chunk touches, do this:
# Compare the last modified time of the chunk and tile. If the
# tile is older, mark it in a DirtyTiles object as dirty.
#
# IDEA: check last render time against mtime of the region to short
# circuit checking mtimes of all chunks in a region
for chunkx, chunky, chunkmtime in self.world.iterate_chunk_metadata():
chunkcol, chunkrow = self.world.convert_coords(chunkx, chunky)
#logging.debug("Looking at chunk %s,%s", chunkcol, chunkrow)
# find tile coordinates
tilex = chunkcol - chunkcol % 2
tiley = chunkrow - chunkrow % 4
if chunkcol % 2 == 0:
# This chunk is half-in one column and half-in another column.
# tilex is the right one, also do tilex-2
x_tiles = 2
else:
x_tiles = 1
# The tile at tilex,tiley obviously contains chunk, but so do the
# next 4 tiles down because chunks are very tall
for i in xrange(x_tiles):
for j in xrange(5):
tile = Tile.compute_path(tilex-2*i, tiley+4*j, depth)
tile_path = tile.get_filepath(self.full_tiledir, self.imgformat)
try:
tile_mtime = os.stat(tile_path)[stat.ST_MTIME]
except OSError, e:
if e.errno != errno.ENOENT:
raise
tile_mtime = 0
#logging.debug("tile %s(%s) vs chunk %s,%s (%s)",
# tile, tile_mtime, chunkcol, chunkrow, chunkmtime)
if tile_mtime < chunkmtime:
dirty.set_dirty(tile.path)
#logging.debug(" Setting tile as dirty. Will render.")
# Now that we know which tiles need rendering, return an iterator over them
return (Tile.from_path(tpath) for tpath in dirty.iterate_dirty())
class DirtyTiles(object): class DirtyTiles(object):
"""This tree holds which tiles need rendering. """This tree holds which tiles need rendering.
@@ -503,15 +558,24 @@ class DirtyTiles(object):
def set_dirty(self, path): def set_dirty(self, path):
"""Marks the requested leaf node as "dirty". """Marks the requested leaf node as "dirty".
Path is a list of integers representing the path to the leaf node Path is an iterable of integers representing the path to the leaf node
that is requested to be marked as dirty. Path must be presented in that is requested to be marked as dirty.
reverse order (leaf node at index 0, root node at index -1)
"""
path = list(path)
assert len(path) == self.level
path.reverse()
self._set_dirty_helper(path)
def _set_dirty_helper(self, path):
"""Recursive call for set_dirty()
Expects path to be a list in reversed order
If *all* the nodes below this one are dirty, this function returns If *all* the nodes below this one are dirty, this function returns
true. Otherwise, returns None. true. Otherwise, returns None.
""" """
assert len(path) == self.level
if self.level == 1: if self.level == 1:
# Base case # Base case
@@ -522,8 +586,6 @@ class DirtyTiles(object):
return True return True
else: else:
# Recursive case # Recursive case
if not isinstance(path,list):
path = list(path)
childnum = path.pop() childnum = path.pop()
child = self.children[childnum] child = self.children[childnum]
@@ -531,14 +593,14 @@ class DirtyTiles(object):
if child == False: if child == False:
# Create a new node # Create a new node
child = self.__class__(self.level-1) child = self.__class__(self.level-1)
child.set_dirty(path) child._set_dirty_helper(path)
self.children[childnum] = child self.children[childnum] = child
elif child == True: elif child == True:
# Every child is already dirty. Nothing to do. # Every child is already dirty. Nothing to do.
return return
else: else:
# subtree is mixed clean/dirty. Recurse # subtree is mixed clean/dirty. Recurse
ret = child.set_dirty(path) ret = child._set_dirty_helper(path)
if ret: if ret:
# Child says it's completely dirty, so we can purge the # Child says it's completely dirty, so we can purge the
# subtree and mark it as dirty. The subtree will be garbage # subtree and mark it as dirty. The subtree will be garbage
@@ -555,9 +617,10 @@ class DirtyTiles(object):
yielded is a sequence of integers representing the quadtree path to the yielded is a sequence of integers representing the quadtree path to the
dirty tile. Yielded sequences are of length self.level. dirty tile. Yielded sequences are of length self.level.
Remember yielded paths are in reverse order. Leaf nodes at index 0!
""" """
return (reversed(rpath) for rpath in self._iterate_dirty_helper())
def _iterate_dirty_helper(self):
if self.level == 1: if self.level == 1:
# Base case # Base case
if self.children[0]: yield [0] if self.children[0]: yield [0]
@@ -576,7 +639,7 @@ class DirtyTiles(object):
yield x yield x
elif child != False: elif child != False:
# Mixed dirty/clean down this subtree, recurse # Mixed dirty/clean down this subtree, recurse
for path in child.iterate_dirty(): for path in child._iterate_dirty_helper():
path.append(c) path.append(c)
yield path yield path
@@ -620,6 +683,8 @@ class Tile(object):
the tile and constructs a new tile object. the tile and constructs a new tile object.
""" """
path = tuple(path)
depth = len(path) depth = len(path)
# Radius of the world in chunk cols/rows # Radius of the world in chunk cols/rows

View File

@@ -78,7 +78,6 @@ class World(object):
# figure out chunk format is in use # figure out chunk format is in use
# if not mcregion, error out early # if not mcregion, error out early
data = nbt.load(os.path.join(self.worlddir, "level.dat"))[1]['Data'] data = nbt.load(os.path.join(self.worlddir, "level.dat"))[1]['Data']
#print data
if not ('version' in data and data['version'] == 19132): if not ('version' in data and data['version'] == 19132):
logging.error("Sorry, This version of Minecraft-Overviewer only works with the new McRegion chunk format") logging.error("Sorry, This version of Minecraft-Overviewer only works with the new McRegion chunk format")
sys.exit(1) sys.exit(1)
@@ -358,6 +357,20 @@ class World(object):
elif self.north_direction == 'lower-left': elif self.north_direction == 'lower-left':
return 0 return 0
def iterate_chunk_metadata(self):
"""Returns an iterator over (x,y,chunk mtime) of every chunk loaded in
memory. Provides a public way for external routines to iterate over the
world.
Written for use in quadtree.py's QuadtreeGen.scan_chunks, which only
needs chunk locations and mtimes.
"""
for regionx, regiony, _, mcr in self.regionfiles.itervalues():
for chunkx, chunky in mcr.get_chunks():
yield chunkx+32*regionx, chunky+32*regiony, mcr.get_chunk_timestamp(chunkx, chunky)
def _iterate_regionfiles(self,regionlist=None): def _iterate_regionfiles(self,regionlist=None):
"""Returns an iterator of all of the region files, along with their """Returns an iterator of all of the region files, along with their
coordinates coordinates