The CuboidRegion class will now queue blocks in layers for a chunk
before moving onto the next chunk. This results in higher cache hits for
history enabled queues. It also allows the block placer to start earlier
during preprocessing with edits affecting > 64 (configurable) chunks.

Note: with history on disk enabled, this means near unlimited sized
edits (for certain commands) might be feasible.
This commit is contained in:
Jesse Boyd 2016-04-15 00:18:22 +10:00
parent caa0e475ad
commit 5097f0cf63
10 changed files with 173 additions and 88 deletions

View File

@ -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<Long, FaweChunk<Chunk>> blocks = new ConcurrentHashMap<>();
private ArrayDeque<FaweChunk<Chunk>> 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<Long> 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<Chunk> 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<Chunk> 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<Entry<Long, FaweChunk<Chunk>>> iter = this.blocks.entrySet().iterator();
FaweChunk<Chunk> toReturn = iter.next().getValue();
if (SetQueue.IMP.isWaiting()) {
return null;
synchronized (blocks) {
FaweChunk<Chunk> 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<FaweChunk<Chunk>> 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>) chunk);
FaweChunk<Chunk> previous = this.blocks.put(chunk.longHash(), (FaweChunk<Chunk>) chunk);
if (previous != null) {
chunks.remove(previous);
}
chunks.add((FaweChunk<Chunk>) chunk);
}
public abstract Collection<FaweChunk<Chunk>> sendChunk(Collection<FaweChunk<Chunk>> fcs);

View File

@ -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;

View File

@ -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);
}

View File

@ -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;

View File

@ -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<String, FaweLimit> 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<String, Object> 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;
}

View File

@ -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);
}

View File

@ -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<FaweQueue> queues;
public final ArrayDeque<FaweQueue> activeQueues;
public final ArrayDeque<FaweQueue> 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<FaweQueue> 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<FaweQueue> 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) {

View File

@ -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

View File

@ -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;

View File

@ -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<Long, FaweChunk<Chunk>> blocks = new ConcurrentHashMap<>();
private ArrayDeque<FaweChunk<Chunk>> 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<Chunk> 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<Chunk> 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<Map.Entry<Long, FaweChunk<Chunk>>> iter = this.blocks.entrySet().iterator();
final FaweChunk<Chunk> toReturn = iter.next().getValue();
if (SetQueue.IMP.isWaiting()) {
return null;
synchronized (blocks) {
FaweChunk<Chunk> 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<FaweChunk<Chunk>> 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>) chunk);
FaweChunk<Chunk> previous = this.blocks.put(chunk.longHash(), (FaweChunk<Chunk>) chunk);
if (previous != null) {
chunks.remove(previous);
}
chunks.add((FaweChunk<Chunk>) chunk);
}
public abstract Collection<FaweChunk<Chunk>> sendChunk(final Collection<FaweChunk<Chunk>> fcs);