diff options
| author | mat <git@matdoes.dev> | 2026-05-06 18:38:23 -0545 |
|---|---|---|
| committer | mat <git@matdoes.dev> | 2026-05-07 08:05:58 -1200 |
| commit | cabc8b60a729ba17f5b75f7a7956c6d1ddcc8919 (patch) | |
| tree | 237fd12a9768fe7431ce42dfbdde60f4c7850e06 | |
| parent | 9ffd0e80bbb3feace231553d6539124585b03e3c (diff) | |
| download | azalea-drasl-cabc8b60a729ba17f5b75f7a7956c6d1ddcc8919.tar.xz | |
azalea-brigadier now allows commands to return a Result
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<i32, _>` 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<S>(context: &CommandContext<S>, name: &str) -> Option<bool> { +pub fn get_bool<S, R>(context: &CommandContext<S, R>, name: &str) -> Option<bool> { 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<S>(context: &CommandContext<S>, name: &str) -> Option<f64> { +pub fn get_double<S, R>(context: &CommandContext<S, R>, name: &str) -> Option<f64> { 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<S>(context: &CommandContext<S>, name: &str) -> Option<f32> { +pub fn get_float<S, R>(context: &CommandContext<S, R>, name: &str) -> Option<f32> { 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<S>(context: &CommandContext<S>, name: &str) -> Option<i32> { +pub fn get_integer<S, R>(context: &CommandContext<S, R>, name: &str) -> Option<i32> { 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<S>(context: &CommandContext<S>, name: &str) -> Option<i64> { +pub fn get_long<S, R>(context: &CommandContext<S, R>, name: &str) -> Option<i64> { 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<S>(context: &CommandContext<S>, name: &str) -> Option<String> { +pub fn get_string<S, R>(context: &CommandContext<S, R>, name: &str) -> Option<String> { 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<S> { +pub enum ArgumentBuilderType<S, R> { Literal(Literal), - Argument(Argument<S>), + Argument(Argument<S, R>), } -impl<S> Clone for ArgumentBuilderType<S> { +impl<S, R> Clone for ArgumentBuilderType<S, R> { fn clone(&self) -> Self { match self { ArgumentBuilderType::Literal(literal) => ArgumentBuilderType::Literal(literal.clone()), @@ -30,20 +30,20 @@ impl<S> Clone for ArgumentBuilderType<S> { } /// A node that hasn't yet been built. -pub struct ArgumentBuilder<S> { - arguments: CommandNode<S>, +pub struct ArgumentBuilder<S, R = i32> { + arguments: CommandNode<S, R>, - command: Command<S>, + command: Command<S, R>, requirement: Arc<dyn Fn(&S) -> bool + Send + Sync>, - target: Option<Arc<RwLock<CommandNode<S>>>>, + target: Option<Arc<RwLock<CommandNode<S, R>>>>, forks: bool, - modifier: Option<Arc<RedirectModifier<S>>>, + modifier: Option<Arc<RedirectModifier<S, R>>>, } /// A node that isn't yet built. -impl<S> ArgumentBuilder<S> { - pub fn new(value: ArgumentBuilderType<S>) -> Self { +impl<S, R> ArgumentBuilder<S, R> { + pub fn new(value: ArgumentBuilderType<S, R>) -> Self { Self { arguments: CommandNode { value, @@ -65,14 +65,14 @@ impl<S> ArgumentBuilder<S> { /// literal("foo").then(literal("bar").executes(|ctx: &CommandContext<()>| 42)) /// # ; /// ``` - pub fn then(self, argument: ArgumentBuilder<S>) -> Self { + pub fn then(self, argument: ArgumentBuilder<S, R>) -> 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<S>) -> Self { + pub fn then_built(mut self, argument: CommandNode<S, R>) -> Self { self.arguments.add_child(&Arc::new(RwLock::new(argument))); self } @@ -90,9 +90,9 @@ impl<S> ArgumentBuilder<S> { /// ``` pub fn executes<F>(mut self, f: F) -> Self where - F: Fn(&CommandContext<S>) -> i32 + Send + Sync + 'static, + F: Fn(&CommandContext<S, R>) -> R + Send + Sync + 'static, { - self.command = Some(Arc::new(move |ctx: &CommandContext<S>| Ok(f(ctx)))); + self.command = Some(Arc::new(move |ctx: &CommandContext<S, R>| Ok(f(ctx)))); self } @@ -100,7 +100,7 @@ impl<S> ArgumentBuilder<S> { /// CommandSyntaxError>`. pub fn executes_result<F>(mut self, f: F) -> Self where - F: Fn(&CommandContext<S>) -> Result<i32, CommandSyntaxError> + Send + Sync + 'static, + F: Fn(&CommandContext<S, R>) -> Result<R, CommandSyntaxError> + Send + Sync + 'static, { self.command = Some(Arc::new(f)); self @@ -131,22 +131,22 @@ impl<S> ArgumentBuilder<S> { self } - pub fn redirect(self, target: Arc<RwLock<CommandNode<S>>>) -> Self { + pub fn redirect(self, target: Arc<RwLock<CommandNode<S, R>>>) -> Self { self.forward(target, None, false) } pub fn fork( self, - target: Arc<RwLock<CommandNode<S>>>, - modifier: Arc<RedirectModifier<S>>, + target: Arc<RwLock<CommandNode<S, R>>>, + modifier: Arc<RedirectModifier<S, R>>, ) -> Self { self.forward(target, Some(modifier), true) } pub fn forward( mut self, - target: Arc<RwLock<CommandNode<S>>>, - modifier: Option<Arc<RedirectModifier<S>>>, + target: Arc<RwLock<CommandNode<S, R>>>, + modifier: Option<Arc<RedirectModifier<S, R>>>, fork: bool, ) -> Self { if !self.arguments.children.is_empty() { @@ -158,13 +158,13 @@ impl<S> ArgumentBuilder<S> { self } - pub fn arguments(&self) -> &CommandNode<S> { + pub fn arguments(&self) -> &CommandNode<S, R> { &self.arguments } /// Manually build this node into a [`CommandNode`]. You probably don't need /// to do this yourself. - pub fn build(self) -> CommandNode<S> { + pub fn build(self) -> CommandNode<S, R> { let mut result = CommandNode { value: self.arguments.value, command: self.command, @@ -185,7 +185,7 @@ impl<S> ArgumentBuilder<S> { } } -impl<S> Debug for ArgumentBuilder<S> { +impl<S, R> Debug for ArgumentBuilder<S, R> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ArgumentBuilder") .field("arguments", &self.arguments) @@ -197,7 +197,7 @@ impl<S> Debug for ArgumentBuilder<S> { .finish() } } -impl<S> Clone for ArgumentBuilder<S> { +impl<S, R> Clone for ArgumentBuilder<S, R> { 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<S> From<Literal> for ArgumentBuilderType<S> { +impl<S, R> From<Literal> for ArgumentBuilderType<S, R> { fn from(literal: Literal) -> Self { Self::Literal(literal) } } /// Shortcut for creating a new literal builder node. -pub fn literal<S>(value: &str) -> ArgumentBuilder<S> { +pub fn literal<S, R>(value: &str) -> ArgumentBuilder<S, R> { 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<S> { +pub struct Argument<S, R> { pub name: String, parser: Arc<dyn ArgumentType + Send + Sync>, - custom_suggestions: Option<Arc<dyn SuggestionProvider<S> + Send + Sync>>, + custom_suggestions: Option<Arc<dyn SuggestionProvider<S, R> + Send + Sync>>, } -impl<S> Argument<S> { +impl<S, R> Argument<S, R> { pub fn new( name: &str, parser: Arc<dyn ArgumentType + Send + Sync>, - custom_suggestions: Option<Arc<dyn SuggestionProvider<S> + Send + Sync>>, + custom_suggestions: Option<Arc<dyn SuggestionProvider<S, R> + Send + Sync>>, ) -> Self { Self { name: name.to_owned(), @@ -40,7 +40,7 @@ impl<S> Argument<S> { pub fn list_suggestions( &self, - context: CommandContext<S>, + context: CommandContext<S, R>, builder: SuggestionsBuilder, ) -> Suggestions { if let Some(s) = &self.custom_suggestions { @@ -55,13 +55,13 @@ impl<S> Argument<S> { } } -impl<S> From<Argument<S>> for ArgumentBuilderType<S> { - fn from(argument: Argument<S>) -> Self { +impl<S, R> From<Argument<S, R>> for ArgumentBuilderType<S, R> { + fn from(argument: Argument<S, R>) -> Self { Self::Argument(argument) } } -impl<S> Debug for Argument<S> { +impl<S, R> Debug for Argument<S, R> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Argument") .field("name", &self.name) @@ -71,14 +71,14 @@ impl<S> Debug for Argument<S> { } /// Shortcut for creating a new argument builder node. -pub fn argument<S>( +pub fn argument<S, R>( name: &str, parser: impl ArgumentType + Send + Sync + 'static, -) -> ArgumentBuilder<S> { +) -> ArgumentBuilder<S, R> { ArgumentBuilder::new(Argument::new(name, Arc::new(parser), None).into()) } -impl<S> Clone for Argument<S> { +impl<S, R> Clone for Argument<S, R> { 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::<CommandSource>::new(); /// ``` -pub struct CommandDispatcher<S> +pub struct CommandDispatcher<S, R = i32> where Self: Sync + Send, { - pub root: Arc<RwLock<CommandNode<S>>>, - consumer: Box<dyn ResultConsumer<S> + Send + Sync>, + pub root: Arc<RwLock<CommandNode<S, R>>>, + consumer: Box<dyn ResultConsumer<S, R> + Send + Sync>, } -impl<S> CommandDispatcher<S> { +impl<S, R: CommandResultTrait> CommandDispatcher<S, R> { pub fn new() -> Self { Self { root: Arc::new(RwLock::new(CommandNode::default())), @@ -49,13 +49,13 @@ impl<S> CommandDispatcher<S> { /// # let mut subject = CommandDispatcher::<()>::new(); /// subject.register(literal("foo").executes(|_| 42)); /// ``` - pub fn register(&mut self, node: ArgumentBuilder<S>) -> Arc<RwLock<CommandNode<S>>> { + pub fn register(&mut self, node: ArgumentBuilder<S, R>) -> Arc<RwLock<CommandNode<S, R>>> { 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<S> CommandDispatcher<S> { fn parse_nodes<'a>( &'a self, - node: &Arc<RwLock<CommandNode<S>>>, + node: &Arc<RwLock<CommandNode<S, R>>>, original_reader: &StringReader, - context_so_far: CommandContextBuilder<'a, S>, - ) -> Result<ParseResults<'a, S>, CommandSyntaxError> { + context_so_far: CommandContextBuilder<'a, S, R>, + ) -> Result<ParseResults<'a, S, R>, 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::<Rc<CommandNode<S>>, CommandSyntaxError>::new(); - let mut potentials: Vec<ParseResults<S>> = vec![]; + let mut errors = HashMap::<Rc<CommandNode<S, R>>, CommandSyntaxError>::new(); + let mut potentials: Vec<ParseResults<S, R>> = vec![]; let cursor = original_reader.cursor(); for child in node.read().get_relevant_nodes(&mut original_reader.clone()) { @@ -183,7 +183,7 @@ impl<S> CommandDispatcher<S> { &self, input: impl Into<StringReader>, source: S, - ) -> Result<i32, CommandSyntaxError> { + ) -> Result<R, CommandSyntaxError> { let input = input.into(); let parse = self.parse(input, source); @@ -191,9 +191,9 @@ impl<S> CommandDispatcher<S> { } pub fn add_paths( - node: Arc<RwLock<CommandNode<S>>>, - result: &mut Vec<Vec<Arc<RwLock<CommandNode<S>>>>>, - parents: Vec<Arc<RwLock<CommandNode<S>>>>, + node: Arc<RwLock<CommandNode<S, R>>>, + result: &mut Vec<Vec<Arc<RwLock<CommandNode<S, R>>>>>, + parents: Vec<Arc<RwLock<CommandNode<S, R>>>>, ) { let mut current = parents; current.push(node.clone()); @@ -204,9 +204,9 @@ impl<S> CommandDispatcher<S> { } } - pub fn get_path(&self, target: CommandNode<S>) -> Vec<String> { + pub fn get_path(&self, target: CommandNode<S, R>) -> Vec<String> { let rc_target = Arc::new(RwLock::new(target)); - let mut nodes: Vec<Vec<Arc<RwLock<CommandNode<S>>>>> = Vec::new(); + let mut nodes: Vec<Vec<Arc<RwLock<CommandNode<S, R>>>>> = Vec::new(); Self::add_paths(self.root.clone(), &mut nodes, vec![]); for list in nodes { @@ -223,7 +223,7 @@ impl<S> CommandDispatcher<S> { vec![] } - pub fn find_node(&self, path: &[&str]) -> Option<Arc<RwLock<CommandNode<S>>>> { + pub fn find_node(&self, path: &[&str]) -> Option<Arc<RwLock<CommandNode<S, R>>>> { let mut node = self.root.clone(); for name in path { match node.clone().read().child(name) { @@ -239,7 +239,7 @@ impl<S> CommandDispatcher<S> { } /// Executes a given pre-parsed command. - pub fn execute_parsed(&self, parse: ParseResults<S>) -> Result<i32, CommandSyntaxError> { + pub fn execute_parsed(&self, parse: ParseResults<S, R>) -> Result<R, CommandSyntaxError> { if parse.reader.can_read() { return Err(if parse.exceptions.len() == 1 { parse.exceptions.values().next().unwrap().clone() @@ -263,7 +263,7 @@ impl<S> CommandDispatcher<S> { pub fn get_all_usage( &self, - node: &CommandNode<S>, + node: &CommandNode<S, R>, source: &S, restricted: bool, ) -> Vec<String> { @@ -274,7 +274,7 @@ impl<S> CommandDispatcher<S> { fn get_all_usage_recursive( &self, - node: &CommandNode<S>, + node: &CommandNode<S, R>, source: &S, result: &mut Vec<String>, prefix: &str, @@ -325,9 +325,9 @@ impl<S> CommandDispatcher<S> { /// command tree. pub fn get_smart_usage( &self, - node: &CommandNode<S>, + node: &CommandNode<S, R>, source: &S, - ) -> Vec<(Arc<RwLock<CommandNode<S>>>, String)> { + ) -> Vec<(Arc<RwLock<CommandNode<S, R>>>, String)> { let mut result = Vec::new(); let optional = node.command.is_some(); @@ -343,7 +343,7 @@ impl<S> CommandDispatcher<S> { fn get_smart_usage_recursive( &self, - node: &CommandNode<S>, + node: &CommandNode<S, R>, source: &S, optional: bool, deep: bool, @@ -435,13 +435,13 @@ impl<S> CommandDispatcher<S> { Some(this) } - pub fn get_completion_suggestions(parse: ParseResults<S>) -> Suggestions { + pub fn get_completion_suggestions(parse: ParseResults<S, R>) -> Suggestions { let cursor = parse.reader.total_length(); Self::get_completion_suggestions_with_cursor(parse, cursor) } pub fn get_completion_suggestions_with_cursor( - parse: ParseResults<S>, + parse: ParseResults<S, R>, cursor: usize, ) -> Suggestions { let context = parse.context; @@ -471,7 +471,7 @@ impl<S> CommandDispatcher<S> { } } -impl<S> Default for CommandDispatcher<S> { +impl<S, R: CommandResultTrait> Default for CommandDispatcher<S, R> { 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<S> { +pub struct CommandContext<S, R = i32> { pub source: Arc<S>, pub(super) input: String, pub(super) arguments: HashMap<String, ParsedArgument>, - pub(super) command: Command<S>, - pub(super) root_node: Arc<RwLock<CommandNode<S>>>, - pub(super) nodes: Vec<ParsedCommandNode<S>>, + pub(super) command: Command<S, R>, + pub(super) root_node: Arc<RwLock<CommandNode<S, R>>>, + pub(super) nodes: Vec<ParsedCommandNode<S, R>>, pub(super) range: StringRange, - pub(super) child: Option<Rc<CommandContext<S>>>, - pub(super) modifier: Option<Arc<RedirectModifier<S>>>, + pub(super) child: Option<Rc<CommandContext<S, R>>>, + pub(super) modifier: Option<Arc<RedirectModifier<S, R>>>, pub(super) forks: bool, } -impl<S> Clone for CommandContext<S> { +impl<S, R> Clone for CommandContext<S, R> { fn clone(&self) -> Self { Self { source: self.source.clone(), @@ -45,7 +45,7 @@ impl<S> Clone for CommandContext<S> { } } -impl<S> Debug for CommandContext<S> { +impl<S, R> Debug for CommandContext<S, R> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("CommandContext") // .field("source", &self.source) @@ -62,7 +62,7 @@ impl<S> Debug for CommandContext<S> { } } -impl<S> CommandContext<S> { +impl<S, R> CommandContext<S, R> { pub fn copy_for(&self, source: Arc<S>) -> Self { if Arc::ptr_eq(&source, &self.source) { // fast path @@ -83,11 +83,11 @@ impl<S> CommandContext<S> { } } - pub fn child(&self) -> Option<&CommandContext<S>> { + pub fn child(&self) -> Option<&CommandContext<S, R>> { self.child.as_ref().map(|c| c.as_ref()) } - pub fn last_child(&self) -> &CommandContext<S> { + pub fn last_child(&self) -> &CommandContext<S, R> { let mut result = self; while let Some(child) = result.child() { result = child; @@ -95,7 +95,7 @@ impl<S> CommandContext<S> { result } - pub fn command(&self) -> &Command<S> { + pub fn command(&self) -> &Command<S, R> { &self.command } @@ -104,7 +104,7 @@ impl<S> CommandContext<S> { argument.map(|a| a.result.as_ref()) } - pub fn redirect_modifier(&self) -> Option<&RedirectModifier<S>> { + pub fn redirect_modifier(&self) -> Option<&RedirectModifier<S, R>> { self.modifier.as_ref().map(|m| m.as_ref()) } @@ -116,11 +116,11 @@ impl<S> CommandContext<S> { &self.input } - pub fn root_node(&self) -> &Arc<RwLock<CommandNode<S>>> { + pub fn root_node(&self) -> &Arc<RwLock<CommandNode<S, R>>> { &self.root_node } - pub fn nodes(&self) -> &[ParsedCommandNode<S>] { + pub fn nodes(&self) -> &[ParsedCommandNode<S, R>] { &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<String, ParsedArgument>, - pub root: Arc<RwLock<CommandNode<S>>>, - pub nodes: Vec<ParsedCommandNode<S>>, - pub dispatcher: &'a CommandDispatcher<S>, + pub root: Arc<RwLock<CommandNode<S, R>>>, + pub nodes: Vec<ParsedCommandNode<S, R>>, + pub dispatcher: &'a CommandDispatcher<S, R>, pub source: Arc<S>, - pub command: Command<S>, - pub child: Option<Rc<CommandContextBuilder<'a, S>>>, + pub command: Command<S, R>, + pub child: Option<Rc<CommandContextBuilder<'a, S, R>>>, pub range: StringRange, - pub modifier: Option<Arc<RedirectModifier<S>>>, + pub modifier: Option<Arc<RedirectModifier<S, R>>>, pub forks: bool, } -impl<S> Clone for CommandContextBuilder<'_, S> { +impl<S, R> Clone for CommandContextBuilder<'_, S, R> { fn clone(&self) -> Self { Self { arguments: self.arguments.clone(), @@ -47,11 +47,11 @@ impl<S> Clone for CommandContextBuilder<'_, S> { } } -impl<'a, S> CommandContextBuilder<'a, S> { +impl<'a, S, R> CommandContextBuilder<'a, S, R> { pub fn new( - dispatcher: &'a CommandDispatcher<S>, + dispatcher: &'a CommandDispatcher<S, R>, source: Arc<S>, - root_node: Arc<RwLock<CommandNode<S>>>, + root_node: Arc<RwLock<CommandNode<S, R>>>, start: usize, ) -> Self { Self { @@ -68,11 +68,11 @@ impl<'a, S> CommandContextBuilder<'a, S> { } } - pub fn with_command(&mut self, command: &Command<S>) -> &Self { + pub fn with_command(&mut self, command: &Command<S, R>) -> &Self { self.command.clone_from(command); self } - pub fn with_child(&mut self, child: Rc<CommandContextBuilder<'a, S>>) -> &Self { + pub fn with_child(&mut self, child: Rc<CommandContextBuilder<'a, S, R>>) -> &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<RwLock<CommandNode<S>>>, range: StringRange) -> &Self { + pub fn with_node(&mut self, node: Arc<RwLock<CommandNode<S, R>>>, 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<S> { + pub fn build(&self, input: &str) -> CommandContext<S, R> { 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<S> { + pub fn find_suggestion_context(&self, cursor: usize) -> SuggestionContext<S, R> { if self.range.start() > cursor { panic!("Can't find node before cursor"); } @@ -144,7 +144,7 @@ impl<'a, S> CommandContextBuilder<'a, S> { } } -impl<S> Debug for CommandContextBuilder<'_, S> { +impl<S, R> 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<S> { - modifiers: Vec<Rc<CommandContext<S>>>, - executable: Rc<CommandContext<S>>, - next_stage_cache: Option<Rc<ContextChain<S>>>, +use crate::{ + errors::{CommandResultTrait, CommandSyntaxError}, + result_consumer::ResultConsumer, +}; + +pub struct ContextChain<S, R> { + modifiers: Vec<Rc<CommandContext<S, R>>>, + executable: Rc<CommandContext<S, R>>, + next_stage_cache: Option<Rc<ContextChain<S, R>>>, } -impl<S> ContextChain<S> { - pub fn new(modifiers: Vec<Rc<CommandContext<S>>>, executable: Rc<CommandContext<S>>) -> Self { +impl<S, R: CommandResultTrait> ContextChain<S, R> { + pub fn new( + modifiers: Vec<Rc<CommandContext<S, R>>>, + executable: Rc<CommandContext<S, R>>, + ) -> Self { if executable.command.is_none() { panic!("Last command in chain must be executable"); } @@ -21,7 +27,7 @@ impl<S> ContextChain<S> { } } - pub fn try_flatten(root_context: Rc<CommandContext<S>>) -> Option<Self> { + pub fn try_flatten(root_context: Rc<CommandContext<S, R>>) -> Option<Self> { let mut modifiers = Vec::new(); let mut current = root_context; loop { @@ -39,9 +45,9 @@ impl<S> ContextChain<S> { } pub fn run_modifier( - modifier: Rc<CommandContext<S>>, + modifier: Rc<CommandContext<S, R>>, source: Arc<S>, - result_consumer: &dyn ResultConsumer<S>, + result_consumer: &dyn ResultConsumer<S, R>, forked_mode: bool, ) -> Result<Vec<Arc<S>>, CommandSyntaxError> { let source_modifier = modifier.redirect_modifier(); @@ -64,33 +70,43 @@ impl<S> ContextChain<S> { pub fn run_executable( &self, - executable: Rc<CommandContext<S>>, + executable: Rc<CommandContext<S, R>>, source: Arc<S>, - result_consumer: &dyn ResultConsumer<S>, + result_consumer: &dyn ResultConsumer<S, R>, forked_mode: bool, - ) -> Result<i32, CommandSyntaxError> { + ) -> Result<R, CommandSyntaxError> { 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<S>, - result_consumer: &dyn ResultConsumer<S>, - ) -> Result<i32, CommandSyntaxError> { + result_consumer: &dyn ResultConsumer<S, R>, + ) -> Result<R, CommandSyntaxError> { if self.modifiers.is_empty() { return self.run_executable(self.executable.clone(), source, result_consumer, false); } @@ -103,30 +119,37 @@ impl<S> ContextChain<S> { 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<S> ContextChain<S> { } } - pub fn top_context(&self) -> Rc<CommandContext<S>> { + pub fn top_context(&self) -> Rc<CommandContext<S, R>> { self.modifiers .first() .cloned() .unwrap_or_else(|| self.executable.clone()) } - pub fn next_stage(&mut self) -> Option<Rc<ContextChain<S>>> { + pub fn next_stage(&mut self) -> Option<Rc<ContextChain<S, R>>> { 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<S> { - pub node: Arc<RwLock<CommandNode<S>>>, +pub struct ParsedCommandNode<S, R> { + pub node: Arc<RwLock<CommandNode<S, R>>>, pub range: StringRange, } -impl<S> Clone for ParsedCommandNode<S> { +impl<S, R> Clone for ParsedCommandNode<S, R> { 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<S> { - pub parent: Arc<RwLock<CommandNode<S>>>, +pub struct SuggestionContext<S, R> { + pub parent: Arc<RwLock<CommandNode<S, R>>>, 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<String> { - &self.input + pub fn input(&self) -> Option<&str> { + self.input.as_deref() } pub fn cursor(&self) -> Option<usize> { 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<i32>; +} +impl CommandResultTrait for i32 { + fn new(i: i32) -> Self { + i + } + fn as_i32(&self) -> Option<i32> { + Some(*self) + } +} +impl<E> CommandResultTrait for Result<i32, E> { + fn new(i: i32) -> Self { + Ok(i) + } + fn as_i32(&self) -> Option<i32> { + 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<S> = - dyn Fn(&CommandContext<S>) -> Result<Vec<Arc<S>>, CommandSyntaxError> + Send + Sync; +pub type RedirectModifier<S, R> = + dyn Fn(&CommandContext<S, R>) -> Result<Vec<Arc<S>>, 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<Rc<CommandNode<S>>, CommandSyntaxError>, + pub exceptions: HashMap<Rc<CommandNode<S, R>>, CommandSyntaxError>, } -impl<S> Debug for ParseResults<'_, S> { +impl<S, R> 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<S> { - fn on_command_complete(&self, context: Rc<CommandContext<S>>, success: bool, result: i32); +pub trait ResultConsumer<S, R> { + fn on_command_complete(&self, context: Rc<CommandContext<S, R>>, success: bool, result: i32); } pub struct DefaultResultConsumer; -impl<S> ResultConsumer<S> for DefaultResultConsumer { - fn on_command_complete(&self, _context: Rc<CommandContext<S>>, _success: bool, _result: i32) {} +impl<S, R> ResultConsumer<S, R> for DefaultResultConsumer { + fn on_command_complete( + &self, + _context: Rc<CommandContext<S, R>>, + _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<S> { +pub trait SuggestionProvider<S, R> { fn get_suggestions( &self, - context: CommandContext<S>, + context: CommandContext<S, R>, 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<S> = - Option<Arc<dyn Fn(&CommandContext<S>) -> Result<i32, CommandSyntaxError> + Send + Sync>>; +pub type Command<S, R> = + Option<Arc<dyn Fn(&CommandContext<S, R>) -> Result<R, CommandSyntaxError> + Send + Sync>>; /// An ArgumentBuilder that has been built. #[non_exhaustive] -pub struct CommandNode<S> { - pub value: ArgumentBuilderType<S>, +pub struct CommandNode<S, R = i32> { + pub value: ArgumentBuilderType<S, R>, // this is a BTreeMap because children need to be ordered when getting command suggestions - pub children: BTreeMap<String, Arc<RwLock<CommandNode<S>>>>, - pub literals: HashMap<String, Arc<RwLock<CommandNode<S>>>>, - pub arguments: HashMap<String, Arc<RwLock<CommandNode<S>>>>, + pub children: BTreeMap<String, Arc<RwLock<CommandNode<S, R>>>>, + pub literals: HashMap<String, Arc<RwLock<CommandNode<S, R>>>>, + pub arguments: HashMap<String, Arc<RwLock<CommandNode<S, R>>>>, - pub command: Command<S>, + pub command: Command<S, R>, pub requirement: Arc<dyn Fn(&S) -> bool + Send + Sync>, - pub redirect: Option<Arc<RwLock<CommandNode<S>>>>, + pub redirect: Option<Arc<RwLock<CommandNode<S, R>>>>, pub forks: bool, - pub modifier: Option<Arc<RedirectModifier<S>>>, + pub modifier: Option<Arc<RedirectModifier<S, R>>>, } -impl<S> Clone for CommandNode<S> { +impl<S, R> Clone for CommandNode<S, R> { fn clone(&self) -> Self { Self { value: self.value.clone(), @@ -56,7 +56,7 @@ impl<S> Clone for CommandNode<S> { } } -impl<S> CommandNode<S> { +impl<S, R> CommandNode<S, R> { /// Returns the value as a literal from this command node, assuming it's /// already been checked. /// @@ -77,14 +77,17 @@ impl<S> CommandNode<S> { /// /// Will panic if this node is not an argument. Consider using a match /// statement instead. - pub fn argument(&self) -> &Argument<S> { + pub fn argument(&self) -> &Argument<S, R> { 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<Arc<RwLock<CommandNode<S>>>> { + pub fn get_relevant_nodes( + &self, + input: &mut StringReader, + ) -> Vec<Arc<RwLock<CommandNode<S, R>>>> { let literals = &self.literals; if literals.is_empty() { @@ -114,7 +117,7 @@ impl<S> CommandNode<S> { (self.requirement)(source) } - pub fn add_child(&mut self, node: &Arc<RwLock<CommandNode<S>>>) { + pub fn add_child(&mut self, node: &Arc<RwLock<CommandNode<S, R>>>) { 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<S> CommandNode<S> { } } - pub fn child(&self, name: &str) -> Option<Arc<RwLock<CommandNode<S>>>> { + pub fn child(&self, name: &str) -> Option<Arc<RwLock<CommandNode<S, R>>>> { self.children.get(name).cloned() } pub fn parse_with_context( &self, reader: &mut StringReader, - context_builder: &mut CommandContextBuilder<S>, + context_builder: &mut CommandContextBuilder<S, R>, ) -> Result<(), CommandSyntaxError> { match self.value { ArgumentBuilderType::Argument(ref argument) => { @@ -225,7 +228,7 @@ impl<S> CommandNode<S> { pub fn list_suggestions( &self, - context: CommandContext<S>, + context: CommandContext<S, R>, builder: SuggestionsBuilder, ) -> Suggestions { match &self.value { @@ -245,7 +248,7 @@ impl<S> CommandNode<S> { } } -impl<S> Debug for CommandNode<S> { +impl<S, R> Debug for CommandNode<S, R> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("CommandNode") // .field("value", &self.value) @@ -259,7 +262,7 @@ impl<S> Debug for CommandNode<S> { } } -impl<S> Default for CommandNode<S> { +impl<S, R> Default for CommandNode<S, R> { fn default() -> Self { Self { value: ArgumentBuilderType::Literal(Literal::default()), @@ -277,7 +280,7 @@ impl<S> Default for CommandNode<S> { } } -impl<S> Hash for CommandNode<S> { +impl<S, R> Hash for CommandNode<S, R> { fn hash<H: Hasher>(&self, state: &mut H) { // hash the children for (k, v) in &self.children { @@ -289,7 +292,7 @@ impl<S> Hash for CommandNode<S> { } } -impl<S> PartialEq for CommandNode<S> { +impl<S, R> PartialEq for CommandNode<S, R> { fn eq(&self, other: &Self) -> bool { if self.children.len() != other.children.len() { return false; @@ -324,4 +327,4 @@ impl<S> PartialEq for CommandNode<S> { true } } -impl<S> Eq for CommandNode<S> {} +impl<S, R> Eq for CommandNode<S, R> {} 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<Mutex<CommandSource>>; +pub type Ctx = CommandContext<Mutex<CommandSource>, eyre::Result<i32>>; +pub type Dispatcher = CommandDispatcher<Mutex<CommandSource>, eyre::Result<i32>>; pub struct CommandSource { pub bot: Client, @@ -42,7 +43,7 @@ impl CommandSource { } } -pub fn register_commands(commands: &mut CommandDispatcher<Mutex<CommandSource>>) { +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<Mutex<CommandSource>>) { +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<Mutex<CommandSource>>) { +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<Mutex<CommandSource>>) { 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::<EntityKindComponent>(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<Mutex<CommandSource>>) { } } - 1 + Ok(1) })); commands.register(literal("getblock").then(argument("x", integer()).then( @@ -149,9 +149,9 @@ pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) { 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<Mutex<CommandSource>>) { 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<Mutex<CommandSource>>) { } } } - 1 + Ok(1) })); commands.register(literal("pathfinderstate").executes(|ctx: &Ctx| { let source = ctx.source.lock(); - let pathfinder = source.bot.get_component::<Pathfinder>(); - let Some(pathfinder) = pathfinder else { + let pathfinder = source.bot.component::<Pathfinder>(); + 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::<ExecutingPath>(); - let Some(executing_path) = executing_path else { + let executing_path = source.bot.component::<ExecutingPath>(); + 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<Mutex<CommandSource>>) { "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<Mutex<CommandSource>>) { } } - 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::<MaxStackSize>() .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<metadata::Player>>(|_: ()| true); - let tab_list = source.bot.tab_list(); + .nearest_entities_by::<(), With<metadata::Player>>(|_: ()| 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<Mutex<CommandSource>>) { uuid )); } - 1 + Ok(1) })); commands.register(literal("enchants").executes(|ctx: &Ctx| { @@ -297,15 +297,15 @@ pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) { 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<Mutex<CommandSource>>) { 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<Mutex<CommandSource>>) { .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<Mutex<CommandSource>>) { +pub fn register(commands: &mut Dispatcher) { commands.register( literal("goto") .executes(|ctx: &Ctx| { @@ -19,14 +19,14 @@ pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) { // 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<Mutex<CommandSource>>) { 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<Mutex<CommandSource>>) { pos: BlockPos::new(x, y, z).center(), radius, }); - 1 + Ok(1) }), )), ))) @@ -66,7 +66,7 @@ pub fn register(commands: &mut CommandDispatcher<Mutex<CommandSource>>) { 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<Mutex<CommandSource>>) { // 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<Mutex<CommandSource>>) { 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<Mutex<CommandSource>>) { 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<Mutex<CommandSource>>) { 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<Mutex<CommandSource>>) { 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<Mutex<CommandSource>>) { 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<Mutex<CommandSource>>) { 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<Args>, - pub commands: Arc<CommandDispatcher<Mutex<CommandSource>>>, + pub commands: Arc<commands::Dispatcher>, } 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<Arc<RwLock<PartialWorld>>> { let world_holder = self.component::<WorldHolder>()?; 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<FormattedText> { 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<T> = error::AzaleaResult<T>; + pub type BoxHandleFn<S, R> = Box<dyn Fn(Client, Event, S) -> BoxFuture<'static, R> + Send>; pub type HandleFn<S, Fut> = fn(Client, Event, S) -> Fut; |
