aboutsummaryrefslogtreecommitdiff
path: root/azalea/src/client_impl
diff options
context:
space:
mode:
Diffstat (limited to 'azalea/src/client_impl')
-rw-r--r--azalea/src/client_impl/attack.rs15
-rw-r--r--azalea/src/client_impl/chat.rs6
-rw-r--r--azalea/src/client_impl/entity_query.rs144
-rw-r--r--azalea/src/client_impl/interact.rs6
-rw-r--r--azalea/src/client_impl/inventory.rs2
-rw-r--r--azalea/src/client_impl/mining.rs4
-rw-r--r--azalea/src/client_impl/mod.rs76
-rw-r--r--azalea/src/client_impl/movement.rs8
8 files changed, 151 insertions, 110 deletions
diff --git a/azalea/src/client_impl/attack.rs b/azalea/src/client_impl/attack.rs
index f4cac51f..16721c1c 100644
--- a/azalea/src/client_impl/attack.rs
+++ b/azalea/src/client_impl/attack.rs
@@ -12,7 +12,7 @@ impl Client {
/// This doesn't automatically look at the entity or perform any
/// range/visibility checks, so it might trigger anticheats.
pub fn attack(&self, entity: Entity) {
- self.ecs.lock().write_message(AttackEvent {
+ self.ecs.write().write_message(AttackEvent {
entity: self.entity,
target: entity,
});
@@ -27,18 +27,19 @@ impl Client {
// attack? whatever, just return false
return false;
};
- *attack_strength_scale < 1.0
+ **attack_strength_scale < 1.0
}
/// Returns the number of ticks until we can attack at full strength again.
///
/// Also see [`Client::has_attack_cooldown`].
pub fn attack_cooldown_remaining_ticks(&self) -> usize {
- let mut ecs = self.ecs.lock();
- let Ok((attributes, ticks_since_last_attack)) = ecs
- .query::<(&Attributes, &TicksSinceLastAttack)>()
- .get(&ecs, self.entity)
- else {
+ let ecs = self.ecs.read();
+
+ let Some(attributes) = ecs.get::<Attributes>(self.entity) else {
+ return 0;
+ };
+ let Some(ticks_since_last_attack) = ecs.get::<TicksSinceLastAttack>(self.entity) else {
return 0;
};
diff --git a/azalea/src/client_impl/chat.rs b/azalea/src/client_impl/chat.rs
index 3ca98631..e71a208f 100644
--- a/azalea/src/client_impl/chat.rs
+++ b/azalea/src/client_impl/chat.rs
@@ -11,7 +11,7 @@ impl Client {
/// the message is a command and using the proper packet for you, so you
/// should use that instead.
pub fn write_chat_packet(&self, message: &str) {
- self.ecs.lock().write_message(SendChatKindEvent {
+ self.ecs.write().write_message(SendChatKindEvent {
entity: self.entity,
content: message.to_owned(),
kind: ChatKind::Message,
@@ -24,7 +24,7 @@ impl Client {
/// You can also just use [`Client::chat`] and start your message with a `/`
/// to send a command.
pub fn write_command_packet(&self, command: &str) {
- self.ecs.lock().write_message(SendChatKindEvent {
+ self.ecs.write().write_message(SendChatKindEvent {
entity: self.entity,
content: command.to_owned(),
kind: ChatKind::Command,
@@ -41,7 +41,7 @@ impl Client {
/// # }
/// ```
pub fn chat(&self, content: impl Into<String>) {
- self.ecs.lock().write_message(SendChatEvent {
+ self.ecs.write().write_message(SendChatEvent {
entity: self.entity,
content: content.into(),
});
diff --git a/azalea/src/client_impl/entity_query.rs b/azalea/src/client_impl/entity_query.rs
index 85e46525..ae623dc7 100644
--- a/azalea/src/client_impl/entity_query.rs
+++ b/azalea/src/client_impl/entity_query.rs
@@ -9,41 +9,100 @@ use bevy_ecs::{
query::{QueryData, QueryEntityError, QueryFilter, QueryItem, ROQueryItem},
world::World,
};
-use parking_lot::Mutex;
+use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard};
use crate::Client;
impl Client {
- /// A convenience function for getting components from our client's entity.
+ /// Get a component from the client.
///
- /// To query another entity, you can use [`Self::query_entity`].
+ /// This allows you to access certain data stored about the client entity
+ /// that isn't accessible in a simpler way.
+ ///
+ /// This returns a reference to the component wrapped by a read guard. This
+ /// makes the component cheap to access, but means that the ECS cannot be
+ /// mutated while it's in scope. In some cases, it may be simpler for you to
+ /// immediately clone the component after accessing it.
+ ///
+ /// If the component isn't guaranteed to be present, consider using
+ /// [`Self::get_component`] instead.
+ ///
+ /// To do more complex queries or to mutate data, see [`Self::query_self`].
+ ///
+ /// To access data about other entities, you can use
+ /// [`Self::entity_component`] (and its other related functions).
+ ///
+ /// You may also use [`Self::ecs`] 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. Use
+ /// [`Self::get_component`] to avoid this.
///
/// # Examples
+ ///
/// ```
/// # use azalea_world::InstanceName;
+ /// # fn example(client: &azalea::Client) {
+ /// let world_name = client.component::<InstanceName>();
+ /// # }
+ pub fn component<T: Component>(&self) -> MappedRwLockReadGuard<'_, T> {
+ self.get_component::<T>().unwrap_or_else(|| {
+ panic!(
+ "Our client is missing a required component: {:?}",
+ any::type_name::<&T>()
+ )
+ })
+ }
+
+ /// Get a component on this client, or `None` if it doesn't exist.
+ ///
+ /// If the component is guaranteed to be present, consider using
+ /// [`Self::component`]. Also see that function for more details.
+ pub fn get_component<T: Component>(&self) -> Option<MappedRwLockReadGuard<'_, T>> {
+ self.get_entity_component::<T>(self.entity)
+ }
+
+ /// Query the ECS for data from our client entity.
+ ///
+ /// To query another entity, you can use [`Self::query_entity`].
+ ///
+ /// You can use this to mutate data on the client.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use azalea_entity::Position;
/// # fn example(mut client: azalea::Client) {
- /// let is_logged_in = client.query_self::<Option<&InstanceName>, _>(|ins| ins.is_some());
+ /// // teleport one block up
+ /// client.query_self::<&mut Position, _>(|mut pos| pos.y += 1.0);
/// # }
/// ```
///
/// # Panics
///
- /// This will panic if the component doesn't exist on the client.
+ /// This will panic if the client is missing a component required by the
+ /// query.
pub fn query_self<D: QueryData, R>(&self, f: impl FnOnce(QueryItem<D>) -> R) -> R {
- let mut ecs = self.ecs.lock();
+ let mut ecs = self.ecs.write();
let mut qs = ecs.query::<D>();
let res = qs.get_mut(&mut ecs, self.entity).unwrap_or_else(|_| {
panic!(
- "Our client is missing a required component {:?}",
+ "`Client::query_self` failed when querying for {:?}",
any::type_name::<D>()
)
});
f(res)
}
- /// A convenience function for getting components from any entity.
+ /// Query the ECS for data from an entity.
///
- /// If you're querying the client, you should use [`Self::query_self`].
+ /// Note that it is often simpler to use [`Self::entity_component`].
+ ///
+ /// To query the client, you should use [`Self::query_self`].
+ ///
+ /// You can also use this to mutate data on an entity.
///
/// # Panics
///
@@ -73,7 +132,7 @@ impl Client {
entity: Entity,
f: impl FnOnce(QueryItem<D>) -> R,
) -> Result<R, QueryEntityError> {
- let mut ecs = self.ecs.lock();
+ let mut ecs = self.ecs.write();
let mut qs = ecs.query::<D>();
qs.get_mut(&mut ecs, entity).map(f)
}
@@ -138,7 +197,7 @@ impl Client {
/// if let Some(nearest_player) =
/// bot.nearest_entity_by::<(), (With<Player>, Without<LocalEntity>)>(|_: ()| true)
/// {
- /// let nearest_player_pos = *bot.entity_component::<Position>(nearest_player);
+ /// let nearest_player_pos = **bot.entity_component::<Position>(nearest_player);
/// bot.chat(format!("You are at {nearest_player_pos}"));
/// }
/// # }
@@ -175,46 +234,61 @@ impl Client {
let Some(position) = self.get_component::<Position>() else {
return vec![];
};
+ let (instance_name, position) = (instance_name.clone(), *position);
predicate.find_all_sorted(self.ecs.clone(), &instance_name, (&position).into())
}
/// Get a component from an entity.
///
- /// Note that this will return an owned type (i.e. not a reference) so it
- /// may be expensive for larger types.
+ /// This allows you to access data stored about entities that isn't
+ /// accessible in a simpler way.
+ ///
+ /// This returns a reference to the component wrapped by a read guard. This
+ /// makes the component cheap to access, but means that the ECS cannot be
+ /// mutated while it's in scope. In some cases, it may be simpler for you to
+ /// immediately clone the component after accessing it.
+ ///
+ /// If you're trying to get a component for this client, you should use
+ /// [`Self::component`] instead.
+ ///
+ /// To do more complex queries or to mutate data, see
+ /// [`Self::query_entity`].
///
- /// If you're trying to get a component for this client, use
- /// [`Self::component`].
- pub fn entity_component<Q: Component + Clone>(&self, entity: Entity) -> Q {
- let mut ecs = self.ecs.lock();
- let mut q = ecs.query::<&Q>();
- let components = q.get(&ecs, entity).unwrap_or_else(|_| {
+ /// # Panics
+ ///
+ /// This will panic if the component doesn't exist on the entity. Use
+ /// [`Self::get_entity_component`] to avoid this.
+ pub fn entity_component<T: Component>(&self, entity: Entity) -> MappedRwLockReadGuard<'_, T> {
+ self.get_entity_component::<T>(entity).unwrap_or_else(|| {
panic!(
- "Entity is missing a required component {:?}",
- any::type_name::<Q>()
+ "Entity {entity} is missing a required component: {:?}",
+ any::type_name::<&T>()
)
- });
- components.clone()
+ })
}
/// Get a component from an entity, if it exists.
///
/// This is similar to [`Self::entity_component`] but returns an `Option`
/// instead of panicking if the component isn't present.
- pub fn get_entity_component<Q: Component + Clone>(&self, entity: Entity) -> Option<Q> {
- let mut ecs = self.ecs.lock();
- let mut q = ecs.query::<&Q>();
- let components = q.get(&ecs, entity).ok();
- components.cloned()
+ pub fn get_entity_component<T: Component>(
+ &self,
+ entity: Entity,
+ ) -> Option<MappedRwLockReadGuard<'_, T>> {
+ let ecs = self.ecs.read();
+ RwLockReadGuard::try_map(ecs, |ecs: &World| ecs.get(entity)).ok()
}
}
pub trait EntityPredicate<Q: QueryData, Filter: QueryFilter> {
- fn find_any(&self, ecs_lock: Arc<Mutex<World>>, instance_name: &InstanceName)
- -> Option<Entity>;
+ fn find_any(
+ &self,
+ ecs_lock: Arc<RwLock<World>>,
+ instance_name: &InstanceName,
+ ) -> Option<Entity>;
fn find_all_sorted(
&self,
- ecs_lock: Arc<Mutex<World>>,
+ ecs_lock: Arc<RwLock<World>>,
instance_name: &InstanceName,
nearest_to: Vec3,
) -> Vec<Entity>;
@@ -226,10 +300,10 @@ where
{
fn find_any(
&self,
- ecs_lock: Arc<Mutex<World>>,
+ ecs_lock: Arc<RwLock<World>>,
instance_name: &InstanceName,
) -> Option<Entity> {
- let mut ecs = ecs_lock.lock();
+ let mut ecs = ecs_lock.write();
let mut query = ecs.query_filtered::<(Entity, &InstanceName, Q), Filter>();
query
.iter(&ecs)
@@ -239,11 +313,11 @@ where
fn find_all_sorted(
&self,
- ecs_lock: Arc<Mutex<World>>,
+ ecs_lock: Arc<RwLock<World>>,
instance_name: &InstanceName,
nearest_to: Vec3,
) -> Vec<Entity> {
- let mut ecs = ecs_lock.lock();
+ let mut ecs = ecs_lock.write();
let mut query = ecs.query_filtered::<(Entity, &InstanceName, &Position, Q), Filter>();
let mut entities = query
.iter(&ecs)
diff --git a/azalea/src/client_impl/interact.rs b/azalea/src/client_impl/interact.rs
index 6ff93549..8a9cf475 100644
--- a/azalea/src/client_impl/interact.rs
+++ b/azalea/src/client_impl/interact.rs
@@ -15,7 +15,7 @@ impl Client {
/// Note that this may trigger anticheats as it doesn't take into account
/// whether you're actually looking at the block.
pub fn block_interact(&self, position: BlockPos) {
- self.ecs.lock().write_message(StartUseItemEvent {
+ self.ecs.write().write_message(StartUseItemEvent {
entity: self.entity,
hand: InteractionHand::MainHand,
force_block: Some(position),
@@ -28,7 +28,7 @@ impl Client {
/// behavior isn't desired, consider using [`Client::start_use_item`]
/// instead.
pub fn entity_interact(&self, entity: Entity) {
- self.ecs.lock().trigger(EntityInteractEvent {
+ self.ecs.write().trigger(EntityInteractEvent {
client: self.entity,
target: entity,
location: None,
@@ -43,7 +43,7 @@ impl Client {
/// If we're looking at a block or entity, then it will be clicked. Also see
/// [`Client::block_interact`] and [`Client::entity_interact`].
pub fn start_use_item(&self) {
- self.ecs.lock().write_message(StartUseItemEvent {
+ self.ecs.write().write_message(StartUseItemEvent {
entity: self.entity,
hand: InteractionHand::MainHand,
force_block: None,
diff --git a/azalea/src/client_impl/inventory.rs b/azalea/src/client_impl/inventory.rs
index 0ea11477..fc1e2e73 100644
--- a/azalea/src/client_impl/inventory.rs
+++ b/azalea/src/client_impl/inventory.rs
@@ -36,7 +36,7 @@ impl Client {
"Hotbar slot index must be in the range 0..=8"
);
- let mut ecs = self.ecs.lock();
+ let mut ecs = self.ecs.write();
ecs.trigger(SetSelectedHotbarSlotEvent {
entity: self.entity,
slot: new_hotbar_slot_index,
diff --git a/azalea/src/client_impl/mining.rs b/azalea/src/client_impl/mining.rs
index 14794765..980fc47f 100644
--- a/azalea/src/client_impl/mining.rs
+++ b/azalea/src/client_impl/mining.rs
@@ -5,7 +5,7 @@ use crate::Client;
impl Client {
pub fn start_mining(&self, position: BlockPos) {
- let mut ecs = self.ecs.lock();
+ let mut ecs = self.ecs.write();
ecs.write_message(StartMiningBlockEvent {
entity: self.entity,
@@ -17,7 +17,7 @@ impl Client {
/// When enabled, the bot will mine any block that it is looking at if it is
/// reachable.
pub fn left_click_mine(&self, enabled: bool) {
- let mut ecs = self.ecs.lock();
+ let mut ecs = self.ecs.write();
let mut entity_mut = ecs.entity_mut(self.entity);
if enabled {
diff --git a/azalea/src/client_impl/mod.rs b/azalea/src/client_impl/mod.rs
index 36995656..bbbe23e0 100644
--- a/azalea/src/client_impl/mod.rs
+++ b/azalea/src/client_impl/mod.rs
@@ -31,12 +31,11 @@ use azalea_registry::{DataRegistryKeyRef, identifier::Identifier};
use azalea_world::{Instance, InstanceName, MinecraftEntityId, PartialInstance};
use bevy_app::App;
use bevy_ecs::{
- component::Component,
entity::Entity,
resource::Resource,
world::{Mut, World},
};
-use parking_lot::{Mutex, RwLock};
+use parking_lot::{MappedRwLockReadGuard, RwLock};
use tokio::sync::mpsc;
use uuid::Uuid;
@@ -70,11 +69,15 @@ pub struct Client {
/// You probably don't need to access this directly. Note that if you're
/// using a shared world (i.e. a swarm), the ECS will contain all entities
/// in all instances/dimensions.
- pub ecs: Arc<Mutex<World>>,
+ ///
+ /// You can nearly always use [`Self::component`], [`Self::query_self`],
+ /// [`Self::query_entity`], or another one of those related functions to
+ /// access the ECS instead.
+ pub ecs: Arc<RwLock<World>>,
}
pub struct StartClientOpts {
- pub ecs_lock: Arc<Mutex<World>>,
+ pub ecs_lock: Arc<RwLock<World>>,
pub account: Account,
pub connect_opts: ConnectOpts,
pub event_sender: Option<mpsc::UnboundedSender<Event>>,
@@ -141,7 +144,7 @@ impl Client {
/// World, and schedule runner function.
/// You should only use this if you want to change these fields from the
/// defaults, otherwise use [`Client::join`].
- pub fn new(entity: Entity, ecs: Arc<Mutex<World>>) -> Self {
+ pub fn new(entity: Entity, ecs: Arc<RwLock<World>>) -> Self {
Self {
// default our id to 0, it'll be set later
entity,
@@ -209,7 +212,7 @@ impl Client {
let (start_join_callback_tx, mut start_join_callback_rx) =
mpsc::unbounded_channel::<Entity>();
- ecs_lock.lock().write_message(StartJoinServerEvent {
+ ecs_lock.write().write_message(StartJoinServerEvent {
account,
connect_opts,
start_join_callback_tx: Some(start_join_callback_tx),
@@ -221,7 +224,7 @@ impl Client {
if let Some(event_sender) = event_sender {
ecs_lock
- .lock()
+ .write()
.entity_mut(entity)
.insert(LocalPlayerEvents(event_sender));
}
@@ -233,7 +236,7 @@ impl Client {
pub fn write_packet(&self, packet: impl Packet<ServerboundGamePacket>) {
let packet = packet.into_variant();
self.ecs
- .lock()
+ .write()
.commands()
.trigger(SendGamePacketEvent::new(self.entity, packet));
}
@@ -243,7 +246,7 @@ impl Client {
/// The OwnedReadHalf for the TCP connection is in one of the tasks, so it
/// automatically closes the connection when that's dropped.
pub fn disconnect(&self) {
- self.ecs.lock().write_message(DisconnectEvent {
+ self.ecs.write().write_message(DisconnectEvent {
entity: self.entity,
reason: None,
});
@@ -256,58 +259,21 @@ impl Client {
self.query_self::<&mut RawConnection, _>(f)
}
- /// Get a component from this client. This will clone the component and
- /// return it.
- ///
- ///
- /// If the component can't be cloned, try [`Self::query_self`] instead.
- /// If it isn't guaranteed to be present, you can use
- /// [`Self::get_component`] or [`Self::query_self`].
- ///
- ///
- /// You may also use [`Self::ecs`] 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.
- ///
- /// # Examples
- ///
- /// ```
- /// # use azalea_world::InstanceName;
- /// # fn example(client: &azalea::Client) {
- /// let world_name = client.component::<InstanceName>();
- /// # }
- pub fn component<T: Component + Clone>(&self) -> T {
- self.query_self::<&T, _>(|t| t.clone())
- }
-
- /// Get a component from this client, or `None` if it doesn't exist.
- ///
- /// If the component can't be cloned, consider using [`Self::query_self`]
- /// with `Option<&T>` instead.
- ///
- /// You may also have to use [`Self::query_self`] directly.
- pub fn get_component<T: Component + Clone>(&self) -> Option<T> {
- self.query_self::<Option<&T>, _>(|t| t.cloned())
- }
-
/// Get a resource from the ECS. This will clone the resource and return it.
pub fn resource<T: Resource + Clone>(&self) -> T {
- self.ecs.lock().resource::<T>().clone()
+ self.ecs.read().resource::<T>().clone()
}
/// Get a required ECS resource and call the given function with it.
pub fn map_resource<T: Resource, R>(&self, f: impl FnOnce(&T) -> R) -> R {
- let ecs = self.ecs.lock();
+ let ecs = self.ecs.read();
let value = ecs.resource::<T>();
f(value)
}
/// Get an optional ECS resource and call the given function with it.
pub fn map_get_resource<T: Resource, R>(&self, f: impl FnOnce(Option<&T>) -> R) -> R {
- let ecs = self.ecs.lock();
+ let ecs = self.ecs.read();
let value = ecs.get_resource::<T>();
f(value)
}
@@ -354,7 +320,7 @@ impl Client {
/// later.
pub fn position(&self) -> Vec3 {
Vec3::from(
- &self
+ &*self
.get_component::<Position>()
.expect("the client's position hasn't been initialized yet"),
)
@@ -366,7 +332,7 @@ impl Client {
/// This is a shortcut for
/// `self.component::<EntityDimensions>()`.
pub fn dimensions(&self) -> EntityDimensions {
- self.component::<EntityDimensions>()
+ self.component::<EntityDimensions>().clone()
}
/// Get the position of this client's eyes.
@@ -383,7 +349,7 @@ impl Client {
///
/// This is a shortcut for `*bot.component::<Health>()`.
pub fn health(&self) -> f32 {
- *self.component::<Health>()
+ **self.component::<Health>()
}
/// Get the hunger level of this client, which includes both food and
@@ -413,7 +379,7 @@ impl Client {
///
/// This is a shortcut for `*bot.component::<TabList>()`.
pub fn tab_list(&self) -> HashMap<Uuid, PlayerInfo> {
- (*self.component::<TabList>()).clone()
+ (**self.component::<TabList>()).clone()
}
/// Returns the [`GameProfile`] for our client. This contains your username,
@@ -426,12 +392,12 @@ impl Client {
///
/// This as also available from the ECS as [`GameProfileComponent`].
pub fn profile(&self) -> GameProfile {
- (*self.component::<GameProfileComponent>()).clone()
+ (**self.component::<GameProfileComponent>()).clone()
}
/// Returns the attribute values of our player, which can be used to
/// determine things like our movement speed.
- pub fn attributes(&self) -> Attributes {
+ pub fn attributes(&self) -> MappedRwLockReadGuard<'_, Attributes> {
self.component::<Attributes>()
}
diff --git a/azalea/src/client_impl/movement.rs b/azalea/src/client_impl/movement.rs
index b47da9a7..a708e5f6 100644
--- a/azalea/src/client_impl/movement.rs
+++ b/azalea/src/client_impl/movement.rs
@@ -19,7 +19,7 @@ impl Client {
/// Returns whether the player will try to jump next tick.
pub fn jumping(&self) -> bool {
- *self.component::<Jumping>()
+ **self.component::<Jumping>()
}
pub fn set_crouching(&self, crouching: bool) {
@@ -49,7 +49,7 @@ impl Client {
///
/// See [`Self::set_direction`] for more details.
pub fn direction(&self) -> (f32, f32) {
- let look_direction: LookDirection = self.component::<LookDirection>();
+ let look_direction = *self.component::<LookDirection>();
(look_direction.y_rot(), look_direction.x_rot())
}
@@ -71,7 +71,7 @@ impl Client {
/// # }
/// ```
pub fn walk(&self, direction: WalkDirection) {
- let mut ecs = self.ecs.lock();
+ let mut ecs = self.ecs.write();
ecs.write_message(StartWalkEvent {
entity: self.entity,
direction,
@@ -95,7 +95,7 @@ impl Client {
/// # }
/// ```
pub fn sprint(&self, direction: SprintDirection) {
- let mut ecs = self.ecs.lock();
+ let mut ecs = self.ecs.write();
ecs.write_message(StartSprintEvent {
entity: self.entity,
direction,