Optimizing Scripts

Optimizing Scripts

I'm currently reviewing best I can my memory isn't that great anymore....strings meaning in reference to arrays? This morning I was looking at some macros written using the assert function.....used to save myself and  co's tons of bugs at a small company I worked for many moons ago. 
 
Prefer Integers Over Strings

Here's one I see a lot, sometimes derived from one of my old example functions circa 2007: Passing the string name of a constant or file as an argument, then concatenating it into some useful value later, like this

@cmd do_something_cool openborconstant("ANI_FREESPECIAL")

Damon Caskey
Hmm, now I understand better what you are talking about. I will make some tests to see how much impact it can have.
Please, continue making these tutorials, are extremely useful
 
Kratus said:

Glad to hear it Kratus. You know enough about code I don't need to explain why strings are bad, but what you may not know is that's doubly important for openborconstant().

Openborconstant() can really make or break script performance. By necessity it is a massive and horribly slow function. It has to do a string comparison of every script exposed constant available one by one until it finds the one you want. Yet in practice it's the fastest function you can use because if the value is known at load time, all openborconstant() calls are pre-processed and optimized away, making them completely free just like #defined constants. Unless... you are concatenating the openborconstant() string argument at run time. Then there's no way for the pre-processor to understand it and the constant must also be found at run time.

Good:

C:
openborconstant("ANI_FREESPECIAL23");

Bad (like, really bad!)

C:
// This...
openborconstant(<some variable>);

// Or this, or anything else like it...
openborconstant("ANI_FREESPECIAL" + <some variable>);

DC
 
Last edited:
O Ilusionista said:
Damon Caskey and what would be the best solution for this?
openborconstant("ANI_FREESPECIAL" + <some variable>);

O Ilusionista,

The solution is to never do it! ;)

The Openborconstant() needs to be upstream, at the point your animation is selected.

Example: Let's say you are using a function to change animation at a certain frame, and you do it like this:

C:
void change_animation(int freespecial_number)
{
    void ent = getlocalvar("self");
    performattack(ent, openborconstant("ANI_FREESPECIAL"+freespecial_number));
}

Code:
frame data/chars/dude/frame_0.png
 @cmd change_animation 2
frame data/chars/dude/frame_0.png
frame data/chars/dude/frame_0.png

