2023-10-22 21:20:31 -05:00
74 changed files with 9132 additions and 250 deletions

View File

@ -12,7 +12,7 @@ applyPlatformAndCoreConfiguration()
dependencies {
constraints {
implementation(libs.snakeyaml) {
version { strictly("2.0") }
version { strictly("2.2") }
because("Bukkit provides SnakeYaml")
}
}

View File

@ -144,7 +144,7 @@ public class Fawe {
0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(),
new ThreadFactoryBuilder().setNameFormat("fawe-clipboard-%d").build()
new ThreadFactoryBuilder().setNameFormat("FAWE Clipboard - %d").build()
));
}

View File

@ -18,6 +18,7 @@ import com.fastasyncworldedit.core.util.collection.CleanableThreadLocal;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.sk89q.jnbt.ByteArrayTag;
import com.sk89q.jnbt.ByteTag;
import com.sk89q.jnbt.CompoundTag;
@ -48,7 +49,6 @@ import java.util.Map.Entry;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@ -616,7 +616,7 @@ public enum FaweCache implements Trimable {
ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(nThreads, true);
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS, queue,
Executors.defaultThreadFactory(),
new ThreadFactoryBuilder().setNameFormat("FAWE Blocking Executor - %d").build(),
new ThreadPoolExecutor.CallerRunsPolicy()
) {

View File

@ -74,7 +74,12 @@ public class InspectBrush extends BrushTool {
return false;
}
try {
BlockVector3 target = getTarget(player, rightClick).toBlockPoint();
Vector3 targetVector = getTarget(player, rightClick);
if (targetVector == null) {
player.print(Caption.of("worldedit.tool.no-block"));
return true;
}
BlockVector3 target = targetVector.toBlockPoint();
final int x = target.getBlockX();
final int y = target.getBlockY();
final int z = target.getBlockZ();

View File

@ -12,8 +12,6 @@ import com.sk89q.worldedit.world.block.BlockTypesCache;
import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
public class HeightmapProcessor implements IBatchProcessor {
@ -25,7 +23,7 @@ public class HeightmapProcessor implements IBatchProcessor {
static {
Arrays.fill(COMPLETE, true);
Arrays.fill(AIR_LAYER, (char) 1);
Arrays.fill(AIR_LAYER, (char) BlockTypesCache.ReservedIDs.AIR);
}
private final int minY;

View File

@ -179,14 +179,14 @@ public abstract class AbstractChangeSet implements ChangeSet, IBatchProcessor {
for (int z = 0; z < 16; z++) {
int zz = z + bz;
for (int x = 0; x < 16; x++, index++) {
int xx = bx + x;
int from = blocksGet[index];
if (from == BlockTypesCache.ReservedIDs.__RESERVED__) {
from = BlockTypesCache.ReservedIDs.AIR;
}
final int combinedFrom = from;
final int combinedTo = blocksSet[index];
if (combinedTo != BlockTypesCache.ReservedIDs.__RESERVED__) {
int xx = bx + x;
int from = blocksGet[index];
if (from == BlockTypesCache.ReservedIDs.__RESERVED__) {
from = BlockTypesCache.ReservedIDs.AIR;
}
final int combinedFrom = from;
add(xx, yy, zz, combinedFrom, combinedTo);
}
}

View File

@ -50,13 +50,31 @@ public interface IChunkGet extends IBlocks, Trimable, InputExtent, ITileInput {
boolean isCreateCopy();
void setCreateCopy(boolean createCopy);
/**
* Not for external API use. Internal use only.
*/
int setCreateCopy(boolean createCopy);
@Nullable
default IChunkGet getCopy() {
default IChunkGet getCopy(int key) {
return null;
}
/**
* Lock the {@link IChunkGet#call(IChunkSet, Runnable)} method to the current thread using a reentrant lock. Also locks
* related methods e.g. {@link IChunkGet#setCreateCopy(boolean)}
*
* @since TODO
*/
default void lockCall() {}
/**
* Unlock {@link IChunkGet#call(IChunkSet, Runnable)} (and other related methods) to executions from other threads
*
* @since TODO
*/
default void unlockCall() {}
/**
* Flush the block lighting array (section*blocks) to the chunk GET between the given section indices. Negative allowed.
*

View File

@ -36,4 +36,18 @@ public interface IQueueChunk<T extends Future<T>> extends IChunk, Callable<T> {
}
}
/**
* Get if the thank has any running tasks, locked locks, etc.
*/
default boolean hasRunning() {
return false;
}
/**
* Prevent set operations to the chunk, should typically be used when a chunk is submitted before the edit is necessarily
* completed.
*/
default void lockSet() {
}
}

View File

@ -62,8 +62,8 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen
private boolean initialized;
private Thread currentThread;
// Last access pointers
private IQueueChunk lastChunk;
private long lastPair = Long.MAX_VALUE;
private volatile IQueueChunk lastChunk;
private volatile long lastPair = Long.MAX_VALUE;
private boolean enabledQueue = true;
private boolean fastmode = false;
// Array for lazy avoidance of concurrent modification exceptions and needless overcomplication of code (synchronisation is
@ -283,6 +283,7 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen
private ChunkHolder poolOrCreate(int chunkX, int chunkZ) {
ChunkHolder next = create(false);
next.init(this, chunkX, chunkZ);
next.setFastMode(isFastMode());
return next;
}
@ -454,7 +455,8 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen
if (!chunks.isEmpty()) {
getChunkLock.lock();
if (MemUtil.isMemoryLimited()) {
for (IQueueChunk chunk : chunks.values()) {
while (!chunks.isEmpty()) {
IQueueChunk chunk = chunks.removeFirst();
final Future future = submitUnchecked(chunk);
if (future != null && !future.isDone()) {
pollSubmissions(Settings.settings().QUEUE.PARALLEL_THREADS, true);
@ -462,14 +464,14 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen
}
}
} else {
for (IQueueChunk chunk : chunks.values()) {
while (!chunks.isEmpty()) {
IQueueChunk chunk = chunks.removeFirst();
final Future future = submitUnchecked(chunk);
if (future != null && !future.isDone()) {
submissions.add(future);
}
}
}
chunks.clear();
getChunkLock.unlock();
}
pollSubmissions(0, true);

View File

@ -121,6 +121,7 @@ public abstract class CharBlocks implements IBlocks {
public synchronized IChunkSet reset() {
for (int i = 0; i < sectionCount; i++) {
sections[i] = EMPTY;
blocks[i] = null;
}
return null;
}

View File

@ -69,7 +69,8 @@ public final class NullChunkGet implements IChunkGet {
}
@Override
public void setCreateCopy(boolean createCopy) {
public int setCreateCopy(boolean createCopy) {
return -1;
}
@Override

View File

@ -25,8 +25,6 @@ import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Future;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* An abstract {@link IChunk} class that implements basic get/set blocks.
@ -44,8 +42,6 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
return POOL.poll();
}
private final Lock calledLock = new ReentrantLock();
private volatile IChunkGet chunkExisting; // The existing chunk (e.g. a clipboard, or the world, before changes)
private volatile IChunkSet chunkSet; // The blocks to be set to the chunkExisting
private IBlockDelegate delegate; // delegate handles the abstraction of the chunk layers
@ -68,7 +64,6 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
@Override
public synchronized void recycle() {
calledLock.lock();
delegate = NULL;
if (chunkSet != null) {
chunkSet.recycle();
@ -77,7 +72,6 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
chunkExisting = null;
extent = null;
POOL.offer(this);
calledLock.unlock();
}
public long initAge() {
@ -88,68 +82,49 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
return delegate;
}
/**
* If the chunk is currently being "called", this method will block until completed.
*/
private void checkAndWaitOnCalledLock() {
if (!calledLock.tryLock()) {
calledLock.lock();
}
calledLock.unlock();
}
@Override
public boolean setTile(int x, int y, int z, CompoundTag tag) {
checkAndWaitOnCalledLock();
return delegate.set(this).setTile(x, y, z, tag);
}
@Override
public CompoundTag getTile(int x, int y, int z) {
checkAndWaitOnCalledLock();
return delegate.set(this).getTile(x, y, z);
}
@Override
public void setEntity(CompoundTag tag) {
checkAndWaitOnCalledLock();
delegate.set(this).setEntity(tag);
}
@Override
public void removeEntity(UUID uuid) {
checkAndWaitOnCalledLock();
delegate.set(this).removeEntity(uuid);
}
@Override
public Set<UUID> getEntityRemoves() {
checkAndWaitOnCalledLock();
return delegate.set(this).getEntityRemoves();
}
@Override
public BiomeType[][] getBiomes() {
checkAndWaitOnCalledLock();
// Uses set as this method is only used to retrieve biomes that have been set to the extent/chunk.
return delegate.set(this).getBiomes();
}
@Override
public char[][] getLight() {
checkAndWaitOnCalledLock();
return delegate.set(this).getLight();
}
@Override
public char[][] getSkyLight() {
checkAndWaitOnCalledLock();
return delegate.set(this).getSkyLight();
}
@Override
public void setBlocks(int layer, char[] data) {
checkAndWaitOnCalledLock();
delegate.set(this).setBlocks(layer, data);
}
@ -174,12 +149,10 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
@Override
public void setFastMode(boolean fastmode) {
checkAndWaitOnCalledLock();
this.fastmode = fastmode;
}
public void setBitMask(int bitMask) {
checkAndWaitOnCalledLock();
this.bitMask = bitMask;
}
@ -189,7 +162,6 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
@Override
public boolean hasBiomes(final int layer) {
checkAndWaitOnCalledLock();
// No need to go through delegate. hasBiomes is SET only.
return chunkSet != null && chunkSet.hasBiomes(layer);
}
@ -200,14 +172,13 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
@Override
public CompoundTag getEntity(UUID uuid) {
checkAndWaitOnCalledLock();
return delegate.get(this).getEntity(uuid);
}
@Override
public void setCreateCopy(boolean createCopy) {
checkAndWaitOnCalledLock();
public int setCreateCopy(boolean createCopy) {
this.createCopy = createCopy;
return -1;
}
@Override
@ -217,19 +188,16 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
@Override
public void setLightingToGet(char[][] lighting, int minSectionPosition, int maxSectionPosition) {
checkAndWaitOnCalledLock();
delegate.setLightingToGet(this, lighting);
}
@Override
public void setSkyLightingToGet(char[][] lighting, int minSectionPosition, int maxSectionPosition) {
checkAndWaitOnCalledLock();
delegate.setSkyLightingToGet(this, lighting);
}
@Override
public void setHeightmapToGet(HeightMapType type, int[] data) {
checkAndWaitOnCalledLock();
delegate.setHeightmapToGet(this, type, data);
}
@ -254,7 +222,6 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
}
public void flushLightToGet() {
checkAndWaitOnCalledLock();
delegate.flushLightToGet(this);
}
@ -921,19 +888,16 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
@Override
public Map<BlockVector3, CompoundTag> getTiles() {
checkAndWaitOnCalledLock();
return delegate.get(this).getTiles();
}
@Override
public Set<CompoundTag> getEntities() {
checkAndWaitOnCalledLock();
return delegate.get(this).getEntities();
}
@Override
public boolean hasSection(int layer) {
checkAndWaitOnCalledLock();
return chunkExisting != null && chunkExisting.hasSection(layer);
}
@ -958,6 +922,7 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
if (result) {
delegate = NULL;
chunkExisting = null;
chunkSet.recycle();
chunkSet = null;
return true;
}
@ -985,7 +950,6 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
@Override
public boolean isEmpty() {
checkAndWaitOnCalledLock();
return chunkSet == null || chunkSet.isEmpty();
}
@ -993,7 +957,6 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
* Get or create the existing part of this chunk.
*/
public final IChunkGet getOrCreateGet() {
checkAndWaitOnCalledLock();
if (chunkExisting == null) {
chunkExisting = newWrappedGet();
chunkExisting.trim(false);
@ -1005,7 +968,6 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
* Get or create the settable part of this chunk.
*/
public final IChunkSet getOrCreateSet() {
checkAndWaitOnCalledLock();
if (chunkSet == null) {
chunkSet = newWrappedSet();
}
@ -1026,7 +988,7 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
* - The purpose of wrapping is to allow different extents to intercept / alter behavior
* - e.g., caching, optimizations, filtering
*/
private synchronized IChunkGet newWrappedGet() {
private IChunkGet newWrappedGet() {
return extent.getCachedGet(chunkX, chunkZ);
}
@ -1048,42 +1010,42 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
@Override
public synchronized T call() {
calledLock.lock();
if (chunkSet != null && !chunkSet.isEmpty()) {
this.delegate = GET;
chunkSet.setBitMask(bitMask);
try {
IChunkSet copy = chunkSet.createCopy();
chunkSet = null;
return this.call(copy, () -> {
// Do nothing
});
} catch (Throwable t) {
calledLock.unlock();
throw t;
}
IChunkSet copy = chunkSet.createCopy();
return this.call(copy, () -> {
// Do nothing
});
}
recycle();
return null;
}
/**
* This method should never be called from outside ChunkHolder
*/
@Override
public synchronized T call(IChunkSet set, Runnable finalize) {
if (set != null) {
IChunkGet get = getOrCreateGet();
boolean postProcess = !(getExtent().getPostProcessor() instanceof EmptyBatchProcessor);
get.setCreateCopy(postProcess);
final IChunkSet iChunkSet = getExtent().processSet(this, get, set);
Runnable finalizer;
if (postProcess) {
finalizer = () -> {
getExtent().postProcess(this, get.getCopy(), iChunkSet);
finalize.run();
};
} else {
finalizer = finalize;
try {
get.lockCall();
boolean postProcess = !(getExtent().getPostProcessor() instanceof EmptyBatchProcessor);
final IChunkSet iChunkSet = getExtent().processSet(this, get, set);
Runnable finalizer;
if (postProcess) {
int copyKey = get.setCreateCopy(true);
finalizer = () -> {
getExtent().postProcess(this, get.getCopy(copyKey), iChunkSet);
finalize.run();
};
} else {
finalizer = finalize;
}
return get.call(set, finalizer);
} finally {
get.unlockCall();
}
calledLock.unlock();
return get.call(set, finalizer);
}
return null;
}
@ -1107,103 +1069,86 @@ public class ChunkHolder<T extends Future<T>> implements IQueueChunk<T> {
@Override
public boolean setBiome(int x, int y, int z, BiomeType biome) {
checkAndWaitOnCalledLock();
return delegate.setBiome(this, x, y, z, biome);
}
@Override
public <B extends BlockStateHolder<B>> boolean setBlock(int x, int y, int z, B block) {
checkAndWaitOnCalledLock();
return delegate.setBlock(this, x, y, z, block);
}
@Override
public BiomeType getBiomeType(int x, int y, int z) {
checkAndWaitOnCalledLock();
return delegate.getBiome(this, x, y, z);
}
@Override
public BlockState getBlock(int x, int y, int z) {
checkAndWaitOnCalledLock();
return delegate.getBlock(this, x, y, z);
}
@Override
public BaseBlock getFullBlock(int x, int y, int z) {
checkAndWaitOnCalledLock();
return delegate.getFullBlock(this, x, y, z);
}
@Override
public void setSkyLight(int x, int y, int z, int value) {
checkAndWaitOnCalledLock();
delegate.setSkyLight(this, x, y, z, value);
}
@Override
public void setHeightMap(HeightMapType type, int[] heightMap) {
checkAndWaitOnCalledLock();
delegate.setHeightMap(this, type, heightMap);
}
@Override
public void removeSectionLighting(int layer, boolean sky) {
checkAndWaitOnCalledLock();
delegate.removeSectionLighting(this, layer, sky);
}
@Override
public void setFullBright(int layer) {
checkAndWaitOnCalledLock();
delegate.setFullBright(this, layer);
}
@Override
public void setBlockLight(int x, int y, int z, int value) {
checkAndWaitOnCalledLock();
delegate.setBlockLight(this, x, y, z, value);
}
@Override
public void setLightLayer(int layer, char[] toSet) {
checkAndWaitOnCalledLock();
delegate.setLightLayer(this, layer, toSet);
}
@Override
public void setSkyLightLayer(int layer, char[] toSet) {
checkAndWaitOnCalledLock();
delegate.setSkyLightLayer(this, layer, toSet);
}
@Override
public int getSkyLight(int x, int y, int z) {
checkAndWaitOnCalledLock();
return delegate.getSkyLight(this, x, y, z);
}
@Override
public int getEmittedLight(int x, int y, int z) {
checkAndWaitOnCalledLock();
return delegate.getEmittedLight(this, x, y, z);
}
@Override
public int getBrightness(int x, int y, int z) {
checkAndWaitOnCalledLock();
return delegate.getBrightness(this, x, y, z);
}
@Override
public int getOpacity(int x, int y, int z) {
checkAndWaitOnCalledLock();
return delegate.getOpacity(this, x, y, z);
}
@Override
public int[] getHeightMap(HeightMapType type) {
checkAndWaitOnCalledLock();
return delegate.getHeightMap(this, type);
}

View File

@ -184,7 +184,8 @@ public final class NullChunk implements IQueueChunk {
}
@Override
public void setCreateCopy(boolean createCopy) {
public int setCreateCopy(boolean createCopy) {
return -1;
}
@Override

View File

@ -2,19 +2,22 @@ package com.fastasyncworldedit.core.util.task;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinWorkerThread;
import java.util.concurrent.atomic.AtomicInteger;
public class FaweForkJoinWorkerThreadFactory implements ForkJoinPool.ForkJoinWorkerThreadFactory {
private final String nameFormat;
private final AtomicInteger idCounter;
public FaweForkJoinWorkerThreadFactory(String nameFormat) {
this.nameFormat = nameFormat;
this.idCounter = new AtomicInteger(0);
}
@Override
public ForkJoinWorkerThread newThread(ForkJoinPool pool) {
final ForkJoinWorkerThread worker = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool);
worker.setName(String.format(nameFormat, worker.getPoolIndex()));
worker.setName(String.format(nameFormat, idCounter.getAndIncrement()));
return worker;
}

View File

@ -257,7 +257,8 @@ public class ExtentEntityCopy implements EntityFunction {
//FAWE start
if (hasRotation) {
ListTag orgrot = state.getNbtData().getListTag("Rotation");
Vector3 orgDirection = new Location(source, 0, 0, 0, orgrot.getFloat(0), orgrot.getFloat(1)).getDirection();
// source extent may be null: use non-nullable destination instead since this is just a conversion into a vector.
Vector3 orgDirection = new Location(destination, 0, 0, 0, orgrot.getFloat(0), orgrot.getFloat(1)).getDirection();
Vector3 newDirection = transform.apply(orgDirection).subtract(transform.apply(Vector3.ZERO)).normalize();
builder.put(
"Rotation",
@ -276,7 +277,8 @@ public class ExtentEntityCopy implements EntityFunction {
CompoundTagBuilder builder = tag.createBuilder();
ListTag orgrot = state.getNbtData().getListTag("Rotation");
Vector3 orgDirection = new Location(source, 0, 0, 0, orgrot.getFloat(0), orgrot.getFloat(1)).getDirection();
// source extent may be null: use non-nullable destination instead since this is just a conversion into a vector.
Vector3 orgDirection = new Location(destination, 0, 0, 0, orgrot.getFloat(0), orgrot.getFloat(1)).getDirection();
Vector3 newDirection = transform.apply(orgDirection).subtract(transform.apply(Vector3.ZERO)).normalize();
builder.put(
"Rotation",

View File

@ -446,6 +446,10 @@ public class ForwardExtentCopy implements Operation {
}
affectedBlocks += blockCopy.getAffected();
if (copyingBiomes) {
// We know biomes will have happened unless something else has gone wrong. Just calculate it.
affectedBiomeCols += source.fullySupports3DBiomes() ? (getAffected() >> 2) : (region.getWidth() * region.getLength());
}
//FAWE end
return null;
}

View File

@ -164,14 +164,23 @@ public class RegionIntersection extends AbstractRegion {
int bz = chunk.getZ() << 4;
int tx = bx + 15;
int tz = bz + 15;
List<Region> intersecting = new ArrayList<>(2);
for (Region region : regions) {
BlockVector3 regMin = region.getMinimumPoint();
BlockVector3 regMax = region.getMaximumPoint();
if (tx >= regMin.getX() && bx <= regMax.getX() && tz >= regMin.getZ() && bz <= regMax.getZ()) {
return region.processSet(chunk, get, set);
intersecting.add(region);
}
}
return null;
if (intersecting.isEmpty()) {
return null;
}
if (intersecting.size() == 1) {
return intersecting.get(0).processSet(chunk, get, set);
}
// if multiple regions intersect with this chunk, we must be more careful, otherwise one region might trim content of
// another region
return super.processSet(chunk, get, set);
}
@Override

View File

@ -254,6 +254,8 @@ public class CylinderRegionSelector implements RegionSelector, CUIRegion {
@Override
public void clear() {
region = new CylinderRegion(region.getWorld());
selectedCenter = false;
selectedRadius = false;
}
@Override

View File

@ -227,6 +227,8 @@ public class EllipsoidRegionSelector implements RegionSelector, CUIRegion {
public void clear() {
region.setCenter(BlockVector3.ZERO);
region.setRadius(Vector3.ZERO);
started = false;
selectedRadius = false;
}
@Override