|
@@ -2,33 +2,38 @@ package com.gmail.nossr50.util.blockmeta;
|
|
|
|
|
|
import org.bukkit.Bukkit;
|
|
import org.bukkit.Bukkit;
|
|
import org.bukkit.World;
|
|
import org.bukkit.World;
|
|
|
|
+import org.jetbrains.annotations.NotNull;
|
|
|
|
+import org.jetbrains.annotations.Nullable;
|
|
|
|
|
|
import java.io.*;
|
|
import java.io.*;
|
|
import java.util.BitSet;
|
|
import java.util.BitSet;
|
|
import java.util.UUID;
|
|
import java.util.UUID;
|
|
|
|
|
|
-public class BitSetChunkStore implements ChunkStore, Serializable {
|
|
|
|
- private static final long serialVersionUID = -1L;
|
|
|
|
- transient private boolean dirty = false;
|
|
|
|
- // Bitset store conforms to a "bottom-up" bit ordering consisting of a stack of {worldHeight} Y planes, each Y plane consists of 16 Z rows of 16 X bits.
|
|
|
|
- private BitSet store;
|
|
|
|
|
|
+public class BitSetChunkStore implements ChunkStore {
|
|
private static final int CURRENT_VERSION = 8;
|
|
private static final int CURRENT_VERSION = 8;
|
|
private static final int MAGIC_NUMBER = 0xEA5EDEBB;
|
|
private static final int MAGIC_NUMBER = 0xEA5EDEBB;
|
|
- private int cx;
|
|
|
|
- private int cz;
|
|
|
|
- private int worldHeight;
|
|
|
|
- private UUID worldUid;
|
|
|
|
|
|
|
|
- public BitSetChunkStore(World world, int cx, int cz) {
|
|
|
|
|
|
+ private final int cx;
|
|
|
|
+ private final int cz;
|
|
|
|
+ private final int worldHeight;
|
|
|
|
+ private final @NotNull UUID worldUid;
|
|
|
|
+ // Bitset store conforms to a "bottom-up" bit ordering consisting of a stack of {worldHeight} Y planes, each Y plane consists of 16 Z rows of 16 X bits.
|
|
|
|
+ private final @NotNull BitSet store;
|
|
|
|
+
|
|
|
|
+ private transient boolean dirty = false;
|
|
|
|
+
|
|
|
|
+ public BitSetChunkStore(@NotNull World world, int cx, int cz) {
|
|
|
|
+ this(world.getUID(), world.getMaxHeight(), cx, cz);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private BitSetChunkStore(@NotNull UUID worldUid, int worldHeight, int cx, int cz) {
|
|
this.cx = cx;
|
|
this.cx = cx;
|
|
this.cz = cz;
|
|
this.cz = cz;
|
|
- this.worldUid = world.getUID();
|
|
|
|
- this.worldHeight = world.getMaxHeight();
|
|
|
|
|
|
+ this.worldUid = worldUid;
|
|
|
|
+ this.worldHeight = worldHeight;
|
|
this.store = new BitSet(16 * 16 * worldHeight);
|
|
this.store = new BitSet(16 * 16 * worldHeight);
|
|
}
|
|
}
|
|
|
|
|
|
- private BitSetChunkStore() {}
|
|
|
|
-
|
|
|
|
@Override
|
|
@Override
|
|
public boolean isDirty() {
|
|
public boolean isDirty() {
|
|
return dirty;
|
|
return dirty;
|
|
@@ -50,7 +55,7 @@ public class BitSetChunkStore implements ChunkStore, Serializable {
|
|
}
|
|
}
|
|
|
|
|
|
@Override
|
|
@Override
|
|
- public UUID getWorldId() {
|
|
|
|
|
|
+ public @NotNull UUID getWorldId() {
|
|
return worldUid;
|
|
return worldUid;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -81,61 +86,27 @@ public class BitSetChunkStore implements ChunkStore, Serializable {
|
|
}
|
|
}
|
|
|
|
|
|
private int coordToIndex(int x, int y, int z) {
|
|
private int coordToIndex(int x, int y, int z) {
|
|
|
|
+ return coordToIndex(x, y, z, worldHeight);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private static int coordToIndex(int x, int y, int z, int worldHeight) {
|
|
if (x < 0 || x >= 16 || y < 0 || y >= worldHeight || z < 0 || z >= 16)
|
|
if (x < 0 || x >= 16 || y < 0 || y >= worldHeight || z < 0 || z >= 16)
|
|
- throw new IndexOutOfBoundsException();
|
|
|
|
|
|
+ throw new IndexOutOfBoundsException(String.format("x: %d y: %d z: %d World Height: %d", x, y, z, worldHeight));
|
|
return (z * 16 + x) + (256 * y);
|
|
return (z * 16 + x) + (256 * y);
|
|
}
|
|
}
|
|
|
|
|
|
- private void fixWorldHeight() {
|
|
|
|
|
|
+ private static int getWorldHeight(@NotNull UUID worldUid, int storedWorldHeight)
|
|
|
|
+ {
|
|
World world = Bukkit.getWorld(worldUid);
|
|
World world = Bukkit.getWorld(worldUid);
|
|
|
|
|
|
// Not sure how this case could come up, but might as well handle it gracefully. Loading a chunkstore for an unloaded world?
|
|
// Not sure how this case could come up, but might as well handle it gracefully. Loading a chunkstore for an unloaded world?
|
|
if (world == null)
|
|
if (world == null)
|
|
- return;
|
|
|
|
-
|
|
|
|
- // Lop off any extra data if the world height has shrunk
|
|
|
|
- int currentWorldHeight = world.getMaxHeight();
|
|
|
|
- if (currentWorldHeight < worldHeight)
|
|
|
|
- {
|
|
|
|
- store.clear(coordToIndex(16, currentWorldHeight, 16), store.length());
|
|
|
|
- worldHeight = currentWorldHeight;
|
|
|
|
- dirty = true;
|
|
|
|
- }
|
|
|
|
- // If the world height has grown, update the worldHeight variable, but don't bother marking it dirty as unless something else changes we don't need to force a file write;
|
|
|
|
- else if (currentWorldHeight > worldHeight)
|
|
|
|
- worldHeight = currentWorldHeight;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- @Deprecated
|
|
|
|
- private void writeObject(ObjectOutputStream out) throws IOException {
|
|
|
|
- throw new UnsupportedOperationException("Serializable support should only be used for legacy deserialization");
|
|
|
|
- }
|
|
|
|
|
|
+ return storedWorldHeight;
|
|
|
|
|
|
- @Deprecated
|
|
|
|
- private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
|
|
|
|
- in.readInt(); // Magic number
|
|
|
|
- in.readInt(); // Format version
|
|
|
|
- long lsb = in.readLong();
|
|
|
|
- long msb = in.readLong();
|
|
|
|
- worldUid = new UUID(msb, lsb);
|
|
|
|
- cx = in.readInt();
|
|
|
|
- cz = in.readInt();
|
|
|
|
-
|
|
|
|
- boolean[][][] oldStore = (boolean[][][]) in.readObject();
|
|
|
|
- worldHeight = oldStore[0][0].length;
|
|
|
|
- store = new BitSet(16 * 16 * worldHeight / 8);
|
|
|
|
- for (int x = 0; x < 16; x++) {
|
|
|
|
- for (int z = 0; z < 16; z++) {
|
|
|
|
- for (int y = 0; y < worldHeight; y++) {
|
|
|
|
- store.set(coordToIndex(x, y, z), oldStore[x][z][y]);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- dirty = true;
|
|
|
|
- fixWorldHeight();
|
|
|
|
|
|
+ return world.getMaxHeight();
|
|
}
|
|
}
|
|
|
|
|
|
- private void serialize(DataOutputStream out) throws IOException {
|
|
|
|
|
|
+ private void serialize(@NotNull DataOutputStream out) throws IOException {
|
|
out.writeInt(MAGIC_NUMBER);
|
|
out.writeInt(MAGIC_NUMBER);
|
|
out.writeInt(CURRENT_VERSION);
|
|
out.writeInt(CURRENT_VERSION);
|
|
|
|
|
|
@@ -153,7 +124,7 @@ public class BitSetChunkStore implements ChunkStore, Serializable {
|
|
dirty = false;
|
|
dirty = false;
|
|
}
|
|
}
|
|
|
|
|
|
- private static BitSetChunkStore deserialize(DataInputStream in) throws IOException {
|
|
|
|
|
|
+ private static @NotNull BitSetChunkStore deserialize(@NotNull DataInputStream in) throws IOException {
|
|
int magic = in.readInt();
|
|
int magic = in.readInt();
|
|
// Can be used to determine the format of the file
|
|
// Can be used to determine the format of the file
|
|
int fileVersionNumber = in.readInt();
|
|
int fileVersionNumber = in.readInt();
|
|
@@ -161,28 +132,36 @@ public class BitSetChunkStore implements ChunkStore, Serializable {
|
|
if (magic != MAGIC_NUMBER || fileVersionNumber != CURRENT_VERSION)
|
|
if (magic != MAGIC_NUMBER || fileVersionNumber != CURRENT_VERSION)
|
|
throw new IOException();
|
|
throw new IOException();
|
|
|
|
|
|
- BitSetChunkStore chunkStore = new BitSetChunkStore();
|
|
|
|
-
|
|
|
|
long lsb = in.readLong();
|
|
long lsb = in.readLong();
|
|
long msb = in.readLong();
|
|
long msb = in.readLong();
|
|
- chunkStore.worldUid = new UUID(msb, lsb);
|
|
|
|
- chunkStore.cx = in.readInt();
|
|
|
|
- chunkStore.cz = in.readInt();
|
|
|
|
|
|
+ UUID worldUid = new UUID(msb, lsb);
|
|
|
|
+ int cx = in.readInt();
|
|
|
|
+ int cz = in.readInt();
|
|
|
|
|
|
- chunkStore.worldHeight = in.readInt();
|
|
|
|
|
|
+ int worldHeight = in.readInt();
|
|
byte[] temp = new byte[in.readInt()];
|
|
byte[] temp = new byte[in.readInt()];
|
|
in.readFully(temp);
|
|
in.readFully(temp);
|
|
- chunkStore.store = BitSet.valueOf(temp);
|
|
|
|
|
|
+ BitSet stored = BitSet.valueOf(temp);
|
|
|
|
+
|
|
|
|
+ int currentWorldHeight = getWorldHeight(worldUid, worldHeight);
|
|
|
|
+
|
|
|
|
+ boolean worldHeightShrunk = currentWorldHeight < worldHeight;
|
|
|
|
+ // Lop off extra data if world height has shrunk
|
|
|
|
+ if (worldHeightShrunk)
|
|
|
|
+ stored.clear(coordToIndex(16, currentWorldHeight, 16, worldHeight), stored.length());
|
|
|
|
+
|
|
|
|
+ BitSetChunkStore chunkStore = new BitSetChunkStore(worldUid, currentWorldHeight, cx, cz);
|
|
|
|
+ chunkStore.store.or(stored);
|
|
|
|
+ chunkStore.dirty = worldHeightShrunk; // In the expanded case there is no reason to re-write it unless the data changes
|
|
|
|
|
|
- chunkStore.fixWorldHeight();
|
|
|
|
return chunkStore;
|
|
return chunkStore;
|
|
}
|
|
}
|
|
|
|
|
|
public static class Serialization {
|
|
public static class Serialization {
|
|
|
|
|
|
- public static final short STREAM_MAGIC = (short)0xACDC;
|
|
|
|
|
|
+ public static final short STREAM_MAGIC = (short)0xACDC; // Rock on
|
|
|
|
|
|
- public static ChunkStore readChunkStore(DataInputStream inputStream) throws IOException {
|
|
|
|
|
|
+ public static @Nullable ChunkStore readChunkStore(@NotNull DataInputStream inputStream) throws IOException {
|
|
if (inputStream.markSupported())
|
|
if (inputStream.markSupported())
|
|
inputStream.mark(2);
|
|
inputStream.mark(2);
|
|
short magicNumber = inputStream.readShort();
|
|
short magicNumber = inputStream.readShort();
|
|
@@ -196,7 +175,7 @@ public class BitSetChunkStore implements ChunkStore, Serializable {
|
|
{
|
|
{
|
|
// Creates a new stream with the two magic number bytes and then the rest of the original stream... Java is so dumb. I just wanted to look at two bytes.
|
|
// Creates a new stream with the two magic number bytes and then the rest of the original stream... Java is so dumb. I just wanted to look at two bytes.
|
|
PushbackInputStream pushbackInputStream = new PushbackInputStream(inputStream, 2);
|
|
PushbackInputStream pushbackInputStream = new PushbackInputStream(inputStream, 2);
|
|
- pushbackInputStream.unread((magicNumber >>> 0) & 0xFF);
|
|
|
|
|
|
+ pushbackInputStream.unread((magicNumber) & 0xFF);
|
|
pushbackInputStream.unread((magicNumber >>> 8) & 0xFF);
|
|
pushbackInputStream.unread((magicNumber >>> 8) & 0xFF);
|
|
inputStream = new DataInputStream(pushbackInputStream);
|
|
inputStream = new DataInputStream(pushbackInputStream);
|
|
}
|
|
}
|
|
@@ -209,31 +188,85 @@ public class BitSetChunkStore implements ChunkStore, Serializable {
|
|
throw new IOException("Bad Data Format");
|
|
throw new IOException("Bad Data Format");
|
|
}
|
|
}
|
|
|
|
|
|
- public static void writeChunkStore(DataOutputStream outputStream, ChunkStore chunkStore) throws IOException {
|
|
|
|
|
|
+ public static void writeChunkStore(@NotNull DataOutputStream outputStream, @NotNull ChunkStore chunkStore) throws IOException {
|
|
if (!(chunkStore instanceof BitSetChunkStore))
|
|
if (!(chunkStore instanceof BitSetChunkStore))
|
|
throw new InvalidClassException("ChunkStore must be instance of BitSetChunkStore");
|
|
throw new InvalidClassException("ChunkStore must be instance of BitSetChunkStore");
|
|
outputStream.writeShort(STREAM_MAGIC);
|
|
outputStream.writeShort(STREAM_MAGIC);
|
|
((BitSetChunkStore)chunkStore).serialize(outputStream);
|
|
((BitSetChunkStore)chunkStore).serialize(outputStream);
|
|
}
|
|
}
|
|
|
|
|
|
- // Handles loading the old serialized classes even though we have changed name/package
|
|
|
|
|
|
+ // Handles loading the old serialized class
|
|
private static class LegacyDeserializationInputStream extends ObjectInputStream {
|
|
private static class LegacyDeserializationInputStream extends ObjectInputStream {
|
|
- public LegacyDeserializationInputStream(InputStream in) throws IOException {
|
|
|
|
|
|
+ private static class LegacyChunkStoreDeserializer implements Serializable
|
|
|
|
+ {
|
|
|
|
+ private static final long serialVersionUID = -1L;
|
|
|
|
+
|
|
|
|
+ private int cx;
|
|
|
|
+ private int cz;
|
|
|
|
+ private int worldHeight;
|
|
|
|
+ private UUID worldUid;
|
|
|
|
+ private boolean[][][] store;
|
|
|
|
+
|
|
|
|
+ private LegacyChunkStoreDeserializer() {}
|
|
|
|
+
|
|
|
|
+ @Deprecated
|
|
|
|
+ private void writeObject(@NotNull ObjectOutputStream out) throws IOException {
|
|
|
|
+ throw new UnsupportedOperationException("You goofed.");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Deprecated
|
|
|
|
+ private void readObject(@NotNull ObjectInputStream in) throws IOException, ClassNotFoundException {
|
|
|
|
+ in.readInt(); // Magic number
|
|
|
|
+ in.readInt(); // Format version
|
|
|
|
+ long lsb = in.readLong();
|
|
|
|
+ long msb = in.readLong();
|
|
|
|
+
|
|
|
|
+ worldUid = new UUID(msb, lsb);
|
|
|
|
+ cx = in.readInt();
|
|
|
|
+ cz = in.readInt();
|
|
|
|
+
|
|
|
|
+ store = (boolean[][][]) in.readObject();
|
|
|
|
+ worldHeight = store[0][0].length;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public @NotNull BitSetChunkStore convert()
|
|
|
|
+ {
|
|
|
|
+ int currentWorldHeight = getWorldHeight(worldUid, worldHeight);
|
|
|
|
+
|
|
|
|
+ BitSetChunkStore converted = new BitSetChunkStore(worldUid, currentWorldHeight, cx, cz);
|
|
|
|
+
|
|
|
|
+ // Read old data into new chunkstore
|
|
|
|
+ for (int x = 0; x < 16; x++) {
|
|
|
|
+ for (int z = 0; z < 16; z++) {
|
|
|
|
+ for (int y = 0; y < worldHeight && y < currentWorldHeight; y++) {
|
|
|
|
+ converted.store.set(converted.coordToIndex(x, y, z), store[x][z][y]);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ // Mark dirty so it will be re-written in new format on close
|
|
|
|
+ converted.dirty = true;
|
|
|
|
+ return converted;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ public LegacyDeserializationInputStream(@NotNull InputStream in) throws IOException {
|
|
super(in);
|
|
super(in);
|
|
enableResolveObject(true);
|
|
enableResolveObject(true);
|
|
}
|
|
}
|
|
|
|
|
|
@Override
|
|
@Override
|
|
- protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException {
|
|
|
|
|
|
+ protected @NotNull ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException {
|
|
ObjectStreamClass read = super.readClassDescriptor();
|
|
ObjectStreamClass read = super.readClassDescriptor();
|
|
if (read.getName().contentEquals("com.gmail.nossr50.util.blockmeta.chunkmeta.PrimitiveChunkStore"))
|
|
if (read.getName().contentEquals("com.gmail.nossr50.util.blockmeta.chunkmeta.PrimitiveChunkStore"))
|
|
- return ObjectStreamClass.lookup(BitSetChunkStore.class);
|
|
|
|
|
|
+ return ObjectStreamClass.lookup(LegacyChunkStoreDeserializer.class);
|
|
return read;
|
|
return read;
|
|
}
|
|
}
|
|
|
|
|
|
- public ChunkStore readLegacyChunkStore(){
|
|
|
|
|
|
+ public @Nullable ChunkStore readLegacyChunkStore(){
|
|
try {
|
|
try {
|
|
- return (ChunkStore) readObject();
|
|
|
|
|
|
+ LegacyChunkStoreDeserializer deserializer = (LegacyChunkStoreDeserializer)readObject();
|
|
|
|
+ return deserializer.convert();
|
|
} catch (IOException | ClassNotFoundException e) {
|
|
} catch (IOException | ClassNotFoundException e) {
|
|
return null;
|
|
return null;
|
|
}
|
|
}
|