From d9bf099c3d411e5f493180de9c487a93e2fcfda7 Mon Sep 17 00:00:00 2001
From: Josh Roy <10731363+JRoy@users.noreply.github.com>
Date: Wed, 24 Jun 2020 04:52:25 -0400
Subject: [PATCH] Reduce sync loads for teleporting (#3102)
This PR reduces the number of sync loads occurring on any teleport caused by essentials.
Fixes #2861
Fixes #2287
Fixes #3274
Fixes #3201
Fixes #2120
Before this PR, essentials would get a block multiple times causing sync loads to check if it was safe to teleport to. Now, the target block's chunk if fetched async with PaperLib and passed along to `LocationUtil#isBlockUnsafeForUser` (which internally calls other LocationUtil methods what that chunk object) resulting in the chunk only loading once, off the main thread. The only operations remaining on the main thread is `LocationUtil#getSafeDestination`. This is due to the method's recursion which would be a pain to move async. **However:** since the chunk was already loaded async, `LocationUtil#getSafeDestination` most of the time won't cause sync chunk loads. The only time it would cause sync chunk loads is with an unsafe location near a chunk border.
-----------------------------------------
* Reduce sync teleporting loads
* Avoid argument re-assigning
* Remove async teleports when unnecessary
* Make exceptions cleaner
* Async all the things
Made an async version of every method with fallbacks for deprecated methods.
* Remove old now fallback method
* Migrate everything to the new async teleport API
* Update ITeleport javadocs
* Fix invoking via async context
* Fix /jail using deprecated method
* Fix jail join handler using deprecated method
* Rename all teleport classes to indicate async
* Remove deprecated methods
* Add (and deprecate) old teleport api
* Revert TimedTeleport.java
* Reduce Diff
* Add legacy sendToJail method
* Reduce Diff Further
* Use getNewExceptionFuture in Commandtpo
* Use getNewExceptionFuture everywhere
* Fix even more usages
* Revert LocationUtil.java
* Fix issue causing unsafe locations to not work properly
* Add deprecated notice in IUser implementation
* Use CompletableFuture#completeExceptionally for exceptions
* Use Essentials' logger in EssentialsCommand#showError
* Return implementation rather than interface
* Avoid possible deadlocks with entity ejections
* Nuke some sync loads with homes
Took 7 hours and 2 PRs to paper but it's here!
* Fix ABI and make the codestyle worse
* Make the codestyle worse because muh diff
* Further ruin the codestyle
* Fix error messages not showing in TimedTeleports
* Improve messages around beds for /home
* Fix #3274
Allow unsafe locations for different worlds + spectator mode
* Fix fly safety operators
---
Essentials/pom.xml | 2 +-
.../earth2me/essentials/AsyncTeleport.java | 428 ++++++++++++++++++
.../essentials/AsyncTimedTeleport.java | 151 ++++++
.../src/com/earth2me/essentials/IUser.java | 7 +
.../src/com/earth2me/essentials/Jails.java | 37 +-
.../src/com/earth2me/essentials/Teleport.java | 26 ++
.../earth2me/essentials/TimedTeleport.java | 24 +-
.../src/com/earth2me/essentials/Trade.java | 53 ++-
.../src/com/earth2me/essentials/User.java | 33 +-
.../essentials/api/IAsyncTeleport.java | 123 +++++
.../com/earth2me/essentials/api/IJails.java | 15 +
.../earth2me/essentials/api/ITeleport.java | 16 +-
.../essentials/commands/Commandback.java | 18 +-
.../essentials/commands/Commandhome.java | 87 ++--
.../essentials/commands/Commandjump.java | 3 +-
.../commands/Commandtogglejail.java | 44 +-
.../essentials/commands/Commandtop.java | 12 +-
.../essentials/commands/Commandtp.java | 35 +-
.../essentials/commands/Commandtpa.java | 18 +-
.../essentials/commands/Commandtpaccept.java | 43 +-
.../essentials/commands/Commandtpall.java | 12 +-
.../essentials/commands/Commandtphere.java | 3 +-
.../essentials/commands/Commandtpo.java | 12 +-
.../essentials/commands/Commandtpoffline.java | 2 +-
.../essentials/commands/Commandtpohere.java | 3 +-
.../essentials/commands/Commandtppos.java | 6 +-
.../essentials/commands/Commandwarp.java | 15 +-
.../essentials/commands/Commandworld.java | 3 +-
.../commands/EssentialsCommand.java | 21 +
.../commands/IEssentialsCommand.java | 4 +
.../earth2me/essentials/signs/SignWarp.java | 19 +-
.../essentials/utils/LocationUtil.java | 2 +-
Essentials/src/messages.properties | 1 +
Essentials/src/net/ess3/api/ITeleport.java | 3 +
.../ess3/api/events/UserTeleportEvent.java | 4 +-
.../essentials/spawn/Commandspawn.java | 39 +-
.../spawn/EssentialsSpawnPlayerListener.java | 16 +-
37 files changed, 1149 insertions(+), 191 deletions(-)
create mode 100644 Essentials/src/com/earth2me/essentials/AsyncTeleport.java
create mode 100644 Essentials/src/com/earth2me/essentials/AsyncTimedTeleport.java
create mode 100644 Essentials/src/com/earth2me/essentials/api/IAsyncTeleport.java
diff --git a/Essentials/pom.xml b/Essentials/pom.xml
index e18d0b618..d0401835d 100644
--- a/Essentials/pom.xml
+++ b/Essentials/pom.xml
@@ -63,7 +63,7 @@
io.papermc
paperlib
- 1.0.2
+ 1.0.3
compile
diff --git a/Essentials/src/com/earth2me/essentials/AsyncTeleport.java b/Essentials/src/com/earth2me/essentials/AsyncTeleport.java
new file mode 100644
index 000000000..26059dec8
--- /dev/null
+++ b/Essentials/src/com/earth2me/essentials/AsyncTeleport.java
@@ -0,0 +1,428 @@
+package com.earth2me.essentials;
+
+import com.earth2me.essentials.api.IAsyncTeleport;
+import com.earth2me.essentials.commands.WarpNotFoundException;
+import com.earth2me.essentials.utils.DateUtil;
+import com.earth2me.essentials.utils.LocationUtil;
+import io.papermc.lib.PaperLib;
+import net.ess3.api.IEssentials;
+import net.ess3.api.IUser;
+import net.ess3.api.InvalidWorldException;
+import net.ess3.api.events.UserTeleportEvent;
+import net.ess3.api.events.UserWarpEvent;
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.entity.Player;
+import org.bukkit.event.player.PlayerRespawnEvent;
+import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
+
+import java.math.BigDecimal;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+
+import static com.earth2me.essentials.I18n.tl;
+
+
+public class AsyncTeleport implements IAsyncTeleport {
+ private final IUser teleportOwner;
+ private final IEssentials ess;
+ private AsyncTimedTeleport timedTeleport;
+
+ private TeleportType tpType;
+
+ public AsyncTeleport(IUser user, IEssentials ess) {
+ this.teleportOwner = user;
+ this.ess = ess;
+ tpType = TeleportType.NORMAL;
+ }
+
+ public enum TeleportType {
+ TPA,
+ BACK,
+ NORMAL
+ }
+
+ public void cooldown(boolean check) throws Throwable {
+ CompletableFuture exceptionFuture = new CompletableFuture<>();
+ if (cooldown(check, exceptionFuture)) {
+ try {
+ exceptionFuture.get();
+ } catch (ExecutionException e) {
+ throw e.getCause();
+ }
+ }
+ }
+
+ public boolean cooldown(boolean check, CompletableFuture future) {
+ final Calendar time = new GregorianCalendar();
+ if (teleportOwner.getLastTeleportTimestamp() > 0) {
+ // Take the current time, and remove the delay from it.
+ final double cooldown = ess.getSettings().getTeleportCooldown();
+ final Calendar earliestTime = new GregorianCalendar();
+ earliestTime.add(Calendar.SECOND, -(int) cooldown);
+ earliestTime.add(Calendar.MILLISECOND, -(int) ((cooldown * 1000.0) % 1000.0));
+ // This value contains the most recent time a teleportPlayer could have been used that would allow another use.
+ final long earliestLong = earliestTime.getTimeInMillis();
+
+ // When was the last teleportPlayer used?
+ final long lastTime = teleportOwner.getLastTeleportTimestamp();
+
+ if (lastTime > time.getTimeInMillis()) {
+ // This is to make sure time didn't get messed up on last teleportPlayer use.
+ // If this happens, let's give the user the benifit of the doubt.
+ teleportOwner.setLastTeleportTimestamp(time.getTimeInMillis());
+ return false;
+ } else if (lastTime > earliestLong
+ && cooldownApplies()) {
+ time.setTimeInMillis(lastTime);
+ time.add(Calendar.SECOND, (int) cooldown);
+ time.add(Calendar.MILLISECOND, (int) ((cooldown * 1000.0) % 1000.0));
+ future.completeExceptionally(new Exception(tl("timeBeforeTeleport", DateUtil.formatDateDiff(time.getTimeInMillis()))));
+ return true;
+ }
+ }
+ // if justCheck is set, don't update lastTeleport; we're just checking
+ if (!check) {
+ teleportOwner.setLastTeleportTimestamp(time.getTimeInMillis());
+ }
+ return false;
+ }
+
+ private boolean cooldownApplies() {
+ boolean applies = true;
+ String globalBypassPerm = "essentials.teleport.cooldown.bypass";
+ switch (tpType) {
+ case NORMAL:
+ applies = !teleportOwner.isAuthorized(globalBypassPerm);
+ break;
+ case BACK:
+ applies = !(teleportOwner.isAuthorized(globalBypassPerm) &&
+ teleportOwner.isAuthorized("essentials.teleport.cooldown.bypass.back"));
+ break;
+ case TPA:
+ applies = !(teleportOwner.isAuthorized(globalBypassPerm) &&
+ teleportOwner.isAuthorized("essentials.teleport.cooldown.bypass.tpa"));
+ break;
+ }
+ return applies;
+ }
+
+ private void warnUser(final IUser user, final double delay) {
+ Calendar c = new GregorianCalendar();
+ c.add(Calendar.SECOND, (int) delay);
+ c.add(Calendar.MILLISECOND, (int) ((delay * 1000.0) % 1000.0));
+ user.sendMessage(tl("dontMoveMessage", DateUtil.formatDateDiff(c.getTimeInMillis())));
+ }
+
+
+ @Override
+ public void now(Location loc, boolean cooldown, TeleportCause cause, CompletableFuture future) {
+ if (cooldown && cooldown(false, future)) {
+ return;
+ }
+ final ITarget target = new LocationTarget(loc);
+ nowAsync(teleportOwner, target, cause, future);
+ }
+
+ @Override
+ public void now(Player entity, boolean cooldown, TeleportCause cause, CompletableFuture future) {
+ if (cooldown && cooldown(false, future)) {
+ future.complete(false);
+ return;
+ }
+ final ITarget target = new PlayerTarget(entity);
+ nowAsync(teleportOwner, target, cause, future);
+ future.thenAccept(success -> {
+ if (success) {
+ teleportOwner.sendMessage(tl("teleporting", target.getLocation().getWorld().getName(), target.getLocation().getBlockX(), target.getLocation().getBlockY(), target.getLocation().getBlockZ()));
+ }
+ });
+ }
+
+ private void runOnMain(Runnable runnable) throws ExecutionException, InterruptedException {
+ if (Bukkit.isPrimaryThread()) {
+ runnable.run();
+ return;
+ }
+ CompletableFuture