Merge pull request #122 from BaronyCraft/asyncFill
Async fill support for Paper fork of Sponge
This commit is contained in:
commit
8258963fd7
35
pom.xml
35
pom.xml
|
@ -24,6 +24,10 @@
|
|||
<id>dynmap-repo</id>
|
||||
<url>https://repo.mikeprimm.com/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>papermc</id>
|
||||
<url>https://papermc.io/repo/repository/maven-public/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
|
@ -32,19 +36,28 @@
|
|||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.13.1-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<!--Bukkit API-->
|
||||
<dependency>
|
||||
<groupId>org.bukkit</groupId>
|
||||
<artifactId>bukkit</artifactId>
|
||||
<version>1.13.1-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<!--Dynmap API-->
|
||||
<dependency>
|
||||
<groupId>us.dynmap</groupId>
|
||||
<artifactId>dynmap-api</artifactId>
|
||||
<version>2.5</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.papermc</groupId>
|
||||
<artifactId>paperlib</artifactId>
|
||||
<version>1.0.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
@ -60,6 +73,28 @@
|
|||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.1.1</version>
|
||||
<configuration>
|
||||
<dependencyReducedPomLocation>${project.build.directory}/dependency-reduced-pom.xml</dependencyReducedPomLocation>
|
||||
<relocations>
|
||||
<relocation>
|
||||
<pattern>io.papermc.lib</pattern>
|
||||
<shadedPattern>com.wimbli.WorldBorder.paperlib</shadedPattern> <!-- Replace this -->
|
||||
</relocation>
|
||||
</relocations>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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,6 +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;
|
||||
|
||||
|
||||
public class WorldFillTask implements Runnable
|
||||
|
@ -47,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<CoordXZ> storedChunks = new LinkedList<>();
|
||||
private Set<CoordXZ> originalChunks = new HashSet<>();
|
||||
private transient CoordXZ lastChunk = new CoordXZ(0, 0);
|
||||
|
||||
// for reporting progress back to user occasionally
|
||||
|
@ -57,7 +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<CompletableFuture<Chunk>, 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<UnloadDependency> 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)
|
||||
{
|
||||
|
@ -94,6 +139,9 @@ public class WorldFillTask implements Runnable
|
|||
this.stop();
|
||||
return;
|
||||
}
|
||||
|
||||
pendingChunks = new HashMap<>();
|
||||
preventUnload = new HashSet<>();
|
||||
|
||||
this.border.setRadiusX(border.getRadiusX() + fillDistance);
|
||||
this.border.setRadiusZ(border.getRadiusZ() + fillDistance);
|
||||
|
@ -109,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)
|
||||
{
|
||||
|
@ -132,7 +173,6 @@ public class WorldFillTask implements Runnable
|
|||
this.taskID = ID;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
|
@ -161,11 +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();
|
||||
|
||||
for (int loop = 0; loop < chunksPerRun; loop++)
|
||||
// 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<CompletableFuture<Chunk>, CoordXZ> newPendingChunks = new HashMap<>();
|
||||
Set<CoordXZ> chunksToUnload = new HashSet<>();
|
||||
for (CompletableFuture<Chunk> 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<UnloadDependency> 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);
|
||||
}
|
||||
}
|
||||
|
||||
// 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();
|
||||
|
||||
|
@ -184,7 +299,9 @@ public class WorldFillTask implements Runnable
|
|||
while (!border.insideBorder(CoordXZ.chunkToBlock(x) + 8, CoordXZ.chunkToBlock(z) + 8))
|
||||
{
|
||||
if (!moveToNext())
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
insideBorder = true;
|
||||
|
||||
|
@ -197,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;
|
||||
|
@ -206,40 +325,26 @@ public class WorldFillTask implements Runnable
|
|||
}
|
||||
}
|
||||
|
||||
// load the target chunk and generate it if necessary
|
||||
world.loadChunk(x, z, true);
|
||||
worldData.chunkExistsNow(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));
|
||||
world.loadChunk(popX, popZ, false);
|
||||
|
||||
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)
|
||||
if (!storedChunks.contains(lastChunk) && !originalChunks.contains(lastChunk))
|
||||
{
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
@ -347,11 +452,13 @@ public class WorldFillTask implements Runnable
|
|||
server = null;
|
||||
|
||||
// go ahead and unload any chunks we still have loaded
|
||||
while(!storedChunks.isEmpty())
|
||||
// Set preventUnload to emptry first so the ChunkUnloadEvent Listener
|
||||
// doesn't get in our way
|
||||
Set<UnloadDependency> tempPreventUnload = preventUnload;
|
||||
preventUnload = null;
|
||||
for (UnloadDependency entry: tempPreventUnload)
|
||||
{
|
||||
CoordXZ coord = storedChunks.remove(0);
|
||||
if (!originalChunks.contains(coord))
|
||||
world.unloadChunkRequest(coord.x, coord.z);
|
||||
world.unloadChunkRequest(entry.neededX, entry.neededZ);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -387,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()
|
||||
|
|
Loading…
Reference in New Issue