From ec843cde03aac693186f75638b2ebdf865128dd2 Mon Sep 17 00:00:00 2001 From: Guntram Blohm Date: Thu, 21 Feb 2019 20:39:17 +0100 Subject: [PATCH 1/7] test async fill --- pom.xml | 11 +-- .../com/wimbli/WorldBorder/WorldFillTask.java | 77 ++++++++++++++++--- 2 files changed, 72 insertions(+), 16 deletions(-) diff --git a/pom.xml b/pom.xml index 5718d06..a9a033e 100644 --- a/pom.xml +++ b/pom.xml @@ -17,8 +17,8 @@ - spigot-repo - https://hub.spigotmc.org/nexus/content/groups/public/ + papermc + https://papermc.io/repo/repository/maven-public/ dynmap-repo @@ -29,9 +29,10 @@ - org.spigotmc - spigot-api - 1.13.1-R0.1-SNAPSHOT + com.destroystokyo.paper + paper-api + 1.13.2-R0.1-SNAPSHOT + provided diff --git a/src/main/java/com/wimbli/WorldBorder/WorldFillTask.java b/src/main/java/com/wimbli/WorldBorder/WorldFillTask.java index 6e48066..5b6e64e 100644 --- a/src/main/java/com/wimbli/WorldBorder/WorldFillTask.java +++ b/src/main/java/com/wimbli/WorldBorder/WorldFillTask.java @@ -13,6 +13,8 @@ import org.bukkit.World; import com.wimbli.WorldBorder.Events.WorldBorderFillFinishedEvent; import com.wimbli.WorldBorder.Events.WorldBorderFillStartEvent; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; public class WorldFillTask implements Runnable @@ -57,7 +59,9 @@ public class WorldFillTask implements Runnable private transient int reportTarget = 0; private transient int reportTotal = 0; private transient int reportNum = 0; - + + private transient boolean canUsePaperAPI = false; + private Set> pendingChunks; public WorldFillTask(Server theServer, Player player, String worldName, int fillDistance, int chunksPerRun, int tickFrequency, boolean forceLoad) { @@ -94,6 +98,11 @@ public class WorldFillTask implements Runnable this.stop(); return; } + + canUsePaperAPI = checkForPaperAPI(); + if (canUsePaperAPI) { + pendingChunks = new HashSet<>(); + } this.border.setRadiusX(border.getRadiusX() + fillDistance); this.border.setRadiusZ(border.getRadiusZ() + fillDistance); @@ -131,6 +140,15 @@ public class WorldFillTask implements Runnable if (ID == -1) this.stop(); this.taskID = ID; } + + private boolean checkForPaperAPI() { + try { + Class.forName("com.destroystokyo.paper.PaperConfig"); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } @Override @@ -159,8 +177,34 @@ public class WorldFillTask implements Runnable // this is set so it only does one iteration at a time, no matter how frequently the timer fires readyToGo = false; // and this is tracked to keep one iteration from dragging on too long and possibly choking the system if the user specified a really high frequency - long loopStartTime = Config.Now(); + + if (canUsePaperAPI) { + Set> newPendingChunks = new HashSet<>(); + for (CompletableFuture cf: pendingChunks) { + if (cf.isDone()) { + try { + Chunk chunk=cf.get(); + // System.out.println(chunk); + if (chunk==null) + continue; + CoordXZ xz = new CoordXZ(chunk.getX(), chunk.getZ()); + worldData.chunkExistsNow(xz.x, xz.z); + storedChunks.add(xz); + } catch (InterruptedException | ExecutionException ex) { + Config.log(ex.getMessage()); + } + } else { + newPendingChunks.add(cf); + } + } + pendingChunks=newPendingChunks; + if (pendingChunks.size() > chunksPerRun*2) { + readyToGo = true; + return; + } + } + long loopStartTime = Config.Now(); for (int loop = 0; loop < chunksPerRun; loop++) { // in case the task has been paused while we're repeating... @@ -200,26 +244,37 @@ public class WorldFillTask implements Runnable } // load the target chunk and generate it if necessary - world.loadChunk(x, z, true); - worldData.chunkExistsNow(x, z); + if (canUsePaperAPI) { + pendingChunks.add(world.getChunkAtAsync(x, z, true)); + } else { + world.loadChunk(x, z, true); + worldData.chunkExistsNow(x, z); + storedChunks.add(new CoordXZ(x, z)); + } // There need to be enough nearby chunks loaded to make the server populate a chunk with trees, snow, etc. // So, we keep the last few chunks loaded, and need to also temporarily load an extra inside chunk (neighbor closest to center of map) int popX = !isZLeg ? x : (x + (isNeg ? -1 : 1)); int popZ = isZLeg ? z : (z + (!isNeg ? -1 : 1)); - world.loadChunk(popX, popZ, false); + + if (canUsePaperAPI) { + pendingChunks.add(world.getChunkAtAsync(popX, popZ, false)); + } else { + world.loadChunk(popX, popZ, false); + storedChunks.add(new CoordXZ(popX, popZ)); + } // make sure the previous chunk in our spiral is loaded as well (might have already existed and been skipped over) if (!storedChunks.contains(lastChunk) && !originalChunks.contains(lastChunk)) { - world.loadChunk(lastChunk.x, lastChunk.z, false); - storedChunks.add(new CoordXZ(lastChunk.x, lastChunk.z)); + if (canUsePaperAPI) { + pendingChunks.add(world.getChunkAtAsync(lastChunk.x, lastChunk.z, false)); + } else { + world.loadChunk(lastChunk.x, lastChunk.z, false); + storedChunks.add(new CoordXZ(lastChunk.x, lastChunk.z)); + } } - // Store the coordinates of these latest 2 chunks we just loaded, so we can unload them after a bit... - storedChunks.add(new CoordXZ(popX, popZ)); - storedChunks.add(new CoordXZ(x, z)); - // If enough stored chunks are buffered in, go ahead and unload the oldest to free up memory while (storedChunks.size() > 8) { From 12bb4b1da924a6dd21495c44c6fb9de702e90a3f Mon Sep 17 00:00:00 2001 From: Guntram Blohm Date: Wed, 20 Mar 2019 19:02:55 +0100 Subject: [PATCH 2/7] Use async Chunk generation, if possible, using PaperLib This routes all world generation requests through PaperLib, which will generate Chunks asynchronously if the server allows it (Paper does, Spigot doesn't). This means changes to which chunks are still needed, and which can be unloaded, as well; the code keeps a list of Chunks that are needed for others, and will unload them only when the target chunk has been generated. Unloads by the server itself get prevented while the chunk is needed; else the server could decide on a tick that chunk has no players nearby and needs to be unloaded. --- pom.xml | 57 +++- .../com/wimbli/WorldBorder/WBListener.java | 22 ++ .../com/wimbli/WorldBorder/WorldFillTask.java | 258 +++++++++++------- 3 files changed, 237 insertions(+), 100 deletions(-) diff --git a/pom.xml b/pom.xml index a9a033e..e55ff5a 100644 --- a/pom.xml +++ b/pom.xml @@ -17,8 +17,8 @@ - papermc - https://papermc.io/repo/repository/maven-public/ + papermc + https://papermc.io/repo/repository/maven-public/ dynmap-repo @@ -29,10 +29,10 @@ - com.destroystokyo.paper - paper-api - 1.13.2-R0.1-SNAPSHOT - provided + com.destroystokyo.paper + paper-api + 1.13.2-R0.1-SNAPSHOT + provided @@ -46,6 +46,12 @@ dynmap-api 2.5 + + io.papermc + paperlib + 1.0.2 + compile + @@ -61,6 +67,43 @@ 1.8 - + + org.apache.maven.plugins + maven-shade-plugin + 3.1.1 + + ${project.build.directory}/dependency-reduced-pom.xml + + + io.papermc.lib + com.wimbli.WorldBorder.paperlib + + + + + + package + + shade + + + + + commons-lang:commons-lang + com.googlecode.json-simple:json-simple + junit:junit + org.hamcrest:hamcrest-core + com.google.guava:guava + com.google.code.gson:gson + org.yaml:snakeyaml + org.bukkit:bukkit + us.dynmap:dynmap-api + + + + + + + diff --git a/src/main/java/com/wimbli/WorldBorder/WBListener.java b/src/main/java/com/wimbli/WorldBorder/WBListener.java index ad29191..6797a18 100644 --- a/src/main/java/com/wimbli/WorldBorder/WBListener.java +++ b/src/main/java/com/wimbli/WorldBorder/WBListener.java @@ -1,5 +1,6 @@ package com.wimbli.WorldBorder; +import org.bukkit.Chunk; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; @@ -7,6 +8,7 @@ import org.bukkit.event.player.PlayerTeleportEvent; import org.bukkit.event.player.PlayerPortalEvent; import org.bukkit.event.world.ChunkLoadEvent; import org.bukkit.Location; +import org.bukkit.event.world.ChunkUnloadEvent; public class WBListener implements Listener @@ -68,4 +70,24 @@ public class WBListener implements Listener Config.logWarn("Border-checking task was not running! Something on your server apparently killed it. It will now be restarted."); Config.StartBorderTimer(); } + + /* + * Check if there is a fill task running, and if yes, if it's for the + * world that the unload event refers to and if the chunk should be + * kept in memory because generation still needs it. + */ + @EventHandler + public void onChunkUnload(ChunkUnloadEvent e) + { + if (Config.fillTask!=null) + { + Chunk chunk=e.getChunk(); + if (e.getWorld() == Config.fillTask.getWorld() + && Config.fillTask.chunkOnUnloadPreventionList(chunk.getX(), chunk.getZ())) + { + e.setCancelled(true); + } + } + } + } diff --git a/src/main/java/com/wimbli/WorldBorder/WorldFillTask.java b/src/main/java/com/wimbli/WorldBorder/WorldFillTask.java index 49eab92..328db9c 100644 --- a/src/main/java/com/wimbli/WorldBorder/WorldFillTask.java +++ b/src/main/java/com/wimbli/WorldBorder/WorldFillTask.java @@ -1,8 +1,6 @@ package com.wimbli.WorldBorder; import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; import java.util.Set; import org.bukkit.Bukkit; @@ -13,8 +11,10 @@ import org.bukkit.World; import com.wimbli.WorldBorder.Events.WorldBorderFillFinishedEvent; import com.wimbli.WorldBorder.Events.WorldBorderFillStartEvent; +import io.papermc.lib.PaperLib; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; public class WorldFillTask implements Runnable @@ -49,8 +49,6 @@ public class WorldFillTask implements Runnable private transient int length = -1; private transient int current = 0; private transient boolean insideBorder = true; - private List storedChunks = new LinkedList<>(); - private Set originalChunks = new HashSet<>(); private transient CoordXZ lastChunk = new CoordXZ(0, 0); // for reporting progress back to user occasionally @@ -60,8 +58,51 @@ public class WorldFillTask implements Runnable private transient int reportTotal = 0; private transient int reportNum = 0; - private transient boolean canUsePaperAPI = false; - private Set> pendingChunks; + // A map that holds to-be-loaded chunks, and their coordinates + private transient Map, CoordXZ> pendingChunks; + + // and a set of "Chunk a needed for Chunk b" dependencies, which + // unfortunately can't be a Map as a chunk might be needed for + // several others. + private transient Set preventUnload; + + private class UnloadDependency + { + int neededX, neededZ; + int forX, forZ; + + UnloadDependency(int neededX, int neededZ, int forX, int forZ) + { + this.neededX=neededX; + this.neededZ=neededZ; + this.forX=forX; + this.forZ=forZ; + } + + @Override + public boolean equals(Object other) + { + if (other == null || !(other instanceof UnloadDependency)) + { + return false; + } + return this.neededX == ((UnloadDependency) other).neededX + && this.neededZ == ((UnloadDependency) other).neededZ + && this.forX == ((UnloadDependency) other).forX + && this.forZ == ((UnloadDependency) other).forZ; + } + + @Override + public int hashCode() + { + int hash = 7; + hash = 79 * hash + this.neededX; + hash = 79 * hash + this.neededZ; + hash = 79 * hash + this.forX; + hash = 79 * hash + this.forZ; + return hash; + } + } public WorldFillTask(Server theServer, Player player, String worldName, int fillDistance, int chunksPerRun, int tickFrequency, boolean forceLoad) { @@ -99,10 +140,8 @@ public class WorldFillTask implements Runnable return; } - canUsePaperAPI = checkForPaperAPI(); - if (canUsePaperAPI) { - pendingChunks = new HashSet<>(); - } + pendingChunks = new HashMap<>(); + preventUnload = new HashSet<>(); this.border.setRadiusX(border.getRadiusX() + fillDistance); this.border.setRadiusZ(border.getRadiusZ() + fillDistance); @@ -118,17 +157,10 @@ public class WorldFillTask implements Runnable //this.reportTarget = (this.border.getShape()) ? ((int) Math.ceil(chunkWidthX * chunkWidthZ / 4 * Math.PI + 2 * chunkWidthX)) : (chunkWidthX * chunkWidthZ); // Area of the ellipse just to be safe area of the rectangle - - // keep track of the chunks which are already loaded when the task starts, to not unload them - Chunk[] originals = world.getLoadedChunks(); - for (Chunk original : originals) - { - originalChunks.add(new CoordXZ(original.getX(), original.getZ())); - } - this.readyToGo = true; Bukkit.getServer().getPluginManager().callEvent(new WorldBorderFillStartEvent(this)); } + // for backwards compatibility public WorldFillTask(Server theServer, Player player, String worldName, int fillDistance, int chunksPerRun, int tickFrequency) { @@ -140,16 +172,6 @@ public class WorldFillTask implements Runnable if (ID == -1) this.stop(); this.taskID = ID; } - - private boolean checkForPaperAPI() { - try { - Class.forName("com.destroystokyo.paper.PaperConfig"); - return true; - } catch (ClassNotFoundException e) { - return false; - } - } - @Override public void run() @@ -177,39 +199,88 @@ public class WorldFillTask implements Runnable // this is set so it only does one iteration at a time, no matter how frequently the timer fires readyToGo = false; // and this is tracked to keep one iteration from dragging on too long and possibly choking the system if the user specified a really high frequency + long loopStartTime = Config.Now(); + + // Process async results from last time. We don't make a difference + // whether they were really async, or sync. - if (canUsePaperAPI) { - Set> newPendingChunks = new HashSet<>(); - for (CompletableFuture cf: pendingChunks) { - if (cf.isDone()) { - try { - Chunk chunk=cf.get(); - // System.out.println(chunk); - if (chunk==null) - continue; - CoordXZ xz = new CoordXZ(chunk.getX(), chunk.getZ()); - worldData.chunkExistsNow(xz.x, xz.z); - storedChunks.add(xz); - } catch (InterruptedException | ExecutionException ex) { - Config.log(ex.getMessage()); - } - } else { - newPendingChunks.add(cf); - } + // First, Check which chunk generations have been finished. + // Mark those chunks as existing and unloadable, and remove + // them from the pending set. + int chunksProcessedLastTick = 0; + Map, CoordXZ> newPendingChunks = new HashMap<>(); + Set chunksToUnload = new HashSet<>(); + for (CompletableFuture cf: pendingChunks.keySet()) + { + if (cf.isDone()) + { + ++chunksProcessedLastTick; + // If cf.get() returned the chunk reliably, pendingChunks could + // be a set and we wouldn't have to map CFs to coords ... + CoordXZ xz=pendingChunks.get(cf); + worldData.chunkExistsNow(xz.x, xz.z); + chunksToUnload.add(xz); } - pendingChunks=newPendingChunks; - if (pendingChunks.size() > chunksPerRun*2) { - readyToGo = true; - return; + else + { + newPendingChunks.put(cf, pendingChunks.get(cf)); + } + } + pendingChunks = newPendingChunks; + + // Next, check which chunks had been loaded because a to-be-generated + // chunk needed them, and don't have to remain in memory any more. + Set newPreventUnload = new HashSet<>(); + for (UnloadDependency dependency: preventUnload) + { + if (worldData.doesChunkExist(dependency.forX, dependency.forZ)) + { + chunksToUnload.add(new CoordXZ(dependency.neededX, dependency.neededZ)); + } + else + { + newPreventUnload.add(dependency); + } + } + preventUnload = newPreventUnload; + + // Unload all chunks that aren't needed anymore. NB a chunk could have + // been needed for two different others, or been generated and needed + // for one other chunk, so it might be in the unload set wrongly. + // The ChunkUnloadListener checks this anyway, but it doesn't hurt to + // save a few µs by not even requesting the unload. + + for (CoordXZ unload: chunksToUnload) + { + if (!chunkOnUnloadPreventionList(unload.x, unload.z)) + { + world.unloadChunkRequest(unload.x, unload.z); } } - long loopStartTime = Config.Now(); - for (int loop = 0; loop < chunksPerRun; loop++) + // Put some damper on chunksPerRun. We don't want the queue to be too + // full; only fill it to a bit more than what we can + // process per tick. This ensures the background task can run at + // full speed and we recover from a temporary drop in generation rate, + // but doesn't push user-induced chunk generations behind a very + // long queue of fill-generations. + + int chunksToProcess = chunksPerRun; + if (chunksProcessedLastTick > 0 || pendingChunks.size() > 0) + { + // Note we generally queue 3 chunks, so real numbers are 1/3 of chunksProcessedLastTick and pendingchunks.size + int chunksExpectedToGetProcessed = (chunksProcessedLastTick - pendingChunks.size()) / 3 + 3; + if (chunksExpectedToGetProcessed < chunksToProcess) + chunksToProcess = chunksExpectedToGetProcessed; + } + + for (int loop = 0; loop < chunksToProcess; loop++) { // in case the task has been paused while we're repeating... if (paused || pausedForMemory) + { return; + } long now = Config.Now(); @@ -228,7 +299,9 @@ public class WorldFillTask implements Runnable while (!border.insideBorder(CoordXZ.chunkToBlock(x) + 8, CoordXZ.chunkToBlock(z) + 8)) { if (!moveToNext()) + { return; + } } insideBorder = true; @@ -241,7 +314,9 @@ public class WorldFillTask implements Runnable rLoop++; insideBorder = true; if (!moveToNext()) + { return; + } if (rLoop > 255) { // only skim through max 256 chunks (~8 region files) at a time here, to allow process to take a break if needed readyToGo = true; @@ -250,51 +325,26 @@ public class WorldFillTask implements Runnable } } - // load the target chunk and generate it if necessary - if (canUsePaperAPI) { - pendingChunks.add(world.getChunkAtAsync(x, z, true)); - } else { - world.loadChunk(x, z, true); - worldData.chunkExistsNow(x, z); - storedChunks.add(new CoordXZ(x, z)); - } + pendingChunks.put(PaperLib.getChunkAtAsync(world, x, z, true), new CoordXZ(x, z)); // There need to be enough nearby chunks loaded to make the server populate a chunk with trees, snow, etc. // So, we keep the last few chunks loaded, and need to also temporarily load an extra inside chunk (neighbor closest to center of map) int popX = !isZLeg ? x : (x + (isNeg ? -1 : 1)); int popZ = isZLeg ? z : (z + (!isNeg ? -1 : 1)); + + pendingChunks.put(PaperLib.getChunkAtAsync(world, popX, popZ, false), new CoordXZ(popX, popZ)); + preventUnload.add(new UnloadDependency(popX, popZ, x, z)); - if (canUsePaperAPI) { - pendingChunks.add(world.getChunkAtAsync(popX, popZ, false)); - } else { - world.loadChunk(popX, popZ, false); - storedChunks.add(new CoordXZ(popX, popZ)); - } - // make sure the previous chunk in our spiral is loaded as well (might have already existed and been skipped over) - if (!storedChunks.contains(lastChunk) && !originalChunks.contains(lastChunk)) - { - if (canUsePaperAPI) { - pendingChunks.add(world.getChunkAtAsync(lastChunk.x, lastChunk.z, false)); - } else { - world.loadChunk(lastChunk.x, lastChunk.z, false); - storedChunks.add(new CoordXZ(lastChunk.x, lastChunk.z)); - } - } - - // If enough stored chunks are buffered in, go ahead and unload the oldest to free up memory - while (storedChunks.size() > 8) - { - CoordXZ coord = storedChunks.remove(0); - if (!originalChunks.contains(coord)) - world.unloadChunkRequest(coord.x, coord.z); - } + pendingChunks.put(PaperLib.getChunkAtAsync(world, lastChunk.x, lastChunk.z, false), new CoordXZ(lastChunk.x, lastChunk.z)); // <-- new CoordXZ as lastChunk isn't immutable + preventUnload.add(new UnloadDependency(lastChunk.x, lastChunk.z, x, z)); // move on to next chunk if (!moveToNext()) + { return; + } } - // ready for the next iteration to run readyToGo = true; } @@ -401,13 +451,15 @@ public class WorldFillTask implements Runnable server.getScheduler().cancelTask(taskID); server = null; - // go ahead and unload any chunks we still have loaded - while(!storedChunks.isEmpty()) - { - CoordXZ coord = storedChunks.remove(0); - if (!originalChunks.contains(coord)) - world.unloadChunkRequest(coord.x, coord.z); - } + // go ahead and unload any chunks we still have loaded + // Set preventUnload to emptry first so the ChunkUnloadEvent Listener + // doesn't get in our way + Set tempPreventUnload = preventUnload; + preventUnload = null; + for (UnloadDependency entry: tempPreventUnload) + { + world.unloadChunkRequest(entry.neededX, entry.neededZ); + } } // is this task still valid/workable? @@ -442,6 +494,26 @@ public class WorldFillTask implements Runnable { return this.paused || this.pausedForMemory; } + + public boolean chunkOnUnloadPreventionList(int x, int z) + { + if (preventUnload != null) + { + for (UnloadDependency entry: preventUnload) + { + if (entry.neededX == x && entry.neededZ == z) + { + return true; + } + } + } + return false; + } + + public World getWorld() + { + return world; + } // let the user know how things are coming along private void reportProgress() From 21adadd7e6a8d1e9a2725eb6855a22bb05d8f9fc Mon Sep 17 00:00:00 2001 From: Guntram Blohm Date: Wed, 20 Mar 2019 19:26:48 +0100 Subject: [PATCH 3/7] Use spigot API instead of Paper API again --- pom.xml | 111 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 58 insertions(+), 53 deletions(-) diff --git a/pom.xml b/pom.xml index e55ff5a..c4c62a5 100644 --- a/pom.xml +++ b/pom.xml @@ -17,23 +17,26 @@ - papermc - https://papermc.io/repo/repository/maven-public/ - + spigot-repo + https://hub.spigotmc.org/nexus/content/groups/public/ + dynmap-repo https://repo.mikeprimm.com/ - + + papermc + https://papermc.io/repo/repository/maven-public/ + + - com.destroystokyo.paper - paper-api - 1.13.2-R0.1-SNAPSHOT - provided - + org.spigotmc + spigot-api + 1.13.1-R0.1-SNAPSHOT + org.bukkit @@ -46,12 +49,12 @@ dynmap-api 2.5 - - io.papermc - paperlib - 1.0.2 - compile - + + io.papermc + paperlib + 1.0.2 + compile + @@ -67,43 +70,45 @@ 1.8 - - org.apache.maven.plugins - maven-shade-plugin - 3.1.1 - - ${project.build.directory}/dependency-reduced-pom.xml - - - io.papermc.lib - com.wimbli.WorldBorder.paperlib - - - - - - package - - shade - - - - - commons-lang:commons-lang - com.googlecode.json-simple:json-simple - junit:junit - org.hamcrest:hamcrest-core - com.google.guava:guava - com.google.code.gson:gson - org.yaml:snakeyaml - org.bukkit:bukkit - us.dynmap:dynmap-api - - - - - - - + + org.apache.maven.plugins + maven-shade-plugin + 3.1.1 + + ${project.build.directory}/dependency-reduced-pom.xml + + + io.papermc.lib + com.wimbli.WorldBorder.paperlib + + + + + + package + + shade + + + + + org.spigotmc:spigot-api + commons-lang:commons-lang + com.googlecode.json-simple:json-simple + junit:junit + org.hamcrest:hamcrest-core + com.google.guava:guava + com.google.code.gson:gson + org.yaml:snakeyaml + net.md-5:bungeecord-chat + org.bukkit:bukkit + us.dynmap:dynmap-api + + + + + + + From ade2ca635232cea95cebce88a4873e19df86568c Mon Sep 17 00:00:00 2001 From: Guntram Blohm Date: Wed, 20 Mar 2019 19:27:10 +0100 Subject: [PATCH 4/7] Fix formatting (spaces/tabs) --- .../com/wimbli/WorldBorder/WBListener.java | 36 +- .../com/wimbli/WorldBorder/WorldFillTask.java | 318 +++++++++--------- 2 files changed, 177 insertions(+), 177 deletions(-) diff --git a/src/main/java/com/wimbli/WorldBorder/WBListener.java b/src/main/java/com/wimbli/WorldBorder/WBListener.java index 6797a18..e232fe4 100644 --- a/src/main/java/com/wimbli/WorldBorder/WBListener.java +++ b/src/main/java/com/wimbli/WorldBorder/WBListener.java @@ -71,23 +71,23 @@ public class WBListener implements Listener Config.StartBorderTimer(); } - /* - * Check if there is a fill task running, and if yes, if it's for the - * world that the unload event refers to and if the chunk should be - * kept in memory because generation still needs it. - */ - @EventHandler - public void onChunkUnload(ChunkUnloadEvent e) - { - if (Config.fillTask!=null) - { - Chunk chunk=e.getChunk(); - if (e.getWorld() == Config.fillTask.getWorld() - && Config.fillTask.chunkOnUnloadPreventionList(chunk.getX(), chunk.getZ())) - { - e.setCancelled(true); - } - } - } + /* + * Check if there is a fill task running, and if yes, if it's for the + * world that the unload event refers to and if the chunk should be + * kept in memory because generation still needs it. + */ + @EventHandler + public void onChunkUnload(ChunkUnloadEvent e) + { + if (Config.fillTask!=null) + { + Chunk chunk=e.getChunk(); + if (e.getWorld() == Config.fillTask.getWorld() + && Config.fillTask.chunkOnUnloadPreventionList(chunk.getX(), chunk.getZ())) + { + e.setCancelled(true); + } + } + } } diff --git a/src/main/java/com/wimbli/WorldBorder/WorldFillTask.java b/src/main/java/com/wimbli/WorldBorder/WorldFillTask.java index 328db9c..502f9b4 100644 --- a/src/main/java/com/wimbli/WorldBorder/WorldFillTask.java +++ b/src/main/java/com/wimbli/WorldBorder/WorldFillTask.java @@ -57,52 +57,52 @@ public class WorldFillTask implements Runnable private transient int reportTarget = 0; private transient int reportTotal = 0; private transient int reportNum = 0; - - // A map that holds to-be-loaded chunks, and their coordinates - private transient Map, CoordXZ> pendingChunks; - - // and a set of "Chunk a needed for Chunk b" dependencies, which - // unfortunately can't be a Map as a chunk might be needed for - // several others. - private transient Set preventUnload; - - private class UnloadDependency - { - int neededX, neededZ; - int forX, forZ; - - UnloadDependency(int neededX, int neededZ, int forX, int forZ) - { - this.neededX=neededX; - this.neededZ=neededZ; - this.forX=forX; - this.forZ=forZ; - } - - @Override - public boolean equals(Object other) - { - if (other == null || !(other instanceof UnloadDependency)) - { - return false; - } - return this.neededX == ((UnloadDependency) other).neededX - && this.neededZ == ((UnloadDependency) other).neededZ - && this.forX == ((UnloadDependency) other).forX - && this.forZ == ((UnloadDependency) other).forZ; - } + + // A map that holds to-be-loaded chunks, and their coordinates + private transient Map, CoordXZ> pendingChunks; + + // and a set of "Chunk a needed for Chunk b" dependencies, which + // unfortunately can't be a Map as a chunk might be needed for + // several others. + private transient Set preventUnload; + + private class UnloadDependency + { + int neededX, neededZ; + int forX, forZ; + + UnloadDependency(int neededX, int neededZ, int forX, int forZ) + { + this.neededX=neededX; + this.neededZ=neededZ; + this.forX=forX; + this.forZ=forZ; + } + + @Override + public boolean equals(Object other) + { + if (other == null || !(other instanceof UnloadDependency)) + { + return false; + } + return this.neededX == ((UnloadDependency) other).neededX + && this.neededZ == ((UnloadDependency) other).neededZ + && this.forX == ((UnloadDependency) other).forX + && this.forZ == ((UnloadDependency) other).forZ; + } - @Override - public int hashCode() - { - int hash = 7; - hash = 79 * hash + this.neededX; - hash = 79 * hash + this.neededZ; - hash = 79 * hash + this.forX; - hash = 79 * hash + this.forZ; - return hash; - } - } + @Override + public int hashCode() + { + int hash = 7; + hash = 79 * hash + this.neededX; + hash = 79 * hash + this.neededZ; + hash = 79 * hash + this.forX; + hash = 79 * hash + this.forZ; + return hash; + } + } public WorldFillTask(Server theServer, Player player, String worldName, int fillDistance, int chunksPerRun, int tickFrequency, boolean forceLoad) { @@ -139,9 +139,9 @@ public class WorldFillTask implements Runnable this.stop(); return; } - - pendingChunks = new HashMap<>(); - preventUnload = new HashSet<>(); + + pendingChunks = new HashMap<>(); + preventUnload = new HashSet<>(); this.border.setRadiusX(border.getRadiusX() + fillDistance); this.border.setRadiusZ(border.getRadiusZ() + fillDistance); @@ -160,7 +160,7 @@ public class WorldFillTask implements Runnable this.readyToGo = true; Bukkit.getServer().getPluginManager().callEvent(new WorldBorderFillStartEvent(this)); } - + // for backwards compatibility public WorldFillTask(Server theServer, Player player, String worldName, int fillDistance, int chunksPerRun, int tickFrequency) { @@ -201,86 +201,86 @@ public class WorldFillTask implements Runnable // and this is tracked to keep one iteration from dragging on too long and possibly choking the system if the user specified a really high frequency long loopStartTime = Config.Now(); - // Process async results from last time. We don't make a difference - // whether they were really async, or sync. - - // First, Check which chunk generations have been finished. - // Mark those chunks as existing and unloadable, and remove - // them from the pending set. - int chunksProcessedLastTick = 0; - Map, CoordXZ> newPendingChunks = new HashMap<>(); - Set chunksToUnload = new HashSet<>(); - for (CompletableFuture cf: pendingChunks.keySet()) - { - if (cf.isDone()) - { - ++chunksProcessedLastTick; - // If cf.get() returned the chunk reliably, pendingChunks could - // be a set and we wouldn't have to map CFs to coords ... - CoordXZ xz=pendingChunks.get(cf); - worldData.chunkExistsNow(xz.x, xz.z); - chunksToUnload.add(xz); - } - else - { - newPendingChunks.put(cf, pendingChunks.get(cf)); - } - } - pendingChunks = newPendingChunks; + // Process async results from last time. We don't make a difference + // whether they were really async, or sync. + + // First, Check which chunk generations have been finished. + // Mark those chunks as existing and unloadable, and remove + // them from the pending set. + int chunksProcessedLastTick = 0; + Map, CoordXZ> newPendingChunks = new HashMap<>(); + Set chunksToUnload = new HashSet<>(); + for (CompletableFuture cf: pendingChunks.keySet()) + { + if (cf.isDone()) + { + ++chunksProcessedLastTick; + // If cf.get() returned the chunk reliably, pendingChunks could + // be a set and we wouldn't have to map CFs to coords ... + CoordXZ xz=pendingChunks.get(cf); + worldData.chunkExistsNow(xz.x, xz.z); + chunksToUnload.add(xz); + } + else + { + newPendingChunks.put(cf, pendingChunks.get(cf)); + } + } + pendingChunks = newPendingChunks; - // Next, check which chunks had been loaded because a to-be-generated - // chunk needed them, and don't have to remain in memory any more. - Set newPreventUnload = new HashSet<>(); - for (UnloadDependency dependency: preventUnload) - { - if (worldData.doesChunkExist(dependency.forX, dependency.forZ)) - { - chunksToUnload.add(new CoordXZ(dependency.neededX, dependency.neededZ)); - } - else - { - newPreventUnload.add(dependency); - } - } - preventUnload = newPreventUnload; + // Next, check which chunks had been loaded because a to-be-generated + // chunk needed them, and don't have to remain in memory any more. + Set newPreventUnload = new HashSet<>(); + for (UnloadDependency dependency: preventUnload) + { + if (worldData.doesChunkExist(dependency.forX, dependency.forZ)) + { + chunksToUnload.add(new CoordXZ(dependency.neededX, dependency.neededZ)); + } + else + { + newPreventUnload.add(dependency); + } + } + preventUnload = newPreventUnload; - // Unload all chunks that aren't needed anymore. NB a chunk could have - // been needed for two different others, or been generated and needed - // for one other chunk, so it might be in the unload set wrongly. - // The ChunkUnloadListener checks this anyway, but it doesn't hurt to - // save a few µs by not even requesting the unload. + // Unload all chunks that aren't needed anymore. NB a chunk could have + // been needed for two different others, or been generated and needed + // for one other chunk, so it might be in the unload set wrongly. + // The ChunkUnloadListener checks this anyway, but it doesn't hurt to + // save a few µs by not even requesting the unload. - for (CoordXZ unload: chunksToUnload) - { - if (!chunkOnUnloadPreventionList(unload.x, unload.z)) - { - world.unloadChunkRequest(unload.x, unload.z); - } - } + for (CoordXZ unload: chunksToUnload) + { + if (!chunkOnUnloadPreventionList(unload.x, unload.z)) + { + world.unloadChunkRequest(unload.x, unload.z); + } + } - // Put some damper on chunksPerRun. We don't want the queue to be too - // full; only fill it to a bit more than what we can - // process per tick. This ensures the background task can run at - // full speed and we recover from a temporary drop in generation rate, - // but doesn't push user-induced chunk generations behind a very - // long queue of fill-generations. + // Put some damper on chunksPerRun. We don't want the queue to be too + // full; only fill it to a bit more than what we can + // process per tick. This ensures the background task can run at + // full speed and we recover from a temporary drop in generation rate, + // but doesn't push user-induced chunk generations behind a very + // long queue of fill-generations. - int chunksToProcess = chunksPerRun; - if (chunksProcessedLastTick > 0 || pendingChunks.size() > 0) - { - // Note we generally queue 3 chunks, so real numbers are 1/3 of chunksProcessedLastTick and pendingchunks.size - int chunksExpectedToGetProcessed = (chunksProcessedLastTick - pendingChunks.size()) / 3 + 3; - if (chunksExpectedToGetProcessed < chunksToProcess) - chunksToProcess = chunksExpectedToGetProcessed; - } + int chunksToProcess = chunksPerRun; + if (chunksProcessedLastTick > 0 || pendingChunks.size() > 0) + { + // Note we generally queue 3 chunks, so real numbers are 1/3 of chunksProcessedLastTick and pendingchunks.size + int chunksExpectedToGetProcessed = (chunksProcessedLastTick - pendingChunks.size()) / 3 + 3; + if (chunksExpectedToGetProcessed < chunksToProcess) + chunksToProcess = chunksExpectedToGetProcessed; + } for (int loop = 0; loop < chunksToProcess; loop++) { // in case the task has been paused while we're repeating... if (paused || pausedForMemory) - { + { return; - } + } long now = Config.Now(); @@ -299,9 +299,9 @@ public class WorldFillTask implements Runnable while (!border.insideBorder(CoordXZ.chunkToBlock(x) + 8, CoordXZ.chunkToBlock(z) + 8)) { if (!moveToNext()) - { + { return; - } + } } insideBorder = true; @@ -314,9 +314,9 @@ public class WorldFillTask implements Runnable rLoop++; insideBorder = true; if (!moveToNext()) - { + { return; - } + } if (rLoop > 255) { // only skim through max 256 chunks (~8 region files) at a time here, to allow process to take a break if needed readyToGo = true; @@ -325,25 +325,25 @@ public class WorldFillTask implements Runnable } } - pendingChunks.put(PaperLib.getChunkAtAsync(world, x, z, true), new CoordXZ(x, z)); + pendingChunks.put(PaperLib.getChunkAtAsync(world, x, z, true), new CoordXZ(x, z)); // There need to be enough nearby chunks loaded to make the server populate a chunk with trees, snow, etc. // So, we keep the last few chunks loaded, and need to also temporarily load an extra inside chunk (neighbor closest to center of map) int popX = !isZLeg ? x : (x + (isNeg ? -1 : 1)); int popZ = isZLeg ? z : (z + (!isNeg ? -1 : 1)); - pendingChunks.put(PaperLib.getChunkAtAsync(world, popX, popZ, false), new CoordXZ(popX, popZ)); - preventUnload.add(new UnloadDependency(popX, popZ, x, z)); - + pendingChunks.put(PaperLib.getChunkAtAsync(world, popX, popZ, false), new CoordXZ(popX, popZ)); + preventUnload.add(new UnloadDependency(popX, popZ, x, z)); + // make sure the previous chunk in our spiral is loaded as well (might have already existed and been skipped over) - pendingChunks.put(PaperLib.getChunkAtAsync(world, lastChunk.x, lastChunk.z, false), new CoordXZ(lastChunk.x, lastChunk.z)); // <-- new CoordXZ as lastChunk isn't immutable - preventUnload.add(new UnloadDependency(lastChunk.x, lastChunk.z, x, z)); + pendingChunks.put(PaperLib.getChunkAtAsync(world, lastChunk.x, lastChunk.z, false), new CoordXZ(lastChunk.x, lastChunk.z)); // <-- new CoordXZ as lastChunk isn't immutable + preventUnload.add(new UnloadDependency(lastChunk.x, lastChunk.z, x, z)); // move on to next chunk if (!moveToNext()) - { + { return; - } + } } // ready for the next iteration to run readyToGo = true; @@ -451,15 +451,15 @@ public class WorldFillTask implements Runnable server.getScheduler().cancelTask(taskID); server = null; - // go ahead and unload any chunks we still have loaded - // Set preventUnload to emptry first so the ChunkUnloadEvent Listener - // doesn't get in our way - Set tempPreventUnload = preventUnload; - preventUnload = null; - for (UnloadDependency entry: tempPreventUnload) - { - world.unloadChunkRequest(entry.neededX, entry.neededZ); - } + // go ahead and unload any chunks we still have loaded + // Set preventUnload to emptry first so the ChunkUnloadEvent Listener + // doesn't get in our way + Set tempPreventUnload = preventUnload; + preventUnload = null; + for (UnloadDependency entry: tempPreventUnload) + { + world.unloadChunkRequest(entry.neededX, entry.neededZ); + } } // is this task still valid/workable? @@ -494,26 +494,26 @@ public class WorldFillTask implements Runnable { return this.paused || this.pausedForMemory; } - - public boolean chunkOnUnloadPreventionList(int x, int z) - { - if (preventUnload != null) - { - for (UnloadDependency entry: preventUnload) - { - if (entry.neededX == x && entry.neededZ == z) - { - return true; - } - } - } - return false; - } - - public World getWorld() - { - return world; - } + + public boolean chunkOnUnloadPreventionList(int x, int z) + { + if (preventUnload != null) + { + for (UnloadDependency entry: preventUnload) + { + if (entry.neededX == x && entry.neededZ == z) + { + return true; + } + } + } + return false; + } + + public World getWorld() + { + return world; + } // let the user know how things are coming along private void reportProgress() From 17c0c2a8864e2042e0297e01b386868a040bbbd7 Mon Sep 17 00:00:00 2001 From: Guntram Blohm Date: Wed, 20 Mar 2019 19:30:53 +0100 Subject: [PATCH 5/7] More spaces/tabs formatting --- pom.xml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index c4c62a5..61c135b 100644 --- a/pom.xml +++ b/pom.xml @@ -19,16 +19,16 @@ spigot-repo https://hub.spigotmc.org/nexus/content/groups/public/ - + dynmap-repo https://repo.mikeprimm.com/ - papermc - https://papermc.io/repo/repository/maven-public/ + papermc + https://papermc.io/repo/repository/maven-public/ - + @@ -36,7 +36,7 @@ org.spigotmc spigot-api 1.13.1-R0.1-SNAPSHOT - + org.bukkit @@ -92,7 +92,7 @@ - org.spigotmc:spigot-api + org.spigotmc:spigot-api commons-lang:commons-lang com.googlecode.json-simple:json-simple junit:junit @@ -100,7 +100,7 @@ com.google.guava:guava com.google.code.gson:gson org.yaml:snakeyaml - net.md-5:bungeecord-chat + net.md-5:bungeecord-chat org.bukkit:bukkit us.dynmap:dynmap-api From 6ffeab72dd31ce3dd6cb356b9913cafb3ea7d157 Mon Sep 17 00:00:00 2001 From: Guntram Blohm Date: Wed, 17 Apr 2019 18:46:07 +0200 Subject: [PATCH 6/7] remove exludes, explicitly include paperlib --- pom.xml | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/pom.xml b/pom.xml index 61c135b..80882c4 100644 --- a/pom.xml +++ b/pom.xml @@ -75,6 +75,11 @@ maven-shade-plugin 3.1.1 + + + io.papermc:paperlib:jar:* + + ${project.build.directory}/dependency-reduced-pom.xml @@ -89,23 +94,6 @@ shade - - - - org.spigotmc:spigot-api - commons-lang:commons-lang - com.googlecode.json-simple:json-simple - junit:junit - org.hamcrest:hamcrest-core - com.google.guava:guava - com.google.code.gson:gson - org.yaml:snakeyaml - net.md-5:bungeecord-chat - org.bukkit:bukkit - us.dynmap:dynmap-api - - - From 7e2336fc8a5ee2c22c559ec9699a589b898b3723 Mon Sep 17 00:00:00 2001 From: Guntram Blohm Date: Wed, 17 Apr 2019 18:49:31 +0200 Subject: [PATCH 7/7] set scope of stuff that shouldn't be shaded in to provided so we don't need explicit excludes/includes --- pom.xml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 80882c4..4bc2ea1 100644 --- a/pom.xml +++ b/pom.xml @@ -36,18 +36,21 @@ org.spigotmc spigot-api 1.13.1-R0.1-SNAPSHOT + provided org.bukkit bukkit 1.13.1-R0.1-SNAPSHOT + provided us.dynmap dynmap-api 2.5 + provided io.papermc @@ -75,11 +78,6 @@ maven-shade-plugin 3.1.1 - - - io.papermc:paperlib:jar:* - - ${project.build.directory}/dependency-reduced-pom.xml