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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 975 additions and 399 deletions

4
.lift.toml Normal file
View File

@ -0,0 +1,4 @@
jdkVersion = "17"
build = "gradle clean build -x test"
tools = ["findsecbugs", "ErrorProne", "Semgrep", "Detekt", "ESLint", "Infer"]
ignoreRules = ["CatchAndPrintStackTrace", "ReferenceEquality", "FallThrough", "FutureReturnValueIgnored"]

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)
);
}
}

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;
@ -686,4 +687,9 @@ public final class PaperweightFaweAdapter extends CachedBukkitAdapter implements
}
}
@Override
public IBatchProcessor getTickingPostProcessor() {
return new PaperweightPostProcessor();
}
}

View File

@ -398,11 +398,10 @@ 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);
boolean fastmode = set.isFastMode() && Settings.settings().QUEUE.NO_TICK_FASTMODE;
// Remove existing tiles. Create a copy so that we can remove blocks
Map<BlockPos, BlockEntity> chunkTiles = new HashMap<>(nmsChunk.getBlockEntities());
@ -470,7 +469,6 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
LevelChunkSection newSection = PaperweightPlatformAdapter.newChunkSection(
layerNo,
new char[4096],
fastmode,
adapter,
biomeRegistry,
biomeData
@ -498,6 +496,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
bitMask |= 1 << getSectionIndex;
// 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);
@ -508,6 +507,11 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
LevelChunkSection newSection;
LevelChunkSection existingSection = levelChunkSections[getSectionIndex];
// Don't attempt to tick section whilst we're editing
if (existingSection != null) {
PaperweightPlatformAdapter.clearCounts(existingSection);
existingSection.tickingList.clear();
}
if (createCopy) {
char[] tmpLoad = loadPrivately(layerNo);
@ -529,7 +533,6 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
newSection = PaperweightPlatformAdapter.newChunkSection(
layerNo,
setArr,
fastmode,
adapter,
biomeRegistry,
biomeData
@ -547,9 +550,10 @@ 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)
PaperweightPlatformAdapter.clearCounts(existingSection);
existingSection.tickingList.clear();
DelegateSemaphore lock = PaperweightPlatformAdapter.applyLock(existingSection);
synchronized (lock) {
@ -583,7 +587,6 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
layerNo,
this::loadPrivately,
setArr,
fastmode,
adapter,
biomeRegistry,
biomeData
@ -1052,7 +1055,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
x,
y,
z,
biomeRegistry.get(ResourceLocation.tryParse(biomeType.getId()))
biomeRegistry.getOptional(ResourceLocation.tryParse(biomeType.getId())).orElseThrow()
);
}
}

View File

@ -18,6 +18,7 @@ import net.minecraft.server.level.ServerLevel;
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.LevelChunk;
import net.minecraft.world.level.chunk.PalettedContainer;
import javax.annotation.Nullable;
@ -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 PalettedContainer<Biome>[] biomes = null;
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

