Jump to content

Multithreading Help


ScottyDoesKnow

Recommended Posts

So I've created a mod that makes Fists of Steel work based on calculated armor rating instead of base armor rating. It uses a script attached to the player actor. Unfortunately it takes about 1 1/4 seconds to run, so it can easily run multiple times at once, which screws up the calculations. So I'm now using a combination of versioning and a spin lock on the critical section to protect it - http://www.creationkit.com/Threading_Notes_(Papyrus). But it can still stack up a whole bunch of runs of the code because it takes so long to complete, which I don't like. The only thing I've thought of is to constantly check the versioning after every couple lines so I can return as soon as a new run starts, but that's hideously ugly. So, basically just looking for advice if anyone with more experience with papyrus has any. The main method in question:

 

Function UpdateFistsOfSteelBonus()

  float fTimeStart = Utility.GetCurrentRealTime()

  Version = (Version + 1) % 1000000
  int iLocalVersion = Version

  FUDGE_FACTOR = 0.0475 ; 0.0
  SKILL_FACTOR = 0.4
  ANCIENT_KNOWLEDGE_FACTOR = 0.2 ; 0.25
  WELL_FITTED_FACTOR = 0.25
  MATCHING_SET_FACTOR = 0.25
  JUGGERNAUT_FACTOR = 0.2

  USE_QUALITY_BONUS = true

  SHOW_TRACE = false
  SHOW_TRACE_VERBOSE = SHOW_TRACE && false
  SHOW_NOTIFICATION = false

  Actor akPlayer = Game.GetPlayer()
  Armor akGauntlets = akPlayer.GetWornForm(Armor.GetMaskForSlot(33) + Armor.GetMaskForSlot(34)) as Armor
  Armor akBoots = akPlayer.GetWornForm(Armor.GetMaskForSlot(37)) as Armor
  Armor akCuirass = akPlayer.GetWornForm(Armor.GetMaskForSlot(32)) as Armor
  Armor akHelmet = akPlayer.GetWornForm(Armor.GetMaskForSlot(30) + Armor.GetMaskForSlot(31)) as Armor

  Debug.TraceConditional("~ FistsOfSteelFixed ~", SHOW_TRACE_VERBOSE)

  int iOldBonus = GetOldFistsOfSteelBonus(akPlayer)
  int iBonus = GetFistsOfSteelBonus(akPlayer, akGauntlets, akBoots, akCuirass, akHelmet)
  int iDelta = iBonus - iOldBonus - AppliedBonus

  if iLocalVersion == Version

    if Locked
      Debug.TraceConditional("~ FistsOfSteelFixed ~ LOCK IN", SHOW_TRACE_VERBOSE)
      while Locked
        Utility.WaitMenuMode(1.0)
      endWhile
      Debug.TraceConditional("~ FistsOfSteelFixed ~ LOCK OUT", SHOW_TRACE_VERBOSE)
    endIf

    Locked = true

    akPlayer.ModAV("UnarmedDamage", iDelta)
    AppliedBonus = iBonus - iOldBonus

    Debug.TraceConditional("~ FistsOfSteelFixed ~ Old Bonus: " + iOldBonus, SHOW_TRACE_VERBOSE)
    Debug.TraceConditional("~ FistsOfSteelFixed ~ Bonus: " + iBonus, SHOW_TRACE_VERBOSE || (SHOW_TRACE && iBonus != 0))
    Debug.TraceConditional("~ FistsOfSteelFixed ~ Delta: " + iDelta, SHOW_TRACE_VERBOSE)
    Debug.TraceConditional("~ FistsOfSteelFixed ~ Applied Bonus: " + AppliedBonus, SHOW_TRACE_VERBOSE)

    if SHOW_NOTIFICATION && iBonus != 0
      Debug.Notification("Fists of Steel Bonus: " + iBonus)
    endIf

    Locked = false

  else
    Debug.TraceConditional("~ FistsOfSteelFixed ~ LOCK SKIPPED", SHOW_TRACE_VERBOSE)
  endIf

  float fTimeEnd = Utility.GetCurrentRealTime()

  Debug.TraceConditional("~ FistsOfSteelFixed ~ " + (fTimeEnd - fTimeStart) + " seconds", SHOW_TRACE)
  Debug.TraceConditional("~ FistsOfSteelFixed ~", SHOW_TRACE_VERBOSE)

  Debug.Notification((fTimeEnd - fTimeStart) + " seconds")

endFunction
Edited by ScottyDoesKnow
Link to comment
Share on other sites

Some general comments:

 

It takes time to instantiate variables and assign values to them. All of your *_FACTOR, USE_QUALITY_BONUS, and SHOW_* variables take time to create, and I would suggest moving them outside of this function; in fact, since these obviously only change when you go in and manually change them, I would make them script properties, and handle assigning their values in the CK.

 

Calling functions takes time. Notably Game.GetPlayer() has been noted to take quite some time itself; a much more efficient way of getting the player is to use a property:

 

Actor Property PlayerREF Auto

That handily auto-fills with the player in the CK, and by some benchmarks is several orders of magnitude faster. Additionally, your Armor.GetMask() calls are going to be constant, so it would behoove you to set up script properties for those as well, and use a run-once initialization routine (the event OnInit is perfect for this) to populate them with their values.

 

Your various armor variables will only change when the player dons something else; make them script properties and use the OnObjectEquipped and OnObjectUnequipped events on the player to update them only when they will conceivably change. Ditto your calculation functions for iBonus and iOldBonus. Actually, this whole function could be placed into these events, to only run when these values could change (you don't mention how it's called).

 

Debug.TraceConditional() takes some time, too -- potentially a lot of time, as branching logic is an expensive operation, but file I/O is even more expensive! -- so try commenting those lines out (not merely setting your variables to false, but actually commenting them out) and see what happens to your script execution time.

 

If you use SetAV instead of ModAV to set the unarmed damage bonus, you save yourself quite a few calculations -- I'm assuming iBonus is what the damage bonus should be, so just apply that via SetAV rather than going through and calculating the old bonus, the delta, etc. This cuts out an entire function call, which is another expensive operation.

 

I really can't tell what you're doing with Version, frankly, but I'm not sure it's really helping your cause here at all...

Link to comment
Share on other sites

Thanks for the help, but none of those things made much if any difference. I didn't try SetAV, it would mean I have to take into account enchantment and khajit bonuses and include those, using ModAV lets me only deal with Fists of Steel.

 

I did manage to take the method down to 0.1s when unequipping (no bonus calculation) and 1s when equipping from optimizing a couple things. It's really the HasKeyword and WornHasKeyword calls elsewhere that take all the time.

 

But either way, I was mainly asking about the multithreading stuff since I knew the efficiency couldn't be improved too much. What the versioning does is basically "cancel" all threads but the most recent. So if I begin a calculation in thread A and then a new one starts in thread B, thread A will check if its version is the same as localVersion and it won't be anymore. Unfortunately the vast majority of the time is spent in GetFistsOfSteelBonus, so it doesn't cancel much, just prevents the value being set a bunch of times. What I need to do is put a bunch of those checks in the long running methods so it actually stops before wasting too much time. The downside is that it's damn ugly, but I can't think of much else to do.

Link to comment
Share on other sites

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...