How to script efficiently, clean up, and avoid bloat?

Post » Tue Feb 16, 2016 10:06 am

Hi all, I'm working on a gameplay mod and need advice on how to make it run efficiently and without leaving a bunch of debris all over peoples' saves and papyrus logs. The mod uses several scripted constant effect abilities on the player, including ones with multiple magic effects tied to conditions and cloaking spells to attach scripted abilities to nearby NPCs. Several of these use costly functions like registering updates and line of sight checks. I understand these are performance intensive features, but there's no way around them to achieve the results I want, so I at least want to make sure it runs cleanly despite this.

I've gleaned a few tips about clean scripting from cautionary posts like this (https://www.reddit.com/r/skyrimmods/comments/3y7ztv/so_what_do_you_guys_think_about_the_popular) reddit post, which warns against using several popular combat mods that attach scripted abilities to NPCs but fail to remove them, causing save bloat and other problems. However, most of the information I've found seems to be about how to fix these problems as a user rather than how to avoid them as a modder. The only resource I've found is this wii page (http://www.creationkit.com/Dynamically_Attaching_Scripts#References) that explains how to avoid the brawl bug, as well as a few other jargon-y forum forums like the above that describe what scripting practices cause problems, but not what to do to avoid them.

As such, I have a few questions about a handful of topics, if you have the answers to any one of them it would be much appreciated:


Adding Effects to NPCs by Cloak:

- If I want to attach magic effects to nearby NPCs using a cloak ability attached to the player, this wiki page (http://www.creationkit.com/Dynamically_Attaching_Scripts#Death_Dispel) recommends attaching this spell through a quest, then using a script running on the quest to toggle it on and off at regular intervals. How necessary is this? Are there notable performance disadvantages to simply leaving the cloak ability on the player constantly, or instead using conditions on the cloak effect to ensure that it's only active when the player draws a weapon or equips an item for example?


Cleaning Effects from NPCs via Dispel on Death Flag vs. OnDying() Event

- To clean up scripted effects added to actors by cloak, most forum posts seem to imply that you should never use the "Dispel on Death" flag on the effect form, and instead use an "OnDying()" event to point the script to an empty state. Is this necessary? How exactly does this prevent save bloat? Is there any specific reason to use "OnDying()" over "OnDeath()"? Should the same techique be used to remove an effect from an actor even if they're not dead, using OnUnload() or OnCellDetach() or something like that?


RegisterForUpdate()

- Many forums treat "RegisterForUpdate()" functions like the boogeyman, and seem to suggest that they're terrible for performance and can lead to suspended scripts and save bloat and eat your children. Are these concerns valid? Posts like this (http://www.gamesas.com/topic/1359724-on-the-run-time-of-skyrims-papyrus-scripts) suggest that registering an update commits a papyrus thread (I'm not entirely sure what that is) to attending to that update and proposes several ways to circumvent this by jumping between states. Is the concern about updates more with short update intervals eating up performance and potentially breaking, or is it just as resource-demanding to use a single update with a long interval, say several game hours instead of real seconds? Does using "SingleUpdate()" rectify some of these issues by preventing repeated calls to update if one is already queued?


If I've missed any source that covers these please let me know, and thanks for your help
User avatar
Stephanie Valentine
 
Posts: 3281
Joined: Wed Jun 28, 2006 2:09 pm

Post » Tue Feb 16, 2016 1:44 pm

First, I'll say I've never used this cloak spell technique to affect NPCs, but here's what I know about it.



Spells and their conditions perform their calculations every second. If you know that checking less frequently would achieve the same results then it's optimal if you can find a way to do that. Conditions are checked in the order they are listed so if you put conditions that are most likely to be false first you can sometimes optimize that way. Things like checking to see if the player has a weapon drawn or a specific item equipped are good examples, but remember that any extra checks you make will have to run every second so you don't want to add too many conditions for the cases when they all are true and the effect really should apply.



If you can afford to only apply the affect every 5 seconds or more then the cleanest alternative is to replace the cloaking spell with a quest and a set of aliases. You can use conditions an the quest's ReferenceAliases to select which nearby NPCs need to have the effect added instead of using the ApplyingSpell's conditions. You can simply add a script to the quest itself which goes through and assigns an ability to the NPCs in each of the filled aliases then waits for some amount of time and restarts the quest. (You can also attach a script to each alias that applies the ability to the character when the alias is filled if you prefer.) The quest and its aliases take over the role of the CloakAbility and ApplyingSpell from that example. You still need your MonitorAbility. If your particular case is one that only needs to be triggered periodically (like at the start of combat or when changing locations) you could potentially have the quest started and stopped by the Story Manager.



Scripts can persist after the object or effect they were attached to is gone. If the script is busy running a function or is registered for an event when the active magic effect ends then the script can become orphaned. The OnDying() event gives your script a chance to realize that the NPC no longer needs to be monitored, OnDeath() would be too late. The general principle here is to avoid having the script registered for events or continuing to run functions when there's no possibility that something useful can happen. Generally effects are supposed to clean up registrations automatically but if you were in the middle of an update event when the effect ends you could potentially re-register an event even though there's no reason. By switching your script to a different state you can ensure that even if you did reregister you won't make that mistake a second time.



The worst problems with RegisterForUpdate are certainly those that involve setting the update frequency too low. If you ever get in a situation where the update event is happening faster than the code in the event can finish running you'll break the game. As long as your code in the OnUpdate event is sure to complete well before the next event fires there won't be a performance problem.



RegisterForUpdate basically works like this:



Event OnUpdate()
RegisterForSingleUpdate(1.0)
; Do stuff here but the one second countdown has already begun.
EndEvent

The better way to write your code is to explicitly use RegisterForSingleUpdate at the end of your update event like this:



Event OnUpdate()
; Do stuff here and take as long as you need
RegisterForSingleUpdate(1.0) ; after a pause of one second we'll do stuff again.
EndEvent

The first version will call OnUpdate every second regardless of how long the code inside runs. The second will run OnUpdate with a one second delay between each call.



If you do use RegisterForUpdate you want to make absolutely sure you unregister it when it's no longer needed. And if someone were to uninstall your mod (which they absolutely should not do, but some people will anyway) the script would still be registered for the event even though the script is now missing. (SKSE adds a way to clean up those orphaned reservations, but it's always better to avoid having them.) If you use RegisterForSingleUpdate the worst that happens is that the script isn't found and the event fails once.

User avatar
YO MAma
 
Posts: 3321
Joined: Thu Dec 21, 2006 8:24 am


Return to V - Skyrim