Jump to content

Overhauling the weapon rack scripts


Sclerocephalus

Recommended Posts

I did a simple test; I added "ItemToPlace=None" at the top of PlaceItem(), to see if the error would cause the thread to be aborted. It did not. Instead, I got this set of errors exactly once per frame draw, or ~60 times per second:

[10/21/2013 - 03:08:04PM] warning: Assigning None to a non-object variable named "::temp23"
stack:
	[ (0901E610)].WeaponRackActivateScript.PlaceItem() - "WeaponRackActivateScript.psc" Line 238
	[ (0901E610)].WeaponRackActivateScript.HandleStartingItem() - "WeaponRackActivateScript.psc" Line 333
	[ (0901E610)].WeaponRackActivateScript.InitActivator() - "WeaponRackActivateScript.psc" Line 106
	[ (0901E610)].WeaponRackActivateScript.OnCellAttach() - "WeaponRackActivateScript.psc" Line 64
[10/21/2013 - 03:08:05PM] error: Cannot call Is3DLoaded() on a None object, aborting function call
stack:
	[ (0901B363)].WeaponRackActivateScript.PlaceItem() - "WeaponRackActivateScript.psc" Line 238
	[ (0901B363)].WeaponRackActivateScript.HandleStartingItem() - "WeaponRackActivateScript.psc" Line 325
	[ (0901B363)].WeaponRackActivateScript.InitActivator() - "WeaponRackActivateScript.psc" Line 106
	[ (0901B363)].WeaponRackActivateScript.OnCellAttach() - "WeaponRackActivateScript.psc" Line 64

So I definitely think the loop needs a sanity-check failsafe.

Link to comment
Share on other sites

