3
0
Mirror von https://github.com/IntellectualSites/FastAsyncWorldEdit.git synchronisiert 2024-09-29 10:30:05 +02:00
Dieser Commit ist enthalten in:
dordsor21 2023-08-20 15:18:32 +01:00
Ursprung 5c975eda3c
Commit e3ea0c2b4a
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: 1E53E88969FFCF0B
31 geänderte Dateien mit 3578 neuen und 247 gelöschten Zeilen

Datei anzeigen

@ -630,6 +630,16 @@ public final class PaperweightAdapter implements BukkitImplAdapter<net.minecraft
return CraftItemStack.asCraftMirror(stack); return CraftItemStack.asCraftMirror(stack);
} }
@Override
public char[] getIbdToStateOrdinal() {
return new char[0];
}
@Override
public int[] getOrdinalToIbdID() {
return new int[0];
}
@Override @Override
public BaseItemStack adapt(org.bukkit.inventory.ItemStack itemStack) { public BaseItemStack adapt(org.bukkit.inventory.ItemStack itemStack) {
final ItemStack nmsStack = CraftItemStack.asNMSCopy(itemStack); final ItemStack nmsStack = CraftItemStack.asNMSCopy(itemStack);

Datei anzeigen

@ -629,6 +629,16 @@ public final class PaperweightAdapter implements BukkitImplAdapter<net.minecraft
return CraftItemStack.asCraftMirror(stack); return CraftItemStack.asCraftMirror(stack);
} }
@Override
public char[] getIbdToStateOrdinal() {
return new char[0];
}
@Override
public int[] getOrdinalToIbdID() {
return new int[0];
}
@Override @Override
public BaseItemStack adapt(org.bukkit.inventory.ItemStack itemStack) { public BaseItemStack adapt(org.bukkit.inventory.ItemStack itemStack) {
final ItemStack nmsStack = CraftItemStack.asNMSCopy(itemStack); final ItemStack nmsStack = CraftItemStack.asNMSCopy(itemStack);

Datei anzeigen

@ -644,6 +644,16 @@ public final class PaperweightAdapter implements BukkitImplAdapter<net.minecraft
return CraftItemStack.asCraftMirror(stack); return CraftItemStack.asCraftMirror(stack);
} }
@Override
public char[] getIbdToStateOrdinal() {
return new char[0];
}
@Override
public int[] getOrdinalToIbdID() {
return new int[0];
}
@Override @Override
public BaseItemStack adapt(org.bukkit.inventory.ItemStack itemStack) { public BaseItemStack adapt(org.bukkit.inventory.ItemStack itemStack) {
var registryAccess = DedicatedServer.getServer().registryAccess(); var registryAccess = DedicatedServer.getServer().registryAccess();

Datei anzeigen

@ -654,6 +654,16 @@ public final class PaperweightAdapter implements BukkitImplAdapter<net.minecraft
return CraftItemStack.asCraftMirror(stack); return CraftItemStack.asCraftMirror(stack);
} }
@Override
public char[] getIbdToStateOrdinal() {
return new char[0];
}
@Override
public int[] getOrdinalToIbdID() {
return new int[0];
}
@Override @Override
public BaseItemStack adapt(org.bukkit.inventory.ItemStack itemStack) { public BaseItemStack adapt(org.bukkit.inventory.ItemStack itemStack) {
var registryAccess = DedicatedServer.getServer().registryAccess(); var registryAccess = DedicatedServer.getServer().registryAccess();

Datei anzeigen

@ -45,6 +45,7 @@ import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.PluginManager;
import java.io.File; import java.io.File;
import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
@ -312,6 +313,16 @@ public class FaweBukkit implements IFawe, Listener {
return platformAdapter; return platformAdapter;
} }
@Override
public Path getWorldsFolder() {
return Bukkit.getWorldContainer().toPath();
}
@Override
public boolean isWorldLoaded(String world) {
return Bukkit.getWorld(world) != null;
}
private void setupPlotSquared() { private void setupPlotSquared() {
Plugin plotSquared = this.plugin.getServer().getPluginManager().getPlugin("PlotSquared"); Plugin plotSquared = this.plugin.getServer().getPluginManager().getPlugin("PlotSquared");
if (plotSquared == null) { if (plotSquared == null) {

Datei anzeigen

@ -100,8 +100,4 @@ public abstract class CachedBukkitAdapter implements IBukkitAdapter {
} }
} }
protected abstract char[] getIbdToStateOrdinal();
protected abstract int[] getOrdinalToIbdID();
} }

Datei anzeigen

@ -396,4 +396,14 @@ public interface IBukkitAdapter {
return TaskManager.taskManager().sync(world::getEntities); return TaskManager.taskManager().sync(world::getEntities);
} }
/**
* Get a char array of minecraft internal IDs against FAWE char IDs
*/
char[] getIbdToStateOrdinal();
/**
* Get an int array of FAWE char IDs against minecraft internal IDs
*/
int[] getOrdinalToIbdID();
} }

Datei anzeigen

@ -23,12 +23,12 @@ public class SimpleBukkitAdapter extends CachedBukkitAdapter {
} }
@Override @Override
protected char[] getIbdToStateOrdinal() { public char[] getIbdToStateOrdinal() {
return new char[Character.MAX_VALUE + 1]; return new char[Character.MAX_VALUE + 1];
} }
@Override @Override
protected int[] getOrdinalToIbdID() { public int[] getOrdinalToIbdID() {
return new int[Character.MAX_VALUE + 1]; return new int[Character.MAX_VALUE + 1];
} }

Datei anzeigen

@ -495,4 +495,20 @@ public enum BukkitAdapter {
return getAdapter().adapt(item); return getAdapter().adapt(item);
//FAWE end //FAWE end
} }
//FAWE start
/**
* Get a char array of minecraft internal IDs against FAWE char IDs
*/
public static char[] getIbdToStateOrdinal() {
return getAdapter().getIbdToStateOrdinal();
}
/**
* Get an int array of FAWE char IDs against minecraft internal IDs
*/
public static int[] getOrdinalToIbdID() {
return getAdapter().getOrdinalToIbdID();
}
//FAWE end
} }

Datei anzeigen

@ -309,5 +309,17 @@ public class BukkitServerInterface extends AbstractPlatform implements MultiUser
} }
return this.plugin.getBukkitImplAdapter().getTickingPostProcessor(); return this.plugin.getBukkitImplAdapter().getTickingPostProcessor();
} }
@Nullable
@Override
public char[] getIbdToStateOrdinal() {
return BukkitAdapter.getIbdToStateOrdinal();
}
@Nullable
@Override
public int[] getOrdinalToIbdID() {
return BukkitAdapter.getOrdinalToIbdID();
}
//FAWE end //FAWE end
} }

Datei anzeigen

@ -8,6 +8,7 @@ import com.fastasyncworldedit.core.util.image.ImageViewer;
import com.sk89q.worldedit.entity.Player; import com.sk89q.worldedit.entity.Player;
import java.io.File; import java.io.File;
import java.nio.file.Path;
import java.util.Collection; import java.util.Collection;
import java.util.UUID; import java.util.UUID;
@ -49,4 +50,8 @@ public interface IFawe {
FAWEPlatformAdapterImpl getPlatformAdapter(); FAWEPlatformAdapterImpl getPlatformAdapter();
Path getWorldsFolder();
boolean isWorldLoaded(String world);
} }

Datei-Diff unterdrückt, da er zu groß ist Diff laden

Datei anzeigen

