switched to a hashmap instead of an RTree
I haven't formally benchmarked it but I managed to mine half a chunk with a total of 2s of cpu time
This commit is contained in:
parent
8adcfdea38
commit
9859f64d65
4 changed files with 101 additions and 74 deletions
|
@ -1,11 +1,10 @@
|
||||||
use std::{sync::Arc, ops::Sub};
|
use std::{sync::Arc, ops::Sub, collections::HashMap};
|
||||||
|
|
||||||
use anyhow::Ok;
|
use anyhow::{Ok, anyhow};
|
||||||
use nalgebra::Vector3;
|
use nalgebra::Vector3;
|
||||||
use rstar::{PointDistance, RTree, RTreeObject, AABB, Envelope};
|
use rstar::{PointDistance, RTree, RTreeObject, AABB, Envelope};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::sync::{RwLock, OwnedRwLockReadGuard};
|
use tokio::sync::{RwLock, OwnedRwLockReadGuard};
|
||||||
use memoize::memoize;
|
|
||||||
|
|
||||||
use crate::{turtle::TurtleCommand, paths::{self, TRANSPARENT}};
|
use crate::{turtle::TurtleCommand, paths::{self, TRANSPARENT}};
|
||||||
|
|
||||||
|
@ -13,41 +12,36 @@ const CHUNK_SIZE: usize = 16;
|
||||||
const CHUNK_VOLUME: usize = CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE;
|
const CHUNK_VOLUME: usize = CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE;
|
||||||
const CHUNK_VEC: Vec3 = Vec3::new(CHUNK_SIZE as i32, CHUNK_SIZE as i32, CHUNK_SIZE as i32);
|
const CHUNK_VEC: Vec3 = Vec3::new(CHUNK_SIZE as i32, CHUNK_SIZE as i32, CHUNK_SIZE as i32);
|
||||||
|
|
||||||
pub struct World(RTree<Chunk>);
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct World(HashMap<Vec3, Chunk>); // TODO: make r-trees faster than this, for my sanity
|
||||||
|
|
||||||
impl World {
|
impl World {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
World(HashMap::new())
|
||||||
|
}
|
||||||
pub fn get(&self, block: Vec3) -> Option<Block> {
|
pub fn get(&self, block: Vec3) -> Option<Block> {
|
||||||
let chunk = block.component_div(&CHUNK_VEC);
|
let chunk = self.get_chunk(block)?;
|
||||||
let chunk = self.get_chunk(chunk)?;
|
Some(chunk.get(block)?)
|
||||||
chunk.get(block)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set(&mut self, block: Block) -> Option<Block> {
|
pub fn set(&mut self, block: Block) {
|
||||||
let chunk = block.pos.component_div(&CHUNK_VEC);
|
let chunk = block.pos.map(|n| i32::div_floor(n,CHUNK_SIZE as i32));
|
||||||
let chunk = self.get_chunk(chunk)?;
|
match self.0.get_mut(&chunk) {
|
||||||
chunk.set(block)
|
Some(chunk) => {
|
||||||
}
|
chunk.set(block).unwrap();
|
||||||
|
},
|
||||||
fn get_chunk(&self, block: Vec3) -> &Chunk {
|
None => {
|
||||||
let block = block.component_div(&CHUNK_VEC);
|
let mut new_chunk = Chunk::new(chunk);
|
||||||
if let Some(chunk) = self.0.locate_at_point(&block.into()) {
|
new_chunk.set(block).unwrap();
|
||||||
return chunk;
|
self.0.insert(chunk, new_chunk);
|
||||||
|
},
|
||||||
}
|
}
|
||||||
self.0.insert(Chunk::new(block));
|
|
||||||
&Chunk::new(block)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_bulk<const COUNT:usize>(&self, blocks: [Vec3;COUNT]) -> [Option<&Block>;COUNT] {
|
fn get_chunk(&self, block: Vec3) -> Option<&Chunk> {
|
||||||
let mut chunk: Option<&Chunk> = None;
|
let block = block.map(|n| i32::div_floor(n,CHUNK_SIZE as i32));
|
||||||
|
self.0.get(&block)
|
||||||
blocks.iter().map(|b|{
|
|
||||||
if !chunk.is_some_and(|c| c.contains(b)) {
|
|
||||||
chunk = Some(self.get_chunk(b));
|
|
||||||
}
|
|
||||||
chunk.unwrap().get(b)
|
|
||||||
}).collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -57,11 +51,11 @@ pub struct SharedWorld {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SharedWorld {
|
impl SharedWorld {
|
||||||
pub fn new() -> Self { Self { state: Arc::new(RwLock::new(RTree::new())) } }
|
pub fn new() -> Self { Self { state: Arc::new(RwLock::new(World::new())) } }
|
||||||
pub fn from_world(tree: World) -> Self { Self { state: Arc::new(RwLock::new(tree)) } }
|
pub fn from_world(tree: World) -> Self { Self { state: Arc::new(RwLock::new(tree)) } }
|
||||||
|
|
||||||
pub async fn get(&self, block: Vec3) -> Option<Block> {
|
pub async fn get(&self, block: Vec3) -> Option<Block> {
|
||||||
self.state.read().await.get(block)
|
Some(self.state.read().await.get(block)?.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn set(&self, block: Block) {
|
pub async fn set(&self, block: Block) {
|
||||||
|
@ -92,43 +86,48 @@ pub struct Block {
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct Chunk {
|
pub struct Chunk {
|
||||||
pos: Vec3, /// position in chunk coordinates (world/16)
|
pos: Vec3, /// position in chunk coordinates (world/16)
|
||||||
data: [[[Option<Block>;CHUNK_SIZE];CHUNK_SIZE];CHUNK_SIZE]
|
data: [[[Option<String>;CHUNK_SIZE];CHUNK_SIZE];CHUNK_SIZE]
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Chunk {
|
impl Chunk {
|
||||||
fn new(pos: Vec3) -> Self {
|
fn new(pos: Vec3) -> Self {
|
||||||
|
let data :[[[Option<String>;CHUNK_SIZE];CHUNK_SIZE];CHUNK_SIZE]= Default::default();
|
||||||
Self {
|
Self {
|
||||||
pos,
|
pos,
|
||||||
data:[[[None;CHUNK_SIZE];CHUNK_SIZE];CHUNK_SIZE]
|
data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set(&mut self, pos: Block) -> Result<(), ()> {
|
fn set(&mut self, pos: Block) -> anyhow::Result<()> {
|
||||||
let chunk = self.pos.component_mul(&CHUNK_VEC);
|
let chunk = self.pos.component_mul(&CHUNK_VEC);
|
||||||
let local = pos.pos - chunk;
|
if !self.contains(&pos.pos) {
|
||||||
if !self.contains(&pos) {
|
return Err(anyhow!("out of bounds"));
|
||||||
return Err(());
|
|
||||||
}
|
}
|
||||||
|
let local: Vector3<usize> = (pos.pos - chunk).map(|n| n as usize);
|
||||||
|
|
||||||
self.data[local.x][local.y][local.z] = pos;
|
self.data[local.x][local.y][local.z] = Some(pos.name);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get(&self, pos: Position) -> Option<&Block> {
|
fn get(&self, pos: Vec3) -> Option<Block> {
|
||||||
let chunk = self.pos.component_mul(&CHUNK_VEC);
|
let chunk = self.pos.component_mul(&CHUNK_VEC);
|
||||||
let local = pos.pos - chunk;
|
let local = pos - chunk;
|
||||||
if !self.contains(&pos) {
|
if !self.contains(&pos) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
let local = local.map(|n| n as usize);
|
||||||
|
|
||||||
Ok(self.data[local.x][local.y][local.z])
|
Some(Block {
|
||||||
|
name: self.data[local.x][local.y][local.z].clone()?,
|
||||||
|
pos,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn contains(&self, pos:&Position) -> bool {
|
fn contains(&self, pos:&Vec3) -> bool {
|
||||||
let chunk = self.pos.component_mul(&CHUNK_VEC);
|
let chunk = self.pos.component_mul(&CHUNK_VEC);
|
||||||
let local = pos.pos - chunk;
|
let local = pos - chunk;
|
||||||
AABB::from_corners(chunk, chunk+CHUNK_VEC).contains_point(&local)
|
local >= Vec3::zeros() && local <= CHUNK_VEC
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -279,3 +278,28 @@ pub fn nearest(from: Vec3, to: Vec3) -> Position {
|
||||||
dir
|
dir
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn single_point(point: Vec3) {
|
||||||
|
let mut world = World::new();
|
||||||
|
world.set(Block { name: "a".to_string(), pos: point});
|
||||||
|
|
||||||
|
assert_eq!("a", world.get(point).unwrap().name);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn origin() {
|
||||||
|
single_point(Vec3::zeros())
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn big() {
|
||||||
|
single_point(Vec3::new(1212,100,1292))
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn small() {
|
||||||
|
single_point(Vec3::new(-1212,100,-1292))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#![feature(iter_map_windows, iter_collect_into)]
|
#![feature(iter_map_windows, iter_collect_into, int_roundings)]
|
||||||
|
|
||||||
use std::{collections::VecDeque, io::ErrorKind, sync::Arc, env::args, path, borrow::BorrowMut, time::Duration};
|
use std::{collections::VecDeque, io::ErrorKind, sync::Arc, env::args, path, borrow::BorrowMut, time::Duration};
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ use axum::{
|
||||||
routing::{get},
|
routing::{get},
|
||||||
Router,
|
Router,
|
||||||
};
|
};
|
||||||
use blocks::{SharedWorld, Position, };
|
use blocks::{SharedWorld, Position, World, };
|
||||||
use depot::Depots;
|
use depot::Depots;
|
||||||
use opentelemetry::global;
|
use opentelemetry::global;
|
||||||
use opentelemetry_sdk::{runtime::Tokio, trace::BatchConfig};
|
use opentelemetry_sdk::{runtime::Tokio, trace::BatchConfig};
|
||||||
|
@ -136,11 +136,15 @@ async fn flush(State(state): State<SharedControl>) -> &'static str {
|
||||||
|
|
||||||
async fn write_to_disk(state: &LiveState) -> anyhow::Result<()> {
|
async fn write_to_disk(state: &LiveState) -> anyhow::Result<()> {
|
||||||
let tasks = &state.tasks;
|
let tasks = &state.tasks;
|
||||||
let state = state.save().await;
|
let mut turtles = Vec::new();
|
||||||
|
for turtle in state.turtles.iter() {
|
||||||
|
turtles.push(turtle.read().await.info());
|
||||||
|
};
|
||||||
|
let depots = state.depots.clone().to_vec().await;
|
||||||
|
|
||||||
let turtles = serde_json::to_string_pretty(&state.turtles)?;
|
let turtles = serde_json::to_string_pretty(&turtles)?;
|
||||||
let world = bincode::serialize(&state.world)?;
|
let world = bincode::serialize(&*state.world.clone().lock().await)?;
|
||||||
let depots = serde_json::to_string_pretty(&state.depots)?;
|
let depots = serde_json::to_string_pretty(&depots)?;
|
||||||
let tasks = serde_json::to_string_pretty(tasks)?;
|
let tasks = serde_json::to_string_pretty(tasks)?;
|
||||||
|
|
||||||
let path = &SAVE.get().unwrap();
|
let path = &SAVE.get().unwrap();
|
||||||
|
@ -152,7 +156,7 @@ async fn write_to_disk(state: &LiveState) -> anyhow::Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn read_from_disk(kill: watch::Sender<bool>) -> anyhow::Result<LiveState> {
|
async fn read_from_disk(kill: watch::Sender<bool>) -> anyhow::Result<LiveState> {
|
||||||
let turtles = match tokio::fs::OpenOptions::new()
|
let turtles: Vec<Turtle> = match tokio::fs::OpenOptions::new()
|
||||||
.read(true)
|
.read(true)
|
||||||
.open(SAVE.get().unwrap().join("turtles.json"))
|
.open(SAVE.get().unwrap().join("turtles.json"))
|
||||||
.await
|
.await
|
||||||
|
@ -192,27 +196,32 @@ async fn read_from_disk(kill: watch::Sender<bool>) -> anyhow::Result<LiveState>
|
||||||
.read(true).open(SAVE.get().unwrap().join("world.bin")).await {
|
.read(true).open(SAVE.get().unwrap().join("world.bin")).await {
|
||||||
tokio::io::Result::Ok(file) => bincode::deserialize_from(file.into_std().await)?,
|
tokio::io::Result::Ok(file) => bincode::deserialize_from(file.into_std().await)?,
|
||||||
tokio::io::Result::Err(e) => match e.kind() {
|
tokio::io::Result::Err(e) => match e.kind() {
|
||||||
ErrorKind::NotFound => RTree::new(),
|
ErrorKind::NotFound => World::new(),
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
},
|
},
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let saved = SavedState {
|
let scheduler = scheduler;let sender = kill;
|
||||||
turtles,
|
let mut bound_turtles: Vec<Turtle> = Vec::new();
|
||||||
world,
|
for turtle in turtles.into_iter() {
|
||||||
depots,
|
let (tx, rx) = mpsc::channel(1);
|
||||||
|
bound_turtles.push(Turtle::with_channel(turtle.name.to_num(), turtle.position, turtle.fuel, turtle.fuel_limit, tx, rx));
|
||||||
};
|
};
|
||||||
|
let depots = Depots::from_vec(depots);
|
||||||
let live = LiveState::from_save(saved, scheduler, kill);
|
|
||||||
|
Ok(LiveState { turtles: bound_turtles.into_iter().map(|t| Arc::new(RwLock::new(t))).collect(), tasks: scheduler,
|
||||||
Ok(live)
|
world: SharedWorld::from_world(world),
|
||||||
|
depots,
|
||||||
|
started: Instant::now(),
|
||||||
|
kill:sender,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
struct SavedState {
|
struct SavedState {
|
||||||
turtles: Vec<turtle::Turtle>,
|
turtles: Vec<turtle::Turtle>,
|
||||||
world: RTree<Block>,
|
world: World,
|
||||||
depots: Vec<Position>,
|
depots: Vec<Position>,
|
||||||
//chunkloaders: unimplemented!(),
|
//chunkloaders: unimplemented!(),
|
||||||
}
|
}
|
||||||
|
@ -227,15 +236,6 @@ struct LiveState {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LiveState {
|
impl LiveState {
|
||||||
async fn save(&self) -> SavedState {
|
|
||||||
let mut turtles = Vec::new();
|
|
||||||
for turtle in self.turtles.iter() {
|
|
||||||
turtles.push(turtle.read().await.info());
|
|
||||||
};
|
|
||||||
let depots = self.depots.clone().to_vec().await;
|
|
||||||
SavedState { turtles, world: self.world.tree().await, depots }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_save(save: SavedState, scheduler: Scheduler, sender: watch::Sender<bool>) -> Self {
|
fn from_save(save: SavedState, scheduler: Scheduler, sender: watch::Sender<bool>) -> Self {
|
||||||
let mut turtles = Vec::new();
|
let mut turtles = Vec::new();
|
||||||
for turtle in save.turtles.into_iter() {
|
for turtle in save.turtles.into_iter() {
|
||||||
|
@ -244,7 +244,7 @@ impl LiveState {
|
||||||
};
|
};
|
||||||
let depots = Depots::from_vec(save.depots);
|
let depots = Depots::from_vec(save.depots);
|
||||||
|
|
||||||
Self { turtles: turtles.into_iter().map(|t| Arc::new(RwLock::new(t))).collect(), tasks: scheduler, world: SharedWorld::from_tree(save.world),
|
Self { turtles: turtles.into_iter().map(|t| Arc::new(RwLock::new(t))).collect(), tasks: scheduler, world: SharedWorld::from_world(save.world),
|
||||||
depots,
|
depots,
|
||||||
started: Instant::now(),
|
started: Instant::now(),
|
||||||
kill:sender,
|
kill:sender,
|
||||||
|
|
|
@ -71,8 +71,11 @@ pub async fn mine_chunk_and_sweep(turtle: TurtleCommander, pos: Vec3, chunk: Vec
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn near_valuables(turtle: &TurtleCommander, pos: Vec3, chunk: Vec3) -> Vec<Vec3> {
|
async fn near_valuables(turtle: &TurtleCommander, pos: Vec3, chunk: Vec3) -> Vec<Vec3> {
|
||||||
turtle.world().lock().await
|
let scan = (0..=(chunk*2).product()).map(|n| fill(chunk * 2, n) - chunk/2);
|
||||||
.locate_within_distance(pos.into(), chunk.map(|n| n.pow(2)).sum())
|
|
||||||
|
let world = turtle.world().lock().await;
|
||||||
|
scan.map(|n| world.get(n))
|
||||||
|
.filter_map(|f| f)
|
||||||
.filter(|n| n.name != "minecraft:air")
|
.filter(|n| n.name != "minecraft:air")
|
||||||
.filter(|n| VALUABLE.iter().any(|v| n.name.contains(v)))
|
.filter(|n| VALUABLE.iter().any(|v| n.name.contains(v)))
|
||||||
.map(|b|b.pos).collect()
|
.map(|b|b.pos).collect()
|
||||||
|
|
|
@ -74,7 +74,7 @@ fn next(from: &Position, world: &World) -> Vec<(Position, u32)> {
|
||||||
unknown: Option<u32>,
|
unknown: Option<u32>,
|
||||||
) {
|
) {
|
||||||
world
|
world
|
||||||
.locate_at_point(&point.into())
|
.get(point)
|
||||||
.map_or(unknown, |b| difficulty(&b.name))
|
.map_or(unknown, |b| difficulty(&b.name))
|
||||||
.map(|d| vec.push((Position::new(point, orientation), d)));
|
.map(|d| vec.push((Position::new(point, orientation), d)));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue