Skip to content

Commit 2dbd63a

Browse files
lewisgibsonRaw2d
andauthored
Use PaperLib's asynchronous teleports and chunk loads on 26.x (#6634)
### Information This PR fixes #6632. ### Details **Proposed fix:** PaperLib 1.0.6 reads the server version from `Bukkit#getVersion()` with a regex that only accepts a single digit major version, so on 26.x it reads the version as 0 and selects its synchronous fallbacks. Every `PaperLib.teleportAsync`, `getChunkAtAsync` and `getBedSpawnLocationAsync` call in EssentialsX then loads the chunk and teleports on the calling thread instead of using Paper's asynchronous API. When PaperLib has not recognised a Paper server that is newer than 1.13, EssentialsX now installs a PaperLib environment which selects the handlers PaperLib would have chosen for 1.15.2 and later. Nothing changes on servers where PaperLib reads the version correctly. **Environments tested:** OS: Ubuntu 24.04 (WSL2) Java version: Temurin 25.0.4.1 - [x] Most recent Paper version (26.3, git-Paper-140) - ~~CraftBukkit/Spigot/Paper 1.12.2~~ - ~~CraftBukkit 1.8.8~~ **Demonstration:** `ModernPaperEnvironmentTest` checks that a 26.x version string leaves PaperLib's own environment calling `Entity#teleport` and that the new environment calls `Entity#teleportAsync` and `World#getChunkAtAsync`. `./gradlew build` passes. Handlers PaperLib selected on Paper 26.3 (build 140), read from the bundled PaperLib by a test plugin: Before (2.x, Paper 26.3 build 140): ``` PROBE paperlib env=PaperEnvironment version=0 asyncChunksHandler=AsyncChunksSync asyncTeleportHandler=AsyncTeleportSync isGeneratedHandler=ChunkIsGeneratedUnknown bedSpawnLocationHandler=BedSpawnLocationSync ``` After (this PR): ``` PROBE paperlib env=ModernPaperEnvironment version=0 asyncChunksHandler=AsyncChunksPaper_15 asyncTeleportHandler=AsyncTeleportPaper_13 isGeneratedHandler=ChunkIsGeneratedApiExists bedSpawnLocationHandler=BedSpawnLocationPaper ``` The same scripted run of EssentialsX commands (teleports across 12000 blocks, `/tpa`, `/home`, `/spawn`, `/back` after death, a passenger, 22 checks) passes before and after. Co-authored-by: Raw2d <255907405+Raw2d@users.noreply.github.com>
1 parent cfb6f12 commit 2dbd63a

3 files changed

Lines changed: 90 additions & 0 deletions

File tree

‎Essentials/src/main/java/com/earth2me/essentials/Essentials.java‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import com.earth2me.essentials.updatecheck.UpdateChecker;
4747
import com.earth2me.essentials.userstorage.ModernUserMap;
4848
import com.earth2me.essentials.utils.FormatUtil;
49+
import com.earth2me.essentials.utils.ModernPaperEnvironment;
4950
import com.earth2me.essentials.utils.PasteUtil;
5051
import com.earth2me.essentials.utils.VersionUtil;
5152
import io.papermc.lib.PaperLib;
@@ -261,6 +262,10 @@ public void onEnable() {
261262
getLogger().severe(getAdventureFacet().miniToLegacy(tlLiteral("serverSnapshot")));
262263
}
263264