@ -11,7 +11,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;
@ -85,9 +84,9 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
public static final Field fieldPalette;
public static final Field fieldTickingFluidCount;
public static final Field fieldTickingBlockCount;
public static final Field fieldNonEmptyBlockCount;
private static final Field fieldTickingFluidCount;
private static final Field fieldTickingBlockCount;
private static final Field fieldNonEmptyBlockCount;
private static final MethodHandle methodGetVisibleChunk;
@ -301,16 +300,21 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
NMS conversion
*/
public static LevelChunkSection newChunkSection(
final int layer, final char[] blocks, boolean fastmode,
CachedBukkitAdapter adapter, Registry<Biome> biomeRegistry,
final int layer,
final char[] blocks,
CachedBukkitAdapter adapter,
Registry<Biome> biomeRegistry,
@Nullable PalettedContainer<Biome> biomes
) {
return newChunkSection(layer, null, blocks, fastmode, adapter, biomeRegistry, biomes);
return newChunkSection(layer, null, blocks, adapter, biomeRegistry, biomes);
}
public static LevelChunkSection newChunkSection(
final int layer, final Function<Integer, char[]> get, char[] set,
boolean fastmode, CachedBukkitAdapter adapter, Registry<Biome> biomeRegistry,
final int layer,
final Function<Integer, char[]> get,
char[] set,
CachedBukkitAdapter adapter,
Registry<Biome> biomeRegistry,
@Nullable PalettedContainer<Biome> biomes
) {
if (set == null) {
@ -321,24 +325,14 @@ 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);
Object configuration =
PalettedContainer.Strategy.SECTION_STATES.getConfiguration(new FakeIdMapBlock(num_palette), bitsPerEntry);
if (bitsPerEntry > 0 && bitsPerEntry < 5) {
bitsPerEntry = 4;
} else if (bitsPerEntry > 8) {
@ -365,7 +359,6 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
} else {
nmsBits = new SimpleBitStorage(bitsPerEntry, 4096, bits);
}
final Palette<net.minecraft.world.level.block.state.BlockState> blockStatePalette;
List<net.minecraft.world.level.block.state.BlockState> palette;
if (bitsPerEntry < 9) {
palette = new ArrayList<>();
@ -390,9 +383,6 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
palette
);
LevelChunkSection levelChunkSection;
try {
//fieldStorage.set(dataPaletteBlocks, nmsBits);
//fieldPalette.set(dataPaletteBlocks, blockStatePalettedContainer);
if (biomes == null) {
biomes = new PalettedContainer<>(
biomeRegistry,
@ -402,18 +392,6 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
);
}
levelChunkSection = new LevelChunkSection(layer, blockStatePalettedContainer, biomes);
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);
}
return levelChunkSection;
} catch (final Throwable e) {
@ -422,23 +400,21 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
}
}
@SuppressWarnings("deprecation") // Only deprecated in paper
private static LevelChunkSection newChunkSection(
int layer, Registry<Biome> biomeRegistry,
@Nullable PalettedContainer<Biome> biomes
) {
if (biomes == null) {
return new LevelChunkSection(layer, biomeRegistry);
}
PalettedContainer<net.minecraft.world.level.block.state.BlockState> dataPaletteBlocks = new PalettedContainer<>(
Block.BLOCK_STATE_REGISTRY,
Blocks.AIR.defaultBlockState(),
PalettedContainer.Strategy.SECTION_STATES,
null
);
PalettedContainer<Biome> biomesPalette = biomes != null ? biomes : new PalettedContainer<>(
biomeRegistry,
biomeRegistry.getOrThrow(Biomes.PLAINS),
PalettedContainer.Strategy.SECTION_BIOMES,
null
);
return new LevelChunkSection(layer, dataPaletteBlocks, biomesPalette);
return new LevelChunkSection(layer, dataPaletteBlocks, biomes);
}
/**
@ -541,11 +517,9 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
return biomePalettedContainer;
}
public static void setCount(final int tickingBlockCount, final int nonEmptyBlockCount, final LevelChunkSection section) throws
IllegalAccessException {
fieldTickingFluidCount.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 {
fieldTickingFluidCount.setShort(section, (short) 0);
fieldTickingBlockCount.setShort(section, (short) 0);
}
public static BiomeType adapt(Biome biome, LevelAccessor levelAccessor) {

View File

@ -0,0 +1,175 @@
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_18_R1;
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.scheduleTick(
position,
type,
type.getTickDelay(serverLevel)
);
}
}

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;
@ -690,4 +691,9 @@ public final class PaperweightFaweAdapter extends CachedBukkitAdapter implements
}
}
@Override
public IBatchProcessor getTickingPostProcessor() {
return new PaperweightPostProcessor();
}
}

View File

@ -99,7 +99,8 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
private final int maxHeight;
private final int minSectionPosition;
private final int maxSectionPosition;
private final IdMap<Holder<Biome>> biomeRegistry;
private final Registry<Biome> biomeRegistry;
private final IdMap<Holder<Biome>> biomeHolderIdMap;
private LevelChunkSection[] sections;
private LevelChunk levelChunk;
private DataLayer[] blockLight;
@ -124,7 +125,8 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
this.maxSectionPosition = maxHeight >> 4;
this.skyLight = new DataLayer[getSectionCount()];
this.blockLight = new DataLayer[getSectionCount()];
this.biomeRegistry = serverLevel.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY).asHolderIdMap();
this.biomeRegistry = serverLevel.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY);
this.biomeHolderIdMap = biomeRegistry.asHolderIdMap();
}
public int getChunkX() {
@ -399,11 +401,10 @@ 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);
boolean fastmode = set.isFastMode() && Settings.settings().QUEUE.NO_TICK_FASTMODE;
// Remove existing tiles. Create a copy so that we can remove blocks
Map<BlockPos, BlockEntity> chunkTiles = new HashMap<>(nmsChunk.getBlockEntities());
@ -466,12 +467,11 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
if (existingSection == null) {
PalettedContainer<Holder<Biome>> biomeData = PaperweightPlatformAdapter.getBiomePalettedContainer(
biomes[setSectionIndex],
biomeRegistry
biomeHolderIdMap
);
LevelChunkSection newSection = PaperweightPlatformAdapter.newChunkSection(
layerNo,
new char[4096],
fastmode,
adapter,
biomeRegistry,
biomeData
@ -527,19 +527,18 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
if (existingSection == null) {
PalettedContainer<Holder<Biome>> biomeData = biomes == null ? new PalettedContainer<>(
biomeRegistry,
biomeRegistry.byIdOrThrow(WorldEditPlugin
biomeHolderIdMap,
biomeHolderIdMap.byIdOrThrow(WorldEditPlugin
.getInstance()
.getBukkitImplAdapter()
.getInternalBiomeId(
BiomeTypes.PLAINS)),
PalettedContainer.Strategy.SECTION_BIOMES,
null
) : PaperweightPlatformAdapter.getBiomePalettedContainer(biomes[setSectionIndex], biomeRegistry);
) : PaperweightPlatformAdapter.getBiomePalettedContainer(biomes[setSectionIndex], biomeHolderIdMap);
newSection = PaperweightPlatformAdapter.newChunkSection(
layerNo,
setArr,
fastmode,
adapter,
biomeRegistry,
biomeData
@ -562,9 +561,10 @@ 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)
PaperweightPlatformAdapter.clearCounts(existingSection);
existingSection.tickingList.clear();
DelegateSemaphore lock = PaperweightPlatformAdapter.applyLock(existingSection);
synchronized (lock) {
@ -601,7 +601,6 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
layerNo,
this::loadPrivately,
setArr,
fastmode,
adapter,
biomeRegistry,
biomeData
@ -1070,7 +1069,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
x,
y,
z,
biomeRegistry.byIdOrThrow(WorldEditPlugin
biomeHolderIdMap.byIdOrThrow(WorldEditPlugin
.getInstance()
.getBukkitImplAdapter()
.getInternalBiomeId(biomeType))

View File

@ -20,6 +20,7 @@ import net.minecraft.server.level.ServerLevel;
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.LevelChunk;
import net.minecraft.world.level.chunk.PalettedContainer;
import javax.annotation.Nullable;
@ -37,13 +38,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 PalettedContainer<Holder<Biome>>[] biomes = null;
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

@ -13,7 +13,6 @@ import com.mojang.datafixers.util.Either;
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter;
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;
@ -84,10 +83,9 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
public static final Field fieldStorage;
public static final Field fieldPalette;
public static final Field fieldTickingFluidCount;
public static final Field fieldTickingBlockCount;
public static final Field fieldNonEmptyBlockCount;
private static final Field fieldTickingFluidCount;
private static final Field fieldTickingBlockCount;
private static final Field fieldNonEmptyBlockCount;
private static final MethodHandle methodGetVisibleChunk;
@ -303,16 +301,21 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
NMS conversion
*/
public static LevelChunkSection newChunkSection(
final int layer, final char[] blocks, boolean fastmode,
CachedBukkitAdapter adapter, IdMap<Holder<Biome>> biomeRegistry,
final int layer,
final char[] blocks,
CachedBukkitAdapter adapter,
Registry<Biome> biomeRegistry,
@Nullable PalettedContainer<Holder<Biome>> biomes
) {
return newChunkSection(layer, null, blocks, fastmode, adapter, biomeRegistry, biomes);
return newChunkSection(layer, null, blocks, adapter, biomeRegistry, biomes);
}
public static LevelChunkSection newChunkSection(
final int layer, final Function<Integer, char[]> get, char[] set,
boolean fastmode, CachedBukkitAdapter adapter, IdMap<Holder<Biome>> biomeRegistry,
final int layer,
final Function<Integer, char[]> get,
char[] set,
CachedBukkitAdapter adapter,
Registry<Biome> biomeRegistry,
@Nullable PalettedContainer<Holder<Biome>> biomes
) {
if (set == null) {
@ -323,24 +326,14 @@ 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);
Object configuration =
PalettedContainer.Strategy.SECTION_STATES.getConfiguration(new FakeIdMapBlock(num_palette), bitsPerEntry);
if (bitsPerEntry > 0 && bitsPerEntry < 5) {
bitsPerEntry = 4;
} else if (bitsPerEntry > 8) {
@ -367,7 +360,6 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
} else {
nmsBits = new SimpleBitStorage(bitsPerEntry, 4096, bits);
}
final Palette<net.minecraft.world.level.block.state.BlockState> blockStatePalette;
List<net.minecraft.world.level.block.state.BlockState> palette;
if (bitsPerEntry < 9) {
palette = new ArrayList<>();
@ -391,62 +383,43 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
nmsBits,
palette
);
LevelChunkSection levelChunkSection;
try {
//fieldStorage.set(dataPaletteBlocks, nmsBits);
//fieldPalette.set(dataPaletteBlocks, blockStatePalettedContainer);
if (biomes == null) {
IdMap<Holder<Biome>> biomeHolderIdMap = biomeRegistry.asHolderIdMap();
biomes = new PalettedContainer<>(
biomeRegistry,
biomeRegistry.byIdOrThrow(WorldEditPlugin
biomeHolderIdMap,
biomeHolderIdMap.byIdOrThrow(WorldEditPlugin
.getInstance()
.getBukkitImplAdapter()
.getInternalBiomeId(BiomeTypes.PLAINS)),
.getInternalBiomeId(
BiomeTypes.PLAINS)),
PalettedContainer.Strategy.SECTION_BIOMES,
null
);
}
levelChunkSection = new LevelChunkSection(layer, blockStatePalettedContainer, biomes);
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);
}
return levelChunkSection;
return new LevelChunkSection(layer, blockStatePalettedContainer, biomes);
} catch (final Throwable e) {
Arrays.fill(blockToPalette, Integer.MAX_VALUE);
throw e;
}
}
@SuppressWarnings("deprecation") // Only deprecated in paper
private static LevelChunkSection newChunkSection(
int layer, IdMap<Holder<Biome>> biomeRegistry,
int layer,
Registry<Biome> biomeRegistry,
@Nullable PalettedContainer<Holder<Biome>> biomes
) {
if (biomes == null) {
return new LevelChunkSection(layer, biomeRegistry);
}
PalettedContainer<net.minecraft.world.level.block.state.BlockState> dataPaletteBlocks = new PalettedContainer<>(
Block.BLOCK_STATE_REGISTRY,
Blocks.AIR.defaultBlockState(),
PalettedContainer.Strategy.SECTION_STATES,
null
);
PalettedContainer<Holder<Biome>> biomesPalette = biomes != null ? biomes : new PalettedContainer<>(
biomeRegistry,
biomeRegistry.byIdOrThrow(WorldEditPlugin
.getInstance()
.getBukkitImplAdapter()
.getInternalBiomeId(BiomeTypes.PLAINS)),
PalettedContainer.Strategy.SECTION_BIOMES,
null
);
return new LevelChunkSection(layer, dataPaletteBlocks, biomesPalette);
return new LevelChunkSection(layer, dataPaletteBlocks, biomes);
}
/**
@ -556,11 +529,9 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
return biomePalettedContainer;
}
public static void setCount(final int tickingBlockCount, final int nonEmptyBlockCount, final LevelChunkSection section) throws
IllegalAccessException {
fieldTickingFluidCount.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 {
fieldTickingFluidCount.setShort(section, (short) 0);
fieldTickingBlockCount.setShort(section, (short) 0);
}
public static BiomeType adapt(Holder<Biome> biome, LevelAccessor levelAccessor) {

View File

@ -0,0 +1,175 @@
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_18_R2;
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.scheduleTick(
position,
type,
type.getTickDelay(serverLevel)
);
}
}

View File

@ -1,25 +1,21 @@
package com.fastasyncworldedit.bukkit.adapter;
import com.fastasyncworldedit.core.FAWEPlatformAdapterImpl;
import com.fastasyncworldedit.core.configuration.Settings;
import com.fastasyncworldedit.core.queue.IChunkGet;
import com.fastasyncworldedit.core.util.MathMan;
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.world.block.BlockTypesCache;
import java.util.Map;
import java.util.function.Function;
public class NMSAdapter implements FAWEPlatformAdapterImpl {
public static int createPalette(
int[] blockToPalette, int[] paletteToBlock, int[] blocksCopy,
int[] num_palette_buffer, char[] set, Map<BlockVector3, Integer> ticking_blocks, boolean fastmode,
int[] blockToPalette,
int[] paletteToBlock,
int[] blocksCopy,
char[] set,
CachedBukkitAdapter adapter
) {
int air = 0;
int num_palette = 0;
for (int i = 0; i < 4096; i++) {
char ordinal = set[i];
@ -42,52 +38,26 @@ public class NMSAdapter implements FAWEPlatformAdapterImpl {
}
System.arraycopy(adapter.getOrdinalToIbdID(), 0, blockToPalette, 0, adapter.getOrdinalToIbdID().length);
}
char lastOrdinal = 0;
boolean lastticking = false;
boolean tick_placed = Settings.settings().EXPERIMENTAL.ALLOW_TICK_PLACED;
for (int i = 0; i < 4096; i++) {
char ordinal = set[i];
switch (ordinal) {
case BlockTypesCache.ReservedIDs.__RESERVED__:
if (ordinal == BlockTypesCache.ReservedIDs.__RESERVED__) {
ordinal = BlockTypesCache.ReservedIDs.AIR;
case BlockTypesCache.ReservedIDs.AIR, BlockTypesCache.ReservedIDs.CAVE_AIR, BlockTypesCache.ReservedIDs.VOID_AIR:
air++;
break;
default:
if (!fastmode && !tick_placed) {
boolean ticking;
if (ordinal != lastOrdinal) {
ticking = BlockTypesCache.ticking[ordinal];
lastOrdinal = ordinal;
lastticking = ticking;
} else {
ticking = lastticking;
}
if (ticking) {
BlockState state = BlockState.getFromOrdinal(ordinal);
ticking_blocks
.put(
BlockVector3.at(i & 15, (i >> 8) & 15, (i >> 4) & 15),
WorldEditPlugin.getInstance().getBukkitImplAdapter()
.getInternalBlockStateId(state).orElse(0)
);
}
}
}
int palette = blockToPalette[ordinal];
blocksCopy[i] = palette;
}
num_palette_buffer[0] = num_palette;
return air;
return num_palette;
}
public static int createPalette(
int layer, int[] blockToPalette, int[] paletteToBlock,
int[] blocksCopy, int[] num_palette_buffer, Function<Integer, char[]> get, char[] set,
Map<BlockVector3, Integer> ticking_blocks, boolean fastmode,
int layer,
int[] blockToPalette,
int[] paletteToBlock,
int[] blocksCopy,
Function<Integer, char[]> get,
char[] set,
CachedBukkitAdapter adapter
) {
int air = 0;
int num_palette = 0;
char[] getArr = null;
for (int i = 0; i < 4096; i++) {
@ -117,73 +87,21 @@ public class NMSAdapter implements FAWEPlatformAdapterImpl {
}
System.arraycopy(adapter.getOrdinalToIbdID(), 0, blockToPalette, 0, adapter.getOrdinalToIbdID().length);
}
char lastOrdinal = BlockTypesCache.ReservedIDs.__RESERVED__;
boolean lastticking = false;
boolean tick_placed = Settings.settings().EXPERIMENTAL.ALLOW_TICK_PLACED;
boolean tick_existing = Settings.settings().EXPERIMENTAL.ALLOW_TICK_EXISTING;
for (int i = 0; i < 4096; i++) {
char ordinal = set[i];
switch (ordinal) {
case BlockTypesCache.ReservedIDs.__RESERVED__ -> {
char ordinal= set[i];
if (ordinal == BlockTypesCache.ReservedIDs.__RESERVED__) {
if (getArr == null) {
getArr = get.apply(layer);
}
set[i] = switch (ordinal = getArr[i]) {
case BlockTypesCache.ReservedIDs.__RESERVED__:
if ((ordinal = getArr[i]) == BlockTypesCache.ReservedIDs.__RESERVED__) {
ordinal = BlockTypesCache.ReservedIDs.AIR;
case BlockTypesCache.ReservedIDs.AIR, BlockTypesCache.ReservedIDs.CAVE_AIR, BlockTypesCache.ReservedIDs.VOID_AIR:
air++;
yield ordinal;
default:
if (!fastmode && !tick_placed && tick_existing) {
boolean ticking;
if (ordinal != lastOrdinal) {
ticking = BlockTypesCache.ticking[ordinal];
lastOrdinal = ordinal;
lastticking = ticking;
} else {
ticking = lastticking;
}
if (ticking) {
BlockState state = BlockState.getFromOrdinal(ordinal);
ticking_blocks.put(BlockVector3.at(i & 15, (i >> 8) & 15, (i >> 4) & 15),
WorldEditPlugin
.getInstance()
.getBukkitImplAdapter()
.getInternalBlockStateId(state)
.orElse(0)
);
}
}
yield ordinal;
};
}
case BlockTypesCache.ReservedIDs.AIR, BlockTypesCache.ReservedIDs.CAVE_AIR, BlockTypesCache.ReservedIDs.VOID_AIR -> air++;
}
if (!fastmode && tick_placed) {
boolean ticking;
if (ordinal != lastOrdinal) {
ticking = BlockTypesCache.ticking[ordinal];
lastOrdinal = ordinal;
lastticking = ticking;
} else {
ticking = lastticking;
}
if (ticking) {
BlockState state = BlockState.getFromOrdinal(ordinal);
ticking_blocks.put(
BlockVector3.at(i & 15, (i >> 8) & 15, (i >> 4) & 15),
WorldEditPlugin.getInstance().getBukkitImplAdapter()
.getInternalBlockStateId(state).orElse(0)
);
}
}
int palette = blockToPalette[ordinal];
blocksCopy[i] = palette;
}
num_palette_buffer[0] = num_palette;
return air;
return num_palette;
}
@Override

View File

@ -20,7 +20,9 @@
package com.sk89q.worldedit.bukkit;
import com.fastasyncworldedit.bukkit.util.MinecraftVersion;
import com.fastasyncworldedit.core.configuration.Settings;
import com.fastasyncworldedit.core.extent.processor.lighting.RelighterFactory;
import com.fastasyncworldedit.core.queue.IBatchProcessor;
import com.google.common.collect.Sets;
import com.sk89q.bukkit.util.CommandInfo;
import com.sk89q.bukkit.util.CommandRegistration;
@ -289,5 +291,17 @@ public class BukkitServerInterface extends AbstractPlatform implements MultiUser
public int versionMaxY() {
return MinecraftVersion.getCurrent().isEqualOrHigherThan(MinecraftVersion.CAVES_18) ? 319 : 255;
}
@Override
public IBatchProcessor getPlatformPostProcessor(boolean fastMode) {
boolean tickFluid = Settings.settings().EXPERIMENTAL.ALLOW_TICK_FLUIDS;
if (!tickFluid) {
return null;
}
if (Settings.settings().QUEUE.NO_TICK_FASTMODE && fastMode) {
return null;
}
return this.plugin.getBukkitImplAdapter().getTickingPostProcessor();
}
//FAWE end
}

View File

@ -24,6 +24,7 @@ import com.fastasyncworldedit.bukkit.adapter.IBukkitAdapter;
import com.fastasyncworldedit.bukkit.adapter.NMSRelighterFactory;
import com.fastasyncworldedit.core.Fawe;
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.sk89q.jnbt.AdventureNBTConverter;
@ -350,5 +351,15 @@ public interface BukkitImplAdapter<T> extends IBukkitAdapter {
default Map<String, List<Property<?>>> getAllProperties() {
return Collections.emptyMap();
}
/**
* Returns an {@link IBatchProcessor} instance for post-processing of chunks to sort ticking of placed/existing blocks and
* fluids if the plugin is configured to do so
*
* @since TODO
*/
default IBatchProcessor getTickingPostProcessor() {
return null;
}
//FAWE end
}

