factored out turtle and safe_kill modules
This commit is contained in:
parent
96a8de6858
commit
93dc487750
4 changed files with 354 additions and 305 deletions
|
@ -2,7 +2,7 @@ use std::{env::args, sync::Arc, fs, io::ErrorKind, collections::VecDeque};
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
routing::{get, post},
|
routing::{get, post},
|
||||||
Router, extract::{State, Path}, Json, http::{request, Request},
|
Router, extract::{State, Path}, Json, http::{request},
|
||||||
};
|
};
|
||||||
use anyhow::{Error, Ok, Context};
|
use anyhow::{Error, Ok, Context};
|
||||||
use blocks::World;
|
use blocks::World;
|
||||||
|
@ -10,7 +10,7 @@ use rstar::{self, AABB};
|
||||||
|
|
||||||
mod names;
|
mod names;
|
||||||
use names::Name;
|
use names::Name;
|
||||||
use tokio::{sync::{Mutex, RwLock, watch::{self, Sender}}, signal};
|
use tokio::{sync::{Mutex, RwLock, watch::{self}}};
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use const_format::formatcp;
|
use const_format::formatcp;
|
||||||
use hyper_util::rt::TokioIo;
|
use hyper_util::rt::TokioIo;
|
||||||
|
@ -18,10 +18,10 @@ use tower::Service;
|
||||||
use hyper::body::Incoming;
|
use hyper::body::Incoming;
|
||||||
use nalgebra::Vector3;
|
use nalgebra::Vector3;
|
||||||
|
|
||||||
use crate::{blocks::Block, turtle::route};
|
use crate::{blocks::Block, paths::route};
|
||||||
mod blocks;
|
mod blocks;
|
||||||
|
|
||||||
mod turtle;
|
mod paths;
|
||||||
|
|
||||||
pub type Vec3 = Vector3<i32>;
|
pub type Vec3 = Vector3<i32>;
|
||||||
|
|
||||||
|
@ -61,28 +61,9 @@ impl Direction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
struct Turtle {
|
|
||||||
name: Name,
|
|
||||||
fuel: usize,
|
|
||||||
/// movement vector of last given command
|
|
||||||
queued_movement: Vec3,
|
|
||||||
position: Position,
|
|
||||||
goal: Option<Position>,
|
|
||||||
pending_update: bool,
|
|
||||||
moves: VecDeque<TurtleCommand>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Turtle {
|
|
||||||
fn new(id: u32, position: Vec3, facing: Direction, fuel: usize) -> Self {
|
|
||||||
Self { name: Name::from_num(id), fuel, queued_movement: Vec3::new(0, 0, 0), position: (position, facing), goal: None, pending_update: true, moves: VecDeque::new() }
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
struct ControlState {
|
struct ControlState {
|
||||||
turtles: Vec<Turtle>,
|
turtles: Vec<turtle::Turtle>,
|
||||||
world: blocks::World,
|
world: blocks::World,
|
||||||
//chunkloaders: unimplemented!(),
|
//chunkloaders: unimplemented!(),
|
||||||
}
|
}
|
||||||
|
@ -117,7 +98,7 @@ async fn main() -> Result<(), Error> {
|
||||||
.with_state(state.clone());
|
.with_state(state.clone());
|
||||||
|
|
||||||
|
|
||||||
let server = serve(server).await;
|
let server = safe_kill::serve(server).await;
|
||||||
|
|
||||||
println!("writing");
|
println!("writing");
|
||||||
write_to_disk(state).await?;
|
write_to_disk(state).await?;
|
||||||
|
@ -127,60 +108,7 @@ async fn main() -> Result<(), Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn serve(server: Router) -> Sender<()> {
|
mod safe_kill;
|
||||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:48228").await.unwrap();
|
|
||||||
|
|
||||||
let (close_tx, close_rx) = watch::channel(());
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let (socket, _) = tokio::select! {
|
|
||||||
result = listener.accept() => {
|
|
||||||
result.unwrap()
|
|
||||||
}
|
|
||||||
_ = shutdown_signal() => {
|
|
||||||
println!("cancelled connection");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let tower = server.clone();
|
|
||||||
let close_rx = close_rx.clone();
|
|
||||||
|
|
||||||
tokio::spawn(async move {
|
|
||||||
let socket = TokioIo::new(socket);
|
|
||||||
let hyper_service = hyper::service::service_fn(move |request: Request<Incoming>| {
|
|
||||||
tower.clone().call(request)
|
|
||||||
});
|
|
||||||
|
|
||||||
let conn = hyper::server::conn::http1::Builder::new()
|
|
||||||
.serve_connection(socket, hyper_service)
|
|
||||||
.with_upgrades(); // future
|
|
||||||
|
|
||||||
let mut conn = std::pin::pin!(conn);
|
|
||||||
|
|
||||||
loop {
|
|
||||||
tokio::select! {
|
|
||||||
result = conn.as_mut() => {
|
|
||||||
if result.is_err() {
|
|
||||||
println!("req failed");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
_ = shutdown_signal() => {
|
|
||||||
println!("starting shutdown");
|
|
||||||
conn.as_mut().graceful_shutdown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
drop(close_rx);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
drop(listener);
|
|
||||||
|
|
||||||
close_tx
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn write_to_disk(state: SharedControl) -> anyhow::Result<()> {
|
async fn write_to_disk(state: SharedControl) -> anyhow::Result<()> {
|
||||||
let json = serde_json::to_string_pretty(&(*state.read().await))?;
|
let json = serde_json::to_string_pretty(&(*state.read().await))?;
|
||||||
|
@ -194,26 +122,17 @@ async fn flush(State(state): State<SharedControl>) -> &'static str {
|
||||||
"ACK"
|
"ACK"
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn shutdown_signal() {
|
|
||||||
let ctrl_c = async {
|
|
||||||
signal::ctrl_c()
|
|
||||||
.await.unwrap();
|
|
||||||
};
|
|
||||||
|
|
||||||
ctrl_c.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn create_turtle(
|
async fn create_turtle(
|
||||||
State(state): State<SharedControl>,
|
State(state): State<SharedControl>,
|
||||||
Json(req): Json<TurtleRegister>,
|
Json(req): Json<turtle::TurtleRegister>,
|
||||||
) -> Json<TurtleResponse> {
|
) -> Json<turtle::TurtleResponse> {
|
||||||
let turtles = &mut state.write().await.turtles;
|
let turtles = &mut state.write().await.turtles;
|
||||||
let id = turtles.len() as u32;
|
let id = turtles.len() as u32;
|
||||||
turtles.push(Turtle::new(id, req.position, req.facing, req.fuel));
|
turtles.push(turtle::Turtle::new(id, req.position, req.facing, req.fuel));
|
||||||
|
|
||||||
println!("turt {id}");
|
println!("turt {id}");
|
||||||
|
|
||||||
Json(TurtleResponse {name: Name::from_num(id).to_str(), id, command: TurtleCommand::Update})
|
Json(turtle::TurtleResponse {name: Name::from_num(id).to_str(), id, command: turtle::TurtleCommand::Update})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn set_goal(
|
async fn set_goal(
|
||||||
|
@ -236,14 +155,14 @@ async fn update_turtles(
|
||||||
async fn turtle_info(
|
async fn turtle_info(
|
||||||
Path(id): Path<u32>,
|
Path(id): Path<u32>,
|
||||||
State(state): State<SharedControl>,
|
State(state): State<SharedControl>,
|
||||||
) -> Json<Turtle> {
|
) -> Json<turtle::Turtle> {
|
||||||
let state = &mut state.read().await;
|
let state = &mut state.read().await;
|
||||||
let turtle = &state.turtles[id as usize];
|
let turtle = &state.turtles[id as usize];
|
||||||
|
|
||||||
let mut pseudomoves: VecDeque<TurtleCommand> = VecDeque::new();
|
let mut pseudomoves: VecDeque<turtle::TurtleCommand> = VecDeque::new();
|
||||||
turtle.moves.front().map(|m| pseudomoves.push_front(m.clone()));
|
turtle.moves.front().map(|m| pseudomoves.push_front(m.clone()));
|
||||||
|
|
||||||
let cloned = Turtle {
|
let cloned = turtle::Turtle {
|
||||||
name: turtle.name.clone(),
|
name: turtle.name.clone(),
|
||||||
fuel: turtle.fuel,
|
fuel: turtle.fuel,
|
||||||
queued_movement: turtle.queued_movement.clone(),
|
queued_movement: turtle.queued_movement.clone(),
|
||||||
|
@ -259,104 +178,26 @@ async fn turtle_info(
|
||||||
async fn command(
|
async fn command(
|
||||||
Path(id): Path<u32>,
|
Path(id): Path<u32>,
|
||||||
State(state): State<SharedControl>,
|
State(state): State<SharedControl>,
|
||||||
Json(req): Json<TurtleUpdate>,
|
Json(req): Json<turtle::TurtleUpdate>,
|
||||||
) -> Json<TurtleCommand> {
|
) -> Json<turtle::TurtleCommand> {
|
||||||
let mut state = &mut state.write().await;
|
let mut state = &mut state.write().await;
|
||||||
|
|
||||||
println!("{:?}", &req);
|
println!("{:?}", &req);
|
||||||
|
|
||||||
if id as usize > state.turtles.len() {
|
if id as usize > state.turtles.len() {
|
||||||
return Json(TurtleCommand::Update);
|
return Json(turtle::TurtleCommand::Update);
|
||||||
}
|
}
|
||||||
|
|
||||||
Json(
|
Json(
|
||||||
process_turtle_update(id, &mut state, req).unwrap_or(TurtleCommand::Update),
|
turtle::process_turtle_update(id, &mut state, req).unwrap_or(turtle::TurtleCommand::Update),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_turtle_update(
|
|
||||||
id: u32,
|
|
||||||
state: &mut ControlState,
|
|
||||||
update: TurtleUpdate,
|
|
||||||
) -> anyhow::Result<TurtleCommand> {
|
|
||||||
let turtle = state.turtles.get_mut(id as usize).context("nonexisting turtle")?;
|
|
||||||
let world = &mut state.world;
|
|
||||||
|
|
||||||
if turtle.pending_update {
|
|
||||||
turtle.pending_update = false;
|
|
||||||
return Ok(TurtleCommand::Update);
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("above: {}, below: {}, ahead: {}", update.above, update.below, update.ahead);
|
|
||||||
if turtle.fuel != update.fuel {
|
|
||||||
turtle.fuel = update.fuel;
|
|
||||||
|
|
||||||
turtle.position.0 += turtle.queued_movement;
|
|
||||||
turtle.queued_movement = Vec3::zeros();
|
|
||||||
}
|
|
||||||
|
|
||||||
let above = Block {
|
|
||||||
name: update.above,
|
|
||||||
pos: turtle.position.0 + Vec3::y(),
|
|
||||||
};
|
|
||||||
world.remove_at_point(&above.pos.into());
|
|
||||||
world.insert(above);
|
|
||||||
|
|
||||||
let ahead = Block {
|
|
||||||
name: update.ahead,
|
|
||||||
pos: turtle.position.0 + turtle.position.1.clone().unit(),
|
|
||||||
};
|
|
||||||
world.remove_at_point(&ahead.pos.into());
|
|
||||||
world.insert(ahead);
|
|
||||||
|
|
||||||
let below = Block {
|
|
||||||
name: update.below,
|
|
||||||
pos: turtle.position.0 - Vec3::y(),
|
|
||||||
};
|
|
||||||
world.remove_at_point(&below.pos.into());
|
|
||||||
world.insert(below);
|
|
||||||
|
|
||||||
if turtle.goal.is_some_and(|g| g == turtle.position) {
|
|
||||||
turtle.goal = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(goal) = turtle.goal {
|
|
||||||
// TODO: memoize this whenever we aren't digging
|
|
||||||
let route = route(turtle.position, goal, world).unwrap();
|
|
||||||
println!("route: {:?}", route);
|
|
||||||
let mut next_move = difference(route[0], route[1]).unwrap();
|
|
||||||
if world.locate_at_point(&route[1].0.into()).is_some_and(|b| b.name != "minecraft:air") {
|
|
||||||
next_move = match next_move {
|
|
||||||
TurtleCommand::Up(_) => TurtleCommand::DigUp,
|
|
||||||
TurtleCommand::Down(_) => TurtleCommand::DigDown,
|
|
||||||
TurtleCommand::Forward(_) => TurtleCommand::Dig,
|
|
||||||
_ => next_move,
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
turtle.queued_movement = next_move.delta(turtle.position.1);
|
|
||||||
match next_move {
|
|
||||||
TurtleCommand::Left => turtle.position.1 = turtle.position.1.left(),
|
|
||||||
TurtleCommand::Right => turtle.position.1 = turtle.position.1.right(),
|
|
||||||
_ => {},
|
|
||||||
}
|
|
||||||
return Ok(next_move);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(TurtleCommand::Wait(3))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
enum TurtleTask {
|
|
||||||
Mining(TurtleMineJob),
|
|
||||||
Idle,
|
|
||||||
}
|
|
||||||
|
|
||||||
type Position = (Vec3, Direction);
|
type Position = (Vec3, Direction);
|
||||||
|
|
||||||
/// Get a turtle command to map two adjacent positions
|
/// Get a turtle command to map two adjacent positions
|
||||||
fn difference(from: Position, to: Position) -> Option<TurtleCommand> {
|
fn difference(from: Position, to: Position) -> Option<turtle::TurtleCommand> {
|
||||||
use TurtleCommand::*;
|
use turtle::TurtleCommand::*;
|
||||||
|
|
||||||
if from.0 == to.0 {
|
if from.0 == to.0 {
|
||||||
if to.1 == from.1.left() {
|
if to.1 == from.1.left() {
|
||||||
|
@ -406,85 +247,8 @@ enum TurtleMineMethod {
|
||||||
Clear,
|
Clear,
|
||||||
Strip,
|
Strip,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
|
||||||
enum TurtleCommand {
|
|
||||||
Wait(u32),
|
|
||||||
Forward(u32),
|
|
||||||
Backward(u32),
|
|
||||||
Up(u32),
|
|
||||||
Down(u32),
|
|
||||||
Left,
|
|
||||||
Right,
|
|
||||||
Dig,
|
|
||||||
DigUp,
|
|
||||||
DigDown,
|
|
||||||
PlaceUp,
|
|
||||||
Place,
|
|
||||||
PlaceDown,
|
|
||||||
/// Count
|
|
||||||
DropFront(u32),
|
|
||||||
DropUp(u32),
|
|
||||||
DropDown(u32),
|
|
||||||
Select(u32),
|
|
||||||
/// Slot in inventory
|
|
||||||
ItemInfo(u32),
|
|
||||||
Update,
|
|
||||||
Poweroff,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
|
||||||
struct InventorySlot {
|
|
||||||
name: String,
|
|
||||||
count: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
|
||||||
enum TurtleCommandResponse {
|
|
||||||
None,
|
|
||||||
Success,
|
|
||||||
Failure,
|
|
||||||
Item(InventorySlot),
|
|
||||||
Inventory(Vec<InventorySlot>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TurtleCommand {
|
|
||||||
fn delta(&self, direction: Direction) -> Vec3 {
|
|
||||||
let dir = direction.unit();
|
|
||||||
match self {
|
|
||||||
TurtleCommand::Forward(count) => dir * *count as i32,
|
|
||||||
TurtleCommand::Backward(count) => -dir * *count as i32,
|
|
||||||
TurtleCommand::Up(count) => Vec3::y() * *count as i32,
|
|
||||||
TurtleCommand::Down(count) => -Vec3::y() * *count as i32,
|
|
||||||
_ => Vec3::zeros(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
struct TurtleUpdate {
|
|
||||||
fuel: usize,
|
|
||||||
/// Block name
|
|
||||||
ahead: String,
|
|
||||||
above: String,
|
|
||||||
below: String,
|
|
||||||
ret: TurtleCommandResponse,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
struct TurtleRegister {
|
|
||||||
fuel: usize,
|
|
||||||
position: Vec3,
|
|
||||||
facing: Direction,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
struct TurtleResponse {
|
|
||||||
name: String,
|
|
||||||
id: u32,
|
|
||||||
command: TurtleCommand,
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn client() -> &'static str {
|
async fn client() -> &'static str {
|
||||||
formatcp!("local ipaddr = {}\n{}", include_str!("../ipaddr.txt"), include_str!("../../client/client.lua"))
|
formatcp!("local ipaddr = {}\n{}", include_str!("../ipaddr.txt"), include_str!("../../client/client.lua"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod turtle;
|
||||||
|
|
62
server/src/paths.rs
Normal file
62
server/src/paths.rs
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use pathfinding::prelude::astar;
|
||||||
|
use crate::{blocks::{World, Block}, Position, Direction};
|
||||||
|
|
||||||
|
use super::Vec3;
|
||||||
|
|
||||||
|
pub fn route(from: Position, to: Position, world: &World) -> Option<Vec<Position>> {
|
||||||
|
// attempt at not crashing by looking infinitely into the abyss
|
||||||
|
if world.locate_at_point(&to.0.into())
|
||||||
|
.is_some_and(|b| difficulty(&b.name).is_none()) {
|
||||||
|
return None;
|
||||||
|
|
||||||
|
}
|
||||||
|
let route = astar(&from, move |p| {next(p, world)} , |p1| {(p1.0 - &to.0).abs().sum() as u32}, |p| {p == &to}).unwrap();
|
||||||
|
Some(route.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next(from: &Position, world: &World) -> Vec<(Position, u32)> {
|
||||||
|
let mut vec: Vec<(Position, u32)> = Vec::new();
|
||||||
|
vec.push(((from.0, from.1.left()),1));
|
||||||
|
vec.push(((from.0, from.1.right()),1));
|
||||||
|
|
||||||
|
fn insert(vec: &mut Vec<(Position, u32)>, point: Vec3, orientation: Direction, world: &World, unknown: Option<u32>) {
|
||||||
|
world.locate_at_point(&point.into())
|
||||||
|
.map_or(unknown, |b| difficulty(&b.name))
|
||||||
|
.map(|d| vec.push(((point, orientation), d)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let ahead = from.0 + from.1.unit();
|
||||||
|
insert(&mut vec, ahead, from.1, world, UNKNOWN);
|
||||||
|
|
||||||
|
//let behind = from.0 - from.1.unit();
|
||||||
|
//insert(&mut vec, behind, from.1, world, None);
|
||||||
|
|
||||||
|
let above = from.0 + Vec3::y();
|
||||||
|
insert(&mut vec, above, from.1, world,UNKNOWN);
|
||||||
|
|
||||||
|
let below = from.0 - Vec3::y();
|
||||||
|
insert(&mut vec, below, from.1, world,UNKNOWN);
|
||||||
|
|
||||||
|
vec
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Blocks that are fine to tunnel through
|
||||||
|
const GARBAGE: [&str; 5] = [
|
||||||
|
"minecraft:stone",
|
||||||
|
"minecraft:dirt",
|
||||||
|
"minecraft:andesite",
|
||||||
|
"minecraft:sand",
|
||||||
|
"minecraft:gravel",
|
||||||
|
];
|
||||||
|
|
||||||
|
/// time taken to go through uncharted territory (in turtle. calls)
|
||||||
|
const UNKNOWN: Option<u32> = Some(2);
|
||||||
|
|
||||||
|
// time to go somewhere
|
||||||
|
fn difficulty(name: &str) -> Option<u32> {
|
||||||
|
if name == "minecraft:air" { return Some(1) };
|
||||||
|
if GARBAGE.contains(&name) { return Some(2)};
|
||||||
|
None
|
||||||
|
}
|
83
server/src/safe_kill.rs
Normal file
83
server/src/safe_kill.rs
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
use tokio::signal;
|
||||||
|
|
||||||
|
use std;
|
||||||
|
|
||||||
|
use hyper::body::Incoming;
|
||||||
|
|
||||||
|
use axum::http::Request;
|
||||||
|
|
||||||
|
use tower::Service;
|
||||||
|
|
||||||
|
use hyper_util::rt::TokioIo;
|
||||||
|
|
||||||
|
use tokio::sync::watch;
|
||||||
|
|
||||||
|
use tokio;
|
||||||
|
|
||||||
|
use tokio::sync::watch::Sender;
|
||||||
|
|
||||||
|
use axum::Router;
|
||||||
|
|
||||||
|
pub(crate) async fn serve(server: Router) -> Sender<()> {
|
||||||
|
let listener = tokio::net::TcpListener::bind("0.0.0.0:48228").await.unwrap();
|
||||||
|
|
||||||
|
let (close_tx, close_rx) = watch::channel(());
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let (socket, _) = tokio::select! {
|
||||||
|
result = listener.accept() => {
|
||||||
|
result.unwrap()
|
||||||
|
}
|
||||||
|
_ = shutdown_signal() => {
|
||||||
|
println!("cancelled connection");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let tower = server.clone();
|
||||||
|
let close_rx = close_rx.clone();
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let socket = TokioIo::new(socket);
|
||||||
|
let hyper_service = hyper::service::service_fn(move |request: Request<Incoming>| {
|
||||||
|
tower.clone().call(request)
|
||||||
|
});
|
||||||
|
|
||||||
|
let conn = hyper::server::conn::http1::Builder::new()
|
||||||
|
.serve_connection(socket, hyper_service)
|
||||||
|
.with_upgrades(); // future
|
||||||
|
|
||||||
|
let mut conn = std::pin::pin!(conn);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
result = conn.as_mut() => {
|
||||||
|
if result.is_err() {
|
||||||
|
println!("req failed");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_ = shutdown_signal() => {
|
||||||
|
println!("starting shutdown");
|
||||||
|
conn.as_mut().graceful_shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
drop(close_rx);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
drop(listener);
|
||||||
|
|
||||||
|
close_tx
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn shutdown_signal() {
|
||||||
|
let ctrl_c = async {
|
||||||
|
signal::ctrl_c()
|
||||||
|
.await.unwrap();
|
||||||
|
};
|
||||||
|
|
||||||
|
ctrl_c.await
|
||||||
|
}
|
|
@ -1,62 +1,202 @@
|
||||||
use std::rc::Rc;
|
use super::TurtleMineJob;
|
||||||
|
|
||||||
use pathfinding::prelude::astar;
|
use super::difference;
|
||||||
use crate::{blocks::{World, Block}, Position, Direction};
|
|
||||||
|
use crate::blocks::Block;
|
||||||
|
|
||||||
|
use anyhow::Ok;
|
||||||
|
|
||||||
|
use anyhow;
|
||||||
|
use anyhow::Context;
|
||||||
|
|
||||||
|
use super::ControlState;
|
||||||
|
|
||||||
|
use super::Direction;
|
||||||
|
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
use super::Position;
|
||||||
|
|
||||||
use super::Vec3;
|
use super::Vec3;
|
||||||
|
|
||||||
pub fn route(from: Position, to: Position, world: &World) -> Option<Vec<Position>> {
|
use super::names::Name;
|
||||||
// attempt at not crashing by looking infinitely into the abyss
|
|
||||||
if world.locate_at_point(&to.0.into())
|
use serde::Serialize;
|
||||||
.is_some_and(|b| difficulty(&b.name).is_none()) {
|
use serde::Deserialize;
|
||||||
return None;
|
|
||||||
|
use super::paths::route;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub(crate) struct Turtle {
|
||||||
|
pub(crate) name: Name,
|
||||||
|
pub(crate) fuel: usize,
|
||||||
|
/// movement vector of last given command
|
||||||
|
pub(crate) queued_movement: Vec3,
|
||||||
|
pub(crate) position: Position,
|
||||||
|
pub(crate) goal: Option<Position>,
|
||||||
|
pub(crate) pending_update: bool,
|
||||||
|
pub(crate) moves: VecDeque<TurtleCommand>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Turtle {
|
||||||
|
pub(crate) fn new(id: u32, position: Vec3, facing: Direction, fuel: usize) -> Self {
|
||||||
|
Self { name: Name::from_num(id), fuel, queued_movement: Vec3::new(0, 0, 0), position: (position, facing), goal: None, pending_update: true, moves: VecDeque::new() }
|
||||||
|
|
||||||
}
|
}
|
||||||
let route = astar(&from, move |p| {next(p, world)} , |p1| {(p1.0 - &to.0).abs().sum() as u32}, |p| {p == &to}).unwrap();
|
|
||||||
Some(route.0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next(from: &Position, world: &World) -> Vec<(Position, u32)> {
|
pub(crate) fn process_turtle_update(
|
||||||
let mut vec: Vec<(Position, u32)> = Vec::new();
|
id: u32,
|
||||||
vec.push(((from.0, from.1.left()),1));
|
state: &mut ControlState,
|
||||||
vec.push(((from.0, from.1.right()),1));
|
update: TurtleUpdate,
|
||||||
|
) -> anyhow::Result<TurtleCommand> {
|
||||||
|
let turtle = state.turtles.get_mut(id as usize).context("nonexisting turtle")?;
|
||||||
|
let world = &mut state.world;
|
||||||
|
|
||||||
fn insert(vec: &mut Vec<(Position, u32)>, point: Vec3, orientation: Direction, world: &World, unknown: Option<u32>) {
|
if turtle.pending_update {
|
||||||
world.locate_at_point(&point.into())
|
turtle.pending_update = false;
|
||||||
.map_or(unknown, |b| difficulty(&b.name))
|
return Ok(TurtleCommand::Update);
|
||||||
.map(|d| vec.push(((point, orientation), d)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let ahead = from.0 + from.1.unit();
|
println!("above: {}, below: {}, ahead: {}", update.above, update.below, update.ahead);
|
||||||
insert(&mut vec, ahead, from.1, world, UNKNOWN);
|
if turtle.fuel != update.fuel {
|
||||||
|
turtle.fuel = update.fuel;
|
||||||
|
|
||||||
//let behind = from.0 - from.1.unit();
|
turtle.position.0 += turtle.queued_movement;
|
||||||
//insert(&mut vec, behind, from.1, world, None);
|
turtle.queued_movement = Vec3::zeros();
|
||||||
|
}
|
||||||
|
|
||||||
let above = from.0 + Vec3::y();
|
let above = Block {
|
||||||
insert(&mut vec, above, from.1, world,UNKNOWN);
|
name: update.above,
|
||||||
|
pos: turtle.position.0 + Vec3::y(),
|
||||||
|
};
|
||||||
|
world.remove_at_point(&above.pos.into());
|
||||||
|
world.insert(above);
|
||||||
|
|
||||||
let below = from.0 - Vec3::y();
|
let ahead = Block {
|
||||||
insert(&mut vec, below, from.1, world,UNKNOWN);
|
name: update.ahead,
|
||||||
|
pos: turtle.position.0 + turtle.position.1.clone().unit(),
|
||||||
|
};
|
||||||
|
world.remove_at_point(&ahead.pos.into());
|
||||||
|
world.insert(ahead);
|
||||||
|
|
||||||
vec
|
let below = Block {
|
||||||
|
name: update.below,
|
||||||
|
pos: turtle.position.0 - Vec3::y(),
|
||||||
|
};
|
||||||
|
world.remove_at_point(&below.pos.into());
|
||||||
|
world.insert(below);
|
||||||
|
|
||||||
|
if turtle.goal.is_some_and(|g| g == turtle.position) {
|
||||||
|
turtle.goal = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(goal) = turtle.goal {
|
||||||
|
// TODO: memoize this whenever we aren't digging
|
||||||
|
let route = route(turtle.position, goal, world).unwrap();
|
||||||
|
println!("route: {:?}", route);
|
||||||
|
let mut next_move = difference(route[0], route[1]).unwrap();
|
||||||
|
if world.locate_at_point(&route[1].0.into()).is_some_and(|b| b.name != "minecraft:air") {
|
||||||
|
next_move = match next_move {
|
||||||
|
TurtleCommand::Up(_) => TurtleCommand::DigUp,
|
||||||
|
TurtleCommand::Down(_) => TurtleCommand::DigDown,
|
||||||
|
TurtleCommand::Forward(_) => TurtleCommand::Dig,
|
||||||
|
_ => next_move,
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
turtle.queued_movement = next_move.delta(turtle.position.1);
|
||||||
|
match next_move {
|
||||||
|
TurtleCommand::Left => turtle.position.1 = turtle.position.1.left(),
|
||||||
|
TurtleCommand::Right => turtle.position.1 = turtle.position.1.right(),
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
return Ok(next_move);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(TurtleCommand::Wait(3))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Blocks that are fine to tunnel through
|
#[derive(Serialize, Deserialize)]
|
||||||
const GARBAGE: [&str; 5] = [
|
pub(crate) enum TurtleTask {
|
||||||
"minecraft:stone",
|
Mining(TurtleMineJob),
|
||||||
"minecraft:dirt",
|
Idle,
|
||||||
"minecraft:andesite",
|
}
|
||||||
"minecraft:sand",
|
|
||||||
"minecraft:gravel",
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
];
|
pub(crate) enum TurtleCommand {
|
||||||
|
Wait(u32),
|
||||||
/// time taken to go through uncharted territory (in turtle. calls)
|
Forward(u32),
|
||||||
const UNKNOWN: Option<u32> = Some(2);
|
Backward(u32),
|
||||||
|
Up(u32),
|
||||||
// time to go somewhere
|
Down(u32),
|
||||||
fn difficulty(name: &str) -> Option<u32> {
|
Left,
|
||||||
if name == "minecraft:air" { return Some(1) };
|
Right,
|
||||||
if GARBAGE.contains(&name) { return Some(2)};
|
Dig,
|
||||||
None
|
DigUp,
|
||||||
|
DigDown,
|
||||||
|
PlaceUp,
|
||||||
|
Place,
|
||||||
|
PlaceDown,
|
||||||
|
/// Count
|
||||||
|
DropFront(u32),
|
||||||
|
DropUp(u32),
|
||||||
|
DropDown(u32),
|
||||||
|
Select(u32),
|
||||||
|
/// Slot in inventory
|
||||||
|
ItemInfo(u32),
|
||||||
|
Update,
|
||||||
|
Poweroff,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub(crate) struct InventorySlot {
|
||||||
|
pub(crate) name: String,
|
||||||
|
pub(crate) count: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub(crate) enum TurtleCommandResponse {
|
||||||
|
None,
|
||||||
|
Success,
|
||||||
|
Failure,
|
||||||
|
Item(InventorySlot),
|
||||||
|
Inventory(Vec<InventorySlot>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TurtleCommand {
|
||||||
|
pub(crate) fn delta(&self, direction: Direction) -> Vec3 {
|
||||||
|
let dir = direction.unit();
|
||||||
|
match self {
|
||||||
|
TurtleCommand::Forward(count) => dir * *count as i32,
|
||||||
|
TurtleCommand::Backward(count) => -dir * *count as i32,
|
||||||
|
TurtleCommand::Up(count) => Vec3::y() * *count as i32,
|
||||||
|
TurtleCommand::Down(count) => -Vec3::y() * *count as i32,
|
||||||
|
_ => Vec3::zeros(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub(crate) struct TurtleUpdate {
|
||||||
|
pub(crate) fuel: usize,
|
||||||
|
/// Block name
|
||||||
|
pub(crate) ahead: String,
|
||||||
|
pub(crate) above: String,
|
||||||
|
pub(crate) below: String,
|
||||||
|
pub(crate) ret: TurtleCommandResponse,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub(crate) struct TurtleRegister {
|
||||||
|
pub(crate) fuel: usize,
|
||||||
|
pub(crate) position: Vec3,
|
||||||
|
pub(crate) facing: Direction,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub(crate) struct TurtleResponse {
|
||||||
|
pub(crate) name: String,
|
||||||
|
pub(crate) id: u32,
|
||||||
|
pub(crate) command: TurtleCommand,
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue