Fixes bug when loaded a large number of islands.

Also attempts to fix duplicate islands during loading.

See https://github.com/BentoBoxWorld/BSkyBlock/issues/144
This commit is contained in:
tastybento 2019-06-19 20:44:59 -07:00
parent 61d9d2810b
commit 4b6ec575be
5 changed files with 139 additions and 13 deletions

View File

@ -50,14 +50,23 @@ public class AdminTrashCommand extends CompositeCommand {
}
return false;
} else {
user.sendMessage("commands.admin.trash.title");
for (int i = 0; i < islands.size(); i++) {
user.sendMessage("commands.admin.trash.count", TextVariables.NUMBER, String.valueOf(i+1));
islands.get(i).showInfo(user);
if (targetUUID == null) {
showTrash(user, islands);
} else {
getIslands().getQuarantineCache().values().forEach(v -> showTrash(user, v));
}
user.sendMessage("commands.admin.trash.use-switch", TextVariables.LABEL, getTopLabel());
user.sendMessage("commands.admin.trash.use-emptytrash", TextVariables.LABEL, getTopLabel());
return true;
}
}
private void showTrash(User user, List<Island> islands) {
user.sendMessage("commands.admin.trash.title");
for (int i = 0; i < islands.size(); i++) {
user.sendMessage("commands.admin.trash.count", TextVariables.NUMBER, String.valueOf(i+1));
islands.get(i).showInfo(user);
}
user.sendMessage("commands.admin.trash.use-switch", TextVariables.LABEL, getTopLabel());
user.sendMessage("commands.admin.trash.use-emptytrash", TextVariables.LABEL, getTopLabel());
}
}

View File

