Started work on disk storage for history

This commit is contained in:
Jesse Boyd 2016-04-01 22:35:17 +11:00
parent d9d806ac4a
commit 3cf106842d
19 changed files with 814 additions and 128 deletions

View File

@ -28,6 +28,7 @@ import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.command.SchematicCommands; import com.sk89q.worldedit.command.SchematicCommands;
import com.sk89q.worldedit.command.ScriptingCommands; 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.operation.Operations;
import com.sk89q.worldedit.function.visitor.BreadthFirstSearch; import com.sk89q.worldedit.function.visitor.BreadthFirstSearch;
import com.sk89q.worldedit.function.visitor.DownwardVisitor; import com.sk89q.worldedit.function.visitor.DownwardVisitor;
@ -212,6 +213,8 @@ public class Fawe {
NonRisingVisitor.inject(); NonRisingVisitor.inject();
RecursiveVisitor.inject(); RecursiveVisitor.inject();
RegionVisitor.inject(); RegionVisitor.inject();
CommandManager.inject();
// DispatcherWrapper.inject();
} }
private void setupMemoryListener() { private void setupMemoryListener() {

View File

@ -37,4 +37,136 @@ public class FaweCache {
CACHE_DATA[i] = (byte) k; 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;
}
}
} }

View File

@ -10,6 +10,8 @@ import java.util.Map.Entry;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.YamlConfiguration;
import com.sk89q.worldedit.LocalSession;
public class Settings { public class Settings {
public static int MAX_BLOCKSTATES = 1337; 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 List<String> WE_BLACKLIST = Arrays.asList("cs", ".s", "restore", "snapshot", "delchunks", "listchunks");
public static long MEM_FREE = 95; public static long MEM_FREE = 95;
public static boolean ENABLE_HARD_LIMIT = true; public static boolean ENABLE_HARD_LIMIT = true;
public static boolean STORE_HISTORY_ON_DISK = true;
public static void setup(final File file) { public static void setup(final File file) {
if (!file.exists()) { if (!file.exists()) {
@ -45,6 +48,7 @@ public class Settings {
options.put("max-memory-percent", MEM_FREE); options.put("max-memory-percent", MEM_FREE);
options.put("crash-mitigation", ENABLE_HARD_LIMIT); options.put("crash-mitigation", ENABLE_HARD_LIMIT);
options.put("fix-all-lighting", FIX_ALL_LIGHTING); 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()) { for (final Entry<String, Object> node : options.entrySet()) {
if (!config.contains(node.getKey())) { if (!config.contains(node.getKey())) {
@ -61,6 +65,9 @@ public class Settings {
REQUIRE_SELECTION = config.getBoolean("require-selection-in-mask"); REQUIRE_SELECTION = config.getBoolean("require-selection-in-mask");
WE_BLACKLIST = config.getStringList("command-blacklist"); WE_BLACKLIST = config.getStringList("command-blacklist");
ENABLE_HARD_LIMIT = config.getBoolean("crash-mitigation"); 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 { try {
config.save(file); config.save(file);

View 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;
}
}

View File

@ -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 {
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -1,4 +1,4 @@
package com.boydti.fawe.object; package com.boydti.fawe.object.extent;
import java.util.List; import java.util.List;

View File

@ -1,4 +1,4 @@
package com.boydti.fawe.object; package com.boydti.fawe.object.extent;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;

View File

@ -1,10 +1,12 @@
package com.boydti.fawe.object; package com.boydti.fawe.object.extent;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import com.boydti.fawe.config.BBC; import com.boydti.fawe.config.BBC;
import com.boydti.fawe.config.Settings; 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.MainUtil;
import com.boydti.fawe.util.SetQueue; import com.boydti.fawe.util.SetQueue;
import com.boydti.fawe.util.TaskManager; import com.boydti.fawe.util.TaskManager;

View File

@ -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.config.BBC;
import com.boydti.fawe.object.FawePlayer; 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.Vector;
import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.blocks.BaseBlock; import com.sk89q.worldedit.blocks.BaseBlock;

View File

@ -0,0 +1,5 @@
package com.boydti.fawe.util;
public class FileUtil {
}

View File

@ -7,8 +7,8 @@ import java.util.HashSet;
import com.boydti.fawe.bukkit.regions.FaweMask; import com.boydti.fawe.bukkit.regions.FaweMask;
import com.boydti.fawe.config.BBC; import com.boydti.fawe.config.BBC;
import com.boydti.fawe.object.FawePlayer; import com.boydti.fawe.object.FawePlayer;
import com.boydti.fawe.object.NullExtent;
import com.boydti.fawe.object.RegionWrapper; import com.boydti.fawe.object.RegionWrapper;
import com.boydti.fawe.object.extent.NullExtent;
import com.boydti.fawe.regions.FaweMaskManager; import com.boydti.fawe.regions.FaweMaskManager;
import com.sk89q.worldedit.extent.AbstractDelegateExtent; import com.sk89q.worldedit.extent.AbstractDelegateExtent;
import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.extent.Extent;

View File

@ -42,15 +42,16 @@ import com.boydti.fawe.Fawe;
import com.boydti.fawe.FaweCache; import com.boydti.fawe.FaweCache;
import com.boydti.fawe.config.BBC; import com.boydti.fawe.config.BBC;
import com.boydti.fawe.object.EditSessionWrapper; import com.boydti.fawe.object.EditSessionWrapper;
import com.boydti.fawe.object.FastWorldEditExtent;
import com.boydti.fawe.object.FawePlayer; 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.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.ExtentWrapper;
import com.boydti.fawe.util.MemUtil; import com.boydti.fawe.util.MemUtil;
import com.boydti.fawe.util.Perm; import com.boydti.fawe.util.Perm;
import com.boydti.fawe.util.SafeExtentWrapper;
import com.boydti.fawe.util.TaskManager; import com.boydti.fawe.util.TaskManager;
import com.boydti.fawe.util.WEManager; import com.boydti.fawe.util.WEManager;
import com.sk89q.worldedit.blocks.BaseBlock; 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.function.visitor.RegionVisitor;
import com.sk89q.worldedit.history.UndoContext; import com.sk89q.worldedit.history.UndoContext;
import com.sk89q.worldedit.history.change.BlockChange; 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.history.changeset.ChangeSet;
import com.sk89q.worldedit.internal.expression.Expression; import com.sk89q.worldedit.internal.expression.Expression;
import com.sk89q.worldedit.internal.expression.ExpressionException; import com.sk89q.worldedit.internal.expression.ExpressionException;
@ -149,7 +149,7 @@ public class EditSession implements Extent {
} }
protected final World world; protected final World world;
private final ChangeSet changeSet = new BlockOptimizedHistory(); private final ChangeSet changeSet;
private final EditSessionWrapper wrapper; private final EditSessionWrapper wrapper;
private MultiStageReorder reorderExtent; private MultiStageReorder reorderExtent;
private @Nullable Extent changeSetExtent; private @Nullable Extent changeSetExtent;
@ -214,6 +214,8 @@ public class EditSession implements Extent {
this.thread = Fawe.get().getMainThread(); this.thread = Fawe.get().getMainThread();
this.world = world; this.world = world;
this.wrapper = Fawe.imp().getEditSessionWrapper(this); this.wrapper = Fawe.imp().getEditSessionWrapper(this);
// this.changeSet = new BlockOptimizedHistory();
this.changeSet = new DiskStorageHistory();
// Invalid; return null extent // Invalid; return null extent
if (world == null) { if (world == null) {

View File

@ -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();
}
}
});
}
}

View File

@ -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;
}
}

View File

@ -37,7 +37,7 @@ import com.sk89q.worldedit.function.operation.RunContext;
public class EntityVisitor implements Operation { public class EntityVisitor implements Operation {
private final EntityFunction function; private final EntityFunction function;
private final int affected = 0; private int affected = 0;
private final Iterator<? extends Entity> iterator; private final Iterator<? extends Entity> iterator;
/** /**
@ -66,7 +66,9 @@ public class EntityVisitor implements Operation {
@Override @Override
public Operation resume(final RunContext run) throws WorldEditException { public Operation resume(final RunContext run) throws WorldEditException {
while (this.iterator.hasNext()) { while (this.iterator.hasNext()) {
this.function.apply(this.iterator.next()); if (this.function.apply(this.iterator.next())) {
affected++;
}
} }
return null; return null;
} }

View File

@ -37,7 +37,7 @@ import com.sk89q.worldedit.regions.FlatRegion;
public class FlatRegionVisitor implements Operation { public class FlatRegionVisitor implements Operation {
private final FlatRegionFunction function; private final FlatRegionFunction function;
private final int affected = 0; private int affected = 0;
private final Iterable<Vector2D> iterator; private final Iterable<Vector2D> iterator;
/** /**
@ -65,7 +65,9 @@ public class FlatRegionVisitor implements Operation {
@Override @Override
public Operation resume(final RunContext run) throws WorldEditException { public Operation resume(final RunContext run) throws WorldEditException {
for (final Vector2D pt : this.iterator) { for (final Vector2D pt : this.iterator) {
this.function.apply(pt); if (this.function.apply(pt)) {
affected++;
}
} }
return null; return null;
} }

View File

@ -36,7 +36,7 @@ import com.sk89q.worldedit.regions.Region;
public class RegionVisitor implements Operation { public class RegionVisitor implements Operation {
private final RegionFunction function; private final RegionFunction function;
private final int affected = 0; private int affected = 0;
private final Iterator<BlockVector> iterator; private final Iterator<BlockVector> iterator;
@ -58,7 +58,9 @@ public class RegionVisitor implements Operation {
@Override @Override
public Operation resume(final RunContext run) throws WorldEditException { public Operation resume(final RunContext run) throws WorldEditException {
while (this.iterator.hasNext()) { while (this.iterator.hasNext()) {
this.function.apply(this.iterator.next()); if (this.function.apply(this.iterator.next())) {
affected++;
}
} }
return null; return null;
} }