diff --git a/build-logic/src/main/kotlin/buildlogic.common.gradle.kts b/build-logic/src/main/kotlin/buildlogic.common.gradle.kts index 227ec60d08..886b62205d 100644 --- a/build-logic/src/main/kotlin/buildlogic.common.gradle.kts +++ b/build-logic/src/main/kotlin/buildlogic.common.gradle.kts @@ -11,9 +11,12 @@ configurations.all { } } -plugins.withId("java") { - the().toolchain { - languageVersion.set(JavaLanguageVersion.of(21)) +allprojects { + plugins.withId("java") { + the().toolchain { + languageVersion.set(JavaLanguageVersion.of(21)) + vendor.set(JvmVendorSpec.ADOPTIUM) + } } } diff --git a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightFaweAdapter.java index e524fd0459..f0b9eed75b 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightFaweAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightFaweAdapter.java @@ -10,6 +10,7 @@ import com.fastasyncworldedit.core.queue.IBatchProcessor; import com.fastasyncworldedit.core.queue.IChunkGet; import com.fastasyncworldedit.core.queue.implementation.packet.ChunkPacket; +import com.fastasyncworldedit.core.util.FoliaUtil; import com.fastasyncworldedit.core.util.NbtUtils; import com.fastasyncworldedit.core.util.TaskManager; import com.google.common.base.Preconditions; @@ -21,6 +22,7 @@ import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.bukkit.WorldEditPlugin; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.nbt.PaperweightLazyCompoundTag; import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R2.regen.PaperweightRegen; @@ -54,6 +56,7 @@ import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.registry.BlockMaterial; import io.papermc.lib.PaperLib; +import io.papermc.paper.threadedregions.scheduler.ScheduledTask; import net.minecraft.core.BlockPos; import net.minecraft.core.Registry; import net.minecraft.core.SectionPos; @@ -127,6 +130,8 @@ import java.util.Objects; import java.util.OptionalInt; import java.util.Set; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -350,28 +355,45 @@ public Set getSupportedSideEffects() { public BaseEntity getEntity(org.bukkit.entity.Entity entity) { Preconditions.checkNotNull(entity); - CraftEntity craftEntity = ((CraftEntity) entity); - Entity mcEntity = craftEntity.getHandle(); - - String id = getEntityId(mcEntity); - - if (id != null) { - EntityType type = com.sk89q.worldedit.world.entity.EntityTypes.get(id); - Supplier saveTag = () -> { - final net.minecraft.nbt.CompoundTag minecraftTag = new net.minecraft.nbt.CompoundTag(); - if (!readEntityIntoTag(mcEntity, minecraftTag)) { + String id = entity.getType().getKey().toString(); + EntityType type = com.sk89q.worldedit.world.entity.EntityTypes.get(id); + Supplier saveTag = () -> { + net.minecraft.nbt.CompoundTag minecraftTag = new net.minecraft.nbt.CompoundTag(); + Entity mcEntity; + + if (FoliaUtil.isFoliaServer()) { + CompletableFuture handleFuture = new CompletableFuture<>(); + entity.getScheduler().run( + WorldEditPlugin.getInstance(), + (ScheduledTask task) -> { + try { + handleFuture.complete(((CraftEntity) entity).getHandle()); + } catch (Throwable t) { + handleFuture.completeExceptionally(t); + } + }, + () -> handleFuture.completeExceptionally(new CancellationException("Entity scheduler task cancelled")) + ); + try { + mcEntity = handleFuture.join(); + } catch (Throwable t) { + LOGGER.error("Failed to safely get NMS handle for {}", id, t); return null; } - //add Id for AbstractChangeSet to work - final LinCompoundTag tag = (LinCompoundTag) toNativeLin(minecraftTag); - final Map> tags = NbtUtils.getLinCompoundTagValues(tag); - tags.put("Id", LinStringTag.of(id)); - return LinCompoundTag.of(tags); - }; - return new LazyBaseEntity(type, saveTag); - } else { - return null; - } + } else { + mcEntity = ((CraftEntity) entity).getHandle(); + } + + if (mcEntity == null || !readEntityIntoTag(mcEntity, minecraftTag)) { + return null; + } + + LinCompoundTag tag = (LinCompoundTag) toNativeLin(minecraftTag); + Map> tags = NbtUtils.getLinCompoundTagValues(tag); + tags.put("Id", LinStringTag.of(id)); + return LinCompoundTag.of(tags); + }; + return new LazyBaseEntity(type, saveTag); } @Override @@ -550,20 +572,62 @@ public org.bukkit.inventory.ItemStack adapt(BaseItemStack baseItemStack) { @Override protected void preCaptureStates(final ServerLevel serverLevel) { - serverLevel.captureTreeGeneration = true; - serverLevel.captureBlockStates = true; + try { + Field captureTreeField = ServerLevel.class.getDeclaredField("captureTreeGeneration"); + captureTreeField.setAccessible(true); + captureTreeField.setBoolean(serverLevel, true); + } catch (NoSuchFieldException | IllegalAccessException e) { + // Unable to read captureTreeGeneration field + } + try { + Field captureBlockField = ServerLevel.class.getDeclaredField("captureBlockStates"); + captureBlockField.setAccessible(true); + captureBlockField.setBoolean(serverLevel, true); + } catch (NoSuchFieldException | IllegalAccessException e) { + // Unable to read captureTreeGeneration field + } } @Override protected List getCapturedBlockStatesCopy(final ServerLevel serverLevel) { - return new ArrayList<>(serverLevel.capturedBlockStates.values()); + try { + Field capturedStatesField = ServerLevel.class.getDeclaredField("capturedBlockStates"); + capturedStatesField.setAccessible(true); + @SuppressWarnings("unchecked") + Map capturedStates = (Map) capturedStatesField.get(serverLevel); + return capturedStates != null ? new ArrayList<>(capturedStates.values()) : new ArrayList<>(); + } catch (NoSuchFieldException | IllegalAccessException e) { + return new ArrayList<>(); + } } @Override protected void postCaptureBlockStates(final ServerLevel serverLevel) { - serverLevel.captureBlockStates = false; - serverLevel.captureTreeGeneration = false; - serverLevel.capturedBlockStates.clear(); + try { + Field captureBlockField = ServerLevel.class.getDeclaredField("captureBlockStates"); + captureBlockField.setAccessible(true); + captureBlockField.setBoolean(serverLevel, false); + } catch (NoSuchFieldException | IllegalAccessException e) { + // Unable to read captureTreeGeneration field + } + try { + Field captureTreeField = ServerLevel.class.getDeclaredField("captureTreeGeneration"); + captureTreeField.setAccessible(true); + captureTreeField.setBoolean(serverLevel, false); + } catch (NoSuchFieldException | IllegalAccessException e) { + // Unable to read captureTreeGeneration field + } + try { + Field capturedStatesField = ServerLevel.class.getDeclaredField("capturedBlockStates"); + capturedStatesField.setAccessible(true); + @SuppressWarnings("unchecked") + Map capturedStates = (Map) capturedStatesField.get(serverLevel); + if (capturedStates != null) { + capturedStates.clear(); + } + } catch (NoSuchFieldException | IllegalAccessException e) { + // Unable to read captureTreeGeneration field + } } @Override diff --git a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightFaweWorldNativeAccess.java b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightFaweWorldNativeAccess.java index 352e9b9583..1fce0d1d6a 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightFaweWorldNativeAccess.java +++ b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightFaweWorldNativeAccess.java @@ -20,6 +20,7 @@ import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.chunk.LevelChunk; +import org.bukkit.Bukkit; import org.bukkit.craftbukkit.v1_20_R2.CraftWorld; import org.bukkit.craftbukkit.v1_20_R2.block.data.CraftBlockData; import org.bukkit.event.block.BlockPhysicsEvent; @@ -58,7 +59,7 @@ public PaperweightFaweWorldNativeAccess(PaperweightFaweAdapter paperweightFaweAd this.level = level; // Use the actual tick as minecraft-defined so we don't try to force blocks into the world when the server's already lagging. // - With the caveat that we don't want to have too many cached changed (1024) so we'd flush those at 1024 anyway. - this.lastTick = new AtomicInteger(MinecraftServer.currentTick); + this.lastTick = new AtomicInteger(getCurrentTick()); } private Level getLevel() { @@ -94,7 +95,7 @@ public synchronized net.minecraft.world.level.block.state.BlockState setBlockSta LevelChunk levelChunk, BlockPos blockPos, net.minecraft.world.level.block.state.BlockState blockState ) { - int currentTick = MinecraftServer.currentTick; + int currentTick = getCurrentTick(); if (Fawe.isMainThread()) { return levelChunk.setBlockState(blockPos, blockState, this.sideEffectSet != null && this.sideEffectSet.shouldApply(SideEffect.UPDATE) @@ -289,4 +290,16 @@ private record CachedChange( } + private int getCurrentTick() { + try { + return MinecraftServer.currentTick; + } catch (NoSuchFieldError e) { + try { + return Bukkit.getCurrentTick(); + } catch (Exception ex) { + return 0; + } + } + } + } 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 a16d439ee5..f79b045d47 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 @@ -11,6 +11,7 @@ import com.fastasyncworldedit.core.math.IntPair; import com.fastasyncworldedit.core.nbt.FaweCompoundTag; import com.fastasyncworldedit.core.queue.IChunkSet; +import com.fastasyncworldedit.core.util.FoliaUtil; import com.fastasyncworldedit.core.util.MathMan; import com.fastasyncworldedit.core.util.NbtUtils; import com.fastasyncworldedit.core.util.collection.AdaptedMap; @@ -56,6 +57,8 @@ import net.minecraft.world.level.levelgen.Heightmap; import net.minecraft.world.level.lighting.LevelLightEngine; import org.apache.logging.log4j.Logger; +import org.bukkit.Bukkit; +import org.bukkit.Location; import org.bukkit.World; import org.bukkit.craftbukkit.v1_20_R2.CraftWorld; import org.bukkit.craftbukkit.v1_20_R2.block.CraftBlock; @@ -194,23 +197,30 @@ public void removeSectionLighting(int layer, boolean sky) { @Override public FaweCompoundTag tile(final int x, final int y, final int z) { - BlockEntity blockEntity = getChunk().getBlockEntity(new BlockPos((x & 15) + ( - chunkX << 4), y, (z & 15) + ( - chunkZ << 4))); - if (blockEntity == null) { + try { + BlockEntity blockEntity = getChunk().getBlockEntity(new BlockPos((x & 15) + ( + chunkX << 4), y, (z & 15) + ( + chunkZ << 4))); + if (blockEntity == null) { + return null; + } + return NMS_TO_TILE.apply(blockEntity); + } catch (NullPointerException e) { return null; } - return NMS_TO_TILE.apply(blockEntity); - } @Override public Map tiles() { - Map nmsTiles = getChunk().getBlockEntities(); - if (nmsTiles.isEmpty()) { + try { + Map nmsTiles = getChunk().getBlockEntities(); + if (nmsTiles.isEmpty()) { + return Collections.emptyMap(); + } + return AdaptedMap.immutable(nmsTiles, posNms2We, NMS_TO_TILE); + } catch (NullPointerException e) { return Collections.emptyMap(); } - return AdaptedMap.immutable(nmsTiles, posNms2We, NMS_TO_TILE); } @Override @@ -317,7 +327,20 @@ public Set getFullEntities() { } private void removeEntity(Entity entity) { - entity.discard(); + if (FoliaUtil.isFoliaServer()) { + entity.getBukkitEntity().getScheduler().execute( + WorldEditPlugin.getInstance(), + () -> { + if (!entity.isRemoved()) { + entity.discard(); + } + }, + null, + 1L + ); + } else if (!entity.isRemoved()) { + entity.discard(); + } } @Override @@ -345,11 +368,11 @@ protected > T internalCall( 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; + BlockPos pos = entry.getKey(); + int lx = pos.getX() & 15; + int ly = pos.getY(); + int lz = pos.getZ() & 15; + int layer = ly >> 4; if (!set.hasSection(layer)) { continue; } @@ -357,12 +380,26 @@ protected > T internalCall( int ordinal = set.getBlock(lx, ly, lz).getOrdinal(); if (ordinal != BlockTypesCache.ReservedIDs.__RESERVED__) { BlockEntity tile = entry.getValue(); - if (PaperLib.isPaper() && tile instanceof BeaconBlockEntity) { + if (PaperLib.isPaper() && tile instanceof BeaconBlockEntity beacon) { if (beacons == null) { beacons = new ArrayList<>(); } - beacons.add(tile); - PaperweightPlatformAdapter.removeBeacon(tile, nmsChunk); + beacons.add(beacon); + Location location = new Location( + nmsWorld.getWorld(), + beacon.getBlockPos().getX(), + beacon.getBlockPos().getY(), + beacon.getBlockPos().getZ() + ); + if (FoliaUtil.isFoliaServer()) { + Bukkit.getServer().getRegionScheduler().execute( + WorldEditPlugin.getInstance(), + location, + () -> PaperweightPlatformAdapter.removeBeacon(beacon, nmsChunk) + ); + } else { + PaperweightPlatformAdapter.removeBeacon(beacon, nmsChunk); + } continue; } nmsChunk.removeBlockEntity(tile.getBlockPos()); @@ -372,7 +409,7 @@ protected > T internalCall( } } } - final BiomeType[][] biomes = set.getBiomes(); + BiomeType[][] biomes = set.getBiomes(); int bitMask = 0; synchronized (nmsChunk) { @@ -598,7 +635,7 @@ protected > T internalCall( // 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; + List finalBeacons = beacons; syncTasks.add(() -> { for (BlockEntity beacon : finalBeacons) { BeaconBlockEntity.playSound(beacon.getLevel(), beacon.getBlockPos(), SoundEvents.BEACON_DEACTIVATE); @@ -611,7 +648,7 @@ protected > T internalCall( if (entityRemoves != null && !entityRemoves.isEmpty()) { syncTasks.add(() -> { Set entitiesRemoved = new HashSet<>(); - final List entities = PaperweightPlatformAdapter.getEntities(nmsChunk); + List entities = PaperweightPlatformAdapter.getEntities(nmsChunk); for (Entity entity : entities) { UUID uuid = entity.getUUID(); @@ -643,44 +680,60 @@ protected > T internalCall( syncTasks.add(() -> { 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()); + FaweCompoundTag nativeTag = iterator.next(); + LinCompoundTag linTag = nativeTag.linTag(); + LinStringTag idTag = linTag.findTag("Id", LinTagType.stringTag()); + LinListTag posTag = linTag.findListTag("Pos", LinTagType.doubleTag()); + 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(); + double x = posTag.get(0).valueAsDouble(); + double y = posTag.get(1).valueAsDouble(); + double z = posTag.get(2).valueAsDouble(); + float yaw = rotTag.get(0).valueAsFloat(); + float pitch = rotTag.get(1).valueAsFloat(); + 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) { + CompoundTag tag = (CompoundTag) adapter.fromNativeLin(linTag); + for (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 (!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(); + Location location = new Location(nmsWorld.getWorld(), x, y, z); + if (FoliaUtil.isFoliaServer()) { + Bukkit.getServer().getRegionScheduler().execute(WorldEditPlugin.getInstance(), location, () -> { + 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 + ); + iterator.remove(); + } + }); + } else { + 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 + ); + iterator.remove(); + } } } } @@ -692,27 +745,37 @@ protected > T internalCall( Map tiles = set.tiles(); if (tiles != null && !tiles.isEmpty()) { syncTasks.add(() -> { - 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 (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); + World world = nmsWorld.getWorld(); + for (Map.Entry entry : tiles.entrySet()) { + FaweCompoundTag nativeTag = entry.getValue(); + BlockVector3 blockHash = entry.getKey(); + int x = blockHash.x() + bx; + int y = blockHash.y(); + int z = blockHash.z() + bz; + BlockPos pos = new BlockPos(x, y, z); + Location location = new Location(world, x, y, z); + + Runnable setTile = () -> { + synchronized (nmsChunk) { + BlockEntity tileEntity = nmsChunk.getBlockEntity(pos); + if (tileEntity == null || tileEntity.isRemoved()) { + nmsChunk.removeBlockEntity(pos); + tileEntity = nmsChunk.getBlockEntity(pos); + } + if (tileEntity != null) { + 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); + } } + }; + + if (FoliaUtil.isFoliaServer()) { + Bukkit.getServer().getRegionScheduler().execute(WorldEditPlugin.getInstance(), location, setTile); + } else { + setTile.run(); } } }); 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 934060cec7..056e1e075e 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 @@ -8,6 +8,7 @@ import com.fastasyncworldedit.core.FaweCache; import com.fastasyncworldedit.core.math.BitArrayUnstretched; import com.fastasyncworldedit.core.math.IntPair; +import com.fastasyncworldedit.core.util.FoliaUtil; import com.fastasyncworldedit.core.util.MathMan; import com.fastasyncworldedit.core.util.TaskManager; import com.mojang.datafixers.util.Either; @@ -20,6 +21,7 @@ import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockTypesCache; import io.papermc.lib.PaperLib; +import io.papermc.paper.util.MCUtil; import io.papermc.paper.world.ChunkEntitySlices; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; @@ -327,10 +329,22 @@ private static LevelChunk toLevelChunk(Chunk chunk) { } private static void addTicket(ServerLevel serverLevel, int chunkX, int chunkZ) { - // Ensure chunk is definitely loaded before applying a ticket - io.papermc.paper.util.MCUtil.MAIN_EXECUTOR.execute(() -> serverLevel - .getChunkSource() - .addRegionTicket(TicketType.UNLOAD_COOLDOWN, new ChunkPos(chunkX, chunkZ), 0, Unit.INSTANCE)); + ChunkPos pos = new ChunkPos(chunkX, chunkZ); + if (FoliaUtil.isFoliaServer()) { + try { + serverLevel.getChunkSource().addRegionTicket(TicketType.UNLOAD_COOLDOWN, pos, 0, Unit.INSTANCE); + } catch (Exception ignored) { + } + return; + } + try { + MCUtil.MAIN_EXECUTOR.execute(() -> serverLevel.getChunkSource().addRegionTicket(TicketType.UNLOAD_COOLDOWN, pos, 0, Unit.INSTANCE)); + } catch (Exception e) { + try { + serverLevel.getChunkSource().addRegionTicket(TicketType.UNLOAD_COOLDOWN, pos, 0, Unit.INSTANCE); + } catch (Exception ignored) { + } + } } public static ChunkHolder getPlayerChunk(ServerLevel nmsWorld, final int chunkX, final int chunkZ) { @@ -348,19 +362,9 @@ public static void sendChunk(IntPair pair, ServerLevel nmsWorld, int chunkX, int if (chunkHolder == null) { return; } - ChunkPos coordIntPair = new ChunkPos(chunkX, chunkZ); - LevelChunk levelChunk; - if (PaperLib.isPaper()) { - // getChunkAtIfLoadedImmediately is paper only - levelChunk = nmsWorld - .getChunkSource() - .getChunkAtIfLoadedImmediately(chunkX, chunkZ); - } else { - levelChunk = ((Optional) ((Either) chunkHolder - .getTickingChunkFuture() // method is not present with new paper chunk system - .getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK)).left()) - .orElse(null); - } + LevelChunk levelChunk = PaperLib.isPaper() + ? nmsWorld.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ) + : chunkHolder.getTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK).left().orElse(null); if (levelChunk == null) { return; } @@ -369,31 +373,37 @@ public static void sendChunk(IntPair pair, ServerLevel nmsWorld, int chunkX, int if (lockHolder.chunkLock == null) { return; } - MinecraftServer.getServer().execute(() -> { + + ChunkPos pos = levelChunk.getPos(); + ClientboundLevelChunkWithLightPacket packet = PaperLib.isPaper() + ? new ClientboundLevelChunkWithLightPacket(levelChunk, nmsWorld.getLightEngine(), null, null, false) + : new ClientboundLevelChunkWithLightPacket(levelChunk, nmsWorld.getLightEngine(), null, null); + + Runnable sendPacket = () -> nearbyPlayers(nmsWorld, pos).forEach(p -> p.connection.send(packet)); + + if (FoliaUtil.isFoliaServer()) { try { - ClientboundLevelChunkWithLightPacket packet; - if (PaperLib.isPaper()) { - packet = new ClientboundLevelChunkWithLightPacket( - levelChunk, - nmsWorld.getChunkSource().getLightEngine(), - null, - null, - false // last false is to not bother with x-ray - ); - } else { - // deprecated on paper - deprecation suppressed - packet = new ClientboundLevelChunkWithLightPacket( - levelChunk, - nmsWorld.getChunkSource().getLightEngine(), - null, - null - ); - } - nearbyPlayers(nmsWorld, coordIntPair).forEach(p -> p.connection.send(packet)); + sendPacket.run(); } finally { NMSAdapter.endChunkPacketSend(nmsWorld.getWorld().getName(), pair, lockHolder); } - }); + } else { + try { + MinecraftServer.getServer().execute(() -> { + try { + sendPacket.run(); + } finally { + NMSAdapter.endChunkPacketSend(nmsWorld.getWorld().getName(), pair, lockHolder); + } + }); + } catch (Exception e) { + try { + sendPacket.run(); + } finally { + NMSAdapter.endChunkPacketSend(nmsWorld.getWorld().getName(), pair, lockHolder); + } + } + } } private static List nearbyPlayers(ServerLevel serverLevel, ChunkPos coordIntPair) { @@ -655,8 +665,9 @@ public static BiomeType adapt(Holder biome, LevelAccessor levelAccessor) static void removeBeacon(BlockEntity beacon, LevelChunk levelChunk) { try { + Map blockEntities = levelChunk.getBlockEntities(); if (levelChunk.loaded || levelChunk.level.isClientSide()) { - BlockEntity blockEntity = levelChunk.blockEntities.remove(beacon.getBlockPos()); + BlockEntity blockEntity = blockEntities.remove(beacon.getBlockPos()); if (blockEntity != null) { if (!levelChunk.level.isClientSide) { methodRemoveGameEventListener.invoke(levelChunk, beacon, levelChunk.level); diff --git a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/regen/PaperweightRegen.java b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/regen/PaperweightRegen.java index e892c6f7e0..7af0345ebc 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/regen/PaperweightRegen.java +++ b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/regen/PaperweightRegen.java @@ -5,6 +5,7 @@ import com.fastasyncworldedit.core.queue.IChunkCache; import com.fastasyncworldedit.core.queue.IChunkGet; import com.fastasyncworldedit.core.queue.implementation.chunk.ChunkCache; +import com.fastasyncworldedit.core.util.FoliaUtil; import com.google.common.collect.ImmutableList; import com.mojang.serialization.Lifecycle; import com.sk89q.worldedit.bukkit.BukkitAdapter; @@ -43,6 +44,8 @@ import java.nio.file.Path; import java.util.Map; import java.util.OptionalLong; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.function.BooleanSupplier; import java.util.function.Supplier; @@ -208,9 +211,52 @@ public void save( if (paperConfigField != null) { paperConfigField.set(freshWorld, originalServerWorld.paperConfig()); } + + if (FoliaUtil.isFoliaServer()) { + return initWorldForFolia(newWorldData); + } + return true; } + private boolean initWorldForFolia(PrimaryLevelData worldData) throws ExecutionException, InterruptedException { + MinecraftServer console = ((CraftServer) Bukkit.getServer()).getServer(); + + ChunkPos spawnChunk = new ChunkPos( + freshWorld.getChunkSource().randomState().sampler().findSpawnPosition() + ); + + setRandomSpawnSelection(spawnChunk); + + CompletableFuture initFuture = new CompletableFuture<>(); + + Bukkit.getServer().getRegionScheduler().run( + WorldEditPlugin.getInstance(), + freshWorld.getWorld(), + spawnChunk.x, spawnChunk.z, + task -> { + try { + console.initWorld(freshWorld, worldData, worldData, worldData.worldGenOptions()); + initFuture.complete(true); + } catch (Exception e) { + initFuture.completeExceptionally(e); + } + } + ); + + return initFuture.get(); + } + + private void setRandomSpawnSelection(ChunkPos spawnChunk) { + try { + Field randomSpawnField = ServerLevel.class.getDeclaredField("randomSpawnSelection"); + randomSpawnField.setAccessible(true); + randomSpawnField.set(freshWorld, spawnChunk); + } catch (ReflectiveOperationException e) { + throw new RuntimeException("Failed to set randomSpawnSelection for Folia world initialization", e); + } + } + @Override protected void cleanup() { try { diff --git a/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightFaweAdapter.java index d642c06b80..a5a69415a1 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightFaweAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightFaweAdapter.java @@ -10,6 +10,7 @@ import com.fastasyncworldedit.core.queue.IBatchProcessor; import com.fastasyncworldedit.core.queue.IChunkGet; import com.fastasyncworldedit.core.queue.implementation.packet.ChunkPacket; +import com.fastasyncworldedit.core.util.FoliaUtil; import com.fastasyncworldedit.core.util.NbtUtils; import com.fastasyncworldedit.core.util.TaskManager; import com.google.common.base.Preconditions; @@ -21,6 +22,7 @@ import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.bukkit.WorldEditPlugin; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R3.nbt.PaperweightLazyCompoundTag; import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R3.regen.PaperweightRegen; @@ -53,6 +55,7 @@ import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.registry.BlockMaterial; import io.papermc.lib.PaperLib; +import io.papermc.paper.threadedregions.scheduler.ScheduledTask; import net.minecraft.core.BlockPos; import net.minecraft.core.Registry; import net.minecraft.core.SectionPos; @@ -126,6 +129,8 @@ import java.util.Objects; import java.util.OptionalInt; import java.util.Set; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -349,28 +354,45 @@ public Set getSupportedSideEffects() { public BaseEntity getEntity(org.bukkit.entity.Entity entity) { Preconditions.checkNotNull(entity); - CraftEntity craftEntity = ((CraftEntity) entity); - Entity mcEntity = craftEntity.getHandle(); - - String id = getEntityId(mcEntity); - - if (id != null) { - EntityType type = com.sk89q.worldedit.world.entity.EntityTypes.get(id); - Supplier saveTag = () -> { - final net.minecraft.nbt.CompoundTag minecraftTag = new net.minecraft.nbt.CompoundTag(); - if (!readEntityIntoTag(mcEntity, minecraftTag)) { + String id = entity.getType().getKey().toString(); + EntityType type = com.sk89q.worldedit.world.entity.EntityTypes.get(id); + Supplier saveTag = () -> { + net.minecraft.nbt.CompoundTag minecraftTag = new net.minecraft.nbt.CompoundTag(); + Entity mcEntity; + + if (FoliaUtil.isFoliaServer()) { + CompletableFuture handleFuture = new CompletableFuture<>(); + entity.getScheduler().run( + WorldEditPlugin.getInstance(), + (ScheduledTask task) -> { + try { + handleFuture.complete(((CraftEntity) entity).getHandle()); + } catch (Throwable t) { + handleFuture.completeExceptionally(t); + } + }, + () -> handleFuture.completeExceptionally(new CancellationException("Entity scheduler task cancelled")) + ); + try { + mcEntity = handleFuture.join(); + } catch (Throwable t) { + LOGGER.error("Failed to safely get NMS handle for {}", id, t); return null; } - //add Id for AbstractChangeSet to work - final LinCompoundTag tag = (LinCompoundTag) toNativeLin(minecraftTag); - final Map> tags = NbtUtils.getLinCompoundTagValues(tag); - tags.put("Id", LinStringTag.of(id)); - return LinCompoundTag.of(tags); - }; - return new LazyBaseEntity(type, saveTag); - } else { - return null; - } + } else { + mcEntity = ((CraftEntity) entity).getHandle(); + } + + if (mcEntity == null || !readEntityIntoTag(mcEntity, minecraftTag)) { + return null; + } + + LinCompoundTag tag = (LinCompoundTag) toNativeLin(minecraftTag); + Map> tags = NbtUtils.getLinCompoundTagValues(tag); + tags.put("Id", LinStringTag.of(id)); + return LinCompoundTag.of(tags); + }; + return new LazyBaseEntity(type, saveTag); } @Override @@ -548,20 +570,62 @@ public org.bukkit.inventory.ItemStack adapt(BaseItemStack baseItemStack) { @Override protected void preCaptureStates(final ServerLevel serverLevel) { - serverLevel.captureTreeGeneration = true; - serverLevel.captureBlockStates = true; + try { + Field captureTreeField = ServerLevel.class.getDeclaredField("captureTreeGeneration"); + captureTreeField.setAccessible(true); + captureTreeField.setBoolean(serverLevel, true); + } catch (NoSuchFieldException | IllegalAccessException e) { + // Unable to read captureTreeGeneration field + } + try { + Field captureBlockField = ServerLevel.class.getDeclaredField("captureBlockStates"); + captureBlockField.setAccessible(true); + captureBlockField.setBoolean(serverLevel, true); + } catch (NoSuchFieldException | IllegalAccessException e) { + // Unable to read captureTreeGeneration field + } } @Override protected List getCapturedBlockStatesCopy(final ServerLevel serverLevel) { - return new ArrayList<>(serverLevel.capturedBlockStates.values()); + try { + Field capturedStatesField = ServerLevel.class.getDeclaredField("capturedBlockStates"); + capturedStatesField.setAccessible(true); + @SuppressWarnings("unchecked") + Map capturedStates = (Map) capturedStatesField.get(serverLevel); + return capturedStates != null ? new ArrayList<>(capturedStates.values()) : new ArrayList<>(); + } catch (NoSuchFieldException | IllegalAccessException e) { + return new ArrayList<>(); + } } @Override protected void postCaptureBlockStates(final ServerLevel serverLevel) { - serverLevel.captureBlockStates = false; - serverLevel.captureTreeGeneration = false; - serverLevel.capturedBlockStates.clear(); + try { + Field captureBlockField = ServerLevel.class.getDeclaredField("captureBlockStates"); + captureBlockField.setAccessible(true); + captureBlockField.setBoolean(serverLevel, false); + } catch (NoSuchFieldException | IllegalAccessException e) { + // Unable to read captureTreeGeneration field + } + try { + Field captureTreeField = ServerLevel.class.getDeclaredField("captureTreeGeneration"); + captureTreeField.setAccessible(true); + captureTreeField.setBoolean(serverLevel, false); + } catch (NoSuchFieldException | IllegalAccessException e) { + // Unable to read captureTreeGeneration field + } + try { + Field capturedStatesField = ServerLevel.class.getDeclaredField("capturedBlockStates"); + capturedStatesField.setAccessible(true); + @SuppressWarnings("unchecked") + Map capturedStates = (Map) capturedStatesField.get(serverLevel); + if (capturedStates != null) { + capturedStates.clear(); + } + } catch (NoSuchFieldException | IllegalAccessException e) { + // Unable to read captureTreeGeneration field + } } @Override diff --git a/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightFaweWorldNativeAccess.java b/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightFaweWorldNativeAccess.java index 4fb3e04851..26131962e5 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightFaweWorldNativeAccess.java +++ b/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightFaweWorldNativeAccess.java @@ -20,6 +20,7 @@ import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.chunk.LevelChunk; +import org.bukkit.Bukkit; import org.bukkit.craftbukkit.v1_20_R3.CraftWorld; import org.bukkit.craftbukkit.v1_20_R3.block.data.CraftBlockData; import org.bukkit.event.block.BlockPhysicsEvent; @@ -58,7 +59,7 @@ public PaperweightFaweWorldNativeAccess(PaperweightFaweAdapter paperweightFaweAd this.level = level; // Use the actual tick as minecraft-defined so we don't try to force blocks into the world when the server's already lagging. // - With the caveat that we don't want to have too many cached changed (1024) so we'd flush those at 1024 anyway. - this.lastTick = new AtomicInteger(MinecraftServer.currentTick); + this.lastTick = new AtomicInteger(getCurrentTick()); } private Level getLevel() { @@ -94,7 +95,7 @@ public synchronized net.minecraft.world.level.block.state.BlockState setBlockSta LevelChunk levelChunk, BlockPos blockPos, net.minecraft.world.level.block.state.BlockState blockState ) { - int currentTick = MinecraftServer.currentTick; + int currentTick = getCurrentTick(); if (Fawe.isMainThread()) { return levelChunk.setBlockState(blockPos, blockState, this.sideEffectSet != null && this.sideEffectSet.shouldApply(SideEffect.UPDATE) @@ -289,4 +290,17 @@ private record CachedChange( } + + private int getCurrentTick() { + try { + return MinecraftServer.currentTick; + } catch (NoSuchFieldError e) { + try { + return Bukkit.getCurrentTick(); + } catch (Exception ex) { + return 0; + } + } + } + } 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 0b1522d7f7..0df8ba353a 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 @@ -11,6 +11,7 @@ import com.fastasyncworldedit.core.math.IntPair; import com.fastasyncworldedit.core.nbt.FaweCompoundTag; import com.fastasyncworldedit.core.queue.IChunkSet; +import com.fastasyncworldedit.core.util.FoliaUtil; import com.fastasyncworldedit.core.util.MathMan; import com.fastasyncworldedit.core.util.NbtUtils; import com.fastasyncworldedit.core.util.collection.AdaptedMap; @@ -56,6 +57,8 @@ import net.minecraft.world.level.levelgen.Heightmap; import net.minecraft.world.level.lighting.LevelLightEngine; import org.apache.logging.log4j.Logger; +import org.bukkit.Bukkit; +import org.bukkit.Location; import org.bukkit.World; import org.bukkit.craftbukkit.v1_20_R3.CraftWorld; import org.bukkit.craftbukkit.v1_20_R3.block.CraftBlock; @@ -194,23 +197,30 @@ public void removeSectionLighting(int layer, boolean sky) { @Override public FaweCompoundTag tile(final int x, final int y, final int z) { - BlockEntity blockEntity = getChunk().getBlockEntity(new BlockPos((x & 15) + ( - chunkX << 4), y, (z & 15) + ( - chunkZ << 4))); - if (blockEntity == null) { + try { + BlockEntity blockEntity = getChunk().getBlockEntity(new BlockPos((x & 15) + ( + chunkX << 4), y, (z & 15) + ( + chunkZ << 4))); + if (blockEntity == null) { + return null; + } + return NMS_TO_TILE.apply(blockEntity); + } catch (NullPointerException e) { return null; } - return NMS_TO_TILE.apply(blockEntity); - } @Override public Map tiles() { - Map nmsTiles = getChunk().getBlockEntities(); - if (nmsTiles.isEmpty()) { + try { + Map nmsTiles = getChunk().getBlockEntities(); + if (nmsTiles.isEmpty()) { + return Collections.emptyMap(); + } + return AdaptedMap.immutable(nmsTiles, posNms2We, NMS_TO_TILE); + } catch (NullPointerException e) { return Collections.emptyMap(); } - return AdaptedMap.immutable(nmsTiles, posNms2We, NMS_TO_TILE); } @Override @@ -317,7 +327,20 @@ public Set getFullEntities() { } private void removeEntity(Entity entity) { - entity.discard(); + if (FoliaUtil.isFoliaServer()) { + entity.getBukkitEntity().getScheduler().execute( + WorldEditPlugin.getInstance(), + () -> { + if (!entity.isRemoved()) { + entity.discard(); + } + }, + null, + 1L + ); + } else if (!entity.isRemoved()) { + entity.discard(); + } } @Override @@ -345,11 +368,11 @@ protected > T internalCall( 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; + BlockPos pos = entry.getKey(); + int lx = pos.getX() & 15; + int ly = pos.getY(); + int lz = pos.getZ() & 15; + int layer = ly >> 4; if (!set.hasSection(layer)) { continue; } @@ -357,12 +380,26 @@ protected > T internalCall( int ordinal = set.getBlock(lx, ly, lz).getOrdinal(); if (ordinal != BlockTypesCache.ReservedIDs.__RESERVED__) { BlockEntity tile = entry.getValue(); - if (PaperLib.isPaper() && tile instanceof BeaconBlockEntity) { + if (PaperLib.isPaper() && tile instanceof BeaconBlockEntity beacon) { if (beacons == null) { beacons = new ArrayList<>(); } - beacons.add(tile); - PaperweightPlatformAdapter.removeBeacon(tile, nmsChunk); + beacons.add(beacon); + Location location = new Location( + nmsWorld.getWorld(), + beacon.getBlockPos().getX(), + beacon.getBlockPos().getY(), + beacon.getBlockPos().getZ() + ); + if (FoliaUtil.isFoliaServer()) { + Bukkit.getServer().getRegionScheduler().execute( + WorldEditPlugin.getInstance(), + location, + () -> PaperweightPlatformAdapter.removeBeacon(beacon, nmsChunk) + ); + } else { + PaperweightPlatformAdapter.removeBeacon(beacon, nmsChunk); + } continue; } nmsChunk.removeBlockEntity(tile.getBlockPos()); @@ -372,7 +409,7 @@ protected > T internalCall( } } } - final BiomeType[][] biomes = set.getBiomes(); + BiomeType[][] biomes = set.getBiomes(); int bitMask = 0; synchronized (nmsChunk) { @@ -598,7 +635,7 @@ protected > T internalCall( // 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; + List finalBeacons = beacons; syncTasks.add(() -> { for (BlockEntity beacon : finalBeacons) { BeaconBlockEntity.playSound(beacon.getLevel(), beacon.getBlockPos(), SoundEvents.BEACON_DEACTIVATE); @@ -611,7 +648,7 @@ protected > T internalCall( if (entityRemoves != null && !entityRemoves.isEmpty()) { syncTasks.add(() -> { Set entitiesRemoved = new HashSet<>(); - final List entities = PaperweightPlatformAdapter.getEntities(nmsChunk); + List entities = PaperweightPlatformAdapter.getEntities(nmsChunk); for (Entity entity : entities) { UUID uuid = entity.getUUID(); @@ -643,45 +680,60 @@ protected > T internalCall( syncTasks.add(() -> { 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()); + FaweCompoundTag nativeTag = iterator.next(); + LinCompoundTag linTag = nativeTag.linTag(); + LinStringTag idTag = linTag.findTag("Id", LinTagType.stringTag()); + LinListTag posTag = linTag.findListTag("Pos", LinTagType.doubleTag()); + 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(); + double x = posTag.get(0).valueAsDouble(); + double y = posTag.get(1).valueAsDouble(); + double z = posTag.get(2).valueAsDouble(); + float yaw = rotTag.get(0).valueAsFloat(); + float pitch = rotTag.get(1).valueAsFloat(); + 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) { + CompoundTag tag = (CompoundTag) adapter.fromNativeLin(linTag); + for (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 (!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(); + Location location = new Location(nmsWorld.getWorld(), x, y, z); + if (FoliaUtil.isFoliaServer()) { + Bukkit.getServer().getRegionScheduler().execute(WorldEditPlugin.getInstance(), location, () -> { + 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 + ); + iterator.remove(); + } + }); + } else { + 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 + ); + iterator.remove(); + } } } } @@ -693,27 +745,37 @@ protected > T internalCall( Map tiles = set.tiles(); if (tiles != null && !tiles.isEmpty()) { syncTasks.add(() -> { - 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 (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); + World world = nmsWorld.getWorld(); + for (Map.Entry entry : tiles.entrySet()) { + FaweCompoundTag nativeTag = entry.getValue(); + BlockVector3 blockHash = entry.getKey(); + int x = blockHash.x() + bx; + int y = blockHash.y(); + int z = blockHash.z() + bz; + BlockPos pos = new BlockPos(x, y, z); + Location location = new Location(world, x, y, z); + + Runnable setTile = () -> { + synchronized (nmsChunk) { + BlockEntity tileEntity = nmsChunk.getBlockEntity(pos); + if (tileEntity == null || tileEntity.isRemoved()) { + nmsChunk.removeBlockEntity(pos); + tileEntity = nmsChunk.getBlockEntity(pos); + } + if (tileEntity != null) { + 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); + } } + }; + + if (FoliaUtil.isFoliaServer()) { + Bukkit.getServer().getRegionScheduler().execute(WorldEditPlugin.getInstance(), location, setTile); + } else { + setTile.run(); } } }); 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 9367612169..ff4dca7b04 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 @@ -8,6 +8,7 @@ import com.fastasyncworldedit.core.FaweCache; import com.fastasyncworldedit.core.math.BitArrayUnstretched; import com.fastasyncworldedit.core.math.IntPair; +import com.fastasyncworldedit.core.util.FoliaUtil; import com.fastasyncworldedit.core.util.MathMan; import com.fastasyncworldedit.core.util.TaskManager; import com.mojang.datafixers.util.Either; @@ -20,6 +21,7 @@ import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockTypesCache; import io.papermc.lib.PaperLib; +import io.papermc.paper.util.MCUtil; import io.papermc.paper.world.ChunkEntitySlices; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; @@ -327,10 +329,22 @@ private static LevelChunk toLevelChunk(Chunk chunk) { } private static void addTicket(ServerLevel serverLevel, int chunkX, int chunkZ) { - // Ensure chunk is definitely loaded before applying a ticket - io.papermc.paper.util.MCUtil.MAIN_EXECUTOR.execute(() -> serverLevel - .getChunkSource() - .addRegionTicket(TicketType.UNLOAD_COOLDOWN, new ChunkPos(chunkX, chunkZ), 0, Unit.INSTANCE)); + ChunkPos pos = new ChunkPos(chunkX, chunkZ); + if (FoliaUtil.isFoliaServer()) { + try { + serverLevel.getChunkSource().addRegionTicket(TicketType.UNLOAD_COOLDOWN, pos, 0, Unit.INSTANCE); + } catch (Exception ignored) { + } + return; + } + try { + MCUtil.MAIN_EXECUTOR.execute(() -> serverLevel.getChunkSource().addRegionTicket(TicketType.UNLOAD_COOLDOWN, pos, 0, Unit.INSTANCE)); + } catch (Exception e) { + try { + serverLevel.getChunkSource().addRegionTicket(TicketType.UNLOAD_COOLDOWN, pos, 0, Unit.INSTANCE); + } catch (Exception ignored) { + } + } } public static ChunkHolder getPlayerChunk(ServerLevel nmsWorld, final int chunkX, final int chunkZ) { @@ -348,19 +362,9 @@ public static void sendChunk(IntPair pair, ServerLevel nmsWorld, int chunkX, int if (chunkHolder == null) { return; } - ChunkPos coordIntPair = new ChunkPos(chunkX, chunkZ); - LevelChunk levelChunk; - if (PaperLib.isPaper()) { - // getChunkAtIfLoadedImmediately is paper only - levelChunk = nmsWorld - .getChunkSource() - .getChunkAtIfLoadedImmediately(chunkX, chunkZ); - } else { - levelChunk = ((Optional) ((Either) chunkHolder - .getTickingChunkFuture() // method is not present with new paper chunk system - .getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK)).left()) - .orElse(null); - } + LevelChunk levelChunk = PaperLib.isPaper() + ? nmsWorld.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ) + : chunkHolder.getTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK).left().orElse(null); if (levelChunk == null) { return; } @@ -369,31 +373,37 @@ public static void sendChunk(IntPair pair, ServerLevel nmsWorld, int chunkX, int if (lockHolder.chunkLock == null) { return; } - MinecraftServer.getServer().execute(() -> { + + ChunkPos pos = levelChunk.getPos(); + ClientboundLevelChunkWithLightPacket packet = PaperLib.isPaper() + ? new ClientboundLevelChunkWithLightPacket(levelChunk, nmsWorld.getLightEngine(), null, null, false) + : new ClientboundLevelChunkWithLightPacket(levelChunk, nmsWorld.getLightEngine(), null, null); + + Runnable sendPacket = () -> nearbyPlayers(nmsWorld, pos).forEach(p -> p.connection.send(packet)); + + if (FoliaUtil.isFoliaServer()) { try { - ClientboundLevelChunkWithLightPacket packet; - if (PaperLib.isPaper()) { - packet = new ClientboundLevelChunkWithLightPacket( - levelChunk, - nmsWorld.getChunkSource().getLightEngine(), - null, - null, - false // last false is to not bother with x-ray - ); - } else { - // deprecated on paper - deprecation suppressed - packet = new ClientboundLevelChunkWithLightPacket( - levelChunk, - nmsWorld.getChunkSource().getLightEngine(), - null, - null - ); - } - nearbyPlayers(nmsWorld, coordIntPair).forEach(p -> p.connection.send(packet)); + sendPacket.run(); } finally { NMSAdapter.endChunkPacketSend(nmsWorld.getWorld().getName(), pair, lockHolder); } - }); + } else { + try { + MinecraftServer.getServer().execute(() -> { + try { + sendPacket.run(); + } finally { + NMSAdapter.endChunkPacketSend(nmsWorld.getWorld().getName(), pair, lockHolder); + } + }); + } catch (Exception e) { + try { + sendPacket.run(); + } finally { + NMSAdapter.endChunkPacketSend(nmsWorld.getWorld().getName(), pair, lockHolder); + } + } + } } private static List nearbyPlayers(ServerLevel serverLevel, ChunkPos coordIntPair) { @@ -655,8 +665,9 @@ public static BiomeType adapt(Holder biome, LevelAccessor levelAccessor) static void removeBeacon(BlockEntity beacon, LevelChunk levelChunk) { try { + Map blockEntities = levelChunk.getBlockEntities(); if (levelChunk.loaded || levelChunk.level.isClientSide()) { - BlockEntity blockEntity = levelChunk.blockEntities.remove(beacon.getBlockPos()); + BlockEntity blockEntity = blockEntities.remove(beacon.getBlockPos()); if (blockEntity != null) { if (!levelChunk.level.isClientSide) { methodRemoveGameEventListener.invoke(levelChunk, beacon, levelChunk.level); diff --git a/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/regen/PaperweightRegen.java b/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/regen/PaperweightRegen.java index cce14ee1ca..2014df2011 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/regen/PaperweightRegen.java +++ b/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/regen/PaperweightRegen.java @@ -5,6 +5,7 @@ import com.fastasyncworldedit.core.queue.IChunkCache; import com.fastasyncworldedit.core.queue.IChunkGet; import com.fastasyncworldedit.core.queue.implementation.chunk.ChunkCache; +import com.fastasyncworldedit.core.util.FoliaUtil; import com.google.common.collect.ImmutableList; import com.mojang.serialization.Lifecycle; import com.sk89q.worldedit.bukkit.BukkitAdapter; @@ -43,6 +44,8 @@ import java.nio.file.Path; import java.util.Map; import java.util.OptionalLong; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.function.BooleanSupplier; import java.util.function.Supplier; @@ -208,9 +211,52 @@ public void save( if (paperConfigField != null) { paperConfigField.set(freshWorld, originalServerWorld.paperConfig()); } + + if (FoliaUtil.isFoliaServer()) { + return initWorldForFolia(newWorldData); + } + return true; } + private boolean initWorldForFolia(PrimaryLevelData worldData) throws ExecutionException, InterruptedException { + MinecraftServer console = ((CraftServer) Bukkit.getServer()).getServer(); + + ChunkPos spawnChunk = new ChunkPos( + freshWorld.getChunkSource().randomState().sampler().findSpawnPosition() + ); + + setRandomSpawnSelection(spawnChunk); + + CompletableFuture initFuture = new CompletableFuture<>(); + + Bukkit.getServer().getRegionScheduler().run( + WorldEditPlugin.getInstance(), + freshWorld.getWorld(), + spawnChunk.x, spawnChunk.z, + task -> { + try { + console.initWorld(freshWorld, worldData, worldData, worldData.worldGenOptions()); + initFuture.complete(true); + } catch (Exception e) { + initFuture.completeExceptionally(e); + } + } + ); + + return initFuture.get(); + } + + private void setRandomSpawnSelection(ChunkPos spawnChunk) { + try { + Field randomSpawnField = ServerLevel.class.getDeclaredField("randomSpawnSelection"); + randomSpawnField.setAccessible(true); + randomSpawnField.set(freshWorld, spawnChunk); + } catch (ReflectiveOperationException e) { + throw new RuntimeException("Failed to set randomSpawnSelection for Folia world initialization", e); + } + } + @Override protected void cleanup() { try { diff --git a/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightFaweAdapter.java index 4e778b2895..67c331ff41 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightFaweAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightFaweAdapter.java @@ -10,6 +10,7 @@ import com.fastasyncworldedit.core.queue.IBatchProcessor; import com.fastasyncworldedit.core.queue.IChunkGet; import com.fastasyncworldedit.core.queue.implementation.packet.ChunkPacket; +import com.fastasyncworldedit.core.util.FoliaUtil; import com.fastasyncworldedit.core.util.NbtUtils; import com.fastasyncworldedit.core.util.TaskManager; import com.google.common.base.Preconditions; @@ -22,6 +23,7 @@ import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.bukkit.WorldEditPlugin; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R4.nbt.PaperweightLazyCompoundTag; import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_20_R4.regen.PaperweightRegen; @@ -55,6 +57,7 @@ import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.registry.BlockMaterial; import io.papermc.lib.PaperLib; +import io.papermc.paper.threadedregions.scheduler.ScheduledTask; import net.minecraft.core.BlockPos; import net.minecraft.core.Registry; import net.minecraft.core.RegistryAccess; @@ -131,6 +134,8 @@ import java.util.Objects; import java.util.OptionalInt; import java.util.Set; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -357,28 +362,45 @@ public Set getSupportedSideEffects() { public BaseEntity getEntity(org.bukkit.entity.Entity entity) { Preconditions.checkNotNull(entity); - CraftEntity craftEntity = ((CraftEntity) entity); - Entity mcEntity = craftEntity.getHandle(); - - String id = getEntityId(mcEntity); - - if (id != null) { - EntityType type = com.sk89q.worldedit.world.entity.EntityTypes.get(id); - Supplier saveTag = () -> { - final net.minecraft.nbt.CompoundTag minecraftTag = new net.minecraft.nbt.CompoundTag(); - if (!readEntityIntoTag(mcEntity, minecraftTag)) { + String id = entity.getType().getKey().toString(); + EntityType type = com.sk89q.worldedit.world.entity.EntityTypes.get(id); + Supplier saveTag = () -> { + net.minecraft.nbt.CompoundTag minecraftTag = new net.minecraft.nbt.CompoundTag(); + Entity mcEntity; + + if (FoliaUtil.isFoliaServer()) { + CompletableFuture handleFuture = new CompletableFuture<>(); + entity.getScheduler().run( + WorldEditPlugin.getInstance(), + (ScheduledTask task) -> { + try { + handleFuture.complete(((CraftEntity) entity).getHandle()); + } catch (Throwable t) { + handleFuture.completeExceptionally(t); + } + }, + () -> handleFuture.completeExceptionally(new CancellationException("Entity scheduler task cancelled")) + ); + try { + mcEntity = handleFuture.join(); + } catch (Throwable t) { + LOGGER.error("Failed to safely get NMS handle for {}", id, t); return null; } - //add Id for AbstractChangeSet to work - final LinCompoundTag tag = (LinCompoundTag) toNativeLin(minecraftTag); - final Map> tags = NbtUtils.getLinCompoundTagValues(tag); - tags.put("Id", LinStringTag.of(id)); - return LinCompoundTag.of(tags); - }; - return new LazyBaseEntity(type, saveTag); - } else { - return null; - } + } else { + mcEntity = ((CraftEntity) entity).getHandle(); + } + + if (mcEntity == null || !readEntityIntoTag(mcEntity, minecraftTag)) { + return null; + } + + LinCompoundTag tag = (LinCompoundTag) toNativeLin(minecraftTag); + Map> tags = NbtUtils.getLinCompoundTagValues(tag); + tags.put("Id", LinStringTag.of(id)); + return LinCompoundTag.of(tags); + }; + return new LazyBaseEntity(type, saveTag); } @Override @@ -563,20 +585,62 @@ public org.bukkit.inventory.ItemStack adapt(BaseItemStack baseItemStack) { @Override protected void preCaptureStates(final ServerLevel serverLevel) { - serverLevel.captureTreeGeneration = true; - serverLevel.captureBlockStates = true; + try { + Field captureTreeField = ServerLevel.class.getDeclaredField("captureTreeGeneration"); + captureTreeField.setAccessible(true); + captureTreeField.setBoolean(serverLevel, true); + } catch (NoSuchFieldException | IllegalAccessException e) { + // Unable to read captureTreeGeneration field + } + try { + Field captureBlockField = ServerLevel.class.getDeclaredField("captureBlockStates"); + captureBlockField.setAccessible(true); + captureBlockField.setBoolean(serverLevel, true); + } catch (NoSuchFieldException | IllegalAccessException e) { + // Unable to read captureTreeGeneration field + } } @Override protected List getCapturedBlockStatesCopy(final ServerLevel serverLevel) { - return new ArrayList<>(serverLevel.capturedBlockStates.values()); + try { + Field capturedStatesField = ServerLevel.class.getDeclaredField("capturedBlockStates"); + capturedStatesField.setAccessible(true); + @SuppressWarnings("unchecked") + Map capturedStates = (Map) capturedStatesField.get(serverLevel); + return capturedStates != null ? new ArrayList<>(capturedStates.values()) : new ArrayList<>(); + } catch (NoSuchFieldException | IllegalAccessException e) { + return new ArrayList<>(); + } } @Override protected void postCaptureBlockStates(final ServerLevel serverLevel) { - serverLevel.captureBlockStates = false; - serverLevel.captureTreeGeneration = false; - serverLevel.capturedBlockStates.clear(); + try { + Field captureBlockField = ServerLevel.class.getDeclaredField("captureBlockStates"); + captureBlockField.setAccessible(true); + captureBlockField.setBoolean(serverLevel, false); + } catch (NoSuchFieldException | IllegalAccessException e) { + // Unable to read captureTreeGeneration field + } + try { + Field captureTreeField = ServerLevel.class.getDeclaredField("captureTreeGeneration"); + captureTreeField.setAccessible(true); + captureTreeField.setBoolean(serverLevel, false); + } catch (NoSuchFieldException | IllegalAccessException e) { + // Unable to read captureTreeGeneration field + } + try { + Field capturedStatesField = ServerLevel.class.getDeclaredField("capturedBlockStates"); + capturedStatesField.setAccessible(true); + @SuppressWarnings("unchecked") + Map capturedStates = (Map) capturedStatesField.get(serverLevel); + if (capturedStates != null) { + capturedStates.clear(); + } + } catch (NoSuchFieldException | IllegalAccessException e) { + // Unable to read captureTreeGeneration field + } } @Override diff --git a/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightFaweWorldNativeAccess.java b/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightFaweWorldNativeAccess.java index 4fa9988b81..68d18726f6 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightFaweWorldNativeAccess.java +++ b/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightFaweWorldNativeAccess.java @@ -21,6 +21,7 @@ import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.chunk.LevelChunk; +import org.bukkit.Bukkit; import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.craftbukkit.block.data.CraftBlockData; import org.bukkit.event.block.BlockPhysicsEvent; @@ -59,7 +60,7 @@ public PaperweightFaweWorldNativeAccess(PaperweightFaweAdapter paperweightFaweAd this.level = level; // Use the actual tick as minecraft-defined so we don't try to force blocks into the world when the server's already lagging. // - With the caveat that we don't want to have too many cached changed (1024) so we'd flush those at 1024 anyway. - this.lastTick = new AtomicInteger(MinecraftServer.currentTick); + this.lastTick = new AtomicInteger(getCurrentTick()); } private Level getLevel() { @@ -95,7 +96,7 @@ public synchronized net.minecraft.world.level.block.state.BlockState setBlockSta LevelChunk levelChunk, BlockPos blockPos, net.minecraft.world.level.block.state.BlockState blockState ) { - int currentTick = MinecraftServer.currentTick; + int currentTick = getCurrentTick(); if (Fawe.isMainThread()) { return levelChunk.setBlockState(blockPos, blockState, this.sideEffectSet != null && this.sideEffectSet.shouldApply(SideEffect.UPDATE) @@ -290,4 +291,16 @@ private record CachedChange( } + private int getCurrentTick() { + try { + return MinecraftServer.currentTick; + } catch (NoSuchFieldError e) { + try { + return Bukkit.getCurrentTick(); + } catch (Exception ex) { + return 0; + } + } + } + } 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 6195054ffe..69bdbe86b5 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 @@ -11,6 +11,7 @@ import com.fastasyncworldedit.core.math.IntPair; import com.fastasyncworldedit.core.nbt.FaweCompoundTag; import com.fastasyncworldedit.core.queue.IChunkSet; +import com.fastasyncworldedit.core.util.FoliaUtil; import com.fastasyncworldedit.core.util.MathMan; import com.fastasyncworldedit.core.util.NbtUtils; import com.fastasyncworldedit.core.util.collection.AdaptedMap; @@ -57,6 +58,8 @@ import net.minecraft.world.level.levelgen.Heightmap; import net.minecraft.world.level.lighting.LevelLightEngine; import org.apache.logging.log4j.Logger; +import org.bukkit.Bukkit; +import org.bukkit.Location; import org.bukkit.World; import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.craftbukkit.block.CraftBlock; @@ -195,23 +198,30 @@ public void removeSectionLighting(int layer, boolean sky) { @Override public FaweCompoundTag tile(final int x, final int y, final int z) { - BlockEntity blockEntity = getChunk().getBlockEntity(new BlockPos((x & 15) + ( - chunkX << 4), y, (z & 15) + ( - chunkZ << 4))); - if (blockEntity == null) { + try { + BlockEntity blockEntity = getChunk().getBlockEntity(new BlockPos((x & 15) + ( + chunkX << 4), y, (z & 15) + ( + chunkZ << 4))); + if (blockEntity == null) { + return null; + } + return NMS_TO_TILE.apply(blockEntity); + } catch (NullPointerException e) { return null; } - return NMS_TO_TILE.apply(blockEntity); - } @Override public Map tiles() { - Map nmsTiles = getChunk().getBlockEntities(); - if (nmsTiles.isEmpty()) { + try { + Map nmsTiles = getChunk().getBlockEntities(); + if (nmsTiles.isEmpty()) { + return Collections.emptyMap(); + } + return AdaptedMap.immutable(nmsTiles, posNms2We, NMS_TO_TILE); + } catch (NullPointerException e) { return Collections.emptyMap(); } - return AdaptedMap.immutable(nmsTiles, posNms2We, NMS_TO_TILE); } @Override @@ -318,7 +328,20 @@ public Set getFullEntities() { } private void removeEntity(Entity entity) { - entity.discard(); + if (FoliaUtil.isFoliaServer()) { + entity.getBukkitEntity().getScheduler().execute( + WorldEditPlugin.getInstance(), + () -> { + if (!entity.isRemoved()) { + entity.discard(); + } + }, + null, + 1L + ); + } else if (!entity.isRemoved()) { + entity.discard(); + } } @Override @@ -346,11 +369,11 @@ protected > T internalCall( 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; + BlockPos pos = entry.getKey(); + int lx = pos.getX() & 15; + int ly = pos.getY(); + int lz = pos.getZ() & 15; + int layer = ly >> 4; if (!set.hasSection(layer)) { continue; } @@ -358,12 +381,26 @@ protected > T internalCall( int ordinal = set.getBlock(lx, ly, lz).getOrdinal(); if (ordinal != BlockTypesCache.ReservedIDs.__RESERVED__) { BlockEntity tile = entry.getValue(); - if (PaperLib.isPaper() && tile instanceof BeaconBlockEntity) { + if (PaperLib.isPaper() && tile instanceof BeaconBlockEntity beacon) { if (beacons == null) { beacons = new ArrayList<>(); } - beacons.add(tile); - PaperweightPlatformAdapter.removeBeacon(tile, nmsChunk); + beacons.add(beacon); + Location location = new Location( + nmsWorld.getWorld(), + beacon.getBlockPos().getX(), + beacon.getBlockPos().getY(), + beacon.getBlockPos().getZ() + ); + if (FoliaUtil.isFoliaServer()) { + Bukkit.getServer().getRegionScheduler().execute( + WorldEditPlugin.getInstance(), + location, + () -> PaperweightPlatformAdapter.removeBeacon(beacon, nmsChunk) + ); + } else { + PaperweightPlatformAdapter.removeBeacon(beacon, nmsChunk); + } continue; } nmsChunk.removeBlockEntity(tile.getBlockPos()); @@ -373,7 +410,7 @@ protected > T internalCall( } } } - final BiomeType[][] biomes = set.getBiomes(); + BiomeType[][] biomes = set.getBiomes(); int bitMask = 0; synchronized (nmsChunk) { @@ -599,7 +636,7 @@ protected > T internalCall( // 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; + List finalBeacons = beacons; syncTasks.add(() -> { for (BlockEntity beacon : finalBeacons) { BeaconBlockEntity.playSound(beacon.getLevel(), beacon.getBlockPos(), SoundEvents.BEACON_DEACTIVATE); @@ -612,7 +649,7 @@ protected > T internalCall( if (entityRemoves != null && !entityRemoves.isEmpty()) { syncTasks.add(() -> { Set entitiesRemoved = new HashSet<>(); - final List entities = PaperweightPlatformAdapter.getEntities(nmsChunk); + List entities = PaperweightPlatformAdapter.getEntities(nmsChunk); for (Entity entity : entities) { UUID uuid = entity.getUUID(); @@ -644,45 +681,60 @@ protected > T internalCall( syncTasks.add(() -> { 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()); + FaweCompoundTag nativeTag = iterator.next(); + LinCompoundTag linTag = nativeTag.linTag(); + LinStringTag idTag = linTag.findTag("Id", LinTagType.stringTag()); + LinListTag posTag = linTag.findListTag("Pos", LinTagType.doubleTag()); + 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(); + double x = posTag.get(0).valueAsDouble(); + double y = posTag.get(1).valueAsDouble(); + double z = posTag.get(2).valueAsDouble(); + float yaw = rotTag.get(0).valueAsFloat(); + float pitch = rotTag.get(1).valueAsFloat(); + 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) { + CompoundTag tag = (CompoundTag) adapter.fromNativeLin(linTag); + for (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 (!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(); + Location location = new Location(nmsWorld.getWorld(), x, y, z); + if (FoliaUtil.isFoliaServer()) { + Bukkit.getServer().getRegionScheduler().execute(WorldEditPlugin.getInstance(), location, () -> { + 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 + ); + iterator.remove(); + } + }); + } else { + 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 + ); + iterator.remove(); + } } } } @@ -694,27 +746,37 @@ protected > T internalCall( Map tiles = set.tiles(); if (tiles != null && !tiles.isEmpty()) { syncTasks.add(() -> { - 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 (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()); + World world = nmsWorld.getWorld(); + for (Map.Entry entry : tiles.entrySet()) { + FaweCompoundTag nativeTag = entry.getValue(); + BlockVector3 blockHash = entry.getKey(); + int x = blockHash.x() + bx; + int y = blockHash.y(); + int z = blockHash.z() + bz; + BlockPos pos = new BlockPos(x, y, z); + Location location = new Location(world, x, y, z); + + Runnable setTile = () -> { + synchronized (nmsChunk) { + BlockEntity tileEntity = nmsChunk.getBlockEntity(pos); + if (tileEntity == null || tileEntity.isRemoved()) { + nmsChunk.removeBlockEntity(pos); + tileEntity = nmsChunk.getBlockEntity(pos); + } + if (tileEntity != null) { + 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()); + } } + }; + + if (FoliaUtil.isFoliaServer()) { + Bukkit.getServer().getRegionScheduler().execute(WorldEditPlugin.getInstance(), location, setTile); + } else { + setTile.run(); } } }); 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 b6da05cc6e..fe247eac24 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 @@ -8,6 +8,7 @@ import com.fastasyncworldedit.core.FaweCache; import com.fastasyncworldedit.core.math.BitArrayUnstretched; import com.fastasyncworldedit.core.math.IntPair; +import com.fastasyncworldedit.core.util.FoliaUtil; import com.fastasyncworldedit.core.util.MathMan; import com.fastasyncworldedit.core.util.TaskManager; import com.sk89q.worldedit.bukkit.WorldEditPlugin; @@ -19,6 +20,7 @@ import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockTypesCache; import io.papermc.lib.PaperLib; +import io.papermc.paper.util.MCUtil; import io.papermc.paper.world.ChunkEntitySlices; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; @@ -326,10 +328,22 @@ private static LevelChunk toLevelChunk(Chunk chunk) { } private static void addTicket(ServerLevel serverLevel, int chunkX, int chunkZ) { - // Ensure chunk is definitely loaded before applying a ticket - io.papermc.paper.util.MCUtil.MAIN_EXECUTOR.execute(() -> serverLevel - .getChunkSource() - .addRegionTicket(TicketType.UNLOAD_COOLDOWN, new ChunkPos(chunkX, chunkZ), 0, Unit.INSTANCE)); + ChunkPos pos = new ChunkPos(chunkX, chunkZ); + if (FoliaUtil.isFoliaServer()) { + try { + serverLevel.getChunkSource().addRegionTicket(TicketType.UNLOAD_COOLDOWN, pos, 0, Unit.INSTANCE); + } catch (Exception ignored) { + } + return; + } + try { + MCUtil.MAIN_EXECUTOR.execute(() -> serverLevel.getChunkSource().addRegionTicket(TicketType.UNLOAD_COOLDOWN, pos, 0, Unit.INSTANCE)); + } catch (Exception e) { + try { + serverLevel.getChunkSource().addRegionTicket(TicketType.UNLOAD_COOLDOWN, pos, 0, Unit.INSTANCE); + } catch (Exception ignored) { + } + } } public static ChunkHolder getPlayerChunk(ServerLevel nmsWorld, final int chunkX, final int chunkZ) { @@ -347,14 +361,9 @@ public static void sendChunk(IntPair pair, ServerLevel nmsWorld, int chunkX, int if (chunkHolder == null) { return; } - ChunkPos coordIntPair = new ChunkPos(chunkX, chunkZ); - LevelChunk levelChunk; - if (PaperLib.isPaper()) { - // getChunkAtIfLoadedImmediately is paper only - levelChunk = nmsWorld.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ); - } else { - levelChunk = chunkHolder.getTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK).orElse(null); - } + LevelChunk levelChunk = PaperLib.isPaper() + ? nmsWorld.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ) + : chunkHolder.getTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK).orElse(null); if (levelChunk == null) { return; } @@ -363,31 +372,37 @@ public static void sendChunk(IntPair pair, ServerLevel nmsWorld, int chunkX, int if (lockHolder.chunkLock == null) { return; } - MinecraftServer.getServer().execute(() -> { + + ChunkPos pos = levelChunk.getPos(); + ClientboundLevelChunkWithLightPacket packet = PaperLib.isPaper() + ? new ClientboundLevelChunkWithLightPacket(levelChunk, nmsWorld.getLightEngine(), null, null, false) + : new ClientboundLevelChunkWithLightPacket(levelChunk, nmsWorld.getLightEngine(), null, null); + + Runnable sendPacket = () -> nearbyPlayers(nmsWorld, pos).forEach(p -> p.connection.send(packet)); + + if (FoliaUtil.isFoliaServer()) { try { - ClientboundLevelChunkWithLightPacket packet; - if (PaperLib.isPaper()) { - packet = new ClientboundLevelChunkWithLightPacket( - levelChunk, - nmsWorld.getChunkSource().getLightEngine(), - null, - null, - false // last false is to not bother with x-ray - ); - } else { - // deprecated on paper - deprecation suppressed - packet = new ClientboundLevelChunkWithLightPacket( - levelChunk, - nmsWorld.getChunkSource().getLightEngine(), - null, - null - ); - } - nearbyPlayers(nmsWorld, coordIntPair).forEach(p -> p.connection.send(packet)); + sendPacket.run(); } finally { NMSAdapter.endChunkPacketSend(nmsWorld.getWorld().getName(), pair, lockHolder); } - }); + } else { + try { + MinecraftServer.getServer().execute(() -> { + try { + sendPacket.run(); + } finally { + NMSAdapter.endChunkPacketSend(nmsWorld.getWorld().getName(), pair, lockHolder); + } + }); + } catch (Exception e) { + try { + sendPacket.run(); + } finally { + NMSAdapter.endChunkPacketSend(nmsWorld.getWorld().getName(), pair, lockHolder); + } + } + } } private static List nearbyPlayers(ServerLevel serverLevel, ChunkPos coordIntPair) { @@ -649,8 +664,9 @@ public static BiomeType adapt(Holder biome, LevelAccessor levelAccessor) static void removeBeacon(BlockEntity beacon, LevelChunk levelChunk) { try { + Map blockEntities = levelChunk.getBlockEntities(); if (levelChunk.loaded || levelChunk.level.isClientSide()) { - BlockEntity blockEntity = levelChunk.blockEntities.remove(beacon.getBlockPos()); + BlockEntity blockEntity = blockEntities.remove(beacon.getBlockPos()); if (blockEntity != null) { if (!levelChunk.level.isClientSide) { methodRemoveGameEventListener.invoke(levelChunk, beacon, levelChunk.level); diff --git a/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/regen/PaperweightRegen.java b/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/regen/PaperweightRegen.java index d80d098ffd..5cd5073c95 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/regen/PaperweightRegen.java +++ b/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/regen/PaperweightRegen.java @@ -5,6 +5,7 @@ import com.fastasyncworldedit.core.queue.IChunkCache; import com.fastasyncworldedit.core.queue.IChunkGet; import com.fastasyncworldedit.core.queue.implementation.chunk.ChunkCache; +import com.fastasyncworldedit.core.util.FoliaUtil; import com.google.common.collect.ImmutableList; import com.mojang.serialization.Lifecycle; import com.sk89q.worldedit.bukkit.BukkitAdapter; @@ -42,6 +43,8 @@ import java.nio.file.Path; import java.util.Map; import java.util.OptionalLong; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.function.BooleanSupplier; import java.util.function.Supplier; @@ -207,9 +210,52 @@ public void save( if (paperConfigField != null) { paperConfigField.set(freshWorld, originalServerWorld.paperConfig()); } + + if (FoliaUtil.isFoliaServer()) { + return initWorldForFolia(newWorldData); + } + return true; } + private boolean initWorldForFolia(PrimaryLevelData worldData) throws ExecutionException, InterruptedException { + MinecraftServer console = ((CraftServer) Bukkit.getServer()).getServer(); + + ChunkPos spawnChunk = new ChunkPos( + freshWorld.getChunkSource().randomState().sampler().findSpawnPosition() + ); + + setRandomSpawnSelection(spawnChunk); + + CompletableFuture initFuture = new CompletableFuture<>(); + + Bukkit.getServer().getRegionScheduler().run( + WorldEditPlugin.getInstance(), + freshWorld.getWorld(), + spawnChunk.x, spawnChunk.z, + task -> { + try { + console.initWorld(freshWorld, worldData, worldData, worldData.worldGenOptions()); + initFuture.complete(true); + } catch (Exception e) { + initFuture.completeExceptionally(e); + } + } + ); + + return initFuture.get(); + } + + private void setRandomSpawnSelection(ChunkPos spawnChunk) { + try { + Field randomSpawnField = ServerLevel.class.getDeclaredField("randomSpawnSelection"); + randomSpawnField.setAccessible(true); + randomSpawnField.set(freshWorld, spawnChunk); + } catch (ReflectiveOperationException e) { + throw new RuntimeException("Failed to set randomSpawnSelection for Folia world initialization", e); + } + } + @Override protected void cleanup() { try { diff --git a/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightFaweAdapter.java index 7be8d693be..508cb06a0c 100644 --- a/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightFaweAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightFaweAdapter.java @@ -10,6 +10,7 @@ import com.fastasyncworldedit.core.queue.IBatchProcessor; import com.fastasyncworldedit.core.queue.IChunkGet; import com.fastasyncworldedit.core.queue.implementation.packet.ChunkPacket; +import com.fastasyncworldedit.core.util.FoliaUtil; import com.fastasyncworldedit.core.util.NbtUtils; import com.fastasyncworldedit.core.util.TaskManager; import com.google.common.base.Preconditions; @@ -22,6 +23,7 @@ import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.bukkit.WorldEditPlugin; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_21_R1.nbt.PaperweightLazyCompoundTag; import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_21_R1.regen.PaperweightRegen; @@ -55,6 +57,7 @@ import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.registry.BlockMaterial; import io.papermc.lib.PaperLib; +import io.papermc.paper.threadedregions.scheduler.ScheduledTask; import net.minecraft.core.BlockPos; import net.minecraft.core.Registry; import net.minecraft.core.RegistryAccess; @@ -131,6 +134,8 @@ import java.util.Objects; import java.util.OptionalInt; import java.util.Set; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -357,28 +362,45 @@ public Set getSupportedSideEffects() { public BaseEntity getEntity(org.bukkit.entity.Entity entity) { Preconditions.checkNotNull(entity); - CraftEntity craftEntity = ((CraftEntity) entity); - Entity mcEntity = craftEntity.getHandle(); - - String id = getEntityId(mcEntity); - - if (id != null) { - EntityType type = com.sk89q.worldedit.world.entity.EntityTypes.get(id); - Supplier saveTag = () -> { - final net.minecraft.nbt.CompoundTag minecraftTag = new net.minecraft.nbt.CompoundTag(); - if (!readEntityIntoTag(mcEntity, minecraftTag)) { + String id = entity.getType().getKey().toString(); + EntityType type = com.sk89q.worldedit.world.entity.EntityTypes.get(id); + Supplier saveTag = () -> { + net.minecraft.nbt.CompoundTag minecraftTag = new net.minecraft.nbt.CompoundTag(); + Entity mcEntity; + + if (FoliaUtil.isFoliaServer()) { + CompletableFuture handleFuture = new CompletableFuture<>(); + entity.getScheduler().run( + WorldEditPlugin.getInstance(), + (ScheduledTask task) -> { + try { + handleFuture.complete(((CraftEntity) entity).getHandle()); + } catch (Throwable t) { + handleFuture.completeExceptionally(t); + } + }, + () -> handleFuture.completeExceptionally(new CancellationException("Entity scheduler task cancelled")) + ); + try { + mcEntity = handleFuture.join(); + } catch (Throwable t) { + LOGGER.error("Failed to safely get NMS handle for {}", id, t); return null; } - //add Id for AbstractChangeSet to work - final LinCompoundTag tag = (LinCompoundTag) toNativeLin(minecraftTag); - final Map> tags = NbtUtils.getLinCompoundTagValues(tag); - tags.put("Id", LinStringTag.of(id)); - return LinCompoundTag.of(tags); - }; - return new LazyBaseEntity(type, saveTag); - } else { - return null; - } + } else { + mcEntity = ((CraftEntity) entity).getHandle(); + } + + if (mcEntity == null || !readEntityIntoTag(mcEntity, minecraftTag)) { + return null; + } + + LinCompoundTag tag = (LinCompoundTag) toNativeLin(minecraftTag); + Map> tags = NbtUtils.getLinCompoundTagValues(tag); + tags.put("Id", LinStringTag.of(id)); + return LinCompoundTag.of(tags); + }; + return new LazyBaseEntity(type, saveTag); } @Override @@ -564,20 +586,62 @@ public org.bukkit.inventory.ItemStack adapt(BaseItemStack baseItemStack) { @Override protected void preCaptureStates(final ServerLevel serverLevel) { - serverLevel.captureTreeGeneration = true; - serverLevel.captureBlockStates = true; + try { + Field captureTreeField = ServerLevel.class.getDeclaredField("captureTreeGeneration"); + captureTreeField.setAccessible(true); + captureTreeField.setBoolean(serverLevel, true); + } catch (NoSuchFieldException | IllegalAccessException e) { + // Unable to read captureTreeGeneration field + } + try { + Field captureBlockField = ServerLevel.class.getDeclaredField("captureBlockStates"); + captureBlockField.setAccessible(true); + captureBlockField.setBoolean(serverLevel, true); + } catch (NoSuchFieldException | IllegalAccessException e) { + // Unable to read captureTreeGeneration field + } } @Override protected List getCapturedBlockStatesCopy(final ServerLevel serverLevel) { - return new ArrayList<>(serverLevel.capturedBlockStates.values()); + try { + Field capturedStatesField = ServerLevel.class.getDeclaredField("capturedBlockStates"); + capturedStatesField.setAccessible(true); + @SuppressWarnings("unchecked") + Map capturedStates = (Map) capturedStatesField.get(serverLevel); + return capturedStates != null ? new ArrayList<>(capturedStates.values()) : new ArrayList<>(); + } catch (NoSuchFieldException | IllegalAccessException e) { + return new ArrayList<>(); + } } @Override protected void postCaptureBlockStates(final ServerLevel serverLevel) { - serverLevel.captureBlockStates = false; - serverLevel.captureTreeGeneration = false; - serverLevel.capturedBlockStates.clear(); + try { + Field captureBlockField = ServerLevel.class.getDeclaredField("captureBlockStates"); + captureBlockField.setAccessible(true); + captureBlockField.setBoolean(serverLevel, false); + } catch (NoSuchFieldException | IllegalAccessException e) { + // Unable to read captureTreeGeneration field + } + try { + Field captureTreeField = ServerLevel.class.getDeclaredField("captureTreeGeneration"); + captureTreeField.setAccessible(true); + captureTreeField.setBoolean(serverLevel, false); + } catch (NoSuchFieldException | IllegalAccessException e) { + // Unable to read captureTreeGeneration field + } + try { + Field capturedStatesField = ServerLevel.class.getDeclaredField("capturedBlockStates"); + capturedStatesField.setAccessible(true); + @SuppressWarnings("unchecked") + Map capturedStates = (Map) capturedStatesField.get(serverLevel); + if (capturedStates != null) { + capturedStates.clear(); + } + } catch (NoSuchFieldException | IllegalAccessException e) { + // Unable to read captureTreeGeneration field + } } @Override diff --git a/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightFaweWorldNativeAccess.java b/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightFaweWorldNativeAccess.java index f7c2dc8e84..5bba575749 100644 --- a/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightFaweWorldNativeAccess.java +++ b/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightFaweWorldNativeAccess.java @@ -21,6 +21,7 @@ import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.chunk.LevelChunk; +import org.bukkit.Bukkit; import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.craftbukkit.block.data.CraftBlockData; import org.bukkit.event.block.BlockPhysicsEvent; @@ -59,7 +60,7 @@ public PaperweightFaweWorldNativeAccess(PaperweightFaweAdapter paperweightFaweAd this.level = level; // Use the actual tick as minecraft-defined so we don't try to force blocks into the world when the server's already lagging. // - With the caveat that we don't want to have too many cached changed (1024) so we'd flush those at 1024 anyway. - this.lastTick = new AtomicInteger(MinecraftServer.currentTick); + this.lastTick = new AtomicInteger(getCurrentTick()); } private Level getLevel() { @@ -95,7 +96,7 @@ public synchronized net.minecraft.world.level.block.state.BlockState setBlockSta LevelChunk levelChunk, BlockPos blockPos, net.minecraft.world.level.block.state.BlockState blockState ) { - int currentTick = MinecraftServer.currentTick; + int currentTick = getCurrentTick(); if (Fawe.isMainThread()) { return levelChunk.setBlockState(blockPos, blockState, this.sideEffectSet != null && this.sideEffectSet.shouldApply(SideEffect.UPDATE) @@ -290,4 +291,16 @@ private record CachedChange( } + private int getCurrentTick() { + try { + return MinecraftServer.currentTick; + } catch (NoSuchFieldError e) { + try { + return Bukkit.getCurrentTick(); + } catch (Exception ex) { + return 0; + } + } + } + } 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 436c28f981..7c2888acc0 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 @@ -3,7 +3,6 @@ import com.fastasyncworldedit.bukkit.adapter.AbstractBukkitGetBlocks; import com.fastasyncworldedit.bukkit.adapter.DelegateSemaphore; import com.fastasyncworldedit.bukkit.adapter.NativeEntityFunctionSet; -import com.fastasyncworldedit.core.Fawe; import com.fastasyncworldedit.core.FaweCache; import com.fastasyncworldedit.core.configuration.Settings; import com.fastasyncworldedit.core.extent.processor.heightmap.HeightMapType; @@ -12,7 +11,7 @@ import com.fastasyncworldedit.core.math.IntPair; import com.fastasyncworldedit.core.nbt.FaweCompoundTag; import com.fastasyncworldedit.core.queue.IChunkSet; -import com.fastasyncworldedit.core.queue.implementation.QueueHandler; +import com.fastasyncworldedit.core.util.FoliaUtil; import com.fastasyncworldedit.core.util.MathMan; import com.fastasyncworldedit.core.util.NbtUtils; import com.fastasyncworldedit.core.util.collection.AdaptedMap; @@ -59,6 +58,8 @@ import net.minecraft.world.level.levelgen.Heightmap; import net.minecraft.world.level.lighting.LevelLightEngine; import org.apache.logging.log4j.Logger; +import org.bukkit.Bukkit; +import org.bukkit.Location; import org.bukkit.World; import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.craftbukkit.block.CraftBlock; @@ -82,7 +83,6 @@ import java.util.Map; import java.util.Set; import java.util.UUID; -import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; @@ -198,23 +198,30 @@ public void removeSectionLighting(int layer, boolean sky) { @Override public FaweCompoundTag tile(final int x, final int y, final int z) { - BlockEntity blockEntity = getChunk().getBlockEntity(new BlockPos((x & 15) + ( - chunkX << 4), y, (z & 15) + ( - chunkZ << 4))); - if (blockEntity == null) { + try { + BlockEntity blockEntity = getChunk().getBlockEntity(new BlockPos((x & 15) + ( + chunkX << 4), y, (z & 15) + ( + chunkZ << 4))); + if (blockEntity == null) { + return null; + } + return NMS_TO_TILE.apply(blockEntity); + } catch (NullPointerException e) { return null; } - return NMS_TO_TILE.apply(blockEntity); - } @Override public Map tiles() { - Map nmsTiles = getChunk().getBlockEntities(); - if (nmsTiles.isEmpty()) { + try { + Map nmsTiles = getChunk().getBlockEntities(); + if (nmsTiles.isEmpty()) { + return Collections.emptyMap(); + } + return AdaptedMap.immutable(nmsTiles, posNms2We, NMS_TO_TILE); + } catch (NullPointerException e) { return Collections.emptyMap(); } - return AdaptedMap.immutable(nmsTiles, posNms2We, NMS_TO_TILE); } @Override @@ -321,7 +328,20 @@ public Set getFullEntities() { } private void removeEntity(Entity entity) { - entity.discard(); + if (FoliaUtil.isFoliaServer()) { + entity.getBukkitEntity().getScheduler().execute( + WorldEditPlugin.getInstance(), + () -> { + if (!entity.isRemoved()) { + entity.discard(); + } + }, + null, + 1L + ); + } else if (!entity.isRemoved()) { + entity.discard(); + } } @Override @@ -349,11 +369,11 @@ protected > T internalCall( 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; + BlockPos pos = entry.getKey(); + int lx = pos.getX() & 15; + int ly = pos.getY(); + int lz = pos.getZ() & 15; + int layer = ly >> 4; if (!set.hasSection(layer)) { continue; } @@ -361,12 +381,26 @@ protected > T internalCall( int ordinal = set.getBlock(lx, ly, lz).getOrdinal(); if (ordinal != BlockTypesCache.ReservedIDs.__RESERVED__) { BlockEntity tile = entry.getValue(); - if (PaperLib.isPaper() && tile instanceof BeaconBlockEntity) { + if (PaperLib.isPaper() && tile instanceof BeaconBlockEntity beacon) { if (beacons == null) { beacons = new ArrayList<>(); } - beacons.add(tile); - PaperweightPlatformAdapter.removeBeacon(tile, nmsChunk); + beacons.add(beacon); + Location location = new Location( + nmsWorld.getWorld(), + beacon.getBlockPos().getX(), + beacon.getBlockPos().getY(), + beacon.getBlockPos().getZ() + ); + if (FoliaUtil.isFoliaServer()) { + Bukkit.getServer().getRegionScheduler().execute( + WorldEditPlugin.getInstance(), + location, + () -> PaperweightPlatformAdapter.removeBeacon(beacon, nmsChunk) + ); + } else { + PaperweightPlatformAdapter.removeBeacon(beacon, nmsChunk); + } continue; } nmsChunk.removeBlockEntity(tile.getBlockPos()); @@ -376,7 +410,7 @@ protected > T internalCall( } } } - final BiomeType[][] biomes = set.getBiomes(); + BiomeType[][] biomes = set.getBiomes(); int bitMask = 0; synchronized (nmsChunk) { @@ -589,7 +623,7 @@ protected > T internalCall( set.getMaxSectionPosition() ); - Runnable[] syncTasks = null; + List syncTasks = new ArrayList<>(); int bx = chunkX << 4; int bz = chunkZ << 4; @@ -597,27 +631,20 @@ protected > T internalCall( // 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] = () -> { + List finalBeacons = beacons; + syncTasks.add(() -> { 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] = () -> { + syncTasks.add(() -> { Set entitiesRemoved = new HashSet<>(); - final List entities = PaperweightPlatformAdapter.getEntities(nmsChunk); + List entities = PaperweightPlatformAdapter.getEntities(nmsChunk); for (Entity entity : entities) { UUID uuid = entity.getUUID(); @@ -641,95 +668,113 @@ protected > T internalCall( // 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]; - } - - syncTasks[1] = () -> { + syncTasks.add(() -> { 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()); + FaweCompoundTag nativeTag = iterator.next(); + LinCompoundTag linTag = nativeTag.linTag(); + LinStringTag idTag = linTag.findTag("Id", LinTagType.stringTag()); + LinListTag posTag = linTag.findListTag("Pos", LinTagType.doubleTag()); + 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(); + double x = posTag.get(0).valueAsDouble(); + double y = posTag.get(1).valueAsDouble(); + double z = posTag.get(2).valueAsDouble(); + float yaw = rotTag.get(0).valueAsFloat(); + float pitch = rotTag.get(1).valueAsFloat(); + 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) { + CompoundTag tag = (CompoundTag) adapter.fromNativeLin(linTag); + for (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 (!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(); + Location location = new Location(nmsWorld.getWorld(), x, y, z); + if (FoliaUtil.isFoliaServer()) { + Bukkit.getServer().getRegionScheduler().execute(WorldEditPlugin.getInstance(), location, () -> { + 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 + ); + iterator.remove(); + } + }); + } else { + 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 + ); + iterator.remove(); + } } } } } - }; + }); } // 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 (nmsWorld) { - BlockEntity tileEntity = nmsWorld.getBlockEntity(pos); - if (tileEntity == null || tileEntity.isRemoved()) { - nmsWorld.removeBlockEntity(pos); - tileEntity = nmsWorld.getBlockEntity(pos); - } - 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()); + syncTasks.add(() -> { + World world = nmsWorld.getWorld(); + for (Map.Entry entry : tiles.entrySet()) { + FaweCompoundTag nativeTag = entry.getValue(); + BlockVector3 blockHash = entry.getKey(); + int x = blockHash.x() + bx; + int y = blockHash.y(); + int z = blockHash.z() + bz; + BlockPos pos = new BlockPos(x, y, z); + Location location = new Location(world, x, y, z); + + Runnable setTile = () -> { + synchronized (nmsChunk) { + BlockEntity tileEntity = nmsChunk.getBlockEntity(pos); + if (tileEntity == null || tileEntity.isRemoved()) { + nmsChunk.removeBlockEntity(pos); + tileEntity = nmsChunk.getBlockEntity(pos); + } + if (tileEntity != null) { + 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()); + } } + }; + + if (FoliaUtil.isFoliaServer()) { + Bukkit.getServer().getRegionScheduler().execute(WorldEditPlugin.getInstance(), location, setTile); + } else { + setTile.run(); } } - }; + }); } Runnable callback; @@ -737,11 +782,13 @@ protected > T internalCall( callback = null; } else { int finalMask = bitMask != 0 ? bitMask : lightUpdate ? set.getBitMask() : 0; - callback = () -> { + syncTasks.add(() -> { // Set Modified - nmsChunk.setLightCorrect(true); // Set Modified + nmsChunk.setLightCorrect(true); nmsChunk.mustNotSave = false; nmsChunk.setUnsaved(true); + }); + callback = () -> { // send to player if (!set .getSideEffectSet() @@ -753,45 +800,8 @@ protected > T internalCall( } }; } - 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) { - 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 handleCallFinalizer(syncTasks, callback, finalizer); } - return null; } private void updateGet( 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 be8e7d734f..daf1556907 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 @@ -9,6 +9,7 @@ import com.fastasyncworldedit.core.FaweCache; import com.fastasyncworldedit.core.math.BitArrayUnstretched; import com.fastasyncworldedit.core.math.IntPair; +import com.fastasyncworldedit.core.util.FoliaUtil; import com.fastasyncworldedit.core.util.MathMan; import com.fastasyncworldedit.core.util.TaskManager; import com.sk89q.worldedit.bukkit.WorldEditPlugin; @@ -20,6 +21,7 @@ import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockTypesCache; import io.papermc.lib.PaperLib; +import io.papermc.paper.util.MCUtil; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; import net.minecraft.core.IdMap; @@ -316,10 +318,22 @@ private static LevelChunk toLevelChunk(Chunk chunk) { } private static void addTicket(ServerLevel serverLevel, int chunkX, int chunkZ) { - // Ensure chunk is definitely loaded before applying a ticket - io.papermc.paper.util.MCUtil.MAIN_EXECUTOR.execute(() -> serverLevel - .getChunkSource() - .addRegionTicket(ChunkHolderManager.UNLOAD_COOLDOWN, new ChunkPos(chunkX, chunkZ), 0, Unit.INSTANCE)); + ChunkPos pos = new ChunkPos(chunkX, chunkZ); + if (FoliaUtil.isFoliaServer()) { + try { + serverLevel.getChunkSource().addRegionTicket(ChunkHolderManager.UNLOAD_COOLDOWN, pos, 0, Unit.INSTANCE); + } catch (Exception ignored) { + } + return; + } + try { + MCUtil.MAIN_EXECUTOR.execute(() -> serverLevel.getChunkSource().addRegionTicket(ChunkHolderManager.UNLOAD_COOLDOWN, pos, 0, Unit.INSTANCE)); + } catch (Exception e) { + try { + serverLevel.getChunkSource().addRegionTicket(ChunkHolderManager.UNLOAD_COOLDOWN, pos, 0, Unit.INSTANCE); + } catch (Exception ignored) { + } + } } public static ChunkHolder getPlayerChunk(ServerLevel nmsWorld, final int chunkX, final int chunkZ) { @@ -337,14 +351,9 @@ public static void sendChunk(IntPair pair, ServerLevel nmsWorld, int chunkX, int if (chunkHolder == null) { return; } - ChunkPos coordIntPair = new ChunkPos(chunkX, chunkZ); - LevelChunk levelChunk; - if (PaperLib.isPaper()) { - // getChunkAtIfLoadedImmediately is paper only - levelChunk = nmsWorld.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ); - } else { - levelChunk = chunkHolder.getTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK).orElse(null); - } + LevelChunk levelChunk = PaperLib.isPaper() + ? nmsWorld.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ) + : chunkHolder.getTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK).orElse(null); if (levelChunk == null) { return; } @@ -353,31 +362,37 @@ public static void sendChunk(IntPair pair, ServerLevel nmsWorld, int chunkX, int if (lockHolder.chunkLock == null) { return; } - MinecraftServer.getServer().execute(() -> { + + ChunkPos pos = levelChunk.getPos(); + ClientboundLevelChunkWithLightPacket packet = PaperLib.isPaper() + ? new ClientboundLevelChunkWithLightPacket(levelChunk, nmsWorld.getLightEngine(), null, null, false) + : new ClientboundLevelChunkWithLightPacket(levelChunk, nmsWorld.getLightEngine(), null, null); + + Runnable sendPacket = () -> nearbyPlayers(nmsWorld, pos).forEach(p -> p.connection.send(packet)); + + if (FoliaUtil.isFoliaServer()) { try { - ClientboundLevelChunkWithLightPacket packet; - if (PaperLib.isPaper()) { - packet = new ClientboundLevelChunkWithLightPacket( - levelChunk, - nmsWorld.getChunkSource().getLightEngine(), - null, - null, - false // last false is to not bother with x-ray - ); - } else { - // deprecated on paper - deprecation suppressed - packet = new ClientboundLevelChunkWithLightPacket( - levelChunk, - nmsWorld.getChunkSource().getLightEngine(), - null, - null - ); - } - nearbyPlayers(nmsWorld, coordIntPair).forEach(p -> p.connection.send(packet)); + sendPacket.run(); } finally { NMSAdapter.endChunkPacketSend(nmsWorld.getWorld().getName(), pair, lockHolder); } - }); + } else { + try { + MinecraftServer.getServer().execute(() -> { + try { + sendPacket.run(); + } finally { + NMSAdapter.endChunkPacketSend(nmsWorld.getWorld().getName(), pair, lockHolder); + } + }); + } catch (Exception e) { + try { + sendPacket.run(); + } finally { + NMSAdapter.endChunkPacketSend(nmsWorld.getWorld().getName(), pair, lockHolder); + } + } + } } private static List nearbyPlayers(ServerLevel serverLevel, ChunkPos coordIntPair) { @@ -639,8 +654,9 @@ public static BiomeType adapt(Holder biome, LevelAccessor levelAccessor) static void removeBeacon(BlockEntity beacon, LevelChunk levelChunk) { try { + Map blockEntities = levelChunk.getBlockEntities(); if (levelChunk.loaded || levelChunk.level.isClientSide()) { - BlockEntity blockEntity = levelChunk.blockEntities.remove(beacon.getBlockPos()); + BlockEntity blockEntity = blockEntities.remove(beacon.getBlockPos()); if (blockEntity != null) { if (!levelChunk.level.isClientSide) { methodRemoveGameEventListener.invoke(levelChunk, beacon, levelChunk.level); diff --git a/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/regen/PaperweightRegen.java b/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/regen/PaperweightRegen.java index f62ae8e5d2..2e9803f905 100644 --- a/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/regen/PaperweightRegen.java +++ b/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/regen/PaperweightRegen.java @@ -5,6 +5,7 @@ import com.fastasyncworldedit.core.queue.IChunkCache; import com.fastasyncworldedit.core.queue.IChunkGet; import com.fastasyncworldedit.core.queue.implementation.chunk.ChunkCache; +import com.fastasyncworldedit.core.util.FoliaUtil; import com.google.common.collect.ImmutableList; import com.mojang.serialization.Lifecycle; import com.sk89q.worldedit.bukkit.BukkitAdapter; @@ -42,6 +43,8 @@ import java.nio.file.Path; import java.util.Map; import java.util.OptionalLong; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.function.BooleanSupplier; import java.util.function.Supplier; @@ -207,9 +210,52 @@ public void save( if (paperConfigField != null) { paperConfigField.set(freshWorld, originalServerWorld.paperConfig()); } + + if (FoliaUtil.isFoliaServer()) { + return initWorldForFolia(newWorldData); + } + return true; } + private boolean initWorldForFolia(PrimaryLevelData worldData) throws ExecutionException, InterruptedException { + MinecraftServer console = ((CraftServer) Bukkit.getServer()).getServer(); + + ChunkPos spawnChunk = new ChunkPos( + freshWorld.getChunkSource().randomState().sampler().findSpawnPosition() + ); + + setRandomSpawnSelection(spawnChunk); + + CompletableFuture initFuture = new CompletableFuture<>(); + + Bukkit.getServer().getRegionScheduler().run( + WorldEditPlugin.getInstance(), + freshWorld.getWorld(), + spawnChunk.x, spawnChunk.z, + task -> { + try { + console.initWorld(freshWorld, worldData, worldData, worldData.worldGenOptions()); + initFuture.complete(true); + } catch (Exception e) { + initFuture.completeExceptionally(e); + } + } + ); + + return initFuture.get(); + } + + private void setRandomSpawnSelection(ChunkPos spawnChunk) { + try { + Field randomSpawnField = ServerLevel.class.getDeclaredField("randomSpawnSelection"); + randomSpawnField.setAccessible(true); + randomSpawnField.set(freshWorld, spawnChunk); + } catch (ReflectiveOperationException e) { + throw new RuntimeException("Failed to set randomSpawnSelection for Folia world initialization", e); + } + } + @Override protected void cleanup() { try { diff --git a/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_4/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_4/PaperweightFaweAdapter.java index 82cccce624..ad44400381 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_4/PaperweightFaweAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_4/PaperweightFaweAdapter.java @@ -10,6 +10,7 @@ import com.fastasyncworldedit.core.queue.IBatchProcessor; import com.fastasyncworldedit.core.queue.IChunkGet; import com.fastasyncworldedit.core.queue.implementation.packet.ChunkPacket; +import com.fastasyncworldedit.core.util.FoliaUtil; import com.fastasyncworldedit.core.util.NbtUtils; import com.fastasyncworldedit.core.util.TaskManager; import com.google.common.base.Preconditions; @@ -20,6 +21,7 @@ import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.bukkit.WorldEditPlugin; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; import com.sk89q.worldedit.bukkit.adapter.ext.fawe.v1_21_4.PaperweightAdapter; import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_21_4.regen.PaperweightRegen; @@ -53,6 +55,7 @@ import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.registry.BlockMaterial; import io.papermc.lib.PaperLib; +import io.papermc.paper.threadedregions.scheduler.ScheduledTask; import net.minecraft.core.BlockPos; import net.minecraft.core.Registry; import net.minecraft.core.RegistryAccess; @@ -129,6 +132,8 @@ import java.util.Objects; import java.util.OptionalInt; import java.util.Set; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -334,7 +339,7 @@ public Set getSupportedSideEffects() { } @Override - public WorldNativeAccess createWorldNativeAccess(World world) { + public WorldNativeAccess createWorldNativeAccess(org.bukkit.World world) { return new PaperweightFaweWorldNativeAccess(this, new WeakReference<>(getServerLevel(world))); } @@ -342,24 +347,45 @@ public Set getSupportedSideEffects() { public BaseEntity getEntity(org.bukkit.entity.Entity entity) { Preconditions.checkNotNull(entity); - CraftEntity craftEntity = ((CraftEntity) entity); - Entity mcEntity = craftEntity.getHandle(); - - String id = getEntityId(mcEntity); + String id = entity.getType().getKey().toString(); EntityType type = com.sk89q.worldedit.world.entity.EntityTypes.get(id); Supplier saveTag = () -> { - final net.minecraft.nbt.CompoundTag minecraftTag = new net.minecraft.nbt.CompoundTag(); - if (!readEntityIntoTag(mcEntity, minecraftTag)) { + net.minecraft.nbt.CompoundTag minecraftTag = new net.minecraft.nbt.CompoundTag(); + Entity mcEntity; + + if (FoliaUtil.isFoliaServer()) { + CompletableFuture handleFuture = new CompletableFuture<>(); + entity.getScheduler().run( + WorldEditPlugin.getInstance(), + (ScheduledTask task) -> { + try { + handleFuture.complete(((CraftEntity) entity).getHandle()); + } catch (Throwable t) { + handleFuture.completeExceptionally(t); + } + }, + () -> handleFuture.completeExceptionally(new CancellationException("Entity scheduler task cancelled")) + ); + try { + mcEntity = handleFuture.join(); + } catch (Throwable t) { + LOGGER.error("Failed to safely get NMS handle for {}", id, t); + return null; + } + } else { + mcEntity = ((CraftEntity) entity).getHandle(); + } + + if (mcEntity == null || !readEntityIntoTag(mcEntity, minecraftTag)) { return null; } - //add Id for AbstractChangeSet to work - final LinCompoundTag tag = (LinCompoundTag) toNativeLin(minecraftTag); - final Map> tags = NbtUtils.getLinCompoundTagValues(tag); + + LinCompoundTag tag = (LinCompoundTag) toNativeLin(minecraftTag); + Map> tags = NbtUtils.getLinCompoundTagValues(tag); tags.put("Id", LinStringTag.of(id)); return LinCompoundTag.of(tags); }; return new LazyBaseEntity(type, saveTag); - } @Override @@ -546,20 +572,62 @@ public org.bukkit.inventory.ItemStack adapt(BaseItemStack baseItemStack) { @Override protected void preCaptureStates(final ServerLevel serverLevel) { - serverLevel.captureTreeGeneration = true; - serverLevel.captureBlockStates = true; + try { + Field captureTreeField = ServerLevel.class.getDeclaredField("captureTreeGeneration"); + captureTreeField.setAccessible(true); + captureTreeField.setBoolean(serverLevel, true); + } catch (NoSuchFieldException | IllegalAccessException e) { + // Unable to read captureTreeGeneration field + } + try { + Field captureBlockField = ServerLevel.class.getDeclaredField("captureBlockStates"); + captureBlockField.setAccessible(true); + captureBlockField.setBoolean(serverLevel, true); + } catch (NoSuchFieldException | IllegalAccessException e) { + // Unable to read captureTreeGeneration field + } } @Override protected List getCapturedBlockStatesCopy(final ServerLevel serverLevel) { - return new ArrayList<>(serverLevel.capturedBlockStates.values()); + try { + Field capturedStatesField = ServerLevel.class.getDeclaredField("capturedBlockStates"); + capturedStatesField.setAccessible(true); + @SuppressWarnings("unchecked") + Map capturedStates = (Map) capturedStatesField.get(serverLevel); + return capturedStates != null ? new ArrayList<>(capturedStates.values()) : new ArrayList<>(); + } catch (NoSuchFieldException | IllegalAccessException e) { + return new ArrayList<>(); + } } @Override protected void postCaptureBlockStates(final ServerLevel serverLevel) { - serverLevel.captureBlockStates = false; - serverLevel.captureTreeGeneration = false; - serverLevel.capturedBlockStates.clear(); + try { + Field captureBlockField = ServerLevel.class.getDeclaredField("captureBlockStates"); + captureBlockField.setAccessible(true); + captureBlockField.setBoolean(serverLevel, false); + } catch (NoSuchFieldException | IllegalAccessException e) { + // Unable to read captureTreeGeneration field + } + try { + Field captureTreeField = ServerLevel.class.getDeclaredField("captureTreeGeneration"); + captureTreeField.setAccessible(true); + captureTreeField.setBoolean(serverLevel, false); + } catch (NoSuchFieldException | IllegalAccessException e) { + // Unable to read captureTreeGeneration field + } + try { + Field capturedStatesField = ServerLevel.class.getDeclaredField("capturedBlockStates"); + capturedStatesField.setAccessible(true); + @SuppressWarnings("unchecked") + Map capturedStates = (Map) capturedStatesField.get(serverLevel); + if (capturedStates != null) { + capturedStates.clear(); + } + } catch (NoSuchFieldException | IllegalAccessException e) { + // Unable to read captureTreeGeneration field + } } @Override diff --git a/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_4/PaperweightFaweWorldNativeAccess.java b/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_4/PaperweightFaweWorldNativeAccess.java index 60a1d5c13a..73dc792077 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_4/PaperweightFaweWorldNativeAccess.java +++ b/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_4/PaperweightFaweWorldNativeAccess.java @@ -22,6 +22,7 @@ import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.redstone.ExperimentalRedstoneUtils; +import org.bukkit.Bukkit; import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.craftbukkit.block.data.CraftBlockData; import org.bukkit.event.block.BlockPhysicsEvent; @@ -60,7 +61,7 @@ public PaperweightFaweWorldNativeAccess(PaperweightFaweAdapter paperweightFaweAd this.level = level; // Use the actual tick as minecraft-defined so we don't try to force blocks into the world when the server's already lagging. // - With the caveat that we don't want to have too many cached changed (1024) so we'd flush those at 1024 anyway. - this.lastTick = new AtomicInteger(MinecraftServer.currentTick); + this.lastTick = new AtomicInteger(getCurrentTick()); } private Level getLevel() { @@ -96,7 +97,7 @@ public synchronized net.minecraft.world.level.block.state.BlockState setBlockSta LevelChunk levelChunk, BlockPos blockPos, net.minecraft.world.level.block.state.BlockState blockState ) { - int currentTick = MinecraftServer.currentTick; + int currentTick = getCurrentTick(); if (Fawe.isMainThread()) { return levelChunk.setBlockState(blockPos, blockState, this.sideEffectSet != null && this.sideEffectSet.shouldApply(SideEffect.UPDATE) @@ -291,4 +292,17 @@ private record CachedChange( } + + private int getCurrentTick() { + try { + return MinecraftServer.currentTick; + } catch (NoSuchFieldError e) { + try { + return Bukkit.getCurrentTick(); + } catch (Exception ex) { + return 0; + } + } + } + } 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 4d173a9f05..501ffd7e1a 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 @@ -11,6 +11,7 @@ import com.fastasyncworldedit.core.math.IntPair; import com.fastasyncworldedit.core.nbt.FaweCompoundTag; import com.fastasyncworldedit.core.queue.IChunkSet; +import com.fastasyncworldedit.core.util.FoliaUtil; import com.fastasyncworldedit.core.util.MathMan; import com.fastasyncworldedit.core.util.NbtUtils; import com.fastasyncworldedit.core.util.collection.AdaptedMap; @@ -58,6 +59,8 @@ import net.minecraft.world.level.levelgen.Heightmap; import net.minecraft.world.level.lighting.LevelLightEngine; import org.apache.logging.log4j.Logger; +import org.bukkit.Bukkit; +import org.bukkit.Location; import org.bukkit.World; import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.craftbukkit.block.CraftBlock; @@ -196,23 +199,30 @@ public void removeSectionLighting(int layer, boolean sky) { @Override public FaweCompoundTag tile(final int x, final int y, final int z) { - BlockEntity blockEntity = getChunk().getBlockEntity(new BlockPos((x & 15) + ( - chunkX << 4), y, (z & 15) + ( - chunkZ << 4))); - if (blockEntity == null) { + try { + BlockEntity blockEntity = getChunk().getBlockEntity(new BlockPos((x & 15) + ( + chunkX << 4), y, (z & 15) + ( + chunkZ << 4))); + if (blockEntity == null) { + return null; + } + return NMS_TO_TILE.apply(blockEntity); + } catch (NullPointerException e) { return null; } - return NMS_TO_TILE.apply(blockEntity); - } @Override public Map tiles() { - Map nmsTiles = getChunk().getBlockEntities(); - if (nmsTiles.isEmpty()) { + try { + Map nmsTiles = getChunk().getBlockEntities(); + if (nmsTiles.isEmpty()) { + return Collections.emptyMap(); + } + return AdaptedMap.immutable(nmsTiles, posNms2We, NMS_TO_TILE); + } catch (NullPointerException e) { return Collections.emptyMap(); } - return AdaptedMap.immutable(nmsTiles, posNms2We, NMS_TO_TILE); } @Override @@ -319,7 +329,20 @@ public Set getFullEntities() { } private void removeEntity(Entity entity) { - entity.discard(); + if (FoliaUtil.isFoliaServer()) { + entity.getBukkitEntity().getScheduler().execute( + WorldEditPlugin.getInstance(), + () -> { + if (!entity.isRemoved()) { + entity.discard(); + } + }, + null, + 1L + ); + } else if (!entity.isRemoved()) { + entity.discard(); + } } @Override @@ -339,7 +362,7 @@ protected > T internalCall( if (createCopy) { if (copies.containsKey(copyKey)) { throw new IllegalStateException("Copy key already used."); - } + } copies.put(copyKey, copy); } // Remove existing tiles. Create a copy so that we can remove blocks @@ -347,11 +370,11 @@ protected > T internalCall( 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; + BlockPos pos = entry.getKey(); + int lx = pos.getX() & 15; + int ly = pos.getY(); + int lz = pos.getZ() & 15; + int layer = ly >> 4; if (!set.hasSection(layer)) { continue; } @@ -359,12 +382,26 @@ protected > T internalCall( int ordinal = set.getBlock(lx, ly, lz).getOrdinal(); if (ordinal != BlockTypesCache.ReservedIDs.__RESERVED__) { BlockEntity tile = entry.getValue(); - if (PaperLib.isPaper() && tile instanceof BeaconBlockEntity) { + if (PaperLib.isPaper() && tile instanceof BeaconBlockEntity beacon) { if (beacons == null) { beacons = new ArrayList<>(); } - beacons.add(tile); - PaperweightPlatformAdapter.removeBeacon(tile, nmsChunk); + beacons.add(beacon); + Location location = new Location( + nmsWorld.getWorld(), + beacon.getBlockPos().getX(), + beacon.getBlockPos().getY(), + beacon.getBlockPos().getZ() + ); + if (FoliaUtil.isFoliaServer()) { + Bukkit.getServer().getRegionScheduler().execute( + WorldEditPlugin.getInstance(), + location, + () -> PaperweightPlatformAdapter.removeBeacon(beacon, nmsChunk) + ); + } else { + PaperweightPlatformAdapter.removeBeacon(beacon, nmsChunk); + } continue; } nmsChunk.removeBlockEntity(tile.getBlockPos()); @@ -374,7 +411,7 @@ protected > T internalCall( } } } - final BiomeType[][] biomes = set.getBiomes(); + BiomeType[][] biomes = set.getBiomes(); int bitMask = 0; synchronized (nmsChunk) { @@ -593,7 +630,7 @@ protected > T internalCall( // 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; + List finalBeacons = beacons; syncTasks.add(() -> { for (BlockEntity beacon : finalBeacons) { BeaconBlockEntity.playSound(beacon.getLevel(), beacon.getBlockPos(), SoundEvents.BEACON_DEACTIVATE); @@ -606,7 +643,7 @@ protected > T internalCall( if (entityRemoves != null && !entityRemoves.isEmpty()) { syncTasks.add(() -> { Set entitiesRemoved = new HashSet<>(); - final List entities = PaperweightPlatformAdapter.getEntities(nmsChunk); + List entities = PaperweightPlatformAdapter.getEntities(nmsChunk); for (Entity entity : entities) { UUID uuid = entity.getUUID(); @@ -638,44 +675,60 @@ protected > T internalCall( syncTasks.add(() -> { 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()); + FaweCompoundTag nativeTag = iterator.next(); + LinCompoundTag linTag = nativeTag.linTag(); + LinStringTag idTag = linTag.findTag("Id", LinTagType.stringTag()); + LinListTag posTag = linTag.findListTag("Pos", LinTagType.doubleTag()); + 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(); + double x = posTag.get(0).valueAsDouble(); + double y = posTag.get(1).valueAsDouble(); + double z = posTag.get(2).valueAsDouble(); + float yaw = rotTag.get(0).valueAsFloat(); + float pitch = rotTag.get(1).valueAsFloat(); + 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) { + CompoundTag tag = (CompoundTag) adapter.fromNativeLin(linTag); + for (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 (!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(); + Location location = new Location(nmsWorld.getWorld(), x, y, z); + if (FoliaUtil.isFoliaServer()) { + Bukkit.getServer().getRegionScheduler().execute(WorldEditPlugin.getInstance(), location, () -> { + 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 + ); + iterator.remove(); + } + }); + } else { + 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 + ); + iterator.remove(); + } } } } @@ -687,27 +740,37 @@ protected > T internalCall( Map tiles = set.tiles(); if (tiles != null && !tiles.isEmpty()) { syncTasks.add(() -> { - 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 (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()); + World world = nmsWorld.getWorld(); + for (Map.Entry entry : tiles.entrySet()) { + FaweCompoundTag nativeTag = entry.getValue(); + BlockVector3 blockHash = entry.getKey(); + int x = blockHash.x() + bx; + int y = blockHash.y(); + int z = blockHash.z() + bz; + BlockPos pos = new BlockPos(x, y, z); + Location location = new Location(world, x, y, z); + + Runnable setTile = () -> { + synchronized (nmsChunk) { + BlockEntity tileEntity = nmsChunk.getBlockEntity(pos); + if (tileEntity == null || tileEntity.isRemoved()) { + nmsChunk.removeBlockEntity(pos); + tileEntity = nmsChunk.getBlockEntity(pos); + } + if (tileEntity != null) { + 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()); + } } + }; + + if (FoliaUtil.isFoliaServer()) { + Bukkit.getServer().getRegionScheduler().execute(WorldEditPlugin.getInstance(), location, setTile); + } else { + setTile.run(); } } }); 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 cdd4f7d7b7..a05f2b6883 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 @@ -9,6 +9,7 @@ import com.fastasyncworldedit.core.FaweCache; import com.fastasyncworldedit.core.math.BitArrayUnstretched; import com.fastasyncworldedit.core.math.IntPair; +import com.fastasyncworldedit.core.util.FoliaUtil; import com.fastasyncworldedit.core.util.MathMan; import com.fastasyncworldedit.core.util.TaskManager; import com.sk89q.worldedit.bukkit.WorldEditPlugin; @@ -20,6 +21,7 @@ import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockTypesCache; import io.papermc.lib.PaperLib; +import io.papermc.paper.util.MCUtil; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; import net.minecraft.core.IdMap; @@ -302,10 +304,22 @@ private static LevelChunk toLevelChunk(Chunk chunk) { } private static void addTicket(ServerLevel serverLevel, int chunkX, int chunkZ) { - // Ensure chunk is definitely loaded before applying a ticket - io.papermc.paper.util.MCUtil.MAIN_EXECUTOR.execute(() -> serverLevel - .getChunkSource() - .addRegionTicket(ChunkHolderManager.UNLOAD_COOLDOWN, new ChunkPos(chunkX, chunkZ), 0, Unit.INSTANCE)); + ChunkPos pos = new ChunkPos(chunkX, chunkZ); + if (FoliaUtil.isFoliaServer()) { + try { + serverLevel.getChunkSource().addRegionTicket(ChunkHolderManager.UNLOAD_COOLDOWN, pos, 0, Unit.INSTANCE); + } catch (Exception ignored) { + } + return; + } + try { + MCUtil.MAIN_EXECUTOR.execute(() -> serverLevel.getChunkSource().addRegionTicket(ChunkHolderManager.UNLOAD_COOLDOWN, pos, 0, Unit.INSTANCE)); + } catch (Exception e) { + try { + serverLevel.getChunkSource().addRegionTicket(ChunkHolderManager.UNLOAD_COOLDOWN, pos, 0, Unit.INSTANCE); + } catch (Exception ignored) { + } + } } public static ChunkHolder getPlayerChunk(ServerLevel nmsWorld, final int chunkX, final int chunkZ) { @@ -323,13 +337,9 @@ public static void sendChunk(IntPair pair, ServerLevel nmsWorld, int chunkX, int if (chunkHolder == null) { return; } - LevelChunk levelChunk; - if (PaperLib.isPaper()) { - // getChunkAtIfLoadedImmediately is paper only - levelChunk = nmsWorld.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ); - } else { - levelChunk = chunkHolder.getTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK).orElse(null); - } + LevelChunk levelChunk = PaperLib.isPaper() + ? nmsWorld.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ) + : chunkHolder.getTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK).orElse(null); if (levelChunk == null) { return; } @@ -338,32 +348,37 @@ public static void sendChunk(IntPair pair, ServerLevel nmsWorld, int chunkX, int if (lockHolder.chunkLock == null) { return; } - MinecraftServer.getServer().execute(() -> { + + ChunkPos pos = levelChunk.getPos(); + ClientboundLevelChunkWithLightPacket packet = PaperLib.isPaper() + ? new ClientboundLevelChunkWithLightPacket(levelChunk, nmsWorld.getLightEngine(), null, null, false) + : new ClientboundLevelChunkWithLightPacket(levelChunk, nmsWorld.getLightEngine(), null, null); + + Runnable sendPacket = () -> nearbyPlayers(nmsWorld, pos).forEach(p -> p.connection.send(packet)); + + if (FoliaUtil.isFoliaServer()) { try { - ChunkPos pos = levelChunk.getPos(); - ClientboundLevelChunkWithLightPacket packet; - if (PaperLib.isPaper()) { - packet = new ClientboundLevelChunkWithLightPacket( - levelChunk, - nmsWorld.getLightEngine(), - null, - null, - false // last false is to not bother with x-ray - ); - } else { - // deprecated on paper - deprecation suppressed - packet = new ClientboundLevelChunkWithLightPacket( - levelChunk, - nmsWorld.getLightEngine(), - null, - null - ); - } - nearbyPlayers(nmsWorld, pos).forEach(p -> p.connection.send(packet)); + sendPacket.run(); } finally { NMSAdapter.endChunkPacketSend(nmsWorld.getWorld().getName(), pair, lockHolder); } - }); + } else { + try { + MinecraftServer.getServer().execute(() -> { + try { + sendPacket.run(); + } finally { + NMSAdapter.endChunkPacketSend(nmsWorld.getWorld().getName(), pair, lockHolder); + } + }); + } catch (Exception e) { + try { + sendPacket.run(); + } finally { + NMSAdapter.endChunkPacketSend(nmsWorld.getWorld().getName(), pair, lockHolder); + } + } + } } private static List nearbyPlayers(ServerLevel serverLevel, ChunkPos coordIntPair) { @@ -625,8 +640,9 @@ public static BiomeType adapt(Holder biome, LevelAccessor levelAccessor) static void removeBeacon(BlockEntity beacon, LevelChunk levelChunk) { try { + Map blockEntities = levelChunk.getBlockEntities(); if (levelChunk.loaded || levelChunk.level.isClientSide()) { - BlockEntity blockEntity = levelChunk.blockEntities.remove(beacon.getBlockPos()); + BlockEntity blockEntity = blockEntities.remove(beacon.getBlockPos()); if (blockEntity != null) { if (!levelChunk.level.isClientSide) { methodRemoveGameEventListener.invoke(levelChunk, beacon, levelChunk.level); diff --git a/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_4/regen/PaperweightRegen.java b/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_4/regen/PaperweightRegen.java index f542469d36..8f336f3d80 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_4/regen/PaperweightRegen.java +++ b/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_4/regen/PaperweightRegen.java @@ -5,6 +5,7 @@ import com.fastasyncworldedit.core.queue.IChunkCache; import com.fastasyncworldedit.core.queue.IChunkGet; import com.fastasyncworldedit.core.queue.implementation.chunk.ChunkCache; +import com.fastasyncworldedit.core.util.FoliaUtil; import com.google.common.collect.ImmutableList; import com.mojang.serialization.Lifecycle; import com.sk89q.worldedit.bukkit.BukkitAdapter; @@ -41,6 +42,8 @@ import java.nio.file.Path; import java.util.Map; import java.util.OptionalLong; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.function.BooleanSupplier; import java.util.function.Supplier; @@ -206,9 +209,52 @@ public void save( if (paperConfigField != null) { paperConfigField.set(freshWorld, originalServerWorld.paperConfig()); } + + if (FoliaUtil.isFoliaServer()) { + return initWorldForFolia(newWorldData); + } + return true; } + private boolean initWorldForFolia(PrimaryLevelData worldData) throws ExecutionException, InterruptedException { + MinecraftServer console = ((CraftServer) Bukkit.getServer()).getServer(); + + ChunkPos spawnChunk = new ChunkPos( + freshWorld.getChunkSource().randomState().sampler().findSpawnPosition() + ); + + setRandomSpawnSelection(spawnChunk); + + CompletableFuture initFuture = new CompletableFuture<>(); + + Bukkit.getServer().getRegionScheduler().run( + WorldEditPlugin.getInstance(), + freshWorld.getWorld(), + spawnChunk.x, spawnChunk.z, + task -> { + try { + console.initWorld(freshWorld, worldData, worldData, worldData.worldGenOptions()); + initFuture.complete(true); + } catch (Exception e) { + initFuture.completeExceptionally(e); + } + } + ); + + return initFuture.get(); + } + + private void setRandomSpawnSelection(ChunkPos spawnChunk) { + try { + Field randomSpawnField = ServerLevel.class.getDeclaredField("randomSpawnSelection"); + randomSpawnField.setAccessible(true); + randomSpawnField.set(freshWorld, spawnChunk); + } catch (ReflectiveOperationException e) { + throw new RuntimeException("Failed to set randomSpawnSelection for Folia world initialization", e); + } + } + @Override protected void cleanup() { try { diff --git a/worldedit-bukkit/adapters/adapter-1_21_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_5/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_21_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_5/PaperweightFaweAdapter.java index d69c0899fa..fa0d44d9b3 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_5/PaperweightFaweAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_21_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_5/PaperweightFaweAdapter.java @@ -10,6 +10,7 @@ import com.fastasyncworldedit.core.queue.IBatchProcessor; import com.fastasyncworldedit.core.queue.IChunkGet; import com.fastasyncworldedit.core.queue.implementation.packet.ChunkPacket; +import com.fastasyncworldedit.core.util.FoliaUtil; import com.fastasyncworldedit.core.util.NbtUtils; import com.fastasyncworldedit.core.util.TaskManager; import com.google.common.base.Preconditions; @@ -21,6 +22,7 @@ import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.bukkit.WorldEditPlugin; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; import com.sk89q.worldedit.bukkit.adapter.ext.fawe.v1_21_5.PaperweightAdapter; import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_21_5.regen.PaperweightRegen; @@ -54,6 +56,7 @@ import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.registry.BlockMaterial; import io.papermc.lib.PaperLib; +import io.papermc.paper.threadedregions.scheduler.ScheduledTask; import net.minecraft.core.BlockPos; import net.minecraft.core.Registry; import net.minecraft.core.RegistryAccess; @@ -129,6 +132,8 @@ import java.util.Objects; import java.util.OptionalInt; import java.util.Set; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -342,24 +347,45 @@ public Set getSupportedSideEffects() { public BaseEntity getEntity(org.bukkit.entity.Entity entity) { Preconditions.checkNotNull(entity); - CraftEntity craftEntity = ((CraftEntity) entity); - Entity mcEntity = craftEntity.getHandle(); - - String id = getEntityId(mcEntity); + String id = entity.getType().getKey().toString(); EntityType type = com.sk89q.worldedit.world.entity.EntityTypes.get(id); Supplier saveTag = () -> { - final net.minecraft.nbt.CompoundTag minecraftTag = new net.minecraft.nbt.CompoundTag(); - if (!readEntityIntoTag(mcEntity, minecraftTag)) { + net.minecraft.nbt.CompoundTag minecraftTag = new net.minecraft.nbt.CompoundTag(); + Entity mcEntity; + + if (FoliaUtil.isFoliaServer()) { + CompletableFuture handleFuture = new CompletableFuture<>(); + entity.getScheduler().run( + WorldEditPlugin.getInstance(), + (ScheduledTask task) -> { + try { + handleFuture.complete(((CraftEntity) entity).getHandle()); + } catch (Throwable t) { + handleFuture.completeExceptionally(t); + } + }, + () -> handleFuture.completeExceptionally(new CancellationException("Entity scheduler task cancelled")) + ); + try { + mcEntity = handleFuture.join(); + } catch (Throwable t) { + LOGGER.error("Failed to safely get NMS handle for {}", id, t); + return null; + } + } else { + mcEntity = ((CraftEntity) entity).getHandle(); + } + + if (mcEntity == null || !readEntityIntoTag(mcEntity, minecraftTag)) { return null; } - //add Id for AbstractChangeSet to work - final LinCompoundTag tag = (LinCompoundTag) toNativeLin(minecraftTag); - final Map> tags = NbtUtils.getLinCompoundTagValues(tag); + + LinCompoundTag tag = (LinCompoundTag) toNativeLin(minecraftTag); + Map> tags = NbtUtils.getLinCompoundTagValues(tag); tags.put("Id", LinStringTag.of(id)); return LinCompoundTag.of(tags); }; return new LazyBaseEntity(type, saveTag); - } @Override @@ -546,20 +572,62 @@ public org.bukkit.inventory.ItemStack adapt(BaseItemStack baseItemStack) { @Override protected void preCaptureStates(final ServerLevel serverLevel) { - serverLevel.captureTreeGeneration = true; - serverLevel.captureBlockStates = true; + try { + Field captureTreeField = ServerLevel.class.getDeclaredField("captureTreeGeneration"); + captureTreeField.setAccessible(true); + captureTreeField.setBoolean(serverLevel, true); + } catch (NoSuchFieldException | IllegalAccessException e) { + // Unable to read captureTreeGeneration field + } + try { + Field captureBlockField = ServerLevel.class.getDeclaredField("captureBlockStates"); + captureBlockField.setAccessible(true); + captureBlockField.setBoolean(serverLevel, true); + } catch (NoSuchFieldException | IllegalAccessException e) { + // Unable to read captureTreeGeneration field + } } @Override protected List getCapturedBlockStatesCopy(final ServerLevel serverLevel) { - return new ArrayList<>(serverLevel.capturedBlockStates.values()); + try { + Field capturedStatesField = ServerLevel.class.getDeclaredField("capturedBlockStates"); + capturedStatesField.setAccessible(true); + @SuppressWarnings("unchecked") + Map capturedStates = (Map) capturedStatesField.get(serverLevel); + return capturedStates != null ? new ArrayList<>(capturedStates.values()) : new ArrayList<>(); + } catch (NoSuchFieldException | IllegalAccessException e) { + return new ArrayList<>(); + } } @Override protected void postCaptureBlockStates(final ServerLevel serverLevel) { - serverLevel.captureBlockStates = false; - serverLevel.captureTreeGeneration = false; - serverLevel.capturedBlockStates.clear(); + try { + Field captureBlockField = ServerLevel.class.getDeclaredField("captureBlockStates"); + captureBlockField.setAccessible(true); + captureBlockField.setBoolean(serverLevel, false); + } catch (NoSuchFieldException | IllegalAccessException e) { + // Unable to read captureTreeGeneration field + } + try { + Field captureTreeField = ServerLevel.class.getDeclaredField("captureTreeGeneration"); + captureTreeField.setAccessible(true); + captureTreeField.setBoolean(serverLevel, false); + } catch (NoSuchFieldException | IllegalAccessException e) { + // Unable to read captureTreeGeneration field + } + try { + Field capturedStatesField = ServerLevel.class.getDeclaredField("capturedBlockStates"); + capturedStatesField.setAccessible(true); + @SuppressWarnings("unchecked") + Map capturedStates = (Map) capturedStatesField.get(serverLevel); + if (capturedStates != null) { + capturedStates.clear(); + } + } catch (NoSuchFieldException | IllegalAccessException e) { + // Unable to read captureTreeGeneration field + } } @Override diff --git a/worldedit-bukkit/adapters/adapter-1_21_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_5/PaperweightFaweWorldNativeAccess.java b/worldedit-bukkit/adapters/adapter-1_21_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_5/PaperweightFaweWorldNativeAccess.java index f5a49fbb67..604214ab26 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_5/PaperweightFaweWorldNativeAccess.java +++ b/worldedit-bukkit/adapters/adapter-1_21_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_5/PaperweightFaweWorldNativeAccess.java @@ -22,6 +22,7 @@ import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.redstone.ExperimentalRedstoneUtils; +import org.bukkit.Bukkit; import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.craftbukkit.block.data.CraftBlockData; import org.bukkit.event.block.BlockPhysicsEvent; @@ -60,7 +61,7 @@ public PaperweightFaweWorldNativeAccess(PaperweightFaweAdapter paperweightFaweAd this.level = level; // Use the actual tick as minecraft-defined so we don't try to force blocks into the world when the server's already lagging. // - With the caveat that we don't want to have too many cached changed (1024) so we'd flush those at 1024 anyway. - this.lastTick = new AtomicInteger(MinecraftServer.currentTick); + this.lastTick = new AtomicInteger(getCurrentTick()); } private Level getLevel() { @@ -96,7 +97,7 @@ public synchronized net.minecraft.world.level.block.state.BlockState setBlockSta LevelChunk levelChunk, BlockPos blockPos, net.minecraft.world.level.block.state.BlockState blockState ) { - int currentTick = MinecraftServer.currentTick; + int currentTick = getCurrentTick(); if (Fawe.isMainThread()) { return levelChunk.setBlockState(blockPos, blockState, this.sideEffectSet.shouldApply(SideEffect.UPDATE) ? 0 : 512 @@ -291,4 +292,16 @@ private record CachedChange( } + private int getCurrentTick() { + try { + return MinecraftServer.currentTick; + } catch (NoSuchFieldError e) { + try { + return Bukkit.getCurrentTick(); + } catch (Exception ex) { + return 0; + } + } + } + } diff --git a/worldedit-bukkit/adapters/adapter-1_21_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_5/PaperweightGetBlocks.java b/worldedit-bukkit/adapters/adapter-1_21_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_5/PaperweightGetBlocks.java index 82df9b11be..7fb79304de 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_5/PaperweightGetBlocks.java +++ b/worldedit-bukkit/adapters/adapter-1_21_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_5/PaperweightGetBlocks.java @@ -11,6 +11,7 @@ import com.fastasyncworldedit.core.math.IntPair; import com.fastasyncworldedit.core.nbt.FaweCompoundTag; import com.fastasyncworldedit.core.queue.IChunkSet; +import com.fastasyncworldedit.core.util.FoliaUtil; import com.fastasyncworldedit.core.util.MathMan; import com.fastasyncworldedit.core.util.NbtUtils; import com.fastasyncworldedit.core.util.collection.AdaptedMap; @@ -58,6 +59,8 @@ import net.minecraft.world.level.levelgen.Heightmap; import net.minecraft.world.level.lighting.LevelLightEngine; import org.apache.logging.log4j.Logger; +import org.bukkit.Bukkit; +import org.bukkit.Location; import org.bukkit.World; import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.craftbukkit.block.CraftBlock; @@ -196,23 +199,30 @@ public void removeSectionLighting(int layer, boolean sky) { @Override public FaweCompoundTag tile(final int x, final int y, final int z) { - BlockEntity blockEntity = getChunk().getBlockEntity(new BlockPos((x & 15) + ( - chunkX << 4), y, (z & 15) + ( - chunkZ << 4))); - if (blockEntity == null) { + try { + BlockEntity blockEntity = getChunk().getBlockEntity(new BlockPos((x & 15) + ( + chunkX << 4), y, (z & 15) + ( + chunkZ << 4))); + if (blockEntity == null) { + return null; + } + return NMS_TO_TILE.apply(blockEntity); + } catch (NullPointerException e) { return null; } - return NMS_TO_TILE.apply(blockEntity); - } @Override public Map tiles() { - Map nmsTiles = getChunk().getBlockEntities(); - if (nmsTiles.isEmpty()) { + try { + Map nmsTiles = getChunk().getBlockEntities(); + if (nmsTiles.isEmpty()) { + return Collections.emptyMap(); + } + return AdaptedMap.immutable(nmsTiles, posNms2We, NMS_TO_TILE); + } catch (NullPointerException e) { return Collections.emptyMap(); } - return AdaptedMap.immutable(nmsTiles, posNms2We, NMS_TO_TILE); } @Override @@ -319,7 +329,20 @@ public Set getFullEntities() { } private void removeEntity(Entity entity) { - entity.discard(); + if (FoliaUtil.isFoliaServer()) { + entity.getBukkitEntity().getScheduler().execute( + WorldEditPlugin.getInstance(), + () -> { + if (!entity.isRemoved()) { + entity.discard(); + } + }, + null, + 1L + ); + } else if (!entity.isRemoved()) { + entity.discard(); + } } @Override @@ -339,7 +362,7 @@ protected > T internalCall( if (createCopy) { if (copies.containsKey(copyKey)) { throw new IllegalStateException("Copy key already used."); - } + } copies.put(copyKey, copy); } // Remove existing tiles. Create a copy so that we can remove blocks @@ -347,11 +370,11 @@ protected > T internalCall( 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; + BlockPos pos = entry.getKey(); + int lx = pos.getX() & 15; + int ly = pos.getY(); + int lz = pos.getZ() & 15; + int layer = ly >> 4; if (!set.hasSection(layer)) { continue; } @@ -359,12 +382,26 @@ protected > T internalCall( int ordinal = set.getBlock(lx, ly, lz).getOrdinal(); if (ordinal != BlockTypesCache.ReservedIDs.__RESERVED__) { BlockEntity tile = entry.getValue(); - if (PaperLib.isPaper() && tile instanceof BeaconBlockEntity) { + if (PaperLib.isPaper() && tile instanceof BeaconBlockEntity beacon) { if (beacons == null) { beacons = new ArrayList<>(); } - beacons.add(tile); - PaperweightPlatformAdapter.removeBeacon(tile, nmsChunk); + beacons.add(beacon); + Location location = new Location( + nmsWorld.getWorld(), + beacon.getBlockPos().getX(), + beacon.getBlockPos().getY(), + beacon.getBlockPos().getZ() + ); + if (FoliaUtil.isFoliaServer()) { + Bukkit.getServer().getRegionScheduler().execute( + WorldEditPlugin.getInstance(), + location, + () -> PaperweightPlatformAdapter.removeBeacon(beacon, nmsChunk) + ); + } else { + PaperweightPlatformAdapter.removeBeacon(beacon, nmsChunk); + } continue; } nmsChunk.removeBlockEntity(tile.getBlockPos()); @@ -374,7 +411,7 @@ protected > T internalCall( } } } - final BiomeType[][] biomes = set.getBiomes(); + BiomeType[][] biomes = set.getBiomes(); int bitMask = 0; synchronized (nmsChunk) { @@ -593,7 +630,7 @@ protected > T internalCall( // 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; + List finalBeacons = beacons; syncTasks.add(() -> { for (BlockEntity beacon : finalBeacons) { BeaconBlockEntity.playSound(beacon.getLevel(), beacon.getBlockPos(), SoundEvents.BEACON_DEACTIVATE); @@ -606,7 +643,7 @@ protected > T internalCall( if (entityRemoves != null && !entityRemoves.isEmpty()) { syncTasks.add(() -> { Set entitiesRemoved = new HashSet<>(); - final List entities = PaperweightPlatformAdapter.getEntities(nmsChunk); + List entities = PaperweightPlatformAdapter.getEntities(nmsChunk); for (Entity entity : entities) { UUID uuid = entity.getUUID(); @@ -638,44 +675,60 @@ protected > T internalCall( syncTasks.add(() -> { 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()); + FaweCompoundTag nativeTag = iterator.next(); + LinCompoundTag linTag = nativeTag.linTag(); + LinStringTag idTag = linTag.findTag("Id", LinTagType.stringTag()); + LinListTag posTag = linTag.findListTag("Pos", LinTagType.doubleTag()); + 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(); + double x = posTag.get(0).valueAsDouble(); + double y = posTag.get(1).valueAsDouble(); + double z = posTag.get(2).valueAsDouble(); + float yaw = rotTag.get(0).valueAsFloat(); + float pitch = rotTag.get(1).valueAsFloat(); + 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) { + CompoundTag tag = (CompoundTag) adapter.fromNativeLin(linTag); + for (String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) { tag.remove(name); } entity.load(tag); entity.absSnapTo(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(); + Location location = new Location(nmsWorld.getWorld(), x, y, z); + if (FoliaUtil.isFoliaServer()) { + Bukkit.getServer().getRegionScheduler().execute(WorldEditPlugin.getInstance(), location, () -> { + 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 + ); + iterator.remove(); + } + }); + } else { + 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 + ); + iterator.remove(); + } } } } @@ -687,27 +740,37 @@ protected > T internalCall( Map tiles = set.tiles(); if (tiles != null && !tiles.isEmpty()) { syncTasks.add(() -> { - 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 (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()); + World world = nmsWorld.getWorld(); + for (Map.Entry entry : tiles.entrySet()) { + FaweCompoundTag nativeTag = entry.getValue(); + BlockVector3 blockHash = entry.getKey(); + int x = blockHash.x() + bx; + int y = blockHash.y(); + int z = blockHash.z() + bz; + BlockPos pos = new BlockPos(x, y, z); + Location location = new Location(world, x, y, z); + + Runnable setTile = () -> { + synchronized (nmsChunk) { + BlockEntity tileEntity = nmsChunk.getBlockEntity(pos); + if (tileEntity == null || tileEntity.isRemoved()) { + nmsChunk.removeBlockEntity(pos); + tileEntity = nmsChunk.getBlockEntity(pos); + } + if (tileEntity != null) { + 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()); + } } + }; + + if (FoliaUtil.isFoliaServer()) { + Bukkit.getServer().getRegionScheduler().execute(WorldEditPlugin.getInstance(), location, setTile); + } else { + setTile.run(); } } }); diff --git a/worldedit-bukkit/adapters/adapter-1_21_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_5/PaperweightPlatformAdapter.java b/worldedit-bukkit/adapters/adapter-1_21_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_5/PaperweightPlatformAdapter.java index c0df010c53..ef62ab841c 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_5/PaperweightPlatformAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_21_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_5/PaperweightPlatformAdapter.java @@ -9,6 +9,7 @@ import com.fastasyncworldedit.core.FaweCache; import com.fastasyncworldedit.core.math.BitArrayUnstretched; import com.fastasyncworldedit.core.math.IntPair; +import com.fastasyncworldedit.core.util.FoliaUtil; import com.fastasyncworldedit.core.util.MathMan; import com.fastasyncworldedit.core.util.TaskManager; import com.sk89q.worldedit.bukkit.WorldEditPlugin; @@ -20,6 +21,7 @@ import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockTypesCache; import io.papermc.lib.PaperLib; +import io.papermc.paper.util.MCUtil; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; import net.minecraft.core.IdMap; @@ -301,10 +303,22 @@ private static LevelChunk toLevelChunk(Chunk chunk) { } private static void addTicket(ServerLevel serverLevel, int chunkX, int chunkZ) { - // Ensure chunk is definitely loaded before applying a ticket - io.papermc.paper.util.MCUtil.MAIN_EXECUTOR.execute(() -> serverLevel - .getChunkSource() - .addTicketWithRadius(ChunkHolderManager.UNLOAD_COOLDOWN, new ChunkPos(chunkX, chunkZ), 0)); + ChunkPos pos = new ChunkPos(chunkX, chunkZ); + if (FoliaUtil.isFoliaServer()) { + try { + serverLevel.getChunkSource().addTicketWithRadius(ChunkHolderManager.UNLOAD_COOLDOWN, pos, 0); + } catch (Exception ignored) { + } + return; + } + try { + MCUtil.MAIN_EXECUTOR.execute(() -> serverLevel.getChunkSource().addTicketWithRadius(ChunkHolderManager.UNLOAD_COOLDOWN, pos, 0)); + } catch (Exception e) { + try { + serverLevel.getChunkSource().addTicketWithRadius(ChunkHolderManager.UNLOAD_COOLDOWN, pos, 0); + } catch (Exception ignored) { + } + } } public static ChunkHolder getPlayerChunk(ServerLevel nmsWorld, final int chunkX, final int chunkZ) { @@ -322,13 +336,9 @@ public static void sendChunk(IntPair pair, ServerLevel nmsWorld, int chunkX, int if (chunkHolder == null) { return; } - LevelChunk levelChunk; - if (PaperLib.isPaper()) { - // getChunkAtIfLoadedImmediately is paper only - levelChunk = nmsWorld.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ); - } else { - levelChunk = chunkHolder.getTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK).orElse(null); - } + LevelChunk levelChunk = PaperLib.isPaper() + ? nmsWorld.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ) + : chunkHolder.getTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK).orElse(null); if (levelChunk == null) { return; } @@ -337,32 +347,37 @@ public static void sendChunk(IntPair pair, ServerLevel nmsWorld, int chunkX, int if (lockHolder.chunkLock == null) { return; } - MinecraftServer.getServer().execute(() -> { + + ChunkPos pos = levelChunk.getPos(); + ClientboundLevelChunkWithLightPacket packet = PaperLib.isPaper() + ? new ClientboundLevelChunkWithLightPacket(levelChunk, nmsWorld.getLightEngine(), null, null, false) + : new ClientboundLevelChunkWithLightPacket(levelChunk, nmsWorld.getLightEngine(), null, null); + + Runnable sendPacket = () -> nearbyPlayers(nmsWorld, pos).forEach(p -> p.connection.send(packet)); + + if (FoliaUtil.isFoliaServer()) { try { - ChunkPos pos = levelChunk.getPos(); - ClientboundLevelChunkWithLightPacket packet; - if (PaperLib.isPaper()) { - packet = new ClientboundLevelChunkWithLightPacket( - levelChunk, - nmsWorld.getLightEngine(), - null, - null, - false // last false is to not bother with x-ray - ); - } else { - // deprecated on paper - deprecation suppressed - packet = new ClientboundLevelChunkWithLightPacket( - levelChunk, - nmsWorld.getLightEngine(), - null, - null - ); - } - nearbyPlayers(nmsWorld, pos).forEach(p -> p.connection.send(packet)); + sendPacket.run(); } finally { NMSAdapter.endChunkPacketSend(nmsWorld.getWorld().getName(), pair, lockHolder); } - }); + } else { + try { + MinecraftServer.getServer().execute(() -> { + try { + sendPacket.run(); + } finally { + NMSAdapter.endChunkPacketSend(nmsWorld.getWorld().getName(), pair, lockHolder); + } + }); + } catch (Exception e) { + try { + sendPacket.run(); + } finally { + NMSAdapter.endChunkPacketSend(nmsWorld.getWorld().getName(), pair, lockHolder); + } + } + } } private static List nearbyPlayers(ServerLevel serverLevel, ChunkPos coordIntPair) { @@ -624,8 +639,9 @@ public static BiomeType adapt(Holder biome, LevelAccessor levelAccessor) static void removeBeacon(BlockEntity beacon, LevelChunk levelChunk) { try { + Map blockEntities = levelChunk.getBlockEntities(); if (levelChunk.loaded || levelChunk.level.isClientSide()) { - BlockEntity blockEntity = levelChunk.blockEntities.remove(beacon.getBlockPos()); + BlockEntity blockEntity = blockEntities.remove(beacon.getBlockPos()); if (blockEntity != null) { if (!levelChunk.level.isClientSide) { methodRemoveGameEventListener.invoke(levelChunk, beacon, levelChunk.level); diff --git a/worldedit-bukkit/adapters/adapter-1_21_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_5/regen/PaperweightRegen.java b/worldedit-bukkit/adapters/adapter-1_21_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_5/regen/PaperweightRegen.java index 62335772f1..e39eecc796 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_5/regen/PaperweightRegen.java +++ b/worldedit-bukkit/adapters/adapter-1_21_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_5/regen/PaperweightRegen.java @@ -5,6 +5,7 @@ import com.fastasyncworldedit.core.queue.IChunkCache; import com.fastasyncworldedit.core.queue.IChunkGet; import com.fastasyncworldedit.core.queue.implementation.chunk.ChunkCache; +import com.fastasyncworldedit.core.util.FoliaUtil; import com.google.common.collect.ImmutableList; import com.mojang.serialization.Lifecycle; import com.sk89q.worldedit.bukkit.BukkitAdapter; @@ -41,6 +42,8 @@ import java.nio.file.Path; import java.util.Map; import java.util.OptionalLong; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.function.BooleanSupplier; import java.util.function.Supplier; @@ -206,9 +209,52 @@ public void save( if (paperConfigField != null) { paperConfigField.set(freshWorld, originalServerWorld.paperConfig()); } + + if (FoliaUtil.isFoliaServer()) { + return initWorldForFolia(newWorldData); + } + return true; } + private boolean initWorldForFolia(PrimaryLevelData worldData) throws ExecutionException, InterruptedException { + MinecraftServer console = ((CraftServer) Bukkit.getServer()).getServer(); + + ChunkPos spawnChunk = new ChunkPos( + freshWorld.getChunkSource().randomState().sampler().findSpawnPosition() + ); + + setRandomSpawnSelection(spawnChunk); + + CompletableFuture initFuture = new CompletableFuture<>(); + + Bukkit.getServer().getRegionScheduler().run( + WorldEditPlugin.getInstance(), + freshWorld.getWorld(), + spawnChunk.x, spawnChunk.z, + task -> { + try { + console.initWorld(freshWorld, worldData, worldData, worldData.worldGenOptions()); + initFuture.complete(true); + } catch (Exception e) { + initFuture.completeExceptionally(e); + } + } + ); + + return initFuture.get(); + } + + private void setRandomSpawnSelection(ChunkPos spawnChunk) { + try { + Field randomSpawnField = ServerLevel.class.getDeclaredField("randomSpawnSelection"); + randomSpawnField.setAccessible(true); + randomSpawnField.set(freshWorld, spawnChunk); + } catch (ReflectiveOperationException e) { + throw new RuntimeException("Failed to set randomSpawnSelection for Folia world initialization", e); + } + } + @Override protected void cleanup() { try { diff --git a/worldedit-bukkit/adapters/adapter-1_21_6/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_6/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_21_6/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_6/PaperweightFaweAdapter.java index f347d27cb6..cd858a11ba 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_6/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_6/PaperweightFaweAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_21_6/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_6/PaperweightFaweAdapter.java @@ -10,6 +10,7 @@ import com.fastasyncworldedit.core.queue.IBatchProcessor; import com.fastasyncworldedit.core.queue.IChunkGet; import com.fastasyncworldedit.core.queue.implementation.packet.ChunkPacket; +import com.fastasyncworldedit.core.util.FoliaUtil; import com.fastasyncworldedit.core.util.NbtUtils; import com.fastasyncworldedit.core.util.TaskManager; import com.google.common.base.Preconditions; @@ -21,6 +22,7 @@ import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.bukkit.WorldEditPlugin; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; import com.sk89q.worldedit.bukkit.adapter.ext.fawe.v1_21_6.PaperweightAdapter; import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_21_6.regen.PaperweightRegen; @@ -54,6 +56,7 @@ import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.registry.BlockMaterial; import io.papermc.lib.PaperLib; +import io.papermc.paper.threadedregions.scheduler.ScheduledTask; import net.minecraft.core.BlockPos; import net.minecraft.core.Registry; import net.minecraft.core.RegistryAccess; @@ -131,6 +134,8 @@ import java.util.Objects; import java.util.OptionalInt; import java.util.Set; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -353,24 +358,45 @@ public Set getSupportedSideEffects() { public BaseEntity getEntity(org.bukkit.entity.Entity entity) { Preconditions.checkNotNull(entity); - CraftEntity craftEntity = ((CraftEntity) entity); - Entity mcEntity = craftEntity.getHandle(); - - String id = getEntityId(mcEntity); + String id = entity.getType().getKey().toString(); EntityType type = com.sk89q.worldedit.world.entity.EntityTypes.get(id); Supplier saveTag = () -> { - final net.minecraft.nbt.CompoundTag minecraftTag = new net.minecraft.nbt.CompoundTag(); - if (!readEntityIntoTag(mcEntity, minecraftTag)) { + net.minecraft.nbt.CompoundTag minecraftTag = new net.minecraft.nbt.CompoundTag(); + Entity mcEntity; + + if (FoliaUtil.isFoliaServer()) { + CompletableFuture handleFuture = new CompletableFuture<>(); + entity.getScheduler().run( + WorldEditPlugin.getInstance(), + (ScheduledTask task) -> { + try { + handleFuture.complete(((CraftEntity) entity).getHandle()); + } catch (Throwable t) { + handleFuture.completeExceptionally(t); + } + }, + () -> handleFuture.completeExceptionally(new CancellationException("Entity scheduler task cancelled")) + ); + try { + mcEntity = handleFuture.join(); + } catch (Throwable t) { + LOGGER.error("Failed to safely get NMS handle for {}", id, t); + return null; + } + } else { + mcEntity = ((CraftEntity) entity).getHandle(); + } + + if (mcEntity == null || !readEntityIntoTag(mcEntity, minecraftTag)) { return null; } - //add Id for AbstractChangeSet to work - final LinCompoundTag tag = (LinCompoundTag) toNativeLin(minecraftTag); - final Map> tags = NbtUtils.getLinCompoundTagValues(tag); + + LinCompoundTag tag = (LinCompoundTag) toNativeLin(minecraftTag); + Map> tags = NbtUtils.getLinCompoundTagValues(tag); tags.put("Id", LinStringTag.of(id)); return LinCompoundTag.of(tags); }; return new LazyBaseEntity(type, saveTag); - } @Override @@ -557,20 +583,62 @@ public org.bukkit.inventory.ItemStack adapt(BaseItemStack baseItemStack) { @Override protected void preCaptureStates(final ServerLevel serverLevel) { - serverLevel.captureTreeGeneration = true; - serverLevel.captureBlockStates = true; + try { + Field captureTreeField = ServerLevel.class.getDeclaredField("captureTreeGeneration"); + captureTreeField.setAccessible(true); + captureTreeField.setBoolean(serverLevel, true); + } catch (NoSuchFieldException | IllegalAccessException e) { + // Unable to read captureTreeGeneration field + } + try { + Field captureBlockField = ServerLevel.class.getDeclaredField("captureBlockStates"); + captureBlockField.setAccessible(true); + captureBlockField.setBoolean(serverLevel, true); + } catch (NoSuchFieldException | IllegalAccessException e) { + // Unable to read captureTreeGeneration field + } } @Override protected List getCapturedBlockStatesCopy(final ServerLevel serverLevel) { - return new ArrayList<>(serverLevel.capturedBlockStates.values()); + try { + Field capturedStatesField = ServerLevel.class.getDeclaredField("capturedBlockStates"); + capturedStatesField.setAccessible(true); + @SuppressWarnings("unchecked") + Map capturedStates = (Map) capturedStatesField.get(serverLevel); + return capturedStates != null ? new ArrayList<>(capturedStates.values()) : new ArrayList<>(); + } catch (NoSuchFieldException | IllegalAccessException e) { + return new ArrayList<>(); + } } @Override protected void postCaptureBlockStates(final ServerLevel serverLevel) { - serverLevel.captureBlockStates = false; - serverLevel.captureTreeGeneration = false; - serverLevel.capturedBlockStates.clear(); + try { + Field captureBlockField = ServerLevel.class.getDeclaredField("captureBlockStates"); + captureBlockField.setAccessible(true); + captureBlockField.setBoolean(serverLevel, false); + } catch (NoSuchFieldException | IllegalAccessException e) { + // Unable to read captureTreeGeneration field + } + try { + Field captureTreeField = ServerLevel.class.getDeclaredField("captureTreeGeneration"); + captureTreeField.setAccessible(true); + captureTreeField.setBoolean(serverLevel, false); + } catch (NoSuchFieldException | IllegalAccessException e) { + // Unable to read captureTreeGeneration field + } + try { + Field capturedStatesField = ServerLevel.class.getDeclaredField("capturedBlockStates"); + capturedStatesField.setAccessible(true); + @SuppressWarnings("unchecked") + Map capturedStates = (Map) capturedStatesField.get(serverLevel); + if (capturedStates != null) { + capturedStates.clear(); + } + } catch (NoSuchFieldException | IllegalAccessException e) { + // Unable to read captureTreeGeneration field + } } @Override @@ -762,7 +830,6 @@ public void setupFeatures() { } } - @Override protected ServerLevel getServerLevel(final World world) { return ((CraftWorld) world).getHandle(); diff --git a/worldedit-bukkit/adapters/adapter-1_21_6/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_6/PaperweightFaweWorldNativeAccess.java b/worldedit-bukkit/adapters/adapter-1_21_6/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_6/PaperweightFaweWorldNativeAccess.java index 36370d286f..eebf3b36fc 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_6/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_6/PaperweightFaweWorldNativeAccess.java +++ b/worldedit-bukkit/adapters/adapter-1_21_6/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_6/PaperweightFaweWorldNativeAccess.java @@ -22,6 +22,7 @@ import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.redstone.ExperimentalRedstoneUtils; import net.minecraft.world.level.storage.ValueInput; +import org.bukkit.Bukkit; import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.craftbukkit.block.data.CraftBlockData; import org.bukkit.event.block.BlockPhysicsEvent; @@ -62,7 +63,7 @@ public PaperweightFaweWorldNativeAccess(PaperweightFaweAdapter paperweightFaweAd this.level = level; // Use the actual tick as minecraft-defined so we don't try to force blocks into the world when the server's already lagging. // - With the caveat that we don't want to have too many cached changed (1024) so we'd flush those at 1024 anyway. - this.lastTick = new AtomicInteger(MinecraftServer.currentTick); + this.lastTick = new AtomicInteger(getCurrentTick()); } private Level getLevel() { @@ -98,7 +99,7 @@ public synchronized net.minecraft.world.level.block.state.BlockState setBlockSta LevelChunk levelChunk, BlockPos blockPos, net.minecraft.world.level.block.state.BlockState blockState ) { - int currentTick = MinecraftServer.currentTick; + int currentTick = getCurrentTick(); if (Fawe.isMainThread()) { return levelChunk.setBlockState( blockPos, blockState, @@ -309,4 +310,16 @@ private record CachedChange( } + private int getCurrentTick() { + try { + return MinecraftServer.currentTick; + } catch (NoSuchFieldError e) { + try { + return Bukkit.getCurrentTick(); + } catch (Exception ex) { + return 0; + } + } + } + } diff --git a/worldedit-bukkit/adapters/adapter-1_21_6/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_6/PaperweightGetBlocks.java b/worldedit-bukkit/adapters/adapter-1_21_6/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_6/PaperweightGetBlocks.java index 8146620d38..910356ed77 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_6/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_6/PaperweightGetBlocks.java +++ b/worldedit-bukkit/adapters/adapter-1_21_6/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_6/PaperweightGetBlocks.java @@ -11,6 +11,7 @@ import com.fastasyncworldedit.core.math.IntPair; import com.fastasyncworldedit.core.nbt.FaweCompoundTag; import com.fastasyncworldedit.core.queue.IChunkSet; +import com.fastasyncworldedit.core.util.FoliaUtil; import com.fastasyncworldedit.core.util.MathMan; import com.fastasyncworldedit.core.util.NbtUtils; import com.fastasyncworldedit.core.util.collection.AdaptedMap; @@ -59,6 +60,8 @@ import net.minecraft.world.level.storage.TagValueOutput; import net.minecraft.world.level.storage.ValueInput; import org.apache.logging.log4j.Logger; +import org.bukkit.Bukkit; +import org.bukkit.Location; import org.bukkit.World; import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.craftbukkit.block.CraftBlock; @@ -199,23 +202,30 @@ public void removeSectionLighting(int layer, boolean sky) { @Override public FaweCompoundTag tile(final int x, final int y, final int z) { - BlockEntity blockEntity = getChunk().getBlockEntity(new BlockPos((x & 15) + ( - chunkX << 4), y, (z & 15) + ( - chunkZ << 4))); - if (blockEntity == null) { + try { + BlockEntity blockEntity = getChunk().getBlockEntity(new BlockPos((x & 15) + ( + chunkX << 4), y, (z & 15) + ( + chunkZ << 4))); + if (blockEntity == null) { + return null; + } + return NMS_TO_TILE.apply(blockEntity); + } catch (NullPointerException e) { return null; } - return NMS_TO_TILE.apply(blockEntity); - } @Override public Map tiles() { - Map nmsTiles = getChunk().getBlockEntities(); - if (nmsTiles.isEmpty()) { + try { + Map nmsTiles = getChunk().getBlockEntities(); + if (nmsTiles.isEmpty()) { + return Collections.emptyMap(); + } + return AdaptedMap.immutable(nmsTiles, posNms2We, NMS_TO_TILE); + } catch (NullPointerException e) { return Collections.emptyMap(); } - return AdaptedMap.immutable(nmsTiles, posNms2We, NMS_TO_TILE); } @Override @@ -323,7 +333,20 @@ public Set getFullEntities() { } private void removeEntity(Entity entity) { - entity.discard(); + if (FoliaUtil.isFoliaServer()) { + entity.getBukkitEntity().getScheduler().execute( + WorldEditPlugin.getInstance(), + () -> { + if (!entity.isRemoved()) { + entity.discard(); + } + }, + null, + 1L + ); + } else if (!entity.isRemoved()) { + entity.discard(); + } } @Override @@ -343,7 +366,7 @@ protected > T internalCall( if (createCopy) { if (copies.containsKey(copyKey)) { throw new IllegalStateException("Copy key already used."); - } + } copies.put(copyKey, copy); } // Remove existing tiles. Create a copy so that we can remove blocks @@ -351,11 +374,11 @@ protected > T internalCall( 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; + BlockPos pos = entry.getKey(); + int lx = pos.getX() & 15; + int ly = pos.getY(); + int lz = pos.getZ() & 15; + int layer = ly >> 4; if (!set.hasSection(layer)) { continue; } @@ -363,12 +386,26 @@ protected > T internalCall( int ordinal = set.getBlock(lx, ly, lz).getOrdinal(); if (ordinal != BlockTypesCache.ReservedIDs.__RESERVED__) { BlockEntity tile = entry.getValue(); - if (PaperLib.isPaper() && tile instanceof BeaconBlockEntity) { + if (PaperLib.isPaper() && tile instanceof BeaconBlockEntity beacon) { if (beacons == null) { beacons = new ArrayList<>(); } - beacons.add(tile); - PaperweightPlatformAdapter.removeBeacon(tile, nmsChunk); + beacons.add(beacon); + Location location = new Location( + nmsWorld.getWorld(), + beacon.getBlockPos().getX(), + beacon.getBlockPos().getY(), + beacon.getBlockPos().getZ() + ); + if (FoliaUtil.isFoliaServer()) { + Bukkit.getServer().getRegionScheduler().execute( + WorldEditPlugin.getInstance(), + location, + () -> PaperweightPlatformAdapter.removeBeacon(beacon, nmsChunk) + ); + } else { + PaperweightPlatformAdapter.removeBeacon(beacon, nmsChunk); + } continue; } nmsChunk.removeBlockEntity(tile.getBlockPos()); @@ -378,7 +415,7 @@ protected > T internalCall( } } } - final BiomeType[][] biomes = set.getBiomes(); + BiomeType[][] biomes = set.getBiomes(); int bitMask = 0; synchronized (nmsChunk) { @@ -597,7 +634,7 @@ protected > T internalCall( // 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; + List finalBeacons = beacons; syncTasks.add(() -> { for (BlockEntity beacon : finalBeacons) { BeaconBlockEntity.playSound(beacon.getLevel(), beacon.getBlockPos(), SoundEvents.BEACON_DEACTIVATE); @@ -610,7 +647,7 @@ protected > T internalCall( if (entityRemoves != null && !entityRemoves.isEmpty()) { syncTasks.add(() -> { Set entitiesRemoved = new HashSet<>(); - final List entities = PaperweightPlatformAdapter.getEntities(nmsChunk); + List entities = PaperweightPlatformAdapter.getEntities(nmsChunk); for (Entity entity : entities) { UUID uuid = entity.getUUID(); @@ -642,28 +679,28 @@ protected > T internalCall( syncTasks.add(() -> { 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()); + FaweCompoundTag nativeTag = iterator.next(); + LinCompoundTag linTag = nativeTag.linTag(); + LinStringTag idTag = linTag.findTag("Id", LinTagType.stringTag()); + LinListTag posTag = linTag.findListTag("Pos", LinTagType.doubleTag()); + 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(); + double x = posTag.get(0).valueAsDouble(); + double y = posTag.get(1).valueAsDouble(); + double z = posTag.get(2).valueAsDouble(); + float yaw = rotTag.get(0).valueAsFloat(); + float pitch = rotTag.get(1).valueAsFloat(); + 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) { + CompoundTag tag = (CompoundTag) adapter.fromNativeLin(linTag); + for (String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) { tag.remove(name); } // TODO (VI/O) @@ -671,17 +708,33 @@ protected > T internalCall( entity.load(input); entity.absSnapTo(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(); + Location location = new Location(nmsWorld.getWorld(), x, y, z); + if (FoliaUtil.isFoliaServer()) { + Bukkit.getServer().getRegionScheduler().execute(WorldEditPlugin.getInstance(), location, () -> { + 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 + ); + iterator.remove(); + } + }); + } else { + 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 + ); + iterator.remove(); + } } } } @@ -693,29 +746,38 @@ protected > T internalCall( Map tiles = set.tiles(); if (tiles != null && !tiles.isEmpty()) { syncTasks.add(() -> { - 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 (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)); - // TODO (VI/O) - ValueInput input = createInput(tag); - tileEntity.loadWithComponents(input); + World world = nmsWorld.getWorld(); + for (Map.Entry entry : tiles.entrySet()) { + FaweCompoundTag nativeTag = entry.getValue(); + BlockVector3 blockHash = entry.getKey(); + int x = blockHash.x() + bx; + int y = blockHash.y(); + int z = blockHash.z() + bz; + BlockPos pos = new BlockPos(x, y, z); + Location location = new Location(world, x, y, z); + + Runnable setTile = () -> { + synchronized (nmsChunk) { + BlockEntity tileEntity = nmsChunk.getBlockEntity(pos); + if (tileEntity == null || tileEntity.isRemoved()) { + nmsChunk.removeBlockEntity(pos); + tileEntity = nmsChunk.getBlockEntity(pos); + } + if (tileEntity != null) { + 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)); + ValueInput input = createInput(tag); + tileEntity.loadWithComponents(input); + } } + }; + + if (FoliaUtil.isFoliaServer()) { + Bukkit.getServer().getRegionScheduler().execute(WorldEditPlugin.getInstance(), location, setTile); + } else { + setTile.run(); } } }); diff --git a/worldedit-bukkit/adapters/adapter-1_21_6/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_6/PaperweightPlatformAdapter.java b/worldedit-bukkit/adapters/adapter-1_21_6/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_6/PaperweightPlatformAdapter.java index 99864d45f7..79df825884 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_6/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_6/PaperweightPlatformAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_21_6/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_6/PaperweightPlatformAdapter.java @@ -9,6 +9,7 @@ import com.fastasyncworldedit.core.FaweCache; import com.fastasyncworldedit.core.math.BitArrayUnstretched; import com.fastasyncworldedit.core.math.IntPair; +import com.fastasyncworldedit.core.util.FoliaUtil; import com.fastasyncworldedit.core.util.MathMan; import com.fastasyncworldedit.core.util.TaskManager; import com.sk89q.worldedit.bukkit.WorldEditPlugin; @@ -20,6 +21,7 @@ import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockTypesCache; import io.papermc.lib.PaperLib; +import io.papermc.paper.util.MCUtil; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; import net.minecraft.core.IdMap; @@ -330,10 +332,22 @@ private static LevelChunk toLevelChunk(Chunk chunk) { } private static void addTicket(ServerLevel serverLevel, int chunkX, int chunkZ) { - // Ensure chunk is definitely loaded before applying a ticket - io.papermc.paper.util.MCUtil.MAIN_EXECUTOR.execute(() -> serverLevel - .getChunkSource() - .addTicketWithRadius(ChunkHolderManager.UNLOAD_COOLDOWN, new ChunkPos(chunkX, chunkZ), 0)); + ChunkPos pos = new ChunkPos(chunkX, chunkZ); + if (FoliaUtil.isFoliaServer()) { + try { + serverLevel.getChunkSource().addTicketWithRadius(ChunkHolderManager.UNLOAD_COOLDOWN, pos, 0); + } catch (Exception ignored) { + } + return; + } + try { + MCUtil.MAIN_EXECUTOR.execute(() -> serverLevel.getChunkSource().addTicketWithRadius(ChunkHolderManager.UNLOAD_COOLDOWN, pos, 0)); + } catch (Exception e) { + try { + serverLevel.getChunkSource().addTicketWithRadius(ChunkHolderManager.UNLOAD_COOLDOWN, pos, 0); + } catch (Exception ignored) { + } + } } public static ChunkHolder getPlayerChunk(ServerLevel nmsWorld, final int chunkX, final int chunkZ) { @@ -351,13 +365,9 @@ public static void sendChunk(IntPair pair, ServerLevel nmsWorld, int chunkX, int if (chunkHolder == null) { return; } - LevelChunk levelChunk; - if (PaperLib.isPaper()) { - // getChunkAtIfLoadedImmediately is paper only - levelChunk = nmsWorld.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ); - } else { - levelChunk = chunkHolder.getTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK).orElse(null); - } + LevelChunk levelChunk = PaperLib.isPaper() + ? nmsWorld.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ) + : chunkHolder.getTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK).orElse(null); if (levelChunk == null) { return; } @@ -366,32 +376,37 @@ public static void sendChunk(IntPair pair, ServerLevel nmsWorld, int chunkX, int if (lockHolder.chunkLock == null) { return; } - MinecraftServer.getServer().execute(() -> { + + ChunkPos pos = levelChunk.getPos(); + ClientboundLevelChunkWithLightPacket packet = PaperLib.isPaper() + ? new ClientboundLevelChunkWithLightPacket(levelChunk, nmsWorld.getLightEngine(), null, null, false) + : new ClientboundLevelChunkWithLightPacket(levelChunk, nmsWorld.getLightEngine(), null, null); + + Runnable sendPacket = () -> nearbyPlayers(nmsWorld, pos).forEach(p -> p.connection.send(packet)); + + if (FoliaUtil.isFoliaServer()) { try { - ChunkPos pos = levelChunk.getPos(); - ClientboundLevelChunkWithLightPacket packet; - if (PaperLib.isPaper()) { - packet = new ClientboundLevelChunkWithLightPacket( - levelChunk, - nmsWorld.getLightEngine(), - null, - null, - false // last false is to not bother with x-ray - ); - } else { - // deprecated on paper - deprecation suppressed - packet = new ClientboundLevelChunkWithLightPacket( - levelChunk, - nmsWorld.getLightEngine(), - null, - null - ); - } - nearbyPlayers(nmsWorld, pos).forEach(p -> p.connection.send(packet)); + sendPacket.run(); } finally { NMSAdapter.endChunkPacketSend(nmsWorld.getWorld().getName(), pair, lockHolder); } - }); + } else { + try { + MinecraftServer.getServer().execute(() -> { + try { + sendPacket.run(); + } finally { + NMSAdapter.endChunkPacketSend(nmsWorld.getWorld().getName(), pair, lockHolder); + } + }); + } catch (Exception e) { + try { + sendPacket.run(); + } finally { + NMSAdapter.endChunkPacketSend(nmsWorld.getWorld().getName(), pair, lockHolder); + } + } + } } private static List nearbyPlayers(ServerLevel serverLevel, ChunkPos coordIntPair) { @@ -653,8 +668,9 @@ public static BiomeType adapt(Holder biome, LevelAccessor levelAccessor) static void removeBeacon(BlockEntity beacon, LevelChunk levelChunk) { try { + Map blockEntities = levelChunk.getBlockEntities(); if (levelChunk.loaded || levelChunk.level.isClientSide()) { - BlockEntity blockEntity = levelChunk.blockEntities.remove(beacon.getBlockPos()); + BlockEntity blockEntity = blockEntities.remove(beacon.getBlockPos()); if (blockEntity != null) { if (!levelChunk.level.isClientSide) { methodRemoveGameEventListener.invoke(levelChunk, beacon, levelChunk.level); diff --git a/worldedit-bukkit/adapters/adapter-1_21_6/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_6/regen/PaperweightRegen.java b/worldedit-bukkit/adapters/adapter-1_21_6/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_6/regen/PaperweightRegen.java index a2efec8865..589677a4e4 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_6/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_6/regen/PaperweightRegen.java +++ b/worldedit-bukkit/adapters/adapter-1_21_6/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_6/regen/PaperweightRegen.java @@ -5,6 +5,7 @@ import com.fastasyncworldedit.core.queue.IChunkCache; import com.fastasyncworldedit.core.queue.IChunkGet; import com.fastasyncworldedit.core.queue.implementation.chunk.ChunkCache; +import com.fastasyncworldedit.core.util.FoliaUtil; import com.google.common.collect.ImmutableList; import com.mojang.serialization.Lifecycle; import com.sk89q.worldedit.bukkit.BukkitAdapter; @@ -41,6 +42,8 @@ import java.nio.file.Path; import java.util.Map; import java.util.OptionalLong; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.function.BooleanSupplier; import java.util.function.Supplier; @@ -206,9 +209,52 @@ public void save( if (paperConfigField != null) { paperConfigField.set(freshWorld, originalServerWorld.paperConfig()); } + + if (FoliaUtil.isFoliaServer()) { + return initWorldForFolia(newWorldData); + } + return true; } + private boolean initWorldForFolia(PrimaryLevelData worldData) throws ExecutionException, InterruptedException { + MinecraftServer console = ((CraftServer) Bukkit.getServer()).getServer(); + + ChunkPos spawnChunk = new ChunkPos( + freshWorld.getChunkSource().randomState().sampler().findSpawnPosition() + ); + + setRandomSpawnSelection(spawnChunk); + + CompletableFuture initFuture = new CompletableFuture<>(); + + Bukkit.getServer().getRegionScheduler().run( + WorldEditPlugin.getInstance(), + freshWorld.getWorld(), + spawnChunk.x, spawnChunk.z, + task -> { + try { + console.initWorld(freshWorld, worldData, worldData, worldData.worldGenOptions()); + initFuture.complete(true); + } catch (Exception e) { + initFuture.completeExceptionally(e); + } + } + ); + + return initFuture.get(); + } + + private void setRandomSpawnSelection(ChunkPos spawnChunk) { + try { + Field randomSpawnField = ServerLevel.class.getDeclaredField("randomSpawnSelection"); + randomSpawnField.setAccessible(true); + randomSpawnField.set(freshWorld, spawnChunk); + } catch (ReflectiveOperationException e) { + throw new RuntimeException("Failed to set randomSpawnSelection for Folia world initialization", e); + } + } + @Override protected void cleanup() { try { diff --git a/worldedit-bukkit/adapters/adapter-1_21_9/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_9/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_21_9/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_9/PaperweightFaweAdapter.java index 4fa880e95b..abe8d111ab 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_9/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_9/PaperweightFaweAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_21_9/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_9/PaperweightFaweAdapter.java @@ -10,6 +10,7 @@ import com.fastasyncworldedit.core.queue.IBatchProcessor; import com.fastasyncworldedit.core.queue.IChunkGet; import com.fastasyncworldedit.core.queue.implementation.packet.ChunkPacket; +import com.fastasyncworldedit.core.util.FoliaUtil; import com.fastasyncworldedit.core.util.NbtUtils; import com.fastasyncworldedit.core.util.TaskManager; import com.google.common.base.Preconditions; @@ -21,6 +22,7 @@ import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.bukkit.WorldEditPlugin; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; import com.sk89q.worldedit.bukkit.adapter.ext.fawe.v1_21_9.PaperweightAdapter; import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v1_21_9.regen.PaperweightRegen; @@ -54,6 +56,7 @@ import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.registry.BlockMaterial; import io.papermc.lib.PaperLib; +import io.papermc.paper.threadedregions.scheduler.ScheduledTask; import net.minecraft.core.BlockPos; import net.minecraft.core.Registry; import net.minecraft.core.RegistryAccess; @@ -131,6 +134,8 @@ import java.util.Objects; import java.util.OptionalInt; import java.util.Set; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -353,24 +358,45 @@ public Set getSupportedSideEffects() { public BaseEntity getEntity(org.bukkit.entity.Entity entity) { Preconditions.checkNotNull(entity); - CraftEntity craftEntity = ((CraftEntity) entity); - Entity mcEntity = craftEntity.getHandle(); - - String id = getEntityId(mcEntity); + String id = entity.getType().getKey().toString(); EntityType type = com.sk89q.worldedit.world.entity.EntityTypes.get(id); Supplier saveTag = () -> { - final net.minecraft.nbt.CompoundTag minecraftTag = new net.minecraft.nbt.CompoundTag(); - if (!readEntityIntoTag(mcEntity, minecraftTag)) { + net.minecraft.nbt.CompoundTag minecraftTag = new net.minecraft.nbt.CompoundTag(); + Entity mcEntity; + + if (FoliaUtil.isFoliaServer()) { + CompletableFuture handleFuture = new CompletableFuture<>(); + entity.getScheduler().run( + WorldEditPlugin.getInstance(), + (ScheduledTask task) -> { + try { + handleFuture.complete(((CraftEntity) entity).getHandle()); + } catch (Throwable t) { + handleFuture.completeExceptionally(t); + } + }, + () -> handleFuture.completeExceptionally(new CancellationException("Entity scheduler task cancelled")) + ); + try { + mcEntity = handleFuture.join(); + } catch (Throwable t) { + LOGGER.error("Failed to safely get NMS handle for {}", id, t); + return null; + } + } else { + mcEntity = ((CraftEntity) entity).getHandle(); + } + + if (mcEntity == null || !readEntityIntoTag(mcEntity, minecraftTag)) { return null; } - //add Id for AbstractChangeSet to work - final LinCompoundTag tag = (LinCompoundTag) toNativeLin(minecraftTag); - final Map> tags = NbtUtils.getLinCompoundTagValues(tag); + + LinCompoundTag tag = (LinCompoundTag) toNativeLin(minecraftTag); + Map> tags = NbtUtils.getLinCompoundTagValues(tag); tags.put("Id", LinStringTag.of(id)); return LinCompoundTag.of(tags); }; return new LazyBaseEntity(type, saveTag); - } @Override @@ -557,20 +583,62 @@ public org.bukkit.inventory.ItemStack adapt(BaseItemStack baseItemStack) { @Override protected void preCaptureStates(final ServerLevel serverLevel) { - serverLevel.captureTreeGeneration = true; - serverLevel.captureBlockStates = true; + try { + Field captureTreeField = ServerLevel.class.getDeclaredField("captureTreeGeneration"); + captureTreeField.setAccessible(true); + captureTreeField.setBoolean(serverLevel, true); + } catch (NoSuchFieldException | IllegalAccessException e) { + // Unable to read captureTreeGeneration field + } + try { + Field captureBlockField = ServerLevel.class.getDeclaredField("captureBlockStates"); + captureBlockField.setAccessible(true); + captureBlockField.setBoolean(serverLevel, true); + } catch (NoSuchFieldException | IllegalAccessException e) { + // Unable to read captureTreeGeneration field + } } @Override protected List getCapturedBlockStatesCopy(final ServerLevel serverLevel) { - return new ArrayList<>(serverLevel.capturedBlockStates.values()); + try { + Field capturedStatesField = ServerLevel.class.getDeclaredField("capturedBlockStates"); + capturedStatesField.setAccessible(true); + @SuppressWarnings("unchecked") + Map capturedStates = (Map) capturedStatesField.get(serverLevel); + return capturedStates != null ? new ArrayList<>(capturedStates.values()) : new ArrayList<>(); + } catch (NoSuchFieldException | IllegalAccessException e) { + return new ArrayList<>(); + } } @Override protected void postCaptureBlockStates(final ServerLevel serverLevel) { - serverLevel.captureBlockStates = false; - serverLevel.captureTreeGeneration = false; - serverLevel.capturedBlockStates.clear(); + try { + Field captureBlockField = ServerLevel.class.getDeclaredField("captureBlockStates"); + captureBlockField.setAccessible(true); + captureBlockField.setBoolean(serverLevel, false); + } catch (NoSuchFieldException | IllegalAccessException e) { + // Unable to read captureTreeGeneration field + } + try { + Field captureTreeField = ServerLevel.class.getDeclaredField("captureTreeGeneration"); + captureTreeField.setAccessible(true); + captureTreeField.setBoolean(serverLevel, false); + } catch (NoSuchFieldException | IllegalAccessException e) { + // Unable to read captureTreeGeneration field + } + try { + Field capturedStatesField = ServerLevel.class.getDeclaredField("capturedBlockStates"); + capturedStatesField.setAccessible(true); + @SuppressWarnings("unchecked") + Map capturedStates = (Map) capturedStatesField.get(serverLevel); + if (capturedStates != null) { + capturedStates.clear(); + } + } catch (NoSuchFieldException | IllegalAccessException e) { + // Unable to read captureTreeGeneration field + } } @Override public boolean generateFeature(ConfiguredFeatureType feature, World world, EditSession editSession, BlockVector3 pt) { diff --git a/worldedit-bukkit/adapters/adapter-1_21_9/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_9/PaperweightFaweWorldNativeAccess.java b/worldedit-bukkit/adapters/adapter-1_21_9/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_9/PaperweightFaweWorldNativeAccess.java index e778eecae8..47148da7c0 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_9/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_9/PaperweightFaweWorldNativeAccess.java +++ b/worldedit-bukkit/adapters/adapter-1_21_9/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_9/PaperweightFaweWorldNativeAccess.java @@ -22,6 +22,7 @@ import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.redstone.ExperimentalRedstoneUtils; import net.minecraft.world.level.storage.ValueInput; +import org.bukkit.Bukkit; import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.craftbukkit.block.data.CraftBlockData; import org.bukkit.event.block.BlockPhysicsEvent; @@ -62,7 +63,7 @@ public PaperweightFaweWorldNativeAccess(PaperweightFaweAdapter paperweightFaweAd this.level = level; // Use the actual tick as minecraft-defined so we don't try to force blocks into the world when the server's already lagging. // - With the caveat that we don't want to have too many cached changed (1024) so we'd flush those at 1024 anyway. - this.lastTick = new AtomicInteger(MinecraftServer.currentTick); + this.lastTick = new AtomicInteger(getCurrentTick()); } private Level getLevel() { @@ -98,7 +99,7 @@ public synchronized net.minecraft.world.level.block.state.BlockState setBlockSta LevelChunk levelChunk, BlockPos blockPos, net.minecraft.world.level.block.state.BlockState blockState ) { - int currentTick = MinecraftServer.currentTick; + int currentTick = getCurrentTick(); if (Fawe.isMainThread()) { return levelChunk.setBlockState(blockPos, blockState, this.sideEffectSet.shouldApply(SideEffect.UPDATE) ? 0 : 512 @@ -296,4 +297,16 @@ private record CachedChange( } + private int getCurrentTick() { + try { + return MinecraftServer.currentTick; + } catch (NoSuchFieldError e) { + try { + return Bukkit.getCurrentTick(); + } catch (Exception ex) { + return 0; + } + } + } + } diff --git a/worldedit-bukkit/adapters/adapter-1_21_9/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_9/PaperweightGetBlocks.java b/worldedit-bukkit/adapters/adapter-1_21_9/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_9/PaperweightGetBlocks.java index 84e6d721d1..7baba4d51b 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_9/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_9/PaperweightGetBlocks.java +++ b/worldedit-bukkit/adapters/adapter-1_21_9/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_9/PaperweightGetBlocks.java @@ -11,6 +11,7 @@ import com.fastasyncworldedit.core.math.IntPair; import com.fastasyncworldedit.core.nbt.FaweCompoundTag; import com.fastasyncworldedit.core.queue.IChunkSet; +import com.fastasyncworldedit.core.util.FoliaUtil; import com.fastasyncworldedit.core.util.MathMan; import com.fastasyncworldedit.core.util.NbtUtils; import com.fastasyncworldedit.core.util.collection.AdaptedMap; @@ -23,6 +24,7 @@ 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; import io.papermc.lib.PaperLib; import io.papermc.paper.event.block.BeaconDeactivatedEvent; @@ -58,6 +60,8 @@ import net.minecraft.world.level.storage.TagValueOutput; import net.minecraft.world.level.storage.ValueInput; import org.apache.logging.log4j.Logger; +import org.bukkit.Bukkit; +import org.bukkit.Location; import org.bukkit.World; import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.craftbukkit.block.CraftBlock; @@ -198,23 +202,30 @@ public void removeSectionLighting(int layer, boolean sky) { @Override public FaweCompoundTag tile(final int x, final int y, final int z) { - BlockEntity blockEntity = getChunk().getBlockEntity(new BlockPos((x & 15) + ( - chunkX << 4), y, (z & 15) + ( - chunkZ << 4))); - if (blockEntity == null) { + try { + BlockEntity blockEntity = getChunk().getBlockEntity(new BlockPos((x & 15) + ( + chunkX << 4), y, (z & 15) + ( + chunkZ << 4))); + if (blockEntity == null) { + return null; + } + return NMS_TO_TILE.apply(blockEntity); + } catch (NullPointerException e) { return null; } - return NMS_TO_TILE.apply(blockEntity); - } @Override public Map tiles() { - Map nmsTiles = getChunk().getBlockEntities(); - if (nmsTiles.isEmpty()) { + try { + Map nmsTiles = getChunk().getBlockEntities(); + if (nmsTiles.isEmpty()) { + return Collections.emptyMap(); + } + return AdaptedMap.immutable(nmsTiles, posNms2We, NMS_TO_TILE); + } catch (NullPointerException e) { return Collections.emptyMap(); } - return AdaptedMap.immutable(nmsTiles, posNms2We, NMS_TO_TILE); } @Override @@ -322,7 +333,20 @@ public Set getFullEntities() { } private void removeEntity(Entity entity) { - entity.discard(); + if (FoliaUtil.isFoliaServer()) { + entity.getBukkitEntity().getScheduler().execute( + WorldEditPlugin.getInstance(), + () -> { + if (!entity.isRemoved()) { + entity.discard(); + } + }, + null, + 1L + ); + } else if (!entity.isRemoved()) { + entity.discard(); + } } @Override @@ -350,11 +374,11 @@ protected > T internalCall( 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; + BlockPos pos = entry.getKey(); + int lx = pos.getX() & 15; + int ly = pos.getY(); + int lz = pos.getZ() & 15; + int layer = ly >> 4; if (!set.hasSection(layer)) { continue; } @@ -362,12 +386,26 @@ protected > T internalCall( int ordinal = set.getBlock(lx, ly, lz).getOrdinal(); if (ordinal != BlockTypesCache.ReservedIDs.__RESERVED__) { BlockEntity tile = entry.getValue(); - if (PaperLib.isPaper() && tile instanceof BeaconBlockEntity) { + if (PaperLib.isPaper() && tile instanceof BeaconBlockEntity beacon) { if (beacons == null) { beacons = new ArrayList<>(); } - beacons.add(tile); - PaperweightPlatformAdapter.removeBeacon(tile, nmsChunk); + beacons.add(beacon); + Location location = new Location( + nmsWorld.getWorld(), + beacon.getBlockPos().getX(), + beacon.getBlockPos().getY(), + beacon.getBlockPos().getZ() + ); + if (FoliaUtil.isFoliaServer()) { + Bukkit.getServer().getRegionScheduler().execute( + WorldEditPlugin.getInstance(), + location, + () -> PaperweightPlatformAdapter.removeBeacon(beacon, nmsChunk) + ); + } else { + PaperweightPlatformAdapter.removeBeacon(beacon, nmsChunk); + } continue; } nmsChunk.removeBlockEntity(tile.getBlockPos()); @@ -377,7 +415,7 @@ protected > T internalCall( } } } - final BiomeType[][] biomes = set.getBiomes(); + BiomeType[][] biomes = set.getBiomes(); int bitMask = 0; synchronized (nmsChunk) { @@ -595,8 +633,7 @@ protected > T internalCall( // 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; - + List finalBeacons = beacons; syncTasks.add(() -> { for (BlockEntity beacon : finalBeacons) { BeaconBlockEntity.playSound(beacon.getLevel(), beacon.getBlockPos(), SoundEvents.BEACON_DEACTIVATE); @@ -607,10 +644,9 @@ protected > T internalCall( Set entityRemoves = set.getEntityRemoves(); if (entityRemoves != null && !entityRemoves.isEmpty()) { - syncTasks.add(() -> { Set entitiesRemoved = new HashSet<>(); - final List entities = PaperweightPlatformAdapter.getEntities(nmsChunk); + List entities = PaperweightPlatformAdapter.getEntities(nmsChunk); for (Entity entity : entities) { UUID uuid = entity.getUUID(); @@ -639,32 +675,31 @@ protected > T internalCall( Collection entities = set.entities(); if (entities != null && !entities.isEmpty()) { - syncTasks.add(() -> { 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()); + FaweCompoundTag nativeTag = iterator.next(); + LinCompoundTag linTag = nativeTag.linTag(); + LinStringTag idTag = linTag.findTag("Id", LinTagType.stringTag()); + LinListTag posTag = linTag.findListTag("Pos", LinTagType.doubleTag()); + 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(); + double x = posTag.get(0).valueAsDouble(); + double y = posTag.get(1).valueAsDouble(); + double z = posTag.get(2).valueAsDouble(); + float yaw = rotTag.get(0).valueAsFloat(); + float pitch = rotTag.get(1).valueAsFloat(); + 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) { + CompoundTag tag = (CompoundTag) adapter.fromNativeLin(linTag); + for (String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) { tag.remove(name); } // TODO (VI/O) @@ -672,17 +707,33 @@ protected > T internalCall( entity.load(input); entity.absSnapTo(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(); + Location location = new Location(nmsWorld.getWorld(), x, y, z); + if (FoliaUtil.isFoliaServer()) { + Bukkit.getServer().getRegionScheduler().execute(WorldEditPlugin.getInstance(), location, () -> { + 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 + ); + iterator.remove(); + } + }); + } else { + 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 + ); + iterator.remove(); + } } } } @@ -693,31 +744,39 @@ protected > T internalCall( // set tiles Map tiles = set.tiles(); if (tiles != null && !tiles.isEmpty()) { - syncTasks.add(() -> { - 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 (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)); - // TODO (VI/O) - ValueInput input = createInput(tag); - tileEntity.loadWithComponents(input); + World world = nmsWorld.getWorld(); + for (Map.Entry entry : tiles.entrySet()) { + FaweCompoundTag nativeTag = entry.getValue(); + BlockVector3 blockHash = entry.getKey(); + int x = blockHash.x() + bx; + int y = blockHash.y(); + int z = blockHash.z() + bz; + BlockPos pos = new BlockPos(x, y, z); + Location location = new Location(world, x, y, z); + + Runnable setTile = () -> { + synchronized (nmsChunk) { + BlockEntity tileEntity = nmsChunk.getBlockEntity(pos); + if (tileEntity == null || tileEntity.isRemoved()) { + nmsChunk.removeBlockEntity(pos); + tileEntity = nmsChunk.getBlockEntity(pos); + } + if (tileEntity != null) { + 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)); + ValueInput input = createInput(tag); + tileEntity.loadWithComponents(input); + } } + }; + + if (FoliaUtil.isFoliaServer()) { + Bukkit.getServer().getRegionScheduler().execute(WorldEditPlugin.getInstance(), location, setTile); + } else { + setTile.run(); } } }); @@ -729,15 +788,13 @@ protected > T internalCall( } else { int finalMask = bitMask != 0 ? bitMask : lightUpdate ? set.getBitMask() : 0; syncTasks.add(() -> { - // Set Modified nmsChunk.setLightCorrect(true); nmsChunk.mustNotSave = false; }); callback = () -> { - // send to player - if (!set - .getSideEffectSet() - .shouldApply(SideEffect.LIGHTING) || !Settings.settings().LIGHTING.DELAY_PACKET_SENDING || finalMask == 0 && biomes != null) { + if (!set.getSideEffectSet().shouldApply(SideEffect.LIGHTING) + || !Settings.settings().LIGHTING.DELAY_PACKET_SENDING + || finalMask == 0 && biomes != null) { this.send(); } if (finalizer != null) { diff --git a/worldedit-bukkit/adapters/adapter-1_21_9/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_9/PaperweightPlatformAdapter.java b/worldedit-bukkit/adapters/adapter-1_21_9/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_9/PaperweightPlatformAdapter.java index e1fca93ca2..01a8c6e93f 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_9/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_9/PaperweightPlatformAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_21_9/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_9/PaperweightPlatformAdapter.java @@ -9,6 +9,7 @@ import com.fastasyncworldedit.core.FaweCache; import com.fastasyncworldedit.core.math.BitArrayUnstretched; import com.fastasyncworldedit.core.math.IntPair; +import com.fastasyncworldedit.core.util.FoliaUtil; import com.fastasyncworldedit.core.util.MathMan; import com.fastasyncworldedit.core.util.TaskManager; import com.mojang.serialization.DataResult; @@ -21,6 +22,7 @@ import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockTypesCache; import io.papermc.lib.PaperLib; +import io.papermc.paper.util.MCUtil; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; import net.minecraft.core.IdMap; @@ -70,6 +72,7 @@ import java.util.Collections; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; @@ -337,10 +340,22 @@ private static LevelChunk toLevelChunk(Chunk chunk) { } private static void addTicket(ServerLevel serverLevel, int chunkX, int chunkZ) { - // Ensure chunk is definitely loaded before applying a ticket - io.papermc.paper.util.MCUtil.MAIN_EXECUTOR.execute(() -> serverLevel - .getChunkSource() - .addTicketWithRadius(ChunkHolderManager.UNLOAD_COOLDOWN, new ChunkPos(chunkX, chunkZ), 0)); + ChunkPos pos = new ChunkPos(chunkX, chunkZ); + if (FoliaUtil.isFoliaServer()) { + try { + serverLevel.getChunkSource().addTicketWithRadius(ChunkHolderManager.UNLOAD_COOLDOWN, pos, 0); + } catch (Exception ignored) { + } + return; + } + try { + MCUtil.MAIN_EXECUTOR.execute(() -> serverLevel.getChunkSource().addTicketWithRadius(ChunkHolderManager.UNLOAD_COOLDOWN, pos, 0)); + } catch (Exception e) { + try { + serverLevel.getChunkSource().addTicketWithRadius(ChunkHolderManager.UNLOAD_COOLDOWN, pos, 0); + } catch (Exception ignored) { + } + } } public static ChunkHolder getPlayerChunk(ServerLevel nmsWorld, final int chunkX, final int chunkZ) { @@ -358,13 +373,9 @@ public static void sendChunk(IntPair pair, ServerLevel nmsWorld, int chunkX, int if (chunkHolder == null) { return; } - LevelChunk levelChunk; - if (PaperLib.isPaper()) { - // getChunkAtIfLoadedImmediately is paper only - levelChunk = nmsWorld.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ); - } else { - levelChunk = chunkHolder.getTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK).orElse(null); - } + LevelChunk levelChunk = PaperLib.isPaper() + ? nmsWorld.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ) + : chunkHolder.getTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK).orElse(null); if (levelChunk == null) { return; } @@ -373,32 +384,37 @@ public static void sendChunk(IntPair pair, ServerLevel nmsWorld, int chunkX, int if (lockHolder.chunkLock == null) { return; } - MinecraftServer.getServer().execute(() -> { + + ChunkPos pos = levelChunk.getPos(); + ClientboundLevelChunkWithLightPacket packet = PaperLib.isPaper() + ? new ClientboundLevelChunkWithLightPacket(levelChunk, nmsWorld.getLightEngine(), null, null, false) + : new ClientboundLevelChunkWithLightPacket(levelChunk, nmsWorld.getLightEngine(), null, null); + + Runnable sendPacket = () -> nearbyPlayers(nmsWorld, pos).forEach(p -> p.connection.send(packet)); + + if (FoliaUtil.isFoliaServer()) { try { - ChunkPos pos = levelChunk.getPos(); - ClientboundLevelChunkWithLightPacket packet; - if (PaperLib.isPaper()) { - packet = new ClientboundLevelChunkWithLightPacket( - levelChunk, - nmsWorld.getLightEngine(), - null, - null, - false // last false is to not bother with x-ray - ); - } else { - // deprecated on paper - deprecation suppressed - packet = new ClientboundLevelChunkWithLightPacket( - levelChunk, - nmsWorld.getLightEngine(), - null, - null - ); - } - nearbyPlayers(nmsWorld, pos).forEach(p -> p.connection.send(packet)); + sendPacket.run(); } finally { NMSAdapter.endChunkPacketSend(nmsWorld.getWorld().getName(), pair, lockHolder); } - }); + } else { + try { + MinecraftServer.getServer().execute(() -> { + try { + sendPacket.run(); + } finally { + NMSAdapter.endChunkPacketSend(nmsWorld.getWorld().getName(), pair, lockHolder); + } + }); + } catch (Exception e) { + try { + sendPacket.run(); + } finally { + NMSAdapter.endChunkPacketSend(nmsWorld.getWorld().getName(), pair, lockHolder); + } + } + } } private static List nearbyPlayers(ServerLevel serverLevel, ChunkPos coordIntPair) { @@ -612,8 +628,9 @@ public static BiomeType adapt(Holder biome, LevelAccessor levelAccessor) static void removeBeacon(BlockEntity beacon, LevelChunk levelChunk) { try { + Map blockEntities = levelChunk.getBlockEntities(); if (levelChunk.loaded || levelChunk.level.isClientSide()) { - BlockEntity blockEntity = levelChunk.blockEntities.remove(beacon.getBlockPos()); + BlockEntity blockEntity = blockEntities.remove(beacon.getBlockPos()); if (blockEntity != null) { if (!levelChunk.level.isClientSide()) { methodRemoveGameEventListener.invoke(levelChunk, beacon, levelChunk.level); diff --git a/worldedit-bukkit/adapters/adapter-1_21_9/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_9/regen/PaperweightRegen.java b/worldedit-bukkit/adapters/adapter-1_21_9/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_9/regen/PaperweightRegen.java index e31a940e58..b14b564818 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_9/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_9/regen/PaperweightRegen.java +++ b/worldedit-bukkit/adapters/adapter-1_21_9/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_9/regen/PaperweightRegen.java @@ -5,6 +5,7 @@ import com.fastasyncworldedit.core.queue.IChunkCache; import com.fastasyncworldedit.core.queue.IChunkGet; import com.fastasyncworldedit.core.queue.implementation.chunk.ChunkCache; +import com.fastasyncworldedit.core.util.FoliaUtil; import com.google.common.collect.ImmutableList; import com.mojang.serialization.Lifecycle; import com.sk89q.worldedit.bukkit.BukkitAdapter; @@ -20,6 +21,7 @@ import net.minecraft.server.dedicated.DedicatedServer; import net.minecraft.server.level.ServerLevel; import net.minecraft.util.ProgressListener; +import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelSettings; import net.minecraft.world.level.biome.Biome; @@ -39,6 +41,8 @@ import java.nio.file.Path; import java.util.Map; import java.util.OptionalLong; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.function.BooleanSupplier; import java.util.function.Supplier; @@ -203,9 +207,52 @@ public void save( if (paperConfigField != null) { paperConfigField.set(freshWorld, originalServerWorld.paperConfig()); } + + if (FoliaUtil.isFoliaServer()) { + return initWorldForFolia(newWorldData); + } + return true; } + private boolean initWorldForFolia(PrimaryLevelData worldData) throws ExecutionException, InterruptedException { + MinecraftServer console = ((CraftServer) Bukkit.getServer()).getServer(); + + ChunkPos spawnChunk = new ChunkPos( + freshWorld.getChunkSource().randomState().sampler().findSpawnPosition() + ); + + setRandomSpawnSelection(spawnChunk); + + CompletableFuture initFuture = new CompletableFuture<>(); + + Bukkit.getServer().getRegionScheduler().run( + WorldEditPlugin.getInstance(), + freshWorld.getWorld(), + spawnChunk.x, spawnChunk.z, + task -> { + try { + console.initWorld(freshWorld, worldData, worldData.worldGenOptions()); + initFuture.complete(true); + } catch (Exception e) { + initFuture.completeExceptionally(e); + } + } + ); + + return initFuture.get(); + } + + private void setRandomSpawnSelection(ChunkPos spawnChunk) { + try { + Field randomSpawnField = ServerLevel.class.getDeclaredField("randomSpawnSelection"); + randomSpawnField.setAccessible(true); + randomSpawnField.set(freshWorld, spawnChunk); + } catch (ReflectiveOperationException e) { + throw new RuntimeException("Failed to set randomSpawnSelection for Folia world initialization", e); + } + } + @Override protected void cleanup() { try { diff --git a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/FaweBukkit.java b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/FaweBukkit.java index 3614e3b0f1..91599c12af 100644 --- a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/FaweBukkit.java +++ b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/FaweBukkit.java @@ -12,7 +12,9 @@ import com.fastasyncworldedit.bukkit.regions.WorldGuardFeature; import com.fastasyncworldedit.bukkit.util.BukkitTaskManager; import com.fastasyncworldedit.bukkit.util.ItemUtil; -import com.fastasyncworldedit.bukkit.util.image.BukkitImageViewer; +import com.fastasyncworldedit.bukkit.util.scheduler.BukkitScheduler; +import com.fastasyncworldedit.bukkit.util.scheduler.FoliaScheduler; +import com.fastasyncworldedit.bukkit.util.scheduler.Scheduler; import com.fastasyncworldedit.core.FAWEPlatformAdapterImpl; import com.fastasyncworldedit.core.Fawe; import com.fastasyncworldedit.core.IFawe; @@ -21,6 +23,7 @@ import com.fastasyncworldedit.core.queue.implementation.preloader.AsyncPreloader; import com.fastasyncworldedit.core.queue.implementation.preloader.Preloader; import com.fastasyncworldedit.core.regions.FaweMaskManager; +import com.fastasyncworldedit.core.util.FoliaUtil; import com.fastasyncworldedit.core.util.TaskManager; import com.fastasyncworldedit.core.util.WEManager; import com.fastasyncworldedit.core.util.image.ImageViewer; @@ -41,7 +44,6 @@ import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.world.WorldLoadEvent; import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.PluginManager; import java.io.File; import java.util.ArrayList; @@ -57,6 +59,7 @@ public class FaweBukkit implements IFawe, Listener { private static final Logger LOGGER = LogManagerCompat.getLogger(); private final Plugin plugin; + private Scheduler scheduler; private final FAWEPlatformAdapterImpl platformAdapter; private ItemUtil itemUtil; private Preloader preloader; @@ -64,6 +67,7 @@ public class FaweBukkit implements IFawe, Listener { public FaweBukkit(Plugin plugin) { this.plugin = plugin; + initializeScheduler(); try { Fawe.set(this); Fawe.setupInjector(); @@ -83,7 +87,7 @@ public FaweBukkit(Plugin plugin) { platformAdapter = new NMSAdapter(); //PlotSquared support is limited to Spigot/Paper as of 02/20/2020 - TaskManager.taskManager().later(this::setupPlotSquared, 0); + TaskManager.taskManager().later(this::setupPlotSquared, 1); // Registered delayed Event Listeners TaskManager.taskManager().task(() -> { @@ -172,7 +176,7 @@ public String getDebugInfo() { */ @Override public TaskManager getTaskManager() { - return new BukkitTaskManager(plugin); + return new BukkitTaskManager(plugin, scheduler); } public Plugin getPlugin() { @@ -301,4 +305,14 @@ private void setupPlotSquared() { } } + private void initializeScheduler() { + this.scheduler = FoliaUtil.isFoliaServer() + ? new FoliaScheduler(this.plugin) + : new BukkitScheduler(this.plugin); + } + + public Scheduler getScheduler() { + return scheduler; + } + } diff --git a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/FaweAdapter.java b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/FaweAdapter.java index b902696d2f..01af54b671 100644 --- a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/FaweAdapter.java +++ b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/FaweAdapter.java @@ -1,21 +1,28 @@ package com.fastasyncworldedit.bukkit.adapter; +import com.fastasyncworldedit.core.util.FoliaUtil; import com.fastasyncworldedit.core.util.TaskManager; import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.bukkit.BukkitWorld; +import com.sk89q.worldedit.bukkit.WorldEditPlugin; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.registry.state.Property; import com.sk89q.worldedit.util.TreeGenerator; +import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.TreeType; import org.bukkit.World; +import org.bukkit.block.Block; import org.bukkit.block.BlockState; -import java.util.Collection; +import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; /** * A base class for version-specific implementations of the BukkitImplAdapter @@ -37,10 +44,10 @@ protected FaweAdapter(final BukkitImplAdapter parent) { @Override public boolean generateTree( - final TreeGenerator.TreeType treeType, - final EditSession editSession, + TreeGenerator.TreeType treeType, + EditSession editSession, BlockVector3 blockVector3, - final World world + World world ) { TreeType bukkitType = BukkitWorld.toBukkitTreeType(treeType); if (bukkitType == TreeType.CHORUS_PLANT) { @@ -49,6 +56,11 @@ public boolean generateTree( } BlockVector3 target = blockVector3; SERVER_LEVEL serverLevel = getServerLevel(world); + + if (FoliaUtil.isFoliaServer()) { + return generateTreeFolia(bukkitType, editSession, target, world); + } + List placed = TaskManager.taskManager().sync(() -> { preCaptureStates(serverLevel); try { @@ -75,6 +87,92 @@ public boolean generateTree( return true; } + private boolean generateTreeFolia( + TreeType treeType, + EditSession editSession, + BlockVector3 target, + World world + ) { + if (Bukkit.isOwnedByCurrentRegion(world, target.x() >> 4, target.z() >> 4)) { + return generateTreeFoliaInternal(treeType, editSession, target, world); + } + + CompletableFuture future = new CompletableFuture<>(); + + Bukkit.getServer().getRegionScheduler().run( + WorldEditPlugin.getInstance(), + world, + target.x() >> 4, + target.z() >> 4, + task -> { + try { + boolean result = generateTreeFoliaInternal(treeType, editSession, target, world); + future.complete(result); + } catch (Exception e) { + future.completeExceptionally(e); + } + } + ); + + try { + return future.get(); + } catch (Exception e) { + throw new RuntimeException("Failed to generate tree on Folia", e); + } + } + + private boolean generateTreeFoliaInternal( + TreeType treeType, + EditSession editSession, + BlockVector3 target, + World world + ) { + Set beforeBlocks = new HashSet<>(); + + int radius = 10; + int height = 32; + for (int x = -radius; x <= radius; x++) { + for (int y = -5; y <= height; y++) { + for (int z = -radius; z <= radius; z++) { + BlockVector3 pos = target.add(x, y, z); + org.bukkit.block.Block block = world.getBlockAt(pos.x(), pos.y(), pos.z()); + if (block.getType() != Material.AIR) { + beforeBlocks.add(pos); + } + } + } + } + + boolean generated = world.generateTree(BukkitAdapter.adapt(world, target), treeType); + + if (!generated) { + return false; + } + + List newBlocks = new ArrayList<>(); + for (int x = -radius; x <= radius; x++) { + for (int y = -5; y <= height; y++) { + for (int z = -radius; z <= radius; z++) { + BlockVector3 pos = target.add(x, y, z); + if (!beforeBlocks.contains(pos)) { + Block block = world.getBlockAt(pos.x(), pos.y(), pos.z()); + if (block.getType() != Material.AIR) { + newBlocks.add(block.getState()); + } + } + } + } + } + + for (BlockState blockState : newBlocks) { + editSession.setBlock(blockState.getX(), blockState.getY(), blockState.getZ(), + BukkitAdapter.adapt(blockState.getBlockData()) + ); + } + + return !newBlocks.isEmpty(); + } + public void mapFromGlobalPalette(char[] data) { assert data.length == 4096; ensureInit(); diff --git a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/IBukkitAdapter.java b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/IBukkitAdapter.java index 068f965970..30ae40b91a 100644 --- a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/IBukkitAdapter.java +++ b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/IBukkitAdapter.java @@ -1,6 +1,7 @@ package com.fastasyncworldedit.bukkit.adapter; import com.fastasyncworldedit.bukkit.util.BukkitItemStack; +import com.fastasyncworldedit.core.util.FoliaUtil; import com.fastasyncworldedit.core.util.TaskManager; import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.NotABlockException; @@ -388,7 +389,9 @@ default boolean generateTree(TreeGenerator.TreeType type, EditSession editSessio * @return list of {@link org.bukkit.entity.Entity} */ default List getEntities(org.bukkit.World world) { - return TaskManager.taskManager().sync(world::getEntities); + return FoliaUtil.isFoliaServer() + ? TaskManager.taskManager().syncWhenFree(world::getEntities) + : TaskManager.taskManager().sync(world::getEntities); } /** diff --git a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/Regenerator.java b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/Regenerator.java index 0fce7c7dfd..792cd19376 100644 --- a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/Regenerator.java +++ b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/Regenerator.java @@ -4,10 +4,12 @@ import com.fastasyncworldedit.core.queue.IChunkCache; import com.fastasyncworldedit.core.queue.IChunkGet; import com.fastasyncworldedit.core.queue.implementation.SingleThreadQueueExtent; +import com.fastasyncworldedit.core.util.FoliaUtil; import com.fastasyncworldedit.core.util.TaskManager; import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.bukkit.BukkitWorld; +import com.sk89q.worldedit.bukkit.WorldEditPlugin; import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.function.pattern.Pattern; import com.sk89q.worldedit.math.BlockVector3; @@ -15,6 +17,9 @@ import com.sk89q.worldedit.world.RegenOptions; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.block.BaseBlock; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; import org.bukkit.generator.BiomeProvider; import org.bukkit.generator.WorldInfo; import org.jetbrains.annotations.NotNull; @@ -103,30 +108,85 @@ private void createSource() { private void copyToWorld() { createSource(); - final long timeoutPerTick = TimeUnit.MILLISECONDS.toNanos(10); - int taskId = TaskManager.taskManager().repeat(() -> { - final long startTime = System.nanoTime(); + long timeoutPerTick = TimeUnit.MILLISECONDS.toNanos(10); + int taskId = FoliaUtil.isFoliaServer() + ? scheduleFoliaRegenTask(timeoutPerTick) + : scheduleRegenTask(timeoutPerTick); + + Pattern pattern = createRegenerationPattern(); + target.setBlocks(region, pattern); + + TaskManager.taskManager().cancel(taskId); + } + + private int scheduleRegenTask(long timeoutPerTick) { + return TaskManager.taskManager().repeat(() -> { + long startTime = System.nanoTime(); runTasks(() -> System.nanoTime() - startTime < timeoutPerTick); }, 1); - //Setting Blocks - boolean genbiomes = options.shouldRegenBiomes(); + } + + private int scheduleFoliaRegenTask(long timeoutPerTick) { + org.bukkit.World freshWorld = getFreshWorldViaReflection(); + if (freshWorld == null) { + throw new UnsupportedOperationException( + "Cannot find fresh world for Folia regeneration. " + + "The regenerator must have a 'freshWorld' field of type ServerLevel." + ); + } + + BlockVector3 min = region.getMinimumPoint(); + Location location = new Location(freshWorld, min.x(), min.y(), min.z()); + + var task = Bukkit.getServer().getRegionScheduler().runAtFixedRate( + WorldEditPlugin.getInstance(), + location, + scheduledTask -> { + long startTime = System.nanoTime(); + runTasks(() -> System.nanoTime() - startTime < timeoutPerTick); + }, + 1, + 1 + ); + + return System.identityHashCode(task); + } + + private @Nullable World getFreshWorldViaReflection() { + try { + var field = this.getClass().getDeclaredField("freshWorld"); + field.setAccessible(true); + var freshWorld = field.get(this); + if (freshWorld == null) { + return null; + } + + var getWorldMethod = freshWorld.getClass().getMethod("getWorld"); + return (World) getWorldMethod.invoke(freshWorld); + } catch (Exception e) { + return null; + } + } + + private Pattern createRegenerationPattern() { + boolean genBiomes = options.shouldRegenBiomes(); boolean hasBiome = options.hasBiomeType(); - BiomeType biome = options.getBiomeType(); - Pattern pattern; - if (!genbiomes && !hasBiome) { - pattern = new PlacementPattern(); - } else if (hasBiome) { - pattern = new WithBiomePlacementPattern((ignored1, ignored2) -> biome); - } else { - pattern = new WithBiomePlacementPattern((vec, chunk) -> { - if (chunk != null) { - return chunk.getBiomeType(vec.x() & 15, vec.y(), vec.z() & 15); - } - return source.getBiome(vec); - }); + + if (!genBiomes && !hasBiome) { + return new PlacementPattern(); } - target.setBlocks(region, pattern); - TaskManager.taskManager().cancel(taskId); + + if (hasBiome) { + BiomeType biome = options.getBiomeType(); + return new WithBiomePlacementPattern((ignored1, ignored2) -> biome); + } + + return new WithBiomePlacementPattern((vec, chunk) -> { + if (chunk != null) { + return chunk.getBiomeType(vec.x() & 15, vec.y(), vec.z() & 15); + } + return source.getBiome(vec); + }); } private abstract class ChunkwisePattern implements Pattern { @@ -135,36 +195,32 @@ private abstract class ChunkwisePattern implements Pattern { protected @Nullable IChunk chunk; @Override - public @NotNull T applyChunk(final T chunk, @Nullable final Region region) { + public @NotNull T applyChunk(T chunk, @Nullable Region region) { this.chunk = source.getOrCreateChunk(chunk.getX(), chunk.getZ()); return chunk; } @Override - public void finishChunk(final IChunk chunk) { + public void finishChunk(IChunk chunk) { this.chunk = null; } @Override public abstract Pattern fork(); - } private class PlacementPattern extends ChunkwisePattern { @Override - public BaseBlock applyBlock(final BlockVector3 position) { + public BaseBlock applyBlock(BlockVector3 position) { return source.getFullBlock(position); } @Override - public boolean apply(final Extent extent, final BlockVector3 get, final BlockVector3 set) throws WorldEditException { - BaseBlock fullBlock; - if (chunk != null) { - fullBlock = chunk.getFullBlock(get.x() & 15, get.y(), get.z() & 15); - } else { - fullBlock = source.getFullBlock(get.x(), get.y(), get.z()); - } + public boolean apply(Extent extent, BlockVector3 get, BlockVector3 set) throws WorldEditException { + BaseBlock fullBlock = chunk != null + ? chunk.getFullBlock(get.x() & 15, get.y(), get.z() & 15) + : source.getFullBlock(get.x(), get.y(), get.z()); return set.setFullBlock(extent, fullBlock); } @@ -178,23 +234,20 @@ private class WithBiomePlacementPattern extends ChunkwisePattern { private final BiFunction biomeGetter; - private WithBiomePlacementPattern(final BiFunction biomeGetter) { + private WithBiomePlacementPattern(BiFunction biomeGetter) { this.biomeGetter = biomeGetter; } @Override - public BaseBlock applyBlock(final BlockVector3 position) { + public BaseBlock applyBlock(BlockVector3 position) { return source.getFullBlock(position); } @Override - public boolean apply(final Extent extent, final BlockVector3 get, final BlockVector3 set) throws WorldEditException { - final BaseBlock fullBlock; - if (chunk != null) { - fullBlock = chunk.getFullBlock(get.x() & 15, get.y(), get.z() & 15); - } else { - fullBlock = source.getFullBlock(get.x(), get.y(), get.z()); - } + public boolean apply(Extent extent, BlockVector3 get, BlockVector3 set) throws WorldEditException { + BaseBlock fullBlock = chunk != null + ? chunk.getFullBlock(get.x() & 15, get.y(), get.z() & 15) + : source.getFullBlock(get.x(), get.y(), get.z()); return extent.setBlock(set.x(), set.y(), set.z(), fullBlock) && extent.setBiome(set.x(), set.y(), set.z(), biomeGetter.apply(get, chunk)); } @@ -265,12 +318,12 @@ public class SingleBiomeProvider extends BiomeProvider { private final org.bukkit.block.Biome biome = BukkitAdapter.adapt(options.getBiomeType()); @Override - public org.bukkit.block.Biome getBiome(final WorldInfo worldInfo, final int x, final int y, final int z) { + public org.bukkit.block.Biome getBiome(WorldInfo worldInfo, int x, int y, int z) { return biome; } @Override - public List getBiomes(final WorldInfo worldInfo) { + public List getBiomes(WorldInfo worldInfo) { return Collections.singletonList(biome); } diff --git a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/regions/plotsquared/FaweDelegateSchematicHandler.java b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/regions/plotsquared/FaweDelegateSchematicHandler.java index 5c6f97d212..1d4ae7e4a0 100644 --- a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/regions/plotsquared/FaweDelegateSchematicHandler.java +++ b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/regions/plotsquared/FaweDelegateSchematicHandler.java @@ -19,7 +19,7 @@ import com.plotsquared.core.util.FileUtils; import com.plotsquared.core.util.SchematicHandler; import com.plotsquared.core.util.task.RunnableVal; -import com.plotsquared.core.util.task.TaskManager; +import com.fastasyncworldedit.core.util.TaskManager; import com.sk89q.jnbt.CompoundTag; import com.sk89q.jnbt.NBTInputStream; import com.sk89q.jnbt.NBTOutputStream; @@ -91,7 +91,7 @@ public void paste( } if (schematic == null) { if (whenDone != null) { - TaskManager.runTask(whenDone); + TaskManager.taskManager().task(whenDone); } return; } @@ -110,14 +110,16 @@ public void paste( if (actor != null) { actor.sendMessage(TranslatableCaption.of("schematics.schematic_size_mismatch")); } - TaskManager.runTask(whenDone); + if (whenDone != null) { + TaskManager.taskManager().task(whenDone); + } return; } if (((region.getMaximumPoint().x() - region.getMinimumPoint().x() + xOffset + 1) < WIDTH) || ( (region.getMaximumPoint().z() - region.getMinimumPoint().z() + zOffset + 1) < LENGTH) || (HEIGHT > worldHeight)) { if (whenDone != null) { - TaskManager.runTask(whenDone); + TaskManager.taskManager().task(whenDone); } return; } @@ -158,7 +160,7 @@ public void paste( clipboard.paste(editSession, to, true, false, true); if (whenDone != null) { whenDone.value = true; - TaskManager.runTask(whenDone); + TaskManager.taskManager().task(whenDone); } } }; @@ -213,7 +215,7 @@ public void upload(final CompoundTag tag, final UUID uuid, final String file, fi if (tag == null) { LOGGER.warn("Cannot save empty tag"); if (whenDone != null) { - TaskManager.runTask(whenDone); + TaskManager.taskManager().task(whenDone); } return; } diff --git a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/util/BukkitTaskManager.java b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/util/BukkitTaskManager.java index 11b8565a67..d050af04e5 100644 --- a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/util/BukkitTaskManager.java +++ b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/util/BukkitTaskManager.java @@ -1,5 +1,6 @@ package com.fastasyncworldedit.bukkit.util; +import com.fastasyncworldedit.bukkit.util.scheduler.Scheduler; import com.fastasyncworldedit.core.util.TaskManager; import org.bukkit.Bukkit; import org.bukkit.plugin.Plugin; @@ -9,45 +10,53 @@ public class BukkitTaskManager extends TaskManager { private final Plugin plugin; + private final Scheduler scheduler; - public BukkitTaskManager(final Plugin plugin) { + public BukkitTaskManager(final Plugin plugin, final Scheduler scheduler) { this.plugin = plugin; + this.scheduler = scheduler; } @Override public int repeat(@Nonnull final Runnable runnable, final int interval) { - return this.plugin.getServer().getScheduler().scheduleSyncRepeatingTask(this.plugin, runnable, interval, interval); + this.scheduler.runTaskTimer(this.plugin, runnable, interval, interval); + return 0; } @Override public int repeatAsync(@Nonnull final Runnable runnable, final int interval) { - return this.plugin.getServer().getScheduler().scheduleAsyncRepeatingTask(this.plugin, runnable, interval, interval); + this.scheduler.runTaskTimer(this.plugin, runnable, interval, interval); + return 0; } @Override public void async(@Nonnull final Runnable runnable) { - this.plugin.getServer().getScheduler().runTaskAsynchronously(this.plugin, runnable).getTaskId(); + this.scheduler.runTaskAsynchronously(this.plugin, runnable); } @Override public void task(@Nonnull final Runnable runnable) { - this.plugin.getServer().getScheduler().runTask(this.plugin, runnable).getTaskId(); + this.scheduler.runTask(this.plugin, runnable); } @Override public void later(@Nonnull final Runnable runnable, final int delay) { - this.plugin.getServer().getScheduler().runTaskLater(this.plugin, runnable, delay).getTaskId(); + this.scheduler.runTaskLater(this.plugin, runnable, delay); } @Override public void laterAsync(@Nonnull final Runnable runnable, final int delay) { - this.plugin.getServer().getScheduler().runTaskLaterAsynchronously(this.plugin, runnable, delay); + this.scheduler.runTaskLaterAsynchronously(this.plugin, runnable, delay); } @Override public void cancel(final int task) { if (task != -1) { - Bukkit.getScheduler().cancelTask(task); + try { + Bukkit.getScheduler().cancelTask(task); + } catch (Exception e) { + // Ignore errors on Folia - task cancellation by ID is not supported + } } } diff --git a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/util/scheduler/BukkitScheduler.java b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/util/scheduler/BukkitScheduler.java new file mode 100644 index 0000000000..76e35dfc6e --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/util/scheduler/BukkitScheduler.java @@ -0,0 +1,187 @@ +package com.fastasyncworldedit.bukkit.util.scheduler; + +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitTask; + +/** + * Bukkit implementation of the Scheduler interface. + */ +public record BukkitScheduler(Plugin plugin) implements Scheduler { + + @Override + public void runTaskTimer(Plugin plugin, Runnable runnable, long delay, long period) { + if (plugin == null || runnable == null) { + throw new IllegalArgumentException("Plugin and runnable cannot be null"); + } + + plugin.getServer().getScheduler().runTaskTimer(plugin, runnable, delay, period); + } + + @Override + public void runTask(Plugin plugin, Runnable runnable) { + if (plugin == null || runnable == null) { + throw new IllegalArgumentException("Plugin and runnable cannot be null"); + } + + plugin.getServer().getScheduler().runTask(plugin, runnable); + } + + @Override + public void runTaskAsynchronously(Plugin plugin, Runnable runnable) { + if (plugin == null || runnable == null) { + throw new IllegalArgumentException("Plugin and runnable cannot be null"); + } + + plugin.getServer().getScheduler().runTaskAsynchronously(plugin, runnable); + } + + @Override + public void runTaskLater(Plugin plugin, Runnable runnable, long delay) { + if (plugin == null || runnable == null) { + throw new IllegalArgumentException("Plugin and runnable cannot be null"); + } + + plugin.getServer().getScheduler().runTaskLater(plugin, runnable, delay); + } + + @Override + public void runTaskLaterAsynchronously(Plugin plugin, Runnable runnable, long delay) { + if (plugin == null || runnable == null) { + throw new IllegalArgumentException("Plugin and runnable cannot be null"); + } + + plugin.getServer().getScheduler().runTaskLaterAsynchronously(plugin, runnable, delay); + } + + @Override + public void runTaskTimer(Runnable runnable, long delay, long period) { + if (runnable == null) { + throw new IllegalArgumentException("Runnable cannot be null"); + } + + plugin.getServer().getScheduler().runTaskTimer(plugin, runnable, delay, period); + } + + @Override + public void runTask(Runnable runnable) { + if (runnable == null) { + throw new IllegalArgumentException("Runnable cannot be null"); + } + + plugin.getServer().getScheduler().runTask(plugin, runnable); + } + + @Override + public void runTaskAsynchronously(Runnable runnable) { + if (runnable == null) { + throw new IllegalArgumentException("Runnable cannot be null"); + } + + plugin.getServer().getScheduler().runTaskAsynchronously(plugin, runnable); + } + + @Override + public void runTaskLater(Runnable runnable, long delay) { + if (runnable == null) { + throw new IllegalArgumentException("Runnable cannot be null"); + } + + plugin.getServer().getScheduler().runTaskLater(plugin, runnable, delay); + } + + @Override + public void runTaskLaterAsynchronously(Runnable runnable, long delay) { + if (runnable == null) { + throw new IllegalArgumentException("Runnable cannot be null"); + } + + plugin.getServer().getScheduler().runTaskLaterAsynchronously(plugin, runnable, delay); + } + + @Override + public void scheduleSyncDelayedTask(Plugin plugin, Runnable runnable, long delay) { + if (plugin == null || runnable == null) { + throw new IllegalArgumentException("Plugin and runnable cannot be null"); + } + + plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, runnable, delay); + } + + @Override + public CancellableTask runTaskCancellable(Plugin plugin, Runnable runnable) { + if (plugin == null || runnable == null) { + throw new IllegalArgumentException("Plugin and runnable cannot be null"); + } + + BukkitTask task = plugin.getServer().getScheduler().runTask(plugin, runnable); + return new BukkitCancellableTask(task); + } + + @Override + public CancellableTask runTaskLaterCancellable(Plugin plugin, Runnable runnable, long delay) { + if (plugin == null || runnable == null) { + throw new IllegalArgumentException("Plugin and runnable cannot be null"); + } + + BukkitTask task = plugin.getServer().getScheduler().runTaskLater(plugin, runnable, delay); + return new BukkitCancellableTask(task); + } + + @Override + public CancellableTask runTaskTimerCancellable(Plugin plugin, Runnable runnable, long delay, long period) { + if (plugin == null || runnable == null) { + throw new IllegalArgumentException("Plugin and runnable cannot be null"); + } + + BukkitTask task = plugin.getServer().getScheduler().runTaskTimer(plugin, runnable, delay, period); + return new BukkitCancellableTask(task); + } + + @Override + public CancellableTask runTaskCancellable(Runnable runnable) { + if (runnable == null) { + throw new IllegalArgumentException("Runnable cannot be null"); + } + + BukkitTask task = plugin.getServer().getScheduler().runTask(plugin, runnable); + return new BukkitCancellableTask(task); + } + + @Override + public CancellableTask runTaskLaterCancellable(Runnable runnable, long delay) { + if (runnable == null) { + throw new IllegalArgumentException("Runnable cannot be null"); + } + + BukkitTask task = plugin.getServer().getScheduler().runTaskLater(plugin, runnable, delay); + return new BukkitCancellableTask(task); + } + + @Override + public CancellableTask runTaskTimerCancellable(Runnable runnable, long delay, long period) { + if (runnable == null) { + throw new IllegalArgumentException("Runnable cannot be null"); + } + + BukkitTask task = plugin.getServer().getScheduler().runTaskTimer(plugin, runnable, delay, period); + return new BukkitCancellableTask(task); + } + + /** + * Wrapper for BukkitTask to implement CancellableTask interface. + */ + private record BukkitCancellableTask(BukkitTask task) implements CancellableTask { + + @Override + public void cancel() { + if (task != null) { + task.cancel(); + } + } + + @Override + public boolean isCancelled() { + return task == null || task.isCancelled(); + } + } +} diff --git a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/util/scheduler/FoliaScheduler.java b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/util/scheduler/FoliaScheduler.java new file mode 100644 index 0000000000..7204193f28 --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/util/scheduler/FoliaScheduler.java @@ -0,0 +1,279 @@ +package com.fastasyncworldedit.bukkit.util.scheduler; + +import io.papermc.paper.threadedregions.scheduler.ScheduledTask; +import org.bukkit.plugin.Plugin; + +import java.util.concurrent.TimeUnit; + +/** + * Folia implementation of the Scheduler interface. + */ +public record FoliaScheduler(Plugin plugin) implements Scheduler { + + @Override + public void runTaskTimer(Plugin plugin, Runnable runnable, long delay, long period) { + if (plugin == null || runnable == null) { + throw new IllegalArgumentException("Plugin and runnable cannot be null"); + } + + if (delay < 0 || period < 0) { + throw new IllegalArgumentException("Delay and period must be non-negative"); + } + + if (delay == 0) { + plugin.getServer().getGlobalRegionScheduler().run(plugin, scheduledTask -> runnable.run()); + if (period > 0) { + plugin.getServer().getGlobalRegionScheduler().runAtFixedRate(plugin, scheduledTask -> runnable.run(), (int) period, (int) period); + } + } else { + plugin.getServer().getGlobalRegionScheduler().runAtFixedRate(plugin, scheduledTask -> runnable.run(), (int) delay, (int) period); + } + } + + @Override + public void runTask(Plugin plugin, Runnable runnable) { + if (plugin == null || runnable == null) { + throw new IllegalArgumentException("Plugin and runnable cannot be null"); + } + + plugin.getServer().getGlobalRegionScheduler().run(plugin, scheduledTask -> runnable.run()); + } + + @Override + public void runTaskAsynchronously(Plugin plugin, Runnable runnable) { + if (plugin == null || runnable == null) { + throw new IllegalArgumentException("Plugin and runnable cannot be null"); + } + plugin.getServer().getAsyncScheduler().runNow(plugin, scheduledTask -> runnable.run()); + } + + @Override + public void runTaskLater(Plugin plugin, Runnable runnable, long delay) { + if (plugin == null || runnable == null) { + throw new IllegalArgumentException("Plugin and runnable cannot be null"); + } + + if (delay < 0) { + throw new IllegalArgumentException("Delay must be non-negative"); + } + + if (delay == 0) { + plugin.getServer().getGlobalRegionScheduler().run(plugin, scheduledTask -> runnable.run()); + } else { + plugin.getServer().getGlobalRegionScheduler().runDelayed(plugin, scheduledTask -> runnable.run(), delay); + } + } + + @Override + public void runTaskLaterAsynchronously(Plugin plugin, Runnable runnable, long delay) { + if (plugin == null || runnable == null) { + throw new IllegalArgumentException("Plugin and runnable cannot be null"); + } + + if (delay < 0) { + throw new IllegalArgumentException("Delay must be non-negative"); + } + + if (delay == 0) { + plugin.getServer().getAsyncScheduler().runNow(plugin, scheduledTask -> runnable.run()); + } else { + // Convert ticks to milliseconds for async scheduler + long delayMs = delay * 50L; + plugin.getServer().getAsyncScheduler().runDelayed(plugin, scheduledTask -> runnable.run(), delayMs, TimeUnit.MILLISECONDS); + } + } + + @Override + public void runTaskTimer(Runnable runnable, long delay, long period) { + if (runnable == null) { + throw new IllegalArgumentException("Runnable cannot be null"); + } + + if (delay < 0 || period < 0) { + throw new IllegalArgumentException("Delay and period must be non-negative"); + } + + plugin.getServer().getGlobalRegionScheduler().runAtFixedRate(plugin, scheduledTask -> runnable.run(), (int) delay, (int) period); + } + + @Override + public void runTask(Runnable runnable) { + if (runnable == null) { + throw new IllegalArgumentException("Runnable cannot be null"); + } + + plugin.getServer().getGlobalRegionScheduler().run(plugin, scheduledTask -> runnable.run()); + } + + @Override + public void runTaskAsynchronously(Runnable runnable) { + if (runnable == null) { + throw new IllegalArgumentException("Runnable cannot be null"); + } + + plugin.getServer().getAsyncScheduler().runNow(plugin, scheduledTask -> runnable.run()); + } + + @Override + public void runTaskLater(Runnable runnable, long delay) { + if (runnable == null) { + throw new IllegalArgumentException("Runnable cannot be null"); + } + + if (delay < 0) { + throw new IllegalArgumentException("Delay must be non-negative"); + } + + if (delay == 0) { + plugin.getServer().getGlobalRegionScheduler().run(plugin, scheduledTask -> runnable.run()); + } else { + plugin.getServer().getGlobalRegionScheduler().runDelayed(plugin, scheduledTask -> runnable.run(), delay); + } + } + + @Override + public void runTaskLaterAsynchronously(Runnable runnable, long delay) { + if (runnable == null) { + throw new IllegalArgumentException("Runnable cannot be null"); + } + + if (delay < 0) { + throw new IllegalArgumentException("Delay must be non-negative"); + } + + if (delay == 0) { + plugin.getServer().getAsyncScheduler().runNow(plugin, scheduledTask -> runnable.run()); + } else { + // Convert ticks to milliseconds for async scheduler + long delayMs = delay * 50L; + plugin.getServer().getAsyncScheduler().runDelayed(plugin, scheduledTask -> runnable.run(), delayMs, TimeUnit.MILLISECONDS); + } + } + + @Override + public void scheduleSyncDelayedTask(Plugin plugin, Runnable runnable, long delay) { + if (plugin == null || runnable == null) { + throw new IllegalArgumentException("Plugin and runnable cannot be null"); + } + + if (delay < 0) { + throw new IllegalArgumentException("Delay must be non-negative"); + } + + if (delay == 0) { + plugin.getServer().getGlobalRegionScheduler().run(plugin, scheduledTask -> runnable.run()); + } else { + plugin.getServer().getGlobalRegionScheduler().runDelayed(plugin, scheduledTask -> runnable.run(), delay); + } + } + + @Override + public CancellableTask runTaskCancellable(Plugin plugin, Runnable runnable) { + if (plugin == null || runnable == null) { + throw new IllegalArgumentException("Plugin and runnable cannot be null"); + } + + ScheduledTask task = plugin.getServer().getGlobalRegionScheduler().run(plugin, scheduledTask -> runnable.run()); + return new FoliaCancellableTask(task); + } + + @Override + public CancellableTask runTaskLaterCancellable(Plugin plugin, Runnable runnable, long delay) { + if (plugin == null || runnable == null) { + throw new IllegalArgumentException("Plugin and runnable cannot be null"); + } + if (delay < 0) { + throw new IllegalArgumentException("Delay must be non-negative"); + } + + ScheduledTask task; + if (delay == 0) { + task = plugin.getServer().getGlobalRegionScheduler().run(plugin, scheduledTask -> runnable.run()); + } else { + task = plugin.getServer().getGlobalRegionScheduler().runDelayed(plugin, scheduledTask -> runnable.run(), delay); + } + + return new FoliaCancellableTask(task); + } + + @Override + public CancellableTask runTaskTimerCancellable(Plugin plugin, Runnable runnable, long delay, long period) { + if (plugin == null || runnable == null) { + throw new IllegalArgumentException("Plugin and runnable cannot be null"); + } + if (delay < 0 || period < 0) { + throw new IllegalArgumentException("Delay and period must be non-negative"); + } + + ScheduledTask task = plugin.getServer().getGlobalRegionScheduler().runAtFixedRate(plugin, scheduledTask -> runnable.run(), (int) delay, (int) period); + return new FoliaCancellableTask(task); + } + + @Override + public CancellableTask runTaskCancellable(Runnable runnable) { + if (runnable == null) { + throw new IllegalArgumentException("Runnable cannot be null"); + } + + ScheduledTask task = plugin.getServer().getGlobalRegionScheduler().run(plugin, scheduledTask -> runnable.run()); + return new FoliaCancellableTask(task); + } + + @Override + public CancellableTask runTaskLaterCancellable(Runnable runnable, long delay) { + if (runnable == null) { + throw new IllegalArgumentException("Runnable cannot be null"); + } + if (delay < 0) { + throw new IllegalArgumentException("Delay must be non-negative"); + } + ScheduledTask task; + if (delay == 0) { + task = plugin.getServer().getGlobalRegionScheduler().run(plugin, scheduledTask -> runnable.run()); + } else { + task = plugin.getServer().getGlobalRegionScheduler().runDelayed(plugin, scheduledTask -> runnable.run(), delay); + } + + return new FoliaCancellableTask(task); + } + + @Override + public CancellableTask runTaskTimerCancellable(Runnable runnable, long delay, long period) { + if (runnable == null) { + throw new IllegalArgumentException("Runnable cannot be null"); + } + if (delay < 0 || period < 0) { + throw new IllegalArgumentException("Delay and period must be non-negative"); + } + ScheduledTask task = plugin.getServer().getGlobalRegionScheduler().runAtFixedRate(plugin, scheduledTask -> runnable.run(), (int) delay, (int) period); + return new FoliaCancellableTask(task); + } + + /** + * Wrapper for Folia's ScheduledTask to implement CancellableTask interface. + */ + private static class FoliaCancellableTask implements CancellableTask { + + private final ScheduledTask task; + private volatile boolean cancelled = false; + + public FoliaCancellableTask(ScheduledTask task) { + this.task = task; + } + + @Override + public void cancel() { + if (!cancelled) { + cancelled = true; + if (task != null) { + task.cancel(); + } + } + } + + @Override + public boolean isCancelled() { + return cancelled || (task != null && task.isCancelled()); + } + } +} diff --git a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/util/scheduler/Scheduler.java b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/util/scheduler/Scheduler.java new file mode 100644 index 0000000000..7bc6bf10ba --- /dev/null +++ b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/util/scheduler/Scheduler.java @@ -0,0 +1,176 @@ +package com.fastasyncworldedit.bukkit.util.scheduler; + +import org.bukkit.plugin.Plugin; + +/** + * Unified scheduler interface that works across both Bukkit and Folia servers. + * This interface provides a consistent API for scheduling tasks regardless of the server type. + */ +public interface Scheduler { + + /** + * Schedules a repeating task that runs on the main thread. + * + * @param plugin The plugin instance + * @param runnable The task to run + * @param delay The initial delay in ticks + * @param period The period between executions in ticks + */ + void runTaskTimer(Plugin plugin, Runnable runnable, long delay, long period); + + /** + * Schedules a task to run on the main thread. + * + * @param plugin The plugin instance + * @param runnable The task to run + */ + void runTask(Plugin plugin, Runnable runnable); + + /** + * Schedules a task to run asynchronously. + * + * @param plugin The plugin instance + * @param runnable The task to run + */ + void runTaskAsynchronously(Plugin plugin, Runnable runnable); + + /** + * Schedules a delayed task to run on the main thread. + * + * @param plugin The plugin instance + * @param runnable The task to run + * @param delay The delay in ticks + */ + void runTaskLater(Plugin plugin, Runnable runnable, long delay); + + /** + * Schedules a delayed task to run asynchronously. + * + * @param plugin The plugin instance + * @param runnable The task to run + * @param delay The delay in ticks + */ + void runTaskLaterAsynchronously(Plugin plugin, Runnable runnable, long delay); + + /** + * Schedules a repeating task that runs on the main thread (uses library plugin). + * + * @param runnable The task to run + * @param delay The initial delay in ticks + * @param period The period between executions in ticks + */ + void runTaskTimer(Runnable runnable, long delay, long period); + + /** + * Schedules a task to run on the main thread (uses library plugin). + * + * @param runnable The task to run + */ + void runTask(Runnable runnable); + + /** + * Schedules a task to run asynchronously (uses library plugin). + * + * @param runnable The task to run + */ + void runTaskAsynchronously(Runnable runnable); + + /** + * Schedules a delayed task to run on the main thread (uses library plugin). + * + * @param runnable The task to run + * @param delay The delay in ticks + */ + void runTaskLater(Runnable runnable, long delay); + + /** + * Schedules a delayed task to run asynchronously (uses library plugin). + * + * @param runnable The task to run + * @param delay The delay in ticks + */ + void runTaskLaterAsynchronously(Runnable runnable, long delay); + + /** + * Legacy method for scheduling a delayed sync task. + * + * @param plugin The plugin instance + * @param runnable The task to run + * @param delay The delay in ticks + */ + void scheduleSyncDelayedTask(Plugin plugin, Runnable runnable, long delay); + + /** + * Runs a task and returns a cancellable task instance. + * + * @param plugin The plugin instance + * @param runnable The task to run + * @return Cancellable task instance + */ + CancellableTask runTaskCancellable(Plugin plugin, Runnable runnable); + + /** + * Runs a delayed task and returns a cancellable task instance. + * + * @param plugin The plugin instance + * @param runnable The task to run + * @param delay The delay in ticks + * @return Cancellable task instance + */ + CancellableTask runTaskLaterCancellable(Plugin plugin, Runnable runnable, long delay); + + /** + * Runs a repeating task and returns a cancellable task instance. + * + * @param plugin The plugin instance + * @param runnable The task to run + * @param delay The initial delay in ticks + * @param period The period between executions in ticks + * @return Cancellable task instance + */ + CancellableTask runTaskTimerCancellable(Plugin plugin, Runnable runnable, long delay, long period); + + /** + * Runs a task and returns a cancellable task instance (uses library plugin). + * + * @param runnable The task to run + * @return Cancellable task instance + */ + CancellableTask runTaskCancellable(Runnable runnable); + + /** + * Runs a delayed task and returns a cancellable task instance (uses library plugin). + * + * @param runnable The task to run + * @param delay The delay in ticks + * @return Cancellable task instance + */ + CancellableTask runTaskLaterCancellable(Runnable runnable, long delay); + + /** + * Runs a repeating task and returns a cancellable task instance (uses library plugin). + * + * @param runnable The task to run + * @param delay The initial delay in ticks + * @param period The period between executions in ticks + * @return Cancellable task instance + */ + CancellableTask runTaskTimerCancellable(Runnable runnable, long delay, long period); + + /** + * Interface for cancellable tasks that work across both Bukkit and Folia. + */ + interface CancellableTask { + + /** + * Cancels this task. + */ + void cancel(); + + /** + * Checks if this task is cancelled. + * @return true if cancelled, false otherwise + */ + boolean isCancelled(); + } +} diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitBlockCommandSender.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitBlockCommandSender.java index 3c2e99f420..f0a7f73a93 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitBlockCommandSender.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitBlockCommandSender.java @@ -19,6 +19,8 @@ package com.sk89q.worldedit.bukkit; +import com.fastasyncworldedit.bukkit.FaweBukkit; +import com.fastasyncworldedit.core.Fawe; import com.fastasyncworldedit.core.util.TaskManager; import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.extension.platform.AbstractCommandBlockActor; @@ -196,13 +198,28 @@ public boolean isActive() { updateActive(); } else { // we should update it eventually - Bukkit.getScheduler().callSyncMethod( - plugin, - () -> { - updateActive(); - return null; - } - ); + try { + FaweBukkit faweBukkit = Fawe.platform(); + if (faweBukkit != null && faweBukkit.getScheduler() != null) { + faweBukkit.getScheduler().runTask(plugin, this::updateActive); + } else { + Bukkit.getScheduler().callSyncMethod( + plugin, + () -> { + updateActive(); + return null; + } + ); + } + } catch (Exception e) { + Bukkit.getScheduler().callSyncMethod( + plugin, + () -> { + updateActive(); + return null; + } + ); + } } return active; } diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitEntity.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitEntity.java index adf0ee8465..28b0887beb 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitEntity.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitEntity.java @@ -19,6 +19,7 @@ package com.sk89q.worldedit.bukkit; +import com.fastasyncworldedit.core.util.FoliaUtil; import com.fastasyncworldedit.core.util.TaskManager; import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; import com.sk89q.worldedit.entity.BaseEntity; @@ -83,11 +84,18 @@ public Location getLocation() { @Override public boolean setLocation(Location location) { org.bukkit.entity.Entity entity = entityRef.get(); - if (entity != null) { - return entity.teleport(BukkitAdapter.adapt(location)); - } else { + if (entity == null) { return false; } + if (FoliaUtil.isFoliaServer()) { + try { + entity.teleportAsync(BukkitAdapter.adapt(location)).get(); + return true; + } catch (Exception e) { + return false; + } + } + return entity.teleport(BukkitAdapter.adapt(location)); } @Override @@ -111,20 +119,35 @@ public BaseEntity getState() { @Override public boolean remove() { - // synchronize the whole method, not just the remove operation as we always need to synchronize and - // can make sure the entity reference was not invalidated in the few milliseconds between the next available tick (lol) - return TaskManager.taskManager().sync(() -> { - org.bukkit.entity.Entity entity = entityRef.get(); - if (entity != null) { + if (FoliaUtil.isFoliaServer()) { + return TaskManager.taskManager().syncWhenFree(() -> { + org.bukkit.entity.Entity entity = entityRef.get(); + if (entity == null) { + return true; + } try { - entity.remove(); + entity.getScheduler().execute(WorldEditPlugin.getInstance(), entity::remove, null, 1); + return true; } catch (UnsupportedOperationException e) { return false; } - return entity.isDead(); - } else { + }); + } + return TaskManager.taskManager().sync(() -> { + org.bukkit.entity.Entity entity = entityRef.get(); + if (entity == null) { return true; } + try { + entity.remove(); + } catch (UnsupportedOperationException e) { + return false; + } + try { + return entity.isDead(); + } catch (Throwable t) { + return !entity.isValid(); + } }); } diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitEntityProperties.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitEntityProperties.java index 28ef9274a1..e46ed95e4b 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitEntityProperties.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitEntityProperties.java @@ -19,6 +19,7 @@ package com.sk89q.worldedit.bukkit; +import com.fastasyncworldedit.core.util.FoliaUtil; import com.sk89q.worldedit.entity.metadata.EntityProperties; import org.bukkit.entity.AbstractVillager; import org.bukkit.entity.Ambient; @@ -148,12 +149,24 @@ public boolean isGolem() { @Override public boolean isTamed() { - return entity instanceof Tameable && ((Tameable) entity).isTamed(); + if (!(entity instanceof Tameable)) { + return false; + } + if (FoliaUtil.isFoliaServer()) { + return false; + } + return ((Tameable) entity).isTamed(); } @Override public boolean isTagged() { - return entity instanceof LivingEntity && entity.getCustomName() != null; + if (!(entity instanceof LivingEntity)) { + return false; + } + if (FoliaUtil.isFoliaServer()) { + return false; + } + return entity.customName() != null; } @Override diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitPlayer.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitPlayer.java index a7a680b68b..be90097c13 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitPlayer.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitPlayer.java @@ -21,6 +21,7 @@ import com.fastasyncworldedit.core.configuration.Caption; import com.fastasyncworldedit.core.configuration.Settings; +import com.fastasyncworldedit.core.util.FoliaUtil; import com.fastasyncworldedit.core.util.TaskManager; import com.sk89q.util.StringUtil; import com.sk89q.wepif.VaultResolver; @@ -242,6 +243,21 @@ public boolean trySetPosition(Vector3 pos, float pitch, float yaw) { } org.bukkit.World finalWorld = world; //FAWE end + if (FoliaUtil.isFoliaServer()) { + try { + player.teleportAsync(new Location( + finalWorld, + pos.x(), + pos.y(), + pos.z(), + yaw, + pitch + )).get(); + return true; + } catch (Exception e) { + return false; + } + } return TaskManager.taskManager().sync(() -> player.teleport(new Location( finalWorld, pos.x(), @@ -363,6 +379,14 @@ public com.sk89q.worldedit.util.Location getLocation() { @Override public boolean setLocation(com.sk89q.worldedit.util.Location location) { + if (FoliaUtil.isFoliaServer()) { + try { + player.teleportAsync(BukkitAdapter.adapt(location)).get(); + return true; + } catch (Exception e) { + return false; + } + } return player.teleport(BukkitAdapter.adapt(location)); } diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitServerInterface.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitServerInterface.java index 73a7421c34..70f2d3a4e0 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitServerInterface.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitServerInterface.java @@ -19,7 +19,9 @@ package com.sk89q.worldedit.bukkit; +import com.fastasyncworldedit.bukkit.FaweBukkit; import com.fastasyncworldedit.bukkit.util.MinecraftVersion; +import com.fastasyncworldedit.core.Fawe; import com.fastasyncworldedit.core.configuration.Settings; import com.fastasyncworldedit.core.extent.processor.PlacementStateProcessor; import com.fastasyncworldedit.core.extent.processor.lighting.RelighterFactory; @@ -135,6 +137,15 @@ public void reload() { @Override public int schedule(long delay, long period, Runnable task) { + try { + FaweBukkit faweBukkit = Fawe.platform(); + if (faweBukkit != null && faweBukkit.getScheduler() != null) { + faweBukkit.getScheduler().runTaskTimer(plugin, task, delay, period); + return 0; + } + } catch (Exception e) { + throw new RuntimeException(e); + } return Bukkit.getScheduler().scheduleSyncRepeatingTask(plugin, task, delay, period); } diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java index 99aaefdd12..33b70eaf4c 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java @@ -27,6 +27,7 @@ import com.fastasyncworldedit.core.nbt.FaweCompoundTag; import com.fastasyncworldedit.core.queue.IChunkGet; import com.fastasyncworldedit.core.queue.implementation.packet.ChunkPacket; +import com.fastasyncworldedit.core.util.FoliaUtil; import com.fastasyncworldedit.core.util.TaskManager; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; @@ -70,6 +71,7 @@ import org.bukkit.entity.Entity; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryHolder; +import org.bukkit.plugin.Plugin; import java.lang.ref.WeakReference; import java.nio.file.Path; @@ -81,6 +83,7 @@ import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import static com.google.common.base.Preconditions.checkNotNull; @@ -147,8 +150,9 @@ public BukkitWorld(World world) { @Override public List getEntities(Region region) { World world = getWorld(); - - List ents = TaskManager.taskManager().sync(world::getEntities); + List ents = FoliaUtil.isFoliaServer() + ? TaskManager.taskManager().syncWhenFree(world::getEntities) + : TaskManager.taskManager().sync(world::getEntities); List entities = new ArrayList<>(); for (Entity ent : ents) { if (region.contains(BukkitAdapter.asBlockVector(ent.getLocation()))) { @@ -160,9 +164,11 @@ public List getEntities(Region region) { @Override public List getEntities() { + World world = getWorld(); + List ents = FoliaUtil.isFoliaServer() + ? TaskManager.taskManager().syncWhenFree(world::getEntities) + : TaskManager.taskManager().sync(world::getEntities); List list = new ArrayList<>(); - - List ents = TaskManager.taskManager().sync(getWorld()::getEntities); for (Entity entity : ents) { list.add(BukkitAdapter.adapt(entity)); } @@ -170,11 +176,47 @@ public List getEntities() { } @Override - public int removeEntities(final Region region) { - List entities = getEntities(region); - return TaskManager.taskManager().sync(() -> entities.stream() - .mapToInt(entity -> entity.remove() ? 1 : 0).sum() - ); + public int removeEntities(Region region) { + World world = getWorld(); + if (FoliaUtil.isFoliaServer()) { + return TaskManager.taskManager().syncWhenFree(() -> { + Plugin plugin = WorldEditPlugin.getInstance(); + AtomicInteger scheduled = new AtomicInteger(0); + for (Entity entity : world.getEntities()) { + if (!region.contains(BukkitAdapter.asBlockVector(entity.getLocation()))) { + continue; + } + try { + entity.getScheduler().execute(plugin, entity::remove, null, 1); + scheduled.incrementAndGet(); + } catch (UnsupportedOperationException ignored) { + } + } + return scheduled.get(); + }); + } + return TaskManager.taskManager().sync(() -> { + int removed = 0; + for (Entity entity : world.getEntities()) { + if (!region.contains(BukkitAdapter.asBlockVector(entity.getLocation()))) { + continue; + } + try { + entity.remove(); + try { + if (entity.isDead() || !entity.isValid()) { + removed++; + } + } catch (Throwable t) { + if (!entity.isValid()) { + removed++; + } + } + } catch (UnsupportedOperationException ignored) { + } + } + return removed; + }); } //FAWE: createEntity was moved to IChunkExtent to prevent issues with Async Entity Add. diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java index d0708887b0..aa885fefb4 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java @@ -21,6 +21,7 @@ import com.fastasyncworldedit.bukkit.BukkitPermissionAttachmentManager; import com.fastasyncworldedit.bukkit.FaweBukkit; +import com.fastasyncworldedit.core.util.FoliaUtil; import com.fastasyncworldedit.core.Fawe; import com.fastasyncworldedit.core.util.UpdateNotification; import com.fastasyncworldedit.core.util.WEManager; @@ -457,7 +458,23 @@ public void onDisable() { if (config != null) { config.unload(); } - this.getServer().getScheduler().cancelTasks(this); + if (FoliaUtil.isFoliaServer()) { + return; + } + try { + FaweBukkit faweBukkit = Fawe.platform(); + if (faweBukkit != null && faweBukkit.getScheduler() != null) { + try { + this.getServer().getScheduler().cancelTasks(this); + } catch (Exception ignored) { + } + } + } catch (Exception e) { + try { + this.getServer().getScheduler().cancelTasks(this); + } catch (Exception ignored) { + } + } } /** diff --git a/worldedit-bukkit/src/main/resources/plugin.yml b/worldedit-bukkit/src/main/resources/plugin.yml index 86f95e9ef7..1db84efd09 100644 --- a/worldedit-bukkit/src/main/resources/plugin.yml +++ b/worldedit-bukkit/src/main/resources/plugin.yml @@ -9,6 +9,7 @@ website: https://modrinth.com/plugin/fastasyncworldedit/ description: Blazingly fast world manipulation for builders, large networks and developers. authors: [ Empire92, MattBDev, IronApollo, dordsor21, NotMyFault ] loadbefore: [ WorldGuard, PlotSquared ] +folia-supported: true database: false permissions: fawe.plotsquared: diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/history/changeset/FaweStreamChangeSet.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/history/changeset/FaweStreamChangeSet.java index 8286a1ed5f..e6ddff48db 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/history/changeset/FaweStreamChangeSet.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/history/changeset/FaweStreamChangeSet.java @@ -155,6 +155,7 @@ public void readCombined(FaweInputStream is, MutableFullBlockChange change) thro } if (mode == 1 || mode == 4) { // small posDel = new FaweStreamPositionDelegate() { + final byte[] buffer = new byte[4]; int lx; int ly; int lz; @@ -179,8 +180,6 @@ public void write(OutputStream out, int x, int y, int z) throws IOException { out.write(b4); } - final byte[] buffer = new byte[4]; - @Override public int readX(FaweInputStream in) throws IOException { in.readFully(buffer); @@ -562,7 +561,10 @@ public Iterator getIterator(BlockBag blockBag, int mode, boolean redo) { public Iterator getFullBlockIterator(BlockBag blockBag, int inventory, final boolean dir) throws IOException { - final FaweInputStream is = new FaweInputStream(getBlockIS()); + final FaweInputStream is = getBlockIS(); + if (is == null) { + return Collections.emptyIterator(); + } final MutableFullBlockChange change = new MutableFullBlockChange(blockBag, inventory, dir); return new Iterator() { private MutableFullBlockChange last = read(); @@ -577,7 +579,6 @@ public MutableFullBlockChange read() { } catch (EOFException ignored) { } catch (Exception e) { e.printStackTrace(); - e.printStackTrace(); } try { is.close(); @@ -850,7 +851,7 @@ protected void write(final MutableEntityChange change, final CompoundTag tag) { @Override public boolean accepts(final Change change) { - return change instanceof MutableTileChange; + return change instanceof MutableEntityChange; } } @@ -1071,7 +1072,7 @@ public SimpleChangeSetSummary summarize(Region region, boolean shallow) { for (int i = 0; i < amount; i++) { int x = posDel.readX(fis) + ox; int y = posDel.readY(fis); - int z = posDel.readZ(fis) + ox; + int z = posDel.readZ(fis) + oz; idDel.readCombined(fis, change); summary.add(x, z, change.to); } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IChunkExtent.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IChunkExtent.java index 615f5fb504..6f6968e1a1 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IChunkExtent.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/queue/IChunkExtent.java @@ -91,7 +91,7 @@ default void setSkyLight(int x, int y, int z, int value) { @Override default void setBlockLight(int x, int y, int z, int value) { final IChunk chunk = getOrCreateChunk(x >> 4, z >> 4); - chunk.setSkyLight(x & 15, y, z & 15, value); + chunk.setBlockLight(x & 15, y, z & 15, value); } @Override 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 e5f43ddbcb..40cdb1c2ae 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 @@ -11,6 +11,7 @@ import com.fastasyncworldedit.core.queue.IQueueExtent; import com.fastasyncworldedit.core.queue.Trimable; import com.fastasyncworldedit.core.queue.implementation.chunk.ChunkCache; +import com.fastasyncworldedit.core.util.FoliaUtil; import com.fastasyncworldedit.core.util.MemUtil; import com.fastasyncworldedit.core.util.TaskManager; import com.fastasyncworldedit.core.util.collection.CleanableThreadLocal; @@ -106,7 +107,9 @@ public ThreadPoolExecutor getBlockingExecutor() { @Override public void run() { - if (!Fawe.isMainThread()) { + boolean isFolia = FoliaUtil.isFoliaServer(); + + if (!isFolia && !Fawe.isMainThread()) { throw new IllegalStateException("Not main thread"); } if (!syncTasks.isEmpty()) { @@ -328,7 +331,7 @@ public Future syncWhenFree(Supplier supplier) { } private Future sync(Runnable run, T value, Queue queue) { - if (Fawe.isMainThread()) { + if (!FoliaUtil.isFoliaServer() && Fawe.isMainThread()) { run.run(); return Futures.immediateFuture(value); } @@ -339,7 +342,7 @@ private Future sync(Runnable run, T value, Queue queue) { } private Future sync(Runnable run, Queue queue) { - if (Fawe.isMainThread()) { + if (!FoliaUtil.isFoliaServer() && Fawe.isMainThread()) { run.run(); return Futures.immediateCancelledFuture(); } @@ -350,7 +353,7 @@ private Future sync(Runnable run, Queue queue) { } private Future sync(Callable call, Queue queue) throws Exception { - if (Fawe.isMainThread()) { + if (!FoliaUtil.isFoliaServer() && Fawe.isMainThread()) { return Futures.immediateFuture(call.call()); } final FutureTask result = new FutureTask<>(call); @@ -360,7 +363,7 @@ private Future sync(Callable call, Queue queue) throws Exc } private Future sync(Supplier call, Queue queue) { - if (Fawe.isMainThread()) { + if (!FoliaUtil.isFoliaServer() && Fawe.isMainThread()) { return Futures.immediateFuture(call.get()); } final FutureTask result = new FutureTask<>(call::get); diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/FoliaUtil.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/FoliaUtil.java new file mode 100644 index 0000000000..0ce0b5d049 --- /dev/null +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/FoliaUtil.java @@ -0,0 +1,33 @@ +package com.fastasyncworldedit.core.util; + +/** + * Utility class for Folia server detection and compatibility. + */ +public class FoliaUtil { + + private static final Boolean FOLIA_DETECTED = detectFolia(); + + /** + * Check if we're running on a Folia server. + * + * @return true if running on Folia + */ + public static boolean isFoliaServer() { + return FOLIA_DETECTED; + } + + /** + * Detect if Folia is available by checking for the RegionizedServer class. + * This method is called once during class initialization for performance. + * + * @return true if Folia is detected + */ + private static boolean detectFolia() { + try { + Class.forName("io.papermc.paper.threadedregions.RegionizedServer"); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } +} diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/TaskManager.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/TaskManager.java index 176e02673b..00b3b756c5 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/TaskManager.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/TaskManager.java @@ -223,7 +223,6 @@ public void taskSoonMain(@Nonnull final Runnable runnable, boolean async) { } } - /** * Run a task later on the main thread. * @@ -326,7 +325,7 @@ public T syncWhenFree(@Nonnull final RunnableVal function) { return function.value; } try { - return Fawe.instance().getQueueHandler().sync((Supplier) function).get(); + return Fawe.instance().getQueueHandler().syncWhenFree((Supplier) function).get(); } catch (InterruptedException | ExecutionException e) { throw new RuntimeException(e); } @@ -342,7 +341,7 @@ public T syncWhenFree(@Nonnull final Supplier supplier) { return supplier.get(); } try { - return Fawe.instance().getQueueHandler().sync(supplier).get(); + return Fawe.instance().getQueueHandler().syncWhenFree(supplier).get(); } catch (InterruptedException | ExecutionException e) { throw new RuntimeException(e); } @@ -363,7 +362,7 @@ public T sync(@Nonnull final RunnableVal function) { * - Usually wait time is around 25ms
*/ public T sync(final Supplier function) { - if (Fawe.isMainThread()) { + if (Fawe.isMainThread() || FoliaUtil.isFoliaServer()) { return function.get(); } try { diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/wrappers/WorldWrapper.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/wrappers/WorldWrapper.java index 8421df551c..22f6569ea5 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/wrappers/WorldWrapper.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/wrappers/WorldWrapper.java @@ -4,6 +4,7 @@ import com.fastasyncworldedit.core.queue.IChunkGet; import com.fastasyncworldedit.core.queue.implementation.packet.ChunkPacket; import com.fastasyncworldedit.core.util.ExtentTraverser; +import com.fastasyncworldedit.core.util.FoliaUtil; import com.fastasyncworldedit.core.util.TaskManager; import com.fastasyncworldedit.core.util.task.RunnableVal; import com.sk89q.jnbt.CompoundTag; @@ -42,6 +43,7 @@ import com.sk89q.worldedit.world.weather.WeatherType; import javax.annotation.Nullable; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Set; @@ -261,7 +263,14 @@ public void run(Object value) { } @Override - public Collection getBlockDrops(final BlockVector3 position) { + public Collection getBlockDrops(BlockVector3 position) { + if (FoliaUtil.isFoliaServer()) { + try { + return parent.getBlockDrops(position); + } catch (Exception e) { + return new ArrayList<>(); + } + } return TaskManager.taskManager().sync(() -> parent.getBlockDrops(position)); } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformManager.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformManager.java index 377e32e35f..325876731e 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformManager.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformManager.java @@ -22,6 +22,7 @@ import com.fastasyncworldedit.core.configuration.Caption; import com.fastasyncworldedit.core.function.pattern.PatternTraverser; import com.fastasyncworldedit.core.internal.exception.FaweException; +import com.fastasyncworldedit.core.util.FoliaUtil; import com.fastasyncworldedit.core.wrappers.LocationMaskedPlayerWrapper; import com.fastasyncworldedit.core.wrappers.WorldWrapper; import com.google.common.collect.Maps; @@ -438,7 +439,7 @@ public void handleBlockInteract(BlockInteractEvent event) { player.runAction(() -> reset(superPickaxe) .actPrimary(queryCapability(Capability.WORLD_EDITING), getConfiguration(), player, session, location, event.getFace() - ), false, true); + ), false, !FoliaUtil.isFoliaServer()); //FAWE end event.setCancelled(true); return; @@ -451,7 +452,7 @@ public void handleBlockInteract(BlockInteractEvent event) { player.runAction(() -> reset((DoubleActionBlockTool) tool) .actSecondary(queryCapability(Capability.WORLD_EDITING), getConfiguration(), player, session, location, event.getFace() - ), false, true); + ), false, !FoliaUtil.isFoliaServer()); //FAWE end event.setCancelled(true); } @@ -470,7 +471,7 @@ public void handleBlockInteract(BlockInteractEvent event) { blockTool.actPrimary(queryCapability(Capability.WORLD_EDITING), getConfiguration(), player, session, location, event.getFace() ); - }, false, true); + }, false, !FoliaUtil.isFoliaServer()); //FAWE end event.setCancelled(true); } @@ -509,10 +510,17 @@ public void handlePlayerInput(PlayerInputEvent event) { Tool tool = session.getTool(player); if (tool instanceof DoubleActionTraceTool && tool.canUse(player)) { //FAWE start - run async - player.runAsyncIfFree(() -> reset((DoubleActionTraceTool) tool) - .actSecondary(queryCapability(Capability.WORLD_EDITING), - getConfiguration(), player, session - )); + if (FoliaUtil.isFoliaServer()) { + player.runIfFree(() -> reset((DoubleActionTraceTool) tool) + .actSecondary(queryCapability(Capability.WORLD_EDITING), + getConfiguration(), player, session + )); + } else { + player.runAsyncIfFree(() -> reset((DoubleActionTraceTool) tool) + .actSecondary(queryCapability(Capability.WORLD_EDITING), + getConfiguration(), player, session + )); + } //FAWE end event.setCancelled(true); return; diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/world/SurvivalModeExtent.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/world/SurvivalModeExtent.java index b2670113a1..7e21d4cc70 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/world/SurvivalModeExtent.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/world/SurvivalModeExtent.java @@ -19,6 +19,7 @@ package com.sk89q.worldedit.extent.world; +import com.fastasyncworldedit.core.util.FoliaUtil; import com.fastasyncworldedit.core.util.TaskManager; import com.fastasyncworldedit.core.util.task.RunnableVal; import com.sk89q.worldedit.WorldEditException; @@ -99,14 +100,20 @@ public > boolean setBlock(BlockVector3 location, B Collection drops = world.getBlockDrops(location); boolean canSet = super.setBlock(location, block); if (canSet) { - TaskManager.taskManager().sync(new RunnableVal<>() { - @Override - public void run(Object value) { - for (BaseItemStack stack : drops) { - world.dropItem(location.toVector3(), stack); - } + if (FoliaUtil.isFoliaServer()) { + for (BaseItemStack stack : drops) { + world.dropItem(location.toVector3(), stack); } - }); + } else { + TaskManager.taskManager().sync(new RunnableVal<>() { + @Override + public void run(Object value) { + for (BaseItemStack stack : drops) { + world.dropItem(location.toVector3(), stack); + } + } + }); + } return true; } else {