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 | dumps/*.json | ||||||
| plot.py | plot.py | ||||||
| *.tmp | *.tmp | ||||||
|  | *.idx | ||||||
|  |  | ||||||
							
								
								
									
										1
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
								
							|  | @ -174,6 +174,7 @@ name = "ed_lrr" | ||||||
| version = "0.1.0" | version = "0.1.0" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "bincode 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)", |  "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)", |  "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)", |  "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)", |  "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" | repository = "https://gitlab.com/Earthnuker/ed_lrr.git" | ||||||
| license = "WTFPL" | license = "WTFPL" | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| [profile.release] | [profile.release] | ||||||
| # debug=true | # debug=true | ||||||
| 
 | 
 | ||||||
|  | @ -22,3 +23,4 @@ indicatif = "0.11.0" | ||||||
| fnv = "1.0.6" | fnv = "1.0.6" | ||||||
| bincode = "1.1.4" | bincode = "1.1.4" | ||||||
| sha3 = "0.8.2" | 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; | ||||||
							
								
								
									
										922
									
								
								src/main.rs
									
										
									
									
									
								
							
							
						
						
									
										922
									
								
								src/main.rs
									
										
									
									
									
								
							|  | @ -1,926 +1,28 @@ | ||||||
| use core::cmp::Ordering; | use ed_lrr::preprocess::{preprocess_files, PreprocessOpts}; | ||||||
| use fnv::{FnvHashMap, FnvHashSet}; | use ed_lrr::route::{route, RouteOpts}; | ||||||
| use humantime::format_duration; | 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 std::time::Instant; | ||||||
| use structopt::StructOpt; | 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)] | #[derive(Debug, StructOpt)] | ||||||
| #[structopt(
 | #[structopt(
 | ||||||
|     name = "ed_lrr", |     name = "ed_lrr", | ||||||
|     about = "Elite: Dangerous Long-Range Router", |     about = "Elite: Dangerous Long-Range Router", | ||||||
|     rename_all = "snake_case" |     rename_all = "snake_case" | ||||||
| )] | )] | ||||||
| /// Plots a route through multiple systems
 | enum Opts { | ||||||
| struct Opts { |     /// Plots a route through multiple systems
 | ||||||
|     #[structopt(short, long = "range")] |     Route(RouteOpts), | ||||||
|     /// Jump Range
 |     /// Preprocess EDSM Dump
 | ||||||
|     range: Option<f32>, |     Preprocess(PreprocessOpts), | ||||||
|     #[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; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| fn dist(p1: &[f32; 3], p2: &[f32; 3]) -> f32 { | fn main() -> std::io::Result<()> { | ||||||
|     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() { |  | ||||||
|     let t_start = Instant::now(); |     let t_start = Instant::now(); | ||||||
|     let opts = Opts::from_args(); |     let opts = Opts::from_args(); | ||||||
|     if opts.systems.len() == 0 { |     let ret = match opts { | ||||||
|         if opts.precomp_file.is_some() { |         Opts::Route(opts) => route(opts), | ||||||
|             eprintln!("Error: Please specify exatly one system"); |         Opts::Preprocess(opts) => preprocess_files(opts), | ||||||
|         } 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); |  | ||||||
|             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())); |     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