Compare commits

...

28 Commits

Author SHA1 Message Date
Funnycube
f14b081f0b Add modrinth 2025-08-15 17:06:52 +10:00
PiggyPiglet
b51fbf4e13 Fix jenkins build
disableAutoTargetJvm in gradle
2025-07-03 13:40:53 +08:00
PiggyPiglet
e48b6fe702 Update NMSVersion enum 2025-07-01 20:44:21 +08:00
PiggyPiglet
984f944daf Merge pull request #1127 from PlaceholderAPI/folia
Merge UniversalScheduler (Folia Support) #980
2025-07-01 20:24:13 +08:00
PiggyPiglet
0575c8cf41 Merge UniversalScheduler 2025-07-01 09:39:48 +08:00
PiggyPiglet
c181d18f5e Revert "Merge pull request #1092 from CoderKuo/master"
This reverts commit 2e0d9eb846, reversing
changes made to 33d1009d6a.
2025-06-16 11:08:43 +08:00
PiggyPiglet
2e0d9eb846 Merge pull request #1092 from CoderKuo/master
Optimize text replacement algorithm performance
2025-04-21 15:23:40 +08:00
PiggyPiglet
33d1009d6a Merge pull request #1095 from Kqliber/fix/1094
fix for all expansions stop loading after an exception is thrown (#1094)
2025-03-30 20:46:34 +08:00
PiggyPiglet
b16d62fb75 fix maven repo publishing (maybe?) 2025-03-30 20:41:51 +08:00
Funnycube
e019d6370c Merge pull request #1098 from Funny-cube/master
Revert api links
2025-01-16 13:31:08 +11:00
Funnycube
581b73dbc7 revert home link 2025-01-16 13:28:43 +11:00
Funnycube
25f7eafe32 revert api link 2025-01-16 13:27:45 +11:00
Kqliber
ef22d564f3 remove imports 2025-01-08 18:23:49 +00:00
Kqliber
787a053d98 fix for all expansions stop loading after an exception is thrown (#1094) 2025-01-08 18:21:43 +00:00
大阔
f31dd2bea9 Add fast path check for closure.head and closure.tail 2025-01-03 03:18:40 +08:00
大阔
5c8086150a Optimize text replacement algorithm performance 2025-01-03 02:23:17 +08:00
PiggyPiglet
b838d1c52a Update ecloud api link 2024-11-27 19:58:05 +08:00
Andre_601
0a712e6530 Fix API version badge + update flex 2024-11-13 14:40:41 +01:00
Gabriel Dumitru
272e2e7904 Merge pull request #1000 from mfnalex/master
Added missing NotNull annotations to setBracketPlaceholders methods
2024-07-10 22:35:49 +03:00
PiggyPiglet
98082398fc Target java release 8 for compilation 2024-07-04 17:46:04 +08:00
PiggyPiglet
c66806ecf8 Merge pull request #1067 from PlaceholderAPI/feat/1.21
feat: initial work on 1.21
2024-07-04 17:26:16 +08:00
Andre_601
c97f5aa5a6 Bump download stats 2024-06-18 22:45:15 +02:00
Glare
c1487898a9 chore(deps): update gradle + shadow fork 2024-06-14 10:07:06 -05:00
Glare
907ced6d7d feat: initial work on 1.21 2024-06-13 18:08:58 -05:00
PiggyPiglet
ac771207c3 bump to dev version 2024-05-21 18:51:05 +08:00
mfnalex
32f3d14682 added missing @NotNull to the returned List's type parameter and the parameter List's type parameter (is that proper English?) 2023-08-29 00:57:42 +02:00
mfnalex
152105017d added missing @NotNull to return value and String parameter of setBracketPlaceholders(Player, String) 2023-08-29 00:56:12 +02:00
mfnalex
be956f52b0 added missing @NotNull to return value and List parameter of setBracketPlaceholders(Player, List) 2023-08-29 00:55:28 +02:00
24 changed files with 1235 additions and 44 deletions

View File

@@ -9,6 +9,7 @@
[spigot]: https://www.spigotmc.org/resources/6245/
[hangar]: https://hangar.papermc.io/HelpChat/PlaceholderAPI
[bbb]: https://builtbybit.com/resources/placeholderapi.24306
[modrinth]: https://modrinth.com/plugin/placeholderapi
[Expansions cloud]: https://api.extendedclip.com/home
[placeholder list]: https://helpch.at/placeholders
[statistics]: https://bstats.org/plugin/bukkit/PlaceholderAPI
@@ -16,7 +17,7 @@
[ci]: http://ci.extendedclip.com/job/PlaceholderAPI/
[ciImg]: http://ci.extendedclip.com/buildStatus/icon?job=PlaceholderAPI
[APIversionImg]: https://img.shields.io/nexus/placeholderapi/me.clip/placeholderapi?server=https%3A%2F%2Frepo.extendedclip.com&label=API%20Version
[APIversionImg]: https://repo.extendedclip.com/api/badge/latest/releases/me/clip/placeholderapi?name=API%20Version
[logo]: https://wiki.placeholderapi.com/assets/img/papi-logo.png
[contributing]: https://github.com/PlaceholderAPI/PlaceholderAPI/blob/master/.github/CONTRIBUTING.md
@@ -32,7 +33,7 @@
Support for specific plugins are provided either by the plugin itself or through expansions. The expansions may be downloaded in-game through the PAPI Expansion Cloud. There are currently over 240+ expansions that support a wide variety of plugins, such as Essentials, Factions, LuckPerms, and Vault.
PlaceholderAPI has been downloaded over 1,000,000 times and has been used concurrently on over 40,000 servers, which makes it a must-have for a server of any type or scale.
PlaceholderAPI has been downloaded over 1,700,000 times on Spigot and has been used concurrently on over 45,000 servers, which makes it a must-have for a server of any type or scale.
## Contribute
If you would like to contribute towards PlaceholderAPI should you take a look at our [Contributing file][contributing] for the ins and outs on how you can do that and what you need to keep in mind.
@@ -51,4 +52,5 @@ If you would like to create your own Placeholder Expansion for PlaceholderAPI, t
- [Spigot Page][spigot]
- [Hangar Page][hangar]
- [BuiltByBit Page][bbb]
- [Modrinth Page][modrinth]
- [Plugin Statistics][statistics]

View File

@@ -4,11 +4,11 @@ plugins {
`java-library`
`maven-publish`
id("com.github.hierynomus.license") version "0.16.1"
id("com.github.johnrengelman.shadow") version "8.1.0"
id("io.github.goooler.shadow") version "8.1.7"
}
group = "me.clip"
version = "2.11.6"
version = "2.11.7-DEV-${System.getProperty("BUILD_NUMBER")}"
description = "An awesome placeholder provider!"
@@ -20,13 +20,15 @@ repositories {
maven("https://repo.codemc.org/repository/maven-public/")
maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/")
maven("https://repo.papermc.io/repository/maven-public/")
}
dependencies {
implementation("org.bstats:bstats-bukkit:3.0.1")
implementation("net.kyori:adventure-platform-bukkit:4.3.2")
implementation("net.kyori:adventure-platform-bukkit:4.3.3")
compileOnly("org.spigotmc:spigot-api:1.20-R0.1-SNAPSHOT")
//compileOnly("org.spigotmc:spigot-api:1.21-R0.1-SNAPSHOT")
compileOnly("dev.folia:folia-api:1.20.1-R0.1-SNAPSHOT")
compileOnlyApi("org.jetbrains:annotations:23.0.0")
testImplementation("org.openjdk.jmh:jmh-core:1.32")
@@ -43,6 +45,8 @@ java {
withJavadocJar()
withSourcesJar()
disableAutoTargetJvm()
}
license {
@@ -71,6 +75,7 @@ tasks {
withType<JavaCompile> {
options.encoding = "UTF-8"
options.release = 8
}
withType<Javadoc> {
@@ -107,9 +112,9 @@ tasks {
repositories {
maven {
if ("-DEV" in version.toString()) {
url = uri("https://repo.extendedclip.com/content/repositories/dev/")
url = uri("https://repo.extendedclip.com/snapshots")
} else {
url = uri("https://repo.extendedclip.com/content/repositories/placeholderapi/")
url = uri("https://repo.extendedclip.com/releases")
}
credentials {

Binary file not shown.

View File

@@ -1,5 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

28
gradlew vendored
View File

@@ -1,7 +1,7 @@
#!/bin/sh
#
# Copyright <EFBFBD> 2015-2021 the original authors.
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -32,10 +32,10 @@
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions <EFBFBD>$var<EFBFBD>, <EFBFBD>${var}<EFBFBD>, <EFBFBD>${var:-default}<EFBFBD>, <EFBFBD>${var+SET}<EFBFBD>,
# <EFBFBD>${var#prefix}<EFBFBD>, <EFBFBD>${var%suffix}<EFBFBD>, and <EFBFBD>$( cmd )<EFBFBD>;
# * compound commands having a testable exit status, especially <EFBFBD>case<EFBFBD>;
# * various built-in commands including <EFBFBD>command<EFBFBD>, <EFBFBD>set<EFBFBD>, and <EFBFBD>ulimit<EFBFBD>.
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
@@ -55,7 +55,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -80,10 +80,10 @@ do
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
@@ -143,12 +143,16 @@ fi
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -205,6 +209,12 @@ set -- \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.

15
gradlew.bat vendored
View File

@@ -14,7 +14,7 @@
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@@ -25,7 +25,8 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal

View File

@@ -136,8 +136,8 @@ public final class PlaceholderAPI {
* @return String containing all translated placeholders
*/
@NotNull
public static List<String> setBracketPlaceholders(final OfflinePlayer player,
@NotNull final List<String> text) {
public static List<@NotNull String> setBracketPlaceholders(final OfflinePlayer player,
@NotNull final List<@NotNull String> text) {
return text.stream().map(line -> setBracketPlaceholders(player, line))
.collect(Collectors.toList());
}
@@ -150,7 +150,8 @@ public final class PlaceholderAPI {
* @param text Text to set the placeholder values in
* @return String containing all translated placeholders
*/
public static String setBracketPlaceholders(Player player, String text) {
@NotNull
public static String setBracketPlaceholders(Player player, @NotNull String text) {
return setBracketPlaceholders((OfflinePlayer) player, text);
}
@@ -162,7 +163,8 @@ public final class PlaceholderAPI {
* @param text List of Strings to set the placeholder values in
* @return String containing all translated placeholders
*/
public static List<String> setBracketPlaceholders(Player player, List<String> text) {
@NotNull
public static List<String> setBracketPlaceholders(Player player, @NotNull List<String> text) {
return setBracketPlaceholders((OfflinePlayer) player, text);
}

View File

@@ -31,6 +31,8 @@ import me.clip.placeholderapi.expansion.Version;
import me.clip.placeholderapi.expansion.manager.CloudExpansionManager;
import me.clip.placeholderapi.expansion.manager.LocalExpansionManager;
import me.clip.placeholderapi.listeners.ServerLoadEventListener;
import me.clip.placeholderapi.scheduler.UniversalScheduler;
import me.clip.placeholderapi.scheduler.scheduling.schedulers.TaskScheduler;
import me.clip.placeholderapi.updatechecker.UpdateChecker;
import me.clip.placeholderapi.util.Msg;
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
@@ -86,9 +88,12 @@ public final class PlaceholderAPIPlugin extends JavaPlugin {
private final LocalExpansionManager localExpansionManager = new LocalExpansionManager(this);
@NotNull
private final CloudExpansionManager cloudExpansionManager = new CloudExpansionManager(this);
@NotNull
private final TaskScheduler scheduler = UniversalScheduler.getScheduler(this);
private BukkitAudiences adventure;
/**
* Gets the static instance of the main class for PlaceholderAPI. This class is not the actual API
* class, this is the main class that extends JavaPlugin. For most API methods, use static methods
@@ -174,7 +179,7 @@ public final class PlaceholderAPIPlugin extends JavaPlugin {
HandlerList.unregisterAll(this);
Bukkit.getScheduler().cancelTasks(this);
scheduler.cancelTasks(this);
adventure.close();
adventure = null;
@@ -215,6 +220,11 @@ public final class PlaceholderAPIPlugin extends JavaPlugin {
return adventure;
}
@NotNull
public TaskScheduler getScheduler() {
return scheduler;
}
/**
* Obtain the configuration class for PlaceholderAPI.
*
@@ -262,8 +272,8 @@ public final class PlaceholderAPIPlugin extends JavaPlugin {
Class.forName("org.bukkit.event.server.ServerLoadEvent");
new ServerLoadEventListener(this);
} catch (final ClassNotFoundException ignored) {
Bukkit.getScheduler()
.runTaskLater(this, () -> getLocalExpansionManager().load(Bukkit.getConsoleSender()), 1);
scheduler
.runTaskLater(() -> getLocalExpansionManager().load(Bukkit.getConsoleSender()), 1);
}
}

View File

@@ -50,7 +50,11 @@ public enum NMSVersion {
SPIGOT_1_20_R1("v1_20_R1"),
SPIGOT_1_20_R2("v1_20_R2"),
SPIGOT_1_20_R3("v1_20_R3"),
SPIGOT_1_20_R4("v1_20_R4");
SPIGOT_1_20_R4("v1_20_R4"),
SPIGOT_1_21_R1("v1_21_R1"),
SPIGOT_1_21_R2("V1_21_R2"),
SPIGOT_1_21_R3("V1_21_R3"),
SPIGOT_1_21_R4("V1_21_R4");
private final String version;

View File

@@ -202,10 +202,8 @@ public final class CloudExpansionManager {
// loop through what's left on the main thread
plugin
.getServer()
.getScheduler()
.runTask(
plugin,
() -> {
try {
for (Map.Entry<String, CloudExpansion> entry : values.entrySet()) {

View File

@@ -34,9 +34,9 @@ import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.stream.Collectors;
import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.events.ExpansionRegisterEvent;
@@ -440,7 +440,8 @@ public final class LocalExpansionManager implements Listener {
Msg.severe("Failed to load expansion %s (is a dependency missing?)", e, file.getName());
return null;
} catch (Exception e) {
throw new CompletionException(e.getMessage() + " (expansion file: " + file.getAbsolutePath() + ")", e);
plugin.getLogger().log(Level.SEVERE, "Failed to load expansion file: " + file.getAbsolutePath(), e);
return null;
}
});
}

View File

@@ -0,0 +1,178 @@
/*
* MIT License
*
* Copyright (c) 2023 Sevastjan
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package me.clip.placeholderapi.scheduler;
import me.clip.placeholderapi.scheduler.scheduling.schedulers.TaskScheduler;
import me.clip.placeholderapi.scheduler.scheduling.tasks.MyScheduledTask;
import org.bukkit.plugin.Plugin;
/** Just modified BukkitRunnable */
public abstract class UniversalRunnable implements Runnable {
MyScheduledTask task;
public synchronized void cancel() throws IllegalStateException {
checkScheduled();
task.cancel();
}
/**
* Returns true if this task has been cancelled.
*
* @return true if the task has been cancelled
* @throws IllegalStateException if task was not scheduled yet
*/
public synchronized boolean isCancelled() throws IllegalStateException {
checkScheduled();
return task.isCancelled();
}
/**
* Schedules this in the Bukkit scheduler to run on next tick.
*
* @param plugin the reference to the plugin scheduling task
* @return {@link MyScheduledTask}
* @throws IllegalArgumentException if plugin is null
* @throws IllegalStateException if this was already scheduled
* @see TaskScheduler#runTask(Runnable)
*/
public synchronized MyScheduledTask runTask(Plugin plugin) throws IllegalArgumentException, IllegalStateException {
checkNotYetScheduled();
return setupTask(UniversalScheduler.getScheduler(plugin).runTask(this));
}
/**
* <b>Asynchronous tasks should never access any API in Bukkit. Great care
* should be taken to assure the thread-safety of asynchronous tasks.</b>
* <p>
* Schedules this in the Bukkit scheduler to run asynchronously.
*
* @param plugin the reference to the plugin scheduling task
* @return {@link MyScheduledTask}
* @throws IllegalArgumentException if plugin is null
* @throws IllegalStateException if this was already scheduled
* @see TaskScheduler#runTaskAsynchronously(Runnable)
*/
public synchronized MyScheduledTask runTaskAsynchronously(Plugin plugin) throws IllegalArgumentException, IllegalStateException {
checkNotYetScheduled();
return setupTask(UniversalScheduler.getScheduler(plugin).runTaskAsynchronously(this));
}
/**
* Schedules this to run after the specified number of server ticks.
*
* @param plugin the reference to the plugin scheduling task
* @param delay the ticks to wait before running the task
* @return {@link MyScheduledTask}
* @throws IllegalArgumentException if plugin is null
* @throws IllegalStateException if this was already scheduled
* @see TaskScheduler#runTaskLater(Runnable, long)
*/
public synchronized MyScheduledTask runTaskLater(Plugin plugin, long delay) throws IllegalArgumentException, IllegalStateException {
checkNotYetScheduled();
return setupTask(UniversalScheduler.getScheduler(plugin).runTaskLater(this, delay));
}
/**
* <b>Asynchronous tasks should never access any API in Bukkit. Great care
* should be taken to assure the thread-safety of asynchronous tasks.</b>
* <p>
* Schedules this to run asynchronously after the specified number of
* server ticks.
*
* @param plugin the reference to the plugin scheduling task
* @param delay the ticks to wait before running the task
* @return {@link MyScheduledTask}
* @throws IllegalArgumentException if plugin is null
* @throws IllegalStateException if this was already scheduled
* @see TaskScheduler#runTaskLaterAsynchronously(Runnable, long)
*/
public synchronized MyScheduledTask runTaskLaterAsynchronously(Plugin plugin, long delay) throws IllegalArgumentException, IllegalStateException {
checkNotYetScheduled();
return setupTask(UniversalScheduler.getScheduler(plugin).runTaskLaterAsynchronously(this, delay));
}
/**
* Schedules this to repeatedly run until cancelled, starting after the
* specified number of server ticks.
*
* @param plugin the reference to the plugin scheduling task
* @param delay the ticks to wait before running the task
* @param period the ticks to wait between runs
* @return {@link MyScheduledTask}
* @throws IllegalArgumentException if plugin is null
* @throws IllegalStateException if this was already scheduled
* @see TaskScheduler#runTaskTimer(Runnable, long, long)
*/
public synchronized MyScheduledTask runTaskTimer(Plugin plugin, long delay, long period) throws IllegalArgumentException, IllegalStateException {
checkNotYetScheduled();
return setupTask(UniversalScheduler.getScheduler(plugin).runTaskTimer(this, delay, period));
}
/**
* <b>Asynchronous tasks should never access any API in Bukkit. Great care
* should be taken to assure the thread-safety of asynchronous tasks.</b>
* <p>
* Schedules this to repeatedly run asynchronously until cancelled,
* starting after the specified number of server ticks.
*
* @param plugin the reference to the plugin scheduling task
* @param delay the ticks to wait before running the task for the first
* time
* @param period the ticks to wait between runs
* @return {@link MyScheduledTask}
* @throws IllegalArgumentException if plugin is null
* @throws IllegalStateException if this was already scheduled
* @see TaskScheduler#runTaskTimerAsynchronously(Runnable, long, long)
*/
public synchronized MyScheduledTask runTaskTimerAsynchronously(Plugin plugin, long delay, long period) throws IllegalArgumentException, IllegalStateException {
checkNotYetScheduled();
return setupTask(UniversalScheduler.getScheduler(plugin).runTaskTimerAsynchronously(this, delay, period));
}
private void checkScheduled() {
if (task == null) {
throw new IllegalStateException("Not scheduled yet");
}
}
private void checkNotYetScheduled() {
if (task != null) {
throw new IllegalStateException("Already scheduled");
}
}
private MyScheduledTask setupTask(final MyScheduledTask task) {
this.task = task;
return task;
}
}

View File

@@ -0,0 +1,42 @@
/*
* MIT License
*
* Copyright (c) 2023 Sevastjan
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package me.clip.placeholderapi.scheduler;
import me.clip.placeholderapi.scheduler.bukkit.BukkitScheduler;
import me.clip.placeholderapi.scheduler.folia.FoliaScheduler;
import me.clip.placeholderapi.scheduler.paper.PaperScheduler;
import me.clip.placeholderapi.scheduler.scheduling.schedulers.TaskScheduler;
import me.clip.placeholderapi.scheduler.utils.JavaUtil;
import org.bukkit.plugin.Plugin;
public class UniversalScheduler {
private static final boolean IS_FOLIA = JavaUtil.classExists("io.papermc.paper.threadedregions.RegionizedServer");
private static final boolean IS_CANVAS = JavaUtil.classExists("io.canvasmc.canvas.server.ThreadedServer");
private static final boolean IS_EXPANDED_SCHEDULING_AVAILABLE = JavaUtil.classExists("io.papermc.paper.threadedregions.scheduler.ScheduledTask");
public static TaskScheduler getScheduler(Plugin plugin) {
return IS_FOLIA || IS_CANVAS ? new FoliaScheduler(plugin) : (IS_EXPANDED_SCHEDULING_AVAILABLE ? new PaperScheduler(plugin) : new BukkitScheduler(plugin));
}
}

View File

@@ -0,0 +1,71 @@
/*
* MIT License
*
* Copyright (c) 2023 Sevastjan
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package me.clip.placeholderapi.scheduler.bukkit;
import me.clip.placeholderapi.scheduler.scheduling.tasks.MyScheduledTask;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitTask;
public class BukkitScheduledTask implements MyScheduledTask {
BukkitTask task;
boolean isRepeating;
public BukkitScheduledTask(final BukkitTask task) {
this.task = task;
this.isRepeating = false;
}
public BukkitScheduledTask(final BukkitTask task, boolean isRepeating) {
this.task = task;
this.isRepeating = isRepeating;
}
@Override
public void cancel() {
task.cancel();
}
@Override
public boolean isCancelled() {
return task.isCancelled();
}
@Override
public Plugin getOwningPlugin() {
return task.getOwner();
}
@Override
public boolean isCurrentlyRunning() {
return Bukkit.getServer().getScheduler().isCurrentlyRunning(this.task.getTaskId()); //There's no other way. Fuck bukkit
}
@Override
public boolean isRepeatingTask() {
return isRepeating;
}
}

View File

@@ -0,0 +1,129 @@
/*
* MIT License
*
* Copyright (c) 2023 Sevastjan
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package me.clip.placeholderapi.scheduler.bukkit;
import me.clip.placeholderapi.scheduler.scheduling.schedulers.TaskScheduler;
import me.clip.placeholderapi.scheduler.scheduling.tasks.MyScheduledTask;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.plugin.Plugin;
public class BukkitScheduler implements TaskScheduler {
final Plugin plugin;
public BukkitScheduler(Plugin plugin) {
this.plugin = plugin;
}
@Override
public boolean isGlobalThread() {
return Bukkit.getServer().isPrimaryThread();
}
@Override
public boolean isEntityThread(Entity entity) {
return Bukkit.getServer().isPrimaryThread();
}
@Override
public boolean isRegionThread(Location location) {
return Bukkit.getServer().isPrimaryThread();
}
@Override
public MyScheduledTask runTask(Runnable runnable) {
return new BukkitScheduledTask(Bukkit.getScheduler().runTask(plugin, runnable));
}
@Override
public MyScheduledTask runTaskLater(Runnable runnable, long delay) {
return new BukkitScheduledTask(Bukkit.getScheduler().runTaskLater(plugin, runnable, delay));
}
@Override
public MyScheduledTask runTaskTimer(Runnable runnable, long delay, long period) {
return new BukkitScheduledTask(Bukkit.getScheduler().runTaskTimer(plugin, runnable, delay, period));
}
@Override
public MyScheduledTask runTaskAsynchronously(Runnable runnable) {
return new BukkitScheduledTask(Bukkit.getScheduler().runTaskAsynchronously(plugin, runnable));
}
@Override
public MyScheduledTask runTaskLaterAsynchronously(Runnable runnable, long delay) {
return new BukkitScheduledTask(Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, runnable, delay));
}
@Override
public MyScheduledTask runTaskTimerAsynchronously(Runnable runnable, long delay, long period) {
return new BukkitScheduledTask(Bukkit.getScheduler().runTaskTimerAsynchronously(plugin, runnable, delay, period));
}
//Useless? Or...
public MyScheduledTask runTask(Plugin plugin, Runnable runnable) {
return new BukkitScheduledTask(Bukkit.getScheduler().runTask(plugin, runnable));
}
@Override
public MyScheduledTask runTaskLater(Plugin plugin, Runnable runnable, long delay) {
return new BukkitScheduledTask(Bukkit.getScheduler().runTaskLater(plugin, runnable, delay));
}
@Override
public MyScheduledTask runTaskTimer(Plugin plugin, Runnable runnable, long delay, long period) {
return new BukkitScheduledTask(Bukkit.getScheduler().runTaskTimer(plugin, runnable, delay, period));
}
@Override
public MyScheduledTask runTaskAsynchronously(Plugin plugin, Runnable runnable) {
return new BukkitScheduledTask(Bukkit.getScheduler().runTaskAsynchronously(plugin, runnable));
}
@Override
public MyScheduledTask runTaskLaterAsynchronously(Plugin plugin, Runnable runnable, long delay) {
return new BukkitScheduledTask(Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, runnable, delay));
}
@Override
public MyScheduledTask runTaskTimerAsynchronously(Plugin plugin, Runnable runnable, long delay, long period) {
return new BukkitScheduledTask(Bukkit.getScheduler().runTaskTimerAsynchronously(plugin, runnable, delay, period));
}
@Override
public void execute(Runnable runnable) {
Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, runnable);
}
@Override
public void cancelTasks() {
Bukkit.getScheduler().cancelTasks(plugin);
}
@Override
public void cancelTasks(Plugin plugin) {
Bukkit.getScheduler().cancelTasks(plugin);
}
}

View File

@@ -0,0 +1,57 @@
/*
* MIT License
*
* Copyright (c) 2023 Sevastjan
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package me.clip.placeholderapi.scheduler.folia;
import me.clip.placeholderapi.scheduler.scheduling.tasks.MyScheduledTask;
import io.papermc.paper.threadedregions.scheduler.ScheduledTask;
import org.bukkit.plugin.Plugin;
public class FoliaScheduledTask implements MyScheduledTask {
private final ScheduledTask task;
public FoliaScheduledTask(final ScheduledTask task) {
this.task = task;
}
public void cancel() {
this.task.cancel();
}
public boolean isCancelled() {
return this.task.isCancelled();
}
public Plugin getOwningPlugin() {
return this.task.getOwningPlugin();
}
public boolean isCurrentlyRunning() {
final ScheduledTask.ExecutionState state = this.task.getExecutionState();
return state == ScheduledTask.ExecutionState.RUNNING || state == ScheduledTask.ExecutionState.CANCELLED_RUNNING;
}
public boolean isRepeatingTask() {
return this.task.isRepeatingTask();
}
}

View File

@@ -0,0 +1,220 @@
/*
* MIT License
*
* Copyright (c) 2023 Sevastjan
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package me.clip.placeholderapi.scheduler.folia;
import me.clip.placeholderapi.scheduler.scheduling.schedulers.TaskScheduler;
import me.clip.placeholderapi.scheduler.scheduling.tasks.MyScheduledTask;
import io.papermc.paper.threadedregions.scheduler.AsyncScheduler;
import io.papermc.paper.threadedregions.scheduler.GlobalRegionScheduler;
import io.papermc.paper.threadedregions.scheduler.RegionScheduler;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.plugin.Plugin;
import java.util.concurrent.TimeUnit;
public class FoliaScheduler implements TaskScheduler {
final Plugin plugin;
public FoliaScheduler(Plugin plugin) {
this.plugin = plugin;
}
private final RegionScheduler regionScheduler = Bukkit.getServer().getRegionScheduler();
private final GlobalRegionScheduler globalRegionScheduler = Bukkit.getServer().getGlobalRegionScheduler();
private final AsyncScheduler asyncScheduler = Bukkit.getServer().getAsyncScheduler();
@Override
public boolean isGlobalThread() {
return Bukkit.getServer().isGlobalTickThread();
}
@Override
public boolean isTickThread() {
return Bukkit.getServer().isPrimaryThread(); // The Paper implementation checks whether this is a tick thread, this method exists to avoid confusion.
}
@Override
public boolean isEntityThread(Entity entity) {
return Bukkit.getServer().isOwnedByCurrentRegion(entity);
}
@Override
public boolean isRegionThread(Location location) {
return Bukkit.getServer().isOwnedByCurrentRegion(location);
}
@Override
public MyScheduledTask runTask(Runnable runnable) {
return new FoliaScheduledTask(globalRegionScheduler.run(plugin, task -> runnable.run()));
}
@Override
public MyScheduledTask runTaskLater(Runnable runnable, long delay) {
//Folia exception: Delay ticks may not be <= 0
if (delay <= 0) {
return runTask(runnable);
}
return new FoliaScheduledTask(globalRegionScheduler.runDelayed(plugin, task -> runnable.run(), delay));
}
@Override
public MyScheduledTask runTaskTimer(Runnable runnable, long delay, long period) {
//Folia exception: Delay ticks may not be <= 0
delay = getOneIfNotPositive(delay);
return new FoliaScheduledTask(globalRegionScheduler.runAtFixedRate(plugin, task -> runnable.run(), delay, period));
}
@Override
public MyScheduledTask runTask(Plugin plugin, Runnable runnable) {
return new FoliaScheduledTask(globalRegionScheduler.run(plugin, task -> runnable.run()));
}
@Override
public MyScheduledTask runTaskLater(Plugin plugin, Runnable runnable, long delay) {
//Folia exception: Delay ticks may not be <= 0
if (delay <= 0) {
return runTask(plugin, runnable);
}
return new FoliaScheduledTask(globalRegionScheduler.runDelayed(plugin, task -> runnable.run(), delay));
}
@Override
public MyScheduledTask runTaskTimer(Plugin plugin, Runnable runnable, long delay, long period) {
//Folia exception: Delay ticks may not be <= 0
delay = getOneIfNotPositive(delay);
return new FoliaScheduledTask(globalRegionScheduler.runAtFixedRate(plugin, task -> runnable.run(), delay, period));
}
@Override
public MyScheduledTask runTask(Location location, Runnable runnable) {
return new FoliaScheduledTask(regionScheduler.run(plugin, location, task -> runnable.run()));
}
@Override
public MyScheduledTask runTaskLater(Location location, Runnable runnable, long delay) {
//Folia exception: Delay ticks may not be <= 0
if (delay <= 0) {
return runTask(runnable);
}
return new FoliaScheduledTask(regionScheduler.runDelayed(plugin, location, task -> runnable.run(), delay));
}
@Override
public MyScheduledTask runTaskTimer(Location location, Runnable runnable, long delay, long period) {
//Folia exception: Delay ticks may not be <= 0
delay = getOneIfNotPositive(delay);
return new FoliaScheduledTask(regionScheduler.runAtFixedRate(plugin, location, task -> runnable.run(), delay, period));
}
@Override
public MyScheduledTask runTask(Entity entity, Runnable runnable) {
return new FoliaScheduledTask(entity.getScheduler().run(plugin, task -> runnable.run(), null));
}
@Override
public MyScheduledTask runTaskLater(Entity entity, Runnable runnable, long delay) {
//Folia exception: Delay ticks may not be <= 0
if (delay <= 0) {
return runTask(entity, runnable);
}
return new FoliaScheduledTask(entity.getScheduler().runDelayed(plugin, task -> runnable.run(), null, delay));
}
@Override
public MyScheduledTask runTaskTimer(Entity entity, Runnable runnable, long delay, long period) {
//Folia exception: Delay ticks may not be <= 0
delay = getOneIfNotPositive(delay);
return new FoliaScheduledTask(entity.getScheduler().runAtFixedRate(plugin, task -> runnable.run(), null, delay, period));
}
@Override
public MyScheduledTask runTaskAsynchronously(Runnable runnable) {
return new FoliaScheduledTask(asyncScheduler.runNow(plugin, task -> runnable.run()));
}
@Override
public MyScheduledTask runTaskLaterAsynchronously(Runnable runnable, long delay) {
//Folia exception: Delay ticks may not be <= 0
delay = getOneIfNotPositive(delay);
return new FoliaScheduledTask(asyncScheduler.runDelayed(plugin, task -> runnable.run(), delay * 50L, TimeUnit.MILLISECONDS));
}
@Override
public MyScheduledTask runTaskTimerAsynchronously(Runnable runnable, long delay, long period) {
return new FoliaScheduledTask(asyncScheduler.runAtFixedRate(plugin, task -> runnable.run(), delay * 50, period * 50, TimeUnit.MILLISECONDS));
}
@Override
public MyScheduledTask runTaskAsynchronously(Plugin plugin, Runnable runnable) {
return new FoliaScheduledTask(asyncScheduler.runNow(plugin, task -> runnable.run()));
}
@Override
public MyScheduledTask runTaskLaterAsynchronously(Plugin plugin, Runnable runnable, long delay) {
//Folia exception: Delay ticks may not be <= 0
delay = getOneIfNotPositive(delay);
return new FoliaScheduledTask(asyncScheduler.runDelayed(plugin, task -> runnable.run(), delay * 50L, TimeUnit.MILLISECONDS));
}
@Override
public MyScheduledTask runTaskTimerAsynchronously(Plugin plugin, Runnable runnable, long delay, long period) {
//Folia exception: Delay ticks may not be <= 0
delay = getOneIfNotPositive(delay);
return new FoliaScheduledTask(asyncScheduler.runAtFixedRate(plugin, task -> runnable.run(), delay * 50, period * 50, TimeUnit.MILLISECONDS));
}
@Override
public void execute(Runnable runnable) {
globalRegionScheduler.execute(plugin, runnable);
}
@Override
public void execute(Location location, Runnable runnable) {
regionScheduler.execute(plugin, location, runnable);
}
@Override
public void execute(Entity entity, Runnable runnable) {
entity.getScheduler().execute(plugin, runnable, null, 1L);
}
@Override
public void cancelTasks() {
globalRegionScheduler.cancelTasks(plugin);
asyncScheduler.cancelTasks(plugin);
}
@Override
public void cancelTasks(Plugin plugin) {
globalRegionScheduler.cancelTasks(plugin);
asyncScheduler.cancelTasks(plugin);
}
private long getOneIfNotPositive(long x) {
return x <= 0 ? 1L : x;
}
}

View File

@@ -0,0 +1,41 @@
/*
* MIT License
*
* Copyright (c) 2023 Sevastjan
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package me.clip.placeholderapi.scheduler.paper;
import me.clip.placeholderapi.scheduler.folia.FoliaScheduler;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
//Thanks to Towny
public class PaperScheduler extends FoliaScheduler {
public PaperScheduler(Plugin plugin) {
super(plugin);
}
@Override
public boolean isGlobalThread() {
// isGlobalThread does not exist on paper, match the bukkit task scheduler's behaviour.
return Bukkit.getServer().isPrimaryThread();
}
}

View File

@@ -0,0 +1,346 @@
/*
* MIT License
*
* Copyright (c) 2023 Sevastjan
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package me.clip.placeholderapi.scheduler.scheduling.schedulers;
import me.clip.placeholderapi.scheduler.scheduling.tasks.MyScheduledTask;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.plugin.Plugin;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
public interface TaskScheduler {
/**
* <b>Folia</b>: Returns whether the current thread is ticking the global region <br>
* <b>Paper & Bukkit</b>: Returns {@link org.bukkit.Server#isPrimaryThread}
*/
boolean isGlobalThread();
/**
* @return {@link org.bukkit.Server#isPrimaryThread}
*/
default boolean isTickThread() {
return Bukkit.getServer().isPrimaryThread();
}
/**
* <b>Folia & Paper</b>: Returns whether the current thread is ticking a region and that the region
* being ticked owns the specified entity. Note that this function is the only appropriate method of
* checking for ownership of an entity, as retrieving the entity's location is undefined unless the
* entity is owned by the current region
* <p>
* <b>Bukkit</b>: returns {@link org.bukkit.Server#isPrimaryThread}
*
* @param entity Specified entity
*/
boolean isEntityThread(Entity entity);
/**
* <b>Folia & Paper</b>: Returns whether the current thread is ticking a region and that the region
* being ticked owns the chunk at the specified world and block position as included in the specified location
* <p>
* <b>Bukkit</b>: returns {@link org.bukkit.Server#isPrimaryThread}
*
* @param location Specified location, must have a non-null world.
*/
boolean isRegionThread(Location location);
/**
* Schedules a task to be executed on the next tick <br>
* <b>Folia & Paper</b>: ...on the global region <br>
* <b>Bukkit</b>: ...on the main thread
*
* @param runnable The task to execute
*/
MyScheduledTask runTask(Runnable runnable);
/**
* Schedules a task to be executed after the specified delay in ticks <br>
* <b>Folia & Paper</b>: ...on the global region <br>
* <b>Bukkit</b>: ...on the main thread
*
* @param runnable The task to execute
* @param delay The delay, in ticks
*/
MyScheduledTask runTaskLater(Runnable runnable, long delay);
/**
* Schedules a repeating task to be executed after the initial delay with the specified period <br>
* <b>Folia & Paper</b>: ...on the global region <br>
* <b>Bukkit</b>: ...on the main thread
*
* @param runnable The task to execute
* @param delay The initial delay, in ticks.
* @param period The period, in ticks.
*/
MyScheduledTask runTaskTimer(Runnable runnable, long delay, long period);
/**
* Deprecated: use {@link #runTask(Runnable)}
*/
@Deprecated
default MyScheduledTask runTask(Plugin plugin, Runnable runnable) {
return runTask(runnable);
}
/**
* Deprecated: use {@link #runTaskLater(Runnable, long)}
*/
@Deprecated
default MyScheduledTask runTaskLater(Plugin plugin, Runnable runnable, long delay) {
return runTaskLater(runnable, delay);
}
/**
* Deprecated: use {@link #runTaskTimer(Runnable, long, long)}
*/
@Deprecated
default MyScheduledTask runTaskTimer(Plugin plugin, Runnable runnable, long delay, long period) {
return runTaskTimer(runnable, delay, period);
}
/**
* <b>Folia & Paper</b>: Schedules a task to be executed on the region which owns the location on the next tick
* <p>
* <b>Bukkit</b>: same as {@link #runTask(Runnable)}
*
* @param location The location which the region executing should own
* @param runnable The task to execute
*/
default MyScheduledTask runTask(Location location, Runnable runnable) {
return runTask(runnable);
}
/**
* <b>Folia & Paper</b>: Schedules a task to be executed on the region which owns the location after the
* specified delay in ticks
* <p>
* <b>Bukkit</b>: same as {@link #runTaskLater(Runnable, long)}
*
* @param location The location which the region executing should own
* @param runnable The task to execute
* @param delay The delay, in ticks.
*/
default MyScheduledTask runTaskLater(Location location, Runnable runnable, long delay) {
return runTaskLater(runnable, delay);
}
/**
* <b>Folia & Paper</b>: Schedules a repeating task to be executed on the region which owns the location
* after the initial delay with the specified period
* <p>
* <b>Bukkit</b>: same as {@link #runTaskTimer(Runnable, long, long)}
*
* @param location The location which the region executing should own
* @param runnable The task to execute
* @param delay The initial delay, in ticks.
* @param period The period, in ticks.
*/
default MyScheduledTask runTaskTimer(Location location, Runnable runnable, long delay, long period) {
return runTaskTimer(runnable, delay, period);
}
/**
* Deprecated: use {@link #runTaskLater(Runnable, long)}
*/
@Deprecated
default MyScheduledTask scheduleSyncDelayedTask(Runnable runnable, long delay) {
return runTaskLater(runnable, delay);
}
/**
* Deprecated: use {@link #execute(Runnable)} or {@link #runTask(Runnable)}
*/
@Deprecated
default MyScheduledTask scheduleSyncDelayedTask(Runnable runnable) {
return runTask(runnable);
}
/**
* Deprecated: use {@link #runTaskTimer(Runnable, long, long)}
*/
@Deprecated
default MyScheduledTask scheduleSyncRepeatingTask(Runnable runnable, long delay, long period) {
return runTaskTimer(runnable, delay, period);
}
/**
* <b>Folia & Paper</b>: Schedules a task to be executed on the region which owns the location
* of given entity on the next tick
* <p>
* <b>Bukkit</b>: same as {@link #runTask(Runnable)}
*
* @param entity The entity whose location the region executing should own
* @param runnable The task to execute
*/
default MyScheduledTask runTask(Entity entity, Runnable runnable) {
return runTask(runnable);
}
/**
* <b>Folia & Paper</b>: Schedules a task to be executed on the region which owns the location
* of given entity after the specified delay in ticks
* <p>
* <b>Bukkit</b>: same as {@link #runTaskLater(Runnable, long)}
*
* @param entity The entity whose location the region executing should own
* @param runnable The task to execute
* @param delay The delay, in ticks.
*/
default MyScheduledTask runTaskLater(Entity entity, Runnable runnable, long delay) {
return runTaskLater(runnable, delay);
}
/**
* <b>Folia & Paper</b>: Schedules a repeating task to be executed on the region which owns the
* location of given entity after the initial delay with the specified period
* <p>
* <b>Bukkit</b>: same as {@link #runTaskTimer(Runnable, long, long)}
*
* @param entity The entity whose location the region executing should own
* @param runnable The task to execute
* @param delay The initial delay, in ticks.
* @param period The period, in ticks.
*/
default MyScheduledTask runTaskTimer(Entity entity, Runnable runnable, long delay, long period) {
return runTaskTimer(runnable, delay, period);
}
/**
* Schedules the specified task to be executed asynchronously immediately
*
* @param runnable The task to execute
* @return The {@link MyScheduledTask} that represents the scheduled task
*/
MyScheduledTask runTaskAsynchronously(Runnable runnable);
/**
* Schedules the specified task to be executed asynchronously after the time delay has passed
*
* @param runnable The task to execute
* @param delay The time delay to pass before the task should be executed
* @return The {@link MyScheduledTask} that represents the scheduled task
*/
MyScheduledTask runTaskLaterAsynchronously(Runnable runnable, long delay);
/**
* Schedules the specified task to be executed asynchronously after the initial delay has passed,
* and then periodically executed with the specified period
*
* @param runnable The task to execute
* @param delay The time delay to pass before the first execution of the task, in ticks
* @param period The time between task executions after the first execution of the task, in ticks
* @return The {@link MyScheduledTask} that represents the scheduled task
*/
MyScheduledTask runTaskTimerAsynchronously(Runnable runnable, long delay, long period);
/**
* Deprecated: use {@link #runTaskAsynchronously(Runnable)}
*/
@Deprecated
default MyScheduledTask runTaskAsynchronously(Plugin plugin, Runnable runnable) {
return runTaskAsynchronously(runnable);
}
/**
* Deprecated: use {@link #runTaskLaterAsynchronously(Runnable, long)}
*/
@Deprecated
default MyScheduledTask runTaskLaterAsynchronously(Plugin plugin, Runnable runnable, long delay) {
return runTaskLaterAsynchronously(runnable, delay);
}
/**
* Deprecated: use {@link #runTaskTimerAsynchronously(Runnable, long, long)}
*/
@Deprecated
default MyScheduledTask runTaskTimerAsynchronously(Plugin plugin, Runnable runnable, long delay, long period) {
return runTaskTimerAsynchronously(runnable, delay, period);
}
/**
* Calls a method on the main thread and returns a Future object. This task will be executed
* by the main(Bukkit)/global(Folia&Paper) server thread.
* <p>
* Note: The Future.get() methods must NOT be called from the main thread.
* <p>
* Note2: There is at least an average of 10ms latency until the isDone() method returns true.
*
* @param task Task to be executed
*/
default <T> Future<T> callSyncMethod(final Callable<T> task) {
CompletableFuture<T> completableFuture = new CompletableFuture<>();
execute(() -> {
try {
completableFuture.complete(task.call());
} catch (Exception e) {
throw new RuntimeException(e);
}
});
return completableFuture;
}
/**
* Schedules a task to be executed on the global region
*
* @param runnable The task to execute
*/
void execute(Runnable runnable);
/**
* Schedules a task to be executed on the region which owns the location
*
* @param location The location which the region executing should own
* @param runnable The task to execute
*/
default void execute(Location location, Runnable runnable) {
execute(runnable);
}
/**
* Schedules a task to be executed on the region which owns the location of given entity
*
* @param entity The entity which location the region executing should own
* @param runnable The task to execute
*/
default void execute(Entity entity, Runnable runnable) {
execute(runnable);
}
/**
* Attempts to cancel all tasks scheduled by this plugin
*/
void cancelTasks();
/**
* Attempts to cancel all tasks scheduled by the specified plugin
*
* @param plugin specified plugin
*/
void cancelTasks(Plugin plugin);
}

View File

@@ -0,0 +1,53 @@
/*
* MIT License
*
* Copyright (c) 2023 Sevastjan
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package me.clip.placeholderapi.scheduler.scheduling.tasks;
import org.bukkit.plugin.Plugin;
public interface MyScheduledTask {
/**
* Cancels executing task
*/
void cancel();
/**
* @return true if task is cancelled, false otherwise
*/
boolean isCancelled();
/**
* @return The plugin under which the task was scheduled.
*/
Plugin getOwningPlugin();
/**
* @return true if task is currently executing, false otherwise
*/
boolean isCurrentlyRunning();
/**
* @return true if task is repeating, false otherwise
*/
boolean isRepeatingTask();
}

View File

@@ -0,0 +1,12 @@
package me.clip.placeholderapi.scheduler.utils;
public class JavaUtil {
public static boolean classExists(String className) {
try {
Class.forName(className);
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
}

View File

@@ -26,6 +26,7 @@ import java.net.URL;
import java.util.Arrays;
import javax.net.ssl.HttpsURLConnection;
import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.scheduler.scheduling.schedulers.TaskScheduler;
import me.clip.placeholderapi.util.Msg;
import org.bukkit.Bukkit;
import org.bukkit.event.EventHandler;
@@ -35,15 +36,17 @@ import org.bukkit.event.player.PlayerJoinEvent;
public class UpdateChecker implements Listener {
private final int RESOURCE_ID = 6245;
private static final int RESOURCE_ID = 6245;
private final PlaceholderAPIPlugin plugin;
private final TaskScheduler scheduler;
private final String pluginVersion;
private String spigotVersion;
private boolean updateAvailable;
public UpdateChecker(PlaceholderAPIPlugin i) {
plugin = i;
pluginVersion = i.getDescription().getVersion();
public UpdateChecker(PlaceholderAPIPlugin plugin) {
this.plugin = plugin;
scheduler = plugin.getScheduler();
pluginVersion = plugin.getDescription().getVersion();
}
public boolean hasUpdateAvailable() {
@@ -55,7 +58,7 @@ public class UpdateChecker implements Listener {
}
public void fetch() {
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
scheduler.runTaskAsynchronously(() -> {
try {
HttpsURLConnection con = (HttpsURLConnection) new URL(
"https://api.spigotmc.org/legacy/update.php?resource=" + RESOURCE_ID).openConnection();
@@ -76,7 +79,7 @@ public class UpdateChecker implements Listener {
return;
}
Bukkit.getScheduler().runTask(plugin, () -> {
scheduler.runTask(() -> {
plugin.getLogger()
.info("An update for PlaceholderAPI (v" + getSpigotVersion() + ") is available at:");
plugin.getLogger()

View File

@@ -27,6 +27,8 @@ import java.util.function.BiConsumer;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import me.clip.placeholderapi.PlaceholderAPIPlugin;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
@@ -36,14 +38,14 @@ public final class Futures {
private Futures() {}
public static <T> void onMainThread(@NotNull final Plugin plugin,
public static <T> void onMainThread(@NotNull final PlaceholderAPIPlugin plugin,
@NotNull final CompletableFuture<T> future,
@NotNull final BiConsumer<T, Throwable> consumer) {
future.whenComplete((value, exception) -> {
if (Bukkit.isPrimaryThread()) {
consumer.accept(value, exception);
} else {
Bukkit.getScheduler().runTask(plugin, () -> consumer.accept(value, exception));
plugin.getScheduler().runTask(() -> consumer.accept(value, exception));
}
});
}

View File

@@ -1,5 +1,6 @@
name: PlaceholderAPI
main: "me.clip.placeholderapi.PlaceholderAPIPlugin"
folia-supported: true
version: ${version}
author: HelpChat