You should immediately change it to this instead. It will perform much better (as a bonus, it's easier to read and debug):

C:
void change_animation(int animation_id)
{
    void ent = getlocalvar("self");
    performattack(ent, animation_id);
}

Code:
frame data/chars/dude/frame_0.png
 @cmd change_animation openborconstant("ANI_FREESPECIAL2")
frame data/chars/dude/frame_0.png
frame data/chars/dude/frame_0.png
 
Last edited:
Kratus

In general it can depend on what type of program is being coded. But some times when arrays and pointers are used correctly it can often help you manage things a little easier without flaw and managing memory efficiently. Their uses vary and they can be made constant. You can use const for pointers before, after, and in both places so code like this is legal to the compiler

const int pOne;
int * const pTwo;
const int * const pThree;

Pone is a pointer to a constant integer.The value it points to can't be changed so if you tried *pOne = 5 the compiler would flag an error

pTwo is a constant pointer to an integer the integer can be changed but pTwo cannot point to anything else

pThree is a constant pointer to an integer, the value it points to can't be changed and it cannot point to anything else


const int * p1;  // the int pointed to is constant
int * const p2;  // p2 is constant, it can't point to anything else



You can envision an imaginary line just to the right of the asterisk if the word const is to the left of the line that means the object is constant, if const is to the right of the line pointer itself is constant.

Programmers generally deal with 5 areas of memory

Global name space
The free store
Registers
Code Space
The Stack

Often Pointers are used to hold the address of objects on the free store and are used to pass arguments by reference. And when used in more advanced functions they can be implemented into class polymorphism. You might ask why should I bother to declare anything on the free store? It's because objects on the free store persist after the return of a function this also ensures the capability to decide at runtime how many objects you need instead of having to declare this in advance. Objects created on the free store can be used to create complex data structures.


Often simpler programs  pointers. They can be a common source of bugs if not managed carefully in your code. Once the pointer is deleted It's just so very important to not only delete a pointer but set it to null. That disarms it.

When you declare an array you tell the compiler exactly how many objects you expect to store in it. The compiler sets aside memory for all the objects even if you never need to use it.


It is is possible to put an entire array on the free store. You can do this by calling "new" and using the subscript operator. The result is is an area on the free store that holds the array

CAT  *Family = new CAT(500};

declares Family to be a pointer to the first in an array of 500 CATs so Family points to--or has the address of--Family[0]



pointers can be powerful techniques when using the free store they bring risks of leaks and stray pointers but if you are very careful they can be a very effective to use in programs

TO the point concerning memory management pointers are often learned early for a reason. And Arrays can use pointers

Arrays can be used on the stack for smaller memory usages but stack memory is severely limited but like pointers they cab be used on the free store (AKA heap)


When you declare an array for instance, you can tell the compiler how many objects you expect to store in it. The compiler sets aside memory for all the objects You can always estimate how many objects you will need.

The previously mentioned CATs might give birth to between 1 and 10 kittens. So if there are just a few pregnant cats it easy to arrive to an estimate as opposed to if you had fields of cats with 500 roaming about. In a case where you might have no idea how many objects you might need you have to use more advanced data structures. 


It is also possible to put the entire array on free store. By calling "new" and the subscript operator. This is essentially a pointer to an area on the free store that holds the array itself


int *Family = new CAT[500]; //declares Family to be a pointer to the first in an array of 500 CATs. Since Arrays start with 0. So family points to or you could say has the address of FAMILY[0] The advantage to using Family in this way is that you can use the pointer arithmetic to access each member of the Family so you write something like


CAT * Family = new CAT[500];   
CAT pCAT = Family;              // pCAT points to Family[0]
pCat->SetAge(10)                // set Family[0] to 10
pCat++;                        // advance to Family[1]
pCat->SetAge(20)              // set Family[0] to 10

This declares a array of 500 CATs and a pointer to point to the start of the array...using that pointer the first CATs SetAge() function is called with the value of 10 the pointer is then incremented to point to the next CAT and the second CATs SetAGe() method is called.


It is very important to know a pointer to an array VS. an array pf pointers.

CAT  FamilyOne[500]
CAT * FamilyTwo[500]
CAT * FamilyThree = new CAT[500]



the difference among these three code lines dramatically affect how these arrays operate FamilyThree is a variant of FanilyOne but is very different from FamilyTwo

This sorta touches on the thorny issue of how pointers relate to arrays. In the third case FamilyThree is a pointer to an array. The address of FamilyThree is the address of the first item of the array. This is exactly the case as FamilyOne

The address accessed when you write Family + 4 isn't 4 bytes past the address it's 4 objects passed it. If each object is 4 bytes long Family +4 is 16 bytes. If each CAT that has four long member variables of 4 bytes each and two short member variables of 2 bytes each,, each CAT is 20 bytes and Family  + 4 is 80 bytes past the start of the array. It's important not to read or write past the end of an array and not to confuse an array of pointers with a pointer to an array. Like with pointers you will want to delete arrays when no longer using them on the heap which often what the free store is referred to. The bracket is a signal to the compiler that that the array is being deleted. If you forget the brackets only the first object in the array is deleted. Then you have a memory leak.

You can stake things steps further with more advanced functions

For instance what is referred to as inheritance can be used to establish an observation of the entire animal kingdom

      Animal
        |
-Mammal    -Reptile
    /  \
    Dog Horse
    /
Yorkie
  /
Terrier
and so forth

In Video game object oriented programming the Assert function is so crucial because it raises a debugging error if the Assert is false. This piece of code ha saved tons of bugs!


#if defined(_DEBUG)
extern BOOL MYAssertFunc(BOOL, int, char*);
#define NatBreak()  {_asm { int 3 } }
// Asserts that exp is true.
#define Assert (exp)  \
  if (MyAssertFunc((int) (exp),_ _LINE_ _,__FILE__)) \
    NATBREAK();
// Non-debug case:
#else
#define
#endif    //_DEBUG


in a release build all Assert macros get compiled out in the debug build the MyAssertFunc wa icluded in a library. The function evaluates up the first argument and brings up a message box . The programmer can then choose to continue executing or break if so the function returns true and the int 3 instruction causes the debugger to break on the Assert macro line.



I graduated college in the early 2000s and the great thing about C++ is it's a powerful flexible tool  and as with life as well... you never stop learning...coding with can be both art and science like. I stepped out of it for over a decade but I decided to get some old disks out. Coding is the DNA of your program.  I only recently took a gander at  OB and while back. I don't know the engine that well. I now have my character crouching, attacking, air attacking I was struggling with all this.  Although I am having some tricky time attempting to make this giant snake I want in my game work correctly...he has turned out to be a real challenge. it's tricky coding his movements and attacks. Good Luck with it!
 
Kratus said:
Wombat2112
Wow! Thanks for the explanation

I hadn't done much in the last decade. I'm so out of practice I need to review "Hello world" lol. Good thing about game engines you can keep the coding at a minimum or go as far as you want with it and add your own contributions. What I like about OB and this community is it hasn't been abandoned like so many engines and thier support forums have.  :-[
I like playing my PS4 and modern games but games like Megaman and Castlevania are some of my all time favorites. I've always wanted to make a 2D beat em up or side scrolling game that felt like the arcade era. You have been here longer than I so I know I probably don't have to tell you this but I broke out an 3D engine I bought years ago, LOL doesn't have half the online support that OB has in terms of experienced designers and people that can really help guide you.  Took me a while to get going...but I have had a lot of fun.

Take Care  :)
 
