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 f17e8d3e35..e4e1b2e4e3 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 @@ -1,18 +1,15 @@ package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2; -import com.fastasyncworldedit.bukkit.adapter.BukkitGetBlocks; +import com.fastasyncworldedit.bukkit.adapter.AbstractBukkitGetBlocks; import com.fastasyncworldedit.bukkit.adapter.DelegateSemaphore; -import com.fastasyncworldedit.core.Fawe; import com.fastasyncworldedit.core.FaweCache; import com.fastasyncworldedit.core.configuration.Settings; import com.fastasyncworldedit.core.extent.processor.heightmap.HeightMapType; +import com.fastasyncworldedit.core.internal.exception.FaweException; 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; -import com.fastasyncworldedit.core.queue.implementation.QueueHandler; -import com.fastasyncworldedit.core.queue.implementation.blocks.CharGetBlocks; import com.fastasyncworldedit.core.util.MathMan; import com.fastasyncworldedit.core.util.NbtUtils; import com.fastasyncworldedit.core.util.collection.AdaptedMap; @@ -22,6 +19,7 @@ import com.sk89q.worldedit.internal.util.LogManagerCompat; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.util.SideEffect; +import com.sk89q.worldedit.util.formatting.text.TextComponent; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.biome.BiomeTypes; import com.sk89q.worldedit.world.block.BlockTypesCache; @@ -81,18 +79,17 @@ import java.util.Map; import java.util.Set; import java.util.UUID; -import java.util.concurrent.Callable; -import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.Semaphore; import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.Function; import static net.minecraft.core.registries.Registries.BIOME; -public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBlocks { +public class PaperweightGetBlocks extends AbstractBukkitGetBlocks { private static final Logger LOGGER = LogManagerCompat.getLogger(); @@ -104,84 +101,25 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc .getInstance() .getBukkitImplAdapter()); private final ReadWriteLock sectionLock = new ReentrantReadWriteLock(); - private final ReentrantLock callLock = new ReentrantLock(); - 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; - private final int maxSectionPosition; private final Registry biomeRegistry; private final IdMap> biomeHolderIdMap; - private final ConcurrentHashMap copies = new ConcurrentHashMap<>(); private final Object sendLock = new Object(); - private LevelChunkSection[] sections; private LevelChunk levelChunk; + private LevelChunkSection[] sections; private DataLayer[] blockLight; private DataLayer[] skyLight; - private boolean createCopy = false; - private boolean forceLoadSections = true; private boolean lightUpdate = false; - private int copyKey = 0; public PaperweightGetBlocks(World world, int chunkX, int chunkZ) { this(((CraftWorld) world).getHandle(), chunkX, chunkZ); } public PaperweightGetBlocks(ServerLevel serverLevel, int chunkX, int chunkZ) { - super(serverLevel.getMinBuildHeight() >> 4, (serverLevel.getMaxBuildHeight() - 1) >> 4); - this.serverLevel = serverLevel; - this.chunkX = chunkX; - this.chunkZ = chunkZ; - this.minHeight = serverLevel.getMinBuildHeight(); - this.maxHeight = serverLevel.getMaxBuildHeight() - 1; // Minecraft max limit is exclusive. - this.minSectionPosition = minHeight >> 4; - this.maxSectionPosition = maxHeight >> 4; + super(serverLevel, chunkX, chunkZ, serverLevel.getMinBuildHeight(), serverLevel.getMaxBuildHeight() - 1); this.skyLight = new DataLayer[getSectionCount()]; this.blockLight = new DataLayer[getSectionCount()]; this.biomeRegistry = serverLevel.registryAccess().registryOrThrow(BIOME); this.biomeHolderIdMap = biomeRegistry.asHolderIdMap(); - this.chunkPos = new IntPair(chunkX, chunkZ); - } - - public int getChunkX() { - return chunkX; - } - - public int getChunkZ() { - return chunkZ; - } - - @Override - public boolean isCreateCopy() { - return createCopy; - } - - @Override - public int setCreateCopy(boolean createCopy) { - if (!callLock.isHeldByCurrentThread()) { - throw new IllegalStateException("Attempting to set if chunk GET should create copy, but it is not call-locked."); - } - this.createCopy = createCopy; - // Increment regardless of whether copy will be created or not to return null from getCopy() - return ++this.copyKey; - } - - @Override - public IChunkGet getCopy(final int key) { - return copies.remove(key); - } - - @Override - public void lockCall() { - this.callLock.lock(); - } - - @Override - public void unlockCall() { - this.callLock.unlock(); } @Override @@ -191,7 +129,7 @@ public void setLightingToGet(char[][] light, int minSectionPosition, int maxSect try { fillLightNibble(light, LightLayer.BLOCK, minSectionPosition, maxSectionPosition); } catch (Throwable e) { - e.printStackTrace(); + LOGGER.error("Error setting lighting to get", e); } } } @@ -203,7 +141,7 @@ public void setSkyLightingToGet(char[][] light, int minSectionPosition, int maxS try { fillLightNibble(light, LightLayer.SKY, minSectionPosition, maxSectionPosition); } catch (Throwable e) { - e.printStackTrace(); + LOGGER.error("Error setting sky lighting to get", e); } } } @@ -218,16 +156,6 @@ public void setHeightmapToGet(HeightMapType type, int[] data) { heightMap.setRawData(getChunk(), nativeType, bitArray.getData()); } - @Override - public int getMaxY() { - return maxHeight; - } - - @Override - public int getMinY() { - return minHeight; - } - @Override public BiomeType getBiomeType(int x, int y, int z) { LevelChunkSection section = getSections(false)[(y >> 4) - getMinSectionPosition()]; @@ -346,7 +274,7 @@ public int[] getHeightMap(HeightMapType type) { @Override public @Nullable FaweCompoundTag entity(final UUID uuid) { - ensureLoaded(serverLevel, chunkX, chunkZ); + ensureLoaded(serverLevel); List entities = PaperweightPlatformAdapter.getEntities(getChunk()); Entity entity = null; for (Entity e : entities) { @@ -369,7 +297,7 @@ public int[] getHeightMap(HeightMapType type) { @Override public Collection entities() { - ensureLoaded(serverLevel, chunkX, chunkZ); + ensureLoaded(serverLevel); List entities = PaperweightPlatformAdapter.getEntities(getChunk()); if (entities.isEmpty()) { return Collections.emptyList(); @@ -418,18 +346,19 @@ private void removeEntity(Entity entity) { entity.discard(); } - public LevelChunk ensureLoaded(ServerLevel nmsWorld, int chunkX, int chunkZ) { + @Override + public CompletableFuture ensureLoaded(ServerLevel nmsWorld) { return PaperweightPlatformAdapter.ensureLoaded(nmsWorld, chunkX, chunkZ); } @Override - @SuppressWarnings("rawtypes") - public synchronized > T call(IChunkSet set, Runnable finalizer) { - if (!callLock.isHeldByCurrentThread()) { - throw new IllegalStateException("Attempted to call chunk GET but chunk was not call-locked."); - } - forceLoadSections = false; - LevelChunk nmsChunk = ensureLoaded(serverLevel, chunkX, chunkZ); + protected > T internalCall( + IChunkSet set, + Runnable finalizer, + int copyKey, + LevelChunk nmsChunk, + ServerLevel nmsWorld + ) throws Exception { PaperweightGetBlocks_Copy copy = createCopy ? new PaperweightGetBlocks_Copy(nmsChunk) : null; if (createCopy) { if (copies.containsKey(copyKey)) { @@ -437,464 +366,421 @@ public synchronized > T call(IChunkSet set, Runnable finaliz } copies.put(copyKey, copy); } - try { - // Remove existing tiles. Create a copy so that we can remove blocks - Map chunkTiles = new HashMap<>(nmsChunk.getBlockEntities()); - List beacons = null; - if (!chunkTiles.isEmpty()) { - for (Map.Entry entry : chunkTiles.entrySet()) { - final BlockPos pos = entry.getKey(); - final int lx = pos.getX() & 15; - final int ly = pos.getY(); - final int lz = pos.getZ() & 15; - final int layer = ly >> 4; - if (!set.hasSection(layer)) { - continue; - } + // Remove existing tiles. Create a copy so that we can remove blocks + Map chunkTiles = new HashMap<>(nmsChunk.getBlockEntities()); + List beacons = null; + if (!chunkTiles.isEmpty()) { + for (Map.Entry entry : chunkTiles.entrySet()) { + final BlockPos pos = entry.getKey(); + final int lx = pos.getX() & 15; + final int ly = pos.getY(); + final int lz = pos.getZ() & 15; + final int layer = ly >> 4; + if (!set.hasSection(layer)) { + continue; + } - int ordinal = set.getBlock(lx, ly, lz).getOrdinal(); - if (ordinal != BlockTypesCache.ReservedIDs.__RESERVED__) { - BlockEntity tile = entry.getValue(); - if (PaperLib.isPaper() && tile instanceof BeaconBlockEntity) { - if (beacons == null) { - beacons = new ArrayList<>(); - } - beacons.add(tile); - PaperweightPlatformAdapter.removeBeacon(tile, nmsChunk); - continue; - } - nmsChunk.removeBlockEntity(tile.getBlockPos()); - if (createCopy) { - copy.storeTile(tile); + int ordinal = set.getBlock(lx, ly, lz).getOrdinal(); + if (ordinal != BlockTypesCache.ReservedIDs.__RESERVED__) { + BlockEntity tile = entry.getValue(); + if (PaperLib.isPaper() && tile instanceof BeaconBlockEntity) { + if (beacons == null) { + beacons = new ArrayList<>(); } + beacons.add(tile); + PaperweightPlatformAdapter.removeBeacon(tile, nmsChunk); + continue; + } + nmsChunk.removeBlockEntity(tile.getBlockPos()); + if (createCopy) { + copy.storeTile(tile); } } } - final BiomeType[][] biomes = set.getBiomes(); + } + final BiomeType[][] biomes = set.getBiomes(); - int bitMask = 0; - synchronized (nmsChunk) { - LevelChunkSection[] levelChunkSections = nmsChunk.getSections(); + int bitMask = 0; + synchronized (nmsChunk) { + LevelChunkSection[] levelChunkSections = nmsChunk.getSections(); - for (int layerNo = getMinSectionPosition(); layerNo <= getMaxSectionPosition(); layerNo++) { + for (int layerNo = getMinSectionPosition(); layerNo <= getMaxSectionPosition(); layerNo++) { - int getSectionIndex = layerNo - getMinSectionPosition(); - int setSectionIndex = layerNo - set.getMinSectionPosition(); + int getSectionIndex = layerNo - getMinSectionPosition(); + int setSectionIndex = layerNo - set.getMinSectionPosition(); - if (!set.hasSection(layerNo)) { - // No blocks, but might be biomes present. Handle this lazily. - if (biomes == null) { - continue; - } - if (layerNo < set.getMinSectionPosition() || layerNo > set.getMaxSectionPosition()) { - continue; - } - if (biomes[setSectionIndex] != null) { - synchronized (super.sectionLocks[getSectionIndex]) { - LevelChunkSection existingSection = levelChunkSections[getSectionIndex]; - if (createCopy && existingSection != null) { - copy.storeBiomes(getSectionIndex, existingSection.getBiomes()); - } + if (!set.hasSection(layerNo)) { + // No blocks, but might be biomes present. Handle this lazily. + if (biomes == null) { + continue; + } + if (layerNo < set.getMinSectionPosition() || layerNo > set.getMaxSectionPosition()) { + continue; + } + if (biomes[setSectionIndex] != null) { + synchronized (super.sectionLocks[getSectionIndex]) { + LevelChunkSection existingSection = levelChunkSections[getSectionIndex]; + if (createCopy && existingSection != null) { + copy.storeBiomes(getSectionIndex, existingSection.getBiomes()); + } - if (existingSection == null) { - PalettedContainer> biomeData = PaperweightPlatformAdapter.getBiomePalettedContainer( - biomes[setSectionIndex], - biomeHolderIdMap - ); - LevelChunkSection newSection = PaperweightPlatformAdapter.newChunkSection( - layerNo, - new char[4096], - adapter, - biomeRegistry, - biomeData - ); - if (PaperweightPlatformAdapter.setSectionAtomic( - serverLevel.getWorld().getName(), - chunkPos, - levelChunkSections, - null, - newSection, - getSectionIndex - )) { - updateGet(nmsChunk, levelChunkSections, newSection, new char[4096], getSectionIndex); - continue; - } else { - existingSection = levelChunkSections[getSectionIndex]; - if (existingSection == null) { - LOGGER.error("Skipping invalid null section. chunk: {}, {} layer: {}", chunkX, chunkZ, - getSectionIndex - ); - continue; - } - } + if (existingSection == null) { + PalettedContainer> biomeData = PaperweightPlatformAdapter.getBiomePalettedContainer( + biomes[setSectionIndex], + biomeHolderIdMap + ); + LevelChunkSection newSection = PaperweightPlatformAdapter.newChunkSection( + layerNo, + new char[4096], + adapter, + biomeRegistry, + biomeData + ); + if (PaperweightPlatformAdapter.setSectionAtomic( + nmsWorld.getWorld().getName(), + chunkPos, + levelChunkSections, + null, + newSection, + getSectionIndex + )) { + updateGet(nmsChunk, levelChunkSections, newSection, new char[4096], getSectionIndex); + continue; } else { - PalettedContainer> paletteBiomes = setBiomesToPalettedContainer( - biomes, - setSectionIndex, - existingSection.getBiomes() - ); - if (paletteBiomes != null) { - PaperweightPlatformAdapter.setBiomesToChunkSection(existingSection, paletteBiomes); + existingSection = levelChunkSections[getSectionIndex]; + if (existingSection == null) { + LOGGER.error( + "Skipping invalid null section. chunk: {}, {} layer: {}", + chunkX, + chunkZ, + getSectionIndex + ); + continue; } } + } else { + PalettedContainer> paletteBiomes = setBiomesToPalettedContainer( + biomes, + setSectionIndex, + existingSection.getBiomes() + ); + if (paletteBiomes != null) { + PaperweightPlatformAdapter.setBiomesToChunkSection(existingSection, paletteBiomes); + } } } - continue; } + continue; + } - bitMask |= 1 << getSectionIndex; - - // setArr is modified by PaperweightPlatformAdapter#newChunkSection. This is in order to write changes to - // this chunk GET when #updateGet is called. Future dords, please listen this time. - char[] tmp = set.load(layerNo); - char[] setArr = new char[tmp.length]; - System.arraycopy(tmp, 0, setArr, 0, tmp.length); - - // synchronise on internal section to avoid circular locking with a continuing edit if the chunk was - // submitted to keep loaded internal chunks to queue target size. - synchronized (super.sectionLocks[getSectionIndex]) { - - LevelChunkSection newSection; - LevelChunkSection existingSection = levelChunkSections[getSectionIndex]; - // Don't attempt to tick section whilst we're editing - if (existingSection != null) { - PaperweightPlatformAdapter.clearCounts(existingSection); - if (PaperLib.isPaper()) { - existingSection.tickingList.clear(); - } - } + bitMask |= 1 << getSectionIndex; - if (createCopy) { - char[] tmpLoad = loadPrivately(layerNo); - char[] copyArr = new char[4096]; - System.arraycopy(tmpLoad, 0, copyArr, 0, 4096); - copy.storeSection(getSectionIndex, copyArr); - if (biomes != null && existingSection != null) { - copy.storeBiomes(getSectionIndex, existingSection.getBiomes()); - } - } + // setArr is modified by PaperweightPlatformAdapter#newChunkSection. This is in order to write changes to + // this chunk GET when #updateGet is called. Future dords, please listen this time. + char[] tmp = set.load(layerNo); + char[] setArr = new char[tmp.length]; + System.arraycopy(tmp, 0, setArr, 0, tmp.length); - if (existingSection == null) { - PalettedContainer> biomeData = biomes == null ? new PalettedContainer<>( - biomeHolderIdMap, - biomeHolderIdMap.byIdOrThrow(adapter.getInternalBiomeId(BiomeTypes.PLAINS)), - PalettedContainer.Strategy.SECTION_BIOMES - ) : PaperweightPlatformAdapter.getBiomePalettedContainer(biomes[setSectionIndex], biomeHolderIdMap); - newSection = PaperweightPlatformAdapter.newChunkSection( - layerNo, - setArr, - adapter, - biomeRegistry, - biomeData - ); - if (PaperweightPlatformAdapter.setSectionAtomic( - serverLevel.getWorld().getName(), - chunkPos, - levelChunkSections, - null, - newSection, - getSectionIndex - )) { - updateGet(nmsChunk, levelChunkSections, newSection, setArr, getSectionIndex); - continue; - } else { - existingSection = levelChunkSections[getSectionIndex]; - if (existingSection == null) { - LOGGER.error("Skipping invalid null section. chunk: {}, {} layer: {}", chunkX, chunkZ, - getSectionIndex - ); - continue; - } - } - } + // synchronise on internal section to avoid circular locking with a continuing edit if the chunk was + // submitted to keep loaded internal chunks to queue target size. + synchronized (super.sectionLocks[getSectionIndex]) { - //ensure that the server doesn't try to tick the chunksection while we're editing it. (Again) + LevelChunkSection newSection; + LevelChunkSection existingSection = levelChunkSections[getSectionIndex]; + // Don't attempt to tick section whilst we're editing + if (existingSection != null) { PaperweightPlatformAdapter.clearCounts(existingSection); if (PaperLib.isPaper()) { existingSection.tickingList.clear(); } - DelegateSemaphore lock = PaperweightPlatformAdapter.applyLock(existingSection); - - // Synchronize to prevent further acquisitions - synchronized (lock) { - lock.acquire(); // Wait until we have the lock - lock.release(); - try { - sectionLock.writeLock().lock(); - if (this.getChunk() != nmsChunk) { - this.levelChunk = nmsChunk; - this.sections = null; - this.reset(); - } else if (existingSection != getSections(false)[getSectionIndex]) { - this.sections[getSectionIndex] = existingSection; - this.reset(); - } else if (!Arrays.equals( - update(getSectionIndex, new char[4096], true), - loadPrivately(layerNo) - )) { - this.reset(layerNo); - /*} else if (lock.isModified()) { - this.reset(layerNo);*/ - } - } finally { - sectionLock.writeLock().unlock(); - } + } - PalettedContainer> biomeData = setBiomesToPalettedContainer( - biomes, - setSectionIndex, - existingSection.getBiomes() - ); + if (createCopy) { + char[] tmpLoad = loadPrivately(layerNo); + char[] copyArr = new char[4096]; + System.arraycopy(tmpLoad, 0, copyArr, 0, 4096); + copy.storeSection(getSectionIndex, copyArr); + if (biomes != null && existingSection != null) { + copy.storeBiomes(getSectionIndex, existingSection.getBiomes()); + } + } - newSection = PaperweightPlatformAdapter.newChunkSection( - layerNo, - this::loadPrivately, - setArr, - adapter, - biomeRegistry, - biomeData != null ? biomeData : (PalettedContainer>) existingSection.getBiomes() - ); - if (!PaperweightPlatformAdapter.setSectionAtomic( - serverLevel.getWorld().getName(), - chunkPos, - levelChunkSections, - existingSection, - newSection, - getSectionIndex - )) { - LOGGER.error("Skipping invalid null section. chunk: {}, {} layer: {}", chunkX, chunkZ, + if (existingSection == null) { + PalettedContainer> biomeData = biomes == null ? new PalettedContainer<>( + biomeHolderIdMap, + biomeHolderIdMap.byIdOrThrow(adapter.getInternalBiomeId(BiomeTypes.PLAINS)), + PalettedContainer.Strategy.SECTION_BIOMES + ) : PaperweightPlatformAdapter.getBiomePalettedContainer(biomes[setSectionIndex], biomeHolderIdMap); + newSection = PaperweightPlatformAdapter.newChunkSection( + layerNo, + setArr, + adapter, + biomeRegistry, + biomeData + ); + if (PaperweightPlatformAdapter.setSectionAtomic( + nmsWorld.getWorld().getName(), + chunkPos, + levelChunkSections, + null, + newSection, + getSectionIndex + )) { + updateGet(nmsChunk, levelChunkSections, newSection, setArr, getSectionIndex); + continue; + } else { + existingSection = levelChunkSections[getSectionIndex]; + if (existingSection == null) { + LOGGER.error( + "Skipping invalid null section. chunk: {}, {} layer: {}", + chunkX, + chunkZ, getSectionIndex ); - } else { - updateGet(nmsChunk, levelChunkSections, newSection, setArr, getSectionIndex); + continue; } } } - } - Map heightMaps = set.getHeightMaps(); - for (Map.Entry entry : heightMaps.entrySet()) { - PaperweightGetBlocks.this.setHeightmapToGet(entry.getKey(), entry.getValue()); + //ensure that the server doesn't try to tick the chunksection while we're editing it. (Again) + PaperweightPlatformAdapter.clearCounts(existingSection); + if (PaperLib.isPaper()) { + existingSection.tickingList.clear(); + } + DelegateSemaphore lock = PaperweightPlatformAdapter.applyLock(existingSection); + // Synchronize to prevent further acquisitions + synchronized (lock) { + lock.acquire(); // Wait until we have the lock + lock.release(); + try { + sectionLock.writeLock().lock(); + if (this.getChunk() != nmsChunk) { + this.levelChunk = nmsChunk; + this.sections = null; + this.reset(); + } else if (existingSection != getSections(false)[getSectionIndex]) { + this.sections[getSectionIndex] = existingSection; + this.reset(); + } else if (!Arrays.equals(update(getSectionIndex, new char[4096], true), loadPrivately(layerNo))) { + this.reset(layerNo); + /*} else if (lock.isModified()) { + this.reset(layerNo);*/ + } + } finally { + sectionLock.writeLock().unlock(); + } + + PalettedContainer> biomeData = setBiomesToPalettedContainer( + biomes, + setSectionIndex, + existingSection.getBiomes() + ); + + newSection = PaperweightPlatformAdapter.newChunkSection( + layerNo, + this::loadPrivately, + setArr, + adapter, + biomeRegistry, + biomeData != null ? biomeData : (PalettedContainer>) existingSection.getBiomes() + ); + if (!PaperweightPlatformAdapter.setSectionAtomic( + nmsWorld.getWorld().getName(), + chunkPos, + levelChunkSections, + existingSection, + newSection, + getSectionIndex + )) { + LOGGER.error( + "Skipping invalid null section. chunk: {}, {} layer: {}", + chunkX, + chunkZ, + getSectionIndex + ); + } else { + updateGet(nmsChunk, levelChunkSections, newSection, setArr, getSectionIndex); + } + } } - PaperweightGetBlocks.this.setLightingToGet( - set.getLight(), - set.getMinSectionPosition(), - set.getMaxSectionPosition() - ); - PaperweightGetBlocks.this.setSkyLightingToGet( - set.getSkyLight(), - set.getMinSectionPosition(), - set.getMaxSectionPosition() - ); + } - Runnable[] syncTasks = null; + Map heightMaps = set.getHeightMaps(); + for (Map.Entry entry : heightMaps.entrySet()) { + PaperweightGetBlocks.this.setHeightmapToGet(entry.getKey(), entry.getValue()); + } + PaperweightGetBlocks.this.setLightingToGet(set.getLight(), set.getMinSectionPosition(), set.getMaxSectionPosition()); + PaperweightGetBlocks.this.setSkyLightingToGet( + set.getSkyLight(), + set.getMinSectionPosition(), + set.getMaxSectionPosition() + ); - int bx = chunkX << 4; - int bz = chunkZ << 4; + Runnable[] syncTasks = null; - // Call beacon deactivate events here synchronously - // list will be null on spigot, so this is an implicit isPaper check - if (beacons != null && !beacons.isEmpty()) { - final List finalBeacons = beacons; + int bx = chunkX << 4; + int bz = chunkZ << 4; - syncTasks = new Runnable[4]; + // Call beacon deactivate events here synchronously + // list will be null on spigot, so this is an implicit isPaper check + if (beacons != null && !beacons.isEmpty()) { + final List finalBeacons = beacons; - syncTasks[3] = () -> { - for (BlockEntity beacon : finalBeacons) { - BeaconBlockEntity.playSound(beacon.getLevel(), beacon.getBlockPos(), SoundEvents.BEACON_DEACTIVATE); - new BeaconDeactivatedEvent(CraftBlock.at(beacon.getLevel(), beacon.getBlockPos())).callEvent(); - } - }; - } + syncTasks = new Runnable[4]; - Set entityRemoves = set.getEntityRemoves(); - if (entityRemoves != null && !entityRemoves.isEmpty()) { - if (syncTasks == null) { - syncTasks = new Runnable[3]; + syncTasks[3] = () -> { + for (BlockEntity beacon : finalBeacons) { + BeaconBlockEntity.playSound(beacon.getLevel(), beacon.getBlockPos(), SoundEvents.BEACON_DEACTIVATE); + new BeaconDeactivatedEvent(CraftBlock.at(beacon.getLevel(), beacon.getBlockPos())).callEvent(); } + }; + } - syncTasks[2] = () -> { - Set entitiesRemoved = new HashSet<>(); - final List entities = PaperweightPlatformAdapter.getEntities(nmsChunk); + Set entityRemoves = set.getEntityRemoves(); + if (entityRemoves != null && !entityRemoves.isEmpty()) { + if (syncTasks == null) { + syncTasks = new Runnable[3]; + } - for (Entity entity : entities) { - UUID uuid = entity.getUUID(); - if (entityRemoves.contains(uuid)) { - if (createCopy) { - copy.storeEntity(entity); - } - removeEntity(entity); - entitiesRemoved.add(uuid); - entityRemoves.remove(uuid); + syncTasks[2] = () -> { + Set entitiesRemoved = new HashSet<>(); + final List entities = PaperweightPlatformAdapter.getEntities(nmsChunk); + + for (Entity entity : entities) { + UUID uuid = entity.getUUID(); + if (entityRemoves.contains(uuid)) { + if (createCopy) { + copy.storeEntity(entity); } + removeEntity(entity); + entitiesRemoved.add(uuid); + entityRemoves.remove(uuid); } - if (Settings.settings().EXPERIMENTAL.REMOVE_ENTITY_FROM_WORLD_ON_CHUNK_FAIL) { - for (UUID uuid : entityRemoves) { - Entity entity = serverLevel.getEntities().get(uuid); - if (entity != null) { - removeEntity(entity); - } + } + if (Settings.settings().EXPERIMENTAL.REMOVE_ENTITY_FROM_WORLD_ON_CHUNK_FAIL) { + for (UUID uuid : entityRemoves) { + Entity entity = nmsWorld.getEntities().get(uuid); + if (entity != null) { + removeEntity(entity); } } - // Only save entities that were actually removed to history - set.getEntityRemoves().clear(); - set.getEntityRemoves().addAll(entitiesRemoved); - }; - } - - Collection entities = set.entities(); - if (entities != null && !entities.isEmpty()) { - if (syncTasks == null) { - syncTasks = new Runnable[2]; } + // Only save entities that were actually removed to history + set.getEntityRemoves().clear(); + set.getEntityRemoves().addAll(entitiesRemoved); + }; + } - syncTasks[1] = () -> { - Iterator iterator = entities.iterator(); - while (iterator.hasNext()) { - final FaweCompoundTag nativeTag = iterator.next(); - final LinCompoundTag linTag = nativeTag.linTag(); - final LinStringTag idTag = linTag.findTag("Id", LinTagType.stringTag()); - final LinListTag posTag = linTag.findListTag("Pos", LinTagType.doubleTag()); - final LinListTag rotTag = linTag.findListTag("Rotation", LinTagType.floatTag()); - if (idTag == null || posTag == null || rotTag == null) { - LOGGER.error("Unknown entity tag: {}", nativeTag); - continue; - } - final double x = posTag.get(0).valueAsDouble(); - final double y = posTag.get(1).valueAsDouble(); - final double z = posTag.get(2).valueAsDouble(); - final float yaw = rotTag.get(0).valueAsFloat(); - final float pitch = rotTag.get(1).valueAsFloat(); - final String id = idTag.value(); - - EntityType type = EntityType.byString(id).orElse(null); - if (type != null) { - 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) { - tag.remove(name); - } - entity.load(tag); - entity.absMoveTo(x, y, z, yaw, pitch); - entity.setUUID(NbtUtils.uuid(nativeTag)); - if (!serverLevel.addFreshEntity(entity, CreatureSpawnEvent.SpawnReason.CUSTOM)) { - LOGGER.warn( - "Error creating entity of type `{}` in world `{}` at location `{},{},{}`", - id, - serverLevel.getWorld().getName(), - x, - y, - z - ); - // Unsuccessful create should not be saved to history - iterator.remove(); - } - } - } - } - }; + Collection entities = set.entities(); + if (entities != null && !entities.isEmpty()) { + if (syncTasks == null) { + syncTasks = new Runnable[2]; } - // set tiles - Map tiles = set.tiles(); - if (tiles != null && !tiles.isEmpty()) { - if (syncTasks == null) { - syncTasks = new Runnable[1]; - } - - syncTasks[0] = () -> { - for (final Map.Entry entry : tiles.entrySet()) { - final FaweCompoundTag nativeTag = entry.getValue(); - final BlockVector3 blockHash = entry.getKey(); - final int x = blockHash.x() + bx; - final int y = blockHash.y(); - final int z = blockHash.z() + bz; - final BlockPos pos = new BlockPos(x, y, z); - - synchronized (serverLevel) { - BlockEntity tileEntity = serverLevel.getBlockEntity(pos); - if (tileEntity == null || tileEntity.isRemoved()) { - serverLevel.removeBlockEntity(pos); - tileEntity = serverLevel.getBlockEntity(pos); + syncTasks[1] = () -> { + Iterator iterator = entities.iterator(); + while (iterator.hasNext()) { + final FaweCompoundTag nativeTag = iterator.next(); + final LinCompoundTag linTag = nativeTag.linTag(); + final LinStringTag idTag = linTag.findTag("Id", LinTagType.stringTag()); + final LinListTag posTag = linTag.findListTag("Pos", LinTagType.doubleTag()); + final LinListTag rotTag = linTag.findListTag("Rotation", LinTagType.floatTag()); + if (idTag == null || posTag == null || rotTag == null) { + LOGGER.error("Unknown entity tag: {}", nativeTag); + continue; + } + final double x = posTag.get(0).valueAsDouble(); + final double y = posTag.get(1).valueAsDouble(); + final double z = posTag.get(2).valueAsDouble(); + final float yaw = rotTag.get(0).valueAsFloat(); + final float pitch = rotTag.get(1).valueAsFloat(); + final String id = idTag.value(); + + EntityType type = EntityType.byString(id).orElse(null); + if (type != null) { + Entity entity = type.create(nmsWorld); + if (entity != null) { + final CompoundTag tag = (CompoundTag) adapter.fromNativeLin(linTag); + for (final String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) { + tag.remove(name); } - if (tileEntity != null) { - final CompoundTag tag = (CompoundTag) adapter.fromNativeLin(nativeTag.linTag()); - tag.put("x", IntTag.valueOf(x)); - tag.put("y", IntTag.valueOf(y)); - tag.put("z", IntTag.valueOf(z)); - tileEntity.load(tag); + entity.load(tag); + entity.absMoveTo(x, y, z, yaw, pitch); + entity.setUUID(NbtUtils.uuid(nativeTag)); + if (!nmsWorld.addFreshEntity(entity, CreatureSpawnEvent.SpawnReason.CUSTOM)) { + LOGGER.warn( + "Error creating entity of type `{}` in world `{}` at location `{},{},{}`", + id, + nmsWorld.getWorld().getName(), + x, + y, + z + ); + // Unsuccessful create should not be saved to history + iterator.remove(); } } } - }; - } + } + }; + } - Runnable callback; - if (bitMask == 0 && biomes == null && !lightUpdate) { - callback = null; - } else { - int finalMask = bitMask != 0 ? bitMask : lightUpdate ? set.getBitMask() : 0; - callback = () -> { - // Set Modified - nmsChunk.setLightCorrect(true); // Set Modified - nmsChunk.mustNotSave = false; - nmsChunk.setUnsaved(true); - // send to player - if (!set - .getSideEffectSet() - .shouldApply(SideEffect.LIGHTING) || !Settings.settings().LIGHTING.DELAY_PACKET_SENDING || finalMask == 0 && biomes != null) { - this.send(); - } - if (finalizer != null) { - finalizer.run(); - } - }; + // set tiles + Map tiles = set.tiles(); + if (tiles != null && !tiles.isEmpty()) { + if (syncTasks == null) { + syncTasks = new Runnable[1]; } - if (syncTasks != null) { - QueueHandler queueHandler = Fawe.instance().getQueueHandler(); - Runnable[] finalSyncTasks = syncTasks; - // Chain the sync tasks and the callback - Callable chain = () -> { - try { - // Run the sync tasks - for (Runnable task : finalSyncTasks) { - if (task != null) { - task.run(); - } + syncTasks[0] = () -> { + for (final Map.Entry entry : tiles.entrySet()) { + final FaweCompoundTag nativeTag = entry.getValue(); + final BlockVector3 blockHash = entry.getKey(); + final int x = blockHash.x() + bx; + final int y = blockHash.y(); + final int z = blockHash.z() + bz; + final BlockPos pos = new BlockPos(x, y, z); + + synchronized (nmsWorld) { + BlockEntity tileEntity = nmsWorld.getBlockEntity(pos); + if (tileEntity == null || tileEntity.isRemoved()) { + nmsWorld.removeBlockEntity(pos); + tileEntity = nmsWorld.getBlockEntity(pos); } - if (callback == null) { - if (finalizer != null) { - queueHandler.async(finalizer, null); - } - return null; - } else { - return queueHandler.async(callback, null); + if (tileEntity != null) { + final CompoundTag tag = (CompoundTag) adapter.fromNativeLin(nativeTag.linTag()); + tag.put("x", IntTag.valueOf(x)); + tag.put("y", IntTag.valueOf(y)); + tag.put("z", IntTag.valueOf(z)); + tileEntity.load(tag); } - } catch (Throwable e) { - e.printStackTrace(); - throw e; - } - }; - //noinspection unchecked - required at compile time - return (T) (Future) queueHandler.sync(chain); - } else { - if (callback == null) { - if (finalizer != null) { - finalizer.run(); } - } else { - callback.run(); } - } + }; } - return null; - } catch (Throwable e) { - e.printStackTrace(); - return null; - } finally { - forceLoadSections = true; + + Runnable callback; + if (bitMask == 0 && biomes == null && !lightUpdate) { + callback = null; + } else { + int finalMask = bitMask != 0 ? bitMask : lightUpdate ? set.getBitMask() : 0; + callback = () -> { + // Set Modified + nmsChunk.setLightCorrect(true); // Set Modified + nmsChunk.mustNotSave = false; + nmsChunk.setUnsaved(true); + // send to player + if (!set + .getSideEffectSet() + .shouldApply(SideEffect.LIGHTING) || !Settings.settings().LIGHTING.DELAY_PACKET_SENDING || finalMask == 0 && biomes != null) { + this.send(); + } + if (finalizer != null) { + finalizer.run(); + } + }; + } + return handleCallFinalizer(syncTasks, callback, finalizer); } } @@ -927,18 +813,6 @@ private void updateGet( this.blocks[layer] = arr; } - private char[] loadPrivately(int layer) { - layer -= getMinSectionPosition(); - if (super.sections[layer] != null) { - synchronized (super.sectionLocks[layer]) { - if (super.sections[layer].isFull() && super.blocks[layer] != null) { - return super.blocks[layer]; - } - } - } - return PaperweightGetBlocks.this.update(layer, null, true); - } - @Override public void send() { synchronized (sendLock) { @@ -1036,7 +910,7 @@ public char[] update(int layer, char[] data, boolean aggressive) { } return data; } catch (IllegalAccessException | InterruptedException e) { - e.printStackTrace(); + LOGGER.error("Could not read block data from palette", e); throw new RuntimeException(e); } finally { lock.release(); @@ -1078,7 +952,16 @@ public LevelChunk getChunk() { synchronized (this) { levelChunk = this.levelChunk; if (levelChunk == null) { - this.levelChunk = levelChunk = ensureLoaded(this.serverLevel, chunkX, chunkZ); + try { + this.levelChunk = levelChunk = ensureLoaded(this.serverLevel).get(); + } catch (InterruptedException | ExecutionException e) { + LOGGER.error("Could not get chunk at {},{}", chunkX, chunkZ, e); + throw new FaweException( + TextComponent.of("Could not get chunk at " + chunkX + "," + chunkZ + ": " + e.getMessage()), + FaweException.Type.OTHER, + false + ); + } } } } diff --git a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightGetBlocks_Copy.java b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightGetBlocks_Copy.java index e655dd206c..e9376b74e4 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightGetBlocks_Copy.java +++ b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightGetBlocks_Copy.java @@ -3,8 +3,10 @@ import com.fastasyncworldedit.core.extent.processor.heightmap.HeightMapType; import com.fastasyncworldedit.core.nbt.FaweCompoundTag; import com.fastasyncworldedit.core.queue.IBlocks; +import com.fastasyncworldedit.core.queue.IChunk; import com.fastasyncworldedit.core.queue.IChunkGet; import com.fastasyncworldedit.core.queue.IChunkSet; +import com.fastasyncworldedit.core.queue.IQueueExtent; import com.fastasyncworldedit.core.util.NbtUtils; import com.sk89q.worldedit.bukkit.WorldEditPlugin; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; @@ -254,7 +256,7 @@ public int[] getHeightMap(HeightMapType type) { } @Override - public > T call(IChunkSet set, Runnable finalize) { + public > T call(IQueueExtent owner, IChunkSet set, Runnable finalize) { return null; } 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 99f52e4feb..706e854e98 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 @@ -57,7 +57,6 @@ import net.minecraft.world.level.chunk.SingleValuePalette; import net.minecraft.world.level.entity.PersistentEntitySectionManager; import org.apache.logging.log4j.Logger; -import org.bukkit.Bukkit; import org.bukkit.craftbukkit.v1_20_R2.CraftChunk; import javax.annotation.Nonnull; @@ -79,9 +78,8 @@ import java.util.Map; import java.util.Optional; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import java.util.function.IntFunction; import static java.lang.invoke.MethodType.methodType; @@ -276,12 +274,49 @@ static DelegateSemaphore applyLock(LevelChunkSection section) { } } } catch (Throwable e) { - e.printStackTrace(); + LOGGER.error("Error apply DelegateSemaphore", e); throw new RuntimeException(e); } } - public static LevelChunk ensureLoaded(ServerLevel serverLevel, int chunkX, int chunkZ) { + public static CompletableFuture ensureLoaded(ServerLevel serverLevel, int chunkX, int chunkZ) { + LevelChunk levelChunk = getChunkImmediatelyAsync(serverLevel, chunkX, chunkZ); + if (levelChunk != null) { + return CompletableFuture.completedFuture(levelChunk); + } + if (PaperLib.isPaper()) { + CompletableFuture future = serverLevel + .getWorld() + .getChunkAtAsync(chunkX, chunkZ, true, true) + .thenApply(chunk -> { + addTicket(serverLevel, chunkX, chunkZ); + try { + return (LevelChunk) CRAFT_CHUNK_GET_HANDLE.invoke(chunk); + } catch (Throwable e) { + LOGGER.error("Could not asynchronously load chunk at {},{}", chunkX, chunkZ, e); + return null; + } + }); + try { + if (!future.isCompletedExceptionally() || (future.isDone() && future.get() != null)) { + return future; + } + Throwable t = future.exceptionNow(); + LOGGER.error("Asynchronous chunk load at {},{} exceptionally completed immediately", chunkX, chunkZ, t); + } catch (InterruptedException | ExecutionException e) { + LOGGER.error( + "Unexpected error when getting completed future at chunk {},{}. Returning to default.", + chunkX, + chunkZ, + e + ); + } + } + return CompletableFuture.supplyAsync(() -> TaskManager.taskManager().sync(() -> serverLevel.getChunk(chunkX, chunkZ))); + } + + + public static @Nullable LevelChunk getChunkImmediatelyAsync(ServerLevel serverLevel, int chunkX, int chunkZ) { if (!PaperLib.isPaper()) { LevelChunk nmsChunk = serverLevel.getChunkSource().getChunk(chunkX, chunkZ, false); if (nmsChunk != null) { @@ -290,6 +325,7 @@ public static LevelChunk ensureLoaded(ServerLevel serverLevel, int chunkX, int c if (Fawe.isMainThread()) { return serverLevel.getChunk(chunkX, chunkZ); } + return null; } else { LevelChunk nmsChunk = serverLevel.getChunkSource().getChunkAtIfCachedImmediately(chunkX, chunkZ); if (nmsChunk != null) { @@ -305,30 +341,8 @@ public static LevelChunk ensureLoaded(ServerLevel serverLevel, int chunkX, int c if (Fawe.isMainThread()) { return serverLevel.getChunk(chunkX, chunkZ); } - CompletableFuture future = serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true); - try { - CraftChunk chunk; - try { - chunk = (CraftChunk) future.get(10, TimeUnit.SECONDS); - } catch (TimeoutException e) { - String world = serverLevel.getWorld().getName(); - // We've already taken 10 seconds we can afford to wait a little here. - boolean loaded = TaskManager.taskManager().sync(() -> Bukkit.getWorld(world) != null); - if (loaded) { - LOGGER.warn("Chunk {},{} failed to load in 10 seconds in world {}. Retrying...", chunkX, chunkZ, world); - // Retry chunk load - chunk = (CraftChunk) serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true).get(); - } else { - throw new UnsupportedOperationException("Cannot load chunk from unloaded world " + world + "!"); - } - } - addTicket(serverLevel, chunkX, chunkZ); - return (LevelChunk) CRAFT_CHUNK_GET_HANDLE.invoke(chunk); - } catch (Throwable e) { - e.printStackTrace(); - } + return null; } - return TaskManager.taskManager().sync(() -> serverLevel.getChunk(chunkX, chunkZ)); } private static void addTicket(ServerLevel serverLevel, int chunkX, int chunkZ) { @@ -672,7 +686,7 @@ static void removeBeacon(BlockEntity beacon, LevelChunk levelChunk) { } methodremoveTickingBlockEntity.invoke(levelChunk, beacon.getBlockPos()); } catch (Throwable throwable) { - throwable.printStackTrace(); + LOGGER.error("Error removing beacon", throwable); } } diff --git a/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightGetBlocks.java b/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightGetBlocks.java index 3ac1bb7bbf..3941de6082 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightGetBlocks.java +++ b/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightGetBlocks.java @@ -1,18 +1,15 @@ package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R3; -import com.fastasyncworldedit.bukkit.adapter.BukkitGetBlocks; +import com.fastasyncworldedit.bukkit.adapter.AbstractBukkitGetBlocks; import com.fastasyncworldedit.bukkit.adapter.DelegateSemaphore; -import com.fastasyncworldedit.core.Fawe; import com.fastasyncworldedit.core.FaweCache; import com.fastasyncworldedit.core.configuration.Settings; import com.fastasyncworldedit.core.extent.processor.heightmap.HeightMapType; +import com.fastasyncworldedit.core.internal.exception.FaweException; 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; -import com.fastasyncworldedit.core.queue.implementation.QueueHandler; -import com.fastasyncworldedit.core.queue.implementation.blocks.CharGetBlocks; import com.fastasyncworldedit.core.util.MathMan; import com.fastasyncworldedit.core.util.NbtUtils; import com.fastasyncworldedit.core.util.collection.AdaptedMap; @@ -22,6 +19,7 @@ import com.sk89q.worldedit.internal.util.LogManagerCompat; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.util.SideEffect; +import com.sk89q.worldedit.util.formatting.text.TextComponent; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.biome.BiomeTypes; import com.sk89q.worldedit.world.block.BlockTypesCache; @@ -81,18 +79,17 @@ import java.util.Map; import java.util.Set; import java.util.UUID; -import java.util.concurrent.Callable; -import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.Semaphore; import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.Function; import static net.minecraft.core.registries.Registries.BIOME; -public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBlocks { +public class PaperweightGetBlocks extends AbstractBukkitGetBlocks { private static final Logger LOGGER = LogManagerCompat.getLogger(); @@ -104,84 +101,25 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc .getInstance() .getBukkitImplAdapter()); private final ReadWriteLock sectionLock = new ReentrantReadWriteLock(); - private final ReentrantLock callLock = new ReentrantLock(); - 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; - private final int maxSectionPosition; private final Registry biomeRegistry; private final IdMap> biomeHolderIdMap; - private final ConcurrentHashMap copies = new ConcurrentHashMap<>(); private final Object sendLock = new Object(); - private LevelChunkSection[] sections; private LevelChunk levelChunk; + private LevelChunkSection[] sections; private DataLayer[] blockLight; private DataLayer[] skyLight; - private boolean createCopy = false; - private boolean forceLoadSections = true; private boolean lightUpdate = false; - private int copyKey = 0; public PaperweightGetBlocks(World world, int chunkX, int chunkZ) { this(((CraftWorld) world).getHandle(), chunkX, chunkZ); } public PaperweightGetBlocks(ServerLevel serverLevel, int chunkX, int chunkZ) { - super(serverLevel.getMinBuildHeight() >> 4, (serverLevel.getMaxBuildHeight() - 1) >> 4); - this.serverLevel = serverLevel; - this.chunkX = chunkX; - this.chunkZ = chunkZ; - this.minHeight = serverLevel.getMinBuildHeight(); - this.maxHeight = serverLevel.getMaxBuildHeight() - 1; // Minecraft max limit is exclusive. - this.minSectionPosition = minHeight >> 4; - this.maxSectionPosition = maxHeight >> 4; + super(serverLevel, chunkX, chunkZ, serverLevel.getMinBuildHeight(), serverLevel.getMaxBuildHeight() - 1); this.skyLight = new DataLayer[getSectionCount()]; this.blockLight = new DataLayer[getSectionCount()]; this.biomeRegistry = serverLevel.registryAccess().registryOrThrow(BIOME); this.biomeHolderIdMap = biomeRegistry.asHolderIdMap(); - this.chunkPos = new IntPair(chunkX, chunkZ); - } - - public int getChunkX() { - return chunkX; - } - - public int getChunkZ() { - return chunkZ; - } - - @Override - public boolean isCreateCopy() { - return createCopy; - } - - @Override - public int setCreateCopy(boolean createCopy) { - if (!callLock.isHeldByCurrentThread()) { - throw new IllegalStateException("Attempting to set if chunk GET should create copy, but it is not call-locked."); - } - this.createCopy = createCopy; - // Increment regardless of whether copy will be created or not to return null from getCopy() - return ++this.copyKey; - } - - @Override - public IChunkGet getCopy(final int key) { - return copies.remove(key); - } - - @Override - public void lockCall() { - this.callLock.lock(); - } - - @Override - public void unlockCall() { - this.callLock.unlock(); } @Override @@ -191,7 +129,7 @@ public void setLightingToGet(char[][] light, int minSectionPosition, int maxSect try { fillLightNibble(light, LightLayer.BLOCK, minSectionPosition, maxSectionPosition); } catch (Throwable e) { - e.printStackTrace(); + LOGGER.error("Error setting lighting to get", e); } } } @@ -203,7 +141,7 @@ public void setSkyLightingToGet(char[][] light, int minSectionPosition, int maxS try { fillLightNibble(light, LightLayer.SKY, minSectionPosition, maxSectionPosition); } catch (Throwable e) { - e.printStackTrace(); + LOGGER.error("Error setting sky lighting to get", e); } } } @@ -218,16 +156,6 @@ public void setHeightmapToGet(HeightMapType type, int[] data) { heightMap.setRawData(getChunk(), nativeType, bitArray.getData()); } - @Override - public int getMaxY() { - return maxHeight; - } - - @Override - public int getMinY() { - return minHeight; - } - @Override public BiomeType getBiomeType(int x, int y, int z) { LevelChunkSection section = getSections(false)[(y >> 4) - getMinSectionPosition()]; @@ -346,7 +274,7 @@ public int[] getHeightMap(HeightMapType type) { @Override public @Nullable FaweCompoundTag entity(final UUID uuid) { - ensureLoaded(serverLevel, chunkX, chunkZ); + ensureLoaded(serverLevel); List entities = PaperweightPlatformAdapter.getEntities(getChunk()); Entity entity = null; for (Entity e : entities) { @@ -369,7 +297,7 @@ public int[] getHeightMap(HeightMapType type) { @Override public Collection entities() { - ensureLoaded(serverLevel, chunkX, chunkZ); + ensureLoaded(serverLevel); List entities = PaperweightPlatformAdapter.getEntities(getChunk()); if (entities.isEmpty()) { return Collections.emptyList(); @@ -418,18 +346,19 @@ private void removeEntity(Entity entity) { entity.discard(); } - public LevelChunk ensureLoaded(ServerLevel nmsWorld, int chunkX, int chunkZ) { + @Override + public CompletableFuture ensureLoaded(ServerLevel nmsWorld) { return PaperweightPlatformAdapter.ensureLoaded(nmsWorld, chunkX, chunkZ); } @Override - @SuppressWarnings("rawtypes") - public synchronized > T call(IChunkSet set, Runnable finalizer) { - if (!callLock.isHeldByCurrentThread()) { - throw new IllegalStateException("Attempted to call chunk GET but chunk was not call-locked."); - } - forceLoadSections = false; - LevelChunk nmsChunk = ensureLoaded(serverLevel, chunkX, chunkZ); + protected > T internalCall( + IChunkSet set, + Runnable finalizer, + int copyKey, + LevelChunk nmsChunk, + ServerLevel nmsWorld + ) throws Exception { PaperweightGetBlocks_Copy copy = createCopy ? new PaperweightGetBlocks_Copy(nmsChunk) : null; if (createCopy) { if (copies.containsKey(copyKey)) { @@ -437,464 +366,422 @@ public synchronized > T call(IChunkSet set, Runnable finaliz } copies.put(copyKey, copy); } - try { - // Remove existing tiles. Create a copy so that we can remove blocks - Map chunkTiles = new HashMap<>(nmsChunk.getBlockEntities()); - List beacons = null; - if (!chunkTiles.isEmpty()) { - for (Map.Entry entry : chunkTiles.entrySet()) { - final BlockPos pos = entry.getKey(); - final int lx = pos.getX() & 15; - final int ly = pos.getY(); - final int lz = pos.getZ() & 15; - final int layer = ly >> 4; - if (!set.hasSection(layer)) { - continue; - } + // Remove existing tiles. Create a copy so that we can remove blocks + Map chunkTiles = new HashMap<>(nmsChunk.getBlockEntities()); + List beacons = null; + if (!chunkTiles.isEmpty()) { + for (Map.Entry entry : chunkTiles.entrySet()) { + final BlockPos pos = entry.getKey(); + final int lx = pos.getX() & 15; + final int ly = pos.getY(); + final int lz = pos.getZ() & 15; + final int layer = ly >> 4; + if (!set.hasSection(layer)) { + continue; + } - int ordinal = set.getBlock(lx, ly, lz).getOrdinal(); - if (ordinal != BlockTypesCache.ReservedIDs.__RESERVED__) { - BlockEntity tile = entry.getValue(); - if (PaperLib.isPaper() && tile instanceof BeaconBlockEntity) { - if (beacons == null) { - beacons = new ArrayList<>(); - } - beacons.add(tile); - PaperweightPlatformAdapter.removeBeacon(tile, nmsChunk); - continue; - } - nmsChunk.removeBlockEntity(tile.getBlockPos()); - if (createCopy) { - copy.storeTile(tile); + int ordinal = set.getBlock(lx, ly, lz).getOrdinal(); + if (ordinal != BlockTypesCache.ReservedIDs.__RESERVED__) { + BlockEntity tile = entry.getValue(); + if (PaperLib.isPaper() && tile instanceof BeaconBlockEntity) { + if (beacons == null) { + beacons = new ArrayList<>(); } + beacons.add(tile); + PaperweightPlatformAdapter.removeBeacon(tile, nmsChunk); + continue; + } + nmsChunk.removeBlockEntity(tile.getBlockPos()); + if (createCopy) { + copy.storeTile(tile); } } } - final BiomeType[][] biomes = set.getBiomes(); + } + final BiomeType[][] biomes = set.getBiomes(); - int bitMask = 0; - synchronized (nmsChunk) { - LevelChunkSection[] levelChunkSections = nmsChunk.getSections(); + int bitMask = 0; + synchronized (nmsChunk) { + LevelChunkSection[] levelChunkSections = nmsChunk.getSections(); - for (int layerNo = getMinSectionPosition(); layerNo <= getMaxSectionPosition(); layerNo++) { + for (int layerNo = getMinSectionPosition(); layerNo <= getMaxSectionPosition(); layerNo++) { - int getSectionIndex = layerNo - getMinSectionPosition(); - int setSectionIndex = layerNo - set.getMinSectionPosition(); + int getSectionIndex = layerNo - getMinSectionPosition(); + int setSectionIndex = layerNo - set.getMinSectionPosition(); - if (!set.hasSection(layerNo)) { - // No blocks, but might be biomes present. Handle this lazily. - if (biomes == null) { - continue; - } - if (layerNo < set.getMinSectionPosition() || layerNo > set.getMaxSectionPosition()) { - continue; - } - if (biomes[setSectionIndex] != null) { - synchronized (super.sectionLocks[getSectionIndex]) { - LevelChunkSection existingSection = levelChunkSections[getSectionIndex]; - if (createCopy && existingSection != null) { - copy.storeBiomes(getSectionIndex, existingSection.getBiomes()); - } + if (!set.hasSection(layerNo)) { + // No blocks, but might be biomes present. Handle this lazily. + if (biomes == null) { + continue; + } + if (layerNo < set.getMinSectionPosition() || layerNo > set.getMaxSectionPosition()) { + continue; + } + if (biomes[setSectionIndex] != null) { + synchronized (super.sectionLocks[getSectionIndex]) { + LevelChunkSection existingSection = levelChunkSections[getSectionIndex]; + if (createCopy && existingSection != null) { + copy.storeBiomes(getSectionIndex, existingSection.getBiomes()); + } - if (existingSection == null) { - PalettedContainer> biomeData = PaperweightPlatformAdapter.getBiomePalettedContainer( - biomes[setSectionIndex], - biomeHolderIdMap - ); - LevelChunkSection newSection = PaperweightPlatformAdapter.newChunkSection( - layerNo, - new char[4096], - adapter, - biomeRegistry, - biomeData - ); - if (PaperweightPlatformAdapter.setSectionAtomic( - serverLevel.getWorld().getName(), - chunkPos, - levelChunkSections, - null, - newSection, - getSectionIndex - )) { - updateGet(nmsChunk, levelChunkSections, newSection, new char[4096], getSectionIndex); - continue; - } else { - existingSection = levelChunkSections[getSectionIndex]; - if (existingSection == null) { - LOGGER.error("Skipping invalid null section. chunk: {}, {} layer: {}", chunkX, chunkZ, - getSectionIndex - ); - continue; - } - } + if (existingSection == null) { + PalettedContainer> biomeData = PaperweightPlatformAdapter.getBiomePalettedContainer( + biomes[setSectionIndex], + biomeHolderIdMap + ); + LevelChunkSection newSection = PaperweightPlatformAdapter.newChunkSection( + layerNo, + new char[4096], + adapter, + biomeRegistry, + biomeData + ); + if (PaperweightPlatformAdapter.setSectionAtomic( + nmsWorld.getWorld().getName(), + chunkPos, + levelChunkSections, + null, + newSection, + getSectionIndex + )) { + updateGet(nmsChunk, levelChunkSections, newSection, new char[4096], getSectionIndex); + continue; } else { - PalettedContainer> paletteBiomes = setBiomesToPalettedContainer( - biomes, - setSectionIndex, - existingSection.getBiomes() - ); - if (paletteBiomes != null) { - PaperweightPlatformAdapter.setBiomesToChunkSection(existingSection, paletteBiomes); + existingSection = levelChunkSections[getSectionIndex]; + if (existingSection == null) { + LOGGER.error( + "Skipping invalid null section. chunk: {}, {} layer: {}", + chunkX, + chunkZ, + getSectionIndex + ); + continue; } } + } else { + PalettedContainer> paletteBiomes = setBiomesToPalettedContainer( + biomes, + setSectionIndex, + existingSection.getBiomes() + ); + if (paletteBiomes != null) { + PaperweightPlatformAdapter.setBiomesToChunkSection(existingSection, paletteBiomes); + } } } - continue; } + continue; + } - bitMask |= 1 << getSectionIndex; - - // setArr is modified by PaperweightPlatformAdapter#newChunkSection. This is in order to write changes to - // this chunk GET when #updateGet is called. Future dords, please listen this time. - char[] tmp = set.load(layerNo); - char[] setArr = new char[tmp.length]; - System.arraycopy(tmp, 0, setArr, 0, tmp.length); - - // synchronise on internal section to avoid circular locking with a continuing edit if the chunk was - // submitted to keep loaded internal chunks to queue target size. - synchronized (super.sectionLocks[getSectionIndex]) { - - LevelChunkSection newSection; - LevelChunkSection existingSection = levelChunkSections[getSectionIndex]; - // Don't attempt to tick section whilst we're editing - if (existingSection != null) { - PaperweightPlatformAdapter.clearCounts(existingSection); - if (PaperLib.isPaper()) { - existingSection.tickingList.clear(); - } + bitMask |= 1 << getSectionIndex; + + // setArr is modified by PaperweightPlatformAdapter#newChunkSection. This is in order to write changes to + // this chunk GET when #updateGet is called. Future dords, please listen this time. + char[] tmp = set.load(layerNo); + char[] setArr = new char[tmp.length]; + System.arraycopy(tmp, 0, setArr, 0, tmp.length); + + // synchronise on internal section to avoid circular locking with a continuing edit if the chunk was + // submitted to keep loaded internal chunks to queue target size. + synchronized (super.sectionLocks[getSectionIndex]) { + + LevelChunkSection newSection; + LevelChunkSection existingSection = levelChunkSections[getSectionIndex]; + // Don't attempt to tick section whilst we're editing + if (existingSection != null) { + PaperweightPlatformAdapter.clearCounts(existingSection); + if (PaperLib.isPaper()) { + existingSection.tickingList.clear(); } + } - if (createCopy) { - char[] tmpLoad = loadPrivately(layerNo); - char[] copyArr = new char[4096]; - System.arraycopy(tmpLoad, 0, copyArr, 0, 4096); - copy.storeSection(getSectionIndex, copyArr); - if (biomes != null && existingSection != null) { - copy.storeBiomes(getSectionIndex, existingSection.getBiomes()); - } + if (createCopy) { + char[] tmpLoad = loadPrivately(layerNo); + char[] copyArr = new char[4096]; + System.arraycopy(tmpLoad, 0, copyArr, 0, 4096); + copy.storeSection(getSectionIndex, copyArr); + if (biomes != null && existingSection != null) { + copy.storeBiomes(getSectionIndex, existingSection.getBiomes()); } + } - if (existingSection == null) { - PalettedContainer> biomeData = biomes == null ? new PalettedContainer<>( - biomeHolderIdMap, - biomeHolderIdMap.byIdOrThrow(adapter.getInternalBiomeId(BiomeTypes.PLAINS)), - PalettedContainer.Strategy.SECTION_BIOMES - ) : PaperweightPlatformAdapter.getBiomePalettedContainer(biomes[setSectionIndex], biomeHolderIdMap); - newSection = PaperweightPlatformAdapter.newChunkSection( - layerNo, - setArr, - adapter, - biomeRegistry, - biomeData - ); - if (PaperweightPlatformAdapter.setSectionAtomic( - serverLevel.getWorld().getName(), - chunkPos, - levelChunkSections, - null, - newSection, - getSectionIndex - )) { - updateGet(nmsChunk, levelChunkSections, newSection, setArr, getSectionIndex); + if (existingSection == null) { + PalettedContainer> biomeData = biomes == null ? new PalettedContainer<>( + biomeHolderIdMap, + biomeHolderIdMap.byIdOrThrow(adapter.getInternalBiomeId(BiomeTypes.PLAINS)), + PalettedContainer.Strategy.SECTION_BIOMES + ) : PaperweightPlatformAdapter.getBiomePalettedContainer(biomes[setSectionIndex], biomeHolderIdMap); + newSection = PaperweightPlatformAdapter.newChunkSection( + layerNo, + setArr, + adapter, + biomeRegistry, + biomeData + ); + if (PaperweightPlatformAdapter.setSectionAtomic( + nmsWorld.getWorld().getName(), + chunkPos, + levelChunkSections, + null, + newSection, + getSectionIndex + )) { + updateGet(nmsChunk, levelChunkSections, newSection, setArr, getSectionIndex); + continue; + } else { + existingSection = levelChunkSections[getSectionIndex]; + if (existingSection == null) { + LOGGER.error( + "Skipping invalid null section. chunk: {}, {} layer: {}", + chunkX, + chunkZ, + getSectionIndex + ); continue; - } else { - existingSection = levelChunkSections[getSectionIndex]; - if (existingSection == null) { - LOGGER.error("Skipping invalid null section. chunk: {}, {} layer: {}", chunkX, chunkZ, - getSectionIndex - ); - continue; - } } } + } - //ensure that the server doesn't try to tick the chunksection while we're editing it. (Again) - PaperweightPlatformAdapter.clearCounts(existingSection); - if (PaperLib.isPaper()) { - existingSection.tickingList.clear(); - } - DelegateSemaphore lock = PaperweightPlatformAdapter.applyLock(existingSection); - - // Synchronize to prevent further acquisitions - synchronized (lock) { - lock.acquire(); // Wait until we have the lock - lock.release(); - try { - sectionLock.writeLock().lock(); - if (this.getChunk() != nmsChunk) { - this.levelChunk = nmsChunk; - this.sections = null; - this.reset(); - } else if (existingSection != getSections(false)[getSectionIndex]) { - this.sections[getSectionIndex] = existingSection; - this.reset(); - } else if (!Arrays.equals( - update(getSectionIndex, new char[4096], true), - loadPrivately(layerNo) - )) { - this.reset(layerNo); + //ensure that the server doesn't try to tick the chunksection while we're editing it. (Again) + PaperweightPlatformAdapter.clearCounts(existingSection); + if (PaperLib.isPaper()) { + existingSection.tickingList.clear(); + } + DelegateSemaphore lock = PaperweightPlatformAdapter.applyLock(existingSection); + // Synchronize to prevent further acquisitions + synchronized (lock) { + lock.acquire(); // Wait until we have the lock + lock.release(); + try { + sectionLock.writeLock().lock(); + if (this.getChunk() != nmsChunk) { + this.levelChunk = nmsChunk; + this.sections = null; + this.reset(); + } else if (existingSection != getSections(false)[getSectionIndex]) { + this.sections[getSectionIndex] = existingSection; + this.reset(); + } else if (!Arrays.equals(update(getSectionIndex, new char[4096], true), loadPrivately(layerNo))) { + this.reset(layerNo); /*} else if (lock.isModified()) { this.reset(layerNo);*/ - } - } finally { - sectionLock.writeLock().unlock(); } + } finally { + sectionLock.writeLock().unlock(); + } - PalettedContainer> biomeData = setBiomesToPalettedContainer( - biomes, - setSectionIndex, - existingSection.getBiomes() - ); + PalettedContainer> biomeData = setBiomesToPalettedContainer( + biomes, + setSectionIndex, + existingSection.getBiomes() + ); - newSection = PaperweightPlatformAdapter.newChunkSection( - layerNo, - this::loadPrivately, - setArr, - adapter, - biomeRegistry, - biomeData != null ? biomeData : (PalettedContainer>) existingSection.getBiomes() - ); - if (!PaperweightPlatformAdapter.setSectionAtomic( - serverLevel.getWorld().getName(), - chunkPos, - levelChunkSections, - existingSection, - newSection, + newSection = PaperweightPlatformAdapter.newChunkSection( + layerNo, + this::loadPrivately, + setArr, + adapter, + biomeRegistry, + biomeData != null ? biomeData : (PalettedContainer>) existingSection.getBiomes() + ); + if (!PaperweightPlatformAdapter.setSectionAtomic( + nmsWorld.getWorld().getName(), + chunkPos, + levelChunkSections, + existingSection, + newSection, + getSectionIndex + )) { + LOGGER.error( + "Skipping invalid null section. chunk: {}, {} layer: {}", + chunkX, + chunkZ, getSectionIndex - )) { - LOGGER.error("Skipping invalid null section. chunk: {}, {} layer: {}", chunkX, chunkZ, - getSectionIndex - ); - } else { - updateGet(nmsChunk, levelChunkSections, newSection, setArr, getSectionIndex); - } + ); + } else { + updateGet(nmsChunk, levelChunkSections, newSection, setArr, getSectionIndex); } } } + } - Map heightMaps = set.getHeightMaps(); - for (Map.Entry entry : heightMaps.entrySet()) { - PaperweightGetBlocks.this.setHeightmapToGet(entry.getKey(), entry.getValue()); - } - PaperweightGetBlocks.this.setLightingToGet( - set.getLight(), - set.getMinSectionPosition(), - set.getMaxSectionPosition() - ); - PaperweightGetBlocks.this.setSkyLightingToGet( - set.getSkyLight(), - set.getMinSectionPosition(), - set.getMaxSectionPosition() - ); - - Runnable[] syncTasks = null; + Map heightMaps = set.getHeightMaps(); + for (Map.Entry entry : heightMaps.entrySet()) { + PaperweightGetBlocks.this.setHeightmapToGet(entry.getKey(), entry.getValue()); + } + PaperweightGetBlocks.this.setLightingToGet(set.getLight(), set.getMinSectionPosition(), set.getMaxSectionPosition()); + PaperweightGetBlocks.this.setSkyLightingToGet( + set.getSkyLight(), + set.getMinSectionPosition(), + set.getMaxSectionPosition() + ); - int bx = chunkX << 4; - int bz = chunkZ << 4; + Runnable[] syncTasks = null; - // Call beacon deactivate events here synchronously - // list will be null on spigot, so this is an implicit isPaper check - if (beacons != null && !beacons.isEmpty()) { - final List finalBeacons = beacons; + int bx = chunkX << 4; + int bz = chunkZ << 4; - syncTasks = new Runnable[4]; + // Call beacon deactivate events here synchronously + // list will be null on spigot, so this is an implicit isPaper check + if (beacons != null && !beacons.isEmpty()) { + final List finalBeacons = beacons; - syncTasks[3] = () -> { - for (BlockEntity beacon : finalBeacons) { - BeaconBlockEntity.playSound(beacon.getLevel(), beacon.getBlockPos(), SoundEvents.BEACON_DEACTIVATE); - new BeaconDeactivatedEvent(CraftBlock.at(beacon.getLevel(), beacon.getBlockPos())).callEvent(); - } - }; - } + syncTasks = new Runnable[4]; - Set entityRemoves = set.getEntityRemoves(); - if (entityRemoves != null && !entityRemoves.isEmpty()) { - if (syncTasks == null) { - syncTasks = new Runnable[3]; + syncTasks[3] = () -> { + for (BlockEntity beacon : finalBeacons) { + BeaconBlockEntity.playSound(beacon.getLevel(), beacon.getBlockPos(), SoundEvents.BEACON_DEACTIVATE); + new BeaconDeactivatedEvent(CraftBlock.at(beacon.getLevel(), beacon.getBlockPos())).callEvent(); } + }; + } - syncTasks[2] = () -> { - Set entitiesRemoved = new HashSet<>(); - final List entities = PaperweightPlatformAdapter.getEntities(nmsChunk); + Set entityRemoves = set.getEntityRemoves(); + if (entityRemoves != null && !entityRemoves.isEmpty()) { + if (syncTasks == null) { + syncTasks = new Runnable[3]; + } - for (Entity entity : entities) { - UUID uuid = entity.getUUID(); - if (entityRemoves.contains(uuid)) { - if (createCopy) { - copy.storeEntity(entity); - } - removeEntity(entity); - entitiesRemoved.add(uuid); - entityRemoves.remove(uuid); + syncTasks[2] = () -> { + Set entitiesRemoved = new HashSet<>(); + final List entities = PaperweightPlatformAdapter.getEntities(nmsChunk); + + for (Entity entity : entities) { + UUID uuid = entity.getUUID(); + if (entityRemoves.contains(uuid)) { + if (createCopy) { + copy.storeEntity(entity); } + removeEntity(entity); + entitiesRemoved.add(uuid); + entityRemoves.remove(uuid); } - if (Settings.settings().EXPERIMENTAL.REMOVE_ENTITY_FROM_WORLD_ON_CHUNK_FAIL) { - for (UUID uuid : entityRemoves) { - Entity entity = serverLevel.getEntities().get(uuid); - if (entity != null) { - removeEntity(entity); - } + } + if (Settings.settings().EXPERIMENTAL.REMOVE_ENTITY_FROM_WORLD_ON_CHUNK_FAIL) { + for (UUID uuid : entityRemoves) { + Entity entity = nmsWorld.getEntities().get(uuid); + if (entity != null) { + removeEntity(entity); } } - // Only save entities that were actually removed to history - set.getEntityRemoves().clear(); - set.getEntityRemoves().addAll(entitiesRemoved); - }; - } - - Collection entities = set.entities(); - if (entities != null && !entities.isEmpty()) { - if (syncTasks == null) { - syncTasks = new Runnable[2]; } + // Only save entities that were actually removed to history + set.getEntityRemoves().clear(); + set.getEntityRemoves().addAll(entitiesRemoved); + }; + } - syncTasks[1] = () -> { - Iterator iterator = entities.iterator(); - while (iterator.hasNext()) { - final FaweCompoundTag nativeTag = iterator.next(); - final LinCompoundTag linTag = nativeTag.linTag(); - final LinStringTag idTag = linTag.findTag("Id", LinTagType.stringTag()); - final LinListTag posTag = linTag.findListTag("Pos", LinTagType.doubleTag()); - final LinListTag rotTag = linTag.findListTag("Rotation", LinTagType.floatTag()); - if (idTag == null || posTag == null || rotTag == null) { - LOGGER.error("Unknown entity tag: {}", nativeTag); - continue; - } - final double x = posTag.get(0).valueAsDouble(); - final double y = posTag.get(1).valueAsDouble(); - final double z = posTag.get(2).valueAsDouble(); - final float yaw = rotTag.get(0).valueAsFloat(); - final float pitch = rotTag.get(1).valueAsFloat(); - final String id = idTag.value(); - - EntityType type = EntityType.byString(id).orElse(null); - if (type != null) { - 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) { - tag.remove(name); - } - entity.load(tag); - entity.absMoveTo(x, y, z, yaw, pitch); - entity.setUUID(NbtUtils.uuid(nativeTag)); - if (!serverLevel.addFreshEntity(entity, CreatureSpawnEvent.SpawnReason.CUSTOM)) { - LOGGER.warn( - "Error creating entity of type `{}` in world `{}` at location `{},{},{}`", - id, - serverLevel.getWorld().getName(), - x, - y, - z - ); - // Unsuccessful create should not be saved to history - iterator.remove(); - } - } - } - } - }; + Collection entities = set.entities(); + if (entities != null && !entities.isEmpty()) { + if (syncTasks == null) { + syncTasks = new Runnable[2]; } - // set tiles - Map tiles = set.tiles(); - if (tiles != null && !tiles.isEmpty()) { - if (syncTasks == null) { - syncTasks = new Runnable[1]; - } - - syncTasks[0] = () -> { - for (final Map.Entry entry : tiles.entrySet()) { - final FaweCompoundTag nativeTag = entry.getValue(); - final BlockVector3 blockHash = entry.getKey(); - final int x = blockHash.x() + bx; - final int y = blockHash.y(); - final int z = blockHash.z() + bz; - final BlockPos pos = new BlockPos(x, y, z); - - synchronized (serverLevel) { - BlockEntity tileEntity = serverLevel.getBlockEntity(pos); - if (tileEntity == null || tileEntity.isRemoved()) { - serverLevel.removeBlockEntity(pos); - tileEntity = serverLevel.getBlockEntity(pos); + syncTasks[1] = () -> { + Iterator iterator = entities.iterator(); + while (iterator.hasNext()) { + final FaweCompoundTag nativeTag = iterator.next(); + final LinCompoundTag linTag = nativeTag.linTag(); + final LinStringTag idTag = linTag.findTag("Id", LinTagType.stringTag()); + final LinListTag posTag = linTag.findListTag("Pos", LinTagType.doubleTag()); + final LinListTag rotTag = linTag.findListTag("Rotation", LinTagType.floatTag()); + if (idTag == null || posTag == null || rotTag == null) { + LOGGER.error("Unknown entity tag: {}", nativeTag); + continue; + } + final double x = posTag.get(0).valueAsDouble(); + final double y = posTag.get(1).valueAsDouble(); + final double z = posTag.get(2).valueAsDouble(); + final float yaw = rotTag.get(0).valueAsFloat(); + final float pitch = rotTag.get(1).valueAsFloat(); + final String id = idTag.value(); + + EntityType type = EntityType.byString(id).orElse(null); + if (type != null) { + Entity entity = type.create(nmsWorld); + 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) { + tag.remove(name); } - if (tileEntity != null) { - final net.minecraft.nbt.CompoundTag tag = (CompoundTag) adapter.fromNativeLin(nativeTag.linTag()); - tag.put("x", IntTag.valueOf(x)); - tag.put("y", IntTag.valueOf(y)); - tag.put("z", IntTag.valueOf(z)); - tileEntity.load(tag); + entity.load(tag); + entity.absMoveTo(x, y, z, yaw, pitch); + entity.setUUID(NbtUtils.uuid(nativeTag)); + if (!nmsWorld.addFreshEntity(entity, CreatureSpawnEvent.SpawnReason.CUSTOM)) { + LOGGER.warn( + "Error creating entity of type `{}` in world `{}` at location `{},{},{}`", + id, + nmsWorld.getWorld().getName(), + x, + y, + z + ); + // Unsuccessful create should not be saved to history + iterator.remove(); } } } - }; - } + } + }; + } - Runnable callback; - if (bitMask == 0 && biomes == null && !lightUpdate) { - callback = null; - } else { - int finalMask = bitMask != 0 ? bitMask : lightUpdate ? set.getBitMask() : 0; - callback = () -> { - // Set Modified - nmsChunk.setLightCorrect(true); // Set Modified - nmsChunk.mustNotSave = false; - nmsChunk.setUnsaved(true); - // send to player - if (!set - .getSideEffectSet() - .shouldApply(SideEffect.LIGHTING) || !Settings.settings().LIGHTING.DELAY_PACKET_SENDING || finalMask == 0 && biomes != null) { - this.send(); - } - if (finalizer != null) { - finalizer.run(); - } - }; + // set tiles + Map tiles = set.tiles(); + if (tiles != null && !tiles.isEmpty()) { + if (syncTasks == null) { + syncTasks = new Runnable[1]; } - if (syncTasks != null) { - QueueHandler queueHandler = Fawe.instance().getQueueHandler(); - Runnable[] finalSyncTasks = syncTasks; - // Chain the sync tasks and the callback - Callable chain = () -> { - try { - // Run the sync tasks - for (Runnable task : finalSyncTasks) { - if (task != null) { - task.run(); - } + syncTasks[0] = () -> { + for (final Map.Entry entry : tiles.entrySet()) { + final FaweCompoundTag nativeTag = entry.getValue(); + final BlockVector3 blockHash = entry.getKey(); + final int x = blockHash.x() + bx; + final int y = blockHash.y(); + final int z = blockHash.z() + bz; + final BlockPos pos = new BlockPos(x, y, z); + + synchronized (nmsWorld) { + BlockEntity tileEntity = nmsWorld.getBlockEntity(pos); + if (tileEntity == null || tileEntity.isRemoved()) { + nmsWorld.removeBlockEntity(pos); + tileEntity = nmsWorld.getBlockEntity(pos); } - if (callback == null) { - if (finalizer != null) { - queueHandler.async(finalizer, null); - } - return null; - } else { - return queueHandler.async(callback, null); + if (tileEntity != null) { + final net.minecraft.nbt.CompoundTag tag = (CompoundTag) adapter.fromNativeLin(nativeTag.linTag()); + tag.put("x", IntTag.valueOf(x)); + tag.put("y", IntTag.valueOf(y)); + tag.put("z", IntTag.valueOf(z)); + tileEntity.load(tag); } - } catch (Throwable e) { - e.printStackTrace(); - throw e; - } - }; - //noinspection unchecked - required at compile time - return (T) (Future) queueHandler.sync(chain); - } else { - if (callback == null) { - if (finalizer != null) { - finalizer.run(); } - } else { - callback.run(); } - } + }; } - return null; - } catch (Throwable e) { - e.printStackTrace(); - return null; - } finally { - forceLoadSections = true; + + Runnable callback; + if (bitMask == 0 && biomes == null && !lightUpdate) { + callback = null; + } else { + int finalMask = bitMask != 0 ? bitMask : lightUpdate ? set.getBitMask() : 0; + callback = () -> { + // Set Modified + nmsChunk.setLightCorrect(true); // Set Modified + nmsChunk.mustNotSave = false; + nmsChunk.setUnsaved(true); + // send to player + if (!set + .getSideEffectSet() + .shouldApply(SideEffect.LIGHTING) || !Settings.settings().LIGHTING.DELAY_PACKET_SENDING || finalMask == 0 && biomes != null) { + this.send(); + } + if (finalizer != null) { + finalizer.run(); + } + }; + } + return handleCallFinalizer(syncTasks, callback, finalizer); } } @@ -927,21 +814,11 @@ private void updateGet( this.blocks[layer] = arr; } - private char[] loadPrivately(int layer) { - layer -= getMinSectionPosition(); - if (super.sections[layer] != null) { - synchronized (super.sectionLocks[layer]) { - if (super.sections[layer].isFull() && super.blocks[layer] != null) { - return super.blocks[layer]; - } - } - } - return PaperweightGetBlocks.this.update(layer, null, true); - } - @Override public void send() { - PaperweightPlatformAdapter.sendChunk(new IntPair(chunkX, chunkZ), serverLevel, chunkX, chunkZ); + synchronized (sendLock) { + PaperweightPlatformAdapter.sendChunk(new IntPair(chunkX, chunkZ), serverLevel, chunkX, chunkZ); + } } /** @@ -1034,7 +911,7 @@ public char[] update(int layer, char[] data, boolean aggressive) { } return data; } catch (IllegalAccessException | InterruptedException e) { - e.printStackTrace(); + LOGGER.error("Could not read block data from palette", e); throw new RuntimeException(e); } finally { lock.release(); @@ -1076,7 +953,16 @@ public LevelChunk getChunk() { synchronized (this) { levelChunk = this.levelChunk; if (levelChunk == null) { - this.levelChunk = levelChunk = ensureLoaded(this.serverLevel, chunkX, chunkZ); + try { + this.levelChunk = levelChunk = ensureLoaded(this.serverLevel).get(); + } catch (InterruptedException | ExecutionException e) { + LOGGER.error("Could not get chunk at {},{}", chunkX, chunkZ, e); + throw new FaweException( + TextComponent.of("Could not get chunk at " + chunkX + "," + chunkZ + ": " + e.getMessage()), + FaweException.Type.OTHER, + false + ); + } } } } diff --git a/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightGetBlocks_Copy.java b/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightGetBlocks_Copy.java index 1163c2d331..7abbbe5797 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightGetBlocks_Copy.java +++ b/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightGetBlocks_Copy.java @@ -3,8 +3,10 @@ import com.fastasyncworldedit.core.extent.processor.heightmap.HeightMapType; import com.fastasyncworldedit.core.nbt.FaweCompoundTag; import com.fastasyncworldedit.core.queue.IBlocks; +import com.fastasyncworldedit.core.queue.IChunk; import com.fastasyncworldedit.core.queue.IChunkGet; import com.fastasyncworldedit.core.queue.IChunkSet; +import com.fastasyncworldedit.core.queue.IQueueExtent; import com.fastasyncworldedit.core.util.NbtUtils; import com.sk89q.worldedit.bukkit.WorldEditPlugin; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; @@ -254,7 +256,7 @@ public int[] getHeightMap(HeightMapType type) { } @Override - public > T call(IChunkSet set, Runnable finalize) { + public > T call(IQueueExtent owner, IChunkSet set, Runnable finalize) { return null; } 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 1ff6819638..0cae77ede2 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 @@ -57,7 +57,6 @@ import net.minecraft.world.level.chunk.SingleValuePalette; import net.minecraft.world.level.entity.PersistentEntitySectionManager; import org.apache.logging.log4j.Logger; -import org.bukkit.Bukkit; import org.bukkit.craftbukkit.v1_20_R3.CraftChunk; import javax.annotation.Nonnull; @@ -79,9 +78,8 @@ import java.util.Map; import java.util.Optional; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import java.util.function.IntFunction; import static java.lang.invoke.MethodType.methodType; @@ -276,12 +274,48 @@ static DelegateSemaphore applyLock(LevelChunkSection section) { } } } catch (Throwable e) { - e.printStackTrace(); + LOGGER.error("Error apply DelegateSemaphore", e); throw new RuntimeException(e); } } - public static LevelChunk ensureLoaded(ServerLevel serverLevel, int chunkX, int chunkZ) { + public static CompletableFuture ensureLoaded(ServerLevel serverLevel, int chunkX, int chunkZ) { + LevelChunk levelChunk = getChunkImmediatelyAsync(serverLevel, chunkX, chunkZ); + if (levelChunk != null) { + return CompletableFuture.completedFuture(levelChunk); + } + if (PaperLib.isPaper()) { + CompletableFuture future = serverLevel + .getWorld() + .getChunkAtAsync(chunkX, chunkZ, true, true) + .thenApply(chunk -> { + addTicket(serverLevel, chunkX, chunkZ); + try { + return (LevelChunk) CRAFT_CHUNK_GET_HANDLE.invoke(chunk); + } catch (Throwable e) { + LOGGER.error("Could not asynchronously load chunk at {},{}", chunkX, chunkZ, e); + return null; + } + }); + try { + if (!future.isCompletedExceptionally() || (future.isDone() && future.get() != null)) { + return future; + } + Throwable t = future.exceptionNow(); + LOGGER.error("Asynchronous chunk load at {},{} exceptionally completed immediately", chunkX, chunkZ, t); + } catch (InterruptedException | ExecutionException e) { + LOGGER.error( + "Unexpected error when getting completed future at chunk {},{}. Returning to default.", + chunkX, + chunkZ, + e + ); + } + } + return CompletableFuture.supplyAsync(() -> TaskManager.taskManager().sync(() -> serverLevel.getChunk(chunkX, chunkZ))); + } + + public static @Nullable LevelChunk getChunkImmediatelyAsync(ServerLevel serverLevel, int chunkX, int chunkZ) { if (!PaperLib.isPaper()) { LevelChunk nmsChunk = serverLevel.getChunkSource().getChunk(chunkX, chunkZ, false); if (nmsChunk != null) { @@ -290,6 +324,7 @@ public static LevelChunk ensureLoaded(ServerLevel serverLevel, int chunkX, int c if (Fawe.isMainThread()) { return serverLevel.getChunk(chunkX, chunkZ); } + return null; } else { LevelChunk nmsChunk = serverLevel.getChunkSource().getChunkAtIfCachedImmediately(chunkX, chunkZ); if (nmsChunk != null) { @@ -305,30 +340,8 @@ public static LevelChunk ensureLoaded(ServerLevel serverLevel, int chunkX, int c if (Fawe.isMainThread()) { return serverLevel.getChunk(chunkX, chunkZ); } - CompletableFuture future = serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true); - try { - CraftChunk chunk; - try { - chunk = (CraftChunk) future.get(10, TimeUnit.SECONDS); - } catch (TimeoutException e) { - String world = serverLevel.getWorld().getName(); - // We've already taken 10 seconds we can afford to wait a little here. - boolean loaded = TaskManager.taskManager().sync(() -> Bukkit.getWorld(world) != null); - if (loaded) { - LOGGER.warn("Chunk {},{} failed to load in 10 seconds in world {}. Retrying...", chunkX, chunkZ, world); - // Retry chunk load - chunk = (CraftChunk) serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true).get(); - } else { - throw new UnsupportedOperationException("Cannot load chunk from unloaded world " + world + "!"); - } - } - addTicket(serverLevel, chunkX, chunkZ); - return (LevelChunk) CRAFT_CHUNK_GET_HANDLE.invoke(chunk); - } catch (Throwable e) { - e.printStackTrace(); - } + return null; } - return TaskManager.taskManager().sync(() -> serverLevel.getChunk(chunkX, chunkZ)); } private static void addTicket(ServerLevel serverLevel, int chunkX, int chunkZ) { @@ -672,7 +685,7 @@ static void removeBeacon(BlockEntity beacon, LevelChunk levelChunk) { } methodremoveTickingBlockEntity.invoke(levelChunk, beacon.getBlockPos()); } catch (Throwable throwable) { - throwable.printStackTrace(); + LOGGER.error("Error removing beacon", throwable); } } diff --git a/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightGetBlocks.java b/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightGetBlocks.java index 17d51add28..2be0f0c059 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightGetBlocks.java +++ b/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightGetBlocks.java @@ -1,18 +1,15 @@ package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R4; -import com.fastasyncworldedit.bukkit.adapter.BukkitGetBlocks; +import com.fastasyncworldedit.bukkit.adapter.AbstractBukkitGetBlocks; import com.fastasyncworldedit.bukkit.adapter.DelegateSemaphore; -import com.fastasyncworldedit.core.Fawe; import com.fastasyncworldedit.core.FaweCache; import com.fastasyncworldedit.core.configuration.Settings; import com.fastasyncworldedit.core.extent.processor.heightmap.HeightMapType; +import com.fastasyncworldedit.core.internal.exception.FaweException; 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; -import com.fastasyncworldedit.core.queue.implementation.QueueHandler; -import com.fastasyncworldedit.core.queue.implementation.blocks.CharGetBlocks; import com.fastasyncworldedit.core.util.MathMan; import com.fastasyncworldedit.core.util.NbtUtils; import com.fastasyncworldedit.core.util.collection.AdaptedMap; @@ -22,6 +19,7 @@ import com.sk89q.worldedit.internal.util.LogManagerCompat; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.util.SideEffect; +import com.sk89q.worldedit.util.formatting.text.TextComponent; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.biome.BiomeTypes; import com.sk89q.worldedit.world.block.BlockTypesCache; @@ -82,18 +80,17 @@ import java.util.Map; import java.util.Set; import java.util.UUID; -import java.util.concurrent.Callable; -import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.Semaphore; import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.Function; import static net.minecraft.core.registries.Registries.BIOME; -public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBlocks { +public class PaperweightGetBlocks extends AbstractBukkitGetBlocks { private static final Logger LOGGER = LogManagerCompat.getLogger(); @@ -105,84 +102,25 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc .getInstance() .getBukkitImplAdapter()); private final ReadWriteLock sectionLock = new ReentrantReadWriteLock(); - private final ReentrantLock callLock = new ReentrantLock(); - 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; - private final int maxSectionPosition; private final Registry biomeRegistry; private final IdMap> biomeHolderIdMap; - private final ConcurrentHashMap copies = new ConcurrentHashMap<>(); private final Object sendLock = new Object(); - private LevelChunkSection[] sections; private LevelChunk levelChunk; + private LevelChunkSection[] sections; private DataLayer[] blockLight; private DataLayer[] skyLight; - private boolean createCopy = false; - private boolean forceLoadSections = true; private boolean lightUpdate = false; - private int copyKey = 0; public PaperweightGetBlocks(World world, int chunkX, int chunkZ) { this(((CraftWorld) world).getHandle(), chunkX, chunkZ); } public PaperweightGetBlocks(ServerLevel serverLevel, int chunkX, int chunkZ) { - super(serverLevel.getMinBuildHeight() >> 4, (serverLevel.getMaxBuildHeight() - 1) >> 4); - this.serverLevel = serverLevel; - this.chunkX = chunkX; - this.chunkZ = chunkZ; - this.minHeight = serverLevel.getMinBuildHeight(); - this.maxHeight = serverLevel.getMaxBuildHeight() - 1; // Minecraft max limit is exclusive. - this.minSectionPosition = minHeight >> 4; - this.maxSectionPosition = maxHeight >> 4; + super(serverLevel, chunkX, chunkZ, serverLevel.getMinBuildHeight(), serverLevel.getMaxBuildHeight() - 1); this.skyLight = new DataLayer[getSectionCount()]; this.blockLight = new DataLayer[getSectionCount()]; this.biomeRegistry = serverLevel.registryAccess().registryOrThrow(BIOME); this.biomeHolderIdMap = biomeRegistry.asHolderIdMap(); - this.chunkPos = new IntPair(chunkX, chunkZ); - } - - public int getChunkX() { - return chunkX; - } - - public int getChunkZ() { - return chunkZ; - } - - @Override - public boolean isCreateCopy() { - return createCopy; - } - - @Override - public int setCreateCopy(boolean createCopy) { - if (!callLock.isHeldByCurrentThread()) { - throw new IllegalStateException("Attempting to set if chunk GET should create copy, but it is not call-locked."); - } - this.createCopy = createCopy; - // Increment regardless of whether copy will be created or not to return null from getCopy() - return ++this.copyKey; - } - - @Override - public IChunkGet getCopy(final int key) { - return copies.remove(key); - } - - @Override - public void lockCall() { - this.callLock.lock(); - } - - @Override - public void unlockCall() { - this.callLock.unlock(); } @Override @@ -192,7 +130,7 @@ public void setLightingToGet(char[][] light, int minSectionPosition, int maxSect try { fillLightNibble(light, LightLayer.BLOCK, minSectionPosition, maxSectionPosition); } catch (Throwable e) { - e.printStackTrace(); + LOGGER.error("Error setting lighting to get", e); } } } @@ -204,7 +142,7 @@ public void setSkyLightingToGet(char[][] light, int minSectionPosition, int maxS try { fillLightNibble(light, LightLayer.SKY, minSectionPosition, maxSectionPosition); } catch (Throwable e) { - e.printStackTrace(); + LOGGER.error("Error setting sky lighting to get", e); } } } @@ -219,16 +157,6 @@ public void setHeightmapToGet(HeightMapType type, int[] data) { heightMap.setRawData(getChunk(), nativeType, bitArray.getData()); } - @Override - public int getMaxY() { - return maxHeight; - } - - @Override - public int getMinY() { - return minHeight; - } - @Override public BiomeType getBiomeType(int x, int y, int z) { LevelChunkSection section = getSections(false)[(y >> 4) - getMinSectionPosition()]; @@ -347,7 +275,7 @@ public int[] getHeightMap(HeightMapType type) { @Override public @Nullable FaweCompoundTag entity(final UUID uuid) { - ensureLoaded(serverLevel, chunkX, chunkZ); + ensureLoaded(serverLevel); List entities = PaperweightPlatformAdapter.getEntities(getChunk()); Entity entity = null; for (Entity e : entities) { @@ -370,7 +298,7 @@ public int[] getHeightMap(HeightMapType type) { @Override public Collection entities() { - ensureLoaded(serverLevel, chunkX, chunkZ); + ensureLoaded(serverLevel); List entities = PaperweightPlatformAdapter.getEntities(getChunk()); if (entities.isEmpty()) { return Collections.emptyList(); @@ -419,18 +347,19 @@ private void removeEntity(Entity entity) { entity.discard(); } - public LevelChunk ensureLoaded(ServerLevel nmsWorld, int chunkX, int chunkZ) { + @Override + public CompletableFuture ensureLoaded(ServerLevel nmsWorld) { return PaperweightPlatformAdapter.ensureLoaded(nmsWorld, chunkX, chunkZ); } @Override - @SuppressWarnings("rawtypes") - public synchronized > T call(IChunkSet set, Runnable finalizer) { - if (!callLock.isHeldByCurrentThread()) { - throw new IllegalStateException("Attempted to call chunk GET but chunk was not call-locked."); - } - forceLoadSections = false; - LevelChunk nmsChunk = ensureLoaded(serverLevel, chunkX, chunkZ); + protected > T internalCall( + IChunkSet set, + Runnable finalizer, + int copyKey, + LevelChunk nmsChunk, + ServerLevel nmsWorld + ) throws Exception { PaperweightGetBlocks_Copy copy = createCopy ? new PaperweightGetBlocks_Copy(nmsChunk) : null; if (createCopy) { if (copies.containsKey(copyKey)) { @@ -438,464 +367,422 @@ public synchronized > T call(IChunkSet set, Runnable finaliz } copies.put(copyKey, copy); } - try { - // Remove existing tiles. Create a copy so that we can remove blocks - Map chunkTiles = new HashMap<>(nmsChunk.getBlockEntities()); - List beacons = null; - if (!chunkTiles.isEmpty()) { - for (Map.Entry entry : chunkTiles.entrySet()) { - final BlockPos pos = entry.getKey(); - final int lx = pos.getX() & 15; - final int ly = pos.getY(); - final int lz = pos.getZ() & 15; - final int layer = ly >> 4; - if (!set.hasSection(layer)) { - continue; - } + // Remove existing tiles. Create a copy so that we can remove blocks + Map chunkTiles = new HashMap<>(nmsChunk.getBlockEntities()); + List beacons = null; + if (!chunkTiles.isEmpty()) { + for (Map.Entry entry : chunkTiles.entrySet()) { + final BlockPos pos = entry.getKey(); + final int lx = pos.getX() & 15; + final int ly = pos.getY(); + final int lz = pos.getZ() & 15; + final int layer = ly >> 4; + if (!set.hasSection(layer)) { + continue; + } - int ordinal = set.getBlock(lx, ly, lz).getOrdinal(); - if (ordinal != BlockTypesCache.ReservedIDs.__RESERVED__) { - BlockEntity tile = entry.getValue(); - if (PaperLib.isPaper() && tile instanceof BeaconBlockEntity) { - if (beacons == null) { - beacons = new ArrayList<>(); - } - beacons.add(tile); - PaperweightPlatformAdapter.removeBeacon(tile, nmsChunk); - continue; - } - nmsChunk.removeBlockEntity(tile.getBlockPos()); - if (createCopy) { - copy.storeTile(tile); + int ordinal = set.getBlock(lx, ly, lz).getOrdinal(); + if (ordinal != BlockTypesCache.ReservedIDs.__RESERVED__) { + BlockEntity tile = entry.getValue(); + if (PaperLib.isPaper() && tile instanceof BeaconBlockEntity) { + if (beacons == null) { + beacons = new ArrayList<>(); } + beacons.add(tile); + PaperweightPlatformAdapter.removeBeacon(tile, nmsChunk); + continue; + } + nmsChunk.removeBlockEntity(tile.getBlockPos()); + if (createCopy) { + copy.storeTile(tile); } } } - final BiomeType[][] biomes = set.getBiomes(); + } + final BiomeType[][] biomes = set.getBiomes(); - int bitMask = 0; - synchronized (nmsChunk) { - LevelChunkSection[] levelChunkSections = nmsChunk.getSections(); + int bitMask = 0; + synchronized (nmsChunk) { + LevelChunkSection[] levelChunkSections = nmsChunk.getSections(); - for (int layerNo = getMinSectionPosition(); layerNo <= getMaxSectionPosition(); layerNo++) { + for (int layerNo = getMinSectionPosition(); layerNo <= getMaxSectionPosition(); layerNo++) { - int getSectionIndex = layerNo - getMinSectionPosition(); - int setSectionIndex = layerNo - set.getMinSectionPosition(); + int getSectionIndex = layerNo - getMinSectionPosition(); + int setSectionIndex = layerNo - set.getMinSectionPosition(); - if (!set.hasSection(layerNo)) { - // No blocks, but might be biomes present. Handle this lazily. - if (biomes == null) { - continue; - } - if (layerNo < set.getMinSectionPosition() || layerNo > set.getMaxSectionPosition()) { - continue; - } - if (biomes[setSectionIndex] != null) { - synchronized (super.sectionLocks[getSectionIndex]) { - LevelChunkSection existingSection = levelChunkSections[getSectionIndex]; - if (createCopy && existingSection != null) { - copy.storeBiomes(getSectionIndex, existingSection.getBiomes()); - } + if (!set.hasSection(layerNo)) { + // No blocks, but might be biomes present. Handle this lazily. + if (biomes == null) { + continue; + } + if (layerNo < set.getMinSectionPosition() || layerNo > set.getMaxSectionPosition()) { + continue; + } + if (biomes[setSectionIndex] != null) { + synchronized (super.sectionLocks[getSectionIndex]) { + LevelChunkSection existingSection = levelChunkSections[getSectionIndex]; + if (createCopy && existingSection != null) { + copy.storeBiomes(getSectionIndex, existingSection.getBiomes()); + } - if (existingSection == null) { - PalettedContainer> biomeData = PaperweightPlatformAdapter.getBiomePalettedContainer( - biomes[setSectionIndex], - biomeHolderIdMap - ); - LevelChunkSection newSection = PaperweightPlatformAdapter.newChunkSection( - layerNo, - new char[4096], - adapter, - biomeRegistry, - biomeData - ); - if (PaperweightPlatformAdapter.setSectionAtomic( - serverLevel.getWorld().getName(), - chunkPos, - levelChunkSections, - null, - newSection, - getSectionIndex - )) { - updateGet(nmsChunk, levelChunkSections, newSection, new char[4096], getSectionIndex); - continue; - } else { - existingSection = levelChunkSections[getSectionIndex]; - if (existingSection == null) { - LOGGER.error("Skipping invalid null section. chunk: {}, {} layer: {}", chunkX, chunkZ, - getSectionIndex - ); - continue; - } - } + if (existingSection == null) { + PalettedContainer> biomeData = PaperweightPlatformAdapter.getBiomePalettedContainer( + biomes[setSectionIndex], + biomeHolderIdMap + ); + LevelChunkSection newSection = PaperweightPlatformAdapter.newChunkSection( + layerNo, + new char[4096], + adapter, + biomeRegistry, + biomeData + ); + if (PaperweightPlatformAdapter.setSectionAtomic( + nmsWorld.getWorld().getName(), + chunkPos, + levelChunkSections, + null, + newSection, + getSectionIndex + )) { + updateGet(nmsChunk, levelChunkSections, newSection, new char[4096], getSectionIndex); + continue; } else { - PalettedContainer> paletteBiomes = setBiomesToPalettedContainer( - biomes, - setSectionIndex, - existingSection.getBiomes() - ); - if (paletteBiomes != null) { - PaperweightPlatformAdapter.setBiomesToChunkSection(existingSection, paletteBiomes); + existingSection = levelChunkSections[getSectionIndex]; + if (existingSection == null) { + LOGGER.error( + "Skipping invalid null section. chunk: {}, {} layer: {}", + chunkX, + chunkZ, + getSectionIndex + ); + continue; } } + } else { + PalettedContainer> paletteBiomes = setBiomesToPalettedContainer( + biomes, + setSectionIndex, + existingSection.getBiomes() + ); + if (paletteBiomes != null) { + PaperweightPlatformAdapter.setBiomesToChunkSection(existingSection, paletteBiomes); + } } } - continue; } + continue; + } - bitMask |= 1 << getSectionIndex; - - // setArr is modified by PaperweightPlatformAdapter#newChunkSection. This is in order to write changes to - // this chunk GET when #updateGet is called. Future dords, please listen this time. - char[] tmp = set.load(layerNo); - char[] setArr = new char[tmp.length]; - System.arraycopy(tmp, 0, setArr, 0, tmp.length); - - // synchronise on internal section to avoid circular locking with a continuing edit if the chunk was - // submitted to keep loaded internal chunks to queue target size. - synchronized (super.sectionLocks[getSectionIndex]) { - - LevelChunkSection newSection; - LevelChunkSection existingSection = levelChunkSections[getSectionIndex]; - // Don't attempt to tick section whilst we're editing - if (existingSection != null) { - PaperweightPlatformAdapter.clearCounts(existingSection); - if (PaperLib.isPaper()) { - existingSection.tickingList.clear(); - } + bitMask |= 1 << getSectionIndex; + + // setArr is modified by PaperweightPlatformAdapter#newChunkSection. This is in order to write changes to + // this chunk GET when #updateGet is called. Future dords, please listen this time. + char[] tmp = set.load(layerNo); + char[] setArr = new char[tmp.length]; + System.arraycopy(tmp, 0, setArr, 0, tmp.length); + + // synchronise on internal section to avoid circular locking with a continuing edit if the chunk was + // submitted to keep loaded internal chunks to queue target size. + synchronized (super.sectionLocks[getSectionIndex]) { + + LevelChunkSection newSection; + LevelChunkSection existingSection = levelChunkSections[getSectionIndex]; + // Don't attempt to tick section whilst we're editing + if (existingSection != null) { + PaperweightPlatformAdapter.clearCounts(existingSection); + if (PaperLib.isPaper()) { + existingSection.tickingList.clear(); } + } - if (createCopy) { - char[] tmpLoad = loadPrivately(layerNo); - char[] copyArr = new char[4096]; - System.arraycopy(tmpLoad, 0, copyArr, 0, 4096); - copy.storeSection(getSectionIndex, copyArr); - if (biomes != null && existingSection != null) { - copy.storeBiomes(getSectionIndex, existingSection.getBiomes()); - } + if (createCopy) { + char[] tmpLoad = loadPrivately(layerNo); + char[] copyArr = new char[4096]; + System.arraycopy(tmpLoad, 0, copyArr, 0, 4096); + copy.storeSection(getSectionIndex, copyArr); + if (biomes != null && existingSection != null) { + copy.storeBiomes(getSectionIndex, existingSection.getBiomes()); } + } - if (existingSection == null) { - PalettedContainer> biomeData = biomes == null ? new PalettedContainer<>( - biomeHolderIdMap, - biomeHolderIdMap.byIdOrThrow(adapter.getInternalBiomeId(BiomeTypes.PLAINS)), - PalettedContainer.Strategy.SECTION_BIOMES - ) : PaperweightPlatformAdapter.getBiomePalettedContainer(biomes[setSectionIndex], biomeHolderIdMap); - newSection = PaperweightPlatformAdapter.newChunkSection( - layerNo, - setArr, - adapter, - biomeRegistry, - biomeData - ); - if (PaperweightPlatformAdapter.setSectionAtomic( - serverLevel.getWorld().getName(), - chunkPos, - levelChunkSections, - null, - newSection, - getSectionIndex - )) { - updateGet(nmsChunk, levelChunkSections, newSection, setArr, getSectionIndex); + if (existingSection == null) { + PalettedContainer> biomeData = biomes == null ? new PalettedContainer<>( + biomeHolderIdMap, + biomeHolderIdMap.byIdOrThrow(adapter.getInternalBiomeId(BiomeTypes.PLAINS)), + PalettedContainer.Strategy.SECTION_BIOMES + ) : PaperweightPlatformAdapter.getBiomePalettedContainer(biomes[setSectionIndex], biomeHolderIdMap); + newSection = PaperweightPlatformAdapter.newChunkSection( + layerNo, + setArr, + adapter, + biomeRegistry, + biomeData + ); + if (PaperweightPlatformAdapter.setSectionAtomic( + nmsWorld.getWorld().getName(), + chunkPos, + levelChunkSections, + null, + newSection, + getSectionIndex + )) { + updateGet(nmsChunk, levelChunkSections, newSection, setArr, getSectionIndex); + continue; + } else { + existingSection = levelChunkSections[getSectionIndex]; + if (existingSection == null) { + LOGGER.error( + "Skipping invalid null section. chunk: {}, {} layer: {}", + chunkX, + chunkZ, + getSectionIndex + ); continue; - } else { - existingSection = levelChunkSections[getSectionIndex]; - if (existingSection == null) { - LOGGER.error("Skipping invalid null section. chunk: {}, {} layer: {}", chunkX, chunkZ, - getSectionIndex - ); - continue; - } } } + } - //ensure that the server doesn't try to tick the chunksection while we're editing it. (Again) - PaperweightPlatformAdapter.clearCounts(existingSection); - if (PaperLib.isPaper()) { - existingSection.tickingList.clear(); - } - DelegateSemaphore lock = PaperweightPlatformAdapter.applyLock(existingSection); - - // Synchronize to prevent further acquisitions - synchronized (lock) { - lock.acquire(); // Wait until we have the lock - lock.release(); - try { - sectionLock.writeLock().lock(); - if (this.getChunk() != nmsChunk) { - this.levelChunk = nmsChunk; - this.sections = null; - this.reset(); - } else if (existingSection != getSections(false)[getSectionIndex]) { - this.sections[getSectionIndex] = existingSection; - this.reset(); - } else if (!Arrays.equals( - update(getSectionIndex, new char[4096], true), - loadPrivately(layerNo) - )) { - this.reset(layerNo); + //ensure that the server doesn't try to tick the chunksection while we're editing it. (Again) + PaperweightPlatformAdapter.clearCounts(existingSection); + if (PaperLib.isPaper()) { + existingSection.tickingList.clear(); + } + DelegateSemaphore lock = PaperweightPlatformAdapter.applyLock(existingSection); + // Synchronize to prevent further acquisitions + synchronized (lock) { + lock.acquire(); // Wait until we have the lock + lock.release(); + try { + sectionLock.writeLock().lock(); + if (this.getChunk() != nmsChunk) { + this.levelChunk = nmsChunk; + this.sections = null; + this.reset(); + } else if (existingSection != getSections(false)[getSectionIndex]) { + this.sections[getSectionIndex] = existingSection; + this.reset(); + } else if (!Arrays.equals(update(getSectionIndex, new char[4096], true), loadPrivately(layerNo))) { + this.reset(layerNo); /*} else if (lock.isModified()) { this.reset(layerNo);*/ - } - } finally { - sectionLock.writeLock().unlock(); } + } finally { + sectionLock.writeLock().unlock(); + } - PalettedContainer> biomeData = setBiomesToPalettedContainer( - biomes, - setSectionIndex, - existingSection.getBiomes() - ); + PalettedContainer> biomeData = setBiomesToPalettedContainer( + biomes, + setSectionIndex, + existingSection.getBiomes() + ); - newSection = PaperweightPlatformAdapter.newChunkSection( - layerNo, - this::loadPrivately, - setArr, - adapter, - biomeRegistry, - biomeData != null ? biomeData : (PalettedContainer>) existingSection.getBiomes() - ); - if (!PaperweightPlatformAdapter.setSectionAtomic( - serverLevel.getWorld().getName(), - chunkPos, - levelChunkSections, - existingSection, - newSection, + newSection = PaperweightPlatformAdapter.newChunkSection( + layerNo, + this::loadPrivately, + setArr, + adapter, + biomeRegistry, + biomeData != null ? biomeData : (PalettedContainer>) existingSection.getBiomes() + ); + if (!PaperweightPlatformAdapter.setSectionAtomic( + nmsWorld.getWorld().getName(), + chunkPos, + levelChunkSections, + existingSection, + newSection, + getSectionIndex + )) { + LOGGER.error( + "Skipping invalid null section. chunk: {}, {} layer: {}", + chunkX, + chunkZ, getSectionIndex - )) { - LOGGER.error("Skipping invalid null section. chunk: {}, {} layer: {}", chunkX, chunkZ, - getSectionIndex - ); - } else { - updateGet(nmsChunk, levelChunkSections, newSection, setArr, getSectionIndex); - } + ); + } else { + updateGet(nmsChunk, levelChunkSections, newSection, setArr, getSectionIndex); } } } + } - Map heightMaps = set.getHeightMaps(); - for (Map.Entry entry : heightMaps.entrySet()) { - PaperweightGetBlocks.this.setHeightmapToGet(entry.getKey(), entry.getValue()); - } - PaperweightGetBlocks.this.setLightingToGet( - set.getLight(), - set.getMinSectionPosition(), - set.getMaxSectionPosition() - ); - PaperweightGetBlocks.this.setSkyLightingToGet( - set.getSkyLight(), - set.getMinSectionPosition(), - set.getMaxSectionPosition() - ); - - Runnable[] syncTasks = null; + Map heightMaps = set.getHeightMaps(); + for (Map.Entry entry : heightMaps.entrySet()) { + PaperweightGetBlocks.this.setHeightmapToGet(entry.getKey(), entry.getValue()); + } + PaperweightGetBlocks.this.setLightingToGet(set.getLight(), set.getMinSectionPosition(), set.getMaxSectionPosition()); + PaperweightGetBlocks.this.setSkyLightingToGet( + set.getSkyLight(), + set.getMinSectionPosition(), + set.getMaxSectionPosition() + ); - int bx = chunkX << 4; - int bz = chunkZ << 4; + Runnable[] syncTasks = null; - // Call beacon deactivate events here synchronously - // list will be null on spigot, so this is an implicit isPaper check - if (beacons != null && !beacons.isEmpty()) { - final List finalBeacons = beacons; + int bx = chunkX << 4; + int bz = chunkZ << 4; - syncTasks = new Runnable[4]; + // Call beacon deactivate events here synchronously + // list will be null on spigot, so this is an implicit isPaper check + if (beacons != null && !beacons.isEmpty()) { + final List finalBeacons = beacons; - syncTasks[3] = () -> { - for (BlockEntity beacon : finalBeacons) { - BeaconBlockEntity.playSound(beacon.getLevel(), beacon.getBlockPos(), SoundEvents.BEACON_DEACTIVATE); - new BeaconDeactivatedEvent(CraftBlock.at(beacon.getLevel(), beacon.getBlockPos())).callEvent(); - } - }; - } + syncTasks = new Runnable[4]; - Set entityRemoves = set.getEntityRemoves(); - if (entityRemoves != null && !entityRemoves.isEmpty()) { - if (syncTasks == null) { - syncTasks = new Runnable[3]; + syncTasks[3] = () -> { + for (BlockEntity beacon : finalBeacons) { + BeaconBlockEntity.playSound(beacon.getLevel(), beacon.getBlockPos(), SoundEvents.BEACON_DEACTIVATE); + new BeaconDeactivatedEvent(CraftBlock.at(beacon.getLevel(), beacon.getBlockPos())).callEvent(); } + }; + } - syncTasks[2] = () -> { - Set entitiesRemoved = new HashSet<>(); - final List entities = PaperweightPlatformAdapter.getEntities(nmsChunk); + Set entityRemoves = set.getEntityRemoves(); + if (entityRemoves != null && !entityRemoves.isEmpty()) { + if (syncTasks == null) { + syncTasks = new Runnable[3]; + } - for (Entity entity : entities) { - UUID uuid = entity.getUUID(); - if (entityRemoves.contains(uuid)) { - if (createCopy) { - copy.storeEntity(entity); - } - removeEntity(entity); - entitiesRemoved.add(uuid); - entityRemoves.remove(uuid); + syncTasks[2] = () -> { + Set entitiesRemoved = new HashSet<>(); + final List entities = PaperweightPlatformAdapter.getEntities(nmsChunk); + + for (Entity entity : entities) { + UUID uuid = entity.getUUID(); + if (entityRemoves.contains(uuid)) { + if (createCopy) { + copy.storeEntity(entity); } + removeEntity(entity); + entitiesRemoved.add(uuid); + entityRemoves.remove(uuid); } - if (Settings.settings().EXPERIMENTAL.REMOVE_ENTITY_FROM_WORLD_ON_CHUNK_FAIL) { - for (UUID uuid : entityRemoves) { - Entity entity = serverLevel.getEntities().get(uuid); - if (entity != null) { - removeEntity(entity); - } + } + if (Settings.settings().EXPERIMENTAL.REMOVE_ENTITY_FROM_WORLD_ON_CHUNK_FAIL) { + for (UUID uuid : entityRemoves) { + Entity entity = nmsWorld.getEntities().get(uuid); + if (entity != null) { + removeEntity(entity); } } - // Only save entities that were actually removed to history - set.getEntityRemoves().clear(); - set.getEntityRemoves().addAll(entitiesRemoved); - }; - } - - Collection entities = set.entities(); - if (entities != null && !entities.isEmpty()) { - if (syncTasks == null) { - syncTasks = new Runnable[2]; } + // Only save entities that were actually removed to history + set.getEntityRemoves().clear(); + set.getEntityRemoves().addAll(entitiesRemoved); + }; + } - syncTasks[1] = () -> { - Iterator iterator = entities.iterator(); - while (iterator.hasNext()) { - final FaweCompoundTag nativeTag = iterator.next(); - final LinCompoundTag linTag = nativeTag.linTag(); - final LinStringTag idTag = linTag.findTag("Id", LinTagType.stringTag()); - final LinListTag posTag = linTag.findListTag("Pos", LinTagType.doubleTag()); - final LinListTag rotTag = linTag.findListTag("Rotation", LinTagType.floatTag()); - if (idTag == null || posTag == null || rotTag == null) { - LOGGER.error("Unknown entity tag: {}", nativeTag); - continue; - } - final double x = posTag.get(0).valueAsDouble(); - final double y = posTag.get(1).valueAsDouble(); - final double z = posTag.get(2).valueAsDouble(); - final float yaw = rotTag.get(0).valueAsFloat(); - final float pitch = rotTag.get(1).valueAsFloat(); - final String id = idTag.value(); - - EntityType type = EntityType.byString(id).orElse(null); - if (type != null) { - 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) { - tag.remove(name); - } - entity.load(tag); - entity.absMoveTo(x, y, z, yaw, pitch); - entity.setUUID(NbtUtils.uuid(nativeTag)); - if (!serverLevel.addFreshEntity(entity, CreatureSpawnEvent.SpawnReason.CUSTOM)) { - LOGGER.warn( - "Error creating entity of type `{}` in world `{}` at location `{},{},{}`", - id, - serverLevel.getWorld().getName(), - x, - y, - z - ); - // Unsuccessful create should not be saved to history - iterator.remove(); - } - } - } - } - }; + Collection entities = set.entities(); + if (entities != null && !entities.isEmpty()) { + if (syncTasks == null) { + syncTasks = new Runnable[2]; } - // set tiles - Map tiles = set.tiles(); - if (tiles != null && !tiles.isEmpty()) { - if (syncTasks == null) { - syncTasks = new Runnable[1]; - } - - syncTasks[0] = () -> { - for (final Map.Entry entry : tiles.entrySet()) { - final FaweCompoundTag nativeTag = entry.getValue(); - final BlockVector3 blockHash = entry.getKey(); - final int x = blockHash.x() + bx; - final int y = blockHash.y(); - final int z = blockHash.z() + bz; - final BlockPos pos = new BlockPos(x, y, z); - - synchronized (serverLevel) { - BlockEntity tileEntity = serverLevel.getBlockEntity(pos); - if (tileEntity == null || tileEntity.isRemoved()) { - serverLevel.removeBlockEntity(pos); - tileEntity = serverLevel.getBlockEntity(pos); + syncTasks[1] = () -> { + Iterator iterator = entities.iterator(); + while (iterator.hasNext()) { + final FaweCompoundTag nativeTag = iterator.next(); + final LinCompoundTag linTag = nativeTag.linTag(); + final LinStringTag idTag = linTag.findTag("Id", LinTagType.stringTag()); + final LinListTag posTag = linTag.findListTag("Pos", LinTagType.doubleTag()); + final LinListTag rotTag = linTag.findListTag("Rotation", LinTagType.floatTag()); + if (idTag == null || posTag == null || rotTag == null) { + LOGGER.error("Unknown entity tag: {}", nativeTag); + continue; + } + final double x = posTag.get(0).valueAsDouble(); + final double y = posTag.get(1).valueAsDouble(); + final double z = posTag.get(2).valueAsDouble(); + final float yaw = rotTag.get(0).valueAsFloat(); + final float pitch = rotTag.get(1).valueAsFloat(); + final String id = idTag.value(); + + EntityType type = EntityType.byString(id).orElse(null); + if (type != null) { + Entity entity = type.create(nmsWorld); + 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) { + tag.remove(name); } - if (tileEntity != null) { - final net.minecraft.nbt.CompoundTag tag = (CompoundTag) adapter.fromNativeLin(nativeTag.linTag()); - tag.put("x", IntTag.valueOf(x)); - tag.put("y", IntTag.valueOf(y)); - tag.put("z", IntTag.valueOf(z)); - tileEntity.loadWithComponents(tag, DedicatedServer.getServer().registryAccess()); + entity.load(tag); + entity.absMoveTo(x, y, z, yaw, pitch); + entity.setUUID(NbtUtils.uuid(nativeTag)); + if (!nmsWorld.addFreshEntity(entity, CreatureSpawnEvent.SpawnReason.CUSTOM)) { + LOGGER.warn( + "Error creating entity of type `{}` in world `{}` at location `{},{},{}`", + id, + nmsWorld.getWorld().getName(), + x, + y, + z + ); + // Unsuccessful create should not be saved to history + iterator.remove(); } } } - }; - } + } + }; + } - Runnable callback; - if (bitMask == 0 && biomes == null && !lightUpdate) { - callback = null; - } else { - int finalMask = bitMask != 0 ? bitMask : lightUpdate ? set.getBitMask() : 0; - callback = () -> { - // Set Modified - nmsChunk.setLightCorrect(true); // Set Modified - nmsChunk.mustNotSave = false; - nmsChunk.setUnsaved(true); - // send to player - if (!set - .getSideEffectSet() - .shouldApply(SideEffect.LIGHTING) || !Settings.settings().LIGHTING.DELAY_PACKET_SENDING || finalMask == 0 && biomes != null) { - this.send(); - } - if (finalizer != null) { - finalizer.run(); - } - }; + // set tiles + Map tiles = set.tiles(); + if (tiles != null && !tiles.isEmpty()) { + if (syncTasks == null) { + syncTasks = new Runnable[1]; } - if (syncTasks != null) { - QueueHandler queueHandler = Fawe.instance().getQueueHandler(); - Runnable[] finalSyncTasks = syncTasks; - // Chain the sync tasks and the callback - Callable chain = () -> { - try { - // Run the sync tasks - for (Runnable task : finalSyncTasks) { - if (task != null) { - task.run(); - } + syncTasks[0] = () -> { + for (final Map.Entry entry : tiles.entrySet()) { + final FaweCompoundTag nativeTag = entry.getValue(); + final BlockVector3 blockHash = entry.getKey(); + final int x = blockHash.x() + bx; + final int y = blockHash.y(); + final int z = blockHash.z() + bz; + final BlockPos pos = new BlockPos(x, y, z); + + synchronized (nmsWorld) { + BlockEntity tileEntity = nmsWorld.getBlockEntity(pos); + if (tileEntity == null || tileEntity.isRemoved()) { + nmsWorld.removeBlockEntity(pos); + tileEntity = nmsWorld.getBlockEntity(pos); } - if (callback == null) { - if (finalizer != null) { - queueHandler.async(finalizer, null); - } - return null; - } else { - return queueHandler.async(callback, null); + if (tileEntity != null) { + final net.minecraft.nbt.CompoundTag tag = (CompoundTag) adapter.fromNativeLin(nativeTag.linTag()); + tag.put("x", IntTag.valueOf(x)); + tag.put("y", IntTag.valueOf(y)); + tag.put("z", IntTag.valueOf(z)); + tileEntity.loadWithComponents(tag, DedicatedServer.getServer().registryAccess()); } - } catch (Throwable e) { - e.printStackTrace(); - throw e; - } - }; - //noinspection unchecked - required at compile time - return (T) (Future) queueHandler.sync(chain); - } else { - if (callback == null) { - if (finalizer != null) { - finalizer.run(); } - } else { - callback.run(); } - } + }; } - return null; - } catch (Throwable e) { - e.printStackTrace(); - return null; - } finally { - forceLoadSections = true; + + Runnable callback; + if (bitMask == 0 && biomes == null && !lightUpdate) { + callback = null; + } else { + int finalMask = bitMask != 0 ? bitMask : lightUpdate ? set.getBitMask() : 0; + callback = () -> { + // Set Modified + nmsChunk.setLightCorrect(true); // Set Modified + nmsChunk.mustNotSave = false; + nmsChunk.setUnsaved(true); + // send to player + if (!set + .getSideEffectSet() + .shouldApply(SideEffect.LIGHTING) || !Settings.settings().LIGHTING.DELAY_PACKET_SENDING || finalMask == 0 && biomes != null) { + this.send(); + } + if (finalizer != null) { + finalizer.run(); + } + }; + } + return handleCallFinalizer(syncTasks, callback, finalizer); } } @@ -928,18 +815,6 @@ private void updateGet( this.blocks[layer] = arr; } - private char[] loadPrivately(int layer) { - layer -= getMinSectionPosition(); - if (super.sections[layer] != null) { - synchronized (super.sectionLocks[layer]) { - if (super.sections[layer].isFull() && super.blocks[layer] != null) { - return super.blocks[layer]; - } - } - } - return PaperweightGetBlocks.this.update(layer, null, true); - } - @Override public void send() { synchronized (sendLock) { @@ -1037,7 +912,7 @@ public char[] update(int layer, char[] data, boolean aggressive) { } return data; } catch (IllegalAccessException | InterruptedException e) { - e.printStackTrace(); + LOGGER.error("Could not read block data from palette", e); throw new RuntimeException(e); } finally { lock.release(); @@ -1079,7 +954,16 @@ public LevelChunk getChunk() { synchronized (this) { levelChunk = this.levelChunk; if (levelChunk == null) { - this.levelChunk = levelChunk = ensureLoaded(this.serverLevel, chunkX, chunkZ); + try { + this.levelChunk = levelChunk = ensureLoaded(this.serverLevel).get(); + } catch (InterruptedException | ExecutionException e) { + LOGGER.error("Could not get chunk at {},{}", chunkX, chunkZ, e); + throw new FaweException( + TextComponent.of("Could not get chunk at " + chunkX + "," + chunkZ + ": " + e.getMessage()), + FaweException.Type.OTHER, + false + ); + } } } } diff --git a/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightGetBlocks_Copy.java b/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightGetBlocks_Copy.java index 7d199c7f63..c60958eb84 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightGetBlocks_Copy.java +++ b/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightGetBlocks_Copy.java @@ -3,8 +3,10 @@ import com.fastasyncworldedit.core.extent.processor.heightmap.HeightMapType; import com.fastasyncworldedit.core.nbt.FaweCompoundTag; import com.fastasyncworldedit.core.queue.IBlocks; +import com.fastasyncworldedit.core.queue.IChunk; import com.fastasyncworldedit.core.queue.IChunkGet; import com.fastasyncworldedit.core.queue.IChunkSet; +import com.fastasyncworldedit.core.queue.IQueueExtent; import com.fastasyncworldedit.core.util.NbtUtils; import com.sk89q.worldedit.bukkit.WorldEditPlugin; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; @@ -257,7 +259,7 @@ public int[] getHeightMap(HeightMapType type) { } @Override - public > T call(IChunkSet set, Runnable finalize) { + public > T call(IQueueExtent owner, IChunkSet set, Runnable finalize) { return null; } 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 3ddfbc6fbf..20c0993fd4 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 @@ -56,7 +56,6 @@ import net.minecraft.world.level.chunk.status.ChunkStatus; import net.minecraft.world.level.entity.PersistentEntitySectionManager; import org.apache.logging.log4j.Logger; -import org.bukkit.Bukkit; import org.bukkit.craftbukkit.CraftChunk; import javax.annotation.Nonnull; @@ -77,9 +76,8 @@ import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import java.util.function.IntFunction; import static java.lang.invoke.MethodType.methodType; @@ -274,12 +272,48 @@ static DelegateSemaphore applyLock(LevelChunkSection section) { } } } catch (Throwable e) { - e.printStackTrace(); + LOGGER.error("Error apply DelegateSemaphore", e); throw new RuntimeException(e); } } - public static LevelChunk ensureLoaded(ServerLevel serverLevel, int chunkX, int chunkZ) { + public static CompletableFuture ensureLoaded(ServerLevel serverLevel, int chunkX, int chunkZ) { + LevelChunk levelChunk = getChunkImmediatelyAsync(serverLevel, chunkX, chunkZ); + if (levelChunk != null) { + return CompletableFuture.completedFuture(levelChunk); + } + if (PaperLib.isPaper()) { + CompletableFuture future = serverLevel + .getWorld() + .getChunkAtAsync(chunkX, chunkZ, true, true) + .thenApply(chunk -> { + addTicket(serverLevel, chunkX, chunkZ); + try { + return (LevelChunk) CRAFT_CHUNK_GET_HANDLE.invoke(chunk); + } catch (Throwable e) { + LOGGER.error("Could not asynchronously load chunk at {},{}", chunkX, chunkZ, e); + return null; + } + }); + try { + if (!future.isCompletedExceptionally() || (future.isDone() && future.get() != null)) { + return future; + } + Throwable t = future.exceptionNow(); + LOGGER.error("Asynchronous chunk load at {},{} exceptionally completed immediately", chunkX, chunkZ, t); + } catch (InterruptedException | ExecutionException e) { + LOGGER.error( + "Unexpected error when getting completed future at chunk {},{}. Returning to default.", + chunkX, + chunkZ, + e + ); + } + } + return CompletableFuture.supplyAsync(() -> TaskManager.taskManager().sync(() -> serverLevel.getChunk(chunkX, chunkZ))); + } + + public static @Nullable LevelChunk getChunkImmediatelyAsync(ServerLevel serverLevel, int chunkX, int chunkZ) { if (!PaperLib.isPaper()) { LevelChunk nmsChunk = serverLevel.getChunkSource().getChunk(chunkX, chunkZ, false); if (nmsChunk != null) { @@ -288,6 +322,7 @@ public static LevelChunk ensureLoaded(ServerLevel serverLevel, int chunkX, int c if (Fawe.isMainThread()) { return serverLevel.getChunk(chunkX, chunkZ); } + return null; } else { LevelChunk nmsChunk = serverLevel.getChunkSource().getChunkAtIfCachedImmediately(chunkX, chunkZ); if (nmsChunk != null) { @@ -303,30 +338,8 @@ public static LevelChunk ensureLoaded(ServerLevel serverLevel, int chunkX, int c if (Fawe.isMainThread()) { return serverLevel.getChunk(chunkX, chunkZ); } - CompletableFuture future = serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true); - try { - CraftChunk chunk; - try { - chunk = (CraftChunk) future.get(10, TimeUnit.SECONDS); - } catch (TimeoutException e) { - String world = serverLevel.getWorld().getName(); - // We've already taken 10 seconds we can afford to wait a little here. - boolean loaded = TaskManager.taskManager().sync(() -> Bukkit.getWorld(world) != null); - if (loaded) { - LOGGER.warn("Chunk {},{} failed to load in 10 seconds in world {}. Retrying...", chunkX, chunkZ, world); - // Retry chunk load - chunk = (CraftChunk) serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true).get(); - } else { - throw new UnsupportedOperationException("Cannot load chunk from unloaded world " + world + "!"); - } - } - addTicket(serverLevel, chunkX, chunkZ); - return (LevelChunk) CRAFT_CHUNK_GET_HANDLE.invoke(chunk); - } catch (Throwable e) { - e.printStackTrace(); - } + return null; } - return TaskManager.taskManager().sync(() -> serverLevel.getChunk(chunkX, chunkZ)); } private static void addTicket(ServerLevel serverLevel, int chunkX, int chunkZ) { @@ -665,7 +678,7 @@ static void removeBeacon(BlockEntity beacon, LevelChunk levelChunk) { } methodremoveTickingBlockEntity.invoke(levelChunk, beacon.getBlockPos()); } catch (Throwable throwable) { - throwable.printStackTrace(); + LOGGER.error("Error removing beacon", throwable); } } diff --git a/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightGetBlocks.java b/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightGetBlocks.java index 0f5ab9449c..686a4477c1 100644 --- a/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightGetBlocks.java +++ b/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightGetBlocks.java @@ -1,18 +1,17 @@ package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_21_R1; -import com.fastasyncworldedit.bukkit.adapter.BukkitGetBlocks; +import com.fastasyncworldedit.bukkit.adapter.AbstractBukkitGetBlocks; import com.fastasyncworldedit.bukkit.adapter.DelegateSemaphore; import com.fastasyncworldedit.core.Fawe; import com.fastasyncworldedit.core.FaweCache; import com.fastasyncworldedit.core.configuration.Settings; import com.fastasyncworldedit.core.extent.processor.heightmap.HeightMapType; +import com.fastasyncworldedit.core.internal.exception.FaweException; 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; import com.fastasyncworldedit.core.queue.implementation.QueueHandler; -import com.fastasyncworldedit.core.queue.implementation.blocks.CharGetBlocks; import com.fastasyncworldedit.core.util.MathMan; import com.fastasyncworldedit.core.util.NbtUtils; import com.fastasyncworldedit.core.util.collection.AdaptedMap; @@ -22,6 +21,7 @@ import com.sk89q.worldedit.internal.util.LogManagerCompat; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.util.SideEffect; +import com.sk89q.worldedit.util.formatting.text.TextComponent; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.biome.BiomeTypes; import com.sk89q.worldedit.world.block.BlockTypesCache; @@ -83,17 +83,17 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.Callable; -import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.Semaphore; import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.Function; import static net.minecraft.core.registries.Registries.BIOME; -public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBlocks { +public class PaperweightGetBlocks extends AbstractBukkitGetBlocks { private static final Logger LOGGER = LogManagerCompat.getLogger(); @@ -105,84 +105,25 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc .getInstance() .getBukkitImplAdapter()); private final ReadWriteLock sectionLock = new ReentrantReadWriteLock(); - private final ReentrantLock callLock = new ReentrantLock(); - 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; - private final int maxSectionPosition; private final Registry biomeRegistry; private final IdMap> biomeHolderIdMap; - private final ConcurrentHashMap copies = new ConcurrentHashMap<>(); private final Object sendLock = new Object(); - private LevelChunkSection[] sections; private LevelChunk levelChunk; + private LevelChunkSection[] sections; private DataLayer[] blockLight; private DataLayer[] skyLight; - private boolean createCopy = false; - private boolean forceLoadSections = true; private boolean lightUpdate = false; - private int copyKey = 0; public PaperweightGetBlocks(World world, int chunkX, int chunkZ) { this(((CraftWorld) world).getHandle(), chunkX, chunkZ); } public PaperweightGetBlocks(ServerLevel serverLevel, int chunkX, int chunkZ) { - super(serverLevel.getMinBuildHeight() >> 4, (serverLevel.getMaxBuildHeight() - 1) >> 4); - this.serverLevel = serverLevel; - this.chunkX = chunkX; - this.chunkZ = chunkZ; - this.minHeight = serverLevel.getMinBuildHeight(); - this.maxHeight = serverLevel.getMaxBuildHeight() - 1; // Minecraft max limit is exclusive. - this.minSectionPosition = minHeight >> 4; - this.maxSectionPosition = maxHeight >> 4; + super(serverLevel, chunkX, chunkZ, serverLevel.getMinBuildHeight(), serverLevel.getMaxBuildHeight() - 1); this.skyLight = new DataLayer[getSectionCount()]; this.blockLight = new DataLayer[getSectionCount()]; this.biomeRegistry = serverLevel.registryAccess().registryOrThrow(BIOME); this.biomeHolderIdMap = biomeRegistry.asHolderIdMap(); - this.chunkPos = new IntPair(chunkX, chunkZ); - } - - public int getChunkX() { - return chunkX; - } - - public int getChunkZ() { - return chunkZ; - } - - @Override - public boolean isCreateCopy() { - return createCopy; - } - - @Override - public int setCreateCopy(boolean createCopy) { - if (!callLock.isHeldByCurrentThread()) { - throw new IllegalStateException("Attempting to set if chunk GET should create copy, but it is not call-locked."); - } - this.createCopy = createCopy; - // Increment regardless of whether copy will be created or not to return null from getCopy() - return ++this.copyKey; - } - - @Override - public IChunkGet getCopy(final int key) { - return copies.remove(key); - } - - @Override - public void lockCall() { - this.callLock.lock(); - } - - @Override - public void unlockCall() { - this.callLock.unlock(); } @Override @@ -192,7 +133,7 @@ public void setLightingToGet(char[][] light, int minSectionPosition, int maxSect try { fillLightNibble(light, LightLayer.BLOCK, minSectionPosition, maxSectionPosition); } catch (Throwable e) { - e.printStackTrace(); + LOGGER.error("Error setting lighting to get", e); } } } @@ -204,7 +145,7 @@ public void setSkyLightingToGet(char[][] light, int minSectionPosition, int maxS try { fillLightNibble(light, LightLayer.SKY, minSectionPosition, maxSectionPosition); } catch (Throwable e) { - e.printStackTrace(); + LOGGER.error("Error setting sky lighting to get", e); } } } @@ -219,16 +160,6 @@ public void setHeightmapToGet(HeightMapType type, int[] data) { heightMap.setRawData(getChunk(), nativeType, bitArray.getData()); } - @Override - public int getMaxY() { - return maxHeight; - } - - @Override - public int getMinY() { - return minHeight; - } - @Override public BiomeType getBiomeType(int x, int y, int z) { LevelChunkSection section = getSections(false)[(y >> 4) - getMinSectionPosition()]; @@ -347,7 +278,7 @@ public int[] getHeightMap(HeightMapType type) { @Override public @Nullable FaweCompoundTag entity(final UUID uuid) { - ensureLoaded(serverLevel, chunkX, chunkZ); + ensureLoaded(serverLevel); List entities = PaperweightPlatformAdapter.getEntities(getChunk()); Entity entity = null; for (Entity e : entities) { @@ -370,7 +301,7 @@ public int[] getHeightMap(HeightMapType type) { @Override public Collection entities() { - ensureLoaded(serverLevel, chunkX, chunkZ); + ensureLoaded(serverLevel); List entities = PaperweightPlatformAdapter.getEntities(getChunk()); if (entities.isEmpty()) { return Collections.emptyList(); @@ -420,18 +351,19 @@ private void removeEntity(Entity entity) { entity.discard(); } - public LevelChunk ensureLoaded(ServerLevel nmsWorld, int chunkX, int chunkZ) { + @Override + public CompletableFuture ensureLoaded(ServerLevel nmsWorld) { return PaperweightPlatformAdapter.ensureLoaded(nmsWorld, chunkX, chunkZ); } @Override - @SuppressWarnings("rawtypes") - public synchronized > T call(IChunkSet set, Runnable finalizer) { - if (!callLock.isHeldByCurrentThread()) { - throw new IllegalStateException("Attempted to call chunk GET but chunk was not call-locked."); - } - forceLoadSections = false; - LevelChunk nmsChunk = ensureLoaded(serverLevel, chunkX, chunkZ); + protected > T internalCall( + IChunkSet set, + Runnable finalizer, + int copyKey, + LevelChunk nmsChunk, + ServerLevel nmsWorld + ) throws Exception { PaperweightGetBlocks_Copy copy = createCopy ? new PaperweightGetBlocks_Copy(nmsChunk) : null; if (createCopy) { if (copies.containsKey(copyKey)) { @@ -439,459 +371,454 @@ public synchronized > T call(IChunkSet set, Runnable finaliz } copies.put(copyKey, copy); } - try { - // Remove existing tiles. Create a copy so that we can remove blocks - Map chunkTiles = new HashMap<>(nmsChunk.getBlockEntities()); - List beacons = null; - if (!chunkTiles.isEmpty()) { - for (Map.Entry entry : chunkTiles.entrySet()) { - final BlockPos pos = entry.getKey(); - final int lx = pos.getX() & 15; - final int ly = pos.getY(); - final int lz = pos.getZ() & 15; - final int layer = ly >> 4; - if (!set.hasSection(layer)) { - continue; - } + // Remove existing tiles. Create a copy so that we can remove blocks + Map chunkTiles = new HashMap<>(nmsChunk.getBlockEntities()); + List beacons = null; + if (!chunkTiles.isEmpty()) { + for (Map.Entry entry : chunkTiles.entrySet()) { + final BlockPos pos = entry.getKey(); + final int lx = pos.getX() & 15; + final int ly = pos.getY(); + final int lz = pos.getZ() & 15; + final int layer = ly >> 4; + if (!set.hasSection(layer)) { + continue; + } - int ordinal = set.getBlock(lx, ly, lz).getOrdinal(); - if (ordinal != BlockTypesCache.ReservedIDs.__RESERVED__) { - BlockEntity tile = entry.getValue(); - if (PaperLib.isPaper() && tile instanceof BeaconBlockEntity) { - if (beacons == null) { - beacons = new ArrayList<>(); - } - beacons.add(tile); - PaperweightPlatformAdapter.removeBeacon(tile, nmsChunk); - continue; - } - nmsChunk.removeBlockEntity(tile.getBlockPos()); - if (createCopy) { - copy.storeTile(tile); + int ordinal = set.getBlock(lx, ly, lz).getOrdinal(); + if (ordinal != BlockTypesCache.ReservedIDs.__RESERVED__) { + BlockEntity tile = entry.getValue(); + if (PaperLib.isPaper() && tile instanceof BeaconBlockEntity) { + if (beacons == null) { + beacons = new ArrayList<>(); } + beacons.add(tile); + PaperweightPlatformAdapter.removeBeacon(tile, nmsChunk); + continue; + } + nmsChunk.removeBlockEntity(tile.getBlockPos()); + if (createCopy) { + copy.storeTile(tile); } } } - final BiomeType[][] biomes = set.getBiomes(); + } + final BiomeType[][] biomes = set.getBiomes(); - int bitMask = 0; - synchronized (nmsChunk) { - LevelChunkSection[] levelChunkSections = nmsChunk.getSections(); + int bitMask = 0; + synchronized (nmsChunk) { + LevelChunkSection[] levelChunkSections = nmsChunk.getSections(); - for (int layerNo = getMinSectionPosition(); layerNo <= getMaxSectionPosition(); layerNo++) { + for (int layerNo = getMinSectionPosition(); layerNo <= getMaxSectionPosition(); layerNo++) { - int getSectionIndex = layerNo - getMinSectionPosition(); - int setSectionIndex = layerNo - set.getMinSectionPosition(); + int getSectionIndex = layerNo - getMinSectionPosition(); + int setSectionIndex = layerNo - set.getMinSectionPosition(); - if (!set.hasSection(layerNo)) { - // No blocks, but might be biomes present. Handle this lazily. - if (biomes == null) { - continue; - } - if (layerNo < set.getMinSectionPosition() || layerNo > set.getMaxSectionPosition()) { - continue; - } - if (biomes[setSectionIndex] != null) { - synchronized (super.sectionLocks[getSectionIndex]) { - LevelChunkSection existingSection = levelChunkSections[getSectionIndex]; - if (createCopy && existingSection != null) { - copy.storeBiomes(getSectionIndex, existingSection.getBiomes()); - } + if (!set.hasSection(layerNo)) { + // No blocks, but might be biomes present. Handle this lazily. + if (biomes == null) { + continue; + } + if (layerNo < set.getMinSectionPosition() || layerNo > set.getMaxSectionPosition()) { + continue; + } + if (biomes[setSectionIndex] != null) { + synchronized (super.sectionLocks[getSectionIndex]) { + LevelChunkSection existingSection = levelChunkSections[getSectionIndex]; + if (createCopy && existingSection != null) { + copy.storeBiomes(getSectionIndex, existingSection.getBiomes()); + } - if (existingSection == null) { - PalettedContainer> biomeData = PaperweightPlatformAdapter.getBiomePalettedContainer( - biomes[setSectionIndex], - biomeHolderIdMap - ); - LevelChunkSection newSection = PaperweightPlatformAdapter.newChunkSection( - layerNo, - new char[4096], - adapter, - biomeRegistry, - biomeData - ); - if (PaperweightPlatformAdapter.setSectionAtomic( - serverLevel.getWorld().getName(), - chunkPos, - levelChunkSections, - null, - newSection, - getSectionIndex - )) { - updateGet(nmsChunk, levelChunkSections, newSection, new char[4096], getSectionIndex); - continue; - } else { - existingSection = levelChunkSections[getSectionIndex]; - if (existingSection == null) { - LOGGER.error("Skipping invalid null section. chunk: {}, {} layer: {}", chunkX, chunkZ, - getSectionIndex - ); - continue; - } - } + if (existingSection == null) { + PalettedContainer> biomeData = PaperweightPlatformAdapter.getBiomePalettedContainer( + biomes[setSectionIndex], + biomeHolderIdMap + ); + LevelChunkSection newSection = PaperweightPlatformAdapter.newChunkSection( + layerNo, + new char[4096], + adapter, + biomeRegistry, + biomeData + ); + if (PaperweightPlatformAdapter.setSectionAtomic( + nmsWorld.getWorld().getName(), + chunkPos, + levelChunkSections, + null, + newSection, + getSectionIndex + )) { + updateGet(nmsChunk, levelChunkSections, newSection, new char[4096], getSectionIndex); + continue; } else { - PalettedContainer> paletteBiomes = setBiomesToPalettedContainer( - biomes, - setSectionIndex, - existingSection.getBiomes() - ); - if (paletteBiomes != null) { - PaperweightPlatformAdapter.setBiomesToChunkSection(existingSection, paletteBiomes); + existingSection = levelChunkSections[getSectionIndex]; + if (existingSection == null) { + LOGGER.error( + "Skipping invalid null section. chunk: {}, {} layer: {}", + chunkX, + chunkZ, + getSectionIndex + ); + continue; } } + } else { + PalettedContainer> paletteBiomes = setBiomesToPalettedContainer( + biomes, + setSectionIndex, + existingSection.getBiomes() + ); + if (paletteBiomes != null) { + PaperweightPlatformAdapter.setBiomesToChunkSection(existingSection, paletteBiomes); + } } } - continue; } + continue; + } - bitMask |= 1 << getSectionIndex; + bitMask |= 1 << getSectionIndex; - // setArr is modified by PaperweightPlatformAdapter#newChunkSection. This is in order to write changes to - // this chunk GET when #updateGet is called. Future dords, please listen this time. - char[] tmp = set.load(layerNo); - char[] setArr = new char[tmp.length]; - System.arraycopy(tmp, 0, setArr, 0, tmp.length); + // setArr is modified by PaperweightPlatformAdapter#newChunkSection. This is in order to write changes to + // this chunk GET when #updateGet is called. Future dords, please listen this time. + char[] tmp = set.load(layerNo); + char[] setArr = new char[tmp.length]; + System.arraycopy(tmp, 0, setArr, 0, tmp.length); - // synchronise on internal section to avoid circular locking with a continuing edit if the chunk was - // submitted to keep loaded internal chunks to queue target size. - synchronized (super.sectionLocks[getSectionIndex]) { + // synchronise on internal section to avoid circular locking with a continuing edit if the chunk was + // submitted to keep loaded internal chunks to queue target size. + synchronized (super.sectionLocks[getSectionIndex]) { - LevelChunkSection newSection; - LevelChunkSection existingSection = levelChunkSections[getSectionIndex]; - // Don't attempt to tick section whilst we're editing - if (existingSection != null) { - PaperweightPlatformAdapter.clearCounts(existingSection); - } + LevelChunkSection newSection; + LevelChunkSection existingSection = levelChunkSections[getSectionIndex]; + // Don't attempt to tick section whilst we're editing + if (existingSection != null) { + PaperweightPlatformAdapter.clearCounts(existingSection); + } - if (createCopy) { - char[] tmpLoad = loadPrivately(layerNo); - char[] copyArr = new char[4096]; - System.arraycopy(tmpLoad, 0, copyArr, 0, 4096); - copy.storeSection(getSectionIndex, copyArr); - if (biomes != null && existingSection != null) { - copy.storeBiomes(getSectionIndex, existingSection.getBiomes()); - } + if (createCopy) { + char[] tmpLoad = loadPrivately(layerNo); + char[] copyArr = new char[4096]; + System.arraycopy(tmpLoad, 0, copyArr, 0, 4096); + copy.storeSection(getSectionIndex, copyArr); + if (biomes != null && existingSection != null) { + copy.storeBiomes(getSectionIndex, existingSection.getBiomes()); } + } - if (existingSection == null) { - PalettedContainer> biomeData = biomes == null ? new PalettedContainer<>( - biomeHolderIdMap, - biomeHolderIdMap.byIdOrThrow(adapter.getInternalBiomeId(BiomeTypes.PLAINS)), - PalettedContainer.Strategy.SECTION_BIOMES - ) : PaperweightPlatformAdapter.getBiomePalettedContainer(biomes[setSectionIndex], biomeHolderIdMap); - newSection = PaperweightPlatformAdapter.newChunkSection( - layerNo, - setArr, - adapter, - biomeRegistry, - biomeData - ); - if (PaperweightPlatformAdapter.setSectionAtomic( - serverLevel.getWorld().getName(), - chunkPos, - levelChunkSections, - null, - newSection, - getSectionIndex - )) { - updateGet(nmsChunk, levelChunkSections, newSection, setArr, getSectionIndex); + if (existingSection == null) { + PalettedContainer> biomeData = biomes == null ? new PalettedContainer<>( + biomeHolderIdMap, + biomeHolderIdMap.byIdOrThrow(adapter.getInternalBiomeId(BiomeTypes.PLAINS)), + PalettedContainer.Strategy.SECTION_BIOMES + ) : PaperweightPlatformAdapter.getBiomePalettedContainer(biomes[setSectionIndex], biomeHolderIdMap); + newSection = PaperweightPlatformAdapter.newChunkSection( + layerNo, + setArr, + adapter, + biomeRegistry, + biomeData + ); + if (PaperweightPlatformAdapter.setSectionAtomic( + nmsWorld.getWorld().getName(), + chunkPos, + levelChunkSections, + null, + newSection, + getSectionIndex + )) { + updateGet(nmsChunk, levelChunkSections, newSection, setArr, getSectionIndex); + continue; + } else { + existingSection = levelChunkSections[getSectionIndex]; + if (existingSection == null) { + LOGGER.error( + "Skipping invalid null section. chunk: {}, {} layer: {}", + chunkX, + chunkZ, + getSectionIndex + ); continue; - } else { - existingSection = levelChunkSections[getSectionIndex]; - if (existingSection == null) { - LOGGER.error("Skipping invalid null section. chunk: {}, {} layer: {}", chunkX, chunkZ, - getSectionIndex - ); - continue; - } } } + } - //ensure that the server doesn't try to tick the chunksection while we're editing it. (Again) - PaperweightPlatformAdapter.clearCounts(existingSection); - DelegateSemaphore lock = PaperweightPlatformAdapter.applyLock(existingSection); - - // Synchronize to prevent further acquisitions - synchronized (lock) { - lock.acquire(); // Wait until we have the lock - lock.release(); - try { - sectionLock.writeLock().lock(); - if (this.getChunk() != nmsChunk) { - this.levelChunk = nmsChunk; - this.sections = null; - this.reset(); - } else if (existingSection != getSections(false)[getSectionIndex]) { - this.sections[getSectionIndex] = existingSection; - this.reset(); - } else if (!Arrays.equals( - update(getSectionIndex, new char[4096], true), - loadPrivately(layerNo) - )) { - this.reset(layerNo); + //ensure that the server doesn't try to tick the chunksection while we're editing it. (Again) + PaperweightPlatformAdapter.clearCounts(existingSection); + DelegateSemaphore lock = PaperweightPlatformAdapter.applyLock(existingSection); + + // Synchronize to prevent further acquisitions + synchronized (lock) { + lock.acquire(); // Wait until we have the lock + lock.release(); + try { + sectionLock.writeLock().lock(); + if (this.getChunk() != nmsChunk) { + this.levelChunk = nmsChunk; + this.sections = null; + this.reset(); + } else if (existingSection != getSections(false)[getSectionIndex]) { + this.sections[getSectionIndex] = existingSection; + this.reset(); + } else if (!Arrays.equals(update(getSectionIndex, new char[4096], true), loadPrivately(layerNo))) { + this.reset(layerNo); /*} else if (lock.isModified()) { this.reset(layerNo);*/ - } - } finally { - sectionLock.writeLock().unlock(); } + } finally { + sectionLock.writeLock().unlock(); + } - PalettedContainer> biomeData = setBiomesToPalettedContainer( - biomes, - setSectionIndex, - existingSection.getBiomes() - ); + PalettedContainer> biomeData = setBiomesToPalettedContainer( + biomes, + setSectionIndex, + existingSection.getBiomes() + ); - newSection = PaperweightPlatformAdapter.newChunkSection( - layerNo, - this::loadPrivately, - setArr, - adapter, - biomeRegistry, - biomeData != null ? biomeData : (PalettedContainer>) existingSection.getBiomes() - ); - if (!PaperweightPlatformAdapter.setSectionAtomic( - serverLevel.getWorld().getName(), - chunkPos, - levelChunkSections, - existingSection, - newSection, + newSection = PaperweightPlatformAdapter.newChunkSection( + layerNo, + this::loadPrivately, + setArr, + adapter, + biomeRegistry, + biomeData != null ? biomeData : (PalettedContainer>) existingSection.getBiomes() + ); + if (!PaperweightPlatformAdapter.setSectionAtomic( + nmsWorld.getWorld().getName(), + chunkPos, + levelChunkSections, + existingSection, + newSection, + getSectionIndex + )) { + LOGGER.error( + "Skipping invalid null section. chunk: {}, {} layer: {}", + chunkX, + chunkZ, getSectionIndex - )) { - LOGGER.error("Skipping invalid null section. chunk: {}, {} layer: {}", chunkX, chunkZ, - getSectionIndex - ); - } else { - updateGet(nmsChunk, levelChunkSections, newSection, setArr, getSectionIndex); - } + ); + } else { + updateGet(nmsChunk, levelChunkSections, newSection, setArr, getSectionIndex); } } } + } - Map heightMaps = set.getHeightMaps(); - for (Map.Entry entry : heightMaps.entrySet()) { - PaperweightGetBlocks.this.setHeightmapToGet(entry.getKey(), entry.getValue()); - } - PaperweightGetBlocks.this.setLightingToGet( - set.getLight(), - set.getMinSectionPosition(), - set.getMaxSectionPosition() - ); - PaperweightGetBlocks.this.setSkyLightingToGet( - set.getSkyLight(), - set.getMinSectionPosition(), - set.getMaxSectionPosition() - ); - - Runnable[] syncTasks = null; + Map heightMaps = set.getHeightMaps(); + for (Map.Entry entry : heightMaps.entrySet()) { + PaperweightGetBlocks.this.setHeightmapToGet(entry.getKey(), entry.getValue()); + } + PaperweightGetBlocks.this.setLightingToGet(set.getLight(), set.getMinSectionPosition(), set.getMaxSectionPosition()); + PaperweightGetBlocks.this.setSkyLightingToGet( + set.getSkyLight(), + set.getMinSectionPosition(), + set.getMaxSectionPosition() + ); - int bx = chunkX << 4; - int bz = chunkZ << 4; + Runnable[] syncTasks = null; - // Call beacon deactivate events here synchronously - // list will be null on spigot, so this is an implicit isPaper check - if (beacons != null && !beacons.isEmpty()) { - final List finalBeacons = beacons; + int bx = chunkX << 4; + int bz = chunkZ << 4; - syncTasks = new Runnable[4]; + // Call beacon deactivate events here synchronously + // list will be null on spigot, so this is an implicit isPaper check + if (beacons != null && !beacons.isEmpty()) { + final List finalBeacons = beacons; - syncTasks[3] = () -> { - for (BlockEntity beacon : finalBeacons) { - BeaconBlockEntity.playSound(beacon.getLevel(), beacon.getBlockPos(), SoundEvents.BEACON_DEACTIVATE); - new BeaconDeactivatedEvent(CraftBlock.at(beacon.getLevel(), beacon.getBlockPos())).callEvent(); - } - }; - } + syncTasks = new Runnable[4]; - Set entityRemoves = set.getEntityRemoves(); - if (entityRemoves != null && !entityRemoves.isEmpty()) { - if (syncTasks == null) { - syncTasks = new Runnable[3]; + syncTasks[3] = () -> { + for (BlockEntity beacon : finalBeacons) { + BeaconBlockEntity.playSound(beacon.getLevel(), beacon.getBlockPos(), SoundEvents.BEACON_DEACTIVATE); + new BeaconDeactivatedEvent(CraftBlock.at(beacon.getLevel(), beacon.getBlockPos())).callEvent(); } + }; + } - syncTasks[2] = () -> { - Set entitiesRemoved = new HashSet<>(); - final List entities = PaperweightPlatformAdapter.getEntities(nmsChunk); + Set entityRemoves = set.getEntityRemoves(); + if (entityRemoves != null && !entityRemoves.isEmpty()) { + if (syncTasks == null) { + syncTasks = new Runnable[3]; + } - for (Entity entity : entities) { - UUID uuid = entity.getUUID(); - if (entityRemoves.contains(uuid)) { - if (createCopy) { - copy.storeEntity(entity); - } - removeEntity(entity); - entitiesRemoved.add(uuid); - entityRemoves.remove(uuid); + syncTasks[2] = () -> { + Set entitiesRemoved = new HashSet<>(); + final List entities = PaperweightPlatformAdapter.getEntities(nmsChunk); + + for (Entity entity : entities) { + UUID uuid = entity.getUUID(); + if (entityRemoves.contains(uuid)) { + if (createCopy) { + copy.storeEntity(entity); } + removeEntity(entity); + entitiesRemoved.add(uuid); + entityRemoves.remove(uuid); } - if (Settings.settings().EXPERIMENTAL.REMOVE_ENTITY_FROM_WORLD_ON_CHUNK_FAIL) { - for (UUID uuid : entityRemoves) { - Entity entity = serverLevel.getEntities().get(uuid); - if (entity != null) { - removeEntity(entity); - } + } + if (Settings.settings().EXPERIMENTAL.REMOVE_ENTITY_FROM_WORLD_ON_CHUNK_FAIL) { + for (UUID uuid : entityRemoves) { + Entity entity = nmsWorld.getEntities().get(uuid); + if (entity != null) { + removeEntity(entity); } } - // Only save entities that were actually removed to history - set.getEntityRemoves().clear(); - set.getEntityRemoves().addAll(entitiesRemoved); - }; - } - - Collection entities = set.entities(); - if (entities != null && !entities.isEmpty()) { - if (syncTasks == null) { - syncTasks = new Runnable[2]; } + // Only save entities that were actually removed to history + set.getEntityRemoves().clear(); + set.getEntityRemoves().addAll(entitiesRemoved); + }; + } - syncTasks[1] = () -> { - Iterator iterator = entities.iterator(); - while (iterator.hasNext()) { - final FaweCompoundTag nativeTag = iterator.next(); - final LinCompoundTag linTag = nativeTag.linTag(); - final LinStringTag idTag = linTag.findTag("Id", LinTagType.stringTag()); - final LinListTag posTag = linTag.findListTag("Pos", LinTagType.doubleTag()); - final LinListTag rotTag = linTag.findListTag("Rotation", LinTagType.floatTag()); - if (idTag == null || posTag == null || rotTag == null) { - LOGGER.error("Unknown entity tag: {}", nativeTag); - continue; - } - final double x = posTag.get(0).valueAsDouble(); - final double y = posTag.get(1).valueAsDouble(); - final double z = posTag.get(2).valueAsDouble(); - final float yaw = rotTag.get(0).valueAsFloat(); - final float pitch = rotTag.get(1).valueAsFloat(); - final String id = idTag.value(); - - EntityType type = EntityType.byString(id).orElse(null); - if (type != null) { - 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) { - tag.remove(name); - } - entity.load(tag); - entity.absMoveTo(x, y, z, yaw, pitch); - entity.setUUID(NbtUtils.uuid(nativeTag)); - if (!serverLevel.addFreshEntity(entity, CreatureSpawnEvent.SpawnReason.CUSTOM)) { - LOGGER.warn( - "Error creating entity of type `{}` in world `{}` at location `{},{},{}`", - id, - serverLevel.getWorld().getName(), - x, - y, - z - ); - // Unsuccessful create should not be saved to history - iterator.remove(); - } - } - } - } - }; + Collection entities = set.entities(); + if (entities != null && !entities.isEmpty()) { + if (syncTasks == null) { + syncTasks = new Runnable[2]; } - // set tiles - Map tiles = set.tiles(); - if (tiles != null && !tiles.isEmpty()) { - if (syncTasks == null) { - syncTasks = new Runnable[1]; - } - - syncTasks[0] = () -> { - for (final Map.Entry entry : tiles.entrySet()) { - final FaweCompoundTag nativeTag = entry.getValue(); - final BlockVector3 blockHash = entry.getKey(); - final int x = blockHash.x() + bx; - final int y = blockHash.y(); - final int z = blockHash.z() + bz; - final BlockPos pos = new BlockPos(x, y, z); - - synchronized (serverLevel) { - BlockEntity tileEntity = serverLevel.getBlockEntity(pos); - if (tileEntity == null || tileEntity.isRemoved()) { - serverLevel.removeBlockEntity(pos); - tileEntity = serverLevel.getBlockEntity(pos); + syncTasks[1] = () -> { + Iterator iterator = entities.iterator(); + while (iterator.hasNext()) { + final FaweCompoundTag nativeTag = iterator.next(); + final LinCompoundTag linTag = nativeTag.linTag(); + final LinStringTag idTag = linTag.findTag("Id", LinTagType.stringTag()); + final LinListTag posTag = linTag.findListTag("Pos", LinTagType.doubleTag()); + final LinListTag rotTag = linTag.findListTag("Rotation", LinTagType.floatTag()); + if (idTag == null || posTag == null || rotTag == null) { + LOGGER.error("Unknown entity tag: {}", nativeTag); + continue; + } + final double x = posTag.get(0).valueAsDouble(); + final double y = posTag.get(1).valueAsDouble(); + final double z = posTag.get(2).valueAsDouble(); + final float yaw = rotTag.get(0).valueAsFloat(); + final float pitch = rotTag.get(1).valueAsFloat(); + final String id = idTag.value(); + + EntityType type = EntityType.byString(id).orElse(null); + if (type != null) { + Entity entity = type.create(nmsWorld); + if (entity != null) { + final CompoundTag tag = (CompoundTag) adapter.fromNativeLin(linTag); + for (final String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) { + tag.remove(name); } - if (tileEntity != null) { - final CompoundTag tag = (CompoundTag) adapter.fromNativeLin(nativeTag.linTag()); - tag.put("x", IntTag.valueOf(x)); - tag.put("y", IntTag.valueOf(y)); - tag.put("z", IntTag.valueOf(z)); - tileEntity.loadWithComponents(tag, DedicatedServer.getServer().registryAccess()); + entity.load(tag); + entity.absMoveTo(x, y, z, yaw, pitch); + entity.setUUID(NbtUtils.uuid(nativeTag)); + if (!nmsWorld.addFreshEntity(entity, CreatureSpawnEvent.SpawnReason.CUSTOM)) { + LOGGER.warn( + "Error creating entity of type `{}` in world `{}` at location `{},{},{}`", + id, + nmsWorld.getWorld().getName(), + x, + y, + z + ); + // Unsuccessful create should not be saved to history + iterator.remove(); } } } - }; - } + } + }; + } - Runnable callback; - if (bitMask == 0 && biomes == null && !lightUpdate) { - callback = null; - } else { - int finalMask = bitMask != 0 ? bitMask : lightUpdate ? set.getBitMask() : 0; - callback = () -> { - // Set Modified - nmsChunk.setLightCorrect(true); // Set Modified - nmsChunk.mustNotSave = false; - nmsChunk.setUnsaved(true); - // send to player - if (!set - .getSideEffectSet() - .shouldApply(SideEffect.LIGHTING) || !Settings.settings().LIGHTING.DELAY_PACKET_SENDING || finalMask == 0 && biomes != null) { - this.send(); - } - if (finalizer != null) { - finalizer.run(); - } - }; + // set tiles + Map tiles = set.tiles(); + if (tiles != null && !tiles.isEmpty()) { + if (syncTasks == null) { + syncTasks = new Runnable[1]; } - if (syncTasks != null) { - QueueHandler queueHandler = Fawe.instance().getQueueHandler(); - Runnable[] finalSyncTasks = syncTasks; - // Chain the sync tasks and the callback - Callable chain = () -> { - try { - // Run the sync tasks - for (Runnable task : finalSyncTasks) { - if (task != null) { - task.run(); - } + syncTasks[0] = () -> { + for (final Map.Entry entry : tiles.entrySet()) { + final FaweCompoundTag nativeTag = entry.getValue(); + final BlockVector3 blockHash = entry.getKey(); + final int x = blockHash.x() + bx; + final int y = blockHash.y(); + final int z = blockHash.z() + bz; + final BlockPos pos = new BlockPos(x, y, z); + + synchronized (nmsWorld) { + BlockEntity tileEntity = nmsWorld.getBlockEntity(pos); + if (tileEntity == null || tileEntity.isRemoved()) { + nmsWorld.removeBlockEntity(pos); + tileEntity = nmsWorld.getBlockEntity(pos); } - if (callback == null) { - if (finalizer != null) { - queueHandler.async(finalizer, null); - } - return null; - } else { - return queueHandler.async(callback, null); + if (tileEntity != null) { + final CompoundTag tag = (CompoundTag) adapter.fromNativeLin(nativeTag.linTag()); + tag.put("x", IntTag.valueOf(x)); + tag.put("y", IntTag.valueOf(y)); + tag.put("z", IntTag.valueOf(z)); + tileEntity.loadWithComponents(tag, DedicatedServer.getServer().registryAccess()); } - } catch (Throwable e) { - e.printStackTrace(); - throw e; } - }; - //noinspection unchecked - required at compile time - return (T) (Future) queueHandler.sync(chain); - } else { - if (callback == null) { - if (finalizer != null) { - finalizer.run(); + } + }; + } + + Runnable callback; + if (bitMask == 0 && biomes == null && !lightUpdate) { + callback = null; + } else { + int finalMask = bitMask != 0 ? bitMask : lightUpdate ? set.getBitMask() : 0; + callback = () -> { + // Set Modified + nmsChunk.setLightCorrect(true); // Set Modified + nmsChunk.mustNotSave = false; + nmsChunk.setUnsaved(true); + // send to player + if (!set + .getSideEffectSet() + .shouldApply(SideEffect.LIGHTING) || !Settings.settings().LIGHTING.DELAY_PACKET_SENDING || finalMask == 0 && biomes != null) { + this.send(); + } + if (finalizer != null) { + finalizer.run(); + } + }; + } + if (syncTasks != null) { + QueueHandler queueHandler = Fawe.instance().getQueueHandler(); + Runnable[] finalSyncTasks = syncTasks; + + // Chain the sync tasks and the callback + Callable chain = () -> { + try { + // Run the sync tasks + for (Runnable task : finalSyncTasks) { + if (task != null) { + task.run(); + } } - } else { - callback.run(); + if (callback == null) { + if (finalizer != null) { + queueHandler.async(finalizer, null); + } + return null; + } else { + return queueHandler.async(callback, null); + } + } catch (Throwable e) { + LOGGER.error("Error performing final chunk calling at {},{}", chunkX, chunkZ, e); + throw e; + } + }; + //noinspection unchecked - required at compile time + return (T) (Future) queueHandler.sync(chain); + } else { + if (callback == null) { + if (finalizer != null) { + finalizer.run(); } + } else { + callback.run(); } } - return null; - } catch (Throwable e) { - e.printStackTrace(); - return null; - } finally { - forceLoadSections = true; } + return null; } private void updateGet( @@ -923,18 +850,6 @@ private void updateGet( this.blocks[layer] = arr; } - private char[] loadPrivately(int layer) { - layer -= getMinSectionPosition(); - if (super.sections[layer] != null) { - synchronized (super.sectionLocks[layer]) { - if (super.sections[layer].isFull() && super.blocks[layer] != null) { - return super.blocks[layer]; - } - } - } - return PaperweightGetBlocks.this.update(layer, null, true); - } - @Override public void send() { synchronized (sendLock) { @@ -1032,7 +947,7 @@ public char[] update(int layer, char[] data, boolean aggressive) { } return data; } catch (IllegalAccessException | InterruptedException e) { - e.printStackTrace(); + LOGGER.error("Could not read block data from palette", e); throw new RuntimeException(e); } finally { lock.release(); @@ -1074,7 +989,16 @@ public LevelChunk getChunk() { synchronized (this) { levelChunk = this.levelChunk; if (levelChunk == null) { - this.levelChunk = levelChunk = ensureLoaded(this.serverLevel, chunkX, chunkZ); + try { + this.levelChunk = levelChunk = ensureLoaded(this.serverLevel).get(); + } catch (InterruptedException | ExecutionException e) { + LOGGER.error("Could not get chunk at {},{}", chunkX, chunkZ, e); + throw new FaweException( + TextComponent.of("Could not get chunk at " + chunkX + "," + chunkZ + ": " + e.getMessage()), + FaweException.Type.OTHER, + false + ); + } } } } diff --git a/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightGetBlocks_Copy.java b/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightGetBlocks_Copy.java index 18b557b977..c791c81a0f 100644 --- a/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightGetBlocks_Copy.java +++ b/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightGetBlocks_Copy.java @@ -3,8 +3,10 @@ import com.fastasyncworldedit.core.extent.processor.heightmap.HeightMapType; import com.fastasyncworldedit.core.nbt.FaweCompoundTag; import com.fastasyncworldedit.core.queue.IBlocks; +import com.fastasyncworldedit.core.queue.IChunk; import com.fastasyncworldedit.core.queue.IChunkGet; import com.fastasyncworldedit.core.queue.IChunkSet; +import com.fastasyncworldedit.core.queue.IQueueExtent; import com.fastasyncworldedit.core.util.NbtUtils; import com.sk89q.worldedit.bukkit.WorldEditPlugin; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; @@ -257,7 +259,7 @@ public int[] getHeightMap(HeightMapType type) { } @Override - public > T call(IChunkSet set, Runnable finalize) { + public > T call(IQueueExtent owner, IChunkSet set, Runnable finalize) { return null; } 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 6881ea4e24..9c91d4bbb9 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 @@ -55,7 +55,6 @@ import net.minecraft.world.level.chunk.status.ChunkStatus; import net.minecraft.world.level.entity.PersistentEntitySectionManager; import org.apache.logging.log4j.Logger; -import org.bukkit.Bukkit; import org.bukkit.craftbukkit.CraftChunk; import javax.annotation.Nonnull; @@ -76,9 +75,8 @@ import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import java.util.function.IntFunction; import static java.lang.invoke.MethodType.methodType; @@ -258,12 +256,48 @@ static DelegateSemaphore applyLock(LevelChunkSection section) { } } } catch (Throwable e) { - e.printStackTrace(); + LOGGER.error("Error apply DelegateSemaphore", e); throw new RuntimeException(e); } } - public static LevelChunk ensureLoaded(ServerLevel serverLevel, int chunkX, int chunkZ) { + public static CompletableFuture ensureLoaded(ServerLevel serverLevel, int chunkX, int chunkZ) { + LevelChunk levelChunk = getChunkImmediatelyAsync(serverLevel, chunkX, chunkZ); + if (levelChunk != null) { + return CompletableFuture.completedFuture(levelChunk); + } + if (PaperLib.isPaper()) { + CompletableFuture future = serverLevel + .getWorld() + .getChunkAtAsync(chunkX, chunkZ, true, true) + .thenApply(chunk -> { + addTicket(serverLevel, chunkX, chunkZ); + try { + return (LevelChunk) CRAFT_CHUNK_GET_HANDLE.invoke(chunk); + } catch (Throwable e) { + LOGGER.error("Could not asynchronously load chunk at {},{}", chunkX, chunkZ, e); + return null; + } + }); + try { + if (!future.isCompletedExceptionally() || (future.isDone() && future.get() != null)) { + return future; + } + Throwable t = future.exceptionNow(); + LOGGER.error("Asynchronous chunk load at {},{} exceptionally completed immediately", chunkX, chunkZ, t); + } catch (InterruptedException | ExecutionException e) { + LOGGER.error( + "Unexpected error when getting completed future at chunk {},{}. Returning to default.", + chunkX, + chunkZ, + e + ); + } + } + return CompletableFuture.supplyAsync(() -> TaskManager.taskManager().sync(() -> serverLevel.getChunk(chunkX, chunkZ))); + } + + public static @Nullable LevelChunk getChunkImmediatelyAsync(ServerLevel serverLevel, int chunkX, int chunkZ) { if (!PaperLib.isPaper()) { LevelChunk nmsChunk = serverLevel.getChunkSource().getChunk(chunkX, chunkZ, false); if (nmsChunk != null) { @@ -272,6 +306,7 @@ public static LevelChunk ensureLoaded(ServerLevel serverLevel, int chunkX, int c if (Fawe.isMainThread()) { return serverLevel.getChunk(chunkX, chunkZ); } + return null; } else { LevelChunk nmsChunk = serverLevel.getChunkSource().getChunkAtIfCachedImmediately(chunkX, chunkZ); if (nmsChunk != null) { @@ -287,30 +322,8 @@ public static LevelChunk ensureLoaded(ServerLevel serverLevel, int chunkX, int c if (Fawe.isMainThread()) { return serverLevel.getChunk(chunkX, chunkZ); } - CompletableFuture future = serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true); - try { - CraftChunk chunk; - try { - chunk = (CraftChunk) future.get(10, TimeUnit.SECONDS); - } catch (TimeoutException e) { - String world = serverLevel.getWorld().getName(); - // We've already taken 10 seconds we can afford to wait a little here. - boolean loaded = TaskManager.taskManager().sync(() -> Bukkit.getWorld(world) != null); - if (loaded) { - LOGGER.warn("Chunk {},{} failed to load in 10 seconds in world {}. Retrying...", chunkX, chunkZ, world); - // Retry chunk load - chunk = (CraftChunk) serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true).get(); - } else { - throw new UnsupportedOperationException("Cannot load chunk from unloaded world " + world + "!"); - } - } - addTicket(serverLevel, chunkX, chunkZ); - return (LevelChunk) CRAFT_CHUNK_GET_HANDLE.invoke(chunk); - } catch (Throwable e) { - e.printStackTrace(); - } + return null; } - return TaskManager.taskManager().sync(() -> serverLevel.getChunk(chunkX, chunkZ)); } private static void addTicket(ServerLevel serverLevel, int chunkX, int chunkZ) { @@ -649,7 +662,7 @@ static void removeBeacon(BlockEntity beacon, LevelChunk levelChunk) { } methodremoveTickingBlockEntity.invoke(levelChunk, beacon.getBlockPos()); } catch (Throwable throwable) { - throwable.printStackTrace(); + LOGGER.error("Error removing beacon", throwable); } } diff --git a/worldedit-bukkit/adapters/adapter-1_21_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_3/PaperweightGetBlocks.java b/worldedit-bukkit/adapters/adapter-1_21_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_3/PaperweightGetBlocks.java index 5b3aae691a..26e310e412 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_3/PaperweightGetBlocks.java +++ b/worldedit-bukkit/adapters/adapter-1_21_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_3/PaperweightGetBlocks.java @@ -1,18 +1,15 @@ package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_21_3; -import com.fastasyncworldedit.bukkit.adapter.BukkitGetBlocks; +import com.fastasyncworldedit.bukkit.adapter.AbstractBukkitGetBlocks; import com.fastasyncworldedit.bukkit.adapter.DelegateSemaphore; -import com.fastasyncworldedit.core.Fawe; import com.fastasyncworldedit.core.FaweCache; import com.fastasyncworldedit.core.configuration.Settings; import com.fastasyncworldedit.core.extent.processor.heightmap.HeightMapType; +import com.fastasyncworldedit.core.internal.exception.FaweException; 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; -import com.fastasyncworldedit.core.queue.implementation.QueueHandler; -import com.fastasyncworldedit.core.queue.implementation.blocks.CharGetBlocks; import com.fastasyncworldedit.core.util.MathMan; import com.fastasyncworldedit.core.util.NbtUtils; import com.fastasyncworldedit.core.util.collection.AdaptedMap; @@ -22,6 +19,7 @@ import com.sk89q.worldedit.internal.util.LogManagerCompat; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.util.SideEffect; +import com.sk89q.worldedit.util.formatting.text.TextComponent; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.biome.BiomeTypes; import com.sk89q.worldedit.world.block.BlockTypesCache; @@ -83,18 +81,17 @@ import java.util.Map; import java.util.Set; import java.util.UUID; -import java.util.concurrent.Callable; -import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.Semaphore; import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.Function; import static net.minecraft.core.registries.Registries.BIOME; -public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBlocks { +public class PaperweightGetBlocks extends AbstractBukkitGetBlocks { private static final Logger LOGGER = LogManagerCompat.getLogger(); @@ -106,84 +103,25 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc .getInstance() .getBukkitImplAdapter()); private final ReadWriteLock sectionLock = new ReentrantReadWriteLock(); - private final ReentrantLock callLock = new ReentrantLock(); - 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; - private final int maxSectionPosition; private final Registry biomeRegistry; private final IdMap> biomeHolderIdMap; - private final ConcurrentHashMap copies = new ConcurrentHashMap<>(); private final Object sendLock = new Object(); - private LevelChunkSection[] sections; private LevelChunk levelChunk; + private LevelChunkSection[] sections; private DataLayer[] blockLight; private DataLayer[] skyLight; - private boolean createCopy = false; - private boolean forceLoadSections = true; private boolean lightUpdate = false; - private int copyKey = 0; public PaperweightGetBlocks(World world, int chunkX, int chunkZ) { this(((CraftWorld) world).getHandle(), chunkX, chunkZ); } public PaperweightGetBlocks(ServerLevel serverLevel, int chunkX, int chunkZ) { - super(serverLevel.getMinY() >> 4, (serverLevel.getMaxY() - 1) >> 4); - this.serverLevel = serverLevel; - this.chunkX = chunkX; - this.chunkZ = chunkZ; - this.minHeight = serverLevel.getMinY(); - this.maxHeight = serverLevel.getMaxY() - 1; // Minecraft max limit is exclusive. - this.minSectionPosition = minHeight >> 4; - this.maxSectionPosition = maxHeight >> 4; + super(serverLevel, chunkX, chunkZ, serverLevel.getMinY(), serverLevel.getMaxY() - 1); this.skyLight = new DataLayer[getSectionCount()]; this.blockLight = new DataLayer[getSectionCount()]; this.biomeRegistry = serverLevel.registryAccess().lookupOrThrow(BIOME); this.biomeHolderIdMap = biomeRegistry.asHolderIdMap(); - this.chunkPos = new IntPair(chunkX, chunkZ); - } - - public int getChunkX() { - return chunkX; - } - - public int getChunkZ() { - return chunkZ; - } - - @Override - public boolean isCreateCopy() { - return createCopy; - } - - @Override - public int setCreateCopy(boolean createCopy) { - if (!callLock.isHeldByCurrentThread()) { - throw new IllegalStateException("Attempting to set if chunk GET should create copy, but it is not call-locked."); - } - this.createCopy = createCopy; - // Increment regardless of whether copy will be created or not to return null from getCopy() - return ++this.copyKey; - } - - @Override - public IChunkGet getCopy(final int key) { - return copies.remove(key); - } - - @Override - public void lockCall() { - this.callLock.lock(); - } - - @Override - public void unlockCall() { - this.callLock.unlock(); } @Override @@ -193,7 +131,7 @@ public void setLightingToGet(char[][] light, int minSectionPosition, int maxSect try { fillLightNibble(light, LightLayer.BLOCK, minSectionPosition, maxSectionPosition); } catch (Throwable e) { - e.printStackTrace(); + LOGGER.error("Error setting lighting to get", e); } } } @@ -205,7 +143,7 @@ public void setSkyLightingToGet(char[][] light, int minSectionPosition, int maxS try { fillLightNibble(light, LightLayer.SKY, minSectionPosition, maxSectionPosition); } catch (Throwable e) { - e.printStackTrace(); + LOGGER.error("Error setting sky lighting to get", e); } } } @@ -220,16 +158,6 @@ public void setHeightmapToGet(HeightMapType type, int[] data) { heightMap.setRawData(getChunk(), nativeType, bitArray.getData()); } - @Override - public int getMaxY() { - return maxHeight; - } - - @Override - public int getMinY() { - return minHeight; - } - @Override public BiomeType getBiomeType(int x, int y, int z) { LevelChunkSection section = getSections(false)[(y >> 4) - getMinSectionPosition()]; @@ -348,7 +276,7 @@ public int[] getHeightMap(HeightMapType type) { @Override public @Nullable FaweCompoundTag entity(final UUID uuid) { - ensureLoaded(serverLevel, chunkX, chunkZ); + ensureLoaded(serverLevel); List entities = PaperweightPlatformAdapter.getEntities(getChunk()); Entity entity = null; for (Entity e : entities) { @@ -371,7 +299,7 @@ public int[] getHeightMap(HeightMapType type) { @Override public Collection entities() { - ensureLoaded(serverLevel, chunkX, chunkZ); + ensureLoaded(serverLevel); List entities = PaperweightPlatformAdapter.getEntities(getChunk()); if (entities.isEmpty()) { return Collections.emptyList(); @@ -421,18 +349,19 @@ private void removeEntity(Entity entity) { entity.discard(); } - public LevelChunk ensureLoaded(ServerLevel nmsWorld, int chunkX, int chunkZ) { + @Override + public CompletableFuture ensureLoaded(ServerLevel nmsWorld) { return PaperweightPlatformAdapter.ensureLoaded(nmsWorld, chunkX, chunkZ); } @Override - @SuppressWarnings("rawtypes") - public synchronized > T call(IChunkSet set, Runnable finalizer) { - if (!callLock.isHeldByCurrentThread()) { - throw new IllegalStateException("Attempted to call chunk GET but chunk was not call-locked."); - } - forceLoadSections = false; - LevelChunk nmsChunk = ensureLoaded(serverLevel, chunkX, chunkZ); + protected > T internalCall( + IChunkSet set, + Runnable finalizer, + int copyKey, + LevelChunk nmsChunk, + ServerLevel nmsWorld + ) throws Exception { PaperweightGetBlocks_Copy copy = createCopy ? new PaperweightGetBlocks_Copy(nmsChunk) : null; if (createCopy) { if (copies.containsKey(copyKey)) { @@ -440,458 +369,414 @@ public synchronized > T call(IChunkSet set, Runnable finaliz } copies.put(copyKey, copy); } - try { - // Remove existing tiles. Create a copy so that we can remove blocks - Map chunkTiles = new HashMap<>(nmsChunk.getBlockEntities()); - List beacons = null; - if (!chunkTiles.isEmpty()) { - for (Map.Entry entry : chunkTiles.entrySet()) { - final BlockPos pos = entry.getKey(); - final int lx = pos.getX() & 15; - final int ly = pos.getY(); - final int lz = pos.getZ() & 15; - final int layer = ly >> 4; - if (!set.hasSection(layer)) { - continue; - } + // Remove existing tiles. Create a copy so that we can remove blocks + Map chunkTiles = new HashMap<>(nmsChunk.getBlockEntities()); + List beacons = null; + if (!chunkTiles.isEmpty()) { + for (Map.Entry entry : chunkTiles.entrySet()) { + final BlockPos pos = entry.getKey(); + final int lx = pos.getX() & 15; + final int ly = pos.getY(); + final int lz = pos.getZ() & 15; + final int layer = ly >> 4; + if (!set.hasSection(layer)) { + continue; + } - int ordinal = set.getBlock(lx, ly, lz).getOrdinal(); - if (ordinal != BlockTypesCache.ReservedIDs.__RESERVED__) { - BlockEntity tile = entry.getValue(); - if (PaperLib.isPaper() && tile instanceof BeaconBlockEntity) { - if (beacons == null) { - beacons = new ArrayList<>(); - } - beacons.add(tile); - PaperweightPlatformAdapter.removeBeacon(tile, nmsChunk); - continue; - } - nmsChunk.removeBlockEntity(tile.getBlockPos()); - if (createCopy) { - copy.storeTile(tile); + int ordinal = set.getBlock(lx, ly, lz).getOrdinal(); + if (ordinal != BlockTypesCache.ReservedIDs.__RESERVED__) { + BlockEntity tile = entry.getValue(); + if (PaperLib.isPaper() && tile instanceof BeaconBlockEntity) { + if (beacons == null) { + beacons = new ArrayList<>(); } + beacons.add(tile); + PaperweightPlatformAdapter.removeBeacon(tile, nmsChunk); + continue; + } + nmsChunk.removeBlockEntity(tile.getBlockPos()); + if (createCopy) { + copy.storeTile(tile); } } } - final BiomeType[][] biomes = set.getBiomes(); + } + final BiomeType[][] biomes = set.getBiomes(); - int bitMask = 0; - synchronized (nmsChunk) { - LevelChunkSection[] levelChunkSections = nmsChunk.getSections(); + int bitMask = 0; + synchronized (nmsChunk) { + LevelChunkSection[] levelChunkSections = nmsChunk.getSections(); - for (int layerNo = getMinSectionPosition(); layerNo <= getMaxSectionPosition(); layerNo++) { + for (int layerNo = getMinSectionPosition(); layerNo <= getMaxSectionPosition(); layerNo++) { - int getSectionIndex = layerNo - getMinSectionPosition(); - int setSectionIndex = layerNo - set.getMinSectionPosition(); + int getSectionIndex = layerNo - getMinSectionPosition(); + int setSectionIndex = layerNo - set.getMinSectionPosition(); - if (!set.hasSection(layerNo)) { - // No blocks, but might be biomes present. Handle this lazily. - if (biomes == null) { - continue; - } - if (layerNo < set.getMinSectionPosition() || layerNo > set.getMaxSectionPosition()) { - continue; - } - if (biomes[setSectionIndex] != null) { - synchronized (super.sectionLocks[getSectionIndex]) { - LevelChunkSection existingSection = levelChunkSections[getSectionIndex]; - if (createCopy && existingSection != null) { - copy.storeBiomes(getSectionIndex, existingSection.getBiomes()); - } + if (!set.hasSection(layerNo)) { + // No blocks, but might be biomes present. Handle this lazily. + if (biomes == null) { + continue; + } + if (layerNo < set.getMinSectionPosition() || layerNo > set.getMaxSectionPosition()) { + continue; + } + if (biomes[setSectionIndex] != null) { + synchronized (super.sectionLocks[getSectionIndex]) { + LevelChunkSection existingSection = levelChunkSections[getSectionIndex]; + if (createCopy && existingSection != null) { + copy.storeBiomes(getSectionIndex, existingSection.getBiomes()); + } - if (existingSection == null) { - PalettedContainer> biomeData = PaperweightPlatformAdapter.getBiomePalettedContainer( - biomes[setSectionIndex], - biomeHolderIdMap - ); - LevelChunkSection newSection = PaperweightPlatformAdapter.newChunkSection( - layerNo, - new char[4096], - adapter, - biomeRegistry, - biomeData - ); - if (PaperweightPlatformAdapter.setSectionAtomic( - serverLevel.getWorld().getName(), - chunkPos, - levelChunkSections, - null, - newSection, - getSectionIndex - )) { - updateGet(nmsChunk, levelChunkSections, newSection, new char[4096], getSectionIndex); - continue; - } else { - existingSection = levelChunkSections[getSectionIndex]; - if (existingSection == null) { - LOGGER.error("Skipping invalid null section. chunk: {}, {} layer: {}", chunkX, chunkZ, - getSectionIndex - ); - continue; - } - } + if (existingSection == null) { + PalettedContainer> biomeData = PaperweightPlatformAdapter.getBiomePalettedContainer( + biomes[setSectionIndex], + biomeHolderIdMap + ); + LevelChunkSection newSection = PaperweightPlatformAdapter.newChunkSection( + layerNo, + new char[4096], + adapter, + biomeRegistry, + biomeData + ); + if (PaperweightPlatformAdapter.setSectionAtomic( + nmsWorld.getWorld().getName(), + chunkPos, + levelChunkSections, + null, + newSection, + getSectionIndex + )) { + updateGet(nmsChunk, levelChunkSections, newSection, new char[4096], getSectionIndex); + continue; } else { - PalettedContainer> paletteBiomes = setBiomesToPalettedContainer( - biomes, - setSectionIndex, - existingSection.getBiomes() - ); - if (paletteBiomes != null) { - PaperweightPlatformAdapter.setBiomesToChunkSection(existingSection, paletteBiomes); + existingSection = levelChunkSections[getSectionIndex]; + if (existingSection == null) { + LOGGER.error("Skipping invalid null section. chunk: {}, {} layer: {}", chunkX, chunkZ, + getSectionIndex + ); + continue; } } + } else { + PalettedContainer> paletteBiomes = setBiomesToPalettedContainer( + biomes, + setSectionIndex, + existingSection.getBiomes() + ); + if (paletteBiomes != null) { + PaperweightPlatformAdapter.setBiomesToChunkSection(existingSection, paletteBiomes); + } } } - continue; } + continue; + } - bitMask |= 1 << getSectionIndex; - - // setArr is modified by PaperweightPlatformAdapter#newChunkSection. This is in order to write changes to - // this chunk GET when #updateGet is called. Future dords, please listen this time. - char[] tmp = set.load(layerNo); - char[] setArr = new char[tmp.length]; - System.arraycopy(tmp, 0, setArr, 0, tmp.length); - - // synchronise on internal section to avoid circular locking with a continuing edit if the chunk was - // submitted to keep loaded internal chunks to queue target size. - synchronized (super.sectionLocks[getSectionIndex]) { + bitMask |= 1 << getSectionIndex; - LevelChunkSection newSection; - LevelChunkSection existingSection = levelChunkSections[getSectionIndex]; - // Don't attempt to tick section whilst we're editing - if (existingSection != null) { - PaperweightPlatformAdapter.clearCounts(existingSection); - } + // setArr is modified by PaperweightPlatformAdapter#newChunkSection. This is in order to write changes to + // this chunk GET when #updateGet is called. Future dords, please listen this time. + char[] tmp = set.load(layerNo); + char[] setArr = new char[tmp.length]; + System.arraycopy(tmp, 0, setArr, 0, tmp.length); - if (createCopy) { - char[] tmpLoad = loadPrivately(layerNo); - char[] copyArr = new char[4096]; - System.arraycopy(tmpLoad, 0, copyArr, 0, 4096); - copy.storeSection(getSectionIndex, copyArr); - if (biomes != null && existingSection != null) { - copy.storeBiomes(getSectionIndex, existingSection.getBiomes()); - } - } - - if (existingSection == null) { - PalettedContainer> biomeData = biomes == null ? new PalettedContainer<>( - biomeHolderIdMap, - biomeHolderIdMap.byIdOrThrow(adapter.getInternalBiomeId(BiomeTypes.PLAINS)), - PalettedContainer.Strategy.SECTION_BIOMES - ) : PaperweightPlatformAdapter.getBiomePalettedContainer(biomes[setSectionIndex], biomeHolderIdMap); - newSection = PaperweightPlatformAdapter.newChunkSection( - layerNo, - setArr, - adapter, - biomeRegistry, - biomeData - ); - if (PaperweightPlatformAdapter.setSectionAtomic( - serverLevel.getWorld().getName(), - chunkPos, - levelChunkSections, - null, - newSection, - getSectionIndex - )) { - updateGet(nmsChunk, levelChunkSections, newSection, setArr, getSectionIndex); - continue; - } else { - existingSection = levelChunkSections[getSectionIndex]; - if (existingSection == null) { - LOGGER.error("Skipping invalid null section. chunk: {}, {} layer: {}", chunkX, chunkZ, - getSectionIndex - ); - continue; - } - } - } + // synchronise on internal section to avoid circular locking with a continuing edit if the chunk was + // submitted to keep loaded internal chunks to queue target size. + synchronized (super.sectionLocks[getSectionIndex]) { - //ensure that the server doesn't try to tick the chunksection while we're editing it. (Again) + LevelChunkSection newSection; + LevelChunkSection existingSection = levelChunkSections[getSectionIndex]; + // Don't attempt to tick section whilst we're editing + if (existingSection != null) { PaperweightPlatformAdapter.clearCounts(existingSection); - DelegateSemaphore lock = PaperweightPlatformAdapter.applyLock(existingSection); - - // Synchronize to prevent further acquisitions - synchronized (lock) { - lock.acquire(); // Wait until we have the lock - lock.release(); - try { - sectionLock.writeLock().lock(); - if (this.getChunk() != nmsChunk) { - this.levelChunk = nmsChunk; - this.sections = null; - this.reset(); - } else if (existingSection != getSections(false)[getSectionIndex]) { - this.sections[getSectionIndex] = existingSection; - this.reset(); - } else if (!Arrays.equals( - update(getSectionIndex, new char[4096], true), - loadPrivately(layerNo) - )) { - this.reset(layerNo); - /*} else if (lock.isModified()) { - this.reset(layerNo);*/ - } - } finally { - sectionLock.writeLock().unlock(); - } + } - PalettedContainer> biomeData = setBiomesToPalettedContainer( - biomes, - setSectionIndex, - existingSection.getBiomes() - ); + if (createCopy) { + char[] tmpLoad = loadPrivately(layerNo); + char[] copyArr = new char[4096]; + System.arraycopy(tmpLoad, 0, copyArr, 0, 4096); + copy.storeSection(getSectionIndex, copyArr); + if (biomes != null && existingSection != null) { + copy.storeBiomes(getSectionIndex, existingSection.getBiomes()); + } + } - newSection = PaperweightPlatformAdapter.newChunkSection( - layerNo, - this::loadPrivately, - setArr, - adapter, - biomeRegistry, - biomeData != null ? biomeData : (PalettedContainer>) existingSection.getBiomes() - ); - if (!PaperweightPlatformAdapter.setSectionAtomic( - serverLevel.getWorld().getName(), - chunkPos, - levelChunkSections, - existingSection, - newSection, - getSectionIndex - )) { + if (existingSection == null) { + PalettedContainer> biomeData = biomes == null ? new PalettedContainer<>( + biomeHolderIdMap, + biomeHolderIdMap.byIdOrThrow(adapter.getInternalBiomeId(BiomeTypes.PLAINS)), + PalettedContainer.Strategy.SECTION_BIOMES + ) : PaperweightPlatformAdapter.getBiomePalettedContainer(biomes[setSectionIndex], biomeHolderIdMap); + newSection = PaperweightPlatformAdapter.newChunkSection( + layerNo, + setArr, + adapter, + biomeRegistry, + biomeData + ); + if (PaperweightPlatformAdapter.setSectionAtomic( + nmsWorld.getWorld().getName(), + chunkPos, + levelChunkSections, + null, + newSection, + getSectionIndex + )) { + updateGet(nmsChunk, levelChunkSections, newSection, setArr, getSectionIndex); + continue; + } else { + existingSection = levelChunkSections[getSectionIndex]; + if (existingSection == null) { LOGGER.error("Skipping invalid null section. chunk: {}, {} layer: {}", chunkX, chunkZ, getSectionIndex ); - } else { - updateGet(nmsChunk, levelChunkSections, newSection, setArr, getSectionIndex); + continue; } } } - } - - Map heightMaps = set.getHeightMaps(); - for (Map.Entry entry : heightMaps.entrySet()) { - PaperweightGetBlocks.this.setHeightmapToGet(entry.getKey(), entry.getValue()); - } - PaperweightGetBlocks.this.setLightingToGet( - set.getLight(), - set.getMinSectionPosition(), - set.getMaxSectionPosition() - ); - PaperweightGetBlocks.this.setSkyLightingToGet( - set.getSkyLight(), - set.getMinSectionPosition(), - set.getMaxSectionPosition() - ); - - Runnable[] syncTasks = null; - int bx = chunkX << 4; - int bz = chunkZ << 4; + //ensure that the server doesn't try to tick the chunksection while we're editing it. (Again) + PaperweightPlatformAdapter.clearCounts(existingSection); + DelegateSemaphore lock = PaperweightPlatformAdapter.applyLock(existingSection); - // Call beacon deactivate events here synchronously - // list will be null on spigot, so this is an implicit isPaper check - if (beacons != null && !beacons.isEmpty()) { - final List finalBeacons = beacons; + // Synchronize to prevent further acquisitions + synchronized (lock) { + lock.acquire(); // Wait until we have the lock + lock.release(); + try { + sectionLock.writeLock().lock(); + if (this.getChunk() != nmsChunk) { + this.levelChunk = nmsChunk; + this.sections = null; + this.reset(); + } else if (existingSection != getSections(false)[getSectionIndex]) { + this.sections[getSectionIndex] = existingSection; + this.reset(); + } else if (!Arrays.equals( + update(getSectionIndex, new char[4096], true), + loadPrivately(layerNo) + )) { + this.reset(layerNo); + /*} else if (lock.isModified()) { + this.reset(layerNo);*/ + } + } finally { + sectionLock.writeLock().unlock(); + } - syncTasks = new Runnable[4]; + PalettedContainer> biomeData = setBiomesToPalettedContainer( + biomes, + setSectionIndex, + existingSection.getBiomes() + ); - syncTasks[3] = () -> { - for (BlockEntity beacon : finalBeacons) { - BeaconBlockEntity.playSound(beacon.getLevel(), beacon.getBlockPos(), SoundEvents.BEACON_DEACTIVATE); - new BeaconDeactivatedEvent(CraftBlock.at(beacon.getLevel(), beacon.getBlockPos())).callEvent(); + newSection = PaperweightPlatformAdapter.newChunkSection( + layerNo, + this::loadPrivately, + setArr, + adapter, + biomeRegistry, + biomeData != null ? biomeData : (PalettedContainer>) existingSection.getBiomes() + ); + if (!PaperweightPlatformAdapter.setSectionAtomic( + nmsWorld.getWorld().getName(), + chunkPos, + levelChunkSections, + existingSection, + newSection, + getSectionIndex + )) { + LOGGER.error("Skipping invalid null section. chunk: {}, {} layer: {}", chunkX, chunkZ, + getSectionIndex + ); + } else { + updateGet(nmsChunk, levelChunkSections, newSection, setArr, getSectionIndex); } - }; + } } + } - Set entityRemoves = set.getEntityRemoves(); - if (entityRemoves != null && !entityRemoves.isEmpty()) { - if (syncTasks == null) { - syncTasks = new Runnable[3]; + Map heightMaps = set.getHeightMaps(); + for (Map.Entry entry : heightMaps.entrySet()) { + PaperweightGetBlocks.this.setHeightmapToGet(entry.getKey(), entry.getValue()); + } + PaperweightGetBlocks.this.setLightingToGet( + set.getLight(), + set.getMinSectionPosition(), + set.getMaxSectionPosition() + ); + PaperweightGetBlocks.this.setSkyLightingToGet( + set.getSkyLight(), + set.getMinSectionPosition(), + set.getMaxSectionPosition() + ); + + Runnable[] syncTasks = null; + + int bx = chunkX << 4; + int bz = chunkZ << 4; + + // Call beacon deactivate events here synchronously + // list will be null on spigot, so this is an implicit isPaper check + if (beacons != null && !beacons.isEmpty()) { + final List finalBeacons = beacons; + + syncTasks = new Runnable[4]; + + syncTasks[3] = () -> { + for (BlockEntity beacon : finalBeacons) { + BeaconBlockEntity.playSound(beacon.getLevel(), beacon.getBlockPos(), SoundEvents.BEACON_DEACTIVATE); + new BeaconDeactivatedEvent(CraftBlock.at(beacon.getLevel(), beacon.getBlockPos())).callEvent(); } + }; + } - syncTasks[2] = () -> { - Set entitiesRemoved = new HashSet<>(); - final List entities = PaperweightPlatformAdapter.getEntities(nmsChunk); + Set entityRemoves = set.getEntityRemoves(); + if (entityRemoves != null && !entityRemoves.isEmpty()) { + if (syncTasks == null) { + syncTasks = new Runnable[3]; + } - for (Entity entity : entities) { - UUID uuid = entity.getUUID(); - if (entityRemoves.contains(uuid)) { - if (createCopy) { - copy.storeEntity(entity); - } - removeEntity(entity); - entitiesRemoved.add(uuid); - entityRemoves.remove(uuid); + syncTasks[2] = () -> { + Set entitiesRemoved = new HashSet<>(); + final List entities = PaperweightPlatformAdapter.getEntities(nmsChunk); + + for (Entity entity : entities) { + UUID uuid = entity.getUUID(); + if (entityRemoves.contains(uuid)) { + if (createCopy) { + copy.storeEntity(entity); } + removeEntity(entity); + entitiesRemoved.add(uuid); + entityRemoves.remove(uuid); } - if (Settings.settings().EXPERIMENTAL.REMOVE_ENTITY_FROM_WORLD_ON_CHUNK_FAIL) { - for (UUID uuid : entityRemoves) { - Entity entity = serverLevel.getEntities().get(uuid); - if (entity != null) { - removeEntity(entity); - } + } + if (Settings.settings().EXPERIMENTAL.REMOVE_ENTITY_FROM_WORLD_ON_CHUNK_FAIL) { + for (UUID uuid : entityRemoves) { + Entity entity = serverLevel.getEntities().get(uuid); + if (entity != null) { + removeEntity(entity); } } - // Only save entities that were actually removed to history - set.getEntityRemoves().clear(); - set.getEntityRemoves().addAll(entitiesRemoved); - }; - } - - Collection entities = set.entities(); - if (entities != null && !entities.isEmpty()) { - if (syncTasks == null) { - syncTasks = new Runnable[2]; } + // Only save entities that were actually removed to history + set.getEntityRemoves().clear(); + set.getEntityRemoves().addAll(entitiesRemoved); + }; + } - syncTasks[1] = () -> { - Iterator iterator = entities.iterator(); - while (iterator.hasNext()) { - final FaweCompoundTag nativeTag = iterator.next(); - final LinCompoundTag linTag = nativeTag.linTag(); - final LinStringTag idTag = linTag.findTag("Id", LinTagType.stringTag()); - final LinListTag posTag = linTag.findListTag("Pos", LinTagType.doubleTag()); - final LinListTag rotTag = linTag.findListTag("Rotation", LinTagType.floatTag()); - if (idTag == null || posTag == null || rotTag == null) { - LOGGER.error("Unknown entity tag: {}", nativeTag); - continue; - } - final double x = posTag.get(0).valueAsDouble(); - final double y = posTag.get(1).valueAsDouble(); - final double z = posTag.get(2).valueAsDouble(); - final float yaw = rotTag.get(0).valueAsFloat(); - final float pitch = rotTag.get(1).valueAsFloat(); - final String id = idTag.value(); - - EntityType type = EntityType.byString(id).orElse(null); - if (type != null) { - Entity entity = type.create(serverLevel, EntitySpawnReason.COMMAND); - if (entity != null) { - final CompoundTag tag = (CompoundTag) adapter.fromNativeLin(linTag); - for (final String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) { - tag.remove(name); - } - entity.load(tag); - entity.absMoveTo(x, y, z, yaw, pitch); - entity.setUUID(NbtUtils.uuid(nativeTag)); - if (!serverLevel.addFreshEntity(entity, CreatureSpawnEvent.SpawnReason.CUSTOM)) { - LOGGER.warn( - "Error creating entity of type `{}` in world `{}` at location `{},{},{}`", - id, - serverLevel.getWorld().getName(), - x, - y, - z - ); - // Unsuccessful create should not be saved to history - iterator.remove(); - } - } - } - } - }; + Collection entities = set.entities(); + if (entities != null && !entities.isEmpty()) { + if (syncTasks == null) { + syncTasks = new Runnable[2]; } - // set tiles - Map tiles = set.tiles(); - if (tiles != null && !tiles.isEmpty()) { - if (syncTasks == null) { - syncTasks = new Runnable[1]; - } - - syncTasks[0] = () -> { - for (final Map.Entry entry : tiles.entrySet()) { - final FaweCompoundTag nativeTag = entry.getValue(); - final BlockVector3 blockHash = entry.getKey(); - final int x = blockHash.x() + bx; - final int y = blockHash.y(); - final int z = blockHash.z() + bz; - final BlockPos pos = new BlockPos(x, y, z); - - synchronized (serverLevel) { - BlockEntity tileEntity = serverLevel.getBlockEntity(pos); - if (tileEntity == null || tileEntity.isRemoved()) { - serverLevel.removeBlockEntity(pos); - tileEntity = serverLevel.getBlockEntity(pos); + syncTasks[1] = () -> { + Iterator iterator = entities.iterator(); + while (iterator.hasNext()) { + final FaweCompoundTag nativeTag = iterator.next(); + final LinCompoundTag linTag = nativeTag.linTag(); + final LinStringTag idTag = linTag.findTag("Id", LinTagType.stringTag()); + final LinListTag posTag = linTag.findListTag("Pos", LinTagType.doubleTag()); + final LinListTag rotTag = linTag.findListTag("Rotation", LinTagType.floatTag()); + if (idTag == null || posTag == null || rotTag == null) { + LOGGER.error("Unknown entity tag: {}", nativeTag); + continue; + } + final double x = posTag.get(0).valueAsDouble(); + final double y = posTag.get(1).valueAsDouble(); + final double z = posTag.get(2).valueAsDouble(); + final float yaw = rotTag.get(0).valueAsFloat(); + final float pitch = rotTag.get(1).valueAsFloat(); + final String id = idTag.value(); + + EntityType type = EntityType.byString(id).orElse(null); + if (type != null) { + Entity entity = type.create(nmsWorld, EntitySpawnReason.COMMAND); + if (entity != null) { + final CompoundTag tag = (CompoundTag) adapter.fromNativeLin(linTag); + for (final String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) { + tag.remove(name); } - if (tileEntity != null) { - final CompoundTag tag = (CompoundTag) adapter.fromNativeLin(nativeTag.linTag()); - tag.put("x", IntTag.valueOf(x)); - tag.put("y", IntTag.valueOf(y)); - tag.put("z", IntTag.valueOf(z)); - tileEntity.loadWithComponents(tag, DedicatedServer.getServer().registryAccess()); + entity.load(tag); + entity.absMoveTo(x, y, z, yaw, pitch); + entity.setUUID(NbtUtils.uuid(nativeTag)); + if (!nmsWorld.addFreshEntity(entity, CreatureSpawnEvent.SpawnReason.CUSTOM)) { + LOGGER.warn( + "Error creating entity of type `{}` in world `{}` at location `{},{},{}`", + id, + nmsWorld.getWorld().getName(), + x, + y, + z + ); + // Unsuccessful create should not be saved to history + iterator.remove(); } } } - }; - } + } + }; + } - Runnable callback; - if (bitMask == 0 && biomes == null && !lightUpdate) { - callback = null; - } else { - int finalMask = bitMask != 0 ? bitMask : lightUpdate ? set.getBitMask() : 0; - callback = () -> { - // Set Modified - nmsChunk.setLightCorrect(true); // Set Modified - nmsChunk.mustNotSave = false; - nmsChunk.markUnsaved(); - // send to player - if (!set - .getSideEffectSet() - .shouldApply(SideEffect.LIGHTING) || !Settings.settings().LIGHTING.DELAY_PACKET_SENDING || finalMask == 0 && biomes != null) { - this.send(); - } - if (finalizer != null) { - finalizer.run(); - } - }; + // set tiles + Map tiles = set.tiles(); + if (tiles != null && !tiles.isEmpty()) { + if (syncTasks == null) { + syncTasks = new Runnable[1]; } - if (syncTasks != null) { - QueueHandler queueHandler = Fawe.instance().getQueueHandler(); - Runnable[] finalSyncTasks = syncTasks; - // Chain the sync tasks and the callback - Callable chain = () -> { - try { - // Run the sync tasks - for (Runnable task : finalSyncTasks) { - if (task != null) { - task.run(); - } + syncTasks[0] = () -> { + for (final Map.Entry entry : tiles.entrySet()) { + final FaweCompoundTag nativeTag = entry.getValue(); + final BlockVector3 blockHash = entry.getKey(); + final int x = blockHash.x() + bx; + final int y = blockHash.y(); + final int z = blockHash.z() + bz; + final BlockPos pos = new BlockPos(x, y, z); + + synchronized (nmsWorld) { + BlockEntity tileEntity = nmsWorld.getBlockEntity(pos); + if (tileEntity == null || tileEntity.isRemoved()) { + nmsWorld.removeBlockEntity(pos); + tileEntity = nmsWorld.getBlockEntity(pos); } - if (callback == null) { - if (finalizer != null) { - queueHandler.async(finalizer, null); - } - return null; - } else { - return queueHandler.async(callback, null); + if (tileEntity != null) { + final CompoundTag tag = (CompoundTag) adapter.fromNativeLin(nativeTag.linTag()); + tag.put("x", IntTag.valueOf(x)); + tag.put("y", IntTag.valueOf(y)); + tag.put("z", IntTag.valueOf(z)); + tileEntity.loadWithComponents(tag, DedicatedServer.getServer().registryAccess()); } - } catch (Throwable e) { - e.printStackTrace(); - throw e; - } - }; - //noinspection unchecked - required at compile time - return (T) (Future) queueHandler.sync(chain); - } else { - if (callback == null) { - if (finalizer != null) { - finalizer.run(); } - } else { - callback.run(); } - } + }; } - return null; - } catch (Throwable e) { - e.printStackTrace(); - return null; - } finally { - forceLoadSections = true; + + Runnable callback; + if (bitMask == 0 && biomes == null && !lightUpdate) { + callback = null; + } else { + int finalMask = bitMask != 0 ? bitMask : lightUpdate ? set.getBitMask() : 0; + callback = () -> { + // Set Modified + nmsChunk.setLightCorrect(true); // Set Modified + nmsChunk.mustNotSave = false; + nmsChunk.markUnsaved(); + // send to player + if (!set + .getSideEffectSet() + .shouldApply(SideEffect.LIGHTING) || !Settings.settings().LIGHTING.DELAY_PACKET_SENDING || finalMask == 0 && biomes != null) { + this.send(); + } + if (finalizer != null) { + finalizer.run(); + } + }; + } + return handleCallFinalizer(syncTasks, callback, finalizer); } } @@ -924,18 +809,6 @@ private void updateGet( this.blocks[layer] = arr; } - private char[] loadPrivately(int layer) { - layer -= getMinSectionPosition(); - if (super.sections[layer] != null) { - synchronized (super.sectionLocks[layer]) { - if (super.sections[layer].isFull() && super.blocks[layer] != null) { - return super.blocks[layer]; - } - } - } - return PaperweightGetBlocks.this.update(layer, null, true); - } - @Override public void send() { synchronized (sendLock) { @@ -1033,7 +906,7 @@ public char[] update(int layer, char[] data, boolean aggressive) { } return data; } catch (IllegalAccessException | InterruptedException e) { - e.printStackTrace(); + LOGGER.error("Error calling chunk at {},{}", chunkX, chunkZ, e); throw new RuntimeException(e); } finally { lock.release(); @@ -1075,7 +948,16 @@ public LevelChunk getChunk() { synchronized (this) { levelChunk = this.levelChunk; if (levelChunk == null) { - this.levelChunk = levelChunk = ensureLoaded(this.serverLevel, chunkX, chunkZ); + try { + this.levelChunk = levelChunk = ensureLoaded(this.serverLevel).get(); + } catch (InterruptedException | ExecutionException e) { + LOGGER.error("Could not get chunk at {},{}", chunkX, chunkZ, e); + throw new FaweException( + TextComponent.of("Could not get chunk at " + chunkX + "," + chunkZ + ": " + e.getMessage()), + FaweException.Type.OTHER, + false + ); + } } } } diff --git a/worldedit-bukkit/adapters/adapter-1_21_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_3/PaperweightGetBlocks_Copy.java b/worldedit-bukkit/adapters/adapter-1_21_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_3/PaperweightGetBlocks_Copy.java index 60649041be..73211e407a 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_3/PaperweightGetBlocks_Copy.java +++ b/worldedit-bukkit/adapters/adapter-1_21_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_3/PaperweightGetBlocks_Copy.java @@ -3,8 +3,10 @@ import com.fastasyncworldedit.core.extent.processor.heightmap.HeightMapType; import com.fastasyncworldedit.core.nbt.FaweCompoundTag; import com.fastasyncworldedit.core.queue.IBlocks; +import com.fastasyncworldedit.core.queue.IChunk; import com.fastasyncworldedit.core.queue.IChunkGet; import com.fastasyncworldedit.core.queue.IChunkSet; +import com.fastasyncworldedit.core.queue.IQueueExtent; import com.fastasyncworldedit.core.util.NbtUtils; import com.sk89q.worldedit.bukkit.WorldEditPlugin; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; @@ -257,7 +259,7 @@ public int[] getHeightMap(HeightMapType type) { } @Override - public > T call(IChunkSet set, Runnable finalize) { + public > T call(IQueueExtent owner, IChunkSet set, Runnable finalize) { return null; } diff --git a/worldedit-bukkit/adapters/adapter-1_21_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_3/PaperweightPlatformAdapter.java b/worldedit-bukkit/adapters/adapter-1_21_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_3/PaperweightPlatformAdapter.java index 3bd0f5fb5a..91c1513551 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_3/PaperweightPlatformAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_21_3/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_3/PaperweightPlatformAdapter.java @@ -56,7 +56,6 @@ import net.minecraft.world.level.chunk.status.ChunkStatus; import net.minecraft.world.level.entity.PersistentEntitySectionManager; import org.apache.logging.log4j.Logger; -import org.bukkit.Bukkit; import org.bukkit.craftbukkit.CraftChunk; import javax.annotation.Nonnull; @@ -77,9 +76,8 @@ import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import java.util.function.IntFunction; import static java.lang.invoke.MethodType.methodType; @@ -259,12 +257,48 @@ static DelegateSemaphore applyLock(LevelChunkSection section) { } } } catch (Throwable e) { - e.printStackTrace(); + LOGGER.error("Error apply DelegateSemaphore", e); throw new RuntimeException(e); } } - public static LevelChunk ensureLoaded(ServerLevel serverLevel, int chunkX, int chunkZ) { + public static CompletableFuture ensureLoaded(ServerLevel serverLevel, int chunkX, int chunkZ) { + LevelChunk levelChunk = getChunkImmediatelyAsync(serverLevel, chunkX, chunkZ); + if (levelChunk != null) { + return CompletableFuture.completedFuture(levelChunk); + } + if (PaperLib.isPaper()) { + CompletableFuture future = serverLevel + .getWorld() + .getChunkAtAsync(chunkX, chunkZ, true, true) + .thenApply(chunk -> { + addTicket(serverLevel, chunkX, chunkZ); + try { + return (LevelChunk) CRAFT_CHUNK_GET_HANDLE.invoke(chunk); + } catch (Throwable e) { + LOGGER.error("Could not asynchronously load chunk at {},{}", chunkX, chunkZ, e); + return null; + } + }); + try { + if (!future.isCompletedExceptionally() || (future.isDone() && future.get() != null)) { + return future; + } + Throwable t = future.exceptionNow(); + LOGGER.error("Asynchronous chunk load at {},{} exceptionally completed immediately", chunkX, chunkZ, t); + } catch (InterruptedException | ExecutionException e) { + LOGGER.error( + "Unexpected error when getting completed future at chunk {},{}. Returning to default.", + chunkX, + chunkZ, + e + ); + } + } + return CompletableFuture.supplyAsync(() -> TaskManager.taskManager().sync(() -> serverLevel.getChunk(chunkX, chunkZ))); + } + + public static @Nullable LevelChunk getChunkImmediatelyAsync(ServerLevel serverLevel, int chunkX, int chunkZ) { if (!PaperLib.isPaper()) { LevelChunk nmsChunk = serverLevel.getChunkSource().getChunk(chunkX, chunkZ, false); if (nmsChunk != null) { @@ -273,6 +307,7 @@ public static LevelChunk ensureLoaded(ServerLevel serverLevel, int chunkX, int c if (Fawe.isMainThread()) { return serverLevel.getChunk(chunkX, chunkZ); } + return null; } else { LevelChunk nmsChunk = serverLevel.getChunkSource().getChunkAtIfCachedImmediately(chunkX, chunkZ); if (nmsChunk != null) { @@ -288,30 +323,8 @@ public static LevelChunk ensureLoaded(ServerLevel serverLevel, int chunkX, int c if (Fawe.isMainThread()) { return serverLevel.getChunk(chunkX, chunkZ); } - CompletableFuture future = serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true); - try { - CraftChunk chunk; - try { - chunk = (CraftChunk) future.get(10, TimeUnit.SECONDS); - } catch (TimeoutException e) { - String world = serverLevel.getWorld().getName(); - // We've already taken 10 seconds we can afford to wait a little here. - boolean loaded = TaskManager.taskManager().sync(() -> Bukkit.getWorld(world) != null); - if (loaded) { - LOGGER.warn("Chunk {},{} failed to load in 10 seconds in world {}. Retrying...", chunkX, chunkZ, world); - // Retry chunk load - chunk = (CraftChunk) serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true).get(); - } else { - throw new UnsupportedOperationException("Cannot load chunk from unloaded world " + world + "!"); - } - } - addTicket(serverLevel, chunkX, chunkZ); - return (LevelChunk) CRAFT_CHUNK_GET_HANDLE.invoke(chunk); - } catch (Throwable e) { - e.printStackTrace(); - } + return null; } - return TaskManager.taskManager().sync(() -> serverLevel.getChunk(chunkX, chunkZ)); } private static void addTicket(ServerLevel serverLevel, int chunkX, int chunkZ) { @@ -656,7 +669,7 @@ static void removeBeacon(BlockEntity beacon, LevelChunk levelChunk) { } methodremoveTickingBlockEntity.invoke(levelChunk, beacon.getBlockPos()); } catch (Throwable throwable) { - throwable.printStackTrace(); + LOGGER.error("Error removing beacon", throwable); } } diff --git a/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_4/PaperweightGetBlocks.java b/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_4/PaperweightGetBlocks.java index 8e5ac0c175..be3efd3082 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_4/PaperweightGetBlocks.java +++ b/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_4/PaperweightGetBlocks.java @@ -1,18 +1,15 @@ package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_21_4; -import com.fastasyncworldedit.bukkit.adapter.BukkitGetBlocks; +import com.fastasyncworldedit.bukkit.adapter.AbstractBukkitGetBlocks; import com.fastasyncworldedit.bukkit.adapter.DelegateSemaphore; -import com.fastasyncworldedit.core.Fawe; import com.fastasyncworldedit.core.FaweCache; import com.fastasyncworldedit.core.configuration.Settings; import com.fastasyncworldedit.core.extent.processor.heightmap.HeightMapType; +import com.fastasyncworldedit.core.internal.exception.FaweException; 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; -import com.fastasyncworldedit.core.queue.implementation.QueueHandler; -import com.fastasyncworldedit.core.queue.implementation.blocks.CharGetBlocks; import com.fastasyncworldedit.core.util.MathMan; import com.fastasyncworldedit.core.util.NbtUtils; import com.fastasyncworldedit.core.util.collection.AdaptedMap; @@ -22,6 +19,7 @@ import com.sk89q.worldedit.internal.util.LogManagerCompat; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.util.SideEffect; +import com.sk89q.worldedit.util.formatting.text.TextComponent; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.biome.BiomeTypes; import com.sk89q.worldedit.world.block.BlockTypesCache; @@ -83,18 +81,17 @@ import java.util.Map; import java.util.Set; import java.util.UUID; -import java.util.concurrent.Callable; -import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.Semaphore; import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.Function; import static net.minecraft.core.registries.Registries.BIOME; -public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBlocks { +public class PaperweightGetBlocks extends AbstractBukkitGetBlocks { private static final Logger LOGGER = LogManagerCompat.getLogger(); @@ -106,84 +103,25 @@ public class PaperweightGetBlocks extends CharGetBlocks implements BukkitGetBloc .getInstance() .getBukkitImplAdapter()); private final ReadWriteLock sectionLock = new ReentrantReadWriteLock(); - private final ReentrantLock callLock = new ReentrantLock(); - 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; - private final int maxSectionPosition; private final Registry biomeRegistry; private final IdMap> biomeHolderIdMap; - private final ConcurrentHashMap copies = new ConcurrentHashMap<>(); private final Object sendLock = new Object(); - private LevelChunkSection[] sections; private LevelChunk levelChunk; + private LevelChunkSection[] sections; private DataLayer[] blockLight; private DataLayer[] skyLight; - private boolean createCopy = false; - private boolean forceLoadSections = true; private boolean lightUpdate = false; - private int copyKey = 0; public PaperweightGetBlocks(World world, int chunkX, int chunkZ) { this(((CraftWorld) world).getHandle(), chunkX, chunkZ); } public PaperweightGetBlocks(ServerLevel serverLevel, int chunkX, int chunkZ) { - super(serverLevel.getMinY() >> 4, (serverLevel.getMaxY() - 1) >> 4); - this.serverLevel = serverLevel; - this.chunkX = chunkX; - this.chunkZ = chunkZ; - this.minHeight = serverLevel.getMinY(); - this.maxHeight = serverLevel.getMaxY() - 1; // Minecraft max limit is exclusive. - this.minSectionPosition = minHeight >> 4; - this.maxSectionPosition = maxHeight >> 4; + super(serverLevel, chunkX, chunkZ, serverLevel.getMinY(), serverLevel.getMaxY() - 1); this.skyLight = new DataLayer[getSectionCount()]; this.blockLight = new DataLayer[getSectionCount()]; this.biomeRegistry = serverLevel.registryAccess().lookupOrThrow(BIOME); this.biomeHolderIdMap = biomeRegistry.asHolderIdMap(); - this.chunkPos = new IntPair(chunkX, chunkZ); - } - - public int getChunkX() { - return chunkX; - } - - public int getChunkZ() { - return chunkZ; - } - - @Override - public boolean isCreateCopy() { - return createCopy; - } - - @Override - public int setCreateCopy(boolean createCopy) { - if (!callLock.isHeldByCurrentThread()) { - throw new IllegalStateException("Attempting to set if chunk GET should create copy, but it is not call-locked."); - } - this.createCopy = createCopy; - // Increment regardless of whether copy will be created or not to return null from getCopy() - return ++this.copyKey; - } - - @Override - public IChunkGet getCopy(final int key) { - return copies.remove(key); - } - - @Override - public void lockCall() { - this.callLock.lock(); - } - - @Override - public void unlockCall() { - this.callLock.unlock(); } @Override @@ -193,7 +131,7 @@ public void setLightingToGet(char[][] light, int minSectionPosition, int maxSect try { fillLightNibble(light, LightLayer.BLOCK, minSectionPosition, maxSectionPosition); } catch (Throwable e) { - e.printStackTrace(); + LOGGER.error("Error setting lighting to get", e); } } } @@ -205,7 +143,7 @@ public void setSkyLightingToGet(char[][] light, int minSectionPosition, int maxS try { fillLightNibble(light, LightLayer.SKY, minSectionPosition, maxSectionPosition); } catch (Throwable e) { - e.printStackTrace(); + LOGGER.error("Error setting sky lighting to get", e); } } } @@ -220,16 +158,6 @@ public void setHeightmapToGet(HeightMapType type, int[] data) { heightMap.setRawData(getChunk(), nativeType, bitArray.getData()); } - @Override - public int getMaxY() { - return maxHeight; - } - - @Override - public int getMinY() { - return minHeight; - } - @Override public BiomeType getBiomeType(int x, int y, int z) { LevelChunkSection section = getSections(false)[(y >> 4) - getMinSectionPosition()]; @@ -348,7 +276,7 @@ public int[] getHeightMap(HeightMapType type) { @Override public @Nullable FaweCompoundTag entity(final UUID uuid) { - ensureLoaded(serverLevel, chunkX, chunkZ); + ensureLoaded(serverLevel); List entities = PaperweightPlatformAdapter.getEntities(getChunk()); Entity entity = null; for (Entity e : entities) { @@ -371,7 +299,7 @@ public int[] getHeightMap(HeightMapType type) { @Override public Collection entities() { - ensureLoaded(serverLevel, chunkX, chunkZ); + ensureLoaded(serverLevel); List entities = PaperweightPlatformAdapter.getEntities(getChunk()); if (entities.isEmpty()) { return Collections.emptyList(); @@ -421,475 +349,434 @@ private void removeEntity(Entity entity) { entity.discard(); } - public LevelChunk ensureLoaded(ServerLevel nmsWorld, int chunkX, int chunkZ) { + @Override + public CompletableFuture ensureLoaded(ServerLevel nmsWorld) { return PaperweightPlatformAdapter.ensureLoaded(nmsWorld, chunkX, chunkZ); } @Override - @SuppressWarnings("rawtypes") - public synchronized > T call(IChunkSet set, Runnable finalizer) { - if (!callLock.isHeldByCurrentThread()) { - throw new IllegalStateException("Attempted to call chunk GET but chunk was not call-locked."); - } - forceLoadSections = false; - LevelChunk nmsChunk = ensureLoaded(serverLevel, chunkX, chunkZ); + protected > T internalCall( + IChunkSet set, + Runnable finalizer, + int copyKey, + LevelChunk nmsChunk, + ServerLevel nmsWorld + ) throws Exception { PaperweightGetBlocks_Copy copy = createCopy ? new PaperweightGetBlocks_Copy(nmsChunk) : null; if (createCopy) { if (copies.containsKey(copyKey)) { throw new IllegalStateException("Copy key already used."); - } + } copies.put(copyKey, copy); } - try { - // Remove existing tiles. Create a copy so that we can remove blocks - Map chunkTiles = new HashMap<>(nmsChunk.getBlockEntities()); - List beacons = null; - if (!chunkTiles.isEmpty()) { - for (Map.Entry entry : chunkTiles.entrySet()) { - final BlockPos pos = entry.getKey(); - final int lx = pos.getX() & 15; - final int ly = pos.getY(); - final int lz = pos.getZ() & 15; - final int layer = ly >> 4; - if (!set.hasSection(layer)) { - continue; - } + // Remove existing tiles. Create a copy so that we can remove blocks + Map chunkTiles = new HashMap<>(nmsChunk.getBlockEntities()); + List beacons = null; + if (!chunkTiles.isEmpty()) { + for (Map.Entry entry : chunkTiles.entrySet()) { + final BlockPos pos = entry.getKey(); + final int lx = pos.getX() & 15; + final int ly = pos.getY(); + final int lz = pos.getZ() & 15; + final int layer = ly >> 4; + if (!set.hasSection(layer)) { + continue; + } - int ordinal = set.getBlock(lx, ly, lz).getOrdinal(); - if (ordinal != BlockTypesCache.ReservedIDs.__RESERVED__) { - BlockEntity tile = entry.getValue(); - if (PaperLib.isPaper() && tile instanceof BeaconBlockEntity) { - if (beacons == null) { - beacons = new ArrayList<>(); - } - beacons.add(tile); - PaperweightPlatformAdapter.removeBeacon(tile, nmsChunk); - continue; - } - nmsChunk.removeBlockEntity(tile.getBlockPos()); - if (createCopy) { - copy.storeTile(tile); + int ordinal = set.getBlock(lx, ly, lz).getOrdinal(); + if (ordinal != BlockTypesCache.ReservedIDs.__RESERVED__) { + BlockEntity tile = entry.getValue(); + if (PaperLib.isPaper() && tile instanceof BeaconBlockEntity) { + if (beacons == null) { + beacons = new ArrayList<>(); } + beacons.add(tile); + PaperweightPlatformAdapter.removeBeacon(tile, nmsChunk); + continue; + } + nmsChunk.removeBlockEntity(tile.getBlockPos()); + if (createCopy) { + copy.storeTile(tile); } } } - final BiomeType[][] biomes = set.getBiomes(); + } + final BiomeType[][] biomes = set.getBiomes(); - int bitMask = 0; - synchronized (nmsChunk) { - LevelChunkSection[] levelChunkSections = nmsChunk.getSections(); + int bitMask = 0; + synchronized (nmsChunk) { + LevelChunkSection[] levelChunkSections = nmsChunk.getSections(); - for (int layerNo = getMinSectionPosition(); layerNo <= getMaxSectionPosition(); layerNo++) { + for (int layerNo = getMinSectionPosition(); layerNo <= getMaxSectionPosition(); layerNo++) { - int getSectionIndex = layerNo - getMinSectionPosition(); - int setSectionIndex = layerNo - set.getMinSectionPosition(); + int getSectionIndex = layerNo - getMinSectionPosition(); + int setSectionIndex = layerNo - set.getMinSectionPosition(); - if (!set.hasSection(layerNo)) { - // No blocks, but might be biomes present. Handle this lazily. - if (biomes == null) { - continue; - } - if (layerNo < set.getMinSectionPosition() || layerNo > set.getMaxSectionPosition()) { - continue; - } - if (biomes[setSectionIndex] != null) { - synchronized (super.sectionLocks[getSectionIndex]) { - LevelChunkSection existingSection = levelChunkSections[getSectionIndex]; - if (createCopy && existingSection != null) { - copy.storeBiomes(getSectionIndex, existingSection.getBiomes()); - } + if (!set.hasSection(layerNo)) { + // No blocks, but might be biomes present. Handle this lazily. + if (biomes == null) { + continue; + } + if (layerNo < set.getMinSectionPosition() || layerNo > set.getMaxSectionPosition()) { + continue; + } + if (biomes[setSectionIndex] != null) { + synchronized (super.sectionLocks[getSectionIndex]) { + LevelChunkSection existingSection = levelChunkSections[getSectionIndex]; + if (createCopy && existingSection != null) { + copy.storeBiomes(getSectionIndex, existingSection.getBiomes()); + } - if (existingSection == null) { - PalettedContainer> biomeData = PaperweightPlatformAdapter.getBiomePalettedContainer( - biomes[setSectionIndex], - biomeHolderIdMap - ); - LevelChunkSection newSection = PaperweightPlatformAdapter.newChunkSection( - layerNo, - new char[4096], - adapter, - biomeRegistry, - biomeData - ); - if (PaperweightPlatformAdapter.setSectionAtomic( - serverLevel.getWorld().getName(), - chunkPos, - levelChunkSections, - null, - newSection, - getSectionIndex - )) { - updateGet(nmsChunk, levelChunkSections, newSection, new char[4096], getSectionIndex); - continue; - } else { - existingSection = levelChunkSections[getSectionIndex]; - if (existingSection == null) { - LOGGER.error("Skipping invalid null section. chunk: {}, {} layer: {}", chunkX, chunkZ, - getSectionIndex - ); - continue; - } - } + if (existingSection == null) { + PalettedContainer> biomeData = PaperweightPlatformAdapter.getBiomePalettedContainer( + biomes[setSectionIndex], + biomeHolderIdMap + ); + LevelChunkSection newSection = PaperweightPlatformAdapter.newChunkSection( + layerNo, + new char[4096], + adapter, + biomeRegistry, + biomeData + ); + if (PaperweightPlatformAdapter.setSectionAtomic( + nmsWorld.getWorld().getName(), + chunkPos, + levelChunkSections, + null, + newSection, + getSectionIndex + )) { + updateGet(nmsChunk, levelChunkSections, newSection, new char[4096], getSectionIndex); + continue; } else { - PalettedContainer> paletteBiomes = setBiomesToPalettedContainer( - biomes, - setSectionIndex, - existingSection.getBiomes() - ); - if (paletteBiomes != null) { - PaperweightPlatformAdapter.setBiomesToChunkSection(existingSection, paletteBiomes); + existingSection = levelChunkSections[getSectionIndex]; + if (existingSection == null) { + LOGGER.error("Skipping invalid null section. chunk: {}, {} layer: {}", chunkX, chunkZ, + getSectionIndex + ); + continue; } } + } else { + PalettedContainer> paletteBiomes = setBiomesToPalettedContainer( + biomes, + setSectionIndex, + existingSection.getBiomes() + ); + if (paletteBiomes != null) { + PaperweightPlatformAdapter.setBiomesToChunkSection(existingSection, paletteBiomes); + } } } - continue; } + continue; + } - bitMask |= 1 << getSectionIndex; - - // setArr is modified by PaperweightPlatformAdapter#newChunkSection. This is in order to write changes to - // this chunk GET when #updateGet is called. Future dords, please listen this time. - char[] tmp = set.load(layerNo); - char[] setArr = new char[tmp.length]; - System.arraycopy(tmp, 0, setArr, 0, tmp.length); + bitMask |= 1 << getSectionIndex; - // synchronise on internal section to avoid circular locking with a continuing edit if the chunk was - // submitted to keep loaded internal chunks to queue target size. - synchronized (super.sectionLocks[getSectionIndex]) { + // setArr is modified by PaperweightPlatformAdapter#newChunkSection. This is in order to write changes to + // this chunk GET when #updateGet is called. Future dords, please listen this time. + char[] tmp = set.load(layerNo); + char[] setArr = new char[tmp.length]; + System.arraycopy(tmp, 0, setArr, 0, tmp.length); - LevelChunkSection newSection; - LevelChunkSection existingSection = levelChunkSections[getSectionIndex]; - // Don't attempt to tick section whilst we're editing - if (existingSection != null) { - PaperweightPlatformAdapter.clearCounts(existingSection); - } - - if (createCopy) { - char[] tmpLoad = loadPrivately(layerNo); - char[] copyArr = new char[4096]; - System.arraycopy(tmpLoad, 0, copyArr, 0, 4096); - copy.storeSection(getSectionIndex, copyArr); - if (biomes != null && existingSection != null) { - copy.storeBiomes(getSectionIndex, existingSection.getBiomes()); - } - } - - if (existingSection == null) { - PalettedContainer> biomeData = biomes == null ? new PalettedContainer<>( - biomeHolderIdMap, - biomeHolderIdMap.byIdOrThrow(adapter.getInternalBiomeId(BiomeTypes.PLAINS)), - PalettedContainer.Strategy.SECTION_BIOMES - ) : PaperweightPlatformAdapter.getBiomePalettedContainer(biomes[setSectionIndex], biomeHolderIdMap); - newSection = PaperweightPlatformAdapter.newChunkSection( - layerNo, - setArr, - adapter, - biomeRegistry, - biomeData - ); - if (PaperweightPlatformAdapter.setSectionAtomic( - serverLevel.getWorld().getName(), - chunkPos, - levelChunkSections, - null, - newSection, - getSectionIndex - )) { - updateGet(nmsChunk, levelChunkSections, newSection, setArr, getSectionIndex); - continue; - } else { - existingSection = levelChunkSections[getSectionIndex]; - if (existingSection == null) { - LOGGER.error("Skipping invalid null section. chunk: {}, {} layer: {}", chunkX, chunkZ, - getSectionIndex - ); - continue; - } - } - } + // synchronise on internal section to avoid circular locking with a continuing edit if the chunk was + // submitted to keep loaded internal chunks to queue target size. + synchronized (super.sectionLocks[getSectionIndex]) { - //ensure that the server doesn't try to tick the chunksection while we're editing it. (Again) + LevelChunkSection newSection; + LevelChunkSection existingSection = levelChunkSections[getSectionIndex]; + // Don't attempt to tick section whilst we're editing + if (existingSection != null) { PaperweightPlatformAdapter.clearCounts(existingSection); - DelegateSemaphore lock = PaperweightPlatformAdapter.applyLock(existingSection); - - // Synchronize to prevent further acquisitions - synchronized (lock) { - lock.acquire(); // Wait until we have the lock - lock.release(); - try { - sectionLock.writeLock().lock(); - if (this.getChunk() != nmsChunk) { - this.levelChunk = nmsChunk; - this.sections = null; - this.reset(); - } else if (existingSection != getSections(false)[getSectionIndex]) { - this.sections[getSectionIndex] = existingSection; - this.reset(); - } else if (!Arrays.equals( - update(getSectionIndex, new char[4096], true), - loadPrivately(layerNo) - )) { - this.reset(layerNo); - /*} else if (lock.isModified()) { - this.reset(layerNo);*/ - } - } finally { - sectionLock.writeLock().unlock(); - } + } - PalettedContainer> biomeData = setBiomesToPalettedContainer( - biomes, - setSectionIndex, - existingSection.getBiomes() - ); + if (createCopy) { + char[] tmpLoad = loadPrivately(layerNo); + char[] copyArr = new char[4096]; + System.arraycopy(tmpLoad, 0, copyArr, 0, 4096); + copy.storeSection(getSectionIndex, copyArr); + if (biomes != null && existingSection != null) { + copy.storeBiomes(getSectionIndex, existingSection.getBiomes()); + } + } - newSection = PaperweightPlatformAdapter.newChunkSection( - layerNo, - this::loadPrivately, - setArr, - adapter, - biomeRegistry, - biomeData != null ? biomeData : (PalettedContainer>) existingSection.getBiomes() - ); - if (!PaperweightPlatformAdapter.setSectionAtomic( - serverLevel.getWorld().getName(), - chunkPos, - levelChunkSections, - existingSection, - newSection, - getSectionIndex - )) { + if (existingSection == null) { + PalettedContainer> biomeData = biomes == null ? new PalettedContainer<>( + biomeHolderIdMap, + biomeHolderIdMap.byIdOrThrow(adapter.getInternalBiomeId(BiomeTypes.PLAINS)), + PalettedContainer.Strategy.SECTION_BIOMES + ) : PaperweightPlatformAdapter.getBiomePalettedContainer(biomes[setSectionIndex], biomeHolderIdMap); + newSection = PaperweightPlatformAdapter.newChunkSection( + layerNo, + setArr, + adapter, + biomeRegistry, + biomeData + ); + if (PaperweightPlatformAdapter.setSectionAtomic( + nmsWorld.getWorld().getName(), + chunkPos, + levelChunkSections, + null, + newSection, + getSectionIndex + )) { + updateGet(nmsChunk, levelChunkSections, newSection, setArr, getSectionIndex); + continue; + } else { + existingSection = levelChunkSections[getSectionIndex]; + if (existingSection == null) { LOGGER.error("Skipping invalid null section. chunk: {}, {} layer: {}", chunkX, chunkZ, getSectionIndex ); - } else { - updateGet(nmsChunk, levelChunkSections, newSection, setArr, getSectionIndex); + continue; } } } - } - - Map heightMaps = set.getHeightMaps(); - for (Map.Entry entry : heightMaps.entrySet()) { - PaperweightGetBlocks.this.setHeightmapToGet(entry.getKey(), entry.getValue()); - } - PaperweightGetBlocks.this.setLightingToGet( - set.getLight(), - set.getMinSectionPosition(), - set.getMaxSectionPosition() - ); - PaperweightGetBlocks.this.setSkyLightingToGet( - set.getSkyLight(), - set.getMinSectionPosition(), - set.getMaxSectionPosition() - ); - Runnable[] syncTasks = null; + //ensure that the server doesn't try to tick the chunksection while we're editing it. (Again) + PaperweightPlatformAdapter.clearCounts(existingSection); + DelegateSemaphore lock = PaperweightPlatformAdapter.applyLock(existingSection); - int bx = chunkX << 4; - int bz = chunkZ << 4; - - // Call beacon deactivate events here synchronously - // list will be null on spigot, so this is an implicit isPaper check - if (beacons != null && !beacons.isEmpty()) { - final List finalBeacons = beacons; + // Synchronize to prevent further acquisitions + synchronized (lock) { + lock.acquire(); // Wait until we have the lock + lock.release(); + try { + sectionLock.writeLock().lock(); + if (this.getChunk() != nmsChunk) { + this.levelChunk = nmsChunk; + this.sections = null; + this.reset(); + } else if (existingSection != getSections(false)[getSectionIndex]) { + this.sections[getSectionIndex] = existingSection; + this.reset(); + } else if (!Arrays.equals( + update(getSectionIndex, new char[4096], true), + loadPrivately(layerNo) + )) { + this.reset(layerNo); + /*} else if (lock.isModified()) { + this.reset(layerNo);*/ + } + } finally { + sectionLock.writeLock().unlock(); + } - syncTasks = new Runnable[4]; + PalettedContainer> biomeData = setBiomesToPalettedContainer( + biomes, + setSectionIndex, + existingSection.getBiomes() + ); - syncTasks[3] = () -> { - for (BlockEntity beacon : finalBeacons) { - BeaconBlockEntity.playSound(beacon.getLevel(), beacon.getBlockPos(), SoundEvents.BEACON_DEACTIVATE); - new BeaconDeactivatedEvent(CraftBlock.at(beacon.getLevel(), beacon.getBlockPos())).callEvent(); + newSection = PaperweightPlatformAdapter.newChunkSection( + layerNo, + this::loadPrivately, + setArr, + adapter, + biomeRegistry, + biomeData != null ? biomeData : (PalettedContainer>) existingSection.getBiomes() + ); + if (!PaperweightPlatformAdapter.setSectionAtomic( + nmsWorld.getWorld().getName(), + chunkPos, + levelChunkSections, + existingSection, + newSection, + getSectionIndex + )) { + LOGGER.error("Skipping invalid null section. chunk: {}, {} layer: {}", chunkX, chunkZ, + getSectionIndex + ); + } else { + updateGet(nmsChunk, levelChunkSections, newSection, setArr, getSectionIndex); } - }; + } } + } - Set entityRemoves = set.getEntityRemoves(); - if (entityRemoves != null && !entityRemoves.isEmpty()) { - if (syncTasks == null) { - syncTasks = new Runnable[3]; + Map heightMaps = set.getHeightMaps(); + for (Map.Entry entry : heightMaps.entrySet()) { + PaperweightGetBlocks.this.setHeightmapToGet(entry.getKey(), entry.getValue()); + } + PaperweightGetBlocks.this.setLightingToGet( + set.getLight(), + set.getMinSectionPosition(), + set.getMaxSectionPosition() + ); + PaperweightGetBlocks.this.setSkyLightingToGet( + set.getSkyLight(), + set.getMinSectionPosition(), + set.getMaxSectionPosition() + ); + + Runnable[] syncTasks = null; + + int bx = chunkX << 4; + int bz = chunkZ << 4; + + // Call beacon deactivate events here synchronously + // list will be null on spigot, so this is an implicit isPaper check + if (beacons != null && !beacons.isEmpty()) { + final List finalBeacons = beacons; + + syncTasks = new Runnable[4]; + + syncTasks[3] = () -> { + for (BlockEntity beacon : finalBeacons) { + BeaconBlockEntity.playSound(beacon.getLevel(), beacon.getBlockPos(), SoundEvents.BEACON_DEACTIVATE); + new BeaconDeactivatedEvent(CraftBlock.at(beacon.getLevel(), beacon.getBlockPos())).callEvent(); } + }; + } + + Set entityRemoves = set.getEntityRemoves(); + if (entityRemoves != null && !entityRemoves.isEmpty()) { + if (syncTasks == null) { + syncTasks = new Runnable[3]; + } - syncTasks[2] = () -> { - Set entitiesRemoved = new HashSet<>(); - final List entities = PaperweightPlatformAdapter.getEntities(nmsChunk); + syncTasks[2] = () -> { + Set entitiesRemoved = new HashSet<>(); + final List entities = PaperweightPlatformAdapter.getEntities(nmsChunk); - for (Entity entity : entities) { - UUID uuid = entity.getUUID(); - if (entityRemoves.contains(uuid)) { - if (createCopy) { - copy.storeEntity(entity); - } - removeEntity(entity); - entitiesRemoved.add(uuid); - entityRemoves.remove(uuid); + for (Entity entity : entities) { + UUID uuid = entity.getUUID(); + if (entityRemoves.contains(uuid)) { + if (createCopy) { + copy.storeEntity(entity); } + removeEntity(entity); + entitiesRemoved.add(uuid); + entityRemoves.remove(uuid); } - if (Settings.settings().EXPERIMENTAL.REMOVE_ENTITY_FROM_WORLD_ON_CHUNK_FAIL) { - for (UUID uuid : entityRemoves) { - Entity entity = serverLevel.getEntities().get(uuid); - if (entity != null) { - removeEntity(entity); - } + } + if (Settings.settings().EXPERIMENTAL.REMOVE_ENTITY_FROM_WORLD_ON_CHUNK_FAIL) { + for (UUID uuid : entityRemoves) { + Entity entity = nmsWorld.getEntities().get(uuid); + if (entity != null) { + removeEntity(entity); } } - // Only save entities that were actually removed to history - set.getEntityRemoves().clear(); - set.getEntityRemoves().addAll(entitiesRemoved); - }; - } - - Collection entities = set.entities(); - if (entities != null && !entities.isEmpty()) { - if (syncTasks == null) { - syncTasks = new Runnable[2]; } + // Only save entities that were actually removed to history + set.getEntityRemoves().clear(); + set.getEntityRemoves().addAll(entitiesRemoved); + }; + } - syncTasks[1] = () -> { - Iterator iterator = entities.iterator(); - while (iterator.hasNext()) { - final FaweCompoundTag nativeTag = iterator.next(); - final LinCompoundTag linTag = nativeTag.linTag(); - final LinStringTag idTag = linTag.findTag("Id", LinTagType.stringTag()); - final LinListTag posTag = linTag.findListTag("Pos", LinTagType.doubleTag()); - final LinListTag rotTag = linTag.findListTag("Rotation", LinTagType.floatTag()); - if (idTag == null || posTag == null || rotTag == null) { - LOGGER.error("Unknown entity tag: {}", nativeTag); - continue; - } - final double x = posTag.get(0).valueAsDouble(); - final double y = posTag.get(1).valueAsDouble(); - final double z = posTag.get(2).valueAsDouble(); - final float yaw = rotTag.get(0).valueAsFloat(); - final float pitch = rotTag.get(1).valueAsFloat(); - final String id = idTag.value(); - - EntityType type = EntityType.byString(id).orElse(null); - if (type != null) { - Entity entity = type.create(serverLevel, EntitySpawnReason.COMMAND); - if (entity != null) { - final CompoundTag tag = (CompoundTag) adapter.fromNativeLin(linTag); - for (final String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) { - tag.remove(name); - } - entity.load(tag); - entity.absMoveTo(x, y, z, yaw, pitch); - entity.setUUID(NbtUtils.uuid(nativeTag)); - if (!serverLevel.addFreshEntity(entity, CreatureSpawnEvent.SpawnReason.CUSTOM)) { - LOGGER.warn( - "Error creating entity of type `{}` in world `{}` at location `{},{},{}`", - id, - serverLevel.getWorld().getName(), - x, - y, - z - ); - // Unsuccessful create should not be saved to history - iterator.remove(); - } - } - } - } - }; + Collection entities = set.entities(); + if (entities != null && !entities.isEmpty()) { + if (syncTasks == null) { + syncTasks = new Runnable[2]; } - // set tiles - Map tiles = set.tiles(); - if (tiles != null && !tiles.isEmpty()) { - if (syncTasks == null) { - syncTasks = new Runnable[1]; - } - - syncTasks[0] = () -> { - for (final Map.Entry entry : tiles.entrySet()) { - final FaweCompoundTag nativeTag = entry.getValue(); - final BlockVector3 blockHash = entry.getKey(); - final int x = blockHash.x() + bx; - final int y = blockHash.y(); - final int z = blockHash.z() + bz; - final BlockPos pos = new BlockPos(x, y, z); - - synchronized (serverLevel) { - BlockEntity tileEntity = serverLevel.getBlockEntity(pos); - if (tileEntity == null || tileEntity.isRemoved()) { - serverLevel.removeBlockEntity(pos); - tileEntity = serverLevel.getBlockEntity(pos); + syncTasks[1] = () -> { + Iterator iterator = entities.iterator(); + while (iterator.hasNext()) { + final FaweCompoundTag nativeTag = iterator.next(); + final LinCompoundTag linTag = nativeTag.linTag(); + final LinStringTag idTag = linTag.findTag("Id", LinTagType.stringTag()); + final LinListTag posTag = linTag.findListTag("Pos", LinTagType.doubleTag()); + final LinListTag rotTag = linTag.findListTag("Rotation", LinTagType.floatTag()); + if (idTag == null || posTag == null || rotTag == null) { + LOGGER.error("Unknown entity tag: {}", nativeTag); + continue; + } + final double x = posTag.get(0).valueAsDouble(); + final double y = posTag.get(1).valueAsDouble(); + final double z = posTag.get(2).valueAsDouble(); + final float yaw = rotTag.get(0).valueAsFloat(); + final float pitch = rotTag.get(1).valueAsFloat(); + final String id = idTag.value(); + + EntityType type = EntityType.byString(id).orElse(null); + if (type != null) { + Entity entity = type.create(nmsWorld, EntitySpawnReason.COMMAND); + if (entity != null) { + final CompoundTag tag = (CompoundTag) adapter.fromNativeLin(linTag); + for (final String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) { + tag.remove(name); } - if (tileEntity != null) { - final CompoundTag tag = (CompoundTag) adapter.fromNativeLin(nativeTag.linTag()); - tag.put("x", IntTag.valueOf(x)); - tag.put("y", IntTag.valueOf(y)); - tag.put("z", IntTag.valueOf(z)); - tileEntity.loadWithComponents(tag, DedicatedServer.getServer().registryAccess()); + entity.load(tag); + entity.absMoveTo(x, y, z, yaw, pitch); + entity.setUUID(NbtUtils.uuid(nativeTag)); + if (!nmsWorld.addFreshEntity(entity, CreatureSpawnEvent.SpawnReason.CUSTOM)) { + LOGGER.warn( + "Error creating entity of type `{}` in world `{}` at location `{},{},{}`", + id, + nmsWorld.getWorld().getName(), + x, + y, + z + ); + // Unsuccessful create should not be saved to history + iterator.remove(); } } } - }; - } + } + }; + } - Runnable callback; - if (bitMask == 0 && biomes == null && !lightUpdate) { - callback = null; - } else { - int finalMask = bitMask != 0 ? bitMask : lightUpdate ? set.getBitMask() : 0; - callback = () -> { - // Set Modified - nmsChunk.setLightCorrect(true); // Set Modified - nmsChunk.mustNotSave = false; - nmsChunk.markUnsaved(); - // send to player - if (!set.getSideEffectSet().shouldApply(SideEffect.LIGHTING) || !Settings.settings().LIGHTING.DELAY_PACKET_SENDING || finalMask == 0 && biomes != null) { - this.send(); - } - if (finalizer != null) { - finalizer.run(); - } - }; + // set tiles + Map tiles = set.tiles(); + if (tiles != null && !tiles.isEmpty()) { + if (syncTasks == null) { + syncTasks = new Runnable[1]; } - if (syncTasks != null) { - QueueHandler queueHandler = Fawe.instance().getQueueHandler(); - Runnable[] finalSyncTasks = syncTasks; - // Chain the sync tasks and the callback - Callable chain = () -> { - try { - // Run the sync tasks - for (Runnable task : finalSyncTasks) { - if (task != null) { - task.run(); - } + syncTasks[0] = () -> { + for (final Map.Entry entry : tiles.entrySet()) { + final FaweCompoundTag nativeTag = entry.getValue(); + final BlockVector3 blockHash = entry.getKey(); + final int x = blockHash.x() + bx; + final int y = blockHash.y(); + final int z = blockHash.z() + bz; + final BlockPos pos = new BlockPos(x, y, z); + + synchronized (nmsWorld) { + BlockEntity tileEntity = nmsWorld.getBlockEntity(pos); + if (tileEntity == null || tileEntity.isRemoved()) { + nmsWorld.removeBlockEntity(pos); + tileEntity = nmsWorld.getBlockEntity(pos); } - if (callback == null) { - if (finalizer != null) { - queueHandler.async(finalizer, null); - } - return null; - } else { - return queueHandler.async(callback, null); + if (tileEntity != null) { + final CompoundTag tag = (CompoundTag) adapter.fromNativeLin(nativeTag.linTag()); + tag.put("x", IntTag.valueOf(x)); + tag.put("y", IntTag.valueOf(y)); + tag.put("z", IntTag.valueOf(z)); + tileEntity.loadWithComponents(tag, DedicatedServer.getServer().registryAccess()); } - } catch (Throwable e) { - e.printStackTrace(); - throw e; - } - }; - //noinspection unchecked - required at compile time - return (T) (Future) queueHandler.sync(chain); - } else { - if (callback == null) { - if (finalizer != null) { - finalizer.run(); } - } else { - callback.run(); } - } + }; } - return null; - } catch (Throwable e) { - e.printStackTrace(); - return null; - } finally { - forceLoadSections = true; + + Runnable callback; + if (bitMask == 0 && biomes == null && !lightUpdate) { + callback = null; + } else { + int finalMask = bitMask != 0 ? bitMask : lightUpdate ? set.getBitMask() : 0; + callback = () -> { + // Set Modified + nmsChunk.setLightCorrect(true); // Set Modified + nmsChunk.mustNotSave = false; + nmsChunk.markUnsaved(); + // send to player + if (!set + .getSideEffectSet() + .shouldApply(SideEffect.LIGHTING) || !Settings.settings().LIGHTING.DELAY_PACKET_SENDING || finalMask == 0 && biomes != null) { + this.send(); + } + if (finalizer != null) { + finalizer.run(); + } + }; + } + return handleCallFinalizer(syncTasks, callback, finalizer); } } @@ -922,18 +809,6 @@ private void updateGet( this.blocks[layer] = arr; } - private char[] loadPrivately(int layer) { - layer -= getMinSectionPosition(); - if (super.sections[layer] != null) { - synchronized (super.sectionLocks[layer]) { - if (super.sections[layer].isFull() && super.blocks[layer] != null) { - return super.blocks[layer]; - } - } - } - return PaperweightGetBlocks.this.update(layer, null, true); - } - @Override public void send() { synchronized (sendLock) { @@ -1031,7 +906,7 @@ public char[] update(int layer, char[] data, boolean aggressive) { } return data; } catch (IllegalAccessException | InterruptedException e) { - e.printStackTrace(); + LOGGER.error("Could not read block data from palette", e); throw new RuntimeException(e); } finally { lock.release(); @@ -1073,7 +948,16 @@ public LevelChunk getChunk() { synchronized (this) { levelChunk = this.levelChunk; if (levelChunk == null) { - this.levelChunk = levelChunk = ensureLoaded(this.serverLevel, chunkX, chunkZ); + try { + this.levelChunk = levelChunk = ensureLoaded(this.serverLevel).get(); + } catch (InterruptedException | ExecutionException e) { + LOGGER.error("Could not get chunk at {},{}", chunkX, chunkZ, e); + throw new FaweException( + TextComponent.of("Could not get chunk at " + chunkX + "," + chunkZ + ": " + e.getMessage()), + FaweException.Type.OTHER, + false + ); + } } } } diff --git a/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_4/PaperweightGetBlocks_Copy.java b/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_4/PaperweightGetBlocks_Copy.java index 9400b0ca60..60a99245d5 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_4/PaperweightGetBlocks_Copy.java +++ b/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_4/PaperweightGetBlocks_Copy.java @@ -3,8 +3,10 @@ import com.fastasyncworldedit.core.extent.processor.heightmap.HeightMapType; import com.fastasyncworldedit.core.nbt.FaweCompoundTag; import com.fastasyncworldedit.core.queue.IBlocks; +import com.fastasyncworldedit.core.queue.IChunk; import com.fastasyncworldedit.core.queue.IChunkGet; import com.fastasyncworldedit.core.queue.IChunkSet; +import com.fastasyncworldedit.core.queue.IQueueExtent; import com.fastasyncworldedit.core.util.NbtUtils; import com.sk89q.worldedit.bukkit.WorldEditPlugin; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; @@ -257,7 +259,7 @@ public int[] getHeightMap(HeightMapType type) { } @Override - public > T call(IChunkSet set, Runnable finalize) { + public > T call(IQueueExtent owner, IChunkSet set, Runnable finalize) { return null; } diff --git a/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_4/PaperweightPlatformAdapter.java b/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_4/PaperweightPlatformAdapter.java index c5f896da0c..a247adf126 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_4/PaperweightPlatformAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_4/PaperweightPlatformAdapter.java @@ -55,7 +55,6 @@ import net.minecraft.world.level.chunk.status.ChunkStatus; import net.minecraft.world.level.entity.PersistentEntitySectionManager; import org.apache.logging.log4j.Logger; -import org.bukkit.Bukkit; import org.bukkit.craftbukkit.CraftChunk; import javax.annotation.Nonnull; @@ -76,9 +75,8 @@ import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import java.util.function.IntFunction; import static java.lang.invoke.MethodType.methodType; @@ -260,12 +258,48 @@ static DelegateSemaphore applyLock(LevelChunkSection section) { } } } catch (Throwable e) { - e.printStackTrace(); + LOGGER.error("Error apply DelegateSemaphore", e); throw new RuntimeException(e); } } - public static LevelChunk ensureLoaded(ServerLevel serverLevel, int chunkX, int chunkZ) { + public static CompletableFuture ensureLoaded(ServerLevel serverLevel, int chunkX, int chunkZ) { + LevelChunk levelChunk = getChunkImmediatelyAsync(serverLevel, chunkX, chunkZ); + if (levelChunk != null) { + return CompletableFuture.completedFuture(levelChunk); + } + if (PaperLib.isPaper()) { + CompletableFuture future = serverLevel + .getWorld() + .getChunkAtAsync(chunkX, chunkZ, true, true) + .thenApply(chunk -> { + addTicket(serverLevel, chunkX, chunkZ); + try { + return (LevelChunk) CRAFT_CHUNK_GET_HANDLE.invoke(chunk); + } catch (Throwable e) { + LOGGER.error("Could not asynchronously load chunk at {},{}", chunkX, chunkZ, e); + return null; + } + }); + try { + if (!future.isCompletedExceptionally() || (future.isDone() && future.get() != null)) { + return future; + } + Throwable t = future.exceptionNow(); + LOGGER.error("Asynchronous chunk load at {},{} exceptionally completed immediately", chunkX, chunkZ, t); + } catch (InterruptedException | ExecutionException e) { + LOGGER.error( + "Unexpected error when getting completed future at chunk {},{}. Returning to default.", + chunkX, + chunkZ, + e + ); + } + } + return CompletableFuture.supplyAsync(() -> TaskManager.taskManager().sync(() -> serverLevel.getChunk(chunkX, chunkZ))); + } + + public static @Nullable LevelChunk getChunkImmediatelyAsync(ServerLevel serverLevel, int chunkX, int chunkZ) { if (!PaperLib.isPaper()) { LevelChunk nmsChunk = serverLevel.getChunkSource().getChunk(chunkX, chunkZ, false); if (nmsChunk != null) { @@ -274,6 +308,7 @@ public static LevelChunk ensureLoaded(ServerLevel serverLevel, int chunkX, int c if (Fawe.isMainThread()) { return serverLevel.getChunk(chunkX, chunkZ); } + return null; } else { LevelChunk nmsChunk = serverLevel.getChunkSource().getChunkAtIfCachedImmediately(chunkX, chunkZ); if (nmsChunk != null) { @@ -289,30 +324,8 @@ public static LevelChunk ensureLoaded(ServerLevel serverLevel, int chunkX, int c if (Fawe.isMainThread()) { return serverLevel.getChunk(chunkX, chunkZ); } - CompletableFuture future = serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true); - try { - CraftChunk chunk; - try { - chunk = (CraftChunk) future.get(10, TimeUnit.SECONDS); - } catch (TimeoutException e) { - String world = serverLevel.getWorld().getName(); - // We've already taken 10 seconds we can afford to wait a little here. - boolean loaded = TaskManager.taskManager().sync(() -> Bukkit.getWorld(world) != null); - if (loaded) { - LOGGER.warn("Chunk {},{} failed to load in 10 seconds in world {}. Retrying...", chunkX, chunkZ, world); - // Retry chunk load - chunk = (CraftChunk) serverLevel.getWorld().getChunkAtAsync(chunkX, chunkZ, true, true).get(); - } else { - throw new UnsupportedOperationException("Cannot load chunk from unloaded world " + world + "!"); - } - } - addTicket(serverLevel, chunkX, chunkZ); - return (LevelChunk) CRAFT_CHUNK_GET_HANDLE.invoke(chunk); - } catch (Throwable e) { - e.printStackTrace(); - } + return null; } - return TaskManager.taskManager().sync(() -> serverLevel.getChunk(chunkX, chunkZ)); } private static void addTicket(ServerLevel serverLevel, int chunkX, int chunkZ) { @@ -651,7 +664,7 @@ static void removeBeacon(BlockEntity beacon, LevelChunk levelChunk) { } methodremoveTickingBlockEntity.invoke(levelChunk, beacon.getBlockPos()); } catch (Throwable throwable) { - throwable.printStackTrace(); + LOGGER.error("Error removing beacon", throwable); } } diff --git a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/AbstractBukkitGetBlocks.java b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/AbstractBukkitGetBlocks.java new file mode 100644 index 0000000000..eb2f4a72cb --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/AbstractBukkitGetBlocks.java @@ -0,0 +1,209 @@ +package com.fastasyncworldedit.bukkit.adapter; + +import com.fastasyncworldedit.core.Fawe; +import com.fastasyncworldedit.core.configuration.Settings; +import com.fastasyncworldedit.core.internal.exception.FaweException; +import com.fastasyncworldedit.core.math.IntPair; +import com.fastasyncworldedit.core.queue.IChunk; +import com.fastasyncworldedit.core.queue.IChunkGet; +import com.fastasyncworldedit.core.queue.IChunkSet; +import com.fastasyncworldedit.core.queue.IQueueExtent; +import com.fastasyncworldedit.core.queue.implementation.QueueHandler; +import com.fastasyncworldedit.core.queue.implementation.blocks.CharGetBlocks; +import com.fastasyncworldedit.core.util.MemUtil; +import com.sk89q.worldedit.internal.util.LogManagerCompat; +import com.sk89q.worldedit.util.formatting.text.TextComponent; +import org.apache.logging.log4j.Logger; + +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.locks.ReentrantLock; + +public abstract class AbstractBukkitGetBlocks extends CharGetBlocks { + + private static final Logger LOGGER = LogManagerCompat.getLogger(); + + protected final ServerLevel serverLevel; + protected final int chunkX; + protected final int chunkZ; + protected final ReentrantLock callLock = new ReentrantLock(); + protected final ConcurrentHashMap copies = new ConcurrentHashMap<>(); + protected final IntPair chunkPos; + protected final int minHeight; + protected final int maxHeight; + protected boolean createCopy = false; + protected boolean forceLoadSections = true; + protected int copyKey = 0; + + protected AbstractBukkitGetBlocks( + ServerLevel serverLevel, int chunkX, int chunkZ, int minY, int maxY + ) { + super(minY >> 4, maxY >> 4); + this.serverLevel = serverLevel; + this.chunkX = chunkX; + this.chunkZ = chunkZ; + this.minHeight = minY; + this.maxHeight = maxY; // Minecraft max limit is exclusive + this.chunkPos = new IntPair(chunkX, chunkZ); + } + + protected abstract void send(); + + protected abstract CompletableFuture ensureLoaded(ServerLevel serverLevel); + + protected abstract > T internalCall( + IChunkSet set, + Runnable finalizer, + int copyKey, + LevelChunk nmsChunk, + ServerLevel nmsWorld + ) throws Exception; + + @Override + @SuppressWarnings({"rawtypes", "unchecked"}) + public synchronized > T call(IQueueExtent owner, IChunkSet set, Runnable finalizer) { + if (!callLock.isHeldByCurrentThread()) { + throw new IllegalStateException("Attempted to call chunk GET but chunk was not call-locked."); + } + forceLoadSections = false; + final ServerLevel nmsWorld = serverLevel; + CompletableFuture nmsChunkFuture = ensureLoaded(nmsWorld); + LevelChunk chunk = nmsChunkFuture.getNow(null); + if ((chunk == null && MemUtil.shouldBeginSlow()) || Settings.settings().QUEUE.ASYNC_CHUNK_LOAD_WRITE) { + try { + // "Artificially" slow FAWE down if memory low as performing the operation async can cause large amounts of + // memory usage + chunk = nmsChunkFuture.get(); + } catch (InterruptedException | ExecutionException e) { + LOGGER.error("Could not get chunk at {},{} whilst low memory", chunkX, chunkZ, e); + throw new FaweException( + TextComponent.of("Could not get chunk at " + chunkX + "," + chunkZ + " whilst low memory: " + e.getMessage())); + } + } + final int finalCopyKey = copyKey; + // Run immediately if possible + if (chunk != null) { + return tryWrappedInternalCall(set, finalizer, finalCopyKey, chunk, nmsWorld); + } + // Submit via the STQE as that will help handle excessive queuing by waiting for the submission count to fall below the + // target size + nmsChunkFuture.thenApply(nmsChunk -> owner.submitTaskUnchecked(() -> (T) tryWrappedInternalCall( + set, + finalizer, + finalCopyKey, + nmsChunk, + nmsWorld + ))); + // If we have re-submitted, return a completed future to prevent potential deadlocks where a future reliant on the + // above submission is halting the BlockingExecutor, and preventing the above task from actually running. The futures + // submitted above will still be added to the STQE submissions. + return (T) (Future) CompletableFuture.completedFuture(null); + } + + private > T tryWrappedInternalCall( + IChunkSet set, + Runnable finalizer, + int copyKey, + LevelChunk nmsChunk, + ServerLevel nmsWorld + ) { + try { + return internalCall(set, finalizer, copyKey, nmsChunk, nmsWorld); + } catch (Throwable e) { + LOGGER.error("Error performing chunk call at chunk {},{}", chunkX, chunkZ, e); + return null; + } finally { + forceLoadSections = true; + } + } + + protected > T handleCallFinalizer(Runnable[] syncTasks, Runnable callback, Runnable finalizer) throws + Exception { + if (syncTasks != null) { + QueueHandler queueHandler = Fawe.instance().getQueueHandler(); + Runnable[] finalSyncTasks = syncTasks; + + // Chain the sync tasks and the callback + Callable> chain = () -> { + try { + // Run the sync tasks + for (Runnable task : finalSyncTasks) { + if (task != null) { + task.run(); + } + } + if (callback != null) { + return queueHandler.async(callback, null); + } else if (finalizer != null) { + return queueHandler.async(finalizer, null); + } + return null; + } catch (Throwable e) { + LOGGER.error("Error performing final chunk calling at {},{}", chunkX, chunkZ, e); + throw e; + } + }; + //noinspection unchecked - required at compile time + return (T) (Future) queueHandler.sync(chain); + } else { + if (callback != null) { + callback.run(); + } else if (finalizer != null) { + finalizer.run(); + } + } + return null; + } + + public int getChunkX() { + return chunkX; + } + + public int getChunkZ() { + return chunkZ; + } + + @Override + public boolean isCreateCopy() { + return createCopy; + } + + @Override + public int setCreateCopy(boolean createCopy) { + if (!callLock.isHeldByCurrentThread()) { + throw new IllegalStateException("Attempting to set if chunk GET should create copy, but it is not call-locked."); + } + this.createCopy = createCopy; + // Increment regardless of whether copy will be created or not to return null from getCopy() + return ++this.copyKey; + } + + @Override + public IChunkGet getCopy(final int key) { + return copies.remove(key); + } + + @Override + public void lockCall() { + this.callLock.lock(); + } + + @Override + public void unlockCall() { + this.callLock.unlock(); + } + + @Override + public int getMaxY() { + return maxHeight; + } + + @Override + public int getMinY() { + return minHeight; + } + +} diff --git a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/BukkitGetBlocks.java b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/BukkitGetBlocks.java deleted file mode 100644 index 8a5cc9d8a9..0000000000 --- a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/BukkitGetBlocks.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.fastasyncworldedit.bukkit.adapter; - -public interface BukkitGetBlocks { - - void send(); - -} 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 52035aa697..13e02fcd0a 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 @@ -95,10 +95,10 @@ private static void mapPalette( @Override public void sendChunk(IChunkGet chunk, int mask, boolean lighting) { - if (!(chunk instanceof BukkitGetBlocks)) { + if (!(chunk instanceof AbstractBukkitGetBlocks)) { throw new IllegalArgumentException("(IChunkGet) chunk not of type BukkitGetBlocks"); } - ((BukkitGetBlocks) chunk).send(); + ((AbstractBukkitGetBlocks) chunk).send(); } /** diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/Fawe.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/Fawe.java index 9045d0e0ec..57a61d1f0e 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/Fawe.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/Fawe.java @@ -135,6 +135,7 @@ private Fawe(final IFawe implementation) { } catch (Throwable ignored) { } }, 0); + TaskManager.taskManager().repeatAsync(MemUtil::checkAndSetApproachingLimit, 1); TaskManager.taskManager().repeat(timer, 1); uuidKeyQueuedExecutorService = new KeyQueuedExecutorService<>(new ThreadPoolExecutor( @@ -418,6 +419,7 @@ private void setupMemoryListener() { if (heapSize < heapMaxSize) { return; } + LOGGER.warn("High memory usage detected, FAWE will attempt to slow operations to prevent a crash."); MemUtil.memoryLimitedTask(); }, null, null); diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/FaweCache.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/FaweCache.java index 3ed494298f..242d5f637a 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/FaweCache.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/FaweCache.java @@ -50,6 +50,7 @@ import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; +import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -645,7 +646,7 @@ public ThreadPoolExecutor newBlockingExecutor(String name) { */ public ThreadPoolExecutor newBlockingExecutor(String name, Logger logger) { int nThreads = Settings.settings().QUEUE.PARALLEL_THREADS; - ArrayBlockingQueue queue = new ArrayBlockingQueue<>(nThreads, true); + LinkedBlockingQueue queue = new LinkedBlockingQueue<>(); return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, queue, new ThreadFactoryBuilder().setNameFormat(name).build(), diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/configuration/Config.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/configuration/Config.java index 880b4cdf79..e872f6fdeb 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/configuration/Config.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/configuration/Config.java @@ -19,7 +19,6 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; -import java.util.AbstractMap; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -34,7 +33,7 @@ public class Config { private final Map removedKeyVals = new HashMap<>(); @Nullable - private Map> copyTo = new HashMap<>(); + private Map copyTo = new HashMap<>(); private boolean performCopyTo = false; private List existingMigrateNodes = null; @@ -85,11 +84,11 @@ private void setLoadedNode(String key, Object value, Class root) { if (copyTo != null) { copyTo.remove(key); // Remove if the config field is already written final Object finalValue = value; - copyTo.replaceAll((copyToNode, entry) -> { - if (!key.equals(entry.getKey())) { - return entry; + copyTo.replaceAll((copyToNode, fromNode) -> { + if (!key.equals(fromNode.node())) { + return fromNode; } - return new AbstractMap.SimpleEntry<>(key, finalValue); + return new FromNode(key, fromNode.computation, finalValue); }); } Migrate migrate = field.getAnnotation(Migrate.class); @@ -234,6 +233,16 @@ public void save(File file) { } + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.FIELD}) + @interface ComputedFrom { + + String node(); + + Class> computer(); + + } + @Ignore // This is not part of the config public static class ConfigBlock { @@ -319,14 +328,27 @@ private void save(PrintWriter writer, Class clazz, final Object instance, int if (copyTo != null && (copiedFrom = field.getAnnotation(CopiedFrom.class)) != null) { String node = toNodeName(field.getName()); node = parentNode == null ? node : parentNode + "." + node; - Map.Entry entry = copyTo.remove(node); + FromNode entry = copyTo.remove(node); Object copiedVal; if (entry == null) { - copyTo.put(node, new AbstractMap.SimpleEntry<>(copiedFrom.value(), null)); - } else if ((copiedVal = entry.getValue()) != null) { + copyTo.put(node, new FromNode(copiedFrom.value(), null, null)); + } else if ((copiedVal = entry.val()) != null) { field.set(instance, copiedVal); } } + ComputedFrom computedFrom; + if (copyTo != null && (computedFrom = field.getAnnotation(ComputedFrom.class)) != null) { + String node = toNodeName(field.getName()); + node = parentNode == null ? node : parentNode + "." + node; + FromNode entry = copyTo.remove(node); + Object copiedVal; + if (entry == null) { + copyTo.put(node, new FromNode(computedFrom.node(), computedFrom.computer(), null)); + } else if ((copiedVal = entry.val()) != null) { + ConfigOptComputation computer = computedFrom.computer().getDeclaredConstructor().newInstance(); + field.set(instance, computer.apply(copiedVal)); + } + } Create create = field.getAnnotation(Create.class); if (create != null) { Object value = field.get(instance); @@ -513,4 +535,8 @@ private void setAccessible(Field field) throws NoSuchFieldException, IllegalAcce } } + private record FromNode(String node, Class> computation, Object val) { + + } + } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/configuration/ConfigOptComputation.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/configuration/ConfigOptComputation.java new file mode 100644 index 0000000000..31d4d9133e --- /dev/null +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/configuration/ConfigOptComputation.java @@ -0,0 +1,17 @@ +package com.fastasyncworldedit.core.configuration; + +sealed abstract class ConfigOptComputation permits + ConfigOptComputation.THREAD_TARGET_SIZE_COMPUTATION { + + public abstract R apply(Object val); + + static final class THREAD_TARGET_SIZE_COMPUTATION extends ConfigOptComputation { + + @Override + public Integer apply(Object val) { + return 100 * 2 / (Integer) val; + } + + } + +} diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/configuration/Settings.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/configuration/Settings.java index 60ec9311a3..7c456f5df5 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/configuration/Settings.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/configuration/Settings.java @@ -57,6 +57,12 @@ public class Settings extends Config { " - Disable with 100 or -1." }) public int MAX_MEMORY_PERCENT = 95; + @Comment({ + "When percent memory usage reaches this threshold some aspects of editing will be slowed down:", + " - FAWE-Asynchronous chunk loading when writing changes (see queue.async-chunk-load-write)" + }) + public int SLOWER_MEMORY_PERCENT = 80; + @Create public ENABLED_COMPONENTS ENABLED_COMPONENTS; @Create @@ -599,6 +605,24 @@ public static class QUEUE { }) public boolean POOL = true; + @Comment({ + "If chunk loading for writing edits to the world should be performed asynchronously to FAWE", + " - Enable to improve performance at the expense of memory", + " - If experience out of memory crashed, disable this or reduce slower-memory-percent" + }) + public boolean ASYNC_CHUNK_LOAD_WRITE = true; + + @Comment({ + "Percentage of queue.target-size to use per thread in multi-threaded operations", + " - Minimum of 100 / queue.parallel-threads (queue.target-size split across threads)", + " - Maximum of 100 (queue.target-size per thread)", + " - Higher performance at the expense of memory", + " - I.e. target-size=400, parallel-threads=8 and threads-target-size=25 means target-size of 100 per thread", + " - Defaults to 100 * 2 / parallel-threads" + }) + @ComputedFrom(node = "queue.parallel-threads", computer = ConfigOptComputation.THREAD_TARGET_SIZE_COMPUTATION.class) + public int THREAD_TARGET_SIZE_PERCENT = 100 * 2 / Runtime.getRuntime().availableProcessors(); + public static class PROGRESS { @Comment({"Display constant titles about the progress of a user's edit", diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IBatchProcessor.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IBatchProcessor.java index 7bce208fca..91d037f906 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IBatchProcessor.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IBatchProcessor.java @@ -11,6 +11,7 @@ import com.sk89q.worldedit.world.block.BlockTypesCache; import javax.annotation.Nullable; +import java.util.Arrays; import java.util.Collection; import java.util.Map; import java.util.concurrent.CompletableFuture; @@ -128,9 +129,7 @@ default boolean trimY(IChunkSet set, int minY, int maxY, final boolean keepInsid char[] arr = set.loadIfPresent(layer); if (arr != null) { int index = (minY & 15) << 8; - for (int i = index; i < 4096; i++) { - arr[i] = BlockTypesCache.ReservedIDs.__RESERVED__; - } + Arrays.fill(arr, index, 4096, (char) BlockTypesCache.ReservedIDs.__RESERVED__); } set.setBlocks(layer, arr); } else if (layer == maxLayer) { diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IChunkGet.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IChunkGet.java index b726900299..a260dae7f3 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IChunkGet.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IChunkGet.java @@ -45,7 +45,7 @@ default void optimize() { } - > T call(IChunkSet set, Runnable finalize); + > T call(IQueueExtent owner, IChunkSet set, Runnable finalize); @Deprecated(forRemoval = true, since = "2.11.2") default CompoundTag getEntity(UUID uuid) { @@ -81,7 +81,7 @@ default IChunkGet getCopy(int key) { } /** - * Lock the {@link IChunkGet#call(IChunkSet, Runnable)} method to the current thread using a reentrant lock. Also locks + * Lock the {@link IChunkGet#call(IQueueExtent, IChunkSet, Runnable)} method to the current thread using a reentrant lock. Also locks * related methods e.g. {@link IChunkGet#setCreateCopy(boolean)} * * @since 2.8.2 @@ -89,7 +89,7 @@ default IChunkGet getCopy(int key) { default void lockCall() {} /** - * Unlock {@link IChunkGet#call(IChunkSet, Runnable)} (and other related methods) to executions from other threads + * Unlock {@link IChunkGet#call(IQueueExtent, IChunkSet, Runnable)} (and other related methods) to executions from other threads * * @since 2.8.2 */ diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IQueueExtent.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IQueueExtent.java index e1714f0398..aef3acf4a1 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IQueueExtent.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IQueueExtent.java @@ -11,10 +11,12 @@ import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.util.SideEffectSet; +import org.jetbrains.annotations.ApiStatus; import javax.annotation.Nullable; import java.io.Flushable; import java.util.Set; +import java.util.concurrent.Callable; import java.util.concurrent.Future; /** @@ -62,7 +64,7 @@ default boolean isQueueEnabled() { IChunkSet getCachedSet(int chunkX, int chunkZ); /** - * Submit the chunk so that it's changes are applied to the world + * Submit the chunk so that its changes are applied to the world * * @return Future */ @@ -96,6 +98,12 @@ default BlockVector3 getMaximumPoint() { */ SideEffectSet getSideEffectSet(); + /** + * Submit a task to the extent to be queued as if it were a chunk + */ + @ApiStatus.Internal + > V submitTaskUnchecked(Callable callable); + /** * Create a new root IChunk object. Full chunks will be reused, so a more optimized chunk can be * returned in that case. diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/ParallelQueueExtent.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/ParallelQueueExtent.java index 591c1bcd03..cc1db08ba7 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/ParallelQueueExtent.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/ParallelQueueExtent.java @@ -132,6 +132,7 @@ private IQueueExtent getNewQueue() { queue.setFastMode(fastmode); queue.setSideEffectSet(sideEffectSet); queue.setFaweExceptionArray(faweExceptionReasonsUsed); + queue.setTargetSize(Settings.settings().QUEUE.TARGET_SIZE * Settings.settings().QUEUE.THREAD_TARGET_SIZE_PERCENT / 100); enter(queue); return queue; } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/QueueHandler.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/QueueHandler.java index 7bdbf46455..315c900274 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/QueueHandler.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/QueueHandler.java @@ -18,6 +18,7 @@ import com.fastasyncworldedit.core.wrappers.WorldWrapper; import com.google.common.util.concurrent.Futures; import com.sk89q.worldedit.world.World; +import org.jetbrains.annotations.ApiStatus; import java.lang.ref.WeakReference; import java.util.HashMap; @@ -41,14 +42,12 @@ @SuppressWarnings({"unchecked", "rawtypes"}) public abstract class QueueHandler implements Trimable, Runnable { - private static final int PROCESSORS = Runtime.getRuntime().availableProcessors(); - /** * Primary queue should be used for tasks that are unlikely to wait on other tasks, IO, etc. (i.e. spend most of their * time utilising CPU. */ private final ForkJoinPool forkJoinPoolPrimary = new ForkJoinPool( - PROCESSORS, + Settings.settings().QUEUE.PARALLEL_THREADS, new FaweForkJoinWorkerThreadFactory("FAWE Fork Join Pool Primary - %s"), null, false @@ -59,7 +58,7 @@ public abstract class QueueHandler implements Trimable, Runnable { * primary queue. They may be IO-bound tasks. */ private final ForkJoinPool forkJoinPoolSecondary = new ForkJoinPool( - PROCESSORS, + Settings.settings().QUEUE.PARALLEL_THREADS, new FaweForkJoinWorkerThreadFactory("FAWE Fork Join Pool Secondary - %s"), null, false @@ -93,6 +92,11 @@ protected QueueHandler() { TaskManager.taskManager().repeat(this, 1); } + @ApiStatus.Internal + public ThreadPoolExecutor getBlockingExecutor() { + return blockingExecutor; + } + @Override public void run() { if (!Fawe.isMainThread()) { @@ -380,6 +384,11 @@ public > T submit(IQueueChunk chunk) { return (T) blockingExecutor.submit(chunk); } + @ApiStatus.Internal + public > T submitToBlocking(Callable callable) { + return (T) blockingExecutor.submit(callable); + } + /** * Get or create the WorldChunkCache for a world */ diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/SingleThreadQueueExtent.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/SingleThreadQueueExtent.java index 389f0d66e7..82342da727 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/SingleThreadQueueExtent.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/SingleThreadQueueExtent.java @@ -32,6 +32,7 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; import org.apache.logging.log4j.Logger; +import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; @@ -70,6 +71,7 @@ public class SingleThreadQueueExtent extends ExtentBatchProcessorHolder implemen private int lastException = Integer.MIN_VALUE; private int exceptionCount = 0; private SideEffectSet sideEffectSet = SideEffectSet.defaults(); + private int targetSize = Settings.settings().QUEUE.TARGET_SIZE; public SingleThreadQueueExtent() { } @@ -136,6 +138,10 @@ public World getWorld() { return world; } + public void setTargetSize(int targetSize) { + this.targetSize = targetSize; + } + /** * Sets the cached boolean array of length {@code FaweException.Type.values().length} that determines if a thrown * {@link FaweException} of type {@link FaweException.Type} should be output to console, rethrown to attempt to be visible @@ -170,6 +176,7 @@ protected synchronized void reset() { this.setPostProcessor(EmptyBatchProcessor.getInstance()); this.world = null; this.faweExceptionReasonsUsed = new boolean[FaweException.Type.values().length]; + this.targetSize = Settings.settings().QUEUE.TARGET_SIZE; } /** @@ -262,6 +269,13 @@ private > V submitUnchecked(IQueueChunk chunk) { return (V) Fawe.instance().getQueueHandler().submit(chunk); } + @Override + public > V submitTaskUnchecked(Callable callable) { + V future = (V) Fawe.instance().getQueueHandler().submitToBlocking(callable); + submissions.add(future); + return future; + } + @Override public synchronized boolean trim(boolean aggressive) { cacheGet.trim(aggressive); @@ -330,7 +344,7 @@ public final IQueueChunk getOrCreateChunk(int x, int z) { // If queueing is enabled AND either of the following // - memory is low & queue size > num threads + 8 // - queue size > target size and primary queue has less than num threads submissions - int targetSize = lowMem ? Settings.settings().QUEUE.PARALLEL_THREADS + 8 : Settings.settings().QUEUE.TARGET_SIZE; + int targetSize = lowMem ? Settings.settings().QUEUE.PARALLEL_THREADS + 8 : this.targetSize; if (enabledQueue && size > targetSize && (lowMem || Fawe.instance().getQueueHandler().isUnderutilized())) { chunk = chunks.removeFirst(); final Future future = submitUnchecked(chunk); diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/blocks/CharBlocks.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/blocks/CharBlocks.java index c338033f0a..1c5690da58 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/blocks/CharBlocks.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/blocks/CharBlocks.java @@ -9,6 +9,8 @@ import org.apache.logging.log4j.Logger; import javax.annotation.Nullable; +import java.lang.invoke.VarHandle; +import java.util.Arrays; public abstract class CharBlocks implements IBlocks { @@ -20,7 +22,10 @@ public char[] get(CharBlocks blocks, int layer) { char[] arr = blocks.blocks[layer]; if (arr == null) { // Chunk probably trimmed mid-operations, but do nothing about it to avoid other issues - return EMPTY.get(blocks, layer, false); + synchronized (blocks.sectionLocks[layer]) { + LOGGER.warn("Unexpected null section, please report this occurence alongside a debugpaste."); + return getSkipFull(blocks, layer, false); + } } return arr; } @@ -32,6 +37,7 @@ public char[] get(CharBlocks blocks, int layer, boolean aggressive) { if (arr == null) { // Chunk probably trimmed mid-operations, but do nothing about it to avoid other issues synchronized (blocks.sectionLocks[layer]) { + LOGGER.warn("Unexpected null section, please report this occurence alongside a debugpaste."); return getSkipFull(blocks, layer, aggressive); } } @@ -118,6 +124,7 @@ public boolean trim(boolean aggressive, int layer) { public synchronized IChunkSet reset() { for (int i = 0; i < sectionCount; i++) { sections[i] = EMPTY; + VarHandle.storeStoreFence(); blocks[i] = null; } return null; @@ -134,12 +141,22 @@ public char[] update(int layer, char[] data, boolean aggressive) { if (data == null) { return new char[4096]; } - for (int i = 0; i < 4096; i++) { - data[i] = defaultOrdinal(); - } + Arrays.fill(data, defaultOrdinal()); return data; } + protected char[] loadPrivately(int layer) { + layer -= getMinSectionPosition(); + if (sections[layer] != null) { + synchronized (sectionLocks[layer]) { + if (sections[layer].isFull() && blocks[layer] != null) { + return blocks[layer]; + } + } + } + return update(layer, null, true); + } + // Not synchronized as any subsequent methods called from this class will be, or the section shouldn't appear as loaded anyway. @Override public boolean hasSection(int layer) { diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/blocks/NullChunkGet.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/blocks/NullChunkGet.java index 273f449523..6c73bc3205 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/blocks/NullChunkGet.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/blocks/NullChunkGet.java @@ -4,8 +4,10 @@ import com.fastasyncworldedit.core.extent.processor.heightmap.HeightMapType; import com.fastasyncworldedit.core.nbt.FaweCompoundTag; import com.fastasyncworldedit.core.queue.IBlocks; +import com.fastasyncworldedit.core.queue.IChunk; import com.fastasyncworldedit.core.queue.IChunkGet; import com.fastasyncworldedit.core.queue.IChunkSet; +import com.fastasyncworldedit.core.queue.IQueueExtent; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.biome.BiomeTypes; @@ -114,7 +116,7 @@ public boolean trim(boolean aggressive, int layer) { } @Nullable - public > T call(@Nonnull IChunkSet set, @Nonnull Runnable finalize) { + public > T call(IQueueExtent owner, @Nonnull IChunkSet set, @Nonnull Runnable finalize) { return null; } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/chunk/ChunkHolder.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/chunk/ChunkHolder.java index 375578e44c..1183d8b658 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/chunk/ChunkHolder.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/chunk/ChunkHolder.java @@ -1010,7 +1010,8 @@ public synchronized void init(IQueueExtent extent, int chu public synchronized T call() { if (chunkSet != null && !chunkSet.isEmpty()) { IChunkSet copy = chunkSet.createCopy(); - return this.call(copy, () -> { + + return this.call(extent, copy, () -> { // Do nothing }); } @@ -1020,8 +1021,9 @@ public synchronized T call() { /** * This method should never be called from outside ChunkHolder */ + @Override - public synchronized T call(IChunkSet set, Runnable finalize) { + public > U call(IQueueExtent owner, IChunkSet set, Runnable finalize) { if (set != null) { IChunkGet get = getOrCreateGet(); try { @@ -1039,7 +1041,7 @@ public synchronized T call(IChunkSet set, Runnable finalize) { } else { finalizer = finalize; } - return get.call(set, finalizer); + return get.call(extent, set, finalizer); } finally { get.unlockCall(); untrackExtent(); diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/chunk/NullChunk.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/chunk/NullChunk.java index 770a878a95..26a6078c67 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/chunk/NullChunk.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/implementation/chunk/NullChunk.java @@ -4,8 +4,10 @@ import com.fastasyncworldedit.core.extent.processor.heightmap.HeightMapType; import com.fastasyncworldedit.core.nbt.FaweCompoundTag; import com.fastasyncworldedit.core.queue.Filter; +import com.fastasyncworldedit.core.queue.IChunk; import com.fastasyncworldedit.core.queue.IChunkSet; import com.fastasyncworldedit.core.queue.IQueueChunk; +import com.fastasyncworldedit.core.queue.IQueueExtent; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.util.SideEffectSet; @@ -236,7 +238,7 @@ public int getMinSectionPosition() { } @Nullable - public > T call(@Nullable IChunkSet set, @Nullable Runnable finalize) { + public > T call(IQueueExtent owner, @Nullable IChunkSet set, @Nullable Runnable finalize) { return null; } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/MemUtil.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/MemUtil.java index 7760663ea2..5f01db884d 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/MemUtil.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/MemUtil.java @@ -1,6 +1,8 @@ package com.fastasyncworldedit.core.util; import com.fastasyncworldedit.core.configuration.Settings; +import com.sk89q.worldedit.internal.util.LogManagerCompat; +import org.apache.logging.log4j.Logger; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; @@ -8,7 +10,9 @@ public class MemUtil { + private static final Logger LOGGER = LogManagerCompat.getLogger(); private static final AtomicBoolean memory = new AtomicBoolean(false); + private static final AtomicBoolean slower = new AtomicBoolean(false); public static boolean isMemoryFree() { return !memory.get(); @@ -28,6 +32,10 @@ public static boolean isMemoryLimitedSlow() { return false; } + public static boolean shouldBeginSlow() { + return slower.get(); + } + public static long getUsedBytes() { return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); } @@ -51,6 +59,14 @@ public static int calculateMemory() { return size; } + public static void checkAndSetApproachingLimit() { + final long heapFreeSize = Runtime.getRuntime().freeMemory(); + final long heapMaxSize = Runtime.getRuntime().maxMemory(); + final int size = (int) ((heapFreeSize * 100) / heapMaxSize); + boolean limited = size >= Settings.settings().SLOWER_MEMORY_PERCENT; + slower.set(limited); + } + private static final Queue memoryLimitedTasks = new ConcurrentLinkedQueue<>(); private static final Queue memoryPlentifulTasks = new ConcurrentLinkedQueue<>();