1
Fork 0

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:
Andy Killorin 2023-12-26 00:09:25 -06:00
parent 8adcfdea38
commit 9859f64d65
Signed by: ank
GPG key ID: B6241CA3B552BCA4
4 changed files with 101 additions and 74 deletions

View file

@ -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))
}
}

View file

@ -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,

View file

@ -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()

View file

@ -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)));
} }