From 58f43ef0eb21a9139824847915fc3652a3a170bd Mon Sep 17 00:00:00 2001 From: xCodiq Date: Mon, 17 Feb 2025 23:14:38 +0100 Subject: [PATCH 1/4] feat(folia): added folia support --- pom.xml | 40 +++++++++++++++++-- .../entitydetection/EntityDetection.java | 4 +- .../searcher/EntitySearch.java | 8 ++-- src/main/resources/plugin.yml | 1 + 4 files changed, 44 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 8d4fc4e..db28906 100644 --- a/pom.xml +++ b/pom.xml @@ -19,13 +19,17 @@ - spigot-repo - https://hub.spigotmc.org/nexus/content/groups/public/ + paper-mc + https://repo.papermc.io/repository/maven-public/ enginehub-repo https://maven.enginehub.org/repo/ + + jitpack.io + https://jitpack.io + @@ -38,9 +42,15 @@ com.sk89q.worldguard worldguard-bukkit - 7.0.2 + 7.0.13-SNAPSHOT provided + + com.github.NahuLD.folia-scheduler-wrapper + folia-scheduler-wrapper + v0.0.3 + compile + @@ -72,6 +82,30 @@ ${project.name} + + + org.apache.maven.plugins + maven-shade-plugin + 3.5.0 + + + package + + shade + + + + + false + + + me.nahu.scheduler.wrapper + de.themoep.entitydetection.folia.scheduler + + + + + true diff --git a/src/main/java/de/themoep/entitydetection/EntityDetection.java b/src/main/java/de/themoep/entitydetection/EntityDetection.java index 85432f3..bc0bfc0 100644 --- a/src/main/java/de/themoep/entitydetection/EntityDetection.java +++ b/src/main/java/de/themoep/entitydetection/EntityDetection.java @@ -9,6 +9,7 @@ import de.themoep.entitydetection.searcher.SearchResult; import de.themoep.entitydetection.searcher.SearchResultEntry; import de.themoep.entitydetection.searcher.SearchType; +import me.nahu.scheduler.wrapper.FoliaWrappedJavaPlugin; import net.md_5.bungee.api.chat.ClickEvent; import net.md_5.bungee.api.chat.ComponentBuilder; import net.md_5.bungee.api.chat.HoverEvent; @@ -17,7 +18,6 @@ import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; -import org.bukkit.plugin.java.JavaPlugin; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -44,7 +44,7 @@ * You should have received a copy of the Mozilla Public License v2.0 * along with this program. If not, see . */ -public class EntityDetection extends JavaPlugin { +public class EntityDetection extends FoliaWrappedJavaPlugin { private EntitySearch currentSearch; diff --git a/src/main/java/de/themoep/entitydetection/searcher/EntitySearch.java b/src/main/java/de/themoep/entitydetection/searcher/EntitySearch.java index cf756ce..83f7960 100644 --- a/src/main/java/de/themoep/entitydetection/searcher/EntitySearch.java +++ b/src/main/java/de/themoep/entitydetection/searcher/EntitySearch.java @@ -1,6 +1,8 @@ package de.themoep.entitydetection.searcher; import de.themoep.entitydetection.EntityDetection; +import me.nahu.scheduler.wrapper.runnable.WrappedRunnable; +import me.nahu.scheduler.wrapper.task.WrappedTask; import net.md_5.bungee.api.ChatColor; import org.bukkit.Chunk; import org.bukkit.Material; @@ -9,8 +11,6 @@ import org.bukkit.command.CommandSender; import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; -import org.bukkit.scheduler.BukkitRunnable; -import org.bukkit.scheduler.BukkitTask; import java.util.ArrayList; import java.util.Arrays; @@ -34,7 +34,7 @@ * You should have received a copy of the Mozilla Public License v2.0 * along with this program. If not, see . */ -public class EntitySearch extends BukkitRunnable { +public class EntitySearch extends WrappedRunnable { private final EntityDetection plugin; private final CommandSender owner; private SearchType type = SearchType.CUSTOM; @@ -117,7 +117,7 @@ public long getDuration() { return (System.currentTimeMillis() - getStartTime()) / 1000; } - public BukkitTask start() { + public WrappedTask start() { if (searchedEntities.size() > 0) { for (World world : plugin.getServer().getWorlds()) { entities.addAll(world.getEntities()); diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 0da981f..916d2a1 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -2,6 +2,7 @@ name: ${project.name} main: de.themoep.entitydetection.EntityDetection version: '${minecraft.plugin.version}' api-version: 1.13 +folia-supported: true description: '${project.description}' softdepend: ["WorldGuard"] authors: [Phoenix616] From 3f140719bf1b6b5f6e718da1bc5cb6bd59fbcb95 Mon Sep 17 00:00:00 2001 From: xCodiq Date: Mon, 17 Feb 2025 23:19:40 +0100 Subject: [PATCH 2/4] fix(search): change to synchronous task execution --- .../java/de/themoep/entitydetection/searcher/EntitySearch.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/de/themoep/entitydetection/searcher/EntitySearch.java b/src/main/java/de/themoep/entitydetection/searcher/EntitySearch.java index 83f7960..5c43a47 100644 --- a/src/main/java/de/themoep/entitydetection/searcher/EntitySearch.java +++ b/src/main/java/de/themoep/entitydetection/searcher/EntitySearch.java @@ -130,7 +130,7 @@ public WrappedTask start() { } } } - return runTaskAsynchronously(plugin); + return runTask(plugin); } public boolean isRunning() { From b3f290367139f1764b407a573d5ba6e9d9546399 Mon Sep 17 00:00:00 2001 From: xCodiq Date: Mon, 17 Feb 2025 23:42:57 +0100 Subject: [PATCH 3/4] feat(plugin): implement singleton pattern for EntityDetection --- .../java/de/themoep/entitydetection/EntityDetection.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/de/themoep/entitydetection/EntityDetection.java b/src/main/java/de/themoep/entitydetection/EntityDetection.java index bc0bfc0..9272cca 100644 --- a/src/main/java/de/themoep/entitydetection/EntityDetection.java +++ b/src/main/java/de/themoep/entitydetection/EntityDetection.java @@ -46,6 +46,8 @@ */ public class EntityDetection extends FoliaWrappedJavaPlugin { + private static EntityDetection instance = null; + private EntitySearch currentSearch; private Map> results = new HashMap<>(); @@ -54,7 +56,13 @@ public class EntityDetection extends FoliaWrappedJavaPlugin { private boolean serverIsSpigot = true; + public static EntityDetection getInstance() { + if (instance == null) throw new IllegalStateException("EntityDetection has not been initialized yet!"); + return instance; + } + public void onEnable() { + instance = this; try { Bukkit.class.getMethod("spigot"); } catch (NoSuchMethodException noSpigot) { From 8ee5bdfe0f9dca6b8ac9cb204accf52c778c4783 Mon Sep 17 00:00:00 2001 From: xCodiq Date: Tue, 18 Feb 2025 01:14:03 +0100 Subject: [PATCH 4/4] feat(folia): revision of the addition of folia support --- pom.xml | 40 +--- .../entitydetection/EntityDetection.java | 15 +- .../searcher/ChunkSearchResult.java | 72 +++---- .../searcher/EntitySearch.java | 18 +- .../searcher/SearchResult.java | 16 +- .../entitydetection/searcher/SearchType.java | 28 ++- .../searcher/WGSearchResult.java | 36 ++-- .../util/folia/AsyncScheduler.java | 140 +++++++++++++ .../util/folia/EntityScheduler.java | 135 ++++++++++++ .../util/folia/FoliaScheduler.java | 130 ++++++++++++ .../util/folia/GlobalRegionScheduler.java | 125 +++++++++++ .../util/folia/RegionScheduler.java | 197 ++++++++++++++++++ .../util/folia/TaskWrapper.java | 64 ++++++ 13 files changed, 908 insertions(+), 108 deletions(-) create mode 100644 src/main/java/de/themoep/entitydetection/util/folia/AsyncScheduler.java create mode 100644 src/main/java/de/themoep/entitydetection/util/folia/EntityScheduler.java create mode 100644 src/main/java/de/themoep/entitydetection/util/folia/FoliaScheduler.java create mode 100644 src/main/java/de/themoep/entitydetection/util/folia/GlobalRegionScheduler.java create mode 100644 src/main/java/de/themoep/entitydetection/util/folia/RegionScheduler.java create mode 100644 src/main/java/de/themoep/entitydetection/util/folia/TaskWrapper.java diff --git a/pom.xml b/pom.xml index db28906..4108067 100644 --- a/pom.xml +++ b/pom.xml @@ -26,17 +26,13 @@ enginehub-repo https://maven.enginehub.org/repo/ - - jitpack.io - https://jitpack.io - - org.spigotmc - spigot-api - 1.13.2-R0.1-SNAPSHOT + io.papermc.paper + paper-api + 1.21.4-R0.1-SNAPSHOT provided @@ -45,12 +41,6 @@ 7.0.13-SNAPSHOT provided - - com.github.NahuLD.folia-scheduler-wrapper - folia-scheduler-wrapper - v0.0.3 - compile - @@ -82,30 +72,6 @@ ${project.name} - - - org.apache.maven.plugins - maven-shade-plugin - 3.5.0 - - - package - - shade - - - - - false - - - me.nahu.scheduler.wrapper - de.themoep.entitydetection.folia.scheduler - - - - - true diff --git a/src/main/java/de/themoep/entitydetection/EntityDetection.java b/src/main/java/de/themoep/entitydetection/EntityDetection.java index 9272cca..3833f14 100644 --- a/src/main/java/de/themoep/entitydetection/EntityDetection.java +++ b/src/main/java/de/themoep/entitydetection/EntityDetection.java @@ -9,7 +9,7 @@ import de.themoep.entitydetection.searcher.SearchResult; import de.themoep.entitydetection.searcher.SearchResultEntry; import de.themoep.entitydetection.searcher.SearchType; -import me.nahu.scheduler.wrapper.FoliaWrappedJavaPlugin; +import de.themoep.entitydetection.util.folia.TaskWrapper; import net.md_5.bungee.api.chat.ClickEvent; import net.md_5.bungee.api.chat.ComponentBuilder; import net.md_5.bungee.api.chat.HoverEvent; @@ -18,6 +18,7 @@ import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; +import org.bukkit.plugin.java.JavaPlugin; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -44,11 +45,12 @@ * You should have received a copy of the Mozilla Public License v2.0 * along with this program. If not, see . */ -public class EntityDetection extends FoliaWrappedJavaPlugin { +public class EntityDetection extends JavaPlugin { private static EntityDetection instance = null; private EntitySearch currentSearch; + private TaskWrapper currentSearchTask; private Map> results = new HashMap<>(); private Map> customResults = new HashMap<>(); @@ -79,14 +81,19 @@ public boolean startSearch(EntitySearch search) { if(currentSearch != null && currentSearch.isRunning()) { return false; } + currentSearch = search; - return search.start() != null; + currentSearchTask = search.start(); + + return currentSearchTask != null; } public boolean stopSearch(String stopper) { - if(currentSearch == null || !currentSearch.isRunning()) { + if(currentSearch == null || !currentSearch.isRunning() || currentSearchTask.isCancelled()) { return false; } + + currentSearchTask.cancel(); currentSearch.stop(stopper); clearCurrentSearch(); return true; diff --git a/src/main/java/de/themoep/entitydetection/searcher/ChunkSearchResult.java b/src/main/java/de/themoep/entitydetection/searcher/ChunkSearchResult.java index 07ef724..9d6a41d 100644 --- a/src/main/java/de/themoep/entitydetection/searcher/ChunkSearchResult.java +++ b/src/main/java/de/themoep/entitydetection/searcher/ChunkSearchResult.java @@ -2,10 +2,8 @@ import de.themoep.entitydetection.ChunkLocation; import de.themoep.entitydetection.Utils; -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.Chunk; -import org.bukkit.Location; +import de.themoep.entitydetection.util.folia.FoliaScheduler; +import org.bukkit.*; import org.bukkit.block.BlockState; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; @@ -16,16 +14,6 @@ public ChunkSearchResult(EntitySearch search) { super(search); } - @Override - public void addEntity(Entity entity) { - add(entity.getLocation(), entity.getType().toString()); - } - - @Override - public void addBlockState(BlockState blockState) { - add(blockState.getLocation(), blockState.getType().toString()); - } - @Override public void add(Location location, String type) { ChunkLocation chunkLocation = new ChunkLocation(location); @@ -39,36 +27,48 @@ public void add(Location location, String type) { @Override public void teleport(Player sender, SearchResultEntry entry, int i) { try { - Chunk chunk = entry.getLocation().toBukkit(Bukkit.getServer()); + final ChunkLocation location = entry.getLocation(); + final World world = Bukkit.getWorld(location.getWorld()); + if (world == null) return; - Location loc = null; + final Runnable runnable = () -> { + final Chunk chunk = world.getChunkAt(location.getX(), location.getZ()); + Location loc = null; - for(Entity e : chunk.getEntities()) { - if(e.getType().toString().equals(entry.getEntryCount().get(0).getKey())) { - loc = e.getLocation(); - break; + for(Entity e : chunk.getEntities()) { + if(e.getType().toString().equals(entry.getEntryCount().get(0).getKey())) { + loc = e.getLocation(); + break; + } } - } - for (BlockState b : chunk.getTileEntities()) { - if(b.getType().toString().equals(entry.getEntryCount().get(0).getKey())) { - loc = b.getLocation().add(0, 1, 0); - break; + for (BlockState b : chunk.getTileEntities()) { + if(b.getType().toString().equals(entry.getEntryCount().get(0).getKey())) { + loc = b.getLocation().add(0, 1, 0); + break; + } } - } - if (loc == null) { - loc = chunk.getWorld().getHighestBlockAt(chunk.getX() * 16 + 8, chunk.getZ() * 16 + 8).getLocation().add(0, 2, 0); - } + if (loc == null) { + loc = chunk.getWorld().getHighestBlockAt(chunk.getX() * 16 + 8, chunk.getZ() * 16 + 8).getLocation().add(0, 2, 0); + } + + sender.teleportAsync(loc, PlayerTeleportEvent.TeleportCause.PLUGIN); + sender.sendMessage( + ChatColor.GREEN + "Teleported to entry " + ChatColor.WHITE + i + ": " + + ChatColor.YELLOW + location + " " + ChatColor.RED + entry.getSize() + " " + + ChatColor.GREEN + Utils.enumToHumanName(entry.getEntryCount().get(0).getKey()) + "[" + + ChatColor.WHITE + entry.getEntryCount().get(0).getValue() + ChatColor.GREEN + "]" + ); + }; - sender.teleport(loc, PlayerTeleportEvent.TeleportCause.PLUGIN); - sender.sendMessage( - ChatColor.GREEN + "Teleported to entry " + ChatColor.WHITE + i + ": " + - ChatColor.YELLOW + entry.getLocation() + " " + ChatColor.RED + entry.getSize() + " " + - ChatColor.GREEN + Utils.enumToHumanName(entry.getEntryCount().get(0).getKey()) + "[" + - ChatColor.WHITE + entry.getEntryCount().get(0).getValue() + ChatColor.GREEN + "]" - ); + if (FoliaScheduler.isFolia()) { + FoliaScheduler.getRegionScheduler().run(PLUGIN, world, location.getX(), location.getZ(), + $ -> runnable.run()); + return; + } + runnable.run(); } catch(IllegalArgumentException e) { sender.sendMessage(ChatColor.RED + e.getMessage()); } diff --git a/src/main/java/de/themoep/entitydetection/searcher/EntitySearch.java b/src/main/java/de/themoep/entitydetection/searcher/EntitySearch.java index 5c43a47..d243df4 100644 --- a/src/main/java/de/themoep/entitydetection/searcher/EntitySearch.java +++ b/src/main/java/de/themoep/entitydetection/searcher/EntitySearch.java @@ -1,8 +1,8 @@ package de.themoep.entitydetection.searcher; import de.themoep.entitydetection.EntityDetection; -import me.nahu.scheduler.wrapper.runnable.WrappedRunnable; -import me.nahu.scheduler.wrapper.task.WrappedTask; +import de.themoep.entitydetection.util.folia.FoliaScheduler; +import de.themoep.entitydetection.util.folia.TaskWrapper; import net.md_5.bungee.api.ChatColor; import org.bukkit.Chunk; import org.bukkit.Material; @@ -34,7 +34,7 @@ * You should have received a copy of the Mozilla Public License v2.0 * along with this program. If not, see . */ -public class EntitySearch extends WrappedRunnable { +public class EntitySearch implements Runnable { private final EntityDetection plugin; private final CommandSender owner; private SearchType type = SearchType.CUSTOM; @@ -117,7 +117,7 @@ public long getDuration() { return (System.currentTimeMillis() - getStartTime()) / 1000; } - public WrappedTask start() { + public TaskWrapper start() { if (searchedEntities.size() > 0) { for (World world : plugin.getServer().getWorlds()) { entities.addAll(world.getEntities()); @@ -126,11 +126,18 @@ public WrappedTask start() { if (searchedBlockStates.size() > 0 || searchedMaterial.size() > 0) { for (World world : plugin.getServer().getWorlds()) { for (Chunk chunk : world.getLoadedChunks()) { + if (FoliaScheduler.isFolia()) { + FoliaScheduler.getRegionScheduler().run(plugin, chunk.getWorld(), chunk.getX(), chunk.getZ(), + $ -> blockStates.addAll(Arrays.asList(chunk.getTileEntities()))); + continue; + } + blockStates.addAll(Arrays.asList(chunk.getTileEntities())); } } } - return runTask(plugin); + + return FoliaScheduler.getAsyncScheduler().runNow(plugin, $ -> this.run()); } public boolean isRunning() { @@ -139,7 +146,6 @@ public boolean isRunning() { public void stop(String name) { running = false; - cancel(); if(!owner.getName().equals(name)) { owner.sendMessage(ChatColor.YELLOW + name + ChatColor.RED + " stopped your " + getType() + " search after " + getDuration() + "s!"); } diff --git a/src/main/java/de/themoep/entitydetection/searcher/SearchResult.java b/src/main/java/de/themoep/entitydetection/searcher/SearchResult.java index a61648c..11d2e30 100644 --- a/src/main/java/de/themoep/entitydetection/searcher/SearchResult.java +++ b/src/main/java/de/themoep/entitydetection/searcher/SearchResult.java @@ -1,5 +1,7 @@ package de.themoep.entitydetection.searcher; +import de.themoep.entitydetection.EntityDetection; +import de.themoep.entitydetection.util.folia.FoliaScheduler; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.block.BlockState; @@ -14,6 +16,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; /** * Copyright 2016 Max Lee (https://github.com/Phoenix616/) @@ -31,6 +34,8 @@ * along with this program. If not, see . */ public abstract class SearchResult { + protected static final EntityDetection PLUGIN = EntityDetection.getInstance(); + private SearchType type; private Set searched; private long startTime; @@ -39,7 +44,8 @@ public abstract class SearchResult { /** * Working search map, use resultEntryList after sorting this result! */ - protected Map> resultEntryMap = new HashMap<>(); + protected Map> resultEntryMap = FoliaScheduler.isFolia() ? + new ConcurrentHashMap<>() : new HashMap<>(); /** * Sorted, highest entity count per chunks first, only propagated after running .sort() @@ -65,13 +71,17 @@ public SearchResult(EntitySearch search) { * Add an entity to this result * @param entity The entity to add */ - public abstract void addEntity(Entity entity); + public void addEntity(Entity entity) { + add(entity.getLocation(), entity.getType().toString()); + } /** * Add a BlockState to this result * @param blockState The entity to add */ - public abstract void addBlockState(BlockState blockState); + public void addBlockState(BlockState blockState) { + add(blockState.getLocation(), blockState.getType().toString()); + } public abstract void add(Location location, String type); diff --git a/src/main/java/de/themoep/entitydetection/searcher/SearchType.java b/src/main/java/de/themoep/entitydetection/searcher/SearchType.java index 0bbef98..ba9f6ec 100644 --- a/src/main/java/de/themoep/entitydetection/searcher/SearchType.java +++ b/src/main/java/de/themoep/entitydetection/searcher/SearchType.java @@ -14,6 +14,7 @@ import org.bukkit.entity.Projectile; import org.bukkit.entity.Slime; import org.bukkit.entity.WaterMob; +import org.bukkit.entity.boat.*; import java.util.Arrays; import java.util.Collections; @@ -56,9 +57,28 @@ public enum SearchType { ), MISC( new EntityType[]{ - EntityType.FIREWORK, - EntityType.ENDER_SIGNAL, - EntityType.BOAT + EntityType.FIREWORK_ROCKET, + EntityType.EYE_OF_ENDER, + EntityType.ACACIA_BOAT, + EntityType.ACACIA_CHEST_BOAT, + EntityType.BAMBOO_RAFT, + EntityType.BAMBOO_CHEST_RAFT, + EntityType.BIRCH_BOAT, + EntityType.BIRCH_CHEST_BOAT, + EntityType.CHERRY_BOAT, + EntityType.CHERRY_CHEST_BOAT, + EntityType.DARK_OAK_BOAT, + EntityType.DARK_OAK_CHEST_BOAT, + EntityType.JUNGLE_BOAT, + EntityType.JUNGLE_CHEST_BOAT, + EntityType.MANGROVE_BOAT, + EntityType.MANGROVE_CHEST_BOAT, + EntityType.OAK_BOAT, + EntityType.OAK_CHEST_BOAT, + EntityType.PALE_OAK_BOAT, + EntityType.PALE_OAK_CHEST_BOAT, + EntityType.SPRUCE_BOAT, + EntityType.SPRUCE_CHEST_BOAT }, new Class[]{ Projectile.class, @@ -70,7 +90,7 @@ public enum SearchType { new EntityType[]{ EntityType.ARMOR_STAND, EntityType.FALLING_BLOCK, - EntityType.ENDER_CRYSTAL + EntityType.END_CRYSTAL }, new Class[]{Hanging.class} ), diff --git a/src/main/java/de/themoep/entitydetection/searcher/WGSearchResult.java b/src/main/java/de/themoep/entitydetection/searcher/WGSearchResult.java index dea4bcb..57c0e05 100644 --- a/src/main/java/de/themoep/entitydetection/searcher/WGSearchResult.java +++ b/src/main/java/de/themoep/entitydetection/searcher/WGSearchResult.java @@ -7,6 +7,7 @@ import com.sk89q.worldguard.protection.regions.ProtectedRegion; import com.sk89q.worldguard.protection.regions.RegionQuery; import de.themoep.entitydetection.Utils; +import de.themoep.entitydetection.util.folia.FoliaScheduler; import org.bukkit.ChatColor; import org.bukkit.Location; import org.bukkit.World; @@ -24,27 +25,26 @@ public WGSearchResult(EntitySearch search) { } @Override - public void addEntity(Entity entity) { - add(entity.getLocation(), entity.getType().toString()); - } + public void add(Location location, String type) { + final Runnable runnable = () -> { + RegionQuery query = WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery(); + ApplicableRegionSet applicableRegions = query.getApplicableRegions(BukkitAdapter.adapt(location)); - @Override - public void addBlockState(BlockState blockState) { - add(blockState.getLocation(), blockState.getType().toString()); - } + applicableRegions.forEach(region -> { + ProtectedRegionEntry protectedRegionEntry = new ProtectedRegionEntry(location.getWorld(), region); + if (!resultEntryMap.containsKey(protectedRegionEntry)) { + resultEntryMap.put(protectedRegionEntry, new SearchResultEntry<>(protectedRegionEntry)); + } + resultEntryMap.get(protectedRegionEntry).increment(type); + }); + }; - @Override - public void add(Location location, String type) { - RegionQuery query = WorldGuard.getInstance().getPlatform().getRegionContainer().createQuery(); - ApplicableRegionSet applicableRegions = query.getApplicableRegions(BukkitAdapter.adapt(location)); + if (FoliaScheduler.isFolia()) { + FoliaScheduler.getRegionScheduler().run(PLUGIN, location, $ -> runnable.run()); + return; + } - applicableRegions.forEach(region -> { - ProtectedRegionEntry protectedRegionEntry = new ProtectedRegionEntry(location.getWorld(), region); - if(!resultEntryMap.containsKey(protectedRegionEntry)) { - resultEntryMap.put(protectedRegionEntry, new SearchResultEntry<>(protectedRegionEntry)); - } - resultEntryMap.get(protectedRegionEntry).increment(type); - }); + runnable.run(); } @Override diff --git a/src/main/java/de/themoep/entitydetection/util/folia/AsyncScheduler.java b/src/main/java/de/themoep/entitydetection/util/folia/AsyncScheduler.java new file mode 100644 index 0000000..21c322d --- /dev/null +++ b/src/main/java/de/themoep/entitydetection/util/folia/AsyncScheduler.java @@ -0,0 +1,140 @@ +/* + * This file is part of packetevents - https://github.com/retrooper/packetevents + * Copyright (C) 2024 retrooper 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 . + */ + +package de.themoep.entitydetection.util.folia; + +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitScheduler; +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +/** + * Represents a scheduler for executing tasks asynchronously. + */ +public class AsyncScheduler { + + private BukkitScheduler bukkitScheduler; + private io.papermc.paper.threadedregions.scheduler.AsyncScheduler asyncScheduler; + + protected AsyncScheduler() { + if (FoliaScheduler.isFolia) { + asyncScheduler = Bukkit.getAsyncScheduler(); + } else { + bukkitScheduler = Bukkit.getScheduler(); + } + } + + /** + * Schedules the specified task to be executed asynchronously immediately. + * + * @param plugin Plugin which owns the specified task. + * @param task Specified task. + * @return {@link TaskWrapper} instance representing a wrapped task + */ + public TaskWrapper runNow(@NotNull Plugin plugin, @NotNull Consumer task) { + if (!FoliaScheduler.isFolia) { + return new TaskWrapper(bukkitScheduler.runTaskAsynchronously(plugin, () -> task.accept(null))); + } + + return new TaskWrapper(asyncScheduler.runNow(plugin, (o) -> task.accept(null))); + } + + /** + * Schedules the specified task to be executed asynchronously after the specified delay. + * + * @param plugin Plugin which owns the specified task. + * @param task Specified task. + * @param delay The time delay to pass before the task should be executed. + * @param timeUnit The time unit for the time delay. + * @return {@link TaskWrapper} instance representing a wrapped task + */ + public TaskWrapper runDelayed(@NotNull Plugin plugin, @NotNull Consumer task, long delay, @NotNull TimeUnit timeUnit) { + if (!FoliaScheduler.isFolia) { + return new TaskWrapper(bukkitScheduler.runTaskLaterAsynchronously(plugin, () -> task.accept(null), convertTimeToTicks(delay, timeUnit))); + } + + return new TaskWrapper(asyncScheduler.runDelayed(plugin, (o) -> task.accept(null), delay, timeUnit)); + } + + /** + * Schedules the specified task to be executed asynchronously after the initial delay has passed, and then periodically executed with the specified period. + * + * @param plugin Plugin which owns the specified task. + * @param task Specified task. + * @param delay The time delay to pass before the task should be executed. + * @param period The time period between each task execution. Any value less-than 1 is treated as 1. + * @param timeUnit The time unit for the initial delay and period. + * @return {@link TaskWrapper} instance representing a wrapped task + */ + public TaskWrapper runAtFixedRate(@NotNull Plugin plugin, @NotNull Consumer task, long delay, long period, @NotNull TimeUnit timeUnit) { + if (period < 1) period = 1; + + if (!FoliaScheduler.isFolia) { + return new TaskWrapper(bukkitScheduler.runTaskTimerAsynchronously(plugin, () -> task.accept(null), convertTimeToTicks(delay, timeUnit), convertTimeToTicks(period, timeUnit))); + } + + return new TaskWrapper(asyncScheduler.runAtFixedRate(plugin, (o) -> task.accept(null), delay, period, timeUnit)); + } + + /** + * Schedules the specified task to be executed asynchronously after the initial delay has passed, and then periodically executed. + * + * @param plugin Plugin which owns the specified task. + * @param task Specified task. + * @param initialDelayTicks The time delay in ticks to pass before the task should be executed. + * @param periodTicks The time period in ticks between each task execution. Any value less-than 1 is treated as 1. + * @return {@link TaskWrapper} instance representing a wrapped task + */ + public TaskWrapper runAtFixedRate(@NotNull Plugin plugin, @NotNull Consumer task, long initialDelayTicks, long periodTicks) { + if (periodTicks < 1) periodTicks = 1; + + if (!FoliaScheduler.isFolia) { + return new TaskWrapper(bukkitScheduler.runTaskTimerAsynchronously(plugin, () -> task.accept(null), initialDelayTicks, periodTicks)); + } + + return new TaskWrapper(asyncScheduler.runAtFixedRate(plugin, (o) -> task.accept(null), initialDelayTicks * 50, periodTicks * 50, TimeUnit.MILLISECONDS)); + } + + /** + * Attempts to cancel all tasks scheduled by the specified plugin. + * + * @param plugin Specified plugin. + */ + public void cancel(@NotNull Plugin plugin) { + if (!FoliaScheduler.isFolia) { + bukkitScheduler.cancelTasks(plugin); + return; + } + + asyncScheduler.cancelTasks(plugin); + } + + /** + * Converts the specified time to ticks. + * + * @param time The time to convert. + * @param timeUnit The time unit of the time. + * @return The time converted to ticks. + */ + private long convertTimeToTicks(long time, TimeUnit timeUnit) { + return timeUnit.toMillis(time) / 50; + } +} \ No newline at end of file diff --git a/src/main/java/de/themoep/entitydetection/util/folia/EntityScheduler.java b/src/main/java/de/themoep/entitydetection/util/folia/EntityScheduler.java new file mode 100644 index 0000000..58a2a96 --- /dev/null +++ b/src/main/java/de/themoep/entitydetection/util/folia/EntityScheduler.java @@ -0,0 +1,135 @@ +/* + * This file is part of packetevents - https://github.com/retrooper/packetevents + * Copyright (C) 2024 retrooper 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 . + */ + +package de.themoep.entitydetection.util.folia; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Entity; +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitScheduler; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.function.Consumer; + +/** + * Represents a scheduler for executing entity tasks. + */ +public class EntityScheduler { + private BukkitScheduler bukkitScheduler; + + protected EntityScheduler() { + if (!de.themoep.entitydetection.util.folia.FoliaScheduler.isFolia) { + bukkitScheduler = Bukkit.getScheduler(); + } + } + + /** + * Schedules a task with the given delay. If the task failed to schedule because the scheduler is retired (entity removed), then returns false. + * Otherwise, either the run callback will be invoked after the specified delay, or the retired callback will be invoked if the scheduler is retired. + * Note that the retired callback is invoked in critical code, so it should not attempt to remove the entity, + * remove other entities, load chunks, load worlds, modify ticket levels, etc. + *

