package speedytools.serverside.worldmanipulation; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.util.BlockPos; import net.minecraft.util.EnumFacing; import net.minecraft.world.WorldServer; import speedytools.common.blocks.BlockWithMetadata; import speedytools.common.selections.VoxelSelectionWithOrigin; import speedytools.common.utilities.QuadOrientation; import java.lang.ref.WeakReference; import java.util.*; /** * User: The Grey Ghost * Date: 8/06/2014 * Holds the undo history for the WorldServers. * Every player gets: * a) at least one "complex" undo eg for clone & copy tools. They will get more if there is enough space. * If the history is full, these "extra" undo layers are discarded, oldest first. * b) a fixed maximum number of "simple" undos with instant placement eg for wand and orb * The layers are grouped according to WorldServer (different dimensions will have different WorldServers) * Automatically gets rid of EntityPlayerMP and WorldServer which are no longer valid */ public class WorldHistory { public WorldHistory(int maximumComplexHistoryDepth, int maximumSimpleUndosPerPlayer) { assert (maximumComplexHistoryDepth >= 1); maximumComplexDepth = maximumComplexHistoryDepth; maximumSimpleDepthPerPlayer = maximumSimpleUndosPerPlayer; } /** write the given fragment to the World, storing undo information * @param player * @param worldServer * @param fragmentToWrite * @param wxOfOrigin * @param wyOfOrigin * @param wzOfOrigin */ public void writeToWorldWithUndo(EntityPlayerMP player, WorldServer worldServer, WorldFragment fragmentToWrite, int wxOfOrigin, int wyOfOrigin, int wzOfOrigin) { QuadOrientation noChange = new QuadOrientation(0, 0, 1, 1); writeToWorldWithUndo(player, worldServer, fragmentToWrite, wxOfOrigin, wyOfOrigin, wzOfOrigin, noChange); } /** write the given fragment to the World, storing undo information in the "complex tools" history * @param player * @param worldServer * @param fragmentToWrite * @param wxOfOrigin * @param wyOfOrigin * @param wzOfOrigin */ public void writeToWorldWithUndo(EntityPlayerMP player, WorldServer worldServer, WorldFragment fragmentToWrite, int wxOfOrigin, int wyOfOrigin, int wzOfOrigin, QuadOrientation quadOrientation) { AsynchronousToken token = writeToWorldWithUndoAsynchronous(player, worldServer, fragmentToWrite, wxOfOrigin, wyOfOrigin, wzOfOrigin, quadOrientation, null); if (token == null) return; token.setTimeOfInterrupt(token.INFINITE_TIMEOUT); token.continueProcessing(); } /** write the given fragment to the World, storing undo information in the "complex tools" history * Runs asynchronously: after the initial call, use the returned token to monitor and advance the task * -> repeatedly call token.setTimeToInterrupt() and token.continueProcessing() * @param player * @param worldServer * @param fragmentToWrite * @param wxOfOrigin * @param wyOfOrigin * @param wzOfOrigin * @param quadOrientation * @return the asynchronous token for further processing, or null if a complex operation is already in progress */ public AsynchronousToken writeToWorldWithUndoAsynchronous(EntityPlayerMP player, WorldServer worldServer, WorldFragment fragmentToWrite, int wxOfOrigin, int wyOfOrigin, int wzOfOrigin, QuadOrientation quadOrientation, UniqueTokenID transactionID) { if (currentAsynchronousTask != null && !currentAsynchronousTask.isTaskComplete()) return null; // only one complex task at once! WorldSelectionUndo worldSelectionUndo = new WorldSelectionUndo(); AsynchronousToken subToken = worldSelectionUndo.writeToWorldAsynchronous(worldServer, fragmentToWrite, wxOfOrigin, wyOfOrigin, wzOfOrigin, quadOrientation, transactionID); UndoLayerInfo undoLayerInfo = new UndoLayerInfo(System.nanoTime(), worldServer, player, worldSelectionUndo); currentAsynchronousTask = new AsynchronousWriteOrUndo(AsynchronousActionType.WRITE, subToken, undoLayerInfo, transactionID); return currentAsynchronousTask; // once the operation is complete, the token will add the undoLayerInfo to the complex list } /** write the given fragment to the World, storing undo information in the "simple tools" history * @param worldServer */ public void writeToWorldWithUndo(WorldServer worldServer, EntityPlayerMP entityPlayerMP, BlockWithMetadata blockToPlace, EnumFacing sideToPlace, List<BlockPos> blockSelection) { if (currentAsynchronousTask != null && !currentAsynchronousTask.isTaskComplete()) { blockSelection = currentAsynchronousTask.cullLockedVoxels(worldServer, blockSelection); } if (blockSelection.isEmpty()) return; WorldSelectionUndo worldSelectionUndo = new WorldSelectionUndo(); worldSelectionUndo.writeToWorld(worldServer, entityPlayerMP, blockToPlace, sideToPlace, blockSelection); UndoLayerInfo undoLayerInfo = new UndoLayerInfo(System.nanoTime(), worldServer, entityPlayerMP, worldSelectionUndo); undoLayersSimple.add(undoLayerInfo); final int ARBITRARY_LARGE_VALUE = 1000000; cullUndoLayers(undoLayersSimple, maximumSimpleDepthPerPlayer, ARBITRARY_LARGE_VALUE); } /** perform complex undo action for the given player - finds the most recent complex action that they did in the current WorldServer * @param player * @param worldServer * @return true for success, or failure if either no undo found or if an asynchronous task is currently running */ public boolean performComplexUndo(EntityPlayerMP player, WorldServer worldServer) { AsynchronousToken token = performComplexUndoAsynchronous(player, worldServer, null); if (token == null) return false; token.setTimeOfInterrupt(token.INFINITE_TIMEOUT); token.continueProcessing(); return true; } /** perform complex undo action for the given player - finds the most recent complex action that they did in the current WorldServer * * Runs asynchronously: after the initial call, use the returned token to monitor and advance the task * -> repeatedly call token.setTimeToInterrupt() and token.continueProcessing() * @param player * @param worldServer * @param transactionID if not null, perform the undo with the given transactionID, if it still exists. * @return the asynchronous token for further processing, or null if no undo found or a complex operation is already in progress */ public AsynchronousToken performComplexUndoAsynchronous(EntityPlayerMP player, WorldServer worldServer, UniqueTokenID transactionID) { if (currentAsynchronousTask != null && !currentAsynchronousTask.isTaskComplete()) return null; UndoLayerInfo undoLayerFound = null; if (transactionID != null) { undoLayerFound = getSpecificUndo(undoLayersComplex, player, worldServer, transactionID); } else { undoLayerFound = getMostRecentUndo(undoLayersComplex, player, worldServer); } if (undoLayerFound == null) return null; LinkedList<WorldSelectionUndo> subsequentUndoLayers = collateSubsequentUndoLayersAllHistories(undoLayerFound.creationTime, worldServer); AsynchronousToken subToken = undoLayerFound.worldSelectionUndo.undoChangesAsynchronous(worldServer, subsequentUndoLayers); currentAsynchronousTask = new AsynchronousWriteOrUndo(AsynchronousActionType.UNDO, subToken, undoLayerFound, null); return currentAsynchronousTask; } /** get the unique transaction ID for the undo that will be performed next * * @param player * @param worldServer * @return null if no undo found */ public UniqueTokenID getTransactionIDForNextComplexUndo(EntityPlayerMP player, WorldServer worldServer) { UndoLayerInfo undoLayerFound = getMostRecentUndo(undoLayersComplex, player, worldServer); if (undoLayerFound == null) return null; return undoLayerFound.transactionID; } /** perform simple undo action for the given player - finds the most recent simple action that they did in the given WorldServer * @param player * @param worldServer * @return true for success, or failure if no undo found */ public boolean performSimpleUndo(EntityPlayerMP player, WorldServer worldServer) { UndoLayerInfo undoLayerFound = getMostRecentUndo(undoLayersSimple, player, worldServer); if (undoLayerFound == null) return false; undoLayerFound.undoHasCommenced = true; // prevent future performUndo from finding this undo (in case of deferred removal) boolean deferLayerRemoval = false; if (currentAsynchronousTask != null && !currentAsynchronousTask.isTaskComplete()) { // if an asynch task is happening, strip out any voxels locked by the task and only undo these. // the task will queue the remaining (locked) voxels for later undo UndoLayerInfo unlockedOnly = currentAsynchronousTask.removeLockedVoxelsAndScheduleForLaterExecution(undoLayerFound); if (unlockedOnly != null) { // null means no locked voxels so just perform undo as normal undoLayerFound = unlockedOnly; deferLayerRemoval = true; } } LinkedList<WorldSelectionUndo> subsequentUndoLayers = collateSubsequentUndoLayersAllHistories(undoLayerFound.creationTime, worldServer); undoLayerFound.worldSelectionUndo.undoChanges(worldServer, subsequentUndoLayers); if (!deferLayerRemoval) { undoLayersSimple.remove(undoLayerFound); } return true; } /** find the most recent undo entry, in the given undoHistory, for the given player and worldServerReader * @param undoHistory * @param player * @param worldServer * @return the undo entry, or null if none found */ private UndoLayerInfo getMostRecentUndo(LinkedList<UndoLayerInfo> undoHistory, EntityPlayerMP player, WorldServer worldServer) { UndoLayerInfo undoLayerFound = null; Iterator<UndoLayerInfo> undoLayerInfoIterator = undoHistory.descendingIterator(); while (undoLayerFound == null && undoLayerInfoIterator.hasNext()) { UndoLayerInfo undoLayerInfo = undoLayerInfoIterator.next(); if (undoLayerInfo.worldServer.get() == worldServer && undoLayerInfo.entityPlayerMP.get() == player && !undoLayerInfo.undoHasCommenced ) { undoLayerFound = undoLayerInfo; } } return undoLayerFound; } /** find the most recent undo entry, in the given undoHistory, for the given player and worldServerReader * @param undoHistory * @param player * @param worldServer * @return the undo entry, or null if none found */ private UndoLayerInfo getSpecificUndo(LinkedList<UndoLayerInfo> undoHistory, EntityPlayerMP player, WorldServer worldServer, UniqueTokenID transactionID) { UndoLayerInfo undoLayerFound = null; Iterator<UndoLayerInfo> undoLayerInfoIterator = undoHistory.descendingIterator(); while (undoLayerFound == null && undoLayerInfoIterator.hasNext()) { UndoLayerInfo undoLayerInfo = undoLayerInfoIterator.next(); if (undoLayerInfo.transactionID.equals(transactionID) && undoLayerInfo.worldServer.get() == worldServer && undoLayerInfo.entityPlayerMP.get() == player && !undoLayerInfo.undoHasCommenced ) { undoLayerFound = undoLayerInfo; } } return undoLayerFound; } /** * removes the specified player from the history. * Optional, since any entityPlayerMP entries in the history which become invalid will eventually be removed automatically. * @param entityPlayerMP */ public void removePlayer(EntityPlayerMP entityPlayerMP) { for (UndoLayerInfo undoLayerInfo : undoLayersComplex) { if (undoLayerInfo.entityPlayerMP.get() == entityPlayerMP) { undoLayerInfo.entityPlayerMP.clear(); } } for (UndoLayerInfo undoLayerInfo : undoLayersSimple) { if (undoLayerInfo.entityPlayerMP.get() == entityPlayerMP) { undoLayerInfo.entityPlayerMP.clear(); } } } /** * removes the specified worldServerReader from the history. * Optional, since any worldServerReader entries in the history which become invalid will eventually be removed automatically. * @param worldServer */ public void removeWorldServer(WorldServer worldServer) { for (UndoLayerInfo undoLayerInfo : undoLayersComplex) { if (undoLayerInfo.worldServer.get() == worldServer) { undoLayerInfo.worldServer.clear(); } } for (UndoLayerInfo undoLayerInfo : undoLayersSimple) { if (undoLayerInfo.worldServer.get() == worldServer) { undoLayerInfo.worldServer.clear(); } } } /** for debugging purposes */ public void printUndoStackYSlice(WorldServer worldServer, BlockPos origin, int xSize, int y, int zSize) { for (int x = 0; x < xSize; ++x) { for (UndoLayerInfo undoLayerInfo : undoLayersComplex) { if (undoLayerInfo.worldServer.get() == worldServer) { for (int z = 0; z < zSize; ++z) { Integer metadata = undoLayerInfo.worldSelectionUndo.getStoredMetadata(x + origin.getX(), y + origin.getY(), z + origin.getZ()); System.out.print((metadata == null) ? "-" : metadata); System.out.print(" "); } } System.out.print(": "); } System.out.println(); } } /** * Tries to reduce the number of undoLayers to the target size * 1) culls all invalid layers (Player or WorldServer no longer exist) * 2) limit each player to the given maximum per player * 3) If the total layers is still above target - for each player with more than one undolayer, delete the extra layers, starting from oldest first * NB does nothing if there is an asynchronous task currently in progress */ private void cullUndoLayers(LinkedList<UndoLayerInfo> historyToCull, int maxUndoPerPlayer, int targetTotalSize) { if (currentAsynchronousTask != null && !currentAsynchronousTask.isTaskComplete()) return; // delete all invalid layers Iterator<UndoLayerInfo> undoLayerInfoIterator = historyToCull.iterator(); while (undoLayerInfoIterator.hasNext()) { UndoLayerInfo undoLayerInfo = undoLayerInfoIterator.next(); if (undoLayerInfo.worldServer.get() == null) { undoLayerInfoIterator.remove(); } else { if (undoLayerInfo.entityPlayerMP.get() == null) { LinkedList<WorldSelectionUndo> precedingUndoLayers = collatePrecedingUndoLayersAllHistories(undoLayerInfo.creationTime, undoLayerInfo.worldServer.get()); undoLayerInfo.worldSelectionUndo.makePermanent(undoLayerInfo.worldServer.get(), precedingUndoLayers); undoLayerInfoIterator.remove(); } } } HashMap<EntityPlayerMP, Integer> playerUndoCount = new HashMap<EntityPlayerMP, Integer>(); HashSet<UniqueTokenID> uniqueTransactions = new HashSet<UniqueTokenID>(); for (UndoLayerInfo undoLayerInfo : historyToCull) { if (!uniqueTransactions.contains(undoLayerInfo.transactionID)) { uniqueTransactions.add(undoLayerInfo.transactionID); EntityPlayerMP entityPlayerMP = undoLayerInfo.entityPlayerMP.get(); assert (entityPlayerMP != null); if (!playerUndoCount.containsKey(entityPlayerMP)) { playerUndoCount.put(entityPlayerMP, 1); } else { playerUndoCount.put(entityPlayerMP, playerUndoCount.get(entityPlayerMP) + 1); } } } int transactionCount = uniqueTransactions.size(); int layersToDelete = transactionCount - targetTotalSize; for (Integer layerCount : playerUndoCount.values()) { // account for layers which will be deleted due to per-player limits if (layerCount > maxUndoPerPlayer) { layersToDelete -= (layerCount - maxUndoPerPlayer); } } HashSet<UniqueTokenID> deletedTransactions = new HashSet<UniqueTokenID>(); Iterator<UndoLayerInfo> excessIterator = historyToCull.iterator(); while (excessIterator.hasNext()) { UndoLayerInfo undoLayerInfo = excessIterator.next(); EntityPlayerMP entityPlayerMP = undoLayerInfo.entityPlayerMP.get(); assert (entityPlayerMP != null); if (playerUndoCount.get(entityPlayerMP) > 1 && (layersToDelete > 0 || playerUndoCount.get(entityPlayerMP) > maxUndoPerPlayer)) { deletedTransactions.add(undoLayerInfo.transactionID); if (playerUndoCount.get(entityPlayerMP) <= maxUndoPerPlayer) { --layersToDelete; } playerUndoCount.put(entityPlayerMP, playerUndoCount.get(entityPlayerMP) - 1); } if (deletedTransactions.contains(undoLayerInfo.transactionID)) { LinkedList<WorldSelectionUndo> precedingUndoLayers = collatePrecedingUndoLayersAllHistories(undoLayerInfo.creationTime, undoLayerInfo.worldServer.get()); undoLayerInfo.worldSelectionUndo.makePermanent(undoLayerInfo.worldServer.get(), precedingUndoLayers); excessIterator.remove(); } } } /** * collates a list of undo layers with a creation time after the given time, for the given worldServerReader * @param creationTime only collate layers with a creation time > this value * @param worldServerToMatch the worldServerReader to match against * @return a list of matching WorldSelectionUndo in ascending order of time. */ private LinkedList<WorldSelectionUndo> collateSubsequentUndoLayersAllHistories(long creationTime, WorldServer worldServerToMatch) { LinkedList<UndoLayerInfo> combinedList = collateSubsequentUndoLayers(undoLayersSimple, creationTime, worldServerToMatch); combinedList.addAll(collateSubsequentUndoLayers(undoLayersComplex, creationTime, worldServerToMatch)); Collections.sort(combinedList); LinkedList<WorldSelectionUndo> collatedList = new LinkedList<WorldSelectionUndo>(); for (UndoLayerInfo layerInfo : combinedList) { collatedList.add(layerInfo.worldSelectionUndo); } return collatedList; } private LinkedList<UndoLayerInfo> collateSubsequentUndoLayers(LinkedList<UndoLayerInfo> whichHistory, long creationTime, WorldServer worldServerToMatch) { LinkedList<UndoLayerInfo> collatedList = new LinkedList<UndoLayerInfo>(); for (UndoLayerInfo undoLayerInfo : whichHistory) { if (undoLayerInfo.worldServer.get() == worldServerToMatch && undoLayerInfo.creationTime > creationTime) { collatedList.add(undoLayerInfo); } } return collatedList; } /** * collates a list of undo layers with a creation time before the given time, for the given worldServerReader * @param creationTime only collate layers with a creation time < this value * @param worldServerToMatch the worldServerReader to match against * @return a list of matching WorldSelectionUndo in descending order of time. */ private LinkedList<WorldSelectionUndo> collatePrecedingUndoLayersAllHistories(long creationTime, WorldServer worldServerToMatch) { LinkedList<UndoLayerInfo> combinedList = collatePrecedingUndoLayers(undoLayersSimple, creationTime, worldServerToMatch); combinedList.addAll(collatePrecedingUndoLayers(undoLayersComplex, creationTime, worldServerToMatch)); Collections.sort(combinedList); LinkedList<WorldSelectionUndo> collatedList = new LinkedList<WorldSelectionUndo>(); for (UndoLayerInfo layerInfo : combinedList) { collatedList.addFirst(layerInfo.worldSelectionUndo); // reverse the order } return collatedList; } private LinkedList<UndoLayerInfo> collatePrecedingUndoLayers(LinkedList<UndoLayerInfo> whichHistory, long creationTime, WorldServer worldServerToMatch) { LinkedList<UndoLayerInfo> collatedList = new LinkedList<UndoLayerInfo>(); for (UndoLayerInfo undoLayerInfo : whichHistory) { if (undoLayerInfo.worldServer.get() == worldServerToMatch && undoLayerInfo.creationTime < creationTime) { collatedList.add(undoLayerInfo); } } return collatedList; } enum AsynchronousActionType {WRITE, UNDO}; private class AsynchronousWriteOrUndo implements AsynchronousToken { @Override public boolean isTaskComplete() { return completed; } @Override public boolean isTaskAborted() { return aborting && isTaskComplete(); } @Override public boolean isTimeToInterrupt() { return (interruptTimeNS == IMMEDIATE_TIMEOUT || (interruptTimeNS != INFINITE_TIMEOUT && System.nanoTime() >= interruptTimeNS)); } @Override public void setTimeOfInterrupt(long timeToStopNS) { interruptTimeNS = timeToStopNS; } @Override public void continueProcessing() { // first, complete the subTask (placement or undo of the fragment) // then perform all of the deferred undos (i.e. the Voxels locked by the subTask) and remove them from the history // beware - further undo may be added every time we interrupt if (completed) return; if (!executeSubTask()) return; if (undoLayerInfo != null) { switch (asynchronousActionType) { case WRITE: { undoLayerInfo.transactionID = transactionID; undoLayersComplex.add(undoLayerInfo); break; } case UNDO: { if (!aborting) { undoLayersComplex.remove(undoLayerInfo); } break; } default: assert false : "Invalid asynchronousActionType: " + asynchronousActionType; } undoLayerInfo = null; } while (!deferredSimpleUndoToPerform.isEmpty()) { UndoLayerInfo queuedUndoLayerInfo = deferredSimpleUndoToPerform.remove(0); LinkedList<WorldSelectionUndo> subsequentUndoLayers = collateSubsequentUndoLayersAllHistories(queuedUndoLayerInfo.creationTime, queuedUndoLayerInfo.worldServer.get()); queuedUndoLayerInfo.worldSelectionUndo.undoChanges(queuedUndoLayerInfo.worldServer.get(), subsequentUndoLayers); boolean foundAndRemoved = undoLayersSimple.remove(queuedUndoLayerInfo); assert (foundAndRemoved); if (isTimeToInterrupt()) return; } completed = true; cullUndoLayers(undoLayersComplex, maximumSimpleDepthPerPlayer, maximumComplexDepth); } @Override public void abortProcessing() { aborting = true; } @Override public double getFractionComplete() { return fractionComplete; } public AsynchronousWriteOrUndo(AsynchronousActionType i_actionType, AsynchronousToken i_subTask, UndoLayerInfo i_undoLayerInfo, UniqueTokenID i_transactionID) { subTask = i_subTask; undoLayerInfo = i_undoLayerInfo; interruptTimeNS = INFINITE_TIMEOUT; fractionComplete = 0; completed = false; asynchronousActionType = i_actionType; transactionID = (i_transactionID == null) ? new UniqueTokenID() : i_transactionID; } // returns true if the sub-task is finished public boolean executeSubTask() { if (subTask == null || subTask.isTaskComplete()) { return true; } if (aborting) { subTask.abortProcessing(); } subTask.setTimeOfInterrupt(interruptTimeNS); subTask.continueProcessing(); fractionComplete = subTask.getFractionComplete(); // System.out.println("AsynchronousWriteOrUndo fractionComplete:" + fractionComplete); return subTask.isTaskComplete(); } public VoxelSelectionWithOrigin getLockedRegion() { if (subTask == null || subTask.isTaskComplete()) return null; return subTask.getLockedRegion(); } @Override public UniqueTokenID getUniqueTokenID() { return uniqueTokenID; } /** * remove all blocks from the placement list which are locked by the current task * @param worldServer * @param blocksToCheck * @return */ public List<BlockPos> cullLockedVoxels(WorldServer worldServer, List<BlockPos> blocksToCheck) { if (undoLayerInfo == null) return blocksToCheck; VoxelSelectionWithOrigin lockedRegion = getLockedRegion(); if (lockedRegion == null) return blocksToCheck; WorldServer taskWorldServer = undoLayerInfo.worldServer.get(); if (taskWorldServer == null || taskWorldServer != worldServer) return blocksToCheck; LinkedList<BlockPos> culledList = new LinkedList<BlockPos>(); for (BlockPos coordinate : blocksToCheck) { if (!lockedRegion.getVoxelWXYZ(coordinate.getX(), coordinate.getY(), coordinate.getZ())) { culledList.add(coordinate); } } return culledList; } /** * Used when performing a simple undo at the same time as a complex task is taking place. * The simple undo is split into two parts: * 1) a portion that can be performed immediately (not locked by the complex task) * 2) a portion that will be performed after the complex task is finished * After the call, layerToBeSplit will contain only the voxels locked by the current task * These locked voxels are scheduled to be undone at the end of the current task. * The unlocked voxels, which can be executed immediately, are placed in the return value * @param layerToBeSplit * @return a new undo layer which contains only unlocked voxels. Shallow copy of original! Might have no voxels at all. If null, there is no locked * region and the undo hasn't been scheduled for later */ public UndoLayerInfo removeLockedVoxelsAndScheduleForLaterExecution(UndoLayerInfo layerToBeSplit) { if (undoLayerInfo == null) return null; UndoLayerInfo unlockedVoxelsOnly = new UndoLayerInfo(layerToBeSplit); VoxelSelectionWithOrigin lockedRegion = getLockedRegion(); if (lockedRegion == null) return null; unlockedVoxelsOnly.worldSelectionUndo = layerToBeSplit.worldSelectionUndo.splitByLockedVoxels(lockedRegion); deferredSimpleUndoToPerform.add(layerToBeSplit); // only the locked voxels remain return unlockedVoxelsOnly; } private List<UndoLayerInfo> deferredSimpleUndoToPerform = new ArrayList<UndoLayerInfo>(); private boolean completed; private long interruptTimeNS; private double fractionComplete; private AsynchronousToken subTask; private UndoLayerInfo undoLayerInfo; private AsynchronousActionType asynchronousActionType; private boolean aborting = false; private final UniqueTokenID uniqueTokenID = new UniqueTokenID(); private UniqueTokenID transactionID = null; } private AsynchronousWriteOrUndo currentAsynchronousTask; private LinkedList<UndoLayerInfo> undoLayersComplex = new LinkedList<UndoLayerInfo>(); // cloning tools private LinkedList<UndoLayerInfo> undoLayersSimple = new LinkedList<UndoLayerInfo>(); // instant tools private int maximumComplexDepth = 0; private int maximumSimpleDepthPerPlayer = 0; private static class UndoLayerInfo implements Comparable<UndoLayerInfo> { public UndoLayerInfo(long i_creationTime, WorldServer i_worldServer, EntityPlayerMP i_entityPlayerMP, WorldSelectionUndo i_worldSelectionUndo) { creationTime = i_creationTime; worldServer = new WeakReference<WorldServer>(i_worldServer); entityPlayerMP = new WeakReference<EntityPlayerMP>(i_entityPlayerMP); worldSelectionUndo = i_worldSelectionUndo; transactionID = new UniqueTokenID(); // default; might be replaced later // creatingTaskID = null; } // public UndoLayerInfo(long i_creationTime, WorldServer i_worldServer, EntityPlayerMP i_entityPlayerMP, WorldSelectionUndo i_worldSelectionUndo, UniqueTokenID i_creatingTaskID) // { // this(i_creationTime, i_worldServer, i_entityPlayerMP, i_worldSelectionUndo); //// creatingTaskID = i_creatingTaskID; // } // public void setCreatingTaskID(UniqueTokenID newCreatingTaskID) {creatingTaskID = newCreatingTaskID;} // shallow copy! public UndoLayerInfo(UndoLayerInfo source) { creationTime = source.creationTime; worldServer = source.worldServer; entityPlayerMP = source.entityPlayerMP; worldSelectionUndo = source.worldSelectionUndo; // creatingTaskID = source.creatingTaskID; undoHasCommenced = false; } public long creationTime; public WeakReference<WorldServer> worldServer; public WeakReference<EntityPlayerMP> entityPlayerMP; public WorldSelectionUndo worldSelectionUndo; public UniqueTokenID transactionID; boolean undoHasCommenced; // set to true once the player has commenced this undo // public UniqueTokenID creatingTaskID; @Override public int compareTo(UndoLayerInfo objectToCompareAgainst) { if (creationTime > objectToCompareAgainst.creationTime) return 1; if (creationTime < objectToCompareAgainst.creationTime) return -1; return 0; } } }