diff --git a/src/main/java/com/gmail/nossr50/config/HiddenConfig.java b/src/main/java/com/gmail/nossr50/config/HiddenConfig.java index e450e06cf..d1d4f1638 100644 --- a/src/main/java/com/gmail/nossr50/config/HiddenConfig.java +++ b/src/main/java/com/gmail/nossr50/config/HiddenConfig.java @@ -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", 1); } } public boolean getChunkletsEnabled() { return chunkletsEnabled; } + + public int getConversionRate() { + return conversionRate; + } } diff --git a/src/main/java/com/gmail/nossr50/listeners/WorldListener.java b/src/main/java/com/gmail/nossr50/listeners/WorldListener.java index c1f0fd6e6..5c6d36551 100644 --- a/src/main/java/com/gmail/nossr50/listeners/WorldListener.java +++ b/src/main/java/com/gmail/nossr50/listeners/WorldListener.java @@ -1,23 +1,43 @@ 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.BlockStoreConversionMain; +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.PrimitiveChunkStore; +import com.gmail.nossr50.util.blockmeta.HashChunkletManager; +import com.gmail.nossr50.util.blockmeta.HashChunkManager; public class WorldListener implements Listener { + ArrayList converters = new ArrayList(); + @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 +54,98 @@ 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(); + HashChunkletManager manager = new HashChunkletManager(); + HashChunkManager newManager = (HashChunkManager) mcMMO.p.placeStore; + + manager.loadChunk(cx, cz, world); + + for(int y = 0; y < 4; 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) newManager.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; + + newManager.setTrue(cxPos, y2, czPos, world); + } + } + } + continue; + } + + newManager.setTrue(cx * 16, 0, cz * 16, world); + newManager.setFalse(cx * 16, 0, cz * 16, world); + cChunk = (PrimitiveChunkStore) newManager.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]; + System.arraycopy(oldArray, 0, newArray, (y * 64), 64); + } + } + } + } + + manager.unloadChunk(cx, cz, world); + newManager.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(); + } } diff --git a/src/main/java/com/gmail/nossr50/mcMMO.java b/src/main/java/com/gmail/nossr50/mcMMO.java index 3e8ead029..630ed95b0 100644 --- a/src/main/java/com/gmail/nossr50/mcMMO.java +++ b/src/main/java/com/gmail/nossr50/mcMMO.java @@ -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.ChunkManager; +import com.gmail.nossr50.util.blockmeta.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(); } /** diff --git a/src/main/java/com/gmail/nossr50/runnables/BlockStoreConversionMain.java b/src/main/java/com/gmail/nossr50/runnables/BlockStoreConversionMain.java new file mode 100755 index 000000000..abbbe34cf --- /dev/null +++ b/src/main/java/com/gmail/nossr50/runnables/BlockStoreConversionMain.java @@ -0,0 +1,90 @@ +package com.gmail.nossr50.runnables; + +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; + } +} diff --git a/src/main/java/com/gmail/nossr50/runnables/BlockStoreConversionXDirectory.java b/src/main/java/com/gmail/nossr50/runnables/BlockStoreConversionXDirectory.java new file mode 100755 index 000000000..87570296b --- /dev/null +++ b/src/main/java/com/gmail/nossr50/runnables/BlockStoreConversionXDirectory.java @@ -0,0 +1,79 @@ +package com.gmail.nossr50.runnables; + +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; + } +} \ No newline at end of file diff --git a/src/main/java/com/gmail/nossr50/runnables/BlockStoreConversionZDirectory.java b/src/main/java/com/gmail/nossr50/runnables/BlockStoreConversionZDirectory.java new file mode 100755 index 000000000..c8ceb27ae --- /dev/null +++ b/src/main/java/com/gmail/nossr50/runnables/BlockStoreConversionZDirectory.java @@ -0,0 +1,154 @@ +package com.gmail.nossr50.runnables; + +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.PrimitiveExChunkletStore; +import com.gmail.nossr50.util.blockmeta.PrimitiveChunkStore; +import com.gmail.nossr50.util.blockmeta.HashChunkletManager; +import com.gmail.nossr50.util.blockmeta.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 PrimitiveExChunkletStore currentChunklet; + 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 < 4; this.y++) { + this.chunkletName = this.world.getName() + "," + this.cx + "," + this.cz + "," + this.y; + this.currentChunklet = (PrimitiveExChunkletStore) this.manager.store.get(this.chunkletName); + if(this.currentChunklet == 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++) { + this.oldArray = this.currentChunklet.store[x][z]; + this.newArray = this.currentChunk.store[x][z]; + 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.currentChunklet = null; + this.currentChunk = null; + } +} \ No newline at end of file diff --git a/src/main/java/com/gmail/nossr50/util/blockmeta/ChunkManager.java b/src/main/java/com/gmail/nossr50/util/blockmeta/ChunkManager.java new file mode 100755 index 000000000..7bc86cfb8 --- /dev/null +++ b/src/main/java/com/gmail/nossr50/util/blockmeta/ChunkManager.java @@ -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(); +} diff --git a/src/main/java/com/gmail/nossr50/util/blockmeta/ChunkManagerFactory.java b/src/main/java/com/gmail/nossr50/util/blockmeta/ChunkManagerFactory.java new file mode 100755 index 000000000..b0a548ec8 --- /dev/null +++ b/src/main/java/com/gmail/nossr50/util/blockmeta/ChunkManagerFactory.java @@ -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(); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/gmail/nossr50/util/blockmeta/ChunkStore.java b/src/main/java/com/gmail/nossr50/util/blockmeta/ChunkStore.java new file mode 100755 index 000000000..b40f21c6b --- /dev/null +++ b/src/main/java/com/gmail/nossr50/util/blockmeta/ChunkStore.java @@ -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); +} \ No newline at end of file diff --git a/src/main/java/com/gmail/nossr50/util/blockmeta/ChunkStoreFactory.java b/src/main/java/com/gmail/nossr50/util/blockmeta/ChunkStoreFactory.java new file mode 100755 index 000000000..7a8a33c7e --- /dev/null +++ b/src/main/java/com/gmail/nossr50/util/blockmeta/ChunkStoreFactory.java @@ -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); + } +} \ No newline at end of file diff --git a/src/main/java/com/gmail/nossr50/util/blockmeta/HashChunkManager.java b/src/main/java/com/gmail/nossr50/util/blockmeta/HashChunkManager.java new file mode 100755 index 000000000..33cc5216a --- /dev/null +++ b/src/main/java/com/gmail/nossr50/util/blockmeta/HashChunkManager.java @@ -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> regionFiles = new HashMap>(); + public HashMap store = new HashMap(); + + @Override + public void closeAll() { + for (UUID uid : regionFiles.keySet()) { + HashMap worldRegions = regionFiles.get(uid); + Iterator 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 worldRegions = regionFiles.get(key); + + if (worldRegions == null) { + worldRegions = new HashMap(); + 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() {} +} diff --git a/src/main/java/com/gmail/nossr50/util/blockmeta/HashChunkletManager.java b/src/main/java/com/gmail/nossr50/util/blockmeta/HashChunkletManager.java index bc0899730..d03605b62 100644 --- a/src/main/java/com/gmail/nossr50/util/blockmeta/HashChunkletManager.java +++ b/src/main/java/com/gmail/nossr50/util/blockmeta/HashChunkletManager.java @@ -19,7 +19,7 @@ import com.gmail.nossr50.mcMMO; import com.gmail.nossr50.runnables.ChunkletUnloader; public class HashChunkletManager implements ChunkletManager { - private HashMap store = new HashMap(); + public HashMap store = new HashMap(); @Override public void loadChunklet(int cx, int cy, int cz, World world) { diff --git a/src/main/java/com/gmail/nossr50/util/blockmeta/NullChunkManager.java b/src/main/java/com/gmail/nossr50/util/blockmeta/NullChunkManager.java new file mode 100755 index 000000000..b104e14ee --- /dev/null +++ b/src/main/java/com/gmail/nossr50/util/blockmeta/NullChunkManager.java @@ -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() {} +} \ No newline at end of file diff --git a/src/main/java/com/gmail/nossr50/util/blockmeta/PrimitiveChunkStore.java b/src/main/java/com/gmail/nossr50/util/blockmeta/PrimitiveChunkStore.java new file mode 100755 index 000000000..0a22b308b --- /dev/null +++ b/src/main/java/com/gmail/nossr50/util/blockmeta/PrimitiveChunkStore.java @@ -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; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/gmail/nossr50/util/blockmeta/PrimitiveChunkletStore.java b/src/main/java/com/gmail/nossr50/util/blockmeta/PrimitiveChunkletStore.java index ed9d8872f..6ce9bd24b 100644 --- a/src/main/java/com/gmail/nossr50/util/blockmeta/PrimitiveChunkletStore.java +++ b/src/main/java/com/gmail/nossr50/util/blockmeta/PrimitiveChunkletStore.java @@ -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) { diff --git a/src/main/java/com/gmail/nossr50/util/blockmeta/PrimitiveExChunkletStore.java b/src/main/java/com/gmail/nossr50/util/blockmeta/PrimitiveExChunkletStore.java index 3feba7cae..bb5ec9ac8 100644 --- a/src/main/java/com/gmail/nossr50/util/blockmeta/PrimitiveExChunkletStore.java +++ b/src/main/java/com/gmail/nossr50/util/blockmeta/PrimitiveExChunkletStore.java @@ -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) { diff --git a/src/main/java/org/getspout/spoutapi/chunkstore/SimpleChunkBuffer.java b/src/main/java/org/getspout/spoutapi/chunkstore/SimpleChunkBuffer.java new file mode 100755 index 000000000..db8ef6fe2 --- /dev/null +++ b/src/main/java/org/getspout/spoutapi/chunkstore/SimpleChunkBuffer.java @@ -0,0 +1,39 @@ +/* + * This file is part of SpoutPlugin. + * + * Copyright (c) 2011-2012, SpoutDev + * 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 . + */ +package org.getspout.spoutapi.chunkstore; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +public class SimpleChunkBuffer extends ByteArrayOutputStream { + final SimpleRegionFile rf; + final int index; + + SimpleChunkBuffer(SimpleRegionFile rf, int index) { + super(1024); + this.rf = rf; + this.index = index; + } + + @Override + public void close() throws IOException { + rf.write(index, buf, count); + } +} \ No newline at end of file diff --git a/src/main/java/org/getspout/spoutapi/chunkstore/SimpleRegionFile.java b/src/main/java/org/getspout/spoutapi/chunkstore/SimpleRegionFile.java new file mode 100755 index 000000000..0e8ea2173 --- /dev/null +++ b/src/main/java/org/getspout/spoutapi/chunkstore/SimpleRegionFile.java @@ -0,0 +1,300 @@ +/* + * This file is part of SpoutPlugin. + * + * Copyright (c) 2011-2012, SpoutDev + * 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 . + */ +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 SimpleRegionFile { + 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 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 long TIMEOUT_TIME = 300000; // 5 min + + public SimpleRegionFile(File f, int rx, int rz) { + this(f, rx, rz, 10); + } + + public SimpleRegionFile(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 SimpleChunkBuffer(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]); + } + } +} \ No newline at end of file diff --git a/src/main/resources/hidden.yml b/src/main/resources/hidden.yml index 55f954b8c..175985949 100644 --- a/src/main/resources/hidden.yml +++ b/src/main/resources/hidden.yml @@ -4,4 +4,6 @@ ### Options: # true to use Chunklets metadata store system, false to disable - Chunklets: true \ No newline at end of file + Chunklets: true + # Square root of the number of chunks to convert per tick. + ConversionRate: 1 \ No newline at end of file