diff --git a/src/com/dre/brewery/BCauldron.java b/src/com/dre/brewery/BCauldron.java index ecae7fe..f7e2e3f 100644 --- a/src/com/dre/brewery/BCauldron.java +++ b/src/com/dre/brewery/BCauldron.java @@ -36,7 +36,6 @@ public class BCauldron { public BCauldron(Block block) { this.block = block; - bcauldrons.put(block, this); } // loading from file @@ -44,7 +43,6 @@ public class BCauldron { this.block = block; this.state = state; this.ingredients = ingredients; - bcauldrons.put(block, this); } public void onUpdate() { @@ -74,6 +72,20 @@ public class BCauldron { } } + /** + * Get the Block that this BCauldron represents + */ + public Block getBlock() { + return block; + } + + /** + * Get the State (Time in Minutes) that this Cauldron currently has + */ + public int getState() { + return state; + } + // get cauldron by Block @Nullable public static BCauldron get(Block block) { @@ -99,6 +111,7 @@ public class BCauldron { BCauldron bcauldron = get(block); if (bcauldron == null) { bcauldron = new BCauldron(block); + BCauldron.bcauldrons.put(block, bcauldron); } IngedientAddEvent event = new IngedientAddEvent(player, block, bcauldron, ingredient.clone(), rItem); diff --git a/src/com/dre/brewery/Barrel.java b/src/com/dre/brewery/Barrel.java index e94a680..0cc0c01 100644 --- a/src/com/dre/brewery/Barrel.java +++ b/src/com/dre/brewery/Barrel.java @@ -59,6 +59,15 @@ public class Barrel implements InventoryHolder { * load from file */ public Barrel(Block spigot, byte sign, BoundingBox bounds, Map items, float time) { + this(spigot, sign, bounds, items, time, false); + } + + /** + * Load from File + *

If async: true, The Barrel Bounds will not be recreated when missing/corrupt, getBody().getBounds() will be null if it needs recreating + * + */ + public Barrel(Block spigot, byte sign, BoundingBox bounds, Map items, float time, boolean async) { this.spigot = spigot; if (isLarge()) { this.inventory = P.p.getServer().createInventory(this, 27, P.p.languageReader.get("Etc_Barrel")); @@ -74,7 +83,7 @@ public class Barrel implements InventoryHolder { } this.time = time; - body = new BarrelBody(this, sign, bounds); + body = new BarrelBody(this, sign, bounds, async); } public static void onUpdate() { diff --git a/src/com/dre/brewery/BarrelBody.java b/src/com/dre/brewery/BarrelBody.java index 279c29a..46ce0ce 100644 --- a/src/com/dre/brewery/BarrelBody.java +++ b/src/com/dre/brewery/BarrelBody.java @@ -30,17 +30,17 @@ public class BarrelBody { /** * Loading from file */ - public BarrelBody(Barrel barrel, byte signoffset, BoundingBox bounds) { + public BarrelBody(Barrel barrel, byte signoffset, BoundingBox bounds, boolean async) { this(barrel, signoffset); - if (bounds == null || bounds.area() > 64 ) { + if (boundsSeemBad(bounds)) { + if (async) { + this.bounds = null; + return; + } // If loading from old data, or block locations are missing, or other error, regenerate BoundingBox // This will only be done in those extreme cases. - P.p.log("Regenerating Barrel BoundingBox: " + (bounds == null ? "was null" : "area=" + bounds.area())); - Block broken = getBrokenBlock(true); - if (broken != null) { - barrel.remove(broken, null, true); - } + regenerateBounds(); } else { this.bounds = bounds; } @@ -79,6 +79,15 @@ public class BarrelBody { signoffset = 0; } + /** + * Quick check if the bounds are valid or seem corrupt + */ + public static boolean boundsSeemBad(BoundingBox bounds) { + if (bounds == null) return true; + long area = bounds.area(); + return area > 64 || area < 4; + } + /** * direction of the barrel from the spigot */ @@ -218,6 +227,21 @@ public class BarrelBody { return block; } + /** + * Regenerate the Barrel Bounds. + * + * @return true if successful, false if Barrel was broken and should be removed. + */ + public boolean regenerateBounds() { + P.p.log("Regenerating Barrel BoundingBox: " + (bounds == null ? "was null" : "area=" + bounds.area())); + Block broken = getBrokenBlock(true); + if (broken != null) { + barrel.remove(broken, null, true); + return false; + } + return true; + } + /** * returns null if Barrel is correctly placed; the block that is missing when not. *

the barrel needs to be formed correctly diff --git a/src/com/dre/brewery/Wakeup.java b/src/com/dre/brewery/Wakeup.java index 46e2906..75583c0 100644 --- a/src/com/dre/brewery/Wakeup.java +++ b/src/com/dre/brewery/Wakeup.java @@ -266,4 +266,8 @@ public class Wakeup { } } + public static void onUnload(String worldName) { + wakeups.removeIf(wakeup -> wakeup.loc.getWorld().getName().equals(worldName)); + } + } diff --git a/src/com/dre/brewery/filedata/BData.java b/src/com/dre/brewery/filedata/BData.java index 5307321..593f389 100644 --- a/src/com/dre/brewery/filedata/BData.java +++ b/src/com/dre/brewery/filedata/BData.java @@ -23,9 +23,12 @@ import java.io.DataInputStream; import java.io.File; import java.io.IOException; import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; public class BData { + public static AtomicInteger dataMutex = new AtomicInteger(0); // -1 = Saving, 0 = Free, >= 1 = Loading + // load all Data public static void readData() { @@ -136,7 +139,7 @@ public class BData { P.p.bad = 0; P.p.terr = 0; if (!Brew.noLegacy()) { - for (Brew brew : Brew.legacyPotions.values()) { + for (int i = Brew.legacyPotions.size(); i > 0; i--) { P.p.metricsForCreate(false); } } @@ -186,13 +189,27 @@ public class BData { } } - for (World world : P.p.getServer().getWorlds()) { - if (world.getName().startsWith("DXL_")) { - loadWorldData(BUtil.getDxlName(world.getName()), world, data); - } else { - loadWorldData(world.getUID().toString(), world, data); + + final FileConfiguration finalData = data; + final List worlds = P.p.getServer().getWorlds(); + P.p.getServer().getScheduler().runTaskAsynchronously(P.p, () -> { + if (!acquireDataLoadMutex()) return; // Tries for 60 sec + + try { + for (World world : worlds) { + P.p.log("World Init: " + world.getName()); + if (world.getName().startsWith("DXL_")) { + loadWorldData(BUtil.getDxlName(world.getName()), world, finalData); + } else { + loadWorldData(world.getUID().toString(), world, finalData); + } + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + releaseDataLoadMutex(); } - } + }); } else { P.p.log("No data.yml found, will create new one!"); @@ -266,6 +283,7 @@ public class BData { } // load Block locations of given world + // can be run async public static void loadWorldData(String uuid, World world, FileConfiguration data) { if (data == null) { @@ -278,6 +296,7 @@ public class BData { } // loading BCauldron + final Map initCauldrons = new HashMap<>(); if (data.contains("BCauldron." + uuid)) { ConfigurationSection section = data.getConfigurationSection("BCauldron." + uuid); for (String cauldron : section.getKeys(false)) { @@ -291,7 +310,7 @@ public class BData { BIngredients ingredients = loadCauldronIng(section, cauldron + ".ingredients"); int state = section.getInt(cauldron + ".state", 0); - new BCauldron(worldBlock, ingredients, state); + initCauldrons.put(worldBlock, new BCauldron(worldBlock, ingredients, state)); } else { P.p.errorLog("Incomplete Block-Data in data.yml: " + section.getCurrentPath() + "." + cauldron); } @@ -302,6 +321,8 @@ public class BData { } // loading Barrel + final List initBarrels = new ArrayList<>(); + final List initBadBarrels = new ArrayList<>(); if (data.contains("Barrel." + uuid)) { ConfigurationSection section = data.getConfigurationSection("Barrel." + uuid); for (String barrel : section.getKeys(false)) { @@ -346,16 +367,17 @@ public class BData { Barrel b; if (invSection != null) { - b = new Barrel(block, sign, box, invSection.getValues(true), time); + b = new Barrel(block, sign, box, invSection.getValues(true), time, true); } else { // Barrel has no inventory - b = new Barrel(block, sign, box, null, time); + b = new Barrel(block, sign, box, null, time, true); } - // In case Barrel Block locations were missing and could not be recreated: do not add the barrel - if (b.getBody().getBounds() != null) { - Barrel.barrels.add(b); + initBarrels.add(b); + } else { + // The Barrel Bounds need recreating, as they were missing or corrupt + initBadBarrels.add(b); } } else { @@ -368,6 +390,7 @@ public class BData { } // loading Wakeup + final List initWakeups = new ArrayList<>(); if (data.contains("Wakeup." + uuid)) { ConfigurationSection section = data.getConfigurationSection("Wakeup." + uuid); for (String wakeup : section.getKeys(false)) { @@ -384,7 +407,7 @@ public class BData { float yaw = NumberUtils.toFloat(splitted[4]); Location location = new Location(world, x, y, z, yaw, pitch); - Wakeup.wakeups.add(new Wakeup(location)); + initWakeups.add(new Wakeup(location)); } else { P.p.errorLog("Incomplete Location-Data in data.yml: " + section.getCurrentPath() + "." + wakeup); @@ -393,5 +416,51 @@ public class BData { } } + // Merge Loaded Data in Main Thred + P.p.getServer().getScheduler().runTask(P.p, () -> { + if (P.p.getServer().getWorld(world.getUID()) == null) { + return; + } + if (!initCauldrons.isEmpty()) { + BCauldron.bcauldrons.putAll(initCauldrons); + } + if (!initBarrels.isEmpty()) { + Barrel.barrels.addAll(initBarrels); + } + if (!initBadBarrels.isEmpty()) { + for (Barrel badBarrel : initBadBarrels) { + if (badBarrel.getBody().regenerateBounds()) { + Barrel.barrels.add(badBarrel); + } + // In case Barrel Block locations were missing and could not be recreated: do not add the barrel + } + + } + if (!initWakeups.isEmpty()) { + Wakeup.wakeups.addAll(initWakeups); + } + }); + } + + public static boolean acquireDataLoadMutex() { + int wait = 0; + // Increment the Data Mutex if it is not -1 + while (BData.dataMutex.updateAndGet(i -> i >= 0 ? i + 1 : i) <= 0) { + wait++; + if (wait > 60) { + P.p.errorLog("Could not load World Data, Mutex: " + BData.dataMutex.get()); + return false; + } + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + return false; + } + } + return true; + } + + public static void releaseDataLoadMutex() { + dataMutex.decrementAndGet(); } } diff --git a/src/com/dre/brewery/filedata/DataSave.java b/src/com/dre/brewery/filedata/DataSave.java index 5c3dfa3..9706b11 100644 --- a/src/com/dre/brewery/filedata/DataSave.java +++ b/src/com/dre/brewery/filedata/DataSave.java @@ -34,75 +34,87 @@ public class DataSave extends BukkitRunnable { @Override public void run() { long saveTime = System.nanoTime(); - FileConfiguration oldData; - if (read != null) { - if (!read.done) { - // Wait for async thread to load old data - if (System.currentTimeMillis() - time > 30000) { - P.p.errorLog("Old Data took too long to load!"); - cancel(); + // Mutex has been acquired in ReadOldData + try { + FileConfiguration oldData; + if (read != null) { + if (!read.done) { + // Wait for async thread to load old data + if (System.currentTimeMillis() - time > 50000) { + P.p.errorLog("Old Data took too long to load! Mutex: " + BData.dataMutex.get()); + try { + cancel(); + read.cancel(); + } catch (IllegalStateException ignored) { + } + running = null; + BData.dataMutex.set(0); + } return; } - return; + oldData = read.getData(); + } else { + oldData = new YamlConfiguration(); + } + try { + cancel(); + } catch (IllegalStateException ignored) { } - oldData = read.getData(); - } else { - oldData = new YamlConfiguration(); - } - try { - cancel(); - } catch (IllegalStateException ignored) { - } - FileConfiguration configFile = new YamlConfiguration(); + FileConfiguration configFile = new YamlConfiguration(); - configFile.set("installTime", Brew.installTime); - configFile.set("MCBarrelTime", MCBarrel.mcBarrelTime); + configFile.set("installTime", Brew.installTime); + configFile.set("MCBarrelTime", MCBarrel.mcBarrelTime); - Brew.writePrevSeeds(configFile); + Brew.writePrevSeeds(configFile); - List brewsCreated = new ArrayList<>(7); - brewsCreated.add(P.p.brewsCreated); - brewsCreated.add(P.p.brewsCreatedCmd); - brewsCreated.add(P.p.exc); - brewsCreated.add(P.p.good); - brewsCreated.add(P.p.norm); - brewsCreated.add(P.p.bad); - brewsCreated.add(P.p.terr); - configFile.set("brewsCreated", brewsCreated); - configFile.set("brewsCreatedH", brewsCreated.hashCode()); + List brewsCreated = new ArrayList<>(7); + brewsCreated.add(P.p.brewsCreated); + brewsCreated.add(P.p.brewsCreatedCmd); + brewsCreated.add(P.p.exc); + brewsCreated.add(P.p.good); + brewsCreated.add(P.p.norm); + brewsCreated.add(P.p.bad); + brewsCreated.add(P.p.terr); + configFile.set("brewsCreated", brewsCreated); + configFile.set("brewsCreatedH", brewsCreated.hashCode()); - if (!Brew.legacyPotions.isEmpty()) { - Brew.saveLegacy(configFile.createSection("Brew")); - } + if (!Brew.legacyPotions.isEmpty()) { + Brew.saveLegacy(configFile.createSection("Brew")); + } - if (!BCauldron.bcauldrons.isEmpty() || oldData.contains("BCauldron")) { - BCauldron.save(configFile.createSection("BCauldron"), oldData.getConfigurationSection("BCauldron")); - } + if (!BCauldron.bcauldrons.isEmpty() || oldData.contains("BCauldron")) { + BCauldron.save(configFile.createSection("BCauldron"), oldData.getConfigurationSection("BCauldron")); + } - if (!Barrel.barrels.isEmpty() || oldData.contains("Barrel")) { - Barrel.save(configFile.createSection("Barrel"), oldData.getConfigurationSection("Barrel")); - } + if (!Barrel.barrels.isEmpty() || oldData.contains("Barrel")) { + Barrel.save(configFile.createSection("Barrel"), oldData.getConfigurationSection("Barrel")); + } - if (!BPlayer.isEmpty()) { - BPlayer.save(configFile.createSection("Player")); - } + if (!BPlayer.isEmpty()) { + BPlayer.save(configFile.createSection("Player")); + } - if (!Wakeup.wakeups.isEmpty() || oldData.contains("Wakeup")) { - Wakeup.save(configFile.createSection("Wakeup"), oldData.getConfigurationSection("Wakeup")); - } + if (!Wakeup.wakeups.isEmpty() || oldData.contains("Wakeup")) { + Wakeup.save(configFile.createSection("Wakeup"), oldData.getConfigurationSection("Wakeup")); + } - saveWorldNames(configFile, oldData.getConfigurationSection("Worlds")); - configFile.set("Version", dataVersion); + saveWorldNames(configFile, oldData.getConfigurationSection("Worlds")); + configFile.set("Version", dataVersion); - collected = true; + collected = true; - P.p.debugLog("saving: " + ((System.nanoTime() - saveTime) / 1000000.0) + "ms"); + P.p.debugLog("saving: " + ((System.nanoTime() - saveTime) / 1000000.0) + "ms"); - if (P.p.isEnabled()) { - P.p.getServer().getScheduler().runTaskAsynchronously(P.p, new WriteData(configFile)); - } else { - new WriteData(configFile).run(); + if (P.p.isEnabled()) { + P.p.getServer().getScheduler().runTaskAsynchronously(P.p, new WriteData(configFile)); + } else { + new WriteData(configFile).run(); + } + // Mutex will be released in WriteData + } catch (Exception e) { + e.printStackTrace(); + BData.dataMutex.set(0); } } diff --git a/src/com/dre/brewery/filedata/ReadOldData.java b/src/com/dre/brewery/filedata/ReadOldData.java index e24e065..d25383a 100644 --- a/src/com/dre/brewery/filedata/ReadOldData.java +++ b/src/com/dre/brewery/filedata/ReadOldData.java @@ -17,6 +17,22 @@ public class ReadOldData extends BukkitRunnable { @SuppressWarnings("ResultOfMethodCallIgnored") @Override public void run() { + int wait = 0; + // Set the Data Mutex to -1 if it is 0=Free + while (!BData.dataMutex.compareAndSet(0, -1)) { + if (wait > 300) { + P.p.errorLog("Loading Process active for too long while trying to save! Mutex: " + BData.dataMutex.get()); + return; + } + wait++; + try { + Thread.sleep(100); + } catch (InterruptedException e) { + return; + } + } + + File datafile = new File(P.p.getDataFolder(), "data.yml"); if (!datafile.exists()) { data = new YamlConfiguration(); diff --git a/src/com/dre/brewery/filedata/WriteData.java b/src/com/dre/brewery/filedata/WriteData.java index a1f5a44..f8e7755 100644 --- a/src/com/dre/brewery/filedata/WriteData.java +++ b/src/com/dre/brewery/filedata/WriteData.java @@ -25,11 +25,12 @@ public class WriteData implements Runnable { try { data.save(datafile); - } catch (IOException e) { + } catch (Exception e) { e.printStackTrace(); } DataSave.lastSave = 1; DataSave.running = null; + BData.dataMutex.set(0); } } diff --git a/src/com/dre/brewery/listeners/WorldListener.java b/src/com/dre/brewery/listeners/WorldListener.java index b19b03c..b716220 100644 --- a/src/com/dre/brewery/listeners/WorldListener.java +++ b/src/com/dre/brewery/listeners/WorldListener.java @@ -2,6 +2,8 @@ package com.dre.brewery.listeners; import com.dre.brewery.BCauldron; import com.dre.brewery.Barrel; +import com.dre.brewery.P; +import com.dre.brewery.Wakeup; import com.dre.brewery.utility.BUtil; import com.dre.brewery.filedata.BData; import com.dre.brewery.filedata.DataSave; @@ -16,20 +18,35 @@ public class WorldListener implements Listener { @EventHandler public void onWorldLoad(WorldLoadEvent event) { - World world = event.getWorld(); + final World world = event.getWorld(); + P.p.log("World Load: " + event.getWorld().getName()); + + P.p.getServer().getScheduler().runTaskAsynchronously(P.p, () -> { + if (!BData.acquireDataLoadMutex()) return; // Tries for 60 sec + + try { + if (world.getName().startsWith("DXL_")) { + BData.loadWorldData(BUtil.getDxlName(world.getName()), world, null); + } else { + BData.loadWorldData(world.getUID().toString(), world, null); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + BData.releaseDataLoadMutex(); + } + + }); - if (world.getName().startsWith("DXL_")) { - BData.loadWorldData(BUtil.getDxlName(world.getName()), world, null); - } else { - BData.loadWorldData(world.getUID().toString(), world, null); - } } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onWorldUnload(WorldUnloadEvent event) { DataSave.save(true); - Barrel.onUnload(event.getWorld().getName()); - BCauldron.onUnload(event.getWorld().getName()); + String worldName = event.getWorld().getName(); + Barrel.onUnload(worldName); + BCauldron.onUnload(worldName); + Wakeup.onUnload(worldName); } }