1
Fork 0

printed a tree!

This commit is contained in:
Andy Killorin 2023-12-29 22:30:01 -06:00
parent d309a4264c
commit 44837cfc1d
Signed by: ank
GPG key ID: B6241CA3B552BCA4
4 changed files with 226 additions and 4 deletions

View file

@ -18,7 +18,7 @@ indoc = "2.0.4"
nalgebra = { version = "0.32.3", features = ["serde-serialize"] } nalgebra = { version = "0.32.3", features = ["serde-serialize"] }
pathfinding = "4.6.0" pathfinding = "4.6.0"
rstar = { version = "0.11.0", features = ["serde"] } 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 = { version = "1.0.193", features = ["rc", "derive"] }
serde_json = "1.0.108" serde_json = "1.0.108"
time = { version = "0.3.31", features = ["serde"] } time = { version = "0.3.31", features = ["serde"] }

185
server/src/construct.rs Normal file
View file

@ -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<SharedWorld>,
/// Input chest with the block to use, assumed infinite
input: Position,
#[serde(skip_deserializing)]
miners: Arc<AtomicUsize>,
progress: Arc<AtomicI32>,
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
}
}

View file

@ -1,6 +1,6 @@
use std::sync::{Arc, atomic::{AtomicBool, Ordering}}; 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 axum::{Router, routing::post, extract::State, Json};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tokio::task::AbortHandle; 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 schematic = rustmatica::Litematic::from_bytes(&schematic.bytes().await?)?;
let region = schematic.regions.get(0).context("no regions")?;
info!("schematic \"{}\" downloaded", &schematic.name); info!("schematic \"{}\" downloaded", &schematic.name);
info!("{} blocks", schematic.total_blocks()); info!("{} blocks", schematic.total_blocks());
@ -121,7 +120,13 @@ async fn omni_inner(state: SharedControl, req: GoogleOmniForm) -> anyhow::Result
Direction::West, 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 => { GoogleOmniFormMode::RemoveVein => {
let block = req.block.context("missing block name")?; let block = req.block.context("missing block name")?;

View file

@ -3,6 +3,8 @@ use tracing::trace;
use tokio; use tokio;
use blocks::Vec3; use blocks::Vec3;
use tokio::time::Instant; use tokio::time::Instant;
use crate::blocks::Direction;
use crate::construct::BuildSimple;
use crate::fell::TreeFarm; use crate::fell::TreeFarm;
use crate::mine::Mine; use crate::mine::Mine;
use crate::mine::Quarry; use crate::mine::Quarry;
@ -51,6 +53,7 @@ pub fn turtle_api() -> Router<SharedControl> {
.route("/:id/register", get(register_turtle)) .route("/:id/register", get(register_turtle))
.route("/createTreeFarm", post(fell)) .route("/createTreeFarm", post(fell))
.route("/createMine", post(dig)) .route("/createMine", post(dig))
.route("/build", post(build))
.route("/registerDepot", post(new_depot)) .route("/registerDepot", post(new_depot))
.route("/pollScheduler", get(poll)) .route("/pollScheduler", get(poll))
.route("/shutdown", get(shutdown)) // probably tramples the rfc .route("/shutdown", get(shutdown)) // probably tramples the rfc
@ -286,6 +289,35 @@ pub(crate) async fn command(
Json(command) Json(command)
} }
pub(crate) async fn build(
State(state): State<SharedControl>,
Json(req): Json<Vec3>,
) -> &'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 { pub(crate) async fn client() -> String {
formatdoc!(r#" formatdoc!(r#"
local ipaddr = {} local ipaddr = {}