From cabc8b60a729ba17f5b75f7a7956c6d1ddcc8919 Mon Sep 17 00:00:00 2001 From: mat Date: Wed, 6 May 2026 18:38:23 -0545 Subject: azalea-brigadier now allows commands to return a Result --- CHANGELOG.md | 5 +- .../src/arguments/bool_argument_type.rs | 2 +- .../src/arguments/double_argument_type.rs | 2 +- .../src/arguments/float_argument_type.rs | 2 +- .../src/arguments/integer_argument_type.rs | 2 +- .../src/arguments/long_argument_type.rs | 2 +- .../src/arguments/string_argument_type.rs | 2 +- azalea-brigadier/src/builder/argument_builder.rs | 48 ++++----- .../src/builder/literal_argument_builder.rs | 4 +- .../src/builder/required_argument_builder.rs | 22 ++--- azalea-brigadier/src/command_dispatcher.rs | 56 +++++------ azalea-brigadier/src/context/command_context.rs | 30 +++--- .../src/context/command_context_builder.rs | 34 +++---- azalea-brigadier/src/context/context_chain.rs | 81 +++++++++------ .../src/context/parsed_command_node.rs | 6 +- azalea-brigadier/src/context/suggestion_context.rs | 4 +- .../src/errors/command_syntax_error.rs | 4 +- azalea-brigadier/src/errors/mod.rs | 21 ++++ azalea-brigadier/src/modifier.rs | 4 +- azalea-brigadier/src/parse_results.rs | 8 +- azalea-brigadier/src/result_consumer.rs | 14 ++- .../src/suggestion/suggestion_provider.rs | 4 +- azalea-brigadier/src/tree/mod.rs | 49 ++++----- azalea-brigadier/tests/bevy_app_usage.rs | 4 +- azalea-brigadier/tests/command_dispatcher_test.rs | 6 +- azalea/examples/steal.rs | 6 +- azalea/examples/testbot/commands.rs | 5 +- azalea/examples/testbot/commands/combat.rs | 11 +-- azalea/examples/testbot/commands/debug.rs | 110 ++++++++++----------- azalea/examples/testbot/commands/movement.rs | 74 +++++++------- azalea/examples/testbot/main.rs | 17 +++- azalea/src/_docs/performance.md | 5 +- azalea/src/client_impl/mod.rs | 5 +- azalea/src/container.rs | 13 +-- azalea/src/lib.rs | 7 +- 35 files changed, 371 insertions(+), 298 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 37696906..6db37359 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,10 +10,13 @@ is breaking anyways, semantic versioning is not followed. ### Added +- `azalea-brigadier` now optionally allows commands to return a `Result` instead of just an `i32`. +- `azalea-chat` is now re-exported in `azalea::chat`. + ### Changed -- Re-export `azalea-chat` from `azalea`, and move the other `azalea::chat` module to `azalea::client_chat`. - Many functions in `Client` and `EntityRef` now return an `AzaleaResult` instead of panicking when getting entity data fails. +- The previous `azalea::chat` module (from `azalea-client`) was moved to `azalea::client_chat`. ### Fixed diff --git a/azalea-brigadier/src/arguments/bool_argument_type.rs b/azalea-brigadier/src/arguments/bool_argument_type.rs index 5005cda4..51d4a216 100644 --- a/azalea-brigadier/src/arguments/bool_argument_type.rs +++ b/azalea-brigadier/src/arguments/bool_argument_type.rs @@ -34,7 +34,7 @@ impl ArgumentType for Boolean { pub fn bool() -> impl ArgumentType { Boolean } -pub fn get_bool(context: &CommandContext, name: &str) -> Option { +pub fn get_bool(context: &CommandContext, name: &str) -> Option { context .argument(name) .expect("argument with name not found") diff --git a/azalea-brigadier/src/arguments/double_argument_type.rs b/azalea-brigadier/src/arguments/double_argument_type.rs index f4d1d05f..daa1bab3 100644 --- a/azalea-brigadier/src/arguments/double_argument_type.rs +++ b/azalea-brigadier/src/arguments/double_argument_type.rs @@ -51,7 +51,7 @@ impl ArgumentType for Double { pub fn double() -> impl ArgumentType { Double::default() } -pub fn get_double(context: &CommandContext, name: &str) -> Option { +pub fn get_double(context: &CommandContext, name: &str) -> Option { context .argument(name) .unwrap() diff --git a/azalea-brigadier/src/arguments/float_argument_type.rs b/azalea-brigadier/src/arguments/float_argument_type.rs index 766a733f..f68700ec 100644 --- a/azalea-brigadier/src/arguments/float_argument_type.rs +++ b/azalea-brigadier/src/arguments/float_argument_type.rs @@ -51,7 +51,7 @@ impl ArgumentType for Float { pub fn float() -> impl ArgumentType { Float::default() } -pub fn get_float(context: &CommandContext, name: &str) -> Option { +pub fn get_float(context: &CommandContext, name: &str) -> Option { context .argument(name) .unwrap() diff --git a/azalea-brigadier/src/arguments/integer_argument_type.rs b/azalea-brigadier/src/arguments/integer_argument_type.rs index ea547f06..24e9c95f 100644 --- a/azalea-brigadier/src/arguments/integer_argument_type.rs +++ b/azalea-brigadier/src/arguments/integer_argument_type.rs @@ -51,7 +51,7 @@ impl ArgumentType for Integer { pub fn integer() -> impl ArgumentType { Integer::default() } -pub fn get_integer(context: &CommandContext, name: &str) -> Option { +pub fn get_integer(context: &CommandContext, name: &str) -> Option { context .argument(name) .unwrap() diff --git a/azalea-brigadier/src/arguments/long_argument_type.rs b/azalea-brigadier/src/arguments/long_argument_type.rs index de9b27cc..d4369a0d 100644 --- a/azalea-brigadier/src/arguments/long_argument_type.rs +++ b/azalea-brigadier/src/arguments/long_argument_type.rs @@ -51,7 +51,7 @@ impl ArgumentType for Long { pub fn long() -> impl ArgumentType { Long::default() } -pub fn get_long(context: &CommandContext, name: &str) -> Option { +pub fn get_long(context: &CommandContext, name: &str) -> Option { context .argument(name) .unwrap() diff --git a/azalea-brigadier/src/arguments/string_argument_type.rs b/azalea-brigadier/src/arguments/string_argument_type.rs index b074132f..1cac531a 100644 --- a/azalea-brigadier/src/arguments/string_argument_type.rs +++ b/azalea-brigadier/src/arguments/string_argument_type.rs @@ -52,7 +52,7 @@ pub fn string() -> impl ArgumentType { pub fn greedy_string() -> impl ArgumentType { StringArgument::GreedyPhrase } -pub fn get_string(context: &CommandContext, name: &str) -> Option { +pub fn get_string(context: &CommandContext, name: &str) -> Option { context .argument(name) .unwrap() diff --git a/azalea-brigadier/src/builder/argument_builder.rs b/azalea-brigadier/src/builder/argument_builder.rs index 27320eba..7617d08a 100644 --- a/azalea-brigadier/src/builder/argument_builder.rs +++ b/azalea-brigadier/src/builder/argument_builder.rs @@ -14,11 +14,11 @@ use crate::{ }; #[derive(Debug)] -pub enum ArgumentBuilderType { +pub enum ArgumentBuilderType { Literal(Literal), - Argument(Argument), + Argument(Argument), } -impl Clone for ArgumentBuilderType { +impl Clone for ArgumentBuilderType { fn clone(&self) -> Self { match self { ArgumentBuilderType::Literal(literal) => ArgumentBuilderType::Literal(literal.clone()), @@ -30,20 +30,20 @@ impl Clone for ArgumentBuilderType { } /// A node that hasn't yet been built. -pub struct ArgumentBuilder { - arguments: CommandNode, +pub struct ArgumentBuilder { + arguments: CommandNode, - command: Command, + command: Command, requirement: Arc bool + Send + Sync>, - target: Option>>>, + target: Option>>>, forks: bool, - modifier: Option>>, + modifier: Option>>, } /// A node that isn't yet built. -impl ArgumentBuilder { - pub fn new(value: ArgumentBuilderType) -> Self { +impl ArgumentBuilder { + pub fn new(value: ArgumentBuilderType) -> Self { Self { arguments: CommandNode { value, @@ -65,14 +65,14 @@ impl ArgumentBuilder { /// literal("foo").then(literal("bar").executes(|ctx: &CommandContext<()>| 42)) /// # ; /// ``` - pub fn then(self, argument: ArgumentBuilder) -> Self { + pub fn then(self, argument: ArgumentBuilder) -> Self { self.then_built(argument.build()) } /// Add an already built child node to this node. /// /// You should usually use [`Self::then`] instead. - pub fn then_built(mut self, argument: CommandNode) -> Self { + pub fn then_built(mut self, argument: CommandNode) -> Self { self.arguments.add_child(&Arc::new(RwLock::new(argument))); self } @@ -90,9 +90,9 @@ impl ArgumentBuilder { /// ``` pub fn executes(mut self, f: F) -> Self where - F: Fn(&CommandContext) -> i32 + Send + Sync + 'static, + F: Fn(&CommandContext) -> R + Send + Sync + 'static, { - self.command = Some(Arc::new(move |ctx: &CommandContext| Ok(f(ctx)))); + self.command = Some(Arc::new(move |ctx: &CommandContext| Ok(f(ctx)))); self } @@ -100,7 +100,7 @@ impl ArgumentBuilder { /// CommandSyntaxError>`. pub fn executes_result(mut self, f: F) -> Self where - F: Fn(&CommandContext) -> Result + Send + Sync + 'static, + F: Fn(&CommandContext) -> Result + Send + Sync + 'static, { self.command = Some(Arc::new(f)); self @@ -131,22 +131,22 @@ impl ArgumentBuilder { self } - pub fn redirect(self, target: Arc>>) -> Self { + pub fn redirect(self, target: Arc>>) -> Self { self.forward(target, None, false) } pub fn fork( self, - target: Arc>>, - modifier: Arc>, + target: Arc>>, + modifier: Arc>, ) -> Self { self.forward(target, Some(modifier), true) } pub fn forward( mut self, - target: Arc>>, - modifier: Option>>, + target: Arc>>, + modifier: Option>>, fork: bool, ) -> Self { if !self.arguments.children.is_empty() { @@ -158,13 +158,13 @@ impl ArgumentBuilder { self } - pub fn arguments(&self) -> &CommandNode { + pub fn arguments(&self) -> &CommandNode { &self.arguments } /// Manually build this node into a [`CommandNode`]. You probably don't need /// to do this yourself. - pub fn build(self) -> CommandNode { + pub fn build(self) -> CommandNode { let mut result = CommandNode { value: self.arguments.value, command: self.command, @@ -185,7 +185,7 @@ impl ArgumentBuilder { } } -impl Debug for ArgumentBuilder { +impl Debug for ArgumentBuilder { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ArgumentBuilder") .field("arguments", &self.arguments) @@ -197,7 +197,7 @@ impl Debug for ArgumentBuilder { .finish() } } -impl Clone for ArgumentBuilder { +impl Clone for ArgumentBuilder { fn clone(&self) -> Self { Self { arguments: self.arguments.clone(), diff --git a/azalea-brigadier/src/builder/literal_argument_builder.rs b/azalea-brigadier/src/builder/literal_argument_builder.rs index 7045b79f..c55fbeed 100644 --- a/azalea-brigadier/src/builder/literal_argument_builder.rs +++ b/azalea-brigadier/src/builder/literal_argument_builder.rs @@ -12,13 +12,13 @@ impl Literal { } } -impl From for ArgumentBuilderType { +impl From for ArgumentBuilderType { fn from(literal: Literal) -> Self { Self::Literal(literal) } } /// Shortcut for creating a new literal builder node. -pub fn literal(value: &str) -> ArgumentBuilder { +pub fn literal(value: &str) -> ArgumentBuilder { ArgumentBuilder::new(ArgumentBuilderType::Literal(Literal::new(value))) } diff --git a/azalea-brigadier/src/builder/required_argument_builder.rs b/azalea-brigadier/src/builder/required_argument_builder.rs index 01bfc0bc..e0822db3 100644 --- a/azalea-brigadier/src/builder/required_argument_builder.rs +++ b/azalea-brigadier/src/builder/required_argument_builder.rs @@ -16,16 +16,16 @@ use crate::{ /// An argument node type. /// /// The `T` type parameter is the type of the argument, which can be anything. -pub struct Argument { +pub struct Argument { pub name: String, parser: Arc, - custom_suggestions: Option + Send + Sync>>, + custom_suggestions: Option + Send + Sync>>, } -impl Argument { +impl Argument { pub fn new( name: &str, parser: Arc, - custom_suggestions: Option + Send + Sync>>, + custom_suggestions: Option + Send + Sync>>, ) -> Self { Self { name: name.to_owned(), @@ -40,7 +40,7 @@ impl Argument { pub fn list_suggestions( &self, - context: CommandContext, + context: CommandContext, builder: SuggestionsBuilder, ) -> Suggestions { if let Some(s) = &self.custom_suggestions { @@ -55,13 +55,13 @@ impl Argument { } } -impl From> for ArgumentBuilderType { - fn from(argument: Argument) -> Self { +impl From> for ArgumentBuilderType { + fn from(argument: Argument) -> Self { Self::Argument(argument) } } -impl Debug for Argument { +impl Debug for Argument { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Argument") .field("name", &self.name) @@ -71,14 +71,14 @@ impl Debug for Argument { } /// Shortcut for creating a new argument builder node. -pub fn argument( +pub fn argument( name: &str, parser: impl ArgumentType + Send + Sync + 'static, -) -> ArgumentBuilder { +) -> ArgumentBuilder { ArgumentBuilder::new(Argument::new(name, Arc::new(parser), None).into()) } -impl Clone for Argument { +impl Clone for Argument { fn clone(&self) -> Self { Self { name: self.name.clone(), diff --git a/azalea-brigadier/src/command_dispatcher.rs b/azalea-brigadier/src/command_dispatcher.rs index ecf5d157..5b162964 100644 --- a/azalea-brigadier/src/command_dispatcher.rs +++ b/azalea-brigadier/src/command_dispatcher.rs @@ -11,7 +11,7 @@ use parking_lot::RwLock; use crate::{ builder::argument_builder::ArgumentBuilder, context::{CommandContextBuilder, ContextChain}, - errors::{BuiltInError, CommandSyntaxError}, + errors::{BuiltInError, CommandResultTrait, CommandSyntaxError}, parse_results::ParseResults, result_consumer::{DefaultResultConsumer, ResultConsumer}, string_reader::StringReader, @@ -26,15 +26,15 @@ use crate::{ /// # struct CommandSource; /// let mut subject = CommandDispatcher::::new(); /// ``` -pub struct CommandDispatcher +pub struct CommandDispatcher where Self: Sync + Send, { - pub root: Arc>>, - consumer: Box + Send + Sync>, + pub root: Arc>>, + consumer: Box + Send + Sync>, } -impl CommandDispatcher { +impl CommandDispatcher { pub fn new() -> Self { Self { root: Arc::new(RwLock::new(CommandNode::default())), @@ -49,13 +49,13 @@ impl CommandDispatcher { /// # let mut subject = CommandDispatcher::<()>::new(); /// subject.register(literal("foo").executes(|_| 42)); /// ``` - pub fn register(&mut self, node: ArgumentBuilder) -> Arc>> { + pub fn register(&mut self, node: ArgumentBuilder) -> Arc>> { let build = Arc::new(RwLock::new(node.build())); self.root.write().add_child(&build); build } - pub fn parse(&self, command: StringReader, source: S) -> ParseResults<'_, S> { + pub fn parse(&self, command: StringReader, source: S) -> ParseResults<'_, S, R> { let source = Arc::new(source); let context = CommandContextBuilder::new(self, source, self.root.clone(), command.cursor()); @@ -64,14 +64,14 @@ impl CommandDispatcher { fn parse_nodes<'a>( &'a self, - node: &Arc>>, + node: &Arc>>, original_reader: &StringReader, - context_so_far: CommandContextBuilder<'a, S>, - ) -> Result, CommandSyntaxError> { + context_so_far: CommandContextBuilder<'a, S, R>, + ) -> Result, CommandSyntaxError> { let source = context_so_far.source.clone(); #[allow(clippy::mutable_key_type)] // this is fine because we don't mutate the key - let mut errors = HashMap::>, CommandSyntaxError>::new(); - let mut potentials: Vec> = vec![]; + let mut errors = HashMap::>, CommandSyntaxError>::new(); + let mut potentials: Vec> = vec![]; let cursor = original_reader.cursor(); for child in node.read().get_relevant_nodes(&mut original_reader.clone()) { @@ -183,7 +183,7 @@ impl CommandDispatcher { &self, input: impl Into, source: S, - ) -> Result { + ) -> Result { let input = input.into(); let parse = self.parse(input, source); @@ -191,9 +191,9 @@ impl CommandDispatcher { } pub fn add_paths( - node: Arc>>, - result: &mut Vec>>>>, - parents: Vec>>>, + node: Arc>>, + result: &mut Vec>>>>, + parents: Vec>>>, ) { let mut current = parents; current.push(node.clone()); @@ -204,9 +204,9 @@ impl CommandDispatcher { } } - pub fn get_path(&self, target: CommandNode) -> Vec { + pub fn get_path(&self, target: CommandNode) -> Vec { let rc_target = Arc::new(RwLock::new(target)); - let mut nodes: Vec>>>> = Vec::new(); + let mut nodes: Vec>>>> = Vec::new(); Self::add_paths(self.root.clone(), &mut nodes, vec![]); for list in nodes { @@ -223,7 +223,7 @@ impl CommandDispatcher { vec![] } - pub fn find_node(&self, path: &[&str]) -> Option>>> { + pub fn find_node(&self, path: &[&str]) -> Option>>> { let mut node = self.root.clone(); for name in path { match node.clone().read().child(name) { @@ -239,7 +239,7 @@ impl CommandDispatcher { } /// Executes a given pre-parsed command. - pub fn execute_parsed(&self, parse: ParseResults) -> Result { + pub fn execute_parsed(&self, parse: ParseResults) -> Result { if parse.reader.can_read() { return Err(if parse.exceptions.len() == 1 { parse.exceptions.values().next().unwrap().clone() @@ -263,7 +263,7 @@ impl CommandDispatcher { pub fn get_all_usage( &self, - node: &CommandNode, + node: &CommandNode, source: &S, restricted: bool, ) -> Vec { @@ -274,7 +274,7 @@ impl CommandDispatcher { fn get_all_usage_recursive( &self, - node: &CommandNode, + node: &CommandNode, source: &S, result: &mut Vec, prefix: &str, @@ -325,9 +325,9 @@ impl CommandDispatcher { /// command tree. pub fn get_smart_usage( &self, - node: &CommandNode, + node: &CommandNode, source: &S, - ) -> Vec<(Arc>>, String)> { + ) -> Vec<(Arc>>, String)> { let mut result = Vec::new(); let optional = node.command.is_some(); @@ -343,7 +343,7 @@ impl CommandDispatcher { fn get_smart_usage_recursive( &self, - node: &CommandNode, + node: &CommandNode, source: &S, optional: bool, deep: bool, @@ -435,13 +435,13 @@ impl CommandDispatcher { Some(this) } - pub fn get_completion_suggestions(parse: ParseResults) -> Suggestions { + pub fn get_completion_suggestions(parse: ParseResults) -> Suggestions { let cursor = parse.reader.total_length(); Self::get_completion_suggestions_with_cursor(parse, cursor) } pub fn get_completion_suggestions_with_cursor( - parse: ParseResults, + parse: ParseResults, cursor: usize, ) -> Suggestions { let context = parse.context; @@ -471,7 +471,7 @@ impl CommandDispatcher { } } -impl Default for CommandDispatcher { +impl Default for CommandDispatcher { fn default() -> Self { Self::new() } diff --git a/azalea-brigadier/src/context/command_context.rs b/azalea-brigadier/src/context/command_context.rs index a9959895..0139f7e9 100644 --- a/azalea-brigadier/src/context/command_context.rs +++ b/azalea-brigadier/src/context/command_context.rs @@ -15,20 +15,20 @@ use crate::{ }; /// A built `CommandContextBuilder`. -pub struct CommandContext { +pub struct CommandContext { pub source: Arc, pub(super) input: String, pub(super) arguments: HashMap, - pub(super) command: Command, - pub(super) root_node: Arc>>, - pub(super) nodes: Vec>, + pub(super) command: Command, + pub(super) root_node: Arc>>, + pub(super) nodes: Vec>, pub(super) range: StringRange, - pub(super) child: Option>>, - pub(super) modifier: Option>>, + pub(super) child: Option>>, + pub(super) modifier: Option>>, pub(super) forks: bool, } -impl Clone for CommandContext { +impl Clone for CommandContext { fn clone(&self) -> Self { Self { source: self.source.clone(), @@ -45,7 +45,7 @@ impl Clone for CommandContext { } } -impl Debug for CommandContext { +impl Debug for CommandContext { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("CommandContext") // .field("source", &self.source) @@ -62,7 +62,7 @@ impl Debug for CommandContext { } } -impl CommandContext { +impl CommandContext { pub fn copy_for(&self, source: Arc) -> Self { if Arc::ptr_eq(&source, &self.source) { // fast path @@ -83,11 +83,11 @@ impl CommandContext { } } - pub fn child(&self) -> Option<&CommandContext> { + pub fn child(&self) -> Option<&CommandContext> { self.child.as_ref().map(|c| c.as_ref()) } - pub fn last_child(&self) -> &CommandContext { + pub fn last_child(&self) -> &CommandContext { let mut result = self; while let Some(child) = result.child() { result = child; @@ -95,7 +95,7 @@ impl CommandContext { result } - pub fn command(&self) -> &Command { + pub fn command(&self) -> &Command { &self.command } @@ -104,7 +104,7 @@ impl CommandContext { argument.map(|a| a.result.as_ref()) } - pub fn redirect_modifier(&self) -> Option<&RedirectModifier> { + pub fn redirect_modifier(&self) -> Option<&RedirectModifier> { self.modifier.as_ref().map(|m| m.as_ref()) } @@ -116,11 +116,11 @@ impl CommandContext { &self.input } - pub fn root_node(&self) -> &Arc>> { + pub fn root_node(&self) -> &Arc>> { &self.root_node } - pub fn nodes(&self) -> &[ParsedCommandNode] { + pub fn nodes(&self) -> &[ParsedCommandNode] { &self.nodes } diff --git a/azalea-brigadier/src/context/command_context_builder.rs b/azalea-brigadier/src/context/command_context_builder.rs index a3819246..e3b09fc1 100644 --- a/azalea-brigadier/src/context/command_context_builder.rs +++ b/azalea-brigadier/src/context/command_context_builder.rs @@ -17,20 +17,20 @@ use crate::{ tree::{Command, CommandNode}, }; -pub struct CommandContextBuilder<'a, S> { +pub struct CommandContextBuilder<'a, S, R> { pub arguments: HashMap, - pub root: Arc>>, - pub nodes: Vec>, - pub dispatcher: &'a CommandDispatcher, + pub root: Arc>>, + pub nodes: Vec>, + pub dispatcher: &'a CommandDispatcher, pub source: Arc, - pub command: Command, - pub child: Option>>, + pub command: Command, + pub child: Option>>, pub range: StringRange, - pub modifier: Option>>, + pub modifier: Option>>, pub forks: bool, } -impl Clone for CommandContextBuilder<'_, S> { +impl Clone for CommandContextBuilder<'_, S, R> { fn clone(&self) -> Self { Self { arguments: self.arguments.clone(), @@ -47,11 +47,11 @@ impl Clone for CommandContextBuilder<'_, S> { } } -impl<'a, S> CommandContextBuilder<'a, S> { +impl<'a, S, R> CommandContextBuilder<'a, S, R> { pub fn new( - dispatcher: &'a CommandDispatcher, + dispatcher: &'a CommandDispatcher, source: Arc, - root_node: Arc>>, + root_node: Arc>>, start: usize, ) -> Self { Self { @@ -68,11 +68,11 @@ impl<'a, S> CommandContextBuilder<'a, S> { } } - pub fn with_command(&mut self, command: &Command) -> &Self { + pub fn with_command(&mut self, command: &Command) -> &Self { self.command.clone_from(command); self } - pub fn with_child(&mut self, child: Rc>) -> &Self { + pub fn with_child(&mut self, child: Rc>) -> &Self { self.child = Some(child); self } @@ -80,7 +80,7 @@ impl<'a, S> CommandContextBuilder<'a, S> { self.arguments.insert(name.to_owned(), argument); self } - pub fn with_node(&mut self, node: Arc>>, range: StringRange) -> &Self { + pub fn with_node(&mut self, node: Arc>>, range: StringRange) -> &Self { self.nodes.push(ParsedCommandNode { node: node.clone(), range, @@ -91,7 +91,7 @@ impl<'a, S> CommandContextBuilder<'a, S> { self } - pub fn build(&self, input: &str) -> CommandContext { + pub fn build(&self, input: &str) -> CommandContext { CommandContext { arguments: self.arguments.clone(), root_node: self.root.clone(), @@ -106,7 +106,7 @@ impl<'a, S> CommandContextBuilder<'a, S> { } } - pub fn find_suggestion_context(&self, cursor: usize) -> SuggestionContext { + pub fn find_suggestion_context(&self, cursor: usize) -> SuggestionContext { if self.range.start() > cursor { panic!("Can't find node before cursor"); } @@ -144,7 +144,7 @@ impl<'a, S> CommandContextBuilder<'a, S> { } } -impl Debug for CommandContextBuilder<'_, S> { +impl Debug for CommandContextBuilder<'_, S, R> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("CommandContextBuilder") // .field("arguments", &self.arguments) diff --git a/azalea-brigadier/src/context/context_chain.rs b/azalea-brigadier/src/context/context_chain.rs index 8ad5b320..ab66fcf9 100644 --- a/azalea-brigadier/src/context/context_chain.rs +++ b/azalea-brigadier/src/context/context_chain.rs @@ -1,16 +1,22 @@ use std::{rc::Rc, sync::Arc}; use super::CommandContext; -use crate::{errors::CommandSyntaxError, result_consumer::ResultConsumer}; - -pub struct ContextChain { - modifiers: Vec>>, - executable: Rc>, - next_stage_cache: Option>>, +use crate::{ + errors::{CommandResultTrait, CommandSyntaxError}, + result_consumer::ResultConsumer, +}; + +pub struct ContextChain { + modifiers: Vec>>, + executable: Rc>, + next_stage_cache: Option>>, } -impl ContextChain { - pub fn new(modifiers: Vec>>, executable: Rc>) -> Self { +impl ContextChain { + pub fn new( + modifiers: Vec>>, + executable: Rc>, + ) -> Self { if executable.command.is_none() { panic!("Last command in chain must be executable"); } @@ -21,7 +27,7 @@ impl ContextChain { } } - pub fn try_flatten(root_context: Rc>) -> Option { + pub fn try_flatten(root_context: Rc>) -> Option { let mut modifiers = Vec::new(); let mut current = root_context; loop { @@ -39,9 +45,9 @@ impl ContextChain { } pub fn run_modifier( - modifier: Rc>, + modifier: Rc>, source: Arc, - result_consumer: &dyn ResultConsumer, + result_consumer: &dyn ResultConsumer, forked_mode: bool, ) -> Result>, CommandSyntaxError> { let source_modifier = modifier.redirect_modifier(); @@ -64,33 +70,43 @@ impl ContextChain { pub fn run_executable( &self, - executable: Rc>, + executable: Rc>, source: Arc, - result_consumer: &dyn ResultConsumer, + result_consumer: &dyn ResultConsumer, forked_mode: bool, - ) -> Result { + ) -> Result { let context_to_use = Rc::new(executable.copy_for(source)); let Some(command) = &executable.command else { unimplemented!(); }; - let err = match (command)(&context_to_use) { - Ok(result) => { - result_consumer.on_command_complete(context_to_use, true, result); - return if forked_mode { Ok(1) } else { Ok(result) }; + let res = (command)(&context_to_use); + let err = match res { + Ok(res) => { + let Some(res) = res.as_i32() else { + // these are treated as exceptions, so they can bubble up without doing anything + // else + return Ok(res); + }; + result_consumer.on_command_complete(context_to_use, true, res); + return if forked_mode { + Ok(R::new(1)) + } else { + Ok(R::new(res)) + }; } Err(err) => err, }; result_consumer.on_command_complete(context_to_use, false, 0); - if forked_mode { Ok(0) } else { Err(err) } + if forked_mode { Ok(R::new(0)) } else { Err(err) } } pub fn execute_all( &self, source: Arc, - result_consumer: &dyn ResultConsumer, - ) -> Result { + result_consumer: &dyn ResultConsumer, + ) -> Result { if self.modifiers.is_empty() { return self.run_executable(self.executable.clone(), source, result_consumer, false); } @@ -103,30 +119,37 @@ impl ContextChain { let mut next_sources = Vec::new(); for source_to_run in current_sources { - next_sources.extend(Self::run_modifier( + match Self::run_modifier( modifier.clone(), source_to_run.clone(), result_consumer, forked_mode, - )?); + ) { + Ok(res) => next_sources.extend(res), + Err(err) => return Err(err), + } } if next_sources.is_empty() { - return Ok(0); + return Ok(R::new(0)); } current_sources = next_sources; } - let mut result = 0; + let mut summed = 0; for execution_source in current_sources { - result += self.run_executable( + let res = self.run_executable( self.executable.clone(), execution_source, result_consumer, forked_mode, )?; + match res.as_i32() { + Some(res) => summed += res, + None => return Ok(res), + } } - Ok(result) + Ok(R::new(summed)) } pub fn stage(&self) -> Stage { @@ -137,14 +160,14 @@ impl ContextChain { } } - pub fn top_context(&self) -> Rc> { + pub fn top_context(&self) -> Rc> { self.modifiers .first() .cloned() .unwrap_or_else(|| self.executable.clone()) } - pub fn next_stage(&mut self) -> Option>> { + pub fn next_stage(&mut self) -> Option>> { let modifier_count = self.modifiers.len(); if modifier_count == 0 { return None; diff --git a/azalea-brigadier/src/context/parsed_command_node.rs b/azalea-brigadier/src/context/parsed_command_node.rs index 62da13d8..8dbadb49 100644 --- a/azalea-brigadier/src/context/parsed_command_node.rs +++ b/azalea-brigadier/src/context/parsed_command_node.rs @@ -6,12 +6,12 @@ use super::string_range::StringRange; use crate::tree::CommandNode; #[derive(Debug)] -pub struct ParsedCommandNode { - pub node: Arc>>, +pub struct ParsedCommandNode { + pub node: Arc>>, pub range: StringRange, } -impl Clone for ParsedCommandNode { +impl Clone for ParsedCommandNode { fn clone(&self) -> Self { Self { node: self.node.clone(), diff --git a/azalea-brigadier/src/context/suggestion_context.rs b/azalea-brigadier/src/context/suggestion_context.rs index 58a73fdb..ed3dbfa2 100644 --- a/azalea-brigadier/src/context/suggestion_context.rs +++ b/azalea-brigadier/src/context/suggestion_context.rs @@ -5,7 +5,7 @@ use parking_lot::RwLock; use crate::tree::CommandNode; #[derive(Debug)] -pub struct SuggestionContext { - pub parent: Arc>>, +pub struct SuggestionContext { + pub parent: Arc>>, pub start_pos: usize, } diff --git a/azalea-brigadier/src/errors/command_syntax_error.rs b/azalea-brigadier/src/errors/command_syntax_error.rs index fd97b050..a29c95ee 100644 --- a/azalea-brigadier/src/errors/command_syntax_error.rs +++ b/azalea-brigadier/src/errors/command_syntax_error.rs @@ -77,8 +77,8 @@ impl CommandSyntaxError { &self.kind } - pub fn input(&self) -> &Option { - &self.input + pub fn input(&self) -> Option<&str> { + self.input.as_deref() } pub fn cursor(&self) -> Option { diff --git a/azalea-brigadier/src/errors/mod.rs b/azalea-brigadier/src/errors/mod.rs index facebe5e..49d0f956 100644 --- a/azalea-brigadier/src/errors/mod.rs +++ b/azalea-brigadier/src/errors/mod.rs @@ -3,3 +3,24 @@ mod command_syntax_error; pub use builtin_errors::BuiltInError; pub use command_syntax_error::CommandSyntaxError; + +pub trait CommandResultTrait { + fn new(i: i32) -> Self; + fn as_i32(&self) -> Option; +} +impl CommandResultTrait for i32 { + fn new(i: i32) -> Self { + i + } + fn as_i32(&self) -> Option { + Some(*self) + } +} +impl CommandResultTrait for Result { + fn new(i: i32) -> Self { + Ok(i) + } + fn as_i32(&self) -> Option { + self.as_ref().copied().ok() + } +} diff --git a/azalea-brigadier/src/modifier.rs b/azalea-brigadier/src/modifier.rs index aba5e95f..90be22db 100644 --- a/azalea-brigadier/src/modifier.rs +++ b/azalea-brigadier/src/modifier.rs @@ -2,5 +2,5 @@ use std::sync::Arc; use crate::{context::CommandContext, errors::CommandSyntaxError}; -pub type RedirectModifier = - dyn Fn(&CommandContext) -> Result>, CommandSyntaxError> + Send + Sync; +pub type RedirectModifier = + dyn Fn(&CommandContext) -> Result>, CommandSyntaxError> + Send + Sync; diff --git a/azalea-brigadier/src/parse_results.rs b/azalea-brigadier/src/parse_results.rs index 7d3cf98a..f77b0b6f 100644 --- a/azalea-brigadier/src/parse_results.rs +++ b/azalea-brigadier/src/parse_results.rs @@ -9,13 +9,13 @@ use crate::{ tree::CommandNode, }; -pub struct ParseResults<'a, S> { - pub context: CommandContextBuilder<'a, S>, +pub struct ParseResults<'a, S, R> { + pub context: CommandContextBuilder<'a, S, R>, pub reader: StringReader, - pub exceptions: HashMap>, CommandSyntaxError>, + pub exceptions: HashMap>, CommandSyntaxError>, } -impl Debug for ParseResults<'_, S> { +impl Debug for ParseResults<'_, S, R> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ParseResults") .field("context", &self.context) diff --git a/azalea-brigadier/src/result_consumer.rs b/azalea-brigadier/src/result_consumer.rs index fb9dd135..bd84dab5 100644 --- a/azalea-brigadier/src/result_consumer.rs +++ b/azalea-brigadier/src/result_consumer.rs @@ -2,11 +2,17 @@ use std::rc::Rc; use crate::context::CommandContext; -pub trait ResultConsumer { - fn on_command_complete(&self, context: Rc>, success: bool, result: i32); +pub trait ResultConsumer { + fn on_command_complete(&self, context: Rc>, success: bool, result: i32); } pub struct DefaultResultConsumer; -impl ResultConsumer for DefaultResultConsumer { - fn on_command_complete(&self, _context: Rc>, _success: bool, _result: i32) {} +impl ResultConsumer for DefaultResultConsumer { + fn on_command_complete( + &self, + _context: Rc>, + _success: bool, + _result: i32, + ) { + } } diff --git a/azalea-brigadier/src/suggestion/suggestion_provider.rs b/azalea-brigadier/src/suggestion/suggestion_provider.rs index 4c3a9f41..058acefb 100644 --- a/azalea-brigadier/src/suggestion/suggestion_provider.rs +++ b/azalea-brigadier/src/suggestion/suggestion_provider.rs @@ -1,10 +1,10 @@ use super::{Suggestions, SuggestionsBuilder}; use crate::context::CommandContext; -pub trait SuggestionProvider { +pub trait SuggestionProvider { fn get_suggestions( &self, - context: CommandContext, + context: CommandContext, builder: SuggestionsBuilder, ) -> Suggestions; } diff --git a/azalea-brigadier/src/tree/mod.rs b/azalea-brigadier/src/tree/mod.rs index b9b11fd6..4cbe2dd5 100644 --- a/azalea-brigadier/src/tree/mod.rs +++ b/azalea-brigadier/src/tree/mod.rs @@ -20,27 +20,27 @@ use crate::{ suggestion::{Suggestions, SuggestionsBuilder}, }; -pub type Command = - Option) -> Result + Send + Sync>>; +pub type Command = + Option) -> Result + Send + Sync>>; /// An ArgumentBuilder that has been built. #[non_exhaustive] -pub struct CommandNode { - pub value: ArgumentBuilderType, +pub struct CommandNode { + pub value: ArgumentBuilderType, // this is a BTreeMap because children need to be ordered when getting command suggestions - pub children: BTreeMap>>>, - pub literals: HashMap>>>, - pub arguments: HashMap>>>, + pub children: BTreeMap>>>, + pub literals: HashMap>>>, + pub arguments: HashMap>>>, - pub command: Command, + pub command: Command, pub requirement: Arc bool + Send + Sync>, - pub redirect: Option>>>, + pub redirect: Option>>>, pub forks: bool, - pub modifier: Option>>, + pub modifier: Option>>, } -impl Clone for CommandNode { +impl Clone for CommandNode { fn clone(&self) -> Self { Self { value: self.value.clone(), @@ -56,7 +56,7 @@ impl Clone for CommandNode { } } -impl CommandNode { +impl CommandNode { /// Returns the value as a literal from this command node, assuming it's /// already been checked. /// @@ -77,14 +77,17 @@ impl CommandNode { /// /// Will panic if this node is not an argument. Consider using a match /// statement instead. - pub fn argument(&self) -> &Argument { + pub fn argument(&self) -> &Argument { match self.value { ArgumentBuilderType::Argument(ref argument) => argument, _ => panic!("CommandNode::argument() called on non-argument node"), } } - pub fn get_relevant_nodes(&self, input: &mut StringReader) -> Vec>>> { + pub fn get_relevant_nodes( + &self, + input: &mut StringReader, + ) -> Vec>>> { let literals = &self.literals; if literals.is_empty() { @@ -114,7 +117,7 @@ impl CommandNode { (self.requirement)(source) } - pub fn add_child(&mut self, node: &Arc>>) { + pub fn add_child(&mut self, node: &Arc>>) { let child = self.children.get(node.read().name()); if let Some(child) = child { // We've found something to merge onto @@ -152,14 +155,14 @@ impl CommandNode { } } - pub fn child(&self, name: &str) -> Option>>> { + pub fn child(&self, name: &str) -> Option>>> { self.children.get(name).cloned() } pub fn parse_with_context( &self, reader: &mut StringReader, - context_builder: &mut CommandContextBuilder, + context_builder: &mut CommandContextBuilder, ) -> Result<(), CommandSyntaxError> { match self.value { ArgumentBuilderType::Argument(ref argument) => { @@ -225,7 +228,7 @@ impl CommandNode { pub fn list_suggestions( &self, - context: CommandContext, + context: CommandContext, builder: SuggestionsBuilder, ) -> Suggestions { match &self.value { @@ -245,7 +248,7 @@ impl CommandNode { } } -impl Debug for CommandNode { +impl Debug for CommandNode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("CommandNode") // .field("value", &self.value) @@ -259,7 +262,7 @@ impl Debug for CommandNode { } } -impl Default for CommandNode { +impl Default for CommandNode { fn default() -> Self { Self { value: ArgumentBuilderType::Literal(Literal::default()), @@ -277,7 +280,7 @@ impl Default for CommandNode { } } -impl Hash for CommandNode { +impl Hash for CommandNode { fn hash(&self, state: &mut H) { // hash the children for (k, v) in &self.children { @@ -289,7 +292,7 @@ impl Hash for CommandNode { } } -impl PartialEq for CommandNode { +impl PartialEq for CommandNode { fn eq(&self, other: &Self) -> bool { if self.children.len() != other.children.len() { return false; @@ -324,4 +327,4 @@ impl PartialEq for CommandNode { true } } -impl Eq for CommandNode {} +impl Eq for CommandNode {} diff --git a/azalea-brigadier/tests/bevy_app_usage.rs b/azalea-brigadier/tests/bevy_app_usage.rs index e0bd8d23..af2cea3b 100644 --- a/azalea-brigadier/tests/bevy_app_usage.rs +++ b/azalea-brigadier/tests/bevy_app_usage.rs @@ -86,7 +86,7 @@ impl DispatchStorage { let result = storage.dispatch.execute("spawn_entity", source.clone()); // Ensure the command was successful - assert_eq!(result, Ok(0)); + assert_eq!(result.unwrap(), 0); // Query the World for the spawned entity let mut world = source.write(); @@ -106,7 +106,7 @@ impl DispatchStorage { .execute("spawn_entity_num 3", source.clone()); // Ensure the command was successful - assert_eq!(result, Ok(0)); + assert_eq!(result.unwrap(), 0); // Query the World for spawned entities let mut world = source.write(); diff --git a/azalea-brigadier/tests/command_dispatcher_test.rs b/azalea-brigadier/tests/command_dispatcher_test.rs index d6a3f2cc..f2766ced 100644 --- a/azalea-brigadier/tests/command_dispatcher_test.rs +++ b/azalea-brigadier/tests/command_dispatcher_test.rs @@ -43,7 +43,7 @@ fn create_and_merge_commands() { #[test] fn execute_unknown_command() { - let mut subject = CommandDispatcher::new(); + let mut subject = CommandDispatcher::<_, i32>::new(); subject.register(literal("bar")); subject.register(literal("baz")); @@ -56,7 +56,7 @@ fn execute_unknown_command() { #[test] fn execute_impermissible_command() { - let mut subject = CommandDispatcher::new(); + let mut subject = CommandDispatcher::<_, i32>::new(); subject.register(literal("foo").requires(|_| false)); let execute_result = subject.execute("foo", &CommandSource {}); @@ -68,7 +68,7 @@ fn execute_impermissible_command() { #[test] fn execute_empty_command() { - let mut subject = CommandDispatcher::new(); + let mut subject = CommandDispatcher::<_, i32>::new(); subject.register(literal("")); let execute_result = subject.execute("", &CommandSource {}); diff --git a/azalea/examples/steal.rs b/azalea/examples/steal.rs index d5c0b913..44d2eb57 100644 --- a/azalea/examples/steal.rs +++ b/azalea/examples/steal.rs @@ -53,9 +53,9 @@ async fn steal(bot: Client, state: State) -> eyre::Result<()> { loop { let chest_block = bot - .world() + .world()? .read() - .find_blocks(bot.position(), &BlockKind::Chest.into()) + .find_blocks(bot.position()?, &BlockKind::Chest.into()) .find( // find the closest chest that hasn't been checked |block_pos| !state.checked_chests.lock().contains(block_pos), @@ -68,7 +68,7 @@ async fn steal(bot: Client, state: State) -> eyre::Result<()> { bot.goto(RadiusGoal::new(chest_block.center(), 3.)).await; - let Some(chest) = bot.open_container_at(chest_block).await else { + let Some(chest) = bot.open_container_at(chest_block).await? else { println!("Couldn't open chest at {chest_block:?}"); continue; }; diff --git a/azalea/examples/testbot/commands.rs b/azalea/examples/testbot/commands.rs index 03705617..599c693d 100644 --- a/azalea/examples/testbot/commands.rs +++ b/azalea/examples/testbot/commands.rs @@ -11,7 +11,8 @@ use parking_lot::Mutex; use crate::State; -pub type Ctx = CommandContext>; +pub type Ctx = CommandContext, eyre::Result>; +pub type Dispatcher = CommandDispatcher, eyre::Result>; pub struct CommandSource { pub bot: Client, @@ -42,7 +43,7 @@ impl CommandSource { } } -pub fn register_commands(commands: &mut CommandDispatcher>) { +pub fn register_commands(commands: &mut Dispatcher) { combat::register(commands); debug::register(commands); movement::register(commands); diff --git a/azalea/examples/testbot/commands/combat.rs b/azalea/examples/testbot/commands/combat.rs index af147e96..617ac1bd 100644 --- a/azalea/examples/testbot/commands/combat.rs +++ b/azalea/examples/testbot/commands/combat.rs @@ -1,22 +1,21 @@ use azalea::brigadier::prelude::*; -use parking_lot::Mutex; -use super::{CommandSource, Ctx}; -use crate::State; +use super::Ctx; +use crate::{State, commands::Dispatcher}; -pub fn register(commands: &mut CommandDispatcher>) { +pub fn register(commands: &mut Dispatcher) { commands.register( literal("killaura").then(argument("enabled", bool()).executes(|ctx: &Ctx| { let enabled = get_bool(ctx, "enabled").unwrap(); let source = ctx.source.lock(); let bot = source.bot.clone(); - bot.query_self::<&mut State, _>(|mut state| state.killaura = enabled); + bot.query_self::<&mut State, _>(|mut state| state.killaura = enabled)?; source.reply(if enabled { "Enabled killaura" } else { "Disabled killaura" }); - 1 + Ok(1) })), ); } diff --git a/azalea/examples/testbot/commands/debug.rs b/azalea/examples/testbot/commands/debug.rs index 55b41403..50821a8b 100644 --- a/azalea/examples/testbot/commands/debug.rs +++ b/azalea/examples/testbot/commands/debug.rs @@ -14,72 +14,72 @@ use azalea::{ }, }; use azalea_core::hit_result::HitResult; -use azalea_entity::{EntityKindComponent, metadata}; +use azalea_entity::metadata; use azalea_inventory::{Menu, components::MaxStackSize}; use azalea_world::{Worlds, chunk::storage::WeakChunkStorage}; use bevy_app::AppExit; use bevy_ecs::{message::Messages, query::With, world::EntityRef}; -use parking_lot::Mutex; -use super::{CommandSource, Ctx}; +use super::Ctx; +use crate::commands::Dispatcher; -pub fn register(commands: &mut CommandDispatcher>) { +pub fn register(commands: &mut Dispatcher) { commands.register(literal("ping").executes(|ctx: &Ctx| { let source = ctx.source.lock(); source.reply("pong!"); - 1 + Ok(1) })); commands.register( literal("say").then(argument("message", greedy_string()).executes(|ctx: &Ctx| { let source = ctx.source.lock(); let message = get_string(ctx, "message").unwrap(); source.bot.chat(message); - 1 + Ok(1) })), ); commands.register(literal("disconnect").executes(|ctx: &Ctx| { let source = ctx.source.lock(); source.bot.disconnect(); - 1 + Ok(1) })); commands.register(literal("whereami").executes(|ctx: &Ctx| { let source = ctx.source.lock(); let Some(entity) = source.entity() else { source.reply("You aren't in render distance!"); - return 0; + return Ok(0); }; - let position = entity.position(); + let position = entity.position()?; source.reply(format!( "You are at {}, {}, {}", position.x, position.y, position.z )); - 1 + Ok(1) })); commands.register(literal("entityid").executes(|ctx: &Ctx| { let source = ctx.source.lock(); let Some(entity) = source.entity() else { source.reply("You aren't in render distance!"); - return 0; + return Ok(0); }; - let entity_id = entity.minecraft_id(); + let entity_id = entity.minecraft_id()?; source.reply(format!( "Your Minecraft ID is {} and your ECS ID is {entity:?}", *entity_id )); - 1 + Ok(1) })); let whereareyou = |ctx: &Ctx| { let source = ctx.source.lock(); - let position = source.bot.position(); + let position = source.bot.position()?; source.reply(format!( "I'm at {}, {}, {}", position.x, position.y, position.z )); - 1 + Ok(1) }; commands.register(literal("whereareyou").executes(whereareyou)); commands.register(literal("pos").executes(whereareyou)); @@ -92,45 +92,45 @@ pub fn register(commands: &mut CommandDispatcher>) { source.bot.uuid(), source.bot.entity )); - 1 + Ok(1) })); commands.register(literal("getdirection").executes(|ctx: &Ctx| { let source = ctx.source.lock(); - let direction = source.bot.direction(); + let direction = source.bot.direction()?; source.reply(format!( "I'm looking at {}, {}", direction.y_rot(), direction.x_rot() )); - 1 + Ok(1) })); commands.register(literal("health").executes(|ctx: &Ctx| { let source = ctx.source.lock(); - let health = source.bot.health(); + let health = source.bot.health()?; source.reply(format!("I have {health} health")); - 1 + Ok(1) })); commands.register(literal("lookingat").executes(|ctx: &Ctx| { let source = ctx.source.lock(); - let hit_result = source.bot.hit_result(); + let hit_result = source.bot.hit_result()?; match &hit_result { HitResult::Block(r) => { if r.miss { source.reply("I'm not looking at anything"); - return 0; + return Ok(0); } let block_pos = r.block_pos; - let block = source.bot.world().read().get_block_state(block_pos); + let block = source.bot.world()?.read().get_block_state(block_pos); source.reply(format!("I'm looking at {block:?} at {block_pos:?}")); } HitResult::Entity(r) => { - let entity_kind = **source.bot.entity_component::(r.entity); + let entity_kind = source.bot.entity_ref_for(r.entity).kind()?; source.reply(format!( "I'm looking at {entity_kind} ({:?}) at {}", r.entity, r.location @@ -138,7 +138,7 @@ pub fn register(commands: &mut CommandDispatcher>) { } } - 1 + Ok(1) })); commands.register(literal("getblock").then(argument("x", integer()).then( @@ -149,9 +149,9 @@ pub fn register(commands: &mut CommandDispatcher>) { let z = get_integer(ctx, "z").unwrap(); println!("getblock xyz {x} {y} {z}"); let block_pos = BlockPos::new(x, y, z); - let block = source.bot.world().read().get_block_state(block_pos); + let block = source.bot.world()?.read().get_block_state(block_pos); source.reply(format!("BlockKind at {block_pos} is {block:?}")); - 1 + Ok(1) })), ))); commands.register(literal("getfluid").then(argument("x", integer()).then( @@ -162,15 +162,15 @@ pub fn register(commands: &mut CommandDispatcher>) { let z = get_integer(ctx, "z").unwrap(); println!("getfluid xyz {x} {y} {z}"); let block_pos = BlockPos::new(x, y, z); - let block = source.bot.world().read().get_fluid_state(block_pos); + let block = source.bot.world()?.read().get_fluid_state(block_pos); source.reply(format!("Fluid at {block_pos} is {block:?}")); - 1 + Ok(1) })), ))); commands.register(literal("inventory").executes(|ctx: &Ctx| { let source = ctx.source.lock(); - for item in source.bot.menu().slots() { + for item in source.bot.menu()?.slots() { if item.is_empty() { continue; } @@ -181,25 +181,25 @@ pub fn register(commands: &mut CommandDispatcher>) { } } } - 1 + Ok(1) })); commands.register(literal("pathfinderstate").executes(|ctx: &Ctx| { let source = ctx.source.lock(); - let pathfinder = source.bot.get_component::(); - let Some(pathfinder) = pathfinder else { + let pathfinder = source.bot.component::(); + let Ok(pathfinder) = pathfinder else { source.reply("I don't have the Pathfinder component"); - return 1; + return Ok(1); }; source.reply(format!( "pathfinder.is_calculating: {}", pathfinder.is_calculating )); - let executing_path = source.bot.get_component::(); - let Some(executing_path) = executing_path else { + let executing_path = source.bot.component::(); + let Ok(executing_path) = executing_path else { source.reply("I'm not executing a path"); - return 1; + return Ok(1); }; source.reply(format!( "is_path_partial: {}, path.len: {}, queued_path.len: {}", @@ -211,20 +211,20 @@ pub fn register(commands: &mut CommandDispatcher>) { "n/a".to_owned() }, )); - 1 + Ok(1) })); commands.register(literal("pathfindermoves").executes(|ctx: &Ctx| { let source = ctx.source.lock(); let Some(entity) = source.entity() else { source.reply("You aren't in render distance!"); - return 0; + return Ok(0); }; - let position = entity.position(); + let position = entity.position()?; let position = BlockPos::from(position); let mut edges = Vec::new(); - let cached_world = CachedWorld::new(source.bot.world(), position); + let cached_world = CachedWorld::new(source.bot.world()?, position); let mining_cache = MiningCache::new(Some(Menu::Player(inventory::Player::default()))); let custom_state = CustomPathfinderStateRef::default(); @@ -247,41 +247,41 @@ pub fn register(commands: &mut CommandDispatcher>) { } } - 1 + Ok(1) })); commands.register(literal("startuseitem").executes(|ctx: &Ctx| { let source = ctx.source.lock(); source.bot.start_use_item(); source.reply("Ok!"); - 1 + Ok(1) })); commands.register(literal("maxstacksize").executes(|ctx: &Ctx| { let source = ctx.source.lock(); let max_stack_size = source .bot - .get_held_item() + .get_held_item()? .get_component::() .map_or(-1, |s| s.count); source.reply(format!("{max_stack_size}")); - 1 + Ok(1) })); commands.register(literal("dimensions").executes(|ctx: &Ctx| { let source = ctx.source.lock(); let bot_dimensions = source.bot.dimensions(); source.reply(format!("{bot_dimensions:?}")); - 1 + Ok(1) })); commands.register(literal("players").executes(|ctx: &Ctx| { let source = ctx.source.lock(); let player_entities = source .bot - .nearest_entities_by::<(), With>(|_: ()| true); - let tab_list = source.bot.tab_list(); + .nearest_entities_by::<(), With>(|_: ()| true)?; + let tab_list = source.bot.tab_list()?; for player_entity in player_entities { - let uuid = player_entity.uuid(); + let uuid = player_entity.uuid()?; source.reply(format!( "{} - {} ({:?})", player_entity.id(), @@ -289,7 +289,7 @@ pub fn register(commands: &mut CommandDispatcher>) { uuid )); } - 1 + Ok(1) })); commands.register(literal("enchants").executes(|ctx: &Ctx| { @@ -297,15 +297,15 @@ pub fn register(commands: &mut CommandDispatcher>) { source.bot.with_registry_holder(|r| { let enchants = &r.enchantment; println!("enchants: {enchants:?}"); - }); - 1 + })?; + Ok(1) })); commands.register(literal("attributes").executes(|ctx: &Ctx| { let source = ctx.source.lock(); let attributes = source.bot.attributes(); println!("attributes: {attributes:?}"); - 1 + Ok(1) })); commands.register(literal("debugecsleak").executes(|ctx: &Ctx| { @@ -406,7 +406,7 @@ pub fn register(commands: &mut CommandDispatcher>) { println!("\x1b[1mWrote report to {}\x1b[m", report_path.display()); }); - 1 + Ok(1) })); commands.register(literal("exit").executes(|ctx: &Ctx| { @@ -427,6 +427,6 @@ pub fn register(commands: &mut CommandDispatcher>) { .write_message(AppExit::Success); }); - 1 + Ok(1) })); } diff --git a/azalea/examples/testbot/commands/movement.rs b/azalea/examples/testbot/commands/movement.rs index 500e17b0..df3b7418 100644 --- a/azalea/examples/testbot/commands/movement.rs +++ b/azalea/examples/testbot/commands/movement.rs @@ -6,11 +6,11 @@ use azalea::{ pathfinder::goals::{BlockPosGoal, RadiusGoal, XZGoal}, prelude::*, }; -use parking_lot::Mutex; -use super::{CommandSource, Ctx}; +use super::Ctx; +use crate::commands::Dispatcher; -pub fn register(commands: &mut CommandDispatcher>) { +pub fn register(commands: &mut Dispatcher) { commands.register( literal("goto") .executes(|ctx: &Ctx| { @@ -19,14 +19,14 @@ pub fn register(commands: &mut CommandDispatcher>) { // look for the sender let Some(entity) = source.entity() else { source.reply("I can't see you!"); - return 0; + return Ok(0); }; - let position = entity.position(); + let position = entity.position()?; source.reply("ok"); source .bot .start_goto(BlockPosGoal(BlockPos::from(position.up(0.5)))); - 1 + Ok(1) }) .then(literal("xz").then(argument("x", integer()).then( argument("z", integer()).executes(|ctx: &Ctx| { @@ -36,7 +36,7 @@ pub fn register(commands: &mut CommandDispatcher>) { println!("goto xz {x} {z}"); source.reply("ok"); source.bot.start_goto(XZGoal { x, z }); - 1 + Ok(1) }), ))) .then(literal("radius").then(argument("radius", float()).then( @@ -53,7 +53,7 @@ pub fn register(commands: &mut CommandDispatcher>) { pos: BlockPos::new(x, y, z).center(), radius, }); - 1 + Ok(1) }), )), ))) @@ -66,7 +66,7 @@ pub fn register(commands: &mut CommandDispatcher>) { println!("goto xyz {x} {y} {z}"); source.reply("ok"); source.bot.start_goto(BlockPosGoal(BlockPos::new(x, y, z))); - 1 + Ok(1) }), ))), ); @@ -77,23 +77,23 @@ pub fn register(commands: &mut CommandDispatcher>) { // look for the sender let Some(entity) = source.entity() else { source.reply("I can't see you!"); - return 0; + return Ok(0); }; source.reply("ok"); *source.state.following_entity.lock() = Some(entity); - 1 + Ok(1) })); commands.register(literal("down").executes(|ctx: &Ctx| { let source = ctx.source.clone(); + let bot = source.lock().bot.clone(); + let position = BlockPos::from(bot.position()?); tokio::spawn(async move { - let bot = source.lock().bot.clone(); - let position = BlockPos::from(bot.position()); source.lock().reply("mining..."); bot.mine(position.down(1)).await; source.lock().reply("done"); }); - 1 + Ok(1) })); commands.register( @@ -103,11 +103,11 @@ pub fn register(commands: &mut CommandDispatcher>) { let source = ctx.source.lock(); let Some(entity) = source.entity() else { source.reply("I can't see you!"); - return 0; + return Ok(0); }; - let eye_position = entity.eye_position(); + let eye_position = entity.eye_position()?; source.bot.look_at(eye_position); - 1 + Ok(1) }) .then(argument("x", integer()).then(argument("y", integer()).then( argument("z", integer()).executes(|ctx: &Ctx| { @@ -119,7 +119,7 @@ pub fn register(commands: &mut CommandDispatcher>) { println!("{pos:?}"); let source = ctx.source.lock(); source.bot.look_at(pos.center()); - 1 + Ok(1) }), ))), ); @@ -142,7 +142,7 @@ pub fn register(commands: &mut CommandDispatcher>) { bot.walk(WalkDirection::None); }); source.reply(format!("ok, walking for {seconds} seconds")); - 1 + Ok(1) })), ); commands.register( @@ -156,33 +156,33 @@ pub fn register(commands: &mut CommandDispatcher>) { bot.walk(WalkDirection::None); }); source.reply(format!("ok, sprinting for {seconds} seconds")); - 1 + Ok(1) })), ); commands.register(literal("north").executes(|ctx: &Ctx| { let source = ctx.source.lock(); - source.bot.set_direction(180., 0.); + source.bot.set_direction(180., 0.)?; source.reply("ok"); - 1 + Ok(1) })); commands.register(literal("south").executes(|ctx: &Ctx| { let source = ctx.source.lock(); - source.bot.set_direction(0., 0.); + source.bot.set_direction(0., 0.)?; source.reply("ok"); - 1 + Ok(1) })); commands.register(literal("east").executes(|ctx: &Ctx| { let source = ctx.source.lock(); - source.bot.set_direction(-90., 0.); + source.bot.set_direction(-90., 0.)?; source.reply("ok"); - 1 + Ok(1) })); commands.register(literal("west").executes(|ctx: &Ctx| { let source = ctx.source.lock(); - source.bot.set_direction(90., 0.); + source.bot.set_direction(90., 0.)?; source.reply("ok"); - 1 + Ok(1) })); commands.register( literal("jump") @@ -190,27 +190,27 @@ pub fn register(commands: &mut CommandDispatcher>) { let source = ctx.source.lock(); source.bot.jump(); source.reply("ok"); - 1 + Ok(1) }) .then(argument("enabled", bool()).executes(|ctx: &Ctx| { let jumping = get_bool(ctx, "enabled").unwrap(); let source = ctx.source.lock(); - source.bot.set_jumping(jumping); - 1 + source.bot.set_jumping(jumping)?; + Ok(1) })), ); let sneak = |ctx: &Ctx| { let source = ctx.source.lock(); - source.bot.set_crouching(!source.bot.crouching()); + source.bot.set_crouching(!source.bot.crouching())?; source.reply("ok"); - 1 + Ok(1) }; let sneak_enabled = argument("enabled", bool()).executes(|ctx: &Ctx| { let sneaking = get_bool(ctx, "enabled").unwrap(); let source = ctx.source.lock(); - source.bot.set_crouching(sneaking); - 1 + source.bot.set_crouching(sneaking)?; + Ok(1) }); commands.register(literal("sneak").executes(sneak).then(sneak_enabled.clone())); commands.register(literal("crouch").executes(sneak).then(sneak_enabled)); @@ -220,13 +220,13 @@ pub fn register(commands: &mut CommandDispatcher>) { source.bot.stop_pathfinding(); source.reply("ok"); *source.state.following_entity.lock() = None; - 1 + Ok(1) })); commands.register(literal("forcestop").executes(|ctx: &Ctx| { let source = ctx.source.lock(); source.bot.force_stop_pathfinding(); source.reply("ok"); *source.state.following_entity.lock() = None; - 1 + Ok(1) })); } diff --git a/azalea/examples/testbot/main.rs b/azalea/examples/testbot/main.rs index 806780b7..7778463c 100644 --- a/azalea/examples/testbot/main.rs +++ b/azalea/examples/testbot/main.rs @@ -126,7 +126,7 @@ impl State { #[derive(Clone, Default, Resource)] struct SwarmState { pub args: Arc, - pub commands: Arc>>, + pub commands: Arc, } async fn handle(bot: Client, event: azalea::Event, state: State) -> eyre::Result<()> { @@ -137,7 +137,7 @@ async fn handle(bot: Client, event: azalea::Event, state: State) -> eyre::Result bot.set_client_information(ClientInformation { view_distance: 32, ..Default::default() - }); + })?; if swarm.args.pathfinder_debug_particles { bot.ecs .write() @@ -169,7 +169,16 @@ async fn handle(bot: Client, event: azalea::Event, state: State) -> eyre::Result state: state.clone(), }), ) { - Ok(_) => {} + Ok(Ok(_)) => {} + Ok(Err(err)) => { + eprintln!("azalea error: {err:?}"); + let command_source = CommandSource { + bot, + chat: chat.clone(), + state: state.clone(), + }; + command_source.reply(format!("azalea error: {err:?}")); + } Err(err) => { eprintln!("{err:?}"); let command_source = CommandSource { @@ -200,7 +209,7 @@ async fn handle(bot: Client, event: azalea::Event, state: State) -> eyre::Result .max_timeout(Duration::from_secs(1)), ); } else { - following.look_at(); + following.look_at()?; } } } diff --git a/azalea/src/_docs/performance.md b/azalea/src/_docs/performance.md index bf580f73..d7d2f596 100644 --- a/azalea/src/_docs/performance.md +++ b/azalea/src/_docs/performance.md @@ -91,14 +91,15 @@ If the client doesn't need a high view distance, then you can reduce the number This can be done by having something like the following in your handler function: ```rust,no_run # use azalea::ClientInformation; -# async fn handle(bot: azalea::Client, event: azalea::Event, state: azalea::NoState) { +# async fn handle(bot: azalea::Client, event: azalea::Event, state: azalea::NoState) -> azalea::error::AzaleaResult<()> { # match event { azalea::Event::Init => bot.set_client_information(ClientInformation { view_distance: 2, ..Default::default() -}), +})?, # _ => {} # } +# Ok(()) # } ``` diff --git a/azalea/src/client_impl/mod.rs b/azalea/src/client_impl/mod.rs index ba78fee4..e0718375 100644 --- a/azalea/src/client_impl/mod.rs +++ b/azalea/src/client_impl/mod.rs @@ -355,9 +355,10 @@ impl Client { /// /// ``` /// # use azalea_core::position::ChunkPos; - /// # fn example(client: &azalea::Client) { - /// let world = client.partial_world(); + /// # fn example(client: &azalea::Client) -> azalea::error::AzaleaResult<()> { + /// let world = client.partial_world()?; /// let is_0_0_loaded = world.read().chunks.limited_get(&ChunkPos::new(0, 0)).is_some(); + /// # Ok(()) /// # } pub fn partial_world(&self) -> AzaleaResult>> { let world_holder = self.component::()?; diff --git a/azalea/src/container.rs b/azalea/src/container.rs index 0a5c8c01..1a8b1721 100644 --- a/azalea/src/container.rs +++ b/azalea/src/container.rs @@ -37,14 +37,14 @@ impl Client { /// /// ``` /// # use azalea::{prelude::*, registry::builtin::BlockKind}; - /// # async fn example(mut bot: azalea::Client) -> AzaleaResult<()> { + /// # async fn example(mut bot: azalea::Client) -> azalea::error::AzaleaResult<()> { /// let target_pos = bot - /// .world() + /// .world()? /// .read() - /// .find_block(bot.position(), &BlockKind::Chest.into()); + /// .find_block(bot.position()?, &BlockKind::Chest.into()); /// let Some(target_pos) = target_pos else { /// bot.chat("no chest found"); - /// return; + /// return Ok(()); /// }; /// let container = bot.open_container_at(target_pos).await?; /// # Ok(()) @@ -256,11 +256,12 @@ impl ContainerHandleRef { /// /// ```no_run /// # use azalea::prelude::*; - /// # fn example(bot: &Client) { - /// let inventory = bot.get_inventory(); + /// # fn example(bot: &Client) -> azalea::error::AzaleaResult<()> { + /// let inventory = bot.get_inventory()?; /// let inventory_title = inventory.title().unwrap_or_default().to_string(); /// // would be true if an unnamed chest is open /// assert_eq!(inventory_title, "Chest"); + /// # Ok(()) /// # } /// ``` pub fn title(&self) -> Option { diff --git a/azalea/src/lib.rs b/azalea/src/lib.rs index 3a2bc0cf..8c29e2c3 100644 --- a/azalea/src/lib.rs +++ b/azalea/src/lib.rs @@ -66,11 +66,16 @@ use futures::future::BoxFuture; pub use join_opts::JoinOpts; pub use crate::{ - client_impl::{Client, StartClientOpts}, + client_impl::{Client, StartClientOpts, error}, entity_ref::EntityRef, events::Event, }; +// for convenience, adds the alias `azalea::Result` instead of +// `azalea::error::AzaleaResult`. the user should probably be using anyhow/eyre, +// but in some cases they may prefer to have the errors more strictly defined. +pub type Result = error::AzaleaResult; + pub type BoxHandleFn = Box BoxFuture<'static, R> + Send>; pub type HandleFn = fn(Client, Event, S) -> Fut; -- cgit v1.2.3