@ -55,9 +55,16 @@ public class JSONDatabaseHandler<T> extends AbstractJSONDatabaseHandler<T> {
// Load each object from the file system, filtered, non-null
for (File file: Objects.requireNonNull(tableFolder.listFiles((dir, name) -> name.toLowerCase(Locale.ENGLISH).endsWith(JSON)))) {
try (FileReader reader = new FileReader(file)){
list.add(getGson().fromJson(reader, dataObject));
T object = getGson().fromJson(reader, dataObject);
if (object == null) {
reader.close();
throw new IOException("JSON file created a null object: " + file.getPath());
}
list.add(object);
reader.close();
} catch (FileNotFoundException e) {
plugin.logError("Could not load file '" + file.getName() + "': File not found.");
} catch (Exception e) {
plugin.logError("Could not load objects " + file.getName() + " " + e.getMessage());
}

View File

@ -158,6 +158,33 @@ public class Island implements DataObject {
this.maxEverProtectionRange = protectionRange;
}
/**
* Clones an island object
* @param island - island to clone
*/
public Island(Island island) {
this.center = island.getCenter().clone();
this.createdDate = island.createdDate;
this.deleted = island.deleted;
this.doNotLoad = island.doNotLoad;
this.flags.putAll(island.getFlags());
this.gameMode = island.gameMode;
this.history.addAll(island.history);
this.levelHandicap = island.levelHandicap;
this.maxEverProtectionRange = island.maxEverProtectionRange;
this.members.putAll(island.members);
this.name = island.name;
this.owner = island.owner;
this.protectionRange = island.protectionRange;
this.purgeProtected = island.purgeProtected;
this.range = island.range;
this.spawn = island.spawn;
island.spawnPoint.forEach((k,v) -> this.spawnPoint.put(k, v.clone()));
this.uniqueId = island.uniqueId;
this.updatedDate = island.updatedDate;
this.world = island.world;
}
/**
* Adds a team member. If player is on banned list, they will be removed from it.
* @param playerUUID - the player's UUID
@ -1019,4 +1046,17 @@ public class Island implements DataObject {
iwm.getEndWorld(getWorld()) != null &&
!getCenter().toVector().toLocation(iwm.getEndWorld(getWorld())).getBlock().getType().equals(Material.AIR);
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "Island [deleted=" + deleted + ", uniqueId=" + uniqueId + ", center=" + center + ", range=" + range
+ ", protectionRange=" + protectionRange + ", maxEverProtectionRange=" + maxEverProtectionRange
+ ", world=" + world + ", gameMode=" + gameMode + ", name=" + name + ", createdDate=" + createdDate
+ ", updatedDate=" + updatedDate + ", owner=" + owner + ", members=" + members + ", spawn=" + spawn
+ ", purgeProtected=" + purgeProtected + ", flags=" + flags + ", history=" + history
+ ", levelHandicap=" + levelHandicap + ", spawnPoint=" + spawnPoint + ", doNotLoad=" + doNotLoad + "]";
}
}

View File

@ -4,8 +4,10 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
@ -744,12 +746,18 @@ public class IslandsManager {
/**
* Clear and reload all islands from database
*/
public void load(){
public void load() {
islandCache.clear();
quarantineCache.clear();
List<Island> toQuarantine = new ArrayList<>();
int owned = 0;
int unowned = 0;
// Attempt to load islands
handler.loadObjects().forEach(island -> {
for (Island island : handler.loadObjects()) {
if (island == null) {
plugin.logWarning("Null island when loading...");
continue;
}
if (island.isDeleted()) {
// These will be deleted later
deletedIslands.add(island.getUniqueId());
@ -765,6 +773,11 @@ public class IslandsManager {
// Add to quarantine cache
island.setDoNotLoad(true);
quarantineCache.computeIfAbsent(island.getOwner(), k -> new ArrayList<>()).add(island);
if (island.getOwner() == null) {
unowned++;
} else {
owned++;
}
} else if (island.isSpawn()) {
// Success, set spawn if this is the spawn island.
this.setSpawn(island);
@ -779,10 +792,34 @@ public class IslandsManager {
if (island.getGameMode() == null) {
island.setGameMode(plugin.getIWM().getAddon(island.getWorld()).map(gm -> gm.getDescription().getName()).orElse(""));
}
});
}
if (!toQuarantine.isEmpty()) {
plugin.logError(toQuarantine.size() + " islands could not be loaded successfully; moving to trash bin.");
plugin.logError(unowned + " are unowned, " + owned + " are owned.");
toQuarantine.forEach(handler::saveObject);
// Check if there are any islands with duplicate islands
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
Set<UUID> duplicatedUUIDRemovedSet = new HashSet<>();
Set<UUID> duplicated = islandCache.getIslands().stream()
.map(Island::getOwner)
.filter(Objects::nonNull)
.filter(n -> !duplicatedUUIDRemovedSet.add(n))
.collect(Collectors.toSet());
if (duplicated.size() > 0) {
plugin.logError("**** Owners that have more than one island = " + duplicated.size());
for (UUID uuid : duplicated) {
Set<Island> set = islandCache.getIslands().stream().filter(i -> uuid.equals(i.getOwner())).collect(Collectors.toSet());
plugin.logError(plugin.getPlayers().getName(uuid) + "(" + uuid.toString() + ") has " + set.size() + " islands:");
set.forEach(i -> {
plugin.logError("Island at " + i.getCenter());
plugin.logError("Island unique ID = " + i.getUniqueId());
});
plugin.logError("You should find out which island is real and delete the uniqueID from the database for the bogus one.");
plugin.logError("");
}
}
});
}
}

View File

@ -13,6 +13,7 @@ import world.bentobox.bentobox.database.objects.Island;
*/
class IslandGrid {
private TreeMap<Integer, TreeMap<Integer, Island>> grid = new TreeMap<>();
private BentoBox plugin = BentoBox.getInstance();
/**
* Adds island to grid
@ -23,9 +24,41 @@ class IslandGrid {
if (grid.containsKey(island.getMinX())) {
TreeMap<Integer, Island> zEntry = grid.get(island.getMinX());
if (zEntry.containsKey(island.getMinZ())) {
BentoBox.getInstance().logError("Cannot add island to grid because there is an overlapping");
BentoBox.getInstance().logError("island already registered at this location: " + island.getCenter());
BentoBox.getInstance().logError("This is most likely caused by island distances changing mid-game, or an old database file");
// There is an overlap or duplicate
plugin.logError("Cannot load island. Overlapping: " + island.getUniqueId());
plugin.logError("Location: " + island.getCenter());
// Get the previously loaded island
Island firstLoaded = zEntry.get(island.getMinZ());
if (firstLoaded.getOwner() == null && island.getOwner() != null) {
// This looks fishy. We prefer to load islands that have an owner. Swap the two
plugin.logError("Duplicate island has an owner, so using that one. " + island.getOwner());
Island clone = new Island(firstLoaded);
firstLoaded = new Island(island);
zEntry.put(island.getMinZ(), firstLoaded);
island = new Island(clone);
} else if (firstLoaded.getOwner() != null && island.getOwner() != null) {
// Check if the owners are the same - this is a true duplicate
if (firstLoaded.getOwner().equals(island.getOwner())) {
// Find out which one is the original
if (firstLoaded.getCreatedDate() > island.getCreatedDate()) {
plugin.logError("Same owner duplicate. Swaping based on creation date.");
// FirstLoaded is the newer
Island clone = new Island(firstLoaded);
firstLoaded = new Island(island);
zEntry.put(island.getMinZ(), firstLoaded);
island = new Island(clone);
} else {
plugin.logError("Same owner duplicate.");
}
} else {
plugin.logError("Duplicate but different owner. Keeping first loaded.");
plugin.logError("This is serious!");
plugin.logError("1st loaded ID: " + firstLoaded.getUniqueId());
plugin.logError("1st loaded owner: " + firstLoaded.getOwner());
plugin.logError("2nd loaded ID: " + island.getUniqueId());
plugin.logError("2nd loaded owner: " + island.getOwner());
}
}
return false;
} else {
// Add island