mirror of
https://github.com/Xiao-MoMi/craft-engine.git
synced 2025-12-30 12:29:15 +00:00
添加区块格式版本迁移
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user