Skip to content

Commit 1a5a9b9

Browse files
committed
Lazy D* Lite
1 parent da0efc1 commit 1a5a9b9

File tree

9 files changed

+139
-135
lines changed

9 files changed

+139
-135
lines changed

common/src/main/kotlin/com/lambda/module/modules/movement/Pathfinder.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,12 @@ import com.lambda.pathing.Pathing.findPathAStar
3636
import com.lambda.pathing.Pathing.thetaStarClearance
3737
import com.lambda.pathing.PathingConfig
3838
import com.lambda.pathing.PathingSettings
39+
import com.lambda.pathing.dstar.DStarLite
40+
import com.lambda.pathing.dstar.LazyGraph
3941
import com.lambda.pathing.goal.SimpleGoal
42+
import com.lambda.pathing.move.MoveFinder.moveOptions
4043
import com.lambda.threading.runConcurrent
44+
import com.lambda.threading.runSafe
4145
import com.lambda.util.Communication.info
4246
import com.lambda.util.Formatting.string
4347
import com.lambda.util.math.setAlpha
@@ -162,7 +166,7 @@ object Pathfinder : Module(
162166
thetaStarClearance(long, pathing)
163167
} else long
164168
}
165-
info("A* (Length: ${long.length().string} Nodes: ${long.moves.size} T: $aStar ms) and Theta* (Length: ${short.length().string} Nodes: ${short.moves.size} T: $thetaStar ms)")
169+
info("A* (Length: ${long.length().string} Nodes: ${long.size} T: $aStar ms) and Theta* (Length: ${short.length().string} Nodes: ${short.size} T: $thetaStar ms)")
166170
println("Long: $long | Short: $short")
167171
short.moves.removeFirstOrNull()
168172
coarsePath = long
@@ -172,7 +176,7 @@ object Pathfinder : Module(
172176
}
173177
PathingConfig.PathingAlgorithm.D_STAR_LITE -> {
174178
runConcurrent {
175-
// 1. Build graph from goal to target
179+
176180
}
177181
}
178182
}

common/src/main/kotlin/com/lambda/pathing/Path.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ data class Path(
5252

5353
fun length() = length.value
5454

55+
val size get() = moves.size
56+
5557
override fun toString() =
5658
moves.joinToString(" -> ") { "(${it.pos.toBlockPos().toShortString()})" }
5759
}

