mirror of
https://github.com/mcMMO-Dev/mcMMO.git
synced 2024-11-02 08:39:49 +01:00
Merge pull request #295 from Glitchfinder/master
Updating block storage to a region-style format.
This commit is contained in:
commit
69a5cd1017
@ -9,6 +9,7 @@ public class HiddenConfig {
|
||||
private static String fileName;
|
||||
private static YamlConfiguration config;
|
||||
private static boolean chunkletsEnabled;
|
||||
private static int conversionRate;
|
||||
|
||||
public HiddenConfig(String fileName) {
|
||||
HiddenConfig.fileName = fileName;
|
||||
@ -27,10 +28,15 @@ public class HiddenConfig {
|
||||
if (mcMMO.p.getResource(fileName) != null) {
|
||||
config = YamlConfiguration.loadConfiguration(mcMMO.p.getResource(fileName));
|
||||
chunkletsEnabled = config.getBoolean("Options.Chunklets", true);
|
||||
conversionRate = config.getInt("Options.ConversionRate", 3);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean getChunkletsEnabled() {
|
||||
return chunkletsEnabled;
|
||||
}
|
||||
|
||||
public int getConversionRate() {
|
||||
return conversionRate;
|
||||
}
|
||||
}
|
||||
|
@ -1,23 +1,38 @@
|
||||
package com.gmail.nossr50.listeners;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.world.ChunkLoadEvent;
|
||||
import org.bukkit.event.world.ChunkUnloadEvent;
|
||||
import org.bukkit.event.world.WorldLoadEvent;
|
||||
import org.bukkit.event.world.WorldInitEvent;
|
||||
import org.bukkit.event.world.WorldSaveEvent;
|
||||
import org.bukkit.event.world.WorldUnloadEvent;
|
||||
import org.bukkit.World;
|
||||
|
||||
import com.gmail.nossr50.mcMMO;
|
||||
import com.gmail.nossr50.runnables.blockstoreconversion.BlockStoreConversionMain;
|
||||
import com.gmail.nossr50.util.blockmeta.chunkmeta.HashChunkManager;
|
||||
|
||||
public class WorldListener implements Listener {
|
||||
ArrayList<BlockStoreConversionMain> converters = new ArrayList<BlockStoreConversionMain>();
|
||||
|
||||
@EventHandler
|
||||
public void onWorldLoad(WorldLoadEvent event) {
|
||||
public void onWorldInit(WorldInitEvent event) {
|
||||
File dataDir = new File(event.getWorld().getWorldFolder(), "mcmmo_data");
|
||||
if(!dataDir.exists()) {
|
||||
dataDir.mkdir();
|
||||
return;
|
||||
}
|
||||
|
||||
if(mcMMO.p == null)
|
||||
return;
|
||||
|
||||
mcMMO.p.getLogger().info("Converting block storage for " + event.getWorld().getName() + " to a new format.");
|
||||
BlockStoreConversionMain converter = new BlockStoreConversionMain(event.getWorld());
|
||||
converter.run();
|
||||
converters.add(converter);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
@ -34,4 +49,19 @@ public class WorldListener implements Listener {
|
||||
public void onChunkUnload(ChunkUnloadEvent event) {
|
||||
mcMMO.placeStore.chunkUnloaded(event.getChunk().getX(), event.getChunk().getZ(), event.getWorld());
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onChunkLoad(ChunkLoadEvent event) {
|
||||
File dataDir = new File(event.getChunk().getWorld().getWorldFolder(), "mcmmo_data");
|
||||
|
||||
if(!dataDir.exists() || !dataDir.isDirectory()) {
|
||||
return;
|
||||
}
|
||||
|
||||
World world = event.getChunk().getWorld();
|
||||
int cx = event.getChunk().getX();
|
||||
int cz = event.getChunk().getZ();
|
||||
|
||||
((HashChunkManager) mcMMO.p.placeStore).convertChunk(dataDir, cx, cz, world);
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import org.bukkit.plugin.PluginDescriptionFile;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.bukkit.scheduler.BukkitScheduler;
|
||||
import org.bukkit.World;
|
||||
|
||||
import com.gmail.nossr50.commands.general.AddlevelsCommand;
|
||||
import com.gmail.nossr50.commands.general.AddxpCommand;
|
||||
@ -78,8 +79,9 @@ import com.gmail.nossr50.util.Leaderboard;
|
||||
import com.gmail.nossr50.util.Metrics;
|
||||
import com.gmail.nossr50.util.Metrics.Graph;
|
||||
import com.gmail.nossr50.util.Users;
|
||||
import com.gmail.nossr50.util.blockmeta.ChunkletManager;
|
||||
import com.gmail.nossr50.util.blockmeta.ChunkletManagerFactory;
|
||||
import com.gmail.nossr50.util.blockmeta.chunkmeta.ChunkManager;
|
||||
import com.gmail.nossr50.util.blockmeta.chunkmeta.ChunkManagerFactory;
|
||||
|
||||
|
||||
public class mcMMO extends JavaPlugin {
|
||||
|
||||
@ -95,7 +97,7 @@ public class mcMMO extends JavaPlugin {
|
||||
private static Database database;
|
||||
public static mcMMO p;
|
||||
|
||||
public static ChunkletManager placeStore;
|
||||
public static ChunkManager placeStore;
|
||||
public static RepairManager repairManager;
|
||||
|
||||
/* Jar Stuff */
|
||||
@ -223,7 +225,7 @@ public class mcMMO extends JavaPlugin {
|
||||
}
|
||||
|
||||
// Get our ChunkletManager
|
||||
placeStore = ChunkletManagerFactory.getChunkletManager();
|
||||
placeStore = ChunkManagerFactory.getChunkManager();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,90 @@
|
||||
package com.gmail.nossr50.runnables.blockstoreconversion;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.Runnable;
|
||||
|
||||
import org.bukkit.scheduler.BukkitScheduler;
|
||||
|
||||
import com.gmail.nossr50.config.HiddenConfig;
|
||||
import com.gmail.nossr50.mcMMO;
|
||||
|
||||
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.scheduleSyncDelayedTask(mcMMO.p, this, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
return;
|
||||
}
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
package com.gmail.nossr50.runnables.blockstoreconversion;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.Runnable;
|
||||
|
||||
import org.bukkit.scheduler.BukkitScheduler;
|
||||
|
||||
import com.gmail.nossr50.config.HiddenConfig;
|
||||
import com.gmail.nossr50.mcMMO;
|
||||
|
||||
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.scheduleSyncDelayedTask(mcMMO.p, this, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@ -0,0 +1,173 @@
|
||||
package com.gmail.nossr50.runnables.blockstoreconversion;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.Runnable;
|
||||
import java.lang.String;
|
||||
|
||||
import org.bukkit.scheduler.BukkitScheduler;
|
||||
|
||||
import com.gmail.nossr50.mcMMO;
|
||||
import com.gmail.nossr50.util.blockmeta.ChunkletStore;
|
||||
import com.gmail.nossr50.util.blockmeta.PrimitiveChunkletStore;
|
||||
import com.gmail.nossr50.util.blockmeta.PrimitiveExChunkletStore;
|
||||
import com.gmail.nossr50.util.blockmeta.chunkmeta.PrimitiveChunkStore;
|
||||
import com.gmail.nossr50.util.blockmeta.HashChunkletManager;
|
||||
import com.gmail.nossr50.util.blockmeta.chunkmeta.HashChunkManager;
|
||||
|
||||
public class BlockStoreConversionZDirectory implements Runnable {
|
||||
private 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.p.placeStore;
|
||||
this.dataDir = dataDir;
|
||||
this.xDir = xDir;
|
||||
|
||||
if(this.taskID >= 0)
|
||||
return;
|
||||
|
||||
this.taskID = this.scheduler.scheduleSyncDelayedTask(mcMMO.p, this, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
} else {
|
||||
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++) {
|
||||
if(!this.manager.isTrue(this.cxPos, this.y2, this.czPos, this.world))
|
||||
continue;
|
||||
|
||||
this.newManager.setTrue(this.cxPos, this.y2, this.czPos, this.world);
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
168
src/main/java/com/gmail/nossr50/util/blockmeta/chunkmeta/ChunkManager.java
Executable file
168
src/main/java/com/gmail/nossr50/util/blockmeta/chunkmeta/ChunkManager.java
Executable file
@ -0,0 +1,168 @@
|
||||
package com.gmail.nossr50.util.blockmeta.chunkmeta;
|
||||
|
||||
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();
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package com.gmail.nossr50.util.blockmeta.chunkmeta;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
74
src/main/java/com/gmail/nossr50/util/blockmeta/chunkmeta/ChunkStore.java
Executable file
74
src/main/java/com/gmail/nossr50/util/blockmeta/chunkmeta/ChunkStore.java
Executable file
@ -0,0 +1,74 @@
|
||||
package com.gmail.nossr50.util.blockmeta.chunkmeta;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import com.gmail.nossr50.util.blockmeta.ChunkletStore;
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
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);
|
||||
}
|
||||
}
|
464
src/main/java/com/gmail/nossr50/util/blockmeta/chunkmeta/HashChunkManager.java
Executable file
464
src/main/java/com/gmail/nossr50/util/blockmeta/chunkmeta/HashChunkManager.java
Executable file
@ -0,0 +1,464 @@
|
||||
package com.gmail.nossr50.util.blockmeta.chunkmeta;
|
||||
|
||||
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 com.gmail.nossr50.util.blockmeta.ChunkletStore;
|
||||
import com.gmail.nossr50.util.blockmeta.PrimitiveChunkletStore;
|
||||
import com.gmail.nossr50.util.blockmeta.PrimitiveExChunkletStore;
|
||||
import com.gmail.nossr50.util.blockmeta.HashChunkletManager;
|
||||
|
||||
import org.getspout.spoutapi.chunkstore.mcMMOSimpleRegionFile;
|
||||
|
||||
public class HashChunkManager implements ChunkManager {
|
||||
private HashMap<UUID, HashMap<Long, mcMMOSimpleRegionFile>> regionFiles = new HashMap<UUID, HashMap<Long, mcMMOSimpleRegionFile>>();
|
||||
public HashMap<String, ChunkStore> store = new HashMap<String, ChunkStore>();
|
||||
|
||||
@Override
|
||||
public void closeAll() {
|
||||
for (UUID uid : regionFiles.keySet()) {
|
||||
HashMap<Long, mcMMOSimpleRegionFile> worldRegions = regionFiles.get(uid);
|
||||
Iterator<mcMMOSimpleRegionFile> itr = worldRegions.values().iterator();
|
||||
while (itr.hasNext()) {
|
||||
mcMMOSimpleRegionFile rf = itr.next();
|
||||
if (rf != null) {
|
||||
rf.close();
|
||||
itr.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
regionFiles.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public 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;
|
||||
}
|
||||
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 {
|
||||
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 void closeChunkStore(World world, int x, int z) {
|
||||
mcMMOSimpleRegionFile rf = getSimpleRegionFile(world, x, z);
|
||||
if (rf != null) {
|
||||
rf.close();
|
||||
}
|
||||
}
|
||||
|
||||
private 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.get(key);
|
||||
|
||||
if (worldRegions == null) {
|
||||
worldRegions = new HashMap<Long, mcMMOSimpleRegionFile>();
|
||||
regionFiles.put(key, worldRegions);
|
||||
}
|
||||
|
||||
int rx = x >> 5;
|
||||
int rz = z >> 5;
|
||||
|
||||
long key2 = (((long) rx) << 32) | (((long) 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 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() {}
|
||||
|
||||
public void convertChunk(File dataDir, int cx, int cz, World world) {
|
||||
HashChunkletManager manager = new HashChunkletManager();
|
||||
manager.loadChunk(cx, cz, world);
|
||||
|
||||
for(int y = 0; y < (world.getMaxHeight() / 64); y++) {
|
||||
String chunkletName = world.getName() + "," + cx + "," + cz + "," + y;
|
||||
ChunkletStore tempChunklet = manager.store.get(chunkletName);
|
||||
PrimitiveChunkletStore primitiveChunklet = null;
|
||||
PrimitiveExChunkletStore primitiveExChunklet = null;
|
||||
if(tempChunklet instanceof PrimitiveChunkletStore)
|
||||
primitiveChunklet = (PrimitiveChunkletStore) tempChunklet;
|
||||
else if(tempChunklet instanceof PrimitiveExChunkletStore)
|
||||
primitiveExChunklet = (PrimitiveExChunkletStore) tempChunklet;
|
||||
if(tempChunklet == null) {
|
||||
continue;
|
||||
} else {
|
||||
String chunkName = world.getName() + "," + cx + "," + cz;
|
||||
PrimitiveChunkStore cChunk = (PrimitiveChunkStore) store.get(chunkName);
|
||||
|
||||
if(cChunk != null) {
|
||||
int xPos = cx * 16;
|
||||
int zPos = cz * 16;
|
||||
|
||||
for(int x = 0; x < 16; x++) {
|
||||
for(int z = 0; z < 16; z++) {
|
||||
int cxPos = xPos + x;
|
||||
int czPos = zPos + z;
|
||||
|
||||
for(int y2 = (64 * y); y2 < (64 * y + 64); y2++) {
|
||||
if(!manager.isTrue(cxPos, y2, czPos, world))
|
||||
continue;
|
||||
|
||||
setTrue(cxPos, y2, czPos, world);
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
setTrue(cx * 16, 0, cz * 16, world);
|
||||
setFalse(cx * 16, 0, cz * 16, world);
|
||||
cChunk = (PrimitiveChunkStore) store.get(chunkName);
|
||||
|
||||
for(int x = 0; x < 16; x++) {
|
||||
for(int z = 0; z < 16; z++) {
|
||||
boolean[] oldArray;
|
||||
if(primitiveChunklet != null)
|
||||
oldArray = primitiveChunklet.store[x][z];
|
||||
if(primitiveExChunklet != null)
|
||||
oldArray = primitiveExChunklet.store[x][z];
|
||||
else
|
||||
return;
|
||||
boolean[] newArray = cChunk.store[x][z];
|
||||
if(oldArray.length < 64)
|
||||
return;
|
||||
else if(newArray.length < ((y * 64) + 64))
|
||||
return;
|
||||
System.arraycopy(oldArray, 0, newArray, (y * 64), 64);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
manager.unloadChunk(cx, cz, world);
|
||||
unloadChunk(cx, cz, world);
|
||||
|
||||
File cxDir = new File(dataDir, "" + cx);
|
||||
if(!cxDir.exists()) return;
|
||||
File czDir = new File(cxDir, "" + cz);
|
||||
if(!czDir.exists()) return;
|
||||
|
||||
for(File yFile : czDir.listFiles()) {
|
||||
if(!yFile.exists())
|
||||
continue;
|
||||
|
||||
yFile.delete();
|
||||
}
|
||||
|
||||
if(czDir.listFiles().length <= 0)
|
||||
czDir.delete();
|
||||
if(cxDir.listFiles().length <= 0)
|
||||
cxDir.delete();
|
||||
if(dataDir.listFiles().length <= 0)
|
||||
dataDir.delete();
|
||||
}
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
package com.gmail.nossr50.util.blockmeta.chunkmeta;
|
||||
|
||||
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() {}
|
||||
}
|
@ -0,0 +1,145 @@
|
||||
package com.gmail.nossr50.util.blockmeta.chunkmeta;
|
||||
|
||||
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;
|
||||
import com.gmail.nossr50.util.blockmeta.ChunkletStore;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
39
src/main/java/org/getspout/spoutapi/chunkstore/mcMMOSimpleChunkBuffer.java
Executable file
39
src/main/java/org/getspout/spoutapi/chunkstore/mcMMOSimpleChunkBuffer.java
Executable file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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 org.getspout.spoutapi.chunkstore;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
300
src/main/java/org/getspout/spoutapi/chunkstore/mcMMOSimpleRegionFile.java
Executable file
300
src/main/java/org/getspout/spoutapi/chunkstore/mcMMOSimpleRegionFile.java
Executable file
@ -0,0 +1,300 @@
|
||||
/*
|
||||
* 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 org.getspout.spoutapi.chunkstore;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
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<Boolean>();
|
||||
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 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 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 boolean testCloseTimeout() {
|
||||
/*if (System.currentTimeMillis() - TIMEOUT_TIME > lastAccessTime) {
|
||||
close();
|
||||
return true;
|
||||
}*/
|
||||
return false;
|
||||
}
|
||||
|
||||
public DataOutputStream getOutputStream(int x, int z) {
|
||||
int index = getChunkIndex(x, z);
|
||||
return new DataOutputStream(new DeflaterOutputStream(new mcMMOSimpleChunkBuffer(this, index)));
|
||||
}
|
||||
|
||||
public 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)));
|
||||
}
|
||||
|
||||
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 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 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");
|
||||
} else {
|
||||
throw new IllegalStateException("Attempting to delete empty segment");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dataStart[index];
|
||||
}
|
||||
|
||||
private void extendFile() throws IOException {
|
||||
long extend = (-getFile().length()) & segmentMask;
|
||||
|
||||
getFile().seek(getFile().length());
|
||||
|
||||
while ((extend--) > 0) {
|
||||
getFile().write(0);
|
||||
}
|
||||
}
|
||||
|
||||
private 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 int sizeToSegments(int size) {
|
||||
if (size <= 0) {
|
||||
return 1;
|
||||
} else {
|
||||
return ((size - 1) >> segmentSize) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
private 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 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]);
|
||||
}
|
||||
}
|
||||
}
|
@ -5,3 +5,5 @@
|
||||
Options:
|
||||
# true to use Chunklets metadata store system, false to disable
|
||||
Chunklets: true
|
||||
# Square root of the number of chunks to convert per tick.
|
||||
ConversionRate: 3
|
Loading…
Reference in New Issue
Block a user