diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/beta/BukkitChunkHolder.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/beta/BukkitChunkHolder.java index 5a9fcd233..3faba2ebf 100644 --- a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/beta/BukkitChunkHolder.java +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/beta/BukkitChunkHolder.java @@ -38,7 +38,7 @@ public class BukkitChunkHolder extends ChunkHolder { } @Override - public void filter(final Filter filter) { + public void set(final Filter filter) { // for each block // filter.applyBlock(block) throw new UnsupportedOperationException("Not implemented"); diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/beta/BukkitFullChunk.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/beta/BukkitFullChunk.java index 27104553b..61306140e 100644 --- a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/beta/BukkitFullChunk.java +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/beta/BukkitFullChunk.java @@ -21,7 +21,7 @@ public class BukkitFullChunk extends ChunkHolder { } @Override - public void filter(Filter filter) { + public void set(Filter filter) { } diff --git a/worldedit-core/src/main/java/com/boydti/fawe/beta/Filter.java b/worldedit-core/src/main/java/com/boydti/fawe/beta/Filter.java index 6038aacb5..0d1450fe9 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/beta/Filter.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/beta/Filter.java @@ -2,7 +2,10 @@ package com.boydti.fawe.beta; import com.sk89q.worldedit.world.block.BaseBlock; -public interface Filter { +/** + * A filter is an interface used for setting blocks + */ +public interface Filter { /** * Check whether a chunk should be read * @@ -47,4 +50,13 @@ public interface Filter { */ default void finishChunk(final IChunk chunk) { } + + /** + * Fork this for use by another thread + * - Typically filters are simple and don't need to create another copy to be thread safe here + * @return this + */ + default Filter fork() { + return this; + } } diff --git a/worldedit-core/src/main/java/com/boydti/fawe/beta/IBlocks.java b/worldedit-core/src/main/java/com/boydti/fawe/beta/IBlocks.java index 951ec4a9b..064510830 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/beta/IBlocks.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/beta/IBlocks.java @@ -1,5 +1,8 @@ package com.boydti.fawe.beta; +/** + * Shared interface for IGetBlocks and ISetBlocks + */ public interface IBlocks { -} +} \ No newline at end of file diff --git a/worldedit-core/src/main/java/com/boydti/fawe/beta/IChunk.java b/worldedit-core/src/main/java/com/boydti/fawe/beta/IChunk.java index 5472773fc..32206ef75 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/beta/IChunk.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/beta/IChunk.java @@ -5,30 +5,59 @@ import com.sk89q.worldedit.world.block.BaseBlock; import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockStateHolder; -public interface IChunk { - /* set */ - boolean setBiome(int x, int y, int z, BiomeType biome); - - boolean setBlock(int x, int y, int z, BlockStateHolder block); - - /* get */ - BiomeType getBiome(int x, int z); - - BlockState getBlock(int x, int y, int z); - - BaseBlock getFullBlock(int x, int y, int z); - +/** + * Represents a chunk in the queue {@link IQueueExtent} + * Used for getting and setting blocks / biomes / entities + * @param The result type (typically returns true when the chunk is applied) + * @param The IQueue class + */ +public interface IChunk extends Trimable { + /** + * Initialize at the location + * @param extent + * @param X + * @param Z + */ void init(V extent, int X, int Z); - T apply(); - int getX(); int getZ(); + /** + * If the chunk is a delegate, returns it's paren'ts root + * @return root IChunk + */ default IChunk getRoot() { return this; } - void filter(Filter filter); + /** + * @return true if no changes are queued for this chunk + */ + boolean isEmpty(); + + /** + * Apply the queued changes to the world + * @return + */ + T apply(); + + /* set - queues a change */ + boolean setBiome(int x, int y, int z, BiomeType biome); + + boolean setBlock(int x, int y, int z, BlockStateHolder block); + + /** + * Set using the filter + * @param filter + */ + void set(Filter filter); + + /* get - from the world */ + BiomeType getBiome(int x, int z); + + BlockState getBlock(int x, int y, int z); + + BaseBlock getFullBlock(int x, int y, int z); } diff --git a/worldedit-core/src/main/java/com/boydti/fawe/beta/IDelegateChunk.java b/worldedit-core/src/main/java/com/boydti/fawe/beta/IDelegateChunk.java index bc36a19f1..e919fb90e 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/beta/IDelegateChunk.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/beta/IDelegateChunk.java @@ -1,11 +1,16 @@ package com.boydti.fawe.beta; -import com.boydti.fawe.beta.implementation.SingleThreadQueueExtent; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.block.BaseBlock; import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockStateHolder; +/** + * Delegate for IChunk + * @param The result type (typically returns true when the chunk is applied) + * @param The IQueue class + * @param parent class + */ public interface IDelegateChunk> extends IChunk { U getParent(); @@ -57,11 +62,26 @@ public interface IDelegateChunk T findParent(final Class clazz) { IChunk root = getParent(); if (clazz.isAssignableFrom(root.getClass())) return (T) root; @@ -73,7 +93,7 @@ public interface IDelegateChunk result type + * @return result + */ Future submit(IChunk chunk); default boolean setBlock(final int x, final int y, final int z, final BlockStateHolder state) { @@ -36,14 +52,16 @@ public interface IQueueExtent extends Flushable, Trimable { } /** - * Return the IChunk + * Create a new root IChunk object
+ * - Full chunks will be reused, so a more optimized chunk can be returned in that case
+ * - Don't wrap the chunk, that should be done in {@link #wrap(IChunk)} * @param full * @return */ IChunk create(boolean full); /** - * Wrap the chunk object (i.e. for region restrictions etc.) + * Wrap the chunk object (i.e. for region restrictions / limits etc.) * @param root * @return wrapped chunk */ @@ -51,6 +69,10 @@ public interface IQueueExtent extends Flushable, Trimable { return root; } + /** + * Flush all changes to the world + * - Best to call this async so it doesn't hang the server + */ @Override void flush(); } \ No newline at end of file diff --git a/worldedit-core/src/main/java/com/boydti/fawe/beta/ISetBlocks.java b/worldedit-core/src/main/java/com/boydti/fawe/beta/ISetBlocks.java index f4b453e12..f06a4fd6d 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/beta/ISetBlocks.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/beta/ISetBlocks.java @@ -3,8 +3,13 @@ package com.boydti.fawe.beta; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.block.BlockStateHolder; +/** + * Interface for setting blocks + */ public interface ISetBlocks extends IBlocks { boolean setBiome(int x, int y, int z, BiomeType biome); boolean setBlock(int x, int y, int z, BlockStateHolder holder); + + boolean isEmpty(); } diff --git a/worldedit-core/src/main/java/com/boydti/fawe/beta/Trimable.java b/worldedit-core/src/main/java/com/boydti/fawe/beta/Trimable.java index 3cfaffd34..34efe59ae 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/beta/Trimable.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/beta/Trimable.java @@ -1,5 +1,14 @@ package com.boydti.fawe.beta; +/** + * Interface for objects that can be trimmed (memory related)
+ * - Trimming will reduce it's memory footprint + */ public interface Trimable { + /** + * Trim the object, reducing it's memory footprint + * @param aggressive if trimming should be aggressive e.g. Not return early when the first element cannot be trimmed + * @return if this object is empty at the end of the trim, and can therefore be deleted + */ boolean trim(boolean aggressive); } diff --git a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/QueueHandler.java b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/QueueHandler.java index d6dbff8ac..213c903d4 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/QueueHandler.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/QueueHandler.java @@ -4,6 +4,7 @@ import com.boydti.fawe.beta.Filter; import com.boydti.fawe.beta.IQueueExtent; import com.boydti.fawe.beta.Trimable; import com.boydti.fawe.object.collection.IterableThreadLocal; +import com.boydti.fawe.wrappers.WorldWrapper; import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.world.World; @@ -12,8 +13,12 @@ import java.util.HashMap; import java.util.Iterator; import java.util.Map; +/** + * Class which handles all the queues {@link IQueueExtent} + */ public abstract class QueueHandler implements Trimable { private Map> chunkCache = new HashMap<>(); + private IterableThreadLocal pool = new IterableThreadLocal() { @Override public IQueueExtent init() { @@ -21,7 +26,14 @@ public abstract class QueueHandler implements Trimable { } }; - public WorldChunkCache getOrCreate(final World world) { + /** + * Get or create the WorldChunkCache for a world + * @param world + * @return + */ + public WorldChunkCache getOrCreate(World world) { + world = WorldWrapper.unwrap(world); + synchronized (chunkCache) { final WeakReference ref = chunkCache.get(world); if (ref != null) { @@ -38,6 +50,7 @@ public abstract class QueueHandler implements Trimable { public abstract IQueueExtent create(); + @Override public boolean trim(final boolean aggressive) { boolean result = true; synchronized (chunkCache) { diff --git a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/SingleThreadQueueExtent.java b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/SingleThreadQueueExtent.java index fa66b8395..bab38ff70 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/SingleThreadQueueExtent.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/SingleThreadQueueExtent.java @@ -6,6 +6,7 @@ import com.boydti.fawe.beta.implementation.holder.ReferenceChunk; import com.boydti.fawe.config.Settings; import com.boydti.fawe.util.MathMan; import com.boydti.fawe.util.MemUtil; +import com.boydti.fawe.util.SetQueue; import com.boydti.fawe.util.TaskManager; import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; @@ -16,20 +17,37 @@ import java.util.concurrent.ForkJoinTask; import static com.google.common.base.Preconditions.checkNotNull; +/** + * Single threaded implementation for IQueueExtent (still abstract) + * - Does not implement creation of chunks (that has to implemented by the platform e.g. Bukkit) + * + * This queue is reusable {@link #init(WorldChunkCache)} + */ public abstract class SingleThreadQueueExtent implements IQueueExtent { private WorldChunkCache cache; private Thread currentThread; + /** + * Safety check to ensure that the thread being used matches the one being initialized on + * - Can be removed later + */ private void checkThread() { if (Thread.currentThread() != currentThread && currentThread != null) { throw new UnsupportedOperationException("This class must be used from a single thread. Use multiple queues for concurrent operations"); } } + /** + * Get the {@link WorldChunkCache} + * @return + */ public WorldChunkCache getCache() { return cache; } + /** + * Reset the queue + */ protected synchronized void reset() { checkThread(); cache = null; @@ -37,7 +55,7 @@ public abstract class SingleThreadQueueExtent implements IQueueExtent { for (IChunk chunk : chunks.values()) { chunk = chunk.getRoot(); if (chunk != null) { - chunkPool.add(chunk); + CHUNK_POOL.add(chunk); } } chunks.clear(); @@ -47,6 +65,10 @@ public abstract class SingleThreadQueueExtent implements IQueueExtent { currentThread = null; } + /** + * Initialize the queue + * @param cache + */ @Override public synchronized void init(final WorldChunkCache cache) { if (cache != null) { @@ -57,24 +79,32 @@ public abstract class SingleThreadQueueExtent implements IQueueExtent { this.cache = cache; } + // Last access pointers private IChunk lastChunk; private long lastPair = Long.MAX_VALUE; + // Chunks currently being queued / worked on private final Long2ObjectLinkedOpenHashMap chunks = new Long2ObjectLinkedOpenHashMap<>(); - private final ConcurrentLinkedQueue chunkPool = new ConcurrentLinkedQueue<>(); + // Pool discarded chunks for reuse (can safely be cleared by another thread) + private static final ConcurrentLinkedQueue CHUNK_POOL = new ConcurrentLinkedQueue<>(); @Override - public ForkJoinTask submit(final IChunk tmp) { + public ForkJoinTask submit(final IChunk chunk) { + if (chunk.isEmpty()) { + CHUNK_POOL.add(chunk); + return null; + } + // TODO use SetQueue to run in parallel final ForkJoinPool pool = TaskManager.IMP.getPublicForkJoinPool(); return pool.submit(new Callable() { @Override public T call() { - IChunk chunk = tmp; + IChunk tmp = chunk; - T result = chunk.apply(); + T result = tmp.apply(); - chunk = chunk.getRoot(); - if (chunk != null) { - chunkPool.add(chunk); + tmp = tmp.getRoot(); + if (tmp != null) { + CHUNK_POOL.add(tmp); } return result; } @@ -83,7 +113,8 @@ public abstract class SingleThreadQueueExtent implements IQueueExtent { @Override public synchronized boolean trim(boolean aggressive) { - chunkPool.clear(); + // TODO trim individial chunk sections + CHUNK_POOL.clear(); if (Thread.currentThread() == currentThread) { lastChunk = null; lastPair = Long.MAX_VALUE; @@ -94,13 +125,21 @@ public abstract class SingleThreadQueueExtent implements IQueueExtent { } } - private IChunk pool(final int X, final int Z) { - IChunk next = chunkPool.poll(); + /** + * Get a new IChunk from either the pool, or create a new one
+ * + Initialize it at the coordinates + * @param X + * @param Z + * @return IChunk + */ + private IChunk poolOrCreate(final int X, final int Z) { + IChunk next = CHUNK_POOL.poll(); if (next == null) next = create(false); next.init(this, X, Z); return next; } + @Override public final IChunk getCachedChunk(final int X, final int Z) { final long pair = MathMan.pairInt(X, Z); if (pair == lastPair) { @@ -125,7 +164,7 @@ public abstract class SingleThreadQueueExtent implements IQueueExtent { submit(chunk); } } - chunk = pool(X, Z); + chunk = poolOrCreate(X, Z); chunk = wrap(chunk); chunks.put(pair, chunk); diff --git a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/WorldChunkCache.java b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/WorldChunkCache.java index 93ee2fda9..9e5d4461f 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/WorldChunkCache.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/WorldChunkCache.java @@ -10,6 +10,10 @@ import it.unimi.dsi.fastutil.objects.ObjectIterator; import java.lang.ref.WeakReference; import java.util.function.Supplier; +/** + * IGetBlocks may be cached by the WorldChunkCache so that it can be used between multiple IQueueExtents + * - avoids conversion between palette and raw data on every block get + */ public class WorldChunkCache implements Trimable { protected final Long2ObjectLinkedOpenHashMap> getCache; private final World world; @@ -27,6 +31,12 @@ public class WorldChunkCache implements Trimable { return getCache.size(); } + /** + * Get or create the IGetBlocks + * @param index chunk index {@link com.boydti.fawe.util.MathMan#pairInt(int, int)} + * @param provider used to create if it isn't already cached + * @return cached IGetBlocks + */ public synchronized IGetBlocks get(final long index, final Supplier provider) { final WeakReference ref = getCache.get(index); if (ref != null) { @@ -52,7 +62,7 @@ public class WorldChunkCache implements Trimable { result = false; if (!aggressive) return result; synchronized (igb) { - igb.trim(); + igb.trim(aggressive); } } } diff --git a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/holder/ChunkHolder.java b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/holder/ChunkHolder.java index 9204fa951..024da75bf 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/holder/ChunkHolder.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/holder/ChunkHolder.java @@ -15,6 +15,9 @@ import com.sk89q.worldedit.world.block.BlockStateHolder; import java.util.function.Supplier; +/** + * Abstract IChunk class that implements basic get/set blocks + */ public abstract class ChunkHolder implements IChunk, Supplier { private IGetBlocks get; private ISetBlocks set; @@ -30,6 +33,11 @@ public abstract class ChunkHolder implemen this.delegate = delegate; } + @Override + public boolean isEmpty() { + return set == null || set.isEmpty(); + } + public final IGetBlocks cachedGet() { if (get == null) get = newGet(); return get; diff --git a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/holder/DelegateChunk.java b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/holder/DelegateChunk.java index 73ee2db00..a41e8357e 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/holder/DelegateChunk.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/holder/DelegateChunk.java @@ -3,6 +3,10 @@ package com.boydti.fawe.beta.implementation.holder; import com.boydti.fawe.beta.IChunk; import com.boydti.fawe.beta.IDelegateChunk; +/** + * Implementation of IDelegateChunk + * @param + */ public class DelegateChunk implements IDelegateChunk { private T parent; diff --git a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/holder/ReferenceChunk.java b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/holder/ReferenceChunk.java index 956865d6a..6ef9b01a1 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/holder/ReferenceChunk.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/holder/ReferenceChunk.java @@ -6,6 +6,11 @@ import com.boydti.fawe.beta.IQueueExtent; import java.lang.ref.Reference; +/** + * An IChunk may be wrapped by a ReferenceChunk if there is low memory
+ * A reference chunk stores a reference (for garbage collection purposes)
+ * - If it is garbage collected, the {@link FinalizedChunk} logic is run + */ public abstract class ReferenceChunk implements IDelegateChunk { private final Reference ref; diff --git a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/holder/SoftChunk.java b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/holder/SoftChunk.java index d23c28f57..361b7083d 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/holder/SoftChunk.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/holder/SoftChunk.java @@ -6,9 +6,12 @@ import com.boydti.fawe.beta.IQueueExtent; import java.lang.ref.Reference; import java.lang.ref.SoftReference; +/** + * Soft reference implementation of {@link ReferenceChunk} + */ public class SoftChunk extends ReferenceChunk { - public SoftChunk(final IChunk parent, IQueueExtent queueExtent) { + public SoftChunk(final IChunk parent, final IQueueExtent queueExtent) { super(parent, queueExtent); } diff --git a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/holder/WeakChunk.java b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/holder/WeakChunk.java index ad8225bea..a97c915d5 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/holder/WeakChunk.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/holder/WeakChunk.java @@ -1,13 +1,17 @@ package com.boydti.fawe.beta.implementation.holder; import com.boydti.fawe.beta.IChunk; +import com.boydti.fawe.beta.IQueueExtent; import java.lang.ref.Reference; import java.lang.ref.WeakReference; +/** + * Weak reference implementation of {@link ReferenceChunk} + */ public class WeakChunk extends ReferenceChunk { - public WeakChunk(final IChunk parent) { - super(parent); + public WeakChunk(final IChunk parent, final IQueueExtent queueExtent) { + super(parent, queueExtent); } @Override