From 8ec1ac98755cc9f2384471287895d19aa84cb235 Mon Sep 17 00:00:00 2001 From: RePixelatedMC Date: Sat, 30 Dec 2023 14:29:31 +0100 Subject: [PATCH] sync --- .../iris/core/commands/CommandIris.java | 1 + .../core/commands/CommandTurboPregen.java | 131 +++++++++ .../core/pregenerator/LazyPregenerator.java | 6 +- .../core/pregenerator/TurboPregenerator.java | 276 ++++++++++++++++++ 4 files changed, 409 insertions(+), 5 deletions(-) create mode 100644 core/src/main/java/com/volmit/iris/core/commands/CommandTurboPregen.java create mode 100644 core/src/main/java/com/volmit/iris/core/pregenerator/TurboPregenerator.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 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/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/LazyPregenerator.java b/core/src/main/java/com/volmit/iris/core/pregenerator/LazyPregenerator.java index d3ee26b54..2db95e1f4 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 { 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; + } +} +