aboutsummaryrefslogtreecommitdiff
path: root/azalea/src
diff options
context:
space:
mode:
authorTheDudeFromCI <thedudefromci@gmail.com>2023-08-15 22:12:37 -0700
committerGitHub <noreply@github.com>2023-08-16 00:12:37 -0500
commitf0ff8e7f295270795000f6a8fcd8c2230a3733f5 (patch)
treec71d8f4873731b1f18b5654adc8a35e5e0c01e10 /azalea/src
parent8a90a8e109b88c1c70b88f7488a0a8eb5f52d724 (diff)
downloadazalea-drasl-f0ff8e7f295270795000f6a8fcd8c2230a3733f5.tar.xz
no_handler() option for ClientBuilder (#100)
* Added no_handler client builder option Signed-off-by: TheDudeFromCI <thedudefromci@gmail.com> * Made EmptyState public Signed-off-by: TheDudeFromCI <thedudefromci@gmail.com> * Packaged no_handler placeholders in own module Signed-off-by: TheDudeFromCI <thedudefromci@gmail.com> * imply no state and remove Fut generic from ClientBuilder and SwarmBuilder * use destructuring in set_handler and fix a doc --------- Signed-off-by: TheDudeFromCI <thedudefromci@gmail.com> Co-authored-by: mat <git@matdoes.dev>
Diffstat (limited to 'azalea/src')
-rw-r--r--azalea/src/lib.rs65
-rw-r--r--azalea/src/swarm/mod.rs185
2 files changed, 149 insertions, 101 deletions
diff --git a/azalea/src/lib.rs b/azalea/src/lib.rs
index f564cd9b..ce3494e2 100644
--- a/azalea/src/lib.rs
+++ b/azalea/src/lib.rs
@@ -2,6 +2,7 @@
#![feature(async_closure)]
#![allow(incomplete_features)]
#![feature(async_fn_in_trait)]
+#![feature(type_changing_struct_update)]
mod auto_respawn;
mod bot;
@@ -23,7 +24,7 @@ pub use azalea_registry::{Block, EntityKind, Item};
pub use azalea_world as world;
pub use bot::DefaultBotPlugins;
use ecs::component::Component;
-use futures::Future;
+use futures::{future::BoxFuture, Future};
use protocol::{
resolver::{self, ResolverError},
ServerAddress,
@@ -34,7 +35,9 @@ use tokio::sync::mpsc;
pub use bevy_app as app;
pub use bevy_ecs as ecs;
-pub type HandleFn<Fut, S> = fn(Client, azalea_client::Event, S) -> Fut;
+pub type BoxHandleFn<S> =
+ Box<dyn Fn(Client, azalea_client::Event, S) -> BoxFuture<'static, Result<(), anyhow::Error>>>;
+pub type HandleFn<S, Fut> = fn(Client, azalea_client::Event, S) -> Fut;
#[derive(Error, Debug)]
pub enum StartError {
@@ -64,24 +67,19 @@ pub enum StartError {
/// # Ok(())
/// # }
/// ```
-pub struct ClientBuilder<S, Fut>
+pub struct ClientBuilder<S>
where
- S: Default + Send + Sync + Clone + 'static,
- Fut: Future<Output = Result<(), anyhow::Error>>,
+ S: Default + Send + Sync + Clone + Component + 'static,
{
app: App,
/// The function that's called every time a bot receives an [`Event`].
- handler: Option<HandleFn<Fut, S>>,
+ handler: Option<BoxHandleFn<S>>,
state: S,
}
-impl<S, Fut> ClientBuilder<S, Fut>
-where
- S: Default + Send + Sync + Clone + Component + 'static,
- Fut: Future<Output = Result<(), anyhow::Error>> + Send + 'static,
-{
+impl ClientBuilder<NoState> {
/// Start building a client that can join the world.
#[must_use]
- pub fn new() -> Self {
+ pub fn new() -> ClientBuilder<NoState> {
Self::new_without_plugins()
.add_plugins(DefaultPlugins)
.add_plugins(DefaultBotPlugins)
@@ -111,21 +109,20 @@ where
/// # }
/// ```
#[must_use]
- pub fn new_without_plugins() -> Self {
+ pub fn new_without_plugins() -> ClientBuilder<NoState> {
Self {
// we create the app here so plugins can add onto it.
// the schedules won't run until [`Self::start`] is called.
app: App::new(),
handler: None,
- state: S::default(),
+ state: NoState,
}
}
/// Set the function that's called every time a bot receives an [`Event`].
/// This is the way to handle normal per-bot events.
///
- /// You must have exactly one client handler, calling this again will
- /// replace the old client handler function.
+ /// Currently you can have up to one client handler.
///
/// ```
/// # use azalea::prelude::*;
@@ -139,10 +136,24 @@ where
/// }
/// ```
#[must_use]
- pub fn set_handler(mut self, handler: HandleFn<Fut, S>) -> Self {
- self.handler = Some(handler);
- self
+ pub fn set_handler<S, Fut>(self, handler: HandleFn<S, Fut>) -> ClientBuilder<S>
+ where
+ S: Default + Send + Sync + Clone + Component + 'static,
+ Fut: Future<Output = Result<(), anyhow::Error>> + Send + 'static,
+ {
+ ClientBuilder {
+ handler: Some(Box::new(move |bot, event, state| {
+ Box::pin(handler(bot, event, state))
+ })),
+ state: S::default(),
+ ..self
+ }
}
+}
+impl<S> ClientBuilder<S>
+where
+ S: Default + Send + Sync + Clone + Component + 'static,
+{
/// Set the client state instead of initializing defaults.
#[must_use]
pub fn set_state(mut self, state: S) -> Self {
@@ -186,7 +197,7 @@ where
.await?;
while let Some(event) = rx.recv().await {
- if let Some(handler) = self.handler {
+ if let Some(handler) = &self.handler {
tokio::spawn((handler)(bot.clone(), event.clone(), self.state.clone()));
}
}
@@ -194,12 +205,16 @@ where
Ok(())
}
}
-impl<S, Fut> Default for ClientBuilder<S, Fut>
-where
- S: Default + Send + Sync + Clone + Component + 'static,
- Fut: Future<Output = Result<(), anyhow::Error>> + Send + 'static,
-{
+impl Default for ClientBuilder<NoState> {
fn default() -> Self {
Self::new()
}
}
+
+/// A marker that can be used in place of a State in [`ClientBuilder`] or
+/// [`SwarmBuilder`]. You probably don't need to use this manually since the
+/// compiler will infer it for you.
+///
+/// [`SwarmBuilder`]: swarm::SwarmBuilder
+#[derive(Component, Clone, Default)]
+pub struct NoState;
diff --git a/azalea/src/swarm/mod.rs b/azalea/src/swarm/mod.rs
index 1ea4aa02..f84905ec 100644
--- a/azalea/src/swarm/mod.rs
+++ b/azalea/src/swarm/mod.rs
@@ -4,7 +4,6 @@ mod chat;
mod events;
pub mod prelude;
-use crate::{bot::DefaultBotPlugins, HandleFn};
use azalea_client::{
chat::ChatPacket, start_ecs, Account, Client, DefaultPlugins, Event, JoinError,
};
@@ -16,13 +15,15 @@ use azalea_protocol::{
use azalea_world::InstanceContainer;
use bevy_app::{App, PluginGroup, PluginGroupBuilder, Plugins};
use bevy_ecs::{component::Component, entity::Entity, system::Resource, world::World};
-use futures::future::join_all;
+use futures::future::{join_all, BoxFuture};
use log::error;
use parking_lot::{Mutex, RwLock};
use std::{collections::HashMap, future::Future, net::SocketAddr, sync::Arc, time::Duration};
use thiserror::Error;
use tokio::sync::mpsc;
+use crate::{BoxHandleFn, DefaultBotPlugins, HandleFn, NoState};
+
/// A swarm is a way to conveniently control many bots at once, while also
/// being able to control bots at an individual level when desired.
///
@@ -48,12 +49,10 @@ pub struct Swarm {
}
/// Create a new [`Swarm`].
-pub struct SwarmBuilder<S, SS, Fut, SwarmFut>
+pub struct SwarmBuilder<S, SS>
where
- S: Send + Sync + Clone + 'static,
- SS: Default + Send + Sync + Clone + 'static,
- Fut: Future<Output = Result<(), anyhow::Error>>,
- SwarmFut: Future<Output = Result<(), anyhow::Error>>,
+ S: Send + Sync + Clone + Component + 'static,
+ SS: Default + Send + Sync + Clone + Resource + 'static,
{
app: App,
/// The accounts that are going to join the server.
@@ -64,10 +63,10 @@ where
/// The state for the overall swarm.
swarm_state: SS,
/// The function that's called every time a bot receives an [`Event`].
- handler: Option<HandleFn<Fut, S>>,
+ handler: Option<BoxHandleFn<S>>,
/// The function that's called every time the swarm receives a
/// [`SwarmEvent`].
- swarm_handler: Option<SwarmHandleFn<SwarmFut, SS>>,
+ swarm_handler: Option<BoxSwarmHandleFn<SS>>,
/// How long we should wait between each bot joining the server. Set to
/// None to have every bot connect at the same time. None is different than
@@ -75,16 +74,10 @@ where
/// the previous one to be ready.
join_delay: Option<std::time::Duration>,
}
-impl<S, SS, Fut, SwarmFut> SwarmBuilder<S, SS, Fut, SwarmFut>
-where
- Fut: Future<Output = Result<(), anyhow::Error>> + Send + 'static,
- SwarmFut: Future<Output = Result<(), anyhow::Error>> + Send + 'static,
- S: Send + Sync + Clone + Component + 'static,
- SS: Default + Send + Sync + Clone + Resource + 'static,
-{
+impl SwarmBuilder<NoState, NoSwarmState> {
/// Start creating the swarm.
#[must_use]
- pub fn new() -> Self {
+ pub fn new() -> SwarmBuilder<NoState, NoSwarmState> {
Self::new_without_plugins()
.add_plugins(DefaultPlugins)
.add_plugins(DefaultBotPlugins)
@@ -117,62 +110,29 @@ where
/// # }
/// ```
#[must_use]
- pub fn new_without_plugins() -> Self {
- Self {
+ pub fn new_without_plugins() -> SwarmBuilder<NoState, NoSwarmState> {
+ SwarmBuilder {
// we create the app here so plugins can add onto it.
// the schedules won't run until [`Self::start`] is called.
app: App::new(),
-
accounts: Vec::new(),
states: Vec::new(),
- swarm_state: SS::default(),
+ swarm_state: NoSwarmState,
handler: None,
swarm_handler: None,
join_delay: None,
}
}
+}
- /// Add a vec of [`Account`]s to the swarm.
- ///
- /// 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`].
- #[must_use]
- pub fn add_accounts(mut self, accounts: Vec<Account>) -> Self
- where
- S: Default,
- {
- for account in accounts {
- self = self.add_account(account);
- }
- self
- }
- /// Add a single new [`Account`] to the swarm. Use [`Self::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.
- #[must_use]
- pub fn add_account(self, account: Account) -> Self
- where
- S: Default,
- {
- 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.
- #[must_use]
- pub fn add_account_with_state(mut self, account: Account, state: S) -> Self {
- self.accounts.push(account);
- self.states.push(state);
- self
- }
-
+impl<SS> SwarmBuilder<NoState, SS>
+where
+ SS: Default + Send + Sync + Clone + Resource + 'static,
+{
/// Set the function that's called every time a bot receives an [`Event`].
/// This is the way to handle normal per-bot events.
///
- /// You must have exactly one client handler and one swarm handler, calling
- /// this again will replace the old client handler function.
+ /// Currently you can have up to one handler.
///
/// ```
/// # use azalea::{prelude::*, swarm::prelude::*};
@@ -196,15 +156,30 @@ where
/// # }
/// ```
#[must_use]
- pub fn set_handler(mut self, handler: HandleFn<Fut, S>) -> Self {
- self.handler = Some(handler);
- self
+ pub fn set_handler<S, Fut>(self, handler: HandleFn<S, Fut>) -> SwarmBuilder<S, SS>
+ where
+ Fut: Future<Output = Result<(), anyhow::Error>> + Send + 'static,
+ S: Send + Sync + Clone + Component + 'static,
+ {
+ SwarmBuilder {
+ handler: Some(Box::new(move |bot, event, state: S| {
+ Box::pin(handler(bot, event, state))
+ })),
+ states: Vec::new(),
+ app: self.app,
+ ..self
+ }
}
+}
+
+impl<S> SwarmBuilder<S, NoSwarmState>
+where
+ S: Send + Sync + Clone + Component + 'static,
+{
/// Set the function that's called every time the swarm receives a
/// [`SwarmEvent`]. This is the way to handle global swarm events.
///
- /// You must have exactly one client handler and one swarm handler, calling
- /// this again will replace the old swarm handler function.
+ /// Currently you can have up to one swarm handler.
///
/// ```
/// # use azalea::{prelude::*, swarm::prelude::*};
@@ -229,10 +204,66 @@ where
/// }
/// ```
#[must_use]
- pub fn set_swarm_handler(mut self, handler: SwarmHandleFn<SwarmFut, SS>) -> Self {
- self.swarm_handler = Some(handler);
+ pub fn set_swarm_handler<SS, Fut>(self, handler: SwarmHandleFn<SS, Fut>) -> SwarmBuilder<S, SS>
+ where
+ SS: Default + Send + Sync + Clone + Resource + 'static,
+ Fut: Future<Output = Result<(), anyhow::Error>> + Send + 'static,
+ {
+ SwarmBuilder {
+ handler: self.handler,
+ app: self.app,
+ accounts: self.accounts,
+ states: self.states,
+ swarm_state: SS::default(),
+ swarm_handler: Some(Box::new(move |swarm, event, state| {
+ Box::pin(handler(swarm, event, state))
+ })),
+ join_delay: self.join_delay,
+ }
+ }
+}
+
+impl<S, SS> SwarmBuilder<S, SS>
+where
+ S: Send + Sync + Clone + Component + 'static,
+ SS: Default + Send + Sync + Clone + Resource + 'static,
+{
+ /// Add a vec of [`Account`]s to the swarm.
+ ///
+ /// 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`].
+ #[must_use]
+ pub fn add_accounts(mut self, accounts: Vec<Account>) -> Self
+ where
+ S: Default,
+ {
+ for account in accounts {
+ self = self.add_account(account);
+ }
+ self
+ }
+ /// Add a single new [`Account`] to the swarm. Use [`Self::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.
+ #[must_use]
+ pub fn add_account(self, account: Account) -> Self
+ where
+ S: Default,
+ {
+ 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.
+ #[must_use]
+ pub fn add_account_with_state(mut self, account: Account, state: S) -> Self {
+ self.accounts.push(account);
+ self.states.push(state);
self
}
+
/// Set the swarm state instead of initializing defaults.
#[must_use]
pub fn set_swarm_state(mut self, swarm_state: SS) -> Self {
@@ -344,7 +375,7 @@ where
let swarm_clone = swarm.clone();
tokio::spawn(async move {
while let Some(event) = swarm_rx.recv().await {
- if let Some(swarm_handler) = self.swarm_handler {
+ if let Some(swarm_handler) = &self.swarm_handler {
tokio::spawn((swarm_handler)(
swarm_clone.clone(),
event,
@@ -356,7 +387,7 @@ where
// bot events
while let Some((Some(event), bot)) = bots_rx.recv().await {
- if let Some(handler) = self.handler {
+ if let Some(handler) = &self.handler {
let state = bot.component::<S>();
tokio::spawn((handler)(bot, event, state));
}
@@ -368,13 +399,7 @@ where
}
}
-impl<S, SS, Fut, SwarmFut> Default for SwarmBuilder<S, SS, Fut, SwarmFut>
-where
- 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 + Resource + 'static,
-{
+impl Default for SwarmBuilder<NoState, NoSwarmState> {
fn default() -> Self {
Self::new()
}
@@ -397,7 +422,9 @@ pub enum SwarmEvent {
Chat(ChatPacket),
}
-pub type SwarmHandleFn<Fut, SS> = fn(Swarm, SwarmEvent, SS) -> Fut;
+pub type SwarmHandleFn<SS, Fut> = fn(Swarm, SwarmEvent, SS) -> Fut;
+pub type BoxSwarmHandleFn<SS> =
+ Box<dyn Fn(Swarm, SwarmEvent, SS) -> BoxFuture<'static, Result<(), anyhow::Error>> + Send>;
#[derive(Error, Debug)]
pub enum SwarmStartError {
@@ -598,3 +625,9 @@ impl PluginGroup for DefaultSwarmPlugins {
.add(events::SwarmPlugin)
}
}
+
+/// A marker that can be used in place of a SwarmState in [`SwarmBuilder`]. You
+/// probably don't need to use this manually since the compiler will infer it
+/// for you.
+#[derive(Resource, Clone, Default)]
+pub struct NoSwarmState;