From c49fdd19e17341fd869b899fad179e771d385401 Mon Sep 17 00:00:00 2001 From: Johan Kiviniemi Date: Fri, 27 Dec 2013 08:36:12 +0200 Subject: [PATCH] RendertileSet: Round-robin the four top-level subtrees --- overviewer_core/tileset.py | 35 +++++++++++++++------- test/test_rendertileset.py | 60 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 11 deletions(-) diff --git a/overviewer_core/tileset.py b/overviewer_core/tileset.py index 6aad5c0..c958919 100644 --- a/overviewer_core/tileset.py +++ b/overviewer_core/tileset.py @@ -25,7 +25,7 @@ import time import errno import stat from collections import namedtuple -from itertools import product, izip +from itertools import product, izip, chain from PIL import Image @@ -446,7 +446,7 @@ class TileSet(object): # render. Iterate over the tiles in using the posttraversal() method. # Yield each item. Easy. if self.options['renderchecks'] in (0,2): - for tilepath in self.dirtytree.posttraversal(): + for tilepath in self.dirtytree.posttraversal(robin=True): dependencies = [] # These tiles may or may not exist, but the dispatcher won't # care according to the worker interface protocol It will only @@ -1349,7 +1349,7 @@ class RendertileSet(object): def __iter__(self): return self.iterate() - def iterate(self, level=None, offset=(0,0)): + def iterate(self, level=None, robin=False, 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. @@ -1362,6 +1362,9 @@ class RendertileSet(object): In other words, specifying level causes the tree to be iterated as if it was only that depth. + If the `robin` parameter is True, recurses to the four top-level + subtrees simultaneously in a round-robin manner. + """ if level is None: todepth = 1 @@ -1370,18 +1373,22 @@ class RendertileSet(object): raise ValueError("Level parameter must be between 1 and %s" % self.depth) todepth = self.depth - level + 1 - return (tuple(path) for path in self._iterate_helper([], self.children, self.depth, onlydepth=todepth, offset=offset)) + return (tuple(path) for path in self._iterate_helper([], self.children, self.depth, onlydepth=todepth, robin=robin, offset=offset)) - def posttraversal(self, offset=(0,0)): + def posttraversal(self, robin=False, 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)): + If the `robin` parameter is True, recurses to the four top-level + subtrees simultaneously in a round-robin manner. + + """ + return (tuple(path) for path in self._iterate_helper([], self.children, self.depth, robin=robin, offset=offset)) + + def _iterate_helper(self, path, children, depth, onlydepth=None, robin=False, 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. @@ -1395,10 +1402,16 @@ class RendertileSet(object): if child: yield path + [childnum] else: - for (childnum, child), childoffset in distance_sort(enumerate(children_list), offset): + gens = [] + 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 + def go(childnum, childoffset): + for p in self._iterate_helper(path + [childnum], children_list[childnum], depth-1, onlydepth=onlydepth, offset=childoffset): + yield p + gens.append(go(childnum_, childoffset_)) + + for p in roundrobin(gens) if robin else chain(*gens): + yield p if onlydepth is None and children: yield path diff --git a/test/test_rendertileset.py b/test/test_rendertileset.py index d19bda4..943fdfa 100644 --- a/test/test_rendertileset.py +++ b/test/test_rendertileset.py @@ -1,6 +1,7 @@ import unittest from overviewer_core.tileset import iterate_base4, RendertileSet +from overviewer_core.util import roundrobin class RendertileSetTest(unittest.TestCase): # If you change this definition, you must also change the hard-coded @@ -196,6 +197,65 @@ class RendertileSetTest(unittest.TestCase): self.assertRaises(StopIteration, next, iterator) + def test_posttraverse_roundrobin(self): + """Test a round-robin post-traversal of the tree's dirty tiles""" + # Expect these results in this proper order. + expected_lists = [ + [ + (0,0,3), + (0,0,1), + (0,0,2), + (0,0,0), + (0,0), + (0,), + ], + [ + (1,2,0), + (1,2), + + (1,0,3), + (1,0), + + (1,1,3), + (1,1), + (1,), + ], + [ + (2,1,1), + (2,1,0), + (2,1,3), + (2,1,2), + (2,1), + + (2,0,1), + (2,0,3), + (2,0,0), + (2,0,2), + (2,0), + + (2,3,1), + (2,3,0), + (2,3,3), + (2,3,2), + (2,3), + + (2,2,1), + (2,2,0), + (2,2,3), + (2,2,2), + (2,2), + (2,), + ], + ] + expected_list = list(roundrobin(expected_lists)) + [ () ] + + iterator = iter(self.tree.posttraversal(robin=True)) + from itertools import izip + for expected, actual in izip(expected_list, iterator): + self.assertEqual(actual, expected) + + self.assertRaises(StopIteration, next, iterator) + def test_count_all(self): """Tests getting a count of all tiles (render tiles plus upper tiles)