From 03566f957f0352761807ba9cfee7de7ea491d01b Mon Sep 17 00:00:00 2001 From: David Vierra Date: Mon, 12 Nov 2012 17:43:27 -1000 Subject: [PATCH] Added: Add copyChunkFrom to MCInfdevOldLevel and MCRegionFile. This function copies a single chunk from the same location in another world. This is much faster than using copyBlocksFrom because it doesn't have to adjust entity locations or even fully load the chunk if it's not necessary. --- infiniteworld.py | 46 +++++++++++++++++++++++++++++++++++++++++++++- regionfile.py | 23 ++++++++++++++++++++--- test/anvil_test.py | 10 ++++++++++ 3 files changed, 75 insertions(+), 4 deletions(-) diff --git a/infiniteworld.py b/infiniteworld.py index 7f666cf..ad7576d 100644 --- a/infiniteworld.py +++ b/infiniteworld.py @@ -4,6 +4,7 @@ Created on Jul 22, 2011 @author: Rio ''' +import copy from datetime import datetime import itertools from logging import getLogger @@ -971,13 +972,17 @@ class AnvilWorldFolder(object): def readChunk(self, cx, cz): if not self.containsChunk(cx, cz): raise ChunkNotPresent((cx, cz)) - + return self.getRegionForChunk(cx, cz).readChunk(cx, cz) def saveChunk(self, cx, cz, data): regionFile = self.getRegionForChunk(cx, cz) regionFile.saveChunk(cx, cz, data) + def copyChunkFrom(self, worldFolder, cx, cz): + fromRF = worldFolder.getRegionForChunk(cx, cz) + rf = self.getRegionForChunk(cx, cz) + rf.copyChunkFrom(fromRF, cx, cz) class MCInfdevOldLevel(ChunkedLevelMixin, EntityLevel): @@ -1329,6 +1334,45 @@ class MCInfdevOldLevel(ChunkedLevelMixin, EntityLevel): self.preloadChunkPositions() return self._allChunks.__iter__() + def copyChunkFrom(self, world, cx, cz): + """ + Copy a chunk from world into the same chunk position in self. + """ + assert isinstance(world, MCInfdevOldLevel) + + destChunk = self._loadedChunks.get((cx, cz)) + sourceChunk = world._loadedChunks.get((cx, cz)) + + if sourceChunk: + if destChunk: + # Both chunks loaded. Use block copy. + destChunk.copyBlocksFrom(sourceChunk, destChunk.bounds, destChunk.bounds.origin) + return + else: + # Only source chunk loaded. Discard destination chunk and save source chunk in its place. + self._loadedChunkData.pop((cx, cz), None) + self.unsavedWorkFolder.saveChunk(cx, cz, sourceChunk.savedTagData()) + return + else: + if destChunk: + # Only destination chunk loaded. Use block copy. + destChunk.copyBlocksFrom(world.getChunk(cx, cz), destChunk.bounds, destChunk.bounds.origin) + else: + # Neither chunk loaded. Copy via world folders. + self._loadedChunkData.pop((cx, cz), None) + + # If the source chunk is dirty, write it to the work folder. + chunkData = world._loadedChunkData.pop((cx, cz), None) + if chunkData and chunkData.dirty: + data = chunkData.savedTagData() + world.unsavedWorkFolder.saveChunk(cx, cz, data) + + if world.unsavedWorkFolder.containsChunk(cx, cz): + sourceFolder = world.unsavedWorkFolder + else: + sourceFolder = world.worldFolder + + self.unsavedWorkFolder.copyChunkFrom(sourceFolder, cx, cz) def _getChunkBytes(self, cx, cz): try: diff --git a/regionfile.py b/regionfile.py index 1451b41..dce3635 100644 --- a/regionfile.py +++ b/regionfile.py @@ -167,7 +167,7 @@ class MCRegionFile(object): log.info("Repair complete. Removed {0} chunks, recovered {1} chunks, net {2}".format(deleted, recovered, recovered - deleted)) - def readChunk(self, cx, cz): + def _readChunk(self, cx, cz): cx &= 0x1f cz &= 0x1f offset = self.getOffset(cx, cz) @@ -191,7 +191,10 @@ class MCRegionFile(object): length = struct.unpack_from(">I", data)[0] format = struct.unpack_from("B", data, 4)[0] data = data[5:length + 5] + return data, format + def readChunk(self, cx, cz): + data, format = self._readChunk(cx, cz) if format == self.VERSION_GZIP: return nbt.gunzip(data) if format == self.VERSION_DEFLATE: @@ -199,14 +202,28 @@ class MCRegionFile(object): raise IOError("Unknown compress format: {0}".format(format)) + def copyChunkFrom(self, regionFile, cx, cz): + """ + Silently fails if regionFile does not contain the requested chunk. + """ + try: + data, format = regionFile._readChunk(cx, cz) + self._saveChunk(cx, cz, data, format) + except ChunkNotPresent: + pass + def saveChunk(self, cx, cz, uncompressedData): + data = deflate(uncompressedData) + self._saveChunk(cx, cz, data, self.VERSION_DEFLATE) + + def _saveChunk(self, cx, cz, data, format): cx &= 0x1f cz &= 0x1f offset = self.getOffset(cx, cz) sectorNumber = offset >> 8 sectorsAllocated = offset & 0xff - format = self.VERSION_DEFLATE - data = deflate(uncompressedData) + + sectorsNeeded = (len(data) + self.CHUNK_HEADER_SIZE) / self.SECTOR_BYTES + 1 if sectorsNeeded >= 256: diff --git a/test/anvil_test.py b/test/anvil_test.py index 1404365..f7b48f7 100644 --- a/test/anvil_test.py +++ b/test/anvil_test.py @@ -45,6 +45,16 @@ class TestAnvilLevel(unittest.TestCase): level.deleteChunk(*ch) level.createChunksInBox(BoundingBox((0, 0, 0), (32, 0, 32))) + def testCopyChunks(self): + level = self.anvilLevel.level + temppath = mktemp("AnvilCreate") + newLevel = MCInfdevOldLevel(filename=temppath, create=True) + for cx, cz in level.allChunks: + newLevel.copyChunkFrom(level, cx, cz) + + newLevel.close() + shutil.rmtree(temppath) + def testCopyConvertBlocks(self): indevlevel = self.indevLevel.level level = self.anvilLevel.level