Snark

UIPartActionWindow questions/problems

Recommended Posts

So, I've got a mod that touches content that involves the part action window.  Works great... except that I'm in the process of trying to add a new feature, that apparently is tickling UIPartActionWindow in some way that it doesn't like, resulting in NRE spew.  I've been going round in circles trying to figure out, 1. what it wants, or even 2. how to figure out what it wants.  Was wondering if anyone here might have any advice.

My mod, SimpleFuelSwitch, is a pretty basic one that just swaps out resources in the editor (e.g. you can toggle a fuel tank between LFO and LF-only, that sort of thing).  Mod thread is here, source code is here.  The specific source file where I do the resource switching is here.

The currently released functionality-- which works just fine-- involves the user clicking a button in the PAW, while the PAW is displayed.  No problems.  I'm in the process of trying to add a new feature that would involve doing the same resource switching via another (simple) mechanism, the only difference being that it happens while the PAW is not displayed.  But other than that-- it's calling the same code to do the switch, it's calling the same refresh-and-update-things-afterwards.  And yet, for reasons I have been unable to determine, it works fine in case A (published version) but not in case B (the feature I'm working on).

Here's what I do to swap out resources:

  1. Call part.Resources.Clear(), followed by calling part.Resources.Add(resource) for each of the resources I'm setting up.  Ditto process with part.SimulationResources.
  2. Get the UIPartActionWindow for the part, and set displayDirty = true.  (Needed so that it will redraw itself with the correct resources.)
  3. Fire GameEvents.onEditorShipModified.Fire(EditorLogic.fetch.ship) so that other KSP UI elements (such as engineer's report) will update themselves.

When I do the above sequence of events while the PAW is up (as a result of clicking a PAW button), everything works perfectly and no problems.

However, when I do the exact same operations, but happen to do it via another logic path while the PAW happens not to be currently displayed... then it "mostly" works, but fails in one very specific case:

  • Works fine if I add a part with no symmetry.
  • Works fine if I add a part with symmetry.
  • Works fine if I add a part with symmetry, and I then add an alt-click copy of it (also with symmetry), as long as I haven't changed the resources from the default.
  • But if I switch the resources, and have the part as part of a symmetry group, and I make an alt-click symmetrical copy... then right-clicking the part doesn't bring up the PAW, but instead results in endless NRE spam.

The NRE I'm seeing looks like this:

[EXC 17:32:11.181] NullReferenceException: Object reference not set to an instance of an object
	UIPartActionResourceItem.SetSymCounterpartsAmount (Double amount)
	UIPartActionResourceEditor.onSliderChangeProcess ()
	UIPartActionResourceEditor.Setup (.UIPartActionWindow window, .Part part, UI_Scene scene, .UI_Control control, .PartResource resource)
	UIPartActionWindow.AddResourceEditorControl (.PartResource r)
	UIPartActionWindow.SetupResourceControls (.PartResource r, Boolean clearFirst, UI_Scene scene, System.Int32& controlIndex)
	UIPartActionWindow.CreatePartList (Boolean clearFirst)
	UIPartActionWindow.UpdateWindow ()
	UIPartActionController.UpdateActiveWindows ()
	UIPartActionController.UpdateEditor ()
	UIPartActionController.Update ()

Before posting here, I did a forum search for UIPartActionWindow.  There were references in several places... and a lot of them featured NREs with a call stack looking exactly like the above, in posts going back as far as 2014.  So I gather that this particular error isn't super informative, it's kind of the PAW's default go-to "barf symptom" whenever it's unhappy about something.  Unfortunately... none of those places were doing the same exact thing that I'm doing, nor was I able to find what solution people had for them (even if their solution were relevant to me, which it might not be).

Anyone have any ideas about anything I could try?  I've been trial-and-error poking at everything I can think of, and haven't gotten anywhere.

On a related note:  Gosh, it sure would be nice if there were some event hook for "We're about to display the PAW".  Is there one?  The only GameEvents events I've been able to find that appear to be PAW related are onPartActionUICreate and onPartActionUIDismiss.  I thought the former looked promising... except when I try to use it, I find that it's called on every single update frame as long as the PAW is visible.  I was hoping for something that would fire once, when the PAW is displayed, rather than every-frame-continuously while it's up.  Any ideas?

(Incidentally, a bit of experimental logging inserted in GameEvents.onPartActionUICreate shows that when I trigger the NRE, I see that onPartActionUICreate gets called right before each NRE is emitted.  So, presumably if I could figure out what the PAW is unhappy about, I could put code in my onPartActionUICreate handler to placate it, though I'd need to kludge together some logic to make it do it just once and not on every single frame.)

  • Like 1

Share this post


Link to post
Share on other sites

I don't know what would be causing all those problems. But if you are looking for a way to be notified whenever a PAW is generated then you can let Unity's magic methods and serialization work for you, for once.

Find the prefab of the PAW window, attach a simple MonoBehaviour to it with an Awake method, then trigger your own event when that Awake method is run. Then the next time KSP opens a PAW, it will find that prefab, make a clone of it along with the script you attached, then call the Awake methods for all scripts active on that clone.

I do this in SEP to add some fields to the PAW when on EVA:

Finding the prefab (you might want some logic to make sure everything is not null when this happens, and that you only do it once): https://github.com/CobaltWolf/Surface-Experiment-Pack/blob/master/Source/SEPScience/SEP_Utilities.cs#L131-L142

The script being added to the prefab: https://github.com/CobaltWolf/Surface-Experiment-Pack/blob/master/Source/SEPScience/SEP_UIWindow.cs

The event being called in the Awake method: https://github.com/CobaltWolf/Surface-Experiment-Pack/blob/master/Source/SEPScience/SEP_Utilities.cs#L47-L48

Edited by DMagic
  • Like 1

Share this post


Link to post
Share on other sites
25 minutes ago, DMagic said:

I don't know what would be causing all those problems. But if you are looking for a way to be notified whenever a PAW is generated then you can let Unity's magic methods and serialization work for you, for once.

Thank you!

Unfortunately... it's not clear to me that that would actually help me, in this case.  :(  Based on additional kludging, logging, trial-and-error in my code, coupled with looking at the NRE stack trace that's all I have to go on, here's what I believe is happening:

  1. A certain amount of setup happens in KSP when the PAW is popped up.
  2. During that setup, onUIPartActionCreate gets called.  (It gets called on every frame, but with a bit of jury-rigging I can insert my own code either for on-every-iteration or for just-the-first-time.  Some things that I can verify at this time:
    • The part has exactly the resources that I think it does (i.e. set up based on my code).
    • The part's symmetry counterparts also have exactly the same resources as the part itself.
    • I can retrieve the UIPartActionWindow for the PAW at this point. If I query its status, it has isValid = false, and no resources.
  3. After calling onUIPartActionCreate, this setup code tries to set up any resource controls that the PAW has.
  4. As part of setting that up, it tries to talk to symmetry counterparts.
  5. NRE, presumably because it thinks that the resources on the counterpart somehow don't line up with the resources on the part where the PAW is being popped up.
    • Note that the symmetry counterparts' resources are, in fact, identical with the part being processed, which I can verify in step 2 above.
    • So, I have no idea what the heck is going wrong.

In other words:  either there's some flaw in the logic of UIPartActionWindow itself, or else there's something wrong in the way I'm setting things up (though I can't imagine what that could be, since I'm doing exactly the same steps for this control path as I do in another control path that's proven to work, the only difference being whether the PAW is on-display or not when I make the change).

Hooking onto the PAW at initial generation time wouldn't help me, I think, because whatever-it-is is going off the rails after the place I've already hooked in (at onUIPartActionCreate) time.  Hooking earlier wouldn't help me, I think, unless I'm missing something.

  • Like 1

Share this post


Link to post
Share on other sites

What's being missed is the Resources is a collection dictionary.. and your mod is changing the resource definition. which I think is exposing a bug (which can only occur if a mod changes the resources on a part). It looks like the collection dictionary key is incorrectly set to the old Resource hash instead of the new Resource hash. - Probably due to the fact that in stock - this never changes when instantiating a copy of a part.
I'd recommend resetting the Resources list after any part copied events in the Editor scene would resolve the issue.

  • Like 3

Share this post


Link to post
Share on other sites

Excellent, thank you!  This was the missing piece.  After a bit of tinkering, I was able to get things to work the way I need them to.

Share this post


Link to post
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