make commands async (#179)

This commit is contained in:
kralverde
2024-10-25 05:36:55 -04:00
committed by GitHub
parent 4bb52a7711
commit 7347bae275
13 changed files with 323 additions and 163 deletions

View File

@@ -63,3 +63,6 @@ png = "0.17.14"
# logging
simple_logger = { version = "5.0.0", features = ["threads"] }
# commands
async-trait = "0.1.83"

View File

@@ -1,32 +1,44 @@
use async_trait::async_trait;
use pumpkin_inventory::OpenContainer;
use crate::commands::tree::CommandTree;
use super::RunFunctionType;
const NAMES: [&str; 2] = ["echest", "enderchest"];
const DESCRIPTION: &str =
"Show your personal enderchest (this command is used for testing container behaviour)";
#[allow(unused_variables)]
struct EchestExecutor {}
pub fn init_command_tree<'a>() -> CommandTree<'a> {
CommandTree::new(NAMES, DESCRIPTION).execute(&|sender, server, _| {
#[async_trait]
impl RunFunctionType for EchestExecutor {
async fn execute(
&self,
sender: &mut super::CommandSender,
server: &crate::server::Server,
_args: &super::tree::ConsumedArgs,
) -> Result<(), super::dispatcher::InvalidTreeError> {
if let Some(player) = sender.as_player() {
let entity_id = player.entity_id();
// player.open_container.store(Some(0));
// {
// let mut open_containers = server.open_containers.write().await;
// match open_containers.get_mut(&0) {
// Some(ender_chest) => {
// ender_chest.add_player(entity_id);
// }
// None => {
// let open_container = OpenContainer::empty(entity_id);
// open_containers.insert(0, open_container);
// }
// }
// }
// player.open_container(server, "minecraft:generic_9x3");
player.open_container.store(Some(0));
{
let mut open_containers = server.open_containers.write().await;
if let Some(ender_chest) = open_containers.get_mut(&0) {
ender_chest.add_player(entity_id);
} else {
let open_container = OpenContainer::empty(entity_id);
open_containers.insert(0, open_container);
}
}
player.open_container(server, "minecraft:generic_9x3").await;
}
Ok(())
})
}
}
pub fn init_command_tree<'a>() -> CommandTree<'a> {
CommandTree::new(NAMES, DESCRIPTION).execute(&EchestExecutor {})
}

View File

@@ -1,8 +1,11 @@
use std::str::FromStr;
use async_trait::async_trait;
use num_traits::FromPrimitive;
use pumpkin_core::GameMode;
use crate::TextComponent;
use crate::commands::arg_player::{consume_arg_player, parse_arg_player};
use crate::commands::dispatcher::InvalidTreeError;
@@ -15,6 +18,8 @@ use crate::commands::CommandSender;
use crate::commands::CommandSender::Player;
use crate::server::Server;
use super::RunFunctionType;
const NAMES: [&str; 1] = ["gamemode"];
const DESCRIPTION: &str = "Change a player's gamemode.";
@@ -63,56 +68,83 @@ pub fn parse_arg_gamemode(consumed_args: &ConsumedArgs) -> Result<GameMode, Inva
}
}
struct GamemodeTargetSelf {}
#[async_trait]
impl RunFunctionType for GamemodeTargetSelf {
async fn execute(
&self,
sender: &mut CommandSender,
_server: &Server,
args: &ConsumedArgs,
) -> Result<(), InvalidTreeError> {
let gamemode = parse_arg_gamemode(args)?;
if let Player(target) = sender {
if target.gamemode.load() == gamemode {
target
.send_system_message(&TextComponent::text(&format!(
"You already in {gamemode:?} gamemode"
)))
.await;
} else {
target.set_gamemode(gamemode).await;
target
.send_system_message(&TextComponent::text(&format!(
"Game mode was set to {gamemode:?}"
)))
.await;
}
Ok(())
} else {
Err(InvalidRequirementError)
}
}
}
struct GamemodeTargetPlayer {}
#[async_trait]
impl RunFunctionType for GamemodeTargetPlayer {
async fn execute(
&self,
sender: &mut CommandSender,
server: &Server,
args: &ConsumedArgs,
) -> Result<(), InvalidTreeError> {
let gamemode = parse_arg_gamemode(args)?;
let target = parse_arg_player(sender, server, ARG_TARGET, args)?;
if target.gamemode.load() == gamemode {
sender
.send_message(TextComponent::text(&format!(
"{} is already in {:?} gamemode",
target.gameprofile.name, gamemode
)))
.await;
} else {
target.set_gamemode(gamemode).await;
sender
.send_message(TextComponent::text(&format!(
"{}'s Game mode was set to {:?}",
target.gameprofile.name, gamemode
)))
.await;
}
Ok(())
}
}
#[allow(clippy::redundant_closure_for_method_calls)]
pub fn init_command_tree<'a>() -> CommandTree<'a> {
CommandTree::new(NAMES, DESCRIPTION).with_child(
require(&|sender| sender.permission_lvl() >= 2).with_child(
argument(ARG_GAMEMODE, consume_arg_gamemode)
.with_child(require(&|sender| sender.is_player()).execute(&GamemodeTargetSelf {}))
.with_child(
require(&|sender| sender.is_player()).execute(&|sender, _, args| {
let gamemode = parse_arg_gamemode(args)?;
if let Player(target) = sender {
if target.gamemode.load() == gamemode {
// target.send_system_message(&TextComponent::text(&format!(
// "You already in {:?} gamemode",
// gamemode
// )));
} else {
// TODO
// target.set_gamemode(gamemode);
// target.send_system_message(&TextComponent::text(&format!(
// "Game mode was set to {:?}",
// gamemode
// )));
}
Ok(())
} else {
Err(InvalidRequirementError)
}
}),
)
.with_child(argument(ARG_TARGET, consume_arg_player).execute(
&|sender, server, args| {
let gamemode = parse_arg_gamemode(args)?;
let target = parse_arg_player(sender, server, ARG_TARGET, args)?;
if target.gamemode.load() == gamemode {
// sender.send_message(TextComponent::text(&format!(
// "{} is already in {:?} gamemode",
// target.gameprofile.name, gamemode
// )));
} else {
// TODO
// target.set_gamemode(gamemode);
// sender.send_message(TextComponent::text(&format!(
// "{}'s Game mode was set to {:?}",
// target.gameprofile.name, gamemode
// )));
}
Ok(())
},
)),
argument(ARG_TARGET, consume_arg_player).execute(&GamemodeTargetPlayer {}),
),
),
)
}

View File

@@ -1,3 +1,6 @@
use async_trait::async_trait;
use pumpkin_core::text::TextComponent;
use crate::commands::dispatcher::InvalidTreeError::InvalidConsumptionError;
use crate::commands::dispatcher::{CommandDispatcher, InvalidTreeError};
use crate::commands::tree::{Command, CommandTree, ConsumedArgs, RawArgs};
@@ -5,6 +8,8 @@ use crate::commands::tree_builder::argument;
use crate::commands::CommandSender;
use crate::server::Server;
use super::RunFunctionType;
const NAMES: [&str; 3] = ["help", "h", "?"];
const DESCRIPTION: &str = "Print a help message.";
@@ -36,41 +41,65 @@ fn parse_arg_command<'a>(
.map_err(|_| InvalidConsumptionError(Some(command_name.into())))
}
#[allow(unused_variables)]
struct BaseHelpExecutor {}
#[async_trait]
impl RunFunctionType for BaseHelpExecutor {
async fn execute(
&self,
sender: &mut CommandSender,
server: &Server,
args: &ConsumedArgs,
) -> Result<(), InvalidTreeError> {
let tree = parse_arg_command(args, &server.command_dispatcher)?;
sender
.send_message(TextComponent::text(&format!(
"{} - {} Usage: {}",
tree.names.join("/"),
tree.description,
tree
)))
.await;
Ok(())
}
}
struct CommandHelpExecutor {}
#[async_trait]
impl RunFunctionType for CommandHelpExecutor {
async fn execute(
&self,
sender: &mut CommandSender,
server: &Server,
_args: &ConsumedArgs,
) -> Result<(), InvalidTreeError> {
let mut keys: Vec<&str> = server.command_dispatcher.commands.keys().copied().collect();
keys.sort_unstable();
for key in keys {
let Command::Tree(tree) = &server.command_dispatcher.commands[key] else {
continue;
};
sender
.send_message(TextComponent::text(&format!(
"{} - {} Usage: {}",
tree.names.join("/"),
tree.description,
tree
)))
.await;
}
Ok(())
}
}
pub fn init_command_tree<'a>() -> CommandTree<'a> {
CommandTree::new(NAMES, DESCRIPTION)
.with_child(
argument(ARG_COMMAND, consume_arg_command).execute(&|sender, server, args| {
let tree = parse_arg_command(args, &server.command_dispatcher)?;
// sender.send_message(TextComponent::text(&format!(
// "{} - {} Usage: {}",
// tree.names.join("/"),
// tree.description,
// tree
// )));
Ok(())
}),
)
.execute(&|sender, server, _args| {
let mut keys: Vec<&str> = server.command_dispatcher.commands.keys().copied().collect();
keys.sort_unstable();
for key in keys {
let Command::Tree(tree) = &server.command_dispatcher.commands[key] else {
continue;
};
// sender.send_message(TextComponent::text(&format!(
// "{} - {} Usage: {}",
// tree.names.join("/"),
// tree.description,
// tree
// )));
}
Ok(())
})
.with_child(argument(ARG_COMMAND, consume_arg_command).execute(&BaseHelpExecutor {}))
.execute(&CommandHelpExecutor {})
}

