async chunk loading

This commit is contained in:
Jesse Boyd
2019-05-02 01:45:18 +10:00
parent 33e119ccb6
commit eec08c81ad
20 changed files with 1224 additions and 882 deletions

View File

@ -6,7 +6,9 @@ import com.boydti.fawe.beta.IQueueExtent;
import com.boydti.fawe.beta.implementation.blocks.CharSetBlocks;
import com.boydti.fawe.beta.implementation.holder.ChunkHolder;
public class BukkitChunkHolder extends ChunkHolder<Boolean> {
import java.util.concurrent.Future;
public class BukkitChunkHolder<T extends Future<T>> extends ChunkHolder {
@Override
public void init(final IQueueExtent extent, final int X, final int Z) {
super.init(extent, X, Z);
@ -19,30 +21,37 @@ public class BukkitChunkHolder extends ChunkHolder<Boolean> {
}
@Override
public boolean applyAsync() {
BukkitGetBlocks get = (BukkitGetBlocks) cachedGet();
CharSetBlocks set = (CharSetBlocks) cachedSet();
// - getBlocks
// - set lock
// - synchronize on chunk object
// - verify section is same object as chunk's section
// - merge with setblocks
// - set section
// - verify chunk is same
// - verify section is same
// - Otherwise repeat steps on main thread
// - set status to needs relighting
// - mark as dirty
// - skip verification if main thread
public T call() {
BukkitQueue extent = (BukkitQueue) getExtent();
BukkitGetBlocks get = (BukkitGetBlocks) getOrCreateGet();
CharSetBlocks set = (CharSetBlocks) getOrCreateSet();
/*
- getBlocks
- set ChunkSection lock with a tracking lock
- synchronize on chunk object (so no other FAWE thread updates it at the same time)
- verify cached section is same object as NMS chunk section
otherwise, fetch the new section, set the tracking lock and reconstruct the getBlocks array
- Merge raw getBlocks and setBlocks array
- Construct the ChunkSection
- In parallel on the main thread
- if the tracking lock has had no updates and the cached ChunkSection == the NMS chunk section
- Otherwise, reconstruct the ChunkSection (TODO: Benchmark if this is a performance concern)
- swap in the new ChunkSection
- Update tile entities/entities (if necessary)
- Merge the biome array (if necessary)
- set chunk status to needs relighting
- mark as dirty
*/
throw new UnsupportedOperationException("Not implemented");
// return true;
}
@Override
public boolean applySync() {
return true;
}
@Override
public void set(final Filter filter) {
// for each block

View File

@ -1,42 +0,0 @@
package com.boydti.fawe.bukkit.beta;
import com.boydti.fawe.beta.Filter;
import com.boydti.fawe.beta.IQueueExtent;
import com.boydti.fawe.beta.ISetBlocks;
import com.boydti.fawe.beta.implementation.holder.ChunkHolder;
public class BukkitFullChunk extends ChunkHolder {
public BukkitFullChunk() {
throw new UnsupportedOperationException("Not implemented");
}
@Override
public void init(IQueueExtent extent, int X, int Z) {
}
@Override
public boolean applyAsync() {
return false;
}
@Override
public boolean applySync() {
return false;
}
@Override
public void set(Filter filter) {
}
@Override
public Object get() {
return null;
}
@Override
public ISetBlocks set() {
return null;
}
}

View File

@ -24,10 +24,10 @@ import net.minecraft.server.v1_13_R2.World;
import static com.boydti.fawe.bukkit.v0.BukkitQueue_0.getAdapter;
public class BukkitGetBlocks extends CharGetBlocks {
private ChunkSection[] sections;
private Chunk nmsChunk;
private World nmsWorld;
private int X, Z;
public ChunkSection[] sections;
public Chunk nmsChunk;
public World nmsWorld;
public int X, Z;
public BukkitGetBlocks(World nmsWorld, int X, int Z) {/*d*/
this.nmsWorld = nmsWorld;
@ -48,12 +48,12 @@ public class BukkitGetBlocks extends CharGetBlocks {
}
@Override
protected char[] load(int layer) {
public char[] load(int layer) {
return load(layer, null);
}
@Override
protected char[] load(int layer, char[] data) {
public char[] load(int layer, char[] data) {
ChunkSection section = getSections()[layer];
// Section is null, return empty array
if (section == null) {
@ -136,7 +136,7 @@ public class BukkitGetBlocks extends CharGetBlocks {
return data;
}
private ChunkSection[] getSections() {
public ChunkSection[] getSections() {
ChunkSection[] tmp = sections;
if (tmp == null) {
Chunk chunk = getChunk();
@ -145,16 +145,10 @@ public class BukkitGetBlocks extends CharGetBlocks {
return tmp;
}
private Chunk getChunk() {
public Chunk getChunk() {
Chunk tmp = nmsChunk;
if (tmp == null) {
ChunkProviderServer provider = (ChunkProviderServer) nmsWorld.getChunkProvider();
nmsChunk = tmp = provider.chunks.get(ChunkCoordIntPair.a(X, Z));
if (tmp == null) {
System.out.println("SYNC");
// TOOD load with paper
nmsChunk = tmp = TaskManager.IMP.sync(() -> nmsWorld.getChunkAt(X, Z));
}
nmsChunk = tmp = BukkitQueue.ensureLoaded(nmsWorld, X, Z);
}
return tmp;
}

View File

@ -1,16 +1,43 @@
package com.boydti.fawe.bukkit.beta;
import com.boydti.fawe.Fawe;
import com.boydti.fawe.FaweCache;
import com.boydti.fawe.beta.IChunk;
import com.boydti.fawe.beta.implementation.SimpleCharQueueExtent;
import com.boydti.fawe.beta.implementation.SingleThreadQueueExtent;
import com.boydti.fawe.beta.implementation.WorldChunkCache;
import com.boydti.fawe.bukkit.adapter.v1_13_1.BlockMaterial_1_13;
import com.boydti.fawe.config.Settings;
import com.boydti.fawe.jnbt.anvil.BitArray4096;
import com.boydti.fawe.object.collection.IterableThreadLocal;
import com.boydti.fawe.util.MathMan;
import com.boydti.fawe.util.TaskManager;
import com.sk89q.worldedit.bukkit.BukkitWorld;
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.minecraft.server.v1_13_R2.Block;
import net.minecraft.server.v1_13_R2.Chunk;
import net.minecraft.server.v1_13_R2.ChunkCoordIntPair;
import net.minecraft.server.v1_13_R2.ChunkProviderServer;
import net.minecraft.server.v1_13_R2.ChunkSection;
import net.minecraft.server.v1_13_R2.DataBits;
import net.minecraft.server.v1_13_R2.DataPalette;
import net.minecraft.server.v1_13_R2.DataPaletteBlock;
import net.minecraft.server.v1_13_R2.DataPaletteLinear;
import net.minecraft.server.v1_13_R2.GameProfileSerializer;
import net.minecraft.server.v1_13_R2.IBlockData;
import net.minecraft.server.v1_13_R2.WorldServer;
import org.bukkit.Bukkit;
import org.bukkit.craftbukkit.v1_13_R2.CraftChunk;
import org.bukkit.craftbukkit.v1_13_R2.CraftWorld;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import static com.google.common.base.Preconditions.checkNotNull;
public class BukkitQueue extends SimpleCharQueueExtent {
@ -45,19 +72,177 @@ public class BukkitQueue extends SimpleCharQueueExtent {
super.reset();
}
private static final IterableThreadLocal<BukkitFullChunk> FULL_CHUNKS = new IterableThreadLocal<BukkitFullChunk>() {
@Override
public BukkitFullChunk init() {
return new BukkitFullChunk();
}
};
// private static final IterableThreadLocal<BukkitFullChunk> FULL_CHUNKS = new IterableThreadLocal<BukkitFullChunk>() {
// @Override
// public BukkitFullChunk init() {
// return new BukkitFullChunk();
// }
// };
@Override
public IChunk create(boolean full) {
if (full) {
// TODO implement
// if (full) {
// //TODO implement
// return FULL_CHUNKS.get();
}
// }
return new BukkitChunkHolder();
}
/*
NMS fields
*/
public final static Field fieldBits;
public final static Field fieldPalette;
public final static Field fieldSize;
public final static Field fieldFluidCount;
public final static Field fieldTickingBlockCount;
public final static Field fieldNonEmptyBlockCount;
static {
try {
fieldSize = DataPaletteBlock.class.getDeclaredField("i");
fieldSize.setAccessible(true);
fieldBits = DataPaletteBlock.class.getDeclaredField("a");
fieldBits.setAccessible(true);
fieldPalette = DataPaletteBlock.class.getDeclaredField("h");
fieldPalette.setAccessible(true);
fieldFluidCount = ChunkSection.class.getDeclaredField("e");
fieldFluidCount.setAccessible(true);
fieldTickingBlockCount = ChunkSection.class.getDeclaredField("tickingBlockCount");
fieldTickingBlockCount.setAccessible(true);
fieldNonEmptyBlockCount = ChunkSection.class.getDeclaredField("nonEmptyBlockCount");
fieldNonEmptyBlockCount.setAccessible(true);
} catch (RuntimeException e) {
throw e;
} catch (Throwable rethrow) {
rethrow.printStackTrace();
throw new RuntimeException(rethrow);
}
}
private static boolean PAPER = true;
public Chunk ensureLoaded(int X, int Z) {
return ensureLoaded(nmsWorld, X, Z);
}
public static Chunk ensureLoaded(net.minecraft.server.v1_13_R2.World nmsWorld, int X, int Z) {
ChunkProviderServer provider = (ChunkProviderServer) nmsWorld.getChunkProvider();
Chunk nmsChunk = provider.chunks.get(ChunkCoordIntPair.a(X, Z));
if (nmsChunk != null) {
return nmsChunk;
}
if (Fawe.isMainThread()) {
return nmsWorld.getChunkAt(X, Z);
}
if (PAPER) {
CraftWorld craftWorld = nmsWorld.getWorld();
CompletableFuture<org.bukkit.Chunk> future = craftWorld.getChunkAtAsync(X, Z, true);
try {
CraftChunk chunk = (CraftChunk) future.get();
return chunk.getHandle();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (Throwable e) {
System.out.println("Error, cannot load chunk async (paper not installed?)");
PAPER = false;
}
}
// TODO optimize
return TaskManager.IMP.sync(() -> nmsWorld.getChunkAt(X, Z));
}
/*
NMS conversion
*/
public static ChunkSection newChunkSection(final int y2, final boolean flag, final char[] blocks) {
ChunkSection section = new ChunkSection(y2 << 4, flag);
if (blocks == null) {
return section;
}
final int[] blockToPalette = FaweCache.BLOCK_TO_PALETTE.get();
final int[] paletteToBlock = FaweCache.PALETTE_TO_BLOCK.get();
final long[] blockstates = FaweCache.BLOCK_STATES.get();
final int[] blocksCopy = FaweCache.SECTION_BLOCKS.get();
try {
int num_palette = 0;
int air = 0;
for (int i = 0; i < 4096; i++) {
char ordinal = blocks[i];
switch (ordinal) {
case 0:
case BlockID.AIR:
case BlockID.CAVE_AIR:
case BlockID.VOID_AIR:
air++;
}
int palette = blockToPalette[ordinal];
if (palette == Integer.MAX_VALUE) {
blockToPalette[ordinal] = palette = num_palette;
paletteToBlock[num_palette] = ordinal;
num_palette++;
}
blocksCopy[i] = palette;
}
// BlockStates
int bitsPerEntry = MathMan.log2nlz(num_palette - 1);
if (Settings.IMP.PROTOCOL_SUPPORT_FIX || num_palette != 1) {
bitsPerEntry = Math.max(bitsPerEntry, 4); // Protocol support breaks <4 bits per entry
} else {
bitsPerEntry = Math.max(bitsPerEntry, 1); // For some reason minecraft needs 4096 bits to store 0 entries
}
final int blockBitArrayEnd = (bitsPerEntry * 4096) >> 6;
if (num_palette == 1) {
for (int i = 0; i < blockBitArrayEnd; i++) blockstates[i] = 0;
} else {
final BitArray4096 bitArray = new BitArray4096(blockstates, bitsPerEntry);
bitArray.fromRaw(blocksCopy);
}
// set palette & data bits
final DataPaletteBlock<IBlockData> dataPaletteBlocks = section.getBlocks();
// private DataPalette<T> h;
// protected DataBits a;
final long[] bits = Arrays.copyOfRange(blockstates, 0, blockBitArrayEnd);
final DataBits nmsBits = new DataBits(bitsPerEntry, 4096, bits);
final DataPalette<IBlockData> palette;
// palette = new DataPaletteHash<>(Block.REGISTRY_ID, bitsPerEntry, dataPaletteBlocks, GameProfileSerializer::d, GameProfileSerializer::a);
palette = new DataPaletteLinear<>(Block.REGISTRY_ID, bitsPerEntry, dataPaletteBlocks, GameProfileSerializer::d);
// set palette
for (int i = 0; i < num_palette; i++) {
final int ordinal = paletteToBlock[i];
blockToPalette[ordinal] = Integer.MAX_VALUE;
final BlockState state = BlockTypes.states[ordinal];
final IBlockData ibd = ((BlockMaterial_1_13) state.getMaterial()).getState();
palette.a(ibd);
}
try {
fieldBits.set(dataPaletteBlocks, nmsBits);
fieldPalette.set(dataPaletteBlocks, palette);
fieldSize.set(dataPaletteBlocks, bitsPerEntry);
setCount(0, 4096 - air, section);
} catch (final IllegalAccessException | NoSuchFieldException e) {
throw new RuntimeException(e);
}
return section;
} catch (final Throwable e){
Arrays.fill(blockToPalette, Integer.MAX_VALUE);
throw e;
}
}
public static void setCount(final int tickingBlockCount, final int nonEmptyBlockCount, final ChunkSection section) throws NoSuchFieldException, IllegalAccessException {
fieldFluidCount.set(section, 0); // TODO FIXME
fieldTickingBlockCount.set(section, tickingBlockCount);
fieldNonEmptyBlockCount.set(section, nonEmptyBlockCount);
}
}

View File

@ -0,0 +1,78 @@
package com.boydti.fawe.bukkit.beta;
import java.util.Collection;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class DelegateLock extends ReentrantLock {
private final ReentrantLock parent;
private volatile boolean modified;
public DelegateLock(ReentrantLock parent) {
this.parent = parent;
}
@Override
public void lock() {
modified = true;
parent.lock();
}
@Override
public synchronized void lockInterruptibly() throws InterruptedException {
parent.lockInterruptibly();
}
@Override
public synchronized boolean tryLock() {
return parent.tryLock();
}
@Override
public synchronized boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
return parent.tryLock(timeout, unit);
}
@Override
public synchronized void unlock() {
modified = true;
parent.unlock();
this.notifyAll();
}
@Override
public synchronized Condition newCondition() {
return parent.newCondition();
}
@Override
public synchronized int getHoldCount() {
return parent.getHoldCount();
}
@Override
public synchronized boolean isHeldByCurrentThread() {
return parent.isHeldByCurrentThread();
}
@Override
public synchronized boolean isLocked() {
return parent.isLocked();
}
@Override
public synchronized boolean hasWaiters(Condition condition) {
return parent.hasWaiters(condition);
}
@Override
public synchronized int getWaitQueueLength(Condition condition) {
return parent.getWaitQueueLength(condition);
}
@Override
public synchronized String toString() {
return parent.toString();
}
}

View File

@ -70,7 +70,6 @@ public class BukkitQueue_1_13 extends BukkitQueue_0<net.minecraft.server.v1_13_R
public final static Field fieldBits;
public final static Field fieldPalette;
protected final static Field fieldSize;
protected final static Field fieldHashBlocks;
@ -88,6 +87,7 @@ public class BukkitQueue_1_13 extends BukkitQueue_0<net.minecraft.server.v1_13_R
protected final static Field fieldFluidCount;
protected final static Field fieldTickingBlockCount;
protected final static Field fieldNonEmptyBlockCount;
protected final static Field fieldSection;
protected final static Field fieldLiquidCount;
protected final static Field fieldEmittedLight;
@ -847,85 +847,84 @@ public class BukkitQueue_1_13 extends BukkitQueue_0<net.minecraft.server.v1_13_R
}
public static ChunkSection newChunkSection(final int y2, final boolean flag, final int[] blocks) {
ChunkSection section = new ChunkSection(y2 << 4, flag);
if (blocks == null) {
return new ChunkSection(y2 << 4, flag);
} else {
final ChunkSection section = new ChunkSection(y2 << 4, flag);
final int[] blockToPalette = FaweCache.BLOCK_TO_PALETTE.get();
final int[] paletteToBlock = FaweCache.PALETTE_TO_BLOCK.get();
final long[] blockstates = FaweCache.BLOCK_STATES.get();
final int[] blocksCopy = FaweCache.SECTION_BLOCKS.get();
try {
int num_palette = 0;
int air = 0;
for (int i = 0; i < 4096; i++) {
int stateId = blocks[i];
switch (stateId) {
case 0:
case BlockID.AIR:
case BlockID.CAVE_AIR:
case BlockID.VOID_AIR:
stateId = BlockID.AIR;
air++;
}
final int ordinal = BlockState.getFromInternalId(stateId).getOrdinal(); // TODO fixme Remove all use of BlockTypes.BIT_OFFSET so that this conversion isn't necessary
int palette = blockToPalette[ordinal];
if (palette == Integer.MAX_VALUE) {
blockToPalette[ordinal] = palette = num_palette;
paletteToBlock[num_palette] = ordinal;
num_palette++;
}
blocksCopy[i] = palette;
return section;
}
final int[] blockToPalette = FaweCache.BLOCK_TO_PALETTE.get();
final int[] paletteToBlock = FaweCache.PALETTE_TO_BLOCK.get();
final long[] blockstates = FaweCache.BLOCK_STATES.get();
final int[] blocksCopy = FaweCache.SECTION_BLOCKS.get();
try {
int num_palette = 0;
int air = 0;
for (int i = 0; i < 4096; i++) {
int stateId = blocks[i];
switch (stateId) {
case 0:
case BlockID.AIR:
case BlockID.CAVE_AIR:
case BlockID.VOID_AIR:
stateId = BlockID.AIR;
air++;
}
// BlockStates
int bitsPerEntry = MathMan.log2nlz(num_palette - 1);
if (Settings.IMP.PROTOCOL_SUPPORT_FIX || num_palette != 1) {
bitsPerEntry = Math.max(bitsPerEntry, 4); // Protocol support breaks <4 bits per entry
} else {
bitsPerEntry = Math.max(bitsPerEntry, 1); // For some reason minecraft needs 4096 bits to store 0 entries
final int ordinal = BlockState.getFromInternalId(stateId).getOrdinal(); // TODO fixme Remove all use of BlockTypes.BIT_OFFSET so that this conversion isn't necessary
int palette = blockToPalette[ordinal];
if (palette == Integer.MAX_VALUE) {
blockToPalette[ordinal] = palette = num_palette;
paletteToBlock[num_palette] = ordinal;
num_palette++;
}
final int blockBitArrayEnd = (bitsPerEntry * 4096) >> 6;
if (num_palette == 1) {
for (int i = 0; i < blockBitArrayEnd; i++) blockstates[i] = 0;
} else {
final BitArray4096 bitArray = new BitArray4096(blockstates, bitsPerEntry);
bitArray.fromRaw(blocksCopy);
}
// set palette & data bits
final DataPaletteBlock<IBlockData> dataPaletteBlocks = section.getBlocks();
// private DataPalette<T> h;
// protected DataBits a;
final long[] bits = Arrays.copyOfRange(blockstates, 0, blockBitArrayEnd);
final DataBits nmsBits = new DataBits(bitsPerEntry, 4096, bits);
final DataPalette<IBlockData> palette;
// palette = new DataPaletteHash<>(Block.REGISTRY_ID, bitsPerEntry, dataPaletteBlocks, GameProfileSerializer::d, GameProfileSerializer::a);
palette = new DataPaletteLinear<>(Block.REGISTRY_ID, bitsPerEntry, dataPaletteBlocks, GameProfileSerializer::d);
// set palette
for (int i = 0; i < num_palette; i++) {
final int ordinal = paletteToBlock[i];
blockToPalette[ordinal] = Integer.MAX_VALUE;
final BlockState state = BlockTypes.states[ordinal];
final IBlockData ibd = ((BlockMaterial_1_13) state.getMaterial()).getState();
palette.a(ibd);
}
try {
fieldBits.set(dataPaletteBlocks, nmsBits);
fieldPalette.set(dataPaletteBlocks, palette);
fieldSize.set(dataPaletteBlocks, bitsPerEntry);
setCount(0, 4096 - air, section);
} catch (final IllegalAccessException | NoSuchFieldException e) {
throw new RuntimeException(e);
}
return section;
} catch (final Throwable e){
Arrays.fill(blockToPalette, Integer.MAX_VALUE);
throw e;
blocksCopy[i] = palette;
}
// BlockStates
int bitsPerEntry = MathMan.log2nlz(num_palette - 1);
if (Settings.IMP.PROTOCOL_SUPPORT_FIX || num_palette != 1) {
bitsPerEntry = Math.max(bitsPerEntry, 4); // Protocol support breaks <4 bits per entry
} else {
bitsPerEntry = Math.max(bitsPerEntry, 1); // For some reason minecraft needs 4096 bits to store 0 entries
}
final int blockBitArrayEnd = (bitsPerEntry * 4096) >> 6;
if (num_palette == 1) {
for (int i = 0; i < blockBitArrayEnd; i++) blockstates[i] = 0;
} else {
final BitArray4096 bitArray = new BitArray4096(blockstates, bitsPerEntry);
bitArray.fromRaw(blocksCopy);
}
// set palette & data bits
final DataPaletteBlock<IBlockData> dataPaletteBlocks = section.getBlocks();
// private DataPalette<T> h;
// protected DataBits a;
final long[] bits = Arrays.copyOfRange(blockstates, 0, blockBitArrayEnd);
final DataBits nmsBits = new DataBits(bitsPerEntry, 4096, bits);
final DataPalette<IBlockData> palette;
// palette = new DataPaletteHash<>(Block.REGISTRY_ID, bitsPerEntry, dataPaletteBlocks, GameProfileSerializer::d, GameProfileSerializer::a);
palette = new DataPaletteLinear<>(Block.REGISTRY_ID, bitsPerEntry, dataPaletteBlocks, GameProfileSerializer::d);
// set palette
for (int i = 0; i < num_palette; i++) {
final int ordinal = paletteToBlock[i];
blockToPalette[ordinal] = Integer.MAX_VALUE;
final BlockState state = BlockTypes.states[ordinal];
final IBlockData ibd = ((BlockMaterial_1_13) state.getMaterial()).getState();
palette.a(ibd);
}
try {
fieldBits.set(dataPaletteBlocks, nmsBits);
fieldPalette.set(dataPaletteBlocks, palette);
fieldSize.set(dataPaletteBlocks, bitsPerEntry);
setCount(0, 4096 - air, section);
} catch (final IllegalAccessException | NoSuchFieldException e) {
throw new RuntimeException(e);
}
return section;
} catch (final Throwable e){
Arrays.fill(blockToPalette, Integer.MAX_VALUE);
throw e;
}
}