1
2
Fork 0
mirror of https://github.com/mat-1/azalea.git synced 2025-08-02 06:16:04 +00:00

remove normal d* lite

This commit is contained in:
mat 2022-10-09 00:23:16 -05:00
parent eb4ea3aecc
commit e1254c32b6
5 changed files with 161 additions and 514 deletions

120
Cargo.lock generated
View file

@ -116,7 +116,7 @@ dependencies = [
name = "azalea-auth"
version = "0.1.0"
dependencies = [
"azalea-buf",
"azalea-buf 0.1.0",
"uuid",
]
@ -125,7 +125,7 @@ name = "azalea-block"
version = "0.1.0"
dependencies = [
"azalea-block-macros",
"azalea-buf",
"azalea-buf 0.1.0",
]
[[package]]
@ -145,7 +145,21 @@ version = "0.1.0"
name = "azalea-buf"
version = "0.1.0"
dependencies = [
"azalea-buf-macros",
"azalea-buf-macros 0.1.0",
"byteorder",
"serde_json",
"thiserror",
"tokio",
"uuid",
]
[[package]]
name = "azalea-buf"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7b2b996a5642abaac7de05a9a1416c5fbaad28bdfe12d446f71a5f7a3075aa7"
dependencies = [
"azalea-buf-macros 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder",
"serde_json",
"thiserror",
@ -162,12 +176,36 @@ dependencies = [
"syn",
]
[[package]]
name = "azalea-buf-macros"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5d949019583d78ab38ca00305bdf7107a94a12496e5af2df45ebc6994a7fddd"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "azalea-chat"
version = "0.1.1"
dependencies = [
"azalea-buf",
"azalea-language",
"azalea-buf 0.1.0",
"azalea-language 0.1.0",
"lazy_static",
"serde",
"serde_json",
]
[[package]]
name = "azalea-chat"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ad571c2c007c414fdbc59becf49008ab28a745db55908169dcff3856cf6a04a"
dependencies = [
"azalea-buf 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"azalea-language 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static",
"serde",
"serde_json",
@ -180,8 +218,8 @@ dependencies = [
"anyhow",
"azalea-auth",
"azalea-block",
"azalea-chat",
"azalea-core",
"azalea-chat 0.1.1",
"azalea-core 0.1.0",
"azalea-crypto",
"azalea-physics",
"azalea-protocol",
@ -197,9 +235,21 @@ dependencies = [
name = "azalea-core"
version = "0.1.0"
dependencies = [
"azalea-buf",
"azalea-chat",
"azalea-nbt",
"azalea-buf 0.1.0",
"azalea-chat 0.1.1",
"azalea-nbt 0.1.0",
"uuid",
]
[[package]]
name = "azalea-core"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e49ec44a7bc0627d66e417b645b35d58c42313cdf827dee98981fc4f99e3fc97"
dependencies = [
"azalea-buf 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"azalea-chat 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"azalea-nbt 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"uuid",
]
@ -208,7 +258,7 @@ name = "azalea-crypto"
version = "0.1.0"
dependencies = [
"aes",
"azalea-buf",
"azalea-buf 0.1.0",
"cfb8",
"criterion",
"num-bigint",
@ -227,12 +277,23 @@ dependencies = [
"serde_json",
]
[[package]]
name = "azalea-language"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e54337d15218c60db4bfcf7e0af1247f3dfabccb908fc75424656120380be78b"
dependencies = [
"lazy_static",
"serde",
"serde_json",
]
[[package]]
name = "azalea-nbt"
version = "0.1.0"
dependencies = [
"ahash",
"azalea-buf",
"azalea-buf 0.1.0",
"byteorder",
"criterion",
"flate2",
@ -240,6 +301,20 @@ dependencies = [
"num-traits",
]
[[package]]
name = "azalea-nbt"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bfb3dfcfac6014fdb75e95d36760985633300f8246e646979050f762f88f61a"
dependencies = [
"ahash",
"azalea-buf 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder",
"flate2",
"num-derive",
"num-traits",
]
[[package]]
name = "azalea-pathfinder"
version = "0.1.0"
@ -248,6 +323,7 @@ dependencies = [
"async-trait",
"azalea",
"azalea-client",
"azalea-core 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num-traits",
"priority-queue",
]
@ -257,7 +333,7 @@ name = "azalea-physics"
version = "0.1.0"
dependencies = [
"azalea-block",
"azalea-core",
"azalea-core 0.1.0",
"azalea-world",
"lazy_static",
"uuid",
@ -272,11 +348,11 @@ dependencies = [
"azalea-auth",
"azalea-block",
"azalea-brigadier",
"azalea-buf",
"azalea-chat",
"azalea-core",
"azalea-buf 0.1.0",
"azalea-chat 0.1.1",
"azalea-core 0.1.0",
"azalea-crypto",
"azalea-nbt",
"azalea-nbt 0.1.0",
"azalea-protocol-macros",
"azalea-registry",
"azalea-world",
@ -308,7 +384,7 @@ dependencies = [
name = "azalea-registry"
version = "0.1.0"
dependencies = [
"azalea-buf",
"azalea-buf 0.1.0",
"azalea-registry-macros",
]
@ -326,10 +402,10 @@ name = "azalea-world"
version = "0.1.0"
dependencies = [
"azalea-block",
"azalea-buf",
"azalea-chat",
"azalea-core",
"azalea-nbt",
"azalea-buf 0.1.0",
"azalea-chat 0.1.1",
"azalea-core 0.1.0",
"azalea-nbt 0.1.0",
"azalea-registry",
"log",
"nohash-hasher",

View file

@ -10,5 +10,6 @@ anyhow = "1.0.65"
async-trait = "0.1.57"
azalea = {version = "0.1", path = "../azalea"}
azalea-client = { version = "0.1.0", path = "../azalea-client" }
azalea-core = "0.1.0"
num-traits = "0.2.15"
priority-queue = "1.2.3"

View file

@ -1,418 +0,0 @@
//! An implementation of D* Lite: second version (optimized version) as
//! described in <https://www.cs.cmu.edu/~maxim/files/dlite_tro05.pdf>
//!
//! Future optimization attempt ideas:
//! - Use a different priority queue (e.g. fibonacci heap)
//! - Use FxHash instead of the default hasher
//! - Have a `cost(a: Vertex, b: Vertex)` function instead of having the cost be stored in `Edge`
use priority_queue::PriorityQueue;
use std::error::Error;
use std::fmt::{Debug, Display, Formatter};
use std::{
borrow::Cow,
cmp,
collections::HashMap,
hash::Hash,
ops::{Add, Deref},
};
#[derive(Debug)]
pub struct VertexScore<W: Default + num_traits::Bounded + Debug> {
pub g: W,
pub rhs: W,
}
impl<W: Default + num_traits::Bounded + Debug> Default for VertexScore<W> {
fn default() -> Self {
Self {
g: W::max_value(),
rhs: W::max_value(),
}
}
}
/// The D* Lite pathfinding algorithm
pub struct DStarLite<
'a,
N: Eq + Hash + Clone,
W: PartialOrd + Eq + Default + Copy + num_traits::Bounded + Debug,
HeuristicFn: Fn(&N, &N) -> W,
SuccessorsFn: Fn(&N) -> Vec<EdgeTo<N, W>>,
PredecessorsFn: Fn(&N) -> Vec<EdgeTo<N, W>>,
> {
/// Rough estimate of how close we are to the goal. Lower = closer.
pub heuristic: HeuristicFn,
/// Get the nodes that can be reached from the current one
pub successors: SuccessorsFn,
/// Get the nodes that would direct us to the current node
pub predecessors: PredecessorsFn,
pub start: Cow<'a, N>,
start_last: Cow<'a, N>,
goal: N,
queue: PriorityQueue<N, Priority<W>>,
k_m: W,
vertex_scores: HashMap<N, VertexScore<W>>,
/// This is just here so we can reference it. It should never be modified.
default_score: VertexScore<W>,
/// A list of edges and costs that we'll be updating next time.
pub updated_edge_costs: Vec<(Edge<'a, N, W>, W)>,
}
pub struct Edge<'a, N: Eq + Hash + Clone, W: PartialOrd + Copy> {
pub predecessor: Cow<'a, N>,
pub successor: Cow<'a, N>,
pub cost: W,
}
pub struct EdgeTo<N: Eq + Hash + Clone, W: PartialOrd + Copy> {
pub target: N,
pub cost: W,
}
// rust does lexicographic ordering by default when we derive Ord
#[derive(Eq, PartialEq, Debug)]
pub struct Priority<W>(W, W)
where
W: PartialOrd + Debug;
impl<W: PartialOrd + Debug> PartialOrd for Priority<W> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
if self.0 < other.0 {
Some(std::cmp::Ordering::Less)
} else if self.0 > other.0 {
Some(std::cmp::Ordering::Greater)
} else if self.1 < other.1 {
Some(std::cmp::Ordering::Less)
} else if self.1 > other.1 {
Some(std::cmp::Ordering::Greater)
} else {
Some(std::cmp::Ordering::Equal)
}
}
}
impl<W: PartialOrd + Debug + Eq> Ord for Priority<W> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.partial_cmp(other)
.expect("Partial compare should not fail for Priority")
}
}
#[derive(Debug)]
pub struct NoPathError;
impl Error for NoPathError {}
impl Display for NoPathError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "No path found")
}
}
impl<
'a,
N: Eq + Hash + Clone + Debug,
W: PartialOrd + Eq + Add<Output = W> + Default + Copy + num_traits::bounds::Bounded + Debug,
HeuristicFn: Fn(&N, &N) -> W,
SuccessorsFn: Fn(&N) -> Vec<EdgeTo<N, W>>,
PredecessorsFn: Fn(&N) -> Vec<EdgeTo<N, W>>,
> DStarLite<'a, N, W, HeuristicFn, SuccessorsFn, PredecessorsFn>
{
fn score(&self, node: &N) -> &VertexScore<W> {
self.vertex_scores.get(node).unwrap_or(&self.default_score)
}
fn score_mut(&mut self, node: &N) -> &mut VertexScore<W> {
self.vertex_scores.entry(node.clone()).or_default()
}
fn calculate_key(&self, s: &N) -> Priority<W> {
let s_score = self.score(s);
let min_score = if s_score.g < s_score.rhs {
s_score.g
} else {
s_score.rhs
};
Priority(
if min_score == W::max_value() {
min_score
} else {
min_score + (self.heuristic)(&self.start, s) + self.k_m
},
min_score,
)
}
pub fn new(
start: N,
goal: N,
heuristic: HeuristicFn,
successors: SuccessorsFn,
predecessors: PredecessorsFn,
) -> Self {
let mut queue = PriorityQueue::with_capacity(1);
// Vertex<N, W>, Priority<W>
let mut vertex_scores = HashMap::new();
vertex_scores.insert(
goal.clone(),
VertexScore {
g: W::max_value(),
rhs: W::default(),
},
);
queue.push(
goal.clone(),
Priority(heuristic(&start, &goal), W::default()),
);
let mut s = Self {
start: Cow::Owned(start.clone()),
start_last: Cow::Owned(start),
goal,
heuristic,
successors,
predecessors,
default_score: VertexScore::default(),
queue,
k_m: W::default(),
vertex_scores,
updated_edge_costs: Vec::new(),
};
s.compute_shortest_path();
s
}
pub fn update_vertex(&mut self, u: &N) {
let VertexScore { g, rhs } = self.score(u);
// if(g(u)) != rhs(u) AND u is in U) U.Update(u, calculate_key(u))
if g != rhs && self.queue.get(u).is_some() {
self.queue.change_priority(u, self.calculate_key(u));
} else if g != rhs && self.queue.get(u).is_none() {
self.queue.push(u.clone(), self.calculate_key(u));
} else if g == rhs && self.queue.get(u).is_some() {
self.queue.remove(u);
}
}
fn compute_shortest_path(&mut self) {
while {
let score = self.score(&self.start);
if let Some(queue_top) = self.queue.peek() {
(queue_top.1 < &self.calculate_key(&self.start)) || (score.rhs > score.g)
} else {
false
}
} {
let (u, k_old) = self.queue.pop().unwrap();
let k_new = self.calculate_key(&u);
if k_old < k_new {
self.queue.change_priority(&u, k_new);
continue;
}
let u_score = self.score_mut(&u);
if u_score.g > u_score.rhs {
u_score.g = u_score.rhs;
let g_u = u_score.g;
self.queue.remove(&u);
for s in (self.predecessors)(&u) {
let target_score = self.score_mut(&s.target);
if s.cost + g_u < target_score.rhs {
target_score.rhs = s.cost + g_u;
}
// TODO: i think this can be moved up, but i'm not 100% sure it won't break anything
self.update_vertex(&s.target);
}
} else {
let g_old = u_score.g;
u_score.g = W::max_value();
for s in ((self.predecessors)(&u)).into_iter().chain(
[EdgeTo {
target: u,
cost: W::default(),
}]
.into_iter(),
) {
if self.score(&s.target).rhs == s.cost + g_old && s.target != self.goal {
let mut lowest_score = W::max_value();
for s_prime in (self.successors)(&s.target) {
let s_prime_score = s_prime.cost + self.score(&s_prime.target).g;
if s_prime_score < lowest_score {
lowest_score = s_prime_score;
}
}
self.score_mut(&s.target).rhs = lowest_score;
}
self.update_vertex(&s.target);
}
}
}
}
pub fn update_from_updated_edges(&mut self) {
self.k_m = self.k_m + (self.heuristic)(&self.start, &self.start_last);
self.start_last = self.start.clone();
while let Some((mut edge, new_cost)) = self.updated_edge_costs.pop() {
let old_cost = edge.cost;
edge.cost = new_cost;
let target_score = self.score_mut(&edge.successor);
if old_cost > new_cost {
if edge.cost + target_score.g < target_score.rhs {
target_score.rhs = edge.cost + target_score.g;
}
} else if target_score.rhs == old_cost + target_score.g {
let g_score = target_score.g;
if edge.successor.deref() != &self.goal {
let successors = (self.successors)(&edge.successor);
let mut lowest_score = W::max_value();
for s in successors {
let score = s.cost + g_score;
if score < lowest_score {
lowest_score = score;
}
}
self.score_mut(&edge.successor).rhs = lowest_score;
}
}
self.update_vertex(&edge.successor);
}
}
/// Return the next vertex to visit and set our current position to be there.
pub fn try_next(&mut self) -> Result<Option<&N>, NoPathError> {
if self.start.deref() == &self.goal {
return Ok(None);
}
let start_score = self.score(&self.start);
if start_score.rhs == W::max_value() {
return Err(NoPathError);
}
let get_score = |edge: &EdgeTo<N, W>| -> W {
let g_score = self.score(&edge.target).g;
if g_score == W::max_value() {
W::max_value()
} else {
edge.cost + g_score
}
};
*self.start.to_mut() = (self.successors)(&self.start)
.into_iter()
.min_by(|a, b| get_score(a).partial_cmp(&get_score(b)).unwrap())
.expect("No possible successors")
.target;
return Ok(Some(self.start.as_ref()));
}
// /// Change our current position.
// pub fn set_start(&mut self, s: Vertex<N, W>) {
// *self.start.to_mut() = s;
// }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dstarlite() {
let maze = [
[0, 1, 0, 0, 0],
[0, 1, 0, 1, 0],
[0, 0, 0, 1, 0],
[0, 1, 0, 1, 0],
[0, 0, 1, 0, 0],
];
let width = maze[0].len();
let height = maze.len();
fn heuristic(a: &(usize, usize), b: &(usize, usize)) -> usize {
((a.0 as isize - b.0 as isize).abs() + (a.1 as isize - b.1 as isize).abs()) as usize
}
let successors = |a: &(usize, usize)| -> Vec<EdgeTo<(usize, usize), usize>> {
let mut successors = Vec::with_capacity(4);
let (x, y) = *a;
if x > 0 && maze[y][x - 1] == 0 {
successors.push(EdgeTo {
target: ((x - 1, y)),
cost: 1,
});
}
if x < width - 1 && maze[y][x + 1] == 0 {
successors.push(EdgeTo {
target: ((x + 1, y)),
cost: 1,
});
}
if y > 0 && maze[y - 1][x] == 0 {
successors.push(EdgeTo {
target: ((x, y - 1)),
cost: 1,
});
}
if y < height - 1 && maze[y + 1][x] == 0 {
successors.push(EdgeTo {
target: ((x, y + 1)),
cost: 1,
});
}
successors
};
let predecessors = |a: &(usize, usize)| -> Vec<EdgeTo<(usize, usize), usize>> {
let mut predecessors = Vec::with_capacity(4);
let (x, y) = *a;
if x > 0 && maze[y][x - 1] == 0 {
predecessors.push(EdgeTo {
target: ((x - 1, y)),
cost: 1,
});
}
if x < width - 1 && maze[y][x + 1] == 0 {
predecessors.push(EdgeTo {
target: ((x + 1, y)),
cost: 1,
});
}
if y > 0 && maze[y - 1][x] == 0 {
predecessors.push(EdgeTo {
target: ((x, y - 1)),
cost: 1,
});
}
if y < height - 1 && maze[y + 1][x] == 0 {
predecessors.push(EdgeTo {
target: ((x, y + 1)),
cost: 1,
});
}
predecessors
};
let mut dstar = DStarLite::new((0, 0), (4, 4), heuristic, successors, predecessors);
assert!(dstar.try_next().unwrap() == Some(&(0, 1)));
assert!(dstar.try_next().unwrap() == Some(&(0, 2)));
assert!(dstar.try_next().unwrap() == Some(&(1, 2)));
assert!(dstar.try_next().unwrap() == Some(&(2, 2)));
assert!(dstar.try_next().unwrap() == Some(&(2, 1)));
assert!(dstar.try_next().unwrap() == Some(&(2, 0)));
assert!(dstar.try_next().unwrap() == Some(&(3, 0)));
assert!(dstar.try_next().unwrap() == Some(&(4, 0)));
assert!(dstar.try_next().unwrap() == Some(&(4, 1)));
assert!(dstar.try_next().unwrap() == Some(&(4, 2)));
assert!(dstar.try_next().unwrap() == Some(&(4, 3)));
assert!(dstar.try_next().unwrap() == Some(&(4, 4)));
assert!(dstar.try_next().unwrap() == None);
}
}

View file

@ -1,45 +1,49 @@
#![feature(let_chains)]
mod dstarlite;
mod mtdstarlite;
use async_trait::async_trait;
use azalea::{Client, Event};
pub use dstarlite::DStarLite;
use azalea_core::BlockPos;
pub use mtdstarlite::MTDStarLite;
use std::sync::{Arc, Mutex};
// #[derive(Default)]
// pub struct Plugin {
// pub state: Arc<Mutex<State>>,
// }
#[derive(Default)]
pub struct Plugin {
pub state: Arc<Mutex<State>>,
}
// #[derive(Default)]
// pub struct State {
// // pathfinder: Option<DStarLite< Node, FloatOrd<f32>>>,
// }
#[derive(Default)]
pub struct State {
// pathfinder: Option<MTDStarLite<Node, f32>>,
}
// #[async_trait]
// impl azalea::Plugin for Plugin {
// async fn handle(self: Arc<Self>, bot: Client, event: Arc<Event>) {
// // match *
// }
// }
#[async_trait]
impl azalea::Plugin for Plugin {
async fn handle(self: Arc<Self>, bot: Client, event: Arc<Event>) {
// match *
}
}
// pub trait Trait {
// fn goto(&self, goal: impl Goal);
// }
pub trait Trait {
fn goto(&self, goal: impl Goal);
}
// impl Trait for azalea_client::Client {
// fn goto(&self, goal: impl Goal) {
// let start = BlockPos::from(self.position());
impl Trait for azalea_client::Client {
fn goto(&self, goal: impl Goal) {
// let start = Node {
// pos: BlockPos::from(self.position()),
// };
// let pf = DStarLite::new();
// }
// }
// let pf = MTDStarLite::new(start);
}
}
// // fn heuristt
pub struct Node {
pub pos: BlockPos,
}
// pub trait Goal {
// fn heuristic(&self, x: i32, y: i32) -> f32;
// }
pub trait Goal {
fn heuristic(&self, x: i32, y: i32, z: i32) -> f32;
fn success(&self, x: i32, y: i32, z: i32) -> bool;
}

View file

@ -9,12 +9,7 @@
//! - Store edge costs in their own map
use priority_queue::DoublePriorityQueue;
use std::{
collections::HashMap,
fmt::Debug,
hash::Hash,
ops::{Add, Sub},
};
use std::{collections::HashMap, fmt::Debug, hash::Hash, ops::Add};
/// Nodes are coordinates.
pub struct MTDStarLite<
@ -34,12 +29,6 @@ pub struct MTDStarLite<
start: N,
goal: N,
// TODO: these are only used because the paper does it like this
// we should get rid of these and only rely on `start` and `goal` in the
// future
pub new_start: N,
pub new_goal: N,
old_start: N,
old_goal: N,
@ -93,9 +82,6 @@ impl<
start,
goal,
new_start: start,
new_goal: goal,
old_start: start,
old_goal: goal,
@ -149,7 +135,6 @@ impl<
let u = self.state_mut(&u_node);
if u.g > u.rhs {
u.g = u.rhs;
let u = self.state(&u_node);
self.open.remove(&u_node);
for edge in (self.successors)(&u_node) {
let s_node = edge.target;
@ -207,34 +192,7 @@ impl<
return None;
}
self.old_start = self.start;
self.old_goal = self.goal;
self.compute_cost_minimal_path();
if self.state(&self.goal).rhs == W::max_value() {
// no path exists
return None;
}
let mut reverse_path = vec![self.goal];
// identify a path from sstart to sgoal using the parent pointers
let mut target = self.state(&self.goal).par;
while !(Some(self.start) == target) && let Some(this_target) = target {
// hunter follows path from self.start to self.goal;
reverse_path.push(this_target);
target = self.state(&this_target).par;
}
// if hunter caught target {
// return None;
// }
let path: Vec<N> = reverse_path.into_iter().rev().collect();
self.start = self.new_start;
self.goal = self.new_goal;
//
self.k_m = self.k_m + (self.heuristic)(&self.goal, &self.old_goal);
if self.old_start != self.start {
@ -274,6 +232,32 @@ impl<
self.update_state(&v_node);
}
}
//
self.old_start = self.start;
self.old_goal = self.goal;
self.compute_cost_minimal_path();
if self.state(&self.goal).rhs == W::max_value() {
// no path exists
return None;
}
let mut reverse_path = vec![self.goal];
// identify a path from sstart to sgoal using the parent pointers
let mut target = self.state(&self.goal).par;
while !(Some(self.start) == target) && let Some(this_target) = target {
// hunter follows path from self.start to self.goal;
reverse_path.push(this_target);
target = self.state(&this_target).par;
}
// if hunter caught target {
// return None;
// }
let path: Vec<N> = reverse_path.into_iter().rev().collect();
Some(path)
}
@ -378,7 +362,7 @@ mod tests {
use super::*;
#[test]
fn test_dstarlite() {
fn test_mtdstarlite() {
let maze = [
[0, 1, 0, 0, 0],
[0, 1, 0, 1, 0],