diff options
40 files changed, 1024 insertions, 737 deletions
@@ -166,6 +166,7 @@ dependencies = [ "async-trait", "azalea-auth", "azalea-block", + "azalea-brigadier", "azalea-chat", "azalea-client", "azalea-core", @@ -232,6 +233,7 @@ version = "0.6.0" dependencies = [ "azalea-buf", "azalea-chat", + "parking_lot", ] [[package]] diff --git a/azalea-brigadier/Cargo.toml b/azalea-brigadier/Cargo.toml index 254ccc83..a1f5966b 100644 --- a/azalea-brigadier/Cargo.toml +++ b/azalea-brigadier/Cargo.toml @@ -11,6 +11,7 @@ version = "0.6.0" [dependencies] azalea-buf = {path = "../azalea-buf", version = "^0.6.0", optional = true} azalea-chat = {path = "../azalea-chat", version = "^0.6.0", optional = true} +parking_lot = "0.12.1" [features] azalea-buf = ["dep:azalea-buf", "dep:azalea-chat"] diff --git a/azalea-brigadier/README.md b/azalea-brigadier/README.md index c5aff629..6e573c8b 100755 --- a/azalea-brigadier/README.md +++ b/azalea-brigadier/README.md @@ -4,4 +4,23 @@ A Rust port of Mojang's [Brigadier](https://github.com/Mojang/brigadier) command # Examples -See the [tests](https://github.com/mat-1/azalea/tree/main/azalea-brigadier/tests). +```rust +use azalea_brigadier::prelude::*; +use std::sync::Arc; + +#[derive(Debug, PartialEq)] +struct CommandSource {} + +let mut subject = CommandDispatcher::new(); +subject.register(literal("foo").executes(|_| 42)); + +assert_eq!( + subject + .execute("foo", Arc::new(CommandSource {})) + .unwrap(), + 42 +); +``` + +See the [tests](https://github.com/mat-1/azalea/tree/main/azalea-brigadier/tests) for more. + diff --git a/azalea-brigadier/src/arguments/bool_argument_type.rs b/azalea-brigadier/src/arguments/bool_argument_type.rs new file mode 100644 index 00000000..57fa8a03 --- /dev/null +++ b/azalea-brigadier/src/arguments/bool_argument_type.rs @@ -0,0 +1,21 @@ +use std::{any::Any, rc::Rc}; + +use crate::{ + context::CommandContext, exceptions::CommandSyntaxException, string_reader::StringReader, +}; + +use super::ArgumentType; + +impl ArgumentType for bool { + fn parse(&self, reader: &mut StringReader) -> Result<Rc<dyn Any>, CommandSyntaxException> { + Ok(Rc::new(reader.read_boolean())) + } +} + +pub fn get_bool<S>(context: &CommandContext<S>, name: &str) -> Option<bool> { + context + .argument(name) + .unwrap() + .downcast_ref::<bool>() + .cloned() +} diff --git a/azalea-brigadier/src/arguments/double_argument_type.rs b/azalea-brigadier/src/arguments/double_argument_type.rs new file mode 100644 index 00000000..d83bfb2a --- /dev/null +++ b/azalea-brigadier/src/arguments/double_argument_type.rs @@ -0,0 +1,54 @@ +use std::{any::Any, rc::Rc}; + +use crate::{ + context::CommandContext, + exceptions::{BuiltInExceptions, CommandSyntaxException}, + string_reader::StringReader, +}; + +use super::ArgumentType; + +#[derive(Default)] +struct Double { + pub minimum: Option<f64>, + pub maximum: Option<f64>, +} + +impl ArgumentType for Double { + fn parse(&self, reader: &mut StringReader) -> Result<Rc<dyn Any>, CommandSyntaxException> { + let start = reader.cursor; + let result = reader.read_double()?; + if let Some(minimum) = self.minimum { + if result < minimum { + reader.cursor = start; + return Err(BuiltInExceptions::DoubleTooSmall { + found: result, + min: minimum, + } + .create_with_context(reader)); + } + } + if let Some(maximum) = self.maximum { + if result > maximum { + reader.cursor = start; + return Err(BuiltInExceptions::DoubleTooBig { + found: result, + max: maximum, + } + .create_with_context(reader)); + } + } + Ok(Rc::new(result)) + } +} + +pub fn double() -> impl ArgumentType { + Double::default() +} +pub fn get_double<S>(context: &CommandContext<S>, name: &str) -> Option<f64> { + context + .argument(name) + .unwrap() + .downcast_ref::<f64>() + .copied() +} diff --git a/azalea-brigadier/src/arguments/float_argument_type.rs b/azalea-brigadier/src/arguments/float_argument_type.rs new file mode 100644 index 00000000..0d194efe --- /dev/null +++ b/azalea-brigadier/src/arguments/float_argument_type.rs @@ -0,0 +1,54 @@ +use std::{any::Any, rc::Rc}; + +use crate::{ + context::CommandContext, + exceptions::{BuiltInExceptions, CommandSyntaxException}, + string_reader::StringReader, +}; + +use super::ArgumentType; + +#[derive(Default)] +struct Float { + pub minimum: Option<f32>, + pub maximum: Option<f32>, +} + +impl ArgumentType for Float { + fn parse(&self, reader: &mut StringReader) -> Result<Rc<dyn Any>, CommandSyntaxException> { + let start = reader.cursor; + let result = reader.read_float()?; + if let Some(minimum) = self.minimum { + if result < minimum { + reader.cursor = start; + return Err(BuiltInExceptions::FloatTooSmall { + found: result, + min: minimum, + } + .create_with_context(reader)); + } + } + if let Some(maximum) = self.maximum { + if result > maximum { + reader.cursor = start; + return Err(BuiltInExceptions::FloatTooBig { + found: result, + max: maximum, + } + .create_with_context(reader)); + } + } + Ok(Rc::new(result)) + } +} + +pub fn float() -> impl ArgumentType { + Float::default() +} +pub fn get_float<S>(context: &CommandContext<S>, name: &str) -> Option<f32> { + context + .argument(name) + .unwrap() + .downcast_ref::<f32>() + .copied() +} diff --git a/azalea-brigadier/src/arguments/integer_argument_type.rs b/azalea-brigadier/src/arguments/integer_argument_type.rs index 336046a7..336046a7 100755..100644 --- a/azalea-brigadier/src/arguments/integer_argument_type.rs +++ b/azalea-brigadier/src/arguments/integer_argument_type.rs diff --git a/azalea-brigadier/src/arguments/long_argument_type.rs b/azalea-brigadier/src/arguments/long_argument_type.rs new file mode 100644 index 00000000..9b3469b6 --- /dev/null +++ b/azalea-brigadier/src/arguments/long_argument_type.rs @@ -0,0 +1,54 @@ +use std::{any::Any, rc::Rc}; + +use crate::{ + context::CommandContext, + exceptions::{BuiltInExceptions, CommandSyntaxException}, + string_reader::StringReader, +}; + +use super::ArgumentType; + +#[derive(Default)] +struct Long { + pub minimum: Option<i64>, + pub maximum: Option<i64>, +} + +impl ArgumentType for Long { + fn parse(&self, reader: &mut StringReader) -> Result<Rc<dyn Any>, CommandSyntaxException> { + let start = reader.cursor; + let result = reader.read_long()?; + if let Some(minimum) = self.minimum { + if result < minimum { + reader.cursor = start; + return Err(BuiltInExceptions::LongTooSmall { + found: result, + min: minimum, + } + .create_with_context(reader)); + } + } + if let Some(maximum) = self.maximum { + if result > maximum { + reader.cursor = start; + return Err(BuiltInExceptions::LongTooBig { + found: result, + max: maximum, + } + .create_with_context(reader)); + } + } + Ok(Rc::new(result)) + } +} + +pub fn long() -> impl ArgumentType { + Long::default() +} +pub fn get_long<S>(context: &CommandContext<S>, name: &str) -> Option<i64> { + context + .argument(name) + .unwrap() + .downcast_ref::<i64>() + .copied() +} diff --git a/azalea-brigadier/src/arguments/mod.rs b/azalea-brigadier/src/arguments/mod.rs index dec39297..9d2c2cd0 100755 --- a/azalea-brigadier/src/arguments/mod.rs +++ b/azalea-brigadier/src/arguments/mod.rs @@ -1,4 +1,9 @@ mod argument_type; +pub mod bool_argument_type; +pub mod double_argument_type; +pub mod float_argument_type; pub mod integer_argument_type; +pub mod long_argument_type; +pub mod string_argument_type; pub use argument_type::ArgumentType; diff --git a/azalea-brigadier/src/arguments/string_argument_type.rs b/azalea-brigadier/src/arguments/string_argument_type.rs new file mode 100644 index 00000000..27363bd4 --- /dev/null +++ b/azalea-brigadier/src/arguments/string_argument_type.rs @@ -0,0 +1,53 @@ +use std::{any::Any, rc::Rc}; + +use crate::{ + context::CommandContext, exceptions::CommandSyntaxException, string_reader::StringReader, +}; + +use super::ArgumentType; + +pub enum StringArgument { + /// Match up until the next space. + SingleWord, + /// Same as single word unless the argument is wrapped in quotes, in which + /// case it can contain spaces. + QuotablePhrase, + /// Match the rest of the input. + GreedyPhrase, +} + +impl ArgumentType for StringArgument { + fn parse(&self, reader: &mut StringReader) -> Result<Rc<dyn Any>, CommandSyntaxException> { + let result = match self { + StringArgument::SingleWord => reader.read_unquoted_string().to_string(), + StringArgument::QuotablePhrase => reader.read_string()?, + StringArgument::GreedyPhrase => { + let text = reader.remaining().to_string(); + reader.cursor = reader.total_length(); + text + } + }; + Ok(Rc::new(result)) + } +} + +/// Match up until the next space. +pub fn word() -> impl ArgumentType { + StringArgument::SingleWord +} +/// Same as single word unless the argument is wrapped in quotes, in which case +/// it can contain spaces. +pub fn string() -> impl ArgumentType { + StringArgument::QuotablePhrase +} +/// Match the rest of the input. +pub fn greedy_string() -> impl ArgumentType { + StringArgument::GreedyPhrase +} +pub fn get_string<S>(context: &CommandContext<S>, name: &str) -> Option<String> { + context + .argument(name) + .unwrap() + .downcast_ref::<String>() + .cloned() +} diff --git a/azalea-brigadier/src/builder/argument_builder.rs b/azalea-brigadier/src/builder/argument_builder.rs index 38ccc98c..643a3bd0 100755 --- a/azalea-brigadier/src/builder/argument_builder.rs +++ b/azalea-brigadier/src/builder/argument_builder.rs @@ -1,3 +1,5 @@ +use parking_lot::RwLock; + use crate::{ context::CommandContext, modifier::RedirectModifier, @@ -5,7 +7,7 @@ use crate::{ }; use super::{literal_argument_builder::Literal, required_argument_builder::Argument}; -use std::{cell::RefCell, fmt::Debug, rc::Rc}; +use std::{fmt::Debug, sync::Arc}; #[derive(Debug, Clone)] pub enum ArgumentBuilderType { @@ -18,24 +20,11 @@ pub struct ArgumentBuilder<S> { arguments: CommandNode<S>, command: Command<S>, - requirement: Rc<dyn Fn(Rc<S>) -> bool>, - target: Option<Rc<RefCell<CommandNode<S>>>>, + requirement: Arc<dyn Fn(Arc<S>) -> bool + Send + Sync>, + target: Option<Arc<RwLock<CommandNode<S>>>>, forks: bool, - modifier: Option<Rc<RedirectModifier<S>>>, -} - -impl<S> Clone for ArgumentBuilder<S> { - fn clone(&self) -> Self { - Self { - arguments: self.arguments.clone(), - command: self.command.clone(), - requirement: self.requirement.clone(), - target: self.target.clone(), - forks: self.forks, - modifier: self.modifier.clone(), - } - } + modifier: Option<Arc<RedirectModifier<S>>>, } /// A node that isn't yet built. @@ -47,54 +36,93 @@ impl<S> ArgumentBuilder<S> { ..Default::default() }, command: None, - requirement: Rc::new(|_| true), + requirement: Arc::new(|_| true), forks: false, modifier: None, target: None, } } - pub fn then(&mut self, argument: ArgumentBuilder<S>) -> Self { + /// Continue building this node with a child node. + /// + /// ``` + /// # use azalea_brigadier::prelude::*; + /// # let mut subject = CommandDispatcher::<()>::new(); + /// literal("foo").then( + /// literal("bar").executes(|ctx: &CommandContext<()>| 42) + /// ) + /// # ; + /// ``` + pub fn then(self, argument: ArgumentBuilder<S>) -> Self { self.then_built(argument.build()) } - pub fn then_built(&mut self, argument: CommandNode<S>) -> Self { - self.arguments.add_child(&Rc::new(RefCell::new(argument))); - self.clone() + /// 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 { + self.arguments.add_child(&Arc::new(RwLock::new(argument))); + self } - pub fn executes<F>(&mut self, f: F) -> Self + /// Set the command to be executed when this node is reached. If this is not + /// present on a node, it is not a valid command. + /// + /// ``` + /// # use azalea_brigadier::prelude::*; + /// # let mut subject = CommandDispatcher::<()>::new(); + /// # subject.register( + /// literal("foo").executes(|ctx: &CommandContext<()>| 42) + /// # ); + /// ``` + pub fn executes<F>(mut self, f: F) -> Self where - F: Fn(&CommandContext<S>) -> i32 + 'static, + F: Fn(&CommandContext<S>) -> i32 + Send + Sync + 'static, { - self.command = Some(Rc::new(f)); - self.clone() + self.command = Some(Arc::new(f)); + self } - pub fn requires<F>(&mut self, requirement: F) -> Self + /// Set the requirement for this node to be considered. If this is not + /// present on a node, it is considered to always pass. + /// + /// ``` + /// # use azalea_brigadier::prelude::*; + /// # use std::sync::Arc; + /// # pub struct CommandSource { + /// # pub opped: bool, + /// # } + /// # let mut subject = CommandDispatcher::<CommandSource>::new(); + /// # subject.register( + /// literal("foo") + /// .requires(|s: Arc<CommandSource>| s.opped) + /// // ... + /// # .executes(|ctx: &CommandContext<CommandSource>| 42) + /// # ); + pub fn requires<F>(mut self, requirement: F) -> Self where - F: Fn(Rc<S>) -> bool + 'static, + F: Fn(Arc<S>) -> bool + Send + Sync + 'static, { - self.requirement = Rc::new(requirement); - self.clone() + self.requirement = Arc::new(requirement); + self } - pub fn redirect(&mut self, target: Rc<RefCell<CommandNode<S>>>) -> Self { + pub fn redirect(self, target: Arc<RwLock<CommandNode<S>>>) -> Self { self.forward(target, None, false) } pub fn fork( - &mut self, - target: Rc<RefCell<CommandNode<S>>>, - modifier: Rc<RedirectModifier<S>>, + self, + target: Arc<RwLock<CommandNode<S>>>, + modifier: Arc<RedirectModifier<S>>, ) -> Self { self.forward(target, Some(modifier), true) } pub fn forward( - &mut self, - target: Rc<RefCell<CommandNode<S>>>, - modifier: Option<Rc<RedirectModifier<S>>>, + mut self, + target: Arc<RwLock<CommandNode<S>>>, + modifier: Option<Arc<RedirectModifier<S>>>, fork: bool, ) -> Self { if !self.arguments.children.is_empty() { @@ -103,9 +131,11 @@ impl<S> ArgumentBuilder<S> { self.target = Some(target); self.modifier = modifier; self.forks = fork; - self.clone() + self } + /// Manually build this node into a [`CommandNode`]. You probably don't need + /// to do this yourself. pub fn build(self) -> CommandNode<S> { let mut result = CommandNode { value: self.arguments.value, diff --git a/azalea-brigadier/src/builder/required_argument_builder.rs b/azalea-brigadier/src/builder/required_argument_builder.rs index 9d4d9e0a..0363d204 100755 --- a/azalea-brigadier/src/builder/required_argument_builder.rs +++ b/azalea-brigadier/src/builder/required_argument_builder.rs @@ -2,17 +2,17 @@ use super::argument_builder::{ArgumentBuilder, ArgumentBuilderType}; use crate::{ arguments::ArgumentType, exceptions::CommandSyntaxException, string_reader::StringReader, }; -use std::{any::Any, fmt::Debug, rc::Rc}; +use std::{any::Any, fmt::Debug, rc::Rc, sync::Arc}; /// An argument node type. The `T` type parameter is the type of the argument, /// which can be anything. #[derive(Clone)] pub struct Argument { pub name: String, - parser: Rc<dyn ArgumentType>, + parser: Arc<dyn ArgumentType + Send + Sync>, } impl Argument { - pub fn new(name: &str, parser: Rc<dyn ArgumentType>) -> Self { + pub fn new(name: &str, parser: Arc<dyn ArgumentType + Send + Sync>) -> Self { Self { name: name.to_string(), parser, @@ -40,6 +40,9 @@ impl Debug for Argument { } /// Shortcut for creating a new argument builder node. -pub fn argument<S>(name: &str, parser: impl ArgumentType + 'static) -> ArgumentBuilder<S> { - ArgumentBuilder::new(Argument::new(name, Rc::new(parser)).into()) +pub fn argument<S>( + name: &str, + parser: impl ArgumentType + Send + Sync + 'static, +) -> ArgumentBuilder<S> { + ArgumentBuilder::new(Argument::new(name, Arc::new(parser)).into()) } diff --git a/azalea-brigadier/src/command_dispatcher.rs b/azalea-brigadier/src/command_dispatcher.rs index 273b1681..595bc57d 100755 --- a/azalea-brigadier/src/command_dispatcher.rs +++ b/azalea-brigadier/src/command_dispatcher.rs @@ -1,3 +1,5 @@ +use parking_lot::RwLock; + use crate::{ builder::argument_builder::ArgumentBuilder, context::{CommandContext, CommandContextBuilder}, @@ -6,64 +8,72 @@ use crate::{ string_reader::StringReader, tree::CommandNode, }; -use std::{cell::RefCell, cmp::Ordering, collections::HashMap, marker::PhantomData, mem, rc::Rc}; +use std::{cmp::Ordering, collections::HashMap, mem, rc::Rc, sync::Arc}; -#[derive(Default)] -pub struct CommandDispatcher<S> { - pub root: Rc<RefCell<CommandNode<S>>>, - _marker: PhantomData<S>, +/// The root of the command tree. You need to make this to register commands. +/// +/// ``` +/// # use azalea_brigadier::prelude::*; +/// # struct CommandSource; +/// let mut subject = CommandDispatcher::<CommandSource>::new(); +/// ``` +pub struct CommandDispatcher<S> +where + Self: Sync + Send, +{ + pub root: Arc<RwLock<CommandNode<S>>>, } impl<S> CommandDispatcher<S> { pub fn new() -> Self { Self { - root: Rc::new(RefCell::new(CommandNode::default())), - _marker: PhantomData, + root: Arc::new(RwLock::new(CommandNode::default())), } } - pub fn register(&mut self, node: ArgumentBuilder<S>) -> Rc<RefCell<CommandNode<S>>> { - let build = Rc::new(RefCell::new(node.build())); - self.root.borrow_mut().add_child(&build); + /// Add a new node to the root. + /// + /// ``` + /// # use azalea_brigadier::prelude::*; + /// # let mut subject = CommandDispatcher::<()>::new(); + /// subject.register(literal("foo").executes(|_| 42)); + /// ``` + pub fn register(&mut self, node: ArgumentBuilder<S>) -> Arc<RwLock<CommandNode<S>>> { + let build = Arc::new(RwLock::new(node.build())); + self.root.write().add_child(&build); build } - pub fn parse(&self, command: StringReader, source: Rc<S>) -> ParseResults<S> { - let context = CommandContextBuilder::new( - Rc::new(self.clone()), - source, - self.root.clone(), - command.cursor(), - ); + pub fn parse(&self, command: StringReader, source: S) -> ParseResults<S> { + let source = Arc::new(source); + + let context = CommandContextBuilder::new(self, source, self.root.clone(), command.cursor()); self.parse_nodes(&self.root, &command, context).unwrap() } - fn parse_nodes( - &self, - node: &Rc<RefCell<CommandNode<S>>>, + fn parse_nodes<'a>( + &'a self, + node: &Arc<RwLock<CommandNode<S>>>, original_reader: &StringReader, - context_so_far: CommandContextBuilder<S>, - ) -> Result<ParseResults<S>, CommandSyntaxException> { + context_so_far: CommandContextBuilder<'a, S>, + ) -> Result<ParseResults<'a, S>, CommandSyntaxException> { let source = context_so_far.source.clone(); let mut errors = HashMap::<Rc<CommandNode<S>>, CommandSyntaxException>::new(); let mut potentials: Vec<ParseResults<S>> = vec![]; let cursor = original_reader.cursor(); - for child in node - .borrow() - .get_relevant_nodes(&mut original_reader.clone()) - { - if !child.borrow().can_use(source.clone()) { + for child in node.read().get_relevant_nodes(&mut original_reader.clone()) { + if !child.read().can_use(source.clone()) { continue; } let mut context = context_so_far.clone(); let mut reader = original_reader.clone(); let parse_with_context_result = - child.borrow().parse_with_context(&mut reader, &mut context); + child.read().parse_with_context(&mut reader, &mut context); if let Err(ex) = parse_with_context_result { errors.insert( - Rc::new((*child.borrow()).clone()), + Rc::new((*child.read()).clone()), BuiltInExceptions::DispatcherParseException { message: ex.message(), } @@ -74,7 +84,7 @@ impl<S> CommandDispatcher<S> { } if reader.can_read() && reader.peek() != ' ' { errors.insert( - Rc::new((*child.borrow()).clone()), + Rc::new((*child.read()).clone()), BuiltInExceptions::DispatcherExpectedArgumentSeparator .create_with_context(&reader), ); @@ -82,24 +92,20 @@ impl<S> CommandDispatcher<S> { continue; } - context.with_command(&child.borrow().command); - if reader.can_read_length(if child.borrow().redirect.is_none() { + context.with_command(&child.read().command); + if reader.can_read_length(if child.read().redirect.is_none() { 2 } else { 1 }) { reader.skip(); - if let Some(redirect) = &child.borrow().redirect { - let child_context = CommandContextBuilder::new( - Rc::new(self.clone()), - source, - redirect.clone(), - reader.cursor, - ); + if let Some(redirect) = &child.read().redirect { + let child_context = + CommandContextBuilder::new(self, source, redirect.clone(), reader.cursor); let parse = self .parse_nodes(redirect, &reader, child_context) .expect("Parsing nodes failed"); - context.with_child(Rc::new(parse.context)); + context.with_child(Arc::new(parse.context)); return Ok(ParseResults { context, reader: parse.reader, @@ -149,40 +155,46 @@ impl<S> CommandDispatcher<S> { }) } + /// Parse and execute the command using the given input and context. The + /// number returned depends on the command, and may not be of significance. + /// + /// This is a shortcut for `Self::parse` and `Self::execute_parsed`. pub fn execute( &self, - input: StringReader, - source: Rc<S>, + input: impl Into<StringReader>, + source: S, ) -> Result<i32, CommandSyntaxException> { + let input = input.into(); + let parse = self.parse(input, source); Self::execute_parsed(parse) } pub fn add_paths( - node: Rc<RefCell<CommandNode<S>>>, - result: &mut Vec<Vec<Rc<RefCell<CommandNode<S>>>>>, - parents: Vec<Rc<RefCell<CommandNode<S>>>>, + node: Arc<RwLock<CommandNode<S>>>, + result: &mut Vec<Vec<Arc<RwLock<CommandNode<S>>>>>, + parents: Vec<Arc<RwLock<CommandNode<S>>>>, ) { let mut current = parents; current.push(node.clone()); result.push(current.clone()); - for child in node.borrow().children.values() { + for child in node.read().children.values() { Self::add_paths(child.clone(), result, current.clone()); } } pub fn get_path(&self, target: CommandNode<S>) -> Vec<String> { - let rc_target = Rc::new(RefCell::new(target)); - let mut nodes: Vec<Vec<Rc<RefCell<CommandNode<S>>>>> = Vec::new(); + let rc_target = Arc::new(RwLock::new(target)); + let mut nodes: Vec<Vec<Arc<RwLock<CommandNode<S>>>>> = Vec::new(); Self::add_paths(self.root.clone(), &mut nodes, vec![]); for list in nodes { - if *list.last().expect("Nothing in list").borrow() == *rc_target.borrow() { + if *list.last().expect("Nothing in list").read() == *rc_target.read() { let mut result: Vec<String> = Vec::with_capacity(list.len()); for node in list { - if node != self.root { - result.push(node.borrow().name().to_string()); + if !Arc::ptr_eq(&node, &self.root) { + result.push(node.read().name().to_string()); } } return result; @@ -191,10 +203,10 @@ impl<S> CommandDispatcher<S> { vec![] } - pub fn find_node(&self, path: &[&str]) -> Option<Rc<RefCell<CommandNode<S>>>> { + pub fn find_node(&self, path: &[&str]) -> Option<Arc<RwLock<CommandNode<S>>>> { let mut node = self.root.clone(); for name in path { - if let Some(child) = node.clone().borrow().child(name) { + if let Some(child) = node.clone().read().child(name) { node = child; } else { return None; @@ -287,11 +299,8 @@ impl<S> CommandDispatcher<S> { } } -impl<S> Clone for CommandDispatcher<S> { - fn clone(&self) -> Self { - Self { - root: self.root.clone(), - _marker: PhantomData, - } +impl<S> Default for CommandDispatcher<S> { + fn default() -> Self { + Self::new() } } diff --git a/azalea-brigadier/src/context/command_context.rs b/azalea-brigadier/src/context/command_context.rs index 98609a6e..1734bb05 100755 --- a/azalea-brigadier/src/context/command_context.rs +++ b/azalea-brigadier/src/context/command_context.rs @@ -1,21 +1,23 @@ +use parking_lot::RwLock; + use super::{parsed_command_node::ParsedCommandNode, string_range::StringRange, ParsedArgument}; use crate::{ modifier::RedirectModifier, tree::{Command, CommandNode}, }; -use std::{any::Any, cell::RefCell, collections::HashMap, fmt::Debug, rc::Rc}; +use std::{any::Any, collections::HashMap, fmt::Debug, rc::Rc, sync::Arc}; /// A built `CommandContextBuilder`. pub struct CommandContext<S> { - pub source: Rc<S>, + pub source: Arc<S>, pub input: String, pub arguments: HashMap<String, ParsedArgument>, pub command: Command<S>, - pub root_node: Rc<RefCell<CommandNode<S>>>, + pub root_node: Arc<RwLock<CommandNode<S>>>, pub nodes: Vec<ParsedCommandNode<S>>, pub range: StringRange, - pub child: Option<Rc<CommandContext<S>>>, - pub modifier: Option<Rc<RedirectModifier<S>>>, + pub child: Option<Arc<CommandContext<S>>>, + pub modifier: Option<Arc<RedirectModifier<S>>>, pub forks: bool, } @@ -54,8 +56,8 @@ impl<S> Debug for CommandContext<S> { } impl<S> CommandContext<S> { - pub fn copy_for(&self, source: Rc<S>) -> Self { - if Rc::ptr_eq(&source, &self.source) { + pub fn copy_for(&self, source: Arc<S>) -> Self { + if Arc::ptr_eq(&source, &self.source) { return self.clone(); } CommandContext { diff --git a/azalea-brigadier/src/context/command_context_builder.rs b/azalea-brigadier/src/context/command_context_builder.rs index 7516ab9e..78088941 100755 --- a/azalea-brigadier/src/context/command_context_builder.rs +++ b/azalea-brigadier/src/context/command_context_builder.rs @@ -1,3 +1,5 @@ +use parking_lot::RwLock; + use super::{ command_context::CommandContext, parsed_command_node::ParsedCommandNode, string_range::StringRange, ParsedArgument, @@ -7,28 +9,28 @@ use crate::{ modifier::RedirectModifier, tree::{Command, CommandNode}, }; -use std::{cell::RefCell, collections::HashMap, fmt::Debug, rc::Rc}; +use std::{collections::HashMap, fmt::Debug, sync::Arc}; -pub struct CommandContextBuilder<S> { +pub struct CommandContextBuilder<'a, S> { pub arguments: HashMap<String, ParsedArgument>, - pub root: Rc<RefCell<CommandNode<S>>>, + pub root: Arc<RwLock<CommandNode<S>>>, pub nodes: Vec<ParsedCommandNode<S>>, - pub dispatcher: Rc<CommandDispatcher<S>>, - pub source: Rc<S>, + pub dispatcher: &'a CommandDispatcher<S>, + pub source: Arc<S>, pub command: Command<S>, - pub child: Option<Rc<CommandContextBuilder<S>>>, + pub child: Option<Arc<CommandContextBuilder<'a, S>>>, pub range: StringRange, - pub modifier: Option<Rc<RedirectModifier<S>>>, + pub modifier: Option<Arc<RedirectModifier<S>>>, pub forks: bool, } -impl<S> Clone for CommandContextBuilder<S> { +impl<S> Clone for CommandContextBuilder<'_, S> { fn clone(&self) -> Self { Self { arguments: self.arguments.clone(), root: self.root.clone(), nodes: self.nodes.clone(), - dispatcher: self.dispatcher.clone(), + dispatcher: self.dispatcher, source: self.source.clone(), command: self.command.clone(), child: self.child.clone(), @@ -39,11 +41,11 @@ impl<S> Clone for CommandContextBuilder<S> { } } -impl<S> CommandContextBuilder<S> { +impl<'a, S> CommandContextBuilder<'a, S> { pub fn new( - dispatcher: Rc<CommandDispatcher<S>>, - source: Rc<S>, - root_node: Rc<RefCell<CommandNode<S>>>, + dispatcher: &'a CommandDispatcher<S>, + source: Arc<S>, + root_node: Arc<RwLock<CommandNode<S>>>, start: usize, ) -> Self { Self { @@ -64,7 +66,7 @@ impl<S> CommandContextBuilder<S> { self.command = command.clone(); self } - pub fn with_child(&mut self, child: Rc<CommandContextBuilder<S>>) -> &Self { + pub fn with_child(&mut self, child: Arc<CommandContextBuilder<'a, S>>) -> &Self { self.child = Some(child); self } @@ -72,14 +74,14 @@ impl<S> CommandContextBuilder<S> { self.arguments.insert(name.to_string(), argument); self } - pub fn with_node(&mut self, node: Rc<RefCell<CommandNode<S>>>, range: StringRange) -> &Self { + pub fn with_node(&mut self, node: Arc<RwLock<CommandNode<S>>>, range: StringRange) -> &Self { self.nodes.push(ParsedCommandNode { node: node.clone(), range: range.clone(), }); self.range = StringRange::encompassing(&self.range, &range); - self.modifier = node.borrow().modifier.clone(); - self.forks = node.borrow().forks; + self.modifier = node.read().modifier.clone(); + self.forks = node.read().forks; self } @@ -90,7 +92,7 @@ impl<S> CommandContextBuilder<S> { nodes: self.nodes.clone(), source: self.source.clone(), command: self.command.clone(), - child: self.child.clone().map(|c| Rc::new(c.build(input))), + child: self.child.clone().map(|c| Arc::new(c.build(input))), range: self.range.clone(), forks: self.forks, modifier: self.modifier.clone(), @@ -99,7 +101,7 @@ impl<S> CommandContextBuilder<S> { } } -impl<S> Debug for CommandContextBuilder<S> { +impl<S> Debug for CommandContextBuilder<'_, S> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("CommandContextBuilder") // .field("arguments", &self.arguments) diff --git a/azalea-brigadier/src/context/parsed_command_node.rs b/azalea-brigadier/src/context/parsed_command_node.rs index ed49928d..bba5d121 100755 --- a/azalea-brigadier/src/context/parsed_command_node.rs +++ b/azalea-brigadier/src/context/parsed_command_node.rs @@ -1,10 +1,12 @@ +use parking_lot::RwLock; + use super::string_range::StringRange; use crate::tree::CommandNode; -use std::{cell::RefCell, rc::Rc}; +use std::sync::Arc; #[derive(Debug)] pub struct ParsedCommandNode<S> { - pub node: Rc<RefCell<CommandNode<S>>>, + pub node: Arc<RwLock<CommandNode<S>>>, pub range: StringRange, } diff --git a/azalea-brigadier/src/lib.rs b/azalea-brigadier/src/lib.rs index eb670643..161ef83a 100755 --- a/azalea-brigadier/src/lib.rs +++ b/azalea-brigadier/src/lib.rs @@ -10,3 +10,18 @@ pub mod parse_results; pub mod string_reader; pub mod suggestion; pub mod tree; + +pub mod prelude { + pub use crate::{ + arguments::{ + double_argument_type::{double, get_double}, + float_argument_type::{float, get_float}, + integer_argument_type::{get_integer, integer}, + long_argument_type::{get_long, long}, + string_argument_type::{get_string, greedy_string, string, word}, + }, + builder::{literal_argument_builder::literal, required_argument_builder::argument}, + command_dispatcher::CommandDispatcher, + context::CommandContext, + }; +} diff --git a/azalea-brigadier/src/modifier.rs b/azalea-brigadier/src/modifier.rs index d40d59f9..bebdd0cb 100755 --- a/azalea-brigadier/src/modifier.rs +++ b/azalea-brigadier/src/modifier.rs @@ -1,6 +1,6 @@ -use std::rc::Rc; +use std::sync::Arc; use crate::{context::CommandContext, exceptions::CommandSyntaxException}; pub type RedirectModifier<S> = - dyn Fn(&CommandContext<S>) -> Result<Vec<Rc<S>>, CommandSyntaxException>; + dyn Fn(&CommandContext<S>) -> Result<Vec<Arc<S>>, CommandSyntaxException> + Send + Sync; diff --git a/azalea-brigadier/src/parse_results.rs b/azalea-brigadier/src/parse_results.rs index 3698ae82..aa7d79ea 100755 --- a/azalea-brigadier/src/parse_results.rs +++ b/azalea-brigadier/src/parse_results.rs @@ -4,13 +4,13 @@ use crate::{ }; use std::{collections::HashMap, fmt::Debug, rc::Rc}; -pub struct ParseResults<S> { - pub context: CommandContextBuilder<S>, +pub struct ParseResults<'a, S> { + pub context: CommandContextBuilder<'a, S>, pub reader: StringReader, pub exceptions: HashMap<Rc<CommandNode<S>>, CommandSyntaxException>, } -impl<S> Debug for ParseResults<S> { +impl<S> Debug for ParseResults<'_, S> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("ParseResults") .field("context", &self.context) diff --git a/azalea-brigadier/src/tree/mod.rs b/azalea-brigadier/src/tree/mod.rs index bb6af68d..cec972dc 100755 --- a/azalea-brigadier/src/tree/mod.rs +++ b/azalea-brigadier/src/tree/mod.rs @@ -1,3 +1,5 @@ +use parking_lot::RwLock; + use crate::{ builder::{ argument_builder::ArgumentBuilderType, literal_argument_builder::Literal, @@ -8,24 +10,24 @@ use crate::{ modifier::RedirectModifier, string_reader::StringReader, }; -use std::{cell::RefCell, collections::HashMap, fmt::Debug, hash::Hash, ptr, rc::Rc}; +use std::{collections::HashMap, fmt::Debug, hash::Hash, ptr, sync::Arc}; -pub type Command<S> = Option<Rc<dyn Fn(&CommandContext<S>) -> i32>>; +pub type Command<S> = Option<Arc<dyn Fn(&CommandContext<S>) -> i32 + Send + Sync>>; /// An ArgumentBuilder that has been built. #[non_exhaustive] pub struct CommandNode<S> { pub value: ArgumentBuilderType, - pub children: HashMap<String, Rc<RefCell<CommandNode<S>>>>, - pub literals: HashMap<String, Rc<RefCell<CommandNode<S>>>>, - pub arguments: HashMap<String, Rc<RefCell<CommandNode<S>>>>, + pub children: HashMap<String, Arc<RwLock<CommandNode<S>>>>, + pub literals: HashMap<String, Arc<RwLock<CommandNode<S>>>>, + pub arguments: HashMap<String, Arc<RwLock<CommandNode<S>>>>, pub command: Command<S>, - pub requirement: Rc<dyn Fn(Rc<S>) -> bool>, - pub redirect: Option<Rc<RefCell<CommandNode<S>>>>, + pub requirement: Arc<dyn Fn(Arc<S>) -> bool + Send + Sync>, + pub redirect: Option<Arc<RwLock<CommandNode<S>>>>, pub forks: bool, - pub modifier: Option<Rc<RedirectModifier<S>>>, + pub modifier: Option<Arc<RedirectModifier<S>>>, } impl<S> Clone for CommandNode<S> { @@ -62,7 +64,7 @@ impl<S> CommandNode<S> { } } - pub fn get_relevant_nodes(&self, input: &mut StringReader) -> Vec<Rc<RefCell<CommandNode<S>>>> { + pub fn get_relevant_nodes(&self, input: &mut StringReader) -> Vec<Arc<RwLock<CommandNode<S>>>> { let literals = &self.literals; if literals.is_empty() { @@ -88,24 +90,24 @@ impl<S> CommandNode<S> { } } - pub fn can_use(&self, source: Rc<S>) -> bool { + pub fn can_use(&self, source: Arc<S>) -> bool { (self.requirement)(source) } - pub fn add_child(&mut self, node: &Rc<RefCell<CommandNode<S>>>) { - let child = self.children.get(node.borrow().name()); + pub fn add_child(&mut self, node: &Arc<RwLock<CommandNode<S>>>) { + let child = self.children.get(node.read().name()); if let Some(child) = child { // We've found something to merge onto - if let Some(command) = &node.borrow().command { - child.borrow_mut().command = Some(command.clone()); + if let Some(command) = &node.read().command { + child.write().command = Some(command.clone()); } - for grandchild in node.borrow().children.values() { - child.borrow_mut().add_child(grandchild); + for grandchild in node.read().children.values() { + child.write().add_child(grandchild); } } else { self.children - .insert(node.borrow().name().to_string(), node.clone()); - match &node.borrow().value { + .insert(node.read().name().to_string(), node.clone()); + match &node.read().value { ArgumentBuilderType::Literal(literal) => { self.literals.insert(literal.value.clone(), node.clone()); } @@ -123,7 +125,7 @@ impl<S> CommandNode<S> { } } - pub fn child(&self, name: &str) -> Option<Rc<RefCell<CommandNode<S>>>> { + pub fn child(&self, name: &str) -> Option<Arc<RwLock<CommandNode<S>>>> { self.children.get(name).cloned() } @@ -142,7 +144,7 @@ impl<S> CommandNode<S> { }; context_builder.with_argument(&argument.name, parsed.clone()); - context_builder.with_node(Rc::new(RefCell::new(self.clone())), parsed.range); + context_builder.with_node(Arc::new(RwLock::new(self.clone())), parsed.range); Ok(()) } @@ -152,7 +154,7 @@ impl<S> CommandNode<S> { if let Some(end) = end { context_builder.with_node( - Rc::new(RefCell::new(self.clone())), + Arc::new(RwLock::new(self.clone())), StringRange::between(start, end), ); return Ok(()); @@ -219,7 +221,7 @@ impl<S> Default for CommandNode<S> { arguments: HashMap::new(), command: None, - requirement: Rc::new(|_| true), + requirement: Arc::new(|_| true), redirect: None, forks: false, modifier: None, @@ -232,7 +234,7 @@ impl<S> Hash for CommandNode<S> { // hash the children for (k, v) in &self.children { k.hash(state); - v.borrow().hash(state); + v.read().hash(state); } // i hope this works because if doesn't then that'll be a problem ptr::hash(&self.command, state); @@ -241,14 +243,21 @@ impl<S> Hash for CommandNode<S> { impl<S> PartialEq for CommandNode<S> { fn eq(&self, other: &Self) -> bool { - if self.children != other.children { + if self.children.len() != other.children.len() { return false; } + for (k, v) in &self.children { + let other_child = other.children.get(k).unwrap(); + if !Arc::ptr_eq(v, other_child) { + return false; + } + } + if let Some(selfexecutes) = &self.command { // idk how to do this better since we can't compare `dyn Fn`s if let Some(otherexecutes) = &other.command { #[allow(clippy::vtable_address_comparisons)] - if !Rc::ptr_eq(selfexecutes, otherexecutes) { + if !Arc::ptr_eq(selfexecutes, otherexecutes) { return false; } } else { diff --git a/azalea-brigadier/tests/builder/argument_builder_test.rs b/azalea-brigadier/tests/builder/argument_builder_test.rs index e570c988..ee44f5e6 100755 --- a/azalea-brigadier/tests/builder/argument_builder_test.rs +++ b/azalea-brigadier/tests/builder/argument_builder_test.rs @@ -17,13 +17,14 @@ use super::ArgumentBuilder; // @Test // public void testArguments() throws Exception { -// final RequiredArgumentBuilder<Object, ?> argument = argument("bar", integer()); +// final RequiredArgumentBuilder<Object, ?> argument = argument("bar", +// integer()); // builder.then(argument); // assertThat(builder.getArguments(), hasSize(1)); -// assertThat(builder.getArguments(), hasItem((CommandNode<Object>) argument.build())); -// } +// assertThat(builder.getArguments(), hasItem((CommandNode<Object>) +// argument.build())); } #[test] fn test_arguments() { @@ -37,7 +38,7 @@ fn test_arguments() { .arguments .children .values() - .any(|e| *e.borrow() == *built_argument)); + .any(|e| *e.read() == *built_argument)); } // @Test @@ -61,8 +62,8 @@ fn test_arguments() { // builder.then(literal("foo")); // } -// private static class TestableArgumentBuilder<S> extends ArgumentBuilder<S, TestableArgumentBuilder<S>> { -// @Override +// private static class TestableArgumentBuilder<S> extends +// ArgumentBuilder<S, TestableArgumentBuilder<S>> { @Override // protected TestableArgumentBuilder<S> getThis() { // return this; // } diff --git a/azalea-brigadier/tests/command_dispatcher_test.rs b/azalea-brigadier/tests/command_dispatcher_test.rs index e30dd2c1..eecbf668 100755 --- a/azalea-brigadier/tests/command_dispatcher_test.rs +++ b/azalea-brigadier/tests/command_dispatcher_test.rs @@ -1,4 +1,4 @@ -use std::rc::Rc; +use std::sync::Arc; use azalea_brigadier::{ arguments::integer_argument_type::integer, @@ -19,26 +19,13 @@ fn input_with_offset(input: &str, offset: usize) -> StringReader { } #[test] -fn create_and_execute_command() { - let mut subject = CommandDispatcher::new(); - subject.register(literal("foo").executes(|_| 42)); - - assert_eq!( - subject - .execute("foo".into(), Rc::new(CommandSource {})) - .unwrap(), - 42 - ); -} - -#[test] fn create_and_execute_offset_command() { let mut subject = CommandDispatcher::new(); subject.register(literal("foo").executes(|_| 42)); assert_eq!( subject - .execute(input_with_offset("/foo", 1), Rc::new(CommandSource {})) + .execute(input_with_offset("/foo", 1), &CommandSource {}) .unwrap(), 42 ); @@ -50,18 +37,8 @@ fn create_and_merge_commands() { subject.register(literal("base").then(literal("foo").executes(|_| 42))); subject.register(literal("base").then(literal("bar").executes(|_| 42))); - assert_eq!( - subject - .execute("base foo".into(), Rc::new(CommandSource {})) - .unwrap(), - 42 - ); - assert_eq!( - subject - .execute("base bar".into(), Rc::new(CommandSource {})) - .unwrap(), - 42 - ); + assert_eq!(subject.execute("base foo", &CommandSource {}).unwrap(), 42); + assert_eq!(subject.execute("base bar", &CommandSource {}).unwrap(), 42); } #[test] @@ -70,7 +47,7 @@ fn execute_unknown_command() { subject.register(literal("bar")); subject.register(literal("baz")); - let execute_result = subject.execute("foo".into(), Rc::new(CommandSource {})); + let execute_result = subject.execute("foo", &CommandSource {}); let err = execute_result.err().unwrap(); match err.type_ { @@ -85,7 +62,7 @@ fn execute_impermissible_command() { let mut subject = CommandDispatcher::new(); subject.register(literal("foo").requires(|_| false)); - let execute_result = subject.execute("foo".into(), Rc::new(CommandSource {})); + let execute_result = subject.execute("foo", &CommandSource {}); let err = execute_result.err().unwrap(); match err.type_ { @@ -100,7 +77,7 @@ fn execute_empty_command() { let mut subject = CommandDispatcher::new(); subject.register(literal("")); - let execute_result = subject.execute("".into(), Rc::new(CommandSource {})); + let execute_result = subject.execute("", &CommandSource {}); let err = execute_result.err().unwrap(); match err.type_ { @@ -115,7 +92,7 @@ fn execute_unknown_subcommand() { let mut subject = CommandDispatcher::new(); subject.register(literal("foo").executes(|_| 42)); - let execute_result = subject.execute("foo bar".into(), Rc::new(CommandSource {})); + let execute_result = subject.execute("foo bar", &CommandSource {}); let err = execute_result.err().unwrap(); match err.type_ { @@ -130,7 +107,7 @@ fn execute_incorrect_literal() { let mut subject = CommandDispatcher::new(); subject.register(literal("foo").executes(|_| 42).then(literal("bar"))); - let execute_result = subject.execute("foo baz".into(), Rc::new(CommandSource {})); + let execute_result = subject.execute("foo baz", &CommandSource {}); let err = execute_result.err().unwrap(); match err.type_ { @@ -150,7 +127,7 @@ fn execute_ambiguous_incorrect_argument() { .then(literal("baz")), ); - let execute_result = subject.execute("foo unknown".into(), Rc::new(CommandSource {})); + let execute_result = subject.execute("foo unknown", &CommandSource {}); let err = execute_result.err().unwrap(); match err.type_ { @@ -172,12 +149,7 @@ fn execute_subcommand() { .executes(|_| 42), ); - assert_eq!( - subject - .execute("foo =".into(), Rc::new(CommandSource {})) - .unwrap(), - 100 - ); + assert_eq!(subject.execute("foo =", &CommandSource {}).unwrap(), 100); } #[test] @@ -185,7 +157,7 @@ fn parse_incomplete_literal() { let mut subject = CommandDispatcher::new(); subject.register(literal("foo").then(literal("bar").executes(|_| 42))); - let parse = subject.parse("foo ".into(), Rc::new(CommandSource {})); + let parse = subject.parse("foo ".into(), &CommandSource {}); assert_eq!(parse.reader.remaining(), " "); assert_eq!(parse.context.nodes.len(), 1); } @@ -195,7 +167,7 @@ fn parse_incomplete_argument() { let mut subject = CommandDispatcher::new(); subject.register(literal("foo").then(argument("bar", integer()).executes(|_| 42))); - let parse = subject.parse("foo ".into(), Rc::new(CommandSource {})); + let parse = subject.parse("foo ".into(), &CommandSource {}); assert_eq!(parse.reader.remaining(), " "); assert_eq!(parse.context.nodes.len(), 1); } @@ -210,12 +182,7 @@ fn execute_ambiguious_parent_subcommand() { .then(argument("right", integer()).then(argument("sub", integer()).executes(|_| 100))), ); - assert_eq!( - subject - .execute("test 1 2".into(), Rc::new(CommandSource {})) - .unwrap(), - 100 - ); + assert_eq!(subject.execute("test 1 2", &CommandSource {}).unwrap(), 100); } #[test] @@ -231,9 +198,7 @@ fn execute_ambiguious_parent_subcommand_via_redirect() { subject.register(literal("redirect").redirect(real)); assert_eq!( - subject - .execute("redirect 1 2".into(), Rc::new(CommandSource {})) - .unwrap(), + subject.execute("redirect 1 2", &CommandSource {}).unwrap(), 100 ); } @@ -248,34 +213,37 @@ fn execute_redirected_multiple_times() { let input = "redirected redirected actual"; - let parse = subject.parse(input.into(), Rc::new(CommandSource {})); + let parse = subject.parse(input.into(), &CommandSource {}); assert_eq!(parse.context.range.get(input), "redirected"); assert_eq!(parse.context.nodes.len(), 1); - assert_eq!(parse.context.root, root); + assert_eq!(*parse.context.root.read(), *root.read()); assert_eq!(parse.context.nodes[0].range, parse.context.range); - assert_eq!(parse.context.nodes[0].node, redirect_node); + assert_eq!(*parse.context.nodes[0].node.read(), *redirect_node.read()); let child1 = parse.context.child.clone(); assert!(child1.is_some()); assert_eq!(child1.clone().unwrap().range.get(input), "redirected"); assert_eq!(child1.clone().unwrap().nodes.len(), 1); - assert_eq!(child1.clone().unwrap().root, root); + assert_eq!(*child1.clone().unwrap().root.read(), *root.read()); assert_eq!( child1.clone().unwrap().nodes[0].range, child1.clone().unwrap().range ); - assert_eq!(child1.clone().unwrap().nodes[0].node, redirect_node); + assert_eq!( + *child1.clone().unwrap().nodes[0].node.read(), + *redirect_node.read() + ); let child2 = child1.unwrap().child.clone(); assert!(child2.is_some()); assert_eq!(child2.clone().unwrap().range.get(input), "actual"); assert_eq!(child2.clone().unwrap().nodes.len(), 1); - assert_eq!(child2.clone().unwrap().root, root); + assert_eq!(*child2.clone().unwrap().root.read(), *root.read()); assert_eq!( child2.clone().unwrap().nodes[0].range, child2.clone().unwrap().range ); - assert_eq!(child2.unwrap().nodes[0].node, concrete_node); + assert_eq!(*child2.unwrap().nodes[0].node.read(), *concrete_node.read()); assert_eq!(CommandDispatcher::execute_parsed(parse).unwrap(), 42); } @@ -284,34 +252,34 @@ fn execute_redirected_multiple_times() { fn execute_redirected() { let mut subject = CommandDispatcher::new(); - let source1 = Rc::new(CommandSource {}); - let source2 = Rc::new(CommandSource {}); + let source1 = Arc::new(CommandSource {}); + let source2 = Arc::new(CommandSource {}); - let modifier = move |_: &CommandContext<CommandSource>| -> Result<Vec<Rc<CommandSource>>, CommandSyntaxException> { + let modifier = move |_: &CommandContext<CommandSource>| -> Result<Vec<Arc<CommandSource>>, CommandSyntaxException> { Ok(vec![source1.clone(), source2.clone()]) }; let concrete_node = subject.register(literal("actual").executes(|_| 42)); let redirect_node = - subject.register(literal("redirected").fork(subject.root.clone(), Rc::new(modifier))); + subject.register(literal("redirected").fork(subject.root.clone(), Arc::new(modifier))); let input = "redirected actual"; - let parse = subject.parse(input.into(), Rc::new(CommandSource {})); + let parse = subject.parse(input.into(), CommandSource {}); assert_eq!(parse.context.range.get(input), "redirected"); assert_eq!(parse.context.nodes.len(), 1); - assert_eq!(parse.context.root, subject.root); + assert_eq!(*parse.context.root.read(), *subject.root.read()); assert_eq!(parse.context.nodes[0].range, parse.context.range); - assert_eq!(parse.context.nodes[0].node, redirect_node); + assert_eq!(*parse.context.nodes[0].node.read(), *redirect_node.read()); let parent = parse.context.child.clone(); assert!(parent.is_some()); let parent = parent.unwrap(); assert_eq!(parent.range.get(input), "actual"); assert_eq!(parent.nodes.len(), 1); - assert_eq!(parse.context.root, subject.root); + assert_eq!(*parse.context.root.read(), *subject.root.read()); assert_eq!(parent.nodes[0].range, parent.range); - assert_eq!(parent.nodes[0].node, concrete_node); - assert_eq!(parent.source, Rc::new(CommandSource {})); + assert_eq!(*parent.nodes[0].node.read(), *concrete_node.read()); + assert_eq!(*parent.source, CommandSource {}); assert_eq!(CommandDispatcher::execute_parsed(parse).unwrap(), 2); } @@ -326,7 +294,7 @@ fn execute_orphaned_subcommand() { .executes(|_| 42), ); - let result = subject.execute("foo 5".into(), Rc::new(CommandSource {})); + let result = subject.execute("foo 5", &CommandSource {}); assert!(result.is_err()); let result = result.unwrap_err(); assert_eq!( @@ -343,12 +311,7 @@ fn execute_invalid_other() { subject.register(literal("w").executes(|_| panic!("This should not run"))); subject.register(literal("world").executes(|_| 42)); - assert_eq!( - subject - .execute("world".into(), Rc::new(CommandSource {})) - .unwrap(), - 42 - ); + assert_eq!(subject.execute("world", &CommandSource {}).unwrap(), 42); } #[test] @@ -361,7 +324,7 @@ fn parse_no_space_separator() { .executes(|_| 42), ); - let result = subject.execute("foo$".into(), Rc::new(CommandSource {})); + let result = subject.execute("foo$", &CommandSource {}); assert!(result.is_err()); let result = result.unwrap_err(); assert_eq!( @@ -381,7 +344,7 @@ fn execute_invalid_subcommand() { .executes(|_| 42), ); - let result = subject.execute("foo bar".into(), Rc::new(CommandSource {})); + let result = subject.execute("foo bar", &CommandSource {}); assert!(result.is_err()); let result = result.unwrap_err(); // this fails for some reason, i blame mojang @@ -406,5 +369,5 @@ fn get_path() { fn find_node_doesnt_exist() { let subject = CommandDispatcher::<()>::new(); - assert_eq!(subject.find_node(&["foo", "bar"]), None) + assert!(subject.find_node(&["foo", "bar"]).is_none()) } diff --git a/azalea-client/src/client.rs b/azalea-client/src/client.rs index 7a4285e6..f2e91dfa 100644 --- a/azalea-client/src/client.rs +++ b/azalea-client/src/client.rs @@ -11,6 +11,7 @@ use crate::{ movement::{LastSentLookDirection, PlayerMovePlugin}, packet_handling::{self, PacketHandlerPlugin, PacketReceiver}, player::retroactively_add_game_profile_component, + respawn::RespawnPlugin, task_pool::TaskPoolPlugin, Account, PlayerInfo, }; @@ -717,6 +718,7 @@ impl PluginGroup for DefaultPlugins { .add(DisconnectPlugin) .add(PlayerMovePlugin) .add(InteractPlugin) + .add(RespawnPlugin) .add(TickBroadcastPlugin) } } diff --git a/azalea-client/src/interact.rs b/azalea-client/src/interact.rs index ec5ed87b..2da80760 100644 --- a/azalea-client/src/interact.rs +++ b/azalea-client/src/interact.rs @@ -6,7 +6,7 @@ use azalea_protocol::packets::game::{ }; use azalea_world::{ entity::{clamp_look_direction, view_vector, EyeHeight, LookDirection, Position, WorldName}, - InstanceContainer, + Instance, InstanceContainer, }; use bevy_app::{App, Plugin}; use bevy_ecs::{ @@ -153,13 +153,13 @@ fn update_hit_result_component( y: position.y + **eye_height as f64, z: position.z, }; - let hit_result = pick( - look_direction, - &eye_position, - world_name, - &instance_container, - pick_range, - ); + + let Some(instance_lock) = instance_container.get(world_name) else { + continue; + }; + let instance = instance_lock.read(); + + let hit_result = pick(look_direction, &eye_position, &instance, pick_range); if let Some(mut hit_result_ref) = hit_result_ref { **hit_result_ref = hit_result; } else { @@ -178,16 +178,11 @@ fn update_hit_result_component( pub fn pick( look_direction: &LookDirection, eye_position: &Vec3, - world_name: &WorldName, - instance_container: &InstanceContainer, + instance: &Instance, pick_range: f64, ) -> BlockHitResult { let view_vector = view_vector(look_direction); let end_position = eye_position + &(view_vector * pick_range); - let instance_lock = instance_container - .get(world_name) - .expect("entities must always be in a valid world"); - let instance = instance_lock.read(); azalea_physics::clip::clip( &instance.chunks, ClipContext { diff --git a/azalea-client/src/lib.rs b/azalea-client/src/lib.rs index c198ced3..2c08033b 100644 --- a/azalea-client/src/lib.rs +++ b/azalea-client/src/lib.rs @@ -25,6 +25,7 @@ mod movement; pub mod packet_handling; pub mod ping; mod player; +pub mod respawn; pub mod task_pool; pub use account::{Account, AccountOpts}; diff --git a/azalea-client/src/local_player.rs b/azalea-client/src/local_player.rs index 423b4308..691f9ced 100644 --- a/azalea-client/src/local_player.rs +++ b/azalea-client/src/local_player.rs @@ -139,9 +139,10 @@ pub fn update_in_loaded_chunk( ) { for (entity, local_player, position) in &query { let player_chunk_pos = ChunkPos::from(position); - let instance_lock = instance_container - .get(local_player) - .expect("local player should always be in an instance"); + let Some(instance_lock) = instance_container.get(local_player) else { + continue; + }; + let in_loaded_chunk = instance_lock.read().chunks.get(&player_chunk_pos).is_some(); if in_loaded_chunk { commands.entity(entity).insert(LocalPlayerInLoadedChunk); diff --git a/azalea-client/src/packet_handling.rs b/azalea-client/src/packet_handling.rs index 8ffff870..50887096 100644 --- a/azalea-client/src/packet_handling.rs +++ b/azalea-client/src/packet_handling.rs @@ -9,7 +9,9 @@ use azalea_protocol::{ serverbound_custom_payload_packet::ServerboundCustomPayloadPacket, serverbound_keep_alive_packet::ServerboundKeepAlivePacket, serverbound_move_player_pos_rot_packet::ServerboundMovePlayerPosRotPacket, - ClientboundGamePacket, ServerboundGamePacket, + serverbound_pong_packet::ServerboundPongPacket, + serverbound_resource_pack_packet::ServerboundResourcePackPacket, ClientboundGamePacket, + ServerboundGamePacket, }, read::ReadPacketError, }; @@ -995,7 +997,15 @@ fn process_packet_events(ecs: &mut World) { }) } ClientboundGamePacket::OpenSignEditor(_) => {} - ClientboundGamePacket::Ping(_) => {} + ClientboundGamePacket::Ping(p) => { + debug!("Got ping packet {:?}", p); + + let mut system_state: SystemState<Query<&mut LocalPlayer>> = SystemState::new(ecs); + let mut query = system_state.get_mut(ecs); + + let local_player = query.get_mut(player_entity).unwrap(); + local_player.write_packet(ServerboundPongPacket { id: p.id }.get()); + } ClientboundGamePacket::PlaceGhostRecipe(_) => {} ClientboundGamePacket::PlayerCombatEnd(_) => {} ClientboundGamePacket::PlayerCombatEnter(_) => {} @@ -1023,7 +1033,16 @@ fn process_packet_events(ecs: &mut World) { } ClientboundGamePacket::PlayerLookAt(_) => {} ClientboundGamePacket::RemoveMobEffect(_) => {} - ClientboundGamePacket::ResourcePack(_) => {} + ClientboundGamePacket::ResourcePack(p) => { + debug!("Got resource pack packet {:?}", p); + + let mut system_state: SystemState<Query<&mut LocalPlayer>> = SystemState::new(ecs); + let mut query = system_state.get_mut(ecs); + + let local_player = query.get_mut(player_entity).unwrap(); + // always accept resource pack + local_player.write_packet(ServerboundResourcePackPacket { action: azalea_protocol::packets::game::serverbound_resource_pack_packet::Action::Accepted }.get()); + } ClientboundGamePacket::Respawn(p) => { debug!("Got respawn packet {:?}", p); diff --git a/azalea-client/src/respawn.rs b/azalea-client/src/respawn.rs new file mode 100644 index 00000000..4e42157c --- /dev/null +++ b/azalea-client/src/respawn.rs @@ -0,0 +1,38 @@ +use azalea_protocol::packets::game::serverbound_client_command_packet::{ + self, ServerboundClientCommandPacket, +}; +use bevy_app::{App, Plugin}; +use bevy_ecs::prelude::*; + +use crate::LocalPlayer; + +/// Tell the server that we're respawning. +#[derive(Debug, Clone)] +pub struct PerformRespawnEvent { + pub entity: Entity, +} + +/// A plugin that makes [`PerformRespawnEvent`] send the packet to respawn. +pub struct RespawnPlugin; +impl Plugin for RespawnPlugin { + fn build(&self, app: &mut App) { + app.add_event::<PerformRespawnEvent>() + .add_system(perform_respawn); + } +} + +pub fn perform_respawn( + mut events: EventReader<PerformRespawnEvent>, + mut query: Query<&mut LocalPlayer>, +) { + for event in events.iter() { + if let Ok(local_player) = query.get_mut(event.entity) { + local_player.write_packet( + ServerboundClientCommandPacket { + action: serverbound_client_command_packet::Action::PerformRespawn, + } + .get(), + ); + } + } +} diff --git a/azalea-inventory/src/item/mod.rs b/azalea-inventory/src/item/mod.rs index 07e51363..0ad7b2c0 100644 --- a/azalea-inventory/src/item/mod.rs +++ b/azalea-inventory/src/item/mod.rs @@ -3,6 +3,8 @@ pub trait MaxStackSizeExt { /// /// This is a signed integer to be consistent with the `count` field of /// [`ItemSlotData`]. + /// + /// [`ItemSlotData`]: crate::ItemSlotData fn max_stack_size(&self) -> i8; /// Whether this item can be stacked with other items. diff --git a/azalea-nbt/src/serde/mod.rs b/azalea-nbt/src/serde/mod.rs new file mode 100644 index 00000000..21d53a0f --- /dev/null +++ b/azalea-nbt/src/serde/mod.rs @@ -0,0 +1 @@ +mod serializer; diff --git a/azalea-nbt/src/serde/serializer.rs b/azalea-nbt/src/serde/serializer.rs new file mode 100644 index 00000000..945d2b42 --- /dev/null +++ b/azalea-nbt/src/serde/serializer.rs @@ -0,0 +1,205 @@ +use serde::{ser, Serialize}; + +use crate::Nbt; + +use std; +use std::fmt::{self, Display}; + +use serde::de; + +pub type Result<T> = std::result::Result<T, Error>; + +// This is a bare-bones implementation. A real library would provide additional +// information in its error type, for example the line and column at which the +// error occurred, the byte offset into the input, or the current key being +// processed. +#[derive(Debug)] +pub enum Error { + Message(String), +} + +impl Display for Error { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + match self { + Error::Message(msg) => formatter.write_str(msg), + } + } +} + +impl ser::Error for Error { + fn custom<T: Display>(msg: T) -> Self { + Error::Message(msg.to_string()) + } +} + +impl de::Error for Error { + fn custom<T: Display>(msg: T) -> Self { + Error::Message(msg.to_string()) + } +} + +impl std::error::Error for Error {} + +impl<'a> ser::Serializer for &'a mut Nbt { + type Ok = (); + + type Error = Error; + + type SerializeSeq = Self; + + type SerializeTuple = Self; + + type SerializeTupleStruct = Self; + + type SerializeTupleVariant = Self; + + type SerializeMap = Self; + + type SerializeStruct = Self; + + type SerializeStructVariant = Self; + + fn serialize_bool(self, v: bool) -> Result<()> { + todo!() + } + + fn serialize_i8(self, v: i8) -> Result<()> { + todo!() + } + + fn serialize_i16(self, v: i16) -> Result<()> { + todo!() + } + + fn serialize_i32(self, v: i32) -> Result<()> { + todo!() + } + + fn serialize_i64(self, v: i64) -> Result<()> { + todo!() + } + + fn serialize_u8(self, v: u8) -> Result<()> { + todo!() + } + + fn serialize_u16(self, v: u16) -> Result<()> { + todo!() + } + + fn serialize_u32(self, v: u32) -> Result<()> { + todo!() + } + + fn serialize_u64(self, v: u64) -> Result<()> { + todo!() + } + + fn serialize_f32(self, v: f32) -> Result<()> { + todo!() + } + + fn serialize_f64(self, v: f64) -> Result<()> { + todo!() + } + + fn serialize_char(self, v: char) -> Result<()> { + todo!() + } + + fn serialize_str(self, v: &str) -> Result<()> { + todo!() + } + + fn serialize_bytes(self, v: &[u8]) -> Result<()> { + todo!() + } + + fn serialize_none(self) -> Result<()> { + todo!() + } + + fn serialize_some<T: ?Sized>(self, value: &T) -> Result<()> + where + T: Serialize, + { + todo!() + } + + fn serialize_unit(self) -> Result<()> { + todo!() + } + + fn serialize_unit_struct(self, name: &'static str) -> Result<()> { + todo!() + } + + fn serialize_unit_variant( + self, + name: &'static str, + variant_index: u32, + variant: &'static str, + ) -> Result<()> { + todo!() + } + + fn serialize_newtype_struct<T: ?Sized>(self, name: &'static str, value: &T) -> Result<()> + where + T: Serialize, + { + todo!() + } + + fn serialize_newtype_variant<T: ?Sized>( + self, + name: &'static str, + variant_index: u32, + variant: &'static str, + value: &T, + ) -> Result<()> + where + T: Serialize, + { + todo!() + } + + fn serialize_seq(self, len: Option<usize>) -> Result<()> { + todo!() + } + + fn serialize_tuple(self, len: usize) -> Result<()> { + todo!() + } + + fn serialize_tuple_struct(self, name: &'static str, len: usize) -> Result<()> { + todo!() + } + + fn serialize_tuple_variant( + self, + name: &'static str, + variant_index: u32, + variant: &'static str, + len: usize, + ) -> Result<()> { + todo!() + } + + fn serialize_map(self, len: Option<usize>) -> Result<()> { + todo!() + } + + fn serialize_struct(self, name: &'static str, len: usize) -> Result<()> { + todo!() + } + + fn serialize_struct_variant( + self, + name: &'static str, + variant_index: u32, + variant: &'static str, + len: usize, + ) -> Result<()> { + todo!() + } +} diff --git a/azalea/Cargo.toml b/azalea/Cargo.toml index 5dbc7556..4a7469d3 100644 --- a/azalea/Cargo.toml +++ b/azalea/Cargo.toml @@ -24,6 +24,7 @@ azalea-protocol = { version = "0.6.0", path = "../azalea-protocol" } azalea-registry = { version = "0.6.0", path = "../azalea-registry" } azalea-world = { version = "0.6.0", path = "../azalea-world" } azalea-auth = { version = "0.6.0", path = "../azalea-auth" } +azalea-brigadier = { version = "0.6.0", path = "../azalea-brigadier" } bevy_app = "0.10.0" bevy_ecs = "0.10.0" bevy_tasks = "0.10.0" diff --git a/azalea/examples/testbot.rs b/azalea/examples/testbot.rs index 3fe9253c..1774d005 100644 --- a/azalea/examples/testbot.rs +++ b/azalea/examples/testbot.rs @@ -8,10 +8,9 @@ use azalea::entity::{EyeHeight, Position}; use azalea::interact::HitResultComponent; use azalea::inventory::ItemSlot; use azalea::pathfinder::BlockPosGoal; +use azalea::protocol::packets::game::ClientboundGamePacket; use azalea::{prelude::*, swarm::prelude::*, BlockPos, GameProfileComponent, WalkDirection}; use azalea::{Account, Client, Event}; -use azalea_protocol::packets::game::serverbound_client_command_packet::ServerboundClientCommandPacket; -use azalea_protocol::packets::game::ClientboundGamePacket; use std::time::Duration; #[derive(Default, Clone, Component)] @@ -212,11 +211,6 @@ async fn handle(mut bot: Client, event: Event, _state: State) -> anyhow::Result< } } } - Event::Death(_) => { - bot.write_packet(ServerboundClientCommandPacket { - action: azalea_protocol::packets::game::serverbound_client_command_packet::Action::PerformRespawn, - }.get()); - } Event::Packet(packet) => match *packet { ClientboundGamePacket::Login(_) => { println!("login packet"); diff --git a/azalea/src/auto_respawn.rs b/azalea/src/auto_respawn.rs new file mode 100644 index 00000000..c5dd12a4 --- /dev/null +++ b/azalea/src/auto_respawn.rs @@ -0,0 +1,24 @@ +use crate::app::{App, Plugin}; +use azalea_client::packet_handling::DeathEvent; +use azalea_client::respawn::PerformRespawnEvent; +use bevy_ecs::prelude::*; + +/// A plugin that makes [`DeathEvent`]s send [`PerformRespawnEvent`]s. +#[derive(Clone, Default)] +pub struct AutoRespawnPlugin; +impl Plugin for AutoRespawnPlugin { + fn build(&self, app: &mut App) { + app.add_system(auto_respawn); + } +} + +fn auto_respawn( + mut events: EventReader<DeathEvent>, + mut perform_respawn_events: EventWriter<PerformRespawnEvent>, +) { + for event in events.iter() { + perform_respawn_events.send(PerformRespawnEvent { + entity: event.entity, + }); + } +} diff --git a/azalea/src/bot.rs b/azalea/src/bot.rs index e5ea4c28..13b33bb0 100644 --- a/azalea/src/bot.rs +++ b/azalea/src/bot.rs @@ -1,4 +1,5 @@ use crate::app::{App, CoreSchedule, IntoSystemAppConfig, Plugin, PluginGroup, PluginGroupBuilder}; +use crate::auto_respawn::AutoRespawnPlugin; use crate::container::ContainerPlugin; use crate::ecs::{ component::Component, @@ -135,5 +136,6 @@ impl PluginGroup for DefaultBotPlugins { .add(BotPlugin) .add(PathfinderPlugin) .add(ContainerPlugin) + .add(AutoRespawnPlugin) } } diff --git a/azalea/src/container.rs b/azalea/src/container.rs index 0016caad..fefcf189 100644 --- a/azalea/src/container.rs +++ b/azalea/src/container.rs @@ -21,10 +21,12 @@ impl Plugin for ContainerPlugin { pub trait ContainerClientExt { async fn open_container(&mut self, pos: BlockPos) -> Option<ContainerHandle>; + fn open_inventory(&mut self) -> Option<ContainerHandle>; } impl ContainerClientExt for Client { - /// Open a container in the world, like a chest. + /// Open a container in the world, like a chest. Use + /// [`Client::open_inventory`] to open your own inventory. /// /// ``` /// # use azalea::prelude::*; @@ -72,12 +74,36 @@ impl ContainerClientExt for Client { }) } } + + /// Open the player's inventory. This will return None if another + /// container is open. + /// + /// Note that this will send a packet to the server once it's dropped. Also, + /// due to how it's implemented, you could call this function multiple times + /// while another inventory handle already exists (but you shouldn't). + fn open_inventory(&mut self) -> Option<ContainerHandle> { + let ecs = self.ecs.lock(); + let inventory = ecs + .get::<InventoryComponent>(self.entity) + .expect("no inventory"); + + if inventory.id == 0 { + Some(ContainerHandle { + id: 0, + client: self.clone(), + }) + } else { + None + } + } } /// A handle to the open container. The container will be closed once this is /// dropped. pub struct ContainerHandle { - pub id: u8, + /// The id of the container. If this is 0, that means it's the player's + /// inventory. + id: u8, client: Client, } impl Drop for ContainerHandle { @@ -96,6 +122,13 @@ impl Debug for ContainerHandle { } } impl ContainerHandle { + /// Get the id of the container. If this is 0, that means it's the player's + /// inventory. Otherwise, the number isn't really meaningful since only one + /// container can be open at a time. + pub fn id(&self) -> u8 { + self.id + } + /// Returns the menu of the container. If the container is closed, this /// will return `None`. pub fn menu(&self) -> Option<Menu> { @@ -103,8 +136,14 @@ impl ContainerHandle { let inventory = ecs .get::<InventoryComponent>(self.client.entity) .expect("no inventory"); + + // this also makes sure we can't access the inventory while a container is open if inventory.id == self.id { - Some(inventory.container_menu.clone().unwrap()) + if self.id == 0 { + Some(inventory.inventory_menu.clone()) + } else { + Some(inventory.container_menu.clone().unwrap()) + } } else { None } diff --git a/azalea/src/lib.rs b/azalea/src/lib.rs index 2e8e4fa1..604961f4 100644 --- a/azalea/src/lib.rs +++ b/azalea/src/lib.rs @@ -3,6 +3,7 @@ #![allow(incomplete_features)] #![feature(async_fn_in_trait)] +mod auto_respawn; mod bot; mod container; pub mod pathfinder; @@ -12,6 +13,7 @@ pub mod swarm; use app::{App, Plugin, PluginGroup}; pub use azalea_auth as auth; pub use azalea_block as blocks; +pub use azalea_brigadier as brigadier; pub use azalea_client::*; pub use azalea_core::{BlockPos, Vec3}; pub use azalea_protocol as protocol; diff --git a/azalea/src/pathfinder/astar.rs b/azalea/src/pathfinder/astar.rs new file mode 100644 index 00000000..65caf337 --- /dev/null +++ b/azalea/src/pathfinder/astar.rs @@ -0,0 +1,111 @@ +use std::{cmp::Reverse, collections::HashMap, fmt::Debug, hash::Hash, ops::Add}; + +use priority_queue::PriorityQueue; + +pub fn a_star<N, W, HeuristicFn, SuccessorsFn, SuccessFn>( + start: N, + heuristic: HeuristicFn, + successors: SuccessorsFn, + success: SuccessFn, +) -> Option<Vec<N>> +where + N: Eq + Hash + Copy + Debug, + W: PartialOrd + Default + Copy + num_traits::Bounded + Debug + Add<Output = W>, + HeuristicFn: Fn(&N) -> W, + SuccessorsFn: Fn(&N) -> Vec<Edge<N, W>>, + SuccessFn: Fn(&N) -> bool, +{ + let mut open_set = PriorityQueue::new(); + open_set.push(start, Reverse(Weight(W::default()))); + let mut nodes: HashMap<N, Node<N, W>> = HashMap::new(); + nodes.insert( + start, + Node { + data: start, + came_from: None, + g_score: W::default(), + f_score: W::max_value(), + }, + ); + + while let Some((current_node, _)) = open_set.pop() { + if success(¤t_node) { + return Some(reconstruct_path(&nodes, current_node)); + } + + let current_g_score = nodes + .get(¤t_node) + .map(|n| n.g_score) + .unwrap_or(W::max_value()); + + for neighbor in successors(¤t_node) { + let tentative_g_score = current_g_score + neighbor.cost; + let neighbor_g_score = nodes + .get(&neighbor.target) + .map(|n| n.g_score) + .unwrap_or(W::max_value()); + if tentative_g_score < neighbor_g_score { + let f_score = tentative_g_score + heuristic(&neighbor.target); + nodes.insert( + neighbor.target, + Node { + data: neighbor.target, + came_from: Some(current_node), + g_score: tentative_g_score, + f_score, + }, + ); + open_set.push(neighbor.target, Reverse(Weight(f_score))); + } + } + } + + None +} + +fn reconstruct_path<N, W>(nodes: &HashMap<N, Node<N, W>>, current: N) -> Vec<N> +where + N: Eq + Hash + Copy + Debug, + W: PartialOrd + Default + Copy + num_traits::Bounded + Debug, +{ + let mut path = vec![current]; + let mut current = current; + while let Some(node) = nodes.get(¤t) { + if let Some(came_from) = node.came_from { + path.push(came_from); + current = came_from; + } else { + break; + } + } + path.reverse(); + path +} + +pub struct Node<N, W> { + pub data: N, + pub came_from: Option<N>, + pub g_score: W, + pub f_score: W, +} + +pub struct Edge<N: Eq + Hash + Copy, W: PartialOrd + Copy> { + pub target: N, + pub cost: W, +} + +#[derive(PartialEq)] +pub struct Weight<W: PartialOrd + Debug>(W); +impl<W: PartialOrd + Debug> Ord for Weight<W> { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.0 + .partial_cmp(&other.0) + .unwrap_or(std::cmp::Ordering::Equal) + } +} +impl<W: PartialOrd + Debug> Eq for Weight<W> {} +impl<W: PartialOrd + Debug> PartialOrd for Weight<W> { + fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { + self.0.partial_cmp(&other.0) + } +} diff --git a/azalea/src/pathfinder/mod.rs b/azalea/src/pathfinder/mod.rs index 56c8e0ce..3f978c95 100644 --- a/azalea/src/pathfinder/mod.rs +++ b/azalea/src/pathfinder/mod.rs @@ -1,7 +1,8 @@ +mod astar; mod moves; -mod mtdstarlite; use crate::bot::{JumpEvent, LookAtEvent}; +use crate::pathfinder::astar::a_star; use crate::{SprintDirection, WalkDirection}; use crate::app::{App, CoreSchedule, IntoSystemAppConfig, Plugin}; @@ -13,6 +14,7 @@ use crate::ecs::{ schedule::IntoSystemConfig, system::{Commands, Query, Res}, }; +use astar::Edge; use azalea_client::{StartSprintEvent, StartWalkEvent}; use azalea_core::{BlockPos, CardinalDirection}; use azalea_physics::PhysicsSet; @@ -25,8 +27,6 @@ use azalea_world::{ use bevy_tasks::{AsyncComputeTaskPool, Task}; use futures_lite::future; use log::{debug, error}; -use mtdstarlite::Edge; -pub use mtdstarlite::MTDStarLite; use std::collections::VecDeque; use std::sync::Arc; @@ -152,17 +152,22 @@ fn goto_listener( edges }; - let mut pf = MTDStarLite::new( + // let mut pf = MTDStarLite::new( + // start, + // end, + // |n| goal.heuristic(n), + // successors, + // successors, + // |n| goal.success(n), + // ); + + let start_time = std::time::Instant::now(); + let p = a_star( start, - end, |n| goal.heuristic(n), successors, - successors, |n| goal.success(n), ); - - let start_time = std::time::Instant::now(); - let p = pf.find_path(); let end_time = std::time::Instant::now(); debug!("path: {p:?}"); debug!("time: {:?}", end_time - start_time); diff --git a/azalea/src/pathfinder/mtdstarlite.rs b/azalea/src/pathfinder/mtdstarlite.rs deleted file mode 100644 index ce463279..00000000 --- a/azalea/src/pathfinder/mtdstarlite.rs +++ /dev/null @@ -1,454 +0,0 @@ -//! An implementation of Moving Target D* Lite as described in -//! <http://idm-lab.org/bib/abstracts/papers/aamas10a.pdf> -//! -//! Future optimization attempt ideas: -//! - Use a different priority queue (e.g. fibonacci heap) -//! - Use `FxHash` instead of the default hasher -//! - Have `par` be a raw pointer -//! - Try borrowing vs copying the Node in several places (like `state_mut`) -//! - Store edge costs in their own map - -use priority_queue::DoublePriorityQueue; -use std::{collections::HashMap, fmt::Debug, hash::Hash, ops::Add}; - -/// Nodes are coordinates. -pub struct MTDStarLite< - N: Eq + Hash + Copy + Debug, - W: PartialOrd + Default + Copy + num_traits::Bounded + Debug, - HeuristicFn: Fn(&N) -> W, - SuccessorsFn: Fn(&N) -> Vec<Edge<N, W>>, - PredecessorsFn: Fn(&N) -> Vec<Edge<N, W>>, - SuccessFn: Fn(&N) -> bool, -> { - /// Returns a rough estimate of how close we are to the goal. Lower = - /// closer. - pub heuristic: HeuristicFn, - /// Returns the nodes that can be reached from the given node. - pub successors: SuccessorsFn, - /// Returns the nodes that would direct us to the given node. If the graph - /// isn't directed (i.e. you can always return to the previous node), this - /// can be the same as `successors`. - pub predecessors: PredecessorsFn, - /// Returns true if the given node is at the goal. - /// A simple implementation is to check if the given node is equal to the - /// goal. - pub success: SuccessFn, - - start: N, - goal: N, - - old_start: N, - old_goal: N, - - k_m: W, - open: DoublePriorityQueue<N, Priority<W>>, - node_states: HashMap<N, NodeState<N, W>>, - updated_edge_costs: Vec<ChangedEdge<N, W>>, - - /// This only exists so it can be referenced by `state()` when there's no - /// state. - default_state: NodeState<N, W>, -} - -impl< - N: Eq + Hash + Copy + Debug, - W: PartialOrd + Add<Output = W> + Default + Copy + num_traits::Bounded + Debug, - HeuristicFn: Fn(&N) -> W, - SuccessorsFn: Fn(&N) -> Vec<Edge<N, W>>, - PredecessorsFn: Fn(&N) -> Vec<Edge<N, W>>, - SuccessFn: Fn(&N) -> bool, - > MTDStarLite<N, W, HeuristicFn, SuccessorsFn, PredecessorsFn, SuccessFn> -{ - fn calculate_key(&self, n: &N) -> Priority<W> { - let s = self.state(n); - let min_score = if s.g < s.rhs { s.g } else { s.rhs }; - Priority( - if min_score == W::max_value() { - min_score - } else { - min_score + (self.heuristic)(n) + self.k_m - }, - min_score, - ) - } - - pub fn new( - start: N, - goal: N, - heuristic: HeuristicFn, - successors: SuccessorsFn, - predecessors: PredecessorsFn, - success: SuccessFn, - ) -> Self { - let open = DoublePriorityQueue::default(); - let k_m = W::default(); - - let known_nodes = vec![start, goal]; - - let mut pf = MTDStarLite { - heuristic, - successors, - predecessors, - success, - - start, - goal, - - old_start: start, - old_goal: goal, - - k_m, - open, - node_states: HashMap::new(), - updated_edge_costs: Vec::new(), - - default_state: NodeState::default(), - }; - - for n in &known_nodes { - *pf.state_mut(n) = NodeState::default(); - } - pf.state_mut(&start).rhs = W::default(); - pf.open.push(start, pf.calculate_key(&start)); - - pf - } - - fn update_state(&mut self, n: &N) { - let u = self.state_mut(n); - if u.g != u.rhs { - if self.open.get(n).is_some() { - self.open.change_priority(n, self.calculate_key(n)); - } else { - self.open.push(*n, self.calculate_key(n)); - } - } else if self.open.get(n).is_some() { - self.open.remove(n); - } - } - - fn compute_cost_minimal_path(&mut self) { - while { - if let Some((_, top_key)) = self.open.peek_min() { - (top_key < &self.calculate_key(&self.goal)) || { - let goal_state = self.state(&self.goal); - goal_state.rhs > goal_state.g - } - } else { - false - } - } { - let (u_node, k_old) = self.open.pop_min().unwrap(); - let k_new = self.calculate_key(&u_node); - if k_old < k_new { - self.open.change_priority(&u_node, k_new); - continue; - } - let u = self.state_mut(&u_node); - if u.g > u.rhs { - u.g = u.rhs; - self.open.remove(&u_node); - for edge in (self.successors)(&u_node) { - let s_node = edge.target; - let s = self.state(&s_node); - let u = self.state(&u_node); - if s_node != self.start && (s.rhs > u.g + edge.cost) { - let s_rhs = u.g + edge.cost; - let s = self.state_mut(&s_node); - s.par = Some(u_node); - s.rhs = s_rhs; - self.update_state(&s_node); - } - } - } else { - u.g = W::max_value(); - let u_edge = Edge { - target: u_node, - cost: W::default(), - }; - for edge in (self.successors)(&u_node) - .iter() - .chain([&u_edge].into_iter()) - { - let s_node = edge.target; - let s = self.state(&s_node); - if s_node != self.start && s.par == Some(u_node) { - let mut min_pred = u_node; - let mut min_score = W::max_value(); - - for edge in (self.predecessors)(&s_node) { - let s = self.state(&edge.target); - let score = s.g + edge.cost; - if score < min_score { - min_score = score; - min_pred = edge.target; - } - } - - let s = self.state_mut(&s_node); - s.rhs = min_score; - if s.rhs == W::max_value() { - s.par = None; - } else { - s.par = Some(min_pred); - } - } - self.update_state(&s_node); - } - } - } - } - - pub fn find_path(&mut self) -> Option<Vec<N>> { - if (self.success)(&self.start) { - return None; - } - - // - self.k_m = self.k_m + (self.heuristic)(&self.old_goal); - - if self.old_start != self.start { - self.optimized_deletion(); - } - - while let Some(edge) = self.updated_edge_costs.pop() { - let (u_node, v_node) = (edge.predecessor, edge.successor); - // update the edge cost c(u, v); - if edge.old_cost > edge.cost { - let u_g = self.state(&u_node).g; - if v_node != self.start && self.state(&v_node).rhs > u_g + edge.cost { - let v = self.state_mut(&v_node); - v.par = Some(u_node); - v.rhs = u_g + edge.cost; - } - } else if v_node != self.start && self.state(&v_node).par == Some(u_node) { - let mut min_pred = u_node; - let mut min_score = W::max_value(); - - for edge in (self.predecessors)(&v_node) { - let s = self.state(&edge.target); - let score = s.g + edge.cost; - if score < min_score { - min_score = score; - min_pred = edge.target; - } - } - - let v = self.state_mut(&v_node); - v.rhs = min_score; - if v.rhs == W::max_value() { - v.par = None; - } else { - v.par = Some(min_pred); - } - self.update_state(&v_node); - } - } - // - - self.old_start = self.start; - self.old_goal = self.goal; - - self.compute_cost_minimal_path(); - if self.state(&self.goal).rhs == W::max_value() { - // no path exists - return None; - } - - let mut reverse_path = vec![self.goal]; - - // identify a path from sstart to sgoal using the parent pointers - let mut target = self.state(&self.goal).par; - while !(Some(self.start) == target) { - let Some(this_target) = target else { - break; - }; - // hunter follows path from start to goal; - reverse_path.push(this_target); - target = self.state(&this_target).par; - } - - // if hunter caught target { - // return None; - // } - - let path: Vec<N> = reverse_path.into_iter().rev().collect(); - - Some(path) - } - - fn optimized_deletion(&mut self) { - let start = self.start; - self.state_mut(&start).par = None; - - let mut min_pred = self.old_start; - let mut min_score = W::max_value(); - - for edge in (self.predecessors)(&self.old_start) { - let s = self.state(&edge.target); - let score = s.g + edge.cost; - if score < min_score { - min_score = score; - min_pred = edge.target; - } - } - - let old_start = self.old_start; - let s = self.state_mut(&old_start); - s.rhs = min_score; - if s.rhs == W::max_value() { - s.par = None; - } else { - s.par = Some(min_pred); - } - self.update_state(&old_start); - } - - fn state(&self, n: &N) -> &NodeState<N, W> { - self.node_states.get(n).unwrap_or(&self.default_state) - } - - fn state_mut(&mut self, n: &N) -> &mut NodeState<N, W> { - self.node_states.entry(*n).or_default() - } -} - -#[derive(PartialEq, Debug)] -pub struct Priority<W>(W, W) -where - W: PartialOrd + Debug; - -impl<W: PartialOrd + Debug> PartialOrd for Priority<W> { - fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { - if self.0 < other.0 { - Some(std::cmp::Ordering::Less) - } else if self.0 > other.0 { - Some(std::cmp::Ordering::Greater) - } else if self.1 < other.1 { - Some(std::cmp::Ordering::Less) - } else if self.1 > other.1 { - Some(std::cmp::Ordering::Greater) - } else { - Some(std::cmp::Ordering::Equal) - } - } -} -impl<W: PartialOrd + Debug> Ord for Priority<W> { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.partial_cmp(other) - .expect("Partial compare should not fail for Priority") - } -} -impl<W: PartialOrd + Debug> Eq for Priority<W> {} - -#[derive(Debug)] -pub struct NodeState<N: Eq + Hash + Copy + Debug, W: Default + num_traits::Bounded + Debug> { - pub g: W, - pub rhs: W, - // future possible optimization: try making this a pointer - pub par: Option<N>, -} - -impl<N: Eq + Hash + Copy + Debug, W: Default + num_traits::Bounded + Debug> Default - for NodeState<N, W> -{ - fn default() -> Self { - NodeState { - g: W::max_value(), - rhs: W::max_value(), - par: None, - } - } -} - -pub struct Edge<N: Eq + Hash + Copy, W: PartialOrd + Copy> { - pub target: N, - pub cost: W, -} - -pub struct ChangedEdge<N: Eq + Hash + Clone, W: PartialOrd + Copy> { - pub predecessor: N, - pub successor: N, - pub old_cost: W, - pub cost: W, -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_mtdstarlite() { - let maze = [ - [0, 1, 0, 0, 0], - [0, 1, 0, 1, 0], - [0, 0, 0, 1, 0], - [0, 1, 0, 1, 0], - [0, 0, 1, 0, 0], - ]; - let width = maze[0].len(); - let height = maze.len(); - - let goal = (4, 4); - - let heuristic = |n: &(usize, usize)| -> usize { - ((n.0 as isize - goal.0 as isize).abs() + (n.1 as isize - goal.1 as isize).abs()) - as usize - }; - let successors = |n: &(usize, usize)| -> Vec<Edge<(usize, usize), usize>> { - let mut successors = Vec::with_capacity(4); - let (x, y) = *n; - - if x > 0 && maze[y][x - 1] == 0 { - successors.push(Edge { - target: ((x - 1, y)), - cost: 1, - }); - } - if x < width - 1 && maze[y][x + 1] == 0 { - successors.push(Edge { - target: ((x + 1, y)), - cost: 1, - }); - } - if y > 0 && maze[y - 1][x] == 0 { - successors.push(Edge { - target: ((x, y - 1)), - cost: 1, - }); - } - if y < height - 1 && maze[y + 1][x] == 0 { - successors.push(Edge { - target: ((x, y + 1)), - cost: 1, - }); - } - - successors - }; - let predecessors = - |n: &(usize, usize)| -> Vec<Edge<(usize, usize), usize>> { successors(n) }; - - let mut pf = MTDStarLite::new((0, 0), goal, heuristic, successors, predecessors, |n| { - n == &goal - }); - let path = pf.find_path().unwrap(); - assert_eq!( - path, - vec![ - (0, 1), - (0, 2), - (1, 2), - (2, 2), - (2, 1), - (2, 0), - (3, 0), - (4, 0), - (4, 1), - (4, 2), - (4, 3), - (4, 4), - ] - ); - } -} |
