The Peacenet: Why Writing Command Shells Is So Freakin’ Hard.

If you’ve either seen or played The Peacenet, you’ll know the game has a Terminal in it that runs an in-game version of the Bourne shell (a.k.a, ‘sh’ on Linux). Such a simple in-game program… yet I’m always reluctant to implement new features in it! Why!? Well, the goal of this article is to explain why.

I can never ever block the thread.

A real-life shell will literally sit there waiting for you to enter a command and won’t do anything else until you do. In this state, the shell’s thread is blocked. And the shell is most likely single-threaded, so, since its threadis blocked, the shell itself is completely blocked.

This isn’t a problem for real-life operating systems because shells are run just like any other program – in a separate process with its own main thread, allowing that thread to block without blocking the entire OS and preventing you from using the computer. This is the kind of behaviour that multi-tasking operating systems allow.

HOWEVER, Peacenet is not a full-blown multitasking OS. It is a game. Games work a little bit differently. A game is essentially a big infinite loop that executes until the game stops running. The basic tasks of this loop are to update all the objects in the game and then render everything to the screen. The loop does this several times every second. In most cases, 60 times. Each time the loop does these two things, one frame is executed. So when people say what the frame rate of a game is, it’s how many times every second that the game is able to update all of its objects and render them to the screen.

In The Peacenet’s case, each program and Terminal Command, including the desktop, is one of these objects. And if one of these objects ever blocks the game’s thread while updating or rendering, the game loop is never able to continue so the game freezes and you can’t play! ):

So…how the frick do you write a full-blown Bourne shell that runs as an object in a game loop without freezing the game? Smoke and mirrors. That’s how. That’s the first reason writing the shell is hard.

I Can’t Spawn Separate THreads.

Okay, I can spawn separate threads in Unreal Engine. There’s complete documentation on it on the Epic Games website. But… multithreading in a game is extremely difficult to do right. If you do it wrong, you’re opening yourself up to a ton of bugs. And debugging multithreading-related bugs is a pain.

So while the UE4 API wil allow me to spawn separate threads, in this case, they are NOT the right tool for the job and I shouldn’t use them. So effectively, I can’t. Can’t run a command on a separate thread, can’t run the shell on a separate thread, can’t block the game thread. Yikes.

So how do we get around it?

I already mentioned that “Smoke and mirrors” is how I get around these issues. While Peacenet’s shell looks like it is patiently waiting for you to enter a command, it’s actually constantly pestering you every frame – asking you “Hey, have you entered a command?”

So while a regular shell will do this:

while(ShellIsRunning)
{
    string Command = ReadLine();
    ParseLine(Command);
    ExecuteLine(Command);
}

Peacenet’s shell actually does this.

void Update(float DeltaTime)
{
    if (WaitingForCommand)
    {
        string Command = "";
        if(GetLine(Command))
        {
            ParseLine(Command);
            ExecuteLine(Command);

            WaitingForCommand = false;
        }
    }
    else
    {
        WriteLine("> ");
        WaitingForCommand = true;
    }
}

That is basically pseudocode for every single shell in The Peacenet. The game will do this, essentially.

  • Are we waiting for a command?
  • If we are…
    • Has the user entered a line of text yet?
    • If they have, parse it, execute it, and wait for the command to finish.
    • Now we’re not waiting for a command.
  • If we’re not waiting for a command then we write a prompt to the console conveying “You can enter a command now.” and start waiting.

This type of code is what allows the game to keep waiting for a command and execute it when it comes, without blocking the thread until the command comes.

We do this because if the thread is blocked because we’re waiting, the game can never read for keyboard input. So the command will never come! Infinite loop.

It gets far more complicated when we start to account for waiting for a command to complete. Not only can the shell not block the thread, a command can’t either.

We nee to wait for the command to complete, but that’s easy to do. We just use the same code as above but instead of waiting for a command to be entered by the user, we just wait for the current command to say “Hey! I’m done!”

If the command itself blocks the thread, the game just won’t update the UI until the command finishes. This isn’t as bad, but, for those of you writing commands, try not to block the thread – try to use as much non-blocking code as possible. 🙂

To see actual code for the solution to our command shell problem, check out the header and source files on GitHub.

https://github.com/bitphoenixsoftware/the-peacenet/blob/master/Source/ProjectOglowia/Public/CommandShell.h

https://github.com/bitphoenixsoftware/the-peacenet/blob/master/Source/ProjectOglowia/Private/CommandShell.cpp

1 thought on “The Peacenet: Why Writing Command Shells Is So Freakin’ Hard.

Leave a Comment