// #![deny(warnings)] #![allow(dead_code, clippy::needless_return, clippy::too_many_arguments)] pub mod common; pub mod edsm; pub mod galaxy; pub mod journal; pub mod route; pub mod ship; extern crate derivative; use crate::common::{find_matches, SysEntry}; use crate::route::{Router, SearchState}; use crate::ship::Ship; use pyo3::exceptions::*; use pyo3::prelude::*; use pyo3::types::{PyDict, PyList, PyTuple}; use pyo3::PyObjectProtocol; use std::path::PathBuf; #[pyclass(dict)] #[derive(Debug)] #[text_signature = "(callback, /)"] struct PyRouter { router: Router, primary_only: bool, stars_path: String, } #[pymethods] impl PyRouter { #[new] #[args(callback = "None")] fn new(callback: Option, py: Python<'static>) -> PyResult { Ok(PyRouter { router: Router::new(Box::new( move |state: &SearchState| { match callback.as_ref() { Some(cb) => cb.call(py, (state.clone(),), None), None => Ok(py.None()), } } )), primary_only: false, stars_path: String::from(""), }) } #[text_signature = "(ship, /)"] fn set_ship(&mut self, py: Python, ship: &PyShip) -> PyResult { self.router.set_ship(ship.ship.clone()); Ok(py.None()) } #[args(primary_only = "false")] #[text_signature = "(path, primary_only, /)"] fn load( &mut self, path: String, primary_only: bool, py: Python, ) -> PyResult { self.stars_path = path; self.primary_only = primary_only; Ok(py.None()) } #[args(greedyness = "0.5", num_workers = "0", beam_width = "0")] #[text_signature = "(hops, range, greedyness, beam_width, num_workers, /)"] fn route( &mut self, hops: &PyList, range: Option, greedyness: f32, beam_width: usize, num_workers: usize, py: Python, ) -> PyResult { let route_res = self .router .load(&PathBuf::from(self.stars_path.clone()), self.primary_only); if let Err(err_msg) = route_res { return Err(PyErr::new::(err_msg)); }; let mut sys_entries: Vec = Vec::new(); for hop in hops { if let Ok(id) = hop.extract() { sys_entries.push(SysEntry::ID(id)); } else { sys_entries.push(SysEntry::parse(hop.extract()?)); } } println!("Resolving systems..."); let ids: Vec = match resolve(&sys_entries, &self.router.path) { Ok(ids) => ids, Err(err_msg) => { return Err(PyErr::new::(err_msg)); } }; match self .router .compute_route(&ids, range, greedyness, beam_width, num_workers) { Ok(route) => { let py_route: Vec<_> = route.iter().map(|hop| hop.to_object(py)).collect(); Ok(py_route.to_object(py)) } Err(err_msg) => Err(PyErr::new::(err_msg)), } } #[args(hops = "*")] #[text_signature = "(sys_1, sys_2, ..., /)"] fn resolve_systems(&self, hops: &PyTuple, py: Python) -> PyResult { let mut sys_entries: Vec = Vec::new(); for hop in hops { if let Ok(id) = hop.extract() { sys_entries.push(SysEntry::ID(id)); } else { sys_entries.push(SysEntry::parse(hop.extract()?)); } } println!("Resolving systems..."); let stars_path = PathBuf::from(self.stars_path.clone()); let ids: Vec = match resolve(&sys_entries, &stars_path) { Ok(ids) => ids, Err(err_msg) => { return Err(PyErr::new::(err_msg)); } }; let ret: Vec<(_, u32)> = hops.into_iter().zip(ids.into_iter()).collect(); Ok(PyDict::from_sequence(py, ret.to_object(py))?.to_object(py)) } #[staticmethod] fn preprocess_edsm() -> PyResult<()> { todo!("Implement EDSM Preprocessor") } #[staticmethod] fn preprocess_galaxy() -> PyResult<()> { todo!("Implement galaxy.json Preprocessor") } } #[pyproto] impl PyObjectProtocol for PyRouter { fn __str__(&self) -> PyResult { Ok(format!("{:?}", &self)) } fn __repr__(&self) -> PyResult { Ok(format!("{:?}", &self)) } } fn resolve(entries: &[SysEntry], path: &PathBuf) -> Result, String> { let mut names: Vec = Vec::new(); let mut ids: Vec = Vec::new(); let mut ret: Vec = Vec::new(); for ent in entries { match ent { SysEntry::Name(name) => names.push(name.to_owned()), SysEntry::ID(id) => ids.push(*id), } } if !path.exists() { return Err(format!( "Source file \"{:?}\" does not exist!", path.display() )); } let name_ids = find_matches(path, names, false)?; for ent in entries { match ent { SysEntry::Name(name) => { let ent_res = name_ids .get(&name.to_owned()) .ok_or(format!("System {} not found", name))?; let sys = ent_res .1 .as_ref() .ok_or(format!("System {} not found", name))?; if ent_res.0 < 0.75 { println!( "WARNING: {} match to {} with low confidence ({:.2}%)", name, sys.system, ent_res.0 * 100.0 ); } ret.push(sys.id); } SysEntry::ID(id) => ret.push(*id), } } Ok(ret) } #[pyclass(dict)] #[derive(Debug)] struct PyShip { ship: Ship, } #[pyproto] impl PyObjectProtocol for PyShip { fn __str__(&self) -> PyResult { Ok(format!("{:?}", &self.ship)) } fn __repr__(&self) -> PyResult { Ok(format!("{:?}", &self.ship)) } } #[pymethods] impl PyShip { #[staticmethod] fn from_loadout(py: Python, loadout: &str) -> PyResult { match Ship::new_from_json(loadout) { Ok(ship) => Ok((PyShip { ship }).into_py(py)), Err(err_msg) => Err(PyErr::new::(err_msg)), } } #[staticmethod] fn from_journal(py: Python) -> PyResult { let mut ship = match Ship::new_from_journal() { Ok(ship) => ship, Err(err_msg) => { return Err(PyErr::new::(err_msg)); } }; let ships: Vec<(PyObject, PyObject)> = ship .drain() .map(|(k, v)| { let k_py = k.to_object(py); let v_py = (PyShip { ship: v }).into_py(py); (k_py, v_py) }) .collect(); Ok(PyDict::from_sequence(py, ships.to_object(py))?.to_object(py)) } fn to_dict(&self, py: Python) -> PyResult { self.ship.to_object(py) } #[text_signature = "(dist, /)"] fn fuel_cost(&self, _py: Python, dist: f32) -> PyResult { Ok(self.ship.fuel_cost(dist)) } #[text_signature = "(/)"] fn range(&self, _py: Python) -> PyResult { Ok(self.ship.range()) } #[text_signature = "(/)"] fn max_range(&self, _py: Python) -> PyResult { Ok(self.ship.max_range()) } #[text_signature = "(dist, /)"] fn make_jump(&mut self, dist: f32, _py: Python) -> PyResult> { Ok(self.ship.make_jump(dist)) } #[text_signature = "(dist, /)"] fn can_jump(&self, dist: f32, _py: Python) -> PyResult { Ok(self.ship.can_jump(dist)) } #[args(fuel_amount = "None")] #[text_signature = "(fuel_amount, /)"] fn refuel(&mut self, fuel_amount: Option, _py: Python) -> PyResult<()> { if let Some(fuel) = fuel_amount { self.ship.fuel_mass = (self.ship.fuel_mass + fuel).min(self.ship.fuel_capacity) } else { self.ship.fuel_mass = self.ship.fuel_capacity; } Ok(()) } #[text_signature = "(factor, /)"] fn boost(&mut self, factor: f32, _py: Python) -> PyResult<()> { self.ship.boost(factor); Ok(()) } } #[pymodule] pub fn _ed_lrr(_py: Python, m: &PyModule) -> PyResult<()> { better_panic::install(); m.add_class::()?; m.add_class::()?; /* #[pyfn(m, "get_ships_from_journal")] fn get_ships_from_journal(py: Python) -> PyResult { let ship = match Ship::new_from_journal() { Ok(ship) => ship, Err(err_msg) => { return Err(PyErr::new::(err_msg)); } }; let ships: Vec<(_,_)> = ship.iter().map(|(k,v)| (k.to_object(py),v.to_object(py))).collect(); Ok(PyDict::from_sequence(py, ships.to_object(py))?.to_object(py)) } #[pyfn(m, "get_ships_from_loadout")] fn get_ship_from_loadout(py: Python, loadout: &str) -> PyResult { let ship = match Ship::new_from_json(loadout) { Ok(ship) => ship, Err(err_msg) => { return Err(PyErr::new::(err_msg)); } }; Ok(ship.to_object(py)) } */ Ok(()) } /* /// Preprocess bodies.json and systemsWithCoordinates.json into stars.csv #[pyfn(m, "preprocess")] #[text_signature = "(infile_systems, infile_bodies, outfile, callback, /)"] fn ed_lrr_preprocess( py: Python<'static>, infile_systems: String, infile_bodies: String, outfile: String, callback: PyObject, ) -> PyResult { use preprocess::*; let state = PyDict::new(py); let state_dict = PyDict::new(py); callback.call(py, (state_dict,), None).unwrap(); let callback_wrapped = move |state: &PreprocessState| { // println!("SEND: {:?}",state); state_dict.set_item("file", state.file.clone())?; state_dict.set_item("total", state.total)?; state_dict.set_item("count", state.count)?; state_dict.set_item("done", state.done)?; state_dict.set_item("message", state.message.clone())?; callback.call(py, (state_dict,), None) }; preprocess_files( &PathBuf::from(infile_bodies), &PathBuf::from(infile_systems), &PathBuf::from(outfile), &callback_wrapped, ) .unwrap(); Ok(state.to_object(py)) } /// Find system by name #[pyfn(m, "find_sys")] #[text_signature = "(sys_names, sys_list_path, /)"] fn find_sys(py: Python, sys_names: Vec, sys_list: String) -> PyResult { let path = PathBuf::from(sys_list); match find_matches(&path, sys_names, false) { Ok(vals) => { let ret = PyDict::new(py); for (key, (diff, sys)) in vals { let ret_dict = PyDict::new(py); if let Some(val) = sys { let pos = PyList::new(py, val.pos.iter()); ret_dict.set_item("star_type", val.star_type.clone())?; ret_dict.set_item("system", val.system.clone())?; ret_dict.set_item("body", val.body.clone())?; ret_dict.set_item("distance", val.distance)?; ret_dict.set_item("pos", pos)?; ret_dict.set_item("id", val.id)?; ret.set_item(key, (diff, ret_dict).to_object(py))?; } } Ok(ret.to_object(py)) } Err(e) => Err(PyErr::new::(e)), } } /// Compute a Route using the suplied parameters #[pyfn(m, "route")] #[text_signature = "(hops, range, mode, primary, permute, keep_first, keep_last, greedyness, precomp, path, num_workers, callback, /)"] #[allow(clippy::too_many_arguments)] fn py_route( py: Python<'static>, hops: Vec<&str>, range: f32, mode: String, primary: bool, permute: bool, keep_first: bool, keep_last: bool, greedyness: Option, precomp: Option, path: String, num_workers: Option, callback: PyObject, ) -> PyResult { use route::*; let num_workers = num_workers.unwrap_or(1); let mode = match Mode::parse(&mode) { Ok(val) => val, Err(e) => { return Err(PyErr::new::(e)); } }; let state_dict = PyDict::new(py); { let cb_res = callback.call(py, (state_dict,), None); if cb_res.is_err() { println!("Error: {:?}", cb_res); } } let callback_wrapped = move |state: &SearchState| { state_dict.set_item("mode", state.mode.clone())?; state_dict.set_item("system", state.system.clone())?; state_dict.set_item("body", state.body.clone())?; state_dict.set_item("depth", state.depth)?; state_dict.set_item("queue_size", state.queue_size)?; state_dict.set_item("d_rem", state.d_rem)?; state_dict.set_item("d_total", state.d_total)?; state_dict.set_item("prc_done", state.prc_done)?; state_dict.set_item("n_seen", state.n_seen)?; state_dict.set_item("prc_seen", state.prc_seen)?; state_dict.set_item("from", state.from.clone())?; state_dict.set_item("to", state.to.clone())?; let cb_res = callback.call(py, (state_dict,), None); if cb_res.is_err() { println!("Error: {:?}", cb_res); } cb_res }; let hops: Vec = (hops.iter().map(|v| SysEntry::from_str(&v)).collect::,_>>())?; println!("Resolving systems..."); let hops: Vec = match resolve(&hops, &PathBuf::from(&path)) { Ok(ids) => ids, Err(err_msg) => { return Err(PyErr::new::(err_msg)); } }; let opts = RouteOpts { systems: hops, range: Some(range), file_path: PathBuf::from(path), precomp_file: precomp.map(PathBuf::from), callback: Box::new(callback_wrapped), mode, factor: greedyness, precompute: false, permute, keep_first, keep_last, primary, workers: num_workers, }; match route(opts) { Ok(Some(route)) => { let hops = route.iter().map(|hop| { let pos = PyList::new(py, hop.pos.iter()); let elem = PyDict::new(py); elem.set_item("star_type", hop.star_type.clone()).unwrap(); elem.set_item("system", hop.system.clone()).unwrap(); elem.set_item("body", hop.body.clone()).unwrap(); elem.set_item("distance", hop.distance).unwrap(); elem.set_item("pos", pos).unwrap(); elem }); let lst = PyList::new(py, hops); Ok(lst.to_object(py)) } Ok(None) => Ok(py.None()), Err(e) => Err(PyErr::new::(e)), } } */