From 0e9b1af76004cccbfce5cb8fd5c40c3d830a01e9 Mon Sep 17 00:00:00 2001 From: kralverde <80051564+kralverde@users.noreply.github.com> Date: Wed, 25 Dec 2024 08:43:26 -0500 Subject: [PATCH] make command dispatcher writable (#405) --- pumpkin/src/command/args/arg_command.rs | 10 ++++---- pumpkin/src/command/args/mod.rs | 2 +- pumpkin/src/command/client_cmd_suggestions.rs | 8 ++++--- pumpkin/src/command/commands/cmd_help.rs | 4 ++-- pumpkin/src/command/dispatcher.rs | 24 ++++++++++++++----- pumpkin/src/command/mod.rs | 4 ++-- pumpkin/src/command/tree.rs | 5 ++-- pumpkin/src/main.rs | 2 +- pumpkin/src/net/packet/play.rs | 8 +++---- pumpkin/src/net/rcon/mod.rs | 2 +- pumpkin/src/server/mod.rs | 4 ++-- pumpkin/src/world/mod.rs | 4 +--- 12 files changed, 44 insertions(+), 33 deletions(-) diff --git a/pumpkin/src/command/args/arg_command.rs b/pumpkin/src/command/args/arg_command.rs index 939559872..9055fde10 100644 --- a/pumpkin/src/command/args/arg_command.rs +++ b/pumpkin/src/command/args/arg_command.rs @@ -37,10 +37,10 @@ impl ArgumentConsumer for CommandTreeArgumentConsumer { ) -> Option> { let s = args.pop()?; - let dispatcher = &server.command_dispatcher; - return dispatcher + let dispatcher = server.command_dispatcher.read().await; + dispatcher .get_tree(s) - .map_or_else(|_| None, |tree| Some(Arg::CommandTree(tree))); + .map_or_else(|_| None, |tree| Some(Arg::CommandTree(tree))) } async fn suggest<'a>( @@ -53,8 +53,8 @@ impl ArgumentConsumer for CommandTreeArgumentConsumer { return Ok(None); }; - let suggestions = server - .command_dispatcher + let dispatcher = server.command_dispatcher.read().await; + let suggestions = dispatcher .commands .keys() .filter(|suggestion| suggestion.starts_with(input)) diff --git a/pumpkin/src/command/args/mod.rs b/pumpkin/src/command/args/mod.rs index f27d850a5..8342386bd 100644 --- a/pumpkin/src/command/args/mod.rs +++ b/pumpkin/src/command/args/mod.rs @@ -83,7 +83,7 @@ pub(crate) enum Arg<'a> { Pos2D(Vector2), Rotation(f32, f32), GameMode(GameMode), - CommandTree(&'a CommandTree<'a>), + CommandTree(CommandTree<'a>), Item(&'a str), ResourceLocation(&'a str), Block(&'a str), diff --git a/pumpkin/src/command/client_cmd_suggestions.rs b/pumpkin/src/command/client_cmd_suggestions.rs index a1bec5da8..7c8c7b2fb 100644 --- a/pumpkin/src/command/client_cmd_suggestions.rs +++ b/pumpkin/src/command/client_cmd_suggestions.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use pumpkin_protocol::client::play::{CCommands, ProtoNode, ProtoNodeType}; +use tokio::sync::RwLock; use crate::entity::player::Player; @@ -11,11 +12,12 @@ use super::{ pub async fn send_c_commands_packet<'a>( player: &Arc, - dispatcher: &'a CommandDispatcher<'a>, + dispatcher: &RwLock>, ) { let cmd_src = super::CommandSender::Player(player.clone()); let mut first_level = Vec::new(); + let dispatcher = dispatcher.read().await; for key in dispatcher.commands.keys() { let Ok(tree) = dispatcher.get_tree(key) else { continue; @@ -72,8 +74,8 @@ impl<'a> ProtoNodeBuilder<'a> { fn nodes_to_proto_node_builders<'a>( cmd_src: &super::CommandSender, - nodes: &'a [Node<'a>], - children: &'a [usize], + nodes: &[Node<'a>], + children: &[usize], ) -> (bool, Vec>) { let mut child_nodes = Vec::new(); let mut is_executable = false; diff --git a/pumpkin/src/command/commands/cmd_help.rs b/pumpkin/src/command/commands/cmd_help.rs index 8c714dcbc..e382febf1 100644 --- a/pumpkin/src/command/commands/cmd_help.rs +++ b/pumpkin/src/command/commands/cmd_help.rs @@ -121,8 +121,8 @@ impl CommandExecutor for BaseHelpExecutor { } }; - let mut commands: Vec<&CommandTree> = server - .command_dispatcher + let dispatcher = server.command_dispatcher.read().await; + let mut commands: Vec<&CommandTree> = dispatcher .commands .values() .filter_map(|cmd| match cmd { diff --git a/pumpkin/src/command/dispatcher.rs b/pumpkin/src/command/dispatcher.rs index e63a67ac2..b2dc27ae1 100644 --- a/pumpkin/src/command/dispatcher.rs +++ b/pumpkin/src/command/dispatcher.rs @@ -102,7 +102,7 @@ impl<'a> CommandDispatcher<'a> { // try paths and collect the nodes that fail // todo: make this more fine-grained for path in tree.iter_paths() { - match Self::try_find_suggestions_on_path(src, server, &path, tree, &mut raw_args, cmd) + match Self::try_find_suggestions_on_path(src, server, &path, &tree, &mut raw_args, cmd) .await { Err(InvalidConsumption(s)) => { @@ -151,7 +151,7 @@ impl<'a> CommandDispatcher<'a> { // try paths until fitting path is found for path in tree.iter_paths() { - if Self::try_is_fitting_path(src, server, &path, tree, &mut raw_args.clone()).await? { + if Self::try_is_fitting_path(src, server, &path, &tree, &mut raw_args.clone()).await? { return Ok(()); } } @@ -160,22 +160,22 @@ impl<'a> CommandDispatcher<'a> { ))) } - pub(crate) fn get_tree(&'a self, key: &str) -> Result<&'a CommandTree<'a>, CommandError> { + pub(crate) fn get_tree(&self, key: &str) -> Result, CommandError> { let command = self .commands .get(key) .ok_or(GeneralCommandIssue("Command not found".to_string()))?; match command { - Command::Tree(tree) => Ok(tree), + Command::Tree(tree) => Ok(tree.clone()), Command::Alias(target) => { - let Some(Command::Tree(tree)) = &self.commands.get(target) else { + let Some(Command::Tree(tree)) = self.commands.get(target) else { log::error!("Error while parsing command alias \"{key}\": pointing to \"{target}\" which is not a valid tree"); return Err(GeneralCommandIssue( "Internal Error (See logs for details)".into(), )); }; - Ok(tree) + Ok(tree.clone()) } } } @@ -282,3 +282,15 @@ impl<'a> CommandDispatcher<'a> { self.commands.insert(primary_name, Command::Tree(tree)); } } + +#[cfg(test)] +mod test { + use crate::command::{default_dispatcher, tree::CommandTree}; + + #[test] + fn test_dynamic_command() { + let mut dispatcher = default_dispatcher(); + let tree = CommandTree::new(["test"], "test_desc"); + dispatcher.register(tree); + } +} diff --git a/pumpkin/src/command/mod.rs b/pumpkin/src/command/mod.rs index c3f1b67d3..9ab330381 100644 --- a/pumpkin/src/command/mod.rs +++ b/pumpkin/src/command/mod.rs @@ -107,7 +107,7 @@ impl<'a> CommandSender<'a> { } #[must_use] -pub fn default_dispatcher<'a>() -> Arc> { +pub fn default_dispatcher<'a>() -> CommandDispatcher<'a> { let mut dispatcher = CommandDispatcher::default(); dispatcher.register(cmd_pumpkin::init_command_tree()); @@ -129,7 +129,7 @@ pub fn default_dispatcher<'a>() -> Arc> { dispatcher.register(cmd_transfer::init_command_tree()); dispatcher.register(cmd_fill::init_command_tree()); - Arc::new(dispatcher) + dispatcher } #[async_trait] diff --git a/pumpkin/src/command/tree.rs b/pumpkin/src/command/tree.rs index 06d9c0e58..9ea18e350 100644 --- a/pumpkin/src/command/tree.rs +++ b/pumpkin/src/command/tree.rs @@ -5,12 +5,13 @@ use std::{collections::VecDeque, fmt::Debug}; /// see [`crate::commands::tree_builder::argument`] pub type RawArgs<'a> = Vec<&'a str>; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Node<'a> { pub(crate) children: Vec, pub(crate) node_type: NodeType<'a>, } +#[derive(Clone)] pub enum NodeType<'a> { ExecuteLeaf { executor: &'a dyn CommandExecutor, @@ -50,7 +51,7 @@ pub enum Command<'a> { Alias(&'a str), } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct CommandTree<'a> { pub(crate) nodes: Vec>, pub(crate) children: Vec, diff --git a/pumpkin/src/main.rs b/pumpkin/src/main.rs index 232f30a16..5009e96f3 100644 --- a/pumpkin/src/main.rs +++ b/pumpkin/src/main.rs @@ -306,7 +306,7 @@ fn setup_console(server: Arc) { .expect("Failed to read console line"); if !out.is_empty() { - let dispatcher = server.command_dispatcher.clone(); + let dispatcher = server.command_dispatcher.read().await; dispatcher .handle_command(&mut command::CommandSender::Console, &server, &out) .await; diff --git a/pumpkin/src/net/packet/play.rs b/pumpkin/src/net/packet/play.rs index e30626781..b674f4dfb 100644 --- a/pumpkin/src/net/packet/play.rs +++ b/pumpkin/src/net/packet/play.rs @@ -286,7 +286,7 @@ impl Player { server: &Arc, command: SChatCommand, ) { - let dispatcher = server.command_dispatcher.clone(); + let dispatcher = server.command_dispatcher.read().await; dispatcher .handle_command( &mut CommandSender::Player(self.clone()), @@ -877,10 +877,8 @@ impl Player { return; }; - let suggestions = server - .command_dispatcher - .find_suggestions(&mut src, server, cmd) - .await; + let dispatcher = server.command_dispatcher.read().await; + let suggestions = dispatcher.find_suggestions(&mut src, server, cmd).await; let response = CCommandSuggestions::new( packet.id, diff --git a/pumpkin/src/net/rcon/mod.rs b/pumpkin/src/net/rcon/mod.rs index 96adbc968..1f36d827b 100644 --- a/pumpkin/src/net/rcon/mod.rs +++ b/pumpkin/src/net/rcon/mod.rs @@ -104,7 +104,7 @@ impl RCONClient { ServerboundPacket::ExecCommand => { if self.logged_in { let output = tokio::sync::Mutex::new(Vec::new()); - let dispatcher = server.command_dispatcher.clone(); + let dispatcher = server.command_dispatcher.read().await; dispatcher .handle_command( &mut crate::command::CommandSender::Rcon(&output), diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index c672eb2c8..cb5c57b13 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -47,7 +47,7 @@ pub struct Server { /// Saves server branding information. server_branding: CachedBranding, /// Saves and Dispatches commands to appropriate handlers. - pub command_dispatcher: Arc>, + pub command_dispatcher: RwLock>, /// Saves and calls blocks blocks pub block_manager: Arc, /// Manages multiple worlds within the server. @@ -79,7 +79,7 @@ impl Server { }); // First register default command, after that plugins can put in their own - let command_dispatcher = default_dispatcher(); + let command_dispatcher = RwLock::new(default_dispatcher()); let world = World::load( Dimension::OverWorld.into_level( diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index 02c13b10f..52d6b8c3c 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -229,7 +229,6 @@ impl World { player: Arc, server: &Server, ) { - let command_dispatcher = &server.command_dispatcher; let dimensions: Vec = server.dimensions.iter().map(DimensionType::name).collect(); @@ -270,8 +269,7 @@ impl World { .await; // permissions, i. e. the commands a player may use player.send_permission_lvl_update().await; - client_cmd_suggestions::send_c_commands_packet(&player, command_dispatcher).await; - + client_cmd_suggestions::send_c_commands_packet(&player, &server.command_dispatcher).await; // teleport let mut position = Vector3::new(10.0, 120.0, 10.0); let yaw = 10.0;