1
Fork 0

averted turtle deadlock with liberal use of smart pointers

This commit is contained in:
Andy Killorin 2023-12-20 00:18:05 -06:00
parent 1694c461ff
commit 69e7d20df1
Signed by: ank
GPG key ID: B6241CA3B552BCA4
5 changed files with 47 additions and 30 deletions

View file

@ -89,7 +89,7 @@ end
local commands = { local commands = {
["Wait"] = sleep, ["Wait"] = sleep,
["Forward"] = cyclefn(turtle.forward), ["Forward"] = cyclefn(turtle.forward),
["Backward"] = cyclefn(turtle.backward), ["Backward"] = cyclefn(turtle.back),
["Up"] = cyclefn(turtle.up), ["Up"] = cyclefn(turtle.up),
["Down"] = cyclefn(turtle.down), ["Down"] = cyclefn(turtle.down),
["DropFront"] = turtle.dropfront, ["DropFront"] = turtle.dropfront,

View file

@ -30,6 +30,7 @@ impl World {
self.state.write().await.insert(block); self.state.write().await.insert(block);
} }
/// Returns true if a known non-air block exists at the point
pub async fn occupied(&self, block: Vec3) -> bool { pub async fn occupied(&self, block: Vec3) -> bool {
self.state.read().await.locate_at_point(&block.into()).is_some_and(|b| b.name != "minecraft:air") self.state.read().await.locate_at_point(&block.into()).is_some_and(|b| b.name != "minecraft:air")
} }

View file

@ -171,8 +171,7 @@ async fn dig(
let turtle = state.read().await.get_turtle(id).await.unwrap(); let turtle = state.read().await.get_turtle(id).await.unwrap();
tokio::spawn( tokio::spawn(
async move { async move {
let pos = turtle.goto_adjacent(req).await.unwrap(); mine::mine_chunk(turtle.clone(), req).await
turtle.execute(pos.dig(req).unwrap()).await
} }
); );
@ -230,8 +229,6 @@ async fn command(
) -> Json<turtle::TurtleCommand> { ) -> Json<turtle::TurtleCommand> {
let mut state = &mut state.write().await; let mut state = &mut state.write().await;
println!("{:?}", &req);
if id as usize > state.turtles.len() { if id as usize > state.turtles.len() {
return Json(turtle::TurtleCommand::Update); return Json(turtle::TurtleCommand::Update);
} }

View file

@ -16,6 +16,12 @@ pub async fn route_facing(from: Position, to: Vec3, world: &World) -> Option<Vec
} }
pub async fn route(from: Position, to: Position, world: &World) -> Option<Vec<Position>> { pub async fn route(from: Position, to: Position, world: &World) -> Option<Vec<Position>> {
// attempt at not crashing by looking infinitely into the abyss
if world.get(to.pos).await
.is_some_and(|b| difficulty(&b.name).is_none())
{
return None;
}
route_to(from, to.pos, |p| p == &to, world).await route_to(from, to.pos, |p| p == &to, world).await
} }
@ -24,13 +30,6 @@ where D: FnMut(&Position) -> bool {
// lock once, we'll be doing a lot of lookups // lock once, we'll be doing a lot of lookups
let world = world.clone().lock().await; let world = world.clone().lock().await;
// attempt at not crashing by looking infinitely into the abyss
if world
.locate_at_point(&to.into())
.is_some_and(|b| difficulty(&b.name).is_none())
{
return None;
}
let route = astar( let route = astar(
&from, &from,
move |p| next(p, &world), move |p| next(p, &world),
@ -63,8 +62,8 @@ fn next(from: &Position, world: &WorldReadLock) -> Vec<(Position, u32)> {
let ahead = from.pos + from.dir.unit(); let ahead = from.pos + from.dir.unit();
insert(&mut vec, ahead, from.dir, world, UNKNOWN); insert(&mut vec, ahead, from.dir, world, UNKNOWN);
//let behind = from.pos - from.dir.unit(); let behind = from.pos - from.dir.unit();
//insert(&mut vec, behind, from.dir, world, None); insert(&mut vec, behind, from.dir, world, None);
let above = from.pos + Vec3::y(); let above = from.pos + Vec3::y();
insert(&mut vec, above, from.dir, world, UNKNOWN); insert(&mut vec, above, from.dir, world, UNKNOWN);

View file

@ -25,6 +25,7 @@ use super::LiveState;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::future::Ready; use std::future::Ready;
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::AtomicUsize;
use std::time::Duration; use std::time::Duration;
@ -143,16 +144,24 @@ impl Turtle {
pub struct TurtleCommander { pub struct TurtleCommander {
sender: Arc<Sender>, sender: Arc<Sender>,
world: World, world: World,
turtle: Arc<RwLock<Turtle>>, // everything below is best-effort
// TODO: make not bad
pos: Arc<RwLock<Position>>,
fuel: Arc<AtomicUsize>,
max_fuel: Arc<AtomicUsize>,
} }
impl TurtleCommander { impl TurtleCommander {
pub async fn new(turtle: Name, state: &LiveState) -> Option<TurtleCommander> { pub async fn new(turtle: Name, state: &LiveState) -> Option<TurtleCommander> {
let turtle = state.turtles.get(turtle.to_num() as usize)?.clone(); let turtle = state.turtles.get(turtle.to_num() as usize)?.clone();
let turtle = turtle.read().await;
Some(TurtleCommander { Some(TurtleCommander {
sender: turtle.clone().read().await.sender.as_ref().unwrap().clone(), sender: turtle.sender.as_ref().unwrap().clone(),
world: state.world.clone(), world: state.world.clone(),
turtle, pos: Arc::new(RwLock::new(turtle.position)),
fuel: Arc::new(AtomicUsize::new(turtle.fuel)),
max_fuel: Arc::new(AtomicUsize::new(turtle.fuel_limit)),
}) })
} }
@ -161,22 +170,26 @@ impl TurtleCommander {
self.sender.to_owned().send((command,send)).await.unwrap(); self.sender.to_owned().send((command,send)).await.unwrap();
recv.await.unwrap() let resp = recv.await.unwrap();
let mut pos = self.pos.write().await;
*pos = resp.pos;
self.fuel.store(resp.fuel, std::sync::atomic::Ordering::SeqCst);
resp
} }
pub async fn pos(&self) -> Position { pub async fn pos(&self) -> Position {
self.turtle.read().await.position self.pos.read().await.clone()
} }
pub async fn fuel(&self) -> usize { pub async fn fuel(&self) -> usize {
self.turtle.read().await.fuel self.fuel.load(std::sync::atomic::Ordering::SeqCst)
} }
pub async fn fuel_limit(&self) -> usize { pub async fn fuel_limit(&self) -> usize {
self.turtle.read().await.fuel_limit self.max_fuel.load(std::sync::atomic::Ordering::SeqCst)
} }
pub async fn world(&self) -> World { pub fn world(&self) -> World {
self.world.clone() self.world.clone()
} }
@ -188,7 +201,9 @@ impl TurtleCommander {
break; break;
} }
let route = route(recent, pos, &world).await?; // easiest way to not eventually take over all memory
let routing = timeout(Duration::from_secs(1), route(recent, pos, &world));
let route = routing.await.ok()??;
let steps: Vec<TurtleCommand> = route.iter().map_windows(|[from,to]| from.difference(**to).unwrap()).collect(); let steps: Vec<TurtleCommand> = route.iter().map_windows(|[from,to]| from.difference(**to).unwrap()).collect();
@ -212,18 +227,19 @@ impl TurtleCommander {
let world = self.world.clone(); let world = self.world.clone();
loop { loop {
if pos == recent.dir.unit() + recent.pos { if pos == recent.dir.unit() + recent.pos
|| pos == recent.pos + Vec3::y()
|| pos == recent.pos - Vec3::y()
{
break; break;
} }
let route = route_facing(recent, pos, &world).await?; let routing = timeout(Duration::from_secs(1), route_facing(recent, pos, &world));
let route = routing.await.ok()??;
let steps: Vec<TurtleCommand> = route.iter().map_windows(|[from,to]| from.difference(**to).unwrap()).collect(); let steps: Vec<TurtleCommand> = route.iter().map_windows(|[from,to]| from.difference(**to).unwrap()).collect();
'route: for (next_position, command) in route.into_iter().skip(1).zip(steps) { 'route: for (next_position, command) in route.into_iter().skip(1).zip(steps) {
// reroute if the goal point is not empty before moving
// valid routes will explicitly tell you to break ground
if world.occupied(next_position.pos).await { if world.occupied(next_position.pos).await {
if world.garbage(next_position.pos).await { if world.garbage(next_position.pos).await {
self.execute(dbg!(recent.dig(next_position.pos))?).await; self.execute(dbg!(recent.dig(next_position.pos))?).await;
@ -232,9 +248,13 @@ impl TurtleCommander {
} }
} }
let state = self.execute(command).await; let state = self.execute(command.clone()).await;
if let TurtleCommandResponse::Failure = state.ret { if let TurtleCommandResponse::Failure = state.ret {
if let TurtleCommand::Backward(_) = command {
// turn around if you bump your rear on something
self.goto(Position::new(recent.pos, recent.dir.left().left())).await?
}
break 'route; break 'route;
} }
@ -309,7 +329,7 @@ pub(crate) async fn process_turtle_update(
_ => {} _ => {}
} }
turtle.queued_movement = cmd.unit(turtle.position.dir); turtle.queued_movement = cmd.unit(turtle.position.dir);
println!("Turtle {}: {cmd:?}", turtle.name.to_str()); println!("{}: {cmd:?}", turtle.name.to_str());
return Ok(cmd); return Ok(cmd);
} }
} }