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.
This commit is contained in:
David Vierra 2012-11-12 17:43:27 -10:00
parent ec52850bd6
commit 03566f957f
3 changed files with 75 additions and 4 deletions

View File

@ -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:

View File

@ -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:

View File

@ -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