View File

@@ -1,27 +1,45 @@
use async_trait::async_trait;
use pumpkin_core::text::color::NamedColor;
use pumpkin_core::text::TextComponent;
use crate::commands::arg_player::parse_arg_player;
use crate::commands::tree::CommandTree;
use crate::commands::tree_builder::argument;
use super::arg_player::consume_arg_player;
use super::RunFunctionType;
const NAMES: [&str; 1] = ["kick"];
const DESCRIPTION: &str = "Kicks the target player from the server.";
const ARG_TARGET: &str = "target";
#[expect(unused)]
struct KickExecutor {}
#[async_trait]
impl RunFunctionType for KickExecutor {
async fn execute(
&self,
sender: &mut super::CommandSender,
server: &crate::server::Server,
args: &super::tree::ConsumedArgs,
) -> Result<(), super::dispatcher::InvalidTreeError> {
let target = parse_arg_player(sender, server, ARG_TARGET, args)?;
target
.kick(TextComponent::text("Kicked by an operator"))
.await;
sender
.send_message(
TextComponent::text("Player has been kicked.").color_named(NamedColor::Blue),
)
.await;
Ok(())
}
}
pub fn init_command_tree<'a>() -> CommandTree<'a> {
CommandTree::new(NAMES, DESCRIPTION).with_child(
argument(ARG_TARGET, consume_arg_player).execute(&|sender, server, args| {
let target = parse_arg_player(sender, server, ARG_TARGET, args)?;
// target.kick(TextComponent::text("Kicked by an operator"));
// sender.send_message(
// TextComponent::text("Player has been kicked.").color_named(NamedColor::Blue),
// );
Ok(())
}),
)
CommandTree::new(NAMES, DESCRIPTION)
.with_child(argument(ARG_TARGET, consume_arg_player).execute(&KickExecutor {}))
}

View File

@@ -1,26 +1,43 @@
use async_trait::async_trait;
use pumpkin_core::text::color::NamedColor;
use pumpkin_core::text::TextComponent;
use crate::commands::arg_player::{consume_arg_player, parse_arg_player};
use crate::commands::tree::CommandTree;
use crate::commands::tree_builder::argument;
use super::RunFunctionType;
const NAMES: [&str; 1] = ["kill"];
const DESCRIPTION: &str = "Kills a target player.";
const ARG_TARGET: &str = "target";
#[expect(unused)]
struct KillExecutor {}
#[async_trait]
impl RunFunctionType for KillExecutor {
async fn execute(
&self,
sender: &mut super::CommandSender,
server: &crate::server::Server,
args: &super::tree::ConsumedArgs,
) -> Result<(), super::dispatcher::InvalidTreeError> {
// TODO parse entities not only players
let target = parse_arg_player(sender, server, ARG_TARGET, args)?;
target.living_entity.kill().await;
sender
.send_message(
TextComponent::text("Player has been killed.").color_named(NamedColor::Blue),
)
.await;
Ok(())
}
}
pub fn init_command_tree<'a>() -> CommandTree<'a> {
CommandTree::new(NAMES, DESCRIPTION).with_child(
argument(ARG_TARGET, consume_arg_player).execute(&|sender, server, args| {
// TODO parse entities not only players
let target = parse_arg_player(sender, server, ARG_TARGET, args)?;
// target.living_entity.kill();
// sender.send_message(
// TextComponent::text("Player has been killed.").color_named(NamedColor::Blue),
// );
Ok(())
}),
)
CommandTree::new(NAMES, DESCRIPTION)
.with_child(argument(ARG_TARGET, consume_arg_player).execute(&KillExecutor {}))
}

View File

