From c3ed7080dc6e8cb091cdf64f88c849d7b1879f5f Mon Sep 17 00:00:00 2001 From: Julian Krings Date: Fri, 23 Aug 2024 12:33:51 +0200 Subject: [PATCH] improve headless chunk saving --- .../iris/core/nms/v1_20_R3/Headless.java | 90 ++++++++++++++----- .../nms/v1_20_R3/mca/RegionFileStorage.java | 2 +- 2 files changed, 70 insertions(+), 22 deletions(-) diff --git a/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/Headless.java b/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/Headless.java index e8b2aa0c1..60f854fdf 100644 --- a/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/Headless.java +++ b/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/Headless.java @@ -24,10 +24,12 @@ import com.volmit.iris.core.nms.IHeadless; import com.volmit.iris.core.nms.v1_20_R3.mca.MCATerrainChunk; import com.volmit.iris.core.nms.v1_20_R3.mca.RegionFileStorage; import com.volmit.iris.core.pregenerator.PregenListener; +import com.volmit.iris.engine.data.cache.Cache; import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.engine.framework.EngineStage; import com.volmit.iris.engine.framework.WrongEngineBroException; import com.volmit.iris.engine.object.IrisBiome; +import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.context.ChunkContext; import com.volmit.iris.util.context.IrisContext; @@ -36,26 +38,32 @@ import com.volmit.iris.util.documentation.RegionCoordinates; import com.volmit.iris.util.hunk.Hunk; import com.volmit.iris.util.hunk.view.BiomeGridHunkHolder; import com.volmit.iris.util.hunk.view.ChunkDataHunkHolder; +import com.volmit.iris.util.hunk.view.SyncChunkDataHunkHolder; import com.volmit.iris.util.mantle.MantleFlag; import com.volmit.iris.util.math.RNG; import com.volmit.iris.util.parallel.MultiBurst; import com.volmit.iris.util.scheduling.J; import com.volmit.iris.util.scheduling.PrecisionStopwatch; import lombok.Getter; +import lombok.RequiredArgsConstructor; import net.minecraft.core.Holder; import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtIo; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.LevelHeightAccessor; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.ChunkStatus; import net.minecraft.world.level.chunk.ProtoChunk; +import net.minecraft.world.level.chunk.storage.RegionFile; import org.bukkit.Material; import org.bukkit.block.data.BlockData; +import java.io.DataOutputStream; import java.io.File; import java.io.IOException; import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; @@ -63,7 +71,8 @@ public class Headless implements IHeadless, LevelHeightAccessor { private final NMSBinding binding; private final Engine engine; private final RegionFileStorage storage; - private final ExecutorService executor = Executors.newSingleThreadExecutor(); + private final ExecutorService executor = Executors.newCachedThreadPool(); + private final KMap regions = new KMap<>(); private final AtomicInteger loadedChunks = new AtomicInteger(); private final KMap> customBiomes = new KMap<>(); private final KMap> minecraftBiomes = new KMap<>(); @@ -167,7 +176,10 @@ public class Headless implements IHeadless, LevelHeightAccessor { inject(engine, chunk, ctx); chunk.setStatus(ChunkStatus.FULL); - executor.submit(saveChunk(chunk)); + + long key = Cache.key(pos.getRegionX(), pos.getRegionZ()); + regions.computeIfAbsent(key, Region::new) + .add(chunk); } catch (Throwable e) { loadedChunks.decrementAndGet(); Iris.error("Failed to generate " + x + ", " + z); @@ -175,19 +187,6 @@ public class Headless implements IHeadless, LevelHeightAccessor { } } - private Runnable saveChunk(ProtoChunk chunk) { - return () -> { - if (closed) return; - try { - storage.write(chunk.getPos(), binding.serializeChunk(chunk, this)); - loadedChunks.decrementAndGet(); - } catch (Throwable e) { - Iris.error("Failed to save chunk " + chunk.getPos().x + ", " + chunk.getPos().z); - e.printStackTrace(); - } - }; - } - @BlockCoordinates private ChunkContext generate(Engine engine, int x, int z, Hunk vblocks, Hunk vbiomes) throws WrongEngineBroException { if (engine.isClosed()) { @@ -263,13 +262,12 @@ public class Headless implements IHeadless, LevelHeightAccessor { public void close() throws IOException { if (closed) return; try { - while (loadedChunks.get() > 0) - J.sleep(50); + regions.values().forEach(Region::submit); + Iris.info("Waiting for " + loadedChunks.get() + " chunks to unload..."); + while (loadedChunks.get() > 0 || !regions.isEmpty()) + J.sleep(1); + Iris.info("All chunks unloaded"); executor.shutdown(); - try { - if (executor.awaitTermination(5, TimeUnit.SECONDS)) - executor.shutdownNow(); - } catch (InterruptedException ignored) {} storage.close(); engine.getWorld().headless(null); } finally { @@ -278,4 +276,54 @@ public class Headless implements IHeadless, LevelHeightAccessor { minecraftBiomes.clear(); } } + + @RequiredArgsConstructor + private class Region implements Runnable { + private final int x, z; + private final long key; + private final KList chunks = new KList<>(1024); + private final AtomicBoolean full = new AtomicBoolean(); + + public Region(long key) { + this.x = Cache.keyX(key); + this.z = Cache.keyZ(key); + this.key = key; + } + + @Override + public void run() { + RegionFile regionFile; + try { + regionFile = storage.getRegionFile(new ChunkPos(x, z), false); + } catch (IOException e) { + Iris.error("Failed to load region file " + x + ", " + z); + Iris.reportError(e); + return; + } + if (regionFile == null) return; + + for (var chunk : chunks) { + try (DataOutputStream dos = regionFile.getChunkDataOutputStream(chunk.getPos())) { + NbtIo.write(binding.serializeChunk(chunk, Headless.this), dos); + } catch (Throwable e) { + Iris.error("Failed to save chunk " + chunk.getPos().x + ", " + chunk.getPos().z); + e.printStackTrace(); + } + loadedChunks.decrementAndGet(); + } + regions.remove(key); + } + + public synchronized void add(ProtoChunk chunk) { + chunks.add(chunk); + if (chunks.size() < 1024) + return; + submit(); + } + + public void submit() { + if (full.getAndSet(true)) return; + executor.submit(this); + } + } } diff --git a/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/mca/RegionFileStorage.java b/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/mca/RegionFileStorage.java index db51c2a56..4fe469492 100644 --- a/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/mca/RegionFileStorage.java +++ b/nms/v1_20_R3/src/main/java/com/volmit/iris/core/nms/v1_20_R3/mca/RegionFileStorage.java @@ -45,7 +45,7 @@ public final class RegionFileStorage implements AutoCloseable { this.sync = sync; } - public RegionFile getRegionFile(ChunkPos chunkPos, boolean existingOnly) throws IOException { + public synchronized RegionFile getRegionFile(ChunkPos chunkPos, boolean existingOnly) throws IOException { long id = ChunkPos.asLong(chunkPos.getRegionX(), chunkPos.getRegionZ()); RegionFile regionFile = this.regionCache.getAndMoveToFirst(id); if (regionFile != null) {