use std::{env::args, sync::Arc, fs, io::ErrorKind, collections::VecDeque}; use axum::{ routing::{get, post}, Router, extract::{State, Path}, Json, http::{request, Request}, }; use anyhow::{Error, Ok, Context}; use blocks::World; use rstar::{self, AABB}; use rustmatica::{BlockState}; mod names; use names::Name; use serde_json::Value; use tokio::{sync::{Mutex, RwLock, watch}, signal}; use serde::{Serialize, Deserialize}; use const_format::formatcp; use hyper_util::rt::TokioIo; use tower::Service; use hyper::body::Incoming; use nalgebra::Vector3; use crate::{blocks::Block, turtle::route}; mod blocks; mod turtle; pub type Vec3 = Vector3; #[derive(Serialize, Deserialize, Clone, Hash, PartialEq, Eq, Copy, Debug)] enum Direction { North, South, East, West, } impl Direction { fn left(self) -> Self { match self { Direction::North => Direction::West, Direction::South => Direction::East, Direction::East => Direction::North, Direction::West => Direction::South, } } fn right(self) -> Self { match self { Direction::North => Direction::East, Direction::South => Direction::West, Direction::East => Direction::South, Direction::West => Direction::North, } } fn unit(self) -> Vec3 { match self { Direction::North => Vec3::new(0, 0, -1), Direction::South => Vec3::new(0, 0, 1), Direction::East => Vec3::new(1, 0, 0), Direction::West => Vec3::new(-1, 0, 0), } } } #[derive(Serialize, Deserialize, Clone)] struct Turtle { name: Name, fuel: usize, /// movement vector of last given command queued_movement: Vec3, position: Position, goal: Option, pending_update: bool, } 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 } } } #[derive(Serialize, Deserialize)] struct ControlState { turtles: Vec, world: blocks::World, //chunkloaders: unimplemented!(), } type SharedControl = Arc>; #[tokio::main] async fn main() -> Result<(), Error> { let state = match tokio::fs::OpenOptions::new().read(true).open("state.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 => { ControlState { turtles:Vec::new(), world: World::new() } }, _ => panic!() } }; let state = SharedControl::new(RwLock::new(state)); let serv = Router::new() .route("/turtle/new", post(create_turtle)) .route("/turtle/update/:id", post(command)) .route("/turtle/client.lua", get(client)) .route("/turtle/setGoal/:id", post(set_goal)) .route("/turtle/info/:id", get(turtle_info)) .route("/turtle/updateAll", get(update_turtles)) .route("/flush", get(flush)) .with_state(state.clone()); let listener = tokio::net::TcpListener::bind("0.0.0.0:48228").await.unwrap(); let (close_tx, close_rx) = watch::channel(()); loop { let (socket, remote_addr) = tokio::select! { result = listener.accept() => { result.unwrap() } _ = shutdown_signal() => { println!("cancelled connection"); break; } }; let tower = serv.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| { 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); }); }; write_to_disk(state).await?; Ok(()) } async fn write_to_disk(state: SharedControl) -> anyhow::Result<()> { let json = serde_json::to_string_pretty(&(*state.read().await))?; tokio::fs::write("state.json", json).await?; Ok(()) } async fn flush(State(state): State) -> &'static str { write_to_disk(state).await.unwrap(); "ACK" } async fn shutdown_signal() { let ctrl_c = async { signal::ctrl_c() .await.unwrap(); }; ctrl_c.await } async fn create_turtle( State(state): State, Json(req): Json, ) -> Json { let turtles = &mut state.write().await.turtles; let id = turtles.len() as u32; turtles.push(Turtle::new(id, req.position, req.facing, req.fuel)); println!("turt {id}"); Json(TurtleResponse {name: Name::from_num(id).to_str(), id, command: TurtleCommand::Update}) } async fn set_goal( Path(id): Path, State(state): State, Json(req): Json, ) -> &'static str { state.write().await.turtles[id as usize].goal = Some(req); "ACK" } async fn update_turtles( State(state): State, ) -> &'static str { state.write().await.turtles.iter_mut().for_each(|t| t.pending_update = true); "ACK" } async fn turtle_info( Path(id): Path, State(state): State, ) -> Json { let state = &mut state.read().await; Json(state.turtles[id as usize].clone()) } async fn command( Path(id): Path, State(state): State, Json(req): Json, ) -> Json { let mut state = &mut state.write().await; if id as usize > state.turtles.len() { return Json(TurtleCommand::Update); } Json( process_turtle_update(id, &mut state, req).unwrap_or(TurtleCommand::Update), ) } fn process_turtle_update( id: u32, state: &mut ControlState, update: TurtleUpdate, ) -> anyhow::Result { 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; } 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); turtle.queued_movement = turtle.position.1.clone().unit(); 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); println!("route: {:?}", route); let next_move = difference(route[0], route[1]).unwrap(); 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) } #[derive(Serialize, Deserialize)] enum TurtleTask { Mining(TurtleMineJob), Idle, } type Position = (Vec3, Direction); /// Get a turtle command to map two adjacent positions fn difference(from: Position, to: Position) -> Option { use TurtleCommand::*; if from.0 == to.0 { if to.1 == from.1.left() { Some(Left) } else if to.1 == from.1.right() { Some(Right) } else { None } } else if to.1 == from.1 { if to.0 == from.0 + from.1.unit() { Some(Forward) } else if to.0 == from.0 - from.1.unit() { Some(Backward) } else if to.0 == from.0 + Vec3::y() { Some(Up) } else if to.0 == from.0 - Vec3::y() { Some(Down) } else { None } } else { None } } #[derive(Serialize, Deserialize)] struct TurtleMineJobParams { region: AABB<[i32;3]>, to_mine: Vec, method: TurtleMineMethod, refuel: Position, storage: Position, } #[derive(Serialize, Deserialize)] struct TurtleMineJob { to_mine: VecDeque, mined: AABB<[i32;3]>, params: TurtleMineJobParams, } #[derive(Serialize, Deserialize)] enum TurtleMineMethod { Clear, Strip, } #[derive(Serialize, Deserialize)] enum TurtleCommand { Wait, Forward, Backward, Up, Down, Left, Right, Dig, DigUp, DigDown, TakeInventory, Update, Poweroff, } impl TurtleCommand { fn delta(&self, direction: Direction) -> Vec3 { let dir = direction.unit(); match self { TurtleCommand::Forward => dir, TurtleCommand::Backward => -dir, TurtleCommand::Up => Vec3::y(), TurtleCommand::Down => -Vec3::y(), _ => Vec3::zeros(), } } } #[derive(Serialize, Deserialize)] struct TurtleUpdate { fuel: usize, /// Block name ahead: String, above: String, below: String, } #[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 { formatcp!("local ipaddr = {}\n{}", include_str!("../ipaddr.txt"), include_str!("../../client/client.lua")) }