Kramer

[1.0.4] Kramax Plugin Reload: plugin development tool for rapid prototyping

Recommended Posts

V0.1 Download: GitHub

Kramax Plugin Reload

I started developing an autopilot mode (Kramax Autopilot) and I normally develop software using a very quick modify/test cycle. I quickly realized that waiting for KSP to restart every time I wanted to try out a change I had made would make it take so long to develop that I would probably give up first. I looked around for a solution that would allow a plugin developer to dynamically reload the compiled plugin without restarting KSP.

I found "KSPPluginReload" by m1nd0. Unfortunately I could not get it to work properly with the way my other mod was structured. The mod used a MonoBehavior sub-class and it seems that there isa bug in Unity that made it instantiate the old version of my objects even when I loaded a new DLL. The only way I was able to get it to work was to actually dynamically change the class names of my components every time the mod was loaded. This works for a plugin that uses KSPAddon to start and stop. It will NOT work with a plugin that uses sub-classes of VesselModule to work. Your sub-classes MUST be direct sub-classes of MonoBehavior.

Instructions

First you need to setup your project. I setup the "Debug" build of the project to use plugin reload and the "Release" build to NOT use it. This way you can debug with the reloadable mod and release your mod to the world without it.

To do this you need to first add the assembly "KramaxReloadExtensions" that is in this mod as a reference assembly.This assembly is used in your debug build to do the DLL reloading.Next you need to copy the file "ReleaseReloadableMonoBehaviour.cs" into your project. This file is used in release mode when you want to release your mod to the world.

Sadly in order to make this work you will have to hand edit your "csproj" to make the use of the KramaxReloadExtensions assembly conditional based on build configuration. You need to find the line the csproj file that is like this:


<Reference Include="KramaxReloadExtensions">
<HintPath>..\..\KramaxPluginReload\bin\Debug\KramaxReloadExtensions.dll</HintPath>
</Reference>

and change it to this:


<Reference Condition="'$(Configuration)' == 'Debug'" Include="KramaxReloadExtensions">
<HintPath>..\..\KramaxPluginReload\bin\Debug\KramaxReloadExtensions.dll</HintPath>
</Reference>

Using ReloadableMonoBehaviour

Your sub-classes which inherit from MonoBehavior need to inherit from this mods class ReloadableMonoBehaviour instead. ReloadableMonoBehaviour is a direct sub-class of MonoBehavior that adds a "type mapping" property that is used to ensure the correct class types are used for the DLL that is reloaded.

Next, you need to change any calls to GameObject.AddComponent to use the method provided by ReloadableMonoBehaviour ReloadableMonoBehaviour.AddComponent(type).

For example, in my autopilot, my main top-level object is a MonoBehavior. It looked like this:


[KSPAddon(KSPAddon.Startup.Flight, false)]
public class KramaxAutoPilot : MonoBehaviour
{
public void Start()
{
mainPilot = gameObject.AddComponent<George>();
}
...
}

This needed to be changed to this:


[KSPAddon(KSPAddon.Startup.Flight, false)]
public class KramaxAutoPilot : ReloadableMonoBehaviour /* note changed baseclass here */
{
public void Start()
{
mainPilot = AddComponent<type(George)> as George; /* note AddComponent call and cast */
}
...
}

If you use any other varient of AddComponent they also need to be changed.

Installation

This mod needs to be installed in your KSP GameData directory as a normal mod would be installed.Normally it would go into GameData/GramaxPluginReload. The DLL files will be in GameData/GramaxPluginReload/Plugins including both KramaxPluginReload.dll and KramaxReloadExtensions.dll. The Settings.cfg file in GameData/GramaxPluginReload is used to configure where the plugin you are developing gets loaded from. You can just copy the folder https://github.com/Kramax/KramaxPluginReload/tree/master/GameData/KramaxPluginReload to your KSP GameData folder.

Here is a sample entry for my autopilot mod:


PluginSetting
{
name = KramaxAutoPilot
path = C:\root\DevKSP\KramaxAutoPilot\Source\bin\Debug\KramaxAutoPilot.dll
loadOnce = false
methodsAllowedToFail = false
}

You should change the name and path to match your plugin. The other settings should be left alone.You can have multiple PluginSetting objects in the config file for more than one plugin.Note the path can be outside of your KSP install directories--in fact using the place where your compiled DLL gets put works best.

Reloading

The plugin reload mod will create a UI window with a title "Plugins" and one button "Reload plugins".Simply recompile your mod and press the button. It should load the new version of your DLL. If you do not actually create a new DLL the reload will not work.

