Storage: Modified, unsaved chunks are stored on disk instead of being retained in memory.

This allows potentially unlimited numbers of chunks to be modified in a single session.
This commit is contained in:
David Vierra 2012-10-29 06:27:59 -10:00
parent 94bfb1ab43
commit 6713404fe2

View File

@ -1247,6 +1247,14 @@ class MCInfdevOldLevel(ChunkedLevelMixin, EntityLevel):
self.worldFolder = AnvilWorldFolder(filename) self.worldFolder = AnvilWorldFolder(filename)
self.filename = self.worldFolder.getFilePath("level.dat") self.filename = self.worldFolder.getFilePath("level.dat")
workFolderPath = self.worldFolder.getFolderPath("##MCEDIT.TEMP##")
if os.path.exists(workFolderPath):
# xxxxxxx Opening a world a second time deletes the first world's work folder and crashes when the first
# world tries to read a modified chunk from the work folder. This mainly happens when importing a world
# into itself after modifying it.
shutil.rmtree(workFolderPath, True)
self.unsavedWorkFolder = AnvilWorldFolder(workFolderPath)
# maps (cx, cz) pairs to AnvilChunk # maps (cx, cz) pairs to AnvilChunk
self._loadedChunks = weakref.WeakValueDictionary() self._loadedChunks = weakref.WeakValueDictionary()
@ -1334,6 +1342,13 @@ class MCInfdevOldLevel(ChunkedLevelMixin, EntityLevel):
self.worldFolder.saveChunk(cx, cz, data) self.worldFolder.saveChunk(cx, cz, data)
chunk.dirty = False chunk.dirty = False
for cx, cz in self.unsavedWorkFolder.listChunks():
data = self.unsavedWorkFolder.readChunk(cx, cz)
self.worldFolder.saveChunk(cx, cz, data)
self.unsavedWorkFolder.closeRegions()
shutil.rmtree(self.unsavedWorkFolder.filename, True)
os.mkdir(self.unsavedWorkFolder.filename)
for path, tag in self.playerTagCache.iteritems(): for path, tag in self.playerTagCache.iteritems():
tag.save(path) tag.save(path)
@ -1348,11 +1363,17 @@ class MCInfdevOldLevel(ChunkedLevelMixin, EntityLevel):
Unload all chunks and close all open filehandles. Discard any unsaved data. Unload all chunks and close all open filehandles. Discard any unsaved data.
""" """
self.worldFolder.closeRegions() self.worldFolder.closeRegions()
self.unsavedWorkFolder.closeRegions()
shutil.rmtree(self.unsavedWorkFolder.filename, True)
self._allChunks = None self._allChunks = None
self._loadedChunks.clear() self._loadedChunks.clear()
self._loadedChunkData.clear() self._loadedChunkData.clear()
# --- Resource limits ---
loadedChunkLimit = 400
# --- Constants --- # --- Constants ---
GAMETYPE_SURVIVAL = 0 GAMETYPE_SURVIVAL = 0
@ -1543,6 +1564,9 @@ class MCInfdevOldLevel(ChunkedLevelMixin, EntityLevel):
return self._allChunks.__iter__() return self._allChunks.__iter__()
def _getChunkBytes(self, cx, cz): def _getChunkBytes(self, cx, cz):
data = self.unsavedWorkFolder.readChunk(cx, cz)
if data: return data
return self.worldFolder.readChunk(cx, cz) return self.worldFolder.readChunk(cx, cz)
def _getChunkData(self, cx, cz): def _getChunkData(self, cx, cz):
@ -1558,6 +1582,19 @@ class MCInfdevOldLevel(ChunkedLevelMixin, EntityLevel):
raise ChunkMalformed, "Chunk {0} had an error: {1!r}".format((cx, cz), e), sys.exc_info()[2] raise ChunkMalformed, "Chunk {0} had an error: {1!r}".format((cx, cz), e), sys.exc_info()[2]
chunkData = AnvilChunkData(self, (cx, cz), root_tag) chunkData = AnvilChunkData(self, (cx, cz), root_tag)
if len(self._loadedChunkData) > self.loadedChunkLimit:
# Try to find a chunk to unload. The chunk must not be in _loadedChunks, which contains only chunks that
# are in use by another object. If the chunk is dirty, save it to the temporary folder.
for (ocx, ocz), oldChunkData in self._loadedChunkData.items():
if (ocx, ocz) not in self._loadedChunks:
if oldChunkData.dirty:
data = oldChunkData.savedTagData()
self.unsavedWorkFolder.saveChunk(ocx, ocz, data)
del self._loadedChunkData[ocx, ocz]
break
self._loadedChunkData[cx, cz] = chunkData self._loadedChunkData[cx, cz] = chunkData
return chunkData return chunkData
@ -1704,7 +1741,7 @@ class MCInfdevOldLevel(ChunkedLevelMixin, EntityLevel):
self.worldFolder.deleteChunk(cx, cz) self.worldFolder.deleteChunk(cx, cz)
if self._allChunks is not None: if self._allChunks is not None:
self._allChunks.discard((cx, cz)) self._allChunks.discard((cx, cz))
self._bounds = None self._bounds = None