blowfish

The Lifecycle of a Part Module - A Primer

Recommended Posts

I've answered a number of questions related to this in recent times, so I figured I'd explain in detail the entire life cycle of a PartModule.  

Birth

When parsing a part from the game database, KSP looks for MODULE nodes to parse as part modules.  It looks for a name = XXX value in the MODULE node and tries to find a class that derives from PartModule with that name.  If it finds one, it will attach an instance to the part's GameObject (part modules are Unity components and can only exist attached to game objects).  The newly created module will then be added to the part's module list.  Load(ConfigNode) will be called on the module with that node.  This loads any fields with the [KSPField] attribute automatically, and calls OnLoad(ConfigNode) with the same node to allow parsing of any custom data.

KSP stores the part (and its modules) as what's called a prefab - any time the part actually needs to exist in the game, this prefab is copied.

Instantiation in the Editor

When adding a part the the editor, KSP will instantiate (copy) the part prefab.  In order to do this, Unity serializes the entire part and then deserializes it as a new instance (see here for more information on serialization in Unity).  This is often a source of grief with part modules because custom data frequently does not get serialized (brief note on this below, I will dive into more depth on this issue in a future post)

Spoiler

Unity's docs say that it will serialize any custom type with the [Serializable] attribute, but that's not strictly true.  It also must be present when Unity starts, which means that anything in a KSP mod assembly does not work because KSP loads mod assemblies dynamically.

