diff --git a/core/src/main/java/com/volmit/iris/Iris.java b/core/src/main/java/com/volmit/iris/Iris.java index 14607fdba..9b782a5ed 100644 --- a/core/src/main/java/com/volmit/iris/Iris.java +++ b/core/src/main/java/com/volmit/iris/Iris.java @@ -58,6 +58,7 @@ import com.volmit.iris.util.parallel.MultiBurst; import com.volmit.iris.util.plugin.IrisService; import com.volmit.iris.util.plugin.VolmitPlugin; import com.volmit.iris.util.plugin.VolmitSender; +import com.volmit.iris.util.plugin.chunk.ChunkTickets; import com.volmit.iris.util.scheduling.J; import com.volmit.iris.util.scheduling.Queue; import com.volmit.iris.util.scheduling.ShurikenQueue; @@ -95,6 +96,7 @@ public class Iris extends VolmitPlugin implements Listener { public static MultiverseCoreLink linkMultiverseCore; public static IrisCompat compat; public static FileWatcher configWatcher; + public static ChunkTickets tickets; private static VolmitSender sender; private static Thread shutdownHook; @@ -450,6 +452,7 @@ public class Iris extends VolmitPlugin implements Listener { IrisSafeguard.IrisSafeguardSystem(); getSender().setTag(getTag()); IrisSafeguard.splash(true); + tickets = new ChunkTickets(); linkMultiverseCore = new MultiverseCoreLink(); configWatcher = new FileWatcher(getDataFile("settings.json")); services.values().forEach(IrisService::onEnable); diff --git a/core/src/main/java/com/volmit/iris/core/IrisSettings.java b/core/src/main/java/com/volmit/iris/core/IrisSettings.java index 28d84b686..4a613db89 100644 --- a/core/src/main/java/com/volmit/iris/core/IrisSettings.java +++ b/core/src/main/java/com/volmit/iris/core/IrisSettings.java @@ -177,6 +177,9 @@ public class IrisSettings { @Data public static class IrisSettingsUpdater { public int maxConcurrency = 256; + public boolean nativeThreads = false; + public double threadMultiplier = 2; + public double chunkLoadSensitivity = 0.7; public MsRange emptyMsRange = new MsRange(80, 100); public MsRange defaultMsRange = new MsRange(20, 40); @@ -185,6 +188,10 @@ public class IrisSettings { return Math.max(Math.abs(maxConcurrency), 1); } + public double getThreadMultiplier() { + return Math.min(Math.abs(threadMultiplier), 0.1); + } + public double getChunkLoadSensitivity() { return Math.min(chunkLoadSensitivity, 0.9); } diff --git a/core/src/main/java/com/volmit/iris/core/pregenerator/ChunkUpdater.java b/core/src/main/java/com/volmit/iris/core/pregenerator/ChunkUpdater.java index bce79233c..1dbd26172 100644 --- a/core/src/main/java/com/volmit/iris/core/pregenerator/ChunkUpdater.java +++ b/core/src/main/java/com/volmit/iris/core/pregenerator/ChunkUpdater.java @@ -2,16 +2,15 @@ package com.volmit.iris.core.pregenerator; import com.volmit.iris.Iris; import com.volmit.iris.core.IrisSettings; -import com.volmit.iris.core.nms.container.Pair; +import com.volmit.iris.core.service.PreservationSVC; import com.volmit.iris.core.tools.IrisToolbelt; -import com.volmit.iris.engine.data.cache.Cache; import com.volmit.iris.engine.framework.Engine; -import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.format.Form; import com.volmit.iris.util.mantle.flag.MantleFlag; import com.volmit.iris.util.math.M; import com.volmit.iris.util.math.Position2; import com.volmit.iris.util.math.RollingSequence; +import com.volmit.iris.util.plugin.chunk.TicketHolder; import com.volmit.iris.util.profile.LoadBalancer; import com.volmit.iris.util.scheduling.J; import io.papermc.lib.PaperLib; @@ -21,7 +20,6 @@ import org.bukkit.World; import java.io.File; -import java.util.ArrayList; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -31,7 +29,7 @@ public class ChunkUpdater { private static final String REGION_PATH = "region" + File.separator + "r."; private final AtomicBoolean paused = new AtomicBoolean(); private final AtomicBoolean cancelled = new AtomicBoolean(); - private final KMap> lastUse = new KMap<>(); + private final TicketHolder holder; private final RollingSequence chunksPerSecond = new RollingSequence(5); private final AtomicInteger totalMaxChunks = new AtomicInteger(); private final AtomicInteger chunksProcessed = new AtomicInteger(); @@ -40,13 +38,13 @@ public class ChunkUpdater { private final AtomicBoolean serverEmpty = new AtomicBoolean(true); private final AtomicLong lastCpsTime = new AtomicLong(M.ms()); private final int maxConcurrency = IrisSettings.get().getUpdater().getMaxConcurrency(); + private final int coreLimit = (int) Math.max(Runtime.getRuntime().availableProcessors() * IrisSettings.get().getUpdater().getThreadMultiplier(), 1); private final Semaphore semaphore = new Semaphore(maxConcurrency); private final LoadBalancer loadBalancer = new LoadBalancer(semaphore, maxConcurrency, IrisSettings.get().getUpdater().emptyMsRange); private final AtomicLong startTime = new AtomicLong(); private final Dimensions dimensions; private final PregenTask task; - private final ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor(); - private final ExecutorService chunkExecutor = Executors.newVirtualThreadPerTaskExecutor(); + private final ExecutorService chunkExecutor = IrisSettings.get().getUpdater().isNativeThreads() ? Executors.newFixedThreadPool(coreLimit) : Executors.newVirtualThreadPerTaskExecutor(); private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); private final CountDownLatch latch; private final Engine engine; @@ -55,6 +53,7 @@ public class ChunkUpdater { public ChunkUpdater(World world) { this.engine = IrisToolbelt.access(world).getEngine(); this.world = world; + this.holder = Iris.tickets.getHolder(world); this.dimensions = calculateWorldDimensions(new File(world.getWorldFolder(), "region")); this.task = dimensions.task(); this.totalMaxChunks.set(dimensions.count * 1024); @@ -113,7 +112,6 @@ public class ChunkUpdater { e.printStackTrace(); } }, 0, 3, TimeUnit.SECONDS); - scheduler.scheduleAtFixedRate(this::unloadChunks, 0, 1, TimeUnit.SECONDS); scheduler.scheduleAtFixedRate(() -> { boolean empty = Bukkit.getOnlinePlayers().isEmpty(); if (serverEmpty.getAndSet(empty) == empty) @@ -128,6 +126,7 @@ public class ChunkUpdater { t.setPriority(Thread.MAX_PRIORITY); t.start(); + Iris.service(PreservationSVC.class).register(t); } catch (Exception e) { e.printStackTrace(); } @@ -140,8 +139,6 @@ public class ChunkUpdater { chunkExecutor.shutdown(); chunkExecutor.awaitTermination(5, TimeUnit.SECONDS); - executor.shutdown(); - executor.awaitTermination(5, TimeUnit.SECONDS); scheduler.shutdownNow(); unloadAndSaveAllChunks(); } catch (Exception ignored) {} @@ -200,20 +197,16 @@ public class ChunkUpdater { return; } + var mc = engine.getMantle().getMantle().getChunk(x, z).use(); try { Chunk c = world.getChunkAt(x, z); - engine.getMantle().getMantle().getChunk(c); engine.updateChunk(c); - for (int xx = -1; xx <= 1; xx++) { - for (int zz = -1; zz <= 1; zz++) { - var counter = lastUse.get(Cache.key(x + xx, z + zz)); - if (counter != null) counter.getB().decrementAndGet(); - } - } + removeTickets(x, z); } finally { chunksUpdated.incrementAndGet(); chunksProcessed.getAndIncrement(); + mc.release(); } } @@ -235,41 +228,16 @@ public class ChunkUpdater { for (int dz = -1; dz <= 1; dz++) { int xx = x + dx; int zz = z + dz; - executor.submit(() -> { - try { - Chunk c; - try { - c = PaperLib.getChunkAtAsync(world, xx, zz, false, true) - .thenApply(chunk -> { - if (chunk != null) - chunk.addPluginChunkTicket(Iris.instance); - return chunk; - }).get(); - } catch (InterruptedException | ExecutionException e) { - generated.set(false); - return; - } - - if (c == null) { - generated.set(false); - return; - } - - if (!c.isLoaded()) { - var future = J.sfut(() -> c.load(false)); - if (future != null) future.join(); - } - - if (!PaperLib.isChunkGenerated(c.getWorld(), xx, zz)) - generated.set(false); - - var pair = lastUse.computeIfAbsent(Cache.key(c), k -> new Pair<>(0L, new AtomicInteger(-1))); - pair.setA(M.ms()); - pair.getB().updateAndGet(i -> i == -1 ? 1 : ++i); - } finally { - latch.countDown(); - } - }); + PaperLib.getChunkAtAsync(world, xx, zz, false, true) + .thenAccept(chunk -> { + if (chunk == null || !chunk.isGenerated()) { + latch.countDown(); + generated.set(false); + return; + } + holder.addTicket(chunk); + latch.countDown(); + }); } } @@ -278,27 +246,16 @@ public class ChunkUpdater { } catch (InterruptedException e) { Iris.info("Interrupted while waiting for chunks to load"); } - return generated.get(); + + if (generated.get()) return true; + removeTickets(x, z); + return false; } - private synchronized void unloadChunks() { - for (var key : new ArrayList<>(lastUse.keySet())) { - if (key == null) continue; - var pair = lastUse.get(key); - if (pair == null) continue; - var lastUseTime = pair.getA(); - var counter = pair.getB(); - if (lastUseTime == null || counter == null) - continue; - - if (M.ms() - lastUseTime >= 5000 && counter.get() == 0) { - int x = Cache.keyX(key); - int z = Cache.keyZ(key); - J.s(() -> { - world.removePluginChunkTicket(x, z, Iris.instance); - world.unloadChunk(x, z); - lastUse.remove(key); - }); + private void removeTickets(int x, int z) { + for (int xx = -1; xx <= 1; xx++) { + for (int zz = -1; zz <= 1; zz++) { + holder.removeTicket(x + xx, z + zz); } } } @@ -311,7 +268,6 @@ public class ChunkUpdater { return; } - unloadChunks(); world.save(); }).get(); } catch (Throwable e) { diff --git a/core/src/main/java/com/volmit/iris/core/service/IrisEngineSVC.java b/core/src/main/java/com/volmit/iris/core/service/IrisEngineSVC.java index 54738aa41..36e4b6d98 100644 --- a/core/src/main/java/com/volmit/iris/core/service/IrisEngineSVC.java +++ b/core/src/main/java/com/volmit/iris/core/service/IrisEngineSVC.java @@ -136,12 +136,12 @@ public class IrisEngineSVC implements IrisService { @Override protected long loop() { try { - queuedTectonicPlates.set(0); - tectonicPlates.set(0); - loadedChunks.set(0); - unloaderAlive.set(0); - trimmerAlive.set(0); - totalWorlds.set(0); + int queuedPlates = 0; + int totalPlates = 0; + long chunks = 0; + int unloaders = 0; + int trimmers = 0; + int iris = 0; double maxDuration = Long.MIN_VALUE; double minDuration = Long.MAX_VALUE; @@ -149,23 +149,30 @@ public class IrisEngineSVC implements IrisService { var registered = entry.getValue(); if (registered.closed) continue; - totalWorlds.incrementAndGet(); - unloaderAlive.addAndGet(registered.unloaderAlive() ? 1 : 0); - trimmerAlive.addAndGet(registered.trimmerAlive() ? 1 : 0); + iris++; + if (registered.unloaderAlive()) unloaders++; + if (registered.trimmerAlive()) trimmers++; var engine = registered.getEngine(); if (engine == null) continue; - queuedTectonicPlates.addAndGet((int) engine.getMantle().getUnloadRegionCount()); - tectonicPlates.addAndGet(engine.getMantle().getLoadedRegionCount()); - loadedChunks.addAndGet(entry.getKey().getLoadedChunks().length); + queuedPlates += engine.getMantle().getUnloadRegionCount(); + totalPlates += engine.getMantle().getLoadedRegionCount(); + chunks += entry.getKey().getLoadedChunks().length; double duration = engine.getMantle().getAdjustedIdleDuration(); if (duration > maxDuration) maxDuration = duration; if (duration < minDuration) minDuration = duration; } + + trimmerAlive.set(trimmers); + unloaderAlive.set(unloaders); + tectonicPlates.set(totalPlates); + queuedTectonicPlates.set(queuedPlates); maxIdleDuration.set(maxDuration); minIdleDuration.set(minDuration); + loadedChunks.set(chunks); + totalWorlds.set(iris); worlds.values().forEach(Registered::update); } catch (Throwable e) { diff --git a/core/src/main/java/com/volmit/iris/core/service/WandSVC.java b/core/src/main/java/com/volmit/iris/core/service/WandSVC.java index 8d984ba91..e55991245 100644 --- a/core/src/main/java/com/volmit/iris/core/service/WandSVC.java +++ b/core/src/main/java/com/volmit/iris/core/service/WandSVC.java @@ -90,6 +90,7 @@ public class WandSVC implements IrisService { int total = c.getSizeX() * c.getSizeY() * c.getSizeZ(); var latch = new CountDownLatch(1); + var holder = Iris.tickets.getHolder(p.getWorld()); new Job() { private int i; private Chunk chunk; @@ -108,7 +109,7 @@ public class WandSVC implements IrisService { while (time > M.ms()) { if (!it.hasNext()) { if (chunk != null) { - chunk.removePluginChunkTicket(Iris.instance); + holder.removeTicket(chunk); chunk = null; } @@ -122,9 +123,10 @@ public class WandSVC implements IrisService { var bChunk = b.getChunk(); if (chunk == null) { chunk = bChunk; - chunk.addPluginChunkTicket(Iris.instance); + holder.addTicket(chunk); } else if (chunk != bChunk) { - chunk.removePluginChunkTicket(Iris.instance); + holder.removeTicket(chunk); + holder.addTicket(bChunk); chunk = bChunk; } diff --git a/core/src/main/java/com/volmit/iris/engine/IrisWorldManager.java b/core/src/main/java/com/volmit/iris/engine/IrisWorldManager.java index 1edfee1f1..9e2d50a59 100644 --- a/core/src/main/java/com/volmit/iris/engine/IrisWorldManager.java +++ b/core/src/main/java/com/volmit/iris/engine/IrisWorldManager.java @@ -440,7 +440,7 @@ public class IrisWorldManager extends EngineAssignedWorldManager { //INMS.get().injectBiomesFromMantle(e, getMantle()); if (!IrisSettings.get().getGenerator().earlyCustomBlocks) return; - e.addPluginChunkTicket(Iris.instance); + Iris.tickets.addTicket(e); J.s(() -> { var chunk = getMantle().getChunk(e).use(); int minY = getTarget().getWorld().minHeight(); @@ -452,7 +452,7 @@ public class IrisWorldManager extends EngineAssignedWorldManager { }); } finally { chunk.release(); - e.removePluginChunkTicket(Iris.instance); + Iris.tickets.removeTicket(e); } }, RNG.r.i(20, 60)); } diff --git a/core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java b/core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java index 00d9c2015..0ebe6cc53 100644 --- a/core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java +++ b/core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java @@ -249,7 +249,7 @@ public interface EngineMantle extends MatterGenerator { } } - default long getUnloadRegionCount() { + default int getUnloadRegionCount() { return getMantle().getUnloadRegionCount(); } diff --git a/core/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java b/core/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java index fe991f753..54bfefd5a 100644 --- a/core/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java +++ b/core/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java @@ -207,7 +207,7 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun Chunk c = PaperLib.getChunkAtAsync(world, x, z) .thenApply(d -> { - d.addPluginChunkTicket(Iris.instance); + Iris.tickets.addTicket(d); for (Entity ee : d.getEntities()) { if (ee instanceof Player) { @@ -217,7 +217,6 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun ee.remove(); } - engine.getWorldManager().onChunkLoad(d, false); return d; }).get(); @@ -243,7 +242,7 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) .thenRunAsync(() -> { - c.removePluginChunkTicket(Iris.instance); + Iris.tickets.removeTicket(c); engine.getWorldManager().onChunkLoad(c, true); }, syncExecutor) .get(); diff --git a/core/src/main/java/com/volmit/iris/util/plugin/chunk/ChunkTickets.java b/core/src/main/java/com/volmit/iris/util/plugin/chunk/ChunkTickets.java new file mode 100644 index 000000000..320c74436 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/util/plugin/chunk/ChunkTickets.java @@ -0,0 +1,57 @@ +package com.volmit.iris.util.plugin.chunk; + +import com.volmit.iris.Iris; +import lombok.NonNull; +import org.bukkit.Bukkit; +import org.bukkit.Chunk; +import org.bukkit.World; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.world.WorldLoadEvent; +import org.bukkit.event.world.WorldUnloadEvent; + +import java.util.HashMap; +import java.util.Map; + +public class ChunkTickets implements Listener { + private final Map holders = new HashMap<>(); + + public ChunkTickets() { + Iris.instance.registerListener(this); + Bukkit.getWorlds().forEach(w -> holders.put(w, new TicketHolder(w))); + } + + public TicketHolder getHolder(@NonNull World world) { + return holders.get(world); + } + + public void addTicket(@NonNull Chunk chunk) { + addTicket(chunk.getWorld(), chunk.getX(), chunk.getZ()); + } + + public void addTicket(@NonNull World world, int x, int z) { + var holder = getHolder(world); + if (holder != null) holder.addTicket(x, z); + } + + public boolean removeTicket(@NonNull Chunk chunk) { + return removeTicket(chunk.getWorld(), chunk.getX(), chunk.getZ()); + } + + public boolean removeTicket(@NonNull World world, int x, int z) { + var holder = getHolder(world); + if (holder != null) return holder.removeTicket(x, z); + return false; + } + + @EventHandler(priority = EventPriority.LOWEST) + public void on(@NonNull WorldLoadEvent event) { + holders.put(event.getWorld(), new TicketHolder(event.getWorld())); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(@NonNull WorldUnloadEvent event) { + holders.remove(event.getWorld()); + } +} diff --git a/core/src/main/java/com/volmit/iris/util/plugin/chunk/TicketHolder.java b/core/src/main/java/com/volmit/iris/util/plugin/chunk/TicketHolder.java new file mode 100644 index 000000000..abf521c91 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/util/plugin/chunk/TicketHolder.java @@ -0,0 +1,48 @@ +package com.volmit.iris.util.plugin.chunk; + +import com.volmit.iris.Iris; +import com.volmit.iris.engine.data.cache.Cache; +import com.volmit.iris.util.collection.KMap; +import lombok.NonNull; +import org.bukkit.Chunk; +import org.bukkit.World; + +public class TicketHolder { + private final World world; + private final KMap tickets = new KMap<>(); + + public TicketHolder(@NonNull World world) { + this.world = world; + } + + public void addTicket(@NonNull Chunk chunk) { + if (chunk.getWorld() != world) return; + addTicket(chunk.getX(), chunk.getZ()); + } + + public void addTicket(int x, int z) { + tickets.compute(Cache.key(x, z), ($, ref) -> { + if (ref == null) { + world.addPluginChunkTicket(x, z, Iris.instance); + return 1L; + } + return ++ref; + }); + } + + public boolean removeTicket(@NonNull Chunk chunk) { + if (chunk.getWorld() != world) return false; + return removeTicket(chunk.getX(), chunk.getZ()); + } + + public boolean removeTicket(int x, int z) { + return tickets.compute(Cache.key(x, z), ($, ref) -> { + if (ref == null) return null; + if (--ref <= 0) { + world.removePluginChunkTicket(x, z, Iris.instance); + return null; + } + return ref; + }) == null; + } +}