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
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
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.configuration.Settings;
import com.fastasyncworldedit.core.extent.processor.heightmap.HeightMapType; import com.fastasyncworldedit.core.extent.processor.heightmap.HeightMapType;
import com.fastasyncworldedit.core.math.BitArrayUnstretched; import com.fastasyncworldedit.core.math.BitArrayUnstretched;
import com.fastasyncworldedit.core.math.IntPair;
import com.fastasyncworldedit.core.nbt.FaweCompoundTag; import com.fastasyncworldedit.core.nbt.FaweCompoundTag;
import com.fastasyncworldedit.core.queue.IChunkGet; import com.fastasyncworldedit.core.queue.IChunkGet;
import com.fastasyncworldedit.core.queue.IChunkSet; import com.fastasyncworldedit.core.queue.IChunkSet;
@ -106,6 +107,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
private final ServerLevel serverLevel; private final ServerLevel serverLevel;
private final int chunkX; private final int chunkX;
private final int chunkZ; private final int chunkZ;
private final IntPair chunkPos;
private final int minHeight; private final int minHeight;
private final int maxHeight; private final int maxHeight;
private final int minSectionPosition; private final int minSectionPosition;
@ -140,6 +142,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
this.blockLight = new DataLayer[getSectionCount()]; this.blockLight = new DataLayer[getSectionCount()];
this.biomeRegistry = serverLevel.registryAccess().registryOrThrow(BIOME); this.biomeRegistry = serverLevel.registryAccess().registryOrThrow(BIOME);
this.biomeHolderIdMap = biomeRegistry.asHolderIdMap(); this.biomeHolderIdMap = biomeRegistry.asHolderIdMap();
this.chunkPos = new IntPair(chunkX, chunkZ);
} }
public int getChunkX() { 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."); throw new IllegalStateException("Attempted to call chunk GET but chunk was not call-locked.");
} }
forceLoadSections = false; 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 (createCopy) {
if (copies.containsKey(copyKey)) { if (copies.containsKey(copyKey)) {
throw new IllegalStateException("Copy key already used."); throw new IllegalStateException("Copy key already used.");
@ -433,9 +437,6 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
copies.put(copyKey, copy); copies.put(copyKey, copy);
} }
try { try {
ServerLevel nmsWorld = serverLevel;
LevelChunk nmsChunk = ensureLoaded(nmsWorld, chunkX, chunkZ);
// Remove existing tiles. Create a copy so that we can remove blocks // Remove existing tiles. Create a copy so that we can remove blocks
Map<BlockPos, BlockEntity> chunkTiles = new HashMap<>(nmsChunk.getBlockEntities()); Map<BlockPos, BlockEntity> chunkTiles = new HashMap<>(nmsChunk.getBlockEntities());
List<BlockEntity> beacons = null; List<BlockEntity> beacons = null;
@ -507,6 +508,8 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
biomeData biomeData
); );
if (PaperweightPlatformAdapter.setSectionAtomic( if (PaperweightPlatformAdapter.setSectionAtomic(
serverLevel.getWorld().getName(),
chunkPos,
levelChunkSections, levelChunkSections,
null, null,
newSection, newSection,
@ -584,6 +587,8 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
biomeData biomeData
); );
if (PaperweightPlatformAdapter.setSectionAtomic( if (PaperweightPlatformAdapter.setSectionAtomic(
serverLevel.getWorld().getName(),
chunkPos,
levelChunkSections, levelChunkSections,
null, null,
newSection, newSection,
@ -649,6 +654,8 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
biomeData != null ? biomeData : (PalettedContainer<Holder<Biome>>) existingSection.getBiomes() biomeData != null ? biomeData : (PalettedContainer<Holder<Biome>>) existingSection.getBiomes()
); );
if (!PaperweightPlatformAdapter.setSectionAtomic( if (!PaperweightPlatformAdapter.setSectionAtomic(
serverLevel.getWorld().getName(),
chunkPos,
levelChunkSections, levelChunkSections,
existingSection, existingSection,
newSection, newSection,
@ -722,7 +729,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
} }
if (Settings.settings().EXPERIMENTAL.REMOVE_ENTITY_FROM_WORLD_ON_CHUNK_FAIL) { if (Settings.settings().EXPERIMENTAL.REMOVE_ENTITY_FROM_WORLD_ON_CHUNK_FAIL) {
for (UUID uuid : entityRemoves) { for (UUID uuid : entityRemoves) {
Entity entity = nmsWorld.getEntities().get(uuid); Entity entity = serverLevel.getEntities().get(uuid);
if (entity != null) { if (entity != null) {
removeEntity(entity); removeEntity(entity);
} }
@ -761,7 +768,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
EntityType<?> type = EntityType.byString(id).orElse(null); EntityType<?> type = EntityType.byString(id).orElse(null);
if (type != null) { if (type != null) {
Entity entity = type.create(nmsWorld); Entity entity = type.create(serverLevel);
if (entity != null) { if (entity != null) {
final CompoundTag tag = (CompoundTag) adapter.fromNativeLin(linTag); final CompoundTag tag = (CompoundTag) adapter.fromNativeLin(linTag);
for (final String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) { 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.load(tag);
entity.absMoveTo(x, y, z, yaw, pitch); entity.absMoveTo(x, y, z, yaw, pitch);
entity.setUUID(NbtUtils.uuid(nativeTag)); entity.setUUID(NbtUtils.uuid(nativeTag));
if (!nmsWorld.addFreshEntity(entity, CreatureSpawnEvent.SpawnReason.CUSTOM)) { if (!serverLevel.addFreshEntity(entity, CreatureSpawnEvent.SpawnReason.CUSTOM)) {
LOGGER.warn( LOGGER.warn(
"Error creating entity of type `{}` in world `{}` at location `{},{},{}`", "Error creating entity of type `{}` in world `{}` at location `{},{},{}`",
id, id,
nmsWorld.getWorld().getName(), serverLevel.getWorld().getName(),
x, x,
y, y,
z z
@ -804,11 +811,11 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
final int z = blockHash.z() + bz; final int z = blockHash.z() + bz;
final BlockPos pos = new BlockPos(x, y, z); final BlockPos pos = new BlockPos(x, y, z);
synchronized (nmsWorld) { synchronized (serverLevel) {
BlockEntity tileEntity = nmsWorld.getBlockEntity(pos); BlockEntity tileEntity = serverLevel.getBlockEntity(pos);
if (tileEntity == null || tileEntity.isRemoved()) { if (tileEntity == null || tileEntity.isRemoved()) {
nmsWorld.removeBlockEntity(pos); serverLevel.removeBlockEntity(pos);
tileEntity = nmsWorld.getBlockEntity(pos); tileEntity = serverLevel.getBlockEntity(pos);
} }
if (tileEntity != null) { if (tileEntity != null) {
final CompoundTag tag = (CompoundTag) adapter.fromNativeLin(nativeTag.linTag()); final CompoundTag tag = (CompoundTag) adapter.fromNativeLin(nativeTag.linTag());
@ -827,7 +834,6 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
callback = null; callback = null;
} else { } else {
int finalMask = bitMask != 0 ? bitMask : lightUpdate ? set.getBitMask() : 0; int finalMask = bitMask != 0 ? bitMask : lightUpdate ? set.getBitMask() : 0;
boolean finalLightUpdate = lightUpdate;
callback = () -> { callback = () -> {
// Set Modified // Set Modified
nmsChunk.setLightCorrect(true); // Set Modified nmsChunk.setLightCorrect(true); // Set Modified
@ -933,7 +939,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
@Override @Override
public void send() { public void send() {
synchronized (sendLock) { 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.Fawe;
import com.fastasyncworldedit.core.FaweCache; import com.fastasyncworldedit.core.FaweCache;
import com.fastasyncworldedit.core.math.BitArrayUnstretched; import com.fastasyncworldedit.core.math.BitArrayUnstretched;
import com.fastasyncworldedit.core.math.IntPair;
import com.fastasyncworldedit.core.util.MathMan; import com.fastasyncworldedit.core.util.MathMan;
import com.fastasyncworldedit.core.util.ReflectionUtils;
import com.fastasyncworldedit.core.util.TaskManager; import com.fastasyncworldedit.core.util.TaskManager;
import com.mojang.datafixers.util.Either; import com.mojang.datafixers.util.Either;
import com.sk89q.worldedit.bukkit.WorldEditPlugin; import com.sk89q.worldedit.bukkit.WorldEditPlugin;
@ -243,15 +243,14 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
} }
static boolean setSectionAtomic( static boolean setSectionAtomic(
String worldName,
IntPair pair,
LevelChunkSection[] sections, LevelChunkSection[] sections,
LevelChunkSection expected, LevelChunkSection expected,
LevelChunkSection value, LevelChunkSection value,
int layer int layer
) { ) {
if (layer >= 0 && layer < sections.length) { return NMSAdapter.setSectionAtomic(worldName, pair, sections, expected, value, layer);
return ReflectionUtils.compareAndSet(sections, expected, value, layer);
}
return false;
} }
// There is no point in having a functional semaphore for paper servers. // There is no point in having a functional semaphore for paper servers.
@ -349,7 +348,7 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
} }
@SuppressWarnings("deprecation") @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); ChunkHolder chunkHolder = getPlayerChunk(nmsWorld, chunkX, chunkZ);
if (chunkHolder == null) { if (chunkHolder == null) {
return; return;
@ -370,7 +369,13 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
if (levelChunk == null) { if (levelChunk == null) {
return; return;
} }
StampLockHolder lockHolder = new StampLockHolder();
NMSAdapter.beginChunkPacketSend(nmsWorld.getWorld().getName(), pair, lockHolder);
if (lockHolder.chunkLock == null) {
return;
}
MinecraftServer.getServer().execute(() -> { MinecraftServer.getServer().execute(() -> {
try {
ClientboundLevelChunkWithLightPacket packet; ClientboundLevelChunkWithLightPacket packet;
if (PaperLib.isPaper()) { if (PaperLib.isPaper()) {
packet = new ClientboundLevelChunkWithLightPacket( packet = new ClientboundLevelChunkWithLightPacket(
@ -390,6 +395,9 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
); );
} }
nearbyPlayers(nmsWorld, coordIntPair).forEach(p -> p.connection.send(packet)); nearbyPlayers(nmsWorld, coordIntPair).forEach(p -> p.connection.send(packet));
} finally {
NMSAdapter.endChunkPacketSend(nmsWorld.getWorld().getName(), pair, lockHolder);
}
}); });
} }

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

View File

@ -7,6 +7,7 @@ import com.fastasyncworldedit.core.FaweCache;
import com.fastasyncworldedit.core.configuration.Settings; import com.fastasyncworldedit.core.configuration.Settings;
import com.fastasyncworldedit.core.extent.processor.heightmap.HeightMapType; import com.fastasyncworldedit.core.extent.processor.heightmap.HeightMapType;
import com.fastasyncworldedit.core.math.BitArrayUnstretched; import com.fastasyncworldedit.core.math.BitArrayUnstretched;
import com.fastasyncworldedit.core.math.IntPair;
import com.fastasyncworldedit.core.nbt.FaweCompoundTag; import com.fastasyncworldedit.core.nbt.FaweCompoundTag;
import com.fastasyncworldedit.core.queue.IChunkGet; import com.fastasyncworldedit.core.queue.IChunkGet;
import com.fastasyncworldedit.core.queue.IChunkSet; import com.fastasyncworldedit.core.queue.IChunkSet;
@ -106,6 +107,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
private final ServerLevel serverLevel; private final ServerLevel serverLevel;
private final int chunkX; private final int chunkX;
private final int chunkZ; private final int chunkZ;
private final IntPair chunkPos;
private final int minHeight; private final int minHeight;
private final int maxHeight; private final int maxHeight;
private final int minSectionPosition; private final int minSectionPosition;
@ -140,6 +142,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
this.blockLight = new DataLayer[getSectionCount()]; this.blockLight = new DataLayer[getSectionCount()];
this.biomeRegistry = serverLevel.registryAccess().registryOrThrow(BIOME); this.biomeRegistry = serverLevel.registryAccess().registryOrThrow(BIOME);
this.biomeHolderIdMap = biomeRegistry.asHolderIdMap(); this.biomeHolderIdMap = biomeRegistry.asHolderIdMap();
this.chunkPos = new IntPair(chunkX, chunkZ);
} }
public int getChunkX() { 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."); throw new IllegalStateException("Attempted to call chunk GET but chunk was not call-locked.");
} }
forceLoadSections = false; 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 (createCopy) {
if (copies.containsKey(copyKey)) { if (copies.containsKey(copyKey)) {
throw new IllegalStateException("Copy key already used."); throw new IllegalStateException("Copy key already used.");
@ -433,9 +437,6 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
copies.put(copyKey, copy); copies.put(copyKey, copy);
} }
try { try {
ServerLevel nmsWorld = serverLevel;
LevelChunk nmsChunk = ensureLoaded(nmsWorld, chunkX, chunkZ);
// Remove existing tiles. Create a copy so that we can remove blocks // Remove existing tiles. Create a copy so that we can remove blocks
Map<BlockPos, BlockEntity> chunkTiles = new HashMap<>(nmsChunk.getBlockEntities()); Map<BlockPos, BlockEntity> chunkTiles = new HashMap<>(nmsChunk.getBlockEntities());
List<BlockEntity> beacons = null; List<BlockEntity> beacons = null;
@ -507,6 +508,8 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
biomeData biomeData
); );
if (PaperweightPlatformAdapter.setSectionAtomic( if (PaperweightPlatformAdapter.setSectionAtomic(
serverLevel.getWorld().getName(),
chunkPos,
levelChunkSections, levelChunkSections,
null, null,
newSection, newSection,
@ -584,6 +587,8 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
biomeData biomeData
); );
if (PaperweightPlatformAdapter.setSectionAtomic( if (PaperweightPlatformAdapter.setSectionAtomic(
serverLevel.getWorld().getName(),
chunkPos,
levelChunkSections, levelChunkSections,
null, null,
newSection, newSection,
@ -649,6 +654,8 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
biomeData != null ? biomeData : (PalettedContainer<Holder<Biome>>) existingSection.getBiomes() biomeData != null ? biomeData : (PalettedContainer<Holder<Biome>>) existingSection.getBiomes()
); );
if (!PaperweightPlatformAdapter.setSectionAtomic( if (!PaperweightPlatformAdapter.setSectionAtomic(
serverLevel.getWorld().getName(),
chunkPos,
levelChunkSections, levelChunkSections,
existingSection, existingSection,
newSection, newSection,
@ -722,7 +729,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
} }
if (Settings.settings().EXPERIMENTAL.REMOVE_ENTITY_FROM_WORLD_ON_CHUNK_FAIL) { if (Settings.settings().EXPERIMENTAL.REMOVE_ENTITY_FROM_WORLD_ON_CHUNK_FAIL) {
for (UUID uuid : entityRemoves) { for (UUID uuid : entityRemoves) {
Entity entity = nmsWorld.getEntities().get(uuid); Entity entity = serverLevel.getEntities().get(uuid);
if (entity != null) { if (entity != null) {
removeEntity(entity); removeEntity(entity);
} }
@ -761,7 +768,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
EntityType<?> type = EntityType.byString(id).orElse(null); EntityType<?> type = EntityType.byString(id).orElse(null);
if (type != null) { if (type != null) {
Entity entity = type.create(nmsWorld); Entity entity = type.create(serverLevel);
if (entity != null) { if (entity != null) {
final net.minecraft.nbt.CompoundTag tag = (net.minecraft.nbt.CompoundTag) adapter.fromNativeLin(linTag); final net.minecraft.nbt.CompoundTag tag = (net.minecraft.nbt.CompoundTag) adapter.fromNativeLin(linTag);
for (final String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) { 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.load(tag);
entity.absMoveTo(x, y, z, yaw, pitch); entity.absMoveTo(x, y, z, yaw, pitch);
entity.setUUID(NbtUtils.uuid(nativeTag)); entity.setUUID(NbtUtils.uuid(nativeTag));
if (!nmsWorld.addFreshEntity(entity, CreatureSpawnEvent.SpawnReason.CUSTOM)) { if (!serverLevel.addFreshEntity(entity, CreatureSpawnEvent.SpawnReason.CUSTOM)) {
LOGGER.warn( LOGGER.warn(
"Error creating entity of type `{}` in world `{}` at location `{},{},{}`", "Error creating entity of type `{}` in world `{}` at location `{},{},{}`",
id, id,
nmsWorld.getWorld().getName(), serverLevel.getWorld().getName(),
x, x,
y, y,
z z
@ -804,11 +811,11 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
final int z = blockHash.z() + bz; final int z = blockHash.z() + bz;
final BlockPos pos = new BlockPos(x, y, z); final BlockPos pos = new BlockPos(x, y, z);
synchronized (nmsWorld) { synchronized (serverLevel) {
BlockEntity tileEntity = nmsWorld.getBlockEntity(pos); BlockEntity tileEntity = serverLevel.getBlockEntity(pos);
if (tileEntity == null || tileEntity.isRemoved()) { if (tileEntity == null || tileEntity.isRemoved()) {
nmsWorld.removeBlockEntity(pos); serverLevel.removeBlockEntity(pos);
tileEntity = nmsWorld.getBlockEntity(pos); tileEntity = serverLevel.getBlockEntity(pos);
} }
if (tileEntity != null) { if (tileEntity != null) {
final net.minecraft.nbt.CompoundTag tag = (CompoundTag) adapter.fromNativeLin(nativeTag.linTag()); final net.minecraft.nbt.CompoundTag tag = (CompoundTag) adapter.fromNativeLin(nativeTag.linTag());
@ -827,7 +834,6 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
callback = null; callback = null;
} else { } else {
int finalMask = bitMask != 0 ? bitMask : lightUpdate ? set.getBitMask() : 0; int finalMask = bitMask != 0 ? bitMask : lightUpdate ? set.getBitMask() : 0;
boolean finalLightUpdate = lightUpdate;
callback = () -> { callback = () -> {
// Set Modified // Set Modified
nmsChunk.setLightCorrect(true); // Set Modified nmsChunk.setLightCorrect(true); // Set Modified
@ -932,7 +938,7 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc
@Override @Override
public void send() { public void send() {
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.Fawe;
import com.fastasyncworldedit.core.FaweCache; import com.fastasyncworldedit.core.FaweCache;
import com.fastasyncworldedit.core.math.BitArrayUnstretched; import com.fastasyncworldedit.core.math.BitArrayUnstretched;
import com.fastasyncworldedit.core.math.IntPair;
import com.fastasyncworldedit.core.util.MathMan; import com.fastasyncworldedit.core.util.MathMan;
import com.fastasyncworldedit.core.util.ReflectionUtils;
import com.fastasyncworldedit.core.util.TaskManager; import com.fastasyncworldedit.core.util.TaskManager;
import com.mojang.datafixers.util.Either; import com.mojang.datafixers.util.Either;
import com.sk89q.worldedit.bukkit.WorldEditPlugin; import com.sk89q.worldedit.bukkit.WorldEditPlugin;
@ -243,15 +243,14 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
} }
static boolean setSectionAtomic( static boolean setSectionAtomic(
String worldName,
IntPair pair,
LevelChunkSection[] sections, LevelChunkSection[] sections,
LevelChunkSection expected, LevelChunkSection expected,
LevelChunkSection value, LevelChunkSection value,
int layer int layer
) { ) {
if (layer >= 0 && layer < sections.length) { return NMSAdapter.setSectionAtomic(worldName, pair, sections, expected, value, layer);
return ReflectionUtils.compareAndSet(sections, expected, value, layer);
}
return false;
} }
// There is no point in having a functional semaphore for paper servers. // There is no point in having a functional semaphore for paper servers.
@ -349,7 +348,7 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
} }
@SuppressWarnings("deprecation") @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); ChunkHolder chunkHolder = getPlayerChunk(nmsWorld, chunkX, chunkZ);
if (chunkHolder == null) { if (chunkHolder == null) {
return; return;
@ -370,7 +369,13 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
if (levelChunk == null) { if (levelChunk == null) {
return; return;
} }
StampLockHolder lockHolder = new StampLockHolder();
NMSAdapter.beginChunkPacketSend(nmsWorld.getWorld().getName(), pair, lockHolder);
if (lockHolder.chunkLock == null) {
return;
}
MinecraftServer.getServer().execute(() -> { MinecraftServer.getServer().execute(() -> {
try {
ClientboundLevelChunkWithLightPacket packet; ClientboundLevelChunkWithLightPacket packet;
if (PaperLib.isPaper()) { if (PaperLib.isPaper()) {
packet = new ClientboundLevelChunkWithLightPacket( packet = new ClientboundLevelChunkWithLightPacket(
@ -390,6 +395,9 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
); );
} }
nearbyPlayers(nmsWorld, coordIntPair).forEach(p -> p.connection.send(packet)); nearbyPlayers(nmsWorld, coordIntPair).forEach(p -> p.connection.send(packet));
} finally {
NMSAdapter.endChunkPacketSend(nmsWorld.getWorld().getName(), pair, lockHolder);
}
}); });
} }

View File

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

View File

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

View File

@ -7,9 +7,10 @@ import com.fastasyncworldedit.bukkit.adapter.NMSAdapter;
import com.fastasyncworldedit.core.Fawe; import com.fastasyncworldedit.core.Fawe;
import com.fastasyncworldedit.core.FaweCache; import com.fastasyncworldedit.core.FaweCache;
import com.fastasyncworldedit.core.math.BitArrayUnstretched; import com.fastasyncworldedit.core.math.BitArrayUnstretched;
import com.fastasyncworldedit.core.math.IntPair;
import com.fastasyncworldedit.core.util.MathMan; import com.fastasyncworldedit.core.util.MathMan;
import com.fastasyncworldedit.core.util.ReflectionUtils;
import com.fastasyncworldedit.core.util.TaskManager; import com.fastasyncworldedit.core.util.TaskManager;
import com.mojang.datafixers.util.Either;
import com.sk89q.worldedit.bukkit.WorldEditPlugin; import com.sk89q.worldedit.bukkit.WorldEditPlugin;
import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter;
import com.sk89q.worldedit.bukkit.adapter.Refraction; import com.sk89q.worldedit.bukkit.adapter.Refraction;
@ -76,6 +77,7 @@ import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Semaphore; import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -241,15 +243,14 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
} }
static boolean setSectionAtomic( static boolean setSectionAtomic(
String worldName,
IntPair pair,
LevelChunkSection[] sections, LevelChunkSection[] sections,
LevelChunkSection expected, LevelChunkSection expected,
LevelChunkSection value, LevelChunkSection value,
int layer int layer
) { ) {
if (layer >= 0 && layer < sections.length) { return NMSAdapter.setSectionAtomic(worldName, pair, sections, expected, value, layer);
return ReflectionUtils.compareAndSet(sections, expected, value, layer);
}
return false;
} }
// There is no point in having a functional semaphore for paper servers. // There is no point in having a functional semaphore for paper servers.
@ -347,7 +348,7 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
} }
@SuppressWarnings("deprecation") @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); ChunkHolder chunkHolder = getPlayerChunk(nmsWorld, chunkX, chunkZ);
if (chunkHolder == null) { if (chunkHolder == null) {
return; return;
@ -360,13 +361,21 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
.getChunkSource() .getChunkSource()
.getChunkAtIfLoadedImmediately(chunkX, chunkZ); .getChunkAtIfLoadedImmediately(chunkX, chunkZ);
} else { } else {
levelChunk = chunkHolder.getTickingChunkFuture() levelChunk = ((Optional<LevelChunk>) ((Either) chunkHolder
.getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK).orElse(null); .getTickingChunkFuture() // method is not present with new paper chunk system
.getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK)).left())
.orElse(null);
} }
if (levelChunk == null) { if (levelChunk == null) {
return; return;
} }
StampLockHolder lockHolder = new StampLockHolder();
NMSAdapter.beginChunkPacketSend(nmsWorld.getWorld().getName(), pair, lockHolder);
if (lockHolder.chunkLock == null) {
return;
}
MinecraftServer.getServer().execute(() -> { MinecraftServer.getServer().execute(() -> {
try {
ClientboundLevelChunkWithLightPacket packet; ClientboundLevelChunkWithLightPacket packet;
if (PaperLib.isPaper()) { if (PaperLib.isPaper()) {
packet = new ClientboundLevelChunkWithLightPacket( packet = new ClientboundLevelChunkWithLightPacket(
@ -386,6 +395,9 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
); );
} }
nearbyPlayers(nmsWorld, coordIntPair).forEach(p -> p.connection.send(packet)); nearbyPlayers(nmsWorld, coordIntPair).forEach(p -> p.connection.send(packet));
} finally {
NMSAdapter.endChunkPacketSend(nmsWorld.getWorld().getName(), pair, lockHolder);
}
}); });
} }

View File

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

View File

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

View File

@ -8,9 +8,10 @@ import com.fastasyncworldedit.bukkit.adapter.NMSAdapter;
import com.fastasyncworldedit.core.Fawe; import com.fastasyncworldedit.core.Fawe;
import com.fastasyncworldedit.core.FaweCache; import com.fastasyncworldedit.core.FaweCache;
import com.fastasyncworldedit.core.math.BitArrayUnstretched; import com.fastasyncworldedit.core.math.BitArrayUnstretched;
import com.fastasyncworldedit.core.math.IntPair;
import com.fastasyncworldedit.core.util.MathMan; import com.fastasyncworldedit.core.util.MathMan;
import com.fastasyncworldedit.core.util.ReflectionUtils;
import com.fastasyncworldedit.core.util.TaskManager; import com.fastasyncworldedit.core.util.TaskManager;
import com.mojang.datafixers.util.Either;
import com.sk89q.worldedit.bukkit.WorldEditPlugin; import com.sk89q.worldedit.bukkit.WorldEditPlugin;
import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter;
import com.sk89q.worldedit.bukkit.adapter.Refraction; 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.ServerLevel;
import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.BitStorage; import net.minecraft.util.BitStorage;
import net.minecraft.util.ExceptionCollector;
import net.minecraft.util.SimpleBitStorage; import net.minecraft.util.SimpleBitStorage;
import net.minecraft.util.ThreadingDetector; import net.minecraft.util.ThreadingDetector;
import net.minecraft.util.Unit; import net.minecraft.util.Unit;
@ -76,6 +76,7 @@ import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Semaphore; import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -226,15 +227,14 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
} }
static boolean setSectionAtomic( static boolean setSectionAtomic(
String worldName,
IntPair pair,
LevelChunkSection[] sections, LevelChunkSection[] sections,
LevelChunkSection expected, LevelChunkSection expected,
LevelChunkSection value, LevelChunkSection value,
int layer int layer
) { ) {
if (layer >= 0 && layer < sections.length) { return NMSAdapter.setSectionAtomic(worldName, pair, sections, expected, value, layer);
return ReflectionUtils.compareAndSet(sections, expected, value, layer);
}
return false;
} }
// There is no point in having a functional semaphore for paper servers. // There is no point in having a functional semaphore for paper servers.
@ -332,7 +332,7 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
} }
@SuppressWarnings("deprecation") @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); ChunkHolder chunkHolder = getPlayerChunk(nmsWorld, chunkX, chunkZ);
if (chunkHolder == null) { if (chunkHolder == null) {
return; return;
@ -345,13 +345,21 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
.getChunkSource() .getChunkSource()
.getChunkAtIfLoadedImmediately(chunkX, chunkZ); .getChunkAtIfLoadedImmediately(chunkX, chunkZ);
} else { } else {
levelChunk = chunkHolder.getTickingChunkFuture() levelChunk = ((Optional<LevelChunk>) ((Either) chunkHolder
.getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK).orElse(null); .getTickingChunkFuture() // method is not present with new paper chunk system
.getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK)).left())
.orElse(null);
} }
if (levelChunk == null) { if (levelChunk == null) {
return; return;
} }
StampLockHolder lockHolder = new StampLockHolder();
NMSAdapter.beginChunkPacketSend(nmsWorld.getWorld().getName(), pair, lockHolder);
if (lockHolder.chunkLock == null) {
return;
}
MinecraftServer.getServer().execute(() -> { MinecraftServer.getServer().execute(() -> {
try {
ClientboundLevelChunkWithLightPacket packet; ClientboundLevelChunkWithLightPacket packet;
if (PaperLib.isPaper()) { if (PaperLib.isPaper()) {
packet = new ClientboundLevelChunkWithLightPacket( packet = new ClientboundLevelChunkWithLightPacket(
@ -371,6 +379,9 @@ public final class PaperweightPlatformAdapter extends NMSAdapter {
); );
} }
nearbyPlayers(nmsWorld, coordIntPair).forEach(p -> p.connection.send(packet)); nearbyPlayers(nmsWorld, coordIntPair).forEach(p -> p.connection.send(packet));
} finally {
NMSAdapter.endChunkPacketSend(nmsWorld.getWorld().getName(), pair, lockHolder);
}
}); });
} }

View File

@ -2,6 +2,7 @@ package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_21_R1;
import com.fastasyncworldedit.bukkit.adapter.StarlightRelighter; import com.fastasyncworldedit.bukkit.adapter.StarlightRelighter;
import com.fastasyncworldedit.core.configuration.Settings; import com.fastasyncworldedit.core.configuration.Settings;
import com.fastasyncworldedit.core.math.IntPair;
import com.fastasyncworldedit.core.queue.IQueueExtent; import com.fastasyncworldedit.core.queue.IQueueExtent;
import net.minecraft.server.level.ChunkMap; import net.minecraft.server.level.ChunkMap;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
@ -70,7 +71,7 @@ public class PaperweightStarlightRelighter extends StarlightRelighter<ServerLeve
int x = pos.x; int x = pos.x;
int z = pos.z; int z = pos.z;
if (delay) { // we still need to send the block changes of that chunk 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); serverLevel.getChunkSource().removeTicketAtLevel(FAWE_TICKET, pos, LIGHT_LEVEL, Unit.INSTANCE);
} }

View File

@ -0,0 +1,67 @@
package com.fastasyncworldedit.bukkit;
import com.fastasyncworldedit.bukkit.adapter.NMSAdapter;
import com.fastasyncworldedit.bukkit.util.WorldUnloadedException;
import com.fastasyncworldedit.core.math.IntPair;
import com.sk89q.worldedit.bukkit.BukkitWorld;
import org.bukkit.Bukkit;
import org.bukkit.World;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
public class FaweBukkitWorld extends BukkitWorld {
private static final Map<World, FaweBukkitWorld> CACHE = Collections.synchronizedMap(new WeakHashMap<>());
private final ConcurrentHashMap<IntPair, NMSAdapter.ChunkSendLock> 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<IntPair, NMSAdapter.ChunkSendLock> getWorldSendingChunksMap(FaweBukkitWorld world) {
return world.SENDING_CHUNKS;
}
public static ConcurrentHashMap<IntPair, NMSAdapter.ChunkSendLock> 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);
}
}
}

View File

@ -1,12 +1,17 @@
package com.fastasyncworldedit.bukkit.adapter; package com.fastasyncworldedit.bukkit.adapter;
import com.fastasyncworldedit.bukkit.FaweBukkitWorld;
import com.fastasyncworldedit.core.FAWEPlatformAdapterImpl; import com.fastasyncworldedit.core.FAWEPlatformAdapterImpl;
import com.fastasyncworldedit.core.math.IntPair;
import com.fastasyncworldedit.core.queue.IChunkGet; import com.fastasyncworldedit.core.queue.IChunkGet;
import com.fastasyncworldedit.core.util.MathMan; import com.fastasyncworldedit.core.util.MathMan;
import com.fastasyncworldedit.core.util.ReflectionUtils;
import com.sk89q.worldedit.world.block.BlockTypesCache; import com.sk89q.worldedit.world.block.BlockTypesCache;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.StampedLock;
import java.util.function.Function; import java.util.function.Function;
public class NMSAdapter implements FAWEPlatformAdapterImpl { public class NMSAdapter implements FAWEPlatformAdapterImpl {
@ -140,4 +145,118 @@ public class NMSAdapter implements FAWEPlatformAdapterImpl {
((BukkitGetBlocks) chunk).send(); ((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.
* <p>
* 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.
* <p>
* 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 <LevelChunkSection> 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<IntPair, ChunkSendLock> 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.
* <p>
* 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.
* <p>
* Utilises ConcurrentHashMap#compute for easy synchronisation
*
* @since TODO
*/
protected static void beginChunkPacketSend(String worldName, IntPair pair, StampLockHolder stampedLock) {
ConcurrentHashMap<IntPair, ChunkSendLock> 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<IntPair, ChunkSendLock> 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;
}
} }

View File

@ -118,9 +118,9 @@ public class BukkitWorld extends AbstractWorld {
HAS_MIN_Y = temp; HAS_MIN_Y = temp;
} }
private WeakReference<World> worldRef; protected WeakReference<World> worldRef;
//FAWE start //FAWE start
private final String worldNameRef; protected final String worldNameRef;
//FAWE end //FAWE end
private final WorldNativeAccess<?, ?, ?> worldNativeAccess; private final WorldNativeAccess<?, ?, ?> worldNativeAccess;

View File

@ -4,7 +4,9 @@ public record IntPair(int x, int z) {
@Override @Override
public int hashCode() { public int hashCode() {
return (x << 16) | (z & 0xFFFF); int i = 1664525 * x + 1013904223;
int j = 1664525 * (z ^ -559038737) + 1013904223;
return i ^ j;
} }
@Override @Override