PERFORMANCE SCRIPTING IN 2K3

How to write high performance 2k3 script code (LVL: Expert)

  • Fhizban
  • 06/19/2010 10:25 PM
  • 7847 views
The author is german, please excuse any english grammar errors

INTRODUCTION
Welcome to my first RMN tutorial,
Being a programmer myself I would like to share some of my knowledge with the community. Please pardon if this (and future) article(s) seem somewhat abstract, but I am working with sourcecode everyday - so the best I can give to this community in return is some advice here and there regarding scripts, performance and memory management in the 2k3. Please note that the secrets taught here also apply to the RPGMaker95, 2k as well as XP & VX to some degree. As some of you might already now - I focus only on the 2k3 (being oldschool), this is why the tutorial was written maker specific.

ABOUT PERFORMANCE
So, you have this super shiny game polished and ready. All is well, but after adding the first couple of events, forks and paralell processes the engine slows down. "Hell!" you might think at first "Is this all the RPGMaker can do? In a world of super-fast 3d enviroments this lousy program is completely outdated and not able to cope with higher scripting load?" The answer is both No and Yes: The RPGMaker is an outdated program, but like with all scripting languages, the focus lies on elegant scripts, performant scripts, scripts that lessen the burden of your game. This old matron may have come of age - but its someone to respect, and someone who is willing to share lots of wisdom (speak gameplay fun / maker experience) if treated right.

This is the point where performance comes in. Wich means you have to write "performant scripts". Performance compromises many levels: Its about your graphical resources, the sound effects, the size and waste of music files and so on. But mainly its about clean scripting style and not cluttering your game with too many events and paralell processes. Lets take a closer look to some do's and dont's about RPGMaker Performance.

1. REDUCE PARALLEL PROCESSES

A. The Situation
Assume we have a caterpillar script, a weather script, a hunger script and a day-and-night script in our game - and each of them uses a common parallel process . And you wonder why your game is slow? Thats 4 processes for the engine to handle every millisecond, I wonder how you would look with that workload!

B. The Solution
The answer is easy: Turn the 4 (or how many you have) processes into just one. But how do we do that exactly? First of all, you create one Main Parallel Process as a common event, from there you use Call Event commands to call all other scripts. Make a common event that is only triggered by call for your caterpillar script, your weather system and all the others. This reduces the number of processes from 4 to just 1 - it does not make your coding simpler, but it reduces the workload of the engine during gameplay.

C. The Summary
Keep the number of parallel processes low (just 1 at best) by converting various processes into call events and call them from a single parallel process. This parallel process functions as your "Main Game Loop".

2. REDUCE OVERHEAD WITH COMMON EVENTS

A. The Situation
The purpose of common events is to write a piece of code only once, and have it accessible from all over your project using a "Call Event" command. Imagine you want a exclamation mark to appear above the head of your player character, you use 1. a battle animation 2. a nice soundeffect maybe 3. you flash your player character sprite to spice things up a bit. Now, thats 3 commands you have to repeat all over every time you want that exclamation mark show up on your char. If you copy those commands into every possible script where you need it - it will just clutter the events of your project needlessly.

B. The Solution
The trick is to put the 3 commands into a common event and use the Call Event - it reduces the lines of code in your project. And one more, very important point: If you like to change how your exclamation mark code works, you only have to change the Common Event. Imagine you put those 3 commands in 100 different places of your project and then later on you want to change it: You would be forced to change it 100 times - wich soon turns into an awful job.

C. The Summary
Put all the "everyday code" and common sequences of commands you are using in your project into Common Events and use the Call Event command to access them from everywhere. It makes your project slim and enables you to change things on the fly.

3. DON'T BRANCH FORKS TOO OFTEN IN ONE GO

A. The Situation
Imagine you have a very complex situation in your game where there are 100 forked possibilities of script flow. The usual approach is to nest 100 Conditional Branches within each other - by placing a new Branch in the else handler of the last Branch. Not only does this lead to unreadable code - it also slows the engine down as the rpgmaker has to check all 99 branches before checking conditional branch number 100.

B. The Solution
As easy as nesting multiply conditional branches sounds at first - as clumsy it gets when you get used to this procedure in your projects. The solution is to divide your branches into "chunks" to make them more readable, more manageable and more performant. You do this by wrapping another set of branches around them, this time divided into steps of 10 (or any other number of your choice).

Lets say you have a variable with possible numbers from 1 to 100: Instead of nesting 100 conditional branches you first nest 10 conditional branches within each other. The first branch checks if the variable is <=10, the second checks if it is <=20 and so on, until you got a branch for each decade. Within those branches you check for every unit position. This splits up your checks into chunks of ten and subdivides them furthermore into subsections of ten.

