package mapwriter; import java.util.Map; import mapwriter.region.MwChunk; import mapwriter.tasks.SaveChunkTask; import mapwriter.tasks.UpdateSurfaceChunksTask; import net.minecraft.world.chunk.Chunk; import net.minecraft.world.chunk.storage.ExtendedBlockStorage; public class ChunkManager { public Mw mw; private boolean closed = false; private CircularHashMap<Chunk, Integer> chunkMap = new CircularHashMap<Chunk, Integer>(); private static final int VISIBLE_FLAG = 0x01; private static final int VIEWED_FLAG = 0x02; public ChunkManager(Mw mw) { this.mw = mw; } public synchronized void close() { this.closed = true; this.saveChunks(); this.chunkMap.clear(); } // create MwChunk from Minecraft chunk. // only MwChunk's should be used in the background thread. // TODO: make this a full copy of chunk data to prevent possible race conditions public static MwChunk copyToMwChunk(Chunk chunk) { byte[][] msbArray = new byte[16][]; byte[][] lsbArray = new byte[16][]; byte[][] metaArray = new byte[16][]; byte[][] lightingArray = new byte[16][]; ExtendedBlockStorage[] storageArrays = chunk.getBlockStorageArray(); if (storageArrays != null) { for (ExtendedBlockStorage storage : storageArrays) { if (storage != null) { int y = (storage.getYLocation() >> 4) & 0xf; lsbArray[y] = storage.getBlockLSBArray(); msbArray[y] = (storage.getBlockMSBArray() != null) ? storage.getBlockMSBArray().data : null; metaArray[y] = (storage.getMetadataArray() != null) ? storage.getMetadataArray().data : null; lightingArray[y] = (storage.getBlocklightArray() != null) ? storage.getBlocklightArray().data : null; } } } return new MwChunk(chunk.xPosition, chunk.zPosition, chunk.worldObj.provider.dimensionId, msbArray, lsbArray, metaArray, lightingArray, chunk.getBiomeArray()); } public synchronized void addChunk(Chunk chunk) { if (!this.closed && (chunk != null)) { this.chunkMap.put(chunk, 0); } } public synchronized void removeChunk(Chunk chunk) { if (!this.closed && (chunk != null)) { if(!this.chunkMap.containsKey(chunk)) return; //FIXME: Is this failsafe enough for unloading? int flags = this.chunkMap.get(chunk); if ((flags & VIEWED_FLAG) != 0) { this.addSaveChunkTask(chunk); } this.chunkMap.remove(chunk); } } public synchronized void saveChunks() { for (Map.Entry<Chunk, Integer> entry : this.chunkMap.entrySet()) { int flags = entry.getValue(); if ((flags & VIEWED_FLAG) != 0) { this.addSaveChunkTask(entry.getKey()); } } } public void updateUndergroundChunks() { int chunkArrayX = (this.mw.playerXInt >> 4) - 1; int chunkArrayZ = (this.mw.playerZInt >> 4) - 1; MwChunk[] chunkArray = new MwChunk[9]; for (int z = 0; z < 3; z++) { for (int x = 0; x < 3; x++) { Chunk chunk = this.mw.mc.theWorld.getChunkFromChunkCoords( chunkArrayX + x, chunkArrayZ + z ); if (!chunk.isEmpty()) { chunkArray[(z * 3) + x] = copyToMwChunk(chunk); } } } } public void updateSurfaceChunks() { int chunksToUpdate = Math.min(this.chunkMap.size(), this.mw.chunksPerTick); MwChunk[] chunkArray = new MwChunk[chunksToUpdate]; for (int i = 0; i < chunksToUpdate; i++) { Map.Entry<Chunk, Integer> entry = this.chunkMap.getNextEntry(); if (entry != null) { // if this chunk is within a certain distance to the player then // add it to the viewed set Chunk chunk = entry.getKey(); int flags = entry.getValue(); if (MwUtil.distToChunkSq(this.mw.playerXInt, this.mw.playerZInt, chunk) <= this.mw.maxChunkSaveDistSq) { flags |= (VISIBLE_FLAG | VIEWED_FLAG); } else { flags &= ~VISIBLE_FLAG; } entry.setValue(flags); if ((flags & VISIBLE_FLAG) != 0) { chunkArray[i] = copyToMwChunk(chunk); } else { chunkArray[i] = null; } } } this.mw.executor.addTask(new UpdateSurfaceChunksTask(this.mw, chunkArray)); } public void onTick() { if (!this.closed) { if ((this.mw.tickCounter & 0xf) == 0) { this.updateUndergroundChunks(); } else { this.updateSurfaceChunks(); } } } public void forceChunks(MwChunk[] chunkArray){ this.mw.executor.addTask(new UpdateSurfaceChunksTask(this.mw, chunkArray)); } private void addSaveChunkTask(Chunk chunk) { if ((this.mw.multiplayer && this.mw.regionFileOutputEnabledMP) || (!this.mw.multiplayer && this.mw.regionFileOutputEnabledSP)) { if (!chunk.isEmpty()) { this.mw.executor.addTask(new SaveChunkTask(copyToMwChunk(chunk), this.mw.regionManager)); } } } }