From dbbc41705e560da5d7b00326d4a249ea1ba2ae8e Mon Sep 17 00:00:00 2001 From: Andy Killorin <37423245+Speedy6451@users.noreply.github.com> Date: Fri, 22 Dec 2023 16:35:30 -0600 Subject: [PATCH] felling --- server/Cargo.toml | 1 + server/Makefile | 2 +- server/src/depot.rs | 10 +-- server/src/fell.rs | 75 ++++++++++++++++++++++- server/src/main.rs | 129 +++++++++++++++++++++++---------------- server/src/mine.rs | 14 ++--- server/src/tasks.rs | 20 +++--- server/src/turtle.rs | 22 ++++++- server/src/turtle_api.rs | 10 +-- 9 files changed, 202 insertions(+), 81 deletions(-) diff --git a/server/Cargo.toml b/server/Cargo.toml index a5e4a6d..4284ae2 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -27,6 +27,7 @@ rstar = { version = "0.11.0", features = ["serde"] } rustmatica = "0.1.1" serde = { version = "1.0.193", features = ["rc", "derive"] } serde_json = "1.0.108" +time = { version = "0.3.31", features = ["serde"] } tokio = { version = "1.0", features = ["full"] } tower = { version = "0.4", features = ["util", "timeout", "load-shed", "limit"] } tower-http = { version = "0.5.0", features = [ diff --git a/server/Makefile b/server/Makefile index 257cef3..5414f5f 100644 --- a/server/Makefile +++ b/server/Makefile @@ -14,7 +14,7 @@ local: surnames.txt names.txt ipaddr.txt global: surnames.txt names.txt ipaddr.txt make -B ipaddr.txt mkdir -p prod - cd prod; cargo run --release $(PRODPORT) $(PRODFILE) + cargo run --release $(PRODPORT) $(PRODFILE) surnames.txt: curl https://raw.githubusercontent.com/Hyneman/moby-project/672f6bdca054c42d375f065ffee87e8ceba0c242/moby/mwords/21986na.mes |\ diff --git a/server/src/depot.rs b/server/src/depot.rs index 33fd546..f730bf3 100644 --- a/server/src/depot.rs +++ b/server/src/depot.rs @@ -38,15 +38,15 @@ impl Depots { // refuel turtle.execute(Select(1)).await; - let limit = turtle.fuel_limit().await; - while turtle.fuel().await < limit { + let limit = turtle.fuel_limit(); + while turtle.fuel() < limit { turtle.execute(SuckFront(64)).await; let re = turtle.execute(Refuel).await; turtle.execute(DropDown(64)).await; if let TurtleCommandResponse::Failure = re.ret { // partial refuel, good enough - warn!("only received {} fuel", turtle.fuel().await); - if turtle.fuel().await > 5000 { + warn!("only received {} fuel", turtle.fuel()); + if turtle.fuel() > 5000 { break; } else { turtle.execute(Wait(15)).await; @@ -58,7 +58,7 @@ impl Depots { drop(depot); // assumes that the turtle will very quickly leave - Some(turtle.fuel().await) + Some(turtle.fuel()) } pub fn from_vec(vec: Vec) -> Self { diff --git a/server/src/fell.rs b/server/src/fell.rs index a2d6396..effe6ca 100644 --- a/server/src/fell.rs +++ b/server/src/fell.rs @@ -1,5 +1,76 @@ -use crate::{blocks::Vec3, turtle::TurtleCommander}; +use serde::{Serialize, Deserialize}; +use time::OffsetDateTime; +use tokio::task::JoinHandle; +use typetag::serde; -pub async fn fell(turtle: TurtleCommander, corner: Vec3) -> Option<()> { +use crate::{blocks::{Vec3, Position, Direction}, turtle::TurtleCommander, tasks::Task, depot::Depots, mine::fill}; + +pub async fn fell_tree(turtle: TurtleCommander, bottom: Vec3) -> Option<()> { + let mut log = bottom; + loop { + let near = turtle.goto_adjacent(log).await?; + if turtle.world().get(log).await.is_some_and(|b| !b.name.contains("log")) { + break; + } + turtle.execute(near.dig(log)?).await; + log += Vec3::y(); + } Some(()) } + +/// Minutes before checking +const SWEEP_DELAY: usize = 16; + +#[derive(Serialize, Deserialize, Clone)] +struct TreeFarm { + position: Vec3, + size: Vec3, + last_sweep: OffsetDateTime, +} + +impl TreeFarm { + pub async fn sweep(&self, turtle: TurtleCommander) -> Option<()> { + let trees = self.size.x * self.size.y * self.size.z; + turtle.dock().await; + for tree in 0..trees { + let index = fill(self.size, tree); + let offset = index.component_mul(&Vec3::new(2, 32, 2)); + let tree = self.position + offset; + fell_tree(turtle.clone(), tree).await?; + } + + Some(()) + } + + pub async fn build(&self, turtle: TurtleCommander) -> Option<()> { + let trees = self.size.x * self.size.y * self.size.z; + turtle.dock().await; + for tree in 0..trees { + let index = fill(self.size, tree); + let offset = index.component_mul(&Vec3::new(2, 32, 2)); + let tree = self.position + offset; + // TODO: item management + } + + Some(()) + } +} + +#[serde] +impl Task for TreeFarm { + fn run(&mut self,turtle:TurtleCommander) -> JoinHandle<()> { + let frozen = self.clone(); + tokio::spawn(async move { + frozen.sweep(turtle).await.unwrap(); + }) + } + + fn poll(&mut self) -> Option { + let elapsed = OffsetDateTime::now_utc() - self.last_sweep; + if elapsed.whole_minutes() <= 16 { + return None; + } + self.last_sweep = OffsetDateTime::now_utc(); + Some(Position::new(self.position, Direction::North)) // request a turtle + } +} diff --git a/server/src/main.rs b/server/src/main.rs index c3e5288..1a7c583 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -14,6 +14,7 @@ use log::info; use rstar::RTree; use names::Name; +use tasks::Scheduler; use tokio::{sync::{ RwLock, mpsc, OnceCell, Mutex }, fs}; @@ -34,50 +35,6 @@ mod turtle_api; mod tasks; mod depot; -#[derive(Serialize, Deserialize)] -struct SavedState { - turtles: Vec, - world: RTree, - depots: Vec, - //chunkloaders: unimplemented!(), -} - -struct LiveState { - turtles: Vec>>, - tasks: Vec>, - world: blocks::World, - depots: Depots, -} - -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) -> Self { - let mut turtles = Vec::new(); - for turtle in save.turtles.into_iter() { - let (tx, rx) = mpsc::channel(1); - turtles.push(Turtle::with_channel(turtle.name.to_num(), turtle.position, turtle.fuel, turtle.fuel_limit, tx, rx)); - }; - let depots = Depots::from_vec(save.depots); - - Self { turtles: turtles.into_iter().map(|t| Arc::new(RwLock::new(t))).collect(), tasks: Vec::new(), world: World::from_tree(save.world), - depots, - } - } - - async fn get_turtle(&self, name: u32) -> Option { - TurtleCommander::new(Name::from_num(name), self).await - } - -} - static PORT: OnceCell = OnceCell::const_new(); static SAVE: OnceCell = OnceCell::const_new(); @@ -99,8 +56,6 @@ async fn main() -> Result<(), Error> { let state = read_from_disk().await?; - let state = LiveState::from_save(state); - let state = SharedControl::new(RwLock::new(state)); let server = Router::new() @@ -115,7 +70,7 @@ async fn main() -> Result<(), Error> { let server = safe_kill::serve(server, listener).await; info!("writing"); - write_to_disk(state.read().await.save().await).await?; + write_to_disk(&*state.read().await).await?; server.closed().await; @@ -123,23 +78,29 @@ async fn main() -> Result<(), Error> { } async fn flush(State(state): State) -> &'static str { - write_to_disk(state.read().await.save().await).await.unwrap(); + write_to_disk(&*state.read().await).await.unwrap(); "ACK" } -async fn write_to_disk(state: SavedState) -> anyhow::Result<()> { +async fn write_to_disk(state: &LiveState) -> anyhow::Result<()> { + let tasks = &state.tasks; + let state = state.save().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 tasks = serde_json::to_string_pretty(tasks)?; + let path = &SAVE.get().unwrap(); tokio::fs::write(path.join("turtles.json"), turtles).await?; tokio::fs::write(path.join("depots.json"), depots).await?; + tokio::fs::write(path.join("tasks.json"), tasks).await?; tokio::fs::write(path.join("world.bin"), world).await?; Ok(()) } -async fn read_from_disk() -> anyhow::Result { +async fn read_from_disk() -> anyhow::Result { let turtles = match tokio::fs::OpenOptions::new() .read(true) .open(SAVE.get().unwrap().join("turtles.json")) @@ -164,6 +125,18 @@ async fn read_from_disk() -> anyhow::Result { }, }; + let scheduler = match tokio::fs::OpenOptions::new() + .read(true) + .open(SAVE.get().unwrap().join("scheduler.json")) + .await + { + tokio::io::Result::Ok(file) => serde_json::from_reader(file.into_std().await)?, + tokio::io::Result::Err(e) => match e.kind() { + ErrorKind::NotFound => Default::default(), + _ => panic!(), + }, + }; + let world = match tokio::fs::OpenOptions::new() .read(true).open(SAVE.get().unwrap().join("world.bin")).await { tokio::io::Result::Ok(file) => bincode::deserialize_from(file.into_std().await)?, @@ -174,9 +147,61 @@ async fn read_from_disk() -> anyhow::Result { }; - Ok(SavedState { + let saved = SavedState { turtles, world, depots, - }) + }; + + let mut live = LiveState::from_save(saved, scheduler); + + for turtle in live.turtles.iter() { + live.tasks.add_turtle(&TurtleCommander::new(turtle.read().await.name,&live).await.unwrap()) + } + + Ok(live) +} + +#[derive(Serialize, Deserialize)] +struct SavedState { + turtles: Vec, + world: RTree, + depots: Vec, + //chunkloaders: unimplemented!(), +} + +struct LiveState { + turtles: Vec>>, + tasks: Scheduler, + world: blocks::World, + depots: Depots, +} + +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) -> Self { + let mut turtles = Vec::new(); + for turtle in save.turtles.into_iter() { + let (tx, rx) = mpsc::channel(1); + turtles.push(Turtle::with_channel(turtle.name.to_num(), turtle.position, turtle.fuel, turtle.fuel_limit, tx, rx)); + }; + 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), + depots, + } + } + + async fn get_turtle(&self, name: u32) -> Option { + TurtleCommander::new(Name::from_num(name), self).await + } + } diff --git a/server/src/mine.rs b/server/src/mine.rs index 087d1f0..45bd40f 100644 --- a/server/src/mine.rs +++ b/server/src/mine.rs @@ -25,8 +25,8 @@ pub async fn mine(turtle: TurtleCommander, pos: Vec3, fuel: Position, storage: P let mut valuables = Vec::new(); async fn refuel_needed(turtle: &TurtleCommander, volume: i32, fuel: Position) -> Option<()> { - Some(if (turtle.fuel().await as f64) < (2 * volume + (fuel.pos-turtle.pos().await.pos).abs().sum()) as f64 * 1.8 { - let name = turtle.name().await.to_str(); + Some(if (turtle.fuel() as f64) < (2 * volume + (fuel.pos-turtle.pos().await.pos).abs().sum()) as f64 * 1.8 { + let name = turtle.name().to_str(); info!("{name}: refueling"); turtle.goto(fuel).await?; info!("{name}: docked"); @@ -94,14 +94,14 @@ pub async fn mine_chunk(turtle: TurtleCommander, pos: Vec3, chunk: Vec3) -> Opti async fn refuel(turtle: TurtleCommander) { turtle.execute(Select(16)).await; turtle.execute(DropUp(64)).await; - let limit = turtle.fuel_limit().await; - while turtle.fuel().await < limit { + let limit = turtle.fuel_limit(); + while turtle.fuel() < limit { turtle.execute(SuckFront(64)).await; let re = turtle.execute(Refuel).await; if let TurtleCommandResponse::Failure = re.ret { // partial refuel, good enough - warn!("only received {} fuel", turtle.fuel().await); - if turtle.fuel().await > 5000 { + warn!("only received {} fuel", turtle.fuel()); + if turtle.fuel() > 5000 { break; } else { turtle.execute(Wait(15)).await; @@ -148,7 +148,7 @@ fn step(n: i32, x: i32) -> i32 { } /// generates a sequence of adjacent positions within a volume -fn fill(scale: Vec3, n: i32) -> Vec3 { +pub fn fill(scale: Vec3, n: i32) -> Vec3 { assert!(n < scale.x * scale.y * scale.z); Vec3::new( step(n,scale.x), diff --git a/server/src/tasks.rs b/server/src/tasks.rs index 8cdd2c9..109fa42 100644 --- a/server/src/tasks.rs +++ b/server/src/tasks.rs @@ -6,18 +6,19 @@ use tokio::sync::{Mutex, MutexGuard, RwLock, OwnedMutexGuard}; use tokio::task::JoinHandle; use crate::LiveState; +use crate::names::Name; use crate::{turtle::{self, TurtleCommander}, blocks::Position}; #[typetag::serde(tag = "type")] -trait Task { +pub trait Task: Send + Sync { /// Execute the task - fn run(&self, turtle: TurtleCommander) -> JoinHandle<()>; + fn run(&mut self, turtle: TurtleCommander) -> JoinHandle<()>; /// Return Some if the task should be scheduled - fn poll(&self) -> Option; + fn poll(&mut self) -> Option; } #[derive(Serialize, Deserialize)] -struct Scheduler { +pub struct Scheduler { #[serde(skip)] turtles: Vec<(TurtleCommander, Option>)>, tasks: Vec>, @@ -35,18 +36,18 @@ impl Default for Scheduler { impl Scheduler { /// Add a new turtle to the scheduler /// Whether or not the turtle is already in the scheduler is not verified - fn add_turtle(&mut self, turtle: &TurtleCommander) { + pub fn add_turtle(&mut self, turtle: &TurtleCommander) { self.turtles.push(( turtle.clone(), None )); } - fn add_task(&mut self, task: Box) { + pub fn add_task(&mut self, task: Box) { self.tasks.push(task); } - async fn poll(&mut self) { + pub async fn poll(&mut self) { for turtle in &mut self.turtles { if let Some(join) = &turtle.1 { if join.is_finished() { @@ -77,4 +78,9 @@ impl Scheduler { } } + + pub async fn cancel(&mut self, turtle: Name) -> Option<()> { + self.turtles.iter_mut().find(|t| t.0.name() == turtle)?.1.take()?.abort(); + Some(()) + } } diff --git a/server/src/turtle.rs b/server/src/turtle.rs index 1b57792..23fccea 100644 --- a/server/src/turtle.rs +++ b/server/src/turtle.rs @@ -167,6 +167,18 @@ impl TurtleCommander { }) } + pub fn with_turtle(turtle: &Turtle, state: &LiveState) -> TurtleCommander { + TurtleCommander { + sender: turtle.sender.as_ref().unwrap().clone(), + world: state.world.clone(), + pos: Arc::new(RwLock::new(turtle.position)), + fuel: Arc::new(AtomicUsize::new(turtle.fuel)), + max_fuel: Arc::new(AtomicUsize::new(turtle.fuel_limit)), + name: Arc::new(OnceCell::new_with(Some(turtle.name))), + depots: state.depots.clone(), + } + } + pub async fn execute(&self, command: TurtleCommand) -> TurtleInfo { let (send, recv) = oneshot::channel::(); @@ -179,7 +191,7 @@ impl TurtleCommander { resp } - pub async fn name(&self) -> Name { + pub fn name(&self) -> Name { self.name.get().unwrap().clone() } @@ -187,11 +199,11 @@ impl TurtleCommander { self.pos.read().await.clone() } - pub async fn fuel(&self) -> usize { + pub fn fuel(&self) -> usize { self.fuel.load(std::sync::atomic::Ordering::SeqCst) } - pub async fn fuel_limit(&self) -> usize { + pub fn fuel_limit(&self) -> usize { self.max_fuel.load(std::sync::atomic::Ordering::SeqCst) } @@ -199,6 +211,10 @@ impl TurtleCommander { self.world.clone() } + pub async fn dock(&self) -> Option { + Depots::dock(&self.depots, self.to_owned()).await + } + pub async fn goto(&self, pos: Position) -> Option<()> { let mut recent = self.pos().await; let world = self.world.clone(); diff --git a/server/src/turtle_api.rs b/server/src/turtle_api.rs index 839e4d3..fd385ee 100644 --- a/server/src/turtle_api.rs +++ b/server/src/turtle_api.rs @@ -1,6 +1,7 @@ use tokio; use blocks::Vec3; use crate::turtle::TurtleCommandResponse; +use crate::turtle::TurtleCommander; use crate::turtle::TurtleInfo; use axum::extract::Path; use crate::turtle::TurtleCommand; @@ -45,12 +46,13 @@ pub(crate) async fn create_turtle( let state = &mut state.write().await; let id = state.turtles.len() as u32; let (send, receive) = mpsc::channel(1); + let turtle = turtle::Turtle::with_channel(id, Position::new(req.position, req.facing), req.fuel, req.fuellimit, send,receive); + let commander = TurtleCommander::with_turtle(&turtle, state); + state.tasks.add_turtle(&commander); state.turtles.push( Arc::new(RwLock::new( - turtle::Turtle::with_channel(id, Position::new(req.position, req.facing), req.fuel, req.fuellimit, send,receive) + turtle ))); - state.tasks.push(VecDeque::new()); - info!("new turtle: {id}"); @@ -115,7 +117,7 @@ pub(crate) async fn cancel( Path(id): Path, State(state): State, ) -> &'static str { - state.write().await.tasks[id as usize].pop_front(); + //state.write().await.tasks "ACK" }