Added first anvil command (countall)
Added parallelized filter for MCAQueue
Start on some optimizations for block queuing (reduce object creation +
casting)
Fix shortcut for set and fastmode
This commit is contained in:
Jesse Boyd 2016-08-25 00:34:07 +10:00
parent dd181d9378
commit 151cbf5679
15 changed files with 919 additions and 98 deletions

View File

@ -1,24 +1,25 @@
package com.boydti.fawe.command;
import com.boydti.fawe.FaweCache;
import com.boydti.fawe.config.BBC;
import com.boydti.fawe.jnbt.anvil.MCAChunk;
import com.boydti.fawe.jnbt.anvil.MCAFilter;
import com.boydti.fawe.jnbt.anvil.MCAQueue;
import com.boydti.fawe.object.number.LongAdder;
import com.sk89q.minecraft.util.commands.Command;
import com.sk89q.minecraft.util.commands.CommandPermissions;
import com.sk89q.minecraft.util.commands.Logging;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.function.mask.ExistingBlockMask;
import com.sk89q.worldedit.function.mask.Mask;
import com.sk89q.worldedit.function.pattern.Pattern;
import com.sk89q.worldedit.function.pattern.Patterns;
import com.sk89q.worldedit.internal.annotation.Selection;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.util.command.binding.Switch;
import com.sk89q.worldedit.util.command.parametric.Optional;
import java.io.File;
import java.util.Set;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.sk89q.minecraft.util.commands.Logging.LogMode.REGION;
public class AnvilCommands {
@ -35,21 +36,78 @@ public class AnvilCommands {
}
@Command(
aliases = { "/replaceall", "/rea", "/repall" },
usage = "[from-block] <to-block>",
desc = "Replace all blocks in the selection with another",
flags = "f",
min = 1,
max = 2
aliases = { "/countall" },
usage = "<folder> [hasSky] <id>",
desc = "Count all blocks in a world",
flags = "d",
min = 2,
max = 3
)
@CommandPermissions("worldedit.region.replace")
@Logging(REGION)
public void replace(Player player, EditSession editSession, @Selection Region region, @Optional Mask from, Pattern to) throws WorldEditException {
if (from == null) {
from = new ExistingBlockMask(editSession);
@CommandPermissions("worldedit.anvil.countallstone")
public void countAll(Player player, EditSession editSession, String folder, @Optional("true") boolean hasSky, String arg, @Switch('d') boolean useData) throws WorldEditException {
File root = new File(folder + File.separator + "region");
MCAQueue queue = new MCAQueue(folder, root, hasSky);
final LongAdder count = new LongAdder();
if (arg.contains(":")) {
useData = true; //override d flag, if they specified data they want it
}
int affected = editSession.replaceBlocks(region, from, Patterns.wrap(to));
BBC.VISITOR_BLOCK.send(player, affected);
Set<BaseBlock> searchBlocks = worldEdit.getBlocks(player, arg, true);
final boolean[] allowedId = new boolean[FaweCache.getId(Character.MAX_VALUE)];
for (BaseBlock block : searchBlocks) {
allowedId[block.getId()] = true;
}
MCAFilter filter;
if (useData) { // Optimize for both cases
final boolean[] allowed = new boolean[Character.MAX_VALUE];
for (BaseBlock block : searchBlocks) {
allowed[FaweCache.getCombined(block)] = true;
}
filter = new MCAFilter() {
@Override
public MCAChunk applyChunk(MCAChunk chunk) {
for (int layer = 0; layer < chunk.ids.length; layer++) {
byte[] ids = chunk.ids[layer];
if (ids == null) {
continue;
}
byte[] datas = chunk.data[layer];
for (int i = 0; i < ids.length; i++) {
int id = ids[i] & 0xFF;
if (!allowedId[id]) {
continue;
}
int combined = (id) << 4;
if (FaweCache.hasData(id)) {
combined += chunk.getNibble(i, datas);
}
if (allowed[combined]) {
count.add(1);
}
}
}
return null;
}
};
} else {
filter = new MCAFilter() {
@Override
public MCAChunk applyChunk(MCAChunk chunk) {
for (int layer = 0; layer < chunk.ids.length; layer++) {
byte[] ids = chunk.ids[layer];
if (ids != null) {
for (byte i : ids) {
if (allowedId[i & 0xFF]) {
count.add(1);
}
}
}
}
return null;
}
};
}
queue.filterWorld(filter);
BBC.SELECTION_COUNT.send(player, count.longValue());
}
}

View File

@ -1,4 +0,0 @@
package com.boydti.fawe.jnbt.anvil;
public class AnvilRegion {
}

View File

@ -197,6 +197,11 @@ public class MCAChunk extends FaweChunk<Void> {
return modified;
}
@Deprecated
public void setModified() {
this.modified = true;
}
@Override
public int getBitMask() {
int bitMask = 0;
@ -214,7 +219,11 @@ public class MCAChunk extends FaweChunk<Void> {
byte i = MathMan.pair16((byte) x, (byte) z);
byte j = (byte) y;
BytePair pair = new BytePair(i, j);
tiles.put(pair, tile);
if (tile != null) {
tiles.put(pair, tile);
} else {
tiles.remove(pair);
}
}
@Override
@ -349,16 +358,16 @@ public class MCAChunk extends FaweChunk<Void> {
}
}
private int getNibble(int index, byte[] array) {
public int getNibble(int index, byte[] array) {
int indexShift = index >> 1;
if((index & 1) == 0) {
return array[index] & 15;
return array[indexShift] & 15;
} else {
return array[index] >> 4 & 15;
return array[indexShift] >> 4 & 15;
}
}
private void setNibble(int index, byte[] array, int value) {
public void setNibble(int index, byte[] array, int value) {
int indexShift = index >> 1;
if((index & 1) == 0) {
array[indexShift] = (byte)(array[indexShift] & 240 | value & 15);

View File

@ -18,6 +18,7 @@ import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
@ -33,8 +34,9 @@ import java.util.zip.InflaterInputStream;
* e.g.: `.Level.Entities.#` (Starts with a . as the root tag is unnamed)
*/
public class MCAFile {
private final File file;
private final BufferedRandomAccessFile raf;
private final RandomAccessFile raf;
private final byte[] locations;
private final FaweQueue queue;
private Field fieldBuf1;
@ -58,7 +60,7 @@ public class MCAFile {
}
this.locations = new byte[4096];
this.raf = new BufferedRandomAccessFile(file, "rw", Settings.HISTORY.BUFFER_SIZE);
raf.read(locations);
raf.readFully(locations);
fieldBuf1 = BufferedInputStream.class.getDeclaredField("buf");
fieldBuf1.setAccessible(true);
fieldBuf2 = InflaterInputStream.class.getDeclaredField("buf");
@ -159,9 +161,9 @@ public class MCAFile {
private byte[] getChunkCompressedBytes(int offset) throws IOException{
raf.seek(offset);
int size = raf.readInt();
int compression = raf.readByte();
int compression = raf.read();
byte[] data = new byte[size];
raf.read(data);
raf.readFully(data);
return data;
}
@ -172,7 +174,7 @@ public class MCAFile {
raf.setLength(offset + len);
}
raf.writeInt(data.length);
raf.writeByte(2);
raf.write(2);
raf.write(data);
}
@ -364,7 +366,9 @@ public class MCAFile {
}
start += newSize << 12;
}
raf.flush();
if (raf instanceof BufferedRandomAccessFile) {
((BufferedRandomAccessFile) raf).flush();
}
} catch (Throwable e) {
e.printStackTrace();
}

View File

@ -0,0 +1,23 @@
package com.boydti.fawe.jnbt.anvil;
import com.sk89q.worldedit.blocks.BaseBlock;
public class MCAFilter {
public boolean appliesFile(int mcaX, int mcaZ) {
return true;
}
public MCAFile applyFile(MCAFile file) {
return file;
}
public boolean appliesChunk(int cx, int cz) {
return true;
}
public MCAChunk applyChunk(MCAChunk chunk) {
return chunk;
}
public void applyBlock(int x, int y, int z, BaseBlock block) {}
}

View File

@ -1,16 +1,20 @@
package com.boydti.fawe.jnbt.anvil;
import com.boydti.fawe.FaweCache;
import com.boydti.fawe.example.CharFaweChunk;
import com.boydti.fawe.example.NMSMappedFaweQueue;
import com.boydti.fawe.object.FaweChunk;
import com.boydti.fawe.object.FaweQueue;
import com.boydti.fawe.object.RunnableVal;
import com.boydti.fawe.object.RunnableVal4;
import com.boydti.fawe.util.TaskManager;
import com.sk89q.jnbt.CompoundTag;
import java.io.File;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
public class MCAQueue extends NMSMappedFaweQueue<FaweQueue, FaweChunk, FaweChunk, FaweChunk> {
@ -37,6 +41,77 @@ public class MCAQueue extends NMSMappedFaweQueue<FaweQueue, FaweChunk, FaweChunk
this.hasSky = hasSky;
}
public void filterWorld(final MCAFilter filter) {
File folder = getSaveFolder();
for (File file : folder.listFiles()) {
try {
String name = file.getName();
String[] split = name.split("\\.");
final int mcaX = Integer.parseInt(split[1]);
final int mcaZ = Integer.parseInt(split[2]);
if (filter.appliesFile(mcaX, mcaZ)) {
MCAFile mcaFile = new MCAFile(this, file);
final MCAFile finalFile = filter.applyFile(mcaFile);
if (finalFile != null) {
Runnable run = new Runnable() {
@Override
public void run() {
final MutableMCABackedBaseBlock mutableBlock = new MutableMCABackedBaseBlock();
final int cbx = mcaX << 5;
final int cbz = mcaZ << 5;
finalFile.forEachChunk(new RunnableVal4<Integer, Integer, Integer, Integer>() {
@Override
public void run(final Integer rcx, final Integer rcz, Integer offset, Integer size) {
int cx = cbx + rcx;
int cz = cbz + rcz;
if (filter.appliesChunk(cx, cz)) {
try {
MCAChunk chunk = finalFile.getChunk(cx, cz);
try {
chunk = filter.applyChunk(chunk);
if (chunk != null) {
mutableBlock.setChunk(chunk);
int bx = cx << 4;
int bz = cz << 4;
for (int layer = 0; layer < chunk.ids.length; layer++) {
if (chunk.doesSectionExist(layer)) {
mutableBlock.setArrays(layer);
int yStart = layer << 4;
for (int y = yStart; y < yStart + 16; y++) {
short[][] cacheY = FaweCache.CACHE_J[y];
for (int z = bz; z < bz + 16; z++) {
int rz = z & 15;
short[] cacheYZ = cacheY[rz];
for (int x = 0; x < 16; x++) {
int rx = x & 15;
short index = cacheYZ[rx];
mutableBlock.setIndex(rx, y, rz, index);
filter.applyBlock(x, y, z, mutableBlock);
}
}
}
}
}
}
} catch (Throwable e) {
e.printStackTrace();
}
} catch (Throwable e) {
System.out.println("Failed to load: r." + mcaX + "." + mcaZ + ".mca -> (local) " + rcx + "," + rcz);
}
}
}
});
}
};
TaskManager.IMP.getPublicForkJoinPool().submit(run);
}
}
} catch (Throwable ignore) {}
}
TaskManager.IMP.getPublicForkJoinPool().awaitQuiescence(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
}
@Override
public void relight(int x, int y, int z) {
throw new UnsupportedOperationException("Not supported");

View File

@ -8,7 +8,6 @@ import com.boydti.fawe.object.FaweQueue;
import com.boydti.fawe.object.RunnableVal;
import com.boydti.fawe.object.exception.FaweException;
import com.boydti.fawe.util.MathMan;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
@ -35,15 +34,6 @@ public class MCAQueueMap implements IFaweQueueMap {
private int lastFileX = Integer.MIN_VALUE;
private int lastFileZ = Integer.MIN_VALUE;
public void forEachMCAFile(RunnableVal<MCAFile> onEach) {
File folder = queue.getSaveFolder();
for (File file : folder.listFiles()) {
try {
onEach.run(new MCAFile(queue, file));
} catch (Throwable ignore) {}
}
}
public MCAFile getMCAFile(int cx, int cz) {
int mcaX = cx >> 5;
int mcaZ = cz >> 5;

View File

@ -0,0 +1,88 @@
package com.boydti.fawe.jnbt.anvil;
import com.boydti.fawe.FaweCache;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.worldedit.blocks.BaseBlock;
import javax.annotation.Nullable;
/**
* I'm aware this isn't OOP, but object creation is expensive
*/
public class MutableMCABackedBaseBlock extends BaseBlock {
private MCAChunk chunk;
private byte[] data;
private byte[] ids;
private int index;
private int x;
private int y;
private int z;
public MutableMCABackedBaseBlock() {
super(0);
}
public void setChunk(MCAChunk chunk) {
this.chunk = chunk;
}
public void setArrays(int layer) {
data = chunk.data[layer];
ids = chunk.ids[layer];
}
public void setIndex(int x, int y, int z, int index) {
this.x = x;
this.y = y;
this.z = z;
this.index = index;
}
@Override
public int getId() {
return ids[index] & 0xFF;
}
@Override
public int getData() {
if (!FaweCache.hasData(ids[index])) {
return 0;
} else {
int indexShift = index >> 1;
if ((index & 1) == 0) {
return data[index] & 15;
} else {
return data[index] >> 4 & 15;
}
}
}
@Nullable
@Override
public CompoundTag getNbtData() {
return chunk.getTile(x, y, z);
}
@Override
public void setId(int id) {
ids[index] = (byte) id;
chunk.setModified();
}
@Override
public void setData(int value) {
int indexShift = index >> 1;
if((index & 1) == 0) {
data[indexShift] = (byte)(data[indexShift] & 240 | value & 15);
} else {
data[indexShift] = (byte)(data[indexShift] & 15 | (value & 15) << 4);
}
chunk.setModified();
}
@Override
public void setNbtData(@Nullable CompoundTag nbtData) {
chunk.setTile(x, y, z, null);
chunk.setModified();
}
}

View File

@ -1,7 +1,9 @@
package com.boydti.fawe.object;
import com.boydti.fawe.FaweCache;
import com.boydti.fawe.util.MainUtil;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.world.biome.BaseBiome;
import java.util.ArrayDeque;
import java.util.Map;
@ -93,6 +95,32 @@ public abstract class FaweChunk<T> {
*/
public abstract int getBlockCombinedId(int x, int y, int z);
public void setBlock(int x, int y, int z, BaseBlock block) {
setBlock(x, y, z, block.getId(), block.getData());
if (block.hasNbtData()) {
setTile(x & 15, y, z & 15, block.getNbtData());
}
}
public BaseBlock getBlock(int x, int y, int z) {
int combined = getBlockCombinedId(x, y, z);
int id = FaweCache.getId(combined);
if (!FaweCache.hasNBT(id)) {
return FaweCache.CACHE_BLOCK[combined];
}
try {
CompoundTag tile = getTile(x & 15, y, z & 15);
if (tile != null) {
return new BaseBlock(id, FaweCache.getData(combined), tile);
} else {
return FaweCache.CACHE_BLOCK[combined];
}
} catch (Throwable e) {
MainUtil.handleError(e);
return FaweCache.CACHE_BLOCK[combined];
}
}
public char[][] getCombinedIdArrays() {
char[][] ids = new char[16][];
for (int y = 0; y < 16; y++) {

View File

@ -151,6 +151,7 @@ public class BufferedRandomAccessFile extends RandomAccessFile
this.diskPos_ = 0L;
}
@Override
public void close() throws IOException
{
this.flush();
@ -216,6 +217,7 @@ public class BufferedRandomAccessFile extends RandomAccessFile
* is at or past the end-of-file, which can only happen if the file was
* opened in read-only mode.
*/
@Override
public void seek(long pos) throws IOException
{
if (pos >= this.hi_ || pos < this.lo_)
@ -244,41 +246,44 @@ public class BufferedRandomAccessFile extends RandomAccessFile
this.curr_ = pos;
}
// /*
// * Seek and do not flush if within the current buffer when going backwards
// * - Assumes no writes were made
// * @param pos
// * @throws IOException
// */
// public void seekUnsafe(long pos) throws IOException
// {
// if (pos >= this.hi_ || pos < this.lo_)
// {
// // seeking outside of current buffer -- flush and read
// this.flushBuffer();
// this.lo_ = pos & BuffMask_; // start at BuffSz boundary
// this.maxHi_ = this.lo_ + (long) this.buff_.length;
// if (this.diskPos_ != this.lo_)
// {
// super.seek(this.lo_);
// this.diskPos_ = this.lo_;
// }
// int n = this.fillBuffer();
// this.hi_ = this.lo_ + (long) n;
// }
// this.curr_ = pos;
// }
/*
* Does not maintain V4 (i.e. buffer differs from disk contents if previously written to)
* - Assumes no writes were made
* @param pos
* @throws IOException
*/
public void seekUnsafe(long pos) throws IOException
{
if (pos >= this.hi_ || pos < this.lo_)
{
// seeking outside of current buffer -- flush and read
this.flushBuffer();
this.lo_ = pos & BuffMask_; // start at BuffSz boundary
this.maxHi_ = this.lo_ + (long) this.buff_.length;
if (this.diskPos_ != this.lo_)
{
super.seek(this.lo_);
this.diskPos_ = this.lo_;
}
int n = this.fillBuffer();
this.hi_ = this.lo_ + (long) n;
}
this.curr_ = pos;
}
@Override
public long getFilePointer()
{
return this.curr_;
}
@Override
public long length() throws IOException
{
return Math.max(this.curr_, super.length());
}
@Override
public int read() throws IOException
{
if (this.curr_ >= this.hi_)
@ -315,11 +320,13 @@ public class BufferedRandomAccessFile extends RandomAccessFile
return res;
}
@Override
public int read(byte[] b) throws IOException
{
return this.read(b, 0, b.length);
}
@Override
public int read(byte[] b, int off, int len) throws IOException
{
if (this.curr_ >= this.hi_)
@ -381,6 +388,7 @@ public class BufferedRandomAccessFile extends RandomAccessFile
this.dirty_ = true;
}
@Override
public void write(int b) throws IOException
{
if (this.curr_ >= this.hi_)
@ -406,11 +414,13 @@ public class BufferedRandomAccessFile extends RandomAccessFile
this.dirty_ = true;
}
@Override
public void write(byte[] b) throws IOException
{
this.write(b, 0, b.length);
}
@Override
public void write(byte[] b, int off, int len) throws IOException
{
while (len > 0)

View File

@ -0,0 +1,179 @@
/*
* Written by Doug Lea with assistance from members of JCP JSR-166
* Expert Group and released to the public domain, as explained at
* http://creativecommons.org/publicdomain/zero/1.0/
*/
/*
* Source:
* http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/jsr166e/LongAdder.java?revision=1.17
*/
package com.boydti.fawe.object.number;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public final class LongAdder extends Striped64 implements Serializable {
private static final long serialVersionUID = 7249069246863182397L;
/**
* Version of plus for use in retryUpdate
*/
final long fn(long v, long x) { return v + x; }
/**
* Creates a new adder with initial sum of zero.
*/
public LongAdder() {
}
/**
* Adds the given value.
*
* @param x the value to add
*/
public void add(long x) {
Cell[] as; long b, v; int[] hc; Cell a; int n;
if ((as = cells) != null || !casBase(b = base, b + x)) {
boolean uncontended = true;
if ((hc = threadHashCode.get()) == null ||
as == null || (n = as.length) < 1 ||
(a = as[(n - 1) & hc[0]]) == null ||
!(uncontended = a.cas(v = a.value, v + x)))
retryUpdate(x, hc, uncontended);
}
}
/**
* Equivalent to {@code add(1)}.
*/
public void increment() {
add(1L);
}
/**
* Equivalent to {@code add(-1)}.
*/
public void decrement() {
add(-1L);
}
/**
* Returns the current sum. The returned value is <em>NOT</em> an
* atomic snapshot; invocation in the absence of concurrent
* updates returns an accurate result, but concurrent updates that
* occur while the sum is being calculated might not be
* incorporated.
*
* @return the sum
*/
public long sum() {
long sum = base;
Cell[] as = cells;
if (as != null) {
int n = as.length;
for (int i = 0; i < n; ++i) {
Cell a = as[i];
if (a != null)
sum += a.value;
}
}
return sum;
}
/**
* Resets variables maintaining the sum to zero. This method may
* be a useful alternative to creating a new adder, but is only
* effective if there are no concurrent updates. Because this
* method is intrinsically racy, it should only be used when it is
* known that no threads are concurrently updating.
*/
public void reset() {
internalReset(0L);
}
/**
* Equivalent in effect to {@link #sum} followed by {@link
* #reset}. This method may apply for example during quiescent
* points between multithreaded computations. If there are
* updates concurrent with this method, the returned value is
* <em>not</em> guaranteed to be the final value occurring before
* the reset.
*
* @return the sum
*/
public long sumThenReset() {
long sum = base;
Cell[] as = cells;
base = 0L;
if (as != null) {
int n = as.length;
for (int i = 0; i < n; ++i) {
Cell a = as[i];
if (a != null) {
sum += a.value;
a.value = 0L;
}
}
}
return sum;
}
/**
* Returns the String representation of the {@link #sum}.
* @return the String representation of the {@link #sum}
*/
public String toString() {
return Long.toString(sum());
}
/**
* Equivalent to {@link #sum}.
*
* @return the sum
*/
public long longValue() {
return sum();
}
/**
* Returns the {@link #sum} as an {@code int} after a narrowing
* primitive conversion.
*/
public int intValue() {
return (int)sum();
}
/**
* Returns the {@link #sum} as a {@code float}
* after a widening primitive conversion.
*/
public float floatValue() {
return (float)sum();
}
/**
* Returns the {@link #sum} as a {@code double} after a widening
* primitive conversion.
*/
public double doubleValue() {
return (double)sum();
}
private void writeObject(ObjectOutputStream s) throws IOException {
s.defaultWriteObject();
s.writeLong(sum());
}
private void readObject(ObjectInputStream s)
throws IOException, ClassNotFoundException {
s.defaultReadObject();
busy = 0;
cells = null;
base = s.readLong();
}
}

View File

@ -0,0 +1,341 @@
package com.boydti.fawe.object.number;
/*
* Written by Doug Lea with assistance from members of JCP JSR-166
* Expert Group and released to the public domain, as explained at
* http://creativecommons.org/publicdomain/zero/1.0/
*/
/*
* Source:
* http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/jsr166e/Striped64.java?revision=1.9
*/
import com.boydti.fawe.object.PseudoRandom;
import java.util.Random;
/**
* A package-local class holding common representation and mechanics
* for classes supporting dynamic striping on 64bit values. The class
* extends Number so that concrete subclasses must publicly do so.
*/
abstract class Striped64 extends Number {
/*
* This class maintains a lazily-initialized table of atomically
* updated variables, plus an extra "base" field. The table size
* is a power of two. Indexing uses masked per-thread hash codes.
* Nearly all declarations in this class are package-private,
* accessed directly by subclasses.
*
* Table entries are of class Cell; a variant of AtomicLong padded
* to reduce cache contention on most processors. Padding is
* overkill for most Atomics because they are usually irregularly
* scattered in memory and thus don't interfere much with each
* other. But Atomic objects residing in arrays will tend to be
* placed adjacent to each other, and so will most often share
* cache lines (with a huge negative performance impact) without
* this precaution.
*
* In part because Cells are relatively large, we avoid creating
* them until they are needed. When there is no contention, all
* updates are made to the base field. Upon first contention (a
* failed CAS on base update), the table is initialized to size 2.
* The table size is doubled upon further contention until
* reaching the nearest power of two greater than or equal to the
* number of CPUS. Table slots remain empty (null) until they are
* needed.
*
* A single spinlock ("busy") is used for initializing and
* resizing the table, as well as populating slots with new Cells.
* There is no need for a blocking lock; when the lock is not
* available, threads try other slots (or the base). During these
* retries, there is increased contention and reduced locality,
* which is still better than alternatives.
*
* Per-thread hash codes are initialized to random values.
* Contention and/or table collisions are indicated by failed
* CASes when performing an update operation (see method
* retryUpdate). Upon a collision, if the table size is less than
* the capacity, it is doubled in size unless some other thread
* holds the lock. If a hashed slot is empty, and lock is
* available, a new Cell is created. Otherwise, if the slot
* exists, a CAS is tried. Retries proceed by "double hashing",
* using a secondary hash (Marsaglia XorShift) to try to find a
* free slot.
*
* The table size is capped because, when there are more threads
* than CPUs, supposing that each thread were bound to a CPU,
* there would exist a perfect hash function mapping threads to
* slots that eliminates collisions. When we reach capacity, we
* search for this mapping by randomly varying the hash codes of
* colliding threads. Because search is random, and collisions
* only become known via CAS failures, convergence can be slow,
* and because threads are typically not bound to CPUS forever,
* may not occur at all. However, despite these limitations,
* observed contention rates are typically low in these cases.
*
* It is possible for a Cell to become unused when threads that
* once hashed to it terminate, as well as in the case where
* doubling the table causes no thread to hash to it under
* expanded mask. We do not try to detect or remove such cells,
* under the assumption that for long-running instances, observed
* contention levels will recur, so the cells will eventually be
* needed again; and for short-lived ones, it does not matter.
*/
/**
* Padded variant of AtomicLong supporting only raw accesses plus CAS.
* The value field is placed between pads, hoping that the JVM doesn't
* reorder them.
*
* JVM intrinsics note: It would be possible to use a release-only
* form of CAS here, if it were provided.
*/
static final class Cell {
volatile long p0, p1, p2, p3, p4, p5, p6;
volatile long value;
volatile long q0, q1, q2, q3, q4, q5, q6;
Cell(long x) { value = x; }
final boolean cas(long cmp, long val) {
return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);
}
// Unsafe mechanics
private static final sun.misc.Unsafe UNSAFE;
private static final long valueOffset;
static {
try {
UNSAFE = getUnsafe();
Class<?> ak = Cell.class;
valueOffset = UNSAFE.objectFieldOffset
(ak.getDeclaredField("value"));
} catch (Exception e) {
throw new Error(e);
}
}
}
/**
* ThreadLocal holding a single-slot int array holding hash code.
* Unlike the JDK8 version of this class, we use a suboptimal
* int[] representation to avoid introducing a new type that can
* impede class-unloading when ThreadLocals are not removed.
*/
static final ThreadLocal<int[]> threadHashCode = new ThreadLocal<int[]>();
/**
* Generator of new random hash codes
*/
static final PseudoRandom prng = new PseudoRandom(System.nanoTime());
static final Random rng = new Random();
/** Number of CPUS, to place bound on table size */
static final int NCPU = Runtime.getRuntime().availableProcessors();
/**
* Table of cells. When non-null, size is a power of 2.
*/
transient volatile Cell[] cells;
/**
* Base value, used mainly when there is no contention, but also as
* a fallback during table initialization races. Updated via CAS.
*/
transient volatile long base;
/**
* Spinlock (locked via CAS) used when resizing and/or creating Cells.
*/
transient volatile int busy;
/**
* Package-private default constructor
*/
Striped64() {
}
/**
* CASes the base field.
*/
final boolean casBase(long cmp, long val) {
return UNSAFE.compareAndSwapLong(this, baseOffset, cmp, val);
}
/**
* CASes the busy field from 0 to 1 to acquire lock.
*/
final boolean casBusy() {
return UNSAFE.compareAndSwapInt(this, busyOffset, 0, 1);
}
/**
* Computes the function of current and new value. Subclasses
* should open-code this update function for most uses, but the
* virtualized form is needed within retryUpdate.
*
* @param currentValue the current value (of either base or a cell)
* @param newValue the argument from a user update call
* @return result of the update function
*/
abstract long fn(long currentValue, long newValue);
/**
* Handles cases of updates involving initialization, resizing,
* creating new Cells, and/or contention. See above for
* explanation. This method suffers the usual non-modularity
* problems of optimistic retry code, relying on rechecked sets of
* reads.
*
* @param x the value
* @param hc the hash code holder
* @param wasUncontended false if CAS failed before call
*/
final void retryUpdate(long x, int[] hc, boolean wasUncontended) {
int h;
if (hc == null) {
threadHashCode.set(hc = new int[1]); // Initialize randomly
int r = prng.random(Integer.MAX_VALUE); // Avoid zero to allow xorShift rehash
h = hc[0] = (r == 0) ? 1 : r;
}
else
h = hc[0];
boolean collide = false; // True if last slot nonempty
for (;;) {
Cell[] as; Cell a; int n; long v;
if ((as = cells) != null && (n = as.length) > 0) {
if ((a = as[(n - 1) & h]) == null) {
if (busy == 0) { // Try to attach new Cell
Cell r = new Cell(x); // Optimistically create
if (busy == 0 && casBusy()) {
boolean created = false;
try { // Recheck under lock
Cell[] rs; int m, j;
if ((rs = cells) != null &&
(m = rs.length) > 0 &&
rs[j = (m - 1) & h] == null) {
rs[j] = r;
created = true;
}
} finally {
busy = 0;
}
if (created)
break;
continue; // Slot is now non-empty
}
}
collide = false;
}
else if (!wasUncontended) // CAS already known to fail
wasUncontended = true; // Continue after rehash
else if (a.cas(v = a.value, fn(v, x)))
break;
else if (n >= NCPU || cells != as)
collide = false; // At max size or stale
else if (!collide)
collide = true;
else if (busy == 0 && casBusy()) {
try {
if (cells == as) { // Expand table unless stale
Cell[] rs = new Cell[n << 1];
for (int i = 0; i < n; ++i)
rs[i] = as[i];
cells = rs;
}
} finally {
busy = 0;
}
collide = false;
continue; // Retry with expanded table
}
h ^= h << 13; // Rehash
h ^= h >>> 17;
h ^= h << 5;
hc[0] = h; // Record index for next time
}
else if (busy == 0 && cells == as && casBusy()) {
boolean init = false;
try { // Initialize table
if (cells == as) {
Cell[] rs = new Cell[2];
rs[h & 1] = new Cell(x);
cells = rs;
init = true;
}
} finally {
busy = 0;
}
if (init)
break;
}
else if (casBase(v = base, fn(v, x)))
break; // Fall back on using base
}
}
/**
* Sets base and all cells to the given value.
*/
final void internalReset(long initialValue) {
Cell[] as = cells;
base = initialValue;
if (as != null) {
int n = as.length;
for (int i = 0; i < n; ++i) {
Cell a = as[i];
if (a != null)
a.value = initialValue;
}
}
}
// Unsafe mechanics
private static final sun.misc.Unsafe UNSAFE;
private static final long baseOffset;
private static final long busyOffset;
static {
try {
UNSAFE = getUnsafe();
Class<?> sk = Striped64.class;
baseOffset = UNSAFE.objectFieldOffset
(sk.getDeclaredField("base"));
busyOffset = UNSAFE.objectFieldOffset
(sk.getDeclaredField("busy"));
} catch (Exception e) {
throw new Error(e);
}
}
/**
* Returns a sun.misc.Unsafe. Suitable for use in a 3rd party package.
* Replace with a simple call to Unsafe.getUnsafe when integrating
* into a jdk.
*
* @return a sun.misc.Unsafe
*/
private static sun.misc.Unsafe getUnsafe() {
try {
return sun.misc.Unsafe.getUnsafe();
} catch (SecurityException tryReflectionInstead) {}
try {
return java.security.AccessController.doPrivileged
(new java.security.PrivilegedExceptionAction<sun.misc.Unsafe>() {
public sun.misc.Unsafe run() throws Exception {
Class<sun.misc.Unsafe> k = sun.misc.Unsafe.class;
for (java.lang.reflect.Field f : k.getDeclaredFields()) {
f.setAccessible(true);
Object x = f.get(null);
if (k.isInstance(x))
return k.cast(x);
}
throw new NoSuchFieldError("the Unsafe");
}});
} catch (java.security.PrivilegedActionException e) {
throw new RuntimeException("Could not initialize intrinsics",
e.getCause());
}
}
}

View File

@ -135,7 +135,7 @@ public class SelectionCommand extends SimpleCommand<Operation> {
int bx = value[2] & 15;
int tx = value[4] & 15;
int bz = value[3] & 15;
int tz = value[4] & 15;
int tz = value[5] & 15;
if (bx == 0 && tx == 15 && bz == 0 && tz == 15) {
newChunk = fc.copy(true);
newChunk.setLoc(queue, value[0], value[1]);

View File

@ -20,6 +20,7 @@
package com.sk89q.worldedit.extension.platform;
import com.boydti.fawe.Fawe;
import com.boydti.fawe.command.AnvilCommands;
import com.boydti.fawe.config.BBC;
import com.boydti.fawe.object.FawePlayer;
import com.boydti.fawe.object.changeset.FaweStreamChangeSet;
@ -147,23 +148,39 @@ public final class CommandManager {
builder.addInvokeListener(new LegacyCommandsHandler());
builder.addInvokeListener(new CommandLoggingHandler(worldEdit, commandLog));
dispatcher = new CommandGraph().builder(builder).commands().registerMethods(new BiomeCommands(worldEdit)).registerMethods(new ChunkCommands(worldEdit))
.registerMethods(new ClipboardCommands(worldEdit)).registerMethods(new GeneralCommands(worldEdit)).registerMethods(new GenerationCommands(worldEdit))
.registerMethods(new HistoryCommands(worldEdit)).registerMethods(new NavigationCommands(worldEdit)).registerMethods(new RegionCommands(worldEdit))
.registerMethods(new ScriptingCommands(worldEdit)).registerMethods(new SelectionCommands(worldEdit)).registerMethods(new SnapshotUtilCommands(worldEdit))
.registerMethods(new ToolUtilCommands(worldEdit)).registerMethods(new ToolCommands(worldEdit)).registerMethods(new UtilityCommands(worldEdit))
.register(adapt(new SelectionCommand(new ApplyCommand(new ReplaceParser(), "Set all blocks within selection"), "worldedit.region.set")), "/set").group("worldedit", "we")
.describeAs("WorldEdit commands").registerMethods(new WorldEditCommands(worldEdit)).parent().group("schematic", "schem", "/schematic", "/schem")
.describeAs("Schematic commands for saving/loading areas").registerMethods(new SchematicCommands(worldEdit)).parent().group("snapshot", "snap")
.describeAs("Schematic commands for saving/loading areas").registerMethods(new SnapshotCommands(worldEdit)).parent().group("brush", "br").describeAs("Brushing commands")
.registerMethods(new BrushCommands(worldEdit)).register(adapt(new ShapedBrushCommand(new DeformCommand(), "worldedit.brush.deform")), "deform")
.register(adapt(new ShapedBrushCommand(new ApplyCommand(new ReplaceParser(), "Set all blocks within region"), "worldedit.brush.set")), "set")
.register(adapt(new ShapedBrushCommand(new PaintCommand(), "worldedit.brush.paint")), "paint").register(adapt(new ShapedBrushCommand(new ApplyCommand(), "worldedit.brush.apply")), "apply")
.register(adapt(new ShapedBrushCommand(new PaintCommand(new TreeGeneratorParser("treeType")), "worldedit.brush.forest")), "forest")
.register(adapt(new ShapedBrushCommand(ProvidedValue.create(new Deform("y-=1", Mode.RAW_COORD), "Raise one block"), "worldedit.brush.raise")), "raise")
.register(adapt(new ShapedBrushCommand(ProvidedValue.create(new Deform("y+=1", Mode.RAW_COORD), "Lower one block"), "worldedit.brush.lower")), "lower").parent()
.group("superpickaxe", "pickaxe", "sp").describeAs("Super-pickaxe commands").registerMethods(new SuperPickaxeCommands(worldEdit)).parent().group("tool")
.describeAs("Bind functions to held items").registerMethods(new ToolCommands(worldEdit)).parent().graph().getDispatcher();
dispatcher = new CommandGraph().builder(builder).commands()
.registerMethods(new AnvilCommands(worldEdit)) // Added
.registerMethods(new BiomeCommands(worldEdit))
.registerMethods(new ChunkCommands(worldEdit))
.registerMethods(new ClipboardCommands(worldEdit))
.registerMethods(new GeneralCommands(worldEdit))
.registerMethods(new GenerationCommands(worldEdit))
.registerMethods(new HistoryCommands(worldEdit))
.registerMethods(new NavigationCommands(worldEdit))
.registerMethods(new RegionCommands(worldEdit))
.registerMethods(new ScriptingCommands(worldEdit))
.registerMethods(new SelectionCommands(worldEdit))
.registerMethods(new SnapshotUtilCommands(worldEdit))
.registerMethods(new ToolUtilCommands(worldEdit))
.registerMethods(new ToolCommands(worldEdit))
.registerMethods(new UtilityCommands(worldEdit))
.register(adapt(new SelectionCommand(new ApplyCommand(new ReplaceParser(), "Set all blocks within selection"), "worldedit.region.set")), "/set").group("worldedit", "we")
.describeAs("WorldEdit commands")
.registerMethods(new WorldEditCommands(worldEdit)).parent().group("schematic", "schem", "/schematic", "/schem")
.describeAs("Schematic commands for saving/loading areas")
.registerMethods(new SchematicCommands(worldEdit)).parent().group("snapshot", "snap")
.describeAs("Schematic commands for saving/loading areas")
.registerMethods(new SnapshotCommands(worldEdit)).parent().group("brush", "br").describeAs("Brushing commands")
.registerMethods(new BrushCommands(worldEdit)).register(adapt(new ShapedBrushCommand(new DeformCommand(), "worldedit.brush.deform")), "deform")
.register(adapt(new ShapedBrushCommand(new ApplyCommand(new ReplaceParser(), "Set all blocks within region"), "worldedit.brush.set")), "set")
.register(adapt(new ShapedBrushCommand(new PaintCommand(), "worldedit.brush.paint")), "paint").register(adapt(new ShapedBrushCommand(new ApplyCommand(), "worldedit.brush.apply")), "apply")
.register(adapt(new ShapedBrushCommand(new PaintCommand(new TreeGeneratorParser("treeType")), "worldedit.brush.forest")), "forest")
.register(adapt(new ShapedBrushCommand(ProvidedValue.create(new Deform("y-=1", Mode.RAW_COORD), "Raise one block"), "worldedit.brush.raise")), "raise")
.register(adapt(new ShapedBrushCommand(ProvidedValue.create(new Deform("y+=1", Mode.RAW_COORD), "Lower one block"), "worldedit.brush.lower")), "lower").parent()
.group("superpickaxe", "pickaxe", "sp").describeAs("Super-pickaxe commands")
.registerMethods(new SuperPickaxeCommands(worldEdit)).parent().group("tool")
.describeAs("Bind functions to held items")
.registerMethods(new ToolCommands(worldEdit)).parent().graph().getDispatcher();
}
public static CommandManager getInstance() {
@ -204,7 +221,7 @@ public final class CommandManager {
public void unregister() {
dynamicHandler.setHandler(null);
}
public String[] commandDetection(String[] split) {
// Quick script shortcut
if (split[0].matches("^[^/].*\\.js$")) {
@ -214,9 +231,9 @@ public final class CommandManager {
newSplit[1] = newSplit[1];
split = newSplit;
}
String searchCmd = split[0].toLowerCase();
// Try to detect the command
if (!dispatcher.contains(searchCmd)) {
if (worldEdit.getConfiguration().noDoubleSlash && dispatcher.contains("/" + searchCmd)) {
@ -225,10 +242,10 @@ public final class CommandManager {
split[0] = split[0].substring(1);
}
}
return split;
}
@Subscribe
public void handleCommand(final CommandEvent event) {
Request.reset();
@ -350,7 +367,7 @@ public final class CommandManager {
event.getActor().printError(e.getMessage());
}
}
/**
* Get the command dispatcher instance.
*
@ -359,7 +376,7 @@ public final class CommandManager {
public Dispatcher getDispatcher() {
return dispatcher;
}
public static Logger getLogger() {
return commandLog;
}

View File

@ -27,13 +27,13 @@ import com.sk89q.worldedit.entity.BaseEntity;
import com.sk89q.worldedit.entity.Entity;
import com.sk89q.worldedit.function.operation.Operation;
import com.sk89q.worldedit.function.operation.OperationQueue;
import com.sk89q.worldedit.util.Location;
import com.sk89q.worldedit.regions.CuboidRegion;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.util.Location;
import com.sk89q.worldedit.world.biome.BaseBiome;
import java.util.List;
import javax.annotation.Nullable;
import java.util.List;
import static com.google.common.base.Preconditions.checkNotNull;
@ -149,4 +149,7 @@ public abstract class AbstractDelegateExtent implements Extent {
}
}
public static Class<?> inject() {
return AbstractDelegateExtent.class;
}
}