Skip to content

Commit

Permalink
Cache all paths in memory
Browse files Browse the repository at this point in the history
This should slightly reduce the memory footprint while pathfinding and also ensure paths are not needlessly recalculated.
  • Loading branch information
AlexProgrammerDE committed Jan 25, 2025
1 parent fb1a877 commit 4a83061
Show file tree
Hide file tree
Showing 13 changed files with 84 additions and 56 deletions.
5 changes: 5 additions & 0 deletions server/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import me.champeau.jmh.JMHTask

plugins {
`sf-project-conventions`
alias(libs.plugins.jmh)
Expand Down Expand Up @@ -105,6 +107,9 @@ tasks {
withType<Checkstyle> {
exclude("**/com/soulfiremc/server/data**")
}
withType<JMHTask> {
outputs.upToDateWhen { false }
}
}

jmh {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
*/
package com.soulfiremc.server.pathfinding;

import com.google.common.math.DoubleMath;
import com.soulfiremc.server.pathfinding.execution.WorldAction;
import com.soulfiremc.server.pathfinding.graph.actions.movement.ActionDirection;
import lombok.AllArgsConstructor;
Expand Down Expand Up @@ -82,7 +81,7 @@ public MinecraftRouteNode(NodeState node, List<WorldAction> actions,

@Override
public int compareTo(MinecraftRouteNode other) {
return DoubleMath.fuzzyCompare(this.totalRouteScore, other.totalRouteScore, 0.0001);
return Double.compare(this.totalRouteScore, other.totalRouteScore);
}

public void setBetterParent(MinecraftRouteNode parent,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public boolean equals(Object o) {
return false;
}

return usableBlockItems == otherUsableBlockItems && blockPosition.equals(otherBlockPosition);
return usableBlockItems == otherUsableBlockItems && blockPosition.minecraftEquals(otherBlockPosition);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import com.soulfiremc.server.util.structs.CallLimiter;
import it.unimi.dsi.fastutil.longs.Long2IntMap;
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectHeapPriorityQueue;
import lombok.extern.slf4j.Slf4j;
Expand Down Expand Up @@ -89,6 +90,7 @@ public List<WorldAction> findRouteSync(NodeState from) {

// Store block positions and the best route to them
var blockItemsIndex = new Long2IntOpenHashMap();
var instructionCache = new Long2ObjectOpenHashMap<GraphInstructions[]>();
var routeIndex = new Object2ObjectOpenHashMap<NodeState, MinecraftRouteNode>();

// Store block positions that we need to look at
Expand Down Expand Up @@ -161,11 +163,27 @@ public List<WorldAction> findRouteSync(NodeState from) {
}

try {
graph.insertActions(
current.node(),
current.parentToNodeDirection(),
instructions -> handleInstructions(openSet, routeIndex, blockItemsIndex, current, instructions)
);
instructionCache.compute(current.node().blockPosition().asMinecraftLong(), (k, v) -> {
if (v == null) {
var list = new ArrayList<GraphInstructions>();
graph.insertActions(
current.node().blockPosition(),
current.parentToNodeDirection(),
instructions -> {
list.add(instructions);
handleInstructions(openSet, routeIndex, blockItemsIndex, current, instructions);
}
);

return list.toArray(GraphInstructions[]::new);
}

for (var instructions : v) {
handleInstructions(openSet, routeIndex, blockItemsIndex, current, instructions);
}

return v;
});
} catch (OutOfLevelException e) {
log.debug("Found a node out of the level: {}", current.node());
stopwatch.stop();
Expand Down Expand Up @@ -197,7 +215,14 @@ private void handleInstructions(ObjectHeapPriorityQueue<MinecraftRouteNode> open
Long2IntMap blockItemsIndex,
MinecraftRouteNode current,
GraphInstructions instructions) {
var instructionNode = instructions.node();
var newBlocks = current.node().usableBlockItems() + instructions.deltaUsableBlockItems();

// If we don't have enough items to reach this node, we can skip it
if (newBlocks < 0) {
return;
}

var instructionNode = new NodeState(instructions.blockPosition(), newBlocks);

// Pre-check if we can reach this node with the current amount of items
// We don't want to consider nodes again where we have even less usable items
Expand Down Expand Up @@ -239,6 +264,7 @@ private void handleInstructions(ObjectHeapPriorityQueue<MinecraftRouteNode> open

log.debug("Found a new node: {}", instructionNode);


openSet.enqueue(node);

return node;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ public final class SFVec3i {
public final int z;
private int hashCode;
private boolean hashCodeSet;
private long minecraftLong;
private boolean minecraftLongSet;

public static SFVec3i fromDouble(Vector3d vec) {
return fromInt(vec.toInt());
Expand Down Expand Up @@ -68,6 +70,10 @@ public boolean equals(Object obj) {
return this.x == other.x && this.y == other.y && this.z == other.z;
}

public boolean minecraftEquals(SFVec3i vec) {
return asMinecraftLong() == vec.asMinecraftLong();
}

@Override
public int hashCode() {
if (!hashCodeSet) {
Expand All @@ -79,7 +85,12 @@ public int hashCode() {
}

public long asMinecraftLong() {
return VectorHelper.asLong(x, y, z);
if (!minecraftLongSet) {
minecraftLong = VectorHelper.asLong(x, y, z);
minecraftLongSet = true;
}

return minecraftLong;
}

public SFVec3i add(int x, int y, int z) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
*/
package com.soulfiremc.server.pathfinding.graph;

import com.soulfiremc.server.pathfinding.NodeState;
import com.soulfiremc.server.pathfinding.SFVec3i;
import com.soulfiremc.server.pathfinding.execution.WorldAction;
import com.soulfiremc.server.pathfinding.graph.actions.movement.ActionDirection;
import lombok.With;
Expand All @@ -26,4 +26,4 @@

@With
public record GraphInstructions(
NodeState node, ActionDirection moveDirection, double actionCost, List<WorldAction> actions) {}
SFVec3i blockPosition, int deltaUsableBlockItems, ActionDirection moveDirection, double actionCost, List<WorldAction> actions) {}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import com.soulfiremc.server.data.BlockState;
import com.soulfiremc.server.data.BlockType;
import com.soulfiremc.server.data.FluidType;
import com.soulfiremc.server.pathfinding.NodeState;
import com.soulfiremc.server.pathfinding.SFVec3i;
import com.soulfiremc.server.pathfinding.graph.actions.*;
import com.soulfiremc.server.pathfinding.graph.actions.movement.ActionDirection;
Expand Down Expand Up @@ -112,7 +111,7 @@ public boolean disallowedToBreakBlockType(BlockType blockType) {
return !pathConstraint.canBreakBlockType(blockType);
}

public void insertActions(NodeState node, ActionDirection fromDirection, Consumer<GraphInstructions> callback) {
public void insertActions(SFVec3i node, ActionDirection fromDirection, Consumer<GraphInstructions> callback) {
log.debug("Inserting actions for node: {}", node);
calculateActions(node, generateTemplateActions(fromDirection), callback);
}
Expand All @@ -132,7 +131,7 @@ private GraphAction[] generateTemplateActions(ActionDirection fromDirection) {
}

private void calculateActions(
NodeState node,
SFVec3i node,
GraphAction[] actions,
Consumer<GraphInstructions> callback) {
for (var i = 0; i < SUBSCRIPTION_KEYS.length; i++) {
Expand All @@ -141,7 +140,7 @@ private void calculateActions(
}

private void processSubscription(
NodeState node,
SFVec3i node,
GraphAction[] actions,
Consumer<GraphInstructions> callback,
int i) {
Expand All @@ -161,7 +160,7 @@ private void processSubscription(

if (blockState == null) {
// Lazy calculation to avoid unnecessary calls
absolutePositionBlock = node.blockPosition().add(key);
absolutePositionBlock = node.add(key);
blockState = blockAccessor.getBlockState(absolutePositionBlock);

if (pathConstraint.isOutOfLevel(blockState, absolutePositionBlock)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ public GraphInstructions modifyAsNeeded(GraphInstructions instruction) {
var addedPenalty = 0D;
for (var entity : unfriendlyEntities.get()) {
var followRange = entity.followRange;
var distance = instruction.node().blockPosition().distance(entity.entityPosition);
var distance = instruction.blockPosition().distance(entity.entityPosition);
if (distance <= followRange) {
addedPenalty += MAX_CLOSE_TO_ENEMY_PENALTY * (followRange - distance) / followRange;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@

import com.soulfiremc.server.data.BlockState;
import com.soulfiremc.server.pathfinding.Costs;
import com.soulfiremc.server.pathfinding.NodeState;
import com.soulfiremc.server.pathfinding.SFVec3i;
import com.soulfiremc.server.pathfinding.execution.BlockBreakAction;
import com.soulfiremc.server.pathfinding.graph.BlockFace;
Expand Down Expand Up @@ -93,7 +92,7 @@ private void registerCheckSafeMineBlocks(SubscriptionConsumer blockSubscribers)
}

@Override
public List<GraphInstructions> getInstructions(MinecraftGraph graph, NodeState node) {
public List<GraphInstructions> getInstructions(MinecraftGraph graph, SFVec3i node) {
if (closestBlockToFallOn == Integer.MIN_VALUE || closestObstructingBlock > closestBlockToFallOn) {
return Collections.emptyList();
}
Expand All @@ -110,10 +109,11 @@ public List<GraphInstructions> getInstructions(MinecraftGraph graph, NodeState n

cost += breakCost.miningCost();

var absoluteTargetFeetBlock = node.blockPosition().add(0, closestBlockToFallOn + 1, 0);
var absoluteTargetFeetBlock = node.add(0, closestBlockToFallOn + 1, 0);

return Collections.singletonList(new GraphInstructions(
new NodeState(absoluteTargetFeetBlock, node.usableBlockItems() + (breakCost.willDropUsableBlockItem() ? 1 : 0)),
absoluteTargetFeetBlock,
breakCost.willDropUsableBlockItem() ? 1 : 0,
actionDirection,
cost,
List.of(new BlockBreakAction(breakCost))));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
*/
package com.soulfiremc.server.pathfinding.graph.actions;

import com.soulfiremc.server.pathfinding.NodeState;
import com.soulfiremc.server.pathfinding.SFVec3i;
import com.soulfiremc.server.pathfinding.graph.GraphInstructions;
import com.soulfiremc.server.pathfinding.graph.MinecraftGraph;
import com.soulfiremc.server.pathfinding.graph.actions.movement.ActionDirection;
Expand All @@ -41,7 +41,7 @@ public boolean decrementAndIsDone() {
return --subscriptionCounter == 0;
}

public abstract List<GraphInstructions> getInstructions(MinecraftGraph graph, NodeState node);
public abstract List<GraphInstructions> getInstructions(MinecraftGraph graph, SFVec3i node);

public abstract GraphAction copy();
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@

import com.soulfiremc.server.data.BlockState;
import com.soulfiremc.server.pathfinding.Costs;
import com.soulfiremc.server.pathfinding.NodeState;
import com.soulfiremc.server.pathfinding.SFVec3i;
import com.soulfiremc.server.pathfinding.execution.GapJumpAction;
import com.soulfiremc.server.pathfinding.graph.GraphInstructions;
Expand Down Expand Up @@ -85,11 +84,12 @@ private void registerRequiredSolidBlock(SubscriptionConsumer blockSubscribers) {
}

@Override
public List<GraphInstructions> getInstructions(MinecraftGraph graph, NodeState node) {
var absoluteTargetFeetBlock = node.blockPosition().add(targetFeetBlock);
public List<GraphInstructions> getInstructions(MinecraftGraph graph, SFVec3i node) {
var absoluteTargetFeetBlock = node.add(targetFeetBlock);

return Collections.singletonList(new GraphInstructions(
new NodeState(absoluteTargetFeetBlock, node.usableBlockItems()),
absoluteTargetFeetBlock,
0,
actionDirection,
Costs.ONE_GAP_JUMP,
List.of(new GapJumpAction(absoluteTargetFeetBlock))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@

import com.soulfiremc.server.data.BlockState;
import com.soulfiremc.server.pathfinding.Costs;
import com.soulfiremc.server.pathfinding.NodeState;
import com.soulfiremc.server.pathfinding.SFVec3i;
import com.soulfiremc.server.pathfinding.execution.BlockBreakAction;
import com.soulfiremc.server.pathfinding.execution.BlockPlaceAction;
Expand Down Expand Up @@ -258,7 +257,7 @@ private void registerPossibleBlocksToPlaceAgainst(SubscriptionConsumer blockSubs
}

@Override
public List<GraphInstructions> getInstructions(MinecraftGraph graph, NodeState node) {
public List<GraphInstructions> getInstructions(MinecraftGraph graph, SFVec3i node) {
if (requiresAgainstBlock && blockPlaceAgainstData == null) {
return Collections.emptyList();
}
Expand All @@ -285,30 +284,25 @@ public List<GraphInstructions> getInstructions(MinecraftGraph graph, NodeState n
}
}

var absoluteTargetFeetBlock = node.blockPosition().add(targetFeetBlock);
var afterBreakUsableBlockItems = node.usableBlockItems() + usableBlockItemsDiff;
var absoluteTargetFeetBlock = node.add(targetFeetBlock);

if (requiresAgainstBlock) {
if (afterBreakUsableBlockItems < 1) {
// Not enough blocks to place below us
return Collections.emptyList();
} else {
if (graph.doUsableBlocksDecreaseWhenPlaced()) {
// After the place we'll have one less usable block item
afterBreakUsableBlockItems--;
}

cost += Costs.PLACE_BLOCK_PENALTY;
if (graph.doUsableBlocksDecreaseWhenPlaced()) {
// After the place we'll have one less usable block item
usableBlockItemsDiff--;
}

cost += Costs.PLACE_BLOCK_PENALTY;

var floorBlock = absoluteTargetFeetBlock.sub(0, 1, 0);
actions.add(new BlockPlaceAction(floorBlock, blockPlaceAgainstData));
}

actions.add(new MovementAction(absoluteTargetFeetBlock, diagonal));

return Collections.singletonList(new GraphInstructions(
new NodeState(absoluteTargetFeetBlock, afterBreakUsableBlockItems),
absoluteTargetFeetBlock,
usableBlockItemsDiff,
actionDirection,
cost,
actions
Expand Down
Loading

0 comments on commit 4a83061

Please sign in to comment.