From 44837cfc1d7fa4f5f88f3a424a7b0bbad7151352 Mon Sep 17 00:00:00 2001 From: Andy Killorin <37423245+Speedy6451@users.noreply.github.com> Date: Fri, 29 Dec 2023 22:30:01 -0600 Subject: [PATCH] printed a tree! --- server/Cargo.toml | 2 +- server/src/construct.rs | 185 ++++++++++++++++++++++++++++++++++++++ server/src/googleforms.rs | 11 ++- server/src/turtle_api.rs | 32 +++++++ 4 files changed, 226 insertions(+), 4 deletions(-) create mode 100644 server/src/construct.rs diff --git a/server/Cargo.toml b/server/Cargo.toml index 52be68a..2c2ef26 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -18,7 +18,7 @@ indoc = "2.0.4" nalgebra = { version = "0.32.3", features = ["serde-serialize"] } pathfinding = "4.6.0" rstar = { version = "0.11.0", features = ["serde"] } -rustmatica = "0.1.1" +rustmatica = { git = "https://github.com/RubixDev/rustmatica" } serde = { version = "1.0.193", features = ["rc", "derive"] } serde_json = "1.0.108" time = { version = "0.3.31", features = ["serde"] } diff --git a/server/src/construct.rs b/server/src/construct.rs new file mode 100644 index 0000000..93c4b13 --- /dev/null +++ b/server/src/construct.rs @@ -0,0 +1,185 @@ +use std::{sync::{atomic::{AtomicBool, AtomicUsize, Ordering, AtomicI32}, Arc}, borrow::Cow}; + +use rustmatica::{Region, Litematic, BlockState, util::UVec3}; +use serde::{Serialize, Deserialize}; +use tokio::task::AbortHandle; +use tracing::{error, info, trace}; +use typetag::serde; + +use crate::{blocks::{Vec3, Position, World, Block, SharedWorld, Direction}, mine::{ChunkedTask, fill}, turtle::{TurtleCommander, TurtleCommandResponse, TurtleCommand}, tasks::{Task, TaskState}}; + +fn region2world<'a>(region: &'a Region) -> World { + let mut world = World::new(); + let min = Vec3::new( + region.min_x() as i32, + region.min_y() as i32, + region.min_z() as i32, + ); + + let max = Vec3::new( + region.max_x() as i32 + 1, + region.max_y() as i32 + 1, + region.max_z() as i32 + 1, + ); + + let area = max - min; + info!("area {}", area); + + // region.blocks() is broken (or how I was using it), which cost me quite some time TODO: make a pr + + for position in (0..area.product()).map(|n| fill(area, n)) { + let block = UVec3::new(position.x as usize, position.y as usize, position.z as usize); + let block = region.get_block(block); + + println!("{:#?}, {}", block, position); + + let name = match block { + BlockState::Air => None, + BlockState::Stone => Some("minecraft:stone"), + // who cares + _ => Some("terrestria:hemlock_planks") + }.map(|s| s.to_string()); + + if let Some(name) = name { + println!("{:#?}, {:?}", name, block); + let block = Block { + name, + pos: position - min, + }; + world.set(block); + } + } + + world +} + +#[derive(Serialize, Deserialize,Clone)] +pub struct BuildSimple { + pos: Vec3, + size: Vec3, + #[serde(skip)] + region: Option, + /// Input chest with the block to use, assumed infinite + input: Position, + #[serde(skip_deserializing)] + miners: Arc, + progress: Arc, + height: i32, +} + +impl BuildSimple { + pub fn new<'a>(position: Vec3, schematic: &'a Region, input: Position) -> Self { + let size = Vec3::new( + (1 + schematic.max_x() - schematic.min_x()) as i32, + (1 + schematic.max_y() - schematic.min_y()) as i32, + (1 + schematic.max_z() - schematic.min_z()) as i32, + ); + Self { + pos: position, + size, + region: Some(SharedWorld::from_world(region2world(schematic))), + input, + miners: Default::default(), + progress: Default::default(), + height: size.y, + } + } + + async fn place_block(&self, turtle: TurtleCommander, at: Vec3) -> Option<()> { + let mut near = turtle.goto_adjacent(at).await?; + while let TurtleCommandResponse::Failure = turtle.execute(near.place(at)?).await.ret { + trace!("failed, looking for blocks"); + if let Some(slot) = turtle.inventory().await.iter().enumerate() + .filter(|n| n.1.clone().is_some_and(|s| s.count > 0)) + .map(|n| n.0).next() { + turtle.execute(TurtleCommand::Select(slot as u32 + 1)).await; + } else { + trace!("docking"); + turtle.goto(self.input).await; + for _ in 1..=16 { + turtle.execute(TurtleCommand::SuckFront(64)).await; + } + near = turtle.goto_adjacent(at).await?; + } + } + + Some(()) + } + + async fn build_layer(&self, turtle: TurtleCommander, layer: i32) -> Option<()> { + let layer_size = Vec3::new(self.size.x, 1, self.size.z); + + for point in (0..layer_size.product()) + .map(|n| fill(layer_size, n)) { + let point = point + Vec3::y() * layer; + trace!("block {point}"); + + if self.region.as_ref()?.get(point).await.is_none() { + trace!("empty: {:?}", self.region.as_ref()?.get(point).await); + continue; + } + + let point = point + self.pos; + + if turtle.world().occupied(point).await { + trace!("already full: {:?}", turtle.world().get(point).await); + continue; + } + + self.place_block(turtle.clone(), point).await; + } + Some(()) + } +} + +#[serde] +impl Task for BuildSimple { + fn run(&mut self,turtle:TurtleCommander) -> AbortHandle { + let owned = self.clone(); + + tokio::spawn(async move { + if turtle.fuel() < 5000 { + turtle.dock().await; + } + let layer = owned.progress.fetch_add(1, Ordering::AcqRel); + if owned.height < layer { + error!("scheduled layer out of range"); + return; + } + info!("layer {}", layer); + if let None = owned.build_layer(turtle, layer).await { + error!("building layer {} failed", layer); + owned.progress.fetch_sub(1, Ordering::AcqRel); + } else { + trace!("building layer {} successful", layer); + } + owned.miners.fetch_sub(1, Ordering::AcqRel); + }).abort_handle() + } + + fn poll(&mut self) -> TaskState { + if self.region.is_none() { + error!("attempted to restart schematic printing, which is unimplemented"); + return TaskState::Complete; + } + + let layer = self.progress.load(Ordering::SeqCst); + + if layer == self.height { + return TaskState::Complete; + } + + let only = self.miners.fetch_update(Ordering::AcqRel, Ordering::Acquire, |n| { + if n < 1 { + Some(n+1) + }else { + None + } + }).is_ok(); + + if only { + return TaskState::Ready(Position::new(self.pos, Direction::North)); + } + TaskState::Waiting + } +} diff --git a/server/src/googleforms.rs b/server/src/googleforms.rs index 299def3..6532ece 100644 --- a/server/src/googleforms.rs +++ b/server/src/googleforms.rs @@ -1,6 +1,6 @@ use std::sync::{Arc, atomic::{AtomicBool, Ordering}}; -use anyhow::{Ok, Context, anyhow}; +use anyhow::{Ok, Context, anyhow, Result}; use axum::{Router, routing::post, extract::State, Json}; use serde::{Deserialize, Serialize}; use tokio::task::AbortHandle; @@ -110,7 +110,6 @@ async fn omni_inner(state: SharedControl, req: GoogleOmniForm) -> anyhow::Result let schematic = rustmatica::Litematic::from_bytes(&schematic.bytes().await?)?; - let region = schematic.regions.get(0).context("no regions")?; info!("schematic \"{}\" downloaded", &schematic.name); info!("{} blocks", schematic.total_blocks()); @@ -121,7 +120,13 @@ async fn omni_inner(state: SharedControl, req: GoogleOmniForm) -> anyhow::Result Direction::West, ); - schedule.add_task(Box::new(BuildSimple::new(position, region, input))); + // this converts to my memory representation so it can take a while + let builder = tokio::task::spawn_blocking(move || { + let region = schematic.regions.get(0).context("no regions"); + Ok(BuildSimple::new(position, region?, input)) + }).await??; + + schedule.add_task(Box::new(builder)); }, GoogleOmniFormMode::RemoveVein => { let block = req.block.context("missing block name")?; diff --git a/server/src/turtle_api.rs b/server/src/turtle_api.rs index 17c2e2e..af58e5a 100644 --- a/server/src/turtle_api.rs +++ b/server/src/turtle_api.rs @@ -3,6 +3,8 @@ use tracing::trace; use tokio; use blocks::Vec3; use tokio::time::Instant; +use crate::blocks::Direction; +use crate::construct::BuildSimple; use crate::fell::TreeFarm; use crate::mine::Mine; use crate::mine::Quarry; @@ -51,6 +53,7 @@ pub fn turtle_api() -> Router { .route("/:id/register", get(register_turtle)) .route("/createTreeFarm", post(fell)) .route("/createMine", post(dig)) + .route("/build", post(build)) .route("/registerDepot", post(new_depot)) .route("/pollScheduler", get(poll)) .route("/shutdown", get(shutdown)) // probably tramples the rfc @@ -286,6 +289,35 @@ pub(crate) async fn command( Json(command) } +pub(crate) async fn build( + State(state): State, + Json(req): Json, +) -> &'static str { + let state = state.read().await; + let mut schedule = state.tasks.lock().await; + let schematic = rustmatica::Litematic::read_file("Tree.litematic").unwrap(); + + + info!("schematic \"{}\" downloaded", &schematic.name); + info!("{} blocks", schematic.total_blocks()); + info!("{} regions", schematic.regions.len()); + + let input = Position::new( + Vec3::new(53,73,77), + Direction::West, + ); + + // this converts to my memory representation so it can take a while + let builder = tokio::task::spawn_blocking(move || { + let region = schematic.regions.get(0); + BuildSimple::new(req, region.unwrap(), input) + }).await.unwrap(); + + schedule.add_task(Box::new(builder)); + + "ACK" +} + pub(crate) async fn client() -> String { formatdoc!(r#" local ipaddr = {}