[Feature request] Plugin IPC #707
Replies: 1 comment
-
Personally, I'd say Reflection can solve most IPC use cases for .NET mods. It's not terribly performant compared to direct calls, but IPC usually doesn't need to be. IMO, if you're making a mod that can be used as a soft dependency, create a public API the same way you would for a hard dependency, and don't make breaking changes to that public API without a major version bump. Then, soft dependendents can just check your version and make calls via Reflection (via Traverse, usually) - it's not hard to do all this already in BepInEx 5. Unless there's something that a system like this gives over that, I'm not sure it'd be worth the effort to build (and maintain) such a system. |
Beta Was this translation helpful? Give feedback.
-
Plugins that want to communciate with each other currently have no easy way of doing so. I've come up with a few conceptual workarounds, but I propose an IPC system within BepInEx itself to make this trivial.
I do not know if this exists in BepInEx 6 already - I'm purely a BepInEx 5 user hoping for something cool. If this already exists, neat, please let me know!
The current methods
For the following examples, we will have two projects,
BepInTestA
andBepInTestB
. A will be the producer (holds data that B wants), B will be the consumer (reads data from A). I tested this on BepInEx 5.4.21.0 using the game Bomb Rush Cyberfunk (Unity Mono 2021.3.27.13254651, CLR 4.0.30319.42000, .NET Framework 4.6) - I am aware that this feature, if ever implemented, will likely not land in BepInEx 5.Referencing the other plugin directly
We can reference A from B's project (either the .csproj itself or the assembly) and run it:
However, this has actually output A into B's build artifacts, meaning that it is effectively a hard dependency (BepInEx will detect the assembly of A and load it):
Deleting the assembly from the output, or marking the reference as
Private="false"
, will cause B to not load if A is not present:Referencing the other plugin directly + Chainloader check
One may think to do something like this:
However, this does not work, throwing the same
TypeLoadException
as above. This is because the constructor IL references the type of BepInTestA. Splitting it into where the reference is only evaluated if the plugin is loaded works, albeit janky:This approach technically works to establish a soft dependency, but quickly falls apart when versioning is introduced. Let's remove the field from A without recompiling B:
The field no longer exists, which causes a MissingFieldException:
Separate 'API' assembly
This is the strategy I used in my plugin, Slop Crew, but it suffers a similar flaw. A separate assembly, which we'll call BepInTestA.API, is created:
Then, we implement and register the API in A:
We can then reference the API assembly from B:
Because the same assembly will resolve to the same instance in memory, this can be used for communication effectively. When A is not installed, B does nothing, because the API persists as null and is never registered.
However, if we were to remove and update a new field, it will break:
Worse, this would require every plugin that uses your API to update it before it begins working again, else it can resolve to the wrong version of your assembly.
DontDestroyOnLoad GameObject
This example uses Harmony to traverse the properties, which can theoretically be applied to any strategy here - I just thought this would be a good time to show it.
A proposed solution
I propose that IPC be split into different "contexts" - identified by an ID, likely a string. Your plugin can request a context for a given ID, which allows them to retrieve values, call methods, and receive events.
An ideal solution for flexibility could be something like this:
However, I'm just spitballing here, and I haven't put much thought into it. I also don't know how the delegates/methods would work internally, I'm not sure if you can reflect a delegate to get the types of its arguments. Also, this isn't very type safe, but ¯\_(ツ)_/¯.
Prior art
Beta Was this translation helpful? Give feedback.
All reactions