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"
|
"If a player's clipboard should be deleted upon logout"
|
||||||
})
|
})
|
||||||
public boolean DELETE_ON_LOGOUT = false;
|
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.Fawe;
|
||||||
import com.fastasyncworldedit.core.configuration.Settings;
|
import com.fastasyncworldedit.core.configuration.Settings;
|
||||||
import com.fastasyncworldedit.core.internal.exception.FaweClipboardVersionMismatchException;
|
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.jnbt.streamer.IntValueReader;
|
||||||
import com.fastasyncworldedit.core.math.IntTriple;
|
import com.fastasyncworldedit.core.math.IntTriple;
|
||||||
import com.fastasyncworldedit.core.util.MainUtil;
|
import com.fastasyncworldedit.core.util.MainUtil;
|
||||||
import com.fastasyncworldedit.core.util.ReflectionUtils;
|
import com.fastasyncworldedit.core.util.ReflectionUtils;
|
||||||
import com.sk89q.jnbt.CompoundTag;
|
import com.sk89q.jnbt.CompoundTag;
|
||||||
|
import com.sk89q.jnbt.DoubleTag;
|
||||||
import com.sk89q.jnbt.IntTag;
|
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.jnbt.Tag;
|
||||||
import com.sk89q.worldedit.entity.BaseEntity;
|
import com.sk89q.worldedit.entity.BaseEntity;
|
||||||
import com.sk89q.worldedit.entity.Entity;
|
import com.sk89q.worldedit.entity.Entity;
|
||||||
import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard;
|
import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard;
|
||||||
import com.sk89q.worldedit.internal.util.LogManagerCompat;
|
import com.sk89q.worldedit.internal.util.LogManagerCompat;
|
||||||
import com.sk89q.worldedit.math.BlockVector3;
|
import com.sk89q.worldedit.math.BlockVector3;
|
||||||
import com.sk89q.worldedit.regions.CuboidRegion;
|
|
||||||
import com.sk89q.worldedit.regions.Region;
|
import com.sk89q.worldedit.regions.Region;
|
||||||
import com.sk89q.worldedit.util.Location;
|
import com.sk89q.worldedit.util.Location;
|
||||||
import com.sk89q.worldedit.world.biome.BiomeType;
|
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 org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.DataInputStream;
|
import java.io.DataInputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
@ -52,21 +57,27 @@ import java.util.stream.Collectors;
|
|||||||
*/
|
*/
|
||||||
public class DiskOptimizedClipboard extends LinearClipboard {
|
public class DiskOptimizedClipboard extends LinearClipboard {
|
||||||
|
|
||||||
|
public static final int VERSION = 2;
|
||||||
private static final Logger LOGGER = LogManagerCompat.getLogger();
|
private static final Logger LOGGER = LogManagerCompat.getLogger();
|
||||||
|
private static final int HEADER_SIZE = 27; // Current header size
|
||||||
private static final int VERSION = 1;
|
private static final int VERSION_1_HEADER_SIZE = 22; // Header size of "version 1"
|
||||||
private static final int HEADER_SIZE = 22;
|
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 HashMap<IntTriple, CompoundTag> nbtMap;
|
||||||
private final File file;
|
private final File file;
|
||||||
|
private final int headerSize;
|
||||||
|
|
||||||
private RandomAccessFile braf;
|
private RandomAccessFile braf;
|
||||||
private MappedByteBuffer byteBuffer;
|
private MappedByteBuffer byteBuffer;
|
||||||
|
|
||||||
private FileChannel fileChannel;
|
private FileChannel fileChannel;
|
||||||
private boolean hasBiomes;
|
private boolean hasBiomes = false;
|
||||||
private boolean canHaveBiomes = true;
|
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) {
|
public DiskOptimizedClipboard(Region region, UUID uuid) {
|
||||||
this(
|
this(
|
||||||
region.getDimensions(),
|
region.getDimensions(),
|
||||||
@ -79,6 +90,14 @@ public class DiskOptimizedClipboard extends LinearClipboard {
|
|||||||
setOrigin(region.getMinimumPoint());
|
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) {
|
public DiskOptimizedClipboard(BlockVector3 dimensions) {
|
||||||
this(
|
this(
|
||||||
dimensions,
|
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) {
|
public DiskOptimizedClipboard(BlockVector3 dimensions, File file) {
|
||||||
super(dimensions, BlockVector3.ZERO);
|
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(
|
throw new IllegalArgumentException(
|
||||||
"Dimensions too large for this clipboard format. Use //lazycopy for large selections.");
|
"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");
|
LOGGER.error("Dimensions are too large for biomes to be stored in a DiskOptimizedClipboard");
|
||||||
canHaveBiomes = false;
|
canHaveBiomes = false;
|
||||||
}
|
}
|
||||||
@ -113,9 +137,10 @@ public class DiskOptimizedClipboard extends LinearClipboard {
|
|||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
this.braf = new RandomAccessFile(file, "rw");
|
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(0);
|
||||||
braf.setLength(fileLength);
|
braf.setLength(fileLength);
|
||||||
|
this.nbtBytesRemaining = Integer.MAX_VALUE - (int) fileLength;
|
||||||
init();
|
init();
|
||||||
// write getLength() etc
|
// write getLength() etc
|
||||||
byteBuffer.putChar(2, (char) (VERSION));
|
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) {
|
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<>();
|
nbtMap = new HashMap<>();
|
||||||
try {
|
try {
|
||||||
this.file = file;
|
this.file = file;
|
||||||
this.braf = new RandomAccessFile(file, "rw");
|
this.braf = new RandomAccessFile(file, "rw");
|
||||||
braf.setLength(file.length());
|
braf.setLength(file.length());
|
||||||
|
this.nbtBytesRemaining = Integer.MAX_VALUE - (int) file.length();
|
||||||
init();
|
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;
|
hasBiomes = true;
|
||||||
}
|
}
|
||||||
getAndSetOffsetAndOrigin();
|
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))) {
|
try (DataInputStream is = new DataInputStream(new FileInputStream(file))) {
|
||||||
is.skipBytes(2);
|
is.skipBytes(2);
|
||||||
int version = is.readChar();
|
int version = is.readChar();
|
||||||
if (version != VERSION) {
|
if (version != expectedVersion) {
|
||||||
throw new FaweClipboardVersionMismatchException();
|
throw new FaweClipboardVersionMismatchException(expectedVersion, version);
|
||||||
}
|
}
|
||||||
return BlockVector3.at(is.readChar(), is.readChar(), is.readChar());
|
return BlockVector3.at(is.readChar(), is.readChar(), is.readChar());
|
||||||
} catch (IOException e) {
|
} 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
|
@Override
|
||||||
public URI getURI() {
|
public URI getURI() {
|
||||||
return file.toURI();
|
return file.toURI();
|
||||||
@ -185,8 +304,11 @@ public class DiskOptimizedClipboard extends LinearClipboard {
|
|||||||
close();
|
close();
|
||||||
this.braf = new RandomAccessFile(file, "rw");
|
this.braf = new RandomAccessFile(file, "rw");
|
||||||
// Since biomes represent a 4x4x4 cube, we store fewer biome bytes that volume at 1 byte per biome
|
// 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
|
// +1 to each to 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));
|
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();
|
init();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
@ -216,7 +338,7 @@ public class DiskOptimizedClipboard extends LinearClipboard {
|
|||||||
public void setBiome(int index, BiomeType biome) {
|
public void setBiome(int index, BiomeType biome) {
|
||||||
if (initBiome()) {
|
if (initBiome()) {
|
||||||
try {
|
try {
|
||||||
byteBuffer.put(HEADER_SIZE + (getVolume() << 1) + index, (byte) biome.getInternalId());
|
byteBuffer.put(headerSize + (getVolume() << 1) + index, (byte) biome.getInternalId());
|
||||||
} catch (IndexOutOfBoundsException e) {
|
} catch (IndexOutOfBoundsException e) {
|
||||||
LOGGER.info((long) (getHeight() >> 2) * (getLength() >> 2) * (getWidth() >> 2));
|
LOGGER.info((long) (getHeight() >> 2) * (getLength() >> 2) * (getWidth() >> 2));
|
||||||
LOGGER.info(index);
|
LOGGER.info(index);
|
||||||
@ -230,7 +352,7 @@ public class DiskOptimizedClipboard extends LinearClipboard {
|
|||||||
if (!hasBiomes()) {
|
if (!hasBiomes()) {
|
||||||
return null;
|
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);
|
return BiomeTypes.get(biomeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -239,7 +361,7 @@ public class DiskOptimizedClipboard extends LinearClipboard {
|
|||||||
if (!hasBiomes()) {
|
if (!hasBiomes()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int mbbIndex = HEADER_SIZE + (getVolume() << 1);
|
int mbbIndex = headerSize + (getVolume() << 1);
|
||||||
try {
|
try {
|
||||||
for (int y = 0; y < getHeight(); y++) {
|
for (int y = 0; y < getHeight(); y++) {
|
||||||
for (int z = 0; z < getLength(); z++) {
|
for (int z = 0; z < getLength(); z++) {
|
||||||
@ -325,11 +447,45 @@ public class DiskOptimizedClipboard extends LinearClipboard {
|
|||||||
ReflectionUtils.getUnsafe().invokeCleaner(cb);
|
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
|
@Override
|
||||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||||
public void close() {
|
public void close() {
|
||||||
try {
|
try {
|
||||||
if (byteBuffer != null) {
|
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();
|
byteBuffer.force();
|
||||||
fileChannel.close();
|
fileChannel.close();
|
||||||
braf.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
|
@Override
|
||||||
public Collection<CompoundTag> getTileEntities() {
|
public Collection<CompoundTag> getTileEntities() {
|
||||||
return nbtMap.values();
|
return nbtMap.values();
|
||||||
@ -403,7 +647,7 @@ public class DiskOptimizedClipboard extends LinearClipboard {
|
|||||||
@Override
|
@Override
|
||||||
public BlockState getBlock(int index) {
|
public BlockState getBlock(int index) {
|
||||||
try {
|
try {
|
||||||
int diskIndex = HEADER_SIZE + (index << 1);
|
int diskIndex = headerSize + (index << 1);
|
||||||
char ordinal = byteBuffer.getChar(diskIndex);
|
char ordinal = byteBuffer.getChar(diskIndex);
|
||||||
return BlockState.getFromOrdinal(ordinal);
|
return BlockState.getFromOrdinal(ordinal);
|
||||||
} catch (IndexOutOfBoundsException ignored) {
|
} catch (IndexOutOfBoundsException ignored) {
|
||||||
@ -431,7 +675,7 @@ public class DiskOptimizedClipboard extends LinearClipboard {
|
|||||||
@Override
|
@Override
|
||||||
public <B extends BlockStateHolder<B>> boolean setBlock(int x, int y, int z, B block) {
|
public <B extends BlockStateHolder<B>> boolean setBlock(int x, int y, int z, B block) {
|
||||||
try {
|
try {
|
||||||
int index = HEADER_SIZE + (getIndex(x, y, z) << 1);
|
int index = headerSize + (getIndex(x, y, z) << 1);
|
||||||
char ordinal = block.getOrdinalChar();
|
char ordinal = block.getOrdinalChar();
|
||||||
if (ordinal == 0) {
|
if (ordinal == 0) {
|
||||||
ordinal = 1;
|
ordinal = 1;
|
||||||
@ -452,7 +696,7 @@ public class DiskOptimizedClipboard extends LinearClipboard {
|
|||||||
public <B extends BlockStateHolder<B>> boolean setBlock(int i, B block) {
|
public <B extends BlockStateHolder<B>> boolean setBlock(int i, B block) {
|
||||||
try {
|
try {
|
||||||
char ordinal = block.getOrdinalChar();
|
char ordinal = block.getOrdinalChar();
|
||||||
int index = HEADER_SIZE + (i << 1);
|
int index = headerSize + (i << 1);
|
||||||
byteBuffer.putChar(index, ordinal);
|
byteBuffer.putChar(index, ordinal);
|
||||||
boolean hasNbt = block instanceof BaseBlock && block.hasNbtData();
|
boolean hasNbt = block instanceof BaseBlock && block.hasNbtData();
|
||||||
if (hasNbt) {
|
if (hasNbt) {
|
||||||
|
@ -1,11 +1,62 @@
|
|||||||
package com.fastasyncworldedit.core.internal.exception;
|
package com.fastasyncworldedit.core.internal.exception;
|
||||||
|
|
||||||
|
import com.fastasyncworldedit.core.Fawe;
|
||||||
import com.fastasyncworldedit.core.configuration.Caption;
|
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 {
|
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() {
|
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.AbstractMap;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@ -652,4 +653,46 @@ public final class NBTInputStream implements Closeable {
|
|||||||
is.close();
|
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) {
|
} catch (EmptyClipboardException ignored) {
|
||||||
}
|
}
|
||||||
DiskOptimizedClipboard doc = new DiskOptimizedClipboard(file);
|
DiskOptimizedClipboard doc = DiskOptimizedClipboard.loadFromFile(file);
|
||||||
Clipboard clip = doc.toClipboard();
|
Clipboard clip = doc.toClipboard();
|
||||||
ClipboardHolder holder = new ClipboardHolder(clip);
|
ClipboardHolder holder = new ClipboardHolder(clip);
|
||||||
session.setClipboard(holder);
|
session.setClipboard(holder);
|
||||||
}
|
}
|
||||||
} catch (FaweClipboardVersionMismatchException e) {
|
} catch (FaweClipboardVersionMismatchException e) {
|
||||||
print(Caption.of("fawe.error.clipboard.on.disk.version.mismatch"));
|
print(e.getComponent());
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
print(Caption.of("fawe.error.clipboard.invalid"));
|
print(Caption.of("fawe.error.clipboard.invalid"));
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
@ -126,7 +126,7 @@
|
|||||||
"fawe.error.clipboard.invalid": "====== INVALID CLIPBOARD ======",
|
"fawe.error.clipboard.invalid": "====== INVALID CLIPBOARD ======",
|
||||||
"fawe.error.clipboard.invalid.info": "File: {0} (len: {1})",
|
"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.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-block": "Your limit disallows use of block '{0}'",
|
||||||
"fawe.error.limit.disallowed-property": "Your limit disallows use of property '{0}'",
|
"fawe.error.limit.disallowed-property": "Your limit disallows use of property '{0}'",
|
||||||
"fawe.error.region-mask-invalid": "Invalid region mask: {0}",
|
"fawe.error.region-mask-invalid": "Invalid region mask: {0}",
|
||||||
|
Loading…
Reference in New Issue
Block a user