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

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};
@ -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 depots = Depots::from_vec(depots);
let live = LiveState::from_save(saved, scheduler, kill);
Ok(live)
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,

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

View file

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