Mirror von
https://github.com/IntellectualSites/FastAsyncWorldEdit.git
synchronisiert 2024-11-16 16:10:07 +01:00
more work
Dieser Commit ist enthalten in:
Ursprung
e3ea0c2b4a
Commit
34009d1d06
@ -588,7 +588,7 @@ public class MCAChunk implements IChunk {
|
||||
}
|
||||
|
||||
public boolean isModified() {
|
||||
return modified;
|
||||
return modified || deleted;
|
||||
}
|
||||
|
||||
public boolean isDeleted() {
|
||||
|
@ -1,10 +1,11 @@
|
||||
package com.fastasyncworldedit.core.anvil;
|
||||
|
||||
import com.fastasyncworldedit.core.FaweCache;
|
||||
import com.fastasyncworldedit.core.internal.exception.FaweException;
|
||||
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;
|
||||
@ -53,26 +54,71 @@ public class MCAFile implements Closeable, Flushable {
|
||||
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) {
|
||||
private MCAFile(int mcrX, int mcrZ, Path file) {
|
||||
this.file = file;
|
||||
X = mcrX;
|
||||
Z = mcrZ;
|
||||
offsetMap = new Int2IntOpenHashMap();
|
||||
offsetMap.defaultReturnValue(Integer.MAX_VALUE);
|
||||
init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new mca file
|
||||
*
|
||||
* @param mcrX region x
|
||||
* @param mcrZ region z
|
||||
* @param directory directory to create mca file in
|
||||
* @return new mca file
|
||||
* @throws IOException on io error
|
||||
*/
|
||||
public static MCAFile create(int mcrX, int mcrZ, Path directory) throws IOException {
|
||||
Files.createDirectories(directory);
|
||||
Path file = directory.resolve("r." + mcrX + "." + mcrZ + ".mca");
|
||||
Files.createFile(file);
|
||||
MCAFile mca = new MCAFile(mcrX, mcrZ, file);
|
||||
mca.initEmpty();
|
||||
return mca;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load an mca file
|
||||
*
|
||||
* @param mcrX region x
|
||||
* @param mcrZ region z
|
||||
* @param directory directory to load mca file from
|
||||
* @return loaded mca file
|
||||
* @throws IOException on io error
|
||||
*/
|
||||
@Nullable
|
||||
public static MCAFile load(int mcrX, int mcrZ, Path directory) throws IOException {
|
||||
Path file = directory.resolve("r." + mcrX + "." + mcrZ + ".mca");
|
||||
if (!Files.exists(file)) {
|
||||
return null;
|
||||
}
|
||||
MCAFile mca = new MCAFile(mcrX, mcrZ, file);
|
||||
mca.init();
|
||||
return mca;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load or create an mca file
|
||||
*
|
||||
* @param mcrX region x
|
||||
* @param mcrZ region z
|
||||
* @param directory directory to load or create mca file in
|
||||
* @return mca file
|
||||
* @throws IOException on io error
|
||||
*/
|
||||
public static MCAFile loadOrCreate(int mcrX, int mcrZ, Path directory) throws IOException {
|
||||
Path file = directory.resolve("r." + mcrX + "." + mcrZ + ".mca");
|
||||
MCAFile mca = new MCAFile(mcrX, mcrZ, file);
|
||||
if (!Files.exists(file)) {
|
||||
Files.createFile(file);
|
||||
mca.initEmpty();
|
||||
} else {
|
||||
mca.init();
|
||||
}
|
||||
return mca;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
@ -80,7 +126,7 @@ public class MCAFile implements Closeable, Flushable {
|
||||
try {
|
||||
raf.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
LOGGER.error("Error clearing MCAFile", e);
|
||||
}
|
||||
}
|
||||
synchronized (chunks) {
|
||||
@ -94,9 +140,7 @@ public class MCAFile implements Closeable, Flushable {
|
||||
* Set if the file should be delete
|
||||
*/
|
||||
public void setDeleted(boolean deleted) {
|
||||
if (!init) {
|
||||
init();
|
||||
}
|
||||
checkInit();
|
||||
this.deleted = deleted;
|
||||
}
|
||||
|
||||
@ -107,49 +151,68 @@ public class MCAFile implements Closeable, Flushable {
|
||||
return deleted;
|
||||
}
|
||||
|
||||
protected void checkInit() {
|
||||
if (!init) {
|
||||
try {
|
||||
init();
|
||||
} catch (IOException e) {
|
||||
throw new FaweException("Error initialising MCA file", FaweException.Type.ANVIL_IO, false, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
private synchronized void init() throws IOException {
|
||||
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();
|
||||
init = true;
|
||||
closed = false;
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void initEmpty() throws IOException {
|
||||
this.offsets = new int[SECTOR_INTS];
|
||||
this.raf = new RandomAccessFile(file.toFile(), "rw");
|
||||
|
||||
// Initialises with all zeroes
|
||||
this.raf.setLength(8192L);
|
||||
this.raf.seek(0);
|
||||
this.raf.write(FaweCache.INSTANCE.BYTE_BUFFER_8192.get());
|
||||
sectorFree = new FastBitSet(2);
|
||||
init = true;
|
||||
closed = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the region file X
|
||||
*/
|
||||
@ -183,9 +246,7 @@ public class MCAFile implements Closeable, Flushable {
|
||||
*/
|
||||
@Nullable
|
||||
public MCAChunk getCachedChunk(int cx, int cz) {
|
||||
if (!init) {
|
||||
init();
|
||||
}
|
||||
checkInit();
|
||||
short pair = (short) ((cx & 31) + ((cz & 31) << 5));
|
||||
synchronized (chunks) {
|
||||
return chunks.get(pair);
|
||||
@ -196,9 +257,7 @@ public class MCAFile implements Closeable, Flushable {
|
||||
* Create a new empty {@link MCAChunk}.
|
||||
*/
|
||||
public MCAChunk newChunk(int cx, int cz) {
|
||||
if (!init) {
|
||||
init();
|
||||
}
|
||||
checkInit();
|
||||
short pair = (short) ((cx & 31) + ((cz & 31) << 5));
|
||||
MCAChunk chunk;
|
||||
synchronized (chunks) {
|
||||
@ -211,9 +270,7 @@ public class MCAFile implements Closeable, Flushable {
|
||||
* Insert a {@link MCAChunk} into the cache.
|
||||
*/
|
||||
public void setChunk(MCAChunk chunk) {
|
||||
if (!init) {
|
||||
init();
|
||||
}
|
||||
checkInit();
|
||||
int cx = chunk.getX();
|
||||
int cz = chunk.getZ();
|
||||
short pair = (short) ((cx & 31) + ((cz & 31) << 5));
|
||||
@ -226,9 +283,7 @@ public class MCAFile implements Closeable, Flushable {
|
||||
* Load data from the mca region into the given {@link MCAChunk}.
|
||||
*/
|
||||
public void loadIntoChunkFromFile(MCAChunk chunk) throws IOException {
|
||||
if (!init) {
|
||||
init();
|
||||
}
|
||||
checkInit();
|
||||
int cx = chunk.getX();
|
||||
int cz = chunk.getZ();
|
||||
int i = (cx & 31) + ((cz & 31) << 5);
|
||||
@ -238,37 +293,13 @@ public class MCAFile implements Closeable, Flushable {
|
||||
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);
|
||||
};
|
||||
}
|
||||
chunk.loadFromNIS(new NBTInputStream(getChunkBIS(offset >> 8)), false);
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public MCAChunk getChunk(int cx, int cz) throws IOException {
|
||||
if (!init) {
|
||||
init();
|
||||
}
|
||||
public MCAChunk getChunk(int cx, int cz) {
|
||||
checkInit();
|
||||
MCAChunk cached = getCachedChunk(cx, cz);
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
@ -277,10 +308,8 @@ public class MCAFile implements Closeable, Flushable {
|
||||
}
|
||||
}
|
||||
|
||||
public MCAChunk readChunk(int cx, int cz) throws IOException {
|
||||
if (!init) {
|
||||
init();
|
||||
}
|
||||
public MCAChunk readChunk(int cx, int cz) {
|
||||
checkInit();
|
||||
int i = (cx & 31) + ((cz & 31) << 5);
|
||||
int offset = offsets[i];
|
||||
if (offset == 0) {
|
||||
@ -289,26 +318,8 @@ public class MCAFile implements Closeable, Flushable {
|
||||
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);
|
||||
};
|
||||
NBTInputStream nis = new NBTInputStream(getChunkBIS(offset >> 8));
|
||||
chunk = new MCAChunk(this, nis, cx, cz, false);
|
||||
}
|
||||
short pair = (short) ((cx & 31) + ((cz & 31) << 5));
|
||||
synchronized (chunks) {
|
||||
@ -322,12 +333,10 @@ public class MCAFile implements Closeable, Flushable {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param onEach cx, cz, offset, size (in kB)
|
||||
* @param onEach cx, cz, sector, sectorsAllocated (in kB)
|
||||
*/
|
||||
public void forEachChunk(RunnableVal4<Integer, Integer, Integer, Integer> onEach) {
|
||||
if (!init) {
|
||||
init();
|
||||
}
|
||||
checkInit();
|
||||
int i = 0;
|
||||
for (int z = 0; z < 32; z++) {
|
||||
for (int x = 0; x < 32; x++, i += 4) {
|
||||
@ -341,9 +350,7 @@ public class MCAFile implements Closeable, Flushable {
|
||||
}
|
||||
|
||||
public void forEachChunk(RunnableVal<MCAChunk> onEach) {
|
||||
if (!init) {
|
||||
init();
|
||||
}
|
||||
checkInit();
|
||||
int rx = X << 5;
|
||||
int rz = Z << 5;
|
||||
for (int z = 0; z < 32; z++) {
|
||||
@ -359,60 +366,90 @@ public class MCAFile implements Closeable, Flushable {
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
@Nullable
|
||||
protected BufferedInputStream getChunkIS(int cx, int cz) throws IOException {
|
||||
checkInit();
|
||||
int offset = offsets[(cx & 31) + ((cz & 31) << 5)];
|
||||
return getChunkBIS(offset);
|
||||
}
|
||||
|
||||
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);
|
||||
@Nullable
|
||||
private BufferedInputStream getChunkBIS(long offset) throws IOException {
|
||||
if (offset == 0) {
|
||||
return null;
|
||||
}
|
||||
byte version;
|
||||
byte[] data;
|
||||
synchronized (this) {
|
||||
raf.seek(offset << 12);
|
||||
int length = raf.readInt();
|
||||
version = raf.readByte();
|
||||
data = new byte[length - 1];
|
||||
raf.read(data);
|
||||
}
|
||||
FastByteArrayInputStream bais = new FastByteArrayInputStream(data);
|
||||
return 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);
|
||||
};
|
||||
}
|
||||
|
||||
public byte[] getFullChunkBytes(int cx, int cz) throws IOException {
|
||||
checkInit();
|
||||
int offset = offsets[(cx & 31) + ((cz & 31) << 5)];
|
||||
int sectorNumber = offset >> 8;
|
||||
int sectorsAllocated = offset & 0xFF;
|
||||
byte[] data = new byte[sectorsAllocated << 12];
|
||||
raf.seek((long) sectorNumber << 12);
|
||||
raf.read(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes chunk bytes directly to the MCAFile and returns the next sector after the chunk
|
||||
*
|
||||
* @param cx chunk x
|
||||
* @param cz chunk z
|
||||
* @param sectorNumber sector to write chunk from
|
||||
* @param bytes byte to write
|
||||
* @return next sector after the chunk
|
||||
* @throws IOException on IO error
|
||||
*/
|
||||
public synchronized int writeFullChunkBytes(int cx, int cz, int sectorNumber, byte[] bytes) throws IOException {
|
||||
checkInit();
|
||||
int allocate = (bytes.length >> 12) + 1;
|
||||
int offset = (sectorNumber << 8) | (allocate & 0xFF);
|
||||
setOffset(cx, cz, offset);
|
||||
raf.seek((long) sectorNumber << 12);
|
||||
raf.write(bytes);
|
||||
int newSector = sectorNumber + allocate;
|
||||
sectorFree.expandTo(newSector, false);
|
||||
return newSector;
|
||||
}
|
||||
|
||||
public synchronized void replaceChunkBytes(int cx, int cz, byte[] bytes) throws IOException {
|
||||
checkInit();
|
||||
int currOffset = offsets[(cx & 31) + ((cz & 31) << 5)];
|
||||
int allocate = (bytes.length >> 12) + 1;
|
||||
int sectorNumber = sectorFree.size() + 1;
|
||||
if (currOffset != 0) {
|
||||
int currSector = currOffset >> 8;
|
||||
int currAllocate = currOffset & 0xFF;
|
||||
sectorFree.setRange(currSector, currSector + currAllocate);
|
||||
if (allocate <= currAllocate) {
|
||||
sectorNumber = currSector;
|
||||
sectorFree.clearRange(currSector, currSector + allocate);
|
||||
}
|
||||
System.out.println(sectorNumber);
|
||||
}
|
||||
int offset = (sectorNumber << 8) | (allocate & 0xFF);
|
||||
setOffset(cx, cz, offset);
|
||||
raf.seek((long) sectorNumber << 12);
|
||||
raf.write(bytes);
|
||||
int newSector = sectorNumber + allocate;
|
||||
sectorFree.expandTo(newSector, false);
|
||||
}
|
||||
|
||||
public List<MCAChunk> getCachedChunks() {
|
||||
@ -428,16 +465,17 @@ public class MCAFile implements Closeable, Flushable {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() throws IOException {
|
||||
public synchronized void close(boolean flush) throws IOException {
|
||||
if (raf == null || closed) {
|
||||
return;
|
||||
}
|
||||
flush();
|
||||
if (flush) {
|
||||
flush();
|
||||
}
|
||||
try {
|
||||
raf.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
LOGGER.error("Error closing MCAFile", e);
|
||||
}
|
||||
raf = null;
|
||||
offsets = null;
|
||||
@ -446,6 +484,11 @@ public class MCAFile implements Closeable, Flushable {
|
||||
init = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() throws IOException {
|
||||
close(true);
|
||||
}
|
||||
|
||||
public boolean isModified() {
|
||||
if (isDeleted()) {
|
||||
return true;
|
||||
@ -453,7 +496,7 @@ public class MCAFile implements Closeable, Flushable {
|
||||
synchronized (chunks) {
|
||||
for (Int2ObjectMap.Entry<MCAChunk> entry : chunks.int2ObjectEntrySet()) {
|
||||
MCAChunk chunk = entry.getValue();
|
||||
if (chunk.isModified() || chunk.isDeleted()) {
|
||||
if (chunk.isModified()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -461,15 +504,19 @@ public class MCAFile implements Closeable, Flushable {
|
||||
return false;
|
||||
}
|
||||
|
||||
public synchronized void setOffset(final int x, final int z, final int offset)
|
||||
public int getOffset(int cx, int cz) {
|
||||
return offsets[cx + (cz << 5)];
|
||||
}
|
||||
|
||||
public synchronized void setOffset(int cx, int cz, final int offset)
|
||||
throws IOException {
|
||||
int i = (x & 31) + ((z & 31) << 5);
|
||||
int i = (cx & 31) + ((cz & 31) << 5);
|
||||
if (offset == 0) {
|
||||
offsetMap.remove(offsets[i]);
|
||||
} else {
|
||||
offsetMap.put(offset, i);
|
||||
}
|
||||
offsets[x + (z << 5)] = offset;
|
||||
offsets[cx + (cz << 5)] = offset;
|
||||
raf.seek((long) i << 2);
|
||||
raf.writeInt(offset);
|
||||
}
|
||||
@ -479,6 +526,7 @@ public class MCAFile implements Closeable, Flushable {
|
||||
*/
|
||||
@Override
|
||||
public synchronized void flush() throws IOException {
|
||||
System.out.println(sectorFree);
|
||||
boolean delete = true;
|
||||
int currentSector = 2;
|
||||
Queue<Integer> offsets =
|
||||
@ -577,11 +625,12 @@ public class MCAFile implements Closeable, Flushable {
|
||||
int size = 0;
|
||||
for (int i = sectorFree.size(); i > 0; i--) {
|
||||
if (!sectorFree.get(i)) {
|
||||
size = i + 1;
|
||||
size = i + 1; // + 1 to fully encompass
|
||||
break;
|
||||
}
|
||||
}
|
||||
raf.setLength((long) (size + 1) * SECTOR_BYTES);
|
||||
System.out.println(sectorFree);
|
||||
raf.setLength((long) size << 12);
|
||||
if (delete || size < 3) {
|
||||
clear();
|
||||
Files.delete(file);
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.fastasyncworldedit.core.anvil;
|
||||
|
||||
import com.fastasyncworldedit.core.Fawe;
|
||||
import com.fastasyncworldedit.core.internal.exception.FaweException;
|
||||
import com.fastasyncworldedit.core.queue.IChunkGet;
|
||||
import com.fastasyncworldedit.core.queue.implementation.packet.ChunkPacket;
|
||||
import com.fastasyncworldedit.core.util.MathMan;
|
||||
@ -64,7 +65,9 @@ public class MCAWorld extends AbstractWorld {
|
||||
* @param folder World file/folder
|
||||
*/
|
||||
public static synchronized MCAWorld of(String name, Path folder) {
|
||||
if (Fawe.platform().isWorldLoaded(name)) {
|
||||
if (folder.getParent().toAbsolutePath().equals(Fawe.platform().getWorldsFolder().toAbsolutePath()) && 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
|
||||
@ -78,10 +81,18 @@ public class MCAWorld extends AbstractWorld {
|
||||
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"))
|
||||
);
|
||||
mcaFileCache.computeIfAbsent(paired, (i) -> {
|
||||
try {
|
||||
return MCAFile.load(regionX, regionZ, regionFolder);
|
||||
} catch (IOException e) {
|
||||
throw new FaweException(
|
||||
"Error loading MCA file: `" + file.getFileName() + "`",
|
||||
FaweException.Type.ANVIL_IO,
|
||||
false,
|
||||
e
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
return mcaFileCache.values();
|
||||
}
|
||||
@ -95,6 +106,29 @@ public class MCAWorld extends AbstractWorld {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create MCA file for the given region coordinates
|
||||
*
|
||||
* @param mcrX x region coordinate
|
||||
* @param mcrZ z region coordinate
|
||||
* @return mca file
|
||||
*/
|
||||
public MCAFile getOrCreateMCA(int mcrX, int mcrZ) {
|
||||
int pair = MathMan.pair((short) mcrX, (short) mcrZ);
|
||||
return mcaFileCache.computeIfAbsent(pair, (i) -> {
|
||||
try {
|
||||
return MCAFile.loadOrCreate(mcrX, mcrZ, regionFolder);
|
||||
} catch (IOException e) {
|
||||
throw new FaweException(
|
||||
"Error loading MCA file " + mcrX + "," + mcrZ + " in folder " + regionFolder,
|
||||
FaweException.Type.ANVIL_IO,
|
||||
false,
|
||||
e
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setTile(final int x, final int y, final int z, final CompoundTag tile) throws WorldEditException {
|
||||
return false;
|
||||
@ -114,6 +148,10 @@ public class MCAWorld extends AbstractWorld {
|
||||
return folder;
|
||||
}
|
||||
|
||||
public Path getRegionFolder() {
|
||||
return regionFolder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <B extends BlockStateHolder<B>> boolean setBlock(
|
||||
final BlockVector3 position,
|
||||
@ -173,14 +211,20 @@ public class MCAWorld extends AbstractWorld {
|
||||
int paired = MathMan.pair(regionX, regionZ);
|
||||
MCAFile mca = mcaFileCache.computeIfAbsent(
|
||||
paired,
|
||||
(i) -> new MCAFile(regionX, regionZ, regionFolder.resolve("r." + regionX + "." + regionZ + ".mca"))
|
||||
(i) -> {
|
||||
try {
|
||||
return MCAFile.loadOrCreate(regionX, regionZ, regionFolder);
|
||||
} catch (IOException e) {
|
||||
throw new FaweException(
|
||||
"Error loading MCA file " + regionX + "," + regionZ + " in folder " + regionFolder,
|
||||
FaweException.Type.ANVIL_IO,
|
||||
false,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
try {
|
||||
return mca.getChunk(chunkX, chunkZ);
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("Error loading chunk. Creating empty chunk.", e);
|
||||
return mca.newChunk(chunkX, chunkZ);
|
||||
}
|
||||
return mca.getChunk(chunkX, chunkZ);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -192,11 +236,31 @@ public class MCAWorld extends AbstractWorld {
|
||||
public synchronized void flush() {
|
||||
for (MCAFile mca : mcaFileCache.values()) {
|
||||
try {
|
||||
mca.close();
|
||||
mca.flush();
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("Could not flush MCAFile {}", mca.getFile().getFileName(), e);
|
||||
LOGGER.error("Could not close MCAFile {}", mca.getFile().getFileName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void close(boolean flush) {
|
||||
for (MCAFile mca : mcaFileCache.values()) {
|
||||
try {
|
||||
mca.close(flush);
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("Could not close MCAFile {}", mca.getFile().getFileName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new MCA file for the given region x,z
|
||||
*
|
||||
* @param rX region X
|
||||
* @param rZ region Z
|
||||
*/
|
||||
public synchronized MCAFile createMCA(int rX, int rZ) throws IOException {
|
||||
return MCAFile.create(rX, rZ, regionFolder);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -97,7 +97,7 @@ public final class LimitExtent extends AbstractDelegateExtent implements IBatchP
|
||||
}
|
||||
|
||||
private void handleException(FaweException e) {
|
||||
if (e.ignorable() || !limit.MAX_FAILS()) {
|
||||
if (!e.ignorable() || !limit.MAX_FAILS()) {
|
||||
throw e;
|
||||
}
|
||||
if (!faweExceptionReasonsUsed[e.getType().ordinal()]) {
|
||||
|
@ -23,6 +23,26 @@ public class FaweException extends RuntimeException {
|
||||
this(TextComponent.of(reason));
|
||||
}
|
||||
|
||||
/**
|
||||
* New instance. Defaults to {@link FaweException.Type#OTHER}.
|
||||
*/
|
||||
public FaweException(Component reason, Type type, boolean ignorable, Exception e) {
|
||||
super(e);
|
||||
this.message = reason;
|
||||
this.type = type;
|
||||
this.ignorable = ignorable;
|
||||
}
|
||||
|
||||
/**
|
||||
* New instance. Defaults to {@link FaweException.Type#OTHER}.
|
||||
*/
|
||||
public FaweException(String reason, Type type, boolean ignorable, Exception e) {
|
||||
super(e);
|
||||
this.message = TextComponent.of(reason);
|
||||
this.type = type;
|
||||
this.ignorable = ignorable;
|
||||
}
|
||||
|
||||
/**
|
||||
* New instance. Defaults to {@link FaweException.Type#OTHER}.
|
||||
*/
|
||||
@ -114,6 +134,7 @@ public class FaweException extends RuntimeException {
|
||||
ACTOR_REQUIRED,
|
||||
CLIPBOARD,
|
||||
HISTORY,
|
||||
ANVIL_IO,
|
||||
OTHER
|
||||
}
|
||||
|
||||
|
@ -1,434 +0,0 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
@ -95,10 +95,9 @@ 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) {
|
||||
if (this.size < newSize) {
|
||||
this.size = newSize;
|
||||
}
|
||||
return;
|
||||
|
@ -31,8 +31,11 @@
|
||||
"fawe.worldedit.paste.command.paste": "The clipboard has been pasted at {0}",
|
||||
"fawe.worldedit.history.command.undo.disabled": "Undo disabled, use: //fast",
|
||||
"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.error.mca.init.failed": "Failed to initialise MCA file.",
|
||||
"fawe.worldedit.anvil.error.world.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.anvil.replace.complete": "Replace with anvil completed.",
|
||||
"fawe.worldedit.anvil.set.complete": "Set with anvil completed.",
|
||||
"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.scroll.action.set": "Set scroll action to {0}",
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren