diff --git a/server/Cargo.toml b/server/Cargo.toml index 757b697..a5e4a6d 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -25,7 +25,7 @@ parking_lot = { version = "0.11", features = ["serde"] } pathfinding = "4.6.0" rstar = { version = "0.11.0", features = ["serde"] } rustmatica = "0.1.1" -serde = { version = "1.0.193", features = ["rc"] } +serde = { version = "1.0.193", features = ["rc", "derive"] } serde_json = "1.0.108" tokio = { version = "1.0", features = ["full"] } tower = { version = "0.4", features = ["util", "timeout", "load-shed", "limit"] } diff --git a/server/src/depot.rs b/server/src/depot.rs new file mode 100644 index 0000000..33fd546 --- /dev/null +++ b/server/src/depot.rs @@ -0,0 +1,79 @@ +use std::sync::Arc; + +use log::warn; +use tokio::sync::{Mutex, OwnedMutexGuard}; + +use crate::{blocks::Position, turtle::TurtleCommander}; +use crate::turtle::{TurtleCommand::*, TurtleCommandResponse}; + + +/// List of available depots +/// +/// below the specified position is an output chest of infinite capacity +/// ahead of the specified position is a chest of combustibles +#[derive(Clone)] +pub struct Depots { + depots: Arc>>>> +} + +impl Depots { + /// Nearest depot to the given position + pub async fn nearest(&self, pos: Position) -> Option> { + self.depots.lock().await + .iter().map(|i| i.clone()) + .filter_map(|i| i.try_lock_owned().ok()) + .min_by_key(|d| d.manhattan(pos)) + .map(|d| d) + } + + pub async fn dock(&self, turtle: TurtleCommander) -> Option { + let depot = self.nearest(turtle.pos().await).await?; + turtle.goto(*depot).await?; + + // dump inventory + for i in 1..=16 { + turtle.execute(Select(i)).await; + turtle.execute(DropFront(64)).await; + } + + // refuel + turtle.execute(Select(1)).await; + let limit = turtle.fuel_limit().await; + while turtle.fuel().await < limit { + turtle.execute(SuckFront(64)).await; + let re = turtle.execute(Refuel).await; + turtle.execute(DropDown(64)).await; + if let TurtleCommandResponse::Failure = re.ret { + // partial refuel, good enough + warn!("only received {} fuel", turtle.fuel().await); + if turtle.fuel().await > 5000 { + break; + } else { + turtle.execute(Wait(15)).await; + } + } + } + + turtle.execute(Backward(1)).await; + + drop(depot); // assumes that the turtle will very quickly leave + + Some(turtle.fuel().await) + } + + pub fn from_vec(vec: Vec) -> Self { + let mut depots = Vec::new(); + for depot in vec { + depots.push(Arc::new(Mutex::new(depot))); + } + Depots { depots: Arc::new(Mutex::new(depots)) } + } + + pub async fn to_vec(self) -> Vec { + let mut depots = Vec::new(); + for depot in self.depots.lock().await.iter() { + depots.push(*depot.lock().await) + } + depots + } +} diff --git a/server/src/fell.rs b/server/src/fell.rs new file mode 100644 index 0000000..a2d6396 --- /dev/null +++ b/server/src/fell.rs @@ -0,0 +1,5 @@ +use crate::{blocks::Vec3, turtle::TurtleCommander}; + +pub async fn fell(turtle: TurtleCommander, corner: Vec3) -> Option<()> { + Some(()) +} diff --git a/server/src/main.rs b/server/src/main.rs index 7e8cb00..c3e5288 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -9,6 +9,7 @@ use axum::{ Router, }; use blocks::{World, Position, }; +use depot::Depots; use log::info; use rstar::RTree; @@ -31,6 +32,7 @@ mod safe_kill; mod turtle; mod turtle_api; mod tasks; +mod depot; #[derive(Serialize, Deserialize)] struct SavedState { @@ -44,7 +46,7 @@ struct LiveState { turtles: Vec>>, tasks: Vec>, world: blocks::World, - depots: Mutex>>>, + depots: Depots, } impl LiveState { @@ -53,10 +55,7 @@ impl LiveState { for turtle in self.turtles.iter() { turtles.push(turtle.read().await.info()); }; - let mut depots = Vec::new(); - for depot in self.depots.lock().await.iter() { - depots.push(*depot.lock().await); - }; + let depots = self.depots.clone().to_vec().await; SavedState { turtles, world: self.world.tree().await, depots } } @@ -66,12 +65,10 @@ impl LiveState { let (tx, rx) = mpsc::channel(1); turtles.push(Turtle::with_channel(turtle.name.to_num(), turtle.position, turtle.fuel, turtle.fuel_limit, tx, rx)); }; - let mut depots = Vec::new(); - for depot in save.depots { - depots.push(Arc::new(Mutex::new(depot))); - } + let depots = Depots::from_vec(save.depots); + Self { turtles: turtles.into_iter().map(|t| Arc::new(RwLock::new(t))).collect(), tasks: Vec::new(), world: World::from_tree(save.world), - depots: Mutex::new(depots) + depots, } } diff --git a/server/src/tasks.rs b/server/src/tasks.rs new file mode 100644 index 0000000..8cdd2c9 --- /dev/null +++ b/server/src/tasks.rs @@ -0,0 +1,80 @@ +use std::sync::Arc; + +use erased_serde::serialize_trait_object; +use serde::{Deserialize, Serialize}; +use tokio::sync::{Mutex, MutexGuard, RwLock, OwnedMutexGuard}; +use tokio::task::JoinHandle; + +use crate::LiveState; +use crate::{turtle::{self, TurtleCommander}, blocks::Position}; + +#[typetag::serde(tag = "type")] +trait Task { + /// Execute the task + fn run(&self, turtle: TurtleCommander) -> JoinHandle<()>; + /// Return Some if the task should be scheduled + fn poll(&self) -> Option; +} + +#[derive(Serialize, Deserialize)] +struct Scheduler { + #[serde(skip)] + turtles: Vec<(TurtleCommander, Option>)>, + tasks: Vec>, +} + +impl Default for Scheduler { + fn default() -> Self { + Self { + turtles: Vec::new(), + tasks: Vec::new(), + } + } +} + +impl Scheduler { + /// Add a new turtle to the scheduler + /// Whether or not the turtle is already in the scheduler is not verified + fn add_turtle(&mut self, turtle: &TurtleCommander) { + self.turtles.push(( + turtle.clone(), + None + )); + } + + fn add_task(&mut self, task: Box) { + self.tasks.push(task); + } + + async fn poll(&mut self) { + for turtle in &mut self.turtles { + if let Some(join) = &turtle.1 { + if join.is_finished() { + turtle.1 = None; + } + } + } + + let mut free_turtles: Vec<&mut (TurtleCommander, Option>)> = + self.turtles.iter_mut().filter(|t| t.1.is_none()).collect(); + + let mut turtle_positions = Vec::new(); + for turtle in &free_turtles { + turtle_positions.push(turtle.0.pos().await); + } + + for task in &mut self.tasks { + if let Some(position) = task.poll() { + let closest_turtle = match free_turtles.iter_mut().zip(turtle_positions.iter()).min_by_key( |(_,p)| { + p.manhattan(position) + }) { + Some(turtle) => turtle.0, + None => break, + }; + + closest_turtle.1 = Some(task.run(closest_turtle.0.clone())); + } + } + + } +} diff --git a/server/src/turtle.rs b/server/src/turtle.rs index 518f102..1b57792 100644 --- a/server/src/turtle.rs +++ b/server/src/turtle.rs @@ -3,6 +3,7 @@ use crate::blocks::Direction; use crate::blocks::Position; use crate::blocks::Vec3; use crate::blocks::World; +use crate::depot::Depots; use crate::paths::route_facing; use anyhow::Ok; @@ -12,6 +13,7 @@ use anyhow::Context; use log::trace; use log::warn; use log::info; +use tokio::sync::Mutex; use tokio::sync::OnceCell; use tokio::sync::RwLock; use tokio::sync::mpsc; @@ -141,6 +143,7 @@ impl Turtle { pub struct TurtleCommander { sender: Arc, world: World, + depots: Depots, // everything below is best-effort // TODO: make not bad pos: Arc>, @@ -160,6 +163,7 @@ impl TurtleCommander { fuel: Arc::new(AtomicUsize::new(turtle.fuel)), max_fuel: Arc::new(AtomicUsize::new(turtle.fuel_limit)), name: Arc::new(OnceCell::new_with(Some(turtle.name))), + depots: state.depots.clone(), }) }