Make Anti-Xray multithreaded (#3520)

Obfuscate multiple chunks at a time over the server thread pool.

Will speed up chunk processing when anti xray is enabled.

Co-authored-by: Aikar <aikar@aikar.co>
This commit is contained in:
stonar96 2020-06-09 10:12:20 +02:00
parent 2dc5846adf
commit 65154f7929
5 changed files with 49 additions and 42 deletions

View file

@ -24,7 +24,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
} }
+ +
+ public boolean antiXray; + public boolean antiXray;
+ public boolean asynchronous;
+ public EngineMode engineMode; + public EngineMode engineMode;
+ public int maxChunkSectionIndex; + public int maxChunkSectionIndex;
+ public int updateRadius; + public int updateRadius;
@ -32,7 +31,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ public List<String> replacementBlocks; + public List<String> replacementBlocks;
+ private void antiXray() { + private void antiXray() {
+ antiXray = getBoolean("anti-xray.enabled", false); + antiXray = getBoolean("anti-xray.enabled", false);
+ asynchronous = true;
+ engineMode = EngineMode.getById(getInt("anti-xray.engine-mode", EngineMode.HIDE.getId())); + engineMode = EngineMode.getById(getInt("anti-xray.engine-mode", EngineMode.HIDE.getId()));
+ engineMode = engineMode == null ? EngineMode.HIDE : engineMode; + engineMode = engineMode == null ? EngineMode.HIDE : engineMode;
+ maxChunkSectionIndex = getInt("anti-xray.max-chunk-section-index", 3); + maxChunkSectionIndex = getInt("anti-xray.max-chunk-section-index", 3);
@ -110,8 +108,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+import java.util.HashSet; +import java.util.HashSet;
+import java.util.List; +import java.util.List;
+import java.util.Set; +import java.util.Set;
+import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+ +
+import net.minecraft.server.*; +import net.minecraft.server.*;
+import org.bukkit.Bukkit; +import org.bukkit.Bukkit;
@ -121,9 +118,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ +
+public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockController { +public final class ChunkPacketBlockControllerAntiXray extends ChunkPacketBlockController {
+ +
+ private static ExecutorService executorServiceInstance = null; + private final Executor executor;
+ private final ExecutorService executorService;
+ private final boolean asynchronous;
+ private final EngineMode engineMode; + private final EngineMode engineMode;
+ private final int maxChunkSectionIndex; + private final int maxChunkSectionIndex;
+ private final int updateRadius; + private final int updateRadius;
@ -140,17 +135,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ private final ChunkSection[] emptyNearbyChunkSections = {Chunk.EMPTY_CHUNK_SECTION, Chunk.EMPTY_CHUNK_SECTION, Chunk.EMPTY_CHUNK_SECTION, Chunk.EMPTY_CHUNK_SECTION}; + private final ChunkSection[] emptyNearbyChunkSections = {Chunk.EMPTY_CHUNK_SECTION, Chunk.EMPTY_CHUNK_SECTION, Chunk.EMPTY_CHUNK_SECTION, Chunk.EMPTY_CHUNK_SECTION};
+ private final int maxBlockYUpdatePosition; + private final int maxBlockYUpdatePosition;
+ +
+ public ChunkPacketBlockControllerAntiXray(PaperWorldConfig paperWorldConfig) { + public ChunkPacketBlockControllerAntiXray(PaperWorldConfig paperWorldConfig, Executor executor) {
+ asynchronous = paperWorldConfig.asynchronous;
+ engineMode = paperWorldConfig.engineMode; + engineMode = paperWorldConfig.engineMode;
+ maxChunkSectionIndex = paperWorldConfig.maxChunkSectionIndex; + maxChunkSectionIndex = paperWorldConfig.maxChunkSectionIndex;
+ updateRadius = paperWorldConfig.updateRadius; + updateRadius = paperWorldConfig.updateRadius;
+ +
+ if (asynchronous) { + this.executor = executor;
+ executorService = getExecutorServiceInstance();
+ } else {
+ executorService = null;
+ }
+ +
+ List<String> toObfuscate; + List<String> toObfuscate;
+ +
@ -216,12 +206,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ this.maxBlockYUpdatePosition = (maxChunkSectionIndex + 1) * 16 + updateRadius - 1; + this.maxBlockYUpdatePosition = (maxChunkSectionIndex + 1) * 16 + updateRadius - 1;
+ } + }
+ +
+ private static ExecutorService getExecutorServiceInstance() { + private int getPredefinedBlockDataLength() {
+ if (executorServiceInstance == null) { + return engineMode == EngineMode.HIDE ? 1 : predefinedBlockData.length;
+ executorServiceInstance = Executors.newSingleThreadExecutor();
+ }
+
+ return executorServiceInstance;
+ } + }
+ +
+ @Override + @Override
@ -274,26 +260,30 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ (Chunk) world.getChunkIfLoadedImmediately(x, z - 1), + (Chunk) world.getChunkIfLoadedImmediately(x, z - 1),
+ (Chunk) world.getChunkIfLoadedImmediately(x, z + 1)); + (Chunk) world.getChunkIfLoadedImmediately(x, z + 1));
+ +
+ if (asynchronous) { + executor.execute((ChunkPacketInfoAntiXray) chunkPacketInfo);
+ executorService.submit((ChunkPacketInfoAntiXray) chunkPacketInfo);
+ } else {
+ obfuscate((ChunkPacketInfoAntiXray) chunkPacketInfo);
+ }
+ } + }
+ +
+ // Actually these fields should be variables inside the obfuscate method but in sync mode or with SingleThreadExecutor in async mode it's okay + // Actually these fields should be variables inside the obfuscate method but in sync mode or with SingleThreadExecutor in async mode it's okay (even without ThreadLocal)
+ private int[] predefinedBlockDataBits; + // If an ExecutorService with multiple threads is used, ThreadLocal must be used here
+ private final boolean[] solid = new boolean[Block.REGISTRY_ID.size()]; + private final ThreadLocal<int[]> predefinedBlockDataBits = ThreadLocal.withInitial(() -> new int[getPredefinedBlockDataLength()]);
+ private final boolean[] obfuscate = new boolean[Block.REGISTRY_ID.size()]; + private static final ThreadLocal<boolean[]> solid = ThreadLocal.withInitial(() -> new boolean[Block.REGISTRY_ID.size()]);
+ private static final ThreadLocal<boolean[]> obfuscate = ThreadLocal.withInitial(() -> new boolean[Block.REGISTRY_ID.size()]);
+ // These boolean arrays represent chunk layers, true means don't obfuscate, false means obfuscate + // These boolean arrays represent chunk layers, true means don't obfuscate, false means obfuscate
+ private boolean[][] current = new boolean[16][16]; + private static final ThreadLocal<boolean[][]> current = ThreadLocal.withInitial(() -> new boolean[16][16]);
+ private boolean[][] next = new boolean[16][16]; + private static final ThreadLocal<boolean[][]> next = ThreadLocal.withInitial(() -> new boolean[16][16]);
+ private boolean[][] nextNext = new boolean[16][16]; + private static final ThreadLocal<boolean[][]> nextNext = ThreadLocal.withInitial(() -> new boolean[16][16]);
+ private final DataBitsReader dataBitsReader = new DataBitsReader();
+ private final DataBitsWriter dataBitsWriter = new DataBitsWriter();
+ private final ChunkSection[] nearbyChunkSections = new ChunkSection[4];
+ +
+ public void obfuscate(ChunkPacketInfoAntiXray chunkPacketInfoAntiXray) { + public void obfuscate(ChunkPacketInfoAntiXray chunkPacketInfoAntiXray) {
+ int[] predefinedBlockDataBits = this.predefinedBlockDataBits.get();
+ boolean[] solid = this.solid.get();
+ boolean[] obfuscate = this.obfuscate.get();
+ boolean[][] current = this.current.get();
+ boolean[][] next = this.next.get();
+ boolean[][] nextNext = this.nextNext.get();
+ // dataBitsReader, dataBitsWriter and nearbyChunkSections could also be reused (with ThreadLocal if necessary) but it's not worth it
+ DataBitsReader dataBitsReader = new DataBitsReader();
+ DataBitsWriter dataBitsWriter = new DataBitsWriter();
+ ChunkSection[] nearbyChunkSections = new ChunkSection[4];
+ boolean[] solidTemp = null; + boolean[] solidTemp = null;
+ boolean[] obfuscateTemp = null; + boolean[] obfuscateTemp = null;
+ dataBitsReader.setDataBits(chunkPacketInfoAntiXray.getData()); + dataBitsReader.setDataBits(chunkPacketInfoAntiXray.getData());
@ -307,7 +297,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ if (chunkPacketInfoAntiXray.getDataPalette(chunkSectionIndex) == ChunkSection.GLOBAL_PALETTE) { + if (chunkPacketInfoAntiXray.getDataPalette(chunkSectionIndex) == ChunkSection.GLOBAL_PALETTE) {
+ predefinedBlockDataBitsTemp = engineMode == EngineMode.HIDE ? chunkPacketInfoAntiXray.getChunk().world.getWorld().getEnvironment() == Environment.NETHER ? predefinedBlockDataBitsNetherrackGlobal : chunkPacketInfoAntiXray.getChunk().world.getWorld().getEnvironment() == Environment.THE_END ? predefinedBlockDataBitsEndStoneGlobal : predefinedBlockDataBitsStoneGlobal : predefinedBlockDataBitsGlobal; + predefinedBlockDataBitsTemp = engineMode == EngineMode.HIDE ? chunkPacketInfoAntiXray.getChunk().world.getWorld().getEnvironment() == Environment.NETHER ? predefinedBlockDataBitsNetherrackGlobal : chunkPacketInfoAntiXray.getChunk().world.getWorld().getEnvironment() == Environment.THE_END ? predefinedBlockDataBitsEndStoneGlobal : predefinedBlockDataBitsStoneGlobal : predefinedBlockDataBitsGlobal;
+ } else { + } else {
+ predefinedBlockDataBitsTemp = predefinedBlockDataBits == null ? predefinedBlockDataBits = engineMode == EngineMode.HIDE ? new int[1] : new int[predefinedBlockData.length] : predefinedBlockDataBits; + predefinedBlockDataBitsTemp = predefinedBlockDataBits;
+ +
+ for (int i = 0; i < predefinedBlockDataBitsTemp.length; i++) { + for (int i = 0; i < predefinedBlockDataBitsTemp.length; i++) {
+ predefinedBlockDataBitsTemp[i] = chunkPacketInfoAntiXray.getDataPalette(chunkSectionIndex).getOrCreateIdFor(chunkPacketInfoAntiXray.getPredefinedObjects(chunkSectionIndex)[i]); + predefinedBlockDataBitsTemp[i] = chunkPacketInfoAntiXray.getDataPalette(chunkSectionIndex).getOrCreateIdFor(chunkPacketInfoAntiXray.getPredefinedObjects(chunkSectionIndex)[i]);
@ -1413,10 +1403,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
public final co.aikar.timings.WorldTimingsHandler timings; // Paper public final co.aikar.timings.WorldTimingsHandler timings; // Paper
public static BlockPosition lastPhysicsProblem; // Spigot public static BlockPosition lastPhysicsProblem; // Spigot
@@ -0,0 +0,0 @@ public abstract class World implements GeneratorAccess, AutoCloseable { @@ -0,0 +0,0 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
protected World(WorldData worlddata, DimensionManager dimensionmanager, BiFunction<World, WorldProvider, IChunkProvider> bifunction, GameProfilerFiller gameprofilerfiller, boolean flag, org.bukkit.generator.ChunkGenerator gen, org.bukkit.World.Environment env) { return ((ChunkProviderServer) this.chunkProvider).getChunkAt(x, z, false);
}
- protected World(WorldData worlddata, DimensionManager dimensionmanager, BiFunction<World, WorldProvider, IChunkProvider> bifunction, GameProfilerFiller gameprofilerfiller, boolean flag, org.bukkit.generator.ChunkGenerator gen, org.bukkit.World.Environment env) {
+ protected World(WorldData worlddata, DimensionManager dimensionmanager, java.util.concurrent.Executor executor, BiFunction<World, WorldProvider, IChunkProvider> bifunction, GameProfilerFiller gameprofilerfiller, boolean flag, org.bukkit.generator.ChunkGenerator gen, org.bukkit.World.Environment env) { // Paper - executor
this.spigotConfig = new org.spigotmc.SpigotWorldConfig( worlddata.getName() ); // Spigot this.spigotConfig = new org.spigotmc.SpigotWorldConfig( worlddata.getName() ); // Spigot
this.paperConfig = new com.destroystokyo.paper.PaperWorldConfig(worlddata.getName(), this.spigotConfig); // Paper this.paperConfig = new com.destroystokyo.paper.PaperWorldConfig(worlddata.getName(), this.spigotConfig); // Paper
+ this.chunkPacketBlockController = this.paperConfig.antiXray ? new ChunkPacketBlockControllerAntiXray(this.paperConfig) : ChunkPacketBlockController.NO_OPERATION_INSTANCE; // Paper - Anti-Xray + this.chunkPacketBlockController = this.paperConfig.antiXray ? new ChunkPacketBlockControllerAntiXray(this.paperConfig, executor) : ChunkPacketBlockController.NO_OPERATION_INSTANCE; // Paper - Anti-Xray
this.generator = gen; this.generator = gen;
if (dimensionmanager.world == null) dimensionmanager.world = (WorldServer) this; // Paper if (dimensionmanager.world == null) dimensionmanager.world = (WorldServer) this; // Paper
this.world = new CraftWorld((WorldServer) this, gen, env); this.world = new CraftWorld((WorldServer) this, gen, env);
@ -1428,6 +1422,19 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
if (iblockdata1 == null) { if (iblockdata1 == null) {
// CraftBukkit start - remove blockstate if failed (or the same) // CraftBukkit start - remove blockstate if failed (or the same)
diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/WorldServer.java
+++ b/src/main/java/net/minecraft/server/WorldServer.java
@@ -0,0 +0,0 @@ public class WorldServer extends World {
// Add env and gen to constructor
public WorldServer(MinecraftServer minecraftserver, Executor executor, WorldNBTStorage worldnbtstorage, WorldData worlddata, DimensionManager dimensionmanager, GameProfilerFiller gameprofilerfiller, WorldLoadListener worldloadlistener, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen) {
- super(worlddata, dimensionmanager, (world, worldprovider) -> {
+ super(worlddata, dimensionmanager, executor, (world, worldprovider) -> { // Paper - pass executor down
// CraftBukkit start
ChunkGenerator<?> chunkGenerator;
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java --- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java

View file

@ -4015,7 +4015,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ +
// Add env and gen to constructor // Add env and gen to constructor
public WorldServer(MinecraftServer minecraftserver, Executor executor, WorldNBTStorage worldnbtstorage, WorldData worlddata, DimensionManager dimensionmanager, GameProfilerFiller gameprofilerfiller, WorldLoadListener worldloadlistener, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen) { public WorldServer(MinecraftServer minecraftserver, Executor executor, WorldNBTStorage worldnbtstorage, WorldData worlddata, DimensionManager dimensionmanager, GameProfilerFiller gameprofilerfiller, WorldLoadListener worldloadlistener, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen) {
super(worlddata, dimensionmanager, (world, worldprovider) -> { super(worlddata, dimensionmanager, executor, (world, worldprovider) -> { // Paper - pass executor down
@@ -0,0 +0,0 @@ public class WorldServer extends World { @@ -0,0 +0,0 @@ public class WorldServer extends World {
this.mobSpawnerTrader = this.worldProvider.getDimensionManager().getType() == DimensionManager.OVERWORLD ? new MobSpawnerTrader(this) : null; // CraftBukkit - getType() this.mobSpawnerTrader = this.worldProvider.getDimensionManager().getType() == DimensionManager.OVERWORLD ? new MobSpawnerTrader(this) : null; // CraftBukkit - getType()

View file

@ -35,8 +35,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ } + }
+ +
public boolean antiXray; public boolean antiXray;
public boolean asynchronous;
public EngineMode engineMode; public EngineMode engineMode;
public int maxChunkSectionIndex;
diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/WorldServer.java --- a/src/main/java/net/minecraft/server/WorldServer.java

View file

@ -1179,7 +1179,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ +
// Add env and gen to constructor // Add env and gen to constructor
public WorldServer(MinecraftServer minecraftserver, Executor executor, WorldNBTStorage worldnbtstorage, WorldData worlddata, DimensionManager dimensionmanager, GameProfilerFiller gameprofilerfiller, WorldLoadListener worldloadlistener, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen) { public WorldServer(MinecraftServer minecraftserver, Executor executor, WorldNBTStorage worldnbtstorage, WorldData worlddata, DimensionManager dimensionmanager, GameProfilerFiller gameprofilerfiller, WorldLoadListener worldloadlistener, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen) {
super(worlddata, dimensionmanager, (world, worldprovider) -> { super(worlddata, dimensionmanager, executor, (world, worldprovider) -> { // Paper - pass executor down
@@ -0,0 +0,0 @@ public class WorldServer extends World { @@ -0,0 +0,0 @@ public class WorldServer extends World {
this.pvpMode = minecraftserver.getPVP(); this.pvpMode = minecraftserver.getPVP();
worlddata.world = this; worlddata.world = this;

View file

@ -40,7 +40,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ +
+ // Paper end + // Paper end
protected World(WorldData worlddata, DimensionManager dimensionmanager, BiFunction<World, WorldProvider, IChunkProvider> bifunction, GameProfilerFiller gameprofilerfiller, boolean flag, org.bukkit.generator.ChunkGenerator gen, org.bukkit.World.Environment env) { protected World(WorldData worlddata, DimensionManager dimensionmanager, java.util.concurrent.Executor executor, BiFunction<World, WorldProvider, IChunkProvider> bifunction, GameProfilerFiller gameprofilerfiller, boolean flag, org.bukkit.generator.ChunkGenerator gen, org.bukkit.World.Environment env) { // Paper - executor
this.spigotConfig = new org.spigotmc.SpigotWorldConfig( worlddata.getName() ); // Spigot this.spigotConfig = new org.spigotmc.SpigotWorldConfig( worlddata.getName() ); // Spigot
@@ -0,0 +0,0 @@ public abstract class World implements GeneratorAccess, AutoCloseable { @@ -0,0 +0,0 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
} }