Mirror von
https://github.com/IntellectualSites/FastAsyncWorldEdit.git
synchronisiert 2024-11-16 16:10:07 +01:00
fix: improve FAWE stream history (#2844)
- reset "origin"/relative X/Z when larger than short min/max value so we do not write incorrect positions - history size can be larger than int max value - fixes #2583
Dieser Commit ist enthalten in:
Ursprung
6052fc3128
Commit
d1f9d3d6d5
@ -91,7 +91,7 @@ public class CopyPastaBrush implements Brush, ResettableTool {
|
||||
newClipboard.setOrigin(position);
|
||||
ClipboardHolder holder = new ClipboardHolder(newClipboard);
|
||||
session.setClipboard(holder);
|
||||
int blocks = builder.size();
|
||||
long blocks = builder.longSize();
|
||||
player.print(Caption.of("fawe.worldedit.copy.command.copy", blocks));
|
||||
} else {
|
||||
AffineTransform transform = null;
|
||||
|
@ -64,26 +64,47 @@ public class RollbackDatabase extends AsyncNotifyQueue {
|
||||
public Future<Boolean> init() {
|
||||
return call(() -> {
|
||||
try (PreparedStatement stmt = connection.prepareStatement("CREATE TABLE IF NOT EXISTS`" + this.prefix +
|
||||
"edits` (`player` BLOB(16) NOT NULL,`id` INT NOT NULL, `time` INT NOT NULL,`x1`" +
|
||||
"INT NOT NULL,`x2` INT NOT NULL,`z1` INT NOT NULL,`z2` INT NOT NULL,`y1`" +
|
||||
"INT NOT NULL, `y2` INT NOT NULL, `size` INT NOT NULL, `command` VARCHAR, PRIMARY KEY (player, id))")) {
|
||||
"_edits` (`player` BLOB(16) NOT NULL,`id` INT NOT NULL, `time` INT NOT NULL,`x1` " +
|
||||
"INT NOT NULL,`x2` INT NOT NULL,`z1` INT NOT NULL,`z2` INT NOT NULL,`y1` " +
|
||||
"INT NOT NULL, `y2` INT NOT NULL, `size` BIGINT NOT NULL, `command` VARCHAR, PRIMARY KEY (player, id))")) {
|
||||
stmt.executeUpdate();
|
||||
}
|
||||
try (PreparedStatement stmt = connection.prepareStatement("ALTER TABLE`" + this.prefix + "edits` ADD COLUMN `command` VARCHAR")) {
|
||||
String alterTablePrefix = "ALTER TABLE`" + this.prefix + "edits` ";
|
||||
try (PreparedStatement stmt =
|
||||
connection.prepareStatement(alterTablePrefix + "ADD COLUMN `command` VARCHAR")) {
|
||||
stmt.executeUpdate();
|
||||
} catch (SQLException ignored) {
|
||||
} // Already updated
|
||||
try (PreparedStatement stmt = connection.prepareStatement("ALTER TABLE`" + this.prefix + "edits` ADD SIZE INT DEFAULT 0 NOT NULL")) {
|
||||
try (PreparedStatement stmt =
|
||||
connection.prepareStatement(alterTablePrefix + "ADD COLUMN `size` BIGINT DEFAULT 0 NOT NULL")) {
|
||||
stmt.executeUpdate();
|
||||
} catch (SQLException ignored) {
|
||||
} // Already updated
|
||||
|
||||
boolean migrated = false;
|
||||
try (PreparedStatement stmt =
|
||||
connection.prepareStatement("INSERT INTO `" + this.prefix + "_edits` " +
|
||||
"(player, id, time, x1, x2, z1, z2, y1, y2, size, command) " +
|
||||
"SELECT player, id, time, x1, x2, z1, z2, y1, y2, size, command " +
|
||||
"FROM `" + this.prefix + "edits`")) {
|
||||
|
||||
stmt.executeUpdate();
|
||||
migrated = true;
|
||||
} catch (SQLException ignored) {
|
||||
} // Already updated
|
||||
if (migrated) {
|
||||
try (PreparedStatement stmt = connection.prepareStatement("DROP TABLE `" + this.prefix + "edits`")) {
|
||||
stmt.executeUpdate();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
public Future<Integer> delete(UUID uuid, int id) {
|
||||
return call(() -> {
|
||||
try (PreparedStatement stmt = connection.prepareStatement("DELETE FROM`" + this.prefix + "edits` WHERE `player`=? AND `id`=?")) {
|
||||
try (PreparedStatement stmt = connection.prepareStatement("DELETE FROM`" + this.prefix + "_edits` WHERE `player`=? " +
|
||||
"AND `id`=?")) {
|
||||
stmt.setBytes(1, toBytes(uuid));
|
||||
stmt.setInt(2, id);
|
||||
return stmt.executeUpdate();
|
||||
@ -94,7 +115,7 @@ public class RollbackDatabase extends AsyncNotifyQueue {
|
||||
public Future<RollbackOptimizedHistory> getEdit(@Nonnull UUID uuid, int id) {
|
||||
return call(() -> {
|
||||
try (PreparedStatement stmt = connection.prepareStatement("SELECT * FROM`" + this.prefix +
|
||||
"edits` WHERE `player`=? AND `id`=?")) {
|
||||
"_edits` WHERE `player`=? AND `id`=?")) {
|
||||
stmt.setBytes(1, toBytes(uuid));
|
||||
stmt.setInt(2, id);
|
||||
ResultSet result = stmt.executeQuery();
|
||||
@ -119,7 +140,7 @@ public class RollbackDatabase extends AsyncNotifyQueue {
|
||||
CuboidRegion region = new CuboidRegion(BlockVector3.at(x1, y1, z1), BlockVector3.at(x2, y2, z2));
|
||||
|
||||
long time = result.getInt("time") * 1000L;
|
||||
long size = result.getInt("size");
|
||||
long size = result.getLong("size");
|
||||
|
||||
String command = result.getString("command");
|
||||
|
||||
@ -135,7 +156,7 @@ public class RollbackDatabase extends AsyncNotifyQueue {
|
||||
long now = System.currentTimeMillis() / 1000;
|
||||
final int then = (int) (now - diff);
|
||||
return call(() -> {
|
||||
try (PreparedStatement stmt = connection.prepareStatement("DELETE FROM`" + this.prefix + "edits` WHERE `time`<?")) {
|
||||
try (PreparedStatement stmt = connection.prepareStatement("DELETE FROM`" + this.prefix + "_edits` WHERE `time`<?")) {
|
||||
stmt.setInt(1, then);
|
||||
return stmt.executeUpdate();
|
||||
}
|
||||
@ -160,7 +181,7 @@ public class RollbackDatabase extends AsyncNotifyQueue {
|
||||
try {
|
||||
int count = 0;
|
||||
String stmtStr = """
|
||||
SELECT * FROM `%sedits`
|
||||
SELECT * FROM `%s_edits`
|
||||
WHERE `time` > ?
|
||||
AND `x2` >= ?
|
||||
AND `x1` <= ?
|
||||
@ -202,7 +223,8 @@ public class RollbackDatabase extends AsyncNotifyQueue {
|
||||
}
|
||||
if (delete && uuid != null) {
|
||||
try (PreparedStatement stmt = connection.prepareStatement("DELETE FROM`" + this.prefix +
|
||||
"edits` WHERE `player`=? AND `time`>? AND `x2`>=? AND `x1`<=? AND `y2`>=? AND `y1`<=? AND `z2`>=? AND `z1`<=?")) {
|
||||
"_edits` WHERE `player`=? AND `time`>? AND `x2`>=? AND `x1`<=? AND `y2`>=? AND `y1`<=? AND `z2`>=? " +
|
||||
"AND `z1`<=?")) {
|
||||
byte[] uuidBytes = ByteBuffer
|
||||
.allocate(16)
|
||||
.putLong(uuid.getMostSignificantBits())
|
||||
@ -249,7 +271,7 @@ public class RollbackDatabase extends AsyncNotifyQueue {
|
||||
RollbackOptimizedHistory[] copy = IntStream.range(0, size)
|
||||
.mapToObj(i -> historyChanges.poll()).toArray(RollbackOptimizedHistory[]::new);
|
||||
|
||||
try (PreparedStatement stmt = connection.prepareStatement("INSERT OR REPLACE INTO`" + this.prefix + "edits`" +
|
||||
try (PreparedStatement stmt = connection.prepareStatement("INSERT OR REPLACE INTO`" + this.prefix + "_edits`" +
|
||||
" (`player`,`id`,`time`,`x1`,`x2`,`z1`,`z2`,`y1`,`y2`,`command`,`size`) VALUES(?,?,?,?,?,?,?,?,?,?,?)")) {
|
||||
// `player`,`id`,`time`,`x1`,`x2`,`z1`,`z2`,`y1`,`y2`,`command`,`size`) VALUES(?,?,?,?,?,?,?,?,?,?,?)"
|
||||
for (RollbackOptimizedHistory change : copy) {
|
||||
@ -270,7 +292,7 @@ public class RollbackDatabase extends AsyncNotifyQueue {
|
||||
stmt.setInt(8, pos1.y() - 128);
|
||||
stmt.setInt(9, pos2.y() - 128);
|
||||
stmt.setString(10, change.getCommand());
|
||||
stmt.setInt(11, change.size());
|
||||
stmt.setLong(11, change.longSize());
|
||||
stmt.executeUpdate();
|
||||
stmt.clearParameters();
|
||||
}
|
||||
|
@ -306,7 +306,7 @@ public abstract class AbstractChangeSet implements ChangeSet, IBatchProcessor {
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return queue.isEmpty() && workerSemaphore.availablePermits() == 1 && size() == 0;
|
||||
return queue.isEmpty() && workerSemaphore.availablePermits() == 1 && longSize() == 0;
|
||||
}
|
||||
|
||||
public void add(BlockVector3 loc, BaseBlock from, BaseBlock to) {
|
||||
|
@ -176,6 +176,11 @@ public class AbstractDelegateChangeSet extends AbstractChangeSet {
|
||||
return parent.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long longSize() {
|
||||
return parent.longSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete() {
|
||||
parent.delete();
|
||||
|
@ -25,6 +25,7 @@ import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
@ -35,11 +36,18 @@ import java.util.NoSuchElementException;
|
||||
public abstract class FaweStreamChangeSet extends AbstractChangeSet {
|
||||
|
||||
public static final int HEADER_SIZE = 9;
|
||||
private static final int version = 1;
|
||||
private static final int VERSION = 2;
|
||||
// equivalent to Short#MIN_VALUE three times stored with [(x) & 0xff, ((rx) >> 8) & 0xff]
|
||||
private static final byte[] MAGIC_NEW_RELATIVE = new byte[]{0, (byte) 128, 0, (byte) 128, 0, (byte) 128};
|
||||
private int mode;
|
||||
private final int compression;
|
||||
private final int minY;
|
||||
|
||||
protected long blockSize;
|
||||
private int originX;
|
||||
private int originZ;
|
||||
private int version;
|
||||
|
||||
protected FaweStreamIdDelegate idDel;
|
||||
protected FaweStreamPositionDelegate posDel;
|
||||
|
||||
@ -192,6 +200,20 @@ public abstract class FaweStreamChangeSet extends AbstractChangeSet {
|
||||
int rx = -lx + (lx = x);
|
||||
int ry = -ly + (ly = y);
|
||||
int rz = -lz + (lz = z);
|
||||
// Use LE/GE to ensure we don't accidentally write MAGIC_NEW_RELATIVE
|
||||
if (rx >= Short.MAX_VALUE || rz >= Short.MAX_VALUE || rx <= Short.MIN_VALUE || rz <= Short.MIN_VALUE) {
|
||||
stream.write(MAGIC_NEW_RELATIVE);
|
||||
stream.write((byte) (x >> 24));
|
||||
stream.write((byte) (x >> 16));
|
||||
stream.write((byte) (x >> 8));
|
||||
stream.write((byte) (x));
|
||||
stream.write((byte) (z >> 24));
|
||||
stream.write((byte) (z >> 16));
|
||||
stream.write((byte) (z >> 8));
|
||||
stream.write((byte) (z));
|
||||
rx = 0;
|
||||
rz = 0;
|
||||
}
|
||||
stream.write((rx) & 0xff);
|
||||
stream.write(((rx) >> 8) & 0xff);
|
||||
stream.write((rz) & 0xff);
|
||||
@ -203,6 +225,12 @@ public abstract class FaweStreamChangeSet extends AbstractChangeSet {
|
||||
@Override
|
||||
public int readX(FaweInputStream is) throws IOException {
|
||||
is.readFully(buffer);
|
||||
// Don't break reading version 1 history (just in case)
|
||||
if (version == 2 && Arrays.equals(buffer, MAGIC_NEW_RELATIVE)) {
|
||||
lx = ((is.read() << 24) + (is.read() << 16) + (is.read() << 8) + is.read());
|
||||
lz = ((is.read() << 24) + (is.read() << 16) + (is.read() << 8) + is.read());
|
||||
is.readFully(buffer);
|
||||
}
|
||||
return lx = lx + ((buffer[0] & 0xFF) | (buffer[1] << 8));
|
||||
}
|
||||
|
||||
@ -222,7 +250,7 @@ public abstract class FaweStreamChangeSet extends AbstractChangeSet {
|
||||
public void writeHeader(OutputStream os, int x, int y, int z) throws IOException {
|
||||
os.write(mode);
|
||||
// Allows for version detection of history in case of changes to format.
|
||||
os.write(version);
|
||||
os.write(VERSION);
|
||||
setOrigin(x, z);
|
||||
os.write((byte) (x >> 24));
|
||||
os.write((byte) (x >> 16));
|
||||
@ -238,8 +266,8 @@ public abstract class FaweStreamChangeSet extends AbstractChangeSet {
|
||||
public void readHeader(InputStream is) throws IOException {
|
||||
// skip mode
|
||||
int mode = is.read();
|
||||
int version = is.read();
|
||||
if (version != FaweStreamChangeSet.version) {
|
||||
version = is.read();
|
||||
if (version != 1 && version != VERSION) { // version 1 is fine
|
||||
throw new UnsupportedOperationException(String.format("Version %s history not supported!", version));
|
||||
}
|
||||
// origin
|
||||
@ -266,12 +294,17 @@ public abstract class FaweStreamChangeSet extends AbstractChangeSet {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
public long longSize() {
|
||||
// Flush so we can accurately get the size
|
||||
flush();
|
||||
return blockSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return (int) longSize();
|
||||
}
|
||||
|
||||
public abstract int getCompressedSize();
|
||||
|
||||
public abstract long getSizeInMemory();
|
||||
@ -304,11 +337,6 @@ public abstract class FaweStreamChangeSet extends AbstractChangeSet {
|
||||
|
||||
public abstract NBTInputStream getTileRemoveIS() throws IOException;
|
||||
|
||||
protected int blockSize;
|
||||
|
||||
private int originX;
|
||||
private int originZ;
|
||||
|
||||
public void setOrigin(int x, int z) {
|
||||
originX = x;
|
||||
originZ = z;
|
||||
|
@ -217,7 +217,7 @@ public class MainUtil {
|
||||
// } else if (changeSet instanceof CPUOptimizedChangeSet) {
|
||||
// return changeSet.size() + 32;
|
||||
} else if (changeSet != null) {
|
||||
return changeSet.size() * 128L;
|
||||
return changeSet.longSize() * 128; // Approx
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
@ -500,7 +500,7 @@ public class LocalSession implements TextureHolder {
|
||||
if (Settings.settings().HISTORY.USE_DISK) {
|
||||
LocalSession.MAX_HISTORY_SIZE = Integer.MAX_VALUE;
|
||||
}
|
||||
if (changeSet.size() == 0) {
|
||||
if (changeSet.longSize() == 0) {
|
||||
return;
|
||||
}
|
||||
loadSessionHistoryFromDisk(player.getUniqueId(), world);
|
||||
|
@ -251,7 +251,7 @@ public class HistorySubCommands {
|
||||
long seconds = (System.currentTimeMillis() - edit.getBDFile().lastModified()) / 1000;
|
||||
String timeStr = MainUtil.secToTime(seconds);
|
||||
|
||||
int size = edit.size();
|
||||
long size = edit.longSize();
|
||||
boolean biomes = edit.getBioFile().exists();
|
||||
boolean createdEnts = edit.getEnttFile().exists();
|
||||
boolean removedEnts = edit.getEntfFile().exists();
|
||||
@ -335,7 +335,7 @@ public class HistorySubCommands {
|
||||
long seconds = (System.currentTimeMillis() - rollback.getBDFile().lastModified()) / 1000;
|
||||
String timeStr = MainUtil.secToTime(seconds);
|
||||
|
||||
int size = edit.size();
|
||||
long size = edit.longSize();
|
||||
|
||||
TranslatableComponent elem = Caption.of(
|
||||
"fawe.worldedit.history.find.element",
|
||||
|
@ -81,11 +81,24 @@ public interface ChangeSet extends Closeable {
|
||||
* Get the number of stored changes.
|
||||
*
|
||||
* @return the change count
|
||||
* @deprecated History could be larger than int max value so FAWE prefers {@link ChangeSet#longSize()}
|
||||
*/
|
||||
@Deprecated(since = "TODO")
|
||||
int size();
|
||||
|
||||
//FAWE start
|
||||
|
||||
/**
|
||||
* Get the number of stored changes.
|
||||
* History could be larger than int max value so FAWE prefers this method.
|
||||
*
|
||||
* @return the change count
|
||||
* @since TODO
|
||||
*/
|
||||
default long longSize() {
|
||||
return size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the changeset.
|
||||
*/
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren