Skip to content

Commit d188618

Browse files
authored
Merge pull request #4120 from ashduino101/v3.0
1.21 support for Bukkit/Spigot
2 parents 425ca0f + 092aa52 commit d188618

File tree

9 files changed

+861
-0
lines changed

9 files changed

+861
-0
lines changed

bukkit-helper-121/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/build/

bukkit-helper-121/build.gradle

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
eclipse {
2+
project {
3+
name = "Dynmap(Spigot-1.21)"
4+
}
5+
}
6+
7+
description = 'bukkit-helper-1.21'
8+
9+
sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = JavaLanguageVersion.of(17) // Need this here so eclipse task generates correctly.
10+
11+
dependencies {
12+
implementation project(':bukkit-helper')
13+
implementation project(':dynmap-api')
14+
implementation project(path: ':DynmapCore', configuration: 'shadow')
15+
compileOnly group: 'org.spigotmc', name: 'spigot-api', version:'1.21-R0.1-SNAPSHOT'
16+
compileOnly group: 'org.spigotmc', name: 'spigot', version:'1.21-R0.1-SNAPSHOT'
17+
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package org.dynmap.bukkit.helper.v121;
2+
3+
import net.minecraft.nbt.NBTTagCompound;
4+
import net.minecraft.server.MinecraftServer;
5+
import net.minecraft.server.level.WorldServer;
6+
import net.minecraft.world.level.chunk.Chunk;
7+
import net.minecraft.world.level.chunk.IChunkAccess;
8+
import net.minecraft.world.level.chunk.storage.ChunkRegionLoader;
9+
import org.bukkit.Bukkit;
10+
import org.bukkit.craftbukkit.v1_21_R1.CraftServer;
11+
import org.bukkit.craftbukkit.v1_21_R1.CraftWorld;
12+
import org.dynmap.MapManager;
13+
14+
import java.lang.reflect.InvocationTargetException;
15+
import java.lang.reflect.Method;
16+
import java.util.Arrays;
17+
import java.util.Objects;
18+
import java.util.concurrent.CompletableFuture;
19+
import java.util.concurrent.ExecutionException;
20+
import java.util.function.BiConsumer;
21+
import java.util.function.Supplier;
22+
23+
/**
24+
* The provider used to work with paper libs
25+
* Because paper libs need java 17 we can't interact with them directly
26+
*/
27+
@SuppressWarnings({"JavaReflectionMemberAccess"}) //java don't know about paper
28+
public class AsyncChunkProvider121 {
29+
private final Method getChunk;
30+
private final Method getAsyncSaveData;
31+
private final Method save;
32+
private final Enum<?> data;
33+
private final Enum<?> priority;
34+
private int currTick = MinecraftServer.currentTick;
35+
private int currChunks = 0;
36+
37+
AsyncChunkProvider121() {
38+
try {
39+
Method getChunk1 = null;
40+
Method getAsyncSaveData1 = null;
41+
Method save1 = null;
42+
Enum<?> priority1 = null;
43+
Enum<?> data1 = null;
44+
try {
45+
Class<?> threadClass = Class.forName("io.papermc.paper.chunk.system.io.RegionFileIOThread");
46+
47+
Class<?> dataclass = Arrays.stream(threadClass.getDeclaredClasses())
48+
.filter(c -> c.getSimpleName().equals("RegionFileType"))
49+
.findAny()
50+
.orElseThrow(NullPointerException::new);
51+
data1 = Enum.valueOf(cast(dataclass), "CHUNK_DATA");
52+
53+
Class<?> priorityClass = Arrays.stream(Class.forName("ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor").getClasses())
54+
.filter(c -> c.getSimpleName().equals("Priority"))
55+
.findAny()
56+
.orElseThrow(NullPointerException::new);
57+
//Almost lowest priority, but not quite so low as to be considered idle
58+
//COMPLETING->BLOCKING->HIGHEST->HIGHER->HIGH->NORMAL->LOW->LOWER->LOWEST->IDLE
59+
priority1 = Enum.valueOf(cast(priorityClass), "LOWEST");
60+
61+
getAsyncSaveData1 = ChunkRegionLoader.class.getMethod("getAsyncSaveData", WorldServer.class, IChunkAccess.class);
62+
save1 = ChunkRegionLoader.class.getMethod("saveChunk", WorldServer.class, IChunkAccess.class, getAsyncSaveData1.getReturnType());
63+
getChunk1 = threadClass.getMethod("loadDataAsync", WorldServer.class, int.class, int.class, data1.getClass(), BiConsumer.class, boolean.class, priority1.getClass());
64+
} catch (ClassNotFoundException | NoSuchMethodException e) {
65+
e.printStackTrace();
66+
}
67+
getAsyncSaveData = Objects.requireNonNull(getAsyncSaveData1);
68+
save = Objects.requireNonNull(save1);
69+
getChunk = Objects.requireNonNull(getChunk1);
70+
data = Objects.requireNonNull(data1);
71+
priority = Objects.requireNonNull(priority1);
72+
} catch (Throwable e) {
73+
e.printStackTrace();
74+
throw new RuntimeException(e);
75+
}
76+
}
77+
78+
@SuppressWarnings("unchecked")
79+
private <T> T cast(Object o) {
80+
return (T) o;
81+
}
82+
public CompletableFuture<NBTTagCompound> getChunk(WorldServer world, int x, int y) throws InvocationTargetException, IllegalAccessException {
83+
CompletableFuture<NBTTagCompound> future = new CompletableFuture<>();
84+
getChunk.invoke(null, world, x, y, data, (BiConsumer<NBTTagCompound, Throwable>) (nbt, exception) -> future.complete(nbt), true, priority);
85+
return future;
86+
}
87+
88+
public synchronized Supplier<NBTTagCompound> getLoadedChunk(CraftWorld world, int x, int z) {
89+
if (!world.isChunkLoaded(x, z)) return () -> null;
90+
Chunk c = world.getHandle().getChunkIfLoaded(x, z); //already safe async on vanilla
91+
if ((c == null) || !c.q) return () -> null; // c.loaded
92+
if (currTick != MinecraftServer.currentTick) {
93+
currTick = MinecraftServer.currentTick;
94+
currChunks = 0;
95+
}
96+
//prepare data synchronously
97+
CompletableFuture<?> future = CompletableFuture.supplyAsync(() -> {
98+
//Null will mean that we save with spigot methods, which may be risky on async
99+
//Since we're not in main thread, it now refuses new tasks because of shutdown, the risk is lower
100+
if (!Bukkit.isPrimaryThread()) return null;
101+
try {
102+
return getAsyncSaveData.invoke(null, world.getHandle(), c);
103+
} catch (ReflectiveOperationException e) {
104+
throw new RuntimeException(e);
105+
}
106+
}, ((CraftServer) Bukkit.getServer()).getServer());
107+
//we shouldn't stress main thread
108+
if (++currChunks > MapManager.mapman.getMaxChunkLoadsPerTick()) {
109+
try {
110+
Thread.sleep(25); //hold the lock so other threads also won't stress main thread
111+
} catch (InterruptedException ignored) {}
112+
}
113+
//save data asynchronously
114+
return () -> {
115+
Object o = null;
116+
try {
117+
o = future.get();
118+
return (NBTTagCompound) save.invoke(null, world.getHandle(), c, o);
119+
} catch (InterruptedException e) {
120+
return null;
121+
} catch (InvocationTargetException e) {
122+
//We tried to use simple spigot methods at shutdown and failed, hopes for reading from disk
123+
if (o == null) return null;
124+
throw new RuntimeException(e);
125+
} catch (ReflectiveOperationException | ExecutionException e) {
126+
throw new RuntimeException(e);
127+
}
128+
};
129+
}
130+
}

0 commit comments

Comments
 (0)