6 minutes to read
Created by
Calytic
Updated by
Wulf

Integration

Creating integration points between plugins and their dependencies

This guide is for uMod, not Oxide.
Join our discord for the latest updates and the latest news! Join discord

Introduction

A dependency is a plugin that another plugin requires in order to perform a particular function.

It is strongly recommended to separate functionality into different plugins, collaborate with other developers, and search for other plugins that might contain needed functionality as opposed to rewriting it every time.

Modularity and interopability

It is recommend to use SOLID object oriented design principles when implementing plugins to maximize flexibility. Using these principles correctly means that functionality is refactored into smaller more digestible pieces of code. Ideally each class or method should have the least amount of code and responsibilities possible.

This approach is beneficial because it...

  1. Prevents code from becoming spaghetti (or a big mess).
  2. Ensures that error messages remain as useful as possible.
  3. Code remains easy to understand and maintain.
  4. Larger API surface for potential integrations.

As systems become more complex, a good rule of thumb is to refactor any code after it is used in more than 2 places. To learn more about developing SOLID applications, please consider using software design patterns.

Optional

An optional dependency references a plugin that is not required but can provide additional functionality if it is loaded.

The property name must match the name of the plugin being referenced unless named explicitly by the Optional attribute.

Implicit

Implement an implicit dependency using one of the following global abstractions (uMod.Common.IPlugin or Plugin).

[Optional]
private IPlugin EpicStuff;

void Loaded()
{
    if (EpicStuff != null)
    {
        EpicStuff.Call("SomeMethod");
    }
}

By default the EpicStuff property will be null. When another plugin named EpicStuff is loaded then the EpicStuff property is automatically assigned with a reference to that plugin. Conversely, when EpicStuff is unloaded the property will be set back to null.

Explicit

Implement an explicit dependency by referencing the class of the plugin directly.

[Optional]
private EpicStuff EpicStuff;

Though the dependency is marked optional, because it is explicitly defined the dependent plugin will still fail to compile if the EpicStuff plugin is not loaded. Use duck typing to implement optional dependencies that are also explicitly typed.

Requires

A required dependency ensures that a plugin will not load unless the dependency is loaded.

The property name must match the name of the plugin being referenced unless named explicitly by the Requires attribute.

Implicit

Implement an implicit requirement using one of the following global abstractions (uMod.Common.IPlugin or Plugin).

[Requires]
private IPlugin EpicStuff;

The dependent plugin will fail to load if the EpicStuff plugin is not also loaded.

Explicit

Implement an explicit requirement by referencing the class of the plugin directly.

[Requires]
private EpicStuff EpicStuff;

The dependent plugin will fail to compile if the EpicStuff plugin is not also loaded.

Named dependency

A plugin dependency field must have the same name as the plugin class name (e.g. "HelpText"). However, the property may have a different name if the plugin class name is specified by the plugin reference attribute.

[Optional("HelpText")]
private IPlugin HelpPlugin;

Interchangable dependency

The optional attribute may specify multiple dependency names when a plugin needs to depend on multiple plugins that serve the same purpose (or have the same API).

[Optional("HelpText", "AdvancedHelpText")]
private IPlugin HelpPlugin;

Incremental compilation

The uMod compiler is an "incremental compiler." An incremental compiler can compile and load plugins without interupting the operation of other currently loaded plugins. Each plugin is compiled in isolation because it only compiles the changes of a known set of plugins and overloads (in-memory) any previous plugin assemblies.

While an incremental compiler is important to maintain availability, it also makes managing dependencies more difficult and, in some cases, adversely affects the performance of plugin integrations.

For those familiar with C# application development, when a reference is added to a project then the project has direct access to all of the classes and methods within the referenced assembly. Further, classes within the same assembly also have direct access to eachother.

Implementing implicitly typed dependencies creates a "loose joint" by referencing global abstractions like uMod.Common.IPlugin interface.

Implementing explicitly typed dependencies creates a "hard joint", binding both plugins together directly (similar to C# project references). Explicit dependencies require that both plugins must be compiled together, and this is handled automatically by the incrmental compiler.