Commands Architecture & Dispatcher
Pumpkin features a high-performance, pure-Rust implementation of the Brigadier command tree specification through the dedicated pumpkin-command crate. It coordinates command parsing, requirement resolution, type-safe argument deserialization, execution branching, and dual-edition client tree synchronization for both Minecraft: Java Edition and Minecraft: Bedrock Edition.
Command Processing Pipeline
Every command executed in Pumpkin flows through the following discrete stages:
- Input Normalization: Strips single leading slashes while preserving plugin multi-slash conventions (such as
//set). - Availability Check: Rejects commands disabled via server configuration with standard
unknown commandsyntax errors. - Permission Evaluation: Validates requirements (
PermissionLvlor custom registry nodes) against the executing sender. - Tokenization & Parsing:
StringReaderrecursively walks theTree<S>, matching literal keywords and parsing typed argument parameters. - Context Dispatch: Invokes the matched
CommandExecutorcallback with a typedCommandContext<S>. - Feedback & Result Consumption: Formats cross-platform translation components, notifies observers, and writes exit codes back to the caller.
The pumpkin-command Crate
Pumpkin decouples command parsing and tree representation from the main server runtime into the modular pumpkin-command crate (crates/pumpkin-command).
Key components of pumpkin-command include:
Tree<S>: Contiguous vector-backed storage of attached command nodes (AttachedNode).CommandDispatcher<S>: The central registry and dispatch engine. It processes raw command strings, parses tokens withStringReader, traverses the command tree, and executes target callbacks.ArgumentBuilder: Fluent builder API (command,literal,argument) modeled after Mojang's Brigadier library.CommandSource: Generic execution state representing the current position, rotation, dimension, entity, and feedback output stream.
The Command Node Hierarchy
Commands in Pumpkin are modeled as directed graphs of nodes. To optimize memory allocations and avoid circular reference issues during building, the system separates nodes into detached and attached states:
1. Detached Nodes (DetachedNode)
Constructed using fluent builders (CommandArgumentBuilder, LiteralArgumentBuilder, RequiredArgumentBuilder) before registration:
CommandDetachedNode: Represents a root command entrypoint with an identifier and a description.LiteralDetachedNode: Represents a literal keyword in a subcommand branch (e.g.,setin/gamemode creative).ArgumentDetachedNode: Represents a typed parameter parsed by anArgumentType(e.g., coordinates, entity selectors, integer ranges).
2. Attached Nodes (AttachedNode)
Once registered into the CommandDispatcher, detached trees are flattened into an indexed Tree<S> structure:
pub enum AttachedNode<S: CommandSource = DummySource> {
Root(RootAttachedNode<S>),
Literal(LiteralAttachedNode<S>),
Argument(ArgumentAttachedNode<S>),
Command(CommandAttachedNode<S>),
}Each attached node has:
NodeId: A zero-cost typed index (NonZero<usize>) into the contiguous node arena.children: Fast lookup map (FxHashMap<String, NodeId>) for literals and child arguments.requirements: Predicates determining whether a sender has permission to view or execute the node.command: OptionalCommandExecutorcallback invoked when parsing terminates on this node.redirect: OptionalRedirectionpointer to loop back or redirect to another sub-tree (used heavily by/execute).
Dispatch & Execution Pipeline
When a command arrives at the server, CommandDispatcher::handle_command processes it through several stages:
pub fn handle_command<'a>(&'a self, source: &S, mut input: &'a str) {
// 1. Strip single leading slash (preserving double-slash commands like //set)
if let Some(sliced) = input.strip_prefix('/') {
let first_token = input.split_whitespace().next().unwrap_or("");
let sliced_token = sliced.split_whitespace().next().unwrap_or("");
if !self.has_command(first_token)
&& (self.has_command(sliced_token) || !first_token.starts_with("//"))
{
input = sliced;
}
}
// 2. Check disabled commands from configuration
if self.is_disabled(Self::command_name(input)) {
let reader = StringReader::new(input);
Self::send_error_to_source(source, DISPATCHER_UNKNOWN_COMMAND.create(&reader), input);
return;
}
// 3. Parse and execute
let output = self.execute_input(input, source);
if let Err(error) = output {
Self::send_error_to_source(source, error, input);
}
}Syntax Error Formatting
When parsing encounters invalid syntax, unexpected tokens, or out-of-range arguments, Pumpkin constructs a CommandSyntaxError. If context is available, the dispatcher formats a visual pointer highlighting exactly where the parser failed:
Unknown or incomplete command, see below for error
/gamemode creatve<--[HERE]In the Java Edition client, this error text is annotated with a ClickEvent::SuggestCommand, allowing the player to click the error in chat to copy the failed command into their input box for quick editing.
Dual-Edition Network Synchronization
Unlike vanilla servers which only serialize command graphs for Java Edition, Pumpkin dynamically synchronizes the registered command tree across both Java and Bedrock clients:
1. Java Edition: CCommands Packet
When a player joins or their permission level updates (e.g., via /op or /deop), Pumpkin builds and sends a CCommands packet (pumpkin_protocol::java::client::play::CCommands):
- Converts
AttachedNodes into a flat array ofProtoNodeentries. - Filters out disabled commands and nodes where
requirements.evaluate(source)returnsfalse. - Serializes argument parsers (
brigadier:string,brigadier:integer,minecraft:entity, etc.) and client-side suggestion identifiers.
2. Bedrock Edition: CAvailableCommands Packet
Bedrock Edition uses an overload-and-enum format rather than Brigadier's recursive graph. In client_suggestions.rs, Pumpkin traverses the AttachedNode tree to synthesize:
CommandData: Root command name, description, permission flags, and aliases.OverloadData: Flattened permutations of argument chains leading to executable endpoints.ParamData: Bedrock parameter types, bitflags (ARG_FLAG_VALID,ARG_FLAG_ENUM), and dynamically collected enum value tables.
Universal Execution Targets
Pumpkin commands can be executed across diverse senders:
| Sender | Source Representation | Output Destination | Default Permission |
|---|---|---|---|
| Player | CommandSender::Player(Arc<Player>) | Client system chat packet | Loaded from permission level / permissions file |
| Server Console | CommandSender::Console | Standard stdout (tracing::info!) | Level 4 (Full administrative bypass) |
| RCON Console | CommandSender::Rcon(Arc<Mutex<Vec<String>>>) | Network response packet payload | Level 4 |
| Command Block | CommandSender::CommandBlock(Arc<CommandBlockEntity>, Arc<World>) | NBT LastOutput field | Level 2 |
| Datapack Function | CommandSender inherited from caller | Inherited | Inherited |
| WASM Plugin | Invoked through WIT execute_command host call | Captured via ResultDeferrer | Configurable |
Command Configuration & Disabling
Pumpkin allows server administrators to disable individual commands via config/commands.toml (CommandsConfig). When a command is disabled:
- It is omitted from the
CCommandspacket for Java clients. - It is omitted from the
CAvailableCommandspacket for Bedrock clients. - Tab completion ignores the command entirely.
- Any attempt to invoke the command returns
DISPATCHER_UNKNOWN_COMMAND.