AiGameDev.com

“Join leading experts and industry veterans in Paris on June 23-24 for the largest independent conference about artifical intelligence in video games.” — Alex

membership

The Premium Membership area at AiGameDev.com is the best place to stay on the cutting edge of artificial intelligence in video games.
Find out more!

sponsors

categories


subscribe

Search


related articles

Sponsors

SpirOps

PathEngine


Fast Delegates for Efficient AI

Alex J. Champandard
June 11, 2007

This post about the efficiency of fast delegates has been superseded by this article. It’s hard to make a fast delegate as efficient as an equivalent virtual interface (though it’s possible). The main benefit of fast delegates is their effectiveness at keeping software architectures simple, which indirectly can improve efficiency.

From the perspective of maintainable software, having a class for low-level tasks and individual observers is very useful. However, this particular implementation of object oriented behaviors can have its costs. All behaviors must be exposed as tasks when they are added to the scheduler, which requires many small memory allocations. On top of that, all the function calls are virtual — which adds a non-negligible overhead on most platforms.

In plain C, having tasks as function pointers would resolve these performance problems, but it would exclude most object oriented designs that are common in C++. Having unique tasks for certain NPC for example would be much harder to achieve.

Conceptually, closures are the solution to this problem. A closure is a function that has its own execution environment. In the case of AI logic, a task is such a closure with its own code and memory. Most languages support some kind of closures, but C++ has always had trouble with this. Anyone who has worked with function pointers knows how hard it is to deal with different types of functions (even if they have the same prototype).

Fast delegates are the answer to this C++ conundrum. Delegates are just like interfaces, but they have much lower overhead. They store only a function pointer and the associated “this” object. As such, it’s possible to implement closures with them very efficiently. Fast delegates, on most platforms, take barely 8 bytes of memory and compile down to two instructions in assembler. (Edit: Sadly, since compilers must compile C++ member function pointers with the worst case scenario in mind, the general fast delegate ends up taking up more memory and instructions than the best case C function pointer.)

The fast delegate library makes it very easy to create such closures from any type of function:

using namespace fastdelegate;
typedef FastDelegate()> Task;

Status MyTaskFunction()
{
    return Running;
}

struct MyHolder
{
    Status MyTaskMember()
    {
        return Completed;
    }
};

This is how the tasks are initialized and called:

// Initialize the delegates.
Task t1(&MyTaskFunction);

MyHolder holder;
Task t2(&holder, &MyHolder::MyTaskMember);

// Run them.
Status s1 = t1();
Status s2 = t2();

This particular library isn’t 100% C++ standards compliant under the hood, but all modern compilers are fully supported. The interface, however, is type-safe so it’s generally not much of a problem. On the other hand, this alternative implementation is standards compliant, but not supported on as many platforms.

Once you get comfortable with these delegates, you’ll find yourself using them everywhere. They allow you to use object oriented C++ designs, and still get the most performance out of the system — as if you were implementing it in plain C!


Bookmark and Share




Comments