gbadev.org forum archive

This is a read-only mirror of the content originally found on forum.gbadev.org (now offline), salvaged from Wayback machine copies. A new forum can be found here.

C/C++ > How could I speed up my actor management/scripting system?

#28953 - Andor - Tue Nov 09, 2004 4:22 am

For my game, I've coded up a actor management/scripting system based on one that I programmed in Visual Basic for my games on the PC. In this system, each actor is an instance of the class cActor, which contains all the variables an actor could ever possibly need, along with all the routines for interpreting the scripts that I feed it.

However, it appears that my actor management system is terribly slow on the GBA: 12 actors on the screen, doing very little aside from walking around, consume no less than 127 vblanks (~156,464 cpu cycles). I have no idea why this should take so long (aside from the obvious: I'm new to c++, and my code is poorly written =P).

What's especially slow is when I have one actor read/write to another actor's variables:

From the Actor's script (stored in a s16 const array):
Code:
//get the value of variable8 of the Actor whose index = 5.
//in other words: me.variable9 = Actors[5].variable1
asGetOtherVar, 5, avVariable1, avVariable9,


This is the routine that is run by that statement:
Code:
void cActorEntity::ActorScript_GetOtherVar(s16 vvar1, s16 vvar2, s16 vvar3)
{
   s16 ActorIndex, VarToGet, retVariable, retValue;
   
   if (ActorScript_IsAVariable(vvar1) == 1)      {ActorIndex = ActorScript_GetVariable(vvar1);}
      else                     {ActorIndex = vvar1;}

   //don't get the value of the second vvar - instead, pass the index of the variable desired.
   VarToGet = vvar2;
   
   //don't get the value of the third vvar - instead, pass the index of the variable desired.
   retVariable = vvar3;
      
   retValue = Actors[ActorIndex].ActorScript_GetVariable(VarToGet);
   ActorScript_SetVariable(retVariable, retValue);
   return;
}


And this is the routine that gets a variable (this is called every time the Actor needs to get the contents of a variable:
Code:
s16 cActorEntity::ActorScript_GetVariable(u16 vartoget)
{
switch (vartoget)
   {
      case avTileX : //tileX
         return tileX;
      case avTileY : //tileY
         return tileY;
      case avOffsetX : //offsetX
         return offsetX;
      case avOffsetY : //offsetY
         return offsetY;
      case avSpriteID : //spriteID
         return spriteID;
      case avSpriteFrame : //spriteFrame
         return spriteFrame;
      case avSpriteSize : //spriteSize
         return 0; //because you can't read the sprite size. oh well, eh?
      case avSpritePalette : //spritePalette
         return spritePalette;
      case avSpriteAddress : //spriteAddress
         return spriteAddress;
      case avSpeed : //speed
         return speed;
      case avScriptID : //scriptID
         return scriptID;
      case avScriptLine : //scriptLine
         return scriptLine;
      case avReturnScriptLine : //returnScriptLine
         return returnScriptLine;
      case avActivateID : //activateID
         return activateID;
      case avActivateLine : //activateLine
         return activateLine;
      case avReturnActivateLine : //returnActivateLine
         return returnActivateLine;
      case avVariable1 : //variable1
         return variable1;
      case avVariable2 : //variable2
         return variable2;
      case avVariable3 : //variable3
         return variable3;
      case avVariable4 : //variable4
         return variable4;
      case avVariable5 : //variable5
         return variable5;
      case avVariable6 : //variable6
         return variable6;
      case avVariable7 : //variable7
         return variable7;
      case avVariable8 : //variable8
         return variable8;
      case avVariable9 : //variable9
         return variable9;
      case avVariable10 : //variable10
         return variable10;
      case avVariable11 : //variable11
         return variable11;
      case avVariable12 : //variable12
         return variable12;
      case avFlag_ActivateN : //flag_activateN
         switch (flags & 0x8000)
         {
            case 0 :
               return 0;
            default :
               return 1;
         }
      case avFlag_ActivateS : //flag_activateS
         switch (flags & 0x4000)
         {
            case 0 :
               return 0;
            default :
               return 1;
         }
      case avFlag_ActivateW : //flag_activateW
         switch (flags & 0x2000)
         {
            case 0 :
               return 0;
            default :
               return 1;
         }
      case avFlag_ActivateE : //flag_activateE
         switch (flags & 0x1000)
         {
            case 0 :
               return 0;
            default :
               return 1;
         }
      case avFlag_MovingN : //flag_movingN
         switch (flags & 0x0800)
         {
            case 0 :
               return 0;
            default :
               return 1;
         }
      case avFlag_MovingS : //flag_movingS
         switch (flags & 0x0400)
         {
            case 0 :
               return 0;
            default :
               return 1;
         }
      case avFlag_MovingW : //flag_movingW
         switch (flags & 0x0200)
         {
            case 0 :
               return 0;
            default :
               return 1;
         }
      case avFlag_MovingE : //flag_movingE
         switch (flags & 0x0100)
         {
            case 0 :
               return 0;
            default :
               return 1;
         }
      case avFlag_HFlip : //flag_HFlip
         switch (flags & 0x0010)
         {
            case 0 :
               return 0;
            default :
               return 1;
         }
      case avFlag_Visible : //flag_Visible
         switch (flags & 0x0008)
         {
            case 0 :
               return 0;
            default :
               return 1;
         }
      case avFlag_WhichScript : //flag_WhichScript
         switch (flags & 0x0004)
         {
            case 0 :
               return 0;
            default :
               return 1;
         }
      case avFlag_NoPause : //flag_NoPause
         switch (flags & 0x0002)
         {
            case 0 :
               return 0;
            default :
               return 1;
         }
      case avFlag_InUse : //flag_InUse
         switch (flags & 0x0001)
         {
            case 0 :
               return 0;
            default :
               return 1;
         }
   }
}


And for the record, here is the definition of class cActor:
Code:
class cActor {
public:
   s16 tileX, tileY;
   s16 offsetX, offsetY;
   u16 spriteAddress;
   s16 spriteID, spriteFrame, spriteSize1, spriteSize2, spritePalette;
   s16 speed;   
   s16 scriptID, scriptLine, returnScriptLine;
   s16 activateID, activateLine, returnActivateLine;
   s16 variable1, variable2, variable3, variable4;
   s16 variable5, variable6, variable7, variable8;
   s16 variable9, variable10, variable11, variable12;
   u16 flags;
   void RunActorScript();
   void Activate();
   void Deactivate();
   s16 ActorScript_GetVariable(u16 vartoget);
   void ActorScript_SetVariable(u16 vartoset, s32 value);
private:
   u8 ActorScript_IsAVariable(s16 vartotest);
   void ActorScript_Set(s16 var1, s16 vvar2);
   void ActorScript_Add(s16 var1, s16 vvar2, s16 vvar3);
   void ActorScript_Sub(s16 var1, s16 vvar2, s16 vvar3);
   void ActorScript_Mul(s16 var1, s16 vvar2, s16 vvar3);
   void ActorScript_Div(s16 var1, s16 vvar2, s16 vvar3);
   void ActorScript_Mod(s16 var1, s16 vvar2, s16 vvar3);
   void ActorScript_Inc(s16 var1);
   void ActorScript_Dec(s16 var1);
   void ActorScript_Rnd(s16 var1, s16 vvar2, s16 vvar3);
   void ActorScript_MovA(s16 vvar1, s16 vvar2);
   void ActorScript_MovR(s16 vvar1, s16 vvar2);
   void ActorScript_Goto(s16 value);
   u8 ActorScript_If(s16 vvar1, s16 var2, s16 vvar3);
   void ActorScript_SendCmd(s16 var1, s16 vvar2, s16 vvar3, s16 vvar4);
   void ActorScript_CallMacro(s16 var1);
   void ActorScript_Return();
   void ActorScript_GetBlockStatus(s16 vvar1, s16 vvar2, s16 var3);
   void ActorScript_SetBlockStatus(s16 vvar1, s16 vvar2, s16 vvar3);
   void ActorScript_Spawn(s16 var1);
   void ActorScript_Kill(s16 var1);
   void ActorScript_SetOtherVar(s16 vvar1, s16 vvar2, s16 vvar3);
   void ActorScript_GetOtherVar(s16 vvar1, s16 vvar2, s16 vvar3);
   void AdvanceScriptToEndIf();
};


I would be happy to post the entire system, but it's over 1400 lines long, and I'm not sure anyone would want to bother looking through it (if I'm wrong, and anyone is interested, I'd be more than happy to post it - it's reasonably well formated and shouldn't be too hard to get through).

Off the top of anyone's head, am I doing anything wrong - something that might explain why my actor system is so slow?

#28958 - sajiimori - Tue Nov 09, 2004 6:38 am

Start with good style, and speed tends to follow.

Use meaningful variable names, and don't use magic numbers.

If you have a big repetitive pattern, like that switch statement, find the common thread and express the core idea concisely.

In this case, you have 2 seperate sections to the switch: accessors for variables, and accessors for flags. Each of those can be given a simple, concise definition.
Code:

class Actor
{
  public:
    enum Var
    {
      VAR_TILE_X,
      VAR_TILE_Y,
      ...
      VAR_MAX
    };
   
    enum Flag
    {
      FLAG_IN_USE,
      FLAG_NO_PAUSE,
      ...
      FLAG_MAX
    };

    int getVar(Var v) { return vars[v]; }
    bool getFlag(Flag f) { return flags & (1 << f); }

  private:
    s16 vars[VAR_MAX];
    u16 flags;
};

#28959 - Andor - Tue Nov 09, 2004 6:59 am

Good style, eh? I'll have to run down to the hardware store and see if I can buy that in bulk.

Thanks for the suggestion, btw. I hadn't considered declaring an array of variables via an enumeration (I'm not even sure you can do that in VB); I'll go about implementing it and come back with the results.

If anyone is interested, btw, this is what I have done so far:
http://home.comcast.net/~mithrandel/GBADev/battletest1.gba

It's a fairly simple (and obviously incomplete) battle engine for an RPG. It may not be much, but I'm fairly proud of it for not knowing anything about GBA dev'ing three weeks ago. =P

#28962 - allenu - Tue Nov 09, 2004 7:32 am

Looks pretty good so far. Keep it up!

#28964 - sajiimori - Tue Nov 09, 2004 7:47 am

Good stuff. ^_^ The scripting system will definitely help in the long run.

#29053 - Andor - Wed Nov 10, 2004 10:04 pm

Great! My scripting system now runs ~37% faster, and I'm positive there's still room for improvement. Nevertheless, I think this is good enough for now; I can always revise this particular subsystem later.

Thanks for your help, sajiimori.