Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ private void onConstruct(ServerLevel worldIn,
this.cubeQueueSorter.createExecutor(delegatedtaskexecutor1, false));

try {
regionCubeIO = new RegionCubeIO(storageFolder, "chunk", "cube");
regionCubeIO = new RegionCubeIO(storageFolder, p_i51538_3_, "chunk", "cube");
} catch (IOException e) {
throw new UncheckedIOException(e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public abstract class MixinEntityStorage implements CubicEntityStorage {
@Inject(method = "<init>", at = @At("RETURN"))
private void setupCubeIO(ServerLevel serverLevel, File file, DataFixer dataFixer, boolean bl, Executor executor, CallbackInfo ci) throws IOException {
if (((CubicLevelHeightAccessor) serverLevel).isCubic()) {
cubeWorker = new RegionCubeIO(file, file.getName(), file.getName());
cubeWorker = new RegionCubeIO(file, dataFixer, file.getName(), file.getName());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ private void getServerLevel(File file, Function<Runnable, Codec<R>> function, Fu
LevelHeightAccessor heightAccessor, CallbackInfo ci) throws IOException {

if (((CubicLevelHeightAccessor) levelHeightAccessor).isCubic()) {
cubeWorker = new RegionCubeIO(file, file.getName() + "-chunk", file.getName());
cubeWorker = new RegionCubeIO(file, dataFixer, file.getName() + "-chunk", file.getName());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@

import static net.minecraft.nbt.NbtIo.writeCompressed;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.Iterator;
import java.util.Map;
Expand All @@ -15,23 +18,37 @@
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.zip.GZIPInputStream;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import com.google.common.collect.Maps;
import com.mojang.datafixers.DataFixer;
import com.mojang.datafixers.util.Either;
import cubicchunks.regionlib.impl.EntryLocation2D;
import cubicchunks.regionlib.impl.EntryLocation3D;
import cubicchunks.regionlib.impl.SaveCubeColumns;
import io.github.opencubicchunks.cubicchunks.CubicChunks;
import io.github.opencubicchunks.cubicchunks.chunk.util.CubePos;
import io.github.opencubicchunks.cubicchunks.utils.Coords;
import net.minecraft.SharedConstants;
import net.minecraft.Util;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.NbtAccounter;
import net.minecraft.nbt.NbtIo;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.nbt.Tag;
import net.minecraft.resources.ResourceKey;
import net.minecraft.util.datafix.DataFixTypes;
import net.minecraft.util.thread.ProcessorMailbox;
import net.minecraft.util.thread.StrictQueue;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.chunk.ChunkStatus;
import net.minecraft.world.level.chunk.storage.ChunkStorage;
import net.minecraft.world.level.storage.DimensionDataStorage;
import org.apache.logging.log4j.Logger;

public class RegionCubeIO {
Expand All @@ -46,13 +63,15 @@ public class RegionCubeIO {
private final Map<ChunkPos, SaveEntry> pendingChunkWrites = Maps.newLinkedHashMap();
private final Map<CubePos, SaveEntry> pendingCubeWrites = Maps.newLinkedHashMap();

private final DataFixer fixerUpper;
private final ProcessorMailbox<StrictQueue.IntRunnable> chunkExecutor;
private final ProcessorMailbox<StrictQueue.IntRunnable> cubeExecutor;

private final AtomicBoolean shutdownRequested = new AtomicBoolean();

public RegionCubeIO(File storageFolder, @Nullable String chunkWorkerName, String cubeWorkerName) throws IOException {
public RegionCubeIO(File storageFolder, DataFixer dataFixer, @Nullable String chunkWorkerName, String cubeWorkerName) throws IOException {
this.storageFolder = storageFolder;
this.fixerUpper = dataFixer;


this.chunkExecutor = new ProcessorMailbox<>(new StrictQueue.FixedPriorityQueue(Priority.values().length), Util.ioPool(), "RegionCubeIO-" + chunkWorkerName);
Expand Down Expand Up @@ -118,6 +137,22 @@ public CompletableFuture<Void> saveCubeNBT(CubePos cubePos, CompoundTag cubeNBT)
return Either.left(null);
}

boolean isOldData = false;
try (GZIPInputStream in = new GZIPInputStream(new ByteArrayInputStream(buf.get().array()))) {
// NBT can't begin with byte 255, so this acts as a marker for
// 1.12.2 "proto-big-cube" converted data
if (in.read() == 255) {
// a second byte with value 0 for potential future extension
if (in.read() != 0) {
throw new RuntimeException("Invalid data, expected 0");
}
isOldData = true;
}
}
if (isOldData) {
return Either.left(loadOldNbt(cubePos, buf.get().array()));
}

CompoundTag compoundnbt = NbtIo.readCompressed(new ByteArrayInputStream(buf.get().array()));
return Either.left(compoundnbt);
} catch (Exception exception) {
Expand All @@ -138,6 +173,121 @@ public CompletableFuture<Void> saveCubeNBT(CubePos cubePos, CompoundTag cubeNBT)
}
}

@Nullable
private CompoundTag loadOldNbt(CubePos pos, byte[] data) throws IOException {
ByteArrayInputStream in = new ByteArrayInputStream(data);
CompoundTag[] cubeTags = new CompoundTag[8];
try (InputStream gzip = new BufferedInputStream(new GZIPInputStream(in))) {
// skip byte 255 and byte 0
gzip.read();
gzip.read();
for (int i = 0; i < 8; i++) {
cubeTags[i] = NbtIo.read(new DataInputStream(gzip), NbtAccounter.UNLIMITED);
}
}

return mergeOldNbt(pos, cubeTags);
}

@Nullable
private CompoundTag mergeOldNbt(CubePos pos, CompoundTag[] cubeTags) {
CompoundTag outTag = new CompoundTag();
for (int i = 0; i < cubeTags.length; i++) {
CompoundTag level = cubeTags[i].getCompound("Level");
level.put("TerrainPopulated", level.get("populated"));
level.put("LightPopulated", level.get("initLightDone"));
ListTag tileTicks = level.getList("TileTicks", CompoundTag.TAG_COMPOUND);

// prepare tile ticks to that high bits of "y" coordinate are actually cube section index
for (Tag tileTick : tileTicks) {
CompoundTag tick = (CompoundTag) tileTick;
int x = tick.getInt("x");
int y = tick.getInt("y");
int z = tick.getInt("z");
int idx = Coords.blockToIndex(x, y, z);
tick.putInt("y", y & 0xF | idx << 4);
}

int version = ChunkStorage.getVersion(cubeTags[i]);

cubeTags[i] = NbtUtils.update(this.fixerUpper, DataFixTypes.CHUNK, cubeTags[i], version, 1493);
//if (nbt.getCompound("Level").getBoolean("hasLegacyStructureData")) {
// if (this.legacyStructureHandler == null) {
// this.legacyStructureHandler = LegacyStructureDataHandler.getLegacyStructureHandler(worldKey, (DimensionDataStorage)persistentStateManagerFactory.get());
// }
// nbt = this.legacyStructureHandler.updateFromLegacy(nbt);
//}
cubeTags[i] = NbtUtils.update(this.fixerUpper, DataFixTypes.CHUNK, cubeTags[i], Math.max(1493, version));
if (cubeTags[i] == null) {
LOGGER.warn("Dropping incomplete cube at " + pos);
return null;
}
}
CompoundTag levelOut = new CompoundTag();
levelOut.putInt("xPos", pos.getX());
levelOut.putInt("yPos", pos.getY());
levelOut.putInt("zPos", pos.getZ());

outTag.put("ToBeTicked", new ListTag());
outTag.put("Level", levelOut);
// TODO: biomes
outTag.putIntArray("Biomes", new int[8 * 8 * 8]);
for (int i = 0; i < cubeTags.length; i++) {
CompoundTag cube = cubeTags[i];
CompoundTag level = cube.getCompound("Level");
ListTag toBeTicked = cube.getList("ToBeTicked", Tag.TAG_LIST);
ListTag outToBeTicked = outTag.getList("ToBeTicked", Tag.TAG_LIST);
for (int i1 = 0; i1 < toBeTicked.size(); i1++) {
ListTag toTickEntry = toBeTicked.getList(i1);
ListTag toTickEntryOut = outToBeTicked.getList(i1);
toTickEntryOut.addAll(toTickEntry);
outToBeTicked.set(i1, toTickEntryOut);
}

level.putLong("LastUpdate", Math.max(level.getLong("LastUpdate"), levelOut.getLong("LastUpdate")));
level.putLong("InhabitedTime", Math.max(level.getLong("InhabitedTime"), levelOut.getLong("InhabitedTime")));

ChunkStatus status = ChunkStatus.byName(level.getString("Status"));
ChunkStatus oldStatus = levelOut.contains("Status") ? ChunkStatus.byName(level.getString("Status")) : null;
ChunkStatus newStatus = oldStatus == null ? status : oldStatus.isOrAfter(ChunkStatus.SPAWN) ? status : ChunkStatus.EMPTY;
levelOut.putString("Status", newStatus.getName());

ListTag sections = levelOut.getList("Sections", Tag.TAG_COMPOUND);
levelOut.put("Sections", sections);
CompoundTag section = level.getList("Sections", Tag.TAG_COMPOUND).getCompound(0);
section.putShort("i", (short) i);
sections.add(section);

levelOut.putBoolean("isLightOn", true);

ListTag tileEntities = levelOut.getList("TileEntities", Tag.TAG_COMPOUND);
levelOut.put("TileEntities", tileEntities);
ListTag tileEntitiesOld = level.getList("TileEntities", Tag.TAG_COMPOUND);
tileEntities.addAll(tileEntitiesOld);

ListTag entities = levelOut.getList("Entities", Tag.TAG_COMPOUND);
levelOut.put("Entities", entities);
ListTag entitiesOld = level.getList("Entities", Tag.TAG_COMPOUND);
entities.addAll(entitiesOld);

}
return outTag;
}

public CompoundTag upgradeChunkTag(ResourceKey<Level> worldKey, Supplier<DimensionDataStorage> persistentStateManagerFactory, CompoundTag nbt) {
int i = ChunkStorage.getVersion(nbt);
if (i < 1493) {
throw new IllegalArgumentException("Pre-1.17 version handled elsewhere, but trying " + i);
}

nbt = NbtUtils.update(this.fixerUpper, DataFixTypes.CHUNK, nbt, Math.max(1493, i));
if (i < SharedConstants.getCurrentVersion().getWorldVersion()) {
nbt.putInt("DataVersion", SharedConstants.getCurrentVersion().getWorldVersion());
}

return nbt;
}

public CompletableFuture<Void> saveChunkNBT(ChunkPos chunkPos, CompoundTag cubeNBT) {
return this.submitChunkTask(() -> {
SaveEntry entry = this.pendingChunkWrites.computeIfAbsent(chunkPos, (pos) -> new SaveEntry(cubeNBT));
Expand Down