chunkedge_command/graph.rs
1//! # Command graph implementation
2//!
3//! This is the core of the command system. It is a graph of `CommandNode`s that
4//! are connected by the `CommandEdgeType`. The graph is used to determine what
5//! command to run when a command is entered. The graph is also used to generate
6//! the command tree that is sent to the client.
7//!
8//! ### The graph is a directed graph with 3 types of nodes:
9//! * Root node ([`NodeData::Root`]) - This is the root of the graph. It is
10//! used to connect all the other nodes to the graph. It is always present and
11//! there should only be one.
12//! * Literal node ([`NodeData::Literal`]) - This is a literal part of a
13//! command. It is a string that must be matched exactly by the client to
14//! trigger the validity of the node. For example, the command `/teleport`
15//! would have a literal node with the name `teleport` which is a child of the
16//! root node.
17//! * Argument node ([`NodeData::Argument`]) - This is a node that represents an
18//! argument in a command. It is a string that is matched by the client and
19//! checked by the server. For example, the command `/teleport 0 0 0` would
20//! have 1 argument node with the name "<destination:location>" and the parser
21//! [`Parser::Vec3`] which is a child of the literal node with the name
22//! `teleport`.
23//!
24//! #### and 2 types of edges:
25//! * Child edge ([`CommandEdgeType::Child`]) - This is an edge that connects a
26//! parent node to a child node. It is used to determine what nodes are valid
27//! children of a parent node. for example, the literal node with the name
28//! `teleport` would have a child edge to the argument node with the name
29//! "<destination:location>". This means that the argument node is a valid
30//! child of the literal node.
31//! * Redirect edge ([`CommandEdgeType::Redirect`]) - This edge is special. It
32//! is used to redirect the client to another node. For example, the literal
33//! node with the name `tp` would have a Redirect edge to the literal node
34//! with the name `teleport`. This means that if the client enters the command
35//! `/tp` the server will redirect the client to the literal node with the
36//! name `teleport`. Making the command `/tp` functionally equivalent to
37//! `/teleport`.
38//!
39//! # Cool Example Graph For Possible Implementation Of Teleport Command (made with graphviz)
40//! ```text
41//! ┌────────────────────────────────┐
42//! │ Root │ ─┐
43//! └────────────────────────────────┘ │
44//! │ │
45//! │ Child │
46//! ▼ │
47//! ┌────────────────────────────────┐ │
48//! │ Literal: tp │ │
49//! └────────────────────────────────┘ │
50//! │ │
51//! │ Redirect │ Child
52//! ▼ ▼
53//! ┌──────────────────────────────────┐ Child ┌──────────────────────────────────────────────────────────────────────────────┐
54//! │ Argument: <destination:entity> │ ◀─────── │ Literal: teleport │
55//! └──────────────────────────────────┘ └──────────────────────────────────────────────────────────────────────────────┘
56//! │ │
57//! │ Child │ Child
58//! ▼ ▼
59//! ┌──────────────────────────────────┐ Child ┌────────────────────────────────┐ ┌──────────────────────────────────┐
60//! │ Argument: <destination:location> │ ◀─────── │ Argument: <target:entity> │ │ Argument: <destination:location> │
61//! └──────────────────────────────────┘ └────────────────────────────────┘ └──────────────────────────────────┘
62//! │
63//! │ Child
64//! ▼
65//! ┌────────────────────────────────┐
66//! │ Argument: <destination:entity> │
67//! └────────────────────────────────┘
68//! ```
69//! If you want a cool graph of your own command graph you can use the display
70//! trait on the [`CommandGraph`] struct. Then you can use a tool like
71//! [Graphviz Online](https://dreampuf.github.io/GraphvizOnline) to look at the graph.
72
73use std::collections::HashMap;
74use std::fmt::{Display, Formatter};
75
76use chunkedge_server::protocol::packets::play::commands_s2c::{Node, NodeData, Parser, StringArg};
77use chunkedge_server::protocol::packets::play::CommandsS2c;
78use chunkedge_server::protocol::VarInt;
79use petgraph::dot::Dot;
80use petgraph::prelude::*;
81
82use crate::modifier_value::ModifierValue;
83use crate::parsers::{CommandArg, ParseInput};
84use crate::{CommandRegistry, CommandScopeRegistry};
85
86/// This struct is used to store the command graph. (see module level docs for
87/// more info)
88#[derive(Debug, Clone)]
89pub struct CommandGraph<'a> {
90 pub graph: Graph<CommandNode<'a>, CommandEdgeType>,
91 pub root: NodeIndex,
92}
93
94impl Default for CommandGraph<'_> {
95 fn default() -> Self {
96 Self::new()
97 }
98}
99
100/// Output the graph in graphviz dot format to do visual debugging. (this was
101/// used to make the cool graph in the module level docs)
102impl Display for CommandGraph<'_> {
103 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
104 write!(f, "{}", Dot::new(&self.graph))
105 }
106}
107
108impl CommandGraph<'_> {
109 pub fn new() -> Self {
110 let mut graph = Graph::<CommandNode, CommandEdgeType>::new();
111 let root = graph.add_node(CommandNode {
112 executable: false,
113 data: NodeData::Root,
114 scopes: vec![],
115 });
116
117 Self { graph, root }
118 }
119}
120
121/// Data for the nodes in the graph (see module level docs for more info)
122#[derive(Clone, Debug, PartialEq)]
123pub struct CommandNode<'a> {
124 pub executable: bool,
125 pub data: NodeData<'a>,
126 pub scopes: Vec<String>,
127}
128
129impl Display for CommandNode<'_> {
130 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
131 match &self.data {
132 NodeData::Root => write!(f, "Root"),
133 NodeData::Literal { name } => write!(f, "Literal: {name}"),
134 NodeData::Argument { name, .. } => write!(f, "Argument: <{name}>"),
135 }
136 }
137}
138
139#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
140pub enum CommandEdgeType {
141 Redirect,
142 Child,
143}
144
145impl Display for CommandEdgeType {
146 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
147 match self {
148 CommandEdgeType::Redirect => write!(f, "Redirect"),
149 CommandEdgeType::Child => write!(f, "Child"),
150 }
151 }
152}
153
154impl<'a> From<CommandGraph<'a>> for CommandsS2c<'a> {
155 fn from(command_graph: CommandGraph<'a>) -> Self {
156 let graph = command_graph.graph;
157 let nodes_and_edges = graph.into_nodes_edges();
158
159 let mut nodes: Vec<Node> = nodes_and_edges
160 .0
161 .into_iter()
162 .map(|node| Node::<'a> {
163 children: Vec::new(),
164 data: node.weight.data,
165 executable: node.weight.executable,
166 redirect_node: None,
167 is_restricted: false, // TODO:
168 })
169 .collect();
170
171 let edges = nodes_and_edges.1;
172
173 for edge in edges {
174 match edge.weight {
175 CommandEdgeType::Child => {
176 nodes[edge.source().index()]
177 .children
178 .push(VarInt::from(edge.target().index() as i32));
179 }
180 CommandEdgeType::Redirect => {
181 nodes[edge.source().index()].redirect_node =
182 Some(VarInt::from(edge.target().index() as i32));
183 }
184 }
185 }
186
187 CommandsS2c {
188 commands: nodes,
189 root_index: VarInt::from(command_graph.root.index() as i32),
190 }
191 }
192}
193
194/// Ergonomic builder pattern for adding executables, literals and arguments to
195/// a command graph. See the derive macro for a more ergonomic way of doing this
196/// for a basic command with an enum or struct.
197///
198/// # Type Parameters
199/// * `T` - the type that should be constructed by an executable when the
200/// command is executed
201///
202/// # Example
203/// ```
204/// use std::collections::HashMap;
205/// use petgraph::visit::{EdgeCount, NodeCount};
206/// use chunkedge_command::graph::{
207/// CommandGraph, CommandGraphBuilder
208/// };
209/// use chunkedge_command::{CommandRegistry};
210/// use chunkedge_command::parsers::CommandArg;
211///
212/// struct TestCommand {
213/// test: i32,
214/// }
215///
216/// let mut command_graph = CommandRegistry::default();
217/// let mut executable_map = HashMap::new();
218/// let mut parser_map = HashMap::new();
219/// let mut modifier_map = HashMap::new();
220/// let mut command_graph_builder = CommandGraphBuilder::<TestCommand>::new(&mut command_graph, &mut executable_map, &mut parser_map, &mut modifier_map);
221///
222/// // simple command
223/// let simple_command = command_graph_builder
224/// .root() // transition to the root node
225/// .literal("test") // add a literal node then transition to it
226/// .argument("test")
227/// // a player needs one of these scopes to execute the command
228/// // (note: if you want an admin scope you should use the link method on the scope registry.)
229/// .with_scopes(vec!["test:admin", "command:test"])
230/// .with_parser::<i32>()
231/// // it is reasonably safe to unwrap here because we know that the argument is an integer
232/// .with_executable(|args| TestCommand { test: i32::parse_arg(args).unwrap() })
233/// .id();
234///
235/// // complex command (redirects back to the simple command)
236/// command_graph_builder
237/// .root()
238/// .literal("test")
239/// .literal("command")
240/// .redirect_to(simple_command);
241///
242/// assert_eq!(command_graph.graph.graph.node_count(), 5); // root, test, command, <test>, test
243/// // 5 edges, 2 for the simple command, 2 for the complex command and 1 for the redirect
244/// assert_eq!(command_graph.graph.graph.edge_count(), 5);
245/// ```
246///
247/// in this example we can execute either of the following commands for the same
248/// result:
249/// - `/test test 1`
250/// - `/test command test 1`
251///
252/// the executables from these commands will both return a `TestCommand` with
253/// the value `1`
254#[allow(clippy::type_complexity)]
255pub struct CommandGraphBuilder<'a, T> {
256 registry: &'a mut CommandRegistry,
257 current_node: NodeIndex,
258 executables: &'a mut HashMap<NodeIndex, fn(&mut ParseInput) -> T>,
259 parsers: &'a mut HashMap<NodeIndex, fn(&mut ParseInput) -> bool>,
260 modifiers: &'a mut HashMap<NodeIndex, fn(String, &mut HashMap<ModifierValue, ModifierValue>)>,
261 scopes_added: Vec<String>, /* we need to keep track of added scopes so we can add them to
262 * the registry later */
263}
264
265impl<'a, T> CommandGraphBuilder<'a, T> {
266 /// Creates a new command graph builder
267 ///
268 /// # Arguments
269 /// * registry - the command registry to add the commands to
270 /// * executables - the map of node indices to executable parser functions
271 #[allow(clippy::type_complexity)]
272 pub fn new(
273 registry: &'a mut CommandRegistry,
274 executables: &'a mut HashMap<NodeIndex, fn(&mut ParseInput) -> T>,
275 parsers: &'a mut HashMap<NodeIndex, fn(&mut ParseInput) -> bool>,
276 modifiers: &'a mut HashMap<
277 NodeIndex,
278 fn(String, &mut HashMap<ModifierValue, ModifierValue>),
279 >,
280 ) -> Self {
281 CommandGraphBuilder::<'a> {
282 current_node: registry.graph.root,
283 registry,
284 executables,
285 parsers,
286 modifiers,
287 scopes_added: Vec::new(),
288 }
289 }
290
291 /// Transitions to the root node. Use this to start building a new command
292 /// from root.
293 pub fn root(&mut self) -> &mut Self {
294 self.current_node = self.registry.graph.root;
295 self
296 }
297
298 /// Creates a new literal node and transitions to it.
299 ///
300 /// # Default Values
301 /// * executable - `false`
302 /// * scopes - `Vec::new()`
303 pub fn literal<S: Into<String>>(&mut self, literal: S) -> &mut Self {
304 let graph = &mut self.registry.graph.graph;
305 let current_node = &mut self.current_node;
306
307 let literal_node = graph.add_node(CommandNode {
308 executable: false,
309 data: NodeData::Literal {
310 name: literal.into().into(),
311 },
312 scopes: Vec::new(),
313 });
314
315 graph.add_edge(*current_node, literal_node, CommandEdgeType::Child);
316
317 *current_node = literal_node;
318
319 self
320 }
321
322 /// Creates a new argument node and transitions to it.
323 ///
324 /// # Default Values
325 /// * executable - `false`
326 /// * scopes - `Vec::new()`
327 /// * parser - `StringArg::SingleWord`
328 /// * suggestion - `None`
329 pub fn argument<A: Into<String>>(&mut self, argument: A) -> &mut Self {
330 let graph = &mut self.registry.graph.graph;
331 let current_node = &mut self.current_node;
332
333 let argument_node = graph.add_node(CommandNode {
334 executable: false,
335 data: NodeData::Argument {
336 name: argument.into().into(),
337 parser: Parser::String(StringArg::SingleWord),
338 suggestion: None,
339 },
340 scopes: Vec::new(),
341 });
342
343 graph.add_edge(*current_node, argument_node, CommandEdgeType::Child);
344
345 *current_node = argument_node;
346
347 self
348 }
349
350 /// Creates a new redirect edge from the current node to the node specified.
351 /// For info on what a redirect edge is, see the module level documentation.
352 ///
353 /// # Example
354 /// ```
355 /// use std::collections::HashMap;
356 ///
357 /// use chunkedge_command::graph::CommandGraphBuilder;
358 /// use chunkedge_command::CommandRegistry;
359 ///
360 /// struct TestCommand;
361 ///
362 /// let mut command_graph = CommandRegistry::default();
363 /// let mut executable_map = HashMap::new();
364 /// let mut parser_map = HashMap::new();
365 /// let mut modifier_map = HashMap::new();
366 /// let mut command_graph_builder = CommandGraphBuilder::<TestCommand>::new(
367 /// &mut command_graph,
368 /// &mut executable_map,
369 /// &mut parser_map,
370 /// &mut modifier_map,
371 /// );
372 ///
373 /// let simple_command = command_graph_builder
374 /// .root() // transition to the root node
375 /// .literal("test") // add a literal node then transition to it
376 /// .id(); // get the id of the literal node
377 ///
378 /// command_graph_builder
379 /// .root() // transition to the root node
380 /// .literal("test") // add a literal node then transition to it
381 /// .literal("command") // add a literal node then transition to it
382 /// .redirect_to(simple_command); // redirect to the simple command
383 /// ```
384 pub fn redirect_to(&mut self, node: NodeIndex) -> &mut Self {
385 let graph = &mut self.registry.graph.graph;
386 let current_node = &mut self.current_node;
387
388 graph.add_edge(*current_node, node, CommandEdgeType::Redirect);
389
390 *current_node = node;
391
392 self
393 }
394
395 /// Sets up the executable function for the current node. This function will
396 /// be called when the command is executed and should parse the args and
397 /// return the `T` type.
398 ///
399 /// # Arguments
400 /// * executable - the executable function to add
401 ///
402 /// # Example
403 /// have a look at the example for [`CommandGraphBuilder`]
404 pub fn with_executable(&mut self, executable: fn(&mut ParseInput) -> T) -> &mut Self {
405 let graph = &mut self.registry.graph.graph;
406 let current_node = &mut self.current_node;
407
408 let node = graph.node_weight_mut(*current_node).unwrap();
409
410 node.executable = true;
411 self.executables.insert(*current_node, executable);
412
413 self
414 }
415
416 /// Adds a modifier to the current node
417 ///
418 /// # Arguments
419 /// * modifier - the modifier function to add
420 ///
421 /// # Example
422 /// ```
423 /// use std::collections::HashMap;
424 ///
425 /// use chunkedge_command::graph::CommandGraphBuilder;
426 /// use chunkedge_command::CommandRegistry;
427 ///
428 /// struct TestCommand;
429 ///
430 /// let mut command_graph = CommandRegistry::default();
431 /// let mut executable_map = HashMap::new();
432 /// let mut parser_map = HashMap::new();
433 /// let mut modifier_map = HashMap::new();
434 /// let mut command_graph_builder =
435 /// CommandGraphBuilder::<TestCommand>::new(&mut command_graph, &mut executable_map, &mut parser_map, &mut modifier_map);
436 ///
437 /// command_graph_builder
438 /// .root() // transition to the root node
439 /// .literal("test") // add a literal node then transition to it
440 /// .with_modifier(|_, modifiers| {
441 /// modifiers.insert("test".into(), "test".into()); // this will trigger when the node is passed
442 /// })
443 /// .literal("command") // add a literal node then transition to it
444 /// .with_executable(|_| TestCommand);
445 /// ```
446 pub fn with_modifier(
447 &mut self,
448 modifier: fn(String, &mut HashMap<ModifierValue, ModifierValue>),
449 ) -> &mut Self {
450 let current_node = &mut self.current_node;
451
452 self.modifiers.insert(*current_node, modifier);
453
454 self
455 }
456
457 /// Sets the required scopes for the current node
458 ///
459 /// # Arguments
460 /// * scopes - a list of scopes for that are aloud to access a command node
461 /// and its children (list of strings following the system described in
462 /// [`scopes`](crate::scopes))
463 pub fn with_scopes<S: Into<String>>(&mut self, scopes: Vec<S>) -> &mut Self {
464 let graph = &mut self.registry.graph.graph;
465 let current_node = &mut self.current_node;
466
467 let node = graph.node_weight_mut(*current_node).unwrap();
468
469 node.scopes = scopes.into_iter().map(|s| s.into()).collect();
470 self.scopes_added.extend(node.scopes.clone());
471
472 self
473 }
474
475 /// Applies the scopes to the registry
476 ///
477 /// # Arguments
478 /// * registry - the registry to apply the scopes to
479 pub fn apply_scopes(&mut self, registry: &mut CommandScopeRegistry) -> &mut Self {
480 for scope in self.scopes_added.clone() {
481 registry.add_scope(scope);
482 }
483 self.scopes_added.clear();
484 self
485 }
486
487 /// Sets the parser for the current node. This will decide how the argument
488 /// is parsed client side and will be used to check the argument before
489 /// it is passed to the executable. The node should be an argument node
490 /// or nothing will happen.
491 ///
492 /// # Type Parameters
493 /// * `P` - the parser to use for the current node (must be [`CommandArg`])
494 pub fn with_parser<P: CommandArg>(&mut self) -> &mut Self {
495 let graph = &mut self.registry.graph.graph;
496 let current_node = self.current_node;
497
498 let node = graph.node_weight_mut(current_node).unwrap();
499 self.parsers.insert(current_node, |input: &mut ParseInput| {
500 P::parse_arg(input).is_ok()
501 });
502
503 let parser = P::display();
504
505 node.data = match node.data.clone() {
506 NodeData::Argument {
507 name, suggestion, ..
508 } => NodeData::Argument {
509 name,
510 parser,
511 suggestion,
512 },
513 NodeData::Literal { name } => NodeData::Literal { name },
514 NodeData::Root => NodeData::Root,
515 };
516
517 self
518 }
519
520 /// Transitions to the node specified.
521 pub fn at(&mut self, node: NodeIndex) -> &mut Self {
522 self.current_node = node;
523 self
524 }
525
526 /// Gets the id of the current node (useful for commands that have multiple
527 /// children).
528 pub fn id(&self) -> NodeIndex {
529 self.current_node
530 }
531}