Skip to content

Commit 1b1d238

Browse files
committed
fix reinject on folia servers
1 parent ae594e1 commit 1b1d238

3 files changed

Lines changed: 332 additions & 9 deletions

File tree

src/dev/_2lstudios/hamsterapi/hamsterplayer/HamsterPlayerManager.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
package dev._2lstudios.hamsterapi.hamsterplayer;
22

3-
import java.util.HashMap;
43
import java.util.Map;
54
import java.util.UUID;
5+
import java.util.concurrent.ConcurrentHashMap;
66

77
import org.bukkit.entity.Player;
88

99
public class HamsterPlayerManager {
10-
final Map<UUID, HamsterPlayer> hamsterPlayers = new HashMap<>();
10+
final Map<UUID, HamsterPlayer> hamsterPlayers = new ConcurrentHashMap<>();
1111

1212
public HamsterPlayer add(final Player player) {
1313
final HamsterPlayer hamsterPlayer = new HamsterPlayer(player);

src/dev/_2lstudios/hamsterapi/listeners/PlayerJoinListener.java

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,19 @@
77
import org.bukkit.event.EventPriority;
88
import org.bukkit.event.Listener;
99
import org.bukkit.event.player.PlayerJoinEvent;
10-
import org.bukkit.scheduler.BukkitScheduler;
1110

1211
import dev._2lstudios.hamsterapi.HamsterAPI;
1312
import dev._2lstudios.hamsterapi.hamsterplayer.HamsterPlayer;
1413
import dev._2lstudios.hamsterapi.hamsterplayer.HamsterPlayerManager;
14+
import dev._2lstudios.hamsterapi.utils.FoliaAPI;
1515

1616
public class PlayerJoinListener implements Listener {
1717
private final Logger logger;
1818
private final HamsterPlayerManager hamsterPlayerManager;
19-
private final BukkitScheduler scheduler;
20-
private final HamsterAPI hamsterAPI;
2119

2220
public PlayerJoinListener(final HamsterAPI hamsterAPI) {
2321
this.logger = hamsterAPI.getLogger();
2422
this.hamsterPlayerManager = hamsterAPI.getHamsterPlayerManager();
25-
this.scheduler = hamsterAPI.getServer().getScheduler();
26-
this.hamsterAPI = hamsterAPI;
2723
}
2824

2925
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
@@ -35,7 +31,7 @@ public void onPlayerJoin(final PlayerJoinEvent event) {
3531
logger.warning("Failed to inject player " + player.getName()
3632
+ ". Retrying...");
3733
// Retry after 5 ticks
38-
scheduler.runTaskLater(hamsterAPI, () -> {
34+
FoliaAPI.runTaskAsync(() -> {
3935
if (player != null && player.isOnline() && hamsterPlayerManager.get(player) != null) {
4036
if (hamsterPlayer.tryInject()) {
4137
logger.info("Successfully injected player " + player.getName() + " after failing!");
@@ -44,7 +40,7 @@ public void onPlayerJoin(final PlayerJoinEvent event) {
4440
}
4541
}
4642
}, 5L);
47-
scheduler.runTaskLater(hamsterAPI, () -> {
43+
FoliaAPI.runTaskAsync(() -> {
4844
if (player != null && player.isOnline() && hamsterPlayerManager.get(player) != null) {
4945
hamsterPlayer.checkAndReorderHandlers();
5046
}
Lines changed: 327 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,327 @@
1+
package dev._2lstudios.hamsterapi.utils;
2+
3+
import java.lang.reflect.Method;
4+
import java.util.HashMap;
5+
import java.util.Map;
6+
import java.util.concurrent.CompletableFuture;
7+
import java.util.concurrent.Executors;
8+
import java.util.function.Consumer;
9+
10+
import org.bukkit.Bukkit;
11+
import org.bukkit.Location;
12+
import org.bukkit.Server;
13+
import org.bukkit.World;
14+
import org.bukkit.entity.Entity;
15+
import org.bukkit.entity.Player;
16+
import org.bukkit.plugin.Plugin;
17+
import org.bukkit.scheduler.BukkitScheduler;
18+
19+
import dev._2lstudios.hamsterapi.HamsterAPI;
20+
21+
public class FoliaAPI {
22+
private static Map<String, Method> cachedMethods = new HashMap<>();
23+
24+
private static BukkitScheduler bS = Bukkit.getScheduler();
25+
private static Object globalRegionScheduler = getGlobalRegionScheduler();
26+
private static Object regionScheduler = getRegionScheduler();
27+
private static Object asyncScheduler = getAsyncScheduler();
28+
29+
// Cache methods as early as possible
30+
static {
31+
cacheMethods();
32+
}
33+
34+
private static Method getMethod(Class<?> clazz, String methodName, Class<?>... parameterTypes) {
35+
if (clazz == null) {
36+
return null;
37+
}
38+
try {
39+
return clazz.getMethod(methodName, parameterTypes);
40+
} catch (NoSuchMethodException e) {
41+
// Gracefully handle the case where the method does not exist
42+
return null;
43+
}
44+
}
45+
46+
private static void cacheMethods() {
47+
// Cache methods for globalRegionScheduler
48+
if (globalRegionScheduler != null) {
49+
Method runAtFixedRateMethod = getMethod(globalRegionScheduler.getClass(), "runAtFixedRate", Plugin.class, Consumer.class, long.class, long.class);
50+
if (runAtFixedRateMethod != null) {
51+
cachedMethods.put("globalRegionScheduler.runAtFixedRate", runAtFixedRateMethod);
52+
}
53+
54+
Method runMethod = getMethod(globalRegionScheduler.getClass(), "run", Plugin.class, Consumer.class);
55+
if (runMethod != null) {
56+
cachedMethods.put("globalRegionScheduler.run", runMethod);
57+
}
58+
59+
Method runDelayedMethod = getMethod(globalRegionScheduler.getClass(), "runDelayed", Plugin.class, Consumer.class, long.class);
60+
if (runDelayedMethod != null) {
61+
cachedMethods.put("globalRegionScheduler.runDelayed", runDelayedMethod);
62+
}
63+
64+
Method cancelTasksMethod = getMethod(globalRegionScheduler.getClass(), "cancelTasks", Plugin.class);
65+
if (cancelTasksMethod != null) {
66+
cachedMethods.put("globalRegionScheduler.cancelTasks", cancelTasksMethod);
67+
}
68+
}
69+
70+
// Cache methods for regionScheduler
71+
if (regionScheduler != null) {
72+
Method executeMethod = getMethod(regionScheduler.getClass(), "execute", Plugin.class, World.class, int.class, int.class, Runnable.class);
73+
if (executeMethod != null) {
74+
cachedMethods.put("regionScheduler.execute", executeMethod);
75+
}
76+
77+
Method executeLocationMethod = getMethod(regionScheduler.getClass(), "execute", Plugin.class, Location.class, Runnable.class);
78+
if (executeLocationMethod != null) {
79+
cachedMethods.put("regionScheduler.executeLocation", executeLocationMethod);
80+
}
81+
82+
Method runAtFixedRateMethod = getMethod(regionScheduler.getClass(), "runAtFixedRate", Plugin.class, Location.class, Consumer.class, long.class, long.class);
83+
if (runAtFixedRateMethod != null) {
84+
cachedMethods.put("regionScheduler.runAtFixedRate", runAtFixedRateMethod);
85+
}
86+
87+
Method runDelayedMethod = getMethod(regionScheduler.getClass(), "runDelayed", Plugin.class, Location.class, Consumer.class, long.class);
88+
if (runDelayedMethod != null) {
89+
cachedMethods.put("regionScheduler.runDelayed", runDelayedMethod);
90+
}
91+
}
92+
93+
// Cache methods for entity scheduler
94+
Method getSchedulerMethod = getMethod(Entity.class, "getScheduler");
95+
if (getSchedulerMethod != null) {
96+
cachedMethods.put("entity.getScheduler", getSchedulerMethod);
97+
}
98+
99+
Method executeEntityMethod = getMethod(Entity.class, "execute", Plugin.class, Runnable.class, Runnable.class, long.class);
100+
if (executeEntityMethod != null) {
101+
cachedMethods.put("entityScheduler.execute", executeEntityMethod);
102+
}
103+
104+
Method runAtFixedRateEntityMethod = getMethod(Entity.class, "runAtFixedRate", Plugin.class, Consumer.class, Runnable.class, long.class, long.class);
105+
if (runAtFixedRateEntityMethod != null) {
106+
cachedMethods.put("entityScheduler.runAtFixedRate", runAtFixedRateEntityMethod);
107+
}
108+
109+
// Cache method for Player teleportAsync
110+
Method teleportAsyncMethod = getMethod(Player.class, "teleportAsync", Location.class);
111+
if (teleportAsyncMethod != null) {
112+
cachedMethods.put("player.teleportAsync", teleportAsyncMethod);
113+
}
114+
115+
// Cache methods for asyncScheduler
116+
if (asyncScheduler != null) {
117+
Method cancelTasksMethod = getMethod(asyncScheduler.getClass(), "cancelTasks", Plugin.class);
118+
if (cancelTasksMethod != null) {
119+
cachedMethods.put("asyncScheduler.cancelTasks", cancelTasksMethod);
120+
}
121+
}
122+
}
123+
124+
private static Object invokeMethod(Method method, Object object, Object... args) {
125+
try {
126+
if (method != null && object != null) {
127+
method.setAccessible(true);
128+
return method.invoke(object, args);
129+
}
130+
} catch (Exception e) {
131+
e.printStackTrace();
132+
}
133+
return null;
134+
}
135+
136+
private static Object getGlobalRegionScheduler() {
137+
Method method = getMethod(Server.class, "getGlobalRegionScheduler");
138+
return invokeMethod(method, Bukkit.getServer());
139+
}
140+
141+
private static Object getRegionScheduler() {
142+
Method method = getMethod(Server.class, "getRegionScheduler");
143+
return invokeMethod(method, Bukkit.getServer());
144+
}
145+
146+
private static Object getAsyncScheduler() {
147+
Method method = getMethod(Server.class, "getAsyncScheduler");
148+
return invokeMethod(method, Bukkit.getServer());
149+
}
150+
151+
public static boolean isFolia() {
152+
try {
153+
Class.forName("io.papermc.paper.threadedregions.RegionizedServer");
154+
return globalRegionScheduler != null && regionScheduler != null;
155+
} catch (Exception ig) {
156+
return false;
157+
}
158+
}
159+
160+
public static void runTaskAsync(Runnable run, long delay) {
161+
if (!isFolia()) {
162+
bS.runTaskLaterAsynchronously(HamsterAPI.getInstance(), run, delay);
163+
return;
164+
}
165+
Executors.defaultThreadFactory().newThread(run).start();
166+
}
167+
168+
public static void runTaskAsync(Runnable run) {
169+
runTaskAsync(run, 1L);
170+
}
171+
172+
public static void runTaskTimerAsync(Consumer<Object> run, long delay, long period) {
173+
if (!isFolia()) {
174+
bS.runTaskTimerAsynchronously(HamsterAPI.getInstance(), () -> run.accept(null), delay, period);
175+
return;
176+
}
177+
Method method = cachedMethods.get("globalRegionScheduler.runAtFixedRate");
178+
invokeMethod(method, globalRegionScheduler, HamsterAPI.getInstance(), run, delay, period);
179+
}
180+
181+
public static void runTaskTimerAsync(Runnable runnable, long delay, long period) {
182+
runTaskTimerAsync(obj -> runnable.run(), delay, period);
183+
}
184+
185+
public static void runTaskTimer(Consumer<Object> run, long delay, long period) {
186+
if (!isFolia()) {
187+
bS.runTaskTimer(HamsterAPI.getInstance(), () -> run.accept(null), delay, period);
188+
return;
189+
}
190+
Method method = cachedMethods.get("globalRegionScheduler.runAtFixedRate");
191+
invokeMethod(method, globalRegionScheduler, HamsterAPI.getInstance(), run, delay, period);
192+
}
193+
194+
public static void runTask(Runnable run) {
195+
if (!isFolia()) {
196+
bS.runTask(HamsterAPI.getInstance(), run);
197+
return;
198+
}
199+
Method method = cachedMethods.get("globalRegionScheduler.run");
200+
invokeMethod(method, globalRegionScheduler, HamsterAPI.getInstance(), (Consumer<Object>) ignored -> run.run());
201+
}
202+
203+
public static void runTask(Consumer<Object> run) {
204+
if (!isFolia()) {
205+
bS.runTask(HamsterAPI.getInstance(), () -> run.accept(null));
206+
return;
207+
}
208+
Method method = cachedMethods.get("globalRegionScheduler.run");
209+
invokeMethod(method, globalRegionScheduler, HamsterAPI.getInstance(), run);
210+
}
211+
212+
public static void runTaskLater(Runnable run, long delay) {
213+
if (!isFolia()) {
214+
bS.runTaskLater(HamsterAPI.getInstance(), run, delay);
215+
return;
216+
}
217+
// Use Folia's global region scheduler for a delayed task
218+
Method method = cachedMethods.get("globalRegionScheduler.runDelayed");
219+
invokeMethod(method, globalRegionScheduler, HamsterAPI.getInstance(), (Consumer<Object>) ignored -> run.run(), delay);
220+
}
221+
222+
public static void runTaskLater(Consumer<Object> run, long delay) {
223+
if (!isFolia()) {
224+
bS.runTaskLater(HamsterAPI.getInstance(), () -> run.accept(null), delay);
225+
return;
226+
}
227+
// Use Folia's global region scheduler for a delayed task
228+
Method method = cachedMethods.get("globalRegionScheduler.runDelayed");
229+
invokeMethod(method, globalRegionScheduler, HamsterAPI.getInstance(), run, delay);
230+
}
231+
232+
public static void runTaskForEntity(Entity entity, Runnable run, Runnable retired, long delay) {
233+
if (!isFolia()) {
234+
bS.runTaskLater(HamsterAPI.getInstance(), run, delay);
235+
return;
236+
}
237+
if (entity == null) return;
238+
Method getSchedulerMethod = cachedMethods.get("entity.getScheduler");
239+
Object entityScheduler = invokeMethod(getSchedulerMethod, entity);
240+
Method executeMethod = cachedMethods.get("entityScheduler.execute");
241+
invokeMethod(executeMethod, entityScheduler, HamsterAPI.getInstance(), run, retired, delay);
242+
}
243+
244+
public static void runTaskForEntityRepeating(Entity entity, Consumer<Object> task, Runnable retired,
245+
long initialDelay, long period) {
246+
if (!isFolia()) {
247+
bS.runTaskTimer(HamsterAPI.getInstance(), () -> task.accept(null), initialDelay, period);
248+
return;
249+
}
250+
if (entity == null) return;
251+
Method getSchedulerMethod = cachedMethods.get("entity.getScheduler");
252+
Object entityScheduler = invokeMethod(getSchedulerMethod, entity);
253+
Method runAtFixedRateMethod = cachedMethods.get("entityScheduler.runAtFixedRate");
254+
invokeMethod(runAtFixedRateMethod, entityScheduler, HamsterAPI.getInstance(), task, retired, initialDelay, period);
255+
}
256+
257+
public static void runTaskForRegion(World world, int chunkX, int chunkZ, Runnable run) {
258+
if (!isFolia()) {
259+
bS.runTask(HamsterAPI.getInstance(), run);
260+
return;
261+
}
262+
if (world == null) return;
263+
Method executeMethod = cachedMethods.get("regionScheduler.execute");
264+
invokeMethod(executeMethod, regionScheduler, HamsterAPI.getInstance(), world, chunkX, chunkZ, run);
265+
}
266+
267+
public static void runTaskForRegion(Location location, Runnable run) {
268+
if (!isFolia()) {
269+
bS.runTask(HamsterAPI.getInstance(), run);
270+
return;
271+
}
272+
if (location == null) return;
273+
Method executeMethod = cachedMethods.get("regionScheduler.executeLocation");
274+
invokeMethod(executeMethod, regionScheduler, HamsterAPI.getInstance(), location, run);
275+
}
276+
277+
public static void runTaskForRegionRepeating(Location location, Consumer<Object> task, long initialDelay,
278+
long period) {
279+
if (!isFolia()) {
280+
bS.runTaskTimer(HamsterAPI.getInstance(), () -> task.accept(null), initialDelay, period);
281+
return;
282+
}
283+
if (location == null) return;
284+
Method runAtFixedRateMethod = cachedMethods.get("regionScheduler.runAtFixedRate");
285+
invokeMethod(runAtFixedRateMethod, regionScheduler, HamsterAPI.getInstance(), location, task, initialDelay, period);
286+
}
287+
288+
public static void runTaskForRegionDelayed(Location location, Consumer<Object> task, long delay) {
289+
if (!isFolia()) {
290+
bS.runTaskLater(HamsterAPI.getInstance(), () -> task.accept(null), delay);
291+
return;
292+
}
293+
if (location == null) return;
294+
Method runDelayedMethod = cachedMethods.get("regionScheduler.runDelayed");
295+
invokeMethod(runDelayedMethod, regionScheduler, HamsterAPI.getInstance(), location, task, delay);
296+
}
297+
298+
public static CompletableFuture<Boolean> teleportPlayer(Player e, Location location, Boolean async) {
299+
if (!isFolia()) {
300+
e.teleport(location);
301+
return CompletableFuture.completedFuture(true);
302+
} else if (async) {
303+
Method teleportMethod = cachedMethods.get("player.teleportAsync");
304+
return CompletableFuture.completedFuture(invokeMethod(teleportMethod, e, location) != null);
305+
} else {
306+
e.teleport(location);
307+
return CompletableFuture.completedFuture(true);
308+
}
309+
}
310+
311+
public static void cancelAllTasks() {
312+
Plugin plugin = HamsterAPI.getInstance();
313+
if (!isFolia()) {
314+
// Standard Bukkit/Spigot/Paper: cancel all tasks for the plugin
315+
bS.cancelTasks(plugin);
316+
return;
317+
}
318+
319+
// 1. Cancel tasks on the GlobalRegionScheduler
320+
Method cancelGlobalMethod = cachedMethods.get("globalRegionScheduler.cancelTasks");
321+
invokeMethod(cancelGlobalMethod, globalRegionScheduler, plugin);
322+
323+
// 2. Cancel tasks on the modern AsyncScheduler
324+
Method cancelAsyncMethod = cachedMethods.get("asyncScheduler.cancelTasks");
325+
invokeMethod(cancelAsyncMethod, asyncScheduler, plugin);
326+
}
327+
}

0 commit comments

Comments
 (0)