Updating modified block storage to a region-style format.

This commit is contained in:
Glitchfinder
2012-11-04 13:36:57 -08:00
parent d532164cd6
commit eb5ac80714
19 changed files with 1669 additions and 11 deletions

View File

@ -0,0 +1,168 @@
package com.gmail.nossr50.util.blockmeta;
import java.io.IOException;
import org.bukkit.World;
import org.bukkit.block.Block;
public interface ChunkManager {
public void closeAll();
public ChunkStore readChunkStore(World world, int x, int z) throws IOException;
public void writeChunkStore(World world, int x, int z, ChunkStore data);
public 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
*/
public 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
*/
public 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
*/
public 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
*/
public void unloadChunk(int cx, int cz, World world);
/**
* Saves a given Chunk's Chunklet data
*
* @param cx Chunk X coordinate that is to be saved
* @param cz Chunk Z coordinate that is to be saved
* @param world World that the Chunk is in
*/
public void saveChunk(int cx, int cz, World world);
public 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
*/
public 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
*/
public void chunkUnloaded(int cx, int cz, World world);
/**
* Save all ChunkletStores related to the given world
*
* @param world World to save
*/
public void saveWorld(World world);
/**
* Unload all ChunkletStores from memory related to the given world after saving them
*
* @param world World to unload
*/
public void unloadWorld(World world);
/**
* Load all ChunkletStores from all loaded chunks from this world into memory
*
* @param world World to load
*/
public void loadWorld(World world);
/**
* Save all ChunkletStores
*/
public void saveAll();
/**
* Unload all ChunkletStores after saving them
*/
public 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
*/
public 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
*/
public 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
*/
public 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
*/
public 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
*/
public 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
*/
public void setFalse(Block block);
/**
* Delete any ChunkletStores that are empty
*/
public void cleanUp();
}

View File

@ -0,0 +1,15 @@
package com.gmail.nossr50.util.blockmeta;
import com.gmail.nossr50.config.HiddenConfig;
public class ChunkManagerFactory {
public static ChunkManager getChunkManager() {
HiddenConfig hConfig = HiddenConfig.getInstance();
if(hConfig.getChunkletsEnabled()) {
return new HashChunkManager();
} else {
return new NullChunkManager();
}
}
}

View File

@ -0,0 +1,72 @@
package com.gmail.nossr50.util.blockmeta;
import java.io.Serializable;
/**
* A ChunkStore should be responsible for a 16x16xWorldHeight area of data
*/
public interface ChunkStore extends Serializable {
/**
* Checks the chunk's save state
*
* @return true if the has been modified since it was last saved
*/
public boolean isDirty();
/**
* Checks the chunk's save state
*
* @param dirty the save state of the current chunk
*/
public void setDirty(boolean dirty);
/**
* Checks the chunk's x coordinate
*
* @return the chunk's x coordinate.
*/
public int getChunkX();
/**
* Checks the chunk's z coordinate
*
* @return the chunk's z coordinate.
*/
public int getChunkZ();
/**
* 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
*/
public 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
*/
public 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
*/
public void setFalse(int x, int y, int z);
/**
* @return true if all values in the chunklet are false, false if otherwise
*/
public 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
*/
public void copyFrom(ChunkletStore otherStore);
}

View File

@ -0,0 +1,10 @@
package com.gmail.nossr50.util.blockmeta;
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

