From ad59f4421e95a14325b98917257a4e761fd52c4c Mon Sep 17 00:00:00 2001 From: Xytronix <32957125+Xytronix@users.noreply.github.com> Date: Thu, 26 Feb 2026 14:44:00 +0100 Subject: [PATCH] ft: pathfinding and movement optimizations --- .../cc/irori/refixes/early/EarlyOptions.java | 5 ++ .../refixes/early/mixin/MixinAStarBase.java | 69 +++++++++++++++++++ .../early/mixin/MixinRepulsionTicker.java | 24 +++++++ early/src/main/resources/refixes.mixins.json | 4 +- .../main/java/cc/irori/refixes/Refixes.java | 7 ++ .../refixes/config/impl/EarlyConfig.java | 12 +++- 6 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 early/src/main/java/cc/irori/refixes/early/mixin/MixinAStarBase.java create mode 100644 early/src/main/java/cc/irori/refixes/early/mixin/MixinRepulsionTicker.java diff --git a/early/src/main/java/cc/irori/refixes/early/EarlyOptions.java b/early/src/main/java/cc/irori/refixes/early/EarlyOptions.java index 08fe3ee..203418f 100644 --- a/early/src/main/java/cc/irori/refixes/early/EarlyOptions.java +++ b/early/src/main/java/cc/irori/refixes/early/EarlyOptions.java @@ -34,6 +34,11 @@ public final class EarlyOptions { public static final Value SHARED_INSTANCES_ENABLED = new Value<>(); public static final Value SHARED_INSTANCES_EXCLUDED_PREFIXES = new Value<>(); + /* Pathfinding */ + public static final Value PATHFINDING_MAX_PATH_LENGTH = new Value<>(); + public static final Value PATHFINDING_OPEN_NODES_LIMIT = new Value<>(); + public static final Value PATHFINDING_TOTAL_NODES_LIMIT = new Value<>(); + // Private constructor to prevent instantiation private EarlyOptions() {} diff --git a/early/src/main/java/cc/irori/refixes/early/mixin/MixinAStarBase.java b/early/src/main/java/cc/irori/refixes/early/mixin/MixinAStarBase.java new file mode 100644 index 0000000..b241c3b --- /dev/null +++ b/early/src/main/java/cc/irori/refixes/early/mixin/MixinAStarBase.java @@ -0,0 +1,69 @@ +package cc.irori.refixes.early.mixin; + +import cc.irori.refixes.early.EarlyOptions; +import com.hypixel.hytale.server.npc.navigation.AStarBase; +import com.hypixel.hytale.server.npc.navigation.AStarNode; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import java.util.List; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +/** + * Optimizes A* pathfinding: + * Binary search insert for open nodes (replaces O(n) linear scan) + * Configurable limits for maxPathLength, openNodesLimit, totalNodesLimit + */ +@Mixin(AStarBase.class) +public class MixinAStarBase { + + @Shadow + protected List openNodes; + + @Shadow + protected Long2ObjectMap visitedBlocks; + + @Shadow + protected int maxPathLength; + + @Shadow + protected int openNodesLimit; + + @Shadow + protected int totalNodesLimit; + + @Inject( + method = "addOpenNode(Lcom/hypixel/hytale/server/npc/navigation/AStarNode;J)V", + at = @At("HEAD"), + cancellable = true) + private void refixes$binarySearchInsert(AStarNode node, long index, CallbackInfo ci) { + float totalCost = node.getTotalCost(); + + int lo = 0, hi = openNodes.size(); + while (lo < hi) { + int mid = (lo + hi) >>> 1; + if (openNodes.get(mid).getTotalCost() < totalCost) { + hi = mid; + } else { + lo = mid + 1; + } + } + openNodes.add(lo, node); + visitedBlocks.put(index, node); + + ci.cancel(); + } + + @Inject(method = "initComputePath", at = @At("HEAD")) + private void refixes$applyPathfindingLimits(CallbackInfoReturnable cir) { + if (!EarlyOptions.isAvailable()) { + return; + } + this.maxPathLength = EarlyOptions.PATHFINDING_MAX_PATH_LENGTH.get(); + this.openNodesLimit = EarlyOptions.PATHFINDING_OPEN_NODES_LIMIT.get(); + this.totalNodesLimit = EarlyOptions.PATHFINDING_TOTAL_NODES_LIMIT.get(); + } +} diff --git a/early/src/main/java/cc/irori/refixes/early/mixin/MixinRepulsionTicker.java b/early/src/main/java/cc/irori/refixes/early/mixin/MixinRepulsionTicker.java new file mode 100644 index 0000000..a5519ff --- /dev/null +++ b/early/src/main/java/cc/irori/refixes/early/mixin/MixinRepulsionTicker.java @@ -0,0 +1,24 @@ +package cc.irori.refixes.early.mixin; + +import com.hypixel.hytale.server.core.modules.entity.repulsion.RepulsionSystems; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +// Pools the ObjectArrayList allocated per entity per tick to reduce GC pressure +@Mixin(RepulsionSystems.RepulsionTicker.class) +public class MixinRepulsionTicker { + + @Unique + private static final ThreadLocal refixes$resultList = + ThreadLocal.withInitial(ObjectArrayList::new); + + @Redirect(method = "tick", at = @At(value = "NEW", target = "it/unimi/dsi/fastutil/objects/ObjectArrayList")) + private ObjectArrayList refixes$poolResults() { + ObjectArrayList list = refixes$resultList.get(); + list.clear(); + return list; + } +} diff --git a/early/src/main/resources/refixes.mixins.json b/early/src/main/resources/refixes.mixins.json index 20a0d92..b6717f0 100644 --- a/early/src/main/resources/refixes.mixins.json +++ b/early/src/main/resources/refixes.mixins.json @@ -42,6 +42,8 @@ "MixinWorld", "MixinWorldConfig", "MixinWorldMapTracker", - "MixinWorldSpawningSystem" + "MixinWorldSpawningSystem", + "MixinAStarBase", + "MixinRepulsionTicker" ] } diff --git a/plugin/src/main/java/cc/irori/refixes/Refixes.java b/plugin/src/main/java/cc/irori/refixes/Refixes.java index 30a39fc..07b9e42 100644 --- a/plugin/src/main/java/cc/irori/refixes/Refixes.java +++ b/plugin/src/main/java/cc/irori/refixes/Refixes.java @@ -161,6 +161,13 @@ private void registerEarlyOptions() { EarlyOptions.PARALLEL_ENTITY_TICKING.setSupplier( () -> experimentalConfig.getValue(ExperimentalConfig.PARALLEL_ENTITY_TICKING)); + EarlyOptions.PATHFINDING_MAX_PATH_LENGTH.setSupplier( + () -> config.getValue(EarlyConfig.PATHFINDING_MAX_PATH_LENGTH)); + EarlyOptions.PATHFINDING_OPEN_NODES_LIMIT.setSupplier( + () -> config.getValue(EarlyConfig.PATHFINDING_OPEN_NODES_LIMIT)); + EarlyOptions.PATHFINDING_TOTAL_NODES_LIMIT.setSupplier( + () -> config.getValue(EarlyConfig.PATHFINDING_TOTAL_NODES_LIMIT)); + EarlyOptions.setAvailable(true); /* Tick Sleep Optimization */ diff --git a/plugin/src/main/java/cc/irori/refixes/config/impl/EarlyConfig.java b/plugin/src/main/java/cc/irori/refixes/config/impl/EarlyConfig.java index f316c2a..cd19441 100644 --- a/plugin/src/main/java/cc/irori/refixes/config/impl/EarlyConfig.java +++ b/plugin/src/main/java/cc/irori/refixes/config/impl/EarlyConfig.java @@ -24,6 +24,13 @@ public class EarlyConfig extends Configuration { public static final ConfigurationKey MAX_CHUNKS_PER_TICK = new ConfigurationKey<>("MaxChunksPerTick", ConfigField.INTEGER, 4); + public static final ConfigurationKey PATHFINDING_MAX_PATH_LENGTH = + new ConfigurationKey<>("PathfindingMaxPathLength", ConfigField.INTEGER, 200); + public static final ConfigurationKey PATHFINDING_OPEN_NODES_LIMIT = + new ConfigurationKey<>("PathfindingOpenNodesLimit", ConfigField.INTEGER, 80); + public static final ConfigurationKey PATHFINDING_TOTAL_NODES_LIMIT = + new ConfigurationKey<>("PathfindingTotalNodesLimit", ConfigField.INTEGER, 400); + private static final EarlyConfig INSTANCE = new EarlyConfig(); public EarlyConfig() { @@ -35,7 +42,10 @@ public EarlyConfig() { DISABLE_FLUID_PRE_PROCESS, ASYNC_BLOCK_PRE_PROCESS, MAX_CHUNKS_PER_SECOND, - MAX_CHUNKS_PER_TICK); + MAX_CHUNKS_PER_TICK, + PATHFINDING_MAX_PATH_LENGTH, + PATHFINDING_OPEN_NODES_LIMIT, + PATHFINDING_TOTAL_NODES_LIMIT); } public static EarlyConfig get() {