mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-06-17 14:21:33 +00:00
A lot of improvements thanks to CrazyDev!
This commit is contained in:
@@ -78,6 +78,7 @@ public class CommandIris implements DecreeExecutor {
|
|||||||
private CommandWhat what;
|
private CommandWhat what;
|
||||||
private CommandEdit edit;
|
private CommandEdit edit;
|
||||||
private CommandFind find;
|
private CommandFind find;
|
||||||
|
private CommandUpdater updater;
|
||||||
private CommandDeveloper developer;
|
private CommandDeveloper developer;
|
||||||
public static boolean worldCreation = false;
|
public static boolean worldCreation = false;
|
||||||
String WorldEngine;
|
String WorldEngine;
|
||||||
@@ -319,24 +320,6 @@ public class CommandIris implements DecreeExecutor {
|
|||||||
return dir.delete();
|
return dir.delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Decree(description = "Updates all chunk in the specified world")
|
|
||||||
public void updater(
|
|
||||||
@Param(description = "World to update chunks at")
|
|
||||||
World world
|
|
||||||
) {
|
|
||||||
if (!IrisToolbelt.isIrisWorld(world)) {
|
|
||||||
sender().sendMessage(C.GOLD + "This is not an Iris world");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ChunkUpdater updater = new ChunkUpdater(world);
|
|
||||||
if (sender().isPlayer()) {
|
|
||||||
sender().sendMessage(C.GREEN + "Updating " + world.getName() + " Total chunks: " + Form.f(updater.getChunks()));
|
|
||||||
} else {
|
|
||||||
Iris.info(C.GREEN + "Updating " + world.getName() + " Total chunks: " + Form.f(updater.getChunks()));
|
|
||||||
}
|
|
||||||
updater.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Decree(description = "Set aura spins")
|
@Decree(description = "Set aura spins")
|
||||||
public void aura(
|
public void aura(
|
||||||
@Param(description = "The h color value", defaultValue = "-20")
|
@Param(description = "The h color value", defaultValue = "-20")
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
* 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.volmit.iris.core.commands;
|
||||||
|
|
||||||
|
import org.bukkit.World;
|
||||||
|
|
||||||
|
import com.volmit.iris.Iris;
|
||||||
|
import com.volmit.iris.core.pregenerator.ChunkUpdater;
|
||||||
|
import com.volmit.iris.core.tools.IrisToolbelt;
|
||||||
|
import com.volmit.iris.util.decree.DecreeExecutor;
|
||||||
|
import com.volmit.iris.util.decree.DecreeOrigin;
|
||||||
|
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.format.Form;
|
||||||
|
|
||||||
|
@Decree(name = "Updater", origin = DecreeOrigin.BOTH, description = "Iris World Updater")
|
||||||
|
public class CommandUpdater implements DecreeExecutor {
|
||||||
|
private ChunkUpdater chunkUpdater;
|
||||||
|
|
||||||
|
@Decree(description = "Updates all chunk in the specified world")
|
||||||
|
public void start(
|
||||||
|
@Param(description = "World to update chunks at")
|
||||||
|
World world
|
||||||
|
) {
|
||||||
|
if (!IrisToolbelt.isIrisWorld(world)) {
|
||||||
|
sender().sendMessage(C.GOLD + "This is not an Iris world");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
chunkUpdater = new ChunkUpdater(world);
|
||||||
|
if (sender().isPlayer()) {
|
||||||
|
sender().sendMessage(C.GREEN + "Updating " + world.getName() + C.GRAY + " Total chunks: " + Form.f(chunkUpdater.getChunks()));
|
||||||
|
} else {
|
||||||
|
Iris.info(C.GREEN + "Updating " + world.getName() + C.GRAY + " Total chunks: " + Form.f(chunkUpdater.getChunks()));
|
||||||
|
}
|
||||||
|
chunkUpdater.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Decree(description = "Pause the updater")
|
||||||
|
public void pause(
|
||||||
|
@Param(description = "World to pause the Updater at")
|
||||||
|
World world
|
||||||
|
) {
|
||||||
|
if (!IrisToolbelt.isIrisWorld(world)) {
|
||||||
|
sender().sendMessage(C.GOLD + "This is not an Iris world");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (chunkUpdater == null) {
|
||||||
|
sender().sendMessage(C.GOLD + "You cant pause something that doesnt exist?");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
boolean status = chunkUpdater.pause();
|
||||||
|
if (sender().isPlayer()) {
|
||||||
|
if (status) {
|
||||||
|
sender().sendMessage(C.IRIS + "Paused task for: " + C.GRAY + world.getName());
|
||||||
|
} else {
|
||||||
|
sender().sendMessage(C.IRIS + "Unpause task for: " + C.GRAY + world.getName());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (status) {
|
||||||
|
Iris.info(C.IRIS + "Paused task for: " + C.GRAY + world.getName());
|
||||||
|
} else {
|
||||||
|
Iris.info(C.IRIS + "Unpause task for: " + C.GRAY + world.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Decree(description = "Stops the updater")
|
||||||
|
public void stop(
|
||||||
|
@Param(description = "World to stop the Updater at")
|
||||||
|
World world
|
||||||
|
) {
|
||||||
|
if (!IrisToolbelt.isIrisWorld(world)) {
|
||||||
|
sender().sendMessage(C.GOLD + "This is not an Iris world");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (chunkUpdater == null) {
|
||||||
|
sender().sendMessage(C.GOLD + "You cant stop something that doesnt exist?");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (sender().isPlayer()) {
|
||||||
|
sender().sendMessage("Stopping Updater for: " + C.GRAY + world.getName());
|
||||||
|
} else {
|
||||||
|
Iris.info("Stopping Updater for: " + C.GRAY + world.getName());
|
||||||
|
}
|
||||||
|
chunkUpdater.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -9,14 +9,11 @@ import com.volmit.iris.util.format.Form;
|
|||||||
import com.volmit.iris.util.math.M;
|
import com.volmit.iris.util.math.M;
|
||||||
import com.volmit.iris.util.math.RollingSequence;
|
import com.volmit.iris.util.math.RollingSequence;
|
||||||
import com.volmit.iris.util.math.Spiraler;
|
import com.volmit.iris.util.math.Spiraler;
|
||||||
import com.volmit.iris.util.reflect.V;
|
|
||||||
import com.volmit.iris.util.scheduling.J;
|
import com.volmit.iris.util.scheduling.J;
|
||||||
import io.papermc.lib.PaperLib;
|
import io.papermc.lib.PaperLib;
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
import org.bukkit.Chunk;
|
import org.bukkit.Chunk;
|
||||||
import org.bukkit.World;
|
import org.bukkit.World;
|
||||||
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -26,10 +23,10 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
public class ChunkUpdater {
|
public class ChunkUpdater {
|
||||||
|
private AtomicBoolean paused;
|
||||||
private AtomicBoolean cancelled;
|
private AtomicBoolean cancelled;
|
||||||
private KMap<Chunk, Long> lastUse;
|
private KMap<Chunk, Long> lastUse;
|
||||||
private final RollingSequence chunksPerSecond;
|
private final RollingSequence chunksPerSecond;
|
||||||
private final RollingSequence mcaregionsPerSecond;
|
|
||||||
private final AtomicInteger worldheightsize;
|
private final AtomicInteger worldheightsize;
|
||||||
private final AtomicInteger worldwidthsize;
|
private final AtomicInteger worldwidthsize;
|
||||||
private final AtomicInteger totalChunks;
|
private final AtomicInteger totalChunks;
|
||||||
@@ -40,25 +37,26 @@ public class ChunkUpdater {
|
|||||||
private AtomicInteger chunksUpdated;
|
private AtomicInteger chunksUpdated;
|
||||||
private AtomicLong startTime;
|
private AtomicLong startTime;
|
||||||
private ExecutorService executor;
|
private ExecutorService executor;
|
||||||
|
private ExecutorService chunkExecutor;
|
||||||
private ScheduledExecutorService scheduler;
|
private ScheduledExecutorService scheduler;
|
||||||
private final File[] McaFiles;
|
private CompletableFuture future;
|
||||||
|
private CountDownLatch latch;
|
||||||
|
private final Object pauseLock;
|
||||||
private final Engine engine;
|
private final Engine engine;
|
||||||
private final World world;
|
private final World world;
|
||||||
|
|
||||||
public ChunkUpdater(World world) {
|
public ChunkUpdater(World world) {
|
||||||
File cacheDir = new File("plugins" + File.separator + "iris" + File.separator + "cache");
|
|
||||||
File chunkCacheDir = new File("plugins" + File.separator + "iris" + File.separator + "cache" + File.separator + "spiral");
|
|
||||||
this.engine = IrisToolbelt.access(world).getEngine();
|
this.engine = IrisToolbelt.access(world).getEngine();
|
||||||
this.chunksPerSecond = new RollingSequence(10);
|
this.chunksPerSecond = new RollingSequence(5);
|
||||||
this.mcaregionsPerSecond = new RollingSequence(10);
|
|
||||||
this.world = world;
|
this.world = world;
|
||||||
this.lastUse = new KMap();
|
this.lastUse = new KMap();
|
||||||
this.McaFiles = new File(world.getWorldFolder(), "region").listFiles((dir, name) -> name.endsWith(".mca"));
|
|
||||||
this.worldheightsize = new AtomicInteger(calculateWorldDimensions(new File(world.getWorldFolder(), "region"), 1));
|
this.worldheightsize = new AtomicInteger(calculateWorldDimensions(new File(world.getWorldFolder(), "region"), 1));
|
||||||
this.worldwidthsize = new AtomicInteger(calculateWorldDimensions(new File(world.getWorldFolder(), "region"), 0));
|
this.worldwidthsize = new AtomicInteger(calculateWorldDimensions(new File(world.getWorldFolder(), "region"), 0));
|
||||||
int m = Math.max(worldheightsize.get(), worldwidthsize.get());
|
int m = Math.max(worldheightsize.get(), worldwidthsize.get());
|
||||||
this.executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() - 2);
|
this.executor = Executors.newFixedThreadPool(Math.max(Runtime.getRuntime().availableProcessors() / 3, 1));
|
||||||
|
this.chunkExecutor = Executors.newFixedThreadPool(Math.max(Runtime.getRuntime().availableProcessors() / 3, 1));
|
||||||
this.scheduler = Executors.newScheduledThreadPool(1);
|
this.scheduler = Executors.newScheduledThreadPool(1);
|
||||||
|
this.future = new CompletableFuture<>();
|
||||||
this.startTime = new AtomicLong();
|
this.startTime = new AtomicLong();
|
||||||
this.worldheightsize.set(m);
|
this.worldheightsize.set(m);
|
||||||
this.worldwidthsize.set(m);
|
this.worldwidthsize.set(m);
|
||||||
@@ -66,6 +64,9 @@ public class ChunkUpdater {
|
|||||||
this.chunksProcessed = new AtomicInteger();
|
this.chunksProcessed = new AtomicInteger();
|
||||||
this.chunksUpdated = new AtomicInteger();
|
this.chunksUpdated = new AtomicInteger();
|
||||||
this.position = new AtomicInteger(0);
|
this.position = new AtomicInteger(0);
|
||||||
|
this.latch = new CountDownLatch(totalMaxChunks.get());
|
||||||
|
this.paused = new AtomicBoolean(false);
|
||||||
|
this.pauseLock = new Object();
|
||||||
this.cancelled = new AtomicBoolean(false);
|
this.cancelled = new AtomicBoolean(false);
|
||||||
this.totalChunks = new AtomicInteger(0);
|
this.totalChunks = new AtomicInteger(0);
|
||||||
this.totalMcaregions = new AtomicInteger(0);
|
this.totalMcaregions = new AtomicInteger(0);
|
||||||
@@ -80,6 +81,22 @@ public class ChunkUpdater {
|
|||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean pause() {
|
||||||
|
unloadAndSaveAllChunks();
|
||||||
|
if (paused.get()) {
|
||||||
|
paused.set(false);
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
paused.set(true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
unloadAndSaveAllChunks();
|
||||||
|
cancelled.set(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private void update() {
|
private void update() {
|
||||||
Iris.info("Updating..");
|
Iris.info("Updating..");
|
||||||
@@ -87,47 +104,89 @@ public class ChunkUpdater {
|
|||||||
startTime.set(System.currentTimeMillis());
|
startTime.set(System.currentTimeMillis());
|
||||||
scheduler.scheduleAtFixedRate(() -> {
|
scheduler.scheduleAtFixedRate(() -> {
|
||||||
try {
|
try {
|
||||||
long eta = computeETA();
|
if (!paused.get()) {
|
||||||
long elapsedSeconds = (System.currentTimeMillis() - startTime.get()) / 1000;
|
long eta = computeETA();
|
||||||
int processed = chunksProcessed.get();
|
long elapsedSeconds = (System.currentTimeMillis() - startTime.get()) / 1000;
|
||||||
double cps = elapsedSeconds > 0 ? processed / (double) elapsedSeconds : 0;
|
int processed = chunksProcessed.get();
|
||||||
chunksPerSecond.put(cps);
|
double cps = elapsedSeconds > 0 ? processed / (double) elapsedSeconds : 0;
|
||||||
double percentage = ((double) chunksProcessed.get() / (double) totalMaxChunks.get()) * 100;
|
chunksPerSecond.put(cps);
|
||||||
Iris.info("Updated: " + Form.f(processed) + " of " + Form.f(totalMaxChunks.get()) + " (%.0f%%) " + Form.f(chunksPerSecond.getAverage()) + "/s, ETA: " + Form.duration(eta, 2), percentage);
|
double percentage = ((double) chunksProcessed.get() / (double) totalMaxChunks.get()) * 100;
|
||||||
|
if (!cancelled.get()) {
|
||||||
|
Iris.info("Updated: " + Form.f(processed) + " of " + Form.f(totalMaxChunks.get()) + " (%.0f%%) " + Form.f(chunksPerSecond.getAverage()) + "/s, ETA: " + Form.duration(eta,
|
||||||
|
2), percentage);
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}, 0, 3, TimeUnit.SECONDS);
|
}, 0, 3, TimeUnit.SECONDS);
|
||||||
|
|
||||||
for (int i = 0; i < totalMaxChunks.get(); i++) {
|
CompletableFuture.runAsync(() -> {
|
||||||
executor.submit(this::processNextChunk);
|
for (int i = 0; i < totalMaxChunks.get(); i++) {
|
||||||
}
|
if (paused.get()) {
|
||||||
|
synchronized (pauseLock) {
|
||||||
|
try {
|
||||||
|
pauseLock.wait();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Iris.error("Interrupted while waiting for executor: ");
|
||||||
|
e.printStackTrace();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
executor.submit(() -> {
|
||||||
|
if (!cancelled.get()) {
|
||||||
|
processNextChunk();
|
||||||
|
}
|
||||||
|
latch.countDown();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).thenRun(() -> {
|
||||||
|
try {
|
||||||
|
latch.await();
|
||||||
|
close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
executor.shutdown();
|
|
||||||
executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
|
|
||||||
scheduler.shutdownNow();
|
|
||||||
Iris.info("Processed: " + Form.f(chunksProcessed.get()) + " Chunks");
|
|
||||||
Iris.info("Finished Updating: " + Form.f(chunksUpdated.get()) + " Chunks");
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
try {
|
||||||
|
unloadAndSaveAllChunks();
|
||||||
|
executor.shutdown();
|
||||||
|
executor.awaitTermination(5, TimeUnit.SECONDS);
|
||||||
|
chunkExecutor.shutdown();
|
||||||
|
chunkExecutor.awaitTermination(5, TimeUnit.SECONDS);
|
||||||
|
scheduler.shutdownNow();
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
if (cancelled.get()) {
|
||||||
|
Iris.info("Updated: " + Form.f(chunksUpdated.get()) + " Chunks");
|
||||||
|
Iris.info("Irritated: " + Form.f(chunksProcessed.get()) + " of " + Form.f(totalMaxChunks.get()));
|
||||||
|
Iris.info("Stopped updater.");
|
||||||
|
} else {
|
||||||
|
Iris.info("Processed: " + Form.f(chunksProcessed.get()) + " Chunks");
|
||||||
|
Iris.info("Finished Updating: " + Form.f(chunksUpdated.get()) + " Chunks");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void processNextChunk() {
|
private void processNextChunk() {
|
||||||
int pos = position.getAndIncrement();
|
int pos = position.getAndIncrement();
|
||||||
int[] coords = getChunk(pos);
|
int[] coords = getChunk(pos);
|
||||||
if (areAllChunksGenerated(coords[0], coords[1])) {
|
if (loadChunksIfGenerated(coords[0], coords[1])) {
|
||||||
CompletableFuture<Void> chunkFuture = loadChunksIfGenerated(coords[0], coords[1]);
|
Chunk c = world.getChunkAt(coords[0], coords[1]);
|
||||||
chunkFuture.thenAccept(chunk -> {
|
engine.updateChunk(c);
|
||||||
Chunk c = world.getChunkAt(coords[0], coords[1]);
|
chunksUpdated.incrementAndGet();
|
||||||
engine.updateChunk(c);
|
|
||||||
chunksUpdated.getAndIncrement();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
chunksProcessed.getAndIncrement();
|
chunksProcessed.getAndIncrement();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean areAllChunksGenerated(int x, int z) {
|
private boolean loadChunksIfGenerated(int x, int z) {
|
||||||
for (int dx = -1; dx <= 1; dx++) {
|
for (int dx = -1; dx <= 1; dx++) {
|
||||||
for (int dz = -1; dz <= 1; dz++) {
|
for (int dz = -1; dz <= 1; dz++) {
|
||||||
if (!PaperLib.isChunkGenerated(world, x + dx, z + dz)) {
|
if (!PaperLib.isChunkGenerated(world, x + dx, z + dz)) {
|
||||||
@@ -135,34 +194,47 @@ public class ChunkUpdater {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private CompletableFuture<Void> loadChunksIfGenerated(int x, int z) {
|
AtomicBoolean generated = new AtomicBoolean(true);
|
||||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
KList<Future<?>> futures = new KList<>(9);
|
||||||
if (areAllChunksGenerated(x, z)) {
|
for (int dx = -1; dx <= 1; dx++) {
|
||||||
Runnable task = () -> {
|
for (int dz = -1; dz <= 1; dz++) {
|
||||||
try {
|
int xx = x + dx;
|
||||||
for (int dx = -1; dx <= 1; dx++) {
|
int zz = z + dz;
|
||||||
for (int dz = -1; dz <= 1; dz++) {
|
futures.add(chunkExecutor.submit(() -> {
|
||||||
Chunk c = world.getChunkAt(x + dx, z + dz);
|
Chunk c;
|
||||||
world.loadChunk(c);
|
try {
|
||||||
lastUse.put(c, M.ms());
|
c = PaperLib.getChunkAtAsync(world, xx, zz, false).get();
|
||||||
}
|
} catch (InterruptedException | ExecutionException e) {
|
||||||
|
generated.set(false);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
future.complete(null);
|
if (!c.isLoaded()) {
|
||||||
} catch (Exception e) {
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
future.completeExceptionally(e);
|
J.s(() -> {
|
||||||
}
|
c.load(false);
|
||||||
};
|
latch.countDown();
|
||||||
Bukkit.getScheduler().runTask(Iris.instance, task);
|
});
|
||||||
} else {
|
try {
|
||||||
future.completeExceptionally(new IllegalStateException("Not all chunks are generated"));
|
latch.await();
|
||||||
|
} catch (InterruptedException ignored) {}
|
||||||
|
}
|
||||||
|
if (!c.isGenerated()) {
|
||||||
|
generated.set(false);
|
||||||
|
}
|
||||||
|
lastUse.put(c, M.ms());
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return future;
|
while (!futures.isEmpty()) {
|
||||||
|
futures.removeIf(Future::isDone);
|
||||||
|
try {
|
||||||
|
Thread.sleep(50);
|
||||||
|
} catch (InterruptedException ignored) {}
|
||||||
|
}
|
||||||
|
return generated.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void unloadAndSaveAllChunks() {
|
private void unloadAndSaveAllChunks() {
|
||||||
try {
|
try {
|
||||||
J.sfut(() -> {
|
J.sfut(() -> {
|
||||||
|
|||||||
Reference in New Issue
Block a user