Created
February 5, 2012 14:48
-
-
Save Afforess/1745908 to your computer and use it in GitHub Desktop.
Region Chunk Provider
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package org.bukkit.craftbukkit; | |
| import java.util.ArrayList; | |
| import java.util.Iterator; | |
| import java.util.List; | |
| // CraftBukkit start | |
| import java.util.Random; | |
| import net.minecraft.server.BlockSand; | |
| import net.minecraft.server.Chunk; | |
| import net.minecraft.server.ChunkCoordinates; | |
| import net.minecraft.server.ChunkPosition; | |
| import net.minecraft.server.EmptyChunk; | |
| import net.minecraft.server.EnumCreatureType; | |
| import net.minecraft.server.IChunkLoader; | |
| import net.minecraft.server.IChunkProvider; | |
| import net.minecraft.server.IProgressUpdate; | |
| import net.minecraft.server.MinecraftServer; | |
| import net.minecraft.server.World; | |
| import net.minecraft.server.WorldServer; | |
| import org.bukkit.craftbukkit.util.LongHash; | |
| import org.bukkit.craftbukkit.util.LongHashset; | |
| import org.bukkit.event.world.ChunkLoadEvent; | |
| import org.bukkit.event.world.ChunkPopulateEvent; | |
| import org.bukkit.event.world.ChunkUnloadEvent; | |
| import org.bukkit.generator.BlockPopulator; | |
| // CraftBukkit end | |
| class ChunkRegion { | |
| Chunk[][] chunks = new Chunk[RegionChunkProvider.REGION_SIZE][RegionChunkProvider.REGION_SIZE]; | |
| } | |
| public class RegionChunkProvider implements IChunkProvider { | |
| ChunkRegion[][] quad1 = new ChunkRegion[DEFAULT_SIZE][DEFAULT_SIZE]; | |
| ChunkRegion[][] quad2 = new ChunkRegion[DEFAULT_SIZE][DEFAULT_SIZE]; | |
| ChunkRegion[][] quad3 = new ChunkRegion[DEFAULT_SIZE][DEFAULT_SIZE]; | |
| ChunkRegion[][] quad4 = new ChunkRegion[DEFAULT_SIZE][DEFAULT_SIZE]; | |
| public static final int DEFAULT_SIZE = 12; | |
| public static final int REGION_BITS = 5; | |
| public static final int REGION_SIZE = 1 << 5; | |
| public static final float EXPANSION_FACTOR = 1.5F; | |
| public LongHashset unloadQueue = new LongHashset(); | |
| public Chunk emptyChunk; | |
| public IChunkProvider chunkProvider; | |
| private IChunkLoader chunkLoader; | |
| public boolean forceChunkLoad = false; | |
| public List<Chunk> chunkList = new ArrayList<Chunk>(1000); | |
| public WorldServer world; | |
| private int lastX = Integer.MAX_VALUE, lastZ = Integer.MAX_VALUE; | |
| private Chunk last = null; | |
| public RegionChunkProvider(WorldServer worldserver, IChunkLoader ichunkloader, IChunkProvider ichunkprovider) { | |
| this.emptyChunk = new EmptyChunk(worldserver, new byte[256 * worldserver.height], 0, 0); | |
| this.world = worldserver; | |
| this.chunkLoader = ichunkloader; | |
| this.chunkProvider = ichunkprovider; | |
| for (int x = 0; x < DEFAULT_SIZE; x++) { | |
| for (int z = 0; z < DEFAULT_SIZE; z++) { | |
| quad1[x][z] = new ChunkRegion(); | |
| quad2[x][z] = new ChunkRegion(); | |
| quad3[x][z] = new ChunkRegion(); | |
| quad4[x][z] = new ChunkRegion(); | |
| } | |
| } | |
| } | |
| public boolean isChunkLoaded(int chunkX, int chunkZ) { | |
| if (chunkX == lastX && chunkZ == lastZ) return true; | |
| return getRawChunkAt(chunkX, chunkZ) != null; | |
| } | |
| public Chunk getRawChunkAt(int chunkX, int chunkZ) { | |
| return getChunkAt(Math.abs(chunkX), Math.abs(chunkZ), getQuad(chunkX, chunkZ)); | |
| } | |
| private Chunk getChunkAt(int x, int z, int quad) { | |
| int rx = x >> REGION_BITS; | |
| int rz = z >> REGION_BITS; | |
| System.out.println("Accessing chunk at " + x + ", " + z + " at quad " + quad + " region " + rx + ", " + rz); | |
| ChunkRegion[][] regions = resize(rx, rz, quad); | |
| ChunkRegion region = regions[rx][rz]; | |
| return region.chunks[x & REGION_SIZE][z & REGION_SIZE]; | |
| } | |
| private void setChunkAt(int x, int z, int quad, Chunk chunk) { | |
| int rx = x >> REGION_BITS; | |
| int rz = z >> REGION_BITS; | |
| System.out.println("Setting chunk " + chunk != null ? (chunk.x + ", " + chunk.z) : "null" + " at " + x + " " + z); | |
| switch(quad) { | |
| case 1: quad1[rx][rz].chunks[x & REGION_SIZE][z & REGION_SIZE] = chunk; break; | |
| case 2: quad2[rx][rz].chunks[x & REGION_SIZE][z & REGION_SIZE] = chunk; break; | |
| case 3: quad3[rx][rz].chunks[x & REGION_SIZE][z & REGION_SIZE] = chunk; break; | |
| case 4: quad4[rx][rz].chunks[x & REGION_SIZE][z & REGION_SIZE] = chunk; break; | |
| } | |
| } | |
| private ChunkRegion[][] resize(int rx, int rz, int quad) { | |
| ChunkRegion[][] quadrant = null; | |
| //Find our quadrant | |
| switch(quad) { | |
| case 1: quadrant = quad1; break; | |
| case 2: quadrant = quad2; break; | |
| case 3: quadrant = quad3; break; | |
| case 4: quadrant = quad4; break; | |
| } | |
| //Test if we need to resize | |
| int newSize = quadrant.length; | |
| if (rx > quadrant.length) { | |
| newSize = (int)(rx * EXPANSION_FACTOR); | |
| } | |
| if (rz > quadrant.length) { | |
| newSize = Math.max((int)(rz * EXPANSION_FACTOR), newSize); | |
| } | |
| //Resize | |
| if (newSize != quadrant.length){ | |
| ChunkRegion[][] expanded = new ChunkRegion[newSize][newSize]; | |
| for (int x = 0; x < newSize; x++) { | |
| for (int z = 0; z < newSize; z++) { | |
| if (x < quadrant.length && z < quadrant.length){ | |
| expanded[x][z] = quadrant[x][z]; | |
| } | |
| else { | |
| expanded[x][z] = new ChunkRegion(); | |
| } | |
| } | |
| } | |
| //Save our quadrant | |
| switch(quad) { | |
| case 1: quad1 = expanded; break; | |
| case 2: quad2 = expanded; break; | |
| case 3: quad3 = expanded; break; | |
| case 4: quad4 = expanded; break; | |
| } | |
| } | |
| //Return the quadrant to use | |
| switch(quad) { | |
| case 1: return quad1; | |
| case 2: return quad2; | |
| case 3: return quad3; | |
| case 4: return quad4; | |
| default: return null; | |
| } | |
| } | |
| public void queueUnload(int i, int j) { | |
| if (this.world.worldProvider.c()) { | |
| ChunkCoordinates chunkcoordinates = this.world.getSpawn(); | |
| int k = i * 16 + 8 - chunkcoordinates.x; | |
| int l = j * 16 + 8 - chunkcoordinates.z; | |
| short short1 = 128; | |
| if (k < -short1 || k > short1 || l < -short1 || l > short1 || !(this.world.keepSpawnInMemory)) { // CraftBukkit - added 'this.world.keepSpawnInMemory' | |
| this.unloadQueue.add(i, j); // CraftBukkit | |
| } | |
| } else { | |
| this.unloadQueue.add(i, j); // CraftBukkit | |
| } | |
| } | |
| public void c() { | |
| Iterator<Chunk> iterator = this.chunkList.iterator(); | |
| while (iterator.hasNext()) { | |
| Chunk chunk = iterator.next(); | |
| this.queueUnload(chunk.x, chunk.z); | |
| } | |
| } | |
| public Chunk getChunkAt(int chunkX, int chunkZ) { | |
| if (chunkX == lastX && chunkZ == lastZ) return last; | |
| this.unloadQueue.remove(chunkX, chunkZ); | |
| Chunk chunk = null; | |
| int quad = getQuad(chunkX, chunkZ); | |
| chunk = getChunkAt(Math.abs(chunkX), Math.abs(chunkZ), quad); | |
| boolean newChunk = false; | |
| if (chunk != null) { | |
| lastX = chunkX; | |
| lastZ = chunkZ; | |
| last = chunk; | |
| } | |
| if (chunk == null) { | |
| chunk = this.loadChunk(chunkX, chunkZ); | |
| if (chunk == null) { | |
| if (this.chunkProvider == null) { | |
| chunk = this.emptyChunk; | |
| } else { | |
| chunk = this.chunkProvider.getOrCreateChunk(chunkX, chunkZ); | |
| } | |
| newChunk = true; | |
| } | |
| if (chunk != null) { | |
| setChunkAt(Math.abs(chunkX), Math.abs(chunkZ), quad, chunk); | |
| this.chunkList.add(chunk); | |
| chunk.loadNOP(); | |
| chunk.addEntities(); | |
| lastX = chunkX; | |
| lastZ = chunkZ; | |
| last = chunk; | |
| } | |
| org.bukkit.Server server = this.world.getServer(); | |
| if (server != null) { | |
| /* | |
| * If it's a new world, the first few chunks are generated inside | |
| * the World constructor. We can't reliably alter that, so we have | |
| * no way of creating a CraftWorld/CraftServer at that point. | |
| */ | |
| server.getPluginManager().callEvent(new ChunkLoadEvent(chunk.bukkitChunk, newChunk)); | |
| } | |
| chunk.a(this, this, chunkX, chunkZ); | |
| } | |
| return chunk; | |
| } | |
| public Chunk getOrCreateChunk(int chunkX, int chunkZ) { | |
| if (chunkX == lastX && chunkZ == lastZ) return last; | |
| Chunk chunk = getRawChunkAt(chunkX, chunkZ); | |
| chunk = chunk == null ? (!this.world.isLoading && !this.forceChunkLoad ? this.emptyChunk : this.getChunkAt(chunkX, chunkZ)) : chunk; | |
| if (chunk == this.emptyChunk) return chunk; | |
| if (chunkX != chunk.x || chunkZ != chunk.z) { | |
| MinecraftServer.log.severe("Chunk (" + chunk.x + ", " + chunk.z + ") stored at (" + chunkX + ", " + chunkZ + ") in world '" + world.getWorld().getName() + "'"); | |
| MinecraftServer.log.severe(chunk.getClass().getName()); | |
| Throwable ex = new Throwable(); | |
| ex.fillInStackTrace(); | |
| ex.printStackTrace(); | |
| } | |
| return chunk; | |
| // CraftBukkit end | |
| } | |
| public Chunk loadChunk(int i, int j) { // CraftBukkit - private -> public | |
| if (this.chunkLoader == null) { | |
| return null; | |
| } else { | |
| try { | |
| Chunk chunk = this.chunkLoader.a(this.world, i, j); | |
| if (chunk != null) { | |
| chunk.t = this.world.getTime(); | |
| } | |
| return chunk; | |
| } catch (Exception exception) { | |
| exception.printStackTrace(); | |
| return null; | |
| } | |
| } | |
| } | |
| public void saveChunkNOP(Chunk chunk) { // CraftBukkit - private -> public | |
| if (this.chunkLoader != null) { | |
| try { | |
| this.chunkLoader.b(this.world, chunk); | |
| } catch (Exception exception) { | |
| exception.printStackTrace(); | |
| } | |
| } | |
| } | |
| public void saveChunk(Chunk chunk) { // CraftBukkit - private -> public | |
| if (this.chunkLoader != null) { | |
| try { | |
| chunk.t = this.world.getTime(); | |
| this.chunkLoader.a(this.world, chunk); | |
| } catch (Exception ioexception) { // CraftBukkit - IOException -> Exception | |
| ioexception.printStackTrace(); | |
| } | |
| } | |
| } | |
| public void getChunkAt(IChunkProvider ichunkprovider, int i, int j) { | |
| Chunk chunk = this.getOrCreateChunk(i, j); | |
| if (!chunk.done) { | |
| chunk.done = true; | |
| if (this.chunkProvider != null) { | |
| this.chunkProvider.getChunkAt(ichunkprovider, i, j); | |
| // CraftBukkit start | |
| BlockSand.instaFall = true; | |
| Random random = new Random(); | |
| random.setSeed(world.getSeed()); | |
| long xRand = random.nextLong() / 2L * 2L + 1L; | |
| long zRand = random.nextLong() / 2L * 2L + 1L; | |
| random.setSeed((long) i * xRand + (long) j * zRand ^ world.getSeed()); | |
| org.bukkit.World world = this.world.getWorld(); | |
| if (world != null) { | |
| for (BlockPopulator populator : world.getPopulators()) { | |
| populator.populate(world, random, chunk.bukkitChunk); | |
| } | |
| } | |
| BlockSand.instaFall = false; | |
| this.world.getServer().getPluginManager().callEvent(new ChunkPopulateEvent(chunk.bukkitChunk)); | |
| // CraftBukkit end | |
| chunk.f(); | |
| } | |
| } | |
| } | |
| public boolean saveChunks(boolean flag, IProgressUpdate iprogressupdate) { | |
| int i = 0; | |
| for (int j = 0; j < this.chunkList.size(); ++j) { | |
| Chunk chunk = (Chunk) this.chunkList.get(j); | |
| if (flag && !chunk.r) { | |
| this.saveChunkNOP(chunk); | |
| } | |
| if (chunk.a(flag)) { | |
| this.saveChunk(chunk); | |
| chunk.q = false; | |
| ++i; | |
| if (i == 24 && !flag) { | |
| return false; | |
| } | |
| } | |
| } | |
| if (flag) { | |
| if (this.chunkLoader == null) { | |
| return true; | |
| } | |
| this.chunkLoader.b(); | |
| } | |
| return true; | |
| } | |
| public boolean unloadChunks() { | |
| if (!this.world.savingDisabled) { | |
| // CraftBukkit start | |
| org.bukkit.Server server = this.world.getServer(); | |
| for (int i = 0; i < 50 && !this.unloadQueue.isEmpty(); i++) { | |
| long chunkcoordinates = this.unloadQueue.popFirst(); | |
| int x = LongHash.msw(chunkcoordinates); | |
| int z = LongHash.lsw(chunkcoordinates); | |
| Chunk chunk = getRawChunkAt(x, z); | |
| if (chunk == null) continue; | |
| ChunkUnloadEvent event = new ChunkUnloadEvent(chunk.bukkitChunk); | |
| server.getPluginManager().callEvent(event); | |
| if (!event.isCancelled()) { | |
| this.world.getWorld().preserveChunk((CraftChunk) chunk.bukkitChunk); | |
| chunk.removeEntities(); | |
| this.saveChunk(chunk); | |
| this.saveChunkNOP(chunk); | |
| this.chunkList.remove(chunk); | |
| } | |
| } | |
| // CraftBukkit end | |
| if (this.chunkLoader != null) { | |
| this.chunkLoader.a(); | |
| } | |
| } | |
| return this.chunkProvider.unloadChunks(); | |
| } | |
| public boolean canSave() { | |
| return !this.world.savingDisabled; | |
| } | |
| public String d() { | |
| return "ServerChunkCache: " + this.chunkList.size() + " Drop: " + this.unloadQueue.size(); | |
| } | |
| public List getMobsFor(EnumCreatureType enumcreaturetype, int i, int j, int k) { | |
| return this.chunkProvider.getMobsFor(enumcreaturetype, i, j, k); | |
| } | |
| public ChunkPosition findNearestMapFeature(World world, String s, int i, int j, int k) { | |
| return this.chunkProvider.findNearestMapFeature(world, s, i, j, k); | |
| } | |
| public List<Chunk> getLoadedChunks() { | |
| return chunkList; | |
| } | |
| public void forceUnload(int chunkX, int chunkZ) { | |
| unloadQueue.remove(chunkX, chunkZ); | |
| chunkList.remove(getRawChunkAt(chunkX, chunkZ)); | |
| setChunkAt(Math.abs(chunkX), Math.abs(chunkZ), getQuad(chunkX, chunkZ), null); | |
| } | |
| public Chunk getEmptyChunk() { | |
| return emptyChunk; | |
| } | |
| public IChunkProvider getChunkProvider() { | |
| return chunkProvider; | |
| } | |
| public void preventUnload(int chunkX, int chunkZ) { | |
| unloadQueue.remove(chunkX, chunkZ); | |
| } | |
| public World getWorld() { | |
| return world; | |
| } | |
| public void addChunk(int chunkX, int chunkZ, Chunk chunk) { | |
| chunkList.add(chunk); | |
| setChunkAt(Math.abs(chunkX), Math.abs(chunkZ), getQuad(chunkX, chunkZ), chunk); | |
| } | |
| private int getQuad(int chunkX, int chunkZ) { | |
| int quad = 0; | |
| if (chunkX > 0 && chunkZ > 0) quad = 1; | |
| else if (chunkX > 0 && chunkZ < 0) quad = 2; | |
| else if (chunkX < 0 && chunkZ > 0) quad = 4; | |
| else quad = 3; | |
| return quad; | |
| } | |
| public void setForceChunkLoading(boolean force) { | |
| this.forceChunkLoad = force; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment