Add fluid ticking and refactor post-processing a bit (#1554)

* Make postProcessSet a default method and change to void

* Throwable#getMessage is nullable

* Move (re-)ticking to a post-processor per "platform"
 - Add fluid ticking

* chore: Ignore (for us) irrelevant rules

* chore: Fix correct toml syntax?

* Re-add removed method for API-compliance and refactor it to have a use

* Switch to javax annotations

* Switch to recalcBlockCounts for ticking blocks.

* No need to set air count anymore either

* We can still "not tick" in fast mode in 1.17.2

* update adapters

* Let paper create the chunk section if biomes are null

* Adjust notes to settings

* 1.17.2 didn't exist

* Add 1.18.2

* Don't attempt to cache plains biome ID

* Use correct annotation

Co-authored-by: NotMyFault <mc.cache@web.de>
This commit is contained in:
Jordan
2022-03-10 14:27:25 +00:00
committed by GitHub
parent 5d18e15128
commit e9db749e2f
47 changed files with 975 additions and 399 deletions

View File

@ -6,6 +6,7 @@ import com.fastasyncworldedit.bukkit.adapter.NMSRelighterFactory;
import com.fastasyncworldedit.core.FaweCache;
import com.fastasyncworldedit.core.entity.LazyBaseEntity;
import com.fastasyncworldedit.core.extent.processor.lighting.RelighterFactory;
import com.fastasyncworldedit.core.queue.IBatchProcessor;
import com.fastasyncworldedit.core.queue.IChunkGet;
import com.fastasyncworldedit.core.queue.implementation.packet.ChunkPacket;
import com.fastasyncworldedit.core.util.NbtUtils;
@ -694,4 +695,9 @@ public final class PaperweightFaweAdapter extends CachedBukkitAdapter implements
}
}
@Override
public IBatchProcessor getTickingPostProcessor() {
return new PaperweightPostProcessor();
}
}

View File

@ -410,7 +410,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
@SuppressWarnings("rawtypes")
public synchronized <T extends Future<T>> T call(IChunkSet set, Runnable finalizer) {
forceLoadSections = false;
copy = createCopy ? new PaperweightGetBlocks_Copy(serverLevel) : null;
copy = createCopy ? new PaperweightGetBlocks_Copy(levelChunk) : null;
try {
ServerLevel nmsWorld = serverLevel;
LevelChunk nmsChunk = ensureLoaded(nmsWorld, chunkX, chunkZ);
@ -461,6 +461,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
bitMask |= 1 << layer;
// Changes may still be written to chunk SET
char[] tmp = set.load(layerNo);
char[] setArr = new char[4096];
System.arraycopy(tmp, 0, setArr, 0, 4096);
@ -477,6 +478,12 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
LevelChunkSection newSection;
LevelChunkSection existingSection = levelChunkSections[layer];
// Don't attempt to tick section whilst we're editing
if (existingSection != null) {
PaperweightPlatformAdapter.clearCounts(existingSection);
existingSection.tickingList.clear();
}
if (existingSection == null) {
newSection = PaperweightPlatformAdapter.newChunkSection(layerNo, setArr, fastmode, adapter);
if (PaperweightPlatformAdapter.setSectionAtomic(levelChunkSections, null, newSection, layer)) {
@ -492,10 +499,11 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
}
}
}
PaperweightPlatformAdapter.fieldTickingBlockCount.set(existingSection, (short) 0);
//ensure that the server doesn't try to tick the chunksection while we're editing it.
//ensure that the server doesn't try to tick the chunksection while we're editing it (again).
DelegateSemaphore lock = PaperweightPlatformAdapter.applyLock(existingSection);
PaperweightPlatformAdapter.clearCounts(existingSection);
existingSection.tickingList.clear();
synchronized (lock) {
// lock.acquire();

View File

@ -19,6 +19,7 @@ import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.chunk.ChunkBiomeContainer;
import net.minecraft.world.level.chunk.LevelChunk;
import javax.annotation.Nullable;
import java.util.HashMap;
@ -35,13 +36,15 @@ public class PaperweightGetBlocks_Copy implements IChunkGet {
private final char[][] blocks;
private final int minHeight;
private final int maxHeight;
private final ServerLevel serverLevel;
final ServerLevel serverLevel;
final LevelChunk levelChunk;
private ChunkBiomeContainer chunkBiomeContainer;
protected PaperweightGetBlocks_Copy(ServerLevel world) {
this.serverLevel = world;
this.minHeight = world.getMinBuildHeight();
this.maxHeight = world.getMaxBuildHeight() - 1; // Minecraft max limit is exclusive.
protected PaperweightGetBlocks_Copy(LevelChunk levelChunk) {
this.levelChunk = levelChunk;
this.serverLevel = levelChunk.level;
this.minHeight = serverLevel.getMinBuildHeight();
this.maxHeight = serverLevel.getMaxBuildHeight() - 1; // Minecraft max limit is exclusive.
this.blocks = new char[getSectionCount()][];
}

View File

@ -12,7 +12,6 @@ import com.fastasyncworldedit.core.util.ReflectionUtils;
import com.fastasyncworldedit.core.util.TaskManager;
import com.mojang.datafixers.util.Either;
import com.sk89q.worldedit.bukkit.adapter.Refraction;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.world.biome.BiomeType;
import com.sk89q.worldedit.world.biome.BiomeTypes;
import com.sk89q.worldedit.world.block.BlockState;
@ -54,9 +53,7 @@ import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Semaphore;
@ -71,9 +68,9 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
public static final Field fieldBitsPerEntry;
public static final Field fieldTickingFluidContent;
public static final Field fieldTickingBlockCount;
public static final Field fieldNonEmptyBlockCount;
private static final Field fieldTickingFluidContent;
private static final Field fieldTickingBlockCount;
private static final Field fieldNonEmptyBlockCount;
private static final Field fieldBiomes;
@ -271,15 +268,20 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
NMS conversion
*/
public static LevelChunkSection newChunkSection(
final int layer, final char[] blocks, boolean fastmode,
final int layer,
final char[] blocks,
boolean fastMode,
CachedBukkitAdapter adapter
) {
return newChunkSection(layer, null, blocks, fastmode, adapter);
return newChunkSection(layer, null, blocks, fastMode, adapter);
}
public static LevelChunkSection newChunkSection(
final int layer, final Function<Integer, char[]> get, char[] set,
boolean fastmode, CachedBukkitAdapter adapter
final int layer,
final Function<Integer, char[]> get,
char[] set,
boolean fastMode,
CachedBukkitAdapter adapter
) {
if (set == null) {
return newChunkSection(layer);
@ -289,19 +291,13 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
final long[] blockStates = FaweCache.INSTANCE.BLOCK_STATES.get();
final int[] blocksCopy = FaweCache.INSTANCE.SECTION_BLOCKS.get();
try {
int[] num_palette_buffer = new int[1];
Map<BlockVector3, Integer> ticking_blocks = new HashMap<>();
int air;
int num_palette;
if (get == null) {
air = createPalette(blockToPalette, paletteToBlock, blocksCopy, num_palette_buffer,
set, ticking_blocks, fastmode, adapter
num_palette = createPalette(blockToPalette, paletteToBlock, blocksCopy, set, adapter
);
} else {
air = createPalette(layer, blockToPalette, paletteToBlock, blocksCopy,
num_palette_buffer, get, set, ticking_blocks, fastmode, adapter
);
num_palette = createPalette(layer, blockToPalette, paletteToBlock, blocksCopy, get, set, adapter);
}
int num_palette = num_palette_buffer[0];
// BlockStates
int bitsPerEntry = MathMan.log2nlz(num_palette - 1);
if (Settings.settings().PROTOCOL_SUPPORT_FIX || num_palette != 1) {
@ -364,17 +360,13 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
fieldStorage.set(dataPaletteBlocks, nmsBits);
fieldPalette.set(dataPaletteBlocks, blockStatePalettedContainer);
fieldBits.set(dataPaletteBlocks, bitsPerEntry);
setCount(ticking_blocks.size(), 4096 - air, levelChunkSection);
if (!fastmode) {
ticking_blocks.forEach((pos, ordinal) -> levelChunkSection
.setBlockState(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ(),
Block.stateById(ordinal)
));
}
} catch (final IllegalAccessException e) {
throw new RuntimeException(e);
}
if (!fastMode) {
levelChunkSection.recalcBlockCounts();
}
return levelChunkSection;
} catch (final Throwable e) {
Arrays.fill(blockToPalette, Integer.MAX_VALUE);
@ -386,11 +378,9 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
return new LevelChunkSection(layer);
}
public static void setCount(final int tickingBlockCount, final int nonEmptyBlockCount, final LevelChunkSection section) throws
IllegalAccessException {
fieldTickingFluidContent.setShort(section, (short) 0); // TODO FIXME
fieldTickingBlockCount.setShort(section, (short) tickingBlockCount);
fieldNonEmptyBlockCount.setShort(section, (short) nonEmptyBlockCount);
public static void clearCounts(final LevelChunkSection section) throws IllegalAccessException {
fieldTickingFluidContent.setShort(section, (short) 0);
fieldTickingBlockCount.setShort(section, (short) 0);
}
public static Biome[] getBiomeArray(ChunkBiomeContainer chunkBiomeContainer) {

View File

@ -0,0 +1,175 @@
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_17_R1_2;
import com.fastasyncworldedit.core.configuration.Settings;
import com.fastasyncworldedit.core.extent.processor.ProcessorScope;
import com.fastasyncworldedit.core.queue.IBatchProcessor;
import com.fastasyncworldedit.core.queue.IChunk;
import com.fastasyncworldedit.core.queue.IChunkGet;
import com.fastasyncworldedit.core.queue.IChunkSet;
import com.fastasyncworldedit.core.registry.state.PropertyKey;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.world.block.BlockTypes;
import com.sk89q.worldedit.world.block.BlockTypesCache;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.level.material.Fluids;
import javax.annotation.Nullable;
public class PaperweightPostProcessor implements IBatchProcessor {
@Override
public IChunkSet processSet(final IChunk chunk, final IChunkGet get, final IChunkSet set) {
return set;
}
@SuppressWarnings("deprecation")
@Override
public void postProcess(final IChunk chunk, final IChunkGet iChunkGet, final IChunkSet iChunkSet) {
boolean tickFluid = Settings.settings().EXPERIMENTAL.ALLOW_TICK_FLUIDS;
// The PostProcessor shouldn't be added, but just in case
if (!tickFluid) {
return;
}
PaperweightGetBlocks_Copy getBlocks = (PaperweightGetBlocks_Copy) iChunkGet;
layer:
for (int layer = iChunkSet.getMinSectionPosition(); layer <= iChunkSet.getMaxSectionPosition(); layer++) {
char[] set = iChunkSet.loadIfPresent(layer);
if (set == null) {
// No edit means no need to process
continue;
}
char[] get = null;
for (int i = 0; i < 4096; i++) {
char ordinal = set[i];
char replacedOrdinal = BlockTypesCache.ReservedIDs.__RESERVED__;
boolean fromGet = false; // Used for liquids
if (ordinal == BlockTypesCache.ReservedIDs.__RESERVED__) {
if (get == null) {
get = getBlocks.load(layer);
}
// If this is null, then it's because we're loading a layer in the range of 0->15, but blocks aren't
// actually being set
if (get == null) {
continue layer;
}
fromGet = true;
ordinal = replacedOrdinal = get[i];
}
if (ordinal == BlockTypesCache.ReservedIDs.__RESERVED__) {
continue;
} else if (!fromGet) { // if fromGet, don't do the same again
if (get == null) {
get = getBlocks.load(layer);
}
replacedOrdinal = get[i];
}
boolean ticking = BlockTypesCache.ticking[ordinal];
boolean replacedWasTicking = BlockTypesCache.ticking[replacedOrdinal];
boolean replacedWasLiquid = false;
BlockState replacedState = null;
if (!ticking) {
// If the block being replaced was not ticking, it cannot be a liquid
if (!replacedWasTicking) {
continue;
}
// If the block being replaced is not fluid, we do not need to worry
if (!(replacedWasLiquid =
(replacedState = BlockState.getFromOrdinal(replacedOrdinal)).getMaterial().isLiquid())) {
continue;
}
}
BlockState state = BlockState.getFromOrdinal(ordinal);
boolean liquid = state.getMaterial().isLiquid();
int x = i & 15;
int y = (i >> 8) & 15;
int z = (i >> 4) & 15;
BlockPos position = new BlockPos((chunk.getX() << 4) + x, (layer << 4) + y, (chunk.getZ() << 4) + z);
if (liquid || replacedWasLiquid) {
if (liquid) {
addFluid(getBlocks.serverLevel, state, position);
continue;
}
// If the replaced fluid (is?) adjacent to water. Do not bother to check adjacent chunks(sections) as this
// may be time consuming. Chances are any fluid blocks in adjacent chunks are being replaced or will end up
// being ticked anyway. We only need it to be "hit" once.
if (!wasAdjacentToWater(get, set, i, x, y, z)) {
continue;
}
addFluid(getBlocks.serverLevel, replacedState, position);
}
}
}
}
@Nullable
@Override
public Extent construct(final Extent child) {
throw new UnsupportedOperationException("Processing only");
}
@Override
public ProcessorScope getScope() {
return ProcessorScope.READING_SET_BLOCKS;
}
private boolean wasAdjacentToWater(char[] get, char[] set, int i, int x, int y, int z) {
if (set == null || get == null) {
return false;
}
char ordinal;
char reserved = BlockTypesCache.ReservedIDs.__RESERVED__;
if (x > 0 && set[i - 1] != reserved) {
if (BlockTypesCache.ticking[(ordinal = get[i - 1])] && isFluid(ordinal)) {
return true;
}
}
if (x < 15 && set[i + 1] != reserved) {
if (BlockTypesCache.ticking[(ordinal = get[i + 1])] && isFluid(ordinal)) {
return true;
}
}
if (z > 0 && set[i - 16] != reserved) {
if (BlockTypesCache.ticking[(ordinal = get[i - 16])] && isFluid(ordinal)) {
return true;
}
}
if (z < 15 && set[i + 16] != reserved) {
if (BlockTypesCache.ticking[(ordinal = get[i + 16])] && isFluid(ordinal)) {
return true;
}
}
if (y > 0 && set[i - 256] != reserved) {
if (BlockTypesCache.ticking[(ordinal = get[i - 256])] && isFluid(ordinal)) {
return true;
}
}
if (y < 15 && set[i + 256] != reserved) {
return BlockTypesCache.ticking[(ordinal = get[i + 256])] && isFluid(ordinal);
}
return false;
}
@SuppressWarnings("deprecation")
private boolean isFluid(char ordinal) {
return BlockState.getFromOrdinal(ordinal).getMaterial().isLiquid();
}
@SuppressWarnings("deprecation")
private void addFluid(final ServerLevel serverLevel, final BlockState replacedState, final BlockPos position) {
Fluid type;
if (replacedState.getBlockType() == BlockTypes.LAVA) {
type = (int) replacedState.getState(PropertyKey.LEVEL) == 0 ? Fluids.LAVA : Fluids.FLOWING_LAVA;
} else {
type = (int) replacedState.getState(PropertyKey.LEVEL) == 0 ? Fluids.WATER : Fluids.FLOWING_WATER;
}
serverLevel.getLiquidTicks().scheduleTick(
position,
type,
type.getTickDelay(serverLevel)
);
}
}