From 54ae026c2b8cbf019335a563d2ff8bda118f4f33 Mon Sep 17 00:00:00 2001 From: RePixelatedMC Date: Fri, 15 Dec 2023 19:06:53 +0100 Subject: [PATCH] - Fixed LazyPregenerator.java and improved it. ( Speed decrease on server restart when lazy starts the job up dont know why) - Mem leak fix only works when there is 1 iris world for now ( working on it ) + A capped performance limit still very alpha like seems stable tho. - Disabled HotDropWorldSVC acts a bit weird sometimes. --- .../iris/core/commands/CommandLazyPregen.java | 28 +++- .../core/pregenerator/LazyPregenerator.java | 118 +++++++++---- .../iris/core/service/HotDropWorldSVC.java | 2 +- .../iris/core/service/IrisEngineSVC.java | 158 ++++++++++++------ .../iris/engine/mantle/EngineMantle.java | 3 + .../com/volmit/iris/util/mantle/Mantle.java | 19 +-- 6 files changed, 215 insertions(+), 113 deletions(-) diff --git a/core/src/main/java/com/volmit/iris/core/commands/CommandLazyPregen.java b/core/src/main/java/com/volmit/iris/core/commands/CommandLazyPregen.java index f906056e9..a3d4c622c 100644 --- a/core/src/main/java/com/volmit/iris/core/commands/CommandLazyPregen.java +++ b/core/src/main/java/com/volmit/iris/core/commands/CommandLazyPregen.java @@ -34,6 +34,7 @@ import org.bukkit.World; import org.bukkit.util.Vector; import java.io.File; +import java.io.IOException; @Decree(name = "lazypregen", aliases = "lazy", description = "Pregenerate your Iris worlds!") public class CommandLazyPregen implements DecreeExecutor { @@ -53,6 +54,14 @@ public class CommandLazyPregen implements DecreeExecutor { ) { worldName = world.getName(); + File worldDirectory = new File(Bukkit.getWorldContainer(), world.getName()); + File lazyFile = new File(worldDirectory, "lazygen.json"); + if (lazyFile.exists()) { + sender().sendMessage(C.BLUE + "Lazy pregen is already in progress"); + Iris.info(C.YELLOW + "Lazy pregen is already in progress"); + return; + } + try { if (sender().isPlayer() && access() == null) { sender().sendMessage(C.RED + "The engine access for this world is null!"); @@ -69,7 +78,6 @@ public class CommandLazyPregen implements DecreeExecutor { .silent(silent) .build(); - File worldDirectory = new File(Bukkit.getWorldContainer(), worldName); File lazyGenFile = new File(worldDirectory, "lazygen.json"); LazyPregenerator pregenerator = new LazyPregenerator(pregenJob, lazyGenFile); pregenerator.start(); @@ -85,20 +93,26 @@ public class CommandLazyPregen implements DecreeExecutor { } @Decree(description = "Stop the active pregeneration task", aliases = "x") - public void stop() { + public void stop( + @Param(aliases = "world", description = "The world to pause") + World world + ) throws IOException { if (LazyPregenerator.getInstance() != null) { - LazyPregenerator.getInstance().shutdownInstance(); - Iris.info( C.BLUE + "Shutting down all Lazy Pregens"); + LazyPregenerator.getInstance().shutdownInstance(world); + sender().sendMessage(C.LIGHT_PURPLE + "Closed lazygen instance for " + world.getName()); } else { sender().sendMessage(C.YELLOW + "No active pregeneration tasks to stop"); } } @Decree(description = "Pause / continue the active pregeneration task", aliases = {"t", "resume", "unpause"}) - public void pause() { + public void pause( + @Param(aliases = "world", description = "The world to pause") + World world + ) { if (LazyPregenerator.getInstance() != null) { - LazyPregenerator.getInstance().setPausedLazy(); - sender().sendMessage(C.GREEN + "Paused/unpaused Lazy Pregen, now: " + (LazyPregenerator.getInstance().isPausedLazy() ? "Paused" : "Running") + "."); + LazyPregenerator.getInstance().setPausedLazy(world); + sender().sendMessage(C.GREEN + "Paused/unpaused Lazy Pregen, now: " + (LazyPregenerator.getInstance().isPausedLazy(world) ? "Paused" : "Running") + "."); } else { sender().sendMessage(C.YELLOW + "No active Lazy Pregen tasks to pause/unpause."); diff --git a/core/src/main/java/com/volmit/iris/core/pregenerator/LazyPregenerator.java b/core/src/main/java/com/volmit/iris/core/pregenerator/LazyPregenerator.java index d299fd2ec..d3ee26b54 100644 --- a/core/src/main/java/com/volmit/iris/core/pregenerator/LazyPregenerator.java +++ b/core/src/main/java/com/volmit/iris/core/pregenerator/LazyPregenerator.java @@ -2,8 +2,6 @@ package com.volmit.iris.core.pregenerator; import com.google.gson.Gson; import com.volmit.iris.Iris; -import com.volmit.iris.core.IrisSettings; -import com.volmit.iris.core.gui.PregeneratorJob; import com.volmit.iris.util.format.C; import com.volmit.iris.util.format.Form; import com.volmit.iris.util.io.IO; @@ -11,11 +9,8 @@ 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.parallel.BurstExecutor; -import com.volmit.iris.util.parallel.MultiBurst; import com.volmit.iris.util.scheduling.ChronoLatch; import com.volmit.iris.util.scheduling.J; -import io.lumine.mythic.bukkit.utils.lib.jooq.False; import io.papermc.lib.PaperLib; import lombok.Builder; import lombok.Data; @@ -25,23 +20,26 @@ import org.bukkit.World; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.world.WorldUnloadEvent; +import org.bukkit.scheduler.BukkitRunnable; import java.io.File; import java.io.IOException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; +import java.util.HashMap; +import java.util.Map; + public class LazyPregenerator extends Thread implements Listener { @Getter private static LazyPregenerator instance; private final LazyPregenJob job; private final File destination; private final int maxPosition; - private final World world; + private World world; private final long rate; private final ChronoLatch latch; private static AtomicInteger lazyGeneratedChunks; @@ -49,6 +47,10 @@ public class LazyPregenerator extends Thread implements Listener { private final AtomicInteger lazyTotalChunks; private final AtomicLong startTime; private final RollingSequence chunksPerSecond; + private final RollingSequence chunksPerMinute; + + // A map to keep track of jobs for each world + private static final Map jobs = new HashMap<>(); public LazyPregenerator(LazyPregenJob job, File destination) { this.job = job; @@ -56,13 +58,15 @@ public class LazyPregenerator extends Thread implements Listener { this.maxPosition = new Spiraler(job.getRadiusBlocks() * 2, job.getRadiusBlocks() * 2, (x, z) -> { }).count(); this.world = Bukkit.getWorld(job.getWorld()); - this.rate = Math.round((1D / (job.chunksPerMinute / 60D)) * 1000D); - this.latch = new ChronoLatch(6000); - startTime = new AtomicLong(M.ms()); - chunksPerSecond = new RollingSequence(10); + this.rate = Math.round((1D / (job.getChunksPerMinute() / 60D)) * 1000D); + this.latch = new ChronoLatch(15000); + this.startTime = new AtomicLong(M.ms()); + this.chunksPerSecond = new RollingSequence(10); + this.chunksPerMinute = new RollingSequence(10); lazyGeneratedChunks = new AtomicInteger(0); - generatedLast = new AtomicInteger(0); - lazyTotalChunks = new AtomicInteger((int) Math.ceil(Math.pow((2.0 * job.getRadiusBlocks()) / 16, 2))); + this.generatedLast = new AtomicInteger(0); + this.lazyTotalChunks = new AtomicInteger((int) Math.ceil(Math.pow((2.0 * job.getRadiusBlocks()) / 16, 2))); + jobs.put(job.getWorld(), job); LazyPregenerator.instance = this; } @@ -73,7 +77,6 @@ public class LazyPregenerator extends Thread implements Listener { public static void loadLazyGenerators() { for (World i : Bukkit.getWorlds()) { File lazygen = new File(i.getWorldFolder(), "lazygen.json"); - if (lazygen.exists()) { try { LazyPregenerator p = new LazyPregenerator(lazygen); @@ -107,15 +110,17 @@ public class LazyPregenerator extends Thread implements Listener { } public void tick() { + LazyPregenJob job = jobs.get(world.getName()); if (latch.flip() && !job.paused) { long eta = computeETA(); save(); int secondGenerated = lazyGeneratedChunks.get() - generatedLast.get(); generatedLast.set(lazyGeneratedChunks.get()); - secondGenerated = secondGenerated / 6; + secondGenerated = secondGenerated / 15; chunksPerSecond.put(secondGenerated); + chunksPerMinute.put(secondGenerated * 60); if (!job.isSilent()) { - Iris.info("LazyGen: " + C.IRIS + world.getName() + C.RESET + " RTT: " + Form.f(lazyGeneratedChunks.get()) + " of " + Form.f(lazyTotalChunks.get()) + " " + Form.f((int) chunksPerSecond.getAverage()) + "/s ETA: " + Form.duration((double) eta, 2)); + Iris.info("LazyGen: " + C.IRIS + world.getName() + C.RESET + " RTT: " + Form.f(lazyGeneratedChunks.get()) + " of " + Form.f(lazyTotalChunks.get()) + " " + Form.f((int) chunksPerMinute.getAverage()) + "/m ETA: " + Form.duration((double) eta, 2)); } } @@ -138,12 +143,8 @@ public class LazyPregenerator extends Thread implements Listener { } private long computeETA() { - return (long) (lazyTotalChunks.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) - ((lazyTotalChunks.get() - lazyGeneratedChunks.get()) * ((double) (M.ms() - startTime.get()) / (double) lazyGeneratedChunks.get())) : - // If no, use quick function (which is less accurate over time but responds better to the initial delay) - ((lazyTotalChunks.get() - lazyGeneratedChunks.get()) / chunksPerSecond.getAverage()) * 1000 // - ); + return (long) ((lazyTotalChunks.get() - lazyGeneratedChunks.get()) / chunksPerMinute.getAverage()) * 1000; + // todo broken } private final ExecutorService executorService = Executors.newSingleThreadExecutor(); @@ -154,7 +155,10 @@ public class LazyPregenerator extends Thread implements Listener { if (PaperLib.isPaper()) { PaperLib.getChunkAtAsync(world, chunk.getX(), chunk.getZ(), true) .thenAccept((i) -> { - Iris.verbose("Generated Async " + chunk); + LazyPregenJob j = jobs.get(world.getName()); + if (!j.paused) { + Iris.verbose("Generated Async " + chunk); + } latch.countDown(); }); } else { @@ -201,24 +205,65 @@ public class LazyPregenerator extends Thread implements Listener { } }); } - public void setPausedLazy(){ - if (!job.paused) { - save(); - Iris.info(C.BLUE + "LazyGen: " + C.IRIS + world + C.BLUE + " Paused"); - job.setPaused(true); + + public static void setPausedLazy(World world) { + // todo: doesnt actually pause + LazyPregenJob job = jobs.get(world.getName()); + if (isPausedLazy(world)){ + job.paused = false; } else { - Iris.info(C.BLUE + "LazyGen: " + C.IRIS + world + C.BLUE + " Resumes"); - job.setPaused(false); + job.paused = true; + } + + if ( job.paused) { + Iris.info(C.BLUE + "LazyGen: " + C.IRIS + world.getName() + C.BLUE + " Paused"); + } else { + Iris.info(C.BLUE + "LazyGen: " + C.IRIS + world.getName() + C.BLUE + " Resumed"); } } - public boolean isPausedLazy(){ - return job.isPaused(); + + public static boolean isPausedLazy(World world) { + LazyPregenJob job = jobs.get(world.getName()); + return job != null && job.isPaused(); } - public void shutdownInstance() { - save(); - interrupt(); + + public void shutdownInstance(World world) throws IOException { + Iris.info("LazyGen: " + C.IRIS + world.getName() + C.BLUE + " Shutting down.."); + LazyPregenJob job = jobs.get(world.getName()); + File worldDirectory = new File(Bukkit.getWorldContainer(), world.getName()); + File lazyFile = new File(worldDirectory, "lazygen.json"); + + if (job == null) { + Iris.error("No Lazygen job found for world: " + world.getName()); + return; + } + + try { + if (!job.isPaused()) { + job.setPaused(true); + } + save(); + jobs.remove(world.getName()); + new BukkitRunnable() { + @Override + public void run() { + while (lazyFile.exists()){ + lazyFile.delete(); + J.sleep(1000); + } + Iris.info("LazyGen: " + C.IRIS + world.getName() + C.BLUE + " File deleted and instance closed."); + } + }.runTaskLater(Iris.instance, 20L); + } catch (Exception e) { + Iris.error("Failed to shutdown Lazygen for " + world.getName()); + e.printStackTrace(); + } finally { + saveNow(); + interrupt(); + } } + public void saveNow() throws IOException { IO.writeAll(this.destination, new Gson().toJson(job)); } @@ -232,7 +277,7 @@ public class LazyPregenerator extends Thread implements Listener { @Builder.Default private boolean healing = false; @Builder.Default - private int chunksPerMinute = 32; // 48 hours is roughly 5000 radius + private int chunksPerMinute = 32; @Builder.Default private int radiusBlocks = 5000; @Builder.Default @@ -243,3 +288,4 @@ public class LazyPregenerator extends Thread implements Listener { boolean paused = false; } } + diff --git a/core/src/main/java/com/volmit/iris/core/service/HotDropWorldSVC.java b/core/src/main/java/com/volmit/iris/core/service/HotDropWorldSVC.java index 7018da500..e4b0b7c20 100644 --- a/core/src/main/java/com/volmit/iris/core/service/HotDropWorldSVC.java +++ b/core/src/main/java/com/volmit/iris/core/service/HotDropWorldSVC.java @@ -30,7 +30,7 @@ public class HotDropWorldSVC implements IrisService { @Override public void onEnable() { this.plugin = Iris.instance; - initializeWatchService(); + // initializeWatchService(); } @Override diff --git a/core/src/main/java/com/volmit/iris/core/service/IrisEngineSVC.java b/core/src/main/java/com/volmit/iris/core/service/IrisEngineSVC.java index 700c67bd4..6811ed8fd 100644 --- a/core/src/main/java/com/volmit/iris/core/service/IrisEngineSVC.java +++ b/core/src/main/java/com/volmit/iris/core/service/IrisEngineSVC.java @@ -1,68 +1,120 @@ package com.volmit.iris.core.service; import com.volmit.iris.Iris; -import com.volmit.iris.core.IrisSettings; 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.engine.platform.PlatformChunkGenerator; -import com.volmit.iris.util.collection.KSet; -import com.volmit.iris.util.format.C; -import com.volmit.iris.util.format.Form; -import com.volmit.iris.util.mantle.TectonicPlate; -import com.volmit.iris.util.math.M; import com.volmit.iris.util.misc.getHardware; -import com.volmit.iris.util.parallel.BurstExecutor; -import com.volmit.iris.util.parallel.HyperLock; -import com.volmit.iris.util.parallel.MultiBurst; import com.volmit.iris.util.plugin.IrisService; +import com.volmit.iris.util.scheduling.J; import com.volmit.iris.util.scheduling.Looper; -import io.papermc.lib.PaperLib; -import lombok.Getter; import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.plugin.java.JavaPlugin; -import java.io.File; -import java.io.IOException; import java.util.ArrayList; import java.util.List; -import java.util.Map; +import java.util.Random; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import static com.volmit.iris.util.mantle.Mantle.tectonicLimit; public class IrisEngineSVC implements IrisService { private JavaPlugin plugin; - public Looper ticker; + public Looper ticker1; + public Looper ticker2; + public Looper engineTicker; + public World selectedWorld; public List IrisWorlds = new ArrayList<>(); public List corruptedIrisWorlds = new ArrayList<>(); - @Override public void onEnable() { this.plugin = Iris.instance; - this.IrisStartup(); + tectonicLimit.set(2); + long t = getHardware.getProcessMemory(); + for (; t > 250; ) { + tectonicLimit.getAndAdd(1); + t = t - 250; + } + tectonicLimit.set(10); // DEBUG CODE this.IrisEngine(); - ticker.start(); + engineTicker.start(); + ticker1.start(); + ticker2.start(); + } + + private final AtomicReference selectedWorldRef = new AtomicReference<>(); + + public CompletableFuture initializeAsync() { + return CompletableFuture.supplyAsync(() -> { + World selectedWorld = null; + while (selectedWorld == null) { + synchronized (this) { + IrisWorlds.clear(); + for (World w : Bukkit.getServer().getWorlds()) { + if (IrisToolbelt.access(w) != null) { + IrisWorlds.add(w); + } + } + if (!IrisWorlds.isEmpty()) { + Random rand = new Random(); + int randomIndex = rand.nextInt(IrisWorlds.size()); + selectedWorld = IrisWorlds.get(randomIndex); + } + } + if (selectedWorld == null) { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return null; + } + } + } + return selectedWorld; + }); } public void IrisEngine(){ - ticker = new Looper() { + engineTicker = new Looper() { @Override protected long loop() { try { - for (World world : IrisWorlds) { - Engine engine = IrisToolbelt.access(world).getEngine(); - } - PlatformChunkGenerator generator = IrisToolbelt.access(Bukkit.getWorld("localmemtest")); - if (generator != null && generator.getEngine() != null) { - Engine engine = generator.getEngine(); - engine.getMantle().trim(); + World world = selectedWorldRef.get(); + PlatformChunkGenerator generator = IrisToolbelt.access(world); + if(generator == null) { + initializeAsync().thenAcceptAsync(foundWorld -> selectedWorldRef.set(foundWorld)); } else { - Iris.info("localmemtest is nullmem"); + selectedWorld = world; + } + } catch (Throwable e) { + Iris.reportError(e); + e.printStackTrace(); + return -1; + } + + return 1000; + } + + }; + ticker1 = new Looper() { + @Override + protected long loop() { + try { + World world = selectedWorld; + PlatformChunkGenerator generator = IrisToolbelt.access(world); + if(generator != null) { + Engine engine = IrisToolbelt.access(world).getEngine(); + if (generator != null && generator.getEngine() != null) { + engine.getMantle().trim(); + } else { + Iris.info("something is null 1"); + } + } } catch (Throwable e) { Iris.reportError(e); @@ -73,38 +125,36 @@ public class IrisEngineSVC implements IrisService { return 1000; } }; - } - public void IrisStartup(){ - tectonicLimit.set(2); - long t = getHardware.getProcessMemory(); - for (; t > 250; ) { - tectonicLimit.getAndAdd(1); - t = t - 250; - } - tectonicLimit.set(10); // DEBUG CODE - - for (World w : Bukkit.getServer().getWorlds()) { - File container = Bukkit.getWorldContainer(); - Bukkit.getWorldContainer(); - if(IrisToolbelt.access(w) != null){ - IrisWorlds.add(w); - } else { - File worldDirectory = new File(container, w.getName()); - File IrisWorldTest = new File(worldDirectory, "Iris"); - if (IrisWorldTest.exists()){ - if(IrisToolbelt.access(w) == null){ - corruptedIrisWorlds.add(w); + ticker2 = new Looper() { + @Override + protected long loop() { + try { + World world = selectedWorld; + PlatformChunkGenerator generator = IrisToolbelt.access(world); + if(generator != null) { + Engine engine = IrisToolbelt.access(world).getEngine(); + if (generator != null && generator.getEngine() != null) { + engine.getMantle().unloadTectonicPlate(); + } else { + Iris.info("something is null 2"); + } } + } catch (Throwable e) { + Iris.reportError(e); + e.printStackTrace(); + return -1; } + return 1000; } - } + }; } @Override public void onDisable() { - ticker.interrupt(); - + ticker1.interrupt(); + ticker2.interrupt(); + engineTicker.interrupt(); } } diff --git a/core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java b/core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java index 1a22f797c..c6d234c2e 100644 --- a/core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java +++ b/core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java @@ -178,6 +178,9 @@ public interface EngineMantle extends IObjectPlacer { default void trim() { getMantle().trim(TimeUnit.SECONDS.toMillis(IrisSettings.get().getPerformance().getMantleKeepAlive())); } + default void unloadTectonicPlate(){ + getMantle().unloadTectonicPlate(); + } default MultiBurst burst() { return getEngine().burst(); diff --git a/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java b/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java index ed4267554..b67d52812 100644 --- a/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java +++ b/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java @@ -91,12 +91,6 @@ public class Mantle { loadedRegions = new KMap<>(); lastUse = new KMap<>(); ioBurst = MultiBurst.burst; - if (!ioTectonicUnload.get()) { - this.unloadTectonicPlate(); - if (!ticker.isAlive()) { - ticker.start(); - } - } Iris.debug("Opened The Mantle " + C.DARK_AQUA + dataFolder.getAbsolutePath()); } @@ -421,7 +415,7 @@ public class Mantle { if (closed.get()) { throw new RuntimeException("The Mantle is closed"); } - Iris.info(C.BLUE + "TECTONIC TRIM HAS RUN"); + Iris.debug(C.BLUE + "TECTONIC TRIM HAS RUN"); if (IrisSettings.get().getPerformance().getAggressiveTectonicThreshold() == -1) { forceAggressiveThreshold.set(tectonicLimit.get()); } else { @@ -493,12 +487,10 @@ public class Mantle { } } - protected void unloadTectonicPlate() { - ticker = new Looper() { - protected long loop() { + public synchronized void unloadTectonicPlate() { long time = System.currentTimeMillis(); try { - Iris.info(C.DARK_BLUE + "TECTONIC UNLOAD HAS RUN"); + Iris.debug(C.DARK_BLUE + "TECTONIC UNLOAD HAS RUN"); int threadCount = 1; ExecutorService executorService = Executors.newFixedThreadPool(threadCount); List toUnloadList; @@ -537,11 +529,8 @@ public class Mantle { executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS); } catch (Exception e) { e.printStackTrace(); - return -1; } - return Math.max(0, 1000-(System.currentTimeMillis()-time)); - } - }; + ioTectonicUnload.set(true); }