Most of the time, it should be false (default settings of the "havokOnActivate" and "havokOnZKey" properties are "false", meaning that it won't start havoking when grabbed by the player).

So I guess the next question is, for every item that has this script attached, what are those properties set to on the item? Meanwhile, havokOnHit==TRUE by default at least, though that probably doesn't do much good in this case.

 

EDIT: In the case of Zephyr at least, it looks to me like its reference in DLC1Arkngthamz01 attaches the script, and all three havokOn* properties are False. I can't yet find where (or if) that script is attached to Auriel's Bow.

Link to comment
Share on other sites

Seems like defaultDisableHavokOnLoad has become a candidate for completely disabling whatever it does once the item has been picked up by the player. Dropping it after that point should utterly ignore any havok functions the thing wants to do since the player is not expecting to have the item dangling in front of their face in mid-air at that point.

Link to comment
Share on other sites

I did a simple test; I added "ItemToPlace=None" at the top of PlaceItem(), to see if the error would cause the thread to be aborted. It did not. Instead, I got this set of errors exactly once per frame draw, or ~60 times per second:

[10/21/2013 - 03:08:04PM] warning: Assigning None to a non-object variable named "::temp23"
stack:
	[ (0901E610)].WeaponRackActivateScript.PlaceItem() - "WeaponRackActivateScript.psc" Line 238
	[ (0901E610)].WeaponRackActivateScript.HandleStartingItem() - "WeaponRackActivateScript.psc" Line 333
	[ (0901E610)].WeaponRackActivateScript.InitActivator() - "WeaponRackActivateScript.psc" Line 106
	[ (0901E610)].WeaponRackActivateScript.OnCellAttach() - "WeaponRackActivateScript.psc" Line 64
[10/21/2013 - 03:08:05PM] error: Cannot call Is3DLoaded() on a None object, aborting function call
stack:
	[ (0901B363)].WeaponRackActivateScript.PlaceItem() - "WeaponRackActivateScript.psc" Line 238
	[ (0901B363)].WeaponRackActivateScript.HandleStartingItem() - "WeaponRackActivateScript.psc" Line 325
	[ (0901B363)].WeaponRackActivateScript.InitActivator() - "WeaponRackActivateScript.psc" Line 106
	[ (0901B363)].WeaponRackActivateScript.OnCellAttach() - "WeaponRackActivateScript.psc" Line 64

So I definitely think the loop needs a sanity-check failsafe.

 

Sorry to say, but you are still mixing something up here:

 

The loop doesn't check whether or not the reference is none; it is supposed to check whether the 3D is loaded (whether or not an object reference's 3D is loaded has basically nothing to do with whether or not its reference is "none", i.e. when 3D loads, the reference doesn't change in any way). When the reference to check is "none", not only the loop will fail, but so does every command the script tries to run on that reference.

 

Tell me why the loop fails, when every other command that was run previously on this very reference (in the RunPlayerActivation and HandlePlayerItem functions) did not. It somehow got lost while being handed over form one function to the other - and this is what has to be investigated. Modifying the loop in whatever way (which, BTW, means to abort the placement procedure altogether) will not solve this underlying problem.

 

In normal operating conditions, references don't get lost for no obvious reason. Period.

 

The 3D, whether it's loaded or not, doesn't alter the reference

Link to comment
Share on other sites

Sorry to say, but you are still mixing something up here:

 

The loop doesn't check whether or not the reference is none; it is supposed to check whether the 3D is loaded (whether or not an object reference's 3D is loaded has basically nothing to do with whether or not its reference is "none", i.e. when 3D loads, the reference doesn't change in any way). When the reference to check is "none", not only the loop will fail, but so does every command the script tries to run on that reference.

 

Tell me why the loop fails, when every other command that was run previously on this very reference (in the RunPlayerActivation and HandlePlayerItem functions) did not. It somehow got lost while being handed over form one function to the other - and this is what has to be investigated. Modifying the loop in whatever way (which, BTW, means to abort the placement procedure altogether) will not solve this underlying problem.

 

In normal operating conditions, references don't get lost for no obvious reason. Period.

 

The 3D, whether it's loaded or not, doesn't alter the reference

You're right, the loop doesn't check whether or not the reference is None, and it doesn't "fail" -- that's exactly the problem! If the reference becomes invalid somehow, so that it evaluates to None, that causes the loop to never break. <None>.Is3DLoaded() is not a valid function call, so the engine throws the error and then -- this is the important part -- does NOT stop the thread or throw away the stack frame. It appears to just fill in None or False or <undefined> or who knows what as the result of that failed function call, with the result that the loop is checking "While !None ; Wait(0.1) ; EndWhile". You can see why that is a problem.

 

I'm not going to argue about whether references getting lost is "normal operating conditions" or not, because it doesn't matter. Operating conditions are not always normal in Skyrim, so scripts should be defensive and fail gracefully when necessary. Going into an infinite "While True" loop with no break and no exit is not a graceful failure, especially when every iteration of that loop throws 12 lines into the script error log.

 

All that is needed is for the loop to break if ItemToPlace==None. Obviously in that situation the item can not be placed, but that's better than the thread just spinning its wheels til the end of time.

Link to comment
Share on other sites

So I guess the next question is, for every item that has this script attached, what are those properties set to on the item? Meanwhile, havokOnHit==TRUE by default at least, though that probably doesn't do much good in this case.

 

Stop!

 

You're going to leave the trail of causal logics here, because we have no proof whatsoever of the DefaultDisableHavokOnLoadScript being responsible for the lost references. There are some indications that it will do stuff that is counter-productive to what the weapon rack scripts are supposed to do, but this may or may not be related to your problem.

 

 

EDIT: In the case of Zephyr at least, it looks to me like its reference in DLC1Arkngthamz01 attaches the script, and all three havokOn* properties are False. I can't yet find where (or if) that script is attached to Auriel's Bow.

 

Zephyr is an enterely different problem. Read up on the transient references.

 

Or did you try to place Zephyr, when those errors started to show up ?

Link to comment
Share on other sites

Stop!

 

You're going to leave the trail of causal logics here, because we have no proof whatsoever of the DefaultDisableHavokOnLoadScript being responsible for the lost references. There are some indications that it will do stuff that is counter-productive to what the weapon rack scripts are supposed to do, but this may or may not be related to your problem.

 

Now now, no need to get snippy. You brought up that script so I figured I'd help do a little research for you, I never said it was responsible for lost references or even in any way related to my infinite loop problem. But, now that you mention it,

 

Or did you try to place Zephyr, when those errors started to show up ?

 

Yes, I may have. I remember trying to put some bow on a stand-up rack and it just clattered to the ground like I'd dropped it, so I picked it up again and figured bows can't go on racks. It was awhile later that I discovered the threads looping in my save file and made the connection that the bow on the rack might have been when that started. I'll see if I can reproduce the loop with Zephyr on a new save file.

Link to comment
Share on other sites

You're right, the loop doesn't check whether or not the reference is None, and it doesn't "fail" -- that's exactly the problem! If the reference becomes invalid somehow, so that it evaluates to None, that causes the loop to never break. <None>.Is3DLoaded() is not a valid function call, so the engine throws the error and then -- this is the important part -- does NOT stop the thread or throw away the stack frame. It appears to just fill in None or False or <undefined> or who knows what as the result of that failed function call, with the result that the loop is checking "While !None ; Wait(0.1) ; EndWhile". You can see why that is a problem.

 

I'm not going to argue about whether references getting lost is "normal operating conditions" or not, because it doesn't matter. Operating conditions are not always normal in Skyrim, so scripts should be defensive and fail gracefully when necessary. Going into an infinite "While True" loop with no break and no exit is not a graceful failure, especially when every iteration of that loop throws 12 lines into the script error log.

 

All that is needed is for the loop to break if ItemToPlace==None. Obviously in that situation the item can not be placed, but that's better than the thread just spinning its wheels til the end of time.

 

I fully understand your point, and I am more than willing to help you with your problem.

 

Though, I cannot do anything in case a transient reference slipped through. A transient reference cannot be handled by papyrus. It does behave like a none, but it isn't none (sic!). An "If ItemToPlace == None" check will always return false, and any subsequent commands running on it will still fail with the notorious "none" error message. If this is what happened, I could add hundreds of safety checks and the script would still fail with the same salvo of messages. Therefore, I have to find out where this reference is generated and conceive a workaround to prevent this from happening, because only this will solve the issue.

 

Again, check the link and read the summary. It's a short post.

Link to comment
Share on other sites

I can confirm, trying to place Zephyr on a weapon rack does lead to the infinite loop in PlaceItem(). However, it has to be the particular Zephyr which is pre-placed; if I use

coc DLC1Arkngthamz01
tcl

and pick up the original, that one will bug out if placed on a rack. If I give myself a fresh copy with

player.additem 0200cfb6 1

that one works just fine. I won't conjecture on whether that's because of the disableHavok script that is attached by the world-placed reference, or something else.

 

Meanwhile, I get your point that ItemToPlace==None won't catch transient references, but as an absolute worst case, you could always just put a cycle limiter on that loop just like the one you put in your hotfix. If the object's 3D hasn't loaded after 100 tries, it's not going to, so the placement should bail out anyway.

Link to comment
Share on other sites

I am also able to confirm that Zephyr can't be placed, however rather than it entering an infinite loop and running up mountains of log spam, the game simply told me that it can't be placed on the rack. No matter how many times I try to sit there and spam the rack to accept it, it just keeps telling me no.

 

In a bit of true strangeness though, somewhere along the way the reference to the object (0200cfc2) was deleted by the game. I don't know how I managed it, but I was able to get it to reconstitute itself, pick it up (now marked stolen, sheesh) and used that for the testing. Apparently it's still a persistent reference, which to me validates that the code to look for those and reject attempts at placing them does in fact work. Also explains why searching all my player homes came up empty looking for a weapon I know I didn't just toss aside.... maybe it was lost in the ether of a bad weapon rack eons ago ?

Link to comment
Share on other sites

A transient reference cannot be handled by papyrus. It does behave like a none, but it isn't none (sic!). An "If ItemToPlace == None" check will always return false, and any subsequent commands running on it will still fail with the notorious "none" error message. If this is what happened, I could add hundreds of safety checks and the script would still fail with the same salvo of messages. Therefore, I have to find out where this reference is generated and conceive a workaround to prevent this from happening, because only this will solve the issue.

 

Again, check the link and read the summary. It's a short post.

 

So, papyrus cannot "handle" transient references, but I'm not sure that it cannot detect them. What about ItemToPlace.GetBaseObject() ? For any valid item the player is placing, the ObjectReference that goes into PlaceItem() ought to always return *something*. But if it's a transient reference, we get "no native object bound ..." and, when the call fails, it behaves like it returned None. Does that allow you to detect the bad reference?

 

EDIT: it seems to work okay for me; I added this at the top of PlaceItem():

	If ItemToPlace.GetBaseObject()
		Debug.Trace(self+": normal reference: "+ItemToPlace)
	Else
		Debug.Trace(self+": transient reference: "+ItemToPlace)
		Return
	EndIf

and then tried placing an Iron Sword on a rack, followed by the problematic world-placed Zephyr:

[10/21/2013 - 04:56:39PM] [WeaponRackActivateScript < (09014188)>]: normal reference: [ObjectReference < (FF000DA3)>]
[10/21/2013 - 04:56:46PM] warning: Assigning None to a non-object variable named "::temp55"
stack:
[ (09014185)].WeaponRackActivateScript.LoadsInSameCell() - "WeaponRackActivateScript.psc" Line 431
[ (09014185)].WeaponRackActivateScript.PlaceItem() - "WeaponRackActivateScript.psc" Line 235
[ (09014185)].WeaponRackActivateScript.HandlePlayerItem() - "WeaponRackActivateScript.psc" Line 301
[ (09014185)].WeaponRackActivateScript.RunPlayerActivation() - "WeaponRackActivateScript.psc" Line 173
[ (09014185)].WeaponRackActivateScript.OnActivate() - "WeaponRackActivateScript.psc" Line 80
[10/21/2013 - 04:56:46PM] error: Unable to call GetBaseObject - no native object bound to the script object, or object is of incorrect type
stack:
[Item 1 in container  (00000014)].defaultDisableHavokOnLoad.GetBaseObject() - "<native>" Line ?
[ (09014185)].WeaponRackActivateScript.PlaceItem() - "WeaponRackActivateScript.psc" Line 238
[ (09014185)].WeaponRackActivateScript.HandlePlayerItem() - "WeaponRackActivateScript.psc" Line 301
[ (09014185)].WeaponRackActivateScript.RunPlayerActivation() - "WeaponRackActivateScript.psc" Line 173
[ (09014185)].WeaponRackActivateScript.OnActivate() - "WeaponRackActivateScript.psc" Line 80
[10/21/2013 - 04:56:46PM] [WeaponRackActivateScript < (09014185)>]: transient reference: [defaultDisableHavokOnLoad <Item 1 in container (00000014)>]
Edited by taleden
Link to comment
Share on other sites

I am also able to confirm that Zephyr can't be placed, however rather than it entering an infinite loop and running up mountains of log spam, the game simply told me that it can't be placed on the rack. No matter how many times I try to sit there and spam the rack to accept it, it just keeps telling me no.

 

In a bit of true strangeness though, somewhere along the way the reference to the object (0200cfc2) was deleted by the game. I don't know how I managed it, but I was able to get it to reconstitute itself, pick it up (now marked stolen, sheesh) and used that for the testing. Apparently it's still a persistent reference, which to me validates that the code to look for those and reject attempts at placing them does in fact work. Also explains why searching all my player homes came up empty looking for a weapon I know I didn't just toss aside.... maybe it was lost in the ether of a bad weapon rack eons ago ?

 

What version of the patch are you using? I haven't tried the 2.0 beta (if I even have access to download it), so I've been testing with 1.3.3c and "Activator Hotfix for USKP 1.3.3c". I disabled everything from the load order except the base ESMs, the three main DLCs (not the texture pack), and the four unofficial patches. With those, when I grab the world-placed Zephyr out of Arkngthamz and try to put it on a rack, I get no error message, the item just disappears from my inventory and does not appear on the rack, or anywhere as far as I can tell, it's just gone. The rack itself also can no longer be activated after that, and my log starts filling up with that infinite loop on every frame.

Link to comment
Share on other sites

Now now, no need to get snippy. You brought up that script so I figured I'd help do a little research for you, I never said it was responsible for lost references or even in any way related to my infinite loop problem.

 

Sorry for leaving this impression; this wasn't deliberately. I only wanted to prevent the discussion from spreading uncontrollably.

 

As far as the DefaultDisableHavokOnLoad script is concerned, we already have tons of bug reports about it on our tracker. This script, or more precisely the way Beth has been using it, is a nightmare.

 

 

Yes, I may have. I remember trying to put some bow on a stand-up rack and it just clattered to the ground like I'd dropped it, so I picked it up again and figured bows can't go on racks. It was awhile later that I discovered the threads looping in my save file and made the connection that the bow on the rack might have been when that started. I'll see if I can reproduce the loop with Zephyr on a new save file.

 

Problem solved. The scripts that have been implemented in the forthcoming version 2.0.0 of the USKP will handle this.

 

I will try to explain it very briefly:

 

There is a fair number of items in the game which always have been problematic when it comes to weapon racks. The either fell off after leaving the cell (such as the ebony blade and Keening) or would eventually disappear into Nirvana (such as the ghostblade). Numerous attempts at fixing this remained entirely unsuccessful (including several mods that failed to convince us when stress tested in the long run). The Dawnguard DLC added Zephyr to this list, and Dragonborn contributed another two weapons. Meanwhile though, even some mod-added items were found to be affected by this issue (e.g. the Jade Blade from JaySuS Swords).

 

While those problem weapons never stayed permanently on any rack, they could be placed on racks at least in earlier versions of the game. Though, this stopped working abruptly after one of the latest official patches (almost certainly patch 1.9) and now, when one even attempts at placing them on a rack, they will fall to the ground immediately. About thee months ago, while trying to find a way to place them on racks anyway, I noticed that they also had stopped to return valid references (not even a "none" !) when they were removed from the player's inventory with the DropObject function. Instead they returned nonsensical [item xx in container yy] type references which were impossible to handle. More on this can be found in posts 114-117, starting around here: http://www.afkmods.com/index.php?/topic/3669-overhauling-the-weapon-rack-scripts/?p=146451

 

To deal with this, the weapon rack script overhaul implemented In USKP 1.3.3c prevents those items, of which we were sure at that time that they would not stay on a rack in any circumstances, from even trying to be placed on a rack at all. To do so, we have been using a formlist that would be checked by the script before it even started to handle the player's item. However, form lists will have to be updated regularly, and with the discovery that even mod-added items could be affected, it soon became evident that this was not an ideal solution. Moreover, any attempt by a player at placing an item on the rack that wasn't on the list would return in the logs being flooded with error messages (such as in your case) and there was no way to prevent them from occurring because those "transient references" would slip through all sanity checks.

 

Therefore, I have been investing some time to develop a detection method for problem items, which enables the scripts to detect on their own whether or not an item will be placeable. However, this took much time and there were many things I had to learn the hard way until it eventually worked. While there's still no way to place those items on a rack, we can now safely exclude at least that they will cause any trouble. How this works is explained here (post 128): http://www.afkmods.com/index.php?/topic/3669-overhauling-the-weapon-rack-scripts/?p=146968

Link to comment
Share on other sites

Using the 3.4 update that's going to be in USKP 2.0 live, so I can't say for sure right now what would have happened on the 1.3.3c files.

 

I can tell you though that it's entirely likely I lost my Zephyr bow the same way you did.

 

So you may want to give the 2.0 beta a shot and see what you get - that only has up to the 3.1 script, but 3.4 will be in the live release. And everyone here has access to download the current beta.

Link to comment
Share on other sites

Moreover, any attempt by a player at placing an item on the rack that wasn't on the list would return in the logs being flooded with error messages (such as in your case) and there was no way to prevent them from occurring because those "transient references" would slip through all sanity checks.

 

I understand and accept everything you said, except this part. I just did a test myself, and at least in the case of Zephyr, a simple ItemToPlace.GetBaseObject() immediately reveals the problem by throwing an error and evaluating as None, while any non-transient ObjectReference ought to return something, anything, from GetBaseObject(). So there's a sanity check that seems to work.

 

Even if it doesn't, however, I still contend that all loops must always exit somehow eventually, no matter what. If the PlaceItem() in your upcoming 2.0 release has the same "While !ItemToPlace.Is3DLoaded() ; Utility.Wait(0.1) ; EndWhile" loop, then you *really* should add a failsafe. Because no matter what else, if the worst should happen and a bad reference makes it into that function, it is not okay to just let that thread loop forever and ever throwing errors. It must exit so that the thread can terminate and the errors only hit the log once and not every frame draw for the rest of the player's game. Obviously this failsafe will not make these problematic items magically work with racks, but it will at least avoid the performance degradation and logfile spam which it currently causes.

Link to comment
Share on other sites

I understand and accept everything you said, except this part. I just did a test myself, and at least in the case of Zephyr, a simple ItemToPlace.GetBaseObject() immediately reveals the problem by throwing an error and evaluating as None, while any non-transient ObjectReference ought to return something, anything, from GetBaseObject(). So there's a sanity check that seems to work.

 

That's, very basically, how the v3.4 scripts work. There still remain a few issues though, which you don't already know about (and probably haven't even considered):

 

(1) As long as an item is inside a container (e.g. in the player's inventory), there is no way to access its reference. Thus, for GetBaseObject() or any other check to run, you must remove it from the player. When you do this, the item that drops into the game world has a transient reference. Now imagine that you run the check and it fails. At this point, you know that the item can't be placed and you abort the procedure. However, there's still the item on the floor which needs to be moved back into the player's inventory. How do you do this ? (Hint: AddItem() doesn't work ...)

(2) Imagine that the player is unhappy with this result and tries immediately to place the item again. Try to repeat this procedure and be flabbergasted ...

(Hint: There are some logs in post 117. Unfortunately, this result is perfectly repeatable).

Link to comment
Share on other sites

Using the 3.4 update that's going to be in USKP 2.0 live, so I can't say for sure right now what would have happened on the 1.3.3c files.

 

1.3.3c didn't have this functionality. Back then, we weren't even fully aware of the "transient references" problem.

 

I'm glad however that you did confirm that the procedure works.

Link to comment
Share on other sites

That's, very basically, how the v3.4 scripts work. There still remain a few issues though, which you don't already know about (and probably haven't even considered):

 

(1) As long as an item is inside a container (e.g. in the player's inventory), there is no way to access its reference. Thus, for GetBaseObject() or any other check to run, you must remove it from the player. When you do this, the item that drops into the game world has a transient reference. Now imagine that you run the check and it fails. At this point, you know that the item can't be placed and you abort the procedure. However, there's still the item on the floor which needs to be moved back into the player's inventory. How do you do this ? (Hint: AddItem() doesn't work ...)

(2) Imagine that the player is unhappy with this result and tries immediately to place the item again. Try to repeat this procedure and be flabbergasted ...

(Hint: There are some logs in post 117. Unfortunately, this result is perfectly repeatable).

 

I would say that the item dropping to the floor is a perfectly reasonable failure mode, for this kind of just-in-time sanity checking. People can post "item X just fell on the ground" in the forums, and then you can add that item to your filter lists so that in the future, the player gets a nicer message about some items not being rack-able.

 

I am curious about your (2) -- what will I be flabbergasted by? If the player picks up the item from the ground and tries to place it again, so the rack script calls DropItem() again, do you not just get another transient reference that you handle the same way, by bailing out and leaving it on the ground? Or, if something stranger happens, could you not prepare for this possibility during the first round of floor dropping? i.e. dynamically add the base form ID to the blacklist so it won't try to place it again?

Link to comment
Share on other sites

I may have found a way to fix the infinite-looping threads in my save file, but I want to run it by you guys before I offer it more broadly.

 

The basic problem, as of course you folks already know, is that the engine is too accommodating of baked-in script threads. So long as PlaceItem() is stuck in that loop, any attempt to replace the script that defines it or any of its ancestors in the call stack results in the engine restoring the baked-in copy from the save file. So PlaceItem() itself cannot be hot-fixed once it starts looping.

 

But, the loop does make one external call to Is3DLoaded(), so in theory that script could be swapped out from under the save and it should happily switch to the new version on the next loop iteration. Unfortunately that function is native, and indeed the ObjectReference.psc that comes with SKSE also defines it as such ("bool Function Is3DLoaded() native" with no function body), so normally I'm not sure it could be overridden without help from the SKSE devs or somesuch.

 

But luckily in this case, it's being evaluated first on the defaultDisableHavokOnLoad script, which extends ObjectReference. Normally, since the havok script doesn't override it, the call tries to fall through to the base ObjectReference definition, which fails with the type error.

 

So, I just overrode it in defaultDisableHavokOnLoad. Specifically, I added this to the defaultDisableHavokOnLoad.psc that comes with the CK:

Bool Function Is3DLoaded()
	If !Self.GetBaseObject()
		Debug.Trace(self+".Is3DLoaded(): transient reference failsafe!")
		Return True
	EndIf
	Return Parent.Is3DLoaded()
EndFunction

The GetBaseObject() transient reference detection still works here, so when I load my save with this modified script, what happens is I get a single type error where it tries to call GetBaseObject(), followed by one "transient reference failsafe!" log, followed by several more type errors further down in PlaceItem() because its still dealing with a transient reference. But because Is3DLoaded() finally returned True, the infinite loop is broken and after that single burst of errors, I don't get any more. I believe after saving again and removing my altered defaultDisableHavokOnLoad script, all will still be well (so long as I don't try to put Zephyr on a rack again).

 

Thoughts? Is there a good place to post this modified havok script for people to use to break any loops they have in their saves? It's clearly not something that should be installed all the time, but for the purpose of cleaning up stuck loops in a save and then immediately removing it again, it seems to work for me at least.

Link to comment
Share on other sites

I would say that the item dropping to the floor is a perfectly reasonable failure mode, for this kind of just-in-time sanity checking. People can post "item X just fell on the ground" in the forums, and then you can add that item to your filter lists so that in the future, the player gets a nicer message about some items not being rack-able.

 

Leaving it to the player to pick the item up is not an acceptable solution for us, and it would also break immersion, so I did conceive a workaround which silently adds the item back to the player's inventory. You only have to find a way to retrieve the true reference, not a transient one. Actually, the whole procedure is that fast that you will rarely ever notice in-game that the item left your inventory.

 

 

I am curious about your (2) -- what will I be flabbergasted by? If the player picks up the item from the ground and tries to place it again, so the rack script calls DropItem() again, do you not just get another transient reference that you handle the same way, by bailing out and leaving it on the ground? Or, if something stranger happens, could you not prepare for this possibility during the first round of floor dropping? i.e. dynamically add the base form ID to the blacklist so it won't try to place it again?

 

When you run this procedure once, the reference is borked. When you run it again, you won't even get a transient reference, but only complete bullshit. Perfectly repeatable, unfortunately. Somehow the engine goes crazy or whatever, I don't know.

 

Here are some logs obtained with the ghostblade (forget the dummy and the test container for the moment). Perhaps you have an idea why papyrus denotes the ghostblade as "WE100BaseLetterScript" (which runs on a totally different item that was not even in my inventory) ?

 

First run:

 

Test container: Player item [WEAPON < (00094A2B)>] received from player; Ref = [WE100BaseLetterScript <Item 18 in container  (00000014)>]

Test container: Player item returned to player.

Item handling quest: Player item reference submitted by test container. Counter = 1

[WeaponRackActivateScript < (00102920)>]: Player item ref from quest = [WE100BaseLetterScript <Item 18 in container  (00000014)>]

[WeaponRackActivateScript < (00102920)>]: Player item dropped. Returned ref = [ObjectReference <Item 22 in container  (00000014)>]

[WeaponRackActivateScript < (00102920)>]: Player item (Ref = [WE100BaseLetterScript < (00094A2C)>]) stored in container no. 0

[WeaponRackActivateScript < (00102920)>]: Dummy item added to player's inventory.

[WeaponRackActivateScript < (00102920)>]: Dummy item dropped. Ref = [ObjectReference < (FF0020F6)>]

[WeaponRackActivateScript < (00102920)>]: Placed Item = [WEAPON < (00094A2B)>]; Ref = [ObjectReference < (FF0020F6)>]

[WeaponRackActivateScript < (00102920)>]: Trigger Marker [WeaponRackTriggerSCRIPT < (00102921)>] in empty state.

Item handling quest: Dummy item (Ref = [ObjectReference < (FF0020F6)>]) has been grabbed by the player.

Item handling quest: Dummy (Ref = [ObjectReference < (FF0020F6)>]) removed from player's inventory.

Item handling quest: Player item  returned to player.

[WeaponRackActivateScript < (00102920)>]; Leaving Item = [WEAPON < (00094A2B)>]; Ref = [ObjectReference < (FF0020F6)>]

[WeaponRackActivateScript < (00102920)>]; Mounted Item = [WEAPON < (00094A2B)>]; Ref = [ObjectReference < (FF0020F6)>]

[WeaponRackActivateScript < (00102920)>] enabled; TOC = 0; StartingItemHasBeenGrabbed = True.

 

Second run:

 

[WeaponRackActivateScript < (000DF577)>]: beginning to handle player item.

Item handling quest: Player item sent to test container.

Test container: Player item [WEAPON < (00094A2B)>] received from player; Ref = [ObjectReference <Item 22 in container  (00000014)>]

Test container: Player item returned to player.

Item handling quest: Player item reference submitted by test container. Counter = 1

[WeaponRackActivateScript < (000DF577)>]: Player item ref from quest = [ObjectReference <None>]

[WeaponRackActivateScript < (000DF577)>]: Player item dropped. Returned ref = [ObjectReference <Item 22 in container  (00000014)>]

[WeaponRackActivateScript < (000DF577)>]: Player item (Ref = [ObjectReference <None>]) stored in container no. 0

[WeaponRackActivateScript < (000DF577)>]: Dummy item added to player's inventory.

[WeaponRackActivateScript < (000DF577)>]: Dummy item dropped. Ref = None

 

At this point, the ghostblade disappeared forever ...

Link to comment
Share on other sites

Thoughts? Is there a good place to post this modified havok script for people to use to break any loops they have in their saves? It's clearly not something that should be installed all the time, but for the purpose of cleaning up stuck loops in a save and then immediately removing it again, it seems to work for me at least.

 

Tricky.

 

So far, this would be working only for Zephyr and other items that incidentally both are causing trouble on the racks and have the DefaultDisableHavokOnLoad script attached, right ?

 

You could add a redefined Is3DLoaded function to the activator script instead. The engine won't update a function that is registered as having to complete an open task (and loads the old version from the save instead), but it usually accepts new code without complaining and is using it. The advantage of this method is that it would "purge" all affected racks at once.

 

Also, we had quite a few people with very similar error messages on their logs, whose problems turned out to be related to an inappropriate value set for iMaxAllocatedMemoryBytes. I don't know what this solution would do to their games, if anything at all. Best to note this in the disclaimer though.

Link to comment
Share on other sites

Tricky.

 

So far, this would be working only for Zephyr and other items that incidentally both are causing trouble on the racks and have the DefaultDisableHavokOnLoad script attached, right ?

 

You could add a redefined Is3DLoaded function to the activator script instead. The engine won't update a function that is registered as having to complete an open task (and loads the old version from the save instead), but it usually accepts new code without complaining and is using it. The advantage of this method is that it would "purge" all affected racks at once.

 

Also, we had quite a few people with very similar error messages on their logs, whose problems turned out to be related to an inappropriate value set for iMaxAllocatedMemoryBytes. I don't know what this solution would do to their games, if anything at all. Best to note this in the disclaimer though.

 

I'm not seeing how Is3DLoaded on the activator script would help, since the activator script is not an ancestor of ObjectReference or defaultDisableHavokOnLoad. When PlaceItem() is in a loop calling ItemToPlace.Is3DLoaded(), why would it try to resolve that function on the activator script?

 

But yes, this will only clean up threads that are looping because of items which have that havok script attached. On the other hand, Ghostblade is another such item (same as Zephyr, the havok script is attached by the world-placed reference, so if you use additem to give yourself a base Ghostblade form instead of picking up the placed one, I bet it won't have a problem).

 

I'm trying to understand the logs you posted about Ghostblade. It looks to me like you start out with the base form ID (94a2b), and it's not until you DropItem() with that form that you get the bogus reference (and I agree it doesn't make any sense for WE100BaseLetterScript to be involved at all). But then you add that bogus reference to your item tracking quest in order to detect it later? Why not instead remember the base form ID (94a2b), and the next time the player activates the rack while holding that form, don't even call DropItem() but just print the warning right away?

Link to comment
Share on other sites

But then you add that bogus reference to your item tracking quest in order to detect it later? Why not instead remember the base form ID (94a2b), and the next time the player activates the rack while holding that form, don't even call DropItem() but just print the warning right away?

 

Nothing of that is up-to-date any more.  Please check your private messenger (on this site).

Link to comment
Share on other sites

I'll poke around in your weapon rack overhaul scripts tomorrow probably, for now I'm still trying to see if I can hotfix the looping threads in my save.

 

After applying the fix posted above, the one thread which was looping on a transient reference has exited and is fixed, so I don't get those errors every frame anymore. But, since I've installed your other hotfix, I do still get lots of these on game load:

[10/21/2013 - 09:04:29PM] Warning: Function WeaponRackActivateScript..InitActivator in stack frame 1 in stack 4277881 differs from the in-game resource files - using version from save
[10/21/2013 - 09:04:29PM] Warning: Function WeaponRackActivateScript..HandleStartingItem in stack frame 2 in stack 4277881 differs from the in-game resource files - using version from save
[10/21/2013 - 09:04:29PM] Warning: Function WeaponRackActivateScript..PlaceItem in stack frame 3 in stack 4277881 differs from the in-game resource files - using version from save

78 threads worth of that, actually. They must be stuck in the same loop, but they must not have transient references because they're not spewing the same errors. That also means the defaultDisableHavokOnLoad fix doesn't apply there, because the item they're looping on must not have that script attached.

 

So I figured I'd try something even crazier, and I edited the base ObjectReference.psc that comes with SKSE to redefine Is3DLoaded() from native to scripted and always returning True. I expected that would cause other things to misbehave but should at least break those 78 threads out of their loop, not only because !Is3DLoaded() is the loop condition, but also because according to the CK wiki, "Exception: If a function was native and is now scripted, the stack will be thrown out instead of resumed."

 

But it didn't work; after loading a game with the altered ObjectReference and then re-saving, loading the new save yields exactly the same warnings about threads in the WeaponRackActivateScript being restored from the save. I don't understand how those threads are still running. They aren't generating any errors in the log when they try to evaluate Is3DLoaded(), the way transient or otherwise bad references do, so they must be working with valid non-transient ItemToLoad references. And I can see ~1500 other scripts which extend ObjectReference, but none of them override Is3DLoaded(), so on every iteration, all 78 of those threads ought to be calling the base ObjectReference.Is3DLoaded() which I redefined to always return True. And yet the loops don't break.

 

Anyway, just an update. This scripting engine is crazy. Why didn't they just use Lua or something?

Link to comment
Share on other sites

If you want to know what your Papyrus stack is currently executing, open the console after you load up and type "dps" without the quotes. It will drop a stack dump in your log.

 

You will end up with a bunch of stuff that looks like this:

[10/21/2013 - 07:54:37PM] Dumping stack 18906744:
[10/21/2013 - 07:54:37PM]     Frame count: 1 (Page count: 1)
[10/21/2013 - 07:54:37PM]     State: Running (Freeze state: Frozen)
[10/21/2013 - 07:54:37PM]     Type: Normal
[10/21/2013 - 07:54:37PM]     Return register: None
[10/21/2013 - 07:54:37PM]     Has stack callback: No
[10/21/2013 - 07:54:37PM]     Stack trace:
[10/21/2013 - 07:54:37PM]         [None].DLC1MagicCastFromSunScript.OnEffectFinish() - "<savegame>" Line ?
[10/21/2013 - 07:54:37PM]             IP: 41    Instruction: 2
[10/21/2013 - 07:54:37PM]             [Target]: [Actor < (00105A13)>]
[10/21/2013 - 07:54:37PM]             [Caster]: [Actor < (00000014)>]
[10/21/2013 - 07:54:37PM]             [::temp13]: False
[10/21/2013 - 07:54:37PM]             [::NoneVar]: None
[10/21/2013 - 07:54:37PM]             [::temp14]: None

 

Each one is an operation Papyrus has become stuck on. Those warnings you get about "stack frame 1 in stack <some number>" can be used to find the specific one of these blocks in a dump that it's referring to.

 

This one I used in my example is from a bad script for Auriel's Bow that we've since fixed in the UDGP. It's literally gotten itself stuck in an endless while loop and there appears to be no way to break it out because the Actor (00105A13) is now dead and shouldn't even have a magic effect waiting to terminate. Much less after all this time. So as far as I can tell, that one (and 5 more just like it for me) are permanently stuck there - and I only fired the bow into the sun ONE TIME.

 

I've also got a bunch from Dragonborn that got stuck. Bending room parts in Apocrypha that are apparently still trying to bend even though I'm nowhere near them.

 

And yes, I have one from the weapon rack above the upstairs bed in Honeyside. It got stuck somehow in the PlaceItem() function on a Utility.Wait() statement it can't get out of.

[10/21/2013 - 07:54:37PM] Dumping stack 33430368:
[10/21/2013 - 07:54:37PM]     Frame count: 5 (Page count: 2)
[10/21/2013 - 07:54:37PM]     State: Waiting on latent function (Freeze state: Freezing)
[10/21/2013 - 07:54:37PM]     Type: Normal
[10/21/2013 - 07:54:37PM]     Return register: False
[10/21/2013 - 07:54:37PM]     Has stack callback: No
[10/21/2013 - 07:54:37PM]     Stack trace:
[10/21/2013 - 07:54:37PM]         <unknown self>.utility.Wait() - "<native>" Line ?
[10/21/2013 - 07:54:37PM]             IP: 0
[10/21/2013 - 07:54:37PM]             [param1]: 0.100000
[10/21/2013 - 07:54:37PM]         [ (00102BDA)].WeaponRackActivateScript.PlaceItem() - "<savegame>" Line ?
[10/21/2013 - 07:54:37PM]             IP: 107    Instruction: 3
[10/21/2013 - 07:54:37PM]             [ItemToPlace]: [ObjectReference < (00102BDC)>]
[10/21/2013 - 07:54:37PM]             [TriggerMarker]: [WeaponRackTriggerSCRIPT < (00102BDB)>]
[10/21/2013 - 07:54:37PM]             [::temp24]: True
[10/21/2013 - 07:54:37PM]             [::temp25]: 0
[10/21/2013 - 07:54:37PM]             [::temp26]: False
[10/21/2013 - 07:54:37PM]             [::NoneVar]: None
[10/21/2013 - 07:54:37PM]             [::temp27]: False
[10/21/2013 - 07:54:37PM]             [::temp28]: False
[10/21/2013 - 07:54:37PM]             [::temp29]: False
[10/21/2013 - 07:54:37PM]         [ (00102BDA)].WeaponRackActivateScript.HandleStartingItem() - "<savegame>" Line ?
[10/21/2013 - 07:54:37PM]             IP: 413    Instruction: 14
[10/21/2013 - 07:54:37PM]             [StartingItem]: [ObjectReference < (00102BDC)>]
[10/21/2013 - 07:54:37PM]             [TriggerMarker]: [WeaponRackTriggerSCRIPT < (00102BDB)>]
[10/21/2013 - 07:54:37PM]             [::NoneVar]: None
[10/21/2013 - 07:54:37PM]             [::temp36]: True
[10/21/2013 - 07:54:37PM]             [::temp37]: True
[10/21/2013 - 07:54:37PM]             [::temp38]: False
[10/21/2013 - 07:54:37PM]             [::temp39]: False
[10/21/2013 - 07:54:37PM]         [ (00102BDA)].WeaponRackActivateScript.InitActivator() - "<savegame>" Line ?
[10/21/2013 - 07:54:37PM]             IP: 1489    Instruction: 50
[10/21/2013 - 07:54:37PM]             [::temp5]: [WeaponRackTriggerSCRIPT < (00102BDB)>]
[10/21/2013 - 07:54:37PM]             [TOC]: 1
[10/21/2013 - 07:54:37PM]             [StartingItem]: [ObjectReference < (00102BDC)>]
[10/21/2013 - 07:54:37PM]             [TriggerMarker]: [WeaponRackTriggerSCRIPT < (00102BDB)>]
[10/21/2013 - 07:54:37PM]             [::temp6]: True
[10/21/2013 - 07:54:37PM]             [::temp7]: 1
[10/21/2013 - 07:54:37PM]             [::temp8]: False
[10/21/2013 - 07:54:37PM]             [::temp9]: True
[10/21/2013 - 07:54:37PM]             [::NoneVar]: None
[10/21/2013 - 07:54:37PM]             [::temp10]: [Cell <RiftenHoneyside (00016BDD)>]
[10/21/2013 - 07:54:37PM]             [::temp11]: [Cell <RiftenHoneyside (00016BDD)>]
[10/21/2013 - 07:54:37PM]             [::temp12]: False
[10/21/2013 - 07:54:37PM]         [ (00102BDA)].WeaponRackActivateScript.OnCellAttach() - "<savegame>" Line ?
[10/21/2013 - 07:54:37PM]             IP: 0    Instruction: 0
[10/21/2013 - 07:54:37PM]             [::NoneVar]: None

 

I'd dearly love to know how to get these stack frames to die off because I believe they are impacting game performance since they're not on delayed update timers or anything else that would mitigate their impact.

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...