2022-06-14 21:00:50 +00:00
|
|
|
#![feature(binary_heap_retain)]
|
2020-06-16 13:38:31 +00:00
|
|
|
#![allow(dead_code, clippy::needless_return, clippy::too_many_arguments)]
|
2022-06-14 21:00:50 +00:00
|
|
|
#![warn(rust_2018_idioms, rust_2021_compatibility, clippy::disallowed_types)]
|
2022-02-23 21:45:59 +00:00
|
|
|
//! # Elite: Danerous Long Range Router
|
2020-06-16 13:38:31 +00:00
|
|
|
pub mod common;
|
|
|
|
pub mod galaxy;
|
|
|
|
pub mod journal;
|
2022-02-23 21:45:59 +00:00
|
|
|
pub mod mmap_csv;
|
|
|
|
#[cfg(feature = "profiling")]
|
|
|
|
pub mod profiling;
|
2020-06-16 13:38:31 +00:00
|
|
|
pub mod route;
|
2022-02-23 21:45:59 +00:00
|
|
|
pub mod search_algos;
|
2020-06-16 13:38:31 +00:00
|
|
|
pub mod ship;
|
2020-03-28 13:53:52 +00:00
|
|
|
|
2022-02-23 21:45:59 +00:00
|
|
|
// =========================
|
|
|
|
use stats_alloc::{Region, StatsAlloc, INSTRUMENTED_SYSTEM};
|
|
|
|
use std::alloc::System as SystemAlloc;
|
|
|
|
use std::collections::BTreeMap;
|
|
|
|
use std::io::{BufWriter, Write};
|
|
|
|
use std::path::Path;
|
|
|
|
use std::time::Instant;
|
|
|
|
#[cfg(not(feature = "profiling"))]
|
|
|
|
#[global_allocator]
|
|
|
|
static GLOBAL: &StatsAlloc<SystemAlloc> = &INSTRUMENTED_SYSTEM;
|
|
|
|
// =========================
|
|
|
|
#[cfg(not(feature = "profiling"))]
|
|
|
|
mod profiling {
|
|
|
|
pub fn init() {}
|
|
|
|
}
|
|
|
|
|
2022-06-14 21:00:50 +00:00
|
|
|
use crate::common::{grid_stats, EdLrrError, SysEntry, System};
|
2022-02-23 21:45:59 +00:00
|
|
|
#[cfg(feature = "profiling")]
|
|
|
|
use crate::profiling::*;
|
2020-03-28 13:53:52 +00:00
|
|
|
use crate::route::{Router, SearchState};
|
2020-06-16 13:38:31 +00:00
|
|
|
use crate::ship::Ship;
|
2022-02-23 21:45:59 +00:00
|
|
|
use eyre::Result;
|
|
|
|
#[cfg(not(feature = "profiling"))]
|
|
|
|
use log::*;
|
2022-06-14 21:00:50 +00:00
|
|
|
use pyo3::create_exception;
|
2020-03-28 13:53:52 +00:00
|
|
|
use pyo3::exceptions::*;
|
|
|
|
use pyo3::prelude::*;
|
2022-02-23 21:45:59 +00:00
|
|
|
use pyo3::types::{IntoPyDict, PyDict, PyTuple};
|
2022-06-14 21:00:50 +00:00
|
|
|
use route::PyModeConfig;
|
|
|
|
use std::{collections::HashMap, convert::TryInto, fs::File, path::PathBuf};
|
2022-02-23 21:45:59 +00:00
|
|
|
|
2022-06-14 21:00:50 +00:00
|
|
|
#[cfg(feature = "mem_profiling")]
|
2022-02-23 21:45:59 +00:00
|
|
|
#[global_allocator]
|
|
|
|
static GLOBAL: ProfiledAllocator<std::alloc::System> =
|
|
|
|
ProfiledAllocator::new(std::alloc::System, 1024);
|
|
|
|
|
|
|
|
create_exception!(_ed_lrr, RoutingError, PyException);
|
|
|
|
create_exception!(_ed_lrr, ProcessingError, PyException);
|
|
|
|
create_exception!(_ed_lrr, ResolveError, PyException);
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
enum RangeOrShip {
|
|
|
|
Range(f32),
|
|
|
|
Ship(Ship),
|
|
|
|
}
|
2020-03-28 13:53:52 +00:00
|
|
|
|
2022-02-23 21:45:59 +00:00
|
|
|
impl FromPyObject<'_> for RangeOrShip {
|
|
|
|
fn extract(ob: &PyAny) -> PyResult<Self> {
|
|
|
|
if let Ok(n) = ob.extract() {
|
|
|
|
return Ok(Self::Range(n));
|
|
|
|
}
|
|
|
|
let s: PyShip = ob.extract()?;
|
|
|
|
return Ok(Self::Ship(s.ship));
|
|
|
|
}
|
|
|
|
}
|
2020-03-28 13:53:52 +00:00
|
|
|
#[pyclass(dict)]
|
2020-06-16 13:38:31 +00:00
|
|
|
#[derive(Debug)]
|
2022-02-23 21:45:59 +00:00
|
|
|
#[pyo3(text_signature = "(callback, /)")]
|
2020-03-28 13:53:52 +00:00
|
|
|
struct PyRouter {
|
|
|
|
router: Router,
|
2022-02-23 21:45:59 +00:00
|
|
|
stars_path: Option<String>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl PyRouter {
|
|
|
|
fn check_stars(&self) -> PyResult<PathBuf> {
|
|
|
|
self.stars_path
|
|
|
|
.as_ref()
|
|
|
|
.ok_or_else(|| PyErr::from(EdLrrError::RuntimeError("no stars.csv loaded".to_owned())))
|
|
|
|
.map(PathBuf::from)
|
|
|
|
}
|
2020-03-28 13:53:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[pymethods]
|
|
|
|
impl PyRouter {
|
|
|
|
#[new]
|
|
|
|
#[args(callback = "None")]
|
2022-02-23 21:45:59 +00:00
|
|
|
fn new(callback: Option<PyObject>) -> Self {
|
|
|
|
let mut router = Router::new();
|
|
|
|
if callback.is_some() {
|
|
|
|
router.set_callback(Box::new(move |state: &SearchState| {
|
|
|
|
let gil_guard = Python::acquire_gil();
|
|
|
|
let py = gil_guard.python();
|
|
|
|
match callback.as_ref() {
|
|
|
|
Some(cb) => cb.call(py, (state.clone(),), None),
|
|
|
|
None => Ok(py.None()),
|
2020-06-16 13:38:31 +00:00
|
|
|
}
|
2022-02-23 21:45:59 +00:00
|
|
|
}))
|
|
|
|
}
|
|
|
|
PyRouter {
|
|
|
|
router,
|
|
|
|
stars_path: None,
|
|
|
|
}
|
2020-03-28 13:53:52 +00:00
|
|
|
}
|
|
|
|
|
2022-02-23 21:45:59 +00:00
|
|
|
#[args(primary_only = "false", immediate = "false")]
|
|
|
|
#[pyo3(text_signature = "(path, primary_only, /)")]
|
2022-06-14 21:00:50 +00:00
|
|
|
fn load(&mut self, path: String, py: Python<'_>, immediate: bool) -> PyResult<PyObject> {
|
2022-02-23 21:45:59 +00:00
|
|
|
self.stars_path = Some(path);
|
|
|
|
if immediate {
|
2022-06-14 21:00:50 +00:00
|
|
|
self.router
|
|
|
|
.load(&self.check_stars()?)
|
|
|
|
.map_err(PyErr::new::<PyValueError, _>)?;
|
2022-02-23 21:45:59 +00:00
|
|
|
}
|
2020-06-16 13:38:31 +00:00
|
|
|
Ok(py.None())
|
|
|
|
}
|
|
|
|
|
2022-02-23 21:45:59 +00:00
|
|
|
#[pyo3(text_signature = "(/)")]
|
2022-06-14 21:00:50 +00:00
|
|
|
fn unload(&mut self, py: Python<'_>) -> PyObject {
|
2022-02-23 21:45:59 +00:00
|
|
|
self.router.unload();
|
|
|
|
py.None()
|
|
|
|
}
|
|
|
|
|
2022-06-14 21:00:50 +00:00
|
|
|
fn plot(&mut self, py: Python<'_>) -> PyResult<PyObject> {
|
2022-02-23 21:45:59 +00:00
|
|
|
let stars_path = self.check_stars()?;
|
|
|
|
let route_res = self.router.load(&stars_path);
|
|
|
|
if let Err(err_msg) = route_res {
|
|
|
|
return Err(PyErr::new::<PyValueError, _>(err_msg));
|
|
|
|
};
|
|
|
|
let mut max_v = [0f32, 0f32, 0f32];
|
|
|
|
let mut min_v = [0f32, 0f32, 0f32];
|
|
|
|
for node in self.router.get_tree().iter() {
|
|
|
|
for i in 0..3 {
|
|
|
|
if node.pos[i] > max_v[i] {
|
|
|
|
max_v[i] = node.pos[i];
|
|
|
|
}
|
|
|
|
if node.pos[i] < min_v[i] {
|
|
|
|
min_v[i] = node.pos[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let plot_bbox: ((f32, f32), (f32, f32)) = ((min_v[0], max_v[0]), (min_v[2], max_v[2]));
|
|
|
|
Ok(plot_bbox.to_object(py))
|
|
|
|
}
|
|
|
|
|
2022-06-14 21:00:50 +00:00
|
|
|
fn run_bfs(&mut self, range: f32, py: Python<'_>) -> PyResult<PyObject> {
|
2022-02-23 21:45:59 +00:00
|
|
|
let stars_path = self.check_stars()?;
|
|
|
|
let route_res = self.router.load(&stars_path);
|
|
|
|
if let Err(err_msg) = route_res {
|
|
|
|
return Err(PyErr::new::<PyValueError, _>(err_msg));
|
|
|
|
};
|
|
|
|
self.router
|
|
|
|
.precomp_bfs(range)
|
|
|
|
.map_err(PyErr::new::<RoutingError, _>)
|
|
|
|
.map(|_| py.None())
|
|
|
|
}
|
|
|
|
|
2022-06-14 21:00:50 +00:00
|
|
|
fn precompute_graph(&mut self, range: f32, py: Python<'_>) -> PyResult<PyObject> {
|
2022-02-23 21:45:59 +00:00
|
|
|
let stars_path = self.check_stars()?;
|
|
|
|
let route_res = self.router.load(&stars_path);
|
|
|
|
if let Err(err_msg) = route_res {
|
|
|
|
return Err(PyErr::new::<PyValueError, _>(err_msg));
|
|
|
|
};
|
|
|
|
self.router
|
|
|
|
.precompute_graph(range)
|
|
|
|
.map_err(PyErr::new::<RoutingError, _>)
|
|
|
|
.map(|_| py.None())
|
|
|
|
}
|
|
|
|
|
2022-06-14 21:00:50 +00:00
|
|
|
fn nb_perf_test(&mut self, range: f32, py: Python<'_>) -> PyResult<PyObject> {
|
2022-02-23 21:45:59 +00:00
|
|
|
let stars_path = self.check_stars()?;
|
|
|
|
let route_res = self.router.load(&stars_path);
|
|
|
|
if let Err(err_msg) = route_res {
|
|
|
|
return Err(PyErr::new::<PyValueError, _>(err_msg));
|
|
|
|
};
|
|
|
|
let tree = self.router.get_tree();
|
|
|
|
let total_nodes = tree.size();
|
2022-06-14 21:00:50 +00:00
|
|
|
let mut total_nbs = 0;
|
2022-02-23 21:45:59 +00:00
|
|
|
for (n, node) in tree.iter().enumerate() {
|
2022-06-14 21:00:50 +00:00
|
|
|
total_nbs += self.router.neighbours(node, range).count();
|
|
|
|
// nbmap.insert(node.id, nbs);
|
2022-02-23 21:45:59 +00:00
|
|
|
if n % 100_000 == 0 {
|
2022-06-14 21:00:50 +00:00
|
|
|
let avg = total_nbs as f64 / (n + 1) as f64;
|
|
|
|
info!("{}/{} {} ({})", n, total_nodes, total_nbs, avg);
|
2022-02-23 21:45:59 +00:00
|
|
|
}
|
|
|
|
}
|
2022-06-14 21:00:50 +00:00
|
|
|
let avg = total_nbs as f64 / total_nodes as f64;
|
|
|
|
info!("Total: {} ({})", total_nbs, avg);
|
2020-06-16 13:38:31 +00:00
|
|
|
Ok(py.None())
|
2020-03-28 13:53:52 +00:00
|
|
|
}
|
|
|
|
|
2022-06-14 21:00:50 +00:00
|
|
|
fn precompute_neighbors(&mut self, range: f32, py: Python<'_>) -> PyResult<PyObject> {
|
2022-02-23 21:45:59 +00:00
|
|
|
let stars_path = self.check_stars()?;
|
|
|
|
let route_res = self.router.load(&stars_path);
|
|
|
|
if let Err(err_msg) = route_res {
|
|
|
|
return Err(PyErr::new::<PyValueError, _>(err_msg));
|
|
|
|
};
|
|
|
|
self.router
|
|
|
|
.precompute_all(range)
|
|
|
|
.map_err(PyErr::new::<RoutingError, _>)
|
|
|
|
.map(|_| py.None())
|
|
|
|
}
|
|
|
|
|
2022-06-14 21:00:50 +00:00
|
|
|
fn bfs_test(&mut self, range: f32) -> PyResult<()> {
|
|
|
|
use rand::prelude::*;
|
|
|
|
let stars_path = self.check_stars()?;
|
|
|
|
let route_res = self.router.load(&stars_path);
|
|
|
|
if let Err(err_msg) = route_res {
|
|
|
|
return Err(PyErr::new::<PyValueError, _>(err_msg));
|
|
|
|
};
|
|
|
|
let mut rng = rand::rngs::StdRng::seed_from_u64(0);
|
|
|
|
let nodes = self.router.get_tree().size();
|
|
|
|
loop {
|
|
|
|
let source = *self
|
|
|
|
.router
|
|
|
|
.get_tree()
|
|
|
|
.iter()
|
|
|
|
.nth(rng.gen_range(0..nodes))
|
|
|
|
.unwrap();
|
|
|
|
let goal = *self
|
|
|
|
.router
|
|
|
|
.get_tree()
|
|
|
|
.iter()
|
|
|
|
.nth(rng.gen_range(0..nodes))
|
|
|
|
.unwrap();
|
|
|
|
self.router.bfs_loop_test(range, &source, &goal, 0);
|
|
|
|
for w in 0..=15 {
|
|
|
|
self.router.bfs_loop_test(range, &source, &goal, 2usize.pow(w));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2022-02-23 21:45:59 +00:00
|
|
|
#[args(
|
|
|
|
greedyness = "0.5",
|
|
|
|
max_dist = "0.0",
|
|
|
|
num_workers = "0",
|
|
|
|
beam_width = "BeamWidth::Absolute(0)"
|
|
|
|
)]
|
|
|
|
#[pyo3(text_signature = "(hops, range, mode, num_workers, /)")]
|
2020-03-28 13:53:52 +00:00
|
|
|
fn route(
|
|
|
|
&mut self,
|
2022-02-23 21:45:59 +00:00
|
|
|
hops: Vec<SysEntry>,
|
|
|
|
range: RangeOrShip,
|
|
|
|
mode: Option<PyModeConfig>,
|
2020-03-28 13:53:52 +00:00
|
|
|
num_workers: usize,
|
2022-02-23 21:45:59 +00:00
|
|
|
) -> PyResult<Vec<common::System>> {
|
|
|
|
let stars_path = self.check_stars()?;
|
|
|
|
let route_res = self.router.load(&stars_path);
|
2020-03-28 13:53:52 +00:00
|
|
|
if let Err(err_msg) = route_res {
|
2022-02-23 21:45:59 +00:00
|
|
|
return Err(PyErr::new::<PyValueError, _>(err_msg));
|
2020-03-28 13:53:52 +00:00
|
|
|
};
|
2022-02-23 21:45:59 +00:00
|
|
|
info!("Resolving systems...");
|
|
|
|
let ids: Vec<u32> = match resolve(&hops, &self.router.path, true) {
|
|
|
|
Ok(sytems) => sytems.into_iter().map(|id| id.into_id()).collect(),
|
|
|
|
Err(err_msg) => {
|
2022-06-14 21:00:50 +00:00
|
|
|
return Err(err_msg.into());
|
2022-02-23 21:45:59 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
let mut is_default = false;
|
|
|
|
let mut is_ship = false;
|
|
|
|
info!("{:?}", mode);
|
|
|
|
let mut mode = match mode {
|
|
|
|
Some(mode) => mode,
|
|
|
|
None => {
|
|
|
|
let mode = PyModeConfig::default();
|
|
|
|
is_default = true;
|
|
|
|
mode
|
|
|
|
}
|
|
|
|
};
|
|
|
|
if mode.mode.is_empty() {
|
|
|
|
if mode.ship.is_none() {
|
|
|
|
mode.mode = "bfs".to_string();
|
2020-03-28 13:53:52 +00:00
|
|
|
} else {
|
2022-02-23 21:45:59 +00:00
|
|
|
mode.mode = "ship".to_string();
|
|
|
|
if mode.ship_mode == *"" {
|
|
|
|
mode.ship_mode = "jumps".to_string();
|
|
|
|
}
|
2020-03-28 13:53:52 +00:00
|
|
|
}
|
|
|
|
}
|
2022-02-23 21:45:59 +00:00
|
|
|
let range = match range {
|
|
|
|
RangeOrShip::Range(r) => Some(r),
|
|
|
|
RangeOrShip::Ship(ship) => {
|
|
|
|
mode.mode = "ship".into();
|
|
|
|
mode.ship = Some(ship);
|
|
|
|
is_ship = true;
|
|
|
|
None
|
2020-03-28 13:53:52 +00:00
|
|
|
}
|
|
|
|
};
|
2022-02-23 21:45:59 +00:00
|
|
|
info!("{:?}", mode);
|
|
|
|
let mode = mode.try_into()?;
|
|
|
|
if is_default && !is_ship {
|
|
|
|
warn!("no mode specified, defaulting to {}", mode);
|
2020-03-28 13:53:52 +00:00
|
|
|
}
|
2022-02-23 21:45:59 +00:00
|
|
|
#[cfg(not(feature = "profiling"))]
|
|
|
|
let reg = Region::new(GLOBAL);
|
|
|
|
let res = match self.router.compute_route(&ids, range, mode, num_workers) {
|
|
|
|
Ok(route) => Ok(route),
|
|
|
|
Err(err_msg) => Err(PyErr::new::<RoutingError, _>(err_msg)),
|
|
|
|
};
|
|
|
|
#[cfg(not(feature = "profiling"))]
|
|
|
|
println!("{:?}", reg.change());
|
|
|
|
return res;
|
2020-03-28 13:53:52 +00:00
|
|
|
}
|
|
|
|
|
2022-06-14 21:00:50 +00:00
|
|
|
fn perf_test(&self, callback: PyObject, py: Python<'_>) -> PyResult<PyObject> {
|
2022-02-23 21:45:59 +00:00
|
|
|
use common::TreeNode;
|
|
|
|
let node = TreeNode {
|
|
|
|
pos: [-65.21875, 7.75, -111.03125],
|
|
|
|
flags: 1,
|
|
|
|
id: 0,
|
|
|
|
};
|
|
|
|
let goal = TreeNode {
|
|
|
|
pos: [-9530.5, -910.28125, 19808.125],
|
|
|
|
flags: 1,
|
|
|
|
id: 1,
|
|
|
|
};
|
|
|
|
let kwargs = vec![("goal", goal), ("node", node)].into_py_dict(py);
|
|
|
|
let mut n: usize = 0;
|
|
|
|
let mut d: f64 = 0.0;
|
|
|
|
let num_loops = 10_000_000;
|
|
|
|
loop {
|
|
|
|
let pool = unsafe { Python::new_pool(py) };
|
|
|
|
let t_start = std::time::Instant::now();
|
|
|
|
for _ in 0..num_loops {
|
|
|
|
let val: f64 = callback.call(py, (), Some(kwargs))?.extract(py)?;
|
2020-03-28 13:53:52 +00:00
|
|
|
}
|
2022-02-23 21:45:59 +00:00
|
|
|
d += t_start.elapsed().as_secs_f64();
|
|
|
|
drop(pool);
|
|
|
|
n += num_loops;
|
|
|
|
let dt = std::time::Duration::from_secs_f64(d / (n as f64));
|
|
|
|
println!("{}: {:?}", n, dt);
|
2020-03-28 13:53:52 +00:00
|
|
|
}
|
2022-02-23 21:45:59 +00:00
|
|
|
Ok(py.None())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[args(grid_size = "1.0")]
|
|
|
|
#[pyo3(text_signature = "(grid_size)")]
|
2022-06-14 21:00:50 +00:00
|
|
|
fn get_grid(&self, grid_size: f32, py: Python<'_>) -> PyResult<PyObject> {
|
2022-02-23 21:45:59 +00:00
|
|
|
let stars_path = self.check_stars()?;
|
|
|
|
grid_stats(&stars_path, grid_size)
|
|
|
|
.map(|ret| ret.to_object(py))
|
|
|
|
.map_err(PyErr::new::<PyRuntimeError, _>)
|
|
|
|
}
|
|
|
|
|
2022-06-14 21:00:50 +00:00
|
|
|
fn floyd_warshall_test(&mut self, range: f32) -> PyResult<Vec<common::System>> {
|
|
|
|
let stars_path = self.check_stars()?;
|
|
|
|
self.router
|
|
|
|
.load(&stars_path)
|
|
|
|
.map_err(PyErr::new::<PyValueError, _>)?;
|
|
|
|
let res = self
|
|
|
|
.router
|
|
|
|
.floyd_warshall(range)
|
|
|
|
.map_err(PyErr::new::<RoutingError, _>)?;
|
|
|
|
Ok(res)
|
|
|
|
}
|
|
|
|
|
2022-02-23 21:45:59 +00:00
|
|
|
#[args(hops = "*")]
|
|
|
|
#[pyo3(text_signature = "(sys_1, sys_2, ..., /)")]
|
2022-06-14 21:00:50 +00:00
|
|
|
fn resolve(&self, hops: Vec<SysEntry>, py: Python<'_>) -> PyResult<PyObject> {
|
2022-02-23 21:45:59 +00:00
|
|
|
info!("Resolving systems...");
|
|
|
|
let stars_path = self.check_stars()?;
|
|
|
|
let systems: Vec<System> = match resolve(&hops, &stars_path, false) {
|
|
|
|
Ok(sytems) => sytems.into_iter().map(|sys| sys.into_system()).collect(),
|
2020-03-28 13:53:52 +00:00
|
|
|
Err(err_msg) => {
|
2022-06-14 21:00:50 +00:00
|
|
|
return Err(err_msg.into());
|
2020-03-28 13:53:52 +00:00
|
|
|
}
|
|
|
|
};
|
2022-02-23 21:45:59 +00:00
|
|
|
let ret: Vec<(_, System)> = hops
|
|
|
|
.into_iter()
|
|
|
|
.zip(systems.iter())
|
|
|
|
.map(|(id, sys)| (id, sys.clone()))
|
|
|
|
.collect();
|
2020-03-28 13:53:52 +00:00
|
|
|
Ok(PyDict::from_sequence(py, ret.to_object(py))?.to_object(py))
|
|
|
|
}
|
|
|
|
|
2022-02-23 21:45:59 +00:00
|
|
|
fn str_tree_test(&self) -> common::EdLrrResult<()> {
|
|
|
|
use common::BKTree;
|
|
|
|
const CHUNK_SIZE: usize = 4_000_000;
|
|
|
|
let path = self.check_stars()?;
|
|
|
|
let reader: csv::Reader<File> = csv::ReaderBuilder::new()
|
|
|
|
.has_headers(false)
|
|
|
|
.from_path(path)
|
|
|
|
.map_err(EdLrrError::from)?;
|
|
|
|
let mut data: Vec<String> = Vec::with_capacity(CHUNK_SIZE);
|
|
|
|
let t_start = Instant::now();
|
2022-06-14 21:00:50 +00:00
|
|
|
let mut base_id = 0;
|
2022-02-23 21:45:59 +00:00
|
|
|
let mut wr = BufWriter::new(File::create("test.bktree")?);
|
|
|
|
for sys in reader.into_deserialize::<System>() {
|
|
|
|
let sys = sys?;
|
|
|
|
data.push(sys.name);
|
2022-06-14 21:00:50 +00:00
|
|
|
if data.len() > CHUNK_SIZE {
|
2022-02-23 21:45:59 +00:00
|
|
|
let tree = BKTree::new(&data, base_id);
|
|
|
|
tree.dump(&mut wr)?;
|
2022-06-14 21:00:50 +00:00
|
|
|
base_id = sys.id;
|
2022-02-23 21:45:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if !data.is_empty() {
|
|
|
|
let tree = BKTree::new(&data, base_id);
|
|
|
|
tree.dump(&mut wr)?;
|
|
|
|
}
|
|
|
|
wr.flush()?;
|
|
|
|
println!("Took: {:?}", t_start.elapsed());
|
|
|
|
Ok(())
|
2020-03-28 13:53:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn __str__(&self) -> PyResult<String> {
|
|
|
|
Ok(format!("{:?}", &self))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn __repr__(&self) -> PyResult<String> {
|
|
|
|
Ok(format!("{:?}", &self))
|
|
|
|
}
|
|
|
|
}
|
2022-02-23 21:45:59 +00:00
|
|
|
enum ResolveResult {
|
|
|
|
System(System),
|
|
|
|
ID(u32),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ResolveResult {
|
|
|
|
fn into_id(self) -> u32 {
|
|
|
|
match self {
|
|
|
|
Self::System(sys) => sys.id,
|
|
|
|
Self::ID(id) => id,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn into_system(self) -> System {
|
|
|
|
if let Self::System(sys) = self {
|
|
|
|
return sys;
|
|
|
|
}
|
|
|
|
panic!("Tried to unwrap ID into System");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-14 21:00:50 +00:00
|
|
|
fn resolve(
|
|
|
|
entries: &[SysEntry],
|
|
|
|
path: &Path,
|
|
|
|
id_only: bool,
|
|
|
|
) -> Result<Vec<ResolveResult>, EdLrrError> {
|
2020-03-28 13:53:52 +00:00
|
|
|
let mut names: Vec<String> = Vec::new();
|
|
|
|
let mut ret: Vec<u32> = Vec::new();
|
2022-02-23 21:45:59 +00:00
|
|
|
let mut needs_rtree = false;
|
2020-03-28 13:53:52 +00:00
|
|
|
for ent in entries {
|
|
|
|
match ent {
|
|
|
|
SysEntry::Name(name) => names.push(name.to_owned()),
|
2022-02-23 21:45:59 +00:00
|
|
|
SysEntry::Pos(_) => {
|
|
|
|
needs_rtree |= true;
|
|
|
|
}
|
|
|
|
_ => (),
|
2020-03-28 13:53:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if !path.exists() {
|
2022-06-14 21:00:50 +00:00
|
|
|
return Err(EdLrrError::ResolveError(format!(
|
|
|
|
"Source file {:?} does not exist!",
|
|
|
|
path.display()
|
|
|
|
)));
|
2020-03-28 13:53:52 +00:00
|
|
|
}
|
2022-02-23 21:45:59 +00:00
|
|
|
let name_ids = if !names.is_empty() {
|
|
|
|
mmap_csv::mmap_csv(path, names)?
|
|
|
|
} else {
|
|
|
|
HashMap::new()
|
|
|
|
};
|
|
|
|
let tmp_r = needs_rtree
|
|
|
|
.then(|| {
|
|
|
|
let mut r = Router::new();
|
|
|
|
r.load(path).map(|_| r)
|
|
|
|
})
|
|
|
|
.transpose()?;
|
2020-03-28 13:53:52 +00:00
|
|
|
for ent in entries {
|
|
|
|
match ent {
|
|
|
|
SysEntry::Name(name) => {
|
2022-06-14 21:00:50 +00:00
|
|
|
let ent_res = name_ids.get(name).ok_or_else(|| {
|
|
|
|
EdLrrError::ResolveError(format!("System {} not found", name))
|
|
|
|
})?;
|
|
|
|
let sys = ent_res.as_ref().ok_or_else(|| {
|
|
|
|
EdLrrError::ResolveError(format!("System {} not found", name))
|
|
|
|
})?;
|
2022-02-23 21:45:59 +00:00
|
|
|
ret.push(*sys);
|
2020-03-28 13:53:52 +00:00
|
|
|
}
|
|
|
|
SysEntry::ID(id) => ret.push(*id),
|
2022-02-23 21:45:59 +00:00
|
|
|
SysEntry::Pos((x, y, z)) => ret.push(
|
|
|
|
tmp_r
|
|
|
|
.as_ref()
|
|
|
|
.unwrap()
|
|
|
|
.closest(&[*x, *y, *z])
|
2022-06-14 21:00:50 +00:00
|
|
|
.ok_or_else(|| EdLrrError::ResolveError("No systems loaded!".to_string()))?
|
2022-02-23 21:45:59 +00:00
|
|
|
.id,
|
|
|
|
),
|
2020-03-28 13:53:52 +00:00
|
|
|
}
|
|
|
|
}
|
2022-02-23 21:45:59 +00:00
|
|
|
if id_only {
|
|
|
|
return Ok(ret.iter().map(|id| ResolveResult::ID(*id)).collect());
|
|
|
|
} else {
|
|
|
|
let mut lc = route::LineCache::create(path)?;
|
|
|
|
let mut systems = vec![];
|
|
|
|
for id in ret {
|
|
|
|
let sys = ResolveResult::System(lc.get(id)?.unwrap());
|
|
|
|
systems.push(sys)
|
|
|
|
}
|
|
|
|
return Ok(systems);
|
|
|
|
}
|
2020-06-16 13:38:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[pyclass(dict)]
|
2022-02-23 21:45:59 +00:00
|
|
|
#[derive(Debug, Clone)]
|
2020-06-16 13:38:31 +00:00
|
|
|
struct PyShip {
|
|
|
|
ship: Ship,
|
|
|
|
}
|
|
|
|
#[pymethods]
|
|
|
|
impl PyShip {
|
|
|
|
#[staticmethod]
|
2022-06-14 21:00:50 +00:00
|
|
|
fn from_loadout(py: Python<'_>, loadout: &str) -> PyResult<PyObject> {
|
2020-06-16 13:38:31 +00:00
|
|
|
match Ship::new_from_json(loadout) {
|
2022-02-23 21:45:59 +00:00
|
|
|
Ok(ship) => Ok((PyShip { ship: ship.1 }).into_py(py)),
|
|
|
|
Err(err_msg) => Err(PyErr::new::<PyValueError, _>(err_msg)),
|
2020-06-16 13:38:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
#[staticmethod]
|
2022-06-14 21:00:50 +00:00
|
|
|
fn from_journal(py: Python<'_>) -> PyResult<PyObject> {
|
2020-06-16 13:38:31 +00:00
|
|
|
let mut ship = match Ship::new_from_journal() {
|
|
|
|
Ok(ship) => ship,
|
|
|
|
Err(err_msg) => {
|
2022-02-23 21:45:59 +00:00
|
|
|
return Err(PyErr::new::<PyValueError, _>(err_msg));
|
2020-06-16 13:38:31 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
let ships: Vec<(PyObject, PyObject)> = ship
|
|
|
|
.drain()
|
2022-02-23 21:45:59 +00:00
|
|
|
.map(|(ship_name, ship)| {
|
|
|
|
let ship_name_py = ship_name.to_object(py);
|
|
|
|
let ship_py = (PyShip { ship }).into_py(py);
|
|
|
|
(ship_name_py, ship_py)
|
2020-06-16 13:38:31 +00:00
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
Ok(PyDict::from_sequence(py, ships.to_object(py))?.to_object(py))
|
|
|
|
}
|
|
|
|
|
2022-06-14 21:00:50 +00:00
|
|
|
fn to_dict(&self, py: Python<'_>) -> PyResult<PyObject> {
|
2020-06-16 13:38:31 +00:00
|
|
|
self.ship.to_object(py)
|
|
|
|
}
|
|
|
|
|
2022-02-23 21:45:59 +00:00
|
|
|
#[pyo3(text_signature = "(dist, /)")]
|
2022-06-14 21:00:50 +00:00
|
|
|
fn fuel_cost(&self, _py: Python<'_>, dist: f32) -> f32 {
|
2022-02-23 21:45:59 +00:00
|
|
|
self.ship.fuel_cost(dist)
|
2020-06-16 13:38:31 +00:00
|
|
|
}
|
|
|
|
|
2022-02-23 21:45:59 +00:00
|
|
|
#[getter]
|
2022-06-14 21:00:50 +00:00
|
|
|
fn range(&self, _py: Python<'_>) -> f32 {
|
2022-02-23 21:45:59 +00:00
|
|
|
self.ship.range()
|
2020-06-16 13:38:31 +00:00
|
|
|
}
|
|
|
|
|
2022-02-23 21:45:59 +00:00
|
|
|
#[getter]
|
2022-06-14 21:00:50 +00:00
|
|
|
fn max_range(&self, _py: Python<'_>) -> f32 {
|
2022-02-23 21:45:59 +00:00
|
|
|
self.ship.max_range()
|
2020-06-16 13:38:31 +00:00
|
|
|
}
|
|
|
|
|
2022-02-23 21:45:59 +00:00
|
|
|
#[pyo3(text_signature = "(dist, /)")]
|
2022-06-14 21:00:50 +00:00
|
|
|
fn make_jump(&mut self, dist: f32, _py: Python<'_>) -> Option<f32> {
|
2022-02-23 21:45:59 +00:00
|
|
|
self.ship.make_jump(dist)
|
2020-06-16 13:38:31 +00:00
|
|
|
}
|
|
|
|
|
2022-02-23 21:45:59 +00:00
|
|
|
#[pyo3(text_signature = "(dist, /)")]
|
2022-06-14 21:00:50 +00:00
|
|
|
fn can_jump(&self, dist: f32, _py: Python<'_>) -> bool {
|
2022-02-23 21:45:59 +00:00
|
|
|
self.ship.can_jump(dist)
|
2020-06-16 13:38:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[args(fuel_amount = "None")]
|
2022-02-23 21:45:59 +00:00
|
|
|
#[pyo3(text_signature = "(fuel_amount, /)")]
|
2022-06-14 21:00:50 +00:00
|
|
|
fn refuel(&mut self, fuel_amount: Option<f32>, _py: Python<'_>) {
|
2020-06-16 13:38:31 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-23 21:45:59 +00:00
|
|
|
#[pyo3(text_signature = "(factor, /)")]
|
2022-06-14 21:00:50 +00:00
|
|
|
fn boost(&mut self, factor: f32, _py: Python<'_>) {
|
2020-06-16 13:38:31 +00:00
|
|
|
self.ship.boost(factor);
|
|
|
|
}
|
2022-06-14 21:00:50 +00:00
|
|
|
|
|
|
|
fn __str__(&self) -> PyResult<String> {
|
|
|
|
Ok(format!("{:?}", &self.ship))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn __repr__(&self) -> PyResult<String> {
|
|
|
|
Ok(format!("{:?}", &self.ship))
|
|
|
|
}
|
2020-03-28 13:53:52 +00:00
|
|
|
}
|
|
|
|
|
2022-02-23 21:45:59 +00:00
|
|
|
impl PyShip {
|
|
|
|
fn get_ship(&self) -> Ship {
|
|
|
|
self.ship.clone()
|
2020-06-16 13:38:31 +00:00
|
|
|
}
|
2020-03-28 13:53:52 +00:00
|
|
|
}
|
|
|
|
|
2022-02-23 21:45:59 +00:00
|
|
|
#[pyfunction]
|
|
|
|
fn preprocess_edsm(
|
|
|
|
_bodies_path: &str,
|
|
|
|
_systems_path: &str,
|
|
|
|
_out_path: &str,
|
2022-06-14 21:00:50 +00:00
|
|
|
_py: Python<'_>,
|
2022-02-23 21:45:59 +00:00
|
|
|
) -> PyResult<()> {
|
|
|
|
Err(pyo3::exceptions::PyNotImplementedError::new_err(
|
|
|
|
"please use Spansh's Galaxy dump and preprocess_galaxy()",
|
|
|
|
))
|
|
|
|
}
|
2020-03-28 13:53:52 +00:00
|
|
|
|
2022-06-14 21:00:50 +00:00
|
|
|
fn to_py_value(value: eval::Value, py: Python<'_>) -> PyResult<PyObject> {
|
2022-02-23 21:45:59 +00:00
|
|
|
type Value = eval::Value;
|
|
|
|
match value {
|
|
|
|
Value::String(s) => Ok(s.to_object(py)),
|
|
|
|
Value::Number(n) => {
|
|
|
|
if let Some(n) = n.as_u64() {
|
|
|
|
return Ok(n.to_object(py));
|
2020-03-28 13:53:52 +00:00
|
|
|
}
|
2022-02-23 21:45:59 +00:00
|
|
|
if let Some(n) = n.as_i64() {
|
|
|
|
return Ok(n.to_object(py));
|
2020-03-28 13:53:52 +00:00
|
|
|
}
|
2022-02-23 21:45:59 +00:00
|
|
|
return Ok(n.as_f64().unwrap().to_object(py));
|
2020-03-28 13:53:52 +00:00
|
|
|
}
|
2022-02-23 21:45:59 +00:00
|
|
|
Value::Bool(b) => Ok(b.to_object(py)),
|
|
|
|
Value::Array(mut t) => {
|
|
|
|
let mut res: Vec<PyObject> = vec![];
|
|
|
|
for v in t.drain(..) {
|
|
|
|
res.push(to_py_value(v, py)?);
|
2020-03-28 13:53:52 +00:00
|
|
|
}
|
2022-02-23 21:45:59 +00:00
|
|
|
Ok(PyTuple::new(py, &res).to_object(py))
|
|
|
|
}
|
|
|
|
Value::Object(o) => {
|
|
|
|
let res = PyDict::new(py);
|
|
|
|
for (k, v) in o.iter() {
|
|
|
|
res.set_item(k, to_py_value(v.clone(), py)?)?;
|
2020-03-28 13:53:52 +00:00
|
|
|
}
|
2022-02-23 21:45:59 +00:00
|
|
|
Ok(res.to_object(py))
|
2020-03-28 13:53:52 +00:00
|
|
|
}
|
2022-02-23 21:45:59 +00:00
|
|
|
Value::Null => Ok(py.None()),
|
2020-03-28 13:53:52 +00:00
|
|
|
}
|
2022-02-23 21:45:59 +00:00
|
|
|
}
|
|
|
|
|
2022-06-14 21:00:50 +00:00
|
|
|
fn to_py(res: Result<eval::Value, eval::Error>, py: Python<'_>) -> PyResult<PyObject> {
|
2022-02-23 21:45:59 +00:00
|
|
|
res.map_err(|e| PyErr::from(EdLrrError::EvalError(e)))
|
|
|
|
.and_then(|r| to_py_value(r, py))
|
|
|
|
}
|
2020-03-28 13:53:52 +00:00
|
|
|
|
2022-02-23 21:45:59 +00:00
|
|
|
#[pyfunction]
|
|
|
|
#[pyo3(text_signature = "(expr)")]
|
2022-06-14 21:00:50 +00:00
|
|
|
fn expr_test(expr: &str, py: Python<'_>) -> PyResult<PyObject> {
|
2022-02-23 21:45:59 +00:00
|
|
|
use eval::{to_value, Expr, Value};
|
|
|
|
let mut res = Expr::new(expr)
|
|
|
|
.compile()
|
|
|
|
.map_err(|e| PyErr::from(EdLrrError::EvalError(e)))?;
|
|
|
|
let mut hm: HashMap<&str, Value> = HashMap::new();
|
|
|
|
hm.insert("foo", to_value(42));
|
|
|
|
hm.insert("bar", to_value((-1, -2, -3)));
|
|
|
|
res = res.value("x", vec!["Hello", "world", "!"]);
|
|
|
|
res = res.value("y", 42);
|
|
|
|
res = res.value("p", (2.17, 5.14, 1.62));
|
|
|
|
res = res.value("hw", "Hello World!");
|
|
|
|
res = res.value("hm", hm);
|
|
|
|
to_py(res.exec(), py)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[pyfunction]
|
|
|
|
#[pyo3(text_signature = "(path, out_path, /)")]
|
|
|
|
fn preprocess_galaxy(path: &str, out_path: &str) -> PyResult<()> {
|
|
|
|
use common::build_index;
|
|
|
|
use galaxy::process_galaxy_dump;
|
|
|
|
let path = PathBuf::from(path);
|
|
|
|
let out_path = PathBuf::from(out_path);
|
|
|
|
process_galaxy_dump(&path, &out_path).unwrap();
|
|
|
|
build_index(&out_path)?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[pymodule]
|
2022-06-14 21:00:50 +00:00
|
|
|
pub fn _ed_lrr(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
2022-02-23 21:45:59 +00:00
|
|
|
better_panic::install();
|
|
|
|
pyo3_log::init();
|
|
|
|
profiling::init();
|
|
|
|
m.add_class::<PyRouter>()?;
|
|
|
|
m.add_class::<PyShip>()?;
|
|
|
|
m.add_wrapped(pyo3::wrap_pyfunction!(preprocess_galaxy))?;
|
|
|
|
m.add_wrapped(pyo3::wrap_pyfunction!(preprocess_edsm))?;
|
|
|
|
m.add_wrapped(pyo3::wrap_pyfunction!(expr_test))?;
|
|
|
|
Ok(())
|
|
|
|
}
|