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 rstar::{PointDistance, RTree, RTreeObject, AABB, Envelope};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::sync::{RwLock, OwnedRwLockReadGuard};
|
||||
use memoize::memoize;
|
||||
|
||||
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_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 {
|
||||
pub fn new() -> Self {
|
||||
World(HashMap::new())
|
||||
}
|
||||
pub fn get(&self, block: Vec3) -> Option<Block> {
|
||||
let chunk = block.component_div(&CHUNK_VEC);
|
||||
let chunk = self.get_chunk(chunk)?;
|
||||
chunk.get(block)
|
||||
let chunk = self.get_chunk(block)?;
|
||||
Some(chunk.get(block)?)
|
||||
}
|
||||
|
||||
pub fn set(&mut self, block: Block) -> Option<Block> {
|
||||
let chunk = block.pos.component_div(&CHUNK_VEC);
|
||||
let chunk = self.get_chunk(chunk)?;
|
||||
chunk.set(block)
|
||||
}
|
||||
|
||||
fn get_chunk(&self, block: Vec3) -> &Chunk {
|
||||
let block = block.component_div(&CHUNK_VEC);
|
||||
if let Some(chunk) = self.0.locate_at_point(&block.into()) {
|
||||
return chunk;
|
||||
pub fn set(&mut self, block: Block) {
|
||||
let chunk = block.pos.map(|n| i32::div_floor(n,CHUNK_SIZE as i32));
|
||||
match self.0.get_mut(&chunk) {
|
||||
Some(chunk) => {
|
||||
chunk.set(block).unwrap();
|
||||
},
|
||||
None => {
|
||||
let mut new_chunk = Chunk::new(chunk);
|
||||
new_chunk.set(block).unwrap();
|
||||
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] {
|
||||
let mut chunk: Option<&Chunk> = None;
|
||||
|
||||
blocks.iter().map(|b|{
|
||||
if !chunk.is_some_and(|c| c.contains(b)) {
|
||||
chunk = Some(self.get_chunk(b));
|
||||
}
|
||||
chunk.unwrap().get(b)
|
||||
}).collect()
|
||||
fn get_chunk(&self, block: Vec3) -> Option<&Chunk> {
|
||||
let block = block.map(|n| i32::div_floor(n,CHUNK_SIZE as i32));
|
||||
self.0.get(&block)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -57,11 +51,11 @@ pub struct 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 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) {
|
||||
|
@ -92,43 +86,48 @@ pub struct Block {
|
|||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct Chunk {
|
||||
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 {
|
||||
fn new(pos: Vec3) -> Self {
|
||||
let data :[[[Option<String>;CHUNK_SIZE];CHUNK_SIZE];CHUNK_SIZE]= Default::default();
|
||||
Self {
|
||||
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 local = pos.pos - chunk;
|
||||
if !self.contains(&pos) {
|
||||
return Err(());
|
||||
if !self.contains(&pos.pos) {
|
||||
return Err(anyhow!("out of bounds"));
|
||||
}
|
||||
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(())
|
||||
}
|
||||
|
||||
fn get(&self, pos: Position) -> Option<&Block> {
|
||||
fn get(&self, pos: Vec3) -> Option<Block> {
|
||||
let chunk = self.pos.component_mul(&CHUNK_VEC);
|
||||
let local = pos.pos - chunk;
|
||||
let local = pos - chunk;
|
||||
if !self.contains(&pos) {
|
||||
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 local = pos.pos - chunk;
|
||||
AABB::from_corners(chunk, chunk+CHUNK_VEC).contains_point(&local)
|
||||
let local = pos - chunk;
|
||||
local >= Vec3::zeros() && local <= CHUNK_VEC
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -279,3 +278,28 @@ pub fn nearest(from: Vec3, to: Vec3) -> Position {
|
|||
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};
|
||||
|
||||
|
@ -8,7 +8,7 @@ use axum::{
|
|||
routing::{get},
|
||||
Router,
|
||||
};
|
||||
use blocks::{SharedWorld, Position, };
|
||||
use blocks::{SharedWorld, Position, World, };
|
||||
use depot::Depots;
|
||||
use opentelemetry::global;
|
||||
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<()> {
|
||||
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 world = bincode::serialize(&state.world)?;
|
||||
let depots = serde_json::to_string_pretty(&state.depots)?;
|
||||
let turtles = serde_json::to_string_pretty(&turtles)?;
|
||||
let world = bincode::serialize(&*state.world.clone().lock().await)?;
|
||||
let depots = serde_json::to_string_pretty(&depots)?;
|
||||
let tasks = serde_json::to_string_pretty(tasks)?;
|
||||
|
||||
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> {
|
||||
let turtles = match tokio::fs::OpenOptions::new()
|
||||
let turtles: Vec<Turtle> = match tokio::fs::OpenOptions::new()
|
||||
.read(true)
|
||||
.open(SAVE.get().unwrap().join("turtles.json"))
|
||||
.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 {
|
||||
tokio::io::Result::Ok(file) => bincode::deserialize_from(file.into_std().await)?,
|
||||
tokio::io::Result::Err(e) => match e.kind() {
|
||||
ErrorKind::NotFound => RTree::new(),
|
||||
ErrorKind::NotFound => World::new(),
|
||||
_ => panic!(),
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
let saved = SavedState {
|
||||
turtles,
|
||||
world,
|
||||
depots,
|
||||
let scheduler = scheduler;let sender = kill;
|
||||
let mut bound_turtles: Vec<Turtle> = Vec::new();
|
||||
for turtle in turtles.into_iter() {
|
||||
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 live = LiveState::from_save(saved, scheduler, kill);
|
||||
|
||||
Ok(live)
|
||||
let depots = Depots::from_vec(depots);
|
||||
|
||||
Ok(LiveState { turtles: bound_turtles.into_iter().map(|t| Arc::new(RwLock::new(t))).collect(), tasks: scheduler,
|
||||
world: SharedWorld::from_world(world),
|
||||
depots,
|
||||
started: Instant::now(),
|
||||
kill:sender,
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct SavedState {
|
||||
turtles: Vec<turtle::Turtle>,
|
||||
world: RTree<Block>,
|
||||
world: World,
|
||||
depots: Vec<Position>,
|
||||
//chunkloaders: unimplemented!(),
|
||||
}
|
||||
|
@ -227,15 +236,6 @@ struct 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 {
|
||||
let mut turtles = Vec::new();
|
||||
for turtle in save.turtles.into_iter() {
|
||||
|
@ -244,7 +244,7 @@ impl LiveState {
|
|||
};
|
||||
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,
|
||||
started: Instant::now(),
|
||||
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> {
|
||||
turtle.world().lock().await
|
||||
.locate_within_distance(pos.into(), chunk.map(|n| n.pow(2)).sum())
|
||||
let scan = (0..=(chunk*2).product()).map(|n| fill(chunk * 2, n) - chunk/2);
|
||||
|
||||
let world = turtle.world().lock().await;
|
||||
scan.map(|n| world.get(n))
|
||||
.filter_map(|f| f)
|
||||
.filter(|n| n.name != "minecraft:air")
|
||||
.filter(|n| VALUABLE.iter().any(|v| n.name.contains(v)))
|
||||
.map(|b|b.pos).collect()
|
||||
|
|
|
@ -74,7 +74,7 @@ fn next(from: &Position, world: &World) -> Vec<(Position, u32)> {
|
|||
unknown: Option<u32>,
|
||||
) {
|
||||
world
|
||||
.locate_at_point(&point.into())
|
||||
.get(point)
|
||||
.map_or(unknown, |b| difficulty(&b.name))
|
||||
.map(|d| vec.push((Position::new(point, orientation), d)));
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue