Finish tile entities / some documentation

This commit is contained in:
Jesse Boyd 2016-04-01 16:20:35 +11:00
parent 74a03b2b19
commit d9d806ac4a
18 changed files with 274 additions and 90 deletions

View File

@ -8,7 +8,7 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<artifactId>FastAsyncWorldEdit</artifactId>
<version>3.3.1</version>
<version>3.3.3</version>
<name>FastAsyncWorldEdit</name>
<packaging>jar</packaging>
<build>

View File

@ -38,6 +38,45 @@ import com.sk89q.worldedit.function.visitor.NonRisingVisitor;
import com.sk89q.worldedit.function.visitor.RecursiveVisitor;
import com.sk89q.worldedit.function.visitor.RegionVisitor;
/**
* Simplified overview:
*
* [ WorldEdit action]
* |
* \|/
* [ EditSession ] - The change is processed (area restrictions, change limit, block type)
* |
* \|/
* [Block change] - A block change from some location
* |
* \|/
* [ Set Queue ] - The SetQueue manages the implementation specific queue
* |
* \|/
* [ Fawe Queue] - A queue of chunks - check if the queue has the chunk for a change
* |
* \|/
* [ Fawe Chunk Implementation ] - Otherwise create a new FaweChunk object which is a wrapper around the Chunk object
* |
* \|/
* [ Execution ] - When done, the queue then sets the blocks for the chunk, performs lighting updates and sends the chunk packet to the clients
*
* Why it's faster:
* - The chunk is modified directly rather than through the API
* \ Removes some overhead, and means some processing can be done async
* - Lighting updates are performed on the chunk level rather than for every block
* \ e.g. A blob of stone: only the visible blocks need to have the lighting calculated
* - Block changes are sent with a chunk packet
* \ A chunk packet is generally quicker to create and smaller for large world edits
* - No physics updates
* \ Physics updates are slow, and are usually performed on each block
* - Block data shortcuts
* \ Some known blocks don't need to have the data set or accessed (e.g. air is never going to have data)
* - Remove redundant extents
* \ Up to 11 layers of extents can be removed
* - History bypassing
* \ FastMode bypasses history and means blocks in the world don't need to be checked and recorded
*/
public class Fawe {
/**
* The FAWE instance;

View File

@ -145,6 +145,7 @@ public class FaweAPI {
* If a schematic is too large to be pasted normally<br>
* - Skips any block history
* - Ignores some block data
* - No, it's not streaming it from disk, but it is a lot faster
* @param file
* @param loc
* @return
@ -202,6 +203,7 @@ public class FaweAPI {
* If a schematic is too large to be pasted normally<br>
* - Skips any block history
* - Ignores some block data
* - Not actually streaming from disk, but it does skip a lot of overhead
* @param is
* @param loc
* @throws IOException
@ -321,8 +323,6 @@ public class FaweAPI {
ids = null;
datas = null;
System.gc();
System.gc();
}
/**

View File

@ -13,6 +13,7 @@ public class FaweCache {
public final static short[] CACHE_ID = new short[65535];
public final static byte[] CACHE_DATA = new byte[65535];
// Faster than java random (since the game just needs to look random)
public final static PseudoRandom RANDOM = new PseudoRandom();
static {

View File

@ -34,6 +34,10 @@ public class BukkitPlayer extends FawePlayer<Player> {
@Override
public void setPermission(final String perm, final boolean flag) {
/*
* Permissions are used to managing WorldEdit region restrictions
* - The `/wea` command will give/remove the required bypass permission
*/
if (Fawe.<FaweBukkit> imp().getVault() == null) {
this.parent.addAttachment(Fawe.<FaweBukkit> imp()).setPermission("fawe.bypass", flag);
} else if (flag) {

View File

@ -95,11 +95,24 @@ public class FaweBukkit extends JavaPlugin implements IFawe {
}
}
/**
* Kinda a really messy class I just copied over from an old project<br>
* - Still works, so cbf cleaning it up<br>
* - Completely optional to have this class enabled since things get cancelled further down anyway<br>
* - Useful since it informs the player why an edit changed no blocks etc.<br>
* - Predicts the number of blocks changed and cancels the edit if it's too large<br>
* - Predicts where the edit will effect and cancels it if it's outside a region<br>
* - Restricts the brush iteration limit<br>
*/
@Override
public void setupWEListener() {
this.getServer().getPluginManager().registerEvents(new WEListener(), this);
}
/**
* Vault isn't required, but used for setting player permissions (WorldEdit bypass)
* @return
*/
@Override
public void setupVault() {
try {
@ -109,6 +122,9 @@ public class FaweBukkit extends JavaPlugin implements IFawe {
}
}
/**
* The task manager handles sync/async tasks
*/
@Override
public TaskManager getTaskManager() {
return new BukkitTaskMan(this);
@ -133,6 +149,13 @@ public class FaweBukkit extends JavaPlugin implements IFawe {
}
}
/**
* The FaweQueue is a core part of block placement<br>
* - The queue returned here is used in the SetQueue class (SetQueue handles the implementation specific queue)<br>
* - Block changes are grouped by chunk (as it's more efficient for lighting/packet sending)<br>
* - The FaweQueue returned here will provide the wrapper around the chunk object (FaweChunk)<br>
* - When a block change is requested, the SetQueue will first check if the chunk exists in the queue, or it will create and add it<br>
*/
@Override
public FaweQueue getQueue() {
if (FaweAPI.checkVersion(this.getServerVersion(), 1, 9, 0)) {
@ -167,11 +190,17 @@ public class FaweBukkit extends JavaPlugin implements IFawe {
return this.version;
}
/**
* The EditSessionWrapper should have the same functionality as the normal EditSessionWrapper but with some optimizations
*/
@Override
public EditSessionWrapper getEditSessionWrapper(final EditSession session) {
return new BukkitEditSessionWrapper_1_8(session);
}
/**
* A mask manager handles region restrictions e.g. PlotSquared plots / WorldGuard regions
*/
@Override
public Collection<FaweMaskManager> getMaskManagers() {
final Plugin worldguardPlugin = Bukkit.getServer().getPluginManager().getPlugin("WorldGuard");

View File

@ -27,6 +27,16 @@ import com.sk89q.worldedit.LocalWorld;
import com.sk89q.worldedit.bukkit.BukkitUtil;
import com.sk89q.worldedit.regions.Region;
/**
* Kinda a really messy class I just copied over from an old project<br>
* - Still works, so cbf cleaning it up<br>
* - Completely optional to have this class enabled since things get cancelled further down anyway<br>
* - Useful since it informs the player why an edit changed no blocks etc.<br>
* - Predicts the number of blocks changed and cancels the edit if it's too large<br>
* - Predicts where the edit will effect and cancels it if it's outside a region<br>
* - Restricts the brush iteration limit<br>
* @deprecated as I plan on replacing it at some point
*/
@Deprecated
public class WEListener implements Listener {

View File

@ -14,17 +14,18 @@ public class BukkitEditSessionWrapper_0 extends EditSessionWrapper {
public BukkitEditSessionWrapper_0(final EditSession session) {
super(session);
try {
// Try to hook into BlocksHub
this.hook = new BlocksHubHook();
} catch (final Throwable e) {
}
} catch (final Throwable e) {}
}
@Override
public Extent getHistoryExtent(final Extent parent, final ChangeSet set, final FawePlayer<?> player) {
if (this.hook != null) {
// If we are doing logging, return a custom logging extent
return this.hook.getLoggingExtent(parent, set, player);
}
// Otherwise return the normal history extent
return super.getHistoryExtent(parent, set, player);
}

View File

@ -30,9 +30,20 @@ import com.boydti.fawe.util.SetQueue;
import com.boydti.fawe.util.TaskManager;
import com.sk89q.worldedit.world.biome.BaseBiome;
/**
* The base object for
*/
public abstract class BukkitQueue_0 extends FaweQueue implements Listener {
/**
* Map of loaded chunks for quicker checking
*/
private final HashMap<String, HashSet<Long>> loaded = new HashMap<>();
/**
* Map of chunks in the queue
*/
private final ConcurrentHashMap<ChunkLoc, FaweChunk<Chunk>> blocks = new ConcurrentHashMap<>();
public BukkitQueue_0() {
TaskManager.IMP.task(new Runnable() {
@ -109,8 +120,17 @@ public abstract class BukkitQueue_0 extends FaweQueue implements Listener {
public void onChunkUnload(final ChunkUnloadEvent event) {
this.removeLoaded(event.getChunk());
}
private final ConcurrentHashMap<ChunkLoc, FaweChunk<Chunk>> blocks = new ConcurrentHashMap<>();
@Override
public void addTask(String world, int x, int y, int z, Runnable runnable) {
// TODO Auto-generated method stub
final ChunkLoc wrap = new ChunkLoc(world, x >> 4, z >> 4);
FaweChunk<Chunk> result = this.blocks.get(wrap);
if (result == null) {
throw new IllegalArgumentException("Task must be accompanied by a block change or manually adding to queue!");
}
result.addTask(runnable);
}
@Override
public boolean setBlock(final String world, int x, final int y, int z, final short id, final byte data) {
@ -186,6 +206,7 @@ public abstract class BukkitQueue_0 extends FaweQueue implements Listener {
if (!this.setComponents(fc)) {
return false;
}
fc.executeTasks();
return true;
}

View File

@ -431,8 +431,19 @@ public class BukkitQueue_1_8 extends BukkitQueue_0 {
return false;
}
/**
* This method is called when the server is < 1% available memory (i.e. likely to crash)<br>
* - You can disable this in the conifg<br>
* - Will try to free up some memory<br>
* - Clears the queue<br>
* - Clears worldedit history<br>
* - Clears entities<br>
* - Unloads chunks in vacant worlds<br>
* - Unloads non visible chunks<br>
*/
@Override
public void clear() {
// Clear the queue
super.clear();
ArrayDeque<Chunk> toUnload = new ArrayDeque<>();
final int distance = Bukkit.getViewDistance() + 2;
@ -526,31 +537,6 @@ public class BukkitQueue_1_8 extends BukkitQueue_0 {
}
toUnload = null;
players = null;
System.gc();
System.gc();
free = MemUtil.calculateMemory();
if (free > 1) {
return;
}
Collection<? extends Player> online = Bukkit.getOnlinePlayers();
if (online.size() > 0) {
online.iterator().next().kickPlayer("java.lang.OutOfMemoryError");
}
online = null;
System.gc();
System.gc();
free = MemUtil.calculateMemory();
if ((free > 1) || (Bukkit.getOnlinePlayers().size() > 0)) {
return;
}
for (final World world : Bukkit.getWorlds()) {
final String name = world.getName();
for (final Chunk chunk : world.getLoadedChunks()) {
this.unloadChunk(name, chunk);
}
}
System.gc();
System.gc();
}
public Object newChunkSection(final int i, final boolean flag, final char[] ids) {

View File

@ -28,6 +28,10 @@ import com.boydti.fawe.bukkit.FaweBukkit;
import com.boydti.fawe.config.Settings;
import com.boydti.fawe.object.ChunkLoc;
/**
* Please ignore
*/
@Deprecated
public class FaweGenerator_1_8 extends ChunkGenerator implements Listener {
private boolean events;

View File

@ -30,6 +30,7 @@ import org.bukkit.generator.ChunkGenerator;
import com.boydti.fawe.Fawe;
import com.boydti.fawe.FaweCache;
import com.boydti.fawe.bukkit.v0.BukkitQueue_0;
import com.boydti.fawe.config.Settings;
import com.boydti.fawe.object.ChunkLoc;
import com.boydti.fawe.object.FaweChunk;
import com.boydti.fawe.object.FawePlayer;
@ -41,7 +42,7 @@ import com.boydti.fawe.util.ReflectionUtils.RefConstructor;
import com.boydti.fawe.util.ReflectionUtils.RefField;
import com.boydti.fawe.util.ReflectionUtils.RefMethod;
import com.boydti.fawe.util.ReflectionUtils.RefMethod.RefExecutor;
import com.intellectualcrafters.plot.util.TaskManager;
import com.boydti.fawe.util.TaskManager;
import com.sk89q.worldedit.LocalSession;
public class BukkitQueue_1_9 extends BukkitQueue_0 {
@ -56,12 +57,10 @@ public class BukkitQueue_1_9 extends BukkitQueue_0 {
private final RefClass classBlock = getRefClass("{nms}.Block");
private final RefClass classIBlockData = getRefClass("{nms}.IBlockData");
private final RefMethod methodGetHandleChunk;
private final RefConstructor MapChunk;
private final RefMethod methodInitLighting;
private final RefConstructor classBlockPositionConstructor;
private final RefConstructor classChunkSectionConstructor;
private final RefMethod methodW;
private final RefMethod methodAreNeighborsLoaded;
private final RefField fieldSections;
private final RefField fieldWorld;
private final RefMethod methodGetBlocks;
@ -74,7 +73,6 @@ public class BukkitQueue_1_9 extends BukkitQueue_0 {
public BukkitQueue_1_9() throws NoSuchMethodException, RuntimeException {
this.methodGetHandleChunk = this.classCraftChunk.getMethod("getHandle");
this.methodInitLighting = this.classChunk.getMethod("initLighting");
this.MapChunk = this.classMapChunk.getConstructor(this.classChunk.getRealClass(), boolean.class, int.class);
this.classBlockPositionConstructor = this.classBlockPosition.getConstructor(int.class, int.class, int.class);
this.methodW = this.classWorld.getMethod("w", this.classBlockPosition.getRealClass());
this.fieldSections = this.classChunk.getField("sections");
@ -82,7 +80,6 @@ public class BukkitQueue_1_9 extends BukkitQueue_0 {
this.methodGetByCombinedId = this.classBlock.getMethod("getByCombinedId", int.class);
this.methodGetBlocks = this.classChunkSection.getMethod("getBlocks");
this.methodSetType = this.classChunkSection.getMethod("setType", int.class, int.class, int.class, this.classIBlockData.getRealClass());
this.methodAreNeighborsLoaded = this.classChunk.getMethod("areNeighborsLoaded", int.class);
this.classChunkSectionConstructor = this.classChunkSection.getConstructor(int.class, boolean.class, char[].class);
this.air = this.methodGetByCombinedId.call(0);
this.tileEntityListTick = this.classWorld.getField("tileEntityListTick");
@ -92,12 +89,17 @@ public class BukkitQueue_1_9 extends BukkitQueue_0 {
@Override
public Collection<FaweChunk<Chunk>> sendChunk(final Collection<FaweChunk<Chunk>> fcs) {
for (final FaweChunk<Chunk> fc : fcs) {
final Chunk chunk = fc.getChunk();
final ChunkLoc loc = fc.getChunkLoc();
chunk.getWorld().refreshChunk(loc.x, loc.z);
sendChunk(fc);
}
return new ArrayList<>();
}
public void sendChunk(FaweChunk<Chunk> fc) {
fixLighting(fc, Settings.FIX_ALL_LIGHTING);
final Chunk chunk = fc.getChunk();
final ChunkLoc loc = fc.getChunkLoc();
chunk.getWorld().refreshChunk(loc.x, loc.z);
}
@Override
public boolean fixLighting(final FaweChunk<?> pc, final boolean fixAll) {
@ -358,18 +360,28 @@ public class BukkitQueue_1_9 extends BukkitQueue_0 {
}
}
}
TaskManager.runTaskLater(new Runnable() {
TaskManager.IMP.later(new Runnable() {
@Override
public void run() {
final ChunkLoc loc = fs.getChunkLoc();
world.refreshChunk(loc.x, loc.z);
sendChunk(fs);
}
}, 1);
return true;
}
/**
* This method is called when the server is < 1% available memory (i.e. likely to crash)<br>
* - You can disable this in the conifg<br>
* - Will try to free up some memory<br>
* - Clears the queue<br>
* - Clears worldedit history<br>
* - Clears entities<br>
* - Unloads chunks in vacant worlds<br>
* - Unloads non visible chunks<br>
*/
@Override
public void clear() {
// Clear the queue
super.clear();
ArrayDeque<Chunk> toUnload = new ArrayDeque<>();
final int distance = Bukkit.getViewDistance() + 2;
@ -463,31 +475,6 @@ public class BukkitQueue_1_9 extends BukkitQueue_0 {
}
toUnload = null;
players = null;
System.gc();
System.gc();
free = MemUtil.calculateMemory();
if (free > 1) {
return;
}
Collection<? extends Player> online = Bukkit.getOnlinePlayers();
if (online.size() > 0) {
online.iterator().next().kickPlayer("java.lang.OutOfMemoryError");
}
online = null;
System.gc();
System.gc();
free = MemUtil.calculateMemory();
if ((free > 1) || (Bukkit.getOnlinePlayers().size() > 0)) {
return;
}
for (final World world : Bukkit.getWorlds()) {
final String name = world.getName();
for (final Chunk chunk : world.getLoadedChunks()) {
this.unloadChunk(name, chunk);
}
}
System.gc();
System.gc();
}
public Object newChunkSection(final int i, final boolean flag, final char[] ids) {

View File

@ -4,6 +4,7 @@ import java.util.List;
import com.boydti.fawe.util.SetQueue;
import com.boydti.fawe.util.TaskManager;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.worldedit.BlockVector;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.Vector;
@ -104,6 +105,58 @@ public class FastWorldEditExtent extends AbstractDelegateExtent {
final int y = location.getBlockY();
final int z = location.getBlockZ();
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: {
SetQueue.IMP.setBlock(this.world, x, y, z, id, (byte) block.getData());
if (block.hasNbtData()) {
final CompoundTag nbt = block.getNbtData();
SetQueue.IMP.addTask(this.world, x, y, z, new Runnable() {
@Override
public void run() {
try {
FastWorldEditExtent.super.setBlock(location, block);
} catch (WorldEditException e) {
e.printStackTrace();
}
}
});
}
return true;
}
case 0:
case 2:
case 4:
@ -113,7 +166,6 @@ public class FastWorldEditExtent extends AbstractDelegateExtent {
case 20:
case 21:
case 22:
case 25:
case 30:
case 32:
case 37:
@ -127,13 +179,10 @@ public class FastWorldEditExtent extends AbstractDelegateExtent {
case 48:
case 49:
case 51:
case 52:
case 56:
case 57:
case 58:
case 60:
case 61:
case 62:
case 7:
case 8:
case 9:
@ -147,7 +196,6 @@ public class FastWorldEditExtent extends AbstractDelegateExtent {
case 81:
case 82:
case 83:
case 84:
case 85:
case 87:
case 88:
@ -157,16 +205,10 @@ public class FastWorldEditExtent extends AbstractDelegateExtent {
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:
@ -174,8 +216,6 @@ public class FastWorldEditExtent extends AbstractDelegateExtent {
case 172:
case 173:
case 174:
case 176:
case 177:
case 181:
case 182:
case 188:

View File

@ -1,5 +1,7 @@
package com.boydti.fawe.object;
import java.util.ArrayDeque;
import com.boydti.fawe.config.Settings;
import com.boydti.fawe.util.SetQueue;
import com.sk89q.worldedit.world.biome.BaseBiome;
@ -8,6 +10,8 @@ public abstract class FaweChunk<T> {
private ChunkLoc chunk;
private final ArrayDeque<Runnable> tasks = new ArrayDeque<Runnable>();
/**
* A FaweSections object represents a chunk and the blocks that you wish to change in it.
*/
@ -44,6 +48,19 @@ public abstract class FaweChunk<T> {
}
}
public void addTask(Runnable run) {
if (run != null) {
tasks.add(run);
}
}
public void executeTasks() {
for (Runnable task : tasks) {
task.run();
}
tasks.clear();
}
public abstract T getChunk();
public abstract void setBlock(final int x, final int y, final int z, final int id, final byte data);

View File

@ -187,8 +187,21 @@ public class ProcessedWEExtent extends AbstractDelegateExtent {
return false;
}
SetQueue.IMP.setBlock(this.world, x, location.getBlockY(), z, id, (byte) block.getData());
if (block.hasNbtData()) {
SetQueue.IMP.addTask(this.world, x, location.getBlockY(), z, new Runnable() {
@Override
public void run() {
try {
ProcessedWEExtent.super.setBlock(location, block);
} catch (WorldEditException e) {
e.printStackTrace();
}
}
});
}
return true;
}
break;
return false;
}
default: {
final int x = location.getBlockX();
@ -282,19 +295,17 @@ public class ProcessedWEExtent extends AbstractDelegateExtent {
case 191:
case 192: {
SetQueue.IMP.setBlock(this.world, x, y, z, id);
break;
return true;
}
default: {
SetQueue.IMP.setBlock(this.world, x, y, z, id, (byte) block.getData());
break;
return true;
}
}
return true;
}
return false;
}
}
return false;
}
@Override

View File

@ -7,6 +7,7 @@ import com.boydti.fawe.object.FaweChunk;
import com.sk89q.worldedit.world.biome.BaseBiome;
public abstract class FaweQueue {
public abstract boolean setBlock(final String world, final int x, final int y, final int z, final short id, final byte data);
public abstract boolean setBiome(final String world, final int x, final int z, final BaseBiome biome);
@ -38,5 +39,10 @@ public abstract class FaweQueue {
// Unload chunks
}
/**
* This method is called when the server is < 1% available memory
*/
protected abstract void clear();
public abstract void addTask(String world, int x, int y, int z, Runnable runnable);
}

View File

@ -9,15 +9,30 @@ import com.sk89q.worldedit.world.biome.BaseBiome;
public class SetQueue {
/**
* The implementation specific queue
*/
public static final SetQueue IMP = new SetQueue();
public FaweQueue queue;
/**
* Track the time in ticks
*/
private final AtomicInteger time_waiting = new AtomicInteger(2);
private final AtomicInteger time_current = new AtomicInteger(0);
private final ArrayDeque<Runnable> runnables = new ArrayDeque<>();
/**
* Used to calculate elapsed time in milliseconds and ensure block placement doesn't lag the server
*/
private long last;
private long last2;
/**
* A queue of tasks that will run when the queue is empty
*/
private final ArrayDeque<Runnable> runnables = new ArrayDeque<>();
public SetQueue() {
TaskManager.IMP.repeat(new Runnable() {
@ -145,4 +160,17 @@ public class SetQueue {
public boolean isChunkLoaded(final String world, final int x, final int z) {
return this.queue.isChunkLoaded(world, x, z);
}
/**
* Add a task to run when the chunk is set<br>
* @throws IllegalArgumentException if the chunk is not in the queue
* @param world
* @param x
* @param y
* @param z
* @param runnable
*/
public void addTask(String world, int x, int y, int z, Runnable runnable) {
this.queue.addTask(world, x, y, z, runnable);
}
}

View File

@ -1,6 +1,6 @@
name: FastAsyncWorldEdit
main: com.boydti.fawe.bukkit.FaweBukkit
version: 3.3.1
version: 3.3.3
description: Fast Async WorldEdit plugin
authors: [Empire92]
loadbefore: [WorldEdit]