Hi all,
I'm currently developing a (very rudimentary) artificial intelligence for my game, and I choosed to use the "need-driven" method: every possible action is evaluated and then the best is picked.
In my game there will be something like a hundred of different actions, if not more... and for each of them I would like to have a different way of calculating its value.
For example, the value of attacking could be something like:
| myLifePoints - opponentLifePoints * myDamage;
|
while the value of eating something could be:
| myNeedOfEat * commestibilityOfObject + 1.0f;
|
the first thing I though of was some sort of struct, something like:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 | enum OperationType
{
Operation_sum,
Operation_mul,
...
};
struct Operand
{
real32 value;
uint32 attributeID;
};
struct AttributeCalculation
{
Operand startingValue;
OperationType* operations;
Operand* operationsValues;
};
|
This approach would work quite well I guess, the only problem is that its not very flexible: what if I want the value of attacking to be 0 if the target is of my same type?
| inline real32 EvaluateAttackAction( Entity* actor, Entity* target)
{
real32 result = 0;
if( actor->type != target->type )
{
result = actor->lifepoints - target->lifePoints * actor->damage;
}
return result;
}
|
So I've decided to throw away that idea, and return to what I think are the most common solutions: switch statement or function pointers.
I've profiled the function pointer things, and its 216 cycles vs the 96 cycles that the direct version has. (Of course for now there is only one possible action, so the switch statement doesn't exist)
How bad is that? But how bad would it be to have that very big statement?
Of course the only way to know it for sure is to measure it, but maybe someone can avoid me the bother and tell me "don't use function pointers in this situation, they will not hold".
edit: thinking more about it, 216 cycles for the function pointer version is not too much looking at the 96 cycles at the other end: really the huge switch statement would cost me LESS then 120 cycles overall?
edit2: Yes it was totally dumb to do the question without making a decent test. With a "fake" switch statement of only 10 cases, the cycles have gone up to 238.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | uint32 value = 5;
switch (value)
{
case 0:
{} break;
case 1:
{} break;
case 2:
{} break;
...
case 5:
{
score = testing( regionEntity, entity );
} break;
...
}
|
To give you an idea, the code I have in mind is something like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35 | internal void AnalyzeWorldAndSelectBestAction( World* world, Entity* e )
{
entity* target = 0;
uint32 targetAction = 0;
real32 bestScore = 0;
for( uint32 entityIndex = 0; entityIndex < world->countEntity; entityIndex++ )
{
entity* possibleTarget = world->entities + entityIndex;
if( possibleTarget != e )
{
for( uint32 actionIndex = 0; actionIndex < Action_count; actionIndex++ )
{
FunctionPointer* ActionEvaluatePointer = world->actionPointers + actionIndex;
score = ActionEvaluatePointer( e, possibleTarget );
if( score > bestScore )
{
bestScore = score;
target = possibleTarget;
targetAction = actionIndex;
}
}
}
}
}
if( target )
{
Vec3 toDest = target->regionPosition - e->regionPosition;
e->acceleration = toDest;
e->destAction = targetAction;
e->destEntity = target;
}
}
|
I guess that the loop could be inverted to limit the cache misses for the function pointer?
How much could that influence the final result?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | internal void AnalyzeWorldAndSelectBestAction( World* world, Entity* e )
{
e* target = 0;
uint32 targetAction = 0;
real32 bestScore = 0;
for( uint32 actionIndex = 0; actionIndex < Action_count; actionIndex++ )
{
FunctionPointer* ActionEvaluatePointer = world->actionPointers + actionIndex;
for( uint32 entityIndex = 0; entityIndex < world->countEntity; entityIndex++ )
{
entity* possibleTarget = world->entities + entityIndex;
if( possibleTarget != e )
{
score = ActionEvaluatePointer( e, possibleTarget );
...
|
Leonardo