From 2eade86cf7a12a6ec64496aedbfc3d3a3bd44e1a Mon Sep 17 00:00:00 2001 From: mat Date: Sun, 23 Oct 2022 16:51:49 -0500 Subject: [PATCH] make `handle` cleaner Arc -> Event, Arc> -> State Items in State now need to have interior mutability (i.e. Arc>), but it's a worthwhile tradeoff since it allows the user to customize it for each field --- azalea-client/src/chat.rs | 2 +- azalea/examples/craft_dig_straight_down.rs | 16 +++--- azalea/examples/echo.rs | 13 ++--- azalea/examples/mine_a_chunk.rs | 43 ++++++++-------- azalea/examples/potatobot/autoeat.rs | 8 +-- azalea/examples/potatobot/main.rs | 12 ++--- azalea/examples/pvp.rs | 18 ++++--- azalea/src/bot.rs | 19 ++++--- azalea/src/lib.rs | 58 ++++++++++++++++------ bot/src/main.rs | 10 ++-- 10 files changed, 114 insertions(+), 85 deletions(-) diff --git a/azalea-client/src/chat.rs b/azalea-client/src/chat.rs index 9e3d58a0..fecd76ae 100644 --- a/azalea-client/src/chat.rs +++ b/azalea-client/src/chat.rs @@ -83,7 +83,7 @@ impl Client { /// # } /// ``` pub async fn chat(&self, message: &str) -> Result<(), std::io::Error> { - if message.chars().next() == Some('/') { + if message.starts_with('/') { self.send_command_packet(&message[1..]).await } else { self.send_chat_packet(message).await diff --git a/azalea/examples/craft_dig_straight_down.rs b/azalea/examples/craft_dig_straight_down.rs index 48e1fd22..9e675f28 100644 --- a/azalea/examples/craft_dig_straight_down.rs +++ b/azalea/examples/craft_dig_straight_down.rs @@ -3,9 +3,9 @@ use azalea::{Bot, Client, Event}; use parking_lot::Mutex; use std::sync::Arc; -#[derive(Default)] +#[derive(Default, Clone)] struct State { - pub started: bool, + pub started: Arc>, } #[tokio::main] @@ -16,7 +16,7 @@ async fn main() { azalea::start(azalea::Options { account, address: "localhost", - state: Arc::new(Mutex::new(State::default())), + state: State::default(), plugins: vec![], handle, }) @@ -24,13 +24,13 @@ async fn main() { .unwrap(); } -async fn handle(bot: Client, event: Arc, state: Arc>) { +async fn handle(bot: Client, event: Event, state: State) -> anyhow::Result<()> { match event { - Event::Message(m) => { + Event::Chat(m) => { if m.username == bot.player.username { - return; + return Ok(()); }; - if m.message = "go" { + if m.content == "go" { // make sure we only start once let ctx_lock = ctx.lock().unwrap(); if ctx_lock.started { @@ -74,4 +74,6 @@ async fn handle(bot: Client, event: Arc, state: Arc>) { } _ => {} } + + Ok(()) } diff --git a/azalea/examples/echo.rs b/azalea/examples/echo.rs index a5280d8b..07dd50c1 100644 --- a/azalea/examples/echo.rs +++ b/azalea/examples/echo.rs @@ -12,7 +12,7 @@ async fn main() { azalea::start(azalea::Options { account, address: "localhost", - state: Arc::new(Mutex::new(State::default())), + state: State::default(), plugins: vec![], handle, }) @@ -20,19 +20,16 @@ async fn main() { .unwrap(); } +#[derive(Default, Clone)] pub struct State {} -async fn handle(bot: Client, event: Arc, state: Arc>) -> anyhow::Result<()> { - match *event { +async fn handle(bot: Client, event: Event, state: State) -> anyhow::Result<()> { + match event { Event::Chat(m) => { if m.username == bot.username { return Ok(()); // ignore our own messages }; - bot.chat(m.message).await; - } - Event::Kick(m) => { - println!(m); - bot.reconnect().await.unwrap(); + bot.chat(m.content).await; } _ => {} } diff --git a/azalea/examples/mine_a_chunk.rs b/azalea/examples/mine_a_chunk.rs index bc576513..5f1dabe1 100644 --- a/azalea/examples/mine_a_chunk.rs +++ b/azalea/examples/mine_a_chunk.rs @@ -1,4 +1,4 @@ -use azalea::{pathfinder, Account, Accounts, Client, Event}; +use azalea::{pathfinder, Account, Accounts, Client, Event, Swarm}; use parking_lot::Mutex; use std::sync::Arc; @@ -7,31 +7,31 @@ async fn main() { let accounts = Accounts::new(); for i in 0..10 { - accounts.add(Account::offline(format!("bot{}", i))); + accounts.add(Account::offline(&format!("bot{}", i))); } - azalea::start_group(azalea::GroupOptions { + azalea::start_swarm(azalea::SwarmOptions { accounts, address: "localhost", - group_state: Arc::new(Mutex::new(State::default())), + swarm_state: Arc::new(Mutex::new(State::default())), state: State::default(), - group_plugins: vec![Arc::new(pathfinder::Plugin::default())], + swarm_plugins: vec![Arc::new(pathfinder::Plugin::default())], plugins: vec![], handle: Box::new(handle), - group_handle: Box::new(handle), + swarm_handle: Box::new(swarm_handle), }) .await .unwrap(); } -#[derive(Default)] +#[derive(Default, Clone)] struct State {} -#[derive(Default)] -struct GroupState {} +#[derive(Default, Clone)] +struct SwarmState {} async fn handle(bot: Client, event: Arc, state: Arc>) -> anyhow::Result<()> { match event { @@ -41,26 +41,27 @@ async fn handle(bot: Client, event: Arc, state: Arc>) -> any Ok(()) } -async fn group_handle( - bots: Swarm, +async fn swarm_handle( + swarm: Swarm, event: Arc, - state: Arc>, + state: Arc>, ) -> anyhow::Result<()> { match *event { Event::Login => { - bots.goto(azalea::BlockPos::new(0, 70, 0)).await; + swarm.goto(azalea::BlockPos::new(0, 70, 0)).await; // or bots.goto_goal(pathfinder::Goals::Goto(azalea::BlockPos(0, 70, 0))).await; // destroy the blocks in this area and then leave - bots.fill( - azalea::Selection::Range( - azalea::BlockPos::new(0, 0, 0), - azalea::BlockPos::new(16, 255, 16), - ), - azalea::block::Air, - ) - .await; + swarm + .fill( + azalea::Selection::Range( + azalea::BlockPos::new(0, 0, 0), + azalea::BlockPos::new(16, 255, 16), + ), + azalea::block::Air, + ) + .await; } _ => {} } diff --git a/azalea/examples/potatobot/autoeat.rs b/azalea/examples/potatobot/autoeat.rs index d1296c29..8042e2a2 100644 --- a/azalea/examples/potatobot/autoeat.rs +++ b/azalea/examples/potatobot/autoeat.rs @@ -4,17 +4,17 @@ use async_trait::async_trait; use azalea::{Client, Event}; use std::sync::{Arc, Mutex}; -#[derive(Default)] +#[derive(Default, Clone)] pub struct Plugin { - pub state: Arc>, + pub state: State, } -#[derive(Default)] +#[derive(Default, Clone)] pub struct State {} #[async_trait] impl azalea::Plugin for Plugin { - async fn handle(self: Arc, bot: Client, event: Arc) { + async fn handle(self: Box, event: Event, bot: Client) { match event { Event::UpdateHunger => { if !bot.using_held_item() && bot.food_level() <= 17 { diff --git a/azalea/examples/potatobot/main.rs b/azalea/examples/potatobot/main.rs index a04b199d..5398c68a 100644 --- a/azalea/examples/potatobot/main.rs +++ b/azalea/examples/potatobot/main.rs @@ -17,10 +17,10 @@ async fn main() { azalea::start(azalea::Options { account, address: "localhost", - state: Arc::new(Mutex::new(State::default())), + state: State::default(), plugins: vec![ - Arc::new(autoeat::Plugin::default()), - Arc::new(pathfinder::Plugin::default()), + Box::new(autoeat::Plugin::default()), + Box::new(pathfinder::Plugin::default()), ], handle, }) @@ -28,7 +28,7 @@ async fn main() { .unwrap(); } -async fn handle(bot: Client, event: Arc, state: Arc>) -> anyhow::Result<()> { +async fn handle(bot: Client, event: Event, state: State) -> anyhow::Result<()> { match event { Event::Login => { goto_farm(bot, state).await?; @@ -42,14 +42,14 @@ async fn handle(bot: Client, event: Arc, state: Arc>) -> any } // go to the place where we start farming -async fn goto_farm(bot: Client, state: Arc>) -> anyhow::Result<()> { +async fn goto_farm(bot: Client, state: State) -> anyhow::Result<()> { bot.goto(pathfinder::Goals::Near(5, BlockPos::new(0, 70, 0))) .await?; Ok(()) } // go to the chest and deposit everything in our inventory. -async fn deposit(bot: &mut Client, state: &mut Arc>) -> anyhow::Result<()> { +async fn deposit(bot: &mut Client, state: State) -> anyhow::Result<()> { // first throw away any garbage we might have bot.toss(|item| item.kind != ItemKind::Potato && item.kind != ItemKind::DiamondHoe); diff --git a/azalea/examples/pvp.rs b/azalea/examples/pvp.rs index a2f070f0..8c133576 100644 --- a/azalea/examples/pvp.rs +++ b/azalea/examples/pvp.rs @@ -7,7 +7,7 @@ async fn main() { let accounts = Accounts::new(); for i in 0..10 { - accounts.add(Account::offline(format!("bot{}", i))); + accounts.add(Account::offline(&format!("bot{}", i))); } azalea::start_swarm(azalea::SwarmOptions { @@ -21,24 +21,28 @@ async fn main() { plugins: vec![], handle: Box::new(handle), - swarm_handle: Box::new(handle), + swarm_handle: Box::new(swarm_handle), }) .await .unwrap(); } +#[derive(Default, Clone)] struct State {} + +#[derive(Default, Clone)] struct SwarmState {} -async fn handle(bots: Client, event: Arc, state: Arc>) { - match *event { +async fn handle(bot: Client, event: Event, state: State) {} +async fn swarm_handle(swarm: Swarm, event: Event, state: State) { + match event { Event::Tick => { // choose an arbitrary player within render distance to target - if let Some(target) = bots - .dimension() + if let Some(target) = swarm + .dimension .find_one_entity(|e| e.id == "minecraft:player") { - for bot in bots { + for bot in swarm { bot.tick_goto_goal(pathfinder::Goals::Reach(target.bounding_box)); // if target.bounding_box.distance(bot.eyes) < bot.reach_distance() { if bot.entity.can_reach(target.bounding_box) { diff --git a/azalea/src/bot.rs b/azalea/src/bot.rs index 26e35fda..566ab1e7 100644 --- a/azalea/src/bot.rs +++ b/azalea/src/bot.rs @@ -3,14 +3,14 @@ use async_trait::async_trait; use parking_lot::Mutex; use std::sync::Arc; -#[derive(Default)] +#[derive(Default, Clone)] pub struct Plugin { - pub state: Arc>, + pub state: State, } -#[derive(Default)] +#[derive(Default, Clone)] pub struct State { - jumping_once: bool, + jumping_once: Arc>, } pub trait BotTrait { @@ -18,7 +18,7 @@ pub trait BotTrait { } impl BotTrait for azalea_client::Client { - /// Try to jump next tick. + /// Queue a jump for the next tick. fn jump(&self) { let player_lock = self.player.lock(); let mut dimension_lock = self.dimension.lock(); @@ -33,12 +33,11 @@ impl BotTrait for azalea_client::Client { #[async_trait] impl crate::Plugin for Plugin { - async fn handle(self: Arc, event: Arc, mut bot: Client) { - if let Event::Tick = *event { - let mut state = self.state.lock(); - if state.jumping_once { + async fn handle(self: Box, event: Event, mut bot: Client) { + if let Event::Tick = event { + if *self.state.jumping_once.lock() { if bot.jumping() { - state.jumping_once = false; + *self.state.jumping_once.lock() = false; } else { bot.set_jumping(true); } diff --git a/azalea/src/lib.rs b/azalea/src/lib.rs index 19384761..6a000e6d 100644 --- a/azalea/src/lib.rs +++ b/azalea/src/lib.rs @@ -30,6 +30,7 @@ //! .unwrap(); //! } //! +//! #[derive(Default, Clone)] //! pub struct State {} //! //! async fn handle(bot: Client, event: Arc, state: Arc>) -> anyhow::Result<()> { @@ -52,17 +53,35 @@ pub mod prelude; use async_trait::async_trait; pub use azalea_client::*; use azalea_protocol::ServerAddress; -use parking_lot::Mutex; -use std::{future::Future, sync::Arc}; +use std::future::Future; use thiserror::Error; /// Plugins can keep their own personal state, listen to events, and add new functions to Client. #[async_trait] -pub trait Plugin: Send + Sync { - async fn handle(self: Arc, event: Arc, bot: Client); +pub trait Plugin: Send + Sync + PluginClone + 'static { + async fn handle(self: Box, event: Event, bot: Client); } -pub type HandleFn = fn(Client, Arc, Arc>) -> Fut; +/// An internal trait that allows Plugin to be cloned. +#[doc(hidden)] +pub trait PluginClone { + fn clone_box(&self) -> Box; +} +impl PluginClone for T +where + T: 'static + Plugin + Clone, +{ + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } +} +impl Clone for Box { + fn clone(&self) -> Self { + self.clone_box() + } +} + +pub type HandleFn = fn(Client, Event, S) -> Fut; /// The options that are passed to [`azalea::start`]. /// @@ -80,10 +99,22 @@ where pub account: Account, /// A list of plugins that are going to be used. Plugins are external /// crates that add extra functionality to Azalea. - pub plugins: Vec>, + pub plugins: Vec>, /// A struct that contains the data that you want your bot to remember /// across events. - pub state: Arc>, + /// + /// # Examples + /// + /// ```rust + /// use parking_lot::Mutex; + /// use std::sync::Arc; + /// + /// #[derive(Default, Clone)] + /// struct State { + /// farming: Arc>, + /// } + /// ``` + pub state: S, /// The function that's called whenever we get an event. pub handle: HandleFn, } @@ -107,7 +138,7 @@ pub enum Error { /// }).await.unwrap(); /// ``` pub async fn start< - S: Send + 'static, + S: Send + Sync + Clone + 'static, A: Send + TryInto, Fut: Future> + Send + 'static, >( @@ -121,19 +152,16 @@ pub async fn start< let (bot, mut rx) = Client::join(&options.account, address).await.unwrap(); let state = options.state; - let bot_plugin = Arc::new(bot::Plugin::default()); + let bot_plugin = bot::Plugin::default(); while let Some(event) = rx.recv().await { - // we put it into an Arc so it's cheaper to clone - - let event = Arc::new(event); - for plugin in &options.plugins { - tokio::spawn(plugin.clone().handle(event.clone(), bot.clone())); + let plugin = plugin.clone(); + tokio::spawn(plugin.handle(event.clone(), bot.clone())); } tokio::spawn(bot::Plugin::handle( - bot_plugin.clone(), + Box::new(bot_plugin.clone()), event.clone(), bot.clone(), )); diff --git a/bot/src/main.rs b/bot/src/main.rs index beed4320..9f8bd0c8 100644 --- a/bot/src/main.rs +++ b/bot/src/main.rs @@ -1,9 +1,7 @@ use azalea::prelude::*; use azalea::{Account, Client, Event}; -use parking_lot::Mutex; -use std::sync::Arc; -#[derive(Default)] +#[derive(Default, Clone)] struct State {} #[tokio::main] @@ -15,7 +13,7 @@ async fn main() -> anyhow::Result<()> { azalea::start(azalea::Options { account, address: "localhost", - state: Arc::new(Mutex::new(State::default())), + state: State::default(), plugins: vec![], handle, }) @@ -25,8 +23,8 @@ async fn main() -> anyhow::Result<()> { Ok(()) } -async fn handle(bot: Client, event: Arc, _state: Arc>) -> anyhow::Result<()> { - match *event { +async fn handle(bot: Client, event: Event, _state: State) -> anyhow::Result<()> { + match event { Event::Login => { bot.chat("Hello world").await?; }