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:
parent
8a90a8e109
commit
f0ff8e7f29
2 changed files with 169 additions and 121 deletions
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Reference in a new issue