9
0
mirror of https://github.com/Xiao-MoMi/craft-engine.git synced 2025-12-30 12:29:15 +00:00

添加区块格式版本迁移

This commit is contained in:
XiaoMoMi
2025-04-06 20:07:46 +08:00
parent 7ba13b6a6a
commit db9480a650
7 changed files with 69 additions and 29 deletions

View File

@@ -1,7 +1,6 @@
package net.momirealms.craftengine.bukkit.plugin.network;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufInputStream;
import io.netty.buffer.Unpooled;
import it.unimi.dsi.fastutil.ints.IntList;
import net.kyori.adventure.text.Component;
@@ -30,9 +29,7 @@ import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.craftengine.core.world.chunk.Palette;
import net.momirealms.craftengine.core.world.chunk.PalettedContainer;
import net.momirealms.craftengine.core.world.chunk.packet.MCSection;
import net.momirealms.sparrow.nbt.NBT;
import net.momirealms.sparrow.nbt.Tag;
import net.momirealms.sparrow.nbt.TagTypes;
import net.momirealms.sparrow.nbt.serializer.NBTComponentSerializer;
import org.bukkit.*;
import org.bukkit.block.data.BlockData;

View File

@@ -232,6 +232,7 @@ public abstract class CraftEngine implements Plugin {
if (this.scheduler != null) this.scheduler.shutdownExecutor();
if (this.commandManager != null) this.commandManager.unregisterFeatures();
if (this.senderFactory != null) this.senderFactory.close();
if (this.dependencyManager != null) this.dependencyManager.close();
ResourcePackHost.instance().disable();
}

View File

@@ -6,7 +6,7 @@ import java.util.Set;
/**
* Loads and manages runtime dependencies for the plugin.
*/
public interface DependencyManager extends AutoCloseable {
public interface DependencyManager {
/**
* Loads dependencies.
@@ -23,6 +23,5 @@ public interface DependencyManager extends AutoCloseable {
*/
ClassLoader obtainClassLoaderWith(Set<Dependency> dependencies);
@Override
void close();
}

View File

@@ -11,7 +11,6 @@ import io.netty.util.ByteProcessor;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import net.momirealms.craftengine.core.world.BlockPos;
import net.momirealms.sparrow.nbt.CompoundTag;
import net.momirealms.sparrow.nbt.NBT;
import net.momirealms.sparrow.nbt.Tag;
import org.jetbrains.annotations.NotNull;

View File

@@ -1,7 +1,5 @@
package net.momirealms.craftengine.core.world.chunk.storage;
import com.github.luben.zstd.ZstdInputStream;
import com.github.luben.zstd.ZstdOutputStream;
import it.unimi.dsi.fastutil.io.FastBufferedInputStream;
import net.jpountz.lz4.LZ4BlockInputStream;
import net.jpountz.lz4.LZ4BlockOutputStream;

View File

@@ -15,10 +15,8 @@ import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;
public class DefaultRegionFileStorage implements WorldDataStorage {
private static final int FORMAT_VERSION = 1;
private final Path folder;
public static final String REGION_FILE_SUFFIX = ".mca";
@@ -30,15 +28,6 @@ public class DefaultRegionFileStorage implements WorldDataStorage {
public DefaultRegionFileStorage(Path directory) {
this.folder = directory;
Path metaDataFile = this.folder.getParent().resolve("craftengine.dat");
}
private Path[] regionFiles() throws IOException {
try (Stream<Path> paths = Files.walk(this.folder)) {
return paths
.filter(path -> path.toString().endsWith(REGION_FILE_SUFFIX))
.toArray(Path[]::new);
}
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
@@ -138,7 +127,7 @@ public class DefaultRegionFileStorage implements WorldDataStorage {
if (dataInputStream == null) {
return null;
}
tag = NBT.readCompound(dataInputStream, true);
tag = NBT.readCompound(dataInputStream, false);
} catch (Throwable t1) {
try {
dataInputStream.close();
@@ -163,7 +152,7 @@ public class DefaultRegionFileStorage implements WorldDataStorage {
} else {
DataOutputStream dataOutputStream = regionFile.getChunkDataOutputStream(pos);
try {
NBT.writeCompound(nbt, dataOutputStream, true);
NBT.writeCompound(nbt, dataOutputStream, false);
} catch (Throwable t1) {
if (dataOutputStream != null) {
try {

View File

@@ -3,6 +3,8 @@ package net.momirealms.craftengine.core.world.chunk.storage;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.logger.PluginLogger;
import net.momirealms.craftengine.core.world.ChunkPos;
import net.momirealms.sparrow.nbt.CompoundTag;
import net.momirealms.sparrow.nbt.NBT;
import javax.annotation.Nullable;
import java.io.*;
@@ -15,11 +17,13 @@ import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.time.Instant;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Function;
public class RegionFile implements AutoCloseable {
private static final PluginLogger LOGGER = CraftEngine.instance().logger();
private static final byte FORMAT_VERSION = 1;
public static final int SECTOR_BYTES = 4096;
public static final int CHUNK_HEADER_SIZE = 5;
public static final int EXTERNAL_STREAM_FLAG = 128;
@@ -42,6 +46,19 @@ public class RegionFile implements AutoCloseable {
public final ReentrantLock fileLock = new ReentrantLock(true);
public final Path regionFile;
private static final List<Function<DataInputStream, DataInputStream>> FORMAT_UPDATER = List.of(
// version 0 -> 1 (Use nameless compound tag)
(old) -> {
try {
CompoundTag tag = NBT.readCompound(new DataInputStream(old), true);
return new DataInputStream(new ByteArrayInputStream(NBT.toBytes(tag)));
} catch (IOException e) {
CraftEngine.instance().logger().warn("Failed to migrate data from version 0 -> 1", e);
return null;
}
}
);
public RegionFile(Path path, Path directory, CompressionMethod compressionMethod) throws IOException {
this.header = ByteBuffer.allocateDirect(8192);
this.regionFile = path;
@@ -120,7 +137,10 @@ public class RegionFile implements AutoCloseable {
// Read the chunk's stub information
int size = bytebuffer.getInt();
byte type = bytebuffer.get();
byte flags = bytebuffer.get();
byte compressionScheme = (byte) (flags & 0b00000111);
byte version = (byte) ((flags & 0b01111000) >>> 3);
if (size == 0) {
LOGGER.warn(String.format("Chunk %s is allocated, but stream is missing", pos));
return null;
@@ -128,13 +148,24 @@ public class RegionFile implements AutoCloseable {
// Calculate the actual data size
int actualSize = size - 1;
if (RegionFile.isExternalStreamChunk(type)) {
if (RegionFile.isExternalStreamChunk(flags)) {
// If the chunk has both internal and external streams, log a warning.
if (actualSize != 0) {
LOGGER.warn("Chunk has both internal and external streams");
}
// Create and return an input stream for the external chunk.
return this.createExternalChunkInputStream(pos, RegionFile.getExternalChunkVersion(type));
if (version == FORMAT_VERSION) {
return this.createExternalChunkInputStream(pos, RegionFile.getExternalChunkVersion(compressionScheme));
} else {
int currentVersion = version;
DataInputStream inputStream = this.createExternalChunkInputStream(pos, RegionFile.getExternalChunkVersion(compressionScheme));
while (currentVersion < FORMAT_VERSION) {
inputStream = FORMAT_UPDATER.get(currentVersion).apply(inputStream);
if (inputStream == null) break;
currentVersion++;
}
return inputStream;
}
} else if (actualSize > bytebuffer.remaining()) {
// If the declared size of the chunk is greater than the remaining bytes in the buffer, the stream is truncated.
LOGGER.severe(String.format("Chunk %s stream is truncated: expected %s but read %s", pos, actualSize, bytebuffer.remaining()));
@@ -144,11 +175,36 @@ public class RegionFile implements AutoCloseable {
LOGGER.severe(String.format("Declared size %s of chunk %s is negative", size, pos));
return null;
} else {
// Otherwise, create and return a standard input stream for the chunk data.
return this.createChunkInputStream(pos, type, RegionFile.createInputStream(bytebuffer, actualSize));
if (version == FORMAT_VERSION) {
// Otherwise, create and return a standard input stream for the chunk data.
return this.createChunkInputStream(pos, compressionScheme, RegionFile.createInputStream(bytebuffer, actualSize));
} else {
int currentVersion = version;
DataInputStream inputStream = this.createChunkInputStream(pos, compressionScheme, RegionFile.createInputStream(bytebuffer, actualSize));
while (currentVersion < FORMAT_VERSION) {
inputStream = FORMAT_UPDATER.get(currentVersion).apply(inputStream);
if (inputStream == null) break;
currentVersion++;
}
return inputStream;
}
}
}
public static byte encodeFlag(byte compressionScheme, byte version, boolean external) {
if (compressionScheme <= 0 || compressionScheme > 7) {
throw new IllegalArgumentException("compression method can only be a number between 1 and 7");
}
if (version < 0 || version > 15) {
throw new IllegalArgumentException("Version number can only be a number between 0 and 15");
}
return (byte) (
(external ? 0b10000000 : 0) |
((version & 0b00001111) << 3) |
(compressionScheme & 0b00000111)
);
}
private static int getTimestamp() {
return (int) (Instant.now().toEpochMilli() / 1000L);
}
@@ -307,7 +363,8 @@ public class RegionFile implements AutoCloseable {
private ByteBuffer createExternalHeader(CompressionMethod compression) {
ByteBuffer bytebuffer = ByteBuffer.allocate(CHUNK_HEADER_SIZE);
bytebuffer.putInt(1);
bytebuffer.put((byte) (compression.getId() | EXTERNAL_STREAM_FLAG));
bytebuffer.put(encodeFlag((byte) compression.getId(), FORMAT_VERSION, true));
//bytebuffer.put((byte) (compression.getId() | EXTERNAL_STREAM_FLAG));
((Buffer) bytebuffer).flip();
return bytebuffer;
}
@@ -402,7 +459,7 @@ public class RegionFile implements AutoCloseable {
super.write(0);
super.write(0);
// compression method
super.write(RegionFile.this.compression.getId());
super.write(encodeFlag((byte) RegionFile.this.compression.getId(), FORMAT_VERSION, false));
this.pos = pos;
}