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 681cbeca2..30a7b089d 100644 --- a/core/src/main/java/com/volmit/iris/core/IrisSettings.java +++ b/core/src/main/java/com/volmit/iris/core/IrisSettings.java @@ -184,6 +184,7 @@ public class IrisSettings { public static class IrisSettingsStudio { public boolean studio = true; public boolean openVSCode = true; + public boolean displayTrueHeight = false; public boolean disableTimeAndWeather = true; public boolean autoStartDefaultStudio = false; } diff --git a/core/src/main/java/com/volmit/iris/core/commands/CommandDeepSearch.java b/core/src/main/java/com/volmit/iris/core/commands/CommandDeepSearch.java new file mode 100644 index 000000000..96c91305f --- /dev/null +++ b/core/src/main/java/com/volmit/iris/core/commands/CommandDeepSearch.java @@ -0,0 +1,134 @@ +/* + * 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 com.volmit.iris.Iris; +import com.volmit.iris.core.pregenerator.DeepSearchPregenerator; +import com.volmit.iris.core.pregenerator.PregenTask; +import com.volmit.iris.core.pregenerator.TurboPregenerator; +import com.volmit.iris.core.tools.IrisToolbelt; +import com.volmit.iris.util.data.Dimension; +import com.volmit.iris.util.decree.DecreeExecutor; +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.math.Position2; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.util.Vector; + +import java.io.File; +import java.io.IOException; + +@Decree(name = "DeepSearch", aliases = "search", description = "Pregenerate your Iris worlds!") +public class CommandDeepSearch implements DecreeExecutor { + public String worldName; + @Decree(description = "DeepSearch a world") + public void start( + @Param(description = "The radius of the pregen in blocks", aliases = "size") + int radius, + @Param(description = "The world to pregen", contextual = true) + World world, + @Param(aliases = "middle", description = "The center location of the pregen. Use \"me\" for your current location", defaultValue = "0,0") + Vector center + ) { + + worldName = world.getName(); + File worldDirectory = new File(Bukkit.getWorldContainer(), world.getName()); + File TurboFile = new File(worldDirectory, "DeepSearch.json"); + if (TurboFile.exists()) { + if (DeepSearchPregenerator.getInstance() != null) { + sender().sendMessage(C.BLUE + "DeepSearch is already in progress"); + Iris.info(C.YELLOW + "DeepSearch is already in progress"); + return; + } else { + try { + TurboFile.delete(); + } catch (Exception e){ + Iris.error("Failed to delete the old instance file of DeepSearch!"); + return; + } + } + } + + try { + if (sender().isPlayer() && access() == null) { + sender().sendMessage(C.RED + "The engine access for this world is null!"); + sender().sendMessage(C.RED + "Please make sure the world is loaded & the engine is initialized. Generate a new chunk, for example."); + } + + DeepSearchPregenerator.DeepSearchJob DeepSearchJob = DeepSearchPregenerator.DeepSearchJob.builder() + .world(worldName) + .radiusBlocks(radius) + .position(0) + .build(); + + File SearchGenFile = new File(worldDirectory, "DeepSearch.json"); + DeepSearchPregenerator pregenerator = new DeepSearchPregenerator(DeepSearchJob, SearchGenFile); + pregenerator.start(); + + String msg = C.GREEN + "DeepSearch started in " + C.GOLD + worldName + C.GREEN + " of " + C.GOLD + (radius * 2) + C.GREEN + " by " + C.GOLD + (radius * 2) + C.GREEN + " blocks from " + C.GOLD + center.getX() + "," + center.getZ(); + sender().sendMessage(msg); + Iris.info(msg); + } catch (Throwable e) { + sender().sendMessage(C.RED + "Epic fail. See console."); + Iris.reportError(e); + e.printStackTrace(); + } + } + + @Decree(description = "Stop the active DeepSearch task", aliases = "x") + public void stop(@Param(aliases = "world", description = "The world to pause") World world) throws IOException { + DeepSearchPregenerator DeepSearchInstance = DeepSearchPregenerator.getInstance(); + File worldDirectory = new File(Bukkit.getWorldContainer(), world.getName()); + File turboFile = new File(worldDirectory, "DeepSearch.json"); + + if (DeepSearchInstance != null) { + DeepSearchInstance.shutdownInstance(world); + sender().sendMessage(C.LIGHT_PURPLE + "Closed Turbogen instance for " + world.getName()); + } else if (turboFile.exists() && turboFile.delete()) { + sender().sendMessage(C.LIGHT_PURPLE + "Closed Turbogen instance for " + world.getName()); + } else if (turboFile.exists()) { + Iris.error("Failed to delete the old instance file of Turbo Pregen!"); + } 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( + @Param(aliases = "world", description = "The world to pause") + World world + ) { + if (TurboPregenerator.getInstance() != null) { + TurboPregenerator.setPausedTurbo(world); + sender().sendMessage(C.GREEN + "Paused/unpaused Turbo Pregen, now: " + (TurboPregenerator.isPausedTurbo(world) ? "Paused" : "Running") + "."); + } else { + File worldDirectory = new File(Bukkit.getWorldContainer(), world.getName()); + File TurboFile = new File(worldDirectory, "DeepSearch.json"); + if (TurboFile.exists()){ + TurboPregenerator.loadTurboGenerator(world.getName()); + sender().sendMessage(C.YELLOW + "Started DeepSearch back up!"); + } else { + sender().sendMessage(C.YELLOW + "No active DeepSearch tasks to pause/unpause."); + } + + } + } +} 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 3601b3f11..2470d0efa 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 @@ -72,6 +72,7 @@ public class CommandIris implements DecreeExecutor { private CommandEdit edit; private CommandFind find; private CommandDeveloper developer; + private CommandTurboPregen turboPregen; public static @Getter String BenchDimension; public static boolean worldCreation = false; diff --git a/core/src/main/java/com/volmit/iris/core/commands/CommandStudio.java b/core/src/main/java/com/volmit/iris/core/commands/CommandStudio.java index 41a271c57..621915632 100644 --- a/core/src/main/java/com/volmit/iris/core/commands/CommandStudio.java +++ b/core/src/main/java/com/volmit/iris/core/commands/CommandStudio.java @@ -47,6 +47,7 @@ import com.volmit.iris.util.io.IO; import com.volmit.iris.util.json.JSONArray; import com.volmit.iris.util.json.JSONObject; import com.volmit.iris.util.math.M; +import com.volmit.iris.util.math.Position2; import com.volmit.iris.util.math.RNG; import com.volmit.iris.util.math.Spiraler; import com.volmit.iris.util.noise.CNG; @@ -76,12 +77,14 @@ import java.util.Date; import java.util.Objects; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; @Decree(name = "studio", aliases = {"std", "s"}, description = "Studio Commands", studio = true) public class CommandStudio implements DecreeExecutor { private CommandFind find; private CommandEdit edit; + private CommandDeepSearch deepSearch; public static String hrf(Duration duration) { return duration.toString().substring(2).replaceAll("(\\d[HMS])(?!$)", "$1 ").toLowerCase(); diff --git a/core/src/main/java/com/volmit/iris/core/commands/CommandTurboPregen.java b/core/src/main/java/com/volmit/iris/core/commands/CommandTurboPregen.java new file mode 100644 index 000000000..2e85de859 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/core/commands/CommandTurboPregen.java @@ -0,0 +1,131 @@ +/* + * 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 com.volmit.iris.Iris; +import com.volmit.iris.core.pregenerator.LazyPregenerator; +import com.volmit.iris.core.pregenerator.TurboPregenerator; +import com.volmit.iris.core.pregenerator.TurboPregenerator; +import com.volmit.iris.util.decree.DecreeExecutor; +import com.volmit.iris.util.decree.annotations.Decree; +import com.volmit.iris.util.decree.annotations.Param; +import com.volmit.iris.util.format.C; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.util.Vector; + +import java.io.File; +import java.io.IOException; + +@Decree(name = "turbopregen", aliases = "turbo", description = "Pregenerate your Iris worlds!") +public class CommandTurboPregen implements DecreeExecutor { + public String worldName; + @Decree(description = "Pregenerate a world") + public void start( + @Param(description = "The radius of the pregen in blocks", aliases = "size") + int radius, + @Param(description = "The world to pregen", contextual = true) + World world, + @Param(aliases = "middle", description = "The center location of the pregen. Use \"me\" for your current location", defaultValue = "0,0") + Vector center + ) { + + worldName = world.getName(); + File worldDirectory = new File(Bukkit.getWorldContainer(), world.getName()); + File TurboFile = new File(worldDirectory, "Turbogen.json"); + if (TurboFile.exists()) { + if (TurboPregenerator.getInstance() != null) { + sender().sendMessage(C.BLUE + "Turbo pregen is already in progress"); + Iris.info(C.YELLOW + "Turbo pregen is already in progress"); + return; + } else { + try { + TurboFile.delete(); + } catch (Exception e){ + Iris.error("Failed to delete the old instance file of Turbo Pregen!"); + return; + } + } + } + + try { + if (sender().isPlayer() && access() == null) { + sender().sendMessage(C.RED + "The engine access for this world is null!"); + sender().sendMessage(C.RED + "Please make sure the world is loaded & the engine is initialized. Generate a new chunk, for example."); + } + + TurboPregenerator.TurboPregenJob pregenJob = TurboPregenerator.TurboPregenJob.builder() + .world(worldName) + .radiusBlocks(radius) + .position(0) + .build(); + + File TurboGenFile = new File(worldDirectory, "turbogen.json"); + TurboPregenerator pregenerator = new TurboPregenerator(pregenJob, TurboGenFile); + pregenerator.start(); + + String msg = C.GREEN + "TurboPregen started in " + C.GOLD + worldName + C.GREEN + " of " + C.GOLD + (radius * 2) + C.GREEN + " by " + C.GOLD + (radius * 2) + C.GREEN + " blocks from " + C.GOLD + center.getX() + "," + center.getZ(); + sender().sendMessage(msg); + Iris.info(msg); + } catch (Throwable e) { + sender().sendMessage(C.RED + "Epic fail. See console."); + Iris.reportError(e); + e.printStackTrace(); + } + } + + @Decree(description = "Stop the active pregeneration task", aliases = "x") + public void stop(@Param(aliases = "world", description = "The world to pause") World world) throws IOException { + TurboPregenerator turboPregenInstance = TurboPregenerator.getInstance(); + File worldDirectory = new File(Bukkit.getWorldContainer(), world.getName()); + File turboFile = new File(worldDirectory, "turbogen.json"); + + if (turboPregenInstance != null) { + turboPregenInstance.shutdownInstance(world); + sender().sendMessage(C.LIGHT_PURPLE + "Closed Turbogen instance for " + world.getName()); + } else if (turboFile.exists() && turboFile.delete()) { + sender().sendMessage(C.LIGHT_PURPLE + "Closed Turbogen instance for " + world.getName()); + } else if (turboFile.exists()) { + Iris.error("Failed to delete the old instance file of Turbo Pregen!"); + } 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( + @Param(aliases = "world", description = "The world to pause") + World world + ) { + if (TurboPregenerator.getInstance() != null) { + TurboPregenerator.setPausedTurbo(world); + sender().sendMessage(C.GREEN + "Paused/unpaused Turbo Pregen, now: " + (TurboPregenerator.isPausedTurbo(world) ? "Paused" : "Running") + "."); + } else { + File worldDirectory = new File(Bukkit.getWorldContainer(), world.getName()); + File TurboFile = new File(worldDirectory, "turbogen.json"); + if (TurboFile.exists()){ + TurboPregenerator.loadTurboGenerator(world.getName()); + sender().sendMessage(C.YELLOW + "Started Turbo Pregen back up!"); + } else { + sender().sendMessage(C.YELLOW + "No active Turbo Pregen tasks to pause/unpause."); + } + + } + } +} diff --git a/core/src/main/java/com/volmit/iris/core/pregenerator/DeepSearchPregenerator.java b/core/src/main/java/com/volmit/iris/core/pregenerator/DeepSearchPregenerator.java new file mode 100644 index 000000000..308ff3b41 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/core/pregenerator/DeepSearchPregenerator.java @@ -0,0 +1,297 @@ +package com.volmit.iris.core.pregenerator; + +import com.google.gson.Gson; +import com.volmit.iris.Iris; +import com.volmit.iris.core.tools.IrisToolbelt; +import com.volmit.iris.engine.framework.Engine; +import com.volmit.iris.util.format.C; +import com.volmit.iris.util.format.Form; +import com.volmit.iris.util.io.IO; +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.scheduling.ChronoLatch; +import com.volmit.iris.util.scheduling.J; +import com.volmit.iris.util.scheduling.PrecisionStopwatch; +import io.papermc.lib.PaperLib; +import lombok.Builder; +import lombok.Data; +import lombok.Getter; +import org.bukkit.Bukkit; +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.FileWriter; +import java.io.IOException; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.ReentrantLock; + +public class DeepSearchPregenerator extends Thread implements Listener { + @Getter + private static DeepSearchPregenerator instance; + private final DeepSearchJob job; + private final File destination; + private final int maxPosition; + private World world; + private final ChronoLatch latch; + private static AtomicInteger foundChunks; + private final AtomicInteger foundLast; + private final AtomicInteger foundTotalChunks; + private final AtomicLong startTime; + private final RollingSequence chunksPerSecond; + private final RollingSequence chunksPerMinute; + private final AtomicInteger chunkCachePos; + private final AtomicInteger chunkCacheSize; + private final AtomicInteger foundCacheLast; + private final AtomicInteger foundCache; + private LinkedHashMap chunkCache; + private final ReentrantLock cacheLock = new ReentrantLock(); + + private static final Map jobs = new HashMap<>(); + + public DeepSearchPregenerator(DeepSearchJob job, File destination) { + this.job = job; + this.chunkCacheSize = new AtomicInteger(); // todo + this.chunkCachePos = new AtomicInteger(1000); + this.foundCacheLast = new AtomicInteger(); + this.foundCache = new AtomicInteger(); + this.destination = destination; + this.chunkCache = new LinkedHashMap(); + this.maxPosition = new Spiraler(job.getRadiusBlocks() * 2, job.getRadiusBlocks() * 2, (x, z) -> { + }).count(); + this.world = Bukkit.getWorld(job.getWorld()); + this.latch = new ChronoLatch(3000); + this.startTime = new AtomicLong(M.ms()); + this.chunksPerSecond = new RollingSequence(10); + this.chunksPerMinute = new RollingSequence(10); + foundChunks = new AtomicInteger(0); + this.foundLast = new AtomicInteger(0); + this.foundTotalChunks = new AtomicInteger((int) Math.ceil(Math.pow((2.0 * job.getRadiusBlocks()) / 16, 2))); + jobs.put(job.getWorld(), job); + DeepSearchPregenerator.instance = this; + } + + @EventHandler + public void on(WorldUnloadEvent e) { + if (e.getWorld().equals(world)) { + interrupt(); + } + } + + public void run() { + while (!interrupted()) { + tick(); + } + try { + saveNow(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public void tick() { + DeepSearchJob job = jobs.get(world.getName()); + // chunkCache(); //todo finish this + if (latch.flip() && !job.paused) { + if (cacheLock.isLocked()) { + + Iris.info("DeepFinder: Caching: " + chunkCachePos.get() + " Of " + chunkCacheSize.get()); + } + long eta = computeETA(); + save(); + int secondGenerated = foundChunks.get() - foundLast.get(); + foundLast.set(foundChunks.get()); + secondGenerated = secondGenerated / 3; + chunksPerSecond.put(secondGenerated); + chunksPerMinute.put(secondGenerated * 60); + Iris.info("deepFinder: " + C.IRIS + world.getName() + C.RESET + " RTT: " + Form.f(foundChunks.get()) + " of " + Form.f(foundTotalChunks.get()) + " " + Form.f((int) chunksPerSecond.getAverage()) + "/s ETA: " + Form.duration((double) eta, 2)); + + } + + if (foundChunks.get() >= foundTotalChunks.get()) { + Iris.info("Completed DeepSearch!"); + interrupt(); + } else { + int pos = job.getPosition() + 1; + job.setPosition(pos); + if (!job.paused) { + tickSearch(getChunk(pos)); + } + } + } + + private long computeETA() { + return (long) ((foundTotalChunks.get() - foundChunks.get()) / chunksPerSecond.getAverage()) * 1000; + // todo broken + } + + private void chunkCache() { + if (chunkCache.isEmpty()) { + cacheLock.lock(); + PrecisionStopwatch p = PrecisionStopwatch.start(); + executorService.submit(() -> { + for (; chunkCacheSize.get() > chunkCachePos.get(); chunkCacheSize.getAndAdd(-1)) { + chunkCache.put(chunkCachePos.get(), getChunk(chunkCachePos.get())); + chunkCachePos.getAndAdd(1); + } + Iris.info("Total Time: " + p.getMinutes()); + }); + } + if (cacheLock.isLocked()) { + cacheLock.unlock(); + } + } + + private final ExecutorService executorService = Executors.newSingleThreadExecutor(); + + private void tickSearch(Position2 chunk) { + executorService.submit(() -> { + CountDownLatch latch = new CountDownLatch(1); + try { + findInChunk(world, chunk.getX(), chunk.getZ()); + } catch (IOException e) { + throw new RuntimeException(e); + } + Iris.verbose("Generated Async " + chunk); + latch.countDown(); + + try { + latch.await(); + } catch (InterruptedException ignored) {} + foundChunks.addAndGet(1); + }); + } + + private void findInChunk(World world, int x, int z) throws IOException { + int xx = x * 16; + int zz = z * 16; + Engine engine = IrisToolbelt.access(world).getEngine(); + for (int i = 0; i < 16; i++) { + for (int j = 0; j < 16; j++) { + int height = engine.getHeight(xx + i, zz + j); + if (height > 300) { + File found = new File("plugins" + "iris" + "found.txt"); + FileWriter writer = new FileWriter(found); + if (!found.exists()) { + found.createNewFile(); + } + Iris.info("Found at! " + x + ", " + z); + writer.write("Found at: X: " + xx + " Z: " + zz + ", "); + } + } + } + } + + public Position2 getChunk(int position) { + int p = -1; + AtomicInteger xx = new AtomicInteger(); + AtomicInteger zz = new AtomicInteger(); + Spiraler s = new Spiraler(job.getRadiusBlocks() * 2, job.getRadiusBlocks() * 2, (x, z) -> { + xx.set(x); + zz.set(z); + }); + + while (s.hasNext() && p++ < position) { + s.next(); + } + + return new Position2(xx.get(), zz.get()); + } + + public void save() { + J.a(() -> { + try { + saveNow(); + } catch (Throwable e) { + e.printStackTrace(); + } + }); + } + + public static void setPausedDeep(World world) { + DeepSearchJob job = jobs.get(world.getName()); + if (isPausedDeep(world)){ + job.paused = false; + } else { + job.paused = true; + } + + if ( job.paused) { + Iris.info(C.BLUE + "DeepSearch: " + C.IRIS + world.getName() + C.BLUE + " Paused"); + } else { + Iris.info(C.BLUE + "DeepSearch: " + C.IRIS + world.getName() + C.BLUE + " Resumed"); + } + } + + public static boolean isPausedDeep(World world) { + DeepSearchJob job = jobs.get(world.getName()); + return job != null && job.isPaused(); + } + + public void shutdownInstance(World world) throws IOException { + Iris.info("DeepSearch: " + C.IRIS + world.getName() + C.BLUE + " Shutting down.."); + DeepSearchJob job = jobs.get(world.getName()); + File worldDirectory = new File(Bukkit.getWorldContainer(), world.getName()); + File deepFile = new File(worldDirectory, "DeepSearch.json"); + + if (job == null) { + Iris.error("No DeepSearch 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 (deepFile.exists()){ + deepFile.delete(); + J.sleep(1000); + } + Iris.info("DeepSearch: " + C.IRIS + world.getName() + C.BLUE + " File deleted and instance closed."); + } + }.runTaskLater(Iris.instance, 20L); + } catch (Exception e) { + Iris.error("Failed to shutdown DeepSearch for " + world.getName()); + e.printStackTrace(); + } finally { + saveNow(); + interrupt(); + } + } + + + public void saveNow() throws IOException { + IO.writeAll(this.destination, new Gson().toJson(job)); + } + + @Data + @Builder + public static class DeepSearchJob { + private String world; + @Builder.Default + private int radiusBlocks = 5000; + @Builder.Default + private int position = 0; + @Builder.Default + boolean paused = false; + } +} + 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 d3ee26b54..20e3ace96 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 @@ -49,7 +49,6 @@ public class LazyPregenerator extends Thread implements Listener { 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) { @@ -155,10 +154,7 @@ public class LazyPregenerator extends Thread implements Listener { if (PaperLib.isPaper()) { PaperLib.getChunkAtAsync(world, chunk.getX(), chunk.getZ(), true) .thenAccept((i) -> { - LazyPregenJob j = jobs.get(world.getName()); - if (!j.paused) { - Iris.verbose("Generated Async " + chunk); - } + Iris.verbose("Generated Async " + chunk); latch.countDown(); }); } else { @@ -207,7 +203,6 @@ public class LazyPregenerator extends Thread implements Listener { } public static void setPausedLazy(World world) { - // todo: doesnt actually pause LazyPregenJob job = jobs.get(world.getName()); if (isPausedLazy(world)){ job.paused = false; diff --git a/core/src/main/java/com/volmit/iris/core/pregenerator/TurboPregenerator.java b/core/src/main/java/com/volmit/iris/core/pregenerator/TurboPregenerator.java new file mode 100644 index 000000000..92b76293a --- /dev/null +++ b/core/src/main/java/com/volmit/iris/core/pregenerator/TurboPregenerator.java @@ -0,0 +1,276 @@ +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.util.collection.KList; +import com.volmit.iris.util.format.C; +import com.volmit.iris.util.format.Form; +import com.volmit.iris.util.io.IO; +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.scheduling.ChronoLatch; +import com.volmit.iris.util.scheduling.J; +import io.papermc.lib.PaperLib; +import lombok.Builder; +import lombok.Data; +import lombok.Getter; +import org.bukkit.Bukkit; +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.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +public class TurboPregenerator extends Thread implements Listener { + @Getter + private static TurboPregenerator instance; + private final TurboPregenJob job; + private final File destination; + private final int maxPosition; + private World world; + private final ChronoLatch latch; + private static AtomicInteger turboGeneratedChunks; + private final AtomicInteger generatedLast; + private final AtomicInteger turboTotalChunks; + private final AtomicLong startTime; + private final RollingSequence chunksPerSecond; + private final RollingSequence chunksPerMinute; + private KList queue = new KList<>(); + private AtomicInteger maxWaiting; + private static final Map jobs = new HashMap<>(); + + public TurboPregenerator(TurboPregenJob job, File destination) { + this.job = job; + queue = new KList<>(512); + this.maxWaiting = new AtomicInteger(128); + this.destination = destination; + this.maxPosition = new Spiraler(job.getRadiusBlocks() * 2, job.getRadiusBlocks() * 2, (x, z) -> { + }).count(); + this.world = Bukkit.getWorld(job.getWorld()); + this.latch = new ChronoLatch(3000); + this.startTime = new AtomicLong(M.ms()); + this.chunksPerSecond = new RollingSequence(10); + this.chunksPerMinute = new RollingSequence(10); + turboGeneratedChunks = new AtomicInteger(0); + this.generatedLast = new AtomicInteger(0); + this.turboTotalChunks = new AtomicInteger((int) Math.ceil(Math.pow((2.0 * job.getRadiusBlocks()) / 16, 2))); + jobs.put(job.getWorld(), job); + TurboPregenerator.instance = this; + } + public TurboPregenerator(File file) throws IOException { + this(new Gson().fromJson(IO.readAll(file), TurboPregenerator.TurboPregenJob.class), file); + } + + public static void loadTurboGenerator(String i) { + World x = Bukkit.getWorld(i); + File turbogen = new File(x.getWorldFolder(), "turbogen.json"); + if (turbogen.exists()) { + try { + TurboPregenerator p = new TurboPregenerator(turbogen); + p.start(); + Iris.info("Started Turbo Pregenerator: " + p.job); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + } + + @EventHandler + public void on(WorldUnloadEvent e) { + if (e.getWorld().equals(world)) { + interrupt(); + } + } + + public void run() { + while (!interrupted()) { + tick(); + } + + try { + saveNow(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public void tick() { + TurboPregenJob job = jobs.get(world.getName()); + if (latch.flip() && !job.paused) { + long eta = computeETA(); + save(); + int secondGenerated = turboGeneratedChunks.get() - generatedLast.get(); + generatedLast.set(turboGeneratedChunks.get()); + secondGenerated = secondGenerated / 3; + chunksPerSecond.put(secondGenerated); + chunksPerMinute.put(secondGenerated * 60); + Iris.info("TurboGen: " + C.IRIS + world.getName() + C.RESET + " RTT: " + Form.f(turboGeneratedChunks.get()) + " of " + Form.f(turboTotalChunks.get()) + " " + Form.f((int) chunksPerSecond.getAverage()) + "/s ETA: " + Form.duration((double) eta, 2)); + + } + if (turboGeneratedChunks.get() >= turboTotalChunks.get()) { + Iris.info("Completed Turbo Gen!"); + interrupt(); + } else { + int pos = job.getPosition() + 1; + job.setPosition(pos); + if (!job.paused) { + if (queue.size() < maxWaiting.get()) { + Position2 chunk = getChunk(pos); + queue.add(chunk); + } + waitForChunksPartial(); + } + } + } + + private void waitForChunksPartial() { + while (!queue.isEmpty() && maxWaiting.get() > queue.size()) { + try { + for (Position2 c : new KList<>(queue)) { + tickGenerate(c); + queue.remove(c); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + private long computeETA() { + return (long) ((turboTotalChunks.get() - turboGeneratedChunks.get()) / chunksPerMinute.getAverage()) * 1000; + // todo broken + } + + private final ExecutorService executorService = Executors.newFixedThreadPool(10); + private void tickGenerate(Position2 chunk) { + executorService.submit(() -> { + CountDownLatch latch = new CountDownLatch(1); + PaperLib.getChunkAtAsync(world, chunk.getX(), chunk.getZ(), true) + .thenAccept((i) -> { + Iris.verbose("Generated Async " + chunk); + latch.countDown(); + }); + try { + latch.await(); + } catch (InterruptedException ignored) { + } + turboGeneratedChunks.addAndGet(1); + }); + } + + public Position2 getChunk(int position) { + int p = -1; + AtomicInteger xx = new AtomicInteger(); + AtomicInteger zz = new AtomicInteger(); + Spiraler s = new Spiraler(job.getRadiusBlocks() * 2, job.getRadiusBlocks() * 2, (x, z) -> { + xx.set(x); + zz.set(z); + }); + + while (s.hasNext() && p++ < position) { + s.next(); + } + + return new Position2(xx.get(), zz.get()); + } + + public void save() { + J.a(() -> { + try { + saveNow(); + } catch (Throwable e) { + e.printStackTrace(); + } + }); + } + + public static void setPausedTurbo(World world) { + TurboPregenJob job = jobs.get(world.getName()); + if (isPausedTurbo(world)) { + job.paused = false; + } else { + job.paused = true; + } + + if (job.paused) { + Iris.info(C.BLUE + "TurboGen: " + C.IRIS + world.getName() + C.BLUE + " Paused"); + } else { + Iris.info(C.BLUE + "TurboGen: " + C.IRIS + world.getName() + C.BLUE + " Resumed"); + } + } + + public static boolean isPausedTurbo(World world) { + TurboPregenJob job = jobs.get(world.getName()); + return job != null && job.isPaused(); + } + + public void shutdownInstance(World world) throws IOException { + Iris.info("turboGen: " + C.IRIS + world.getName() + C.BLUE + " Shutting down.."); + TurboPregenJob job = jobs.get(world.getName()); + File worldDirectory = new File(Bukkit.getWorldContainer(), world.getName()); + File turboFile = new File(worldDirectory, "turbogen.json"); + + if (job == null) { + Iris.error("No turbogen 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 (turboFile.exists()) { + turboFile.delete(); + J.sleep(1000); + } + Iris.info("turboGen: " + C.IRIS + world.getName() + C.BLUE + " File deleted and instance closed."); + } + }.runTaskLater(Iris.instance, 20L); + } catch (Exception e) { + Iris.error("Failed to shutdown turbogen for " + world.getName()); + e.printStackTrace(); + } finally { + saveNow(); + interrupt(); + } + } + + + public void saveNow() throws IOException { + IO.writeAll(this.destination, new Gson().toJson(job)); + } + + @Data + @Builder + public static class TurboPregenJob { + private String world; + @Builder.Default + private int radiusBlocks = 5000; + @Builder.Default + private int position = 0; + @Builder.Default + boolean paused = false; + } +} + diff --git a/core/src/main/java/com/volmit/iris/core/safeguard/ServerBootSFG.java b/core/src/main/java/com/volmit/iris/core/safeguard/ServerBootSFG.java index 0a8b2983c..46dda64d7 100644 --- a/core/src/main/java/com/volmit/iris/core/safeguard/ServerBootSFG.java +++ b/core/src/main/java/com/volmit/iris/core/safeguard/ServerBootSFG.java @@ -3,14 +3,23 @@ package com.volmit.iris.core.safeguard; import com.volmit.iris.Iris; import com.volmit.iris.core.nms.INMS; import com.volmit.iris.core.nms.v1X.NMSBinding1X; +import org.apache.logging.log4j.core.util.ExecutorServices; import org.bukkit.Bukkit; import org.bukkit.plugin.Plugin; import javax.print.attribute.standard.Severity; import java.io.File; +import java.nio.channels.FileChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; import java.util.HashMap; import java.util.Map; import java.util.StringJoiner; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import static com.volmit.iris.Iris.getJavaVersion; import static com.volmit.iris.Iris.instance; @@ -19,9 +28,9 @@ import static com.volmit.iris.core.safeguard.IrisSafeguard.*; public class ServerBootSFG { public static final Map incompatibilities = new HashMap<>(); public static boolean isJDK17 = true; - public static boolean hasEnoughDiskSpace = false; + public static boolean hasEnoughDiskSpace = true; public static boolean isJRE = false; - public static boolean hasPrivileges = false; + public static boolean hasPrivileges = true; public static boolean unsuportedversion = false; protected static boolean safeguardPassed; public static boolean passedserversoftware = true; @@ -38,7 +47,7 @@ public class ServerBootSFG { incompatibilities.clear(); incompatibilities.put("Multiverse-Core", false); - incompatibilities.put("Dynmap", false); + incompatibilities.put("dynmap", false); incompatibilities.put("TerraformGenerator", false); incompatibilities.put("Stratos", false); @@ -80,20 +89,23 @@ public class ServerBootSFG { joiner.add("Unsupported Java version"); severityMedium++; } + if (!isJDK()) { isJRE = true; joiner.add("Unsupported JDK"); severityMedium++; } + if (!hasPrivileges()){ hasPrivileges = true; joiner.add("Insufficient Privileges"); - severityHigh++; + severityMedium++; } + if (!enoughDiskSpace()){ hasEnoughDiskSpace = false; joiner.add("Insufficient Disk Space"); - severityHigh++; + severityMedium++; } allIncompatibilities = joiner.toString(); @@ -136,18 +148,12 @@ public class ServerBootSFG { } public static boolean hasPrivileges() { - File pv = new File(Bukkit.getWorldContainer() + "iristest.json"); - if (pv.exists()){ - pv.delete(); - } - try { - if (pv.createNewFile()){ - if (pv.canWrite() && pv.canRead()){ - pv.delete(); - return true; - } + Path pv = Paths.get(Bukkit.getWorldContainer() + "iristest.json"); + try (FileChannel fc = FileChannel.open(pv, StandardOpenOption.CREATE, StandardOpenOption.DELETE_ON_CLOSE, StandardOpenOption.READ, StandardOpenOption.WRITE)) { + if (Files.isReadable(pv) && Files.isWritable(pv)) { + return true; } - } catch (Exception e){ + } catch (Exception e) { return false; } return false; diff --git a/core/src/main/java/com/volmit/iris/core/safeguard/UtilsSFG.java b/core/src/main/java/com/volmit/iris/core/safeguard/UtilsSFG.java index b4dac8a9d..c9155c7b3 100644 --- a/core/src/main/java/com/volmit/iris/core/safeguard/UtilsSFG.java +++ b/core/src/main/java/com/volmit/iris/core/safeguard/UtilsSFG.java @@ -26,7 +26,7 @@ public class UtilsSFG { Iris.safeguard(C.RED + "- The plugin Multiverse is not compatible with the server."); Iris.safeguard(C.RED + "- If you want to have a world manager, consider using PhantomWorlds or MyWorlds instead."); } - if (ServerBootSFG.incompatibilities.get("Dynmap")) { + if (ServerBootSFG.incompatibilities.get("dynmap")) { Iris.safeguard(C.RED + "Dynmap"); Iris.safeguard(C.RED + "- The plugin Dynmap is not compatible with the server."); Iris.safeguard(C.RED + "- If you want to have a map plugin like Dynmap, consider Bluemap."); @@ -40,22 +40,22 @@ public class UtilsSFG { Iris.safeguard(C.RED + "- Iris only supports 1.19.2 > 1.20.2"); } if (!ServerBootSFG.passedserversoftware) { - Iris.safeguard(C.RED + "Unsupported Server Software"); - Iris.safeguard(C.RED + "- Please consider using Paper or Purpur instead."); + Iris.safeguard(C.YELLOW + "Unsupported Server Software"); + Iris.safeguard(C.YELLOW + "- Please consider using Paper or Purpur instead."); } if (!ServerBootSFG.hasPrivileges) { - Iris.safeguard(C.RED + "Insufficient Privileges"); - Iris.safeguard(C.RED + "- The server has insufficient Privileges to run iris. Please contact support."); + Iris.safeguard(C.YELLOW + "Insufficient Privileges"); + Iris.safeguard(C.YELLOW + "- The server has insufficient Privileges to run iris. Please contact support."); } if (!ServerBootSFG.hasEnoughDiskSpace) { - Iris.safeguard(C.RED + "Insufficient Disk Space"); - Iris.safeguard(C.RED + "- The server has insufficient Free DiskSpace to run iris required 3GB+."); + Iris.safeguard(C.YELLOW + "Insufficient Disk Space"); + Iris.safeguard(C.YELLOW + "- The server has insufficient Free DiskSpace to run iris required 3GB+."); } if (!ServerBootSFG.isJDK17) { Iris.safeguard(C.YELLOW + "Unsupported java version"); Iris.safeguard(C.YELLOW + "- Please consider using JDK 17 Instead of JDK " + Iris.getJavaVersion()); } - if (!ServerBootSFG.isJRE) { + if (ServerBootSFG.isJRE) { Iris.safeguard(C.YELLOW + "Unsupported Server JDK"); Iris.safeguard(C.YELLOW + "- Please consider using JDK 17 Instead of JRE " + Iris.getJavaVersion()); } diff --git a/core/src/main/java/com/volmit/iris/core/service/BoardSVC.java b/core/src/main/java/com/volmit/iris/core/service/BoardSVC.java index 4fa115cdd..d8456b0a5 100644 --- a/core/src/main/java/com/volmit/iris/core/service/BoardSVC.java +++ b/core/src/main/java/com/volmit/iris/core/service/BoardSVC.java @@ -19,6 +19,7 @@ package com.volmit.iris.core.service; import com.volmit.iris.Iris; +import com.volmit.iris.core.IrisSettings; import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.core.tools.IrisToolbelt; import com.volmit.iris.engine.framework.Engine; @@ -131,7 +132,11 @@ public class BoardSVC implements IrisService, BoardProvider { lines.add("&7&m "); lines.add(C.AQUA + "Region" + C.GRAY + ": " + engine.getRegion(x, z).getName()); lines.add(C.AQUA + "Biome" + C.GRAY + ": " + engine.getBiomeOrMantle(x, y, z).getName()); - lines.add(C.AQUA + "Height" + C.GRAY + ": " + Math.round(engine.getHeight(x, z) + player.getWorld().getMinHeight())); + if (!IrisSettings.get().getStudio().displayTrueHeight) { + lines.add(C.AQUA + "Height" + C.GRAY + ": " + Math.round(engine.getHeight(x, z) + player.getWorld().getMinHeight())); + } else { + lines.add(C.AQUA + "Height" + C.GRAY + ": " + Math.round(engine.getHeight(x, z))); + } lines.add(C.AQUA + "Slope" + C.GRAY + ": " + Form.f(engine.getComplex().getSlopeStream().get(x, z), 2)); lines.add(C.AQUA + "BUD/s" + C.GRAY + ": " + Form.f(engine.getBlockUpdatesPerSecond())); lines.add("&7&m "); 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 ad79ed916..6f3f79535 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 @@ -79,7 +79,7 @@ public class IrisEngineSVC implements IrisService { } } catch (Throwable e) { Iris.reportError(e); - return -1; + // return -1; } int size = lastUse.size(); diff --git a/core/src/main/java/com/volmit/iris/engine/jigsaw/PlannedStructure.java b/core/src/main/java/com/volmit/iris/engine/jigsaw/PlannedStructure.java index 81986c252..dd7eade40 100644 --- a/core/src/main/java/com/volmit/iris/engine/jigsaw/PlannedStructure.java +++ b/core/src/main/java/com/volmit/iris/engine/jigsaw/PlannedStructure.java @@ -100,7 +100,7 @@ public class PlannedStructure { int sz = (v.getD() / 2); int xx = i.getPosition().getX() + sx; int zz = i.getPosition().getZ() + sz; - RNG rng = new RNG(Cache.key(xx, zz)); + RNG rngf = new RNG(Cache.key(xx, zz)); int offset = i.getPosition().getY() - startHeight; int height; 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 7457b9f1c..4b7f415b3 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 @@ -421,22 +421,27 @@ public class Mantle { } ioTrim.set(true); - //unloadLock.lock(); + unloadLock.lock(); try { Iris.debug("Trimming Tectonic Plates older than " + Form.duration(adjustedIdleDuration.get(), 0)); if (lastUse != null) { - for (Long i : new ArrayList<>(lastUse.keySet())) { - double finalAdjustedIdleDuration = adjustedIdleDuration.get(); - hyperLock.withLong(i, () -> { - Long lastUseTime = lastUse.get(i); - if (lastUseTime != null && M.ms() - lastUseTime >= finalAdjustedIdleDuration) { - toUnload.add(i); - Iris.debug("Tectonic Region added to unload"); - } - }); + if (!lastUse.isEmpty()) { + for (Long i : new ArrayList<>(lastUse.keySet())) { + double finalAdjustedIdleDuration = adjustedIdleDuration.get(); + hyperLock.withLong(i, () -> { + Long lastUseTime = lastUse.get(i); + if (lastUseTime != null && M.ms() - lastUseTime >= finalAdjustedIdleDuration) { + toUnload.add(i); + Iris.debug("Tectonic Region added to unload"); + //Iris.panic(); + } + }); + } } } + } catch (Throwable e) { + Iris.reportError(e); } finally { ioTrim.set(false); unloadLock.unlock(); @@ -444,7 +449,6 @@ public class Mantle { } public synchronized int unloadTectonicPlate(int tectonicLimit) { - // todo: Make it advanced with bursts etc AtomicInteger i = new AtomicInteger(); unloadLock.lock(); BurstExecutor burst = null; @@ -465,12 +469,12 @@ public class Mantle { i.incrementAndGet(); Iris.debug("Unloaded Tectonic Plate " + C.DARK_GREEN + Cache.keyX(id) + " " + Cache.keyZ(id)); } catch (IOException e) { - e.printStackTrace(); + Iris.reportError(e); } } })); - } + } burst.complete(); } catch (Throwable e) { e.printStackTrace();