From 8adcfdea38fd73bf06518acb8e1abb84dfd8a59a Mon Sep 17 00:00:00 2001 From: Andy Killorin <37423245+Speedy6451@users.noreply.github.com> Date: Mon, 25 Dec 2023 22:04:58 -0600 Subject: [PATCH] fix the fact that I barely use any memory (in progress) --- server/Cargo.toml | 1 + server/src/blocks.rs | 117 ++++++++++++++++++++++++++++++++++++------- server/src/main.rs | 6 +-- server/src/paths.rs | 13 ++--- server/src/turtle.rs | 6 +-- 5 files changed, 113 insertions(+), 30 deletions(-) diff --git a/server/Cargo.toml b/server/Cargo.toml index 7baa0fd..772de11 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -40,3 +40,4 @@ opentelemetry = "0.21.0" tracing-opentelemetry = "0.22" opentelemetry-jaeger = { version = "0.20", features = ["rt-tokio"] } opentelemetry_sdk = { version = "0.21.1", features = ["trace"] } +memoize = "0.4.2" diff --git a/server/src/blocks.rs b/server/src/blocks.rs index b41e3b8..b94f2b1 100644 --- a/server/src/blocks.rs +++ b/server/src/blocks.rs @@ -1,46 +1,84 @@ use std::{sync::Arc, ops::Sub}; +use anyhow::Ok; use nalgebra::Vector3; -use rstar::{PointDistance, RTree, RTreeObject, AABB}; +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}}; -pub type WorldReadLock = OwnedRwLockReadGuard>; +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); -#[derive(Clone)] -pub struct World { - state: Arc>>, // interior mutability to get around the - // questionable architecture of this project -} +pub struct World(RTree); impl World { + pub fn get(&self, block: Vec3) -> Option { + let chunk = block.component_div(&CHUNK_VEC); + let chunk = self.get_chunk(chunk)?; + 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; + } + 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() + } + +} + +#[derive(Clone)] +pub struct SharedWorld { + state: Arc>, // interior mutability to get around the + // questionable architecture of this project +} + +impl SharedWorld { pub fn new() -> Self { Self { state: Arc::new(RwLock::new(RTree::new())) } } - pub fn from_tree(tree: RTree) -> Self { Self { state: Arc::new(RwLock::new(tree)) } } - pub async fn to_tree(self) -> RTree { self.state.write().await.to_owned() } - pub async fn tree(&self) -> RTree { self.state.read().await.clone() } + 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.locate_at_point(&block.into()).map(|b| b.to_owned()) + self.state.read().await.get(block) } pub async fn set(&self, block: Block) { - self.state.write().await.remove_at_point(&block.pos.into()); - self.state.write().await.insert(block); + self.state.write().await.set(block); } /// Returns true if a known non-traversable block exists at the point pub async fn occupied(&self, block: Vec3) -> bool { - self.state.read().await.locate_at_point(&block.into()).is_some_and(|b| !TRANSPARENT.contains(&b.name.as_str())) + self.get(block).await.is_some_and(|b| !TRANSPARENT.contains(&b.name.as_str())) } /// Returns true if a "garbage" block exists at the given point which you are free to destroy pub async fn garbage(&self, block: Vec3) -> bool { - self.state.read().await.locate_at_point(&block.into()).is_some_and(|b| paths::difficulty(&b.name).is_some()) + self.get(block).await.is_some_and(|b| paths::difficulty(&b.name).is_some()) } - pub async fn lock(self) -> WorldReadLock { + pub async fn lock(self) -> OwnedRwLockReadGuard { self.state.read_owned().await } } @@ -51,7 +89,50 @@ pub struct Block { pub pos: Vec3, } -impl RTreeObject for 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] +} + +impl Chunk { + fn new(pos: Vec3) -> Self { + Self { + pos, + data:[[[None;CHUNK_SIZE];CHUNK_SIZE];CHUNK_SIZE] + } + } + + fn set(&mut self, pos: Block) -> Result<(), ()> { + let chunk = self.pos.component_mul(&CHUNK_VEC); + let local = pos.pos - chunk; + if !self.contains(&pos) { + return Err(()); + } + + self.data[local.x][local.y][local.z] = pos; + + Ok(()) + } + + fn get(&self, pos: Position) -> Option<&Block> { + let chunk = self.pos.component_mul(&CHUNK_VEC); + let local = pos.pos - chunk; + if !self.contains(&pos) { + return None; + } + + Ok(self.data[local.x][local.y][local.z]) + } + + fn contains(&self, pos:&Position) -> bool { + let chunk = self.pos.component_mul(&CHUNK_VEC); + let local = pos.pos - chunk; + AABB::from_corners(chunk, chunk+CHUNK_VEC).contains_point(&local) + } +} + +impl RTreeObject for Chunk { type Envelope = AABB<[i32; 3]>; fn envelope(&self) -> Self::Envelope { @@ -59,7 +140,7 @@ impl RTreeObject for Block { } } -impl PointDistance for Block { +impl PointDistance for Chunk { fn distance_2(&self, point: &[i32; 3]) -> i32 { (self.pos - Vec3::from(*point)).abs().sum() } diff --git a/server/src/main.rs b/server/src/main.rs index add04f7..759ba76 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -8,7 +8,7 @@ use axum::{ routing::{get}, Router, }; -use blocks::{World, Position, }; +use blocks::{SharedWorld, Position, }; use depot::Depots; use opentelemetry::global; use opentelemetry_sdk::{runtime::Tokio, trace::BatchConfig}; @@ -220,7 +220,7 @@ struct SavedState { struct LiveState { turtles: Vec>>, tasks: Scheduler, - world: blocks::World, + world: blocks::SharedWorld, depots: Depots, started: Instant, kill: watch::Sender, @@ -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: World::from_tree(save.world), + Self { turtles: turtles.into_iter().map(|t| Arc::new(RwLock::new(t))).collect(), tasks: scheduler, world: SharedWorld::from_tree(save.world), depots, started: Instant::now(), kill:sender, diff --git a/server/src/paths.rs b/server/src/paths.rs index c5276df..e49f379 100644 --- a/server/src/paths.rs +++ b/server/src/paths.rs @@ -1,6 +1,7 @@ use crate::{ - blocks::{World, Position, Direction, Vec3, WorldReadLock}, + blocks::{SharedWorld, Position, Direction, Vec3, World, nearest, Block}, }; +use rstar::{AABB, Envelope}; use tokio::task::spawn_blocking; use tracing::{trace, error}; use pathfinding::prelude::astar; @@ -8,7 +9,7 @@ use pathfinding::prelude::astar; const LOOKUP_LIMIT: usize = 10_000_000; #[tracing::instrument(skip(world))] -pub async fn route_facing(from: Position, to: Vec3, world: &World) -> Option> { +pub async fn route_facing(from: Position, to: Vec3, world: &SharedWorld) -> Option> { let facing = move |p: &Position| { let ahead = p.dir.unit() + p.pos; let above = Vec3::y() + p.pos; @@ -19,7 +20,7 @@ pub async fn route_facing(from: Position, to: Vec3, world: &World) -> Option Option> { +pub async fn route(from: Position, to: Position, world: &SharedWorld) -> Option> { trace!("routing from {from:?} to {to:?}"); // attempt at not crashing by looking infinitely into the abyss if world.get(to.pos).await @@ -30,7 +31,7 @@ pub async fn route(from: Position, to: Position, world: &World) -> Option(from: Position, to: Vec3, mut done: D, world: &World) -> Option> +async fn route_to(from: Position, to: Vec3, mut done: D, world: &SharedWorld) -> Option> where D: FnMut(&Position) -> bool + Send + 'static { // lock once, we'll be doing a lot of lookups let world = world.clone().lock().await; @@ -62,14 +63,14 @@ where D: FnMut(&Position) -> bool + Send + 'static { } } -fn next(from: &Position, world: &WorldReadLock) -> Vec<(Position, u32)> { +fn next(from: &Position, world: &World) -> Vec<(Position, u32)> { let mut vec: Vec<(Position, u32)> = Vec::new(); fn insert( vec: &mut Vec<(Position, u32)>, point: Vec3, orientation: Direction, - world: &WorldReadLock, + world: &World, unknown: Option, ) { world diff --git a/server/src/turtle.rs b/server/src/turtle.rs index 743599e..066777c 100644 --- a/server/src/turtle.rs +++ b/server/src/turtle.rs @@ -2,7 +2,7 @@ use crate::blocks::Block; use crate::blocks::Direction; use crate::blocks::Position; use crate::blocks::Vec3; -use crate::blocks::World; +use crate::blocks::SharedWorld; use crate::depot::Depots; use crate::paths::route_facing; @@ -144,7 +144,7 @@ impl Turtle { #[derive(Clone)] pub struct TurtleCommander { sender: Arc, - world: World, + world: SharedWorld, depots: Depots, // everything below is best-effort // TODO: make not bad @@ -221,7 +221,7 @@ impl TurtleCommander { self.max_fuel.load(std::sync::atomic::Ordering::SeqCst) } - pub fn world(&self) -> World { + pub fn world(&self) -> SharedWorld { self.world.clone() }