265+
if (PaperLib.isPaper() && !PaperLib.isVersion(13) && VersionUtil.getServerBukkitVersion().isHigherThanOrEqualTo(VersionUtil.v1_13_0_R01)) {
266+
PaperLib.setCustomEnvironment(new ModernPaperEnvironment());
267+
}
268+
264269
final PluginManager pm = getServer().getPluginManager();
265270
for (final Plugin plugin : pm.getPlugins()) {
266271
if (plugin.getDescription().getName().startsWith("Essentials") && !plugin.getDescription().getVersion().equals(this.getDescription().getVersion()) && !plugin.getDescription().getName().equals("EssentialsAntiCheat")) {
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.earth2me.essentials.utils;
2+
3+
import io.papermc.lib.environments.PaperEnvironment;
4+
import io.papermc.lib.features.asyncchunks.AsyncChunksPaper_15;
5+
import io.papermc.lib.features.asyncteleport.AsyncTeleportPaper_13;
6+
import io.papermc.lib.features.bedspawnlocation.BedSpawnLocationPaper;
7+
import io.papermc.lib.features.chunkisgenerated.ChunkIsGeneratedApiExists;
8+
9+
/**
10+
* A PaperLib environment for Paper servers whose version PaperLib cannot read.
11+
* <p>
12+
* PaperLib 1.0.6 expects a single digit major version in {@code Bukkit#getVersion()}, so it reads 26.x as version 0 and
13+
* selects its synchronous fallbacks for chunk loading, teleporting and bed lookups. All of those exist on every Paper
14+
* version that reports a year based version.
15+
*/
16+
public class ModernPaperEnvironment extends PaperEnvironment {
17+
public ModernPaperEnvironment() {
18+
asyncChunksHandler = new AsyncChunksPaper_15();
19+
asyncTeleportHandler = new AsyncTeleportPaper_13();
20+
isGeneratedHandler = new ChunkIsGeneratedApiExists();
21+
bedSpawnLocationHandler = new BedSpawnLocationPaper();
22+
}
23+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package com.earth2me.essentials.utils;
2+
3+
import io.papermc.lib.environments.PaperEnvironment;
4+
import org.bukkit.Bukkit;
5+
import org.bukkit.Location;
6+
import org.bukkit.World;
7+
import org.bukkit.entity.Entity;
8+
import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
9+
import org.junit.jupiter.api.Test;
10+
import org.mockito.MockedStatic;
11+
12+
import java.util.function.Consumer;
13+
import java.util.function.Supplier;
14+
15+
import static org.mockito.ArgumentMatchers.any;
16+
import static org.mockito.Mockito.mock;
17+
import static org.mockito.Mockito.mockStatic;
18+
import static org.mockito.Mockito.never;
19+
import static org.mockito.Mockito.verify;
20+
21+
public class ModernPaperEnvironmentTest {
22+
private static final String YEAR_VERSION = "26.3-140-main@a1b2c3d (2026-09-30T10:00:00Z) (MC: 26.3)";
23+
24+
@Test
25+
public void testPaperLibFallsBackToSynchronousTeleportOnYearVersions() {
26+
// Documents why ModernPaperEnvironment exists: PaperLib itself picks the synchronous handlers here.
27+
withVersion(YEAR_VERSION, PaperEnvironment::new, env -> {
28+
final Entity entity = mock(Entity.class);
29+
final Location location = new Location(mock(World.class), 0, 64, 0);
30+
env.teleport(entity, location, TeleportCause.PLUGIN);
31+
verify(entity).teleport(location, TeleportCause.PLUGIN);
32+
verify(entity, never()).teleportAsync(any(Location.class), any(TeleportCause.class));
33+
});
34+
}
35+
36+
@Test
37+
public void testTeleportsAsynchronouslyOnYearVersions() {
38+
withVersion(YEAR_VERSION, ModernPaperEnvironment::new, env -> {
39+
final Entity entity = mock(Entity.class);
40+
final Location location = new Location(mock(World.class), 0, 64, 0);
41+
env.teleport(entity, location, TeleportCause.PLUGIN);
42+
verify(entity).teleportAsync(location, TeleportCause.PLUGIN);
43+
verify(entity, never()).teleport(any(Location.class), any(TeleportCause.class));
44+
});
45+
}
46+
47+
@Test
48+
public void testLoadsChunksAsynchronouslyOnYearVersions() {
49+
withVersion(YEAR_VERSION, ModernPaperEnvironment::new, env -> {
50+
final World world = mock(World.class);
51+
env.getChunkAtAsync(world, 1, 2, true);
52+
verify(world).getChunkAtAsync(1, 2, true, false);
53+
});
54+
}
55+
56+
private static <E extends PaperEnvironment> void withVersion(final String version, final Supplier<E> create, final Consumer<E> test) {
57+
try (MockedStatic<Bukkit> bukkit = mockStatic(Bukkit.class)) {
58+
bukkit.when(Bukkit::getVersion).thenReturn(version);
59+
test.accept(create.get());
60+
}
61+
}
62+
}

0 commit comments

Comments
 (0)