diff options
Diffstat (limited to 'azalea/src/client_impl')
| -rw-r--r-- | azalea/src/client_impl/attack.rs | 15 | ||||
| -rw-r--r-- | azalea/src/client_impl/chat.rs | 6 | ||||
| -rw-r--r-- | azalea/src/client_impl/entity_query.rs | 144 | ||||
| -rw-r--r-- | azalea/src/client_impl/interact.rs | 6 | ||||
| -rw-r--r-- | azalea/src/client_impl/inventory.rs | 2 | ||||
| -rw-r--r-- | azalea/src/client_impl/mining.rs | 4 | ||||
| -rw-r--r-- | azalea/src/client_impl/mod.rs | 76 | ||||
| -rw-r--r-- | azalea/src/client_impl/movement.rs | 8 |
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, |
