From b4635e85c9ab3c300644f28b7a217d89d96a831b Mon Sep 17 00:00:00 2001 From: Jordan Date: Wed, 25 Sep 2024 18:20:49 +0100 Subject: [PATCH] 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 --- .../fawe/v1_20_R2/PaperweightGetBlocks.java | 34 ++--- .../v1_20_R2/PaperweightPlatformAdapter.java | 56 +++++---- .../PaperweightStarlightRelighter.java | 3 +- .../fawe/v1_20_R3/PaperweightGetBlocks.java | 34 ++--- .../v1_20_R3/PaperweightPlatformAdapter.java | 56 +++++---- .../PaperweightStarlightRelighter.java | 3 +- .../fawe/v1_20_R4/PaperweightGetBlocks.java | 38 +++--- .../v1_20_R4/PaperweightPlatformAdapter.java | 64 ++++++---- .../PaperweightStarlightRelighter.java | 3 +- .../fawe/v1_21_R1/PaperweightGetBlocks.java | 38 +++--- .../v1_21_R1/PaperweightPlatformAdapter.java | 65 ++++++---- .../PaperweightStarlightRelighter.java | 3 +- .../bukkit/FaweBukkitWorld.java | 67 ++++++++++ .../bukkit/adapter/NMSAdapter.java | 119 ++++++++++++++++++ .../sk89q/worldedit/bukkit/BukkitWorld.java | 4 +- .../fastasyncworldedit/core/math/IntPair.java | 4 +- 16 files changed, 425 insertions(+), 166 deletions(-) create mode 100644 worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/FaweBukkitWorld.java diff --git a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightGetBlocks.java b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightGetBlocks.java index 4bcdee27d..a325bb08b 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightGetBlocks.java +++ b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightGetBlocks.java @@ -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 chunkTiles = new HashMap<>(nmsChunk.getBlockEntities()); List 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>) 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); } } diff --git a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightPlatformAdapter.java b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightPlatformAdapter.java index a56548c46..d7fab2dd6 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightPlatformAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightPlatformAdapter.java @@ -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)); }); } diff --git a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightStarlightRelighter.java b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightStarlightRelighter.java index addf03867..b92835a82 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightStarlightRelighter.java +++ b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightStarlightRelighter.java @@ -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 chunkTiles = new HashMap<>(nmsChunk.getBlockEntities()); List 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>) 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 net.minecraft.nbt.CompoundTag tag = (net.minecraft.nbt.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 net.minecraft.nbt.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 @@ -932,7 +938,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc @Override public void send() { - PaperweightPlatformAdapter.sendChunk(this, serverLevel, chunkX, chunkZ); + PaperweightPlatformAdapter.sendChunk(new IntPair(chunkX, chunkZ), serverLevel, chunkX, chunkZ); } /** diff --git a/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightPlatformAdapter.java b/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightPlatformAdapter.java index 76cc25e02..2c05d7466 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightPlatformAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightPlatformAdapter.java @@ -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)); }); } diff --git a/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightStarlightRelighter.java b/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightStarlightRelighter.java index d9109b4df..a1ce327f9 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightStarlightRelighter.java +++ b/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightStarlightRelighter.java @@ -2,6 +2,7 @@ package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R3; 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 chunkTiles = new HashMap<>(nmsChunk.getBlockEntities()); List beacons = null; @@ -508,6 +509,8 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc biomeData ); if (PaperweightPlatformAdapter.setSectionAtomic( + serverLevel.getWorld().getName(), + chunkPos, levelChunkSections, null, newSection, @@ -585,6 +588,8 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc biomeData ); if (PaperweightPlatformAdapter.setSectionAtomic( + serverLevel.getWorld().getName(), + chunkPos, levelChunkSections, null, newSection, @@ -649,7 +654,11 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc biomeRegistry, biomeData != null ? biomeData : (PalettedContainer>) existingSection.getBiomes() ); - if (!PaperweightPlatformAdapter.setSectionAtomic(levelChunkSections, existingSection, + if (!PaperweightPlatformAdapter.setSectionAtomic( + serverLevel.getWorld().getName(), + chunkPos, + levelChunkSections, + existingSection, newSection, getSectionIndex )) { @@ -721,7 +730,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); } @@ -760,7 +769,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 net.minecraft.nbt.CompoundTag tag = (net.minecraft.nbt.CompoundTag) adapter.fromNativeLin(linTag); for (final String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) { @@ -769,11 +778,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 @@ -803,11 +812,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 net.minecraft.nbt.CompoundTag tag = (CompoundTag) adapter.fromNativeLin(nativeTag.linTag()); @@ -826,7 +835,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 @@ -932,7 +940,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); } } diff --git a/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightPlatformAdapter.java b/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightPlatformAdapter.java index df71dae8c..838a5db53 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightPlatformAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightPlatformAdapter.java @@ -7,9 +7,10 @@ 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; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; import com.sk89q.worldedit.bukkit.adapter.Refraction; @@ -76,6 +77,7 @@ import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; @@ -241,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. @@ -347,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; @@ -360,32 +361,43 @@ public final class PaperweightPlatformAdapter extends NMSAdapter { .getChunkSource() .getChunkAtIfLoadedImmediately(chunkX, chunkZ); } else { - levelChunk = chunkHolder.getTickingChunkFuture() - .getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK).orElse(null); + levelChunk = ((Optional) ((Either) chunkHolder + .getTickingChunkFuture() // method is not present with new paper chunk system + .getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK)).left()) + .orElse(null); } 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)); }); } diff --git a/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightStarlightRelighter.java b/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightStarlightRelighter.java index ae09dcc58..c7b61575c 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightStarlightRelighter.java +++ b/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightStarlightRelighter.java @@ -2,6 +2,7 @@ package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R4; 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 chunkTiles = new HashMap<>(nmsChunk.getBlockEntities()); List beacons = null; @@ -509,6 +510,8 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc biomeData ); if (PaperweightPlatformAdapter.setSectionAtomic( + serverLevel.getWorld().getName(), + chunkPos, levelChunkSections, null, newSection, @@ -583,6 +586,8 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc biomeData ); if (PaperweightPlatformAdapter.setSectionAtomic( + serverLevel.getWorld().getName(), + chunkPos, levelChunkSections, null, newSection, @@ -644,7 +649,11 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc biomeRegistry, biomeData != null ? biomeData : (PalettedContainer>) existingSection.getBiomes() ); - if (!PaperweightPlatformAdapter.setSectionAtomic(levelChunkSections, existingSection, + if (!PaperweightPlatformAdapter.setSectionAtomic( + serverLevel.getWorld().getName(), + chunkPos, + levelChunkSections, + existingSection, newSection, getSectionIndex )) { @@ -716,7 +725,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); } @@ -755,7 +764,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) { @@ -764,11 +773,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 @@ -798,11 +807,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()); @@ -821,7 +830,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 @@ -927,7 +935,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); } } diff --git a/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightPlatformAdapter.java b/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightPlatformAdapter.java index b4ed7c8ae..410152251 100644 --- a/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightPlatformAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightPlatformAdapter.java @@ -8,9 +8,10 @@ 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; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; import com.sk89q.worldedit.bukkit.adapter.Refraction; @@ -31,7 +32,6 @@ import net.minecraft.server.level.ChunkMap; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.util.BitStorage; -import net.minecraft.util.ExceptionCollector; import net.minecraft.util.SimpleBitStorage; import net.minecraft.util.ThreadingDetector; import net.minecraft.util.Unit; @@ -76,6 +76,7 @@ import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; @@ -226,15 +227,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. @@ -332,7 +332,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; @@ -345,32 +345,43 @@ public final class PaperweightPlatformAdapter extends NMSAdapter { .getChunkSource() .getChunkAtIfLoadedImmediately(chunkX, chunkZ); } else { - levelChunk = chunkHolder.getTickingChunkFuture() - .getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK).orElse(null); + levelChunk = ((Optional) ((Either) chunkHolder + .getTickingChunkFuture() // method is not present with new paper chunk system + .getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK)).left()) + .orElse(null); } 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)); }); } diff --git a/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightStarlightRelighter.java b/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightStarlightRelighter.java index f9d06922e..aa7b19b39 100644 --- a/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightStarlightRelighter.java +++ b/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightStarlightRelighter.java @@ -2,6 +2,7 @@ package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_21_R1; 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; @@ -70,7 +71,7 @@ public class PaperweightStarlightRelighter extends StarlightRelighter CACHE = Collections.synchronizedMap(new WeakHashMap<>()); + + private final ConcurrentHashMap SENDING_CHUNKS = new ConcurrentHashMap<>(); + + /** + * Construct the object. + * + * @param world the world + */ + private FaweBukkitWorld(final World world) { + super(world); + } + + public static FaweBukkitWorld of(World world) { + return CACHE.compute(world, (__, val) -> { + if (val == null) { + return new FaweBukkitWorld(world); + } + val.updateReference(); + return val; + }); + } + + public static FaweBukkitWorld of(String worldName) { + World world = Bukkit.getWorld(worldName); + if (world == null) { + throw new UnsupportedOperationException("Unable to find org.bukkit.World instance for " + worldName + ". Is it loaded?"); + } + return of(world); + } + + public static ConcurrentHashMap getWorldSendingChunksMap(FaweBukkitWorld world) { + return world.SENDING_CHUNKS; + } + + public static ConcurrentHashMap getWorldSendingChunksMap(String worldName) { + return of(worldName).SENDING_CHUNKS; + } + + private void updateReference() { + World world = getWorld(); + World bukkitWorld = Bukkit.getWorld(worldNameRef); + if (bukkitWorld == null) { + throw new WorldUnloadedException(worldNameRef); + } else if (bukkitWorld != world) { + worldRef = new WeakReference<>(bukkitWorld); + } + } + +} diff --git a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/NMSAdapter.java b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/NMSAdapter.java index 711c0fecc..4fc00996a 100644 --- a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/NMSAdapter.java +++ b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/NMSAdapter.java @@ -1,12 +1,17 @@ package com.fastasyncworldedit.bukkit.adapter; +import com.fastasyncworldedit.bukkit.FaweBukkitWorld; import com.fastasyncworldedit.core.FAWEPlatformAdapterImpl; +import com.fastasyncworldedit.core.math.IntPair; import com.fastasyncworldedit.core.queue.IChunkGet; import com.fastasyncworldedit.core.util.MathMan; +import com.fastasyncworldedit.core.util.ReflectionUtils; import com.sk89q.worldedit.world.block.BlockTypesCache; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.StampedLock; import java.util.function.Function; public class NMSAdapter implements FAWEPlatformAdapterImpl { @@ -140,4 +145,118 @@ public class NMSAdapter implements FAWEPlatformAdapterImpl { ((BukkitGetBlocks) chunk).send(); } + /** + * Atomically set the given chunk section to the chunk section array stored in the chunk, given the expected existing chunk + * section instance at the given layer position. + *

+ * Acquires a (FAWE-implemented only) write-lock on the chunk packet lock, waiting if required before writing, then freeing + * the lock. Also sets a boolean to indicate a write is waiting and therefore reads should not occur. + *

+ * Utilises ConcurrentHashMap#compute for easy synchronisation for all of the above. Only tryWriteLock is used in blocks + * synchronised using ConcurrentHashMap methods. + * + * @since TODO + */ + protected static boolean setSectionAtomic( + String worldName, + IntPair pair, + LevelChunkSection[] sections, + LevelChunkSection expected, + LevelChunkSection value, + int layer + ) { + if (layer < 0 || layer >= sections.length) { + return false; + } + StampLockHolder holder = new StampLockHolder(); + ConcurrentHashMap chunks = FaweBukkitWorld.getWorldSendingChunksMap(worldName); + chunks.compute(pair, (k, lock) -> { + if (lock == null) { + lock = new ChunkSendLock(); + } else if (lock.writeWaiting) { + throw new IllegalStateException("Attempting to write chunk section when write is already ongoing?!"); + } + holder.stamp = lock.lock.tryWriteLock(); + holder.chunkLock = lock; + lock.writeWaiting = true; + return lock; + }); + try { + if (holder.stamp == 0) { + holder.stamp = holder.chunkLock.lock.writeLock(); + } + return ReflectionUtils.compareAndSet(sections, expected, value, layer); + } finally { + chunks = FaweBukkitWorld.getWorldSendingChunksMap(worldName); + chunks.computeIfPresent(pair, (k, lock) -> { + if (lock != holder.chunkLock) { + throw new IllegalStateException("SENDING_CHUNKS stored lock does not equal lock attempted to be unlocked?!"); + } + lock.lock.unlockWrite(holder.stamp); + lock.writeWaiting = false; + // Keep the lock, etc. in the map as we're going to be accessing again later when sending + return lock; + }); + } + } + + /** + * Called before sending a chunk packet, filling the given stamp and stampedLock arrays' zeroth indices if the chunk packet + * send should go ahead. + *

+ * Chunk packets should be sent if both of the following are met: + * - There is no more than one current packet send ongoing + * - There is no chunk section "write" waiting or ongoing, + * which are determined by the number of readers currently locking the StampedLock (i.e. the number of sends), if the + * stamped lock is currently write-locked and if the boolean for waiting write is true. + *

+ * Utilises ConcurrentHashMap#compute for easy synchronisation + * + * @since TODO + */ + protected static void beginChunkPacketSend(String worldName, IntPair pair, StampLockHolder stampedLock) { + ConcurrentHashMap chunks = FaweBukkitWorld.getWorldSendingChunksMap(worldName); + chunks.compute(pair, (k, lock) -> { + if (lock == null) { + lock = new ChunkSendLock(); + } + // Allow twice-read-locking, so if the packets have been created but not sent, we can queue another read + if (lock.writeWaiting || lock.lock.getReadLockCount() > 1 || lock.lock.isWriteLocked()) { + return lock; + } + stampedLock.stamp = lock.lock.readLock(); + stampedLock.chunkLock = lock; + return lock; + }); + } + + /** + * Releases the read lock acquired when sending a chunk packet for a chunk + * + * @since TODO + */ + protected static void endChunkPacketSend(String worldName, IntPair pair, StampLockHolder lockHolder) { + ConcurrentHashMap chunks = FaweBukkitWorld.getWorldSendingChunksMap(worldName); + chunks.computeIfPresent(pair, (k, lock) -> { + if (lock.lock != lockHolder.chunkLock.lock) { + throw new IllegalStateException("SENDING_CHUNKS stored lock does not equal lock attempted to be unlocked?!"); + } + lock.lock.unlockRead(lockHolder.stamp); + // Do not continue to store the lock if we may not need it (i.e. chunk has been sent, may not be sent again) + return null; + }); + } + + public static final class StampLockHolder { + public long stamp; + public ChunkSendLock chunkLock = null; + } + + public static final class ChunkSendLock { + + public final StampedLock lock = new StampedLock(); + public boolean writeWaiting = false; + + } + } diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java index 55912d770..9e4da5401 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java @@ -118,9 +118,9 @@ public class BukkitWorld extends AbstractWorld { HAS_MIN_Y = temp; } - private WeakReference worldRef; + protected WeakReference worldRef; //FAWE start - private final String worldNameRef; + protected final String worldNameRef; //FAWE end private final WorldNativeAccess worldNativeAccess; diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/math/IntPair.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/math/IntPair.java index f7f451e79..b5667ccef 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/math/IntPair.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/math/IntPair.java @@ -4,7 +4,9 @@ public record IntPair(int x, int z) { @Override public int hashCode() { - return (x << 16) | (z & 0xFFFF); + int i = 1664525 * x + 1013904223; + int j = 1664525 * (z ^ -559038737) + 1013904223; + return i ^ j; } @Override