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}