Skip to content

Commit

Permalink
improve some docs and apis related to pathfinder
Browse files Browse the repository at this point in the history
  • Loading branch information
mat-1 committed Dec 24, 2024
1 parent 30cbeec commit 958848e
Show file tree
Hide file tree
Showing 7 changed files with 232 additions and 77 deletions.
4 changes: 3 additions & 1 deletion azalea-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ license = { workspace = true }
repository = { workspace = true }

[dependencies]
anyhow = { workspace = true }
azalea-auth = { path = "../azalea-auth", version = "0.11.0" }
azalea-block = { path = "../azalea-block", version = "0.11.0" }
azalea-buf = { path = "../azalea-buf", version = "0.11.0" }
Expand Down Expand Up @@ -36,6 +35,9 @@ tokio = { workspace = true, features = ["sync"] }
tracing = { workspace = true }
uuid = { workspace = true }

[dev-dependencies]
anyhow = { workspace = true }

[features]
default = ["log"]
# enables bevy_log::LogPlugin by default
Expand Down
54 changes: 54 additions & 0 deletions azalea-client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,14 @@ impl Client {
/// Get a component from this client. This will clone the component and
/// return it.
///
///
/// If the component can't be cloned, try [`Self::map_component`] instead.
/// If it isn't guaranteed to be present, use [`Self::get_component`] or
/// [`Self::map_get_component`].
///
/// You may also use [`Self::ecs`] and [`Self::query`] directly if you need
/// more control over when the ECS is locked.
///
/// # Panics
///
/// This will panic if the component doesn't exist on the client.
Expand All @@ -534,10 +542,56 @@ impl Client {
}

/// Get a component from this client, or `None` if it doesn't exist.
///
/// If the component can't be cloned, try [`Self::map_component`] instead.
/// You may also have to use [`Self::ecs`] and [`Self::query`] directly.
pub fn get_component<T: Component + Clone>(&self) -> Option<T> {
self.query::<Option<&T>>(&mut self.ecs.lock()).cloned()
}

/// Get a required component for this client and call the given function.
///
/// Similar to [`Self::component`], but doesn't clone the component since
/// it's passed as a reference. [`Self::ecs`] will remain locked while the
/// callback is being run.
///
/// If the component is not guaranteed to be present, use
/// [`Self::get_component`] instead.
///
/// # Panics
///
/// This will panic if the component doesn't exist on the client.
///
/// ```
/// # use azalea_client::{Client, Hunger};
/// # fn example(bot: &Client) {
/// let hunger = bot.map_component::<Hunger, _>(|h| h.food);
/// # }
/// ```
pub fn map_component<T: Component, R>(&self, f: impl FnOnce(&T) -> R) -> R {
let mut ecs = self.ecs.lock();
let value = self.query::<&T>(&mut ecs);
f(value)
}

/// Optionally get a component for this client and call the given function.
///
/// Similar to [`Self::get_component`], but doesn't clone the component
/// since it's passed as a reference. [`Self::ecs`] will remain locked
/// while the callback is being run.
///
/// ```
/// # use azalea_client::{Client, mining::Mining};
/// # fn example(bot: &Client) {
/// let is_mining = bot.map_get_component::<Mining, _>(|m| m.is_some());
/// # }
/// ```
pub fn map_get_component<T: Component, R>(&self, f: impl FnOnce(Option<&T>) -> R) -> R {
let mut ecs = self.ecs.lock();
let value = self.query::<Option<&T>>(&mut ecs);
f(value)
}

/// Get an `RwLock` with a reference to our (potentially shared) world.
///
/// This gets the [`Instance`] from the client's [`InstanceHolder`]
Expand Down
3 changes: 3 additions & 0 deletions azalea-client/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ pub enum Event {
/// it's actually spawned. This can be useful for setting the client
/// information with `Client::set_client_information`, so the packet
/// doesn't have to be sent twice.
///
/// You may want to use [`Event::Login`] instead to wait for the bot to be
/// in the world.
Init,
/// The client is now in the world. Fired when we receive a login packet.
Login,
Expand Down
2 changes: 1 addition & 1 deletion azalea-client/src/local_player.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ pub enum HandlePacketError {
#[error(transparent)]
Io(#[from] io::Error),
#[error(transparent)]
Other(#[from] anyhow::Error),
Other(#[from] Box<dyn std::error::Error + Send + Sync>),
#[error("{0}")]
Send(#[from] mpsc::error::SendError<AzaleaEvent>),
}
Expand Down
5 changes: 5 additions & 0 deletions azalea/src/bot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@ impl BotClientExt for azalea_client::Client {
});
}

/// Returns a Receiver that receives a message every game tick.
///
/// This is useful if you want to efficiently loop until a certain condition
/// is met.
///
/// ```
/// # use azalea::prelude::*;
/// # use azalea::container::WaitingForInventoryOpen;
Expand Down
28 changes: 23 additions & 5 deletions azalea/src/pathfinder/astar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,24 @@ const COEFFICIENTS: [f32; 7] = [1.5, 2., 2.5, 3., 4., 5., 10.];

const MIN_IMPROVEMENT: f32 = 0.01;

pub enum PathfinderTimeout {
/// Time out after a certain duration has passed. This is a good default so
/// you don't waste too much time calculating a path if you're on a slow
/// computer.
Time(Duration),
/// Time out after this many nodes have been considered.
///
/// This is useful as an alternative to a time limit if you're doing
/// something like running tests where you want consistent results.
Nodes(usize),
}

pub fn a_star<P, M, HeuristicFn, SuccessorsFn, SuccessFn>(
start: P,
heuristic: HeuristicFn,
mut successors: SuccessorsFn,
success: SuccessFn,
timeout: Duration,
timeout: PathfinderTimeout,
) -> Path<P, M>
where
P: Eq + Hash + Copy + Debug,
Expand Down Expand Up @@ -104,10 +116,16 @@ where
}

// check for timeout every ~1ms
if num_nodes % 1000 == 0 && start_time.elapsed() > timeout {
// timeout, just return the best path we have so far
trace!("A* couldn't find a path in time, returning best path");
break;
if num_nodes % 1000 == 0 {
let timed_out = match timeout {
PathfinderTimeout::Time(max_duration) => start_time.elapsed() > max_duration,
PathfinderTimeout::Nodes(max_nodes) => num_nodes > max_nodes,
};
if timed_out {
// timeout, just return the best path we have so far
trace!("A* couldn't find a path in time, returning best path");
break;
}
}
}

Expand Down
Loading

0 comments on commit 958848e

Please sign in to comment.