diff --git a/src/main/java/com/volmit/iris/core/command/world/CommandIrisPregen.java b/src/main/java/com/volmit/iris/core/command/world/CommandIrisPregen.java index de70f9ce3..74837c9cb 100644 --- a/src/main/java/com/volmit/iris/core/command/world/CommandIrisPregen.java +++ b/src/main/java/com/volmit/iris/core/command/world/CommandIrisPregen.java @@ -19,8 +19,13 @@ package com.volmit.iris.core.command.world; import com.volmit.iris.Iris; +import com.volmit.iris.core.gui.PregeneratorJob; import com.volmit.iris.core.gui.components.Pregenerator; +import com.volmit.iris.core.pregenerator.PregenTask; +import com.volmit.iris.core.pregenerator.methods.HybridPregenMethod; +import com.volmit.iris.core.pregenerator.methods.PaperOrMedievalPregenMethod; import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.math.Position2; import com.volmit.iris.util.plugin.MortarCommand; import com.volmit.iris.util.plugin.VolmitSender; import org.bukkit.Bukkit; @@ -69,17 +74,17 @@ public class CommandIrisPregen extends MortarCommand { } if (args[0].equalsIgnoreCase("stop") || args[0].equalsIgnoreCase("x")) { - if (Pregenerator.shutdownInstance()) { + if (PregeneratorJob.shutdownInstance()) { sender.sendMessage("Stopped Pregen."); } else { sender.sendMessage("No Active Pregens."); } return true; } else if (args[0].equalsIgnoreCase("pause") || args[0].equalsIgnoreCase("resume")) { - if (Pregenerator.getInstance() != null) { - Pregenerator.pauseResume(); + if (PregeneratorJob.getInstance() != null) { + PregeneratorJob.pauseResume(); - if (Pregenerator.isPaused()) { + if (PregeneratorJob.isPaused()) { sender.sendMessage("Pregen Paused"); } else { sender.sendMessage("Pregen Resumed"); @@ -105,7 +110,12 @@ public class CommandIrisPregen extends MortarCommand { } } try { - new Pregenerator(world, getVal(args[0]) * 2); + new PregeneratorJob(PregenTask + .builder() + .center(new Position2(0, 0)) + .radius(((getVal(args[0])>>4)>>5) + 1) + .build(), + new HybridPregenMethod(world, Runtime.getRuntime().availableProcessors())); } catch (NumberFormatException e) { Iris.reportError(e); sender.sendMessage("Invalid argument in command"); @@ -131,7 +141,12 @@ public class CommandIrisPregen extends MortarCommand { } World world = Bukkit.getWorld(args[1]); try { - new Pregenerator(world, getVal(args[0]) * 2); + new PregeneratorJob(PregenTask + .builder() + .center(new Position2(0, 0)) + .radius(((getVal(args[0])>>4)>>5) + 1) + .build(), + new HybridPregenMethod(world, Runtime.getRuntime().availableProcessors())); } catch (NumberFormatException e) { Iris.reportError(e); sender.sendMessage("Invalid argument in command"); diff --git a/src/main/java/com/volmit/iris/core/gui/PregeneratorJob.java b/src/main/java/com/volmit/iris/core/gui/PregeneratorJob.java new file mode 100644 index 000000000..d4bf7c490 --- /dev/null +++ b/src/main/java/com/volmit/iris/core/gui/PregeneratorJob.java @@ -0,0 +1,351 @@ +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2021 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.gui; + +import com.volmit.iris.Iris; +import com.volmit.iris.core.IrisSettings; +import com.volmit.iris.core.pregenerator.IrisPregenerator; +import com.volmit.iris.core.pregenerator.PregenListener; +import com.volmit.iris.core.pregenerator.PregenTask; +import com.volmit.iris.core.pregenerator.PregeneratorMethod; +import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.format.Form; +import com.volmit.iris.util.function.Consumer2; +import com.volmit.iris.util.math.M; +import com.volmit.iris.util.math.Position2; +import com.volmit.iris.util.math.Spiraler; +import com.volmit.iris.util.scheduling.J; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.image.BufferedImage; +import java.util.concurrent.locks.ReentrantLock; + +public class PregeneratorJob implements PregenListener { + public static PregeneratorJob instance; + private static final Color COLOR_GENERATING = parseColor("#346beb"); + private static final Color COLOR_GENERATED = parseColor("#34eb93"); + private JFrame frame; + private final PregenTask task; + private boolean saving; + private final IrisPregenerator pregenerator; + private PregenRenderer renderer; + private String[] info; + private Position2 min; + private Position2 max; + + public PregeneratorJob(PregenTask task, PregeneratorMethod method) + { + instance = this; + saving = false; + info = new String[]{"Initializing..."}; + this.task = task; + this.pregenerator = new IrisPregenerator(task, method, this); + J.a(this.pregenerator::start); + max = new Position2(0, 0); + min = new Position2(0, 0); + KList draw = new KList<>(); + task.iterateRegions((xx,zz) -> { + min.setX(Math.min(xx << 5, min.getX())); + min.setZ(Math.min(zz << 5, min.getZ())); + max.setX(Math.max((xx << 5) + 31, max.getX())); + max.setZ(Math.max((zz << 5) + 31, max.getZ())); + }); + open(); + } + + public static boolean shutdownInstance() { + if(instance == null) + { + return false; + } + + J.a(() -> instance.pregenerator.close()); + return true; + } + + public static PregeneratorJob getInstance() { + return instance; + } + + public static void pauseResume() { + if(instance == null) + { + return; + } + + if(isPaused()) + { + instance.pregenerator.resume(); + } + + else + { + instance.pregenerator.pause(); + } + } + + public static boolean isPaused() { + if(instance == null) + { + return true; + } + + return instance.paused(); + } + + public void draw(int x, int z, Color color) + { + try + { + if(renderer != null && frame != null && frame.isVisible()) + { + renderer.func.accept(new Position2(x, z), color); + } + } + + catch(Throwable ignored) + { + + } + } + + public void stop() + { + J.a(() -> { + pregenerator.close(); + close(); + instance = null; + }); + } + + public void close() + { + J.a(() -> { + try + { + J.sleep(3000); + frame.setVisible(false); + } + + catch(Throwable e) + { + + } + }); + } + + public void open() + { + J.a(() -> { + try + { + frame = new JFrame("Pregen View"); + renderer = new PregenRenderer(); + frame.addKeyListener(renderer); + renderer.l = new ReentrantLock(); + renderer.frame = frame; + renderer.job = this; + renderer.func = (c, b) -> + { + renderer.l.lock(); + renderer.order.add(() -> renderer.draw(c, b, renderer.bg)); + renderer.l.unlock(); + }; + frame.add(renderer); + frame.setSize(1000, 1000); + frame.setVisible(true); + } + + catch(Throwable e) + { + + } + }); + } + + @Override + public void onTick(double chunksPerSecond, double chunksPerMinute, double regionsPerMinute, double percent, int generated, int totalChunks, int chunksRemaining, long eta, long elapsed, String method) { + info = new String[] { + (paused() ? "PAUSED" : (saving ? "Saving... " : "Generating")) + " " + Form.f(generated) + " of " + Form.f(totalChunks) + " (" + Form.pc(percent, 0) + " Complete)", + "Speed: " + Form.f(chunksPerSecond, 0) + " Chunks/s, " + Form.f(regionsPerMinute, 1) + " Regions/m, " + Form.f(chunksPerMinute, 0) + " Chunks/m", + Form.duration(eta, 2) + " Remaining " + " (" + Form.duration(elapsed, 2) + " Elapsed)", + "Generation Method: " + method, + }; + } + + @Override + public void onChunkGenerating(int x, int z) { + draw(x, z, COLOR_GENERATING); + } + + @Override + public void onChunkGenerated(int x, int z) { + draw(x, z, COLOR_GENERATED); + } + + @Override + public void onRegionGenerated(int x, int z) { + + } + + @Override + public void onRegionGenerating(int x, int z) { + + } + + @Override + public void onRegionSkipped(int x, int z) { + + } + + @Override + public void onClose() { + close(); + instance = null; + } + + @Override + public void onSaving() { + + } + + private Position2 getMax() { + return max; + } + + private Position2 getMin() { + return min; + } + + private boolean paused() { + return pregenerator.paused(); + } + + private String[] getProgress() { + return info; + } + + public static class PregenRenderer extends JPanel implements KeyListener { + private PregeneratorJob job; + private static final long serialVersionUID = 2094606939770332040L; + private final KList order = new KList<>(); + private final int res = 512; + Graphics2D bg; + private ReentrantLock l; + private final BufferedImage image = new BufferedImage(res, res, BufferedImage.TYPE_INT_RGB); + private Consumer2 func; + private JFrame frame; + + public PregenRenderer() { + + } + + public void paint(int x, int z, Color c) { + func.accept(new Position2(x, z), c); + } + + @Override + public void paint(Graphics gx) { + Graphics2D g = (Graphics2D) gx; + bg = (Graphics2D) image.getGraphics(); + l.lock(); + + while (order.isNotEmpty()) { + try { + order.pop().run(); + } catch (Throwable e) { + Iris.reportError(e); + + } + } + + l.unlock(); + g.drawImage(image, 0, 0, getParent().getWidth(), getParent().getHeight(), (img, infoflags, x, y, width, height) -> true); + g.setColor(Color.WHITE); + g.setFont(new Font("Hevetica", Font.BOLD, 28)); + String[] prog = job.getProgress(); + int h = g.getFontMetrics().getHeight() + 5; + int hh = 20; + + if (job.paused()) { + g.drawString("PAUSED", 20, hh += h); + + g.drawString("Press P to Resume", 20, hh += h); + } else { + for (String i : prog) { + g.drawString(i, 20, hh += h); + } + + g.drawString("Press P to Pause", 20, hh += h); + } + + J.sleep(IrisSettings.get().getGui().isMaximumPregenGuiFPS() ? 4 : 250); + repaint(); + } + + private void draw(Position2 p, Color c, Graphics2D bg) { + double pw = M.lerpInverse(job.getMin().getX(), job.getMax().getX(), p.getX()); + double ph = M.lerpInverse(job.getMin().getZ(), job.getMax().getZ(), p.getZ()); + double pwa = M.lerpInverse(job.getMin().getX(), job.getMax().getX(), p.getX() + 1); + double pha = M.lerpInverse(job.getMin().getZ(), job.getMax().getZ(), p.getZ() + 1); + int x = (int) M.lerp(0, res, pw); + int z = (int) M.lerp(0, res, ph); + int xa = (int) M.lerp(0, res, pwa); + int za = (int) M.lerp(0, res, pha); + bg.setColor(c); + bg.fillRect(x, z, xa - x, za - z); + } + + @Override + public void keyTyped(KeyEvent e) { + + } + + @Override + public void keyPressed(KeyEvent e) { + + } + + @Override + public void keyReleased(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_P) { + PregeneratorJob.pauseResume(); + } + } + + public void close() { + frame.setVisible(false); + } + } + + private static Color parseColor(String c) { + String v = (c.startsWith("#") ? c : "#" + c).trim(); + try { + return Color.decode(v); + } catch (Throwable e) { + Iris.reportError(e); + Iris.error("Error Parsing 'color', (" + c + ")"); + } + + return Color.RED; + } +} diff --git a/src/main/java/com/volmit/iris/core/pregenerator/IrisPregenerator.java b/src/main/java/com/volmit/iris/core/pregenerator/IrisPregenerator.java new file mode 100644 index 000000000..16a2052b9 --- /dev/null +++ b/src/main/java/com/volmit/iris/core/pregenerator/IrisPregenerator.java @@ -0,0 +1,212 @@ +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2021 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.pregenerator; + +import com.volmit.iris.util.math.M; +import com.volmit.iris.util.math.RollingSequence; +import com.volmit.iris.util.scheduling.ChronoLatch; +import com.volmit.iris.util.scheduling.J; +import com.volmit.iris.util.scheduling.Looper; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; + +public class IrisPregenerator { + private final PregenTask task; + private final PregeneratorMethod generator; + private final PregenListener listener; + private final Looper ticker; + private final AtomicBoolean paused; + private final AtomicBoolean shutdown; + private final RollingSequence chunksPerSecond; + private final RollingSequence chunksPerMinute; + private final RollingSequence regionsPerMinute; + private final AtomicInteger generated; + private final AtomicInteger generatedLast; + private final AtomicInteger generatedLastMinute; + private final AtomicInteger totalChunks; + private final AtomicLong startTime; + private final ChronoLatch minuteLatch; + private final AtomicReference currentGeneratorMethod; + + public IrisPregenerator(PregenTask task, PregeneratorMethod generator, PregenListener listener) + { + this.listener = listenify(listener); + this.shutdown = new AtomicBoolean(false); + this.paused = new AtomicBoolean(false); + this.task = task; + this.generator = generator; + currentGeneratorMethod = new AtomicReference<>("Void"); + minuteLatch = new ChronoLatch(60000, false); + chunksPerSecond = new RollingSequence(10); + chunksPerMinute = new RollingSequence(10); + regionsPerMinute = new RollingSequence(10); + generated = new AtomicInteger(0); + generatedLast = new AtomicInteger(0); + generatedLastMinute = new AtomicInteger(0); + totalChunks = new AtomicInteger(0); + task.iterateRegions((_a, _b) -> totalChunks.addAndGet(1024)); + startTime = new AtomicLong(M.ms()); + ticker = new Looper() { + @Override + protected long loop() { + long eta = computeETA(); + int secondGenerated = generated.get() - generatedLast.get(); + generatedLast.set(generated.get()); + chunksPerSecond.put(secondGenerated); + + if(minuteLatch.flip()) + { + int minuteGenerated = generated.get() - generatedLastMinute.get(); + generatedLastMinute.set(generated.get()); + chunksPerMinute.put(minuteGenerated); + regionsPerMinute.put((double)minuteGenerated / 1024D); + } + + listener.onTick(chunksPerSecond.getAverage(), chunksPerMinute.getAverage(), + regionsPerMinute.getAverage(), + (double)generated.get() / (double)totalChunks.get(), + generated.get(), totalChunks.get(), + totalChunks.get() - generated.get(), + eta, M.ms() - startTime.get(), currentGeneratorMethod.get()); + return 1000; + } + }; + } + + private long computeETA() { + return (long) ((totalChunks.get() - generated.get()) * + ((double) (M.ms() - startTime.get()) / (double) generated.get())); + } + + public void close() + { + shutdown.set(true); + } + + public void start() + { + init(); + ticker.start(); + task.iterateRegions(this::visitRegion); + shutdown(); + } + + private void init() { + generator.init(); + } + + private void shutdown() { + listener.onSaving(); + generator.close(); + ticker.interrupt(); + listener.onClose(); + } + + private void visitRegion(int x, int z) { + while(paused.get() && !shutdown.get()) + { + J.sleep(50); + } + + if(shutdown.get()) + { + listener.onRegionSkipped(x, z); + return; + } + + listener.onRegionGenerating(x, z); + currentGeneratorMethod.set(generator.getMethod(x, z)); + + if(generator.supportsRegions(x, z)) + { + generator.generateRegion(x, z, listener); + } + + else + { + PregenTask.iterateRegion(x, z, (xx, zz) -> generator.generateChunk(xx, zz, listener)); + } + + listener.onRegionGenerated(x, z); + listener.onSaving(); + generator.save(); + } + + public void pause() + { + paused.set(true); + } + + public void resume() + { + paused.set(false); + } + + private PregenListener listenify(PregenListener listener) { + return new PregenListener() { + @Override + public void onTick(double chunksPerSecond, double chunksPerMinute, double regionsPerMinute, double percent, int generated, int totalChunks, int chunksRemaining, long eta, long elapsed, String method) { + listener.onTick( chunksPerSecond, chunksPerMinute, regionsPerMinute, percent, generated, totalChunks, chunksRemaining, eta, elapsed, method); + } + + @Override + public void onChunkGenerating(int x, int z) { + listener.onChunkGenerating(x, z); + } + + @Override + public void onChunkGenerated(int x, int z) { + listener.onChunkGenerated(x, z); + generated.addAndGet(1); + } + + @Override + public void onRegionGenerated(int x, int z) { + listener.onRegionGenerated(x, z); + } + + @Override + public void onRegionGenerating(int x, int z) { + listener.onRegionGenerating(x, z); + } + + @Override + public void onRegionSkipped(int x, int z) { + listener.onRegionSkipped(x, z); + } + + @Override + public void onClose() { + listener.onClose(); + } + + @Override + public void onSaving() { + listener.onSaving(); + } + }; + } + + public boolean paused() { + return paused.get(); + } +} diff --git a/src/main/java/com/volmit/iris/core/gui/PregeneratorGUI.java b/src/main/java/com/volmit/iris/core/pregenerator/PregenListener.java similarity index 59% rename from src/main/java/com/volmit/iris/core/gui/PregeneratorGUI.java rename to src/main/java/com/volmit/iris/core/pregenerator/PregenListener.java index 0aada2f15..5faec9d13 100644 --- a/src/main/java/com/volmit/iris/core/gui/PregeneratorGUI.java +++ b/src/main/java/com/volmit/iris/core/pregenerator/PregenListener.java @@ -16,7 +16,22 @@ * along with this program. If not, see . */ -package com.volmit.iris.core.gui; +package com.volmit.iris.core.pregenerator; -public class PregeneratorGUI { +public interface PregenListener { + void onTick(double chunksPerSecond, double chunksPerMinute, double regionsPerMinute, double percent, int generated, int totalChunks, int chunksRemaining, long eta, long elapsed, String method); + + void onChunkGenerating(int x, int z); + + void onChunkGenerated(int x, int z); + + void onRegionGenerated(int x, int z); + + void onRegionGenerating(int x, int z); + + void onRegionSkipped(int x, int z); + + void onClose(); + + void onSaving(); } diff --git a/src/main/java/com/volmit/iris/core/pregenerator/PregenTask.java b/src/main/java/com/volmit/iris/core/pregenerator/PregenTask.java new file mode 100644 index 000000000..007b3a49b --- /dev/null +++ b/src/main/java/com/volmit/iris/core/pregenerator/PregenTask.java @@ -0,0 +1,76 @@ +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2021 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.pregenerator; + +import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.math.Position2; +import com.volmit.iris.util.math.Spiraled; +import com.volmit.iris.util.math.Spiraler; +import lombok.Builder; +import lombok.Data; + +import java.util.Comparator; + +@Builder +@Data +public class PregenTask { + @Builder.Default + private Position2 center = new Position2(0,0); + + @Builder.Default + private int radius = 1; + + private static final KList order = computeChunkOrder(); + + public void iterateRegions(Spiraled s) + { + new Spiraler(radius * 2, radius * 2, s) + .setOffset(center.getX(), center.getZ()).drain(); + } + + public static void iterateRegion(int xr, int zr, Spiraled s) + { + for(Position2 i : order) + { + s.on(i.getX() + (xr << 5), i.getZ() + (zr << 5)); + } + } + + public void iterateAllChunks(Spiraled s) + { + new Spiraler(radius * 2, radius * 2, (x, z) -> iterateRegion(x, z, s)) + .setOffset(center.getX(), center.getZ()).drain(); + } + + private static KList computeChunkOrder() { + Position2 center = new Position2(15, 15); + KList p = new KList<>(); + new Spiraler(33, 33, (x, z) -> { + int xx = x + 15; + int zz = z + 15; + if (xx < 0 || xx > 31 || zz < 0 || zz > 31) { + return; + } + + p.add(new Position2(xx, zz)); + }).drain(); + p.sort(Comparator.comparing((i) -> i.distance(center))); + return p; + } +} diff --git a/src/main/java/com/volmit/iris/core/pregenerator/PregeneratorMethod.java b/src/main/java/com/volmit/iris/core/pregenerator/PregeneratorMethod.java new file mode 100644 index 000000000..a5c698ba8 --- /dev/null +++ b/src/main/java/com/volmit/iris/core/pregenerator/PregeneratorMethod.java @@ -0,0 +1,73 @@ +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2021 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.pregenerator; + +/** + * Represents something that is capable of generating in chunks or regions, or both + */ +public interface PregeneratorMethod { + /** + * This is called before any generate methods are called. Setup your generator here + */ + void init(); + + /** + * This is called after the pregenerator is done. Save your work and stop threads + */ + void close(); + + /** + * This is called every X amount of chunks or regions. Save work, + * but no need to save all of it. At the end, close() will still be called. + */ + void save(); + + /** + * Return true if regions can be generated + * @return true if they can be + * @param x the x region + * @param z the z region + */ + boolean supportsRegions(int x, int z); + + /** + * Return the name of the method being used + * @return the name + * @param x the x region + * @param z the z region + */ + String getMethod(int x, int z); + + /** + * Called to generate a region. Execute sync, if multicore internally, wait + * for the task to complete + * @param x the x + * @param z the z + * @param listener signal chunks generating & generated. Parallel capable. + */ + void generateRegion(int x, int z, PregenListener listener); + + /** + * Called to generate a chunk. You can go async so long as save will wait on the threads to finish + * @param x the x + * @param z the z + * @param listener + */ + void generateChunk(int x, int z, PregenListener listener); +} diff --git a/src/main/java/com/volmit/iris/core/pregenerator/methods/DummyPregenMethod.java b/src/main/java/com/volmit/iris/core/pregenerator/methods/DummyPregenMethod.java new file mode 100644 index 000000000..0147a0441 --- /dev/null +++ b/src/main/java/com/volmit/iris/core/pregenerator/methods/DummyPregenMethod.java @@ -0,0 +1,59 @@ +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2021 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.pregenerator.methods; + +import com.volmit.iris.core.pregenerator.PregenListener; +import com.volmit.iris.core.pregenerator.PregeneratorMethod; + +public class DummyPregenMethod implements PregeneratorMethod { + @Override + public void init() { + + } + + @Override + public void close() { + + } + + @Override + public String getMethod(int x, int z) { + return "Dummy"; + } + + @Override + public void save() { + + } + + @Override + public boolean supportsRegions(int x, int z) { + return false; + } + + @Override + public void generateRegion(int x, int z, PregenListener listener) { + + } + + @Override + public void generateChunk(int x, int z, PregenListener listener) { + + } +} diff --git a/src/main/java/com/volmit/iris/core/pregenerator/methods/HeadlessPregenMethod.java b/src/main/java/com/volmit/iris/core/pregenerator/methods/HeadlessPregenMethod.java new file mode 100644 index 000000000..bac3fe69c --- /dev/null +++ b/src/main/java/com/volmit/iris/core/pregenerator/methods/HeadlessPregenMethod.java @@ -0,0 +1,70 @@ +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2021 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.pregenerator.methods; + +import com.volmit.iris.core.pregenerator.PregenListener; +import com.volmit.iris.core.pregenerator.PregeneratorMethod; +import com.volmit.iris.engine.headless.HeadlessGenerator; +import com.volmit.iris.engine.headless.HeadlessWorld; + +public class HeadlessPregenMethod implements PregeneratorMethod { + private final HeadlessWorld world; + private final HeadlessGenerator generator; + + public HeadlessPregenMethod(HeadlessWorld world) + { + this.world = world; + this.generator = world.generate(); + } + + @Override + public void init() { + + } + + @Override + public void close() { + generator.close(); + } + + @Override + public void save() { + generator.save(); + } + + @Override + public boolean supportsRegions(int x, int z) { + return true; + } + + @Override + public String getMethod(int x, int z) { + return "Headless"; + } + + @Override + public void generateRegion(int x, int z, PregenListener listener) { + generator.generateRegion(x, z, listener); + } + + @Override + public void generateChunk(int x, int z, PregenListener listener) { + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/com/volmit/iris/core/pregenerator/methods/HybridPregenMethod.java b/src/main/java/com/volmit/iris/core/pregenerator/methods/HybridPregenMethod.java new file mode 100644 index 000000000..a50538405 --- /dev/null +++ b/src/main/java/com/volmit/iris/core/pregenerator/methods/HybridPregenMethod.java @@ -0,0 +1,87 @@ +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2021 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.pregenerator.methods; + +import com.volmit.iris.core.pregenerator.PregenListener; +import com.volmit.iris.core.pregenerator.PregeneratorMethod; +import com.volmit.iris.engine.IrisWorlds; +import com.volmit.iris.engine.headless.HeadlessWorld; +import org.bukkit.World; + +import java.io.File; + +public class HybridPregenMethod implements PregeneratorMethod { + private final PregeneratorMethod headless; + private final PregeneratorMethod inWorld; + private final World world; + + public HybridPregenMethod(World world, int threads) + { + this.world = world; + headless = supportsHeadless(world) + ? new HeadlessPregenMethod(HeadlessWorld.from(world)) : new DummyPregenMethod(); + inWorld = new PaperOrMedievalPregenMethod(world, threads); + } + + private boolean supportsHeadless(World world) { + return IrisWorlds.access(world) != null; + } + + @Override + public String getMethod(int x, int z) { + return "Hybrid<" + ((supportsRegions(x, z) ? headless.getMethod(x, z) : inWorld.getMethod(x, z)) + ">"); + } + + @Override + public void init() { + headless.init(); + inWorld.init(); + } + + @Override + public void close() { + headless.close(); + inWorld.close(); + } + + @Override + public void save() { + headless.save(); + inWorld.save(); + } + + @Override + public boolean supportsRegions(int x, int z) { + if (headless instanceof DummyPregenMethod) { + return false; + } + + return !new File(world.getWorldFolder(), "region/r." + x + "." + z + ".mca").exists(); + } + + @Override + public void generateRegion(int x, int z, PregenListener listener) { + headless.generateRegion(x, z, listener); + } + + @Override + public void generateChunk(int x, int z, PregenListener listener) { + inWorld.generateChunk(x, z, listener); + } +} diff --git a/src/main/java/com/volmit/iris/core/pregenerator/methods/MedievalPregenMethod.java b/src/main/java/com/volmit/iris/core/pregenerator/methods/MedievalPregenMethod.java new file mode 100644 index 000000000..49d2ed28a --- /dev/null +++ b/src/main/java/com/volmit/iris/core/pregenerator/methods/MedievalPregenMethod.java @@ -0,0 +1,108 @@ +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2021 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.pregenerator.methods; + +import com.volmit.iris.core.pregenerator.PregenListener; +import com.volmit.iris.core.pregenerator.PregeneratorMethod; +import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.scheduling.J; +import org.bukkit.Chunk; +import org.bukkit.World; + +import java.util.concurrent.CompletableFuture; + +public class MedievalPregenMethod implements PregeneratorMethod { + private final World world; + private final KList> futures; + + public MedievalPregenMethod(World world) + { + this.world = world; + futures = new KList<>(); + } + + private void waitForChunks() + { + for(CompletableFuture i : futures) + { + try { + i.get(); + } catch (Throwable e) { + e.printStackTrace(); + } + } + + futures.clear(); + } + + private void unloadAndSaveAllChunks() { + waitForChunks(); + J.s(() -> { + for(Chunk i : world.getLoadedChunks()) + { + i.unload(true); + } + world.save(); + }); + } + + @Override + public void init() { + unloadAndSaveAllChunks(); + } + + @Override + public void close() { + unloadAndSaveAllChunks(); + } + + @Override + public void save() { + unloadAndSaveAllChunks(); + } + + @Override + public boolean supportsRegions(int x, int z) { + return false; + } + + @Override + public void generateRegion(int x, int z, PregenListener listener) { + throw new UnsupportedOperationException(); + } + + @Override + public String getMethod(int x, int z) { + return "Medieval"; + } + + @Override + public void generateChunk(int x, int z, PregenListener listener) { + if(futures.size() > 32) + { + waitForChunks(); + } + + listener.onChunkGenerating(x, z); + futures.add(J.sfut(() -> { + world.getChunkAt(x, z); + listener.onChunkGenerated(x, z); + })); + } +} diff --git a/src/main/java/com/volmit/iris/core/pregenerator/methods/PaperAsyncPregenMethod.java b/src/main/java/com/volmit/iris/core/pregenerator/methods/PaperAsyncPregenMethod.java new file mode 100644 index 000000000..51eeb9ac4 --- /dev/null +++ b/src/main/java/com/volmit/iris/core/pregenerator/methods/PaperAsyncPregenMethod.java @@ -0,0 +1,125 @@ +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2021 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.pregenerator.methods; + +import com.volmit.iris.core.pregenerator.PregenListener; +import com.volmit.iris.core.pregenerator.PregeneratorMethod; +import com.volmit.iris.engine.parallel.MultiBurst; +import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.scheduling.J; +import io.papermc.lib.PaperLib; +import org.bukkit.Chunk; +import org.bukkit.World; + +import java.util.concurrent.CompletableFuture; + +public class PaperAsyncPregenMethod implements PregeneratorMethod { + private final World world; + private final MultiBurst burst; + private final KList> future; + + public PaperAsyncPregenMethod(World world, int threads) + { + if(!PaperLib.isPaper()) + { + throw new UnsupportedOperationException("Cannot use PaperAsync on non paper!"); + } + + this.world = world; + burst = new MultiBurst("Iris PaperAsync Pregenerator", 6, threads); + future = new KList<>(1024); + } + + private void unloadAndSaveAllChunks() { + J.s(() -> { + for(Chunk i : world.getLoadedChunks()) + { + i.unload(true); + } + world.save(); + }); + } + + private void completeChunk(int x, int z, PregenListener listener) { + try { + PaperLib.getChunkAtAsync(world, x, z, true).get(); + listener.onChunkGenerated(x, z); + } catch (Throwable e) { + e.printStackTrace(); + } + } + + private void waitForChunks() + { + for(CompletableFuture i : future) + { + try { + i.get(); + } catch (Throwable e) { + e.printStackTrace(); + } + } + + future.clear(); + } + + @Override + public void init() { + unloadAndSaveAllChunks(); + } + + @Override + public String getMethod(int x, int z) { + return "Async"; + } + + @Override + public void close() { + waitForChunks(); + burst.shutdownAndAwait(); + unloadAndSaveAllChunks(); + } + + @Override + public void save() { + waitForChunks(); + unloadAndSaveAllChunks(); + } + + @Override + public boolean supportsRegions(int x, int z) { + return false; + } + + @Override + public void generateRegion(int x, int z, PregenListener listener) { + throw new UnsupportedOperationException(); + } + + @Override + public void generateChunk(int x, int z, PregenListener listener) { + if(future.size() > 128) + { + waitForChunks(); + } + + listener.onChunkGenerating(x, z); + future.add(burst.complete(() -> completeChunk(x, z, listener))); + } +} diff --git a/src/main/java/com/volmit/iris/core/pregenerator/methods/PaperOrMedievalPregenMethod.java b/src/main/java/com/volmit/iris/core/pregenerator/methods/PaperOrMedievalPregenMethod.java new file mode 100644 index 000000000..6df75a9b7 --- /dev/null +++ b/src/main/java/com/volmit/iris/core/pregenerator/methods/PaperOrMedievalPregenMethod.java @@ -0,0 +1,68 @@ +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2021 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.pregenerator.methods; + +import com.volmit.iris.core.pregenerator.PregenListener; +import com.volmit.iris.core.pregenerator.PregeneratorMethod; +import io.papermc.lib.PaperLib; +import org.bukkit.World; + +public class PaperOrMedievalPregenMethod implements PregeneratorMethod { + private final PregeneratorMethod method; + + public PaperOrMedievalPregenMethod(World world, int threads) + { + method = PaperLib.isPaper() ? new PaperAsyncPregenMethod(world, threads) : new MedievalPregenMethod(world); + } + + @Override + public void init() { + method.init(); + } + + @Override + public void close() { + method.close(); + } + + @Override + public void save() { + method.save(); + } + + @Override + public String getMethod(int x, int z) { + return method.getMethod(x, z); + } + + @Override + public boolean supportsRegions(int x, int z) { + return false; + } + + @Override + public void generateRegion(int x, int z, PregenListener listener) { + throw new UnsupportedOperationException(); + } + + @Override + public void generateChunk(int x, int z, PregenListener listener) { + method.generateChunk(x, z, listener); + } +} diff --git a/src/main/java/com/volmit/iris/engine/data/mca/NBTWorld.java b/src/main/java/com/volmit/iris/engine/data/mca/NBTWorld.java index 3801412a5..22ed2935d 100644 --- a/src/main/java/com/volmit/iris/engine/data/mca/NBTWorld.java +++ b/src/main/java/com/volmit/iris/engine/data/mca/NBTWorld.java @@ -313,15 +313,7 @@ public class NBTWorld { if(mcaf == null) { - File f = getRegionFile(x, z); - try { - mcaf = f.exists() ? MCAUtil.read(f) : new MCAFile(x, z); - } catch (IOException e) { - Iris.error("Failed to properly read MCA File " + f.getPath() + " Using a blank one."); - e.printStackTrace(); - mcaf = new MCAFile(x, z); - } - + mcaf = new MCAFile(x, z); regionLock.lock(); loadedRegions.put(key, mcaf); regionLock.unlock(); diff --git a/src/main/java/com/volmit/iris/engine/framework/EngineCompositeGenerator.java b/src/main/java/com/volmit/iris/engine/framework/EngineCompositeGenerator.java index f16276dcb..145c10abc 100644 --- a/src/main/java/com/volmit/iris/engine/framework/EngineCompositeGenerator.java +++ b/src/main/java/com/volmit/iris/engine/framework/EngineCompositeGenerator.java @@ -23,6 +23,8 @@ import com.volmit.iris.core.IrisDataManager; import com.volmit.iris.core.IrisSettings; import com.volmit.iris.core.nms.BiomeBaseInjector; import com.volmit.iris.core.nms.INMS; +import com.volmit.iris.core.pregenerator.PregenListener; +import com.volmit.iris.core.pregenerator.PregenTask; import com.volmit.iris.engine.IrisEngineCompound; import com.volmit.iris.engine.IrisWorlds; import com.volmit.iris.engine.cache.Cache; @@ -460,17 +462,24 @@ public class EngineCompositeGenerator extends ChunkGenerator implements IrisAcce @Override public void directWriteMCA(IrisWorld w, int x, int z, NBTWorld writer, MultiBurst burst) { - BurstExecutor e = burst.burst(1024); - int mcaox = x << 5; - int mcaoz = z << 5; + directWriteMCA(w, x, z, writer, burst, null); + } - for (int i = 0; i < 32; i++) { - int ii = i; - for (int j = 0; j < 32; j++) { - int jj = j; - e.queue(() -> directWriteChunk(w, ii + mcaox, jj + mcaoz, writer)); + @Override + public void directWriteMCA(IrisWorld w, int x, int z, NBTWorld writer, MultiBurst burst, PregenListener l) { + BurstExecutor e = burst.burst(1024); + + PregenTask.iterateRegion(x, z, (ii, jj) -> e.queue(() -> { + if(l != null) + { + l.onChunkGenerating(ii, jj); } - } + directWriteChunk(w, ii, jj, writer); + if(l != null) + { + l.onChunkGenerated(ii, jj); + } + })); e.complete(); } diff --git a/src/main/java/com/volmit/iris/engine/framework/IrisAccess.java b/src/main/java/com/volmit/iris/engine/framework/IrisAccess.java index 8c6e692f7..1f8919b50 100644 --- a/src/main/java/com/volmit/iris/engine/framework/IrisAccess.java +++ b/src/main/java/com/volmit/iris/engine/framework/IrisAccess.java @@ -20,6 +20,7 @@ package com.volmit.iris.engine.framework; import com.volmit.iris.Iris; import com.volmit.iris.core.IrisDataManager; +import com.volmit.iris.core.pregenerator.PregenListener; import com.volmit.iris.engine.IrisComplex; import com.volmit.iris.engine.data.DataProvider; import com.volmit.iris.engine.data.mca.NBTWorld; @@ -47,6 +48,8 @@ public interface IrisAccess extends Hotloadable, DataProvider { void directWriteMCA(IrisWorld w, int x, int z, NBTWorld writer, MultiBurst burst); + void directWriteMCA(IrisWorld w, int x, int z, NBTWorld writer, MultiBurst burst, PregenListener listener); + void directWriteChunk(IrisWorld w, int x, int z, NBTWorld writer); int getGenerated(); diff --git a/src/main/java/com/volmit/iris/engine/headless/HeadlessGenerator.java b/src/main/java/com/volmit/iris/engine/headless/HeadlessGenerator.java index 68a2dcbc9..95b4e7251 100644 --- a/src/main/java/com/volmit/iris/engine/headless/HeadlessGenerator.java +++ b/src/main/java/com/volmit/iris/engine/headless/HeadlessGenerator.java @@ -18,6 +18,7 @@ package com.volmit.iris.engine.headless; +import com.volmit.iris.core.pregenerator.PregenListener; import com.volmit.iris.engine.data.mca.NBTWorld; import com.volmit.iris.engine.framework.EngineCompositeGenerator; import com.volmit.iris.engine.parallel.MultiBurst; @@ -51,6 +52,11 @@ public class HeadlessGenerator { generator.directWriteMCA(world.getWorld(), x, z, writer, burst); } + public void generateRegion(int x, int z, PregenListener listener) + { + generator.directWriteMCA(world.getWorld(), x, z, writer, burst, listener); + } + public File generateRegionToFile(int x, int z) { generateRegionToFile(x, z); diff --git a/src/main/java/com/volmit/iris/engine/headless/HeadlessWorld.java b/src/main/java/com/volmit/iris/engine/headless/HeadlessWorld.java index c0c09565a..4397274f9 100644 --- a/src/main/java/com/volmit/iris/engine/headless/HeadlessWorld.java +++ b/src/main/java/com/volmit/iris/engine/headless/HeadlessWorld.java @@ -20,7 +20,9 @@ package com.volmit.iris.engine.headless; import com.volmit.iris.Iris; import com.volmit.iris.core.IrisDataManager; +import com.volmit.iris.engine.IrisWorlds; import com.volmit.iris.engine.framework.EngineCompositeGenerator; +import com.volmit.iris.engine.framework.IrisAccess; import com.volmit.iris.engine.object.IrisDimension; import com.volmit.iris.engine.object.common.IrisWorld; import com.volmit.iris.util.plugin.VolmitSender; @@ -73,6 +75,10 @@ public class HeadlessWorld { .createWorld(); } + public static HeadlessWorld from(World world) { + return new HeadlessWorld(world.getName(), IrisWorlds.access(world).getTarget().getDimension(), world.getSeed()); + } + public static HeadlessWorld from(String name, String dimension, long seed) { return new HeadlessWorld(name, IrisDataManager.loadAnyDimension(dimension), seed); diff --git a/src/main/java/com/volmit/iris/util/math/Position2.java b/src/main/java/com/volmit/iris/util/math/Position2.java index 94be84e6e..93b17a101 100644 --- a/src/main/java/com/volmit/iris/util/math/Position2.java +++ b/src/main/java/com/volmit/iris/util/math/Position2.java @@ -43,6 +43,11 @@ public class Position2 { this.z = z; } + public String toString() + { + return "[" + x + "," + z + "]"; + } + @Override public int hashCode() { final int prime = 31; @@ -52,6 +57,11 @@ public class Position2 { return result; } + public Position2 regionToChunk() + { + return new Position2(x << 5, z << 5); + } + @Override public boolean equals(Object obj) { if (this == obj) { @@ -66,4 +76,8 @@ public class Position2 { public double distance(Position2 center) { return Math.pow(center.getX() - x, 2) + Math.pow(center.getZ() - z, 2); } + + public Position2 add(int x, int z) { + return new Position2(this.x + x, this.z + z); + } } diff --git a/src/main/java/com/volmit/iris/util/scheduling/J.java b/src/main/java/com/volmit/iris/util/scheduling/J.java index a9706c505..583492d51 100644 --- a/src/main/java/com/volmit/iris/util/scheduling/J.java +++ b/src/main/java/com/volmit/iris/util/scheduling/J.java @@ -208,6 +208,15 @@ public class J { Bukkit.getScheduler().scheduleSyncDelayedTask(Iris.instance, r); } + public static CompletableFuture sfut(Runnable r) { + CompletableFuture f = new CompletableFuture(); + Bukkit.getScheduler().scheduleSyncDelayedTask(Iris.instance, () -> { + r.run(); + f.complete(null); + }); + return f; + } + /** * Queue a sync task *