Async / Single Plugin
Within Pillars we try to provide an asynchronous operation, wherever possible, but you may ask yourself "Why use a single plugin and make everything async?" . That's a valid question, so in this article we will look at some behavior of HogWarp and provide some examples, why some decisions were made.
Let's say we have two plugins - PluginA and PluginB:
- Both subscribe to the same native PlayerSystem.PlayerJoinEvent.
- Both have an expensive / long running process
Have a look at the output of the console.
Everything Sync
public class Plugin: IPlugin
{
private readonly Logger _logger = new("PluginA");
public string Author { get; } = "PluginA";
public string Name { get; } = "PluginA";
public Version Version { get; } = new Version(1, 0, 0);
public void PostLoad()
{
Server.PlayerSystem.PlayerJoinEvent += PlayerJoined;
}
private void PlayerJoined(Player id)
{
_logger.Info($"PluginA joined {id}, waiting...");
Task.Delay(2_000).Wait(); // Some expensive stuff
_logger.Info($"PluginA waited");
}
}
As you can see in the output, PluginB will wait for the completion of PluginA (yes even skipping the process entirely in case of an exception), even though PluginB has no idea of the existance of PluginA and that it's own execution may be delayed.
Tip
That's why we recommend a single-plugin solution, where no outside plugins interfere with the execution of Pillars.
So, let's try to make it Async!
Async All The Things
In the following example, we wrap the entire execution in an own Task, makeing it async (at least to some extend.)
public class Plugin: IPlugin
{
private readonly Logger _logger = new("PluginA");
public string Author { get; } = "PluginA";
public string Name { get; } = "PluginA";
public Version Version { get; } = new Version(1, 0, 0);
public void PostLoad()
{
Server.PlayerSystem.PlayerJoinEvent += (player) =>
{
Task.Run(async () => await PlayerJoined(player));
};
}
private async Task PlayerJoined(Player id)
{
_logger.Info($"PluginA joined {id}, waiting...");
await Task.Delay(2_000); // Some expensive stuff
_logger.Info($"PluginA waited");
}
}
As you can see in the output, both plugins start their own execution, independent from one another 👍. Now this is a very valid approach, but depends on that all plugins implement this behavior.
Solution
By design, the Bootstrapper and host is running async, managing it's own threadpool:
// Runs the host asynchronously and continues with exiting the environment with a code of -1
return _host.RunAsync().ContinueWith(_ => Environment.Exit(-1));
Now we can subscribe to native events with the simple async keyword:
HogWarpSdk.Server.PlayerSystem.PlayerJoinEvent += async p => PlayerJoined(p);
And together with event delegates instead of actions, e.g. in the PlayerConnectionEvents , you get somewhat true async behavior:
public TestController(PlayerConnectionEvents pce)
{
pce.OnPlayerConnected += OnPlayerConnect;
}
private async Task OnPlayerConnect(PiPlayer player)
{
_logger.Debug("Player {pid} connected - Connection Id: {cid}", player.Id,
player.ConnectionId);
await Task.Delay(5_000);
_logger.Debug("Sending message");
}
Important
It is highly recommended to avoid using the native events and use the corresponding, correctly wrapped events from Pillars