+ * It is guaranteed that the run and retired callback are invoked on the region which owns the entity. + * + * @param plugin Plugin which owns the specified task. + * @param run The callback to run after the specified delay, may not be null. + * @param retired Retire callback to run if the entity is retired before the run callback can be invoked, may be null. + * @param delay The delay in ticks before the run callback is invoked. + */ + public void execute(@NotNull Entity entity, @NotNull Plugin plugin, @NotNull Runnable run, @Nullable Runnable retired, long delay) { + if (!de.themoep.entitydetection.util.folia.FoliaScheduler.isFolia) { + bukkitScheduler.runTaskLater(plugin, run, delay); + return; + } + + entity.getScheduler().execute(plugin, run, retired, delay); + } + + /** + * Schedules a task to execute on the next tick. If the task failed to schedule because the scheduler is retired (entity removed), + * then returns null. Otherwise, either the task callback will be invoked after the specified delay, + * or the retired callback will be invoked if the scheduler is retired. + * Note that the retired callback is invoked in critical code, so it should not attempt to remove the entity, + * remove other entities, load chunks, load worlds, modify ticket levels, etc. + *

+ * It is guaranteed that the task and retired callback are invoked on the region which owns the entity. + * + * @param plugin The plugin that owns the task + * @param task The task to execute + * @param retired Retire callback to run if the entity is retired before the run callback can be invoked, may be null. + * @return {@link TaskWrapper} instance representing a wrapped task + */ + public TaskWrapper run(@NotNull Entity entity, @NotNull Plugin plugin, @NotNull Consumer task, @Nullable Runnable retired) { + if (!de.themoep.entitydetection.util.folia.FoliaScheduler.isFolia) { + return new TaskWrapper(bukkitScheduler.runTask(plugin, () -> task.accept(null))); + } + + return new TaskWrapper(entity.getScheduler().run(plugin, (o) -> task.accept(null), retired)); + } + + /** + * Schedules a task with the given delay. If the task failed to schedule because the scheduler is retired (entity removed), + * then returns null. Otherwise, either the task callback will be invoked after the specified delay, or the retired callback will be invoked if the scheduler is retired. + * Note that the retired callback is invoked in critical code, so it should not attempt to remove the entity, + * remove other entities, load chunks, load worlds, modify ticket levels, etc. + *

+ * It is guaranteed that the task and retired callback are invoked on the region which owns the entity. + * + * @param plugin The plugin that owns the task + * @param task The task to execute + * @param retired Retire callback to run if the entity is retired before the run callback can be invoked, may be null. + * @param delayTicks The delay in ticks before the run callback is invoked. Any value less-than 1 is treated as 1. + * @return {@link TaskWrapper} instance representing a wrapped task + */ + public TaskWrapper runDelayed(@NotNull Entity entity, @NotNull Plugin plugin, @NotNull Consumer task, @Nullable Runnable retired, long delayTicks) { + if (delayTicks < 1) delayTicks = 1; + + if (!de.themoep.entitydetection.util.folia.FoliaScheduler.isFolia) { + return new TaskWrapper(bukkitScheduler.runTaskLater(plugin, () -> task.accept(null), delayTicks)); + } + + return new TaskWrapper(entity.getScheduler().runDelayed(plugin, (o) -> task.accept(null), retired, delayTicks)); + } + + /** + * Schedules a repeating task with the given delay and period. If the task failed to schedule because the scheduler is retired (entity removed), + * then returns null. Otherwise, either the task callback will be invoked after the specified delay, or the retired callback will be invoked if the scheduler is retired. + * Note that the retired callback is invoked in critical code, so it should not attempt to remove the entity, + * remove other entities, load chunks, load worlds, modify ticket levels, etc. + *

+ * It is guaranteed that the task and retired callback are invoked on the region which owns the entity. + * + * @param plugin The plugin that owns the task + * @param task The task to execute + * @param retired Retire callback to run if the entity is retired before the run callback can be invoked, may be null. + * @param initialDelayTicks The initial delay, in ticks before the method is invoked. Any value less-than 1 is treated as 1. + * @param periodTicks The period, in ticks. Any value less-than 1 is treated as 1. + * @return {@link TaskWrapper} instance representing a wrapped task + */ + public TaskWrapper runAtFixedRate(@NotNull Entity entity, @NotNull Plugin plugin, @NotNull Consumer task, @Nullable Runnable retired, long initialDelayTicks, long periodTicks) { + if (initialDelayTicks < 1) initialDelayTicks = 1; + if (periodTicks < 1) periodTicks = 1; + + if (!de.themoep.entitydetection.util.folia.FoliaScheduler.isFolia) { + return new TaskWrapper(bukkitScheduler.runTaskTimer(plugin, () -> task.accept(null), initialDelayTicks, periodTicks)); + } + + return new TaskWrapper(entity.getScheduler().runAtFixedRate(plugin, (o) -> task.accept(null), retired, initialDelayTicks, periodTicks)); + } +} \ No newline at end of file diff --git a/src/main/java/de/themoep/entitydetection/util/folia/FoliaScheduler.java b/src/main/java/de/themoep/entitydetection/util/folia/FoliaScheduler.java new file mode 100644 index 0000000..c8daa5b --- /dev/null +++ b/src/main/java/de/themoep/entitydetection/util/folia/FoliaScheduler.java @@ -0,0 +1,130 @@ +/* + * This file is part of packetevents - https://github.com/retrooper/packetevents + * Copyright (C) 2024 retrooper 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 . + */ + +package de.themoep.entitydetection.util.folia; + +import org.bukkit.Bukkit; +import org.bukkit.event.Event; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.plugin.Plugin; + +/** + * Utility class to handle scheduling tasks. + * It uses Paper's threaded-regions schedulers if Folia is used, + * otherwise it falls back to the default Bukkit scheduler. + */ +public class FoliaScheduler { + static final boolean isFolia; + private static Class regionizedServerInitEventClass; + + private static AsyncScheduler asyncScheduler; + private static EntityScheduler entityScheduler; + private static GlobalRegionScheduler globalRegionScheduler; + private static RegionScheduler regionScheduler; + + static { + boolean folia; + try { + Class.forName("io.papermc.paper.threadedregions.RegionizedServer"); + folia = true; + + // Thanks for this code ViaVersion + // The class is only part of the Folia API, so we need to use reflections to get it + regionizedServerInitEventClass = (Class) Class.forName("io.papermc.paper.threadedregions.RegionizedServerInitEvent"); + } catch (ClassNotFoundException e) { + folia = false; + } + + isFolia = folia; + } + + /** + * @return Whether the server is running Folia + */ + public static boolean isFolia() { + return isFolia; + } + + /** + * Returns the async scheduler. + * + * @return async scheduler instance of {@link AsyncScheduler} + */ + public static AsyncScheduler getAsyncScheduler() { + if (asyncScheduler == null) { + asyncScheduler = new AsyncScheduler(); + } + return asyncScheduler; + } + + /** + * Returns the entity scheduler. + * + * @return entity scheduler instance of {@link EntityScheduler} + */ + public static EntityScheduler getEntityScheduler() { + if (entityScheduler == null) { + entityScheduler = new EntityScheduler(); + } + return entityScheduler; + } + + /** + * Returns the global region scheduler. + * + * @return global region scheduler instance of {@link GlobalRegionScheduler} + */ + public static GlobalRegionScheduler getGlobalRegionScheduler() { + if (globalRegionScheduler == null) { + globalRegionScheduler = new GlobalRegionScheduler(); + } + return globalRegionScheduler; + } + + /** + * Returns the region scheduler. + * + * @return region scheduler instance of {@link RegionScheduler} + */ + public static RegionScheduler getRegionScheduler() { + if (regionScheduler == null) { + regionScheduler = new RegionScheduler(); + } + return regionScheduler; + } + + /** + * Run a task after the server has finished initializing. + * Undefined behavior if called after the server has finished initializing. + *

+ * We still need to use reflections to get the server init event class, as this is only part of the Folia API. + * + * @param plugin Your plugin or PacketEvents + * @param run The task to run + */ + public static void runTaskOnInit(Plugin plugin, Runnable run) { + if (!isFolia) { + Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, run); + return; + } + + Bukkit.getServer().getPluginManager().registerEvent(regionizedServerInitEventClass, new Listener() { + }, EventPriority.HIGHEST, (listener, event) -> run.run(), plugin); + } +} \ No newline at end of file diff --git a/src/main/java/de/themoep/entitydetection/util/folia/GlobalRegionScheduler.java b/src/main/java/de/themoep/entitydetection/util/folia/GlobalRegionScheduler.java new file mode 100644 index 0000000..1aeb578 --- /dev/null +++ b/src/main/java/de/themoep/entitydetection/util/folia/GlobalRegionScheduler.java @@ -0,0 +1,125 @@ +/* + * This file is part of packetevents - https://github.com/retrooper/packetevents + * Copyright (C) 2024 retrooper 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 . + */ + +package de.themoep.entitydetection.util.folia; + +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitScheduler; +import org.jetbrains.annotations.NotNull; + +import java.util.function.Consumer; + +/** + * Represents a scheduler for executing global region tasks. + */ +public class GlobalRegionScheduler { + + private BukkitScheduler bukkitScheduler; + private io.papermc.paper.threadedregions.scheduler.GlobalRegionScheduler globalRegionScheduler; + + protected GlobalRegionScheduler() { + if (FoliaScheduler.isFolia) { + globalRegionScheduler = Bukkit.getGlobalRegionScheduler(); + } else { + bukkitScheduler = Bukkit.getScheduler(); + } + } + + /** + * Schedules a task to be executed on the global region. + * + * @param plugin The plugin that owns the task + * @param run The task to execute + */ + public void execute(@NotNull Plugin plugin, @NotNull Runnable run) { + if (!FoliaScheduler.isFolia) { + bukkitScheduler.runTask(plugin, run); + return; + } + + globalRegionScheduler.execute(plugin, run); + } + + /** + * Schedules a task to be executed on the global region. + * + * @param plugin The plugin that owns the task + * @param task The task to execute + * @return {@link TaskWrapper} instance representing a wrapped task + */ + public TaskWrapper run(@NotNull Plugin plugin, @NotNull Consumer task) { + if (!FoliaScheduler.isFolia) { + return new TaskWrapper(bukkitScheduler.runTask(plugin, () -> task.accept(null))); + } + + return new TaskWrapper(globalRegionScheduler.run(plugin, (o) -> task.accept(null))); + } + + /** + * Schedules a task to be executed on the global region after the specified delay in ticks. + * + * @param plugin The plugin that owns the task + * @param task The task to execute + * @param delay The delay, in ticks before the method is invoked. Any value less-than 1 is treated as 1. + * @return {@link TaskWrapper} instance representing a wrapped task + */ + public TaskWrapper runDelayed(@NotNull Plugin plugin, @NotNull Consumer task, long delay) { + if (delay < 1) delay = 1; + + if (!FoliaScheduler.isFolia) { + return new TaskWrapper(bukkitScheduler.runTaskLater(plugin, () -> task.accept(null), delay)); + } + + return new TaskWrapper(globalRegionScheduler.runDelayed(plugin, (o) -> task.accept(null), delay)); + } + + /** + * Schedules a repeating task to be executed on the global region after the initial delay with the specified period. + * + * @param plugin The plugin that owns the task + * @param task The task to execute + * @param initialDelayTicks The initial delay, in ticks before the method is invoked. Any value less-than 1 is treated as 1. + * @param periodTicks The period, in ticks. Any value less-than 1 is treated as 1. + * @return {@link TaskWrapper} instance representing a wrapped task + */ + public TaskWrapper runAtFixedRate(@NotNull Plugin plugin, @NotNull Consumer task, long initialDelayTicks, long periodTicks) { + if (initialDelayTicks < 1) initialDelayTicks = 1; + if (periodTicks < 1) periodTicks = 1; + + if (!FoliaScheduler.isFolia) { + return new TaskWrapper(bukkitScheduler.runTaskTimer(plugin, () -> task.accept(null), initialDelayTicks, periodTicks)); + } + + return new TaskWrapper(globalRegionScheduler.runAtFixedRate(plugin, (o) -> task.accept(null), initialDelayTicks, periodTicks)); + } + + /** + * Attempts to cancel all tasks scheduled by the specified plugin. + * + * @param plugin Specified plugin. + */ + public void cancel(@NotNull Plugin plugin) { + if (!FoliaScheduler.isFolia) { + Bukkit.getScheduler().cancelTasks(plugin); + return; + } + + globalRegionScheduler.cancelTasks(plugin); + } +} \ No newline at end of file diff --git a/src/main/java/de/themoep/entitydetection/util/folia/RegionScheduler.java b/src/main/java/de/themoep/entitydetection/util/folia/RegionScheduler.java new file mode 100644 index 0000000..da3ef67 --- /dev/null +++ b/src/main/java/de/themoep/entitydetection/util/folia/RegionScheduler.java @@ -0,0 +1,197 @@ +/* + * This file is part of packetevents - https://github.com/retrooper/packetevents + * Copyright (C) 2024 retrooper 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 . + */ + +package de.themoep.entitydetection.util.folia; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitScheduler; +import org.jetbrains.annotations.NotNull; + +import java.util.function.Consumer; + +/** + * Represents a scheduler for executing region tasks + */ +public class RegionScheduler { + + private BukkitScheduler bukkitScheduler; + private io.papermc.paper.threadedregions.scheduler.RegionScheduler regionScheduler; + + protected RegionScheduler() { + if (FoliaScheduler.isFolia) { + regionScheduler = Bukkit.getRegionScheduler(); + } else { + bukkitScheduler = Bukkit.getScheduler(); + } + } + + /** + * Schedules a task to be executed on the region which owns the location. + * + * @param plugin The plugin that owns the task + * @param world The world of the region that owns the task + * @param chunkX The chunk X coordinate of the region that owns the task + * @param chunkZ The chunk Z coordinate of the region that owns the task + * @param run The task to execute + */ + public void execute(@NotNull Plugin plugin, @NotNull World world, int chunkX, int chunkZ, @NotNull Runnable run) { + if (!FoliaScheduler.isFolia) { + bukkitScheduler.runTask(plugin, run); + return; + } + + regionScheduler.execute(plugin, world, chunkX, chunkZ, run); + } + + /** + * Schedules a task to be executed on the region which owns the location. + * + * @param plugin The plugin that owns the task + * @param location The location at which the region executing should own + * @param run The task to execute + */ + public void execute(@NotNull Plugin plugin, @NotNull Location location, @NotNull Runnable run) { + if (!FoliaScheduler.isFolia) { + Bukkit.getScheduler().runTask(plugin, run); + return; + } + + regionScheduler.execute(plugin, location, run); + } + + /** + * Schedules a task to be executed on the region which owns the location on the next tick. + * + * @param plugin The plugin that owns the task + * @param world The world of the region that owns the task + * @param chunkX The chunk X coordinate of the region that owns the task + * @param chunkZ The chunk Z coordinate of the region that owns the task + * @param task The task to execute + * @return {@link TaskWrapper} instance representing a wrapped task + */ + public TaskWrapper run(@NotNull Plugin plugin, @NotNull World world, int chunkX, int chunkZ, @NotNull Consumer task) { + if (!FoliaScheduler.isFolia) { + return new TaskWrapper(Bukkit.getScheduler().runTask(plugin, () -> task.accept(null))); + } + + return new TaskWrapper(regionScheduler.run(plugin, world, chunkX, chunkZ, (o) -> task.accept(null))); + } + + /** + * Schedules a task to be executed on the region which owns the location on the next tick. + * + * @param plugin The plugin that owns the task + * @param location The location at which the region executing should own + * @param task The task to execute + * @return {@link TaskWrapper} instance representing a wrapped task + */ + public TaskWrapper run(@NotNull Plugin plugin, @NotNull Location location, @NotNull Consumer task) { + if (!FoliaScheduler.isFolia) { + return new TaskWrapper(Bukkit.getScheduler().runTask(plugin, () -> task.accept(null))); + } + + return new TaskWrapper(regionScheduler.run(plugin, location, (o) -> task.accept(null))); + } + + /** + * Schedules a task to be executed on the region which owns the location after the specified delay in ticks. + * + * @param plugin The plugin that owns the task + * @param world The world of the region that owns the task + * @param chunkX The chunk X coordinate of the region that owns the task + * @param chunkZ The chunk Z coordinate of the region that owns the task + * @param task The task to execute + * @param delayTicks The delay, in ticks before the method is invoked. Any value less-than 1 is treated as 1. + * @return {@link TaskWrapper} instance representing a wrapped task + */ + public TaskWrapper runDelayed(@NotNull Plugin plugin, @NotNull World world, int chunkX, int chunkZ, @NotNull Consumer task, long delayTicks) { + if (delayTicks < 1) delayTicks = 1; + + if (!FoliaScheduler.isFolia) { + return new TaskWrapper(Bukkit.getScheduler().runTaskLater(plugin, () -> task.accept(null), delayTicks)); + } + + return new TaskWrapper(regionScheduler.runDelayed(plugin, world, chunkX, chunkZ, (o) -> task.accept(null), delayTicks)); + } + + /** + * Schedules a task to be executed on the region which owns the location after the specified delay in ticks. + * + * @param plugin The plugin that owns the task + * @param location The location at which the region executing should own + * @param task The task to execute + * @param delayTicks The delay, in ticks before the method is invoked. Any value less-than 1 is treated as 1. + * @return {@link TaskWrapper} instance representing a wrapped task + */ + public TaskWrapper runDelayed(@NotNull Plugin plugin, @NotNull Location location, @NotNull Consumer task, long delayTicks) { + if (delayTicks < 1) delayTicks = 1; + + if (!FoliaScheduler.isFolia) { + return new TaskWrapper(Bukkit.getScheduler().runTaskLater(plugin, () -> task.accept(null), delayTicks)); + } + + return new TaskWrapper(regionScheduler.runDelayed(plugin, location, (o) -> task.accept(null), delayTicks)); + } + + /** + * Schedules a repeating task to be executed on the region which owns the location after the initial delay with the specified period. + * + * @param plugin The plugin that owns the task + * @param world The world of the region that owns the task + * @param chunkX The chunk X coordinate of the region that owns the task + * @param chunkZ The chunk Z coordinate of the region that owns the task + * @param task The task to execute + * @param initialDelayTicks The initial delay, in ticks before the method is invoked. Any value less-than 1 is treated as 1. + * @param periodTicks The period, in ticks. Any value less-than 1 is treated as 1. + * @return {@link TaskWrapper} instance representing a wrapped task + */ + public TaskWrapper runAtFixedRate(@NotNull Plugin plugin, @NotNull World world, int chunkX, int chunkZ, @NotNull Consumer task, long initialDelayTicks, long periodTicks) { + if (initialDelayTicks < 1) initialDelayTicks = 1; + if (periodTicks < 1) periodTicks = 1; + + if (!FoliaScheduler.isFolia) { + return new TaskWrapper(Bukkit.getScheduler().runTaskTimer(plugin, () -> task.accept(null), initialDelayTicks, periodTicks)); + } + + return new TaskWrapper(regionScheduler.runAtFixedRate(plugin, world, chunkX, chunkZ, (o) -> task.accept(null), initialDelayTicks, periodTicks)); + } + + /** + * Schedules a repeating task to be executed on the region which owns the location after the initial delay with the specified period. + * + * @param plugin The plugin that owns the task + * @param location The location at which the region executing should own + * @param task The task to execute + * @param initialDelayTicks The initial delay, in ticks before the method is invoked. Any value less-than 1 is treated as 1. + * @param periodTicks The period, in ticks. Any value less-than 1 is treated as 1. + * @return {@link TaskWrapper} instance representing a wrapped task + */ + public TaskWrapper runAtFixedRate(@NotNull Plugin plugin, @NotNull Location location, @NotNull Consumer task, long initialDelayTicks, long periodTicks) { + if (initialDelayTicks < 1) initialDelayTicks = 1; + if (periodTicks < 1) periodTicks = 1; + + if (!FoliaScheduler.isFolia) { + return new TaskWrapper(Bukkit.getScheduler().runTaskTimer(plugin, () -> task.accept(null), initialDelayTicks, periodTicks)); + } + + return new TaskWrapper(regionScheduler.runAtFixedRate(plugin, location, (o) -> task.accept(null), initialDelayTicks, periodTicks)); + } +} \ No newline at end of file diff --git a/src/main/java/de/themoep/entitydetection/util/folia/TaskWrapper.java b/src/main/java/de/themoep/entitydetection/util/folia/TaskWrapper.java new file mode 100644 index 0000000..8388c4d --- /dev/null +++ b/src/main/java/de/themoep/entitydetection/util/folia/TaskWrapper.java @@ -0,0 +1,64 @@ +package de.themoep.entitydetection.util.folia; + +import io.papermc.paper.threadedregions.scheduler.ScheduledTask; +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitTask; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a wrapper around {@code BukkitTask} and Paper's {@code ScheduledTask}. + * This class provides a unified interface for interacting with both Bukkit's task scheduler + * and Paper's task scheduler. + */ +public class TaskWrapper { + + private BukkitTask bukkitTask; + private ScheduledTask scheduledTask; + + /** + * Constructs a new TaskWrapper around a BukkitTask. + * + * @param bukkitTask the BukkitTask to wrap + */ + public TaskWrapper(@NotNull BukkitTask bukkitTask) { + this.bukkitTask = bukkitTask; + } + + /** + * Constructs a new TaskWrapper around Paper's ScheduledTask. + * + * @param scheduledTask the ScheduledTask to wrap + */ + public TaskWrapper(@NotNull ScheduledTask scheduledTask) { + this.scheduledTask = scheduledTask; + } + + /** + * Retrieves the Plugin that owns this task. + * + * @return the owning {@link Plugin} + */ + public Plugin getOwner() { + return bukkitTask != null ? bukkitTask.getOwner() : scheduledTask.getOwningPlugin(); + } + + /** + * Checks if the task is canceled. + * + * @return true if the task is canceled, false otherwise + */ + public boolean isCancelled() { + return bukkitTask != null ? bukkitTask.isCancelled() : scheduledTask.isCancelled(); + } + + /** + * Cancels the task. If the task is running, it will be canceled. + */ + public void cancel() { + if (bukkitTask != null) { + bukkitTask.cancel(); + } else { + scheduledTask.cancel(); + } + } +}