1
2
Fork 0
mirror of https://github.com/mat-1/azalea.git synced 2025-08-02 06:16:04 +00:00

don't remove LocalEntity from disconnected players, add new debug logs, and make GameProfile clones cheaper

This commit is contained in:
mat 2025-02-24 03:55:29 +00:00
parent 6a5ab34a2d
commit b9767424f3
6 changed files with 105 additions and 37 deletions

View file

@ -1,4 +1,4 @@
use std::collections::HashMap; use std::{collections::HashMap, sync::Arc};
use azalea_buf::AzBuf; use azalea_buf::AzBuf;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -10,7 +10,7 @@ pub struct GameProfile {
pub uuid: Uuid, pub uuid: Uuid,
/// The username of the player. /// The username of the player.
pub name: String, pub name: String,
pub properties: HashMap<String, ProfilePropertyValue>, pub properties: Arc<HashMap<String, ProfilePropertyValue>>,
} }
impl GameProfile { impl GameProfile {
@ -18,7 +18,7 @@ impl GameProfile {
GameProfile { GameProfile {
uuid, uuid,
name, name,
properties: HashMap::new(), properties: Arc::new(HashMap::new()),
} }
} }
} }
@ -38,7 +38,7 @@ impl From<SerializableGameProfile> for GameProfile {
Self { Self {
uuid: value.id, uuid: value.id,
name: value.name, name: value.name,
properties, properties: Arc::new(properties),
} }
} }
} }
@ -59,11 +59,11 @@ pub struct SerializableGameProfile {
impl From<GameProfile> for SerializableGameProfile { impl From<GameProfile> for SerializableGameProfile {
fn from(value: GameProfile) -> Self { fn from(value: GameProfile) -> Self {
let mut properties = Vec::new(); let mut properties = Vec::new();
for (key, value) in value.properties { for (key, value) in &*value.properties {
properties.push(SerializableProfilePropertyValue { properties.push(SerializableProfilePropertyValue {
name: key, name: key.clone(),
value: value.value, value: value.value.clone(),
signature: value.signature, signature: value.signature.clone(),
}); });
} }
Self { Self {
@ -114,7 +114,7 @@ mod tests {
signature: Some("zxcv".to_string()), signature: Some("zxcv".to_string()),
}, },
); );
map map.into()
}, },
} }
); );

View file

@ -334,6 +334,8 @@ impl Client {
metadata: azalea_entity::metadata::PlayerMetadataBundle::default(), metadata: azalea_entity::metadata::PlayerMetadataBundle::default(),
}, },
InConfigState, InConfigState,
// this component is never removed
LocalEntity,
)); ));
Ok((client, rx)) Ok((client, rx))
@ -813,8 +815,6 @@ pub struct JoinedClientBundle {
pub mining: mining::MineBundle, pub mining: mining::MineBundle,
pub attack: attack::AttackBundle, pub attack: attack::AttackBundle,
pub _local_entity: LocalEntity,
} }
/// A marker component for local players that are currently in the /// A marker component for local players that are currently in the

View file

@ -243,6 +243,15 @@ where
pub fn into_split(self) -> (ReadConnection<R>, WriteConnection<W>) { pub fn into_split(self) -> (ReadConnection<R>, WriteConnection<W>) {
(self.reader, self.writer) (self.reader, self.writer)
} }
/// Split the reader and writer into the state-agnostic
/// [`RawReadConnection`] and [`RawWriteConnection`] types.
///
/// This is meant to help with some types of proxies.
#[must_use]
pub fn into_split_raw(self) -> (RawReadConnection, RawWriteConnection) {
(self.reader.raw, self.writer.raw)
}
} }
#[derive(Error, Debug)] #[derive(Error, Debug)]

View file

@ -35,7 +35,7 @@ pub mod write;
/// assert_eq!(addr.host, "localhost"); /// assert_eq!(addr.host, "localhost");
/// assert_eq!(addr.port, 25565); /// assert_eq!(addr.port, 25565);
/// ``` /// ```
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ServerAddress { pub struct ServerAddress {
pub host: String, pub host: String,
pub port: u16, pub port: u16,

View file

@ -80,7 +80,7 @@ impl AzaleaRead for ClientboundPlayerInfoUpdate {
if actions.add_player { if actions.add_player {
let action = AddPlayerAction::azalea_read(buf)?; let action = AddPlayerAction::azalea_read(buf)?;
entry.profile.name = action.name; entry.profile.name = action.name;
entry.profile.properties = action.properties; entry.profile.properties = action.properties.into();
} }
if actions.initialize_chat { if actions.initialize_chat {
let action = InitializeChatAction::azalea_read(buf)?; let action = InitializeChatAction::azalea_read(buf)?;
@ -129,7 +129,7 @@ impl AzaleaWrite for ClientboundPlayerInfoUpdate {
if self.actions.add_player { if self.actions.add_player {
AddPlayerAction { AddPlayerAction {
name: entry.profile.name.clone(), name: entry.profile.name.clone(),
properties: entry.profile.properties.clone(), properties: (*entry.profile.properties).clone(),
} }
.azalea_write(buf)?; .azalea_write(buf)?;
} }

View file

@ -6,7 +6,16 @@ mod chat;
mod events; mod events;
pub mod prelude; pub mod prelude;
use std::{collections::HashMap, future::Future, net::SocketAddr, sync::Arc, time::Duration}; use std::{
collections::{HashMap, hash_map},
future::Future,
net::SocketAddr,
sync::{
Arc,
atomic::{self, AtomicBool},
},
time::Duration,
};
use azalea_client::{ use azalea_client::{
Account, Client, DefaultPlugins, Event, JoinError, StartClientOpts, chat::ChatPacket, Account, Client, DefaultPlugins, Event, JoinError, StartClientOpts, chat::ChatPacket,
@ -19,7 +28,7 @@ use bevy_ecs::{component::Component, entity::Entity, system::Resource, world::Wo
use futures::future::{BoxFuture, join_all}; use futures::future::{BoxFuture, join_all};
use parking_lot::{Mutex, RwLock}; use parking_lot::{Mutex, RwLock};
use tokio::sync::mpsc; use tokio::sync::mpsc;
use tracing::{debug, error}; use tracing::{debug, error, warn};
use crate::{BoxHandleFn, DefaultBotPlugins, HandleFn, JoinOpts, NoState, StartError}; use crate::{BoxHandleFn, DefaultBotPlugins, HandleFn, JoinOpts, NoState, StartError};
@ -471,8 +480,21 @@ where
// bot events // bot events
while let Some((Some(first_event), first_bot)) = bots_rx.recv().await { while let Some((Some(first_event), first_bot)) = bots_rx.recv().await {
if bots_rx.len() > 1_000 {
static WARNED: AtomicBool = AtomicBool::new(false);
if !WARNED.swap(true, atomic::Ordering::Relaxed) {
warn!("the Client Event channel has more than 1000 items!")
}
}
if let Some(handler) = &self.handler { if let Some(handler) = &self.handler {
let first_bot_state = first_bot.component::<S>(); let Some(first_bot_state) = first_bot.get_component::<S>() else {
error!(
"the first bot ({} / {}) is missing the required state component! none of the client handler functions will be called.",
first_bot.profile.name, first_bot.entity
);
continue;
};
let first_bot_entity = first_bot.entity; let first_bot_entity = first_bot.entity;
tokio::spawn((handler)(first_bot, first_event, first_bot_state.clone())); tokio::spawn((handler)(first_bot, first_event, first_bot_state.clone()));
@ -481,9 +503,19 @@ where
let mut states = HashMap::new(); let mut states = HashMap::new();
states.insert(first_bot_entity, first_bot_state); states.insert(first_bot_entity, first_bot_state);
while let Ok((Some(event), bot)) = bots_rx.try_recv() { while let Ok((Some(event), bot)) = bots_rx.try_recv() {
let state = states let state = match states.entry(bot.entity) {
.entry(bot.entity) hash_map::Entry::Occupied(e) => e.into_mut(),
.or_insert_with(|| bot.component::<S>().clone()); hash_map::Entry::Vacant(e) => {
let Some(state) = bot.get_component::<S>() else {
error!(
"one of our bots ({} / {}) is missing the required state component! its client handler function will not be called.",
bot.profile.name, bot.entity
);
continue;
};
e.insert(state)
}
};
tokio::spawn((handler)(bot, event, state.clone())); tokio::spawn((handler)(bot, event, state.clone()));
} }
} }
@ -610,6 +642,8 @@ impl Swarm {
state: S, state: S,
join_opts: &JoinOpts, join_opts: &JoinOpts,
) -> Result<Client, JoinError> { ) -> Result<Client, JoinError> {
debug!("add_with_opts called for account {}", account.username);
let address = join_opts let address = join_opts
.custom_address .custom_address
.clone() .clone()
@ -618,7 +652,7 @@ impl Swarm {
.custom_resolved_address .custom_resolved_address
.unwrap_or_else(|| *self.resolved_address.read()); .unwrap_or_else(|| *self.resolved_address.read());
let (bot, mut rx) = Client::start_client(StartClientOpts { let (bot, rx) = Client::start_client(StartClientOpts {
ecs_lock: self.ecs_lock.clone(), ecs_lock: self.ecs_lock.clone(),
account, account,
address: &address, address: &address,
@ -640,26 +674,51 @@ impl Swarm {
let cloned_bot = bot.clone(); let cloned_bot = bot.clone();
let swarm_tx = self.swarm_tx.clone(); let swarm_tx = self.swarm_tx.clone();
let join_opts = join_opts.clone(); let join_opts = join_opts.clone();
tokio::spawn(async move { tokio::spawn(Self::event_copying_task(
while let Some(event) = rx.recv().await { rx,
// we can't handle events here (since we can't copy the handler), cloned_bots,
// they're handled above in SwarmBuilder::start cloned_bots_tx,
if let Err(e) = cloned_bots_tx.send((Some(event), cloned_bot.clone())) { cloned_bot,
error!("Error sending event to swarm: {e}"); swarm_tx,
} join_opts,
} ));
cloned_bots.lock().remove(&bot.entity);
let account = cloned_bot
.get_component::<Account>()
.expect("bot is missing required Account component");
swarm_tx
.send(SwarmEvent::Disconnect(Box::new(account), join_opts))
.unwrap();
});
Ok(bot) Ok(bot)
} }
async fn event_copying_task(
mut rx: mpsc::UnboundedReceiver<Event>,
cloned_bots: Arc<Mutex<HashMap<Entity, Client>>>,
cloned_bots_tx: mpsc::UnboundedSender<(Option<Event>, Client)>,
cloned_bot: Client,
swarm_tx: mpsc::UnboundedSender<SwarmEvent>,
join_opts: JoinOpts,
) {
while let Some(event) = rx.recv().await {
if rx.len() > 1_000 {
static WARNED: AtomicBool = AtomicBool::new(false);
if !WARNED.swap(true, atomic::Ordering::Relaxed) {
warn!("the client's Event channel has more than 1000 items!")
}
}
// we can't handle events here (since we can't copy the handler),
// they're handled above in SwarmBuilder::start
if let Err(e) = cloned_bots_tx.send((Some(event), cloned_bot.clone())) {
error!("Error sending event to swarm: {e}");
}
}
debug!("client sender ended, removing from cloned_bots and sending SwarmEvent::Disconnect");
cloned_bots.lock().remove(&cloned_bot.entity);
let account = cloned_bot
.get_component::<Account>()
.expect("bot is missing required Account component");
swarm_tx
.send(SwarmEvent::Disconnect(Box::new(account), join_opts))
.unwrap();
}
/// Add a new account to the swarm, retrying if it couldn't join. This will /// Add a new account to the swarm, retrying if it couldn't join. This will
/// run forever until the bot joins or the task is aborted. /// run forever until the bot joins or the task is aborted.
/// ///