Skip to content

Plugin Service Basics

Jorteck edited this page Sep 1, 2021 · 8 revisions

Prerequisites

This guide assumes you have completed the setup of your environment and server, have some basic knowledge in C#, and have created and compiled an empty plugin.

Have a look at the earlier guides if you are not already at this step, or need an extra refresher: https://github.com/nwn-dotnet/Anvil/wiki/Plugin-Development

Plugin Services

What is a Plugin Service/Service Class?

Plugin services are C# classes that compose the logic of your Plugin.

Typically, you would have at least 1 "service class" for each system/behaviour/feature that you develop, and perhaps even more for complex systems.

Here is an example of a very basic service, that simply logs a message when the server starts up:

using NLog;
using NWN.Services;

[ServiceBinding(typeof(ServiceA))]
public class ServiceA
{
  private static readonly Logger Log = LogManager.GetCurrentClassLogger();

  public ServiceA()
  {
    Log.Info("Service A Loaded!");
  }
}

How does this work? The important part here is the "ServiceBinding" attribute:

[ServiceBinding(typeof(ServiceA))] <-- This
public class ServiceA

When a class is flagged with this attribute, it instructs Anvil that this class should be constructed during startup.

This means that our constructor code gets called just after the server loads:

  public ServiceA()
  {
    Log.Info("Service A Loaded!");
  }
nwnxee-server_1  | I [2021/09/01 19:53:15.620] [ServiceA] Service A Loaded!

Now let's make a second service called "ServiceB":

[ServiceBinding(typeof(ServiceB))]
public class ServiceB
{
  private static readonly Logger Log = LogManager.GetCurrentClassLogger();

  public ServiceB()
  {
    Log.Info("Service B Loaded!");
  }
}

Now if I start the server, I will have two log messages! Wait, why are they logging in the wrong order?

nwnxee-server_1  | I [2021/09/01 20:01:57.689] [ServiceB] Service B Loaded!
nwnxee-server_1  | I [2021/09/01 20:01:57.689] [ServiceA] Service A Loaded!

(They might show up in the right order for you! But it is not deterministic!)

Since ServiceB has no knowledge about ServiceA, Anvil doesn't know that it should construct a certain service before another.

To get around this, we define a Service Dependency in ServiceB's constructor:

  public ServiceB()
  {
    Log.Info("Service B Loaded!");
  }

Becomes

  public ServiceB(ServiceA serviceA) <-- This tells Anvil that we need ServiceA before we run this!
  {
    Log.Info("Service B Loaded!");
  }

Now if I run the program, the log messages output in the expected order:

nwnxee-server_1  | I [2021/09/01 20:01:57.689] [ServiceA] Service A Loaded!
nwnxee-server_1  | I [2021/09/01 20:01:57.689] [ServiceB] Service B Loaded!

This is a pattern called Dependency Injection, and is a fundamental core concept to learn to be able to interact with Anvil's own API and services.

Another way to visualise this is via property injection. This is functionally identical to the constructor method above:

  [ServiceBinding(typeof(ServiceB))]
  public class ServiceB : IInitializable // Notifies anvil that we implement an Init() method, and it should be called during startup.
  {
    private static readonly Logger Log = LogManager.GetCurrentClassLogger();

    [Inject] // Create, and Inject Service A before calling...
    private ServiceA ServiceA { get; set; }

    /// ...The Init() function.
    public void Init()
    {
      Log.Info("Service B Loaded!");
    }
  }

As an exercise, try making your own ServiceC class that logs a message before ServiceA and ServiceB!

Clone this wiki locally