forked from Reloaded-Project/Reloaded-II
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Improved: Documentation Related to Controllers & Plugins
- Loading branch information
Showing
7 changed files
with
288 additions
and
262 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,284 @@ | ||
# Introduction | ||
|
||
Sometimes you might want to check the state of another mod, run some code from it and/or instruct another mod to perform a certain action. Reloaded-II provides a mechanism to do this. | ||
|
||
## How Does it Work? | ||
|
||
Implemented using interfaces, the concept is that the mod loader acts as a middleman between mods (it's a DI container!). This middleman allows mods to communicate by passing implementations of interfaces between each other. | ||
|
||
This may be illustrated by the following diagram: | ||
|
||
![Example](./Diagrams/Images/InterModCommunication.png) | ||
|
||
*An example of an external mod communicating with the [Universal File Redirector](https://github.com/Reloaded-Project/reloaded.universal.redirector).* | ||
|
||
- During initialization, Mod A (Redirector) publishes an interface to the Mod Loader. | ||
- During initialization, Mod B (Other Mod) asks the Mod Loader for the interface. | ||
|
||
Communication with the Mod Loader is performed using the `IModLoader` interface, available at your mod's entry point. | ||
|
||
## Usage: Consumer | ||
|
||
### Prerequisites | ||
|
||
Make sure you add the `ModId` of the mod you are consuming instances/interfaces from in the `ModDependencies` field of your `ModConfig.json`. | ||
|
||
```csharp | ||
"ModDependencies": [ | ||
"reloaded.sharedlib.hooks", | ||
"Riders.Controller.Hook" | ||
] | ||
``` | ||
|
||
You can also do this from within the launcher (`Edit Mod`) and copy the new `ModConfig.json` back to your source code. | ||
|
||
*Reloaded will not allow mods to consume types from other mods without explicit opt-in*, this is a safeguard to ensure no unintended types are shared between mods (ensuring isolation). | ||
|
||
### Consuming Dependencies | ||
|
||
Use the `IModLoader` API to get an instance of the interface you want to consume. | ||
This interface should be available at the entry point (`Start`) of the mod you are consuming. | ||
|
||
```csharp | ||
WeakReference<Controller> _controller; | ||
void GetController() | ||
{ | ||
_controller = _loader.GetController<IController>(); | ||
} | ||
``` | ||
|
||
✅ Always check the controller is valid and hasn't been disposed before usage. | ||
```csharp | ||
void DoSomethingWithController() | ||
{ | ||
// If the mod we got IController from is unloaded, `TryGetTarget` will fail. | ||
if (_controller != null && _controller.TryGetTarget(out var controller)) | ||
{ | ||
// Do something with `controller` | ||
} | ||
} | ||
``` | ||
|
||
### Life Cycle & Disposal | ||
|
||
`*Note: If the publisher is not unloadable, you can ignore this section.*` | ||
|
||
Some Reloaded mods support live, real time loading and unloading. | ||
As such, you must be careful with how you consume interfaces from other mods. | ||
|
||
In order to ensure unloading of publishers correctly happens, Reloaded uses "Weak References" (`WeakReference<T>`). | ||
This also means that when mods are unloaded, the Mod Loader can dispose of them cleanly without explicit code in other mods. | ||
|
||
Here are some guidelines on consuming interfaces: | ||
|
||
✅ Storing Weak References on the Heap is OK | ||
```csharp | ||
WeakReference<IController> _reference; | ||
void AcquireController() | ||
{ | ||
_reference = _loader.GetController<IController>(); | ||
} | ||
``` | ||
|
||
✅ Storing referenced objects on the Stack is OK | ||
```csharp | ||
void AcquireController() | ||
{ | ||
IController controller = _loader.GetController<IController>().Target; | ||
// controller is no longer referenced outside of the scope of the method. | ||
} | ||
``` | ||
|
||
❌ Storing referenced objects on the Heap is NOT OK. | ||
```csharp | ||
IController _controller; | ||
void AcquireController() | ||
{ | ||
_controller = _loader.GetController<IController>().Target; | ||
// This prevents the mod loader from being unable to dispose the controller. | ||
} | ||
``` | ||
|
||
## Usage: Publisher | ||
|
||
### Prerequisites | ||
|
||
If you are developing a mod which will share interfaces to be used by other mods, *you must first share the interface with the Mod Loader*. | ||
|
||
This is done by inheriting the interface `IExports` (from `Reloaded.Mod.Interfaces`) in your main mod, with single method `GetTypes` which should return an array of interfaces to be consumed by other mods. | ||
|
||
**Example:** | ||
|
||
```csharp | ||
public class Exports : IExports | ||
{ | ||
public Type[] GetTypes() => new[] { typeof(IController) }; | ||
} | ||
``` | ||
|
||
Sharing forces other mods to load the same instance of the DLL containing the shared interfaces, regardless of whether the version is newer or older. | ||
|
||
**If you do not do this, `GetController` will not work for other mods.** | ||
|
||
### Publishing Dependencies | ||
|
||
For controllers an additional step is required. During initialization (`Start()`), you need to register it with the mod loader. Simply use the `AddOrReplaceController` method of `IModLoader`. | ||
|
||
```csharp | ||
Controller _controller; | ||
|
||
_controller = new Controller(); // Implements IController | ||
_loader.AddOrReplaceController<IController>(this, _controller); | ||
``` | ||
|
||
### Disposing (Publisher) | ||
|
||
The Mod Loader is capable of automatically disposing your dependencies when your mod is unloaded, however you can still manually (if desired) dispose and/or replace your dependency instances with the `RemoveController` method. | ||
|
||
```csharp | ||
void Unload() | ||
{ | ||
_loader.RemoveController<IController>(); | ||
} | ||
``` | ||
|
||
## Recommendations & Limitations | ||
|
||
### Interface DLLs are Immutable | ||
***Once a DLL containing interfaces has been loaded into the process, it cannot be unloaded.*** | ||
|
||
e.g. If you recompile `SomeMod.Interfaces`, it cannot be swapped out without restarting the application/process. | ||
|
||
This is unfortunately a limitation of .NET (Core), as DLLs cannot be unloaded from `AssemblyLoadContext`(s), only whole load contexts. | ||
|
||
**Note:** This does not mean mods using the interface DLLs cannot be unloaded. Said mods can be loaded and unloaded during runtime as usual without any problems. | ||
|
||
### Keep all your Interfaces in a separate library | ||
|
||
***Interface libraries should contain no external, 3rd party references*** (single DLL). | ||
|
||
There are two issues: | ||
|
||
1. If you store your interfaces in the same DLL as your main mod. | ||
- Not Good: Consumers using them will your entire mod compiled and copied to their output! (Not Good!) | ||
|
||
1. Sharing an interface shares its whole assembly, and all of the assemblies' dependencies. | ||
- This can cause mods to unintentionally be forced to use another mod's dependencies. | ||
|
||
Therefore it is recommended that you make a separate library for storing only your shared interfaces. Both the main mod (producer), and other mods (consumer) should reference that same library. | ||
|
||
### Upgrading Interfaces | ||
Changes to existing interfaces will break mods using the interfaces. This should be fairly obvious to anyone who has used a plugin or a plugin-like system before. | ||
|
||
If you want to add more functionality to existing interfaces, either make a new interface or extend the current interface via inheritance. After being published (Mod Released), interfaces should never change (this includes method names). | ||
|
||
Here is an example of a possible setup: | ||
|
||
```csharp | ||
// Recommended setup using inheritance. | ||
// User can acquire `IController` and have the latest version known at compile time. | ||
interface IController : IControllerV3 { /* Dummy */ } | ||
|
||
interface IControllerV3 : IControllerV2 { void DoZ(); } | ||
interface IControllerV2 : IControllerV1 { void DoY(); } | ||
interface IControllerV1 { void DoX(); } | ||
``` | ||
|
||
## Example Mods | ||
|
||
The following examples contain mods that export interfaces to be used by other mods. | ||
|
||
**Universal Mods** | ||
|
||
- [Reloaded Universal File Redirector](https://github.com/Reloaded-Project/Reloaded.Mod.Universal.Redirector) | ||
- Producer: `Reloaded.Universal.Redirector` | ||
- Contract: `Reloaded.Universal.Redirector.Interfaces` | ||
- Consumer(s): `Reloaded.Universal.Monitor`, `Reloaded.Universal.RedirectorMonitor` | ||
|
||
**Application Specific** | ||
|
||
- [Sonic Heroes Controller Hook](https://github.com/Sewer56/Heroes.Controller.Hook.ReloadedII) (Allows other mods to receive/send game inputs.) | ||
- Producer: `Riders.Controller.Hook` | ||
- Contract: `Riders.Controller.Hook.Interfaces` | ||
- Consumer(s): `Riders.Controller.Hook.Custom`, `Riders.Controller.Hook.XInput`, `Riders.Controller.Hook.PostProcess` | ||
|
||
- [Sonic Riders Controller Hook](https://github.com/Sewer56/Riders.Controller.Hook) (Allows other mods to receive/send game inputs.) | ||
- Producer: `Heroes.Controller.Hook` | ||
- Contract: `Heroes.Controller.Hook.Interfaces` | ||
- Consumer(s): `Heroes.Controller.Hook.Custom`, `Heroes.Controller.Hook.XInput`, `Heroes.Controller.Hook.PostProcess` | ||
|
||
**Libraries as Dependencies** | ||
|
||
- [PRS Compressor/Decompressor](https://github.com/Sewer56/Reloaded.SharedLib.Csharp.Prs.ReloadedII) | ||
- [Reloaded.Hooks (Function Hooking/Detour Library)](https://github.com/Sewer56/Reloaded.SharedLib.Hooks.ReloadedII) | ||
|
||
## Extra Features | ||
|
||
### Factories | ||
|
||
Reloaded's API also provides a functionality to create instances implementing a type. These are sometimes also referred to as `"Plugins"` in older docs. | ||
Acquiring new instances of **Plugins** is performed through the use of the `MakeInterfaces` method. | ||
|
||
```csharp | ||
// _loader is an instance of IModLoader | ||
var interfaces = _loader.MakeInterfaces<ISharedInterface>(); | ||
``` | ||
|
||
When you run the above code, Reloaded will iterate through every loaded mod and create an instance of every class implementing `ISharedInterface`. | ||
The result of this operation (all instances) will be returned as an array. | ||
|
||
### Optional Dependencies | ||
|
||
(`OptionalDependencies` in `ModConfig.json`) | ||
|
||
Mods can also use the `OptionalDependencies` field instead of the `ModDependencies` field. | ||
If the mod is an optional dependency, then the preferred option is to acquire Controllers/Plugins after the Mod Loader has finished initializing (all mods are loaded). | ||
|
||
To do this, simply subscribe to `IModLoader`'s `OnModLoaderInitialized`, and try to obtain an interface instance at that point. | ||
|
||
```csharp | ||
IModLoader _loader; | ||
WeakReference<IController> _controller; | ||
void Start(IModLoaderV1 loader) | ||
{ | ||
_loader = (IModLoader)loader; | ||
_loader.OnModLoaderInitialized += Initialized; | ||
} | ||
|
||
// Called by the mod loader after all mods finished loading. | ||
void Initialized() | ||
{ | ||
_controller = _loader.GetController<IController>(); | ||
} | ||
``` | ||
|
||
*Unlike required dependencies, the Mod Loader does not take load order into account with optional dependencies.* | ||
|
||
## How does it *Really* Work? | ||
|
||
Reloaded makes extensive use of [AssemblyLoadContext](https://docs.microsoft.com/en-us/dotnet/core/dependency-loading/understanding-assemblyloadcontext)(s) to provide isolation between mods, such that they may use 3rd library plugins and dependencies of their choice without interfering with other mods. This can make it difficult to share state between a mod and the loader, and even more so between individual mods. | ||
|
||
Reloaded overcomes this challenge by sharing instances of `Assemblies` (DLLs), loosening up isolation around shared code. Mods can nominate which `Assemblies` they wish to share with other mods, and the loader will load them into a shared `AssemblyLoadContext`. When the mod is loaded, the Mod Loader will then force it use the shared `Assembly` instances from the shared `AssemblyLoadContext`. | ||
|
||
For publishers its own shared types are automatically shared with itself, for consumers this is determined using the `ModDependencies` field in `ModConfig.json`. | ||
![Example](./Diagrams/Images/InterModCommunication-Internal.png) | ||
|
||
### How is this arrangement Setup? | ||
|
||
The whole process for sharing an `Assembly` looks something like this: | ||
|
||
1. Gathering Dependencies | ||
1. Mod Loader loads `Redirector` (Publisher). | ||
2. Mod Loader loads all shared Assemblies into Shared `AssemblyLoadContext` | ||
3. Mod Loader unloads `Redirector` (Publisher). | ||
|
||
2. Loading Mods | ||
1. Mod Loader loads `Redirector`, sharing its own `Assemblies` with itself (from 1.b). | ||
2. Mod Loader loads `RedirectorMonitor` (Consumer). | ||
1. `RedirectorMonitor` specified `Redirector` in its `ModDependencies` field. | ||
2. Therefore Mod Loader shares all of `Redirector`'s exports with `RedirectorMonitor`. | ||
|
||
3. Executing Mods | ||
1. Mod Loader rearranges mod load order to account for mod dependencies. | ||
1. `Redirector` code executes. Redirector publishes `IRedirectorController` (from Reloaded.Mod.Interfaces.IRedirector.dll) to Mod Loader. | ||
2. `Monitor` code executes. Monitor obtains `IRedirectorController` from Mod Loader. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
<mxfile modified="2021-12-18T05:17:55.437Z" host="app.diagrams.net" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.113 Safari/537.36" etag="yhV1A5qjzK6I-Y0XT-0L" version="16.0.0" type="device"><diagram id="4yicWO6-0QshSFdzmrWn" name="Page-1">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</diagram></mxfile> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
<mxfile modified="2020-05-11T18:28:15.589Z" host="app.diagrams.net" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.132 Safari/537.36" etag="OdJZM29RYcq97WwWZS41" version="13.1.0" type="google"><diagram id="4yicWO6-0QshSFdzmrWn" name="Page-1">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</diagram></mxfile> | ||
<mxfile modified="2021-12-18T04:59:07.280Z" host="app.diagrams.net" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.113 Safari/537.36" etag="6qqPNH3Je2uBFi1e3nTI" version="16.0.0" type="device"><diagram id="4yicWO6-0QshSFdzmrWn" name="Page-1">7Vptc9M4EP41meE+NGP5Nf7YpoWDKVyhMAefbhRbdsQpViorNOHXn1aWbfmlvRwkpeVgmEF6tF5Ju8+uVgoTb77avhB4vXzNU8ImrpNuJ975xHVRGHrqH0B2FRJ7YQXkgqZGqAWu6VdiQMegG5qSsiMoOWeSrrtgwouCJLKDYSH4bVcs46w76xrnZABcJ5gN0T9pKpcGRY7TDvxOaL40U88CM7DCtbAByiVO+a0FeRcTby44l1VrtZ0TBsar7VJ99/yO0WZhghRynw8Yev7249vr1fXs1Us/k0v38gyfwAd6cXJX75ikygCmy4Vc8pwXmF206JngmyIloNZRvVbmkvO1ApECPxMpd8abeCO5gpZyxcwo2VL5ET6fBm5s+p9034ld0z/fGv26s7M6V0TQFZFE1Fghxe6j3flkd1pFugeaoqju9nWluFzqncEyhxY2Ri/5RiTGXDe0pOvt9sN8+wEtPr1GHpPlSc1eiUVO5D32NwrB5tYMxoEvCFdrEzslIAjDkn7pkhIbbueNXOt+1TAM+C9sCCrFXzDbmKmuBP8MceU670hGlCXUvscYc4kXKvI7XsaM5oVqJ8p8YOCzL0RIqkLr1AysaJpWhCIl/YoXWh94Yc1pIfXmgrNJcD7qinvpDDOR7WQkQZhZOjHYsbT56sSZ+hGKq29N7qq9tbczjPYr2I6lGk0D1FGM0NSPu2p4lpWKOX2PNgv9Did7Pzbk/TjshDxyoqOEvOsfL+hHDes/zZgPn3rMe4eM+bgXmrVXvzfmvanndhSH0yB6qJBHAxe/I4xjFdDTD4XalCgxm74jKRXKWlxMX4LzMpyowssNmTL+2UKoVg6tZ82g0vhHwcB+l3QhsNj9NmCJqnnW0ISyS9Mh44WsMw3wpVzjhBb5+yqLAA+Ut+pCyzf9pr5CfgVc8ZJKyoFPjGSyS8GMMjbnjAu9BC/LMjdJYCop+N/EGknDRRiE7SIUOhujMAOaW1MOuH3ZE2g43ie/hG3enWEGDL6TqUFdVNYHiGf6t22NCvlPY0urPEU1+D155N6SZ1+SjTDrNaaF0qAuEb+Y9FBM8qIukzw0ZBKKx5jkHYBJMUuv3tz89eaV551/TV7M3U26PnEHRFIMwSvYsGZMt0fbdGTgvCNU93pke9kyca6YJDhjRDQfFYuyte/jYmHqJIS4YywMwgBF5OFZeLukklyvsS6ObgU+VI7rMbNhoc3MsRzXP1cPViohNCDEw16Wrbq5HjnmNdlz76qYv71IdvcskmePqkhGw5yEpro+zmmpzKLqJOc9f8RFMror/L6lSEbqZtwJTT86TJEcTuMwtv90Zomm/tjoA9yYRyqbtoz5dUT8sCPCd3rFS/Sjjwh/QJWLrbKcyvdVYfsY6fJT1rWRu0ddG43Wte6xDpHgZy8f4E5w7AIieJIFxPBh/VppnjNclo8xJfxvTpDIC7ppIoz3PEFmR2LKbMAUfWw4l/CU8ijLjZ/y/FA87xAjcEbOD2+MGAd4FhklRjwgRv2qoaxbdFgR3mzgV+WzpHLFKaS5fIGfqaWpv2p6Z7T1GzTBeg6Q6CTDK8p21ecrXvByXT36WiKlPnVAwFlv23nrR5aJG/T/a0Cgdgyo/t276dUWCLQNFHIObVhYANsMlOH+TRY1srXjv0mN26qpLN2M2DV/LVKZvBGpghG6JhxV0wpIGEAas4JSg45Gm8AEzG8wE5xa0K/BJhgA1iEKAzpI22naQK2XWGd2vQgrXJtxk9+tReqxmUaqwIW+iVAAu8HbGxwPYBAyIWwLnTbqZWUYO+sDrPM+4JaTqojehwbNUMOANuwDCPxG0nNayjg7C/cs/LZxVaXdt8aWrcs0CPeNekwlhgbPrQX0Caq7DUttsBs7Rm4QZKF5LH1epYc6InvnhzJeL7sf8nUCytEqR9z/2+r+eTlEs+7Tg+8O8rI7kpbdY6VlNPz91IWnodPkZqPyBbwMKYetnrzho95dOxi7UKHDWH5iOG694rTs9i7+AQ==</diagram></mxfile> |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.