Paper/src/main/java/net/minecraft/server/ChunkRegionLoader.java

370 Zeilen
13 KiB
Java

package net.minecraft.server;
2012-03-01 11:49:23 +01:00
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.File;
import java.util.ArrayList;
import java.util.HashSet;
2012-03-01 11:49:23 +01:00
import java.util.Iterator;
import java.util.List;
import java.util.Set;
2012-07-29 09:33:13 +02:00
public class ChunkRegionLoader implements IAsyncChunkSaver, IChunkLoader {
private List a = new ArrayList();
private Set b = new HashSet();
private Object c = new Object();
private final File d;
public ChunkRegionLoader(File file1) {
this.d = file1;
}
Load chunks asynchronously for players. When a player triggers a chunk load via walking around or teleporting there is no need to stop everything and get this chunk on the main thread. The client is used to having to wait some time for this chunk and the server doesn't immediately do anything with it except send it to the player. At the same time chunk loading is the last major source of file IO that still runs on the main thread. These two facts make it possible to offload chunks loaded for this reason to another thread. However, not all parts of chunk loading can happen off the main thread. For this we use the new AsynchronousExecutor system to split chunk loading in to three pieces. The first is loading data from disk, decompressing it, and parsing it in to an NBT structure. The second piece is creating entities and tile entities in the chunk and adding them to the world, this is still done on the main thread. The third piece is informing everyone who requested a chunk load that the load is finished. For this we register callbacks and then run them on the main thread once the previous two stages are finished. There are still cases where a chunk is needed immediately and these will still trigger chunk loading entirely on the main thread. The most obvious case is plugins using the API to request a chunk load. We also must load the chunk immediately when something in the world tries to access it. In these cases we ignore any possibly pending or in progress chunk loading that is happening asynchronously as we will have the chunk loaded by the time they are finished. The hope is that overall this system will result in less CPU time and pauses due to blocking file IO on the main thread thus giving more consistent performance. Testing so far has shown that this also speeds up chunk loading client side although some of this is likely to be because we are sending less chunks at once for the client to process. Thanks for @ammaraskar for help with the implementation of this feature.
2012-11-30 09:49:19 +01:00
// CraftBukkit start
public boolean chunkExists(World world, int i, int j) {
ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(i, j);
synchronized (this.c) {
if (this.b.contains(chunkcoordintpair)) {
for (int k = 0; k < this.a.size(); ++k) {
if (((PendingChunkToSave) this.a.get(k)).a.equals(chunkcoordintpair)) {
return true;
}
}
}
}
return RegionFileCache.a(this.d, i, j).chunkExists(i & 31, j & 31);
}
// CraftBukkit end
// CraftBukkit start - add async variant, provide compatibility
public Chunk a(World world, int i, int j) {
Load chunks asynchronously for players. When a player triggers a chunk load via walking around or teleporting there is no need to stop everything and get this chunk on the main thread. The client is used to having to wait some time for this chunk and the server doesn't immediately do anything with it except send it to the player. At the same time chunk loading is the last major source of file IO that still runs on the main thread. These two facts make it possible to offload chunks loaded for this reason to another thread. However, not all parts of chunk loading can happen off the main thread. For this we use the new AsynchronousExecutor system to split chunk loading in to three pieces. The first is loading data from disk, decompressing it, and parsing it in to an NBT structure. The second piece is creating entities and tile entities in the chunk and adding them to the world, this is still done on the main thread. The third piece is informing everyone who requested a chunk load that the load is finished. For this we register callbacks and then run them on the main thread once the previous two stages are finished. There are still cases where a chunk is needed immediately and these will still trigger chunk loading entirely on the main thread. The most obvious case is plugins using the API to request a chunk load. We also must load the chunk immediately when something in the world tries to access it. In these cases we ignore any possibly pending or in progress chunk loading that is happening asynchronously as we will have the chunk loaded by the time they are finished. The hope is that overall this system will result in less CPU time and pauses due to blocking file IO on the main thread thus giving more consistent performance. Testing so far has shown that this also speeds up chunk loading client side although some of this is likely to be because we are sending less chunks at once for the client to process. Thanks for @ammaraskar for help with the implementation of this feature.
2012-11-30 09:49:19 +01:00
Object[] data = this.loadChunk(world, i, j);
if (data != null) {
Chunk chunk = (Chunk) data[0];
NBTTagCompound nbttagcompound = (NBTTagCompound) data[1];
this.loadEntities(chunk, nbttagcompound.getCompound("Level"), world);
return chunk;
}
return null;
}
public Object[] loadChunk(World world, int i, int j) {
// CraftBukkit end
NBTTagCompound nbttagcompound = null;
ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(i, j);
Object object = this.c;
synchronized (this.c) {
if (this.b.contains(chunkcoordintpair)) {
2012-11-06 13:05:28 +01:00
for (int k = 0; k < this.a.size(); ++k) {
if (((PendingChunkToSave) this.a.get(k)).a.equals(chunkcoordintpair)) {
nbttagcompound = ((PendingChunkToSave) this.a.get(k)).b;
break;
}
}
}
}
if (nbttagcompound == null) {
2012-07-29 09:33:13 +02:00
DataInputStream datainputstream = RegionFileCache.c(this.d, i, j);
if (datainputstream == null) {
return null;
}
nbttagcompound = NBTCompressedStreamTools.a((DataInput) datainputstream);
}
2012-03-01 11:49:23 +01:00
return this.a(world, i, j, nbttagcompound);
}
Load chunks asynchronously for players. When a player triggers a chunk load via walking around or teleporting there is no need to stop everything and get this chunk on the main thread. The client is used to having to wait some time for this chunk and the server doesn't immediately do anything with it except send it to the player. At the same time chunk loading is the last major source of file IO that still runs on the main thread. These two facts make it possible to offload chunks loaded for this reason to another thread. However, not all parts of chunk loading can happen off the main thread. For this we use the new AsynchronousExecutor system to split chunk loading in to three pieces. The first is loading data from disk, decompressing it, and parsing it in to an NBT structure. The second piece is creating entities and tile entities in the chunk and adding them to the world, this is still done on the main thread. The third piece is informing everyone who requested a chunk load that the load is finished. For this we register callbacks and then run them on the main thread once the previous two stages are finished. There are still cases where a chunk is needed immediately and these will still trigger chunk loading entirely on the main thread. The most obvious case is plugins using the API to request a chunk load. We also must load the chunk immediately when something in the world tries to access it. In these cases we ignore any possibly pending or in progress chunk loading that is happening asynchronously as we will have the chunk loaded by the time they are finished. The hope is that overall this system will result in less CPU time and pauses due to blocking file IO on the main thread thus giving more consistent performance. Testing so far has shown that this also speeds up chunk loading client side although some of this is likely to be because we are sending less chunks at once for the client to process. Thanks for @ammaraskar for help with the implementation of this feature.
2012-11-30 09:49:19 +01:00
protected Object[] a(World world, int i, int j, NBTTagCompound nbttagcompound) { // CraftBukkit - return Chunk -> Object[]
if (!nbttagcompound.hasKey("Level")) {
System.out.println("Chunk file at " + i + "," + j + " is missing level data, skipping");
return null;
2012-03-01 11:49:23 +01:00
} else if (!nbttagcompound.getCompound("Level").hasKey("Sections")) {
System.out.println("Chunk file at " + i + "," + j + " is missing block data, skipping");
return null;
} else {
2012-03-01 11:49:23 +01:00
Chunk chunk = this.a(world, nbttagcompound.getCompound("Level"));
if (!chunk.a(i, j)) {
System.out.println("Chunk file at " + i + "," + j + " is in the wrong location; relocating. (Expected " + i + ", " + j + ", got " + chunk.x + ", " + chunk.z + ")");
nbttagcompound.getCompound("Level").setInt("xPos", i); // CraftBukkit - .getCompound("Level")
nbttagcompound.getCompound("Level").setInt("zPos", j); // CraftBukkit - .getCompound("Level")
2012-03-01 11:49:23 +01:00
chunk = this.a(world, nbttagcompound.getCompound("Level"));
}
Load chunks asynchronously for players. When a player triggers a chunk load via walking around or teleporting there is no need to stop everything and get this chunk on the main thread. The client is used to having to wait some time for this chunk and the server doesn't immediately do anything with it except send it to the player. At the same time chunk loading is the last major source of file IO that still runs on the main thread. These two facts make it possible to offload chunks loaded for this reason to another thread. However, not all parts of chunk loading can happen off the main thread. For this we use the new AsynchronousExecutor system to split chunk loading in to three pieces. The first is loading data from disk, decompressing it, and parsing it in to an NBT structure. The second piece is creating entities and tile entities in the chunk and adding them to the world, this is still done on the main thread. The third piece is informing everyone who requested a chunk load that the load is finished. For this we register callbacks and then run them on the main thread once the previous two stages are finished. There are still cases where a chunk is needed immediately and these will still trigger chunk loading entirely on the main thread. The most obvious case is plugins using the API to request a chunk load. We also must load the chunk immediately when something in the world tries to access it. In these cases we ignore any possibly pending or in progress chunk loading that is happening asynchronously as we will have the chunk loaded by the time they are finished. The hope is that overall this system will result in less CPU time and pauses due to blocking file IO on the main thread thus giving more consistent performance. Testing so far has shown that this also speeds up chunk loading client side although some of this is likely to be because we are sending less chunks at once for the client to process. Thanks for @ammaraskar for help with the implementation of this feature.
2012-11-30 09:49:19 +01:00
// CraftBukkit start
Object[] data = new Object[2];
data[0] = chunk;
data[1] = nbttagcompound;
return data;
// CraftBukkit end
}
}
public void a(World world, Chunk chunk) {
2012-07-29 09:33:13 +02:00
// CraftBukkit start - "handle" exception
try {
2012-11-06 13:05:28 +01:00
world.D();
2012-07-29 09:33:13 +02:00
} catch (ExceptionWorldConflict ex) {
ex.printStackTrace();
}
// CraftBukkit end
try {
NBTTagCompound nbttagcompound = new NBTTagCompound();
NBTTagCompound nbttagcompound1 = new NBTTagCompound();
nbttagcompound.set("Level", nbttagcompound1);
2012-03-01 11:49:23 +01:00
this.a(chunk, world, nbttagcompound1);
2012-07-29 09:33:13 +02:00
this.a(chunk.l(), nbttagcompound);
} catch (Exception exception) {
exception.printStackTrace();
}
}
2012-03-01 11:49:23 +01:00
protected void a(ChunkCoordIntPair chunkcoordintpair, NBTTagCompound nbttagcompound) {
Object object = this.c;
synchronized (this.c) {
if (this.b.contains(chunkcoordintpair)) {
for (int i = 0; i < this.a.size(); ++i) {
if (((PendingChunkToSave) this.a.get(i)).a.equals(chunkcoordintpair)) {
this.a.set(i, new PendingChunkToSave(chunkcoordintpair, nbttagcompound));
return;
}
}
}
this.a.add(new PendingChunkToSave(chunkcoordintpair, nbttagcompound));
this.b.add(chunkcoordintpair);
FileIOThread.a.a(this);
}
}
public boolean c() {
PendingChunkToSave pendingchunktosave = null;
Object object = this.c;
synchronized (this.c) {
2012-07-29 09:33:13 +02:00
if (this.a.isEmpty()) {
return false;
}
pendingchunktosave = (PendingChunkToSave) this.a.remove(0);
this.b.remove(pendingchunktosave.a);
}
if (pendingchunktosave != null) {
try {
this.a(pendingchunktosave);
} catch (Exception exception) {
exception.printStackTrace();
}
}
return true;
}
2012-07-29 09:33:13 +02:00
public void a(PendingChunkToSave pendingchunktosave) throws java.io.IOException { // CraftBukkit - public -> private, added throws
DataOutputStream dataoutputstream = RegionFileCache.d(this.d, pendingchunktosave.a.x, pendingchunktosave.a.z);
NBTCompressedStreamTools.a(pendingchunktosave.b, (DataOutput) dataoutputstream);
dataoutputstream.close();
}
public void b(World world, Chunk chunk) {}
public void a() {}
public void b() {}
2012-03-01 11:49:23 +01:00
private void a(Chunk chunk, World world, NBTTagCompound nbttagcompound) {
nbttagcompound.setInt("xPos", chunk.x);
nbttagcompound.setInt("zPos", chunk.z);
nbttagcompound.setLong("LastUpdate", world.getTime());
nbttagcompound.setIntArray("HeightMap", chunk.heightMap);
nbttagcompound.setBoolean("TerrainPopulated", chunk.done);
2012-07-29 09:33:13 +02:00
ChunkSection[] achunksection = chunk.i();
2012-03-01 11:49:23 +01:00
NBTTagList nbttaglist = new NBTTagList("Sections");
2012-12-20 05:03:52 +01:00
boolean flag = !world.worldProvider.f;
2012-03-01 11:49:23 +01:00
ChunkSection[] achunksection1 = achunksection;
int i = achunksection.length;
NBTTagCompound nbttagcompound1;
for (int j = 0; j < i; ++j) {
ChunkSection chunksection = achunksection1[j];
2012-07-29 09:33:13 +02:00
if (chunksection != null) {
2012-03-01 11:49:23 +01:00
nbttagcompound1 = new NBTTagCompound();
2012-07-29 09:33:13 +02:00
nbttagcompound1.setByte("Y", (byte) (chunksection.d() >> 4 & 255));
2012-03-01 11:49:23 +01:00
nbttagcompound1.setByteArray("Blocks", chunksection.g());
2012-07-29 09:33:13 +02:00
if (chunksection.i() != null) {
nbttagcompound1.setByteArray("Add", chunksection.i().a);
2012-03-01 11:49:23 +01:00
}
2012-07-29 09:33:13 +02:00
nbttagcompound1.setByteArray("Data", chunksection.j().a);
nbttagcompound1.setByteArray("BlockLight", chunksection.k().a);
2012-12-20 05:03:52 +01:00
if (flag) {
nbttagcompound1.setByteArray("SkyLight", chunksection.l().a);
} else {
nbttagcompound1.setByteArray("SkyLight", new byte[chunksection.k().a.length]);
}
2012-03-01 11:49:23 +01:00
nbttaglist.add(nbttagcompound1);
}
}
nbttagcompound.set("Sections", nbttaglist);
2012-07-29 09:33:13 +02:00
nbttagcompound.setByteArray("Biomes", chunk.m());
2012-03-01 11:49:23 +01:00
chunk.m = false;
NBTTagList nbttaglist1 = new NBTTagList();
Iterator iterator;
for (i = 0; i < chunk.entitySlices.length; ++i) {
iterator = chunk.entitySlices[i].iterator();
while (iterator.hasNext()) {
Entity entity = (Entity) iterator.next();
chunk.m = true;
nbttagcompound1 = new NBTTagCompound();
if (entity.c(nbttagcompound1)) {
nbttaglist1.add(nbttagcompound1);
}
}
}
nbttagcompound.set("Entities", nbttaglist1);
NBTTagList nbttaglist2 = new NBTTagList();
iterator = chunk.tileEntities.values().iterator();
while (iterator.hasNext()) {
TileEntity tileentity = (TileEntity) iterator.next();
nbttagcompound1 = new NBTTagCompound();
tileentity.b(nbttagcompound1);
nbttaglist2.add(nbttagcompound1);
}
nbttagcompound.set("TileEntities", nbttaglist2);
List list = world.a(chunk, false);
if (list != null) {
long k = world.getTime();
NBTTagList nbttaglist3 = new NBTTagList();
Iterator iterator1 = list.iterator();
while (iterator1.hasNext()) {
NextTickListEntry nextticklistentry = (NextTickListEntry) iterator1.next();
NBTTagCompound nbttagcompound2 = new NBTTagCompound();
nbttagcompound2.setInt("i", nextticklistentry.d);
nbttagcompound2.setInt("x", nextticklistentry.a);
nbttagcompound2.setInt("y", nextticklistentry.b);
nbttagcompound2.setInt("z", nextticklistentry.c);
nbttagcompound2.setInt("t", (int) (nextticklistentry.e - k));
nbttaglist3.add(nbttagcompound2);
}
nbttagcompound.set("TileTicks", nbttaglist3);
}
}
private Chunk a(World world, NBTTagCompound nbttagcompound) {
int i = nbttagcompound.getInt("xPos");
int j = nbttagcompound.getInt("zPos");
Chunk chunk = new Chunk(world, i, j);
chunk.heightMap = nbttagcompound.getIntArray("HeightMap");
chunk.done = nbttagcompound.getBoolean("TerrainPopulated");
NBTTagList nbttaglist = nbttagcompound.getList("Sections");
byte b0 = 16;
ChunkSection[] achunksection = new ChunkSection[b0];
2012-12-20 05:03:52 +01:00
boolean flag = !world.worldProvider.f;
2012-03-01 11:49:23 +01:00
for (int k = 0; k < nbttaglist.size(); ++k) {
NBTTagCompound nbttagcompound1 = (NBTTagCompound) nbttaglist.get(k);
byte b1 = nbttagcompound1.getByte("Y");
2012-12-20 05:03:52 +01:00
ChunkSection chunksection = new ChunkSection(b1 << 4, flag);
2012-03-01 11:49:23 +01:00
chunksection.a(nbttagcompound1.getByteArray("Blocks"));
if (nbttagcompound1.hasKey("Add")) {
chunksection.a(new NibbleArray(nbttagcompound1.getByteArray("Add"), 4));
}
chunksection.b(new NibbleArray(nbttagcompound1.getByteArray("Data"), 4));
chunksection.c(new NibbleArray(nbttagcompound1.getByteArray("BlockLight"), 4));
2012-12-20 05:03:52 +01:00
if (flag) {
chunksection.d(new NibbleArray(nbttagcompound1.getByteArray("SkyLight"), 4));
}
chunksection.recalcBlockCounts();
2012-03-01 11:49:23 +01:00
achunksection[b1] = chunksection;
}
chunk.a(achunksection);
if (nbttagcompound.hasKey("Biomes")) {
chunk.a(nbttagcompound.getByteArray("Biomes"));
}
Load chunks asynchronously for players. When a player triggers a chunk load via walking around or teleporting there is no need to stop everything and get this chunk on the main thread. The client is used to having to wait some time for this chunk and the server doesn't immediately do anything with it except send it to the player. At the same time chunk loading is the last major source of file IO that still runs on the main thread. These two facts make it possible to offload chunks loaded for this reason to another thread. However, not all parts of chunk loading can happen off the main thread. For this we use the new AsynchronousExecutor system to split chunk loading in to three pieces. The first is loading data from disk, decompressing it, and parsing it in to an NBT structure. The second piece is creating entities and tile entities in the chunk and adding them to the world, this is still done on the main thread. The third piece is informing everyone who requested a chunk load that the load is finished. For this we register callbacks and then run them on the main thread once the previous two stages are finished. There are still cases where a chunk is needed immediately and these will still trigger chunk loading entirely on the main thread. The most obvious case is plugins using the API to request a chunk load. We also must load the chunk immediately when something in the world tries to access it. In these cases we ignore any possibly pending or in progress chunk loading that is happening asynchronously as we will have the chunk loaded by the time they are finished. The hope is that overall this system will result in less CPU time and pauses due to blocking file IO on the main thread thus giving more consistent performance. Testing so far has shown that this also speeds up chunk loading client side although some of this is likely to be because we are sending less chunks at once for the client to process. Thanks for @ammaraskar for help with the implementation of this feature.
2012-11-30 09:49:19 +01:00
// CraftBukkit start - end this method here and split off entity loading to another method
return chunk;
}
public void loadEntities(Chunk chunk, NBTTagCompound nbttagcompound, World world) {
// CraftBukkit end
2012-03-01 11:49:23 +01:00
NBTTagList nbttaglist1 = nbttagcompound.getList("Entities");
if (nbttaglist1 != null) {
for (int l = 0; l < nbttaglist1.size(); ++l) {
NBTTagCompound nbttagcompound2 = (NBTTagCompound) nbttaglist1.get(l);
Entity entity = EntityTypes.a(nbttagcompound2, world);
chunk.m = true;
if (entity != null) {
chunk.a(entity);
}
}
}
NBTTagList nbttaglist2 = nbttagcompound.getList("TileEntities");
if (nbttaglist2 != null) {
for (int i1 = 0; i1 < nbttaglist2.size(); ++i1) {
NBTTagCompound nbttagcompound3 = (NBTTagCompound) nbttaglist2.get(i1);
TileEntity tileentity = TileEntity.c(nbttagcompound3);
if (tileentity != null) {
chunk.a(tileentity);
}
}
}
if (nbttagcompound.hasKey("TileTicks")) {
NBTTagList nbttaglist3 = nbttagcompound.getList("TileTicks");
if (nbttaglist3 != null) {
for (int j1 = 0; j1 < nbttaglist3.size(); ++j1) {
NBTTagCompound nbttagcompound4 = (NBTTagCompound) nbttaglist3.get(j1);
2012-07-29 09:33:13 +02:00
world.b(nbttagcompound4.getInt("x"), nbttagcompound4.getInt("y"), nbttagcompound4.getInt("z"), nbttagcompound4.getInt("i"), nbttagcompound4.getInt("t"));
2012-03-01 11:49:23 +01:00
}
}
}
Load chunks asynchronously for players. When a player triggers a chunk load via walking around or teleporting there is no need to stop everything and get this chunk on the main thread. The client is used to having to wait some time for this chunk and the server doesn't immediately do anything with it except send it to the player. At the same time chunk loading is the last major source of file IO that still runs on the main thread. These two facts make it possible to offload chunks loaded for this reason to another thread. However, not all parts of chunk loading can happen off the main thread. For this we use the new AsynchronousExecutor system to split chunk loading in to three pieces. The first is loading data from disk, decompressing it, and parsing it in to an NBT structure. The second piece is creating entities and tile entities in the chunk and adding them to the world, this is still done on the main thread. The third piece is informing everyone who requested a chunk load that the load is finished. For this we register callbacks and then run them on the main thread once the previous two stages are finished. There are still cases where a chunk is needed immediately and these will still trigger chunk loading entirely on the main thread. The most obvious case is plugins using the API to request a chunk load. We also must load the chunk immediately when something in the world tries to access it. In these cases we ignore any possibly pending or in progress chunk loading that is happening asynchronously as we will have the chunk loaded by the time they are finished. The hope is that overall this system will result in less CPU time and pauses due to blocking file IO on the main thread thus giving more consistent performance. Testing so far has shown that this also speeds up chunk loading client side although some of this is likely to be because we are sending less chunks at once for the client to process. Thanks for @ammaraskar for help with the implementation of this feature.
2012-11-30 09:49:19 +01:00
// return chunk; // CraftBukkit
2012-03-01 11:49:23 +01:00
}
}