From f8afd77da69fc53f238f0098f215b0a4e1bf6630 Mon Sep 17 00:00:00 2001 From: InspectorBoat Date: Mon, 23 Feb 2026 00:17:05 -0500 Subject: [PATCH 1/9] Use BlockPosSet over Set --- .../skyblock/BuildersWandPreview.java | 11 +- .../skyblock/dungeon/device/SimonSays.java | 15 +- .../skyblock/dungeon/secrets/Room.java | 6 +- .../skyblock/dwarven/CarpetHighlighter.java | 6 +- .../dwarven/CrystalsChestHighlighter.java | 17 +- .../skyblock/dwarven/PickobulusHelper.java | 4 +- .../skyblock/foraging/SweepOverlay.java | 4 +- .../galatea/AbstractBlockHighlighter.java | 7 +- .../galatea/SeaLumiesHighlighter.java | 13 +- .../skyblocker/skyblock/rift/EnigmaSouls.java | 9 +- .../skyblock/waypoint/FairySouls.java | 13 +- .../skyblocker/skyblock/waypoint/Relics.java | 11 +- .../hysky/skyblocker/utils/BlockPosSet.java | 587 ++++++++++++++++++ 13 files changed, 639 insertions(+), 64 deletions(-) create mode 100644 src/main/java/de/hysky/skyblocker/utils/BlockPosSet.java diff --git a/src/main/java/de/hysky/skyblocker/skyblock/BuildersWandPreview.java b/src/main/java/de/hysky/skyblocker/skyblock/BuildersWandPreview.java index 370ef5bbf53..914ae576652 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/BuildersWandPreview.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/BuildersWandPreview.java @@ -2,6 +2,7 @@ import de.hysky.skyblocker.annotations.Init; import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.utils.BlockPosSet; import de.hysky.skyblocker.utils.Utils; import de.hysky.skyblocker.utils.render.WorldRenderExtractionCallback; import de.hysky.skyblocker.utils.render.primitive.PrimitiveCollector; @@ -18,9 +19,7 @@ import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.HitResult; import java.util.ArrayDeque; -import java.util.HashSet; import java.util.Queue; -import java.util.Set; public class BuildersWandPreview { private static final int MAX_BLOCKS = 241; @@ -51,15 +50,15 @@ private static void extractBuildersWandPreview(PrimitiveCollector collector, Blo Direction side = hitResult.getDirection(); if (!client.level.getBlockState(hitPos.relative(side)).isAir()) return; BlockState state = client.level.getBlockState(hitPos); - for (BlockPos pos : findConnectedFaces(client.level, hitPos, side, state)) { + for (BlockPos pos : findConnectedFaces(client.level, hitPos, side, state).destroyAndIterate()) { extractBlockPreview(collector, pos.relative(side), state); } } - private static Set findConnectedFaces(Level world, BlockPos pos, Direction side, BlockState state) { + private static BlockPosSet findConnectedFaces(Level world, BlockPos pos, Direction side, BlockState state) { // bfs connected block faces Queue q = new ArrayDeque<>(); - Set visited = new HashSet<>(); + BlockPosSet visited = new BlockPosSet(); q.add(pos); visited.add(pos); @@ -83,7 +82,7 @@ private static Set findConnectedFaces(Level world, BlockPos pos, Direc mutable.move(dir.getOpposite()); } - if (visited.size() > MAX_BLOCKS) return Set.of(); + if (visited.size() > MAX_BLOCKS) return new BlockPosSet(); } return visited; diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/device/SimonSays.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/device/SimonSays.java index 95f629d87d0..9ec916973f8 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/device/SimonSays.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/device/SimonSays.java @@ -5,15 +5,12 @@ import de.hysky.skyblocker.events.WorldEvents; import de.hysky.skyblocker.skyblock.dungeon.DungeonBoss; import de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonManager; +import de.hysky.skyblocker.utils.BlockPosSet; import de.hysky.skyblocker.utils.ColorUtils; import de.hysky.skyblocker.utils.Utils; import de.hysky.skyblocker.utils.render.RenderHelper; import de.hysky.skyblocker.utils.render.WorldRenderExtractionCallback; import de.hysky.skyblocker.utils.render.primitive.PrimitiveCollector; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; -import it.unimi.dsi.fastutil.objects.ObjectList; -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; -import it.unimi.dsi.fastutil.objects.ObjectSet; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; import net.fabricmc.fabric.api.event.player.UseBlockCallback; import net.minecraft.client.Minecraft; @@ -40,8 +37,8 @@ public class SimonSays { private static final BlockPos START_BUTTON = new BlockPos(110, 121, 91); private static final float[] GREEN = ColorUtils.getFloatComponents(DyeColor.LIME); private static final float[] YELLOW = ColorUtils.getFloatComponents(DyeColor.YELLOW); - private static final ObjectSet CLICKED_BUTTONS = new ObjectOpenHashSet<>(); - private static final ObjectList SIMON_PATTERN = new ObjectArrayList<>(); + private static final BlockPosSet CLICKED_BUTTONS = new BlockPosSet(); + private static final BlockPosSet SIMON_PATTERN = new BlockPosSet(); @Init public static void init() { @@ -60,7 +57,7 @@ private static InteractionResult onBlockInteract(Player player, Level world, Int if (block.equals(Blocks.STONE_BUTTON)) { if (BUTTONS_AREA.contains(Vec3.atLowerCornerOf(pos))) { - CLICKED_BUTTONS.add(new BlockPos(pos)); //Copy just in case it becomes mutable in the future + CLICKED_BUTTONS.add(pos); } else if (pos.equals(START_BUTTON)) { reset(); } @@ -80,7 +77,7 @@ private static void onBlockUpdate(BlockPos pos, @Nullable BlockState oldState, B Block newBlock = newState.getBlock(); if (BOARD_AREA.contains(posVec) && newBlock.equals(Blocks.OBSIDIAN) && oldState != null && oldState.getBlock().equals(Blocks.SEA_LANTERN)) { - SIMON_PATTERN.add(pos.immutable()); //Convert to immutable because chunk delta updates use the mutable variant + SIMON_PATTERN.add(pos); } else if (BUTTONS_AREA.contains(posVec) && newBlock.equals(Blocks.AIR)) { //Upon reaching the showing of the next sequence we need to reset the state so that we don't show old data //Otherwise, the nextIndex will go beyond 5 and that can cause bugs, it also helps with the other case noted above @@ -93,7 +90,7 @@ private static void extractRendering(PrimitiveCollector collector) { if (shouldProcess()) { int buttonsRendered = 0; - for (BlockPos pos : SIMON_PATTERN) { + for (BlockPos pos : SIMON_PATTERN.iterateMut()) { //Offset to west (x - 1) to get the position of the button from the sea lantern block BlockPos buttonPos = pos.west(); ClientLevel world = Objects.requireNonNull(Minecraft.getInstance().level); //Should never be null here diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/Room.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/Room.java index 6f712b01a55..5be56e7ac00 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/Room.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/secrets/Room.java @@ -7,6 +7,7 @@ import com.mojang.serialization.Codec; import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.events.DungeonEvents; +import de.hysky.skyblocker.utils.BlockPosSet; import de.hysky.skyblocker.utils.Constants; import de.hysky.skyblocker.utils.Tickable; import de.hysky.skyblocker.utils.Utils; @@ -45,7 +46,6 @@ import java.util.Collection; import java.util.Collections; import java.util.Comparator; -import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; @@ -91,7 +91,7 @@ public class Room implements Tickable, Renderable { * Contains all blocks that have been checked to prevent checking the same block multiple times. * This is null after the room is matched. */ - private @Nullable Set checkedBlocks = new HashSet<>(); + private @Nullable BlockPosSet checkedBlocks = new BlockPosSet(); /** * The task that is used to check blocks. This is used to ensure only one such task can run at a time. */ @@ -536,7 +536,7 @@ protected void reset() { IntSortedSet segmentsX = IntSortedSets.unmodifiable(new IntRBTreeSet(segments.stream().mapToInt(Vector2ic::x).toArray())); IntSortedSet segmentsY = IntSortedSets.unmodifiable(new IntRBTreeSet(segments.stream().mapToInt(Vector2ic::y).toArray())); possibleRooms = getPossibleRooms(segmentsX, segmentsY); - checkedBlocks = new HashSet<>(); + checkedBlocks = new BlockPosSet(); doubleCheckBlocks = 0; secretWaypoints.clear(); name = null; diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CarpetHighlighter.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CarpetHighlighter.java index 78b18382965..50d4d46822a 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CarpetHighlighter.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CarpetHighlighter.java @@ -5,6 +5,7 @@ import de.hysky.skyblocker.annotations.Init; import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.events.SkyblockEvents; +import de.hysky.skyblocker.utils.BlockPosSet; import de.hysky.skyblocker.utils.Boxes; import de.hysky.skyblocker.utils.Location; import de.hysky.skyblocker.utils.Resettable; @@ -12,7 +13,6 @@ import de.hysky.skyblocker.utils.render.WorldRenderExtractionCallback; import de.hysky.skyblocker.utils.render.primitive.PrimitiveCollector; import de.hysky.skyblocker.utils.scheduler.Scheduler; -import it.unimi.dsi.fastutil.objects.ObjectAVLTreeSet; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; import net.minecraft.client.Minecraft; import net.minecraft.core.BlockPos; @@ -30,7 +30,7 @@ public final class CarpetHighlighter implements Renderable, Resettable { private static final Vec3 CARPET_BOUNDING_BOX = Boxes.getLengthVec(CarpetBlock.SHAPE.bounds()); private static final int SEARCH_RADIUS = 15; private static final int TICK_INTERVAL = 15; - private static final ObjectAVLTreeSet CARPET_LOCATIONS = new ObjectAVLTreeSet<>(); + private static final BlockPosSet CARPET_LOCATIONS = new BlockPosSet(); private static float[] colorComponents; private static boolean isLocationValid = false; @@ -46,7 +46,7 @@ public static void init() { @Override public void extractRendering(PrimitiveCollector collector) { if (!isLocationValid || !SkyblockerConfigManager.get().mining.dwarvenMines.enableCarpetHighlighter) return; - for (BlockPos carpetLocation : CARPET_LOCATIONS) { + for (BlockPos carpetLocation : CARPET_LOCATIONS.iterateMut()) { collector.submitFilledBox(Vec3.atLowerCornerOf(carpetLocation), CARPET_BOUNDING_BOX, colorComponents, colorComponents[3], false); } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsChestHighlighter.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsChestHighlighter.java index 34eb11cf777..07972eba1ed 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsChestHighlighter.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CrystalsChestHighlighter.java @@ -5,6 +5,7 @@ import de.hysky.skyblocker.events.ParticleEvents; import de.hysky.skyblocker.events.PlaySoundEvents; import de.hysky.skyblocker.events.WorldEvents; +import de.hysky.skyblocker.utils.BlockPosSet; import de.hysky.skyblocker.utils.Utils; import de.hysky.skyblocker.utils.render.WorldRenderExtractionCallback; import de.hysky.skyblocker.utils.render.primitive.PrimitiveCollector; @@ -27,8 +28,6 @@ import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.HitResult; import net.minecraft.world.phys.Vec3; -import java.util.HashSet; -import java.util.Set; public class CrystalsChestHighlighter { @@ -38,7 +37,7 @@ public class CrystalsChestHighlighter { private static final Vec3 LOCK_HIGHLIGHT_SIZE = new Vec3(0.1, 0.1, 0.1); private static int waitingForChest = 0; - private static final Set activeChests = new HashSet<>(); + private static final BlockPosSet activeChests = new BlockPosSet(); private static final Object2LongOpenHashMap activeParticles = new Object2LongOpenHashMap<>(); private static int currentLockCount = 0; private static int neededLockCount = 0; @@ -83,19 +82,17 @@ private static void onBlockUpdate(BlockPos pos, BlockState oldState, BlockState return; } - BlockPos immutable = pos.immutable(); - if (waitingForChest > 0 && newState.is(Blocks.CHEST)) { //make sure it is not too far from the player (more than 10 blocks away) - if (immutable.distToCenterSqr(CLIENT.player.position()) > 100) { + if (pos.distToCenterSqr(CLIENT.player.position()) > 100) { return; } - activeChests.add(immutable); + activeChests.add(pos); currentLockCount = 0; waitingForChest -= 1; - } else if (newState.isAir() && activeChests.contains(immutable)) { + } else if (newState.isAir() && activeChests.contains(pos)) { currentLockCount = 0; - activeChests.remove(immutable); + activeChests.remove(pos); } } @@ -159,7 +156,7 @@ private static void extractRendering(PrimitiveCollector collector) { } //render chest outline float[] color = SkyblockerConfigManager.get().mining.crystalHollows.chestHighlightColor.getComponents(new float[]{0, 0, 0, 0}); - for (BlockPos chest : activeChests) { + for (BlockPos chest : activeChests.iterateMut()) { collector.submitOutlinedBox(AABB.ofSize(chest.getCenter().subtract(0, 0.0625, 0), 0.885, 0.885, 0.885), color, color[3], 3, false); } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PickobulusHelper.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PickobulusHelper.java index c55094940f4..ffb09b25eb7 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PickobulusHelper.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/PickobulusHelper.java @@ -4,6 +4,7 @@ import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager; import de.hysky.skyblocker.utils.Area; +import de.hysky.skyblocker.utils.BlockPosSet; import de.hysky.skyblocker.utils.ColorUtils; import de.hysky.skyblocker.utils.ItemAbility; import de.hysky.skyblocker.utils.Utils; @@ -13,7 +14,6 @@ import org.jspecify.annotations.Nullable; import java.util.Arrays; -import java.util.HashSet; import java.util.Optional; import java.util.Set; import net.minecraft.ChatFormatting; @@ -102,7 +102,7 @@ public class PickobulusHelper { private static boolean shouldRender; private static @Nullable Component errorMessage; private static final BlockState[][][] blocks = new BlockState[8][8][8]; - private static final Set breakBlocks = new HashSet<>(); + private static final BlockPosSet breakBlocks = new BlockPosSet(); private static final int[] drops = new int[MiningDrop.values().length]; public static boolean shouldRender() { diff --git a/src/main/java/de/hysky/skyblocker/skyblock/foraging/SweepOverlay.java b/src/main/java/de/hysky/skyblocker/skyblock/foraging/SweepOverlay.java index 3fcf10e179b..ce54633fe28 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/foraging/SweepOverlay.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/foraging/SweepOverlay.java @@ -4,6 +4,7 @@ import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.skyblock.item.ItemCooldowns; import de.hysky.skyblocker.skyblock.tabhud.util.PlayerListManager; +import de.hysky.skyblocker.utils.BlockPosSet; import de.hysky.skyblocker.utils.Constants; import de.hysky.skyblocker.utils.Utils; import de.hysky.skyblocker.utils.render.WorldRenderExtractionCallback; @@ -13,7 +14,6 @@ import java.awt.Color; import java.util.ArrayDeque; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -241,7 +241,7 @@ private static void submitConnectedLogs(PrimitiveCollector collector, BlockHitRe }; } - HashSet visited = new HashSet<>(); + BlockPosSet visited = new BlockPosSet(); ArrayDeque queue = new ArrayDeque<>(); int woodCount = 0; float toughness = getToughness(state); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/galatea/AbstractBlockHighlighter.java b/src/main/java/de/hysky/skyblocker/skyblock/galatea/AbstractBlockHighlighter.java index 931f3612295..e17de74163c 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/galatea/AbstractBlockHighlighter.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/galatea/AbstractBlockHighlighter.java @@ -1,11 +1,11 @@ package de.hysky.skyblocker.skyblock.galatea; import de.hysky.skyblocker.events.WorldEvents; +import de.hysky.skyblocker.utils.BlockPosSet; import de.hysky.skyblocker.utils.ColorUtils; import de.hysky.skyblocker.utils.render.RenderHelper; import de.hysky.skyblocker.utils.render.WorldRenderExtractionCallback; import de.hysky.skyblocker.utils.render.primitive.PrimitiveCollector; -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientChunkEvents; import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; import net.minecraft.client.Minecraft; @@ -18,7 +18,6 @@ import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.phys.AABB; import java.util.Iterator; -import java.util.Set; import java.util.function.Predicate; /** @@ -26,7 +25,7 @@ */ //TODO Move this to a more generic package since this is not Galatea specific (maybe make a world rendering utility package?) public abstract class AbstractBlockHighlighter { - protected final Set highlightedBlocks = new ObjectOpenHashSet<>(); + protected final BlockPosSet highlightedBlocks = new BlockPosSet(); protected final float[] colour; protected final Predicate statePredicate; @@ -98,7 +97,7 @@ private void extractRendering(PrimitiveCollector collector) { Minecraft client = Minecraft.getInstance(); if (!shouldProcess() || client.level == null) return; - for (BlockPos highlight : this.highlightedBlocks) { + for (BlockPos highlight : this.highlightedBlocks.iterateMut()) { AABB outline = RenderHelper.getBlockBoundingBox(client.level, highlight); if (outline != null) { diff --git a/src/main/java/de/hysky/skyblocker/skyblock/galatea/SeaLumiesHighlighter.java b/src/main/java/de/hysky/skyblocker/skyblock/galatea/SeaLumiesHighlighter.java index 31650c62cfa..d5c8b9708ee 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/galatea/SeaLumiesHighlighter.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/galatea/SeaLumiesHighlighter.java @@ -2,10 +2,9 @@ import de.hysky.skyblocker.annotations.Init; import de.hysky.skyblocker.config.SkyblockerConfigManager; +import de.hysky.skyblocker.utils.BlockPosSet; import de.hysky.skyblocker.utils.Utils; -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.util.Iterator; -import java.util.Set; import net.minecraft.client.Minecraft; import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.core.BlockPos; @@ -17,15 +16,15 @@ import net.minecraft.world.level.chunk.LevelChunk; public class SeaLumiesHighlighter extends AbstractBlockHighlighter { - private final Set allBlocks = new ObjectOpenHashSet<>(); + private final BlockPosSet allBlocks = new BlockPosSet(); @Override public void onBlockUpdate(BlockPos pos, BlockState oldState, BlockState newState) { if (!shouldProcess()) return; if (this.statePredicate.test(newState)) { - this.allBlocks.add(pos.immutable()); - if (isEnabled() && isEnoughPickles(newState)) this.highlightedBlocks.add(pos.immutable()); + this.allBlocks.add(pos); + if (isEnabled() && isEnoughPickles(newState)) this.highlightedBlocks.add(pos); } else { this.allBlocks.remove(pos); this.highlightedBlocks.remove(pos); @@ -63,8 +62,8 @@ protected void onChunkLoad(ClientLevel world, LevelChunk chunk) { if (!shouldProcess()) return; chunk.findBlocks(statePredicate, (pos, state) -> { - this.allBlocks.add(pos.immutable()); - if (isEnabled() && isEnoughPickles(state)) this.highlightedBlocks.add(pos.immutable()); + this.allBlocks.add(pos); + if (isEnabled() && isEnoughPickles(state)) this.highlightedBlocks.add(pos); }); } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/rift/EnigmaSouls.java b/src/main/java/de/hysky/skyblocker/skyblock/rift/EnigmaSouls.java index 4aefd1df9e2..63b930ce9ca 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/rift/EnigmaSouls.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/rift/EnigmaSouls.java @@ -9,6 +9,7 @@ import de.hysky.skyblocker.SkyblockerMod; import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.config.configs.OtherLocationsConfig; +import de.hysky.skyblocker.utils.BlockPosSet; import de.hysky.skyblocker.utils.ColorUtils; import de.hysky.skyblocker.utils.Constants; import de.hysky.skyblocker.utils.PosUtils; @@ -36,9 +37,7 @@ import java.nio.file.Path; import java.util.Comparator; import java.util.HashMap; -import java.util.HashSet; import java.util.Map; -import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executors; import java.util.function.Supplier; @@ -88,16 +87,16 @@ static void load(Minecraft client) { } static void save(Minecraft client) { - Map> foundSouls = new HashMap<>(); + Map foundSouls = new HashMap<>(); for (ProfileAwareWaypoint soul : SOUL_WAYPOINTS.values()) { for (String profile : soul.foundProfiles) { - foundSouls.computeIfAbsent(profile, profile_ -> new HashSet<>()); + foundSouls.computeIfAbsent(profile, profile_ -> new BlockPosSet()); foundSouls.get(profile).add(soul.pos); } } JsonObject json = new JsonObject(); - for (Map.Entry> foundSoulsForProfile : foundSouls.entrySet()) { + for (Map.Entry foundSoulsForProfile : foundSouls.entrySet()) { JsonArray foundSoulsJson = new JsonArray(); for (BlockPos foundSoul : foundSoulsForProfile.getValue()) { diff --git a/src/main/java/de/hysky/skyblocker/skyblock/waypoint/FairySouls.java b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/FairySouls.java index 0ca01ace96e..113e9174df8 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/waypoint/FairySouls.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/FairySouls.java @@ -9,6 +9,7 @@ import de.hysky.skyblocker.annotations.Init; import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.config.configs.HelperConfig; +import de.hysky.skyblocker.utils.BlockPosSet; import de.hysky.skyblocker.utils.ColorUtils; import de.hysky.skyblocker.utils.Constants; import de.hysky.skyblocker.utils.NEURepoManager; @@ -40,9 +41,7 @@ import java.nio.file.NoSuchFileException; import java.util.Comparator; import java.util.HashMap; -import java.util.HashSet; import java.util.Map; -import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -108,12 +107,12 @@ private static void loadFairySouls() { } private static void saveFoundFairySouls(Minecraft client) { - Map>> foundFairies = new HashMap<>(); + Map> foundFairies = new HashMap<>(); for (Map.Entry> fairiesForLocation : fairySouls.entrySet()) { for (ProfileAwareWaypoint fairySoul : fairiesForLocation.getValue().values()) { for (String profile : fairySoul.foundProfiles) { foundFairies.computeIfAbsent(profile, profile_ -> new HashMap<>()); - foundFairies.get(profile).computeIfAbsent(fairiesForLocation.getKey(), location_ -> new HashSet<>()); + foundFairies.get(profile).computeIfAbsent(fairiesForLocation.getKey(), location_ -> new BlockPosSet()); foundFairies.get(profile).get(fairiesForLocation.getKey()).add(fairySoul.pos); } } @@ -121,11 +120,11 @@ private static void saveFoundFairySouls(Minecraft client) { try (BufferedWriter writer = Files.newBufferedWriter(SkyblockerMod.CONFIG_DIR.resolve("found_fairy_souls.json"))) { JsonObject foundFairiesJson = new JsonObject(); - for (Map.Entry>> foundFairiesForProfile : foundFairies.entrySet()) { + for (Map.Entry> foundFairiesForProfile : foundFairies.entrySet()) { JsonObject foundFairiesForProfileJson = new JsonObject(); - for (Map.Entry> foundFairiesForLocation : foundFairiesForProfile.getValue().entrySet()) { + for (Map.Entry foundFairiesForLocation : foundFairiesForProfile.getValue().entrySet()) { JsonArray foundFairiesForLocationJson = new JsonArray(); - for (BlockPos foundFairy : foundFairiesForLocation.getValue()) { + for (BlockPos foundFairy : foundFairiesForLocation.getValue().iterateMut()) { foundFairiesForLocationJson.add(PosUtils.getPosString(foundFairy)); } foundFairiesForProfileJson.add(foundFairiesForLocation.getKey(), foundFairiesForLocationJson); diff --git a/src/main/java/de/hysky/skyblocker/skyblock/waypoint/Relics.java b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/Relics.java index 4134186c132..e6b54605158 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/waypoint/Relics.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/waypoint/Relics.java @@ -9,6 +9,7 @@ import de.hysky.skyblocker.annotations.Init; import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.config.configs.OtherLocationsConfig; +import de.hysky.skyblocker.utils.BlockPosSet; import de.hysky.skyblocker.utils.ColorUtils; import de.hysky.skyblocker.utils.Constants; import de.hysky.skyblocker.utils.PosUtils; @@ -37,9 +38,7 @@ import java.nio.file.NoSuchFileException; import java.util.Comparator; import java.util.HashMap; -import java.util.HashSet; import java.util.Map; -import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executors; import java.util.function.Supplier; @@ -97,19 +96,19 @@ private static void loadRelics(Minecraft client) { } private static void saveFoundRelics(Minecraft client) { - Map> foundRelics = new HashMap<>(); + Map foundRelics = new HashMap<>(); for (ProfileAwareWaypoint relic : relics.values()) { for (String profile : relic.foundProfiles) { - foundRelics.computeIfAbsent(profile, profile_ -> new HashSet<>()); + foundRelics.computeIfAbsent(profile, profile_ -> new BlockPosSet()); foundRelics.get(profile).add(relic.pos); } } try (BufferedWriter writer = Files.newBufferedWriter(SkyblockerMod.CONFIG_DIR.resolve("found_relics.json"))) { JsonObject json = new JsonObject(); - for (Map.Entry> foundRelicsForProfile : foundRelics.entrySet()) { + for (Map.Entry foundRelicsForProfile : foundRelics.entrySet()) { JsonArray foundRelicsJson = new JsonArray(); - for (BlockPos foundRelic : foundRelicsForProfile.getValue()) { + for (BlockPos foundRelic : foundRelicsForProfile.getValue().iterateMut()) { foundRelicsJson.add(PosUtils.getPosString(foundRelic)); } json.add(foundRelicsForProfile.getKey(), foundRelicsJson); diff --git a/src/main/java/de/hysky/skyblocker/utils/BlockPosSet.java b/src/main/java/de/hysky/skyblocker/utils/BlockPosSet.java new file mode 100644 index 00000000000..c8485bdd257 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/utils/BlockPosSet.java @@ -0,0 +1,587 @@ +package de.hysky.skyblocker.utils; + +import net.minecraft.core.BlockPos; + +import java.util.*; + +public class BlockPosSet implements Iterable, Set { + /// Sentinels for tombstones (deleted slots) and empty slots + /// This does mean that the set cannot store the positions + /// new BlockPos(unhashLong(TOMBSTONE)) and + /// new BlockPos(unhashLong(EMPTY)), + /// aka the coordinates + /// [15_425_900, 107, 31_185_031] and + /// [31_556_398, 107, 9_164_935]. + /// However, this is unlikely to be an issue, as both positions + /// are outside the world border (+-30_000_000, +-30_000_000) + public final static long TOMBSTONE = Long.MAX_VALUE; + public final static long EMPTY = TOMBSTONE - 1; + public final static float LOAD_FACTOR = 0.75f; + public final static int LANES = 4; + + public int size = 0; + public int tombstones = 0; + /// Must be a power of two + public int capacity = 8; + /// Saves a single subtraction. WOW + public int mask = capacity - 1; + public int nextGrowThreshold = Math.min((int) (this.capacity * LOAD_FACTOR), this.capacity - 1); + public long[] entries = new long[this.capacity + LANES - 1]; + + // debug fields + public final static boolean DEBUG = false; + public static int TOMBSTONES_CLEARED = 0; + public static int QUERIES = 0; + public static int PROBES = 0; + + public BlockPosSet() { + Arrays.fill(this.entries, EMPTY); + if (DEBUG) { + TOMBSTONES_CLEARED = 0; + QUERIES = 0; + PROBES = 0; + } + } + + /// Roughly 1.5x faster than LongOpenHashSet at high load factors + /// Roughly on par at low load factors + public boolean containsXyz(int x, int y, int z) { + if (DEBUG) QUERIES++; + final long hash = hashXyz(x, y, z); + final int mask = this.mask; + int i = (int) hash & mask; + + do { + // TODO: Switch to this when Vector API stabilizes + /* + LongVector n = LongVector.fromArray(LongVector.SPECIES_512, this.entries, i); + if (n.compare(VectorOperators.EQ, hash).anyTrue()) return true; + if (n.compare(VectorOperators.EQ, EMPTY).anyTrue()) return false; + */ + + // The JIT doesn't actually manage to vectorize this code. + // Why is it profitable over a naive loop, then? + // I was originally going something about making the branch more predictable + // (due to the bitwise or), but then the bitwise or randomly + // started being worse than logical or. So frankly your guess is as good as mine. + // The loop unrolling probably helps. + @SuppressWarnings("PointlessArithmeticExpression") + long n0 = entries[i + 0]; + long n1 = entries[i + 1]; + long n2 = entries[i + 2]; + long n3 = entries[i + 3]; + if (n0 == hash || n1 == hash || n2 == hash || n3 == hash) return true; + if (n0 == EMPTY || n1 == EMPTY || n2 == EMPTY || n3 == EMPTY) return false; + i = (i + LANES) & mask; + + // Non-unrolled version + /* + if (entries[i] == hash) return true; + if (entries[i] == EMPTY) return false; + i = (i + 1) & mask; + */ + if (DEBUG) PROBES++; + } while (true); + } + + /// We store the hash instead of the long representing the blockpos. + /// This is a tradeoff: It allows us to resize the map faster, because + /// we don't have to recompute the hash. However, iteration is slower because + /// we have to unhash the values before unpacking them. + public boolean addXYZ(int x, int y, int z) { + if (this.size >= this.nextGrowThreshold) this.resize(this.capacity * 2); + else if (this.tombstones >= this.capacity - this.nextGrowThreshold) this.resize(this.capacity); + + if (DEBUG) QUERIES++; + final long hash = hashXyz(x, y, z); + + final int mask = this.mask; + int slotToInsert = (int) hash & mask; + int candidateTombstoneSlot = Integer.MAX_VALUE; + + // scan slots for empty spot + do { + // already exists + if (this.entries[slotToInsert] == hash) { + return false; + } + // we can't insert immediately if we find a tombstone slot, because the entry could show up later + else if (this.entries[slotToInsert] == TOMBSTONE && candidateTombstoneSlot == Integer.MAX_VALUE) { + candidateTombstoneSlot = slotToInsert; + } + // found an absent slot + else if (this.entries[slotToInsert] == EMPTY) { + // check if we found a tombstone to replace earlier + if (candidateTombstoneSlot != Integer.MAX_VALUE) { + slotToInsert = candidateTombstoneSlot; + // if we replaced a tombstone, reduce tombstone count + this.tombstones--; + } + this.entries[slotToInsert] = hash; + this.size++; + + if (slotToInsert < LANES - 1) this.entries[slotToInsert + this.capacity] = hash; + return true; + } + // wrap to beginning + // we will never enter an infinite loop because there is at least 1 empty slot + + if (DEBUG) PROBES++; + slotToInsert = (slotToInsert + 1) & mask; + } while (true); + } + + public boolean removeXyz(int x, int y, int z) { + if (this.size == 0) return false; + + final long hash = hashXyz(x, y, z); + final int mask = this.capacity - 1; + int i = (int) hash & mask; + + do { + if (this.entries[i] == hash) { + // replace with tombstone + this.entries[i] = TOMBSTONE; + if (i < LANES - 1) this.entries[i + this.capacity] = TOMBSTONE; + this.size--; + this.tombstones++; + + // resize if falling below quarter load + /* + if (this.size < this.capacity * 0.25f && this.size > 8) { + this.resize(this.capacity / 2); + return true; + } + // */ + + // travel backwards and attempt to remove tombstones if the next slot is empty +// /* + do { + if (this.entries[(i + 1) & mask] == TOMBSTONE) { + this.entries[i] = EMPTY; + this.tombstones--; + if (DEBUG) TOMBSTONES_CLEARED++; + } + i = (i - 1) & mask; + } while (this.entries[i] == TOMBSTONE); + // */ + + return true; + } else if (this.entries[i] == EMPTY) return false; + i = (i + 1) & mask; + } while (true); + } + + public void resize(int newCapacity) { + if (newCapacity < this.size) + throw new AssertionError("capacity must be greater than size! (got: %d, size: %d)".formatted(newCapacity, this.size)); + if ((newCapacity & newCapacity - 1) != 0) + throw new AssertionError("capacity must be a power of two! (got: %d)".formatted(newCapacity)); + final int mask = newCapacity - 1; + long[] newEntries = new long[newCapacity + LANES - 1]; + Arrays.fill(newEntries, EMPTY); + + + // iterate through the current entries array + for (int currentIndex = 0; currentIndex < this.capacity; currentIndex++) { + // skip tombstones and absents + if (this.entries[currentIndex] >= EMPTY) continue; + + // add to new entries array + final long hash = this.entries[currentIndex]; + int newIndex = (int) hash & mask; + inner: + while (true) { + // found new slot, add here + // no need to check for tombstones here + if (newEntries[newIndex] == EMPTY) { + newEntries[newIndex] = hash; + if (newIndex < LANES - 1) newEntries[newIndex + newCapacity] = hash; + break inner; + } + // wrap to beginning + // no need to worry about an infinite loop, because we always have space + newIndex = (newIndex + 1) & mask; + } + } + + // no more tombstones + this.tombstones = 0; + this.capacity = newCapacity; + this.mask = newCapacity - 1; + this.nextGrowThreshold = Math.min((int) (this.capacity * LOAD_FACTOR), this.capacity - 1); + this.entries = newEntries; + } + + public void clearRetainingCapacity() { + this.size = 0; + Arrays.fill(this.entries, EMPTY); + } + + public static long hashPos(BlockPos pos) { + return hashXyz(pos.getX(), pos.getY(), pos.getZ()); + } + + public static long hashXyz(int x, int y, int z) { + return hashLong( + BlockPos.asLong(x, y, z) + ); + } + + /// This bijective hash function is faster than the one fastutil uses + /// (2 instructions worth of latency vs 5) while still providing + /// a good distribution from empirical testing. + public static long hashLong(final long l) { + // https://github.com/torvalds/linux/blob/master/include/linux/hash.h#L42 <- this one sucks + // https://www.pcg-random.org/posts/does-it-beat-the-minimal-standard.html <- used the 64 bit mcg constant + return Long.rotateRight(l * 0xcb45348a28cb43bdL, 32); // 64 bit mcg + } + + public static long unhashLong(final long h) { + return Long.rotateLeft(h, 32) * 0xE3EBDD17C2778F95L; + } + + @Override + public int size() { + return this.size; + } + + @Override + public boolean isEmpty() { + return this.size > 0; + } + + @Override + public boolean contains(Object o) { + return o instanceof BlockPos pos && this.containsXyz(pos.getX(), pos.getY(), pos.getZ()); + } + + public boolean contains(BlockPos pos) { + return this.containsXyz(pos.getX(), pos.getY(), pos.getZ()); + } + + @Override + public Object[] toArray() { + int size = this.size; + int capacity = this.capacity; + long[] entries = this.entries; + + BlockPos[] array = new BlockPos[size]; + for (int i = 0, j = 0; i < capacity && j < size; i++) { + long h = entries[i]; + if (h >= EMPTY) continue; + + array[j] = BlockPos.of(unhashLong(h)); + } + return array; + } + + @SuppressWarnings({"unchecked", "DataFlowIssue"}) + @Override + public T[] toArray(T[] a) { + int size = this.size; + int capacity = this.capacity; + long[] entries = this.entries; + + if (a.length < size) a = (T[]) new BlockPos[size]; + else if (a.length > size) a[size] = null; + + for (int i = 0, j = 0; i < capacity && j < size; i++) { + long h = entries[i]; + if (h >= EMPTY) continue; + + a[j] = (T) BlockPos.of(unhashLong(h)); + } + + return a; + } + + @Override + public boolean add(BlockPos blockPos) { + return this.addXYZ(blockPos.getX(), blockPos.getY(), blockPos.getZ()); + } + + @Override + public boolean remove(Object o) { + return o instanceof BlockPos pos && this.removeXyz(pos.getX(), pos.getY(), pos.getZ()); + } + + public boolean remove(BlockPos pos) { + return this.removeXyz(pos.getX(), pos.getY(), pos.getZ()); + } + + @Override + public boolean containsAll(Collection c) { + Iterator iter = c.iterator(); + while (iter.hasNext() && iter.next() instanceof BlockPos pos) { + if (!this.contains(pos)) return false; + } + return true; + } + + @Override + public boolean addAll(Collection c) { + Iterator iter = c.iterator(); + boolean anyAdded = false; + while (iter.hasNext() && iter.next() instanceof BlockPos pos) { + anyAdded |= this.add(pos); + } + return anyAdded; + } + + @Override + public boolean retainAll(Collection c) { + long[] longs = this.entries; + BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(); + boolean removedAny = false; + + for (int i = 0, capacity = this.capacity; i < capacity; i++) { + long h = longs[i]; + if (h >= EMPTY) continue; + pos.set(h); + if (!c.contains(pos)) { + longs[i] = TOMBSTONE; + this.size--; + this.tombstones++; + removedAny = true; + + if (i < LANES - 1) longs[i + capacity] = TOMBSTONE; + } + } + + // TODO: resize at the end + return removedAny; + } + + @Override + public boolean removeAll(Collection c) { + Iterator iter = c.iterator(); + boolean removedAny = false; + + final long[] entries = this.entries; + int capacity = this.capacity; + final int mask = this.mask; + + while (iter.hasNext() && iter.next() instanceof BlockPos pos) { + boolean result = false; + if (this.size != 0) { + final long hash = hashPos(pos); + int i = (int) hash & mask; + do { + if (entries[i] == hash) { + // replace with tombstone + entries[i] = TOMBSTONE; + if (i < LANES - 1) entries[i + capacity] = TOMBSTONE; + this.size--; + this.tombstones++; + + result = true; + break; + } else if (entries[i] == EMPTY) { + break; + } + i = (i + 1) & mask; + } while (true); + } + + removedAny |= result; + } + return removedAny; + } + + @Override + public void clear() { + this.clearRetainingCapacity(); + } + + + @Override + public Iterator iterator() { + return new Iter(this.capacity, this.entries); + } + + private static class Iter implements Iterator { + private int index; + private int nextIndex; + private final int capacity; + private final long[] entries; + + public Iter(int capacity, long[] entries) { + this.index = Integer.MIN_VALUE; + this.nextIndex = Integer.MIN_VALUE; + for (int i = 0; i < capacity; i++) { + if (entries[i] < EMPTY) { + this.nextIndex = i; + break; + } + } + this.capacity = capacity; + this.entries = entries; + } + + @Override + public boolean hasNext() { + return this.nextIndex != Integer.MIN_VALUE; + } + + @Override + public BlockPos next() { + if (!hasNext()) throw new NoSuchElementException(); + this.index = this.nextIndex; + do { + this.nextIndex++; + if (this.nextIndex >= this.capacity) { + nextIndex = Integer.MIN_VALUE; + break; + } + } while (this.entries[this.nextIndex] >= EMPTY); + return BlockPos.of(unhashLong(this.entries[this.index])); + } + + @Override + public void remove() { + this.entries[this.index] = TOMBSTONE; + } + } + + /// Returns the same {@link BlockPos.MutableBlockPos} each time {@link Iterator#next} is called. + public Iterable iterateMut() { + return new IterMut(this.capacity, this.entries); + } + + private static class IterMut implements Iterator, Iterable { + private int index; + private int nextIndex; + private final int capacity; + private final long[] entries; + private final BlockPos.MutableBlockPos pos; + + public IterMut(int capacity, long[] entries) { + this.index = Integer.MIN_VALUE; + this.nextIndex = Integer.MIN_VALUE; + for (int i = 0; i < capacity; i++) { + if (entries[i] < EMPTY) { + this.nextIndex = i; + break; + } + } + this.capacity = capacity; + this.entries = entries; + this.pos = new BlockPos.MutableBlockPos(); + } + + @Override + public boolean hasNext() { + return this.nextIndex != Integer.MIN_VALUE; + } + + @Override + public BlockPos.MutableBlockPos next() { + if (!hasNext()) throw new NoSuchElementException(); + this.index = this.nextIndex; + do { + this.nextIndex++; + if (this.nextIndex >= this.capacity) { + this.nextIndex = Integer.MIN_VALUE; + break; + } + } while (this.entries[this.nextIndex] >= EMPTY); + this.pos.set(unhashLong(this.entries[this.index])); + return this.pos; + } + + @Override + public void remove() { + this.entries[this.index] = TOMBSTONE; + } + + @Override + public Iterator iterator() { + return this; + } + } + + /// Compacts the entries array before iterating + /// Consider using the default iterator if you're going to early out, as this might result in wasted work + /// Using any other methods on the set after this returns is undefined behavior + public Iterable destroyAndIterate() { + return new OwningIter(this.size, this.entries); + } + + private static class OwningIter implements Iterable, Iterator { + private int i = 0; + private final int size; + private final long[] entries; + + private OwningIter(int size, long[] entries) { + for (int i = 0, j = 0; j < size; i++) { + if (entries[i] >= EMPTY) continue; + entries[j] = entries[i]; + j++; + } + this.entries = entries; + this.size = size; + } + + @Override + public boolean hasNext() { + return i < size; + } + + @Override + public BlockPos next() { + if (this.i >= size) throw new NoSuchElementException(); + long l = unhashLong(entries[this.i]); + this.i++; + return BlockPos.of(l); + } + + public Iterator iterator() { + return this; + } + } + + + /// Compacts the entries array before iterating + /// Consider using the default iterator if you're going to early out, as this might result in wasted work + /// Using any other methods on the set after this returns is undefined behavior + /// Returns the same {@link BlockPos.MutableBlockPos} each time {@link Iterator#next} is called. + public Iterable destroyAndIterateMut() { + return new OwningIterMut(this.size, this.entries); + } + + private static class OwningIterMut implements Iterable, Iterator { + private int i = 0; + private final int size; + private final long[] entries; + private final BlockPos.MutableBlockPos pos; + + private OwningIterMut(int size, long[] entries) { + for (int i = 0, j = 0; j < size; i++) { + if (entries[i] >= EMPTY) continue; + entries[j] = entries[i]; + j++; + } + this.entries = entries; + this.size = size; + this.pos = new BlockPos.MutableBlockPos(); + } + + @Override + public boolean hasNext() { + return this.i < this.size; + } + + @Override + public BlockPos.MutableBlockPos next() { + if (this.i >= this.size) throw new NoSuchElementException(); + this.pos.set(unhashLong(this.entries[this.i])); + this.i++; + return this.pos; + } + + public Iterator iterator() { + return this; + } + } +} From ee0284ff87ad283323b7910ebf03a1cb7bac4c6d Mon Sep 17 00:00:00 2001 From: InspectorBoat Date: Mon, 23 Feb 2026 01:47:23 -0500 Subject: [PATCH 2/9] Fix style --- .../de/hysky/skyblocker/utils/BlockPosSet.java | 14 +++++++++----- .../de/hysky/skyblocker/utils/BlockPosSetTest.java | 0 2 files changed, 9 insertions(+), 5 deletions(-) create mode 100644 src/test/java/de/hysky/skyblocker/utils/BlockPosSetTest.java diff --git a/src/main/java/de/hysky/skyblocker/utils/BlockPosSet.java b/src/main/java/de/hysky/skyblocker/utils/BlockPosSet.java index c8485bdd257..d0f9fc0eae9 100644 --- a/src/main/java/de/hysky/skyblocker/utils/BlockPosSet.java +++ b/src/main/java/de/hysky/skyblocker/utils/BlockPosSet.java @@ -2,7 +2,11 @@ import net.minecraft.core.BlockPos; -import java.util.*; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Set; public class BlockPosSet implements Iterable, Set { /// Sentinels for tombstones (deleted slots) and empty slots @@ -14,10 +18,10 @@ public class BlockPosSet implements Iterable, Set { /// [31_556_398, 107, 9_164_935]. /// However, this is unlikely to be an issue, as both positions /// are outside the world border (+-30_000_000, +-30_000_000) - public final static long TOMBSTONE = Long.MAX_VALUE; - public final static long EMPTY = TOMBSTONE - 1; - public final static float LOAD_FACTOR = 0.75f; - public final static int LANES = 4; + public static final long TOMBSTONE = Long.MAX_VALUE; + public static final long EMPTY = TOMBSTONE - 1; + public static final float LOAD_FACTOR = 0.75f; + public static final int LANES = 4; public int size = 0; public int tombstones = 0; diff --git a/src/test/java/de/hysky/skyblocker/utils/BlockPosSetTest.java b/src/test/java/de/hysky/skyblocker/utils/BlockPosSetTest.java new file mode 100644 index 00000000000..e69de29bb2d From ad9a420833b8fe653874ba7c1f858543ecd1ad26 Mon Sep 17 00:00:00 2001 From: InspectorBoat Date: Mon, 23 Feb 2026 01:51:14 -0500 Subject: [PATCH 3/9] Remove debug fields --- .../de/hysky/skyblocker/utils/BlockPosSet.java | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/main/java/de/hysky/skyblocker/utils/BlockPosSet.java b/src/main/java/de/hysky/skyblocker/utils/BlockPosSet.java index d0f9fc0eae9..bfb05874c3d 100644 --- a/src/main/java/de/hysky/skyblocker/utils/BlockPosSet.java +++ b/src/main/java/de/hysky/skyblocker/utils/BlockPosSet.java @@ -32,25 +32,13 @@ public class BlockPosSet implements Iterable, Set { public int nextGrowThreshold = Math.min((int) (this.capacity * LOAD_FACTOR), this.capacity - 1); public long[] entries = new long[this.capacity + LANES - 1]; - // debug fields - public final static boolean DEBUG = false; - public static int TOMBSTONES_CLEARED = 0; - public static int QUERIES = 0; - public static int PROBES = 0; - public BlockPosSet() { Arrays.fill(this.entries, EMPTY); - if (DEBUG) { - TOMBSTONES_CLEARED = 0; - QUERIES = 0; - PROBES = 0; - } } /// Roughly 1.5x faster than LongOpenHashSet at high load factors /// Roughly on par at low load factors public boolean containsXyz(int x, int y, int z) { - if (DEBUG) QUERIES++; final long hash = hashXyz(x, y, z); final int mask = this.mask; int i = (int) hash & mask; @@ -84,7 +72,6 @@ public boolean containsXyz(int x, int y, int z) { if (entries[i] == EMPTY) return false; i = (i + 1) & mask; */ - if (DEBUG) PROBES++; } while (true); } @@ -96,7 +83,6 @@ public boolean addXYZ(int x, int y, int z) { if (this.size >= this.nextGrowThreshold) this.resize(this.capacity * 2); else if (this.tombstones >= this.capacity - this.nextGrowThreshold) this.resize(this.capacity); - if (DEBUG) QUERIES++; final long hash = hashXyz(x, y, z); final int mask = this.mask; @@ -130,7 +116,6 @@ else if (this.entries[slotToInsert] == EMPTY) { // wrap to beginning // we will never enter an infinite loop because there is at least 1 empty slot - if (DEBUG) PROBES++; slotToInsert = (slotToInsert + 1) & mask; } while (true); } @@ -164,7 +149,6 @@ public boolean removeXyz(int x, int y, int z) { if (this.entries[(i + 1) & mask] == TOMBSTONE) { this.entries[i] = EMPTY; this.tombstones--; - if (DEBUG) TOMBSTONES_CLEARED++; } i = (i - 1) & mask; } while (this.entries[i] == TOMBSTONE); From 83222122aca8094cdc1e7321ef3b2146c7fd7272 Mon Sep 17 00:00:00 2001 From: InspectorBoat Date: Mon, 23 Feb 2026 10:16:35 -0500 Subject: [PATCH 4/9] Remove some defensive clones --- .../skyblocker/skyblock/BuildersWandPreview.java | 2 +- .../skyblock/dungeon/device/TargetPractice.java | 15 +++++++-------- .../skyblock/dwarven/CarpetHighlighter.java | 4 +--- .../galatea/AbstractBlockHighlighter.java | 4 ++-- 4 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/main/java/de/hysky/skyblocker/skyblock/BuildersWandPreview.java b/src/main/java/de/hysky/skyblocker/skyblock/BuildersWandPreview.java index 914ae576652..2981295504e 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/BuildersWandPreview.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/BuildersWandPreview.java @@ -50,7 +50,7 @@ private static void extractBuildersWandPreview(PrimitiveCollector collector, Blo Direction side = hitResult.getDirection(); if (!client.level.getBlockState(hitPos.relative(side)).isAir()) return; BlockState state = client.level.getBlockState(hitPos); - for (BlockPos pos : findConnectedFaces(client.level, hitPos, side, state).destroyAndIterate()) { + for (BlockPos pos : findConnectedFaces(client.level, hitPos, side, state).destroyAndIterateMut()) { extractBlockPreview(collector, pos.relative(side), state); } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/device/TargetPractice.java b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/device/TargetPractice.java index 4502d2aaf88..22b67091bf3 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dungeon/device/TargetPractice.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dungeon/device/TargetPractice.java @@ -1,13 +1,13 @@ package de.hysky.skyblocker.skyblock.dungeon.device; -import java.util.ArrayList; -import java.util.List; +import java.util.Set; import de.hysky.skyblocker.annotations.Init; import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.events.WorldEvents; import de.hysky.skyblocker.skyblock.dungeon.DungeonBoss; import de.hysky.skyblocker.skyblock.dungeon.secrets.DungeonManager; +import de.hysky.skyblocker.utils.BlockPosSet; import de.hysky.skyblocker.utils.ColorUtils; import de.hysky.skyblocker.utils.Utils; import de.hysky.skyblocker.utils.render.WorldRenderExtractionCallback; @@ -24,12 +24,12 @@ public class TargetPractice { private static final int ACTIVATION_THRESHOLD = 1; private static final int UNACTIVATED = 0; // In the order you see the grid in the world by row. - private static final List POSSIBLE_TARGETS = List.of( + private static final Set POSSIBLE_TARGETS = Set.of( new BlockPos(68, 130, 50), new BlockPos(66, 130, 50), new BlockPos(64, 130, 50), new BlockPos(68, 128, 50), new BlockPos(66, 128, 50), new BlockPos(64, 128, 50), new BlockPos(68, 126, 50), new BlockPos(66, 126, 50), new BlockPos(64, 126, 50) - ); - private static final List HIT_TARGETS = new ArrayList<>(); + ); + private static final BlockPosSet HIT_TARGETS = new BlockPosSet(); private static final float[] RED = ColorUtils.getFloatComponents(DyeColor.RED); @Init @@ -68,8 +68,7 @@ private static void onBlockStateUpdate(BlockPos pos, BlockState oldState, BlockS // player must shoot, so when that block turns back into Blue Terracotta it has either been successfully shot or the device reset. if (POSSIBLE_TARGETS.contains(pos)) { if (oldState.getBlock().equals(Blocks.EMERALD_BLOCK) && newState.getBlock().equals(Blocks.BLUE_TERRACOTTA)) { - // Convert position to immutable since it might be mutable and we can't have it changing - HIT_TARGETS.add(pos.immutable()); + HIT_TARGETS.add(pos); } } } @@ -77,7 +76,7 @@ private static void onBlockStateUpdate(BlockPos pos, BlockState oldState, BlockS private static void extractRendering(PrimitiveCollector collector) { if (!shouldProcess()) return; - for (BlockPos pos : HIT_TARGETS) { + for (BlockPos pos : HIT_TARGETS.iterateMut()) { collector.submitFilledBox(pos, RED, 0.5f, false); } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CarpetHighlighter.java b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CarpetHighlighter.java index 50d4d46822a..b11e178a694 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CarpetHighlighter.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/dwarven/CarpetHighlighter.java @@ -59,9 +59,7 @@ public void tick() { if (!isLocationValid || !SkyblockerConfigManager.get().mining.dwarvenMines.enableCarpetHighlighter || Minecraft.getInstance().level == null || Minecraft.getInstance().player == null) return; Iterable iterable = BlockPos.withinManhattan(Minecraft.getInstance().player.blockPosition(), SEARCH_RADIUS, SEARCH_RADIUS, SEARCH_RADIUS); for (BlockPos blockPos : iterable) { - //The iterator contains a BlockPos.Mutable that it changes the position of to iterate over blocks, - // so it has to be converted to an immutable BlockPos or the position will change based on the player's position && the search radius - if (checkForCarpet(blockPos)) CARPET_LOCATIONS.add(blockPos.immutable()); + if (checkForCarpet(blockPos)) CARPET_LOCATIONS.add(blockPos); } } diff --git a/src/main/java/de/hysky/skyblocker/skyblock/galatea/AbstractBlockHighlighter.java b/src/main/java/de/hysky/skyblocker/skyblock/galatea/AbstractBlockHighlighter.java index e17de74163c..a161afa8822 100644 --- a/src/main/java/de/hysky/skyblocker/skyblock/galatea/AbstractBlockHighlighter.java +++ b/src/main/java/de/hysky/skyblocker/skyblock/galatea/AbstractBlockHighlighter.java @@ -60,7 +60,7 @@ protected void onBlockUpdate(BlockPos pos, BlockState oldState, BlockState newSt if (!shouldProcess()) return; if (this.statePredicate.test(newState)) { - this.highlightedBlocks.add(pos.immutable()); + this.highlightedBlocks.add(pos); } else { this.highlightedBlocks.remove(pos); } @@ -73,7 +73,7 @@ protected void onBlockUpdate(BlockPos pos, BlockState oldState, BlockState newSt protected void onChunkLoad(ClientLevel world, LevelChunk chunk) { if (!shouldProcess()) return; - chunk.findBlocks(statePredicate, (pos, state) -> this.highlightedBlocks.add(pos.immutable())); + chunk.findBlocks(statePredicate, (pos, state) -> this.highlightedBlocks.add(pos)); } /** From 217849a1166cba84b511622145e24287e4196a34 Mon Sep 17 00:00:00 2001 From: InspectorBoat Date: Mon, 23 Feb 2026 12:47:38 -0500 Subject: [PATCH 5/9] Add tests for BlockPosSet --- .../hysky/skyblocker/utils/BlockPosSet.java | 27 +- .../skyblocker/utils/BlockPosSetTest.java | 252 ++++++++++++++++++ 2 files changed, 277 insertions(+), 2 deletions(-) diff --git a/src/main/java/de/hysky/skyblocker/utils/BlockPosSet.java b/src/main/java/de/hysky/skyblocker/utils/BlockPosSet.java index bfb05874c3d..07e115d56b6 100644 --- a/src/main/java/de/hysky/skyblocker/utils/BlockPosSet.java +++ b/src/main/java/de/hysky/skyblocker/utils/BlockPosSet.java @@ -8,7 +8,7 @@ import java.util.NoSuchElementException; import java.util.Set; -public class BlockPosSet implements Iterable, Set { +public class BlockPosSet implements Iterable, Set, Cloneable { /// Sentinels for tombstones (deleted slots) and empty slots /// This does mean that the set cannot store the positions /// new BlockPos(unhashLong(TOMBSTONE)) and @@ -49,7 +49,7 @@ public boolean containsXyz(int x, int y, int z) { LongVector n = LongVector.fromArray(LongVector.SPECIES_512, this.entries, i); if (n.compare(VectorOperators.EQ, hash).anyTrue()) return true; if (n.compare(VectorOperators.EQ, EMPTY).anyTrue()) return false; - */ + */ // The JIT doesn't actually manage to vectorize this code. // Why is it profitable over a naive loop, then? @@ -75,6 +75,18 @@ public boolean containsXyz(int x, int y, int z) { } while (true); } + public int indexOf(BlockPos pos) { + final long hash = hashXyz(pos.getX(), pos.getY(), pos.getZ()); + final int mask = this.mask; + int i = (int) hash & mask; + + do { + if (entries[i] == hash) return i; + if (entries[i] == EMPTY) return -1; + i = (i + 1) & mask; + } while (true); + } + /// We store the hash instead of the long representing the blockpos. /// This is a tradeoff: It allows us to resize the map faster, because /// we don't have to recompute the hash. However, iteration is slower because @@ -572,4 +584,15 @@ public Iterator iterator() { return this; } } + + @Override + public BlockPosSet clone() { + try { + var cloned = (BlockPosSet) super.clone(); + cloned.entries = cloned.entries.clone(); + return cloned; + } catch (CloneNotSupportedException unreachable) { + throw new RuntimeException(); + } + } } diff --git a/src/test/java/de/hysky/skyblocker/utils/BlockPosSetTest.java b/src/test/java/de/hysky/skyblocker/utils/BlockPosSetTest.java index e69de29bb2d..d28d5251931 100644 --- a/src/test/java/de/hysky/skyblocker/utils/BlockPosSetTest.java +++ b/src/test/java/de/hysky/skyblocker/utils/BlockPosSetTest.java @@ -0,0 +1,252 @@ +package de.hysky.skyblocker.utils; + +import net.minecraft.core.BlockPos; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Test; + +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Objects; +import java.util.Random; +import java.util.Set; + +public class BlockPosSetTest { + // @Test + void testAddRemoveContains() { + long start = System.nanoTime(); + PrintStream devNull = new PrintStream(OutputStream.nullOutputStream()); + fuzzTest(new TestParams(0, 10_000, -64, -64, -64, 64, 64, 64), devNull, System.err); + fuzzTest(new TestParams(187598127, 10_000, -3, -3, -3, 20, 51, 2), devNull, System.err); + fuzzTest(new TestParams(791798532, 10_000, 0, 0, 0, 1, 1, 1), devNull, System.err); + long elapsed = System.nanoTime() - start; + System.err.printf("E: %.2f ms\n", (double) elapsed / 1_000_000); + } + + @Test + void testIterators() { + long start = System.nanoTime(); + PrintStream devNull = new PrintStream(OutputStream.nullOutputStream()); + iterTest(new TestParams(0, 10_000, -64, -64, -64, 64, 64, 64), devNull, System.err); + iterTest(new TestParams(187598127, 10_000, -3, -3, -3, 20, 51, 2), devNull, System.err); + iterTest(new TestParams(791798532, 10_000, 0, 0, 0, 1, 1, 1), devNull, System.err); + long elapsed = System.nanoTime() - start; + System.err.printf("E: %.2f ms\n", (double) elapsed / 1_000_000); + } + + private static void fuzzTest(TestParams params, PrintStream log, PrintStream err) { + final BlockPosSet set = new BlockPosSet(); + final Set javaSet = new HashSet<>(); + final Random random = new Random(params.seed()); + for (int iters = 0; iters < 10000; iters++) { + BlockPos pos = new BlockPos( + random.nextInt(params.minX(), params.maxX()), + random.nextInt(params.minY(), params.maxY()), + random.nextInt(params.minZ(), params.maxZ()) + ); + + int action = random.nextInt(3); + + try { + switch (action) { + case 0 -> { + expectEqual(javaSet.add(pos), set.add(pos)); + log.printf("add %s t %d @ %d\n", pos, BlockPosSet.hashPos(pos) & (set.capacity - 1), set.indexOf(pos)); + } + case 1 -> { + boolean success = javaSet.remove(pos); + int index = set.indexOf(pos); + log.printf("remove %s %s @ %d\n", success ? "Y" : "N", pos, index); + expectEqual(success, set.remove(pos)); + } + case 2 -> { + log.printf("contains %s\n", pos); + expectEqual(javaSet.contains(pos), set.contains(pos)); + } + } + } catch (AssertionError e) { + err.printf("%s\n", set); + throw e; + } + + // check sets are the same + for (BlockPos present : javaSet) { + try { + expectEqual(true, set.contains(present)); + } catch (AssertionError e) { + err.printf("missing %s from custom @ %s\n", present, (int) BlockPosSet.hashPos(pos) & (set.capacity - 1)); + for (int i = (int) BlockPosSet.hashPos(pos) & (set.capacity - 1), j = 0; j < 16; j++, i++) { + long h = set.entries[i]; + if (h == BlockPosSet.EMPTY) err.print("E "); + else if (h == BlockPosSet.TOMBSTONE) err.print("T "); + else err.printf("%s ", BlockPos.of(BlockPosSet.unhashLong(set.entries[i]))); + } + long[] longs = set.entries; + for (int i = 0; i < longs.length; i++) { + long h = longs[i]; + if (h == BlockPosSet.hashPos(pos)) { + err.printf("%d %d\n", i, set.entries.length); + } + } + throw e; + } + } + for (BlockPos present : set) { + try { + expectEqual(true, javaSet.contains(present)); + } catch (AssertionError e) { + err.printf("missing %s from javaSet\n".formatted(present)); + throw e; + } + } + } + } + + private static void iterTest(TestParams params, PrintStream log, PrintStream err) { + final BlockPosSet set = new BlockPosSet(); + final Random random = new Random(params.seed()); + for (int iters = 0; iters < 10000; iters++) { + BlockPos pos = new BlockPos( + random.nextInt(params.minX(), params.maxX()), + random.nextInt(params.minY(), params.maxY()), + random.nextInt(params.minZ(), params.maxZ()) + ); + + int action = random.nextInt(2); + + switch (action) { + case 0 -> set.add(pos); + case 1 -> set.remove(pos); + } + } + + { + Iterator iterator = set.iterator(); + final Set javaSet = new HashSet<>(); + int i = 0; + while (iterator.hasNext()) { + BlockPos pos = iterator.next(); + i++; + javaSet.add(pos); + } + expectEqual(javaSet.size(), i); + expectEqual(true, javaSet.containsAll(set)); + expectEqual(true, set.containsAll(javaSet)); + + Set jSetClone = new HashSet<>(javaSet); + jSetClone.removeAll(set); + expectEqual(0, jSetClone.size()); + + set.removeAll(javaSet); + expectEqual(0, set.size()); + } + + { + Iterator iterator = set.iterateMut().iterator(); + final Set javaSet = new HashSet<>(); + int i = 0; + while (iterator.hasNext()) { + BlockPos pos = iterator.next().immutable(); + i++; + javaSet.add(pos); + } + expectEqual(javaSet.size(), i); + expectEqual(true, javaSet.containsAll(set)); + expectEqual(true, set.containsAll(javaSet)); + + Set jSetClone = new HashSet<>(javaSet); + jSetClone.removeAll(set); + expectEqual(0, jSetClone.size()); + + set.removeAll(javaSet); + expectEqual(0, set.size()); + } + + { + Random rand = new Random(0x837e8598eb1dc288L); + final Set javaSet = new HashSet<>(set); + Iterator iterator = set.iterateMut().iterator(); + int i = 0; + while (iterator.hasNext()) { + BlockPos pos = iterator.next(); + if (rand.nextBoolean()) { + iterator.remove(); + expectEqual(true, javaSet.remove(pos)); + } else { + i ++; + } + } + expectEqual(javaSet.size(), i); + expectEqual(true, javaSet.containsAll(set)); + expectEqual(true, set.containsAll(javaSet)); + + Set jSetClone = new HashSet<>(javaSet); + jSetClone.removeAll(set); + expectEqual(0, jSetClone.size()); + + set.removeAll(javaSet); + expectEqual(0, set.size()); + } + + { + Iterator iterator = set.clone().destroyAndIterate().iterator(); + final Set javaSet = new HashSet<>(); + int i = 0; + while (iterator.hasNext()) { + BlockPos pos = iterator.next(); + i++; + javaSet.add(pos); + } + expectEqual(javaSet.size(), i); + expectEqual(true, javaSet.containsAll(set)); + expectEqual(true, set.containsAll(javaSet)); + + Set jSetClone = new HashSet<>(javaSet); + jSetClone.removeAll(set); + expectEqual(0, jSetClone.size()); + + set.removeAll(javaSet); + expectEqual(0, set.size()); + } + } + + + public static void expectEqual(Object expected, Object actual) { + if (!Objects.equals(expected, actual)) { + throw new AssertionError("Expected %s, got %s".formatted(expected, actual)); + } + } + + private record TestParams(int seed, int iters, int minX, int minY, int minZ, int maxX, int maxY, int maxZ) { + private static TestParams fromRandom(Random random) { + final int stddev = 100; + int x0 = (int) (random.nextGaussian(0, stddev)); + int x1 = (int) (random.nextGaussian(0, stddev)); + int y0 = random.nextInt(0, stddev * 5); + int y1 = random.nextInt(0, stddev * 5); + int z0 = (int) (random.nextGaussian(0, stddev)); + int z1 = (int) (random.nextGaussian(0, stddev)); + return new TestParams( + random.nextInt(), + 10_000, + Math.min(x0, x1), + Math.min(y0, y1), + Math.min(z0, z1), + x0 == x1 ? x0 + 1 : Math.max(x0, x1), + y0 == y1 ? y0 + 1 : Math.max(y0, y1), + z0 == z1 ? z0 + 1 : Math.max(z0, z1) + ); + } + + public @NotNull String toString() { + return "%dx%dx%d=%d (%x)".formatted( + this.maxX - this.minX, + this.maxY - this.minY, + this.maxZ - this.minZ, + (this.maxX - this.minX) * (this.maxY - this.minY) * (this.maxZ - this.minZ), + this.seed + ); + } + } +} From b649cd13efb3bd858177bc5b630151febe90d89a Mon Sep 17 00:00:00 2001 From: InspectorBoat Date: Mon, 23 Feb 2026 12:52:07 -0500 Subject: [PATCH 6/9] Fix tombstome removal bug --- .../de/hysky/skyblocker/utils/BlockPosSet.java | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/main/java/de/hysky/skyblocker/utils/BlockPosSet.java b/src/main/java/de/hysky/skyblocker/utils/BlockPosSet.java index 07e115d56b6..5f75889c15a 100644 --- a/src/main/java/de/hysky/skyblocker/utils/BlockPosSet.java +++ b/src/main/java/de/hysky/skyblocker/utils/BlockPosSet.java @@ -156,15 +156,14 @@ public boolean removeXyz(int x, int y, int z) { // */ // travel backwards and attempt to remove tombstones if the next slot is empty -// /* do { - if (this.entries[(i + 1) & mask] == TOMBSTONE) { - this.entries[i] = EMPTY; - this.tombstones--; - } + if (this.entries[(i + 1) & mask] != EMPTY) break; + + this.entries[i] = EMPTY; + this.tombstones--; + i = (i - 1) & mask; } while (this.entries[i] == TOMBSTONE); - // */ return true; } else if (this.entries[i] == EMPTY) return false; @@ -173,10 +172,9 @@ public boolean removeXyz(int x, int y, int z) { } public void resize(int newCapacity) { - if (newCapacity < this.size) - throw new AssertionError("capacity must be greater than size! (got: %d, size: %d)".formatted(newCapacity, this.size)); - if ((newCapacity & newCapacity - 1) != 0) - throw new AssertionError("capacity must be a power of two! (got: %d)".formatted(newCapacity)); + assert newCapacity >= this.size : "capacity must be greater than size! (got: %d, size: %d)".formatted(newCapacity, this.size); + assert (newCapacity & newCapacity - 1) == 0 : "capacity must be a power of two! (got: %d)".formatted(newCapacity); + final int mask = newCapacity - 1; long[] newEntries = new long[newCapacity + LANES - 1]; Arrays.fill(newEntries, EMPTY); From edcd484976b84c44434fc70a2af4811572c23314 Mon Sep 17 00:00:00 2001 From: InspectorBoat Date: Mon, 23 Feb 2026 12:57:24 -0500 Subject: [PATCH 7/9] Fix style --- src/main/java/de/hysky/skyblocker/utils/BlockPosSet.java | 9 ++++----- .../java/de/hysky/skyblocker/utils/BlockPosSetTest.java | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/main/java/de/hysky/skyblocker/utils/BlockPosSet.java b/src/main/java/de/hysky/skyblocker/utils/BlockPosSet.java index 5f75889c15a..f333cf25873 100644 --- a/src/main/java/de/hysky/skyblocker/utils/BlockPosSet.java +++ b/src/main/java/de/hysky/skyblocker/utils/BlockPosSet.java @@ -392,7 +392,6 @@ public void clear() { this.clearRetainingCapacity(); } - @Override public Iterator iterator() { return new Iter(this.capacity, this.entries); @@ -404,7 +403,7 @@ private static class Iter implements Iterator { private final int capacity; private final long[] entries; - public Iter(int capacity, long[] entries) { + Iter(int capacity, long[] entries) { this.index = Integer.MIN_VALUE; this.nextIndex = Integer.MIN_VALUE; for (int i = 0; i < capacity; i++) { @@ -454,7 +453,7 @@ private static class IterMut implements Iterator, Iter private final long[] entries; private final BlockPos.MutableBlockPos pos; - public IterMut(int capacity, long[] entries) { + IterMut(int capacity, long[] entries) { this.index = Integer.MIN_VALUE; this.nextIndex = Integer.MIN_VALUE; for (int i = 0; i < capacity; i++) { @@ -511,7 +510,7 @@ private static class OwningIter implements Iterable, Iterator= EMPTY) continue; entries[j] = entries[i]; @@ -554,7 +553,7 @@ private static class OwningIterMut implements Iterable private final long[] entries; private final BlockPos.MutableBlockPos pos; - private OwningIterMut(int size, long[] entries) { + OwningIterMut(int size, long[] entries) { for (int i = 0, j = 0; j < size; i++) { if (entries[i] >= EMPTY) continue; entries[j] = entries[i]; diff --git a/src/test/java/de/hysky/skyblocker/utils/BlockPosSetTest.java b/src/test/java/de/hysky/skyblocker/utils/BlockPosSetTest.java index d28d5251931..8edcad6a2f0 100644 --- a/src/test/java/de/hysky/skyblocker/utils/BlockPosSetTest.java +++ b/src/test/java/de/hysky/skyblocker/utils/BlockPosSetTest.java @@ -164,7 +164,7 @@ private static void iterTest(TestParams params, PrintStream log, PrintStream err } { - Random rand = new Random(0x837e8598eb1dc288L); + Random rand = new Random(0x837E8598EB1DC288L); final Set javaSet = new HashSet<>(set); Iterator iterator = set.iterateMut().iterator(); int i = 0; From 5bdd1bee51c5d0f3cdccde2dace498f2cbc2f39c Mon Sep 17 00:00:00 2001 From: InspectorBoat Date: Mon, 23 Feb 2026 13:19:26 -0500 Subject: [PATCH 8/9] Actually fix style --- .../hysky/skyblocker/utils/BlockPosSetTest.java | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/test/java/de/hysky/skyblocker/utils/BlockPosSetTest.java b/src/test/java/de/hysky/skyblocker/utils/BlockPosSetTest.java index 8edcad6a2f0..ab70d0d101e 100644 --- a/src/test/java/de/hysky/skyblocker/utils/BlockPosSetTest.java +++ b/src/test/java/de/hysky/skyblocker/utils/BlockPosSetTest.java @@ -1,7 +1,6 @@ package de.hysky.skyblocker.utils; import net.minecraft.core.BlockPos; -import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; import java.io.OutputStream; @@ -13,7 +12,7 @@ import java.util.Set; public class BlockPosSetTest { - // @Test + @Test void testAddRemoveContains() { long start = System.nanoTime(); PrintStream devNull = new PrintStream(OutputStream.nullOutputStream()); @@ -174,7 +173,7 @@ private static void iterTest(TestParams params, PrintStream log, PrintStream err iterator.remove(); expectEqual(true, javaSet.remove(pos)); } else { - i ++; + i++; } } expectEqual(javaSet.size(), i); @@ -238,15 +237,5 @@ private static TestParams fromRandom(Random random) { z0 == z1 ? z0 + 1 : Math.max(z0, z1) ); } - - public @NotNull String toString() { - return "%dx%dx%d=%d (%x)".formatted( - this.maxX - this.minX, - this.maxY - this.minY, - this.maxZ - this.minZ, - (this.maxX - this.minX) * (this.maxY - this.minY) * (this.maxZ - this.minZ), - this.seed - ); - } } } From b8d58dadb4d7f65dab5f03cee06980154520a582 Mon Sep 17 00:00:00 2001 From: InspectorBoat Date: Mon, 23 Feb 2026 13:31:46 -0500 Subject: [PATCH 9/9] ALL LIES, UNDONE / ALWAYS REBORN / THE CHOICE TO LIVE IN ANGUISH / THE WALLS ARE CLOSING IN --- src/main/java/de/hysky/skyblocker/utils/BlockPosSet.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/de/hysky/skyblocker/utils/BlockPosSet.java b/src/main/java/de/hysky/skyblocker/utils/BlockPosSet.java index f333cf25873..ed91e57e18f 100644 --- a/src/main/java/de/hysky/skyblocker/utils/BlockPosSet.java +++ b/src/main/java/de/hysky/skyblocker/utils/BlockPosSet.java @@ -232,7 +232,7 @@ public static long hashXyz(int x, int y, int z) { public static long hashLong(final long l) { // https://github.com/torvalds/linux/blob/master/include/linux/hash.h#L42 <- this one sucks // https://www.pcg-random.org/posts/does-it-beat-the-minimal-standard.html <- used the 64 bit mcg constant - return Long.rotateRight(l * 0xcb45348a28cb43bdL, 32); // 64 bit mcg + return Long.rotateRight(l * 0xCB45348A28CB43BDL, 32); // 64 bit mcg } public static long unhashLong(final long h) {