Potential workarounds are (1) Serialize data yourself by implementing ISerializationCallbackReceiver (store it in a string or something else) (2) Derive from MonoBehaviour or ScriptableObject, which Unity will serialize regardless (3) Store the relevant objects outside the part module then copy them back when a new instance is created (4) Save the original ConfigNode (either on the module as a string or off the module as a ConfigNode and load it later

Once all the editor setup is done, the part's Start method will call the module's OnStart method, then Unity will call the module's Start method.

Life in the Editor

OnUpdate will be called by the part and Update by Unity every visual frame.  OnFixedUpdate and FixedUpdate the same every physics frame (even though there's no physics in the editor).

Editor Copy

The user may copy an existing part in the editor or change the symmetry mode.  Either way, KSP makes a copy of the existing part.  Instead of copying the prefab as before, KSP will instantiate the existing part instance.  The same serialization rules apply.

Editor Save

When the craft is saved in the editor, Save is called on each part module to save its state.  Save writes any KSPField with isPersistent = true to the saved node, saves data about events and action groups, and calles OnSave, which can be used to save any custom data

Editor Death

When the part is removed in the editor (or a new craft is loaded) the part and all its modules are destroyed completely.  No data that wasn't already saved will survive.

Creation in Flight

This behaves very similar to creation in the editor.  The part prefab is instantiated (again, by serializing and deserializing), Load is called with any saved data (which again calls OnLoad to do any custom loading),  OnStart is called by the part, then Start by Unity once everything is set up.

There's one odd case of this, and that is when dealing with the root part of a vessel already in flight.  KSP does instantiate the part prefab (and by extension its modules), but then copies its fields to a new part with new modules.  I'm not sure why this is done and it usually doesn't matter, but still something that can cause occasional issues.

Life in Flight

OnUpdate will be called by the part and Update by Unity every visual frame.  FixedUpdate will be called every physics frame, and OnFixedUpdate will be called every physics frame once the part has been activated (i.e. staged).

Flight Save

Basically the same as in the editor, Save is called on each part module, which writes any KSPField with isPersistent = true to the saved node, saves data about events and action groups, and calles OnSave, which can be used to save any custom data

Editor Death

When the flight scene is exited, the craft goes out of physics range, or the part explodes, the part is destroyed completely, along with all of its modules.

 

 

 

 

I've probably missed some stuff here, so feel free to ask questions etc.

Share this post


Link to post
Share on other sites

Also note that OnStart is called after Load, so all persistent KSPFields are already parsed for OnStart.

There is an OnAwake method that is called before all of the loading steps, in case you want to do something before loading. Just be careful not to use the standard Unity Awake method, that would hide the base module's Awake and screw things up. I don't think there is a base Start method, but you are probably better off using OnStart in most cases.

The persistent KSPFields are saved and loaded before OnLoad and OnSave are called. So if you want to change anything for those values in OnSave you'll either have to directly modify the config node, or use your own fields.

And there is also the LateUpdate method, called each frame after all Update calls have been made. I don't think there is an OnLateUpdate method, just the standard Unity method.

Share this post


Link to post
Share on other sites
9 hours ago, DMagic said:

that would hide the base module's Awake

Start/Update/FixedUpdate/OnDestroy are all safe, it's just Awake that you need to steer clear of AFAIK.
It's also worth bearing in mind that The On<*>Update methods are only called when a part is activated (staged in most cases) while Update/FixedUpdate are always functional

Share this post


Link to post
Share on other sites
16 hours ago, Crzyrndm said:

Start/Update/FixedUpdate/OnDestroy are all safe, it's just Awake that you need to steer clear of AFAIK.

Right, because PartModule actually uses the Awake() method.  Fortunately it provides an OnAwake() method which you can override (and is just called by Awake() )

16 hours ago, Crzyrndm said:

It's also worth bearing in mind that The On<*>Update methods are only called when a part is activated (staged in most cases) while Update/FixedUpdate are always functional

I think this is true for OnFixedUpdate but OnUpdate is called regardless.

Share this post


Link to post
Share on other sites
2 hours ago, blowfish said:

Right, because PartModule actually uses the Awake() method.  Fortunately it provides an OnAwake() method which you can override (and is just called by Awake() )

I think this is true for OnFixedUpdate but OnUpdate is called regardless.

PartModule.OnUpdate is only called if the PartModule is Enabled. This does not occur in the Editor for one.
I believe the same goes for PartModule.OnFixedUpdate

Edited by JPLRepo

Share this post


Link to post
Share on other sites
8 minutes ago, JPLRepo said:

PartModule.OnUpdate is only called if the PartModule is Enabled. This does not occur in the Editor for one.
I believe the same goes for PartModule.OnFixedUpdate

isEnabled is true in the editor.  It's mostly just used for completely disabling certain modules, e.g. MultiModeEngine disables the inactive engine module.  Both OnUpdate and OnFixedUpdate will only fire if isEnabled is true, but OnFixedUpdate has another check of part.state == PartStates.ACTIVE.

Share this post


Link to post
Share on other sites
Just now, blowfish said:

isEnabled is true in the editor.  It's mostly just used for completely disabling certain modules, e.g. MultiModeEngine disables the inactive engine module.  Both OnUpdate and OnFixedUpdate will only fire if isEnabled is true, but OnFixedUpdate has another check of part.state == PartStates.ACTIVE.

Interesting. I had a PartModule that was definitely not Enabled in the editor and was not running OnUpdate.

Share this post


Link to post
Share on other sites
On 8/5/2016 at 9:46 AM, DMagic said:

Also note that OnStart is called after Load, so all persistent KSPFields are already parsed for OnStart

I will caution that this needs testing, I've run into inconsistent behavior where sometimes Load runs then Start, and sometimes Start runs then Load depending on which mode (Editor/Flight) you are in.

This may have been on a KSPAddon instead of a PartModule also, but as they are so similar it is something to be aware of.

D.

Share this post


Link to post
Share on other sites
2 hours ago, Diazo said:

I will caution that this needs testing, I've run into inconsistent behavior where sometimes Load runs then Start, and sometimes Start runs then Load depending on which mode (Editor/Flight) you are in.

This may have been on a KSPAddon instead of a PartModule also, but as they are so similar it is something to be aware of.

D.

This has been pretty consistent with PartModules in my experience.  If Load is called at all, it is before Start.  Load isn't called in the case when you're creating a new part in the editor from the part catalog.

Share this post


Link to post
Share on other sites
On 4.8.2016 at 3:34 AM, blowfish said:

This loads any fields with the [KSPField] attribute automatically, and calls OnLoad(ConfigNode) with the same node to allow parsing of any custom data.

I'd like to execute code on the prefab before parts are copied from it. In my tests with the TweakScale partModule it looks like OnLoad is not called in the editor. I put a simple debug output at the start of the method and it appears in flight (so I guess that OnLoad is declared correctly) but not in the editor. Am I doing something wrong?

Source: https://github.com/pellinor0/TweakScale/blob/master/Scale.cs

The code already postpones its setup to OnStart when in the editor, so this behavior is probably not new.

Edit: Found it, earlier in the log than expected. And the one from the flight scene is probably for loading the persistent data from the craft file. Slowly things start to make sense...

Edited by pellinor

Share this post


Link to post
Share on other sites

@pellinor Yep, sounds like you mostly figured it out.  Load/OnLoad may not be called in the editor at all, depending on whether the part is freshly created or from a craft/subassembly.  In flight, it will be called to load data from the saved craft.

For code I want to run on the prefab (e.g. icon setup), I usually use OnLoad, but have some code to detect whether it's being run on the prefab or not.  At that point in the part's life, part.partInfo will be null (though that's not the only way to check).

Edited by blowfish

Share this post


Link to post
Share on other sites
44 minutes ago, Van Disaster said:

Copying seems to call OnInitialize(), is there anywhere else?

It seems to be called any time a part is created in the editor or in flight (but only after the entire vessel is loaded if loading a vessel).  I haven't tested extensively though, so I could be wrong about this.

Share this post


Link to post
Share on other sites

I realized something interesting while playing around with using RESOURCE nodes and ModuleResource to handle all resource usage for my parts. Whenever a new part is spawned in the editor or loaded during flight, it is instantiated from the prefab (obviously...), the interesting part is that this clones the prefab's Part Modules and carries along all public fields to the new part.

KSPFields, obviously get cloned, but also, any public field will get cloned.

For instance, I was adding RESOURCE nodes directly to the SCANsat module's in the config files and loading the data in OnLoad:

public List<ModuleResource> resourceInputs = new List<ModuleResource>();
  
public override void OnLoad(ConfigNode node)
		{
			if (node.HasNode("RESOURCE"))
				resourceInputs = new List<ModuleResource>();
			else
				return;

			ConfigNode[] resources = node.GetNodes("RESOURCE");

			int l = resources.Length;

			for (int i = 0; i < l; i++)
			{
				ConfigNode resource = resources[i];
				ModuleResource mod = new ModuleResource();
				mod.Load(resource);
				resourceInputs.Add(mod);
			}
		}

When OnLoad is run for the prefab it loads its data directly from the config file, which has a module like this:

	MODULE
	{
		name = SCANsat
		...
		RESOURCE
		{
			name = ElectricCharge
			rate = 0.4
		}
	}

So it sees the RESOURCE node and populates the ModuleResource list. But that RESOURCE node is never saved to the persistent file, or in a craft file (unless you manually save it), so the resourceInputs list is only ever set when loading the prefab, not when spawning a new part. If you set that list to private it doesn't work anymore, so only public fields can be cloned.

I guess it's sort of obvious if you are familiar with cloning and how Unity instantiates game objects. But since a lot us probably aren't so familiar with that it could be useful information. And it sort of relates to the order of PartModule methods, since the prefab OnLoad can be thought of as a different, earlier version of OnLoad for an active vessel.

Just to be clear, the ConfigNode that is being passed to the OnLoad method comes from the .cfg file for the part when loading the prefab, and it comes from the save file when loading a vessel with that part. The only fields that are written to the save file are KSPFields with IsPersistent = true, and anything that you manually save.

Share this post


Link to post
Share on other sites

@DMagic Correct, any public field of a type that Unity can serialize will be copied from the prefab to the instantiated part/module.  This actually has nothing to do with KSPField, it's all Unity's serialization.  If you don't want it serialized, you can mark the field [NonSerialized] ... similarly, if you want it to serialize a private field you can mark it [SerializeField]

Share this post


Link to post
Share on other sites

There are some unserializeable things which get copied too when there's a clone, I noticed - Material is one that comes to mind, which has a method specifically for copying values - so somewhere in there is something extra, I think.

Edited by Van Disaster

Share this post


Link to post
Share on other sites
1 hour ago, Van Disaster said:

There are some unserializeable things which get copied too when there's a clone, I noticed - Material is one that comes to mind, which has a method specifically for copying values - so somewhere in there is something extra, I think.

Material derives from UnityEngine.Object, meaning that it's serializable.  There are weird cases like Vector3 where Unity's documentation doesn't necessarily say explicitly that it's serializable but it is.  I think as a general rule most built-in Unity types are serializable, however.

Share this post


Link to post
Share on other sites

Hmm, OK - VS explicitly tells me UnityEngine.Material isn't serializable, but the chance of me having a setup issue there is probably high.

Share this post


Link to post
Share on other sites
6 hours ago, Van Disaster said:

Hmm, OK - VS explicitly tells me UnityEngine.Material isn't serializable, but the chance of me having a setup issue there is probably high.

.NET has its own serialization system, and under that Material isn't serializable (since it doesn't have the [Serializable] attribute).  Unity's serialization system is completely separate, and I would be surprised if VS knew anything about it.

Edited by blowfish

Share this post


Link to post
Share on other sites
On 19/08/2016 at 11:10 PM, blowfish said:

It seems to be called any time a part is created in the editor or in flight (but only after the entire vessel is loaded if loading a vessel).  I haven't tested extensively though, so I could be wrong about this.

Usually called after Awake() - loading a craft into the flight scene ( well the active craft ) seems to call it twice, one after Awake() & one after the ship is placed. Called between OnLoad() & OnSave() when loading a saved craft into the editor.

Edited by Van Disaster

Share this post


Link to post
Share on other sites

I am trying to write a partmodule and I am having problems saving the node

during OnLoad I read the node and store the data on  public ConfigNode of the module (this.node)

The problem is that the information is not there anymore at OnSave

If i print the this.node it is empty

 

What should I do to keep a field retain its value between OnLoad and OnSave?

Share this post


Link to post
Share on other sites
3 hours ago, Sigma88 said:

I am trying to write a partmodule and I am having problems saving the node

during OnLoad I read the node and store the data on  public ConfigNode of the module (this.node)

The problem is that the information is not there anymore at OnSave

If i print the this.node it is empty

 

What should I do to keep a field retain its value between OnLoad and OnSave?

The idea is that information will be stored in instance variables between the two (loaded into instance variables from the node in OnLoad, and then saved back into the node in OnSave).  For KSPField fields, it will only save those with isPersistent = true (i.e. those that might be modified by user actions)

Share this post


Link to post
Share on other sites
3 minutes ago, blowfish said:

The idea is that information will be stored in instance variables between the two (loaded into instance variables from the node in OnLoad, and then saved back into the node in OnSave).  For KSPField fields, it will only save those with isPersistent = true (i.e. those that might be modified by user actions)

I solved by generating a static database of objects, they are too complicated to be saved on cfg

Share this post


Link to post
Share on other sites
This thread is quite old. Please consider starting a new thread rather than reviving this one.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.