DC, I think I could try to use the defines by abbreviating function names for animation scripts, the same style both utunnels and White Dragon did on other non animation scripts. Is it okay that I can use to abbreviate in animation scripts? I was using #import the last time and it crashed, but right now, I don't know if it can work for defines.

Example:

Code:
@cmd attack0 ob("ANI_FREESPECIAL15")
 
maxman said:

maxman,

Sorry, I just now saw your post when I was linking the thread. I know it's been a while, but to answer your question about abbreviating openborconstants(), NO!

There's no technical reason you can't, but trust me: It's a really, really bad idea. I know from personal experience. Several years ago I used #define to abbreviate a massive list of openborconstants() for animations, functions, and variable indexes. Over 1200 in total. Seemed like a great idea at the time. Almost ten years later I'm still cleaning it up.

There's no performance benefit, and openborconstants() are by definition already unique. All you accomplish is tying your functions to a monolithic source file while making the constants harder to find in text searches. Take it from someone who went down that rabbit hole and got stuck. Don't do it.

DC
 
@DCurrent I went to the site which you linked and mentioned localvars but just to report it to you that the link is broken. Do you mind fixing the link for localvars? Is there a working link for it?
 
@DCurrent I went to the site which you linked and mentioned localvars but just to report it to you that the link is broken. Do you mind fixing the link for localvars? Is there a working link for it?

Fixed. Sorry about that. I forgot to update link when I built the new wiki.

DC
 
yet it crashes

The engine isn't crashing (it wasn't back in the day either). It's performing a controlled shut down and printing an error report to the log. There's a big difference, and it's important to understand because knowing that helps you to fix the issues. Post the log file and that will help us figure out what's up. :)

DC
 
Optimizing Script Tips

It's no secret scripts are an incredible tool. The ability to easily insert your own code right into modules and have it work along side native engine functions is one of the main reasons OpenBOR can replicate any game play feature imaginable. But like Uncle Ben said, "with great power comes great responsibility."

Done right, scripts can help set your module apart not only from the endless parade of tired BOR clones, but also from each other. Believe it or not, a well conceived script can also optimize your module to consume less resources. This same power can also wreak havoc - transforming your would be work of art into a clunky resource hog.

If you are asking why you should care, that's up to you. Do you want portability? It's important to be aware there is a vast gulf between consoles, mobile devices, and PCs. Your aging laptop can easily brute force through resource intensive modules, but even the latest Androids might hack up a lung. Beyond that, optimized scripts don't just help the platform, they will help YOU in the module building process.

This guide is intended to provide an overview of optimizing techniques. It assumes you already have some proficiency with scripting, or at the very least have experimented a bit. Note that script is an art form, and as such there can be exceptions to every rule. These are general tips that apply to most situations. Several of them may overlap. I'll try to provide an explanation as to why you should follow each tip. That's often where the real knowledge lies, so if you pull a TLDR, it's your own fault. :)

Avoid Inline Scripts

Inline scripts are code that is written right into the animations of a model's text file using the @script and @end_script tags. This is one of the most commonly seen, and unfortunately, worst ways to add animation script. Instead, you should write functions and call them with a @cmd tag. Inline code does have its uses, but it should be saved for very special circumstances.

Why?

