KSP-ModularManagement / KSPe

Extensions and utilities for Kerbal Space Program
http://ksp.lisias.net/add-ons/KSPAPIExtensions
Other
11 stars 6 forks source link

Rework the Loading Mechanism #50

Closed Lisias closed 1 year ago

Lisias commented 1 year ago

The KPSe.InstallChecker stunt is nice, but it have a flaw:

When the user updates KSP from CurseForge or GitHub, the first boot will have two KSPe.dll on memory - and so, the KSPe's initialisation was happening twice.

This is less then ideal, but not exactly (too much) bad because the Install Checker will get rid of one of them after updating and then will ask the user for rebooting KSP. However, I left one use case unhandled: when the user updates https://github.com/net-lisias-ksp/ModularManagement but KSPe itself wasn't upgraded in the process, and so the Install Checker just removes the KSPe.dll inside GameData/000_KSPe and call it a day.

And so we have two KSPes in memory, being initialised twice, what means it will try to load the auxiliary DLLs twice. An annoyance on KSP >= 1.8.0, but a problem on KSP < 1.8.0 as it will load KSPe (and the auxiliary DLLs) on a new AppDomain slowing up everything until the next KSP boot.

Things got a bit worse with the implementation of the Task #49 , however. Now the second KSPe that was silently being reloading the auxiliary DLLs are being yelled at, and so I broke the update process.

Task #49 is important, it will help me (and eventual clientes) to avoid messing up the loading process, as well be yet a new barrier against a really nasty installation problem where multiple copies if the Add'On is installed by accident on the GameData.

So the best way out of the mess is to implement a Loader for what is, now, KSPe.dll (or 000_KSPe.dll) ~and move it to PluginData, becoming an auxiliary DLL itself.~ This new Loader MUST:

This will give us the following benefits:

Solution implemented:

Lisias commented 1 year ago

The Loader can't have its own versioning, otherwise it will break clients using KSPAssmblyDependency. The requirements were updated.

Lisias commented 1 year ago

This is not going to fly.

In C++, we can write a Module Initialiser function easily, but on C# this is extremely convoluted unless you are using C# 9.

It doesn't worth the hassle - at least, for now.

More info: https://stackoverflow.com/questions/505237/net-running-code-when-assembly-is-loaded

Lisias commented 1 year ago

The work done for this issue is preserved on the branch https://github.com/net-lisias-ksp/KSPe/tree/experimental/issue_50 for historical reference.

Lisias commented 1 year ago

this tasked ended up being a duplicate from #41 (I had forgot about that one!)

Lisias commented 1 year ago

I had drawn myself to a corner. I will need to tackle this down somehow, or I will get screwed when updating CurseForge.

Lisias commented 1 year ago

Thanks the problem:

Besides the idiosyncrasies on how KSP load things (and that got worse on KSP >= 1.8), I let a important detail pass trough: the KSPAssemblyVersion.

KSPe.UI, KSPe.HMI, etc, all of them rely on the KSPe KSPAssembly directive to control dependency, not to mention the client add'ons that rely on it too.

On a CurseForge update, these Assemblies are bumped up but the 000_KSPe.dll is still one version behind, and so they are not loaded, exploding Exceptions everywhere and preventing KSPe.InstallChecker from doing it's job (what's ironic, as I had managed to solve the KSP's problems on it by now - #sigh).

So, now, no matter the trouble I'm going to have, I need to have this issue tackled down as it appears to be my only chance of doing a clean job.

This is going to hurt….

Lisias commented 1 year ago

Part of the problem (running code when the DLL is loaded) is solved by using:

I'm not a huge fan of external dependencies (not to mention a brittle system as NuGet, creating a hard dependency on external repositories that can go dark at any moment), but at least on the short run this is working. I can reevaluate the dependencies later.

Lisias commented 1 year ago

This is not going to fly the way I initially intended.

I can load an Assembly without problems on the ModuleInitializer using System.Reflection.Assembly.Load(bytes[]). What I can't do is loading a KSP Plugin (AssemblyLoader.LoadPlugin) on the damned thing without a huge breakage on everybody later:

