add pregen method that doesn't use waiting threads

This commit is contained in:
Julian Krings 2025-05-16 12:02:38 +02:00
parent 49ce84a9e9
commit 976648340e
No known key found for this signature in database
GPG Key ID: 208C6E08C3B718D2
3 changed files with 76 additions and 33 deletions

View File

@ -145,7 +145,10 @@ public class IrisSettings {
@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;
} }

View File

@ -33,7 +33,6 @@ import org.bukkit.Chunk;
import org.bukkit.World; import org.bukkit.World;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
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;
@ -43,22 +42,22 @@ 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 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<>();
} }
@ -70,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) {
@ -84,21 +88,6 @@ 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();
@ -114,7 +103,7 @@ public class AsyncPregenMethod implements PregeneratorMethod {
public void close() { public void close() {
semaphore.acquireUninterruptibly(threads); semaphore.acquireUninterruptibly(threads);
unloadAndSaveAllChunks(); unloadAndSaveAllChunks();
service.shutdown(); executor.shutdown();
resetWorkerThreads(); resetWorkerThreads();
} }
@ -141,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
@ -153,7 +142,6 @@ public class AsyncPregenMethod implements PregeneratorMethod {
return null; return null;
} }
public static void increaseWorkerThreads() { public static void increaseWorkerThreads() {
THREAD_COUNT.updateAndGet(i -> { THREAD_COUNT.updateAndGet(i -> {
if (i > 0) return 1; if (i > 0) return 1;
@ -167,7 +155,7 @@ public class AsyncPregenMethod implements PregeneratorMethod {
pool.getClass().getDeclaredMethod("adjustThreadCount", int.class).invoke(pool, adjusted); pool.getClass().getDeclaredMethod("adjustThreadCount", int.class).invoke(pool, adjusted);
return threads; return threads;
} catch (Throwable e) { } catch (Throwable e) {
Iris.warn("Failed to increase worker threads, please increase it manually to " + adjusted); 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"); Iris.warn("For more information see https://docs.papermc.io/paper/reference/global-configuration#chunk_system_worker_threads");
if (e instanceof InvocationTargetException) e.printStackTrace(); if (e instanceof InvocationTargetException) e.printStackTrace();
} }
@ -191,4 +179,56 @@ public class AsyncPregenMethod implements PregeneratorMethod {
return i; 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());
});
}
}
} }

View File

@ -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);
} }
/** /**