mirror of
https://github.com/boy0001/FastAsyncWorldedit.git
synced 2024-12-29 12:37:53 +01:00
Started work on disk storage for history
This commit is contained in:
parent
d9d806ac4a
commit
3cf106842d
@ -28,6 +28,7 @@ import com.sk89q.worldedit.EditSession;
|
||||
import com.sk89q.worldedit.WorldEdit;
|
||||
import com.sk89q.worldedit.command.SchematicCommands;
|
||||
import com.sk89q.worldedit.command.ScriptingCommands;
|
||||
import com.sk89q.worldedit.extension.platform.CommandManager;
|
||||
import com.sk89q.worldedit.function.operation.Operations;
|
||||
import com.sk89q.worldedit.function.visitor.BreadthFirstSearch;
|
||||
import com.sk89q.worldedit.function.visitor.DownwardVisitor;
|
||||
@ -212,6 +213,8 @@ public class Fawe {
|
||||
NonRisingVisitor.inject();
|
||||
RecursiveVisitor.inject();
|
||||
RegionVisitor.inject();
|
||||
CommandManager.inject();
|
||||
// DispatcherWrapper.inject();
|
||||
}
|
||||
|
||||
private void setupMemoryListener() {
|
||||
|
@ -37,4 +37,136 @@ public class FaweCache {
|
||||
CACHE_DATA[i] = (byte) k;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean hasData(int id) {
|
||||
switch (id) {
|
||||
case 0:
|
||||
case 2:
|
||||
case 4:
|
||||
case 13:
|
||||
case 14:
|
||||
case 15:
|
||||
case 20:
|
||||
case 21:
|
||||
case 22:
|
||||
case 25:
|
||||
case 30:
|
||||
case 32:
|
||||
case 37:
|
||||
case 39:
|
||||
case 40:
|
||||
case 41:
|
||||
case 42:
|
||||
case 45:
|
||||
case 46:
|
||||
case 47:
|
||||
case 48:
|
||||
case 49:
|
||||
case 51:
|
||||
case 52:
|
||||
case 54:
|
||||
case 56:
|
||||
case 57:
|
||||
case 58:
|
||||
case 60:
|
||||
case 61:
|
||||
case 62:
|
||||
case 7:
|
||||
case 8:
|
||||
case 9:
|
||||
case 10:
|
||||
case 11:
|
||||
case 73:
|
||||
case 74:
|
||||
case 78:
|
||||
case 79:
|
||||
case 80:
|
||||
case 81:
|
||||
case 82:
|
||||
case 83:
|
||||
case 84:
|
||||
case 85:
|
||||
case 87:
|
||||
case 88:
|
||||
case 101:
|
||||
case 102:
|
||||
case 103:
|
||||
case 110:
|
||||
case 112:
|
||||
case 113:
|
||||
case 117:
|
||||
case 121:
|
||||
case 122:
|
||||
case 123:
|
||||
case 124:
|
||||
case 129:
|
||||
case 133:
|
||||
case 138:
|
||||
case 137:
|
||||
case 140:
|
||||
case 165:
|
||||
case 166:
|
||||
case 169:
|
||||
case 170:
|
||||
case 172:
|
||||
case 173:
|
||||
case 174:
|
||||
case 176:
|
||||
case 177:
|
||||
case 181:
|
||||
case 182:
|
||||
case 188:
|
||||
case 189:
|
||||
case 190:
|
||||
case 191:
|
||||
case 192:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean hasNBT(int id) {
|
||||
switch (id) {
|
||||
case 54:
|
||||
case 130:
|
||||
case 142:
|
||||
case 27:
|
||||
case 137:
|
||||
case 52:
|
||||
case 154:
|
||||
case 84:
|
||||
case 25:
|
||||
case 144:
|
||||
case 138:
|
||||
case 176:
|
||||
case 177:
|
||||
case 63:
|
||||
case 119:
|
||||
case 68:
|
||||
case 323:
|
||||
case 117:
|
||||
case 116:
|
||||
case 28:
|
||||
case 66:
|
||||
case 157:
|
||||
case 61:
|
||||
case 62:
|
||||
case 140:
|
||||
case 146:
|
||||
case 149:
|
||||
case 150:
|
||||
case 158:
|
||||
case 23:
|
||||
case 123:
|
||||
case 124:
|
||||
case 29:
|
||||
case 33:
|
||||
case 151:
|
||||
case 178:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,8 @@ import java.util.Map.Entry;
|
||||
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
|
||||
import com.sk89q.worldedit.LocalSession;
|
||||
|
||||
public class Settings {
|
||||
|
||||
public static int MAX_BLOCKSTATES = 1337;
|
||||
@ -22,6 +24,7 @@ public class Settings {
|
||||
public static List<String> WE_BLACKLIST = Arrays.asList("cs", ".s", "restore", "snapshot", "delchunks", "listchunks");
|
||||
public static long MEM_FREE = 95;
|
||||
public static boolean ENABLE_HARD_LIMIT = true;
|
||||
public static boolean STORE_HISTORY_ON_DISK = true;
|
||||
|
||||
public static void setup(final File file) {
|
||||
if (!file.exists()) {
|
||||
@ -45,6 +48,7 @@ public class Settings {
|
||||
options.put("max-memory-percent", MEM_FREE);
|
||||
options.put("crash-mitigation", ENABLE_HARD_LIMIT);
|
||||
options.put("fix-all-lighting", FIX_ALL_LIGHTING);
|
||||
options.put("store-history-on-disk", STORE_HISTORY_ON_DISK);
|
||||
|
||||
for (final Entry<String, Object> node : options.entrySet()) {
|
||||
if (!config.contains(node.getKey())) {
|
||||
@ -61,6 +65,9 @@ public class Settings {
|
||||
REQUIRE_SELECTION = config.getBoolean("require-selection-in-mask");
|
||||
WE_BLACKLIST = config.getStringList("command-blacklist");
|
||||
ENABLE_HARD_LIMIT = config.getBoolean("crash-mitigation");
|
||||
if (STORE_HISTORY_ON_DISK = config.getBoolean("store-history-on-disk")) {
|
||||
LocalSession.MAX_HISTORY_SIZE = Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
try {
|
||||
config.save(file);
|
||||
|
29
src/main/java/com/boydti/fawe/object/NullChangeSet.java
Normal file
29
src/main/java/com/boydti/fawe/object/NullChangeSet.java
Normal file
@ -0,0 +1,29 @@
|
||||
package com.boydti.fawe.object;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
|
||||
import com.sk89q.worldedit.history.change.Change;
|
||||
import com.sk89q.worldedit.history.changeset.ChangeSet;
|
||||
|
||||
public class NullChangeSet implements ChangeSet {
|
||||
|
||||
@Override
|
||||
public void add(Change change) {}
|
||||
|
||||
@Override
|
||||
public Iterator<Change> backwardIterator() {
|
||||
return new ArrayList<Change>().iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Change> forwardIterator() {
|
||||
return new ArrayList<Change>().iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package com.boydti.fawe.object.changeset;
|
||||
|
||||
import com.sk89q.worldedit.history.changeset.BlockOptimizedHistory;
|
||||
|
||||
/**
|
||||
* History optimized for speed
|
||||
* - Low CPU usage
|
||||
* - High memory usage
|
||||
* - No disk usage
|
||||
*/
|
||||
public class CPUOptimizedHistory extends BlockOptimizedHistory {
|
||||
|
||||
}
|
@ -0,0 +1,234 @@
|
||||
package com.boydti.fawe.object.changeset;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
import com.boydti.fawe.Fawe;
|
||||
import com.boydti.fawe.FaweCache;
|
||||
import com.sk89q.jnbt.CompoundTag;
|
||||
import com.sk89q.jnbt.NBTOutputStream;
|
||||
import com.sk89q.worldedit.BlockVector;
|
||||
import com.sk89q.worldedit.blocks.BaseBlock;
|
||||
import com.sk89q.worldedit.history.change.BlockChange;
|
||||
import com.sk89q.worldedit.history.change.Change;
|
||||
import com.sk89q.worldedit.history.changeset.ChangeSet;
|
||||
|
||||
/**
|
||||
* Store the change on disk
|
||||
* - High disk usage
|
||||
* - Moderate CPU usage
|
||||
* - Minimal memory usage
|
||||
* - Slow
|
||||
*/
|
||||
public class DiskStorageHistory implements ChangeSet {
|
||||
|
||||
private final File bdFile;
|
||||
private final File nbtFile;
|
||||
private final File anyFile;
|
||||
|
||||
private GZIPOutputStream osBD;
|
||||
private ObjectOutputStream osANY;
|
||||
private NBTOutputStream osNBT;
|
||||
|
||||
private int ox;
|
||||
private int oz;
|
||||
|
||||
private final AtomicInteger size;
|
||||
|
||||
public DiskStorageHistory() {
|
||||
size = new AtomicInteger();
|
||||
UUID uuid = UUID.randomUUID();
|
||||
nbtFile = new File(Fawe.imp().getDirectory(), "history" + File.separator + uuid.toString() + ".nbt");
|
||||
bdFile = new File(Fawe.imp().getDirectory(), "history" + File.separator + uuid.toString() + ".bd");
|
||||
anyFile = new File(Fawe.imp().getDirectory(), "history" + File.separator + uuid.toString() + ".any");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(Change change) {
|
||||
size.incrementAndGet();
|
||||
if ((change instanceof BlockChange)) {
|
||||
add((BlockChange) change);
|
||||
} else {
|
||||
System.out.print("[FAWE] Does not support " + change + " yet! (Please bug Empire92)");
|
||||
}
|
||||
}
|
||||
|
||||
public void flush() {
|
||||
try {
|
||||
if (osBD != null) {
|
||||
osBD.flush();
|
||||
osBD.close();
|
||||
}
|
||||
if (osANY != null) {
|
||||
osANY.flush();
|
||||
osANY.close();
|
||||
}
|
||||
if (osNBT != null) {
|
||||
osNBT.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void add(BlockChange change) {
|
||||
try {
|
||||
BlockVector loc = change.getPosition();
|
||||
int x = loc.getBlockX();
|
||||
int y = loc.getBlockY();
|
||||
int z = loc.getBlockZ();
|
||||
|
||||
BaseBlock from = change.getPrevious();
|
||||
BaseBlock to = change.getCurrent();
|
||||
|
||||
int idfrom = from.getId();
|
||||
int combinedFrom = (byte) (FaweCache.hasData(idfrom) ? ((idfrom << 4) + from.getData()) : (idfrom << 4));
|
||||
CompoundTag nbtFrom = FaweCache.hasData(idfrom) ? from.getNbtData() : null;
|
||||
|
||||
int idTo = to.getId();
|
||||
int combinedTo = (byte) (FaweCache.hasData(idTo) ? ((idTo << 4) + to.getData()) : (idTo << 4));
|
||||
CompoundTag nbtTo = FaweCache.hasData(idTo) ? to.getNbtData() : null;
|
||||
|
||||
GZIPOutputStream stream = getBAOS(x, y, z);
|
||||
//x
|
||||
stream.write((x - ox) & 0xff);
|
||||
stream.write(((x - ox) >> 8) & 0xff);
|
||||
//z
|
||||
stream.write((z - oz) & 0xff);
|
||||
stream.write(((z - oz) >> 8) & 0xff);
|
||||
//y
|
||||
stream.write((byte) y);
|
||||
//from
|
||||
stream.write((combinedFrom) & 0xff);
|
||||
stream.write(((combinedFrom) >> 8) & 0xff);
|
||||
//to
|
||||
stream.write((combinedTo) & 0xff);
|
||||
stream.write(((combinedTo) >> 8) & 0xff);
|
||||
|
||||
/*
|
||||
* [header]
|
||||
* [int x][int z] origin
|
||||
* [contents]
|
||||
* relative: short x,short z,unsigned byte y
|
||||
* from: char
|
||||
* to: char
|
||||
*/
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private GZIPOutputStream getBAOS(int x, int y, int z) throws IOException {
|
||||
if (osBD != null) {
|
||||
return osBD;
|
||||
}
|
||||
bdFile.getParentFile().mkdirs();
|
||||
bdFile.createNewFile();
|
||||
osBD = new GZIPOutputStream(new FileOutputStream(bdFile), true);
|
||||
ox = x;
|
||||
oz = z;
|
||||
osBD.write((ox) & 0xff);
|
||||
osBD.write(((ox) >> 8) & 0xff);
|
||||
osBD.write((oz) & 0xff);
|
||||
osBD.write(((oz) >> 8) & 0xff);
|
||||
return osBD;
|
||||
}
|
||||
|
||||
private NBTOutputStream getNBTOS(int x, int y, int z) throws IOException {
|
||||
if (osNBT != null) {
|
||||
return osNBT;
|
||||
}
|
||||
nbtFile.getParentFile().mkdirs();
|
||||
nbtFile.createNewFile();
|
||||
// osNBT = new FileOutputStream(bdFile);
|
||||
// TODO FIXME
|
||||
return osNBT;
|
||||
}
|
||||
|
||||
@SuppressWarnings("resource")
|
||||
public Iterator<Change> getIterator(final boolean dir) {
|
||||
flush();
|
||||
try {
|
||||
if (bdFile.exists()) {
|
||||
final GZIPInputStream gis = new GZIPInputStream(new FileInputStream(bdFile));
|
||||
gis.skip(4);
|
||||
return new Iterator<Change>() {
|
||||
|
||||
private Change last = read();
|
||||
|
||||
public Change read() {
|
||||
try {
|
||||
int x = gis.read() + (gis.read() << 8) + ox;
|
||||
int z = gis.read() + (gis.read() << 8) + oz;
|
||||
int y = gis.read() & 0xff;
|
||||
int from1 = gis.read();
|
||||
int from2 = gis.read();
|
||||
BaseBlock from = new BaseBlock(((from2 << 4) + (from1 >> 4)), (from1 & 0xf));
|
||||
int to1 = gis.read();
|
||||
int to2 = gis.read();
|
||||
BaseBlock to = new BaseBlock(((to2 << 4) + (to1 >> 4)), (to1 & 0xf));
|
||||
BlockVector position = new BlockVector(x, y, z);
|
||||
return dir ? new BlockChange(position, to, from) : new BlockChange(position, from, to);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
if (last != null) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
gis.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Change next() {
|
||||
Change tmp = last;
|
||||
last = read();
|
||||
return tmp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new IllegalArgumentException("CANNOT REMIVE");
|
||||
}
|
||||
};
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return new ArrayList<Change>().iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Change> backwardIterator() {
|
||||
return getIterator(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Change> forwardIterator() {
|
||||
return getIterator(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
flush();
|
||||
return size.get();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package com.boydti.fawe.object.changeset;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import com.sk89q.worldedit.history.change.Change;
|
||||
import com.sk89q.worldedit.history.changeset.ChangeSet;
|
||||
|
||||
/**
|
||||
* ChangeSet optimized for low memory usage
|
||||
* - No disk usage
|
||||
* - High CPU usage
|
||||
* - Low memory usage
|
||||
*/
|
||||
public class MemoryOptimizedHistory implements ChangeSet {
|
||||
|
||||
@Override
|
||||
public void add(Change paramChange) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Change> backwardIterator() {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Change> forwardIterator() {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
// TODO Auto-generated method stub
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package com.boydti.fawe.object;
|
||||
package com.boydti.fawe.object.extent;
|
||||
|
||||
import java.util.List;
|
||||
|
@ -1,4 +1,4 @@
|
||||
package com.boydti.fawe.object;
|
||||
package com.boydti.fawe.object.extent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
@ -1,10 +1,12 @@
|
||||
package com.boydti.fawe.object;
|
||||
package com.boydti.fawe.object.extent;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
import com.boydti.fawe.config.BBC;
|
||||
import com.boydti.fawe.config.Settings;
|
||||
import com.boydti.fawe.object.FawePlayer;
|
||||
import com.boydti.fawe.object.RegionWrapper;
|
||||
import com.boydti.fawe.util.MainUtil;
|
||||
import com.boydti.fawe.util.SetQueue;
|
||||
import com.boydti.fawe.util.TaskManager;
|
@ -1,7 +1,10 @@
|
||||
package com.boydti.fawe.util;
|
||||
package com.boydti.fawe.object.extent;
|
||||
|
||||
import com.boydti.fawe.config.BBC;
|
||||
import com.boydti.fawe.object.FawePlayer;
|
||||
import com.boydti.fawe.util.MemUtil;
|
||||
import com.boydti.fawe.util.Perm;
|
||||
import com.boydti.fawe.util.WEManager;
|
||||
import com.sk89q.worldedit.Vector;
|
||||
import com.sk89q.worldedit.WorldEditException;
|
||||
import com.sk89q.worldedit.blocks.BaseBlock;
|
5
src/main/java/com/boydti/fawe/util/FileUtil.java
Normal file
5
src/main/java/com/boydti/fawe/util/FileUtil.java
Normal file
@ -0,0 +1,5 @@
|
||||
package com.boydti.fawe.util;
|
||||
|
||||
public class FileUtil {
|
||||
|
||||
}
|
@ -7,8 +7,8 @@ import java.util.HashSet;
|
||||
import com.boydti.fawe.bukkit.regions.FaweMask;
|
||||
import com.boydti.fawe.config.BBC;
|
||||
import com.boydti.fawe.object.FawePlayer;
|
||||
import com.boydti.fawe.object.NullExtent;
|
||||
import com.boydti.fawe.object.RegionWrapper;
|
||||
import com.boydti.fawe.object.extent.NullExtent;
|
||||
import com.boydti.fawe.regions.FaweMaskManager;
|
||||
import com.sk89q.worldedit.extent.AbstractDelegateExtent;
|
||||
import com.sk89q.worldedit.extent.Extent;
|
||||
|
@ -42,15 +42,16 @@ import com.boydti.fawe.Fawe;
|
||||
import com.boydti.fawe.FaweCache;
|
||||
import com.boydti.fawe.config.BBC;
|
||||
import com.boydti.fawe.object.EditSessionWrapper;
|
||||
import com.boydti.fawe.object.FastWorldEditExtent;
|
||||
import com.boydti.fawe.object.FawePlayer;
|
||||
import com.boydti.fawe.object.NullExtent;
|
||||
import com.boydti.fawe.object.ProcessedWEExtent;
|
||||
import com.boydti.fawe.object.RegionWrapper;
|
||||
import com.boydti.fawe.object.changeset.DiskStorageHistory;
|
||||
import com.boydti.fawe.object.extent.FastWorldEditExtent;
|
||||
import com.boydti.fawe.object.extent.NullExtent;
|
||||
import com.boydti.fawe.object.extent.ProcessedWEExtent;
|
||||
import com.boydti.fawe.object.extent.SafeExtentWrapper;
|
||||
import com.boydti.fawe.util.ExtentWrapper;
|
||||
import com.boydti.fawe.util.MemUtil;
|
||||
import com.boydti.fawe.util.Perm;
|
||||
import com.boydti.fawe.util.SafeExtentWrapper;
|
||||
import com.boydti.fawe.util.TaskManager;
|
||||
import com.boydti.fawe.util.WEManager;
|
||||
import com.sk89q.worldedit.blocks.BaseBlock;
|
||||
@ -100,7 +101,6 @@ import com.sk89q.worldedit.function.visitor.RecursiveVisitor;
|
||||
import com.sk89q.worldedit.function.visitor.RegionVisitor;
|
||||
import com.sk89q.worldedit.history.UndoContext;
|
||||
import com.sk89q.worldedit.history.change.BlockChange;
|
||||
import com.sk89q.worldedit.history.changeset.BlockOptimizedHistory;
|
||||
import com.sk89q.worldedit.history.changeset.ChangeSet;
|
||||
import com.sk89q.worldedit.internal.expression.Expression;
|
||||
import com.sk89q.worldedit.internal.expression.ExpressionException;
|
||||
@ -149,7 +149,7 @@ public class EditSession implements Extent {
|
||||
}
|
||||
|
||||
protected final World world;
|
||||
private final ChangeSet changeSet = new BlockOptimizedHistory();
|
||||
private final ChangeSet changeSet;
|
||||
private final EditSessionWrapper wrapper;
|
||||
private MultiStageReorder reorderExtent;
|
||||
private @Nullable Extent changeSetExtent;
|
||||
@ -214,6 +214,8 @@ public class EditSession implements Extent {
|
||||
this.thread = Fawe.get().getMainThread();
|
||||
this.world = world;
|
||||
this.wrapper = Fawe.imp().getEditSessionWrapper(this);
|
||||
// this.changeSet = new BlockOptimizedHistory();
|
||||
this.changeSet = new DiskStorageHistory();
|
||||
|
||||
// Invalid; return null extent
|
||||
if (world == null) {
|
||||
|
@ -1,111 +0,0 @@
|
||||
package com.sk89q.worldedit.command;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import com.boydti.fawe.util.TaskManager;
|
||||
import com.sk89q.minecraft.util.commands.CommandException;
|
||||
import com.sk89q.minecraft.util.commands.CommandLocals;
|
||||
import com.sk89q.worldedit.WorldEdit;
|
||||
import com.sk89q.worldedit.extension.platform.CommandManager;
|
||||
import com.sk89q.worldedit.extension.platform.PlatformManager;
|
||||
import com.sk89q.worldedit.util.command.CommandCallable;
|
||||
import com.sk89q.worldedit.util.command.CommandMapping;
|
||||
import com.sk89q.worldedit.util.command.Description;
|
||||
import com.sk89q.worldedit.util.command.Dispatcher;
|
||||
|
||||
public class DispatcherWrapper implements Dispatcher {
|
||||
private final Dispatcher parent;
|
||||
|
||||
public final Dispatcher getParent() {
|
||||
return this.parent;
|
||||
}
|
||||
|
||||
public DispatcherWrapper(final Dispatcher parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerCommand(final CommandCallable callable, final String... alias) {
|
||||
this.parent.registerCommand(callable, alias);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<CommandMapping> getCommands() {
|
||||
return this.parent.getCommands();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getPrimaryAliases() {
|
||||
return this.parent.getPrimaryAliases();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getAliases() {
|
||||
return this.parent.getAliases();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandMapping get(final String alias) {
|
||||
return this.parent.get(alias);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(final String alias) {
|
||||
return this.parent.contains(alias);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object call(final String arguments, final CommandLocals locals, final String[] parentCommands) throws CommandException {
|
||||
TaskManager.IMP.async(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
DispatcherWrapper.this.parent.call(arguments, locals, parentCommands);
|
||||
} catch (final CommandException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Description getDescription() {
|
||||
return this.parent.getDescription();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean testPermission(final CommandLocals locals) {
|
||||
return this.parent.testPermission(locals);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getSuggestions(final String arguments, final CommandLocals locals) throws CommandException {
|
||||
return this.parent.getSuggestions(arguments, locals);
|
||||
}
|
||||
|
||||
public static void inject() {
|
||||
// Delayed injection
|
||||
TaskManager.IMP.task(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
final PlatformManager platform = WorldEdit.getInstance().getPlatformManager();
|
||||
final CommandManager command = platform.getCommandManager();
|
||||
final Class<? extends CommandManager> clazz = command.getClass();
|
||||
final Field field = clazz.getDeclaredField("dispatcher");
|
||||
field.setAccessible(true);
|
||||
final Dispatcher parent = (Dispatcher) field.get(command);
|
||||
final DispatcherWrapper dispatcher = new DispatcherWrapper(parent);
|
||||
field.set(command, dispatcher);
|
||||
} catch (final Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,321 @@
|
||||
/*
|
||||
* 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 Lesser 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 Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sk89q.worldedit.extension.platform;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.sk89q.worldedit.util.command.composition.LegacyCommandAdapter.adapt;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.logging.FileHandler;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import com.boydti.fawe.util.TaskManager;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.sk89q.minecraft.util.commands.CommandException;
|
||||
import com.sk89q.minecraft.util.commands.CommandLocals;
|
||||
import com.sk89q.minecraft.util.commands.CommandPermissionsException;
|
||||
import com.sk89q.minecraft.util.commands.WrappedCommandException;
|
||||
import com.sk89q.worldedit.EditSession;
|
||||
import com.sk89q.worldedit.LocalConfiguration;
|
||||
import com.sk89q.worldedit.LocalSession;
|
||||
import com.sk89q.worldedit.WorldEdit;
|
||||
import com.sk89q.worldedit.command.BiomeCommands;
|
||||
import com.sk89q.worldedit.command.BrushCommands;
|
||||
import com.sk89q.worldedit.command.ChunkCommands;
|
||||
import com.sk89q.worldedit.command.ClipboardCommands;
|
||||
import com.sk89q.worldedit.command.GeneralCommands;
|
||||
import com.sk89q.worldedit.command.GenerationCommands;
|
||||
import com.sk89q.worldedit.command.HistoryCommands;
|
||||
import com.sk89q.worldedit.command.NavigationCommands;
|
||||
import com.sk89q.worldedit.command.RegionCommands;
|
||||
import com.sk89q.worldedit.command.SchematicCommands;
|
||||
import com.sk89q.worldedit.command.ScriptingCommands;
|
||||
import com.sk89q.worldedit.command.SelectionCommands;
|
||||
import com.sk89q.worldedit.command.SnapshotCommands;
|
||||
import com.sk89q.worldedit.command.SnapshotUtilCommands;
|
||||
import com.sk89q.worldedit.command.SuperPickaxeCommands;
|
||||
import com.sk89q.worldedit.command.ToolCommands;
|
||||
import com.sk89q.worldedit.command.ToolUtilCommands;
|
||||
import com.sk89q.worldedit.command.UtilityCommands;
|
||||
import com.sk89q.worldedit.command.WorldEditCommands;
|
||||
import com.sk89q.worldedit.command.argument.ReplaceParser;
|
||||
import com.sk89q.worldedit.command.argument.TreeGeneratorParser;
|
||||
import com.sk89q.worldedit.command.composition.ApplyCommand;
|
||||
import com.sk89q.worldedit.command.composition.DeformCommand;
|
||||
import com.sk89q.worldedit.command.composition.PaintCommand;
|
||||
import com.sk89q.worldedit.command.composition.SelectionCommand;
|
||||
import com.sk89q.worldedit.command.composition.ShapedBrushCommand;
|
||||
import com.sk89q.worldedit.event.platform.CommandEvent;
|
||||
import com.sk89q.worldedit.event.platform.CommandSuggestionEvent;
|
||||
import com.sk89q.worldedit.function.factory.Deform;
|
||||
import com.sk89q.worldedit.function.factory.Deform.Mode;
|
||||
import com.sk89q.worldedit.internal.command.ActorAuthorizer;
|
||||
import com.sk89q.worldedit.internal.command.CommandLoggingHandler;
|
||||
import com.sk89q.worldedit.internal.command.UserCommandCompleter;
|
||||
import com.sk89q.worldedit.internal.command.WorldEditBinding;
|
||||
import com.sk89q.worldedit.internal.command.WorldEditExceptionConverter;
|
||||
import com.sk89q.worldedit.session.request.Request;
|
||||
import com.sk89q.worldedit.util.command.Dispatcher;
|
||||
import com.sk89q.worldedit.util.command.InvalidUsageException;
|
||||
import com.sk89q.worldedit.util.command.composition.ProvidedValue;
|
||||
import com.sk89q.worldedit.util.command.fluent.CommandGraph;
|
||||
import com.sk89q.worldedit.util.command.parametric.ExceptionConverter;
|
||||
import com.sk89q.worldedit.util.command.parametric.LegacyCommandsHandler;
|
||||
import com.sk89q.worldedit.util.command.parametric.ParametricBuilder;
|
||||
import com.sk89q.worldedit.util.eventbus.Subscribe;
|
||||
import com.sk89q.worldedit.util.formatting.ColorCodeBuilder;
|
||||
import com.sk89q.worldedit.util.formatting.component.CommandUsageBox;
|
||||
import com.sk89q.worldedit.util.logging.DynamicStreamHandler;
|
||||
import com.sk89q.worldedit.util.logging.LogFormat;
|
||||
|
||||
/**
|
||||
* Handles the registration and invocation of commands.
|
||||
*
|
||||
* <p>This class is primarily for internal usage.</p>
|
||||
*/
|
||||
public final class CommandManager {
|
||||
|
||||
public static final Pattern COMMAND_CLEAN_PATTERN = Pattern.compile("^[/]+");
|
||||
private static final Logger log = Logger.getLogger(CommandManager.class.getCanonicalName());
|
||||
private static final Logger commandLog = Logger.getLogger(CommandManager.class.getCanonicalName() + ".CommandLog");
|
||||
private static final Pattern numberFormatExceptionPattern = Pattern.compile("^For input string: \"(.*)\"$");
|
||||
|
||||
private final WorldEdit worldEdit;
|
||||
private final PlatformManager platformManager;
|
||||
private final Dispatcher dispatcher;
|
||||
private final DynamicStreamHandler dynamicHandler = new DynamicStreamHandler();
|
||||
private final ExceptionConverter exceptionConverter;
|
||||
|
||||
/**
|
||||
* Create a new instance.
|
||||
*
|
||||
* @param worldEdit the WorldEdit instance
|
||||
*/
|
||||
public CommandManager(final WorldEdit worldEdit, PlatformManager platformManager) {
|
||||
checkNotNull(worldEdit);
|
||||
checkNotNull(platformManager);
|
||||
this.worldEdit = worldEdit;
|
||||
this.platformManager = platformManager;
|
||||
this.exceptionConverter = new WorldEditExceptionConverter(worldEdit);
|
||||
|
||||
// Register this instance for command events
|
||||
worldEdit.getEventBus().register(this);
|
||||
|
||||
// Setup the logger
|
||||
commandLog.addHandler(dynamicHandler);
|
||||
dynamicHandler.setFormatter(new LogFormat());
|
||||
|
||||
// Set up the commands manager
|
||||
ParametricBuilder builder = new ParametricBuilder();
|
||||
builder.setAuthorizer(new ActorAuthorizer());
|
||||
builder.setDefaultCompleter(new UserCommandCompleter(platformManager));
|
||||
builder.addBinding(new WorldEditBinding(worldEdit));
|
||||
builder.addExceptionConverter(exceptionConverter);
|
||||
builder.addInvokeListener(new LegacyCommandsHandler());
|
||||
builder.addInvokeListener(new CommandLoggingHandler(worldEdit, commandLog));
|
||||
|
||||
dispatcher = new CommandGraph().builder(builder).commands().registerMethods(new BiomeCommands(worldEdit)).registerMethods(new ChunkCommands(worldEdit))
|
||||
.registerMethods(new ClipboardCommands(worldEdit)).registerMethods(new GeneralCommands(worldEdit)).registerMethods(new GenerationCommands(worldEdit))
|
||||
.registerMethods(new HistoryCommands(worldEdit)).registerMethods(new NavigationCommands(worldEdit)).registerMethods(new RegionCommands(worldEdit))
|
||||
.registerMethods(new ScriptingCommands(worldEdit)).registerMethods(new SelectionCommands(worldEdit)).registerMethods(new SnapshotUtilCommands(worldEdit))
|
||||
.registerMethods(new ToolUtilCommands(worldEdit)).registerMethods(new ToolCommands(worldEdit)).registerMethods(new UtilityCommands(worldEdit))
|
||||
.register(adapt(new SelectionCommand(new ApplyCommand(new ReplaceParser(), "Set all blocks within selection"), "worldedit.region.set")), "/set").group("worldedit", "we")
|
||||
.describeAs("WorldEdit commands").registerMethods(new WorldEditCommands(worldEdit)).parent().group("schematic", "schem", "/schematic", "/schem")
|
||||
.describeAs("Schematic commands for saving/loading areas").registerMethods(new SchematicCommands(worldEdit)).parent().group("snapshot", "snap")
|
||||
.describeAs("Schematic commands for saving/loading areas").registerMethods(new SnapshotCommands(worldEdit)).parent().group("brush", "br").describeAs("Brushing commands")
|
||||
.registerMethods(new BrushCommands(worldEdit)).register(adapt(new ShapedBrushCommand(new DeformCommand(), "worldedit.brush.deform")), "deform")
|
||||
.register(adapt(new ShapedBrushCommand(new ApplyCommand(new ReplaceParser(), "Set all blocks within region"), "worldedit.brush.set")), "set")
|
||||
.register(adapt(new ShapedBrushCommand(new PaintCommand(), "worldedit.brush.paint")), "paint").register(adapt(new ShapedBrushCommand(new ApplyCommand(), "worldedit.brush.apply")), "apply")
|
||||
.register(adapt(new ShapedBrushCommand(new PaintCommand(new TreeGeneratorParser("treeType")), "worldedit.brush.forest")), "forest")
|
||||
.register(adapt(new ShapedBrushCommand(ProvidedValue.create(new Deform("y-=1", Mode.RAW_COORD), "Raise one block"), "worldedit.brush.raise")), "raise")
|
||||
.register(adapt(new ShapedBrushCommand(ProvidedValue.create(new Deform("y+=1", Mode.RAW_COORD), "Lower one block"), "worldedit.brush.lower")), "lower").parent()
|
||||
.group("superpickaxe", "pickaxe", "sp").describeAs("Super-pickaxe commands").registerMethods(new SuperPickaxeCommands(worldEdit)).parent().group("tool")
|
||||
.describeAs("Bind functions to held items").registerMethods(new ToolCommands(worldEdit)).parent().graph().getDispatcher();
|
||||
}
|
||||
|
||||
public ExceptionConverter getExceptionConverter() {
|
||||
return exceptionConverter;
|
||||
}
|
||||
|
||||
public void register(Platform platform) {
|
||||
log.log(Level.FINE, "Registering commands with " + platform.getClass().getCanonicalName());
|
||||
|
||||
LocalConfiguration config = platform.getConfiguration();
|
||||
boolean logging = config.logCommands;
|
||||
String path = config.logFile;
|
||||
|
||||
// Register log
|
||||
if (!logging || path.isEmpty()) {
|
||||
dynamicHandler.setHandler(null);
|
||||
commandLog.setLevel(Level.OFF);
|
||||
} else {
|
||||
File file = new File(config.getWorkingDirectory(), path);
|
||||
commandLog.setLevel(Level.ALL);
|
||||
|
||||
log.log(Level.INFO, "Logging WorldEdit commands to " + file.getAbsolutePath());
|
||||
|
||||
try {
|
||||
dynamicHandler.setHandler(new FileHandler(file.getAbsolutePath(), true));
|
||||
} catch (IOException e) {
|
||||
log.log(Level.WARNING, "Could not use command log file " + path + ": " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
platform.registerCommands(dispatcher);
|
||||
}
|
||||
|
||||
public void unregister() {
|
||||
dynamicHandler.setHandler(null);
|
||||
}
|
||||
|
||||
public String[] commandDetection(String[] split) {
|
||||
// Quick script shortcut
|
||||
if (split[0].matches("^[^/].*\\.js$")) {
|
||||
String[] newSplit = new String[split.length + 1];
|
||||
System.arraycopy(split, 0, newSplit, 1, split.length);
|
||||
newSplit[0] = "cs";
|
||||
newSplit[1] = newSplit[1];
|
||||
split = newSplit;
|
||||
}
|
||||
|
||||
String searchCmd = split[0].toLowerCase();
|
||||
|
||||
// Try to detect the command
|
||||
if (!dispatcher.contains(searchCmd)) {
|
||||
if (worldEdit.getConfiguration().noDoubleSlash && dispatcher.contains("/" + searchCmd)) {
|
||||
split[0] = "/" + split[0];
|
||||
} else if (searchCmd.length() >= 2 && searchCmd.charAt(0) == '/' && dispatcher.contains(searchCmd.substring(1))) {
|
||||
split[0] = split[0].substring(1);
|
||||
}
|
||||
}
|
||||
|
||||
return split;
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void handleCommand(final CommandEvent event) {
|
||||
Request.reset();
|
||||
TaskManager.IMP.async(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Actor actor = platformManager.createProxyActor(event.getActor());
|
||||
String[] split = commandDetection(event.getArguments().split(" "));
|
||||
|
||||
// No command found!
|
||||
if (!dispatcher.contains(split[0])) {
|
||||
return;
|
||||
}
|
||||
|
||||
LocalSession session = worldEdit.getSessionManager().get(actor);
|
||||
LocalConfiguration config = worldEdit.getConfiguration();
|
||||
|
||||
CommandLocals locals = new CommandLocals();
|
||||
locals.put(Actor.class, actor);
|
||||
locals.put("arguments", event.getArguments());
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
try {
|
||||
dispatcher.call(Joiner.on(" ").join(split), locals, new String[0]);
|
||||
} catch (CommandPermissionsException e) {
|
||||
actor.printError("You are not permitted to do that. Are you in the right mode?");
|
||||
} catch (InvalidUsageException e) {
|
||||
if (e.isFullHelpSuggested()) {
|
||||
actor.printRaw(ColorCodeBuilder.asColorCodes(new CommandUsageBox(e.getCommand(), e.getCommandUsed("/", ""), locals)));
|
||||
String message = e.getMessage();
|
||||
if (message != null) {
|
||||
actor.printError(message);
|
||||
}
|
||||
} else {
|
||||
String message = e.getMessage();
|
||||
actor.printError(message != null ? message : "The command was not used properly (no more help available).");
|
||||
actor.printError("Usage: " + e.getSimpleUsageString("/"));
|
||||
}
|
||||
} catch (WrappedCommandException e) {
|
||||
Throwable t = e.getCause();
|
||||
actor.printError("Please report this error: [See console]");
|
||||
actor.printRaw(t.getClass().getName() + ": " + t.getMessage());
|
||||
log.log(Level.SEVERE, "An unexpected error while handling a WorldEdit command", t);
|
||||
} catch (CommandException e) {
|
||||
String message = e.getMessage();
|
||||
if (message != null) {
|
||||
actor.printError(e.getMessage());
|
||||
} else {
|
||||
actor.printError("An unknown error has occurred! Please see console.");
|
||||
log.log(Level.SEVERE, "An unknown error occurred", e);
|
||||
}
|
||||
} finally {
|
||||
EditSession editSession = locals.get(EditSession.class);
|
||||
|
||||
if (editSession != null) {
|
||||
session.remember(editSession);
|
||||
editSession.flushQueue();
|
||||
|
||||
if (config.profile) {
|
||||
long time = System.currentTimeMillis() - start;
|
||||
int changed = editSession.getBlockChangeCount();
|
||||
if (time > 0) {
|
||||
double throughput = changed / (time / 1000.0);
|
||||
actor.printDebug((time / 1000.0) + "s elapsed (history: " + changed + " changed; " + Math.round(throughput) + " blocks/sec).");
|
||||
} else {
|
||||
actor.printDebug((time / 1000.0) + "s elapsed.");
|
||||
}
|
||||
}
|
||||
|
||||
worldEdit.flushBlockBag(actor, editSession);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
event.setCancelled(true);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void handleCommandSuggestion(CommandSuggestionEvent event) {
|
||||
try {
|
||||
CommandLocals locals = new CommandLocals();
|
||||
locals.put(Actor.class, event.getActor());
|
||||
locals.put("arguments", event.getArguments());
|
||||
event.setSuggestions(dispatcher.getSuggestions(event.getArguments(), locals));
|
||||
} catch (CommandException e) {
|
||||
event.getActor().printError(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the command dispatcher instance.
|
||||
*
|
||||
* @return the command dispatcher
|
||||
*/
|
||||
public Dispatcher getDispatcher() {
|
||||
return dispatcher;
|
||||
}
|
||||
|
||||
public static Logger getLogger() {
|
||||
return commandLog;
|
||||
}
|
||||
|
||||
public static Class<?> inject() {
|
||||
return CommandManager.class;
|
||||
}
|
||||
}
|
@ -37,7 +37,7 @@ import com.sk89q.worldedit.function.operation.RunContext;
|
||||
public class EntityVisitor implements Operation {
|
||||
|
||||
private final EntityFunction function;
|
||||
private final int affected = 0;
|
||||
private int affected = 0;
|
||||
private final Iterator<? extends Entity> iterator;
|
||||
|
||||
/**
|
||||
@ -66,7 +66,9 @@ public class EntityVisitor implements Operation {
|
||||
@Override
|
||||
public Operation resume(final RunContext run) throws WorldEditException {
|
||||
while (this.iterator.hasNext()) {
|
||||
this.function.apply(this.iterator.next());
|
||||
if (this.function.apply(this.iterator.next())) {
|
||||
affected++;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ import com.sk89q.worldedit.regions.FlatRegion;
|
||||
public class FlatRegionVisitor implements Operation {
|
||||
|
||||
private final FlatRegionFunction function;
|
||||
private final int affected = 0;
|
||||
private int affected = 0;
|
||||
private final Iterable<Vector2D> iterator;
|
||||
|
||||
/**
|
||||
@ -65,7 +65,9 @@ public class FlatRegionVisitor implements Operation {
|
||||
@Override
|
||||
public Operation resume(final RunContext run) throws WorldEditException {
|
||||
for (final Vector2D pt : this.iterator) {
|
||||
this.function.apply(pt);
|
||||
if (this.function.apply(pt)) {
|
||||
affected++;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ import com.sk89q.worldedit.regions.Region;
|
||||
public class RegionVisitor implements Operation {
|
||||
|
||||
private final RegionFunction function;
|
||||
private final int affected = 0;
|
||||
private int affected = 0;
|
||||
|
||||
private final Iterator<BlockVector> iterator;
|
||||
|
||||
@ -58,7 +58,9 @@ public class RegionVisitor implements Operation {
|
||||
@Override
|
||||
public Operation resume(final RunContext run) throws WorldEditException {
|
||||
while (this.iterator.hasNext()) {
|
||||
this.function.apply(this.iterator.next());
|
||||
if (this.function.apply(this.iterator.next())) {
|
||||
affected++;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user