new chunkstore

Co-authored-by: t00thpick1 <t00thpick1dirko@gmail.com>
This commit is contained in:
nossr50
2020-12-30 15:20:24 -08:00
parent 7802d54ebd
commit 01f31e76f5
28 changed files with 1211 additions and 2406 deletions

View File

@ -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.
*

View File

@ -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.SimpleSalvageableManager;
import com.gmail.nossr50.util.*;
import com.gmail.nossr50.util.blockmeta.chunkmeta.ChunkManager;
import com.gmail.nossr50.util.blockmeta.chunkmeta.ChunkManagerFactory;
import com.gmail.nossr50.util.blockmeta.ChunkManager;
import com.gmail.nossr50.util.blockmeta.ChunkManagerFactory;
import com.gmail.nossr50.util.commands.CommandRegistrationManager;
import com.gmail.nossr50.util.compat.CompatibilityManager;
import com.gmail.nossr50.util.experience.FormulaManager;
@ -336,8 +336,8 @@ public class mcMMO extends JavaPlugin {
formulaManager.saveFormula();
holidayManager.saveAnniversaryFiles();
placeStore.saveAll(); // Save our metadata
placeStore.cleanUp(); // Cleanup empty metadata stores
placeStore.closeAll();
}
catch (Exception e) { e.printStackTrace(); }

View File

@ -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;
}
}
}
}
}

View File

@ -1,59 +1,12 @@
package com.gmail.nossr50.util.blockmeta.chunkmeta;
package com.gmail.nossr50.util.blockmeta;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import org.bukkit.entity.Entity;
import java.io.IOException;
public interface ChunkManager {
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
*
@ -63,17 +16,6 @@ public interface ChunkManager {
*/
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
*
@ -97,23 +39,11 @@ public interface ChunkManager {
*/
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
*

View File

@ -1,4 +1,4 @@
package com.gmail.nossr50.util.blockmeta.chunkmeta;
package com.gmail.nossr50.util.blockmeta;
import com.gmail.nossr50.config.HiddenConfig;

View File

@ -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
*/
public interface ChunkStore extends Serializable {
public interface ChunkStore {
/**
* Checks the chunk's save state
*
@ -36,6 +36,8 @@ public interface ChunkStore extends Serializable {
*/
int getChunkZ();
UUID getWorldId();
/**
* Checks the value at the given coordinates
*
@ -64,15 +66,18 @@ public interface ChunkStore extends Serializable {
*/
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
*/
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);
}

View File

@ -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();
}

View File

@ -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();
}
}

View File

@ -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);
}

View File

@ -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();
}
}

View File

@ -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() {}
}

View File

@ -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;
}
}

View File

@ -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
}
}

View File

@ -1,51 +1,17 @@
package com.gmail.nossr50.util.blockmeta.chunkmeta;
package com.gmail.nossr50.util.blockmeta;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import org.bukkit.entity.Entity;
import java.io.IOException;
public class NullChunkManager implements ChunkManager {
@Override
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
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
public void chunkUnloaded(int cx, int cz, World world) {}
@ -55,15 +21,9 @@ public class NullChunkManager implements ChunkManager {
@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;

View File

@ -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() {
}
}

View File

@ -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);
}
}
}
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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]);
}
}
}

View File

@ -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(); }
}
}
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}