@ -0,0 +1,374 @@
package com.gmail.nossr50.util.blockmeta;
import java.io.File;
import java.io.InputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.lang.Integer;
import java.util.HashMap;
import java.util.Iterator;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.block.Block;
import com.gmail.nossr50.mcMMO;
import com.gmail.nossr50.runnables.ChunkletUnloader;
import org.getspout.spoutapi.chunkstore.SimpleRegionFile;
public class HashChunkManager implements ChunkManager {
private HashMap<UUID, HashMap<Long, SimpleRegionFile>> regionFiles = new HashMap<UUID, HashMap<Long, SimpleRegionFile>>();
public HashMap<String, ChunkStore> store = new HashMap<String, ChunkStore>();
@Override
public void closeAll() {
for (UUID uid : regionFiles.keySet()) {
HashMap<Long, SimpleRegionFile> worldRegions = regionFiles.get(uid);
Iterator<SimpleRegionFile> itr = worldRegions.values().iterator();
while (itr.hasNext()) {
SimpleRegionFile rf = itr.next();
if (rf != null) {
rf.close();
itr.remove();
}
}
}
regionFiles.clear();
}
@Override
public ChunkStore readChunkStore(World world, int x, int z) throws IOException {
SimpleRegionFile rf = getSimpleRegionFile(world, x, z);
InputStream in = rf.getInputStream(x, z);
if (in == null) {
return null;
}
ObjectInputStream objectStream = new ObjectInputStream(in);
try {
Object o = objectStream.readObject();
if (o instanceof ChunkStore) {
return (ChunkStore) o;
} else {
throw new RuntimeException("Wrong class type read for chunk meta data for " + x + ", " + z);
}
} catch (IOException e) {
// Assume the format changed
return null;
//throw new RuntimeException("Unable to process chunk meta data for " + x + ", " + z, e);
} catch (ClassNotFoundException e) {
// Assume the format changed
//System.out.println("[SpoutPlugin] is Unable to find serialized class for " + x + ", " + z + ", " + e.getMessage());
return null;
//throw new RuntimeException("Unable to find serialized class for " + x + ", " + z, e);
}
}
@Override
public void writeChunkStore(World world, int x, int z, ChunkStore data) {
if (!data.isDirty()) {
return;
}
try {
SimpleRegionFile 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 void closeChunkStore(World world, int x, int z) {
SimpleRegionFile rf = getSimpleRegionFile(world, x, z);
if (rf != null) {
rf.close();
}
}
private SimpleRegionFile getSimpleRegionFile(World world, int x, int z) {
File directory = new File(world.getWorldFolder(), "mcmmo_regions");
directory.mkdirs();
UUID key = world.getUID();
HashMap<Long, SimpleRegionFile> worldRegions = regionFiles.get(key);
if (worldRegions == null) {
worldRegions = new HashMap<Long, SimpleRegionFile>();
regionFiles.put(key, worldRegions);
}
int rx = x >> 5;
int rz = z >> 5;
long key2 = (((long) rx) << 32) | (((long) rz) & 0xFFFFFFFFL);
SimpleRegionFile regionFile = worldRegions.get(key2);
if (regionFile == null) {
File file = new File(directory, "mcmmo_" + rx + "_" + rz + "_.mcm");
regionFile = new SimpleRegionFile(file, rx, rz);
worldRegions.put(key2, regionFile);
}
return regionFile;
}
@Override
public void loadChunklet(int cx, int cy, int cz, World world) {
loadChunk(cx, cz, world);
}
@Override
public void unloadChunklet(int cx, int cy, int cz, World world) {
unloadChunk(cx, cz, world);
}
@Override
public void loadChunk(int cx, int cz, World world) {
if(world == null)
return;
if(store.containsKey(world.getName() + "," + cx + "," + cz))
return;
ChunkStore in = null;
try {
in = readChunkStore(world, cx, cz);
}
catch(Exception e) {}
if(in != null) {
store.put(world.getName() + "," + cx + "," + cz, in);
}
}
@Override
public 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);
}
}
@Override
public void saveChunk(int cx, int cz, World world) {
if(world == null)
return;
if(store.containsKey(world.getName() + "," + cx + "," + cz)) {
ChunkStore out = store.get(world.getName() + "," + cx + "," + cz);
if(!out.isDirty())
return;
writeChunkStore(world, cx, cz, out);
}
}
@Override
public boolean isChunkLoaded(int cx, int cz, World world) {
if(world == null)
return false;
return store.containsKey(world.getName() + "," + cx + "," + cz);
}
@Override
public void chunkLoaded(int cx, int cz, World world) {}
@Override
public void chunkUnloaded(int cx, int cz, World world) {
if(world == null)
return;
ChunkletUnloader.addToList(cx, cx, world);
}
@Override
public void saveWorld(World world) {
if(world == null)
return;
closeAll();
String worldName = world.getName();
for(String key : store.keySet()) {
String[] info = key.split(",");
if(worldName.equals(info[0])) {
int cx = 0;
int cz = 0;
try {
cx = Integer.parseInt(info[1]);
cz = Integer.parseInt(info[2]);
}
catch(Exception e) {
return;
}
saveChunk(cx, cz, world);
}
}
}
@Override
public void unloadWorld(World world) {
if(world == null)
return;
closeAll();
String worldName = world.getName();
for(String key : store.keySet()) {
String[] info = key.split(",");
if(worldName.equals(info[0])) {
int cx = 0;
int cz = 0;
try {
cx = Integer.parseInt(info[1]);
cz = Integer.parseInt(info[2]);
}
catch(Exception e) {
return;
}
unloadChunk(cx, cz, world);
}
}
}
@Override
public void loadWorld(World world) {}
@Override
public void saveAll() {
closeAll();
for(World world : Bukkit.getWorlds()) {
saveWorld(world);
}
}
@Override
public void unloadAll() {
closeAll();
for(World world : Bukkit.getWorlds()) {
unloadWorld(world);
}
}
@Override
public boolean isTrue(int x, int y, int z, World world) {
if(world == null)
return false;
int cx = x / 16;
int cz = z / 16;
String key = world.getName() + "," + cx + "," + cz;
if (!store.containsKey(key)) {
loadChunk(cx, cz, world);
}
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 boolean isTrue(Block block) {
if(block == null)
return false;
return isTrue(block.getX(), block.getY(), block.getZ(), block.getWorld());
}
@Override
public void setTrue(int x, int y, int z, World world) {
if(world == null)
return;
int cx = x / 16;
int cz = z / 16;
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);
}
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 void setTrue(Block block) {
if(block == null)
return;
setTrue(block.getX(), block.getY(), block.getZ(), block.getWorld());
}
@Override
public void setFalse(int x, int y, int z, World world) {
if(world == null)
return;
int cx = x / 16;
int cz = z / 16;
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);
}
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 void setFalse(Block block) {
if(block == null)
return;
setFalse(block.getX(), block.getY(), block.getZ(), block.getWorld());
}
@Override
public void cleanUp() {}
}

