SMAPI icon indicating copy to clipboard operation
SMAPI copied to clipboard

Support for weak DLL mod dependencies

Open Shockah opened this issue 2 years ago • 3 comments

Currently, SMAPI will straight out disallow loading of mods with assembly references which could not be loaded. This means weak/optional dependencies on code (.dll) mods do not actually do anything and act like strong/required dependencies. This PR changes that.

A dependency can now have an additional ProvidedAssemblies field, with a list of assembly names that dependency provides. Any assemblies which cannot be resolved that are also listed in this way will be ignored, and the mod will be allowed to continue loading.

The mod author still has to make sure to never call any methods referencing types from those assemblies, nor declare any members referencing those types directly. Doing so will throw an exception. Those types can be used just fine as method local variable types. Values can still be returned and stored in properties, as long as they are upcasted to object (or any other type from a resolvable assembly). Then, they can be downcasted again to their respective type inside a method body.

Shockah avatar Mar 25 '22 18:03 Shockah

Thanks for the pull request!

I'm open to weak assembly references, but I'm hesitant to do it with this approach.

I try to create a pit of success with SMAPI's design — i.e. make it easier to do the right thing than to make mistakes that'll destabilize the player's modding experience. With this approach the 'pit' authors fall into by default would be runtime errors, since it'd require strict discipline, testing, and knowledge of how .NET handles assembly references to only use the assemblies when they're available.

A similar approach that solves that could be creating a separate 'bridge' project, which is bundled into the mod folder too. That bridge DLL could reference both the main mod DLL and the dependency, and would only be loaded if the dependency is available. That's pretty easy to implement (it's a simple extension to SMAPI's existing mod loading), is easy to validate for SMAPI, is easy to maintain for the mod author, and the .NET type system itself prevents the author from accidentally using the soft dependency when it's not available.

For example, the manifest.json could something look like this (for the main mod, no manifest is needed for bridge DLLs):

"EntryDll": "ProducerFrameworkMod.dll",
"DependencyBridges": [
    {
        "EntryDll": "PFMAutomate.dll",
        "WhenInstalled": [ "Pathoschild.Automate" ]
    }
]

And then the bridge DLL can do anything needed to fully integrate the mods — call their APIs, directly access the internals of the main mod using InternalsVisibleTo, pass interface implementations to the main mod, etc.

Pathoschild avatar Mar 26 '22 07:03 Pathoschild

My main issue with the "bridge" project is that... it's another project. At that point it's basically no different from creating a second "actual mod" project, depending on both the base mod and the actual dependency. The only difference is that you'll be getting a "cannot load mod" error for the "actual mod" if the dependency is not loaded, but functionally they're exactly the same.

So now you gotta have a separate project in the solution to worry about (which might not be much of a problem if you only have one such dependency, but imagine having multiple... also imagine having a feature requiring dependency A, then a feature requiring dependency B, then a feature requiring BOTH dependencies - now you have four projects for this one mod, and the more optional dependencies you have (and features requiring multiple dependencies at once), the worse it gets).

Also packaging becomes a big annoyance - in theory this could be somehow handled? But then that's a lot of work on the ModBuildConfig side. If that's being done, I'd rather see some accomodations for content pack authors to be done first, as they also struggle with packaging their mods consisting of multiple content packs for different mods.

Shockah avatar Mar 26 '22 07:03 Shockah

There are ways to bundle the bridge project into the mod folder automatically, so it's just magically part of the release package (and it could be handled by the mod build config package if it became a feature). It's quite a bit simpler than having multiple mod pages/downloads/packages to manage.

Having multiple projects in a solution isn't an issue, since that's pretty much the point of a solution. SMAPI has 16 projects for example, and I've worked on repos with 150+ projects.

Your argument applies to a single project too. Having these soft dependencies manually managed within one project becomes exponentially more difficult to maintain, since you need to mentally keep track of every combination of mods where each bit of code might or might not be available, instead of properly decoupling it into a modular project.

Packaging for content packs is certainly something I want to look into too, but unrelated to this PR.

Pathoschild avatar Mar 26 '22 15:03 Pathoschild

I'll close this per the above comments, but we can keep discussing possible solutions on Discord. Thanks for the PR though!

Pathoschild avatar Oct 09 '22 16:10 Pathoschild