aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormat <27899617+mat-1@users.noreply.github.com>2025-12-28 04:31:29 -0600
committerGitHub <noreply@github.com>2025-12-28 04:31:29 -0600
commit20c7e27250148f62bab9e7b99e4f0cd6deb82325 (patch)
tree163b312ef4dc72c5c23be923f8ca17cdf2a7278c
parent7ab3b8924f64f7eadb6b8928b6fae73cb06e4c2f (diff)
downloadazalea-drasl-20c7e27250148f62bab9e7b99e4f0cd6deb82325.tar.xz
Change Client::component to return a reference (#298)
* change Client::component to return a reference * write docs * merge main * remove unused parking_lot feature
-rw-r--r--azalea-brigadier/tests/bevy_app_usage.rs18
-rw-r--r--azalea-client/src/client.rs14
-rw-r--r--azalea/examples/testbot/commands/combat.rs8
-rw-r--r--azalea/examples/testbot/commands/debug.rs17
-rw-r--r--azalea/examples/testbot/commands/movement.rs1
-rw-r--r--azalea/examples/testbot/killaura.rs4
-rw-r--r--azalea/examples/testbot/main.rs2
-rw-r--r--azalea/src/bot.rs6
-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
-rw-r--r--azalea/src/container.rs13
-rw-r--r--azalea/src/pathfinder/debug.rs2
-rw-r--r--azalea/src/pathfinder/mod.rs6
-rw-r--r--azalea/src/pathfinder/tests.rs2
-rw-r--r--azalea/src/swarm/builder.rs6
-rw-r--r--azalea/src/swarm/mod.rs21
-rw-r--r--azalea/src/tick_broadcast.rs6
23 files changed, 217 insertions, 170 deletions
diff --git a/azalea-brigadier/tests/bevy_app_usage.rs b/azalea-brigadier/tests/bevy_app_usage.rs
index 876666d5..e0bd8d23 100644
--- a/azalea-brigadier/tests/bevy_app_usage.rs
+++ b/azalea-brigadier/tests/bevy_app_usage.rs
@@ -3,7 +3,7 @@ use std::{mem, ops::Deref, sync::Arc};
use azalea_brigadier::prelude::*;
use bevy_app::App;
use bevy_ecs::{prelude::*, system::RunSystemOnce};
-use parking_lot::Mutex;
+use parking_lot::RwLock;
#[test]
fn bevy_app() {
@@ -89,7 +89,7 @@ impl DispatchStorage {
assert_eq!(result, Ok(0));
// Query the World for the spawned entity
- let mut world = source.lock();
+ let mut world = source.write();
let mut query = world.query_filtered::<(), With<SpawnedEntity>>();
// Ensure only one entity was spawned
@@ -109,7 +109,7 @@ impl DispatchStorage {
assert_eq!(result, Ok(0));
// Query the World for spawned entities
- let mut world = source.lock();
+ let mut world = source.write();
let mut query = world.query_filtered::<(), With<SpawnedEntity>>();
// Ensure three additional entities were spawned
@@ -131,7 +131,7 @@ impl DispatchStorage {
///
/// Spawns an entity with the [`SpawnedEntity`] component.
fn command_spawn_entity(context: &CommandContext<WorldAccessor>) -> i32 {
- context.source.lock().spawn(SpawnedEntity);
+ context.source.write().spawn(SpawnedEntity);
0
}
@@ -143,7 +143,7 @@ impl DispatchStorage {
let num = get_integer(context, "entities").unwrap();
for _ in 0..num {
- context.source.lock().spawn(SpawnedEntity);
+ context.source.write().spawn(SpawnedEntity);
}
0
@@ -159,20 +159,20 @@ impl DispatchStorage {
/// access from inside a [`CommandDispatcher`].
#[derive(Clone)]
struct WorldAccessor {
- world: Arc<Mutex<World>>,
+ world: Arc<RwLock<World>>,
}
impl WorldAccessor {
/// Create a new empty [`WorldAccessor`].
fn empty() -> Self {
Self {
- world: Arc::new(Mutex::new(World::new())),
+ world: Arc::new(RwLock::new(World::new())),
}
}
/// Swap the internal [`World`] with the given one.
fn swap(&mut self, world: &mut World) {
- mem::swap(&mut *self.lock(), world);
+ mem::swap(&mut *self.write(), world);
}
}
@@ -182,7 +182,7 @@ struct SpawnedEntity;
/// Implemented for convenience.
impl Deref for WorldAccessor {
- type Target = Arc<Mutex<World>>;
+ type Target = Arc<RwLock<World>>;
fn deref(&self) -> &Self::Target {
&self.world
}
diff --git a/azalea-client/src/client.rs b/azalea-client/src/client.rs
index 4c45b537..b7d01d58 100644
--- a/azalea-client/src/client.rs
+++ b/azalea-client/src/client.rs
@@ -18,7 +18,7 @@ use bevy_ecs::{
prelude::*,
schedule::{InternedScheduleLabel, LogLevel, ScheduleBuildSettings},
};
-use parking_lot::Mutex;
+use parking_lot::RwLock;
use tokio::{sync::oneshot, time};
use tracing::{info, warn};
@@ -118,7 +118,11 @@ impl Plugin for AzaleaPlugin {
#[doc(hidden)]
pub fn start_ecs_runner(
app: &mut SubApp,
-) -> (Arc<Mutex<World>>, impl FnOnce(), oneshot::Receiver<AppExit>) {
+) -> (
+ Arc<RwLock<World>>,
+ impl FnOnce(),
+ oneshot::Receiver<AppExit>,
+) {
// this block is based on Bevy's default runner:
// https://github.com/bevyengine/bevy/blob/390877cdae7a17095a75c8f9f1b4241fe5047e83/crates/bevy_app/src/schedule_runner.rs#L77-L85
if app.plugins_state() != PluginsState::Cleaned {
@@ -136,7 +140,7 @@ pub fn start_ecs_runner(
// all resources should have been added by now so we can take the ecs from the
// app
- let ecs = Arc::new(Mutex::new(mem::take(app.world_mut())));
+ let ecs = Arc::new(RwLock::new(mem::take(app.world_mut())));
let ecs_clone = ecs.clone();
let outer_schedule_label = *app.update_schedule.as_ref().unwrap();
@@ -157,7 +161,7 @@ pub fn start_ecs_runner(
///
/// Exits when we receive an `AppExit` event.
async fn run_schedule_loop(
- ecs: Arc<Mutex<World>>,
+ ecs: Arc<RwLock<World>>,
outer_schedule_label: InternedScheduleLabel,
) -> AppExit {
let mut last_update: Option<Instant> = None;
@@ -181,7 +185,7 @@ async fn run_schedule_loop(
}
last_update = Some(now);
- let mut ecs = ecs.lock();
+ let mut ecs = ecs.write();
// if last tick is None or more than 50ms ago, run the GameTick schedule
ecs.run_schedule(outer_schedule_label);
diff --git a/azalea/examples/testbot/commands/combat.rs b/azalea/examples/testbot/commands/combat.rs
index b440b3ac..af147e96 100644
--- a/azalea/examples/testbot/commands/combat.rs
+++ b/azalea/examples/testbot/commands/combat.rs
@@ -2,6 +2,7 @@ use azalea::brigadier::prelude::*;
use parking_lot::Mutex;
use super::{CommandSource, Ctx};
+use crate::State;
pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
commands.register(
@@ -9,12 +10,7 @@ pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
let enabled = get_bool(ctx, "enabled").unwrap();
let source = ctx.source.lock();
let bot = source.bot.clone();
- {
- let mut ecs = bot.ecs.lock();
- let mut entity = ecs.entity_mut(bot.entity);
- let mut state = entity.get_mut::<crate::State>().unwrap();
- state.killaura = enabled
- }
+ bot.query_self::<&mut State, _>(|mut state| state.killaura = enabled);
source.reply(if enabled {
"Enabled killaura"
} else {
diff --git a/azalea/examples/testbot/commands/debug.rs b/azalea/examples/testbot/commands/debug.rs
index edadd697..711c3260 100644
--- a/azalea/examples/testbot/commands/debug.rs
+++ b/azalea/examples/testbot/commands/debug.rs
@@ -110,7 +110,7 @@ pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
let hit_result = source.bot.component::<HitResultComponent>();
- match &*hit_result {
+ match &**hit_result {
HitResult::Block(r) => {
if r.miss {
source.reply("I'm not looking at anything");
@@ -121,7 +121,7 @@ pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
source.reply(format!("I'm looking at {block:?} at {block_pos:?}"));
}
HitResult::Entity(r) => {
- let entity_kind = *source.bot.entity_component::<EntityKindComponent>(r.entity);
+ let entity_kind = **source.bot.entity_component::<EntityKindComponent>(r.entity);
source.reply(format!(
"I'm looking at {entity_kind} ({:?}) at {}",
r.entity, r.location
@@ -180,7 +180,7 @@ pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
"is_path_partial: {}, path.len: {}, queued_path.len: {}",
executing_path.is_path_partial,
executing_path.path.len(),
- if let Some(queued) = executing_path.queued_path {
+ if let Some(queued) = &executing_path.queued_path {
queued.len().to_string()
} else {
"n/a".to_owned()
@@ -261,9 +261,7 @@ pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
thread::sleep(Duration::from_secs(1));
// dump the ecs
- let mut ecs = ecs.lock();
-
-
+ let mut ecs = ecs.write();
let report_path = env::temp_dir().join("azalea-ecs-leak-report.txt");
let mut report = File::create(&report_path).unwrap();
@@ -357,7 +355,12 @@ pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
thread::spawn(move || {
thread::sleep(Duration::from_secs(1));
- source.lock().bot.ecs.lock().write_message(AppExit::Success);
+ source
+ .lock()
+ .bot
+ .ecs
+ .write()
+ .write_message(AppExit::Success);
});
1
diff --git a/azalea/examples/testbot/commands/movement.rs b/azalea/examples/testbot/commands/movement.rs
index a4ac787a..6f43a021 100644
--- a/azalea/examples/testbot/commands/movement.rs
+++ b/azalea/examples/testbot/commands/movement.rs
@@ -28,6 +28,7 @@ pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) {
source.reply("I can't see you!");
return 0;
};
+ let position = position.clone();
source.reply("ok");
source
.bot
diff --git a/azalea/examples/testbot/killaura.rs b/azalea/examples/testbot/killaura.rs
index 093495ea..e3d9091c 100644
--- a/azalea/examples/testbot/killaura.rs
+++ b/azalea/examples/testbot/killaura.rs
@@ -17,9 +17,9 @@ pub fn tick(bot: Client, state: State) -> anyhow::Result<()> {
let mut nearest_entity = None;
let mut nearest_distance = f64::INFINITY;
let bot_position = bot.eye_position();
- let bot_instance_name = bot.component::<InstanceName>();
+ let bot_instance_name = bot.component::<InstanceName>().clone();
{
- let mut ecs = bot.ecs.lock();
+ let mut ecs = bot.ecs.write();
let mut query = ecs
.query_filtered::<(Entity, &Position, &InstanceName), (
With<AbstractMonster>,
diff --git a/azalea/examples/testbot/main.rs b/azalea/examples/testbot/main.rs
index aa4a7b99..9889e250 100644
--- a/azalea/examples/testbot/main.rs
+++ b/azalea/examples/testbot/main.rs
@@ -129,7 +129,7 @@ async fn handle(bot: Client, event: azalea::Event, state: State) -> anyhow::Resu
});
if swarm.args.pathfinder_debug_particles {
bot.ecs
- .lock()
+ .write()
.entity_mut(bot.entity)
.insert(PathfinderDebugParticles);
}
diff --git a/azalea/src/bot.rs b/azalea/src/bot.rs
index 73b225b2..d3527938 100644
--- a/azalea/src/bot.rs
+++ b/azalea/src/bot.rs
@@ -82,7 +82,7 @@ fn stop_jumping(mut query: Query<(&mut Jumping, &mut Bot)>) {
impl Client {
/// Queue a jump for the next tick.
pub fn jump(&self) {
- let mut ecs = self.ecs.lock();
+ let mut ecs = self.ecs.write();
ecs.write_message(JumpEvent {
entity: self.entity,
});
@@ -92,7 +92,7 @@ impl Client {
///
/// To look at the center of a block, you should call [`BlockPos::center`].
pub fn look_at(&self, position: Vec3) {
- let mut ecs = self.ecs.lock();
+ let mut ecs = self.ecs.write();
ecs.write_message(LookAtEvent {
entity: self.entity,
position,
@@ -136,7 +136,7 @@ impl Client {
let mut receiver = self.get_tick_broadcaster();
while receiver.recv().await.is_ok() {
- let ecs = self.ecs.lock();
+ let ecs = self.ecs.read();
if ecs.get::<Mining>(self.entity).is_none() {
break;
}
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,
diff --git a/azalea/src/container.rs b/azalea/src/container.rs
index 3d5f6f01..8b46994e 100644
--- a/azalea/src/container.rs
+++ b/azalea/src/container.rs
@@ -81,7 +81,7 @@ impl Client {
}
self.ecs
- .lock()
+ .write()
.entity_mut(self.entity)
.insert(WaitingForInventoryOpen);
self.block_interact(pos);
@@ -102,7 +102,7 @@ impl Client {
let mut ticks = self.get_tick_broadcaster();
let mut elapsed_ticks = 0;
while ticks.recv().await.is_ok() {
- let ecs = self.ecs.lock();
+ let ecs = self.ecs.read();
if ecs.get::<WaitingForInventoryOpen>(self.entity).is_none() {
break;
}
@@ -115,7 +115,7 @@ impl Client {
}
}
- let ecs = self.ecs.lock();
+ let ecs = self.ecs.read();
let inventory = ecs.get::<Inventory>(self.entity).expect("no inventory");
if inventory.id == 0 {
None
@@ -136,8 +136,7 @@ impl Client {
/// sending any packets, use [`Client::menu`], [`Menu::player_slots_range`],
/// and [`Menu::slots`].
pub fn open_inventory(&self) -> Option<ContainerHandle> {
- let ecs = self.ecs.lock();
- let inventory = ecs.get::<Inventory>(self.entity).expect("no inventory");
+ let inventory = self.component::<Inventory>();
if inventory.id == 0 {
Some(ContainerHandle::new(0, self.clone()))
} else {
@@ -188,7 +187,7 @@ impl ContainerHandleRef {
}
pub fn close(&self) {
- self.client.ecs.lock().trigger(CloseContainerEvent {
+ self.client.ecs.write().trigger(CloseContainerEvent {
entity: self.client.entity,
id: self.id,
});
@@ -285,7 +284,7 @@ impl ContainerHandleRef {
/// action.
pub fn click(&self, operation: impl Into<ClickOperation>) {
let operation = operation.into();
- self.client.ecs.lock().trigger(ContainerClickEvent {
+ self.client.ecs.write().trigger(ContainerClickEvent {
entity: self.client.entity,
window_id: self.id,
operation,
diff --git a/azalea/src/pathfinder/debug.rs b/azalea/src/pathfinder/debug.rs
index f8dc1193..25f82946 100644
--- a/azalea/src/pathfinder/debug.rs
+++ b/azalea/src/pathfinder/debug.rs
@@ -22,7 +22,7 @@ use super::ExecutingPath;
/// match event {
/// azalea::Event::Init => {
/// bot.ecs
-/// .lock()
+/// .write()
/// .entity_mut(bot.entity)
/// .insert(PathfinderDebugParticles);
/// }
diff --git a/azalea/src/pathfinder/mod.rs b/azalea/src/pathfinder/mod.rs
index 155261cc..8a6e5ef2 100644
--- a/azalea/src/pathfinder/mod.rs
+++ b/azalea/src/pathfinder/mod.rs
@@ -242,17 +242,17 @@ impl PathfinderClientExt for Client {
}
fn start_goto_with_opts(&self, goal: impl Goal + 'static, opts: PathfinderOpts) {
self.ecs
- .lock()
+ .write()
.write_message(GotoEvent::new(self.entity, goal, opts));
}
fn stop_pathfinding(&self) {
- self.ecs.lock().write_message(StopPathfindingEvent {
+ self.ecs.write().write_message(StopPathfindingEvent {
entity: self.entity,
force: false,
});
}
fn force_stop_pathfinding(&self) {
- self.ecs.lock().write_message(StopPathfindingEvent {
+ self.ecs.write().write_message(StopPathfindingEvent {
entity: self.entity,
force: true,
});
diff --git a/azalea/src/pathfinder/tests.rs b/azalea/src/pathfinder/tests.rs
index 6f4f7cb7..0297e425 100644
--- a/azalea/src/pathfinder/tests.rs
+++ b/azalea/src/pathfinder/tests.rs
@@ -94,7 +94,7 @@ pub fn wait_until_bot_starts_moving(simulation: &mut Simulation) {
let start_time = Instant::now();
while simulation.position() == start_pos
&& !simulation.is_mining()
- && start_time.elapsed() < Duration::from_millis(500)
+ && start_time.elapsed() < Duration::from_millis(1000)
{
simulation.tick();
thread::yield_now();
diff --git a/azalea/src/swarm/builder.rs b/azalea/src/swarm/builder.rs
index 860e47be..4312a485 100644
--- a/azalea/src/swarm/builder.rs
+++ b/azalea/src/swarm/builder.rs
@@ -448,7 +448,7 @@ where
let (ecs_lock, start_running_systems, appexit_rx) = start_ecs_runner(&mut self.app);
let swarm = Swarm {
- ecs_lock: ecs_lock.clone(),
+ ecs: ecs_lock.clone(),
address: Arc::new(RwLock::new(address)),
instance_container,
@@ -460,7 +460,7 @@ where
// run the main schedule so the startup systems run
{
- let mut ecs = ecs_lock.lock();
+ let mut ecs = ecs_lock.write();
ecs.insert_resource(swarm.clone());
ecs.insert_resource(self.swarm_state.clone());
if let Some(reconnect_after) = self.reconnect_after {
@@ -545,7 +545,7 @@ where
if let Some(handler) = &self.handler {
let ecs_mutex = first_bot.ecs.clone();
- let mut ecs = ecs_mutex.lock();
+ let mut ecs = ecs_mutex.write();
let mut query = ecs.query::<Option<&S>>();
let Ok(Some(first_bot_state)) = query.get(&ecs, first_bot.entity) else {
error!(
diff --git a/azalea/src/swarm/mod.rs b/azalea/src/swarm/mod.rs
index 48ac20a3..90fcf4dd 100644
--- a/azalea/src/swarm/mod.rs
+++ b/azalea/src/swarm/mod.rs
@@ -20,7 +20,7 @@ use bevy_app::{PluginGroup, PluginGroupBuilder};
use bevy_ecs::prelude::*;
pub use builder::SwarmBuilder;
use futures::future::BoxFuture;
-use parking_lot::{Mutex, RwLock};
+use parking_lot::RwLock;
use tokio::{sync::mpsc, task};
use tracing::{debug, error, warn};
@@ -37,7 +37,12 @@ use crate::{Client, JoinOpts, client_impl::StartClientOpts};
/// removed with [`Client::disconnect`].
#[derive(Clone, Resource)]
pub struct Swarm {
- pub ecs_lock: Arc<Mutex<World>>,
+ /// A way to directly access the ECS.
+ ///
+ /// This will not work if called within a system, as the ECS is already
+ /// locked.
+ #[doc(alias = "ecs_lock")] // former type name
+ pub ecs: Arc<RwLock<World>>,
// the address is public and mutable so plugins can change it
pub address: Arc<RwLock<ResolvedAddr>>,
@@ -176,7 +181,7 @@ impl Swarm {
let (tx, rx) = mpsc::unbounded_channel();
let client = Client::start_client(StartClientOpts {
- ecs_lock: self.ecs_lock.clone(),
+ ecs_lock: self.ecs.clone(),
account: account.clone(),
connect_opts: ConnectOpts {
address,
@@ -188,7 +193,7 @@ impl Swarm {
.await;
// add the state to the client
{
- let mut ecs = self.ecs_lock.lock();
+ let mut ecs = self.ecs.write();
ecs.entity_mut(client.entity).insert(state);
}
@@ -251,9 +256,7 @@ impl Swarm {
"Sending SwarmEvent::Disconnect due to receiving an Event::Disconnect from client {}",
bot.entity
);
- let account = bot
- .get_component::<Account>()
- .expect("bot is missing required Account component");
+ let account = bot.component::<Account>().clone();
swarm_tx
.send(SwarmEvent::Disconnect(
Box::new(account),
@@ -284,7 +287,7 @@ impl Swarm {
///
/// [`LocalEntity`]: azalea_entity::LocalEntity
pub fn client_entities(&self) -> Box<[Entity]> {
- let mut ecs = self.ecs_lock.lock();
+ let mut ecs = self.ecs.write();
let mut query = ecs.query_filtered::<Entity, With<LocalEntity>>();
query.iter(&ecs).collect::<Box<[Entity]>>()
}
@@ -312,7 +315,7 @@ impl IntoIterator for Swarm {
client_entities
.into_iter()
- .map(|entity| Client::new(entity, self.ecs_lock.clone()))
+ .map(|entity| Client::new(entity, self.ecs.clone()))
.collect::<Box<[Client]>>()
.into_iter()
}
diff --git a/azalea/src/tick_broadcast.rs b/azalea/src/tick_broadcast.rs
index 479466e2..a082e87e 100644
--- a/azalea/src/tick_broadcast.rs
+++ b/azalea/src/tick_broadcast.rs
@@ -65,7 +65,7 @@ impl Client {
/// # async fn example(bot: &mut azalea::Client) {
/// let mut ticks = bot.get_tick_broadcaster();
/// while ticks.recv().await.is_ok() {
- /// let ecs = bot.ecs.lock();
+ /// let ecs = bot.ecs.read();
/// if ecs.get::<WaitingForInventoryOpen>(bot.entity).is_none() {
/// break;
/// }
@@ -73,7 +73,7 @@ impl Client {
/// # }
/// ```
pub fn get_tick_broadcaster(&self) -> tokio::sync::broadcast::Receiver<()> {
- let ecs = self.ecs.lock();
+ let ecs = self.ecs.read();
let tick_broadcast = ecs.resource::<TickBroadcast>();
tick_broadcast.subscribe()
}
@@ -86,7 +86,7 @@ impl Client {
/// This is useful if you're sending an ECS event and want to make sure it's
/// been handled before continuing.
pub fn get_update_broadcaster(&self) -> tokio::sync::broadcast::Receiver<()> {
- let ecs = self.ecs.lock();
+ let ecs = self.ecs.read();
let update_broadcast = ecs.resource::<UpdateBroadcast>();
update_broadcast.subscribe()
}