3
0
Mirror von https://github.com/IntellectualSites/FastAsyncWorldEdit.git synchronisiert 2024-10-05 05:10:07 +02:00

Merge branch 'v3' into feature/anvil

Dieser Commit ist enthalten in:
Jordan 2023-11-18 15:50:49 +01:00 committet von GitHub
Commit 32c9a80d63
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: 4AEE18F83AFDEB23
58 geänderte Dateien mit 732 neuen und 1001 gelöschten Zeilen

Datei anzeigen

@ -0,0 +1,23 @@
name: "Label conflicting PRs"
on:
push:
pull_request_target:
types: [ synchronize ]
pull_request:
types: [ synchronize ]
permissions:
pull-requests: write
jobs:
main:
if: github.event.pull_request.user.login != 'dependabot[bot]'
runs-on: ubuntu-latest
steps:
- name: Label conflicting PRs
uses: eps1lon/actions-label-merge-conflict@v2.1.0
with:
dirtyLabel: "unresolved-merge-conflict"
repoToken: "${{ secrets.GITHUB_TOKEN }}"
commentOnDirty: "Please take a moment and address the merge conflicts of your pull request. Thanks!"
continueOnMissingPermissions: true

Datei anzeigen

@ -34,7 +34,7 @@ logger.lifecycle("""
******************************************* *******************************************
""") """)
var rootVersion by extra("2.8.2") var rootVersion by extra("2.8.3")
var snapshot by extra("SNAPSHOT") var snapshot by extra("SNAPSHOT")
var revision: String by extra("") var revision: String by extra("")
var buildNumber by extra("") var buildNumber by extra("")

Datei anzeigen

@ -22,7 +22,7 @@ val properties = Properties().also { props ->
dependencies { dependencies {
implementation(gradleApi()) implementation(gradleApi())
implementation("org.ajoberstar.grgit:grgit-gradle:5.2.0") implementation("org.ajoberstar.grgit:grgit-gradle:5.2.1")
implementation("com.github.johnrengelman:shadow:8.1.1") implementation("com.github.johnrengelman:shadow:8.1.1")
implementation("io.papermc.paperweight.userdev:io.papermc.paperweight.userdev.gradle.plugin:1.5.5") implementation("io.papermc.paperweight.userdev:io.papermc.paperweight.userdev.gradle.plugin:1.5.5")
} }

Datei anzeigen

@ -14,8 +14,8 @@ mapmanager = "1.8.0-SNAPSHOT"
griefprevention = "16.18.1" griefprevention = "16.18.1"
griefdefender = "2.1.0-SNAPSHOT" griefdefender = "2.1.0-SNAPSHOT"
residence = "4.5._13.1" residence = "4.5._13.1"
towny = "0.99.6.0" towny = "0.100.0.1"
plotsquared = "7.0.0" plotsquared = "7.1.0"
# Third party # Third party
bstats = "3.0.2" bstats = "3.0.2"
@ -23,7 +23,7 @@ sparsebitset = "1.3"
parallelgzip = "1.0.5" parallelgzip = "1.0.5"
adventure = "4.14.0" adventure = "4.14.0"
adventure-bukkit = "4.3.1" adventure-bukkit = "4.3.1"
checkerqual = "3.39.0" checkerqual = "3.40.0"
truezip = "6.8.4" truezip = "6.8.4"
auto-value = "1.10.4" auto-value = "1.10.4"
findbugs = "3.0.2" findbugs = "3.0.2"
@ -35,7 +35,7 @@ jlibnoise = "1.0.0"
jchronic = "0.2.4a" jchronic = "0.2.4a"
lz4-java = "1.8.0" lz4-java = "1.8.0"
lz4-stream = "1.0.0" lz4-stream = "1.0.0"
commons-cli = "1.5.0" commons-cli = "1.6.0"
paperlib = "1.0.8" paperlib = "1.0.8"
paster = "1.1.5" paster = "1.1.5"
vault = "1.7.1" vault = "1.7.1"

Datei anzeigen

@ -72,9 +72,11 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.Semaphore; import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -91,6 +93,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
.getInstance() .getInstance()
.getBukkitImplAdapter()); .getBukkitImplAdapter());
private final ReadWriteLock sectionLock = new ReentrantReadWriteLock(); private final ReadWriteLock sectionLock = new ReentrantReadWriteLock();
private final ReentrantLock callLock = new ReentrantLock();
private final ServerLevel serverLevel; private final ServerLevel serverLevel;
private final int chunkX; private final int chunkX;
private final int chunkZ; private final int chunkZ;
@ -98,14 +101,16 @@ 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 ConcurrentHashMap<Integer, PaperweightGetBlocks_Copy> copies = new ConcurrentHashMap<>();
private final Object sendLock = new Object();
private LevelChunkSection[] sections; private LevelChunkSection[] sections;
private LevelChunk levelChunk; private LevelChunk levelChunk;
private DataLayer[] blockLight; private DataLayer[] blockLight;
private DataLayer[] skyLight; private DataLayer[] skyLight;
private boolean createCopy = false; private boolean createCopy = false;
private PaperweightGetBlocks_Copy copy = null;
private boolean forceLoadSections = true; private boolean forceLoadSections = true;
private boolean lightUpdate = false; private boolean lightUpdate = false;
private int copyKey = 0;
public PaperweightGetBlocks(World world, int chunkX, int chunkZ) { public PaperweightGetBlocks(World world, int chunkX, int chunkZ) {
this(((CraftWorld) world).getHandle(), chunkX, chunkZ); this(((CraftWorld) world).getHandle(), chunkX, chunkZ);
@ -138,13 +143,27 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
} }
@Override @Override
public void setCreateCopy(boolean createCopy) { public int setCreateCopy(boolean createCopy) {
if (!callLock.isHeldByCurrentThread()) {
throw new IllegalStateException("Attempting to set if chunk GET should create copy, but it is not call-locked.");
}
this.createCopy = createCopy; this.createCopy = createCopy;
return ++this.copyKey;
} }
@Override @Override
public IChunkGet getCopy() { public IChunkGet getCopy(final int key) {
return copy; return copies.remove(key);
}
@Override
public void lockCall() {
this.callLock.lock();
}
@Override
public void unlockCall() {
this.callLock.unlock();
} }
@Override @Override
@ -393,8 +412,17 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
@Override @Override
@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) {
if (!callLock.isHeldByCurrentThread()) {
throw new IllegalStateException("Attempted to call chunk GET but chunk was not call-locked.");
}
forceLoadSections = false; forceLoadSections = false;
copy = createCopy ? new PaperweightGetBlocks_Copy(getChunk()) : null; PaperweightGetBlocks_Copy copy = createCopy ? new PaperweightGetBlocks_Copy(levelChunk) : null;
if (createCopy) {
if (copies.containsKey(copyKey)) {
throw new IllegalStateException("Copy key already used.");
}
copies.put(copyKey, copy);
}
try { try {
ServerLevel nmsWorld = serverLevel; ServerLevel nmsWorld = serverLevel;
LevelChunk nmsChunk = ensureLoaded(nmsWorld, chunkX, chunkZ); LevelChunk nmsChunk = ensureLoaded(nmsWorld, chunkX, chunkZ);
@ -831,9 +859,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
if (super.sections[layer] != null) { if (super.sections[layer] != null) {
synchronized (super.sectionLocks[layer]) { synchronized (super.sectionLocks[layer]) {
if (super.sections[layer].isFull() && super.blocks[layer] != null) { if (super.sections[layer].isFull() && super.blocks[layer] != null) {
char[] blocks = new char[4096]; return super.blocks[layer];
System.arraycopy(super.blocks[layer], 0, blocks, 0, 4096);
return blocks;
} }
} }
} }
@ -841,8 +867,10 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
} }
@Override @Override
public synchronized void send(int mask, boolean lighting) { public void send(int mask, boolean lighting) {
PaperweightPlatformAdapter.sendChunk(serverLevel, chunkX, chunkZ, lighting); synchronized (sendLock) {
PaperweightPlatformAdapter.sendChunk(serverLevel, chunkX, chunkZ, lighting);
}
} }
/** /**
@ -946,9 +974,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
public LevelChunkSection[] getSections(boolean force) { public LevelChunkSection[] getSections(boolean force) {
force &= forceLoadSections; force &= forceLoadSections;
sectionLock.readLock().lock();
LevelChunkSection[] tmp = sections; LevelChunkSection[] tmp = sections;
sectionLock.readLock().unlock();
if (tmp == null || force) { if (tmp == null || force) {
try { try {
sectionLock.writeLock().lock(); sectionLock.writeLock().lock();

Datei anzeigen

@ -22,6 +22,7 @@ import net.minecraft.world.level.chunk.ChunkBiomeContainer;
import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.LevelChunk;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
@ -99,7 +100,8 @@ public class PaperweightGetBlocks_Copy implements IChunkGet {
} }
@Override @Override
public void setCreateCopy(boolean createCopy) { public int setCreateCopy(boolean createCopy) {
return -1;
} }
@Override @Override
@ -196,6 +198,10 @@ public class PaperweightGetBlocks_Copy implements IChunkGet {
@Override @Override
public char[] load(int layer) { public char[] load(int layer) {
layer -= getMinSectionPosition(); layer -= getMinSectionPosition();
if (blocks[layer] == null) {
blocks[layer] = new char[4096];
Arrays.fill(blocks[layer], (char) BlockTypesCache.ReservedIDs.AIR);
}
return blocks[layer]; return blocks[layer];
} }

Datei anzeigen

@ -1,17 +1,8 @@
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_17_R1_2; package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_17_R1_2;
import com.fastasyncworldedit.bukkit.adapter.StarlightRelighter;
import com.fastasyncworldedit.core.configuration.Settings; import com.fastasyncworldedit.core.configuration.Settings;
import com.fastasyncworldedit.core.extent.processor.lighting.NMSRelighter;
import com.fastasyncworldedit.core.extent.processor.lighting.Relighter;
import com.fastasyncworldedit.core.queue.IQueueChunk;
import com.fastasyncworldedit.core.queue.IQueueExtent; import com.fastasyncworldedit.core.queue.IQueueExtent;
import com.fastasyncworldedit.core.util.MathMan;
import com.fastasyncworldedit.core.util.TaskManager;
import com.sk89q.worldedit.internal.util.LogManagerCompat;
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongArraySet;
import it.unimi.dsi.fastutil.longs.LongIterator;
import it.unimi.dsi.fastutil.longs.LongSet;
import net.minecraft.server.MCUtil; import net.minecraft.server.MCUtil;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ThreadedLevelLightEngine; import net.minecraft.server.level.ThreadedLevelLightEngine;
@ -19,27 +10,18 @@ import net.minecraft.server.level.TicketType;
import net.minecraft.util.Unit; import net.minecraft.util.Unit;
import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.ChunkStatus; import net.minecraft.world.level.chunk.ChunkStatus;
import org.apache.logging.log4j.Logger;
import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType; import java.lang.invoke.MethodType;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.IntConsumer; import java.util.function.IntConsumer;
public class PaperweightStarlightRelighter implements Relighter { public class PaperweightStarlightRelighter extends StarlightRelighter<ServerLevel, ChunkPos> {
public static final MethodHandle RELIGHT;
private static final Logger LOGGER = LogManagerCompat.getLogger();
private static final int CHUNKS_PER_BATCH = 1024; // 32 * 32
private static final int CHUNKS_PER_BATCH_SQRT_LOG2 = 5; // for shifting
private static final MethodHandle RELIGHT;
private static final TicketType<Unit> FAWE_TICKET = TicketType.create("fawe_ticket", (a, b) -> 0); private static final TicketType<Unit> FAWE_TICKET = TicketType.create("fawe_ticket", (a, b) -> 0);
private static final int LIGHT_LEVEL = MCUtil.getTicketLevelFor(ChunkStatus.LIGHT); private static final int LIGHT_LEVEL = MCUtil.getTicketLevelFor(ChunkStatus.LIGHT);
@ -58,22 +40,36 @@ public class PaperweightStarlightRelighter implements Relighter {
IntConsumer.class IntConsumer.class
) )
); );
tmp = MethodHandles.dropReturn(tmp);
} catch (NoSuchMethodException | IllegalAccessException e) { } catch (NoSuchMethodException | IllegalAccessException e) {
LOGGER.error("Failed to locate 'relight' method in ThreadedLevelLightEngine. Is everything up to date?", e); LOGGER.error("Failed to locate 'relight' method in ThreadedLevelLightEngine. Is everything up to date?", e);
} }
RELIGHT = tmp; RELIGHT = tmp;
} }
private final ServerLevel serverLevel; public PaperweightStarlightRelighter(ServerLevel serverLevel, IQueueExtent<?> queue) {
private final ReentrantLock lock = new ReentrantLock(); super(serverLevel, queue);
private final Long2ObjectLinkedOpenHashMap<LongSet> regions = new Long2ObjectLinkedOpenHashMap<>(); }
private final ReentrantLock areaLock = new ReentrantLock();
private final NMSRelighter delegate;
@SuppressWarnings("rawtypes") @Override
public PaperweightStarlightRelighter(ServerLevel serverLevel, IQueueExtent<IQueueChunk> queue) { protected ChunkPos createChunkPos(final long chunkKey) {
this.serverLevel = serverLevel; return new ChunkPos(chunkKey);
this.delegate = new NMSRelighter(queue); }
@Override
protected long asLong(final int chunkX, final int chunkZ) {
return ChunkPos.asLong(chunkX, chunkZ);
}
@Override
protected CompletableFuture<?> chunkLoadFuture(final ChunkPos chunkPos) {
return serverLevel.getWorld().getChunkAtAsync(chunkPos.x, chunkPos.z)
.thenAccept(c -> serverLevel.getChunkSource().addTicketAtLevel(
FAWE_TICKET,
chunkPos,
LIGHT_LEVEL,
Unit.INSTANCE
));
} }
public static boolean isUsable() { public static boolean isUsable() {
@ -81,95 +77,13 @@ public class PaperweightStarlightRelighter implements Relighter {
} }
@Override @Override
public boolean addChunk(int cx, int cz, byte[] skipReason, int bitmask) { protected void invokeRelight(
areaLock.lock();
try {
long key = MathMan.pairInt(cx >> CHUNKS_PER_BATCH_SQRT_LOG2, cz >> CHUNKS_PER_BATCH_SQRT_LOG2);
// TODO probably submit here already if chunks.size == CHUNKS_PER_BATCH?
LongSet chunks = this.regions.computeIfAbsent(key, k -> new LongArraySet(CHUNKS_PER_BATCH >> 2));
chunks.add(ChunkPos.asLong(cx, cz));
} finally {
areaLock.unlock();
}
return true;
}
@Override
public void addLightUpdate(int x, int y, int z) {
delegate.addLightUpdate(x, y, z);
}
/*
* This method is called "recursively", iterating and removing elements
* from the regions linked map. This way, chunks are loaded in batches to avoid
* OOMEs.
*/
@Override
public void fixLightingSafe(boolean sky) {
this.areaLock.lock();
try {
if (regions.isEmpty()) {
return;
}
LongSet first = regions.removeFirst();
fixLighting(first, () -> fixLightingSafe(true));
} finally {
this.areaLock.unlock();
}
}
/*
* Processes a set of chunks and runs an action afterwards.
* The action is run async, the chunks are partly processed on the main thread
* (as required by the server).
*/
private void fixLighting(LongSet chunks, Runnable andThen) {
// convert from long keys to ChunkPos
Set<ChunkPos> coords = new HashSet<>();
LongIterator iterator = chunks.iterator();
while (iterator.hasNext()) {
coords.add(new ChunkPos(iterator.nextLong()));
}
TaskManager.taskManager().task(() -> {
// trigger chunk load and apply ticket on main thread
List<CompletableFuture<?>> futures = new ArrayList<>();
for (ChunkPos pos : coords) {
futures.add(serverLevel.getWorld().getChunkAtAsync(pos.x, pos.z)
.thenAccept(c -> serverLevel.getChunkSource().addTicketAtLevel(
FAWE_TICKET,
pos,
LIGHT_LEVEL,
Unit.INSTANCE
))
);
}
// collect futures and trigger relight once all chunks are loaded
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).thenAccept(v ->
invokeRelight(
coords,
c -> {
}, // no callback for single chunks required
i -> {
if (i != coords.size()) {
LOGGER.warn("Processed {} chunks instead of {}", i, coords.size());
}
// post process chunks on main thread
TaskManager.taskManager().task(() -> postProcessChunks(coords));
// call callback on our own threads
TaskManager.taskManager().async(andThen);
}
)
);
});
}
private void invokeRelight(
Set<ChunkPos> coords, Set<ChunkPos> coords,
Consumer<ChunkPos> chunkCallback, Consumer<ChunkPos> chunkCallback,
IntConsumer processCallback IntConsumer processCallback
) { ) {
try { try {
int unused = (int) RELIGHT.invokeExact( RELIGHT.invokeExact(
serverLevel.getChunkSource().getLightEngine(), serverLevel.getChunkSource().getLightEngine(),
coords, coords,
chunkCallback, // callback per chunk chunkCallback, // callback per chunk
@ -184,7 +98,7 @@ public class PaperweightStarlightRelighter implements Relighter {
* Allow the server to unload the chunks again. * Allow the server to unload the chunks again.
* Also, if chunk packets are sent delayed, we need to do that here * Also, if chunk packets are sent delayed, we need to do that here
*/ */
private void postProcessChunks(Set<ChunkPos> coords) { protected void postProcessChunks(Set<ChunkPos> coords) {
boolean delay = Settings.settings().LIGHTING.DELAY_PACKET_SENDING; boolean delay = Settings.settings().LIGHTING.DELAY_PACKET_SENDING;
for (ChunkPos pos : coords) { for (ChunkPos pos : coords) {
int x = pos.x; int x = pos.x;
@ -196,44 +110,4 @@ public class PaperweightStarlightRelighter implements Relighter {
} }
} }
@Override
public void clear() {
}
@Override
public void removeLighting() {
this.delegate.removeLighting();
}
@Override
public void fixBlockLighting() {
fixLightingSafe(true);
}
@Override
public void fixSkyLighting() {
fixLightingSafe(true);
}
@Override
public boolean isEmpty() {
return true;
}
@Override
public ReentrantLock getLock() {
return this.lock;
}
@Override
public boolean isFinished() {
return false;
}
@Override
public void close() throws Exception {
fixLightingSafe(true);
}
} }

Datei anzeigen

@ -4,7 +4,6 @@ import com.fastasyncworldedit.core.extent.processor.lighting.NullRelighter;
import com.fastasyncworldedit.core.extent.processor.lighting.RelightMode; import com.fastasyncworldedit.core.extent.processor.lighting.RelightMode;
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.IQueueChunk;
import com.fastasyncworldedit.core.queue.IQueueExtent; import com.fastasyncworldedit.core.queue.IQueueExtent;
import com.sk89q.worldedit.world.World; import com.sk89q.worldedit.world.World;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
@ -15,9 +14,7 @@ import javax.annotation.Nonnull;
public class PaperweightStarlightRelighterFactory implements RelighterFactory { public class PaperweightStarlightRelighterFactory implements RelighterFactory {
@Override @Override
public @Nonnull public @Nonnull Relighter createRelighter(RelightMode relightMode, World world, IQueueExtent<?> queue) {
@SuppressWarnings("rawtypes")
Relighter createRelighter(RelightMode relightMode, World world, IQueueExtent<IQueueChunk> queue) {
org.bukkit.World w = Bukkit.getWorld(world.getName()); org.bukkit.World w = Bukkit.getWorld(world.getName());
if (w == null) { if (w == null) {
return NullRelighter.INSTANCE; return NullRelighter.INSTANCE;

Datei anzeigen

@ -14,7 +14,6 @@ import com.fastasyncworldedit.core.queue.implementation.blocks.CharGetBlocks;
import com.fastasyncworldedit.core.util.MathMan; import com.fastasyncworldedit.core.util.MathMan;
import com.fastasyncworldedit.core.util.collection.AdaptedMap; import com.fastasyncworldedit.core.util.collection.AdaptedMap;
import com.google.common.base.Suppliers; import com.google.common.base.Suppliers;
import com.google.common.collect.Iterables;
import com.sk89q.jnbt.CompoundTag; import com.sk89q.jnbt.CompoundTag;
import com.sk89q.jnbt.ListTag; import com.sk89q.jnbt.ListTag;
import com.sk89q.jnbt.StringTag; import com.sk89q.jnbt.StringTag;
@ -66,7 +65,6 @@ import javax.annotation.Nonnull;
import java.util.AbstractSet; import java.util.AbstractSet;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
@ -76,13 +74,14 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.Semaphore; import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBlocks { public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBlocks {
@ -95,6 +94,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
.getInstance() .getInstance()
.getBukkitImplAdapter()); .getBukkitImplAdapter());
private final ReadWriteLock sectionLock = new ReentrantReadWriteLock(); private final ReadWriteLock sectionLock = new ReentrantReadWriteLock();
private final ReentrantLock callLock = new ReentrantLock();
private final ServerLevel serverLevel; private final ServerLevel serverLevel;
private final int chunkX; private final int chunkX;
private final int chunkZ; private final int chunkZ;
@ -104,14 +104,16 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
private final int maxSectionPosition; private final int maxSectionPosition;
private final Registry<Biome> biomeRegistry; private final Registry<Biome> biomeRegistry;
private final IdMap<Holder<Biome>> biomeHolderIdMap; private final IdMap<Holder<Biome>> biomeHolderIdMap;
private final ConcurrentHashMap<Integer, PaperweightGetBlocks_Copy> copies = new ConcurrentHashMap<>();
private final Object sendLock = new Object();
private LevelChunkSection[] sections; private LevelChunkSection[] sections;
private LevelChunk levelChunk; private LevelChunk levelChunk;
private DataLayer[] blockLight; private DataLayer[] blockLight;
private DataLayer[] skyLight; private DataLayer[] skyLight;
private boolean createCopy = false; private boolean createCopy = false;
private PaperweightGetBlocks_Copy copy = null;
private boolean forceLoadSections = true; private boolean forceLoadSections = true;
private boolean lightUpdate = false; private boolean lightUpdate = false;
private int copyKey = 0;
public PaperweightGetBlocks(World world, int chunkX, int chunkZ) { public PaperweightGetBlocks(World world, int chunkX, int chunkZ) {
this(((CraftWorld) world).getHandle(), chunkX, chunkZ); this(((CraftWorld) world).getHandle(), chunkX, chunkZ);
@ -146,13 +148,27 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
} }
@Override @Override
public void setCreateCopy(boolean createCopy) { public int setCreateCopy(boolean createCopy) {
if (!callLock.isHeldByCurrentThread()) {
throw new IllegalStateException("Attempting to set if chunk GET should create copy, but it is not call-locked.");
}
this.createCopy = createCopy; this.createCopy = createCopy;
return ++this.copyKey;
} }
@Override @Override
public IChunkGet getCopy() { public IChunkGet getCopy(final int key) {
return copy; return copies.remove(key);
}
@Override
public void lockCall() {
this.callLock.lock();
}
@Override
public void unlockCall() {
this.callLock.unlock();
} }
@Override @Override
@ -387,8 +403,17 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
@Override @Override
@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) {
if (!callLock.isHeldByCurrentThread()) {
throw new IllegalStateException("Attempted to call chunk GET but chunk was not call-locked.");
}
forceLoadSections = false; forceLoadSections = false;
copy = createCopy ? new PaperweightGetBlocks_Copy(getChunk()) : null; PaperweightGetBlocks_Copy copy = createCopy ? new PaperweightGetBlocks_Copy(levelChunk) : null;
if (createCopy) {
if (copies.containsKey(copyKey)) {
throw new IllegalStateException("Copy key already used.");
}
copies.put(copyKey, copy);
}
try { try {
ServerLevel nmsWorld = serverLevel; ServerLevel nmsWorld = serverLevel;
LevelChunk nmsChunk = ensureLoaded(nmsWorld, chunkX, chunkZ); LevelChunk nmsChunk = ensureLoaded(nmsWorld, chunkX, chunkZ);
@ -882,9 +907,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
if (super.sections[layer] != null) { if (super.sections[layer] != null) {
synchronized (super.sectionLocks[layer]) { synchronized (super.sectionLocks[layer]) {
if (super.sections[layer].isFull() && super.blocks[layer] != null) { if (super.sections[layer].isFull() && super.blocks[layer] != null) {
char[] blocks = new char[4096]; return super.blocks[layer];
System.arraycopy(super.blocks[layer], 0, blocks, 0, 4096);
return blocks;
} }
} }
} }
@ -892,8 +915,10 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
} }
@Override @Override
public synchronized void send(int mask, boolean lighting) { public void send(int mask, boolean lighting) {
PaperweightPlatformAdapter.sendChunk(serverLevel, chunkX, chunkZ, lighting); synchronized (sendLock) {
PaperweightPlatformAdapter.sendChunk(serverLevel, chunkX, chunkZ, lighting);
}
} }
/** /**
@ -1004,9 +1029,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
public LevelChunkSection[] getSections(boolean force) { public LevelChunkSection[] getSections(boolean force) {
force &= forceLoadSections; force &= forceLoadSections;
sectionLock.readLock().lock();
LevelChunkSection[] tmp = sections; LevelChunkSection[] tmp = sections;
sectionLock.readLock().unlock();
if (tmp == null || force) { if (tmp == null || force) {
try { try {
sectionLock.writeLock().lock(); sectionLock.writeLock().lock();

Datei anzeigen

@ -11,7 +11,6 @@ import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter;
import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_18_R2.nbt.PaperweightLazyCompoundTag; import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_18_R2.nbt.PaperweightLazyCompoundTag;
import com.sk89q.worldedit.math.BlockVector3; 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.block.BaseBlock; import com.sk89q.worldedit.world.block.BaseBlock;
import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockState;
import com.sk89q.worldedit.world.block.BlockTypesCache; import com.sk89q.worldedit.world.block.BlockTypesCache;
@ -24,6 +23,7 @@ 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;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
@ -101,7 +101,8 @@ public class PaperweightGetBlocks_Copy implements IChunkGet {
} }
@Override @Override
public void setCreateCopy(boolean createCopy) { public int setCreateCopy(boolean createCopy) {
return -1;
} }
@Override @Override
@ -187,6 +188,10 @@ public class PaperweightGetBlocks_Copy implements IChunkGet {
@Override @Override
public char[] load(int layer) { public char[] load(int layer) {
layer -= getMinSectionPosition(); layer -= getMinSectionPosition();
if (blocks[layer] == null) {
blocks[layer] = new char[4096];
Arrays.fill(blocks[layer], (char) BlockTypesCache.ReservedIDs.AIR);
}
return blocks[layer]; return blocks[layer];
} }

Datei anzeigen

@ -1,140 +1,51 @@
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_18_R2; package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_18_R2;
import com.fastasyncworldedit.bukkit.adapter.StarlightRelighter;
import com.fastasyncworldedit.core.configuration.Settings; import com.fastasyncworldedit.core.configuration.Settings;
import com.fastasyncworldedit.core.extent.processor.lighting.NMSRelighter;
import com.fastasyncworldedit.core.extent.processor.lighting.Relighter;
import com.fastasyncworldedit.core.queue.IQueueChunk;
import com.fastasyncworldedit.core.queue.IQueueExtent; import com.fastasyncworldedit.core.queue.IQueueExtent;
import com.fastasyncworldedit.core.util.MathMan;
import com.fastasyncworldedit.core.util.TaskManager;
import com.sk89q.worldedit.internal.util.LogManagerCompat;
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongArraySet;
import it.unimi.dsi.fastutil.longs.LongIterator;
import it.unimi.dsi.fastutil.longs.LongSet;
import net.minecraft.server.MCUtil; import net.minecraft.server.MCUtil;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.TicketType; import net.minecraft.server.level.TicketType;
import net.minecraft.util.Unit; import net.minecraft.util.Unit;
import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.ChunkStatus; import net.minecraft.world.level.chunk.ChunkStatus;
import org.apache.logging.log4j.Logger;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.IntConsumer; import java.util.function.IntConsumer;
public class PaperweightStarlightRelighter implements Relighter { public class PaperweightStarlightRelighter extends StarlightRelighter<ServerLevel, ChunkPos> {
private static final Logger LOGGER = LogManagerCompat.getLogger();
private static final int CHUNKS_PER_BATCH = 1024; // 32 * 32
private static final int CHUNKS_PER_BATCH_SQRT_LOG2 = 5; // for shifting
private static final TicketType<Unit> FAWE_TICKET = TicketType.create("fawe_ticket", (a, b) -> 0); private static final TicketType<Unit> FAWE_TICKET = TicketType.create("fawe_ticket", (a, b) -> 0);
private static final int LIGHT_LEVEL = MCUtil.getTicketLevelFor(ChunkStatus.LIGHT); private static final int LIGHT_LEVEL = MCUtil.getTicketLevelFor(ChunkStatus.LIGHT);
public PaperweightStarlightRelighter(ServerLevel serverLevel, IQueueExtent<?> queue) {
private final ServerLevel serverLevel; super(serverLevel, queue);
private final ReentrantLock lock = new ReentrantLock();
private final Long2ObjectLinkedOpenHashMap<LongSet> regions = new Long2ObjectLinkedOpenHashMap<>();
private final ReentrantLock areaLock = new ReentrantLock();
private final NMSRelighter delegate;
@SuppressWarnings("rawtypes")
public PaperweightStarlightRelighter(ServerLevel serverLevel, IQueueExtent<IQueueChunk> queue) {
this.serverLevel = serverLevel;
this.delegate = new NMSRelighter(queue);
} }
@Override @Override
public boolean addChunk(int cx, int cz, byte[] skipReason, int bitmask) { protected ChunkPos createChunkPos(final long chunkKey) {
areaLock.lock(); return new ChunkPos(chunkKey);
try {
long key = MathMan.pairInt(cx >> CHUNKS_PER_BATCH_SQRT_LOG2, cz >> CHUNKS_PER_BATCH_SQRT_LOG2);
// TODO probably submit here already if chunks.size == CHUNKS_PER_BATCH?
LongSet chunks = this.regions.computeIfAbsent(key, k -> new LongArraySet(CHUNKS_PER_BATCH >> 2));
chunks.add(ChunkPos.asLong(cx, cz));
} finally {
areaLock.unlock();
}
return true;
} }
@Override @Override
public void addLightUpdate(int x, int y, int z) { protected long asLong(final int chunkX, final int chunkZ) {
delegate.addLightUpdate(x, y, z); return ChunkPos.asLong(chunkX, chunkZ);
} }
/*
* This method is called "recursively", iterating and removing elements
* from the regions linked map. This way, chunks are loaded in batches to avoid
* OOMEs.
*/
@Override @Override
public void fixLightingSafe(boolean sky) { protected CompletableFuture<?> chunkLoadFuture(final ChunkPos chunkPos) {
this.areaLock.lock(); return serverLevel.getWorld().getChunkAtAsync(chunkPos.x, chunkPos.z)
try { .thenAccept(c -> serverLevel.getChunkSource().addTicketAtLevel(
if (regions.isEmpty()) { FAWE_TICKET,
return; chunkPos,
} LIGHT_LEVEL,
LongSet first = regions.removeFirst(); Unit.INSTANCE
fixLighting(first, () -> fixLightingSafe(true)); ));
} finally {
this.areaLock.unlock();
}
} }
/* protected void invokeRelight(
* Processes a set of chunks and runs an action afterwards.
* The action is run async, the chunks are partly processed on the main thread
* (as required by the server).
*/
private void fixLighting(LongSet chunks, Runnable andThen) {
// convert from long keys to ChunkPos
Set<ChunkPos> coords = new HashSet<>();
LongIterator iterator = chunks.iterator();
while (iterator.hasNext()) {
coords.add(new ChunkPos(iterator.nextLong()));
}
TaskManager.taskManager().task(() -> {
// trigger chunk load and apply ticket on main thread
List<CompletableFuture<?>> futures = new ArrayList<>();
for (ChunkPos pos : coords) {
futures.add(serverLevel.getWorld().getChunkAtAsync(pos.x, pos.z)
.thenAccept(c -> serverLevel.getChunkSource().addTicketAtLevel(
FAWE_TICKET,
pos,
LIGHT_LEVEL,
Unit.INSTANCE
))
);
}
// collect futures and trigger relight once all chunks are loaded
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).thenAccept(v ->
invokeRelight(
coords,
c -> {
}, // no callback for single chunks required
i -> {
if (i != coords.size()) {
LOGGER.warn("Processed {} chunks instead of {}", i, coords.size());
}
// post process chunks on main thread
TaskManager.taskManager().task(() -> postProcessChunks(coords));
// call callback on our own threads
TaskManager.taskManager().async(andThen);
}
)
);
});
}
private void invokeRelight(
Set<ChunkPos> coords, Set<ChunkPos> coords,
Consumer<ChunkPos> chunkCallback, Consumer<ChunkPos> chunkCallback,
IntConsumer processCallback IntConsumer processCallback
@ -150,7 +61,7 @@ public class PaperweightStarlightRelighter implements Relighter {
* Allow the server to unload the chunks again. * Allow the server to unload the chunks again.
* Also, if chunk packets are sent delayed, we need to do that here * Also, if chunk packets are sent delayed, we need to do that here
*/ */
private void postProcessChunks(Set<ChunkPos> coords) { protected void postProcessChunks(Set<ChunkPos> coords) {
boolean delay = Settings.settings().LIGHTING.DELAY_PACKET_SENDING; boolean delay = Settings.settings().LIGHTING.DELAY_PACKET_SENDING;
for (ChunkPos pos : coords) { for (ChunkPos pos : coords) {
int x = pos.x; int x = pos.x;
@ -162,44 +73,4 @@ public class PaperweightStarlightRelighter implements Relighter {
} }
} }
@Override
public void clear() {
}
@Override
public void removeLighting() {
this.delegate.removeLighting();
}
@Override
public void fixBlockLighting() {
fixLightingSafe(true);
}
@Override
public void fixSkyLighting() {
fixLightingSafe(true);
}
@Override
public boolean isEmpty() {
return true;
}
@Override
public ReentrantLock getLock() {
return this.lock;
}
@Override
public boolean isFinished() {
return false;
}
@Override
public void close() throws Exception {
fixLightingSafe(true);
}
} }

Datei anzeigen

@ -4,7 +4,6 @@ import com.fastasyncworldedit.core.extent.processor.lighting.NullRelighter;
import com.fastasyncworldedit.core.extent.processor.lighting.RelightMode; import com.fastasyncworldedit.core.extent.processor.lighting.RelightMode;
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.IQueueChunk;
import com.fastasyncworldedit.core.queue.IQueueExtent; import com.fastasyncworldedit.core.queue.IQueueExtent;
import com.sk89q.worldedit.world.World; import com.sk89q.worldedit.world.World;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
@ -15,9 +14,7 @@ import javax.annotation.Nonnull;
public class PaperweightStarlightRelighterFactory implements RelighterFactory { public class PaperweightStarlightRelighterFactory implements RelighterFactory {
@Override @Override
public @Nonnull public @Nonnull Relighter createRelighter(RelightMode relightMode, World world, IQueueExtent<?> queue) {
@SuppressWarnings("rawtypes")
Relighter createRelighter(RelightMode relightMode, World world, IQueueExtent<IQueueChunk> queue) {
org.bukkit.World w = Bukkit.getWorld(world.getName()); org.bukkit.World w = Bukkit.getWorld(world.getName());
if (w == null) { if (w == null) {
return NullRelighter.INSTANCE; return NullRelighter.INSTANCE;

Datei anzeigen

@ -75,9 +75,11 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.Semaphore; import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -95,6 +97,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
.getInstance() .getInstance()
.getBukkitImplAdapter()); .getBukkitImplAdapter());
private final ReadWriteLock sectionLock = new ReentrantReadWriteLock(); private final ReadWriteLock sectionLock = new ReentrantReadWriteLock();
private final ReentrantLock callLock = new ReentrantLock();
private final ServerLevel serverLevel; private final ServerLevel serverLevel;
private final int chunkX; private final int chunkX;
private final int chunkZ; private final int chunkZ;
@ -104,14 +107,16 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
private final int maxSectionPosition; private final int maxSectionPosition;
private final Registry<Biome> biomeRegistry; private final Registry<Biome> biomeRegistry;
private final IdMap<Holder<Biome>> biomeHolderIdMap; private final IdMap<Holder<Biome>> biomeHolderIdMap;
private final ConcurrentHashMap<Integer, PaperweightGetBlocks_Copy> copies = new ConcurrentHashMap<>();
private final Object sendLock = new Object();
private LevelChunkSection[] sections; private LevelChunkSection[] sections;
private LevelChunk levelChunk; private LevelChunk levelChunk;
private DataLayer[] blockLight; private DataLayer[] blockLight;
private DataLayer[] skyLight; private DataLayer[] skyLight;
private boolean createCopy = false; private boolean createCopy = false;
private PaperweightGetBlocks_Copy copy = null;
private boolean forceLoadSections = true; private boolean forceLoadSections = true;
private boolean lightUpdate = false; private boolean lightUpdate = false;
private int copyKey = 0;
public PaperweightGetBlocks(World world, int chunkX, int chunkZ) { public PaperweightGetBlocks(World world, int chunkX, int chunkZ) {
this(((CraftWorld) world).getHandle(), chunkX, chunkZ); this(((CraftWorld) world).getHandle(), chunkX, chunkZ);
@ -146,13 +151,27 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
} }
@Override @Override
public void setCreateCopy(boolean createCopy) { public int setCreateCopy(boolean createCopy) {
if (!callLock.isHeldByCurrentThread()) {
throw new IllegalStateException("Attempting to set if chunk GET should create copy, but it is not call-locked.");
}
this.createCopy = createCopy; this.createCopy = createCopy;
return ++this.copyKey;
} }
@Override @Override
public IChunkGet getCopy() { public IChunkGet getCopy(final int key) {
return copy; return copies.remove(key);
}
@Override
public void lockCall() {
this.callLock.lock();
}
@Override
public void unlockCall() {
this.callLock.unlock();
} }
@Override @Override
@ -388,8 +407,17 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
@Override @Override
@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) {
if (!callLock.isHeldByCurrentThread()) {
throw new IllegalStateException("Attempted to call chunk GET but chunk was not call-locked.");
}
forceLoadSections = false; forceLoadSections = false;
copy = createCopy ? new PaperweightGetBlocks_Copy(getChunk()) : null; PaperweightGetBlocks_Copy copy = createCopy ? new PaperweightGetBlocks_Copy(levelChunk) : null;
if (createCopy) {
if (copies.containsKey(copyKey)) {
throw new IllegalStateException("Copy key already used.");
}
copies.put(copyKey, copy);
}
try { try {
ServerLevel nmsWorld = serverLevel; ServerLevel nmsWorld = serverLevel;
LevelChunk nmsChunk = ensureLoaded(nmsWorld, chunkX, chunkZ); LevelChunk nmsChunk = ensureLoaded(nmsWorld, chunkX, chunkZ);
@ -882,9 +910,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
if (super.sections[layer] != null) { if (super.sections[layer] != null) {
synchronized (super.sectionLocks[layer]) { synchronized (super.sectionLocks[layer]) {
if (super.sections[layer].isFull() && super.blocks[layer] != null) { if (super.sections[layer].isFull() && super.blocks[layer] != null) {
char[] blocks = new char[4096]; return super.blocks[layer];
System.arraycopy(super.blocks[layer], 0, blocks, 0, 4096);
return blocks;
} }
} }
} }
@ -892,8 +918,10 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
} }
@Override @Override
public synchronized void send(int mask, boolean lighting) { public void send(int mask, boolean lighting) {
PaperweightPlatformAdapter.sendChunk(serverLevel, chunkX, chunkZ, lighting); synchronized (sendLock) {
PaperweightPlatformAdapter.sendChunk(serverLevel, chunkX, chunkZ, lighting);
}
} }
/** /**
@ -1004,9 +1032,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
public LevelChunkSection[] getSections(boolean force) { public LevelChunkSection[] getSections(boolean force) {
force &= forceLoadSections; force &= forceLoadSections;
sectionLock.readLock().lock();
LevelChunkSection[] tmp = sections; LevelChunkSection[] tmp = sections;
sectionLock.readLock().unlock();
if (tmp == null || force) { if (tmp == null || force) {
try { try {
sectionLock.writeLock().lock(); sectionLock.writeLock().lock();

Datei anzeigen

@ -26,6 +26,7 @@ import net.minecraft.world.level.chunk.PalettedContainerRO;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
@ -105,7 +106,8 @@ public class PaperweightGetBlocks_Copy implements IChunkGet {
} }
@Override @Override
public void setCreateCopy(boolean createCopy) { public int setCreateCopy(boolean createCopy) {
return -1;
} }
@Override @Override
@ -199,6 +201,10 @@ public class PaperweightGetBlocks_Copy implements IChunkGet {
@Override @Override
public char[] load(int layer) { public char[] load(int layer) {
layer -= getMinSectionPosition(); layer -= getMinSectionPosition();
if (blocks[layer] == null) {
blocks[layer] = new char[4096];
Arrays.fill(blocks[layer], (char) BlockTypesCache.ReservedIDs.AIR);
}
return blocks[layer]; return blocks[layer];
} }

Datei anzeigen

@ -1,140 +1,51 @@
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_19_R3; package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_19_R3;
import com.fastasyncworldedit.bukkit.adapter.StarlightRelighter;
import com.fastasyncworldedit.core.configuration.Settings; import com.fastasyncworldedit.core.configuration.Settings;
import com.fastasyncworldedit.core.extent.processor.lighting.NMSRelighter;
import com.fastasyncworldedit.core.extent.processor.lighting.Relighter;
import com.fastasyncworldedit.core.queue.IQueueChunk;
import com.fastasyncworldedit.core.queue.IQueueExtent; import com.fastasyncworldedit.core.queue.IQueueExtent;
import com.fastasyncworldedit.core.util.MathMan;
import com.fastasyncworldedit.core.util.TaskManager;
import com.sk89q.worldedit.internal.util.LogManagerCompat;
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongArraySet;
import it.unimi.dsi.fastutil.longs.LongIterator;
import it.unimi.dsi.fastutil.longs.LongSet;
import net.minecraft.server.level.ChunkMap; import net.minecraft.server.level.ChunkMap;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.TicketType; import net.minecraft.server.level.TicketType;
import net.minecraft.util.Unit; import net.minecraft.util.Unit;
import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.ChunkStatus; import net.minecraft.world.level.chunk.ChunkStatus;
import org.apache.logging.log4j.Logger;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.IntConsumer; import java.util.function.IntConsumer;
public class PaperweightStarlightRelighter implements Relighter { public class PaperweightStarlightRelighter extends StarlightRelighter<ServerLevel, ChunkPos> {
private static final Logger LOGGER = LogManagerCompat.getLogger();
private static final int CHUNKS_PER_BATCH = 1024; // 32 * 32
private static final int CHUNKS_PER_BATCH_SQRT_LOG2 = 5; // for shifting
private static final TicketType<Unit> FAWE_TICKET = TicketType.create("fawe_ticket", (a, b) -> 0); private static final TicketType<Unit> FAWE_TICKET = TicketType.create("fawe_ticket", (a, b) -> 0);
private static final int LIGHT_LEVEL = ChunkMap.MAX_VIEW_DISTANCE + ChunkStatus.getDistance(ChunkStatus.LIGHT); private static final int LIGHT_LEVEL = ChunkMap.MAX_VIEW_DISTANCE + ChunkStatus.getDistance(ChunkStatus.LIGHT);
public PaperweightStarlightRelighter(ServerLevel serverLevel, IQueueExtent<?> queue) {
private final ServerLevel serverLevel; super(serverLevel, queue);
private final ReentrantLock lock = new ReentrantLock();
private final Long2ObjectLinkedOpenHashMap<LongSet> regions = new Long2ObjectLinkedOpenHashMap<>();
private final ReentrantLock areaLock = new ReentrantLock();
private final NMSRelighter delegate;
@SuppressWarnings("rawtypes")
public PaperweightStarlightRelighter(ServerLevel serverLevel, IQueueExtent<IQueueChunk> queue) {
this.serverLevel = serverLevel;
this.delegate = new NMSRelighter(queue);
} }
@Override @Override
public boolean addChunk(int cx, int cz, byte[] skipReason, int bitmask) { protected ChunkPos createChunkPos(final long chunkKey) {
areaLock.lock(); return new ChunkPos(chunkKey);
try {
long key = MathMan.pairInt(cx >> CHUNKS_PER_BATCH_SQRT_LOG2, cz >> CHUNKS_PER_BATCH_SQRT_LOG2);
// TODO probably submit here already if chunks.size == CHUNKS_PER_BATCH?
LongSet chunks = this.regions.computeIfAbsent(key, k -> new LongArraySet(CHUNKS_PER_BATCH >> 2));
chunks.add(ChunkPos.asLong(cx, cz));
} finally {
areaLock.unlock();
}
return true;
} }
@Override @Override
public void addLightUpdate(int x, int y, int z) { protected long asLong(final int chunkX, final int chunkZ) {
delegate.addLightUpdate(x, y, z); return ChunkPos.asLong(chunkX, chunkZ);
} }
/*
* This method is called "recursively", iterating and removing elements
* from the regions linked map. This way, chunks are loaded in batches to avoid
* OOMEs.
*/
@Override @Override
public void fixLightingSafe(boolean sky) { protected CompletableFuture<?> chunkLoadFuture(final ChunkPos chunkPos) {
this.areaLock.lock(); return serverLevel.getWorld().getChunkAtAsync(chunkPos.x, chunkPos.z)
try { .thenAccept(c -> serverLevel.getChunkSource().addTicketAtLevel(
if (regions.isEmpty()) { FAWE_TICKET,
return; chunkPos,
} LIGHT_LEVEL,
LongSet first = regions.removeFirst(); Unit.INSTANCE
fixLighting(first, () -> fixLightingSafe(true)); ));
} finally {
this.areaLock.unlock();
}
} }
/* protected void invokeRelight(
* Processes a set of chunks and runs an action afterwards.
* The action is run async, the chunks are partly processed on the main thread
* (as required by the server).
*/
private void fixLighting(LongSet chunks, Runnable andThen) {
// convert from long keys to ChunkPos
Set<ChunkPos> coords = new HashSet<>();
LongIterator iterator = chunks.iterator();
while (iterator.hasNext()) {
coords.add(new ChunkPos(iterator.nextLong()));
}
TaskManager.taskManager().task(() -> {
// trigger chunk load and apply ticket on main thread
List<CompletableFuture<?>> futures = new ArrayList<>();
for (ChunkPos pos : coords) {
futures.add(serverLevel.getWorld().getChunkAtAsync(pos.x, pos.z)
.thenAccept(c -> serverLevel.getChunkSource().addTicketAtLevel(
FAWE_TICKET,
pos,
LIGHT_LEVEL,
Unit.INSTANCE
))
);
}
// collect futures and trigger relight once all chunks are loaded
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).thenAccept(v ->
invokeRelight(
coords,
c -> {
}, // no callback for single chunks required
i -> {
if (i != coords.size()) {
LOGGER.warn("Processed {} chunks instead of {}", i, coords.size());
}
// post process chunks on main thread
TaskManager.taskManager().task(() -> postProcessChunks(coords));
// call callback on our own threads
TaskManager.taskManager().async(andThen);
}
)
);
});
}
private void invokeRelight(
Set<ChunkPos> coords, Set<ChunkPos> coords,
Consumer<ChunkPos> chunkCallback, Consumer<ChunkPos> chunkCallback,
IntConsumer processCallback IntConsumer processCallback
@ -150,7 +61,7 @@ public class PaperweightStarlightRelighter implements Relighter {
* Allow the server to unload the chunks again. * Allow the server to unload the chunks again.
* Also, if chunk packets are sent delayed, we need to do that here * Also, if chunk packets are sent delayed, we need to do that here
*/ */
private void postProcessChunks(Set<ChunkPos> coords) { protected void postProcessChunks(Set<ChunkPos> coords) {
boolean delay = Settings.settings().LIGHTING.DELAY_PACKET_SENDING; boolean delay = Settings.settings().LIGHTING.DELAY_PACKET_SENDING;
for (ChunkPos pos : coords) { for (ChunkPos pos : coords) {
int x = pos.x; int x = pos.x;
@ -162,44 +73,4 @@ public class PaperweightStarlightRelighter implements Relighter {
} }
} }
@Override
public void clear() {
}
@Override
public void removeLighting() {
this.delegate.removeLighting();
}
@Override
public void fixBlockLighting() {
fixLightingSafe(true);
}
@Override
public void fixSkyLighting() {
fixLightingSafe(true);
}
@Override
public boolean isEmpty() {
return true;
}
@Override
public ReentrantLock getLock() {
return this.lock;
}
@Override
public boolean isFinished() {
return false;
}
@Override
public void close() throws Exception {
fixLightingSafe(true);
}
} }

Datei anzeigen

@ -4,7 +4,6 @@ import com.fastasyncworldedit.core.extent.processor.lighting.NullRelighter;
import com.fastasyncworldedit.core.extent.processor.lighting.RelightMode; import com.fastasyncworldedit.core.extent.processor.lighting.RelightMode;
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.IQueueChunk;
import com.fastasyncworldedit.core.queue.IQueueExtent; import com.fastasyncworldedit.core.queue.IQueueExtent;
import com.sk89q.worldedit.world.World; import com.sk89q.worldedit.world.World;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
@ -15,9 +14,7 @@ import javax.annotation.Nonnull;
public class PaperweightStarlightRelighterFactory implements RelighterFactory { public class PaperweightStarlightRelighterFactory implements RelighterFactory {
@Override @Override
public @Nonnull public @Nonnull Relighter createRelighter(RelightMode relightMode, World world, IQueueExtent<?> queue) {
@SuppressWarnings("rawtypes")
Relighter createRelighter(RelightMode relightMode, World world, IQueueExtent<IQueueChunk> queue) {
org.bukkit.World w = Bukkit.getWorld(world.getName()); org.bukkit.World w = Bukkit.getWorld(world.getName());
if (w == null) { if (w == null) {
return NullRelighter.INSTANCE; return NullRelighter.INSTANCE;

Datei anzeigen

@ -152,7 +152,7 @@ public class PaperweightRegen extends Regenerator<ChunkAccess, ProtoChunk, Level
delegateField = CustomChunkGenerator.class.getDeclaredField("delegate"); delegateField = CustomChunkGenerator.class.getDeclaredField("delegate");
delegateField.setAccessible(true); delegateField.setAccessible(true);
chunkSourceField = ServerLevel.class.getDeclaredField(Refraction.pickName("chunkSource", "M")); chunkSourceField = ServerLevel.class.getDeclaredField(Refraction.pickName("chunkSource", "H"));
chunkSourceField.setAccessible(true); chunkSourceField.setAccessible(true);
generatorStructureStateField = ChunkMap.class.getDeclaredField(Refraction.pickName("chunkGeneratorState", "w")); generatorStructureStateField = ChunkMap.class.getDeclaredField(Refraction.pickName("chunkGeneratorState", "w"));

Datei anzeigen

@ -75,9 +75,11 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.Semaphore; import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -95,6 +97,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
.getInstance() .getInstance()
.getBukkitImplAdapter()); .getBukkitImplAdapter());
private final ReadWriteLock sectionLock = new ReentrantReadWriteLock(); private final ReadWriteLock sectionLock = new ReentrantReadWriteLock();
private final ReentrantLock callLock = new ReentrantLock();
private final ServerLevel serverLevel; private final ServerLevel serverLevel;
private final int chunkX; private final int chunkX;
private final int chunkZ; private final int chunkZ;
@ -104,14 +107,16 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
private final int maxSectionPosition; private final int maxSectionPosition;
private final Registry<Biome> biomeRegistry; private final Registry<Biome> biomeRegistry;
private final IdMap<Holder<Biome>> biomeHolderIdMap; private final IdMap<Holder<Biome>> biomeHolderIdMap;
private final ConcurrentHashMap<Integer, PaperweightGetBlocks_Copy> copies = new ConcurrentHashMap<>();
private final Object sendLock = new Object();
private LevelChunkSection[] sections; private LevelChunkSection[] sections;
private LevelChunk levelChunk; private LevelChunk levelChunk;
private DataLayer[] blockLight; private DataLayer[] blockLight;
private DataLayer[] skyLight; private DataLayer[] skyLight;
private boolean createCopy = false; private boolean createCopy = false;
private PaperweightGetBlocks_Copy copy = null;
private boolean forceLoadSections = true; private boolean forceLoadSections = true;
private boolean lightUpdate = false; private boolean lightUpdate = false;
private int copyKey = 0;
public PaperweightGetBlocks(World world, int chunkX, int chunkZ) { public PaperweightGetBlocks(World world, int chunkX, int chunkZ) {
this(((CraftWorld) world).getHandle(), chunkX, chunkZ); this(((CraftWorld) world).getHandle(), chunkX, chunkZ);
@ -146,13 +151,27 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
} }
@Override @Override
public void setCreateCopy(boolean createCopy) { public int setCreateCopy(boolean createCopy) {
if (!callLock.isHeldByCurrentThread()) {
throw new IllegalStateException("Attempting to set if chunk GET should create copy, but it is not call-locked.");
}
this.createCopy = createCopy; this.createCopy = createCopy;
return ++this.copyKey;
} }
@Override @Override
public IChunkGet getCopy() { public IChunkGet getCopy(final int key) {
return copy; return copies.remove(key);
}
@Override
public void lockCall() {
this.callLock.lock();
}
@Override
public void unlockCall() {
this.callLock.unlock();
} }
@Override @Override
@ -387,8 +406,17 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
@Override @Override
@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) {
if (!callLock.isHeldByCurrentThread()) {
throw new IllegalStateException("Attempted to call chunk GET but chunk was not call-locked.");
}
forceLoadSections = false; forceLoadSections = false;
copy = createCopy ? new PaperweightGetBlocks_Copy(getChunk()) : null; PaperweightGetBlocks_Copy copy = createCopy ? new PaperweightGetBlocks_Copy(levelChunk) : null;
if (createCopy) {
if (copies.containsKey(copyKey)) {
throw new IllegalStateException("Copy key already used.");
}
copies.put(copyKey, copy);
}
try { try {
ServerLevel nmsWorld = serverLevel; ServerLevel nmsWorld = serverLevel;
LevelChunk nmsChunk = ensureLoaded(nmsWorld, chunkX, chunkZ); LevelChunk nmsChunk = ensureLoaded(nmsWorld, chunkX, chunkZ);
@ -880,9 +908,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
if (super.sections[layer] != null) { if (super.sections[layer] != null) {
synchronized (super.sectionLocks[layer]) { synchronized (super.sectionLocks[layer]) {
if (super.sections[layer].isFull() && super.blocks[layer] != null) { if (super.sections[layer].isFull() && super.blocks[layer] != null) {
char[] blocks = new char[4096]; return super.blocks[layer];
System.arraycopy(super.blocks[layer], 0, blocks, 0, 4096);
return blocks;
} }
} }
} }
@ -890,8 +916,10 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
} }
@Override @Override
public synchronized void send(int mask, boolean lighting) { public void send(int mask, boolean lighting) {
PaperweightPlatformAdapter.sendChunk(serverLevel, chunkX, chunkZ, lighting); synchronized (sendLock) {
PaperweightPlatformAdapter.sendChunk(serverLevel, chunkX, chunkZ, lighting);
}
} }
/** /**
@ -1002,9 +1030,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
public LevelChunkSection[] getSections(boolean force) { public LevelChunkSection[] getSections(boolean force) {
force &= forceLoadSections; force &= forceLoadSections;
sectionLock.readLock().lock();
LevelChunkSection[] tmp = sections; LevelChunkSection[] tmp = sections;
sectionLock.readLock().unlock();
if (tmp == null || force) { if (tmp == null || force) {
try { try {
sectionLock.writeLock().lock(); sectionLock.writeLock().lock();

Datei anzeigen

@ -26,6 +26,7 @@ import net.minecraft.world.level.chunk.PalettedContainerRO;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
@ -105,7 +106,8 @@ public class PaperweightGetBlocks_Copy implements IChunkGet {
} }
@Override @Override
public void setCreateCopy(boolean createCopy) { public int setCreateCopy(boolean createCopy) {
return -1;
} }
@Override @Override
@ -199,6 +201,10 @@ public class PaperweightGetBlocks_Copy implements IChunkGet {
@Override @Override
public char[] load(int layer) { public char[] load(int layer) {
layer -= getMinSectionPosition(); layer -= getMinSectionPosition();
if (blocks[layer] == null) {
blocks[layer] = new char[4096];
Arrays.fill(blocks[layer], (char) BlockTypesCache.ReservedIDs.AIR);
}
return blocks[layer]; return blocks[layer];
} }

Datei anzeigen

@ -1,140 +1,51 @@
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R1; package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R1;
import com.fastasyncworldedit.bukkit.adapter.StarlightRelighter;
import com.fastasyncworldedit.core.configuration.Settings; import com.fastasyncworldedit.core.configuration.Settings;
import com.fastasyncworldedit.core.extent.processor.lighting.NMSRelighter;
import com.fastasyncworldedit.core.extent.processor.lighting.Relighter;
import com.fastasyncworldedit.core.queue.IQueueChunk;
import com.fastasyncworldedit.core.queue.IQueueExtent; import com.fastasyncworldedit.core.queue.IQueueExtent;
import com.fastasyncworldedit.core.util.MathMan;
import com.fastasyncworldedit.core.util.TaskManager;
import com.sk89q.worldedit.internal.util.LogManagerCompat;
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongArraySet;
import it.unimi.dsi.fastutil.longs.LongIterator;
import it.unimi.dsi.fastutil.longs.LongSet;
import net.minecraft.server.level.ChunkMap; import net.minecraft.server.level.ChunkMap;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.TicketType; import net.minecraft.server.level.TicketType;
import net.minecraft.util.Unit; import net.minecraft.util.Unit;
import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.ChunkStatus; import net.minecraft.world.level.chunk.ChunkStatus;
import org.apache.logging.log4j.Logger;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.IntConsumer; import java.util.function.IntConsumer;
public class PaperweightStarlightRelighter implements Relighter { public class PaperweightStarlightRelighter extends StarlightRelighter<ServerLevel, ChunkPos> {
private static final Logger LOGGER = LogManagerCompat.getLogger();
private static final int CHUNKS_PER_BATCH = 1024; // 32 * 32
private static final int CHUNKS_PER_BATCH_SQRT_LOG2 = 5; // for shifting
private static final TicketType<Unit> FAWE_TICKET = TicketType.create("fawe_ticket", (a, b) -> 0); private static final TicketType<Unit> FAWE_TICKET = TicketType.create("fawe_ticket", (a, b) -> 0);
private static final int LIGHT_LEVEL = ChunkMap.MAX_VIEW_DISTANCE + ChunkStatus.getDistance(ChunkStatus.LIGHT); private static final int LIGHT_LEVEL = ChunkMap.MAX_VIEW_DISTANCE + ChunkStatus.getDistance(ChunkStatus.LIGHT);
public PaperweightStarlightRelighter(ServerLevel serverLevel, IQueueExtent<?> queue) {
private final ServerLevel serverLevel; super(serverLevel, queue);
private final ReentrantLock lock = new ReentrantLock();
private final Long2ObjectLinkedOpenHashMap<LongSet> regions = new Long2ObjectLinkedOpenHashMap<>();
private final ReentrantLock areaLock = new ReentrantLock();
private final NMSRelighter delegate;
@SuppressWarnings("rawtypes")
public PaperweightStarlightRelighter(ServerLevel serverLevel, IQueueExtent<IQueueChunk> queue) {
this.serverLevel = serverLevel;
this.delegate = new NMSRelighter(queue);
} }
@Override @Override
public boolean addChunk(int cx, int cz, byte[] skipReason, int bitmask) { protected ChunkPos createChunkPos(final long chunkKey) {
areaLock.lock(); return new ChunkPos(chunkKey);
try {
long key = MathMan.pairInt(cx >> CHUNKS_PER_BATCH_SQRT_LOG2, cz >> CHUNKS_PER_BATCH_SQRT_LOG2);
// TODO probably submit here already if chunks.size == CHUNKS_PER_BATCH?
LongSet chunks = this.regions.computeIfAbsent(key, k -> new LongArraySet(CHUNKS_PER_BATCH >> 2));
chunks.add(ChunkPos.asLong(cx, cz));
} finally {
areaLock.unlock();
}
return true;
} }
@Override @Override
public void addLightUpdate(int x, int y, int z) { protected long asLong(final int chunkX, final int chunkZ) {
delegate.addLightUpdate(x, y, z); return ChunkPos.asLong(chunkX, chunkZ);
} }
/*
* This method is called "recursively", iterating and removing elements
* from the regions linked map. This way, chunks are loaded in batches to avoid
* OOMEs.
*/
@Override @Override
public void fixLightingSafe(boolean sky) { protected CompletableFuture<?> chunkLoadFuture(final ChunkPos chunkPos) {
this.areaLock.lock(); return serverLevel.getWorld().getChunkAtAsync(chunkPos.x, chunkPos.z)
try { .thenAccept(c -> serverLevel.getChunkSource().addTicketAtLevel(
if (regions.isEmpty()) { FAWE_TICKET,
return; chunkPos,
} LIGHT_LEVEL,
LongSet first = regions.removeFirst(); Unit.INSTANCE
fixLighting(first, () -> fixLightingSafe(true)); ));
} finally {
this.areaLock.unlock();
}
} }
/* protected void invokeRelight(
* Processes a set of chunks and runs an action afterwards.
* The action is run async, the chunks are partly processed on the main thread
* (as required by the server).
*/
private void fixLighting(LongSet chunks, Runnable andThen) {
// convert from long keys to ChunkPos
Set<ChunkPos> coords = new HashSet<>();
LongIterator iterator = chunks.iterator();
while (iterator.hasNext()) {
coords.add(new ChunkPos(iterator.nextLong()));
}
TaskManager.taskManager().task(() -> {
// trigger chunk load and apply ticket on main thread
List<CompletableFuture<?>> futures = new ArrayList<>();
for (ChunkPos pos : coords) {
futures.add(serverLevel.getWorld().getChunkAtAsync(pos.x, pos.z)
.thenAccept(c -> serverLevel.getChunkSource().addTicketAtLevel(
FAWE_TICKET,
pos,
LIGHT_LEVEL,
Unit.INSTANCE
))
);
}
// collect futures and trigger relight once all chunks are loaded
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).thenAccept(v ->
invokeRelight(
coords,
c -> {
}, // no callback for single chunks required
i -> {
if (i != coords.size()) {
LOGGER.warn("Processed {} chunks instead of {}", i, coords.size());
}
// post process chunks on main thread
TaskManager.taskManager().task(() -> postProcessChunks(coords));
// call callback on our own threads
TaskManager.taskManager().async(andThen);
}
)
);
});
}
private void invokeRelight(
Set<ChunkPos> coords, Set<ChunkPos> coords,
Consumer<ChunkPos> chunkCallback, Consumer<ChunkPos> chunkCallback,
IntConsumer processCallback IntConsumer processCallback
@ -150,7 +61,7 @@ public class PaperweightStarlightRelighter implements Relighter {
* Allow the server to unload the chunks again. * Allow the server to unload the chunks again.
* Also, if chunk packets are sent delayed, we need to do that here * Also, if chunk packets are sent delayed, we need to do that here
*/ */
private void postProcessChunks(Set<ChunkPos> coords) { protected void postProcessChunks(Set<ChunkPos> coords) {
boolean delay = Settings.settings().LIGHTING.DELAY_PACKET_SENDING; boolean delay = Settings.settings().LIGHTING.DELAY_PACKET_SENDING;
for (ChunkPos pos : coords) { for (ChunkPos pos : coords) {
int x = pos.x; int x = pos.x;
@ -162,44 +73,4 @@ public class PaperweightStarlightRelighter implements Relighter {
} }
} }
@Override
public void clear() {
}
@Override
public void removeLighting() {
this.delegate.removeLighting();
}
@Override
public void fixBlockLighting() {
fixLightingSafe(true);
}
@Override
public void fixSkyLighting() {
fixLightingSafe(true);
}
@Override
public boolean isEmpty() {
return true;
}
@Override
public ReentrantLock getLock() {
return this.lock;
}
@Override
public boolean isFinished() {
return false;
}
@Override
public void close() throws Exception {
fixLightingSafe(true);
}
} }

Datei anzeigen

@ -4,7 +4,6 @@ import com.fastasyncworldedit.core.extent.processor.lighting.NullRelighter;
import com.fastasyncworldedit.core.extent.processor.lighting.RelightMode; import com.fastasyncworldedit.core.extent.processor.lighting.RelightMode;
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.IQueueChunk;
import com.fastasyncworldedit.core.queue.IQueueExtent; import com.fastasyncworldedit.core.queue.IQueueExtent;
import com.sk89q.worldedit.world.World; import com.sk89q.worldedit.world.World;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
@ -15,9 +14,7 @@ import javax.annotation.Nonnull;
public class PaperweightStarlightRelighterFactory implements RelighterFactory { public class PaperweightStarlightRelighterFactory implements RelighterFactory {
@Override @Override
public @Nonnull public @Nonnull Relighter createRelighter(RelightMode relightMode, World world, IQueueExtent<?> queue) {
@SuppressWarnings("rawtypes")
Relighter createRelighter(RelightMode relightMode, World world, IQueueExtent<IQueueChunk> queue) {
org.bukkit.World w = Bukkit.getWorld(world.getName()); org.bukkit.World w = Bukkit.getWorld(world.getName());
if (w == null) { if (w == null) {
return NullRelighter.INSTANCE; return NullRelighter.INSTANCE;

Datei anzeigen

@ -12,6 +12,6 @@ repositories {
dependencies { dependencies {
// https://repo.papermc.io/service/rest/repository/browse/maven-public/io/papermc/paper/dev-bundle/ // https://repo.papermc.io/service/rest/repository/browse/maven-public/io/papermc/paper/dev-bundle/
the<PaperweightUserDependenciesExtension>().paperDevBundle("1.20.2-R0.1-20231008.101509-26") the<PaperweightUserDependenciesExtension>().paperDevBundle("1.20.2-R0.1-20231029.153906-63")
compileOnly(libs.paperlib) compileOnly(libs.paperlib)
} }

Datei anzeigen

@ -17,7 +17,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.sk89q.worldedit.bukkit.adapter.impl.v1_20_R2; package com.sk89q.worldedit.bukkit.adapter.ext.fawe.v1_20_R2;
import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader; import com.google.common.cache.CacheLoader;
@ -28,8 +28,6 @@ import com.google.common.util.concurrent.Futures;
import com.mojang.datafixers.util.Either; import com.mojang.datafixers.util.Either;
import com.mojang.serialization.Lifecycle; import com.mojang.serialization.Lifecycle;
import com.sk89q.jnbt.NBTConstants; import com.sk89q.jnbt.NBTConstants;
import com.sk89q.worldedit.bukkit.adapter.ext.fawe.v1_20_R2.PaperweightDataConverters;
import com.sk89q.worldedit.bukkit.adapter.impl.v1_20_R2.PaperweightFakePlayer;
import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.blocks.BaseItem; import com.sk89q.worldedit.blocks.BaseItem;
import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.blocks.BaseItemStack;
@ -358,8 +356,7 @@ public final class PaperweightAdapter implements BukkitImplAdapter<net.minecraft
@Override @Override
public WorldNativeAccess<?, ?, ?> createWorldNativeAccess(org.bukkit.World world) { public WorldNativeAccess<?, ?, ?> createWorldNativeAccess(org.bukkit.World world) {
return new com.sk89q.worldedit.bukkit.adapter.impl.v1_20_R2.PaperweightWorldNativeAccess(this, return new PaperweightWorldNativeAccess(this, new WeakReference<>(((CraftWorld) world).getHandle()));
new WeakReference<>(((CraftWorld) world).getHandle()));
} }
private static net.minecraft.core.Direction adapt(Direction face) { private static net.minecraft.core.Direction adapt(Direction face) {

Datei anzeigen

@ -168,7 +168,7 @@ public class PaperweightDataConverters extends DataFixerBuilder implements com.s
.getValue().getAsString(); .getValue().getAsString();
} }
private final com.sk89q.worldedit.bukkit.adapter.impl.v1_20_R2.PaperweightAdapter adapter; private final PaperweightAdapter adapter;
private static final NbtOps OPS_NBT = NbtOps.INSTANCE; private static final NbtOps OPS_NBT = NbtOps.INSTANCE;
private static final int LEGACY_VERSION = 1343; private static final int LEGACY_VERSION = 1343;
@ -204,8 +204,7 @@ public class PaperweightDataConverters extends DataFixerBuilder implements com.s
} }
} }
public PaperweightDataConverters(int dataVersion, public PaperweightDataConverters(int dataVersion, PaperweightAdapter adapter) {
com.sk89q.worldedit.bukkit.adapter.impl.v1_20_R2.PaperweightAdapter adapter) {
super(dataVersion); super(dataVersion);
DATA_VERSION = dataVersion; DATA_VERSION = dataVersion;
INSTANCE = this; INSTANCE = this;

Datei anzeigen

@ -17,7 +17,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.sk89q.worldedit.bukkit.adapter.impl.v1_20_R2; package com.sk89q.worldedit.bukkit.adapter.ext.fawe.v1_20_R2;
import com.mojang.authlib.GameProfile; import com.mojang.authlib.GameProfile;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;

Datei anzeigen

@ -17,7 +17,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.sk89q.worldedit.bukkit.adapter.impl.v1_20_R2; package com.sk89q.worldedit.bukkit.adapter.ext.fawe.v1_20_R2;
import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.bukkit.BukkitAdapter;
import com.sk89q.worldedit.internal.block.BlockStateIdAccess; import com.sk89q.worldedit.internal.block.BlockStateIdAccess;
@ -43,11 +43,11 @@ public class PaperweightWorldNativeAccess implements WorldNativeAccess<LevelChun
private static final int UPDATE = 1; private static final int UPDATE = 1;
private static final int NOTIFY = 2; private static final int NOTIFY = 2;
private final com.sk89q.worldedit.bukkit.adapter.impl.v1_20_R2.PaperweightAdapter adapter; private final PaperweightAdapter adapter;
private final WeakReference<ServerLevel> world; private final WeakReference<ServerLevel> world;
private SideEffectSet sideEffectSet; private SideEffectSet sideEffectSet;
public PaperweightWorldNativeAccess(com.sk89q.worldedit.bukkit.adapter.impl.v1_20_R2.PaperweightAdapter adapter, WeakReference<ServerLevel> world) { public PaperweightWorldNativeAccess(PaperweightAdapter adapter, WeakReference<ServerLevel> world) {
this.adapter = adapter; this.adapter = adapter;
this.world = world; this.world = world;
} }

Datei anzeigen

@ -20,7 +20,6 @@ import com.sk89q.worldedit.blocks.BaseItemStack;
import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.bukkit.BukkitAdapter;
import com.sk89q.worldedit.bukkit.BukkitWorld; import com.sk89q.worldedit.bukkit.BukkitWorld;
import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter;
import com.sk89q.worldedit.bukkit.adapter.impl.v1_20_R2.PaperweightAdapter;
import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.nbt.PaperweightLazyCompoundTag; import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.nbt.PaperweightLazyCompoundTag;
import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.regen.PaperweightRegen; import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.regen.PaperweightRegen;
import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.entity.BaseEntity;
@ -124,7 +123,7 @@ public final class PaperweightFaweAdapter extends CachedBukkitAdapter implements
} }
} }
private final PaperweightAdapter parent; private final com.sk89q.worldedit.bukkit.adapter.ext.fawe.v1_20_R2.PaperweightAdapter parent;
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// Code that may break between versions of Minecraft // Code that may break between versions of Minecraft
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
@ -135,7 +134,7 @@ public final class PaperweightFaweAdapter extends CachedBukkitAdapter implements
private Map<String, List<Property<?>>> allBlockProperties = null; private Map<String, List<Property<?>>> allBlockProperties = null;
public PaperweightFaweAdapter() throws NoSuchFieldException, NoSuchMethodException { public PaperweightFaweAdapter() throws NoSuchFieldException, NoSuchMethodException {
this.parent = new PaperweightAdapter(); this.parent = new com.sk89q.worldedit.bukkit.adapter.ext.fawe.v1_20_R2.PaperweightAdapter();
} }
@Nullable @Nullable

Datei anzeigen

@ -54,9 +54,11 @@ import org.bukkit.event.entity.CreatureSpawnEvent;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.util.*; import java.util.*;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.Semaphore; import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -74,6 +76,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
.getInstance() .getInstance()
.getBukkitImplAdapter()); .getBukkitImplAdapter());
private final ReadWriteLock sectionLock = new ReentrantReadWriteLock(); private final ReadWriteLock sectionLock = new ReentrantReadWriteLock();
private final ReentrantLock callLock = new ReentrantLock();
private final ServerLevel serverLevel; private final ServerLevel serverLevel;
private final int chunkX; private final int chunkX;
private final int chunkZ; private final int chunkZ;
@ -83,14 +86,16 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
private final int maxSectionPosition; private final int maxSectionPosition;
private final Registry<Biome> biomeRegistry; private final Registry<Biome> biomeRegistry;
private final IdMap<Holder<Biome>> biomeHolderIdMap; private final IdMap<Holder<Biome>> biomeHolderIdMap;
private final ConcurrentHashMap<Integer, PaperweightGetBlocks_Copy> copies = new ConcurrentHashMap<>();
private final Object sendLock = new Object();
private LevelChunkSection[] sections; private LevelChunkSection[] sections;
private LevelChunk levelChunk; private LevelChunk levelChunk;
private DataLayer[] blockLight; private DataLayer[] blockLight;
private DataLayer[] skyLight; private DataLayer[] skyLight;
private boolean createCopy = false; private boolean createCopy = false;
private PaperweightGetBlocks_Copy copy = null;
private boolean forceLoadSections = true; private boolean forceLoadSections = true;
private boolean lightUpdate = false; private boolean lightUpdate = false;
private int copyKey = 0;
public PaperweightGetBlocks(World world, int chunkX, int chunkZ) { public PaperweightGetBlocks(World world, int chunkX, int chunkZ) {
this(((CraftWorld) world).getHandle(), chunkX, chunkZ); this(((CraftWorld) world).getHandle(), chunkX, chunkZ);
@ -125,13 +130,27 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
} }
@Override @Override
public void setCreateCopy(boolean createCopy) { public int setCreateCopy(boolean createCopy) {
if (!callLock.isHeldByCurrentThread()) {
throw new IllegalStateException("Attempting to set if chunk GET should create copy, but it is not call-locked.");
}
this.createCopy = createCopy; this.createCopy = createCopy;
return ++this.copyKey;
} }
@Override @Override
public IChunkGet getCopy() { public IChunkGet getCopy(final int key) {
return copy; return copies.remove(key);
}
@Override
public void lockCall() {
this.callLock.lock();
}
@Override
public void unlockCall() {
this.callLock.unlock();
} }
@Override @Override
@ -374,8 +393,17 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
@Override @Override
@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) {
if (!callLock.isHeldByCurrentThread()) {
throw new IllegalStateException("Attempted to call chunk GET but chunk was not call-locked.");
}
forceLoadSections = false; forceLoadSections = false;
copy = createCopy ? new PaperweightGetBlocks_Copy(levelChunk) : null; PaperweightGetBlocks_Copy copy = createCopy ? new PaperweightGetBlocks_Copy(levelChunk) : null;
if (createCopy) {
if (copies.containsKey(copyKey)) {
throw new IllegalStateException("Copy key already used.");
}
copies.put(copyKey, copy);
}
try { try {
ServerLevel nmsWorld = serverLevel; ServerLevel nmsWorld = serverLevel;
LevelChunk nmsChunk = ensureLoaded(nmsWorld, chunkX, chunkZ); LevelChunk nmsChunk = ensureLoaded(nmsWorld, chunkX, chunkZ);
@ -867,9 +895,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
if (super.sections[layer] != null) { if (super.sections[layer] != null) {
synchronized (super.sectionLocks[layer]) { synchronized (super.sectionLocks[layer]) {
if (super.sections[layer].isFull() && super.blocks[layer] != null) { if (super.sections[layer].isFull() && super.blocks[layer] != null) {
char[] blocks = new char[4096]; return super.blocks[layer];
System.arraycopy(super.blocks[layer], 0, blocks, 0, 4096);
return blocks;
} }
} }
} }
@ -877,8 +903,10 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
} }
@Override @Override
public synchronized void send(int mask, boolean lighting) { public void send(int mask, boolean lighting) {
PaperweightPlatformAdapter.sendChunk(serverLevel, chunkX, chunkZ, lighting); synchronized (sendLock) {
PaperweightPlatformAdapter.sendChunk(serverLevel, chunkX, chunkZ, lighting);
}
} }
/** /**
@ -989,9 +1017,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
public LevelChunkSection[] getSections(boolean force) { public LevelChunkSection[] getSections(boolean force) {
force &= forceLoadSections; force &= forceLoadSections;
sectionLock.readLock().lock();
LevelChunkSection[] tmp = sections; LevelChunkSection[] tmp = sections;
sectionLock.readLock().unlock();
if (tmp == null || force) { if (tmp == null || force) {
try { try {
sectionLock.writeLock().lock(); sectionLock.writeLock().lock();

Datei anzeigen

@ -26,6 +26,7 @@ import net.minecraft.world.level.chunk.PalettedContainerRO;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
@ -105,7 +106,8 @@ public class PaperweightGetBlocks_Copy implements IChunkGet {
} }
@Override @Override
public void setCreateCopy(boolean createCopy) { public int setCreateCopy(boolean createCopy) {
return -1;
} }
@Override @Override
@ -199,6 +201,10 @@ public class PaperweightGetBlocks_Copy implements IChunkGet {
@Override @Override
public char[] load(int layer) { public char[] load(int layer) {
layer -= getMinSectionPosition(); layer -= getMinSectionPosition();
if (blocks[layer] == null) {
blocks[layer] = new char[4096];
Arrays.fill(blocks[layer], (char) BlockTypesCache.ReservedIDs.AIR);
}
return blocks[layer]; return blocks[layer];
} }

Datei anzeigen

@ -1,140 +1,51 @@
package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2; package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2;
import com.fastasyncworldedit.bukkit.adapter.StarlightRelighter;
import com.fastasyncworldedit.core.configuration.Settings; import com.fastasyncworldedit.core.configuration.Settings;
import com.fastasyncworldedit.core.extent.processor.lighting.NMSRelighter;
import com.fastasyncworldedit.core.extent.processor.lighting.Relighter;
import com.fastasyncworldedit.core.queue.IQueueChunk;
import com.fastasyncworldedit.core.queue.IQueueExtent; import com.fastasyncworldedit.core.queue.IQueueExtent;
import com.fastasyncworldedit.core.util.MathMan;
import com.fastasyncworldedit.core.util.TaskManager;
import com.sk89q.worldedit.internal.util.LogManagerCompat;
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongArraySet;
import it.unimi.dsi.fastutil.longs.LongIterator;
import it.unimi.dsi.fastutil.longs.LongSet;
import net.minecraft.server.level.ChunkMap; import net.minecraft.server.level.ChunkMap;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.TicketType; import net.minecraft.server.level.TicketType;
import net.minecraft.util.Unit; import net.minecraft.util.Unit;
import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.ChunkStatus; import net.minecraft.world.level.chunk.ChunkStatus;
import org.apache.logging.log4j.Logger;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.IntConsumer; import java.util.function.IntConsumer;
public class PaperweightStarlightRelighter implements Relighter { public class PaperweightStarlightRelighter extends StarlightRelighter<ServerLevel, ChunkPos> {
private static final Logger LOGGER = LogManagerCompat.getLogger();
private static final int CHUNKS_PER_BATCH = 1024; // 32 * 32
private static final int CHUNKS_PER_BATCH_SQRT_LOG2 = 5; // for shifting
private static final TicketType<Unit> FAWE_TICKET = TicketType.create("fawe_ticket", (a, b) -> 0); private static final TicketType<Unit> FAWE_TICKET = TicketType.create("fawe_ticket", (a, b) -> 0);
private static final int LIGHT_LEVEL = ChunkMap.MAX_VIEW_DISTANCE + ChunkStatus.getDistance(ChunkStatus.LIGHT); private static final int LIGHT_LEVEL = ChunkMap.MAX_VIEW_DISTANCE + ChunkStatus.getDistance(ChunkStatus.LIGHT);
public PaperweightStarlightRelighter(ServerLevel serverLevel, IQueueExtent<?> queue) {
private final ServerLevel serverLevel; super(serverLevel, queue);
private final ReentrantLock lock = new ReentrantLock();
private final Long2ObjectLinkedOpenHashMap<LongSet> regions = new Long2ObjectLinkedOpenHashMap<>();
private final ReentrantLock areaLock = new ReentrantLock();
private final NMSRelighter delegate;
@SuppressWarnings("rawtypes")
public PaperweightStarlightRelighter(ServerLevel serverLevel, IQueueExtent<IQueueChunk> queue) {
this.serverLevel = serverLevel;
this.delegate = new NMSRelighter(queue);
} }
@Override @Override
public boolean addChunk(int cx, int cz, byte[] skipReason, int bitmask) { protected ChunkPos createChunkPos(final long chunkKey) {
areaLock.lock(); return new ChunkPos(chunkKey);
try {
long key = MathMan.pairInt(cx >> CHUNKS_PER_BATCH_SQRT_LOG2, cz >> CHUNKS_PER_BATCH_SQRT_LOG2);
// TODO probably submit here already if chunks.size == CHUNKS_PER_BATCH?
LongSet chunks = this.regions.computeIfAbsent(key, k -> new LongArraySet(CHUNKS_PER_BATCH >> 2));
chunks.add(ChunkPos.asLong(cx, cz));
} finally {
areaLock.unlock();
}
return true;
} }
@Override @Override
public void addLightUpdate(int x, int y, int z) { protected long asLong(final int chunkX, final int chunkZ) {
delegate.addLightUpdate(x, y, z); return ChunkPos.asLong(chunkX, chunkZ);
} }
/*
* This method is called "recursively", iterating and removing elements
* from the regions linked map. This way, chunks are loaded in batches to avoid
* OOMEs.
*/
@Override @Override
public void fixLightingSafe(boolean sky) { protected CompletableFuture<?> chunkLoadFuture(final ChunkPos chunkPos) {
this.areaLock.lock(); return serverLevel.getWorld().getChunkAtAsync(chunkPos.x, chunkPos.z)
try { .thenAccept(c -> serverLevel.getChunkSource().addTicketAtLevel(
if (regions.isEmpty()) { FAWE_TICKET,
return; chunkPos,
} LIGHT_LEVEL,
LongSet first = regions.removeFirst(); Unit.INSTANCE
fixLighting(first, () -> fixLightingSafe(true)); ));
} finally {
this.areaLock.unlock();
}
} }
/* protected void invokeRelight(
* Processes a set of chunks and runs an action afterwards.
* The action is run async, the chunks are partly processed on the main thread
* (as required by the server).
*/
private void fixLighting(LongSet chunks, Runnable andThen) {
// convert from long keys to ChunkPos
Set<ChunkPos> coords = new HashSet<>();
LongIterator iterator = chunks.iterator();
while (iterator.hasNext()) {
coords.add(new ChunkPos(iterator.nextLong()));
}
TaskManager.taskManager().task(() -> {
// trigger chunk load and apply ticket on main thread
List<CompletableFuture<?>> futures = new ArrayList<>();
for (ChunkPos pos : coords) {
futures.add(serverLevel.getWorld().getChunkAtAsync(pos.x, pos.z)
.thenAccept(c -> serverLevel.getChunkSource().addTicketAtLevel(
FAWE_TICKET,
pos,
LIGHT_LEVEL,
Unit.INSTANCE
))
);
}
// collect futures and trigger relight once all chunks are loaded
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).thenAccept(v ->
invokeRelight(
coords,
c -> {
}, // no callback for single chunks required
i -> {
if (i != coords.size()) {
LOGGER.warn("Processed {} chunks instead of {}", i, coords.size());
}
// post process chunks on main thread
TaskManager.taskManager().task(() -> postProcessChunks(coords));
// call callback on our own threads
TaskManager.taskManager().async(andThen);
}
)
);
});
}
private void invokeRelight(
Set<ChunkPos> coords, Set<ChunkPos> coords,
Consumer<ChunkPos> chunkCallback, Consumer<ChunkPos> chunkCallback,
IntConsumer processCallback IntConsumer processCallback
@ -150,7 +61,7 @@ public class PaperweightStarlightRelighter implements Relighter {
* Allow the server to unload the chunks again. * Allow the server to unload the chunks again.
* Also, if chunk packets are sent delayed, we need to do that here * Also, if chunk packets are sent delayed, we need to do that here
*/ */
private void postProcessChunks(Set<ChunkPos> coords) { protected void postProcessChunks(Set<ChunkPos> coords) {
boolean delay = Settings.settings().LIGHTING.DELAY_PACKET_SENDING; boolean delay = Settings.settings().LIGHTING.DELAY_PACKET_SENDING;
for (ChunkPos pos : coords) { for (ChunkPos pos : coords) {
int x = pos.x; int x = pos.x;
@ -162,44 +73,4 @@ public class PaperweightStarlightRelighter implements Relighter {
} }
} }
@Override
public void clear() {
}
@Override
public void removeLighting() {
this.delegate.removeLighting();
}
@Override
public void fixBlockLighting() {
fixLightingSafe(true);
}
@Override
public void fixSkyLighting() {
fixLightingSafe(true);
}
@Override
public boolean isEmpty() {
return true;
}
@Override
public ReentrantLock getLock() {
return this.lock;
}
@Override
public boolean isFinished() {
return false;
}
@Override
public void close() throws Exception {
fixLightingSafe(true);
}
} }

Datei anzeigen

@ -4,7 +4,6 @@ import com.fastasyncworldedit.core.extent.processor.lighting.NullRelighter;
import com.fastasyncworldedit.core.extent.processor.lighting.RelightMode; import com.fastasyncworldedit.core.extent.processor.lighting.RelightMode;
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.IQueueChunk;
import com.fastasyncworldedit.core.queue.IQueueExtent; import com.fastasyncworldedit.core.queue.IQueueExtent;
import com.sk89q.worldedit.world.World; import com.sk89q.worldedit.world.World;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
@ -15,9 +14,7 @@ import javax.annotation.Nonnull;
public class PaperweightStarlightRelighterFactory implements RelighterFactory { public class PaperweightStarlightRelighterFactory implements RelighterFactory {
@Override @Override
public @Nonnull public @Nonnull Relighter createRelighter(RelightMode relightMode, World world, IQueueExtent<?> queue) {
@SuppressWarnings("rawtypes")
Relighter createRelighter(RelightMode relightMode, World world, IQueueExtent<IQueueChunk> queue) {
org.bukkit.World w = Bukkit.getWorld(world.getName()); org.bukkit.World w = Bukkit.getWorld(world.getName());
if (w == null) { if (w == null) {
return NullRelighter.INSTANCE; return NullRelighter.INSTANCE;

Datei anzeigen

@ -5,7 +5,6 @@ import com.fastasyncworldedit.core.extent.processor.lighting.NMSRelighter;
import com.fastasyncworldedit.core.extent.processor.lighting.RelightMode; import com.fastasyncworldedit.core.extent.processor.lighting.RelightMode;
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.IQueueChunk;
import com.fastasyncworldedit.core.queue.IQueueExtent; import com.fastasyncworldedit.core.queue.IQueueExtent;
import com.sk89q.worldedit.world.World; import com.sk89q.worldedit.world.World;
@ -14,8 +13,7 @@ import javax.annotation.Nonnull;
public class NMSRelighterFactory implements RelighterFactory { public class NMSRelighterFactory implements RelighterFactory {
@Override @Override
public @Nonnull public @Nonnull Relighter createRelighter(RelightMode relightMode, World world, IQueueExtent<?> queue) {
Relighter createRelighter(RelightMode relightMode, World world, IQueueExtent<IQueueChunk> queue) {
return new NMSRelighter( return new NMSRelighter(
queue, queue,
relightMode != null ? relightMode : RelightMode.valueOf(Settings.settings().LIGHTING.MODE) relightMode != null ? relightMode : RelightMode.valueOf(Settings.settings().LIGHTING.MODE)

Datei anzeigen

@ -41,6 +41,7 @@ import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.function.Function; import java.util.function.Function;
/** /**
@ -158,16 +159,14 @@ public abstract class Regenerator<IChunkAccess, ProtoChunk extends IChunkAccess,
} }
private boolean generate() throws Exception { private boolean generate() throws Exception {
ThreadFactory factory = new ThreadFactoryBuilder()
.setNameFormat("FAWE Regenerator - %d")
.build();
if (generateConcurrent) { if (generateConcurrent) {
//Using concurrent chunk generation //Using concurrent chunk generation
executor = Executors.newFixedThreadPool(Settings.settings().QUEUE.PARALLEL_THREADS, new ThreadFactoryBuilder() executor = Executors.newFixedThreadPool(Settings.settings().QUEUE.PARALLEL_THREADS, factory);
.setNameFormat("fawe-regen-%d")
.build()
);
} else { // else using sequential chunk generation, concurrent not supported } else { // else using sequential chunk generation, concurrent not supported
executor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder() executor = Executors.newSingleThreadExecutor(factory);
.setNameFormat("fawe-regen-%d")
.build());
} }
//TODO: can we get that required radius down without affecting chunk generation (e.g. strucures, features, ...)? //TODO: can we get that required radius down without affecting chunk generation (e.g. strucures, features, ...)?

Datei anzeigen

@ -0,0 +1,196 @@
package com.fastasyncworldedit.bukkit.adapter;
import com.fastasyncworldedit.core.extent.processor.lighting.NMSRelighter;
import com.fastasyncworldedit.core.extent.processor.lighting.Relighter;
import com.fastasyncworldedit.core.queue.IQueueExtent;
import com.fastasyncworldedit.core.util.MathMan;
import com.fastasyncworldedit.core.util.TaskManager;
import com.sk89q.worldedit.internal.util.LogManagerCompat;
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongArraySet;
import it.unimi.dsi.fastutil.longs.LongIterator;
import it.unimi.dsi.fastutil.longs.LongSet;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
/**
* A base class for version-specific implementations of the starlight relighting mechanism
*
* @param <SERVER_LEVEL> the version-specific ServerLevel type
* @param <CHUNK_POS> the version-specific ChunkPos type
* @since 2.8.2
*/
public abstract class StarlightRelighter<SERVER_LEVEL, CHUNK_POS> implements Relighter {
protected static final Logger LOGGER = LogManagerCompat.getLogger();
private static final int CHUNKS_PER_BATCH = 1024; // 32 * 32
private static final int CHUNKS_PER_BATCH_SQRT_LOG2 = 5; // for shifting
private final ReentrantLock lock = new ReentrantLock();
private final Long2ObjectLinkedOpenHashMap<LongSet> regions = new Long2ObjectLinkedOpenHashMap<>();
private final ReentrantLock areaLock = new ReentrantLock();
private final NMSRelighter delegate;
protected final SERVER_LEVEL serverLevel;
protected StarlightRelighter(SERVER_LEVEL serverLevel, IQueueExtent<?> queue) {
this.serverLevel = serverLevel;
this.delegate = new NMSRelighter(queue);
}
protected Set<CHUNK_POS> convertChunkKeysToChunkPos(LongSet chunks) {
// convert from long keys to ChunkPos
Set<CHUNK_POS> coords = new HashSet<>();
LongIterator iterator = chunks.iterator();
while (iterator.hasNext()) {
coords.add(createChunkPos(iterator.nextLong()));
}
return coords;
}
protected abstract CHUNK_POS createChunkPos(long chunkKey);
protected abstract long asLong(int chunkX, int chunkZ);
protected abstract CompletableFuture<?> chunkLoadFuture(CHUNK_POS pos);
protected List<CompletableFuture<?>> chunkLoadFutures(Set<CHUNK_POS> coords) {
List<CompletableFuture<?>> futures = new ArrayList<>();
for (final CHUNK_POS coord : coords) {
futures.add(chunkLoadFuture(coord));
}
return futures;
}
@NotNull
protected IntConsumer postProcessCallback(Runnable andThen, Set<CHUNK_POS> coords) {
return i -> {
if (i != coords.size()) {
LOGGER.warn("Processed {} chunks instead of {}", i, coords.size());
}
// post process chunks on main thread
TaskManager.taskManager().task(() -> postProcessChunks(coords));
// call callback on our own threads
TaskManager.taskManager().async(andThen);
};
}
protected abstract void invokeRelight(
Set<CHUNK_POS> coords,
Consumer<CHUNK_POS> chunkCallback,
IntConsumer processCallback
);
protected abstract void postProcessChunks(Set<CHUNK_POS> coords);
/*
* Processes a set of chunks and runs an action afterwards.
* The action is run async, the chunks are partly processed on the main thread
* (as required by the server).
*/
protected void fixLighting(LongSet chunks, Runnable andThen) {
Set<CHUNK_POS> coords = convertChunkKeysToChunkPos(chunks);
TaskManager.taskManager().task(() -> {
// trigger chunk load and apply ticket on main thread
List<CompletableFuture<?>> futures = chunkLoadFutures(coords);
// collect futures and trigger relight once all chunks are loaded
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).thenAccept(v ->
invokeRelight(
coords,
c -> {
}, // no callback for single chunks required
postProcessCallback(andThen, coords)
)
);
});
}
@Override
public boolean addChunk(int cx, int cz, byte[] skipReason, int bitmask) {
areaLock.lock();
try {
long key = MathMan.pairInt(cx >> CHUNKS_PER_BATCH_SQRT_LOG2, cz >> CHUNKS_PER_BATCH_SQRT_LOG2);
// TODO probably submit here already if chunks.size == CHUNKS_PER_BATCH?
LongSet chunks = this.regions.computeIfAbsent(key, k -> new LongArraySet(CHUNKS_PER_BATCH >> 2));
chunks.add(asLong(cx, cz));
} finally {
areaLock.unlock();
}
return true;
}
/*
* This method is called "recursively", iterating and removing elements
* from the regions linked map. This way, chunks are loaded in batches to avoid
* OOMEs.
*/
@Override
public void fixLightingSafe(boolean sky) {
this.areaLock.lock();
try {
if (regions.isEmpty()) {
return;
}
LongSet first = regions.removeFirst();
fixLighting(first, () -> fixLightingSafe(true));
} finally {
this.areaLock.unlock();
}
}
@Override
public void addLightUpdate(int x, int y, int z) {
this.delegate.addLightUpdate(x, y, z);
}
@Override
public void clear() {
}
@Override
public void removeLighting() {
this.delegate.removeLighting();
}
@Override
public void fixBlockLighting() {
fixLightingSafe(true);
}
@Override
public void fixSkyLighting() {
fixLightingSafe(true);
}
@Override
public boolean isEmpty() {
return true;
}
@Override
public ReentrantLock getLock() {
return this.lock;
}
@Override
public boolean isFinished() {
return false;
}
@Override
public void close() throws Exception {
fixLightingSafe(true);
}
}

Datei anzeigen

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

Datei anzeigen

@ -18,6 +18,7 @@ import com.fastasyncworldedit.core.util.collection.CleanableThreadLocal;
import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader; import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache; import com.google.common.cache.LoadingCache;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.sk89q.jnbt.ByteArrayTag; import com.sk89q.jnbt.ByteArrayTag;
import com.sk89q.jnbt.ByteTag; import com.sk89q.jnbt.ByteTag;
import com.sk89q.jnbt.CompoundTag; import com.sk89q.jnbt.CompoundTag;
@ -48,7 +49,6 @@ import java.util.Map.Entry;
import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.CancellationException; import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -60,16 +60,17 @@ import java.util.function.Supplier;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
public enum FaweCache implements Trimable { public enum FaweCache implements Trimable {
/**
* @deprecated Use {@link #INSTANCE} to get an instance.
*/
@Deprecated(forRemoval = true, since = "2.0.0")
IMP,
/** /**
* @since 2.0.0 * @since 2.0.0
*/ */
INSTANCE; INSTANCE;
/**
* @deprecated Use {@link #INSTANCE} to get an instance.
*/
@Deprecated(forRemoval = true, since = "2.0.0")
public static final FaweCache IMP = INSTANCE;
private static final Logger LOGGER = LogManagerCompat.getLogger(); private static final Logger LOGGER = LogManagerCompat.getLogger();
public final int BLOCKS_PER_LAYER = 4096; public final int BLOCKS_PER_LAYER = 4096;
@ -616,7 +617,7 @@ public enum FaweCache implements Trimable {
ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(nThreads, true); ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(nThreads, true);
return new ThreadPoolExecutor(nThreads, nThreads, return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS, queue, 0L, TimeUnit.MILLISECONDS, queue,
Executors.defaultThreadFactory(), new ThreadFactoryBuilder().setNameFormat("FAWE Blocking Executor - %d").build(),
new ThreadPoolExecutor.CallerRunsPolicy() new ThreadPoolExecutor.CallerRunsPolicy()
) { ) {

Datei anzeigen

@ -19,14 +19,14 @@ import java.util.stream.Stream;
public class Settings extends Config { public class Settings extends Config {
@Ignore
static Settings INSTANCE = new Settings();
/** /**
* @deprecated Use {@link #settings()} instead to get an instance. * @deprecated Use {@link #settings()} instead to get an instance.
*/ */
@Ignore @Ignore
@Deprecated(forRemoval = true, since = "2.0.0") @Deprecated(forRemoval = true, since = "2.0.0")
public static final Settings IMP = new Settings(); public static final Settings IMP = INSTANCE;
@Ignore
static Settings INSTANCE = new Settings();
@Ignore @Ignore
public boolean PROTOCOL_SUPPORT_FIX = false; public boolean PROTOCOL_SUPPORT_FIX = false;
@Comment("These first 6 aren't configurable") // This is a comment @Comment("These first 6 aren't configurable") // This is a comment

Datei anzeigen

@ -13,7 +13,7 @@ public class DBHandler {
* @deprecated Use {@link #dbHandler()} instead. * @deprecated Use {@link #dbHandler()} instead.
*/ */
@Deprecated(forRemoval = true, since = "2.0.0") @Deprecated(forRemoval = true, since = "2.0.0")
public static final DBHandler IMP = new DBHandler(); public static final DBHandler IMP = dbHandler();
private static final Logger LOGGER = LogManagerCompat.getLogger(); private static final Logger LOGGER = LogManagerCompat.getLogger();
private static DBHandler INSTANCE; private static DBHandler INSTANCE;
private final Map<World, RollbackDatabase> databases = new ConcurrentHashMap<>(8, 0.9f, 1); private final Map<World, RollbackDatabase> databases = new ConcurrentHashMap<>(8, 0.9f, 1);

Datei anzeigen

@ -4,7 +4,6 @@ import com.fastasyncworldedit.core.Fawe;
import com.fastasyncworldedit.core.configuration.Settings; import com.fastasyncworldedit.core.configuration.Settings;
import com.fastasyncworldedit.core.math.BlockVectorSet; import com.fastasyncworldedit.core.math.BlockVectorSet;
import com.fastasyncworldedit.core.math.MutableBlockVector3; import com.fastasyncworldedit.core.math.MutableBlockVector3;
import com.fastasyncworldedit.core.queue.IQueueChunk;
import com.fastasyncworldedit.core.queue.IQueueExtent; import com.fastasyncworldedit.core.queue.IQueueExtent;
import com.fastasyncworldedit.core.queue.implementation.chunk.ChunkHolder; import com.fastasyncworldedit.core.queue.implementation.chunk.ChunkHolder;
import com.fastasyncworldedit.core.util.MathMan; import com.fastasyncworldedit.core.util.MathMan;
@ -34,7 +33,6 @@ import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
@SuppressWarnings("rawtypes")
public class NMSRelighter implements Relighter { public class NMSRelighter implements Relighter {
private static final int DISPATCH_SIZE = 64; private static final int DISPATCH_SIZE = 64;
@ -51,7 +49,7 @@ public class NMSRelighter implements Relighter {
} }
public final MutableBlockVector3 mutableBlockPos = new MutableBlockVector3(0, 0, 0); public final MutableBlockVector3 mutableBlockPos = new MutableBlockVector3(0, 0, 0);
private final IQueueExtent<IQueueChunk> queue; private final IQueueExtent<?> queue;
private final Map<Long, RelightSkyEntry> skyToRelight; private final Map<Long, RelightSkyEntry> skyToRelight;
private final Object present = new Object(); private final Object present = new Object();
private final Map<Long, Integer> chunksToSend; private final Map<Long, Integer> chunksToSend;
@ -66,11 +64,11 @@ public class NMSRelighter implements Relighter {
private final AtomicBoolean finished = new AtomicBoolean(false); private final AtomicBoolean finished = new AtomicBoolean(false);
private boolean removeFirst; private boolean removeFirst;
public NMSRelighter(IQueueExtent<IQueueChunk> queue) { public NMSRelighter(IQueueExtent<?> queue) {
this(queue, null); this(queue, null);
} }
public NMSRelighter(IQueueExtent<IQueueChunk> queue, RelightMode relightMode) { public NMSRelighter(IQueueExtent<?> queue, RelightMode relightMode) {
this.queue = queue; this.queue = queue;
this.skyToRelight = new Long2ObjectOpenHashMap<>(12); this.skyToRelight = new Long2ObjectOpenHashMap<>(12);
this.lightQueue = new Long2ObjectOpenHashMap<>(12); this.lightQueue = new Long2ObjectOpenHashMap<>(12);

Datei anzeigen

@ -1,6 +1,5 @@
package com.fastasyncworldedit.core.extent.processor.lighting; package com.fastasyncworldedit.core.extent.processor.lighting;
import com.fastasyncworldedit.core.queue.IQueueChunk;
import com.fastasyncworldedit.core.queue.IQueueExtent; import com.fastasyncworldedit.core.queue.IQueueExtent;
import com.sk89q.worldedit.world.World; import com.sk89q.worldedit.world.World;
@ -25,6 +24,6 @@ public interface RelighterFactory {
* @return a new Relighter instance with the specified settings. * @return a new Relighter instance with the specified settings.
*/ */
@Nonnull @Nonnull
Relighter createRelighter(RelightMode relightMode, World world, IQueueExtent<IQueueChunk> queue); Relighter createRelighter(RelightMode relightMode, World world, IQueueExtent<?> queue);
} }

Datei anzeigen

@ -51,13 +51,31 @@ public interface IChunkGet extends IBlocks, Trimable, InputExtent, ITileInput {
boolean isCreateCopy(); boolean isCreateCopy();
void setCreateCopy(boolean createCopy); /**
* Not for external API use. Internal use only.
*/
int setCreateCopy(boolean createCopy);
@Nullable @Nullable
default IChunkGet getCopy() { default IChunkGet getCopy(int key) {
return null; 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 2.8.2
*/
default void lockCall() {}
/**
* Unlock {@link IChunkGet#call(IChunkSet, Runnable)} (and other related methods) to executions from other threads
*
* @since 2.8.2
*/
default void unlockCall() {}
/** /**
* Flush the block lighting array (section*blocks) to the chunk GET between the given section indices. Negative allowed. * Flush the block lighting array (section*blocks) to the chunk GET between the given section indices. Negative allowed.
* *

Datei anzeigen

@ -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() {
}
} }

Datei anzeigen

@ -135,9 +135,6 @@ public interface IQueueExtent<T extends IChunk> extends Flushable, Trimable, ICh
return block; return block;
} }
T chunk = this.getOrCreateChunk(chunkX, chunkZ); T chunk = this.getOrCreateChunk(chunkX, chunkZ);
// Initialize
chunk.init(this, chunkX, chunkZ);
chunk.setFastMode(isFastMode());
T newChunk = filter.applyChunk(chunk, region); T newChunk = filter.applyChunk(chunk, region);
if (newChunk != null) { if (newChunk != null) {

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -32,7 +32,7 @@ public class WEManager {
* @deprecated Use {@link #weManager()} instead. * @deprecated Use {@link #weManager()} instead.
*/ */
@Deprecated(forRemoval = true, since = "2.0.0") @Deprecated(forRemoval = true, since = "2.0.0")
public static WEManager IMP = new WEManager(); public static WEManager IMP = weManager();
private final ArrayDeque<FaweMaskManager> managers = new ArrayDeque<>(); private final ArrayDeque<FaweMaskManager> managers = new ArrayDeque<>();
/** /**

Datei anzeigen

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

Datei anzeigen

@ -33,6 +33,7 @@ import com.fastasyncworldedit.core.limit.FaweLimit;
import com.fastasyncworldedit.core.util.BrushCache; import com.fastasyncworldedit.core.util.BrushCache;
import com.fastasyncworldedit.core.util.MainUtil; import com.fastasyncworldedit.core.util.MainUtil;
import com.fastasyncworldedit.core.util.StringMan; import com.fastasyncworldedit.core.util.StringMan;
import com.fastasyncworldedit.core.util.TaskManager;
import com.fastasyncworldedit.core.util.TextureHolder; import com.fastasyncworldedit.core.util.TextureHolder;
import com.fastasyncworldedit.core.util.TextureUtil; import com.fastasyncworldedit.core.util.TextureUtil;
import com.fastasyncworldedit.core.wrappers.WorldWrapper; import com.fastasyncworldedit.core.wrappers.WorldWrapper;
@ -99,7 +100,6 @@ import java.util.TimeZone;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -143,7 +143,7 @@ public class LocalSession implements TextureHolder {
} }
}); });
private transient volatile Integer historyNegativeIndex; private transient volatile Integer historyNegativeIndex;
private transient final Lock historyWriteLock = new ReentrantLock(true); private transient final ReentrantLock historyWriteLock = new ReentrantLock(true);
private final transient Int2ObjectOpenHashMap<Tool> tools = new Int2ObjectOpenHashMap<>(0); private final transient Int2ObjectOpenHashMap<Tool> tools = new Int2ObjectOpenHashMap<>(0);
private transient Mask sourceMask; private transient Mask sourceMask;
private transient TextureUtil texture; private transient TextureUtil texture;
@ -405,6 +405,23 @@ public class LocalSession implements TextureHolder {
*/ */
public void clearHistory() { public void clearHistory() {
//FAWE start //FAWE start
boolean mainThread = Fawe.isMainThread();
if (mainThread && !historyWriteLock.tryLock()) {
// Do not make main thread wait if we cannot immediately clear history (on player logout usually)
TaskManager.taskManager().async(this::clearHistoryTask);
return;
}
try {
clearHistoryTask();
} finally {
// only if we are on the main thread, we ever called tryLock -> need to unlock again
if (mainThread) {
historyWriteLock.unlock();
}
}
}
private void clearHistoryTask() {
historyWriteLock.lock(); historyWriteLock.lock();
try { try {
// Ensure that changesets are properly removed // Ensure that changesets are properly removed
@ -420,8 +437,8 @@ public class LocalSession implements TextureHolder {
save(); save();
historySize = 0; historySize = 0;
currentWorld = null; currentWorld = null;
//FAWE end
} }
//FAWE end
/** /**
* Remember an edit session for the undo history. If the history maximum * Remember an edit session for the undo history. If the history maximum

Datei anzeigen

@ -29,7 +29,6 @@ import com.fastasyncworldedit.core.extent.clipboard.DiskOptimizedClipboard;
import com.fastasyncworldedit.core.extent.clipboard.MultiClipboardHolder; import com.fastasyncworldedit.core.extent.clipboard.MultiClipboardHolder;
import com.fastasyncworldedit.core.extent.clipboard.ReadOnlyClipboard; import com.fastasyncworldedit.core.extent.clipboard.ReadOnlyClipboard;
import com.fastasyncworldedit.core.extent.clipboard.URIClipboardHolder; import com.fastasyncworldedit.core.extent.clipboard.URIClipboardHolder;
import com.fastasyncworldedit.core.internal.exception.FaweException;
import com.fastasyncworldedit.core.internal.io.FastByteArrayOutputStream; import com.fastasyncworldedit.core.internal.io.FastByteArrayOutputStream;
import com.fastasyncworldedit.core.limit.FaweLimit; import com.fastasyncworldedit.core.limit.FaweLimit;
import com.fastasyncworldedit.core.util.ImgurUtility; import com.fastasyncworldedit.core.util.ImgurUtility;
@ -449,6 +448,8 @@ public class ClipboardCommands {
boolean atOrigin, boolean atOrigin,
@Switch(name = 's', desc = "Select the region after pasting") @Switch(name = 's', desc = "Select the region after pasting")
boolean selectPasted, boolean selectPasted,
@Switch(name = 'n', desc = "No paste, select only. (Implies -s)")
boolean onlySelect,
@Switch(name = 'e', desc = "Paste entities if available") @Switch(name = 'e', desc = "Paste entities if available")
boolean pasteEntities, boolean pasteEntities,
@Switch(name = 'b', desc = "Paste biomes if available") @Switch(name = 'b', desc = "Paste biomes if available")
@ -460,10 +461,12 @@ public class ClipboardCommands {
final BlockVector3 to = atOrigin ? origin : session.getPlacementPosition(actor); final BlockVector3 to = atOrigin ? origin : session.getPlacementPosition(actor);
checkPaste(actor, editSession, to, holder, clipboard); checkPaste(actor, editSession, to, holder, clipboard);
clipboard.paste(editSession, to, !ignoreAirBlocks, pasteEntities, pasteBiomes); if (!onlySelect) {
clipboard.paste(editSession, to, !ignoreAirBlocks, pasteEntities, pasteBiomes);
}
Region region = clipboard.getRegion().clone(); Region region = clipboard.getRegion().clone();
if (selectPasted) { if (selectPasted || onlySelect) {
BlockVector3 clipboardOffset = clipboard.getRegion().getMinimumPoint().subtract(clipboard.getOrigin()); BlockVector3 clipboardOffset = clipboard.getRegion().getMinimumPoint().subtract(clipboard.getOrigin());
BlockVector3 realTo = to.add(holder.getTransform().apply(clipboardOffset.toVector3()).toBlockPoint()); BlockVector3 realTo = to.add(holder.getTransform().apply(clipboardOffset.toVector3()).toBlockPoint());
BlockVector3 max = realTo.add(holder BlockVector3 max = realTo.add(holder
@ -475,7 +478,11 @@ public class ClipboardCommands {
selector.learnChanges(); selector.learnChanges();
selector.explainRegionAdjust(actor, session); selector.explainRegionAdjust(actor, session);
} }
actor.print(Caption.of("fawe.worldedit.paste.command.paste", to)); if (onlySelect) {
actor.print(Caption.of("worldedit.paste.selected"));
} else {
actor.print(Caption.of("fawe.worldedit.paste.command.paste", to));
}
if (!actor.hasPermission("fawe.tips")) { if (!actor.hasPermission("fawe.tips")) {
actor.print(Caption.of("fawe.tips.tip.copypaste")); actor.print(Caption.of("fawe.tips.tip.copypaste"));
@ -512,7 +519,7 @@ public class ClipboardCommands {
ClipboardHolder holder = session.getClipboard(); ClipboardHolder holder = session.getClipboard();
//FAWE start - use place //FAWE start - use place
if (holder.getTransform().isIdentity() && sourceMask == null) { if (holder.getTransform().isIdentity() && sourceMask == null) {
place(actor, world, session, editSession, ignoreAirBlocks, atOrigin, selectPasted, place(actor, world, session, editSession, ignoreAirBlocks, atOrigin, selectPasted, onlySelect,
pasteEntities, pasteBiomes pasteEntities, pasteBiomes
); );
return; return;

Datei anzeigen

@ -201,7 +201,7 @@ public class HistorySubCommands {
.at(summary.maxX, world.getMaxY(), summary.maxZ) .at(summary.maxX, world.getMaxY(), summary.maxZ)
); );
rollback.setTime(historyFile.lastModified()); rollback.setTime(historyFile.lastModified());
RollbackDatabase db = DBHandler.IMP RollbackDatabase db = DBHandler.dbHandler()
.getDatabase(world); .getDatabase(world);
db.logEdit(rollback); db.logEdit(rollback);
actor.print(TextComponent.of("Logging: " + historyFile)); actor.print(TextComponent.of("Logging: " + historyFile));

Datei anzeigen

@ -313,9 +313,9 @@ public class SchematicCommands {
Actor actor, LocalSession session, Actor actor, LocalSession session,
@Arg(desc = "File name.") @Arg(desc = "File name.")
String filename, String filename,
@Arg(desc = "Format name.", def = "fast")
String formatName,
//FAWE start - random rotation //FAWE start - random rotation
@Arg(desc = "Format name.", def = "")
String formatName,
@Switch(name = 'r', desc = "Apply random rotation to the clipboard") @Switch(name = 'r', desc = "Apply random rotation to the clipboard")
boolean randomRotate boolean randomRotate
//FAWE end //FAWE end
@ -325,6 +325,11 @@ public class SchematicCommands {
//FAWE start //FAWE start
ClipboardFormat format; ClipboardFormat format;
InputStream in = null; InputStream in = null;
// if format is set explicitly, do not look up by extension!
boolean noExplicitFormat = formatName == null;
if (noExplicitFormat) {
formatName = "fast";
}
try { try {
URI uri; URI uri;
if (formatName.startsWith("url:")) { if (formatName.startsWith("url:")) {
@ -369,7 +374,7 @@ public class SchematicCommands {
actor.print(Caption.of("fawe.error.no-perm", "worldedit.schematic.load.other")); actor.print(Caption.of("fawe.error.no-perm", "worldedit.schematic.load.other"));
return; return;
} }
if (filename.matches(".*\\.[\\w].*")) { if (noExplicitFormat && filename.matches(".*\\.[\\w].*")) {
format = ClipboardFormats format = ClipboardFormats
.findByExtension(filename.substring(filename.lastIndexOf('.') + 1)); .findByExtension(filename.substring(filename.lastIndexOf('.') + 1));
} else { } else {

Datei anzeigen

@ -26,7 +26,6 @@ import com.fastasyncworldedit.core.extent.clipboard.MultiClipboardHolder;
import com.fastasyncworldedit.core.extent.clipboard.URIClipboardHolder; import com.fastasyncworldedit.core.extent.clipboard.URIClipboardHolder;
import com.fastasyncworldedit.core.internal.io.FastByteArrayOutputStream; import com.fastasyncworldedit.core.internal.io.FastByteArrayOutputStream;
import com.fastasyncworldedit.core.util.MainUtil; import com.fastasyncworldedit.core.util.MainUtil;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps; import com.google.common.collect.Multimaps;
import com.google.common.io.ByteSource; import com.google.common.io.ByteSource;
@ -51,6 +50,7 @@ import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
@ -64,7 +64,9 @@ import static com.google.common.base.Preconditions.checkNotNull;
public class ClipboardFormats { public class ClipboardFormats {
private static final Map<String, ClipboardFormat> aliasMap = new HashMap<>(); private static final Map<String, ClipboardFormat> aliasMap = new HashMap<>();
private static final Multimap<String, ClipboardFormat> fileExtensionMap = HashMultimap.create(); // FAWE start - keep order of ClipboardFormat entries -> prefer FAST over SPONGE_SCHEMATIC
private static final Multimap<String, ClipboardFormat> fileExtensionMap = Multimaps.newMultimap(new HashMap<>(), LinkedHashSet::new);
// FAWE end
private static final List<ClipboardFormat> registeredFormats = new ArrayList<>(); private static final List<ClipboardFormat> registeredFormats = new ArrayList<>();
public static void registerClipboardFormat(ClipboardFormat format) { public static void registerClipboardFormat(ClipboardFormat format) {

Datei anzeigen

@ -164,14 +164,23 @@ public class RegionIntersection extends AbstractRegion {
int bz = chunk.getZ() << 4; int bz = chunk.getZ() << 4;
int tx = bx + 15; int tx = bx + 15;
int tz = bz + 15; int tz = bz + 15;
List<Region> intersecting = new ArrayList<>(2);
for (Region region : regions) { for (Region region : regions) {
BlockVector3 regMin = region.getMinimumPoint(); BlockVector3 regMin = region.getMinimumPoint();
BlockVector3 regMax = region.getMaximumPoint(); BlockVector3 regMax = region.getMaximumPoint();
if (tx >= regMin.getX() && bx <= regMax.getX() && tz >= regMin.getZ() && bz <= regMax.getZ()) { 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 @Override

Datei anzeigen

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

Datei anzeigen

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