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 0abb77a15..1635dd3bb 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 @@ -46,18 +46,20 @@ import com.volmit.iris.util.interpolation.InterpolationMethod; 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.mantle.MantleChunk; +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.RNG; import com.volmit.iris.util.math.Spiraler; import com.volmit.iris.util.noise.CNG; -import com.volmit.iris.util.parallel.BurstExecutor; import com.volmit.iris.util.parallel.MultiBurst; +import com.volmit.iris.util.parallel.SyncExecutor; import com.volmit.iris.util.plugin.VolmitSender; import com.volmit.iris.util.scheduling.J; import com.volmit.iris.util.scheduling.O; import com.volmit.iris.util.scheduling.PrecisionStopwatch; -import com.volmit.iris.util.scheduling.jobs.QueueJob; +import com.volmit.iris.util.scheduling.jobs.ParallelQueueJob; import io.papermc.lib.PaperLib; import org.bukkit.*; import org.bukkit.event.inventory.InventoryType; @@ -76,8 +78,7 @@ import java.time.temporal.ChronoUnit; import java.util.Arrays; import java.util.Date; import java.util.Objects; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; @@ -161,70 +162,77 @@ public class CommandStudio implements DecreeExecutor { @Param(name = "radius", description = "The radius of nearby cunks", defaultValue = "5") int radius ) { - if (IrisToolbelt.isIrisWorld(player().getWorld())) { - VolmitSender sender = sender(); - J.a(() -> { - DecreeContext.touch(sender); - PlatformChunkGenerator plat = IrisToolbelt.access(player().getWorld()); - Engine engine = plat.getEngine(); - try { - Chunk cx = player().getLocation().getChunk(); - KList js = new KList<>(); - BurstExecutor b = MultiBurst.burst.burst(); - b.setMulticore(false); - int rad = engine.getMantle().getRadius(); - for (int i = -(radius + rad); i <= radius + rad; i++) { - for (int j = -(radius + rad); j <= radius + rad; j++) { - engine.getMantle().getMantle().deleteChunk(i + cx.getX(), j + cx.getZ()); - } - } - - for (int i = -radius; i <= radius; i++) { - for (int j = -radius; j <= radius; j++) { - int finalJ = j; - int finalI = i; - b.queue(() -> plat.injectChunkReplacement(player().getWorld(), finalI + cx.getX(), finalJ + cx.getZ(), (f) -> { - synchronized (js) { - js.add(f); - } - })); - } - } - - b.complete(); - sender().sendMessage(C.GREEN + "Regenerating " + Form.f(js.size()) + " Sections"); - QueueJob r = new QueueJob<>() { - final KList> futures = new KList<>(); - - @Override - public void execute(Runnable runnable) { - futures.add(J.sfut(runnable)); - - if (futures.size() > 64) { - while (futures.isNotEmpty()) { - try { - futures.remove(0).get(); - } catch (InterruptedException | ExecutionException e) { - e.printStackTrace(); - } - } - } - } - - @Override - public String getName() { - return "Regenerating"; - } - }; - r.queue(js); - r.execute(sender()); - } catch (Throwable e) { - sender().sendMessage("Unable to parse view-distance"); - } - }); - } else { + World world = player().getWorld(); + if (!IrisToolbelt.isIrisWorld(world)) { sender().sendMessage(C.RED + "You must be in an Iris World to use regen!"); } + + VolmitSender sender = sender(); + var loc = player().getLocation().clone(); + + J.a(() -> { + DecreeContext.touch(sender); + PlatformChunkGenerator plat = IrisToolbelt.access(world); + Engine engine = plat.getEngine(); + try (SyncExecutor executor = new SyncExecutor(20)) { + int x = loc.getBlockX() >> 4; + int z = loc.getBlockZ() >> 4; + + int rad = engine.getMantle().getRadius(); + var mantle = engine.getMantle().getMantle(); + var chunkMap = new KMap(); + for (int i = -(radius + rad); i <= radius + rad; i++) { + for (int j = -(radius + rad); j <= radius + rad; j++) { + int xx = i + x, zz = j + z; + if (Math.abs(i) <= radius && Math.abs(j) <= radius) { + mantle.deleteChunk(xx, zz); + continue; + } + chunkMap.put(new Position2(xx, zz), mantle.getChunk(xx, zz)); + mantle.deleteChunk(xx, zz); + } + } + + ParallelQueueJob job = new ParallelQueueJob<>() { + @Override + public void execute(Position2 p) { + plat.injectChunkReplacement(world, p.getX(), p.getZ(), executor); + } + + @Override + public String getName() { + return "Regenerating"; + } + }; + for (int i = -radius; i <= radius; i++) { + for (int j = -radius; j <= radius; j++) { + job.queue(new Position2(i + x, j + z)); + } + } + + CountDownLatch latch = new CountDownLatch(1); + job.execute(sender(), latch::countDown); + latch.await(); + + int sections = mantle.getWorldHeight() >> 4; + chunkMap.forEach((pos, chunk) -> { + var c = mantle.getChunk(pos.getX(), pos.getZ()); + for (MantleFlag flag : MantleFlag.values()) { + c.flag(flag, chunk.isFlagged(flag)); + } + c.clear(); + for (int y = 0; y < sections; y++) { + var slice = chunk.get(y); + if (slice == null) continue; + var s = c.getOrCreate(y); + slice.getSliceMap().forEach(s::putSlice); + } + }); + } catch (Throwable e) { + sender().sendMessage("Error while regenerating chunks"); + e.printStackTrace(); + } + }); } @Decree(description = "Convert objects in the \"convert\" folder") diff --git a/core/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java b/core/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java index 2e6035534..1f7fbd9a6 100644 --- a/core/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java +++ b/core/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java @@ -64,12 +64,10 @@ import org.jetbrains.annotations.Nullable; import java.io.File; import java.util.List; import java.util.Random; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Semaphore; +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.ReentrantLock; -import java.util.function.Consumer; @EqualsAndHashCode(callSuper = true) @Data @@ -95,8 +93,6 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun @Setter private volatile StudioGenerator studioGenerator; - private boolean initialized = false; - public BukkitChunkGenerator(IrisWorld world, boolean studio, File dataLocation, String dimensionKey) { setup = new AtomicBoolean(false); studioGenerator = null; @@ -114,8 +110,8 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun @EventHandler(priority = EventPriority.LOWEST) public void onWorldInit(WorldInitEvent event) { - if (initialized || !world.name().equals(event.getWorld().getName())) - return; + if (!world.name().equals(event.getWorld().getName())) return; + Iris.instance.unregisterListener(this); world.setRawWorldSeed(event.getWorld().getSeed()); if (initialize(event.getWorld())) return; @@ -140,7 +136,6 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun } spawnChunks.complete(INMS.get().getSpawnChunkCount(world)); Iris.instance.unregisterListener(this); - initialized = true; IrisWorlds.get().put(world.getName(), dimensionKey); return true; } @@ -205,50 +200,57 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun } @Override - public void injectChunkReplacement(World world, int x, int z, Consumer jobs) { + public void injectChunkReplacement(World world, int x, int z, Executor syncExecutor) { try { loadLock.acquire(); IrisBiomeStorage st = new IrisBiomeStorage(); TerrainChunk tc = TerrainChunk.createUnsafe(world, st); - Hunk blocks = Hunk.view(tc); - Hunk biomes = Hunk.view(tc, tc.getMinHeight(), tc.getMaxHeight()); this.world.bind(world); - getEngine().generate(x << 4, z << 4, blocks, biomes, true); - Iris.debug("Regenerated " + x + " " + z); - int t = 0; + getEngine().generate(x << 4, z << 4, tc, false); + + Chunk c = PaperLib.getChunkAtAsync(world, x, z) + .thenApply(d -> { + d.addPluginChunkTicket(Iris.instance); + + for (Entity ee : d.getEntities()) { + if (ee instanceof Player) { + continue; + } + + ee.remove(); + } + + engine.getWorldManager().onChunkLoad(d, false); + return d; + }).get(); + + + KList> futures = new KList<>(1 + getEngine().getHeight() >> 4); for (int i = getEngine().getHeight() >> 4; i >= 0; i--) { - if (!world.isChunkLoaded(x, z)) { - continue; - } - - Chunk c = world.getChunkAt(x, z); - for (Entity ee : c.getEntities()) { - if (ee instanceof Player) { - continue; - } - - J.s(ee::remove); - } - - J.s(() -> engine.getWorldManager().onChunkLoad(c, false)); - - int finalI = i; - jobs.accept(() -> { - + int finalI = i << 4; + futures.add(CompletableFuture.runAsync(() -> { for (int xx = 0; xx < 16; xx++) { for (int yy = 0; yy < 16; yy++) { for (int zz = 0; zz < 16; zz++) { - if (yy + (finalI << 4) >= engine.getHeight() || yy + (finalI << 4) < 0) { + if (yy + finalI >= engine.getHeight() || yy + finalI < 0) { continue; } - c.getBlock(xx, yy + (finalI << 4) + world.getMinHeight(), zz) - .setBlockData(tc.getBlockData(xx, yy + (finalI << 4) + world.getMinHeight(), zz), false); + int y = yy + finalI + world.getMinHeight(); + c.getBlock(xx, y, zz).setBlockData(tc.getBlockData(xx, y, zz), false); } } } - }); + }, syncExecutor)); } + CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) + .thenRunAsync(() -> { + c.removePluginChunkTicket(Iris.instance); + c.unload(); + }, syncExecutor) + .get(); + Iris.debug("Regenerated " + x + " " + z); + loadLock.release(); } catch (Throwable e) { loadLock.release(); diff --git a/core/src/main/java/com/volmit/iris/engine/platform/PlatformChunkGenerator.java b/core/src/main/java/com/volmit/iris/engine/platform/PlatformChunkGenerator.java index 687788527..93066fb62 100644 --- a/core/src/main/java/com/volmit/iris/engine/platform/PlatformChunkGenerator.java +++ b/core/src/main/java/com/volmit/iris/engine/platform/PlatformChunkGenerator.java @@ -28,7 +28,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.concurrent.CompletableFuture; -import java.util.function.Consumer; +import java.util.concurrent.Executor; public interface PlatformChunkGenerator extends Hotloadable, DataProvider { @Nullable @@ -42,7 +42,7 @@ public interface PlatformChunkGenerator extends Hotloadable, DataProvider { @NotNull EngineTarget getTarget(); - void injectChunkReplacement(World world, int x, int z, Consumer jobs); + void injectChunkReplacement(World world, int x, int z, Executor syncExecutor); void close(); diff --git a/core/src/main/java/com/volmit/iris/util/parallel/SyncExecutor.java b/core/src/main/java/com/volmit/iris/util/parallel/SyncExecutor.java new file mode 100644 index 000000000..00966b226 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/util/parallel/SyncExecutor.java @@ -0,0 +1,46 @@ +package com.volmit.iris.util.parallel; + +import com.volmit.iris.util.math.M; +import com.volmit.iris.util.scheduling.SR; +import org.jetbrains.annotations.NotNull; + +import java.util.Queue; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; + +public class SyncExecutor implements Executor, AutoCloseable { + private final CountDownLatch latch = new CountDownLatch(1); + private final Queue queue = new ConcurrentLinkedQueue<>(); + private final AtomicBoolean closed = new AtomicBoolean(false); + + public SyncExecutor(int msPerTick) { + new SR() { + @Override + public void run() { + var time = M.ms() + msPerTick; + while (time > M.ms()) { + Runnable r = queue.poll(); + if (r == null) break; + r.run(); + } + + if (closed.get() && queue.isEmpty()) { + cancel(); + latch.countDown(); + } + } + }; + } + + @Override + public void execute(@NotNull Runnable command) { + if (closed.get()) throw new IllegalStateException("Executor is closed!"); + queue.add(command); + } + + @Override + public void close() throws Exception { + closed.set(true); + latch.await(); + } +}