mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-02-16 10:30:53 +00:00
implement Headless for 1.20.4
This commit is contained in:
@@ -150,10 +150,12 @@ public class CommandDeveloper implements DecreeExecutor {
|
||||
@Decree(description = "Test")
|
||||
public void packBenchmark(
|
||||
@Param(description = "The pack to bench", aliases = {"pack"})
|
||||
IrisDimension dimension
|
||||
IrisDimension dimension,
|
||||
@Param(description = "Headless", defaultValue = "false")
|
||||
boolean headless
|
||||
) {
|
||||
Iris.info("test");
|
||||
IrisPackBenchmarking benchmark = new IrisPackBenchmarking(dimension, 1);
|
||||
IrisPackBenchmarking benchmark = new IrisPackBenchmarking(dimension, 1, headless);
|
||||
|
||||
}
|
||||
|
||||
|
||||
22
core/src/main/java/com/volmit/iris/core/nms/IHeadless.java
Normal file
22
core/src/main/java/com/volmit/iris/core/nms/IHeadless.java
Normal file
@@ -0,0 +1,22 @@
|
||||
package com.volmit.iris.core.nms;
|
||||
|
||||
import com.volmit.iris.core.pregenerator.PregenListener;
|
||||
import com.volmit.iris.util.documentation.ChunkCoordinates;
|
||||
import com.volmit.iris.util.documentation.RegionCoordinates;
|
||||
import com.volmit.iris.util.parallel.MultiBurst;
|
||||
|
||||
import java.io.Closeable;
|
||||
|
||||
public interface IHeadless extends Closeable {
|
||||
|
||||
void saveAll();
|
||||
|
||||
@RegionCoordinates
|
||||
boolean exists(int x, int z);
|
||||
|
||||
@RegionCoordinates
|
||||
void generateRegion(MultiBurst burst, int x, int z, PregenListener listener);
|
||||
|
||||
@ChunkCoordinates
|
||||
void generateChunk(int x, int z);
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.engine.object.IrisDimension;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.collection.KMap;
|
||||
import com.volmit.iris.util.documentation.RegionCoordinates;
|
||||
import com.volmit.iris.util.mantle.Mantle;
|
||||
import com.volmit.iris.util.math.Vector3d;
|
||||
import com.volmit.iris.util.nbt.mca.palette.MCABiomeContainer;
|
||||
@@ -119,4 +120,8 @@ public interface INMSBinding {
|
||||
boolean registerDimension(String name, IrisDimension dimension);
|
||||
|
||||
void injectBukkit();
|
||||
|
||||
default IHeadless createHeadless(Engine engine) {
|
||||
throw new IllegalStateException("Headless mode not supported");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
package com.volmit.iris.core.pregenerator.methods;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.IHeadless;
|
||||
import com.volmit.iris.core.nms.INMS;
|
||||
import com.volmit.iris.core.pregenerator.PregenListener;
|
||||
import com.volmit.iris.core.pregenerator.PregeneratorMethod;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.mantle.Mantle;
|
||||
import com.volmit.iris.util.parallel.MultiBurst;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
public class HeadlessPregenMethod implements PregeneratorMethod {
|
||||
private final Engine engine;
|
||||
private final IHeadless headless;
|
||||
private final MultiBurst burst;
|
||||
private final KList<Future<?>> futures;
|
||||
|
||||
public HeadlessPregenMethod(Engine engine) {
|
||||
this.engine = engine;
|
||||
this.headless = INMS.get().createHeadless(engine);
|
||||
this.burst = new MultiBurst("Iris Headless", Thread.MAX_PRIORITY);
|
||||
this.futures = new KList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() {}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
waitForChunksPartial(0);
|
||||
burst.close();
|
||||
headless.saveAll();
|
||||
try {
|
||||
headless.close();
|
||||
} catch (IOException e) {
|
||||
Iris.error("Failed to close headless");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save() {
|
||||
headless.saveAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRegions(int x, int z, PregenListener listener) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMethod(int x, int z) {
|
||||
return "Headless";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateRegion(int x, int z, PregenListener listener) {}
|
||||
|
||||
@Override
|
||||
public void generateChunk(int x, int z, PregenListener listener) {
|
||||
futures.removeIf(Future::isDone);
|
||||
waitForChunksPartial(512);
|
||||
futures.add(burst.complete(() -> {
|
||||
listener.onChunkGenerating(x, z);
|
||||
headless.generateChunk(x, z);
|
||||
listener.onChunkGenerated(x, z);
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mantle getMantle() {
|
||||
return engine.getMantle().getMantle();
|
||||
}
|
||||
|
||||
private void waitForChunksPartial(int maxWaiting) {
|
||||
futures.removeWhere(Objects::isNull);
|
||||
while (futures.size() > maxWaiting) {
|
||||
try {
|
||||
Future<?> i = futures.remove(0);
|
||||
|
||||
if (i == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
i.get();
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,17 @@ package com.volmit.iris.core.tools;
|
||||
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.IrisSettings;
|
||||
import com.volmit.iris.core.loader.IrisData;
|
||||
import com.volmit.iris.core.pregenerator.PregenTask;
|
||||
import com.volmit.iris.core.pregenerator.methods.HeadlessPregenMethod;
|
||||
import com.volmit.iris.core.pregenerator.methods.HybridPregenMethod;
|
||||
import com.volmit.iris.core.service.StudioSVC;
|
||||
import com.volmit.iris.engine.IrisEngine;
|
||||
import com.volmit.iris.engine.framework.Engine;
|
||||
import com.volmit.iris.engine.framework.EngineTarget;
|
||||
import com.volmit.iris.engine.object.IrisDimension;
|
||||
import com.volmit.iris.engine.object.IrisWorld;
|
||||
import com.volmit.iris.util.collection.KList;
|
||||
import com.volmit.iris.util.collection.KMap;
|
||||
import com.volmit.iris.util.exceptions.IrisException;
|
||||
@@ -36,13 +44,16 @@ public class IrisPackBenchmarking {
|
||||
public static boolean benchmarkInProgress = false;
|
||||
private IrisDimension IrisDimension;
|
||||
private int radius;
|
||||
private final boolean headless;
|
||||
private boolean finished = false;
|
||||
private Engine engine;
|
||||
PrecisionStopwatch stopwatch;
|
||||
|
||||
public IrisPackBenchmarking(IrisDimension dimension, int r) {
|
||||
public IrisPackBenchmarking(IrisDimension dimension, int r, boolean headless) {
|
||||
instance = this;
|
||||
this.IrisDimension = dimension;
|
||||
this.radius = r;
|
||||
this.headless = headless;
|
||||
runBenchmark();
|
||||
}
|
||||
|
||||
@@ -52,12 +63,12 @@ public class IrisPackBenchmarking {
|
||||
service.submit(() -> {
|
||||
Iris.info("Setting up benchmark environment ");
|
||||
benchmarkInProgress = true;
|
||||
File file = new File("benchmark");
|
||||
File file = new File(Bukkit.getWorldContainer(), "benchmark");
|
||||
if (file.exists()) {
|
||||
deleteDirectory(file.toPath());
|
||||
}
|
||||
createBenchmark();
|
||||
while (!IrisToolbelt.isIrisWorld(Bukkit.getWorld("benchmark"))) {
|
||||
engine = createBenchmark();
|
||||
while (!headless && !IrisToolbelt.isIrisWorld(Bukkit.getWorld("benchmark"))) {
|
||||
J.sleep(1000);
|
||||
Iris.debug("Iris PackBenchmark: Waiting...");
|
||||
}
|
||||
@@ -75,7 +86,6 @@ public class IrisPackBenchmarking {
|
||||
public void finishedBenchmark(KList<Integer> cps) {
|
||||
try {
|
||||
String time = Form.duration(stopwatch.getMillis());
|
||||
Engine engine = IrisToolbelt.access(Bukkit.getWorld("benchmark")).getEngine();
|
||||
Iris.info("-----------------");
|
||||
Iris.info("Results:");
|
||||
Iris.info("- Total time: " + time);
|
||||
@@ -88,8 +98,8 @@ public class IrisPackBenchmarking {
|
||||
File profilers = new File("plugins" + File.separator + "Iris" + File.separator + "packbenchmarks");
|
||||
profilers.mkdir();
|
||||
|
||||
File results = new File("plugins " + File.separator + "Iris", IrisDimension.getName() + LocalDateTime.now(Clock.systemDefaultZone()) + ".txt");
|
||||
results.createNewFile();
|
||||
File results = new File("plugins" + File.separator + "Iris", IrisDimension.getName() + " " + LocalDateTime.now(Clock.systemDefaultZone()).toString().replace(':', '-') + ".txt");
|
||||
results.getParentFile().mkdirs();
|
||||
KMap<String, Double> metrics = engine.getMetrics().pull();
|
||||
try (FileWriter writer = new FileWriter(results)) {
|
||||
writer.write("-----------------\n");
|
||||
@@ -123,15 +133,32 @@ public class IrisPackBenchmarking {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
private void createBenchmark(){
|
||||
private Engine createBenchmark(){
|
||||
try {
|
||||
IrisToolbelt.createWorld()
|
||||
if (headless) {
|
||||
IrisWorld world = IrisWorld.builder()
|
||||
.name("benchmark")
|
||||
.minHeight(IrisDimension.getMinHeight())
|
||||
.maxHeight(IrisDimension.getMaxHeight())
|
||||
.seed(1337)
|
||||
.worldFolder(new File(Bukkit.getWorldContainer(), "benchmark"))
|
||||
.environment(IrisDimension.getEnvironment())
|
||||
.build();
|
||||
Iris.service(StudioSVC.class).installIntoWorld(
|
||||
Iris.getSender(),
|
||||
IrisDimension.getLoadKey(),
|
||||
world.worldFolder());
|
||||
var data = IrisData.get(new File(world.worldFolder(), "iris/pack"));
|
||||
var dim = data.getDimensionLoader().load(IrisDimension.getLoadKey());
|
||||
return new IrisEngine(new EngineTarget(world, dim, data), false);
|
||||
}
|
||||
return IrisToolbelt.access(IrisToolbelt.createWorld()
|
||||
.dimension(IrisDimension.getName())
|
||||
.name("benchmark")
|
||||
.seed(1337)
|
||||
.studio(false)
|
||||
.benchmark(true)
|
||||
.create();
|
||||
.create()).getEngine();
|
||||
} catch (IrisException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@@ -146,8 +173,8 @@ public class IrisPackBenchmarking {
|
||||
.center(new Position2(x, z))
|
||||
.width(5)
|
||||
.height(5)
|
||||
.build(), Bukkit.getWorld("benchmark")
|
||||
);
|
||||
.build(), headless ? new HeadlessPregenMethod(engine) : new HybridPregenMethod(engine.getWorld().realWorld(),
|
||||
IrisSettings.getThreadCount(IrisSettings.get().getConcurrency().getParallelism())), engine);
|
||||
}
|
||||
|
||||
private double calculateAverage(KList<Integer> list) {
|
||||
|
||||
@@ -286,6 +286,13 @@ public class IrisEngine implements Engine {
|
||||
return generated.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addGenerated() {
|
||||
if (generated.incrementAndGet() == 661) {
|
||||
J.a(() -> getData().savePrefetch(this));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getGeneratedPerSecond() {
|
||||
if (perSecondLatch.flip()) {
|
||||
@@ -468,11 +475,7 @@ public class IrisEngine implements Engine {
|
||||
|
||||
getMantle().getMantle().flag(x >> 4, z >> 4, MantleFlag.REAL, true);
|
||||
getMetrics().getTotal().put(p.getMilliseconds());
|
||||
generated.incrementAndGet();
|
||||
|
||||
if (generated.get() == 661) {
|
||||
J.a(() -> getData().savePrefetch(this));
|
||||
}
|
||||
addGenerated();
|
||||
} catch (Throwable e) {
|
||||
Iris.reportError(e);
|
||||
fail("Failed to generate " + x + ", " + z, e);
|
||||
|
||||
@@ -577,6 +577,8 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat
|
||||
|
||||
int getGenerated();
|
||||
|
||||
void addGenerated();
|
||||
|
||||
default <T> IrisPosition lookForStreamResult(T find, ProceduralStream<T> stream, Function2<T, T, Boolean> matcher, long timeout) {
|
||||
AtomicInteger checked = new AtomicInteger();
|
||||
AtomicLong time = new AtomicLong(M.ms());
|
||||
|
||||
@@ -0,0 +1,262 @@
|
||||
package com.volmit.iris.core.nms.v1_20_R3;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.BiomeBaseInjector;
|
||||
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.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.KMap;
|
||||
import com.volmit.iris.util.context.ChunkContext;
|
||||
import com.volmit.iris.util.context.IrisContext;
|
||||
import com.volmit.iris.util.documentation.BlockCoordinates;
|
||||
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.mantle.MantleFlag;
|
||||
import com.volmit.iris.util.math.RNG;
|
||||
import com.volmit.iris.util.parallel.MultiBurst;
|
||||
import com.volmit.iris.util.scheduling.Looper;
|
||||
import com.volmit.iris.util.scheduling.PrecisionStopwatch;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
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 org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class Headless implements IHeadless, LevelHeightAccessor {
|
||||
private final NMSBinding binding;
|
||||
private final Engine engine;
|
||||
private final RegionFileStorage storage;
|
||||
private final Queue<ProtoChunk> chunkQueue = new ArrayDeque<>();
|
||||
private final ReentrantLock manualLock = new ReentrantLock();
|
||||
private final KMap<String, Holder<Biome>> customBiomes = new KMap<>();
|
||||
private final KMap<NamespacedKey, Holder<Biome>> minecraftBiomes = new KMap<>();
|
||||
private boolean closed = false;
|
||||
|
||||
public Headless(NMSBinding binding, Engine engine) {
|
||||
this.binding = binding;
|
||||
this.engine = engine;
|
||||
this.storage = new RegionFileStorage(new File(engine.getWorld().worldFolder(), "region").toPath(), false);
|
||||
var queueLooper = new Looper() {
|
||||
@Override
|
||||
protected long loop() {
|
||||
if (manualLock.isLocked()) {
|
||||
manualLock.lock();
|
||||
manualLock.unlock();
|
||||
}
|
||||
saveAll();
|
||||
return closed ? -1 : 100;
|
||||
}
|
||||
};
|
||||
queueLooper.setName("Region Save Looper");
|
||||
queueLooper.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(int x, int z) {
|
||||
if (closed) return false;
|
||||
try {
|
||||
return storage.getRegionFile(new ChunkPos(x << 5, z << 5), true) != null;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveAll() {
|
||||
manualLock.lock();
|
||||
try {
|
||||
save();
|
||||
} finally {
|
||||
manualLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private void save() {
|
||||
if (closed) return;
|
||||
while (!chunkQueue.isEmpty()) {
|
||||
ChunkAccess chunk = chunkQueue.poll();
|
||||
if (chunk == null) break;
|
||||
try {
|
||||
storage.write(chunk.getPos(), binding.serializeChunk(chunk, this));
|
||||
} catch (Throwable e) {
|
||||
Iris.error("Failed to save chunk " + chunk.getPos().x + ", " + chunk.getPos().z);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateRegion(MultiBurst burst, int x, int z, PregenListener listener) {
|
||||
if (closed) return;
|
||||
boolean listening = listener != null;
|
||||
if (listening) listener.onRegionGenerating(x, z);
|
||||
CountDownLatch latch = new CountDownLatch(1024);
|
||||
iterateRegion(x, z, pos -> burst.complete(() -> {
|
||||
if (listening) listener.onChunkGenerating(pos.x, pos.z);
|
||||
generateChunk(pos.x, pos.z);
|
||||
if (listening) listener.onChunkGenerated(pos.x, pos.z);
|
||||
latch.countDown();
|
||||
}));
|
||||
try {
|
||||
latch.await();
|
||||
} catch (InterruptedException ignored) {}
|
||||
if (listening) listener.onRegionGenerated(x, z);
|
||||
}
|
||||
|
||||
@RegionCoordinates
|
||||
private static void iterateRegion(int x, int z, Consumer<ChunkPos> chunkPos) {
|
||||
int cX = x << 5;
|
||||
int cZ = z << 5;
|
||||
for (int xx = 0; xx < 32; xx++) {
|
||||
for (int zz = 0; zz < 32; zz++) {
|
||||
chunkPos.accept(new ChunkPos(cX + xx, cZ + zz));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateChunk(int x, int z) {
|
||||
if (closed) return;
|
||||
try {
|
||||
var pos = new ChunkPos(x, z);
|
||||
try {
|
||||
CompoundTag tag = storage.read(pos);
|
||||
if (tag != null && !"empty".equals(tag.getString("Status"))) {
|
||||
return;
|
||||
}
|
||||
} catch (Throwable ignored) {}
|
||||
|
||||
ProtoChunk chunk = binding.createProtoChunk(pos, this);
|
||||
var tc = new MCATerrainChunk(chunk);
|
||||
|
||||
ChunkDataHunkHolder blocks = new ChunkDataHunkHolder(tc);
|
||||
BiomeGridHunkHolder biomes = new BiomeGridHunkHolder(tc, tc.getMinHeight(), tc.getMaxHeight());
|
||||
ChunkContext ctx = generate(engine, pos.x << 4, pos.z << 4, blocks, biomes);
|
||||
blocks.apply();
|
||||
biomes.apply();
|
||||
|
||||
inject(engine, tc.getBiomeBaseInjector(), chunk, ctx); //TODO improve
|
||||
chunk.setStatus(ChunkStatus.FULL);
|
||||
chunkQueue.add(chunk);
|
||||
} catch (Throwable e) {
|
||||
Iris.error("Failed to generate " + x + ", " + z);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@BlockCoordinates
|
||||
private ChunkContext generate(Engine engine, int x, int z, Hunk<BlockData> vblocks, Hunk<org.bukkit.block.Biome> vbiomes) throws WrongEngineBroException {
|
||||
if (engine.isClosed()) {
|
||||
throw new WrongEngineBroException();
|
||||
}
|
||||
|
||||
engine.getContext().touch();
|
||||
engine.getEngineData().getStatistics().generatedChunk();
|
||||
ChunkContext ctx = null;
|
||||
try {
|
||||
PrecisionStopwatch p = PrecisionStopwatch.start();
|
||||
Hunk<BlockData> blocks = vblocks.listen((xx, y, zz, t) -> engine.catchBlockUpdates(x + xx, y + engine.getMinHeight(), z + zz, t));
|
||||
|
||||
var dimension = engine.getDimension();
|
||||
if (dimension.isDebugChunkCrossSections() && ((x >> 4) % dimension.getDebugCrossSectionsMod() == 0 || (z >> 4) % dimension.getDebugCrossSectionsMod() == 0)) {
|
||||
for (int i = 0; i < 16; i++) {
|
||||
for (int j = 0; j < 16; j++) {
|
||||
blocks.set(i, 0, j, Material.CRYING_OBSIDIAN.createBlockData());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ctx = new ChunkContext(x, z, engine.getComplex());
|
||||
IrisContext.getOr(engine).setChunkContext(ctx);
|
||||
|
||||
for (EngineStage i : engine.getMode().getStages()) {
|
||||
i.generate(x, z, blocks, vbiomes, true, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
engine.getMantle().getMantle().flag(x >> 4, z >> 4, MantleFlag.REAL, true);
|
||||
engine.getMetrics().getTotal().put(p.getMilliseconds());
|
||||
engine.addGenerated();
|
||||
|
||||
} catch (Throwable e) {
|
||||
Iris.reportError(e);
|
||||
engine.fail("Failed to generate " + x + ", " + z, e);
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
private void inject(Engine engine, BiomeBaseInjector injector, ChunkAccess chunk, ChunkContext ctx) {
|
||||
var pos = chunk.getPos();
|
||||
for (int y = engine.getMinHeight(); y < engine.getMaxHeight(); y++) {
|
||||
for (int x = 0; x < 16; x++) {
|
||||
for (int z = 0; z < 16; z++) {
|
||||
int wX = pos.getBlockX(x);
|
||||
int wZ = pos.getBlockZ(z);
|
||||
try {
|
||||
injector.setBiome(x, y, z, getNoiseBiome(engine, ctx, x, z, wX, y, wZ));
|
||||
} catch (Throwable e) {
|
||||
Iris.error("Failed to inject biome for " + wX + ", " + y + ", " + wZ);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Holder<Biome> getNoiseBiome(Engine engine, ChunkContext ctx, int rX, int rZ, int x, int y, int z) {
|
||||
RNG rng = new RNG(engine.getSeedManager().getBiome());
|
||||
int m = (y - engine.getMinHeight()) << 2;
|
||||
IrisBiome ib = ctx == null ?
|
||||
engine.getComplex().getTrueBiomeStream().get(x << 2, z << 2) :
|
||||
ctx.getBiome().get(rX, rZ);
|
||||
if (ib.isCustom()) {
|
||||
return customBiomes.computeIfAbsent(ib.getCustomBiome(rng, x << 2, m, z << 2).getId(),
|
||||
id -> binding.getBiomeHolder(engine.getDimension().getLoadKey(), id));
|
||||
} else {
|
||||
return minecraftBiomes.computeIfAbsent(ib.getSkyBiome(rng, x << 2, m, z << 2).getKey(),
|
||||
id -> binding.getBiomeHolder(id.getNamespace(), id.getKey()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (closed) return;
|
||||
try {
|
||||
storage.close();
|
||||
} finally {
|
||||
closed = true;
|
||||
customBiomes.clear();
|
||||
minecraftBiomes.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return engine.getHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinBuildHeight() {
|
||||
return engine.getMinHeight();
|
||||
}
|
||||
}
|
||||
@@ -21,9 +21,18 @@ import com.mojang.datafixers.util.Pair;
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.JsonOps;
|
||||
import com.mojang.serialization.Lifecycle;
|
||||
import com.volmit.iris.core.nms.IHeadless;
|
||||
import com.volmit.iris.core.nms.v1_20_R3.mca.ChunkSerializer;
|
||||
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.engine.object.IrisDimension;
|
||||
import com.volmit.iris.util.documentation.RegionCoordinates;
|
||||
import com.volmit.iris.util.format.C;
|
||||
import com.volmit.iris.util.hunk.view.BiomeGridHunkHolder;
|
||||
import com.volmit.iris.util.hunk.view.ChunkDataHunkHolder;
|
||||
import com.volmit.iris.util.io.IO;
|
||||
import com.volmit.iris.util.parallel.MultiBurst;
|
||||
import com.volmit.iris.util.scheduling.PrecisionStopwatch;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
|
||||
import net.bytebuddy.ByteBuddy;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
@@ -35,7 +44,11 @@ import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.progress.ChunkProgressListener;
|
||||
import net.minecraft.util.GsonHelper;
|
||||
import net.minecraft.world.RandomSequences;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.LevelHeightAccessor;
|
||||
import net.minecraft.world.level.chunk.ProtoChunk;
|
||||
import net.minecraft.world.level.chunk.UpgradeData;
|
||||
import net.minecraft.world.level.dimension.DimensionType;
|
||||
import net.minecraft.world.level.dimension.LevelStem;
|
||||
import net.minecraft.world.level.storage.LevelStorageSource;
|
||||
@@ -782,6 +795,11 @@ public class NMSBinding implements INMSBinding {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public IHeadless createHeadless(Engine engine) {
|
||||
return new Headless(this, engine);
|
||||
}
|
||||
|
||||
private static class ServerLevelAdvice {
|
||||
@Advice.OnMethodEnter
|
||||
static void enter(@Advice.Argument(0) MinecraftServer server, @Advice.Argument(2) LevelStorageSource.LevelStorageAccess access, @Advice.Argument(4) ResourceKey<Level> key, @Advice.Argument(value = 5, readOnly = false) LevelStem levelStem) {
|
||||
@@ -814,4 +832,16 @@ public class NMSBinding implements INMSBinding {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Holder.Reference<net.minecraft.world.level.biome.Biome> getBiomeHolder(String namespace, String id) {
|
||||
return getCustomBiomeRegistry().getHolder(ResourceKey.create(Registries.BIOME, new ResourceLocation(namespace, id))).orElse(null);
|
||||
}
|
||||
|
||||
ProtoChunk createProtoChunk(ChunkPos pos, LevelHeightAccessor heightAccessor) {
|
||||
return new ProtoChunk(pos, UpgradeData.EMPTY, heightAccessor, getCustomBiomeRegistry(), null);
|
||||
}
|
||||
|
||||
net.minecraft.nbt.CompoundTag serializeChunk(ChunkAccess chunkAccess, LevelHeightAccessor heightAccessor) {
|
||||
return ChunkSerializer.write(chunkAccess, heightAccessor, getCustomBiomeRegistry());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
package com.volmit.iris.core.nms.v1_20_R3.mca;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.DataResult;
|
||||
import it.unimi.dsi.fastutil.shorts.ShortList;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.ListTag;
|
||||
import net.minecraft.nbt.LongArrayTag;
|
||||
import net.minecraft.nbt.NbtOps;
|
||||
import net.minecraft.nbt.NbtUtils;
|
||||
import net.minecraft.nbt.ShortTag;
|
||||
import net.minecraft.nbt.Tag;
|
||||
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.CarvingMask;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.LevelChunkSection;
|
||||
import net.minecraft.world.level.chunk.PalettedContainerRO;
|
||||
import net.minecraft.world.level.chunk.ProtoChunk;
|
||||
import net.minecraft.world.level.chunk.UpgradeData;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess.TicksToSave;
|
||||
import net.minecraft.world.level.levelgen.BelowZeroRetrogen;
|
||||
import net.minecraft.world.level.levelgen.Heightmap;
|
||||
import net.minecraft.world.level.levelgen.GenerationStep.Carving;
|
||||
import net.minecraft.world.level.levelgen.Heightmap.Types;
|
||||
import net.minecraft.world.level.levelgen.blending.BlendingData;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import static net.minecraft.world.level.chunk.storage.ChunkSerializer.BLOCK_STATE_CODEC;
|
||||
|
||||
public class ChunkSerializer {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static Method CODEC = null;
|
||||
|
||||
static {
|
||||
for (Method method : net.minecraft.world.level.chunk.storage.ChunkSerializer.class.getDeclaredMethods()) {
|
||||
if (method.getReturnType().equals(Codec.class) && method.getParameterCount() == 1) {
|
||||
CODEC = method;
|
||||
CODEC.setAccessible(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static CompoundTag write(ChunkAccess chunk, LevelHeightAccessor heightAccessor, Registry<Biome> biomeRegistry) {
|
||||
ChunkPos pos = chunk.getPos();
|
||||
CompoundTag data = NbtUtils.addCurrentDataVersion(new CompoundTag());
|
||||
data.putInt("xPos", pos.x);
|
||||
data.putInt("yPos", ((LevelHeightAccessor) chunk).getMinSection());
|
||||
data.putInt("zPos", pos.z);
|
||||
data.putLong("LastUpdate", 0L);
|
||||
data.putLong("InhabitedTime", chunk.getInhabitedTime());
|
||||
data.putString("Status", BuiltInRegistries.CHUNK_STATUS.getKey(chunk.getStatus()).toString());
|
||||
BlendingData blendingdata = chunk.getBlendingData();
|
||||
if (blendingdata != null) {
|
||||
DataResult<Tag> dataResult = BlendingData.CODEC.encodeStart(NbtOps.INSTANCE, blendingdata);
|
||||
dataResult.resultOrPartial(LOGGER::error).ifPresent(base -> data.put("blending_data", base));
|
||||
}
|
||||
|
||||
BelowZeroRetrogen belowzeroretrogen = chunk.getBelowZeroRetrogen();
|
||||
if (belowzeroretrogen != null) {
|
||||
DataResult<Tag> dataResult = BelowZeroRetrogen.CODEC.encodeStart(NbtOps.INSTANCE, belowzeroretrogen);
|
||||
dataResult.resultOrPartial(LOGGER::error).ifPresent(base -> data.put("below_zero_retrogen", base));
|
||||
}
|
||||
|
||||
UpgradeData upgradeData = chunk.getUpgradeData();
|
||||
if (!upgradeData.isEmpty()) {
|
||||
data.put("UpgradeData", upgradeData.write());
|
||||
}
|
||||
|
||||
LevelChunkSection[] chunkSections = chunk.getSections();
|
||||
ListTag sections = new ListTag();
|
||||
Codec<PalettedContainerRO<Holder<Biome>>> codec;
|
||||
try {
|
||||
codec = (Codec<PalettedContainerRO<Holder<Biome>>>) CODEC.invoke(null, biomeRegistry);
|
||||
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
int minLightSection = heightAccessor.getMinSection() - 1;
|
||||
int maxLightSection = minLightSection + heightAccessor.getSectionsCount() + 2;
|
||||
for (int y = minLightSection; y < maxLightSection; y++) {
|
||||
int i = ((LevelHeightAccessor) chunk).getSectionIndexFromSectionY(y);
|
||||
if (i >= 0 && i < chunkSections.length) {
|
||||
CompoundTag section = new CompoundTag();
|
||||
LevelChunkSection chunkSection = chunkSections[i];
|
||||
DataResult<Tag> dataResult = BLOCK_STATE_CODEC.encodeStart(NbtOps.INSTANCE, chunkSection.getStates());
|
||||
section.put("block_states", dataResult.getOrThrow(false, LOGGER::error));
|
||||
dataResult = codec.encodeStart(NbtOps.INSTANCE, chunkSection.getBiomes());
|
||||
section.put("biomes", dataResult.getOrThrow(false, LOGGER::error));
|
||||
|
||||
if (!section.isEmpty()) {
|
||||
section.putByte("Y", (byte)y);
|
||||
sections.add(section);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.put("sections", sections);
|
||||
if (chunk.isLightCorrect()) {
|
||||
data.putBoolean("isLightOn", true);
|
||||
}
|
||||
|
||||
ListTag blockEntities = new ListTag();
|
||||
for (BlockPos blockPos : chunk.getBlockEntitiesPos()) {
|
||||
CompoundTag blockEntityNbt = chunk.getBlockEntityNbtForSaving(blockPos);
|
||||
if (blockEntityNbt != null) {
|
||||
blockEntities.add(blockEntityNbt);
|
||||
}
|
||||
}
|
||||
data.put("block_entities", blockEntities);
|
||||
|
||||
if (chunk instanceof ProtoChunk protoChunk) {
|
||||
ListTag entities = new ListTag();
|
||||
entities.addAll(protoChunk.getEntities());
|
||||
data.put("entities", entities);
|
||||
|
||||
CompoundTag carvingMasks = new CompoundTag();
|
||||
for (Carving carving : Carving.values()) {
|
||||
CarvingMask carvingMask = protoChunk.getCarvingMask(carving);
|
||||
if (carvingMask != null) {
|
||||
carvingMasks.putLongArray(carving.toString(), carvingMask.toArray());
|
||||
}
|
||||
}
|
||||
data.put("CarvingMasks", carvingMasks);
|
||||
}
|
||||
|
||||
saveTicks(data, chunk.getTicksForSerialization());
|
||||
data.put("PostProcessing", packOffsets(chunk.getPostProcessing()));
|
||||
CompoundTag heightmaps = new CompoundTag();
|
||||
|
||||
for (Entry<Types, Heightmap> entry : chunk.getHeightmaps()) {
|
||||
if (chunk.getStatus().heightmapsAfter().contains(entry.getKey())) {
|
||||
heightmaps.put(entry.getKey().getSerializationKey(), new LongArrayTag(entry.getValue().getRawData()));
|
||||
}
|
||||
}
|
||||
|
||||
data.put("Heightmaps", heightmaps);
|
||||
|
||||
CompoundTag structures = new CompoundTag();
|
||||
structures.put("starts", new CompoundTag());
|
||||
structures.put("References", new CompoundTag());
|
||||
data.put("structures", structures);
|
||||
if (!chunk.persistentDataContainer.isEmpty()) {
|
||||
data.put("ChunkBukkitValues", chunk.persistentDataContainer.toTagCompound());
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private static void saveTicks(CompoundTag compoundTag, TicksToSave ticksToSave) {
|
||||
compoundTag.put("block_ticks", ticksToSave.blocks().save(0, block -> BuiltInRegistries.BLOCK.getKey(block).toString()));
|
||||
compoundTag.put("fluid_ticks", ticksToSave.fluids().save(0, fluid -> BuiltInRegistries.FLUID.getKey(fluid).toString()));
|
||||
}
|
||||
|
||||
public static ListTag packOffsets(ShortList[] offsets) {
|
||||
ListTag tags = new ListTag();
|
||||
for (ShortList shorts : offsets) {
|
||||
ListTag listTag = new ListTag();
|
||||
if (shorts != null) {
|
||||
for (Short s : shorts) {
|
||||
listTag.add(ShortTag.valueOf(s));
|
||||
}
|
||||
}
|
||||
tags.add(listTag);
|
||||
}
|
||||
|
||||
return tags;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
package com.volmit.iris.core.nms.v1_20_R3.mca;
|
||||
|
||||
import com.volmit.iris.Iris;
|
||||
import com.volmit.iris.core.nms.BiomeBaseInjector;
|
||||
import com.volmit.iris.engine.data.chunk.TerrainChunk;
|
||||
import com.volmit.iris.util.data.IrisBlockData;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.world.level.LevelHeightAccessor;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.craftbukkit.v1_20_R3.block.CraftBiome;
|
||||
import org.bukkit.craftbukkit.v1_20_R3.block.data.CraftBlockData;
|
||||
import org.bukkit.generator.ChunkGenerator;
|
||||
import org.bukkit.material.MaterialData;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public record MCATerrainChunk(ChunkAccess chunk) implements TerrainChunk {
|
||||
|
||||
@Override
|
||||
public BiomeBaseInjector getBiomeBaseInjector() {
|
||||
return (x, y, z, biomeBase) -> chunk.setBiome(x, y, z, (Holder<net.minecraft.world.level.biome.Biome>) biomeBase);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Biome getBiome(int x, int z) {
|
||||
return Biome.THE_VOID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Biome getBiome(int x, int y, int z) {
|
||||
return Biome.THE_VOID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBiome(int x, int z, Biome bio) {
|
||||
setBiome(x, 0, z, bio);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBiome(int x, int y, int z, Biome bio) {
|
||||
chunk.setBiome(x & 15, y, z & 15, CraftBiome.bukkitToMinecraftHolder(bio));
|
||||
}
|
||||
|
||||
private LevelHeightAccessor height() {
|
||||
return chunk;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinHeight() {
|
||||
return height().getMinBuildHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxHeight() {
|
||||
return height().getMaxBuildHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlock(int x, int y, int z, BlockData blockData) {
|
||||
if (y > getMaxHeight() || y < getMinHeight()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (blockData == null) {
|
||||
Iris.error("NULL BD");
|
||||
}
|
||||
if (blockData instanceof IrisBlockData data)
|
||||
blockData = data.getBase();
|
||||
if (!(blockData instanceof CraftBlockData craftBlockData))
|
||||
throw new IllegalArgumentException("Expected CraftBlockData, got " + blockData.getClass().getSimpleName() + " instead");
|
||||
chunk.setBlockState(new BlockPos(x & 15, y, z & 15), craftBlockData.getState(), false);
|
||||
}
|
||||
|
||||
private BlockState getBlockState(int x, int y, int z) {
|
||||
if (y > getMaxHeight()) {
|
||||
y = getMaxHeight();
|
||||
}
|
||||
|
||||
if (y < getMinHeight()) {
|
||||
y = getMinHeight();
|
||||
}
|
||||
|
||||
return chunk.getBlockState(new BlockPos(x & 15, y, z & 15));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public org.bukkit.block.data.BlockData getBlockData(int x, int y, int z) {
|
||||
return CraftBlockData.fromData(getBlockState(x, y, z));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChunkGenerator.ChunkData getRaw() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRaw(ChunkGenerator.ChunkData data) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void inject(ChunkGenerator.BiomeGrid biome) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlock(int x, int y, int z, @NotNull Material material) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void setBlock(int x, int y, int z, @NotNull MaterialData material) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, @NotNull Material material) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, @NotNull MaterialData material) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRegion(int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, @NotNull BlockData blockData) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Material getType(int x, int y, int z) {
|
||||
return getBlockData(x, y, z).getMaterial();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public MaterialData getTypeAndData(int x, int y, int z) {
|
||||
return getBlockData(x, y, z).createBlockState().getData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getData(int x, int y, int z) {
|
||||
return getTypeAndData(x, y, z).getData();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package com.volmit.iris.core.nms.v1_20_R3.mca;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import javax.annotation.Nullable;
|
||||
import net.minecraft.FileUtil;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.NbtAccounter;
|
||||
import net.minecraft.nbt.NbtIo;
|
||||
import net.minecraft.nbt.StreamTagVisitor;
|
||||
import net.minecraft.util.ExceptionCollector;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.chunk.storage.RegionFile;
|
||||
|
||||
public final class RegionFileStorage implements AutoCloseable {
|
||||
public final Long2ObjectLinkedOpenHashMap<RegionFile> regionCache = new Long2ObjectLinkedOpenHashMap<>();
|
||||
private final Path folder;
|
||||
private final boolean sync;
|
||||
|
||||
public RegionFileStorage(Path folder, boolean sync) {
|
||||
this.folder = folder;
|
||||
this.sync = sync;
|
||||
}
|
||||
|
||||
public 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) {
|
||||
return regionFile;
|
||||
} else {
|
||||
if (this.regionCache.size() >= 256) {
|
||||
this.regionCache.removeLast().close();
|
||||
}
|
||||
|
||||
FileUtil.createDirectoriesSafe(this.folder);
|
||||
Path path = folder.resolve("r." + chunkPos.getRegionX() + "." + chunkPos.getRegionZ() + ".mca");
|
||||
if (existingOnly && !Files.exists(path)) {
|
||||
return null;
|
||||
} else {
|
||||
regionFile = new RegionFile(path, this.folder, this.sync);
|
||||
this.regionCache.putAndMoveToFirst(id, regionFile);
|
||||
return regionFile;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public CompoundTag read(ChunkPos chunkPos) throws IOException {
|
||||
RegionFile regionFile = this.getRegionFile(chunkPos, true);
|
||||
if (regionFile != null) {
|
||||
try (DataInputStream datainputstream = regionFile.getChunkDataInputStream(chunkPos)) {
|
||||
if (datainputstream != null) {
|
||||
return NbtIo.read(datainputstream);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void scanChunk(ChunkPos chunkPos, StreamTagVisitor visitor) throws IOException {
|
||||
RegionFile regionFile = this.getRegionFile(chunkPos, true);
|
||||
if (regionFile != null) {
|
||||
try (DataInputStream din = regionFile.getChunkDataInputStream(chunkPos)) {
|
||||
if (din != null) {
|
||||
NbtIo.parse(din, visitor, NbtAccounter.unlimitedHeap());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void write(ChunkPos chunkPos, @Nullable CompoundTag compound) throws IOException {
|
||||
RegionFile regionFile = this.getRegionFile(chunkPos, false);
|
||||
Preconditions.checkArgument(regionFile != null, "Failed to find region file for chunk %s", chunkPos);
|
||||
if (compound == null) {
|
||||
regionFile.clear(chunkPos);
|
||||
} else {
|
||||
try (DataOutputStream dos = regionFile.getChunkDataOutputStream(chunkPos)) {
|
||||
NbtIo.write(compound, dos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
ExceptionCollector<IOException> collector = new ExceptionCollector<>();
|
||||
|
||||
for (RegionFile regionFile : this.regionCache.values()) {
|
||||
try {
|
||||
regionFile.close();
|
||||
} catch (IOException e) {
|
||||
collector.add(e);
|
||||
}
|
||||
}
|
||||
|
||||
collector.throwIfPresent();
|
||||
}
|
||||
|
||||
public void flush() throws IOException {
|
||||
for (RegionFile regionfile : this.regionCache.values()) {
|
||||
regionfile.flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user