common/src/main/kotlin/com/lambda/pathing/Pathing.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ object Pathing {
5656

5757
closedSet.add(current.pos)
5858

59-
moveOptions(current, goal, config).forEach { move ->
59+
moveOptions(current.pos, goal::heuristic, config).forEach { move ->
6060
// println("Considering move: $move")
6161
if (closedSet.contains(move.pos)) return@forEach
6262
val tentativeGCost = current.gCost + move.cost

common/src/main/kotlin/com/lambda/pathing/dstar/DStarLite.kt

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,19 +34,17 @@ import kotlin.math.min
3434
* @param goal The fixed goal vertex.
3535
*/
3636
class DStarLite(
37-
private val graph: Graph,
37+
private val graph: LazyGraph,
3838
private val heuristic: (FastVector, FastVector) -> Double,
3939
var start: FastVector,
4040
private val goal: FastVector
4141
) {
42-
private val INF = Double.POSITIVE_INFINITY
43-
4442
// gMap[u], rhsMap[u] store g(u) and rhs(u) or default to ∞ if not present
4543
private val gMap = mutableMapOf<FastVector, Double>()
4644
private val rhsMap = mutableMapOf<FastVector, Double>()
4745

4846
// Priority queue holding inconsistent vertices.
49-
private val U = PriorityQueueDStar()
47+
private val U = PriorityQueueDStar<FastVector>()
5048

5149
// km accumulates heuristic differences as the start changes.
5250
private var km = 0.0
@@ -193,4 +191,8 @@ class DStarLite(
193191
}
194192
return path
195193
}
194+
195+
companion object {
196+
private const val INF = Double.POSITIVE_INFINITY
197+
}
196198
}

common/src/main/kotlin/com/lambda/pathing/dstar/Graph.kt

Lines changed: 0 additions & 50 deletions
This file was deleted.
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright 2025 Lambda
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
18+
package com.lambda.pathing.dstar
19+
20+
import com.lambda.context.SafeContext
21+
import com.lambda.util.world.FastVector
22+
23+
/**
24+
* A 3D graph that uses FastVector (a Long) to represent 3D nodes.
25+
*
26+
* Runtime Complexity:
27+
* - successor(u): O(N), where N is the number of neighbors of node u, due to node initialization.
28+
* - predecessors(u): O(N), similar reasoning to successors(u).
29+
* - cost(u, v): O(1), constant time lookup after initialization.
30+
* - contains(u): O(1), hash map lookup.
31+
*
32+
* Space Complexity:
33+
* - O(V + E), where V = number of nodes (vertices) initialized and E = number of edges stored.
34+
* - Additional memory overhead is based on the dynamically expanding hash maps.
35+
*/
36+
37+
class LazyGraph(
38+
private val nodeInitializer: (FastVector) -> Map<FastVector, Double>
39+
) {
40+
private val successors = hashMapOf<FastVector, MutableMap<FastVector, Double>>()
41+
private val predecessors = hashMapOf<FastVector, MutableMap<FastVector, Double>>()
42+
43+
/** Initializes a node if not already initialized, then returns successors. */
44+
fun successors(u: FastVector) =
45+
successors.computeIfAbsent(u) {
46+
val neighbors = nodeInitializer(u)
47+
neighbors.forEach { (neighbor, cost) ->
48+
predecessors.computeIfAbsent(neighbor) { hashMapOf() }[u] = cost
49+
}
50+
neighbors.toMutableMap()
51+
}
52+
53+
/** Initializes predecessors by ensuring successors of neighboring nodes. */
54+
fun predecessors(u: FastVector): Map<FastVector, Double> {
55+
successors(u)
56+
return predecessors[u] ?: emptyMap()
57+
}
58+
59+
/** Returns the cost of the edge from u to v (or ∞ if none exists) */
60+
fun cost(u: FastVector, v: FastVector): Double = successors(u)[v] ?: Double.POSITIVE_INFINITY
61+
62+
fun contains(u: FastVector): Boolean = u in successors
63+
64+
fun clear() {
65+
successors.clear()
66+
predecessors.clear()
67+
}
68+
}

common/src/main/kotlin/com/lambda/pathing/dstar/PriorityQueueDStar.kt

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,14 @@
1717

1818
package com.lambda.pathing.dstar
1919

20-
import com.lambda.util.world.FastVector
2120
import java.util.*
2221

2322
/**
2423
* Priority queue for D* Lite 3D.
25-
* Supports: topKey, top, pop, insertOrUpdate, and remove.
2624
*/
27-
class PriorityQueueDStar {
28-
private val pq = PriorityQueue<Pair<FastVector, Key>>(compareBy { it.second })
29-
private val vertexToKey = mutableMapOf<FastVector, Key>()
25+
class PriorityQueueDStar<T : Any> {
26+
private val pq = PriorityQueue<Pair<T, Key>>(compareBy { it.second })
27+
private val vertexToKey = mutableMapOf<T, Key>()
3028

3129
fun isEmpty() = pq.isEmpty()
3230

@@ -35,16 +33,16 @@ class PriorityQueueDStar {
3533
else pq.peek().second
3634
}
3735

38-
fun top() = pq.peek()?.first
36+
fun top(): T? = pq.peek()?.first
3937

40-
fun pop(): FastVector? {
38+
fun pop(): T? {
4139
if (pq.isEmpty()) return null
4240
val (v, _) = pq.poll()
4341
vertexToKey.remove(v)
4442
return v
4543
}
4644

47-
fun insertOrUpdate(v: FastVector, key: Key) {
45+
fun insertOrUpdate(v: T, key: Key) {
4846
val oldKey = vertexToKey[v]
4947
if (oldKey == null || oldKey != key) {
5048
remove(v)
@@ -53,7 +51,7 @@ class PriorityQueueDStar {
5351
}
5452
}
5553

56-
fun remove(v: FastVector) {
54+
fun remove(v: T) {
5755
val oldKey = vertexToKey[v] ?: return
5856
vertexToKey.remove(v)
5957
pq.remove(Pair(v, oldKey))

common/src/main/kotlin/com/lambda/pathing/move/MoveFinder.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,25 +51,25 @@ import kotlin.reflect.KFunction1
5151
object MoveFinder {
5252
private val nodeTypeCache = HashMap<FastVector, NodeType>()
5353

54-
fun SafeContext.moveOptions(origin: Move, goal: Goal, config: PathingConfig) =
54+
fun SafeContext.moveOptions(origin: FastVector, heuristic: KFunction1<FastVector, Double>, config: PathingConfig) =
5555
EightWayDirection.entries.flatMap { direction ->
5656
(-1..1).mapNotNull { y ->
57-
getPathNode(goal::heuristic, origin, direction, y, config)
57+
getPathNode(heuristic, origin, direction, y, config)
5858
}
5959
}
6060

6161
private fun SafeContext.getPathNode(
6262
heuristic: KFunction1<FastVector, Double>,
63-
origin: Move,
63+
origin: FastVector,
6464
direction: EightWayDirection,
6565
height: Int,
6666
config: PathingConfig
6767
): Move? {
6868
val offset = fastVectorOf(direction.offsetX, height, direction.offsetZ)
6969
val diagonal = direction.ordinal.mod(2) == 1
70-
val checkingPos = origin.pos.add(offset)
70+
val checkingPos = origin.add(offset)
7171
val checkingBlockPos = checkingPos.toBlockPos()
72-
val originBlockPos = origin.pos.toBlockPos()
72+
val originBlockPos = origin.toBlockPos()
7373
if (!world.worldBorder.contains(checkingBlockPos)) return null
7474

7575
val nodeType = findPathType(checkingPos)

0 commit comments

Comments
 (0)