Locational Damage Fixes Part I - Ranged Attackers
In the process of repairing Locational Damage I plan to document a series of major changes to the mod. All credit should go to the original author Kahmul, whom absolutely put in much more work to starting and developing this mod. I cannot begin to describe the attention to detail put into this mod, indicative of long hours both designing and performing trial and error within the game. I have merely patched it with current tools. Additionally out of respect for the author I will not release the source code which was obtained through the use of a decompiler. In this first stage of changes we will deal with the efficiency of storing ranged attacker coordinates. Skip to the bottom if you just want to grab the download featuring the repairs in this post.
A major source of performance degradation in the original code lies within the design of its data storage routines. Specifically, the mod assumes that Arrays in Papyrus are performant in real-time. A simple array is below:
Form MyArray Event OnInit() MyArray = New Form EndEvent
For typical uses there is likely to be no noticeable performance detriment with Arrays in Papyrus. Typical uses include storing a range of static values with an emphasis on reading values over updating values. But Locational Damage does not use them typically. In this case the data changes in real-time with pushing and popping occurring in the worst case many times a second (large, outdoor battles).
Memory in Papyrus is not in complete control of the designer. Papyrus is a garbage collected language. This essentially means that the run-time is protected from certain reference exceptions and memory leaks. It takes the burden of managing objects in memory off the programmer and allows them to focus more on logic as opposed to efficiency. However, this comes at the cost of performance.
There is contemporary research into making garbage collection perform well in real-time. From two researchers at Vienna University of Technology:
Of course, GC does not come for free. It introduces memory and performance
overheads and may therefore make it necessary to use more expensive hardware for a given system. On the other hand, dynamic memory management increases programmer productivity and program safety. The low intrusiveness of the proposed GC mechanisms allows deciding on this trade-off without sacrificing
Garbage collection in Papyrus- a scripted and (primarily) single-threaded language- is likely to be outstandingly slow relative to the competition. It is for this reason that classic Papyrus Arrays should not be used for real-time purposes. This is an aspect of Locational Damage that is unfortunately an issue at scale. For small numbers of Actors this implementation will be fine, but for large battles it can slow to a crawl due to the limits of Papyrus. When archer actors attack, their reference (and some coordinate values) is stored in a global array. When actors are hit by a ranged attack, their reference is pulled from the array. Some pseudocode below:
Initialization: archers = Array event onArcherAttack(Me) archers.push_value(Me) EndEvent event onHit(Attacker) archers.pop_value(Attacker) then do calculations EndEvent
The solution to this is to store the required information on the Form reference itself as opposed to inside a global array. This is only possible through the use of a 3rd party tool known as PapyrusUtil. PapyrusUtil's storage module includes functions that get us ahead of this problem by leaps and bounds:
ObjKey: form to save on. Set none to save globally. KeyName: name of value. value: value to set to the given keys. If zero, empty, or none are given, the key will be unset. /; int function SetIntValue(Form ObjKey, string KeyName, int value) global native float function SetFloatValue(Form ObjKey, string KeyName, float value) global native string function SetStringValue(Form ObjKey, string KeyName, string value) global native
These functions allow us to set values directly on the Actor objects we are concerned with, in an effectively O(1) operation.
For a version of Locational Damage with this fix and a copy of its dependency, please go here. In the coming weeks the releases may be moved to GitHub for easier version reconciliation. Be aware that this is a test; install with the usual caution of alpha and beta mods! Create a saved game before installing this mod. Specifically there are some bugs with attacking at ranged while in sneak mode. There are still a few fixes on the way before Locational Damage should be considered truly stable.