From e4638467ef36e866c5030d1adf6fb43bcfdbd41c Mon Sep 17 00:00:00 2001 From: Johan Kiviniemi Date: Fri, 27 Dec 2013 08:20:06 +0200 Subject: [PATCH] Simplify RendertileSet code --- overviewer_core/tileset.py | 195 +++++++++++++------------------------ 1 file changed, 68 insertions(+), 127 deletions(-) diff --git a/overviewer_core/tileset.py b/overviewer_core/tileset.py index 8f22d83..6aad5c0 100644 --- a/overviewer_core/tileset.py +++ b/overviewer_core/tileset.py @@ -1268,18 +1268,16 @@ class RendertileSet(object): It is typically used to hold tiles that need rendering. This implementation collapses subtrees that are completely in or out of the set to save memory. - Each instance of this class is a node in the tree, and therefore each - instance is the root of a subtree. + An instance of this class holds a full tree. - Each node knows its "level", which corresponds to the zoom level where 0 is - the inner-most (most zoomed in) tiles. + The instance knows its "level", which corresponds to the zoom level where 1 + is the inner-most (most zoomed in) tiles. Instances hold the state of their children (in or out of the set). Leaf nodes are images and do not physically exist in the tree as objects, but are represented as booleans held by the objects at the second-to-last level; level 1 nodes keep track of leaf image state. Level 2 nodes keep - track of level 1 state, and so fourth. - + track of level 1 state, and so forth. """ __slots__ = ("depth", "children") @@ -1303,54 +1301,13 @@ class RendertileSet(object): # All children down this subtree are not in the set # True # All children down this subtree are in the set - # A RendertileSet instance - # the instance defines which children down that subtree are in the - # set. + # An array of the same format + # The array defines which children down that subtree are in the set # A node with depth=1 cannot have a RendertileSet instance in its # children since its children are leaves, representing images, not more # tree self.children = [False] * 4 - def posttraversal(self, offset=(0,0)): - """Returns an iterator over tile paths for every tile in the - set, including the explictly marked render-tiles, as well as the - implicitly marked ancestors of those render-tiles. Returns in - post-traversal order, so that tiles with dependencies will always be - yielded after their dependencies. - - """ - return (tuple(reversed(rpath)) for rpath in self._posttraversal_helper(offset=offset)) - - def _posttraversal_helper(self, onlydepth=None, offset=(0,0)): - """Each node returns an iterator over lists of reversed paths""" - targetdepth = 1 if onlydepth is None else onlydepth - - if self.depth == targetdepth: - # Base case - for childnum, _ in distance_sort(xrange(4), offset): - if self.children[childnum]: - yield [childnum] - else: - for (childnum, child), childoffset in distance_sort(enumerate(self.children), offset): - if child == True: - for path in post_traversal_complete_subtree_recursion_helper(self.depth-targetdepth, onlydepth=onlydepth, offset=childoffset): - path.append(childnum) - yield path - - elif child == False: - pass # do nothing - - else: - # Recurse - for path in child._posttraversal_helper(onlydepth=onlydepth, offset=childoffset): - path.append(childnum) - yield path - - # Now do this node itself - if onlydepth is None and bool(self): - yield [] - - def add(self, path): """Marks the requested leaf node as in this set @@ -1360,67 +1317,46 @@ class RendertileSet(object): """ path = list(path) assert len(path) == self.depth - path.reverse() - self._set_add_helper(path) - def _set_add_helper(self, path): + self._add_helper(self.children, list(reversed(path))) + + def _add_helper(self, children, path): """Recursive helper for add() - - Expects path to be a list in reversed order - - If *all* the nodes below this one are in the set, this function returns - true. Otherwise, returns None. - """ - if self.depth == 1: - # Base case - self.children[path[0]] = True + childnum = path.pop() - # Check to see if all children are in the set - if all(self.children): - return True - else: - # Recursive case + if path: + # We are not at the leaf, recurse. - childnum = path.pop() - child = self.children[childnum] - - if child == False: - # Create a new node and recurse. - # (The use of __class__ is so possible subclasses of this class - # work as expected) - child = self.__class__(self.depth-1) - child._set_add_helper(path) - self.children[childnum] = child - elif child == True: - # Every child is already in the set and the subtree is already - # collapsed. Nothing to do. + if children[childnum] == True: + # The child is already in the tree. return - else: - # subtree is mixed. Recurse to the already existing child node - ret = child._set_add_helper(path) - if ret: - # Child says every descendent is in the set, so we can - # purge the subtree and mark it as such. The subtree will - # be garbage collected when this method exits. - self.children[childnum] = True + elif children[childnum] == False: + # Expand all-false. + children[childnum] = [False]*4 - # Since we've marked an entire sub-tree as in the set, we - # may be able to signal to our parent to do the same - if all(x is True for x in self.children): - return True + self._add_helper(children[childnum], path) + + if children[childnum] == [True]*4: + # Collapse all-true. + children[childnum] = True + + else: + # We are at the leaf. + children[childnum] = True def __iter__(self): return self.iterate() + def iterate(self, level=None, offset=(0,0)): """Returns an iterator over every tile in this set. Each item yielded is a sequence of integers representing the quadtree path to the tiles in the set. Yielded sequences are of length self.depth. If level is None, iterates over tiles of the highest level, i.e. - worldtiles. If level is a value between 0 and the depth of this tree, - this method iterates over tiles at that level. Zoom level 0 is zoomed + worldtiles. If level is a value between 1 and the depth of this tree, + this method iterates over tiles at that level. Zoom level 1 is zoomed all the way out, zoom level `depth` is all the way in. In other words, specifying level causes the tree to be iterated as if @@ -1434,7 +1370,38 @@ class RendertileSet(object): raise ValueError("Level parameter must be between 1 and %s" % self.depth) todepth = self.depth - level + 1 - return (tuple(reversed(rpath)) for rpath in self._posttraversal_helper(onlydepth=todepth, offset=offset)) + return (tuple(path) for path in self._iterate_helper([], self.children, self.depth, onlydepth=todepth, offset=offset)) + + def posttraversal(self, offset=(0,0)): + """Returns an iterator over tile paths for every tile in the + set, including the explictly marked render-tiles, as well as the + implicitly marked ancestors of those render-tiles. Returns in + post-traversal order, so that tiles with dependencies will always be + yielded after their dependencies. + """ + return (tuple(path) for path in self._iterate_helper([], self.children, self.depth, offset=offset)) + + def _iterate_helper(self, path, children, depth, onlydepth=None, offset=(0,0)): + """Returns an iterator over tile paths for every tile in the set.""" + + # A variant of children with a collapsed False/True expanded to a list. + children_list = [children] * 4 if isinstance(children, bool) else children + + targetdepth = 1 if onlydepth is None else onlydepth + + if depth == targetdepth: + # Base case + for (childnum, child), _ in distance_sort(enumerate(children_list), offset): + if child: + yield path + [childnum] + else: + for (childnum, child), childoffset in distance_sort(enumerate(children_list), offset): + if child: + for p in self._iterate_helper(path + [childnum], children_list[childnum], depth-1, onlydepth=onlydepth, offset=childoffset): + yield p + + if onlydepth is None and children: + yield path def query_path(self, path): """Queries for the state of the given tile in the tree. @@ -1448,30 +1415,23 @@ class RendertileSet(object): # collapsed, then just return the stored boolean. Otherwise, if we find # the specific tree node requested, return its state using the # __nonzero__ call. - treenode = self + treenode = self.children for pathelement in path: - treenode = treenode.children[pathelement] - if not isinstance(treenode, RendertileSet): + treenode = treenode[pathelement] + if isinstance(treenode, bool): return treenode # If the method has not returned at this point, treenode is the - # requested node, but it is an inner node with possibly mixed state - # subtrees. If any of the children are True return True. This call - # relies on the __nonzero__ method - return bool(treenode) + # requested node, but it is an inner node. That will only happen if one + # or more of the children down the tree are True. + return True def __nonzero__(self): """Returns the boolean context of this particular node. If any descendent of this node is True return True. Otherwise, False. """ - # Any chilren that are True or are a RendertileSet that evaluate to - # True - # IDEA: look at all children for True before recursing - # Better idea: every node except the root /must/ have a descendent in - # the set or it wouldn't exist. This assumption is only valid as long - # as there is no method to remove a tile from the set. So this should - # check to see if any children are not False. + # Any children that are True or are a list evaluate to True. return any(self.children) def count(self): @@ -1497,25 +1457,6 @@ class RendertileSet(object): c += 1 return c -def post_traversal_complete_subtree_recursion_helper(depth, onlydepth=None, offset=(0,0)): - """Fakes the recursive calls for RendertileSet.posttraversal() for the case - that a subtree is collapsed, so that items are still yielded in the correct - order. - - """ - if depth == 1: - # Base case - for childnum, _ in distance_sort(xrange(4), offset): - yield [childnum] - else: - for childnum, childoffset in distance_sort(xrange(4), offset): - for item in post_traversal_complete_subtree_recursion_helper(depth-1, onlydepth=onlydepth, offset=childoffset): - item.append(childnum) - yield item - - if onlydepth is None: - yield [] - def distance_sort(children, (off_x, off_y)): order = [] for child, (dx, dy) in izip(children, [(-1,-1), (1,-1), (-1,1), (1,1)]):