From 68603a72c51f60290346aeb93f0bfc9864abd843 Mon Sep 17 00:00:00 2001 From: Byteflux Date: Wed, 2 Mar 2016 11:59:48 -0600 Subject: [PATCH] Optimize explosions The process of determining an entity's exposure from explosions can be expensive when there are hundreds or more entities in range. This patch adds a per-tick cache that is used for storing and retrieving an entity's exposure during an explosion. --- .../server/MinecraftServer.java.patch | 54 ++++++----- .../minecraft/world/level/Level.java.patch | 42 +++++---- .../world/level/ServerExplosion.java.patch | 94 ++++++++++++++++++- 3 files changed, 147 insertions(+), 43 deletions(-) diff --git a/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch b/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch index d4c482c5e1..b063052bfb 100644 --- a/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch @@ -708,10 +708,15 @@ } -@@ -720,6 +1025,64 @@ - - } - +@@ -715,10 +1020,68 @@ + this.serverThread.join(); + } catch (InterruptedException interruptedexception) { + MinecraftServer.LOGGER.error("Error while shutting down", interruptedexception); ++ } ++ } ++ ++ } ++ + // Spigot Start + private static double calcTps(double avg, double exp, double tps) + { @@ -744,9 +749,9 @@ + for (int i = 0; i < size; i++) { + this.samples[i] = dec(TPS); + this.times[i] = SEC_IN_NANO; -+ } -+ } -+ + } + } + + private static java.math.BigDecimal dec(long t) { + return new java.math.BigDecimal(t); + } @@ -765,14 +770,13 @@ + public double getAverage() { + return total.divide(dec(time), 30, java.math.RoundingMode.HALF_UP).doubleValue(); + } -+ } + } + private static final java.math.BigDecimal TPS_BASE = new java.math.BigDecimal(1E9).multiply(new java.math.BigDecimal(SAMPLE_INTERVAL)); + // Paper end + // Spigot End -+ + protected void runServer() { try { - if (!this.initServer()) { @@ -727,9 +1090,15 @@ } @@ -971,7 +975,15 @@ gameprofilerfiller.push("tick"); -@@ -1265,7 +1690,23 @@ +@@ -1186,6 +1611,7 @@ + + gameprofilerfiller.pop(); + gameprofilerfiller.pop(); ++ worldserver.explosionDensityCache.clear(); // Paper - Optimize explosions + } + + gameprofilerfiller.popPush("connection"); +@@ -1265,7 +1691,23 @@ @Nullable public ServerLevel getLevel(ResourceKey key) { return (ServerLevel) this.levels.get(key); @@ -995,7 +1007,7 @@ public Set> levelKeys() { return this.levels.keySet(); -@@ -1296,7 +1737,7 @@ +@@ -1296,7 +1738,7 @@ @DontObfuscate public String getServerModName() { @@ -1004,7 +1016,7 @@ } public SystemReport fillSystemReport(SystemReport details) { -@@ -1347,7 +1788,7 @@ +@@ -1347,7 +1789,7 @@ @Override public void sendSystemMessage(Component message) { @@ -1013,7 +1025,7 @@ } public KeyPair getKeyPair() { -@@ -1481,10 +1922,20 @@ +@@ -1481,10 +1923,20 @@ @Override public String getMotd() { @@ -1035,7 +1047,7 @@ this.motd = motd; } -@@ -1507,7 +1958,7 @@ +@@ -1507,7 +1959,7 @@ } public ServerConnectionListener getConnection() { @@ -1044,7 +1056,7 @@ } public boolean isReady() { -@@ -1634,11 +2085,11 @@ +@@ -1634,11 +2086,11 @@ public CompletableFuture reloadResources(Collection dataPacks) { CompletableFuture completablefuture = CompletableFuture.supplyAsync(() -> { @@ -1058,7 +1070,7 @@ }, this).thenCompose((immutablelist) -> { MultiPackResourceManager resourcemanager = new MultiPackResourceManager(PackType.SERVER_DATA, immutablelist); List> list = TagLoader.loadTagsForExistingRegistries(resourcemanager, this.registries.compositeAccess()); -@@ -1654,6 +2105,7 @@ +@@ -1654,6 +2106,7 @@ }).thenAcceptAsync((minecraftserver_reloadableresources) -> { this.resources.close(); this.resources = minecraftserver_reloadableresources; @@ -1066,7 +1078,7 @@ this.packRepository.setSelected(dataPacks); WorldDataConfiguration worlddataconfiguration = new WorldDataConfiguration(MinecraftServer.getSelectedPacks(this.packRepository, true), this.worldData.enabledFeatures()); -@@ -1952,7 +2404,7 @@ +@@ -1952,7 +2405,7 @@ final List list = Lists.newArrayList(); final GameRules gamerules = this.getGameRules(); @@ -1075,7 +1087,7 @@ @Override public > void visit(GameRules.Key key, GameRules.Type type) { list.add(String.format(Locale.ROOT, "%s=%s\n", key.getId(), gamerules.getRule(key))); -@@ -2058,7 +2510,7 @@ +@@ -2058,7 +2511,7 @@ try { label51: { @@ -1084,7 +1096,7 @@ try { arraylist = Lists.newArrayList(NativeModuleLister.listModules()); -@@ -2108,6 +2560,22 @@ +@@ -2108,6 +2561,22 @@ } @@ -1107,7 +1119,7 @@ private ProfilerFiller createProfiler() { if (this.willStartRecordingMetrics) { this.metricsRecorder = ActiveMetricsRecorder.createStarted(new ServerMetricsSamplersProvider(Util.timeSource, this.isDedicatedServer()), Util.timeSource, Util.ioPool(), new MetricsPersister("server"), this.onMetricsRecordingStopped, (path) -> { -@@ -2225,18 +2693,24 @@ +@@ -2225,18 +2694,24 @@ } public void logChatMessage(Component message, ChatType.Bound params, @Nullable String prefix) { diff --git a/paper-server/patches/sources/net/minecraft/world/level/Level.java.patch b/paper-server/patches/sources/net/minecraft/world/level/Level.java.patch index 209e29afaf..2969e0bb88 100644 --- a/paper-server/patches/sources/net/minecraft/world/level/Level.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/level/Level.java.patch @@ -57,7 +57,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { public static final Codec> RESOURCE_KEY_CODEC = ResourceKey.codec(Registries.DIMENSION); -@@ -121,23 +144,72 @@ +@@ -121,23 +144,73 @@ private final DamageSources damageSources; private long subTickCount; @@ -92,6 +92,7 @@ + private org.spigotmc.TickLimiter entityLimiter; + private org.spigotmc.TickLimiter tileLimiter; + private int tileTickPosition; ++ public final Map explosionDensityCache = new HashMap<>(); // Paper - Optimize explosions + + public CraftWorld getWorld() { + return this.world; @@ -139,7 +140,7 @@ } }; } else { -@@ -145,11 +217,49 @@ +@@ -145,11 +218,49 @@ } this.thread = Thread.currentThread(); @@ -194,7 +195,7 @@ } @Override -@@ -163,6 +273,13 @@ +@@ -163,6 +274,13 @@ return null; } @@ -208,7 +209,7 @@ public boolean isInWorldBounds(BlockPos pos) { return !this.isOutsideBuildHeight(pos) && Level.isInWorldBoundsHorizontal(pos); } -@@ -179,18 +296,52 @@ +@@ -179,18 +297,52 @@ return y < -20000000 || y >= 20000000; } @@ -264,7 +265,7 @@ ChunkAccess ichunkaccess = this.getChunkSource().getChunk(chunkX, chunkZ, leastStatus, create); if (ichunkaccess == null && create) { -@@ -207,6 +358,18 @@ +@@ -207,6 +359,18 @@ @Override public boolean setBlock(BlockPos pos, BlockState state, int flags, int maxUpdateDepth) { @@ -283,7 +284,7 @@ if (this.isOutsideBuildHeight(pos)) { return false; } else if (!this.isClientSide && this.isDebug()) { -@@ -214,44 +377,123 @@ +@@ -214,45 +378,124 @@ } else { LevelChunk chunk = this.getChunkAt(pos); Block block = state.getBlock(); @@ -367,10 +368,10 @@ + // CraftBukkit end + return true; -+ } -+ } -+ } -+ + } + } + } + + // CraftBukkit start - Split off from above in order to directly send client and physic updates + public void notifyAndUpdatePhysics(BlockPos blockposition, LevelChunk chunk, BlockState oldBlock, BlockState newBlock, BlockState actualBlock, int i, int j) { + BlockState iblockdata = newBlock; @@ -390,7 +391,7 @@ + if (!this.isClientSide && iblockdata.hasAnalogOutputSignal()) { + this.updateNeighbourForOutputSignal(blockposition, newBlock.getBlock()); + } - } ++ } + + if ((i & 16) == 0 && j > 0) { + int k = i & -34; @@ -416,13 +417,14 @@ + this.onBlockStateChange(blockposition, iblockdata1, iblockdata2); + } + // CraftBukkit end - } - } ++ } ++ } + // CraftBukkit end - ++ public void onBlockStateChange(BlockPos pos, BlockState oldBlock, BlockState newBlock) {} -@@ -340,10 +582,18 @@ + @Override +@@ -340,10 +583,18 @@ @Override public BlockState getBlockState(BlockPos pos) { @@ -442,7 +444,7 @@ return chunk.getBlockState(pos); } -@@ -446,14 +696,23 @@ +@@ -446,14 +697,23 @@ this.pendingBlockEntityTickers.clear(); } @@ -470,7 +472,7 @@ } else if (flag && this.shouldTickBlocksAt(tickingblockentity.getPos())) { tickingblockentity.tick(); } -@@ -461,17 +720,18 @@ +@@ -461,17 +721,18 @@ this.tickingBlockEntities = false; gameprofilerfiller.pop(); @@ -494,7 +496,7 @@ } } -@@ -510,13 +770,29 @@ +@@ -510,13 +771,29 @@ @Nullable @Override public BlockEntity getBlockEntity(BlockPos pos) { @@ -525,7 +527,7 @@ this.getChunkAt(blockposition).addAndRegisterBlockEntity(blockEntity); } } -@@ -643,7 +919,7 @@ +@@ -643,7 +920,7 @@ for (int k = 0; k < j; ++k) { EnderDragonPart entitycomplexpart = aentitycomplexpart[k]; @@ -534,7 +536,7 @@ if (t0 != null && predicate.test(t0)) { result.add(t0); -@@ -912,7 +1188,7 @@ +@@ -912,7 +1189,7 @@ public static enum ExplosionInteraction implements StringRepresentable { diff --git a/paper-server/patches/sources/net/minecraft/world/level/ServerExplosion.java.patch b/paper-server/patches/sources/net/minecraft/world/level/ServerExplosion.java.patch index 6d7d4a7cc1..d32cd7be53 100644 --- a/paper-server/patches/sources/net/minecraft/world/level/ServerExplosion.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/level/ServerExplosion.java.patch @@ -61,8 +61,12 @@ Iterator iterator = list.iterator(); while (iterator.hasNext()) { -@@ -195,7 +209,35 @@ - float f2 = !flag && f1 == 0.0F ? 0.0F : ServerExplosion.getSeenPercent(this.center, entity); +@@ -192,10 +206,38 @@ + d3 /= d4; + boolean flag = this.damageCalculator.shouldDamageEntity(this, entity); + float f1 = this.damageCalculator.getKnockbackMultiplier(entity); +- float f2 = !flag && f1 == 0.0F ? 0.0F : ServerExplosion.getSeenPercent(this.center, entity); ++ float f2 = !flag && f1 == 0.0F ? 0.0F : this.getBlockDensity(this.center, entity); // Paper - Optimize explosions if (flag) { - entity.hurtServer(this.level, this.damageSource, this.damageCalculator.getEntityDamageAmount(this, entity, f2)); @@ -211,3 +215,89 @@ Iterator iterator = droppedItemsOut.iterator(); do { +@@ -372,4 +487,85 @@ + + } + } ++ ++ // Paper start - Optimize explosions ++ private float getBlockDensity(Vec3 vec3d, Entity entity) { ++ if (!this.level.paperConfig().environment.optimizeExplosions) { ++ return getSeenPercent(vec3d, entity); ++ } ++ CacheKey key = new CacheKey(this, entity.getBoundingBox()); ++ Float blockDensity = this.level.explosionDensityCache.get(key); ++ if (blockDensity == null) { ++ blockDensity = getSeenPercent(vec3d, entity); ++ this.level.explosionDensityCache.put(key, blockDensity); ++ } ++ ++ return blockDensity; ++ } ++ ++ static class CacheKey { ++ private final Level world; ++ private final double posX, posY, posZ; ++ private final double minX, minY, minZ; ++ private final double maxX, maxY, maxZ; ++ ++ public CacheKey(Explosion explosion, AABB aabb) { ++ this.world = explosion.level(); ++ this.posX = explosion.center().x; ++ this.posY = explosion.center().y; ++ this.posZ = explosion.center().z; ++ this.minX = aabb.minX; ++ this.minY = aabb.minY; ++ this.minZ = aabb.minZ; ++ this.maxX = aabb.maxX; ++ this.maxY = aabb.maxY; ++ this.maxZ = aabb.maxZ; ++ } ++ ++ @Override ++ public boolean equals(Object o) { ++ if (this == o) return true; ++ if (o == null || getClass() != o.getClass()) return false; ++ ++ CacheKey cacheKey = (CacheKey) o; ++ ++ if (Double.compare(cacheKey.posX, posX) != 0) return false; ++ if (Double.compare(cacheKey.posY, posY) != 0) return false; ++ if (Double.compare(cacheKey.posZ, posZ) != 0) return false; ++ if (Double.compare(cacheKey.minX, minX) != 0) return false; ++ if (Double.compare(cacheKey.minY, minY) != 0) return false; ++ if (Double.compare(cacheKey.minZ, minZ) != 0) return false; ++ if (Double.compare(cacheKey.maxX, maxX) != 0) return false; ++ if (Double.compare(cacheKey.maxY, maxY) != 0) return false; ++ if (Double.compare(cacheKey.maxZ, maxZ) != 0) return false; ++ return world.equals(cacheKey.world); ++ } ++ ++ @Override ++ public int hashCode() { ++ int result; ++ long temp; ++ result = world.hashCode(); ++ temp = Double.doubleToLongBits(posX); ++ result = 31 * result + (int) (temp ^ (temp >>> 32)); ++ temp = Double.doubleToLongBits(posY); ++ result = 31 * result + (int) (temp ^ (temp >>> 32)); ++ temp = Double.doubleToLongBits(posZ); ++ result = 31 * result + (int) (temp ^ (temp >>> 32)); ++ temp = Double.doubleToLongBits(minX); ++ result = 31 * result + (int) (temp ^ (temp >>> 32)); ++ temp = Double.doubleToLongBits(minY); ++ result = 31 * result + (int) (temp ^ (temp >>> 32)); ++ temp = Double.doubleToLongBits(minZ); ++ result = 31 * result + (int) (temp ^ (temp >>> 32)); ++ temp = Double.doubleToLongBits(maxX); ++ result = 31 * result + (int) (temp ^ (temp >>> 32)); ++ temp = Double.doubleToLongBits(maxY); ++ result = 31 * result + (int) (temp ^ (temp >>> 32)); ++ temp = Double.doubleToLongBits(maxZ); ++ result = 31 * result + (int) (temp ^ (temp >>> 32)); ++ return result; ++ } ++ } ++ // Paper end + }