printed a tree!
This commit is contained in:
parent
d309a4264c
commit
44837cfc1d
4 changed files with 226 additions and 4 deletions
|
@ -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
185
server/src/construct.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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")?;
|
||||||
|
|
|
@ -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 = {}
|
||||||
|
|
Loading…
Reference in a new issue