@ -0,0 +1,594 @@
package com.fastasyncworldedit.core.anvil;
import com.fastasyncworldedit.core.math.FastBitSet;
import com.fastasyncworldedit.core.util.task.RunnableVal4;
import com.plotsquared.core.util.task.RunnableVal;
import com.sk89q.jnbt.NBTInputStream;
import com.sk89q.worldedit.MissingWorldException;
import com.sk89q.worldedit.internal.util.LogManagerCompat;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.io.FastByteArrayInputStream;
import org.apache.logging.log4j.Logger;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.BufferedInputStream;
import java.io.Closeable;
import java.io.Flushable;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.zip.GZIPInputStream;
import java.util.zip.InflaterInputStream;
@SuppressWarnings({"removal"})
public class MCAFile implements Closeable, Flushable {
private static final Logger LOGGER = LogManagerCompat.getLogger();
private static final int CHUNK_HEADER_SIZE = 5;
private static final byte VERSION_GZIP = 1;
private static final byte VERSION_DEFLATE = 2;
private static final byte VERSION_UNCOMPRESSED = 3;
private static final int SECTOR_BYTES = 4096;
private static final int SECTOR_INTS = SECTOR_BYTES / 4;
private final Int2IntOpenHashMap offsetMap;
private final Path file;
private RandomAccessFile raf;
private int[] offsets;
private boolean deleted;
private final int X, Z;
private final Int2ObjectOpenHashMap<MCAChunk> chunks = new Int2ObjectOpenHashMap<>();
private FastBitSet sectorFree;
private boolean closed = false;
private volatile boolean init = false;
public MCAFile(Path file) {
this.file = file;
if (!Files.exists(file)) {
throw new MissingWorldException();
}
String[] split = file.getFileName().toString().split("\\.");
X = Integer.parseInt(split[1]);
Z = Integer.parseInt(split[2]);
offsetMap = new Int2IntOpenHashMap();
offsetMap.defaultReturnValue(Integer.MAX_VALUE);
init();
}
public MCAFile(int mcrX, int mcrZ, Path file) {
this.file = file;
X = mcrX;
Z = mcrZ;
offsetMap = new Int2IntOpenHashMap();
offsetMap.defaultReturnValue(Integer.MAX_VALUE);
init();
}
public void clear() {
if (raf != null) {
try {
raf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
synchronized (chunks) {
chunks.clear();
}
offsetMap.clear();
offsets = null;
}
/**
* Set if the file should be delete
*/
public void setDeleted(boolean deleted) {
if (!init) {
init();
}
this.deleted = deleted;
}
/**
* Get if the file has been set to be deleted
*/
public boolean isDeleted() {
return deleted;
}
/**
* Initialises the RandomAccessFile and loads the location header from disk if not done yet
*/
public synchronized void init() {
try {
if (raf == null) {
this.offsets = new int[SECTOR_INTS];
if (file != null) {
this.raf = new RandomAccessFile(file.toFile(), "rw");
final int nSectors = (int) Math.round(Math.ceil((double) raf.length() / SECTOR_BYTES));
sectorFree = new FastBitSet(nSectors);
sectorFree.setAll();
sectorFree.set(0, false);
sectorFree.set(1, false);
if (raf.length() < 8192) {
raf.setLength(8192);
} else {
if ((raf.length() & 0xFFF) != 0) {
raf.setLength(((raf.length() + 0xFFF) >> 12) << 12);
}
raf.seek(0);
for (int i = 0; i < SECTOR_INTS; i++) {
final int offset = raf.readInt();
offsets[i] = offset;
int sectorStart = offset >> 8;
int numSectors = offset & 0xFF;
if (offset != 0 && sectorStart + numSectors <= sectorFree.size()) {
offsetMap.put(offset, i);
for (int sectorNum = 0; sectorNum < (offset & 0xFF); sectorNum++) {
sectorFree.set((offset >> 8) + sectorNum, false);
}
}
}
}
}
init = true;
closed = false;
}
} catch (Throwable e) {
e.printStackTrace();
}
}
/**
* Get the region file X
*/
public int getX() {
return X;
}
/**
* Get the region file Z
*/
public int getZ() {
return Z;
}
/**
* Get the RandomAccessFile of the MCA region file
*/
public RandomAccessFile getRandomAccessFile() {
return raf;
}
/**
* Get the MCA region file
*/
public Path getFile() {
return file;
}
/**
* Gets a cached {@link MCAChunk} if present else returns null
*/
@Nullable
public MCAChunk getCachedChunk(int cx, int cz) {
if (!init) {
init();
}
short pair = (short) ((cx & 31) + ((cz & 31) << 5));
synchronized (chunks) {
return chunks.get(pair);
}
}
/**
* Create a new empty {@link MCAChunk}.
*/
public MCAChunk newChunk(int cx, int cz) {
if (!init) {
init();
}
short pair = (short) ((cx & 31) + ((cz & 31) << 5));
MCAChunk chunk;
synchronized (chunks) {
chunks.put(pair, chunk = new MCAChunk(this, cx, cx));
}
return chunk;
}
/**
* Insert a {@link MCAChunk} into the cache.
*/
public void setChunk(MCAChunk chunk) {
if (!init) {
init();
}
int cx = chunk.getX();
int cz = chunk.getZ();
short pair = (short) ((cx & 31) + ((cz & 31) << 5));
synchronized (chunks) {
chunks.put(pair, chunk);
}
}
/**
* Load data from the mca region into the given {@link MCAChunk}.
*/
public void loadIntoChunkFromFile(MCAChunk chunk) throws IOException {
if (!init) {
init();
}
int cx = chunk.getX();
int cz = chunk.getZ();
int i = (cx & 31) + ((cz & 31) << 5);
int offset = offsets[i];
synchronized (this) {
if (offset == 0) {
chunk.setEmpty(true);
return;
}
chunk.loadFromNIS(getChunkIS(offset >> 8), false);
if (offset == 0) {
return;
}
if (i < 2) {
int length;
byte version;
byte[] data;
synchronized (this) {
raf.seek((long) (offset >> 8) << 12);
length = raf.readInt();
version = raf.readByte();
data = new byte[length - 1];
raf.read(data);
}
FastByteArrayInputStream bais = new FastByteArrayInputStream(data);
BufferedInputStream bis = switch (version) {
case VERSION_GZIP -> new BufferedInputStream(new GZIPInputStream(bais));
case VERSION_DEFLATE -> new BufferedInputStream(new InflaterInputStream(bais));
case VERSION_UNCOMPRESSED -> new BufferedInputStream(bais);
default -> throw new IllegalStateException("Unexpected compression version: " + version);
};
}
}
}
@Nonnull
public MCAChunk getChunk(int cx, int cz) throws IOException {
if (!init) {
init();
}
MCAChunk cached = getCachedChunk(cx, cz);
if (cached != null) {
return cached;
} else {
return readChunk(cx, cz);
}
}
public MCAChunk readChunk(int cx, int cz) throws IOException {
if (!init) {
init();
}
int i = (cx & 31) + ((cz & 31) << 5);
int offset = offsets[i];
if (offset == 0) {
return newChunk(cx, cz);
}
try {
MCAChunk chunk;
synchronized (this) {
chunk = getChunkIS(offset >> 8, cx, cz);
}
if (i < 2) {
int length;
byte version;
byte[] data;
synchronized (this) {
raf.seek((long) (offset >> 8) << 12);
length = raf.readInt();
version = raf.readByte();
data = new byte[length - 1];
raf.read(data);
}
FastByteArrayInputStream bais = new FastByteArrayInputStream(data);
BufferedInputStream bis = switch (version) {
case VERSION_GZIP -> new BufferedInputStream(new GZIPInputStream(bais));
case VERSION_DEFLATE -> new BufferedInputStream(new InflaterInputStream(bais));
case VERSION_UNCOMPRESSED -> new BufferedInputStream(bais);
default -> throw new IllegalStateException("Unexpected compression version: " + version);
};
}
short pair = (short) ((cx & 31) + ((cz & 31) << 5));
synchronized (chunks) {
chunks.put(pair, chunk);
}
return chunk;
} catch (Exception e) {
throw new RuntimeException("Error attempting to read chunk locally located at `" + (cx & 31) + "," + (cz & 31) + "`" +
" in file `" + file.getFileName() + "` at offset: `" + (offset >> 8) + "`", e);
}
}
/**
* @param onEach cx, cz, offset, size (in kB)
*/
public void forEachChunk(RunnableVal4<Integer, Integer, Integer, Integer> onEach) {
if (!init) {
init();
}
int i = 0;
for (int z = 0; z < 32; z++) {
for (int x = 0; x < 32; x++, i += 4) {
int offset = offsets[x + (z << 5)];
if (offset != 0) {
int size = offset & 0xFF;
onEach.run(x, z, offset >> 8, size);
}
}
}
}
public void forEachChunk(RunnableVal<MCAChunk> onEach) {
if (!init) {
init();
}
int rx = X << 5;
int rz = Z << 5;
for (int z = 0; z < 32; z++) {
for (int x = 0; x < 32; x++) {
int offset = offsets[x + (z << 5)];
if (offset != 0) {
try {
onEach.run(getChunk(rx + x, rz + z));
} catch (Throwable ignore) {
}
}
}
}
}
private NBTInputStream getChunkIS(int offset) throws IOException {
int length = -1;
byte version = -1;
byte[] data;
try {
if (offset == 0) {
return null;
}
synchronized (this) {
raf.seek((long) offset << 12);
length = raf.readInt();
version = raf.readByte();
data = new byte[length - 1];
raf.read(data);
}
FastByteArrayInputStream bais = new FastByteArrayInputStream(data);
BufferedInputStream bis = switch (version) {
case VERSION_GZIP -> new BufferedInputStream(new GZIPInputStream(bais));
case VERSION_DEFLATE -> new BufferedInputStream(new InflaterInputStream(bais));
case VERSION_UNCOMPRESSED -> new BufferedInputStream(bais);
default -> throw new IllegalStateException("Unexpected compression version: " + version);
};
return new NBTInputStream(bis);
} catch (IOException e) {
throw new IOException("Length: " + length + ", version: " + version + ", offset: " + offset, e);
}
}
private MCAChunk getChunkIS(int offset, int cx, int cz) throws IOException {
int length = -1;
byte version = -1;
byte[] data;
try {
if (offset == 0) {
return null;
}
synchronized (this) {
raf.seek((long) offset << 12);
length = raf.readInt();
version = raf.readByte();
data = new byte[length - 1];
raf.read(data);
}
FastByteArrayInputStream bais = new FastByteArrayInputStream(data);
BufferedInputStream bis = switch (version) {
case VERSION_GZIP -> new BufferedInputStream(new GZIPInputStream(bais));
case VERSION_DEFLATE -> new BufferedInputStream(new InflaterInputStream(bais));
case VERSION_UNCOMPRESSED -> new BufferedInputStream(bais);
default -> throw new IllegalStateException("Unexpected compression version: " + version);
};
return new MCAChunk(this, new NBTInputStream(bis), cx, cz, false);
} catch (Exception e) {
throw new IOException("Length: " + length + ", version: " + version + ", offset: " + offset, e);
}
}
public List<MCAChunk> getCachedChunks() {
synchronized (chunks) {
return new ArrayList<>(chunks.values());
}
}
public void uncache(int cx, int cz) {
int pair = (cx & 31) + ((cz & 31) << 5);
synchronized (chunks) {
chunks.remove(pair);
}
}
@Override
public synchronized void close() throws IOException {
if (raf == null || closed) {
return;
}
flush();
try {
raf.close();
} catch (IOException e) {
e.printStackTrace();
}
raf = null;
offsets = null;
offsetMap.clear();
closed = true;
init = false;
}
public boolean isModified() {
if (isDeleted()) {
return true;
}
synchronized (chunks) {
for (Int2ObjectMap.Entry<MCAChunk> entry : chunks.int2ObjectEntrySet()) {
MCAChunk chunk = entry.getValue();
if (chunk.isModified() || chunk.isDeleted()) {
return true;
}
}
}
return false;
}
public synchronized void setOffset(final int x, final int z, final int offset)
throws IOException {
int i = (x & 31) + ((z & 31) << 5);
if (offset == 0) {
offsetMap.remove(offsets[i]);
} else {
offsetMap.put(offset, i);
}
offsets[x + (z << 5)] = offset;
raf.seek((long) i << 2);
raf.writeInt(offset);
}
/**
* Write the chunk to the file
*/
@Override
public synchronized void flush() throws IOException {
boolean delete = true;
int currentSector = 2;
Queue<Integer> offsets =
new LinkedBlockingDeque<>(Arrays.stream(this.offsets).boxed().sorted(Comparator.comparingInt(i -> (i >> 8))).toList());
int offset;
int count = 0;
while (offsets.peek() != null) {
count++;
offset = offsets.poll();
if (offset == 0) {
continue;
}
delete = false;
int pair = offsetMap.get(offset);
int sectorNumber = offset >> 8;
int sectorsAllocated = offset & 0xFF;
if (sectorNumber < 2) {
throw new IllegalStateException("Sector number cannot be < 2!");
}
if (pair == Integer.MAX_VALUE) {
sectorFree.setRange(sectorNumber, sectorNumber + sectorsAllocated);
continue;
}
MCAChunk chunk = chunks.remove(pair);
byte[] data;
int sectorsNeeded;
boolean writeChunkHeader;
if (currentSector <= sectorNumber) { // Only set free if we definitely won't be overwriting
sectorFree.setRange(sectorNumber, sectorNumber + sectorsAllocated);
} else {
throw new IllegalStateException("Current sector number being written to cannot exceed sector number of chunk to" +
" be written!");
}
if (chunk == null) {
if (currentSector != sectorNumber) {
writeChunkHeader = false;
data = new byte[sectorsAllocated << 12];
sectorsNeeded = sectorsAllocated;
raf.seek((long) sectorNumber << 12);
raf.read(data);
} else {
sectorFree.clearRange(currentSector, currentSector + sectorsAllocated);
currentSector += sectorsAllocated;
continue;
}
} else if (chunk.isDeleted()) {
int x = pair & 31;
int z = (pair >> 5) & 31;
setOffset(x, z, 0);
continue;
} else {
data = chunk.toBytes(null);
writeChunkHeader = true;
sectorsNeeded = ((data.length + CHUNK_HEADER_SIZE) >> 12) + 1;
}
boolean hasSpace = sectorsNeeded <= sectorsAllocated;
long position = (long) currentSector << 12;
if (!hasSpace) {
hasSpace = true;
for (int i = currentSector; i < currentSector + sectorsNeeded; i++) {
if (!sectorFree.get(i) || i > sectorFree.size()) {
hasSpace = false;
break;
}
}
if (!hasSpace) {
if (sectorNumber > 2500 || count > 30000) {
throw new RuntimeException();
}
sectorNumber = sectorFree.size() + 1;
offset = (sectorNumber << 8) | (sectorsNeeded & 0xFF);
setOffset(pair & 31, (pair >> 5) & 31, offset);
position = (long) sectorNumber << 12;
raf.setLength((long) (sectorNumber + sectorsNeeded + 1) << 12);
sectorFree.expandTo(sectorNumber + sectorsNeeded, true);
sectorFree.clearRange(sectorNumber, sectorNumber + sectorsNeeded);
offsets.add(offset); // Come back later to clean up.
}
}
raf.seek(position);
if (writeChunkHeader) {
raf.writeInt(data.length + 1);
raf.writeByte(VERSION_DEFLATE);
}
raf.write(data);
if (hasSpace) {
offset = (currentSector << 8) | (sectorsNeeded & 0xFF);
setOffset(pair & 31, (pair >> 5) & 31, offset);
sectorFree.clearRange(currentSector, currentSector + sectorsNeeded);
currentSector += sectorsNeeded;
}
}
int size = 0;
for (int i = sectorFree.size(); i > 0; i--) {
if (!sectorFree.get(i)) {
size = i + 1;
break;
}
}
raf.setLength((long) (size + 1) * SECTOR_BYTES);
if (delete || size < 3) {
clear();
Files.delete(file);
}
synchronized (chunks) {
chunks.clear();
}
}
}

Datei anzeigen

@ -0,0 +1,202 @@
package com.fastasyncworldedit.core.anvil;
import com.fastasyncworldedit.core.Fawe;
import com.fastasyncworldedit.core.queue.IChunkGet;
import com.fastasyncworldedit.core.queue.implementation.packet.ChunkPacket;
import com.fastasyncworldedit.core.util.MathMan;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.MaxChangedBlocksException;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.blocks.BaseItemStack;
import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.internal.util.LogManagerCompat;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.math.Vector3;
import com.sk89q.worldedit.util.SideEffect;
import com.sk89q.worldedit.util.SideEffectSet;
import com.sk89q.worldedit.util.TreeGenerator;
import com.sk89q.worldedit.world.AbstractWorld;
import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.world.block.BlockStateHolder;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
public class MCAWorld extends AbstractWorld {
private static final Logger LOGGER = LogManagerCompat.getLogger();
private static final Int2ObjectOpenHashMap<MCAWorld> worldMap = new Int2ObjectOpenHashMap<>();
private final String name;
private final Path folder;
private final Path regionFolder;
private final Int2ObjectOpenHashMap<MCAFile> mcaFileCache = new Int2ObjectOpenHashMap<>();
private MCAWorld(String name, Path folder) {
this.name = name;
this.folder = folder;
this.regionFolder = folder.resolve("region");
}
/**
* New MCAWorld instance.
*
* @param name World name
*/
public static synchronized MCAWorld of(String name) {
return of(name, Fawe.platform().getWorldsFolder().resolve(name));
}
/**
* New MCAWorld instance.
*
* @param name World name
* @param folder World file/folder
*/
public static synchronized MCAWorld of(String name, Path folder) {
if (Fawe.platform().isWorldLoaded(name)) {
throw new IllegalStateException("World " + name + " is loaded. Anvil operations cannot be completed on a loaded world.");
}
// World could be the same name but in a different folder
int combinedHash = Objects.hash(name, folder);
return worldMap.computeIfAbsent(combinedHash, (i) -> new MCAWorld(name, folder));
}
public Collection<MCAFile> getMCAs() {
getRegionFileFiles().forEach(file -> {
String[] split = file.getFileName().toString().split("\\.");
short regionX = Short.parseShort(split[1]);
short regionZ = Short.parseShort(split[2]);
int paired = MathMan.pair(regionX, regionZ);
mcaFileCache.computeIfAbsent(
paired,
(i) -> new MCAFile(regionX, regionZ, regionFolder.resolve("r." + regionX + "." + regionZ + ".mca"))
);
});
return mcaFileCache.values();
}
public List<Path> getRegionFileFiles() {
try {
return Files.list(regionFolder).filter(p -> p.toString().endsWith(".mca")).toList();
} catch (IOException e) {
LOGGER.error("Error listing region files", e);
return Collections.emptyList();
}
}
@Override
public boolean setTile(final int x, final int y, final int z, final CompoundTag tile) throws WorldEditException {
return false;
}
@Override
public String getName() {
return name;
}
@Override
public String getNameUnsafe() {
return name;
}
public Path getFolder() {
return folder;
}
@Override
public <B extends BlockStateHolder<B>> boolean setBlock(
final BlockVector3 position,
final B block,
final SideEffectSet sideEffects
) throws WorldEditException {
return false;
}
@Override
public Set<SideEffect> applySideEffects(
final BlockVector3 position,
final BlockState previousType,
final SideEffectSet sideEffectSet
) throws WorldEditException {
return null;
}
@Override
public boolean clearContainerBlockContents(final BlockVector3 position) {
return false;
}
@Override
public void dropItem(final Vector3 position, final BaseItemStack item) {
}
@Override
public void simulateBlockMine(final BlockVector3 position) {
}
@Override
public boolean generateTree(
final TreeGenerator.TreeType type,
final EditSession editSession,
final BlockVector3 position
) throws MaxChangedBlocksException {
return false;
}
@Override
public BlockVector3 getSpawnPosition() {
return null;
}
@Override
public void refreshChunk(final int chunkX, final int chunkZ) {
throw new UnsupportedOperationException("Not supported in anvil operations.");
}
@Override
public IChunkGet get(final int chunkX, final int chunkZ) {
short regionX = (short) (chunkX >> 5);
short regionZ = (short) (chunkZ >> 5);
int paired = MathMan.pair(regionX, regionZ);
MCAFile mca = mcaFileCache.computeIfAbsent(
paired,
(i) -> new MCAFile(regionX, regionZ, regionFolder.resolve("r." + regionX + "." + regionZ + ".mca"))
);
try {
return mca.getChunk(chunkX, chunkZ);
} catch (IOException e) {
LOGGER.error("Error loading chunk. Creating empty chunk.", e);
return mca.newChunk(chunkX, chunkZ);
}
}
@Override
public void sendFakeChunk(@Nullable final Player player, final ChunkPacket packet) {
throw new UnsupportedOperationException("Not supported in anvil operations.");
}
@Override
public synchronized void flush() {
for (MCAFile mca : mcaFileCache.values()) {
try {
mca.close();
} catch (IOException e) {
LOGGER.error("Could not flush MCAFile {}", mca.getFile().getFileName(), e);
}
}
}
}

Datei anzeigen

@ -0,0 +1,434 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fastasyncworldedit.core.internal.io;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.Arrays;
/**
* A <code>BufferedRandomAccessFile</code> is like a
* <code>RandomAccessFile</code>, but it uses a private buffer so that most
* operations do not require a disk access.
* <P>
*
* Note: The operations on this class are unmonitored. Also, the correct
* functioning of the <code>RandomAccessFile</code> methods that are not
* overridden here relies on the implementation of those methods in the
* superclass.
* Author : Avinash Lakshman ( alakshman@facebook.com) & Prashant Malik ( pmalik@facebook.com )
*/
public class BufferedRandomAccessFile extends RandomAccessFile {
static final int LogBuffSz_ = 16; // 64K buffer
public static final int BuffSz_ = (1 << LogBuffSz_);
static final long BuffMask_ = ~(((long) BuffSz_) - 1L);
/*
* This implementation is based on the buffer implementation in Modula-3's
* "Rd", "Wr", "RdClass", and "WrClass" interfaces.
*/
private boolean dirty_; // true iff unflushed bytes exist
private boolean closed_; // true iff the file is closed
private long curr_; // current position in file
private long lo_, hi_; // bounds on characters in "buff"
private byte[] buff_; // local buffer
private long maxHi_; // this.lo + this.buff.length
private boolean hitEOF_; // buffer contains last file block?
private long diskPos_; // disk position
/*
* To describe the above fields, we introduce the following abstractions for
* the file "f":
*
* len(f) the length of the file curr(f) the current position in the file
* c(f) the abstract contents of the file disk(f) the contents of f's
* backing disk file closed(f) true iff the file is closed
*
* "curr(f)" is an index in the closed interval [0, len(f)]. "c(f)" is a
* character sequence of length "len(f)". "c(f)" and "disk(f)" may differ if
* "c(f)" contains unflushed writes not reflected in "disk(f)". The flush
* operation has the effect of making "disk(f)" identical to "c(f)".
*
* A file is said to be *valid* if the following conditions hold:
*
* V1. The "closed" and "curr" fields are correct:
*
* f.closed == closed(f) f.curr == curr(f)
*
* V2. The current position is either contained in the buffer, or just past
* the buffer:
*
* f.lo <= f.curr <= f.hi
*
* V3. Any (possibly) unflushed characters are stored in "f.buff":
*
* (forall i in [f.lo, f.curr): c(f)[i] == f.buff[i - f.lo])
*
* V4. For all characters not covered by V3, c(f) and disk(f) agree:
*
* (forall i in [f.lo, len(f)): i not in [f.lo, f.curr) => c(f)[i] ==
* disk(f)[i])
*
* V5. "f.dirty" is true iff the buffer contains bytes that should be
* flushed to the file; by V3 and V4, only part of the buffer can be dirty.
*
* f.dirty == (exists i in [f.lo, f.curr): c(f)[i] != f.buff[i - f.lo])
*
* V6. this.maxHi == this.lo + this.buff.length
*
* Note that "f.buff" can be "null" in a valid file, since the range of
* characters in V3 is empty when "f.lo == f.curr".
*
* A file is said to be *ready* if the buffer contains the current position,
* i.e., when:
*
* R1. !f.closed && f.buff != null && f.lo <= f.curr && f.curr < f.hi
*
* When a file is ready, reading or writing a single byte can be performed
* by reading or writing the in-memory buffer without performing a disk
* operation.
*/
/**
* Open a new <code>BufferedRandomAccessFile</code> on <code>file</code>
* in mode <code>mode</code>, which should be "r" for reading only, or
* "rw" for reading and writing.
*/
public BufferedRandomAccessFile(File file, String mode) throws IOException {
super(file, mode);
this.init(0);
}
public BufferedRandomAccessFile(File file, String mode, int size) throws IOException {
super(file, mode);
this.init(size);
}
/**
* Open a new <code>BufferedRandomAccessFile</code> on the file named
* <code>name</code> in mode <code>mode</code>, which should be "r" for
* reading only, or "rw" for reading and writing.
*/
public BufferedRandomAccessFile(String name, String mode) throws IOException {
super(name, mode);
this.init(0);
}
public BufferedRandomAccessFile(String name, String mode, int size) throws FileNotFoundException {
super(name, mode);
this.init(size);
}
public BufferedRandomAccessFile(File file, String mode, byte[] buf) throws FileNotFoundException {
super(file, mode);
this.dirty_ = this.closed_ = false;
this.lo_ = this.curr_ = this.hi_ = 0;
this.buff_ = buf;
this.maxHi_ = (long) BuffSz_;
this.hitEOF_ = false;
this.diskPos_ = 0L;
}
private void init(int size) {
this.dirty_ = this.closed_ = false;
this.lo_ = this.curr_ = this.hi_ = 0;
this.buff_ = (size > BuffSz_) ? new byte[size] : new byte[BuffSz_];
this.maxHi_ = (long) BuffSz_;
this.hitEOF_ = false;
this.diskPos_ = 0L;
}
@Override
public void close() throws IOException {
this.flush();
this.closed_ = true;
super.close();
}
/**
* Flush any bytes in the file's buffer that have not yet been written to
* disk. If the file was created read-only, this method is a no-op.
*/
public void flush() throws IOException {
this.flushBuffer();
}
/* Flush any dirty bytes in the buffer to disk. */
private void flushBuffer() throws IOException {
if (this.dirty_) {
if (this.diskPos_ != this.lo_)
super.seek(this.lo_);
int len = (int) (this.curr_ - this.lo_);
super.write(this.buff_, 0, len);
this.diskPos_ = this.curr_;
this.dirty_ = false;
}
}
/*
* Read at most "this.buff.length" bytes into "this.buff", returning the
* number of bytes read. If the return result is less than
* "this.buff.length", then EOF was read.
*/
private int fillBuffer() throws IOException {
int cnt = 0;
int rem = this.buff_.length;
while (rem > 0) {
int n = super.read(this.buff_, cnt, rem);
if (n < 0)
break;
cnt += n;
rem -= n;
}
if ((cnt < 0) && (this.hitEOF_ = (cnt < this.buff_.length))) {
// make sure buffer that wasn't read is initialized with -1
Arrays.fill(this.buff_, cnt, this.buff_.length, (byte) 0xff);
}
this.diskPos_ += cnt;
return cnt;
}
/*
* This method positions <code>this.curr</code> at position <code>pos</code>.
* If <code>pos</code> does not fall in the current buffer, it flushes the
* current buffer and loads the correct one.<p>
*
* On exit from this routine <code>this.curr == this.hi</code> iff <code>pos</code>
* is at or past the end-of-file, which can only happen if the file was
* opened in read-only mode.
*/
@Override
public void seek(long pos) throws IOException {
if (pos >= this.hi_ || pos < this.lo_) {
// seeking outside of current buffer -- flush and read
this.flushBuffer();
this.lo_ = pos & BuffMask_; // start at BuffSz boundary
this.maxHi_ = this.lo_ + (long) this.buff_.length;
if (this.diskPos_ != this.lo_) {
super.seek(this.lo_);
this.diskPos_ = this.lo_;
}
int n = this.fillBuffer();
this.hi_ = this.lo_ + (long) n;
} else {
// seeking inside current buffer -- no read required
if (pos < this.curr_) {
// if seeking backwards, we must flush to maintain V4
this.flushBuffer();
}
}
this.curr_ = pos;
}
/*
* Does not maintain V4 (i.e. buffer differs from disk contents if previously written to)
* - Assumes no writes were made
* @param pos
* @throws IOException
*/
public void seekUnsafe(long pos) throws IOException {
if (pos >= this.hi_ || pos < this.lo_) {
// seeking outside of current buffer -- flush and read
this.flushBuffer();
this.lo_ = pos & BuffMask_; // start at BuffSz boundary
this.maxHi_ = this.lo_ + (long) this.buff_.length;
if (this.diskPos_ != this.lo_) {
super.seek(this.lo_);
this.diskPos_ = this.lo_;
}
int n = this.fillBuffer();
this.hi_ = this.lo_ + (long) n;
}
this.curr_ = pos;
}
@Override
public long getFilePointer() {
return this.curr_;
}
@Override
public long length() throws IOException {
return Math.max(this.curr_, super.length());
}
@Override
public int read() throws IOException {
if (this.curr_ >= this.hi_) {
// test for EOF
// if (this.hi < this.maxHi) return -1;
if (this.hitEOF_)
return -1;
// slow path -- read another buffer
this.seek(this.curr_);
if (this.curr_ == this.hi_)
return -1;
}
byte res = this.buff_[(int) (this.curr_ - this.lo_)];
this.curr_++;
return ((int) res) & 0xFF; // convert byte -> int
}
public byte read1() throws IOException {
if (this.curr_ >= this.hi_) {
// test for EOF
// if (this.hi < this.maxHi) return -1;
if (this.hitEOF_)
return -1;
// slow path -- read another buffer
this.seek(this.curr_);
if (this.curr_ == this.hi_)
return -1;
}
byte res = this.buff_[(int) (this.curr_ - this.lo_)];
this.curr_++;
return res;
}
@Override
public int read(byte[] b) throws IOException {
return this.read(b, 0, b.length);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
if (this.curr_ >= this.hi_) {
// test for EOF
// if (this.hi < this.maxHi) return -1;
if (this.hitEOF_)
return -1;
// slow path -- read another buffer
this.seek(this.curr_);
if (this.curr_ == this.hi_)
return -1;
}
len = Math.min(len, (int) (this.hi_ - this.curr_));
int buffOff = (int) (this.curr_ - this.lo_);
System.arraycopy(this.buff_, buffOff, b, off, len);
this.curr_ += len;
return len;
}
public byte readCurrent() throws IOException {
if (this.curr_ >= this.hi_) {
// test for EOF
// if (this.hi < this.maxHi) return -1;
if (this.hitEOF_)
return -1;
// slow path -- read another buffer
this.seek(this.curr_);
if (this.curr_ == this.hi_)
return -1;
}
byte res = this.buff_[(int) (this.curr_ - this.lo_)];
return res;
}
public void writeCurrent(byte b) throws IOException {
if (this.curr_ >= this.hi_) {
if (this.hitEOF_ && this.hi_ < this.maxHi_) {
// at EOF -- bump "hi"
this.hi_++;
} else {
// slow path -- write current buffer; read next one
this.seek(this.curr_);
if (this.curr_ == this.hi_) {
// appending to EOF -- bump "hi"
this.hi_++;
}
}
}
this.buff_[(int) (this.curr_ - this.lo_)] = (byte) b;
this.dirty_ = true;
}
public void writeUnsafe(int b) throws IOException {
this.buff_[(int) (this.curr_ - this.lo_)] = (byte) b;
this.curr_++;
this.dirty_ = true;
}
@Override
public void write(int b) throws IOException {
if (this.curr_ >= this.hi_) {
if (this.hitEOF_ && this.hi_ < this.maxHi_) {
// at EOF -- bump "hi"
this.hi_++;
} else {
// slow path -- write current buffer; read next one
this.seek(this.curr_);
if (this.curr_ == this.hi_) {
// appending to EOF -- bump "hi"
this.hi_++;
}
}
}
this.buff_[(int) (this.curr_ - this.lo_)] = (byte) b;
this.curr_++;
this.dirty_ = true;
}
@Override
public void write(byte[] b) throws IOException {
this.write(b, 0, b.length);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
while (len > 0) {
int n = this.writeAtMost(b, off, len);
off += n;
len -= n;
this.dirty_ = true;
}
}
/*
* Write at most "len" bytes to "b" starting at position "off", and return
* the number of bytes written.
*/
private int writeAtMost(byte[] b, int off, int len) throws IOException {
if (this.curr_ >= this.hi_) {
if (this.hitEOF_ && this.hi_ < this.maxHi_) {
// at EOF -- bump "hi"
this.hi_ = this.maxHi_;
} else {
// slow path -- write current buffer; read next one
this.seek(this.curr_);
if (this.curr_ == this.hi_) {
// appending to EOF -- bump "hi"
this.hi_ = this.maxHi_;
}
}
}
len = Math.min(len, (int) (this.hi_ - this.curr_));
int buffOff = (int) (this.curr_ - this.lo_);
System.arraycopy(b, off, this.buff_, buffOff, len);
this.curr_ += len;
return len;
}
}

Datei anzeigen

@ -2,13 +2,18 @@ package com.fastasyncworldedit.core.jnbt.streamer;
import com.sk89q.jnbt.NBTConstants; import com.sk89q.jnbt.NBTConstants;
import com.sk89q.jnbt.NBTInputStream; import com.sk89q.jnbt.NBTInputStream;
import com.sk89q.jnbt.Tag;
import com.sk89q.worldedit.internal.util.LogManagerCompat; import com.sk89q.worldedit.internal.util.LogManagerCompat;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import javax.annotation.Nullable;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
@SuppressWarnings({"unchecked", "rawtypes"}) @SuppressWarnings({"unchecked", "rawtypes", "removal"})
public class StreamDelegate { public class StreamDelegate {
private static final Logger LOGGER = LogManagerCompat.getLogger(); private static final Logger LOGGER = LogManagerCompat.getLogger();
@ -16,6 +21,8 @@ public class StreamDelegate {
private static final byte[][] ZERO_KEYS = new byte[0][]; private static final byte[][] ZERO_KEYS = new byte[0][];
private static final StreamDelegate[] ZERO_VALUES = new StreamDelegate[0]; private static final StreamDelegate[] ZERO_VALUES = new StreamDelegate[0];
private Map<String, Tag> retained = null;
private byte[] buffer; private byte[] buffer;
private byte[][] keys; private byte[][] keys;
private StreamDelegate[] values; private StreamDelegate[] values;
@ -25,25 +32,36 @@ public class StreamDelegate {
private InfoReader infoReader; private InfoReader infoReader;
private ValueReader valueReader; private ValueReader valueReader;
private String retainedName = null;
private String currentName = null;
/**
* Used to read a streamed {@link NBTInputStream}
*/
public StreamDelegate() { public StreamDelegate() {
keys = ZERO_KEYS; keys = ZERO_KEYS;
values = ZERO_VALUES; values = ZERO_VALUES;
} }
/**
* Set that keys not added to this StreamDelegate instance should still be retained alongside their value retained. They can
* be accessed via {@link StreamDelegate#getRetained}
*/
public StreamDelegate retainOthers() {
retained = new LinkedHashMap<>();
return this;
}
public StreamDelegate addAndGetParent(String name) { public StreamDelegate addAndGetParent(String name) {
add(name); add(name);
return this; return this;
} }
public StreamDelegate add() { public StreamDelegate add(@Nullable String name) {
return add("");
}
public StreamDelegate add(String name) {
return add(name, new StreamDelegate()); return add(name, new StreamDelegate());
} }
private StreamDelegate add(String name, StreamDelegate scope) { private StreamDelegate add(@Nullable String name, StreamDelegate scope) {
if (valueReader != null) { if (valueReader != null) {
LOGGER.warn( LOGGER.warn(
"Scope {} | {} may not run, as the stream is only read once, and a value reader is already set", "Scope {} | {} may not run, as the stream is only read once, and a value reader is already set",
@ -51,7 +69,7 @@ public class StreamDelegate {
scope scope
); );
} }
byte[] bytes = name.getBytes(NBTConstants.CHARSET); byte[] bytes = name == null ? new byte[0] : name.getBytes(NBTConstants.CHARSET);
int maxSize = bytes.length; int maxSize = bytes.length;
byte[][] tmpKeys = new byte[keys.length + 1][]; byte[][] tmpKeys = new byte[keys.length + 1][];
@ -96,10 +114,12 @@ public class StreamDelegate {
public StreamDelegate get(DataInputStream is) throws IOException { public StreamDelegate get(DataInputStream is) throws IOException {
int nameLength = is.readShort() & 0xFFFF; int nameLength = is.readShort() & 0xFFFF;
if (nameLength == 0 && keys.length > 0 && keys[0].length == 0) { if (nameLength == 0 && keys.length > 0 && keys[0].length == 0) {
currentName = "";
retainedName = null;
return values[0]; return values[0];
} }
if (nameLength > buffer.length) { if (nameLength > buffer.length) {
is.skipBytes(nameLength); setRetained(is, nameLength);
return null; return null;
} }
int index = 0; int index = 0;
@ -139,34 +159,59 @@ public class StreamDelegate {
continue middle; continue middle;
} }
} }
currentName = new String(key);
retainedName = null;
return values[i]; return values[i];
} }
currentName = null;
retainedName = new String(Arrays.copyOf(buffer, nameLength), NBTConstants.CHARSET);
return null; return null;
} }
} }
// fall through
} }
case 1: { case 1: {
byte[] key = keys[index]; byte[] key = keys[index];
if (key.length == nameLength) { if (key.length == nameLength) {
int i = 0; int i = 0;
boolean retain = false;
for (; nameLength > 0; nameLength--, i++) { for (; nameLength > 0; nameLength--, i++) {
byte b = is.readByte(); byte b = is.readByte();
buffer[i] = b; buffer[i] = b;
if (b != key[i]) { if (!retain && b != key[i]) {
if (retained == null) {
nameLength--; nameLength--;
break outer; break outer;
} }
retain = true;
} }
}
if (!retain) {
currentName = new String(key);
retainedName = null;
return values[index]; return values[index];
} }
retainedName = new String(Arrays.copyOf(buffer, i), NBTConstants.CHARSET);
return null;
}
break; break;
} }
} }
is.skipBytes(nameLength); setRetained(is, nameLength);
return null; return null;
} }
private void setRetained(DataInputStream is, int nameLength) throws IOException {
if (retained == null) {
is.skipBytes(nameLength);
} else {
byte[] nameBytes = new byte[nameLength];
is.readFully(nameBytes);
retainedName = new String(nameBytes, NBTConstants.CHARSET);
}
currentName = null;
}
public StreamDelegate withLong(LongValueReader valueReader) { public StreamDelegate withLong(LongValueReader valueReader) {
return withElem(valueReader); return withElem(valueReader);
} }
@ -220,6 +265,19 @@ public class StreamDelegate {
return elemReader; return elemReader;
} }
@Nullable
public Map<String, Tag> getRetained() {
return retained;
}
public void retain(Tag tag) {
if (retainedName == null) {
throw new IllegalStateException("Retained name null?!");
}
retained.put(retainedName, tag);
retainedName = null;
}
public void acceptInfo(int length, int type) throws IOException { public void acceptInfo(int length, int type) throws IOException {
if (infoReader != null) { if (infoReader != null) {
infoReader.apply(length, type); infoReader.apply(length, type);
@ -234,4 +292,12 @@ public class StreamDelegate {
return false; return false;
} }
public String getCurrentName() {
return currentName;
}
public String getRetainedName() {
return retainedName;
}
} }

Datei anzeigen

@ -1,11 +1,12 @@
package com.fastasyncworldedit.core.math; package com.fastasyncworldedit.core.math;
import java.math.BigInteger;
import java.util.Arrays; import java.util.Arrays;
public class FastBitSet { public class FastBitSet {
private final int size; private int size;
private final long[] bits; private long[] bits;
public FastBitSet(int size) { public FastBitSet(int size) {
this.size = size; this.size = size;
@ -93,6 +94,28 @@ public class FastBitSet {
} }
} }
public void expandTo(int newSize, boolean value) {
//System.out.println(newSize);
int newLength = (newSize + 64) >> 6;
if (newLength <= this.bits.length) {
if (this.size > newSize) {
this.size = newSize;
}
return;
}
long[] tmp = new long[newLength];
if (value) {
Arrays.fill(tmp, -1L);
}
System.arraycopy(bits, 0, tmp, 0, bits.length);
this.bits = tmp;
this.size = newSize;
}
public void setAll() {
setAll(bits);
}
public boolean get(final int i) { public boolean get(final int i) {
return (bits[i >> 6] & (1L << (i & 0x3F))) != 0; return (bits[i >> 6] & (1L << (i & 0x3F))) != 0;
} }
@ -197,4 +220,16 @@ public class FastBitSet {
} }
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < this.bits.length; i++) {
String bits = new StringBuilder(String.format("%064d", new BigInteger(Long.toBinaryString(this.bits[i]))))
.reverse()
.toString();
builder.append(i * 64).append(":").append(bits).append(" ");
}
return builder.toString();
}
} }

