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

* Make postProcessSet a default method and change to void

* Throwable#getMessage is nullable

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

* chore: Ignore (for us) irrelevant rules

* chore: Fix correct toml syntax?

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

* Switch to javax annotations

* Switch to recalcBlockCounts for ticking blocks.

* No need to set air count anymore either

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

* update adapters

* Let paper create the chunk section if biomes are null

* Adjust notes to settings

* 1.17.2 didn't exist

* Add 1.18.2

* Don't attempt to cache plains biome ID

* Use correct annotation

Co-authored-by: NotMyFault <mc.cache@web.de>
This commit is contained in:
Jordan 2022-03-10 14:27:25 +00:00 committed by GitHub
parent 5d18e15128
commit e9db749e2f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 975 additions and 399 deletions

4
.lift.toml Normal file
View File

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

View File

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

View File

@ -410,7 +410,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
public synchronized <T extends Future<T>> T call(IChunkSet set, Runnable finalizer) { public synchronized <T extends Future<T>> T call(IChunkSet set, Runnable finalizer) {
forceLoadSections = false; forceLoadSections = false;
copy = createCopy ? new PaperweightGetBlocks_Copy(serverLevel) : null; copy = createCopy ? new PaperweightGetBlocks_Copy(levelChunk) : null;
try { try {
ServerLevel nmsWorld = serverLevel; ServerLevel nmsWorld = serverLevel;
LevelChunk nmsChunk = ensureLoaded(nmsWorld, chunkX, chunkZ); LevelChunk nmsChunk = ensureLoaded(nmsWorld, chunkX, chunkZ);
@ -461,6 +461,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
bitMask |= 1 << layer; bitMask |= 1 << layer;
// Changes may still be written to chunk SET
char[] tmp = set.load(layerNo); char[] tmp = set.load(layerNo);
char[] setArr = new char[4096]; char[] setArr = new char[4096];
System.arraycopy(tmp, 0, setArr, 0, 4096); System.arraycopy(tmp, 0, setArr, 0, 4096);
@ -477,6 +478,12 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
LevelChunkSection newSection; LevelChunkSection newSection;
LevelChunkSection existingSection = levelChunkSections[layer]; 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) { if (existingSection == null) {
newSection = PaperweightPlatformAdapter.newChunkSection(layerNo, setArr, fastmode, adapter); newSection = PaperweightPlatformAdapter.newChunkSection(layerNo, setArr, fastmode, adapter);
if (PaperweightPlatformAdapter.setSectionAtomic(levelChunkSections, null, newSection, layer)) { 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); DelegateSemaphore lock = PaperweightPlatformAdapter.applyLock(existingSection);
PaperweightPlatformAdapter.clearCounts(existingSection);
existingSection.tickingList.clear();
synchronized (lock) { synchronized (lock) {
// lock.acquire(); // lock.acquire();

View File

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

View File

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

View File

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

View File

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

View File

@ -398,11 +398,10 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
public synchronized <T extends Future<T>> T call(IChunkSet set, Runnable finalizer) { public synchronized <T extends Future<T>> T call(IChunkSet set, Runnable finalizer) {
forceLoadSections = false; forceLoadSections = false;
copy = createCopy ? new PaperweightGetBlocks_Copy(serverLevel) : null; copy = createCopy ? new PaperweightGetBlocks_Copy(levelChunk) : null;
try { try {
ServerLevel nmsWorld = serverLevel; ServerLevel nmsWorld = serverLevel;
LevelChunk nmsChunk = ensureLoaded(nmsWorld, chunkX, chunkZ); 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 // Remove existing tiles. Create a copy so that we can remove blocks
Map<BlockPos, BlockEntity> chunkTiles = new HashMap<>(nmsChunk.getBlockEntities()); Map<BlockPos, BlockEntity> chunkTiles = new HashMap<>(nmsChunk.getBlockEntities());
@ -470,7 +469,6 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
LevelChunkSection newSection = PaperweightPlatformAdapter.newChunkSection( LevelChunkSection newSection = PaperweightPlatformAdapter.newChunkSection(
layerNo, layerNo,
new char[4096], new char[4096],
fastmode,
adapter, adapter,
biomeRegistry, biomeRegistry,
biomeData biomeData
@ -498,6 +496,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
bitMask |= 1 << getSectionIndex; bitMask |= 1 << getSectionIndex;
// Changes may still be written to chunk SET
char[] tmp = set.load(layerNo); char[] tmp = set.load(layerNo);
char[] setArr = new char[4096]; char[] setArr = new char[4096];
System.arraycopy(tmp, 0, setArr, 0, 4096); System.arraycopy(tmp, 0, setArr, 0, 4096);
@ -508,6 +507,11 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
LevelChunkSection newSection; LevelChunkSection newSection;
LevelChunkSection existingSection = levelChunkSections[getSectionIndex]; 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) { if (createCopy) {
char[] tmpLoad = loadPrivately(layerNo); char[] tmpLoad = loadPrivately(layerNo);
@ -529,7 +533,6 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
newSection = PaperweightPlatformAdapter.newChunkSection( newSection = PaperweightPlatformAdapter.newChunkSection(
layerNo, layerNo,
setArr, setArr,
fastmode,
adapter, adapter,
biomeRegistry, biomeRegistry,
biomeData 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); DelegateSemaphore lock = PaperweightPlatformAdapter.applyLock(existingSection);
synchronized (lock) { synchronized (lock) {
@ -583,7 +587,6 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
layerNo, layerNo,
this::loadPrivately, this::loadPrivately,
setArr, setArr,
fastmode,
adapter, adapter,
biomeRegistry, biomeRegistry,
biomeData biomeData
@ -1052,7 +1055,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
x, x,
y, y,
z, z,
biomeRegistry.get(ResourceLocation.tryParse(biomeType.getId())) biomeRegistry.getOptional(ResourceLocation.tryParse(biomeType.getId())).orElseThrow()
); );
} }
} }

View File

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

View File

@ -11,7 +11,6 @@ import com.fastasyncworldedit.core.util.ReflectionUtils;
import com.fastasyncworldedit.core.util.TaskManager; import com.fastasyncworldedit.core.util.TaskManager;
import com.mojang.datafixers.util.Either; import com.mojang.datafixers.util.Either;
import com.sk89q.worldedit.bukkit.adapter.Refraction; 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.BiomeType;
import com.sk89q.worldedit.world.biome.BiomeTypes; import com.sk89q.worldedit.world.biome.BiomeTypes;
import com.sk89q.worldedit.world.block.BlockState; 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 fieldPalette;
public static final Field fieldTickingFluidCount; private static final Field fieldTickingFluidCount;
public static final Field fieldTickingBlockCount; private static final Field fieldTickingBlockCount;
public static final Field fieldNonEmptyBlockCount; private static final Field fieldNonEmptyBlockCount;
private static final MethodHandle methodGetVisibleChunk; private static final MethodHandle methodGetVisibleChunk;
@ -301,16 +300,21 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
NMS conversion NMS conversion
*/ */
public static LevelChunkSection newChunkSection( public static LevelChunkSection newChunkSection(
final int layer, final char[] blocks, boolean fastmode, final int layer,
CachedBukkitAdapter adapter, Registry<Biome> biomeRegistry, final char[] blocks,
CachedBukkitAdapter adapter,
Registry<Biome> biomeRegistry,
@Nullable PalettedContainer<Biome> biomes @Nullable PalettedContainer<Biome> biomes
) { ) {
return newChunkSection(layer, null, blocks, fastmode, adapter, biomeRegistry, biomes); return newChunkSection(layer, null, blocks, adapter, biomeRegistry, biomes);
} }
public static LevelChunkSection newChunkSection( public static LevelChunkSection newChunkSection(
final int layer, final Function<Integer, char[]> get, char[] set, final int layer,
boolean fastmode, CachedBukkitAdapter adapter, Registry<Biome> biomeRegistry, final Function<Integer, char[]> get,
char[] set,
CachedBukkitAdapter adapter,
Registry<Biome> biomeRegistry,
@Nullable PalettedContainer<Biome> biomes @Nullable PalettedContainer<Biome> biomes
) { ) {
if (set == null) { if (set == null) {
@ -321,24 +325,14 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
final long[] blockStates = FaweCache.INSTANCE.BLOCK_STATES.get(); final long[] blockStates = FaweCache.INSTANCE.BLOCK_STATES.get();
final int[] blocksCopy = FaweCache.INSTANCE.SECTION_BLOCKS.get(); final int[] blocksCopy = FaweCache.INSTANCE.SECTION_BLOCKS.get();
try { try {
int[] num_palette_buffer = new int[1]; int num_palette;
Map<BlockVector3, Integer> ticking_blocks = new HashMap<>();
int air;
if (get == null) { if (get == null) {
air = createPalette(blockToPalette, paletteToBlock, blocksCopy, num_palette_buffer, num_palette = createPalette(blockToPalette, paletteToBlock, blocksCopy, set, adapter);
set, ticking_blocks, fastmode, adapter
);
} else { } else {
air = createPalette(layer, blockToPalette, paletteToBlock, blocksCopy, num_palette = createPalette(layer, blockToPalette, paletteToBlock, blocksCopy, get, set, adapter);
num_palette_buffer, get, set, ticking_blocks, fastmode, adapter
);
} }
int num_palette = num_palette_buffer[0];
// BlockStates
int bitsPerEntry = MathMan.log2nlz(num_palette - 1); int bitsPerEntry = MathMan.log2nlz(num_palette - 1);
Object configuration =
PalettedContainer.Strategy.SECTION_STATES.getConfiguration(new FakeIdMapBlock(num_palette), bitsPerEntry);
if (bitsPerEntry > 0 && bitsPerEntry < 5) { if (bitsPerEntry > 0 && bitsPerEntry < 5) {
bitsPerEntry = 4; bitsPerEntry = 4;
} else if (bitsPerEntry > 8) { } else if (bitsPerEntry > 8) {
@ -365,7 +359,6 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
} else { } else {
nmsBits = new SimpleBitStorage(bitsPerEntry, 4096, bits); nmsBits = new SimpleBitStorage(bitsPerEntry, 4096, bits);
} }
final Palette<net.minecraft.world.level.block.state.BlockState> blockStatePalette;
List<net.minecraft.world.level.block.state.BlockState> palette; List<net.minecraft.world.level.block.state.BlockState> palette;
if (bitsPerEntry < 9) { if (bitsPerEntry < 9) {
palette = new ArrayList<>(); palette = new ArrayList<>();
@ -390,30 +383,15 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
palette palette
); );
LevelChunkSection levelChunkSection; LevelChunkSection levelChunkSection;
try { if (biomes == null) {
//fieldStorage.set(dataPaletteBlocks, nmsBits); biomes = new PalettedContainer<>(
//fieldPalette.set(dataPaletteBlocks, blockStatePalettedContainer); biomeRegistry,
if (biomes == null) { biomeRegistry.getOrThrow(Biomes.PLAINS),
biomes = new PalettedContainer<>( PalettedContainer.Strategy.SECTION_BIOMES,
biomeRegistry, null
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);
} }
levelChunkSection = new LevelChunkSection(layer, blockStatePalettedContainer, biomes);
return levelChunkSection; return levelChunkSection;
} catch (final Throwable e) { } catch (final Throwable e) {
@ -422,23 +400,21 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
} }
} }
@SuppressWarnings("deprecation") // Only deprecated in paper
private static LevelChunkSection newChunkSection( private static LevelChunkSection newChunkSection(
int layer, Registry<Biome> biomeRegistry, int layer, Registry<Biome> biomeRegistry,
@Nullable PalettedContainer<Biome> biomes @Nullable PalettedContainer<Biome> biomes
) { ) {
if (biomes == null) {
return new LevelChunkSection(layer, biomeRegistry);
}
PalettedContainer<net.minecraft.world.level.block.state.BlockState> dataPaletteBlocks = new PalettedContainer<>( PalettedContainer<net.minecraft.world.level.block.state.BlockState> dataPaletteBlocks = new PalettedContainer<>(
Block.BLOCK_STATE_REGISTRY, Block.BLOCK_STATE_REGISTRY,
Blocks.AIR.defaultBlockState(), Blocks.AIR.defaultBlockState(),
PalettedContainer.Strategy.SECTION_STATES, PalettedContainer.Strategy.SECTION_STATES,
null null
); );
PalettedContainer<Biome> biomesPalette = biomes != null ? biomes : new PalettedContainer<>( return new LevelChunkSection(layer, dataPaletteBlocks, biomes);
biomeRegistry,
biomeRegistry.getOrThrow(Biomes.PLAINS),
PalettedContainer.Strategy.SECTION_BIOMES,
null
);
return new LevelChunkSection(layer, dataPaletteBlocks, biomesPalette);
} }
/** /**
@ -541,11 +517,9 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
return biomePalettedContainer; return biomePalettedContainer;
} }
public static void setCount(final int tickingBlockCount, final int nonEmptyBlockCount, final LevelChunkSection section) throws public static void clearCounts(final LevelChunkSection section) throws IllegalAccessException {
IllegalAccessException { fieldTickingFluidCount.setShort(section, (short) 0);
fieldTickingFluidCount.setShort(section, (short) 0); // TODO FIXME fieldTickingBlockCount.setShort(section, (short) 0);
fieldTickingBlockCount.setShort(section, (short) tickingBlockCount);
fieldNonEmptyBlockCount.setShort(section, (short) nonEmptyBlockCount);
} }
public static BiomeType adapt(Biome biome, LevelAccessor levelAccessor) { public static BiomeType adapt(Biome biome, LevelAccessor levelAccessor) {

View File

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

View File

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

View File

@ -99,7 +99,8 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
private final int maxHeight; private final int maxHeight;
private final int minSectionPosition; private final int minSectionPosition;
private final int maxSectionPosition; private final int maxSectionPosition;
private final IdMap<Holder<Biome>> biomeRegistry; private final Registry<Biome> biomeRegistry;
private final IdMap<Holder<Biome>> biomeHolderIdMap;
private LevelChunkSection[] sections; private LevelChunkSection[] sections;
private LevelChunk levelChunk; private LevelChunk levelChunk;
private DataLayer[] blockLight; private DataLayer[] blockLight;
@ -124,7 +125,8 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
this.maxSectionPosition = maxHeight >> 4; this.maxSectionPosition = maxHeight >> 4;
this.skyLight = new DataLayer[getSectionCount()]; this.skyLight = new DataLayer[getSectionCount()];
this.blockLight = 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() { public int getChunkX() {
@ -399,11 +401,10 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
public synchronized <T extends Future<T>> T call(IChunkSet set, Runnable finalizer) { public synchronized <T extends Future<T>> T call(IChunkSet set, Runnable finalizer) {
forceLoadSections = false; forceLoadSections = false;
copy = createCopy ? new PaperweightGetBlocks_Copy(serverLevel) : null; copy = createCopy ? new PaperweightGetBlocks_Copy(levelChunk) : null;
try { try {
ServerLevel nmsWorld = serverLevel; ServerLevel nmsWorld = serverLevel;
LevelChunk nmsChunk = ensureLoaded(nmsWorld, chunkX, chunkZ); 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 // Remove existing tiles. Create a copy so that we can remove blocks
Map<BlockPos, BlockEntity> chunkTiles = new HashMap<>(nmsChunk.getBlockEntities()); Map<BlockPos, BlockEntity> chunkTiles = new HashMap<>(nmsChunk.getBlockEntities());
@ -466,12 +467,11 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
if (existingSection == null) { if (existingSection == null) {
PalettedContainer<Holder<Biome>> biomeData = PaperweightPlatformAdapter.getBiomePalettedContainer( PalettedContainer<Holder<Biome>> biomeData = PaperweightPlatformAdapter.getBiomePalettedContainer(
biomes[setSectionIndex], biomes[setSectionIndex],
biomeRegistry biomeHolderIdMap
); );
LevelChunkSection newSection = PaperweightPlatformAdapter.newChunkSection( LevelChunkSection newSection = PaperweightPlatformAdapter.newChunkSection(
layerNo, layerNo,
new char[4096], new char[4096],
fastmode,
adapter, adapter,
biomeRegistry, biomeRegistry,
biomeData biomeData
@ -527,19 +527,18 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
if (existingSection == null) { if (existingSection == null) {
PalettedContainer<Holder<Biome>> biomeData = biomes == null ? new PalettedContainer<>( PalettedContainer<Holder<Biome>> biomeData = biomes == null ? new PalettedContainer<>(
biomeRegistry, biomeHolderIdMap,
biomeRegistry.byIdOrThrow(WorldEditPlugin biomeHolderIdMap.byIdOrThrow(WorldEditPlugin
.getInstance() .getInstance()
.getBukkitImplAdapter() .getBukkitImplAdapter()
.getInternalBiomeId( .getInternalBiomeId(
BiomeTypes.PLAINS)), BiomeTypes.PLAINS)),
PalettedContainer.Strategy.SECTION_BIOMES, PalettedContainer.Strategy.SECTION_BIOMES,
null null
) : PaperweightPlatformAdapter.getBiomePalettedContainer(biomes[setSectionIndex], biomeRegistry); ) : PaperweightPlatformAdapter.getBiomePalettedContainer(biomes[setSectionIndex], biomeHolderIdMap);
newSection = PaperweightPlatformAdapter.newChunkSection( newSection = PaperweightPlatformAdapter.newChunkSection(
layerNo, layerNo,
setArr, setArr,
fastmode,
adapter, adapter,
biomeRegistry, biomeRegistry,
biomeData 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); DelegateSemaphore lock = PaperweightPlatformAdapter.applyLock(existingSection);
synchronized (lock) { synchronized (lock) {
@ -601,7 +601,6 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
layerNo, layerNo,
this::loadPrivately, this::loadPrivately,
setArr, setArr,
fastmode,
adapter, adapter,
biomeRegistry, biomeRegistry,
biomeData biomeData
@ -1070,7 +1069,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
x, x,
y, y,
z, z,
biomeRegistry.byIdOrThrow(WorldEditPlugin biomeHolderIdMap.byIdOrThrow(WorldEditPlugin
.getInstance() .getInstance()
.getBukkitImplAdapter() .getBukkitImplAdapter()
.getInternalBiomeId(biomeType)) .getInternalBiomeId(biomeType))

View File

@ -20,6 +20,7 @@ import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.chunk.PalettedContainer; import net.minecraft.world.level.chunk.PalettedContainer;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -37,13 +38,15 @@ public class PaperweightGetBlocks_Copy implements IChunkGet {
private final char[][] blocks; private final char[][] blocks;
private final int minHeight; private final int minHeight;
private final int maxHeight; private final int maxHeight;
private final ServerLevel serverLevel; final ServerLevel serverLevel;
final LevelChunk levelChunk;
private PalettedContainer<Holder<Biome>>[] biomes = null; private PalettedContainer<Holder<Biome>>[] biomes = null;
protected PaperweightGetBlocks_Copy(ServerLevel world) { protected PaperweightGetBlocks_Copy(LevelChunk levelChunk) {
this.serverLevel = world; this.levelChunk = levelChunk;
this.minHeight = world.getMinBuildHeight(); this.serverLevel = levelChunk.level;
this.maxHeight = world.getMaxBuildHeight() - 1; // Minecraft max limit is exclusive. this.minHeight = serverLevel.getMinBuildHeight();
this.maxHeight = serverLevel.getMaxBuildHeight() - 1; // Minecraft max limit is exclusive.
this.blocks = new char[getSectionCount()][]; this.blocks = new char[getSectionCount()][];
} }

View File

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

View File

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

View File

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

View File

@ -20,7 +20,9 @@
package com.sk89q.worldedit.bukkit; package com.sk89q.worldedit.bukkit;
import com.fastasyncworldedit.bukkit.util.MinecraftVersion; import com.fastasyncworldedit.bukkit.util.MinecraftVersion;
import com.fastasyncworldedit.core.configuration.Settings;
import com.fastasyncworldedit.core.extent.processor.lighting.RelighterFactory; import com.fastasyncworldedit.core.extent.processor.lighting.RelighterFactory;
import com.fastasyncworldedit.core.queue.IBatchProcessor;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import com.sk89q.bukkit.util.CommandInfo; import com.sk89q.bukkit.util.CommandInfo;
import com.sk89q.bukkit.util.CommandRegistration; import com.sk89q.bukkit.util.CommandRegistration;
@ -289,5 +291,17 @@ public class BukkitServerInterface extends AbstractPlatform implements MultiUser
public int versionMaxY() { public int versionMaxY() {
return MinecraftVersion.getCurrent().isEqualOrHigherThan(MinecraftVersion.CAVES_18) ? 319 : 255; 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 //FAWE end
} }

View File

@ -24,6 +24,7 @@ import com.fastasyncworldedit.bukkit.adapter.IBukkitAdapter;
import com.fastasyncworldedit.bukkit.adapter.NMSRelighterFactory; import com.fastasyncworldedit.bukkit.adapter.NMSRelighterFactory;
import com.fastasyncworldedit.core.Fawe; import com.fastasyncworldedit.core.Fawe;
import com.fastasyncworldedit.core.extent.processor.lighting.RelighterFactory; 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.IChunkGet;
import com.fastasyncworldedit.core.queue.implementation.packet.ChunkPacket; import com.fastasyncworldedit.core.queue.implementation.packet.ChunkPacket;
import com.sk89q.jnbt.AdventureNBTConverter; import com.sk89q.jnbt.AdventureNBTConverter;
@ -350,5 +351,15 @@ public interface BukkitImplAdapter<T> extends IBukkitAdapter {
default Map<String, List<Property<?>>> getAllProperties() { default Map<String, List<Property<?>>> getAllProperties() {
return Collections.emptyMap(); 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 //FAWE end
} }

View File

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

View File

@ -567,7 +567,8 @@ public class Settings extends Config {
public int DISCARD_AFTER_MS = 60000; public int DISCARD_AFTER_MS = 60000;
@Comment({ @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; public boolean NO_TICK_FASTMODE = true;
@ -625,16 +626,11 @@ public class Settings extends Config {
public boolean OTHER = false; public boolean OTHER = false;
@Comment({ @Comment({
"Allow blocks placed by WorldEdit to tick. This could cause the big lags.", "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." "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; public boolean ALLOW_TICK_FLUIDS = false;
@Comment({
"Force re-ticking of existing blocks not edited by FAWE.",
"This will increase time taken slightly."
})
public boolean ALLOW_TICK_EXISTING = true;
@Comment({ @Comment({
"Sets a maximum limit (in kb) for the size of a player's schematics directory (per-player mode only)", "Sets a maximum limit (in kb) for the size of a player's schematics directory (per-player mode only)",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -45,6 +45,7 @@ import com.fastasyncworldedit.core.history.changeset.BlockBagChangeSet;
import com.fastasyncworldedit.core.history.changeset.NullChangeSet; import com.fastasyncworldedit.core.history.changeset.NullChangeSet;
import com.fastasyncworldedit.core.limit.FaweLimit; import com.fastasyncworldedit.core.limit.FaweLimit;
import com.fastasyncworldedit.core.limit.PropertyRemap; import com.fastasyncworldedit.core.limit.PropertyRemap;
import com.fastasyncworldedit.core.queue.IBatchProcessor;
import com.fastasyncworldedit.core.queue.IQueueChunk; import com.fastasyncworldedit.core.queue.IQueueChunk;
import com.fastasyncworldedit.core.queue.IQueueExtent; import com.fastasyncworldedit.core.queue.IQueueExtent;
import com.fastasyncworldedit.core.queue.implementation.ParallelQueueExtent; 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 (placeChunks) {
if (((relightMode != null && relightMode != RelightMode.NONE) || (relightMode == null && Settings.settings().LIGHTING.MODE > 0))) { if (((relightMode != null && relightMode != RelightMode.NONE) || (relightMode == null && Settings.settings().LIGHTING.MODE > 0))) {
relighter = WorldEdit.getInstance().getPlatformManager() relighter = WorldEdit.getInstance().getPlatformManager()
@ -569,6 +570,22 @@ public final class EditSessionBuilder {
extent.addProcessor(new RelightProcessor(relighter)); extent.addProcessor(new RelightProcessor(relighter));
} }
extent.addProcessor(new HeightmapProcessor(world.getMinY(), world.getMaxY())); 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 { } else {
relighter = NullRelighter.INSTANCE; relighter = NullRelighter.INSTANCE;
} }

View File

@ -21,6 +21,7 @@ package com.sk89q.worldedit.extension.platform;
import com.fastasyncworldedit.core.extent.processor.lighting.Relighter; import com.fastasyncworldedit.core.extent.processor.lighting.Relighter;
import com.fastasyncworldedit.core.extent.processor.lighting.RelighterFactory; import com.fastasyncworldedit.core.extent.processor.lighting.RelighterFactory;
import com.fastasyncworldedit.core.queue.IBatchProcessor;
import com.sk89q.worldedit.LocalConfiguration; import com.sk89q.worldedit.LocalConfiguration;
import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.entity.Player; import com.sk89q.worldedit.entity.Player;
@ -248,5 +249,24 @@ public interface Platform extends Keyed {
* @since 2.0.0 * @since 2.0.0
*/ */
int versionMaxY(); 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 //FAWE end
} }

View File

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

View File

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

View File

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

View File

@ -198,6 +198,9 @@ public class BlockTypesCache {
public static final BlockType[] values; public static final BlockType[] values;
public static final BlockState[] states; 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; public static final boolean[] ticking;
private static final Map<String, List<Property<?>>> allProperties = new HashMap<>(); private static final Map<String, List<Property<?>>> allProperties = new HashMap<>();
@ -283,7 +286,8 @@ public class BlockTypesCache {
String enumName = (typeName.startsWith("minecraft:") ? typeName.substring(10) : typeName).toUpperCase(Locale.ROOT); String enumName = (typeName.startsWith("minecraft:") ? typeName.substring(10) : typeName).toUpperCase(Locale.ROOT);
int oldsize = states.size(); int oldsize = states.size();
BlockType existing = new BlockType(id, internalId, states); 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 // register states
BlockType.REGISTRY.register(typeName, existing); BlockType.REGISTRY.register(typeName, existing);
String nameSpace = typeName.substring(0, typeName.indexOf(':')); String nameSpace = typeName.substring(0, typeName.indexOf(':'));