diff --git a/build.gradle b/build.gradle index 90fe9be65..0f7f8c7d5 100644 --- a/build.gradle +++ b/build.gradle @@ -237,6 +237,8 @@ allprojects { implementation "net.kyori:adventure-text-minimessage:4.13.1" implementation 'net.kyori:adventure-platform-bukkit:4.3.2' implementation 'net.kyori:adventure-api:4.13.1' + //implementation 'org.bytedeco:javacpp:1.5.10' + //implementation 'org.bytedeco:cuda-platform:12.3-8.9-1.5.10' compileOnly 'io.lumine:Mythic-Dist:5.2.1' // Dynamically Loaded 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 6ec02ef7f..c7f204a52 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 @@ -21,6 +21,7 @@ package com.volmit.iris.core.commands; import com.volmit.iris.Iris; import com.volmit.iris.core.IrisSettings; import com.volmit.iris.core.loader.IrisData; +import com.volmit.iris.core.pregenerator.ChunkUpdater; import com.volmit.iris.core.service.StudioSVC; import com.volmit.iris.core.tools.IrisBenchmarking; import com.volmit.iris.core.tools.IrisToolbelt; @@ -37,6 +38,7 @@ import com.volmit.iris.util.decree.annotations.Decree; import com.volmit.iris.util.decree.annotations.Param; import com.volmit.iris.util.decree.specialhandlers.NullablePlayerHandler; import com.volmit.iris.util.format.C; +import com.volmit.iris.util.format.Form; import com.volmit.iris.util.plugin.VolmitSender; import com.volmit.iris.util.scheduling.J; import lombok.Getter; @@ -317,6 +319,24 @@ 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/pregenerator/ChunkUpdater.java b/core/src/main/java/com/volmit/iris/core/pregenerator/ChunkUpdater.java index 1e40ab566..c9c32726a 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,18 +1,27 @@ package com.volmit.iris.core.pregenerator; import com.volmit.iris.Iris; +import com.volmit.iris.core.tools.IrisToolbelt; +import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.util.collection.KList; +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.nbt.mca.MCAFile; -import com.volmit.iris.util.nbt.mca.MCAUtil; +import io.papermc.lib.PaperLib; +import org.bukkit.Chunk; import org.bukkit.World; import java.io.File; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; public class ChunkUpdater { private AtomicBoolean cancelled; @@ -25,12 +34,19 @@ public class ChunkUpdater { 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 ScheduledExecutorService scheduler; private final File[] McaFiles; + 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.world = world; @@ -38,33 +54,82 @@ public class ChunkUpdater { 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)); - this.totalMaxChunks = new AtomicInteger((worldheightsize.get() / 16 ) * (worldwidthsize.get() / 16)); + int m = Math.max(worldheightsize.get(), worldwidthsize.get()); + this.executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() - 2); + this.scheduler = Executors.newScheduledThreadPool(1); + 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.cancelled = new AtomicBoolean(false); this.totalChunks = new AtomicInteger(0); this.totalMcaregions = new AtomicInteger(0); } - public void start() { - Initialize(); + public int getChunks() { + return totalMaxChunks.get(); } - public void Initialize() { - Iris.info("Initializing.."); + public void start() { + update(); + } + + + private void update() { + Iris.info("Updating.."); try { - for (File mca : McaFiles) { - MCAFile MCARegion = MCAUtil.read(mca); - for (int pos = 0; pos != totalMaxChunks.get(); pos++) { - int[] coords = getChunk(pos); - if(MCARegion.hasChunk(coords[0], coords[1])) chunkMap.add(coords); + 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); + } catch (Exception e) { + e.printStackTrace(); } + }, 0, 3, TimeUnit.SECONDS); + + for (int i = 0; i < totalMaxChunks.get(); i++) { + executor.submit(this::processNextChunk); } - Iris.info("Finished Initializing.."); + + 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(); } } + private void processNextChunk() { + int pos = position.getAndIncrement(); + int[] coords = getChunk(pos); + if (PaperLib.isChunkGenerated(world, coords[0], coords[1])) { + Chunk chunk = world.getChunkAt(coords[0], coords[1]); + engine.updateChunk(chunk); + chunksUpdated.getAndIncrement(); + } + chunksProcessed.getAndIncrement(); + } + + private long computeETA() { + return (long) (totalMaxChunks.get() > 1024 ? // Generated chunks exceed 1/8th of total? + // If yes, use smooth function (which gets more accurate over time since its less sensitive to outliers) + ((totalMaxChunks.get() - chunksProcessed.get()) * ((double) (M.ms() - startTime.get()) / (double) chunksProcessed.get())) : + // If no, use quick function (which is less accurate over time but responds better to the initial delay) + ((totalMaxChunks.get() - chunksProcessed.get()) / chunksPerSecond.getAverage()) * 1000 + ); + } + public int calculateWorldDimensions(File regionDir, Integer o) { File[] files = regionDir.listFiles((dir, name) -> name.endsWith(".mca"));