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

make swarm use builder

still have to fix some stuff and make client use builder
This commit is contained in:
mat 2023-01-22 17:07:24 -06:00
parent f8230e40af
commit 8940e90863
15 changed files with 206 additions and 168 deletions

1
Cargo.lock generated
View file

@ -176,6 +176,7 @@ dependencies = [
"azalea-world",
"bevy_app",
"bevy_ecs",
"derive_more",
"env_logger",
"futures",
"iyes_loopless",

View file

@ -37,7 +37,10 @@ use azalea_world::{
entity::Entity, EntityInfos, EntityPlugin, Local, PartialWorld, World, WorldContainer,
};
use bevy_app::App;
use bevy_ecs::schedule::{IntoSystemDescriptor, Schedule, Stage, SystemSet};
use bevy_ecs::{
prelude::Component,
schedule::{IntoSystemDescriptor, Schedule, Stage, SystemSet},
};
use bevy_time::TimePlugin;
use iyes_loopless::prelude::*;
use log::{debug, error};
@ -165,11 +168,7 @@ impl Client {
let (conn, game_profile) = Self::handshake(conn, account, address).await?;
let (read_conn, write_conn) = conn.into_split();
// The buffer has to be 1 to avoid a bug where if it lags events are
// received a bit later instead of the instant they were fired.
// That bug especially causes issues with the pathfinder.
let (tx, rx) = mpsc::unbounded_channel();
tx.send(Event::Init).unwrap();
let mut ecs = ecs_lock.lock();
@ -214,8 +213,6 @@ impl Client {
LocalPlayerEvents(tx),
));
// just start up the game loop and we're ready!
Ok((client, rx))
}
@ -366,6 +363,12 @@ impl Client {
self.query::<&mut LocalPlayer>(ecs)
}
/// Get a component from the client. This will clone the component and
/// return it.
pub fn component<T: Component + Clone>(&self) -> T {
self.query::<&T>(&mut self.ecs.lock()).clone()
}
/// Get a reference to our (potentially shared) world.
///
/// This gets the [`WeakWorld`] from our world container. If it's a normal

View file

@ -33,7 +33,7 @@ impl Client {
/// |profile: &&GameProfileComponent| profile.name == sender,
/// );
/// if let Some(entity) = entity {
/// let position = bot.entity_components::<Position>(entity);
/// let position = bot.entity_component::<Position>(entity);
/// }
/// ```
pub fn entity_by<F: ReadOnlyWorldQuery, Q: ReadOnlyWorldQuery>(
@ -45,7 +45,7 @@ impl Client {
/// 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.
pub fn entity_components<Q: Component + Clone>(&mut self, entity: Entity) -> Q {
pub fn entity_component<Q: Component + Clone>(&mut self, entity: Entity) -> Q {
let mut ecs = self.ecs.lock();
let mut q = ecs.query::<&Q>();
let components = q

View file

@ -24,7 +24,7 @@ pub mod ping;
mod player;
pub use account::Account;
pub use bevy_ecs as ecs;
pub use bevy_ecs;
pub use client::{init_ecs_app, start_ecs, ChatPacket, Client, ClientInformation, JoinError};
pub use events::Event;
pub use local_player::{GameProfileComponent, LocalPlayer};

View file

@ -14,7 +14,10 @@ use thiserror::Error;
use tokio::{sync::mpsc, task::JoinHandle};
use uuid::Uuid;
use crate::{events::{Event, LocalPlayerEvents}, ClientInformation, PlayerInfo, WalkDirection};
use crate::{
events::{Event, LocalPlayerEvents},
ClientInformation, PlayerInfo, WalkDirection,
};
/// A player that you control that is currently in a Minecraft server.
#[derive(Component)]

View file

@ -8,32 +8,33 @@ version = "0.5.0"
[package.metadata.release]
pre-release-replacements = [
{ file = "src/lib.rs", search = "//! `azalea = \"[a-z0-9\\.-]+\"`", replace = "//! `azalea = \"{{version}}\"`" },
{file = "src/lib.rs", search = "//! `azalea = \"[a-z0-9\\.-]+\"`", replace = "//! `azalea = \"{{version}}\"`"},
]
[dependencies]
anyhow = "^1.0.65"
async-trait = "0.1.58"
azalea-block = { version = "0.5.0", path = "../azalea-block" }
azalea-chat = { version = "0.5.0", path = "../azalea-chat" }
azalea-client = { version = "0.5.0", path = "../azalea-client" }
azalea-core = { version = "0.5.0", path = "../azalea-core" }
azalea-physics = { version = "0.5.0", path = "../azalea-physics" }
azalea-protocol = { version = "0.5.0", path = "../azalea-protocol" }
azalea-world = { version = "0.5.0", path = "../azalea-world" }
azalea-registry = { version = "0.5.0", path = "../azalea-registry" }
azalea-block = {version = "0.5.0", path = "../azalea-block"}
azalea-chat = {version = "0.5.0", path = "../azalea-chat"}
azalea-client = {version = "0.5.0", path = "../azalea-client"}
azalea-core = {version = "0.5.0", path = "../azalea-core"}
azalea-physics = {version = "0.5.0", path = "../azalea-physics"}
azalea-protocol = {version = "0.5.0", path = "../azalea-protocol"}
azalea-registry = {version = "0.5.0", path = "../azalea-registry"}
azalea-world = {version = "0.5.0", path = "../azalea-world"}
bevy_app = {version = "0.9.1", default-features = false}
bevy_ecs = {version = "0.9.1", default-features = false}
derive_more = {version = "0.99.17", features = ["deref", "deref_mut"]}
futures = "0.3.25"
iyes_loopless = "0.9.1"
log = "0.4.17"
nohash-hasher = "0.2.0"
num-traits = "0.2.15"
parking_lot = { version = "^0.12.1", features = ["deadlock_detection"] }
parking_lot = {version = "^0.12.1", features = ["deadlock_detection"]}
priority-queue = "1.3.0"
thiserror = "^1.0.37"
tokio = "^1.23.1"
uuid = "1.2.2"
bevy_app = { version = "0.9.1", default-features = false }
bevy_ecs = { version = "0.9.1", default-features = false }
iyes_loopless = "0.9.1"
[dev-dependencies]
anyhow = "^1.0.65"

View file

@ -18,7 +18,7 @@ async fn main() {
.unwrap();
}
#[derive(Default, Clone)]
#[derive(Default, Clone, Component)]
pub struct State {}
async fn handle(bot: Client, event: Event, _state: State) -> anyhow::Result<()> {

View file

@ -1,6 +1,7 @@
//! Automatically eat when we get hungry.
use async_trait::async_trait;
use azalea::prelude::*;
use azalea::{Client, Event};
use parking_lot::Mutex;
use std::sync::Arc;
@ -10,7 +11,7 @@ pub struct Plugin {
pub state: State,
}
#[derive(Default, Clone)]
#[derive(Default, Clone, Component)]
pub struct State {}
#[async_trait]

View file

@ -22,16 +22,17 @@
//! For faster compile times, make a `.cargo/config.toml` file in your project
//! and copy
//! [this file](https://github.com/mat-1/azalea/blob/main/.cargo/config.toml)
//! into it.
//! into it. You may have to install the LLD linker.
//!
//! For faster performance in debug mode, add
//! For faster performance in debug mode, add the following code to your
//! Cargo.toml:
//! ```toml
//! [profile.dev]
//! opt-level = 1
//! [profile.dev.package."*"]
//! opt-level = 3
//! ```
//! to your Cargo.toml. You may have to install the LLD linker.
//!
//!
//! # Examples
//!
@ -58,7 +59,7 @@
//! .unwrap();
//! }
//!
//! #[derive(Default, Clone)]
//! #[derive(Default, Clone, Component)]
//! pub struct State {}
//!
//! async fn handle(bot: Client, event: Event, state: State) -> anyhow::Result<()> {

View file

@ -3,5 +3,6 @@
pub use crate::bot::BotClientExt;
pub use crate::pathfinder::PathfinderClientExt;
pub use crate::{plugins, swarm_plugins, Plugin};
pub use azalea_client::{Account, Client, Event};
pub use bevy_ecs;
pub use bevy_ecs::component::Component;

View file

@ -1,5 +1,5 @@
use crate::HandleFn;
use azalea_client::{Account, Client, Plugins};
use azalea_client::{Account, Client};
use azalea_protocol::ServerAddress;
use std::future::Future;
use thiserror::Error;
@ -41,14 +41,6 @@ where
pub address: A,
/// The account that's going to join the server.
pub account: Account,
/// The plugins that are going to be used. Plugins are external crates that
/// add extra functionality to Azalea. You should use the [`plugins`] macro
/// for this field.
///
/// ```rust,no_run
/// plugins![azalea_pathfinder::Plugin]
/// ```
pub plugins: Plugins,
/// A struct that contains the data that you want your bot to remember
/// across events.
///
@ -114,16 +106,9 @@ pub async fn start<
let (bot, mut rx) = Client::join(&options.account, address).await?;
let plugins = options.plugins;
let state = options.state;
while let Some(event) = rx.recv().await {
let cloned_plugins = (*bot.plugins).clone();
for plugin in cloned_plugins.into_iter() {
tokio::spawn(plugin.handle(event.clone(), bot.clone()));
}
tokio::spawn((options.handle)(bot.clone(), event.clone(), state.clone()));
}

View file

@ -13,24 +13,22 @@
// in Swarm that's set to the smallest index of all the bots, and we remove all
// messages from the queue that are before that index.
use crate::{Swarm, SwarmEvent};
use async_trait::async_trait;
use azalea_client::{packet_handling::ChatReceivedEvent, ChatPacket, Client, Event, LocalPlayer};
use azalea_client::{packet_handling::ChatReceivedEvent, ChatPacket, LocalPlayer};
use bevy_ecs::{
prelude::{Component, Entity, EventReader, EventWriter},
query::{Added, Without},
schedule::SystemSet,
system::{Commands, Query, Res, ResMut, Resource},
};
use parking_lot::Mutex;
use std::{collections::VecDeque, sync::Arc};
use tokio::sync::broadcast;
use std::collections::VecDeque;
use crate::{Swarm, SwarmEvent};
#[derive(Clone)]
pub struct Plugin;
impl bevy_app::Plugin for Plugin {
fn build(&self, app: &mut bevy_app::App) {
app.add_system(chat_listener)
app.add_event::<NewChatMessageEvent>()
.add_system(chat_listener)
.add_system(add_default_client_state)
.add_system(update_min_index_and_shrink_queue)
.insert_resource(GlobalChatState {
@ -68,13 +66,13 @@ pub struct GlobalChatState {
}
fn chat_listener(
query: Query<&ClientChatState>,
events: EventReader<ChatReceivedEvent>,
mut query: Query<&mut ClientChatState>,
mut events: EventReader<ChatReceivedEvent>,
mut global_chat_state: ResMut<GlobalChatState>,
new_chat_messages_events: EventWriter<NewChatMessageEvent>,
mut new_chat_messages_events: EventWriter<NewChatMessageEvent>,
) {
for event in events.iter() {
if let Ok(client_chat_state) = query.get(event.entity) {
if let Ok(mut client_chat_state) = query.get_mut(event.entity) {
// When a bot receives a chat messages, it looks into the queue to find the
// earliest instance of the message content that's after the bot's chat index.
// If it finds it, then its personal index is simply updated. Otherwise, fire
@ -111,7 +109,7 @@ fn chat_listener(
if !found {
// didn't find the message, so fire the swarm event and add to the queue
new_chat_messages_events.send(NewChatMessageEvent(event.packet.clone()));
global_chat_state.chat_queue.push_back(event.packet);
global_chat_state.chat_queue.push_back(event.packet.clone());
client_chat_state.chat_index =
global_chat_state.chat_queue.len() + global_chat_state.chat_min_index;
}
@ -119,38 +117,17 @@ fn chat_listener(
}
}
// impl GlobalChatState {
// async fn start<S>(self, swarm: Swarm)
// where
// S: Send + Sync + Clone + 'static,
// {
// // it should never be locked unless we reused the same plugin for two
// swarms // (bad)
// let mut rx = self.rx.lock().await;
// while let Ok(m) = rx.recv().await {
// swarm.swarm_tx.send(SwarmEvent::Chat(m)).unwrap();
// let bot_states = swarm
// .bot_datas
// .lock()
// .iter()
// .map(|(bot, _)| {
// bot.plugins
// .get::<ClientChatState>()
// .expect("Chat plugin not installed")
// .clone()
// })
// .collect::<Vec<_>>();
// self.handle_new_chat_message(&bot_states);
// }
// }
// }
fn update_min_index_and_shrink_queue(
query: Query<&ClientChatState>,
mut global_chat_state: ResMut<GlobalChatState>,
events: EventReader<NewChatMessageEvent>,
mut events: EventReader<NewChatMessageEvent>,
swarm: Res<Swarm>,
) {
for event in events.iter() {
swarm
.swarm_tx
.send(SwarmEvent::Chat(event.0.clone()))
.unwrap();
// To make sure the queue doesn't grow too large, we keep a `chat_min_index`
// in Swarm that's set to the smallest index of all the bots, and we remove all
// messages from the queue that are before that index.
@ -176,7 +153,7 @@ fn update_min_index_and_shrink_queue(
}
// update the min index
self.chat_min_index = new_chat_min_index;
global_chat_state.chat_min_index = new_chat_min_index;
}
}

View file

@ -0,0 +1,43 @@
use azalea_client::LocalPlayer;
use azalea_world::entity::MinecraftEntityId;
use bevy_ecs::{
prelude::EventWriter,
system::{Query, ResMut, Resource},
};
use derive_more::{Deref, DerefMut};
pub struct Plugin;
impl bevy_app::Plugin for Plugin {
fn build(&self, app: &mut bevy_app::App) {
app.add_event::<SwarmReadyEvent>()
.add_system(check_ready)
.init_resource::<IsSwarmReady>();
}
}
/// All the bots from the swarm are now in the world.
pub struct SwarmReadyEvent;
#[derive(Default, Resource, Deref, DerefMut)]
struct IsSwarmReady(bool);
fn check_ready(
query: Query<(&LocalPlayer, Option<&MinecraftEntityId>)>,
mut is_swarm_ready: ResMut<IsSwarmReady>,
mut ready_events: EventWriter<SwarmReadyEvent>,
) {
// if we already know the swarm is ready, do nothing
if **is_swarm_ready {
return;
}
// if all the players are in the world, we're ready
for (player, entity_id) in query.iter() {
if entity_id.is_none() {
return;
}
}
// all the players are in the world, so we're ready
**is_swarm_ready = true;
ready_events.send(SwarmReadyEvent);
}

View file

@ -1,5 +1,7 @@
/// Swarms are a way to conveniently control many bots.
//! Swarms are a way to conveniently control many bots.
mod chat;
mod events;
use crate::HandleFn;
use azalea_client::{init_ecs_app, start_ecs, Account, ChatPacket, Client, Event, JoinError};
@ -10,7 +12,7 @@ use azalea_protocol::{
};
use azalea_world::WorldContainer;
use bevy_app::Plugin;
use bevy_ecs::system::Resource;
use bevy_ecs::{prelude::Component, system::Resource};
use futures::future::join_all;
use log::error;
use parking_lot::{Mutex, RwLock};
@ -29,12 +31,14 @@ use tokio::sync::mpsc;
/// [`azalea::start_swarm`]: fn.start_swarm.html
#[derive(Clone, Resource)]
pub struct Swarm {
pub ecs: Arc<Mutex<bevy_ecs::world::World>>,
pub ecs_lock: Arc<Mutex<bevy_ecs::world::World>>,
bots: Arc<Mutex<Vec<Client>>>,
// bot_datas: Arc<Mutex<Vec<(Client, S)>>>,
resolved_address: SocketAddr,
address: ServerAddress,
pub world_container: Arc<RwLock<WorldContainer>>,
pub ecs_lock: Arc<Mutex<bevy_ecs::world::World>>,
bots_tx: mpsc::UnboundedSender<(Option<Event>, Client)>,
swarm_tx: mpsc::UnboundedSender<SwarmEvent>,
@ -72,10 +76,10 @@ where
}
impl<S, SS, Fut, SwarmFut> SwarmBuilder<S, SS, Fut, SwarmFut>
where
Fut: Future<Output = Result<(), anyhow::Error>>,
SwarmFut: Future<Output = Result<(), anyhow::Error>>,
S: Default + Send + Sync + Clone + 'static,
SS: Default + Send + Sync + Clone + 'static,
Fut: Future<Output = Result<(), anyhow::Error>> + Send + 'static,
SwarmFut: Future<Output = Result<(), anyhow::Error>> + Send + 'static,
S: Default + Send + Sync + Clone + Component + 'static,
SS: Default + Send + Sync + Clone + Component + 'static,
{
/// Start creating the swarm.
pub fn new() -> Self {
@ -91,6 +95,7 @@ where
swarm_handler: None,
join_delay: None,
}
.add_default_swarm_plugins()
}
/// Add a vec of [`Account`]s to the swarm.
@ -98,25 +103,26 @@ where
/// Use [`Self::add_account`] to only add one account. If you want the
/// clients to have different default states, add them one at a time with
/// [`Self::add_account_with_state`].
pub fn add_accounts(&mut self, accounts: Vec<Account>) {
pub fn add_accounts(mut self, accounts: Vec<Account>) -> Self {
for account in accounts {
self.add_account(account);
self = self.add_account(account);
}
self
}
/// Add a single new [`Account`] to the swarm. Use [`add_accounts`] to add
/// multiple accounts at a time.
///
/// This will make the state for this client be the default, use
/// [`Self::add_account_with_state`] to avoid that.
pub fn add_account(&mut self, account: Account) {
self.accounts.push(account);
self.states.push(S::default());
pub fn add_account(mut self, account: Account) -> Self {
self.add_account_with_state(account, S::default())
}
/// Add an account with a custom initial state. Use just
/// [`Self::add_account`] to use the Default implementation for the state.
pub fn add_account_with_state(&mut self, account: Account, state: S) {
pub fn add_account_with_state(mut self, account: Account, state: S) -> Self {
self.accounts.push(account);
self.states.push(state);
self
}
/// Set the function that's called every time a bot receives an [`Event`].
@ -125,8 +131,9 @@ where
/// You can only have one client handler, calling this again will replace
/// the old client handler function (you can have a client handler and swarm
/// handler separately though).
pub fn set_handler(&mut self, handler: HandleFn<Fut, S>) {
pub fn set_handler(mut self, handler: HandleFn<Fut, S>) -> Self {
self.handler = Some(handler);
self
}
/// Set the function that's called every time the swarm receives a
/// [`SwarmEvent`]. This is the way to handle global swarm events.
@ -134,15 +141,32 @@ where
/// You can only have one swarm handler, calling this again will replace
/// the old swarm handler function (you can have a client handler and swarm
/// handler separately though).
pub fn set_swarm_handler(&mut self, handler: SwarmHandleFn<SwarmFut, SS>) {
pub fn set_swarm_handler(mut self, handler: SwarmHandleFn<SwarmFut, SS>) -> Self {
self.swarm_handler = Some(handler);
self
}
/// TODO: write plugin docs probably here
///
/// notes: ECS entities might not be Minecraft entities, filter by MinecraftEntityId to make sure
fn add_plugin<T: Plugin>(&mut self, plugin: T) {
///
/// notes: ECS entities might not be Minecraft entities, filter by
/// MinecraftEntityId to make sure
pub fn add_plugin<T: Plugin>(mut self, plugin: T) -> Self {
self.app.add_plugin(plugin);
self
}
/// Set how long we should wait between each bot joining the server.
///
/// By default, every bot will connect at the same time. If you set this
/// field, however, the bots will wait for the previous one to have
/// connected and *then* they'll wait the given duration.
pub fn join_delay(mut self, delay: std::time::Duration) -> Self {
self.join_delay = Some(delay);
self
}
fn add_default_swarm_plugins(self) -> Self {
self.add_plugin(chat::Plugin).add_plugin(events::Plugin)
}
/// Build this `SwarmBuilder` into an actual [`Swarm`] and join the given
@ -152,7 +176,7 @@ where
/// that implements `TryInto<ServerAddress>`.
///
/// [`ServerAddress`]: azalea_protocol::ServerAddress
pub async fn start(self, address: impl TryInto<ServerAddress>) -> Result<(), SwarmStartError>{
pub async fn start(self, address: impl TryInto<ServerAddress>) -> Result<(), SwarmStartError> {
assert_eq!(
self.accounts.len(),
self.states.len(),
@ -177,8 +201,10 @@ where
let (run_schedule_sender, run_schedule_receiver) = mpsc::channel(1);
let ecs_lock = start_ecs(self.app, run_schedule_receiver, run_schedule_sender.clone());
let mut swarm = Swarm {
// bot_datas: Arc::new(Mutex::new(Vec::new())),
let swarm = Swarm {
ecs_lock: ecs_lock.clone(),
bots: Arc::new(Mutex::new(Vec::new())),
resolved_address,
address,
world_container,
@ -187,26 +213,30 @@ where
swarm_tx: swarm_tx.clone(),
ecs_lock,
run_schedule_sender,
};
self.app.insert_resource(swarm.clone());
swarm.plugins.add(chat::Plugin);
ecs_lock.lock().insert_resource(swarm.clone());
// SwarmBuilder (self) isn't Send so we have to take all the things we need out
// of it
let mut swarm_clone = swarm.clone();
let join_delay = self.join_delay.clone();
let accounts = self.accounts.clone();
let states = self.states.clone();
let join_task = tokio::spawn(async move {
if let Some(join_delay) = self.join_delay {
if let Some(join_delay) = join_delay {
// if there's a join delay, then join one by one
for (account, state) in self.accounts.iter().zip(self.states) {
for (account, state) in accounts.iter().zip(states) {
swarm_clone
.add_with_exponential_backoff(account, state.clone())
.await;
tokio::time::sleep(join_delay).await;
}
} else {
// otherwise, join all at once
let swarm_borrow = &swarm_clone;
join_all(self.accounts.iter().zip(self.states).map(
join_all(accounts.iter().zip(states).map(
async move |(account, state)| -> Result<(), JoinError> {
swarm_borrow
.clone()
@ -220,35 +250,27 @@ where
});
let swarm_state = self.swarm_state;
let mut internal_state = InternalSwarmState::default();
// Watch swarm_rx and send those events to the swarm_handle.
let swarm_clone = swarm.clone();
tokio::spawn(async move {
while let Some(event) = swarm_rx.recv().await {
tokio::spawn((self.swarm_handle)(
swarm_clone.clone(),
event,
swarm_state.clone(),
));
if let Some(swarm_handler) = self.swarm_handler {
tokio::spawn((swarm_handler)(
swarm_clone.clone(),
event,
swarm_state.clone(),
));
}
}
});
// bot events
while let Some((Some(event), (bot, state))) = bots_rx.recv().await {
// remove this #[allow] when more checks are added
// TODO: actually it'd be better to just have this in a system
#[allow(clippy::single_match)]
match &event {
Event::Login => {
internal_state.bots_joined += 1;
if internal_state.bots_joined == swarm.bot_datas.lock().len() {
swarm_tx.send(SwarmEvent::Login).unwrap();
}
}
_ => {}
while let Some((Some(event), bot)) = bots_rx.recv().await {
if let Some(handler) = self.handler {
let state = bot.component::<S>();
tokio::spawn((handler)(bot, event, state));
}
tokio::spawn((self.handle)(bot, event, state));
}
join_task.abort();
@ -372,7 +394,11 @@ pub enum SwarmStartError {
impl Swarm {
/// Add a new account to the swarm. You can remove it later by calling
/// [`Client::disconnect`].
pub async fn add<S: Clone>(&mut self, account: &Account, state: S) -> Result<Client, JoinError> {
pub async fn add<S: Component + Clone>(
&mut self,
account: &Account,
state: S,
) -> Result<Client, JoinError> {
// tx is moved to the bot so it can send us events
// rx is used to receive events from the bot
// An event that causes the schedule to run. This is only used internally.
@ -386,10 +412,14 @@ impl Swarm {
self.run_schedule_sender.clone(),
)
.await?;
// add the state to the client
{
let mut ecs = self.ecs_lock.lock();
ecs.entity_mut(bot.entity).insert(state);
}
let cloned_bots_tx = self.bots_tx.clone();
let cloned_bot = bot.clone();
let cloned_state = state.clone();
let owned_account = account.clone();
let swarm_tx = self.swarm_tx.clone();
// send the init event immediately so it's the first thing we get
@ -398,9 +428,7 @@ impl Swarm {
while let Some(event) = rx.recv().await {
// we can't handle events here (since we can't copy the handler),
// they're handled above in start_swarm
if let Err(e) =
cloned_bots_tx.send((Some(event), (cloned_bot.clone(), cloned_state.clone())))
{
if let Err(e) = cloned_bots_tx.send((Some(event), cloned_bot.clone())) {
error!("Error sending event to swarm: {e}");
}
}
@ -409,7 +437,6 @@ impl Swarm {
.unwrap();
});
Ok(bot)
}
@ -418,7 +445,11 @@ impl Swarm {
///
/// Exponential backoff means if it fails joining it will initially wait 10
/// seconds, then 20, then 40, up to 2 minutes.
pub async fn add_with_exponential_backoff(&mut self, account: &Account, state: S) -> Client {
pub async fn add_with_exponential_backoff<S: Component + Clone>(
&mut self,
account: &Account,
state: S,
) -> Client {
let mut disconnects = 0;
loop {
match self.add(account, state.clone()).await {
@ -436,32 +467,23 @@ impl Swarm {
}
}
impl IntoIterator for Swarm
where
S: Send + Sync + Clone + 'static,
{
type Item = (Client, S);
impl IntoIterator for Swarm {
type Item = Client;
type IntoIter = std::vec::IntoIter<Self::Item>;
/// Iterate over the bots and their states in this swarm.
/// Iterate over the bots in this swarm.
///
/// ```rust,no_run
/// for (bot, state) in swarm {
/// for bot in swarm {
/// let state = bot.component::<State>();
/// // ...
/// }
/// ```
fn into_iter(self) -> Self::IntoIter {
self.
// self.bot_datas.lock().clone().into_iter()
self.bots.lock().clone().into_iter()
}
}
#[derive(Default)]
struct InternalSwarmState {
/// The number of bots connected to the server
pub bots_joined: usize,
}
impl From<ConnectionError> for SwarmStartError {
fn from(e: ConnectionError) -> Self {
SwarmStartError::from(JoinError::from(e))

View file

@ -1,6 +1,6 @@
#![feature(type_alias_impl_trait)]
use azalea::ecs::query::With;
use azalea::bevy_ecs::query::With;
use azalea::entity::metadata::Player;
use azalea::entity::Position;
use azalea::pathfinder::BlockPosGoal;
@ -10,10 +10,10 @@ use azalea::{Account, Client, Event};
use azalea_protocol::packets::game::serverbound_client_command_packet::ServerboundClientCommandPacket;
use std::time::Duration;
#[derive(Default, Clone)]
#[derive(Default, Clone, Component)]
struct State {}
#[derive(Default, Clone)]
#[derive(Default, Clone, Component)]
struct SwarmState {}
#[tokio::main]
@ -56,8 +56,8 @@ async fn main() -> anyhow::Result<()> {
let e = azalea::SwarmBuilder::new()
.add_accounts(accounts.clone())
.set_handler(handle)
.swarm_handle(swarm_handle)
.join_delay(Some(Duration::from_millis(1000)))
.set_swarm_handler(swarm_handle)
.join_delay(Duration::from_millis(1000))
.start("localhost")
.await;
println!("{e:?}");
@ -99,12 +99,12 @@ async fn handle(mut bot: Client, event: Event, _state: State) -> anyhow::Result<
if let Some(entity) = entity {
match m.content().as_str() {
"goto" => {
let entity_pos = bot.entity_components::<Position>(entity);
let entity_pos = bot.entity_component::<Position>(entity);
let target_pos: BlockPos = entity_pos.into();
bot.goto(BlockPosGoal::from(target_pos));
}
"look" => {
let entity_pos = bot.entity_components::<Position>(entity);
let entity_pos = bot.entity_component::<Position>(entity);
let target_pos: BlockPos = entity_pos.into();
println!("target_pos: {:?}", target_pos);
bot.look_at(target_pos.center());
@ -138,7 +138,7 @@ async fn handle(mut bot: Client, event: Event, _state: State) -> anyhow::Result<
}
async fn swarm_handle(
mut swarm: Swarm<State>,
mut swarm: Swarm,
event: SwarmEvent,
_state: SwarmState,
) -> anyhow::Result<()> {
@ -151,7 +151,7 @@ async fn swarm_handle(
SwarmEvent::Chat(m) => {
println!("swarm chat message: {}", m.message().to_ansi());
if m.message().to_string() == "<py5> world" {
for (name, world) in &swarm.worlds.read().worlds {
for (name, world) in &swarm.world_container.read().worlds {
println!("world name: {}", name);
if let Some(w) = world.upgrade() {
for chunk_pos in w.read().chunks.chunks.values() {
@ -163,7 +163,7 @@ async fn swarm_handle(
}
}
if m.message().to_string() == "<py5> hi" {
for (bot, _) in swarm {
for bot in swarm {
bot.chat("hello");
}
}