From 6195319a5f4f49231d3346ccf5b0176871e780b9 Mon Sep 17 00:00:00 2001 From: R00tB33rMan <36140389+R00tB33rMan@users.noreply.github.com> Date: Sun, 2 Nov 2025 23:22:53 -0500 Subject: [PATCH] Folia Support This PR implements Folia support in FastAsyncWorldEdit and even reintroduces `//regen` into Folia, which was thought to be impossible or extraordinarily difficult to achieve. I have gone ahead and tested this comprehensively on a 100-200 player Duels server against Folia that creates and removes schematics at all times. Before this, various commands, including extensions (e.g., copying an entity), have been tested and verified to work, along with some basic testing across every possible NMS version FastAsyncWorldEdit supports. Let me know if any changes are absolutely required! --- .../main/kotlin/buildlogic.common.gradle.kts | 9 +- .../fawe/v1_20_R2/PaperweightFaweAdapter.java | 116 ++++++-- .../PaperweightFaweWorldNativeAccess.java | 17 +- .../fawe/v1_20_R2/PaperweightGetBlocks.java | 193 ++++++++---- .../v1_20_R2/PaperweightPlatformAdapter.java | 89 +++--- .../fawe/v1_20_R2/regen/PaperweightRegen.java | 46 +++ .../fawe/v1_20_R3/PaperweightFaweAdapter.java | 116 ++++++-- .../PaperweightFaweWorldNativeAccess.java | 18 +- .../fawe/v1_20_R3/PaperweightGetBlocks.java | 194 +++++++----- .../v1_20_R3/PaperweightPlatformAdapter.java | 89 +++--- .../fawe/v1_20_R3/regen/PaperweightRegen.java | 46 +++ .../fawe/v1_20_R4/PaperweightFaweAdapter.java | 116 ++++++-- .../PaperweightFaweWorldNativeAccess.java | 17 +- .../fawe/v1_20_R4/PaperweightGetBlocks.java | 194 +++++++----- .../v1_20_R4/PaperweightPlatformAdapter.java | 84 +++--- .../fawe/v1_20_R4/regen/PaperweightRegen.java | 46 +++ .../fawe/v1_21_R1/PaperweightFaweAdapter.java | 116 ++++++-- .../PaperweightFaweWorldNativeAccess.java | 17 +- .../fawe/v1_21_R1/PaperweightGetBlocks.java | 274 ++++++++--------- .../v1_21_R1/PaperweightPlatformAdapter.java | 84 +++--- .../fawe/v1_21_R1/regen/PaperweightRegen.java | 46 +++ .../fawe/v1_21_4/PaperweightFaweAdapter.java | 102 +++++-- .../PaperweightFaweWorldNativeAccess.java | 18 +- .../fawe/v1_21_4/PaperweightGetBlocks.java | 195 +++++++----- .../v1_21_4/PaperweightPlatformAdapter.java | 84 +++--- .../fawe/v1_21_4/regen/PaperweightRegen.java | 46 +++ .../fawe/v1_21_5/PaperweightFaweAdapter.java | 100 ++++++- .../PaperweightFaweWorldNativeAccess.java | 17 +- .../fawe/v1_21_5/PaperweightGetBlocks.java | 195 +++++++----- .../v1_21_5/PaperweightPlatformAdapter.java | 84 +++--- .../fawe/v1_21_5/regen/PaperweightRegen.java | 46 +++ .../fawe/v1_21_6/PaperweightFaweAdapter.java | 101 +++++-- .../PaperweightFaweWorldNativeAccess.java | 17 +- .../fawe/v1_21_6/PaperweightGetBlocks.java | 198 ++++++++----- .../v1_21_6/PaperweightPlatformAdapter.java | 84 +++--- .../fawe/v1_21_6/regen/PaperweightRegen.java | 46 +++ .../fawe/v1_21_9/PaperweightFaweAdapter.java | 100 ++++++- .../PaperweightFaweWorldNativeAccess.java | 17 +- .../fawe/v1_21_9/PaperweightGetBlocks.java | 209 ++++++++----- .../v1_21_9/PaperweightPlatformAdapter.java | 85 +++--- .../fawe/v1_21_9/regen/PaperweightRegen.java | 47 +++ .../fastasyncworldedit/bukkit/FaweBukkit.java | 22 +- .../bukkit/adapter/FaweAdapter.java | 106 ++++++- .../bukkit/adapter/IBukkitAdapter.java | 5 +- .../bukkit/adapter/Regenerator.java | 137 ++++++--- .../FaweDelegateSchematicHandler.java | 14 +- .../bukkit/util/BukkitTaskManager.java | 25 +- .../util/scheduler/BukkitScheduler.java | 187 ++++++++++++ .../bukkit/util/scheduler/FoliaScheduler.java | 279 ++++++++++++++++++ .../bukkit/util/scheduler/Scheduler.java | 176 +++++++++++ .../bukkit/BukkitBlockCommandSender.java | 31 +- .../sk89q/worldedit/bukkit/BukkitEntity.java | 45 ++- .../bukkit/BukkitEntityProperties.java | 17 +- .../sk89q/worldedit/bukkit/BukkitPlayer.java | 24 ++ .../bukkit/BukkitServerInterface.java | 11 + .../sk89q/worldedit/bukkit/BukkitWorld.java | 60 +++- .../worldedit/bukkit/WorldEditPlugin.java | 19 +- .../src/main/resources/plugin.yml | 1 + .../changeset/FaweStreamChangeSet.java | 13 +- .../core/queue/IChunkExtent.java | 2 +- .../queue/implementation/QueueHandler.java | 13 +- .../core/util/FoliaUtil.java | 33 +++ .../core/util/TaskManager.java | 7 +- .../core/wrappers/WorldWrapper.java | 11 +- .../extension/platform/PlatformManager.java | 22 +- .../extent/world/SurvivalModeExtent.java | 21 +- 66 files changed, 3797 insertions(+), 1202 deletions(-) create mode 100644 worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/util/scheduler/BukkitScheduler.java create mode 100644 worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/util/scheduler/FoliaScheduler.java create mode 100644 worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/util/scheduler/Scheduler.java create mode 100644 worldedit-core/src/main/java/com/fastasyncworldedit/core/util/FoliaUtil.java 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 {