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 190420368..b42b21dab 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,7 +2,9 @@ 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.KMap; import com.volmit.iris.util.format.Form; @@ -28,8 +30,7 @@ import java.util.concurrent.atomic.AtomicLong; public class ChunkUpdater { private final AtomicBoolean paused = new AtomicBoolean(); private final AtomicBoolean cancelled = new AtomicBoolean(); - private final KMap lastUse = new KMap<>(); - private final KMap counters = new KMap<>(); + 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(); @@ -58,6 +59,10 @@ public class ChunkUpdater { this.latch = new CountDownLatch(totalMaxChunks.get()); } + public String getName() { + return world.getName(); + } + public int getChunks() { return totalMaxChunks.get(); } @@ -83,7 +88,6 @@ public class ChunkUpdater { cancelled.set(true); } - private void update() { Iris.info("Updating.."); try { @@ -192,9 +196,8 @@ public class ChunkUpdater { for (int xx = -1; xx <= 1; xx++) { for (int zz = -1; zz <= 1; zz++) { - var chunk = world.getChunkAt(x + xx, z + zz, false); - var counter = counters.get(chunk); - if (counter != null) counter.decrementAndGet(); + var counter = lastUse.get(Cache.key(x + xx, z + zz)); + if (counter != null) counter.getB().decrementAndGet(); } } } finally { @@ -243,9 +246,9 @@ public class ChunkUpdater { if (!c.isGenerated()) generated.set(false); - counters.computeIfAbsent(c, k -> new AtomicInteger(-1)) - .updateAndGet(i -> i == -1 ? 1 : ++i); - lastUse.put(c, M.ms()); + 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(); } @@ -262,15 +265,22 @@ public class ChunkUpdater { } private synchronized void unloadChunks() { - for (Chunk i : new ArrayList<>(lastUse.keySet())) { - Long lastUseTime = lastUse.get(i); - var counter = counters.get(i); - if (lastUseTime != null && M.ms() - lastUseTime >= 5000 && (counter == null || counter.get() == 0)) { + 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(() -> { - i.removePluginChunkTicket(Iris.instance); - i.unload(); - lastUse.remove(i); - counters.remove(i); + world.removePluginChunkTicket(x, z, Iris.instance); + world.unloadChunk(x, z); + lastUse.remove(key); }); } } 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 140f4cde9..01123d1ac 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 @@ -82,6 +82,7 @@ import java.awt.*; 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; @@ -289,23 +290,25 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat return; } - if (mantle.hasFlag(c.getX(), c.getZ(), MantleFlag.ETCHED)) return; - mantle.flag(c.getX(), c.getZ(), MantleFlag.ETCHED, true); + var chunk = mantle.getChunk(c); + if (chunk.isFlagged(MantleFlag.ETCHED)) return; + chunk.flag(MantleFlag.ETCHED, true); - mantle.raiseFlag(c.getX(), c.getZ(), MantleFlag.TILE, () -> J.sfut(() -> { + 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()); }); - }).join()); - mantle.raiseFlag(c.getX(), c.getZ(), MantleFlag.CUSTOM, () -> J.sfut(() -> { + }))); + 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); }); - }).join()); + }))); - mantle.raiseFlag(c.getX(), c.getZ(), MantleFlag.UPDATE, () -> J.sfut(() -> { + 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())); @@ -352,7 +355,23 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat }); mantle.deleteChunkSlice(c.getX(), c.getZ(), MatterUpdate.class); getMetrics().getUpdates().put(p.getMilliseconds()); - }, RNG.r.i(0, 20)).join()); + }, 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(); + } +}