Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -133,15 +133,19 @@ public SessionKey getSessionKey() {
private volatile boolean active = true;

private void updateActive() {
Block block = sender.getBlock();
if (!block.getWorld().isChunkLoaded(block.getX() >> 4, block.getZ() >> 4)) {
active = false;
return;
try {
Block block = sender.getBlock();
if (!block.getWorld().isChunkLoaded(block.getX() >> 4, block.getZ() >> 4)) {
active = false;
return;
}
Material type = block.getType();
active = type == Material.COMMAND_BLOCK
|| type == Material.CHAIN_COMMAND_BLOCK
|| type == Material.REPEATING_COMMAND_BLOCK;
} catch (Throwable t) {
WorldEdit.logger.warn("Exception while updating command block sender active state", t);
}
Material type = block.getType();
active = type == Material.COMMAND_BLOCK
|| type == Material.CHAIN_COMMAND_BLOCK
|| type == Material.REPEATING_COMMAND_BLOCK;
}

@Override
Expand All @@ -151,22 +155,30 @@ public String getName() {

@Override
public boolean isActive() {
if (WorldEditPlugin.getInstance().isFolia()) {
// On Folia, we need to perform the update on the thread that owns the block.
if (Bukkit.isOwnedByCurrentRegion(sender.getBlock())) {
// This thread owns the block, so we can immediately update.
updateActive();
} else {
// We need to delegate to the right thread.
Bukkit.getRegionScheduler().execute(plugin, sender.getBlock().getLocation(), this::updateActive);
}

return active;
}

if (Bukkit.isPrimaryThread()) {
// we can update eagerly
updateActive();
} else {
// we should update it eventually
// Suppress FutureReturnValueIgnored: We handle it in the block.
@SuppressWarnings({"FutureReturnValueIgnored", "unused"})
var unused = Bukkit.getScheduler().callSyncMethod(plugin,
() -> {
try {
updateActive();
} catch (Throwable t) {
WorldEdit.logger.warn("Exception while updating command block sender active state", t);
}
return null;
});
var unused = Bukkit.getScheduler().callSyncMethod(plugin, () -> {
updateActive();
return null;
});
}
return active;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@
import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.entity.metadata.EntityProperties;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.regions.RegionOperationException;
import com.sk89q.worldedit.util.Location;
import com.sk89q.worldedit.util.formatting.text.TranslatableComponent;
import com.sk89q.worldedit.world.NullWorld;

import java.lang.ref.WeakReference;
Expand Down Expand Up @@ -101,6 +103,10 @@ public BaseEntity getState() {

@Override
public boolean remove() {
if (WorldEditPlugin.getInstance().isFolia()) {
throw new RuntimeException(new RegionOperationException(TranslatableComponent.of("worldedit.bukkit.unsupported-on-folia")));
}

org.bukkit.entity.Entity entity = entityRef.get();
if (entity != null) {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,14 @@ public void reload() {

@Override
public int schedule(long delay, long period, Runnable task) {
return Bukkit.getScheduler().scheduleSyncRepeatingTask(plugin, task, delay, period);
if (plugin.isFolia()) {
Bukkit.getGlobalRegionScheduler().runAtFixedRate(plugin, scheduledTask -> task.run(), delay, period);
// TODO Paper doesn't appear to have a concept of task IDs, so return 1 here for now.
// We may want to store these tasks and map them to our own IDs to cancel them later.
return 1;
} else {
return Bukkit.getScheduler().scheduleSyncRepeatingTask(plugin, task, delay, period);
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,12 @@
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.math.Vector3;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.regions.RegionOperationException;
import com.sk89q.worldedit.util.Direction;
import com.sk89q.worldedit.util.SideEffect;
import com.sk89q.worldedit.util.SideEffectSet;
import com.sk89q.worldedit.util.TreeGenerator;
import com.sk89q.worldedit.util.formatting.text.TranslatableComponent;
import com.sk89q.worldedit.world.AbstractWorld;
import com.sk89q.worldedit.world.RegenOptions;
import com.sk89q.worldedit.world.biome.BiomeType;
Expand Down Expand Up @@ -134,6 +136,10 @@ public List<com.sk89q.worldedit.entity.Entity> getEntities() {
@Nullable
@Override
public com.sk89q.worldedit.entity.Entity createEntity(com.sk89q.worldedit.util.Location location, BaseEntity entity) {
if (WorldEditPlugin.getInstance().isFolia()) {
throw new RuntimeException(new RegionOperationException(TranslatableComponent.of("worldedit.bukkit.unsupported-on-folia")));
}

BukkitImplAdapter adapter = WorldEditPlugin.getInstance().getBukkitImplAdapter();
if (adapter != null) {
try {
Expand Down Expand Up @@ -191,6 +197,10 @@ public int getBlockLightLevel(BlockVector3 pt) {

@Override
public boolean regenerate(Region region, Extent extent, RegenOptions options) {
if (WorldEditPlugin.getInstance().isFolia()) {
throw new RuntimeException(new RegionOperationException(TranslatableComponent.of("worldedit.bukkit.unsupported-on-folia")));
}

BukkitImplAdapter adapter = WorldEditPlugin.getInstance().getBukkitImplAdapter();
try {
if (adapter != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import com.sk89q.worldedit.bukkit.adapter.AdapterLoadException;
import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter;
import com.sk89q.worldedit.bukkit.adapter.BukkitImplLoader;
import com.sk89q.worldedit.bukkit.folia.FoliaExtentListener;
import com.sk89q.worldedit.event.platform.CommandEvent;
import com.sk89q.worldedit.event.platform.CommandSuggestionEvent;
import com.sk89q.worldedit.event.platform.ConfigurationLoadEvent;
Expand All @@ -48,6 +49,7 @@
import com.sk89q.worldedit.internal.util.LogManagerCompat;
import com.sk89q.worldedit.registry.Registries;
import com.sk89q.worldedit.registry.state.Property;
import com.sk89q.worldedit.util.concurrency.LazyReference;
import com.sk89q.worldedit.util.lifecycle.Lifecycled;
import com.sk89q.worldedit.util.lifecycle.SimpleLifecycled;
import com.sk89q.worldedit.world.World;
Expand All @@ -61,6 +63,8 @@
import com.sk89q.worldedit.world.item.ItemType;
import com.sk89q.worldedit.world.weather.WeatherTypes;
import io.papermc.lib.PaperLib;
import io.papermc.paper.ServerBuildInfo;
import net.kyori.adventure.key.Key;
import org.apache.logging.log4j.Logger;
import org.bstats.bukkit.Metrics;
import org.bukkit.Bukkit;
Expand Down Expand Up @@ -178,6 +182,11 @@ public void onEnable() {
// Enable metrics
new Metrics(this, BSTATS_PLUGIN_ID);
PaperLib.suggestPaper(this);

if (isFolia()) {
// Inject Folia-anti-break extent
WorldEdit.getInstance().getEventBus().register(new FoliaExtentListener());
}
}

private void setupPreWorldData() {
Expand Down Expand Up @@ -311,7 +320,14 @@ public void onDisable() {
if (config != null) {
config.unload();
}
this.getServer().getScheduler().cancelTasks(this);

if (isFolia()) {
this.getServer().getGlobalRegionScheduler().cancelTasks(this);
this.getServer().getAsyncScheduler().cancelTasks(this);
// Region schedulers do not support cancelling tasks
} else {
this.getServer().getScheduler().cancelTasks(this);
}
}

/**
Expand Down Expand Up @@ -507,6 +523,27 @@ BukkitImplAdapter getBukkitImplAdapter() {
return adapter.value().orElse(null);
}

private final LazyReference<Boolean> folia = LazyReference.from(() -> {
try {
// Folia is Paper-based, so this is a good first check.
if (PaperLib.isPaper()) {
// Then we can check against the `papermc:folia` key, as per the `isBrandCompatible` javadoc.
// This API is experimental so might randomly break on us (hence the try/catch)
// TODO if we drop Spigot support in the future, remove this check and purely use Folia-compatible APIs.
return ServerBuildInfo.buildInfo().isBrandCompatible(Key.key("papermc", "folia"));
}
} catch (Throwable t) {
// Ignore, this likely means an outdated version.
LOGGER.warn("Failed to check if server is running Folia", t);
}

return false;
});

protected boolean isFolia() {
return folia.getValue();
}

private class WorldInitListener implements Listener {
private boolean loaded = false;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package com.sk89q.worldedit.bukkit.folia;

import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.bukkit.BukkitAdapter;
import com.sk89q.worldedit.event.extent.EditSessionEvent;
import com.sk89q.worldedit.extent.AbstractDelegateExtent;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.util.eventbus.Subscribe;
import com.sk89q.worldedit.world.biome.BiomeType;
import com.sk89q.worldedit.world.block.BlockStateHolder;
import org.bukkit.Bukkit;

public class FoliaExtentListener {

@Subscribe
public void onEditSessionCreation(EditSessionEvent event) {
// This injects an extent that only allows modifying blocks & biomes that are within the current region.
// This prevents WorldEdit from inadvertently causing chunk loads or other thread-shenanigans when running on Folia.
event.setExtent(new AbstractDelegateExtent(event.getExtent()) {

private boolean isWithinRegion(BlockVector3 location) {
return Bukkit.isOwnedByCurrentRegion(BukkitAdapter.adapt(BukkitAdapter.adapt(event.getWorld()), location));
}

@Override
public <T extends BlockStateHolder<T>> boolean setBlock(BlockVector3 location, T block) throws WorldEditException {
if (isWithinRegion(location)) {
return super.setBlock(location, block);
}

return false;
}

@Override
public boolean setBiome(BlockVector3 position, BiomeType biome) {
if (isWithinRegion(position)) {
return super.setBiome(position, biome);
}

return false;
}
});
}
}
3 changes: 2 additions & 1 deletion worldedit-bukkit/src/main/resources/plugin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ main: com.sk89q.worldedit.bukkit.WorldEditPlugin
version: "${internalVersion}"
load: STARTUP
api-version: 1.21.4
folia-supported: true
softdepend: [Vault]
author: EngineHub
website: https://enginehub.org/worldedit
website: https://enginehub.org/worldedit
3 changes: 2 additions & 1 deletion worldedit-core/src/main/resources/lang/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -489,5 +489,6 @@
"worldedit.cli.unknown-command": "Unknown command!",

"worldedit.version.bukkit.unsupported-adapter": "This WorldEdit version does not fully support your version of Bukkit. Block entities (e.g. chests) will be empty, block properties (e.g. rotation) will be missing, and other things may not work. Update WorldEdit to restore this functionality:\n{0}",
"worldedit.bukkit.no-edit-without-adapter": "Editing on unsupported versions is disabled."
"worldedit.bukkit.no-edit-without-adapter": "Editing on unsupported versions is disabled.",
"worldedit.bukkit.unsupported-on-folia": "This feature of WorldEdit is currently not supported on Folia."
}
Loading