Skip to content
davido262 edited this page Nov 11, 2012 · 14 revisions

The Command Terminal is the backbone of the engine; every action in the engine is executed by a command.

Overview

The Command Terminal basically consists of a queue of strings that stores commands. Each time something happens that triggers a command, that command gets pushed to the back of the queue. At the end of each frame, all of the queued commands get parsed and executed in the order of arrival (FIFO).

How it Works

Command Registration

Every object that will be able to have commands must inherit from the class CommandObject. From there they will inherit four important functions:

  1. void registerCommand(const std::string& commandName, const slot_t& slot);
  2. void registerAttribute(const std::string& attributeName, const slot_t& slot);
  3. void unregisterCommand(const std::string& commandName);
  4. void unregisterAttribute(const std::string& attributeName);

The first two are responsible of registering commands and attributes and the last two unregister them. The first parameter of all the functions above is a string which would be the name of the command or the attribute.

The second parameter of the register* functions is a slot. A slot is basically a pointer to a function. Whenever said command or attribute is accessed, the slot function will execute. A slot function must have the following prototype:

std::string Component::cmdFoo(std::deque<std::string>& args);

As a coding style, all the slots in the engine start with cmd*. This is of course optional, but helps readability. It receives a single parameter which is a double ended queue with all the arguments. Each argument is a string which has been trimmed from any surrounding blank spaces. It is important to check you are not receiving less arguments than needed. The recommended way to parse each argument is by the use of target_t target = boost::lexical_cast<target_t>(args[n]);. It returns a string which is used as the return messages of the commands in the terminal.

In order to reference a member function from an object as a slot, we have to use boost::bind(functionPointer, object, _numberOfArguments).

The commands register into a map in that object. The key is the command Id and it references the slot.

Example

Lets assume we create a Character component to attach to an entity. The Character component should have an action shoot and an attribute health. It is a simple component such as this:

#include "engine/kernel/component.hpp"

class Character: public Component {
public:
    Character(Entity* entity);
    void shoot();

private:
    double m_health;

    std::string cmdShoot(std::deque&);
    std::string cmdHealth(std::deque& args);
};

First we should declare the functions that will be called with each command, such as these:

std::string Character::cmdShoot(std::deque&) {
    // arguments will not be used, it is safe to ignore them
    shoot();
    return "";
}

std::string Character::cmdHealth(std::deque& args) {
    if (args.size() < 1)
        return "Error: too few arguments";
    m_health = boost::lexical_cast(args[0]);
    return "";
}

Our Character constructor might look like this:

Character::Character(Entity* entity):
    Component("character", entity),
    m_health(100.0)
{
    m_entity->registerCommand("shoot", boost::bind(&Character::cmdShoot, this, _1));
    m_entity->registerAttribute("health", boost::bind(&Character::cmdHealth, this, _1));
}

Assuming the functions void cmdShoot(const string& arg) and void cmdHealth(const string& arg) exist. Our destructor might look like this:

Character::~Character() {
    m_entity->unregisterAttribute("health");
    m_entity->unregisterCommand("shoot");
}

Command Parsing

After the commands are queued, once every frame it will parse and execute every command. There are three token tables. Each token table is like a map that assigns each token string an ID number. There is one for all the objects, another for all the commands and another for all the attributes. This way every object, command and attribute has a unique ID number.

Internally for a command to be executed, it needs the object's ID number, the command's ID number and a string with the arguments.

The first step is to check if the first token is found in the objects token table. If it is, it returns the object ID number and now tries to check if the second token is found in the commands token table. If it is, it returns the command ID number.

For the case of the attributes, it is a special command set which is declared into CommandObject. So it always executes that command and has to check if the first token in the arguments string is found in the attributes token table.

If at any stage any token is not found, it prints an error and skips that command.

Command Execution

Once the Terminal has the object ID, the command ID and the string of arguments it runs the command. There is a map which takes as a key the object ID and gives a pointer to the object. Referencing that pointer, the command ID is searched into the map of commands in that object and if it is found it will execute the slot referenced by it. The slot takes as an argument the string of arguments and every slot parse those arguments differently.

Clone this wiki locally