diff --git a/build.gradle b/build.gradle index ac8ff6afd..a1498523e 100644 --- a/build.gradle +++ b/build.gradle @@ -52,7 +52,7 @@ registerCustomOutputTaskUnix('CrazyDev22LT', '/home/julian/Desktop/server/plugin // ============================================================== def NMS_BINDINGS = Map.of( - "v1_21_R1", "1.21-R0.1-SNAPSHOT", + "v1_21_R1", "1.21.1-R0.1-SNAPSHOT", "v1_20_R4", "1.20.6-R0.1-SNAPSHOT", "v1_20_R3", "1.20.4-R0.1-SNAPSHOT", "v1_20_R2", "1.20.2-R0.1-SNAPSHOT", 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 19c16e6ba..6850db5c9 100644 --- a/core/src/main/java/com/volmit/iris/core/IrisSettings.java +++ b/core/src/main/java/com/volmit/iris/core/IrisSettings.java @@ -25,7 +25,10 @@ import com.volmit.iris.util.json.JSONException; import com.volmit.iris.util.json.JSONObject; import com.volmit.iris.util.plugin.VolmitSender; import com.volmit.iris.util.scheduling.ChronoLatch; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; import java.io.File; import java.io.IOException; @@ -42,6 +45,7 @@ public class IrisSettings { private IrisSettingsConcurrency concurrency = new IrisSettingsConcurrency(); private IrisSettingsStudio studio = new IrisSettingsStudio(); private IrisSettingsPerformance performance = new IrisSettingsPerformance(); + private IrisSettingsUpdater updater = new IrisSettingsUpdater(); public static int getThreadCount(int c) { return switch (c) { @@ -144,6 +148,30 @@ public class IrisSettings { public int scriptLoaderCacheSize = 512; } + @Data + public static class IrisSettingsUpdater { + public double threadMultiplier = 2; + public double chunkLoadSensitivity = 0.7; + public MsRange emptyMsRange = new MsRange(80, 100); + public MsRange defaultMsRange = new MsRange(20, 40); + + public double getThreadMultiplier() { + return Math.min(Math.abs(threadMultiplier), 0.1); + } + + public double getChunkLoadSensitivity() { + return Math.min(chunkLoadSensitivity, 0.9); + } + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class MsRange { + public int min = 20; + public int max = 40; + } + @Data public static class IrisSettingsGeneral { public boolean DoomsdayAnnihilationSelfDestructMode = false; 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 a46536e18..926443193 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 @@ -1,6 +1,7 @@ package com.volmit.iris.core.pregenerator; import com.volmit.iris.Iris; +import com.volmit.iris.core.IrisSettings; import com.volmit.iris.core.tools.IrisToolbelt; import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.util.collection.KMap; @@ -9,9 +10,9 @@ import com.volmit.iris.util.mantle.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.profile.LoadBalancer; import com.volmit.iris.util.scheduling.J; import io.papermc.lib.PaperLib; -import lombok.Data; import org.bukkit.Bukkit; import org.bukkit.Chunk; import org.bukkit.World; @@ -34,16 +35,17 @@ public class ChunkUpdater { private final AtomicInteger chunksProcessed = new AtomicInteger(); private final AtomicInteger chunksProcessedLast = new AtomicInteger(); private final AtomicInteger chunksUpdated = new AtomicInteger(); + private final AtomicBoolean serverEmpty = new AtomicBoolean(true); private final AtomicLong lastCpsTime = new AtomicLong(M.ms()); - private final int coreLimit = (int) Math.max(Runtime.getRuntime().availableProcessors() / getProperty(), 1); + private final int coreLimit = (int) Math.max(Runtime.getRuntime().availableProcessors() * getProperty(), 1); private final Semaphore semaphore = new Semaphore(256); - private final PlayerCounter playerCounter = new PlayerCounter(semaphore, 256); + private final LoadBalancer loadBalancer = new LoadBalancer(semaphore, 256, IrisSettings.get().getUpdater().emptyMsRange); private final AtomicLong startTime = new AtomicLong(); private final Dimensions dimensions; private final PregenTask task; private final ExecutorService executor = Executors.newFixedThreadPool(coreLimit); private final ExecutorService chunkExecutor = Executors.newFixedThreadPool(coreLimit); - private final ScheduledExecutorService scheduler; + private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); private final CountDownLatch latch; private final Engine engine; private final World world; @@ -54,7 +56,6 @@ public class ChunkUpdater { this.dimensions = calculateWorldDimensions(new File(world.getWorldFolder(), "region")); this.task = dimensions.task(); this.totalMaxChunks.set(dimensions.count * 1024); - this.scheduler = Executors.newScheduledThreadPool(1); this.latch = new CountDownLatch(totalMaxChunks.get()); } @@ -107,7 +108,12 @@ public class ChunkUpdater { } }, 0, 3, TimeUnit.SECONDS); scheduler.scheduleAtFixedRate(this::unloadChunks, 0, 1, TimeUnit.SECONDS); - scheduler.scheduleAtFixedRate(playerCounter::update, 0, 5, TimeUnit.SECONDS); + scheduler.scheduleAtFixedRate(() -> { + boolean empty = Bukkit.getOnlinePlayers().isEmpty(); + if (serverEmpty.getAndSet(empty) == empty) + return; + loadBalancer.setRange(empty ? IrisSettings.get().getUpdater().emptyMsRange : IrisSettings.get().getUpdater().defaultMsRange); + }, 0, 10, TimeUnit.SECONDS); var t = new Thread(() -> { run(); @@ -123,7 +129,7 @@ public class ChunkUpdater { public void close() { try { - playerCounter.close(); + loadBalancer.close(); semaphore.acquire(256); executor.shutdown(); @@ -334,39 +340,7 @@ public class ChunkUpdater { private record Dimensions(Position2 min, Position2 max, int count, PregenTask task) { } - @Data - private static class PlayerCounter { - private final Semaphore semaphore; - private final int maxPermits; - private int lastCount = 0; - private int permits = 0; - - public void update() { - double count = Bukkit.getOnlinePlayers().size(); - if (count == lastCount) - return; - double p = count == 0 ? 0 : count / (Bukkit.getMaxPlayers() / 2d); - int targetPermits = (int) (maxPermits * p); - - int diff = targetPermits - permits; - permits = targetPermits; - lastCount = (int) count; - try { - if (diff > 0) semaphore.release(diff); - else semaphore.acquire(Math.abs(diff)); - } catch (InterruptedException ignored) {} - } - - public void close() { - semaphore.release(permits); - } - } - private static double getProperty() { - try { - return Double.parseDouble(System.getProperty("iris.updater")); - } catch (Throwable e) { - return 1; - } + return IrisSettings.get().getUpdater().getThreadMultiplier(); } } diff --git a/core/src/main/java/com/volmit/iris/util/profile/LoadBalancer.java b/core/src/main/java/com/volmit/iris/util/profile/LoadBalancer.java new file mode 100644 index 000000000..b10f18e34 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/util/profile/LoadBalancer.java @@ -0,0 +1,73 @@ +package com.volmit.iris.util.profile; + + +import com.volmit.iris.core.IrisSettings; +import com.volmit.iris.util.math.M; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.java.Log; + +import java.util.concurrent.Future; +import java.util.concurrent.Semaphore; + +@Log +@Getter +public class LoadBalancer extends MsptTimings { + private final Semaphore semaphore; + private final int maxPermits; + private final double range; + @Setter + private int minMspt, maxMspt; + private int permits, lastMspt; + private long lastTime = M.ms(); + private Future future; + + public LoadBalancer(Semaphore semaphore, int maxPermits, IrisSettings.MsRange range) { + this(semaphore, maxPermits, range.getMin(), range.getMax()); + } + + public LoadBalancer(Semaphore semaphore, int maxPermits, int minMspt, int maxMspt) { + this.semaphore = semaphore; + this.maxPermits = maxPermits; + this.minMspt = minMspt; + this.maxMspt = maxMspt; + this.range = maxMspt - minMspt; + setName("LoadBalancer"); + start(); + } + + @Override + protected void update(int raw) { + lastTime = M.ms(); + int mspt = raw; + if (mspt < lastMspt) { + int min = (int) Math.max(lastMspt * IrisSettings.get().getUpdater().getChunkLoadSensitivity(), 1); + mspt = Math.max(mspt, min); + } + lastMspt = mspt; + mspt = Math.max(mspt - minMspt, 0); + double percent = mspt / range; + + int target = (int) (maxPermits * percent); + target = Math.min(target, maxPermits - 20); + + int diff = target - permits; + permits = target; + + if (diff == 0) return; + log.info("Adjusting load to %s (%s) permits (%s mspt, %.2f)".formatted(target, diff, raw, percent)); + + if (diff > 0) semaphore.acquireUninterruptibly(diff); + else semaphore.release(Math.abs(diff)); + } + + public void close() { + interrupt(); + semaphore.release(permits); + } + + public void setRange(IrisSettings.MsRange range) { + minMspt = range.getMin(); + maxMspt = range.getMax(); + } +} diff --git a/core/src/main/java/com/volmit/iris/util/profile/MsptTimings.java b/core/src/main/java/com/volmit/iris/util/profile/MsptTimings.java new file mode 100644 index 000000000..2d098e1e7 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/util/profile/MsptTimings.java @@ -0,0 +1,84 @@ +package com.volmit.iris.util.profile; + +import com.volmit.iris.util.math.M; +import com.volmit.iris.util.scheduling.J; +import com.volmit.iris.util.scheduling.Looper; +import org.bukkit.Bukkit; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; + +public abstract class MsptTimings extends Looper { + private final AtomicInteger currentTick = new AtomicInteger(0); + private int lastTick, lastMspt; + private long lastTime; + private int taskId = -1; + + public MsptTimings() { + setName("MsptTimings"); + setPriority(9); + setDaemon(true); + } + + public static MsptTimings of(Consumer update) { + return new Simple(update); + } + + @Override + protected final long loop() { + if (startTickTask()) + return 200; + + long now = M.ms(); + int tick = currentTick.get(); + int deltaTick = tick - lastTick; + if (deltaTick == 0) + return 200; + lastTick = tick; + int deltaTime = (int) (now - lastTime); + lastTime = now; + int mspt = deltaTime / deltaTick; + mspt -= 50; + mspt = Math.max(mspt, 0); + lastMspt = mspt; + update(mspt); + return 200; + } + + public final int getMspt() { + return lastMspt; + } + + protected abstract void update(int mspt); + + private boolean startTickTask() { + if (taskId != -1 && (Bukkit.getScheduler().isQueued(taskId) || Bukkit.getScheduler().isCurrentlyRunning(taskId))) + return false; + + taskId = J.sr(() -> { + if (isInterrupted()) { + J.csr(taskId); + return; + } + + currentTick.incrementAndGet(); + }, 1); + return taskId != -1; + } + + private static class Simple extends MsptTimings { + private final Consumer update; + + private Simple(Consumer update) { + this.update = update; + start(); + } + + @Override + protected void update(int mspt) { + if (update == null) + return; + update.accept(mspt); + } + } +}