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 5b529a410..df1bf9ba8 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,9 @@ 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 java.io.File; import java.io.IOException; @@ -42,6 +44,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) { if (System.getProperty("os.name").toLowerCase().contains("win")) @@ -147,6 +150,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/commands/CommandUpdater.java b/core/src/main/java/com/volmit/iris/core/commands/CommandUpdater.java index bd910ad5d..38aba40a5 100644 --- a/core/src/main/java/com/volmit/iris/core/commands/CommandUpdater.java +++ b/core/src/main/java/com/volmit/iris/core/commands/CommandUpdater.java @@ -43,6 +43,10 @@ public class CommandUpdater implements DecreeExecutor { sender().sendMessage(C.GOLD + "This is not an Iris world"); return; } + if (chunkUpdater != null) { + chunkUpdater.stop(); + } + chunkUpdater = new ChunkUpdater(world); if (sender().isPlayer()) { sender().sendMessage(C.GREEN + "Updating " + world.getName() + C.GRAY + " Total chunks: " + Form.f(chunkUpdater.getChunks())); @@ -53,14 +57,7 @@ public class CommandUpdater implements DecreeExecutor { } @Decree(description = "Pause the updater") - public void pause( - @Param(description = "World to pause the Updater at") - World world - ) { - if (!IrisToolbelt.isIrisWorld(world)) { - sender().sendMessage(C.GOLD + "This is not an Iris world"); - return; - } + public void pause( ) { if (chunkUpdater == null) { sender().sendMessage(C.GOLD + "You cant pause something that doesnt exist?"); return; @@ -68,40 +65,32 @@ public class CommandUpdater implements DecreeExecutor { boolean status = chunkUpdater.pause(); if (sender().isPlayer()) { if (status) { - sender().sendMessage(C.IRIS + "Paused task for: " + C.GRAY + world.getName()); + sender().sendMessage(C.IRIS + "Paused task for: " + C.GRAY + chunkUpdater.getName()); } else { - sender().sendMessage(C.IRIS + "Unpause task for: " + C.GRAY + world.getName()); + sender().sendMessage(C.IRIS + "Unpause task for: " + C.GRAY + chunkUpdater.getName()); } } else { if (status) { - Iris.info(C.IRIS + "Paused task for: " + C.GRAY + world.getName()); + Iris.info(C.IRIS + "Paused task for: " + C.GRAY + chunkUpdater.getName()); } else { - Iris.info(C.IRIS + "Unpause task for: " + C.GRAY + world.getName()); + Iris.info(C.IRIS + "Unpause task for: " + C.GRAY + chunkUpdater.getName()); } } } @Decree(description = "Stops the updater") - public void stop( - @Param(description = "World to stop the Updater at") - World world - ) { - if (!IrisToolbelt.isIrisWorld(world)) { - sender().sendMessage(C.GOLD + "This is not an Iris world"); - return; - } + public void stop() { if (chunkUpdater == null) { sender().sendMessage(C.GOLD + "You cant stop something that doesnt exist?"); return; } if (sender().isPlayer()) { - sender().sendMessage("Stopping Updater for: " + C.GRAY + world.getName()); + sender().sendMessage("Stopping Updater for: " + C.GRAY + chunkUpdater.getName()); } else { - Iris.info("Stopping Updater for: " + C.GRAY + world.getName()); + Iris.info("Stopping Updater for: " + C.GRAY + chunkUpdater.getName()); } chunkUpdater.stop(); } - } 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 d89ad3340..b7d90ed95 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,16 +1,21 @@ 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.tools.IrisToolbelt; +import com.volmit.iris.engine.data.cache.Cache; import com.volmit.iris.engine.framework.Engine; -import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.format.Form; +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.math.Spiraler; +import com.volmit.iris.util.profile.LoadBalancer; import com.volmit.iris.util.scheduling.J; import io.papermc.lib.PaperLib; +import org.bukkit.Bukkit; import org.bukkit.Chunk; import org.bukkit.World; @@ -23,53 +28,40 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; public class ChunkUpdater { - private AtomicBoolean paused; - private AtomicBoolean cancelled; - private KMap lastUse; - private final RollingSequence chunksPerSecond; - private final AtomicInteger worldheightsize; - private final AtomicInteger worldwidthsize; - private final AtomicInteger totalChunks; - private final AtomicInteger totalMaxChunks; - private final AtomicInteger totalMcaregions; - private final AtomicInteger position; - private AtomicInteger chunksProcessed; - private AtomicInteger chunksUpdated; - private AtomicLong startTime; - private ExecutorService executor; - private ExecutorService chunkExecutor; - private ScheduledExecutorService scheduler; - private CompletableFuture future; - private CountDownLatch latch; - private final Object pauseLock; + private final AtomicBoolean paused = new AtomicBoolean(); + private final AtomicBoolean cancelled = new AtomicBoolean(); + private final KMap> lastUse = new KMap<>(); + private final RollingSequence chunksPerSecond = new RollingSequence(5); + private final AtomicInteger totalMaxChunks = new AtomicInteger(); + 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() * IrisSettings.get().getUpdater().getThreadMultiplier(), 1); + private final Semaphore semaphore = new 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 = Executors.newScheduledThreadPool(1); + private final CountDownLatch latch; private final Engine engine; private final World world; public ChunkUpdater(World world) { this.engine = IrisToolbelt.access(world).getEngine(); - this.chunksPerSecond = new RollingSequence(5); this.world = world; - this.lastUse = new KMap(); - this.worldheightsize = new AtomicInteger(calculateWorldDimensions(new File(world.getWorldFolder(), "region"), 1)); - this.worldwidthsize = new AtomicInteger(calculateWorldDimensions(new File(world.getWorldFolder(), "region"), 0)); - int m = Math.max(worldheightsize.get(), worldwidthsize.get()); - this.executor = Executors.newFixedThreadPool(Math.max(Runtime.getRuntime().availableProcessors() / 3, 1)); - this.chunkExecutor = Executors.newFixedThreadPool(Math.max(Runtime.getRuntime().availableProcessors() / 3, 1)); - this.scheduler = Executors.newScheduledThreadPool(1); - this.future = new CompletableFuture<>(); - this.startTime = new AtomicLong(); - this.worldheightsize.set(m); - this.worldwidthsize.set(m); - this.totalMaxChunks = new AtomicInteger((worldheightsize.get() / 16) * (worldwidthsize.get() / 16)); - this.chunksProcessed = new AtomicInteger(); - this.chunksUpdated = new AtomicInteger(); - this.position = new AtomicInteger(0); + this.dimensions = calculateWorldDimensions(new File(world.getWorldFolder(), "region")); + this.task = dimensions.task(); + this.totalMaxChunks.set(dimensions.count * 1024); this.latch = new CountDownLatch(totalMaxChunks.get()); - this.paused = new AtomicBoolean(false); - this.pauseLock = new Object(); - this.cancelled = new AtomicBoolean(false); - this.totalChunks = new AtomicInteger(0); - this.totalMcaregions = new AtomicInteger(0); + } + + public String getName() { + return world.getName(); } public int getChunks() { @@ -97,7 +89,6 @@ public class ChunkUpdater { cancelled.set(true); } - private void update() { Iris.info("Updating.."); try { @@ -106,11 +97,11 @@ public class ChunkUpdater { try { if (!paused.get()) { long eta = computeETA(); - long elapsedSeconds = (System.currentTimeMillis() - startTime.get()) / 1000; int processed = chunksProcessed.get(); - double cps = elapsedSeconds > 0 ? processed / (double) elapsedSeconds : 0; + double last = processed - chunksProcessedLast.getAndSet(processed); + double cps = last / ((M.ms() - lastCpsTime.getAndSet(M.ms())) / 1000d); chunksPerSecond.put(cps); - double percentage = ((double) chunksProcessed.get() / (double) totalMaxChunks.get()) * 100; + double percentage = ((double) processed / (double) totalMaxChunks.get()) * 100; if (!cancelled.get()) { Iris.info("Updated: " + Form.f(processed) + " of " + Form.f(totalMaxChunks.get()) + " (%.0f%%) " + Form.f(chunksPerSecond.getAverage()) + "/s, ETA: " + Form.duration(eta, 2), percentage); @@ -120,35 +111,20 @@ 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) + return; + loadBalancer.setRange(empty ? IrisSettings.get().getUpdater().emptyMsRange : IrisSettings.get().getUpdater().defaultMsRange); + }, 0, 10, TimeUnit.SECONDS); - CompletableFuture.runAsync(() -> { - for (int i = 0; i < totalMaxChunks.get(); i++) { - if (paused.get()) { - synchronized (pauseLock) { - try { - pauseLock.wait(); - } catch (InterruptedException e) { - Iris.error("Interrupted while waiting for executor: "); - e.printStackTrace(); - break; - } - } - } - executor.submit(() -> { - if (!cancelled.get()) { - processNextChunk(); - } - latch.countDown(); - }); - } - }).thenRun(() -> { - try { - latch.await(); - close(); - } catch (Exception e) { - Thread.currentThread().interrupt(); - } - }); + var t = new Thread(() -> { + run(); + close(); + }, "Iris Chunk Updater - " + world.getName()); + t.setPriority(Thread.MAX_PRIORITY); + t.start(); } catch (Exception e) { e.printStackTrace(); @@ -157,14 +133,16 @@ public class ChunkUpdater { public void close() { try { - unloadAndSaveAllChunks(); + loadBalancer.close(); + semaphore.acquire(256); + executor.shutdown(); executor.awaitTermination(5, TimeUnit.SECONDS); chunkExecutor.shutdown(); chunkExecutor.awaitTermination(5, TimeUnit.SECONDS); scheduler.shutdownNow(); - } catch (Exception ignored) { - } + unloadAndSaveAllChunks(); + } catch (Exception ignored) {} if (cancelled.get()) { Iris.info("Updated: " + Form.f(chunksUpdated.get()) + " Chunks"); Iris.info("Irritated: " + Form.f(chunksProcessed.get()) + " of " + Form.f(totalMaxChunks.get())); @@ -175,18 +153,69 @@ public class ChunkUpdater { } } - private void processNextChunk() { - int pos = position.getAndIncrement(); - int[] coords = getChunk(pos); - if (loadChunksIfGenerated(coords[0], coords[1])) { - Chunk c = world.getChunkAt(coords[0], coords[1]); - engine.updateChunk(c); - chunksUpdated.incrementAndGet(); + private void run() { + task.iterateRegions((rX, rZ) -> { + if (cancelled.get()) + return; + + while (paused.get()) { + J.sleep(50); + } + + if (rX < dimensions.min.getX() || rX > dimensions.max.getX() || rZ < dimensions.min.getZ() || rZ > dimensions.max.getZ()) { + return; + } + + PregenTask.iterateRegion(rX, rZ, (x, z) -> { + while (paused.get() && !cancelled.get()) { + J.sleep(50); + } + + try { + semaphore.acquire(); + } catch (InterruptedException ignored) { + return; + } + chunkExecutor.submit(() -> { + try { + if (!cancelled.get()) + processChunk(x, z); + } finally { + latch.countDown(); + semaphore.release(); + } + }); + }); + }); + } + + private void processChunk(int x, int z) { + if (!loadChunksIfGenerated(x, z)) { + chunksProcessed.getAndIncrement(); + return; + } + + 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(); + } + } + } finally { + chunksUpdated.incrementAndGet(); + chunksProcessed.getAndIncrement(); } - chunksProcessed.getAndIncrement(); } private boolean loadChunksIfGenerated(int x, int z) { + if (engine.getMantle().getMantle().hasFlag(x, z, MantleFlag.ETCHED)) + return false; + for (int dx = -1; dx <= 1; dx++) { for (int dz = -1; dz <= 1; dz++) { if (!PaperLib.isChunkGenerated(world, x + dx, z + dz)) { @@ -196,45 +225,73 @@ public class ChunkUpdater { } AtomicBoolean generated = new AtomicBoolean(true); - KList> futures = new KList<>(9); + CountDownLatch latch = new CountDownLatch(9); for (int dx = -1; dx <= 1; dx++) { for (int dz = -1; dz <= 1; dz++) { int xx = x + dx; int zz = z + dz; - futures.add(chunkExecutor.submit(() -> { - Chunk c; + executor.submit(() -> { try { - c = PaperLib.getChunkAtAsync(world, xx, zz, false).get(); - } catch (InterruptedException | ExecutionException e) { - generated.set(false); - return; - } - if (!c.isLoaded()) { - CountDownLatch latch = new CountDownLatch(1); - J.s(() -> { - c.load(false); - latch.countDown(); - }); + Chunk c; try { - latch.await(); - } catch (InterruptedException ignored) {} + c = PaperLib.getChunkAtAsync(world, xx, zz, false, true) + .thenApply(chunk -> { + chunk.addPluginChunkTicket(Iris.instance); + return chunk; + }).get(); + } catch (InterruptedException | ExecutionException e) { + generated.set(false); + return; + } + + if (!c.isLoaded()) { + var future = J.sfut(() -> c.load(false)); + if (future != null) future.join(); + } + + if (!c.isGenerated()) + 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(); } - if (!c.isGenerated()) { - generated.set(false); - } - lastUse.put(c, M.ms()); - })); + }); } } - while (!futures.isEmpty()) { - futures.removeIf(Future::isDone); - try { - Thread.sleep(50); - } catch (InterruptedException ignored) {} + + try { + latch.await(); + } catch (InterruptedException e) { + Iris.info("Interrupted while waiting for chunks to load"); } return generated.get(); } + 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 unloadAndSaveAllChunks() { try { J.sfut(() -> { @@ -243,13 +300,7 @@ public class ChunkUpdater { return; } - for (Chunk i : new ArrayList<>(lastUse.keySet())) { - Long lastUseTime = lastUse.get(i); - if (lastUseTime != null && M.ms() - lastUseTime >= 5000) { - i.unload(); - lastUse.remove(i); - } - } + unloadChunks(); world.save(); }).get(); } catch (Throwable e) { @@ -266,7 +317,7 @@ public class ChunkUpdater { ); } - public int calculateWorldDimensions(File regionDir, Integer o) { + private Dimensions calculateWorldDimensions(File regionDir) { File[] files = regionDir.listFiles((dir, name) -> name.endsWith(".mca")); int minX = Integer.MAX_VALUE; @@ -279,40 +330,23 @@ public class ChunkUpdater { int x = Integer.parseInt(parts[1]); int z = Integer.parseInt(parts[2]); - if (x < minX) minX = x; - if (x > maxX) maxX = x; - if (z < minZ) minZ = z; - if (z > maxZ) maxZ = z; + minX = Math.min(minX, x); + maxX = Math.max(maxX, x); + minZ = Math.min(minZ, z); + maxZ = Math.max(maxZ, z); } + int oX = minX + ((maxX - minX) / 2); + int oZ = minZ + ((maxZ - minZ) / 2); - int height = (maxX - minX + 1) * 32 * 16; - int width = (maxZ - minZ + 1) * 32 * 16; + int height = maxX - minX + 1; + int width = maxZ - minZ + 1; - if (o == 1) { - return height; - } - if (o == 0) { - return width; - } - return 0; + return new Dimensions(new Position2(minX, minZ), new Position2(maxX, maxZ), height * width, PregenTask.builder() + .width((int) Math.ceil(width / 2d)) + .height((int) Math.ceil(height / 2d)) + .center(new Position2(oX, oZ)) + .build()); } - public int[] getChunk(int position) { - int p = -1; - AtomicInteger xx = new AtomicInteger(); - AtomicInteger zz = new AtomicInteger(); - Spiraler s = new Spiraler(worldheightsize.get() * 2, worldwidthsize.get() * 2, (x, z) -> { - xx.set(x); - zz.set(z); - }); - - while (s.hasNext() && p++ < position) { - s.next(); - } - int[] coords = new int[2]; - coords[0] = xx.get(); - coords[1] = zz.get(); - - return coords; - } + private record Dimensions(Position2 min, Position2 max, int count, PregenTask task) { } } diff --git a/core/src/main/java/com/volmit/iris/engine/framework/Engine.java b/core/src/main/java/com/volmit/iris/engine/framework/Engine.java index 16ee5c994..f49594051 100644 --- a/core/src/main/java/com/volmit/iris/engine/framework/Engine.java +++ b/core/src/main/java/com/volmit/iris/engine/framework/Engine.java @@ -28,6 +28,7 @@ import com.volmit.iris.core.loader.IrisRegistrant; import com.volmit.iris.core.nms.IMemoryWorld; import com.volmit.iris.core.nms.container.BlockPos; import com.volmit.iris.core.nms.container.Pair; +import com.volmit.iris.core.pregenerator.ChunkUpdater; import com.volmit.iris.core.service.ExternalDataSVC; import com.volmit.iris.engine.IrisComplex; import com.volmit.iris.engine.data.cache.Cache; @@ -58,6 +59,7 @@ import com.volmit.iris.util.matter.TileWrapper; import com.volmit.iris.util.matter.slices.container.JigsawPieceContainer; import com.volmit.iris.util.parallel.BurstExecutor; import com.volmit.iris.util.parallel.MultiBurst; +import com.volmit.iris.util.reflect.W; import com.volmit.iris.util.scheduling.ChronoLatch; import com.volmit.iris.util.scheduling.J; import com.volmit.iris.util.scheduling.PrecisionStopwatch; @@ -79,6 +81,7 @@ import java.awt.Color; import java.util.Arrays; import java.util.Set; import java.util.UUID; +import java.util.concurrent.Semaphore; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; @@ -276,33 +279,43 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat for (int z = -1; z <= 1; z++) { if (c.getWorld().isChunkLoaded(c.getX() + x, c.getZ() + z)) continue; - Iris.debug("Chunk %s, %s [%s, %s] is not loaded".formatted(c.getX() + x, c.getZ() + z, x, z)); + var msg = "Chunk %s, %s [%s, %s] is not loaded".formatted(c.getX() + x, c.getZ() + z, x, z); + if (W.getStack().getCallerClass().equals(ChunkUpdater.class)) Iris.warn(msg); + else Iris.debug(msg); return; } } - if (!getMantle().getMantle().isLoaded(c)) { - Iris.debug("Mantle Chunk " + c.getX() + c.getX() + " is not loaded"); + var mantle = getMantle().getMantle(); + if (!mantle.isLoaded(c)) { + var msg = "Mantle Chunk " + c.getX() + c.getX() + " is not loaded"; + if (W.getStack().getCallerClass().equals(ChunkUpdater.class)) Iris.warn(msg); + else Iris.debug(msg); return; } - getMantle().getMantle().raiseFlag(c.getX(), c.getZ(), MantleFlag.TILE, () -> J.s(() -> { - getMantle().getMantle().iterateChunk(c.getX(), c.getZ(), TileWrapper.class, (x, y, z, v) -> { + var chunk = mantle.getChunk(c); + if (chunk.isFlagged(MantleFlag.ETCHED)) return; + chunk.flag(MantleFlag.ETCHED, true); + + Semaphore semaphore = new Semaphore(3); + chunk.raiseFlag(MantleFlag.TILE, run(semaphore, () -> J.s(() -> { + mantle.iterateChunk(c.getX(), c.getZ(), TileWrapper.class, (x, y, z, v) -> { int betterY = y + getWorld().minHeight(); if (!TileData.setTileState(c.getBlock(x, betterY, z), v.getData())) Iris.warn("Failed to set tile entity data at [%d %d %d | %s] for tile %s!", x, betterY, z, c.getBlock(x, betterY, z).getBlockData().getMaterial().getKey(), v.getData().getMaterial().name()); }); - })); - getMantle().getMantle().raiseFlag(c.getX(), c.getZ(), MantleFlag.CUSTOM, () -> J.s(() -> { - getMantle().getMantle().iterateChunk(c.getX(), c.getZ(), Identifier.class, (x, y, z, v) -> { + }))); + chunk.raiseFlag(MantleFlag.CUSTOM, run(semaphore, () -> J.s(() -> { + mantle.iterateChunk(c.getX(), c.getZ(), Identifier.class, (x, y, z, v) -> { Iris.service(ExternalDataSVC.class).processUpdate(this, c.getBlock(x & 15, y + getWorld().minHeight(), z & 15), v); }); - })); + }))); - getMantle().getMantle().raiseFlag(c.getX(), c.getZ(), MantleFlag.UPDATE, () -> J.s(() -> { + chunk.raiseFlag(MantleFlag.UPDATE, run(semaphore, () -> J.s(() -> { PrecisionStopwatch p = PrecisionStopwatch.start(); KMap updates = new KMap<>(); RNG r = new RNG(Cache.key(c.getX(), c.getZ())); - getMantle().getMantle().iterateChunk(c.getX(), c.getZ(), MatterCavern.class, (x, yf, z, v) -> { + mantle.iterateChunk(c.getX(), c.getZ(), MatterCavern.class, (x, yf, z, v) -> { int y = yf + getWorld().minHeight(); if (!B.isFluid(c.getBlock(x & 15, y, z & 15).getBlockData())) { return; @@ -332,7 +345,7 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat }); updates.forEach((k, v) -> update(Cache.keyX(k), v, Cache.keyZ(k), c, r)); - getMantle().getMantle().iterateChunk(c.getX(), c.getZ(), MatterUpdate.class, (x, yf, z, v) -> { + mantle.iterateChunk(c.getX(), c.getZ(), MatterUpdate.class, (x, yf, z, v) -> { int y = yf + getWorld().minHeight(); if (v != null && v.isUpdate()) { int vx = x & 15; @@ -343,9 +356,25 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat } } }); - getMantle().getMantle().deleteChunkSlice(c.getX(), c.getZ(), MatterUpdate.class); + mantle.deleteChunkSlice(c.getX(), c.getZ(), MatterUpdate.class); getMetrics().getUpdates().put(p.getMilliseconds()); - }, RNG.r.i(0, 20))); + }, RNG.r.i(0, 20)))); + + try { + semaphore.acquire(3); + } catch (InterruptedException ignored) {} + } + + private static Runnable run(Semaphore semaphore, Runnable runnable) { + return () -> { + if (!semaphore.tryAcquire()) + return; + try { + runnable.run(); + } finally { + semaphore.release(); + } + }; } @BlockCoordinates 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..c92f57a2f --- /dev/null +++ b/core/src/main/java/com/volmit/iris/util/profile/LoadBalancer.java @@ -0,0 +1,70 @@ +package com.volmit.iris.util.profile; + + +import com.volmit.iris.Iris; +import com.volmit.iris.core.IrisSettings; +import com.volmit.iris.util.math.M; +import lombok.Getter; +import lombok.Setter; + +import java.util.concurrent.Semaphore; + +@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(); + + 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; + Iris.debug("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); + } + } +} diff --git a/core/src/main/java/com/volmit/iris/util/reflect/W.java b/core/src/main/java/com/volmit/iris/util/reflect/W.java new file mode 100644 index 000000000..7febb0611 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/util/reflect/W.java @@ -0,0 +1,8 @@ +package com.volmit.iris.util.reflect; + +import lombok.Getter; + +public class W { + @Getter + private static final StackWalker stack = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); +}