From 9e6963b6ce3a53875258aa0f4993a3d1a6fedabd Mon Sep 17 00:00:00 2001 From: RePixelatedMC Date: Tue, 23 Apr 2024 11:24:53 +0200 Subject: [PATCH] A lot of improvements thanks to CrazyDev! --- .../iris/core/commands/CommandIris.java | 19 +- .../iris/core/commands/CommandUpdater.java | 107 ++++++++++ .../iris/core/pregenerator/ChunkUpdater.java | 186 ++++++++++++------ 3 files changed, 237 insertions(+), 75 deletions(-) create mode 100644 core/src/main/java/com/volmit/iris/core/commands/CommandUpdater.java diff --git a/core/src/main/java/com/volmit/iris/core/commands/CommandIris.java b/core/src/main/java/com/volmit/iris/core/commands/CommandIris.java index c7f204a52..3a6206fc3 100644 --- a/core/src/main/java/com/volmit/iris/core/commands/CommandIris.java +++ b/core/src/main/java/com/volmit/iris/core/commands/CommandIris.java @@ -78,6 +78,7 @@ public class CommandIris implements DecreeExecutor { private CommandWhat what; private CommandEdit edit; private CommandFind find; + private CommandUpdater updater; private CommandDeveloper developer; public static boolean worldCreation = false; String WorldEngine; @@ -319,24 +320,6 @@ public class CommandIris implements DecreeExecutor { return dir.delete(); } - @Decree(description = "Updates all chunk in the specified world") - public void updater( - @Param(description = "World to update chunks at") - World world - ) { - if (!IrisToolbelt.isIrisWorld(world)) { - sender().sendMessage(C.GOLD + "This is not an Iris world"); - return; - } - ChunkUpdater updater = new ChunkUpdater(world); - if (sender().isPlayer()) { - sender().sendMessage(C.GREEN + "Updating " + world.getName() + " Total chunks: " + Form.f(updater.getChunks())); - } else { - Iris.info(C.GREEN + "Updating " + world.getName() + " Total chunks: " + Form.f(updater.getChunks())); - } - updater.start(); - } - @Decree(description = "Set aura spins") public void aura( @Param(description = "The h color value", defaultValue = "-20") 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 new file mode 100644 index 000000000..cb1ff6eeb --- /dev/null +++ b/core/src/main/java/com/volmit/iris/core/commands/CommandUpdater.java @@ -0,0 +1,107 @@ +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2022 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.volmit.iris.core.commands; + +import org.bukkit.World; + +import com.volmit.iris.Iris; +import com.volmit.iris.core.pregenerator.ChunkUpdater; +import com.volmit.iris.core.tools.IrisToolbelt; +import com.volmit.iris.util.decree.DecreeExecutor; +import com.volmit.iris.util.decree.DecreeOrigin; +import com.volmit.iris.util.decree.annotations.Decree; +import com.volmit.iris.util.decree.annotations.Param; +import com.volmit.iris.util.format.C; +import com.volmit.iris.util.format.Form; + +@Decree(name = "Updater", origin = DecreeOrigin.BOTH, description = "Iris World Updater") +public class CommandUpdater implements DecreeExecutor { + private ChunkUpdater chunkUpdater; + + @Decree(description = "Updates all chunk in the specified world") + public void start( + @Param(description = "World to update chunks at") + World world + ) { + if (!IrisToolbelt.isIrisWorld(world)) { + sender().sendMessage(C.GOLD + "This is not an Iris world"); + return; + } + chunkUpdater = new ChunkUpdater(world); + if (sender().isPlayer()) { + sender().sendMessage(C.GREEN + "Updating " + world.getName() + C.GRAY + " Total chunks: " + Form.f(chunkUpdater.getChunks())); + } else { + Iris.info(C.GREEN + "Updating " + world.getName() + C.GRAY + " Total chunks: " + Form.f(chunkUpdater.getChunks())); + } + chunkUpdater.start(); + } + + @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; + } + if (chunkUpdater == null) { + sender().sendMessage(C.GOLD + "You cant pause something that doesnt exist?"); + return; + } + boolean status = chunkUpdater.pause(); + if (sender().isPlayer()) { + if (status) { + sender().sendMessage(C.IRIS + "Paused task for: " + C.GRAY + world.getName()); + } else { + sender().sendMessage(C.IRIS + "Unpause task for: " + C.GRAY + world.getName()); + } + } else { + if (status) { + Iris.info(C.IRIS + "Paused task for: " + C.GRAY + world.getName()); + } else { + Iris.info(C.IRIS + "Unpause task for: " + C.GRAY + world.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; + } + 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()); + } else { + Iris.info("Stopping Updater for: " + C.GRAY + world.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 0e783d4c7..d89ad3340 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 @@ -9,14 +9,11 @@ import com.volmit.iris.util.format.Form; import com.volmit.iris.util.math.M; import com.volmit.iris.util.math.RollingSequence; import com.volmit.iris.util.math.Spiraler; -import com.volmit.iris.util.reflect.V; import com.volmit.iris.util.scheduling.J; import io.papermc.lib.PaperLib; -import org.bukkit.Bukkit; import org.bukkit.Chunk; import org.bukkit.World; - import java.io.File; import java.util.ArrayList; @@ -26,10 +23,10 @@ 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 RollingSequence mcaregionsPerSecond; private final AtomicInteger worldheightsize; private final AtomicInteger worldwidthsize; private final AtomicInteger totalChunks; @@ -40,25 +37,26 @@ public class ChunkUpdater { private AtomicInteger chunksUpdated; private AtomicLong startTime; private ExecutorService executor; + private ExecutorService chunkExecutor; private ScheduledExecutorService scheduler; - private final File[] McaFiles; + private CompletableFuture future; + private CountDownLatch latch; + private final Object pauseLock; private final Engine engine; private final World world; public ChunkUpdater(World world) { - File cacheDir = new File("plugins" + File.separator + "iris" + File.separator + "cache"); - File chunkCacheDir = new File("plugins" + File.separator + "iris" + File.separator + "cache" + File.separator + "spiral"); this.engine = IrisToolbelt.access(world).getEngine(); - this.chunksPerSecond = new RollingSequence(10); - this.mcaregionsPerSecond = new RollingSequence(10); + this.chunksPerSecond = new RollingSequence(5); this.world = world; this.lastUse = new KMap(); - this.McaFiles = new File(world.getWorldFolder(), "region").listFiles((dir, name) -> name.endsWith(".mca")); 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(Runtime.getRuntime().availableProcessors() - 2); + 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); @@ -66,6 +64,9 @@ public class ChunkUpdater { this.chunksProcessed = new AtomicInteger(); this.chunksUpdated = new AtomicInteger(); this.position = new AtomicInteger(0); + 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); @@ -80,6 +81,22 @@ public class ChunkUpdater { update(); } + public boolean pause() { + unloadAndSaveAllChunks(); + if (paused.get()) { + paused.set(false); + return false; + } else { + paused.set(true); + return true; + } + } + + public void stop() { + unloadAndSaveAllChunks(); + cancelled.set(true); + } + private void update() { Iris.info("Updating.."); @@ -87,47 +104,89 @@ public class ChunkUpdater { startTime.set(System.currentTimeMillis()); scheduler.scheduleAtFixedRate(() -> { try { - long eta = computeETA(); - long elapsedSeconds = (System.currentTimeMillis() - startTime.get()) / 1000; - int processed = chunksProcessed.get(); - double cps = elapsedSeconds > 0 ? processed / (double) elapsedSeconds : 0; - chunksPerSecond.put(cps); - double percentage = ((double) chunksProcessed.get() / (double) totalMaxChunks.get()) * 100; - Iris.info("Updated: " + Form.f(processed) + " of " + Form.f(totalMaxChunks.get()) + " (%.0f%%) " + Form.f(chunksPerSecond.getAverage()) + "/s, ETA: " + Form.duration(eta, 2), percentage); + 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; + chunksPerSecond.put(cps); + double percentage = ((double) chunksProcessed.get() / (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); + } + } } catch (Exception e) { e.printStackTrace(); } }, 0, 3, TimeUnit.SECONDS); - for (int i = 0; i < totalMaxChunks.get(); i++) { - executor.submit(this::processNextChunk); - } + 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(); + } + }); - executor.shutdown(); - executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS); - scheduler.shutdownNow(); - Iris.info("Processed: " + Form.f(chunksProcessed.get()) + " Chunks"); - Iris.info("Finished Updating: " + Form.f(chunksUpdated.get()) + " Chunks"); } catch (Exception e) { e.printStackTrace(); } } + public void close() { + try { + unloadAndSaveAllChunks(); + executor.shutdown(); + executor.awaitTermination(5, TimeUnit.SECONDS); + chunkExecutor.shutdown(); + chunkExecutor.awaitTermination(5, TimeUnit.SECONDS); + scheduler.shutdownNow(); + } 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())); + Iris.info("Stopped updater."); + } else { + Iris.info("Processed: " + Form.f(chunksProcessed.get()) + " Chunks"); + Iris.info("Finished Updating: " + Form.f(chunksUpdated.get()) + " Chunks"); + } + } + private void processNextChunk() { int pos = position.getAndIncrement(); int[] coords = getChunk(pos); - if (areAllChunksGenerated(coords[0], coords[1])) { - CompletableFuture chunkFuture = loadChunksIfGenerated(coords[0], coords[1]); - chunkFuture.thenAccept(chunk -> { - Chunk c = world.getChunkAt(coords[0], coords[1]); - engine.updateChunk(c); - chunksUpdated.getAndIncrement(); - }); + if (loadChunksIfGenerated(coords[0], coords[1])) { + Chunk c = world.getChunkAt(coords[0], coords[1]); + engine.updateChunk(c); + chunksUpdated.incrementAndGet(); } chunksProcessed.getAndIncrement(); } - private boolean areAllChunksGenerated(int x, int z) { + private boolean loadChunksIfGenerated(int x, int z) { for (int dx = -1; dx <= 1; dx++) { for (int dz = -1; dz <= 1; dz++) { if (!PaperLib.isChunkGenerated(world, x + dx, z + dz)) { @@ -135,34 +194,47 @@ public class ChunkUpdater { } } } - return true; - } - private CompletableFuture loadChunksIfGenerated(int x, int z) { - CompletableFuture future = new CompletableFuture<>(); - if (areAllChunksGenerated(x, z)) { - Runnable task = () -> { - try { - for (int dx = -1; dx <= 1; dx++) { - for (int dz = -1; dz <= 1; dz++) { - Chunk c = world.getChunkAt(x + dx, z + dz); - world.loadChunk(c); - lastUse.put(c, M.ms()); - } + AtomicBoolean generated = new AtomicBoolean(true); + KList> futures = new KList<>(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; + try { + c = PaperLib.getChunkAtAsync(world, xx, zz, false).get(); + } catch (InterruptedException | ExecutionException e) { + generated.set(false); + return; } - future.complete(null); - } catch (Exception e) { - future.completeExceptionally(e); - } - }; - Bukkit.getScheduler().runTask(Iris.instance, task); - } else { - future.completeExceptionally(new IllegalStateException("Not all chunks are generated")); + if (!c.isLoaded()) { + CountDownLatch latch = new CountDownLatch(1); + J.s(() -> { + c.load(false); + latch.countDown(); + }); + try { + latch.await(); + } catch (InterruptedException ignored) {} + } + if (!c.isGenerated()) { + generated.set(false); + } + lastUse.put(c, M.ms()); + })); + } } - return future; + while (!futures.isEmpty()) { + futures.removeIf(Future::isDone); + try { + Thread.sleep(50); + } catch (InterruptedException ignored) {} + } + return generated.get(); } - private void unloadAndSaveAllChunks() { try { J.sfut(() -> {