use log::trace; use tokio; use blocks::Vec3; use tokio::time::Instant; use crate::fell::TreeFarm; use crate::mine::Mine; use crate::turtle::IDLE_TIME; use crate::turtle::TurtleCommandResponse; use crate::turtle::TurtleCommander; use crate::turtle::TurtleInfo; use axum::extract::Path; use crate::turtle::TurtleCommand; use crate::names::Name; use log::info; use std::collections::VecDeque; use blocks::Position; use crate::turtle::Turtle; use tokio::sync::RwLock; use std::sync::Arc; use tokio::sync::mpsc; use crate::turtle; use axum::Json; use axum::extract::State; use axum::routing::get; use axum::routing::post; use crate::blocks; use crate::mine; use super::SharedControl; use axum::Router; use indoc::formatdoc; use crate::PORT; use tokio::fs; /// Time (s) after boot to start allocating turtles to tasks /// too short of a time could make fast-booting turtles do far away tasks over closer ones const STARTUP_ALLOWANCE: f64 = 4.0; pub fn turtle_api() -> Router { Router::new() .route("/new", post(create_turtle)) .route("/:id/update", post(command)) .route("/client.lua", get(client)) .route("/:id/setGoal", post(set_goal)) .route("/:id/cancelTask", post(cancel)) .route("/:id/manual", post(run_command)) .route("/:id/dock", post(dock)) .route("/:id/info", get(turtle_info)) .route("/createTreeFarm", post(fell)) .route("/createMine", post(dig)) .route("/registerDepot", post(new_depot)) .route("/pollScheduler", get(poll)) .route("/updateAll", get(update_turtles)) } pub(crate) async fn create_turtle( State(state): State, Json(req): Json, ) -> Json { let state = &mut state.write().await; let id = state.turtles.len() as u32; let (send, receive) = mpsc::channel(1); let turtle = turtle::Turtle::with_channel(id, Position::new(req.position, req.facing), req.fuel, req.fuellimit, send,receive); let commander = TurtleCommander::with_turtle(&turtle, state); state.tasks.add_turtle(&commander); state.turtles.push( Arc::new(RwLock::new( turtle ))); info!("new turtle: {id}"); Json(turtle::TurtleResponse { name: Name::from_num(id).to_str(), id, command: turtle::TurtleCommand::Update, }) } pub(crate) async fn place_up( Path(id): Path, State(state): State, ) -> Json { let turtle = state.read().await.get_turtle(id).await.unwrap(); let response = turtle.execute(turtle::TurtleCommand::PlaceUp).await; Json(response) } pub(crate) async fn dock( Path(id): Path, State(state): State, ) -> Json { let state = state.read().await; let commander = state.get_turtle(id).await.unwrap().clone(); drop(state); Json(commander.dock().await) } pub(crate) async fn run_command( Path(id): Path, State(state): State, Json(req): Json, ) -> Json { let state = state.read().await; let commander = state.get_turtle(id).await.unwrap().clone(); drop(state); Json(commander.execute(req).await.ret) } pub(crate) async fn dig( State(state): State, Json(req): Json, ) -> &'static str { let schedule = &mut state.write().await.tasks; let chunk = Vec3::new(4,4,4); schedule.add_task(Box::new(Mine::new(req,chunk))); "ACK" } pub(crate) async fn new_depot( State(state): State, Json(req): Json, ) -> &'static str { let depots = &state.read().await.depots; depots.add(req).await; "ACK" } pub(crate) async fn poll( State(state): State, ) -> &'static str { let schedule = &mut state.write().await.tasks; schedule.poll().await; "ACK" } pub(crate) async fn fell( State(state): State, Json(req): Json, ) -> &'static str { let schedule = &mut state.write().await.tasks; schedule.add_task(Box::new(TreeFarm::new(req))); "ACK" } pub(crate) async fn set_goal( Path(id): Path, State(state): State, Json(req): Json, ) -> &'static str { let turtle = state.read().await.get_turtle(id).await.unwrap().clone(); drop(state); tokio::spawn(async move {turtle.goto(req).await.expect("route failed")}); "ACK" } pub(crate) async fn cancel( Path(id): Path, State(state): State, ) -> &'static str { state.write().await.tasks.cancel(Name::from_num(id)).await; "ACK" } pub(crate) async fn update_turtles(State(state): State) -> &'static str { for turtle in state.read().await.turtles.iter() { turtle.write().await.pending_update = true; } "ACK" } pub(crate) async fn turtle_info( Path(id): Path, State(state): State, ) -> Json { let state = &mut state.read().await; let turtle = &state.turtles[id as usize].read().await; let cloned = Turtle::new( turtle.name.to_num(), turtle.position, turtle.fuel, turtle.fuel_limit, ); Json(cloned) } pub(crate) async fn command( Path(id): Path, State(state): State, Json(req): Json, ) -> Json { trace!("reply from turtle {id}: {req:?}"); let state_guard = state.clone().read_owned().await; if id as usize > state_guard.turtles.len() { return Json(turtle::TurtleCommand::Update); } let command = turtle::process_turtle_update(id, &state_guard, req).await; let command = match command { Some(command) => command, None => { tokio::spawn(async move { let state = &state.clone(); if Instant::elapsed(&state.clone().read().await.started).as_secs_f64() > STARTUP_ALLOWANCE { let schedule = &mut state.write().await.tasks; trace!("idle, polling"); schedule.poll().await; } }); turtle::TurtleCommand::Wait(IDLE_TIME) }, }; Json(command) } pub(crate) async fn client() -> String { formatdoc!(r#" local ipaddr = {} local port = "{}" {}"#, include_str!("../ipaddr.txt"), PORT.get().unwrap(), fs::read_to_string("../client/client.lua").await.unwrap(), // TODO: cache handle if bottleneck ) }