2022-06-14
This commit is contained in:
parent
dc68cce9ed
commit
652609ca71
18 changed files with 565 additions and 2144 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,5 +1,6 @@
|
||||||
rust/target
|
rust/target
|
||||||
rust/.history/
|
rust/.history/
|
||||||
|
rust/Cargo.lock
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
*.tmp
|
*.tmp
|
||||||
*.idx
|
*.idx
|
||||||
|
|
|
@ -1,4 +1,11 @@
|
||||||
include rust/Cargo.toml
|
include rust/Cargo.toml
|
||||||
include rust/.cargo/config
|
include rust/.cargo/config
|
||||||
|
exclude docs_mdbook
|
||||||
|
exclude celery_test
|
||||||
|
exclude installer
|
||||||
|
exclude imgui_test
|
||||||
|
exclude icon
|
||||||
recursive-include rust/src *
|
recursive-include rust/src *
|
||||||
recursive-include ed_lrr_gui *
|
recursive-include ed_lrr_gui *
|
||||||
|
recursive-exclude __pycache__ *.pyc *.pyo
|
||||||
|
global-exclude __pycache__
|
|
@ -1,3 +1,9 @@
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["setuptools", "wheel","setuptools_rust"]
|
requires = ["setuptools", "wheel","setuptools_rust"]
|
||||||
build-backend = "setuptools.build_meta"
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[tool.poetry]
|
||||||
|
description = "Elite: Dangerous Long Range Route Plotter"
|
||||||
|
name="ed_lrr"
|
||||||
|
version="0.2.0"
|
||||||
|
authors = ["Daniel Seiller <earthnuker@gmail.com>"]
|
26
rust/.devcontainer/devcontainer.json
Normal file
26
rust/.devcontainer/devcontainer.json
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
|
||||||
|
// https://github.com/microsoft/vscode-dev-containers/tree/v0.238.0/containers/docker-existing-dockerfile
|
||||||
|
{
|
||||||
|
"name": "ED_LRR",
|
||||||
|
|
||||||
|
// Sets the run context to one level up instead of the .devcontainer folder.
|
||||||
|
"context": "..",
|
||||||
|
|
||||||
|
// Update the 'dockerFile' property if you aren't using the standard 'Dockerfile' filename.
|
||||||
|
"dockerFile": "../Dockerfile",
|
||||||
|
|
||||||
|
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||||
|
// "forwardPorts": [],
|
||||||
|
|
||||||
|
// Uncomment the next line to run commands after the container is created - for example installing curl.
|
||||||
|
// "postCreateCommand": "apt-get update && apt-get install -y curl",
|
||||||
|
|
||||||
|
// Uncomment when using a ptrace-based debugger like C++, Go, and Rust
|
||||||
|
"runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ]
|
||||||
|
|
||||||
|
// Uncomment to use the Docker CLI from inside the container. See https://aka.ms/vscode-remote/samples/docker-from-docker.
|
||||||
|
// "mounts": [ "source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind" ],
|
||||||
|
|
||||||
|
// Uncomment to connect as a non-root user if you've added one. See https://aka.ms/vscode-remote/containers/non-root.
|
||||||
|
// "remoteUser": "vscode"
|
||||||
|
}
|
5
rust/.vscode/settings.json
vendored
5
rust/.vscode/settings.json
vendored
|
@ -10,10 +10,11 @@
|
||||||
],
|
],
|
||||||
"discord.enabled": true,
|
"discord.enabled": true,
|
||||||
"python.pythonPath": "..\\.nox\\devenv-3-8\\python.exe",
|
"python.pythonPath": "..\\.nox\\devenv-3-8\\python.exe",
|
||||||
"jupyter.jupyterServerType": "remote",
|
"jupyter.jupyterServerType": "local",
|
||||||
"files.associations": {
|
"files.associations": {
|
||||||
"*.ksy": "yaml",
|
"*.ksy": "yaml",
|
||||||
"*.vpy": "python",
|
"*.vpy": "python",
|
||||||
"stat.h": "c"
|
"stat.h": "c"
|
||||||
}
|
},
|
||||||
|
"rust-analyzer.diagnostics.disabled": ["unresolved-import"]
|
||||||
}
|
}
|
1718
rust/Cargo.lock
generated
1718
rust/Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -18,59 +18,58 @@ lto = "fat"
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
pyo3 = { version = "0.15.1", features = ["extension-module","eyre"] }
|
pyo3 = { version = "0.16.5", features = ["extension-module","eyre","abi3-py37"] }
|
||||||
csv = "1.1.6"
|
csv = "1.1.6"
|
||||||
humantime = "2.1.0"
|
humantime = "2.1.0"
|
||||||
permutohedron = "0.2.4"
|
permutohedron = "0.2.4"
|
||||||
serde_json = "1.0.74"
|
serde_json = "1.0.81"
|
||||||
fnv = "1.0.7"
|
fnv = "1.0.7"
|
||||||
bincode = "1.3.3"
|
bincode = "1.3.3"
|
||||||
sha3 = "0.10.0"
|
sha3 = "0.10.1"
|
||||||
byteorder = "1.4.3"
|
byteorder = "1.4.3"
|
||||||
rstar = "0.9.2"
|
rstar = "0.9.3"
|
||||||
crossbeam-channel = "0.5.2"
|
crossbeam-channel = "0.5.4"
|
||||||
better-panic = "0.3.0"
|
better-panic = "0.3.0"
|
||||||
derivative = "2.2.0"
|
derivative = "2.2.0"
|
||||||
dict_derive = "0.4.0"
|
dict_derive = "0.4.0"
|
||||||
regex = "1.5.4"
|
regex = "1.5.6"
|
||||||
num_cpus = "1.13.1"
|
num_cpus = "1.13.1"
|
||||||
eddie = "0.4.2"
|
eddie = "0.4.2"
|
||||||
thiserror = "1.0.30"
|
thiserror = "1.0.31"
|
||||||
pyo3-log = "0.5.0"
|
pyo3-log = "0.6.0"
|
||||||
log = "0.4.14"
|
log = "0.4.17"
|
||||||
flate2 = "1.0.22"
|
flate2 = "1.0.24"
|
||||||
eval = "0.4.3"
|
eval = "0.4.3"
|
||||||
pythonize = "0.15.0"
|
pythonize = "0.16.0"
|
||||||
itertools = "0.10.3"
|
itertools = "0.10.3"
|
||||||
intmap = "0.7.1"
|
|
||||||
diff-struct = "0.4.1"
|
|
||||||
rustc-hash = "1.1.0"
|
rustc-hash = "1.1.0"
|
||||||
stats_alloc = "0.1.8"
|
stats_alloc = "0.1.10"
|
||||||
|
|
||||||
tracing = { version = "0.1.29", optional = true }
|
tracing = { version = "0.1.34", optional = true }
|
||||||
tracing-subscriber = { version = "0.3.5", optional = true }
|
tracing-subscriber = { version = "0.3.11", optional = true }
|
||||||
tracing-tracy = { version = "0.8.0", optional = true }
|
tracing-tracy = { version = "0.10.0", optional = true }
|
||||||
tracing-unwrap = { version = "0.9.2", optional = true }
|
# tracing-unwrap = { version = "0.9.2", optional = true }
|
||||||
tracy-client = { version = "0.12.6", optional = true }
|
tracy-client = { version = "0.14.0", optional = true }
|
||||||
tracing-chrome = "0.4.0"
|
tracing-chrome = "0.6.0"
|
||||||
rand = "0.8.4"
|
rand = "0.8.5"
|
||||||
eyre = "0.6.6"
|
eyre = "0.6.8"
|
||||||
memmap = "0.7.0"
|
memmap = "0.7.0"
|
||||||
csv-core = "0.1.10"
|
csv-core = "0.1.10"
|
||||||
postcard = { version = "0.7.3", features = ["alloc"] }
|
|
||||||
nohash-hasher = "0.2.0"
|
nohash-hasher = "0.2.0"
|
||||||
|
dashmap = "5.3.4"
|
||||||
|
rayon = "1.5.3"
|
||||||
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
profiling = ["tracing","tracing-subscriber","tracing-tracy","tracing-unwrap","tracy-client"]
|
profiling = ["tracing","tracing-subscriber","tracing-tracy","tracy-client"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = { version = "0.3.5", features = ["real_blackbox"] }
|
criterion = { version = "0.3.5", features = ["real_blackbox"] }
|
||||||
rand = "0.8.4"
|
rand = "0.8.5"
|
||||||
rand_distr = "0.4.2"
|
rand_distr = "0.4.3"
|
||||||
|
|
||||||
[dependencies.serde]
|
[dependencies.serde]
|
||||||
version = "1.0.133"
|
version = "1.0.137"
|
||||||
features = ["derive"]
|
features = ["derive"]
|
||||||
|
|
||||||
|
|
||||||
|
|
5
rust/Dockerfile
Normal file
5
rust/Dockerfile
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
FROM ghcr.io/pyo3/maturin
|
||||||
|
|
||||||
|
LABEL ed_lrr_dev latest
|
||||||
|
RUN rustup default nightly
|
||||||
|
RUN pip install maturin[zig]
|
1
rust/clippy.toml
Normal file
1
rust/clippy.toml
Normal file
|
@ -0,0 +1 @@
|
||||||
|
disallowed-types = ["std::collections::HashMap", "std::collections::HashSet"]
|
8
rust/docker-compose.yml
Normal file
8
rust/docker-compose.yml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
version: "4.0"
|
||||||
|
services:
|
||||||
|
ed_lrr_build:
|
||||||
|
build: .
|
||||||
|
working_dir: /code
|
||||||
|
command: ["build","-r","--zig", "--compatibility","manylinux2010"]
|
||||||
|
volumes:
|
||||||
|
- .:/code
|
|
@ -6,32 +6,46 @@ import shutil
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
|
||||||
def setup_logging(loglevel="INFO"):
|
def setup_logging(loglevel="INFO", file=False):
|
||||||
import logging
|
import logging
|
||||||
import coloredlogs
|
import coloredlogs
|
||||||
|
|
||||||
coloredlogs.DEFAULT_FIELD_STYLES["delta"] = {"color": "green"}
|
logfmt = " | ".join(
|
||||||
coloredlogs.DEFAULT_FIELD_STYLES["levelname"] = {"color": "yellow"}
|
["[%(delta)s] %(levelname)s", "%(name)s:%(pathname)s:%(lineno)s", "%(message)s"]
|
||||||
|
)
|
||||||
|
|
||||||
class DeltaTimeFormatter(coloredlogs.ColoredFormatter):
|
class ColorDeltaTimeFormatter(coloredlogs.ColoredFormatter):
|
||||||
def format(self, record):
|
def format(self, record):
|
||||||
seconds = record.relativeCreated / 1000
|
seconds = record.relativeCreated / 1000
|
||||||
duration = timedelta(seconds=seconds)
|
duration = timedelta(seconds=seconds)
|
||||||
record.delta = str(duration)
|
record.delta = str(duration)
|
||||||
return super().format(record)
|
return super().format(record)
|
||||||
|
|
||||||
coloredlogs.ColoredFormatter = DeltaTimeFormatter
|
class DeltaTimeFormatter(coloredlogs.BasicFormatter):
|
||||||
logfmt = " | ".join(
|
def format(self, record):
|
||||||
["[%(delta)s] %(levelname)s", "%(name)s:%(pathname)s:%(lineno)s", "%(message)s"]
|
seconds = record.relativeCreated / 1000
|
||||||
)
|
duration = timedelta(seconds=seconds)
|
||||||
|
record.delta = str(duration)
|
||||||
|
return super().format(record)
|
||||||
|
|
||||||
|
logger = logging.getLogger()
|
||||||
|
if file:
|
||||||
|
open("ed_lrr_test.log", "w").close()
|
||||||
|
fh = logging.FileHandler("ed_lrr_test.log")
|
||||||
|
fh.setLevel(logging.DEBUG)
|
||||||
|
fh.setFormatter(DeltaTimeFormatter(logfmt))
|
||||||
|
logger.addHandler(fh)
|
||||||
|
coloredlogs.DEFAULT_FIELD_STYLES["delta"] = {"color": "green"}
|
||||||
|
coloredlogs.DEFAULT_FIELD_STYLES["levelname"] = {"color": "yellow"}
|
||||||
|
|
||||||
|
coloredlogs.ColoredFormatter = ColorDeltaTimeFormatter
|
||||||
numeric_level = getattr(logging, loglevel.upper(), None)
|
numeric_level = getattr(logging, loglevel.upper(), None)
|
||||||
if not isinstance(numeric_level, int):
|
if not isinstance(numeric_level, int):
|
||||||
raise ValueError("Invalid log level: %s" % loglevel)
|
raise ValueError("Invalid log level: %s" % loglevel)
|
||||||
coloredlogs.install(level=numeric_level, fmt=logfmt)
|
coloredlogs.install(level=numeric_level, fmt=logfmt, logger=logger)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
setup_logging()
|
|
||||||
|
|
||||||
JUMP_RANGE = 48
|
JUMP_RANGE = 48
|
||||||
globals().setdefault("__file__", r"D:\devel\rust\ed_lrr_gui\rust\run_test.py")
|
globals().setdefault("__file__", r"D:\devel\rust\ed_lrr_gui\rust\run_test.py")
|
||||||
dirname = os.path.dirname(__file__) or "."
|
dirname = os.path.dirname(__file__) or "."
|
||||||
|
@ -46,21 +60,31 @@ if "--build" in sys.argv[1:]:
|
||||||
print("Build+Install took:", datetime.now() - t_start)
|
print("Build+Install took:", datetime.now() - t_start)
|
||||||
|
|
||||||
sys.path.append("..")
|
sys.path.append("..")
|
||||||
|
setup_logging(file=True)
|
||||||
_ed_lrr = __import__("_ed_lrr")
|
_ed_lrr = __import__("_ed_lrr")
|
||||||
|
|
||||||
|
|
||||||
def callback(state):
|
def callback(state):
|
||||||
print(state)
|
print(state)
|
||||||
|
|
||||||
|
|
||||||
print(_ed_lrr)
|
print(_ed_lrr)
|
||||||
r = _ed_lrr.PyRouter(callback)
|
|
||||||
r.load("../stars_2.csv", immediate=False)
|
|
||||||
print(r)
|
|
||||||
r.str_tree_test()
|
|
||||||
|
|
||||||
exit()
|
|
||||||
|
|
||||||
r = _ed_lrr.PyRouter(callback)
|
r = _ed_lrr.PyRouter(callback)
|
||||||
r.load("../stars.csv", immediate=False)
|
r.load("../stars.csv", immediate=False)
|
||||||
print(r.resolve("Sol","Saggitarius A","Colonia","Merope"))
|
# r.nb_perf_test(JUMP_RANGE)
|
||||||
|
# exit()
|
||||||
|
# start, end = "Sol", "Colonia"
|
||||||
|
# systems = r.resolve(start, end)
|
||||||
|
# sys_ids = {k: v["id"] for k, v in systems.items()}
|
||||||
|
|
||||||
|
r.bfs_test(JUMP_RANGE)
|
||||||
|
|
||||||
|
# cfg = {}
|
||||||
|
# cfg["mode"] = "incremental_broadening"
|
||||||
|
# # input("{}>".format(os.getpid()))
|
||||||
|
# # route = r.precompute_neighbors(JUMP_RANGE)
|
||||||
|
# route = r.route([sys_ids[start], sys_ids[end]], JUMP_RANGE, cfg, 0)
|
||||||
|
# print("Optimal route:", len(route))
|
||||||
|
|
||||||
exit()
|
exit()
|
||||||
|
|
||||||
|
@ -68,10 +92,12 @@ ships = _ed_lrr.PyShip.from_journal()
|
||||||
r = _ed_lrr.PyRouter(callback)
|
r = _ed_lrr.PyRouter(callback)
|
||||||
r.load("../stars.csv", immediate=False)
|
r.load("../stars.csv", immediate=False)
|
||||||
|
|
||||||
|
|
||||||
def func(*args, **kwargs):
|
def func(*args, **kwargs):
|
||||||
print(kwargs)
|
print(kwargs)
|
||||||
return 12
|
return 12
|
||||||
|
|
||||||
|
|
||||||
r.precompute_neighbors(JUMP_RANGE)
|
r.precompute_neighbors(JUMP_RANGE)
|
||||||
|
|
||||||
exit()
|
exit()
|
||||||
|
@ -119,6 +145,7 @@ start, end = "Sol", "Colonia"
|
||||||
systems = r.resolve(start, end)
|
systems = r.resolve(start, end)
|
||||||
sys_ids = {k: v["id"] for k, v in systems.items()}
|
sys_ids = {k: v["id"] for k, v in systems.items()}
|
||||||
|
|
||||||
|
|
||||||
cfg = {}
|
cfg = {}
|
||||||
cfg["mode"] = "incremental_broadening"
|
cfg["mode"] = "incremental_broadening"
|
||||||
# input("{}>".format(os.getpid()))
|
# input("{}>".format(os.getpid()))
|
||||||
|
|
2
rust/rust-toolchain.toml
Normal file
2
rust/rust-toolchain.toml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
[toolchain]
|
||||||
|
channel = "nightly"
|
|
@ -1,11 +1,12 @@
|
||||||
//! # Common utlility functions
|
//! # Common utlility functions
|
||||||
use crate::route::{LineCache, Router};
|
use crate::route::Router;
|
||||||
use bincode::Options;
|
use bincode::Options;
|
||||||
use crossbeam_channel::{bounded, Receiver};
|
use crossbeam_channel::{bounded, Receiver};
|
||||||
use csv::ByteRecord;
|
use csv::ByteRecord;
|
||||||
use dict_derive::IntoPyObject;
|
use dict_derive::IntoPyObject;
|
||||||
use eyre::Result;
|
use eyre::Result;
|
||||||
use log::*;
|
use log::*;
|
||||||
|
use nohash_hasher::NoHashHasher;
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
use pyo3::types::PyDict;
|
use pyo3::types::PyDict;
|
||||||
use pyo3::{conversion::ToPyObject, create_exception};
|
use pyo3::{conversion::ToPyObject, create_exception};
|
||||||
|
@ -13,8 +14,7 @@ use pythonize::depythonize;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sha3::{Digest, Sha3_256};
|
use sha3::{Digest, Sha3_256};
|
||||||
use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
|
use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
|
||||||
use std::hash::{Hash, Hasher, BuildHasherDefault};
|
use std::hash::{BuildHasherDefault, Hash, Hasher};
|
||||||
use std::io::Write;
|
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
@ -25,22 +25,21 @@ use std::{
|
||||||
io::{BufReader, BufWriter},
|
io::{BufReader, BufWriter},
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
};
|
};
|
||||||
use nohash_hasher::NoHashHasher;
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn heuristic(range: f32, node: &TreeNode, goal: &TreeNode) -> f32 {
|
pub fn heuristic(range: f32, node: &TreeNode, goal: &TreeNode) -> f32 {
|
||||||
// distance remaining after jumping from node towards goal
|
// distance remaining after jumping from node towards goal
|
||||||
let a2 = dist2(&node.pos, &goal.pos);
|
let a2 = dist(&node.pos, &goal.pos);
|
||||||
let mult = node.get_mult();
|
let mult = node.get_mult();
|
||||||
let b2 = range * range * mult*mult;
|
let b2 = range * mult;
|
||||||
return (a2 - b2).max(0.0);
|
return (a2 - b2).max(0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Min-heap priority queue using f32 as priority
|
/// Min-heap priority queue using f32 as priority
|
||||||
pub struct MinFHeap<T: Ord>(pub BinaryHeap<(Reverse<F32>, T)>);
|
pub struct MinFHeap<T: Ord>(BinaryHeap<(Reverse<F32>, T)>);
|
||||||
/// Max-heap priority queue using f32 as priority
|
/// Max-heap priority queue using f32 as priority
|
||||||
pub struct MaxFHeap<T: Ord>(pub BinaryHeap<(F32, T)>);
|
pub struct MaxFHeap<T: Ord>(BinaryHeap<(F32, T)>);
|
||||||
|
|
||||||
impl<T: Ord> MaxFHeap<T> {
|
impl<T: Ord> MaxFHeap<T> {
|
||||||
/// Create new, empty priority queue
|
/// Create new, empty priority queue
|
||||||
|
@ -391,7 +390,7 @@ pub enum SysEntry {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToPyObject for SysEntry {
|
impl ToPyObject for SysEntry {
|
||||||
fn to_object(&self, py: Python) -> PyObject {
|
fn to_object(&self, py: Python<'_>) -> PyObject {
|
||||||
match self {
|
match self {
|
||||||
Self::ID(id) => id.to_object(py),
|
Self::ID(id) => id.to_object(py),
|
||||||
Self::Name(name) => name.to_object(py),
|
Self::Name(name) => name.to_object(py),
|
||||||
|
@ -483,99 +482,6 @@ pub fn ndot(u: &[f32; 3], v: &[f32; 3]) -> f32 {
|
||||||
(u[0] * v[0]) / lm + (u[1] * v[1]) / lm + (u[2] * v[2]) / lm
|
(u[0] * v[0]) / lm + (u[1] * v[1]) / lm + (u[2] * v[2]) / lm
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fuzzy string matcher, use to resolve star system names
|
|
||||||
#[cfg_attr(feature = "profiling", tracing::instrument(skip(rx)))]
|
|
||||||
fn matcher(
|
|
||||||
rx: Receiver<ByteRecord>,
|
|
||||||
names: Vec<String>,
|
|
||||||
exact: bool,
|
|
||||||
) -> HashMap<String, (f64, Option<u32>)> {
|
|
||||||
let mut best: HashMap<String, (f64, Option<u32>)> = HashMap::new();
|
|
||||||
for name in &names {
|
|
||||||
best.insert(name.to_string(), (0.0, None));
|
|
||||||
}
|
|
||||||
let names_u8: Vec<(String, _)> = names.iter().map(|n| (n.clone(), n.as_bytes())).collect();
|
|
||||||
let sdist = eddie::slice::Levenshtein::new();
|
|
||||||
for sys in rx.into_iter() {
|
|
||||||
for (name, name_b) in &names_u8 {
|
|
||||||
if let Some(ent) = best.get_mut(name) {
|
|
||||||
if (ent.0 - 1.0).abs() < std::f64::EPSILON {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if exact && (&sys[1] == *name_b) {
|
|
||||||
let id = std::str::from_utf8(&sys[0]).unwrap().parse().unwrap();
|
|
||||||
*ent = (1.0, Some(id));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let d = sdist.similarity(&sys[1], name_b);
|
|
||||||
if d > ent.0 {
|
|
||||||
let id = std::str::from_utf8(&sys[0]).unwrap().parse().unwrap();
|
|
||||||
*ent = (d, Some(id));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
best
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Scan through the csv file at `path` and return a hash map
|
|
||||||
/// mapping the strings from `names` to a tuple `(score, Option<system_id>)`.
|
|
||||||
/// Scoring matching uses the normalized Levenshtein distance where 1.0 is an exact match.
|
|
||||||
pub fn find_matches(
|
|
||||||
path: &Path,
|
|
||||||
names: Vec<String>,
|
|
||||||
exact: bool,
|
|
||||||
) -> Result<HashMap<String, (f64, Option<u32>)>, String> {
|
|
||||||
let mut best: HashMap<String, (f64, Option<u32>)> = HashMap::new();
|
|
||||||
if names.is_empty() {
|
|
||||||
return Ok(best);
|
|
||||||
}
|
|
||||||
for name in &names {
|
|
||||||
best.insert(name.to_string(), (0.0, None));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut workers = Vec::new();
|
|
||||||
let ncpus = num_cpus::get();
|
|
||||||
let (tx, rx) = bounded(4096 * ncpus);
|
|
||||||
for _ in 0..ncpus {
|
|
||||||
let names = names.clone();
|
|
||||||
let rx = rx.clone();
|
|
||||||
let th = thread::spawn(move || matcher(rx, names, exact));
|
|
||||||
workers.push(th);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut rdr = match csv::ReaderBuilder::new().has_headers(false).from_path(path) {
|
|
||||||
Ok(rdr) => rdr,
|
|
||||||
Err(e) => {
|
|
||||||
return Err(format!("Error opening {}: {}", path.to_str().unwrap(), e));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let t_start = std::time::Instant::now();
|
|
||||||
let mut processed: usize = 0;
|
|
||||||
for record in rdr.byte_records().flat_map(|v| v.ok()) {
|
|
||||||
tx.send(record).unwrap();
|
|
||||||
processed += 1;
|
|
||||||
}
|
|
||||||
drop(tx);
|
|
||||||
while let Some(th) = workers.pop() {
|
|
||||||
for (name, (score, sys)) in th.join().unwrap().iter() {
|
|
||||||
best.entry(name.clone()).and_modify(|ent| {
|
|
||||||
if score > &ent.0 {
|
|
||||||
*ent = (*score, *sys);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let dt = std::time::Instant::now() - t_start;
|
|
||||||
info!(
|
|
||||||
"Searched {} records in {:?}: {} records/second",
|
|
||||||
processed,
|
|
||||||
dt,
|
|
||||||
(processed as f64) / dt.as_secs_f64()
|
|
||||||
);
|
|
||||||
Ok(best)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Hash the contents of `path` with sha3 and return the hash as a vector of bytes
|
/// Hash the contents of `path` with sha3 and return the hash as a vector of bytes
|
||||||
fn hash_file(path: &Path) -> Vec<u8> {
|
fn hash_file(path: &Path) -> Vec<u8> {
|
||||||
let mut hash_reader = BufReader::new(File::open(path).unwrap());
|
let mut hash_reader = BufReader::new(File::open(path).unwrap());
|
||||||
|
@ -632,7 +538,7 @@ pub struct TreeNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToPyObject for TreeNode {
|
impl ToPyObject for TreeNode {
|
||||||
fn to_object(&self, py: Python) -> PyObject {
|
fn to_object(&self, py: Python<'_>) -> PyObject {
|
||||||
pythonize::pythonize(py, self).unwrap()
|
pythonize::pythonize(py, self).unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -647,7 +553,7 @@ impl TreeNode {
|
||||||
match self.flags {
|
match self.flags {
|
||||||
0b11 => 4.0,
|
0b11 => 4.0,
|
||||||
0b10 => 1.5,
|
0b10 => 1.5,
|
||||||
_ => 1.0
|
_ => 1.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -696,22 +602,21 @@ pub struct System {
|
||||||
|
|
||||||
impl System {
|
impl System {
|
||||||
fn get_flags(&self) -> u8 {
|
fn get_flags(&self) -> u8 {
|
||||||
let mut flags=0;
|
|
||||||
if self.mult == 4.0 {
|
if self.mult == 4.0 {
|
||||||
return 0b11
|
return 0b11;
|
||||||
}
|
}
|
||||||
if self.mult == 1.5 {
|
if self.mult == 1.5 {
|
||||||
return 0b10
|
return 0b10;
|
||||||
}
|
}
|
||||||
if self.has_scoopable {
|
if self.has_scoopable {
|
||||||
return 0b01
|
return 0b01;
|
||||||
}
|
}
|
||||||
return 0b00
|
return 0b00;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToPyObject for System {
|
impl ToPyObject for System {
|
||||||
fn to_object(&self, py: Python) -> PyObject {
|
fn to_object(&self, py: Python<'_>) -> PyObject {
|
||||||
let d = PyDict::new(py);
|
let d = PyDict::new(py);
|
||||||
d.set_item("id", self.id).unwrap();
|
d.set_item("id", self.id).unwrap();
|
||||||
d.set_item("name", self.name.clone()).unwrap();
|
d.set_item("name", self.name.clone()).unwrap();
|
||||||
|
@ -771,24 +676,24 @@ impl<T> Default for DQueue<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||||
struct BKTreeNode {
|
struct BKTreeNode {
|
||||||
ids: HashSet<u32, BuildHasherDefault<NoHashHasher<u32>>>,
|
ids: HashSet<u32, BuildHasherDefault<NoHashHasher<u32>>>,
|
||||||
children: HashMap<u8,Self,BuildHasherDefault<NoHashHasher<u8>>>
|
children: HashMap<u8, Self, BuildHasherDefault<NoHashHasher<u8>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BKTreeNode {
|
impl BKTreeNode {
|
||||||
fn new(data: &[String], dist: &eddie::str::Levenshtein) -> Self {
|
fn new(data: &[String], dist: &eddie::str::Levenshtein) -> Self {
|
||||||
let mut tree = Self::default();
|
let mut tree = Self::default();
|
||||||
let mut max_depth = 0;
|
let mut max_depth = 0;
|
||||||
(0..data.len()).map(|id| {
|
(0..data.len())
|
||||||
|
.map(|id| {
|
||||||
max_depth = max_depth.max(tree.insert(data, id as u32, dist, 0));
|
max_depth = max_depth.max(tree.insert(data, id as u32, dist, 0));
|
||||||
if (id > 0) && (id % 100_000 == 0) {
|
if (id > 0) && (id % 100_000 == 0) {
|
||||||
println!("Inserting ID {}, Max Depth: {}", id, max_depth);
|
println!("Inserting ID {}, Max Depth: {}", id, max_depth);
|
||||||
}
|
}
|
||||||
}).max();
|
})
|
||||||
|
.max();
|
||||||
println!("Max Depth: {}", max_depth);
|
println!("Max Depth: {}", max_depth);
|
||||||
tree
|
tree
|
||||||
}
|
}
|
||||||
|
@ -799,15 +704,19 @@ impl BKTreeNode {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert(&mut self, data: &[String],id: u32, dist: &eddie::str::Levenshtein, depth: usize) -> usize {
|
fn insert(
|
||||||
|
&mut self,
|
||||||
|
data: &[String],
|
||||||
|
id: u32,
|
||||||
|
dist: &eddie::str::Levenshtein,
|
||||||
|
depth: usize,
|
||||||
|
) -> usize {
|
||||||
if self.is_empty() {
|
if self.is_empty() {
|
||||||
self.ids.insert(id);
|
self.ids.insert(id);
|
||||||
return depth;
|
return depth;
|
||||||
}
|
}
|
||||||
let idx = self.get_id().unwrap() as usize;
|
let idx = self.get_id().unwrap() as usize;
|
||||||
let self_key = data.get(idx).unwrap();
|
let dist_key = dist.distance(&data[idx], &data[id as usize]) as u8;
|
||||||
let ins_key = data.get(id as usize).unwrap();
|
|
||||||
let dist_key = dist.distance(self_key,ins_key) as u8;
|
|
||||||
if dist_key == 0 {
|
if dist_key == 0 {
|
||||||
self.ids.insert(id);
|
self.ids.insert(id);
|
||||||
return depth;
|
return depth;
|
||||||
|
@ -835,8 +744,6 @@ pub struct BKTree {
|
||||||
root: BKTreeNode,
|
root: BKTreeNode,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
impl BKTree {
|
impl BKTree {
|
||||||
pub fn new(data: &[String], base_id: u32) -> Self {
|
pub fn new(data: &[String], base_id: u32) -> Self {
|
||||||
let dist = eddie::str::Levenshtein::new();
|
let dist = eddie::str::Levenshtein::new();
|
||||||
|
|
190
rust/src/lib.rs
190
rust/src/lib.rs
|
@ -1,4 +1,6 @@
|
||||||
|
#![feature(binary_heap_retain)]
|
||||||
#![allow(dead_code, clippy::needless_return, clippy::too_many_arguments)]
|
#![allow(dead_code, clippy::needless_return, clippy::too_many_arguments)]
|
||||||
|
#![warn(rust_2018_idioms, rust_2021_compatibility, clippy::disallowed_types)]
|
||||||
//! # Elite: Danerous Long Range Router
|
//! # Elite: Danerous Long Range Router
|
||||||
pub mod common;
|
pub mod common;
|
||||||
pub mod galaxy;
|
pub mod galaxy;
|
||||||
|
@ -10,13 +12,9 @@ pub mod route;
|
||||||
pub mod search_algos;
|
pub mod search_algos;
|
||||||
pub mod ship;
|
pub mod ship;
|
||||||
|
|
||||||
use bincode::Options;
|
|
||||||
use csv::{Position, StringRecord};
|
|
||||||
use eddie::Levenshtein;
|
|
||||||
// =========================
|
// =========================
|
||||||
use stats_alloc::{Region, StatsAlloc, INSTRUMENTED_SYSTEM};
|
use stats_alloc::{Region, StatsAlloc, INSTRUMENTED_SYSTEM};
|
||||||
use std::alloc::System as SystemAlloc;
|
use std::alloc::System as SystemAlloc;
|
||||||
use std::cell::RefMut;
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::io::{BufWriter, Write};
|
use std::io::{BufWriter, Write};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
@ -30,8 +28,7 @@ mod profiling {
|
||||||
pub fn init() {}
|
pub fn init() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
extern crate derivative;
|
use crate::common::{grid_stats, EdLrrError, SysEntry, System};
|
||||||
use crate::common::{find_matches, grid_stats, EdLrrError, SysEntry, System};
|
|
||||||
#[cfg(feature = "profiling")]
|
#[cfg(feature = "profiling")]
|
||||||
use crate::profiling::*;
|
use crate::profiling::*;
|
||||||
use crate::route::{Router, SearchState};
|
use crate::route::{Router, SearchState};
|
||||||
|
@ -39,16 +36,14 @@ use crate::ship::Ship;
|
||||||
use eyre::Result;
|
use eyre::Result;
|
||||||
#[cfg(not(feature = "profiling"))]
|
#[cfg(not(feature = "profiling"))]
|
||||||
use log::*;
|
use log::*;
|
||||||
|
use pyo3::create_exception;
|
||||||
use pyo3::exceptions::*;
|
use pyo3::exceptions::*;
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
use pyo3::types::{IntoPyDict, PyDict, PyTuple};
|
use pyo3::types::{IntoPyDict, PyDict, PyTuple};
|
||||||
use pyo3::{create_exception, PyObjectProtocol};
|
use route::PyModeConfig;
|
||||||
use route::{LineCache, PyModeConfig};
|
use std::{collections::HashMap, convert::TryInto, fs::File, path::PathBuf};
|
||||||
use std::{
|
|
||||||
cell::RefCell, collections::HashMap, convert::TryInto, fs::File, io::BufReader, path::PathBuf,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(feature = "profiling")]
|
#[cfg(feature = "mem_profiling")]
|
||||||
#[global_allocator]
|
#[global_allocator]
|
||||||
static GLOBAL: ProfiledAllocator<std::alloc::System> =
|
static GLOBAL: ProfiledAllocator<std::alloc::System> =
|
||||||
ProfiledAllocator::new(std::alloc::System, 1024);
|
ProfiledAllocator::new(std::alloc::System, 1024);
|
||||||
|
@ -87,8 +82,6 @@ impl PyRouter {
|
||||||
.ok_or_else(|| PyErr::from(EdLrrError::RuntimeError("no stars.csv loaded".to_owned())))
|
.ok_or_else(|| PyErr::from(EdLrrError::RuntimeError("no stars.csv loaded".to_owned())))
|
||||||
.map(PathBuf::from)
|
.map(PathBuf::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pymethods]
|
#[pymethods]
|
||||||
|
@ -115,25 +108,23 @@ impl PyRouter {
|
||||||
|
|
||||||
#[args(primary_only = "false", immediate = "false")]
|
#[args(primary_only = "false", immediate = "false")]
|
||||||
#[pyo3(text_signature = "(path, primary_only, /)")]
|
#[pyo3(text_signature = "(path, primary_only, /)")]
|
||||||
fn load(&mut self, path: String, py: Python, immediate: bool) -> PyResult<PyObject> {
|
fn load(&mut self, path: String, py: Python<'_>, immediate: bool) -> PyResult<PyObject> {
|
||||||
self.stars_path = Some(path);
|
self.stars_path = Some(path);
|
||||||
if immediate {
|
if immediate {
|
||||||
let stars_path = self.check_stars()?;
|
self.router
|
||||||
let route_res = self.router.load(&stars_path);
|
.load(&self.check_stars()?)
|
||||||
if let Err(err_msg) = route_res {
|
.map_err(PyErr::new::<PyValueError, _>)?;
|
||||||
return Err(PyErr::new::<PyValueError, _>(err_msg));
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
Ok(py.None())
|
Ok(py.None())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pyo3(text_signature = "(/)")]
|
#[pyo3(text_signature = "(/)")]
|
||||||
fn unload(&mut self, py: Python) -> PyObject {
|
fn unload(&mut self, py: Python<'_>) -> PyObject {
|
||||||
self.router.unload();
|
self.router.unload();
|
||||||
py.None()
|
py.None()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn plot(&mut self, py: Python) -> PyResult<PyObject> {
|
fn plot(&mut self, py: Python<'_>) -> PyResult<PyObject> {
|
||||||
let stars_path = self.check_stars()?;
|
let stars_path = self.check_stars()?;
|
||||||
let route_res = self.router.load(&stars_path);
|
let route_res = self.router.load(&stars_path);
|
||||||
if let Err(err_msg) = route_res {
|
if let Err(err_msg) = route_res {
|
||||||
|
@ -155,7 +146,7 @@ impl PyRouter {
|
||||||
Ok(plot_bbox.to_object(py))
|
Ok(plot_bbox.to_object(py))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_bfs(&mut self, range: f32, py: Python) -> PyResult<PyObject> {
|
fn run_bfs(&mut self, range: f32, py: Python<'_>) -> PyResult<PyObject> {
|
||||||
let stars_path = self.check_stars()?;
|
let stars_path = self.check_stars()?;
|
||||||
let route_res = self.router.load(&stars_path);
|
let route_res = self.router.load(&stars_path);
|
||||||
if let Err(err_msg) = route_res {
|
if let Err(err_msg) = route_res {
|
||||||
|
@ -167,7 +158,7 @@ impl PyRouter {
|
||||||
.map(|_| py.None())
|
.map(|_| py.None())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn precompute_graph(&mut self, range: f32, py: Python) -> PyResult<PyObject> {
|
fn precompute_graph(&mut self, range: f32, py: Python<'_>) -> PyResult<PyObject> {
|
||||||
let stars_path = self.check_stars()?;
|
let stars_path = self.check_stars()?;
|
||||||
let route_res = self.router.load(&stars_path);
|
let route_res = self.router.load(&stars_path);
|
||||||
if let Err(err_msg) = route_res {
|
if let Err(err_msg) = route_res {
|
||||||
|
@ -179,31 +170,29 @@ impl PyRouter {
|
||||||
.map(|_| py.None())
|
.map(|_| py.None())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn nb_perf_test(&mut self, range: f32, py: Python) -> PyResult<PyObject> {
|
fn nb_perf_test(&mut self, range: f32, py: Python<'_>) -> PyResult<PyObject> {
|
||||||
let stars_path = self.check_stars()?;
|
let stars_path = self.check_stars()?;
|
||||||
let route_res = self.router.load(&stars_path);
|
let route_res = self.router.load(&stars_path);
|
||||||
if let Err(err_msg) = route_res {
|
if let Err(err_msg) = route_res {
|
||||||
return Err(PyErr::new::<PyValueError, _>(err_msg));
|
return Err(PyErr::new::<PyValueError, _>(err_msg));
|
||||||
};
|
};
|
||||||
let mut nbmap = BTreeMap::new();
|
|
||||||
let tree = self.router.get_tree();
|
let tree = self.router.get_tree();
|
||||||
let total_nodes = tree.size();
|
let total_nodes = tree.size();
|
||||||
|
let mut total_nbs = 0;
|
||||||
for (n, node) in tree.iter().enumerate() {
|
for (n, node) in tree.iter().enumerate() {
|
||||||
let nbs = self
|
total_nbs += self.router.neighbours(node, range).count();
|
||||||
.router
|
// nbmap.insert(node.id, nbs);
|
||||||
.neighbours(node, range)
|
|
||||||
.map(|nb| nb.id)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
nbmap.insert(node.id, nbs);
|
|
||||||
if n % 100_000 == 0 {
|
if n % 100_000 == 0 {
|
||||||
println!("{}/{}", n, total_nodes);
|
let avg = total_nbs as f64 / (n + 1) as f64;
|
||||||
|
info!("{}/{} {} ({})", n, total_nodes, total_nbs, avg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
println!("{}", nbmap.len());
|
let avg = total_nbs as f64 / total_nodes as f64;
|
||||||
|
info!("Total: {} ({})", total_nbs, avg);
|
||||||
Ok(py.None())
|
Ok(py.None())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn precompute_neighbors(&mut self, range: f32, py: Python) -> PyResult<PyObject> {
|
fn precompute_neighbors(&mut self, range: f32, py: Python<'_>) -> PyResult<PyObject> {
|
||||||
let stars_path = self.check_stars()?;
|
let stars_path = self.check_stars()?;
|
||||||
let route_res = self.router.load(&stars_path);
|
let route_res = self.router.load(&stars_path);
|
||||||
if let Err(err_msg) = route_res {
|
if let Err(err_msg) = route_res {
|
||||||
|
@ -215,6 +204,36 @@ impl PyRouter {
|
||||||
.map(|_| py.None())
|
.map(|_| py.None())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
|
||||||
#[args(
|
#[args(
|
||||||
greedyness = "0.5",
|
greedyness = "0.5",
|
||||||
max_dist = "0.0",
|
max_dist = "0.0",
|
||||||
|
@ -238,7 +257,7 @@ impl PyRouter {
|
||||||
let ids: Vec<u32> = match resolve(&hops, &self.router.path, true) {
|
let ids: Vec<u32> = match resolve(&hops, &self.router.path, true) {
|
||||||
Ok(sytems) => sytems.into_iter().map(|id| id.into_id()).collect(),
|
Ok(sytems) => sytems.into_iter().map(|id| id.into_id()).collect(),
|
||||||
Err(err_msg) => {
|
Err(err_msg) => {
|
||||||
return Err(EdLrrError::ResolveError(err_msg).into());
|
return Err(err_msg.into());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let mut is_default = false;
|
let mut is_default = false;
|
||||||
|
@ -287,7 +306,7 @@ impl PyRouter {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn perf_test(&self, callback: PyObject, py: Python) -> PyResult<PyObject> {
|
fn perf_test(&self, callback: PyObject, py: Python<'_>) -> PyResult<PyObject> {
|
||||||
use common::TreeNode;
|
use common::TreeNode;
|
||||||
let node = TreeNode {
|
let node = TreeNode {
|
||||||
pos: [-65.21875, 7.75, -111.03125],
|
pos: [-65.21875, 7.75, -111.03125],
|
||||||
|
@ -320,22 +339,34 @@ impl PyRouter {
|
||||||
|
|
||||||
#[args(grid_size = "1.0")]
|
#[args(grid_size = "1.0")]
|
||||||
#[pyo3(text_signature = "(grid_size)")]
|
#[pyo3(text_signature = "(grid_size)")]
|
||||||
fn get_grid(&self, grid_size: f32, py: Python) -> PyResult<PyObject> {
|
fn get_grid(&self, grid_size: f32, py: Python<'_>) -> PyResult<PyObject> {
|
||||||
let stars_path = self.check_stars()?;
|
let stars_path = self.check_stars()?;
|
||||||
grid_stats(&stars_path, grid_size)
|
grid_stats(&stars_path, grid_size)
|
||||||
.map(|ret| ret.to_object(py))
|
.map(|ret| ret.to_object(py))
|
||||||
.map_err(PyErr::new::<PyRuntimeError, _>)
|
.map_err(PyErr::new::<PyRuntimeError, _>)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
#[args(hops = "*")]
|
#[args(hops = "*")]
|
||||||
#[pyo3(text_signature = "(sys_1, sys_2, ..., /)")]
|
#[pyo3(text_signature = "(sys_1, sys_2, ..., /)")]
|
||||||
fn resolve(&self, hops: Vec<SysEntry>, py: Python) -> PyResult<PyObject> {
|
fn resolve(&self, hops: Vec<SysEntry>, py: Python<'_>) -> PyResult<PyObject> {
|
||||||
info!("Resolving systems...");
|
info!("Resolving systems...");
|
||||||
let stars_path = self.check_stars()?;
|
let stars_path = self.check_stars()?;
|
||||||
let systems: Vec<System> = match resolve(&hops, &stars_path, false) {
|
let systems: Vec<System> = match resolve(&hops, &stars_path, false) {
|
||||||
Ok(sytems) => sytems.into_iter().map(|sys| sys.into_system()).collect(),
|
Ok(sytems) => sytems.into_iter().map(|sys| sys.into_system()).collect(),
|
||||||
Err(err_msg) => {
|
Err(err_msg) => {
|
||||||
return Err(EdLrrError::ResolveError(err_msg).into());
|
return Err(err_msg.into());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let ret: Vec<(_, System)> = hops
|
let ret: Vec<(_, System)> = hops
|
||||||
|
@ -375,10 +406,7 @@ impl PyRouter {
|
||||||
println!("Took: {:?}", t_start.elapsed());
|
println!("Took: {:?}", t_start.elapsed());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[pyproto]
|
|
||||||
impl PyObjectProtocol for PyRouter {
|
|
||||||
fn __str__(&self) -> PyResult<String> {
|
fn __str__(&self) -> PyResult<String> {
|
||||||
Ok(format!("{:?}", &self))
|
Ok(format!("{:?}", &self))
|
||||||
}
|
}
|
||||||
|
@ -387,7 +415,6 @@ impl PyObjectProtocol for PyRouter {
|
||||||
Ok(format!("{:?}", &self))
|
Ok(format!("{:?}", &self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ResolveResult {
|
enum ResolveResult {
|
||||||
System(System),
|
System(System),
|
||||||
ID(u32),
|
ID(u32),
|
||||||
|
@ -409,7 +436,11 @@ impl ResolveResult {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve(entries: &[SysEntry], path: &Path, id_only: bool) -> Result<Vec<ResolveResult>, String> {
|
fn resolve(
|
||||||
|
entries: &[SysEntry],
|
||||||
|
path: &Path,
|
||||||
|
id_only: bool,
|
||||||
|
) -> Result<Vec<ResolveResult>, EdLrrError> {
|
||||||
let mut names: Vec<String> = Vec::new();
|
let mut names: Vec<String> = Vec::new();
|
||||||
let mut ret: Vec<u32> = Vec::new();
|
let mut ret: Vec<u32> = Vec::new();
|
||||||
let mut needs_rtree = false;
|
let mut needs_rtree = false;
|
||||||
|
@ -423,7 +454,10 @@ fn resolve(entries: &[SysEntry], path: &Path, id_only: bool) -> Result<Vec<Resol
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
return Err(format!("Source file {:?} does not exist!", path.display()));
|
return Err(EdLrrError::ResolveError(format!(
|
||||||
|
"Source file {:?} does not exist!",
|
||||||
|
path.display()
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
let name_ids = if !names.is_empty() {
|
let name_ids = if !names.is_empty() {
|
||||||
mmap_csv::mmap_csv(path, names)?
|
mmap_csv::mmap_csv(path, names)?
|
||||||
|
@ -439,12 +473,12 @@ fn resolve(entries: &[SysEntry], path: &Path, id_only: bool) -> Result<Vec<Resol
|
||||||
for ent in entries {
|
for ent in entries {
|
||||||
match ent {
|
match ent {
|
||||||
SysEntry::Name(name) => {
|
SysEntry::Name(name) => {
|
||||||
let ent_res = name_ids
|
let ent_res = name_ids.get(name).ok_or_else(|| {
|
||||||
.get(name)
|
EdLrrError::ResolveError(format!("System {} not found", name))
|
||||||
.ok_or(format!("System {} not found", name))?;
|
})?;
|
||||||
let sys = ent_res
|
let sys = ent_res.as_ref().ok_or_else(|| {
|
||||||
.as_ref()
|
EdLrrError::ResolveError(format!("System {} not found", name))
|
||||||
.ok_or(format!("System {} not found", name))?;
|
})?;
|
||||||
ret.push(*sys);
|
ret.push(*sys);
|
||||||
}
|
}
|
||||||
SysEntry::ID(id) => ret.push(*id),
|
SysEntry::ID(id) => ret.push(*id),
|
||||||
|
@ -453,7 +487,7 @@ fn resolve(entries: &[SysEntry], path: &Path, id_only: bool) -> Result<Vec<Resol
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.closest(&[*x, *y, *z])
|
.closest(&[*x, *y, *z])
|
||||||
.ok_or("No systems loaded!")?
|
.ok_or_else(|| EdLrrError::ResolveError("No systems loaded!".to_string()))?
|
||||||
.id,
|
.id,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
@ -476,29 +510,17 @@ fn resolve(entries: &[SysEntry], path: &Path, id_only: bool) -> Result<Vec<Resol
|
||||||
struct PyShip {
|
struct PyShip {
|
||||||
ship: Ship,
|
ship: Ship,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pyproto]
|
|
||||||
impl PyObjectProtocol for PyShip {
|
|
||||||
fn __str__(&self) -> PyResult<String> {
|
|
||||||
Ok(format!("{:?}", &self.ship))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn __repr__(&self) -> PyResult<String> {
|
|
||||||
Ok(format!("{:?}", &self.ship))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[pymethods]
|
#[pymethods]
|
||||||
impl PyShip {
|
impl PyShip {
|
||||||
#[staticmethod]
|
#[staticmethod]
|
||||||
fn from_loadout(py: Python, loadout: &str) -> PyResult<PyObject> {
|
fn from_loadout(py: Python<'_>, loadout: &str) -> PyResult<PyObject> {
|
||||||
match Ship::new_from_json(loadout) {
|
match Ship::new_from_json(loadout) {
|
||||||
Ok(ship) => Ok((PyShip { ship: ship.1 }).into_py(py)),
|
Ok(ship) => Ok((PyShip { ship: ship.1 }).into_py(py)),
|
||||||
Err(err_msg) => Err(PyErr::new::<PyValueError, _>(err_msg)),
|
Err(err_msg) => Err(PyErr::new::<PyValueError, _>(err_msg)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[staticmethod]
|
#[staticmethod]
|
||||||
fn from_journal(py: Python) -> PyResult<PyObject> {
|
fn from_journal(py: Python<'_>) -> PyResult<PyObject> {
|
||||||
let mut ship = match Ship::new_from_journal() {
|
let mut ship = match Ship::new_from_journal() {
|
||||||
Ok(ship) => ship,
|
Ok(ship) => ship,
|
||||||
Err(err_msg) => {
|
Err(err_msg) => {
|
||||||
|
@ -516,38 +538,38 @@ impl PyShip {
|
||||||
Ok(PyDict::from_sequence(py, ships.to_object(py))?.to_object(py))
|
Ok(PyDict::from_sequence(py, ships.to_object(py))?.to_object(py))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_dict(&self, py: Python) -> PyResult<PyObject> {
|
fn to_dict(&self, py: Python<'_>) -> PyResult<PyObject> {
|
||||||
self.ship.to_object(py)
|
self.ship.to_object(py)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pyo3(text_signature = "(dist, /)")]
|
#[pyo3(text_signature = "(dist, /)")]
|
||||||
fn fuel_cost(&self, _py: Python, dist: f32) -> f32 {
|
fn fuel_cost(&self, _py: Python<'_>, dist: f32) -> f32 {
|
||||||
self.ship.fuel_cost(dist)
|
self.ship.fuel_cost(dist)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[getter]
|
#[getter]
|
||||||
fn range(&self, _py: Python) -> f32 {
|
fn range(&self, _py: Python<'_>) -> f32 {
|
||||||
self.ship.range()
|
self.ship.range()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[getter]
|
#[getter]
|
||||||
fn max_range(&self, _py: Python) -> f32 {
|
fn max_range(&self, _py: Python<'_>) -> f32 {
|
||||||
self.ship.max_range()
|
self.ship.max_range()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pyo3(text_signature = "(dist, /)")]
|
#[pyo3(text_signature = "(dist, /)")]
|
||||||
fn make_jump(&mut self, dist: f32, _py: Python) -> Option<f32> {
|
fn make_jump(&mut self, dist: f32, _py: Python<'_>) -> Option<f32> {
|
||||||
self.ship.make_jump(dist)
|
self.ship.make_jump(dist)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pyo3(text_signature = "(dist, /)")]
|
#[pyo3(text_signature = "(dist, /)")]
|
||||||
fn can_jump(&self, dist: f32, _py: Python) -> bool {
|
fn can_jump(&self, dist: f32, _py: Python<'_>) -> bool {
|
||||||
self.ship.can_jump(dist)
|
self.ship.can_jump(dist)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[args(fuel_amount = "None")]
|
#[args(fuel_amount = "None")]
|
||||||
#[pyo3(text_signature = "(fuel_amount, /)")]
|
#[pyo3(text_signature = "(fuel_amount, /)")]
|
||||||
fn refuel(&mut self, fuel_amount: Option<f32>, _py: Python) {
|
fn refuel(&mut self, fuel_amount: Option<f32>, _py: Python<'_>) {
|
||||||
if let Some(fuel) = fuel_amount {
|
if let Some(fuel) = fuel_amount {
|
||||||
self.ship.fuel_mass = (self.ship.fuel_mass + fuel).min(self.ship.fuel_capacity)
|
self.ship.fuel_mass = (self.ship.fuel_mass + fuel).min(self.ship.fuel_capacity)
|
||||||
} else {
|
} else {
|
||||||
|
@ -556,9 +578,17 @@ impl PyShip {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pyo3(text_signature = "(factor, /)")]
|
#[pyo3(text_signature = "(factor, /)")]
|
||||||
fn boost(&mut self, factor: f32, _py: Python) {
|
fn boost(&mut self, factor: f32, _py: Python<'_>) {
|
||||||
self.ship.boost(factor);
|
self.ship.boost(factor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn __str__(&self) -> PyResult<String> {
|
||||||
|
Ok(format!("{:?}", &self.ship))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn __repr__(&self) -> PyResult<String> {
|
||||||
|
Ok(format!("{:?}", &self.ship))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PyShip {
|
impl PyShip {
|
||||||
|
@ -572,14 +602,14 @@ fn preprocess_edsm(
|
||||||
_bodies_path: &str,
|
_bodies_path: &str,
|
||||||
_systems_path: &str,
|
_systems_path: &str,
|
||||||
_out_path: &str,
|
_out_path: &str,
|
||||||
_py: Python,
|
_py: Python<'_>,
|
||||||
) -> PyResult<()> {
|
) -> PyResult<()> {
|
||||||
Err(pyo3::exceptions::PyNotImplementedError::new_err(
|
Err(pyo3::exceptions::PyNotImplementedError::new_err(
|
||||||
"please use Spansh's Galaxy dump and preprocess_galaxy()",
|
"please use Spansh's Galaxy dump and preprocess_galaxy()",
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_py_value(value: eval::Value, py: Python) -> PyResult<PyObject> {
|
fn to_py_value(value: eval::Value, py: Python<'_>) -> PyResult<PyObject> {
|
||||||
type Value = eval::Value;
|
type Value = eval::Value;
|
||||||
match value {
|
match value {
|
||||||
Value::String(s) => Ok(s.to_object(py)),
|
Value::String(s) => Ok(s.to_object(py)),
|
||||||
|
@ -611,14 +641,14 @@ fn to_py_value(value: eval::Value, py: Python) -> PyResult<PyObject> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_py(res: Result<eval::Value, eval::Error>, py: Python) -> PyResult<PyObject> {
|
fn to_py(res: Result<eval::Value, eval::Error>, py: Python<'_>) -> PyResult<PyObject> {
|
||||||
res.map_err(|e| PyErr::from(EdLrrError::EvalError(e)))
|
res.map_err(|e| PyErr::from(EdLrrError::EvalError(e)))
|
||||||
.and_then(|r| to_py_value(r, py))
|
.and_then(|r| to_py_value(r, py))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pyfunction]
|
#[pyfunction]
|
||||||
#[pyo3(text_signature = "(expr)")]
|
#[pyo3(text_signature = "(expr)")]
|
||||||
fn expr_test(expr: &str, py: Python) -> PyResult<PyObject> {
|
fn expr_test(expr: &str, py: Python<'_>) -> PyResult<PyObject> {
|
||||||
use eval::{to_value, Expr, Value};
|
use eval::{to_value, Expr, Value};
|
||||||
let mut res = Expr::new(expr)
|
let mut res = Expr::new(expr)
|
||||||
.compile()
|
.compile()
|
||||||
|
@ -647,7 +677,7 @@ fn preprocess_galaxy(path: &str, out_path: &str) -> PyResult<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pymodule]
|
#[pymodule]
|
||||||
pub fn _ed_lrr(_py: Python, m: &PyModule) -> PyResult<()> {
|
pub fn _ed_lrr(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
|
||||||
better_panic::install();
|
better_panic::install();
|
||||||
pyo3_log::init();
|
pyo3_log::init();
|
||||||
profiling::init();
|
profiling::init();
|
||||||
|
|
|
@ -1,26 +1,62 @@
|
||||||
use crate::common::{EdLrrError, EdLrrResult, System};
|
use crate::common::{EdLrrError, EdLrrResult, System};
|
||||||
use crate::info;
|
use crate::info;
|
||||||
|
use crossbeam_channel::bounded;
|
||||||
use csv_core::{ReadFieldResult, Reader};
|
use csv_core::{ReadFieldResult, Reader};
|
||||||
|
use dashmap::DashMap;
|
||||||
|
use eyre::Result;
|
||||||
|
use itertools::Itertools;
|
||||||
use memmap::Mmap;
|
use memmap::Mmap;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub fn mmap_csv(path: &Path, query: Vec<String>) -> Result<HashMap<String, Option<u32>>, String> {
|
struct MmapCsv {
|
||||||
let file = File::open(path).map_err(|e| e.to_string())?;
|
mm: Mmap,
|
||||||
let mm = unsafe { Mmap::map(&file) }.map_err(|e| e.to_string())?;
|
}
|
||||||
let mut best = query
|
|
||||||
.iter()
|
impl MmapCsv {
|
||||||
.map(|s| (s, (s.as_bytes(), usize::MAX, u32::MAX)))
|
fn new(path: &Path) -> Result<Self> {
|
||||||
.collect::<Vec<(&String, (_, usize, u32))>>();
|
let file = File::open(path)?;
|
||||||
|
let mm = unsafe { Mmap::map(&file) }?;
|
||||||
|
Ok(Self { mm })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search(&self, query: Vec<String>) -> Result<HashMap<String, Option<u32>>, EdLrrError> {
|
||||||
let t_start = std::time::Instant::now();
|
let t_start = std::time::Instant::now();
|
||||||
|
let map = Arc::new(DashMap::new());
|
||||||
|
let (tx, rx) = bounded(1024);
|
||||||
|
let query_b = query.iter().map(|s| s.bytes().collect_vec()).collect_vec();
|
||||||
|
let mut workers = (0..(num_cpus::get()))
|
||||||
|
.map(|_| {
|
||||||
|
let query_b = query_b.clone();
|
||||||
|
let query = query.clone();
|
||||||
|
let rx = rx.clone();
|
||||||
|
let map = map.clone();
|
||||||
|
std::thread::spawn(move || {
|
||||||
let dist = eddie::slice::DamerauLevenshtein::new();
|
let dist = eddie::slice::DamerauLevenshtein::new();
|
||||||
let mut row = 0;
|
rx.into_iter()
|
||||||
{
|
// .flatten()
|
||||||
let mut data = &mm[..];
|
.for_each(|(id, name): (_, Vec<u8>)| {
|
||||||
|
for (query, query_b) in query.iter().zip(query_b.iter()) {
|
||||||
|
let d = dist.distance(name.as_slice(), query_b);
|
||||||
|
let mut e = map.entry(query.clone()).or_insert((usize::MAX, None));
|
||||||
|
if d < e.0 {
|
||||||
|
*e = (d, Some(id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect_vec();
|
||||||
|
drop(rx);
|
||||||
|
let mut data = &self.mm[..];
|
||||||
let mut rdr = Reader::new();
|
let mut rdr = Reader::new();
|
||||||
let mut field = [0; 1024];
|
let mut field = [0; 1024];
|
||||||
let mut fieldidx = 0;
|
let mut fieldidx = 0;
|
||||||
|
// let mut chunk = vec![];
|
||||||
|
let mut sys_id = 0u32;
|
||||||
|
let mut row = 0;
|
||||||
loop {
|
loop {
|
||||||
let (result, nread, nwrite) = rdr.read_field(data, &mut field);
|
let (result, nread, nwrite) = rdr.read_field(data, &mut field);
|
||||||
data = &data[nread..];
|
data = &data[nread..];
|
||||||
|
@ -28,18 +64,22 @@ pub fn mmap_csv(path: &Path, query: Vec<String>) -> Result<HashMap<String, Optio
|
||||||
match result {
|
match result {
|
||||||
ReadFieldResult::InputEmpty => {}
|
ReadFieldResult::InputEmpty => {}
|
||||||
ReadFieldResult::OutputFull => {
|
ReadFieldResult::OutputFull => {
|
||||||
return Err("Encountered field larget than 1024 bytes!".to_string());
|
return Err(EdLrrError::ResolveError(
|
||||||
|
"Encountered field larget than 1024 bytes!".to_string(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
ReadFieldResult::Field { record_end } => {
|
ReadFieldResult::Field { record_end } => {
|
||||||
if fieldidx == 1 {
|
match fieldidx {
|
||||||
for (_, (name_b, best_dist, id)) in best.iter_mut() {
|
0 => {
|
||||||
let d = dist.distance(name_b, field);
|
sys_id = unsafe { std::str::from_utf8_unchecked(field) }
|
||||||
if d < *best_dist {
|
.parse::<u32>()
|
||||||
*best_dist = d;
|
.unwrap();
|
||||||
*id = row;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
1 => tx
|
||||||
|
.send((sys_id, field.to_vec()))
|
||||||
|
.map_err(|e| EdLrrError::ResolveError(e.to_string()))?,
|
||||||
|
_ => (),
|
||||||
|
};
|
||||||
if record_end {
|
if record_end {
|
||||||
fieldidx = 0;
|
fieldidx = 0;
|
||||||
row += 1;
|
row += 1;
|
||||||
|
@ -54,16 +94,28 @@ pub fn mmap_csv(path: &Path, query: Vec<String>) -> Result<HashMap<String, Optio
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
drop(tx);
|
||||||
|
for w in workers.drain(..) {
|
||||||
|
w.join().unwrap();
|
||||||
}
|
}
|
||||||
let search_result = best
|
let res = Arc::try_unwrap(map)
|
||||||
.drain(..)
|
.unwrap()
|
||||||
.map(|(query_name, (_, _, idx))| (query_name.clone(), Some(idx)))
|
.into_iter()
|
||||||
.collect::<HashMap<String, Option<u32>>>();
|
.map(|(k, (_, id))| (k, id))
|
||||||
|
.collect::<HashMap<_, _>>();
|
||||||
let rate = (row as f64) / t_start.elapsed().as_secs_f64();
|
let rate = (row as f64) / t_start.elapsed().as_secs_f64();
|
||||||
info!(
|
info!(
|
||||||
"Took: {:.2?}, {:.2} systems/second",
|
"Took: {:.2?}, {:.2} systems/second",
|
||||||
t_start.elapsed(),
|
t_start.elapsed(),
|
||||||
rate
|
rate
|
||||||
);
|
);
|
||||||
Ok(search_result)
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mmap_csv(
|
||||||
|
path: &Path,
|
||||||
|
query: Vec<String>,
|
||||||
|
) -> Result<HashMap<String, Option<u32>>, EdLrrError> {
|
||||||
|
MmapCsv::new(path)?.search(query)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ use crate::profiling::{span, Level};
|
||||||
use crate::ship::Ship;
|
use crate::ship::Ship;
|
||||||
|
|
||||||
use crossbeam_channel::{bounded, unbounded, Receiver, SendError, Sender};
|
use crossbeam_channel::{bounded, unbounded, Receiver, SendError, Sender};
|
||||||
|
use dashmap::{DashMap, DashSet};
|
||||||
use derivative::Derivative;
|
use derivative::Derivative;
|
||||||
use dict_derive::IntoPyObject;
|
use dict_derive::IntoPyObject;
|
||||||
|
|
||||||
|
@ -18,6 +19,8 @@ use permutohedron::LexicalPermutation;
|
||||||
|
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
use pythonize::depythonize;
|
use pythonize::depythonize;
|
||||||
|
use rayon::prelude::*;
|
||||||
|
use rayon::ThreadPoolBuilder;
|
||||||
use rstar::{PointDistance, RStarInsertionStrategy, RTree, RTreeObject, RTreeParams, AABB};
|
use rstar::{PointDistance, RStarInsertionStrategy, RTree, RTreeObject, RTreeParams, AABB};
|
||||||
use rustc_hash::{FxHashMap, FxHashSet};
|
use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -27,10 +30,11 @@ use std::fs::File;
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::io::{BufReader, BufWriter, Write};
|
use std::io::{BufReader, BufWriter, Write};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::thread::JoinHandle;
|
use std::thread::JoinHandle;
|
||||||
use std::time::Instant;
|
use std::time::{Duration, Instant};
|
||||||
use std::{
|
use std::{
|
||||||
collections::{BinaryHeap, VecDeque},
|
collections::{BinaryHeap, VecDeque},
|
||||||
path::Path,
|
path::Path,
|
||||||
|
@ -317,8 +321,8 @@ impl TryFrom<PyModeConfig> for ModeConfig {
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
pub enum PrecomputeMode {
|
pub enum PrecomputeMode {
|
||||||
Full,
|
Full,
|
||||||
Route_From,
|
Route_From(u32),
|
||||||
Route_To,
|
Route_To(u32),
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -817,6 +821,74 @@ impl Router {
|
||||||
return self.scoopable.contains(&id);
|
return self.scoopable.contains(&id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn bfs_loop_test(&self, range: f32, source: &TreeNode, goal: &TreeNode, n: usize) -> (bool, usize, usize) {
|
||||||
|
// info!("Starting thread pool");
|
||||||
|
// ThreadPoolBuilder::new()
|
||||||
|
// .num_threads(8)
|
||||||
|
// .build_global()
|
||||||
|
// .unwrap();
|
||||||
|
let t_start = Instant::now();
|
||||||
|
let route_dist = dist(&source.pos, &goal.pos);
|
||||||
|
let seen: Arc<DashMap<u32, u32>> = Arc::new(DashMap::new());
|
||||||
|
let mut depth = 0;
|
||||||
|
let mut queue = vec![*source];
|
||||||
|
let mut queue_next = vec![];
|
||||||
|
let tree = self.tree.clone();
|
||||||
|
let r2 = range * range;
|
||||||
|
let mut found = false;
|
||||||
|
while !queue.is_empty() {
|
||||||
|
depth += 1;
|
||||||
|
let seen = seen.clone();
|
||||||
|
queue_next.extend(queue.drain(..).flat_map(|sys| {
|
||||||
|
let seen = seen.clone();
|
||||||
|
tree.locate_within_distance(sys.pos, r2)
|
||||||
|
.filter_map(move |nb| seen.insert(nb.id, sys.id).is_none().then_some(*nb))
|
||||||
|
}));
|
||||||
|
if seen.contains_key(&goal.id) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
std::mem::swap(&mut queue_next, &mut queue);
|
||||||
|
if n != 0 {
|
||||||
|
queue.sort_by_cached_key(|v| F32(heuristic(range, v, goal)));
|
||||||
|
queue.truncate(n);
|
||||||
|
}
|
||||||
|
// info!("[{}|{}] {}", goal.id, depth, queue.len());
|
||||||
|
}
|
||||||
|
let seen = Arc::try_unwrap(seen)
|
||||||
|
.unwrap()
|
||||||
|
.into_iter()
|
||||||
|
.collect::<FxHashMap<u32, u32>>();
|
||||||
|
info!(
|
||||||
|
"[{}|{}->{} ({:.02} Ly)|{}] Depth: {} Seen: {} ({:.02}%) Took: {}",
|
||||||
|
n,
|
||||||
|
source.id,
|
||||||
|
goal.id,
|
||||||
|
route_dist,
|
||||||
|
found,
|
||||||
|
depth,
|
||||||
|
seen.len(),
|
||||||
|
((seen.len() as f64) / (tree.size() as f64)) * 100.0,
|
||||||
|
humantime::format_duration(t_start.elapsed())
|
||||||
|
);
|
||||||
|
return (found, depth, seen.len());
|
||||||
|
let path=self.reconstruct(goal.id, &seen);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reconstruct(&self, goal_id: u32, map: &FxHashMap<u32, u32>) -> Result<Vec<System>, String> {
|
||||||
|
let mut path = vec![];
|
||||||
|
let mut current = goal_id;
|
||||||
|
while let Some(next) = map.get(¤t) {
|
||||||
|
path.push(
|
||||||
|
self.get(*next)?
|
||||||
|
.ok_or(format!("System ID {} not found", next))?,
|
||||||
|
);
|
||||||
|
current = *next;
|
||||||
|
}
|
||||||
|
path.reverse();
|
||||||
|
Ok(path)
|
||||||
|
}
|
||||||
|
|
||||||
fn best_multiroute(
|
fn best_multiroute(
|
||||||
&mut self,
|
&mut self,
|
||||||
waypoints: &[System],
|
waypoints: &[System],
|
||||||
|
@ -1468,7 +1540,9 @@ impl Router {
|
||||||
let mut refuels = state.refuels;
|
let mut refuels = state.refuels;
|
||||||
let dist = dist(&nb.pos, &state.node.pos);
|
let dist = dist(&nb.pos, &state.node.pos);
|
||||||
let (fuel_cost, new_fuel) = {
|
let (fuel_cost, new_fuel) = {
|
||||||
if let Some(res) = ship.fuel_cost_for_jump(state.fuel, dist, state.node.get_mult()) {
|
if let Some(res) =
|
||||||
|
ship.fuel_cost_for_jump(state.fuel, dist, state.node.get_mult())
|
||||||
|
{
|
||||||
// can jump with current amount of fuel
|
// can jump with current amount of fuel
|
||||||
res
|
res
|
||||||
} else if let Some(res) =
|
} else if let Some(res) =
|
||||||
|
@ -1544,7 +1618,22 @@ impl Router {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn floyd_warshall(&self, _range: f32) {
|
pub fn floyd_warshall(&self, range: f32) -> Result<Vec<System>, String> {
|
||||||
|
let mut dist: FxHashMap<u64, usize> = FxHashMap::default();
|
||||||
|
info!("nb...");
|
||||||
|
let total = self.tree.size();
|
||||||
|
for (n, node) in self.tree.iter().enumerate() {
|
||||||
|
if (n % 100_000) == 0 {
|
||||||
|
println!("{}/{}", n, total);
|
||||||
|
}
|
||||||
|
let key = (node.id as u64) << 32;
|
||||||
|
for nb in self.neighbours(node, range) {
|
||||||
|
let key = key | nb.id as u64;
|
||||||
|
dist.entry(key).or_insert(1);
|
||||||
|
}
|
||||||
|
let key = ((node.id as u64) << 32) | node.id as u64;
|
||||||
|
dist.insert(key, 0);
|
||||||
|
}
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1553,86 +1642,81 @@ impl Router {
|
||||||
h = (dist(node,goal)-(range*node.mult)).max(0.0) // remaining distance after jumping from here
|
h = (dist(node,goal)-(range*node.mult)).max(0.0) // remaining distance after jumping from here
|
||||||
*/
|
*/
|
||||||
let src = self.tree.nearest_neighbor(&[0.0, 0.0, 0.0]).unwrap();
|
let src = self.tree.nearest_neighbor(&[0.0, 0.0, 0.0]).unwrap();
|
||||||
|
// let mut route_log = BufWriter::new(File::create("route_log_ib.txt").map_err(|e| e.to_string())?);
|
||||||
let goal = self
|
let goal = self
|
||||||
.tree
|
.tree
|
||||||
// .nearest_neighbor(&[-1111.5625, -134.21875, 65269.75]) // Beagle Point
|
.nearest_neighbor(&[-1111.5625, -134.21875, 65269.75]) // Beagle Point
|
||||||
.nearest_neighbor(&[-9530.5, -910.28125, 19808.125]) // Colonia
|
// .nearest_neighbor(&[-9530.5, -910.28125, 19808.125]) // Colonia
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut best_node = FxHashMap::default();
|
let mut best_node = FxHashMap::default();
|
||||||
let mut prev = FxHashMap::default();
|
// let mut prev = FxHashMap::default();
|
||||||
let mut wait_list: FxHashMap<usize, MinFHeap<TreeNode>> = FxHashMap::default();
|
let mut queue = MinFHeap::new();
|
||||||
let mut in_wait_list: FxHashSet<u32> = FxHashSet::default();
|
|
||||||
loop {
|
|
||||||
let t_start = Instant::now();
|
let t_start = Instant::now();
|
||||||
let mut n = 0usize;
|
let mut n = 0usize;
|
||||||
let mut skipped = 0usize;
|
let mut skipped = 0usize;
|
||||||
let mut depth = 0usize;
|
let mut global_best = u32::MAX;
|
||||||
let mut queue = VecDeque::new();
|
queue.push(heuristic(range, src, goal), (0, src));
|
||||||
queue.push_back(*src);
|
loop {
|
||||||
'outer: loop {
|
println!("Q: {}", queue.len());
|
||||||
// println!("D: {} | Q: {}", depth, queue.len());
|
|
||||||
let mut queue_next = VecDeque::new();
|
|
||||||
if queue.is_empty() {
|
if queue.is_empty() {
|
||||||
warn!(
|
warn!(
|
||||||
"Depth: {} | Visited: {} | Skipped: {} | search space exhausted after {}",
|
"Visited: {} | Skipped: {} | search space exhausted after {}",
|
||||||
depth,
|
|
||||||
n,
|
n,
|
||||||
skipped,
|
skipped,
|
||||||
humantime::format_duration(t_start.elapsed())
|
humantime::format_duration(t_start.elapsed())
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
while let Some(node) = queue.pop_front() {
|
while let Some((_, (depth, node))) = queue.pop() {
|
||||||
let best_len = best_node.len();
|
let best_len = best_node.len();
|
||||||
let best_depth = best_node.entry(node.id).or_insert(depth);
|
let best_depth = best_node.entry(node.id).or_insert(depth);
|
||||||
if depth > *best_depth {
|
if *best_depth > global_best {
|
||||||
skipped += 1;
|
skipped += 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
// writeln!(route_log,"{}, {}",node.id,depth).map_err(|e| e.to_string())?;
|
||||||
|
// route_log.flush().map_err(|e| e.to_string())?;
|
||||||
if depth < *best_depth {
|
if depth < *best_depth {
|
||||||
*best_depth = depth;
|
*best_depth = depth;
|
||||||
}
|
}
|
||||||
n += 1;
|
n += 1;
|
||||||
if node.id == goal.id {
|
if node.id == goal.id {
|
||||||
|
if depth < global_best {
|
||||||
|
global_best = global_best.min(depth);
|
||||||
|
queue.retain(|(_, (d, _))| *d <= global_best);
|
||||||
info!(
|
info!(
|
||||||
"Depth: {}, Skipped: {}, Seen: {} (Total: {}) | Best: {} | elapsed: {}",
|
"Queued: {}, Skipped: {}, Seen: {} (Total: {}) | Best: {} | elapsed: {}",
|
||||||
depth,
|
queue.len(),
|
||||||
skipped,
|
skipped,
|
||||||
n,
|
n,
|
||||||
best_len,
|
best_len,
|
||||||
best_depth,
|
global_best,
|
||||||
humantime::format_duration(t_start.elapsed()).to_string()
|
humantime::format_duration(t_start.elapsed()).to_string()
|
||||||
);
|
);
|
||||||
for layer_n in wait_list.keys().sorted() {
|
|
||||||
println!("WL({}): {}", layer_n, wait_list[layer_n].len());
|
|
||||||
}
|
}
|
||||||
todo!();
|
continue;
|
||||||
break 'outer;
|
} else if n % 10000 == 0 {
|
||||||
|
info!(
|
||||||
|
"Queued: {}, Skipped: {}, Seen: {} (Total: {}) | Best: {} | elapsed: {}",
|
||||||
|
queue.len(),
|
||||||
|
skipped,
|
||||||
|
n,
|
||||||
|
best_len,
|
||||||
|
global_best,
|
||||||
|
humantime::format_duration(t_start.elapsed()).to_string()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
let valid_nbs = self
|
self.neighbours(node, node.get_mult() * range)
|
||||||
.neighbours(&node, node.get_mult() * range)
|
|
||||||
.filter(|nb| (self.valid(nb.id) || (nb.id == goal.id)))
|
.filter(|nb| (self.valid(nb.id) || (nb.id == goal.id)))
|
||||||
.filter(|nb| match best_node.get(&nb.id) {
|
.filter(|nb| match best_node.get(&nb.id) {
|
||||||
Some(&d) => (depth + 1) <= d,
|
Some(&d) => depth < d,
|
||||||
None => true,
|
None => true,
|
||||||
})
|
})
|
||||||
.map(|nb| {
|
.map(|nb| (heuristic(range, nb, goal), nb))
|
||||||
prev.insert(nb.id, node);
|
.for_each(|(h, nb)| {
|
||||||
(F32(heuristic(range, nb, goal)), *nb)
|
// prev.insert(nb.id, node.id);
|
||||||
|
queue.push(h, (depth + 1, nb));
|
||||||
});
|
});
|
||||||
queue_next.extend(valid_nbs);
|
|
||||||
}
|
|
||||||
queue_next.make_contiguous().sort();
|
|
||||||
if let Some((_, nb)) = queue_next.pop_front() {
|
|
||||||
queue.push_back(nb);
|
|
||||||
}
|
|
||||||
let layer = wait_list.entry(depth).or_default();
|
|
||||||
while let Some((F32(v), nb)) = queue_next.pop_front() {
|
|
||||||
if in_wait_list.insert(nb.id) {
|
|
||||||
layer.push(v, nb);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
depth += 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
todo!()
|
todo!()
|
||||||
|
@ -1705,7 +1789,7 @@ impl Router {
|
||||||
let tx = tx_r.clone();
|
let tx = tx_r.clone();
|
||||||
let rx = rx_q.clone();
|
let rx = rx_q.clone();
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
while let Ok(nodes) = rx.recv() {
|
rx.into_iter().for_each(|nodes| {
|
||||||
let mut ret = vec![];
|
let mut ret = vec![];
|
||||||
for node in nodes {
|
for node in nodes {
|
||||||
let res: Vec<TreeNode> =
|
let res: Vec<TreeNode> =
|
||||||
|
@ -1713,9 +1797,8 @@ impl Router {
|
||||||
ret.push((node, res));
|
ret.push((node, res));
|
||||||
}
|
}
|
||||||
tx.send(ret).unwrap();
|
tx.send(ret).unwrap();
|
||||||
}
|
});
|
||||||
drop(tx);
|
drop(tx);
|
||||||
drop(rx);
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
@ -1784,37 +1867,24 @@ impl Router {
|
||||||
|
|
||||||
#[cfg_attr(feature = "profiling", tracing::instrument)]
|
#[cfg_attr(feature = "profiling", tracing::instrument)]
|
||||||
pub fn precompute_all(&mut self, range: f32) -> Result<(), String> {
|
pub fn precompute_all(&mut self, range: f32) -> Result<(), String> {
|
||||||
|
use flate2::write::GzEncoder;
|
||||||
let fh_nb = File::create(format!(r#"O:\nb_{}.dat"#, range)).unwrap();
|
let fh_nb = File::create(format!(r#"O:\nb_{}.dat"#, range)).unwrap();
|
||||||
let mut buf_writer = BufWriter::new(fh_nb);
|
let mut fh_encoder = BufWriter::new(fh_nb);
|
||||||
let mut pos: u64 = 0;
|
let mut pos: u64 = 0;
|
||||||
|
let mut n = 0;
|
||||||
let total = self.tree.size();
|
let total = self.tree.size();
|
||||||
let (tx, rx, threads) = self.neighbor_workers(num_cpus::get(), range);
|
// let (tx, rx, threads) = self.neighbor_workers(num_cpus::get(), range);
|
||||||
let mut n: usize = 0;
|
|
||||||
let mut map: FxHashMap<u32, u64> = FxHashMap::default();
|
let mut map: FxHashMap<u32, u64> = FxHashMap::default();
|
||||||
info!("Precomputing neighbor map");
|
info!("Precomputing neighbor map...");
|
||||||
info!("Sumbitting jobs");
|
self.tree.iter().for_each(|node| {
|
||||||
self.tree
|
let nb = self.neighbours(node, range).map(|nb| nb.id).collect_vec();
|
||||||
.iter()
|
map.insert(node.id, pos);
|
||||||
.chunks(10_000)
|
pos += fh_encoder.write(&bincode::serialize(&nb).unwrap()).unwrap() as u64;
|
||||||
.into_iter()
|
if (n % 10000) == 0 {
|
||||||
.for_each(|chunk| {
|
|
||||||
tx.send(chunk.cloned().collect()).unwrap();
|
|
||||||
});
|
|
||||||
drop(tx);
|
|
||||||
info!("Processing...");
|
|
||||||
rx.into_iter()
|
|
||||||
.flatten()
|
|
||||||
.enumerate()
|
|
||||||
.for_each(|(n, (node, mut neighbors))| {
|
|
||||||
let neighbors: Vec<u32> = neighbors.drain(..).map(|n| n.id).collect();
|
|
||||||
// map.insert(node.id, pos);
|
|
||||||
pos += buf_writer
|
|
||||||
.write(&bincode::serialize(&neighbors).unwrap())
|
|
||||||
.unwrap() as u64;
|
|
||||||
if (n % 100000) == 0 {
|
|
||||||
let prc = ((n as f64) / (total as f64)) * 100f64;
|
let prc = ((n as f64) / (total as f64)) * 100f64;
|
||||||
info!("{}/{} ({:.2}%) done, {} bytes", n, total, prc, pos);
|
info!("{}/{} ({:.2}%) done, {} bytes", n, total, prc, pos);
|
||||||
}
|
}
|
||||||
|
n += 1;
|
||||||
});
|
});
|
||||||
let mut fh_idx = BufWriter::new(File::create(format!(r#"O:\nb_{}.idx"#, range)).unwrap());
|
let mut fh_idx = BufWriter::new(File::create(format!(r#"O:\nb_{}.idx"#, range)).unwrap());
|
||||||
info!("Writing index map");
|
info!("Writing index map");
|
||||||
|
@ -1822,11 +1892,6 @@ impl Router {
|
||||||
"Wrote {} bytes",
|
"Wrote {} bytes",
|
||||||
fh_idx.write(&bincode::serialize(&map).unwrap()).unwrap()
|
fh_idx.write(&bincode::serialize(&map).unwrap()).unwrap()
|
||||||
);
|
);
|
||||||
info!("Joining threads");
|
|
||||||
for t in threads {
|
|
||||||
t.join().unwrap();
|
|
||||||
}
|
|
||||||
info!("Done!");
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2476,7 +2541,9 @@ impl Router {
|
||||||
let next_depth = depth + 1;
|
let next_depth = depth + 1;
|
||||||
match node {
|
match node {
|
||||||
BiDirNode::Forward(node) => {
|
BiDirNode::Forward(node) => {
|
||||||
let nbs = self.neighbours(&node, node.get_mult() * range).filter_map(|nb| {
|
let nbs =
|
||||||
|
self.neighbours(&node, node.get_mult() * range)
|
||||||
|
.filter_map(|nb| {
|
||||||
if !seen_fwd.insert(nb.id) {
|
if !seen_fwd.insert(nb.id) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
|
@ -229,7 +229,7 @@ impl Ship {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FSD {
|
impl FSD {
|
||||||
pub fn to_object(&self, py: Python) -> PyResult<PyObject> {
|
pub fn to_object(&self, py: Python<'_>) -> PyResult<PyObject> {
|
||||||
let elem = PyDict::new(py);
|
let elem = PyDict::new(py);
|
||||||
elem.set_item("rating_val", self.rating_val)?;
|
elem.set_item("rating_val", self.rating_val)?;
|
||||||
elem.set_item("class_val", self.class_val)?;
|
elem.set_item("class_val", self.class_val)?;
|
||||||
|
@ -242,7 +242,7 @@ impl FSD {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ship {
|
impl Ship {
|
||||||
pub fn to_object(&self, py: Python) -> PyResult<PyObject> {
|
pub fn to_object(&self, py: Python<'_>) -> PyResult<PyObject> {
|
||||||
let elem = PyDict::new(py);
|
let elem = PyDict::new(py);
|
||||||
elem.set_item("base_mass", self.base_mass)?;
|
elem.set_item("base_mass", self.base_mass)?;
|
||||||
elem.set_item("fuel_mass", self.fuel_mass)?;
|
elem.set_item("fuel_mass", self.fuel_mass)?;
|
||||||
|
|
6
setup.py
6
setup.py
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from setuptools import find_packages, setup
|
from setuptools import find_packages, find_namespace_packages, setup
|
||||||
from setuptools_rust import Binding, RustExtension, Strip
|
from setuptools_rust import Binding, RustExtension, Strip
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@ setup(
|
||||||
description="Elite: Dangerous long range route plotter",
|
description="Elite: Dangerous long range route plotter",
|
||||||
long_description=long_description,
|
long_description=long_description,
|
||||||
long_description_content_type="text/markdown",
|
long_description_content_type="text/markdown",
|
||||||
url="https://gitlab.com/Earthnuker/ed_lrr/-/tree/pyqt_gui",
|
url="https://gitdab.com/Earthnuker/ED_LRR/src/branch/pyqt_gui",
|
||||||
rust_extensions=[
|
rust_extensions=[
|
||||||
RustExtension(
|
RustExtension(
|
||||||
"_ed_lrr",
|
"_ed_lrr",
|
||||||
|
@ -82,7 +82,7 @@ setup(
|
||||||
quiet=True,
|
quiet=True,
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
packages=find_packages(),
|
packages=find_namespace_packages(),
|
||||||
entry_points={
|
entry_points={
|
||||||
"console_scripts": ["ed_lrr = ed_lrr_gui.__main__:main"],
|
"console_scripts": ["ed_lrr = ed_lrr_gui.__main__:main"],
|
||||||
"gui_scripts": ["ed_lrr_gui = ed_lrr_gui.__main__:gui_main"],
|
"gui_scripts": ["ed_lrr_gui = ed_lrr_gui.__main__:gui_main"],
|
||||||
|
|
Loading…
Reference in a new issue