How It Works

When it reloads (or loads the first time) your plugin DLL it does the following:

Example

I know this all sounds pretty confusing. You can look at my autopilot mod for a real-world example.The source is found at: https://github.com/Kramax/KramaxAutoPilot.

Attributions

This plugin is a modified version of "KSPPluginReload" by m1nd0.Many thanks for an excellent starting point for this mod.

License

This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.

The file "ReleaseReloadableMonoBehaviour.cs" is excepted from this license and is released into the public domain.

This work is a derivative of "KSPPluginReload" by m1nd0 that was distributed under the same license.The original work was found at https://github.com/m1ndst0rm/KSPPluginReload.

Share this post


Link to post
Share on other sites

That's... a lot of crazy in there. I'm glad you can make sense of it, cause it makes my head hurt.

Share this post


Link to post
Share on other sites

Wow, I'll have to try this. I hate the long reload times.

Share this post


Link to post
Share on other sites
That's... a lot of crazy in there. I'm glad you can make sense of it, cause it makes my head hurt.

Yes, I almost gave up on it when I discovered this bug in Unity where AddComponent<type> seems to get the string name of the type and then look that up to actually create an instance. And it always used the initial version of the class instead of the newly loaded one. But then I realized that C# allows you to generate code dynamically and wondered what would happen if I created an uniquely named class on the fly and it worked! The ability to "emit" code is a pretty cool feature of C#. There is nothing like that in C++.

- - - Updated - - -

Wow, I'll have to try this. I hate the long reload times.

Let me know if you have any problems or questions.

Share this post


Link to post
Share on other sites

better put this in the add-ons development thread... that way.. it will not get lost ;)

Share this post


Link to post
Share on other sites
better put this in the add-ons development thread... that way.. it will not get lost ;)

Oops, my bad. Did not realize there was a forum specifically for that. How do I get an admin to move the post? I presume I cannot do that myself?

-Thanks

Share this post


Link to post
Share on other sites

Rapid deployment/testing is a huge efficiency multiplier! With this and the autopilor lander, you are on fire Kramer! Great job!

Share this post


Link to post
Share on other sites

I am not sure if I understand the mechanic of this mod correctly. I try to get a simple mod (display debug info) working


