Skip to content

Instantly share code, notes, and snippets.

@Afforess
Created February 5, 2012 14:48
Show Gist options
  • Save Afforess/1745908 to your computer and use it in GitHub Desktop.
Save Afforess/1745908 to your computer and use it in GitHub Desktop.
Region Chunk Provider
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