mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2025-07-02 16:07:06 +00:00
Merge pull request #1193 from VolmitSoftware/feat/faster_pregen
Feat/faster pregen
This commit is contained in:
commit
463e3d9658
@ -136,11 +136,19 @@ public class IrisSettings {
|
|||||||
@Data
|
@Data
|
||||||
public static class IrisSettingsConcurrency {
|
public static class IrisSettingsConcurrency {
|
||||||
public int parallelism = -1;
|
public int parallelism = -1;
|
||||||
|
public int worldGenParallelism = -1;
|
||||||
|
|
||||||
|
public int getWorldGenThreads() {
|
||||||
|
return getThreadCount(worldGenParallelism);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public static class IrisSettingsPregen {
|
public static class IrisSettingsPregen {
|
||||||
|
public boolean useCacheByDefault = true;
|
||||||
|
public boolean useHighPriority = false;
|
||||||
public boolean useVirtualThreads = false;
|
public boolean useVirtualThreads = false;
|
||||||
|
public boolean useTicketQueue = false;
|
||||||
public int maxConcurrency = 256;
|
public int maxConcurrency = 256;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,30 +32,32 @@ import io.papermc.lib.PaperLib;
|
|||||||
import org.bukkit.Chunk;
|
import org.bukkit.Chunk;
|
||||||
import org.bukkit.World;
|
import org.bukkit.World;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.Semaphore;
|
import java.util.concurrent.Semaphore;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
public class AsyncPregenMethod implements PregeneratorMethod {
|
public class AsyncPregenMethod implements PregeneratorMethod {
|
||||||
|
private static final AtomicInteger THREAD_COUNT = new AtomicInteger();
|
||||||
private final World world;
|
private final World world;
|
||||||
private final ExecutorService service;
|
private final Executor executor;
|
||||||
private final Semaphore semaphore;
|
private final Semaphore semaphore;
|
||||||
private final int threads;
|
private final int threads;
|
||||||
|
private final boolean urgent;
|
||||||
private final Map<Chunk, Long> lastUse;
|
private final Map<Chunk, Long> lastUse;
|
||||||
|
|
||||||
public AsyncPregenMethod(World world, int threads) {
|
public AsyncPregenMethod(World world, int unusedThreads) {
|
||||||
if (!PaperLib.isPaper()) {
|
if (!PaperLib.isPaper()) {
|
||||||
throw new UnsupportedOperationException("Cannot use PaperAsync on non paper!");
|
throw new UnsupportedOperationException("Cannot use PaperAsync on non paper!");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.world = world;
|
this.world = world;
|
||||||
service = IrisSettings.get().getPregen().isUseVirtualThreads() ?
|
this.executor = IrisSettings.get().getPregen().isUseTicketQueue() ? new TicketExecutor() : new ServiceExecutor();
|
||||||
Executors.newVirtualThreadPerTaskExecutor() :
|
|
||||||
new MultiBurst("Iris Async Pregen", Thread.MIN_PRIORITY);
|
|
||||||
this.threads = IrisSettings.get().getPregen().getMaxConcurrency();
|
this.threads = IrisSettings.get().getPregen().getMaxConcurrency();
|
||||||
semaphore = new Semaphore(threads);
|
this.semaphore = new Semaphore(this.threads, true);
|
||||||
|
this.urgent = IrisSettings.get().getPregen().useHighPriority;
|
||||||
this.lastUse = new KMap<>();
|
this.lastUse = new KMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,13 +69,18 @@ public class AsyncPregenMethod implements PregeneratorMethod {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Chunk i : new ArrayList<>(lastUse.keySet())) {
|
long minTime = M.ms() - 10_000;
|
||||||
Long lastUseTime = lastUse.get(i);
|
lastUse.entrySet().removeIf(i -> {
|
||||||
if (!i.isLoaded() || (lastUseTime != null && M.ms() - lastUseTime >= 10000)) {
|
final Chunk chunk = i.getKey();
|
||||||
i.unload();
|
final Long lastUseTime = i.getValue();
|
||||||
lastUse.remove(i);
|
if (!chunk.isLoaded() || lastUseTime == null)
|
||||||
|
return true;
|
||||||
|
if (lastUseTime < minTime) {
|
||||||
|
chunk.unload();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
return false;
|
||||||
|
});
|
||||||
world.save();
|
world.save();
|
||||||
}).get();
|
}).get();
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
@ -81,24 +88,10 @@ public class AsyncPregenMethod implements PregeneratorMethod {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void completeChunk(int x, int z, PregenListener listener) {
|
|
||||||
try {
|
|
||||||
PaperLib.getChunkAtAsync(world, x, z, true).thenAccept((i) -> {
|
|
||||||
lastUse.put(i, M.ms());
|
|
||||||
listener.onChunkGenerated(x, z);
|
|
||||||
listener.onChunkCleaned(x, z);
|
|
||||||
}).get();
|
|
||||||
} catch (InterruptedException ignored) {
|
|
||||||
} catch (Throwable e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
} finally {
|
|
||||||
semaphore.release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init() {
|
public void init() {
|
||||||
unloadAndSaveAllChunks();
|
unloadAndSaveAllChunks();
|
||||||
|
increaseWorkerThreads();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -110,7 +103,8 @@ public class AsyncPregenMethod implements PregeneratorMethod {
|
|||||||
public void close() {
|
public void close() {
|
||||||
semaphore.acquireUninterruptibly(threads);
|
semaphore.acquireUninterruptibly(threads);
|
||||||
unloadAndSaveAllChunks();
|
unloadAndSaveAllChunks();
|
||||||
service.shutdown();
|
executor.shutdown();
|
||||||
|
resetWorkerThreads();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -136,7 +130,7 @@ public class AsyncPregenMethod implements PregeneratorMethod {
|
|||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
service.submit(() -> completeChunk(x, z, listener));
|
executor.generate(x, z, listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -147,4 +141,94 @@ public class AsyncPregenMethod implements PregeneratorMethod {
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void increaseWorkerThreads() {
|
||||||
|
THREAD_COUNT.updateAndGet(i -> {
|
||||||
|
if (i > 0) return 1;
|
||||||
|
var adjusted = IrisSettings.get().getConcurrency().getWorldGenThreads();
|
||||||
|
try {
|
||||||
|
var field = Class.forName("ca.spottedleaf.moonrise.common.util.MoonriseCommon").getDeclaredField("WORKER_POOL");
|
||||||
|
var pool = field.get(null);
|
||||||
|
var threads = ((Thread[]) pool.getClass().getDeclaredMethod("getCoreThreads").invoke(pool)).length;
|
||||||
|
if (threads >= adjusted) return 0;
|
||||||
|
|
||||||
|
pool.getClass().getDeclaredMethod("adjustThreadCount", int.class).invoke(pool, adjusted);
|
||||||
|
return threads;
|
||||||
|
} catch (Throwable e) {
|
||||||
|
Iris.warn("Failed to increase worker threads, if you are on paper or a fork of it please increase it manually to " + adjusted);
|
||||||
|
Iris.warn("For more information see https://docs.papermc.io/paper/reference/global-configuration#chunk_system_worker_threads");
|
||||||
|
if (e instanceof InvocationTargetException) e.printStackTrace();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void resetWorkerThreads() {
|
||||||
|
THREAD_COUNT.updateAndGet(i -> {
|
||||||
|
if (i == 0) return 0;
|
||||||
|
try {
|
||||||
|
var field = Class.forName("ca.spottedleaf.moonrise.common.util.MoonriseCommon").getDeclaredField("WORKER_POOL");
|
||||||
|
var pool = field.get(null);
|
||||||
|
var method = pool.getClass().getDeclaredMethod("adjustThreadCount", int.class);
|
||||||
|
method.invoke(pool, i);
|
||||||
|
return 0;
|
||||||
|
} catch (Throwable e) {
|
||||||
|
Iris.error("Failed to reset worker threads");
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return i;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface Executor {
|
||||||
|
void generate(int x, int z, PregenListener listener);
|
||||||
|
default void shutdown() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ServiceExecutor implements Executor {
|
||||||
|
private final ExecutorService service = IrisSettings.get().getPregen().isUseVirtualThreads() ?
|
||||||
|
Executors.newVirtualThreadPerTaskExecutor() :
|
||||||
|
new MultiBurst("Iris Async Pregen", Thread.MIN_PRIORITY);
|
||||||
|
|
||||||
|
public void generate(int x, int z, PregenListener listener) {
|
||||||
|
service.submit(() -> {
|
||||||
|
try {
|
||||||
|
PaperLib.getChunkAtAsync(world, x, z, true, urgent).thenAccept((i) -> {
|
||||||
|
listener.onChunkGenerated(x, z);
|
||||||
|
listener.onChunkCleaned(x, z);
|
||||||
|
if (i == null) return;
|
||||||
|
lastUse.put(i, M.ms());
|
||||||
|
}).get();
|
||||||
|
} catch (InterruptedException ignored) {
|
||||||
|
} catch (Throwable e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
semaphore.release();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void shutdown() {
|
||||||
|
service.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TicketExecutor implements Executor {
|
||||||
|
@Override
|
||||||
|
public void generate(int x, int z, PregenListener listener) {
|
||||||
|
PaperLib.getChunkAtAsync(world, x, z, true, urgent)
|
||||||
|
.exceptionally(e -> {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.thenAccept(i -> {
|
||||||
|
semaphore.release();
|
||||||
|
listener.onChunkGenerated(x, z);
|
||||||
|
listener.onChunkCleaned(x, z);
|
||||||
|
if (i == null) return;
|
||||||
|
lastUse.put(i, M.ms());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import com.volmit.iris.util.collection.KList;
|
|||||||
import com.volmit.iris.util.collection.KMap;
|
import com.volmit.iris.util.collection.KMap;
|
||||||
import com.volmit.iris.util.exceptions.IrisException;
|
import com.volmit.iris.util.exceptions.IrisException;
|
||||||
import com.volmit.iris.util.format.Form;
|
import com.volmit.iris.util.format.Form;
|
||||||
|
import com.volmit.iris.util.io.IO;
|
||||||
import com.volmit.iris.util.scheduling.J;
|
import com.volmit.iris.util.scheduling.J;
|
||||||
import com.volmit.iris.util.scheduling.PrecisionStopwatch;
|
import com.volmit.iris.util.scheduling.PrecisionStopwatch;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
@ -17,11 +18,6 @@ import org.bukkit.Bukkit;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileWriter;
|
import java.io.FileWriter;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.FileVisitResult;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.SimpleFileVisitor;
|
|
||||||
import java.nio.file.attribute.BasicFileAttributes;
|
|
||||||
import java.time.Clock;
|
import java.time.Clock;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@ -50,10 +46,7 @@ public class IrisPackBenchmarking {
|
|||||||
.start(() -> {
|
.start(() -> {
|
||||||
Iris.info("Setting up benchmark environment ");
|
Iris.info("Setting up benchmark environment ");
|
||||||
benchmarkInProgress = true;
|
benchmarkInProgress = true;
|
||||||
File file = new File("benchmark");
|
IO.delete(new File(Bukkit.getWorldContainer(), "benchmark"));
|
||||||
if (file.exists()) {
|
|
||||||
deleteDirectory(file.toPath());
|
|
||||||
}
|
|
||||||
createBenchmark();
|
createBenchmark();
|
||||||
while (!IrisToolbelt.isIrisWorld(Bukkit.getWorld("benchmark"))) {
|
while (!IrisToolbelt.isIrisWorld(Bukkit.getWorld("benchmark"))) {
|
||||||
J.sleep(1000);
|
J.sleep(1000);
|
||||||
@ -72,7 +65,7 @@ public class IrisPackBenchmarking {
|
|||||||
|
|
||||||
public void finishedBenchmark(KList<Integer> cps) {
|
public void finishedBenchmark(KList<Integer> cps) {
|
||||||
try {
|
try {
|
||||||
String time = Form.duration(stopwatch.getMillis());
|
String time = Form.duration((long) stopwatch.getMilliseconds());
|
||||||
Engine engine = IrisToolbelt.access(Bukkit.getWorld("benchmark")).getEngine();
|
Engine engine = IrisToolbelt.access(Bukkit.getWorld("benchmark")).getEngine();
|
||||||
Iris.info("-----------------");
|
Iris.info("-----------------");
|
||||||
Iris.info("Results:");
|
Iris.info("Results:");
|
||||||
@ -83,11 +76,7 @@ public class IrisPackBenchmarking {
|
|||||||
Iris.info(" - Lowest CPS: " + findLowest(cps));
|
Iris.info(" - Lowest CPS: " + findLowest(cps));
|
||||||
Iris.info("-----------------");
|
Iris.info("-----------------");
|
||||||
Iris.info("Creating a report..");
|
Iris.info("Creating a report..");
|
||||||
File profilers = new File("plugins" + File.separator + "Iris" + File.separator + "packbenchmarks");
|
File results = Iris.instance.getDataFile("packbenchmarks", dimension.getName() + " " + LocalDateTime.now(Clock.systemDefaultZone()).toString().replace(':', '-') + ".txt");
|
||||||
profilers.mkdir();
|
|
||||||
|
|
||||||
File results = new File(profilers, dimension.getName() + " " + LocalDateTime.now(Clock.systemDefaultZone()).toString().replace(':', '-') + ".txt");
|
|
||||||
results.getParentFile().mkdirs();
|
|
||||||
KMap<String, Double> metrics = engine.getMetrics().pull();
|
KMap<String, Double> metrics = engine.getMetrics().pull();
|
||||||
try (FileWriter writer = new FileWriter(results)) {
|
try (FileWriter writer = new FileWriter(results)) {
|
||||||
writer.write("-----------------\n");
|
writer.write("-----------------\n");
|
||||||
@ -178,26 +167,4 @@ public class IrisPackBenchmarking {
|
|||||||
private int findHighest(KList<Integer> list) {
|
private int findHighest(KList<Integer> list) {
|
||||||
return Collections.max(list);
|
return Collections.max(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean deleteDirectory(Path dir) {
|
|
||||||
try {
|
|
||||||
Files.walkFileTree(dir, new SimpleFileVisitor<>() {
|
|
||||||
@Override
|
|
||||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
|
||||||
Files.delete(file);
|
|
||||||
return FileVisitResult.CONTINUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
|
|
||||||
Files.delete(dir);
|
|
||||||
return FileVisitResult.CONTINUE;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -142,7 +142,7 @@ public class IrisToolbelt {
|
|||||||
* @return the pregenerator job (already started)
|
* @return the pregenerator job (already started)
|
||||||
*/
|
*/
|
||||||
public static PregeneratorJob pregenerate(PregenTask task, PregeneratorMethod method, Engine engine) {
|
public static PregeneratorJob pregenerate(PregenTask task, PregeneratorMethod method, Engine engine) {
|
||||||
return pregenerate(task, method, engine, true);
|
return pregenerate(task, method, engine, IrisSettings.get().getPregen().useCacheByDefault);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
x
Reference in New Issue
Block a user