This post is going to document in prose how the command and interpreting system currently works. This was one of the early parts of the MUD I wrote, since it's a little hard to interact with the game without it being able to understand what you're trying to say. Along the way, I might even notice some bugs or shortcomings that ought to be fixed.

The easiest way to do this thoroughly is to follow a command from the beginning to the end of its lifetime. Let's start.

A human being types in a command into this telnet client. Let's say it's the "say" command. He types, "say hi".

There's a NetworkConnection instance looping through every line (newline-delimited) that comes in and passing it off to a ConnectionManager instance. The ConnectionManager ties a network connection to an entity in the game. In a way, it's the "brain" of a character, which controls it in the game world. Likewise, a NipicManager performs the same function for a Nipic in the game. You could imagine that the logic behind mobprogs would live in the NipicManager.

The manager passes that line of text to something called an InteractionMode. Since there are various ways that a player interacts with the world, the interaction mode encapsulates the interpretation of commands and displaying of output in those modes. For instance, most of the time, for example, when a character is moving around the world, the player is in the MainInteractionMode; but when he starts to edit his character's description, he's in the TextDocumentInteractionMode. Currently there's also a LoginInteractionMode and a CreationInteractionMode.

The interaction mode employs an Interpreter to parse the line of text received from the client into a Command object that contains information about the command and its arguments. Actually, the interaction mode receives an Invocation object that contains the Command object in question and its arguments. The Command is ignorant of the arguments being passed to it at runtime. Now that I think about it, I'm not really sure why Invocations need to exist. Instances of Command objects should be roughly equivalent to Invocations.

Anyway, the interaction mode in this case receives an Invocation object that contains a SayCommand and the argument, "hi". Now comes the fun stuff. The invocation is not immediately executed, but rather is placed in a queue of invocations. Invocations are picked off of the front of the queue on a periodic basis and executed.

The period is determined by the main loop of the MUD. Every manager, when connected to a valid network connection, signs up to be a PulseListener, which just means it implements the pulse() method. Every so often--to give you a frame of reference, SecondsPerPulse = 0.1--the main loop of the MUD calls pulse on all subscribed PulseListeners. The manager uses this call to ask the interaction mode to manage its queue.

The interaction mode does some processing and decides whether or not to execute an invocation. The reason all of this queue nonsense exists is simply because different commands have different amounts of "lag" associated with them. For instance, by default, every command has a delay of 1 pulse, but some commands deserve a larger delay. As a classic example, if you flee from battle, typically there is a delay of a couple seconds before you can continue running. In terms of what I just told you, for the next 20 times its pulse method was called, it would count down the time before the next invocation could be picked off the queue and executed. Note that the delay from a command comes into effect after the invocation is executed.

Once the invocation is finally executed, that's the end of the command's lifetime.

It's worth pointing out a couple other neat features of the interaction mode. The first is called a "fast" command. Certain commands, e.g. OOC (out-of-character) ones, should be exempt from lag that is preventing further commands in the queue from executing. The interaction mode's queue management takes into account fast commands, which are currently marked by having a delay of 0. Even if lag is halting the queue, fast commands are immediately executed. For now, the "emote" command is a fast command, because I believe that even if you're lagged, you should be allowed to type in-character text about what's going on with your character; it's a roleplay tool.

Another neat feature is called "flushing," which is something every good queue should have. Say you type in a series of commands with a long delay, but something happens in the middle--for instance, you get attacked--that causes you to want to cancel all those long commands you have queued. The interpreter, which is called immediately whenever a line of text comes in, immediately calls a method to flush the queue of the interaction mode, so the rest of those commands are short-circuited. Of course, it doesn't get you out of the lag from the last command. And since it doesn't hold the queue empty, you can immediately start filling it with a new series of commands that will start executing as soon as the lag clears.

In the previous description, I mentioned that the interpreter calls a method on the interaction mode to flush the queue. I just noticed that this design could probably be better. By leveraging fast commands, I could eliminate the need for the interpreter to have any knowledge of interaction modes. Less knowledge is better, as a general rule. I've made a note to investigate changing that design.

In terms of future work, there are two big work items: implementing the "!" command, which executes the last-typed command; and implementing aliases that are full-featured and help players equalize differences in typing speed. I'll be sure to post on both of these when I put them in.


Post a Comment