use crate::common::{System, SystemSerde, TreeNode}; use crate::edsm::build_index; use crate::ship::Ship; use core::cmp::Ordering; use crossbeam_channel::{bounded, unbounded, Receiver, SendError, Sender, TryIter}; use derivative::Derivative; use dict_derive::IntoPyObject; use fnv::{FnvHashMap, FnvHashSet}; use humantime::format_duration; use permutohedron::LexicalPermutation; use pyo3::prelude::*; use rstar::{PointDistance, RStarInsertionStrategy, RTree, RTreeObject, RTreeParams, AABB}; use sha3::{Digest, Sha3_256}; use std::collections::VecDeque; use std::fs::File; use std::hash::{Hash, Hasher}; use std::io::{BufReader, BufWriter, Write}; use std::path::PathBuf; use std::sync::{Arc, Mutex}; use std::thread; use std::thread::JoinHandle; use std::time::Instant; const STATUS_INVERVAL: u128 = 5000; //ms struct Weight { dist_from_start: f32, dist_to_goal: f32, dist_to_point: Vec<(f32, [f32; 3])>, } impl Weight { fn calc(&self, node: &TreeNode, dst: &TreeNode, src: &TreeNode) -> f32 { let d_total = dist(&src.pos, &dst.pos); let d_start = (dist(&node.pos, &src.pos) / d_total) * self.dist_from_start; let d_goal = (dist(&node.pos, &dst.pos) / d_total) * self.dist_to_goal; let points: f32 = self .dist_to_point .iter() .map(|&(f, p)| dist(&p, &node.pos) * f) .sum(); return d_start + d_goal + points; } } #[derive(Debug, Clone, IntoPyObject)] pub struct SearchState { pub mode: String, pub system: String, pub body: String, pub from: String, pub to: String, pub depth: usize, pub queue_size: usize, pub d_rem: f32, pub d_total: f32, pub prc_done: f32, pub n_seen: usize, pub prc_seen: f32, } pub struct RouteOpts { pub range: Option, pub file_path: PathBuf, pub precomp_file: Option, pub precompute: bool, pub permute: bool, pub primary: bool, pub keep_first: bool, pub keep_last: bool, pub factor: Option, pub mode: Mode, pub systems: Vec, pub callback: Box PyResult>, pub workers: usize, } #[derive(Debug)] #[allow(non_camel_case_types)] pub enum Mode { BFS, BiDir, // TODO: implement bidirectional BFS Greedy, AStar, Shortest, // TODO: A-Star with distance as weight } #[derive(Debug)] #[allow(non_camel_case_types)] pub enum PrecomputeMode { Full, Route_From, Route_To, None, } impl Mode { pub fn parse(s: &str) -> Result { let s = s.to_lowercase(); match s.as_ref() { "bfs" => Ok(Mode::BFS), "greedy" => Ok(Mode::Greedy), "astar" => Ok(Mode::AStar), "bidir" => Ok(Mode::BiDir), val => Err(format!("Invalid Mode: {}", val)), } } } fn dist2(p1: &[f32; 3], p2: &[f32; 3]) -> f32 { let dx = p1[0] - p2[0]; let dy = p1[1] - p2[1]; let dz = p1[2] - p2[2]; dx * dx + dy * dy + dz * dz } fn dist(p1: &[f32; 3], p2: &[f32; 3]) -> f32 { dist2(p1, p2).sqrt() } fn fcmp(a: f32, b: f32) -> Ordering { match (a, b) { (x, y) if x.is_nan() && y.is_nan() => Ordering::Equal, (x, _) if x.is_nan() => Ordering::Greater, (_, y) if y.is_nan() => Ordering::Less, (..) => a.partial_cmp(&b).unwrap(), } } impl System { pub fn dist2(&self, p: &[f32; 3]) -> f32 { dist2(&self.pos, p) } pub fn distp(&self, p: &System) -> f32 { dist(&self.pos, &p.pos) } pub fn distp2(&self, p: &System) -> f32 { self.dist2(&p.pos) } } impl TreeNode { pub fn dist2(&self, p: &[f32; 3]) -> f32 { dist2(&self.pos, p) } pub fn distp(&self, p: &System) -> f32 { dist(&self.pos, &p.pos) } } impl PartialEq for System { fn eq(&self, other: &Self) -> bool { self.id == other.id } } impl Eq for System {} impl Hash for System { fn hash(&self, state: &mut H) { self.id.hash(state); } } impl RTreeObject for TreeNode { type Envelope = AABB<[f32; 3]>; fn envelope(&self) -> Self::Envelope { AABB::from_point(self.pos) } } impl PointDistance for TreeNode { fn distance_2(&self, point: &[f32; 3]) -> f32 { self.dist2(&point) } } fn hash_file(path: &PathBuf) -> Vec { let mut hash_reader = BufReader::new(File::open(path).unwrap()); let mut hasher = Sha3_256::new(); std::io::copy(&mut hash_reader, &mut hasher).unwrap(); hasher.finalize().iter().copied().collect() } pub struct LineCache { cache: Vec, reader: csv::Reader, } impl LineCache { pub fn new(path: &PathBuf) -> Option>> { //TODO: Verify match between index file and csv file let idx_path = path.with_extension("idx"); if !idx_path.exists() { eprintln!("No index found for {:?}, building...", path); build_index(path) .map_err(|e| { eprintln!("Error creating index for {:?}: {}", path, e); }) .ok()?; } let cache = bincode::deserialize_from(&mut BufReader::new( File::open(idx_path) .map_err(|e| { eprintln!("Error opening index for {:?}: {}", path, e); }) .ok()?, )) .map_err(|e| { eprintln!("Reading index for {:?}: {}", path, e); }) .ok()?; let reader = csv::Reader::from_path(path).ok()?; let ret = Self { reader, cache }; Some(Arc::new(Mutex::new(ret))) } fn read_sys(&mut self) -> Option { Some( self.reader .deserialize() .next() .expect("Failed to read SystemSerde") .expect("Failed to parse SystemSerde"), ) } pub fn get(&mut self, id: u32) -> Option { let mut pos = csv::Position::new(); pos.set_byte(self.cache[id as usize]); self.reader.seek(pos).ok()?; self.read_sys().map(SystemSerde::build) } } pub struct LargeNodeParameters; impl RTreeParams for LargeNodeParameters { const MIN_SIZE: usize = 200; const MAX_SIZE: usize = 400; const REINSERTION_COUNT: usize = 100; type DefaultInsertionStrategy = RStarInsertionStrategy; } // Optional but helpful: Define a type alias for the new r-tree pub type LargeNodeRTree = RTree; #[derive(Debug, Clone)] struct WorkUnit { node: TreeNode, depth: usize, parent_id: Option, range: f32, } #[derive(Debug)] enum WorkerSet { Empty, Workers { handles: Vec>, tx: Sender>, rx: Receiver, }, } impl WorkerSet { fn new(tree: Arc>, num_workers: usize) -> Self { if num_workers == 0 { return WorkerSet::Empty; } let (jobs_tx, jobs_rx) = unbounded(); let (result_tx, result_rx) = bounded(100_000); let handles = (0..num_workers) .map(|_| { thread::spawn({ let rx = jobs_rx.clone(); let tx = result_tx.clone(); let tree = tree.clone(); move || { Self::work(&tree, rx, tx); } }) }) .collect(); return WorkerSet::Workers { handles, tx: jobs_tx, rx: result_rx, }; } fn work(tree: &LargeNodeRTree, rx: Receiver>, tx: Sender) { while let Ok(Some(unit)) = rx.recv() { let range = unit.range * unit.node.mult; tree.locate_within_distance(unit.node.pos, range * range) .cloned() .for_each(|nb| { let wu = WorkUnit { node: nb, depth: unit.depth + 1, parent_id: Some(unit.node.id), range: unit.range, }; tx.send(wu).unwrap(); }); } drop(tx); } fn resize(self, tree: Arc>, num: usize) -> Result { self.close()?; return Ok(WorkerSet::new(tree, num)); } // fn replace(self, tree: Arc>) -> Result { // let num=self.num(); // return self.resize(tree.clone(),num); // } fn close(self) -> Result<(), String> { if let WorkerSet::Workers { mut handles, tx, rx, } = self { let t_start = Instant::now(); loop { if rx.is_empty() && tx.is_empty() { break; } rx.try_iter().for_each(|_| {}); } for _ in &handles { match tx.send(None) { Ok(_) => {} Err(e) => { return Err(format!("{:?}", e)); } } } drop(tx); while let Some(handle) = handles.pop() { handle.join().unwrap(); } drop(rx); println!( "workerset cleared in {}", format_duration(t_start.elapsed()) ); } return Ok(()); } fn queue_size(&self) -> usize { match self { WorkerSet::Empty => 0, WorkerSet::Workers { rx, tx, .. } => tx.len() + rx.len(), } } fn queue_empty(&self) -> bool { return self.queue_size() == 0; } fn send(&self, wu: WorkUnit) -> Result<(), SendError>> { match self { WorkerSet::Empty => { panic!("send() on empty WorkerSet"); } WorkerSet::Workers { tx, .. } => { return tx.send(Some(wu)); } } } fn num(&self) -> usize { match self { WorkerSet::Empty => 1, WorkerSet::Workers { handles, .. } => handles.len(), } } fn is_empty(&self) -> bool { match self { WorkerSet::Empty => true, WorkerSet::Workers { handles, .. } => handles.len() == 0, } } // impl Iterator fn iter(&self) -> Result, String> { match self { WorkerSet::Empty => Err("can't iterate on empty WorkerSet".to_string()), WorkerSet::Workers { rx, .. } => Ok(rx.try_iter()), } } // fn join(mut self) -> thread::Result<()> { // drop(self.tx); // drop(self.rx); // let ret: thread::Result> = self.handles.drain(..).map(|v| v.join()).collect(); // ret?; // return Ok(()); // } } #[derive(Derivative)] #[derivative(Debug)] pub struct Router { #[derivative(Debug = "ignore")] tree: Arc>, #[derivative(Debug = "ignore")] scoopable: FnvHashSet, #[derivative(Debug = "ignore")] pub route_tree: Option>, pub ship: Option, #[derivative(Debug = "ignore")] pub cache: Option>>, pub path: PathBuf, pub primary_only: bool, #[derivative(Debug = "ignore")] workers: WorkerSet, #[derivative(Debug = "ignore")] pub callback: Box PyResult>, } impl Router { pub fn new(callback: Box PyResult>) -> Self { Self { tree: Arc::new(LargeNodeRTree::default()), scoopable: FnvHashSet::default(), route_tree: None, cache: None, ship: None, callback, primary_only: false, workers: WorkerSet::Empty, path: PathBuf::from(""), } } pub fn load(&mut self, path: &PathBuf, primary_only: bool) -> Result<(), String> { if self.path == path.to_path_buf() && self.primary_only == primary_only { return Ok(()); } self.tree = Arc::new(LargeNodeRTree::default()); // clear R*-Tree let mut scoopable = FnvHashSet::default(); let mut reader = match csv::ReaderBuilder::new().from_path(path) { Ok(rdr) => rdr, Err(e) => { return Err(format!("Error opening {}: {}", path.display(), e)); } }; let t_load = Instant::now(); println!("Loading {}...", path.display()); let systems: Vec = reader .deserialize::() .filter_map(|res| { let sys = res.expect("Failed to read"); if primary_only && sys.distance != 0.0 { return None; } if sys.mult > 1.0f32 { scoopable.insert(sys.id); } else { for c in "KGBFOAM".chars() { if sys.star_type.starts_with(c) { scoopable.insert(sys.id); break; } } } Some(sys.to_node()) }) .collect(); println!( "{} Systems loaded in {}", systems.len(), format_duration(t_load.elapsed()) ); let t_load = Instant::now(); self.tree = Arc::new(LargeNodeRTree::bulk_load_with_params(systems)); self.path = path.to_path_buf(); self.cache = LineCache::new(&path.to_path_buf()); self.scoopable = scoopable; println!("R*-Tree built in {}", format_duration(t_load.elapsed())); Ok(()) } pub fn set_ship(&mut self, ship: Ship) { self.ship = Some(ship); } fn start_workers(&mut self, num: usize) -> Result<(), String> { let mut w = WorkerSet::Empty; std::mem::swap(&mut self.workers, &mut w); self.workers = w.resize(self.tree.clone(), num)?; Ok(()) } pub fn from_file( filename: &PathBuf, callback: Box PyResult>, ) -> Result<(PathBuf, f32, Self), String> { let mut reader = BufReader::new(match File::open(&filename) { Ok(fh) => fh, Err(e) => return Err(format!("Error opening file {}: {}", filename.display(), e)), }); println!("Loading {}", filename.display()); let (primary, range, file_hash, path, route_tree): ( bool, f32, Vec, PathBuf, FnvHashMap, ) = match bincode::deserialize_from(&mut reader) { Ok(res) => res, Err(e) => return Err(format!("Error loading file {}: {}", filename.display(), e)), }; if hash_file(&path) != file_hash { return Err("File hash mismatch!".to_string()); } let cache = LineCache::new(&path); Ok(( path.clone(), range, Self { tree: Arc::new(RTree::default()), scoopable: FnvHashSet::default(), route_tree: Some(route_tree), cache, path, callback, ship: None, primary_only: primary, workers: WorkerSet::Empty, }, )) } fn points_in_sphere(&self, center: &[f32; 3], radius: f32) -> impl Iterator { self.tree.locate_within_distance(*center, radius * radius) } fn neighbours(&self, node: &TreeNode, range: f32) -> impl Iterator { self.points_in_sphere(&node.pos, node.mult * range) } fn neighbours_r(&self, node: &TreeNode, range: f32) -> impl Iterator { let pos = node.pos; self.points_in_sphere(&node.pos, range * 4.0) .filter(move |s| { return s.dist2(&pos) < (range * s.mult) * (range * s.mult); }) } fn valid(&self, id: u32) -> bool { let scoopable = self.scoopable.contains(&id); return scoopable; } fn make_jump(&mut self, d: f32) -> Option { let ship = self.ship.as_mut().expect("Tried to jump without a ship!"); return ship.make_jump(d); } fn best_multiroute( &mut self, waypoints: &[System], range: f32, keep: (bool, bool), factor: f32, beam_width: usize, num_workers: usize, ) -> Result, String> { let mut best_score: f32 = std::f32::MAX; let mut waypoints = waypoints.to_owned(); let mut best_permutation_waypoints = waypoints.to_owned(); let first = waypoints.first().cloned(); let last = waypoints.last().cloned(); println!("Finding best permutation of hops..."); while waypoints.prev_permutation() {} loop { let c_first = waypoints.first().cloned(); let c_last = waypoints.last().cloned(); let valid = (keep.0 && (c_first == first)) && (keep.1 && (c_last == last)); if valid { let mut total_d = 0.0; for pair in waypoints.windows(2) { match pair { [src, dst] => { total_d += src.distp2(dst); } _ => return Err("Invalid routing parameters!".to_string()), } } if total_d < best_score { best_score = total_d; best_permutation_waypoints = waypoints.to_owned(); } } if !waypoints.next_permutation() { break; } } println!("Best permutation: {:?}", best_permutation_waypoints); self.multiroute( &best_permutation_waypoints, range, factor, beam_width, num_workers, ) } fn multiroute( &mut self, waypoints: &[System], range: f32, factor: f32, beam_width: usize, num_workers: usize, ) -> Result, String> { if self.tree.size() == 0 { return Err("No Systems loaded, pleased load some with the 'load' method!".to_string()); } if factor < 0.0 || factor > 1.0 { return Err("Factor needs to be between 0.0 (BFS) and 1.0 (Greedy Search)".to_string()); } let mut route: Vec = Vec::new(); if factor == 0.0 { self.start_workers(num_workers)?; } for pair in waypoints.windows(2) { match pair { [src, dst] => { let d_total = dist(&src.pos, &dst.pos); println!( "Plotting route from [{}] to [{}]...", src.system, dst.system ); println!( "Jump Range: {} Ly, Distance: {} Ly, Estimated Jumps: {}", range, d_total, d_total / range ); let block = self.route_astar(&src, &dst, factor, beam_width, range)?; if route.is_empty() { for sys in block.iter() { route.push(sys.clone()); } } else { for sys in block.iter().skip(1) { route.push(sys.clone()); } } } _ => { return Err("Invalid routing parameters!".to_owned()); } } } Ok(route) } fn route_astar( &self, src: &System, dst: &System, factor: f32, beam_width: usize, range: f32, ) -> Result, String> { if factor == 0.0 { return self.route_bfs(src, dst, range, beam_width); } if (factor - 1.0).abs() < 1e-3 { if beam_width != 0 { eprintln!("Usign greedy algorithm, ignorimg beam width!") } return self.route_greedy(src, dst, range); } let src_name = src.system.clone(); let dst_name = dst.system.clone(); let start_sys = src; let goal_sys = dst; let d_total = dist(&start_sys.pos, &goal_sys.pos); let mut d_rem = d_total; let mut state = SearchState { mode: "A-Star".into(), depth: 0, queue_size: 0, d_rem: d_total, d_total, prc_done: 0.0, n_seen: 0, prc_seen: 0.0, from: src_name.clone(), to: dst_name.clone(), body: start_sys.body.clone(), system: start_sys.system.clone(), }; let total = self.tree.size() as f32; let mut t_last = Instant::now(); let mut prev = FnvHashMap::default(); let mut seen: FnvHashMap = FnvHashMap::default(); let mut found = false; let mut queue: Vec<(usize, usize, TreeNode)> = Vec::new(); queue.push(( 0, // depth (start_sys.distp(goal_sys) / range) as usize, // h start_sys.to_node(), )); seen.insert(start_sys.id, 0.0); while !found { while let Some((depth, _, node)) = queue.pop() { if t_last.elapsed().as_millis() > STATUS_INVERVAL { let sys = node .get(&self) .unwrap_or_else(|| panic!("System-ID {} not found!", node.id)); t_last = Instant::now(); state.depth = depth; state.queue_size = queue.len(); state.prc_done = ((d_total - d_rem) * 100f32) / d_total; state.d_rem = d_rem; state.n_seen = seen.len(); state.prc_seen = ((seen.len() * 100) as f32) / total; state.body = sys.body.clone(); state.system = sys.system.clone(); match (self.callback)(&state) { Ok(_) => (), Err(e) => { return Err(format!("{:?}", e)); } }; } if node.id == goal_sys.id { queue.clear(); found = true; break; } let new_nodes: Vec<_> = self .neighbours(&node, range) .filter(|nb| (self.valid(nb.id) || (nb.id == goal_sys.id))) .filter(|nb| !seen.contains_key(&nb.id)) .map(|nb| { prev.insert(nb.id, node); let d_g = nb.distp(goal_sys); if d_g < d_rem { d_rem = d_g; } (depth + 1, (d_g / (range * 4.0)) as usize, *nb) }) .collect(); for node in new_nodes { seen.insert(node.2.id, 0.0); queue.push(node); } queue.sort_by(|b, a| { let (a_0, a_1) = (a.0 as f32, a.1 as f32); let (b_0, b_1) = (b.0 as f32, b.1 as f32); let v_a = a_0 * (1.0 - factor) + a_1 * factor; let v_b = b_0 * (1.0 - factor) + b_1 * factor; fcmp(v_a, v_b) }); // queue.reverse(); } if queue.is_empty() { break; } } println!(); println!(); if !found { return Err(format!("No route from {} to {} found!", src_name, dst_name)); } let mut v: Vec = Vec::new(); let mut curr_sys = goal_sys.clone(); loop { v.push(curr_sys.clone()); match prev.get(&curr_sys.id) { Some(sys) => curr_sys = sys.get(&self).unwrap(), None => { break; } } } v.reverse(); Ok(v) } fn route_greedy(&self, src: &System, dst: &System, range: f32) -> Result, String> { let src_name = src.system.clone(); let dst_name = dst.system.clone(); let start_sys = src; let goal_sys = dst; let d_total = dist(&start_sys.pos, &goal_sys.pos); let mut d_rem = d_total; let mut state = SearchState { mode: "Greedy".into(), depth: 0, queue_size: 0, d_rem: d_total, d_total, prc_done: 0.0, n_seen: 0, prc_seen: 0.0, from: src_name.clone(), to: dst_name.clone(), body: start_sys.body.clone(), system: start_sys.system.clone(), }; let total = self.tree.size() as f32; let mut t_last = Instant::now(); let mut prev = FnvHashMap::default(); let mut seen: FnvHashMap = FnvHashMap::default(); let mut found = false; let mut queue: Vec<(f32, usize, TreeNode)> = Vec::new(); queue.push((start_sys.distp(goal_sys), 0, start_sys.to_node())); seen.insert(start_sys.id, 0.0); while !found { while let Some((_, depth, node)) = queue.pop() { if t_last.elapsed().as_millis() > STATUS_INVERVAL { let sys = node .get(&self) .unwrap_or_else(|| panic!("System-ID {} does not exist!", &node.id)); t_last = Instant::now(); state.depth = depth; state.queue_size = queue.len(); state.prc_done = ((d_total - d_rem) * 100f32) / d_total; state.d_rem = d_rem; state.n_seen = seen.len(); state.prc_seen = ((seen.len() * 100) as f32) / total; state.body = sys.body.clone(); state.system = sys.system.clone(); match (self.callback)(&state) { Ok(_) => (), Err(e) => { return Err(format!("{:?}", e)); } }; } if node.id == goal_sys.id { queue.clear(); found = true; break; } let new_nodes: Vec<_> = self .neighbours(&node, range) .filter(|nb| (self.valid(nb.id) || (nb.id == goal_sys.id))) .filter(|nb| !seen.contains_key(&nb.id)) .map(|nb| { prev.insert(nb.id, node); let d_g = nb.distp(goal_sys); if d_g < d_rem { d_rem = d_g; } (d_g, depth + 1, *nb) }) .collect(); for node in new_nodes { seen.insert(node.2.id, 0.0); queue.push(node); } queue.sort_by(|a, b| fcmp(b.0, a.0).then(b.1.cmp(&a.1))); } if queue.is_empty() { break; } } if !found { return Err(format!("No route from {} to {} found!", src_name, dst_name)); } let mut v: Vec = Vec::new(); let mut curr_sys = goal_sys.clone(); loop { v.push(curr_sys.clone()); match prev.get(&curr_sys.id) { Some(sys) => curr_sys = sys.get(&self).unwrap(), None => { break; } } } v.reverse(); Ok(v) } fn precompute_all(&mut self, range: f32) -> Result<(), String> { // TODO: implement all-pairs shortest path based on optimized BFS unimplemented!(); } fn precompute_to(&mut self, dst: &System, range: f32) -> Result<(), String> { // TODO: -> precompute to unimplemented!(); } fn precompute(&mut self, src: &System, range: f32) -> Result<(), String> { // TODO: -> precompute from let total = self.tree.size() as f32; let t_start = Instant::now(); let mut prev = FnvHashMap::default(); let mut seen = FnvHashSet::default(); let mut depth = 0; let mut queue: VecDeque<(usize, TreeNode)> = VecDeque::new(); let mut queue_next: VecDeque<(usize, TreeNode)> = VecDeque::new(); queue.push_front((0, src.to_node())); seen.insert(src.id); while !queue.is_empty() { print!( "[{}] Depth: {}, Queue: {}, Seen: {} ({:.02}%) \r", format_duration(t_start.elapsed()), depth, queue.len(), seen.len(), ((seen.len() * 100) as f32) / total ); std::io::stdout().flush().unwrap(); while let Some((d, sys)) = queue.pop_front() { queue_next.extend( self.neighbours(&sys, range) // .filter(|&nb| self.valid(nb)) .filter(|&nb| seen.insert(nb.id)) .map(|nb| { prev.insert(nb.id, sys.id); (d + 1, *nb) }), ); } std::mem::swap(&mut queue, &mut queue_next); depth += 1; } self.route_tree = Some(prev); let file_hash = hash_file(&self.path); let file_hash_hex = file_hash .iter() .map(|v| format!("{:02x}", v)) .collect::>() .join(""); let ofn = format!( "{}_{}_{}.router", src.system.replace("*", "").replace(" ", "_"), range, file_hash_hex ); let mut out_fh = BufWriter::new(File::create(&ofn).unwrap()); let data = ( self.tree.size(), range, file_hash, self.path.clone(), self.route_tree.as_ref().unwrap(), ); match bincode::serialize_into(&mut out_fh, &data) { Ok(_) => Ok(()), Err(e) => Err(format!("Error: {}", e)), } } fn get_sys(&self, id: u32) -> Result, String> { let path = &self.path; if let Some(c) = &self.cache { if let Some(sys) = c.lock().unwrap().get(id) { return Ok(Some(sys)); }; } let mut reader = match csv::ReaderBuilder::new().from_path(path) { Ok(reader) => reader, Err(e) => { return Err(format!("Error opening {}: {}", path.display(), e)); } }; println!("Running serial search for ID: {:?}", id); return Ok(reader .deserialize::() .map(|res| res.unwrap()) .filter(|sys| sys.id == id) .map(|sys| sys.build()) .last()); } fn get_systems_by_ids(&self, ids: &[u32]) -> Result, String> { let path = &self.path; let mut ret = FnvHashMap::default(); if let Some(c) = &self.cache { let mut c = c.lock().unwrap(); let mut missing = false; for id in ids { match c.get(*id) { Some(sys) => { ret.insert(*id, sys); } None => { missing = true; break; } } } if !missing { return Ok(ret); } } let mut reader = match csv::ReaderBuilder::new().from_path(path) { Ok(reader) => reader, Err(e) => { return Err(format!("Error opening {}: {}", path.display(), e)); } }; println!("Running serial search for IDs: {:?}", ids); reader .deserialize::() .map(|res| res.unwrap()) .filter(|sys| ids.contains(&sys.id)) .for_each(|sys| { ret.insert(sys.id, sys.build()); }); for id in ids { if !ret.contains_key(&id) { return Err(format!("ID {} not found", id)); } } Ok(ret) } fn route_to(&self, dst: &System) -> Result, String> { if self.route_tree.is_none() { return Err("Can't computer route without a precomputed route-tree".to_owned()); } let prev = self.route_tree.as_ref().unwrap(); if !prev.contains_key(&dst.id) { return Err(format!("System-ID {} not found", dst.id)); }; let mut v_ids: Vec = Vec::new(); let mut v: Vec = Vec::new(); let mut curr_sys: u32 = dst.id; loop { v_ids.push(curr_sys); match prev.get(&curr_sys) { Some(sys_id) => curr_sys = *sys_id, None => { break; } } } v_ids.reverse(); let id_map = self.get_systems_by_ids(&v_ids)?; for sys_id in v_ids { let sys = match id_map.get(&sys_id) { Some(sys) => sys, None => { return Err(format!("System-ID {} not found!", sys_id)); } }; v.push(sys.clone()) } Ok(v) } fn route_bfs( &self, start_sys: &System, goal_sys: &System, range: f32, beam_width: usize, ) -> Result, String> { if self.workers.is_empty() { return self.route_bfs_serial(start_sys, goal_sys, range, beam_width); } println!("Running BFS with {} worker(s)", self.workers.num()); let t_start = Instant::now(); let mut t_last = Instant::now(); let mut prev = FnvHashMap::default(); let mut seen: FnvHashMap = FnvHashMap::default(); let src_name = start_sys.system.clone(); let dst_name = goal_sys.system.clone(); let workers = &self.workers; let wu = WorkUnit { node: start_sys.to_node(), parent_id: None, depth: 0, range, }; if wu.node.id == goal_sys.id { return Ok(vec![goal_sys.clone()]); } let mut found = false; let total = self.tree.size() as f32; let d_total = dist(&start_sys.pos, &goal_sys.pos); let mut d_rem = d_total; let mut state = SearchState { mode: format!("BFS_parallel({})", self.workers.num()), depth: 0, queue_size: 0, d_rem, d_total, prc_done: 0.0, n_seen: 0, prc_seen: 0.0, from: src_name.clone(), to: dst_name.clone(), system: start_sys.system.clone(), body: start_sys.body.clone(), }; seen.insert(wu.node.id, 0.0); workers.send(wu).unwrap(); loop { if found { break; } let num_seen = seen.len(); let mut nbs: Vec<_> = workers .iter()? .filter(|wu| !found && !seen.contains_key(&wu.node.id)) .filter(|wu| wu.parent_id.is_some()) .inspect(|wu| { if t_last.elapsed().as_millis() > STATUS_INVERVAL { let dist = wu.node.distp(goal_sys); if dist < d_rem { d_rem = dist; }; state.depth = wu.depth; state.queue_size = workers.queue_size(); state.prc_done = ((d_total - d_rem) * 100f32) / d_total; state.d_rem = d_rem; state.n_seen = num_seen; state.prc_seen = ((num_seen * 100) as f32) / total; { let s = wu.node.get(&self).unwrap(); state.system = s.system; state.body = s.body; } match (self.callback)(&state) { Ok(_) => (), Err(e) => { println!("CB_ERROR: {:?}", e); } }; t_last = Instant::now(); } }) .collect(); if nbs.is_empty() && workers.queue_empty() && seen.len() > 1 { break; } if beam_width != 0 && nbs.len() > beam_width { nbs.sort_by(|a, b| { let d_a = a.node.dist2(&goal_sys.pos); let d_b = b.node.dist2(&goal_sys.pos); return fcmp(d_a, d_b); }); nbs = nbs.iter().take(beam_width).cloned().collect(); } while let Some(wu) = nbs.pop() { if let Some(parent_id) = wu.parent_id { prev.insert(wu.node.id, parent_id); } seen.insert(wu.node.id, 0.0); if wu.node.id == goal_sys.id { found = true; println!("FOUND!"); break; } workers.send(wu).unwrap(); } } println!(); println!(); println!("Took: {}", format_duration(t_start.elapsed())); if !found { return Err(format!("No route from {} to {} found!", src_name, dst_name)); } let mut v: Vec = Vec::new(); let mut curr_sys = goal_sys.clone(); loop { v.push(curr_sys.clone()); match prev.get(&curr_sys.id) { Some(sys) => { curr_sys = self .get_sys(*sys)? .ok_or(format!("System id {} not found", sys))? } None => { break; } } } v.reverse(); Ok(v) } fn route_bfs_serial( &self, start_sys: &System, goal_sys: &System, range: f32, beam_width: usize, ) -> Result, String> { if start_sys.id == goal_sys.id { return Ok(vec![goal_sys.clone()]); } let t_start = Instant::now(); println!("Running BFS"); let src_name = start_sys.system.clone(); let dst_name = goal_sys.system.clone(); let d_total = dist(&start_sys.pos, &goal_sys.pos); let mut d_rem = d_total; let mut state = SearchState { mode: "BFS_serial".into(), depth: 0, queue_size: 0, d_rem, d_total, prc_done: 0.0, n_seen: 0, prc_seen: 0.0, from: src_name.clone(), to: dst_name.clone(), system: start_sys.system.clone(), body: start_sys.body.clone(), }; let total = self.tree.size() as f32; let mut prev = FnvHashMap::default(); let mut seen: FnvHashMap = FnvHashMap::default(); let mut depth = 0; let mut found = false; let mut t_last = Instant::now(); let mut queue: VecDeque = VecDeque::new(); let mut queue_next: VecDeque = VecDeque::new(); queue.push_front(start_sys.to_node()); seen.insert(start_sys.id, 0.0); while !found { while let Some(node) = queue.pop_front() { if node.id == goal_sys.id { queue.clear(); found = true; break; } if t_last.elapsed().as_millis() > STATUS_INVERVAL { state.depth = depth; state.queue_size = queue.len() + queue_next.len(); state.prc_done = ((d_total - d_rem) * 100f32) / d_total; state.d_rem = d_rem; state.n_seen = seen.len(); state.prc_seen = ((seen.len() * 100) as f32) / total; if !queue.is_empty() { let s = queue.get(0).unwrap().get(&self).unwrap(); state.system = s.system.clone(); state.body = s.body.clone(); } match (self.callback)(&state) { Ok(_) => (), Err(e) => { return Err(format!("{:?}", e)); } }; t_last = Instant::now(); } let valid_nbs = self .neighbours(&node, range) .filter(|nb| (self.valid(nb.id) || (nb.id == goal_sys.id))) .filter(|nb| seen.insert(nb.id, 0.0).is_none()) .map(|nb| { prev.insert(nb.id, node); let dist = nb.distp(goal_sys); if dist < d_rem { d_rem = dist; }; *nb }); queue_next.extend(valid_nbs); } if beam_width != 0 { let mut q = Vec::new(); while let Some(v) = queue_next.pop_front() { q.push(v); } q.sort_by(|a, b| { let d_a = a.dist2(&goal_sys.pos); let d_b = b.dist2(&goal_sys.pos); return fcmp(d_a, d_b); }); queue.clear(); for v in q.iter().take(beam_width).cloned() { queue.push_back(v); } queue_next.clear(); } else { std::mem::swap(&mut queue, &mut queue_next); } if found { break; } if queue.is_empty() { break; } depth += 1; } println!(); println!(); println!("Took: {}", format_duration(t_start.elapsed())); if !found { return Err(format!("No route from {} to {} found!", src_name, dst_name)); } let mut v: Vec = Vec::new(); let mut curr_sys = goal_sys.clone(); loop { v.push(curr_sys.clone()); match prev.get(&curr_sys.id) { Some(sys) => curr_sys = sys.get(&self).unwrap(), None => { break; } } } v.reverse(); Ok(v) } } impl Router { pub fn compute_route( &mut self, sys_ids: &[u32], range: Option, factor: f32, beam_width: usize, num_workers: usize, ) -> Result, String> { if range.is_none() && self.ship.is_none() { return Err("Need either a jump range or a ship to compute a route with!".to_owned()); } let range = range.ok_or("Dynamic range calculation is not yet implemented, sorry!")?; let id_map = self.get_systems_by_ids(sys_ids)?; let hops: Vec = sys_ids .iter() .map(|id| id_map.get(&id).unwrap()) .cloned() .collect(); self.multiroute(&hops, range, factor, beam_width, num_workers) } }