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..bd3f110f9
--- /dev/null
+++ b/src/main/java/com/volmit/iris/core/gui/PregeneratorJob.java
@@ -0,0 +1,299 @@
+/*
+ * 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.gui.components.Pregenerator;
+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.engine.object.IrisBiomeCustom;
+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.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;
+
+ public PregeneratorJob(PregenTask task, PregeneratorMethod method)
+ {
+ instance = this;
+ saving = false;
+ info = new String[]{"Initializing..."};
+ this.task = task;
+ this.pregenerator = new IrisPregenerator(task, method, this);
+ this.pregenerator.start();
+ }
+
+ 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
+ {
+ 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() {
+ saving = true;
+ }
+
+ private Position2 getMax() {
+ return task.getCenter().add(task.getRadius(), task.getRadius()).bottomRightChunkOfRegion();
+ }
+
+ private Position2 getMin() {
+ return task.getCenter().add(-task.getRadius(), -task.getRadius()).topLeftChunkOfRegion();
+ }
+
+ 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) {
+ Pregenerator.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;
+ }
+}