From adb2c37a02a3a002b76769011739f150f3fd6e63 Mon Sep 17 00:00:00 2001 From: Jesse Boyd Date: Thu, 2 May 2019 04:19:15 +1000 Subject: [PATCH] set blocks --- .../fawe/bukkit/beta/BukkitChunkHolder.java | 80 ++++++++-- .../fawe/bukkit/beta/BukkitGetBlocks.java | 139 ++++++++++-------- .../boydti/fawe/bukkit/beta/BukkitQueue.java | 50 +++++++ .../boydti/fawe/bukkit/beta/DelegateLock.java | 14 +- .../SingleThreadQueueExtent.java | 5 +- .../implementation/blocks/CharBlocks.java | 4 + .../java/net/jpountz/util/UnsafeUtils.java | 4 + 7 files changed, 221 insertions(+), 75 deletions(-) diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/beta/BukkitChunkHolder.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/beta/BukkitChunkHolder.java index 27ba85f7b..4dc63bf9f 100644 --- a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/beta/BukkitChunkHolder.java +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/beta/BukkitChunkHolder.java @@ -7,11 +7,14 @@ import com.boydti.fawe.beta.IQueueExtent; import com.boydti.fawe.beta.implementation.blocks.CharSetBlocks; import com.boydti.fawe.beta.implementation.holder.ChunkHolder; import com.google.common.util.concurrent.Futures; +import net.jpountz.util.UnsafeUtils; import net.minecraft.server.v1_13_R2.Chunk; import net.minecraft.server.v1_13_R2.ChunkSection; import org.bukkit.World; +import java.util.Arrays; import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicReferenceArray; public class BukkitChunkHolder> extends ChunkHolder { @Override @@ -33,20 +36,73 @@ public class BukkitChunkHolder> extends ChunkHolder { int X = getX(); int Z = getZ(); - Chunk currentNmsChunk = extent.ensureLoaded(X, Z); - ChunkSection[] sections = currentNmsChunk.getSections(); - World world = extent.getBukkitWorld(); - boolean hasSky = world.getEnvironment() == World.Environment.NORMAL; + Chunk nmsChunk = extent.ensureLoaded(X, Z); + try { + synchronized (nmsChunk) { + ChunkSection[] sections = nmsChunk.getSections(); + World world = extent.getBukkitWorld(); + boolean hasSky = world.getEnvironment() == World.Environment.NORMAL; - for (int layer = 0; layer < 16; layer++) { - if (!set.hasSection(layer)) continue; - char[] arr = set.blocks[layer]; - ChunkSection newSection = extent.newChunkSection(layer, hasSky, arr); - sections[layer] = newSection; + for (int layer = 0; layer < 16; layer++) { + if (!set.hasSection(layer)) continue; + char[] setArr = set.blocks[layer]; + ChunkSection newSection; + ChunkSection existingSection = sections[layer]; + if (existingSection == null) { + newSection = extent.newChunkSection(layer, hasSky, setArr); + if (BukkitQueue.setSectionAtomic(sections, null, newSection, layer)) { + continue; + } else { + existingSection = sections[layer]; + if (existingSection == null) { + System.out.println("Skipping invalid null section. chunk:" + X + "," + Z + " layer: " + layer); + continue; + } + } + } + DelegateLock lock = BukkitQueue.applyLock(existingSection); + synchronized (lock) { + if (lock.isLocked()) { + lock.lock(); + lock.unlock(); + } + synchronized (get) { + ChunkSection getSection; + if (get.nmsChunk != nmsChunk) { + get.nmsChunk = nmsChunk; + get.sections = null; + get.reset(); + System.out.println("chunk doesn't match"); + } else { + getSection = get.getSections()[layer]; + if (getSection != existingSection) { + get.sections[layer] = existingSection; + get.reset(); + System.out.println("Section doesn't match"); + } else if (lock.isModified()) { + System.out.println("lock is outdated"); + get.reset(layer); + } + } + char[] getArr = get.load(layer); + for (int i = 0; i < 4096; i++) { + char value = setArr[i]; + if (value != 0) { + getArr[i] = value; + } + } + newSection = extent.newChunkSection(layer, hasSky, getArr); + if (!BukkitQueue.setSectionAtomic(sections, existingSection, newSection, layer)) { + System.out.println("Failed to set chunk section:" + X + "," + Z + " layer: " + layer); + continue; + } + } + } + } + } + } finally { + extent.returnToPool(this); } - - - /* - getBlocks diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/beta/BukkitGetBlocks.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/beta/BukkitGetBlocks.java index d1d8d135e..e0f2328c6 100644 --- a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/beta/BukkitGetBlocks.java +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/beta/BukkitGetBlocks.java @@ -62,85 +62,97 @@ public class BukkitGetBlocks extends CharGetBlocks { if (data == null || data == FaweCache.EMPTY_CHAR_4096) { data = new char[4096]; } + DelegateLock lock = BukkitQueue.applyLock(section); + synchronized (lock) { + if (lock.isLocked()) { + lock.lock(); + lock.unlock(); + } + lock.setModified(false); + // Efficiently convert ChunkSection to raw data + try { + final DataPaletteBlock blocks = section.getBlocks(); + final DataBits bits = (DataBits) BukkitQueue_1_13.fieldBits.get(blocks); + final DataPalette palette = (DataPalette) BukkitQueue_1_13.fieldPalette.get(blocks); + final int bitsPerEntry = bits.c(); - // Efficiently convert ChunkSection to raw data - try { - final DataPaletteBlock blocks = section.getBlocks(); - final DataBits bits = (DataBits) BukkitQueue_1_13.fieldBits.get(blocks); - final DataPalette palette = (DataPalette) BukkitQueue_1_13.fieldPalette.get(blocks); - final int bitsPerEntry = bits.c(); + final long[] blockStates = bits.a(); + new BitArray4096(blockStates, bitsPerEntry).toRaw(data); - final long[] blockStates = bits.a(); - new BitArray4096(blockStates, bitsPerEntry).toRaw(data); + int num_palette; + if (palette instanceof DataPaletteLinear) { + num_palette = ((DataPaletteLinear) palette).b(); + } else if (palette instanceof DataPaletteHash) { + num_palette = ((DataPaletteHash) palette).b(); + } else { + num_palette = 0; + int[] paletteToBlockInts = FaweCache.PALETTE_TO_BLOCK.get(); + char[] paletteToBlockChars = FaweCache.PALETTE_TO_BLOCK_CHAR.get(); + try { + for (int i = 0; i < 4096; i++) { + char paletteVal = data[i]; + char ordinal = paletteToBlockChars[paletteVal]; + if (ordinal == Character.MAX_VALUE) { + paletteToBlockInts[num_palette++] = paletteVal; + IBlockData ibd = palette.a(data[i]); + if (ibd == null) { + ordinal = BlockTypes.AIR.getDefaultState().getOrdinalChar(); + } else { + ordinal = ((Spigot_v1_13_R2) getAdapter()).adaptToChar(ibd); + } + paletteToBlockChars[paletteVal] = ordinal; + } + data[i] = ordinal; + } + } finally { + for (int i = 0; i < num_palette; i++) { + int paletteVal = paletteToBlockInts[i]; + paletteToBlockChars[paletteVal] = Character.MAX_VALUE; + } + } + return data; + } - int num_palette; - if (palette instanceof DataPaletteLinear) { - num_palette = ((DataPaletteLinear) palette).b(); - } else if (palette instanceof DataPaletteHash) { - num_palette = ((DataPaletteHash) palette).b(); - } else { - num_palette = 0; - int[] paletteToBlockInts = FaweCache.PALETTE_TO_BLOCK.get(); char[] paletteToBlockChars = FaweCache.PALETTE_TO_BLOCK_CHAR.get(); try { + for (int i = 0; i < num_palette; i++) { + IBlockData ibd = palette.a(i); + char ordinal; + if (ibd == null) { + ordinal = BlockTypes.AIR.getDefaultState().getOrdinalChar(); + System.out.println("Invalid palette"); + } else { + ordinal = ((Spigot_v1_13_R2) getAdapter()).adaptToChar(ibd); + } + paletteToBlockChars[i] = ordinal; + } for (int i = 0; i < 4096; i++) { char paletteVal = data[i]; - char ordinal = paletteToBlockChars[paletteVal]; - if (ordinal == Character.MAX_VALUE) { - paletteToBlockInts[num_palette++] = paletteVal; - IBlockData ibd = palette.a(data[i]); - if (ibd == null) { - ordinal = BlockTypes.AIR.getDefaultState().getOrdinalChar(); - } else { - ordinal = ((Spigot_v1_13_R2) getAdapter()).adaptToChar(ibd); - } - paletteToBlockChars[paletteVal] = ordinal; - } - data[i] = ordinal; + data[i] = paletteToBlockChars[paletteVal]; } } finally { for (int i = 0; i < num_palette; i++) { - int paletteVal = paletteToBlockInts[i]; - paletteToBlockChars[paletteVal] = Character.MAX_VALUE; + paletteToBlockChars[i] = Character.MAX_VALUE; } } - return data; + } catch (IllegalAccessException e) { + e.printStackTrace(); + throw new RuntimeException(e); } - - char[] paletteToBlockChars = FaweCache.PALETTE_TO_BLOCK_CHAR.get(); - try { - for (int i = 0; i < num_palette; i++) { - IBlockData ibd = palette.a(i); - char ordinal; - if (ibd == null) { - ordinal = BlockTypes.AIR.getDefaultState().getOrdinalChar(); - System.out.println("Invalid palette"); - } else { - ordinal = ((Spigot_v1_13_R2) getAdapter()).adaptToChar(ibd); - } - paletteToBlockChars[i] = ordinal; - } - for (int i = 0; i < 4096; i++) { - char paletteVal = data[i]; - data[i] = paletteToBlockChars[paletteVal]; - } - } finally { - for (int i = 0; i < num_palette; i++) { - paletteToBlockChars[i] = Character.MAX_VALUE; - } - } - } catch (IllegalAccessException e) { - e.printStackTrace(); - throw new RuntimeException(e); + return data; } - return data; } public ChunkSection[] getSections() { ChunkSection[] tmp = sections; if (tmp == null) { - Chunk chunk = getChunk(); - sections = tmp = chunk.getSections(); + synchronized (this) { + tmp = sections; + if (tmp == null) { + Chunk chunk = getChunk(); + sections = tmp = chunk.getSections().clone(); + } + } } return tmp; } @@ -148,7 +160,12 @@ public class BukkitGetBlocks extends CharGetBlocks { public Chunk getChunk() { Chunk tmp = nmsChunk; if (tmp == null) { - nmsChunk = tmp = BukkitQueue.ensureLoaded(nmsWorld, X, Z); + synchronized (this) { + tmp = nmsChunk; + if (tmp == null) { + nmsChunk = tmp = BukkitQueue.ensureLoaded(nmsWorld, X, Z); + } + } } return tmp; } diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/beta/BukkitQueue.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/beta/BukkitQueue.java index f63dfd327..c7803c6c2 100644 --- a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/beta/BukkitQueue.java +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/beta/BukkitQueue.java @@ -17,6 +17,7 @@ import com.sk89q.worldedit.world.World; import com.sk89q.worldedit.world.block.BlockID; import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockTypes; +import net.jpountz.util.UnsafeUtils; import net.minecraft.server.v1_13_R2.Block; import net.minecraft.server.v1_13_R2.Chunk; import net.minecraft.server.v1_13_R2.ChunkCoordIntPair; @@ -34,9 +35,13 @@ import org.bukkit.craftbukkit.v1_13_R2.CraftChunk; import org.bukkit.craftbukkit.v1_13_R2.CraftWorld; import java.lang.reflect.Field; +import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; +import java.util.concurrent.locks.ReentrantLock; + +import sun.misc.Unsafe; import static com.google.common.base.Preconditions.checkNotNull; @@ -99,6 +104,11 @@ public class BukkitQueue extends SimpleCharQueueExtent { public final static Field fieldTickingBlockCount; public final static Field fieldNonEmptyBlockCount; + private static final int CHUNKSECTION_BASE; + private static final int CHUNKSECTION_SHIFT; + + private static final Field fieldLock; + static { try { fieldSize = DataPaletteBlock.class.getDeclaredField("i"); @@ -114,6 +124,20 @@ public class BukkitQueue extends SimpleCharQueueExtent { fieldTickingBlockCount.setAccessible(true); fieldNonEmptyBlockCount = ChunkSection.class.getDeclaredField("nonEmptyBlockCount"); fieldNonEmptyBlockCount.setAccessible(true); + + fieldLock = DataPaletteBlock.class.getDeclaredField("j"); + fieldLock.setAccessible(true); + Field modifiersField = Field.class.getDeclaredField("modifiers"); + int modifiers = modifiersField.getInt(fieldLock); + modifiers &= ~Modifier.FINAL; + modifiersField.setInt(fieldLock, modifiers); + + Unsafe unsafe = UnsafeUtils.getUNSAFE(); + CHUNKSECTION_BASE = unsafe.arrayBaseOffset(ChunkSection[].class); + int scale = unsafe.arrayIndexScale(ChunkSection[].class); + if ((scale & (scale - 1)) != 0) + throw new Error("data type scale not a power of two"); + CHUNKSECTION_SHIFT = 31 - Integer.numberOfLeadingZeros(scale); } catch (RuntimeException e) { throw e; } catch (Throwable rethrow) { @@ -122,6 +146,32 @@ public class BukkitQueue extends SimpleCharQueueExtent { } } + public static boolean setSectionAtomic(ChunkSection[] sections, ChunkSection expected, ChunkSection value, int layer) { + long offset = ((long) layer << CHUNKSECTION_SHIFT) + CHUNKSECTION_BASE; + if (layer >= 0 && layer < sections.length) { + return UnsafeUtils.getUNSAFE().compareAndSwapObject(sections, offset, expected, value); + } + return false; + } + + public static DelegateLock applyLock(ChunkSection section) { + try { + synchronized (section) { + DataPaletteBlock blocks = section.getBlocks(); + ReentrantLock currentLock = (ReentrantLock) fieldLock.get(blocks); + if (currentLock instanceof DelegateLock) { + return (DelegateLock) currentLock; + } + DelegateLock newLock = new DelegateLock(currentLock); + fieldLock.set(blocks, newLock); + return newLock; + } + } catch (IllegalAccessException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + private static boolean PAPER = true; public Chunk ensureLoaded(int X, int Z) { diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/beta/DelegateLock.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/beta/DelegateLock.java index 59bfccd3b..20f94166c 100644 --- a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/beta/DelegateLock.java +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/beta/DelegateLock.java @@ -13,6 +13,14 @@ public class DelegateLock extends ReentrantLock { this.parent = parent; } + public boolean isModified() { + return modified; + } + + public void setModified(boolean modified) { + this.modified = modified; + } + @Override public void lock() { modified = true; @@ -35,12 +43,16 @@ public class DelegateLock extends ReentrantLock { } @Override - public synchronized void unlock() { + public void unlock() { modified = true; parent.unlock(); this.notifyAll(); } + public ReentrantLock getParent() { + return parent; + } + @Override public synchronized Condition newCondition() { return parent.newCondition(); diff --git a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/SingleThreadQueueExtent.java b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/SingleThreadQueueExtent.java index 9da4c0f3e..4f57656be 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/SingleThreadQueueExtent.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/SingleThreadQueueExtent.java @@ -78,6 +78,10 @@ public abstract class SingleThreadQueueExtent implements IQueueExtent { // Pool discarded chunks for reuse (can safely be cleared by another thread) private static final ConcurrentLinkedQueue CHUNK_POOL = new ConcurrentLinkedQueue<>(); + public void returnToPool(IChunk chunk) { + CHUNK_POOL.add(chunk); + } + @Override public > T submit(final IChunk chunk) { if (chunk.isEmpty()) { @@ -123,7 +127,6 @@ public abstract class SingleThreadQueueExtent implements IQueueExtent { private IChunk poolOrCreate(final int X, final int Z) { IChunk next = CHUNK_POOL.poll(); if (next == null) { - System.out.println("Create"); next = create(false); } next.init(this, X, Z); diff --git a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/blocks/CharBlocks.java b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/blocks/CharBlocks.java index c348f8d18..7005b5ed1 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/blocks/CharBlocks.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/beta/implementation/blocks/CharBlocks.java @@ -30,6 +30,10 @@ public class CharBlocks implements IBlocks { for (int i = 0; i < 16; i++) sections[i] = NULL; } + public void reset(int layer) { + sections[layer] = NULL; + } + protected char[] load(int layer) { return new char[4096]; } diff --git a/worldedit-core/src/main/java/net/jpountz/util/UnsafeUtils.java b/worldedit-core/src/main/java/net/jpountz/util/UnsafeUtils.java index dbb24ccb5..665616011 100644 --- a/worldedit-core/src/main/java/net/jpountz/util/UnsafeUtils.java +++ b/worldedit-core/src/main/java/net/jpountz/util/UnsafeUtils.java @@ -144,4 +144,8 @@ public enum UnsafeUtils { public static void writeShort(short[] dest, int destOff, int value) { UNSAFE.putShort(dest, SHORT_ARRAY_OFFSET + SHORT_ARRAY_SCALE * destOff, (short) value); } + + public static Unsafe getUNSAFE() { + return UNSAFE; + } }