fix: some improvements to GET chunk writing (#2853)

* fix: some improvements to GET chunk writing
 - ensure levelChunk is loaded before giving to copy GET - this is not necessarily guaranteed to be nonnull if two edits overlap. Whilst not advised, such an easy failure should not occur when two edits collide

* Prevent writing chunk sections when FAWE is also sending packets for a chunk and vice versa
- alter IntPair hashcode to be more often unique
- Utilise ConcurrentHashMap for free synchronisation

* Minor comment changes

* Use one-per-world-instance FaweBukkitWorld to store world chunk map
This commit is contained in:
Jordan
2024-09-25 18:20:49 +01:00
committed by GitHub
parent 5ac60f0d2e
commit b4635e85c9
16 changed files with 425 additions and 166 deletions

View File

@ -7,6 +7,7 @@ import com.fastasyncworldedit.core.FaweCache;
import com.fastasyncworldedit.core.configuration.Settings;
import com.fastasyncworldedit.core.extent.processor.heightmap.HeightMapType;
import com.fastasyncworldedit.core.math.BitArrayUnstretched;
import com.fastasyncworldedit.core.math.IntPair;
import com.fastasyncworldedit.core.nbt.FaweCompoundTag;
import com.fastasyncworldedit.core.queue.IChunkGet;
import com.fastasyncworldedit.core.queue.IChunkSet;
@ -106,6 +107,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
private final ServerLevel serverLevel;
private final int chunkX;
private final int chunkZ;
private final IntPair chunkPos;
private final int minHeight;
private final int maxHeight;
private final int minSectionPosition;
@ -140,6 +142,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
this.blockLight = new DataLayer[getSectionCount()];
this.biomeRegistry = serverLevel.registryAccess().registryOrThrow(BIOME);
this.biomeHolderIdMap = biomeRegistry.asHolderIdMap();
this.chunkPos = new IntPair(chunkX, chunkZ);
}
public int getChunkX() {
@ -425,7 +428,8 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
throw new IllegalStateException("Attempted to call chunk GET but chunk was not call-locked.");
}
forceLoadSections = false;
PaperweightGetBlocks_Copy copy = createCopy ? new PaperweightGetBlocks_Copy(levelChunk) : null;
LevelChunk nmsChunk = ensureLoaded(serverLevel, chunkX, chunkZ);
PaperweightGetBlocks_Copy copy = createCopy ? new PaperweightGetBlocks_Copy(nmsChunk) : null;
if (createCopy) {
if (copies.containsKey(copyKey)) {
throw new IllegalStateException("Copy key already used.");
@ -433,9 +437,6 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
copies.put(copyKey, copy);
}
try {
ServerLevel nmsWorld = serverLevel;
LevelChunk nmsChunk = ensureLoaded(nmsWorld, chunkX, chunkZ);
// Remove existing tiles. Create a copy so that we can remove blocks
Map<BlockPos, BlockEntity> chunkTiles = new HashMap<>(nmsChunk.getBlockEntities());
List<BlockEntity> beacons = null;
@ -507,6 +508,8 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
biomeData
);
if (PaperweightPlatformAdapter.setSectionAtomic(
serverLevel.getWorld().getName(),
chunkPos,
levelChunkSections,
null,
newSection,
@ -584,6 +587,8 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
biomeData
);
if (PaperweightPlatformAdapter.setSectionAtomic(
serverLevel.getWorld().getName(),
chunkPos,
levelChunkSections,
null,
newSection,
@ -649,6 +654,8 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
biomeData != null ? biomeData : (PalettedContainer<Holder<Biome>>) existingSection.getBiomes()
);
if (!PaperweightPlatformAdapter.setSectionAtomic(
serverLevel.getWorld().getName(),
chunkPos,
levelChunkSections,
existingSection,
newSection,
@ -722,7 +729,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
}
if (Settings.settings().EXPERIMENTAL.REMOVE_ENTITY_FROM_WORLD_ON_CHUNK_FAIL) {
for (UUID uuid : entityRemoves) {
Entity entity = nmsWorld.getEntities().get(uuid);
Entity entity = serverLevel.getEntities().get(uuid);
if (entity != null) {
removeEntity(entity);
}
@ -761,7 +768,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
EntityType<?> type = EntityType.byString(id).orElse(null);
if (type != null) {
Entity entity = type.create(nmsWorld);
Entity entity = type.create(serverLevel);
if (entity != null) {
final CompoundTag tag = (CompoundTag) adapter.fromNativeLin(linTag);
for (final String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) {
@ -770,11 +777,11 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
entity.load(tag);
entity.absMoveTo(x, y, z, yaw, pitch);
entity.setUUID(NbtUtils.uuid(nativeTag));
if (!nmsWorld.addFreshEntity(entity, CreatureSpawnEvent.SpawnReason.CUSTOM)) {
if (!serverLevel.addFreshEntity(entity, CreatureSpawnEvent.SpawnReason.CUSTOM)) {
LOGGER.warn(
"Error creating entity of type `{}` in world `{}` at location `{},{},{}`",
id,
nmsWorld.getWorld().getName(),
serverLevel.getWorld().getName(),
x,
y,
z
@ -804,11 +811,11 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
final int z = blockHash.z() + bz;
final BlockPos pos = new BlockPos(x, y, z);
synchronized (nmsWorld) {
BlockEntity tileEntity = nmsWorld.getBlockEntity(pos);
synchronized (serverLevel) {
BlockEntity tileEntity = serverLevel.getBlockEntity(pos);
if (tileEntity == null || tileEntity.isRemoved()) {
nmsWorld.removeBlockEntity(pos);
tileEntity = nmsWorld.getBlockEntity(pos);
serverLevel.removeBlockEntity(pos);
tileEntity = serverLevel.getBlockEntity(pos);
}
if (tileEntity != null) {
final CompoundTag tag = (CompoundTag) adapter.fromNativeLin(nativeTag.linTag());
@ -827,7 +834,6 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
callback = null;
} else {
int finalMask = bitMask != 0 ? bitMask : lightUpdate ? set.getBitMask() : 0;
boolean finalLightUpdate = lightUpdate;
callback = () -> {
// Set Modified
nmsChunk.setLightCorrect(true); // Set Modified
@ -933,7 +939,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
@Override
public void send() {
synchronized (sendLock) {
PaperweightPlatformAdapter.sendChunk(this, serverLevel, chunkX, chunkZ);
PaperweightPlatformAdapter.sendChunk(new IntPair(chunkX, chunkZ), serverLevel, chunkX, chunkZ);
}
}

View File

@ -7,8 +7,8 @@ import com.fastasyncworldedit.bukkit.adapter.NMSAdapter;
import com.fastasyncworldedit.core.Fawe;
import com.fastasyncworldedit.core.FaweCache;
import com.fastasyncworldedit.core.math.BitArrayUnstretched;
import com.fastasyncworldedit.core.math.IntPair;
import com.fastasyncworldedit.core.util.MathMan;
import com.fastasyncworldedit.core.util.ReflectionUtils;
import com.fastasyncworldedit.core.util.TaskManager;
import com.mojang.datafixers.util.Either;
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
@ -243,15 +243,14 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
}
static boolean setSectionAtomic(
String worldName,
IntPair pair,
LevelChunkSection[] sections,
LevelChunkSection expected,
LevelChunkSection value,
int layer
) {
if (layer >= 0 && layer < sections.length) {
return ReflectionUtils.compareAndSet(sections, expected, value, layer);
}
return false;
return NMSAdapter.setSectionAtomic(worldName, pair, sections, expected, value, layer);
}
// There is no point in having a functional semaphore for paper servers.
@ -349,7 +348,7 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
}
@SuppressWarnings("deprecation")
public static void sendChunk(Object chunk, ServerLevel nmsWorld, int chunkX, int chunkZ) {
public static void sendChunk(IntPair pair, ServerLevel nmsWorld, int chunkX, int chunkZ) {
ChunkHolder chunkHolder = getPlayerChunk(nmsWorld, chunkX, chunkZ);
if (chunkHolder == null) {
return;
@ -370,26 +369,35 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
if (levelChunk == null) {
return;
}
StampLockHolder lockHolder = new StampLockHolder();
NMSAdapter.beginChunkPacketSend(nmsWorld.getWorld().getName(), pair, lockHolder);
if (lockHolder.chunkLock == null) {
return;
}
MinecraftServer.getServer().execute(() -> {
ClientboundLevelChunkWithLightPacket packet;
if (PaperLib.isPaper()) {
packet = new ClientboundLevelChunkWithLightPacket(
levelChunk,
nmsWorld.getChunkSource().getLightEngine(),
null,
null,
false // last false is to not bother with x-ray
);
} else {
// deprecated on paper - deprecation suppressed
packet = new ClientboundLevelChunkWithLightPacket(
levelChunk,
nmsWorld.getChunkSource().getLightEngine(),
null,
null
);
try {
ClientboundLevelChunkWithLightPacket packet;
if (PaperLib.isPaper()) {
packet = new ClientboundLevelChunkWithLightPacket(
levelChunk,
nmsWorld.getChunkSource().getLightEngine(),
null,
null,
false // last false is to not bother with x-ray
);
} else {
// deprecated on paper - deprecation suppressed
packet = new ClientboundLevelChunkWithLightPacket(
levelChunk,
nmsWorld.getChunkSource().getLightEngine(),
null,
null
);
}
nearbyPlayers(nmsWorld, coordIntPair).forEach(p -> p.connection.send(packet));
} finally {
NMSAdapter.endChunkPacketSend(nmsWorld.getWorld().getName(), pair, lockHolder);
}
nearbyPlayers(nmsWorld, coordIntPair).forEach(p -> p.connection.send(packet));
});
}

View File

@ -2,6 +2,7 @@ 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.math.IntPair;
import com.fastasyncworldedit.core.queue.IQueueExtent;
import net.minecraft.server.level.ChunkMap;
import net.minecraft.server.level.ServerLevel;
@ -67,7 +68,7 @@ public class PaperweightStarlightRelighter extends StarlightRelighter<ServerLeve
int x = pos.x;
int z = pos.z;
if (delay) { // we still need to send the block changes of that chunk
PaperweightPlatformAdapter.sendChunk(pos, serverLevel, x, z);
PaperweightPlatformAdapter.sendChunk(new IntPair(x, z), serverLevel, x, z);
}
serverLevel.getChunkSource().removeTicketAtLevel(FAWE_TICKET, pos, LIGHT_LEVEL, Unit.INSTANCE);
}