chore: switch command_dispatcher to ArcSwap

i need this for patchbukkit, but this is also safer in terms of deadlocks
This commit is contained in:
Alexander Medvedev
2026-08-19 18:57:31 +02:00
parent 399bf14150
commit 489403c58b
19 changed files with 40 additions and 41 deletions

View File

@@ -141,8 +141,7 @@ impl CommandBlock {
server
.command_dispatcher
.read()
.await
.load()
.handle_command(&source, command)
.await;
}

View File

@@ -40,7 +40,7 @@ impl ArgumentConsumer for CommandTreeArgumentConsumer {
};
Box::pin(async move {
let dispatcher = server.command_dispatcher.read().await;
let dispatcher = server.command_dispatcher.load();
dispatcher
.fallback_dispatcher
@@ -61,7 +61,7 @@ impl ArgumentConsumer for CommandTreeArgumentConsumer {
return Ok(None);
};
let dispatcher = server.command_dispatcher.read().await;
let dispatcher = server.command_dispatcher.load();
let suggestions = dispatcher
.fallback_dispatcher
.commands

View File

@@ -41,7 +41,7 @@ impl CommandExecutor for Executor {
succeeded_deops += 1;
if let Some(player) = server.get_player_by_uuid(profile.id) {
let command_dispatcher = server.command_dispatcher.read().await;
let command_dispatcher = server.command_dispatcher.load();
player
.set_permission_lvl(
server,

View File

@@ -116,7 +116,7 @@ impl HelpCommandExecutor {
Box::pin(async move {
let server = context.server();
let dispatcher = server.command_dispatcher.read().await;
let dispatcher = server.command_dispatcher.load();
let commands = dispatcher
.get_all_permitted_commands_usage(&context.source)
.await;
@@ -206,7 +206,7 @@ impl HelpCommandExecutor {
fn command<'a>(context: &'a CommandContext, command: &'a str) -> CommandExecutorResult<'a> {
Box::pin(async move {
let dispatcher = context.server().command_dispatcher.read().await;
let dispatcher = context.server().command_dispatcher.load();
let Some((description, usage)) = dispatcher
.get_permitted_command_usage(&context.source, command)
@@ -274,7 +274,7 @@ impl HelpCommandExecutor {
fn plugin<'a>(context: &'a CommandContext, plugin_name: &'a str) -> CommandExecutorResult<'a> {
Box::pin(async move {
let server = context.server();
let dispatcher = server.command_dispatcher.read().await;
let dispatcher = server.command_dispatcher.load();
let commands = dispatcher
.get_all_permitted_commands_usage_by_plugin(&context.source, plugin_name)
.await;
@@ -335,7 +335,7 @@ impl HelpCommandExecutor {
Box::pin(async move {
// Prioritize commands ig
{
let dispatcher = context.server().command_dispatcher.read().await;
let dispatcher = context.server().command_dispatcher.load();
if dispatcher
.get_permitted_command_usage(&context.source, input)
.await
@@ -346,7 +346,7 @@ impl HelpCommandExecutor {
}
{
let dispatcher = context.server().command_dispatcher.read().await;
let dispatcher = context.server().command_dispatcher.load();
let plugin_commands = dispatcher
.get_all_permitted_commands_usage_by_plugin(&context.source, input)
.await;

View File

@@ -50,7 +50,7 @@ impl CommandExecutor for OpCommandExecutor {
}
if let Some(player) = server.get_player_by_uuid(profile.id) {
let command_dispatcher = server.command_dispatcher.read().await;
let command_dispatcher = server.command_dispatcher.load();
player
.set_permission_lvl(server, new_level, &command_dispatcher)
.await;

View File

@@ -228,7 +228,7 @@ fn path_failure(
}
}
#[derive(Default)]
#[derive(Default, Clone)]
pub struct CommandDispatcher {
pub commands: FxHashMap<String, Command>,
pub permissions: FxHashMap<String, String>,

View File

@@ -95,6 +95,7 @@ pub static RESULT_DEFERRER: LazyLock<Arc<ResultDeferrer>> =
///
/// Internally, this dispatcher stores a [`Tree`]. Refer to its documentation
/// for more information about nodes.
#[derive(Clone)]
pub struct CommandDispatcher {
pub tree: Tree,
pub consumer: Arc<dyn ResultConsumer>,

View File

@@ -67,6 +67,7 @@ impl Debug for NodeType {
}
}
#[derive(Clone, Debug)]
pub enum Command {
Tree(CommandTree),
Alias(String),

View File

@@ -722,8 +722,7 @@ fn setup_stdin_console(server: Arc<Server>) {
if !event.cancelled {
server
.command_dispatcher
.read()
.await
.load()
.handle_command(
&command::CommandSender::Console.into_source(&server).await,
command.as_str(),
@@ -795,8 +794,7 @@ fn setup_console(mut rl: Editor<PumpkinCommandCompleter, FileHistory>, server: A
if !event.cancelled {
server
.command_dispatcher
.read()
.await
.load()
.handle_command(
&command::CommandSender::Console.into_source(&server).await,
&line,

View File

@@ -476,7 +476,7 @@ impl Completer for PumpkinCommandCompleter {
let ends_with_space = cmd.ends_with(' ');
handle.block_on(async {
let dispatcher = server.command_dispatcher.read().await;
let dispatcher = server.command_dispatcher.load();
let source = CommandSender::Console.into_source(server).await;
// Temporary setups to unify both dispatchers for now:

View File

@@ -31,7 +31,7 @@ impl BedrockClient {
// Some commands can take a long time to execute. If they do, they block packet processing for the player.
// That's why we will spawn a task instead.
server.spawn_task(async move {
let dispatcher = server_clone.command_dispatcher.read().await;
let dispatcher = server_clone.command_dispatcher.load();
dispatcher.handle_command(
&player_clone.get_command_source(&server_clone).await,
&command_clone

View File

@@ -28,7 +28,7 @@ impl JavaClient {
// Some commands can take a long time to execute. If they do, they block packet processing for the player.
// That's why we will spawn a task instead.
server.spawn_task(async move {
let dispatcher = server_clone.command_dispatcher.read().await;
let dispatcher = server_clone.command_dispatcher.load();
dispatcher.handle_command(
&player_clone.get_command_source(&server_clone).await,
&command_clone

View File

@@ -19,8 +19,7 @@ impl JavaClient {
let suggestions = server
.command_dispatcher
.read()
.await
.load()
.suggest(cmd, &player.get_command_source(server).await)
.await;

View File

@@ -146,8 +146,7 @@ impl RCONClient {
let _ = tokio::spawn(async move {
server_clone
.command_dispatcher
.read()
.await
.load()
.handle_command(&command_source, &packet_body)
.await;
})

View File

@@ -175,12 +175,13 @@ impl Context {
format!("{}:{permission}", self.metadata.name)
};
{
let mut dispatcher_lock = self.server.command_dispatcher.write().await;
dispatcher_lock
self.server.command_dispatcher.rcu(|dispatcher| {
let mut new_dispatcher = (**dispatcher).clone();
new_dispatcher
.fallback_dispatcher
.register(tree, full_permission_node);
};
.register(tree.clone(), full_permission_node.clone());
Arc::new(new_dispatcher)
});
self.reload_commands_for_everyone().await;
}
@@ -190,10 +191,11 @@ impl Context {
/// # Arguments
/// - `name`: The name of the command to unregister.
pub async fn unregister_command(&self, name: &str) {
{
let mut dispatcher_lock = self.server.command_dispatcher.write().await;
dispatcher_lock.fallback_dispatcher.unregister(name);
};
self.server.command_dispatcher.rcu(|dispatcher| {
let mut new_dispatcher = (**dispatcher).clone();
new_dispatcher.fallback_dispatcher.unregister(name);
Arc::new(new_dispatcher)
});
self.reload_commands_for_everyone().await;
}
@@ -212,7 +214,7 @@ impl Context {
/// # Arguments
/// - `player`: The player for which the commands will be reloaded.
pub async fn reload_commands_for(&self, player: &Arc<Player>) {
let command_dispatcher = self.server.command_dispatcher.read().await;
let command_dispatcher = self.server.command_dispatcher.load();
if let ClientPlatform::Bedrock(_) = player.client.as_ref() {
client_suggestions::send_bedrock_commands_packet(
player,

View File

@@ -1349,7 +1349,7 @@ impl pumpkin::plugin::player::HostPlayer for PluginHostState {
let player = player_from_resource(self, &player)?;
let server = self.server.as_ref().expect("server not available");
let level = from_wit_permission_level(level);
let command_dispatcher = server.command_dispatcher.read().await;
let command_dispatcher = server.command_dispatcher.load();
player
.set_permission_lvl(server, level, &command_dispatcher)
.await;

View File

@@ -348,7 +348,7 @@ impl pumpkin::plugin::server::HostServer for PluginHostState {
}
};
let dispatcher = server.command_dispatcher.read().await;
let dispatcher = server.command_dispatcher.load();
dispatcher
.handle_command(&native_sender.into_source(server).await, &command)
.await;
@@ -700,7 +700,7 @@ impl pumpkin::plugin::server::HostOpManager for PluginHostState {
drop(config);
if let Some(player) = server.get_player_by_uuid(uuid) {
let command_dispatcher = server.command_dispatcher.read().await;
let command_dispatcher = server.command_dispatcher.load();
player
.set_permission_lvl(server, internal_level, &command_dispatcher)
.await;
@@ -727,7 +727,7 @@ impl pumpkin::plugin::server::HostOpManager for PluginHostState {
drop(config);
if let Some(player) = server.get_player_by_uuid(uuid) {
let command_dispatcher = server.command_dispatcher.read().await;
let command_dispatcher = server.command_dispatcher.load();
player
.set_permission_lvl(
server,

View File

@@ -94,7 +94,7 @@ pub struct Server {
/// Saves server branding information.
branding: CachedBranding,
/// Saves and dispatches commands to appropriate handlers.
pub command_dispatcher: RwLock<CommandDispatcher>,
pub command_dispatcher: ArcSwap<CommandDispatcher>,
/// Block behaviour.
pub block_registry: Arc<BlockRegistry>,
/// Item behaviour.
@@ -156,7 +156,7 @@ impl Server {
) -> Arc<Self> {
let permission_registry = Arc::new(RwLock::new(PermissionRegistry::new()));
// First register the default commands. After that, plugins can put in their own.
let command_dispatcher = RwLock::new(
let command_dispatcher = ArcSwap::from_pointee(
default_dispatcher(
&permission_registry,
&basic_config,

View File

@@ -2714,7 +2714,7 @@ impl World {
player.send_abilities_update().await;
{
let command_dispatcher = server.command_dispatcher.read().await;
let command_dispatcher = server.command_dispatcher.load();
client_suggestions::send_bedrock_commands_packet(&player, server, &command_dispatcher)
.await;
};
@@ -3092,7 +3092,7 @@ impl World {
// Difficulty of the world
player.send_difficulty_update().await;
{
let command_dispatcher = server.command_dispatcher.read().await;
let command_dispatcher = server.command_dispatcher.load();
client_suggestions::send_c_commands_packet(player, server, &command_dispatcher).await;
};