implement Headless for 1.20.4

This commit is contained in:
CrazyDev22
2024-05-17 16:01:21 +02:00
parent 4576b95814
commit e1a0481cdf
12 changed files with 914 additions and 19 deletions

View File

@@ -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);
}

View 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);
}

View File

@@ -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");
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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());

View File

@@ -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();
}
}

View File

@@ -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());
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}
}