Inline code runs on every single frame of a given animation. This means you must either add code to avoid taking action except on specific frames, or just put up with code executing when it doesn't need to. Additionally, by its nature inline code is impossible to reuse (more on that later), and any local variables you need must be reacquired each time the code runs.

Inline code is instead best used as a means to execute predefined functions in the rare case you want to call the function on every frame.

Whenever Possible, Break Tasks Down to Reusable Functions

Just as you should avoid inline code, you should be breaking tasks down into functions. Remember that functions can call other functions. Therefore, try to break big tasks down into smaller bits of reusable code. Then you can piece them back together as needed.

There is a tiny bit of overhead for a function call, but this is more than made up for by the memory and time you can save by building portions of code that can be reused over and over again.

Why?

Script requires memory to compile and prepare for use. The more you can reuse code, generally the more you can save memory (assuming proper use of #import and #include... more on that later). Additionally, human time is also very expensive. By defining and executing simple functions, then executing those functions with other functions to assemble more complicated tasks like a set of Lego blocks, you centralize your maintenance points. This also makes life easier when something goes wrong.

Avoid Magic Numbers

Magic numbers are any static/constant values (typically numbers, hence the name) inserted directly into code. For instance, you might have a function that takes a value of 0 to 2, then performs an action accordingly. In your function, the code looks something like this:

C:
switch(value)
{
    case 0:
     // do something.
 
    break;

    case 1:
     // do something else.

    break;

    case 2:
     // do something different than before.

    break;
}

Then you call the function like this:

Code:
@cmd my_doing_something_function 1

That doesn't look so terrible at first glance, but it is a very poor practice that will eventually cause you headaches. Instead, use the #define directive to create named constants, like this:

C:
#define DO_SOMETHING  0
#define DO_SOMETHING_ELSE 1
#define DO_SOMETHING_DIFFERENT 2

switch(value)
{
   case DO_SOMETHING:
     // do something.

   break;
 
   case DO_SOMETHING_ELSE:
     // do something else.

   break;

   case DO_SOMETHING_DIFFERENT:
     // do something different than before.

   break;
}

You can (and should) use the #defined constant in your function call too, so now it looks like this:

Code:
@cmd my_doing_something_function DO_SOMETHING_ELSE

See how the function call is a lot more readable? You know you're telling it to do "something else" without needing to go back and review the function itself.

Why?

This has no effect on CPU or memory use, but any experienced coder will tell you the single most important thing you can do is make the code readable. Magic values are almost impossible to understand and maintain without wasting valuable time analyzing. If something goes wrong you have to fix it all by hand. That's a job that could take days. I've written an entire article about magic numbers you can check out here for further details.

Turn On NOCMDCOMPATIBLE

The nocmdcompatible command is a binary option in script.txt that when turned on (1) disables the compatibility mode for processing @cmd tags. By default this value is 0, meaning OpenBOR processes tags in a less optimal way to accommodate older modules.

Why?

The @cmd tag executes native or user defined functions on the frame they are placed. In compatibility mode, when there are multiple @cmd tags on a frame, the frame number is evaluated for every tag. Here's an example:

Code:
@cmd f1
@cmd f2
@cmd f3
frame data/chars/ffff/1.png

Compatibility mode enabled (nocmdcompatible 0).

Code:
if(frame==3)
{
    f1();
}
if(frame==3)
{
    f2();
}
if(frame==3)
{
    f3();
}

With compatibility mode disabled (nocmdcompatible 1), the engine compiles with a more optimal algorithm where the frame number is evaluated only once:

Code:
if(frame==3)
{
    f1();
    f2();
    f3();
    return;
}


Avoid Global Vars

Global variables are great tools, but it is important to note they are intentionally designed to remain in memory until you clear them or shut down the module. Certain settings allow them to remain in save files even when the engine is turned off. This means two things:

  • You have to make sure these variables are deleted. Depending on your function design, that might mean adding and executing execute code in other unrelated events. It can add a lot of unwanted complexity that is error prone and difficult to keep up with. And of course extra code can mean extra work for the engine.
  • If you forget or make just one tiny mistake in step 1, the variable stays around forever. It might cause bugs by retaining a value you thought was blank, and will likely give your module a memory leak (gradually using more and more memory over time).
What to do then? Easy, use local vars. Most of the time I see global vars in use, it is to track values across several iterations of an animation script or monitor some entity specific value. There's no need for global variables to do this. Local vars will work just fine.

Why?

Unlike global variables, the engine cleans up local vars when the script they are defined in is unloaded. Note, the script, not the function. So while you can't use a local var to do something like pass information from an animation script to an update script, you can pass information around within either one of those all day. And when the script is done, the variable dies with it. It's more convenient for you, and more optimal for your module.

In other words, most of the time you are using global variables, you could probably use local variables instead, in which case you should. Save the global variables for the jobs that only they can do.
@DCurrent

Man, I really need to thank you for all the programming tutorials, they are helping me a lot to improve my codes. I was studying some of your posts in caskeys.com/dc and I found some interesting topics there too.

Gradually I'm replacing some of my old methods with yours, especially in the SORX. There's many things to do yet but I can clearly see a huge progress.

EDIT: @DCurrent I forgot to ask a question. Since I knew about the animation script event on entities I didn't use in-line scripts anymore. However, I would like to know if there's a way to avoid in-line scripts on level files too.

I know that I can call a spawnscript but they aren't reusable like the @cmd function because I need to code a lot of separated spawnscripts and call each one according to a specific situation. In this case I use small in-line script, like the example below:

C:
spawn        Barrel 1
@script void main(){setentityvar(getlocalvar("self"), "item", "Pipe");}@end_script
flip        1
coords        350 180 0
at            0
 
Last edited:
void change_animation(int freespecial_number) { void ent = getlocalvar("self"); performattack(ent, openborconstant("ANI_FREESPECIAL"+freespecial_number)); }
ehh, now I just saw your post... I have a lot of these in my scripts.... in my FF LNS Ultimate Mod as I just used their go_bys...
I guess it is a good time for me to create my own game...
 
Is it okay to make it like this?

C:
void jumpLand()
{// Lands with jumpland animation
    void self = getlocalvar("self");
    int jumpLandId = openborconstant("ANI_JUMPLAND");

    performattack(self, jumpLandId);
}

Example:
Code:
anim    follow1 #Jump straight
    #idle 1
    #cancel 0 99 0 d f a freespecial11
    offset    23 121
    delay    25
    landframe    9
    bbox        6 7 44 92
    frame        data/chars/ryu/0004100000.gif
    offset        19 112
    delay    3
    bbox 2 4 46 84
    frame    data/chars/ryu/0004100001.gif
    offset    21 105
    bbox 2 2 46 64
    frame    data/chars/ryu/0004100002.gif
    offset    21 102
    bbox 2 2 48 56
    frame    data/chars/ryu/0004100003.gif
    offset    24 101
    delay    11
    bbox 6 2 46 54
    frame    data/chars/ryu/0004100004.gif
    offset    21 102
    delay    3
    frame    data/chars/ryu/0004100003.gif
    offset    21 105
    bbox 2 2 46 64
    frame    data/chars/ryu/0004100002.gif
    offset    19 112
    bbox 2 4 46 84
    frame    data/chars/ryu/0004100001.gif
    offset    23 121
    delay    100
    bbox 6 7 44 92
    frame    data/chars/ryu/0004100000.gif
    bbox 6 7 44 92
    delay    2
    @cmd jumpLand
    frame    data/chars/ryu/0004100000.gif
    frame    data/chars/ryu/0004100000.gif

I know that I can call a spawnscript but they aren't reusable like the @cmd function because I need to code a lot of separated spawnscripts and call each one according to a specific situation. In this case I use small in-line script, like the example below:
I wish I have it like that too. I think it only reads the ones that are from the main() function.
 
Can someone please explain the difference between:

changeentityproperty(self, "animation", animation_id);
vs
performattack(self, animation_id);

I tested both, and they worked fine, but I can't understand what the difference is between the two.

Thank you
 
Can someone please help me with the full example of this?

Code:
#define DO_SOMETHING  0
#define DO_SOMETHING_ELSE 1
#define DO_SOMETHING_DIFFERENT 2

switch(value)
{
   case DO_SOMETHING:
     // do something.

   break;
 
   case DO_SOMETHING_ELSE:
     // do something else.

   break;

   case DO_SOMETHING_DIFFERENT:
     // do something different than before.

   break;
}

You can (and should) use the #defined constant in your function call too, so now it looks like this:

Code:
@cmd my_doing_something_function DO_SOMETHING_ELSE


I can understand it, but I don't know how to set up the #define functions.
Thank you so much for your help
 
Back
Top Bottom