diff --git a/core/src/main/java/com/boydti/fawe/command/AnvilCommands.java b/core/src/main/java/com/boydti/fawe/command/AnvilCommands.java index 53a5402c..1cb7246c 100644 --- a/core/src/main/java/com/boydti/fawe/command/AnvilCommands.java +++ b/core/src/main/java/com/boydti/fawe/command/AnvilCommands.java @@ -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] ", - desc = "Replace all blocks in the selection with another", - flags = "f", - min = 1, - max = 2 + aliases = { "/countall" }, + usage = " [hasSky] ", + 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 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()); } } diff --git a/core/src/main/java/com/boydti/fawe/jnbt/anvil/AnvilRegion.java b/core/src/main/java/com/boydti/fawe/jnbt/anvil/AnvilRegion.java deleted file mode 100644 index 350d295d..00000000 --- a/core/src/main/java/com/boydti/fawe/jnbt/anvil/AnvilRegion.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.boydti.fawe.jnbt.anvil; - -public class AnvilRegion { -} diff --git a/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAChunk.java b/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAChunk.java index a467b6da..cd16377a 100644 --- a/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAChunk.java +++ b/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAChunk.java @@ -197,6 +197,11 @@ public class MCAChunk extends FaweChunk { 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 { 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 { } } - 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); diff --git a/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAFile.java b/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAFile.java index 269fa561..21a9072e 100644 --- a/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAFile.java +++ b/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAFile.java @@ -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(); } diff --git a/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAFilter.java b/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAFilter.java new file mode 100644 index 00000000..4b9d70dc --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAFilter.java @@ -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) {} +} diff --git a/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAQueue.java b/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAQueue.java index 771c9fde..e19e6559 100644 --- a/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAQueue.java +++ b/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAQueue.java @@ -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 { @@ -37,6 +41,77 @@ public class MCAQueue extends NMSMappedFaweQueue() { + @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"); diff --git a/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAQueueMap.java b/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAQueueMap.java index 86aa301a..3ae3b92a 100644 --- a/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAQueueMap.java +++ b/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAQueueMap.java @@ -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 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; diff --git a/core/src/main/java/com/boydti/fawe/jnbt/anvil/MutableMCABackedBaseBlock.java b/core/src/main/java/com/boydti/fawe/jnbt/anvil/MutableMCABackedBaseBlock.java new file mode 100644 index 00000000..b48c0a7e --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/jnbt/anvil/MutableMCABackedBaseBlock.java @@ -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(); + } +} diff --git a/core/src/main/java/com/boydti/fawe/object/FaweChunk.java b/core/src/main/java/com/boydti/fawe/object/FaweChunk.java index e3a39b95..8d70a53b 100644 --- a/core/src/main/java/com/boydti/fawe/object/FaweChunk.java +++ b/core/src/main/java/com/boydti/fawe/object/FaweChunk.java @@ -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 { */ 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++) { diff --git a/core/src/main/java/com/boydti/fawe/object/io/BufferedRandomAccessFile.java b/core/src/main/java/com/boydti/fawe/object/io/BufferedRandomAccessFile.java index a710436a..1ab2101e 100644 --- a/core/src/main/java/com/boydti/fawe/object/io/BufferedRandomAccessFile.java +++ b/core/src/main/java/com/boydti/fawe/object/io/BufferedRandomAccessFile.java @@ -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) diff --git a/core/src/main/java/com/boydti/fawe/object/number/LongAdder.java b/core/src/main/java/com/boydti/fawe/object/number/LongAdder.java new file mode 100644 index 00000000..798887f6 --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/object/number/LongAdder.java @@ -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 NOT 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 + * not 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(); + } + +} \ No newline at end of file diff --git a/core/src/main/java/com/boydti/fawe/object/number/Striped64.java b/core/src/main/java/com/boydti/fawe/object/number/Striped64.java new file mode 100644 index 00000000..7a08e074 --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/object/number/Striped64.java @@ -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 threadHashCode = new ThreadLocal(); + + /** + * 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() { + public sun.misc.Unsafe run() throws Exception { + Class 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()); + } + } +} \ No newline at end of file diff --git a/core/src/main/java/com/sk89q/worldedit/command/composition/SelectionCommand.java b/core/src/main/java/com/sk89q/worldedit/command/composition/SelectionCommand.java index 00034677..1bae4aff 100644 --- a/core/src/main/java/com/sk89q/worldedit/command/composition/SelectionCommand.java +++ b/core/src/main/java/com/sk89q/worldedit/command/composition/SelectionCommand.java @@ -135,7 +135,7 @@ public class SelectionCommand extends SimpleCommand { 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]); diff --git a/core/src/main/java/com/sk89q/worldedit/extension/platform/CommandManager.java b/core/src/main/java/com/sk89q/worldedit/extension/platform/CommandManager.java index ba26aa7d..a8e0bf30 100644 --- a/core/src/main/java/com/sk89q/worldedit/extension/platform/CommandManager.java +++ b/core/src/main/java/com/sk89q/worldedit/extension/platform/CommandManager.java @@ -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; } diff --git a/core/src/main/java/com/sk89q/worldedit/extent/AbstractDelegateExtent.java b/core/src/main/java/com/sk89q/worldedit/extent/AbstractDelegateExtent.java index 645cdf74..ac3c8940 100644 --- a/core/src/main/java/com/sk89q/worldedit/extent/AbstractDelegateExtent.java +++ b/core/src/main/java/com/sk89q/worldedit/extent/AbstractDelegateExtent.java @@ -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; + } }