diff --git a/bukkit-helper-116-2/src/main/java/org/dynmap/bukkit/helper/v116_2/BukkitVersionHelperSpigot116_2.java b/bukkit-helper-116-2/src/main/java/org/dynmap/bukkit/helper/v116_2/BukkitVersionHelperSpigot116_2.java index e493ec2d..1b4fa1e9 100644 --- a/bukkit-helper-116-2/src/main/java/org/dynmap/bukkit/helper/v116_2/BukkitVersionHelperSpigot116_2.java +++ b/bukkit-helper-116-2/src/main/java/org/dynmap/bukkit/helper/v116_2/BukkitVersionHelperSpigot116_2.java @@ -37,12 +37,14 @@ import net.minecraft.server.v1_16_R2.MinecraftServer; * Helper for isolation of bukkit version specific issues */ public class BukkitVersionHelperSpigot116_2 extends BukkitVersionHelperGeneric { - private Field watercolorfield; + private final boolean unsafeAsync; + private Field watercolorfield; public BukkitVersionHelperSpigot116_2() { Class biomefog = getNMSClass("net.minecraft.server.BiomeFog"); watercolorfield = getPrivateField(biomefog, new String[] { "c" }, int.class); - } + this.unsafeAsync = true; + } /** * Get block short name list @@ -69,7 +71,13 @@ public class BukkitVersionHelperSpigot116_2 extends BukkitVersionHelperGeneric { } private Object[] biomelist; - /** + + @Override + public boolean isUnsafeAsync() { + return unsafeAsync; + } + + /** * Get list of defined biomebase objects */ @Override diff --git a/bukkit-helper-116-3/src/main/java/org/dynmap/bukkit/helper/v116_3/BukkitVersionHelperSpigot116_3.java b/bukkit-helper-116-3/src/main/java/org/dynmap/bukkit/helper/v116_3/BukkitVersionHelperSpigot116_3.java index ae63946c..16cd5805 100644 --- a/bukkit-helper-116-3/src/main/java/org/dynmap/bukkit/helper/v116_3/BukkitVersionHelperSpigot116_3.java +++ b/bukkit-helper-116-3/src/main/java/org/dynmap/bukkit/helper/v116_3/BukkitVersionHelperSpigot116_3.java @@ -37,12 +37,14 @@ import net.minecraft.server.v1_16_R2.BlockPosition; * Helper for isolation of bukkit version specific issues */ public class BukkitVersionHelperSpigot116_3 extends BukkitVersionHelperGeneric { - private Field watercolorfield; + private final boolean unsafeAsync; + private Field watercolorfield; public BukkitVersionHelperSpigot116_3() { Class biomefog = getNMSClass("net.minecraft.server.BiomeFog"); watercolorfield = getPrivateField(biomefog, new String[] { "c" }, int.class); - } + this.unsafeAsync = true; + } /** * Get block short name list @@ -69,7 +71,13 @@ public class BukkitVersionHelperSpigot116_3 extends BukkitVersionHelperGeneric { } private Object[] biomelist; - /** + + @Override + public boolean isUnsafeAsync() { + return unsafeAsync; + } + + /** * Get list of defined biomebase objects */ @Override diff --git a/bukkit-helper-116-4/src/main/java/org/dynmap/bukkit/helper/v116_4/BukkitVersionHelperSpigot116_4.java b/bukkit-helper-116-4/src/main/java/org/dynmap/bukkit/helper/v116_4/BukkitVersionHelperSpigot116_4.java index 32ae9698..b0243d8f 100644 --- a/bukkit-helper-116-4/src/main/java/org/dynmap/bukkit/helper/v116_4/BukkitVersionHelperSpigot116_4.java +++ b/bukkit-helper-116-4/src/main/java/org/dynmap/bukkit/helper/v116_4/BukkitVersionHelperSpigot116_4.java @@ -26,12 +26,14 @@ import java.util.List; * Helper for isolation of bukkit version specific issues */ public class BukkitVersionHelperSpigot116_4 extends BukkitVersionHelperGeneric { - private Field watercolorfield; + private final boolean unsafeAsync; + private Field watercolorfield; public BukkitVersionHelperSpigot116_4() { Class biomefog = getNMSClass("net.minecraft.server.BiomeFog"); watercolorfield = getPrivateField(biomefog, new String[] { "c" }, int.class); - } + this.unsafeAsync = true; + } /** * Get block short name list @@ -58,7 +60,13 @@ public class BukkitVersionHelperSpigot116_4 extends BukkitVersionHelperGeneric { } private Object[] biomelist; - /** + + @Override + public boolean isUnsafeAsync() { + return unsafeAsync; + } + + /** * Get list of defined biomebase objects */ @Override diff --git a/bukkit-helper-116/src/main/java/org/dynmap/bukkit/helper/v116/BukkitVersionHelperSpigot116.java b/bukkit-helper-116/src/main/java/org/dynmap/bukkit/helper/v116/BukkitVersionHelperSpigot116.java index c522d77d..9646e6cd 100644 --- a/bukkit-helper-116/src/main/java/org/dynmap/bukkit/helper/v116/BukkitVersionHelperSpigot116.java +++ b/bukkit-helper-116/src/main/java/org/dynmap/bukkit/helper/v116/BukkitVersionHelperSpigot116.java @@ -36,12 +36,14 @@ import net.minecraft.server.v1_16_R1.BlockPosition; * Helper for isolation of bukkit version specific issues */ public class BukkitVersionHelperSpigot116 extends BukkitVersionHelperGeneric { + private final boolean unsafeAsync; private Field watercolorfield; public BukkitVersionHelperSpigot116() { Class biomefog = getNMSClass("net.minecraft.server.BiomeFog"); watercolorfield = getPrivateField(biomefog, new String[] { "c" }, int.class); - } + this.unsafeAsync = true; + } /** * Get block short name list @@ -59,7 +61,13 @@ public class BukkitVersionHelperSpigot116 extends BukkitVersionHelperGeneric { } private Object[] biomelist; - /** + + @Override + public boolean isUnsafeAsync() { + return unsafeAsync; + } + + /** * Get list of defined biomebase objects */ @Override diff --git a/bukkit-helper-117/src/main/java/org/dynmap/bukkit/helper/v117/BukkitVersionHelperSpigot117.java b/bukkit-helper-117/src/main/java/org/dynmap/bukkit/helper/v117/BukkitVersionHelperSpigot117.java index 96116e88..e56fad95 100644 --- a/bukkit-helper-117/src/main/java/org/dynmap/bukkit/helper/v117/BukkitVersionHelperSpigot117.java +++ b/bukkit-helper-117/src/main/java/org/dynmap/bukkit/helper/v117/BukkitVersionHelperSpigot117.java @@ -58,11 +58,19 @@ import java.util.Map; /** * Helper for isolation of bukkit version specific issues */ -public class BukkitVersionHelperSpigot117 extends BukkitVersionHelper { - public BukkitVersionHelperSpigot117() { +public class BukkitVersionHelperSpigot117 extends BukkitVersionHelper { + private final boolean unsafeAsync; + + public BukkitVersionHelperSpigot117() { + this.unsafeAsync = true; } - - /** + + @Override + public boolean isUnsafeAsync() { + return unsafeAsync; + } + + /** * Get block short name list */ @Override diff --git a/bukkit-helper-118-2/src/main/java/org/dynmap/bukkit/helper/v118_2/AsyncChunkProvider118_2.java b/bukkit-helper-118-2/src/main/java/org/dynmap/bukkit/helper/v118_2/AsyncChunkProvider118_2.java new file mode 100644 index 00000000..9bbdd6b5 --- /dev/null +++ b/bukkit-helper-118-2/src/main/java/org/dynmap/bukkit/helper/v118_2/AsyncChunkProvider118_2.java @@ -0,0 +1,56 @@ +package org.dynmap.bukkit.helper.v118_2; + +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.server.level.WorldServer; +import net.minecraft.world.level.World; +import net.minecraft.world.level.chunk.Chunk; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; +import java.util.function.Predicate; + +/** + * The provider used to work with paper libs + * Because paper libs need java 17 we can't interact with them directly + */ +public class AsyncChunkProvider118_2 { + private static final Thread ioThread; + private static final Method getChunk; + private static final Predicate ifFailed; + static { + try { + Predicate ifFailed1 = null; + Method getChunk1 = null; + Thread ioThread1 = null; + try { + Class threadClass = Class.forName("com.destroystokyo.paper.io.PaperFileIOThread"); + Class[] classes = threadClass.getClasses(); + Class holder = Arrays.stream(classes).filter(aClass -> aClass.getSimpleName().equals("Holder")).findAny().orElseThrow(RuntimeException::new); + ioThread1 = (Thread) holder.getField("INSTANCE").get(null); + getChunk1 = threadClass.getMethod("loadChunkDataAsync", WorldServer.class, int.class, int.class, int.class, Consumer.class, boolean.class, boolean.class, boolean.class); + NBTTagCompound failure = (NBTTagCompound) threadClass.getField("FAILURE_VALUE").get(null); + ifFailed1 = nbtTagCompound -> nbtTagCompound == failure; + } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException | NoSuchMethodException e) { + e.printStackTrace(); + } + ifFailed = Objects.requireNonNull(ifFailed1); + getChunk = Objects.requireNonNull(getChunk1); + ioThread = Objects.requireNonNull(ioThread1); + } catch (Throwable e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + public NBTTagCompound getChunk(WorldServer world, int x, int y) throws InvocationTargetException, IllegalAccessException, NoSuchFieldException { + CompletableFuture future = new CompletableFuture<>(); + getChunk.invoke(ioThread,world,x,y,5,(Consumer) future::complete, false, true, true); + Object resultFuture = future.join(); + if (resultFuture == null) return null; + NBTTagCompound result = (NBTTagCompound) resultFuture.getClass().getField("chunkData").get(resultFuture); + return ifFailed.test(result) ? null : result; + } +} diff --git a/bukkit-helper-118-2/src/main/java/org/dynmap/bukkit/helper/v118_2/BukkitVersionHelperSpigot118_2.java b/bukkit-helper-118-2/src/main/java/org/dynmap/bukkit/helper/v118_2/BukkitVersionHelperSpigot118_2.java index 83c80870..9d40746e 100644 --- a/bukkit-helper-118-2/src/main/java/org/dynmap/bukkit/helper/v118_2/BukkitVersionHelperSpigot118_2.java +++ b/bukkit-helper-118-2/src/main/java/org/dynmap/bukkit/helper/v118_2/BukkitVersionHelperSpigot118_2.java @@ -64,12 +64,25 @@ import java.util.Set; * Helper for isolation of bukkit version specific issues */ public class BukkitVersionHelperSpigot118_2 extends BukkitVersionHelper { - + private final boolean unsafeAsync; + public BukkitVersionHelperSpigot118_2() { - - } - - /** + boolean unsafeAsync1; + try { + Class.forName("com.destroystokyo.paper.io.PaperFileIOThread"); + unsafeAsync1 = false; + } catch (ClassNotFoundException e) { + unsafeAsync1 = true; + } + this.unsafeAsync = unsafeAsync1; + } + + @Override + public boolean isUnsafeAsync() { + return unsafeAsync; + } + + /** * Get block short name list */ @Override diff --git a/bukkit-helper-118-2/src/main/java/org/dynmap/bukkit/helper/v118_2/MapChunkCache118_2.java b/bukkit-helper-118-2/src/main/java/org/dynmap/bukkit/helper/v118_2/MapChunkCache118_2.java index 2e8b1972..ab135e87 100644 --- a/bukkit-helper-118-2/src/main/java/org/dynmap/bukkit/helper/v118_2/MapChunkCache118_2.java +++ b/bukkit-helper-118-2/src/main/java/org/dynmap/bukkit/helper/v118_2/MapChunkCache118_2.java @@ -1,8 +1,11 @@ package org.dynmap.bukkit.helper.v118_2; +import org.bukkit.Bukkit; import org.bukkit.World; +import org.bukkit.craftbukkit.v1_18_R2.CraftServer; import org.bukkit.craftbukkit.v1_18_R2.CraftWorld; import org.dynmap.DynmapChunk; +import org.dynmap.bukkit.helper.BukkitVersionHelper; import org.dynmap.bukkit.helper.BukkitWorld; import org.dynmap.common.chunk.GenericChunk; import org.dynmap.common.chunk.GenericChunkCache; @@ -14,12 +17,15 @@ import net.minecraft.world.level.chunk.storage.ChunkRegionLoader; import net.minecraft.world.level.chunk.Chunk; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; import java.util.List; +import java.util.concurrent.CompletableFuture; /** * Container for managing chunks - dependent upon using chunk snapshots, since rendering is off server thread */ public class MapChunkCache118_2 extends GenericMapChunkCache { + private final AsyncChunkProvider118_2 provider = BukkitVersionHelper.helper.isUnsafeAsync() ? null : new AsyncChunkProvider118_2(); private World w; /** * Construct empty cache @@ -30,20 +36,24 @@ public class MapChunkCache118_2 extends GenericMapChunkCache { // Load generic chunk from existing and already loaded chunk protected GenericChunk getLoadedChunk(DynmapChunk chunk) { - CraftWorld cw = (CraftWorld) w; - NBTTagCompound nbt = null; - GenericChunk gc = null; - if (cw.isChunkLoaded(chunk.x, chunk.z)) { - Chunk c = cw.getHandle().getChunkIfLoaded(chunk.x, chunk.z); - if ((c != null) && c.o) { // c.loaded - nbt = ChunkRegionLoader.a(cw.getHandle(), c); - } - if (nbt != null) { - gc = parseChunkFromNBT(new NBT.NBTCompound(nbt)); - } + CraftWorld cw = (CraftWorld) w; + NBTTagCompound nbt = null; + GenericChunk gc = null; + if (cw.isChunkLoaded(chunk.x, chunk.z)) { + Chunk c = cw.getHandle().getChunkIfLoaded(chunk.x, chunk.z); //already safe async on vanilla + if ((c != null) && c.o) { // c.loaded + if (provider == null) { //idk why, but paper uses this only sync, so I won't be smarter + nbt = ChunkRegionLoader.a(cw.getHandle(), c); + } else { + nbt = CompletableFuture.supplyAsync(() -> ChunkRegionLoader.a(cw.getHandle(), c), ((CraftServer) Bukkit.getServer()).getServer()).join(); } - return gc; - } + } + if (nbt != null) { + gc = parseChunkFromNBT(new NBT.NBTCompound(nbt)); + } + } + return gc; + } // Load generic chunk from unloaded chunk protected GenericChunk loadChunk(DynmapChunk chunk) { CraftWorld cw = (CraftWorld) w; @@ -51,8 +61,12 @@ public class MapChunkCache118_2 extends GenericMapChunkCache { ChunkCoordIntPair cc = new ChunkCoordIntPair(chunk.x, chunk.z); GenericChunk gc = null; try { - nbt = cw.getHandle().k().a.f(cc); // playerChunkMap - } catch (IOException iox) { + if (provider == null){ + nbt = cw.getHandle().k().a.f(cc); // playerChunkMap + } else { + nbt = provider.getChunk(cw.getHandle(),chunk.x, chunk.z); + } + } catch (IOException | InvocationTargetException | IllegalAccessException | NoSuchFieldException ignored) { } if (nbt != null) { gc = parseChunkFromNBT(new NBT.NBTCompound(nbt)); diff --git a/bukkit-helper-118/src/main/java/org/dynmap/bukkit/helper/v118/BukkitVersionHelperSpigot118.java b/bukkit-helper-118/src/main/java/org/dynmap/bukkit/helper/v118/BukkitVersionHelperSpigot118.java index d5381c60..dd9aa5c3 100644 --- a/bukkit-helper-118/src/main/java/org/dynmap/bukkit/helper/v118/BukkitVersionHelperSpigot118.java +++ b/bukkit-helper-118/src/main/java/org/dynmap/bukkit/helper/v118/BukkitVersionHelperSpigot118.java @@ -63,13 +63,20 @@ import java.util.Set; /** * Helper for isolation of bukkit version specific issues */ -public class BukkitVersionHelperSpigot118 extends BukkitVersionHelper { - - public BukkitVersionHelperSpigot118() { - +public class BukkitVersionHelperSpigot118 extends BukkitVersionHelper { + + private final boolean unsafeAsync; + + public BukkitVersionHelperSpigot118() { + this.unsafeAsync = true; } - - /** + + @Override + public boolean isUnsafeAsync() { + return unsafeAsync; + } + + /** * Get block short name list */ @Override diff --git a/bukkit-helper/src/main/java/org/dynmap/bukkit/helper/BukkitVersionHelper.java b/bukkit-helper/src/main/java/org/dynmap/bukkit/helper/BukkitVersionHelper.java index 8724eac8..58cdb90d 100644 --- a/bukkit-helper/src/main/java/org/dynmap/bukkit/helper/BukkitVersionHelper.java +++ b/bukkit-helper/src/main/java/org/dynmap/bukkit/helper/BukkitVersionHelper.java @@ -34,6 +34,10 @@ public abstract class BukkitVersionHelper { protected BukkitVersionHelper() { } + /** + * Get if it's unsafe to load chunks async + */ + public abstract boolean isUnsafeAsync(); /** * Get list of defined biomebase objects */ diff --git a/bukkit-helper/src/main/java/org/dynmap/bukkit/helper/BukkitVersionHelperCB.java b/bukkit-helper/src/main/java/org/dynmap/bukkit/helper/BukkitVersionHelperCB.java index 05da8fbe..d9ddd264 100644 --- a/bukkit-helper/src/main/java/org/dynmap/bukkit/helper/BukkitVersionHelperCB.java +++ b/bukkit-helper/src/main/java/org/dynmap/bukkit/helper/BukkitVersionHelperCB.java @@ -54,6 +54,12 @@ public class BukkitVersionHelperCB extends BukkitVersionHelperGeneric { isBadUnload = HDBlockModels.checkVersionRange(mcver, "1.9-"); Log.verboseinfo("MCVER=" + mcver + ", isBadUnload=" + isBadUnload); } + + @Override + public boolean isUnsafeAsync() { + return true; + } + @Override protected String getNMSPackage() { Server srv = Bukkit.getServer(); diff --git a/bukkit-helper/src/main/java/org/dynmap/bukkit/helper/BukkitVersionHelperGlowstone.java b/bukkit-helper/src/main/java/org/dynmap/bukkit/helper/BukkitVersionHelperGlowstone.java index 2629abc0..92629236 100644 --- a/bukkit-helper/src/main/java/org/dynmap/bukkit/helper/BukkitVersionHelperGlowstone.java +++ b/bukkit-helper/src/main/java/org/dynmap/bukkit/helper/BukkitVersionHelperGlowstone.java @@ -32,7 +32,12 @@ public class BukkitVersionHelperGlowstone extends BukkitVersionHelper { throw new IllegalArgumentException("Error initializing dynmap - Glowstone version incompatible!"); } } - + + @Override + public boolean isUnsafeAsync() { + return true; + } + @Override public Object[] getBiomeBaseList() { return new Object[0]; diff --git a/spigot/src/main/java/org/dynmap/bukkit/DynmapPlugin.java b/spigot/src/main/java/org/dynmap/bukkit/DynmapPlugin.java index fec426fd..75b203ca 100644 --- a/spigot/src/main/java/org/dynmap/bukkit/DynmapPlugin.java +++ b/spigot/src/main/java/org/dynmap/bukkit/DynmapPlugin.java @@ -481,6 +481,7 @@ public class DynmapPlugin extends JavaPlugin implements DynmapAPI { boolean rslt = permissions.hasOfflinePermission(player, perm); return rslt; } + private final Object[] lock = {}; /** * Render processor helper - used by code running on render threads to request chunk snapshot cache from server/sync thread */ @@ -514,46 +515,64 @@ public class DynmapPlugin extends JavaPlugin implements DynmapAPI { final MapChunkCache cc = c; while(!cc.isDoneLoading()) { - Future f = core.getServer().callSyncMethod(new Callable() { - public Boolean call() throws Exception { - boolean exhausted = true; - - if (prev_tick != cur_tick) { - prev_tick = cur_tick; - cur_tick_starttime = System.nanoTime(); - } - if(chunks_in_cur_tick > 0) { - boolean done = false; - while (!done) { - int cnt = chunks_in_cur_tick; - if (cnt > 5) cnt = 5; - chunks_in_cur_tick -= cc.loadChunks(cnt); - exhausted = (chunks_in_cur_tick == 0) || ((System.nanoTime() - cur_tick_starttime) > perTickLimit); - done = exhausted || cc.isDoneLoading(); + if (BukkitVersionHelper.helper.isUnsafeAsync()) { + Future f = core.getServer().callSyncMethod(new Callable() { + public Boolean call() throws Exception { + boolean exhausted = true; + + if (prev_tick != cur_tick) { + prev_tick = cur_tick; + cur_tick_starttime = System.nanoTime(); } + if (chunks_in_cur_tick > 0) { + boolean done = false; + while (!done) { + int cnt = chunks_in_cur_tick; + if (cnt > 5) cnt = 5; + chunks_in_cur_tick -= cc.loadChunks(cnt); + exhausted = (chunks_in_cur_tick == 0) || ((System.nanoTime() - cur_tick_starttime) > perTickLimit); + done = exhausted || cc.isDoneLoading(); + } + } + return exhausted; } - return exhausted; + }); + if (f == null) { + return null; + } + Boolean delay; + try { + delay = f.get(); + } catch (CancellationException cx) { + return null; + } catch (InterruptedException cx) { + return null; + } catch (ExecutionException ex) { + Log.severe("Exception while fetching chunks: ", ex.getCause()); + return null; + } catch (Exception ix) { + Log.severe(ix); + return null; + } + + if ((delay != null) && delay.booleanValue()) { + try { + Thread.sleep(25); + } catch (InterruptedException ix) { + } + } + } else { + try { + synchronized (lock) { + if (prev_tick != cur_tick) { + prev_tick = cur_tick; + cur_tick_starttime = System.nanoTime(); + } + cc.loadChunks(Integer.MAX_VALUE); + } + } catch (Exception e) { + e.printStackTrace(); } - }); - if (f == null) { - return null; - } - Boolean delay; - try { - delay = f.get(); - } catch (CancellationException cx) { - return null; - } catch (InterruptedException cx) { - return null; - } catch (ExecutionException ex) { - Log.severe("Exception while fetching chunks: ", ex.getCause()); - return null; - } catch (Exception ix) { - Log.severe(ix); - return null; - } - if((delay != null) && delay.booleanValue()) { - try { Thread.sleep(25); } catch (InterruptedException ix) {} } } /* If cancelled due to world unload return nothing */