Preface
When developing a command system or modules, you may want to consider
building a post-execution handling system so you can have a finer
control over commands. Discord.Net offers several different
post-execution workflow for you to work with.
If you recall, in the Command Guide, we've shown the following
example for executing and handling commands,
[!codeCommand Handler]
You may notice that after we perform ExecuteAsync, we store the
result and print it to the chat. This is essentially the most
basic post-execution handling. With this in mind, we could start doing
things like the following,
[!codeBasic Command Handler]
But! This may not always be preferred, because you are
creating your post-execution logic with the essential command
handler. This could lead to messy code and has another potential
issue, working with RunMode.Async
.
If your command is marked with RunMode.Async
, ExecuteAsync will
always return a successful ExecuteResult instead of whatever
results the actual command may return. Because of the way
RunMode.Async
works, handling within the
command handler may not always achieve the desired effect.
CommandExecuted Event
Enter CommandExecuted, an event that was introduced in
Discord.Net 2.0. This event is raised when the command is
sucessfully executed without any runtime exceptions or without
any parsing or precondition failure. This means this event can be
used to streamline your post-execution design, and the best thing
about this event is that it is not prone to RunMode.Async
's
ExecuteAsync drawbacks.
Thus, we can begin working on code such as:
[!codeCommandExecuted demo]
So now we have a streamlined post-execution pipeline, great! What's
next? We can take this further by using RuntimeResult.
RuntimeResult
This class was introduced in 1.0, but it wasn't widely adopted due to
the previously mentioned ExecuteAsync drawback. Since we now have
access to proper result handling via the CommandExecuted event,
we can start making use of this class.
What is it?
RuntimeResult
was introduced to allow developers to centralize
their command result logic. It is a result type that is designed to
be returned when the command has finished its logic.
How to make use of it?
The best way to make use of it is to create your own version of
RuntimeResult
. You can achieve this by inheriting the RuntimeResult
class. The following creates a bare-minimum for a sub-class
of RuntimeResult
,
[!codeBase Use]
The sky's the limit from here. You can add any additional information
you'd like regarding the execution result. For example, you may
want to add your own result type or other helpful information
regarding the execution, or something simple like static methods to
help you create return types easily.
[!codeExtended Use]
After you're done creating your own RuntimeResult, you can
implement it in your command by marking its return type to
Task<RuntimeResult>
.
[!NOTE]
You must mark the return type as Task<RuntimeResult>
instead of
Task<MyCustomResult>
. Only the former will be picked up when
building the module.
Here's an example of a command that utilizes such logic:
[!codeUsage]
CommandService.Log Event
We have so far covered the handling of various result types, but we
haven't talked about what to do if the command enters a catastrophic
failure (i.e. exceptions). To resolve this, we can make use of the
CommandService.Log event.
All exceptions thrown during a command execution will be caught and
sent to the Log event under the LogMessage.Exception property as a
CommandException type. The CommandException class allows us to
access the exception thrown, as well as the context of the command.
[!codeLogger Sample]