[KSPAddon(KSPAddon.Startup.SpaceCentre, false)]
public class MyTestDebug : ReloadableMonoBehaviour
{
public void Start()
{
Debug.Log("MyTestDebug: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
}
}

When I change the contents of the log-message, recompile, reload plugin then still the initial debug message appears.

According to the log-messages, the new plugin is reloaded

[Log]: KramaxPluginReload.PluginClass.CreateInstance create object MyTestDebug_2_.

Is it essential for this to work, to use "AddComponent" or is there something else I am missing?

Share this post


Link to post
Share on other sites
I am not sure if I understand the mechanic of this mod correctly. I try to get a simple mod (display debug info) working


[KSPAddon(KSPAddon.Startup.SpaceCentre, false)]
public class MyTestDebug : ReloadableMonoBehaviour
{
public void Start()
{
Debug.Log("MyTestDebug: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
}
}

When I change the contents of the log-message, recompile, reload plugin then still the initial debug message appears.

According to the log-messages, the new plugin is reloaded

[Log]: KramaxPluginReload.PluginClass.CreateInstance create object MyTestDebug_2_.

Is it essential for this to work, to use "AddComponent" or is there something else I am missing?

It sounds like you are doing the right things. Anyway you can post more of the KSP.log file? And if it is easy to put the entire project up somewhere I could look over all the source.

Share this post


Link to post
Share on other sites

Just a note to those following this--the moderators moved it to the Add-on Development forum at my request.

Share this post


Link to post
Share on other sites
It sounds like you are doing the right things. Anyway you can post more of the KSP.log file? And if it is easy to put the entire project up somewhere I could look over all the source.

Thanks for letting me know that I am on the right track. There are other classes in my project - maybe they interfere. I will report back after digging deeper into this.

Share this post


Link to post
Share on other sites

Now I was able to get a clean, new project working in Visual Studio 2013 Community.

The thing I had to do was to manually increment the version number in the file Properties/AssemblyInfo.cs from

[assembly: AssemblyVersion("1.0.0.0")]

to

[assembly: AssemblyVersion("1.0.0.1")]

and for each compilation after that.

Or as I just found out how this can be accomplished more easily:

Replace in the file Properties/AssemblyInfo.cs

[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

by

[assembly: AssemblyVersion("1.0.*")]

and get it incremented automatically.

Now I have another question: From what you have learned during your implementation, do you think it is feasible to implement a "ReloadablePartModule" for the PartModule class in the same way as "ReloadableMonoBehaviour" works for MonoBehaviour?

Edited by mhoram

Share this post


Link to post
Share on other sites
Now I was able to get a clean, new project working in Visual Studio 2013 Community.

The thing I had to do was to manually increment the version number in the file Properties/AssemblyInfo.cs from

[assembly: AssemblyVersion("1.0.0.0")]

to

[assembly: AssemblyVersion("1.0.0.1")]

and for each compilation after that.

Or as I just found out how this can be accomplished more easily:

Replace in the file Properties/AssemblyInfo.cs

[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

by

[assembly: AssemblyVersion("1.0.*")]

and get it incremented automatically.

Now I have another question: From what you have learned during your implementation, do you think it is feasible to implement a "ReloadablePartModule" for the PartModule class in the same way as "ReloadableMonoBehaviour" works for MonoBehaviour?

Glad you got it working---I forgot that you had to do that to make it work. I will edit the readme to point that out. Sorry about that.

The main issue with the implementation is that you have to be in control of the calls to make components.

I am not sure if that would be the case for part modules. I know it is a problem for vessel modules because

the KSP internals are creating the component on their own and I cannot intercept it. I have a feeling the same

may be true of part modules. But I am just guessing--I have never created a part module so I am not

familiar with the way it works.

Share this post


Link to post
Share on other sites

Updating the readme would be beneficial for others who try this.

Thanks for your insight about PartModule and vessel reloading.

Share this post


Link to post
Share on other sites
Note the path can be outside of your KSP install directories--in fact using the place where your compiled DLL gets put works best.

It did not work at all if I left my DLL in GameData. I removed my DLL from GameData and pointed the Settings.cfg at my DLL outside of GameData, and my mod loaded normally and "reload plugins" seems to work - I changed the window title and hit reload, and sure enough I saw the new window title. Amazing!

FYI I also had to add "using KramaxReloadExtensions;" to each file, otherwise ReloadableMonoBehavior wasn't visible. I'm using Xamarin Studio on OS X, if it makes a difference. I wrapped it all in #if DEBUG statements:


#if DEBUG
using KramaxReloadExtensions;
#endif


[KSPAddon(KSPAddon.Startup.EditorVAB, false)]
#if DEBUG
public class AutoAsparagus: ReloadableMonoBehaviour
#else
public class AutoAsparagus: MonoBehaviour
#endif

There was some weird UI behavior that appeared only when using KramaxReloadExtensions. First, my window normally puts itself in the middle of the screen (new Rect(Screen.width * 0.35f,Screen.height * 0.1f,1,1);), but while using this it just plopped itself in the top left. Second, when my window was visible and I hovered over a part, my window disappeared and the part info window was broken:

lXduvztl.jpg

This was accompanied by a whole lot of log spam:

[EXC 23:22:38.072] ArgumentException: GUILayout: Mismatched LayoutGroup.Repaint
UnityEngine.GUILayoutUtility.BeginLayoutGroup (UnityEngine.GUIStyle style, UnityEngine.GUILayoutOption[] options, System.Type LayoutType)
UnityEngine.GUILayout.BeginVertical (UnityEngine.GUIContent content, UnityEngine.GUIStyle style, UnityEngine.GUILayoutOption[] options)
UnityEngine.GUILayout.BeginVertical (UnityEngine.GUILayoutOption[] options)
PartListTooltips.DrawTooltip (Int32 id)
UnityEngine.GUILayout+LayoutedWindow.DoWindow (Int32 windowID)
UnityEngine.GUI.CallWindowDelegate (UnityEngine.WindowFunction func, Int32 id, UnityEngine.GUISkin _skin, Int32 forceRect, Single width, Single height, UnityEngine.GUIStyle style)

If I don't hover over a part, it looks fine:

xAfXVY4l.jpg

Neither of these problems appear when loading my mod normally. Neither are deal breakers, but I figured I'd report them.

I love this mod... I have some logic changes I've been putting off because it takes so long to test them, and I knocked out a huge portion tonight just because I could change one line, compile, reload, and test all in about 15 seconds, instead of the 5 minutes it would normally take.

Thank you so much for making this!!

Share this post


Link to post
Share on other sites
On 11/22/2015 at 6:30 PM, rhoark said:

Does this work with 1.0.5?

Yep!  Just tested it.

Share this post


Link to post
Share on other sites

I just tested this mod with 1.2pre - and it works!

@Kramer: thank you so much.

Edited by AndyMt

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.