User:DavidJCobb/Stack dumping

< User:DavidJCobb
Revision as of 02:03, 2 March 2015 by imported>DavidJCobb (→‎Overview)

Stack dumping is a Papyrus event that can cause unpredictable and potentially severe errors in a player's savegame. My understanding of the phenomenon is as follows:

Overview

Whenever the game engine calls a function, a call stack is generated. When that function calls another function, the callee is added to the caller's call stack. Papyrus can only have a certain number of call stacks running at a time; if too many stacks accumulate, some may be suspended (paused). Stacks may also be suspended for other reasons:

  • A function that calls Utility.Wait(n) will suspend its call stack for n seconds.
  • If a function tries to access a resource that another script is currently accessing (a "shared resource"), the function will be suspended until the resource becomes available.
    • One common example of a shared resource is the player object.
  • Some events' handlers cannot run concurrently with themselves. If the event occurs several times in a short period, only one call to its event handler will run immediately; the other calls will exist as suspended stacks.

If too many suspended stacks accumulate, the Papyrus engine will not be able to hold them all; suspended stacks will be selected at random and discarded, and information about those stacks will be printed to the Papyrus logs, prefixed by this message:

Suspended stack count is over our warning threshold, dumping stacks:

This means that if a mod has a problem that leads to stack dumping, that mod will interfere with itself, with other mods, and with Bethesda-authored scripts (because Papyrus doesn't care where the problem is coming from; it'll dump as many stacks as it can, indiscriminately, until it feels safe). The problem will have unpredictable consequences, which may or may not break other content or otherwise lead to save corruption. As a modder, the best approach to take here is the maximally cautious one, because even if a stack dumping issue doesn't wreck your savegame, it could wreck someone else's.

Specific methods to minimize stack dumping

  • There are a number of events that can rapidly generate suspended stacks, because they can occur several times in a short period (such that several calls to your event handlers are queued). If you must listen to these events, then minimize the length of your event handlers, so that the resulting stacks are executed as quickly as possible.
    • One way to do this is to have the event handlers RegisterForSingleUpdate(n), and do your actual processing in an OnUpdate() event.
      • OnUpdate() is called by the game engine. Using it will generate a new call stack.
      • If x events occur within n seconds of each other, you will process all of those events just one time.
    • Don't use Utility.Wait() in these event handlers. It will by definition make your event handlers take longer to execute.

Events that can trigger large numbers of suspended stacks

If you try to do advanced processing within these events, you're gonna have a bad time. In a worst-case scenario, the player will rapidly (or even instantly) generate more suspended stacks than Papyrus can handle, leading to stack dumping.

  • OnHit can generate a large number of suspended stacks, because the event is duplicated if the victim is hit with an enchanted weapon.
    • Consider the case of an encounter between a player and five bandits, each of which takes four to seven hits to kill, and each of which has an OnHit event handler. Without an enchanted weapon, the player will generate 20 to 35 OnHit events to kill the bandits. If the player's weapon has two enchantments, she will generate 60 to 105 OnHit events. Meanwhile, the bandits will generate many OnHit events on the player (who can heal herself). The problem can quickly balloon out of control.
      • Incidentally, there's an area in Pinewatch where you can end up fighting five or six bandits all at once. It's also possible to obtain a sword with two enchantments very early in the game, courtesy of Meridia.
  • OnItemAdded and OnItemRemoved can generate a large number of suspended stacks if no inventory event filter is used. The player can transfer large masses of items (e.g. in their home), for example.
    • Even batch operations, like RemoveAllItems(), can cause stack dumping. One stack is generated for every type of item transferred, and inventory events can't run concurrently with themselves, so all but one of those stacks will be suspended the instant it is created.
      • Friendly reminder: built-in, Bethesda-authored content calls RemoveAllItems() on the player. Diplomatic Immunity, The Forsworn Conspiracy, The Mind of Madness, Restoration Ritual Spell, and Unbound all remove the player's inventory at some point.
    • For OnItemAdded and OnItemRemoved, I list some additional methods for avoiding stack dumping here.

Things to investigate

How do update events work?

  • Possibility 1: when you call RegisterForSingleUpdate(x), Skyrim queues an update event to fire in x seconds. No new stacks are created.
  • Possibility 2: when you call RegisterForSingleUpdate(x), Skyrim immediately creates a call stack for OnUpdate, and suspends it for x seconds.
    • If this one is the case, then you would not be able to use update events to minimize generated stacks.
  • Test procedure: RegisterForSingleUpdate(20.0), and then DumpPapyrusStacks using the debug console. See if an OnUpdate stack is dumped.