Datei anzeigen

@ -8,6 +8,7 @@ import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.biome.BiomeType;
import com.sk89q.worldedit.world.block.BaseBlock; import com.sk89q.worldedit.world.block.BaseBlock;
import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.world.storage.InvalidFormatException;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.UUID; import java.util.UUID;

Datei anzeigen

@ -173,4 +173,6 @@ public interface IQueueExtent<T extends IChunk> extends Flushable, Trimable, ICh
return filter; return filter;
} }
void addFlushTask(Runnable task);
} }

Datei anzeigen

@ -0,0 +1,279 @@
package com.fastasyncworldedit.core.queue.implementation;
import com.fastasyncworldedit.core.Fawe;
import com.fastasyncworldedit.core.FaweCache;
import com.fastasyncworldedit.core.anvil.MCAWorld;
import com.fastasyncworldedit.core.configuration.Settings;
import com.fastasyncworldedit.core.extent.NullExtent;
import com.fastasyncworldedit.core.extent.PassthroughExtent;
import com.fastasyncworldedit.core.extent.clipboard.WorldCopyClipboard;
import com.fastasyncworldedit.core.extent.filter.CountFilter;
import com.fastasyncworldedit.core.extent.filter.DistrFilter;
import com.fastasyncworldedit.core.extent.filter.LinkedFilter;
import com.fastasyncworldedit.core.extent.filter.block.ChunkFilterBlock;
import com.fastasyncworldedit.core.extent.processor.BatchProcessorHolder;
import com.fastasyncworldedit.core.extent.processor.MultiBatchProcessor;
import com.fastasyncworldedit.core.function.mask.BlockMaskBuilder;
import com.fastasyncworldedit.core.internal.exception.FaweException;
import com.fastasyncworldedit.core.queue.Filter;
import com.fastasyncworldedit.core.queue.IQueueChunk;
import com.fastasyncworldedit.core.queue.IQueueExtent;
import com.sk89q.worldedit.MaxChangedBlocksException;
import com.sk89q.worldedit.extent.clipboard.Clipboard;
import com.sk89q.worldedit.function.mask.BlockMask;
import com.sk89q.worldedit.function.mask.ExistingBlockMask;
import com.sk89q.worldedit.function.mask.Mask;
import com.sk89q.worldedit.function.pattern.BlockPattern;
import com.sk89q.worldedit.function.pattern.Pattern;
import com.sk89q.worldedit.internal.util.LogManagerCompat;
import com.sk89q.worldedit.math.BlockVector2;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.util.Countable;
import com.sk89q.worldedit.world.block.BaseBlock;
import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.world.block.BlockStateHolder;
import com.sk89q.worldedit.world.block.BlockType;
import org.apache.logging.log4j.Logger;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.IntStream;
public class ParallelAnvilQueueExtent extends PassthroughExtent {
private static final Logger LOGGER = LogManagerCompat.getLogger();
private final MCAWorld world;
private final QueueHandler handler;
private final BatchProcessorHolder processor;
private final BatchProcessorHolder postProcessor;
// Array for lazy avoidance of concurrent modification exceptions and needless overcomplication of code (synchronisation is
// not very important)
private final boolean[] faweExceptionReasonsUsed = new boolean[FaweException.Type.values().length];
private final boolean fastmode;
private int changes;
private int lastException = Integer.MIN_VALUE;
private int exceptionCount = 0;
public ParallelAnvilQueueExtent(QueueHandler handler, MCAWorld world, boolean fastmode) {
super(handler.getQueue(world, new BatchProcessorHolder(), new BatchProcessorHolder()));
this.world = world;
this.handler = handler;
this.processor = (BatchProcessorHolder) getExtent().getProcessor();
if (this.processor.getProcessor() instanceof MultiBatchProcessor) {
((MultiBatchProcessor) this.processor.getProcessor()).setFaweExceptionArray(faweExceptionReasonsUsed);
}
this.postProcessor = (BatchProcessorHolder) getExtent().getPostProcessor();
if (this.postProcessor.getProcessor() instanceof MultiBatchProcessor) {
((MultiBatchProcessor) this.postProcessor.getProcessor()).setFaweExceptionArray(faweExceptionReasonsUsed);
}
this.fastmode = fastmode;
}
@Override
@SuppressWarnings({"unchecked", "rawtypes"})
public IQueueExtent<IQueueChunk> getExtent() {
return (IQueueExtent<IQueueChunk>) super.getExtent();
}
@Override
public boolean cancel() {
if (super.cancel()) {
processor.setProcessor(new NullExtent(this, FaweCache.MANUAL));
postProcessor.setPostProcessor(new NullExtent(this, FaweCache.MANUAL));
return true;
}
return false;
}
@SuppressWarnings("rawtypes")
private IQueueExtent<IQueueChunk> getNewQueue() {
return handler.getMCAQueue(world, this.processor, this.postProcessor);
}
@Override
@SuppressWarnings("rawtypes")
public <T extends Filter> T apply(Region region, T filter, boolean full) {
// The chunks positions to iterate over
final Set<BlockVector2> chunks = region.getChunks();
final Iterator<BlockVector2> chunksIter = chunks.iterator();
// Get a pool, to operate on the chunks in parallel
final int size = Math.min(chunks.size(), Settings.settings().QUEUE.PARALLEL_THREADS);
if (size <= 1 && chunksIter.hasNext()) {
BlockVector2 pos = chunksIter.next();
getExtent().apply(null, filter, region, pos.getX(), pos.getZ(), full);
} else {
final ForkJoinTask[] tasks = IntStream.range(0, size).mapToObj(i -> handler.submit(() -> {
try {
final Filter newFilter = filter.fork();
// Create a chunk that we will reuse/reset for each operation
final SingleThreadQueueExtent queue = (SingleThreadQueueExtent) getNewQueue();
queue.setFastMode(fastmode);
queue.setFaweExceptionArray(faweExceptionReasonsUsed);
synchronized (queue) {
try {
ChunkFilterBlock block = null;
while (true) {
// Get the next chunk posWeakChunk
final int chunkX;
final int chunkZ;
synchronized (chunksIter) {
if (!chunksIter.hasNext()) {
break;
}
final BlockVector2 pos = chunksIter.next();
chunkX = pos.getX();
chunkZ = pos.getZ();
}
block = queue.apply(block, newFilter, region, chunkX, chunkZ, full);
}
queue.flush();
} catch (Throwable t) {
if (t instanceof FaweException) {
Fawe.handleFaweException(faweExceptionReasonsUsed, (FaweException) t, LOGGER);
} else if (t.getCause() instanceof FaweException) {
Fawe.handleFaweException(faweExceptionReasonsUsed, (FaweException) t.getCause(), LOGGER);
} else {
throw t;
}
}
}
} catch (Throwable e) {
String message = e.getMessage();
int hash = message != null ? message.hashCode() : 0;
if (lastException != hash) {
lastException = hash;
exceptionCount = 0;
LOGGER.catching(e);
} else if (exceptionCount < Settings.settings().QUEUE.PARALLEL_THREADS) {
exceptionCount++;
LOGGER.warn(message);
}
}
})).toArray(ForkJoinTask[]::new);
// Join filters
for (ForkJoinTask task : tasks) {
if (task != null) {
task.quietlyJoin();
}
}
filter.join();
}
return filter;
}
@Override
public int countBlocks(Region region, Mask searchMask) {
return
// Apply a filter over a region
apply(region, searchMask
.toFilter(new CountFilter()), searchMask.replacesAir()) // Adapt the mask to a filter which counts
.getParent() // Get the counter of this mask
.getTotal(); // Get the total from the counter
}
@Override
public <B extends BlockStateHolder<B>> int setBlocks(Region region, B block) throws MaxChangedBlocksException {
Mask mask = new BlockMaskBuilder().add(block).build(this).inverse();
return this.changes = apply(region, mask.toFilter(block), mask.replacesAir())
.getBlocksApplied();
}
@Override
public int setBlocks(Region region, Pattern pattern) throws MaxChangedBlocksException {
return this.changes = apply(region, new LinkedFilter<>(pattern, new CountFilter()), true).getChild().getTotal();
}
@Override
public int setBlocks(Set<BlockVector3> vset, Pattern pattern) {
if (vset instanceof Region) {
this.changes = setBlocks((Region) vset, pattern);
return this.changes;
}
// TODO optimize parallel
for (BlockVector3 blockVector3 : vset) {
if (pattern.apply(this, blockVector3, blockVector3)) {
this.changes++;
}
}
return this.changes;
}
@Override
public int replaceBlocks(Region region, Mask mask, Pattern pattern)
throws MaxChangedBlocksException {
boolean full = mask.replacesAir();
return this.changes = apply(region, mask.toFilter(pattern), full).getBlocksApplied();
}
@Override
public List<Countable<BlockState>> getBlockDistributionWithData(Region region) {
return apply(region, new DistrFilter(), true).getDistribution();
}
@Override
public List<Countable<BlockType>> getBlockDistribution(Region region) {
return apply(region, new DistrFilter(), true).getTypeDistribution();
}
/**
* Lazily copy a region
*/
@Override
public Clipboard lazyCopy(Region region) {
Clipboard clipboard = new WorldCopyClipboard(() -> this, region);
clipboard.setOrigin(region.getMinimumPoint());
return clipboard;
}
/**
* Count the number of blocks of a list of types in a region.
*
* @param region the region
* @param searchBlocks the list of blocks to search
* @return the number of blocks that matched the block
*/
@Override
public int countBlocks(Region region, Set<BaseBlock> searchBlocks) {
Mask mask = new BlockMask(this, searchBlocks);
return countBlocks(region, mask);
}
/**
* Replaces all the blocks matching a given filter, within a given region, to a block
* returned by a given pattern.
*
* @param region the region to replace the blocks within
* @param filter a list of block types to match, or null to use {@link ExistingBlockMask}
* @param replacement the replacement block
* @return number of blocks affected
* @throws MaxChangedBlocksException thrown if too many blocks are changed
*/
@Override
public <B extends BlockStateHolder<B>> int replaceBlocks(Region region, Set<BaseBlock> filter, B replacement) throws
MaxChangedBlocksException {
return replaceBlocks(region, filter, new BlockPattern(replacement));
}
/**
* Replaces all the blocks matching a given filter, within a given region, to a block
* returned by a given pattern.
*
* @param region the region to replace the blocks within
* @param filter a list of block types to match, or null to use {@link ExistingBlockMask}
* @param pattern the pattern that provides the new blocks
* @return number of blocks affected
* @throws MaxChangedBlocksException thrown if too many blocks are changed
*/
@Override
public int replaceBlocks(Region region, Set<BaseBlock> filter, Pattern pattern) throws MaxChangedBlocksException {
Mask mask = filter == null ? new ExistingBlockMask(this) : new BlockMask(this, filter);
return replaceBlocks(region, mask, pattern);
}
}

