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

* Make postProcessSet a default method and change to void

* Throwable#getMessage is nullable

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

* chore: Ignore (for us) irrelevant rules

* chore: Fix correct toml syntax?

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

* Switch to javax annotations

* Switch to recalcBlockCounts for ticking blocks.

* No need to set air count anymore either

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

* update adapters

* Let paper create the chunk section if biomes are null

* Adjust notes to settings

* 1.17.2 didn't exist

* Add 1.18.2

* Don't attempt to cache plains biome ID

* Use correct annotation

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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