diff --git a/src/main/java/com/gmail/nossr50/util/blockmeta/BitSetChunkStore.java b/src/main/java/com/gmail/nossr50/util/blockmeta/BitSetChunkStore.java index 2f4ffe9ce..a04ed501b 100644 --- a/src/main/java/com/gmail/nossr50/util/blockmeta/BitSetChunkStore.java +++ b/src/main/java/com/gmail/nossr50/util/blockmeta/BitSetChunkStore.java @@ -3,33 +3,35 @@ package com.gmail.nossr50.util.blockmeta; import org.bukkit.Bukkit; import org.bukkit.World; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.io.*; import java.util.BitSet; 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 MAGIC_NUMBER = 0xEA5EDEBB; - private int cx; - private int cz; - private int worldHeight; - private UUID worldUid; + + private final int cx; + private final int cz; + private final int worldHeight; + private final 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 BitSet store; + + private transient boolean dirty = false; public BitSetChunkStore(@NotNull World world, int cx, int cz) { - this.cx = cx; - this.cz = cz; - this.worldUid = world.getUID(); - this.worldHeight = world.getMaxHeight(); - this.store = new BitSet(16 * 16 * worldHeight); + this(world.getUID(), world.getMaxHeight(), cx, cz); } - private BitSetChunkStore() {} + private BitSetChunkStore(@NotNull UUID worldUid, int worldHeight, int cx, int cz) { + this.cx = cx; + this.cz = cz; + this.worldUid = worldUid; + this.worldHeight = worldHeight; + this.store = new BitSet(16 * 16 * worldHeight); + } @Override public boolean isDirty() { @@ -83,58 +85,24 @@ public class BitSetChunkStore implements ChunkStore, Serializable { } 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) throw new IndexOutOfBoundsException(String.format("x: %d y: %d z: %d World Height: %d", x, y, z, worldHeight)); return (z * 16 + x) + (256 * y); } - private void fixWorldHeight() { + private static int getWorldHeight(UUID worldUid, int storedWorldHeight) + { 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? if (world == null) - return; + return storedWorldHeight; - // 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"); - } - - @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 { @@ -163,26 +131,34 @@ public class BitSetChunkStore implements ChunkStore, Serializable { if (magic != MAGIC_NUMBER || fileVersionNumber != CURRENT_VERSION) throw new IOException(); - BitSetChunkStore chunkStore = new BitSetChunkStore(); - long lsb = 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()]; 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; } public static class Serialization { - public static final short STREAM_MAGIC = (short)0xACDC; + public static final short STREAM_MAGIC = (short)0xACDC; // Rock on public static @NotNull ChunkStore readChunkStore(DataInputStream inputStream) throws IOException { if (inputStream.markSupported()) @@ -198,7 +174,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. PushbackInputStream pushbackInputStream = new PushbackInputStream(inputStream, 2); - pushbackInputStream.unread((magicNumber >>> 0) & 0xFF); + pushbackInputStream.unread((magicNumber) & 0xFF); pushbackInputStream.unread((magicNumber >>> 8) & 0xFF); inputStream = new DataInputStream(pushbackInputStream); } @@ -218,8 +194,61 @@ public class BitSetChunkStore implements ChunkStore, Serializable { ((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 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(ObjectOutputStream out) throws IOException { + throw new UnsupportedOperationException("You goofed."); + } + + @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(); + + store = (boolean[][][]) in.readObject(); + worldHeight = store[0][0].length; + } + + public 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(InputStream in) throws IOException { super(in); enableResolveObject(true); @@ -229,13 +258,14 @@ public class BitSetChunkStore implements ChunkStore, Serializable { protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException { ObjectStreamClass read = super.readClassDescriptor(); if (read.getName().contentEquals("com.gmail.nossr50.util.blockmeta.chunkmeta.PrimitiveChunkStore")) - return ObjectStreamClass.lookup(BitSetChunkStore.class); + return ObjectStreamClass.lookup(LegacyChunkStoreDeserializer.class); return read; } public ChunkStore readLegacyChunkStore(){ try { - return (ChunkStore) readObject(); + LegacyChunkStoreDeserializer deserializer = (LegacyChunkStoreDeserializer)readObject(); + return deserializer.convert(); } catch (IOException | ClassNotFoundException e) { return null; }