[EXC 19:16:28.043] NullReferenceException: Object reference not set to an instance of an object
        KSPAPIExtensions.SystemUtils+<>c__DisplayClass1_0.<TypeElectionWinner>b__0 (.LoadedAssembly ass)
        System.Linq.Enumerable+<CreateWhereIterator>c__Iterator1D`1[AssemblyLoader+LoadedAssembly].MoveNext ()
        System.Linq.Enumerable+<CreateSelectIterator>c__Iterator10`2[AssemblyLoader+LoadedAssembly,<>f__AnonymousType0`2[AssemblyLoader+LoadedAssembly,System.Type]].MoveNext ()
        System.Linq.Enumerable+<CreateWhereIterator>c__Iterator1D`1[<>f__AnonymousType0`2[AssemblyLoader+LoadedAssembly,System.Type]].MoveNext ()
        System.Collections.Generic.List`1[<>f__AnonymousType0`2[AssemblyLoader+LoadedAssembly,System.Type]].AddEnumerable (IEnumerable`1 enumerable)
        System.Collections.Generic.List`1[<>f__AnonymousType0`2[AssemblyLoader+LoadedAssembly,System.Type]]..ctor (IEnumerable`1 collection)
        System.Linq.Enumerable.ToArray[<>f__AnonymousType0`2] (IEnumerable`1 source)
        System.Linq.QuickSort`1[<>f__AnonymousType0`2[AssemblyLoader+LoadedAssembly,System.Type]]..ctor (IEnumerable`1 source, System.Linq.SortContext`1 context)
        System.Linq.QuickSort`1+<Sort>c__Iterator21[<>f__AnonymousType0`2[AssemblyLoader+LoadedAssembly,System.Type]].MoveNext ()
        System.Linq.Enumerable+<CreateSelectIterator>c__Iterator10`2[<>f__AnonymousType0`2[AssemblyLoader+LoadedAssembly,System.Type],System.Type].MoveNext ()
        System.Collections.Generic.List`1[System.Type].AddEnumerable (IEnumerable`1 enumerable)
        System.Collections.Generic.List`1[System.Type]..ctor (IEnumerable`1 collection)
        System.Linq.Enumerable.ToArray[Type] (IEnumerable`1 source)
        KSPAPIExtensions.SystemUtils.TypeElectionWinner (System.Type targetCls, System.String assemName)
        KSPAPIExtensions.OnEditorUpdateUtility..cctor ()
        Rethrow as TypeInitializationException: An exception was thrown by the type initializer for KSPAPIExtensions.OnEditorUpdateUtility
        KSPAPIExtensions.OnEditorUpdateUtility_1_7_5_108..ctor ()
        UnityEngine.GameObject:AddComponent(Type)
        AddonLoader:StartAddon(LoadedAssembly, Type, KSPAddon, Startup)
        AddonLoader:StartAddons(Startup)
        <LoadObjects>c__Iterator1:MoveNext()
        UnityEngine.MonoBehaviour:StartCoroutine(IEnumerator)
        <CreateDatabase>c__Iterator0:MoveNext()
        UnityEngine.MonoBehaviour:StartCoroutine(IEnumerator)
        GameDatabase:StartLoad()
        <LoadSystems>c__Iterator0:MoveNext()
        UnityEngine.MonoBehaviour:StartCoroutine(IEnumerator)
        LoadingScreen:Start()

What kinda make sense, after all, as I was trying to load a plugin while a plugin was being loaded, I surelly broke something internal on the AssemblyLoader.LoadPlugin.

So, nope. I'm not going to dynamically loading KSPe. KSPe.Loader is dead, another approach is needed.

Lisias commented 1 year ago

The experimental/issue_50 branch was updated with the most recent code.

Lisias commented 1 year ago

Well… It ended not being exactly what I wanted, but it works (almost) the same. The ModuleInitializer stunt guarantee that this code is run before anything else due a happy coincidence on how KSP load and start up things (TL;DR: I'm cheating the Assembly Loader/Resolver by initialising the InstallChecker before it initialises this little world).

This way, I managed to display a Modal asking the user to restart KSP before anything else could do it (including KSPe itself).

Foreseeing that perhaps this stunt could need to be reversed by any reason, I'm also moving the KSPe.InstallChecker to be loaded before KSPe itself on GameData, so even by removing the ModuleInitializer stunt, things still will work as intended in the future releases.

Lisias commented 1 year ago

An emergencial release based on the 2.5.2.0 (2.5.2.1 and 2.5.2.2 were scrapped) will be issued, reducing to the minimum any changes on KSPe to allow easier diagnosing if anything goes South on the damned thing.

Lisias commented 1 year ago

Implemented on commit https://github.com/net-lisias-ksp/KSPe/commit/50cf7f31ecf495a4c0405dad326bfb1d43ba90ac .

Lisias commented 1 year ago

Worked as intended on

So I'm assuming it will work fine on everything else (as long is supported).