mirror of
https://github.com/mcMMO-Dev/mcMMO.git
synced 2024-11-25 14:46:46 +01:00
new chunkstore
Co-authored-by: t00thpick1 <t00thpick1dirko@gmail.com>
This commit is contained in:
parent
7802d54ebd
commit
01f31e76f5
@ -1,3 +1,10 @@
|
|||||||
|
Version 2.1.165
|
||||||
|
The mcMMO system which tracks player placed blocks has had some major rewrites (thanks t00thpick1)
|
||||||
|
mcMMO will now be compatible with changes to world height (1.17 compatibility)
|
||||||
|
|
||||||
|
NOTES:
|
||||||
|
t00thpick1 has taken time to rewrite our block meta tracking system to be more efficient, easier to maintain, and support upcoming features such as world height changes
|
||||||
|
|
||||||
Version 2.1.164
|
Version 2.1.164
|
||||||
mcMMO will now let players use vanilla blocks that have interactions (such as the vanilla Anvil) which are assigned as either Repair or Salvage blocks if a player is sneaking (see notes)
|
mcMMO will now let players use vanilla blocks that have interactions (such as the vanilla Anvil) which are assigned as either Repair or Salvage blocks if a player is sneaking (see notes)
|
||||||
The Rarity known as Records has been renamed to Mythic
|
The Rarity known as Records has been renamed to Mythic
|
||||||
|
22
pom.xml
22
pom.xml
@ -2,7 +2,7 @@
|
|||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>com.gmail.nossr50.mcMMO</groupId>
|
<groupId>com.gmail.nossr50.mcMMO</groupId>
|
||||||
<artifactId>mcMMO</artifactId>
|
<artifactId>mcMMO</artifactId>
|
||||||
<version>2.1.164</version>
|
<version>2.1.165-SNAPSHOT</version>
|
||||||
<name>mcMMO</name>
|
<name>mcMMO</name>
|
||||||
<url>https://github.com/mcMMO-Dev/mcMMO</url>
|
<url>https://github.com/mcMMO-Dev/mcMMO</url>
|
||||||
<scm>
|
<scm>
|
||||||
@ -279,7 +279,25 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>junit</groupId>
|
<groupId>junit</groupId>
|
||||||
<artifactId>junit-dep</artifactId>
|
<artifactId>junit-dep</artifactId>
|
||||||
<version>4.10</version>
|
<version>4.11</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.powermock</groupId>
|
||||||
|
<artifactId>powermock-module-junit4</artifactId>
|
||||||
|
<version>2.0.7</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.powermock</groupId>
|
||||||
|
<artifactId>powermock-api-mockito2</artifactId>
|
||||||
|
<version>2.0.7</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mockito</groupId>
|
||||||
|
<artifactId>mockito-core</artifactId>
|
||||||
|
<version>3.4.6</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
|
@ -42,28 +42,6 @@ public class WorldListener implements Listener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Monitor WorldInit events.
|
|
||||||
*
|
|
||||||
* @param event The event to watch
|
|
||||||
*/
|
|
||||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
|
||||||
public void onWorldInit(WorldInitEvent event) {
|
|
||||||
/* WORLD BLACKLIST CHECK */
|
|
||||||
if(WorldBlacklist.isWorldBlacklisted(event.getWorld()))
|
|
||||||
return;
|
|
||||||
|
|
||||||
World world = event.getWorld();
|
|
||||||
|
|
||||||
if (!new File(world.getWorldFolder(), "mcmmo_data").exists() || plugin == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
plugin.getLogger().info("Converting block storage for " + world.getName() + " to a new format.");
|
|
||||||
|
|
||||||
//new BlockStoreConversionMain(world).run();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Monitor WorldUnload events.
|
* Monitor WorldUnload events.
|
||||||
*
|
*
|
||||||
|
@ -38,8 +38,8 @@ import com.gmail.nossr50.skills.salvage.salvageables.Salvageable;
|
|||||||
import com.gmail.nossr50.skills.salvage.salvageables.SalvageableManager;
|
import com.gmail.nossr50.skills.salvage.salvageables.SalvageableManager;
|
||||||
import com.gmail.nossr50.skills.salvage.salvageables.SimpleSalvageableManager;
|
import com.gmail.nossr50.skills.salvage.salvageables.SimpleSalvageableManager;
|
||||||
import com.gmail.nossr50.util.*;
|
import com.gmail.nossr50.util.*;
|
||||||
import com.gmail.nossr50.util.blockmeta.chunkmeta.ChunkManager;
|
import com.gmail.nossr50.util.blockmeta.ChunkManager;
|
||||||
import com.gmail.nossr50.util.blockmeta.chunkmeta.ChunkManagerFactory;
|
import com.gmail.nossr50.util.blockmeta.ChunkManagerFactory;
|
||||||
import com.gmail.nossr50.util.commands.CommandRegistrationManager;
|
import com.gmail.nossr50.util.commands.CommandRegistrationManager;
|
||||||
import com.gmail.nossr50.util.compat.CompatibilityManager;
|
import com.gmail.nossr50.util.compat.CompatibilityManager;
|
||||||
import com.gmail.nossr50.util.experience.FormulaManager;
|
import com.gmail.nossr50.util.experience.FormulaManager;
|
||||||
@ -336,8 +336,8 @@ public class mcMMO extends JavaPlugin {
|
|||||||
|
|
||||||
formulaManager.saveFormula();
|
formulaManager.saveFormula();
|
||||||
holidayManager.saveAnniversaryFiles();
|
holidayManager.saveAnniversaryFiles();
|
||||||
placeStore.saveAll(); // Save our metadata
|
|
||||||
placeStore.cleanUp(); // Cleanup empty metadata stores
|
placeStore.cleanUp(); // Cleanup empty metadata stores
|
||||||
|
placeStore.closeAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (Exception e) { e.printStackTrace(); }
|
catch (Exception e) { e.printStackTrace(); }
|
||||||
|
@ -0,0 +1,243 @@
|
|||||||
|
package com.gmail.nossr50.util.blockmeta;
|
||||||
|
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.World;
|
||||||
|
|
||||||
|
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;
|
||||||
|
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;
|
||||||
|
|
||||||
|
public BitSetChunkStore(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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BitSetChunkStore() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDirty() {
|
||||||
|
return dirty;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDirty(boolean dirty) {
|
||||||
|
this.dirty = dirty;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getChunkX() {
|
||||||
|
return cx;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getChunkZ() {
|
||||||
|
return cz;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UUID getWorldId() {
|
||||||
|
return worldUid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isTrue(int x, int y, int z) {
|
||||||
|
return store.get(coordToIndex(x, y, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTrue(int x, int y, int z) {
|
||||||
|
set(x, y, z, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFalse(int x, int y, int z) {
|
||||||
|
set(x, y, z, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void set(int x, int y, int z, boolean value) {
|
||||||
|
store.set(coordToIndex(x, y, z), value);
|
||||||
|
dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return store.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int coordToIndex(int x, int y, int z) {
|
||||||
|
if (x < 0 || x >= 16 || y < 0 || y >= worldHeight || z < 0 || z >= 16)
|
||||||
|
throw new IndexOutOfBoundsException();
|
||||||
|
return (z * 16 + x) + (256 * y);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fixWorldHeight() {
|
||||||
|
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;
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void serialize(DataOutputStream out) throws IOException {
|
||||||
|
out.writeInt(MAGIC_NUMBER);
|
||||||
|
out.writeInt(CURRENT_VERSION);
|
||||||
|
|
||||||
|
out.writeLong(worldUid.getLeastSignificantBits());
|
||||||
|
out.writeLong(worldUid.getMostSignificantBits());
|
||||||
|
out.writeInt(cx);
|
||||||
|
out.writeInt(cz);
|
||||||
|
out.writeInt(worldHeight);
|
||||||
|
|
||||||
|
// Store the byte array directly so we don't have the object type info overhead
|
||||||
|
byte[] storeData = store.toByteArray();
|
||||||
|
out.writeInt(storeData.length);
|
||||||
|
out.write(storeData);
|
||||||
|
|
||||||
|
dirty = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BitSetChunkStore deserialize(DataInputStream in) throws IOException {
|
||||||
|
int magic = in.readInt();
|
||||||
|
// Can be used to determine the format of the file
|
||||||
|
int fileVersionNumber = in.readInt();
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
chunkStore.worldHeight = in.readInt();
|
||||||
|
byte[] temp = new byte[in.readInt()];
|
||||||
|
in.readFully(temp);
|
||||||
|
chunkStore.store = BitSet.valueOf(temp);
|
||||||
|
|
||||||
|
chunkStore.fixWorldHeight();
|
||||||
|
return chunkStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Serialization {
|
||||||
|
|
||||||
|
public static final short STREAM_MAGIC = (short)0xACDC;
|
||||||
|
|
||||||
|
public static ChunkStore readChunkStore(DataInputStream inputStream) throws IOException {
|
||||||
|
if (inputStream.markSupported())
|
||||||
|
inputStream.mark(2);
|
||||||
|
short magicNumber = inputStream.readShort();
|
||||||
|
|
||||||
|
if (magicNumber == ObjectStreamConstants.STREAM_MAGIC) // Java serializable, use legacy serialization
|
||||||
|
{
|
||||||
|
// "Un-read" the magic number for Serializables, they need it to still be in the stream
|
||||||
|
if (inputStream.markSupported())
|
||||||
|
inputStream.reset(); // Pretend we never read those bytes
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 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 >>> 8) & 0xFF);
|
||||||
|
inputStream = new DataInputStream(pushbackInputStream);
|
||||||
|
}
|
||||||
|
return new LegacyDeserializationInputStream(inputStream).readLegacyChunkStore();
|
||||||
|
}
|
||||||
|
else if (magicNumber == STREAM_MAGIC) // Pure bytes format
|
||||||
|
{
|
||||||
|
return BitSetChunkStore.deserialize(inputStream);
|
||||||
|
}
|
||||||
|
throw new IOException("Bad Data Format");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void writeChunkStore(DataOutputStream outputStream, ChunkStore chunkStore) throws IOException {
|
||||||
|
if (!(chunkStore instanceof BitSetChunkStore))
|
||||||
|
throw new InvalidClassException("ChunkStore must be instance of BitSetChunkStore");
|
||||||
|
outputStream.writeShort(STREAM_MAGIC);
|
||||||
|
((BitSetChunkStore)chunkStore).serialize(outputStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles loading the old serialized classes even though we have changed name/package
|
||||||
|
private static class LegacyDeserializationInputStream extends ObjectInputStream {
|
||||||
|
public LegacyDeserializationInputStream(InputStream in) throws IOException {
|
||||||
|
super(in);
|
||||||
|
enableResolveObject(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
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 read;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChunkStore readLegacyChunkStore(){
|
||||||
|
try {
|
||||||
|
return (ChunkStore) readObject();
|
||||||
|
} catch (IOException | ClassNotFoundException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,59 +1,12 @@
|
|||||||
package com.gmail.nossr50.util.blockmeta.chunkmeta;
|
package com.gmail.nossr50.util.blockmeta;
|
||||||
|
|
||||||
import org.bukkit.World;
|
import org.bukkit.World;
|
||||||
import org.bukkit.block.Block;
|
import org.bukkit.block.Block;
|
||||||
import org.bukkit.block.BlockState;
|
import org.bukkit.block.BlockState;
|
||||||
import org.bukkit.entity.Entity;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
public interface ChunkManager {
|
public interface ChunkManager {
|
||||||
void closeAll();
|
void closeAll();
|
||||||
|
|
||||||
ChunkStore readChunkStore(World world, int x, int z) throws IOException;
|
|
||||||
|
|
||||||
void writeChunkStore(World world, int x, int z, ChunkStore data);
|
|
||||||
|
|
||||||
void closeChunkStore(World world, int x, int z);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads a specific chunklet
|
|
||||||
*
|
|
||||||
* @param cx Chunklet X coordinate that needs to be loaded
|
|
||||||
* @param cy Chunklet Y coordinate that needs to be loaded
|
|
||||||
* @param cz Chunklet Z coordinate that needs to be loaded
|
|
||||||
* @param world World that the chunklet needs to be loaded in
|
|
||||||
*/
|
|
||||||
void loadChunklet(int cx, int cy, int cz, World world);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unload a specific chunklet
|
|
||||||
*
|
|
||||||
* @param cx Chunklet X coordinate that needs to be unloaded
|
|
||||||
* @param cy Chunklet Y coordinate that needs to be unloaded
|
|
||||||
* @param cz Chunklet Z coordinate that needs to be unloaded
|
|
||||||
* @param world World that the chunklet needs to be unloaded from
|
|
||||||
*/
|
|
||||||
void unloadChunklet(int cx, int cy, int cz, World world);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load a given Chunk's Chunklet data
|
|
||||||
*
|
|
||||||
* @param cx Chunk X coordinate that is to be loaded
|
|
||||||
* @param cz Chunk Z coordinate that is to be loaded
|
|
||||||
* @param world World that the Chunk is in
|
|
||||||
*/
|
|
||||||
void loadChunk(int cx, int cz, World world, Entity[] entities);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unload a given Chunk's Chunklet data
|
|
||||||
*
|
|
||||||
* @param cx Chunk X coordinate that is to be unloaded
|
|
||||||
* @param cz Chunk Z coordinate that is to be unloaded
|
|
||||||
* @param world World that the Chunk is in
|
|
||||||
*/
|
|
||||||
void unloadChunk(int cx, int cz, World world);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves a given Chunk's Chunklet data
|
* Saves a given Chunk's Chunklet data
|
||||||
*
|
*
|
||||||
@ -63,17 +16,6 @@ public interface ChunkManager {
|
|||||||
*/
|
*/
|
||||||
void saveChunk(int cx, int cz, World world);
|
void saveChunk(int cx, int cz, World world);
|
||||||
|
|
||||||
boolean isChunkLoaded(int cx, int cz, World world);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Informs the ChunkletManager a chunk is loaded
|
|
||||||
*
|
|
||||||
* @param cx Chunk X coordinate that is loaded
|
|
||||||
* @param cz Chunk Z coordinate that is loaded
|
|
||||||
* @param world World that the chunk was loaded in
|
|
||||||
*/
|
|
||||||
void chunkLoaded(int cx, int cz, World world);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Informs the ChunkletManager a chunk is unloaded
|
* Informs the ChunkletManager a chunk is unloaded
|
||||||
*
|
*
|
||||||
@ -97,23 +39,11 @@ public interface ChunkManager {
|
|||||||
*/
|
*/
|
||||||
void unloadWorld(World world);
|
void unloadWorld(World world);
|
||||||
|
|
||||||
/**
|
|
||||||
* Load all ChunkletStores from all loaded chunks from this world into memory
|
|
||||||
*
|
|
||||||
* @param world World to load
|
|
||||||
*/
|
|
||||||
void loadWorld(World world);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save all ChunkletStores
|
* Save all ChunkletStores
|
||||||
*/
|
*/
|
||||||
void saveAll();
|
void saveAll();
|
||||||
|
|
||||||
/**
|
|
||||||
* Unload all ChunkletStores after saving them
|
|
||||||
*/
|
|
||||||
void unloadAll();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check to see if a given location is set to true
|
* Check to see if a given location is set to true
|
||||||
*
|
*
|
@ -1,4 +1,4 @@
|
|||||||
package com.gmail.nossr50.util.blockmeta.chunkmeta;
|
package com.gmail.nossr50.util.blockmeta;
|
||||||
|
|
||||||
import com.gmail.nossr50.config.HiddenConfig;
|
import com.gmail.nossr50.config.HiddenConfig;
|
||||||
|
|
@ -1,13 +1,13 @@
|
|||||||
package com.gmail.nossr50.util.blockmeta.chunkmeta;
|
package com.gmail.nossr50.util.blockmeta;
|
||||||
|
|
||||||
import com.gmail.nossr50.util.blockmeta.ChunkletStore;
|
import org.bukkit.World;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A ChunkStore should be responsible for a 16x16xWorldHeight area of data
|
* A ChunkStore should be responsible for a 16x16xWorldHeight area of data
|
||||||
*/
|
*/
|
||||||
public interface ChunkStore extends Serializable {
|
public interface ChunkStore {
|
||||||
/**
|
/**
|
||||||
* Checks the chunk's save state
|
* Checks the chunk's save state
|
||||||
*
|
*
|
||||||
@ -36,6 +36,8 @@ public interface ChunkStore extends Serializable {
|
|||||||
*/
|
*/
|
||||||
int getChunkZ();
|
int getChunkZ();
|
||||||
|
|
||||||
|
UUID getWorldId();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks the value at the given coordinates
|
* Checks the value at the given coordinates
|
||||||
*
|
*
|
||||||
@ -64,15 +66,18 @@ public interface ChunkStore extends Serializable {
|
|||||||
*/
|
*/
|
||||||
void setFalse(int x, int y, int z);
|
void setFalse(int x, int y, int z);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the value at the given coordinates
|
||||||
|
*
|
||||||
|
* @param x x coordinate in current chunklet
|
||||||
|
* @param y y coordinate in current chunklet
|
||||||
|
* @param z z coordinate in current chunklet
|
||||||
|
* @param value value to set
|
||||||
|
*/
|
||||||
|
void set(int x, int y, int z, boolean value);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return true if all values in the chunklet are false, false if otherwise
|
* @return true if all values in the chunklet are false, false if otherwise
|
||||||
*/
|
*/
|
||||||
boolean isEmpty();
|
boolean isEmpty();
|
||||||
|
|
||||||
/**
|
|
||||||
* Set all values in this ChunkletStore to the values from another provided ChunkletStore
|
|
||||||
*
|
|
||||||
* @param otherStore Another ChunkletStore that this one should copy all data from
|
|
||||||
*/
|
|
||||||
void copyFrom(ChunkletStore otherStore);
|
|
||||||
}
|
}
|
@ -1,151 +0,0 @@
|
|||||||
package com.gmail.nossr50.util.blockmeta;
|
|
||||||
|
|
||||||
import org.bukkit.World;
|
|
||||||
import org.bukkit.block.Block;
|
|
||||||
|
|
||||||
public interface ChunkletManager {
|
|
||||||
/**
|
|
||||||
* Loads a specific chunklet
|
|
||||||
*
|
|
||||||
* @param cx Chunklet X coordinate that needs to be loaded
|
|
||||||
* @param cy Chunklet Y coordinate that needs to be loaded
|
|
||||||
* @param cz Chunklet Z coordinate that needs to be loaded
|
|
||||||
* @param world World that the chunklet needs to be loaded in
|
|
||||||
*/
|
|
||||||
void loadChunklet(int cx, int cy, int cz, World world);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unload a specific chunklet
|
|
||||||
*
|
|
||||||
* @param cx Chunklet X coordinate that needs to be unloaded
|
|
||||||
* @param cy Chunklet Y coordinate that needs to be unloaded
|
|
||||||
* @param cz Chunklet Z coordinate that needs to be unloaded
|
|
||||||
* @param world World that the chunklet needs to be unloaded from
|
|
||||||
*/
|
|
||||||
void unloadChunklet(int cx, int cy, int cz, World world);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load a given Chunk's Chunklet data
|
|
||||||
*
|
|
||||||
* @param cx Chunk X coordinate that is to be loaded
|
|
||||||
* @param cz Chunk Z coordinate that is to be loaded
|
|
||||||
* @param world World that the Chunk is in
|
|
||||||
*/
|
|
||||||
void loadChunk(int cx, int cz, World world);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unload a given Chunk's Chunklet data
|
|
||||||
*
|
|
||||||
* @param cx Chunk X coordinate that is to be unloaded
|
|
||||||
* @param cz Chunk Z coordinate that is to be unloaded
|
|
||||||
* @param world World that the Chunk is in
|
|
||||||
*/
|
|
||||||
void unloadChunk(int cx, int cz, World world);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Informs the ChunkletManager a chunk is loaded
|
|
||||||
*
|
|
||||||
* @param cx Chunk X coordinate that is loaded
|
|
||||||
* @param cz Chunk Z coordinate that is loaded
|
|
||||||
* @param world World that the chunk was loaded in
|
|
||||||
*/
|
|
||||||
void chunkLoaded(int cx, int cz, World world);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Informs the ChunkletManager a chunk is unloaded
|
|
||||||
*
|
|
||||||
* @param cx Chunk X coordinate that is unloaded
|
|
||||||
* @param cz Chunk Z coordinate that is unloaded
|
|
||||||
* @param world World that the chunk was unloaded in
|
|
||||||
*/
|
|
||||||
void chunkUnloaded(int cx, int cz, World world);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Save all ChunkletStores related to the given world
|
|
||||||
*
|
|
||||||
* @param world World to save
|
|
||||||
*/
|
|
||||||
void saveWorld(World world);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unload all ChunkletStores from memory related to the given world after saving them
|
|
||||||
*
|
|
||||||
* @param world World to unload
|
|
||||||
*/
|
|
||||||
void unloadWorld(World world);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load all ChunkletStores from all loaded chunks from this world into memory
|
|
||||||
*
|
|
||||||
* @param world World to load
|
|
||||||
*/
|
|
||||||
void loadWorld(World world);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Save all ChunkletStores
|
|
||||||
*/
|
|
||||||
void saveAll();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unload all ChunkletStores after saving them
|
|
||||||
*/
|
|
||||||
void unloadAll();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check to see if a given location is set to true
|
|
||||||
*
|
|
||||||
* @param x X coordinate to check
|
|
||||||
* @param y Y coordinate to check
|
|
||||||
* @param z Z coordinate to check
|
|
||||||
* @param world World to check in
|
|
||||||
* @return true if the given location is set to true, false if otherwise
|
|
||||||
*/
|
|
||||||
boolean isTrue(int x, int y, int z, World world);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check to see if a given block location is set to true
|
|
||||||
*
|
|
||||||
* @param block Block location to check
|
|
||||||
* @return true if the given block location is set to true, false if otherwise
|
|
||||||
*/
|
|
||||||
boolean isTrue(Block block);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set a given location to true, should create stores as necessary if the location does not exist
|
|
||||||
*
|
|
||||||
* @param x X coordinate to set
|
|
||||||
* @param y Y coordinate to set
|
|
||||||
* @param z Z coordinate to set
|
|
||||||
* @param world World to set in
|
|
||||||
*/
|
|
||||||
void setTrue(int x, int y, int z, World world);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set a given block location to true, should create stores as necessary if the location does not exist
|
|
||||||
*
|
|
||||||
* @param block Block location to set
|
|
||||||
*/
|
|
||||||
void setTrue(Block block);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set a given location to false, should not create stores if one does not exist for the given location
|
|
||||||
*
|
|
||||||
* @param x X coordinate to set
|
|
||||||
* @param y Y coordinate to set
|
|
||||||
* @param z Z coordinate to set
|
|
||||||
* @param world World to set in
|
|
||||||
*/
|
|
||||||
void setFalse(int x, int y, int z, World world);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set a given block location to false, should not create stores if one does not exist for the given location
|
|
||||||
*
|
|
||||||
* @param block Block location to set
|
|
||||||
*/
|
|
||||||
void setFalse(Block block);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete any ChunkletStores that are empty
|
|
||||||
*/
|
|
||||||
void cleanUp();
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
package com.gmail.nossr50.util.blockmeta;
|
|
||||||
|
|
||||||
import com.gmail.nossr50.config.HiddenConfig;
|
|
||||||
|
|
||||||
public class ChunkletManagerFactory {
|
|
||||||
public static ChunkletManager getChunkletManager() {
|
|
||||||
HiddenConfig hConfig = HiddenConfig.getInstance();
|
|
||||||
|
|
||||||
if (hConfig.getChunkletsEnabled()) {
|
|
||||||
return new HashChunkletManager();
|
|
||||||
}
|
|
||||||
|
|
||||||
return new NullChunkletManager();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
package com.gmail.nossr50.util.blockmeta;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A ChunkletStore should be responsible for a 16x16x64 area of data
|
|
||||||
*/
|
|
||||||
public interface ChunkletStore extends Serializable {
|
|
||||||
/**
|
|
||||||
* Checks the value at the given coordinates
|
|
||||||
*
|
|
||||||
* @param x x coordinate in current chunklet
|
|
||||||
* @param y y coordinate in current chunklet
|
|
||||||
* @param z z coordinate in current chunklet
|
|
||||||
* @return true if the value is true at the given coordinates, false if otherwise
|
|
||||||
*/
|
|
||||||
boolean isTrue(int x, int y, int z);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the value to true at the given coordinates
|
|
||||||
*
|
|
||||||
* @param x x coordinate in current chunklet
|
|
||||||
* @param y y coordinate in current chunklet
|
|
||||||
* @param z z coordinate in current chunklet
|
|
||||||
*/
|
|
||||||
void setTrue(int x, int y, int z);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the value to false at the given coordinates
|
|
||||||
*
|
|
||||||
* @param x x coordinate in current chunklet
|
|
||||||
* @param y y coordinate in current chunklet
|
|
||||||
* @param z z coordinate in current chunklet
|
|
||||||
*/
|
|
||||||
void setFalse(int x, int y, int z);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return true if all values in the chunklet are false, false if otherwise
|
|
||||||
*/
|
|
||||||
boolean isEmpty();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set all values in this ChunkletStore to the values from another provided ChunkletStore
|
|
||||||
*
|
|
||||||
* @param otherStore Another ChunkletStore that this one should copy all data from
|
|
||||||
*/
|
|
||||||
void copyFrom(ChunkletStore otherStore);
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
package com.gmail.nossr50.util.blockmeta;
|
|
||||||
|
|
||||||
public class ChunkletStoreFactory {
|
|
||||||
protected static ChunkletStore getChunkletStore() {
|
|
||||||
// TODO: Add in loading from config what type of store we want.
|
|
||||||
return new PrimitiveExChunkletStore();
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,354 @@
|
|||||||
|
package com.gmail.nossr50.util.blockmeta;
|
||||||
|
|
||||||
|
import com.gmail.nossr50.mcMMO;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.block.Block;
|
||||||
|
import org.bukkit.block.BlockState;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class HashChunkManager implements ChunkManager {
|
||||||
|
private final HashMap<CoordinateKey, McMMOSimpleRegionFile> regionMap = new HashMap<>(); // Tracks active regions
|
||||||
|
private final HashMap<CoordinateKey, HashSet<CoordinateKey>> chunkUsageMap = new HashMap<>(); // Tracks active chunks by region
|
||||||
|
private final HashMap<CoordinateKey, ChunkStore> chunkMap = new HashMap<>(); // Tracks active chunks
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void closeAll() {
|
||||||
|
// Save all dirty chunkstores
|
||||||
|
for (ChunkStore chunkStore : chunkMap.values())
|
||||||
|
{
|
||||||
|
if (!chunkStore.isDirty())
|
||||||
|
continue;
|
||||||
|
writeChunkStore(Bukkit.getWorld(chunkStore.getWorldId()), chunkStore);
|
||||||
|
}
|
||||||
|
// Clear in memory chunks
|
||||||
|
chunkMap.clear();
|
||||||
|
chunkUsageMap.clear();
|
||||||
|
// Close all region files
|
||||||
|
for (McMMOSimpleRegionFile rf : regionMap.values())
|
||||||
|
rf.close();
|
||||||
|
regionMap.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized ChunkStore readChunkStore(World world, int cx, int cz) throws IOException {
|
||||||
|
McMMOSimpleRegionFile rf = getSimpleRegionFile(world, cx, cz, false);
|
||||||
|
if (rf == null)
|
||||||
|
return null; // If there is no region file, there can't be a chunk
|
||||||
|
try (DataInputStream in = rf.getInputStream(cx, cz)) { // Get input stream for chunk
|
||||||
|
if (in == null)
|
||||||
|
return null; // No chunk
|
||||||
|
return BitSetChunkStore.Serialization.readChunkStore(in); // Read in the chunkstore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void writeChunkStore(World world, ChunkStore data) {
|
||||||
|
if (!data.isDirty())
|
||||||
|
return; // Don't save unchanged data
|
||||||
|
try {
|
||||||
|
McMMOSimpleRegionFile rf = getSimpleRegionFile(world, data.getChunkX(), data.getChunkZ(), true);
|
||||||
|
try (DataOutputStream out = rf.getOutputStream(data.getChunkX(), data.getChunkZ())) {
|
||||||
|
BitSetChunkStore.Serialization.writeChunkStore(out, data);
|
||||||
|
}
|
||||||
|
data.setDirty(false);
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
throw new RuntimeException("Unable to write chunk meta data for " + data.getChunkX() + ", " + data.getChunkZ(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized McMMOSimpleRegionFile getSimpleRegionFile(World world, int cx, int cz, boolean createIfAbsent) {
|
||||||
|
CoordinateKey regionKey = toRegionKey(world.getUID(), cx, cz);
|
||||||
|
|
||||||
|
return regionMap.computeIfAbsent(regionKey, k -> {
|
||||||
|
File worldRegionsDirectory = new File(world.getWorldFolder(), "mcmmo_regions");
|
||||||
|
if (!createIfAbsent && !worldRegionsDirectory.isDirectory())
|
||||||
|
return null; // Don't create the directory on read-only operations
|
||||||
|
worldRegionsDirectory.mkdirs(); // Ensure directory exists
|
||||||
|
File regionFile = new File(worldRegionsDirectory, "mcmmo_" + regionKey.x + "_" + regionKey.z + "_.mcm");
|
||||||
|
if (!createIfAbsent && !regionFile.exists())
|
||||||
|
return null; // Don't create the file on read-only operations
|
||||||
|
return new McMMOSimpleRegionFile(regionFile, regionKey.x, regionKey.z);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private ChunkStore loadChunk(int cx, int cz, World world) {
|
||||||
|
try {
|
||||||
|
return readChunkStore(world, cx, cz);
|
||||||
|
}
|
||||||
|
catch (Exception ignored) {}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void unloadChunk(int cx, int cz, World world) {
|
||||||
|
CoordinateKey chunkKey = toChunkKey(world.getUID(), cx, cz);
|
||||||
|
ChunkStore chunkStore = chunkMap.remove(chunkKey); // Remove from chunk map
|
||||||
|
if (chunkStore == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (chunkStore.isDirty())
|
||||||
|
writeChunkStore(world, chunkStore);
|
||||||
|
|
||||||
|
CoordinateKey regionKey = toRegionKey(world.getUID(), cx, cz);
|
||||||
|
HashSet<CoordinateKey> chunkKeys = chunkUsageMap.get(regionKey);
|
||||||
|
chunkKeys.remove(chunkKey); // remove from region file in-use set
|
||||||
|
if (chunkKeys.isEmpty()) // If it was last chunk in region, close the region file and remove it from memory
|
||||||
|
{
|
||||||
|
chunkUsageMap.remove(regionKey);
|
||||||
|
regionMap.remove(regionKey).close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void saveChunk(int cx, int cz, World world) {
|
||||||
|
if (world == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
CoordinateKey chunkKey = toChunkKey(world.getUID(), cx, cz);
|
||||||
|
|
||||||
|
ChunkStore out = chunkMap.get(chunkKey);
|
||||||
|
|
||||||
|
if (out == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!out.isDirty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
writeChunkStore(world, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void chunkUnloaded(int cx, int cz, World world) {
|
||||||
|
if (world == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
unloadChunk(cx, cz, world);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void saveWorld(World world) {
|
||||||
|
if (world == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
UUID wID = world.getUID();
|
||||||
|
|
||||||
|
// Save all teh chunks
|
||||||
|
for (ChunkStore chunkStore : chunkMap.values()) {
|
||||||
|
if (!chunkStore.isDirty())
|
||||||
|
continue;
|
||||||
|
if (!wID.equals(chunkStore.getWorldId()))
|
||||||
|
continue;
|
||||||
|
try {
|
||||||
|
writeChunkStore(world, chunkStore);
|
||||||
|
}
|
||||||
|
catch (Exception ignore) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void unloadWorld(World world) {
|
||||||
|
if (world == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
UUID wID = world.getUID();
|
||||||
|
|
||||||
|
// Save and remove all the chunks
|
||||||
|
List<CoordinateKey> chunkKeys = new ArrayList<>(chunkMap.keySet());
|
||||||
|
for (CoordinateKey chunkKey : chunkKeys) {
|
||||||
|
if (!wID.equals(chunkKey.worldID))
|
||||||
|
continue;
|
||||||
|
ChunkStore chunkStore = chunkMap.remove(chunkKey);
|
||||||
|
if (!chunkStore.isDirty())
|
||||||
|
continue;
|
||||||
|
try {
|
||||||
|
writeChunkStore(world, chunkStore);
|
||||||
|
}
|
||||||
|
catch (Exception ignore) { }
|
||||||
|
}
|
||||||
|
// Clear all the region files
|
||||||
|
List<CoordinateKey> regionKeys = new ArrayList<>(regionMap.keySet());
|
||||||
|
for (CoordinateKey regionKey : regionKeys) {
|
||||||
|
if (!wID.equals(regionKey.worldID))
|
||||||
|
continue;
|
||||||
|
regionMap.remove(regionKey).close();
|
||||||
|
chunkUsageMap.remove(regionKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void saveAll() {
|
||||||
|
for (World world : mcMMO.p.getServer().getWorlds()) {
|
||||||
|
saveWorld(world);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized boolean isTrue(int x, int y, int z, World world) {
|
||||||
|
if (world == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
CoordinateKey chunkKey = blockCoordinateToChunkKey(world.getUID(), x, y, z);
|
||||||
|
|
||||||
|
// Get chunk, load from file if necessary
|
||||||
|
// Get/Load/Create chunkstore
|
||||||
|
ChunkStore check = chunkMap.computeIfAbsent(chunkKey, k -> {
|
||||||
|
// Load from file
|
||||||
|
ChunkStore loaded = loadChunk(chunkKey.x, chunkKey.z, world);
|
||||||
|
if (loaded == null)
|
||||||
|
return null;
|
||||||
|
// Mark chunk in-use for region tracking
|
||||||
|
chunkUsageMap.computeIfAbsent(toRegionKey(chunkKey.worldID, chunkKey.x, chunkKey.z), j -> new HashSet<>()).add(chunkKey);
|
||||||
|
return loaded;
|
||||||
|
});
|
||||||
|
|
||||||
|
// No chunk, return false
|
||||||
|
if (check == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int ix = Math.abs(x) % 16;
|
||||||
|
int iz = Math.abs(z) % 16;
|
||||||
|
|
||||||
|
return check.isTrue(ix, y, iz);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized boolean isTrue(Block block) {
|
||||||
|
if (block == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return isTrue(block.getX(), block.getY(), block.getZ(), block.getWorld());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized boolean isTrue(BlockState blockState) {
|
||||||
|
if (blockState == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return isTrue(blockState.getX(), blockState.getY(), blockState.getZ(), blockState.getWorld());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void setTrue(int x, int y, int z, World world) {
|
||||||
|
set(x, y, z, world, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void setTrue(Block block) {
|
||||||
|
if (block == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
setTrue(block.getX(), block.getY(), block.getZ(), block.getWorld());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void setTrue(BlockState blockState) {
|
||||||
|
if (blockState == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
setTrue(blockState.getX(), blockState.getY(), blockState.getZ(), blockState.getWorld());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void setFalse(int x, int y, int z, World world) {
|
||||||
|
set(x, y, z, world, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void setFalse(Block block) {
|
||||||
|
if (block == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
setFalse(block.getX(), block.getY(), block.getZ(), block.getWorld());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void setFalse(BlockState blockState) {
|
||||||
|
if (blockState == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
setFalse(blockState.getX(), blockState.getY(), blockState.getZ(), blockState.getWorld());
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void set(int x, int y, int z, World world, boolean value){
|
||||||
|
if (world == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
CoordinateKey chunkKey = blockCoordinateToChunkKey(world.getUID(), x, y, z);
|
||||||
|
|
||||||
|
// Get/Load/Create chunkstore
|
||||||
|
ChunkStore cStore = chunkMap.computeIfAbsent(chunkKey, k -> {
|
||||||
|
// Load from file
|
||||||
|
ChunkStore loaded = loadChunk(chunkKey.x, chunkKey.z, world);
|
||||||
|
if (loaded != null)
|
||||||
|
{
|
||||||
|
chunkUsageMap.computeIfAbsent(toRegionKey(chunkKey.worldID, chunkKey.x, chunkKey.z), j -> new HashSet<>()).add(chunkKey);
|
||||||
|
return loaded;
|
||||||
|
}
|
||||||
|
// If setting to false, no need to create an empty chunkstore
|
||||||
|
if (!value)
|
||||||
|
return null;
|
||||||
|
// Mark chunk in-use for region tracking
|
||||||
|
chunkUsageMap.computeIfAbsent(toRegionKey(chunkKey.worldID, chunkKey.x, chunkKey.z), j -> new HashSet<>()).add(chunkKey);
|
||||||
|
// Create a new chunkstore
|
||||||
|
return new BitSetChunkStore(world, chunkKey.x, chunkKey.z);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Indicates setting false on empty chunkstore
|
||||||
|
if (cStore == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Get block offset (offset from chunk corner)
|
||||||
|
int ix = Math.abs(x) % 16;
|
||||||
|
int iz = Math.abs(z) % 16;
|
||||||
|
|
||||||
|
// Set chunk store value
|
||||||
|
cStore.set(ix, y, iz, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CoordinateKey blockCoordinateToChunkKey(UUID worldUid, int x, int y, int z) {
|
||||||
|
return toChunkKey(worldUid, x >> 4, z >> 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CoordinateKey toChunkKey(UUID worldUid, int cx, int cz){
|
||||||
|
return new CoordinateKey(worldUid, cx, cz);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CoordinateKey toRegionKey(UUID worldUid, int cx, int cz) {
|
||||||
|
// Compute region index (32x32 chunk regions)
|
||||||
|
int rx = cx >> 5;
|
||||||
|
int rz = cz >> 5;
|
||||||
|
return new CoordinateKey(worldUid, rx, rz);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class CoordinateKey {
|
||||||
|
public final UUID worldID;
|
||||||
|
public final int x;
|
||||||
|
public final int z;
|
||||||
|
|
||||||
|
private CoordinateKey(UUID worldID, int x, int z) {
|
||||||
|
this.worldID = worldID;
|
||||||
|
this.x = x;
|
||||||
|
this.z = z;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
CoordinateKey coordinateKey = (CoordinateKey) o;
|
||||||
|
return x == coordinateKey.x &&
|
||||||
|
z == coordinateKey.z &&
|
||||||
|
worldID.equals(coordinateKey.worldID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(worldID, x, z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void cleanUp() {}
|
||||||
|
}
|
@ -1,410 +0,0 @@
|
|||||||
package com.gmail.nossr50.util.blockmeta;
|
|
||||||
|
|
||||||
import com.gmail.nossr50.mcMMO;
|
|
||||||
import org.bukkit.World;
|
|
||||||
import org.bukkit.block.Block;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
import java.util.HashMap;
|
|
||||||
|
|
||||||
public class HashChunkletManager implements ChunkletManager {
|
|
||||||
public HashMap<String, ChunkletStore> store = new HashMap<>();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void loadChunklet(int cx, int cy, int cz, World world) {
|
|
||||||
File dataDir = new File(world.getWorldFolder(), "mcmmo_data");
|
|
||||||
File cxDir = new File(dataDir, "" + cx);
|
|
||||||
if (!cxDir.exists()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
File czDir = new File(cxDir, "" + cz);
|
|
||||||
if (!czDir.exists()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
File yFile = new File(czDir, "" + cy);
|
|
||||||
if (!yFile.exists()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ChunkletStore in = deserializeChunkletStore(yFile);
|
|
||||||
if (in != null) {
|
|
||||||
store.put(world.getName() + "," + cx + "," + cz + "," + cy, in);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void unloadChunklet(int cx, int cy, int cz, World world) {
|
|
||||||
File dataDir = new File(world.getWorldFolder(), "mcmmo_data");
|
|
||||||
if (store.containsKey(world.getName() + "," + cx + "," + cz + "," + cy)) {
|
|
||||||
File cxDir = new File(dataDir, "" + cx);
|
|
||||||
if (!cxDir.exists()) {
|
|
||||||
cxDir.mkdir();
|
|
||||||
}
|
|
||||||
File czDir = new File(cxDir, "" + cz);
|
|
||||||
if (!czDir.exists()) {
|
|
||||||
czDir.mkdir();
|
|
||||||
}
|
|
||||||
File yFile = new File(czDir, "" + cy);
|
|
||||||
|
|
||||||
ChunkletStore out = store.get(world.getName() + "," + cx + "," + cz + "," + cy);
|
|
||||||
serializeChunkletStore(out, yFile);
|
|
||||||
store.remove(world.getName() + "," + cx + "," + cz + "," + cy);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void loadChunk(int cx, int cz, World world) {
|
|
||||||
File dataDir = new File(world.getWorldFolder(), "mcmmo_data");
|
|
||||||
File cxDir = new File(dataDir, "" + cx);
|
|
||||||
if (!cxDir.exists()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
File czDir = new File(cxDir, "" + cz);
|
|
||||||
if (!czDir.exists()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int y = 0; y < 4; y++) {
|
|
||||||
File yFile = new File(czDir, "" + y);
|
|
||||||
if (!yFile.exists()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
ChunkletStore in = deserializeChunkletStore(yFile);
|
|
||||||
if (in != null) {
|
|
||||||
store.put(world.getName() + "," + cx + "," + cz + "," + y, in);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void unloadChunk(int cx, int cz, World world) {
|
|
||||||
File dataDir = new File(world.getWorldFolder(), "mcmmo_data");
|
|
||||||
|
|
||||||
for (int y = 0; y < 4; y++) {
|
|
||||||
if (store.containsKey(world.getName() + "," + cx + "," + cz + "," + y)) {
|
|
||||||
File cxDir = new File(dataDir, "" + cx);
|
|
||||||
if (!cxDir.exists()) {
|
|
||||||
cxDir.mkdir();
|
|
||||||
}
|
|
||||||
File czDir = new File(cxDir, "" + cz);
|
|
||||||
if (!czDir.exists()) {
|
|
||||||
czDir.mkdir();
|
|
||||||
}
|
|
||||||
File yFile = new File(czDir, "" + y);
|
|
||||||
|
|
||||||
ChunkletStore out = store.get(world.getName() + "," + cx + "," + cz + "," + y);
|
|
||||||
serializeChunkletStore(out, yFile);
|
|
||||||
store.remove(world.getName() + "," + cx + "," + cz + "," + y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void chunkLoaded(int cx, int cz, World world) {
|
|
||||||
//loadChunk(cx, cz, world);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void chunkUnloaded(int cx, int cz, World world) {
|
|
||||||
unloadChunk(cx, cx, world);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void saveWorld(World world) {
|
|
||||||
String worldName = world.getName();
|
|
||||||
File dataDir = new File(world.getWorldFolder(), "mcmmo_data");
|
|
||||||
if (!dataDir.exists()) {
|
|
||||||
dataDir.mkdirs();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (String key : store.keySet()) {
|
|
||||||
String[] info = key.split(",");
|
|
||||||
if (worldName.equals(info[0])) {
|
|
||||||
File cxDir = new File(dataDir, "" + info[1]);
|
|
||||||
if (!cxDir.exists()) {
|
|
||||||
cxDir.mkdir();
|
|
||||||
}
|
|
||||||
File czDir = new File(cxDir, "" + info[2]);
|
|
||||||
if (!czDir.exists()) {
|
|
||||||
czDir.mkdir();
|
|
||||||
}
|
|
||||||
|
|
||||||
File yFile = new File(czDir, "" + info[3]);
|
|
||||||
serializeChunkletStore(store.get(key), yFile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void unloadWorld(World world) {
|
|
||||||
saveWorld(world);
|
|
||||||
|
|
||||||
String worldName = world.getName();
|
|
||||||
|
|
||||||
for (String key : store.keySet()) {
|
|
||||||
String tempWorldName = key.split(",")[0];
|
|
||||||
if (tempWorldName.equals(worldName)) {
|
|
||||||
store.remove(key);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void loadWorld(World world) {
|
|
||||||
//for (Chunk chunk : world.getLoadedChunks()) {
|
|
||||||
// this.chunkLoaded(chunk.getX(), chunk.getZ(), world);
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void saveAll() {
|
|
||||||
for (World world : mcMMO.p.getServer().getWorlds()) {
|
|
||||||
saveWorld(world);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void unloadAll() {
|
|
||||||
saveAll();
|
|
||||||
for (World world : mcMMO.p.getServer().getWorlds()) {
|
|
||||||
unloadWorld(world);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isTrue(int x, int y, int z, World world) {
|
|
||||||
int cx = x >> 4;
|
|
||||||
int cz = z >> 4;
|
|
||||||
int cy = y >> 6;
|
|
||||||
|
|
||||||
String key = world.getName() + "," + cx + "," + cz + "," + cy;
|
|
||||||
|
|
||||||
if (!store.containsKey(key)) {
|
|
||||||
loadChunklet(cx, cy, cz, world);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!store.containsKey(key)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ChunkletStore check = store.get(world.getName() + "," + cx + "," + cz + "," + cy);
|
|
||||||
int ix = Math.abs(x) % 16;
|
|
||||||
int iz = Math.abs(z) % 16;
|
|
||||||
int iy = Math.abs(y) % 64;
|
|
||||||
|
|
||||||
return check.isTrue(ix, iy, iz);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isTrue(Block block) {
|
|
||||||
return isTrue(block.getX(), block.getY(), block.getZ(), block.getWorld());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setTrue(int x, int y, int z, World world) {
|
|
||||||
int cx = x >> 4;
|
|
||||||
int cz = z >> 4;
|
|
||||||
int cy = y >> 6;
|
|
||||||
|
|
||||||
int ix = Math.abs(x) % 16;
|
|
||||||
int iz = Math.abs(z) % 16;
|
|
||||||
int iy = Math.abs(y) % 64;
|
|
||||||
|
|
||||||
String key = world.getName() + "," + cx + "," + cz + "," + cy;
|
|
||||||
|
|
||||||
if (!store.containsKey(key)) {
|
|
||||||
loadChunklet(cx, cy, cz, world);
|
|
||||||
}
|
|
||||||
|
|
||||||
ChunkletStore cStore = store.get(key);
|
|
||||||
|
|
||||||
if (cStore == null) {
|
|
||||||
cStore = ChunkletStoreFactory.getChunkletStore();
|
|
||||||
|
|
||||||
store.put(world.getName() + "," + cx + "," + cz + "," + cy, cStore);
|
|
||||||
}
|
|
||||||
|
|
||||||
cStore.setTrue(ix, iy, iz);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setTrue(Block block) {
|
|
||||||
setTrue(block.getX(), block.getY(), block.getZ(), block.getWorld());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setFalse(int x, int y, int z, World world) {
|
|
||||||
int cx = x >> 4;
|
|
||||||
int cz = z >> 4;
|
|
||||||
int cy = y >> 6;
|
|
||||||
|
|
||||||
int ix = Math.abs(x) % 16;
|
|
||||||
int iz = Math.abs(z) % 16;
|
|
||||||
int iy = Math.abs(y) % 64;
|
|
||||||
|
|
||||||
String key = world.getName() + "," + cx + "," + cz + "," + cy;
|
|
||||||
|
|
||||||
if (!store.containsKey(key)) {
|
|
||||||
loadChunklet(cx, cy, cz, world);
|
|
||||||
}
|
|
||||||
|
|
||||||
ChunkletStore cStore = store.get(key);
|
|
||||||
|
|
||||||
if (cStore == null) {
|
|
||||||
return; // No need to make a store for something we will be setting to false
|
|
||||||
}
|
|
||||||
|
|
||||||
cStore.setFalse(ix, iy, iz);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setFalse(Block block) {
|
|
||||||
setFalse(block.getX(), block.getY(), block.getZ(), block.getWorld());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void cleanUp() {
|
|
||||||
for (String key : store.keySet()) {
|
|
||||||
if (store.get(key).isEmpty()) {
|
|
||||||
String[] info = key.split(",");
|
|
||||||
File dataDir = new File(mcMMO.p.getServer().getWorld(info[0]).getWorldFolder(), "mcmmo_data");
|
|
||||||
|
|
||||||
File cxDir = new File(dataDir, "" + info[1]);
|
|
||||||
if (!cxDir.exists()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
File czDir = new File(cxDir, "" + info[2]);
|
|
||||||
if (!czDir.exists()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
File yFile = new File(czDir, "" + info[3]);
|
|
||||||
yFile.delete();
|
|
||||||
|
|
||||||
// Delete empty directories
|
|
||||||
if (czDir.list().length == 0) {
|
|
||||||
czDir.delete();
|
|
||||||
}
|
|
||||||
if (cxDir.list().length == 0) {
|
|
||||||
cxDir.delete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param cStore ChunkletStore to save
|
|
||||||
* @param location Where on the disk to put it
|
|
||||||
*/
|
|
||||||
private void serializeChunkletStore(ChunkletStore cStore, File location) {
|
|
||||||
FileOutputStream fileOut = null;
|
|
||||||
ObjectOutputStream objOut = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (!location.exists()) {
|
|
||||||
location.createNewFile();
|
|
||||||
}
|
|
||||||
fileOut = new FileOutputStream(location);
|
|
||||||
objOut = new ObjectOutputStream(fileOut);
|
|
||||||
objOut.writeObject(cStore);
|
|
||||||
}
|
|
||||||
catch (IOException ex) {
|
|
||||||
ex.printStackTrace();
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
if (objOut != null) {
|
|
||||||
try {
|
|
||||||
objOut.flush();
|
|
||||||
objOut.close();
|
|
||||||
}
|
|
||||||
catch (IOException ex) {
|
|
||||||
ex.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fileOut != null) {
|
|
||||||
try {
|
|
||||||
fileOut.close();
|
|
||||||
}
|
|
||||||
catch (IOException ex) {
|
|
||||||
ex.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param location Where on the disk to read from
|
|
||||||
* @return ChunkletStore from the specified location
|
|
||||||
*/
|
|
||||||
private ChunkletStore deserializeChunkletStore(File location) {
|
|
||||||
ChunkletStore storeIn = null;
|
|
||||||
FileInputStream fileIn = null;
|
|
||||||
ObjectInputStream objIn = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
fileIn = new FileInputStream(location);
|
|
||||||
objIn = new ObjectInputStream(new BufferedInputStream(fileIn));
|
|
||||||
storeIn = (ChunkletStore) objIn.readObject();
|
|
||||||
}
|
|
||||||
catch (IOException ex) {
|
|
||||||
if (ex instanceof EOFException) {
|
|
||||||
// EOF should only happen on Chunklets that somehow have been corrupted.
|
|
||||||
//mcMMO.p.getLogger().severe("Chunklet data at " + location.toString() + " could not be read due to an EOFException, data in this area will be lost.");
|
|
||||||
return ChunkletStoreFactory.getChunkletStore();
|
|
||||||
}
|
|
||||||
else if (ex instanceof StreamCorruptedException) {
|
|
||||||
// StreamCorrupted happens when the Chunklet is no good.
|
|
||||||
//mcMMO.p.getLogger().severe("Chunklet data at " + location.toString() + " is corrupted, data in this area will be lost.");
|
|
||||||
return ChunkletStoreFactory.getChunkletStore();
|
|
||||||
}
|
|
||||||
else if (ex instanceof UTFDataFormatException) {
|
|
||||||
// UTF happens when the Chunklet cannot be read or is corrupted
|
|
||||||
//mcMMO.p.getLogger().severe("Chunklet data at " + location.toString() + " could not be read due to an UTFDataFormatException, data in this area will be lost.");
|
|
||||||
return ChunkletStoreFactory.getChunkletStore();
|
|
||||||
}
|
|
||||||
|
|
||||||
ex.printStackTrace();
|
|
||||||
}
|
|
||||||
catch (ClassNotFoundException ex) {
|
|
||||||
ex.printStackTrace();
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
if (objIn != null) {
|
|
||||||
try {
|
|
||||||
objIn.close();
|
|
||||||
}
|
|
||||||
catch (IOException ex) {
|
|
||||||
ex.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fileIn != null) {
|
|
||||||
try {
|
|
||||||
fileIn.close();
|
|
||||||
}
|
|
||||||
catch (IOException ex) {
|
|
||||||
ex.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Make this less messy, as it is, it's kinda... depressing to do it like this.
|
|
||||||
// Might also make a mess when we move to stacks, but at that point I think I will write a new Manager...
|
|
||||||
// IMPORTANT! If ChunkletStoreFactory is going to be returning something other than PrimitiveEx we need to remove this, as it will be breaking time for old maps
|
|
||||||
|
|
||||||
/*
|
|
||||||
if (!(storeIn instanceof PrimitiveExChunkletStore)) {
|
|
||||||
ChunkletStore tempStore = ChunkletStoreFactory.getChunkletStore();
|
|
||||||
if (storeIn != null) {
|
|
||||||
tempStore.copyFrom(storeIn);
|
|
||||||
}
|
|
||||||
storeIn = tempStore;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
return storeIn;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,257 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of SpoutPlugin.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2011-2012, SpoutDev <http://www.spout.org/>
|
||||||
|
* SpoutPlugin is licensed under the GNU Lesser General Public License.
|
||||||
|
*
|
||||||
|
* SpoutPlugin is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* SpoutPlugin is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package com.gmail.nossr50.util.blockmeta;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.BitSet;
|
||||||
|
import java.util.zip.DeflaterOutputStream;
|
||||||
|
import java.util.zip.InflaterInputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File format:
|
||||||
|
* bytes 0-4096 contain 1024 integer values representing the segment index of each chunk
|
||||||
|
* bytes 4096-8192 contain 1024 integer values representing the byte length of each chunk
|
||||||
|
* bytes 8192-8196 is the integer value of the segment exponent
|
||||||
|
* bytes 8196-12288 are reserved for future use
|
||||||
|
* bytes 12288+ contain the data segments, by default 1024 byte segments.
|
||||||
|
* Chunk data is compressed and stored in 1 or more segments as needed.
|
||||||
|
*/
|
||||||
|
public class McMMOSimpleRegionFile {
|
||||||
|
private static final int DEFAULT_SEGMENT_EXPONENT = 10; // TODO, analyze real world usage and determine if a smaller segment(512) is worth it or not. (need to know average chunkstore bytesize)
|
||||||
|
private static final int DEFAULT_SEGMENT_SIZE = (int)Math.pow(2, DEFAULT_SEGMENT_EXPONENT); // 1024
|
||||||
|
private static final int RESERVED_HEADER_BYTES = 12288; // This needs to be divisible by segment size
|
||||||
|
private static final int NUM_CHUNKS = 1024; // 32x32
|
||||||
|
private static final int SEEK_CHUNK_SEGMENT_INDICES = 0;
|
||||||
|
private static final int SEEK_CHUNK_BYTE_LENGTHS = 4096;
|
||||||
|
private static final int SEEK_FILE_INFO = 8192;
|
||||||
|
// Chunk info
|
||||||
|
private final int[] chunkSegmentIndex = new int[NUM_CHUNKS];
|
||||||
|
private final int[] chunkNumBytes = new int[NUM_CHUNKS];
|
||||||
|
private final int[] chunkNumSegments = new int[NUM_CHUNKS];
|
||||||
|
|
||||||
|
// Segments
|
||||||
|
private final BitSet segments = new BitSet(); // Used to denote which segments are in use or not
|
||||||
|
|
||||||
|
// Segment size/mask
|
||||||
|
private final int segmentExponent;
|
||||||
|
private final int segmentMask;
|
||||||
|
|
||||||
|
// File location
|
||||||
|
private final File parent;
|
||||||
|
// File access
|
||||||
|
private final RandomAccessFile file;
|
||||||
|
|
||||||
|
// Region index
|
||||||
|
private final int rx;
|
||||||
|
private final int rz;
|
||||||
|
|
||||||
|
public McMMOSimpleRegionFile(File f, int rx, int rz) {
|
||||||
|
this.rx = rx;
|
||||||
|
this.rz = rz;
|
||||||
|
this.parent = f;
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.file = new RandomAccessFile(parent, "rw");
|
||||||
|
|
||||||
|
// New file, write out header bytes
|
||||||
|
if (file.length() < RESERVED_HEADER_BYTES) {
|
||||||
|
file.write(new byte[RESERVED_HEADER_BYTES]);
|
||||||
|
file.seek(SEEK_FILE_INFO);
|
||||||
|
file.writeInt(DEFAULT_SEGMENT_EXPONENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
file.seek(SEEK_FILE_INFO);
|
||||||
|
this.segmentExponent = file.readInt();
|
||||||
|
this.segmentMask = (1 << segmentExponent) - 1;
|
||||||
|
|
||||||
|
// Mark reserved segments reserved
|
||||||
|
int reservedSegments = this.bytesToSegments(RESERVED_HEADER_BYTES);
|
||||||
|
segments.set(0, reservedSegments, true);
|
||||||
|
|
||||||
|
// Read chunk header data
|
||||||
|
file.seek(SEEK_CHUNK_SEGMENT_INDICES);
|
||||||
|
for (int i = 0; i < NUM_CHUNKS; i++)
|
||||||
|
chunkSegmentIndex[i] = file.readInt();
|
||||||
|
|
||||||
|
file.seek(SEEK_CHUNK_BYTE_LENGTHS);
|
||||||
|
for (int i = 0; i < NUM_CHUNKS; i++) {
|
||||||
|
chunkNumBytes[i] = file.readInt();
|
||||||
|
chunkNumSegments[i] = bytesToSegments(chunkNumBytes[i]);
|
||||||
|
markChunkSegments(i, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
fixFileLength();
|
||||||
|
}
|
||||||
|
catch (IOException fnfe) {
|
||||||
|
throw new RuntimeException(fnfe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized DataOutputStream getOutputStream(int x, int z) {
|
||||||
|
int index = getChunkIndex(x, z); // Get chunk index
|
||||||
|
return new DataOutputStream(new DeflaterOutputStream(new McMMOSimpleChunkBuffer(this, index)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class McMMOSimpleChunkBuffer extends ByteArrayOutputStream {
|
||||||
|
final McMMOSimpleRegionFile rf;
|
||||||
|
final int index;
|
||||||
|
|
||||||
|
McMMOSimpleChunkBuffer(McMMOSimpleRegionFile rf, int index) {
|
||||||
|
super(DEFAULT_SEGMENT_SIZE);
|
||||||
|
this.rf = rf;
|
||||||
|
this.index = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
rf.write(index, buf, count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void write(int index, byte[] buffer, int size) throws IOException {
|
||||||
|
int oldSegmentIndex = chunkSegmentIndex[index]; // Get current segment index
|
||||||
|
markChunkSegments(index, false); // Clear our old segments
|
||||||
|
int newSegmentIndex = findContiguousSegments(oldSegmentIndex, size); // Find contiguous segments to save to
|
||||||
|
file.seek(newSegmentIndex << segmentExponent); // Seek to file location
|
||||||
|
file.write(buffer, 0, size); // Write data
|
||||||
|
// update in memory info
|
||||||
|
chunkSegmentIndex[index] = newSegmentIndex;
|
||||||
|
chunkNumBytes[index] = size;
|
||||||
|
chunkNumSegments[index] = bytesToSegments(size);
|
||||||
|
// Mark segments in use
|
||||||
|
markChunkSegments(index, true);
|
||||||
|
// Update header info
|
||||||
|
file.seek(SEEK_CHUNK_SEGMENT_INDICES + (4 * index));
|
||||||
|
file.writeInt(chunkSegmentIndex[index]);
|
||||||
|
file.seek(SEEK_CHUNK_BYTE_LENGTHS + (4 * index));
|
||||||
|
file.writeInt(chunkNumBytes[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized DataInputStream getInputStream(int x, int z) throws IOException {
|
||||||
|
int index = getChunkIndex(x, z); // Get chunk index
|
||||||
|
int byteLength = chunkNumBytes[index]; // Get byte length of data
|
||||||
|
|
||||||
|
// No bytes
|
||||||
|
if (byteLength == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
byte[] data = new byte[byteLength];
|
||||||
|
|
||||||
|
file.seek(chunkSegmentIndex[index] << segmentExponent); // Seek to file location
|
||||||
|
file.readFully(data); // Read in the data
|
||||||
|
return new DataInputStream(new InflaterInputStream(new ByteArrayInputStream(data)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void close() {
|
||||||
|
try {
|
||||||
|
file.close();
|
||||||
|
segments.clear();
|
||||||
|
}
|
||||||
|
catch (IOException ioe) {
|
||||||
|
throw new RuntimeException("Unable to close file", ioe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void markChunkSegments(int index, boolean inUse) {
|
||||||
|
// No bytes used
|
||||||
|
if (chunkNumBytes[index] == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int start = chunkSegmentIndex[index];
|
||||||
|
int end = start + chunkNumSegments[index];
|
||||||
|
|
||||||
|
// If we are writing, assert we don't write over any in-use segments
|
||||||
|
if (inUse)
|
||||||
|
{
|
||||||
|
int nextSetBit = segments.nextSetBit(start);
|
||||||
|
if (nextSetBit != -1 && nextSetBit < end)
|
||||||
|
throw new IllegalStateException("Attempting to overwrite an in-use segment");
|
||||||
|
}
|
||||||
|
|
||||||
|
segments.set(start, end, inUse);
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void fixFileLength() throws IOException {
|
||||||
|
int fileLength = (int)file.length();
|
||||||
|
int extend = -fileLength & segmentMask; // how many bytes do we need to be divisible by segment size
|
||||||
|
|
||||||
|
// Go to end of file
|
||||||
|
file.seek(fileLength);
|
||||||
|
// Append bytes
|
||||||
|
file.write(new byte[extend], 0, extend);
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized int findContiguousSegments(int hint, int size) {
|
||||||
|
if (size == 0)
|
||||||
|
return 0; // Zero byte data will not claim any chunks anyways
|
||||||
|
|
||||||
|
int segments = bytesToSegments(size); // Number of segments we need
|
||||||
|
|
||||||
|
// Check the hinted location (previous location of chunk) most of the time we can fit where we were.
|
||||||
|
boolean oldFree = true;
|
||||||
|
for (int i = hint; i < this.segments.size() && i < hint + segments; i++) {
|
||||||
|
if (this.segments.get(i)) {
|
||||||
|
oldFree = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We fit!
|
||||||
|
if (oldFree)
|
||||||
|
return hint;
|
||||||
|
|
||||||
|
// Find somewhere to put us
|
||||||
|
int start = 0;
|
||||||
|
int current = 0;
|
||||||
|
|
||||||
|
while (current < this.segments.size()) {
|
||||||
|
boolean segmentInUse = this.segments.get(current); // check if segment is in use
|
||||||
|
current++; // Move up a segment
|
||||||
|
|
||||||
|
// Move up start if the segment was in use
|
||||||
|
if (segmentInUse)
|
||||||
|
start = current;
|
||||||
|
|
||||||
|
// If we have enough segments now, return
|
||||||
|
if (current - start >= segments)
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the end of the segments (will expand to fit them)
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized int bytesToSegments(int bytes) {
|
||||||
|
if (bytes <= 0)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
return ((bytes - 1) >> segmentExponent) + 1; // ((bytes - 1) / segmentSize) + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized int getChunkIndex(int x, int z) {
|
||||||
|
if (rx != (x >> 5) || rz != (z >> 5))
|
||||||
|
throw new IndexOutOfBoundsException();
|
||||||
|
|
||||||
|
x = x & 0x1F; // 5 bits (mod 32)
|
||||||
|
z = z & 0x1F; // 5 bits (mod 32)
|
||||||
|
|
||||||
|
return (x << 5) + z; // x in the upper 5 bits, z in the lower 5 bits
|
||||||
|
}
|
||||||
|
}
|
@ -1,51 +1,17 @@
|
|||||||
package com.gmail.nossr50.util.blockmeta.chunkmeta;
|
package com.gmail.nossr50.util.blockmeta;
|
||||||
|
|
||||||
import org.bukkit.World;
|
import org.bukkit.World;
|
||||||
import org.bukkit.block.Block;
|
import org.bukkit.block.Block;
|
||||||
import org.bukkit.block.BlockState;
|
import org.bukkit.block.BlockState;
|
||||||
import org.bukkit.entity.Entity;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
public class NullChunkManager implements ChunkManager {
|
public class NullChunkManager implements ChunkManager {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void closeAll() {}
|
public void closeAll() {}
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChunkStore readChunkStore(World world, int x, int z) throws IOException {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeChunkStore(World world, int x, int z, ChunkStore data) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void closeChunkStore(World world, int x, int z) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void loadChunklet(int cx, int cy, int cz, World world) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void unloadChunklet(int cx, int cy, int cz, World world) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void loadChunk(int cx, int cz, World world, Entity[] entities) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void unloadChunk(int cx, int cz, World world) {}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void saveChunk(int cx, int cz, World world) {}
|
public void saveChunk(int cx, int cz, World world) {}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isChunkLoaded(int cx, int cz, World world) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void chunkLoaded(int cx, int cz, World world) {}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void chunkUnloaded(int cx, int cz, World world) {}
|
public void chunkUnloaded(int cx, int cz, World world) {}
|
||||||
|
|
||||||
@ -55,15 +21,9 @@ public class NullChunkManager implements ChunkManager {
|
|||||||
@Override
|
@Override
|
||||||
public void unloadWorld(World world) {}
|
public void unloadWorld(World world) {}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void loadWorld(World world) {}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void saveAll() {}
|
public void saveAll() {}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void unloadAll() {}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isTrue(int x, int y, int z, World world) {
|
public boolean isTrue(int x, int y, int z, World world) {
|
||||||
return false;
|
return false;
|
@ -1,85 +0,0 @@
|
|||||||
package com.gmail.nossr50.util.blockmeta;
|
|
||||||
|
|
||||||
import org.bukkit.World;
|
|
||||||
import org.bukkit.block.Block;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A ChunkletManager implementation that does nothing and returns false for all checks.
|
|
||||||
*
|
|
||||||
* Useful for turning off Chunklets without actually doing much work
|
|
||||||
*/
|
|
||||||
public class NullChunkletManager implements ChunkletManager {
|
|
||||||
@Override
|
|
||||||
public void loadChunklet(int cx, int cy, int cz, World world) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void unloadChunklet(int cx, int cy, int cz, World world) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void loadChunk(int cx, int cz, World world) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void unloadChunk(int cx, int cz, World world) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void chunkLoaded(int cx, int cz, World world) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void chunkUnloaded(int cx, int cz, World world) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void saveWorld(World world) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void unloadWorld(World world) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void loadWorld(World world) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void saveAll() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void unloadAll() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isTrue(int x, int y, int z, World world) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isTrue(Block block) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setTrue(int x, int y, int z, World world) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setTrue(Block block) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setFalse(int x, int y, int z, World world) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setFalse(Block block) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void cleanUp() {
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
package com.gmail.nossr50.util.blockmeta;
|
|
||||||
|
|
||||||
public class PrimitiveChunkletStore implements ChunkletStore {
|
|
||||||
private static final long serialVersionUID = -3453078050608607478L;
|
|
||||||
|
|
||||||
/** X, Z, Y */
|
|
||||||
public boolean[][][] store = new boolean[16][16][64];
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isTrue(int x, int y, int z) {
|
|
||||||
return store[x][z][y];
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setTrue(int x, int y, int z) {
|
|
||||||
store[x][z][y] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setFalse(int x, int y, int z) {
|
|
||||||
store[x][z][y] = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEmpty() {
|
|
||||||
for (int x = 0; x < 16; x++) {
|
|
||||||
for (int z = 0; z < 16; z++) {
|
|
||||||
for (int y = 0; y < 64; y++) {
|
|
||||||
if (store[x][z][y]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void copyFrom(ChunkletStore otherStore) {
|
|
||||||
for (int x = 0; x < 16; x++) {
|
|
||||||
for (int z = 0; z < 16; z++) {
|
|
||||||
for (int y = 0; y < 64; y++) {
|
|
||||||
store[x][z][y] = otherStore.isTrue(x, y, z);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,180 +0,0 @@
|
|||||||
package com.gmail.nossr50.util.blockmeta;
|
|
||||||
|
|
||||||
import java.io.Externalizable;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.ObjectInput;
|
|
||||||
import java.io.ObjectOutput;
|
|
||||||
|
|
||||||
public class PrimitiveExChunkletStore implements ChunkletStore, Externalizable {
|
|
||||||
private static final long serialVersionUID = 8603603827094383873L;
|
|
||||||
|
|
||||||
/** X, Z, Y */
|
|
||||||
public boolean[][][] store = new boolean[16][16][64];
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isTrue(int x, int y, int z) {
|
|
||||||
return store[x][z][y];
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setTrue(int x, int y, int z) {
|
|
||||||
store[x][z][y] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setFalse(int x, int y, int z) {
|
|
||||||
store[x][z][y] = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEmpty() {
|
|
||||||
for (int x = 0; x < 16; x++) {
|
|
||||||
for (int z = 0; z < 16; z++) {
|
|
||||||
for (int y = 0; y < 64; y++) {
|
|
||||||
if (store[x][z][y]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void copyFrom(ChunkletStore otherStore) {
|
|
||||||
for (int x = 0; x < 16; x++) {
|
|
||||||
for (int z = 0; z < 16; z++) {
|
|
||||||
for (int y = 0; y < 64; y++) {
|
|
||||||
store[x][z][y] = otherStore.isTrue(x, y, z);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeExternal(ObjectOutput out) throws IOException {
|
|
||||||
byte[] buffer = new byte[2304]; // 2304 is 16*16*9
|
|
||||||
int bufferIndex = 0;
|
|
||||||
|
|
||||||
for (int x = 0; x < 16; x++) {
|
|
||||||
for (int z = 0; z < 16; z++) {
|
|
||||||
for (int y = 0; y < 64; y++) {
|
|
||||||
if (store[x][z][y]) {
|
|
||||||
byte[] temp = constructColumn(x, z);
|
|
||||||
|
|
||||||
for (int i = 0; i < 9; i++) {
|
|
||||||
buffer[bufferIndex] = temp[i];
|
|
||||||
bufferIndex++;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
out.write(buffer, 0, bufferIndex);
|
|
||||||
out.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
// For this we assume that store has been initialized to be all false by now
|
|
||||||
@Override
|
|
||||||
public void readExternal(ObjectInput in) throws IOException {
|
|
||||||
byte[] temp = new byte[9];
|
|
||||||
|
|
||||||
// Could probably reorganize this loop to print nasty things if it does not equal 9 or -1
|
|
||||||
while (in.read(temp, 0, 9) == 9) {
|
|
||||||
int x = addressByteX(temp[0]);
|
|
||||||
int z = addressByteZ(temp[0]);
|
|
||||||
boolean[] yColumn = new boolean[64];
|
|
||||||
|
|
||||||
for (int i = 0; i < 8; i++) {
|
|
||||||
for (int j = 0; j < 8; j++) {
|
|
||||||
yColumn[j + (i * 8)] = (temp[i + 1] & (1 << j)) != 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
store[x][z] = yColumn;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The column: An array of 9 bytes which represent all y values for a given (x,z) Chunklet-coordinate
|
|
||||||
*
|
|
||||||
* The first byte is an address byte, this provides the x and z values.
|
|
||||||
* The next 8 bytes are all y values from 0 to 63, with each byte containing 8 bits of true/false data
|
|
||||||
*
|
|
||||||
* Each of these 8 bytes address to a y value from right to left
|
|
||||||
*
|
|
||||||
* Examples:
|
|
||||||
* 00000001 represents that the lowest y value in this byte is true, all others are off
|
|
||||||
* 10000000 represents that the highest y value in this byte is true, all others are off
|
|
||||||
* 10000001 represents that the lowest and highest y values in this byte are true, all others are off
|
|
||||||
*
|
|
||||||
* Full columns:
|
|
||||||
* See comment on Address byte for information on how to use that byte
|
|
||||||
*
|
|
||||||
* Example:
|
|
||||||
* ADDRESS_BYTE 10000000 00000001 00000000 00000000 00000000 00000000 00000000 00000000
|
|
||||||
* - x, z from ADDRESS_BYTE
|
|
||||||
* - The next byte contains data from 0 to 7
|
|
||||||
* - 1 is set in the highest bit position, this is 7 in y coordinate
|
|
||||||
* - The next byte contains data from 8 to 15
|
|
||||||
* - 1 is set in the lowest bit position, this is 8 in the y coordinate
|
|
||||||
* Therefore, for this column: There are true values at (x, 7, z) and (x, 8, z)
|
|
||||||
*/
|
|
||||||
private byte[] constructColumn(int x, int z) {
|
|
||||||
byte[] column = new byte[9];
|
|
||||||
int index = 1;
|
|
||||||
|
|
||||||
column[0] = makeAddressByte(x, z);
|
|
||||||
|
|
||||||
for (int i = 0; i < 8; i++) {
|
|
||||||
byte yCompressed = 0x0;
|
|
||||||
int subColumnIndex = 8 * i;
|
|
||||||
int subColumnEnd = subColumnIndex + 8;
|
|
||||||
|
|
||||||
for (int y = subColumnIndex; y < subColumnEnd; y++) {
|
|
||||||
if (store[x][z][y]) {
|
|
||||||
yCompressed |= 1 << (y % 8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
column[index] = yCompressed;
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return column;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The address byte: A single byte which contains x and z values which correspond to the x and z Chunklet-coordinates
|
|
||||||
*
|
|
||||||
* In Chunklet-coordinates, the only valid values are 0-15, so we can fit both into a single byte.
|
|
||||||
*
|
|
||||||
* The top 4 bits of the address byte are for the x value
|
|
||||||
* The bottom 4 bits of the address byte are for the z value
|
|
||||||
*
|
|
||||||
* Examples:
|
|
||||||
* An address byte with a value 00000001 would be split like so:
|
|
||||||
* - x = 0000 = 0
|
|
||||||
* - z = 0001 = 1
|
|
||||||
* => Chunklet coordinates (0, 1)
|
|
||||||
*
|
|
||||||
* 01011111
|
|
||||||
* - x = 0101 = 5
|
|
||||||
* - z = 1111 = 15
|
|
||||||
* => Chunklet coordinates (5, 15)
|
|
||||||
*/
|
|
||||||
protected static byte makeAddressByte(int x, int z) {
|
|
||||||
return (byte) ((x << 4) + z);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static int addressByteX(byte address) {
|
|
||||||
return (address & 0xF0) >>> 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static int addressByteZ(byte address) {
|
|
||||||
return address & 0x0F;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
package com.gmail.nossr50.util.blockmeta.chunkmeta;
|
|
||||||
|
|
||||||
import org.bukkit.World;
|
|
||||||
|
|
||||||
public class ChunkStoreFactory {
|
|
||||||
protected static ChunkStore getChunkStore(World world, int x, int z) {
|
|
||||||
// TODO: Add in loading from config what type of store we want.
|
|
||||||
return new PrimitiveChunkStore(world, x, z);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,447 +0,0 @@
|
|||||||
package com.gmail.nossr50.util.blockmeta.chunkmeta;
|
|
||||||
|
|
||||||
import com.gmail.nossr50.mcMMO;
|
|
||||||
import com.gmail.nossr50.util.blockmeta.conversion.BlockStoreConversionZDirectory;
|
|
||||||
import org.bukkit.World;
|
|
||||||
import org.bukkit.block.Block;
|
|
||||||
import org.bukkit.block.BlockState;
|
|
||||||
import org.bukkit.entity.Entity;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
public class HashChunkManager implements ChunkManager {
|
|
||||||
private final HashMap<UUID, HashMap<Long, McMMOSimpleRegionFile>> regionFiles = new HashMap<>();
|
|
||||||
public HashMap<String, ChunkStore> store = new HashMap<>();
|
|
||||||
public ArrayList<BlockStoreConversionZDirectory> converters = new ArrayList<>();
|
|
||||||
private final HashMap<UUID, Boolean> oldData = new HashMap<>();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void closeAll() {
|
|
||||||
for (UUID uid : regionFiles.keySet()) {
|
|
||||||
HashMap<Long, McMMOSimpleRegionFile> worldRegions = regionFiles.get(uid);
|
|
||||||
for (Iterator<McMMOSimpleRegionFile> worldRegionIterator = worldRegions.values().iterator(); worldRegionIterator.hasNext(); ) {
|
|
||||||
McMMOSimpleRegionFile rf = worldRegionIterator.next();
|
|
||||||
if (rf != null) {
|
|
||||||
rf.close();
|
|
||||||
worldRegionIterator.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
regionFiles.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized ChunkStore readChunkStore(World world, int x, int z) throws IOException {
|
|
||||||
McMMOSimpleRegionFile rf = getSimpleRegionFile(world, x, z);
|
|
||||||
InputStream in = rf.getInputStream(x, z);
|
|
||||||
if (in == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
try (ObjectInputStream objectStream = new ObjectInputStream(in)) {
|
|
||||||
Object o = objectStream.readObject();
|
|
||||||
if (o instanceof ChunkStore) {
|
|
||||||
return (ChunkStore) o;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new RuntimeException("Wrong class type read for chunk meta data for " + x + ", " + z);
|
|
||||||
} catch (IOException | ClassNotFoundException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
// Assume the format changed
|
|
||||||
return null;
|
|
||||||
//throw new RuntimeException("Unable to process chunk meta data for " + x + ", " + z, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void writeChunkStore(World world, int x, int z, ChunkStore data) {
|
|
||||||
if (!data.isDirty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
McMMOSimpleRegionFile rf = getSimpleRegionFile(world, x, z);
|
|
||||||
ObjectOutputStream objectStream = new ObjectOutputStream(rf.getOutputStream(x, z));
|
|
||||||
objectStream.writeObject(data);
|
|
||||||
objectStream.flush();
|
|
||||||
objectStream.close();
|
|
||||||
data.setDirty(false);
|
|
||||||
}
|
|
||||||
catch (IOException e) {
|
|
||||||
throw new RuntimeException("Unable to write chunk meta data for " + x + ", " + z, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void closeChunkStore(World world, int x, int z) {
|
|
||||||
McMMOSimpleRegionFile rf = getSimpleRegionFile(world, x, z);
|
|
||||||
if (rf != null) {
|
|
||||||
rf.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized McMMOSimpleRegionFile getSimpleRegionFile(World world, int x, int z) {
|
|
||||||
File directory = new File(world.getWorldFolder(), "mcmmo_regions");
|
|
||||||
|
|
||||||
directory.mkdirs();
|
|
||||||
|
|
||||||
UUID key = world.getUID();
|
|
||||||
|
|
||||||
HashMap<Long, McMMOSimpleRegionFile> worldRegions = regionFiles.computeIfAbsent(key, k -> new HashMap<>());
|
|
||||||
|
|
||||||
int rx = x >> 5;
|
|
||||||
int rz = z >> 5;
|
|
||||||
|
|
||||||
long key2 = (((long) rx) << 32) | ((rz) & 0xFFFFFFFFL);
|
|
||||||
|
|
||||||
McMMOSimpleRegionFile regionFile = worldRegions.get(key2);
|
|
||||||
|
|
||||||
if (regionFile == null) {
|
|
||||||
File file = new File(directory, "mcmmo_" + rx + "_" + rz + "_.mcm");
|
|
||||||
regionFile = new McMMOSimpleRegionFile(file, rx, rz);
|
|
||||||
worldRegions.put(key2, regionFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
return regionFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void loadChunklet(int cx, int cy, int cz, World world) {
|
|
||||||
loadChunk(cx, cz, world, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void unloadChunklet(int cx, int cy, int cz, World world) {
|
|
||||||
unloadChunk(cx, cz, world);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void loadChunk(int cx, int cz, World world, Entity[] entities) {
|
|
||||||
if (world == null || store.containsKey(world.getName() + "," + cx + "," + cz)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
UUID key = world.getUID();
|
|
||||||
|
|
||||||
if (!oldData.containsKey(key)) {
|
|
||||||
oldData.put(key, (new File(world.getWorldFolder(), "mcmmo_data")).exists());
|
|
||||||
}
|
|
||||||
else if (oldData.get(key)) {
|
|
||||||
if (convertChunk(new File(world.getWorldFolder(), "mcmmo_data"), cx, cz, world, true)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ChunkStore chunkStore = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
chunkStore = readChunkStore(world, cx, cz);
|
|
||||||
}
|
|
||||||
catch (Exception e) { e.printStackTrace(); }
|
|
||||||
|
|
||||||
if (chunkStore == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
store.put(world.getName() + "," + cx + "," + cz, chunkStore);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void unloadChunk(int cx, int cz, World world) {
|
|
||||||
saveChunk(cx, cz, world);
|
|
||||||
|
|
||||||
if (store.containsKey(world.getName() + "," + cx + "," + cz)) {
|
|
||||||
store.remove(world.getName() + "," + cx + "," + cz);
|
|
||||||
|
|
||||||
//closeChunkStore(world, cx, cz);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void saveChunk(int cx, int cz, World world) {
|
|
||||||
if (world == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String key = world.getName() + "," + cx + "," + cz;
|
|
||||||
|
|
||||||
if (store.containsKey(key)) {
|
|
||||||
ChunkStore out = store.get(world.getName() + "," + cx + "," + cz);
|
|
||||||
|
|
||||||
if (!out.isDirty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
writeChunkStore(world, cx, cz, out);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized boolean isChunkLoaded(int cx, int cz, World world) {
|
|
||||||
if (world == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return store.containsKey(world.getName() + "," + cx + "," + cz);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void chunkLoaded(int cx, int cz, World world) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void chunkUnloaded(int cx, int cz, World world) {
|
|
||||||
if (world == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
unloadChunk(cx, cz, world);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void saveWorld(World world) {
|
|
||||||
if (world == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
closeAll();
|
|
||||||
String worldName = world.getName();
|
|
||||||
|
|
||||||
List<String> keys = new ArrayList<>(store.keySet());
|
|
||||||
for (String key : keys) {
|
|
||||||
String[] info = key.split(",");
|
|
||||||
if (worldName.equals(info[0])) {
|
|
||||||
try {
|
|
||||||
saveChunk(Integer.parseInt(info[1]), Integer.parseInt(info[2]), world);
|
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
// Ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void unloadWorld(World world) {
|
|
||||||
if (world == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String worldName = world.getName();
|
|
||||||
|
|
||||||
List<String> keys = new ArrayList<>(store.keySet());
|
|
||||||
for (String key : keys) {
|
|
||||||
String[] info = key.split(",");
|
|
||||||
if (worldName.equals(info[0])) {
|
|
||||||
try {
|
|
||||||
unloadChunk(Integer.parseInt(info[1]), Integer.parseInt(info[2]), world);
|
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
// Ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
closeAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void loadWorld(World world) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void saveAll() {
|
|
||||||
closeAll();
|
|
||||||
|
|
||||||
for (World world : mcMMO.p.getServer().getWorlds()) {
|
|
||||||
saveWorld(world);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void unloadAll() {
|
|
||||||
closeAll();
|
|
||||||
|
|
||||||
for (World world : mcMMO.p.getServer().getWorlds()) {
|
|
||||||
unloadWorld(world);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized boolean isTrue(int x, int y, int z, World world) {
|
|
||||||
if (world == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int cx = x >> 4;
|
|
||||||
int cz = z >> 4;
|
|
||||||
|
|
||||||
String key = world.getName() + "," + cx + "," + cz;
|
|
||||||
|
|
||||||
if (!store.containsKey(key)) {
|
|
||||||
loadChunk(cx, cz, world, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!store.containsKey(key)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ChunkStore check = store.get(key);
|
|
||||||
int ix = Math.abs(x) % 16;
|
|
||||||
int iz = Math.abs(z) % 16;
|
|
||||||
|
|
||||||
return check.isTrue(ix, y, iz);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized boolean isTrue(Block block) {
|
|
||||||
if (block == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return isTrue(block.getX(), block.getY(), block.getZ(), block.getWorld());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized boolean isTrue(BlockState blockState) {
|
|
||||||
if (blockState == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return isTrue(blockState.getX(), blockState.getY(), blockState.getZ(), blockState.getWorld());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void setTrue(int x, int y, int z, World world) {
|
|
||||||
if (world == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int cx = x >> 4;
|
|
||||||
int cz = z >> 4;
|
|
||||||
|
|
||||||
int ix = Math.abs(x) % 16;
|
|
||||||
int iz = Math.abs(z) % 16;
|
|
||||||
|
|
||||||
String key = world.getName() + "," + cx + "," + cz;
|
|
||||||
|
|
||||||
if (!store.containsKey(key)) {
|
|
||||||
loadChunk(cx, cz, world, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
ChunkStore cStore = store.get(key);
|
|
||||||
|
|
||||||
if (cStore == null) {
|
|
||||||
cStore = ChunkStoreFactory.getChunkStore(world, cx, cz);
|
|
||||||
store.put(key, cStore);
|
|
||||||
}
|
|
||||||
|
|
||||||
cStore.setTrue(ix, y, iz);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void setTrue(Block block) {
|
|
||||||
if (block == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setTrue(block.getX(), block.getY(), block.getZ(), block.getWorld());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setTrue(BlockState blockState) {
|
|
||||||
if (blockState == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setTrue(blockState.getX(), blockState.getY(), blockState.getZ(), blockState.getWorld());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void setFalse(int x, int y, int z, World world) {
|
|
||||||
if (world == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int cx = x >> 4;
|
|
||||||
int cz = z >> 4;
|
|
||||||
|
|
||||||
int ix = Math.abs(x) % 16;
|
|
||||||
int iz = Math.abs(z) % 16;
|
|
||||||
|
|
||||||
String key = world.getName() + "," + cx + "," + cz;
|
|
||||||
|
|
||||||
if (!store.containsKey(key)) {
|
|
||||||
loadChunk(cx, cz, world, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
ChunkStore cStore = store.get(key);
|
|
||||||
|
|
||||||
if (cStore == null) {
|
|
||||||
return; // No need to make a store for something we will be setting to false
|
|
||||||
}
|
|
||||||
|
|
||||||
cStore.setFalse(ix, y, iz);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void setFalse(Block block) {
|
|
||||||
if (block == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setFalse(block.getX(), block.getY(), block.getZ(), block.getWorld());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void setFalse(BlockState blockState) {
|
|
||||||
if (blockState == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setFalse(blockState.getX(), blockState.getY(), blockState.getZ(), blockState.getWorld());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void cleanUp() {}
|
|
||||||
|
|
||||||
public synchronized void convertChunk(File dataDir, int cx, int cz, World world) {
|
|
||||||
convertChunk(dataDir, cx, cz, world, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized boolean convertChunk(File dataDir, int cx, int cz, World world, boolean actually) {
|
|
||||||
if (!actually || !dataDir.exists()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
File cxDir = new File(dataDir, "" + cx);
|
|
||||||
if (!cxDir.exists()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
File czDir = new File(cxDir, "" + cz);
|
|
||||||
if (!czDir.exists()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean conversionSet = false;
|
|
||||||
|
|
||||||
for (BlockStoreConversionZDirectory converter : this.converters) {
|
|
||||||
if (converter == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (converter.taskID >= 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
converter.start(world, cxDir, czDir);
|
|
||||||
conversionSet = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!conversionSet) {
|
|
||||||
BlockStoreConversionZDirectory converter = new BlockStoreConversionZDirectory();
|
|
||||||
converter.start(world, cxDir, czDir);
|
|
||||||
converters.add(converter);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of SpoutPlugin.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2011-2012, SpoutDev <http://www.spout.org/>
|
|
||||||
* SpoutPlugin is licensed under the GNU Lesser General Public License.
|
|
||||||
*
|
|
||||||
* SpoutPlugin is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* SpoutPlugin is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Lesser General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Lesser General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package com.gmail.nossr50.util.blockmeta.chunkmeta;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
public class McMMOSimpleChunkBuffer extends ByteArrayOutputStream {
|
|
||||||
final McMMOSimpleRegionFile rf;
|
|
||||||
final int index;
|
|
||||||
|
|
||||||
McMMOSimpleChunkBuffer(McMMOSimpleRegionFile rf, int index) {
|
|
||||||
super(1024);
|
|
||||||
this.rf = rf;
|
|
||||||
this.index = index;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() throws IOException {
|
|
||||||
rf.write(index, buf, count);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,306 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of SpoutPlugin.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2011-2012, SpoutDev <http://www.spout.org/>
|
|
||||||
* SpoutPlugin is licensed under the GNU Lesser General Public License.
|
|
||||||
*
|
|
||||||
* SpoutPlugin is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* SpoutPlugin is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Lesser General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Lesser General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package com.gmail.nossr50.util.blockmeta.chunkmeta;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.zip.DeflaterOutputStream;
|
|
||||||
import java.util.zip.InflaterInputStream;
|
|
||||||
|
|
||||||
public class McMMOSimpleRegionFile {
|
|
||||||
private RandomAccessFile file;
|
|
||||||
private final int[] dataStart = new int[1024];
|
|
||||||
private final int[] dataActualLength = new int[1024];
|
|
||||||
private final int[] dataLength = new int[1024];
|
|
||||||
private final ArrayList<Boolean> inuse = new ArrayList<>();
|
|
||||||
private int segmentSize;
|
|
||||||
private int segmentMask;
|
|
||||||
private final int rx;
|
|
||||||
private final int rz;
|
|
||||||
private final int defaultSegmentSize;
|
|
||||||
private final File parent;
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
private long lastAccessTime = System.currentTimeMillis();
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
private static final long TIMEOUT_TIME = 300000; // 5 min
|
|
||||||
|
|
||||||
public McMMOSimpleRegionFile(File f, int rx, int rz) {
|
|
||||||
this(f, rx, rz, 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
public McMMOSimpleRegionFile(File f, int rx, int rz, int defaultSegmentSize) {
|
|
||||||
this.rx = rx;
|
|
||||||
this.rz = rz;
|
|
||||||
this.defaultSegmentSize = defaultSegmentSize;
|
|
||||||
this.parent = f;
|
|
||||||
|
|
||||||
lastAccessTime = System.currentTimeMillis();
|
|
||||||
if (file == null) {
|
|
||||||
try {
|
|
||||||
this.file = new RandomAccessFile(parent, "rw");
|
|
||||||
|
|
||||||
if (file.length() < 4096 * 3) {
|
|
||||||
for (int i = 0; i < 1024 * 3; i++) {
|
|
||||||
file.writeInt(0);
|
|
||||||
}
|
|
||||||
file.seek(4096 * 2);
|
|
||||||
file.writeInt(defaultSegmentSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
file.seek(4096 * 2);
|
|
||||||
|
|
||||||
this.segmentSize = file.readInt();
|
|
||||||
this.segmentMask = (1 << segmentSize) - 1;
|
|
||||||
|
|
||||||
int reservedSegments = this.sizeToSegments(4096 * 3);
|
|
||||||
|
|
||||||
for (int i = 0; i < reservedSegments; i++) {
|
|
||||||
while (inuse.size() <= i) {
|
|
||||||
inuse.add(false);
|
|
||||||
}
|
|
||||||
inuse.set(i, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
file.seek(0);
|
|
||||||
|
|
||||||
for (int i = 0; i < 1024; i++) {
|
|
||||||
dataStart[i] = file.readInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < 1024; i++) {
|
|
||||||
dataActualLength[i] = file.readInt();
|
|
||||||
dataLength[i] = sizeToSegments(dataActualLength[i]);
|
|
||||||
setInUse(i, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
extendFile();
|
|
||||||
}
|
|
||||||
catch (IOException fnfe) {
|
|
||||||
throw new RuntimeException(fnfe);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized final RandomAccessFile getFile() {
|
|
||||||
lastAccessTime = System.currentTimeMillis();
|
|
||||||
if (file == null) {
|
|
||||||
try {
|
|
||||||
this.file = new RandomAccessFile(parent, "rw");
|
|
||||||
|
|
||||||
if (file.length() < 4096 * 3) {
|
|
||||||
for (int i = 0; i < 1024 * 3; i++) {
|
|
||||||
file.writeInt(0);
|
|
||||||
}
|
|
||||||
file.seek(4096 * 2);
|
|
||||||
file.writeInt(defaultSegmentSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
file.seek(4096 * 2);
|
|
||||||
|
|
||||||
this.segmentSize = file.readInt();
|
|
||||||
this.segmentMask = (1 << segmentSize) - 1;
|
|
||||||
|
|
||||||
int reservedSegments = this.sizeToSegments(4096 * 3);
|
|
||||||
|
|
||||||
for (int i = 0; i < reservedSegments; i++) {
|
|
||||||
while (inuse.size() <= i) {
|
|
||||||
inuse.add(false);
|
|
||||||
}
|
|
||||||
inuse.set(i, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
file.seek(0);
|
|
||||||
|
|
||||||
for (int i = 0; i < 1024; i++) {
|
|
||||||
dataStart[i] = file.readInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < 1024; i++) {
|
|
||||||
dataActualLength[i] = file.readInt();
|
|
||||||
dataLength[i] = sizeToSegments(dataActualLength[i]);
|
|
||||||
setInUse(i, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
extendFile();
|
|
||||||
}
|
|
||||||
catch (IOException fnfe) {
|
|
||||||
throw new RuntimeException(fnfe);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return file;
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized boolean testCloseTimeout() {
|
|
||||||
/*
|
|
||||||
if (System.currentTimeMillis() - TIMEOUT_TIME > lastAccessTime) {
|
|
||||||
close();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized DataOutputStream getOutputStream(int x, int z) {
|
|
||||||
int index = getChunkIndex(x, z);
|
|
||||||
return new DataOutputStream(new DeflaterOutputStream(new McMMOSimpleChunkBuffer(this, index)));
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized DataInputStream getInputStream(int x, int z) throws IOException {
|
|
||||||
int index = getChunkIndex(x, z);
|
|
||||||
int actualLength = dataActualLength[index];
|
|
||||||
|
|
||||||
if (actualLength == 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] data = new byte[actualLength];
|
|
||||||
|
|
||||||
getFile().seek(dataStart[index] << segmentSize);
|
|
||||||
getFile().readFully(data);
|
|
||||||
return new DataInputStream(new InflaterInputStream(new ByteArrayInputStream(data)));
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized void write(int index, byte[] buffer, int size) throws IOException {
|
|
||||||
int oldStart = setInUse(index, false);
|
|
||||||
int start = findSpace(oldStart, size);
|
|
||||||
getFile().seek(start << segmentSize);
|
|
||||||
getFile().write(buffer, 0, size);
|
|
||||||
dataStart[index] = start;
|
|
||||||
dataActualLength[index] = size;
|
|
||||||
dataLength[index] = sizeToSegments(size);
|
|
||||||
setInUse(index, true);
|
|
||||||
saveFAT();
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void close() {
|
|
||||||
try {
|
|
||||||
if (file != null) {
|
|
||||||
file.seek(4096 * 2);
|
|
||||||
file.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
file = null;
|
|
||||||
}
|
|
||||||
catch (IOException ioe) {
|
|
||||||
throw new RuntimeException("Unable to close file", ioe);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized int setInUse(int index, boolean used) {
|
|
||||||
if (dataActualLength[index] == 0) {
|
|
||||||
return dataStart[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
int start = dataStart[index];
|
|
||||||
int end = start + dataLength[index];
|
|
||||||
|
|
||||||
for (int i = start; i < end; i++) {
|
|
||||||
while (i > inuse.size() - 1) {
|
|
||||||
inuse.add(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
Boolean old = inuse.set(i, used);
|
|
||||||
if (old != null && old == used) {
|
|
||||||
if (old) {
|
|
||||||
throw new IllegalStateException("Attempting to overwrite an in-use segment");
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new IllegalStateException("Attempting to delete empty segment");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return dataStart[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void extendFile() throws IOException {
|
|
||||||
long extend = (-getFile().length()) & segmentMask;
|
|
||||||
|
|
||||||
getFile().seek(getFile().length());
|
|
||||||
|
|
||||||
while ((extend--) > 0) {
|
|
||||||
getFile().write(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized int findSpace(int oldStart, int size) {
|
|
||||||
int segments = sizeToSegments(size);
|
|
||||||
|
|
||||||
boolean oldFree = true;
|
|
||||||
for (int i = oldStart; i < inuse.size() && i < oldStart + segments; i++) {
|
|
||||||
if (inuse.get(i)) {
|
|
||||||
oldFree = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldFree) {
|
|
||||||
return oldStart;
|
|
||||||
}
|
|
||||||
|
|
||||||
int start = 0;
|
|
||||||
int end = 0;
|
|
||||||
|
|
||||||
while (end < inuse.size()) {
|
|
||||||
if (inuse.get(end)) {
|
|
||||||
end++;
|
|
||||||
start = end;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
end++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (end - start >= segments) {
|
|
||||||
return start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return start;
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized int sizeToSegments(int size) {
|
|
||||||
if (size <= 0) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ((size - 1) >> segmentSize) + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized Integer getChunkIndex(int x, int z) {
|
|
||||||
if (rx != (x >> 5) || rz != (z >> 5)) {
|
|
||||||
throw new RuntimeException(x + ", " + z + " not in region " + rx + ", " + rz);
|
|
||||||
}
|
|
||||||
|
|
||||||
x = x & 0x1F;
|
|
||||||
z = z & 0x1F;
|
|
||||||
|
|
||||||
return (x << 5) + z;
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void saveFAT() throws IOException {
|
|
||||||
getFile().seek(0);
|
|
||||||
for (int i = 0; i < 1024; i++) {
|
|
||||||
getFile().writeInt(dataStart[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < 1024; i++) {
|
|
||||||
getFile().writeInt(dataActualLength[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,147 +0,0 @@
|
|||||||
package com.gmail.nossr50.util.blockmeta.chunkmeta;
|
|
||||||
|
|
||||||
import com.gmail.nossr50.util.blockmeta.ChunkletStore;
|
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
import org.bukkit.World;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.ObjectInputStream;
|
|
||||||
import java.io.ObjectOutputStream;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public class PrimitiveChunkStore implements ChunkStore {
|
|
||||||
private static final long serialVersionUID = -1L;
|
|
||||||
transient private boolean dirty = false;
|
|
||||||
/** X, Z, Y */
|
|
||||||
public boolean[][][] store;
|
|
||||||
private static final int CURRENT_VERSION = 7;
|
|
||||||
private static final int MAGIC_NUMBER = 0xEA5EDEBB;
|
|
||||||
private int cx;
|
|
||||||
private int cz;
|
|
||||||
private UUID worldUid;
|
|
||||||
|
|
||||||
public PrimitiveChunkStore(World world, int cx, int cz) {
|
|
||||||
this.cx = cx;
|
|
||||||
this.cz = cz;
|
|
||||||
this.worldUid = world.getUID();
|
|
||||||
this.store = new boolean[16][16][world.getMaxHeight()];
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isDirty() {
|
|
||||||
return dirty;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setDirty(boolean dirty) {
|
|
||||||
this.dirty = dirty;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getChunkX() {
|
|
||||||
return cx;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getChunkZ() {
|
|
||||||
return cz;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isTrue(int x, int y, int z) {
|
|
||||||
return store[x][z][y];
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setTrue(int x, int y, int z) {
|
|
||||||
if (y >= store[0][0].length || y < 0)
|
|
||||||
return;
|
|
||||||
store[x][z][y] = true;
|
|
||||||
dirty = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setFalse(int x, int y, int z) {
|
|
||||||
if (y >= store[0][0].length || y < 0)
|
|
||||||
return;
|
|
||||||
store[x][z][y] = false;
|
|
||||||
dirty = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEmpty() {
|
|
||||||
for (int x = 0; x < 16; x++) {
|
|
||||||
for (int z = 0; z < 16; z++) {
|
|
||||||
for (int y = 0; y < store[0][0].length; y++) {
|
|
||||||
if (store[x][z][y]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void copyFrom(ChunkletStore otherStore) {
|
|
||||||
for (int x = 0; x < 16; x++) {
|
|
||||||
for (int z = 0; z < 16; z++) {
|
|
||||||
for (int y = 0; y < store[0][0].length; y++) {
|
|
||||||
store[x][z][y] = otherStore.isTrue(x, y, z);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dirty = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeObject(ObjectOutputStream out) throws IOException {
|
|
||||||
out.writeInt(MAGIC_NUMBER);
|
|
||||||
out.writeInt(CURRENT_VERSION);
|
|
||||||
|
|
||||||
out.writeLong(worldUid.getLeastSignificantBits());
|
|
||||||
out.writeLong(worldUid.getMostSignificantBits());
|
|
||||||
out.writeInt(cx);
|
|
||||||
out.writeInt(cz);
|
|
||||||
out.writeObject(store);
|
|
||||||
|
|
||||||
dirty = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
|
|
||||||
int magic = in.readInt();
|
|
||||||
// Can be used to determine the format of the file
|
|
||||||
int fileVersionNumber = in.readInt();
|
|
||||||
|
|
||||||
if (magic != MAGIC_NUMBER) {
|
|
||||||
fileVersionNumber = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
long lsb = in.readLong();
|
|
||||||
long msb = in.readLong();
|
|
||||||
worldUid = new UUID(msb, lsb);
|
|
||||||
cx = in.readInt();
|
|
||||||
cz = in.readInt();
|
|
||||||
|
|
||||||
store = (boolean[][][]) in.readObject();
|
|
||||||
|
|
||||||
if (fileVersionNumber < 5) {
|
|
||||||
fixArray();
|
|
||||||
dirty = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void fixArray() {
|
|
||||||
boolean[][][] temp = this.store;
|
|
||||||
this.store = new boolean[16][16][Bukkit.getWorld(worldUid).getMaxHeight()];
|
|
||||||
for (int x = 0; x < 16; x++) {
|
|
||||||
for (int z = 0; z < 16; z++) {
|
|
||||||
for (int y = 0; y < store[0][0].length; y++) {
|
|
||||||
try {
|
|
||||||
store[x][z][y] = temp[x][y][z];
|
|
||||||
}
|
|
||||||
catch (Exception e) { e.printStackTrace(); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,90 +0,0 @@
|
|||||||
package com.gmail.nossr50.util.blockmeta.conversion;
|
|
||||||
|
|
||||||
import com.gmail.nossr50.config.HiddenConfig;
|
|
||||||
import com.gmail.nossr50.mcMMO;
|
|
||||||
import org.bukkit.scheduler.BukkitScheduler;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
public class BlockStoreConversionMain implements Runnable {
|
|
||||||
private int taskID, i;
|
|
||||||
private org.bukkit.World world;
|
|
||||||
BukkitScheduler scheduler;
|
|
||||||
File dataDir;
|
|
||||||
File[] xDirs;
|
|
||||||
BlockStoreConversionXDirectory[] converters;
|
|
||||||
|
|
||||||
public BlockStoreConversionMain(org.bukkit.World world) {
|
|
||||||
this.taskID = -1;
|
|
||||||
this.world = world;
|
|
||||||
this.scheduler = mcMMO.p.getServer().getScheduler();
|
|
||||||
this.dataDir = new File(this.world.getWorldFolder(), "mcmmo_data");
|
|
||||||
this.converters = new BlockStoreConversionXDirectory[HiddenConfig.getInstance().getConversionRate()];
|
|
||||||
}
|
|
||||||
|
|
||||||
public void start() {
|
|
||||||
if (this.taskID >= 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.taskID = this.scheduler.runTaskLater(mcMMO.p, this, 1).getTaskId();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
if (!this.dataDir.exists()) {
|
|
||||||
softStop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.dataDir.isDirectory()) {
|
|
||||||
this.dataDir.delete();
|
|
||||||
softStop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.dataDir.listFiles().length <= 0) {
|
|
||||||
this.dataDir.delete();
|
|
||||||
softStop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.xDirs = this.dataDir.listFiles();
|
|
||||||
|
|
||||||
for (this.i = 0; (this.i < HiddenConfig.getInstance().getConversionRate()) && (this.i < this.xDirs.length); this.i++) {
|
|
||||||
if (this.converters[this.i] == null) {
|
|
||||||
this.converters[this.i] = new BlockStoreConversionXDirectory();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.converters[this.i].start(this.world, this.xDirs[this.i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
softStop();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void stop() {
|
|
||||||
if (this.taskID < 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.scheduler.cancelTask(this.taskID);
|
|
||||||
this.taskID = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void softStop() {
|
|
||||||
stop();
|
|
||||||
|
|
||||||
if (this.dataDir.exists() && this.dataDir.isDirectory()) {
|
|
||||||
start();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mcMMO.p.getLogger().info("Finished converting the storage for " + world.getName() + ".");
|
|
||||||
|
|
||||||
this.dataDir = null;
|
|
||||||
this.xDirs = null;
|
|
||||||
this.world = null;
|
|
||||||
this.scheduler = null;
|
|
||||||
this.converters = null;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,80 +0,0 @@
|
|||||||
package com.gmail.nossr50.util.blockmeta.conversion;
|
|
||||||
|
|
||||||
import com.gmail.nossr50.config.HiddenConfig;
|
|
||||||
import com.gmail.nossr50.mcMMO;
|
|
||||||
import org.bukkit.scheduler.BukkitScheduler;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
public class BlockStoreConversionXDirectory implements Runnable {
|
|
||||||
private int taskID, i;
|
|
||||||
private org.bukkit.World world;
|
|
||||||
BukkitScheduler scheduler;
|
|
||||||
File dataDir;
|
|
||||||
File[] zDirs;
|
|
||||||
BlockStoreConversionZDirectory[] converters;
|
|
||||||
|
|
||||||
public BlockStoreConversionXDirectory() {
|
|
||||||
this.taskID = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void start(org.bukkit.World world, File dataDir) {
|
|
||||||
this.world = world;
|
|
||||||
this.scheduler = mcMMO.p.getServer().getScheduler();
|
|
||||||
this.converters = new BlockStoreConversionZDirectory[HiddenConfig.getInstance().getConversionRate()];
|
|
||||||
this.dataDir = dataDir;
|
|
||||||
|
|
||||||
if (this.taskID >= 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.taskID = this.scheduler.runTaskLater(mcMMO.p, this, 1).getTaskId();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
if (!this.dataDir.exists()) {
|
|
||||||
stop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.dataDir.isDirectory()) {
|
|
||||||
this.dataDir.delete();
|
|
||||||
stop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.dataDir.listFiles().length <= 0) {
|
|
||||||
this.dataDir.delete();
|
|
||||||
stop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.zDirs = this.dataDir.listFiles();
|
|
||||||
|
|
||||||
for (this.i = 0; (this.i < HiddenConfig.getInstance().getConversionRate()) && (this.i < this.zDirs.length); this.i++) {
|
|
||||||
if (this.converters[this.i] == null) {
|
|
||||||
this.converters[this.i] = new BlockStoreConversionZDirectory();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.converters[this.i].start(this.world, this.dataDir, this.zDirs[this.i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void stop() {
|
|
||||||
if (this.taskID < 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.scheduler.cancelTask(this.taskID);
|
|
||||||
this.taskID = -1;
|
|
||||||
|
|
||||||
this.dataDir = null;
|
|
||||||
this.zDirs = null;
|
|
||||||
this.world = null;
|
|
||||||
this.scheduler = null;
|
|
||||||
this.converters = null;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,191 +0,0 @@
|
|||||||
package com.gmail.nossr50.util.blockmeta.conversion;
|
|
||||||
|
|
||||||
import com.gmail.nossr50.mcMMO;
|
|
||||||
import com.gmail.nossr50.util.blockmeta.ChunkletStore;
|
|
||||||
import com.gmail.nossr50.util.blockmeta.HashChunkletManager;
|
|
||||||
import com.gmail.nossr50.util.blockmeta.PrimitiveChunkletStore;
|
|
||||||
import com.gmail.nossr50.util.blockmeta.PrimitiveExChunkletStore;
|
|
||||||
import com.gmail.nossr50.util.blockmeta.chunkmeta.HashChunkManager;
|
|
||||||
import com.gmail.nossr50.util.blockmeta.chunkmeta.PrimitiveChunkStore;
|
|
||||||
import org.bukkit.scheduler.BukkitScheduler;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
public class BlockStoreConversionZDirectory implements Runnable {
|
|
||||||
public int taskID, cx, cz, x, y, z, y2, xPos, zPos, cxPos, czPos;
|
|
||||||
private String cxs, czs, chunkletName, chunkName;
|
|
||||||
private org.bukkit.World world;
|
|
||||||
private BukkitScheduler scheduler;
|
|
||||||
private File xDir, dataDir;
|
|
||||||
private HashChunkletManager manager;
|
|
||||||
private HashChunkManager newManager;
|
|
||||||
private ChunkletStore tempChunklet;
|
|
||||||
private PrimitiveChunkletStore primitiveChunklet = null;
|
|
||||||
private PrimitiveExChunkletStore primitiveExChunklet = null;
|
|
||||||
private PrimitiveChunkStore currentChunk;
|
|
||||||
private boolean[] oldArray, newArray;
|
|
||||||
|
|
||||||
public BlockStoreConversionZDirectory() {
|
|
||||||
this.taskID = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void start(org.bukkit.World world, File xDir, File dataDir) {
|
|
||||||
this.world = world;
|
|
||||||
this.scheduler = mcMMO.p.getServer().getScheduler();
|
|
||||||
this.manager = new HashChunkletManager();
|
|
||||||
this.newManager = (HashChunkManager) mcMMO.getPlaceStore();
|
|
||||||
this.dataDir = dataDir;
|
|
||||||
this.xDir = xDir;
|
|
||||||
|
|
||||||
if (this.taskID >= 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.taskID = this.scheduler.runTaskLater(mcMMO.p, this, 1).getTaskId();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
if (!this.dataDir.exists()) {
|
|
||||||
stop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.dataDir.isDirectory()) {
|
|
||||||
this.dataDir.delete();
|
|
||||||
stop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.dataDir.listFiles().length <= 0) {
|
|
||||||
this.dataDir.delete();
|
|
||||||
stop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.cxs = this.xDir.getName();
|
|
||||||
this.czs = this.dataDir.getName();
|
|
||||||
this.cx = 0;
|
|
||||||
this.cz = 0;
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.cx = Integer.parseInt(this.cxs);
|
|
||||||
this.cz = Integer.parseInt(this.czs);
|
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
this.dataDir.delete();
|
|
||||||
stop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.manager.loadChunk(this.cx, this.cz, this.world);
|
|
||||||
|
|
||||||
for (this.y = 0; this.y < (this.world.getMaxHeight() / 64); this.y++) {
|
|
||||||
this.chunkletName = this.world.getName() + "," + this.cx + "," + this.cz + "," + this.y;
|
|
||||||
this.tempChunklet = this.manager.store.get(this.chunkletName);
|
|
||||||
|
|
||||||
if (this.tempChunklet instanceof PrimitiveChunkletStore) {
|
|
||||||
this.primitiveChunklet = (PrimitiveChunkletStore) this.tempChunklet;
|
|
||||||
}
|
|
||||||
else if (this.tempChunklet instanceof PrimitiveExChunkletStore) {
|
|
||||||
this.primitiveExChunklet = (PrimitiveExChunkletStore) this.tempChunklet;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.tempChunklet == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.chunkName = this.world.getName() + "," + this.cx + "," + this.cz;
|
|
||||||
this.currentChunk = (PrimitiveChunkStore) this.newManager.store.get(this.chunkName);
|
|
||||||
|
|
||||||
if (this.currentChunk != null) {
|
|
||||||
this.xPos = this.cx * 16;
|
|
||||||
this.zPos = this.cz * 16;
|
|
||||||
|
|
||||||
for (this.x = 0; this.x < 16; this.x++) {
|
|
||||||
for (this.z = 0; this.z < 16; this.z++) {
|
|
||||||
this.cxPos = this.xPos + this.x;
|
|
||||||
this.czPos = this.zPos + this.z;
|
|
||||||
|
|
||||||
for (this.y2 = (64 * this.y); this.y2 < (64 * this.y + 64); this.y2++) {
|
|
||||||
try {
|
|
||||||
if (!this.manager.isTrue(this.cxPos, this.y2, this.czPos, this.world)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.newManager.setTrue(this.cxPos, this.y2, this.czPos, this.world);
|
|
||||||
}
|
|
||||||
catch (Exception e) { e.printStackTrace(); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.newManager.setTrue(this.cx * 16, 0, this.cz * 16, this.world);
|
|
||||||
this.newManager.setFalse(this.cx * 16, 0, this.cz * 16, this.world);
|
|
||||||
this.currentChunk = (PrimitiveChunkStore) this.newManager.store.get(this.chunkName);
|
|
||||||
|
|
||||||
for (this.x = 0; this.x < 16; this.x++) {
|
|
||||||
for (this.z = 0; this.z < 16; this.z++) {
|
|
||||||
if (this.primitiveChunklet != null) {
|
|
||||||
this.oldArray = this.primitiveChunklet.store[x][z];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.primitiveExChunklet != null) {
|
|
||||||
this.oldArray = this.primitiveExChunklet.store[x][z];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.newArray = this.currentChunk.store[x][z];
|
|
||||||
|
|
||||||
if (this.oldArray.length < 64) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if (this.newArray.length < ((this.y * 64) + 64)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
System.arraycopy(this.oldArray, 0, this.newArray, (this.y * 64), 64);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.manager.unloadChunk(this.cx, this.cz, this.world);
|
|
||||||
this.newManager.unloadChunk(this.cx, this.cz, this.world);
|
|
||||||
|
|
||||||
for (File yFile : dataDir.listFiles()) {
|
|
||||||
if (!yFile.exists()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
yFile.delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void stop() {
|
|
||||||
if (this.taskID < 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.scheduler.cancelTask(taskID);
|
|
||||||
this.taskID = -1;
|
|
||||||
|
|
||||||
this.cxs = null;
|
|
||||||
this.czs = null;
|
|
||||||
this.chunkletName = null;
|
|
||||||
this.chunkName = null;
|
|
||||||
this.manager = null;
|
|
||||||
this.xDir = null;
|
|
||||||
this.dataDir = null;
|
|
||||||
this.tempChunklet = null;
|
|
||||||
this.primitiveChunklet = null;
|
|
||||||
this.primitiveExChunklet = null;
|
|
||||||
this.currentChunk = null;
|
|
||||||
}
|
|
||||||
}
|
|
308
src/test/java/ChunkStoreTest.java
Normal file
308
src/test/java/ChunkStoreTest.java
Normal file
@ -0,0 +1,308 @@
|
|||||||
|
import com.gmail.nossr50.util.blockmeta.*;
|
||||||
|
import com.google.common.io.Files;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.junit.*;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
import org.powermock.api.mockito.PowerMockito;
|
||||||
|
import org.powermock.core.classloader.annotations.PrepareForTest;
|
||||||
|
import org.powermock.modules.junit4.PowerMockRunner;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Could be alot better. But some tests are better than none! Tests the major things, still kinda unit-testy. Verifies that the serialization isn't completely broken.
|
||||||
|
*/
|
||||||
|
@RunWith(PowerMockRunner.class)
|
||||||
|
@PrepareForTest(Bukkit.class)
|
||||||
|
public class ChunkStoreTest {
|
||||||
|
private static File tempDir;
|
||||||
|
@BeforeClass
|
||||||
|
public static void setUpClass() {
|
||||||
|
tempDir = Files.createTempDir();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterClass
|
||||||
|
public static void tearDownClass() {
|
||||||
|
recursiveDelete(tempDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
private World mockWorld;
|
||||||
|
@Before
|
||||||
|
public void setUpMock(){
|
||||||
|
UUID worldUUID = UUID.randomUUID();
|
||||||
|
mockWorld = mock(World.class);
|
||||||
|
Mockito.when(mockWorld.getUID()).thenReturn(worldUUID);
|
||||||
|
Mockito.when(mockWorld.getMaxHeight()).thenReturn(256);
|
||||||
|
Mockito.when(mockWorld.getWorldFolder()).thenReturn(tempDir);
|
||||||
|
PowerMockito.mockStatic(Bukkit.class);
|
||||||
|
Mockito.when(Bukkit.getWorld(worldUUID)).thenReturn(mockWorld);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSetValue() {
|
||||||
|
BitSetChunkStore original = new BitSetChunkStore(mockWorld, 0, 0);
|
||||||
|
original.setTrue(0, 0, 0);
|
||||||
|
Assert.assertTrue(original.isTrue(0, 0, 0));
|
||||||
|
original.setFalse(0, 0, 0);
|
||||||
|
Assert.assertFalse(original.isTrue(0, 0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsEmpty() {
|
||||||
|
BitSetChunkStore original = new BitSetChunkStore(mockWorld, 0, 0);
|
||||||
|
Assert.assertTrue(original.isEmpty());
|
||||||
|
original.setTrue(0, 0, 0);
|
||||||
|
original.setFalse(0, 0, 0);
|
||||||
|
Assert.assertTrue(original.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRoundTrip() throws IOException {
|
||||||
|
BitSetChunkStore original = new BitSetChunkStore(mockWorld, 1, 2);
|
||||||
|
original.setTrue(14, 89, 12);
|
||||||
|
original.setTrue(14, 90, 12);
|
||||||
|
original.setTrue(13, 89, 12);
|
||||||
|
byte[] serializedBytes = serializeChunkstore(original);
|
||||||
|
ChunkStore deserialized = BitSetChunkStore.Serialization.readChunkStore(new DataInputStream(new ByteArrayInputStream(serializedBytes)));
|
||||||
|
assertEqual(original, deserialized);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testChunkCoords() throws IOException {
|
||||||
|
for (int x = -96; x < 0; x++) {
|
||||||
|
int cx = x >> 4;
|
||||||
|
int ix = Math.abs(x) % 16;
|
||||||
|
System.out.print(cx + ":" + ix + " ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpgrade() throws IOException {
|
||||||
|
LegacyChunkStore original = new LegacyChunkStore(mockWorld, 12, 32);
|
||||||
|
original.setTrue(14, 89, 12);
|
||||||
|
original.setTrue(14, 90, 12);
|
||||||
|
original.setTrue(13, 89, 12);
|
||||||
|
byte[] serializedBytes = serializeChunkstore(original);
|
||||||
|
ChunkStore deserialized = BitSetChunkStore.Serialization.readChunkStore(new DataInputStream(new ByteArrayInputStream(serializedBytes)));
|
||||||
|
assertEqual(original, deserialized);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSimpleRegionRoundtrip() throws IOException {
|
||||||
|
LegacyChunkStore original = new LegacyChunkStore(mockWorld, 12, 12);
|
||||||
|
original.setTrue(14, 89, 12);
|
||||||
|
original.setTrue(14, 90, 12);
|
||||||
|
original.setTrue(13, 89, 12);
|
||||||
|
File file = new File(tempDir, "SimpleRegionRoundTrip.region");
|
||||||
|
McMMOSimpleRegionFile region = new McMMOSimpleRegionFile(file, 0, 0);
|
||||||
|
try (DataOutputStream outputStream = region.getOutputStream(12, 12)){
|
||||||
|
outputStream.write(serializeChunkstore(original));
|
||||||
|
}
|
||||||
|
region.close();
|
||||||
|
region = new McMMOSimpleRegionFile(file, 0, 0);
|
||||||
|
try (DataInputStream is = region.getInputStream(original.getChunkX(), original.getChunkZ()))
|
||||||
|
{
|
||||||
|
Assert.assertNotNull(is);
|
||||||
|
ChunkStore deserialized = BitSetChunkStore.Serialization.readChunkStore(is);
|
||||||
|
assertEqual(original, deserialized);
|
||||||
|
}
|
||||||
|
region.close();
|
||||||
|
file.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSimpleRegionRejectsOutOfBounds() {
|
||||||
|
File file = new File(tempDir, "SimpleRegionRoundTrip.region");
|
||||||
|
McMMOSimpleRegionFile region = new McMMOSimpleRegionFile(file, 0, 0);
|
||||||
|
assertThrows(() -> region.getOutputStream(-1, 0), IndexOutOfBoundsException.class);
|
||||||
|
assertThrows(() -> region.getOutputStream(0, -1), IndexOutOfBoundsException.class);
|
||||||
|
assertThrows(() -> region.getOutputStream(32, 0), IndexOutOfBoundsException.class);
|
||||||
|
assertThrows(() -> region.getOutputStream(0, 32), IndexOutOfBoundsException.class);
|
||||||
|
region.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testChunkStoreRejectsOutOfBounds() {
|
||||||
|
ChunkStore chunkStore = new BitSetChunkStore(mockWorld, 0, 0);
|
||||||
|
assertThrows(() -> chunkStore.setTrue(-1, 0, 0), IndexOutOfBoundsException.class);
|
||||||
|
assertThrows(() -> chunkStore.setTrue(0, -1, 0), IndexOutOfBoundsException.class);
|
||||||
|
assertThrows(() -> chunkStore.setTrue(0, 0, -1), IndexOutOfBoundsException.class);
|
||||||
|
assertThrows(() -> chunkStore.setTrue(16, 0, 0), IndexOutOfBoundsException.class);
|
||||||
|
assertThrows(() -> chunkStore.setTrue(0, mockWorld.getMaxHeight(), 0), IndexOutOfBoundsException.class);
|
||||||
|
assertThrows(() -> chunkStore.setTrue(0, 0, 16), IndexOutOfBoundsException.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRegressionChunkMirrorBug() {
|
||||||
|
ChunkManager chunkManager = new HashChunkManager();
|
||||||
|
chunkManager.setTrue(15,0,15, mockWorld);
|
||||||
|
chunkManager.setFalse(-15, 0, -15, mockWorld);
|
||||||
|
Assert.assertTrue(chunkManager.isTrue(15, 0, 15, mockWorld));
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface Delegate {
|
||||||
|
void run();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertThrows(Delegate delegate, Class<?> clazz) {
|
||||||
|
try {
|
||||||
|
delegate.run();
|
||||||
|
Assert.fail(); // We didn't throw
|
||||||
|
}
|
||||||
|
catch (Throwable t) {
|
||||||
|
Assert.assertTrue(t.getClass().equals(clazz));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertEqual(ChunkStore expected, ChunkStore actual)
|
||||||
|
{
|
||||||
|
Assert.assertEquals(expected.getChunkX(), actual.getChunkX());
|
||||||
|
Assert.assertEquals(expected.getChunkZ(), actual.getChunkZ());
|
||||||
|
Assert.assertEquals(expected.getWorldId(), actual.getWorldId());
|
||||||
|
for (int y = 0; y < 256; y++)
|
||||||
|
for (int x = 0; x < 16; x++)
|
||||||
|
for (int z = 0; z < 16; z++)
|
||||||
|
Assert.assertTrue(expected.isTrue(x, y, z) == actual.isTrue(x, y, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void recursiveDelete(File directoryToBeDeleted) {
|
||||||
|
if (directoryToBeDeleted.isDirectory()) {
|
||||||
|
for (File file : directoryToBeDeleted.listFiles()) {
|
||||||
|
recursiveDelete(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
directoryToBeDeleted.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] serializeChunkstore(ChunkStore chunkStore) throws IOException {
|
||||||
|
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||||
|
if (chunkStore instanceof BitSetChunkStore)
|
||||||
|
BitSetChunkStore.Serialization.writeChunkStore(new DataOutputStream(byteArrayOutputStream), chunkStore);
|
||||||
|
else
|
||||||
|
new UnitTestObjectOutputStream(byteArrayOutputStream).writeObject(chunkStore); // Serializes the class as if it were the old PrimitiveChunkStore
|
||||||
|
return byteArrayOutputStream.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static class LegacyChunkStore implements ChunkStore, Serializable {
|
||||||
|
private static final long serialVersionUID = -1L;
|
||||||
|
transient private boolean dirty = false;
|
||||||
|
public boolean[][][] store;
|
||||||
|
private static final int CURRENT_VERSION = 7;
|
||||||
|
private static final int MAGIC_NUMBER = 0xEA5EDEBB;
|
||||||
|
private int cx;
|
||||||
|
private int cz;
|
||||||
|
private UUID worldUid;
|
||||||
|
|
||||||
|
public LegacyChunkStore(World world, int cx, int cz) {
|
||||||
|
this.cx = cx;
|
||||||
|
this.cz = cz;
|
||||||
|
this.worldUid = world.getUID();
|
||||||
|
this.store = new boolean[16][16][world.getMaxHeight()];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDirty() {
|
||||||
|
return dirty;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDirty(boolean dirty) {
|
||||||
|
this.dirty = dirty;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getChunkX() {
|
||||||
|
return cx;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getChunkZ() {
|
||||||
|
return cz;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UUID getWorldId() {
|
||||||
|
return worldUid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isTrue(int x, int y, int z) {
|
||||||
|
return store[x][z][y];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTrue(int x, int y, int z) {
|
||||||
|
if (y >= store[0][0].length || y < 0)
|
||||||
|
return;
|
||||||
|
store[x][z][y] = true;
|
||||||
|
dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFalse(int x, int y, int z) {
|
||||||
|
if (y >= store[0][0].length || y < 0)
|
||||||
|
return;
|
||||||
|
store[x][z][y] = false;
|
||||||
|
dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void set(int x, int y, int z, boolean value) {
|
||||||
|
if (y >= store[0][0].length || y < 0)
|
||||||
|
return;
|
||||||
|
store[x][z][y] = value;
|
||||||
|
dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
for (int x = 0; x < 16; x++) {
|
||||||
|
for (int z = 0; z < 16; z++) {
|
||||||
|
for (int y = 0; y < store[0][0].length; y++) {
|
||||||
|
if (store[x][z][y]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeObject(ObjectOutputStream out) throws IOException {
|
||||||
|
out.writeInt(MAGIC_NUMBER);
|
||||||
|
out.writeInt(CURRENT_VERSION);
|
||||||
|
|
||||||
|
out.writeLong(worldUid.getLeastSignificantBits());
|
||||||
|
out.writeLong(worldUid.getMostSignificantBits());
|
||||||
|
out.writeInt(cx);
|
||||||
|
out.writeInt(cz);
|
||||||
|
out.writeObject(store);
|
||||||
|
|
||||||
|
dirty = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class UnitTestObjectOutputStream extends ObjectOutputStream {
|
||||||
|
public UnitTestObjectOutputStream(OutputStream outputStream) throws IOException {
|
||||||
|
super(outputStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeUTF(String str) throws IOException {
|
||||||
|
// Pretend to be the old class
|
||||||
|
if (str.equals(LegacyChunkStore.class.getName()))
|
||||||
|
str = "com.gmail.nossr50.util.blockmeta.chunkmeta.PrimitiveChunkStore";
|
||||||
|
super.writeUTF(str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user