felling
This commit is contained in:
parent
ee39602e03
commit
dbbc41705e
9 changed files with 202 additions and 81 deletions
|
@ -27,6 +27,7 @@ rstar = { version = "0.11.0", features = ["serde"] }
|
|||
rustmatica = "0.1.1"
|
||||
serde = { version = "1.0.193", features = ["rc", "derive"] }
|
||||
serde_json = "1.0.108"
|
||||
time = { version = "0.3.31", features = ["serde"] }
|
||||
tokio = { version = "1.0", features = ["full"] }
|
||||
tower = { version = "0.4", features = ["util", "timeout", "load-shed", "limit"] }
|
||||
tower-http = { version = "0.5.0", features = [
|
||||
|
|
|
@ -14,7 +14,7 @@ local: surnames.txt names.txt ipaddr.txt
|
|||
global: surnames.txt names.txt ipaddr.txt
|
||||
make -B ipaddr.txt
|
||||
mkdir -p prod
|
||||
cd prod; cargo run --release $(PRODPORT) $(PRODFILE)
|
||||
cargo run --release $(PRODPORT) $(PRODFILE)
|
||||
|
||||
surnames.txt:
|
||||
curl https://raw.githubusercontent.com/Hyneman/moby-project/672f6bdca054c42d375f065ffee87e8ceba0c242/moby/mwords/21986na.mes |\
|
||||
|
|
|
@ -38,15 +38,15 @@ impl Depots {
|
|||
|
||||
// refuel
|
||||
turtle.execute(Select(1)).await;
|
||||
let limit = turtle.fuel_limit().await;
|
||||
while turtle.fuel().await < limit {
|
||||
let limit = turtle.fuel_limit();
|
||||
while turtle.fuel() < 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 {
|
||||
warn!("only received {} fuel", turtle.fuel());
|
||||
if turtle.fuel() > 5000 {
|
||||
break;
|
||||
} else {
|
||||
turtle.execute(Wait(15)).await;
|
||||
|
@ -58,7 +58,7 @@ impl Depots {
|
|||
|
||||
drop(depot); // assumes that the turtle will very quickly leave
|
||||
|
||||
Some(turtle.fuel().await)
|
||||
Some(turtle.fuel())
|
||||
}
|
||||
|
||||
pub fn from_vec(vec: Vec<Position>) -> Self {
|
||||
|
|
|
@ -1,5 +1,76 @@
|
|||
use crate::{blocks::Vec3, turtle::TurtleCommander};
|
||||
use serde::{Serialize, Deserialize};
|
||||
use time::OffsetDateTime;
|
||||
use tokio::task::JoinHandle;
|
||||
use typetag::serde;
|
||||
|
||||
pub async fn fell(turtle: TurtleCommander, corner: Vec3) -> Option<()> {
|
||||
use crate::{blocks::{Vec3, Position, Direction}, turtle::TurtleCommander, tasks::Task, depot::Depots, mine::fill};
|
||||
|
||||
pub async fn fell_tree(turtle: TurtleCommander, bottom: Vec3) -> Option<()> {
|
||||
let mut log = bottom;
|
||||
loop {
|
||||
let near = turtle.goto_adjacent(log).await?;
|
||||
if turtle.world().get(log).await.is_some_and(|b| !b.name.contains("log")) {
|
||||
break;
|
||||
}
|
||||
turtle.execute(near.dig(log)?).await;
|
||||
log += Vec3::y();
|
||||
}
|
||||
Some(())
|
||||
}
|
||||
|
||||
/// Minutes before checking
|
||||
const SWEEP_DELAY: usize = 16;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
struct TreeFarm {
|
||||
position: Vec3,
|
||||
size: Vec3,
|
||||
last_sweep: OffsetDateTime,
|
||||
}
|
||||
|
||||
impl TreeFarm {
|
||||
pub async fn sweep(&self, turtle: TurtleCommander) -> Option<()> {
|
||||
let trees = self.size.x * self.size.y * self.size.z;
|
||||
turtle.dock().await;
|
||||
for tree in 0..trees {
|
||||
let index = fill(self.size, tree);
|
||||
let offset = index.component_mul(&Vec3::new(2, 32, 2));
|
||||
let tree = self.position + offset;
|
||||
fell_tree(turtle.clone(), tree).await?;
|
||||
}
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
pub async fn build(&self, turtle: TurtleCommander) -> Option<()> {
|
||||
let trees = self.size.x * self.size.y * self.size.z;
|
||||
turtle.dock().await;
|
||||
for tree in 0..trees {
|
||||
let index = fill(self.size, tree);
|
||||
let offset = index.component_mul(&Vec3::new(2, 32, 2));
|
||||
let tree = self.position + offset;
|
||||
// TODO: item management
|
||||
}
|
||||
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
|
||||
#[serde]
|
||||
impl Task for TreeFarm {
|
||||
fn run(&mut self,turtle:TurtleCommander) -> JoinHandle<()> {
|
||||
let frozen = self.clone();
|
||||
tokio::spawn(async move {
|
||||
frozen.sweep(turtle).await.unwrap();
|
||||
})
|
||||
}
|
||||
|
||||
fn poll(&mut self) -> Option<Position> {
|
||||
let elapsed = OffsetDateTime::now_utc() - self.last_sweep;
|
||||
if elapsed.whole_minutes() <= 16 {
|
||||
return None;
|
||||
}
|
||||
self.last_sweep = OffsetDateTime::now_utc();
|
||||
Some(Position::new(self.position, Direction::North)) // request a turtle
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ use log::info;
|
|||
use rstar::RTree;
|
||||
|
||||
use names::Name;
|
||||
use tasks::Scheduler;
|
||||
use tokio::{sync::{
|
||||
RwLock, mpsc, OnceCell, Mutex
|
||||
}, fs};
|
||||
|
@ -34,50 +35,6 @@ mod turtle_api;
|
|||
mod tasks;
|
||||
mod depot;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct SavedState {
|
||||
turtles: Vec<turtle::Turtle>,
|
||||
world: RTree<Block>,
|
||||
depots: Vec<Position>,
|
||||
//chunkloaders: unimplemented!(),
|
||||
}
|
||||
|
||||
struct LiveState {
|
||||
turtles: Vec<Arc<RwLock<turtle::Turtle>>>,
|
||||
tasks: Vec<VecDeque<()>>,
|
||||
world: blocks::World,
|
||||
depots: Depots,
|
||||
}
|
||||
|
||||
impl LiveState {
|
||||
async fn save(&self) -> SavedState {
|
||||
let mut turtles = Vec::new();
|
||||
for turtle in self.turtles.iter() {
|
||||
turtles.push(turtle.read().await.info());
|
||||
};
|
||||
let depots = self.depots.clone().to_vec().await;
|
||||
SavedState { turtles, world: self.world.tree().await, depots }
|
||||
}
|
||||
|
||||
fn from_save(save: SavedState) -> Self {
|
||||
let mut turtles = Vec::new();
|
||||
for turtle in save.turtles.into_iter() {
|
||||
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 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,
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_turtle(&self, name: u32) -> Option<TurtleCommander> {
|
||||
TurtleCommander::new(Name::from_num(name), self).await
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static PORT: OnceCell<u16> = OnceCell::const_new();
|
||||
static SAVE: OnceCell<path::PathBuf> = OnceCell::const_new();
|
||||
|
||||
|
@ -99,8 +56,6 @@ async fn main() -> Result<(), Error> {
|
|||
|
||||
let state = read_from_disk().await?;
|
||||
|
||||
let state = LiveState::from_save(state);
|
||||
|
||||
let state = SharedControl::new(RwLock::new(state));
|
||||
|
||||
let server = Router::new()
|
||||
|
@ -115,7 +70,7 @@ async fn main() -> Result<(), Error> {
|
|||
let server = safe_kill::serve(server, listener).await;
|
||||
|
||||
info!("writing");
|
||||
write_to_disk(state.read().await.save().await).await?;
|
||||
write_to_disk(&*state.read().await).await?;
|
||||
|
||||
server.closed().await;
|
||||
|
||||
|
@ -123,23 +78,29 @@ async fn main() -> Result<(), Error> {
|
|||
}
|
||||
|
||||
async fn flush(State(state): State<SharedControl>) -> &'static str {
|
||||
write_to_disk(state.read().await.save().await).await.unwrap();
|
||||
write_to_disk(&*state.read().await).await.unwrap();
|
||||
|
||||
"ACK"
|
||||
}
|
||||
|
||||
async fn write_to_disk(state: SavedState) -> anyhow::Result<()> {
|
||||
async fn write_to_disk(state: &LiveState) -> anyhow::Result<()> {
|
||||
let tasks = &state.tasks;
|
||||
let state = state.save().await;
|
||||
|
||||
let turtles = serde_json::to_string_pretty(&state.turtles)?;
|
||||
let world = bincode::serialize(&state.world)?;
|
||||
let depots = serde_json::to_string_pretty(&state.depots)?;
|
||||
let tasks = serde_json::to_string_pretty(tasks)?;
|
||||
|
||||
let path = &SAVE.get().unwrap();
|
||||
tokio::fs::write(path.join("turtles.json"), turtles).await?;
|
||||
tokio::fs::write(path.join("depots.json"), depots).await?;
|
||||
tokio::fs::write(path.join("tasks.json"), tasks).await?;
|
||||
tokio::fs::write(path.join("world.bin"), world).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn read_from_disk() -> anyhow::Result<SavedState> {
|
||||
async fn read_from_disk() -> anyhow::Result<LiveState> {
|
||||
let turtles = match tokio::fs::OpenOptions::new()
|
||||
.read(true)
|
||||
.open(SAVE.get().unwrap().join("turtles.json"))
|
||||
|
@ -164,6 +125,18 @@ async fn read_from_disk() -> anyhow::Result<SavedState> {
|
|||
},
|
||||
};
|
||||
|
||||
let scheduler = match tokio::fs::OpenOptions::new()
|
||||
.read(true)
|
||||
.open(SAVE.get().unwrap().join("scheduler.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 => Default::default(),
|
||||
_ => panic!(),
|
||||
},
|
||||
};
|
||||
|
||||
let world = match tokio::fs::OpenOptions::new()
|
||||
.read(true).open(SAVE.get().unwrap().join("world.bin")).await {
|
||||
tokio::io::Result::Ok(file) => bincode::deserialize_from(file.into_std().await)?,
|
||||
|
@ -174,9 +147,61 @@ async fn read_from_disk() -> anyhow::Result<SavedState> {
|
|||
|
||||
};
|
||||
|
||||
Ok(SavedState {
|
||||
let saved = SavedState {
|
||||
turtles,
|
||||
world,
|
||||
depots,
|
||||
})
|
||||
};
|
||||
|
||||
let mut live = LiveState::from_save(saved, scheduler);
|
||||
|
||||
for turtle in live.turtles.iter() {
|
||||
live.tasks.add_turtle(&TurtleCommander::new(turtle.read().await.name,&live).await.unwrap())
|
||||
}
|
||||
|
||||
Ok(live)
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct SavedState {
|
||||
turtles: Vec<turtle::Turtle>,
|
||||
world: RTree<Block>,
|
||||
depots: Vec<Position>,
|
||||
//chunkloaders: unimplemented!(),
|
||||
}
|
||||
|
||||
struct LiveState {
|
||||
turtles: Vec<Arc<RwLock<turtle::Turtle>>>,
|
||||
tasks: Scheduler,
|
||||
world: blocks::World,
|
||||
depots: Depots,
|
||||
}
|
||||
|
||||
impl LiveState {
|
||||
async fn save(&self) -> SavedState {
|
||||
let mut turtles = Vec::new();
|
||||
for turtle in self.turtles.iter() {
|
||||
turtles.push(turtle.read().await.info());
|
||||
};
|
||||
let depots = self.depots.clone().to_vec().await;
|
||||
SavedState { turtles, world: self.world.tree().await, depots }
|
||||
}
|
||||
|
||||
fn from_save(save: SavedState, scheduler: Scheduler) -> Self {
|
||||
let mut turtles = Vec::new();
|
||||
for turtle in save.turtles.into_iter() {
|
||||
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 depots = Depots::from_vec(save.depots);
|
||||
|
||||
Self { turtles: turtles.into_iter().map(|t| Arc::new(RwLock::new(t))).collect(), tasks: scheduler, world: World::from_tree(save.world),
|
||||
depots,
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_turtle(&self, name: u32) -> Option<TurtleCommander> {
|
||||
TurtleCommander::new(Name::from_num(name), self).await
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -25,8 +25,8 @@ pub async fn mine(turtle: TurtleCommander, pos: Vec3, fuel: Position, storage: P
|
|||
let mut valuables = Vec::new();
|
||||
|
||||
async fn refuel_needed(turtle: &TurtleCommander, volume: i32, fuel: Position) -> Option<()> {
|
||||
Some(if (turtle.fuel().await as f64) < (2 * volume + (fuel.pos-turtle.pos().await.pos).abs().sum()) as f64 * 1.8 {
|
||||
let name = turtle.name().await.to_str();
|
||||
Some(if (turtle.fuel() as f64) < (2 * volume + (fuel.pos-turtle.pos().await.pos).abs().sum()) as f64 * 1.8 {
|
||||
let name = turtle.name().to_str();
|
||||
info!("{name}: refueling");
|
||||
turtle.goto(fuel).await?;
|
||||
info!("{name}: docked");
|
||||
|
@ -94,14 +94,14 @@ pub async fn mine_chunk(turtle: TurtleCommander, pos: Vec3, chunk: Vec3) -> Opti
|
|||
async fn refuel(turtle: TurtleCommander) {
|
||||
turtle.execute(Select(16)).await;
|
||||
turtle.execute(DropUp(64)).await;
|
||||
let limit = turtle.fuel_limit().await;
|
||||
while turtle.fuel().await < limit {
|
||||
let limit = turtle.fuel_limit();
|
||||
while turtle.fuel() < limit {
|
||||
turtle.execute(SuckFront(64)).await;
|
||||
let re = turtle.execute(Refuel).await;
|
||||
if let TurtleCommandResponse::Failure = re.ret {
|
||||
// partial refuel, good enough
|
||||
warn!("only received {} fuel", turtle.fuel().await);
|
||||
if turtle.fuel().await > 5000 {
|
||||
warn!("only received {} fuel", turtle.fuel());
|
||||
if turtle.fuel() > 5000 {
|
||||
break;
|
||||
} else {
|
||||
turtle.execute(Wait(15)).await;
|
||||
|
@ -148,7 +148,7 @@ fn step(n: i32, x: i32) -> i32 {
|
|||
}
|
||||
|
||||
/// generates a sequence of adjacent positions within a volume
|
||||
fn fill(scale: Vec3, n: i32) -> Vec3 {
|
||||
pub fn fill(scale: Vec3, n: i32) -> Vec3 {
|
||||
assert!(n < scale.x * scale.y * scale.z);
|
||||
Vec3::new(
|
||||
step(n,scale.x),
|
||||
|
|
|
@ -6,18 +6,19 @@ use tokio::sync::{Mutex, MutexGuard, RwLock, OwnedMutexGuard};
|
|||
use tokio::task::JoinHandle;
|
||||
|
||||
use crate::LiveState;
|
||||
use crate::names::Name;
|
||||
use crate::{turtle::{self, TurtleCommander}, blocks::Position};
|
||||
|
||||
#[typetag::serde(tag = "type")]
|
||||
trait Task {
|
||||
pub trait Task: Send + Sync {
|
||||
/// Execute the task
|
||||
fn run(&self, turtle: TurtleCommander) -> JoinHandle<()>;
|
||||
fn run(&mut self, turtle: TurtleCommander) -> JoinHandle<()>;
|
||||
/// Return Some if the task should be scheduled
|
||||
fn poll(&self) -> Option<Position>;
|
||||
fn poll(&mut self) -> Option<Position>;
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct Scheduler {
|
||||
pub struct Scheduler {
|
||||
#[serde(skip)]
|
||||
turtles: Vec<(TurtleCommander, Option<JoinHandle<()>>)>,
|
||||
tasks: Vec<Box<dyn Task>>,
|
||||
|
@ -35,18 +36,18 @@ impl Default for Scheduler {
|
|||
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) {
|
||||
pub fn add_turtle(&mut self, turtle: &TurtleCommander) {
|
||||
self.turtles.push((
|
||||
turtle.clone(),
|
||||
None
|
||||
));
|
||||
}
|
||||
|
||||
fn add_task(&mut self, task: Box<dyn Task>) {
|
||||
pub fn add_task(&mut self, task: Box<dyn Task>) {
|
||||
self.tasks.push(task);
|
||||
}
|
||||
|
||||
async fn poll(&mut self) {
|
||||
pub async fn poll(&mut self) {
|
||||
for turtle in &mut self.turtles {
|
||||
if let Some(join) = &turtle.1 {
|
||||
if join.is_finished() {
|
||||
|
@ -77,4 +78,9 @@ impl Scheduler {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
pub async fn cancel(&mut self, turtle: Name) -> Option<()> {
|
||||
self.turtles.iter_mut().find(|t| t.0.name() == turtle)?.1.take()?.abort();
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -167,6 +167,18 @@ impl TurtleCommander {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn with_turtle(turtle: &Turtle, state: &LiveState) -> TurtleCommander {
|
||||
TurtleCommander {
|
||||
sender: turtle.sender.as_ref().unwrap().clone(),
|
||||
world: state.world.clone(),
|
||||
pos: Arc::new(RwLock::new(turtle.position)),
|
||||
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(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn execute(&self, command: TurtleCommand) -> TurtleInfo {
|
||||
let (send, recv) = oneshot::channel::<TurtleInfo>();
|
||||
|
||||
|
@ -179,7 +191,7 @@ impl TurtleCommander {
|
|||
resp
|
||||
}
|
||||
|
||||
pub async fn name(&self) -> Name {
|
||||
pub fn name(&self) -> Name {
|
||||
self.name.get().unwrap().clone()
|
||||
}
|
||||
|
||||
|
@ -187,11 +199,11 @@ impl TurtleCommander {
|
|||
self.pos.read().await.clone()
|
||||
}
|
||||
|
||||
pub async fn fuel(&self) -> usize {
|
||||
pub fn fuel(&self) -> usize {
|
||||
self.fuel.load(std::sync::atomic::Ordering::SeqCst)
|
||||
}
|
||||
|
||||
pub async fn fuel_limit(&self) -> usize {
|
||||
pub fn fuel_limit(&self) -> usize {
|
||||
self.max_fuel.load(std::sync::atomic::Ordering::SeqCst)
|
||||
}
|
||||
|
||||
|
@ -199,6 +211,10 @@ impl TurtleCommander {
|
|||
self.world.clone()
|
||||
}
|
||||
|
||||
pub async fn dock(&self) -> Option<usize> {
|
||||
Depots::dock(&self.depots, self.to_owned()).await
|
||||
}
|
||||
|
||||
pub async fn goto(&self, pos: Position) -> Option<()> {
|
||||
let mut recent = self.pos().await;
|
||||
let world = self.world.clone();
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use tokio;
|
||||
use blocks::Vec3;
|
||||
use crate::turtle::TurtleCommandResponse;
|
||||
use crate::turtle::TurtleCommander;
|
||||
use crate::turtle::TurtleInfo;
|
||||
use axum::extract::Path;
|
||||
use crate::turtle::TurtleCommand;
|
||||
|
@ -45,12 +46,13 @@ pub(crate) async fn create_turtle(
|
|||
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::Turtle::with_channel(id, Position::new(req.position, req.facing), req.fuel, req.fuellimit, send,receive)
|
||||
turtle
|
||||
)));
|
||||
state.tasks.push(VecDeque::new());
|
||||
|
||||
|
||||
info!("new turtle: {id}");
|
||||
|
||||
|
@ -115,7 +117,7 @@ pub(crate) async fn cancel(
|
|||
Path(id): Path<u32>,
|
||||
State(state): State<SharedControl>,
|
||||
) -> &'static str {
|
||||
state.write().await.tasks[id as usize].pop_front();
|
||||
//state.write().await.tasks
|
||||
|
||||
"ACK"
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue