Skip to content

Writing plugins for RasterPropMonitor

Mihara edited this page Oct 9, 2014 · 14 revisions

Having struggled enough with Squad's silly plugin loader, I know it can be painful to link to a foreign plugin in KSP. The problem with integrating multiple plugins in KSP is that while normally, you would just reference a plugin and call it's member classes directly, KSP plugin loader will reject your plugin upon loading if the referenced plugin is not already loaded. You can ensure your plugin loads later than the plugin you're referencing, because KSP loads them in alphabetical order, but then, if the referenced plugin is not present, yours won't load at all, which is in many situations unacceptable. You can make a separate assembly, that references both your assembly and the plugin you wish to interface to, but that approach has other drawbacks, namely that by sidestepping this restriction you doom yourself to having to recompile your interface shim every time the plugin you wish to interface to changes. This problem can be solved using reflection and delegate functions, but the solution is quite cumbersome. Well, the trick with RPM API is that:

  • You don't have to hard link to RPM. You don't need to reference RPM's DLL. You don't stop working if RPM is not installed, and you don't need a separate assembly for interfacing to RPM.
  • You don't need to know anything about reflection, either. RPM knows enough about it and will take care of all the dirty details, as long as you present a configuration file and methods RPM can call.

Interfacing to RPM is done by creating classes that contain methods with specific signatures -- that is, which take a certain list of parameters in a specific order, and return specific things -- and writing RPM configuration files which tell RPM what the names of all these things are. RPM will then take care of instantiating those classes as appropriate and calling the methods you name. These classes can be part of your own assembly, and are thus allowed to directly manipulate the internals of your plugin that otherwise does something independently. As long as RPM API doesn't change, which isn't expected to happen, you can expect to continue working with any conceivable future version of RPM, as long as one exists, with no changes. It hasn't changed since 0.10, so I have a pretty good track record on that.

The mechanism is in almost all cases the same:

		PAGE
		{
                            ...
			SOMETHINGANDLER
			{
				name = YourClassName
				method = TheMethodToCall
			}
		}
  • When seeing such a configuration block, RasterPropMonitor (except where explicitly stated otherwise) takes care of locating your class, creating an instance of it and attaching it to a game object in KSP. (In almost all cases, an IVA prop, which is why your class usually needs to be an InternalModule)
  • Except where stated otherwise, the *HANDLER {} block is passed to the module as if it were a MODULE {} block in a regular configuration file. Anything inside it is accessible in your class as a regular [KSPField]. That's the expected way for your plugin to receive configuration parameters.
  • Certain reserved words in that block contain parameters for RasterPropMonitor, telling it about other interesting methods to call on that class when something happens (for example, the user presses an RPM button) or is needed to display a screen. The full list differs depending on the type of handler and what it's supposed to do. Since they also make it to KSPFields, you can even see what they were.
  • The classes are perfectly regular classes otherwise, and as long as they respond to RPM's calls, they can do whatever they please.

There is one little thing you want to be aware of: RPM provides options to create transparent pods. (JSITransparentPod module) Due to the way this works, this may cause your InternalModule plugin to be instantiated while the ship is still in editor, where quite a few things you come to rely on while in flight do not yet exist. Be aware of that and code accordingly. The recommended way to deal with that is to just put that at the start of your initialization and update methods:

if (HighLogic.LoadedSceneIsEditor)
    return;

That will prevent your plugin from activating in the editor and spamming exceptions.

The entire mechanism is used internally for much of RPM's own more advanced functions, so you can see examples in RPM's own code -- every handler works as one, but JSIFlightLog.cs is particularly instructive, as it's commented throughout specifically for this purpose. The particular things that can be plugged into are:

  • Page handlers -- Modules that tell RPM what to print on screen.
  • Background handlers -- Modules that get an RPM screen to draw graphics on.
  • Action handlers -- Modules that can be triggered by RPM-driven switches to perform an action of any kind and report any kind of on/off state.
  • Variable handlers -- Modules that add extra variables that RPM pages can use.

What if I do need to hard link to RPM anyway?

If you do, you need to know about KSPAssembly and KSPAssemblyDependency. These are supposed to help with plugin loading order, at the very least -- if you have a dependency statement in your plugin, and the plugin you depend on has a KSPAssembly statement, they will not be broken by loading out of alphabetical order. In the AssemblyInfo.cs for the main RasterPropMonitor, you can find these lines:

// This supposedly helps avoid plugin depencency problems.
[assembly: KSPAssembly("RasterPropMonitor", 0, 17)]

Check the code for the actual version number given in this statement, then in your own AssemblyInfo.cs write this:

// This depends on RPM 0.17...
[assembly: KSPAssemblyDependency("RasterPropMonitor", 0, 17)]

It's a good idea to always have your own KSPAssembly statement and not to change the version number unless you have altered the public API.