0

Merge remote branch 'xon/dtt-c-render' into dtt-c-render

Conflicts:
	rendernode.py
This commit is contained in:
Aaron Griffith
2011-03-29 04:20:17 -04:00
5 changed files with 76 additions and 73 deletions

View File

@@ -177,8 +177,8 @@ class ChunkRenderer(object):
self.queue = queue self.queue = queue
self.regionfile = worldobj.get_region_path(*chunkcoords) self.regionfile = worldobj.get_region_path(*chunkcoords)
if not os.path.exists(self.regionfile): #if not os.path.exists(self.regionfile):
raise ValueError("Could not find regionfile: %s" % self.regionfile) # raise ValueError("Could not find regionfile: %s" % self.regionfile)
## TODO TODO all of this class ## TODO TODO all of this class
@@ -187,7 +187,7 @@ class ChunkRenderer(object):
#chunkcoords = filename_split[1:3] #chunkcoords = filename_split[1:3]
#self.coords = map(world.base36decode, chunkcoords) #self.coords = map(world.base36decode, chunkcoords)
self.blockid = "%d.%d" % chunkcoords #self.blockid = "%d.%d" % chunkcoords
# chunk coordinates (useful to converting local block coords to # chunk coordinates (useful to converting local block coords to
# global block coords) # global block coords)
@@ -197,12 +197,6 @@ class ChunkRenderer(object):
self.world = worldobj self.world = worldobj
self.rendermode = rendermode self.rendermode = rendermode
if self.world.useBiomeData:
# make sure we've at least *tried* to load the color arrays in this process...
textures.prepareBiomeData(self.world.worlddir)
if not textures.grasscolor or not textures.foliagecolor:
raise Exception("Can't find grasscolor.png or foliagecolor.png")
def _load_level(self): def _load_level(self):
"""Loads and returns the level structure""" """Loads and returns the level structure"""
if not hasattr(self, "_level"): if not hasattr(self, "_level"):
@@ -412,21 +406,6 @@ class ChunkRenderer(object):
For cave mode, all blocks that have any direct sunlight are not For cave mode, all blocks that have any direct sunlight are not
rendered, and blocks are drawn with a color tint depending on their rendered, and blocks are drawn with a color tint depending on their
depth.""" depth."""
blocks = self.blocks
pseudo_ancildata_blocks = set([85])
left_blocks = self.left_blocks
right_blocks = self.right_blocks
if cave:
# Cave mode. Actually go through and 0 out all blocks that are not in a
# cave, so that it only renders caves.
# Places where the skylight is not 0 (there's some amount of skylight
# touching it) change it to something that won't get rendered, AND
# won't get counted as "transparent".
blocks = blocks.copy()
blocks[self.skylight != 0] = 21
blockData = get_blockdata_array(self.level) blockData = get_blockdata_array(self.level)
blockData_expanded = numpy.empty((16,16,128), dtype=numpy.uint8) blockData_expanded = numpy.empty((16,16,128), dtype=numpy.uint8)
@@ -435,7 +414,6 @@ class ChunkRenderer(object):
# Odd elements get the upper 4 bits # Odd elements get the upper 4 bits
blockData_expanded[:,:,1::2] = blockData >> 4 blockData_expanded[:,:,1::2] = blockData >> 4
tileEntities = get_tileentity_data(self.level)
# Each block is 24x24 # Each block is 24x24
# The next block on the X axis adds 12px to x and subtracts 6px from y in the image # The next block on the X axis adds 12px to x and subtracts 6px from y in the image
@@ -449,6 +427,7 @@ class ChunkRenderer(object):
c_overviewer.render_loop(self, img, xoff, yoff, blockData_expanded) c_overviewer.render_loop(self, img, xoff, yoff, blockData_expanded)
tileEntities = get_tileentity_data(self.level)
for entity in tileEntities: for entity in tileEntities:
if entity['id'] == 'Sign': if entity['id'] == 'Sign':
msg=' \n'.join([entity['Text1'], entity['Text2'], entity['Text3'], entity['Text4']]) msg=' \n'.join([entity['Text1'], entity['Text2'], entity['Text3'], entity['Text4']])

View File

@@ -186,7 +186,7 @@ def main():
q.append(qtree) q.append(qtree)
#create the distributed render #create the distributed render
r = rendernode.RenderNode(q, world=w) r = rendernode.RenderNode(q)
# write out the map and web assets # write out the map and web assets
m = googlemap.MapGen(q, skipjs=options.skipjs, web_assets_hook=options.web_assets_hook) m = googlemap.MapGen(q, skipjs=options.skipjs, web_assets_hook=options.web_assets_hook)

View File

@@ -434,6 +434,8 @@ class QuadtreeGen(object):
# Compile this image # Compile this image
tileimg = Image.new("RGBA", (width, height), (38,92,255,0)) tileimg = Image.new("RGBA", (width, height), (38,92,255,0))
world = self.world
rendermode = self.rendermode
# col colstart will get drawn on the image starting at x coordinates -(384/2) # col colstart will get drawn on the image starting at x coordinates -(384/2)
# row rowstart will get drawn on the image starting at y coordinates -(192/2) # row rowstart will get drawn on the image starting at y coordinates -(192/2)
for col, row, chunkx, chunky, regionfile in chunks: for col, row, chunkx, chunky, regionfile in chunks:
@@ -441,8 +443,10 @@ class QuadtreeGen(object):
ypos = -96 + (row-rowstart)*96 ypos = -96 + (row-rowstart)*96
# draw the chunk! # draw the chunk!
# TODO POI queue a = chunk.ChunkRenderer((chunkx, chunky), world, rendermode, poi_queue)
chunk.render_to_image((chunkx, chunky), tileimg, (xpos, ypos), self, False, poi_queue) a.chunk_render(tileimg, xpos, ypos, None)
# chunk.render_to_image((chunkx, chunky), tileimg, (xpos, ypos), self, False, None)
# Save them # Save them
tileimg.save(imgpath) tileimg.save(imgpath)

View File