View File

@ -604,7 +604,7 @@ public enum FaweCache implements Trimable {
} else if (throwable.getCause() instanceof FaweException) {
handleFaweException((FaweException) throwable.getCause());
} else {
int hash = throwable.getMessage().hashCode();
int hash = throwable.getMessage() != null ? throwable.getMessage().hashCode() : 0;
if (hash != lastException) {
lastException = hash;
LOGGER.catching(throwable);

View File

@ -567,7 +567,8 @@ public class Settings extends Config {
public int DISCARD_AFTER_MS = 60000;
@Comment({
"When using fastmode also do not bother to fix existing ticking blocks"
"When using fastmode do not bother to tick existing/placed blocks/fluids",
"Only works in versions up to 1.17.1"
})
public boolean NO_TICK_FASTMODE = true;
@ -625,16 +626,11 @@ public class Settings extends Config {
public boolean OTHER = false;
@Comment({
"Allow blocks placed by WorldEdit to tick. This could cause the big lags.",
"This has no effect on existing blocks one way or the other."
"Allow fluids placed by FAWE to tick (flow). This could cause the big lags.",
"This has no effect on existing blocks one way or the other.",
"Changes due to fluid flow will not be tracked by history, thus may have unintended consequences"
})
public boolean ALLOW_TICK_PLACED = false;
@Comment({
"Force re-ticking of existing blocks not edited by FAWE.",
"This will increase time taken slightly."
})
public boolean ALLOW_TICK_EXISTING = true;
public boolean ALLOW_TICK_FLUIDS = false;
@Comment({
"Sets a maximum limit (in kb) for the size of a player's schematics directory (per-player mode only)",

View File

@ -167,11 +167,6 @@ public class DisallowedBlocksExtent extends AbstractDelegateExtent implements IB
return set;
}
@Override
public Future<IChunkSet> postProcessSet(final IChunk chunk, final IChunkGet get, final IChunkSet set) {
return CompletableFuture.completedFuture(set);
}
@Nullable
@Override
public Extent construct(final Extent child) {

View File

@ -56,9 +56,4 @@ public class HeightBoundExtent extends FaweRegionExtent {
return null;
}
@Override
public Future<IChunkSet> postProcessSet(IChunk chunk, IChunkGet get, IChunkSet set) {
return CompletableFuture.completedFuture(set);
}
}

View File

@ -178,8 +178,13 @@ public class MultiRegionExtent extends FaweRegionExtent {
}
@Override
public Future<IChunkSet> postProcessSet(IChunk chunk, IChunkGet get, IChunkSet set) {
public Future<?> postProcessSet(final IChunk chunk, final IChunkGet get, final IChunkSet set) {
return intersection.postProcessSet(chunk, get, set);
}
@Override
public void postProcess(IChunk chunk, IChunkGet get, IChunkSet set) {
intersection.postProcess(chunk, get, set);
}
}

View File

@ -342,7 +342,12 @@ public class NullExtent extends FaweRegionExtent implements IBatchProcessor {
}
@Override
public Future<IChunkSet> postProcessSet(IChunk chunk, IChunkGet get, IChunkSet set) {
public Future<?> postProcessSet(IChunk chunk, IChunkGet get, IChunkSet set) {
throw reason;
}
@Override
public void postProcess(IChunk chunk, IChunkGet get, IChunkSet set) {
throw reason;
}

View File

@ -46,10 +46,17 @@ public class SingleRegionExtent extends FaweRegionExtent {
}
@Override
public Future<IChunkSet> postProcessSet(IChunk chunk, IChunkGet get, IChunkSet set) {
public Future<?> postProcessSet(IChunk chunk, IChunkGet get, IChunkSet set) {
// Most likely will do nothing, but perhaps people will find some fun way of using this via API (though doubtful)
return region.postProcessSet(chunk, get, set);
}
@Override
public void postProcess(IChunk chunk, IChunkGet get, IChunkSet set) {
// Most likely will do nothing, but perhaps people will find some fun way of using this via API (though doubtful)
region.postProcess(chunk, get, set);
}
@Override
public boolean processGet(int chunkX, int chunkZ) {
return region.containsChunk(chunkX, chunkZ);

View File

@ -143,11 +143,6 @@ public class StripNBTExtent extends AbstractDelegateExtent implements IBatchProc
return set;
}
@Override
public Future<IChunkSet> postProcessSet(final IChunk chunk, final IChunkGet get, final IChunkSet set) {
return CompletableFuture.completedFuture(set);
}
@Nullable
@Override
public Extent construct(final Extent child) {

View File

@ -28,10 +28,15 @@ public class BatchProcessorHolder implements IBatchProcessorHolder {
}
@Override
public Future<IChunkSet> postProcessSet(IChunk chunk, IChunkGet get, IChunkSet set) {
public Future<?> postProcessSet(IChunk chunk, IChunkGet get, IChunkSet set) {
return getPostProcessor().postProcessSet(chunk, get, set);
}
@Override
public void postProcess(IChunk chunk, IChunkGet get, IChunkSet set) {
getPostProcessor().postProcess(chunk, get, set);
}
@Override
public void flush() {
getProcessor().flush();

View File

@ -29,13 +29,6 @@ public final class EmptyBatchProcessor implements IBatchProcessor {
return set;
}
@Override
@Nonnull
public Future<IChunkSet> postProcessSet(@Nullable IChunk chunk, @Nullable IChunkGet get, @Nullable IChunkSet set) {
// Doesn't need to do anything
return CompletableFuture.completedFuture(set);
}
@Nonnull
public IBatchProcessor join(@Nullable IBatchProcessor other) {
return other;

View File

@ -31,10 +31,15 @@ public interface IBatchProcessorHolder extends IBatchProcessor {
}
@Override
default Future<IChunkSet> postProcessSet(IChunk chunk, IChunkGet get, IChunkSet set) {
default Future<?> postProcessSet(IChunk chunk, IChunkGet get, IChunkSet set) {
return getPostProcessor().postProcessSet(chunk, get, set);
}
@Override
default void postProcess(IChunk chunk, IChunkGet get, IChunkSet set) {
getPostProcessor().postProcess(chunk, get, set);
}
@Override
default boolean processGet(int chunkX, int chunkZ) {
return getProcessor().processGet(chunkX, chunkZ);

View File

@ -9,6 +9,7 @@ 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.util.MultiFuture;
import com.fastasyncworldedit.core.util.StringMan;
import com.google.common.cache.LoadingCache;
import com.sk89q.worldedit.extent.Extent;
@ -24,7 +25,6 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;
import java.util.function.Supplier;
@ -129,19 +129,15 @@ public class MultiBatchProcessor implements IBatchProcessor {
}
@Override
public Future<IChunkSet> postProcessSet(IChunk chunk, IChunkGet get, IChunkSet set) {
try {
public Future<?> postProcessSet(IChunk chunk, IChunkGet get, IChunkSet set) {
List<Future<?>> futures = new ArrayList<>();
for (IBatchProcessor processor : processors) {
try {
// We do NOT want to edit blocks in post processing
if (processor.getScope() != ProcessorScope.READING_SET_BLOCKS) {
continue;
}
set = processor.postProcessSet(chunk, get, set).get();
if (set == null) {
return null;
}
}
return CompletableFuture.completedFuture(set);
futures.add(processor.postProcessSet(chunk, get, set));
} catch (Throwable e) {
if (e instanceof FaweException) {
Fawe.handleFaweException(faweExceptionReasonsUsed, (FaweException) e, LOGGER);
@ -149,7 +145,7 @@ public class MultiBatchProcessor implements IBatchProcessor {
Fawe.handleFaweException(faweExceptionReasonsUsed, (FaweException) e.getCause(), LOGGER);
} else {
String message = e.getMessage();
int hash = message.hashCode();
int hash = message != null ? message.hashCode() : 0;
if (lastException != hash) {
lastException = hash;
exceptionCount = 0;
@ -159,7 +155,38 @@ public class MultiBatchProcessor implements IBatchProcessor {
LOGGER.warn(message);
}
}
return null;
}
}
return new MultiFuture(futures);
}
@Override
public void postProcess(IChunk chunk, IChunkGet get, IChunkSet set) {
for (IBatchProcessor processor : processors) {
try {
// We do NOT want to edit blocks in post processing
if (processor.getScope() != ProcessorScope.READING_SET_BLOCKS) {
continue;
}
processor.postProcess(chunk, get, set);
} catch (Throwable e) {
if (e instanceof FaweException) {
Fawe.handleFaweException(faweExceptionReasonsUsed, (FaweException) e, LOGGER);
} else if (e.getCause() instanceof FaweException) {
Fawe.handleFaweException(faweExceptionReasonsUsed, (FaweException) e.getCause(), LOGGER);
} else {
String message = e.getMessage();
int hash = message != null ? message.hashCode() : 0;
if (lastException != hash) {
lastException = hash;
exceptionCount = 0;
LOGGER.catching(e);
} else if (exceptionCount < Settings.settings().QUEUE.PARALLEL_THREADS) {
exceptionCount++;
LOGGER.warn(message);
}
}
}
}
}

View File

@ -24,11 +24,6 @@ public final class NullProcessor implements IBatchProcessor {
return null;
}
@Nullable
public Future<IChunkSet> postProcessSet(@Nonnull IChunk chunk, @Nonnull IChunkGet get, @Nonnull IChunkSet set) {
return null;
}
@Nonnull
public Extent construct(@Nonnull Extent child) {
return new NullExtent();

View File

@ -136,11 +136,6 @@ public class HeightmapProcessor implements IBatchProcessor {
return set;
}
@Override
public Future<IChunkSet> postProcessSet(IChunk chunk, IChunkGet get, IChunkSet set) {
return CompletableFuture.completedFuture(set);
}
@Override
@Nullable
public Extent construct(Extent child) {

View File

@ -46,11 +46,6 @@ public class RelightProcessor implements IBatchProcessor {
return set;
}
@Override
public Future<IChunkSet> postProcessSet(IChunk chunk, IChunkGet get, IChunkSet set) {
return CompletableFuture.completedFuture(set);
}
@Override
public @Nullable
Extent construct(Extent child) {

View File

@ -228,8 +228,13 @@ public abstract class AbstractChangeSet implements ChangeSet, IBatchProcessor {
}
@Override
public Future<IChunkSet> postProcessSet(final IChunk chunk, final IChunkGet get, final IChunkSet set) {
return (Future<IChunkSet>) addWriteTask(() -> processSet(chunk, get, set));
public void postProcess(final IChunk chunk, final IChunkGet get, final IChunkSet set) {
addWriteTask(() -> processSet(chunk, get, set));
}
@Override
public Future<?> postProcessSet(final IChunk chunk, final IChunkGet get, final IChunkSet set) {
return addWriteTask(() -> processSet(chunk, get, set));
}
@Override

View File

@ -6,13 +6,12 @@ import com.fastasyncworldedit.core.extent.processor.ProcessorScope;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.internal.util.LogManagerCompat;
import com.sk89q.worldedit.math.BlockVector3;
import org.apache.logging.log4j.Logger;
import javax.annotation.Nullable;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.function.Function;
@ -23,7 +22,27 @@ public interface IBatchProcessor {
*/
IChunkSet processSet(IChunk chunk, IChunkGet get, IChunkSet set);
Future<IChunkSet> postProcessSet(IChunk chunk, IChunkGet get, IChunkSet set);
/**
* Post-process a chunk that has been edited. Set should NOT be modified here, changes will NOT be flushed to the world,
* but MAY be flushed to history. Defaults to nothing as most Processors will not use it. Post-processors that are not
* technically blocking should override this method to allow post-processors to become blocking if required.
*/
default Future<?> postProcessSet(IChunk chunk, IChunkGet get, IChunkSet set) {
// Do not need to default to below method. FAWE itself by default will only call the method below.
return CompletableFuture.completedFuture(null);
}
/**
* Post-process a chunk that has been edited. Set should NOT be modified here, changes will NOT be flushed to the world,
* but MAY be flushed to history. Defaults to nothing as most Processors will not use it. If the post-processor will run
* tasks asynchronously/not be blocking, use {@link IBatchProcessor#postProcessSet} to return a Future.
*
* @since TODO
*/
default void postProcess(IChunk chunk, IChunkGet get, IChunkSet set) {
// Default to above for compatibility and to ensure whatever method is overridden by child classes is called
postProcessSet(chunk, get, set);
}
default boolean processGet(int chunkX, int chunkZ) {
return true;

View File

@ -145,7 +145,7 @@ public class ParallelQueueExtent extends PassthroughExtent {
}
} catch (Throwable e) {
String message = e.getMessage();
int hash = message.hashCode();
int hash = message != null ? message.hashCode() : 0;
if (lastException != hash) {
lastException = hash;
exceptionCount = 0;

View File

@ -401,7 +401,7 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen
Fawe.handleFaweException(faweExceptionReasonsUsed, (FaweException) e.getCause(), LOGGER);
} else {
String message = e.getMessage();
int hash = message.hashCode();
int hash = message != null ? message.hashCode() : 0;
if (lastException != hash) {
lastException = hash;
exceptionCount = 0;
@ -441,7 +441,7 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen
Fawe.handleFaweException(faweExceptionReasonsUsed, (FaweException) e.getCause(), LOGGER);
} else {
String message = e.getMessage();
int hash = message.hashCode();
int hash = message != null ? message.hashCode() : 0;
if (lastException != hash) {
lastException = hash;
exceptionCount = 0;

View File

@ -1010,7 +1010,7 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
return get.call(set, finalize);
} finally {
if (postProcess) {
getExtent().postProcessSet(this, get.getCopy(), set);
getExtent().postProcess(this, get.getCopy(), set);
}
}
}

View File

@ -0,0 +1,55 @@
package com.fastasyncworldedit.core.util;
import javax.annotation.Nonnull;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class MultiFuture implements Future<Object[]> {
private final List<Future<?>> futures;
public MultiFuture(List<Future<?>> futures) {
this.futures = futures;
}
@Override
public boolean cancel(final boolean mayInterruptIfRunning) {
return futures.stream().allMatch(f -> f.cancel(mayInterruptIfRunning));
}
@Override
public boolean isCancelled() {
return futures.stream().allMatch(Future::isCancelled);
}
@Override
public boolean isDone() {
return futures.stream().allMatch(Future::isDone);
}
@Override
public Object[] get() {
return futures.stream().map(f -> {
try {
return f.get();
} catch (InterruptedException | ExecutionException e) {
return e;
}
}).toArray();
}
@Override
public Object[] get(final long timeout, @Nonnull final TimeUnit unit) {
return futures.stream().map(f -> {
try {
return f.get(timeout / futures.size(), unit);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
return e;
}
}).toArray();
}
}

View File

@ -45,6 +45,7 @@ import com.fastasyncworldedit.core.history.changeset.BlockBagChangeSet;
import com.fastasyncworldedit.core.history.changeset.NullChangeSet;
import com.fastasyncworldedit.core.limit.FaweLimit;
import com.fastasyncworldedit.core.limit.PropertyRemap;
import com.fastasyncworldedit.core.queue.IBatchProcessor;
import com.fastasyncworldedit.core.queue.IQueueChunk;
import com.fastasyncworldedit.core.queue.IQueueExtent;
import com.fastasyncworldedit.core.queue.implementation.ParallelQueueExtent;
@ -560,7 +561,7 @@ public final class EditSessionBuilder {
}
}
}
// There's no need to do lighting (and it'll also just be a pain to implement) if we're not placing chunks
// There's no need to do the below (and it'll also just be a pain to implement) if we're not placing chunks
if (placeChunks) {
if (((relightMode != null && relightMode != RelightMode.NONE) || (relightMode == null && Settings.settings().LIGHTING.MODE > 0))) {
relighter = WorldEdit.getInstance().getPlatformManager()
@ -569,6 +570,22 @@ public final class EditSessionBuilder {
extent.addProcessor(new RelightProcessor(relighter));
}
extent.addProcessor(new HeightmapProcessor(world.getMinY(), world.getMaxY()));
IBatchProcessor platformProcessor = WorldEdit
.getInstance()
.getPlatformManager()
.queryCapability(Capability.WORLD_EDITING)
.getPlatformProcessor(fastMode);
if (platformProcessor != null) {
extent.addProcessor(platformProcessor);
}
IBatchProcessor platformPostProcessor = WorldEdit
.getInstance()
.getPlatformManager()
.queryCapability(Capability.WORLD_EDITING)
.getPlatformPostProcessor(fastMode);
if (platformPostProcessor != null) {
extent.addPostProcessor(platformPostProcessor);
}
} else {
relighter = NullRelighter.INSTANCE;
}

View File

@ -21,6 +21,7 @@ package com.sk89q.worldedit.extension.platform;
import com.fastasyncworldedit.core.extent.processor.lighting.Relighter;
import com.fastasyncworldedit.core.extent.processor.lighting.RelighterFactory;
import com.fastasyncworldedit.core.queue.IBatchProcessor;
import com.sk89q.worldedit.LocalConfiguration;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.entity.Player;
@ -248,5 +249,24 @@ public interface Platform extends Keyed {
* @since 2.0.0
*/
int versionMaxY();
/**
* Get a {@link IBatchProcessor} to be used in edit processing. Null if not required.
* @since TODO
*/
@Nullable
default IBatchProcessor getPlatformProcessor(boolean fastMode) {
return null;
}
/**
* Get a {@link IBatchProcessor} to be used in edit post-processing. Used for things such as tick-placed and tick fluids.
* Null if not required.
* @since TODO
*/
@Nullable
default IBatchProcessor getPlatformPostProcessor(boolean fastMode) {
return null;
}
//FAWE end
}

View File

@ -111,12 +111,6 @@ public class MaskingExtent extends AbstractDelegateExtent implements IBatchProce
return filter.filter(chunk, get, set, MaskingExtent.this);
}
@Override
public Future<IChunkSet> postProcessSet(IChunk chunk, IChunkGet get, IChunkSet set) {
// This should not do anything otherwise dangerous...
return CompletableFuture.completedFuture(set);
}
@Override
public void applyBlock(final FilterBlock block) {
if (!this.mask.test(block)) {

View File

@ -471,12 +471,6 @@ public interface Region extends Iterable<BlockVector3>, Cloneable, IBatchProcess
}
}
@Override
default Future<IChunkSet> postProcessSet(IChunk chunk, IChunkGet get, IChunkSet set) {
// Doesn't need to do anything
return CompletableFuture.completedFuture(set);
}
@Override
default Extent construct(Extent child) {
if (isGlobal()) {

View File

@ -23,6 +23,7 @@ import com.fastasyncworldedit.core.configuration.Caption;
import com.fastasyncworldedit.core.queue.IChunk;
import com.fastasyncworldedit.core.queue.IChunkGet;
import com.fastasyncworldedit.core.queue.IChunkSet;
import com.fastasyncworldedit.core.util.MultiFuture;
import com.google.common.collect.Iterators;
import com.google.common.collect.Sets;
import com.sk89q.worldedit.math.BlockVector2;
@ -35,6 +36,7 @@ import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Future;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
@ -172,6 +174,15 @@ public class RegionIntersection extends AbstractRegion {
return null;
}
@Override
public Future<?> postProcessSet(final IChunk chunk, final IChunkGet get, final IChunkSet set) {
final ArrayList<Future<?>> futures = new ArrayList<>();
for (Region region : regions) {
futures.add(region.postProcessSet(chunk, get, set));
}
return new MultiFuture(futures);
}
@Override
public IChunkSet processSet(IChunk chunk, IChunkGet get, IChunkSet set, boolean asBlacklist) {
if (!asBlacklist) {

View File

@ -198,6 +198,9 @@ public class BlockTypesCache {
public static final BlockType[] values;
public static final BlockState[] states;
/**
* Array of blockstates in order of ordinal indicating if the block ticks, e.g. leaves, water
*/
public static final boolean[] ticking;
private static final Map<String, List<Property<?>>> allProperties = new HashMap<>();
@ -283,7 +286,8 @@ public class BlockTypesCache {
String enumName = (typeName.startsWith("minecraft:") ? typeName.substring(10) : typeName).toUpperCase(Locale.ROOT);
int oldsize = states.size();
BlockType existing = new BlockType(id, internalId, states);
tickList.addAll(Collections.nCopies(states.size() - oldsize, existing.getMaterial().isTicksRandomly()));
tickList.addAll(Collections.nCopies(states.size() - oldsize,
existing.getMaterial().isTicksRandomly() || existing.getMaterial().isLiquid()));
// register states
BlockType.REGISTRY.register(typeName, existing);
String nameSpace = typeName.substring(0, typeName.indexOf(':'));