View File

@ -19,7 +19,7 @@ import com.gmail.nossr50.mcMMO;
import com.gmail.nossr50.runnables.ChunkletUnloader;
public class HashChunkletManager implements ChunkletManager {
private HashMap<String, ChunkletStore> store = new HashMap<String, ChunkletStore>();
public HashMap<String, ChunkletStore> store = new HashMap<String, ChunkletStore>();
@Override
public void loadChunklet(int cx, int cy, int cz, World world) {

View File

@ -0,0 +1,89 @@
package com.gmail.nossr50.util.blockmeta;
import java.io.IOException;
import org.bukkit.World;
import org.bukkit.block.Block;
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) {}
@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) {}
@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

@ -0,0 +1,144 @@
package com.gmail.nossr50.util.blockmeta;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.UUID;
import org.bukkit.World;
import com.gmail.nossr50.mcMMO;
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 = 4;
private static final int MAGIC_NUMBER = 0xEA5EDEBB;
private int cx;
private int cz;
private UUID worldUid;
transient private int worldHeight;
transient private int xBitShifts;
transient private int zBitShifts;
transient private boolean conversionNeeded;
public PrimitiveChunkStore(World world, int cx, int cz) {
this.cx = cx;
this.cz = cz;
this.worldUid = world.getUID();
this.worldHeight = world != null ? world.getMaxHeight() : 128;
this.xBitShifts = 11;
this.zBitShifts = 7;
this.store = new boolean[16][16][this.worldHeight - 1];
conversionNeeded = false;
}
@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) {
store[x][z][y] = true;
dirty = true;
}
@Override
public void setFalse(int x, int y, int z) {
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 < this.worldHeight; 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 < this.worldHeight; 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 fileVersionNumber; // Can be used to determine the format of the file
long lsb = in.readLong();
if (((int) (lsb >> 32)) == MAGIC_NUMBER) {
fileVersionNumber = (int) lsb;
lsb = in.readLong();
} else {
fileVersionNumber = 0;
}
long msb = in.readLong();
worldUid = new UUID(msb, lsb);
cx = in.readInt();
cz = in.readInt();
// Constructor is not invoked, need to set these fields
World world = mcMMO.p.getServer().getWorld(this.worldUid);
this.worldHeight = world.getMaxHeight();
this.xBitShifts = 11;
this.zBitShifts = 7;
store = (boolean[][][]) in.readObject();
if (fileVersionNumber < CURRENT_VERSION) {
dirty = true;
}
}
}

View File

@ -4,7 +4,7 @@ public class PrimitiveChunkletStore implements ChunkletStore {
private static final long serialVersionUID = -3453078050608607478L;
/** X, Z, Y */
private boolean[][][] store = new boolean[16][16][64];
public boolean[][][] store = new boolean[16][16][64];
@Override
public boolean isTrue(int x, int y, int z) {

View File

@ -9,7 +9,7 @@ public class PrimitiveExChunkletStore implements ChunkletStore, Externalizable {
private static final long serialVersionUID = 8603603827094383873L;
/** X, Z, Y */
private boolean[][][] store = new boolean[16][16][64];
public boolean[][][] store = new boolean[16][16][64];
@Override
public boolean isTrue(int x, int y, int z) {