0

added a "changelist" option

This commit is contained in:
Andrew Brown
2012-03-06 23:37:29 -05:00
parent 1f7ccdaa93
commit fe55d5343c
4 changed files with 101 additions and 39 deletions

View File

@@ -426,6 +426,23 @@ values. The valid configuration keys are listed below.
'forcerender': True, 'forcerender': True,
} }
``changelist``
This is a string. It names a file where it will write out, one per line, the
path to tiles that have been updated. You can specify the same file for
multiple (or all) renders and they will all be written to the same file. The
file is cleared when The Overviewer starts.
This option is useful in conjunction with a simple upload script, to upload
the files that have changed.
.. warning::
A solution like ``rsync -a --delete`` is much better because it also
watches for tiles that should be *deleted*, which is impossible to
convey with the changelist option. If your map ever shrinks or you've
removed some tiles, you may need to do some manual deletion on the
remote side.
.. _customrendermodes: .. _customrendermodes:
Custom Rendermodes and Rendermode Primitives Custom Rendermodes and Rendermode Primitives

View File

@@ -253,8 +253,11 @@ dir but you forgot to put quotes around the directory, since it contains spaces.
return 1 return 1
############################################################ ############################################################
# Final validation steps and creation of the destination directory # Final validation steps and creation of the destination directory
logging.info("Welcome to Minecraft Overviewer!")
logging.debug("Current log level: {0}".format(logging.getLogger().level))
# Override some render configdict options depending on one-time command line # Override some render configdict options depending on one-time command line
# modifiers # modifiers
@@ -315,13 +318,23 @@ dir but you forgot to put quotes around the directory, since it contains spaces.
logging.exception("Could not create the output directory.") logging.exception("Could not create the output directory.")
return 1 return 1
# The changelist support.
changelists = {}
for render in config['renders'].itervalues():
if 'changelist' in render:
path = render['changelist']
if path not in changelists:
out = open(path, "w")
logging.debug("Opening changelist %s (%s)", out, out.fileno())
changelists[path] = out
else:
out = changelists[path]
render['changelist'] = out.fileno()
######################################################################## ########################################################################
# Now we start the actual processing, now that all the configuration has # Now we start the actual processing, now that all the configuration has
# been gathered and validated # been gathered and validated
logging.info("Welcome to Minecraft Overviewer!")
logging.debug("Current log level: {0}".format(logging.getLogger().level))
# create our asset manager... ASSMAN # create our asset manager... ASSMAN
assetMrg = assetmanager.AssetManager(destdir) assetMrg = assetmanager.AssetManager(destdir)
@@ -394,7 +407,7 @@ dir but you forgot to put quotes around the directory, since it contains spaces.
# only pass to the TileSet the options it really cares about # only pass to the TileSet the options it really cares about
render['name'] = render_name # perhaps a hack. This is stored here for the asset manager render['name'] = render_name # perhaps a hack. This is stored here for the asset manager
tileSetOpts = util.dict_subset(render, ["name", "imgformat", "renderchecks", "rerenderprob", "bgcolor", "imgquality", "optimizeimg", "rendermode", "worldname_orig", "title", "dimension"]) tileSetOpts = util.dict_subset(render, ["name", "imgformat", "renderchecks", "rerenderprob", "bgcolor", "imgquality", "optimizeimg", "rendermode", "worldname_orig", "title", "dimension", "changelist"])
tset = tileset.TileSet(rset, assetMrg, tex, tileSetOpts, tileset_dir) tset = tileset.TileSet(rset, assetMrg, tex, tileSetOpts, tileset_dir)
tilesets.append(tset) tilesets.append(tset)
@@ -427,6 +440,10 @@ dir but you forgot to put quotes around the directory, since it contains spaces.
assetMrg.finalize(tilesets) assetMrg.finalize(tilesets)
for out in changelists.itervalues():
logging.debug("Closing %s (%s)", out, out.fileno())
out.close()
if config['processes'] == 1: if config['processes'] == 1:
logging.debug("Final cache stats:") logging.debug("Final cache stats:")
for c in caches: for c in caches:

View File

