mirror of
https://github.com/plexusorg/Plex-FAWE.git
synced 2024-12-22 17:27:38 +00:00
Allow NBT stored in DiskOptimizedClipboards to be written to disk as a compressed byte array at the end of the file (#1745)
* Allow NBT stored in DiskOptimizedClipboards to be written to disk as a compressed byte array at the end of the file * Add some deprecations/javadocs and provide the expected clipboard version on error * Javadoc since tags and add location of clipboard folder to error * Refactor load-from-file method into DOC class * Refactor nbt loading code into separate method in DOC
This commit is contained in:
parent
af234b284b
commit
0b33fa8757
@ -721,6 +721,11 @@ public class Settings extends Config {
|
||||
"If a player's clipboard should be deleted upon logout"
|
||||
})
|
||||
public boolean DELETE_ON_LOGOUT = false;
|
||||
@Comment({
|
||||
"Allows NBT stored in a clipboard to be written to disk",
|
||||
" - Requires clipboard.use-disk to be enabled"
|
||||
})
|
||||
public boolean SAVE_CLIPBOARD_NBT_TO_DISK = true;
|
||||
|
||||
}
|
||||
|
||||
|
@ -3,19 +3,23 @@ package com.fastasyncworldedit.core.extent.clipboard;
|
||||
import com.fastasyncworldedit.core.Fawe;
|
||||
import com.fastasyncworldedit.core.configuration.Settings;
|
||||
import com.fastasyncworldedit.core.internal.exception.FaweClipboardVersionMismatchException;
|
||||
import com.fastasyncworldedit.core.internal.io.ByteBufferInputStream;
|
||||
import com.fastasyncworldedit.core.jnbt.streamer.IntValueReader;
|
||||
import com.fastasyncworldedit.core.math.IntTriple;
|
||||
import com.fastasyncworldedit.core.util.MainUtil;
|
||||
import com.fastasyncworldedit.core.util.ReflectionUtils;
|
||||
import com.sk89q.jnbt.CompoundTag;
|
||||
import com.sk89q.jnbt.DoubleTag;
|
||||
import com.sk89q.jnbt.IntTag;
|
||||
import com.sk89q.jnbt.ListTag;
|
||||
import com.sk89q.jnbt.NBTInputStream;
|
||||
import com.sk89q.jnbt.NBTOutputStream;
|
||||
import com.sk89q.jnbt.Tag;
|
||||
import com.sk89q.worldedit.entity.BaseEntity;
|
||||
import com.sk89q.worldedit.entity.Entity;
|
||||
import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard;
|
||||
import com.sk89q.worldedit.internal.util.LogManagerCompat;
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
import com.sk89q.worldedit.regions.CuboidRegion;
|
||||
import com.sk89q.worldedit.regions.Region;
|
||||
import com.sk89q.worldedit.util.Location;
|
||||
import com.sk89q.worldedit.world.biome.BiomeType;
|
||||
@ -27,6 +31,7 @@ import com.sk89q.worldedit.world.block.BlockTypes;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
@ -52,21 +57,27 @@ import java.util.stream.Collectors;
|
||||
*/
|
||||
public class DiskOptimizedClipboard extends LinearClipboard {
|
||||
|
||||
public static final int VERSION = 2;
|
||||
private static final Logger LOGGER = LogManagerCompat.getLogger();
|
||||
|
||||
private static final int VERSION = 1;
|
||||
private static final int HEADER_SIZE = 22;
|
||||
private static final int HEADER_SIZE = 27; // Current header size
|
||||
private static final int VERSION_1_HEADER_SIZE = 22; // Header size of "version 1"
|
||||
private static final int VERSION_2_HEADER_SIZE = 27; // Header size of "version 2" i.e. when NBT/entities could be saved
|
||||
|
||||
private final HashMap<IntTriple, CompoundTag> nbtMap;
|
||||
private final File file;
|
||||
private final int headerSize;
|
||||
|
||||
private RandomAccessFile braf;
|
||||
private MappedByteBuffer byteBuffer;
|
||||
|
||||
private FileChannel fileChannel;
|
||||
private boolean hasBiomes;
|
||||
private boolean hasBiomes = false;
|
||||
private boolean canHaveBiomes = true;
|
||||
private int nbtBytesRemaining;
|
||||
|
||||
/**
|
||||
* Creates a new DiskOptimizedClipboard for the given region. Creates or overwrites a file using the given UUID as a name.
|
||||
*/
|
||||
public DiskOptimizedClipboard(Region region, UUID uuid) {
|
||||
this(
|
||||
region.getDimensions(),
|
||||
@ -79,6 +90,14 @@ public class DiskOptimizedClipboard extends LinearClipboard {
|
||||
setOrigin(region.getMinimumPoint());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new DiskOptimizedClipboard with the given dimensions. Creates a new file with a random UUID name.
|
||||
*
|
||||
* @deprecated Use {@link DiskOptimizedClipboard#DiskOptimizedClipboard(Region, UUID)} or
|
||||
* {@link DiskOptimizedClipboard#DiskOptimizedClipboard(BlockVector3, File)} to avoid creating a large number of clipboard
|
||||
* files that won't be cleaned until `clipboard.delete-after-days` and a server restart.
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "TODO")
|
||||
public DiskOptimizedClipboard(BlockVector3 dimensions) {
|
||||
this(
|
||||
dimensions,
|
||||
@ -89,12 +108,17 @@ public class DiskOptimizedClipboard extends LinearClipboard {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* New DiskOptimizedClipboard. If the file specified exists, then it will be completely overwritten. To load an existing
|
||||
* clipboard, use {@link DiskOptimizedClipboard#DiskOptimizedClipboard(File)}.
|
||||
*/
|
||||
public DiskOptimizedClipboard(BlockVector3 dimensions, File file) {
|
||||
super(dimensions, BlockVector3.ZERO);
|
||||
if (HEADER_SIZE + ((long) getVolume() << 1) >= Integer.MAX_VALUE) {
|
||||
headerSize = HEADER_SIZE;
|
||||
if (headerSize + ((long) getVolume() << 1) >= Integer.MAX_VALUE) {
|
||||
throw new IllegalArgumentException(
|
||||
"Dimensions too large for this clipboard format. Use //lazycopy for large selections.");
|
||||
} else if (HEADER_SIZE + ((long) getVolume() << 1) + (long) ((getHeight() >> 2) + 1) * ((getLength() >> 2) + 1) * ((getWidth() >> 2) + 1) >= Integer.MAX_VALUE) {
|
||||
} else if (headerSize + ((long) getVolume() << 1) + (long) ((getHeight() >> 2) + 1) * ((getLength() >> 2) + 1) * ((getWidth() >> 2) + 1) >= Integer.MAX_VALUE) {
|
||||
LOGGER.error("Dimensions are too large for biomes to be stored in a DiskOptimizedClipboard");
|
||||
canHaveBiomes = false;
|
||||
}
|
||||
@ -113,9 +137,10 @@ public class DiskOptimizedClipboard extends LinearClipboard {
|
||||
e.printStackTrace();
|
||||
}
|
||||
this.braf = new RandomAccessFile(file, "rw");
|
||||
long fileLength = (long) getVolume() * 2L + (long) HEADER_SIZE;
|
||||
long fileLength = (long) getVolume() * 2L + (long) headerSize;
|
||||
braf.setLength(0);
|
||||
braf.setLength(fileLength);
|
||||
this.nbtBytesRemaining = Integer.MAX_VALUE - (int) fileLength;
|
||||
init();
|
||||
// write getLength() etc
|
||||
byteBuffer.putChar(2, (char) (VERSION));
|
||||
@ -127,15 +152,40 @@ public class DiskOptimizedClipboard extends LinearClipboard {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load an existing file as a DiskOptimizedClipboard. The file MUST exist and MUST be created as a DiskOptimizedClipboard
|
||||
* with data written to it.
|
||||
*/
|
||||
public DiskOptimizedClipboard(File file) {
|
||||
super(readSize(file), BlockVector3.ZERO);
|
||||
this(file, VERSION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load an existing file as a DiskOptimizedClipboard. The file MUST exist and MUST be created as a DiskOptimizedClipboard
|
||||
* with data written to it.
|
||||
*
|
||||
* @param file File to read from
|
||||
* @param versionOverride An override version to allow loading of older clipboards if required
|
||||
*/
|
||||
public DiskOptimizedClipboard(File file, int versionOverride) {
|
||||
super(readSize(file, versionOverride), BlockVector3.ZERO);
|
||||
headerSize = getHeaderSizeOverrideFromVersion(versionOverride);
|
||||
nbtMap = new HashMap<>();
|
||||
try {
|
||||
this.file = file;
|
||||
this.braf = new RandomAccessFile(file, "rw");
|
||||
braf.setLength(file.length());
|
||||
this.nbtBytesRemaining = Integer.MAX_VALUE - (int) file.length();
|
||||
init();
|
||||
if (braf.length() - HEADER_SIZE == ((long) getVolume() << 1) + (long) ((getHeight() >> 2) + 1) * ((getLength() >> 2) + 1) * ((getWidth() >> 2) + 1)) {
|
||||
long biomeLength = (long) ((getHeight() >> 2) + 1) * ((getLength() >> 2) + 1) * ((getWidth() >> 2) + 1);
|
||||
if (headerSize >= VERSION_2_HEADER_SIZE) {
|
||||
readBiomeStatusFromHeader();
|
||||
int nbtCount = readNBTSavedCountFromHeader();
|
||||
int entitiesCount = readEntitiesSavedCountFromHeader();
|
||||
if (Settings.settings().CLIPBOARD.SAVE_CLIPBOARD_NBT_TO_DISK && nbtCount + entitiesCount > 0) {
|
||||
loadNBTFromFileFooter(nbtCount, entitiesCount, biomeLength);
|
||||
}
|
||||
} else if (braf.length() - headerSize == ((long) getVolume() << 1) + biomeLength) {
|
||||
hasBiomes = true;
|
||||
}
|
||||
getAndSetOffsetAndOrigin();
|
||||
@ -144,12 +194,30 @@ public class DiskOptimizedClipboard extends LinearClipboard {
|
||||
}
|
||||
}
|
||||
|
||||
private static BlockVector3 readSize(File file) {
|
||||
/**
|
||||
* Attempt to load a file into a new {@link DiskOptimizedClipboard} instance. Will attempt to recover on version mismatch
|
||||
* failure.
|
||||
*
|
||||
* @param file File to load
|
||||
* @return new {@link DiskOptimizedClipboard} instance.
|
||||
*/
|
||||
public static DiskOptimizedClipboard loadFromFile(final File file) {
|
||||
DiskOptimizedClipboard doc;
|
||||
try {
|
||||
doc = new DiskOptimizedClipboard(file);
|
||||
} catch (FaweClipboardVersionMismatchException e) { // Attempt to recover
|
||||
int version = e.getClipboardVersion();
|
||||
doc = new DiskOptimizedClipboard(file, version);
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
|
||||
private static BlockVector3 readSize(File file, int expectedVersion) {
|
||||
try (DataInputStream is = new DataInputStream(new FileInputStream(file))) {
|
||||
is.skipBytes(2);
|
||||
int version = is.readChar();
|
||||
if (version != VERSION) {
|
||||
throw new FaweClipboardVersionMismatchException();
|
||||
if (version != expectedVersion) {
|
||||
throw new FaweClipboardVersionMismatchException(expectedVersion, version);
|
||||
}
|
||||
return BlockVector3.at(is.readChar(), is.readChar(), is.readChar());
|
||||
} catch (IOException e) {
|
||||
@ -158,6 +226,57 @@ public class DiskOptimizedClipboard extends LinearClipboard {
|
||||
}
|
||||
}
|
||||
|
||||
private void loadNBTFromFileFooter(int nbtCount, int entitiesCount, long biomeLength) throws IOException {
|
||||
int biomeBlocksLength = headerSize + (getVolume() << 1) + (hasBiomes ? (int) biomeLength : 0);
|
||||
MappedByteBuffer tmp = fileChannel.map(FileChannel.MapMode.READ_ONLY, biomeBlocksLength, braf.length());
|
||||
try (NBTInputStream nbtIS = new NBTInputStream(MainUtil.getCompressedIS(new ByteBufferInputStream(tmp)))) {
|
||||
Iterator<CompoundTag> iter = nbtIS.toIterator();
|
||||
while (nbtCount > 0 && iter.hasNext()) { // TileEntities are stored "before" entities
|
||||
CompoundTag tag = iter.next();
|
||||
int x = tag.getInt("x");
|
||||
int y = tag.getInt("y");
|
||||
int z = tag.getInt("z");
|
||||
IntTriple pos = new IntTriple(x, y, z);
|
||||
nbtMap.put(pos, tag);
|
||||
nbtCount--;
|
||||
}
|
||||
while (entitiesCount > 0 && iter.hasNext()) {
|
||||
CompoundTag tag = iter.next();
|
||||
Tag posTag = tag.getValue().get("Pos");
|
||||
if (posTag == null) {
|
||||
LOGGER.warn("Missing pos tag: {}", tag);
|
||||
return;
|
||||
}
|
||||
List<DoubleTag> pos = (List<DoubleTag>) posTag.getValue();
|
||||
double x = pos.get(0).getValue();
|
||||
double y = pos.get(1).getValue();
|
||||
double z = pos.get(2).getValue();
|
||||
BaseEntity entity = new BaseEntity(tag);
|
||||
BlockArrayClipboard.ClipboardEntity clipboardEntity = new BlockArrayClipboard.ClipboardEntity(
|
||||
this,
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
0f,
|
||||
0f,
|
||||
entity
|
||||
);
|
||||
this.entities.add(clipboardEntity);
|
||||
entitiesCount--;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private int getHeaderSizeOverrideFromVersion(int versionOverride) {
|
||||
return switch (versionOverride) {
|
||||
case 1 -> VERSION_1_HEADER_SIZE;
|
||||
case 2 -> VERSION_2_HEADER_SIZE;
|
||||
default -> HEADER_SIZE;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getURI() {
|
||||
return file.toURI();
|
||||
@ -185,8 +304,11 @@ public class DiskOptimizedClipboard extends LinearClipboard {
|
||||
close();
|
||||
this.braf = new RandomAccessFile(file, "rw");
|
||||
// Since biomes represent a 4x4x4 cube, we store fewer biome bytes that volume at 1 byte per biome
|
||||
// +1 to each too allow for cubes that lie across the region boundary
|
||||
this.braf.setLength(HEADER_SIZE + ((long) getVolume() << 1) + (long) ((getHeight() >> 2) + 1) * ((getLength() >> 2) + 1) * ((getWidth() >> 2) + 1));
|
||||
// +1 to each to allow for cubes that lie across the region boundary
|
||||
long length =
|
||||
headerSize + ((long) getVolume() << 1) + (long) ((getHeight() >> 2) + 1) * ((getLength() >> 2) + 1) * ((getWidth() >> 2) + 1);
|
||||
this.braf.setLength(length);
|
||||
this.nbtBytesRemaining = Integer.MAX_VALUE - (int) length;
|
||||
init();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
@ -216,7 +338,7 @@ public class DiskOptimizedClipboard extends LinearClipboard {
|
||||
public void setBiome(int index, BiomeType biome) {
|
||||
if (initBiome()) {
|
||||
try {
|
||||
byteBuffer.put(HEADER_SIZE + (getVolume() << 1) + index, (byte) biome.getInternalId());
|
||||
byteBuffer.put(headerSize + (getVolume() << 1) + index, (byte) biome.getInternalId());
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
LOGGER.info((long) (getHeight() >> 2) * (getLength() >> 2) * (getWidth() >> 2));
|
||||
LOGGER.info(index);
|
||||
@ -230,7 +352,7 @@ public class DiskOptimizedClipboard extends LinearClipboard {
|
||||
if (!hasBiomes()) {
|
||||
return null;
|
||||
}
|
||||
int biomeId = byteBuffer.get(HEADER_SIZE + (getVolume() << 1) + index) & 0xFF;
|
||||
int biomeId = byteBuffer.get(headerSize + (getVolume() << 1) + index) & 0xFF;
|
||||
return BiomeTypes.get(biomeId);
|
||||
}
|
||||
|
||||
@ -239,7 +361,7 @@ public class DiskOptimizedClipboard extends LinearClipboard {
|
||||
if (!hasBiomes()) {
|
||||
return;
|
||||
}
|
||||
int mbbIndex = HEADER_SIZE + (getVolume() << 1);
|
||||
int mbbIndex = headerSize + (getVolume() << 1);
|
||||
try {
|
||||
for (int y = 0; y < getHeight(); y++) {
|
||||
for (int z = 0; z < getLength(); z++) {
|
||||
@ -325,11 +447,45 @@ public class DiskOptimizedClipboard extends LinearClipboard {
|
||||
ReflectionUtils.getUnsafe().invokeCleaner(cb);
|
||||
}
|
||||
|
||||
private void writeBiomeStatusToHeader() {
|
||||
byteBuffer.put(22, (byte) (hasBiomes ? 1 : 0));
|
||||
}
|
||||
|
||||
private void writeNBTSavedCountToHeader(int count) {
|
||||
byteBuffer.putChar(23, (char) count);
|
||||
}
|
||||
|
||||
private void writeEntitiesSavedCountToHeader(int count) {
|
||||
byteBuffer.putChar(25, (char) count);
|
||||
}
|
||||
|
||||
private boolean readBiomeStatusFromHeader() {
|
||||
return this.hasBiomes = byteBuffer.get(22) == 1;
|
||||
}
|
||||
|
||||
private int readNBTSavedCountFromHeader() {
|
||||
return byteBuffer.getChar(23);
|
||||
}
|
||||
|
||||
private int readEntitiesSavedCountFromHeader() {
|
||||
return byteBuffer.getChar(25);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
public void close() {
|
||||
try {
|
||||
if (byteBuffer != null) {
|
||||
if (headerSize >= VERSION_2_HEADER_SIZE) {
|
||||
if (Settings.settings().CLIPBOARD.SAVE_CLIPBOARD_NBT_TO_DISK) {
|
||||
try {
|
||||
writeNBTToDisk();
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Unable to save NBT data to disk.", e);
|
||||
}
|
||||
}
|
||||
writeBiomeStatusToHeader();
|
||||
}
|
||||
byteBuffer.force();
|
||||
fileChannel.close();
|
||||
braf.close();
|
||||
@ -344,6 +500,94 @@ public class DiskOptimizedClipboard extends LinearClipboard {
|
||||
}
|
||||
}
|
||||
|
||||
private void writeNBTToDisk() throws IOException {
|
||||
if (!nbtMap.isEmpty() || !entities.isEmpty()) {
|
||||
byte[] output = null;
|
||||
boolean entitiesFit = false;
|
||||
// Closing a BAOS does nothing
|
||||
ByteArrayOutputStream baOS = new ByteArrayOutputStream();
|
||||
try (NBTOutputStream nbtOS = new NBTOutputStream(MainUtil.getCompressedOS(
|
||||
baOS,
|
||||
Settings.settings().CLIPBOARD.COMPRESSION_LEVEL
|
||||
))) {
|
||||
if (!nbtMap.isEmpty()) {
|
||||
try {
|
||||
for (CompoundTag tag : nbtMap.values()) {
|
||||
nbtOS.writeTag(tag);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
nbtOS.flush();
|
||||
if (baOS.size() > nbtBytesRemaining) {
|
||||
LOGGER.warn(
|
||||
"Clipboard file {} does not have enough remaining space to store NBT data on disk.",
|
||||
file.getName()
|
||||
);
|
||||
writeNBTSavedCountToHeader(0);
|
||||
writeEntitiesSavedCountToHeader(0);
|
||||
return;
|
||||
} else {
|
||||
writeNBTSavedCountToHeader(nbtMap.size());
|
||||
nbtBytesRemaining -= baOS.size();
|
||||
}
|
||||
output = baOS.toByteArray(); //Keep this in case entities are unable to fit.
|
||||
}
|
||||
|
||||
if (!entities.isEmpty()) {
|
||||
try {
|
||||
for (BlockArrayClipboard.ClipboardEntity entity : entities) {
|
||||
if (entity.getState() != null && entity.getState().getNbtData() != null) {
|
||||
CompoundTag data = entity.getState().getNbtData();
|
||||
HashMap<String, Tag> value = new HashMap<>(data.getValue());
|
||||
List<DoubleTag> pos = new ArrayList<>(3);
|
||||
pos.add(new DoubleTag(entity.getLocation().getX()));
|
||||
pos.add(new DoubleTag(entity.getLocation().getX()));
|
||||
pos.add(new DoubleTag(entity.getLocation().getX()));
|
||||
value.put("Pos", new ListTag(DoubleTag.class, pos));
|
||||
nbtOS.writeTag(new CompoundTag(value));
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (baOS.size() > nbtBytesRemaining) {
|
||||
LOGGER.warn(
|
||||
"Clipboard file {} does not have enough remaining space to store entity data on disk.",
|
||||
file.getName()
|
||||
);
|
||||
writeEntitiesSavedCountToHeader(0);
|
||||
} else {
|
||||
entitiesFit = true;
|
||||
writeEntitiesSavedCountToHeader(entities.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (output == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (entitiesFit) {
|
||||
output = baOS.toByteArray();
|
||||
}
|
||||
|
||||
long currentLength = this.braf.length();
|
||||
this.braf.setLength(currentLength + baOS.size());
|
||||
MappedByteBuffer tempBuffer = fileChannel.map(
|
||||
FileChannel.MapMode.READ_WRITE,
|
||||
currentLength,
|
||||
baOS.size()
|
||||
);
|
||||
tempBuffer.put(output);
|
||||
tempBuffer.force();
|
||||
closeDirectBuffer(tempBuffer);
|
||||
} else {
|
||||
writeNBTSavedCountToHeader(0);
|
||||
writeEntitiesSavedCountToHeader(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<CompoundTag> getTileEntities() {
|
||||
return nbtMap.values();
|
||||
@ -403,7 +647,7 @@ public class DiskOptimizedClipboard extends LinearClipboard {
|
||||
@Override
|
||||
public BlockState getBlock(int index) {
|
||||
try {
|
||||
int diskIndex = HEADER_SIZE + (index << 1);
|
||||
int diskIndex = headerSize + (index << 1);
|
||||
char ordinal = byteBuffer.getChar(diskIndex);
|
||||
return BlockState.getFromOrdinal(ordinal);
|
||||
} catch (IndexOutOfBoundsException ignored) {
|
||||
@ -431,7 +675,7 @@ public class DiskOptimizedClipboard extends LinearClipboard {
|
||||
@Override
|
||||
public <B extends BlockStateHolder<B>> boolean setBlock(int x, int y, int z, B block) {
|
||||
try {
|
||||
int index = HEADER_SIZE + (getIndex(x, y, z) << 1);
|
||||
int index = headerSize + (getIndex(x, y, z) << 1);
|
||||
char ordinal = block.getOrdinalChar();
|
||||
if (ordinal == 0) {
|
||||
ordinal = 1;
|
||||
@ -452,7 +696,7 @@ public class DiskOptimizedClipboard extends LinearClipboard {
|
||||
public <B extends BlockStateHolder<B>> boolean setBlock(int i, B block) {
|
||||
try {
|
||||
char ordinal = block.getOrdinalChar();
|
||||
int index = HEADER_SIZE + (i << 1);
|
||||
int index = headerSize + (i << 1);
|
||||
byteBuffer.putChar(index, ordinal);
|
||||
boolean hasNbt = block instanceof BaseBlock && block.hasNbtData();
|
||||
if (hasNbt) {
|
||||
|
@ -1,11 +1,62 @@
|
||||
package com.fastasyncworldedit.core.internal.exception;
|
||||
|
||||
import com.fastasyncworldedit.core.Fawe;
|
||||
import com.fastasyncworldedit.core.configuration.Caption;
|
||||
import com.fastasyncworldedit.core.configuration.Settings;
|
||||
import com.fastasyncworldedit.core.extent.clipboard.DiskOptimizedClipboard;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class FaweClipboardVersionMismatchException extends FaweException {
|
||||
|
||||
private final int expected;
|
||||
private final int version;
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link FaweClipboardVersionMismatchException#FaweClipboardVersionMismatchException(int, int)}
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "TODO")
|
||||
public FaweClipboardVersionMismatchException() {
|
||||
super(Caption.of("fawe.error.clipboard.on.disk.version.mismatch"), Type.CLIPBOARD);
|
||||
this(DiskOptimizedClipboard.VERSION, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* New exception specifying a version mismatch between that supported and that loaded.
|
||||
*
|
||||
* @param version version of clipboard attempting to be loaded
|
||||
* @param expected expected version of clipboard
|
||||
* @since TODO
|
||||
*/
|
||||
public FaweClipboardVersionMismatchException(int expected, int version) {
|
||||
super(
|
||||
Caption.of(
|
||||
"fawe.error.clipboard.on.disk.version.mismatch",
|
||||
expected,
|
||||
version,
|
||||
Fawe.platform().getDirectory().getName() + File.separator + Settings.settings().PATHS.CLIPBOARD
|
||||
),
|
||||
Type.CLIPBOARD
|
||||
);
|
||||
this.expected = expected;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the version specified in the clipboard attempting to be loaded.
|
||||
*
|
||||
* @since TODO
|
||||
*/
|
||||
public int getClipboardVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the version that was expected of the clipboard
|
||||
*
|
||||
* @since TODO
|
||||
*/
|
||||
public int getExpectedVersion() {
|
||||
return expected;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,38 @@
|
||||
package com.fastasyncworldedit.core.internal.io;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* <a href="https://stackoverflow.com/questions/4332264/wrapping-a-bytebuffer-with-an-inputstream">https://stackoverflow.com/questions/4332264/wrapping-a-bytebuffer-with-an-inputstream</a>
|
||||
*/
|
||||
public class ByteBufferInputStream extends InputStream {
|
||||
|
||||
ByteBuffer buf;
|
||||
|
||||
public ByteBufferInputStream(ByteBuffer buf) {
|
||||
this.buf = buf;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
if (!buf.hasRemaining()) {
|
||||
return -1;
|
||||
}
|
||||
return buf.get() & 0xFF;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(@Nonnull byte[] bytes, int off, int len) throws IOException {
|
||||
if (!buf.hasRemaining()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
len = Math.min(len, buf.remaining());
|
||||
buf.get(bytes, off, len);
|
||||
return len;
|
||||
}
|
||||
|
||||
}
|
@ -29,6 +29,7 @@ import java.io.InputStream;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@ -652,4 +653,46 @@ public final class NBTInputStream implements Closeable {
|
||||
is.close();
|
||||
}
|
||||
|
||||
//FAWE start - Copied from FaweStreamChangeSet
|
||||
public Iterator<CompoundTag> toIterator() {
|
||||
return new Iterator<CompoundTag>() {
|
||||
private CompoundTag last = read();
|
||||
|
||||
public CompoundTag read() {
|
||||
try {
|
||||
return (CompoundTag) NBTInputStream.this.readTag();
|
||||
} catch (Exception ignored) {
|
||||
// Assume input is complete
|
||||
}
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return last != null || ((last = read()) != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundTag next() {
|
||||
CompoundTag tmp = last;
|
||||
if (tmp == null) {
|
||||
tmp = read();
|
||||
}
|
||||
last = null;
|
||||
return tmp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new IllegalArgumentException("CANNOT REMOVE");
|
||||
}
|
||||
};
|
||||
}
|
||||
//FAWE end
|
||||
|
||||
}
|
||||
|
@ -470,13 +470,13 @@ public interface Player extends Entity, Actor {
|
||||
}
|
||||
} catch (EmptyClipboardException ignored) {
|
||||
}
|
||||
DiskOptimizedClipboard doc = new DiskOptimizedClipboard(file);
|
||||
DiskOptimizedClipboard doc = DiskOptimizedClipboard.loadFromFile(file);
|
||||
Clipboard clip = doc.toClipboard();
|
||||
ClipboardHolder holder = new ClipboardHolder(clip);
|
||||
session.setClipboard(holder);
|
||||
}
|
||||
} catch (FaweClipboardVersionMismatchException e) {
|
||||
print(Caption.of("fawe.error.clipboard.on.disk.version.mismatch"));
|
||||
print(e.getComponent());
|
||||
} catch (RuntimeException e) {
|
||||
print(Caption.of("fawe.error.clipboard.invalid"));
|
||||
e.printStackTrace();
|
||||
|
@ -126,7 +126,7 @@
|
||||
"fawe.error.clipboard.invalid": "====== INVALID CLIPBOARD ======",
|
||||
"fawe.error.clipboard.invalid.info": "File: {0} (len: {1})",
|
||||
"fawe.error.clipboard.load.failure": "Could not load clipboard. Possible that the clipboard is still being written to from another server?!",
|
||||
"fawe.error.clipboard.on.disk.version.mismatch": "Clipboard version mismatch. Please delete your clipboards folder and restart the server.",
|
||||
"fawe.error.clipboard.on.disk.version.mismatch": "Clipboard version mismatch: expected {0} but got {1}. It is recommended you delete the clipboard folder and restart the server.\nYour clipboard folder is located at {2}.",
|
||||
"fawe.error.limit.disallowed-block": "Your limit disallows use of block '{0}'",
|
||||
"fawe.error.limit.disallowed-property": "Your limit disallows use of property '{0}'",
|
||||
"fawe.error.region-mask-invalid": "Invalid region mask: {0}",
|
||||
|
Loading…
Reference in New Issue
Block a user