@@ -1,20 +1,36 @@
use crate::commands::tree::CommandTree;
use async_trait::async_trait;
use pumpkin_core::text::{color::NamedColor, TextComponent};
use pumpkin_protocol::CURRENT_MC_PROTOCOL;
use crate::{commands::tree::CommandTree, server::CURRENT_MC_VERSION};
use super::RunFunctionType;
const NAMES: [&str; 1] = ["pumpkin"];
const DESCRIPTION: &str = "Display information about Pumpkin.";
#[expect(unused)]
struct PumpkinExecutor {}
pub fn init_command_tree<'a>() -> CommandTree<'a> {
CommandTree::new(NAMES, DESCRIPTION).execute(&|sender, _, _| {
#[async_trait]
impl RunFunctionType for PumpkinExecutor {
async fn execute(
&self,
sender: &mut super::CommandSender,
_server: &crate::server::Server,
_args: &super::tree::ConsumedArgs,
) -> Result<(), super::dispatcher::InvalidTreeError> {
let version = env!("CARGO_PKG_VERSION");
let description = env!("CARGO_PKG_DESCRIPTION");
// sender.send_message(TextComponent::text(
// &format!("Pumpkin {version}, {description} (Minecraft {CURRENT_MC_VERSION}, Protocol {CURRENT_MC_PROTOCOL})")
// ).color_named(NamedColor::Green)).await;
sender.send_message(TextComponent::text(
&format!("Pumpkin {version}, {description} (Minecraft {CURRENT_MC_VERSION}, Protocol {CURRENT_MC_PROTOCOL})")
).color_named(NamedColor::Green)).await;
Ok(())
})
}
}
pub fn init_command_tree<'a>() -> CommandTree<'a> {
CommandTree::new(NAMES, DESCRIPTION).execute(&PumpkinExecutor {})
}

View File

@@ -1,16 +1,36 @@
use async_trait::async_trait;
use pumpkin_core::text::color::NamedColor;
use pumpkin_core::text::TextComponent;
use crate::commands::tree::CommandTree;
use crate::commands::tree_builder::require;
use super::RunFunctionType;
const NAMES: [&str; 1] = ["stop"];
const DESCRIPTION: &str = "Stop the server.";
struct StopExecutor {}
#[async_trait]
impl RunFunctionType for StopExecutor {
async fn execute(
&self,
sender: &mut super::CommandSender,
_server: &crate::server::Server,
_args: &super::tree::ConsumedArgs,
) -> Result<(), super::dispatcher::InvalidTreeError> {
sender
.send_message(TextComponent::text("Stopping Server").color_named(NamedColor::Red))
.await;
// TODO: Gracefully stop
std::process::exit(0)
}
}
pub fn init_command_tree<'a>() -> CommandTree<'a> {
CommandTree::new(NAMES, DESCRIPTION).with_child(
require(&|sender| sender.permission_lvl() >= 4).execute(&|_sender, _, _args| {
// sender
// .send_message(TextComponent::text("Stopping Server").color_named(NamedColor::Red));
std::process::exit(0)
}),
)
CommandTree::new(NAMES, DESCRIPTION)
.with_child(require(&|sender| sender.permission_lvl() >= 4).execute(&StopExecutor {}))
}

View File

@@ -26,8 +26,13 @@ pub struct CommandDispatcher<'a> {
/// Stores registered [`CommandTree`]s and dispatches commands to them.
impl<'a> CommandDispatcher<'a> {
pub async fn handle_command(&self, sender: &mut CommandSender<'a>, server: &Server, cmd: &str) {
if let Err(err) = self.dispatch(sender, server, cmd) {
pub async fn handle_command(
&'a self,
sender: &mut CommandSender<'a>,
server: &Server,
cmd: &'a str,
) {
if let Err(err) = self.dispatch(sender, server, cmd).await {
sender
.send_message(
TextComponent::text_string(err)
@@ -38,13 +43,14 @@ impl<'a> CommandDispatcher<'a> {
}
/// Execute a command using its corresponding [`CommandTree`].
pub(crate) fn dispatch(
pub(crate) async fn dispatch(
&'a self,
src: &mut CommandSender,
src: &mut CommandSender<'a>,
server: &Server,
cmd: &str,
cmd: &'a str,
) -> Result<(), String> {
let mut parts = cmd.split_ascii_whitespace();
// Other languages dont use the ascii whitespace
let mut parts = cmd.split_whitespace();
let key = parts.next().ok_or("Empty Command")?;
let raw_args: Vec<&str> = parts.rev().collect();
@@ -52,7 +58,7 @@ impl<'a> CommandDispatcher<'a> {
// try paths until fitting path is found
for path in tree.iter_paths() {
match Self::try_is_fitting_path(src, server, &path, tree, raw_args.clone()) {
match Self::try_is_fitting_path(src, server, &path, tree, raw_args.clone()).await {
Err(InvalidConsumptionError(s)) => {
println!("Error while parsing command \"{cmd}\": {s:?} was consumed, but couldn't be parsed");
return Err("Internal Error (See logs for details)".into());
@@ -90,20 +96,20 @@ impl<'a> CommandDispatcher<'a> {
}
}
fn try_is_fitting_path(
src: &mut CommandSender,
async fn try_is_fitting_path(
src: &mut CommandSender<'a>,
server: &Server,
path: &[usize],
tree: &CommandTree,
mut raw_args: RawArgs,
tree: &CommandTree<'a>,
mut raw_args: RawArgs<'a>,
) -> Result<Result<(), Option<String>>, InvalidTreeError> {
let mut parsed_args: ConsumedArgs = HashMap::new();
for node in path.iter().map(|&i| &tree.nodes[i]) {
match node.node_type {
NodeType::ExecuteLeaf { run } => {
NodeType::ExecuteLeaf { executor } => {
return if raw_args.is_empty() {
run(src, server, &parsed_args)?;
executor.execute(src, server, &parsed_args).await?;
Ok(Ok(()))
} else {
Ok(Err(None))

View File

@@ -1,5 +1,6 @@
use std::sync::Arc;
use async_trait::async_trait;
use dispatcher::InvalidTreeError;
use pumpkin_core::text::TextComponent;
use tree::ConsumedArgs;
@@ -60,7 +61,7 @@ impl<'a> CommandSender<'a> {
}
#[must_use]
pub fn default_dispatcher<'a>() -> CommandDispatcher<'a> {
pub fn default_dispatcher<'a>() -> Arc<CommandDispatcher<'a>> {
let mut dispatcher = CommandDispatcher::default();
dispatcher.register(cmd_pumpkin::init_command_tree());
@@ -71,8 +72,15 @@ pub fn default_dispatcher<'a>() -> CommandDispatcher<'a> {
dispatcher.register(cmd_kill::init_command_tree());
dispatcher.register(cmd_kick::init_command_tree());
dispatcher
Arc::new(dispatcher)
}
type RunFunctionType =
(dyn Fn(&mut CommandSender, &Server, &ConsumedArgs) -> Result<(), InvalidTreeError> + Sync);
#[async_trait]
pub(crate) trait RunFunctionType: Sync {
async fn execute(
&self,
sender: &mut CommandSender,
server: &Server,
args: &ConsumedArgs,
) -> Result<(), InvalidTreeError>;
}

View File

@@ -20,7 +20,7 @@ pub struct Node<'a> {
pub enum NodeType<'a> {
ExecuteLeaf {
run: &'a RunFunctionType,
executor: &'a dyn RunFunctionType,
},
Literal {
string: &'a str,
@@ -39,7 +39,6 @@ pub enum Command<'a> {
Alias(&'a str),
}
#[expect(unused)]
pub struct CommandTree<'a> {
pub(crate) nodes: Vec<Node<'a>>,
pub(crate) children: Vec<usize>,

View File

@@ -40,9 +40,9 @@ impl<'a> CommandTree<'a> {
/// desired type.
///
/// Also see [`NonLeafNodeBuilder::execute`].
pub fn execute(mut self, run: &'a RunFunctionType) -> Self {
pub fn execute(mut self, executor: &'a dyn RunFunctionType) -> Self {
let node = Node {
node_type: NodeType::ExecuteLeaf { run },
node_type: NodeType::ExecuteLeaf { executor },
children: Vec::new(),
};
@@ -113,9 +113,9 @@ impl<'a> NonLeafNodeBuilder<'a> {
/// desired type.
///
/// Also see [`CommandTree::execute`].
pub fn execute(mut self, run: &'a RunFunctionType) -> Self {
pub fn execute(mut self, executor: &'a dyn RunFunctionType) -> Self {
self.leaf_nodes.push(LeafNodeBuilder {
node_type: NodeType::ExecuteLeaf { run },
node_type: NodeType::ExecuteLeaf { executor },
});
self

View File

@@ -84,7 +84,7 @@ impl Server {
// 0 is invalid
entity_id: 2.into(),
worlds: vec![Arc::new(world)],
command_dispatcher: Arc::new(command_dispatcher),
command_dispatcher,
auth_client,
key_store: KeyStore::new(),
server_listing: Mutex::new(CachedStatus::new()),