Refactoring
Splite code into modules Merge ed_lrr and ed_lrr_pp into one binary and use subcommands (route and preprocess) Run clippy and fix warnings
This commit is contained in:
		
							parent
							
								
									bac5907f15
								
							
						
					
					
						commit
						5c3cdac88f
					
				
					 8 changed files with 1190 additions and 910 deletions
				
			
		
							
								
								
									
										1
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							|  | @ -6,3 +6,4 @@ | |||
| dumps/*.json | ||||
| plot.py | ||||
| *.tmp | ||||
| *.idx | ||||
|  |  | |||
							
								
								
									
										1
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
								
							|  | @ -174,6 +174,7 @@ name = "ed_lrr" | |||
| version = "0.1.0" | ||||
| dependencies = [ | ||||
|  "bincode 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "csv 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  |  | |||
|  | @ -6,6 +6,7 @@ edition = "2018" | |||
| repository = "https://gitlab.com/Earthnuker/ed_lrr.git" | ||||
| license = "WTFPL" | ||||
| 
 | ||||
| 
 | ||||
| [profile.release] | ||||
| # debug=true | ||||
| 
 | ||||
|  | @ -22,3 +23,4 @@ indicatif = "0.11.0" | |||
| fnv = "1.0.6" | ||||
| bincode = "1.1.4" | ||||
| sha3 = "0.8.2" | ||||
| byteorder = "1.3.2" | ||||
|  |  | |||
							
								
								
									
										38
									
								
								src/common.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/common.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,38 @@ | |||
| use serde::{Deserialize, Serialize}; | ||||
| #[derive(Debug, Clone, Serialize, Deserialize)] | ||||
| pub struct SystemSerde { | ||||
|     pub id: u32, | ||||
|     pub star_type: String, | ||||
|     pub system: String, | ||||
|     pub body: String, | ||||
|     pub mult: f32, | ||||
|     pub distance: u32, | ||||
|     pub x: f32, | ||||
|     pub y: f32, | ||||
|     pub z: f32, | ||||
| } | ||||
| 
 | ||||
| impl SystemSerde { | ||||
|     pub fn build(&self) -> System { | ||||
|         System { | ||||
|             id: self.id, | ||||
|             star_type: self.star_type.clone(), | ||||
|             system: self.system.clone(), | ||||
|             body: self.body.clone(), | ||||
|             mult: self.mult, | ||||
|             distance: self.distance, | ||||
|             pos: [self.x, self.y, self.z], | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Clone, Deserialize, Serialize)] | ||||
| pub struct System { | ||||
|     pub id: u32, | ||||
|     pub star_type: String, | ||||
|     pub system: String, | ||||
|     pub body: String, | ||||
|     pub mult: f32, | ||||
|     pub distance: u32, | ||||
|     pub pos: [f32; 3], | ||||
| } | ||||
							
								
								
									
										3
									
								
								src/lib.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/lib.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | |||
| pub mod common; | ||||
| pub mod preprocess; | ||||
| pub mod route; | ||||
							
								
								
									
										920
									
								
								src/main.rs
									
										
									
									
									
								
							
							
						
						
									
										920
									
								
								src/main.rs
									
										
									
									
									
								
							|  | @ -1,926 +1,28 @@ | |||
| use core::cmp::Ordering; | ||||
| use fnv::{FnvHashMap, FnvHashSet}; | ||||
| use ed_lrr::preprocess::{preprocess_files, PreprocessOpts}; | ||||
| use ed_lrr::route::{route, RouteOpts}; | ||||
| use humantime::format_duration; | ||||
| use permutohedron::LexicalPermutation; | ||||
| use rstar::{PointDistance, RTree, RTreeObject, AABB}; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| 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::str::FromStr; | ||||
| use std::time::Instant; | ||||
| use structopt::StructOpt; | ||||
| 
 | ||||
| #[derive(Debug)] | ||||
| #[allow(dead_code)] | ||||
| enum StarType { | ||||
|     Neutron, | ||||
|     WhiteDwarf, | ||||
|     Scoopable, | ||||
|     NonScoopable, | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Clone, Deserialize)] | ||||
| struct SystemSerde { | ||||
|     id: u32, | ||||
|     star_type: String, | ||||
|     name: String, | ||||
|     mult: f32, | ||||
|     distance: u32, | ||||
|     x: f32, | ||||
|     y: f32, | ||||
|     z: f32, | ||||
| } | ||||
| #[derive(Debug, Clone, Deserialize, Serialize)] | ||||
| struct System { | ||||
|     id: u32, | ||||
|     star_type: String, | ||||
|     name: String, | ||||
|     mult: f32, | ||||
|     distance: u32, | ||||
|     pos: [f32; 3], | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug)] | ||||
| enum Mode { | ||||
|     BFS, | ||||
|     Greedy, | ||||
|     AStar, | ||||
| } | ||||
| 
 | ||||
| impl FromStr for Mode { | ||||
|     type Err = String; | ||||
|     fn from_str(s: &str) -> Result<Mode, String> { | ||||
|         match s { | ||||
|             "bfs" => Ok(Mode::BFS), | ||||
|             "greedy" => Ok(Mode::Greedy), | ||||
|             "astar" => Ok(Mode::AStar), | ||||
|             _ => Err("Invalid Mode".to_string()), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, StructOpt)] | ||||
| #[structopt(
 | ||||
|     name = "ed_lrr", | ||||
|     about = "Elite: Dangerous Long-Range Router", | ||||
|     rename_all = "snake_case" | ||||
| )] | ||||
| enum Opts { | ||||
|     /// Plots a route through multiple systems
 | ||||
| struct Opts { | ||||
|     #[structopt(short, long = "range")] | ||||
|     /// Jump Range
 | ||||
|     range: Option<f32>, | ||||
|     #[structopt(
 | ||||
|         parse(from_os_str), | ||||
|         short = "i", | ||||
|         long = "path", | ||||
|         default_value = "./stars.csv" | ||||
|     )] | ||||
|     /// Path to stars.csv
 | ||||
|     ///
 | ||||
|     /// Generate using ed_lrr_pp
 | ||||
|     file_path: PathBuf, | ||||
| 
 | ||||
|     #[structopt(
 | ||||
|         parse(from_os_str), | ||||
|         long = "precomp_file", | ||||
|         conflicts_with = "precompute" | ||||
|     )] | ||||
|     /// Path to precomputed route graph
 | ||||
|     ///
 | ||||
|     /// Generate using --precompute option
 | ||||
|     precomp_file: Option<PathBuf>, | ||||
| 
 | ||||
|     #[structopt(
 | ||||
|         short = "c", | ||||
|         long = "precompute", | ||||
|         conflicts_with = "full_permute", | ||||
|         conflicts_with = "permute", | ||||
|         conflicts_with = "precomp_file" | ||||
|     )] | ||||
|     /// Precompute all routes for the specified jump range starting at the specified system and write the result to {system}_{range}.bin
 | ||||
|     precompute: bool, | ||||
| 
 | ||||
|     #[structopt(short = "p", long = "permute", conflicts_with = "full_permute")] | ||||
|     /// Permute intermediate hops to find shortest route
 | ||||
|     permute: bool, | ||||
| 
 | ||||
|     #[structopt(short = "o", long = "primary")] | ||||
|     /// Only route through the primary star of a system
 | ||||
|     primary: bool, | ||||
| 
 | ||||
|     #[structopt(short = "f", long = "full_permute", conflicts_with = "permute")] | ||||
|     /// Permute all hops to find shortest route
 | ||||
|     full_permute: bool, | ||||
| 
 | ||||
|     #[structopt(short = "g", long = "factor")] | ||||
|     /// Greedyness factor for A-Star (0=BFS, inf=Greedy)
 | ||||
|     factor: Option<f32>, | ||||
| 
 | ||||
|     #[structopt(
 | ||||
|         short, | ||||
|         long, | ||||
|         raw(possible_values = "&[\"bfs\", \"greedy\",\"astar\"]"), | ||||
|         default_value = "bfs" | ||||
|     )] | ||||
|     /// Search mode
 | ||||
|     ///
 | ||||
|     /**
 | ||||
|      - BFS is guaranteed to find the shortest route but very slow | ||||
|      - Greedy is a lot faster but will probably not find the shortest route | ||||
|      - A-Star is a good middle ground between speed and accuracy | ||||
|     */ | ||||
|     mode: Mode, | ||||
|     /// Systems to route through
 | ||||
|     systems: Vec<String>, | ||||
| } | ||||
| 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]; | ||||
| 
 | ||||
|     return dx * dx + dy * dy + dz * dz; | ||||
|     Route(RouteOpts), | ||||
|     /// Preprocess EDSM Dump
 | ||||
|     Preprocess(PreprocessOpts), | ||||
| } | ||||
| 
 | ||||
| fn dist(p1: &[f32; 3], p2: &[f32; 3]) -> f32 { | ||||
|     return 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 { | ||||
|         return dist2(&self.pos, p); | ||||
|     } | ||||
|     pub fn distp(&self, p: &System) -> f32 { | ||||
|         return dist(&self.pos, &p.pos); | ||||
|     } | ||||
|     pub fn distp2(&self, p: &System) -> f32 { | ||||
|         return self.dist2(&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<H: Hasher>(&self, state: &mut H) { | ||||
|         self.id.hash(state); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl RTreeObject for System { | ||||
|     type Envelope = AABB<[f32; 3]>; | ||||
| 
 | ||||
|     fn envelope(&self) -> Self::Envelope { | ||||
|         return AABB::from_point(self.pos); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl PointDistance for System { | ||||
|     fn distance_2(&self, point: &[f32; 3]) -> f32 { | ||||
|         return self.dist2(&point); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| fn hash_file(path: &PathBuf) -> Vec<u8> { | ||||
|     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.result().iter().map(|v| *v).collect() | ||||
| } | ||||
| 
 | ||||
| #[derive(Deserialize, Serialize)] | ||||
| struct Router { | ||||
|     tree: RTree<System>, | ||||
|     scoopable: FnvHashSet<u32>, | ||||
|     route_tree: Option<FnvHashMap<u32, u32>>, | ||||
|     range: f32, | ||||
|     primary_only: bool, | ||||
| } | ||||
| 
 | ||||
| impl Router { | ||||
|     pub fn new(path: &PathBuf, range: f32, primary_only: bool) -> Self { | ||||
|         let mut scoopable = FnvHashSet::default(); | ||||
|         let mut reader = csv::ReaderBuilder::new() | ||||
|             .from_path(path) | ||||
|             .unwrap_or_else(|e| { | ||||
|                 println!("Error opening {}: {}", path.to_str().unwrap(), e); | ||||
|                 std::process::exit(1); | ||||
|             }); | ||||
|         let t_load = Instant::now(); | ||||
|         println!("Loading {}...", path.to_str().unwrap()); | ||||
|         let systems: Vec<System> = reader | ||||
|             .deserialize::<SystemSerde>() | ||||
|             .map(|res| res.unwrap()) | ||||
|             .filter(|sys| { | ||||
|                 if primary_only { | ||||
|                     sys.distance == 0 | ||||
|                 } else { | ||||
|                     true | ||||
|                 } | ||||
|             }) | ||||
|             .map(|sys| { | ||||
|                 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; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 return System { | ||||
|                     id: sys.id, | ||||
|                     star_type: sys.star_type, | ||||
|                     name: sys.name, | ||||
|                     mult: sys.mult, | ||||
|                     distance: sys.distance, | ||||
|                     pos: [sys.x, sys.y, sys.z], | ||||
|                 }; | ||||
|             }) | ||||
|             .collect(); | ||||
|         println!("Building RTree..."); | ||||
|         let ret = Self { | ||||
|             tree: RTree::bulk_load(systems), | ||||
|             scoopable, | ||||
|             route_tree: None, | ||||
|             range, | ||||
|             primary_only, | ||||
|         }; | ||||
|         println!( | ||||
|             "{} Systems loaded in {}", | ||||
|             ret.tree.size(), | ||||
|             format_duration(t_load.elapsed()) | ||||
|         ); | ||||
|         return ret; | ||||
|     } | ||||
| 
 | ||||
|     pub fn from_file(filename: &PathBuf) -> (PathBuf, Self) { | ||||
|         let t_load = Instant::now(); | ||||
|         let mut reader = BufReader::new(File::open(&filename).unwrap()); | ||||
|         println!("Loading {}", filename.to_str().unwrap()); | ||||
|         let (primary, range, file_hash, path, route_tree): ( | ||||
|             bool, | ||||
|             f32, | ||||
|             Vec<u8>, | ||||
|             String, | ||||
|             FnvHashMap<u32, u32>, | ||||
|         ) = bincode::deserialize_from(&mut reader).unwrap(); | ||||
|         let path = PathBuf::from(path); | ||||
|         println!("Done in {}!", format_duration(t_load.elapsed())); | ||||
|         if hash_file(&path) != file_hash { | ||||
|             panic!("File hash mismatch!") | ||||
|         } | ||||
|         return ( | ||||
|             path, | ||||
|             Self { | ||||
|                 tree: RTree::default(), | ||||
|                 scoopable: FnvHashSet::default(), | ||||
|                 route_tree: Some(route_tree), | ||||
|                 range, | ||||
|                 primary_only: primary, | ||||
|             }, | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     fn closest(&self, point: &[f32; 3]) -> &System { | ||||
|         return self.tree.nearest_neighbor(point).unwrap(); | ||||
|     } | ||||
|     fn points_in_sphere(&self, center: &[f32; 3], radius: f32) -> impl Iterator<Item = &System> { | ||||
|         return self.tree.locate_within_distance(*center, radius * radius); | ||||
|     } | ||||
| 
 | ||||
|     fn neighbours(&self, sys: &System, r: f32) -> impl Iterator<Item = &System> { | ||||
|         return self.points_in_sphere(&sys.pos, sys.mult * r); | ||||
|     } | ||||
| 
 | ||||
|     fn valid(&self, sys: &System) -> bool { | ||||
|         return self.scoopable.contains(&sys.id); | ||||
|     } | ||||
| 
 | ||||
|     pub fn best_name_multiroute( | ||||
|         &self, | ||||
|         waypoints: &[String], | ||||
|         range: f32, | ||||
|         full: bool, | ||||
|         mode: Mode, | ||||
|         factor: f32, | ||||
|     ) -> Vec<System> { | ||||
|         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(); | ||||
|         let t_start = Instant::now(); | ||||
|         println!("Finding best permutation of hops..."); | ||||
|         while waypoints.prev_permutation() {} | ||||
|         loop { | ||||
|             let c_first = waypoints.first().cloned(); | ||||
|             let c_last = waypoints.last().cloned(); | ||||
|             if full || ((c_first == first) && (c_last == last)) { | ||||
|                 let mut total_d = 0.0; | ||||
|                 for pair in waypoints.windows(2) { | ||||
|                     match pair { | ||||
|                         [src, dst] => { | ||||
|                             let (mut src, dst) = | ||||
|                                 (self.name_to_systems(&src), self.name_to_systems(&dst)); | ||||
|                             src.sort_by_key(|&p| (p.mult * 10.0) as u8); | ||||
|                             let src = src.last().unwrap(); | ||||
|                             let dst = dst.last().unwrap(); | ||||
|                             total_d += src.distp2(dst); | ||||
|                         } | ||||
|                         _ => panic!("Invalid routing parameters!"), | ||||
|                     } | ||||
|                 } | ||||
|                 if total_d < best_score { | ||||
|                     best_score = total_d; | ||||
|                     best_permutation_waypoints = waypoints.to_owned(); | ||||
|                 } | ||||
|             } | ||||
|             if !waypoints.next_permutation() { | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         println!("Done in {}!", format_duration(t_start.elapsed())); | ||||
|         println!("Best permutation: {:?}", best_permutation_waypoints); | ||||
|         return self.name_multiroute(&best_permutation_waypoints, range, mode, factor); | ||||
|     } | ||||
| 
 | ||||
|     pub fn name_multiroute( | ||||
|         &self, | ||||
|         waypoints: &[String], | ||||
|         range: f32, | ||||
|         mode: Mode, | ||||
|         factor: f32, | ||||
|     ) -> Vec<System> { | ||||
|         let mut coords = Vec::new(); | ||||
|         for p_name in waypoints { | ||||
|             let mut p_l = self.name_to_systems(p_name); | ||||
|             p_l.sort_by_key(|&p| (p.mult * 10.0) as u8); | ||||
|             let p = p_l.last().unwrap(); | ||||
|             coords.push((p_name, p.pos)); | ||||
|         } | ||||
|         return self.multiroute(coords.as_slice(), range, mode, factor); | ||||
|     } | ||||
|     pub fn multiroute( | ||||
|         &self, | ||||
|         waypoints: &[(&String, [f32; 3])], | ||||
|         range: f32, | ||||
|         mode: Mode, | ||||
|         factor: f32, | ||||
|     ) -> Vec<System> { | ||||
|         let mut route: Vec<System> = Vec::new(); | ||||
|         for pair in waypoints.windows(2) { | ||||
|             match *pair { | ||||
|                 [src, dst] => { | ||||
|                     let block = match mode { | ||||
|                         Mode::BFS => self.route_bfs(&src, &dst, range), | ||||
|                         Mode::Greedy => self.route_greedy(&src, &dst, range), | ||||
|                         Mode::AStar => self.route_astar(&src, &dst, range, factor), | ||||
|                     }; | ||||
|                     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()); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 _ => panic!("Invalid routing parameters!"), | ||||
|             } | ||||
|         } | ||||
|         return route; | ||||
|     } | ||||
| 
 | ||||
|     fn name_to_systems(&self, name: &str) -> Vec<&System> { | ||||
|         for sys in &self.tree { | ||||
|             if sys.name.contains(name) { | ||||
|                 return self.neighbours(&sys, 0.0).collect(); | ||||
|             } | ||||
|         } | ||||
|         eprintln!("System not found: \"{}\"", name); | ||||
|         std::process::exit(1); | ||||
|     } | ||||
| 
 | ||||
|     pub fn route_astar( | ||||
|         &self, | ||||
|         src: &(&String, [f32; 3]), | ||||
|         dst: &(&String, [f32; 3]), | ||||
|         range: f32, | ||||
|         factor: f32, | ||||
|     ) -> Vec<System> { | ||||
|         if factor == 0.0 { | ||||
|             return self.route_bfs(src, dst, range); | ||||
|         } | ||||
|         println!("Running A-Star with greedy factor of {}", factor); | ||||
|         let (src_name, src) = src; | ||||
|         let (dst_name, dst) = dst; | ||||
|         let start_sys = self.closest(src); | ||||
|         let goal_sys = self.closest(dst); | ||||
|         { | ||||
|             let d = dist(src, dst); | ||||
|             println!("Plotting route from {} to {}...", src_name, dst_name); | ||||
|             println!( | ||||
|                 "Jump Range: {} Ly, Distance: {} Ly, Theoretical Jumps: {}", | ||||
|                 range, | ||||
|                 d, | ||||
|                 d / range | ||||
|             ); | ||||
|         } | ||||
|         let total = self.tree.size() as f32; | ||||
|         let mut prev = FnvHashMap::default(); | ||||
|         let mut seen = FnvHashSet::default(); | ||||
|         let t_start = Instant::now(); | ||||
|         let mut found = false; | ||||
|         let mut maxd = 0; | ||||
|         let mut queue: Vec<(usize, usize, &System)> = Vec::new(); | ||||
|         queue.push(( | ||||
|             0,                                            // depth
 | ||||
|             (start_sys.distp(goal_sys) / range) as usize, // h
 | ||||
|             &start_sys, | ||||
|         )); | ||||
|         seen.insert(start_sys.id); | ||||
| 
 | ||||
|         while !(queue.is_empty() || found) { | ||||
|             while let Some((depth, _, sys)) = queue.pop() { | ||||
|                 if depth > maxd { | ||||
|                     maxd = depth; | ||||
|                     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(); | ||||
|                 } | ||||
|                 if sys.id == goal_sys.id { | ||||
|                     found = true; | ||||
|                     break; | ||||
|                 } | ||||
|                 queue.extend( | ||||
|                     self.neighbours(&sys, range) | ||||
|                         .filter(|&nb| (self.valid(nb) || (nb.id == goal_sys.id))) | ||||
|                         .filter(|&nb| seen.insert(nb.id)) | ||||
|                         .map(|nb| { | ||||
|                             prev.insert(nb.id, sys); | ||||
|                             let d_g = (nb.distp(goal_sys) / range) as usize; | ||||
|                             return (depth + 1, d_g, nb); | ||||
|                         }), | ||||
|                 ); | ||||
|                 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 + a_1 * factor; | ||||
|                     let v_b = b_0 + b_1 * factor; | ||||
|                     return fcmp(v_a, v_b); | ||||
|                 }); | ||||
|                 // queue.reverse();
 | ||||
|             } | ||||
|         } | ||||
|         println!(); | ||||
| 
 | ||||
|         println!(); | ||||
|         if !found { | ||||
|             eprintln!("No route from {} to {} found!", src_name, dst_name); | ||||
|             return Vec::new(); | ||||
|         } | ||||
|         let mut v: Vec<System> = Vec::new(); | ||||
|         let mut curr_sys = goal_sys; | ||||
|         loop { | ||||
|             v.push(curr_sys.clone()); | ||||
|             match prev.get(&curr_sys.id) { | ||||
|                 Some(sys) => curr_sys = *sys, | ||||
|                 None => { | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         v.reverse(); | ||||
|         return v; | ||||
|     } | ||||
| 
 | ||||
|     pub fn route_greedy( | ||||
|         &self, | ||||
|         src: &(&String, [f32; 3]), | ||||
|         dst: &(&String, [f32; 3]), | ||||
|         range: f32, | ||||
|     ) -> Vec<System> { | ||||
|         println!("Running Greedy-Search"); | ||||
|         let (src_name, src) = src; | ||||
|         let (dst_name, dst) = dst; | ||||
|         let start_sys = self.closest(src); | ||||
|         let goal_sys = self.closest(dst); | ||||
|         { | ||||
|             let d = dist(src, dst); | ||||
|             println!("Plotting route from {} to {}...", src_name, dst_name); | ||||
|             println!( | ||||
|                 "Jump Range: {} Ly, Distance: {} Ly, Theoretical Jumps: {}", | ||||
|                 range, | ||||
|                 d, | ||||
|                 d / range | ||||
|             ); | ||||
|         } | ||||
|         let total = self.tree.size() as f32; | ||||
|         let mut prev = FnvHashMap::default(); | ||||
|         let mut seen = FnvHashSet::default(); | ||||
|         let t_start = Instant::now(); | ||||
|         let mut found = false; | ||||
|         let mut maxd = 0; | ||||
|         let mut queue: Vec<(f32, f32, usize, &System)> = Vec::new(); | ||||
|         queue.push((-goal_sys.mult, start_sys.distp2(goal_sys), 0, &start_sys)); | ||||
|         seen.insert(start_sys.id); | ||||
|         while !(queue.is_empty() || found) { | ||||
|             while let Some((_, _, depth, sys)) = queue.pop() { | ||||
|                 if depth > maxd { | ||||
|                     maxd = depth; | ||||
|                     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(); | ||||
|                 } | ||||
|                 if sys.id == goal_sys.id { | ||||
|                     found = true; | ||||
|                     break; | ||||
|                 } | ||||
|                 queue.extend( | ||||
|                     self.neighbours(&sys, range) | ||||
|                         .filter(|&nb| (self.valid(nb) || (nb.id == goal_sys.id))) | ||||
|                         .filter(|&nb| seen.insert(nb.id)) | ||||
|                         .map(|nb| { | ||||
|                             prev.insert(nb.id, sys); | ||||
|                             return (-nb.mult, nb.distp2(goal_sys), depth + 1, nb); | ||||
|                         }), | ||||
|                 ); | ||||
|                 queue.sort_by(|a, b| fcmp(a.0, b.0).then(fcmp(a.1, b.1))); | ||||
|                 queue.reverse(); | ||||
|             } | ||||
|         } | ||||
|         println!(); | ||||
|         println!(); | ||||
|         if !found { | ||||
|             eprintln!("No route from {} to {} found!", src_name, dst_name); | ||||
|             return Vec::new(); | ||||
|         } | ||||
|         let mut v: Vec<System> = Vec::new(); | ||||
|         let mut curr_sys = goal_sys; | ||||
|         loop { | ||||
|             v.push(curr_sys.clone()); | ||||
|             match prev.get(&curr_sys.id) { | ||||
|                 Some(sys) => curr_sys = *sys, | ||||
|                 None => { | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         v.reverse(); | ||||
|         return v; | ||||
|     } | ||||
| 
 | ||||
|     pub fn precompute(&mut self, src: &String) { | ||||
|         println!("Precomputing route starting at {} ...", src); | ||||
|         let mut sys_l = self.name_to_systems(src); | ||||
|         sys_l.sort_by_key(|&sys| (sys.mult * 10.0) as u8); | ||||
|         let sys = sys_l.last().unwrap(); | ||||
|         let total = self.tree.size() as f32; | ||||
|         let mut prev = FnvHashMap::default(); | ||||
|         let mut seen = FnvHashSet::default(); | ||||
|         let t_start = Instant::now(); | ||||
|         let mut depth = 0; | ||||
|         let mut queue: VecDeque<(usize, &System)> = VecDeque::new(); | ||||
|         let mut queue_next: VecDeque<(usize, &System)> = VecDeque::new(); | ||||
|         queue.push_front((0, &sys)); | ||||
|         seen.insert(sys.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, self.range) | ||||
|                         .filter(|&nb| seen.insert(nb.id)) | ||||
|                         .map(|nb| { | ||||
|                             prev.insert(nb.id, sys.id); | ||||
|                             return (d + 1, nb); | ||||
|                         }), | ||||
|                 ); | ||||
|             } | ||||
|             std::mem::swap(&mut queue, &mut queue_next); | ||||
|             depth += 1; | ||||
|         } | ||||
|         self.route_tree = Some(prev); | ||||
|     } | ||||
| 
 | ||||
|     fn get_systems_by_ids(path: &PathBuf, ids: &Vec<u32>) -> FnvHashMap<u32, System> { | ||||
|         let mut ret = FnvHashMap::default(); | ||||
|         let mut reader = csv::ReaderBuilder::new() | ||||
|             .from_path(path) | ||||
|             .unwrap_or_else(|e| { | ||||
|                 println!("Error opening {}: {}", path.to_str().unwrap(), e); | ||||
|                 std::process::exit(1); | ||||
|             }); | ||||
|         reader | ||||
|             .deserialize::<SystemSerde>() | ||||
|             .map(|res| res.unwrap()) | ||||
|             .filter(|sys| ids.contains(&sys.id)) | ||||
|             .map(|sys| { | ||||
|                 ret.insert( | ||||
|                     sys.id, | ||||
|                     System { | ||||
|                         id: sys.id, | ||||
|                         star_type: sys.star_type, | ||||
|                         name: sys.name, | ||||
|                         mult: sys.mult, | ||||
|                         distance: sys.distance, | ||||
|                         pos: [sys.x, sys.y, sys.z], | ||||
|                     }, | ||||
|                 ); | ||||
|             }) | ||||
|             .last() | ||||
|             .expect(&format!("No systems matching {:?} found!", ids)); | ||||
|         return ret; | ||||
|     } | ||||
| 
 | ||||
|     fn get_systems_by_names(path: &PathBuf, names: &Vec<String>) -> FnvHashMap<String, System> { | ||||
|         let mut ret = FnvHashMap::default(); | ||||
|         let mut reader = csv::ReaderBuilder::new() | ||||
|             .from_path(path) | ||||
|             .unwrap_or_else(|e| { | ||||
|                 println!("Error opening {}: {}", path.to_str().unwrap(), e); | ||||
|                 std::process::exit(1); | ||||
|             }); | ||||
|         reader | ||||
|             .deserialize::<SystemSerde>() | ||||
|             .map(|res| res.unwrap()) | ||||
|             .filter(|sys| names.contains(&sys.name)) | ||||
|             .map(|sys| { | ||||
|                 ret.insert( | ||||
|                     sys.name.clone(), | ||||
|                     System { | ||||
|                         id: sys.id, | ||||
|                         star_type: sys.star_type, | ||||
|                         name: sys.name, | ||||
|                         mult: sys.mult, | ||||
|                         distance: sys.distance, | ||||
|                         pos: [sys.x, sys.y, sys.z], | ||||
|                     }, | ||||
|                 ); | ||||
|             }) | ||||
|             .last() | ||||
|             .expect(&format!("No systems matching {:?} found!", names)); | ||||
|         return ret; | ||||
|     } | ||||
| 
 | ||||
|     fn get_system_by_name(path: &PathBuf, name: &str) -> System { | ||||
|         let mut reader = csv::ReaderBuilder::new() | ||||
|             .from_path(path) | ||||
|             .unwrap_or_else(|e| { | ||||
|                 println!("Error opening {}: {}", path.to_str().unwrap(), e); | ||||
|                 std::process::exit(1); | ||||
|             }); | ||||
|         let sys = reader | ||||
|             .deserialize::<SystemSerde>() | ||||
|             .map(|res| res.unwrap()) | ||||
|             .find(|sys| sys.name == name) | ||||
|             .expect(&format!("System '{}' not found!", name)); | ||||
|         return System { | ||||
|             id: sys.id, | ||||
|             star_type: sys.star_type, | ||||
|             name: sys.name, | ||||
|             mult: sys.mult, | ||||
|             distance: sys.distance, | ||||
|             pos: [sys.x, sys.y, sys.z], | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     pub fn route_to(&mut self, dst: &str, systems_path: &PathBuf) -> Vec<System> { | ||||
|         let prev = self.route_tree.as_ref().unwrap(); | ||||
|         let dst = Self::get_system_by_name(&systems_path, dst); | ||||
|         if !prev.contains_key(&dst.id) { | ||||
|             eprintln!("System-ID {} not found", dst.id); | ||||
|             std::process::exit(1); | ||||
|         }; | ||||
|         let mut v_ids: Vec<u32> = Vec::new(); | ||||
|         let mut v: Vec<System> = 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(&systems_path, &v_ids); | ||||
|         for sys_id in v_ids { | ||||
|             let sys = id_map.get(&sys_id).unwrap(); | ||||
|             v.push(sys.clone()) | ||||
|         } | ||||
|         return v; | ||||
|     } | ||||
| 
 | ||||
|     pub fn route_bfs( | ||||
|         &self, | ||||
|         src: &(&String, [f32; 3]), | ||||
|         dst: &(&String, [f32; 3]), | ||||
|         range: f32, | ||||
|     ) -> Vec<System> { | ||||
|         println!("Running BFS"); | ||||
|         let (src_name, src) = src; | ||||
|         let (dst_name, dst) = dst; | ||||
|         let start_sys = self.closest(src); | ||||
|         let goal_sys = self.closest(dst); | ||||
|         { | ||||
|             let d = dist(src, dst); | ||||
|             println!("Plotting route from {} to {}...", src_name, dst_name); | ||||
|             println!( | ||||
|                 "Jump Range: {} Ly, Distance: {} Ly, Theoretical Jumps: {}", | ||||
|                 range, | ||||
|                 d, | ||||
|                 d / range | ||||
|             ); | ||||
|         } | ||||
|         let total = self.tree.size() as f32; | ||||
|         let mut prev = FnvHashMap::default(); | ||||
|         let mut seen = FnvHashSet::default(); | ||||
|         let t_start = Instant::now(); | ||||
|         let mut depth = 0; | ||||
|         let mut found = false; | ||||
|         let mut queue: VecDeque<(usize, &System)> = VecDeque::new(); | ||||
|         let mut queue_next: VecDeque<(usize, &System)> = VecDeque::new(); | ||||
|         queue.push_front((0, &start_sys)); | ||||
|         seen.insert(start_sys.id); | ||||
|         while !(queue.is_empty() || found) { | ||||
|             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() { | ||||
|                 if sys.id == goal_sys.id { | ||||
|                     found = true; | ||||
|                     break; | ||||
|                 } | ||||
|                 queue_next.extend( | ||||
|                     self.neighbours(&sys, range) | ||||
|                         .filter(|&nb| (self.valid(nb) || (nb.id == goal_sys.id))) | ||||
|                         .filter(|&nb| seen.insert(nb.id)) | ||||
|                         .map(|nb| { | ||||
|                             prev.insert(nb.id, sys); | ||||
|                             return (d + 1, nb); | ||||
|                         }), | ||||
|                 ); | ||||
|             } | ||||
|             std::mem::swap(&mut queue, &mut queue_next); | ||||
|             depth += 1; | ||||
|         } | ||||
|         println!(); | ||||
|         println!(); | ||||
|         if !found { | ||||
|             eprintln!("No route from {} to {} found!", src_name, dst_name); | ||||
|             return Vec::new(); | ||||
|         } | ||||
|         let mut v: Vec<System> = Vec::new(); | ||||
|         let mut curr_sys = goal_sys; | ||||
|         loop { | ||||
|             v.push(curr_sys.clone()); | ||||
|             match prev.get(&curr_sys.id) { | ||||
|                 Some(sys) => curr_sys = *sys, | ||||
|                 None => { | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         v.reverse(); | ||||
|         return v; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| fn main() { | ||||
| fn main() -> std::io::Result<()> { | ||||
|     let t_start = Instant::now(); | ||||
|     let opts = Opts::from_args(); | ||||
|     if opts.systems.len() == 0 { | ||||
|         if opts.precomp_file.is_some() { | ||||
|             eprintln!("Error: Please specify exatly one system"); | ||||
|         } else { | ||||
|             if opts.precompute { | ||||
|                 eprintln!("Error: Please specify at least one system"); | ||||
|             } else { | ||||
|                 eprintln!("Error: Please specify at least two systems"); | ||||
|             } | ||||
|         } | ||||
|         std::process::exit(1); | ||||
|     } | ||||
|     let mut path = opts.file_path; | ||||
|     let mut router: Router = if opts.precomp_file.is_some() { | ||||
|         let ret = Router::from_file(&opts.precomp_file.clone().unwrap()); | ||||
|         path = ret.0; | ||||
|         ret.1 | ||||
|     } else { | ||||
|         Router::new(&path, opts.range.unwrap(), opts.primary) | ||||
|     let ret = match opts { | ||||
|         Opts::Route(opts) => route(opts), | ||||
|         Opts::Preprocess(opts) => preprocess_files(opts), | ||||
|     }; | ||||
|     if opts.precompute { | ||||
|         for sys in opts.systems { | ||||
|             router.route_tree = None; | ||||
|             router.precompute(&sys); | ||||
|             let ofn = format!( | ||||
|                 "{}_{}{}.router", | ||||
|                 sys.replace("*", "").replace(" ", "_"), | ||||
|                 opts.range.unwrap(), | ||||
|                 if router.primary_only { "_primary" } else { "" } | ||||
|             ); | ||||
|             println!("\nSaving to {}", ofn); | ||||
|             let mut out_fh = BufWriter::new(File::create(&ofn).unwrap()); | ||||
|             // (range, path, route_tree)
 | ||||
|             let data = ( | ||||
|                 router.primary_only, | ||||
|                 router.range, | ||||
|                 hash_file(&path), | ||||
|                 String::from(path.to_str().unwrap()), | ||||
|                 router.route_tree.unwrap(), | ||||
|             ); | ||||
|             bincode::serialize_into(&mut out_fh, &data).unwrap(); | ||||
|         } | ||||
|         std::process::exit(0); | ||||
|     } | ||||
|     let t_route = Instant::now(); | ||||
|     let route = if router.route_tree.is_some() { | ||||
|         router.route_to(opts.systems.first().unwrap(), &path) | ||||
|     } else { | ||||
|         if opts.permute || opts.full_permute { | ||||
|             router.best_name_multiroute( | ||||
|                 &opts.systems, | ||||
|                 opts.range.unwrap(), | ||||
|                 opts.full_permute, | ||||
|                 opts.mode, | ||||
|                 opts.factor.unwrap_or(1.0), | ||||
|             ) | ||||
|         } else { | ||||
|             router.name_multiroute( | ||||
|                 &opts.systems, | ||||
|                 opts.range.unwrap(), | ||||
|                 opts.mode, | ||||
|                 opts.factor.unwrap_or(1.0), | ||||
|             ) | ||||
|         } | ||||
|     }; | ||||
|     println!("Route computed in {}\n", format_duration(t_route.elapsed())); | ||||
|     if route.is_empty() { | ||||
|         eprintln!("No route found!"); | ||||
|         return; | ||||
|     } | ||||
|     let mut total: f32 = 0.0; | ||||
|     for (sys1, sys2) in route.iter().zip(route.iter().skip(1)) { | ||||
|         let dist = sys1.distp(sys2); | ||||
|         total += dist; | ||||
|         println!( | ||||
|             "{} [{}] ({},{},{}) [{} Ls]: {:.2} Ly", | ||||
|             sys1.name, sys1.star_type, sys1.pos[0], sys1.pos[1], sys1.pos[2], sys1.distance, dist | ||||
|         ); | ||||
|     } | ||||
|     let sys = route.iter().last().unwrap(); | ||||
|     println!( | ||||
|         "{} [{}] ({},{},{}) [{} Ls]: {:.2} Ly", | ||||
|         sys.name, sys.star_type, sys.pos[0], sys.pos[1], sys.pos[2], sys.distance, 0.0 | ||||
|     ); | ||||
|     println!("Total: {:.2} Ly ({} Jumps)", total, route.len()); | ||||
|     println!("Total time: {}", format_duration(t_start.elapsed())); | ||||
|     ret | ||||
| } | ||||
|  |  | |||
							
								
								
									
										220
									
								
								src/preprocess.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										220
									
								
								src/preprocess.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,220 @@ | |||
| use crate::common::SystemSerde; | ||||
| use fnv::FnvHashMap; | ||||
| use humantime::format_duration; | ||||
| use indicatif::{ProgressBar, ProgressStyle}; | ||||
| use serde::Deserialize; | ||||
| use serde_json::Result; | ||||
| use std::fs::File; | ||||
| use std::io::Seek; | ||||
| use std::io::{BufRead, BufReader, BufWriter, SeekFrom}; | ||||
| use std::path::PathBuf; | ||||
| use std::str; | ||||
| use std::time::Instant; | ||||
| use structopt::StructOpt; | ||||
| 
 | ||||
| #[derive(Debug, StructOpt)] | ||||
| pub struct PreprocessOpts { | ||||
|     #[structopt(short, long = "bodies")] | ||||
|     /// Path to bodies.json
 | ||||
|     pub bodies: PathBuf, | ||||
|     #[structopt(short, long = "systems")] | ||||
|     /// Path to systemsWithCoordinates.json
 | ||||
|     pub systems: PathBuf, | ||||
|     #[structopt(default_value = "stars")] | ||||
|     /// outfile prefix
 | ||||
|     pub prefix: String, | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Deserialize)] | ||||
| #[allow(non_snake_case)] | ||||
| struct Body { | ||||
|     name: String, | ||||
|     subType: String, | ||||
|     #[serde(rename = "type")] | ||||
|     body_type: String, | ||||
|     systemId: i32, | ||||
|     systemId64: i64, | ||||
|     #[serde(rename = "distanceToArrival")] | ||||
|     distance: u32, | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Deserialize)] | ||||
| struct Coords { | ||||
|     x: f32, | ||||
|     y: f32, | ||||
|     z: f32, | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Deserialize)] | ||||
| struct System { | ||||
|     id: i32, | ||||
|     id64: i64, | ||||
|     name: String, | ||||
|     coords: Coords, | ||||
|     date: String, | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, StructOpt)] | ||||
| #[structopt(
 | ||||
|     name = "ed_lrr_pp", | ||||
|     about = "Preprocessor for Elite: Dangerous Long-Range Router", | ||||
|     rename_all = "snake_case" | ||||
| )] | ||||
| /// Preprocess data for ed_lrr
 | ||||
| struct Opt { | ||||
|     #[structopt(short, long = "bodies")] | ||||
|     /// Path to bodies.json
 | ||||
|     bodies: PathBuf, | ||||
|     #[structopt(short, long = "systems")] | ||||
|     /// Path to systemsWithCoordinates.json
 | ||||
|     systems: PathBuf, | ||||
|     #[structopt(default_value = "stars")] | ||||
|     /// outfile prefix
 | ||||
|     prefix: String, | ||||
| } | ||||
| 
 | ||||
| fn get_mult(star_type: &str) -> f32 { | ||||
|     if star_type.contains("White Dwarf") { | ||||
|         return 1.5; | ||||
|     } | ||||
|     if star_type.contains("Neutron") { | ||||
|         return 4.0; | ||||
|     } | ||||
|     1.0 | ||||
| } | ||||
| 
 | ||||
| fn process(path: &PathBuf, func: &mut dyn for<'r> FnMut(&'r str) -> ()) -> std::io::Result<()> { | ||||
|     let mut cnt = 0; | ||||
|     let mut buffer = String::new(); | ||||
|     let t_start = Instant::now(); | ||||
|     let fh = File::open(path)?; | ||||
|     let prog_bar = ProgressBar::new(fh.metadata()?.len()); | ||||
|     prog_bar.set_style( | ||||
|         ProgressStyle::default_bar() | ||||
|             .template( | ||||
|                 "[{elapsed_precise}/{eta_precise}]{spinner} [{wide_bar}] {binary_bytes}/{binary_total_bytes} ({percent}%)", | ||||
|             ) | ||||
|             .progress_chars("#9876543210 ") | ||||
|             .tick_chars("/-\\|"), | ||||
|     ); | ||||
|     prog_bar.set_draw_delta(1024 * 1024); | ||||
|     let mut reader = BufReader::new(fh); | ||||
|     println!("Loading {} ...", path.to_str().unwrap()); | ||||
|     while let Ok(n) = reader.read_line(&mut buffer) { | ||||
|         if n == 0 { | ||||
|             break; | ||||
|         } | ||||
|         buffer = buffer.trim_end().trim_end_matches(|c| c == ',').to_string(); | ||||
|         if !buffer.is_empty() { | ||||
|             func(&buffer); | ||||
|         } | ||||
|         prog_bar.set_position(reader.seek(SeekFrom::Current(0)).unwrap()); | ||||
|         cnt += 1; | ||||
|         buffer.clear(); | ||||
|     } | ||||
|     prog_bar.finish_and_clear(); | ||||
|     println!( | ||||
|         "Processed {} lines in {} ...", | ||||
|         cnt, | ||||
|         format_duration(t_start.elapsed()) | ||||
|     ); | ||||
|     Ok(()) | ||||
| } | ||||
| 
 | ||||
| fn process_systems(path: &PathBuf) -> FnvHashMap<i64, System> { | ||||
|     let mut ret = FnvHashMap::default(); | ||||
|     process(path, &mut |line| { | ||||
|         let sys_res: Result<System> = serde_json::from_str(&line); | ||||
|         if let Ok(sys) = sys_res { | ||||
|             ret.insert(sys.id64, sys); | ||||
|         } else { | ||||
|             eprintln!("\nError parsing: {}\n\t{:?}\n", line, sys_res.unwrap_err()); | ||||
|         } | ||||
|     }) | ||||
|     .unwrap(); | ||||
|     ret | ||||
| } | ||||
| 
 | ||||
| fn build_index(path: &PathBuf) -> std::io::Result<()> { | ||||
|     let mut wtr = BufWriter::new(File::create(path.with_extension("idx"))?); | ||||
|     let mut idx: Vec<u64> = Vec::new(); | ||||
|     let mut records = (csv::Reader::from_path(path)?).into_deserialize::<SystemSerde>(); | ||||
|     loop { | ||||
|         idx.push(records.reader().position().byte()); | ||||
|         if records.next().is_none() { | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|     bincode::serialize_into(&mut wtr, &idx).unwrap(); | ||||
|     Ok(()) | ||||
| } | ||||
| 
 | ||||
| fn process_bodies( | ||||
|     path: &PathBuf, | ||||
|     out_prefix: &str, | ||||
|     systems: &mut FnvHashMap<i64, System>, | ||||
| ) -> std::io::Result<()> { | ||||
|     let out_path = PathBuf::from(format!("{}.csv", out_prefix)); | ||||
|     println!( | ||||
|         "Processing {} into {} ...", | ||||
|         path.to_str().unwrap(), | ||||
|         out_path.to_str().unwrap(), | ||||
|     ); | ||||
|     let mut n: u32 = 0; | ||||
|     let mut wtr = csv::Writer::from_path(out_path)?; | ||||
|     process(path, &mut |line| { | ||||
|         if !line.contains("Star") { | ||||
|             return; | ||||
|         } | ||||
|         let body_res: Result<Body> = serde_json::from_str(&line); | ||||
|         if let Ok(body) = body_res { | ||||
|             if !body.body_type.contains("Star") { | ||||
|                 return; | ||||
|             } | ||||
|             if let Some(sys) = systems.get(&body.systemId64) { | ||||
|                 let sub_type = body.subType; | ||||
|                 let mult = get_mult(&sub_type); | ||||
|                 let sys_name = sys.name.clone(); | ||||
|                 let mut body_name = body.name.replace(&sys_name, "").trim().to_string(); | ||||
|                 if body_name == sys_name { | ||||
|                     body_name = "".to_string(); | ||||
|                 } | ||||
|                 let rec = SystemSerde { | ||||
|                     id: n, | ||||
|                     star_type: sub_type, | ||||
|                     system: sys_name, | ||||
|                     body: body_name, | ||||
|                     mult, | ||||
|                     distance: body.distance, | ||||
|                     x: sys.coords.x, | ||||
|                     y: sys.coords.y, | ||||
|                     z: sys.coords.z, | ||||
|                 }; | ||||
|                 wtr.serialize(rec).unwrap(); | ||||
|                 n += 1; | ||||
|             }; | ||||
|         } else { | ||||
|             eprintln!("\nError parsing: {}\n\t{:?}\n", line, body_res.unwrap_err()); | ||||
|         } | ||||
|     }) | ||||
|     .unwrap(); | ||||
|     println!("Total Systems: {}", n); | ||||
|     systems.clear(); | ||||
|     Ok(()) | ||||
| } | ||||
| 
 | ||||
| pub fn preprocess_files(opts: PreprocessOpts) -> std::io::Result<()> { | ||||
|     let out_path = PathBuf::from(format!("{}.csv", &opts.prefix)); | ||||
|     if !out_path.exists() { | ||||
|         let mut systems = process_systems(&opts.systems); | ||||
|         process_bodies(&opts.bodies, &opts.prefix, &mut systems)?; | ||||
|     } else { | ||||
|         println!( | ||||
|             "File '{}' exists, not overwriting it", | ||||
|             out_path.to_str().unwrap() | ||||
|         ); | ||||
|     } | ||||
|     println!("Building index..."); | ||||
|     println!("Index result: {:?}", build_index(&out_path)); | ||||
|     Ok(()) | ||||
| } | ||||
							
								
								
									
										913
									
								
								src/route.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										913
									
								
								src/route.rs
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,913 @@ | |||
| use core::cmp::Ordering; | ||||
| use csv::StringRecord; | ||||
| use fnv::{FnvHashMap, FnvHashSet}; | ||||
| use humantime::format_duration; | ||||
| use permutohedron::LexicalPermutation; | ||||
| use rstar::{PointDistance, RTree, RTreeObject, AABB}; | ||||
| use sha3::{Digest, Sha3_256}; | ||||
| use std::collections::VecDeque; | ||||
| use std::fs::File; | ||||
| use std::hash::{Hash, Hasher}; | ||||
| use std::io::Seek; | ||||
| use std::io::{BufRead, BufReader, BufWriter, Write}; | ||||
| use std::path::PathBuf; | ||||
| use std::str::FromStr; | ||||
| use std::time::Instant; | ||||
| use structopt::StructOpt; | ||||
| 
 | ||||
| use crate::common::{System, SystemSerde}; | ||||
| 
 | ||||
| #[derive(Debug, StructOpt)] | ||||
| pub struct RouteOpts { | ||||
|     #[structopt(short, long = "range")] | ||||
|     /// Jump Range
 | ||||
|     pub range: Option<f32>, | ||||
|     #[structopt(
 | ||||
|         parse(from_os_str), | ||||
|         short = "i", | ||||
|         long = "path", | ||||
|         default_value = "./stars.csv" | ||||
|     )] | ||||
|     /// Path to stars.csv
 | ||||
|     ///
 | ||||
|     /// Generate using ed_lrr_pp
 | ||||
|     pub file_path: PathBuf, | ||||
| 
 | ||||
|     #[structopt(
 | ||||
|         parse(from_os_str), | ||||
|         long = "precomp_file", | ||||
|         conflicts_with = "precompute" | ||||
|     )] | ||||
|     /// Path to precomputed route graph
 | ||||
|     ///
 | ||||
|     /// Generate using --precompute option
 | ||||
|     pub precomp_file: Option<PathBuf>, | ||||
| 
 | ||||
|     #[structopt(
 | ||||
|         short = "c", | ||||
|         long = "precompute", | ||||
|         conflicts_with = "full_permute", | ||||
|         conflicts_with = "permute", | ||||
|         conflicts_with = "precomp_file" | ||||
|     )] | ||||
|     /// Precompute all routes for the specified jump range starting at the specified system and write the result to {system}_{range}.bin
 | ||||
|     pub precompute: bool, | ||||
| 
 | ||||
|     #[structopt(short = "p", long = "permute", conflicts_with = "full_permute")] | ||||
|     /// Permute intermediate hops to find shortest route
 | ||||
|     pub permute: bool, | ||||
| 
 | ||||
|     #[structopt(short = "o", long = "primary")] | ||||
|     /// Only route through the primary star of a system
 | ||||
|     pub primary: bool, | ||||
| 
 | ||||
|     #[structopt(short = "f", long = "full_permute", conflicts_with = "permute")] | ||||
|     /// Permute all hops to find shortest route
 | ||||
|     pub full_permute: bool, | ||||
| 
 | ||||
|     #[structopt(short = "g", long = "factor")] | ||||
|     /// Greedyness factor for A-Star (0=BFS, inf=Greedy)
 | ||||
|     pub factor: Option<f32>, | ||||
| 
 | ||||
|     #[structopt(
 | ||||
|         short, | ||||
|         long, | ||||
|         raw(possible_values = "&[\"bfs\", \"greedy\",\"astar\"]"), | ||||
|         default_value = "bfs" | ||||
|     )] | ||||
|     /// Search mode
 | ||||
|     ///
 | ||||
|     /**
 | ||||
|      - BFS is guaranteed to find the shortest route but very slow | ||||
|     - Greedy is a lot faster but will probably not find the shortest route | ||||
|     - A-Star is a good middle ground between speed and accuracy | ||||
|     */ | ||||
|     pub mode: Mode, | ||||
|     /// Systems to route through
 | ||||
|     pub systems: Vec<String>, | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug)] | ||||
| pub enum Mode { | ||||
|     BFS, | ||||
|     Greedy, | ||||
|     AStar, | ||||
| } | ||||
| 
 | ||||
| impl FromStr for Mode { | ||||
|     type Err = String; | ||||
|     fn from_str(s: &str) -> Result<Mode, String> { | ||||
|         match s { | ||||
|             "bfs" => Ok(Mode::BFS), | ||||
|             "greedy" => Ok(Mode::Greedy), | ||||
|             "astar" => Ok(Mode::AStar), | ||||
|             _ => Err("Invalid Mode".to_string()), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 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 PartialEq for System { | ||||
|     fn eq(&self, other: &Self) -> bool { | ||||
|         self.id == other.id | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Eq for System {} | ||||
| 
 | ||||
| impl Hash for System { | ||||
|     fn hash<H: Hasher>(&self, state: &mut H) { | ||||
|         self.id.hash(state); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl RTreeObject for System { | ||||
|     type Envelope = AABB<[f32; 3]>; | ||||
| 
 | ||||
|     fn envelope(&self) -> Self::Envelope { | ||||
|         AABB::from_point(self.pos) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl PointDistance for System { | ||||
|     fn distance_2(&self, point: &[f32; 3]) -> f32 { | ||||
|         self.dist2(&point) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| fn hash_file(path: &PathBuf) -> Vec<u8> { | ||||
|     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.result().iter().copied().collect() | ||||
| } | ||||
| 
 | ||||
| struct LineCache { | ||||
|     cache: Vec<u64>, | ||||
|     file: BufReader<File>, | ||||
|     header: Option<StringRecord>, | ||||
| } | ||||
| 
 | ||||
| impl LineCache { | ||||
|     pub fn new(path: &PathBuf) -> std::io::Result<Self> { | ||||
|         let idx_path = path.with_extension("idx"); | ||||
|         let t_load = Instant::now(); | ||||
|         println!("Loading {}", path.to_str().unwrap()); | ||||
|         let mut idx_reader = BufReader::new(File::open(idx_path)?); | ||||
|         let cache = match bincode::deserialize_from(&mut idx_reader) { | ||||
|             Ok(value) => value, | ||||
|             err => err.unwrap(), | ||||
|         }; | ||||
|         let mut reader = BufReader::new(File::open(path)?); | ||||
|         let header = Self::read_record(&mut reader); | ||||
|         let ret = Self { | ||||
|             file: reader, | ||||
|             cache, | ||||
|             header, | ||||
|         }; | ||||
|         println!("Done in {}!", format_duration(t_load.elapsed())); | ||||
|         Ok(ret) | ||||
|     } | ||||
|     fn read_record(reader: &mut BufReader<File>) -> Option<StringRecord> { | ||||
|         let mut line = String::new(); | ||||
|         reader.read_line(&mut line).ok()?; | ||||
|         let v: Vec<_> = line.trim_end().split(',').collect(); | ||||
|         let rec = StringRecord::from(v); | ||||
|         Some(rec) | ||||
|     } | ||||
|     pub fn get(&mut self, id: u32) -> Option<System> { | ||||
|         let pos = self.cache[id as usize]; | ||||
|         self.file.seek(std::io::SeekFrom::Start(pos)).unwrap(); | ||||
|         let rec = Self::read_record(&mut self.file).unwrap(); | ||||
|         let sys: SystemSerde = rec.deserialize(self.header.as_ref()).unwrap(); | ||||
|         Some(sys.build()) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub struct Router { | ||||
|     tree: RTree<System>, | ||||
|     scoopable: FnvHashSet<u32>, | ||||
|     pub route_tree: Option<FnvHashMap<u32, u32>>, | ||||
|     cache: Option<LineCache>, | ||||
|     range: f32, | ||||
|     primary_only: bool, | ||||
|     path: PathBuf, | ||||
| } | ||||
| 
 | ||||
| impl Router { | ||||
|     pub fn new(path: &PathBuf, range: f32, primary_only: bool) -> Self { | ||||
|         let mut scoopable = FnvHashSet::default(); | ||||
|         let mut reader = csv::ReaderBuilder::new() | ||||
|             .from_path(path) | ||||
|             .unwrap_or_else(|e| { | ||||
|                 println!("Error opening {}: {}", path.to_str().unwrap(), e); | ||||
|                 std::process::exit(1); | ||||
|             }); | ||||
|         let t_load = Instant::now(); | ||||
|         println!("Loading {}...", path.to_str().unwrap()); | ||||
|         let systems: Vec<System> = reader | ||||
|             .deserialize::<SystemSerde>() | ||||
|             .map(|res| res.unwrap()) | ||||
|             .filter(|sys| { | ||||
|                 if primary_only { | ||||
|                     sys.distance == 0 | ||||
|                 } else { | ||||
|                     true | ||||
|                 } | ||||
|             }) | ||||
|             .map(|sys| { | ||||
|                 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; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 sys.build() | ||||
|             }) | ||||
|             .collect(); | ||||
|         println!("Building RTree..."); | ||||
|         let ret = Self { | ||||
|             tree: RTree::bulk_load(systems), | ||||
|             scoopable, | ||||
|             route_tree: None, | ||||
|             range, | ||||
|             primary_only, | ||||
|             cache: LineCache::new(path).ok(), | ||||
|             path: path.clone(), | ||||
|         }; | ||||
|         println!( | ||||
|             "{} Systems loaded in {}", | ||||
|             ret.tree.size(), | ||||
|             format_duration(t_load.elapsed()) | ||||
|         ); | ||||
|         ret | ||||
|     } | ||||
| 
 | ||||
|     pub fn from_file(filename: &PathBuf) -> (PathBuf, Self) { | ||||
|         let t_load = Instant::now(); | ||||
|         let mut reader = BufReader::new(File::open(&filename).unwrap()); | ||||
|         println!("Loading {}", filename.to_str().unwrap()); | ||||
|         let (primary, range, file_hash, path, route_tree): ( | ||||
|             bool, | ||||
|             f32, | ||||
|             Vec<u8>, | ||||
|             String, | ||||
|             FnvHashMap<u32, u32>, | ||||
|         ) = bincode::deserialize_from(&mut reader).unwrap(); | ||||
|         let path = PathBuf::from(path); | ||||
|         println!("Done in {}!", format_duration(t_load.elapsed())); | ||||
|         if hash_file(&path) != file_hash { | ||||
|             panic!("File hash mismatch!") | ||||
|         } | ||||
|         let cache = LineCache::new(&path).ok(); | ||||
|         ( | ||||
|             path.clone(), | ||||
|             Self { | ||||
|                 tree: RTree::default(), | ||||
|                 scoopable: FnvHashSet::default(), | ||||
|                 route_tree: Some(route_tree), | ||||
|                 range, | ||||
|                 cache, | ||||
|                 primary_only: primary, | ||||
|                 path, | ||||
|             }, | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     fn closest(&self, point: &[f32; 3]) -> &System { | ||||
|         self.tree.nearest_neighbor(point).unwrap() | ||||
|     } | ||||
|     fn points_in_sphere(&self, center: &[f32; 3], radius: f32) -> impl Iterator<Item = &System> { | ||||
|         self.tree.locate_within_distance(*center, radius * radius) | ||||
|     } | ||||
| 
 | ||||
|     fn neighbours(&self, sys: &System, r: f32) -> impl Iterator<Item = &System> { | ||||
|         self.points_in_sphere(&sys.pos, sys.mult * r) | ||||
|     } | ||||
| 
 | ||||
|     fn valid(&self, sys: &System) -> bool { | ||||
|         self.scoopable.contains(&sys.id) | ||||
|     } | ||||
| 
 | ||||
|     pub fn best_name_multiroute( | ||||
|         &self, | ||||
|         waypoints: &[String], | ||||
|         range: f32, | ||||
|         full: bool, | ||||
|         mode: Mode, | ||||
|         factor: f32, | ||||
|     ) -> Vec<System> { | ||||
|         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(); | ||||
|         let t_start = Instant::now(); | ||||
|         println!("Finding best permutation of hops..."); | ||||
|         while waypoints.prev_permutation() {} | ||||
|         loop { | ||||
|             let c_first = waypoints.first().cloned(); | ||||
|             let c_last = waypoints.last().cloned(); | ||||
|             if full || ((c_first == first) && (c_last == last)) { | ||||
|                 let mut total_d = 0.0; | ||||
|                 for pair in waypoints.windows(2) { | ||||
|                     match pair { | ||||
|                         [src, dst] => { | ||||
|                             let (mut src, dst) = | ||||
|                                 (self.name_to_systems(&src), self.name_to_systems(&dst)); | ||||
|                             src.sort_by_key(|&p| (p.mult * 10.0) as u8); | ||||
|                             let src = src.last().unwrap(); | ||||
|                             let dst = dst.last().unwrap(); | ||||
|                             total_d += src.distp2(dst); | ||||
|                         } | ||||
|                         _ => panic!("Invalid routing parameters!"), | ||||
|                     } | ||||
|                 } | ||||
|                 if total_d < best_score { | ||||
|                     best_score = total_d; | ||||
|                     best_permutation_waypoints = waypoints.to_owned(); | ||||
|                 } | ||||
|             } | ||||
|             if !waypoints.next_permutation() { | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         println!("Done in {}!", format_duration(t_start.elapsed())); | ||||
|         println!("Best permutation: {:?}", best_permutation_waypoints); | ||||
|         self.name_multiroute(&best_permutation_waypoints, range, mode, factor) | ||||
|     } | ||||
| 
 | ||||
|     pub fn name_multiroute( | ||||
|         &self, | ||||
|         waypoints: &[String], | ||||
|         range: f32, | ||||
|         mode: Mode, | ||||
|         factor: f32, | ||||
|     ) -> Vec<System> { | ||||
|         let mut coords = Vec::new(); | ||||
|         for p_name in waypoints { | ||||
|             let mut p_l = self.name_to_systems(p_name); | ||||
|             p_l.sort_by_key(|&p| (p.mult * 10.0) as u8); | ||||
|             let p = p_l.last().unwrap(); | ||||
|             coords.push((p_name, p.pos)); | ||||
|         } | ||||
|         self.multiroute(coords.as_slice(), range, mode, factor) | ||||
|     } | ||||
|     pub fn multiroute( | ||||
|         &self, | ||||
|         waypoints: &[(&String, [f32; 3])], | ||||
|         range: f32, | ||||
|         mode: Mode, | ||||
|         factor: f32, | ||||
|     ) -> Vec<System> { | ||||
|         let mut route: Vec<System> = Vec::new(); | ||||
|         for pair in waypoints.windows(2) { | ||||
|             match *pair { | ||||
|                 [src, dst] => { | ||||
|                     let block = match mode { | ||||
|                         Mode::BFS => self.route_bfs(&src, &dst, range), | ||||
|                         Mode::Greedy => self.route_greedy(&src, &dst, range), | ||||
|                         Mode::AStar => self.route_astar(&src, &dst, range, factor), | ||||
|                     }; | ||||
|                     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()); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 _ => panic!("Invalid routing parameters!"), | ||||
|             } | ||||
|         } | ||||
|         route | ||||
|     } | ||||
| 
 | ||||
|     fn name_to_systems(&self, name: &str) -> Vec<&System> { | ||||
|         for sys in &self.tree { | ||||
|             if sys.system == name { | ||||
|                 return self.neighbours(&sys, 0.0).collect(); | ||||
|             } | ||||
|         } | ||||
|         eprintln!("System not found: \"{}\"", name); | ||||
|         std::process::exit(1); | ||||
|     } | ||||
| 
 | ||||
|     pub fn route_astar( | ||||
|         &self, | ||||
|         src: &(&String, [f32; 3]), | ||||
|         dst: &(&String, [f32; 3]), | ||||
|         range: f32, | ||||
|         factor: f32, | ||||
|     ) -> Vec<System> { | ||||
|         if factor == 0.0 { | ||||
|             return self.route_bfs(src, dst, range); | ||||
|         } | ||||
|         println!("Running A-Star with greedy factor of {}", factor); | ||||
|         let (src_name, src) = src; | ||||
|         let (dst_name, dst) = dst; | ||||
|         let start_sys = self.closest(src); | ||||
|         let goal_sys = self.closest(dst); | ||||
|         { | ||||
|             let d = dist(src, dst); | ||||
|             println!("Plotting route from {} to {}...", src_name, dst_name); | ||||
|             println!( | ||||
|                 "Jump Range: {} Ly, Distance: {} Ly, Theoretical Jumps: {}", | ||||
|                 range, | ||||
|                 d, | ||||
|                 d / range | ||||
|             ); | ||||
|         } | ||||
|         let total = self.tree.size() as f32; | ||||
|         let mut prev = FnvHashMap::default(); | ||||
|         let mut seen = FnvHashSet::default(); | ||||
|         let t_start = Instant::now(); | ||||
|         let mut found = false; | ||||
|         let mut maxd = 0; | ||||
|         let mut queue: Vec<(usize, usize, &System)> = Vec::new(); | ||||
|         queue.push(( | ||||
|             0,                                            // depth
 | ||||
|             (start_sys.distp(goal_sys) / range) as usize, // h
 | ||||
|             &start_sys, | ||||
|         )); | ||||
|         seen.insert(start_sys.id); | ||||
| 
 | ||||
|         while !(queue.is_empty() || found) { | ||||
|             while let Some((depth, _, sys)) = queue.pop() { | ||||
|                 if depth > maxd { | ||||
|                     maxd = depth; | ||||
|                     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(); | ||||
|                 } | ||||
|                 if sys.id == goal_sys.id { | ||||
|                     found = true; | ||||
|                     break; | ||||
|                 } | ||||
|                 queue.extend( | ||||
|                     self.neighbours(&sys, range) | ||||
|                         .filter(|&nb| (self.valid(nb) || (nb.id == goal_sys.id))) | ||||
|                         .filter(|&nb| seen.insert(nb.id)) | ||||
|                         .map(|nb| { | ||||
|                             prev.insert(nb.id, sys); | ||||
|                             let d_g = (nb.distp(goal_sys) / range) as usize; | ||||
|                             (depth + 1, d_g, nb) | ||||
|                         }), | ||||
|                 ); | ||||
|                 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 + a_1 * factor; | ||||
|                     let v_b = b_0 + b_1 * factor; | ||||
|                     fcmp(v_a, v_b) | ||||
|                 }); | ||||
|                 // queue.reverse();
 | ||||
|             } | ||||
|         } | ||||
|         println!(); | ||||
| 
 | ||||
|         println!(); | ||||
|         if !found { | ||||
|             eprintln!("No route from {} to {} found!", src_name, dst_name); | ||||
|             return Vec::new(); | ||||
|         } | ||||
|         let mut v: Vec<System> = Vec::new(); | ||||
|         let mut curr_sys = goal_sys; | ||||
|         loop { | ||||
|             v.push(curr_sys.clone()); | ||||
|             match prev.get(&curr_sys.id) { | ||||
|                 Some(sys) => curr_sys = *sys, | ||||
|                 None => { | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         v.reverse(); | ||||
|         v | ||||
|     } | ||||
| 
 | ||||
|     pub fn route_greedy( | ||||
|         &self, | ||||
|         src: &(&String, [f32; 3]), | ||||
|         dst: &(&String, [f32; 3]), | ||||
|         range: f32, | ||||
|     ) -> Vec<System> { | ||||
|         println!("Running Greedy-Search"); | ||||
|         let (src_name, src) = src; | ||||
|         let (dst_name, dst) = dst; | ||||
|         let start_sys = self.closest(src); | ||||
|         let goal_sys = self.closest(dst); | ||||
|         { | ||||
|             let d = dist(src, dst); | ||||
|             println!("Plotting route from {} to {}...", src_name, dst_name); | ||||
|             println!( | ||||
|                 "Jump Range: {} Ly, Distance: {} Ly, Theoretical Jumps: {}", | ||||
|                 range, | ||||
|                 d, | ||||
|                 d / range | ||||
|             ); | ||||
|         } | ||||
|         let total = self.tree.size() as f32; | ||||
|         let mut prev = FnvHashMap::default(); | ||||
|         let mut seen = FnvHashSet::default(); | ||||
|         let t_start = Instant::now(); | ||||
|         let mut found = false; | ||||
|         let mut maxd = 0; | ||||
|         let mut queue: Vec<(f32, f32, usize, &System)> = Vec::new(); | ||||
|         queue.push((-goal_sys.mult, start_sys.distp2(goal_sys), 0, &start_sys)); | ||||
|         seen.insert(start_sys.id); | ||||
|         while !(queue.is_empty() || found) { | ||||
|             while let Some((_, _, depth, sys)) = queue.pop() { | ||||
|                 if depth > maxd { | ||||
|                     maxd = depth; | ||||
|                     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(); | ||||
|                 } | ||||
|                 if sys.id == goal_sys.id { | ||||
|                     found = true; | ||||
|                     break; | ||||
|                 } | ||||
|                 queue.extend( | ||||
|                     self.neighbours(&sys, range) | ||||
|                         .filter(|&nb| (self.valid(nb) || (nb.id == goal_sys.id))) | ||||
|                         .filter(|&nb| seen.insert(nb.id)) | ||||
|                         .map(|nb| { | ||||
|                             prev.insert(nb.id, sys); | ||||
|                             (-nb.mult, nb.distp2(goal_sys), depth + 1, nb) | ||||
|                         }), | ||||
|                 ); | ||||
|                 queue.sort_by(|a, b| fcmp(a.0, b.0).then(fcmp(a.1, b.1))); | ||||
|                 queue.reverse(); | ||||
|             } | ||||
|         } | ||||
|         println!(); | ||||
|         println!(); | ||||
|         if !found { | ||||
|             eprintln!("No route from {} to {} found!", src_name, dst_name); | ||||
|             return Vec::new(); | ||||
|         } | ||||
|         let mut v: Vec<System> = Vec::new(); | ||||
|         let mut curr_sys = goal_sys; | ||||
|         loop { | ||||
|             v.push(curr_sys.clone()); | ||||
|             match prev.get(&curr_sys.id) { | ||||
|                 Some(sys) => curr_sys = *sys, | ||||
|                 None => { | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         v.reverse(); | ||||
|         v | ||||
|     } | ||||
| 
 | ||||
|     pub fn precompute(&mut self, src: &str) { | ||||
|         let mut sys_l = self.name_to_systems(src); | ||||
|         sys_l.sort_by_key(|&sys| (sys.mult * 10.0) as u8); | ||||
|         let sys = sys_l.last().unwrap(); | ||||
|         println!("Precomputing routes starting at {} ...", sys.system); | ||||
|         let total = self.tree.size() as f32; | ||||
|         let mut prev = FnvHashMap::default(); | ||||
|         let mut seen = FnvHashSet::default(); | ||||
|         let t_start = Instant::now(); | ||||
|         let mut depth = 0; | ||||
|         let mut queue: VecDeque<(usize, &System)> = VecDeque::new(); | ||||
|         let mut queue_next: VecDeque<(usize, &System)> = VecDeque::new(); | ||||
|         queue.push_front((0, &sys)); | ||||
|         seen.insert(sys.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, self.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 ofn = format!( | ||||
|             "{}_{}{}.router", | ||||
|             src.replace("*", "").replace(" ", "_"), | ||||
|             self.range, | ||||
|             if self.primary_only { "_primary" } else { "" } | ||||
|         ); | ||||
|         println!("\nSaving to {}", ofn); | ||||
|         let mut out_fh = BufWriter::new(File::create(&ofn).unwrap()); | ||||
|         // (range, path, route_tree)
 | ||||
|         let data = ( | ||||
|             self.primary_only, | ||||
|             self.range, | ||||
|             hash_file(&self.path), | ||||
|             String::from(self.path.to_str().unwrap()), | ||||
|             self.route_tree.as_ref().unwrap(), | ||||
|         ); | ||||
|         bincode::serialize_into(&mut out_fh, &data).unwrap(); | ||||
|     } | ||||
| 
 | ||||
|     fn get_systems_by_ids(&mut self, path: &PathBuf, ids: &[u32]) -> FnvHashMap<u32, System> { | ||||
|         println!("Processing {}", path.to_str().unwrap()); | ||||
|         let mut ret = FnvHashMap::default(); | ||||
|         if let Some(c) = &mut self.cache.as_mut() { | ||||
|             let mut missing = false; | ||||
|             for id in ids { | ||||
|                 match c.get(*id) { | ||||
|                     Some(sys) => { | ||||
|                         ret.insert(*id, sys); | ||||
|                     } | ||||
|                     None => { | ||||
|                         println!("ID {} not found in cache", id); | ||||
|                         missing = true; | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             if !missing { | ||||
|                 return ret; | ||||
|             } | ||||
|         } | ||||
|         let mut reader = csv::ReaderBuilder::new() | ||||
|             .from_path(path) | ||||
|             .unwrap_or_else(|e| { | ||||
|                 println!("Error opening {}: {}", path.to_str().unwrap(), e); | ||||
|                 std::process::exit(1); | ||||
|             }); | ||||
|         reader | ||||
|             .deserialize::<SystemSerde>() | ||||
|             .map(|res| res.unwrap()) | ||||
|             .filter(|sys| ids.contains(&sys.id)) | ||||
|             .map(|sys| { | ||||
|                 ret.insert(sys.id, sys.build()); | ||||
|             }) | ||||
|             .last() | ||||
|             .unwrap_or_else(|| { | ||||
|                 eprintln!("Error: No systems matching {:?} found!", ids); | ||||
|                 std::process::exit(1); | ||||
|             }); | ||||
|         ret | ||||
|     } | ||||
| 
 | ||||
|     fn get_system_by_name(path: &PathBuf, name: &str) -> System { | ||||
|         let mut reader = csv::ReaderBuilder::new() | ||||
|             .from_path(path) | ||||
|             .unwrap_or_else(|e| { | ||||
|                 eprintln!("Error opening {}: {}", path.to_str().unwrap(), e); | ||||
|                 std::process::exit(1); | ||||
|             }); | ||||
|         let sys = reader | ||||
|             .deserialize::<SystemSerde>() | ||||
|             .map(|res| res.unwrap()) | ||||
|             .find(|sys| sys.system == name) | ||||
|             .unwrap_or_else(|| { | ||||
|                 eprintln!("Error: System '{}' not found!", name); | ||||
|                 std::process::exit(1); | ||||
|             }); | ||||
|         sys.build() | ||||
|     } | ||||
| 
 | ||||
|     pub fn route_to(&mut self, dst: &str, systems_path: &PathBuf) -> Vec<System> { | ||||
|         let prev = self.route_tree.as_ref().unwrap(); | ||||
|         let dst = Self::get_system_by_name(&systems_path, dst); | ||||
|         if !prev.contains_key(&dst.id) { | ||||
|             eprintln!("System-ID {} not found", dst.id); | ||||
|             std::process::exit(1); | ||||
|         }; | ||||
|         let mut v_ids: Vec<u32> = Vec::new(); | ||||
|         let mut v: Vec<System> = 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(&systems_path, &v_ids); | ||||
|         for sys_id in v_ids { | ||||
|             let sys = id_map.get(&sys_id).unwrap(); | ||||
|             v.push(sys.clone()) | ||||
|         } | ||||
|         v | ||||
|     } | ||||
| 
 | ||||
|     pub fn route_bfs( | ||||
|         &self, | ||||
|         src: &(&String, [f32; 3]), | ||||
|         dst: &(&String, [f32; 3]), | ||||
|         range: f32, | ||||
|     ) -> Vec<System> { | ||||
|         println!("Running BFS"); | ||||
|         let (src_name, src) = src; | ||||
|         let (dst_name, dst) = dst; | ||||
|         let start_sys = self.closest(src); | ||||
|         let goal_sys = self.closest(dst); | ||||
|         { | ||||
|             let d = dist(src, dst); | ||||
|             println!("Plotting route from {} to {}...", src_name, dst_name); | ||||
|             println!( | ||||
|                 "Jump Range: {} Ly, Distance: {} Ly, Theoretical Jumps: {}", | ||||
|                 range, | ||||
|                 d, | ||||
|                 d / range | ||||
|             ); | ||||
|         } | ||||
|         let total = self.tree.size() as f32; | ||||
|         let mut prev = FnvHashMap::default(); | ||||
|         let mut seen = FnvHashSet::default(); | ||||
|         let t_start = Instant::now(); | ||||
|         let mut depth = 0; | ||||
|         let mut found = false; | ||||
|         let mut queue: VecDeque<(usize, &System)> = VecDeque::new(); | ||||
|         let mut queue_next: VecDeque<(usize, &System)> = VecDeque::new(); | ||||
|         queue.push_front((0, &start_sys)); | ||||
|         seen.insert(start_sys.id); | ||||
|         while !(queue.is_empty() || found) { | ||||
|             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() { | ||||
|                 if sys.id == goal_sys.id { | ||||
|                     found = true; | ||||
|                     break; | ||||
|                 } | ||||
|                 queue_next.extend( | ||||
|                     self.neighbours(&sys, range) | ||||
|                         .filter(|&nb| (self.valid(nb) || (nb.id == goal_sys.id))) | ||||
|                         .filter(|&nb| seen.insert(nb.id)) | ||||
|                         .map(|nb| { | ||||
|                             prev.insert(nb.id, sys); | ||||
|                             (d + 1, nb) | ||||
|                         }), | ||||
|                 ); | ||||
|             } | ||||
|             std::mem::swap(&mut queue, &mut queue_next); | ||||
|             depth += 1; | ||||
|         } | ||||
|         println!(); | ||||
|         println!(); | ||||
|         if !found { | ||||
|             eprintln!("No route from {} to {} found!", src_name, dst_name); | ||||
|             return Vec::new(); | ||||
|         } | ||||
|         let mut v: Vec<System> = Vec::new(); | ||||
|         let mut curr_sys = goal_sys; | ||||
|         loop { | ||||
|             v.push(curr_sys.clone()); | ||||
|             match prev.get(&curr_sys.id) { | ||||
|                 Some(sys) => curr_sys = *sys, | ||||
|                 None => { | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         v.reverse(); | ||||
|         v | ||||
|     } | ||||
| } | ||||
| pub fn route(opts: RouteOpts) -> std::io::Result<()> { | ||||
|     if opts.systems.is_empty() { | ||||
|         if opts.precomp_file.is_some() { | ||||
|             eprintln!("Error: Please specify exatly one system"); | ||||
|         } else if opts.precompute { | ||||
|             eprintln!("Error: Please specify at least one system"); | ||||
|         } else { | ||||
|             eprintln!("Error: Please specify at least two systems"); | ||||
|         } | ||||
|         std::process::exit(1); | ||||
|     } | ||||
|     let mut path = opts.file_path; | ||||
|     let mut router: Router = if opts.precomp_file.is_some() { | ||||
|         let ret = Router::from_file(&opts.precomp_file.clone().unwrap()); | ||||
|         path = ret.0; | ||||
|         ret.1 | ||||
|     } else { | ||||
|         Router::new(&path, opts.range.unwrap(), opts.primary) | ||||
|     }; | ||||
|     if opts.precompute { | ||||
|         for sys in opts.systems { | ||||
|             router.route_tree = None; | ||||
|             router.precompute(&sys); | ||||
|         } | ||||
|         std::process::exit(0); | ||||
|     } | ||||
|     let t_route = Instant::now(); | ||||
|     let route = if router.route_tree.is_some() { | ||||
|         router.route_to(opts.systems.first().unwrap(), &path) | ||||
|     } else if opts.permute || opts.full_permute { | ||||
|         router.best_name_multiroute( | ||||
|             &opts.systems, | ||||
|             opts.range.unwrap(), | ||||
|             opts.full_permute, | ||||
|             opts.mode, | ||||
|             opts.factor.unwrap_or(1.0), | ||||
|         ) | ||||
|     } else { | ||||
|         router.name_multiroute( | ||||
|             &opts.systems, | ||||
|             opts.range.unwrap(), | ||||
|             opts.mode, | ||||
|             opts.factor.unwrap_or(1.0), | ||||
|         ) | ||||
|     }; | ||||
|     println!("Route computed in {}\n", format_duration(t_route.elapsed())); | ||||
|     if route.is_empty() { | ||||
|         eprintln!("No route found!"); | ||||
|         return Ok(()); | ||||
|     } | ||||
|     let mut total: f32 = 0.0; | ||||
|     for (sys1, sys2) in route.iter().zip(route.iter().skip(1)) { | ||||
|         let dist = sys1.distp(sys2); | ||||
|         total += dist; | ||||
|         println!( | ||||
|             "{} [{}] ({},{},{}) [{} Ls]: {:.2} Ly", | ||||
|             format!("{} {}", sys1.system, sys1.system).trim_end(), | ||||
|             sys1.star_type, | ||||
|             sys1.pos[0], | ||||
|             sys1.pos[1], | ||||
|             sys1.pos[2], | ||||
|             sys1.distance, | ||||
|             dist | ||||
|         ); | ||||
|     } | ||||
|     let sys = route.iter().last().unwrap(); | ||||
|     println!( | ||||
|         "{} [{}] ({},{},{}) [{} Ls]: {:.2} Ly", | ||||
|         sys.system, sys.star_type, sys.pos[0], sys.pos[1], sys.pos[2], sys.distance, 0.0 | ||||
|     ); | ||||
|     println!("Total: {:.2} Ly ({} Jumps)", total, route.len()); | ||||
|     Ok(()) | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue