diff --git a/factorio-blueprint-generator/src/bin/generate_factory.rs b/factorio-blueprint-generator/src/bin/generate_factory.rs index d166fd0..d17e546 100644 --- a/factorio-blueprint-generator/src/bin/generate_factory.rs +++ b/factorio-blueprint-generator/src/bin/generate_factory.rs @@ -4,8 +4,9 @@ use factorio_blueprint_generator::factory::{FactoryGraph, generate_factory}; use factorio_core::{prelude::*, visualize::Visualize}; use factorio_graph::{ priority_queue::{ - PriorityQueue, + ByKey, PriorityQueue, binary_heap::{BinaryHeap, FastBinaryHeap}, + bucket_queue::BucketQueue, }, wheighted_graph::shortest_path::QueueObject, }; @@ -19,7 +20,7 @@ use tracing_subscriber::{EnvFilter, fmt::format::FmtSpan}; #[derive(Parser)] struct Args { - #[arg(default_value_t = 0)] + #[arg(short, long, default_value_t = 0)] seed: u64, #[arg(short, long)] json: bool, @@ -44,6 +45,7 @@ enum Tracing { enum PathfinderArg { CaFbh, CaBh, + CaBq, } fn main() { @@ -92,7 +94,7 @@ where let p = ConflictAvoidance { timeout: Some(std::time::Duration::from_millis(100)), priority_queue: std::marker::PhantomData::< - FastBinaryHeap>, + FastBinaryHeap>>, >, }; execute::<_, _>(args, l, p); @@ -101,7 +103,16 @@ where let p = ConflictAvoidance { timeout: Some(std::time::Duration::from_millis(100)), priority_queue: std::marker::PhantomData::< - BinaryHeap>, + BinaryHeap>>, + >, + }; + execute::<_, _>(args, l, p); + } + PathfinderArg::CaBq => { + let p = ConflictAvoidance { + timeout: Some(std::time::Duration::from_millis(100)), + priority_queue: std::marker::PhantomData::< + BucketQueue>, >, }; execute::<_, _>(args, l, p); diff --git a/factorio-graph/src/priority_queue/bucket_queue.rs b/factorio-graph/src/priority_queue/bucket_queue.rs new file mode 100644 index 0000000..fd72a75 --- /dev/null +++ b/factorio-graph/src/priority_queue/bucket_queue.rs @@ -0,0 +1,147 @@ +use std::collections::{HashMap, VecDeque}; + +use super::{PriorityQueue, PriorityQueueByKey}; + +pub struct BucketQueue { + min: i64, + data: VecDeque>, + map: HashMap, + nextfree: usize, +} + +impl std::fmt::Debug for BucketQueue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + struct DebugWrapper2<'a, I>(&'a Vec<(usize, I)>); + impl<'a, I> std::fmt::Debug for DebugWrapper2<'a, I> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_list().entries(self.0.iter().map(|v| v.0)).finish() + } + } + struct DebugWrapper<'a, I>(&'a VecDeque>); + impl<'a, I> std::fmt::Debug for DebugWrapper<'a, I> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_list() + .entries(self.0.iter().map(|v| DebugWrapper2(v))) + .finish() + } + } + f.debug_struct("BucketQueue") + .field("min", &self.min) + .field("data", &DebugWrapper::(&self.data)) + .field("map", &self.map) + .field("nextfree", &self.nextfree) + .finish() + } +} + +pub struct BucketQueueHandle(usize); + +impl PriorityQueueByKey for BucketQueue +where + for<'a> &'a Item: Into, +{ + type Handle = BucketQueueHandle; + + fn new() -> Self { + Self { + min: 0, + data: VecDeque::new(), + map: HashMap::new(), + nextfree: 0, + } + } + + fn insert(&mut self, item: Item) -> Self::Handle { + self.nextfree += 1; + let handle = BucketQueueHandle(self.nextfree - 1); + + self.insert_with_handle(item, &handle); + + handle + } + + fn pop_min(&mut self) -> Option { + if let Some(bucket) = self.data.get_mut(0) { + if let Some((i, item)) = bucket.pop() { + self.map.remove(&i); + Some(item) + } else { + self.data.pop_front(); + self.min += 1; + self.pop_min() + } + } else { + None + } + } + + fn decrease_key(&mut self, handle: &Self::Handle, f: impl Fn(&mut Item)) { + self.update_key(handle, f); + } + + fn increase_key(&mut self, handle: &Self::Handle, f: impl Fn(&mut Item)) { + self.update_key(handle, f); + } +} + +impl BucketQueue +where + for<'a> &'a Item: Into, +{ + fn get_index(&self, i: i64) -> i64 { + i - self.min + } + + fn insert_with_handle(&mut self, item: Item, handle: &BucketQueueHandle) { + let value = (&item).into(); + if self.data.is_empty() { + self.min = value; + self.map.insert(handle.0, (value, 0)); + self.data.push_back(vec![(handle.0, item)]); + } else { + let index = self.get_index(value); + + if index < 0 { + self.min = value; + for _ in 0..index.abs() { + self.data.push_front(vec![]); + } + + self.map.insert(handle.0, (value, 0)); + self.data.get_mut(0).unwrap().push((handle.0, item)); + } else { + while index as usize >= self.data.len() { + self.data.push_back(Vec::new()); + } + + self.map.insert( + handle.0, + (value, self.data.get(index as usize).unwrap().len()), + ); + self.data + .get_mut(index as usize) + .unwrap() + .push((handle.0, item)); + } + } + } + + fn remove(&mut self, handle: &BucketQueueHandle) -> Item { + let (i, o) = self.map.remove(&handle.0).unwrap(); + let index = self.get_index(i); + + let bucket = self.data.get_mut(index as usize).unwrap(); + let item = bucket.swap_remove(o).1; + + if let Some((swapped, _)) = bucket.get(o) { + self.map.insert(*swapped, (i, o)); + } + + item + } + fn update_key(&mut self, handle: &BucketQueueHandle, f: impl Fn(&mut Item)) { + let mut item = self.remove(handle); + f(&mut item); + self.insert_with_handle(item, handle); + } +} diff --git a/factorio-graph/src/priority_queue/mod.rs b/factorio-graph/src/priority_queue/mod.rs index 1d107a0..c59983e 100644 --- a/factorio-graph/src/priority_queue/mod.rs +++ b/factorio-graph/src/priority_queue/mod.rs @@ -1,4 +1,5 @@ pub mod binary_heap; +pub mod bucket_queue; pub mod fibonacci_heap; pub trait PriorityQueue @@ -17,6 +18,68 @@ where fn increase_key(&mut self, handle: &Self::Handle, f: impl Fn(&mut Item)); } +pub trait PriorityQueueByKey +where + for<'a> &'a Item: Into, +{ + type Handle; + fn new() -> Self; + + fn insert(&mut self, item: Item) -> Self::Handle; + fn pop_min(&mut self) -> Option; + fn decrease_key(&mut self, handle: &Self::Handle, f: impl Fn(&mut Item)); + fn increase_key(&mut self, handle: &Self::Handle, f: impl Fn(&mut Item)); +} + +#[derive(Debug, Clone, Copy)] +pub struct ByKey(Item); + +impl PartialEq for ByKey +where + for<'a> &'a Item: Into, +{ + fn eq(&self, other: &Self) -> bool { + (&self.0).into().eq(&(&other.0).into()) + } +} + +impl PartialOrd for ByKey +where + for<'a> &'a Item: Into, +{ + fn partial_cmp(&self, other: &Self) -> Option { + (&self.0).into().partial_cmp(&(&other.0).into()) + } +} + +impl PriorityQueueByKey for P +where + P: PriorityQueue>, + for<'a> &'a Item: Into, +{ + type Handle = P::Handle; + + fn new() -> Self { + P::new() + } + + fn insert(&mut self, item: Item) -> Self::Handle { + self.insert(ByKey(item)) + } + + fn pop_min(&mut self) -> Option { + self.pop_min().map(|r| r.0) + } + + fn decrease_key(&mut self, handle: &Self::Handle, f: impl Fn(&mut Item)) { + self.decrease_key(handle, |i| f(&mut i.0)); + } + + fn increase_key(&mut self, handle: &Self::Handle, f: impl Fn(&mut Item)) { + self.increase_key(handle, |i| f(&mut i.0)); + } +} + #[cfg(test)] mod tests { use super::PriorityQueue; diff --git a/factorio-graph/src/wheighted_graph/shortest_path.rs b/factorio-graph/src/wheighted_graph/shortest_path.rs index 538d470..a4981b0 100644 --- a/factorio-graph/src/wheighted_graph/shortest_path.rs +++ b/factorio-graph/src/wheighted_graph/shortest_path.rs @@ -2,7 +2,7 @@ use std::{collections::HashMap, fmt::Debug, hash::Hash, hash::Hasher}; use tracing::{field::Empty, trace_span}; -use crate::priority_queue::PriorityQueue; +use crate::priority_queue::{PriorityQueue, PriorityQueueByKey}; use super::WheightedGraph; @@ -18,6 +18,12 @@ impl QueueObject { } } +impl Into for &QueueObject { + fn into(self) -> i64 { + self.score + } +} + impl PartialOrd for QueueObject { fn partial_cmp(&self, other: &Self) -> Option { self.score.partial_cmp(&other.score) @@ -58,9 +64,8 @@ impl MapObject { pub fn dijkstra(graph: &G, start: G::Node, end: E) -> Option> where - P: PriorityQueue> + Debug, - P::Handle: Debug, - G::Node: Eq + Hash + Clone + Debug, + P: PriorityQueueByKey>, + G::Node: Eq + Hash + Clone, G: WheightedGraph, E: Fn(&'_ G::Node) -> bool, { @@ -159,9 +164,8 @@ impl i64> WheightedGraph for GraphWrapper< pub fn a_star(graph: &G, start: G::Node, end: E, dist: F) -> Option> where - P: PriorityQueue> + Debug, - P::Handle: Debug, - G::Node: Eq + Hash + Clone + Debug, + P: PriorityQueueByKey>, + G::Node: Eq + Hash + Clone, G: WheightedGraph, E: Fn(&'_ G::Node) -> bool, F: Fn(&G::Node) -> i64, diff --git a/factorio-graph/src/wheighted_graph/steiner_tree.rs b/factorio-graph/src/wheighted_graph/steiner_tree.rs index df9cc4a..2cd55ff 100644 --- a/factorio-graph/src/wheighted_graph/steiner_tree.rs +++ b/factorio-graph/src/wheighted_graph/steiner_tree.rs @@ -1,6 +1,6 @@ use tracing::trace_span; -use crate::priority_queue::PriorityQueue; +use crate::priority_queue::{PriorityQueue, PriorityQueueByKey}; use std::hash::Hash; use std::{collections::HashSet, fmt::Debug}; @@ -32,9 +32,8 @@ where pub fn takaheshi_matsuyama(graph: &G, nodes: &[G::Node]) -> Option>> where - P: PriorityQueue>> + Debug, - P::Handle: Debug, - G::Node: Eq + Hash + Clone + Debug, + P: PriorityQueueByKey>>, + G::Node: Eq + Hash + Clone, G: WheightedGraph, { let _complete_span = trace_span!("takaheshi_matsuyama").entered(); diff --git a/factorio-layout/src/bin/new_layout.rs b/factorio-layout/src/bin/new_layout.rs index 20b337f..13b7e71 100644 --- a/factorio-layout/src/bin/new_layout.rs +++ b/factorio-layout/src/bin/new_layout.rs @@ -3,7 +3,8 @@ use std::path::PathBuf; use clap::Parser; use factorio_core::prelude::{Direction, Position}; use factorio_graph::{ - priority_queue::binary_heap::FastBinaryHeap, wheighted_graph::shortest_path::QueueObject, + priority_queue::{ByKey, binary_heap::FastBinaryHeap}, + wheighted_graph::shortest_path::QueueObject, }; use factorio_layout::{Layouter, valid_layout::ValidLayout}; use factorio_pathfinding::belt_finding::ConflictAvoidance; @@ -36,7 +37,7 @@ fn main() { let p = ConflictAvoidance { timeout: Some(std::time::Duration::from_millis(5)), priority_queue: std::marker::PhantomData::< - FastBinaryHeap>, + FastBinaryHeap>>, >, }; diff --git a/factorio-pathfinding/src/belt_finding/mod.rs b/factorio-pathfinding/src/belt_finding/mod.rs index 974e4c5..e9b60bd 100644 --- a/factorio-pathfinding/src/belt_finding/mod.rs +++ b/factorio-pathfinding/src/belt_finding/mod.rs @@ -8,6 +8,7 @@ use crate::examples::HashMapMap; use factorio_core::misc::Map; use factorio_core::{prelude::*, visualize::Visualize}; use factorio_graph::priority_queue::PriorityQueue; +use factorio_graph::priority_queue::PriorityQueueByKey; use factorio_graph::wheighted_graph::WheightedGraph; use factorio_graph::wheighted_graph::shortest_path::QueueObject; use factorio_graph::wheighted_graph::shortest_path::a_star; @@ -26,8 +27,7 @@ pub struct ConflictAvoidance

{ impl

SinglePathfinder for ConflictAvoidance

where - P: PriorityQueue> + std::fmt::Debug, - P::Handle: std::fmt::Debug, + P: PriorityQueueByKey>, { fn find_paths( &self, @@ -280,8 +280,7 @@ impl WheightedGraph for MapInternal<'_> { impl Problem { pub fn find_path

(&mut self) -> bool where - P: PriorityQueue> + std::fmt::Debug, - P::Handle: std::fmt::Debug, + P: PriorityQueueByKey>, { let _span = span!(Level::TRACE, "find_path").entered(); for i in 0..self.start.len() {