The command pattern places an method call into a object. They are the “object-oriented replacement for callbacks.” This crystallizes the actions of the method into an object which can be passed around, stuck in queue, or held onto for later. This makes implementing things like undo/redo, action queues, and replay a breeze.
I set out to create a couple base classes so that I could have an existing structure and save a little bit of time coding when I encounter a use for this pattern. The two main classes to this pattern are the Command itself, and the CommandQueue. Because the goal was to make a generalizable chunk of code, I decided to use delegates to contain the methods that a Command executes. This basically makes the Command object the subject of an observer pattern and all of the methods that subscribed to the delegates observers. Once the CommandQueue tells a Command it is time to execute, all those observers get to work.
Here is a very basic example of the command system I put together. Note that it handles the color change as an interpolation. The meeting of command and that change over time is something addressed below.
The Command
This is where the delegates live. That’s plural, because both an Execute delegate and an Undo delegate must be defined if undo functionality is desired.
Handling Time
When I first tackled this problem, I was just using delegates with a signature resembling:
delegate void Method ();
Now, this is fine for most software and many functions are performed instantly or block further action until it is done. But I am interested in games. Things in games take time. They move. They are fluid… or they should be. I wanted a system that could queue up an action, do the action over the course of a several frames, and then move on to the next queued action. This is the type of behavior that could be used for queuing up attacks, RTS production queues, or any animated action. It is also necessary when using this pattern to implement a replay system.
To tackle this design problem, I turned to Unity’s coroutines. This allowed me to tell the CommandQueue’s Execute() method (which I changed to a coroutine) to yield until the current Command’s coroutine (shown below) finished.
public virtual IEnumerator Execute () {
if (HasValidExecute ()) {
yield return (ExecuteCoroutine.Invoke());
}
}
public virtual IEnumerator Undo () {
if (HasValidUndo ()) {
yield return (UndoCoroutine.Invoke ());
}
}
My delegates now looked like this:
delegate IEnumerator Coroutine ();
That solved the problem of letting things take time, but what if I also wanted to allow instantaneous method calls in the queue. To address this I brought back my void delegate, so I had two types: Method and Coroutine.
public delegate void Method ();
public delegate IEnumerator Coroutine ();
Then two Command constructors were made, one taking in Methods, one taking in Coroutines. The Coroutine constructor directly assigned the delegates to its Execute and Undo coroutines.
public Command (Coroutine execute, Coroutine undo) {
ExecuteCoroutine = execute;
UndoCoroutine = undo;
}
The Method constructor first wrapped the Methods in coroutines, then assigned them to the Execute and Undo coroutines.
public Command (Method execute, Method undo) {
ExecuteDelegate = execute;
UndoDelegate = undo;
ExecuteCoroutine = ExecuteWrapper;
UndoCoroutine = UndoWrapper;
}
protected virtual IEnumerator ExecuteWrapper () {
ExecuteDelegate.Invoke ();
return null;
}
protected virtual IEnumerator UndoWrapper () {
UndoDelegate.Invoke ();
return null;
}
BOOM! A generalizable way of handling both types of commands. One thing to note is that this implementation at its worst adds two layers of Delegate.Invoke() and two of coroutine yields. Therefore there is a bit of added overhead. But Commands are generally things that stay out of the update loop and iterating over large arrays, so for my use, I will deal with the overhead.
The CommandQueue
The role of the CommandQueue is to house the Commands in a list, keep a current index, prohibit invalid Commands, and provide state information to any class using the queue.
public class CommandQueue {
private List<Command> queue = new List<Command> ();
private int index = 0;
private bool ready = true;
public void QueueCommand (Command command) {
if (command.HasValidExecute()) {
queue.RemoveRange (index, queue.Count - index);
queue.Add (command);
}
}
public IEnumerator Execute () {
if (CanExecute ()) {
ready = false;
yield return (queue [index].Execute ());
ready = true;
index++;
}
}
public IEnumerator Undo () {
if (CanUndo ()) {
ready = false;
yield return (queue [index - 1].Undo ());
ready = true;
index--;
}
}
public bool CanExecute () {
return queue.Count > 0 && index < queue.Count && ready;
}
public bool CanUndo () {
return queue.Count > 0 && index > 0 && ready &&
queue [index - 1].HasValidUndo();
}
public bool IsReady () {
return ready;
}
}
In this current implementation, the Execute and Undo coroutines are limited by whether the previous command has finished. With a more robust state system this would not be necessary. But for my purposes, limiting the queue to serial execution is acceptable.
Future thoughts
The biggest change I plan on making is to the QueueCommand method. I would like to make Commands interruptible by other Commands getting added to the queue. This would then have to alter the state of the interrupted Command so that playback functions properly. An example of this would be a playback system for an RTS game. Clicking in one spot tells a unit to move there. Clicking another before it reached the first spot interrupts the movement with a new goal. For the sake of the playback, the target position of the first Command should be changed to the position at which that Command was interrupted.
Coroutines also need to have better stopping control both for interruption and destruction of Command objects.
Adding time information to queues could be effective for playback systems. Alternatively, idle Commands could fill this niche by clogging up the queue with a WaitForSeconds(float idleDuration) call.