Datei anzeigen

@ -10,6 +10,7 @@ import com.fastasyncworldedit.core.queue.IChunkSet;
import com.fastasyncworldedit.core.queue.IQueueChunk; import com.fastasyncworldedit.core.queue.IQueueChunk;
import com.fastasyncworldedit.core.queue.IQueueExtent; import com.fastasyncworldedit.core.queue.IQueueExtent;
import com.fastasyncworldedit.core.queue.Trimable; import com.fastasyncworldedit.core.queue.Trimable;
import com.fastasyncworldedit.core.anvil.MCAWorld;
import com.fastasyncworldedit.core.queue.implementation.chunk.ChunkCache; import com.fastasyncworldedit.core.queue.implementation.chunk.ChunkCache;
import com.fastasyncworldedit.core.util.MemUtil; import com.fastasyncworldedit.core.util.MemUtil;
import com.fastasyncworldedit.core.util.TaskManager; import com.fastasyncworldedit.core.util.TaskManager;
@ -400,6 +401,26 @@ public abstract class QueueHandler implements Trimable, Runnable {
} }
} }
/**
* Get or create the MCA WorldChunkCache for a world
*/
public IChunkCache<IChunkGet> getOrCreateMCAWorldCache(World world) {
world = WorldWrapper.unwrap(world);
synchronized (chunkGetCache) {
final WeakReference<IChunkCache<IChunkGet>> ref = chunkGetCache.get(world);
if (ref != null) {
final IChunkCache<IChunkGet> cached = ref.get();
if (cached != null) {
return cached;
}
}
final IChunkCache<IChunkGet> created = new ChunkCache<>(world);
chunkGetCache.put(world, new WeakReference<>(created));
return created;
}
}
public IQueueExtent<IQueueChunk> create() { public IQueueExtent<IQueueChunk> create() {
return new SingleThreadQueueExtent(); return new SingleThreadQueueExtent();
} }
@ -456,6 +477,24 @@ public abstract class QueueHandler implements Trimable, Runnable {
*/ */
public abstract void endUnsafe(boolean parallel); public abstract void endUnsafe(boolean parallel);
public IQueueExtent<IQueueChunk> getMCAQueue(MCAWorld world) {
return getMCAQueue(world, null, null);
}
public IQueueExtent<IQueueChunk> getMCAQueue(MCAWorld world, IBatchProcessor processor, IBatchProcessor postProcessor) {
final IQueueExtent<IQueueChunk> queue = pool();
IChunkCache<IChunkGet> cache = getOrCreateMCAWorldCache(world);
queue.init(world, cache, null);
if (processor != null) {
queue.setProcessor(processor);
}
if (postProcessor != null) {
queue.setPostProcessor(postProcessor);
}
queue.addFlushTask(world::flush);
return queue;
}
/** /**
* Create a new queue for a given world. * Create a new queue for a given world.
*/ */

Datei anzeigen

@ -31,6 +31,8 @@ import com.sk89q.worldedit.world.World;
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future; import java.util.concurrent.Future;
@ -68,6 +70,7 @@ public final class SingleThreadQueueExtent extends ExtentBatchProcessorHolder im
private boolean[] faweExceptionReasonsUsed = new boolean[FaweException.Type.values().length]; private boolean[] faweExceptionReasonsUsed = new boolean[FaweException.Type.values().length];
private int lastException = Integer.MIN_VALUE; private int lastException = Integer.MIN_VALUE;
private int exceptionCount = 0; private int exceptionCount = 0;
private List<Runnable> flushTasks = null;
public SingleThreadQueueExtent() { public SingleThreadQueueExtent() {
} }
@ -198,6 +201,14 @@ public final class SingleThreadQueueExtent extends ExtentBatchProcessorHolder im
return chunks.isEmpty() && submissions.isEmpty(); return chunks.isEmpty() && submissions.isEmpty();
} }
@Override
public void addFlushTask(final Runnable task) {
if (flushTasks == null) {
flushTasks = new ArrayList<>();
}
flushTasks.add(task);
}
@Override @Override
public <V extends Future<V>> V submit(IQueueChunk chunk) { public <V extends Future<V>> V submit(IQueueChunk chunk) {
if (lastChunk == chunk) { if (lastChunk == chunk) {
@ -470,6 +481,11 @@ public final class SingleThreadQueueExtent extends ExtentBatchProcessorHolder im
getChunkLock.unlock(); getChunkLock.unlock();
} }
pollSubmissions(0, true); pollSubmissions(0, true);
if (flushTasks != null) {
for (Runnable r : flushTasks) {
r.run();
}
}
} }
@Override @Override

