mirror of
https://github.com/DieReicheErethons/Brewery.git
synced 2025-01-06 18:47:44 +01:00
Implemented Async World-Data Loading
Uses Mutex to assure no concurrent loading and saving Merges Loaded data in Main Thread
This commit is contained in:
parent
f4bfdfa595
commit
cae2b9dfff
@ -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);
|
||||
|
@ -59,6 +59,15 @@ public class Barrel implements InventoryHolder {
|
||||
* load from file
|
||||
*/
|
||||
public Barrel(Block spigot, byte sign, BoundingBox bounds, Map<String, Object> items, float time) {
|
||||
this(spigot, sign, bounds, items, time, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load from File
|
||||
* <p>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<String, Object> 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() {
|
||||
|
@ -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.
|
||||
* <p>the barrel needs to be formed correctly
|
||||
|
@ -266,4 +266,8 @@ public class Wakeup {
|
||||
}
|
||||
}
|
||||
|
||||
public static void onUnload(String worldName) {
|
||||
wakeups.removeIf(wakeup -> wakeup.loc.getWorld().getName().equals(worldName));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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<World> 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<Block, BCauldron> 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<Barrel> initBarrels = new ArrayList<>();
|
||||
final List<Barrel> 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<Wakeup> 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();
|
||||
}
|
||||
}
|
||||
|
@ -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<Integer> 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<Integer> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user