diff --git a/bukkit/src/main/java/com/boydti/fawe/bukkit/v0/BukkitQueue_0.java b/bukkit/src/main/java/com/boydti/fawe/bukkit/v0/BukkitQueue_0.java index ca0d51cd..4e1629c0 100644 --- a/bukkit/src/main/java/com/boydti/fawe/bukkit/v0/BukkitQueue_0.java +++ b/bukkit/src/main/java/com/boydti/fawe/bukkit/v0/BukkitQueue_0.java @@ -3,13 +3,10 @@ package com.boydti.fawe.bukkit.v0; import com.boydti.fawe.Fawe; import com.boydti.fawe.object.FaweChunk; import com.boydti.fawe.util.FaweQueue; -import com.boydti.fawe.util.SetQueue; import com.boydti.fawe.util.TaskManager; import com.sk89q.worldedit.world.biome.BaseBiome; import java.util.ArrayDeque; import java.util.Collection; -import java.util.Iterator; -import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import org.bukkit.Bukkit; import org.bukkit.Chunk; @@ -28,6 +25,7 @@ public abstract class BukkitQueue_0 extends FaweQueue implements Listener { * Map of chunks in the queue */ private ConcurrentHashMap> blocks = new ConcurrentHashMap<>(); + private ArrayDeque> chunks = new ArrayDeque<>(); public BukkitQueue_0(String world) { super(world); @@ -41,7 +39,10 @@ public abstract class BukkitQueue_0 extends FaweQueue implements Listener { @Override public boolean isChunkLoaded(int x, int z) { - return Bukkit.getWorld(world).isChunkLoaded(x, z); + if (bukkitWorld == null) { + bukkitWorld = Bukkit.getServer().getWorld(world); + } + return bukkitWorld.isChunkLoaded(x, z); // long id = ((long) x << 32) | (z & 0xFFFFFFFFL); // HashSet map = this.loaded.get(world); // if (map != null) { @@ -67,6 +68,7 @@ public abstract class BukkitQueue_0 extends FaweQueue implements Listener { result.addTask(runnable); FaweChunk previous = this.blocks.put(pair, result); if (previous == null) { + chunks.add(result); return; } this.blocks.put(pair, previous); @@ -87,6 +89,7 @@ public abstract class BukkitQueue_0 extends FaweQueue implements Listener { result.setBlock(x & 15, y, z & 15, id, data); FaweChunk previous = this.blocks.put(pair, result); if (previous == null) { + chunks.add(result); return true; } this.blocks.put(pair, previous); @@ -106,6 +109,8 @@ public abstract class BukkitQueue_0 extends FaweQueue implements Listener { if (previous != null) { this.blocks.put(pair, previous); result = previous; + } else { + chunks.add(result); } } result.setBiome(x & 15, z & 15, biome); @@ -118,18 +123,23 @@ public abstract class BukkitQueue_0 extends FaweQueue implements Listener { if (this.blocks.size() == 0) { return null; } - Iterator>> iter = this.blocks.entrySet().iterator(); - FaweChunk toReturn = iter.next().getValue(); - if (SetQueue.IMP.isWaiting()) { - return null; + synchronized (blocks) { + FaweChunk chunk = chunks.poll(); + if (chunk != null) { + blocks.remove(chunk.longHash()); + this.execute(chunk); + return chunk; + } } - iter.remove(); - this.execute(toReturn); - return toReturn; } catch (Throwable e) { e.printStackTrace(); - return null; } + return null; + } + + @Override + public int size() { + return chunks.size(); } private ArrayDeque> toUpdate = new ArrayDeque<>(); @@ -156,7 +166,11 @@ public abstract class BukkitQueue_0 extends FaweQueue implements Listener { @Override public void setChunk(FaweChunk chunk) { - this.blocks.put(chunk.longHash(), (FaweChunk) chunk); + FaweChunk previous = this.blocks.put(chunk.longHash(), (FaweChunk) chunk); + if (previous != null) { + chunks.remove(previous); + } + chunks.add((FaweChunk) chunk); } public abstract Collection> sendChunk(Collection> fcs); diff --git a/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_8/BukkitQueue_1_8.java b/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_8/BukkitQueue_1_8.java index 24300e38..93acb8ab 100644 --- a/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_8/BukkitQueue_1_8.java +++ b/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_8/BukkitQueue_1_8.java @@ -155,6 +155,9 @@ public class BukkitQueue_1_8 extends BukkitQueue_0 { } else if (cy == lcy) { return ls != null ? ls[FaweCache.CACHE_J[y][x & 15][z & 15]] : 0; } + if (lc == null) { + return 0; + } Object storage = ((Object[]) fieldSections.of(lc).get())[cy]; if (storage == null) { ls = null; diff --git a/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_9/BukkitQueue_1_9.java b/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_9/BukkitQueue_1_9.java index ee4eee4f..4cabb866 100644 --- a/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_9/BukkitQueue_1_9.java +++ b/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_9/BukkitQueue_1_9.java @@ -149,6 +149,9 @@ public class BukkitQueue_1_9 extends BukkitQueue_0 { } lc = methodGetType.of(methodGetHandleChunk.of(bukkitWorld.getChunkAt(cx, cz)).call()); } + if (lc == null) { + return 0; + } int combined = (int) methodGetCombinedId.call(lc.call(x & 15, y, z & 15)); return ((combined & 4095) << 4) + (combined >> 12); } diff --git a/core/src/main/java/com/boydti/fawe/FaweAPI.java b/core/src/main/java/com/boydti/fawe/FaweAPI.java index 591ddb56..49680286 100644 --- a/core/src/main/java/com/boydti/fawe/FaweAPI.java +++ b/core/src/main/java/com/boydti/fawe/FaweAPI.java @@ -42,13 +42,13 @@ public class FaweAPI { } public static void fixLighting(String world, int x, int z, final boolean fixAll) { - FaweQueue queue = SetQueue.IMP.getNewQueue(world); + FaweQueue queue = SetQueue.IMP.getNewQueue(world, false); queue.fixLighting(queue.getChunk(x, z), fixAll); } public static void fixLighting(final Chunk chunk, final boolean fixAll) { - FaweQueue queue = SetQueue.IMP.getNewQueue(chunk.getWorld().getName()); + FaweQueue queue = SetQueue.IMP.getNewQueue(chunk.getWorld().getName(), false); queue.fixLighting(queue.getChunk(chunk.getX(), chunk.getZ()), fixAll); } @@ -141,7 +141,7 @@ public class FaweAPI { tagMap = null; tag = null; - FaweQueue queue = SetQueue.IMP.getNewQueue(loc.world); + FaweQueue queue = SetQueue.IMP.getNewQueue(loc.world, true); for (int y = 0; y < height; y++) { final int yy = y_offset + y; diff --git a/core/src/main/java/com/boydti/fawe/config/Settings.java b/core/src/main/java/com/boydti/fawe/config/Settings.java index e72fd3f1..d865a666 100644 --- a/core/src/main/java/com/boydti/fawe/config/Settings.java +++ b/core/src/main/java/com/boydti/fawe/config/Settings.java @@ -27,6 +27,9 @@ public class Settings { public static int CHUNK_WAIT = 0; public static boolean REGION_RESTRICTIONS = true; public static int ALLOCATE = 0; + public static int QUEUE_SIZE = 64; + public static int QUEUE_MAX_WAIT = 1000; + public static int QUEUE_DISCARD_AFTER = 60000; public static HashMap limits; @@ -73,6 +76,9 @@ public class Settings { options.put("history.buffer-size", BUFFER_SIZE); options.put("region-restrictions", REGION_RESTRICTIONS); options.put("queue.extra-time-ms", ALLOCATE); + options.put("queue.target-size", QUEUE_SIZE); + options.put("queue.max-wait-ms", QUEUE_MAX_WAIT); + options.put("queue.discard-after-ms", QUEUE_DISCARD_AFTER); options.put("metrics", METRICS); // Default limit @@ -85,8 +91,6 @@ public class Settings { FaweLimit limit = new FaweLimit(); limit.load(config.getConfigurationSection("limits." + key), defaultLimit, false); } - - for (final Entry node : options.entrySet()) { if (!config.contains(node.getKey())) { config.set(node.getKey(), node.getValue()); @@ -104,6 +108,9 @@ public class Settings { BUFFER_SIZE = config.getInt("history.buffer-size", BUFFER_SIZE); CHUNK_WAIT = config.getInt("history.chunk-wait-ms"); ALLOCATE = config.getInt("queue.extra-time-ms"); + QUEUE_SIZE = config.getInt("queue.target-size"); + QUEUE_MAX_WAIT = config.getInt("queue.max-wait-ms"); + QUEUE_DISCARD_AFTER = config.getInt("queue.discard-after-ms"); if (STORE_HISTORY_ON_DISK = config.getBoolean("history.use-disk")) { LocalSession.MAX_HISTORY_SIZE = Integer.MAX_VALUE; } diff --git a/core/src/main/java/com/boydti/fawe/util/FaweQueue.java b/core/src/main/java/com/boydti/fawe/util/FaweQueue.java index 42affefa..9e3e62b4 100644 --- a/core/src/main/java/com/boydti/fawe/util/FaweQueue.java +++ b/core/src/main/java/com/boydti/fawe/util/FaweQueue.java @@ -55,6 +55,8 @@ public abstract class FaweQueue { public abstract int getCombinedId4Data(int x, int y, int z); + public abstract int size(); + public void enqueue() { SetQueue.IMP.enqueue(this); } diff --git a/core/src/main/java/com/boydti/fawe/util/SetQueue.java b/core/src/main/java/com/boydti/fawe/util/SetQueue.java index 392caf37..563f03c8 100644 --- a/core/src/main/java/com/boydti/fawe/util/SetQueue.java +++ b/core/src/main/java/com/boydti/fawe/util/SetQueue.java @@ -6,7 +6,6 @@ import com.boydti.fawe.object.FaweChunk; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; public class SetQueue { @@ -15,19 +14,15 @@ public class SetQueue { */ public static final SetQueue IMP = new SetQueue(); - public final ArrayDeque queues; + public final ArrayDeque activeQueues; + public final ArrayDeque inactiveQueues; /** - * Track the time in ticks - */ - private final AtomicInteger time_waiting = new AtomicInteger(2); - private final AtomicInteger time_current = new AtomicInteger(0); - - /** - * Used to calculate elapsed time in milliseconds and ensure block placement doesn't lag the server + * Used to calculate elapsed time in milliseconds and ensure block placement doesn't lag the server */ private long last; - private long last2; + private long secondLast; + private long lastSuccess; /** * A queue of tasks that will run when the queue is empty @@ -36,7 +31,8 @@ public class SetQueue { public SetQueue() { - queues = new ArrayDeque(); + activeQueues = new ArrayDeque(); + inactiveQueues = new ArrayDeque<>(); TaskManager.IMP.repeat(new Runnable() { @Override public void run() { @@ -52,51 +48,84 @@ public class SetQueue { if (SetQueue.this.forceChunkSet()) { System.gc(); } else { - SetQueue.this.time_current.incrementAndGet(); SetQueue.this.tasks(); } return; } } - final long free = Settings.ALLOCATE + 50 + Math.min((50 + SetQueue.this.last) - (SetQueue.this.last = System.currentTimeMillis()), SetQueue.this.last2 - System.currentTimeMillis()); - SetQueue.this.time_current.incrementAndGet(); + final long free = Settings.ALLOCATE + 50 + Math.min((50 + SetQueue.this.last) - (SetQueue.this.last = System.currentTimeMillis()), SetQueue.this.secondLast - System.currentTimeMillis()); do { - if (SetQueue.this.isWaiting()) { - return; - } final FaweChunk current = next(); if (current == null) { - SetQueue.this.time_waiting.set(Math.max(SetQueue.this.time_waiting.get(), SetQueue.this.time_current.get() - 2)); + lastSuccess = last; SetQueue.this.tasks(); return; } - } while (((SetQueue.this.last2 = System.currentTimeMillis()) - SetQueue.this.last) < free); - SetQueue.this.time_waiting.set(SetQueue.this.time_current.get() - 1); + } while (((SetQueue.this.secondLast = System.currentTimeMillis()) - SetQueue.this.last) < free); } }, 1); } public void enqueue(FaweQueue queue) { - queues.add(queue); + inactiveQueues.remove(queue); + activeQueues.add(queue); } public List getQueues() { - return new ArrayList<>(queues); + return new ArrayList<>(activeQueues); } - public FaweQueue getNewQueue(String world) { - return Fawe.imp().getNewQueue(world); + public FaweQueue getNewQueue(String world, boolean autoqueue) { + FaweQueue queue = Fawe.imp().getNewQueue(world); + if (autoqueue) { + inactiveQueues.add(queue); + } + return queue; } public FaweChunk next() { - while (queues.size() > 0) { - FaweQueue queue = queues.poll(); + while (activeQueues.size() > 0) { + FaweQueue queue = activeQueues.poll(); final FaweChunk set = queue.next(); if (set != null) { - queues.add(queue); + activeQueues.add(queue); return set; } } + if (inactiveQueues.size() > 0) { + ArrayList tmp = new ArrayList<>(inactiveQueues); + if (Settings.QUEUE_MAX_WAIT != -1) { + long now = System.currentTimeMillis(); + long diff = now - lastSuccess; + if (diff > Settings.QUEUE_MAX_WAIT) { + for (FaweQueue queue : tmp) { + FaweChunk result = queue.next(); + if (result != null) { + return result; + } + } + if (diff > Settings.QUEUE_DISCARD_AFTER) { + // These edits never finished + inactiveQueues.clear(); + } + return null; + } + } + if (Settings.QUEUE_SIZE != -1) { + int total = 0; + for (FaweQueue queue : tmp) { + total += queue.size(); + } + if (total > Settings.QUEUE_SIZE) { + for (FaweQueue queue : tmp) { + FaweChunk result = queue.next(); + if (result != null) { + return result; + } + } + } + } + } return null; } @@ -104,16 +133,8 @@ public class SetQueue { return next() != null; } - public boolean isWaiting() { - return this.time_waiting.get() >= this.time_current.get(); - } - public boolean isDone() { - return (this.time_waiting.get() + 1) < this.time_current.get(); - } - - public void setWaiting() { - this.time_waiting.set(this.time_current.get() + 1); + return activeQueues.size() == 0 && inactiveQueues.size() == 0; } public boolean addTask(final Runnable whenDone) { diff --git a/core/src/main/java/com/sk89q/worldedit/EditSession.java b/core/src/main/java/com/sk89q/worldedit/EditSession.java index 725cbcf3..7de060e0 100644 --- a/core/src/main/java/com/sk89q/worldedit/EditSession.java +++ b/core/src/main/java/com/sk89q/worldedit/EditSession.java @@ -232,7 +232,7 @@ public class EditSession implements Extent { } final Actor actor = event.getActor(); - this.queue = SetQueue.IMP.getNewQueue(world.getName()); + this.queue = SetQueue.IMP.getNewQueue(world.getName(), true); this.world = (world = new WorldWrapper((AbstractWorld) world)); this.wrapper = Fawe.imp().getEditSessionWrapper(this); // Not a player; bypass history diff --git a/core/src/main/java/com/sk89q/worldedit/regions/CuboidRegion.java b/core/src/main/java/com/sk89q/worldedit/regions/CuboidRegion.java index 125b0b98..16511b0e 100644 --- a/core/src/main/java/com/sk89q/worldedit/regions/CuboidRegion.java +++ b/core/src/main/java/com/sk89q/worldedit/regions/CuboidRegion.java @@ -348,36 +348,62 @@ public class CuboidRegion extends AbstractRegion implements FlatRegion { private Vector min = getMinimumPoint(); private Vector max = getMaximumPoint(); - int minX = min.getBlockX(); - int minY = min.getBlockY(); - int minZ = min.getBlockZ(); + int bx = min.getBlockX(); + int by = min.getBlockY(); + int bz = min.getBlockZ(); - int maxX = max.getBlockX(); - int maxY = max.getBlockY(); - int maxZ = max.getBlockZ(); + int tx = max.getBlockX(); + int ty = max.getBlockY(); + int tz = max.getBlockZ(); - private int nextX = min.getBlockX(); - private int nextY = min.getBlockY(); - private int nextZ = min.getBlockZ(); + private int x = min.getBlockX(); + private int y = min.getBlockY(); + private int z = min.getBlockZ(); + + int cx = x >> 4; + int cz = z >> 4; + int cbx = Math.max(bx, cx << 4); + int cbz = Math.max(bz, cz << 4); + int ctx = Math.min(tx, 15 + (cx << 4)); + int ctz = Math.min(tz, 15 + (cz << 4)); + + public boolean hasNext = true; @Override public boolean hasNext() { - return (nextX != Integer.MIN_VALUE); + return hasNext; } @Override public BlockVector next() { - if (!hasNext()) throw new java.util.NoSuchElementException(); - v.x = nextX; - v.y = nextY; - v.z = nextZ; - if (++nextX > maxX) { - nextX = minX; - if (++nextY > maxY) { - nextY = minY; - if (++nextZ > maxZ) { - nextX = Integer.MIN_VALUE; + v.x = x; + v.y = y; + v.z = z; + if (++x > ctx) { + if (++z > ctz) { + if (++y > ty) { + y = by; + if (x > tx) { + x = bx; + if (z > tz) { + hasNext = false; + return v; + } + } else { + z = cbz; + } + cx = x >> 4; + cz = z >> 4; + cbx = Math.max(bx, cx << 4); + cbz = Math.max(bz, cz << 4); + ctx = Math.min(tx, 15 + (cx << 4)); + ctz = Math.min(tz, 15 + (cz << 4)); + } else { + x = cbx; + z = cbz; } + } else { + x = cbx; } } return v; diff --git a/forge/src/main/java/com/boydti/fawe/forge/v0/SpongeQueue_0.java b/forge/src/main/java/com/boydti/fawe/forge/v0/SpongeQueue_0.java index 1271f9c5..b1a52f2e 100644 --- a/forge/src/main/java/com/boydti/fawe/forge/v0/SpongeQueue_0.java +++ b/forge/src/main/java/com/boydti/fawe/forge/v0/SpongeQueue_0.java @@ -2,12 +2,9 @@ package com.boydti.fawe.forge.v0; import com.boydti.fawe.object.FaweChunk; import com.boydti.fawe.util.FaweQueue; -import com.boydti.fawe.util.SetQueue; import com.sk89q.worldedit.world.biome.BaseBiome; import java.util.ArrayDeque; import java.util.Collection; -import java.util.Iterator; -import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.spongepowered.api.Sponge; import org.spongepowered.api.world.Chunk; @@ -22,6 +19,7 @@ public abstract class SpongeQueue_0 extends FaweQueue { * Map of chunks in the queue */ private final ConcurrentHashMap> blocks = new ConcurrentHashMap<>(); + private ArrayDeque> chunks = new ArrayDeque<>(); public SpongeQueue_0(String world) { super(world); @@ -43,6 +41,7 @@ public abstract class SpongeQueue_0 extends FaweQueue { result.addTask(runnable); final FaweChunk previous = this.blocks.put(pair, result); if (previous == null) { + chunks.add(result); return; } this.blocks.put(pair, previous); @@ -63,6 +62,7 @@ public abstract class SpongeQueue_0 extends FaweQueue { result.setBlock(x & 15, y, z & 15, id, data); final FaweChunk previous = this.blocks.put(pair, result); if (previous == null) { + chunks.add(result); return true; } this.blocks.put(pair, previous); @@ -94,18 +94,23 @@ public abstract class SpongeQueue_0 extends FaweQueue { if (this.blocks.size() == 0) { return null; } - final Iterator>> iter = this.blocks.entrySet().iterator(); - final FaweChunk toReturn = iter.next().getValue(); - if (SetQueue.IMP.isWaiting()) { - return null; + synchronized (blocks) { + FaweChunk chunk = chunks.poll(); + if (chunk != null) { + blocks.remove(chunk.longHash()); + this.execute(chunk); + return chunk; + } } - iter.remove(); - this.execute(toReturn); - return toReturn; - } catch (final Throwable e) { + } catch (Throwable e) { e.printStackTrace(); - return null; } + return null; + } + + @Override + public int size() { + return chunks.size(); } private final ArrayDeque> toUpdate = new ArrayDeque<>(); @@ -132,7 +137,11 @@ public abstract class SpongeQueue_0 extends FaweQueue { @Override public void setChunk(final FaweChunk chunk) { - this.blocks.put(chunk.longHash(), (FaweChunk) chunk); + FaweChunk previous = this.blocks.put(chunk.longHash(), (FaweChunk) chunk); + if (previous != null) { + chunks.remove(previous); + } + chunks.add((FaweChunk) chunk); } public abstract Collection> sendChunk(final Collection> fcs);