From 8d8a4d088e897fe18fe7ac62261ade2eea1cc0c8 Mon Sep 17 00:00:00 2001 From: mastermc05 Date: Wed, 6 Jul 2022 16:28:14 +0300 Subject: [PATCH 1/3] fix 1.18.2 --- .../common/chunk/GenericMapChunkCache.java | 12 +++- .../v118_2/AsyncChunkProvider118_2.java | 61 ++++++++++++++++++- .../helper/v118_2/MapChunkCache118_2.java | 36 +++++------ 3 files changed, 84 insertions(+), 25 deletions(-) diff --git a/DynmapCore/src/main/java/org/dynmap/common/chunk/GenericMapChunkCache.java b/DynmapCore/src/main/java/org/dynmap/common/chunk/GenericMapChunkCache.java index 2cfc7ce4..a43c9198 100644 --- a/DynmapCore/src/main/java/org/dynmap/common/chunk/GenericMapChunkCache.java +++ b/DynmapCore/src/main/java/org/dynmap/common/chunk/GenericMapChunkCache.java @@ -766,7 +766,7 @@ public abstract class GenericMapChunkCache extends MapChunkCache { } /** - * Read NBT data from loaded chunks - do not needs to be called from server/world + * Read NBT data from loaded chunks - do not needs to be called from server/world

* Will throw {@link IllegalStateException} if not supporting */ public void getLoadedChunksAsync() { @@ -836,7 +836,9 @@ public abstract class GenericMapChunkCache extends MapChunkCache { } /** - * Prepare the chunks async + * Loads all chunks in the world asynchronously. + *

+ * If it is not supported, it will throw {@link IllegalStateException} */ public void loadChunksAsync() { getLoadedChunksAsync(); @@ -923,6 +925,12 @@ public abstract class GenericMapChunkCache extends MapChunkCache { return cnt; } + /** + * It loads chunks from the cache or from the world, and if the chunk is not visible, it fills it with stone, ocean or + * empty chunk + *

+ * if it's not supported, will throw {@link IllegalStateException} + */ public void readChunksAsync() { class SimplePair { //pair of the chunk and the data which is readed async private final Supplier supplier; 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 index 9c21562d..f043d870 100644 --- 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 @@ -1,31 +1,54 @@ package org.dynmap.bukkit.helper.v118_2; import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.WorldServer; +import net.minecraft.world.level.chunk.Chunk; +import net.minecraft.world.level.chunk.IChunkAccess; +import net.minecraft.world.level.chunk.storage.ChunkRegionLoader; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_18_R2.CraftServer; +import org.bukkit.craftbukkit.v1_18_R2.CraftWorld; +import org.dynmap.MapManager; 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.concurrent.ExecutionException; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import java.util.function.Predicate; +import java.util.function.Supplier; /** * The provider used to work with paper libs * Because paper libs need java 17 we can't interact with them directly */ +@SuppressWarnings({"JavaReflectionMemberAccess"}) //java don't know about paper public class AsyncChunkProvider118_2 { private final Thread ioThread; private final Method getChunk; private final Predicate ifFailed; + private final Method getAsyncSaveData; + private final Method save; + private int currTick = MinecraftServer.currentTick; + private int currChunks = 0; + AsyncChunkProvider118_2 () { try { Predicate ifFailed1 = null; - Method getChunk1 = null; + Method getChunk1 = null, getAsyncSaveData1 = null, save1 = null; Thread ioThread1 = null; try { Class threadClass = Class.forName("com.destroystokyo.paper.io.PaperFileIOThread"); + Class asyncChunkData = Arrays.stream(ChunkRegionLoader.class.getClasses()) + .filter(c -> c.getSimpleName().equals("AsyncSaveData")) + .findFirst() + .orElseThrow(RuntimeException::new); + getAsyncSaveData1 = ChunkRegionLoader.class.getMethod("getAsyncSaveData", WorldServer.class, IChunkAccess.class); + save1 = ChunkRegionLoader.class.getMethod("saveChunk", WorldServer.class, IChunkAccess.class, asyncChunkData); 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); @@ -35,6 +58,8 @@ public class AsyncChunkProvider118_2 { } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException | NoSuchMethodException e) { e.printStackTrace(); } + getAsyncSaveData = Objects.requireNonNull(getAsyncSaveData1); + save = Objects.requireNonNull(save1); ifFailed = Objects.requireNonNull(ifFailed1); getChunk = Objects.requireNonNull(getChunk1); ioThread = Objects.requireNonNull(ioThread1); @@ -57,4 +82,38 @@ public class AsyncChunkProvider118_2 { return null; }); } + + public synchronized Supplier getLoadedChunk(CraftWorld world, int x, int z) { + if (!world.isChunkLoaded(x, z)) return () -> null; + Chunk c = world.getHandle().getChunkIfLoaded(x, z); //already safe async on vanilla + if ((c == null) || c.o) return () -> null; // c.loaded + if (currTick != MinecraftServer.currentTick) { + currTick = MinecraftServer.currentTick; + currChunks = 0; + } + //prepare data synchronously + CompletableFuture future = CompletableFuture.supplyAsync(() -> { + try { + return getAsyncSaveData.invoke(null, world.getHandle(), c); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + }, ((CraftServer) Bukkit.getServer()).getServer()); + //we shouldn't stress main thread + if (++currChunks > MapManager.mapman.getMaxChunkLoadsPerTick()) { + try { + Thread.sleep(25); //hold the lock so other threads also won't stress main thread + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + //save data asynchronously + return () -> { + try { + return (NBTTagCompound) save.invoke(null, world.getHandle(), c, future.get()); + } catch (ReflectiveOperationException | ExecutionException | InterruptedException e) { + throw new RuntimeException(e); + } + }; + } } 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 3971de10..c96b6670 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,10 +1,12 @@ package org.dynmap.bukkit.helper.v118_2; +import net.minecraft.server.MinecraftServer; 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.MapManager; import org.dynmap.bukkit.helper.BukkitVersionHelper; import org.dynmap.bukkit.helper.BukkitWorld; import org.dynmap.common.chunk.GenericChunk; @@ -20,6 +22,7 @@ import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.util.List; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; /** @@ -37,11 +40,20 @@ public class MapChunkCache118_2 extends GenericMapChunkCache { // Load generic chunk from existing and already loaded chunk protected GenericChunk getLoadedChunk(DynmapChunk chunk) { - return getLoadedChunk(chunk, false).get(); + CraftWorld cw = (CraftWorld) w; + if (!cw.isChunkLoaded(chunk.x, chunk.z)) return null; + Chunk c = cw.getHandle().getChunkIfLoaded(chunk.x, chunk.z); //already safe async on vanilla + if ((c == null) || !c.o) return null; // c.loaded + NBTTagCompound nbt = ChunkRegionLoader.a(cw.getHandle(), c); + return nbt != null ? parseChunkFromNBT(new NBT.NBTCompound(nbt)) : null; } @Override protected Supplier getLoadedChunkAsync(DynmapChunk ch) { - return getLoadedChunk(ch, true); + Supplier nbtSupplier = provider.getLoadedChunk((CraftWorld) w, ch.x, ch.z); + return () -> { + NBTTagCompound nbt = nbtSupplier.get(); + return nbt == null ? null : parseChunkFromNBT(new NBT.NBTCompound(nbt)); + }; } @Override @@ -57,26 +69,6 @@ public class MapChunkCache118_2 extends GenericMapChunkCache { } } - private Supplier getLoadedChunk(DynmapChunk chunk, boolean async) { - CraftWorld cw = (CraftWorld) w; - if (!cw.isChunkLoaded(chunk.x, chunk.z)) return () -> null; - Chunk c = cw.getHandle().getChunkIfLoaded(chunk.x, chunk.z); //already safe async on vanilla - if ((c == null) || c.o) return () -> null; // c.loaded - if (async) { //the data of the chunk may change while we write, better to write it sync - CompletableFuture nbt = CompletableFuture.supplyAsync(() -> ChunkRegionLoader.a(cw.getHandle(), c), ((CraftServer) Bukkit.getServer()).getServer()); - return () -> { - NBTTagCompound compound = nbt.join(); - return compound == null ? null : parseChunkFromNBT(new NBT.NBTCompound(compound)); - }; - } else { - NBTTagCompound nbt = ChunkRegionLoader.a(cw.getHandle(), c); - GenericChunk genericChunk; - if (nbt != null) genericChunk = parseChunkFromNBT(new NBT.NBTCompound(nbt)); - else genericChunk = null; - return () -> genericChunk; - } - - } // Load generic chunk from unloaded chunk protected GenericChunk loadChunk(DynmapChunk chunk) { CraftWorld cw = (CraftWorld) w; From 17e60cff40b5bfbdb9a65816ac1b5c1031ea09d3 Mon Sep 17 00:00:00 2001 From: mastermc05 Date: Thu, 7 Jul 2022 15:34:16 +0300 Subject: [PATCH 2/3] fix 1.19 --- .../helper/v119/AsyncChunkProvider119.java | 62 ++++++++++++++++++- .../bukkit/helper/v119/MapChunkCache119.java | 44 +++++-------- 2 files changed, 75 insertions(+), 31 deletions(-) diff --git a/bukkit-helper-119/src/main/java/org/dynmap/bukkit/helper/v119/AsyncChunkProvider119.java b/bukkit-helper-119/src/main/java/org/dynmap/bukkit/helper/v119/AsyncChunkProvider119.java index 7438575c..10ee620a 100644 --- a/bukkit-helper-119/src/main/java/org/dynmap/bukkit/helper/v119/AsyncChunkProvider119.java +++ b/bukkit-helper-119/src/main/java/org/dynmap/bukkit/helper/v119/AsyncChunkProvider119.java @@ -1,31 +1,53 @@ package org.dynmap.bukkit.helper.v119; import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.WorldServer; +import net.minecraft.world.level.chunk.Chunk; +import net.minecraft.world.level.chunk.IChunkAccess; +import net.minecraft.world.level.chunk.storage.ChunkRegionLoader; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_19_R1.CraftServer; +import org.bukkit.craftbukkit.v1_19_R1.CraftWorld; +import org.dynmap.MapManager; 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.concurrent.ExecutionException; import java.util.function.Consumer; import java.util.function.Predicate; +import java.util.function.Supplier; /** * The provider used to work with paper libs * Because paper libs need java 17 we can't interact with them directly */ +@SuppressWarnings({"JavaReflectionMemberAccess"}) //java don't know about paper public class AsyncChunkProvider119 { private final Thread ioThread; private final Method getChunk; private final Predicate ifFailed; - AsyncChunkProvider119 () { + private final Method getAsyncSaveData; + private final Method save; + private int currTick = MinecraftServer.currentTick; + private int currChunks = 0; + + AsyncChunkProvider119() { try { Predicate ifFailed1 = null; - Method getChunk1 = null; + Method getChunk1 = null, getAsyncSaveData1 = null, save1 = null; Thread ioThread1 = null; try { Class threadClass = Class.forName("com.destroystokyo.paper.io.PaperFileIOThread"); + Class asyncChunkData = Arrays.stream(ChunkRegionLoader.class.getClasses()) + .filter(c -> c.getSimpleName().equals("AsyncSaveData")) + .findFirst() + .orElseThrow(RuntimeException::new); + getAsyncSaveData1 = ChunkRegionLoader.class.getMethod("getAsyncSaveData", WorldServer.class, IChunkAccess.class); + save1 = ChunkRegionLoader.class.getMethod("saveChunk", WorldServer.class, IChunkAccess.class, asyncChunkData); 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); @@ -35,6 +57,8 @@ public class AsyncChunkProvider119 { } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException | NoSuchMethodException e) { e.printStackTrace(); } + getAsyncSaveData = Objects.requireNonNull(getAsyncSaveData1); + save = Objects.requireNonNull(save1); ifFailed = Objects.requireNonNull(ifFailed1); getChunk = Objects.requireNonNull(getChunk1); ioThread = Objects.requireNonNull(ioThread1); @@ -57,4 +81,38 @@ public class AsyncChunkProvider119 { return null; }); } + + public synchronized Supplier getLoadedChunk(CraftWorld world, int x, int z) { + if (!world.isChunkLoaded(x, z)) return () -> null; + Chunk c = world.getHandle().getChunkIfLoaded(x, z); //already safe async on vanilla + if ((c == null) || c.o) return () -> null; // c.loaded + if (currTick != MinecraftServer.currentTick) { + currTick = MinecraftServer.currentTick; + currChunks = 0; + } + //prepare data synchronously + CompletableFuture future = CompletableFuture.supplyAsync(() -> { + try { + return getAsyncSaveData.invoke(null, world.getHandle(), c); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + }, ((CraftServer) Bukkit.getServer()).getServer()); + //we shouldn't stress main thread + if (++currChunks > MapManager.mapman.getMaxChunkLoadsPerTick()) { + try { + Thread.sleep(25); //hold the lock so other threads also won't stress main thread + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + //save data asynchronously + return () -> { + try { + return (NBTTagCompound) save.invoke(null, world.getHandle(), c, future.get()); + } catch (ReflectiveOperationException | ExecutionException | InterruptedException e) { + throw new RuntimeException(e); + } + }; + } } diff --git a/bukkit-helper-119/src/main/java/org/dynmap/bukkit/helper/v119/MapChunkCache119.java b/bukkit-helper-119/src/main/java/org/dynmap/bukkit/helper/v119/MapChunkCache119.java index 4743e94f..a94c0898 100644 --- a/bukkit-helper-119/src/main/java/org/dynmap/bukkit/helper/v119/MapChunkCache119.java +++ b/bukkit-helper-119/src/main/java/org/dynmap/bukkit/helper/v119/MapChunkCache119.java @@ -1,8 +1,6 @@ package org.dynmap.bukkit.helper.v119; -import org.bukkit.Bukkit; import org.bukkit.World; -import org.bukkit.craftbukkit.v1_19_R1.CraftServer; import org.bukkit.craftbukkit.v1_19_R1.CraftWorld; import org.dynmap.DynmapChunk; import org.dynmap.bukkit.helper.BukkitVersionHelper; @@ -16,7 +14,6 @@ import net.minecraft.world.level.ChunkCoordIntPair; 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.NoSuchElementException; @@ -38,14 +35,24 @@ public class MapChunkCache119 extends GenericMapChunkCache { } // Load generic chunk from existing and already loaded chunk - protected GenericChunk getLoadedChunk(DynmapChunk chunk) { - return getLoadedChunk(chunk, false).get(); - } @Override - protected Supplier getLoadedChunkAsync(DynmapChunk ch) { - return getLoadedChunk(ch, true); + protected Supplier getLoadedChunkAsync(DynmapChunk chunk) { + Supplier supplier = provider.getLoadedChunk((CraftWorld) w, chunk.x, chunk.z); + return () -> { + NBTTagCompound nbt = supplier.get(); + return nbt != null ? parseChunkFromNBT(new NBT.NBTCompound(nbt)) : null; + }; + } + protected GenericChunk getLoadedChunk(DynmapChunk chunk) { + CraftWorld cw = (CraftWorld) w; + if (!cw.isChunkLoaded(chunk.x, chunk.z)) return null; + Chunk c = cw.getHandle().getChunkIfLoaded(chunk.x, chunk.z); + if (c == null || !c.o) return null; // c.loaded + NBTTagCompound nbt = ChunkRegionLoader.a(cw.getHandle(), c); + return nbt != null ? parseChunkFromNBT(new NBT.NBTCompound(nbt)) : null; } + // Load generic chunk from unloaded chunk @Override protected Supplier loadChunkAsync(DynmapChunk chunk){ try { @@ -59,27 +66,6 @@ public class MapChunkCache119 extends GenericMapChunkCache { } } - private Supplier getLoadedChunk(DynmapChunk chunk, boolean async) { - CraftWorld cw = (CraftWorld) w; - if (!cw.isChunkLoaded(chunk.x, chunk.z)) return () -> null; - Chunk c = cw.getHandle().getChunkIfLoaded(chunk.x, chunk.z); //already safe async on vanilla - if ((c == null) || c.o) return () -> null; // c.loaded - if (async) { //the data of the chunk may change while we write, better to write it sync - CompletableFuture nbt = CompletableFuture.supplyAsync(() -> ChunkRegionLoader.a(cw.getHandle(), c), ((CraftServer) Bukkit.getServer()).getServer()); - return () -> { - NBTTagCompound compound = nbt.join(); - return compound == null ? null : parseChunkFromNBT(new NBT.NBTCompound(compound)); - }; - } else { - NBTTagCompound nbt = ChunkRegionLoader.a(cw.getHandle(), c); - GenericChunk genericChunk; - if (nbt != null) genericChunk = parseChunkFromNBT(new NBT.NBTCompound(nbt)); - else genericChunk = null; - return () -> genericChunk; - } - - } - // Load generic chunk from unloaded chunk protected GenericChunk loadChunk(DynmapChunk chunk) { CraftWorld cw = (CraftWorld) w; NBTTagCompound nbt = null; From b933f6b21cb29a781d89fe40964d44b4e589aff9 Mon Sep 17 00:00:00 2001 From: mastermc05 Date: Thu, 7 Jul 2022 16:21:22 +0300 Subject: [PATCH 3/3] Readd fix to copied code --- .../dynmap/bukkit/helper/v118_2/AsyncChunkProvider118_2.java | 2 +- .../org/dynmap/bukkit/helper/v119/AsyncChunkProvider119.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 index f043d870..745527e6 100644 --- 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 @@ -86,7 +86,7 @@ public class AsyncChunkProvider118_2 { public synchronized Supplier getLoadedChunk(CraftWorld world, int x, int z) { if (!world.isChunkLoaded(x, z)) return () -> null; Chunk c = world.getHandle().getChunkIfLoaded(x, z); //already safe async on vanilla - if ((c == null) || c.o) return () -> null; // c.loaded + if ((c == null) || !c.o) return () -> null; // c.loaded if (currTick != MinecraftServer.currentTick) { currTick = MinecraftServer.currentTick; currChunks = 0; diff --git a/bukkit-helper-119/src/main/java/org/dynmap/bukkit/helper/v119/AsyncChunkProvider119.java b/bukkit-helper-119/src/main/java/org/dynmap/bukkit/helper/v119/AsyncChunkProvider119.java index 10ee620a..ac93ee97 100644 --- a/bukkit-helper-119/src/main/java/org/dynmap/bukkit/helper/v119/AsyncChunkProvider119.java +++ b/bukkit-helper-119/src/main/java/org/dynmap/bukkit/helper/v119/AsyncChunkProvider119.java @@ -85,7 +85,7 @@ public class AsyncChunkProvider119 { public synchronized Supplier getLoadedChunk(CraftWorld world, int x, int z) { if (!world.isChunkLoaded(x, z)) return () -> null; Chunk c = world.getHandle().getChunkIfLoaded(x, z); //already safe async on vanilla - if ((c == null) || c.o) return () -> null; // c.loaded + if ((c == null) || !c.o) return () -> null; // c.loaded if (currTick != MinecraftServer.currentTick) { currTick = MinecraftServer.currentTick; currChunks = 0;