@@ -74,6 +74,7 @@ renders = Setting(required=True, default=util.OrderedDict(),
"renderchecks": Setting(required=False, validator=validateInt, default=None), "renderchecks": Setting(required=False, validator=validateInt, default=None),
"rerenderprob": Setting(required=True, validator=validateFloat, default=0), "rerenderprob": Setting(required=True, validator=validateFloat, default=0),
"crop": Setting(required=False, validator=validateCrop, default=None), "crop": Setting(required=False, validator=validateCrop, default=None),
"changelist": Setting(required=False, validator=validateStr, default=None),
# Remove this eventually (once people update their configs) # Remove this eventually (once people update their configs)
"worldname": Setting(required=False, default=None, "worldname": Setting(required=False, default=None,

View File

@@ -97,57 +97,57 @@ Bounds = namedtuple("Bounds", ("mincol", "maxcol", "minrow", "maxrow"))
# #
# For reference, here's what the rendercheck modes are: # For reference, here's what the rendercheck modes are:
# 0 # 0
# Only render tiles that have chunks with a greater mtime than # Only render tiles that have chunks with a greater mtime than the last
# the last render timestamp, and their ancestors. # render timestamp, and their ancestors.
# #
# In other words, only renders parts of the map that have changed # In other words, only renders parts of the map that have changed since
# since last render, nothing more, nothing less. # last render, nothing more, nothing less.
# #
# This is the fastest option, but will not detect tiles that have # This is the fastest option, but will not detect tiles that have e.g.
# e.g. been deleted from the directory tree, or pick up where a # been deleted from the directory tree, or pick up where a partial
# partial interrupted render left off. # interrupted render left off.
# 1 # 1
# For render-tiles, render all whose chunks have an mtime greater # For render-tiles, render all whose chunks have an mtime greater than
# than the mtime of the tile on disk, and their upper-tile # the mtime of the tile on disk, and their composite-tile ancestors.
# ancestors.
# #
# Also check all other upper-tiles and render any that have # Also check all other composite-tiles and render any that have children
# children with more rencent mtimes than itself. # with more rencent mtimes than itself.
# #
# This is slower due to stat calls to determine tile mtimes, but # This is slower due to stat calls to determine tile mtimes, but safe if
# safe if the last render was interrupted. # the last render was interrupted.
# 2 # 2
# Render all tiles unconditionally. This is a "forcerender" and # Render all tiles unconditionally. This is a "forcerender" and is the
# is the slowest, but SHOULD be specified if this is the first # slowest, but SHOULD be specified if this is the first render because
# render because the scan will forgo tile stat calls. It's also # the scan will forgo tile stat calls. It's also useful for changing
# useful for changing texture packs or other options that effect # texture packs or other options that effect the output.
# the output.
# #
# For 0 our caller has explicitly requested not to check mtimes on disk to # For 0 our caller has explicitly requested not to check mtimes on disk to
# speed things up. So the mode 0 chunk scan only looks at chunk mtimes and the # speed things up. So the mode 0 chunk scan only looks at chunk mtimes and the
# last render mtime, and has marked only the render-tiles that need rendering. # last render mtime from the asset manager, and marks only the tiles that need
# Mode 0 then iterates over all dirty render-tiles and upper-tiles that depend # rendering based on that. Mode 0 then iterates over all dirty render-tiles
# on them. It does not check mtimes of upper-tiles, so this is only a good # and composite-tiles that depend on them. It does not check mtimes of any
# option if the last render was not interrupted. # tiles on disk, so this is only a good option if the last render was not
# interrupted.
# For mode 2, this is a forcerender, the caller has requested we render # For mode 2, this is a forcerender, the caller has requested we render
# everything. The mode 2 chunk scan marks every tile as needing rendering, and # everything. The mode 2 chunk scan marks every tile as needing rendering, and
# disregards mtimes completely. Mode 2 then iterates over all render-tiles and # disregards mtimes completely. Mode 2 then iterates over all render-tiles and
# upper-tiles that depend on them, which is every tile that should exist. # composite-tiles that depend on them, which is every tile. It therefore
# renders everything.
# In both 0 and 2 the render iteration is the same: the dirtytile tree built is # In both 0 and 2 the render iteration is the same: the dirtytile tree built is
# authoritive on every tile that needs rendering. # authoritive on every tile that needs rendering.
# In mode 1, things are most complicated. Mode 1 chunk scan is identical to a # In mode 1, things are most complicated. Mode 1 chunk scan is identical to a
# forcerender, or mode 2 scan: every render tile that should exist is marked in # forcerender, or mode 2 scan: every render tile that should exist is marked in
# the dirtytile tree. Then, a special recursive algorithm goes through and # the dirtytile tree. But instead of iterating over that tree directly, a
# checks every tile that should exist and determines whether it needs # special recursive algorithm goes through and checks every tile that should
# rendering. This routine works in such a way so that every tile is stat()'d at # exist and determines whether it needs rendering. This routine works in such a
# most once, so it shouldn't be too bad. This logic happens in the # way so that every tile is stat()'d at most once, so it shouldn't be too bad.
# iterate_work_items() method, and therefore in the master process, not the # This logic happens in the iterate_work_items() method, and therefore in the
# worker processes. # master process, not the worker processes.
# In all three rendercheck modes, the results out of iterate_work_items() is # In all three rendercheck modes, the results out of iterate_work_items() is
# authoritive on what needs rendering. The do_work() method does not need to do # authoritive on what needs rendering. The do_work() method does not need to do
@@ -253,6 +253,11 @@ class TileSet(object):
that a tile which is not marked for render by any mtime checks will that a tile which is not marked for render by any mtime checks will
be rendered anyways. 0 disables this option. be rendered anyways. 0 disables this option.
changelist
Optional: A file descriptor which will be opened and used as the
changelist output: each tile written will get outputted to the
specified fd.
Other options that must be specified but aren't really documented Other options that must be specified but aren't really documented
(oops. consider it a TODO): (oops. consider it a TODO):
* worldname_orig * worldname_orig
@@ -370,6 +375,29 @@ class TileSet(object):
This method returns an iterator over (obj, [dependencies, ...]) This method returns an iterator over (obj, [dependencies, ...])
""" """
fd = self.options.get("changelist", None)
if fd:
logging.debug("Changelist activated for %s (fileno %s)", self, fd)
# This re-implements some of the logic from do_work()
def write_out(tilepath):
if len(tilepath) == self.treedepth:
rt = RenderTile.from_path(tilepath)
imgpath = rt.get_filepath(self.outputdir, self.imgextension)
elif len(tilepath) == 0:
imgpath = os.path.join(self.outputdir, "base."+self.imgextension)
else:
dest = os.path.join(self.outputdir, *(str(x) for x in tilepath[:-1]))
name = str(tilepath[-1])
imgpath = os.path.join(dest, name) + "." + self.imgextension
# We use low-level file output because we don't want open file
# handles being passed to subprocesses. fd is just an integer.
# This method is only called from the master process anyways.
# We don't use os.fdopen() because this fd may be shared by
# many tileset objects, and as soon as this method exists the
# file object may be garbage collected, closing the file.
os.write(fd, imgpath + "\n")
# See note at the top of this file about the rendercheck modes for an # See note at the top of this file about the rendercheck modes for an
# explanation of what this method does in different situations. # explanation of what this method does in different situations.
# #
@@ -384,6 +412,8 @@ class TileSet(object):
# wait for the items that do exist and are in the queue. # wait for the items that do exist and are in the queue.
for i in range(4): for i in range(4):
dependencies.append( tilepath + (i,) ) dependencies.append( tilepath + (i,) )
if fd:
write_out(tilepath)
yield tilepath, dependencies yield tilepath, dependencies
else: else:
@@ -395,9 +425,10 @@ class TileSet(object):
dependencies = [] dependencies = []
for i in range(4): for i in range(4):
dependencies.append( tilepath + (i,) ) dependencies.append( tilepath + (i,) )
if fd:
write_out(tilepath)
yield tilepath, dependencies yield tilepath, dependencies
def do_work(self, tilepath): def do_work(self, tilepath):
"""Renders the given tile. """Renders the given tile.
@@ -405,10 +436,6 @@ class TileSet(object):
integers representing the path of the tile to render. integers representing the path of the tile to render.
""" """
# For rendercheck modes 0 and 2: unconditionally render the specified
# tile.
# For rendercheck mode 1, unconditionally render render-tiles, but
# check if the given upper-tile needs rendering
if len(tilepath) == self.treedepth: if len(tilepath) == self.treedepth:
# A render-tile # A render-tile
self._render_rendertile(RenderTile.from_path(tilepath)) self._render_rendertile(RenderTile.from_path(tilepath))