azalea/swarm/
mod.rs

1//! Swarms are a way to conveniently control many bots.
2//!
3//! See [`Swarm`] for more information.
4
5mod chat;
6mod events;
7pub mod prelude;
8
9use std::{collections::HashMap, future::Future, net::SocketAddr, sync::Arc, time::Duration};
10
11use azalea_client::{
12    chat::ChatPacket, start_ecs_runner, Account, Client, DefaultPlugins, Event, JoinError,
13    StartClientOpts,
14};
15use azalea_protocol::{resolver, ServerAddress};
16use azalea_world::InstanceContainer;
17use bevy_app::{App, PluginGroup, PluginGroupBuilder, Plugins};
18use bevy_ecs::{component::Component, entity::Entity, system::Resource, world::World};
19use futures::future::{join_all, BoxFuture};
20use parking_lot::{Mutex, RwLock};
21use tokio::sync::mpsc;
22use tracing::error;
23
24use crate::{BoxHandleFn, DefaultBotPlugins, HandleFn, JoinOpts, NoState, StartError};
25
26/// A swarm is a way to conveniently control many bots at once, while also
27/// being able to control bots at an individual level when desired.
28///
29/// Swarms are created from [`SwarmBuilder`].
30///
31/// Clients can be added to the swarm later via [`Swarm::add`], and can be
32/// removed with [`Client::disconnect`].
33#[derive(Clone, Resource)]
34pub struct Swarm {
35    pub ecs_lock: Arc<Mutex<World>>,
36
37    bots: Arc<Mutex<HashMap<Entity, Client>>>,
38
39    // the address is public and mutable so plugins can change it
40    pub resolved_address: Arc<RwLock<SocketAddr>>,
41    pub address: Arc<RwLock<ServerAddress>>,
42
43    pub instance_container: Arc<RwLock<InstanceContainer>>,
44
45    bots_tx: mpsc::UnboundedSender<(Option<Event>, Client)>,
46    swarm_tx: mpsc::UnboundedSender<SwarmEvent>,
47
48    run_schedule_sender: mpsc::UnboundedSender<()>,
49}
50
51/// Create a new [`Swarm`].
52///
53/// The generics of this struct stand for the following:
54/// - S: State
55/// - SS: Swarm State
56/// - R: Return type of the handler
57/// - SR: Return type of the swarm handler
58///
59/// You shouldn't have to manually set them though, they'll be inferred for you.
60pub struct SwarmBuilder<S, SS, R, SR>
61where
62    S: Send + Sync + Clone + Component + 'static,
63    SS: Default + Send + Sync + Clone + Resource + 'static,
64{
65    pub(crate) app: App,
66    /// The accounts and proxies that are going to join the server.
67    pub(crate) accounts: Vec<(Account, JoinOpts)>,
68    /// The individual bot states. This must be the same length as `accounts`,
69    /// since each bot gets one state.
70    pub(crate) states: Vec<S>,
71    /// The state for the overall swarm.
72    pub(crate) swarm_state: SS,
73    /// The function that's called every time a bot receives an [`Event`].
74    pub(crate) handler: Option<BoxHandleFn<S, R>>,
75    /// The function that's called every time the swarm receives a
76    /// [`SwarmEvent`].
77    pub(crate) swarm_handler: Option<BoxSwarmHandleFn<SS, SR>>,
78
79    /// How long we should wait between each bot joining the server. Set to
80    /// None to have every bot connect at the same time. None is different than
81    /// a duration of 0, since if a duration is present the bots will wait for
82    /// the previous one to be ready.
83    pub(crate) join_delay: Option<std::time::Duration>,
84}
85impl SwarmBuilder<NoState, NoSwarmState, (), ()> {
86    /// Start creating the swarm.
87    #[must_use]
88    pub fn new() -> Self {
89        Self::new_without_plugins()
90            .add_plugins(DefaultPlugins)
91            .add_plugins(DefaultBotPlugins)
92            .add_plugins(DefaultSwarmPlugins)
93    }
94
95    /// [`Self::new`] but without adding the plugins by default. This is useful
96    /// if you want to disable a default plugin.
97    ///
98    /// You **must** add [`DefaultPlugins`], [`DefaultBotPlugins`], and
99    /// [`DefaultSwarmPlugins`] to this.
100    ///
101    /// ```
102    /// # use azalea::{prelude::*, swarm::prelude::*};
103    /// use azalea::{app::PluginGroup, DefaultBotPlugins, DefaultPlugins, swarm::{DefaultSwarmPlugins}};
104    /// use bevy_log::LogPlugin;
105    ///
106    /// let swarm_builder = SwarmBuilder::new_without_plugins()
107    ///     .add_plugins(DefaultPlugins.build().disable::<LogPlugin>())
108    ///     .add_plugins(DefaultBotPlugins)
109    ///     .add_plugins(DefaultSwarmPlugins);
110    /// # swarm_builder.set_handler(handle).set_swarm_handler(swarm_handle);
111    /// # #[derive(Component, Resource, Clone, Default)]
112    /// # pub struct State;
113    /// # async fn handle(mut bot: Client, event: Event, state: State) -> anyhow::Result<()> {
114    /// #     Ok(())
115    /// # }
116    /// # async fn swarm_handle(swarm: Swarm, event: SwarmEvent, state: State) -> anyhow::Result<()> {
117    /// #     Ok(())
118    /// # }
119    /// ```
120    #[must_use]
121    pub fn new_without_plugins() -> Self {
122        SwarmBuilder {
123            // we create the app here so plugins can add onto it.
124            // the schedules won't run until [`Self::start`] is called.
125            app: App::new(),
126            accounts: Vec::new(),
127            states: Vec::new(),
128            swarm_state: NoSwarmState,
129            handler: None,
130            swarm_handler: None,
131            join_delay: None,
132        }
133    }
134}
135
136impl<SS, SR> SwarmBuilder<NoState, SS, (), SR>
137where
138    SS: Default + Send + Sync + Clone + Resource + 'static,
139{
140    /// Set the function that's called every time a bot receives an [`Event`].
141    /// This is the way to handle normal per-bot events.
142    ///
143    /// Currently you can have up to one handler.
144    ///
145    /// ```
146    /// # use azalea::{prelude::*, swarm::prelude::*};
147    /// # let swarm_builder = SwarmBuilder::new().set_swarm_handler(swarm_handle);
148    /// swarm_builder.set_handler(handle);
149    ///
150    /// #[derive(Component, Default, Clone)]
151    /// struct State {}
152    /// async fn handle(mut bot: Client, event: Event, state: State) -> anyhow::Result<()> {
153    ///     Ok(())
154    /// }
155    ///
156    /// # #[derive(Resource, Default, Clone)]
157    /// # struct SwarmState {}
158    /// # async fn swarm_handle(
159    /// #     mut swarm: Swarm,
160    /// #     event: SwarmEvent,
161    /// #     state: SwarmState,
162    /// # ) -> anyhow::Result<()> {
163    /// #     Ok(())
164    /// # }
165    /// ```
166    #[must_use]
167    pub fn set_handler<S, Fut, R>(self, handler: HandleFn<S, Fut>) -> SwarmBuilder<S, SS, R, SR>
168    where
169        Fut: Future<Output = R> + Send + 'static,
170        S: Send + Sync + Clone + Component + Default + 'static,
171    {
172        SwarmBuilder {
173            handler: Some(Box::new(move |bot, event, state: S| {
174                Box::pin(handler(bot, event, state))
175            })),
176            // if we added accounts before the State was set, we've gotta set it to the default now
177            states: vec![S::default(); self.accounts.len()],
178            app: self.app,
179            ..self
180        }
181    }
182}
183
184impl<S, R> SwarmBuilder<S, NoSwarmState, R, ()>
185where
186    S: Send + Sync + Clone + Component + 'static,
187{
188    /// Set the function that's called every time the swarm receives a
189    /// [`SwarmEvent`]. This is the way to handle global swarm events.
190    ///
191    /// Currently you can have up to one swarm handler.
192    ///
193    /// ```
194    /// # use azalea::{prelude::*, swarm::prelude::*};
195    /// # let swarm_builder = SwarmBuilder::new().set_handler(handle);
196    /// swarm_builder.set_swarm_handler(swarm_handle);
197    ///
198    /// # #[derive(Component, Default, Clone)]
199    /// # struct State {}
200    ///
201    /// # async fn handle(mut bot: Client, event: Event, state: State) -> anyhow::Result<()> {
202    /// #     Ok(())
203    /// # }
204    ///
205    /// #[derive(Resource, Default, Clone)]
206    /// struct SwarmState {}
207    /// async fn swarm_handle(
208    ///     mut swarm: Swarm,
209    ///     event: SwarmEvent,
210    ///     state: SwarmState,
211    /// ) -> anyhow::Result<()> {
212    ///     Ok(())
213    /// }
214    /// ```
215    #[must_use]
216    pub fn set_swarm_handler<SS, Fut, SR>(
217        self,
218        handler: SwarmHandleFn<SS, Fut>,
219    ) -> SwarmBuilder<S, SS, R, SR>
220    where
221        SS: Default + Send + Sync + Clone + Resource + 'static,
222        Fut: Future<Output = SR> + Send + 'static,
223    {
224        SwarmBuilder {
225            handler: self.handler,
226            app: self.app,
227            accounts: self.accounts,
228            states: self.states,
229            swarm_state: SS::default(),
230            swarm_handler: Some(Box::new(move |swarm, event, state| {
231                Box::pin(handler(swarm, event, state))
232            })),
233            join_delay: self.join_delay,
234        }
235    }
236}
237
238impl<S, SS, R, SR> SwarmBuilder<S, SS, R, SR>
239where
240    S: Send + Sync + Clone + Component + 'static,
241    SS: Default + Send + Sync + Clone + Resource + 'static,
242    R: Send + 'static,
243    SR: Send + 'static,
244{
245    /// Add a vec of [`Account`]s to the swarm.
246    ///
247    /// Use [`Self::add_account`] to only add one account. If you want the
248    /// clients to have different default states, add them one at a time with
249    /// [`Self::add_account_with_state`].
250    ///
251    /// By default, every account will join at the same time, you can add a
252    /// delay with [`Self::join_delay`].
253    #[must_use]
254    pub fn add_accounts(mut self, accounts: Vec<Account>) -> Self
255    where
256        S: Default,
257    {
258        for account in accounts {
259            self = self.add_account(account);
260        }
261
262        self
263    }
264
265    /// Add a single new [`Account`] to the swarm. Use [`Self::add_accounts`] to
266    /// add multiple accounts at a time.
267    ///
268    /// This will make the state for this client be the default, use
269    /// [`Self::add_account_with_state`] to avoid that.
270    #[must_use]
271    pub fn add_account(self, account: Account) -> Self
272    where
273        S: Default,
274    {
275        self.add_account_with_state_and_opts(account, S::default(), JoinOpts::default())
276    }
277
278    /// Add an account with a custom initial state. Use just
279    /// [`Self::add_account`] to use the Default implementation for the state.
280    #[must_use]
281    pub fn add_account_with_state(self, account: Account, state: S) -> Self {
282        self.add_account_with_state_and_opts(account, state, JoinOpts::default())
283    }
284
285    /// Add an account with a custom initial state. Use just
286    /// [`Self::add_account`] to use the Default implementation for the state.
287    #[must_use]
288    pub fn add_account_with_opts(self, account: Account, opts: JoinOpts) -> Self
289    where
290        S: Default,
291    {
292        self.add_account_with_state_and_opts(account, S::default(), opts)
293    }
294
295    /// Same as [`Self::add_account_with_state`], but allow passing in custom
296    /// join options.
297    #[must_use]
298    pub fn add_account_with_state_and_opts(
299        mut self,
300        account: Account,
301        state: S,
302        join_opts: JoinOpts,
303    ) -> Self {
304        self.accounts.push((account, join_opts));
305        self.states.push(state);
306        self
307    }
308
309    /// Set the swarm state instead of initializing defaults.
310    #[must_use]
311    pub fn set_swarm_state(mut self, swarm_state: SS) -> Self {
312        self.swarm_state = swarm_state;
313        self
314    }
315
316    /// Add one or more plugins to this swarm.
317    #[must_use]
318    pub fn add_plugins<M>(mut self, plugins: impl Plugins<M>) -> Self {
319        self.app.add_plugins(plugins);
320        self
321    }
322
323    /// Set how long we should wait between each bot joining the server.
324    ///
325    /// By default, every bot will connect at the same time. If you set this
326    /// field, however, the bots will wait for the previous one to have
327    /// connected and *then* they'll wait the given duration.
328    #[must_use]
329    pub fn join_delay(mut self, delay: std::time::Duration) -> Self {
330        self.join_delay = Some(delay);
331        self
332    }
333
334    /// Build this `SwarmBuilder` into an actual [`Swarm`] and join the given
335    /// server.
336    ///
337    /// The `address` argument can be a `&str`, [`ServerAddress`], or anything
338    /// that implements `TryInto<ServerAddress>`.
339    ///
340    /// [`ServerAddress`]: azalea_protocol::ServerAddress
341    pub async fn start(self, address: impl TryInto<ServerAddress>) -> Result<!, StartError> {
342        // convert the TryInto<ServerAddress> into a ServerAddress
343        let address: ServerAddress = match address.try_into() {
344            Ok(address) => address,
345            Err(_) => return Err(StartError::InvalidAddress),
346        };
347
348        self.start_with_default_opts(address, JoinOpts::default())
349            .await
350    }
351
352    /// Do the same as [`Self::start`], but allow passing in default join
353    /// options for the bots.
354    pub async fn start_with_default_opts(
355        self,
356        address: impl TryInto<ServerAddress>,
357        default_join_opts: JoinOpts,
358    ) -> Result<!, StartError> {
359        assert_eq!(
360            self.accounts.len(),
361            self.states.len(),
362            "There must be exactly one state per bot."
363        );
364
365        // convert the TryInto<ServerAddress> into a ServerAddress
366        let address = match address.try_into() {
367            Ok(address) => address,
368            Err(_) => return Err(StartError::InvalidAddress),
369        };
370
371        let address: ServerAddress = default_join_opts.custom_address.clone().unwrap_or(address);
372        let resolved_address = if let Some(a) = default_join_opts.custom_resolved_address {
373            a
374        } else {
375            resolver::resolve_address(&address).await?
376        };
377
378        let instance_container = Arc::new(RwLock::new(InstanceContainer::default()));
379
380        // we can't modify the swarm plugins after this
381        let (bots_tx, mut bots_rx) = mpsc::unbounded_channel();
382        let (swarm_tx, mut swarm_rx) = mpsc::unbounded_channel();
383
384        swarm_tx.send(SwarmEvent::Init).unwrap();
385
386        let (run_schedule_sender, run_schedule_receiver) = mpsc::unbounded_channel();
387
388        let main_schedule_label = self.app.main().update_schedule.unwrap();
389
390        let ecs_lock =
391            start_ecs_runner(self.app, run_schedule_receiver, run_schedule_sender.clone());
392
393        let swarm = Swarm {
394            ecs_lock: ecs_lock.clone(),
395            bots: Arc::new(Mutex::new(HashMap::new())),
396
397            resolved_address: Arc::new(RwLock::new(resolved_address)),
398            address: Arc::new(RwLock::new(address)),
399            instance_container,
400
401            bots_tx,
402
403            swarm_tx: swarm_tx.clone(),
404
405            run_schedule_sender,
406        };
407
408        // run the main schedule so the startup systems run
409        {
410            let mut ecs = ecs_lock.lock();
411            ecs.insert_resource(swarm.clone());
412            ecs.run_schedule(main_schedule_label);
413            ecs.clear_trackers();
414        }
415
416        // SwarmBuilder (self) isn't Send so we have to take all the things we need out
417        // of it
418        let swarm_clone = swarm.clone();
419        let join_delay = self.join_delay;
420        let accounts = self.accounts.clone();
421        let states = self.states.clone();
422
423        tokio::spawn(async move {
424            if let Some(join_delay) = join_delay {
425                // if there's a join delay, then join one by one
426                for ((account, bot_join_opts), state) in accounts.iter().zip(states) {
427                    let mut join_opts = default_join_opts.clone();
428                    join_opts.update(bot_join_opts);
429                    swarm_clone
430                        .add_and_retry_forever_with_opts(account, state, &join_opts)
431                        .await;
432                    tokio::time::sleep(join_delay).await;
433                }
434            } else {
435                // otherwise, join all at once
436                let swarm_borrow = &swarm_clone;
437                join_all(accounts.iter().zip(states).map(
438                    |((account, bot_join_opts), state)| async {
439                        let mut join_opts = default_join_opts.clone();
440                        join_opts.update(bot_join_opts);
441                        swarm_borrow
442                            .clone()
443                            .add_and_retry_forever_with_opts(account, state, &join_opts)
444                            .await;
445                    },
446                ))
447                .await;
448            }
449
450            swarm_tx.send(SwarmEvent::Login).unwrap();
451        });
452
453        let swarm_state = self.swarm_state;
454
455        // Watch swarm_rx and send those events to the swarm_handle.
456        let swarm_clone = swarm.clone();
457        tokio::spawn(async move {
458            while let Some(event) = swarm_rx.recv().await {
459                if let Some(swarm_handler) = &self.swarm_handler {
460                    tokio::spawn((swarm_handler)(
461                        swarm_clone.clone(),
462                        event,
463                        swarm_state.clone(),
464                    ));
465                }
466            }
467        });
468
469        // bot events
470        while let Some((Some(first_event), first_bot)) = bots_rx.recv().await {
471            if let Some(handler) = &self.handler {
472                let first_bot_state = first_bot.component::<S>();
473                let first_bot_entity = first_bot.entity;
474
475                tokio::spawn((handler)(first_bot, first_event, first_bot_state.clone()));
476
477                // this makes it not have to keep locking the ecs
478                let mut states = HashMap::new();
479                states.insert(first_bot_entity, first_bot_state);
480                while let Ok((Some(event), bot)) = bots_rx.try_recv() {
481                    let state = states
482                        .entry(bot.entity)
483                        .or_insert_with(|| bot.component::<S>().clone());
484                    tokio::spawn((handler)(bot, event, state.clone()));
485                }
486            }
487        }
488
489        unreachable!(
490            "bots_rx.recv() should never be None because the bots_tx channel is never closed"
491        );
492    }
493}
494
495impl Default for SwarmBuilder<NoState, NoSwarmState, (), ()> {
496    fn default() -> Self {
497        Self::new()
498    }
499}
500
501/// An event about something that doesn't have to do with a single bot.
502#[derive(Clone, Debug)]
503pub enum SwarmEvent {
504    /// All the bots in the swarm have successfully joined the server.
505    Login,
506    /// The swarm was created. This is only fired once, and it's guaranteed to
507    /// be the first event to fire.
508    Init,
509    /// A bot got disconnected from the server.
510    ///
511    /// You can implement an auto-reconnect by calling [`Swarm::add_with_opts`]
512    /// with the account and options from this event.
513    Disconnect(Box<Account>, JoinOpts),
514    /// At least one bot received a chat message.
515    Chat(ChatPacket),
516}
517
518pub type SwarmHandleFn<SS, Fut> = fn(Swarm, SwarmEvent, SS) -> Fut;
519pub type BoxSwarmHandleFn<SS, R> =
520    Box<dyn Fn(Swarm, SwarmEvent, SS) -> BoxFuture<'static, R> + Send>;
521
522/// Make a bot [`Swarm`].
523///
524/// [`Swarm`]: struct.Swarm.html
525///
526/// # Examples
527/// ```rust,no_run
528/// use azalea::{prelude::*, swarm::prelude::*};
529/// use std::time::Duration;
530///
531/// #[derive(Default, Clone, Component)]
532/// struct State {}
533///
534/// #[derive(Default, Clone, Resource)]
535/// struct SwarmState {}
536///
537/// #[tokio::main]
538/// async fn main() {
539///     let mut accounts = Vec::new();
540///     let mut states = Vec::new();
541///
542///     for i in 0..10 {
543///         accounts.push(Account::offline(&format!("bot{i}")));
544///         states.push(State::default());
545///     }
546///
547///     SwarmBuilder::new()
548///         .add_accounts(accounts.clone())
549///         .set_handler(handle)
550///         .set_swarm_handler(swarm_handle)
551///         .join_delay(Duration::from_millis(1000))
552///         .start("localhost")
553///         .await
554///         .unwrap();
555/// }
556///
557/// async fn handle(bot: Client, event: Event, _state: State) -> anyhow::Result<()> {
558///     match &event {
559///         _ => {}
560///     }
561///     Ok(())
562/// }
563///
564/// async fn swarm_handle(
565///     mut swarm: Swarm,
566///     event: SwarmEvent,
567///     _state: SwarmState,
568/// ) -> anyhow::Result<()> {
569///     match &event {
570///         SwarmEvent::Disconnect(account, join_opts) => {
571///             // automatically reconnect after 5 seconds
572///             tokio::time::sleep(Duration::from_secs(5)).await;
573///             swarm.add_with_opts(account, State::default(), join_opts).await?;
574///         }
575///         SwarmEvent::Chat(m) => {
576///             println!("{}", m.message().to_ansi());
577///         }
578///         _ => {}
579///     }
580///     Ok(())
581/// }
582impl Swarm {
583    /// Add a new account to the swarm. You can remove it later by calling
584    /// [`Client::disconnect`].
585    ///
586    /// # Errors
587    ///
588    /// Returns an `Err` if the bot could not do a handshake successfully.
589    pub async fn add<S: Component + Clone>(
590        &mut self,
591        account: &Account,
592        state: S,
593    ) -> Result<Client, JoinError> {
594        self.add_with_opts(account, state, &JoinOpts::default())
595            .await
596    }
597    /// Add a new account to the swarm, using custom options. This is useful if
598    /// you want bots in the same swarm to connect to different addresses.
599    /// Usually you'll just want [`Self::add`] though.
600    ///
601    /// # Errors
602    ///
603    /// Returns an `Err` if the bot could not do a handshake successfully.
604    pub async fn add_with_opts<S: Component + Clone>(
605        &self,
606        account: &Account,
607        state: S,
608        join_opts: &JoinOpts,
609    ) -> Result<Client, JoinError> {
610        let address = join_opts
611            .custom_address
612            .clone()
613            .unwrap_or_else(|| self.address.read().clone());
614        let resolved_address = join_opts
615            .custom_resolved_address
616            .unwrap_or_else(|| *self.resolved_address.read());
617
618        let (bot, mut rx) = Client::start_client(StartClientOpts {
619            ecs_lock: self.ecs_lock.clone(),
620            account,
621            address: &address,
622            resolved_address: &resolved_address,
623            proxy: join_opts.proxy.clone(),
624            run_schedule_sender: self.run_schedule_sender.clone(),
625        })
626        .await?;
627        // add the state to the client
628        {
629            let mut ecs = self.ecs_lock.lock();
630            ecs.entity_mut(bot.entity).insert(state);
631        }
632
633        self.bots.lock().insert(bot.entity, bot.clone());
634
635        let cloned_bots = self.bots.clone();
636        let cloned_bots_tx = self.bots_tx.clone();
637        let cloned_bot = bot.clone();
638        let swarm_tx = self.swarm_tx.clone();
639        let join_opts = join_opts.clone();
640        tokio::spawn(async move {
641            while let Some(event) = rx.recv().await {
642                // we can't handle events here (since we can't copy the handler),
643                // they're handled above in SwarmBuilder::start
644                if let Err(e) = cloned_bots_tx.send((Some(event), cloned_bot.clone())) {
645                    error!("Error sending event to swarm: {e}");
646                }
647            }
648            cloned_bots.lock().remove(&bot.entity);
649            let account = cloned_bot
650                .get_component::<Account>()
651                .expect("bot is missing required Account component");
652            swarm_tx
653                .send(SwarmEvent::Disconnect(Box::new(account), join_opts))
654                .unwrap();
655        });
656
657        Ok(bot)
658    }
659
660    /// Add a new account to the swarm, retrying if it couldn't join. This will
661    /// run forever until the bot joins or the task is aborted.
662    ///
663    /// This does exponential backoff (though very limited), starting at 5
664    /// seconds and doubling up to 15 seconds.
665    pub async fn add_and_retry_forever<S: Component + Clone>(
666        &self,
667        account: &Account,
668        state: S,
669    ) -> Client {
670        self.add_and_retry_forever_with_opts(account, state, &JoinOpts::default())
671            .await
672    }
673
674    /// Same as [`Self::add_and_retry_forever`], but allow passing custom join
675    /// options.
676    pub async fn add_and_retry_forever_with_opts<S: Component + Clone>(
677        &self,
678        account: &Account,
679        state: S,
680        opts: &JoinOpts,
681    ) -> Client {
682        let mut disconnects = 0;
683        loop {
684            match self.add_with_opts(account, state.clone(), opts).await {
685                Ok(bot) => return bot,
686                Err(e) => {
687                    disconnects += 1;
688                    let delay = (Duration::from_secs(5) * 2u32.pow(disconnects.min(16)))
689                        .min(Duration::from_secs(15));
690                    let username = account.username.clone();
691
692                    if let JoinError::Disconnect { reason } = &e {
693                        error!(
694                            "Error joining as {username}, server says: \"{reason}\". Waiting {delay:?} and trying again."
695                        );
696                    } else {
697                        error!(
698                            "Error joining as {username}: {e}. Waiting {delay:?} and trying again."
699                        );
700                    }
701
702                    tokio::time::sleep(delay).await;
703                }
704            }
705        }
706    }
707}
708
709impl IntoIterator for Swarm {
710    type Item = Client;
711    type IntoIter = std::vec::IntoIter<Self::Item>;
712
713    /// Iterate over the bots in this swarm.
714    ///
715    /// ```rust,no_run
716    /// # use azalea::{prelude::*, swarm::prelude::*};
717    /// #[derive(Component, Clone)]
718    /// # pub struct State;
719    /// # fn example(swarm: Swarm) {
720    /// for bot in swarm {
721    ///     let state = bot.component::<State>();
722    ///     // ...
723    /// }
724    /// # }
725    /// ```
726    fn into_iter(self) -> Self::IntoIter {
727        self.bots
728            .lock()
729            .clone()
730            .into_values()
731            .collect::<Vec<_>>()
732            .into_iter()
733    }
734}
735
736/// This plugin group will add all the default plugins necessary for swarms to
737/// work.
738pub struct DefaultSwarmPlugins;
739
740impl PluginGroup for DefaultSwarmPlugins {
741    fn build(self) -> PluginGroupBuilder {
742        PluginGroupBuilder::start::<Self>()
743            .add(chat::SwarmChatPlugin)
744            .add(events::SwarmPlugin)
745    }
746}
747
748/// A marker that can be used in place of a SwarmState in [`SwarmBuilder`]. You
749/// probably don't need to use this manually since the compiler will infer it
750/// for you.
751#[derive(Resource, Clone, Default)]
752pub struct NoSwarmState;