diff --git a/.lift.toml b/.lift.toml new file mode 100644 index 000000000..ce8dc2d05 --- /dev/null +++ b/.lift.toml @@ -0,0 +1,4 @@ +jdkVersion = "17" +build = "gradle clean build -x test" +tools = ["findsecbugs", "ErrorProne", "Semgrep", "Detekt", "ESLint", "Infer"] +ignoreRules = ["CatchAndPrintStackTrace", "ReferenceEquality", "FallThrough", "FutureReturnValueIgnored"] diff --git a/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/PaperweightFaweAdapter.java index b69272704..0bfdfd0b9 100644 --- a/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/PaperweightFaweAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/PaperweightFaweAdapter.java @@ -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(); + } + } diff --git a/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/PaperweightGetBlocks.java b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/PaperweightGetBlocks.java index 75675d008..9aca32b59 100644 --- a/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/PaperweightGetBlocks.java +++ b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/PaperweightGetBlocks.java @@ -410,7 +410,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc @SuppressWarnings("rawtypes") public synchronized > 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(); diff --git a/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/PaperweightGetBlocks_Copy.java b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/PaperweightGetBlocks_Copy.java index 68d284305..5d9c56092 100644 --- a/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/PaperweightGetBlocks_Copy.java +++ b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/PaperweightGetBlocks_Copy.java @@ -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()][]; } diff --git a/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/PaperweightPlatformAdapter.java b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/PaperweightPlatformAdapter.java index 12392d931..b9d2ce9a0 100644 --- a/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/PaperweightPlatformAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/PaperweightPlatformAdapter.java @@ -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 get, char[] set, - boolean fastmode, CachedBukkitAdapter adapter + final int layer, + final Function 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 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) { diff --git a/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/PaperweightPostProcessor.java b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/PaperweightPostProcessor.java new file mode 100644 index 000000000..0833c6ecb --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_17_1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_17_R1_2/PaperweightPostProcessor.java @@ -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) + ); + } + +} diff --git a/worldedit-bukkit/adapters/adapter-1_18/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R1/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_18/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R1/PaperweightFaweAdapter.java index b41718636..f31a81c61 100644 --- a/worldedit-bukkit/adapters/adapter-1_18/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R1/PaperweightFaweAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_18/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R1/PaperweightFaweAdapter.java @@ -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(); + } + } diff --git a/worldedit-bukkit/adapters/adapter-1_18/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R1/PaperweightGetBlocks.java b/worldedit-bukkit/adapters/adapter-1_18/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R1/PaperweightGetBlocks.java index 7a3f217a6..9a2d50fb2 100644 --- a/worldedit-bukkit/adapters/adapter-1_18/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R1/PaperweightGetBlocks.java +++ b/worldedit-bukkit/adapters/adapter-1_18/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R1/PaperweightGetBlocks.java @@ -398,11 +398,10 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc @SuppressWarnings("rawtypes") public synchronized > 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 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() ); } } diff --git a/worldedit-bukkit/adapters/adapter-1_18/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R1/PaperweightGetBlocks_Copy.java b/worldedit-bukkit/adapters/adapter-1_18/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R1/PaperweightGetBlocks_Copy.java index 8c28e701f..56b9d2410 100644 --- a/worldedit-bukkit/adapters/adapter-1_18/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R1/PaperweightGetBlocks_Copy.java +++ b/worldedit-bukkit/adapters/adapter-1_18/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R1/PaperweightGetBlocks_Copy.java @@ -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[] 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()][]; } diff --git a/worldedit-bukkit/adapters/adapter-1_18/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R1/PaperweightPlatformAdapter.java b/worldedit-bukkit/adapters/adapter-1_18/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R1/PaperweightPlatformAdapter.java index 2e44f4caa..15aea4cfa 100644 --- a/worldedit-bukkit/adapters/adapter-1_18/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R1/PaperweightPlatformAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_18/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R1/PaperweightPlatformAdapter.java @@ -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 biomeRegistry, + final int layer, + final char[] blocks, + CachedBukkitAdapter adapter, + Registry biomeRegistry, @Nullable PalettedContainer 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 get, char[] set, - boolean fastmode, CachedBukkitAdapter adapter, Registry biomeRegistry, + final int layer, + final Function get, + char[] set, + CachedBukkitAdapter adapter, + Registry biomeRegistry, @Nullable PalettedContainer 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 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 blockStatePalette; List palette; if (bitsPerEntry < 9) { palette = new ArrayList<>(); @@ -390,30 +383,15 @@ 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, - biomeRegistry.getOrThrow(Biomes.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); + if (biomes == null) { + biomes = new PalettedContainer<>( + biomeRegistry, + biomeRegistry.getOrThrow(Biomes.PLAINS), + PalettedContainer.Strategy.SECTION_BIOMES, + null + ); } + levelChunkSection = new LevelChunkSection(layer, blockStatePalettedContainer, biomes); 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 biomeRegistry, @Nullable PalettedContainer biomes ) { + if (biomes == null) { + return new LevelChunkSection(layer, biomeRegistry); + } PalettedContainer dataPaletteBlocks = new PalettedContainer<>( Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES, null ); - PalettedContainer 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) { diff --git a/worldedit-bukkit/adapters/adapter-1_18/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R1/PaperweightPostProcessor.java b/worldedit-bukkit/adapters/adapter-1_18/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R1/PaperweightPostProcessor.java new file mode 100644 index 000000000..6354d349f --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_18/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R1/PaperweightPostProcessor.java @@ -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) + ); + } + +} diff --git a/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/PaperweightFaweAdapter.java index e2b926f17..22b5fd3c0 100644 --- a/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/PaperweightFaweAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/PaperweightFaweAdapter.java @@ -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(); + } + } diff --git a/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/PaperweightGetBlocks.java b/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/PaperweightGetBlocks.java index 6eff4675c..0d0676576 100644 --- a/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/PaperweightGetBlocks.java +++ b/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/PaperweightGetBlocks.java @@ -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> biomeRegistry; + private final Registry biomeRegistry; + private final IdMap> 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 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 chunkTiles = new HashMap<>(nmsChunk.getBlockEntities()); @@ -466,12 +467,11 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc if (existingSection == null) { PalettedContainer> 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> 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)) diff --git a/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/PaperweightGetBlocks_Copy.java b/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/PaperweightGetBlocks_Copy.java index d9168b13f..1404fd1c7 100644 --- a/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/PaperweightGetBlocks_Copy.java +++ b/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/PaperweightGetBlocks_Copy.java @@ -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>[] 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()][]; } diff --git a/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/PaperweightPlatformAdapter.java b/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/PaperweightPlatformAdapter.java index 209e867f7..151289d89 100644 --- a/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/PaperweightPlatformAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/PaperweightPlatformAdapter.java @@ -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> biomeRegistry, + final int layer, + final char[] blocks, + CachedBukkitAdapter adapter, + Registry biomeRegistry, @Nullable PalettedContainer> 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 get, char[] set, - boolean fastmode, CachedBukkitAdapter adapter, IdMap> biomeRegistry, + final int layer, + final Function get, + char[] set, + CachedBukkitAdapter adapter, + Registry biomeRegistry, @Nullable PalettedContainer> 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 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 blockStatePalette; List 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) { - biomes = new PalettedContainer<>( - biomeRegistry, - biomeRegistry.byIdOrThrow(WorldEditPlugin - .getInstance() - .getBukkitImplAdapter() - .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); + if (biomes == null) { + IdMap> biomeHolderIdMap = biomeRegistry.asHolderIdMap(); + biomes = new PalettedContainer<>( + biomeHolderIdMap, + biomeHolderIdMap.byIdOrThrow(WorldEditPlugin + .getInstance() + .getBukkitImplAdapter() + .getInternalBiomeId( + BiomeTypes.PLAINS)), + PalettedContainer.Strategy.SECTION_BIOMES, + null + ); } - 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> biomeRegistry, + int layer, + Registry biomeRegistry, @Nullable PalettedContainer> biomes ) { + if (biomes == null) { + return new LevelChunkSection(layer, biomeRegistry); + } PalettedContainer dataPaletteBlocks = new PalettedContainer<>( Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES, null ); - PalettedContainer> 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, LevelAccessor levelAccessor) { diff --git a/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/PaperweightPostProcessor.java b/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/PaperweightPostProcessor.java new file mode 100644 index 000000000..abc8d6150 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-1_18_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_18_R2/PaperweightPostProcessor.java @@ -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) + ); + } + +} diff --git a/worldedit-bukkit/adapters/adapter-legacy/src/main/resources/worldedit-adapters.jar b/worldedit-bukkit/adapters/adapter-legacy/src/main/resources/worldedit-adapters.jar index 6ed8e0e97..ab4f54dec 100644 Binary files a/worldedit-bukkit/adapters/adapter-legacy/src/main/resources/worldedit-adapters.jar and b/worldedit-bukkit/adapters/adapter-legacy/src/main/resources/worldedit-adapters.jar differ diff --git a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/NMSAdapter.java b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/NMSAdapter.java index bb86a8bc4..9726f7089 100644 --- a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/NMSAdapter.java +++ b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/NMSAdapter.java @@ -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 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__: - 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) - ); - } - } + if (ordinal == BlockTypesCache.ReservedIDs.__RESERVED__) { + ordinal = BlockTypesCache.ReservedIDs.AIR; } 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 get, char[] set, - Map ticking_blocks, boolean fastmode, + int layer, + int[] blockToPalette, + int[] paletteToBlock, + int[] blocksCopy, + Function 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__ -> { - if (getArr == null) { - getArr = get.apply(layer); - } - set[i] = switch (ordinal = getArr[i]) { - case 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; - }; + char ordinal= set[i]; + if (ordinal == BlockTypesCache.ReservedIDs.__RESERVED__) { + if (getArr == null) { + getArr = get.apply(layer); } - 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) - ); + if ((ordinal = getArr[i]) == BlockTypesCache.ReservedIDs.__RESERVED__) { + ordinal = BlockTypesCache.ReservedIDs.AIR; } } int palette = blockToPalette[ordinal]; blocksCopy[i] = palette; } - num_palette_buffer[0] = num_palette; - return air; + return num_palette; } @Override diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitServerInterface.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitServerInterface.java index 89bbaa0cf..8278142b6 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitServerInterface.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitServerInterface.java @@ -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 } diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplAdapter.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplAdapter.java index 38fdbff91..028cc1d1f 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplAdapter.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplAdapter.java @@ -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 extends IBukkitAdapter { default Map>> 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 } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/FaweCache.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/FaweCache.java index bb3d0d04f..c87344f7d 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/FaweCache.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/FaweCache.java @@ -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); diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/configuration/Settings.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/configuration/Settings.java index 2824c55ca..32a566935 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/configuration/Settings.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/configuration/Settings.java @@ -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)", diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/DisallowedBlocksExtent.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/DisallowedBlocksExtent.java index 3443c3e33..8f2612b76 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/DisallowedBlocksExtent.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/DisallowedBlocksExtent.java @@ -167,11 +167,6 @@ public class DisallowedBlocksExtent extends AbstractDelegateExtent implements IB return set; } - @Override - public Future postProcessSet(final IChunk chunk, final IChunkGet get, final IChunkSet set) { - return CompletableFuture.completedFuture(set); - } - @Nullable @Override public Extent construct(final Extent child) { diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/HeightBoundExtent.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/HeightBoundExtent.java index 5e60af81b..ff57e00da 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/HeightBoundExtent.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/HeightBoundExtent.java @@ -56,9 +56,4 @@ public class HeightBoundExtent extends FaweRegionExtent { return null; } - @Override - public Future postProcessSet(IChunk chunk, IChunkGet get, IChunkSet set) { - return CompletableFuture.completedFuture(set); - } - } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/MultiRegionExtent.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/MultiRegionExtent.java index f55581f8a..e7f88c7c5 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/MultiRegionExtent.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/MultiRegionExtent.java @@ -178,8 +178,13 @@ public class MultiRegionExtent extends FaweRegionExtent { } @Override - public Future 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); + } + } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/NullExtent.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/NullExtent.java index 7420886a2..92c81394a 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/NullExtent.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/NullExtent.java @@ -342,7 +342,12 @@ public class NullExtent extends FaweRegionExtent implements IBatchProcessor { } @Override - public Future 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; } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/SingleRegionExtent.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/SingleRegionExtent.java index 4249faee7..1caea9dbc 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/SingleRegionExtent.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/SingleRegionExtent.java @@ -46,10 +46,17 @@ public class SingleRegionExtent extends FaweRegionExtent { } @Override - public Future 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); diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/StripNBTExtent.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/StripNBTExtent.java index b6cb3ef96..ea35cb424 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/StripNBTExtent.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/StripNBTExtent.java @@ -143,11 +143,6 @@ public class StripNBTExtent extends AbstractDelegateExtent implements IBatchProc return set; } - @Override - public Future postProcessSet(final IChunk chunk, final IChunkGet get, final IChunkSet set) { - return CompletableFuture.completedFuture(set); - } - @Nullable @Override public Extent construct(final Extent child) { diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/BatchProcessorHolder.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/BatchProcessorHolder.java index aec836426..540e933b0 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/BatchProcessorHolder.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/BatchProcessorHolder.java @@ -28,10 +28,15 @@ public class BatchProcessorHolder implements IBatchProcessorHolder { } @Override - public Future 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(); diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/EmptyBatchProcessor.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/EmptyBatchProcessor.java index cd04ca615..9f2cb6641 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/EmptyBatchProcessor.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/EmptyBatchProcessor.java @@ -29,13 +29,6 @@ public final class EmptyBatchProcessor implements IBatchProcessor { return set; } - @Override - @Nonnull - public Future 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; diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/IBatchProcessorHolder.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/IBatchProcessorHolder.java index a192e9748..2f558dc2a 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/IBatchProcessorHolder.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/IBatchProcessorHolder.java @@ -31,10 +31,15 @@ public interface IBatchProcessorHolder extends IBatchProcessor { } @Override - default Future 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); diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/MultiBatchProcessor.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/MultiBatchProcessor.java index f40047e60..85fe20986 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/MultiBatchProcessor.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/MultiBatchProcessor.java @@ -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,37 +129,64 @@ public class MultiBatchProcessor implements IBatchProcessor { } @Override - public Future postProcessSet(IChunk chunk, IChunkGet get, IChunkSet set) { - try { - for (IBatchProcessor processor : processors) { + public Future postProcessSet(IChunk chunk, IChunkGet get, IChunkSet set) { + List> 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; + futures.add(processor.postProcessSet(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); + } } } - return CompletableFuture.completedFuture(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.hashCode(); - if (lastException != hash) { - lastException = hash; - exceptionCount = 0; - LOGGER.catching(e); - } else if (exceptionCount < Settings.settings().QUEUE.PARALLEL_THREADS) { - exceptionCount++; - LOGGER.warn(message); + } + 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); + } } } - return null; } } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/NullProcessor.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/NullProcessor.java index d741ce93a..571426023 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/NullProcessor.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/NullProcessor.java @@ -24,11 +24,6 @@ public final class NullProcessor implements IBatchProcessor { return null; } - @Nullable - public Future postProcessSet(@Nonnull IChunk chunk, @Nonnull IChunkGet get, @Nonnull IChunkSet set) { - return null; - } - @Nonnull public Extent construct(@Nonnull Extent child) { return new NullExtent(); diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/heightmap/HeightmapProcessor.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/heightmap/HeightmapProcessor.java index efdf79846..efa2447f7 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/heightmap/HeightmapProcessor.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/heightmap/HeightmapProcessor.java @@ -136,11 +136,6 @@ public class HeightmapProcessor implements IBatchProcessor { return set; } - @Override - public Future postProcessSet(IChunk chunk, IChunkGet get, IChunkSet set) { - return CompletableFuture.completedFuture(set); - } - @Override @Nullable public Extent construct(Extent child) { diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/lighting/RelightProcessor.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/lighting/RelightProcessor.java index 8ac56c0f1..6a9f16dee 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/lighting/RelightProcessor.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/lighting/RelightProcessor.java @@ -46,11 +46,6 @@ public class RelightProcessor implements IBatchProcessor { return set; } - @Override - public Future postProcessSet(IChunk chunk, IChunkGet get, IChunkSet set) { - return CompletableFuture.completedFuture(set); - } - @Override public @Nullable Extent construct(Extent child) { diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/history/changeset/AbstractChangeSet.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/history/changeset/AbstractChangeSet.java index 70c3b76ae..5c5e1f682 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/history/changeset/AbstractChangeSet.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/history/changeset/AbstractChangeSet.java @@ -228,8 +228,13 @@ public abstract class AbstractChangeSet implements ChangeSet, IBatchProcessor { } @Override - public Future postProcessSet(final IChunk chunk, final IChunkGet get, final IChunkSet set) { - return (Future) 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 diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IBatchProcessor.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IBatchProcessor.java index 1e9adf713..87012c96e 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IBatchProcessor.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IBatchProcessor.java @@ -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 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; diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/ParallelQueueExtent.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/ParallelQueueExtent.java index 054001a71..0d7436ca5 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/ParallelQueueExtent.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/ParallelQueueExtent.java @@ -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; diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/SingleThreadQueueExtent.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/SingleThreadQueueExtent.java index 85a1760aa..8ad257c76 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/SingleThreadQueueExtent.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/SingleThreadQueueExtent.java @@ -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; diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/chunk/ChunkHolder.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/chunk/ChunkHolder.java index 5634dd1f5..3dede088c 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/chunk/ChunkHolder.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/chunk/ChunkHolder.java @@ -1010,7 +1010,7 @@ public class ChunkHolder> implements IQueueChunk { return get.call(set, finalize); } finally { if (postProcess) { - getExtent().postProcessSet(this, get.getCopy(), set); + getExtent().postProcess(this, get.getCopy(), set); } } } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/MultiFuture.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/MultiFuture.java new file mode 100644 index 000000000..6797cb889 --- /dev/null +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/MultiFuture.java @@ -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 { + + private final List> futures; + + public MultiFuture(List> 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(); + } + +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/EditSessionBuilder.java b/worldedit-core/src/main/java/com/sk89q/worldedit/EditSessionBuilder.java index 11e49f878..108ef1b2f 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/EditSessionBuilder.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/EditSessionBuilder.java @@ -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; } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/Platform.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/Platform.java index fc53e5d44..3e17cf91e 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/Platform.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/Platform.java @@ -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 } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/MaskingExtent.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/MaskingExtent.java index c28f4a07a..0187e52ca 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/MaskingExtent.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/MaskingExtent.java @@ -111,12 +111,6 @@ public class MaskingExtent extends AbstractDelegateExtent implements IBatchProce return filter.filter(chunk, get, set, MaskingExtent.this); } - @Override - public Future 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)) { diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/regions/Region.java b/worldedit-core/src/main/java/com/sk89q/worldedit/regions/Region.java index 407530eea..b0b4e9785 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/regions/Region.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/regions/Region.java @@ -471,12 +471,6 @@ public interface Region extends Iterable, Cloneable, IBatchProcess } } - @Override - default Future 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()) { diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/regions/RegionIntersection.java b/worldedit-core/src/main/java/com/sk89q/worldedit/regions/RegionIntersection.java index a58524825..8298a8b00 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/regions/RegionIntersection.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/regions/RegionIntersection.java @@ -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> 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) { diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockTypesCache.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockTypesCache.java index 325da5140..a8ce03eff 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockTypesCache.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockTypesCache.java @@ -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>> 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(':'));