Datei anzeigen

@ -440,7 +440,7 @@ public class ThreadUnsafeCharBlocks implements IChunkSet, IBlocks {
@Override @Override
public boolean hasBiomes(int layer) { public boolean hasBiomes(int layer) {
layer -= minSectionPosition; layer -= minSectionPosition;
return layer >= 0 && layer < biomes.length && biomes[layer] != null && biomes[layer].length > 0; return layer >= 0 && biomes != null && layer < biomes.length && biomes[layer] != null && biomes[layer].length > 0;
} }
@Override @Override

Datei anzeigen

@ -0,0 +1,281 @@
package com.fastasyncworldedit.core.regions;
import com.fastasyncworldedit.core.anvil.MCAFile;
import com.fastasyncworldedit.core.anvil.MCAWorld;
import com.fastasyncworldedit.core.math.MutableBlockVector3;
import com.fastasyncworldedit.core.util.task.RunnableVal4;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.extension.platform.Capability;
import com.sk89q.worldedit.math.BlockVector2;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.regions.RegionOperationException;
import com.sk89q.worldedit.world.World;
import org.jetbrains.annotations.Nullable;
import javax.annotation.Nonnull;
import java.nio.file.Path;
import java.util.AbstractSet;
import java.util.ArrayDeque;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Queue;
import java.util.Set;
public class WorldRegionsRegion implements Region {
private final MCAWorld world;
private final BlockVector3 min;
private final BlockVector3 max;
private Set<BlockVector2> chunks = null;
public WorldRegionsRegion(@Nonnull final MCAWorld world) {
this.world = Objects.requireNonNull(world);
List<Path> regions = world.getRegionFileFiles();
int minX = Integer.MAX_VALUE;
int minZ = Integer.MAX_VALUE;
int maxX = Integer.MIN_VALUE;
int maxZ = Integer.MIN_VALUE;
for (Path p : regions) {
String[] split = p.getFileName().toString().split("\\.");
int x = Integer.parseInt(split[1]);
int z = Integer.parseInt(split[2]);
minX = Math.min(x, minX);
minZ = Math.min(z, minZ);
maxX = Math.max(x, maxX);
maxZ = Math.max(z, maxZ);
}
this.min = BlockVector3.at(
minX,
WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.WORLD_EDITING).versionMinY(),
minZ
);
this.max = BlockVector3.at(
maxX,
WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.WORLD_EDITING).versionMaxY(),
maxZ
);
}
@Override
public Iterator<BlockVector3> iterator() {
Queue<BlockVector2> queue = new ArrayDeque<>(getChunks());
return new Iterator<>() {
private final int by = min.getY();
private final int ty = max.getY();
private final MutableBlockVector3 mutable = new MutableBlockVector3();
private BlockVector2 chunk = queue.poll();
private int cx = chunk.getX() << 4;
private int cz = chunk.getZ() << 4;
private int x;
private int y;
private int z;
@Override
public boolean hasNext() {
return x < 15 || y < ty || z < 15 || queue.peek() != null;
}
@Override
public MutableBlockVector3 next() {
int curX = x;
int curY = y;
int curZ = z;
if (++x > 15) {
if (++z > 15) {
if (++y > ty) {
if (!hasNext()) {
throw new NoSuchElementException("End of iterator") {
@Override
public Throwable fillInStackTrace() {
return this;
}
};
}
chunk = queue.poll();
x = 0;
y = by;
z = 0;
cx = chunk.getX() << 4;
cz = chunk.getZ() << 4;
return mutable.setComponents(cx + x, y, cz + z);
} else {
x = 0;
z = 0;
}
} else {
x = 0;
}
}
return mutable.setComponents(cx + curX, curY, cz + curZ);
}
};
}
@Override
public void setWorld(final World world) {
throw new UnsupportedOperationException("Cannot modify WorldRegionsRegion - Immutable");
}
@Override
public Region clone() {
return new WorldRegionsRegion(world);
}
@Override
public List<BlockVector2> polygonize(final int maxPoints) {
return null;
}
@Override
public void shift(final BlockVector3 change) throws RegionOperationException {
throw new UnsupportedOperationException("Cannot modify WorldRegionsRegion - Immutable");
}
@Override
public BlockVector3 getMinimumPoint() {
return min;
}
@Override
public BlockVector3 getMaximumPoint() {
return max;
}
/**
* Get X-size.
*
* @return width
*/
@Override
public int getWidth() {
BlockVector3 min = getMinimumPoint();
BlockVector3 max = getMaximumPoint();
return max.getX() - min.getX() + 1;
}
/**
* Get Y-size.
*
* @return height
*/
@Override
public int getHeight() {
BlockVector3 min = getMinimumPoint();
BlockVector3 max = getMaximumPoint();
return max.getY() - min.getY() + 1;
}
/**
* Get Z-size.
*
* @return length
*/
@Override
public int getLength() {
BlockVector3 min = getMinimumPoint();
BlockVector3 max = getMaximumPoint();
return max.getZ() - min.getZ() + 1;
}
@Override
public void expand(final BlockVector3... changes) throws RegionOperationException {
throw new UnsupportedOperationException("Cannot modify WorldRegionsRegion - Immutable");
}
@Override
public void contract(final BlockVector3... changes) throws RegionOperationException {
throw new UnsupportedOperationException("Cannot modify WorldRegionsRegion - Immutable");
}
@Override
public boolean contains(final BlockVector3 position) {
return false;
}
@Override
public Set<BlockVector2> getChunks() {
if (chunks == null) {
synchronized (this) {
if (chunks != null) {
return chunks;
}
Set<BlockVector2> tmp = new HashSet<>();
for (MCAFile mca : world.getMCAs()) {
mca.forEachChunk(new RunnableVal4<>() {
@Override
public void run(Integer x, Integer z, Integer offset, Integer size) {
if (offset != 0 && size > 0) {
tmp.add(BlockVector2.at(x, z));
}
}
});
}
chunks = tmp;
}
}
return chunks;
}
@Override
public Set<BlockVector3> getChunkCubes() {
return new AbstractSet<>() {
@Override
public Iterator<BlockVector3> iterator() {
Queue<BlockVector2> chunks = new ArrayDeque<>(getChunks());
return new Iterator<>() {
private final MutableBlockVector3 mutable = new MutableBlockVector3();
private final int by = min.getY() >> 4;
private final int ty = max.getY() >> 4;
private BlockVector2 chunk = chunks.poll();
private int y;
@Override
public boolean hasNext() {
return y < ty || chunks.peek() != null;
}
@Override
public BlockVector3 next() {
int curY = y;
if (++y > ty) {
if (!hasNext()) {
throw new NoSuchElementException("End of iterator") {
@Override
public Throwable fillInStackTrace() {
return this;
}
};
}
y = by;
chunk = chunks.poll();
return mutable.setComponents(chunk.getX(), y, chunk.getZ());
}
return mutable.setComponents(chunk.getX(), curY, chunk.getZ());
}
};
}
@Override
public int size() {
return getChunks().size() * ((max.getY() >> 4) - (min.getY() >> 4));
}
};
}
@Nullable
@Override
public World getWorld() {
return null;
}
}

