Allowing plugins to be used as dependencies to other plugins (#16)

* fuck everything i did blegh

* better exception tracking for plugins

* Update PluginLoadContext.Load to return existing instances (basically, make plugin api references possible)

* Update docs to help with plugin dependencies

---------

Co-authored-by: UniPM <zoc6x8voc@mozmail.com>
This commit is contained in:
UniPM 2026-05-06 17:38:05 -05:00 committed by GitHub
parent 8e66b2c19e
commit 50ab3597d0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 92 additions and 5 deletions

View file

@ -21,6 +21,13 @@ internal sealed class PluginLoadContext : AssemblyLoadContext
if (assemblyName.Name == typeof(ServerPlugin).Assembly.GetName().Name)
return typeof(ServerPlugin).Assembly;
foreach (var alc in AssemblyLoadContext.All)
{
var existing = alc.Assemblies
.FirstOrDefault(a => a.GetName().Name == assemblyName.Name);
if (existing != null) return existing;
}
string? path = _resolver.ResolveAssemblyToPath(assemblyName);
if (path != null)
return LoadFromAssemblyPath(path);

View file

@ -81,7 +81,13 @@ internal sealed class PluginLoader
var assembly = context.LoadFromAssemblyPath(Path.GetFullPath(dllPath));
int found = 0;
foreach (var type in assembly.GetTypes())
Type[] types;
try {
types = assembly.GetTypes();
} catch (ReflectionTypeLoadException ex) {
types = ex.Types.Where(t => t != null).ToArray()!;
}
foreach (var type in types)
{
if (type.IsAbstract || type.IsInterface)
continue;
@ -173,9 +179,10 @@ internal sealed class PluginLoader
MethodInfo? method = type.GetMethod(camelName, DeclaredPublic, Type.EmptyTypes)
?? type.GetMethod(pascalName, DeclaredPublic, Type.EmptyTypes);
if (method != null)
{
method.Invoke(plugin, null);
try {
method?.Invoke(plugin, null);
} catch (TargetInvocationException ex) {
throw ex.InnerException ?? ex;
}
}

View file

@ -264,6 +264,79 @@ When a plugin folder is made, make sure the main dll matches the name of the fol
You can also avoid this by using [Fody Costura](https://github.com/Fody/Costura) and bundle the dependencies into your DLL.
### Plugin Dependencies
In certain cases, like if you wanted to implement an addon to an already existing plugin, you can put the plugin in your .csproj and use functions from that plugin!
This is much like the Dependencies section above, but you shouldn't use Fody Costura for this.
Example of implementing external plugin functions:
```xml
<ItemGroup>
<Reference Include="CoolPlugin">
<HintPath>.\CoolPlugin\CoolPlugin.dll</HintPath>
</Reference>
</ItemGroup>
```
```csharp
public class PluginAddon : ServerPlugin
{
public override void onEnable() {
CoolPlugin.CoolPluginAPI.CoolFunction(arg1, arg2, arg3);
}
}
```
```csharp
public class CoolPluginAPI
{
// example API function
public static void CoolFunction(arg1, arg2, arg3) {
Console.WriteLine("This is a cool function!");
}
}
```
If the API wants to access instance variables (like a list from the main ServerPlugin), you will have to make sure the instance is already initialized.
This is because the plugin referencing the API might load before the actual plugin with the API. The API will be usable, but not the instance.
You can do this by initializing the instance in the API if its not already initialized.
```csharp
public class CoolPlugin : ServerPlugin
{
private static CoolPlugin? _instance;
public static CoolPlugin? getInstance() => _instance;
internal static void setInstance(CoolPlugin inst) => _instance = inst;
// example instance variable
internal List<int> coolList = [];
public override void onEnable() {
// initializes the instance if it isn't already initialized
instance ??= this;
}
}
```
```csharp
public class CoolPluginAPI
{
// guarantee an instance when the API is used
internal static CoolPlugin? getCoolPlugin() {
if (CoolPlugin.getInstance() == null) CoolPlugin.setInstance(new CoolPlugin());
return CoolPlugin.getInstance();
}
public static void CoolFunction(arg1, arg2, arg3) {
getCoolPlugin()?.coolList.Add(404);
}
}
```
### Fody Costura
Fody Costura isnt very well documented, but heres the general usage guide that has worked for users:
@ -300,4 +373,4 @@ Fody Costura isnt very well documented, but heres the general usage guide that h
Build
<p>After you've done all this, it should build and put all dependencies into one DLL in your output folder.</p>
</li>
</ol>
</ol>