From 9859f64d654f88a2e09eb2323a34937e9707cab0 Mon Sep 17 00:00:00 2001 From: Andy Killorin <37423245+Speedy6451@users.noreply.github.com> Date: Tue, 26 Dec 2023 00:09:25 -0600 Subject: [PATCH] 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 --- server/src/blocks.rs | 112 ++++++++++++++++++++++++++----------------- server/src/main.rs | 54 ++++++++++----------- server/src/mine.rs | 7 ++- server/src/paths.rs | 2 +- 4 files changed, 101 insertions(+), 74 deletions(-) diff --git a/server/src/blocks.rs b/server/src/blocks.rs index b94f2b1..4b05d91 100644 --- a/server/src/blocks.rs +++ b/server/src/blocks.rs @@ -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); +#[derive(Serialize, Deserialize)] +pub struct World(HashMap); // 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 { - 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 { - 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(&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 { - 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;CHUNK_SIZE];CHUNK_SIZE];CHUNK_SIZE] + data: [[[Option;CHUNK_SIZE];CHUNK_SIZE];CHUNK_SIZE] } impl Chunk { fn new(pos: Vec3) -> Self { + let data :[[[Option;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 = (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 { 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)) + } +} diff --git a/server/src/main.rs b/server/src/main.rs index 759ba76..e2fa877 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -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) -> &'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) -> anyhow::Result { - let turtles = match tokio::fs::OpenOptions::new() + let turtles: Vec = 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) -> anyhow::Result .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 = 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, - world: RTree, + world: World, depots: Vec, //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) -> 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, diff --git a/server/src/mine.rs b/server/src/mine.rs index de27456..27377cd 100644 --- a/server/src/mine.rs +++ b/server/src/mine.rs @@ -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 { - 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() diff --git a/server/src/paths.rs b/server/src/paths.rs index e49f379..9f9f916 100644 --- a/server/src/paths.rs +++ b/server/src/paths.rs @@ -74,7 +74,7 @@ fn next(from: &Position, world: &World) -> Vec<(Position, u32)> { unknown: Option, ) { world - .locate_at_point(&point.into()) + .get(point) .map_or(unknown, |b| difficulty(&b.name)) .map(|d| vec.push((Position::new(point, orientation), d))); }