Merge remote branch 'xon/dtt-c-render' into dtt-c-render
Conflicts: rendernode.py
This commit is contained in:
29
chunk.py
29
chunk.py
@@ -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,22 +406,7 @@ 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)
|
||||||
# Even elements get the lower 4 bits
|
# Even elements get the lower 4 bits
|
||||||
@@ -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']])
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -60,7 +60,14 @@ 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):
|
||||||
"roundrobin('ABC', 'D', 'EF') --> A D E B F C"
|
"roundrobin('ABC', 'D', 'EF') --> A D E B F C"
|
||||||
@@ -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)
|
||||||
|
|||||||
20
world.py
20
world.py
@@ -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
|
||||||
@@ -116,7 +117,8 @@ class World(object):
|
|||||||
else:
|
else:
|
||||||
# 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']
|
||||||
|
|||||||
Reference in New Issue
Block a user