@@ -60,6 +60,13 @@ def pool_initializer(rendernode):
#stash the quadtree objects in a global variable after fork() for windows compat. #stash the quadtree objects in a global variable after fork() for windows compat.
global child_rendernode global child_rendernode
child_rendernode = rendernode child_rendernode = rendernode
for quadtree in rendernode.quadtrees:
if quadtree.world.useBiomeData:
import textures
# make sure we've at least *tried* to load the color arrays in this process...
textures.prepareBiomeData(quadtree.world.worlddir)
if not textures.grasscolor or not textures.foliagecolor:
raise Exception("Can't find grasscolor.png or foliagecolor.png")
#http://docs.python.org/library/itertools.html #http://docs.python.org/library/itertools.html
def roundrobin(iterables): def roundrobin(iterables):
@@ -77,7 +84,7 @@ def roundrobin(iterables):
class RenderNode(object): class RenderNode(object):
def __init__(self, quadtrees, world): def __init__(self, quadtrees):
"""Distributes the rendering of a list of quadtrees.""" """Distributes the rendering of a list of quadtrees."""
if not len(quadtrees) > 0: if not len(quadtrees) > 0:
@@ -85,17 +92,21 @@ class RenderNode(object):
self.quadtrees = quadtrees self.quadtrees = quadtrees
#bind an index value to the quadtree so we can find it again #bind an index value to the quadtree so we can find it again
#and figure out which worlds are where
i = 0 i = 0
self.worlds = []
for q in quadtrees: for q in quadtrees:
q._render_index = i q._render_index = i
i += 1 i += 1
if q.world not in self.worlds:
self.worlds.append(q.world)
manager = multiprocessing.Manager()
# queue for receiving interesting events from the renderer # queue for receiving interesting events from the renderer
# (like the discovery of signs! # (like the discovery of signs!
self.manager = multiprocessing.Manager() #stash into the world object like we stash an index into the quadtree
self.poi_q = self.manager.Queue() for world in self.worlds:
world.poi_q = manager.Queue()
self.world = world
def print_statusline(self, complete, total, level, unconditional=False): def print_statusline(self, complete, total, level, unconditional=False):
@@ -120,12 +131,14 @@ class RenderNode(object):
# Create a pool # Create a pool
if procs == 1: if procs == 1:
pool = FakePool() pool = FakePool()
global child_rendernode pool_initializer(self)
child_rendernode = self
else: else:
pool = multiprocessing.Pool(processes=procs,initializer=pool_initializer,initargs=(self,)) pool = multiprocessing.Pool(processes=procs,initializer=pool_initializer,initargs=(self,))
#warm up the pool so it reports all the worker id's #warm up the pool so it reports all the worker id's
pool.map(bool,xrange(multiprocessing.cpu_count()),1) if logging.getLogger().level >= 10:
pool.map(bool,xrange(multiprocessing.cpu_count()),1)
else:
pool.map_async(bool,xrange(multiprocessing.cpu_count()),1)
quadtrees = self.quadtrees quadtrees = self.quadtrees
@@ -160,18 +173,19 @@ class RenderNode(object):
timestamp = timestamp2 timestamp = timestamp2
count_to_remove = (1000//batch_size) count_to_remove = (1000//batch_size)
if count_to_remove < len(results): if count_to_remove < len(results):
try: for world in self.worlds:
while (1): try:
# an exception will break us out of this loop while (1):
item = self.poi_q.get(block=False) # an exception will break us out of this loop
if item[0] == "newpoi": item = world.poi_q.get(block=False)
if item[1] not in self.world.POI: if item[0] == "newpoi":
#print "got an item from the queue!" if item[1] not in world.POI:
self.world.POI.append(item[1]) #print "got an item from the queue!"
elif item[0] == "removePOI": world.POI.append(item[1])
self.world.persistentData['POI'] = filter(lambda x: x['chunk'] != item[1], self.world.persistentData['POI']) elif item[0] == "removePOI":
except Queue.Empty: world.persistentData['POI'] = filter(lambda x: x['chunk'] != item[1], world.persistentData['POI'])
pass except Queue.Empty:
pass
while count_to_remove > 0: while count_to_remove > 0:
count_to_remove -= 1 count_to_remove -= 1
complete += results.popleft().get() complete += results.popleft().get()
@@ -187,18 +201,19 @@ class RenderNode(object):
while len(results) > 0: while len(results) > 0:
complete += results.popleft().get() complete += results.popleft().get()
self.print_statusline(complete, total, 1) self.print_statusline(complete, total, 1)
try: for world in self.worlds:
while (1): try:
# an exception will break us out of this loop while (1):
item = self.poi_q.get(block=False) # an exception will break us out of this loop
if item[0] == "newpoi": item = world.poi_q.get(block=False)
if item[1] not in self.world.POI: if item[0] == "newpoi":
#print "got an item from the queue!" if item[1] not in world.POI:
self.world.POI.append(item[1]) #print "got an item from the queue!"
elif item[0] == "removePOI": world.POI.append(item[1])
self.world.persistentData['POI'] = filter(lambda x: x['chunk'] != item[1], self.world.persistentData['POI']) elif item[0] == "removePOI":
except Queue.Empty: world.persistentData['POI'] = filter(lambda x: x['chunk'] != item[1], world.persistentData['POI'])
pass except Queue.Empty:
pass
self.print_statusline(complete, total, 1, True) self.print_statusline(complete, total, 1, True)
@@ -263,10 +278,10 @@ class RenderNode(object):
jobcount += 1 jobcount += 1
if jobcount >= batch_size: if jobcount >= batch_size:
jobcount = 0 jobcount = 0
yield pool.apply_async(func=render_worldtile_batch, args= [batch, self.poi_q]) yield pool.apply_async(func=render_worldtile_batch, args= [batch])
batch = [] batch = []
if jobcount > 0: if jobcount > 0:
yield pool.apply_async(func=render_worldtile_batch, args= [batch, self.poi_q]) yield pool.apply_async(func=render_worldtile_batch, args= [batch])
def _apply_render_inntertile(self, pool, zoom,batch_size): def _apply_render_inntertile(self, pool, zoom,batch_size):
"""Same as _apply_render_worltiles but for the inntertile routine. """Same as _apply_render_worltiles but for the inntertile routine.
@@ -295,7 +310,7 @@ class RenderNode(object):
yield pool.apply_async(func=render_innertile_batch, args= [batch]) yield pool.apply_async(func=render_innertile_batch, args= [batch])
@catch_keyboardinterrupt @catch_keyboardinterrupt
def render_worldtile_batch(batch, poi_queue): def render_worldtile_batch(batch):
global child_rendernode global child_rendernode
rendernode = child_rendernode rendernode = child_rendernode
count = 0 count = 0
@@ -308,6 +323,7 @@ def render_worldtile_batch(batch, poi_queue):
rowstart = job[3] rowstart = job[3]
rowend = job[4] rowend = job[4]
path = job[5] path = job[5]
poi_queue = quadtree.world.poi_q
path = quadtree.full_tiledir+os.sep+path path = quadtree.full_tiledir+os.sep+path
# (even if tilechunks is empty, render_worldtile will delete # (even if tilechunks is empty, render_worldtile will delete
# existing images if appropriate) # existing images if appropriate)

View File

@@ -84,9 +84,10 @@ class World(object):
self.regionfiles = regionfiles self.regionfiles = regionfiles
# set the number of region file handles we will permit open at any time before we start closing them # set the number of region file handles we will permit open at any time before we start closing them
# self.regionlimit = 1000 # self.regionlimit = 1000
# the max number of chunks we will keep before removing them # the max number of chunks we will keep before removing them (includes emptry chunks)
self.chunklimit = 1024*6 # this should be a multipule of the max chunks per region or things could get wonky ??? self.chunklimit = 1024
self.chunkcount = 0 self.chunkcount = 0
self.empty_chunk = [None,None]
logging.debug("Done scanning regions") logging.debug("Done scanning regions")
# figure out chunk format is in use # figure out chunk format is in use
@@ -117,6 +118,7 @@ class World(object):
# some defaults # some defaults
self.persistentData = dict(POI=[]) self.persistentData = dict(POI=[])
def get_region_path(self, chunkX, chunkY): def get_region_path(self, chunkX, chunkY):
"""Returns the path to the region that contains chunk (chunkX, chunkY) """Returns the path to the region that contains chunk (chunkX, chunkY)
""" """
@@ -131,16 +133,18 @@ class World(object):
chunks = regioninfo[2] chunks = regioninfo[2]
chunk_data = chunks.get((x,y)) chunk_data = chunks.get((x,y))
if chunk_data is None: if chunk_data is None:
nbt = self.load_region(filename).load_chunk(x, y)
if nbt is None:
chunks[(x,y)] = [None,None]
return None ## return none. I think this is who we should indicate missing chunks
#raise IOError("No such chunk in region: (%i, %i)" % (x, y))
#prune the cache if required #prune the cache if required
if self.chunkcount > self.chunklimit: #todo: make the emptying the chunk cache slightly less crazy if self.chunkcount > self.chunklimit: #todo: make the emptying the chunk cache slightly less crazy
[self.reload_region(regionfile) for regionfile in self.regions if regionfile <> filename] [self.reload_region(regionfile) for regionfile in self.regions if regionfile <> filename]
self.chunkcount = 0
self.chunkcount += 1 self.chunkcount += 1
nbt = self.load_region(filename).load_chunk(x, y)
if nbt is None:
chunks[(x,y)] = self.empty_chunk
return None ## return none. I think this is who we should indicate missing chunks
#raise IOError("No such chunk in region: (%i, %i)" % (x, y))
#we cache the transformed data, not it's raw form #we cache the transformed data, not it's raw form
data = nbt.read_all() data = nbt.read_all()
level = data[1]['Level'] level = data[1]['Level']