This may sound more complicated, as it actually increases the number of forks you need - but it destresses the engine by subdividing the initial branching into 10 steps instead of 100. where the subsequent 10 follow up steps are only processed if necessary.

C. The Summary
Where ever you have a large tree of conditional branches, try to divide them into subsections. Check for the decade value of a variable in the outer branch (<=10, <=20, <=30, <=40 etc.) and then for the unit position in the inner branch (<=11, <=12, <=13, <=14, <=15 etc.) of the corresponding decade. It may sound horrible to script - but it speeds up your game.

4. DONT USE PARALLEL PROCESSES ON MAP EVENTS

A. The Situation
Imagine you have 50 events on your map and every one of them has a page set to "Parallel process". Once you start your game, you might wonder why the performance is so bad. The answer is simple: Handling so many processes every millisecond is just too much for the rpgmaker, depending on the complexity of your scripts.

B. The Solution
Dont Panic! You do not have to recode everything. At least not if you can live with the fact that the event processing runs a little bit slower than before. Here is the trick: Turn the parallel process on your map events into "triggered by action key" and just make sure that it cannot be triggered at all.

Now use a single Common event as Parallel process and call the event pages on your map from within that event. This way, you effectively exchange for example 50 parallel processes with just one.

You can even go one step further and add a counter variable to your Common Event wich you increase with every go. Use a fork to call the page of one single map event every time the parallel process runs, reset the variable once you updated all of your map events. This way only one map event is updated when the parallel process common event is called. This narrows done the processor power needed and speeds up your project significantly. The downturn is that it takes a few seconds up to a few minutes to update all the event pages of all your map objects when there are many!

C. The Summary
Never use parallel processes on map events. Use a single common event instead and call the event pages of your map events from there. Reduce the number of parallel processes at all costs.

5. HOWTO FRAGMENT BIG LOOPS

A. The Situation
Say you have a loop script command that repeats 100 times. Thats bad because it slows down event processing and means a lot of extra burden to your game.

B. The Solution
If you are not ultimatively dependent on the speed at wich your loop executes there is a neat solution by fragmenting your loop: Create a common event that should contain your loop code, and a counter variable. Use a single "Game Loop" parellel process (as mentioned earlier) to call your common event. Within the common event, increase your counter variable by +1 until it reaches 100, where you reset it back to 0. With each step your counter variable increases, you execute just one of the tasks you initially wanted to do.

Example:
Imagine you want to call Event page Number 1 of each of your 50 map events int a loop. Given that every map event has a page number 1 wich contains lots of code - this would completely lock your game down. Instead use the mentioned "game loop" common event as a parallel process. maintain a counter variable with you increase by 1 until it reaches 50, where it drops back to 1. each time your "game loop" event is processed, call page 1 of next event on your map, and only the next single one.

C. The Summary
Big loops with lots of instructions tend to paralyze the rpgmaker engine completely, instead use common events with increasing counter variables called by a single parallel process to distribute processing over a longer period of time to destress the engine.

CLOSURE

As you can see, there are many ways to speed up the RPGMaker, especially in older computer enviroments or when making games with lots of scripts and map events. This all may sound very abstract, but sooner or later you have to think about streamlining your project or future development will become more and more difficult. I for myself use the mentioned "Main Game Loop" Event in all of my games, sometimes I call it "Heartbeat". Its the first common event i set on parallel process and its always there. Using forks checking for switches and vars I call various common & map events from within the "Heartbeat". Its the only parallell process I use - in all of my games, and all of my maps.

This all sound way too geeky for you?

Just wait for my next article: "Hacking the RPGMaker made simple" or "How to broaden the horizon of 2k3"

Fhizban over and out

Posts

Pages: 1
LockeZ
I'd really like to get rid of LockeZ. His play style is way too unpredictable. He's always like this too. If he ran a country, he'd just kill and imprison people at random until crime stopped.
5958
This is great, I don't think my game's custom EXP system or custom encounter system cause lag but I should do this stuff anyway just in case it does on older computers.
This is exactly what I needed to read.

If only I knew those things before making Marvel Brothel...
Great tips, and well-organized! Thank you for writing this. My code has become cleaner and more efficient :D
Thank you for the comments so far!

There are a few more tips I would like to share, will edit them in later on

@Fallen-Griever @LockeZ

Yes, this tutorial is really focused on the older makers, with XP/VX performance is usually not an issue anymore
As well intended as this article was, I hate to say that most, if not all, of it is incorrect. This inaccuracy mostly has resulted from a failure to understand the overhead of events and how they execute. I will elaborate.

1) Reducing parallel processes

All these events are not calculation intensive, and what I mean by that is there is no need for them to execute *quickly*. The main reason for lag from events is the overhead of when an evident is actually called (more on this later). You can happily run about 40 linear parallel events by sticking a 0.0 wait on the end of them.

2) Reduce overhead with common events

I think what you mean is reduce code redundancy, which is definitely good practice, but it is not always the best solution when it comes to executing calculation-intensive events. Ironically what you have suggested, while reducing the size of the code, has increased the execution overhead of the event thats calling your common event.

When you call another event it has an overhead of 0.0165 seconds (the execution time of the 0.0s wait). It takes A HUUUUUUUUUGE event to cause that kind of overhead. By separating your code into a common event and calling it over and over you've made a huge overhead for your code. If you're aiming for speed, if your code is less than 100 instructions, you're better off copying and pasting rather than using common events like that because they have a huge overhead. Everytime you call a common event, even the same one, it increases the execution time of the parent event by however many times you called it. A parallel process that makes 6 common event calls takes 0.1155s to get through a single loop, thats hella slow (6*0.0165 + 1*0.0165 for the overhead of looping the parallel event on itself).

3) Don't branch forks too often in one go

Now this one is completely incorrect. I'm not sure why you think this works, but the truth is that rm2k3 runs every line, whether the logic dictates that line is executed or not. I know that in real programming languages this isn't the case, but in rm2k3 it is. You can test this by taking any giant linear event you have with a lot of if statements, and at the end of the event put a counter that increments a variable by 1. Test the variable after 1 second and you'll find whether parts of the code were deactivated with if statements or not, that the entire thing still took the same amount of time to execute.

4) How to fragment big loops

A loop script that loops 100 times, are you kidding me? Rm2k3 can execute a linear loop THOUSANDS of times in a blink of an eye. The issue is that unbeknownst to most the Loop command is FARRRR more quicker than how fast a parallel process loops. I ran a test, in 60 seconds an empty Loop executed 12003333 times, a parallel process only looped 3601. Thats how fricking insane Loop is. You get lag from loops because they execute at lightning speed, which is good sometimes, but if you have a continuous process running in the background rather than a once off calculation, then you'll want to slow it down, because chances are you don't need it executed that fast. Inside the loop you just need a 0.0s wait to bring the loop back down to the speed of a parallel process which is far more tame.

Ironically you solved this by accident, because as I said earlier, calling a common event has an overhead of 0.0165s (the same as a 0.0 wait), so thats why your solution works.

I'm writing up an article now that explains this information in greater detail and how I worked it out.
For the 50 events on one map, here's something you can try. Make these events common events, and put them all in one event (Worldmap). Doing this in most cases removes the lag, as it checks them separately from each map.

Sometimes this won't work but try this anyway (labels help to make sure it skips to the next). Also, remember End Event exits the common event, so if a Common Event is way too long, nest common events within each other, to speed certain slow sections up.

2) Reduce overhead with common events

I think what you mean is reduce code redundancy, which is definitely good practice, but it is not always the best solution when it comes to executing calculation-intensive events. Ironically what you have suggested, while reducing the size of the code, has increased the execution overhead of the event thats calling your common event.


He means overhead. Imagine having the same event on every map. Parallel process a common event, or outright make it switch activated. Not only did this just reduce thousands of hours of code edit if something goes wrong (you just edit the common event), but it actually does speed the execution up. I've got hundreds of common events.
author=bulmabriefs144
2) Reduce overhead with common events

I think what you mean is reduce code redundancy, which is definitely good practice, but it is not always the best solution when it comes to executing calculation-intensive events. Ironically what you have suggested, while reducing the size of the code, has increased the execution overhead of the event thats calling your common event.
He means overhead. Imagine having the same event on every map. Parallel process a common event, or outright make it switch activated. Not only did this just reduce thousands of hours of code edit if something goes wrong (you just edit the common event), but it actually does speed the execution up. I've got hundreds of common events.


Uhhh ... Thats called reducing redundancy. When you have redundant code it means you have code that does the same thing written in multiple places, when you could've put the code in one place and have everywhere reference that one place (in this case a common event), which is what you described but are calling the wrong thing. When you reduce overhead it means things run faster, which using "Call: Common Event" DOES NOT do.

I think you've misunderstood the distinction I was making between a parallel process and CALLING an event. When you have a loop and you put Call: Event, the code that loads the event will lag the loop. If you're doing stuff in a loop it'll run faster if the code is right there rather than off someone else in a different event. It can make things a little bit more difficult when you have to fix code, but if you absolutely want to push rm2k3 to the limit in terms of execution speed its a necessary evil.

I suggest you check out my full tutorial here: http://rpgmaker.net/tutorials/451/.
Pages: 1