Added new history framework, visitors for history.

This commit is contained in:
sk89q 2014-03-30 22:33:16 -07:00
parent cfdd87efac
commit 10e672a94a
9 changed files with 545 additions and 45 deletions

View File

@ -88,8 +88,8 @@ public class DoubleArrayList<A, B> implements Iterable<Map.Entry<A, B>> {
* *
* @return * @return
*/ */
public Iterator<Map.Entry<A, B>> iterator() { public Iterator<Map.Entry<A, B>> iterator(boolean reversed) {
if (isReversed) { if (reversed) {
return new ReverseEntryIterator<Map.Entry<A, B>>( return new ReverseEntryIterator<Map.Entry<A, B>>(
listA.listIterator(listA.size()), listA.listIterator(listA.size()),
listB.listIterator(listB.size())); listB.listIterator(listB.size()));
@ -100,6 +100,15 @@ public class DoubleArrayList<A, B> implements Iterable<Map.Entry<A, B>> {
} }
} }
/**
* Get an entry set.
*
* @return
*/
public Iterator<Map.Entry<A, B>> iterator() {
return iterator(isReversed);
}
/** /**
* Entry iterator. * Entry iterator.
* *

View File

@ -36,6 +36,7 @@ import com.sk89q.worldedit.function.block.BlockReplace;
import com.sk89q.worldedit.function.block.Naturalizer; import com.sk89q.worldedit.function.block.Naturalizer;
import com.sk89q.worldedit.function.generator.GardenPatchGenerator; import com.sk89q.worldedit.function.generator.GardenPatchGenerator;
import com.sk89q.worldedit.function.mask.*; import com.sk89q.worldedit.function.mask.*;
import com.sk89q.worldedit.function.operation.ChangeSetExecutor;
import com.sk89q.worldedit.function.operation.ForwardExtentCopy; import com.sk89q.worldedit.function.operation.ForwardExtentCopy;
import com.sk89q.worldedit.function.operation.OperationHelper; import com.sk89q.worldedit.function.operation.OperationHelper;
import com.sk89q.worldedit.function.operation.OperationQueue; import com.sk89q.worldedit.function.operation.OperationQueue;
@ -43,6 +44,10 @@ import com.sk89q.worldedit.function.pattern.BlockPattern;
import com.sk89q.worldedit.function.pattern.Patterns; import com.sk89q.worldedit.function.pattern.Patterns;
import com.sk89q.worldedit.function.util.RegionOffset; import com.sk89q.worldedit.function.util.RegionOffset;
import com.sk89q.worldedit.function.visitor.*; import com.sk89q.worldedit.function.visitor.*;
import com.sk89q.worldedit.history.changeset.BlockOptimizedHistory;
import com.sk89q.worldedit.history.changeset.ChangeSet;
import com.sk89q.worldedit.history.UndoContext;
import com.sk89q.worldedit.history.change.BlockChange;
import com.sk89q.worldedit.masks.Mask; import com.sk89q.worldedit.masks.Mask;
import com.sk89q.worldedit.math.interpolation.Interpolation; import com.sk89q.worldedit.math.interpolation.Interpolation;
import com.sk89q.worldedit.math.interpolation.KochanekBartelsInterpolation; import com.sk89q.worldedit.math.interpolation.KochanekBartelsInterpolation;
@ -76,27 +81,10 @@ import static com.sk89q.worldedit.regions.Regions.*;
*/ */
public class EditSession implements Extent { public class EditSession implements Extent {
/** private final static Random prng = new Random();
* Random number generator.
*/
private static Random prng = new Random();
/**
* World.
*/
protected LocalWorld world; protected LocalWorld world;
private final ChangeSet changeSet = new BlockOptimizedHistory();
/**
* Stores the original blocks before modification.
*/
private DoubleArrayList<BlockVector, BaseBlock> original =
new DoubleArrayList<BlockVector, BaseBlock>(true);
/**
* Stores the current blocks.
*/
private DoubleArrayList<BlockVector, BaseBlock> current =
new DoubleArrayList<BlockVector, BaseBlock>(false);
/** /**
* Blocks that should be placed before last. * Blocks that should be placed before last.
@ -277,31 +265,35 @@ public class EditSession implements Extent {
} }
} }
// if (!original.containsKey(blockPt)) { if (maxBlocks != -1 && changeSet.size() > maxBlocks) {
original.put(blockPt, getBlock(pt));
if (maxBlocks != -1 && original.size() > maxBlocks) {
throw new MaxChangedBlocksException(maxBlocks); throw new MaxChangedBlocksException(maxBlocks);
} }
// }
current.put(blockPt, block); changeSet.add(new BlockChange(blockPt, getBlock(pt), block));
return smartSetBlock(pt, block); return smartSetBlock(pt, block);
} }
/**
* Get the underlying {@link ChangeSet}.
*
* @return the change set
*/
public ChangeSet getChangeSet() {
return changeSet;
}
/** /**
* Insert a contrived block change into the history. * Insert a contrived block change into the history.
* *
* @param pt * @param position the position
* @param existing * @param existing the previous block at that position
* @param block * @param block the new block
* @deprecated Get the change set with {@link #getChangeSet()} and add the change with that
*/ */
public void rememberChange(Vector pt, BaseBlock existing, BaseBlock block) { @Deprecated
BlockVector blockPt = pt.toBlockVector(); public void rememberChange(Vector position, BaseBlock existing, BaseBlock block) {
changeSet.add(new BlockChange(position.toBlockVector(), existing, block));
original.put(blockPt, existing);
current.put(pt.toBlockVector(), block);
} }
/** /**
@ -436,10 +428,9 @@ public class EditSession implements Extent {
* @param sess * @param sess
*/ */
public void undo(EditSession sess) { public void undo(EditSession sess) {
for (Map.Entry<BlockVector, BaseBlock> entry : original) { UndoContext context = new UndoContext();
BlockVector pt = entry.getKey(); context.setExtent(sess);
sess.smartSetBlock(pt, entry.getValue()); OperationHelper.completeBlindly(ChangeSetExecutor.createUndo(changeSet, context));
}
sess.flushQueue(); sess.flushQueue();
} }
@ -449,10 +440,9 @@ public class EditSession implements Extent {
* @param sess * @param sess
*/ */
public void redo(EditSession sess) { public void redo(EditSession sess) {
for (Map.Entry<BlockVector, BaseBlock> entry : current) { UndoContext context = new UndoContext();
BlockVector pt = entry.getKey(); context.setExtent(sess);
sess.smartSetBlock(pt, entry.getValue()); OperationHelper.completeBlindly(ChangeSetExecutor.createRedo(changeSet, context));
}
sess.flushQueue(); sess.flushQueue();
} }
@ -462,7 +452,7 @@ public class EditSession implements Extent {
* @return * @return
*/ */
public int size() { public int size() {
return original.size(); return changeSet.size();
} }
/** /**
@ -648,7 +638,7 @@ public class EditSession implements Extent {
* @return * @return
*/ */
public int getBlockChangeCount() { public int getBlockChangeCount() {
return original.size(); return changeSet.size();
} }
/** /**

View File

@ -0,0 +1,80 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.function.operation;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.history.change.Change;
import com.sk89q.worldedit.history.changeset.ChangeSet;
import com.sk89q.worldedit.history.UndoContext;
import java.util.Iterator;
import static com.google.common.base.Preconditions.checkNotNull;
public class ChangeSetExecutor implements Operation {
public enum Type {UNDO, REDO}
private final Iterator<Change> iterator;
private final Type type;
private final UndoContext context;
private ChangeSetExecutor(ChangeSet changeSet, Type type, UndoContext context) {
checkNotNull(changeSet);
checkNotNull(type);
checkNotNull(context);
this.type = type;
this.context = context;
if (type == Type.UNDO) {
iterator = changeSet.backwardIterator();
} else {
iterator = changeSet.forwardIterator();
}
}
@Override
public Operation resume() throws WorldEditException {
while (iterator.hasNext()) {
Change change = iterator.next();
if (type == Type.UNDO) {
change.undo(context);
} else {
change.redo(context);
}
}
return null;
}
@Override
public void cancel() {
}
public static ChangeSetExecutor createUndo(ChangeSet changeSet, UndoContext context) {
return new ChangeSetExecutor(changeSet, Type.UNDO, context);
}
public static ChangeSetExecutor createRedo(ChangeSet changeSet, UndoContext context) {
return new ChangeSetExecutor(changeSet, Type.REDO, context);
}
}

View File

@ -0,0 +1,54 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.history;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.history.change.BlockChange;
import javax.annotation.Nullable;
/**
* Provides context for undo and redo operations.
* </p>
* For example, {@link BlockChange}s take the {@link Extent} from the
* context rather than store a reference to one.
*/
public class UndoContext {
private Extent extent;
/**
* Get the extent set on this context.
*
* @return an extent or null
*/
public @Nullable Extent getExtent() {
return extent;
}
/**
* Set the extent on this context.
*
* @param extent an extent or null
*/
public void setExtent(@Nullable Extent extent) {
this.extent = extent;
}
}

View File

@ -0,0 +1,96 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.history.change;
import com.sk89q.worldedit.BlockVector;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.history.UndoContext;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Represents a block change that may be undone or replayed.
* </p>
* This block change does not have an {@link Extent} assigned to it because
* one will be taken from the passed {@link UndoContext}. If the context
* does not have an extent (it is null), cryptic errors may occur.
*/
public class BlockChange implements Change {
private final BlockVector position;
private final BaseBlock previous;
private final BaseBlock current;
/**
* Create a new block change.
*
* @param position the position
* @param previous the previous block
* @param current the current block
*/
public BlockChange(BlockVector position, BaseBlock previous, BaseBlock current) {
checkNotNull(position);
checkNotNull(previous);
checkNotNull(current);
this.position = position;
this.previous = previous;
this.current = current;
}
/**
* Get the position.
*
* @return the position
*/
public BlockVector getPosition() {
return position;
}
/**
* Get the previous block.
*
* @return the previous block
*/
public BaseBlock getPrevious() {
return previous;
}
/**
* Get the current block.
*
* @return the current block
*/
public BaseBlock getCurrent() {
return current;
}
@Override
public void undo(UndoContext context) throws WorldEditException {
checkNotNull(context.getExtent()).setBlock(position, previous, true);
}
@Override
public void redo(UndoContext context) throws WorldEditException {
checkNotNull(context.getExtent()).setBlock(position, current, true);
}
}

View File

@ -0,0 +1,49 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.history.change;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.history.changeset.ChangeSet;
import com.sk89q.worldedit.history.UndoContext;
/**
* Describes a change that can be undone or re-applied.
*/
public interface Change {
/**
* Perform an undo. This method may not be available if the object
* was returned from {@link ChangeSet#forwardIterator()}.
*
* @param context a context for undo
* @throws WorldEditException on an error
*/
void undo(UndoContext context) throws WorldEditException;
/**
* Perform an redo. This method may not be available if the object
* was returned from {@link ChangeSet#backwardIterator()} ()}.
*
* @param context a context for redo
* @throws WorldEditException on an error
*/
void redo(UndoContext context) throws WorldEditException;
}

View File

@ -0,0 +1,59 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.history.changeset;
import com.google.common.collect.Lists;
import com.sk89q.worldedit.history.change.Change;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Stores all {@link Change}s in an {@link ArrayList}.
*/
public class ArrayListHistory implements ChangeSet {
private final List<Change> changes = new ArrayList<Change>();
@Override
public void add(Change change) {
checkNotNull(change);
changes.add(change);
}
@Override
public Iterator<Change> backwardIterator() {
return Lists.reverse(changes).iterator();
}
@Override
public Iterator<Change> forwardIterator() {
return changes.iterator();
}
@Override
public int size() {
return changes.size();
}
}

View File

@ -0,0 +1,97 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.history.changeset;
import com.google.common.base.Function;
import com.google.common.collect.Iterators;
import com.sk89q.worldedit.BlockVector;
import com.sk89q.worldedit.DoubleArrayList;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.history.change.BlockChange;
import com.sk89q.worldedit.history.change.Change;
import java.util.ArrayList;
import java.util.Iterator;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.Map.Entry;
/**
* An extension of {@link ArrayListHistory} that stores {@link BlockChange}s
* separately in two {@link ArrayList}s.
* </p>
* Whether this is a good idea or not is highly questionable, but this class
* exists because this is how history was implemented in WorldEdit for
* many years.
*/
public class BlockOptimizedHistory extends ArrayListHistory {
private final DoubleArrayList<BlockVector, BaseBlock> previous = new DoubleArrayList<BlockVector, BaseBlock>(true);
private final DoubleArrayList<BlockVector, BaseBlock> current = new DoubleArrayList<BlockVector, BaseBlock>(false);
@Override
public void add(Change change) {
checkNotNull(change);
if (change instanceof BlockChange) {
BlockChange blockChange = (BlockChange) change;
BlockVector position = blockChange.getPosition();
previous.put(position, blockChange.getPrevious());
current.put(position, blockChange.getCurrent());
} else {
super.add(change);
}
}
@Override
public Iterator<Change> forwardIterator() {
return Iterators.concat(
super.forwardIterator(),
Iterators.transform(current.iterator(), createTransform()));
}
@Override
public Iterator<Change> backwardIterator() {
return Iterators.concat(
super.backwardIterator(),
Iterators.transform(previous.iterator(true), createTransform()));
}
@Override
public int size() {
return super.size() + previous.size();
}
/**
* Create a function that transforms each entry from the double array lists' iterator
* into an {@link Change}.
*
* @return a function
*/
private Function<Entry<BlockVector, BaseBlock>, Change> createTransform() {
return new Function<Entry<BlockVector, BaseBlock>, Change>() {
@Override
public Change apply(Entry<BlockVector, BaseBlock> entry) {
return new BlockChange(entry.getKey(), entry.getValue(), entry.getValue());
}
};
}
}

View File

@ -0,0 +1,66 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.worldedit.history.changeset;
import com.sk89q.worldedit.history.change.Change;
import java.util.Iterator;
/**
* Tracks a set of undoable operations and allows their undo and redo. The
* entirety of a change set should be undone and redone at once.
*/
public interface ChangeSet {
/**
* Add the given change to the history.
*
* @param change the change
*/
void add(Change change);
/**
* Get a backward directed iterator that can be used for undo.
* </p>
* The iterator may return the changes out of order, as long as the final
* result after all changes have been applied is correct.
*
* @return a undo directed iterator
*/
Iterator<Change> backwardIterator();
/**
* Get a forward directed iterator that can be used for redo.
* </p>
* The iterator may return the changes out of order, as long as the final
* result after all changes have been applied is correct.
*
* @return a forward directed iterator
*/
Iterator<Change> forwardIterator();
/**
* Get the number of stored changes.
*
* @return the change count
*/
int size();
}