Datei anzeigen

@ -63,6 +63,7 @@ public final class NBTInputStream implements Closeable {
this.is = new DataInputStream(is); this.is = new DataInputStream(is);
} }
//FAWE start
public NBTInputStream(DataInputStream dis) { public NBTInputStream(DataInputStream dis) {
this.is = dis; this.is = dis;
} }
@ -75,12 +76,14 @@ public final class NBTInputStream implements Closeable {
is.reset(); is.reset();
} }
//FAWE end
/** /**
* Reads an NBT tag from the stream. * Reads an NBT tag from the stream.
* *
* @return The tag that was read. * @return The tag that was read.
*/ */
public NamedTag readNamedTag() throws IOException { public NamedTag readNamedTag() throws IOException {
//FAWE start
return readNamedTag(0); return readNamedTag(0);
} }
@ -101,7 +104,7 @@ public final class NBTInputStream implements Closeable {
return readTagPayload(type, 0); return readTagPayload(type, 0);
} }
public void readNamedTagLazy(StreamDelegate scope) throws IOException { public void readNamedTagLazy(StreamDelegate scope) {
try { try {
int type = is.readByte(); int type = is.readByte();
if (type == NBTConstants.TYPE_END) { if (type == NBTConstants.TYPE_END) {
@ -112,19 +115,30 @@ public final class NBTInputStream implements Closeable {
if (child != null) { if (child != null) {
child.acceptRoot(this, type, 0); child.acceptRoot(this, type, 0);
} else { } else {
readTagPayloadLazy(type, 0); readTagPayloadLazy(type, 0, scope, scope.getRetained() != null);
} }
} catch (Throwable e) { } catch (Throwable e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
public void readNamedTagLazyExceptionally(StreamDelegate scope) throws IOException {
int type = is.readByte();
if (type == NBTConstants.TYPE_END) {
return;
}
StreamDelegate child = scope.get(is);
if (child != null) {
child.acceptRoot(this, type, 0);
} else {
readTagPayloadLazy(type, 0, scope, scope.getRetained() != null);
}
}
public String readNamedTagName(int type) throws IOException { public String readNamedTagName(int type) throws IOException {
if (type != NBTConstants.TYPE_END) { if (type != NBTConstants.TYPE_END) {
int nameLength = is.readShort() & 0xFFFF; return is.readUTF();
byte[] nameBytes = new byte[nameLength];
is.readFully(nameBytes);
return new String(nameBytes, NBTConstants.CHARSET);
} else { } else {
return ""; return "";
} }
@ -133,43 +147,70 @@ public final class NBTInputStream implements Closeable {
private byte[] buf; private byte[] buf;
public void readTagPayloadLazy(int type, int depth) throws IOException { public void readTagPayloadLazy(int type, int depth) throws IOException {
readTagPayloadLazy(type, depth, null, false);
}
public void readTagPayloadLazy(int type, int depth, StreamDelegate scope, boolean retain) throws IOException {
int length;
switch (type) { switch (type) {
case NBTConstants.TYPE_END: case NBTConstants.TYPE_END -> {
return; }
case NBTConstants.TYPE_BYTE: case NBTConstants.TYPE_BYTE -> {
if (!retain) {
is.skipBytes(1); is.skipBytes(1);
return; } else {
case NBTConstants.TYPE_SHORT: scope.retain(readTagPayload(type, depth));
}
}
case NBTConstants.TYPE_SHORT -> {
if (!retain) {
is.skipBytes(2); is.skipBytes(2);
return; } else {
case NBTConstants.TYPE_INT: scope.retain(readTagPayload(type, depth));
}
}
case NBTConstants.TYPE_INT, NBTConstants.TYPE_FLOAT -> {
if (!retain) {
is.skipBytes(4); is.skipBytes(4);
return; } else {
case NBTConstants.TYPE_LONG: scope.retain(readTagPayload(type, depth));
}
}
case NBTConstants.TYPE_LONG, NBTConstants.TYPE_DOUBLE -> {
if (!retain) {
is.skipBytes(8); is.skipBytes(8);
return; } else {
case NBTConstants.TYPE_FLOAT: scope.retain(readTagPayload(type, depth));
is.skipBytes(4); }
return; }
case NBTConstants.TYPE_DOUBLE: case NBTConstants.TYPE_STRING -> {
is.skipBytes(8); length = is.readShort() & 0xFFFF;
return; if (!retain) {
case NBTConstants.TYPE_STRING:
int length = is.readShort() & 0xFFFF;
is.skipBytes(length); is.skipBytes(length);
return; } else {
case NBTConstants.TYPE_BYTE_ARRAY: scope.retain(readTagPayload(type, depth));
}
}
case NBTConstants.TYPE_BYTE_ARRAY -> {
if (!retain) {
is.skipBytes(is.readInt()); is.skipBytes(is.readInt());
return; } else {
case NBTConstants.TYPE_LIST: { scope.retain(readTagPayload(type, depth));
}
}
case NBTConstants.TYPE_LIST -> {
if (!retain) {
int childType = is.readByte(); int childType = is.readByte();
length = is.readInt(); length = is.readInt();
for (int i = 0; i < length; ++i) { for (int i = 0; i < length; ++i) {
readTagPayloadLazy(childType, depth + 1); readTagPayloadLazy(childType, depth + 1, scope, retain);
} }
return; } else {
scope.retain(readTagPayload(type, depth));
} }
case NBTConstants.TYPE_COMPOUND: { }
case NBTConstants.TYPE_COMPOUND -> {
if (!retain) {
// readDataPayload // readDataPayload
depth++; depth++;
while (true) { while (true) {
@ -178,27 +219,35 @@ public final class NBTInputStream implements Closeable {
return; return;
} }
is.skipBytes(is.readShort() & 0xFFFF); is.skipBytes(is.readShort() & 0xFFFF);
readTagPayloadLazy(childType, depth + 1); readTagPayloadLazy(childType, depth + 1, scope, retain);
}
} else {
scope.retain(readTagPayload(type, depth));
} }
} }
case NBTConstants.TYPE_INT_ARRAY: { case NBTConstants.TYPE_INT_ARRAY -> {
if (!retain) {
is.skipBytes(is.readInt() << 2); is.skipBytes(is.readInt() << 2);
return; } else {
scope.retain(readTagPayload(type, depth));
} }
case NBTConstants.TYPE_LONG_ARRAY: { }
case NBTConstants.TYPE_LONG_ARRAY -> {
if (!retain) {
is.skipBytes(is.readInt() << 3); is.skipBytes(is.readInt() << 3);
return; } else {
scope.retain(readTagPayload(type, depth));
} }
default: }
throw new IOException("Invalid tag type: " + type + "."); default -> throw new IOException("Invalid tag type: " + type + ".");
} }
} }
public void readTagPayloadLazy(int type, int depth, StreamDelegate scope) throws IOException { public void readTagPayloadLazy(int type, int depth, StreamDelegate scope) throws IOException {
switch (type) { switch (type) {
case NBTConstants.TYPE_END: case NBTConstants.TYPE_END -> {
return; }
case NBTConstants.TYPE_BYTE: { case NBTConstants.TYPE_BYTE -> {
ValueReader value = scope.getValueReader(); ValueReader value = scope.getValueReader();
if (value == null) { if (value == null) {
value = scope.getElemReader(); value = scope.getElemReader();
@ -208,9 +257,8 @@ public final class NBTInputStream implements Closeable {
} else { } else {
is.skipBytes(1); is.skipBytes(1);
} }
return;
} }
case NBTConstants.TYPE_SHORT: { case NBTConstants.TYPE_SHORT -> {
ValueReader value = scope.getValueReader(); ValueReader value = scope.getValueReader();
if (value == null) { if (value == null) {
value = scope.getElemReader(); value = scope.getElemReader();
@ -220,9 +268,8 @@ public final class NBTInputStream implements Closeable {
} else { } else {
is.skipBytes(2); is.skipBytes(2);
} }
return;
} }
case NBTConstants.TYPE_INT: { case NBTConstants.TYPE_INT -> {
ValueReader value = scope.getValueReader(); ValueReader value = scope.getValueReader();
if (value == null) { if (value == null) {
value = scope.getElemReader(); value = scope.getElemReader();
@ -232,9 +279,8 @@ public final class NBTInputStream implements Closeable {
} else { } else {
is.skipBytes(4); is.skipBytes(4);
} }
return;
} }
case NBTConstants.TYPE_LONG: { case NBTConstants.TYPE_LONG -> {
ValueReader value = scope.getValueReader(); ValueReader value = scope.getValueReader();
if (value == null) { if (value == null) {
value = scope.getElemReader(); value = scope.getElemReader();
@ -244,9 +290,8 @@ public final class NBTInputStream implements Closeable {
} else { } else {
is.skipBytes(8); is.skipBytes(8);
} }
return;
} }
case NBTConstants.TYPE_FLOAT: { case NBTConstants.TYPE_FLOAT -> {
ValueReader value = scope.getValueReader(); ValueReader value = scope.getValueReader();
if (value == null) { if (value == null) {
value = scope.getElemReader(); value = scope.getElemReader();
@ -256,9 +301,8 @@ public final class NBTInputStream implements Closeable {
} else { } else {
is.skipBytes(4); is.skipBytes(4);
} }
return;
} }
case NBTConstants.TYPE_DOUBLE: { case NBTConstants.TYPE_DOUBLE -> {
ValueReader value = scope.getValueReader(); ValueReader value = scope.getValueReader();
if (value == null) { if (value == null) {
value = scope.getElemReader(); value = scope.getElemReader();
@ -268,24 +312,20 @@ public final class NBTInputStream implements Closeable {
} else { } else {
is.skipBytes(8); is.skipBytes(8);
} }
return;
} }
case NBTConstants.TYPE_STRING: { case NBTConstants.TYPE_STRING -> {
ValueReader value = scope.getValueReader(); ValueReader value = scope.getValueReader();
if (value == null) { if (value == null) {
value = scope.getElemReader(); value = scope.getElemReader();
} }
int length = is.readShort() & 0xFFFF;
if (value != null) { if (value != null) {
byte[] bytes = new byte[length]; value.apply(0, is.readUTF());
is.readFully(bytes);
value.apply(0, new String(bytes, NBTConstants.CHARSET));
} else { } else {
int length = is.readShort() & 0xFFFF;
is.skipBytes(length); is.skipBytes(length);
} }
return;
} }
case NBTConstants.TYPE_LIST: { case NBTConstants.TYPE_LIST -> {
int childType = is.readByte(); int childType = is.readByte();
int length = is.readInt(); int length = is.readInt();
StreamDelegate child; StreamDelegate child;
@ -306,16 +346,15 @@ public final class NBTInputStream implements Closeable {
child = scope.get0(); child = scope.get0();
if (child == null) { if (child == null) {
for (int i = 0; i < length; ++i) { for (int i = 0; i < length; ++i) {
readTagPayloadLazy(childType, depth + 1); readTagPayloadLazy(childType, depth + 1, scope, scope.getRetained() != null);
} }
} else { } else {
for (int i = 0; i < length; ++i) { for (int i = 0; i < length; ++i) {
readTagPayloadLazy(childType, depth + 1, child); readTagPayloadLazy(childType, depth + 1, child);
} }
} }
return;
} }
case NBTConstants.TYPE_COMPOUND: { case NBTConstants.TYPE_COMPOUND -> {
// readDataPayload // readDataPayload
scope.acceptInfo(-1, NBTConstants.TYPE_BYTE); scope.acceptInfo(-1, NBTConstants.TYPE_BYTE);
ValueReader valueReader = scope.getValueReader(); ValueReader valueReader = scope.getValueReader();
@ -342,14 +381,23 @@ public final class NBTInputStream implements Closeable {
return; return;
} }
StreamDelegate child = scope.get(is); StreamDelegate child = scope.get(is);
try {
if (child == null) { if (child == null) {
readTagPayloadLazy(childType, depth + 1); readTagPayloadLazy(childType, depth + 1, scope, scope.getRetained() != null);
} else { } else {
readTagPayloadLazy(childType, depth + 1, child); readTagPayloadLazy(childType, depth + 1, child);
} }
} catch (IOException e) {
String cur = scope.getCurrentName() == null ? scope.getRetainedName() : scope.getCurrentName();
if (cur != null) {
throw new IOException("Error reading child scope: `" + scope.getCurrentName() + "`", e);
} else {
throw e;
} }
} }
case NBTConstants.TYPE_BYTE_ARRAY: { }
}
case NBTConstants.TYPE_BYTE_ARRAY -> {
int length = is.readInt(); int length = is.readInt();
scope.acceptInfo(length, NBTConstants.TYPE_BYTE); scope.acceptInfo(length, NBTConstants.TYPE_BYTE);
if (scope.acceptLazy(length, this)) { if (scope.acceptLazy(length, this)) {
@ -384,9 +432,8 @@ public final class NBTInputStream implements Closeable {
return; return;
} }
is.skipBytes(length); is.skipBytes(length);
return;
} }
case NBTConstants.TYPE_INT_ARRAY: { case NBTConstants.TYPE_INT_ARRAY -> {
int length = is.readInt(); int length = is.readInt();
scope.acceptInfo(length, NBTConstants.TYPE_INT); scope.acceptInfo(length, NBTConstants.TYPE_INT);
if (scope.acceptLazy(length, this)) { if (scope.acceptLazy(length, this)) {
@ -405,9 +452,8 @@ public final class NBTInputStream implements Closeable {
return; return;
} }
is.skipBytes(length << 2); is.skipBytes(length << 2);
return;
} }
case NBTConstants.TYPE_LONG_ARRAY: { case NBTConstants.TYPE_LONG_ARRAY -> {
int length = is.readInt(); int length = is.readInt();
scope.acceptInfo(length, NBTConstants.TYPE_LONG); scope.acceptInfo(length, NBTConstants.TYPE_LONG);
if (scope.acceptLazy(length, this)) { if (scope.acceptLazy(length, this)) {
@ -426,11 +472,8 @@ public final class NBTInputStream implements Closeable {
return; return;
} }
is.skipBytes(length << 3); is.skipBytes(length << 3);
return;
} }
default -> throw new IOException("Invalid tag type: " + type + ".");
default:
throw new IOException("Invalid tag type: " + type + ".");
} }
} }
@ -447,65 +490,60 @@ public final class NBTInputStream implements Closeable {
} }
public static int getSize(int type) { public static int getSize(int type) {
switch (type) { return switch (type) {
default: default -> 1;
case NBTConstants.TYPE_END: case NBTConstants.TYPE_BYTE_ARRAY, NBTConstants.TYPE_STRING, NBTConstants.TYPE_LIST, NBTConstants.TYPE_COMPOUND, NBTConstants.TYPE_INT_ARRAY, NBTConstants.TYPE_LONG_ARRAY, NBTConstants.TYPE_SHORT ->
case NBTConstants.TYPE_BYTE: 2;
return 1; case NBTConstants.TYPE_FLOAT, NBTConstants.TYPE_INT -> 4;
case NBTConstants.TYPE_BYTE_ARRAY: case NBTConstants.TYPE_DOUBLE, NBTConstants.TYPE_LONG -> 8;
case NBTConstants.TYPE_STRING: };
case NBTConstants.TYPE_LIST:
case NBTConstants.TYPE_COMPOUND:
case NBTConstants.TYPE_INT_ARRAY:
case NBTConstants.TYPE_LONG_ARRAY:
case NBTConstants.TYPE_SHORT:
return 2;
case NBTConstants.TYPE_FLOAT:
case NBTConstants.TYPE_INT:
return 4;
case NBTConstants.TYPE_DOUBLE:
case NBTConstants.TYPE_LONG:
return 8;
}
} }
public Object readTagPayloadRaw(int type, int depth) throws IOException { public Object readTagPayloadRaw(int type, int depth) throws IOException {
int length;
byte[] bytes;
switch (type) { switch (type) {
case NBTConstants.TYPE_END: case NBTConstants.TYPE_END -> {
if (depth == 0) { if (depth == 0) {
throw new IOException( throw new IOException(
"TAG_End found without a TAG_Compound/TAG_List tag preceding it."); "TAG_End found without a TAG_Compound/TAG_List tag preceding it.");
} else { } else {
return null; return null;
} }
case NBTConstants.TYPE_BYTE: }
case NBTConstants.TYPE_BYTE -> {
return (is.readByte()); return (is.readByte());
case NBTConstants.TYPE_SHORT: }
case NBTConstants.TYPE_SHORT -> {
return (is.readShort()); return (is.readShort());
case NBTConstants.TYPE_INT: }
case NBTConstants.TYPE_INT -> {
return (is.readInt()); return (is.readInt());
case NBTConstants.TYPE_LONG: }
case NBTConstants.TYPE_LONG -> {
return (is.readLong()); return (is.readLong());
case NBTConstants.TYPE_FLOAT: }
case NBTConstants.TYPE_FLOAT -> {
return (is.readFloat()); return (is.readFloat());
case NBTConstants.TYPE_DOUBLE: }
case NBTConstants.TYPE_DOUBLE -> {
return (is.readDouble()); return (is.readDouble());
case NBTConstants.TYPE_BYTE_ARRAY: }
int length = is.readInt(); case NBTConstants.TYPE_BYTE_ARRAY -> {
byte[] bytes = new byte[length]; length = is.readInt();
is.readFully(bytes);
return (bytes);
case NBTConstants.TYPE_STRING:
length = is.readShort() & 0xFFFF;
bytes = new byte[length]; bytes = new byte[length];
is.readFully(bytes); is.readFully(bytes);
return (new String(bytes, NBTConstants.CHARSET)); return (bytes);
case NBTConstants.TYPE_LIST: { }
case NBTConstants.TYPE_STRING -> {
return is.readUTF();
}
case NBTConstants.TYPE_LIST -> {
int childType = is.readByte(); int childType = is.readByte();
length = is.readInt(); length = is.readInt();
return readListRaw(depth, childType, length); return readListRaw(depth, childType, length);
} }
case NBTConstants.TYPE_COMPOUND: { case NBTConstants.TYPE_COMPOUND -> {
Map<String, Object> tagMap = new HashMap<>(); Map<String, Object> tagMap = new HashMap<>();
while (true) { while (true) {
int childType = is.readByte(); int childType = is.readByte();
@ -517,16 +555,15 @@ public final class NBTInputStream implements Closeable {
tagMap.put(name, value); tagMap.put(name, value);
} }
} }
case NBTConstants.TYPE_INT_ARRAY: { case NBTConstants.TYPE_INT_ARRAY -> {
length = is.readInt(); length = is.readInt();
return readIntArrayRaw(length); return readIntArrayRaw(length);
} }
case NBTConstants.TYPE_LONG_ARRAY: { case NBTConstants.TYPE_LONG_ARRAY -> {
length = is.readInt(); length = is.readInt();
return readLongArrayRaw(length); return readLongArrayRaw(length);
} }
default: default -> throw new IOException("Invalid tag type: " + type + ".");
throw new IOException("Invalid tag type: " + type + ".");
} }
} }
@ -542,7 +579,7 @@ public final class NBTInputStream implements Closeable {
for (int i = 0; i < toRead; i += 4, index++) { for (int i = 0; i < toRead; i += 4, index++) {
data[index] = ((buf[i] & 0xFF) << 24) + ((buf[i + 1] & 0xFF) << 16) + ((buf[i + 2] & 0xFF) << 8) + (buf[i + 3] & 0xFF); data[index] = ((buf[i] & 0xFF) << 24) + ((buf[i + 1] & 0xFF) << 16) + ((buf[i + 2] & 0xFF) << 8) + (buf[i + 3] & 0xFF);
} }
length -= toRead; length -= (toRead >> 2);
} }
return data; return data;
} }
@ -559,7 +596,7 @@ public final class NBTInputStream implements Closeable {
for (int i = 0; i < toRead; i += 8, index++) { for (int i = 0; i < toRead; i += 8, index++) {
data[index] = (((long) buf[i] << 56) | ((long) (buf[i + 1] & 255) << 48) | ((long) (buf[i + 2] & 255) << 40) | ((long) (buf[i + 3] & 255) << 32) | ((long) (buf[i + 4] & 255) << 24) | ((buf[i + 5] & 255) << 16) | ((buf[i + 6] & 255) << 8) | (buf[i + 7] & 255)); data[index] = (((long) buf[i] << 56) | ((long) (buf[i + 1] & 255) << 48) | ((long) (buf[i + 2] & 255) << 40) | ((long) (buf[i + 3] & 255) << 32) | ((long) (buf[i + 4] & 255) << 24) | ((buf[i + 5] & 255) << 16) | ((buf[i + 6] & 255) << 8) | (buf[i + 7] & 255));
} }
length -= toRead; length -= (toRead >> 3);
} }
return (data); return (data);
} }
@ -573,40 +610,47 @@ public final class NBTInputStream implements Closeable {
* @throws IOException if an I/O error occurs. * @throws IOException if an I/O error occurs.
*/ */
public Tag readTagPayload(int type, int depth) throws IOException { //FAWE - public public Tag readTagPayload(int type, int depth) throws IOException { //FAWE - public
int length;
byte[] bytes;
switch (type) { switch (type) {
case NBTConstants.TYPE_END: case NBTConstants.TYPE_END -> {
if (depth == 0) { if (depth == 0) {
throw new IOException( throw new IOException(
"TAG_End found without a TAG_Compound/TAG_List tag preceding it."); "TAG_End found without a TAG_Compound/TAG_List tag preceding it.");
} else { } else {
return new EndTag(); return new EndTag();
} }
case NBTConstants.TYPE_BYTE: }
case NBTConstants.TYPE_BYTE -> {
return new ByteTag(is.readByte()); return new ByteTag(is.readByte());
case NBTConstants.TYPE_SHORT: }
case NBTConstants.TYPE_SHORT -> {
return new ShortTag(is.readShort()); return new ShortTag(is.readShort());
case NBTConstants.TYPE_INT: }
case NBTConstants.TYPE_INT -> {
return new IntTag(is.readInt()); return new IntTag(is.readInt());
case NBTConstants.TYPE_LONG: }
case NBTConstants.TYPE_LONG -> {
return new LongTag(is.readLong()); return new LongTag(is.readLong());
case NBTConstants.TYPE_FLOAT: }
case NBTConstants.TYPE_FLOAT -> {
return new FloatTag(is.readFloat()); return new FloatTag(is.readFloat());
case NBTConstants.TYPE_DOUBLE: }
case NBTConstants.TYPE_DOUBLE -> {
return new DoubleTag(is.readDouble()); return new DoubleTag(is.readDouble());
case NBTConstants.TYPE_BYTE_ARRAY: }
int length = is.readInt(); case NBTConstants.TYPE_BYTE_ARRAY -> {
byte[] bytes = new byte[length]; length = is.readInt();
is.readFully(bytes);
return new ByteArrayTag(bytes);
case NBTConstants.TYPE_STRING:
length = is.readShort() & 0xFFFF;
bytes = new byte[length]; bytes = new byte[length];
is.readFully(bytes); is.readFully(bytes);
return new StringTag(new String(bytes, NBTConstants.CHARSET)); return new ByteArrayTag(bytes);
case NBTConstants.TYPE_LIST: }
case NBTConstants.TYPE_STRING -> {
return new StringTag(is.readUTF());
}
case NBTConstants.TYPE_LIST -> {
int childType = is.readByte(); int childType = is.readByte();
length = is.readInt(); length = is.readInt();
List<Tag> tagList = new ArrayList<>(); List<Tag> tagList = new ArrayList<>();
for (int i = 0; i < length; ++i) { for (int i = 0; i < length; ++i) {
Tag tag = readTagPayload(childType, depth + 1); Tag tag = readTagPayload(childType, depth + 1);
@ -615,9 +659,9 @@ public final class NBTInputStream implements Closeable {
} }
tagList.add(tag); tagList.add(tag);
} }
return new ListTag(NBTUtils.getTypeClass(childType), tagList); return new ListTag(NBTUtils.getTypeClass(childType), tagList);
case NBTConstants.TYPE_COMPOUND: }
case NBTConstants.TYPE_COMPOUND -> {
Map<String, Tag<?, ?>> tagMap = new HashMap<>(); Map<String, Tag<?, ?>> tagMap = new HashMap<>();
while (true) { while (true) {
NamedTag namedTag = readNamedTag(depth + 1); NamedTag namedTag = readNamedTag(depth + 1);
@ -628,24 +672,25 @@ public final class NBTInputStream implements Closeable {
tagMap.put(namedTag.getName(), tag); tagMap.put(namedTag.getName(), tag);
} }
} }
return new CompoundTag(tagMap); return new CompoundTag(tagMap);
case NBTConstants.TYPE_INT_ARRAY: }
case NBTConstants.TYPE_INT_ARRAY -> {
length = is.readInt(); length = is.readInt();
int[] data = new int[length]; int[] data = new int[length];
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
data[i] = is.readInt(); data[i] = is.readInt();
} }
return new IntArrayTag(data); return new IntArrayTag(data);
case NBTConstants.TYPE_LONG_ARRAY: }
case NBTConstants.TYPE_LONG_ARRAY -> {
length = is.readInt(); length = is.readInt();
long[] longData = new long[length]; long[] longData = new long[length];
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
longData[i] = is.readLong(); longData[i] = is.readLong();
} }
return new LongArrayTag(longData); return new LongArrayTag(longData);
default: }
throw new IOException("Invalid tag type: " + type + "."); default -> throw new IOException("Invalid tag type: " + type + ".");
} }
} }

Datei anzeigen

@ -112,9 +112,7 @@ public final class NBTOutputStream extends OutputStream implements Closeable, Da
checkNotNull(value); checkNotNull(value);
int type = NBTConstants.TYPE_STRING; int type = NBTConstants.TYPE_STRING;
writeNamedTagName(name, type); writeNamedTagName(name, type);
byte[] bytes = value.getBytes(NBTConstants.CHARSET); os.writeUTF(value);
os.writeShort(bytes.length);
os.write(bytes);
} }
public void writeNamedTag(String name, int value) throws IOException { public void writeNamedTag(String name, int value) throws IOException {
@ -163,6 +161,16 @@ public final class NBTOutputStream extends OutputStream implements Closeable, Da
} }
} }
public void writeNamedTag(String name, long[] data) throws IOException {
checkNotNull(name);
int type = NBTConstants.TYPE_LONG_ARRAY;
writeNamedTagName(name, type);
os.writeInt(data.length);
for (long aData : data) {
os.writeLong(aData);
}
}
public void writeNamedEmptyList(String name) throws IOException { public void writeNamedEmptyList(String name) throws IOException {
writeNamedEmptyList(name, NBTConstants.TYPE_COMPOUND); writeNamedEmptyList(name, NBTConstants.TYPE_COMPOUND);
} }
@ -183,10 +191,13 @@ public final class NBTOutputStream extends OutputStream implements Closeable, Da
} }
public void writeLazyCompoundTag(String name, LazyWrite next) throws IOException { public void writeLazyCompoundTag(String name, LazyWrite next) throws IOException {
byte[] nameBytes = name.getBytes(NBTConstants.CHARSET);
os.writeByte(NBTConstants.TYPE_COMPOUND); os.writeByte(NBTConstants.TYPE_COMPOUND);
os.writeShort(nameBytes.length); os.writeUTF(name);
os.write(nameBytes); next.write(this);
os.writeByte(NBTConstants.TYPE_END);
}
public void writeLazyListedCompoundTag(LazyWrite next) throws IOException {
next.write(this); next.write(this);
os.writeByte(NBTConstants.TYPE_END); os.writeByte(NBTConstants.TYPE_END);
} }
@ -327,9 +338,7 @@ public final class NBTOutputStream extends OutputStream implements Closeable, Da
* @throws IOException if an I/O error occurs. * @throws IOException if an I/O error occurs.
*/ */
private void writeStringTagPayload(StringTag tag) throws IOException { private void writeStringTagPayload(StringTag tag) throws IOException {
byte[] bytes = tag.getValue().getBytes(NBTConstants.CHARSET); os.writeUTF(tag.getValue());
os.writeShort(bytes.length);
os.write(bytes);
} }
/** /**

Datei anzeigen

@ -1,12 +1,24 @@
package com.fastasyncworldedit.core.command; package com.sk89q.worldedit.command;
import com.fastasyncworldedit.core.Fawe;
import com.fastasyncworldedit.core.anvil.MCAChunk;
import com.fastasyncworldedit.core.anvil.MCAWorld;
import com.fastasyncworldedit.core.configuration.Caption;
import com.fastasyncworldedit.core.queue.IQueueChunk;
import com.fastasyncworldedit.core.queue.IQueueExtent;
import com.fastasyncworldedit.core.regions.WorldRegionsRegion;
import com.fastasyncworldedit.core.util.MaskTraverser;
import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.EditSessionBuilder;
import com.sk89q.worldedit.LocalSession; import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.command.util.CommandPermissions; import com.sk89q.worldedit.command.util.CommandPermissions;
import com.sk89q.worldedit.command.util.CommandPermissionsConditionGenerator; import com.sk89q.worldedit.command.util.CommandPermissionsConditionGenerator;
import com.sk89q.worldedit.entity.Player; import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.extension.platform.Actor;
import com.sk89q.worldedit.function.mask.AbstractExtentMask;
import com.sk89q.worldedit.function.mask.Mask;
import com.sk89q.worldedit.function.pattern.Pattern; import com.sk89q.worldedit.function.pattern.Pattern;
import com.sk89q.worldedit.internal.annotation.Selection; import com.sk89q.worldedit.internal.annotation.Selection;
import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.regions.Region;
@ -20,13 +32,6 @@ import java.io.IOException;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
/**
* @deprecated Anvil classes were used on versions prior to 1.13 to trim chunks.
* The way how it's been done was unsafe and led to issues back the years, hence it
* hasn't been implemented in any modern version. Therefore the current
* implementation is deprecated for removal without replacement.
*/
@Deprecated
@CommandContainer(superTypes = CommandPermissionsConditionGenerator.Registration.class) @CommandContainer(superTypes = CommandPermissionsConditionGenerator.Registration.class)
public class AnvilCommands { public class AnvilCommands {
@ -116,57 +121,22 @@ public class AnvilCommands {
// } // }
@Command( @Command(
name = "replaceall", name = "replaceall", aliases = {"rea", "repall"}, desc = "Replace all blocks in the selection with another")
aliases = {"rea", "repall"},
desc = "Replace all blocks in the selection with another"
)
@CommandPermissions("worldedit.anvil.replaceall") @CommandPermissions("worldedit.anvil.replaceall")
public void replaceAll( public void replaceAll(
Player player, String folder, Actor actor,
@Arg(name = "from", desc = "String", def = "") @Arg(name = "world", desc = "Unloaded world") MCAWorld world,
String fromPattern, @Arg(name = "from", desc = "Mask") Mask from,
String toPatternStr, @Arg(name = "to", desc = "Pattern") Pattern to
@Switch(name = 'd', desc = "Disable wildcard data matching")
boolean useData
) throws WorldEditException { ) throws WorldEditException {
// final FaweBlockMatcher matchFrom; TODO NOT IMPLEMENTED IQueueExtent<IQueueChunk> queueExtent = Fawe.instance().getQueueHandler().getMCAQueue(world);
// if (from == null) { new MaskTraverser(from).setNewExtent(queueExtent);
// matchFrom = FaweBlockMatcher.NOT_AIR; queueExtent.replaceBlocks(new WorldRegionsRegion(world), from, to);
// } else { queueExtent.flush();
// if (from.contains(":")) { actor.print(Caption.of("fawe.worldedit.anvil.replaceall.complete"));
// useData = true; //override d flag, if they specified data they want it
// }
// matchFrom = FaweBlockMatcher.fromBlocks(worldEdit.getBlocks(player, from, true), useData);
// }
// final FaweBlockMatcher matchTo = FaweBlockMatcher.setBlocks(worldEdit.getBlocks(player, to, true));
// ReplaceSimpleFilter filter = new ReplaceSimpleFilter(matchFrom, matchTo);
// ReplaceSimpleFilter result = runWithWorld(player, folder, filter, true);
// if (result != null) player.print(Caption.of("fawe.worldedit.visitor.visitor.block", (result.getTotal())));
} }
@Command( @Command(name = "deleteallunvisited", aliases = {"delunvisited"}, desc = "Delete all chunks which haven't been occupied",
name = "remapall",
descFooter = "Remap the world between MCPE/PC values",
desc = "Remap the world between MCPE/PC values"
)
@CommandPermissions("worldedit.anvil.remapall")
public void remapall(Player player, String folder) throws WorldEditException {
// ClipboardRemapper.RemapPlatform from; TODO NOT IMPLEMENTED
// ClipboardRemapper.RemapPlatform to;
// from = ClipboardRemapper.RemapPlatform.PE;
// to = ClipboardRemapper.RemapPlatform.PC;
// RemapFilter filter = new RemapFilter(from, to);
// RemapFilter result = runWithWorld(player, folder, filter, true);
// if (result != null) {
// player.print(Caption.of("fawe.worldedit.visitor.visitor.block", (result.getTotal())));
// }
}
@Command(
name = "deleteallunvisited",
aliases = {"delunvisited"},
desc = "Delete all chunks which haven't been occupied",
descFooter = "occupied for `age-ticks` (20t = 1s) and \n" descFooter = "occupied for `age-ticks` (20t = 1s) and \n"
+ "Have not been accessed since `file-duration` (ms) after creation and\n" + "Have not been accessed since `file-duration` (ms) after creation and\n"
+ "Have not been used in the past `chunk-inactivity` (ms)" + "Have not been used in the past `chunk-inactivity` (ms)"

Datei anzeigen

@ -0,0 +1,96 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.command.argument;
import com.fastasyncworldedit.core.Fawe;
import com.fastasyncworldedit.core.anvil.MCAWorld;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.extension.platform.Capability;
import com.sk89q.worldedit.internal.util.LogManagerCompat;
import com.sk89q.worldedit.util.formatting.text.Component;
import com.sk89q.worldedit.util.formatting.text.TextComponent;
import com.sk89q.worldedit.world.World;
import org.apache.logging.log4j.Logger;
import org.enginehub.piston.CommandManager;
import org.enginehub.piston.converter.ArgumentConverter;
import org.enginehub.piston.converter.ConversionResult;
import org.enginehub.piston.converter.FailedConversion;
import org.enginehub.piston.converter.SuccessfulConversion;
import org.enginehub.piston.inject.InjectedValueAccess;
import org.enginehub.piston.inject.Key;
import java.io.IOException;
import java.nio.file.Files;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class MCAWorldConverter implements ArgumentConverter<MCAWorld> {
private static final Logger LOGGER = LogManagerCompat.getLogger();
public static void register(CommandManager commandManager) {
commandManager.registerConverter(Key.of(MCAWorld.class), WORLD_CONVERTER);
}
//FAWE start - Accessed by LocationConverter
public static final MCAWorldConverter WORLD_CONVERTER = new MCAWorldConverter();
//FAWE end
private final TextComponent choices;
private MCAWorldConverter() {
this.choices = TextComponent.of("any world");
}
@Override
public Component describeAcceptableArguments() {
return this.choices;
}
private Stream<? extends String> getWorlds() {
try {
return Files.list(Fawe.platform().getWorldsFolder()).filter(p -> Files.isDirectory(p) && Files.exists(p.resolve(
"level.dat"))).map(p -> p.getFileName().toString());
} catch (IOException e) {
LOGGER.error("Error accessing worlds", e);
return Stream.empty();
}
}
@Override
public List<String> getSuggestions(String input, InjectedValueAccess context) {
return getWorlds()
.filter(world -> world.startsWith(input))
.collect(Collectors.toList());
}
@Override
public ConversionResult<MCAWorld> convert(String s, InjectedValueAccess injectedValueAccess) {
String result = getWorlds()
.filter(world -> world.equals(s))
.findAny().orElse(null);
return result == null
? FailedConversion.from(new IllegalArgumentException(
"Not a valid world: " + s))
: SuccessfulConversion.fromSingle(MCAWorld.of(result));
}
}

Datei anzeigen

@ -276,5 +276,21 @@ public interface Platform extends Keyed {
default IBatchProcessor getPlatformPostProcessor(boolean fastMode) { default IBatchProcessor getPlatformPostProcessor(boolean fastMode) {
return null; return null;
} }
/**
* Get a char array of minecraft internal IDs against FAWE char IDs
*/
@Nullable
default char[] getIbdToStateOrdinal() {
return null;
}
/**
* Get an int array of FAWE char IDs against minecraft internal IDs
*/
@Nullable
default int[] getOrdinalToIbdID() {
return null;
}
//FAWE end //FAWE end
} }

Datei anzeigen

@ -42,6 +42,8 @@ import com.sk89q.worldedit.LocalConfiguration;
import com.sk89q.worldedit.LocalSession; import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.MissingWorldException; import com.sk89q.worldedit.MissingWorldException;
import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.command.AnvilCommands;
import com.sk89q.worldedit.command.AnvilCommandsRegistration;
import com.sk89q.worldedit.command.ApplyBrushCommands; import com.sk89q.worldedit.command.ApplyBrushCommands;
import com.sk89q.worldedit.command.BiomeCommands; import com.sk89q.worldedit.command.BiomeCommands;
import com.sk89q.worldedit.command.BiomeCommandsRegistration; import com.sk89q.worldedit.command.BiomeCommandsRegistration;
@ -98,6 +100,7 @@ import com.sk89q.worldedit.command.argument.ExpressionConverter;
import com.sk89q.worldedit.command.argument.FactoryConverter; import com.sk89q.worldedit.command.argument.FactoryConverter;
import com.sk89q.worldedit.command.argument.HeightConverter; import com.sk89q.worldedit.command.argument.HeightConverter;
import com.sk89q.worldedit.command.argument.LocationConverter; import com.sk89q.worldedit.command.argument.LocationConverter;
import com.sk89q.worldedit.command.argument.MCAWorldConverter;
import com.sk89q.worldedit.command.argument.OffsetConverter; import com.sk89q.worldedit.command.argument.OffsetConverter;
import com.sk89q.worldedit.command.argument.RegionFactoryConverter; import com.sk89q.worldedit.command.argument.RegionFactoryConverter;
import com.sk89q.worldedit.command.argument.RegistryConverter; import com.sk89q.worldedit.command.argument.RegistryConverter;
@ -275,6 +278,7 @@ public final class PlatformCommandManager {
ClipboardFormatConverter.register(commandManager); ClipboardFormatConverter.register(commandManager);
ClipboardShareDestinationConverter.register(commandManager); ClipboardShareDestinationConverter.register(commandManager);
//FAWE start //FAWE start
MCAWorldConverter.register(commandManager);
commandManager.registerConverter( commandManager.registerConverter(
Key.of(com.sk89q.worldedit.function.pattern.Pattern.class, Annotations.patternList()), Key.of(com.sk89q.worldedit.function.pattern.Pattern.class, Annotations.patternList()),
CommaSeparatedValuesConverter.wrap(commandManager.getConverter(Key.of( CommaSeparatedValuesConverter.wrap(commandManager.getConverter(Key.of(
@ -536,6 +540,13 @@ public final class PlatformCommandManager {
HistorySubCommandsRegistration.builder(), HistorySubCommandsRegistration.builder(),
new HistorySubCommands(history) new HistorySubCommands(history)
); );
registerSubCommands(
"/anvil",
ImmutableList.of(),
"Anvil commands",
AnvilCommandsRegistration.builder(),
new AnvilCommands(worldEdit)
);
//FAWE end //FAWE end
this.registration.register( this.registration.register(
commandManager, commandManager,

Datei anzeigen

@ -32,6 +32,7 @@
"fawe.worldedit.history.command.undo.disabled": "Undo disabled, use: //fast", "fawe.worldedit.history.command.undo.disabled": "Undo disabled, use: //fast",
"fawe.worldedit.selection.selection.count": "Counted {0} blocks.", "fawe.worldedit.selection.selection.count": "Counted {0} blocks.",
"fawe.worldedit.anvil.world.is.loaded": "The world shouldn't be in use when executing. Unload the world, or use -f to override (save first)", "fawe.worldedit.anvil.world.is.loaded": "The world shouldn't be in use when executing. Unload the world, or use -f to override (save first)",
"fawe.worldedit.anvil.replaceall.complete": "Replaceall with anvil completed.",
"fawe.worldedit.brush.brush.reset": "Reset your brush. (SHIFT + Click)", "fawe.worldedit.brush.brush.reset": "Reset your brush. (SHIFT + Click)",
"fawe.worldedit.brush.brush.none": "You aren't holding a brush!", "fawe.worldedit.brush.brush.none": "You aren't holding a brush!",
"fawe.worldedit.brush.brush.scroll.action.set": "Set scroll action to {0}", "fawe.worldedit.brush.brush.scroll.action.set": "Set scroll action to {0}",