mirror of
https://github.com/PlayPro/CoreProtect.git
synced 2026-10-28 09:44:56 +00:00
Optimize rollback chunk batching and prefetching
This commit is contained in:
1 parent
a0ae242dac
commit
9d04c025be
13 files changed
+384
-257
No files matched your search
@@ -1,8 +1,11 @@
|
||||
package net.coreprotect.consumer.process;
|
||||
|
||||
import java.sql.Statement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import net.coreprotect.consumer.Consumer;
|
||||
import net.coreprotect.database.Database;
|
||||
@@ -15,13 +18,19 @@ class RollbackUpdateProcess {
|
||||
Map<Integer, List<Object[]>> updateLists = Consumer.consumerObjectArrayList.get(processId);
|
||||
if (updateLists.get(id) != null) {
|
||||
List<Object[]> list = updateLists.get(id);
|
||||
boolean inventoryState = RollbackUpdateTargets.usesInventoryRollbackState(table);
|
||||
Map<Integer, List<Long>> rowIdsByValue = new HashMap<>();
|
||||
for (Object[] listRow : list) {
|
||||
long rowid = (Long) listRow[0];
|
||||
int rolledBack = (Integer) listRow[9];
|
||||
if (MaterialUtils.rolledBack(rolledBack, RollbackUpdateTargets.usesInventoryRollbackState(table)) == action) { // 1 = restore, 0 = rollback
|
||||
Database.performUpdate(statement, rowid, rolledBack, table);
|
||||
if (MaterialUtils.rolledBack(rolledBack, inventoryState) == action) { // 1 = restore, 0 = rollback
|
||||
int toggledValue = MaterialUtils.toggleRolledBack(rolledBack, inventoryState);
|
||||
rowIdsByValue.computeIfAbsent(toggledValue, key -> new ArrayList<>()).add(rowid);
|
||||
}
|
||||
}
|
||||
for (Entry<Integer, List<Long>> entry : rowIdsByValue.entrySet()) {
|
||||
Database.performRolledBackUpdate(statement, entry.getKey(), entry.getValue(), table);
|
||||
}
|
||||
updateLists.remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ import net.coreprotect.utility.Chat;
|
||||
import net.coreprotect.utility.Color;
|
||||
import net.coreprotect.utility.HopperTransactionUtils;
|
||||
import net.coreprotect.utility.ItemUtils;
|
||||
import net.coreprotect.utility.MaterialUtils;
|
||||
import net.coreprotect.utility.ErrorReporter;
|
||||
|
||||
public class Database extends Queue {
|
||||
@@ -50,6 +49,8 @@ public class Database extends Queue {
|
||||
public static final int BLOCKDATA = 12;
|
||||
public static final int ITEM = 13;
|
||||
|
||||
private static final int ROLLED_BACK_UPDATE_BATCH_SIZE = 1000;
|
||||
|
||||
private static final Map<Integer, String> SQL_QUERIES = new HashMap<>();
|
||||
|
||||
static {
|
||||
@@ -224,17 +225,31 @@ public class Database extends Queue {
|
||||
}
|
||||
}
|
||||
|
||||
public static void performUpdate(Statement statement, long id, int rb, int table) {
|
||||
public static void performRolledBackUpdate(Statement statement, int rolledBack, List<Long> rowIds, int table) {
|
||||
String tableName;
|
||||
if (RollbackUpdateTargets.updatesContainerTable(table)) {
|
||||
tableName = "container";
|
||||
}
|
||||
else if (RollbackUpdateTargets.updatesItemTable(table)) {
|
||||
tableName = "item";
|
||||
}
|
||||
else {
|
||||
tableName = "block";
|
||||
}
|
||||
|
||||
try {
|
||||
int rolledBack = MaterialUtils.toggleRolledBack(rb, RollbackUpdateTargets.usesInventoryRollbackState(table));
|
||||
if (RollbackUpdateTargets.updatesContainerTable(table)) {
|
||||
statement.executeUpdate("UPDATE " + ConfigHandler.prefix + "container SET rolled_back='" + rolledBack + "' WHERE rowid='" + id + "'");
|
||||
}
|
||||
else if (RollbackUpdateTargets.updatesItemTable(table)) {
|
||||
statement.executeUpdate("UPDATE " + ConfigHandler.prefix + "item SET rolled_back='" + rolledBack + "' WHERE rowid='" + id + "'");
|
||||
}
|
||||
else {
|
||||
statement.executeUpdate("UPDATE " + ConfigHandler.prefix + "block SET rolled_back='" + rolledBack + "' WHERE rowid='" + id + "'");
|
||||
int listSize = rowIds.size();
|
||||
for (int startIndex = 0; startIndex < listSize; startIndex += ROLLED_BACK_UPDATE_BATCH_SIZE) {
|
||||
int endIndex = Math.min(startIndex + ROLLED_BACK_UPDATE_BATCH_SIZE, listSize);
|
||||
StringBuilder query = new StringBuilder("UPDATE " + ConfigHandler.prefix + tableName + " SET rolled_back='" + rolledBack + "' WHERE rowid IN(");
|
||||
for (int index = startIndex; index < endIndex; index++) {
|
||||
if (index > startIndex) {
|
||||
query.append(",");
|
||||
}
|
||||
query.append(rowIds.get(index).longValue());
|
||||
}
|
||||
query.append(")");
|
||||
statement.executeUpdate(query.toString());
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
|
||||
@@ -20,7 +20,6 @@ import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import net.coreprotect.CoreProtect;
|
||||
import net.coreprotect.bukkit.BukkitAdapter;
|
||||
import net.coreprotect.config.Config;
|
||||
import net.coreprotect.config.ConfigHandler;
|
||||
import net.coreprotect.consumer.Queue;
|
||||
@@ -33,7 +32,9 @@ import net.coreprotect.language.Selector;
|
||||
import net.coreprotect.model.BlockGroup;
|
||||
import net.coreprotect.model.action.LookupActions;
|
||||
import net.coreprotect.model.rollback.RollbackUpdateTargets;
|
||||
import net.coreprotect.paper.PaperAdapter;
|
||||
import net.coreprotect.thread.Scheduler;
|
||||
import net.coreprotect.thread.TickTimeMonitor;
|
||||
import net.coreprotect.utility.Chat;
|
||||
import net.coreprotect.utility.Color;
|
||||
import net.coreprotect.utility.DatabaseUtils;
|
||||
@@ -42,7 +43,11 @@ import net.coreprotect.utility.ErrorReporter;
|
||||
|
||||
public class Rollback extends RollbackUtil {
|
||||
|
||||
private static final long ROLLBACK_BATCH_BUDGET_NANOS = 25_000_000L;
|
||||
private static final long ROLLBACK_BATCH_BUDGET_BASELINE_NANOS = 25_000_000L;
|
||||
private static final long ROLLBACK_BATCH_BUDGET_FLOOR_NANOS = 20_000_000L;
|
||||
private static final long ROLLBACK_BATCH_BUDGET_CEILING_NANOS = 50_000_000L;
|
||||
private static final long ROLLBACK_BATCH_TICK_YIELD_NANOS = 50_000_000L;
|
||||
private static final int CHUNK_PREFETCH_DISTANCE = 8;
|
||||
|
||||
public static List<String[]> performRollbackRestore(Statement statement, CommandSender user, List<String> checkUuids, List<String> checkUsers, String timeString, List<Object> restrictList, Map<Object, Boolean> excludeList, List<String> excludeUserList, List<Integer> actionList, Location location, Integer[] radius, long startTime, long endTime, boolean restrictWorld, boolean lookup, boolean verbose, final int rollbackType, final int preview) {
|
||||
List<String[]> list = new ArrayList<>();
|
||||
@@ -96,10 +101,6 @@ public class Rollback extends RollbackUtil {
|
||||
HashMap<Integer, HashMap<Long, ArrayList<Object[]>>> itemDataList = new HashMap<>();
|
||||
boolean inventoryRollback = actionList.contains(LookupActions.ITEM);
|
||||
|
||||
int worldId = -1;
|
||||
int worldMin = 0;
|
||||
int worldMax = 2032;
|
||||
|
||||
int listC = 0;
|
||||
while (listC < 2) {
|
||||
List<Object[]> scanList = lookupList;
|
||||
@@ -118,15 +119,6 @@ public class Rollback extends RollbackUtil {
|
||||
int chunkZ = rowZ >> 4;
|
||||
long chunkKey = inventoryRollback ? 0 : (chunkX & 0xffffffffL | (chunkZ & 0xffffffffL) << 32);
|
||||
|
||||
if (rowWorldId != worldId) {
|
||||
String world = WorldUtils.getWorldName(rowWorldId);
|
||||
World bukkitWorld = Bukkit.getServer().getWorld(world);
|
||||
if (bukkitWorld != null) {
|
||||
worldMin = BukkitAdapter.ADAPTER.getMinHeight(bukkitWorld);
|
||||
worldMax = bukkitWorld.getMaxHeight();
|
||||
}
|
||||
}
|
||||
|
||||
if (chunkList.get(chunkKey) == null) {
|
||||
int distance = 0;
|
||||
if (location != null) {
|
||||
@@ -219,12 +211,13 @@ public class Rollback extends RollbackUtil {
|
||||
ConfigHandler.rollbackHash.put(userString, new int[] { 0, 0, 0, 0, 0 });
|
||||
|
||||
final String finalUserString = userString;
|
||||
RollbackBlockDataCache blockDataCache = new RollbackBlockDataCache();
|
||||
List<Entry<Long, Integer>> sortedChunks = new ArrayList<>(DatabaseUtils.entriesSortedByValues(chunkList));
|
||||
if (ConfigHandler.isFolia) {
|
||||
chunkCount = processFoliaChunks(sortedChunks, worldList, dataList, itemDataList, rollbackType, preview, finalUserString, user, inventoryRollback, verbose, actionList);
|
||||
chunkCount = processFoliaChunks(sortedChunks, worldList, dataList, itemDataList, rollbackType, preview, finalUserString, user, inventoryRollback, verbose, actionList, blockDataCache);
|
||||
}
|
||||
else {
|
||||
chunkCount = processBukkitChunks(sortedChunks, worldList, dataList, itemDataList, rollbackType, preview, finalUserString, user, inventoryRollback, verbose, actionList);
|
||||
chunkCount = processBukkitChunks(sortedChunks, worldList, dataList, itemDataList, rollbackType, preview, finalUserString, user, inventoryRollback, verbose, actionList, blockDataCache);
|
||||
}
|
||||
|
||||
chunkList.clear();
|
||||
@@ -252,7 +245,7 @@ public class Rollback extends RollbackUtil {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static int processFoliaChunks(List<Entry<Long, Integer>> sortedChunks, LinkedHashSet<Integer> worldList, HashMap<Integer, HashMap<Long, ArrayList<Object[]>>> dataList, HashMap<Integer, HashMap<Long, ArrayList<Object[]>>> itemDataList, int rollbackType, int preview, String userString, CommandSender user, boolean inventoryRollback, boolean verbose, List<Integer> actionList) throws InterruptedException {
|
||||
private static int processFoliaChunks(List<Entry<Long, Integer>> sortedChunks, LinkedHashSet<Integer> worldList, HashMap<Integer, HashMap<Long, ArrayList<Object[]>>> dataList, HashMap<Integer, HashMap<Long, ArrayList<Object[]>>> itemDataList, int rollbackType, int preview, String userString, CommandSender user, boolean inventoryRollback, boolean verbose, List<Integer> actionList, RollbackBlockDataCache blockDataCache) throws InterruptedException {
|
||||
FoliaRollbackBatchState batchState = new FoliaRollbackBatchState(buildFoliaChunkWork(sortedChunks, worldList, dataList, itemDataList), sortedChunks.size());
|
||||
while (batchState.hasNext()) {
|
||||
if (!processFoliaEmptyChunks(batchState, userString, verbose, user, preview, actionList)) {
|
||||
@@ -263,7 +256,7 @@ public class Rollback extends RollbackUtil {
|
||||
break;
|
||||
}
|
||||
|
||||
CompletableFuture<Boolean> batchFuture = scheduleFoliaChunkBatchTask(batchState, rollbackType, preview, userString, user, inventoryRollback, verbose, actionList);
|
||||
CompletableFuture<Boolean> batchFuture = scheduleFoliaChunkBatchTask(batchState, rollbackType, preview, userString, user, inventoryRollback, verbose, actionList, blockDataCache);
|
||||
if (!awaitChunkTasks(Collections.singletonList(batchFuture), preview)) {
|
||||
Chat.console(Phrase.build(Phrase.ROLLBACK_ABORTED));
|
||||
break;
|
||||
@@ -316,7 +309,7 @@ public class Rollback extends RollbackUtil {
|
||||
return true;
|
||||
}
|
||||
|
||||
private static CompletableFuture<Boolean> scheduleFoliaChunkBatchTask(FoliaRollbackBatchState batchState, int rollbackType, int preview, String userString, CommandSender user, boolean inventoryRollback, boolean verbose, List<Integer> actionList) {
|
||||
private static CompletableFuture<Boolean> scheduleFoliaChunkBatchTask(FoliaRollbackBatchState batchState, int rollbackType, int preview, String userString, CommandSender user, boolean inventoryRollback, boolean verbose, List<Integer> actionList, RollbackBlockDataCache blockDataCache) {
|
||||
CompletableFuture<Boolean> future = new CompletableFuture<>();
|
||||
FoliaChunkWork firstWork = batchState.peek();
|
||||
Location chunkLocation = new Location(firstWork.world, (firstWork.chunkX << 4), 0, (firstWork.chunkZ << 4));
|
||||
@@ -328,7 +321,7 @@ public class Rollback extends RollbackUtil {
|
||||
FoliaChunkWork work = batchState.next();
|
||||
|
||||
prepareChunkCounters(userString);
|
||||
if (!processChunkWorld(work.chunkX, work.chunkZ, work.chunkKey, work.blockList, work.itemList, rollbackType, preview, userString, user, work.world, inventoryRollback)) {
|
||||
if (!processChunkWorld(work.chunkX, work.chunkZ, work.chunkKey, work.blockList, work.itemList, rollbackType, preview, userString, user, work.world, inventoryRollback, blockDataCache)) {
|
||||
future.complete(false);
|
||||
return;
|
||||
}
|
||||
@@ -354,53 +347,118 @@ public class Rollback extends RollbackUtil {
|
||||
return future;
|
||||
}
|
||||
|
||||
private static int processBukkitChunks(List<Entry<Long, Integer>> sortedChunks, LinkedHashSet<Integer> worldList, HashMap<Integer, HashMap<Long, ArrayList<Object[]>>> dataList, HashMap<Integer, HashMap<Long, ArrayList<Object[]>>> itemDataList, int rollbackType, int preview, String userString, CommandSender user, boolean inventoryRollback, boolean verbose, List<Integer> actionList) throws InterruptedException {
|
||||
private static int processBukkitChunks(List<Entry<Long, Integer>> sortedChunks, LinkedHashSet<Integer> worldList, HashMap<Integer, HashMap<Long, ArrayList<Object[]>>> dataList, HashMap<Integer, HashMap<Long, ArrayList<Object[]>>> itemDataList, int rollbackType, int preview, String userString, CommandSender user, boolean inventoryRollback, boolean verbose, List<Integer> actionList, RollbackBlockDataCache blockDataCache) throws InterruptedException {
|
||||
HashMap<Integer, World> worldMap = getRollbackWorlds(worldList);
|
||||
RollbackBatchState batchState = new RollbackBatchState(sortedChunks);
|
||||
while (batchState.hasNext()) {
|
||||
CompletableFuture<Boolean> batchFuture = scheduleChunkBatchTask(batchState, worldList, dataList, itemDataList, rollbackType, preview, userString, user, inventoryRollback, verbose, actionList);
|
||||
if (!awaitChunkTasks(Collections.singletonList(batchFuture), preview)) {
|
||||
Chat.console(Phrase.build(Phrase.ROLLBACK_ABORTED));
|
||||
break;
|
||||
}
|
||||
if (!batchState.hasNext()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
CompletableFuture<Boolean> completion = new CompletableFuture<>();
|
||||
scheduleChunkBatchTask(batchState, worldMap, dataList, itemDataList, rollbackType, preview, userString, user, inventoryRollback, verbose, actionList, blockDataCache, completion, 0);
|
||||
if (!awaitRollbackCompletion(completion, batchState, preview)) {
|
||||
Chat.console(Phrase.build(Phrase.ROLLBACK_ABORTED));
|
||||
}
|
||||
|
||||
return batchState.chunkCount;
|
||||
}
|
||||
|
||||
private static CompletableFuture<Boolean> scheduleChunkBatchTask(RollbackBatchState batchState, LinkedHashSet<Integer> worldList, HashMap<Integer, HashMap<Long, ArrayList<Object[]>>> dataList, HashMap<Integer, HashMap<Long, ArrayList<Object[]>>> itemDataList, int rollbackType, int preview, String userString, CommandSender user, boolean inventoryRollback, boolean verbose, List<Integer> actionList) {
|
||||
CompletableFuture<Boolean> future = new CompletableFuture<>();
|
||||
private static void scheduleChunkBatchTask(RollbackBatchState batchState, HashMap<Integer, World> worldMap, HashMap<Integer, HashMap<Long, ArrayList<Object[]>>> dataList, HashMap<Integer, HashMap<Long, ArrayList<Object[]>>> itemDataList, int rollbackType, int preview, String userString, CommandSender user, boolean inventoryRollback, boolean verbose, List<Integer> actionList, RollbackBlockDataCache blockDataCache, CompletableFuture<Boolean> completion, int delay) {
|
||||
Scheduler.scheduleSyncDelayedTask(CoreProtect.getInstance(), () -> {
|
||||
if (completion.isDone()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
long batchBudget = adaptiveBatchBudgetNanos();
|
||||
long batchStart = System.nanoTime();
|
||||
do {
|
||||
prefetchUpcomingChunks(batchState, worldMap, inventoryRollback);
|
||||
Entry<Long, Integer> entry = batchState.next();
|
||||
batchState.chunkCount++;
|
||||
|
||||
if (!processChunkEntry(entry, worldList, dataList, itemDataList, rollbackType, preview, userString, user, inventoryRollback)) {
|
||||
future.complete(false);
|
||||
if (!processChunkEntry(entry, worldMap, dataList, itemDataList, rollbackType, preview, userString, user, inventoryRollback, blockDataCache)) {
|
||||
completion.complete(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!completeChunk(userString, batchState.chunkCount, batchState.totalChunks(), verbose, user, preview, actionList)) {
|
||||
future.complete(false);
|
||||
completion.complete(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
while (batchState.hasNext() && (System.nanoTime() - batchStart) < ROLLBACK_BATCH_BUDGET_NANOS);
|
||||
while (batchState.hasNext() && (System.nanoTime() - batchStart) < batchBudget);
|
||||
|
||||
future.complete(true);
|
||||
if (batchState.hasNext()) {
|
||||
batchState.nanosSinceTickYield += (System.nanoTime() - batchStart);
|
||||
int nextDelay = 0;
|
||||
if (batchState.nanosSinceTickYield >= ROLLBACK_BATCH_TICK_YIELD_NANOS) {
|
||||
batchState.nanosSinceTickYield = 0;
|
||||
nextDelay = 1;
|
||||
}
|
||||
scheduleChunkBatchTask(batchState, worldMap, dataList, itemDataList, rollbackType, preview, userString, user, inventoryRollback, verbose, actionList, blockDataCache, completion, nextDelay);
|
||||
}
|
||||
else {
|
||||
completion.complete(true);
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
ErrorReporter.report(e);
|
||||
future.complete(false);
|
||||
completion.complete(false);
|
||||
}
|
||||
}, 0);
|
||||
}, delay);
|
||||
}
|
||||
|
||||
return future;
|
||||
private static boolean awaitRollbackCompletion(CompletableFuture<Boolean> completion, RollbackBatchState batchState, int preview) throws InterruptedException {
|
||||
int delay = preview == 1 ? 1 : 5;
|
||||
int lastChunkCount = -1;
|
||||
long stalledTime = 0;
|
||||
|
||||
while (!completion.isDone()) {
|
||||
int chunkCount = batchState.chunkCount;
|
||||
if (chunkCount != lastChunkCount) {
|
||||
lastChunkCount = chunkCount;
|
||||
stalledTime = 0;
|
||||
}
|
||||
|
||||
stalledTime += delay;
|
||||
if (stalledTime > 300000) {
|
||||
completion.complete(false);
|
||||
return false;
|
||||
}
|
||||
Thread.sleep(delay);
|
||||
}
|
||||
|
||||
try {
|
||||
return Boolean.TRUE.equals(completion.getNow(Boolean.FALSE));
|
||||
}
|
||||
catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static long adaptiveBatchBudgetNanos() {
|
||||
double averageTickTime = PaperAdapter.ADAPTER.getAverageTickTime(Bukkit.getServer());
|
||||
if (averageTickTime <= 0.0D) {
|
||||
averageTickTime = TickTimeMonitor.getEstimatedTickTime();
|
||||
}
|
||||
|
||||
return batchBudgetNanos(averageTickTime);
|
||||
}
|
||||
|
||||
public static long batchBudgetNanos(double averageTickTime) {
|
||||
if (averageTickTime <= 0.0D) {
|
||||
return ROLLBACK_BATCH_BUDGET_BASELINE_NANOS;
|
||||
}
|
||||
|
||||
// Scale linearly from the 50ms ceiling on an idle server down to the 20ms floor on an overloaded one, so rollbacks mostly expand into idle tick time
|
||||
long tickTimeNanos = (long) (averageTickTime * 1_000_000.0D);
|
||||
long budgetNanos = ROLLBACK_BATCH_BUDGET_BASELINE_NANOS + ROLLBACK_BATCH_BUDGET_CEILING_NANOS - tickTimeNanos;
|
||||
return Math.max(ROLLBACK_BATCH_BUDGET_FLOOR_NANOS, Math.min(ROLLBACK_BATCH_BUDGET_CEILING_NANOS, budgetNanos));
|
||||
}
|
||||
|
||||
private static boolean canContinueFoliaBatch(FoliaRollbackBatchState batchState, long batchStart) {
|
||||
if (!batchState.hasNext() || (System.nanoTime() - batchStart) >= ROLLBACK_BATCH_BUDGET_NANOS) {
|
||||
if (!batchState.hasNext() || (System.nanoTime() - batchStart) >= ROLLBACK_BATCH_BUDGET_BASELINE_NANOS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -408,11 +466,10 @@ public class Rollback extends RollbackUtil {
|
||||
return nextWork.world != null && Bukkit.isOwnedByCurrentRegion(nextWork.world, nextWork.chunkX, nextWork.chunkZ);
|
||||
}
|
||||
|
||||
private static boolean processChunkEntry(Entry<Long, Integer> entry, LinkedHashSet<Integer> worldList, HashMap<Integer, HashMap<Long, ArrayList<Object[]>>> dataList, HashMap<Integer, HashMap<Long, ArrayList<Object[]>>> itemDataList, int rollbackType, int preview, String userString, CommandSender user, boolean inventoryRollback) {
|
||||
private static boolean processChunkEntry(Entry<Long, Integer> entry, HashMap<Integer, World> worldMap, HashMap<Integer, HashMap<Long, ArrayList<Object[]>>> dataList, HashMap<Integer, HashMap<Long, ArrayList<Object[]>>> itemDataList, int rollbackType, int preview, String userString, CommandSender user, boolean inventoryRollback, RollbackBlockDataCache blockDataCache) {
|
||||
long chunkKey = entry.getKey();
|
||||
int chunkX = getChunkX(entry);
|
||||
int chunkZ = getChunkZ(entry);
|
||||
HashMap<Integer, World> worldMap = getRollbackWorlds(worldList);
|
||||
prepareChunkCounters(userString);
|
||||
|
||||
for (Entry<Integer, World> rollbackWorlds : worldMap.entrySet()) {
|
||||
@@ -421,7 +478,7 @@ public class Rollback extends RollbackUtil {
|
||||
HashMap<Long, ArrayList<Object[]>> blockList = dataList.get(rollbackWorldId);
|
||||
HashMap<Long, ArrayList<Object[]>> itemList = itemDataList.get(rollbackWorldId);
|
||||
|
||||
if (!processChunkWorld(chunkX, chunkZ, chunkKey, blockList, itemList, rollbackType, preview, userString, user, bukkitRollbackWorld, inventoryRollback)) {
|
||||
if (!processChunkWorld(chunkX, chunkZ, chunkKey, blockList, itemList, rollbackType, preview, userString, user, bukkitRollbackWorld, inventoryRollback, blockDataCache)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -429,6 +486,23 @@ public class Rollback extends RollbackUtil {
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void prefetchUpcomingChunks(RollbackBatchState batchState, HashMap<Integer, World> worldMap, boolean inventoryRollback) {
|
||||
if (inventoryRollback) {
|
||||
return;
|
||||
}
|
||||
|
||||
int prefetchLimit = Math.min(batchState.index + CHUNK_PREFETCH_DISTANCE, batchState.totalChunks());
|
||||
while (batchState.prefetchIndex < prefetchLimit) {
|
||||
Entry<Long, Integer> entry = batchState.sortedChunks.get(batchState.prefetchIndex);
|
||||
int chunkX = getChunkX(entry);
|
||||
int chunkZ = getChunkZ(entry);
|
||||
for (World world : worldMap.values()) {
|
||||
PaperAdapter.ADAPTER.prefetchChunk(world, chunkX, chunkZ);
|
||||
}
|
||||
batchState.prefetchIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
private static int getChunkX(Entry<Long, Integer> entry) {
|
||||
return (int) (long) entry.getKey();
|
||||
}
|
||||
@@ -486,17 +560,19 @@ public class Rollback extends RollbackUtil {
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean processChunkWorld(int chunkX, int chunkZ, long chunkKey, HashMap<Long, ArrayList<Object[]>> blockList, HashMap<Long, ArrayList<Object[]>> itemList, int rollbackType, int preview, String userString, CommandSender user, World world, boolean inventoryRollback) {
|
||||
private static boolean processChunkWorld(int chunkX, int chunkZ, long chunkKey, HashMap<Long, ArrayList<Object[]>> blockList, HashMap<Long, ArrayList<Object[]>> itemList, int rollbackType, int preview, String userString, CommandSender user, World world, boolean inventoryRollback, RollbackBlockDataCache blockDataCache) {
|
||||
ArrayList<Object[]> blockData = blockList != null ? blockList.getOrDefault(chunkKey, new ArrayList<>()) : new ArrayList<>();
|
||||
ArrayList<Object[]> itemData = itemList != null ? itemList.getOrDefault(chunkKey, new ArrayList<>()) : new ArrayList<>();
|
||||
Player rollbackPlayer = user instanceof Player ? (Player) user : null;
|
||||
return RollbackProcessor.processChunk(chunkX, chunkZ, chunkKey, blockData, itemData, rollbackType, preview, userString, rollbackPlayer, world, inventoryRollback);
|
||||
return RollbackProcessor.processChunk(chunkX, chunkZ, chunkKey, blockData, itemData, rollbackType, preview, userString, rollbackPlayer, world, inventoryRollback, blockDataCache);
|
||||
}
|
||||
|
||||
private static final class RollbackBatchState {
|
||||
private final List<Entry<Long, Integer>> sortedChunks;
|
||||
private int index = 0;
|
||||
private int chunkCount = 0;
|
||||
private int prefetchIndex = 0;
|
||||
private long nanosSinceTickYield = 0L;
|
||||
private volatile int chunkCount = 0;
|
||||
|
||||
private RollbackBatchState(List<Entry<Long, Integer>> sortedChunks) {
|
||||
this.sortedChunks = sortedChunks;
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package net.coreprotect.database.rollback;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
|
||||
import net.coreprotect.utility.BlockTypeUtils;
|
||||
import net.coreprotect.utility.BlockUtils;
|
||||
|
||||
public final class RollbackBlockDataCache {
|
||||
|
||||
private final Map<String, BlockData> parsedBlockData = new HashMap<>();
|
||||
private final Map<Integer, BlockData> defaultBlockData = new HashMap<>();
|
||||
|
||||
BlockData getParsedBlockData(String blockDataString) {
|
||||
if (blockDataString == null || !blockDataString.contains(":")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!parsedBlockData.containsKey(blockDataString)) {
|
||||
BlockData blockData = null;
|
||||
try {
|
||||
blockData = BlockTypeUtils.createBlockDataFromString(blockDataString);
|
||||
if (blockData == null) {
|
||||
blockData = Bukkit.getServer().createBlockData(blockDataString);
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
// corrupt BlockData, let the server automatically set the BlockData instead
|
||||
}
|
||||
parsedBlockData.put(blockDataString, blockData);
|
||||
}
|
||||
|
||||
return cloneBlockData(parsedBlockData.get(blockDataString));
|
||||
}
|
||||
|
||||
BlockData getDefaultBlockData(int rowTypeRaw) {
|
||||
if (!defaultBlockData.containsKey(rowTypeRaw)) {
|
||||
defaultBlockData.put(rowTypeRaw, BlockUtils.createBlockData(rowTypeRaw));
|
||||
}
|
||||
|
||||
return cloneBlockData(defaultBlockData.get(rowTypeRaw));
|
||||
}
|
||||
|
||||
private static BlockData cloneBlockData(BlockData blockData) {
|
||||
return blockData != null ? blockData.clone() : null;
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,6 @@ import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import net.coreprotect.bukkit.BukkitAdapter;
|
||||
import net.coreprotect.config.ConfigHandler;
|
||||
import net.coreprotect.consumer.Queue;
|
||||
import net.coreprotect.model.BlockGroup;
|
||||
import net.coreprotect.model.PendingBlockChange;
|
||||
@@ -56,7 +55,7 @@ import net.coreprotect.utility.ErrorReporter;
|
||||
|
||||
public class RollbackBlockHandler extends Queue {
|
||||
|
||||
public static boolean processBlockChange(World bukkitWorld, Block block, Object[] row, int rollbackType, boolean clearInventories, Map<Block, PendingBlockChange> chunkChanges, boolean countBlock, Material oldTypeMaterial, Material pendingChangeType, BlockData pendingChangeData, String finalUserString, BlockData rawBlockData, Material changeType, boolean changeBlock, BlockData changeBlockData, ArrayList<Object> meta, BlockData blockData, String rowUser, Material rowType, int rowX, int rowY, int rowZ, int rowTypeRaw, int rowData, int rowAction, int rowWorldId, String blockDataString) {
|
||||
public static boolean processBlockChange(World bukkitWorld, Block block, Object[] row, int rollbackType, boolean clearInventories, Map<Block, PendingBlockChange> chunkChanges, boolean countBlock, Material oldTypeMaterial, Material pendingChangeType, BlockData pendingChangeData, RollbackCounters counters, BlockData rawBlockData, Material changeType, boolean changeBlock, BlockData changeBlockData, ArrayList<Object> meta, BlockData blockData, String rowUser, Material rowType, int rowX, int rowY, int rowZ, int rowTypeRaw, int rowData, int rowAction, int rowWorldId, String blockDataString) {
|
||||
int unixtimestamp = (int) (System.currentTimeMillis() / 1000L);
|
||||
|
||||
try {
|
||||
@@ -234,7 +233,7 @@ public class RollbackBlockHandler extends Queue {
|
||||
BlockUtils.prepareTypeAndData(chunkChanges, bisectBlock, rowType, null, false);
|
||||
|
||||
if (countBlock) {
|
||||
updateBlockCount(finalUserString, 1);
|
||||
counters.addBlocks(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -281,7 +280,7 @@ public class RollbackBlockHandler extends Queue {
|
||||
else if (BlockGroup.SHULKER_BOXES.contains(rowType)) {
|
||||
BlockUtils.prepareTypeAndData(chunkChanges, block, rowType, blockData, false);
|
||||
if (countBlock) {
|
||||
updateBlockCount(finalUserString, 1);
|
||||
counters.addBlocks(1);
|
||||
}
|
||||
if (meta != null) {
|
||||
Inventory inventory = BlockUtils.getContainerInventory(block.getState(), false);
|
||||
@@ -297,7 +296,7 @@ public class RollbackBlockHandler extends Queue {
|
||||
else if (rowType == Material.COMMAND_BLOCK || rowType == Material.REPEATING_COMMAND_BLOCK || rowType == Material.CHAIN_COMMAND_BLOCK) { // command block
|
||||
BlockUtils.prepareTypeAndData(chunkChanges, block, rowType, blockData, false);
|
||||
if (countBlock) {
|
||||
updateBlockCount(finalUserString, 1);
|
||||
counters.addBlocks(1);
|
||||
}
|
||||
|
||||
if (meta != null) {
|
||||
@@ -333,7 +332,7 @@ public class RollbackBlockHandler extends Queue {
|
||||
}
|
||||
else if (blockData == null && rowData > 0 && (rowType == Material.IRON_DOOR || BlockGroup.DOORS.contains(rowType))) {
|
||||
if (countBlock) {
|
||||
updateBlockCount(finalUserString, 1);
|
||||
counters.addBlocks(1);
|
||||
}
|
||||
|
||||
block.setType(rowType, false);
|
||||
@@ -373,7 +372,7 @@ public class RollbackBlockHandler extends Queue {
|
||||
}
|
||||
else if (blockData == null && rowData > 0 && (rowType.name().endsWith("_BED"))) {
|
||||
if (countBlock) {
|
||||
updateBlockCount(finalUserString, 1);
|
||||
counters.addBlocks(1);
|
||||
}
|
||||
|
||||
block.setType(rowType, false);
|
||||
@@ -404,7 +403,7 @@ public class RollbackBlockHandler extends Queue {
|
||||
else if (rowType.name().endsWith("_BANNER")) {
|
||||
BlockUtils.prepareTypeAndData(chunkChanges, block, rowType, blockData, false);
|
||||
if (countBlock) {
|
||||
updateBlockCount(finalUserString, 1);
|
||||
counters.addBlocks(1);
|
||||
}
|
||||
|
||||
if (meta != null) {
|
||||
@@ -463,7 +462,7 @@ public class RollbackBlockHandler extends Queue {
|
||||
|
||||
BlockUtils.prepareTypeAndData(chunkChanges, block, rowType, blockData, false);
|
||||
if (countBlock) {
|
||||
updateBlockCount(finalUserString, 2);
|
||||
counters.addBlocks(2);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -475,7 +474,7 @@ public class RollbackBlockHandler extends Queue {
|
||||
bedData.setPart(Part.HEAD);
|
||||
BlockUtils.prepareTypeAndData(chunkChanges, adjacentBlock, rowType, bedData, false);
|
||||
if (countBlock) {
|
||||
updateBlockCount(finalUserString, 1);
|
||||
counters.addBlocks(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -549,25 +548,6 @@ public class RollbackBlockHandler extends Queue {
|
||||
return blockData instanceof MultipleFacing || blockData instanceof Waterlogged || blockData instanceof Bisected || blockData instanceof Bed || blockData instanceof Chest || blockData instanceof RedstoneWire || blockData instanceof Snow || blockData instanceof Stairs || blockData instanceof TrapDoor || blockData instanceof Piston || blockData instanceof PistonHead || blockData instanceof TechnicalPiston || blockData instanceof org.bukkit.block.data.Rail;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the block count in the rollback hash
|
||||
*
|
||||
* @param userString
|
||||
* The username for this rollback
|
||||
* @param increment
|
||||
* The amount to increment the block count by
|
||||
*/
|
||||
protected static void updateBlockCount(String userString, int increment) {
|
||||
int[] rollbackHashData = ConfigHandler.rollbackHash.get(userString);
|
||||
int itemCount = rollbackHashData[0];
|
||||
int blockCount = rollbackHashData[1];
|
||||
int entityCount = rollbackHashData[2];
|
||||
int scannedWorlds = rollbackHashData[4];
|
||||
|
||||
blockCount += increment;
|
||||
ConfigHandler.rollbackHash.put(userString, new int[] { itemCount, blockCount, entityCount, 0, scannedWorlds });
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply all pending block changes to the world
|
||||
*
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package net.coreprotect.database.rollback;
|
||||
|
||||
public final class RollbackCounters {
|
||||
|
||||
private int items;
|
||||
private int blocks;
|
||||
private int entities;
|
||||
|
||||
public void addItems(int amount) {
|
||||
items += amount;
|
||||
}
|
||||
|
||||
public void addBlocks(int amount) {
|
||||
blocks += amount;
|
||||
}
|
||||
|
||||
public void addEntities(int amount) {
|
||||
entities += amount;
|
||||
}
|
||||
|
||||
public int getItems() {
|
||||
return items;
|
||||
}
|
||||
|
||||
public int getBlocks() {
|
||||
return blocks;
|
||||
}
|
||||
|
||||
public int getEntities() {
|
||||
return entities;
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package net.coreprotect.database.rollback;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
@@ -13,7 +12,6 @@ import org.bukkit.entity.EntityType;
|
||||
import net.coreprotect.config.ConfigHandler;
|
||||
import net.coreprotect.thread.CacheHandler;
|
||||
import net.coreprotect.utility.EntityUtils;
|
||||
import net.coreprotect.utility.WorldUtils;
|
||||
import net.coreprotect.utility.ErrorReporter;
|
||||
|
||||
public class RollbackEntityHandler {
|
||||
@@ -21,12 +19,8 @@ public class RollbackEntityHandler {
|
||||
/**
|
||||
* Processes an entity-related rollback operation.
|
||||
*
|
||||
* @param row
|
||||
* The database row containing entity data (used only for specific operations)
|
||||
* @param rollbackType
|
||||
* The type of rollback (0 for rollback, 1 for restore)
|
||||
* @param finalUserString
|
||||
* The user string for tracking operations
|
||||
* @param bukkitWorld
|
||||
* The world the entity change belongs to
|
||||
* @param rowTypeRaw
|
||||
* The raw type value
|
||||
* @param rowData
|
||||
@@ -43,26 +37,14 @@ public class RollbackEntityHandler {
|
||||
* The Z coordinate
|
||||
* @param rowWorldId
|
||||
* The world ID
|
||||
* @param rowUserId
|
||||
* The user ID
|
||||
* @param rowUser
|
||||
* The username associated with this entity change
|
||||
* @return The number of entities affected (1 if successful, 0 otherwise)
|
||||
*/
|
||||
public static int processEntity(Object[] row, int rollbackType, String finalUserString, int oldTypeRaw, int rowTypeRaw, int rowData, int rowAction, int rowRolledBack, int rowX, int rowY, int rowZ, int rowWorldId, int rowUserId, String rowUser) {
|
||||
public static int processEntity(World bukkitWorld, int oldTypeRaw, int rowTypeRaw, int rowData, int rowAction, int rowRolledBack, int rowX, int rowY, int rowZ, int rowWorldId, String rowUser) {
|
||||
try {
|
||||
// Entity kill
|
||||
if (rowAction == 3) {
|
||||
String world = getWorldName(rowWorldId);
|
||||
if (world.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
World bukkitWorld = Bukkit.getServer().getWorld(world);
|
||||
if (bukkitWorld == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
Block block = bukkitWorld.getBlockAt(rowX, rowY, rowZ);
|
||||
if (!bukkitWorld.isChunkLoaded(block.getChunk())) {
|
||||
bukkitWorld.getChunkAt(block.getLocation());
|
||||
@@ -74,7 +56,6 @@ public class RollbackEntityHandler {
|
||||
EntityType entityType = EntityUtils.getEntityType(rowTypeRaw);
|
||||
// Use the spawnEntity method from the RollbackUtil class instead of Queue
|
||||
spawnEntity(rowUser, block.getState(), entityType, rowData);
|
||||
updateEntityCount(finalUserString, 1);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -90,10 +71,10 @@ public class RollbackEntityHandler {
|
||||
int zmin = rowZ - 5;
|
||||
int zmax = rowZ + 5;
|
||||
|
||||
boolean removed = removeMatchingEntity(Arrays.asList(block.getChunk().getEntities()), finalUserString, entityId, targetType, xmin, xmax, ymin, ymax, zmin, zmax, true);
|
||||
boolean removed = removeMatchingEntity(Arrays.asList(block.getChunk().getEntities()), entityId, targetType, xmin, xmax, ymin, ymax, zmin, zmax, true);
|
||||
if (!removed && entityId > -1 && !ConfigHandler.isFolia) {
|
||||
// On non-Folia, keep world-wide fallback for moved entities.
|
||||
removed = removeMatchingEntity(block.getWorld().getLivingEntities(), finalUserString, entityId, targetType, xmin, xmax, ymin, ymax, zmin, zmax, false);
|
||||
removed = removeMatchingEntity(block.getWorld().getLivingEntities(), entityId, targetType, xmin, xmax, ymin, ymax, zmin, zmax, false);
|
||||
}
|
||||
|
||||
if (removed) {
|
||||
@@ -110,17 +91,6 @@ public class RollbackEntityHandler {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the world name from a world ID.
|
||||
*
|
||||
* @param worldId
|
||||
* The world ID
|
||||
* @return The world name
|
||||
*/
|
||||
private static String getWorldName(int worldId) {
|
||||
return WorldUtils.getWorldName(worldId);
|
||||
}
|
||||
|
||||
private static int getCachedEntityId(int rowX, int rowY, int rowZ, int rowWorldId, int oldTypeRaw) {
|
||||
String entityName = EntityUtils.getEntityType(oldTypeRaw).name();
|
||||
String token = rowX + "." + rowY + "." + rowZ + "." + rowWorldId + "." + entityName;
|
||||
@@ -131,11 +101,10 @@ public class RollbackEntityHandler {
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static boolean removeMatchingEntity(Iterable<? extends Entity> entities, String userString, int cachedEntityId, EntityType targetType, int xmin, int xmax, int ymin, int ymax, int zmin, int zmax, boolean useBounds) {
|
||||
private static boolean removeMatchingEntity(Iterable<? extends Entity> entities, int cachedEntityId, EntityType targetType, int xmin, int xmax, int ymin, int ymax, int zmin, int zmax, boolean useBounds) {
|
||||
for (Entity entity : entities) {
|
||||
if (cachedEntityId > -1) {
|
||||
if (entity.getEntityId() == cachedEntityId) {
|
||||
updateEntityCount(userString, 1);
|
||||
entity.remove();
|
||||
return true;
|
||||
}
|
||||
@@ -149,7 +118,6 @@ public class RollbackEntityHandler {
|
||||
continue;
|
||||
}
|
||||
|
||||
updateEntityCount(userString, 1);
|
||||
entity.remove();
|
||||
return true;
|
||||
}
|
||||
@@ -163,29 +131,6 @@ public class RollbackEntityHandler {
|
||||
return entityX >= xmin && entityX <= xmax && entityY >= ymin && entityY <= ymax && entityZ >= zmin && entityZ <= zmax;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the entity count in the rollback hash for a specific user.
|
||||
*
|
||||
* @param userString
|
||||
* The user string identifier
|
||||
* @param increment
|
||||
* The amount to increment the entity count by
|
||||
*/
|
||||
public static void updateEntityCount(String userString, int increment) {
|
||||
int[] rollbackHashData = ConfigHandler.rollbackHash.get(userString);
|
||||
if (rollbackHashData != null) {
|
||||
int itemCount = rollbackHashData[0];
|
||||
int blockCount = rollbackHashData[1];
|
||||
int entityCount = rollbackHashData[2];
|
||||
int next = rollbackHashData[3];
|
||||
int scannedWorlds = rollbackHashData[4];
|
||||
|
||||
entityCount += increment;
|
||||
|
||||
ConfigHandler.rollbackHash.put(userString, new int[] { itemCount, blockCount, entityCount, next, scannedWorlds });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Spawns an entity at the given block location.
|
||||
*
|
||||
|
||||
@@ -35,11 +35,9 @@ import net.coreprotect.model.PendingBlockChange;
|
||||
import net.coreprotect.model.item.ItemTransactionActions;
|
||||
import net.coreprotect.thread.Scheduler;
|
||||
import net.coreprotect.utility.BlockUtils;
|
||||
import net.coreprotect.utility.BlockTypeUtils;
|
||||
import net.coreprotect.utility.ItemUtils;
|
||||
import net.coreprotect.utility.MaterialUtils;
|
||||
import net.coreprotect.utility.Teleport;
|
||||
import net.coreprotect.utility.WorldUtils;
|
||||
import net.coreprotect.utility.ErrorReporter;
|
||||
|
||||
public class RollbackProcessor {
|
||||
@@ -73,26 +71,22 @@ public class RollbackProcessor {
|
||||
* The user performing the rollback
|
||||
* @param bukkitRollbackWorld
|
||||
* The world to process
|
||||
* @param blockDataCache
|
||||
* The rollback-scoped BlockData parse cache
|
||||
* @return True if successful, false if there was an error
|
||||
*/
|
||||
public static boolean processChunk(int finalChunkX, int finalChunkZ, long chunkKey, ArrayList<Object[]> blockList, ArrayList<Object[]> itemList, int rollbackType, int preview, String finalUserString, Player finalUser, World bukkitRollbackWorld, boolean inventoryRollback) {
|
||||
public static boolean processChunk(int finalChunkX, int finalChunkZ, long chunkKey, ArrayList<Object[]> blockList, ArrayList<Object[]> itemList, int rollbackType, int preview, String finalUserString, Player finalUser, World bukkitRollbackWorld, boolean inventoryRollback, RollbackBlockDataCache blockDataCache) {
|
||||
RollbackCounters counters = new RollbackCounters();
|
||||
|
||||
try {
|
||||
boolean clearInventories = Config.getGlobal().ROLLBACK_ITEMS;
|
||||
ArrayList<Object[]> data = blockList != null ? blockList : new ArrayList<>();
|
||||
ArrayList<Object[]> itemData = itemList != null ? itemList : new ArrayList<>();
|
||||
Map<Block, PendingBlockChange> chunkChanges = new LinkedHashMap<>();
|
||||
Map<String, BlockData> parsedBlockDataCache = new HashMap<>();
|
||||
Map<Integer, BlockData> defaultBlockDataCache = new HashMap<>();
|
||||
loadChunk(bukkitRollbackWorld, finalChunkX, finalChunkZ, inventoryRollback);
|
||||
|
||||
// Process blocks
|
||||
for (Object[] row : data) {
|
||||
int[] rollbackHashData = ConfigHandler.rollbackHash.get(finalUserString);
|
||||
int itemCount = rollbackHashData[0];
|
||||
int blockCount = rollbackHashData[1];
|
||||
int entityCount = rollbackHashData[2];
|
||||
int scannedWorlds = rollbackHashData[4];
|
||||
|
||||
int rowX = (Integer) row[3];
|
||||
int rowY = (Integer) row[4];
|
||||
int rowZ = (Integer) row[5];
|
||||
@@ -111,13 +105,13 @@ public class RollbackProcessor {
|
||||
meta = RollbackUtil.deserializeMetadata(rowMeta);
|
||||
}
|
||||
|
||||
BlockData blockData = getParsedBlockData(blockDataString, parsedBlockDataCache);
|
||||
BlockData blockData = blockDataCache.getParsedBlockData(blockDataString);
|
||||
BlockData rawBlockData = null;
|
||||
if (blockData != null) {
|
||||
rawBlockData = blockData.clone();
|
||||
}
|
||||
if (rawBlockData == null) {
|
||||
rawBlockData = getDefaultBlockData(rowTypeRaw, defaultBlockDataCache);
|
||||
rawBlockData = blockDataCache.getDefaultBlockData(rowTypeRaw);
|
||||
}
|
||||
if (rowType == Material.NOTE_BLOCK) {
|
||||
normalizeRollbackBlockData(blockData);
|
||||
@@ -148,50 +142,30 @@ public class RollbackProcessor {
|
||||
}
|
||||
if (preview > 0) {
|
||||
if (rowAction != 3) { // entity kill
|
||||
String world = WorldUtils.getWorldName(rowWorldId);
|
||||
if (world.length() == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
World bukkitWorld = Bukkit.getServer().getWorld(world);
|
||||
if (bukkitWorld == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Block block = bukkitWorld.getBlockAt(rowX, rowY, rowZ);
|
||||
Block block = bukkitRollbackWorld.getBlockAt(rowX, rowY, rowZ);
|
||||
if (preview == 2) {
|
||||
Material blockType = block.getType();
|
||||
if (!BukkitAdapter.ADAPTER.isItemFrame(blockType) && !blockType.equals(Material.PAINTING) && !blockType.equals(Material.ARMOR_STAND) && !blockType.equals(Material.END_CRYSTAL)) {
|
||||
BlockUtils.prepareTypeAndData(chunkChanges, block, blockType, block.getBlockData(), true);
|
||||
blockCount++;
|
||||
counters.addBlocks(1);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ((!BukkitAdapter.ADAPTER.isItemFrame(rowType)) && (rowType != Material.PAINTING) && (rowType != Material.ARMOR_STAND) && (rowType != Material.END_CRYSTAL)) {
|
||||
BlockUtils.prepareTypeAndData(chunkChanges, block, rowType, blockData, true);
|
||||
blockCount++;
|
||||
counters.addBlocks(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
entityCount++;
|
||||
counters.addEntities(1);
|
||||
}
|
||||
}
|
||||
else if (rowAction == 3) { // entity kill
|
||||
entityCount += RollbackEntityHandler.processEntity(row, rollbackType, finalUserString, oldTypeRaw, rowTypeRaw, rowData, rowAction, MaterialUtils.rolledBack((Integer) row[9], false), rowX, rowY, rowZ, rowWorldId, (Integer) row[2], rowUser);
|
||||
counters.addEntities(RollbackEntityHandler.processEntity(bukkitRollbackWorld, oldTypeRaw, rowTypeRaw, rowData, rowAction, rowRolledBack, rowX, rowY, rowZ, rowWorldId, rowUser));
|
||||
}
|
||||
else {
|
||||
String world = WorldUtils.getWorldName(rowWorldId);
|
||||
if (world.length() == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
World bukkitWorld = Bukkit.getServer().getWorld(world);
|
||||
if (bukkitWorld == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Block block = bukkitWorld.getBlockAt(rowX, rowY, rowZ);
|
||||
Block block = bukkitRollbackWorld.getBlockAt(rowX, rowY, rowZ);
|
||||
|
||||
boolean changeBlock = true;
|
||||
boolean countBlock = true;
|
||||
@@ -241,12 +215,10 @@ public class RollbackProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
if (RollbackBlockHandler.processBlockChange(bukkitWorld, block, row, rollbackType, clearInventories, chunkChanges, countBlock, oldTypeMaterial, pendingChangeType, pendingChangeData, finalUserString, rawBlockData, changeType, changeBlock, changeBlockData, meta != null ? new ArrayList<>(meta) : null, blockData, rowUser, rowType, rowX, rowY, rowZ, rowTypeRaw, rowData, rowAction, rowWorldId, BlockUtils.byteDataToString((byte[]) row[13], rowTypeRaw)) && countBlock) {
|
||||
blockCount++;
|
||||
if (RollbackBlockHandler.processBlockChange(bukkitRollbackWorld, block, row, rollbackType, clearInventories, chunkChanges, countBlock, oldTypeMaterial, pendingChangeType, pendingChangeData, counters, rawBlockData, changeType, changeBlock, changeBlockData, meta != null ? new ArrayList<>(meta) : null, blockData, rowUser, rowType, rowX, rowY, rowZ, rowTypeRaw, rowData, rowAction, rowWorldId, BlockUtils.byteDataToString((byte[]) row[13], rowTypeRaw)) && countBlock) {
|
||||
counters.addBlocks(1);
|
||||
}
|
||||
}
|
||||
|
||||
ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount, blockCount, entityCount, 0, scannedWorlds });
|
||||
}
|
||||
data.clear();
|
||||
|
||||
@@ -265,11 +237,6 @@ public class RollbackProcessor {
|
||||
String lastFace = "";
|
||||
|
||||
for (Object[] row : itemData) {
|
||||
int[] rollbackHashData1 = ConfigHandler.rollbackHash.get(finalUserString);
|
||||
int itemCount1 = rollbackHashData1[0];
|
||||
int blockCount1 = rollbackHashData1[1];
|
||||
int entityCount1 = rollbackHashData1[2];
|
||||
int scannedWorlds = rollbackHashData1[4];
|
||||
int rowX = (Integer) row[3];
|
||||
int rowY = (Integer) row[4];
|
||||
int rowZ = (Integer) row[5];
|
||||
@@ -322,8 +289,7 @@ public class RollbackProcessor {
|
||||
sortPlayers.put(player, currentSortList);
|
||||
}
|
||||
|
||||
itemCount1 = itemCount1 + rowAmount;
|
||||
ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount1, blockCount1, entityCount1, 0, scannedWorlds });
|
||||
counters.addItems(rowAmount);
|
||||
continue; // remove this for merged rollbacks in future? (be sure to re-enable chunk sorting)
|
||||
}
|
||||
|
||||
@@ -338,16 +304,7 @@ public class RollbackProcessor {
|
||||
|
||||
if (!containerInit || rowX != lastX || rowY != lastY || rowZ != lastZ || rowWorldId != lastWorldId || !faceData.equals(lastFace)) {
|
||||
container = null; // container patch 2.14.0
|
||||
String world = WorldUtils.getWorldName(rowWorldId);
|
||||
if (world.length() == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
World bukkitWorld = Bukkit.getServer().getWorld(world);
|
||||
if (bukkitWorld == null) {
|
||||
continue;
|
||||
}
|
||||
Block block = bukkitWorld.getBlockAt(rowX, rowY, rowZ);
|
||||
Block block = bukkitRollbackWorld.getBlockAt(rowX, rowY, rowZ);
|
||||
|
||||
if (BlockGroup.CONTAINERS.contains(block.getType())) {
|
||||
BlockState blockState = block.getState();
|
||||
@@ -399,13 +356,11 @@ public class RollbackProcessor {
|
||||
itemstack = (ItemStack) populatedStack[2];
|
||||
|
||||
RollbackUtil.modifyContainerItems(containerType, container, slot, itemstack, action);
|
||||
itemCount1 = itemCount1 + rowAmount;
|
||||
counters.addItems(rowAmount);
|
||||
}
|
||||
containerInit = true;
|
||||
}
|
||||
}
|
||||
|
||||
ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount1, blockCount1, entityCount1, 0, scannedWorlds });
|
||||
}
|
||||
itemData.clear();
|
||||
|
||||
@@ -414,12 +369,7 @@ public class RollbackProcessor {
|
||||
}
|
||||
sortPlayers.clear();
|
||||
|
||||
int[] rollbackHashData = ConfigHandler.rollbackHash.get(finalUserString);
|
||||
int itemCount = rollbackHashData[0];
|
||||
int blockCount = rollbackHashData[1];
|
||||
int entityCount = rollbackHashData[2];
|
||||
int scannedWorlds = rollbackHashData[4];
|
||||
ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount, blockCount, entityCount, 1, (scannedWorlds + 1) });
|
||||
updateRollbackHash(finalUserString, counters, 1);
|
||||
|
||||
// Teleport players out of danger if they're within this chunk
|
||||
if (preview == 0) {
|
||||
@@ -439,17 +389,20 @@ public class RollbackProcessor {
|
||||
}
|
||||
catch (Exception e) {
|
||||
ErrorReporter.report(e);
|
||||
int[] rollbackHashData = ConfigHandler.rollbackHash.get(finalUserString);
|
||||
int itemCount = rollbackHashData[0];
|
||||
int blockCount = rollbackHashData[1];
|
||||
int entityCount = rollbackHashData[2];
|
||||
int scannedWorlds = rollbackHashData[4];
|
||||
|
||||
ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount, blockCount, entityCount, 2, (scannedWorlds + 1) });
|
||||
updateRollbackHash(finalUserString, counters, 2);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void updateRollbackHash(String finalUserString, RollbackCounters counters, int status) {
|
||||
int[] rollbackHashData = ConfigHandler.rollbackHash.get(finalUserString);
|
||||
int itemCount = rollbackHashData[0] + counters.getItems();
|
||||
int blockCount = rollbackHashData[1] + counters.getBlocks();
|
||||
int entityCount = rollbackHashData[2] + counters.getEntities();
|
||||
int scannedWorlds = rollbackHashData[4] + 1;
|
||||
ConfigHandler.rollbackHash.put(finalUserString, new int[] { itemCount, blockCount, entityCount, status, scannedWorlds });
|
||||
}
|
||||
|
||||
private static void loadChunk(World world, int chunkX, int chunkZ, boolean inventoryRollback) {
|
||||
if (inventoryRollback || world == null) {
|
||||
return;
|
||||
@@ -460,37 +413,4 @@ public class RollbackProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
private static BlockData getParsedBlockData(String blockDataString, Map<String, BlockData> parsedBlockDataCache) {
|
||||
if (blockDataString == null || !blockDataString.contains(":")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!parsedBlockDataCache.containsKey(blockDataString)) {
|
||||
BlockData blockData = null;
|
||||
try {
|
||||
blockData = BlockTypeUtils.createBlockDataFromString(blockDataString);
|
||||
if (blockData == null) {
|
||||
blockData = Bukkit.getServer().createBlockData(blockDataString);
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
// corrupt BlockData, let the server automatically set the BlockData instead
|
||||
}
|
||||
parsedBlockDataCache.put(blockDataString, blockData);
|
||||
}
|
||||
|
||||
return cloneBlockData(parsedBlockDataCache.get(blockDataString));
|
||||
}
|
||||
|
||||
private static BlockData getDefaultBlockData(int rowTypeRaw, Map<Integer, BlockData> defaultBlockDataCache) {
|
||||
if (!defaultBlockDataCache.containsKey(rowTypeRaw)) {
|
||||
defaultBlockDataCache.put(rowTypeRaw, BlockUtils.createBlockData(rowTypeRaw));
|
||||
}
|
||||
|
||||
return cloneBlockData(defaultBlockDataCache.get(rowTypeRaw));
|
||||
}
|
||||
|
||||
private static BlockData cloneBlockData(BlockData blockData) {
|
||||
return blockData != null ? blockData.clone() : null;
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Sign;
|
||||
import org.bukkit.block.Skull;
|
||||
import org.bukkit.entity.Entity;
|
||||
@@ -77,6 +78,11 @@ public class PaperAdapter implements PaperInterface {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getAverageTickTime(Server server) {
|
||||
return -1.0D;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLine(Sign sign, int line) {
|
||||
return BukkitAdapter.ADAPTER.getLine(sign, line);
|
||||
@@ -87,6 +93,11 @@ public class PaperAdapter implements PaperInterface {
|
||||
entity.teleport(location);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prefetchChunk(World world, int chunkX, int chunkZ) {
|
||||
// chunk prefetching requires the Paper async chunk API
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSkullOwner(Skull skull) {
|
||||
OfflinePlayer player = skull.getOwningPlayer();
|
||||
|
||||
@@ -9,6 +9,7 @@ import java.util.UUID;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Villager;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
@@ -19,17 +20,39 @@ import net.coreprotect.model.entity.VillagerReputationData;
|
||||
|
||||
public class PaperHandler extends PaperAdapter {
|
||||
private volatile boolean supportsSnapshotHolderLookup = true;
|
||||
private volatile boolean supportsAverageTickTime = true;
|
||||
|
||||
@Override
|
||||
public boolean isStopping(Server server) {
|
||||
return server.isStopping();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getAverageTickTime(Server server) {
|
||||
if (supportsAverageTickTime) {
|
||||
try {
|
||||
return server.getAverageTickTime();
|
||||
}
|
||||
catch (LinkageError | UnsupportedOperationException ignored) {
|
||||
supportsAverageTickTime = false;
|
||||
}
|
||||
}
|
||||
|
||||
return -1.0D;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void teleportAsync(Entity entity, Location location) {
|
||||
entity.teleportAsync(location);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prefetchChunk(World world, int chunkX, int chunkZ) {
|
||||
if (!world.isChunkLoaded(chunkX, chunkZ)) {
|
||||
world.getChunkAtAsync(chunkX, chunkZ);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public InventoryHolder getHolder(Inventory holder, boolean useSnapshot) {
|
||||
if (supportsSnapshotHolderLookup) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import java.util.List;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Sign;
|
||||
import org.bukkit.block.Skull;
|
||||
import org.bukkit.entity.Entity;
|
||||
@@ -18,10 +19,14 @@ public interface PaperInterface {
|
||||
|
||||
public boolean isStopping(Server server);
|
||||
|
||||
public double getAverageTickTime(Server server);
|
||||
|
||||
public String getLine(Sign sign, int line);
|
||||
|
||||
public void teleportAsync(Entity entity, Location location);
|
||||
|
||||
public void prefetchChunk(World world, int chunkX, int chunkZ);
|
||||
|
||||
public String getSkullOwner(Skull skull);
|
||||
|
||||
public String getSkullSkin(Skull skull);
|
||||
|
||||
@@ -19,6 +19,7 @@ import net.coreprotect.listener.ListenerHandler;
|
||||
import net.coreprotect.thread.CacheHandler;
|
||||
import net.coreprotect.thread.NetworkHandler;
|
||||
import net.coreprotect.thread.Scheduler;
|
||||
import net.coreprotect.thread.TickTimeMonitor;
|
||||
import net.coreprotect.utility.Chat;
|
||||
import net.coreprotect.utility.ChatUtils;
|
||||
import net.coreprotect.utility.Extensions;
|
||||
@@ -149,6 +150,9 @@ public class PluginInitializationService {
|
||||
}
|
||||
}, 0);
|
||||
|
||||
// Start tick time monitor (only used where native tick timings are unavailable)
|
||||
TickTimeMonitor.initialize(plugin);
|
||||
|
||||
// Start cache cleanup thread
|
||||
Thread cacheCleanUpThread = new Thread(new CacheHandler());
|
||||
cacheCleanUpThread.start();
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package net.coreprotect.thread;
|
||||
|
||||
import net.coreprotect.CoreProtect;
|
||||
import net.coreprotect.config.ConfigHandler;
|
||||
import net.coreprotect.paper.PaperAdapter;
|
||||
|
||||
public final class TickTimeMonitor {
|
||||
|
||||
private static final int MONITOR_INTERVAL_TICKS = 40;
|
||||
private static final double HEALTHY_TICK_MILLIS = 50.0D;
|
||||
private static final double LAGGING_SAMPLE_MILLIS = 51.0D;
|
||||
private static final double ESTIMATE_FLOOR_MILLIS = 38.0D;
|
||||
private static final double ESTIMATE_DECAY_MILLIS = 3.0D;
|
||||
|
||||
private static volatile double estimatedTickTime = -1.0D;
|
||||
private static long lastSampleTime = -1L;
|
||||
|
||||
private TickTimeMonitor() {
|
||||
throw new IllegalStateException("TickTimeMonitor class");
|
||||
}
|
||||
|
||||
public static void initialize(CoreProtect plugin) {
|
||||
if (ConfigHandler.isFolia || PaperAdapter.ADAPTER.getAverageTickTime(plugin.getServer()) >= 0.0D) {
|
||||
return;
|
||||
}
|
||||
|
||||
estimatedTickTime = -1.0D;
|
||||
lastSampleTime = -1L;
|
||||
Scheduler.scheduleSyncRepeatingTask(plugin, TickTimeMonitor::recordSample, null, MONITOR_INTERVAL_TICKS, MONITOR_INTERVAL_TICKS);
|
||||
}
|
||||
|
||||
public static double getEstimatedTickTime() {
|
||||
return estimatedTickTime;
|
||||
}
|
||||
|
||||
private static void recordSample() {
|
||||
long now = System.nanoTime();
|
||||
if (lastSampleTime > 0) {
|
||||
double sampleMillis = (now - lastSampleTime) / (MONITOR_INTERVAL_TICKS * 1_000_000.0D);
|
||||
estimatedTickTime = nextEstimate(estimatedTickTime, sampleMillis);
|
||||
}
|
||||
|
||||
lastSampleTime = now;
|
||||
}
|
||||
|
||||
public static double nextEstimate(double currentEstimate, double sampleMillis) {
|
||||
if (sampleMillis > LAGGING_SAMPLE_MILLIS) {
|
||||
// Ticks are stretching past the 50ms pace, so the wall-clock tick time is the real workload
|
||||
return sampleMillis;
|
||||
}
|
||||
|
||||
// Ticks are keeping pace, which proves the workload is at most a full tick but hides any idle headroom below that;
|
||||
// probe for headroom by decaying the estimate, stopping at the point that maps to a 37ms batch budget
|
||||
double provenEstimate = currentEstimate > 0.0D ? Math.min(currentEstimate, HEALTHY_TICK_MILLIS) : HEALTHY_TICK_MILLIS;
|
||||
return Math.max(ESTIMATE_FLOOR_MILLIS, provenEstimate - ESTIMATE_DECAY_MILLIS);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user