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

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>
This commit is contained in:
TheDudeFromCI 2023-08-15 22:12:37 -07:00 committed by GitHub
parent 8a90a8e109
commit f0ff8e7f29
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 169 additions and 121 deletions

View file

@ -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;

View file

@ -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,21 +110,124 @@ 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,
}
}
}
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.
///
/// Currently you can have up to one handler.
///
/// ```
/// # use azalea::{prelude::*, swarm::prelude::*};
/// # let swarm_builder = SwarmBuilder::new().set_swarm_handler(swarm_handle);
/// swarm_builder.set_handler(handle);
///
/// #[derive(Component, Default, Clone)]
/// struct State {}
/// async fn handle(mut bot: Client, event: Event, state: State) -> anyhow::Result<()> {
/// Ok(())
/// }
///
/// # #[derive(Resource, Default, Clone)]
/// # struct SwarmState {}
/// # async fn swarm_handle(
/// # mut swarm: Swarm,
/// # event: SwarmEvent,
/// # state: SwarmState,
/// # ) -> anyhow::Result<()> {
/// # Ok(())
/// # }
/// ```
#[must_use]
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.
///
/// Currently you can have up to one swarm handler.
///
/// ```
/// # use azalea::{prelude::*, swarm::prelude::*};
/// # let swarm_builder = SwarmBuilder::new().set_handler(handle);
/// swarm_builder.set_swarm_handler(swarm_handle);
///
/// # #[derive(Component, Default, Clone)]
/// # struct State {}
///
/// # async fn handle(mut bot: Client, event: Event, state: State) -> anyhow::Result<()> {
/// # Ok(())
/// # }
///
/// #[derive(Resource, Default, Clone)]
/// struct SwarmState {}
/// async fn swarm_handle(
/// mut swarm: Swarm,
/// event: SwarmEvent,
/// state: SwarmState,
/// ) -> anyhow::Result<()> {
/// Ok(())
/// }
/// ```
#[must_use]
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
@ -168,71 +264,6 @@ where
self
}
/// 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.
///
/// ```
/// # use azalea::{prelude::*, swarm::prelude::*};
/// # let swarm_builder = SwarmBuilder::new().set_swarm_handler(swarm_handle);
/// swarm_builder.set_handler(handle);
///
/// #[derive(Component, Default, Clone)]
/// struct State {}
/// async fn handle(mut bot: Client, event: Event, state: State) -> anyhow::Result<()> {
/// Ok(())
/// }
///
/// # #[derive(Resource, Default, Clone)]
/// # struct SwarmState {}
/// # async fn swarm_handle(
/// # mut swarm: Swarm,
/// # event: SwarmEvent,
/// # state: SwarmState,
/// # ) -> anyhow::Result<()> {
/// # Ok(())
/// # }
/// ```
#[must_use]
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.
///
/// You must have exactly one client handler and one swarm handler, calling
/// this again will replace the old swarm handler function.
///
/// ```
/// # use azalea::{prelude::*, swarm::prelude::*};
/// # let swarm_builder = SwarmBuilder::new().set_handler(handle);
/// swarm_builder.set_swarm_handler(swarm_handle);
///
/// # #[derive(Component, Default, Clone)]
/// # struct State {}
///
/// # async fn handle(mut bot: Client, event: Event, state: State) -> anyhow::Result<()> {
/// # Ok(())
/// # }
///
/// #[derive(Resource, Default, Clone)]
/// struct SwarmState {}
/// async fn swarm_handle(
/// mut swarm: Swarm,
/// event: SwarmEvent,
/// state: SwarmState,
/// ) -> anyhow::Result<()> {
/// Ok(())
/// }
/// ```
#[must_use]
pub fn set_swarm_handler(mut self, handler: SwarmHandleFn<SwarmFut, SS>) -> Self {
self.swarm_handler = Some(handler);
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;