From a9567d986b4b517ec7fbc4ebf63ede538f6ce842 Mon Sep 17 00:00:00 2001 From: mlbv <51232730+mlbv@users.noreply.github.com> Date: Thu, 12 Jun 2025 16:48:37 +0800 Subject: [PATCH 1/4] refactor --- .../ExplosionNukeRayParallelized.java | 18 ++--- src/main/java/com/hbm/util/ChunkKey.java | 37 ---------- src/main/java/com/hbm/util/SubChunkKey.java | 67 +++++++++++++++++++ .../java/com/hbm/util/SubChunkSnapshot.java | 10 +-- 4 files changed, 81 insertions(+), 51 deletions(-) delete mode 100644 src/main/java/com/hbm/util/ChunkKey.java create mode 100644 src/main/java/com/hbm/util/SubChunkKey.java diff --git a/src/main/java/com/hbm/explosion/ExplosionNukeRayParallelized.java b/src/main/java/com/hbm/explosion/ExplosionNukeRayParallelized.java index e6f01e18c..66b88de95 100644 --- a/src/main/java/com/hbm/explosion/ExplosionNukeRayParallelized.java +++ b/src/main/java/com/hbm/explosion/ExplosionNukeRayParallelized.java @@ -3,7 +3,7 @@ package com.hbm.explosion; import com.hbm.config.BombConfig; import com.hbm.interfaces.IExplosionRay; import com.hbm.main.MainRegistry; -import com.hbm.util.ChunkKey; +import com.hbm.util.SubChunkKey; import com.hbm.util.ConcurrentBitSet; import com.hbm.util.SubChunkSnapshot; import net.minecraft.block.Block; @@ -41,10 +41,10 @@ public class ExplosionNukeRayParallelized implements IExplosionRay { private final ConcurrentMap destructionMap; private final ConcurrentMap accumulatedDamageMap; - private final ConcurrentMap snapshots; + private final ConcurrentMap snapshots; private final BlockingQueue rayQueue; - private final BlockingQueue cacheQueue; + private final BlockingQueue cacheQueue; private final ExecutorService pool; private final CountDownLatch latch; private final Thread latchWatcherThread; @@ -115,7 +115,7 @@ public class ExplosionNukeRayParallelized implements IExplosionRay { final long deadline = System.nanoTime() + (timeBudgetMs * 1_000_000L); while (System.nanoTime() < deadline) { - ChunkKey ck = cacheQueue.poll(); + SubChunkKey ck = cacheQueue.poll(); if (ck == null) break; snapshots.computeIfAbsent(ck, k -> SubChunkSnapshot.getSnapshot(world, k, BombConfig.chunkloading)); } @@ -188,7 +188,7 @@ public class ExplosionNukeRayParallelized implements IExplosionRay { if (bs.isEmpty()) { destructionMap.remove(cp); for (int sy = 0; sy < (WORLD_HEIGHT >> 4); sy++) { - snapshots.remove(new ChunkKey(cp, sy)); + snapshots.remove(new SubChunkKey(cp, sy)); } it.remove(); } @@ -293,7 +293,7 @@ public class ExplosionNukeRayParallelized implements IExplosionRay { continue; } - ChunkKey snapshotKey = new ChunkKey(cp, subY); + SubChunkKey snapshotKey = new SubChunkKey(cp, subY); SubChunkSnapshot snap = snapshots.get(snapshotKey); Block originalBlock; @@ -440,7 +440,7 @@ public class ExplosionNukeRayParallelized implements IExplosionRay { if (y < 0 || y >= WORLD_HEIGHT) break; if (currentRayPosition >= radius - PROCESSING_EPSILON) break; - ChunkKey ck = new ChunkKey(x >> 4, z >> 4, y >> 4); + SubChunkKey ck = new SubChunkKey(x >> 4, z >> 4, y >> 4); SubChunkSnapshot snap = snapshots.get(ck); if (snap == null) { @@ -473,14 +473,14 @@ public class ExplosionNukeRayParallelized implements IExplosionRay { if (damageDealt > 0) { int bitIndex = ((WORLD_HEIGHT - 1 - y) << 8) | ((x & 0xF) << 4) | (z & 0xF); if (BombConfig.explosionAlgorithm == 2) { - ChunkCoordIntPair chunkPos = ck.pos; + ChunkCoordIntPair chunkPos = ck.getPos(); ChunkDamageAccumulator chunkAccumulator = accumulatedDamageMap.computeIfAbsent(chunkPos, k -> new ChunkDamageAccumulator()); chunkAccumulator.addDamage(bitIndex, damageDealt); } else { if (energy > 0) { ConcurrentBitSet bs = destructionMap.computeIfAbsent( - ck.pos, + ck.getPos(), posKey -> new ConcurrentBitSet(BITSET_SIZE) ); bs.set(bitIndex); diff --git a/src/main/java/com/hbm/util/ChunkKey.java b/src/main/java/com/hbm/util/ChunkKey.java deleted file mode 100644 index 4f3bd0ba3..000000000 --- a/src/main/java/com/hbm/util/ChunkKey.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.hbm.util; - -import net.minecraft.world.ChunkCoordIntPair; - -import java.util.Objects; - -/** - * Unique identifier for sub-chunks. - * @author mlbv - */ -public class ChunkKey { - public final ChunkCoordIntPair pos; - public final int subY; - - public ChunkKey(int cx, int cz, int sy) { - this.pos = new ChunkCoordIntPair(cx, cz); - this.subY = sy; - } - - public ChunkKey(ChunkCoordIntPair pos, int sy) { - this.pos = pos; - this.subY = sy; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof ChunkKey)) return false; - ChunkKey k = (ChunkKey) o; - return subY == k.subY && pos.equals(k.pos); - } - - @Override - public int hashCode() { - return Objects.hash(pos.chunkXPos, pos.chunkZPos, subY); - } -} diff --git a/src/main/java/com/hbm/util/SubChunkKey.java b/src/main/java/com/hbm/util/SubChunkKey.java new file mode 100644 index 000000000..068bdfef8 --- /dev/null +++ b/src/main/java/com/hbm/util/SubChunkKey.java @@ -0,0 +1,67 @@ +package com.hbm.util; + +import net.minecraft.world.ChunkCoordIntPair; + +/** + * Unique identifier for sub-chunks. + * @author mlbv + */ +public class SubChunkKey { + + private int chunkXPos; + private int chunkZPos; + private int subY; + private int hash; + + public SubChunkKey() { + this(0, 0, 0); + } + + public SubChunkKey(int cx, int cz, int sy) { + this.update(cx, cz, sy); + } + + public SubChunkKey(ChunkCoordIntPair pos, int sy) { + this.update(pos.chunkXPos, pos.chunkZPos, sy); + } + + public SubChunkKey update(int cx, int cz, int sy) { + this.chunkXPos = cx; + this.chunkZPos = cz; + this.subY = sy; + int result = subY; + result = 31 * result + cx; + result = 31 * result + cz; + this.hash = result; + return this; + } + + @Override + public final int hashCode() { + return this.hash; + } + + @Override + public final boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof SubChunkKey)) return false; + SubChunkKey k = (SubChunkKey) o; + return this.subY == k.subY && this.chunkXPos == k.chunkXPos && this.chunkZPos == k.chunkZPos; + } + + public int getSubY() { + return subY; + } + + public int getChunkXPos() { + return chunkXPos; + } + + public int getChunkZPos() { + return chunkZPos; + } + + public ChunkCoordIntPair getPos() { + return new ChunkCoordIntPair(this.chunkXPos, this.chunkZPos); + } +} diff --git a/src/main/java/com/hbm/util/SubChunkSnapshot.java b/src/main/java/com/hbm/util/SubChunkSnapshot.java index cd51d125b..6077ca4b4 100644 --- a/src/main/java/com/hbm/util/SubChunkSnapshot.java +++ b/src/main/java/com/hbm/util/SubChunkSnapshot.java @@ -34,19 +34,19 @@ public class SubChunkSnapshot { * @param world * The World instance from which to retrieve the chunk. * @param key - * The ChunkKey identifying the sub-chunk. + * The SubChunkKey identifying the sub-chunk. * @param allowGeneration * Whether to generate chunks. If false, attempting to retrieve a snapshot of a chunk that does not exist will return {@link SubChunkSnapshot#EMPTY}. * @return * A SubChunkSnapshot containing the palette and block data for the sub-chunk, * or {@link SubChunkSnapshot#EMPTY} if the region contains only air. */ - public static SubChunkSnapshot getSnapshot(World world, ChunkKey key, boolean allowGeneration){ - if (!world.getChunkProvider().chunkExists(key.pos.chunkXPos, key.pos.chunkZPos) && !allowGeneration) { + public static SubChunkSnapshot getSnapshot(World world, SubChunkKey key, boolean allowGeneration){ + if (!world.getChunkProvider().chunkExists(key.getChunkXPos(), key.getChunkZPos()) && !allowGeneration) { return SubChunkSnapshot.EMPTY; } - Chunk chunk = world.getChunkProvider().provideChunk(key.pos.chunkXPos, key.pos.chunkZPos); - ExtendedBlockStorage ebs = chunk.getBlockStorageArray()[key.subY]; + Chunk chunk = world.getChunkProvider().provideChunk(key.getChunkXPos(), key.getChunkZPos()); + ExtendedBlockStorage ebs = chunk.getBlockStorageArray()[key.getSubY()]; if (ebs == null || ebs.isEmpty()) return SubChunkSnapshot.EMPTY; short[] data = new short[16 * 16 * 16]; From d65cc18dd86c19aa0b5ff1b3dffe4deb89b92909 Mon Sep 17 00:00:00 2001 From: mlbv <51232730+mlbv@users.noreply.github.com> Date: Thu, 12 Jun 2025 17:24:10 +0800 Subject: [PATCH 2/4] perf: pre-size maps to avoid overhead --- .../ExplosionNukeRayParallelized.java | 96 +++++++++---------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/src/main/java/com/hbm/explosion/ExplosionNukeRayParallelized.java b/src/main/java/com/hbm/explosion/ExplosionNukeRayParallelized.java index 66b88de95..328e4e995 100644 --- a/src/main/java/com/hbm/explosion/ExplosionNukeRayParallelized.java +++ b/src/main/java/com/hbm/explosion/ExplosionNukeRayParallelized.java @@ -3,8 +3,8 @@ package com.hbm.explosion; import com.hbm.config.BombConfig; import com.hbm.interfaces.IExplosionRay; import com.hbm.main.MainRegistry; -import com.hbm.util.SubChunkKey; import com.hbm.util.ConcurrentBitSet; +import com.hbm.util.SubChunkKey; import com.hbm.util.SubChunkSnapshot; import net.minecraft.block.Block; import net.minecraft.init.Blocks; @@ -29,6 +29,7 @@ public class ExplosionNukeRayParallelized implements IExplosionRay { private static final int WORLD_HEIGHT = 256; private static final int BITSET_SIZE = 16 * WORLD_HEIGHT * 16; + private static final int SUBCHUNK_PER_CHUNK = WORLD_HEIGHT >> 4; protected final World world; private final double explosionX, explosionY, explosionZ; @@ -39,7 +40,7 @@ public class ExplosionNukeRayParallelized implements IExplosionRay { private volatile List directions; private final CompletableFuture> directionsFuture; private final ConcurrentMap destructionMap; - private final ConcurrentMap accumulatedDamageMap; + private final ConcurrentMap> damageMap; private final ConcurrentMap snapshots; @@ -69,19 +70,29 @@ public class ExplosionNukeRayParallelized implements IExplosionRay { int rayCount = Math.max(0, (int) (2.5 * Math.PI * strength * strength)); this.latch = new CountDownLatch(rayCount); - this.destructionMap = new ConcurrentHashMap<>(); - this.accumulatedDamageMap = new ConcurrentHashMap<>(); - this.snapshots = new ConcurrentHashMap<>(); + List affectedChunks = this.collectChunkInRadius(); + int initialCapacity = affectedChunks.size(); + + this.destructionMap = new ConcurrentHashMap<>(initialCapacity); + this.damageMap = new ConcurrentHashMap<>(initialCapacity); + if (BombConfig.explosionAlgorithm == 2) { + final int innerMapCapacity = 256; + for (ChunkCoordIntPair coord : affectedChunks) { + this.damageMap.put(coord, new ConcurrentHashMap<>(innerMapCapacity)); + } + } + this.snapshots = new ConcurrentHashMap<>(initialCapacity * SUBCHUNK_PER_CHUNK); this.orderedChunks = new ArrayList<>(); - this.rayQueue = new LinkedBlockingQueue<>(); + List initialRayTasks = new ArrayList<>(rayCount); + for (int i = 0; i < rayCount; i++) initialRayTasks.add(new RayTask(i)); + this.rayQueue = new LinkedBlockingQueue<>(initialRayTasks); this.cacheQueue = new LinkedBlockingQueue<>(); int workers = Math.max(1, Runtime.getRuntime().availableProcessors() - 1); this.pool = Executors.newWorkStealingPool(workers); this.directionsFuture = CompletableFuture.supplyAsync(() -> generateSphereRays(rayCount)); - for (int i = 0; i < rayCount; i++) rayQueue.add(new RayTask(i)); for (int i = 0; i < workers; i++) pool.submit(new Worker()); this.latchWatcherThread = new Thread(() -> { @@ -187,7 +198,7 @@ public class ExplosionNukeRayParallelized implements IExplosionRay { } if (bs.isEmpty()) { destructionMap.remove(cp); - for (int sy = 0; sy < (WORLD_HEIGHT >> 4); sy++) { + for (int sy = 0; sy < (SUBCHUNK_PER_CHUNK); sy++) { snapshots.remove(new SubChunkKey(cp, sy)); } it.remove(); @@ -237,11 +248,27 @@ public class ExplosionNukeRayParallelized implements IExplosionRay { } } if (this.destructionMap != null) this.destructionMap.clear(); - if (this.accumulatedDamageMap != null) this.accumulatedDamageMap.clear(); + if (this.damageMap != null) this.damageMap.clear(); if (this.snapshots != null) this.snapshots.clear(); if (this.orderedChunks != null) this.orderedChunks.clear(); } + private List collectChunkInRadius() { + int cr = (radius + 15) >> 4; + int minCX = (originX >> 4) - cr; + int maxCX = (originX >> 4) + cr; + int minCZ = (originZ >> 4) - cr; + int maxCZ = (originZ >> 4) + cr; + + List list = new ArrayList<>((maxCX - minCX + 1) * (maxCZ - minCZ + 1)); + for (int cx = minCX; cx <= maxCX; ++cx) { + for (int cz = minCZ; cz <= maxCZ; ++cz) { + list.add(new ChunkCoordIntPair(cx, cz)); + } + } + return list; + } + private List generateSphereRays(int count) { List list = new ArrayList<>(count); if (count == 0) return list; @@ -260,20 +287,20 @@ public class ExplosionNukeRayParallelized implements IExplosionRay { } private void runConsolidation() { - Iterator> chunkEntryIterator = accumulatedDamageMap.entrySet().iterator(); + Iterator>> chunkEntryIterator = damageMap.entrySet().iterator(); while (chunkEntryIterator.hasNext()) { - Map.Entry entry = chunkEntryIterator.next(); + Map.Entry> entry = chunkEntryIterator.next(); ChunkCoordIntPair cp = entry.getKey(); - ChunkDamageAccumulator accumulator = entry.getValue(); + ConcurrentMap innerDamageMap = entry.getValue(); - if (accumulator.isEmpty()) { + if (innerDamageMap.isEmpty()) { chunkEntryIterator.remove(); continue; } ConcurrentBitSet chunkDestructionBitSet = destructionMap.computeIfAbsent(cp, k -> new ConcurrentBitSet(BITSET_SIZE)); - Iterator> damageEntryIterator = accumulator.entrySet().iterator(); + Iterator> damageEntryIterator = innerDamageMap.entrySet().iterator(); while (damageEntryIterator.hasNext()) { Map.Entry damageEntry = damageEntryIterator.next(); int bitIndex = damageEntry.getKey(); @@ -317,43 +344,14 @@ public class ExplosionNukeRayParallelized implements IExplosionRay { } } - if (accumulator.isEmpty()) { + if (innerDamageMap.isEmpty()) { chunkEntryIterator.remove(); } } - accumulatedDamageMap.clear(); + damageMap.clear(); consolidationFinished = true; } - private static class ChunkDamageAccumulator { - // key = bitIndex, value = total accumulated damage - private final ConcurrentHashMap damageMap = new ConcurrentHashMap<>(); - - public void addDamage(int bitIndex, float damageAmount) { - if (damageAmount <= 0) return; - DoubleAdder adder = damageMap.computeIfAbsent(bitIndex, k -> new DoubleAdder()); - adder.add(damageAmount); - } - - /*public float getDamage(int bitIndex) { - DoubleAdder adder = damageMap.get(bitIndex); - return adder == null ? 0f : (float) adder.sum(); - }*/ - - /*public void clearDamage(int bitIndex) { - damageMap.remove(bitIndex); - }*/ - - public Set> entrySet() { - return damageMap.entrySet(); - } - - - public boolean isEmpty() { - return damageMap.isEmpty(); - } - } - private class Worker implements Runnable { @Override public void run() { @@ -372,6 +370,7 @@ public class ExplosionNukeRayParallelized implements IExplosionRay { } } } + private class RayTask { final int dirIndex; double px, py, pz; @@ -474,9 +473,10 @@ public class ExplosionNukeRayParallelized implements IExplosionRay { int bitIndex = ((WORLD_HEIGHT - 1 - y) << 8) | ((x & 0xF) << 4) | (z & 0xF); if (BombConfig.explosionAlgorithm == 2) { ChunkCoordIntPair chunkPos = ck.getPos(); - ChunkDamageAccumulator chunkAccumulator = - accumulatedDamageMap.computeIfAbsent(chunkPos, k -> new ChunkDamageAccumulator()); - chunkAccumulator.addDamage(bitIndex, damageDealt); + ConcurrentMap chunkDamageMap = damageMap.get(chunkPos); + if (chunkDamageMap != null) { + chunkDamageMap.computeIfAbsent(bitIndex, k -> new DoubleAdder()).add(damageDealt); + } } else { if (energy > 0) { ConcurrentBitSet bs = destructionMap.computeIfAbsent( From 7dab92f3b27f706acf795489205c15b29e1cd8f2 Mon Sep 17 00:00:00 2001 From: mlbv <51232730+mlbv@users.noreply.github.com> Date: Fri, 13 Jun 2025 12:39:27 +0800 Subject: [PATCH 3/4] Perf: better caching --- .../ExplosionNukeRayParallelized.java | 306 ++++++++---------- src/main/java/com/hbm/util/SubChunkKey.java | 4 - 2 files changed, 142 insertions(+), 168 deletions(-) diff --git a/src/main/java/com/hbm/explosion/ExplosionNukeRayParallelized.java b/src/main/java/com/hbm/explosion/ExplosionNukeRayParallelized.java index 328e4e995..08926ff5f 100644 --- a/src/main/java/com/hbm/explosion/ExplosionNukeRayParallelized.java +++ b/src/main/java/com/hbm/explosion/ExplosionNukeRayParallelized.java @@ -16,12 +16,15 @@ import net.minecraft.world.chunk.Chunk; import net.minecraft.world.chunk.storage.ExtendedBlockStorage; import org.apache.logging.log4j.Level; -import java.util.*; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; import java.util.concurrent.*; import java.util.concurrent.atomic.DoubleAdder; /** - * Threaded DDA raytracer for the nuke explosion. + * Threaded DDA raytracer for mk5 explosion. * * @author mlbv */ @@ -30,6 +33,8 @@ public class ExplosionNukeRayParallelized implements IExplosionRay { private static final int WORLD_HEIGHT = 256; private static final int BITSET_SIZE = 16 * WORLD_HEIGHT * 16; private static final int SUBCHUNK_PER_CHUNK = WORLD_HEIGHT >> 4; + private static final float NUKE_RESISTANCE_CUTOFF = 2_000_000F; + private static final float INITIAL_ENERGY_FACTOR = 0.3F; protected final World world; private final double explosionX, explosionY, explosionZ; @@ -37,19 +42,19 @@ public class ExplosionNukeRayParallelized implements IExplosionRay { private final int strength; private final int radius; - private volatile List directions; private final CompletableFuture> directionsFuture; private final ConcurrentMap destructionMap; private final ConcurrentMap> damageMap; - private final ConcurrentMap snapshots; - + private final ConcurrentMap> waitingRoom; private final BlockingQueue rayQueue; - private final BlockingQueue cacheQueue; private final ExecutorService pool; private final CountDownLatch latch; private final Thread latchWatcherThread; private final List orderedChunks; + private final BlockingQueue highPriorityReactiveQueue; // cache queue for rays + private final Iterator lowPriorityProactiveIterator; + private volatile List directions; private volatile boolean collectFinished = false; private volatile boolean consolidationFinished = false; private volatile boolean destroyFinished = false; @@ -68,26 +73,24 @@ public class ExplosionNukeRayParallelized implements IExplosionRay { this.radius = radius; int rayCount = Math.max(0, (int) (2.5 * Math.PI * strength * strength)); - this.latch = new CountDownLatch(rayCount); - List affectedChunks = this.collectChunkInRadius(); - int initialCapacity = affectedChunks.size(); + List sortedSubChunks = getAllSubChunks(); + this.lowPriorityProactiveIterator = sortedSubChunks.iterator(); + this.highPriorityReactiveQueue = new LinkedBlockingQueue<>(); - this.destructionMap = new ConcurrentHashMap<>(initialCapacity); - this.damageMap = new ConcurrentHashMap<>(initialCapacity); - if (BombConfig.explosionAlgorithm == 2) { - final int innerMapCapacity = 256; - for (ChunkCoordIntPair coord : affectedChunks) { - this.damageMap.put(coord, new ConcurrentHashMap<>(innerMapCapacity)); - } - } - this.snapshots = new ConcurrentHashMap<>(initialCapacity * SUBCHUNK_PER_CHUNK); + int initialChunkCapacity = (int) sortedSubChunks.stream().map(SubChunkKey::getPos).distinct().count(); + + this.destructionMap = new ConcurrentHashMap<>(initialChunkCapacity); + this.damageMap = new ConcurrentHashMap<>(initialChunkCapacity); + + int subChunkCount = sortedSubChunks.size(); + this.snapshots = new ConcurrentHashMap<>(subChunkCount); + this.waitingRoom = new ConcurrentHashMap<>(subChunkCount); this.orderedChunks = new ArrayList<>(); List initialRayTasks = new ArrayList<>(rayCount); for (int i = 0; i < rayCount; i++) initialRayTasks.add(new RayTask(i)); this.rayQueue = new LinkedBlockingQueue<>(initialRayTasks); - this.cacheQueue = new LinkedBlockingQueue<>(); int workers = Math.max(1, Runtime.getRuntime().availableProcessors() - 1); this.pool = Executors.newWorkStealingPool(workers); @@ -102,11 +105,8 @@ public class ExplosionNukeRayParallelized implements IExplosionRay { Thread.currentThread().interrupt(); } finally { collectFinished = true; - if (BombConfig.explosionAlgorithm == 2) { - pool.submit(this::runConsolidation); - } else { - consolidationFinished = true; - } + if (BombConfig.explosionAlgorithm == 2) pool.submit(this::runConsolidation); + else consolidationFinished = true; } }, "ExplosionNuke-LatchWatcher-" + System.nanoTime()); this.latchWatcherThread.setDaemon(true); @@ -116,25 +116,70 @@ public class ExplosionNukeRayParallelized implements IExplosionRay { private static float getNukeResistance(Block b) { if (b.getMaterial().isLiquid()) return 0.1F; if (b == Blocks.sandstone) return Blocks.stone.getExplosionResistance(null); - if (b == Blocks.obsidian) return Blocks.stone.getExplosionResistance(null) * 3; + if (b == Blocks.obsidian) return Blocks.stone.getExplosionResistance(null) * 3.0F; return b.getExplosionResistance(null); } + private List getAllSubChunks() { + List keys = new ArrayList<>(); + int cr = (radius + 15) >> 4; + int minCX = (originX >> 4) - cr; + int maxCX = (originX >> 4) + cr; + int minCZ = (originZ >> 4) - cr; + int maxCZ = (originZ >> 4) + cr; + int minSubY = Math.max(0, (originY - radius) >> 4); + int maxSubY = Math.min(SUBCHUNK_PER_CHUNK - 1, (originY + radius) >> 4); + int originSubY = originY >> 4; + + for (int cx = minCX; cx <= maxCX; cx++) { + for (int cz = minCZ; cz <= maxCZ; cz++) { + for (int subY = minSubY; subY <= maxSubY; subY++) { + int chunkCenterX = (cx << 4) + 8; + int chunkCenterY = (subY << 4) + 8; + int chunkCenterZ = (cz << 4) + 8; + double dx = chunkCenterX - explosionX; + double dy = chunkCenterY - explosionY; + double dz = chunkCenterZ - explosionZ; + if (dx * dx + dy * dy + dz * dz <= (radius + 14) * (radius + 14)) { // +14 for margin of error + keys.add(new SubChunkKey(cx, cz, subY)); + } + } + } + } + keys.sort(Comparator.comparingInt(key -> { + int distCX = key.getPos().chunkXPos - (originX >> 4); + int distCZ = key.getPos().chunkZPos - (originZ >> 4); + int distSubY = key.getSubY() - originSubY; + return distCX * distCX + distCZ * distCZ + distSubY * distSubY; + })); + return keys; + } + @Override public void cacheChunksTick(int timeBudgetMs) { - if (collectFinished || this.cacheQueue == null) return; - + if (collectFinished) return; final long deadline = System.nanoTime() + (timeBudgetMs * 1_000_000L); while (System.nanoTime() < deadline) { - SubChunkKey ck = cacheQueue.poll(); + SubChunkKey ck = highPriorityReactiveQueue.poll(); if (ck == null) break; - snapshots.computeIfAbsent(ck, k -> SubChunkSnapshot.getSnapshot(world, k, BombConfig.chunkloading)); + processCacheKey(ck); } + while (System.nanoTime() < deadline && lowPriorityProactiveIterator.hasNext()) { + SubChunkKey ck = lowPriorityProactiveIterator.next(); + processCacheKey(ck); + } + } + + private void processCacheKey(SubChunkKey ck) { + if (snapshots.containsKey(ck)) return; + snapshots.put(ck, SubChunkSnapshot.getSnapshot(world, ck, BombConfig.chunkloading)); + ConcurrentLinkedQueue waiters = waitingRoom.remove(ck); + if (waiters != null) rayQueue.addAll(waiters); } @Override public void destructionTick(int timeBudgetMs) { - if (!collectFinished || !consolidationFinished || destroyFinished) return; // Added consolidationFinished check + if (!collectFinished || !consolidationFinished || destroyFinished) return; final long deadline = System.nanoTime() + timeBudgetMs * 1_000_000L; @@ -198,9 +243,7 @@ public class ExplosionNukeRayParallelized implements IExplosionRay { } if (bs.isEmpty()) { destructionMap.remove(cp); - for (int sy = 0; sy < (SUBCHUNK_PER_CHUNK); sy++) { - snapshots.remove(new SubChunkKey(cp, sy)); - } + for (int subY = 0; subY < SUBCHUNK_PER_CHUNK; subY++) snapshots.remove(new SubChunkKey(cp, subY)); it.remove(); } } @@ -223,28 +266,18 @@ public class ExplosionNukeRayParallelized implements IExplosionRay { this.destroyFinished = true; if (this.rayQueue != null) this.rayQueue.clear(); - if (this.cacheQueue != null) this.cacheQueue.clear(); + if (this.waitingRoom != null) this.waitingRoom.clear(); - if (this.latch != null) { - while (this.latch.getCount() > 0) { - this.latch.countDown(); - } - } - if (this.latchWatcherThread != null && this.latchWatcherThread.isAlive()) { - this.latchWatcherThread.interrupt(); - } + if (this.latch != null) while (this.latch.getCount() > 0) this.latch.countDown(); + if (this.latchWatcherThread != null && this.latchWatcherThread.isAlive()) this.latchWatcherThread.interrupt(); if (this.pool != null && !this.pool.isShutdown()) { this.pool.shutdownNow(); try { - if (!this.pool.awaitTermination(100, TimeUnit.MILLISECONDS)) { - MainRegistry.logger.log(Level.ERROR, "ExplosionNukeRayParallelized thread pool did not terminate promptly on cancel."); - } + if (!this.pool.awaitTermination(100, TimeUnit.MILLISECONDS)) MainRegistry.logger.log(Level.ERROR, "ExplosionNukeRayParallelized thread pool did not terminate promptly on cancel."); } catch (InterruptedException e) { Thread.currentThread().interrupt(); - if (!this.pool.isShutdown()) { - this.pool.shutdownNow(); - } + if (!this.pool.isShutdown()) this.pool.shutdownNow(); } } if (this.destructionMap != null) this.destructionMap.clear(); @@ -253,27 +286,11 @@ public class ExplosionNukeRayParallelized implements IExplosionRay { if (this.orderedChunks != null) this.orderedChunks.clear(); } - private List collectChunkInRadius() { - int cr = (radius + 15) >> 4; - int minCX = (originX >> 4) - cr; - int maxCX = (originX >> 4) + cr; - int minCZ = (originZ >> 4) - cr; - int maxCZ = (originZ >> 4) + cr; - - List list = new ArrayList<>((maxCX - minCX + 1) * (maxCZ - minCZ + 1)); - for (int cx = minCX; cx <= maxCX; ++cx) { - for (int cz = minCZ; cz <= maxCZ; ++cz) { - list.add(new ChunkCoordIntPair(cx, cz)); - } - } - return list; - } - private List generateSphereRays(int count) { List list = new ArrayList<>(count); if (count == 0) return list; if (count == 1) { - list.add(Vec3.createVectorHelper(1, 0, 0).normalize()); + list.add(Vec3.createVectorHelper(1, 0, 0)); return list; } double phi = Math.PI * (3.0 - Math.sqrt(5.0)); @@ -287,67 +304,43 @@ public class ExplosionNukeRayParallelized implements IExplosionRay { } private void runConsolidation() { - Iterator>> chunkEntryIterator = damageMap.entrySet().iterator(); - while (chunkEntryIterator.hasNext()) { - Map.Entry> entry = chunkEntryIterator.next(); - ChunkCoordIntPair cp = entry.getKey(); - ConcurrentMap innerDamageMap = entry.getValue(); - + damageMap.forEach((cp, innerDamageMap) -> { if (innerDamageMap.isEmpty()) { - chunkEntryIterator.remove(); - continue; + damageMap.remove(cp); + return; } - ConcurrentBitSet chunkDestructionBitSet = destructionMap.computeIfAbsent(cp, k -> new ConcurrentBitSet(BITSET_SIZE)); - - Iterator> damageEntryIterator = innerDamageMap.entrySet().iterator(); - while (damageEntryIterator.hasNext()) { - Map.Entry damageEntry = damageEntryIterator.next(); - int bitIndex = damageEntry.getKey(); - - float accumulatedDamage = (float) damageEntry.getValue().sum(); - + innerDamageMap.forEach((bitIndex, accumulatedDamageAdder) -> { + float accumulatedDamage = (float) accumulatedDamageAdder.sum(); if (accumulatedDamage <= 0.0f) { - damageEntryIterator.remove(); - continue; + innerDamageMap.remove(bitIndex); + return; } - int yGlobal = WORLD_HEIGHT - 1 - (bitIndex >>> 8); int subY = yGlobal >> 4; - if (subY < 0) { - damageEntryIterator.remove(); - continue; + innerDamageMap.remove(bitIndex); + return; } - SubChunkKey snapshotKey = new SubChunkKey(cp, subY); SubChunkSnapshot snap = snapshots.get(snapshotKey); - Block originalBlock; - if (snap == null || snap == SubChunkSnapshot.EMPTY) { - damageEntryIterator.remove(); - continue; - } else { - int xLocal = (bitIndex >>> 4) & 0xF; - int zLocal = bitIndex & 0xF; - originalBlock = snap.getBlock(xLocal, yGlobal & 0xF, zLocal); - if (originalBlock == Blocks.air) { - damageEntryIterator.remove(); - continue; - } + innerDamageMap.remove(bitIndex); + return; + } + int xLocal = (bitIndex >>> 4) & 0xF; + int zLocal = bitIndex & 0xF; + Block originalBlock = snap.getBlock(xLocal, yGlobal & 0xF, zLocal); + if (originalBlock == Blocks.air) { + innerDamageMap.remove(bitIndex); + return; } - float resistance = getNukeResistance(originalBlock); - if (accumulatedDamage >= resistance) { - chunkDestructionBitSet.set(bitIndex); - damageEntryIterator.remove(); - } - } - - if (innerDamageMap.isEmpty()) { - chunkEntryIterator.remove(); - } - } + if (accumulatedDamage >= resistance) chunkDestructionBitSet.set(bitIndex); + innerDamageMap.remove(bitIndex); + }); + if (innerDamageMap.isEmpty()) damageMap.remove(cp); + }); damageMap.clear(); consolidationFinished = true; } @@ -356,14 +349,9 @@ public class ExplosionNukeRayParallelized implements IExplosionRay { @Override public void run() { try { - while (true) { - if (collectFinished && rayQueue.isEmpty()) break; + while (!collectFinished && !Thread.currentThread().isInterrupted()) { RayTask task = rayQueue.poll(100, TimeUnit.MILLISECONDS); - if (task == null) { - if (collectFinished && rayQueue.isEmpty()) break; - continue; - } - task.trace(); + if (task != null) task.trace(); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); @@ -372,6 +360,10 @@ public class ExplosionNukeRayParallelized implements IExplosionRay { } private class RayTask { + private static final double RAY_DIRECTION_EPSILON = 1e-6; + private static final double PROCESSING_EPSILON = 1e-9; + private static final float MIN_EFFECTIVE_DIST_FOR_ENERGY_CALC = 0.01f; + final int dirIndex; double px, py, pz; int x, y, z; @@ -381,9 +373,8 @@ public class ExplosionNukeRayParallelized implements IExplosionRay { boolean initialised = false; double currentRayPosition; - private static final double RAY_DIRECTION_EPSILON = 1e-6; - private static final double PROCESSING_EPSILON = 1e-9; - private static final float MIN_EFFECTIVE_DIST_FOR_ENERGY_CALC = 0.01f; + private int lastCX = Integer.MIN_VALUE, lastCZ = Integer.MIN_VALUE, lastSubY = Integer.MIN_VALUE; + private SubChunkKey currentSubChunkKey = null; RayTask(int dirIdx) { this.dirIndex = dirIdx; @@ -392,9 +383,7 @@ public class ExplosionNukeRayParallelized implements IExplosionRay { void init() { if (directions == null) directions = directionsFuture.join(); Vec3 dir = directions.get(this.dirIndex); - // This scales the crater. Higher = bigger. - // Currently the crater is a little bit bigger than the original implementation - this.energy = strength * 0.3F; + this.energy = strength * INITIAL_ENERGY_FACTOR; this.px = explosionX; this.py = explosionY; this.pz = explosionZ; @@ -410,20 +399,17 @@ public class ExplosionNukeRayParallelized implements IExplosionRay { double absDirX = Math.abs(dirX); this.stepX = (absDirX < RAY_DIRECTION_EPSILON) ? 0 : (dirX > 0 ? 1 : -1); this.tDeltaX = (stepX == 0) ? Double.POSITIVE_INFINITY : 1.0 / absDirX; - this.tMaxX = (stepX == 0) ? Double.POSITIVE_INFINITY : - ((stepX > 0 ? (this.x + 1 - this.px) : (this.px - this.x)) * this.tDeltaX); + this.tMaxX = (stepX == 0) ? Double.POSITIVE_INFINITY : ((stepX > 0 ? (this.x + 1 - this.px) : (this.px - this.x)) * this.tDeltaX); double absDirY = Math.abs(dirY); this.stepY = (absDirY < RAY_DIRECTION_EPSILON) ? 0 : (dirY > 0 ? 1 : -1); this.tDeltaY = (stepY == 0) ? Double.POSITIVE_INFINITY : 1.0 / absDirY; - this.tMaxY = (stepY == 0) ? Double.POSITIVE_INFINITY : - ((stepY > 0 ? (this.y + 1 - this.py) : (this.py - this.y)) * this.tDeltaY); + this.tMaxY = (stepY == 0) ? Double.POSITIVE_INFINITY : ((stepY > 0 ? (this.y + 1 - this.py) : (this.py - this.y)) * this.tDeltaY); double absDirZ = Math.abs(dirZ); this.stepZ = (absDirZ < RAY_DIRECTION_EPSILON) ? 0 : (dirZ > 0 ? 1 : -1); this.tDeltaZ = (stepZ == 0) ? Double.POSITIVE_INFINITY : 1.0 / absDirZ; - this.tMaxZ = (stepZ == 0) ? Double.POSITIVE_INFINITY : - ((stepZ > 0 ? (this.z + 1 - this.pz) : (this.pz - this.z)) * this.tDeltaZ); + this.tMaxZ = (stepZ == 0) ? Double.POSITIVE_INFINITY : ((stepZ > 0 ? (this.z + 1 - this.pz) : (this.pz - this.z)) * this.tDeltaZ); this.initialised = true; } @@ -436,15 +422,28 @@ public class ExplosionNukeRayParallelized implements IExplosionRay { } while (energy > 0) { - if (y < 0 || y >= WORLD_HEIGHT) break; + if (y < 0 || y >= WORLD_HEIGHT || Thread.currentThread().isInterrupted()) break; if (currentRayPosition >= radius - PROCESSING_EPSILON) break; - SubChunkKey ck = new SubChunkKey(x >> 4, z >> 4, y >> 4); - SubChunkSnapshot snap = snapshots.get(ck); + int cx = x >> 4; + int cz = z >> 4; + int subY = y >> 4; + if (cx != lastCX || cz != lastCZ || subY != lastSubY) { + currentSubChunkKey = new SubChunkKey(cx, cz, subY); + lastCX = cx; + lastCZ = cz; + lastSubY = subY; + } + SubChunkSnapshot snap = snapshots.get(currentSubChunkKey); if (snap == null) { - cacheQueue.offer(ck); - rayQueue.offer(this); + final boolean[] amFirst = {false}; + ConcurrentLinkedQueue waiters = waitingRoom.computeIfAbsent(currentSubChunkKey, k -> { + amFirst[0] = true; + return new ConcurrentLinkedQueue<>(); + }); + if (amFirst[0]) highPriorityReactiveQueue.add(currentSubChunkKey); + waiters.add(this); return; } double t_exit_voxel = Math.min(tMaxX, Math.min(tMaxY, tMaxZ)); @@ -455,15 +454,13 @@ public class ExplosionNukeRayParallelized implements IExplosionRay { if (this.currentRayPosition + segmentLenInVoxel > radius - PROCESSING_EPSILON) { segmentLenForProcessing = Math.max(0.0, radius - this.currentRayPosition); stopAfterThisSegment = true; - } else { - segmentLenForProcessing = segmentLenInVoxel; - } + } else segmentLenForProcessing = segmentLenInVoxel; if (snap != SubChunkSnapshot.EMPTY && segmentLenForProcessing > PROCESSING_EPSILON) { Block block = snap.getBlock(x & 0xF, y & 0xF, z & 0xF); if (block != Blocks.air) { float resistance = getNukeResistance(block); - if (resistance >= 2_000_000F) { // cutoff + if (resistance >= NUKE_RESISTANCE_CUTOFF) { energy = 0; } else { double energyLossFactor = getEnergyLossFactor(resistance); @@ -471,27 +468,16 @@ public class ExplosionNukeRayParallelized implements IExplosionRay { energy -= damageDealt; if (damageDealt > 0) { int bitIndex = ((WORLD_HEIGHT - 1 - y) << 8) | ((x & 0xF) << 4) | (z & 0xF); + ChunkCoordIntPair chunkPos = currentSubChunkKey.getPos(); if (BombConfig.explosionAlgorithm == 2) { - ChunkCoordIntPair chunkPos = ck.getPos(); - ConcurrentMap chunkDamageMap = damageMap.get(chunkPos); - if (chunkDamageMap != null) { - chunkDamageMap.computeIfAbsent(bitIndex, k -> new DoubleAdder()).add(damageDealt); - } - } else { - if (energy > 0) { - ConcurrentBitSet bs = destructionMap.computeIfAbsent( - ck.getPos(), - posKey -> new ConcurrentBitSet(BITSET_SIZE) - ); - bs.set(bitIndex); - } - } + damageMap.computeIfAbsent(chunkPos, cp -> new ConcurrentHashMap<>(256)).computeIfAbsent(bitIndex, k -> new DoubleAdder()).add(damageDealt); + } else if (energy > 0) destructionMap.computeIfAbsent(chunkPos, posKey -> new ConcurrentBitSet(BITSET_SIZE)).set(bitIndex); } } } } this.currentRayPosition = t_exit_voxel; - if (energy <= 0 || stopAfterThisSegment || this.currentRayPosition >= radius - PROCESSING_EPSILON) break; + if (energy <= 0 || stopAfterThisSegment) break; if (tMaxX < tMaxY) { if (tMaxX < tMaxZ) { @@ -515,15 +501,7 @@ public class ExplosionNukeRayParallelized implements IExplosionRay { } private double getEnergyLossFactor(float resistance) { - double dxBlockToCenter = (this.x + 0.5) - explosionX; - double dyBlockToCenter = (this.y + 0.5) - explosionY; - double dzBlockToCenter = (this.z + 0.5) - explosionZ; - double distToBlockCenterSq = dxBlockToCenter * dxBlockToCenter + - dyBlockToCenter * dyBlockToCenter + - dzBlockToCenter * dzBlockToCenter; - double distToBlockCenter = Math.sqrt(distToBlockCenterSq); - - double effectiveDist = Math.max(distToBlockCenter, MIN_EFFECTIVE_DIST_FOR_ENERGY_CALC); + double effectiveDist = Math.max(this.currentRayPosition, MIN_EFFECTIVE_DIST_FOR_ENERGY_CALC); return (Math.pow(resistance + 1.0, 3.0 * (effectiveDist / radius)) - 1.0); } } diff --git a/src/main/java/com/hbm/util/SubChunkKey.java b/src/main/java/com/hbm/util/SubChunkKey.java index 068bdfef8..2e694ab8d 100644 --- a/src/main/java/com/hbm/util/SubChunkKey.java +++ b/src/main/java/com/hbm/util/SubChunkKey.java @@ -13,10 +13,6 @@ public class SubChunkKey { private int subY; private int hash; - public SubChunkKey() { - this(0, 0, 0); - } - public SubChunkKey(int cx, int cz, int sy) { this.update(cx, cz, sy); } From baad5cefbe1edc7ede1d73cf3fc28cb41458ae75 Mon Sep 17 00:00:00 2001 From: mlbv <51232730+mlbv@users.noreply.github.com> Date: Sat, 14 Jun 2025 08:30:42 +0800 Subject: [PATCH 4/4] feat: add RESOLUTION_FACTOR Introduce RESOLUTION_FACTOR to scale ray density for tuning. Consider exposing this in BombConfig for runtime configuration. --- .../com/hbm/explosion/ExplosionNukeRayParallelized.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/hbm/explosion/ExplosionNukeRayParallelized.java b/src/main/java/com/hbm/explosion/ExplosionNukeRayParallelized.java index 08926ff5f..dfcf0acae 100644 --- a/src/main/java/com/hbm/explosion/ExplosionNukeRayParallelized.java +++ b/src/main/java/com/hbm/explosion/ExplosionNukeRayParallelized.java @@ -34,7 +34,8 @@ public class ExplosionNukeRayParallelized implements IExplosionRay { private static final int BITSET_SIZE = 16 * WORLD_HEIGHT * 16; private static final int SUBCHUNK_PER_CHUNK = WORLD_HEIGHT >> 4; private static final float NUKE_RESISTANCE_CUTOFF = 2_000_000F; - private static final float INITIAL_ENERGY_FACTOR = 0.3F; + private static final float INITIAL_ENERGY_FACTOR = 0.3F; // Scales crater, no impact on performance + private static final double RESOLUTION_FACTOR = 1.0; // Scales ray density, no impact on crater radius protected final World world; private final double explosionX, explosionY, explosionZ; @@ -72,7 +73,7 @@ public class ExplosionNukeRayParallelized implements IExplosionRay { this.strength = strength; this.radius = radius; - int rayCount = Math.max(0, (int) (2.5 * Math.PI * strength * strength)); + int rayCount = Math.max(0, (int) (2.5 * Math.PI * strength * strength * RESOLUTION_FACTOR)); this.latch = new CountDownLatch(rayCount); List sortedSubChunks = getAllSubChunks(); this.lowPriorityProactiveIterator = sortedSubChunks.iterator(); @@ -336,7 +337,7 @@ public class ExplosionNukeRayParallelized implements IExplosionRay { return; } float resistance = getNukeResistance(originalBlock); - if (accumulatedDamage >= resistance) chunkDestructionBitSet.set(bitIndex); + if (accumulatedDamage >= resistance * RESOLUTION_FACTOR) chunkDestructionBitSet.set(bitIndex); innerDamageMap.remove(bitIndex); }); if (innerDamageMap.isEmpty()) damageMap.remove(cp);