diff --git a/src/main/java/com/boydti/fawe/Fawe.java b/src/main/java/com/boydti/fawe/Fawe.java index baf671db..2c9b0d3b 100644 --- a/src/main/java/com/boydti/fawe/Fawe.java +++ b/src/main/java/com/boydti/fawe/Fawe.java @@ -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() { diff --git a/src/main/java/com/boydti/fawe/FaweCache.java b/src/main/java/com/boydti/fawe/FaweCache.java index c2de324b..be042e21 100644 --- a/src/main/java/com/boydti/fawe/FaweCache.java +++ b/src/main/java/com/boydti/fawe/FaweCache.java @@ -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; + } + } } diff --git a/src/main/java/com/boydti/fawe/config/Settings.java b/src/main/java/com/boydti/fawe/config/Settings.java index c97354be..d3532002 100644 --- a/src/main/java/com/boydti/fawe/config/Settings.java +++ b/src/main/java/com/boydti/fawe/config/Settings.java @@ -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 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 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); diff --git a/src/main/java/com/boydti/fawe/object/NullChangeSet.java b/src/main/java/com/boydti/fawe/object/NullChangeSet.java new file mode 100644 index 00000000..b63b6fe9 --- /dev/null +++ b/src/main/java/com/boydti/fawe/object/NullChangeSet.java @@ -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 backwardIterator() { + return new ArrayList().iterator(); + } + + @Override + public Iterator forwardIterator() { + return new ArrayList().iterator(); + } + + @Override + public int size() { + return 0; + } + +} diff --git a/src/main/java/com/boydti/fawe/object/changeset/CPUOptimizedHistory.java b/src/main/java/com/boydti/fawe/object/changeset/CPUOptimizedHistory.java new file mode 100644 index 00000000..09be8f8f --- /dev/null +++ b/src/main/java/com/boydti/fawe/object/changeset/CPUOptimizedHistory.java @@ -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 { + +} diff --git a/src/main/java/com/boydti/fawe/object/changeset/DiskStorageHistory.java b/src/main/java/com/boydti/fawe/object/changeset/DiskStorageHistory.java new file mode 100644 index 00000000..8443f82a --- /dev/null +++ b/src/main/java/com/boydti/fawe/object/changeset/DiskStorageHistory.java @@ -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 getIterator(final boolean dir) { + flush(); + try { + if (bdFile.exists()) { + final GZIPInputStream gis = new GZIPInputStream(new FileInputStream(bdFile)); + gis.skip(4); + return new Iterator() { + + 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().iterator(); + } + + @Override + public Iterator backwardIterator() { + return getIterator(false); + } + + @Override + public Iterator forwardIterator() { + return getIterator(false); + } + + @Override + public int size() { + flush(); + return size.get(); + } + +} diff --git a/src/main/java/com/boydti/fawe/object/changeset/MemoryOptimizedHistory.java b/src/main/java/com/boydti/fawe/object/changeset/MemoryOptimizedHistory.java new file mode 100644 index 00000000..39a5231b --- /dev/null +++ b/src/main/java/com/boydti/fawe/object/changeset/MemoryOptimizedHistory.java @@ -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 backwardIterator() { + // TODO Auto-generated method stub + return null; + } + + @Override + public Iterator forwardIterator() { + // TODO Auto-generated method stub + return null; + } + + @Override + public int size() { + // TODO Auto-generated method stub + return 0; + } + +} diff --git a/src/main/java/com/boydti/fawe/object/FastWorldEditExtent.java b/src/main/java/com/boydti/fawe/object/extent/FastWorldEditExtent.java similarity index 99% rename from src/main/java/com/boydti/fawe/object/FastWorldEditExtent.java rename to src/main/java/com/boydti/fawe/object/extent/FastWorldEditExtent.java index fc4811a0..2f09c37b 100644 --- a/src/main/java/com/boydti/fawe/object/FastWorldEditExtent.java +++ b/src/main/java/com/boydti/fawe/object/extent/FastWorldEditExtent.java @@ -1,4 +1,4 @@ -package com.boydti.fawe.object; +package com.boydti.fawe.object.extent; import java.util.List; diff --git a/src/main/java/com/boydti/fawe/object/NullExtent.java b/src/main/java/com/boydti/fawe/object/extent/NullExtent.java similarity index 97% rename from src/main/java/com/boydti/fawe/object/NullExtent.java rename to src/main/java/com/boydti/fawe/object/extent/NullExtent.java index 86f31cd1..8f2eb050 100644 --- a/src/main/java/com/boydti/fawe/object/NullExtent.java +++ b/src/main/java/com/boydti/fawe/object/extent/NullExtent.java @@ -1,4 +1,4 @@ -package com.boydti.fawe.object; +package com.boydti.fawe.object.extent; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/com/boydti/fawe/object/ProcessedWEExtent.java b/src/main/java/com/boydti/fawe/object/extent/ProcessedWEExtent.java similarity index 98% rename from src/main/java/com/boydti/fawe/object/ProcessedWEExtent.java rename to src/main/java/com/boydti/fawe/object/extent/ProcessedWEExtent.java index 150b3f0d..f5764642 100644 --- a/src/main/java/com/boydti/fawe/object/ProcessedWEExtent.java +++ b/src/main/java/com/boydti/fawe/object/extent/ProcessedWEExtent.java @@ -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; diff --git a/src/main/java/com/boydti/fawe/util/SafeExtentWrapper.java b/src/main/java/com/boydti/fawe/object/extent/SafeExtentWrapper.java similarity index 88% rename from src/main/java/com/boydti/fawe/util/SafeExtentWrapper.java rename to src/main/java/com/boydti/fawe/object/extent/SafeExtentWrapper.java index f3240c73..9e413f69 100644 --- a/src/main/java/com/boydti/fawe/util/SafeExtentWrapper.java +++ b/src/main/java/com/boydti/fawe/object/extent/SafeExtentWrapper.java @@ -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; diff --git a/src/main/java/com/boydti/fawe/util/FileUtil.java b/src/main/java/com/boydti/fawe/util/FileUtil.java new file mode 100644 index 00000000..96a6edf4 --- /dev/null +++ b/src/main/java/com/boydti/fawe/util/FileUtil.java @@ -0,0 +1,5 @@ +package com.boydti.fawe.util; + +public class FileUtil { + +} diff --git a/src/main/java/com/boydti/fawe/util/WEManager.java b/src/main/java/com/boydti/fawe/util/WEManager.java index 6efd7461..74ffa576 100644 --- a/src/main/java/com/boydti/fawe/util/WEManager.java +++ b/src/main/java/com/boydti/fawe/util/WEManager.java @@ -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; diff --git a/src/main/java/com/sk89q/worldedit/EditSession.java b/src/main/java/com/sk89q/worldedit/EditSession.java index 4afa48ee..4312d966 100644 --- a/src/main/java/com/sk89q/worldedit/EditSession.java +++ b/src/main/java/com/sk89q/worldedit/EditSession.java @@ -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) { diff --git a/src/main/java/com/sk89q/worldedit/command/DispatcherWrapper.java b/src/main/java/com/sk89q/worldedit/command/DispatcherWrapper.java deleted file mode 100644 index daac9f18..00000000 --- a/src/main/java/com/sk89q/worldedit/command/DispatcherWrapper.java +++ /dev/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 getCommands() { - return this.parent.getCommands(); - } - - @Override - public Collection getPrimaryAliases() { - return this.parent.getPrimaryAliases(); - } - - @Override - public Collection 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 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 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(); - } - } - }); - } - -} diff --git a/src/main/java/com/sk89q/worldedit/extension/platform/CommandManager.java b/src/main/java/com/sk89q/worldedit/extension/platform/CommandManager.java new file mode 100644 index 00000000..87d8d538 --- /dev/null +++ b/src/main/java/com/sk89q/worldedit/extension/platform/CommandManager.java @@ -0,0 +1,321 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * 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 . + */ + +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. + * + *

This class is primarily for internal usage.

+ */ +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; + } +} \ No newline at end of file diff --git a/src/main/java/com/sk89q/worldedit/function/visitor/EntityVisitor.java b/src/main/java/com/sk89q/worldedit/function/visitor/EntityVisitor.java index 23a70456..9adc74d7 100644 --- a/src/main/java/com/sk89q/worldedit/function/visitor/EntityVisitor.java +++ b/src/main/java/com/sk89q/worldedit/function/visitor/EntityVisitor.java @@ -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 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; } diff --git a/src/main/java/com/sk89q/worldedit/function/visitor/FlatRegionVisitor.java b/src/main/java/com/sk89q/worldedit/function/visitor/FlatRegionVisitor.java index 5c2cb47e..544d771e 100644 --- a/src/main/java/com/sk89q/worldedit/function/visitor/FlatRegionVisitor.java +++ b/src/main/java/com/sk89q/worldedit/function/visitor/FlatRegionVisitor.java @@ -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 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; } diff --git a/src/main/java/com/sk89q/worldedit/function/visitor/RegionVisitor.java b/src/main/java/com/sk89q/worldedit/function/visitor/RegionVisitor.java index 1cd4e4dd..2f6a523d 100644 --- a/src/main/java/com/sk89q/worldedit/function/visitor/RegionVisitor.java +++ b/src/main/java/com/sk89q/worldedit/function/visitor/RegionVisitor.java @@ -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 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; }