diff --git a/server/src/main.rs b/server/src/main.rs index 56cea5a..6e449ff 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -17,7 +17,7 @@ use names::Name; use tasks::Scheduler; use tokio::{sync::{ RwLock, mpsc, OnceCell, Mutex -}, fs}; +}, fs, time::Instant}; use turtle::{Turtle, TurtleCommander}; use serde::{Deserialize, Serialize}; use indoc::formatdoc; @@ -175,6 +175,7 @@ struct LiveState { tasks: Scheduler, world: blocks::World, depots: Depots, + started: Instant, } impl LiveState { @@ -197,6 +198,7 @@ impl LiveState { Self { turtles: turtles.into_iter().map(|t| Arc::new(RwLock::new(t))).collect(), tasks: scheduler, world: World::from_tree(save.world), depots, + started: Instant::now(), } } diff --git a/server/src/turtle.rs b/server/src/turtle.rs index 8d83aa9..1b57193 100644 --- a/server/src/turtle.rs +++ b/server/src/turtle.rs @@ -38,7 +38,7 @@ use super::paths::route; /// Time (ms) to wait for a command before letting a turtle go idle const COMMAND_TIMEOUT: u64 = 0o372; /// Time (s) between turtle polls when idle -const IDLE_TIME: u32 = 3; +pub const IDLE_TIME: u32 = 3; #[derive(Serialize, Deserialize)] pub(crate) struct Turtle { @@ -314,16 +314,16 @@ pub(crate) async fn process_turtle_update( id: u32, state: &LiveState, update: TurtleUpdate, -) -> anyhow::Result { +) -> Option { let mut turtle = state .turtles .get(id as usize) - .context("nonexisting turtle")?.write().await; + .context("nonexisting turtle").unwrap().write().await; let world = &state.world; if turtle.pending_update { turtle.pending_update = false; - return Ok(TurtleCommand::Update); + return Some(TurtleCommand::Update); } if turtle.fuel > update.fuel { @@ -376,12 +376,12 @@ pub(crate) async fn process_turtle_update( } turtle.queued_movement = cmd.unit(turtle.position.dir); info!("{}: {cmd:?}", turtle.name.to_str()); - return Ok(cmd); + return Some(cmd); } } trace!("{} idle, connected", turtle.name.to_str()); - Ok(TurtleCommand::Wait(IDLE_TIME)) + None } #[derive(Serialize, Deserialize, Clone, Debug)] diff --git a/server/src/turtle_api.rs b/server/src/turtle_api.rs index bb0d16e..7ee3902 100644 --- a/server/src/turtle_api.rs +++ b/server/src/turtle_api.rs @@ -1,8 +1,10 @@ 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; @@ -29,6 +31,10 @@ 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)) @@ -193,16 +199,29 @@ pub(crate) async fn command( Json(req): Json, ) -> Json { trace!("reply from turtle {id}: {req:?}"); - let mut state = &mut state.read().await; + let state_guard = state.clone().read_owned().await; - if id as usize > state.turtles.len() { + if id as usize > state_guard.turtles.len() { return Json(turtle::TurtleCommand::Update); } - Json( - turtle::process_turtle_update(id, &mut state, req).await - .unwrap_or(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; + schedule.poll().await; + } + }); + turtle::TurtleCommand::Wait(IDLE_TIME) + }, + }; + + Json(command) } pub(crate) async fn client() -> String {