API and brush improvements.

This commit is contained in:
Jesse Boyd 2016-04-23 02:11:46 +10:00
parent 70362d348f
commit 14dd048662
14 changed files with 1095 additions and 166 deletions

View File

@ -23,11 +23,14 @@ import com.boydti.fawe.object.FaweCommand;
import com.boydti.fawe.object.FawePlayer; import com.boydti.fawe.object.FawePlayer;
import com.boydti.fawe.regions.FaweMaskManager; import com.boydti.fawe.regions.FaweMaskManager;
import com.boydti.fawe.util.FaweQueue; import com.boydti.fawe.util.FaweQueue;
import com.boydti.fawe.util.ReflectionUtils;
import com.boydti.fawe.util.StringMan; import com.boydti.fawe.util.StringMan;
import com.boydti.fawe.util.TaskManager; import com.boydti.fawe.util.TaskManager;
import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.bukkit.WorldEditPlugin; import com.sk89q.worldedit.bukkit.WorldEditPlugin;
import java.io.File; import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
@ -64,6 +67,14 @@ public class FaweBukkit extends JavaPlugin implements IFawe, Listener {
try { try {
Bukkit.getPluginManager().registerEvents(this, this); Bukkit.getPluginManager().registerEvents(this, this);
Fawe.set(this); Fawe.set(this);
if (Bukkit.getVersion().contains("git-Spigot") && FaweAPI.checkVersion(this.getVersion(), 1, 7, 10)) {
debug("====== USE PAPER SPIGOT ======");
debug("DOWNLOAD: https://tcpr.ca/downloads/paperspigot");
debug("GUIDE: https://www.spigotmc.org/threads/21726/");
debug(" - This is only a recommendation");
debug("==============================");
}
} catch (final Throwable e) { } catch (final Throwable e) {
e.printStackTrace(); e.printStackTrace();
this.getServer().shutdown(); this.getServer().shutdown();
@ -204,6 +215,19 @@ public class FaweBukkit extends JavaPlugin implements IFawe, Listener {
return new BukkitQueue_1_8(world); return new BukkitQueue_1_8(world);
} catch (Throwable ignore) {} } catch (Throwable ignore) {}
if (hasNMS) { if (hasNMS) {
try {
ReflectionUtils.init();
Field fieldDirtyCount = ReflectionUtils.getRefClass("{nms}.PlayerChunk").getField("dirtyCount").getRealField();
fieldDirtyCount.setAccessible(true);
int mod = fieldDirtyCount.getModifiers();
if ((mod & Modifier.VOLATILE) == 0) {
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(fieldDirtyCount, mod + Modifier.VOLATILE);
}
} catch (Throwable e) {
e.printStackTrace();
}
debug("====== NO NMS BLOCK PLACER FOUND ======"); debug("====== NO NMS BLOCK PLACER FOUND ======");
debug("FAWE couldn't find a fast block placer"); debug("FAWE couldn't find a fast block placer");
debug("Bukkit version: " + Bukkit.getVersion()); debug("Bukkit version: " + Bukkit.getVersion());

View File

@ -6,6 +6,7 @@ import com.boydti.fawe.bukkit.v1_8.BukkitChunk_1_8;
import com.boydti.fawe.config.Settings; import com.boydti.fawe.config.Settings;
import com.boydti.fawe.object.FaweChunk; import com.boydti.fawe.object.FaweChunk;
import com.boydti.fawe.object.IntegerPair; import com.boydti.fawe.object.IntegerPair;
import com.boydti.fawe.object.RunnableVal;
import com.boydti.fawe.object.exception.FaweException; import com.boydti.fawe.object.exception.FaweException;
import com.boydti.fawe.util.TaskManager; import com.boydti.fawe.util.TaskManager;
import com.sk89q.worldedit.LocalWorld; import com.sk89q.worldedit.LocalWorld;
@ -14,7 +15,6 @@ import com.sk89q.worldedit.bukkit.BukkitUtil;
import com.sk89q.worldedit.world.biome.BaseBiome; import com.sk89q.worldedit.world.biome.BaseBiome;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.concurrent.LinkedBlockingDeque;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Chunk; import org.bukkit.Chunk;
import org.bukkit.World; import org.bukkit.World;
@ -26,27 +26,9 @@ import org.bukkit.plugin.Plugin;
import org.spigotmc.AsyncCatcher; import org.spigotmc.AsyncCatcher;
public class BukkitQueue_All extends BukkitQueue_0 { public class BukkitQueue_All extends BukkitQueue_0 {
public final LinkedBlockingDeque<IntegerPair> loadQueue = new LinkedBlockingDeque<>();
public BukkitQueue_All(final String world) { public BukkitQueue_All(final String world) {
super(world); super(world);
TaskManager.IMP.repeat(new Runnable() {
@Override
public void run() {
synchronized (loadQueue) {
while (loadQueue.size() > 0) {
IntegerPair loc = loadQueue.poll();
if (bukkitWorld == null) {
bukkitWorld = Bukkit.getServer().getWorld(world);
}
if (!bukkitWorld.isChunkLoaded(loc.x, loc.z)) {
bukkitWorld.loadChunk(loc.x, loc.z, true);
}
}
loadQueue.notifyAll();
}
}
}, 1);
if (getClass() == BukkitQueue_All.class) { if (getClass() == BukkitQueue_All.class) {
TaskManager.IMP.task(new Runnable() { TaskManager.IMP.task(new Runnable() {
@Override @Override
@ -213,10 +195,6 @@ public class BukkitQueue_All extends BukkitQueue_0 {
return true; return true;
} }
public void loadChunk(IntegerPair chunk) {
loadQueue.add(chunk);
}
public int lastChunkX = Integer.MIN_VALUE; public int lastChunkX = Integer.MIN_VALUE;
public int lastChunkZ = Integer.MIN_VALUE; public int lastChunkZ = Integer.MIN_VALUE;
public int lastChunkY = Integer.MIN_VALUE; public int lastChunkY = Integer.MIN_VALUE;
@ -241,6 +219,15 @@ public class BukkitQueue_All extends BukkitQueue_0 {
return combined; return combined;
} }
private final RunnableVal<IntegerPair> loadChunk = new RunnableVal<IntegerPair>() {
@Override
public void run(IntegerPair coord) {
bukkitWorld.loadChunk(coord.x, coord.z, true);
}
};
long average = 0;
@Override @Override
public int getCombinedId4Data(int x, int y, int z) throws FaweException.FaweChunkLoadException { public int getCombinedId4Data(int x, int y, int z) throws FaweException.FaweChunkLoadException {
if (y < 0 || y > 255) { if (y < 0 || y > 255) {
@ -256,24 +243,26 @@ public class BukkitQueue_All extends BukkitQueue_0 {
lastChunkX = cx; lastChunkX = cx;
lastChunkZ = cz; lastChunkZ = cz;
if (!bukkitWorld.isChunkLoaded(cx, cz)) { if (!bukkitWorld.isChunkLoaded(cx, cz)) {
long start = System.currentTimeMillis();
boolean sync = Thread.currentThread() == Fawe.get().getMainThread(); boolean sync = Thread.currentThread() == Fawe.get().getMainThread();
if (sync) { if (sync) {
bukkitWorld.loadChunk(cx, cz, true); bukkitWorld.loadChunk(cx, cz, true);
} else if (Settings.CHUNK_WAIT > 0) { } else if (Settings.CHUNK_WAIT > 0) {
synchronized (loadQueue) { loadChunk.value = new IntegerPair(cx, cz);
loadQueue.add(new IntegerPair(cx, cz)); TaskManager.IMP.sync(loadChunk, Settings.CHUNK_WAIT);
try {
loadQueue.wait(Settings.CHUNK_WAIT);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (!bukkitWorld.isChunkLoaded(cx, cz)) { if (!bukkitWorld.isChunkLoaded(cx, cz)) {
throw new FaweException.FaweChunkLoadException(); throw new FaweException.FaweChunkLoadException();
} }
} else { } else {
return 0; return 0;
} }
long diff = System.currentTimeMillis() - start;
if (average == 0) {
average = diff;
} else {
average = ((average * 15) + diff) / 16;
}
System.out.println(average);
} }
lastChunk = getCachedChunk(cx, cz); lastChunk = getCachedChunk(cx, cz);
lastSection = getCachedSection(lastChunk, cy); lastSection = getCachedSection(lastChunk, cy);

View File

@ -24,6 +24,7 @@ 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.extension.platform.CommandManager;
import com.sk89q.worldedit.extension.platform.PlatformManager;
import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard; import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard;
import com.sk89q.worldedit.extent.transform.BlockTransformExtent; import com.sk89q.worldedit.extent.transform.BlockTransformExtent;
import com.sk89q.worldedit.function.operation.Operations; import com.sk89q.worldedit.function.operation.Operations;
@ -240,6 +241,7 @@ public class Fawe {
Vector.inject(); Vector.inject();
try { try {
CommandManager.inject(); CommandManager.inject();
PlatformManager.inject();
} catch (Throwable e) { } catch (Throwable e) {
debug("====== UPDATE WORLDEDIT TO 6.1.1 ======"); debug("====== UPDATE WORLDEDIT TO 6.1.1 ======");
e.printStackTrace(); e.printStackTrace();
@ -255,16 +257,18 @@ public class Fawe {
} }
try { try {
Native.load(); Native.load();
String arch = System.getenv("PROCESSOR_ARCHITECTURE"); try {
String wow64Arch = System.getenv("PROCESSOR_ARCHITEW6432"); String arch = System.getenv("PROCESSOR_ARCHITECTURE");
boolean x86OS = arch.endsWith("64") || wow64Arch != null && wow64Arch.endsWith("64") ? false : true; String wow64Arch = System.getenv("PROCESSOR_ARCHITEW6432");
boolean x86JVM = System.getProperty("sun.arch.data.model").equals("32"); boolean x86OS = arch.endsWith("64") || wow64Arch != null && wow64Arch.endsWith("64") ? false : true;
if (x86OS != x86JVM) { boolean x86JVM = System.getProperty("sun.arch.data.model").equals("32");
debug("====== UPGRADE TO 64-BIT JAVA ======"); if (x86OS != x86JVM) {
debug("You are running 32-bit Java on a 64-bit machine"); debug("====== UPGRADE TO 64-BIT JAVA ======");
debug(" - This is a recommendation"); debug("You are running 32-bit Java on a 64-bit machine");
debug("===================================="); debug(" - This is only a recommendation");
} debug("====================================");
}
} catch (Throwable ignore) {}
} catch (Throwable e) { } catch (Throwable e) {
debug("====== LZ4 COMPRESSION BINDING NOT FOUND ======"); debug("====== LZ4 COMPRESSION BINDING NOT FOUND ======");
e.printStackTrace(); e.printStackTrace();
@ -277,7 +281,7 @@ public class Fawe {
if (getJavaVersion() < 1.8) { if (getJavaVersion() < 1.8) {
debug("====== UPGRADE TO JAVA 8 ======"); debug("====== UPGRADE TO JAVA 8 ======");
debug("You are running " + System.getProperty("java.version")); debug("You are running " + System.getProperty("java.version"));
debug(" - This is a recommendation"); debug(" - This is only a recommendation");
debug("===================================="); debug("====================================");
} }
} }

View File

@ -1,14 +1,27 @@
package com.boydti.fawe; package com.boydti.fawe;
import com.boydti.fawe.config.BBC;
import com.boydti.fawe.config.Settings;
import com.boydti.fawe.object.FaweLocation; import com.boydti.fawe.object.FaweLocation;
import com.boydti.fawe.object.FawePlayer;
import com.boydti.fawe.object.RegionWrapper;
import com.boydti.fawe.object.changeset.DiskStorageHistory;
import com.boydti.fawe.regions.FaweMaskManager;
import com.boydti.fawe.util.FaweQueue; import com.boydti.fawe.util.FaweQueue;
import com.boydti.fawe.util.MemUtil;
import com.boydti.fawe.util.SetQueue; import com.boydti.fawe.util.SetQueue;
import com.boydti.fawe.util.TaskManager; import com.boydti.fawe.util.TaskManager;
import com.boydti.fawe.util.WEManager;
import com.intellectualcrafters.plot.object.PseudoRandom;
import com.sk89q.jnbt.ByteArrayTag; import com.sk89q.jnbt.ByteArrayTag;
import com.sk89q.jnbt.IntTag; import com.sk89q.jnbt.IntTag;
import com.sk89q.jnbt.NBTInputStream; import com.sk89q.jnbt.NBTInputStream;
import com.sk89q.jnbt.ShortTag; import com.sk89q.jnbt.ShortTag;
import com.sk89q.jnbt.Tag; import com.sk89q.jnbt.Tag;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.world.World;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
@ -16,7 +29,15 @@ import java.io.InputStream;
import java.net.URL; import java.net.URL;
import java.nio.channels.Channels; import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel; import java.nio.channels.ReadableByteChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
import org.bukkit.Chunk; import org.bukkit.Chunk;
import org.bukkit.Location; import org.bukkit.Location;
@ -24,11 +45,229 @@ import org.bukkit.Location;
/** /**
* The FaweAPI class offers a few useful functions.<br> * The FaweAPI class offers a few useful functions.<br>
* - This class is not intended to replace the WorldEdit API<br> * - This class is not intended to replace the WorldEdit API<br>
* - With FAWE installed, you can use the EditSession and other WorldEdit classes from an async thread.<br>
* <br> * <br>
* FaweAPI.[some method] * FaweAPI.[some method]
*/ */
public class FaweAPI { public class FaweAPI {
/**
* The TaskManager has some useful methods for doing things asynchronously
* @return TaskManager
*/
public TaskManager getTaskManager() {
return TaskManager.IMP;
}
/**
* Wrap some object into a FawePlayer<br>
* - org.bukkit.entity.Player
* - org.spongepowered.api.entity.living.player
* - com.sk89q.worldedit.entity.Player
* - String (name)
* - UUID (player UUID)
* @param obj
* @return
*/
public FawePlayer wrapPlayer(Object obj) {
return FawePlayer.wrap(obj);
}
/**
* You can either use a FaweQueue or an EditSession to change blocks<br>
* - The FaweQueue skips a bit of overhead so it's faster<br>
* - The WorldEdit EditSession can do a lot more<br>
* Remember to enqueue it when you're done!<br>
* @see com.boydti.fawe.util.FaweQueue#enqueue()
* @param worldName The name of the world
* @param autoqueue If it should start dispatching before you enqueue it.
* @return
*/
public FaweQueue createQueue(String worldName, boolean autoqueue) {
return SetQueue.IMP.getNewQueue(worldName, autoqueue);
}
/**
* Get a list of supported protection plugin masks.
* @return Set of FaweMaskManager
*/
public Set<FaweMaskManager> getMaskManagers() {
return new HashSet<>(WEManager.IMP.managers);
}
/**
* Check if the server has more than the configured low memory threshold
* @return True if the server has limited memory
*/
public boolean isMemoryLimited() {
return MemUtil.isMemoryLimited();
}
/**
* If you just need things to look random, use this faster alternative
* @return PseudoRandom
*/
public PseudoRandom getFastRandom() {
return new PseudoRandom();
}
/**
* Get a player's allowed WorldEdit region
* @param player
* @return
*/
public Set<RegionWrapper> getRegions(FawePlayer player) {
return WEManager.IMP.getMask(player);
}
/**
* Cancel the edit with the following extent<br>
* - The extent must be the one being used by an EditSession, otherwise an error may be thrown <br>
* - Insert an extent into the EditSession using the EditSessionEvent: http://wiki.sk89q.com/wiki/WorldEdit/API/Hooking_EditSession <br>
* @see com.sk89q.worldedit.EditSession#getFaweExtent() To get the FaweExtent for an EditSession
* @param extent
* @param reason
*/
public void cancelEdit(Extent extent, BBC reason) {
try {
WEManager.IMP.cancelEdit(extent, reason);
} catch (WorldEditException ignore) {}
}
/**
* Get the DiskStorageHistory object representing a File
* @param file
* @return
*/
public DiskStorageHistory getChangeSetFromFile(File file) {
if (!file.exists() || file.isDirectory()) {
throw new IllegalArgumentException("Not a file!");
}
if (!file.getName().toLowerCase().endsWith(".bd")) {
throw new IllegalArgumentException("Not a BD file!");
}
if (Settings.STORE_HISTORY_ON_DISK) {
throw new IllegalArgumentException("History on disk not enabled!");
}
String[] path = file.getPath().split(File.separator);
if (path.length < 3) {
throw new IllegalArgumentException("Not in history directory!");
}
String worldName = path[path.length - 3];
String uuidString = path[path.length - 2];
World world = null;
for (World current : WorldEdit.getInstance().getServer().getWorlds()) {
if (current.getName().equals(worldName)) {
world = current;
break;
}
}
if (world == null) {
throw new IllegalArgumentException("Corresponding world does not exist: " + worldName);
}
UUID uuid;
try {
uuid = UUID.fromString(uuidString);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Invalid UUID from file path: " + uuidString);
}
DiskStorageHistory history = new DiskStorageHistory(world, uuid, Integer.parseInt(file.getName().split("\\.")[0]));
return history;
}
/**
* Used in the RollBack to generate a list of DiskStorageHistory objects<br>
* - Note: An edit outside the radius may be included if it overlaps with an edit inside that depends on it.
* @param origin - The origin location
* @param user - The uuid (may be null)
* @param radius - The radius from the origin of the edit
* @param timediff - The max age of the file in milliseconds
* @param shallow - If shallow is true, FAWE will only read the first Settings.BUFFER_SIZE bytes to obtain history info<br>
* Reading only part of the file will result in unreliable bounds info for large edits
* @return
*/
public static List<DiskStorageHistory> getBDFiles(FaweLocation origin, UUID user, int radius, long timediff, boolean shallow) {
File history = new File(Fawe.imp().getDirectory(), "history" + File.separator + origin.world);
if (!history.exists()) {
return new ArrayList<>();
}
long now = System.currentTimeMillis();
ArrayList<File> files = new ArrayList<>();
for (File userFile : history.listFiles()) {
if (!userFile.isDirectory()) {
continue;
}
UUID userUUID;
try {
userUUID = UUID.fromString(userFile.getName());
} catch (IllegalArgumentException e) {
continue;
}
if (user != null && !userUUID.equals(user)) {
continue;
}
ArrayList<Integer> ids = new ArrayList<>();
for (File file : userFile.listFiles()) {
if (file.getName().endsWith(".bd")) {
if (timediff >= Integer.MAX_VALUE || now - file.lastModified() <= timediff) {
files.add(file);
if (files.size() > 2048) {
return null;
}
}
}
}
}
World world = origin.getWorld();
Collections.sort(files, new Comparator<File>() {
@Override
public int compare(File a, File b) {
long value = a.lastModified() - b.lastModified();
return value == 0 ? 0 : value < 0 ? -1 : 1;
}
});
RegionWrapper bounds = new RegionWrapper(origin.x - radius, origin.x + radius, origin.z - radius, origin.z + radius);
RegionWrapper boundsPlus = new RegionWrapper(bounds.minX - 64, bounds.maxX + 512, bounds.minZ - 64, bounds.maxZ + 512);
HashSet<RegionWrapper> regionSet = new HashSet<RegionWrapper>(Arrays.asList(bounds));
ArrayList<DiskStorageHistory> result = new ArrayList<>();
for (File file : files) {
UUID uuid = UUID.fromString(file.getParentFile().getName());
DiskStorageHistory dsh = new DiskStorageHistory(world, uuid, Integer.parseInt(file.getName().split("\\.")[0]));
DiskStorageHistory.DiskStorageSummary summary = dsh.summarize(boundsPlus, shallow);
RegionWrapper region = new RegionWrapper(summary.minX, summary.maxX, summary.minZ, summary.maxZ);
boolean encompassed = false;
boolean isIn = false;
for (RegionWrapper allowed : regionSet) {
isIn = isIn || allowed.intersects(region);
if (encompassed = allowed.isIn(region.minX, region.maxX) && allowed.isIn(region.minZ, region.maxZ)) {
break;
}
}
if (isIn) {
result.add(0, dsh);
if (!encompassed) {
regionSet.add(region);
}
if (shallow && result.size() > 64) {
return result;
}
}
}
return result;
}
/**
* The DiskStorageHistory class is what FAWE uses to represent the undo on disk.
* @see com.boydti.fawe.object.changeset.DiskStorageHistory#toEditSession(com.sk89q.worldedit.entity.Player)
* @param world
* @param uuid
* @param index
* @return
*/
public DiskStorageHistory getChangeSetFromDisk(World world, UUID uuid, int index) {
return new DiskStorageHistory(world, uuid, index);
}
/** /**
* Compare two versions * Compare two versions
* @param version * @param version
@ -41,12 +280,23 @@ public class FaweAPI {
return (version[0] > major) || ((version[0] == major) && (version[1] > minor)) || ((version[0] == major) && (version[1] == minor) && (version[2] >= minor2)); return (version[0] > major) || ((version[0] == major) && (version[1] > minor)) || ((version[0] == major) && (version[1] == minor) && (version[2] >= minor2));
} }
/**
* Fix the lighting in a chunk
* @param world
* @param x
* @param z
* @param fixAll
*/
public static void fixLighting(String world, int x, int z, final boolean fixAll) { public static void fixLighting(String world, int x, int z, final boolean fixAll) {
FaweQueue queue = SetQueue.IMP.getNewQueue(world, false); FaweQueue queue = SetQueue.IMP.getNewQueue(world, false);
queue.fixLighting(queue.getChunk(x, z), fixAll); queue.fixLighting(queue.getChunk(x, z), fixAll);
} }
/**
* Fix the lighting in a chunk
* @param chunk
* @param fixAll
*/
public static void fixLighting(final Chunk chunk, final boolean fixAll) { public static void fixLighting(final Chunk chunk, final boolean fixAll) {
FaweQueue queue = SetQueue.IMP.getNewQueue(chunk.getWorld().getName(), false); FaweQueue queue = SetQueue.IMP.getNewQueue(chunk.getWorld().getName(), false);
queue.fixLighting(queue.getChunk(chunk.getX(), chunk.getZ()), fixAll); queue.fixLighting(queue.getChunk(chunk.getX(), chunk.getZ()), fixAll);
@ -55,59 +305,49 @@ public class FaweAPI {
/** /**
* If a schematic is too large to be pasted normally<br> * If a schematic is too large to be pasted normally<br>
* - Skips any block history * - Skips any block history
* - Ignores some block data * - Ignores nbt
* - No, it's not streaming it from disk, but it is a lot faster * - No, technically I haven't added proper streaming yet (WIP)
* @param file * @param file
* @param loc * @param loc
* @return * @return
*/ */
public static void streamSchematicAsync(final File file, final Location loc) { public static void streamSchematic(final File file, final Location loc) {
final FaweLocation fl = new FaweLocation(loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()); final FaweLocation fl = new FaweLocation(loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ());
streamSchematicAsync(file, fl); streamSchematic(file, fl);
} }
/** /**
* If a schematic is too large to be pasted normally<br> * If a schematic is too large to be pasted normally<br>
* - Skips any block history * - Skips any block history
* - Ignores some block data * - Ignores nbt
* @param file * @param file
* @param loc * @param loc
* @return * @return
*/ */
public static void streamSchematicAsync(final File file, final FaweLocation loc) { public static void streamSchematic(final File file, final FaweLocation loc) {
TaskManager.IMP.async(new Runnable() { try {
@Override final FileInputStream is = new FileInputStream(file);
public void run() { streamSchematic(is, loc);
try { } catch (final IOException e) {
final FileInputStream is = new FileInputStream(file); e.printStackTrace();
streamSchematic(is, loc); }
} catch (final IOException e) {
e.printStackTrace();
}
}
});
} }
/** /**
* If a schematic is too large to be pasted normally<br> * If a schematic is too large to be pasted normally<br>
* - Skips any block history * - Skips any block history
* - Ignores some block data * - Ignores nbt
* @param url * @param url
* @param loc * @param loc
*/ */
public static void streamSchematicAsync(final URL url, final FaweLocation loc) { public static void streamSchematic(final URL url, final FaweLocation loc) {
TaskManager.IMP.async(new Runnable() { try {
@Override final ReadableByteChannel rbc = Channels.newChannel(url.openStream());
public void run() { final InputStream is = Channels.newInputStream(rbc);
try { streamSchematic(is, loc);
final ReadableByteChannel rbc = Channels.newChannel(url.openStream()); } catch (final IOException e) {
final InputStream is = Channels.newInputStream(rbc); e.printStackTrace();
streamSchematic(is, loc); }
} catch (final IOException e) {
e.printStackTrace();
}
}
});
} }
/** /**
@ -241,10 +481,34 @@ public class FaweAPI {
} }
/** /**
* Set a task to run when the async queue is empty * Set a task to run when the global queue (SetQueue class) is empty
* @param whenDone * @param whenDone
*/ */
public static void addTask(final Runnable whenDone) { public static void addTask(final Runnable whenDone) {
SetQueue.IMP.addTask(whenDone); SetQueue.IMP.addTask(whenDone);
} }
/**
* Have a task run when the server is low on memory (configured threshold)
* @param run
*/
public static void addMemoryLimitedTask(Runnable run) {
MemUtil.addMemoryLimitedTask(run);
}
/**
* Have a task run when the server is no longer low on memory (configured threshold)
* @param run
*/
public static void addMemoryPlentifulTask(Runnable run) {
MemUtil.addMemoryPlentifulTask(run);
}
/**
* @see BBC
* @return
*/
public BBC[] getTranslations() {
return BBC.values();
}
} }

View File

@ -15,6 +15,7 @@ import com.sk89q.worldedit.regions.RegionSelector;
import com.sk89q.worldedit.regions.selector.CuboidRegionSelector; import com.sk89q.worldedit.regions.selector.CuboidRegionSelector;
import com.sk89q.worldedit.world.World; import com.sk89q.worldedit.world.World;
import java.io.File; import java.io.File;
import java.lang.reflect.Field;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
@ -32,7 +33,35 @@ public abstract class FawePlayer<T> {
*/ */
private volatile ConcurrentHashMap<String, Object> meta; private volatile ConcurrentHashMap<String, Object> meta;
/**
* Wrap some object into a FawePlayer<br>
* - org.bukkit.entity.Player
* - org.spongepowered.api.entity.living.player
* - com.sk89q.worldedit.entity.Player
* - String (name)
* - UUID (player UUID)
* @param obj
* @param <V>
* @return
*/
public static <V> FawePlayer<V> wrap(final Object obj) { public static <V> FawePlayer<V> wrap(final Object obj) {
if (obj == null) {
return null;
}
if (obj instanceof Player) {
Player actor = (Player) obj;
try {
Field fieldBasePlayer = actor.getClass().getDeclaredField("basePlayer");
fieldBasePlayer.setAccessible(true);
Player player = (Player) fieldBasePlayer.get(actor);
Field fieldPlayer = player.getClass().getDeclaredField("player");
fieldPlayer.setAccessible(true);
return Fawe.imp().wrap(fieldPlayer.get(player));
} catch (Throwable e) {
e.printStackTrace();
return Fawe.imp().wrap(actor.getName());
}
}
return Fawe.imp().wrap(obj); return Fawe.imp().wrap(obj);
} }
@ -49,7 +78,7 @@ public abstract class FawePlayer<T> {
if (world != null) { if (world != null) {
if (world.getName().equals(currentWorldName)) { if (world.getName().equals(currentWorldName)) {
getSession().clearHistory(); getSession().clearHistory();
loadSessionFromDisk(world); loadSessionsFromDisk(world);
} }
} }
} catch (Exception e) { } catch (Exception e) {
@ -58,6 +87,10 @@ public abstract class FawePlayer<T> {
} }
} }
/**
* Get the current World
* @return
*/
public World getWorld() { public World getWorld() {
String currentWorldName = getLocation().world; String currentWorldName = getLocation().world;
for (World world : WorldEdit.getInstance().getServer().getWorlds()) { for (World world : WorldEdit.getInstance().getServer().getWorlds()) {
@ -68,7 +101,12 @@ public abstract class FawePlayer<T> {
return null; return null;
} }
public void loadSessionFromDisk(World world) { /**
* Load all the undo EditSession's from disk for a world <br>
* - Usually already called when a player joins or changes world
* @param world
*/
public void loadSessionsFromDisk(World world) {
if (world == null) { if (world == null) {
return; return;
} }
@ -94,26 +132,68 @@ public abstract class FawePlayer<T> {
} }
} }
/**
* Get the player's limit
* @return
*/
public FaweLimit getLimit() { public FaweLimit getLimit() {
return Settings.getLimit(this); return Settings.getLimit(this);
} }
/**
* Get the player's name
* @return
*/
public abstract String getName(); public abstract String getName();
/**
* Get the player's UUID
* @return
*/
public abstract UUID getUUID(); public abstract UUID getUUID();
/**
* Check the player's permission
* @param perm
* @return
*/
public abstract boolean hasPermission(final String perm); public abstract boolean hasPermission(final String perm);
/**
* Set a permission (requires Vault)
* @param perm
* @param flag
*/
public abstract void setPermission(final String perm, final boolean flag); public abstract void setPermission(final String perm, final boolean flag);
/**
* Send a message to the player
* @param message
*/
public abstract void sendMessage(final String message); public abstract void sendMessage(final String message);
/**
* Have the player execute a command
* @param substring
*/
public abstract void executeCommand(final String substring); public abstract void executeCommand(final String substring);
/**
* Get the player's location
* @return
*/
public abstract FaweLocation getLocation(); public abstract FaweLocation getLocation();
/**
* Get the WorldEdit player object
* @return
*/
public abstract Player getPlayer(); public abstract Player getPlayer();
/**
* Get the player's current selection (or null)
* @return
*/
public Region getSelection() { public Region getSelection() {
try { try {
return this.getSession().getSelection(this.getPlayer().getWorld()); return this.getSession().getSelection(this.getPlayer().getWorld());
@ -122,20 +202,36 @@ public abstract class FawePlayer<T> {
} }
} }
/**
* Get the player's current LocalSession
* @return
*/
public LocalSession getSession() { public LocalSession getSession() {
return (this.session != null || this.getPlayer() == null) ? this.session : (session = Fawe.get().getWorldEdit().getSession(this.getPlayer())); return (this.session != null || this.getPlayer() == null) ? this.session : (session = Fawe.get().getWorldEdit().getSession(this.getPlayer()));
} }
/**
* Get the player's current allowed WorldEdit regions
* @return
*/
public HashSet<RegionWrapper> getCurrentRegions() { public HashSet<RegionWrapper> getCurrentRegions() {
return WEManager.IMP.getMask(this); return WEManager.IMP.getMask(this);
} }
/**
* Set the player's WorldEdit selection to the following CuboidRegion
* @param region
*/
public void setSelection(final RegionWrapper region) { public void setSelection(final RegionWrapper region) {
final Player player = this.getPlayer(); final Player player = this.getPlayer();
final RegionSelector selector = new CuboidRegionSelector(player.getWorld(), region.getBottomVector(), region.getTopVector()); final RegionSelector selector = new CuboidRegionSelector(player.getWorld(), region.getBottomVector(), region.getTopVector());
this.getSession().setRegionSelector(player.getWorld(), selector); this.getSession().setRegionSelector(player.getWorld(), selector);
} }
/**
* Get the largest region in the player's allowed WorldEdit region
* @return
*/
public RegionWrapper getLargestRegion() { public RegionWrapper getLargestRegion() {
int area = 0; int area = 0;
RegionWrapper max = null; RegionWrapper max = null;
@ -154,6 +250,10 @@ public abstract class FawePlayer<T> {
return this.getName(); return this.getName();
} }
/**
* Check if the player has WorldEdit bypass enabled
* @return
*/
public boolean hasWorldEditBypass() { public boolean hasWorldEditBypass() {
return this.hasPermission("fawe.bypass"); return this.hasPermission("fawe.bypass");
} }
@ -183,6 +283,13 @@ public abstract class FawePlayer<T> {
return null; return null;
} }
/**
* Get the metadata for a specific key (or return the default provided)
* @param key
* @param def
* @param <V>
* @return
*/
public <V> V getMeta(String key, V def) { public <V> V getMeta(String key, V def) {
if (this.meta != null) { if (this.meta != null) {
V value = (V) this.meta.get(key); V value = (V) this.meta.get(key);
@ -201,6 +308,10 @@ public abstract class FawePlayer<T> {
return this.meta == null ? null : this.meta.remove(key); return this.meta == null ? null : this.meta.remove(key);
} }
/**
* Unregister this player (delets all metadata etc)
* - Usually called on logout
*/
public void unregister() { public void unregister() {
getSession().setClipboard(null); getSession().setClipboard(null);
getSession().clearHistory(); getSession().clearHistory();

View File

@ -19,6 +19,11 @@ public class IntegerPair {
return this.hash; return this.hash;
} }
@Override
public String toString() {
return x + "," + z;
}
@Override @Override
public boolean equals(final Object obj) { public boolean equals(final Object obj) {
if (this == obj) { if (this == obj) {

View File

@ -46,6 +46,10 @@ public class RegionWrapper {
return minZ - z; return minZ - z;
} }
public boolean intersects(RegionWrapper other) {
return other.minX <= this.maxX && other.maxX >= this.minX && other.minZ <= this.maxZ && other.maxZ >= this.minZ;
}
public int distance(int x, int z) { public int distance(int x, int z) {
if (isIn(x, z)) { if (isIn(x, z)) {
return 0; return 0;

View File

@ -130,7 +130,7 @@ public class DiskStorageHistory implements ChangeSet, FaweChangeSet {
init(world, uuid, index); init(world, uuid, index);
} }
public void init(World world, UUID uuid, int i) { private void init(World world, UUID uuid, int i) {
this.uuid = uuid; this.uuid = uuid;
this.world = world; this.world = world;
String base = "history" + File.separator + world.getName() + File.separator + uuid; String base = "history" + File.separator + world.getName() + File.separator + uuid;

View File

@ -15,10 +15,10 @@ public class FaweException extends RuntimeException {
} }
public static FaweException get(Throwable e) { public static FaweException get(Throwable e) {
Throwable cause = e.getCause(); if (e instanceof FaweException) {
if (cause instanceof FaweException) { return (FaweException) e;
return (FaweException) cause;
} }
Throwable cause = e.getCause();
if (cause == null) { if (cause == null) {
return null; return null;
} }

View File

@ -2,24 +2,15 @@ package com.boydti.fawe.util;
import com.boydti.fawe.Fawe; import com.boydti.fawe.Fawe;
import com.boydti.fawe.config.BBC; import com.boydti.fawe.config.BBC;
import com.boydti.fawe.object.FaweLocation;
import com.boydti.fawe.object.FawePlayer; import com.boydti.fawe.object.FawePlayer;
import com.boydti.fawe.object.RegionWrapper;
import com.boydti.fawe.object.RunnableVal; import com.boydti.fawe.object.RunnableVal;
import com.boydti.fawe.object.changeset.DiskStorageHistory;
import com.sk89q.jnbt.CompoundTag; import com.sk89q.jnbt.CompoundTag;
import com.sk89q.jnbt.EndTag; import com.sk89q.jnbt.EndTag;
import com.sk89q.jnbt.ListTag; import com.sk89q.jnbt.ListTag;
import com.sk89q.jnbt.Tag; import com.sk89q.jnbt.Tag;
import com.sk89q.worldedit.world.World;
import java.io.File; import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.UUID;
public class MainUtil { public class MainUtil {
/* /*
@ -139,62 +130,6 @@ public class MainUtil {
return time; return time;
} }
public static List<DiskStorageHistory> getBDFiles(FaweLocation origin, UUID user, int radius, long timediff, boolean shallow) {
File history = new File(Fawe.imp().getDirectory(), "history" + File.separator + origin.world);
if (!history.exists()) {
return new ArrayList<>();
}
long now = System.currentTimeMillis();
ArrayList<File> files = new ArrayList<>();
for (File userFile : history.listFiles()) {
if (!userFile.isDirectory()) {
continue;
}
UUID userUUID;
try {
userUUID = UUID.fromString(userFile.getName());
} catch (IllegalArgumentException e) {
continue;
}
if (user != null && !userUUID.equals(user)) {
continue;
}
ArrayList<Integer> ids = new ArrayList<>();
for (File file : userFile.listFiles()) {
if (file.getName().endsWith(".bd")) {
if (timediff > Integer.MAX_VALUE || now - file.lastModified() <= timediff) {
files.add(file);
if (files.size() > 2048) {
return null;
}
}
}
}
}
World world = origin.getWorld();
Collections.sort(files, new Comparator<File>() {
@Override
public int compare(File a, File b) {
long value = a.lastModified() - b.lastModified();
return value == 0 ? 0 : value < 0 ? 1 : -1;
}
});
ArrayList<DiskStorageHistory> result = new ArrayList<>();
for (File file : files) {
UUID uuid = UUID.fromString(file.getParentFile().getName());
DiskStorageHistory dsh = new DiskStorageHistory(world, uuid, Integer.parseInt(file.getName().split("\\.")[0]));
DiskStorageHistory.DiskStorageSummary summary = dsh.summarize(new RegionWrapper(origin.x - 512, origin.x + 512, origin.z - 512, origin.z + 512), shallow);
RegionWrapper region = new RegionWrapper(summary.minX, summary.maxX, summary.minZ, summary.maxZ);
if (region.distance(origin.x, origin.z) <= radius) {
result.add(dsh);
if (result.size() > 64) {
return null;
}
}
}
return result;
}
public static void deleteOlder(File directory, final long timeDiff) { public static void deleteOlder(File directory, final long timeDiff) {
final long now = System.currentTimeMillis(); final long now = System.currentTimeMillis();
iterateFiles(directory, new RunnableVal<File>() { iterateFiles(directory, new RunnableVal<File>() {

View File

@ -1,6 +1,8 @@
package com.boydti.fawe.util; package com.boydti.fawe.util;
import com.boydti.fawe.config.Settings; import com.boydti.fawe.config.Settings;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
public class MemUtil { public class MemUtil {
@ -30,11 +32,30 @@ public class MemUtil {
return size; return size;
} }
private static BlockingQueue<Runnable> memoryLimitedTasks = new LinkedBlockingQueue<>();
private static BlockingQueue<Runnable> memoryPlentifulTasks = new LinkedBlockingQueue<>();
public static void addMemoryLimitedTask(Runnable run) {
if (run != null)
memoryLimitedTasks.add(run);
}
public static void addMemoryPlentifulTask(Runnable run) {
if (run != null)
memoryPlentifulTasks.add(run);
}
public static void memoryLimitedTask() { public static void memoryLimitedTask() {
for (Runnable task : memoryLimitedTasks) {
task.run();
}
memory.set(true); memory.set(true);
} }
public static void memoryPlentifulTask() { public static void memoryPlentifulTask() {
for (Runnable task : memoryPlentifulTasks) {
task.run();
}
memory.set(false); memory.set(false);
} }
} }

View File

@ -4,19 +4,46 @@ import com.boydti.fawe.Fawe;
import com.boydti.fawe.object.RunnableVal; import com.boydti.fawe.object.RunnableVal;
import java.util.Collection; import java.util.Collection;
import java.util.Iterator; import java.util.Iterator;
import java.util.concurrent.atomic.AtomicBoolean;
public abstract class TaskManager { public abstract class TaskManager {
public static TaskManager IMP; public static TaskManager IMP;
/**
* Run a repeating task on the main thread
* @param r
* @param interval in ticks
* @return
*/
public abstract int repeat(final Runnable r, final int interval); public abstract int repeat(final Runnable r, final int interval);
/**
* Run a repeating task asynchronously
* @param r
* @param interval in ticks
* @return
*/
public abstract int repeatAsync(final Runnable r, final int interval); public abstract int repeatAsync(final Runnable r, final int interval);
/**
* Run a task asynchronously
* @param r
*/
public abstract void async(final Runnable r); public abstract void async(final Runnable r);
/**
* Run a task on the main thread
* @param r
*/
public abstract void task(final Runnable r); public abstract void task(final Runnable r);
/**
* Run a task on either the main thread or asynchronously
* - If it's already the main thread, it will jst call run()
* @param r
* @param async
*/
public void task(final Runnable r, boolean async) { public void task(final Runnable r, boolean async) {
if (async) { if (async) {
async(r); async(r);
@ -31,12 +58,35 @@ public abstract class TaskManager {
} }
} }
/**
* Run a task later on the main thread
* @param r
* @param delay in ticks
*/
public abstract void later(final Runnable r, final int delay); public abstract void later(final Runnable r, final int delay);
/**
* Run a task later asynchronously
* @param r
* @param delay in ticks
*/
public abstract void laterAsync(final Runnable r, final int delay); public abstract void laterAsync(final Runnable r, final int delay);
/**
* Cancel a task
* @param task
*/
public abstract void cancel(final int task); public abstract void cancel(final int task);
/**
* Break up a task and run it in fragments of 5ms.<br>
* - Each task will run on the main thread.<br>
* - Usualy wait time is around 25ms<br>
* @param objects - The list of objects to run the task for
* @param task - The task to run on each object
* @param whenDone - When the object task completes
* @param <T>
*/
public <T> void objectTask(Collection<T> objects, final RunnableVal<T> task, final Runnable whenDone) { public <T> void objectTask(Collection<T> objects, final RunnableVal<T> task, final Runnable whenDone) {
final Iterator<T> iterator = objects.iterator(); final Iterator<T> iterator = objects.iterator();
task(new Runnable() { task(new Runnable() {
@ -57,11 +107,31 @@ public abstract class TaskManager {
}); });
} }
/**
* Quickly run a task on the main thread, and wait for execution to finish:<br>
* - Useful if you need to access something from the Bukkit API from another thread<br>
* @param function
* @param <T>
* @return
*/
public <T> T sync(final RunnableVal<T> function) { public <T> T sync(final RunnableVal<T> function) {
return sync(function, Integer.MAX_VALUE);
}
/**
* Quickly run a task on the main thread, and wait for execution to finish:<br>
* - Useful if you need to access something from the Bukkit API from another thread<br>
* @param function
* @param timeout - How long to wait for execution
* @param <T>
* @return
*/
public <T> T sync(final RunnableVal<T> function, int timeout) {
if (Fawe.get().getMainThread() == Thread.currentThread()) { if (Fawe.get().getMainThread() == Thread.currentThread()) {
function.run(); function.run();
return function.value; return function.value;
} }
final AtomicBoolean running = new AtomicBoolean(true);
RunnableVal<RuntimeException> run = new RunnableVal<RuntimeException>() { RunnableVal<RuntimeException> run = new RunnableVal<RuntimeException>() {
@Override @Override
public void run(RuntimeException value) { public void run(RuntimeException value) {
@ -71,6 +141,8 @@ public abstract class TaskManager {
this.value = e; this.value = e;
} catch (Throwable neverHappens) { } catch (Throwable neverHappens) {
neverHappens.printStackTrace(); neverHappens.printStackTrace();
} finally {
running.set(false);
} }
synchronized (function) { synchronized (function) {
function.notifyAll(); function.notifyAll();
@ -78,16 +150,18 @@ public abstract class TaskManager {
} }
}; };
TaskManager.IMP.task(run); TaskManager.IMP.task(run);
if (run.value != null) {
throw run.value;
}
try { try {
synchronized (function) { synchronized (function) {
function.wait(15000); while (running.get()) {
function.wait(timeout);
}
} }
} catch (InterruptedException e) { } catch (InterruptedException e) {
e.printStackTrace(); e.printStackTrace();
} }
if (run.value != null) {
throw run.value;
}
return function.value; return function.value;
} }
} }

View File

@ -19,7 +19,6 @@
package com.sk89q.worldedit.extension.platform; package com.sk89q.worldedit.extension.platform;
import com.boydti.fawe.Fawe;
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.exception.FaweException; import com.boydti.fawe.object.exception.FaweException;
@ -86,7 +85,6 @@ import com.sk89q.worldedit.util.logging.DynamicStreamHandler;
import com.sk89q.worldedit.util.logging.LogFormat; import com.sk89q.worldedit.util.logging.LogFormat;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Field;
import java.util.logging.FileHandler; import java.util.logging.FileHandler;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -238,19 +236,8 @@ public final class CommandManager {
LocalConfiguration config = worldEdit.getConfiguration(); LocalConfiguration config = worldEdit.getConfiguration();
CommandLocals locals = new CommandLocals(); CommandLocals locals = new CommandLocals();
FawePlayer fp; FawePlayer fp = FawePlayer.wrap(actor);
if (actor != null && actor.isPlayer()) { if (fp != null) {
try {
Field fieldBasePlayer = actor.getClass().getDeclaredField("basePlayer");
fieldBasePlayer.setAccessible(true);
Player player = (Player) fieldBasePlayer.get(actor);
Field fieldPlayer = player.getClass().getDeclaredField("player");
fieldPlayer.setAccessible(true);
fp = Fawe.imp().wrap(fieldPlayer.get(player));
} catch (Throwable e) {
e.printStackTrace();
fp = Fawe.imp().wrap(actor.getName());
}
if (fp.getMeta("fawe_action") != null) { if (fp.getMeta("fawe_action") != null) {
BBC.WORLDEDIT_COMMAND_LIMIT.send(fp); BBC.WORLDEDIT_COMMAND_LIMIT.send(fp);
return; return;
@ -259,7 +246,6 @@ public final class CommandManager {
locals.put(Actor.class, new PlayerWrapper((Player) actor)); locals.put(Actor.class, new PlayerWrapper((Player) actor));
} else { } else {
locals.put(Actor.class, actor); locals.put(Actor.class, actor);
fp = null;
} }
locals.put("arguments", event.getArguments()); locals.put("arguments", event.getArguments());
final long start = System.currentTimeMillis(); final long start = System.currentTimeMillis();

View File

@ -0,0 +1,512 @@
/*
* 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 com.boydti.fawe.config.BBC;
import com.boydti.fawe.object.exception.FaweException;
import com.sk89q.worldedit.LocalConfiguration;
import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.ServerInterface;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.WorldVector;
import com.sk89q.worldedit.command.tool.BlockTool;
import com.sk89q.worldedit.command.tool.DoubleActionBlockTool;
import com.sk89q.worldedit.command.tool.DoubleActionTraceTool;
import com.sk89q.worldedit.command.tool.Tool;
import com.sk89q.worldedit.command.tool.TraceTool;
import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.event.platform.BlockInteractEvent;
import com.sk89q.worldedit.event.platform.ConfigurationLoadEvent;
import com.sk89q.worldedit.event.platform.Interaction;
import com.sk89q.worldedit.event.platform.PlatformInitializeEvent;
import com.sk89q.worldedit.event.platform.PlatformReadyEvent;
import com.sk89q.worldedit.event.platform.PlayerInputEvent;
import com.sk89q.worldedit.extension.platform.permission.ActorSelectorLimits;
import com.sk89q.worldedit.internal.ServerInterfaceAdapter;
import com.sk89q.worldedit.regions.RegionSelector;
import com.sk89q.worldedit.util.Location;
import com.sk89q.worldedit.util.eventbus.Subscribe;
import com.sk89q.worldedit.world.World;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Manages registered {@link Platform}s for WorldEdit. Platforms are
* implementations of WorldEdit.
*
* <p>This class is thread-safe.</p>
*/
public class PlatformManager {
private static final Logger logger = Logger.getLogger(PlatformManager.class.getCanonicalName());
private final WorldEdit worldEdit;
private final CommandManager commandManager;
private final List<Platform> platforms = new ArrayList<Platform>();
private final Map<Capability, Platform> preferences = new EnumMap<Capability, Platform>(Capability.class);
private @Nullable String firstSeenVersion;
private final AtomicBoolean initialized = new AtomicBoolean();
private final AtomicBoolean configured = new AtomicBoolean();
/**
* Create a new platform manager.
*
* @param worldEdit the WorldEdit instance
*/
public PlatformManager(WorldEdit worldEdit) {
checkNotNull(worldEdit);
this.worldEdit = worldEdit;
this.commandManager = new CommandManager(worldEdit, this);
// Register this instance for events
worldEdit.getEventBus().register(this);
}
/**
* Register a platform with WorldEdit.
*
* @param platform the platform
*/
public synchronized void register(Platform platform) {
checkNotNull(platform);
logger.log(Level.FINE, "Got request to register " + platform.getClass() + " with WorldEdit [" + super.toString() + "]");
// Just add the platform to the list of platforms: we'll pick favorites
// once all the platforms have been loaded
platforms.add(platform);
// Make sure that versions are in sync
if (firstSeenVersion != null) {
if (!firstSeenVersion.equals(platform.getVersion())) {
logger.log(Level.WARNING, "Multiple ports of WorldEdit are installed but they report different versions ({0} and {1}). " +
"If these two versions are truly different, then you may run into unexpected crashes and errors.",
new Object[]{ firstSeenVersion, platform.getVersion() });
}
} else {
firstSeenVersion = platform.getVersion();
}
}
/**
* Unregister a platform from WorldEdit.
*
* <p>If the platform has been chosen for any capabilities, then a new
* platform will be found.</p>
*
* @param platform the platform
*/
public synchronized boolean unregister(Platform platform) {
checkNotNull(platform);
boolean removed = platforms.remove(platform);
if (removed) {
logger.log(Level.FINE, "Unregistering " + platform.getClass().getCanonicalName() + " from WorldEdit");
boolean choosePreferred = false;
// Check whether this platform was chosen to be the preferred one
// for any capability and be sure to remove it
Iterator<Entry<Capability, Platform>> it = preferences.entrySet().iterator();
while (it.hasNext()) {
Entry<Capability, Platform> entry = it.next();
if (entry.getValue().equals(platform)) {
Capability key = entry.getKey();
try {
Method methodUnload = key.getClass().getDeclaredMethod("unload", PlatformManager.class, Platform.class);
methodUnload.setAccessible(true);
methodUnload.invoke(key, this, entry.getValue());
} catch (Throwable e) {
throw new RuntimeException(e);
}
it.remove();
choosePreferred = true; // Have to choose new favorites
}
}
if (choosePreferred) {
choosePreferred();
}
}
return removed;
}
/**
* Get the preferred platform for handling a certain capability. Returns
* null if none is available.
*
* @param capability the capability
* @return the platform
* @throws NoCapablePlatformException thrown if no platform is capable
*/
public synchronized Platform queryCapability(Capability capability) throws NoCapablePlatformException {
Platform platform = preferences.get(checkNotNull(capability));
if (platform != null) {
return platform;
} else {
throw new NoCapablePlatformException("No platform was found supporting " + capability.name());
}
}
/**
* Choose preferred platforms and perform necessary initialization.
*/
private synchronized void choosePreferred() {
for (Capability capability : Capability.values()) {
Platform preferred = findMostPreferred(capability);
if (preferred != null) {
preferences.put(capability, preferred);
try {
Method methodInitialize = Capability.class.getDeclaredMethod("initialize", PlatformManager.class, Platform.class);
methodInitialize.setAccessible(true);
methodInitialize.invoke(capability, this, preferred);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
}
// Fire configuration event
if (preferences.containsKey(Capability.CONFIGURATION) && configured.compareAndSet(false, true)) {
worldEdit.getEventBus().post(new ConfigurationLoadEvent(queryCapability(Capability.CONFIGURATION).getConfiguration()));
}
}
/**
* Find the most preferred platform for a given capability from the list of
* platforms. This does not use the map of preferred platforms.
*
* @param capability the capability
* @return the most preferred platform, or null if no platform was found
*/
private synchronized @Nullable Platform findMostPreferred(Capability capability) {
Platform preferred = null;
Preference highest = null;
for (Platform platform : platforms) {
Preference preference = platform.getCapabilities().get(capability);
if (preference != null && (highest == null || preference.isPreferredOver(highest))) {
preferred = platform;
highest = preference;
}
}
return preferred;
}
/**
* Get a list of loaded platforms.
*
* <p>The returned list is a copy of the original and is mutable.</p>
*
* @return a list of platforms
*/
public synchronized List<Platform> getPlatforms() {
return new ArrayList<Platform>(platforms);
}
/**
* Given a world, possibly return the same world but using a different
* platform preferred for world editing operations.
*
* @param base the world to match
* @return the preferred world, if one was found, otherwise the given world
*/
public World getWorldForEditing(World base) {
checkNotNull(base);
World match = queryCapability(Capability.WORLD_EDITING).matchWorld(base);
return match != null ? match : base;
}
/**
* Given an actor, return a new one that may use a different platform
* for permissions and world editing.
*
* @param base the base actor to match
* @return a new delegate actor
*/
@SuppressWarnings("unchecked")
public <T extends Actor> T createProxyActor(T base) {
checkNotNull(base);
if (base instanceof Player) {
Player player = (Player) base;
Player permActor = queryCapability(Capability.PERMISSIONS).matchPlayer(player);
if (permActor == null) {
permActor = player;
}
Player cuiActor = queryCapability(Capability.WORLDEDIT_CUI).matchPlayer(player);
if (cuiActor == null) {
cuiActor = player;
}
try {
Class<?> clazz = Class.forName("com.sk89q.worldedit.extension.platform.PlayerProxy");
Constructor<?> constructor = clazz.getDeclaredConstructor(Player.class, Actor.class, Actor.class, World.class);
constructor.setAccessible(true);
return (T) constructor.newInstance(player, permActor, cuiActor, getWorldForEditing(player.getWorld()));
} catch (Throwable e) {
throw new RuntimeException(e);
}
} else {
return base;
}
}
/**
* Get the command manager.
*
* @return the command manager
*/
public CommandManager getCommandManager() {
return commandManager;
}
/**
* Get the current configuration.
*
* <p>If no platform has been registered yet, then a default configuration
* will be returned.</p>
*
* @return the configuration
*/
public LocalConfiguration getConfiguration() {
return queryCapability(Capability.CONFIGURATION).getConfiguration();
}
/**
* Return a legacy {@link ServerInterface}.
*
* @return a {@link ServerInterface}
* @throws IllegalStateException if no platform has been registered
*/
@SuppressWarnings("deprecation")
public ServerInterface getServerInterface() throws IllegalStateException {
return ServerInterfaceAdapter.adapt(queryCapability(Capability.USER_COMMANDS));
}
@Subscribe
public void handlePlatformReady(PlatformReadyEvent event) {
choosePreferred();
if (initialized.compareAndSet(false, true)) {
worldEdit.getEventBus().post(new PlatformInitializeEvent());
}
}
@SuppressWarnings("deprecation")
@Subscribe
public void handleBlockInteract(BlockInteractEvent event) {
// Create a proxy actor with a potentially different world for
// making changes to the world
Actor actor = createProxyActor(event.getCause());
try {
Location location = event.getLocation();
Vector vector = location.toVector();
// At this time, only handle interaction from players
if (actor instanceof Player) {
Player player = (Player) actor;
LocalSession session = worldEdit.getSessionManager().get(actor);
if (event.getType() == Interaction.HIT) {
if (player.getItemInHand() == getConfiguration().wandItem) {
if (!session.isToolControlEnabled()) {
return;
}
if (!actor.hasPermission("worldedit.selection.pos")) {
return;
}
RegionSelector selector = session.getRegionSelector(player.getWorld());
if (selector.selectPrimary(location.toVector(), ActorSelectorLimits.forActor(player))) {
selector.explainPrimarySelection(actor, session, vector);
}
event.setCancelled(true);
return;
}
if (player.isHoldingPickAxe() && session.hasSuperPickAxe()) {
final BlockTool superPickaxe = session.getSuperPickaxe();
if (superPickaxe != null && superPickaxe.canUse(player)) {
event.setCancelled(superPickaxe.actPrimary(queryCapability(Capability.WORLD_EDITING), getConfiguration(), player, session, location));
return;
}
}
Tool tool = session.getTool(player.getItemInHand());
if (tool != null && tool instanceof DoubleActionBlockTool) {
if (tool.canUse(player)) {
((DoubleActionBlockTool) tool).actSecondary(queryCapability(Capability.WORLD_EDITING), getConfiguration(), player, session, location);
event.setCancelled(true);
}
}
} else if (event.getType() == Interaction.OPEN) {
if (player.getItemInHand() == getConfiguration().wandItem) {
if (!session.isToolControlEnabled()) {
return;
}
if (!actor.hasPermission("worldedit.selection.pos")) {
return;
}
RegionSelector selector = session.getRegionSelector(player.getWorld());
if (selector.selectSecondary(vector, ActorSelectorLimits.forActor(player))) {
selector.explainSecondarySelection(actor, session, vector);
}
event.setCancelled(true);
return;
}
Tool tool = session.getTool(player.getItemInHand());
if (tool != null && tool instanceof BlockTool) {
if (tool.canUse(player)) {
((BlockTool) tool).actPrimary(queryCapability(Capability.WORLD_EDITING), getConfiguration(), player, session, location);
event.setCancelled(true);
}
}
}
}
} catch (Throwable e) {
FaweException faweException = FaweException.get(e);
if (faweException != null) {
actor.printError(BBC.PREFIX.s() + " " + BBC.WORLDEDIT_CANCEL_REASON.format(faweException.getMessage()));
} else {
actor.printError("Please report this error: [See console]");
actor.printRaw(e.getClass().getName() + ": " + e.getMessage());
e.printStackTrace();
}
}
}
@SuppressWarnings("deprecation")
@Subscribe
public void handlePlayerInput(PlayerInputEvent event) {
// Create a proxy actor with a potentially different world for
// making changes to the world
Player player = createProxyActor(event.getPlayer());
try {
switch (event.getInputType()) {
case PRIMARY: {
if (player.getItemInHand() == getConfiguration().navigationWand) {
if (getConfiguration().navigationWandMaxDistance <= 0) {
return;
}
if (!player.hasPermission("worldedit.navigation.jumpto.tool")) {
return;
}
WorldVector pos = player.getSolidBlockTrace(getConfiguration().navigationWandMaxDistance);
if (pos != null) {
player.findFreePosition(pos);
} else {
player.printError("No block in sight (or too far)!");
}
event.setCancelled(true);
return;
}
LocalSession session = worldEdit.getSessionManager().get(player);
Tool tool = session.getTool(player.getItemInHand());
if (tool != null && tool instanceof DoubleActionTraceTool) {
if (tool.canUse(player)) {
((DoubleActionTraceTool) tool).actSecondary(queryCapability(Capability.WORLD_EDITING), getConfiguration(), player, session);
event.setCancelled(true);
return;
}
}
break;
}
case SECONDARY: {
if (player.getItemInHand() == getConfiguration().navigationWand) {
if (getConfiguration().navigationWandMaxDistance <= 0) {
return;
}
if (!player.hasPermission("worldedit.navigation.thru.tool")) {
return;
}
if (!player.passThroughForwardWall(40)) {
player.printError("Nothing to pass through!");
}
event.setCancelled(true);
return;
}
LocalSession session = worldEdit.getSessionManager().get(player);
Tool tool = session.getTool(player.getItemInHand());
if (tool != null && tool instanceof TraceTool) {
if (tool.canUse(player)) {
((TraceTool) tool).actPrimary(queryCapability(Capability.WORLD_EDITING), getConfiguration(), player, session);
event.setCancelled(true);
return;
}
}
break;
}
}
} catch (Throwable e) {
FaweException faweException = FaweException.get(e);
if (faweException != null) {
player.printError(BBC.PREFIX.s() + " " + BBC.WORLDEDIT_CANCEL_REASON.format(faweException.getMessage()));
} else {
player.printError("Please report this error: [See console]");
player.printRaw(e.getClass().getName() + ": " + e.getMessage());
e.printStackTrace();
}
}
}
public static Class<?> inject() {
return PlatformManager.class;
}
}