4 minutes to read
Created by
Calytic
Updated by
Wulf

Duck typing

Duck typing for plugin dependency integration

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

Introduction

Plugins often use functionality defined in other plugins. uMod provides basic integration options to handle optional, required, implicitly typed, and explicitly typed dependencies.

Implement a simple plugin reference that is optional, implicit.

[Optional]
private IPlugin EpicPlugin;

The above reference can be used to integrate with any number of plugins using IPlugin.Call and IPlugin.CallHook which may have considerable overhead in some cases.

Require that two plugins be compiled together by annotating the plugin reference with the [Requires] attribute or declaring the plugin reference with an explicit type definition. Both will will create a "compile-time" constraint that must be fulfilled in order for a plugin to load.

[Requires]
private EpicPlugin EpicPlugin;

A plugin reference annotated as [Optional] can also introduce a compile-time constraint by explicitly declaring the plugin type (not as IPlugin/Plugin). Satisfy the constraint and keep the reference optional by implementing an abstract "duck" type.

[Optional]
private EpicPlugin EpicPlugin;

namespace PluginReference
{
    public abstract class EpicPlugin : Plugin
    {
        /* stubbed methods */
        public abstract void EpicMethod();
    }
}

A duck-typed reference is an optional plugin reference that maintains the performance of a compile-time (or explicitly defined) reference. Further, duck typed references may use the API of the referenced plugin directly without relying on intermediate methods like IPlugin.Call or IPlugin.CallHook. This can be beneficial when integrating with another plugin where the external call occur in a tight loop.

Type promises

uMod saves missing types to ensure that multiple dependent plugins will load regardless of installation order. The compiler will search subsequent compilations for missing types and when found all referenced plugins are automatically loaded or reloaded for maximum availability.

umod/plugins/FirstPlugin.cs

namespace uMod.Plugins
{
    [Info("First Plugin", "uMod", "1.0.0")]
    [Description("Mock plugin")]
    public class FirstPlugin : Plugin
    {
        [Optional]
        private SecondPlugin SecondPlugin;
    }
}

Normally, loading the above plugin will fail because the type uMod.Plugins.SecondPlugin is not defined.

umod/plugins/SecondPlugin.cs

namespace uMod.Plugins
{
    [Info("Second Plugin", "uMod", "1.0.0")]
    [Description("Mock plugin")]
    public class SecondPlugin : Plugin
    {
        public void PrintHelloWorld()
        {
            Logger.Info("Hello world");
        }
    }
}

After loading the plugin above, the first plugin will then compile. However, despite being annotated as [Optional] the SecondPlugin dependency will not work if both plugins are not loaded.

Duck type

The duck type is typically defined as abstract and only stubs methods critical to integration.

umod/plugins/FirstPlugin.cs

namespace uMod.Plugins
{
    [Info("First Plugin", "uMod", "1.0.0")]
    [Description("Mock plugin")]
    public class FirstPlugin : Plugin
    {
        [Optional]
        private SecondPlugin SecondPlugin;
    }
    
    void Loaded()
    {
        SecondPlugin?.PrintHelloWorld();
    }
    
    namespace PluginReference
    {
        public abstract class SecondPlugin : Plugin
        {
            public abstract void PrintHelloWorld();
        }
    }
}

The optional plugin reference will remain optional despite being declared with an explicit type definition.

A duck type determines type compatibility at run-time to only that part of a type's structure which is accessed. A duck type should implement the minimal amount of methods necessary needed for the integration, not the entire API surface of another plugin.