mirror of
https://github.com/mat-1/azalea.git
synced 2024-09-19 22:52:32 +00:00
Merge branch 'main' into 1.20
This commit is contained in:
commit
49952dd150
40 changed files with 1024 additions and 737 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -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]]
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
21
azalea-brigadier/src/arguments/bool_argument_type.rs
Normal file
21
azalea-brigadier/src/arguments/bool_argument_type.rs
Normal file
|
@ -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()
|
||||
}
|
54
azalea-brigadier/src/arguments/double_argument_type.rs
Normal file
54
azalea-brigadier/src/arguments/double_argument_type.rs
Normal file
|
@ -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()
|
||||
}
|
54
azalea-brigadier/src/arguments/float_argument_type.rs
Normal file
54
azalea-brigadier/src/arguments/float_argument_type.rs
Normal file
|
@ -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()
|
||||
}
|
0
azalea-brigadier/src/arguments/integer_argument_type.rs
Executable file → Normal file
0
azalea-brigadier/src/arguments/integer_argument_type.rs
Executable file → Normal file
54
azalea-brigadier/src/arguments/long_argument_type.rs
Normal file
54
azalea-brigadier/src/arguments/long_argument_type.rs
Normal file
|
@ -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()
|
||||
}
|
|
@ -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;
|
||||
|
|
53
azalea-brigadier/src/arguments/string_argument_type.rs
Normal file
53
azalea-brigadier/src/arguments/string_argument_type.rs
Normal file
|
@ -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()
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
// }
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use azalea_brigadier::{
|
||||
arguments::integer_argument_type::integer,
|
||||
|
@ -18,19 +18,6 @@ fn input_with_offset(input: &str, offset: usize) -> StringReader {
|
|||
result
|
||||
}
|
||||
|
||||
#[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();
|
||||
|
@ -38,7 +25,7 @@ fn create_and_execute_offset_command() {
|
|||
|
||||
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())
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
38
azalea-client/src/respawn.rs
Normal file
38
azalea-client/src/respawn.rs
Normal file
|
@ -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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
|
1
azalea-nbt/src/serde/mod.rs
Normal file
1
azalea-nbt/src/serde/mod.rs
Normal file
|
@ -0,0 +1 @@
|
|||
mod serializer;
|
205
azalea-nbt/src/serde/serializer.rs
Normal file
205
azalea-nbt/src/serde/serializer.rs
Normal file
|
@ -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!()
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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");
|
||||
|
|
24
azalea/src/auto_respawn.rs
Normal file
24
azalea/src/auto_respawn.rs
Normal file
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
111
azalea/src/pathfinder/astar.rs
Normal file
111
azalea/src/pathfinder/astar.rs
Normal file
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue