Customize script via new code or ExecuteScript: Which is more efficient?

Two approaches are under consideration for some event scripts that are executed by objects in the game. As background, most of the scripts will be the same. But, some objects need extra custom behavior. To accommodate the custom behavior, the first approach writes the extra “custom” code into a new script for the object that needs it and then sets that script as the event handler. The second approach puts just the custom code that needs to run for that object into its own small script which is saved and the script name is set as a local string on the object. Then, the “main” version of the event script grabs the string and uses ExecuteScript to call the custom script.

IMO, the second system is tidier and easier to maintain. Any changes or improvements to the main script are easy to propagate and there is no need to add those changes to a dozen customized versions of that scripts.

But, I am concerned about whether there is a performance hit from calling ExecuteScript. Is there and is it significant? I would expect that there is some extra overhead, but it might be really tiny. Keep in mind that both approaches would be used to do the same thing.

Don’t worry about the overhead. Unless you’re doing something really insane (you’d know if you were), it won’t be noticeable, so just go with whatever is easier for you to work with.

4 Likes

Yes. Any overhead is tiny. And compared to the cleaner code, better modularity and easier maintenance it’s nothing. I highly recommend the ExecuteScript approach.

1 Like

Thank you both for that! That was my instinct, but I hesitate to start down a road that would end up increasing lag.

If the execution of a script is too long for the computer, NWN2 shut down the process.

I favorise ExecuteScript on the module object or the area object beceause they are very stable object.

If you execute a script on a dead object or a destroyed object, the script won ’ t be run.

True, but I’d consider that case, executing scripts on destroyed object and expecting results, to be a bug.

As too TMI issues, that will happen either way. But an ExecuteScript setup often lends itself easily to a simple DelayCommand(0.0, ExecuteScript…) fix.

1 Like

The scripts I am considering are primarily for areas, though I can see how it would be worth double checking such a system run on an object that might disappear. I suppose that’s what GetIsObjectValid() is for. :wink:

Regarding the TMI issue, does a script that calls another via ExecuteScript have that child script’s instructions counted with its own, when considering total number of instructions against the TMI limit? Or, does the limit apply to each of them separately?

Yes. Scripts executed directly from others count against TMI. As I said you can often break that cycle with a 0.0 time delay as long as you aren’t doing something that has to happen in sequence with the rest of the script.

Also, there is a limit of 7 or 8 calls deep that you can go doing ExecuteScript that in turn ExecuteScripts etc. When you hit that it simply doesn’t run the next script once it gets as deep as possible. No error or anything. You need a lot of chained systems to get into this state.

2 Likes

Note that if you use DelayCommand(), some event-related functions may not work. For example, GetPCChatSpeaker() will not successfully return an object when DelayCommand()ed. Others, like GetEnteringObject(), will.

1 Like

Actually, I think it’s just a matter of whether those values are overwritten by the next one. The module has a variable like oidLastPCSpeaker, and the area has oidLastEntered. These are set just before the main event handler script is run, and because NWN is single threaded they are definitely valid. However, if you delay the command, then call GetPCChatSpeaker(), it will return whatever is in that variable, which might not be what you expect it to be. In some cases, those oid (Object ID) variables will be reset to OBJECT_INVALID as well.

The note is spot on though - if you are using DelayCommand(), be sure to pass all the important data as local variables.

EDIT: And with regards to TMI, you can change the limit in 1.74 through an .ini file.

2 Likes

Agreed. You do still need to write your code right :slight_smile:

It really depends where the original script runs and how many following scripts are executed.

Generally, it shouldn’t be an issue, however in certain cases it can be.

Case 1: PRC’s event based scripting. The awesome idea and I was extensively using it at one point - made scripting much easier. However, it soon become apparent it is slow once you had 10+ scripts “registered” to an event. So while such system can allow to make adding/removing prestige classes completely modular, once you have more of them you will soon see disadvantages.

Case 2: spells can be another such situation where you don’t want to use this. Depends where, but for example PRC uses execute script inside multiple spellscript functions such as MaximizeOrEmpower, MyResistSpell etc. while calling a script for some wrapper for GetCasterLevel is not a problem, calling a script for MyResistSpell might be because unlike the first case, the function MyResistSpell can be called 50+ times per spell if the spell is aoe and there are multiple enemies. PRC spellscripts are slow because of this. It is noticeable in big fights. Or it was on yesterday’s hardware :slight_smile: (it was quite a few years ago…).

Case 3: heartbeats. I wouldn’t add execute scripts into heartbeats scripts.

1 Like